@drafthq/draft 3.1.5 → 3.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.
Files changed (49) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/cli/src/hosts/claude-code.js +4 -1
  4. package/cli/src/installer.js +20 -5
  5. package/cli/src/lib/marker.js +93 -0
  6. package/core/shared/condensation.md +3 -2
  7. package/core/shared/git-report-metadata.md +3 -2
  8. package/core/shared/graph-query.md +140 -8
  9. package/core/shared/tool-resolver.md +71 -4
  10. package/core/templates/plan.md +3 -2
  11. package/integrations/agents/AGENTS.md +481 -106
  12. package/integrations/copilot/.github/copilot-instructions.md +481 -106
  13. package/package.json +2 -1
  14. package/scripts/lib.sh +11 -0
  15. package/scripts/tools/_graph_queries.sh +102 -0
  16. package/scripts/tools/cycle-detect.sh +18 -15
  17. package/scripts/tools/graph-callers.sh +71 -20
  18. package/scripts/tools/graph-deps.sh +76 -0
  19. package/scripts/tools/graph-errors.sh +97 -0
  20. package/scripts/tools/graph-hierarchy.sh +89 -0
  21. package/scripts/tools/graph-query.sh +124 -0
  22. package/scripts/tools/graph-risk.sh +81 -0
  23. package/scripts/tools/graph-search.sh +84 -0
  24. package/scripts/tools/graph-snapshot.sh +13 -1
  25. package/scripts/tools/graph-snippet.sh +92 -0
  26. package/scripts/tools/graph-tests.sh +112 -0
  27. package/scripts/tools/graph-traces.sh +83 -0
  28. package/scripts/tools/hotspot-rank.sh +43 -16
  29. package/scripts/tools/mermaid-from-graph.sh +31 -10
  30. package/scripts/tools/resolve-tools.sh +78 -0
  31. package/skills/adr/SKILL.md +3 -2
  32. package/skills/bughunt/SKILL.md +10 -1
  33. package/skills/coverage/SKILL.md +8 -3
  34. package/skills/debug/SKILL.md +16 -5
  35. package/skills/decompose/SKILL.md +29 -12
  36. package/skills/deep-review/SKILL.md +19 -6
  37. package/skills/deploy-checklist/SKILL.md +6 -5
  38. package/skills/graph/SKILL.md +15 -6
  39. package/skills/impact/SKILL.md +12 -1
  40. package/skills/implement/SKILL.md +20 -4
  41. package/skills/init/SKILL.md +17 -10
  42. package/skills/init/references/architecture-spec.md +17 -6
  43. package/skills/learn/SKILL.md +15 -4
  44. package/skills/quick-review/SKILL.md +13 -3
  45. package/skills/review/SKILL.md +32 -8
  46. package/skills/standup/SKILL.md +3 -2
  47. package/skills/status/SKILL.md +3 -2
  48. package/skills/tech-debt/SKILL.md +20 -6
  49. package/skills/upload/SKILL.md +3 -2
@@ -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.1",
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.1",
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',
@@ -4,10 +4,15 @@ const { spawnSync } = require('child_process');
4
4
  const fsx = require('./lib/fsx');
5
5
  const log = require('./lib/log');
6
6
  const { fetchGraph } = require('./lib/graph');
7
+ const { writePluginRootMarker } = require('./lib/marker');
8
+
9
+ // A short ceiling so a wedged `claude --version` can't hang the installer
10
+ // before we even reach the real (separately-timed) install steps.
11
+ const CHECK_TIMEOUT_MS = 10000;
7
12
 
8
13
  function hasBinary(name) {
9
14
  // ENOENT on the error means the binary is not on PATH.
10
- const r = spawnSync(name, ['--version'], { stdio: 'ignore' });
15
+ const r = spawnSync(name, ['--version'], { stdio: 'ignore', timeout: CHECK_TIMEOUT_MS });
11
16
  return !(r.error && r.error.code === 'ENOENT');
12
17
  }
13
18
 
@@ -59,12 +64,14 @@ function install(host, ctx) {
59
64
  const plan = host.plan(ctx);
60
65
  log.step(`Installing Draft -> ${host.label} [${plan.targetSummary}]${ctx.dryRun ? ' (dry run)' : ''}`);
61
66
 
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.
67
+ // A plan may require an external CLI (e.g. claude). If it's missing, say so
68
+ // loudly, print the manual fallback, and exit non-zero a no-op must never
69
+ // read as success. Only enforced on a real install; a dry run still shows the
70
+ // planned commands.
65
71
  if (plan.requires && !ctx.dryRun && !hasBinary(plan.requires)) {
72
+ log.error(`Cannot auto-install: the "${plan.requires}" CLI is not on your PATH. Nothing was installed.`);
66
73
  printFallback(plan);
67
- return 0;
74
+ return 1;
68
75
  }
69
76
 
70
77
  // Pre-flight: for file copies, every bundled source must exist and guarded
@@ -88,6 +95,7 @@ function install(host, ctx) {
88
95
  const code = execAction(act, ctx);
89
96
  if (code !== 0) {
90
97
  log.error(`Step failed (exit ${code}): ${act.label || act.cmd}`);
98
+ if (plan.onFailHint) log.error(plan.onFailHint);
91
99
  if (plan.fallback) printFallback(plan);
92
100
  return code;
93
101
  }
@@ -100,6 +108,13 @@ function install(host, ctx) {
100
108
  fetchGraph(ctx);
101
109
  }
102
110
 
111
+ // Record the install path so skills can locate scripts/tools/ from the user's
112
+ // project cwd (best-effort; graph skills glob-fallback if the marker is absent).
113
+ if (!ctx.dryRun) {
114
+ const root = writePluginRootMarker(host.id);
115
+ if (root) log.note(`Recorded plugin path for graph tooling: ${root}`);
116
+ }
117
+
103
118
  (plan.notes || []).forEach((n) => log.note(n));
104
119
 
105
120
  if (ctx.dryRun) {
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ // Write the install-path marker (~/.cache/draft/plugin-root) so a draft skill can
4
+ // locate its bundled scripts/tools/ from the user's project cwd. Skills run with
5
+ // cwd = the user's project and ${CLAUDE_PLUGIN_ROOT} is NOT exported into skill Bash,
6
+ // so without this marker resolution falls back to globbing the plugin cache. The
7
+ // marker is the fast, authoritative path; see core/shared/tool-resolver.md.
8
+ //
9
+ // Best-effort: any failure is swallowed — graph skills still resolve via the glob
10
+ // fallback, so a missing marker is never fatal.
11
+
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+ const path = require('path');
15
+
16
+ // Resolve the installed draft plugin root for a given host, or null if unknown.
17
+ function resolvePluginRoot(hostId) {
18
+ const home = os.homedir();
19
+
20
+ if (hostId === 'claude-code') {
21
+ // 1. Claude Code's own registry holds the authoritative installPath.
22
+ const reg = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
23
+ try {
24
+ const data = JSON.parse(fs.readFileSync(reg, 'utf8'));
25
+ const key = Object.keys(data.plugins || {}).find((k) => k.startsWith('draft@'));
26
+ const ip = key && data.plugins[key] && data.plugins[key][0] && data.plugins[key][0].installPath;
27
+ if (ip && fs.existsSync(path.join(ip, 'scripts', 'tools'))) return ip;
28
+ } catch {
29
+ /* registry missing or unparseable — fall through to the cache scan */
30
+ }
31
+ // 2. Fallback: newest versioned dir under the plugin cache.
32
+ return newestCacheRoot(path.join(home, '.claude', 'plugins', 'cache'));
33
+ }
34
+
35
+ if (hostId === 'cursor') {
36
+ const p = path.join(home, '.cursor', 'plugins', 'local', 'draft');
37
+ return fs.existsSync(path.join(p, 'scripts', 'tools')) ? p : null;
38
+ }
39
+
40
+ return null;
41
+ }
42
+
43
+ // Newest <cache>/<marketplace>/draft/<version> dir that carries scripts/tools.
44
+ function newestCacheRoot(cacheDir) {
45
+ try {
46
+ const candidates = [];
47
+ for (const mkt of fs.readdirSync(cacheDir)) {
48
+ const draftDir = path.join(cacheDir, mkt, 'draft');
49
+ let versions;
50
+ try {
51
+ versions = fs.readdirSync(draftDir);
52
+ } catch {
53
+ continue;
54
+ }
55
+ for (const v of versions) {
56
+ const root = path.join(draftDir, v);
57
+ if (fs.existsSync(path.join(root, 'scripts', 'tools'))) candidates.push({ v, root });
58
+ }
59
+ }
60
+ if (!candidates.length) return null;
61
+ candidates.sort((a, b) => compareVersions(a.v, b.v));
62
+ return candidates[candidates.length - 1].root;
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ // Numeric-aware version compare (no semver dependency).
69
+ function compareVersions(a, b) {
70
+ const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
71
+ const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
72
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
73
+ const d = (pa[i] || 0) - (pb[i] || 0);
74
+ if (d) return d;
75
+ }
76
+ return 0;
77
+ }
78
+
79
+ // Write ~/.cache/draft/plugin-root for the host. Returns the path written, or null.
80
+ function writePluginRootMarker(hostId) {
81
+ try {
82
+ const root = resolvePluginRoot(hostId);
83
+ if (!root) return null;
84
+ const dest = path.join(os.homedir(), '.cache', 'draft', 'plugin-root');
85
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
86
+ fs.writeFileSync(dest, root + '\n');
87
+ return root;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ module.exports = { writePluginRootMarker, resolvePluginRoot };
@@ -180,8 +180,9 @@ Write the completed content to `draft/.ai-context.md`.
180
180
  After writing both output files, strip trailing whitespace and blank lines at EOF to prevent GitHub upload failures. Resolve the script via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)):
181
181
 
182
182
  ```bash
183
- DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
184
- [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$HOME/.cursor/plugins/local/draft/scripts/tools"
183
+ DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
184
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
185
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
185
186
  [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
186
187
  [ -x "$DRAFT_TOOLS/fix-whitespace.sh" ] && bash "$DRAFT_TOOLS/fix-whitespace.sh" draft/architecture.md draft/.ai-context.md draft/.ai-profile.md 2>/dev/null || true
187
188
  ```
@@ -16,8 +16,9 @@ Referenced by: All skills that generate Draft reports — including `/draft:bugh
16
16
  Use `git-metadata.sh` from the plugin install, resolved via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)):
17
17
 
18
18
  ```bash
19
- DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
20
- [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$HOME/.cursor/plugins/local/draft/scripts/tools"
19
+ DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
20
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
21
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
21
22
  [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
22
23
  bash "$DRAFT_TOOLS/git-metadata.sh" --yaml \
23
24
  --project "$PROJECT" --module "$MODULE" \
@@ -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.
@@ -92,22 +92,87 @@ These fields are appended to `~/.draft/metrics.jsonl` along with the existing sk
92
92
 
93
93
  ## Tooling Wrappers
94
94
 
95
- For common query modes, prefer the deterministic wrappers that ship with the plugin. Resolve their location via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)) before invoking:
95
+ For common query modes, prefer the deterministic wrappers that ship with the plugin. Resolve their location via the canonical tool resolver (see [tool-resolver.md](tool-resolver.md)) before invoking. Skills run with cwd = the user's project and `${CLAUDE_PLUGIN_ROOT}` is **not** exported into skill Bash, so a bare `scripts/tools/foo.sh` fails — establish `DRAFT_TOOLS` once before the first helper call, in the same Bash session as your tool calls (re-establish it if you split helper calls into a separate, later Bash block):
96
96
 
97
97
  ```bash
98
- DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
99
- [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$HOME/.cursor/plugins/local/draft/scripts/tools"
98
+ DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
99
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
100
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
100
101
  [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
101
102
  ```
102
103
 
103
104
  | Wrapper | Graph mode | Behavior on missing graph |
104
105
  |---|---|---|
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 |
106
+ | `bash "$DRAFT_TOOLS/hotspot-rank.sh" [--top N]` | complexity-weighted hotspots | Emits `{hotspots:[],source:"unavailable"}` and exits 2 |
107
+ | `bash "$DRAFT_TOOLS/cycle-detect.sh"` | call cycles | Emits `{cycles:[],source:"unavailable"}` and exits 2 |
108
+ | `bash "$DRAFT_TOOLS/mermaid-from-graph.sh" [--diagram module-deps\|co-change\|proto-map]` | diagram text | Emits an empty mermaid block and exits 2 |
109
+ | `bash "$DRAFT_TOOLS/graph-callers.sh" --symbol N [--transitive[=N]] [--prod-only] [--qualified]` | callers | `{callers:[],status:"unavailable",source:"unavailable"}`, exit 2 |
110
+ | `bash "$DRAFT_TOOLS/graph-snippet.sh" --qualified N` | verified source + caller/callee counts | `{status:"unavailable",source:"unavailable"}`, exit 2 |
111
+ | `bash "$DRAFT_TOOLS/graph-search.sh" --query "STR" [--limit N]` | semantic/ranked search | `{results:[],source:"unavailable"}`, exit 2 |
112
+ | `bash "$DRAFT_TOOLS/graph-tests.sh" (--symbol N \| --untested)` | test→symbol coverage | `{tests:[]/untested:[],source:"unavailable"}`, exit 2 |
113
+ | `bash "$DRAFT_TOOLS/graph-deps.sh" [--file PATH]` | real IMPORTS graph | `{imports:[],source:"unavailable"}`, exit 2 |
114
+ | `bash "$DRAFT_TOOLS/graph-hierarchy.sh" [--symbol N \| --derived N]` | INHERITS tree | `{edges:[],source:"unavailable"}`, exit 2 |
115
+ | `bash "$DRAFT_TOOLS/graph-errors.sh" (--symbol N \| --type N)` | RAISES/THROWS | `{raises:[]/raisers:[],source:"unavailable"}`, exit 2 |
116
+ | `bash "$DRAFT_TOOLS/graph-risk.sh" [--min-complexity N]` | pre-computed risk flags | `{risky:[],source:"unavailable"}`, exit 2 |
117
+ | `bash "$DRAFT_TOOLS/graph-query.sh" (--cypher STR \| --tool NAME --json '{...}')` | generic read-only passthrough | `{source:"unavailable"}`, exit 2 |
118
+ | `bash "$DRAFT_TOOLS/graph-traces.sh" ingest --file F --experimental` | runtime traces (experimental write) | `{source:"unavailable"}`, exit 2 |
108
119
 
109
120
  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
121
 
122
+ ### Capability wrappers & dialect limits (graph-tooling-v2)
123
+
124
+ All Cypher lives in `scripts/tools/_graph_queries.sh` (the single source of query
125
+ truth). Wrappers are thin arg-parse → builder → fail-loud JSON. Three contracts
126
+ matter when consuming them:
127
+
128
+ **Fail-loud status.** Symbol-scoped wrappers (`graph-callers`, `graph-snippet`,
129
+ `graph-tests --symbol`, `graph-hierarchy --symbol/--derived`, `graph-errors`)
130
+ emit a `status` field that distinguishes the three real outcomes — never read a
131
+ bare `[]` as a confirmed true negative:
132
+
133
+ | `status` | Meaning |
134
+ |---|---|
135
+ | `ok` | node found, edges returned |
136
+ | `no-edges` | node exists but has no matching edge (a *real* negative) |
137
+ | `no-match` | the named symbol was not found at all (check the name / try `--qualified`) |
138
+ | `unavailable` | engine could not be resolved (exit 2) |
139
+
140
+ **Verified engine param shapes** (engine v0.8.x — the runtime source of truth is
141
+ `get_graph_schema`; do not hardcode a property set):
142
+
143
+ ```bash
144
+ get_code_snippet '{"project":P,"qualified_name":"pkg.Mod.Class.method"}' # → source + callers/callees counts + transitive_loop_depth
145
+ search_graph '{"project":P,"query":"order submission to broker","limit":5}' # → {results:[{name,qualified_name,label,file_path,rank}]}
146
+ trace_path '{"project":P,"function_name":"submit_order","depth":3,"direction":"both"}' # depth-bounded caller EXPANDER, not an A→B pathfinder
147
+ detect_changes '{"project":P}' # → {changed_files, changed_count, impacted_symbols, depth}
148
+ get_graph_schema '{"project":P}' # → {node_labels:[{label,count,properties}], edge_types:[{type,count}]}
149
+ ```
150
+
151
+ **Cypher dialect — keep queries inside the SAFE set:**
152
+
153
+ - ✅ SAFE: fixed-length patterns, single/multi-hop explicit patterns, `=`, `<`,
154
+ `STARTS WITH`, `NOT x STARTS WITH`, `AND`, `OR`, relationship-type alternation
155
+ `[:A|B]`, simple `count(x)`.
156
+ - ❌ UNSAFE (rejected or silently empty): `coalesce()`, `<>` / `!=` / `<=` / `>=`,
157
+ `NOT EXISTS(...)`, `NOT (pattern)`, `WITH`-grouping aggregation, multi-pattern
158
+ joins. `graph-query.sh --cypher` returns the engine's raw error, not a silent
159
+ empty — but the builders never emit these forms.
160
+
161
+ **Caveats consumers must respect:**
162
+
163
+ - **`--prod-only` is best-effort.** It filters `is_test=false AND NOT file_path
164
+ STARTS WITH 'tests/'`. `is_test` is partially populated by the engine, so test
165
+ helpers/mocks can leak through. Treat it as a heuristic, not a guarantee.
166
+ - **`--transitive` uses the `trace_path` expander** (a depth-bounded caller
167
+ expansion from one symbol), not a from→to pathfinder. "Path between A and B"
168
+ still needs an explicit fixed-length `graph-query.sh --cypher` pattern.
169
+ - **Honest caps.** `cycle-detect`, `graph-deps`, `graph-risk`, `graph-tests
170
+ --untested` cap their output and report `"truncated": true` when the cap is
171
+ hit — results are a sample, not exhaustive.
172
+ - **`graph-tests --untested`** is a set difference (exported symbols minus TESTS
173
+ targets) because the dialect has no anti-join; coverage depends on the engine
174
+ resolving test→symbol links, which varies by language/framework.
175
+
111
176
  ## Pre-Check
112
177
 
113
178
  Verify graph data exists before any graph operation:
@@ -196,13 +261,80 @@ scripts/tools/mermaid-from-graph.sh --repo . --diagram proto-map # detected
196
261
 
197
262
  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
263
 
264
+ ### Snippet — verified source + caller/callee counts
265
+
266
+ ```bash
267
+ scripts/tools/graph-snippet.sh --repo . --qualified <pkg.Mod.Class.method>
268
+ ```
269
+
270
+ 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.
271
+
272
+ ### Search — semantic / ranked symbol lookup
273
+
274
+ ```bash
275
+ scripts/tools/graph-search.sh --repo . --query "auth token refresh" [--limit N]
276
+ ```
277
+
278
+ 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.
279
+
280
+ ### Tests — coverage edges and untested surface
281
+
282
+ ```bash
283
+ scripts/tools/graph-tests.sh --repo . --symbol <name> # tests covering a symbol
284
+ scripts/tools/graph-tests.sh --repo . --untested # exported symbols with no TESTS edge
285
+ ```
286
+
287
+ Output: `{symbol, tests[{test,file}], status, source}` or `{untested[{symbol,file}], total, truncated, source}`. Feeds coverage gaps for `init`/`testing-strategy`/`coverage`.
288
+
289
+ ### Deps — real module/file import graph
290
+
291
+ ```bash
292
+ scripts/tools/graph-deps.sh --repo . [--file PATH]
293
+ ```
294
+
295
+ 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`.
296
+
297
+ ### Hierarchy — class inheritance
298
+
299
+ ```bash
300
+ scripts/tools/graph-hierarchy.sh --repo . [--symbol <Class> | --derived <Base>]
301
+ ```
302
+
303
+ Output: `{edges[{child,parent}], status, source}`. `--derived` gives the blast radius of changing a base class.
304
+
305
+ ### Errors — error-propagation paths
306
+
307
+ ```bash
308
+ scripts/tools/graph-errors.sh --repo . --symbol <name> # what it raises/throws
309
+ scripts/tools/graph-errors.sh --repo . --type <ErrType> # who raises/throws that type
310
+ ```
311
+
312
+ Output: `{symbol, raises[...], status, source}` or `{type, raisers[...], status, source}`. `--type` drives fail-closed audits.
313
+
314
+ ### Risk — pre-computed risk hotspots
315
+
316
+ ```bash
317
+ scripts/tools/graph-risk.sh --repo . [--min-complexity N]
318
+ ```
319
+
320
+ 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.
321
+
322
+ ### Generic — read-only escape hatch (all 20 edges / ~30 properties)
323
+
324
+ ```bash
325
+ scripts/tools/graph-query.sh --repo . --cypher 'MATCH (f)-[:WRITES]->(v) RETURN f.name, v.name LIMIT 50'
326
+ scripts/tools/graph-query.sh --repo . --tool get_graph_schema --json '{}'
327
+ ```
328
+
329
+ 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.
330
+
199
331
  ### Indexing / refreshing the gate marker
200
332
 
201
333
  ```bash
202
334
  scripts/tools/graph-snapshot.sh --repo .
203
335
  ```
204
336
 
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.
337
+ 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
338
 
207
339
  ## Finding the Engine (Resolution + Usage Report)
208
340
 
@@ -1,10 +1,77 @@
1
1
  ---
2
2
  shared: tool-resolver
3
- applies_to: quality + init + graph skills
3
+ applies_to: every skill that invokes scripts/tools/*
4
4
  ---
5
5
 
6
- # tool-resolver (Foundations Stub)
6
+ # tool-resolver
7
7
 
8
- Portable generalized stub per manifest §2.1. Full content will be expanded in later agent tranche or manual follow-up.
8
+ Canonical procedure for locating Draft's bundled shell helpers (`scripts/tools/*.sh`)
9
+ from inside a skill.
9
10
 
10
- See verification-gates.md and template-hygiene.md for usage contracts.
11
+ ## Why this exists
12
+
13
+ When Claude runs a draft skill, the shell's **working directory is the user's
14
+ project**, not the plugin. The helpers live inside the plugin install directory,
15
+ which on a marketplace/npm install is `~/.claude/plugins/cache/<marketplace>/draft/<version>/`
16
+ — never the cwd. `${CLAUDE_PLUGIN_ROOT}` is **not** exported into skill-driven Bash
17
+ (it is only set for hooks, MCP/LSP servers, and monitor commands), so a bare
18
+ `scripts/tools/foo.sh` or `${CLAUDE_PLUGIN_ROOT}/...` invocation silently fails.
19
+
20
+ Every skill MUST resolve `DRAFT_TOOLS` and invoke helpers as `"$DRAFT_TOOLS/<tool>.sh"`.
21
+
22
+ ## Resolution order
23
+
24
+ `DRAFT_TOOLS` resolves to the first directory that exists, in this order:
25
+
26
+ 1. `${DRAFT_PLUGIN_ROOT}/scripts/tools` — explicit override (testing / pinned installs)
27
+ 2. `$(cat ~/.cache/draft/plugin-root)/scripts/tools` — install marker written by `draft install` (authoritative)
28
+ 3. `${CLAUDE_PLUGIN_ROOT}/scripts/tools` — set in hook/MCP contexts; harmless to probe
29
+ 4. `installed_plugins.json → installPath` for `draft@*` — Claude Code's own registry (needs `jq`)
30
+ 5. `~/.claude/plugins/cache/*/draft/*/scripts/tools` — newest cache install (glob, `sort -V`)
31
+ 6. `~/.claude/plugins/marketplaces/*draft*/scripts/tools` — marketplace clone
32
+ 7. `~/.cursor/plugins/local/draft/scripts/tools` — Cursor local install
33
+ 8. `$PWD/scripts/tools` — dev / dogfooding (running inside the draft repo itself)
34
+
35
+ The marker (step 2) is the fast, authoritative path; steps 5–6 are the glob fallback
36
+ that keeps resolution working on installs predating the marker (no reinstall required).
37
+
38
+ ## Skill preamble (copy verbatim)
39
+
40
+ Establish `DRAFT_TOOLS` once, before the first helper call, in the **same Bash
41
+ session** that runs your tool calls — exactly as skills already define `REPO_ABS`
42
+ once and reuse it. Env vars do not persist across **separate** Bash tool
43
+ invocations (only the cwd does), so if you split helper calls into a later, separate
44
+ Bash block, re-establish `DRAFT_TOOLS` there too:
45
+
46
+ ```bash
47
+ DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
48
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
49
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
50
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
51
+ ```
52
+
53
+ Then invoke helpers through the variable:
54
+
55
+ ```bash
56
+ "$DRAFT_TOOLS/hotspot-rank.sh" --repo . --top 5
57
+ "$DRAFT_TOOLS/graph-arch.sh" --repo .
58
+ ```
59
+
60
+ The four-line inline preamble is self-contained and is the recommended form for
61
+ skills — it needs no marker file and no prior `source`. The full 8-step resolver
62
+ (adding the `${DRAFT_PLUGIN_ROOT}` override, `${CLAUDE_PLUGIN_ROOT}`, the jq-registry
63
+ lookup, and the Cursor path) is shipped as `scripts/tools/resolve-tools.sh` for tests
64
+ and for callers that prefer a single source of truth:
65
+
66
+ ```bash
67
+ DRAFT_TOOLS="$("$PWD/scripts/tools/resolve-tools.sh" 2>/dev/null)" # dev/dogfood
68
+ # installed: "$(cat ~/.cache/draft/plugin-root)/scripts/tools/resolve-tools.sh"
69
+ ```
70
+
71
+ ## The engine binary is separate
72
+
73
+ `DRAFT_TOOLS` locates the **wrapper scripts**. Each wrapper then resolves the
74
+ `codebase-memory-mcp` **engine binary** itself via `_lib.sh:find_memory_bin`
75
+ (`DRAFT_MEMORY_BIN` → `$PATH` → `~/.cache/draft/bin/` → vendored). Do not conflate
76
+ the two: a wrapper that runs but reports `source: unavailable` means the engine
77
+ binary is missing, not the wrapper.
@@ -111,8 +111,9 @@ validator chain via the canonical resolver pattern (see
111
111
  [core/shared/verification-gates.md](../../core/shared/verification-gates.md)):
112
112
 
113
113
  ```bash
114
- DRAFT_TOOLS="${DRAFT_PLUGIN_ROOT:-$HOME/.claude/plugins/draft}/scripts/tools"
115
- [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$HOME/.cursor/plugins/local/draft/scripts/tools"
114
+ DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
115
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
116
+ [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
116
117
  [ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
117
118
  "$DRAFT_TOOLS/check-track-hygiene.sh" .; "$DRAFT_TOOLS/verify-citations.sh" .
118
119
  "$DRAFT_TOOLS/verify-doc-anchors.sh" .; "$DRAFT_TOOLS/check-graph-usage-report.sh" .