DACIP DACIP

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:

  • Regeneratedengine, 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), and unresolved (the regions DACIP could not resolve, its coverage census, and a named skip for every producer this engine does not yet have). --verify recomputes 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. --verify adopts 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.