Passa al contenuto principale

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

ModuloResponsabilità
_shared.pySingleton console, singleton _ui, configure_console() e tutte le utility cross-comando (_build_exclusion_manager, _output_json_findings, _render_link_error, ecc.)
_check.pySub-app Typer check_app + sette comandi check *; helper privati re-esportati da _governance.py, _target_resolver.py e _command_setup.py
_command_setup.pyFactory 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.pySub-app Typer clean_app + comando clean assets
_config_explain.pyComando explain + surface di introspezione della genealogia config e delle regole
_governance.pySub-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.pySub-app Typer guard_app + comandi scan / init per il fast secret guard
_inspect.pySub-app Typer inspect_app + comandi capabilities, codes e routes
_lab.pyComando lab + showcase interattivo di scenari
_metadata.pySingle source of truth per i pannelli help root, il raggruppamento dei comandi e il testo di short help
_standalone.pyComandi 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__.pySurface di re-export pubblica consumata da main.pynon 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 attraverso get_ui() e get_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.DIM per 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 _EMOJI in zenzic/core/ui.py prima 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

  1. Crea src/zenzic/cli/_myfeature.py con myfeature_app = typer.Typer(...) e i tuoi comandi.
  2. Esporta myfeature_app da src/zenzic/cli/__init__.py.
  3. Registra in src/zenzic/main.py: app.add_typer(myfeature_app, name="myfeature", rich_help_panel="...").
  4. Aggiungi una entry CommandMeta(...) in src/zenzic/cli/_metadata.py così i pannelli help root e lo short help rimangono autorevoli.
  5. Se la sub-app usa no_args_is_help=True, aggiungi "myfeature" al frozenset _SUBAPPS_WITH_MENU in cli_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:

CodiceSignificato
0Tutti i check superati
1Problemi di qualità trovati
2SECURITY — credenziale esposta rilevata
3SECURITY — 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.