Passa al contenuto principale

Discovery Universale e Esclusione a Livelli

Zenzic adotta un principio architetturale preciso: un solo punto d'ingresso per la scoperta dei file. Ogni modulo del sistema — scanner, validator, credential scanner, orphan-checker — vede lo stesso identico insieme di file. Non esistono scorciatoie.


L'Autorità della Radice

Prima che Zenzic possa scoprire, escludere o scansionare un singolo file, deve rispondere a una domanda: dove inizia questo progetto?

Zenzic è uno strumento con perimetro di Workspace. Non analizza directory arbitrarie; analizza progetti definiti. Per stabilire il confine del progetto, Zenzic esegue una traversata verso l'alto (upward traversal) a partire dal percorso target, cercando un marcatore di radice autorizzato.

Marcatori di Radice del Repository

Due marcatori sono autorizzati (il primo trovato vince):

MarcatoreDescrizione
.git/Marcatore VCS universale — presente in qualsiasi repository tracciato da Git
.zenzic.tomlIl file di configurazione di Zenzic — il contratto esplicito di governance

Entrambi sono intenzionalmente engine-neutral: mkdocs.yml, docusaurus.config.ts e file analoghi dei motori di build non sono marcatori di radice. Il Core deve rimanere indipendente da qualsiasi framework documentale specifico.

Perché il Marcatore di Radice è Obbligatorio

Senza un marcatore di radice (VCS o configurazione), Zenzic non può stabilire il Perimetro di Sovranità del progetto. Il blocco è necessario per quattro motivi indipendenti:

  • Risolvere i percorsi relativi — ogni segnalazione è espressa come percorso relativo alla radice. Senza un'ancora fissa, la posizione dei file è ambigua e non riproducibile tra macchine diverse.
  • Mappare il workspace nel VSM — Zenzic costruisce sempre una Virtual Site Map, indipendentemente dall'adapter. In modalità engine (Docusaurus, MkDocs), il VSM include ghost route, trasformazioni di slug e pagine virtuali. In modalità Standalone, il VSM è una proiezione 1:1 del filesystem. In entrambi i casi, la radice definisce il punto zero per la canonizzazione di ogni riferimento interno — così che index.md alla radice sia distinguibile senza ambiguità da un index.md in una sottocartella.
  • Applicare le directory_policies — i contratti di governance fanno corrispondere percorsi tramite pattern glob relativi alla radice del workspace. Senza una radice definita, le regole di esenzione sono non deterministiche e non possono essere applicate in modo coerente.
  • Prevenire il Massive Indexing — senza un confine esplicito, il motore potrebbe scansionare accidentalmente l'intero filesystem host, producendo dati incoerenti e rischiando la fuga di informazioni.

L'assenza di un marcatore di radice produce questo errore:

ERROR: Could not locate repo root: no .git directory or .zenzic.toml found in any
ancestor of /percorso/al/target. Run Zenzic from inside the repository.

Non si tratta di un errore di configurazione. È una garanzia di sicurezza: il Quality Gate interrompe una scansione fuori perimetro prima che inizi.

Opzioni di Risoluzione

Zenzic risolve la root del repository risalendo dalla directory di lavoro corrente finché non trova un root marker (.zenzic.toml o .git/). Tre condizioni soddisfano questo requisito:

  • Progetto Zenzic: un file .zenzic.toml nella root della directory target (creato da zenzic init).
  • Repository Git: una directory .git/ in qualsiasi punto dell'albero degli antenati.
  • Invocazione annidata: esecuzione dall'interno di un progetto esistente che contiene già uno dei marker.

Se nessuna di queste condizioni è soddisfatta, Zenzic rifiuta l'invocazione con un errore esplicito.

Vedi Per iniziare per il workflow di setup con zenzic init.


Il punto d'ingresso unico: iter_markdown_sources

La funzione iter_markdown_sources e l'unica porta d'accesso autorizzata per iterare sui file sorgente Markdown. Nessun modulo interno chiama rglob, os.walk o Path.iterdir direttamente per cercare file .md o .mdx.

Questo garantisce che:

  • Le esclusioni vengano applicate universalmente — non esistono moduli "fuori regola"
  • L'ordine dei file sia deterministico (ordinamento imposto da walk_files)
  • Le estensioni riconosciute come documentazione sorgente siano fissate: .md e .mdx
  • I link simbolici vengano sempre saltati

Il vantaggio e architetturale: quando una directory viene esclusa, viene esclusa ovunque — scanner, validator, credential scanner e orphan-checker vedono esattamente lo stesso insieme di file. Non esiste il rischio che un modulo "dimentichi" di applicare una regola di esclusione.

La funzione accetta tre argomenti:

  1. docs_root — percorso assoluto alla directory di documentazione
  2. config — configurazione Zenzic caricata (fornisce excluded_dirs)
  3. exclusion_manager — il LayeredExclusionManager per la valutazione completa a 4 livelli

Gerarchia di esclusione a quattro livelli

Il LayeredExclusionManager orchestra le decisioni di esclusione attraverso quattro livelli. L'ordine di valutazione segue una logica precisa: il primo livello che corrisponde vince.

I Quattro Livelli

LivelloNomeSorgenteModificabile?
L1System GuardrailsHardcoded in SYSTEM_EXCLUDED_DIRSNo
L2Forced Inclusions + VCSincluded_dirs, included_file_patterns, .gitignoreSì (config)
L3Config Exclusionsexcluded_dirs, excluded_file_patterns in .zenzic.toml o [tool.zenzic] in pyproject.tomlSì (config)
L4CLI OverridesFlag --exclude-dir, --include-dirSì (per-run)

L1 — System Guardrails

Priorità assoluta. Immutabili. Non negoziabili.

I System Guardrails sono directory che Zenzic ignora sempre, indipendentemente da qualsiasi configurazione utente. Non possono essere rimossi, ne sovrascritti da inclusioni forzate, ne superati da flag CLI.

DirectoryMotivazione
.git, .githubVCS e CI/CD
.venv, node_modulesAmbienti virtuali e gestori pacchetti
.nox, .toxRunner di test
.pytest_cache, .mypy_cache, .ruff_cacheCache degli strumenti
__pycache__Cache bytecode Python
.docusaurus, .cacheCache del motore di build
.hypothesis, .tempDirectory temporanee

Queste directory vengono unite a excluded_dirs in modo additivo durante model_post_init del modello di configurazione. La logica è: le voci dell'utente si aggiungono ai Guardrail, non li sostituiscono.

L2 — Inclusioni Forzate + VCS

Le inclusioni forzate hanno la precedenza su tutti gli strati di esclusione eccetto L1. Servono due scopi:

Inclusioni forzate a livello di config (included_dirs, included_file_patterns) re-includono file o directory che altrimenti verrebbero esclusi da pattern VCS o esclusioni di config. Un caso d'uso tipico è la documentazione API generata dal build elencata in .gitignore ma che richiede linting.

Esclusione VCS (pattern .gitignore) viene attivata impostando respect_vcs_ignore = true. Quando attiva, Zenzic legge i file .gitignore sia dalla root del repository che dalla directory docs. I file che corrispondono ai pattern VCS vengono esclusi — ma le inclusioni forzate hanno la precedenza sulle esclusioni VCS.

L3 — Esclusioni Config

Esclusioni a livello di config da .zenzic.toml o pyproject.toml:

  • excluded_dirs — nomi di directory inside docs/ da saltare
  • excluded_file_patterns — pattern glob di nomi file da saltare

Sono additive a L1 ma subordinate alle inclusioni forzate L2.

L4 — Override CLI

Gli override per singola esecuzione tramite i flag --exclude-dir e --include-dir estendono o restringono il perimetro di scansione per una singola invocazione senza modificare la configurazione persistente. CLI --include-dir non può sovrascrivere i System Guardrails — tentare di includere .git o .venv tramite CLI viene ignorato silenziosamente.

Per la sintassi dei campi e gli esempi, vedi Riferimento alla Configurazione.


respect_vcs_ignore — Semantica di Esclusione VCS

respect_vcs_ignore controlla se Zenzic applica i pattern .gitignore come strato di esclusione aggiuntivo. Il suo default è false, implementando il principio Zero-Config senza sorprese: il perimetro di scansione è esattamente il filesystem visibile allo sviluppatore, senza esclusioni implicite nascoste.

Quando abilitato, Zenzic carica i pattern .gitignore da due posizioni: la root del repository e la directory docs (se esiste un .gitignore separato lì). Il parser VCS ignore implementa la specifica gitignore completa, incluse la negazione (!), l'ancoraggio dei path e i glob wildcard.

Le inclusioni forzate (included_dirs, included_file_patterns) hanno sempre la precedenza sulle esclusioni VCS. Questo rende sicuro abilitare respect_vcs_ignore in progetti dove la documentazione generata dal build è in .gitignore ma richiede comunque linting.


Exclusion Zone Philosophy

Il sistema di esclusione e progettato seguendo la Exclusion Zone Philosophy:

  • Nessuna sorpresa: senza configurazione, Zenzic funziona con valori predefiniti ragionevoli
  • Nessun file critico esposto: i System Guardrails proteggono directory che non contengono mai documentazione utile (.git, .venv, ecc.)
  • Inclusione esplicita: l'utente deve dichiarare esplicitamente cosa vuole includere di non standard
  • Trasparenza: il flusso di esclusione e deterministico e documentato — nessuna magia nascosta

Il LayeredExclusionManager viene costruito una sola volta dalla CLI e passato lungo tutta la pipeline. Ogni modulo riceve la stessa istanza, garantendo coerenza totale nell'insieme dei file analizzati.


Note sulle Prestazioni

  • La directory pruning viene applicata durante os.walk(), non dopo. Gli alberi esclusi (ad esempio node_modules/ con migliaia di file) non vengono mai attraversati.
  • Per i file non-Markdown, walk_files() usa lo stesso motore os.walk() con pruning in-place. A differenza di Path.rglob("*"), non entra mai negli alberi esclusi.
  • I pattern dei file sono precompilati in re.Pattern al momento della costruzione di LayeredExclusionManager usando fnmatch.translate().
  • I pattern VCS senza regole di negazione usano una fast path con regex combinata — tutte le regole positive vengono fuse in una singola regex compilata per matching O(1) per percorso.
  • LayeredExclusionManager viene costruito una sola volta per invocazione CLI e passato per riferimento lungo l'intera pipeline.
  • Un set separato di hard-prune viene usato da find_unused_assets per excluded_asset_dirs.

Multi-Root Discovery

docs_dir è la radice canonica dei sorgenti, ma gli static-site generator moderni gestiscono regolarmente alberi di contenuto che vivono fuori da docs/. Il caso di scuola è la directory blog/ di Docusaurus: viene materializzata in URL reali dalla build, ma una scansione Zenzic pre-current series non avrebbe mai visto i file al suo interno. Il Virtual Site Map ingeriva solo i file sotto docs_root, perciò i link rotti dentro (o verso) i post del blog sfuggivano a zenzic check all e affioravano solo a valle, quando docusaurus build falliva. Chiamiamo questa modalità di fallimento VSM Blindness.

Multi-Root Discovery cura la cecità permettendo all'adapter attivo di dichiarare ulteriori radici di contenuto — ciascuna con un percorso fisico, un prefisso URL e un'etichetta diagnostica. Quando vengono dichiarate radici extra, tutti gli stadi della pipeline trattano quei file come contenuto di prima classe accanto all'albero primario docs/.

Auto-discovery senza subprocess

Le implementazioni dell'adapter onorano l'invariante Zero Subprocess. L'adapter Docusaurus rileva il plugin blog in due passaggi, entrambi puro parsing:

  1. Parse statico via regex di docusaurus.config.{ts,js,mjs,cjs} per un blocco blog: { path, routeBasePath } (o un sentinella blog: false per disattivare).
  2. Convenzione di fallback: quando il config non fissa il plugin blog e <repo>/blog esiste su disco, si assume il layout di default (path = 'blog', routeBasePath = 'blog').

Nessun processo Node.js viene mai avviato — il config viene letto come dato, non eseguito come codice. Pillar 2 (Engine Sovereignty) preservato.

Invariante di tracciabilità

Ogni voce nel VSM, incluse quelle prodotte da una radice extra, porta un Route.source che risale a un file reale su disco. Una rotta senza origine fisica sarebbe un validatore che urla error senza mai dire dove.

Invariante di Reverse-Mapping e Virtual Routes

Il Multi-Root Discovery (current series) ha risolto il VSM Blindness per i file fisici fuori da docs/. La current series estende la garanzia alle pagine generate dal motore: Docusaurus produce URL — pagine di tag, indici paginati, profili autore — che non hanno un corrispondente fisico in Markdown. Queste rotte esistono solo nell'output del build, mai su disco.

L'invariante garantisce che ogni URL generato dal motore risalga ad almeno un file sorgente. Gli adapter aderiscono implementando il metodo opzionale get_virtual_routes(), e le virtual routes partecipano alla collision detection alla pari delle rotte fisiche.

Matrice di supporto motori

MotoreImplementa get_extra_content_rootsStato
DocusaurusAuto-rileva blog/ da config o convenzione.
MkDocs (Material)NoAdesione rinviata fino all'ingresso del plugin material/blog nello scope v0.8.x
ZensicalNoArchitettura identica — abilitato quando arriva un plugin out-of-tree.
StandaloneNoNessun plugin; docs_root è l'intera superficie di contenuto.

inspect routes — Esportazione della Site Map

Il comando inspect routes espone la VSM ai consumer esterni come struttura JSON deterministica. Ogni record contiene quattro campi: url, kind (physical o virtual), source_files (un array ordinato di path relativi al repo che causano l'esistenza dell'URL) e un digest — un'impronta SHA-256 derivata dall'URL e dai suoi file sorgente.

Il flag --kind restringe l'output a physical, virtual o all (default). Il JSON viene scritto esclusivamente su stdout; la diagnostica va su stderr.

Questo design rende la VSM componibile: strumenti esterni, dashboard CI/CD o tooling specializzato possono consumare la site map senza eseguire lo scanner completo.

Sintassi CLI: vedi Riferimento CLI — inspect routes.