CLI reference
All commands accept a global --repo <path> (default .), placed before the subcommand: dacip --repo ~/work/app scan. dacip version prints the version.
Shared scan flags
scan, investigate, diff, baseline, verify, and the agent-facing commands all take:
| Flag | Meaning |
|---|---|
--scope {all,backend,frontend} |
Limit analysis side. Default all. |
--include <glob> |
Only analyze matching paths. Repeatable. |
--exclude <glob> |
Skip matching paths. Repeatable. |
--client <name> |
Extra frontend HTTP-client name (e.g. _data) whose .get/.post/… calls count as API calls. Repeatable. |
--action-auth <name> |
Extra authorization-assertion function name (e.g. requireRole) whose string argument is a permission marker on a Server Action. Repeatable. Defaults recognise assertPermission, requirePermission, requireAuth. |
--fast |
Faster, shallower scan. |
--quiet |
Suppress progress messages on stderr. |
Analysis commands
scan
dacip scan [repository]
Snapshot the repo and run all analyzers. Prints JSON: snapshot id, file/symbol/route/API-call counts, and coverage — read it; anything DACIP couldn't parse is listed there, not silently dropped. The optional positional repository overrides --repo.
investigate
dacip investigate "<objective>" [--max-findings N] [--command CMD] [--openapi PATH] [--serializers] [--deep-types]
Full analysis against an objective. --max-findings defaults to 5. --command (repeatable) runs a shell command and records its output as evidence. --openapi supplies a schema for payload/auth comparison; --serializers derives backend schemas from DRF serializers when no schema exists. --deep-types resolves frontend payload/response types via the TS type checker (slower). Writes report.md under .dacip/ and prints its path. Objective is required (exit 2 without one).
diff
dacip diff <base_ref> [--format {json,github,sarif}] [--max-findings N] [--openapi PATH] [--serializers]
Analyze both the working tree and base_ref, report only findings introduced between them. --format defaults to json; github emits workflow annotations, sarif emits SARIF for code-scanning upload. Findings are capped at --max-findings (default 5); introduced_total in the JSON is the uncapped count. If a lockfile changed, the JSON carries a note: base analysis ran against the head toolchain, so extraction-instability diagnostics may stem from dependency skew.
certify
dacip certify <base_ref> [--out FILE] [--plan FILE] [--format {json,text}]
dacip certify --verify FILE
Emit a canonical certificate of what DACIP established about a change, and verify one by regenerating it. There is no signature and no key: the integrity argument is determinism — anyone with the repository and the same engine version re-derives the same bytes, so --verify recomputes the document from the local checkout and byte-compares rather than believing the file.
The document has two halves, and the split is the point:
- Regenerated —
engine,subject(the base and head git trees, the analyzed snapshot, whether the tree was dirty, and a digest of the committed fact tier),semantic_delta(routes, API calls, contract links and Server Actions added, removed, regressed or repaired),findings_delta(introduced, fixed, unchanged, and what the baseline held back), andunresolved(the regions DACIP could not resolve, its coverage census, and a named skip for every producer this engine does not yet have).--verifyrecomputes every one of these. - Attested — the patch-gate result and the proof bundles. Neither re-derives from a checkout alone: the gate reads the plan's pinned snapshot from the local cache tier, and a proof records the outcome of running a repro.
--verifyadopts this block verbatim and says so; it never executes repository code.
The unresolved block is emitted in every rendering, including when it is empty. A proof artifact that hid its own ceiling would turn honest incompleteness into implied completeness exactly where a reader trusts it most.
Certificates are derived artifacts, so they are not committed per commit — keep them as CI artifacts, or commit one at a release tag where the growth is bounded. Exit codes: 0 verified identical, 1 the certificate is no longer true of this checkout (the first divergent section is named, with a stale/tampered/determinism triage note), 2 the certificate is about a different tree or the input is unreadable, 3 analysis failure.
baseline
dacip baseline write
Record all current findings as accepted. investigate and diff suppress baselined findings and warn about stale entries that no longer derive.
verify
dacip verify <finding_id>
Run a finding's stored reproduction (a unique id prefix is enough). Reproducing promotes a high-confidence finding to proven. Findings whose repro is a curl template can't be run automatically — DACIP prints the command for you to run manually and exits 2.
Account
| Command | Does |
|---|---|
dacip login |
GitHub device-flow sign-in; unlocks analysis of 1 private repo on the free tier. Exit 1 on failure. |
dacip logout |
Clear the session. |
dacip whoami |
Print {"login": ...} as JSON. |
dacip license |
Print license state, path, and detail as JSON. Exit 0 for valid/expiring/grace, 3 otherwise. Never gated itself. |
Public repos need no account. See the FAQ for tiers.
Setup
| Command | Does |
|---|---|
dacip acknowledge <finding_id> --reason "…" |
Accept a finding, with a reason. --wont-fix records it as intentional rather than accepted. The decision lands in .dacip/ledger/decisions.ndjson, which is committed: a teammate's acknowledgement applies on your machine and in CI without anyone sharing a database. A reason is required — a ledger of unexplained suppressions is technical debt with a schema. |
dacip ledger list |
Every recorded decision, with its reason and author. A decision whose finding no longer derives is marked [stale] and kept — a finding can disappear because the code was fixed or because an analyzer regressed, and dropping the note would lose the record either way. |
dacip ledger prune |
Remove stale decisions. Explicit on purpose: nothing else ever deletes one. |
dacip facts write |
Regenerate the committed fact set under .dacip/facts/. |
dacip facts verify |
Regenerate to a temporary directory and assert a byte-identical diff. Exit 0 identical, 1 drift. This is the determinism proof running in your own CI on your own code. |
dacip init |
Create .dacip/ and add its local tiers to .gitignore. Analysis commands also do this implicitly. The boundary fact set under .dacip/facts/ is committed — routes, API calls, contract links and Server Actions. It is byte-reproducible, so it is a lockfile rather than drifting state: dacip facts verify regenerates it and asserts a zero diff, which is the determinism proof running in your own CI on your own code. Fact identity is derived from the fact's own content, so the diff is readable: adding one route to a 149-route repository changes exactly one line, and a pull request that orphans a route or drops an auth marker shows +/- lines there next to the source change. The symbol graph is deliberately not committed: it is an internal index nobody diffs, and it was 98% of the tier's size on a large repo. |
dacip install |
Write the Claude Code integration (MCP server + PreToolUse hook) into the repo. Restart Claude Code after. |
dacip toolchain install-ts |
npm-install typescript@5 into ~/.cache/dacip/toolchain as a fallback compiler. DACIP never npm-installs implicitly; without a usable TypeScript, frontend calls are not extracted and cross-stack findings are incomplete — DACIP says so on stderr rather than reporting a clean pass. |
dacip mcp |
Run the MCP stdio server (normally launched by Claude Code, not by hand). |
dacip hook |
PreToolUse gate: reads Claude Code hook JSON on stdin, prints an allow/deny decision. Fail-open — no store or no approved plan means allow, with the reason stated. |
Agent-facing commands
Used by the agent loop; all print JSON.
dacip workorder <finding_id>— build a work order and plan skeleton for a finding.dacip validate-plan <plan_file>— validate an ExecutionPlan; a valid plan becomes the active scope the hook enforces. Exit 1 if invalid.dacip validate-patch— check the working tree against the active plan. Exit 1 if invalid, 2 if no plan.
What validate-patch checks
The gate reports every layer together, so one run tells you everything that is wrong. It never stops at the first failure.
| Layer | Rejects |
|---|---|
| Structural | Files changed that the plan does not name; a symbol the plan depends on that no longer exists. |
| Contract | A route or call the patch broke that the plan did not declare. |
| Behavioral | A declared behaviour the patch did not deliver. |
| Change shape | Undeclared weakening of the tests, and undeclared new suppressions. |
| Minimal | A diff much larger than the plan's scope (warning only). |
The Change-shape layer compares the working tree against the snapshot your plan was pinned to, over the files the patch changed. It is one-directional: strengthening the tests or removing a suppression is always allowed and never needs a declaration.
Each clause is authorized by a field on the plan. The gate constrains undeclared change, never change itself:
| Clause | Rejects when | Authorized by |
|---|---|---|
| Test strength | Fewer assertions, cases or enabled cases; a raised timeout; a new focus marker; a deleted test file. | test_changes |
| Suppressions | A new # type: ignore, # noqa, # nosec, @ts-ignore, eslint-disable, bare except, except Exception: pass, cast(Any, …), as any, empty catch {}. |
suppression_changes |
| Resolution | A new construct that blinds DACIP itself: dynamic getattr/setattr, exec, eval, a dynamic import, a star import. Test files are exempt. |
resolution_changes |
A declaration is free text a reviewer reads — the reason the change was made. There is no threshold to configure and no numeric allowance: the budget is "not more than the base".
If the gate cannot recover the base measurements — usually because dacip gc evicted them — it fails closed and tells you to re-run validate-plan. It never passes on a measurement it does not have.
dacip prove <finding_id>— gate + repro after a fix; marks the finding resolved, writes a proof, retires the plan.dacip context "<objective>"— deterministic facts (symbols, routes, calls) filtered by objective keywords.dacip impact <symbol>— impact query from the snapshot; skips full analysis.dacip plan {show,clear}— show or clear the active plan.dacip gc [--keep N]— prune old snapshots, keeping N per repo/scope (default 5); the active plan's snapshot is pinned.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Clean (no new/recurring findings on investigate, nothing introduced on diff, repro succeeded on verify). |
| 1 | Defects found, repro failed, or plan/patch invalid. |
| 2 | Usage error: missing objective, unresolvable ref, no unique finding, no active plan. |
| 3 | Cancelled (Ctrl-C), analysis/toolchain failure, or license/login gate. |
| 4 | Internal DACIP error — please report it. |
See /proof/ for what these results look like on real repos.

