The checks that must pass for code to count as done. Minimal, run after every change.
Write the test first, and watch it fail for the reason it names. A test written after the code it covers confirms what was built; only one that failed first can tell you it is checking anything at all.
Two consequences, both learned by paying for them:
- A guard ships with the mutation that proves it. Break the thing the test is named for and watch that test — not another — go red. A guard nothing fails for is not a guard, and it is indistinguishable from a comment.
- After any scripted edit, read the file back before running anything. A replacement whose anchor drifted applies to nothing and reports nothing, which is how a field can be declared in a type, believed by a display and produced by no code. An inconsistent state is worse than a missing feature: nobody designed it, so nobody can reason about it.
Never state in a commit message or a report anything not just observed in output.
CLI-specific completion criteria:
cli/aidd_docs/memory/coding-assertions.md.
| Order | Command | Checks |
|---|---|---|
| 1 | pnpm exec lefthook run pre-commit |
JSON and YAML validity, scripts/ tests, skill frontmatter and argument hints, cross-plugin orthogonality and router coherence (scripts/check-architecture-rules.js), context imports and reference form, markdown links, the paths the prose names and a sentence written in two documents (scripts/check-doc-duplication.js); cli lint, architecture, typecheck and type honesty when cli/ changed. cli knip and the full cli suite are pre-push, not pre-commit — see below |
| 2 | pnpm exec commitlint --edit |
the message against commitlint.config.cjs |
Same hook regenerates each plugin's CATALOG.md, and the README counts, and stages them.
Every cli job is globbed on cli/**. A change under kanban/ alone fires no local job; run cd kanban && pnpm test by hand. CI does cover it — cli-ci.yml's kanban-checks, see deployment.md.
context-reference-formreads only the three filesupdate_memory.js'sTARGET_FILESnames: rootCLAUDE.md, rootAGENTS.md, thecopilot-instructions.mdunder.github/(absent here). It never walks the tree, socli/CLAUDE.md's memory block is outside it.context-importswalks the whole tree (scripts/check-context-imports.js'scollectContextFiles).context-reference-formalso globsplugins/aidd-context/hooks/update_memory.js, whose table is its source of truth.
| Order | Command | Checks |
|---|---|---|
| 1 | pnpm exec lefthook run pre-push |
cli knip, then the full cli suite, when cli/ changed |
Each runs through scripts/gate-witness.js, which skips a gate this exact tree already passed: same index, same unstaged edits, same untracked files. A tree that changed while the gate ran is never stamped.
--no-verify buys little, not nothing:
- A push to a feature branch fires no workflow.
- Any pull request fires
ci.ymlandcli-ci.yml, whatever its base. - Only a pull request targeting
mainornextreplays the pre-commit, andvalidate.ymldropscli-biome,cli-architectureandcli-typecheckfrom it.
Deleting a remote branch is the one push to send with it. git push --delete carries no commit, and the gates still run: measured, lefthook forwards git's pre-push stdin to no command, the hook environment is byte-identical to a normal push, and a glob filter matches anyway. Nothing inside the hook can tell the two apart, so the full cli suite runs over a push that has nothing to validate.
Done means every gate green. On failure, one agent per failing assertion — typecheck, tests, rules — not one agent for all.