Architettura CLI
La CLI è organizzata come un package (src/zenzic/cli/) invece di un singolo modulo.
Ogni file gestisce un dominio di responsabilità.
Mappa dei Moduli
| Modulo | Responsabilità |
|---|---|
_shared.py | Singleton console, singleton _ui, configure_console() e tutte le utility cross-comando (_build_exclusion_manager, _output_json_findings, _render_link_error, ecc.) |
_check.py | Sub-app Typer check_app + sette comandi check *; helper privati re-esportati da _governance.py, _target_resolver.py e _command_setup.py |
_command_setup.py | Factory setup_command() — consolida la scoperta del repo root, il caricamento config, la risoluzione del target e la costruzione dell'exclusion manager usati da tutti i comandi check |
_clean.py | Sub-app Typer clean_app + comando clean assets |
_config_explain.py | Comando explain + surface di introspezione della genealogia config e delle regole |
_governance.py | Sub-app Typer config_app + comandi del profilo di governance + helper per i filtri per-file-ignore e directory-policy (_apply_per_file_ignores, _apply_directory_policies) |
_guard.py | Sub-app Typer guard_app + comandi scan / init per il fast secret guard |
_inspect.py | Sub-app Typer inspect_app + comandi capabilities, codes e routes |
_lab.py | Comando lab + showcase interattivo di scenari |
_metadata.py | Single source of truth per i pannelli help root, il raggruppamento dei comandi e il testo di short help |
_standalone.py | Comandi score, diff e init + i loro helper privati |
_target_resolver.py | _resolve_target() e _apply_target() — helper per la ricerca del path e il patching della config, condivisi dai comandi check e dal comando lab |
__init__.py | Surface di re-export pubblica consumata da main.py — non aggiungere logica qui |
main.py è la factory di registrazione Typer unificata. I nuovi comandi top-level e le sub-app
devono essere registrati lì, e i metadati dell'help root devono rimanere allineati con _metadata.py.
Il Visual State Manager
_shared.py è il solo proprietario di tutto lo stato della console e UI. Questa è la regola
architetturale più critica nel layer CLI:
DIVIETO: Nessun modulo comando può istanziare
Console()o una classe UI custom direttamente. Tutto l'output deve passare attraversoget_ui()eget_console()da_shared.py.
# ✅ Corretto — in qualsiasi modulo _check.py / _clean.py / _standalone.py
from . import _shared
_shared.get_ui().print_header(__version__)
_shared.get_console().print("output")
# ❌ VIETATO — non farlo mai in un modulo comando
from rich.console import Console
from mypackage.ui import LegacyInterfaceV1
console = Console(...) # rompe lo stato condiviso
ui = LegacyInterfaceV1(console) # crea un'istanza orfana
Questa regola esiste perché configure_console() sostituisce i singleton a livello di modulo
console e _ui quando viene passato --no-color o --force-color. Qualsiasi istanza
Console o UI creata localmente sarà congelata allo stato pre-flag e ignorerà la preferenza
colore dell'utente.
Il parametro force_terminal della Console a livello di modulo è sempre None
(auto-detect via sys.stdout.isatty()), mai False. Impostare force_terminal=False è
un bug silenzioso che rimuove tutto lo styling ANSI anche nei terminali interattivi.
Convenzioni output UI:
- Usa sempre
ZenzicPalette.DIMper testo dim/secondario — mai il tag Rich raw[dim]. - Spaziatura verticale: compatta (stile Ruff). Nessuna riga vuota tra le singole righe del footer.
Usa i separatori
Rule()solo per dividere le sezioni principali del report. - I nuovi simboli devono essere aggiunti a
_EMOJIinzenzic/core/ui.pyprima dell'uso — mai Unicode literal inline.
Aggiungere un Comando a una Sub-App Esistente
# src/zenzic/cli/_check.py (esempio: aggiunta di "check metadata")
@check_app.command(name="metadata")
def check_metadata(path: Path = ...) -> None:
...
Non sono richieste modifiche a __init__.py, main.py, o _metadata.py — la sub-app Typer
esistente gestisce già questa surface di comandi.
Aggiungere una Nuova Sub-App Top-Level
- Crea
src/zenzic/cli/_myfeature.pyconmyfeature_app = typer.Typer(...)e i tuoi comandi. - Esporta
myfeature_appdasrc/zenzic/cli/__init__.py. - Registra in
src/zenzic/main.py:app.add_typer(myfeature_app, name="myfeature", rich_help_panel="..."). - Aggiungi una entry
CommandMeta(...)insrc/zenzic/cli/_metadata.pycosì i pannelli help root e lo short help rimangono autorevoli. - Se la sub-app usa
no_args_is_help=True, aggiungi"myfeature"al frozenset_SUBAPPS_WITH_MENUincli_main()così il banner Zenzic appare quando la sub-app viene invocata senza argomenti.
Codici di Uscita
La CLI termina con uno di quattro codici. Questi sono frozen — non aggiungere nuovi codici di uscita senza una decisione architetturale esplicita:
| Codice | Significato |
|---|---|
0 | Tutti i check superati |
1 | Problemi di qualità trovati |
2 | SECURITY — credenziale esposta rilevata |
3 | SECURITY — traversal del path di sistema rilevato |
PLUGIN_FORBIDDEN_EXITS garantisce che gli adapter di terze parti non possano emettere codici
di uscita al di fuori di questo insieme. Vedi il riferimento Adapter API per
il contratto completo.