Writing a New Check
Zenzic's checks live in src/zenzic/core/. Each check is a standalone function in either
scanner.py (filesystem traversal) or validator.py (content validation). CLI wiring is
in the cli/ package (src/zenzic/cli/).
Six-Step Checklist
- Implement the logic in the appropriate core module (
zenzic.core.scannerorzenzic.core.validator). - Delegate resolution to
InMemoryPathResolver— never callos.path.exists(),Path.is_file(), or any other filesystem probe inside a per-link loop. The resolver is instantiated once before the loop; re-instantiation per file defeats the pre-computed_lookup_mapand drops throughput from 430 000+ to below 30 000 resolutions/s. - Test i18n — if the check involves file paths, test it in all three i18n configurations (none, folder mode, suffix mode).
- Wire the CLI — add a corresponding command or sub-command in the
cli/package. See the CLI Architecture reference. If your command accepts aPATHargument, you must callfind_repo_root(search_from=resolved_path)and invoke_apply_target()to respect Path Sovereignty. - Write tests in
tests/covering both passing and failing cases, including a performance baseline (5 000 links resolved in < 100 ms against a mock in-memory corpus). - Update examples in
examples/to exercise the new check — Zenzic validates its own examples on every commit.
Performance contract: the
zenzic.corehot path must remain allocation-free. NoPathobject construction, no syscalls, and norelative_to()calls inside the resolution loop.
Core Laws
All checks must respect the Core Laws governing the scanner. Before writing a check, ensure you are familiar with the invariants detailed in the Core Laws of the Scanner.
CLI Wiring
Depending on whether you are adding a command to an existing sub-app or introducing a new top-level sub-app, follow the steps below:
Adding a Command to an Existing Sub-App
In the appropriate sub-app module (e.g., src/zenzic/cli/_check.py):
@check_app.command(name="metadata")
def check_metadata(path: Path = ...) -> None:
...
No changes to __init__.py, main.py, or _metadata.py are required.
Adding a New Top-Level Sub-App
- Create a new module
src/zenzic/cli/_myfeature.pydefining the sub-app:myfeature_app = typer.Typer(...). - Export
myfeature_appfromsrc/zenzic/cli/__init__.py. - Register the sub-app in
src/zenzic/main.py:app.add_typer(myfeature_app, name="myfeature", rich_help_panel="..."). - Add a
CommandMeta(...)entry insrc/zenzic/cli/_metadata.pyso root help panels and short help stay authoritative. - If the sub-app uses
no_args_is_help=True, add"myfeature"to the_SUBAPPS_WITH_MENUfrozenset incli_main()so the Zenzic banner appears when the sub-app is invoked with no arguments.
Credential Scanner Obligations
If your check touches the credential scanner or harvest(), see the dedicated
Credential Scanner Obligations reference.
The four obligations (Worker Timeout, Regex-Canary, Dual-Stream Invariant, Mutation Score ≥ 90%)
are enforced on every PR touching src/zenzic/core/.
Finding Codes
Every new check must emit findings using a code registered in FROZEN_CODES. Before
adding a new code:
- Run
zenzic inspect codes— confirm the code does not already exist. - Add the code to
FROZEN_CODESin the appropriate tier (Core,Structure, orGovernance). - Update
CHANGELOG.mdwith the new code in the same commit.
Do not reuse retired codes. Retired codes stay in FROZEN_CODES with status retired.