@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.
- package/README.md +49 -45
- package/dist/ask/fuse.d.ts.map +1 -1
- package/dist/ask/fuse.js +32 -14
- package/dist/ask/fuse.js.map +1 -1
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +6 -3
- package/dist/claude/init.js.map +1 -1
- package/dist/cli.js +93 -39
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +6 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -1
- package/dist/engine.js.map +1 -1
- package/dist/graph/build.d.ts +5 -0
- package/dist/graph/build.d.ts.map +1 -1
- package/dist/graph/build.js +21 -2
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/check.d.ts +1 -1
- package/dist/graph/check.d.ts.map +1 -1
- package/dist/graph/check.js +13 -3
- package/dist/graph/check.js.map +1 -1
- package/dist/graph/generic.d.ts +25 -0
- package/dist/graph/generic.d.ts.map +1 -0
- package/dist/graph/generic.js +320 -0
- package/dist/graph/generic.js.map +1 -0
- package/dist/graph/lsp/client.d.ts +43 -0
- package/dist/graph/lsp/client.d.ts.map +1 -0
- package/dist/graph/lsp/client.js +179 -0
- package/dist/graph/lsp/client.js.map +1 -0
- package/dist/graph/lsp/enrich.d.ts +11 -0
- package/dist/graph/lsp/enrich.d.ts.map +1 -0
- package/dist/graph/lsp/enrich.js +155 -0
- package/dist/graph/lsp/enrich.js.map +1 -0
- package/dist/graph/lsp/registry.d.ts +15 -0
- package/dist/graph/lsp/registry.d.ts.map +1 -0
- package/dist/graph/lsp/registry.js +46 -0
- package/dist/graph/lsp/registry.js.map +1 -0
- package/dist/graph/queries/c.scm +13 -0
- package/dist/graph/queries/c_sharp.scm +19 -0
- package/dist/graph/queries/cpp.scm +23 -0
- package/dist/graph/queries/elixir.scm +54 -0
- package/dist/graph/queries/java.scm +20 -0
- package/dist/graph/queries/kotlin.scm +43 -0
- package/dist/graph/queries/php.scm +40 -0
- package/dist/graph/queries/ruby.scm +65 -0
- package/dist/graph/queries/rust.scm +41 -0
- package/dist/graph/queries/scala.scm +66 -0
- package/dist/graph/queries/solidity.scm +43 -0
- package/dist/graph/queries/swift.scm +51 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +13 -3
- package/dist/graph/resolve.js.map +1 -1
- package/dist/graph/root.d.ts +11 -0
- package/dist/graph/root.d.ts.map +1 -0
- package/dist/graph/root.js +40 -0
- package/dist/graph/root.js.map +1 -0
- package/dist/graph/source-files.d.ts.map +1 -1
- package/dist/graph/source-files.js +5 -1
- package/dist/graph/source-files.js.map +1 -1
- package/dist/graph/types.d.ts +12 -7
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/types.js +2 -2
- package/dist/graph/workspace-cli.d.ts +1 -1
- package/dist/graph/workspace-cli.d.ts.map +1 -1
- package/dist/graph/workspace-cli.js +2 -2
- package/dist/graph/workspace-cli.js.map +1 -1
- package/dist/graph/workspace.d.ts +2 -2
- package/dist/graph/workspace.d.ts.map +1 -1
- package/dist/graph/workspace.js +2 -2
- package/dist/graph/workspace.js.map +1 -1
- package/dist/mcp/tools.js +6 -6
- package/dist/mcp/tools.js.map +1 -1
- package/package.json +6 -2
- package/scripts/build-viewer.mjs +17 -1
- 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
|
|
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%.
|
|
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-
|
|
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
|
package/dist/ask/fuse.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
171
|
-
|
|
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;
|
|
180
|
+
continue;
|
|
182
181
|
const { coverage, coverageStrong } = ops.strength(scope, bestId);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
}
|
package/dist/ask/fuse.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/claude/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync, readFileSync,
|
|
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
|
-
|
|
33
|
-
|
|
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 });
|
package/dist/claude/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,
|
|
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(
|
|
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,
|
|
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(
|
|
207
|
-
runWorkspaceAsk(
|
|
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(
|
|
256
|
+
.argument(...DIR_ARG)
|
|
235
257
|
.option("--json", "output the result as JSON")
|
|
236
258
|
.option(...NO_REFRESH_FLAG)
|
|
237
|
-
.action(async (file,
|
|
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(
|
|
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((
|
|
276
|
+
.action(async (dirArg, opts) => {
|
|
277
|
+
const dir = queryRoot(dirArg);
|
|
254
278
|
const checkGlobalDir = program.opts().dir;
|
|
255
|
-
if (readWorkspace(
|
|
256
|
-
runWorkspaceCheck(
|
|
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(
|
|
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 (
|
|
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(
|
|
323
|
-
.action(async (
|
|
324
|
-
const
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
343
|
-
runWorkspaceCallers(
|
|
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(
|
|
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,
|
|
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(
|
|
375
|
-
runWorkspaceGrep(
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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);
|