contract-driven-delivery 2.1.3 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +219 -0
- package/README.md +124 -1
- package/assets/CLAUDE.template.md +13 -0
- package/assets/agents/backend-engineer.md +3 -1
- package/assets/agents/frontend-engineer.md +3 -2
- package/assets/cdd/conformance.json +16 -0
- package/assets/cdd/tier-policy.json +35 -0
- package/assets/contracts/api/api-contract.md +26 -0
- package/assets/hooks/pre-tool-use-graph-first.sh +65 -0
- package/assets/skills/contract-driven-delivery/scripts/validate_api_conformance.py +671 -0
- package/assets/skills/contract-driven-delivery/scripts/validate_api_semantic.py +8 -1
- package/assets/skills/contract-driven-delivery/scripts/validate_contract_versions.py +4 -0
- package/dist/cli/index.js +2118 -491
- package/docs/adr/0001-contract-to-openapi-export.md +142 -0
- package/docs/adr/0002-schema-carrying-contract-format.md +277 -0
- package/docs/adr/0003-code-intelligence-indexing-strategy.md +110 -0
- package/docs/api-conformance.md +145 -0
- package/docs/openapi-export.md +157 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,224 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
_No unreleased changes yet._
|
|
6
|
+
|
|
7
|
+
## [2.2.1] - 2026-06-03
|
|
8
|
+
|
|
9
|
+
Fix a class of false positives in the 2.2.0 API conformance validator that broke
|
|
10
|
+
CI on correct contracts (issue #15), and stop a heuristic blind spot from being
|
|
11
|
+
fatal by default.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Resolve Flask Blueprint `url_prefix` / FastAPI APIRouter `prefix` across
|
|
16
|
+
files (`validate_api_conformance.py`).** A route declared as
|
|
17
|
+
`@admin_bp.route("/api/logs")` on a `Blueprint(..., url_prefix="/admin")` (or a
|
|
18
|
+
`register_blueprint(bp, url_prefix=...)` in another file) was recorded as
|
|
19
|
+
`/api/logs`, so every prefixed route was flagged `backendRouteNotInContract`
|
|
20
|
+
while the matching contract endpoint was flagged
|
|
21
|
+
`contractEndpointNotImplemented` — two false errors per route against a contract
|
|
22
|
+
that was actually correct. The validator now resolves constructor prefixes per
|
|
23
|
+
file and registration prefixes across files (registration winning) and folds
|
|
24
|
+
them into the route path. Constructor scoping is **per file**, so a bare
|
|
25
|
+
`router` name reused across modules cannot collide; registration prefixes are
|
|
26
|
+
matched across files with each framework's semantics — Flask
|
|
27
|
+
`register_blueprint(url_prefix=...)` **overrides** the Blueprint's own prefix
|
|
28
|
+
while FastAPI `include_router(prefix=...)` is **additive** with the
|
|
29
|
+
`APIRouter(prefix=...)` (served as `<include>/<router>/<route>`). A name
|
|
30
|
+
registered under conflicting prefixes across files is detected and dropped (the
|
|
31
|
+
per-file constructor prefix decides) rather than guessed. The constructor regex
|
|
32
|
+
tolerates a nested-paren kwarg (`APIRouter(dependencies=[Depends(x)],
|
|
33
|
+
prefix=...)`), a module-qualified call (`flask.Blueprint(...)`,
|
|
34
|
+
`fastapi.APIRouter(...)`), and a type-annotated assignment (`router: APIRouter =
|
|
35
|
+
APIRouter(...)`); Flask 2.0 `@bp.get(...)` shorthand is covered too. An explicit
|
|
36
|
+
empty registration prefix (`register_blueprint(bp, url_prefix="")`, a deliberate
|
|
37
|
+
root mount) is preserved and overrides the constructor prefix rather than being
|
|
38
|
+
discarded as falsy. (Issue #15; hardened over three rounds of Codex/Sourcery PR
|
|
39
|
+
review.)
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **`backendRouteNotInContract` now defaults to `warning`, not `error`.** Regex
|
|
44
|
+
scanning cannot resolve every cross-file route prefix (aliased routers, the
|
|
45
|
+
Express `app.use` mount form, module-qualified `include_router(pkg.router, …)`),
|
|
46
|
+
so a scanner blind spot must not break CI on a contract that is correct. Raise it to
|
|
47
|
+
`error` (or set `"strict": true`) to enforce once a project's routing shape is
|
|
48
|
+
known to resolve cleanly. Updated in `DEFAULT_CONFIG`, the scaffolded
|
|
49
|
+
`.cdd/conformance.json`, and `docs/api-conformance.md`.
|
|
50
|
+
|
|
51
|
+
## [2.2.0] - 2026-06-02
|
|
52
|
+
|
|
53
|
+
Make enforcement live by default, add a mechanical risk-tier safety net under the
|
|
54
|
+
AI classifier, and give code indexing an opt-in background mode — the three gaps
|
|
55
|
+
that matter most for a fully automated, no-human-reviewer workflow.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **Arm enforcement chokepoints by default in `cdd-kit init`.** A fresh `init`
|
|
60
|
+
now wires the graph-first PreToolUse hook (Claude provider, advisory) and the
|
|
61
|
+
pre-commit gate hook, instead of shipping them dormant. In an automated
|
|
62
|
+
workflow with no human reviewer, dormant enforcement means the contracts and
|
|
63
|
+
docs only *look* like they prevent drift. Best-effort: a missing `.git` or
|
|
64
|
+
unusual `settings.json` downgrades to a warning, never a failed init. Opt out
|
|
65
|
+
with `cdd-kit init --no-arm`. `installHooks`/`installAgentHooks` gained a
|
|
66
|
+
`fromInit` mode so arming is non-fatal.
|
|
67
|
+
- **Mechanical risk-tier floor (`src/utils/tier-floor.ts`).** A deterministic
|
|
68
|
+
backstop under the (AI) classifier: `cdd-kit gate` scans `change-request.md`
|
|
69
|
+
for sensitive surfaces (auth, payments, migrations, concurrency, secrets, …)
|
|
70
|
+
and **fails** when the declared tier is weaker than the matched floor, so a
|
|
71
|
+
single mis-classification can no longer silently drop the required agents and
|
|
72
|
+
tests. Bypass per-change with `tier-floor-override: "<reason>"` in `tasks.yml`
|
|
73
|
+
frontmatter (downgrades to an audit warning). Policy lives in
|
|
74
|
+
`.cdd/tier-policy.json` (scaffolded, fully editable, `enabled:false` to
|
|
75
|
+
disable); built-in defaults apply when the file is absent so existing repos are
|
|
76
|
+
protected without a re-init.
|
|
77
|
+
- **`cdd-kit classify-check [change-id] | --text "<intent>"`** — advisory probe
|
|
78
|
+
that prints the mechanical tier floor *before* classification, so the
|
|
79
|
+
classifier can be steered up front rather than only caught by the gate.
|
|
80
|
+
Supports `--json`.
|
|
81
|
+
- **`cdd-kit code-map --watch`** — opt-in background auto-indexing. A debounced
|
|
82
|
+
(default 500 ms, `--debounce`) recursive watcher keeps the map fresh during
|
|
83
|
+
long-lived co-editing sessions, with a freshness-polling fallback where
|
|
84
|
+
recursive `fs.watch` is unavailable. Trigger-based indexing stays the default
|
|
85
|
+
for ephemeral CI/agent runs.
|
|
86
|
+
- **ADR 0003** (`docs/adr/0003-code-intelligence-indexing-strategy.md`):
|
|
87
|
+
evaluates LSP (Serena) vs tree-sitter incremental (CocoIndex) vs the kit's
|
|
88
|
+
native AST scanners, and the trigger-vs-background refresh question. Decision:
|
|
89
|
+
keep native AST (LSP does not translate to headless agents), keep trigger-based
|
|
90
|
+
as default, add the opt-in `--watch` above, and sequence per-file incremental
|
|
91
|
+
rebuild as the next step.
|
|
92
|
+
|
|
93
|
+
### Added (mechanical chokepoints — prior unreleased work)
|
|
94
|
+
|
|
95
|
+
- **API conformance validator** (`validate_api_conformance.py`): parses real
|
|
96
|
+
backend route declarations and frontend HTTP call sites and diffs them against
|
|
97
|
+
`contracts/api/api-contract.md`. Catches frontend/backend API drift that the
|
|
98
|
+
markdown-only validators never could. Chained into `cdd-kit validate
|
|
99
|
+
--contracts`, so `cdd-kit gate` blocks on drift. Off until enabled in
|
|
100
|
+
`.cdd/conformance.json` (`"enabled": true`); a disabled config is scaffolded by
|
|
101
|
+
`cdd-kit init`. See `docs/api-conformance.md`.
|
|
102
|
+
- **`--with-source` / `withSource`** on `cdd-kit index query`, `cdd-kit graph
|
|
103
|
+
query`, and the `cdd_index_query` / `cdd_graph_query` MCP tools: returns the
|
|
104
|
+
matched symbol's code inline so the query replaces a follow-up `Read` instead
|
|
105
|
+
of preceding it. `--source-budget` caps total lines and flags truncated ranges.
|
|
106
|
+
- **`hooks/pre-tool-use-graph-first.sh`** (opt-in PreToolUse hook): steers agents
|
|
107
|
+
to `cdd-kit index query --with-source` before reading source files. Advisory by
|
|
108
|
+
default; `CDD_GRAPH_FIRST_STRICT=1` hard-blocks source reads when a code-map
|
|
109
|
+
exists.
|
|
110
|
+
- `cdd-kit doctor` reports whether API conformance is enabled, disabled, or
|
|
111
|
+
unconfigured (informational; never fails `--strict`).
|
|
112
|
+
- `cdd-kit doctor` reports whether the **cdd-kit MCP server is registered** with
|
|
113
|
+
Claude Code (runs `claude mcp list`). If it is not registered, agents never see
|
|
114
|
+
the graph/index tools and silently fall back to `Read`, so doctor surfaces the
|
|
115
|
+
`claude mcp add --scope user cdd-kit -- cdd-kit mcp` command to fix it.
|
|
116
|
+
Informational only (never fails `--strict`), best-effort with a 3s timeout
|
|
117
|
+
(never blocks on a slow/missing `claude` CLI), skipped for non-Claude and
|
|
118
|
+
non-cdd-kit projects. `CDD_CLAUDE_BIN` overrides the CLI path. Closes the gap
|
|
119
|
+
left by `--with-source`: the incentive to use the kit tools only helps if the
|
|
120
|
+
agent can see them.
|
|
121
|
+
- **`cdd-kit install-agent-hooks --graph-first advisory|strict`**: installs the
|
|
122
|
+
graph-first `PreToolUse` hook into `.claude/settings.json` (project-scoped) and
|
|
123
|
+
copies the script to `.claude/hooks/`, so steering agents to
|
|
124
|
+
`cdd-kit index query --with-source` before `Read` becomes an installed harness
|
|
125
|
+
chokepoint instead of manual settings wiring. Advisory by default; `strict`
|
|
126
|
+
writes `CDD_GRAPH_FIRST_STRICT=1`. Idempotent and preserves unrelated settings.
|
|
127
|
+
- **`cdd-kit openapi export`**: projects `contracts/api/api-contract.md` into a
|
|
128
|
+
minimal OpenAPI 3.1 skeleton (`--yaml`, `--out`) for tooling such as
|
|
129
|
+
`openapi-typescript`. One-way projection — the markdown contract stays the
|
|
130
|
+
source of truth. Derives paths/params/auth/status codes; marks free-form
|
|
131
|
+
request/response bodies as `x-cdd-unresolved` rather than fabricating schemas.
|
|
132
|
+
Per-stack client generation is intentionally left to the consumer repo; see
|
|
133
|
+
`docs/adr/0001-contract-to-openapi-export.md` and `docs/openapi-export.md`.
|
|
134
|
+
- **`cdd-kit openapi export --check`**: the OpenAPI sync gate. Instead of
|
|
135
|
+
writing, it verifies the committed artifact at `--out` still equals what the
|
|
136
|
+
contract produces and exits non-zero on drift — so CI fails when the contract
|
|
137
|
+
changes but the export was not regenerated. This is the kit-owned half of the
|
|
138
|
+
preventive chain (the consumer's typed-client codegen runs from an artifact
|
|
139
|
+
that can never be silently stale).
|
|
140
|
+
- **`cdd-kit init` now wires the consumer codegen seam**: when a `package.json`
|
|
141
|
+
is present it adds editable `contract:client` and `contract:client:check` npm
|
|
142
|
+
scripts (the latter is the `openapi export --check` gate), turning the
|
|
143
|
+
consumer half of the OpenAPI seam from a doc into a chokepoint. Additive,
|
|
144
|
+
idempotent, never clobbers existing scripts; `openapi-typescript` is an
|
|
145
|
+
editable default, not a hard dependency.
|
|
146
|
+
- **`cdd-kit doctor` chokepoint dashboard**: reports each enforcement chokepoint
|
|
147
|
+
(graph-first hook, pre-commit gate, OpenAPI sync gate) as `live` or `dormant`
|
|
148
|
+
with the one command to arm it. The kit's mechanisms are opt-in and dormant
|
|
149
|
+
until armed, so a repo could carry all the machinery yet enforce none of it —
|
|
150
|
+
this makes that observable. Advisory only (never fails `--strict`).
|
|
151
|
+
|
|
152
|
+
### Changed
|
|
153
|
+
|
|
154
|
+
- `backend-engineer` and `frontend-engineer` prompts now prefer
|
|
155
|
+
`--with-source` queries and warn that endpoint changes/calls require a contract
|
|
156
|
+
update when conformance is enabled.
|
|
157
|
+
|
|
158
|
+
### Fixed
|
|
159
|
+
|
|
160
|
+
- **graph-first hook now installs in the shape Claude Code executes.**
|
|
161
|
+
`install-agent-hooks` wrote the command directly on the `PreToolUse` matcher
|
|
162
|
+
group; Claude Code requires it nested under an inner `hooks: [{ type:
|
|
163
|
+
"command", … }]` array, so the chokepoint was silently dormant even though
|
|
164
|
+
install/init reported it armed. Re-running upgrades a legacy entry in place and
|
|
165
|
+
preserves unrelated handlers that share the matcher group.
|
|
166
|
+
- **tier floor scans the right path scope.** `cdd-kit gate` now scans the
|
|
167
|
+
**staged** change (rename-aware, both sides) instead of the whole worktree, so
|
|
168
|
+
an unrelated unstaged `auth/` edit can no longer trip the floor and reject a
|
|
169
|
+
low-risk commit; when a single commit stages more than one change directory the
|
|
170
|
+
path signal is dropped (source paths can't be attributed to one change) and
|
|
171
|
+
only the request text sets the floor. `cdd-kit classify-check` keeps
|
|
172
|
+
whole-worktree scope (its in-progress change is not yet committed).
|
|
173
|
+
- **tier-floor pattern accuracy.** Critical-surface patterns now match plural
|
|
174
|
+
directories (`payments/`, `migrations/`); the `token` pattern is qualified to
|
|
175
|
+
security contexts (`access`/`api`/`auth`/`session`/`bearer`/`refresh`/`csrf`/
|
|
176
|
+
`id`/`reset`) so frontend "design tokens" / `theme/tokens.ts` no longer trip
|
|
177
|
+
the secrets floor. The gate / `classify-check` `matched:` line now reports the
|
|
178
|
+
actual matched text (e.g. `session token`) instead of the raw regex pattern.
|
|
179
|
+
- **`cdd-kit doctor` detects a gate armed under a custom `core.hooksPath`** (or a
|
|
180
|
+
worktree/submodule), resolving the hooks dir via git instead of probing only
|
|
181
|
+
`.git/hooks/pre-commit` — no more reporting a live gate as dormant.
|
|
182
|
+
- **`cdd-kit code-map --watch` skips churn under ignored trees** (`node_modules`,
|
|
183
|
+
`dist`, `.git`, `.next`, coverage) before rebuilding, and adds an `error`
|
|
184
|
+
listener so fs-watch runtime errors (ENOSPC, permissions) degrade to polling
|
|
185
|
+
instead of crashing the watch. Edits to `.cdd/code-map-config.yml` still
|
|
186
|
+
trigger a rebuild even though it lives under the ignored `.cdd/` tree.
|
|
187
|
+
- **`cdd-kit gate` no longer passes changes whose artifacts are still unfilled
|
|
188
|
+
scaffolds.** The stub check counted "meaningful chars", but a template's own
|
|
189
|
+
instructional prose (900+ chars) cleared the threshold while every field was
|
|
190
|
+
still an `<id>` / `<date>` / `<change-id>` placeholder — so a change could pass
|
|
191
|
+
`--strict` with raw templates and zero real content. Gate now fails and names
|
|
192
|
+
the remaining placeholder tokens per artifact. The check is a closed allowlist
|
|
193
|
+
(`<id>` / `<date>` / `<change-id>`) anchored to the colon-led, line-final value
|
|
194
|
+
position the templates use (`change-id: <id>`, `# …: <change-id>`), so inline
|
|
195
|
+
XML/markup examples (`<id>123</id>`) and hyphenated custom elements
|
|
196
|
+
(`<my-element>`) are not false-flagged — and a file may carry both a real
|
|
197
|
+
placeholder and an XML example without the placeholder slipping through.
|
|
198
|
+
`context-manifest.md` is exempt (its `<...>` sub-sections are documented as
|
|
199
|
+
illustrative; it is enforced via Allowed Paths, not template fill-ins).
|
|
200
|
+
- **`cdd-kit validate` / `gate` no longer crash with `UnicodeDecodeError` on
|
|
201
|
+
non-UTF-8 Windows locales (e.g. cp950/zh-TW).** `validate_contract_versions.py`
|
|
202
|
+
read `git show` output and the validators wrote stdout using the locale codec,
|
|
203
|
+
spamming tracebacks and mojibaking em-dashes in contracts. The Python
|
|
204
|
+
validators are now spawned with `PYTHONUTF8=1` / `PYTHONIOENCODING=utf-8`, and
|
|
205
|
+
the git subprocesses decode as UTF-8 explicitly.
|
|
206
|
+
- **`cdd-kit openapi export` fails fast on a mis-tagged schema fence** instead of
|
|
207
|
+
silently dropping it: a `### Name` section under `## Schemas` that uses ` ```json `
|
|
208
|
+
(or any non-`json-schema` fence) now errors with the fix — including when a field
|
|
209
|
+
table is also present (the stray fence was previously ignored). A prose-only
|
|
210
|
+
section with no fence stays a valid Tier C contract and is left unresolved.
|
|
211
|
+
- **`cdd-kit openapi export --out <absolute-path>`** no longer ENOENTs on an
|
|
212
|
+
absolute path (it was concatenated onto cwd, e.g. `D:\repo\C:\Users\…`); paths
|
|
213
|
+
are resolved with `path.resolve`.
|
|
214
|
+
|
|
215
|
+
### Added
|
|
216
|
+
|
|
217
|
+
- **`cdd-kit openapi export` typed schemas (ADR 0002)**: `## Schemas` sections
|
|
218
|
+
compile field tables (Tier A) and `json-schema` fenced blocks (Tier B) into
|
|
219
|
+
`components.schemas` with `$ref` resolution; the contract stub documents both
|
|
220
|
+
tiers.
|
|
221
|
+
|
|
3
222
|
## [2.1.3] - 2026-05-29
|
|
4
223
|
|
|
5
224
|
Correct Claude Code MCP registration guidance.
|
package/README.md
CHANGED
|
@@ -269,8 +269,18 @@ cdd-kit init --local-only # only scaffold project files
|
|
|
269
269
|
cdd-kit init --provider codex # scaffold Codex-oriented project guidance
|
|
270
270
|
cdd-kit init --provider both # scaffold Claude Code + Codex guidance
|
|
271
271
|
cdd-kit init --force # overwrite existing project files
|
|
272
|
+
cdd-kit init --no-arm # scaffold without arming enforcement chokepoints
|
|
272
273
|
```
|
|
273
274
|
|
|
275
|
+
By default `init` **arms** the enforcement chokepoints so a fresh repo enforces
|
|
276
|
+
the workflow instead of carrying it dormant: the graph-first PreToolUse hook
|
|
277
|
+
(Claude provider, advisory) and the pre-commit gate hook are wired in place. This
|
|
278
|
+
matters most in a fully automated, no-human-reviewer workflow — dormant
|
|
279
|
+
enforcement means the contracts only *look* like they prevent drift. Arming is
|
|
280
|
+
best-effort (a missing `.git` becomes a warning, never a failed init); pass
|
|
281
|
+
`--no-arm` to skip it, and `cdd-kit doctor` reports the live/dormant status of
|
|
282
|
+
each chokepoint.
|
|
283
|
+
|
|
274
284
|
Creates: `contracts/`, `specs/templates/`, provider guidance files (`CLAUDE.md`, `AGENTS.md`, and/or `CODEX.md`), `hooks/`
|
|
275
285
|
|
|
276
286
|
`.cdd/model-policy.json` stores role-to-model **classes** (`opus`, `sonnet`, `haiku`) instead of provider release IDs such as `claude-opus-4-7`. This keeps the policy stable across Claude and Codex adapters; provider-specific tooling can map the class to the concrete model available in that environment.
|
|
@@ -341,6 +351,29 @@ place. Then run `cdd-kit migrate --all` so existing active change directories
|
|
|
341
351
|
receive `implementation-plan.md`; fill required `design.md` with
|
|
342
352
|
`spec-architect` before resuming the planner or implementation agents.
|
|
343
353
|
|
|
354
|
+
#### Upgrading to 2.2.0
|
|
355
|
+
|
|
356
|
+
2.2.0 **arms enforcement chokepoints by default on a fresh `cdd-kit init`**, adds
|
|
357
|
+
the mechanical **tier floor**, `cdd-kit classify-check`, and `cdd-kit code-map
|
|
358
|
+
--watch`. A repo first set up with an older version keeps its chokepoints
|
|
359
|
+
*dormant* after a plain `npm`/`refresh` update — `cdd-kit doctor` will show them
|
|
360
|
+
as such. To bring an existing repo up to the 2.2.0 enforcement posture:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
npm install -g contract-driven-delivery # get 2.2.0
|
|
364
|
+
cdd-kit refresh --yes # sync agents/skills/templates/hooks/code-map
|
|
365
|
+
cdd-kit install-hooks # arm the pre-commit gate
|
|
366
|
+
cdd-kit install-agent-hooks --graph-first advisory # arm the graph-first hook (or: strict)
|
|
367
|
+
cdd-kit doctor # confirm both chokepoints report "live"
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The tier floor needs **no policy file** — built-in defaults apply when
|
|
371
|
+
`.cdd/tier-policy.json` is absent, so existing repos are protected without a
|
|
372
|
+
re-init. To customize or disable it, scaffold the policy with `cdd-kit upgrade
|
|
373
|
+
--yes` (writes an editable `.cdd/tier-policy.json`) and set rules or
|
|
374
|
+
`"enabled": false`. Bypass a single change with `tier-floor-override:
|
|
375
|
+
"<reason>"` in its `tasks.yml` frontmatter (recorded as an audit warning).
|
|
376
|
+
|
|
344
377
|
If you do not want template overwrites, run the narrower path:
|
|
345
378
|
|
|
346
379
|
```bash
|
|
@@ -366,6 +399,10 @@ cdd-kit doctor --provider codex
|
|
|
366
399
|
|
|
367
400
|
Checks for missing `.cdd/` policy files, provider guidance (`CLAUDE.md`, `AGENTS.md`, `CODEX.md`), context indexes, stale `specs/context/*` outputs, and contract summary metadata gaps. `--strict` treats warnings as errors. `--json` emits a machine-readable report for CI or wrapper scripts. `--fix` currently auto-runs `context-scan` for stale or missing indexes and backfills empty `.cdd/model-policy.json` role bindings, but deliberately does not run invasive repo upgrades for you.
|
|
368
401
|
|
|
402
|
+
For Claude projects, `doctor` also reports whether the **cdd-kit MCP server is registered** with Claude Code (it runs `claude mcp list`). If it is not registered, agents never see the graph/index tools and silently fall back to `Read`, so doctor surfaces the exact `claude mcp add --scope user cdd-kit -- cdd-kit mcp` command to fix it. This check is **informational only** — it never fails `--strict`, never blocks on a slow or missing `claude` CLI (3s timeout, best-effort), and is skipped for non-Claude projects. Point `CDD_CLAUDE_BIN` at an alternate Claude CLI if needed.
|
|
403
|
+
|
|
404
|
+
`doctor` finally prints a **chokepoint dashboard**: for each enforcement mechanism — the graph-first hook, the pre-commit gate, and the OpenAPI sync gate — it reports `live` (armed) or `dormant`, with the one command to arm it. The kit's mechanisms are opt-in and dormant until installed, so a repo can carry all the machinery yet enforce none of it; this makes that state observable. Like the MCP and conformance lines, it is **advisory only** and never fails `--strict`.
|
|
405
|
+
|
|
369
406
|
---
|
|
370
407
|
|
|
371
408
|
### `cdd-kit upgrade`
|
|
@@ -437,6 +474,7 @@ Checks:
|
|
|
437
474
|
- All required artifacts exist (`change-request.md`, `change-classification.md`, `implementation-plan.md`, `test-plan.md`, `ci-gates.md`, `tasks.yml`; new context-governed changes also require `context-manifest.md`)
|
|
438
475
|
- Each artifact has sufficient content and is not a stub.
|
|
439
476
|
- `change-classification.md` contains a tier or risk marker.
|
|
477
|
+
- **Mechanical risk-tier floor.** `change-request.md` is scanned for sensitive surfaces (auth, payments, migrations, concurrency, secrets, …) — and the change's git paths are scanned against the critical (tier-0) rules only, so a generic request whose work lives under `auth/` or `payments/` is still caught. The gate scans the **staged** change (so an unrelated unstaged edit can't trip it; rename-aware on both sides; the path signal is dropped when a commit stages multiple change dirs), while `classify-check` scans the whole worktree. The gate fails when the declared tier is weaker than the matched floor — the deterministic safety net under the AI classifier. Bypass one change with `tier-floor-override: "<reason>"` in `tasks.yml` frontmatter (becomes an audit warning); tune or disable in `.cdd/tier-policy.json`.
|
|
440
478
|
- Atomic `depends-on` upstream changes are completed or archived before dependent work gates.
|
|
441
479
|
- All contract validators pass.
|
|
442
480
|
|
|
@@ -456,6 +494,25 @@ Pre-commit hook uses `--strict` by default (installed via `cdd-kit install-hooks
|
|
|
456
494
|
|
|
457
495
|
---
|
|
458
496
|
|
|
497
|
+
### `cdd-kit classify-check`
|
|
498
|
+
|
|
499
|
+
Advisory probe that prints the **mechanical risk-tier floor** for a change
|
|
500
|
+
*before* classification, so the classifier can be steered up front instead of
|
|
501
|
+
only being caught later by `cdd-kit gate`. The blocking enforcement lives in the
|
|
502
|
+
gate; this command never fails (exit 0).
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
cdd-kit classify-check add-jwt-auth # scan the change's change-request.md
|
|
506
|
+
cdd-kit classify-check --text "add stripe checkout" # scan inline intent
|
|
507
|
+
cdd-kit classify-check add-jwt-auth --json # machine-readable
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
It reads the same ground-truth source the gate enforces against
|
|
511
|
+
(`change-request.md`), reports the strictest matched tier and the matched
|
|
512
|
+
patterns, and points at `.cdd/tier-policy.json` for tuning.
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
459
516
|
### `cdd-kit list`
|
|
460
517
|
|
|
461
518
|
Lists all active changes in `specs/changes/` with status and pending task count.
|
|
@@ -607,13 +664,38 @@ Runs contract validation scripts.
|
|
|
607
664
|
|
|
608
665
|
```bash
|
|
609
666
|
cdd-kit validate # all validators
|
|
610
|
-
cdd-kit validate --contracts # API, CSS, data-shape (+ semantic checks)
|
|
667
|
+
cdd-kit validate --contracts # API, CSS, data-shape (+ semantic + conformance checks)
|
|
611
668
|
cdd-kit validate --env # env contract
|
|
612
669
|
cdd-kit validate --ci # CI gate policy
|
|
613
670
|
cdd-kit validate --spec # spec traceability
|
|
614
671
|
cdd-kit validate --versions # contract frontmatter schema versions
|
|
615
672
|
```
|
|
616
673
|
|
|
674
|
+
`--contracts` includes **API conformance**: a code-vs-contract check that parses
|
|
675
|
+
real backend routes and frontend call sites and fails on drift from
|
|
676
|
+
`contracts/api/api-contract.md` (e.g. the frontend calling an endpoint the
|
|
677
|
+
contract never declares). It is off until you enable it in `.cdd/conformance.json`
|
|
678
|
+
(`"enabled": true`); `cdd-kit init` scaffolds a disabled config. See
|
|
679
|
+
[docs/api-conformance.md](docs/api-conformance.md). This is the mechanical net
|
|
680
|
+
for frontend/backend API drift in a workflow where no human reviews the contract
|
|
681
|
+
by hand.
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
### `cdd-kit openapi export`
|
|
686
|
+
|
|
687
|
+
Projects `contracts/api/api-contract.md` into a minimal **OpenAPI 3.1** skeleton for tooling (e.g. feeding `openapi-typescript` to generate a typed frontend client). The markdown contract stays the source of truth; the OpenAPI document is a one-way, regenerable projection.
|
|
688
|
+
|
|
689
|
+
```bash
|
|
690
|
+
cdd-kit openapi export # JSON to stdout
|
|
691
|
+
cdd-kit openapi export --yaml --out openapi.yaml # YAML to a file
|
|
692
|
+
cdd-kit openapi export --check --out openapi.json # sync gate: fail on drift
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
It derives paths (normalizing `:id`/`{id}`), path parameters, auth → bearer security, and success/error status codes. Free-form request/response schemas in the contract are marked `x-cdd-unresolved` rather than fabricated. Generating an actual client is left to your stack's generator in your own CI — this is the **preventive** complement to the **detective** conformance check above. See [docs/openapi-export.md](docs/openapi-export.md) and [docs/adr/0001-contract-to-openapi-export.md](docs/adr/0001-contract-to-openapi-export.md).
|
|
696
|
+
|
|
697
|
+
`--check` is the **sync gate**: it does not write, it verifies the committed artifact at `--out` still matches the contract and exits non-zero on drift, so CI fails when a contract edit forgets to regenerate the export (and the typed client downstream). To wire the consumer half, `cdd-kit init` scaffolds editable `contract:client` and `contract:client:check` npm scripts when a `package.json` is present — the generic contract→OpenAPI step is the kit's, the stack-specific codegen stays an editable script in your repo.
|
|
698
|
+
|
|
617
699
|
---
|
|
618
700
|
|
|
619
701
|
### `cdd-kit new <name>`
|
|
@@ -651,6 +733,22 @@ Idempotent. Preserves existing hook content. Bypass with `--no-verify` is possib
|
|
|
651
733
|
|
|
652
734
|
---
|
|
653
735
|
|
|
736
|
+
### `cdd-kit install-agent-hooks`
|
|
737
|
+
|
|
738
|
+
Installs Claude Code **agent hooks** into the project's `.claude/settings.json`. Currently installs the graph-first `PreToolUse` hook, which steers agents to `cdd-kit index query --with-source` before reading source files — turning the hook from a documented file you wire by hand into an enforced harness chokepoint.
|
|
739
|
+
|
|
740
|
+
```bash
|
|
741
|
+
cdd-kit install-agent-hooks # advisory (default)
|
|
742
|
+
cdd-kit install-agent-hooks --graph-first strict # hard-block source Reads when a code-map exists
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
- **advisory** (default): reminds the agent to use the graph/index query first; does not block the `Read`.
|
|
746
|
+
- **strict**: writes `CDD_GRAPH_FIRST_STRICT=1` into the hook command so the hook blocks source-file `Read` when `.cdd/code-map.yml` exists.
|
|
747
|
+
|
|
748
|
+
Writes the hook script to `.claude/hooks/pre-tool-use-graph-first.sh` and a `PreToolUse` entry to `.claude/settings.json` (project-scoped, so it travels with the repo). Idempotent: re-running replaces the cdd-kit entry and switches mode cleanly, preserving every other setting and hook.
|
|
749
|
+
|
|
750
|
+
---
|
|
751
|
+
|
|
654
752
|
### `cdd-kit detect-stack`
|
|
655
753
|
|
|
656
754
|
Detects the project tech stack from lockfiles and config files.
|
|
@@ -712,8 +810,21 @@ cdd-kit code-map # whole repo -> .cdd/code-map.yml
|
|
|
712
810
|
cdd-kit code-map --check # exit 1 if regenerating would change the map
|
|
713
811
|
cdd-kit code-map --surface packages/web # monorepo: scope + auto-name the map
|
|
714
812
|
cdd-kit code-map --workers # parallelize JS/TS/Vue scanning (default off)
|
|
813
|
+
cdd-kit code-map --watch # background: keep the map fresh as files change
|
|
814
|
+
cdd-kit code-map --watch --debounce 800 # coalesce change bursts within 800ms
|
|
715
815
|
```
|
|
716
816
|
|
|
817
|
+
Indexing is **trigger-based by default** — the map regenerates when a command
|
|
818
|
+
needs it (gate, `index query --refresh`, `doctor --fix`, the pre-commit code-map
|
|
819
|
+
hook). That is the right default for ephemeral CI containers and one-shot agent
|
|
820
|
+
runs. `--watch` is the opt-in **background** mode for long-lived co-editing
|
|
821
|
+
sessions: a debounced recursive watcher keeps the map fresh so queries stay cheap
|
|
822
|
+
and current, with a freshness-polling fallback where recursive `fs.watch` is
|
|
823
|
+
unavailable. See
|
|
824
|
+
[docs/adr/0003-code-intelligence-indexing-strategy.md](docs/adr/0003-code-intelligence-indexing-strategy.md)
|
|
825
|
+
for why the kit keeps native AST scanners instead of an LSP daemon, and the
|
|
826
|
+
incremental-rebuild roadmap.
|
|
827
|
+
|
|
717
828
|
`--workers [n]` (default off; `n` defaults to CPU count − 1, capped at 16)
|
|
718
829
|
parallelizes the synchronous JS/TS/Vue parsing across child processes for large
|
|
719
830
|
repos. Output is byte-identical to a single-process run, and any worker failure
|
|
@@ -735,10 +846,22 @@ with `--engine codegraph`.
|
|
|
735
846
|
```bash
|
|
736
847
|
cdd-kit graph status
|
|
737
848
|
cdd-kit graph query OrderService
|
|
849
|
+
cdd-kit graph query OrderService --with-source # include code inline; no follow-up Read needed
|
|
738
850
|
cdd-kit graph context "filter options are empty"
|
|
739
851
|
cdd-kit graph impact src/services/orders.ts --depth 2
|
|
740
852
|
```
|
|
741
853
|
|
|
854
|
+
`--with-source` (also on `cdd-kit index query`, and `withSource: true` via MCP)
|
|
855
|
+
returns the matched symbol's code inline so the query *replaces* a `Read` rather
|
|
856
|
+
than preceding it — making the kit tool strictly cheaper than the built-in
|
|
857
|
+
`Read`. `--source-budget <n>` caps total lines returned; truncated ranges are
|
|
858
|
+
flagged so you can `Read` only those.
|
|
859
|
+
|
|
860
|
+
To make graph-first exploration a real chokepoint instead of a prompt
|
|
861
|
+
preference, wire the shipped `hooks/pre-tool-use-graph-first.sh` as a
|
|
862
|
+
`PreToolUse` hook on `Read` (advisory by default; `CDD_GRAPH_FIRST_STRICT=1`
|
|
863
|
+
hard-blocks source `Read`s when a code-map exists).
|
|
864
|
+
|
|
742
865
|
Use `--engine native` for the built-in graph, `--engine codemap` for the older
|
|
743
866
|
code-map-only fallback, `--engine codegraph` to require external CodeGraph, or
|
|
744
867
|
`CDD_CODEGRAPH_BIN=/path/to/codegraph` to point at a custom binary.
|
|
@@ -63,6 +63,19 @@ Prefer these MCP tools before reading source files: `cdd_graph_context`,
|
|
|
63
63
|
available, use the equivalent CLI commands: `cdd-kit graph ...` and
|
|
64
64
|
`cdd-kit index ...`.
|
|
65
65
|
|
|
66
|
+
Pass `withSource: true` (MCP) or `--with-source` (CLI) on `query` to get the
|
|
67
|
+
matched symbol's code inline. The query then replaces a follow-up `Read` instead
|
|
68
|
+
of preceding it — use a plain `Read` only for ranges the query did not return
|
|
69
|
+
(e.g. a range flagged as source-budget truncated).
|
|
70
|
+
|
|
71
|
+
## API Conformance
|
|
72
|
+
|
|
73
|
+
If `.cdd/conformance.json` has `"enabled": true`, `cdd-kit validate --contracts`
|
|
74
|
+
(and `cdd-kit gate`) mechanically check real backend routes and frontend call
|
|
75
|
+
sites against `contracts/api/api-contract.md`. Do not add, rename, or call an
|
|
76
|
+
endpoint without updating the contract in the same change, or the gate will fail
|
|
77
|
+
on the drift. See `docs/api-conformance.md`.
|
|
78
|
+
|
|
66
79
|
## Context Governance
|
|
67
80
|
|
|
68
81
|
For context-governed changes, read `specs/changes/<change-id>/context-manifest.md` before using file-reading or broad search tools.
|
|
@@ -11,7 +11,8 @@ Before editing production code, read `specs/changes/<change-id>/implementation-p
|
|
|
11
11
|
|
|
12
12
|
## Code map (READ FIRST)
|
|
13
13
|
|
|
14
|
-
Before reading ANY source file (`.py`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.vue`), FIRST run `cdd-kit index query "<symbol-or-file>"` or `Read .cdd/code-map.yml`.
|
|
14
|
+
Before reading ANY source file (`.py`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.vue`), FIRST run `cdd-kit index query "<symbol-or-file>" --with-source` or `Read .cdd/code-map.yml`.
|
|
15
|
+
Prefer `--with-source`: it returns the matched symbol's code inline, so you do NOT need a separate `Read` for that range. Use a plain `Read` only when you need lines the query did not return (e.g. a range flagged as source-budget truncated).
|
|
15
16
|
Before editing a chosen source file, run `cdd-kit index impact "<path-or-symbol>"` to identify indexed local imports and dependents.
|
|
16
17
|
|
|
17
18
|
The map is the size oracle. For each file you intend to read:
|
|
@@ -31,6 +32,7 @@ See `references/code-map-protocol.md` for the full protocol.
|
|
|
31
32
|
|
|
32
33
|
## Rules
|
|
33
34
|
|
|
35
|
+
- Do not change API response shape or add/rename/remove endpoints without updating `contracts/api/api-contract.md` in the same change. If `.cdd/conformance.json` is enabled, `cdd-kit validate --contracts` (and the gate) will fail when a backend route is missing from the contract.
|
|
34
36
|
- Do not change API response shape without contract updates.
|
|
35
37
|
- Keep route/controller code thin.
|
|
36
38
|
- Put business logic in service/domain layers.
|
|
@@ -11,7 +11,8 @@ Before editing, read `specs/changes/<change-id>/implementation-plan.md`, API con
|
|
|
11
11
|
|
|
12
12
|
## Code map (READ FIRST)
|
|
13
13
|
|
|
14
|
-
Before reading ANY source file (`.py`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.vue`), FIRST run `cdd-kit graph query "<symbol-or-file>"`, `cdd-kit graph context "<task>"`, `cdd-kit index query "<symbol-or-file>"`, or `Read .cdd/code-map.yml`.
|
|
14
|
+
Before reading ANY source file (`.py`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.vue`), FIRST run `cdd-kit graph query "<symbol-or-file>" --with-source`, `cdd-kit graph context "<task>"`, `cdd-kit index query "<symbol-or-file>" --with-source`, or `Read .cdd/code-map.yml`.
|
|
15
|
+
Prefer `--with-source`: it returns the matched symbol's code inline, so you do NOT need a separate `Read` for that range. Use a plain `Read` only for lines the query did not return (e.g. a range flagged as source-budget truncated).
|
|
15
16
|
Before editing a chosen source file, run `cdd-kit graph impact "<path-or-symbol>" --depth 2` or `cdd-kit index impact "<path-or-symbol>"` to identify imports, dependents, callers/callees when available, and likely affected scope.
|
|
16
17
|
|
|
17
18
|
The map is the size oracle. For each file you intend to read:
|
|
@@ -31,7 +32,7 @@ See `references/code-map-protocol.md` for the full protocol.
|
|
|
31
32
|
|
|
32
33
|
## Rules
|
|
33
34
|
|
|
34
|
-
- Do not assume backend response shape; use the API contract.
|
|
35
|
+
- Do not assume backend response shape; use the API contract. Do not call an endpoint (path + method) that is not in `contracts/api/api-contract.md`. If `.cdd/conformance.json` is enabled, `cdd-kit validate --contracts` (and the gate) will fail on frontend calls that drift from the contract.
|
|
35
36
|
- Follow `implementation-plan.md` for scope, non-goals, required changes, and file-level plan.
|
|
36
37
|
- Do not expand scope beyond the implementation plan unless a Context Expansion Request is approved and the plan is updated.
|
|
37
38
|
- Do not hard-code visual tokens when token system exists.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_README": "Code-vs-contract API conformance. Run via `cdd-kit validate --contracts` (and `cdd-kit gate`). Set enabled:true to mechanically catch frontend/backend drift against contracts/api/api-contract.md. See docs/api-conformance.md.",
|
|
3
|
+
"enabled": false,
|
|
4
|
+
"apiPrefixes": ["/api"],
|
|
5
|
+
"sourceRoots": [],
|
|
6
|
+
"backendGlobsExt": [".py", ".js", ".ts", ".mjs", ".cjs", ".go", ".java", ".php"],
|
|
7
|
+
"frontendGlobsExt": [".js", ".jsx", ".ts", ".tsx", ".mjs", ".vue", ".svelte"],
|
|
8
|
+
"excludeDirs": ["node_modules", "dist", "build", ".git", ".cdd", "coverage", "vendor", "__pycache__", ".next", ".nuxt"],
|
|
9
|
+
"ignorePaths": ["/health", "/metrics"],
|
|
10
|
+
"checks": {
|
|
11
|
+
"backendRouteNotInContract": "warning",
|
|
12
|
+
"contractEndpointNotImplemented": "warning",
|
|
13
|
+
"frontendCallNotInContract": "error"
|
|
14
|
+
},
|
|
15
|
+
"strict": false
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_README": "Mechanical risk-tier floor. A classifier (or any agent) proposes a tier in change-classification.md / tasks.yml; this policy is the safety net that prevents a high-risk surface from being silently under-classified. `cdd-kit gate` and `cdd-kit classify-check` scan change-request.md against ALL rules, and the change's git paths against the critical (maxTier 0) rules only (file paths are noisier than prose) — gate scans the STAGED change (what is about to be committed), classify-check scans the whole worktree (the in-progress change is not yet committed) — then require the declared tier to be at least as strict as the matched floor. Lower tier number = stricter. Set enabled:false to disable, or record `tier-floor-override: \"<reason>\"` in a change's tasks.yml frontmatter to bypass for one change with an audit trail.",
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"schema-version": "0.1.0",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"maxTier": 0,
|
|
8
|
+
"label": "critical surface (auth / payments / data migration / concurrency / secrets)",
|
|
9
|
+
"patterns": [
|
|
10
|
+
"auth", "authn", "authz",
|
|
11
|
+
"authentication", "authorization", "authenticate", "authorize",
|
|
12
|
+
"authenticated", "authorized",
|
|
13
|
+
"login", "logout", "sign-?in", "sign-?up",
|
|
14
|
+
"passwords?", "passwd", "credentials?", "secrets?", "api[- ]?keys?",
|
|
15
|
+
"(access|api|auth|session|bearer|refresh|csrf|id|reset)[- ]?tokens?",
|
|
16
|
+
"jwt", "oauth", "oidc", "saml", "sessions?", "cookies?",
|
|
17
|
+
"payments?", "billing", "invoices?", "charges?", "refunds?", "checkout", "stripe", "paypal",
|
|
18
|
+
"migrations?", "migrate", "alter table", "drop table", "drop column", "schema change",
|
|
19
|
+
"concurrency", "race condition", "mutex", "deadlock", "transaction isolation",
|
|
20
|
+
"encrypt", "decrypt", "crypto", "hashing", "rbac", "permissions?", "access control",
|
|
21
|
+
"privileges?", "pii", "gdpr", "hipaa", "rate limit", "csrf", "xss", "sql injection"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"maxTier": 2,
|
|
26
|
+
"label": "behavioral surface (api / data shape / queue / cache / external integration)",
|
|
27
|
+
"patterns": [
|
|
28
|
+
"endpoint", "route", "api contract", "request schema", "response schema",
|
|
29
|
+
"pagination", "queue", "worker", "cron", "scheduler", "webhook",
|
|
30
|
+
"cache", "redis", "database", "query", "index", "external service",
|
|
31
|
+
"third[- ]?party", "integration", "data shape", "nullable", "breaking change"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -21,6 +21,32 @@ breaking-change-policy: deprecate-2-minors
|
|
|
21
21
|
| method | path | auth | request schema | response schema | errors | tests |
|
|
22
22
|
|---|---|---|---|---|---|---|
|
|
23
23
|
|
|
24
|
+
## Schemas
|
|
25
|
+
|
|
26
|
+
<!--
|
|
27
|
+
Optional. Add named schemas here when request/response bodies should become
|
|
28
|
+
machine-typed in `cdd-kit openapi export`. Reference a schema by name in the
|
|
29
|
+
endpoint table's "request schema" / "response schema" cell (use `Name[]` for an
|
|
30
|
+
array). A schema is defined ONE of two ways — never both:
|
|
31
|
+
|
|
32
|
+
Tier A — a field table (preferred; readable, diffable):
|
|
33
|
+
|
|
34
|
+
### ExampleRequest
|
|
35
|
+
| field | type | required | format | notes |
|
|
36
|
+
|---|---|---|---|---|
|
|
37
|
+
| email | string | yes | email | login identity |
|
|
38
|
+
| status | enum(active, disabled) | no | | lifecycle state |
|
|
39
|
+
| owner | ExampleUser | no | | reference another schema by name |
|
|
40
|
+
|
|
41
|
+
Tier B — a raw JSON Schema, for shapes Tier A can't express (oneOf, etc.).
|
|
42
|
+
The fence MUST be tagged `json-schema` (NOT `json`) or export fails fast:
|
|
43
|
+
|
|
44
|
+
### ExampleEvent
|
|
45
|
+
```json-schema
|
|
46
|
+
{ "type": "object", "oneOf": [ { "required": ["createdAt"] }, { "required": ["deletedAt"] } ] }
|
|
47
|
+
```
|
|
48
|
+
-->
|
|
49
|
+
|
|
24
50
|
## Error Format
|
|
25
51
|
|
|
26
52
|
## Compatibility Policy
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# cdd-kit PreToolUse hook (opt-in): steer agents to graph-first exploration.
|
|
3
|
+
#
|
|
4
|
+
# Prose in agent prompts ("run cdd-kit index query before reading") is a soft
|
|
5
|
+
# preference that loses to the model's built-in habit of reaching for Read.
|
|
6
|
+
# This hook turns that preference into an actual chokepoint: when an agent is
|
|
7
|
+
# about to Read a *source* file and a code-map exists, it reminds the agent to
|
|
8
|
+
# use `cdd-kit index query "<symbol>" --with-source` (which returns the code
|
|
9
|
+
# inline, so the Read is usually unnecessary).
|
|
10
|
+
#
|
|
11
|
+
# Default mode is ADVISORY: it prints guidance to stderr and allows the Read.
|
|
12
|
+
# Set CDD_GRAPH_FIRST_STRICT=1 to BLOCK the Read instead (exit 2), forcing the
|
|
13
|
+
# graph-first path. Contract/spec/markdown/Read of .cdd/code-map.yml itself are
|
|
14
|
+
# always allowed.
|
|
15
|
+
#
|
|
16
|
+
# Wire into Claude Code (~/.claude/settings.json):
|
|
17
|
+
#
|
|
18
|
+
# {
|
|
19
|
+
# "hooks": {
|
|
20
|
+
# "PreToolUse": [
|
|
21
|
+
# { "matcher": "Read", "command": "/path/to/hooks/pre-tool-use-graph-first.sh" }
|
|
22
|
+
# ]
|
|
23
|
+
# }
|
|
24
|
+
# }
|
|
25
|
+
#
|
|
26
|
+
# The hook receives the tool-call payload as JSON on stdin.
|
|
27
|
+
|
|
28
|
+
set -eu
|
|
29
|
+
|
|
30
|
+
# No code-map → nothing to steer toward; allow.
|
|
31
|
+
[ -f ".cdd/code-map.yml" ] || exit 0
|
|
32
|
+
|
|
33
|
+
payload="$(cat || true)"
|
|
34
|
+
[ -z "$payload" ] && exit 0
|
|
35
|
+
|
|
36
|
+
# Extract the Read target path.
|
|
37
|
+
path_value=""
|
|
38
|
+
if command -v jq >/dev/null 2>&1; then
|
|
39
|
+
path_value="$(printf '%s' "$payload" | jq -r '.tool_input.file_path // empty' 2>/dev/null || true)"
|
|
40
|
+
fi
|
|
41
|
+
if [ -z "$path_value" ]; then
|
|
42
|
+
path_value="$(printf '%s' "$payload" | grep -oE '"file_path"[[:space:]]*:[[:space:]]*"[^"]+"' | head -n1 | sed -E 's/.*"file_path"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
43
|
+
fi
|
|
44
|
+
[ -z "$path_value" ] && exit 0
|
|
45
|
+
|
|
46
|
+
# Only steer for source files; never interfere with docs/specs/contracts/config.
|
|
47
|
+
case "$path_value" in
|
|
48
|
+
*.py|*.js|*.jsx|*.mjs|*.cjs|*.ts|*.tsx|*.vue|*.svelte|*.go|*.java|*.rb|*.php) : ;;
|
|
49
|
+
*) exit 0 ;;
|
|
50
|
+
esac
|
|
51
|
+
# Allow reading the map itself.
|
|
52
|
+
case "$path_value" in
|
|
53
|
+
*.cdd/code-map.yml) exit 0 ;;
|
|
54
|
+
esac
|
|
55
|
+
|
|
56
|
+
msg="cdd-kit: prefer \`cdd-kit index query \"<symbol-or-file>\" --with-source\` (or \`cdd-kit graph query ... --with-source\`) before Read — it returns the code inline and keeps token use low. Read directly only for ranges the query did not return."
|
|
57
|
+
|
|
58
|
+
if [ "${CDD_GRAPH_FIRST_STRICT:-0}" = "1" ]; then
|
|
59
|
+
# Block and feed the reason back to the model.
|
|
60
|
+
printf '%s\n' "$msg Set CDD_GRAPH_FIRST_STRICT=0 to make this advisory only." 1>&2
|
|
61
|
+
exit 2
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
printf '%s\n' "$msg" 1>&2
|
|
65
|
+
exit 0
|