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, Shield, orphan-checker — vede lo stesso identico insieme di file. Non esistono scorciatoie.
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:
.mde.mdx - I link simbolici vengano sempre saltati
La funzione accetta tre argomenti:
docs_root— percorso assoluto alla directory di documentazioneconfig— configurazione Zenzic caricata (fornisceexcluded_dirs)exclusion_manager— ilLayeredExclusionManagerper la valutazione completa a 4 livelli
# Uso interno — tutti i moduli seguono questo contratto
for md_file in iter_markdown_sources(docs_root, config, exclusion_manager):
# md_file e un Path assoluto, ordinato, non-symlink, .md o .mdx
content = md_file.read_text(encoding="utf-8")
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.
L1 — Guardrail di Sistema
Priorità assoluta. Immutabili. Non negoziabili.
I Guardrail di Sistema 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.
| Directory | Motivazione |
|---|---|
.git, .github | VCS e CI/CD |
.venv, node_modules | Ambienti virtuali e gestori pacchetti |
.nox, .tox | Runner di test |
.pytest_cache, .mypy_cache, .ruff_cache | Cache degli strumenti |
__pycache__ | Cache bytecode Python |
.docusaurus, .cache | Cache del motore di build |
.hypothesis, .temp | Directory 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
Il livello 2 gestisce due forze opposte:
Inclusioni forzate (included_dirs, included_file_patterns) — sovrascrivono le esclusioni VCS e le esclusioni di configurazione, ma mai i Guardrail di Sistema. Questo permette di forzare la scansione di directory o file che sarebbero altrimenti ignorati dal .gitignore.
VCS ignore (respect_vcs_ignore = true) — quando attivato, Zenzic legge i file .gitignore dalla radice del repository e dalla directory docs. I file corrispondenti vengono esclusi da tutti i controlli. Disabilitato per default (principio Zero-Config).
L'ordine di precedenza tra questi due sotto-livelli e chiaro: le inclusioni forzate vincono sempre sulle esclusioni VCS.
# zenzic.toml
respect_vcs_ignore = true
included_dirs = ["generated-docs"]
included_file_patterns = ["api.generated.md"]
In questo esempio, anche se generated-docs/ e nel .gitignore, Zenzic lo scansionera comunque.
L3 — Configurazione
Le esclusioni dichiarate in zenzic.toml o [tool.zenzic] in pyproject.toml:
excluded_dirs— directory da escludere (additive ai Guardrail)excluded_file_patterns— pattern glob applicati ai nomi dei file
Queste esclusioni vengono valutate dopo le inclusioni forzate e il VCS. Se un file e stato forzatamente incluso al livello L2, le esclusioni L3 non lo toccano.
L4 — CLI
Flag a riga di comando per override temporanei:
--exclude-dir DIR— esclude una directory aggiuntiva (ripetibile)--include-dir DIR— forza l'inclusione di una directory (ripetibile, non sovrasta i Guardrail di Sistema)
I flag CLI vengono valutati con la stessa logica degli altri livelli: --include-dir vince su --exclude-dir per la stessa directory, ma nessuno dei due puo sovrastare L1.
Flusso di valutazione per le directory
Quando LayeredExclusionManager.should_exclude_dir viene chiamato per una directory, il flusso e il seguente:
- L1 — Guardrail di Sistema: la directory e in
SYSTEM_EXCLUDED_DIRS? Se si, escludi (immutabile) - L2 — Inclusioni forzate (config): la directory e in
included_dirs? Se si, includi - L4 — CLI
--exclude-dir: la directory e stata esclusa via CLI? Se si, escludi - L4 — CLI
--include-dir: la directory e stata inclusa via CLI? Se si, includi - L2 VCS — gitignore: con
respect_vcs_ignore = true, la directory corrisponde a un pattern.gitignore? Se si, escludi - L3 — Config
excluded_dirs: la directory e nella lista di configurazione? Se si, escludi - Default: includi
Flusso di valutazione per i file
Il flusso per should_exclude_file e analogo ma piu granulare:
- L1 — Guardrail di Sistema: un componente del percorso del file e in
SYSTEM_EXCLUDED_DIRS? Se si, escludi - L2 —
included_file_patterns: il nome del file corrisponde a un pattern di inclusione forzata? Se si, includi - L2 —
included_dirs: un componente del percorso e inincluded_dirs? Se si, includi - L4 — CLI
--exclude-dir: un componente del percorso e nelle esclusioni CLI? Se si, escludi - L4 — CLI
--include-dir: un componente del percorso e nelle inclusioni CLI? Se si, includi - L2 VCS — gitignore: il percorso relativo corrisponde a un pattern
.gitignore? Se si, escludi - L3 —
excluded_file_patterns: il nome del file corrisponde a un pattern di esclusione? Se si, escludi - L3 —
excluded_dirs: un componente del percorso e nelle directory escluse dalla configurazione? Se si, escludi - Default: includi
Flusso di lavoro respect_vcs_ignore
Quando respect_vcs_ignore = true, il LayeredExclusionManager costruisce un VCSIgnoreParser unificato dai file .gitignore:
- Legge
.gitignoredalla radice del repository (se esiste) - Legge
.gitignoredalla directory docs (se esiste e diversa dalla radice) - Unisce le regole di tutti i parser in un singolo parser
- Pre-compila le espressioni regolari per performance ottimali
Il parser segue la specifica gitignore completa:
- L'ultima regola corrispondente vince (semantica last-match-wins)
!nega una regola (re-include un file precedentemente escluso)*corrisponde a tutto tranne/**corrisponde a tutto incluso/- La
/finale limita la regola alle sole directory - La
/iniziale ancora la regola alla directory base
I percorsi contenenti .. vengono sempre trattati come non corrispondenti (protezione di sicurezza).
Filosofia Porto Sicuro
Il sistema di esclusione e progettato seguendo la filosofia del Porto Sicuro:
- Nessuna sorpresa: senza configurazione, Zenzic funziona con valori predefiniti ragionevoli
- Nessun file critico esposto: i Guardrail di Sistema 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.
Walk dei file non-Markdown
Per i file non-Markdown (asset, immagini, configurazione), Zenzic fornisce walk_files: un iteratore basato su os.walk con pruning in-place delle directory.
A differenza di Path.rglob("*"), walk_files pota gli alberi esclusi (ad esempio .nox/, node_modules/) senza mai entrare in essi. Questo e cruciale per le performance in repository con migliaia di dipendenze.
Il pruning viene pilotato sia dal LayeredExclusionManager (via should_exclude_dir) sia da un set aggiuntivo di directory hard-prune (usato da find_unused_assets per excluded_asset_dirs).