@drafthq/draft 3.1.5 → 3.2.0

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.
@@ -12,7 +12,7 @@
12
12
  "name": "draft",
13
13
  "source": "./",
14
14
  "description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
15
- "version": "3.0.0",
15
+ "version": "3.2.0",
16
16
  "author": {
17
17
  "name": "mayurpise"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "draft",
3
3
  "description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
4
- "version": "3.1.5",
4
+ "version": "3.2.0",
5
5
  "author": {
6
6
  "name": "mayurpise"
7
7
  },
@@ -56,7 +56,10 @@ module.exports = {
56
56
  ],
57
57
  graph: true, // fetch the graph engine at install time (/draft:init also fetches on first use as a fallback)
58
58
  done: 'Installed/updated draft. Restart Claude Code (or start a new session), then run /draft:init.',
59
- fallbackTitle: 'Claude Code CLI not found. Run these in Claude Code instead:',
59
+ // Shown when the `claude` CLI is absent OR a step fails: the in-session
60
+ // /plugin commands work without the terminal CLI on PATH.
61
+ onFailHint: 'If that failed with "unknown command", your Claude Code is too old for the `claude plugin` command — run "claude update" (or upgrade the app), then re-run.',
62
+ fallbackTitle: 'Run these inside a Claude Code session instead (or put the `claude` CLI on your PATH and re-run):',
60
63
  fallback: [
61
64
  '/plugin marketplace add drafthq/draft',
62
65
  '/plugin marketplace update draft-plugins',
@@ -5,9 +5,13 @@ const fsx = require('./lib/fsx');
5
5
  const log = require('./lib/log');
6
6
  const { fetchGraph } = require('./lib/graph');
7
7
 
8
+ // A short ceiling so a wedged `claude --version` can't hang the installer
9
+ // before we even reach the real (separately-timed) install steps.
10
+ const CHECK_TIMEOUT_MS = 10000;
11
+
8
12
  function hasBinary(name) {
9
13
  // ENOENT on the error means the binary is not on PATH.
10
- const r = spawnSync(name, ['--version'], { stdio: 'ignore' });
14
+ const r = spawnSync(name, ['--version'], { stdio: 'ignore', timeout: CHECK_TIMEOUT_MS });
11
15
  return !(r.error && r.error.code === 'ENOENT');
12
16
  }
13
17
 
@@ -59,12 +63,14 @@ function install(host, ctx) {
59
63
  const plan = host.plan(ctx);
60
64
  log.step(`Installing Draft -> ${host.label} [${plan.targetSummary}]${ctx.dryRun ? ' (dry run)' : ''}`);
61
65
 
62
- // A plan may require an external CLI (e.g. claude). If it's missing, print
63
- // the manual fallback and stopbut only when actually installing; a
64
- // dry run still shows the planned commands.
66
+ // A plan may require an external CLI (e.g. claude). If it's missing, say so
67
+ // loudly, print the manual fallback, and exit non-zero a no-op must never
68
+ // read as success. Only enforced on a real install; a dry run still shows the
69
+ // planned commands.
65
70
  if (plan.requires && !ctx.dryRun && !hasBinary(plan.requires)) {
71
+ log.error(`Cannot auto-install: the "${plan.requires}" CLI is not on your PATH. Nothing was installed.`);
66
72
  printFallback(plan);
67
- return 0;
73
+ return 1;
68
74
  }
69
75
 
70
76
  // Pre-flight: for file copies, every bundled source must exist and guarded
@@ -88,6 +94,7 @@ function install(host, ctx) {
88
94
  const code = execAction(act, ctx);
89
95
  if (code !== 0) {
90
96
  log.error(`Step failed (exit ${code}): ${act.label || act.cmd}`);
97
+ if (plan.onFailHint) log.error(plan.onFailHint);
91
98
  if (plan.fallback) printFallback(plan);
92
99
  return code;
93
100
  }
@@ -55,7 +55,7 @@ A single "no" / "list" answer is a halt — fix and re-check before output.
55
55
 
56
56
  Use this recipe whenever the user names a concept, feature, or domain term ("in-memory shuffle", "auth flow", "ingest pipeline") and you need to locate the implementing files. **Run it before any filesystem search.**
57
57
 
58
- 1. **Concept → modules** — query the engine for the package list (`scripts/tools/graph-arch.sh --repo . | jq -r '.packages[].name'`) and cross-reference `draft/.ai-context.md` (module headings). Record the candidate module list.
58
+ 1. **Concept → modules** — query the engine for the package list (`scripts/tools/graph-arch.sh --repo . | jq -r '.packages[].name'`) and cross-reference `draft/.ai-context.md` (module headings). Record the candidate module list. For an **intent/concept** name (not an exact symbol), start with semantic search: `scripts/tools/graph-search.sh --repo . --query "<concept>"` returns ranked candidate symbols directly.
59
59
  2. **Concept → symbols/callers** — for a named function, run `scripts/tools/graph-callers.sh --repo . --symbol <name>` to find call sites, and `scripts/tools/graph-impact.sh --repo . --symbol <name>` for transitive dependents. These are the authoritative structural answers.
60
60
  3. **Modules → risk ranking** — rank with `scripts/tools/hotspot-rank.sh --repo . [--top N]`. High-fanIn symbols are the most likely entry points for impact.
61
61
  4. **Concept → public API** — for API-shaped concepts, read the engine's `.routes` (`get_architecture` output, detected HTTP/gRPC/GraphQL routes) for matching service surface.
@@ -102,12 +102,76 @@ DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
102
102
 
103
103
  | Wrapper | Graph mode | Behavior on missing graph |
104
104
  |---|---|---|
105
- | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N] [--module NAME]` | `--mode hotspots` | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
106
- | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | `--mode cycles` | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
107
- | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|proto-map]` | `--mode mermaid` | Emits an empty mermaid block and exits 2 |
105
+ | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N]` | complexity-weighted hotspots | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
106
+ | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | call cycles | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
107
+ | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|co-change\|proto-map]` | diagram text | Emits an empty mermaid block and exits 2 |
108
+ | `bash "$DRAFT_TOOLS/graph-callers.sh" --symbol N [--transitive[=N]] [--prod-only] [--qualified]` | callers | `{callers:[],status:"unavailable",source:"unavailable"}`, exit 2 |
109
+ | `bash "$DRAFT_TOOLS/graph-snippet.sh" --qualified N` | verified source + caller/callee counts | `{status:"unavailable",source:"unavailable"}`, exit 2 |
110
+ | `bash "$DRAFT_TOOLS/graph-search.sh" --query "STR" [--limit N]` | semantic/ranked search | `{results:[],source:"unavailable"}`, exit 2 |
111
+ | `bash "$DRAFT_TOOLS/graph-tests.sh" (--symbol N \| --untested)` | test→symbol coverage | `{tests:[]/untested:[],source:"unavailable"}`, exit 2 |
112
+ | `bash "$DRAFT_TOOLS/graph-deps.sh" [--file PATH]` | real IMPORTS graph | `{imports:[],source:"unavailable"}`, exit 2 |
113
+ | `bash "$DRAFT_TOOLS/graph-hierarchy.sh" [--symbol N \| --derived N]` | INHERITS tree | `{edges:[],source:"unavailable"}`, exit 2 |
114
+ | `bash "$DRAFT_TOOLS/graph-errors.sh" (--symbol N \| --type N)` | RAISES/THROWS | `{raises:[]/raisers:[],source:"unavailable"}`, exit 2 |
115
+ | `bash "$DRAFT_TOOLS/graph-risk.sh" [--min-complexity N]` | pre-computed risk flags | `{risky:[],source:"unavailable"}`, exit 2 |
116
+ | `bash "$DRAFT_TOOLS/graph-query.sh" (--cypher STR \| --tool NAME --json '{...}')` | generic read-only passthrough | `{source:"unavailable"}`, exit 2 |
117
+ | `bash "$DRAFT_TOOLS/graph-traces.sh" ingest --file F --experimental` | runtime traces (experimental write) | `{source:"unavailable"}`, exit 2 |
108
118
 
109
119
  For lower-level modes, call the engine directly: `codebase-memory-mcp cli <tool> '<json>'` (see the tool list in [bin/README.md](../../bin/README.md)).
110
120
 
121
+ ### Capability wrappers & dialect limits (graph-tooling-v2)
122
+
123
+ All Cypher lives in `scripts/tools/_graph_queries.sh` (the single source of query
124
+ truth). Wrappers are thin arg-parse → builder → fail-loud JSON. Three contracts
125
+ matter when consuming them:
126
+
127
+ **Fail-loud status.** Symbol-scoped wrappers (`graph-callers`, `graph-snippet`,
128
+ `graph-tests --symbol`, `graph-hierarchy --symbol/--derived`, `graph-errors`)
129
+ emit a `status` field that distinguishes the three real outcomes — never read a
130
+ bare `[]` as a confirmed true negative:
131
+
132
+ | `status` | Meaning |
133
+ |---|---|
134
+ | `ok` | node found, edges returned |
135
+ | `no-edges` | node exists but has no matching edge (a *real* negative) |
136
+ | `no-match` | the named symbol was not found at all (check the name / try `--qualified`) |
137
+ | `unavailable` | engine could not be resolved (exit 2) |
138
+
139
+ **Verified engine param shapes** (engine v0.8.x — the runtime source of truth is
140
+ `get_graph_schema`; do not hardcode a property set):
141
+
142
+ ```bash
143
+ get_code_snippet '{"project":P,"qualified_name":"pkg.Mod.Class.method"}' # → source + callers/callees counts + transitive_loop_depth
144
+ search_graph '{"project":P,"query":"order submission to broker","limit":5}' # → {results:[{name,qualified_name,label,file_path,rank}]}
145
+ trace_path '{"project":P,"function_name":"submit_order","depth":3,"direction":"both"}' # depth-bounded caller EXPANDER, not an A→B pathfinder
146
+ detect_changes '{"project":P}' # → {changed_files, changed_count, impacted_symbols, depth}
147
+ get_graph_schema '{"project":P}' # → {node_labels:[{label,count,properties}], edge_types:[{type,count}]}
148
+ ```
149
+
150
+ **Cypher dialect — keep queries inside the SAFE set:**
151
+
152
+ - ✅ SAFE: fixed-length patterns, single/multi-hop explicit patterns, `=`, `<`,
153
+ `STARTS WITH`, `NOT x STARTS WITH`, `AND`, `OR`, relationship-type alternation
154
+ `[:A|B]`, simple `count(x)`.
155
+ - ❌ UNSAFE (rejected or silently empty): `coalesce()`, `<>` / `!=` / `<=` / `>=`,
156
+ `NOT EXISTS(...)`, `NOT (pattern)`, `WITH`-grouping aggregation, multi-pattern
157
+ joins. `graph-query.sh --cypher` returns the engine's raw error, not a silent
158
+ empty — but the builders never emit these forms.
159
+
160
+ **Caveats consumers must respect:**
161
+
162
+ - **`--prod-only` is best-effort.** It filters `is_test=false AND NOT file_path
163
+ STARTS WITH 'tests/'`. `is_test` is partially populated by the engine, so test
164
+ helpers/mocks can leak through. Treat it as a heuristic, not a guarantee.
165
+ - **`--transitive` uses the `trace_path` expander** (a depth-bounded caller
166
+ expansion from one symbol), not a from→to pathfinder. "Path between A and B"
167
+ still needs an explicit fixed-length `graph-query.sh --cypher` pattern.
168
+ - **Honest caps.** `cycle-detect`, `graph-deps`, `graph-risk`, `graph-tests
169
+ --untested` cap their output and report `"truncated": true` when the cap is
170
+ hit — results are a sample, not exhaustive.
171
+ - **`graph-tests --untested`** is a set difference (exported symbols minus TESTS
172
+ targets) because the dialect has no anti-join; coverage depends on the engine
173
+ resolving test→symbol links, which varies by language/framework.
174
+
111
175
  ## Pre-Check
112
176
 
113
177
  Verify graph data exists before any graph operation:
@@ -196,13 +260,80 @@ scripts/tools/mermaid-from-graph.sh --repo . --diagram proto-map # detected
196
260
 
197
261
  Emits a ready-to-inject ` ```mermaid ``` ` block on the fly (computed live by the engine), or an empty stub (exit 2) when the engine is unavailable. Diagrams are generated at the moment of use — they are never committed.
198
262
 
263
+ ### Snippet — verified source + caller/callee counts
264
+
265
+ ```bash
266
+ scripts/tools/graph-snippet.sh --repo . --qualified <pkg.Mod.Class.method>
267
+ ```
268
+
269
+ Output: `{qualified_name, file, start_line, end_line, callers, callees, transitive_loop_depth, complexity, code, status, source}`. Prefer this over grep+Read when you have a qualified name — it returns the engine's attributed source plus pre-computed counts.
270
+
271
+ ### Search — semantic / ranked symbol lookup
272
+
273
+ ```bash
274
+ scripts/tools/graph-search.sh --repo . --query "auth token refresh" [--limit N]
275
+ ```
276
+
277
+ Output: `{query, results[{name, qualified_name, label, file, rank}], total, source}`. Use when the user names an **intent/concept** rather than an exact symbol — this is the first move in the Concept-to-Files recipe.
278
+
279
+ ### Tests — coverage edges and untested surface
280
+
281
+ ```bash
282
+ scripts/tools/graph-tests.sh --repo . --symbol <name> # tests covering a symbol
283
+ scripts/tools/graph-tests.sh --repo . --untested # exported symbols with no TESTS edge
284
+ ```
285
+
286
+ Output: `{symbol, tests[{test,file}], status, source}` or `{untested[{symbol,file}], total, truncated, source}`. Feeds coverage gaps for `init`/`testing-strategy`/`coverage`.
287
+
288
+ ### Deps — real module/file import graph
289
+
290
+ ```bash
291
+ scripts/tools/graph-deps.sh --repo . [--file PATH]
292
+ ```
293
+
294
+ Output: `{imports[{src,dst}], total, truncated, source}` from actual `IMPORTS` edges (self-imports filtered). This is the auto-derived dependency graph behind `mermaid-from-graph.sh --diagram module-deps` and `architecture.md §9`.
295
+
296
+ ### Hierarchy — class inheritance
297
+
298
+ ```bash
299
+ scripts/tools/graph-hierarchy.sh --repo . [--symbol <Class> | --derived <Base>]
300
+ ```
301
+
302
+ Output: `{edges[{child,parent}], status, source}`. `--derived` gives the blast radius of changing a base class.
303
+
304
+ ### Errors — error-propagation paths
305
+
306
+ ```bash
307
+ scripts/tools/graph-errors.sh --repo . --symbol <name> # what it raises/throws
308
+ scripts/tools/graph-errors.sh --repo . --type <ErrType> # who raises/throws that type
309
+ ```
310
+
311
+ Output: `{symbol, raises[...], status, source}` or `{type, raisers[...], status, source}`. `--type` drives fail-closed audits.
312
+
313
+ ### Risk — pre-computed risk hotspots
314
+
315
+ ```bash
316
+ scripts/tools/graph-risk.sh --repo . [--min-complexity N]
317
+ ```
318
+
319
+ Output: `{risky[{symbol, file, complexity, flags}], total, truncated, source}` from the engine's pre-computed flags (`unguarded_recursion`, `recursion_in_loop`, `alloc_in_loop`, `linear_scan_in_loop`). High-signal input for `bughunt`/`deep-review` — the engine already found these.
320
+
321
+ ### Generic — read-only escape hatch (all 20 edges / ~30 properties)
322
+
323
+ ```bash
324
+ scripts/tools/graph-query.sh --repo . --cypher 'MATCH (f)-[:WRITES]->(v) RETURN f.name, v.name LIMIT 50'
325
+ scripts/tools/graph-query.sh --repo . --tool get_graph_schema --json '{}'
326
+ ```
327
+
328
+ Unlocks any edge type or node property without a purpose-built wrapper. Write verbs are rejected; stay inside the SAFE dialect set (above). Emits raw engine JSON.
329
+
199
330
  ### Indexing / refreshing the gate marker
200
331
 
201
332
  ```bash
202
333
  scripts/tools/graph-snapshot.sh --repo .
203
334
  ```
204
335
 
205
- Indexes the repo into the engine and writes the `draft/graph/schema.yaml` gate marker. It writes **no** graph data. Run during `/draft:init` and `/draft:graph`, or whenever the index should be refreshed.
336
+ Indexes the repo into the engine and writes the `draft/graph/schema.yaml` gate marker (now including the `detect_changes` delta: `changed_files`/`impacted_symbols`). It writes **no** graph data. Run during `/draft:init` and `/draft:graph`, or whenever the index should be refreshed.
206
337
 
207
338
  ## Finding the Engine (Resolution + Usage Report)
208
339
 
@@ -16899,7 +16899,7 @@ A single "no" / "list" answer is a halt — fix and re-check before output.
16899
16899
 
16900
16900
  Use this recipe whenever the user names a concept, feature, or domain term ("in-memory shuffle", "auth flow", "ingest pipeline") and you need to locate the implementing files. **Run it before any filesystem search.**
16901
16901
 
16902
- 1. **Concept → modules** — query the engine for the package list (`scripts/tools/graph-arch.sh --repo . | jq -r '.packages[].name'`) and cross-reference `draft/.ai-context.md` (module headings). Record the candidate module list.
16902
+ 1. **Concept → modules** — query the engine for the package list (`scripts/tools/graph-arch.sh --repo . | jq -r '.packages[].name'`) and cross-reference `draft/.ai-context.md` (module headings). Record the candidate module list. For an **intent/concept** name (not an exact symbol), start with semantic search: `scripts/tools/graph-search.sh --repo . --query "<concept>"` returns ranked candidate symbols directly.
16903
16903
  2. **Concept → symbols/callers** — for a named function, run `scripts/tools/graph-callers.sh --repo . --symbol <name>` to find call sites, and `scripts/tools/graph-impact.sh --repo . --symbol <name>` for transitive dependents. These are the authoritative structural answers.
16904
16904
  3. **Modules → risk ranking** — rank with `scripts/tools/hotspot-rank.sh --repo . [--top N]`. High-fanIn symbols are the most likely entry points for impact.
16905
16905
  4. **Concept → public API** — for API-shaped concepts, read the engine's `.routes` (`get_architecture` output, detected HTTP/gRPC/GraphQL routes) for matching service surface.
@@ -16946,12 +16946,76 @@ DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
16946
16946
 
16947
16947
  | Wrapper | Graph mode | Behavior on missing graph |
16948
16948
  |---|---|---|
16949
- | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N] [--module NAME]` | `--mode hotspots` | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
16950
- | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | `--mode cycles` | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
16951
- | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|proto-map]` | `--mode mermaid` | Emits an empty mermaid block and exits 2 |
16949
+ | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N]` | complexity-weighted hotspots | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
16950
+ | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | call cycles | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
16951
+ | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|co-change\|proto-map]` | diagram text | Emits an empty mermaid block and exits 2 |
16952
+ | `bash "$DRAFT_TOOLS/graph-callers.sh" --symbol N [--transitive[=N]] [--prod-only] [--qualified]` | callers | `{callers:[],status:"unavailable",source:"unavailable"}`, exit 2 |
16953
+ | `bash "$DRAFT_TOOLS/graph-snippet.sh" --qualified N` | verified source + caller/callee counts | `{status:"unavailable",source:"unavailable"}`, exit 2 |
16954
+ | `bash "$DRAFT_TOOLS/graph-search.sh" --query "STR" [--limit N]` | semantic/ranked search | `{results:[],source:"unavailable"}`, exit 2 |
16955
+ | `bash "$DRAFT_TOOLS/graph-tests.sh" (--symbol N \| --untested)` | test→symbol coverage | `{tests:[]/untested:[],source:"unavailable"}`, exit 2 |
16956
+ | `bash "$DRAFT_TOOLS/graph-deps.sh" [--file PATH]` | real IMPORTS graph | `{imports:[],source:"unavailable"}`, exit 2 |
16957
+ | `bash "$DRAFT_TOOLS/graph-hierarchy.sh" [--symbol N \| --derived N]` | INHERITS tree | `{edges:[],source:"unavailable"}`, exit 2 |
16958
+ | `bash "$DRAFT_TOOLS/graph-errors.sh" (--symbol N \| --type N)` | RAISES/THROWS | `{raises:[]/raisers:[],source:"unavailable"}`, exit 2 |
16959
+ | `bash "$DRAFT_TOOLS/graph-risk.sh" [--min-complexity N]` | pre-computed risk flags | `{risky:[],source:"unavailable"}`, exit 2 |
16960
+ | `bash "$DRAFT_TOOLS/graph-query.sh" (--cypher STR \| --tool NAME --json '{...}')` | generic read-only passthrough | `{source:"unavailable"}`, exit 2 |
16961
+ | `bash "$DRAFT_TOOLS/graph-traces.sh" ingest --file F --experimental` | runtime traces (experimental write) | `{source:"unavailable"}`, exit 2 |
16952
16962
 
16953
16963
  For lower-level modes, call the engine directly: `codebase-memory-mcp cli <tool> '<json>'` (see the tool list in [bin/README.md](../../bin/README.md)).
16954
16964
 
16965
+ ### Capability wrappers & dialect limits (graph-tooling-v2)
16966
+
16967
+ All Cypher lives in `scripts/tools/_graph_queries.sh` (the single source of query
16968
+ truth). Wrappers are thin arg-parse → builder → fail-loud JSON. Three contracts
16969
+ matter when consuming them:
16970
+
16971
+ **Fail-loud status.** Symbol-scoped wrappers (`graph-callers`, `graph-snippet`,
16972
+ `graph-tests --symbol`, `graph-hierarchy --symbol/--derived`, `graph-errors`)
16973
+ emit a `status` field that distinguishes the three real outcomes — never read a
16974
+ bare `[]` as a confirmed true negative:
16975
+
16976
+ | `status` | Meaning |
16977
+ |---|---|
16978
+ | `ok` | node found, edges returned |
16979
+ | `no-edges` | node exists but has no matching edge (a *real* negative) |
16980
+ | `no-match` | the named symbol was not found at all (check the name / try `--qualified`) |
16981
+ | `unavailable` | engine could not be resolved (exit 2) |
16982
+
16983
+ **Verified engine param shapes** (engine v0.8.x — the runtime source of truth is
16984
+ `get_graph_schema`; do not hardcode a property set):
16985
+
16986
+ ```bash
16987
+ get_code_snippet '{"project":P,"qualified_name":"pkg.Mod.Class.method"}' # → source + callers/callees counts + transitive_loop_depth
16988
+ search_graph '{"project":P,"query":"order submission to broker","limit":5}' # → {results:[{name,qualified_name,label,file_path,rank}]}
16989
+ trace_path '{"project":P,"function_name":"submit_order","depth":3,"direction":"both"}' # depth-bounded caller EXPANDER, not an A→B pathfinder
16990
+ detect_changes '{"project":P}' # → {changed_files, changed_count, impacted_symbols, depth}
16991
+ get_graph_schema '{"project":P}' # → {node_labels:[{label,count,properties}], edge_types:[{type,count}]}
16992
+ ```
16993
+
16994
+ **Cypher dialect — keep queries inside the SAFE set:**
16995
+
16996
+ - ✅ SAFE: fixed-length patterns, single/multi-hop explicit patterns, `=`, `<`,
16997
+ `STARTS WITH`, `NOT x STARTS WITH`, `AND`, `OR`, relationship-type alternation
16998
+ `[:A|B]`, simple `count(x)`.
16999
+ - ❌ UNSAFE (rejected or silently empty): `coalesce()`, `<>` / `!=` / `<=` / `>=`,
17000
+ `NOT EXISTS(...)`, `NOT (pattern)`, `WITH`-grouping aggregation, multi-pattern
17001
+ joins. `graph-query.sh --cypher` returns the engine's raw error, not a silent
17002
+ empty — but the builders never emit these forms.
17003
+
17004
+ **Caveats consumers must respect:**
17005
+
17006
+ - **`--prod-only` is best-effort.** It filters `is_test=false AND NOT file_path
17007
+ STARTS WITH 'tests/'`. `is_test` is partially populated by the engine, so test
17008
+ helpers/mocks can leak through. Treat it as a heuristic, not a guarantee.
17009
+ - **`--transitive` uses the `trace_path` expander** (a depth-bounded caller
17010
+ expansion from one symbol), not a from→to pathfinder. "Path between A and B"
17011
+ still needs an explicit fixed-length `graph-query.sh --cypher` pattern.
17012
+ - **Honest caps.** `cycle-detect`, `graph-deps`, `graph-risk`, `graph-tests
17013
+ --untested` cap their output and report `"truncated": true` when the cap is
17014
+ hit — results are a sample, not exhaustive.
17015
+ - **`graph-tests --untested`** is a set difference (exported symbols minus TESTS
17016
+ targets) because the dialect has no anti-join; coverage depends on the engine
17017
+ resolving test→symbol links, which varies by language/framework.
17018
+
16955
17019
  ## Pre-Check
16956
17020
 
16957
17021
  Verify graph data exists before any graph operation:
@@ -17040,13 +17104,80 @@ scripts/tools/mermaid-from-graph.sh --repo . --diagram proto-map # detected
17040
17104
 
17041
17105
  Emits a ready-to-inject ` ```mermaid ``` ` block on the fly (computed live by the engine), or an empty stub (exit 2) when the engine is unavailable. Diagrams are generated at the moment of use — they are never committed.
17042
17106
 
17107
+ ### Snippet — verified source + caller/callee counts
17108
+
17109
+ ```bash
17110
+ scripts/tools/graph-snippet.sh --repo . --qualified <pkg.Mod.Class.method>
17111
+ ```
17112
+
17113
+ Output: `{qualified_name, file, start_line, end_line, callers, callees, transitive_loop_depth, complexity, code, status, source}`. Prefer this over grep+Read when you have a qualified name — it returns the engine's attributed source plus pre-computed counts.
17114
+
17115
+ ### Search — semantic / ranked symbol lookup
17116
+
17117
+ ```bash
17118
+ scripts/tools/graph-search.sh --repo . --query "auth token refresh" [--limit N]
17119
+ ```
17120
+
17121
+ Output: `{query, results[{name, qualified_name, label, file, rank}], total, source}`. Use when the user names an **intent/concept** rather than an exact symbol — this is the first move in the Concept-to-Files recipe.
17122
+
17123
+ ### Tests — coverage edges and untested surface
17124
+
17125
+ ```bash
17126
+ scripts/tools/graph-tests.sh --repo . --symbol <name> # tests covering a symbol
17127
+ scripts/tools/graph-tests.sh --repo . --untested # exported symbols with no TESTS edge
17128
+ ```
17129
+
17130
+ Output: `{symbol, tests[{test,file}], status, source}` or `{untested[{symbol,file}], total, truncated, source}`. Feeds coverage gaps for `init`/`testing-strategy`/`coverage`.
17131
+
17132
+ ### Deps — real module/file import graph
17133
+
17134
+ ```bash
17135
+ scripts/tools/graph-deps.sh --repo . [--file PATH]
17136
+ ```
17137
+
17138
+ Output: `{imports[{src,dst}], total, truncated, source}` from actual `IMPORTS` edges (self-imports filtered). This is the auto-derived dependency graph behind `mermaid-from-graph.sh --diagram module-deps` and `architecture.md §9`.
17139
+
17140
+ ### Hierarchy — class inheritance
17141
+
17142
+ ```bash
17143
+ scripts/tools/graph-hierarchy.sh --repo . [--symbol <Class> | --derived <Base>]
17144
+ ```
17145
+
17146
+ Output: `{edges[{child,parent}], status, source}`. `--derived` gives the blast radius of changing a base class.
17147
+
17148
+ ### Errors — error-propagation paths
17149
+
17150
+ ```bash
17151
+ scripts/tools/graph-errors.sh --repo . --symbol <name> # what it raises/throws
17152
+ scripts/tools/graph-errors.sh --repo . --type <ErrType> # who raises/throws that type
17153
+ ```
17154
+
17155
+ Output: `{symbol, raises[...], status, source}` or `{type, raisers[...], status, source}`. `--type` drives fail-closed audits.
17156
+
17157
+ ### Risk — pre-computed risk hotspots
17158
+
17159
+ ```bash
17160
+ scripts/tools/graph-risk.sh --repo . [--min-complexity N]
17161
+ ```
17162
+
17163
+ Output: `{risky[{symbol, file, complexity, flags}], total, truncated, source}` from the engine's pre-computed flags (`unguarded_recursion`, `recursion_in_loop`, `alloc_in_loop`, `linear_scan_in_loop`). High-signal input for `bughunt`/`deep-review` — the engine already found these.
17164
+
17165
+ ### Generic — read-only escape hatch (all 20 edges / ~30 properties)
17166
+
17167
+ ```bash
17168
+ scripts/tools/graph-query.sh --repo . --cypher 'MATCH (f)-[:WRITES]->(v) RETURN f.name, v.name LIMIT 50'
17169
+ scripts/tools/graph-query.sh --repo . --tool get_graph_schema --json '{}'
17170
+ ```
17171
+
17172
+ Unlocks any edge type or node property without a purpose-built wrapper. Write verbs are rejected; stay inside the SAFE dialect set (above). Emits raw engine JSON.
17173
+
17043
17174
  ### Indexing / refreshing the gate marker
17044
17175
 
17045
17176
  ```bash
17046
17177
  scripts/tools/graph-snapshot.sh --repo .
17047
17178
  ```
17048
17179
 
17049
- Indexes the repo into the engine and writes the `draft/graph/schema.yaml` gate marker. It writes **no** graph data. Run during `draft init` and `draft graph`, or whenever the index should be refreshed.
17180
+ Indexes the repo into the engine and writes the `draft/graph/schema.yaml` gate marker (now including the `detect_changes` delta: `changed_files`/`impacted_symbols`). It writes **no** graph data. Run during `draft init` and `draft graph`, or whenever the index should be refreshed.
17050
17181
 
17051
17182
  ## Finding the Engine (Resolution + Usage Report)
17052
17183
 
@@ -16899,7 +16899,7 @@ A single "no" / "list" answer is a halt — fix and re-check before output.
16899
16899
 
16900
16900
  Use this recipe whenever the user names a concept, feature, or domain term ("in-memory shuffle", "auth flow", "ingest pipeline") and you need to locate the implementing files. **Run it before any filesystem search.**
16901
16901
 
16902
- 1. **Concept → modules** — query the engine for the package list (`scripts/tools/graph-arch.sh --repo . | jq -r '.packages[].name'`) and cross-reference `draft/.ai-context.md` (module headings). Record the candidate module list.
16902
+ 1. **Concept → modules** — query the engine for the package list (`scripts/tools/graph-arch.sh --repo . | jq -r '.packages[].name'`) and cross-reference `draft/.ai-context.md` (module headings). Record the candidate module list. For an **intent/concept** name (not an exact symbol), start with semantic search: `scripts/tools/graph-search.sh --repo . --query "<concept>"` returns ranked candidate symbols directly.
16903
16903
  2. **Concept → symbols/callers** — for a named function, run `scripts/tools/graph-callers.sh --repo . --symbol <name>` to find call sites, and `scripts/tools/graph-impact.sh --repo . --symbol <name>` for transitive dependents. These are the authoritative structural answers.
16904
16904
  3. **Modules → risk ranking** — rank with `scripts/tools/hotspot-rank.sh --repo . [--top N]`. High-fanIn symbols are the most likely entry points for impact.
16905
16905
  4. **Concept → public API** — for API-shaped concepts, read the engine's `.routes` (`get_architecture` output, detected HTTP/gRPC/GraphQL routes) for matching service surface.
@@ -16946,12 +16946,76 @@ DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
16946
16946
 
16947
16947
  | Wrapper | Graph mode | Behavior on missing graph |
16948
16948
  |---|---|---|
16949
- | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N] [--module NAME]` | `--mode hotspots` | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
16950
- | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | `--mode cycles` | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
16951
- | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|proto-map]` | `--mode mermaid` | Emits an empty mermaid block and exits 2 |
16949
+ | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N]` | complexity-weighted hotspots | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
16950
+ | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | call cycles | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
16951
+ | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|co-change\|proto-map]` | diagram text | Emits an empty mermaid block and exits 2 |
16952
+ | `bash "$DRAFT_TOOLS/graph-callers.sh" --symbol N [--transitive[=N]] [--prod-only] [--qualified]` | callers | `{callers:[],status:"unavailable",source:"unavailable"}`, exit 2 |
16953
+ | `bash "$DRAFT_TOOLS/graph-snippet.sh" --qualified N` | verified source + caller/callee counts | `{status:"unavailable",source:"unavailable"}`, exit 2 |
16954
+ | `bash "$DRAFT_TOOLS/graph-search.sh" --query "STR" [--limit N]` | semantic/ranked search | `{results:[],source:"unavailable"}`, exit 2 |
16955
+ | `bash "$DRAFT_TOOLS/graph-tests.sh" (--symbol N \| --untested)` | test→symbol coverage | `{tests:[]/untested:[],source:"unavailable"}`, exit 2 |
16956
+ | `bash "$DRAFT_TOOLS/graph-deps.sh" [--file PATH]` | real IMPORTS graph | `{imports:[],source:"unavailable"}`, exit 2 |
16957
+ | `bash "$DRAFT_TOOLS/graph-hierarchy.sh" [--symbol N \| --derived N]` | INHERITS tree | `{edges:[],source:"unavailable"}`, exit 2 |
16958
+ | `bash "$DRAFT_TOOLS/graph-errors.sh" (--symbol N \| --type N)` | RAISES/THROWS | `{raises:[]/raisers:[],source:"unavailable"}`, exit 2 |
16959
+ | `bash "$DRAFT_TOOLS/graph-risk.sh" [--min-complexity N]` | pre-computed risk flags | `{risky:[],source:"unavailable"}`, exit 2 |
16960
+ | `bash "$DRAFT_TOOLS/graph-query.sh" (--cypher STR \| --tool NAME --json '{...}')` | generic read-only passthrough | `{source:"unavailable"}`, exit 2 |
16961
+ | `bash "$DRAFT_TOOLS/graph-traces.sh" ingest --file F --experimental` | runtime traces (experimental write) | `{source:"unavailable"}`, exit 2 |
16952
16962
 
16953
16963
  For lower-level modes, call the engine directly: `codebase-memory-mcp cli <tool> '<json>'` (see the tool list in [bin/README.md](../../bin/README.md)).
16954
16964
 
16965
+ ### Capability wrappers & dialect limits (graph-tooling-v2)
16966
+
16967
+ All Cypher lives in `scripts/tools/_graph_queries.sh` (the single source of query
16968
+ truth). Wrappers are thin arg-parse → builder → fail-loud JSON. Three contracts
16969
+ matter when consuming them:
16970
+
16971
+ **Fail-loud status.** Symbol-scoped wrappers (`graph-callers`, `graph-snippet`,
16972
+ `graph-tests --symbol`, `graph-hierarchy --symbol/--derived`, `graph-errors`)
16973
+ emit a `status` field that distinguishes the three real outcomes — never read a
16974
+ bare `[]` as a confirmed true negative:
16975
+
16976
+ | `status` | Meaning |
16977
+ |---|---|
16978
+ | `ok` | node found, edges returned |
16979
+ | `no-edges` | node exists but has no matching edge (a *real* negative) |
16980
+ | `no-match` | the named symbol was not found at all (check the name / try `--qualified`) |
16981
+ | `unavailable` | engine could not be resolved (exit 2) |
16982
+
16983
+ **Verified engine param shapes** (engine v0.8.x — the runtime source of truth is
16984
+ `get_graph_schema`; do not hardcode a property set):
16985
+
16986
+ ```bash
16987
+ get_code_snippet '{"project":P,"qualified_name":"pkg.Mod.Class.method"}' # → source + callers/callees counts + transitive_loop_depth
16988
+ search_graph '{"project":P,"query":"order submission to broker","limit":5}' # → {results:[{name,qualified_name,label,file_path,rank}]}
16989
+ trace_path '{"project":P,"function_name":"submit_order","depth":3,"direction":"both"}' # depth-bounded caller EXPANDER, not an A→B pathfinder
16990
+ detect_changes '{"project":P}' # → {changed_files, changed_count, impacted_symbols, depth}
16991
+ get_graph_schema '{"project":P}' # → {node_labels:[{label,count,properties}], edge_types:[{type,count}]}
16992
+ ```
16993
+
16994
+ **Cypher dialect — keep queries inside the SAFE set:**
16995
+
16996
+ - ✅ SAFE: fixed-length patterns, single/multi-hop explicit patterns, `=`, `<`,
16997
+ `STARTS WITH`, `NOT x STARTS WITH`, `AND`, `OR`, relationship-type alternation
16998
+ `[:A|B]`, simple `count(x)`.
16999
+ - ❌ UNSAFE (rejected or silently empty): `coalesce()`, `<>` / `!=` / `<=` / `>=`,
17000
+ `NOT EXISTS(...)`, `NOT (pattern)`, `WITH`-grouping aggregation, multi-pattern
17001
+ joins. `graph-query.sh --cypher` returns the engine's raw error, not a silent
17002
+ empty — but the builders never emit these forms.
17003
+
17004
+ **Caveats consumers must respect:**
17005
+
17006
+ - **`--prod-only` is best-effort.** It filters `is_test=false AND NOT file_path
17007
+ STARTS WITH 'tests/'`. `is_test` is partially populated by the engine, so test
17008
+ helpers/mocks can leak through. Treat it as a heuristic, not a guarantee.
17009
+ - **`--transitive` uses the `trace_path` expander** (a depth-bounded caller
17010
+ expansion from one symbol), not a from→to pathfinder. "Path between A and B"
17011
+ still needs an explicit fixed-length `graph-query.sh --cypher` pattern.
17012
+ - **Honest caps.** `cycle-detect`, `graph-deps`, `graph-risk`, `graph-tests
17013
+ --untested` cap their output and report `"truncated": true` when the cap is
17014
+ hit — results are a sample, not exhaustive.
17015
+ - **`graph-tests --untested`** is a set difference (exported symbols minus TESTS
17016
+ targets) because the dialect has no anti-join; coverage depends on the engine
17017
+ resolving test→symbol links, which varies by language/framework.
17018
+
16955
17019
  ## Pre-Check
16956
17020
 
16957
17021
  Verify graph data exists before any graph operation:
@@ -17040,13 +17104,80 @@ scripts/tools/mermaid-from-graph.sh --repo . --diagram proto-map # detected
17040
17104
 
17041
17105
  Emits a ready-to-inject ` ```mermaid ``` ` block on the fly (computed live by the engine), or an empty stub (exit 2) when the engine is unavailable. Diagrams are generated at the moment of use — they are never committed.
17042
17106
 
17107
+ ### Snippet — verified source + caller/callee counts
17108
+
17109
+ ```bash
17110
+ scripts/tools/graph-snippet.sh --repo . --qualified <pkg.Mod.Class.method>
17111
+ ```
17112
+
17113
+ Output: `{qualified_name, file, start_line, end_line, callers, callees, transitive_loop_depth, complexity, code, status, source}`. Prefer this over grep+Read when you have a qualified name — it returns the engine's attributed source plus pre-computed counts.
17114
+
17115
+ ### Search — semantic / ranked symbol lookup
17116
+
17117
+ ```bash
17118
+ scripts/tools/graph-search.sh --repo . --query "auth token refresh" [--limit N]
17119
+ ```
17120
+
17121
+ Output: `{query, results[{name, qualified_name, label, file, rank}], total, source}`. Use when the user names an **intent/concept** rather than an exact symbol — this is the first move in the Concept-to-Files recipe.
17122
+
17123
+ ### Tests — coverage edges and untested surface
17124
+
17125
+ ```bash
17126
+ scripts/tools/graph-tests.sh --repo . --symbol <name> # tests covering a symbol
17127
+ scripts/tools/graph-tests.sh --repo . --untested # exported symbols with no TESTS edge
17128
+ ```
17129
+
17130
+ Output: `{symbol, tests[{test,file}], status, source}` or `{untested[{symbol,file}], total, truncated, source}`. Feeds coverage gaps for `init`/`testing-strategy`/`coverage`.
17131
+
17132
+ ### Deps — real module/file import graph
17133
+
17134
+ ```bash
17135
+ scripts/tools/graph-deps.sh --repo . [--file PATH]
17136
+ ```
17137
+
17138
+ Output: `{imports[{src,dst}], total, truncated, source}` from actual `IMPORTS` edges (self-imports filtered). This is the auto-derived dependency graph behind `mermaid-from-graph.sh --diagram module-deps` and `architecture.md §9`.
17139
+
17140
+ ### Hierarchy — class inheritance
17141
+
17142
+ ```bash
17143
+ scripts/tools/graph-hierarchy.sh --repo . [--symbol <Class> | --derived <Base>]
17144
+ ```
17145
+
17146
+ Output: `{edges[{child,parent}], status, source}`. `--derived` gives the blast radius of changing a base class.
17147
+
17148
+ ### Errors — error-propagation paths
17149
+
17150
+ ```bash
17151
+ scripts/tools/graph-errors.sh --repo . --symbol <name> # what it raises/throws
17152
+ scripts/tools/graph-errors.sh --repo . --type <ErrType> # who raises/throws that type
17153
+ ```
17154
+
17155
+ Output: `{symbol, raises[...], status, source}` or `{type, raisers[...], status, source}`. `--type` drives fail-closed audits.
17156
+
17157
+ ### Risk — pre-computed risk hotspots
17158
+
17159
+ ```bash
17160
+ scripts/tools/graph-risk.sh --repo . [--min-complexity N]
17161
+ ```
17162
+
17163
+ Output: `{risky[{symbol, file, complexity, flags}], total, truncated, source}` from the engine's pre-computed flags (`unguarded_recursion`, `recursion_in_loop`, `alloc_in_loop`, `linear_scan_in_loop`). High-signal input for `bughunt`/`deep-review` — the engine already found these.
17164
+
17165
+ ### Generic — read-only escape hatch (all 20 edges / ~30 properties)
17166
+
17167
+ ```bash
17168
+ scripts/tools/graph-query.sh --repo . --cypher 'MATCH (f)-[:WRITES]->(v) RETURN f.name, v.name LIMIT 50'
17169
+ scripts/tools/graph-query.sh --repo . --tool get_graph_schema --json '{}'
17170
+ ```
17171
+
17172
+ Unlocks any edge type or node property without a purpose-built wrapper. Write verbs are rejected; stay inside the SAFE dialect set (above). Emits raw engine JSON.
17173
+
17043
17174
  ### Indexing / refreshing the gate marker
17044
17175
 
17045
17176
  ```bash
17046
17177
  scripts/tools/graph-snapshot.sh --repo .
17047
17178
  ```
17048
17179
 
17049
- Indexes the repo into the engine and writes the `draft/graph/schema.yaml` gate marker. It writes **no** graph data. Run during `draft init` and `draft graph`, or whenever the index should be refreshed.
17180
+ Indexes the repo into the engine and writes the `draft/graph/schema.yaml` gate marker (now including the `detect_changes` delta: `changed_files`/`impacted_symbols`). It writes **no** graph data. Run during `draft init` and `draft graph`, or whenever the index should be refreshed.
17050
17181
 
17051
17182
  ## Finding the Engine (Resolution + Usage Report)
17052
17183
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drafthq/draft",
3
- "version": "3.1.5",
3
+ "version": "3.2.0",
4
4
  "description": "Context-Driven Development for AI coding agents — install Draft into Claude Code, Cursor, Codex, or opencode.",
5
5
  "bin": {
6
6
  "draft": "cli/bin/draft.js"
@@ -24,6 +24,7 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "test": "bash tests/test-cli.sh",
27
+ "version": "bash scripts/sync-version.sh && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json web/index.html",
27
28
  "prepublishOnly": "bash scripts/build-integrations.sh"
28
29
  },
29
30
  "repository": {
package/scripts/lib.sh CHANGED
@@ -161,6 +161,16 @@ TOOLS=(
161
161
  "graph-impact.sh"
162
162
  "graph-callers.sh"
163
163
  "graph-arch.sh"
164
+ # graph-tooling-v2: generic passthrough + purpose-built capability wrappers
165
+ "graph-query.sh"
166
+ "graph-snippet.sh"
167
+ "graph-search.sh"
168
+ "graph-tests.sh"
169
+ "graph-deps.sh"
170
+ "graph-hierarchy.sh"
171
+ "graph-errors.sh"
172
+ "graph-risk.sh"
173
+ "graph-traces.sh"
164
174
  "validate-frontmatter.sh"
165
175
  # Foundations hygiene/verification tools
166
176
  "check-graph-usage-report.sh"