@nanonets/graft 0.9.1 → 0.10.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.
Files changed (75) hide show
  1. package/README.md +49 -45
  2. package/dist/ask/fuse.d.ts.map +1 -1
  3. package/dist/ask/fuse.js +32 -14
  4. package/dist/ask/fuse.js.map +1 -1
  5. package/dist/claude/init.d.ts.map +1 -1
  6. package/dist/claude/init.js +6 -3
  7. package/dist/claude/init.js.map +1 -1
  8. package/dist/cli.js +93 -39
  9. package/dist/cli.js.map +1 -1
  10. package/dist/engine.d.ts +6 -2
  11. package/dist/engine.d.ts.map +1 -1
  12. package/dist/engine.js +4 -1
  13. package/dist/engine.js.map +1 -1
  14. package/dist/graph/build.d.ts +5 -0
  15. package/dist/graph/build.d.ts.map +1 -1
  16. package/dist/graph/build.js +21 -2
  17. package/dist/graph/build.js.map +1 -1
  18. package/dist/graph/check.d.ts +1 -1
  19. package/dist/graph/check.d.ts.map +1 -1
  20. package/dist/graph/check.js +13 -3
  21. package/dist/graph/check.js.map +1 -1
  22. package/dist/graph/generic.d.ts +25 -0
  23. package/dist/graph/generic.d.ts.map +1 -0
  24. package/dist/graph/generic.js +320 -0
  25. package/dist/graph/generic.js.map +1 -0
  26. package/dist/graph/lsp/client.d.ts +43 -0
  27. package/dist/graph/lsp/client.d.ts.map +1 -0
  28. package/dist/graph/lsp/client.js +179 -0
  29. package/dist/graph/lsp/client.js.map +1 -0
  30. package/dist/graph/lsp/enrich.d.ts +11 -0
  31. package/dist/graph/lsp/enrich.d.ts.map +1 -0
  32. package/dist/graph/lsp/enrich.js +155 -0
  33. package/dist/graph/lsp/enrich.js.map +1 -0
  34. package/dist/graph/lsp/registry.d.ts +15 -0
  35. package/dist/graph/lsp/registry.d.ts.map +1 -0
  36. package/dist/graph/lsp/registry.js +46 -0
  37. package/dist/graph/lsp/registry.js.map +1 -0
  38. package/dist/graph/queries/c.scm +13 -0
  39. package/dist/graph/queries/c_sharp.scm +19 -0
  40. package/dist/graph/queries/cpp.scm +23 -0
  41. package/dist/graph/queries/elixir.scm +54 -0
  42. package/dist/graph/queries/java.scm +20 -0
  43. package/dist/graph/queries/kotlin.scm +43 -0
  44. package/dist/graph/queries/php.scm +40 -0
  45. package/dist/graph/queries/ruby.scm +65 -0
  46. package/dist/graph/queries/rust.scm +41 -0
  47. package/dist/graph/queries/scala.scm +66 -0
  48. package/dist/graph/queries/solidity.scm +43 -0
  49. package/dist/graph/queries/swift.scm +51 -0
  50. package/dist/graph/resolve.d.ts.map +1 -1
  51. package/dist/graph/resolve.js +13 -3
  52. package/dist/graph/resolve.js.map +1 -1
  53. package/dist/graph/root.d.ts +11 -0
  54. package/dist/graph/root.d.ts.map +1 -0
  55. package/dist/graph/root.js +40 -0
  56. package/dist/graph/root.js.map +1 -0
  57. package/dist/graph/source-files.d.ts.map +1 -1
  58. package/dist/graph/source-files.js +5 -1
  59. package/dist/graph/source-files.js.map +1 -1
  60. package/dist/graph/types.d.ts +12 -7
  61. package/dist/graph/types.d.ts.map +1 -1
  62. package/dist/graph/types.js +2 -2
  63. package/dist/graph/workspace-cli.d.ts +1 -1
  64. package/dist/graph/workspace-cli.d.ts.map +1 -1
  65. package/dist/graph/workspace-cli.js +2 -2
  66. package/dist/graph/workspace-cli.js.map +1 -1
  67. package/dist/graph/workspace.d.ts +2 -2
  68. package/dist/graph/workspace.d.ts.map +1 -1
  69. package/dist/graph/workspace.js +2 -2
  70. package/dist/graph/workspace.js.map +1 -1
  71. package/dist/mcp/tools.js +6 -6
  72. package/dist/mcp/tools.js.map +1 -1
  73. package/package.json +6 -2
  74. package/scripts/build-viewer.mjs +17 -1
  75. package/scripts/graph-quality.mjs +117 -0
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  ### Turbocharge Claude Code, Cursor, Codex, Gemini & every coding agent: faster, cheaper, with contextual understanding specific to your codebase.
6
6
 
7
7
  <p>
8
+ <a href="https://github.com/NanoNets/Graft"><img src="https://img.shields.io/github/stars/NanoNets/Graft?style=for-the-badge&logo=github&logoColor=white&label=Star%20on%20GitHub&color=FFC83D" /></a>
8
9
  <a href="https://graft.nanonets.ai"><img src="https://img.shields.io/badge/website-graft.nanonets.ai-546FFF?style=for-the-badge" /></a>
9
10
  <a href="https://discord.gg/zxmKweAA29"><img src="https://img.shields.io/badge/Discord-join-5865F2?style=for-the-badge&logo=discord&logoColor=white" /></a>
10
11
  <a href="https://www.npmjs.com/package/@nanonets/graft"><img src="https://img.shields.io/npm/v/%40nanonets%2Fgraft?style=for-the-badge&logo=npm&logoColor=white&label=npm" /></a>
@@ -20,17 +21,17 @@
20
21
 
21
22
  | Metric | Cold Claude Code | Claude Code with graft |
22
23
  |---|---|---|
23
- | Tool-call savings | Baseline | **+46%** |
24
+ | Tool-call reduction | Baseline | **+46%** |
24
25
  | Token savings | Baseline | **+42%** |
25
26
  | Time savings | Baseline | **+60%** |
26
27
  | Correctness | 54% | **66% (+12 pts)** |
27
28
 
28
- <sub>Efficiency is a 162-run controlled benchmark (same agent, same file tools, only the context differs). Correctness is **SWE-bench Verified**, graded by the official harness — graft resolved 66% of instances tested against Cold Claude Code's 54%. The "up to 4× cheaper / 3× faster" figures are the biggest single-task wins from a separate real-repo sweep (PocketBase, ollama, Excalidraw). [Efficiency method ↓](#benchmark) · [SWE-bench ↓](#swe-bench-verified) · [Per-repo numbers ↓](#tested-on-your-popular-repos)</sub>
29
+ <sub>Efficiency is a 162-run controlled benchmark (same agent, same file tools, only the context differs). Correctness is **SWE-bench Verified**, graded by the official harness — graft resolved 66% of instances tested against Cold Claude Code's 54%. [Efficiency method ↓](#benchmark) · [SWE-bench ↓](#swe-bench-verified) · [Per-repo numbers ↓](#tested-on-your-popular-repos)</sub>
29
30
 
30
31
  </div>
31
32
 
32
33
  <p align="center">
33
- <img src="assets/graft-terminal.png" alt="Two commands — npm install and graft init — then Graft rides along in a Claude Code session, statusline synced" width="820"/>
34
+ <img src="assets/graft-comparison-demo.gif" alt="Side-by-side comparison of a coding agent working with and without graft" width="820"/>
34
35
  </p>
35
36
 
36
37
  ---
@@ -43,6 +44,7 @@
43
44
  - [Benchmark](#benchmark)
44
45
  - [SWE-bench Verified](#swe-bench-verified)
45
46
  - [How the graph gets built](#how-the-graph-gets-built)
47
+ - [Supported languages](#supported-languages)
46
48
  - [What's in a node](#whats-in-a-node)
47
49
  - [What runs where](#what-runs-where)
48
50
  - [Agent integration](#agent-integration) — [MCP server](#mcp-server) · [Claude Code (deep integration)](#claude-code-deep-integration)
@@ -75,6 +77,10 @@ git add .claude && git commit -m "wire in graft"
75
77
 
76
78
  Prefer not to install globally? `npx @nanonets/graft init` works the same way.
77
79
 
80
+ <p align="center">
81
+ <img src="assets/graft-terminal.png" alt="Two commands — npm install and graft init — then Graft rides along in a Claude Code session, statusline synced" width="820"/>
82
+ </p>
83
+
78
84
  ---
79
85
 
80
86
  ## The problem
@@ -147,48 +153,6 @@ The sweep above is our harness measuring our mechanism. So we ran the industry-s
147
153
  | API-request savings | 2,455 | **1,875** | **+24%** |
148
154
  | Wall-clock savings | 13,094s | **8,922s** | **+32%** |
149
155
 
150
- ### Where graft fixed what cold missed
151
-
152
- | SWE-bench instance | Cold Claude Code | Claude Code with graft | Token usage vs. cold | Tool usage vs. cold |
153
- |---|---|---|---:|---:|
154
- | `django-11532` | Failed: 131 / 149 tests | **Passed: 149 / 149 tests** | **63%** | **60%** |
155
- | `django-16263` | Failed: 102 / 103 tests | **Passed: 103 / 103 tests** | **51%** | **74%** |
156
- | `sphinx-9461` | Failed: 61 / 62 tests | **Passed: 62 / 62 tests** | **91%** | **88%** |
157
- | `django-11400` | Failed: 62 / 64 tests | **Passed: 64 / 64 tests** | 347% | 308% |
158
- | `pylint-6386` | Failed: 7 / 8 tests | **Passed: 8 / 8 tests** | 163% | 132% |
159
- | `django-11885` | Failed: 42 / 44 tests | **Passed: 44 / 44 tests** | 219% | 172% |
160
- | `pytest-5840` | Failed: 51 / 53 tests | **Passed: 53 / 53 tests** | **30%** | **39%** |
161
- | `sphinx-8548` | Failed: 5 / 6 tests | **Passed: 6 / 6 tests** | **83%** | **80%** |
162
- | `django-12406` | Failed: 166 / 168 tests | **Passed: 168 / 168 tests** | 125% | 103% |
163
- | `django-16938` | Failed: 83 / 88 tests | **Passed: 88 / 88 tests** | 154% | 119% |
164
-
165
- ### Where graft cut the cost
166
-
167
- | SWE-bench instance | Cold Claude Code | Claude Code with graft | Token usage vs. cold | Tool usage vs. cold |
168
- |---|---|---|---:|---:|
169
- | `sphinx-10673` | Passed: 10 / 10 tests | **Passed: 10 / 10 tests** | **50%** | **51%** |
170
- | `django-16263` | Failed: 102 / 103 tests | **Passed: 103 / 103 tests** | **51%** | **74%** |
171
- | `django-11276` | Passed: 574 / 574 tests | **Passed: 574 / 574 tests** | **61%** | **58%** |
172
- | `django-10554` | Passed: 25 / 25 tests | **Passed: 25 / 25 tests** | **66%** | **69%** |
173
- | `django-11133` | Passed: 65 / 65 tests | **Passed: 65 / 65 tests** | **78%** | **61%** |
174
- | `sphinx-9461` | Failed: 61 / 62 tests | **Passed: 62 / 62 tests** | **91%** | **88%** |
175
- | `matplotlib-14623` | Passed: 401 / 401 tests | **Passed: 401 / 401 tests** | **95%** | **89%** |
176
- | `matplotlib-25775` | Passed: 96 / 96 tests | **Passed: 96 / 96 tests** | **98%** | **80%** |
177
- | `pytest-5840` | Failed: 51 / 53 tests | **Passed: 53 / 53 tests** | **30%** | **39%** |
178
- | `django-12741` | Passed: 69 / 69 tests | **Passed: 69 / 69 tests** | **47%** | **42%** |
179
- | `sympy-19783` | Passed: 11 / 11 tests | **Passed: 11 / 11 tests** | **61%** | **64%** |
180
- | `pylint-8898` | Passed: 19 / 19 tests | **Passed: 19 / 19 tests** | **62%** | **67%** |
181
- | `pylint-6528` | Passed: 175 / 175 tests | **Passed: 175 / 175 tests** | **67%** | **71%** |
182
- | `sklearn-12682` | Passed: 67 / 67 tests | **Passed: 67 / 67 tests** | **72%** | **70%** |
183
- | `xarray-3993` | Passed: 2400 / 2400 tests | **Passed: 2400 / 2400 tests** | **74%** | **65%** |
184
- | `xarray-3305` | Passed: 654 / 654 tests | **Passed: 654 / 654 tests** | **77%** | **70%** |
185
- | `sphinx-8548` | Failed: 5 / 6 tests | **Passed: 6 / 6 tests** | **83%** | **80%** |
186
- | `django-16315` | Passed: 43 / 43 tests | **Passed: 43 / 43 tests** | **85%** | **83%** |
187
- | `django-16032` | Passed: 79 / 79 tests | **Passed: 79 / 79 tests** | **90%** | **91%** |
188
- | `django-14011` | Passed: 19 / 19 tests | **Passed: 19 / 19 tests** | **90%** | **96%** |
189
- | `sphinx-8120` | Passed: 45 / 45 tests | **Passed: 45 / 45 tests** | **93%** | **75%** |
190
- | `django-15103` | Passed: 19 / 19 tests | **Passed: 19 / 19 tests** | **96%** | **80%** |
191
-
192
156
  graft resolved **33 of 50 instances** against Cold Claude Code's 27 — and got there with 25% fewer tool calls, 23% fewer tokens, and 32% less wall-clock time. Every correctness win has the same shape: the baseline patches one file and misses its siblings. On `django-11532` it patched 1 of the 5 files the fix requires and broke 18 previously-passing tests, twice over. On `django-16263` it patched 1 of 4 and scored 102 / 103. graft found the rest — and on `django-16263` did it in half the tokens and half the time.
193
157
 
194
158
  Two harnesses, two claims: the controlled sweep says graft is cheaper and faster, SWE-bench says it's also more correct.
@@ -221,6 +185,32 @@ Alongside the markdown graph, `graft build` builds `graft/.graph/wiring.json`
221
185
 
222
186
  ---
223
187
 
188
+ ## Supported languages
189
+
190
+ Graft parses with tree-sitter at two levels of fidelity, plus an optional
191
+ compiler-grade layer — all `$0` and deterministic (no model, no key):
192
+
193
+ - **Full-fidelity** — hand-written extractors with scope-aware, cross-file call
194
+ and import resolution:
195
+ **TypeScript / JavaScript** (incl. JSX & TSX), **Python**, **Go**.
196
+
197
+ - **Broad** — symbols (functions, classes, methods, types, …) plus name-resolved
198
+ call edges via a generic tree-sitter extractor, one grammar per language:
199
+ **Rust, Java, C, C++, C#, Ruby, PHP, Kotlin, Scala, Swift, Elixir, Solidity,
200
+ OCaml, Zig, Dart**.
201
+
202
+ - **Compiler-grade edges (opt-in)** — `graft build --lsp` adds precise
203
+ `lsp_resolved` call edges (member calls the static pass can't type) when a
204
+ language server is on your `PATH`: **rust-analyzer** (Rust), **clangd** (C/C++),
205
+ **gopls** (Go), **pyright** (Python), **typescript-language-server** (TS/JS).
206
+ It's best-effort — with no server installed the graph is unchanged.
207
+
208
+ Twenty languages in total. A file whose language isn't listed is skipped, not
209
+ indexed. Adding a broad-tier language is a small contribution — see
210
+ [CREDITS.md](CREDITS.md) for the folks who added the current set.
211
+
212
+ ---
213
+
224
214
  ## What's in a node
225
215
 
226
216
  A node is a single markdown file. Most code maps stop at an address: this thing lives in that file, on that line. That tells an agent where to look, not what it will find, so it still has to open the source and read. A Graft node holds the meaning inline, so the agent learns what it needs up front and opens the file only when it wants more.
@@ -320,6 +310,11 @@ Where a CLI agent supports user-level `hooks.json`, `init` also installs Graft's
320
310
  - **auto-sync** — every graft query brings the graph up to date first, so an answer always describes the code as it is right now, uncommitted edits included. A query refreshes only what it reads; the markdown under `graft/` is refreshed by the background rebuild at the end of a turn that touched code. Both are structural and `$0` — auto-sync never calls the LLM on its own
321
311
  - **context on tap** — each prompt pulls the matching nodes into the session; editing a file surfaces what depends on it ("blast radius"); new sessions start with the repo map
322
312
 
313
+ <p align="center">
314
+ <img src="assets/graft-hooks-demo.gif" alt="How Claude Code hooks wire graft in: install, graft init, then the hooks loop (session start, user prompt, post tool use, stop) keeps the graph built, read, and committed automatically" width="820"/>
315
+ <br/><sub>install → init → hooks keep the graph fresh every session</sub>
316
+ </p>
317
+
323
318
  <p align="center">
324
319
  <img src="assets/graft-hook-blast-radius-demo.gif" alt="graft's post-edit hook: editing node-file.ts prints its blast radius (who depends on it) inline, the statusline flips stale → syncing → synced on its own, and the same dependents light up in graft viz" width="820"/>
325
320
  <br/><sub>edit a file → blast radius appears inline → graph auto-resyncs → confirmed in <code>graft viz</code></sub>
@@ -438,6 +433,15 @@ Graft handles two shapes without any config:
438
433
  Either way, narrow to one sub-project with `graft ask "<task>" --in <scope>/`
439
434
  once you know where you're working.
440
435
 
436
+ `graft init` at the parent of a multi-repo folder wires **every child repo too**,
437
+ not just the parent — an agent session opens at a repo root and reads its
438
+ instruction files from there, so each child needs its own. A session started in
439
+ the parent gets the federated view; one started in a child sees that repo alone.
440
+
441
+ Commands also find the graph from a subdirectory: with no `[dir]` argument they
442
+ walk up to the nearest `graft/`, so `graft ask` works from `src/deep/inside/`
443
+ without a `cd` to the repo root.
444
+
441
445
  ## Visualize it (`graft viz`)
442
446
 
443
447
  `graft viz` opens a local, interactive view of both graphs — no install, no dev
@@ -1 +1 @@
1
- {"version":3,"file":"fuse.d.ts","sourceRoot":"","sources":["../../src/ask/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;8EAE8E;AAC9E,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B;;;mEAG+D;IAC/D,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,yCAAyC;IACzC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,uGAAuG;IACvG,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED;mEACmE;AACnE,eAAO,MAAM,KAAK,KAAK,CAAC;AAExB;;;;+CAI+C;AAC/C,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC;;;;;;;;;;;UAWU;AACV,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC;;;;;;2DAM2D;AAC3D,eAAO,MAAM,UAAU,MAAM,CAAC;AAQ9B;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,YAAY,CA+D1D;AAED;;;yBAGyB;AACzB,MAAM,WAAW,YAAY;IAC3B;6DACyD;IACzD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;;wBAMoB;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF;8EAC0E;IAC1E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrE;;uCAEmC;IACnC,UAAU,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,EAAE,YAAY,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,YAAY,CAqDd"}
1
+ {"version":3,"file":"fuse.d.ts","sourceRoot":"","sources":["../../src/ask/fuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;8EAE8E;AAC9E,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B;;;mEAG+D;IAC/D,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,yCAAyC;IACzC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,uGAAuG;IACvG,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED;mEACmE;AACnE,eAAO,MAAM,KAAK,KAAK,CAAC;AAExB;;;;+CAI+C;AAC/C,eAAO,MAAM,mBAAmB,OAAO,CAAC;AAExC;;;;;;;;;;;UAWU;AACV,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC;;;;;;2DAM2D;AAC3D,eAAO,MAAM,UAAU,MAAM,CAAC;AAQ9B;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,YAAY,CA+D1D;AAED;;;yBAGyB;AACzB,MAAM,WAAW,YAAY;IAC3B;6DACyD;IACzD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;;wBAMoB;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF;8EAC0E;IAC1E,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrE;;uCAEmC;IACnC,UAAU,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,EAAE,YAAY,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,YAAY,CAoEd"}
package/dist/ask/fuse.js CHANGED
@@ -166,25 +166,44 @@ export function rankScopesAndFuse(scopes, ops, graphWeight, rescueFloor) {
166
166
  }
167
167
  const alsoMatched = [];
168
168
  const scoped = [];
169
+ // Per-scope raw best + strength, computed once so we can pick survivors and,
170
+ // if the gate would exclude EVERY scope, rescue the strongest one (below).
171
+ const meta = new Map();
169
172
  for (const [scope, lex] of lexByScope) {
170
- // Raw best (+ its doc id) for THIS scope, score desc / id asc — same
171
- // tiebreak fuseScopes uses, so the reported bestId is deterministic.
172
- let maxLex = 0;
173
- let bestId = "";
174
- for (const [id, v] of lex) {
173
+ let maxLex = 0, bestId = "";
174
+ for (const [id, v] of lex)
175
175
  if (v > maxLex || (v === maxLex && id < bestId)) {
176
176
  maxLex = v;
177
177
  bestId = id;
178
178
  }
179
- }
180
179
  if (maxLex <= 0)
181
- continue; // no positive-scoring doc — contributes nothing
180
+ continue;
182
181
  const { coverage, coverageStrong } = ops.strength(scope, bestId);
183
- if (coverageStrong < STRONG_FLOOR && coverage < HIGH_FLOOR) {
184
- alsoMatched.push({ scope, bestId });
185
- continue; // excluded from fusion — no walk, no blend
186
- }
187
- // Pass 2: only surviving scopes pay for the graph walk + blend.
182
+ // strength is the scale-INVARIANT [0,1] match share (name vs name+path+body);
183
+ // used to pick the rescue scope fairly across corpora, unlike raw maxLex.
184
+ meta.set(scope, { lex, maxLex, bestId, strength: Math.max(coverage, coverageStrong), passes: coverageStrong >= STRONG_FLOOR || coverage >= HIGH_FLOOR });
185
+ }
186
+ const survivors = [...meta.entries()].filter(([, m]) => m.passes).map(([s]) => s);
187
+ // Anti-abstain rescue: the strength gate is a SHARE of the whole query, so a
188
+ // long/verbose query (a pasted issue body) can push every scope below the
189
+ // floor and make `ask` return nothing — measurably worse than grep. When the
190
+ // gate would exclude every scope yet real matches exist, keep the single
191
+ // strongest scope so `ask` returns best-effort hits (the escalation nudge
192
+ // already tells the agent to switch to grep if they look weak) instead of
193
+ // abstaining. The gate still does its real job — suppressing a weak scope
194
+ // beside a strong one — whenever any scope passes.
195
+ if (survivors.length === 0 && meta.size > 0) {
196
+ // Tiebreak on scale-invariant match STRENGTH (not raw per-corpus maxLex,
197
+ // which would let the biggest scope's numerically-larger score win — the very
198
+ // cross-corpus scale bias RRF exists to avoid). Fall back to maxLex only when
199
+ // strength ties (e.g. all zero), then scope name for determinism.
200
+ survivors.push([...meta.entries()].sort((a, b) => b[1].strength - a[1].strength || b[1].maxLex - a[1].maxLex || a[0].localeCompare(b[0]))[0][0]);
201
+ }
202
+ for (const [scope, m] of meta)
203
+ if (!survivors.includes(scope))
204
+ alsoMatched.push({ scope, bestId: m.bestId });
205
+ for (const scope of survivors) {
206
+ const { lex, maxLex } = meta.get(scope);
188
207
  const pr = ops.walk(scope, lex);
189
208
  const candidates = new Set(lex.keys());
190
209
  for (const [id, p] of pr)
@@ -192,8 +211,7 @@ export function rankScopesAndFuse(scopes, ops, graphWeight, rescueFloor) {
192
211
  candidates.add(id);
193
212
  for (const id of candidates) {
194
213
  const lexN = maxLex > 0 ? (lex.get(id) ?? 0) / maxLex : 0;
195
- const blended = (lexN + graphWeight * (pr.get(id) ?? 0)) *
196
- (ops.rankFactor?.(scope, id) ?? 1);
214
+ const blended = (lexN + graphWeight * (pr.get(id) ?? 0)) * (ops.rankFactor?.(scope, id) ?? 1);
197
215
  if (blended > 0)
198
216
  scoped.push({ id, scope, score: blended });
199
217
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/ask/fuse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/ask/fuse.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6B,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAWzD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CASrG;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,GAAG,EAAE,QAAQ,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,UAAU,CA+BjG"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6B,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAWzD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAWrG;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,GAAG,EAAE,QAAQ,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,UAAU,CA+BjG"}
@@ -1,4 +1,4 @@
1
- import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync } from 'node:fs';
1
+ import { mkdirSync, writeFileSync, readFileSync, chmodSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import { mergeGraftSettings } from './settings-merge.js';
@@ -6,6 +6,7 @@ import { statuslineShim, hooksShim } from './shim-template.js';
6
6
  import { skillTemplate } from './skill-template.js';
7
7
  import { claudeDistDir } from './paths.js';
8
8
  import { mergeJsonKey, serverEntry } from '../hosts/mcp-config.js';
9
+ import { hasGraftIndex } from '../graph/root.js';
9
10
  /**
10
11
  * The files `runInit` writes — pure, no writes, so `--dry-run` and the picker
11
12
  * can report them up front. All repo-local: the Claude Code layer never writes
@@ -29,8 +30,10 @@ export function claudeTargets(dir) {
29
30
  * Best-effort; the user can always run `graft build` (the epilogue says so).
30
31
  */
31
32
  export function buildGraphIfMissing(dir, opts) {
32
- const wiring = join(dir, 'graft', '.graph', 'wiring.json');
33
- if (opts.build === false || !opts.cliPath || existsSync(wiring))
33
+ // `hasGraftIndex`, not just wiring.json: a workspace parent's graph IS its
34
+ // `workspace.json` (nodes live in the children), so testing for wiring.json
35
+ // alone would call it unbuilt and rebuild every child on each init.
36
+ if (opts.build === false || !opts.cliPath || hasGraftIndex(dir))
34
37
  return false;
35
38
  try {
36
39
  execFileSync(process.execPath, [opts.cliPath, 'build', '.'], { cwd: dir, stdio: 'inherit', timeout: 300000 });
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import "dotenv/config";
8
8
  import { Command } from "commander";
9
- import { relative, resolve } from "node:path";
9
+ import { join, relative, resolve } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { Graft } from "./engine.js";
12
12
  import { resolveConfig } from "./ai/providers.js";
@@ -19,6 +19,8 @@ import { contextDirFor } from "./context/node-file.js";
19
19
  import { loadGraphCached } from "./graph/load.js";
20
20
  import { ensureFreshChildren, ensureFreshGraph, refreshNote } from "./graph/refresh.js";
21
21
  import { isWorkspaceBuildRoot, readWorkspace } from "./graph/workspace.js";
22
+ import { nearestGraftRoot } from "./graph/root.js";
23
+ import { discoverWorkspaceChildren } from "./graph/scopes.js";
22
24
  import { runWorkspaceAsk, runWorkspaceBuild, runWorkspaceCallers, runWorkspaceCheck, runWorkspaceGrep, runWorkspaceMap, } from "./graph/workspace-cli.js";
23
25
  import { formatInitEpilogue } from "./cli-epilogue.js";
24
26
  import { planInit, selectedWrites } from "./hosts/plan.js";
@@ -49,6 +51,22 @@ function cliConfig() {
49
51
  };
50
52
  }
51
53
  const engineFrom = () => new Graft(cliConfig());
54
+ /** Text for the omitted-`[dir]` case, shared by every query command's help. */
55
+ const DIR_ARG = ["[dir]", "repository root (default: nearest ancestor with a graft/ index)"];
56
+ /**
57
+ * The root a query runs against: the dir the user named, else the nearest
58
+ * ancestor holding a graft index (`graph/root.ts`) so a shell or agent session
59
+ * in a subdirectory still finds the graph. The walk is announced on stderr —
60
+ * answering from an ancestor's graph must never be silent.
61
+ */
62
+ function queryRoot(dir) {
63
+ if (dir !== undefined)
64
+ return resolve(dir);
65
+ const { root, levels } = nearestGraftRoot(process.cwd(), program.opts().dir);
66
+ if (levels > 0)
67
+ console.error(`[graft] no graft/ here — answering from ${root}/graft`);
68
+ return root;
69
+ }
52
70
  /**
53
71
  * Bring the graph up to date with the working tree before a query answers from it
54
72
  * — the same gate the MCP tools run (see `graph/refresh.ts`). Cheap when nothing
@@ -95,6 +113,7 @@ program
95
113
  .option("-e, --extensions <exts...>", 'code extensions to include (e.g. ".ts" ".py")')
96
114
  .option("-j, --concurrency <n>", "files summarized in parallel during --deep (default 5)")
97
115
  .option("--no-reuse", "re-parse every file instead of replaying unchanged ones from the extraction cache")
116
+ .option("--lsp", "add compiler-grade call edges via a language server if one is installed (opt-in, slower; e.g. rust-analyzer, clangd)")
98
117
  .option("--include-dir <name>", "override SKIP_DIRS for this repo's walks — repeatable (e.g. --include-dir build --include-dir tools); " +
99
118
  "persisted, so a later build (and the hooks/refresh path) include it without the flag; dot-dirs are never overridable", (val, prev) => [...prev, val], [])
100
119
  .action(async (dir, opts) => {
@@ -171,6 +190,7 @@ program
171
190
  llm: deep,
172
191
  concurrency,
173
192
  reuse: opts.reuse,
193
+ lsp: opts.lsp,
174
194
  onProgress: ({ phase, index, total, file }) => process.stderr.write(`\r${phase === "enrich" ? "summarizing" : "parsing"} ${index + 1}/${total}: ${file.slice(0, 50).padEnd(50)}`),
175
195
  });
176
196
  process.stderr.write("\n");
@@ -193,18 +213,20 @@ program
193
213
  .command("ask")
194
214
  .description("Query the graft/ graph — returns ranked nodes + exact file:line, routed to prose or wiring ($0, no key)")
195
215
  .argument("<query>", "what you want to understand, in plain words")
196
- .argument("[dir]", "repository root", ".")
216
+ .argument(...DIR_ARG)
197
217
  .option("-n, --limit <n>", "max results", "8")
198
218
  .option("--source", "inline the source at each file:line hit (retriever mode — the pack IS the answer, no need to re-open files)")
199
219
  .option("--full", "with --source: inline whole definition spans instead of the default ≤8-line crux excerpts")
200
220
  .option("--in <path>", "narrow to nodes under this path prefix, filtered before scoring (segment-aware, like scopeOf)")
201
221
  .option("--json", "output the result as JSON")
222
+ .option("--no-graph-rank", "rank by lexical relevance only, without the graph-connectivity re-rank (ablation/eval)")
202
223
  .option(...NO_REFRESH_FLAG)
203
- .action(async (query, dir, opts) => {
224
+ .action(async (query, dirArg, opts) => {
225
+ const dir = queryRoot(dirArg);
204
226
  await refreshBefore(dir, opts);
205
227
  const askGlobalDir = program.opts().dir;
206
- if (readWorkspace(resolve(dir), askGlobalDir)) {
207
- runWorkspaceAsk(resolve(dir), askGlobalDir, query, {
228
+ if (readWorkspace(dir, askGlobalDir)) {
229
+ runWorkspaceAsk(dir, askGlobalDir, query, {
208
230
  limit: Number(opts.limit), source: opts.source, full: opts.full, in: opts.in, json: opts.json,
209
231
  });
210
232
  return;
@@ -212,7 +234,7 @@ program
212
234
  const engine = engineFrom();
213
235
  let r;
214
236
  try {
215
- r = engine.ask(dir, query, { limit: Number(opts.limit), source: opts.source, full: opts.full, in: opts.in });
237
+ r = engine.ask(dir, query, { limit: Number(opts.limit), source: opts.source, full: opts.full, in: opts.in, graphRank: opts.graphRank });
216
238
  }
217
239
  catch (err) {
218
240
  console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
@@ -231,10 +253,11 @@ program
231
253
  .command("skeleton")
232
254
  .description("Signatures-only view of one file from the wiring graph — the cheapest way to see a file's API surface")
233
255
  .argument("<file>", "repo-relative path (or unique basename) of the file")
234
- .argument("[dir]", "repository root", ".")
256
+ .argument(...DIR_ARG)
235
257
  .option("--json", "output the result as JSON")
236
258
  .option(...NO_REFRESH_FLAG)
237
- .action(async (file, dir, opts) => {
259
+ .action(async (file, dirArg, opts) => {
260
+ const dir = queryRoot(dirArg);
238
261
  await refreshBefore(dir, opts);
239
262
  const { skeleton, formatSkeleton } = await import("./ask/ask.js");
240
263
  const globalOpts = program.opts();
@@ -247,18 +270,19 @@ program
247
270
  program
248
271
  .command("check")
249
272
  .description("Fail if graft/ is stale relative to the code (for CI)")
250
- .argument("[dir]", "repository root", ".")
273
+ .argument(...DIR_ARG)
251
274
  .option("-e, --extensions <exts...>", "code extensions to include")
252
275
  .option("--json", "output the drift as JSON")
253
- .action((dir, opts) => {
276
+ .action(async (dirArg, opts) => {
277
+ const dir = queryRoot(dirArg);
254
278
  const checkGlobalDir = program.opts().dir;
255
- if (readWorkspace(resolve(dir), checkGlobalDir)) {
256
- runWorkspaceCheck(resolve(dir), checkGlobalDir);
279
+ if (readWorkspace(dir, checkGlobalDir)) {
280
+ await runWorkspaceCheck(dir, checkGlobalDir);
257
281
  return;
258
282
  }
259
283
  const engine = engineFrom();
260
284
  const r = engine.check(dir, { extensions: opts.extensions });
261
- const g = engine.checkGraph(dir); // graph.json is only judged when it exists
285
+ const g = await engine.checkGraph(dir); // graph.json is only judged when it exists
262
286
  // A layer that IS present must be in sync; a never-built layer (keyless
263
287
  // build skips the markdown layer) is informational, not a failure.
264
288
  const bothMissing = r.missing && g.missing;
@@ -286,10 +310,11 @@ program
286
310
  program
287
311
  .command("viz")
288
312
  .description("Serve an interactive visualization of the context graph (and graph.json when present)")
289
- .argument("[dir]", "repository root", ".")
313
+ .argument(...DIR_ARG)
290
314
  .option("-p, --port <port>", "port to serve on", "4400")
291
315
  .option("--no-open", "don't open the browser")
292
- .action(async (dir, opts) => {
316
+ .action(async (dirArg, opts) => {
317
+ const dir = queryRoot(dirArg);
293
318
  const { existsSync } = await import("node:fs");
294
319
  const { resolve, basename } = await import("node:path");
295
320
  const { spawn } = await import("node:child_process");
@@ -319,28 +344,29 @@ program
319
344
  program
320
345
  .command("mcp")
321
346
  .description("Serve the graph over MCP (stdio) — exposes graft_find_code, graft_trace_calls, graft_find_all, graft_file_api, graft_repo_map and graft_check_freshness as tools")
322
- .argument("[dir]", "repository root", ".")
323
- .action(async (dir) => {
324
- const { resolve } = await import("node:path");
347
+ .argument(...DIR_ARG)
348
+ .action(async (dirArg) => {
349
+ const dir = queryRoot(dirArg);
325
350
  const { startMcpServer } = await import("./mcp/server.js");
326
351
  const globalOpts = program.opts();
327
- startMcpServer(resolve(dir), globalOpts.dir, currentVersion);
352
+ startMcpServer(dir, globalOpts.dir, currentVersion);
328
353
  });
329
354
  program
330
355
  .command("callers")
331
356
  .description("Who calls/references a symbol ($0, no LLM). --direction out gives callees (what it calls); --depth N (or all) walks transitively for full blast radius")
332
357
  .argument("<symbol>", "bare name, qualified (Class.method), or package-qualified (pkg.Fn)")
333
- .argument("[dir]", "repository root", ".")
358
+ .argument(...DIR_ARG)
334
359
  .option("--direction <in|out>", 'edge direction: "in" = callers (default), "out" = callees')
335
360
  .option("-d, --depth <n>", 'walk transitively up to N hops for blast radius, or "all" for the full connected closure (default 1)')
336
361
  .option("--in <path>", "narrow matches to nodes at or under this path prefix")
337
362
  .option("--json", "output as JSON")
338
363
  .option(...NO_REFRESH_FLAG)
339
- .action(async (symbol, dir, opts) => {
364
+ .action(async (symbol, dirArg, opts) => {
365
+ const dir = queryRoot(dirArg);
340
366
  await refreshBefore(dir, opts);
341
367
  const globalOpts = program.opts();
342
- if (!opts.json && readWorkspace(resolve(dir), globalOpts.dir)) {
343
- runWorkspaceCallers(resolve(dir), globalOpts.dir, symbol, {
368
+ if (!opts.json && readWorkspace(dir, globalOpts.dir)) {
369
+ runWorkspaceCallers(dir, globalOpts.dir, symbol, {
344
370
  direction: opts.direction === "out" ? "out" : "in",
345
371
  depth: opts.depth
346
372
  ? (/^(all|full|max)$/i.test(opts.depth) ? Number.POSITIVE_INFINITY : Number(opts.depth))
@@ -362,17 +388,18 @@ program
362
388
  .command("grep")
363
389
  .description("Regex search over indexed files, hits grouped by enclosing symbol and ranked by coupling ($0, no LLM)")
364
390
  .argument("<pattern>", "regex pattern (or literal string with --fixed)")
365
- .argument("[dir]", "repository root", ".")
391
+ .argument(...DIR_ARG)
366
392
  .option("-i, --ignore-case", "case-insensitive match")
367
393
  .option("--fixed", "treat pattern as a literal string, not a regex")
368
394
  .option("--in <path>", "narrow to files at or under this path prefix")
369
395
  .option("--json", "output as JSON")
370
396
  .option(...NO_REFRESH_FLAG)
371
- .action(async (pattern, dir, opts) => {
397
+ .action(async (pattern, dirArg, opts) => {
398
+ const dir = queryRoot(dirArg);
372
399
  await refreshBefore(dir, opts);
373
400
  const globalOpts = program.opts();
374
- if (readWorkspace(resolve(dir), globalOpts.dir)) {
375
- runWorkspaceGrep(resolve(dir), globalOpts.dir, pattern, {
401
+ if (readWorkspace(dir, globalOpts.dir)) {
402
+ runWorkspaceGrep(dir, globalOpts.dir, pattern, {
376
403
  ignoreCase: opts.ignoreCase, fixed: opts.fixed, json: opts.json,
377
404
  });
378
405
  return;
@@ -389,11 +416,12 @@ program
389
416
  program
390
417
  .command("map")
391
418
  .description("Token-budgeted repo orientation — directory clusters, per-directory hubs, and global hotspots from the wiring graph ($0, no LLM)")
392
- .argument("[dir]", "repository root", ".")
419
+ .argument(...DIR_ARG)
393
420
  .option("--max-dirs <n>", "max directory entries shown, rest counted into dropped (default 16)")
394
421
  .option("--json", "output as JSON")
395
422
  .option(...NO_REFRESH_FLAG)
396
- .action(async (dir, opts) => {
423
+ .action(async (dirArg, opts) => {
424
+ const dir = queryRoot(dirArg);
397
425
  const root = resolve(dir);
398
426
  const globalOpts = program.opts();
399
427
  let maxDirsW;
@@ -485,8 +513,20 @@ program
485
513
  console.error(formatNonInteractiveHelp(detectedIds));
486
514
  return;
487
515
  }
516
+ // Workspace parent: every child repo gets its OWN wiring too. A session
517
+ // opens at a repo root, not at the parent, and reads `.claude/` from there —
518
+ // wiring only the parent leaves each child with no skill, hooks, or MCP.
519
+ // The parent's own wiring stays (queries there federate across children).
520
+ const children = isWorkspaceBuildRoot(repo, program.opts().dir)
521
+ ? discoverWorkspaceChildren(repo)
522
+ : [];
523
+ // Parent FIRST: its build is the workspace build, which builds every child's
524
+ // graph, so each child's own `buildGraphIfMissing` then finds one and no-ops.
525
+ const targets = [repo, ...children.map((c) => join(repo, c))];
488
526
  if (opts.dryRun) {
489
527
  console.error(formatPlan(plan, ids, repo, home));
528
+ for (const child of children)
529
+ console.error(`\n— ${child}/ (workspace child)\n` + formatPlan(planInit(join(repo, child), { home }), ids, join(repo, child), home));
490
530
  return;
491
531
  }
492
532
  if (ids.length === 0) {
@@ -495,6 +535,29 @@ program
495
535
  }
496
536
  const wantClaude = ids.includes("claude");
497
537
  const cliPath = fileURLToPath(import.meta.url);
538
+ if (children.length)
539
+ console.error(`· workspace: wiring ${repo} and ${children.length} child repo(s) — ${children.join(", ")}`);
540
+ for (const target of targets) {
541
+ if (target !== repo)
542
+ console.error(`\n— ${relative(repo, target)}/`);
543
+ wireTarget(target, ids, { home, cliPath, plan, opts, wantClaude });
544
+ }
545
+ // One epilogue for the whole run. A workspace parent holds no nodes of its
546
+ // own, so the totals come from the children — the graph the user actually got.
547
+ const globalDir = program.opts().dir;
548
+ const graphs = (children.length ? children.map((c) => join(repo, c)) : [repo])
549
+ .map((d) => loadGraphCached(contextDirFor(d, children.length ? undefined : globalDir)))
550
+ .filter((g) => g !== null);
551
+ console.error("\n" +
552
+ formatInitEpilogue({
553
+ graphBuilt: graphs.length > 0,
554
+ nodes: graphs.reduce((n, g) => n + g.meta.nodeCount, 0),
555
+ edges: graphs.reduce((n, g) => n + g.meta.edgeCount, 0),
556
+ }));
557
+ });
558
+ /** One repo's worth of `init` writes — the parent, then each workspace child. */
559
+ function wireTarget(repo, ids, ctx) {
560
+ const { home, cliPath, plan, wantClaude, opts } = ctx;
498
561
  if (wantClaude) {
499
562
  const res = runInit(repo, { build: opts.build, cliPath });
500
563
  console.error(`✓ wrote ${res.settingsPath}`);
@@ -539,16 +602,7 @@ program
539
602
  ? "✓ built the graph (graft build)"
540
603
  : "· skipped graph build");
541
604
  }
542
- const globalOpts = program.opts();
543
- const outDir = contextDirFor(repo, globalOpts.dir);
544
- const graph = loadGraphCached(outDir);
545
- console.error("\n" +
546
- formatInitEpilogue({
547
- graphBuilt: graph !== null,
548
- nodes: graph?.meta.nodeCount,
549
- edges: graph?.meta.edgeCount,
550
- }));
551
- });
605
+ }
552
606
  program.parseAsync().catch((err) => {
553
607
  console.error(err instanceof Error ? err.message : err);
554
608
  process.exit(1);