@nanonets/graft 0.5.1 → 0.7.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 +29 -7
- package/dist/ask/ask.d.ts +52 -1
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +274 -83
- package/dist/ask/ask.js.map +1 -1
- package/dist/ask/fuse.d.ts +59 -17
- package/dist/ask/fuse.d.ts.map +1 -1
- package/dist/ask/fuse.js +51 -25
- package/dist/ask/fuse.js.map +1 -1
- package/dist/ask/graphrank.d.ts +6 -0
- package/dist/ask/graphrank.d.ts.map +1 -1
- package/dist/ask/graphrank.js +5 -1
- package/dist/ask/graphrank.js.map +1 -1
- package/dist/claude/format.js +1 -1
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hooks.d.ts +16 -0
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +57 -2
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.d.ts +3 -0
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +6 -1
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +20 -9
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/cli.d.ts +3 -14
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +99 -70
- package/dist/cli.js.map +1 -1
- package/dist/context/savings.d.ts +34 -0
- package/dist/context/savings.d.ts.map +1 -0
- package/dist/context/savings.js +49 -0
- package/dist/context/savings.js.map +1 -0
- package/dist/engine.d.ts +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +7 -1
- package/dist/engine.js.map +1 -1
- package/dist/graph/build.d.ts.map +1 -1
- package/dist/graph/build.js +33 -0
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/map.d.ts +38 -2
- package/dist/graph/map.d.ts.map +1 -1
- package/dist/graph/map.js +111 -24
- package/dist/graph/map.js.map +1 -1
- package/dist/graph/scopes.d.ts +15 -0
- package/dist/graph/scopes.d.ts.map +1 -1
- package/dist/graph/scopes.js +23 -0
- package/dist/graph/scopes.js.map +1 -1
- package/dist/graph/traverse-cli.d.ts +28 -18
- package/dist/graph/traverse-cli.d.ts.map +1 -1
- package/dist/graph/traverse-cli.js +68 -43
- package/dist/graph/traverse-cli.js.map +1 -1
- package/dist/graph/traverse.d.ts +35 -14
- package/dist/graph/traverse.d.ts.map +1 -1
- package/dist/graph/traverse.js +46 -20
- package/dist/graph/traverse.js.map +1 -1
- package/dist/graph/types.d.ts +12 -0
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/workspace-cli.d.ts +37 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -0
- package/dist/graph/workspace-cli.js +70 -0
- package/dist/graph/workspace-cli.js.map +1 -0
- package/dist/graph/workspace.d.ts +128 -0
- package/dist/graph/workspace.d.ts.map +1 -0
- package/dist/graph/workspace.js +395 -0
- package/dist/graph/workspace.js.map +1 -0
- package/dist/hosts/instructions.d.ts.map +1 -1
- package/dist/hosts/instructions.js +7 -3
- package/dist/hosts/instructions.js.map +1 -1
- package/dist/hosts/mcp-config.d.ts +5 -0
- package/dist/hosts/mcp-config.d.ts.map +1 -1
- package/dist/hosts/mcp-config.js +2 -2
- package/dist/hosts/mcp-config.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +94 -66
- package/dist/mcp/tools.js.map +1 -1
- package/dist/search/grep-cli.d.ts.map +1 -1
- package/dist/search/grep-cli.js +4 -0
- package/dist/search/grep-cli.js.map +1 -1
- package/dist/search/grep.d.ts +4 -0
- package/dist/search/grep.d.ts.map +1 -1
- package/dist/search/grep.js +2 -0
- package/dist/search/grep.js.map +1 -1
- package/package.json +1 -1
package/dist/claude/hooks.js
CHANGED
|
@@ -5,6 +5,7 @@ import { readWiring } from './stats.js';
|
|
|
5
5
|
import { formatBlastRadius, relevantRetrieval, formatOrientation } from './format.js';
|
|
6
6
|
import { patchStats, readStats, acquireLock, readSession, writeSession } from './state.js';
|
|
7
7
|
import { graftCliPath, claudeScriptPath } from './paths.js';
|
|
8
|
+
import { scopeOf, scopesOfGraph } from '../graph/scopes.js';
|
|
8
9
|
/** Prompts shorter than this never trigger retrieval — they are almost always
|
|
9
10
|
* conversational ("yes go ahead", "thanks") and the coverage gate can't judge
|
|
10
11
|
* them reliably with so few terms. */
|
|
@@ -34,7 +35,12 @@ export function underGraft(dir, file) {
|
|
|
34
35
|
}
|
|
35
36
|
function graftJson(dir, args) {
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
+
// GRAFT_TEST_CLI is a test seam (mirrors GRAFT_TEST_STDIN/GRAFT_TEST_SYNC_RUN) so
|
|
39
|
+
// tests can point the prompt hook's `graft ask`/`graft check` calls at a stub
|
|
40
|
+
// script and observe the exact args it was invoked with, instead of shelling
|
|
41
|
+
// out to the real CLI (which isn't built relative to the TS source under test).
|
|
42
|
+
const cliPath = process.env.GRAFT_TEST_CLI ?? graftCliPath();
|
|
43
|
+
const out = execFileSync(process.execPath, [cliPath, ...args], { cwd: dir, encoding: 'utf8', timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
38
44
|
return JSON.parse(out);
|
|
39
45
|
}
|
|
40
46
|
catch (e) {
|
|
@@ -69,6 +75,49 @@ async function handlePostEdit(input, dir) {
|
|
|
69
75
|
emit('PostToolUse', br);
|
|
70
76
|
}
|
|
71
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The "you're working in backend/, weight it" hint: on a multi-scope repo,
|
|
80
|
+
* narrow the prompt hook's `ask` call to whatever scope the last-edited file
|
|
81
|
+
* (`stats.lastFile`, captured at {@link handlePostEdit}) sits in.
|
|
82
|
+
*
|
|
83
|
+
* `lastFile` is only a basename (not a repo-relative path — see
|
|
84
|
+
* `handlePostEdit`), so this is a best-effort lookup against the CURRENT
|
|
85
|
+
* graph: any file node whose path ends in `/<lastFile>` (or equals it, for a
|
|
86
|
+
* repo-root file). Fails soft in every direction a hook must never crash on —
|
|
87
|
+
* no graph, a single-scope graph, a lastFile no longer in the graph (moved,
|
|
88
|
+
* deleted, or edited before the first build), or a basename that lands in
|
|
89
|
+
* more than one scope (ambiguous: could be either sub-project) all skip the
|
|
90
|
+
* hint silently, logging one line to stderr so the miss is visible without
|
|
91
|
+
* ever failing the hook.
|
|
92
|
+
*/
|
|
93
|
+
export function lastFileScopeHint(dir, lastFile) {
|
|
94
|
+
if (!lastFile)
|
|
95
|
+
return null;
|
|
96
|
+
try {
|
|
97
|
+
const w = readWiring(dir);
|
|
98
|
+
if (!w)
|
|
99
|
+
return null;
|
|
100
|
+
const scopes = scopesOfGraph(w);
|
|
101
|
+
if (scopes.length <= 1)
|
|
102
|
+
return null; // single-scope: no hint, no --in
|
|
103
|
+
const matches = (w.nodes ?? []).filter((n) => n.kind === 'file' && (n.path === lastFile || n.path.endsWith(`/${lastFile}`)));
|
|
104
|
+
if (matches.length === 0) {
|
|
105
|
+
console.error(`[graft] prompt hook: lastFile "${lastFile}" not found in the graph — skipping scope hint`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const prefixes = new Set(matches.map((n) => scopeOf(n.path, scopes).prefix));
|
|
109
|
+
if (prefixes.size > 1) {
|
|
110
|
+
console.error(`[graft] prompt hook: lastFile "${lastFile}" matches more than one scope — skipping scope hint`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const [prefix] = prefixes;
|
|
114
|
+
return prefix === '' ? null : prefix; // root scope: nothing to narrow
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
console.error(`[graft] prompt hook: scope hint lookup failed (${e?.message ?? e}) — skipping`);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
72
121
|
function handleStop(dir) {
|
|
73
122
|
// sync-run.js ships next to this module inside the package, so it resolves in
|
|
74
123
|
// any repo that installs graft (not just graft's own). Defensive existsSync:
|
|
@@ -119,7 +168,13 @@ export async function main(event) {
|
|
|
119
168
|
// pulls spans itself via `graft ask --source` when a pointer looks right.
|
|
120
169
|
// relevantRetrieval then drops the pack entirely when the prompt barely
|
|
121
170
|
// overlaps the top hit or when every hit was already injected this session.
|
|
122
|
-
const
|
|
171
|
+
const askArgs = ['ask', prompt, '.', '--json', '-n', '3'];
|
|
172
|
+
// "You're working in backend/, weight it": only fires on a multi-scope
|
|
173
|
+
// repo whose lastFile resolves cleanly to one scope — see lastFileScopeHint.
|
|
174
|
+
const scopeHint = lastFileScopeHint(dir, readStats(dir)?.lastFile);
|
|
175
|
+
if (scopeHint)
|
|
176
|
+
askArgs.push('--in', scopeHint);
|
|
177
|
+
const ask = graftJson(dir, askArgs);
|
|
123
178
|
if (!ask)
|
|
124
179
|
return;
|
|
125
180
|
const id = input.session_id || 'default';
|
package/dist/claude/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/claude/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE5D;;sCAEsC;AACtC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACtD,CAAC;AACD,SAAS,WAAW,KAAa,IAAI,CAAC;IAAC,OAAO,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAAC,CAAC;AAAC,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC;AAAC,CAAC,CAAC,CAAC;AAE/F,SAAS,UAAU,CAAC,KAAU;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AACtE,CAAC;AACD,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAY;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,OAAO,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC7E,CAAC;AACD,SAAS,SAAS,CAAC,GAAW,EAAE,IAAc;IAC5C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/claude/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/claude/init.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { type McpWrite } from '../hosts/mcp-config.js';
|
|
1
2
|
export interface InitResult {
|
|
2
3
|
settingsPath: string;
|
|
3
4
|
shims: string[];
|
|
4
5
|
skill: string;
|
|
6
|
+
/** the `.mcp.json` write registering the graft MCP server for Claude Code. */
|
|
7
|
+
mcp: McpWrite;
|
|
5
8
|
warnings: string[];
|
|
6
9
|
built: boolean;
|
|
7
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"AAOA,OAAO,EAA8B,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEnF,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,CAqCjG"}
|
package/dist/claude/init.js
CHANGED
|
@@ -5,6 +5,7 @@ import { mergeGraftSettings } from './settings-merge.js';
|
|
|
5
5
|
import { statuslineShim, hooksShim } from './shim-template.js';
|
|
6
6
|
import { skillTemplate } from './skill-template.js';
|
|
7
7
|
import { claudeDistDir } from './paths.js';
|
|
8
|
+
import { mergeJsonKey, SERVER_ENTRY } from '../hosts/mcp-config.js';
|
|
8
9
|
export function runInit(dir, opts = {}) {
|
|
9
10
|
const helpersDir = join(dir, '.claude', 'helpers');
|
|
10
11
|
mkdirSync(helpersDir, { recursive: true });
|
|
@@ -29,6 +30,10 @@ export function runInit(dir, opts = {}) {
|
|
|
29
30
|
mkdirSync(skillDir, { recursive: true });
|
|
30
31
|
const skillPath = join(skillDir, 'SKILL.md');
|
|
31
32
|
writeFileSync(skillPath, skillTemplate());
|
|
33
|
+
// Register the graft MCP server in the project's .mcp.json so Claude Code
|
|
34
|
+
// exposes graft_ask/graft_callers/etc. as tools — the same keyed merge the
|
|
35
|
+
// other hosts use (existing servers preserved; unparseable files skipped).
|
|
36
|
+
const mcp = mergeJsonKey('claude', join(dir, '.mcp.json'), 'mcpServers', SERVER_ENTRY);
|
|
32
37
|
let built = false;
|
|
33
38
|
const wiring = join(dir, 'graft', '.graph', 'wiring.json');
|
|
34
39
|
if (opts.build !== false && opts.cliPath && !existsSync(wiring)) {
|
|
@@ -38,6 +43,6 @@ export function runInit(dir, opts = {}) {
|
|
|
38
43
|
}
|
|
39
44
|
catch { /* build best-effort; user can run `graft build` manually */ }
|
|
40
45
|
}
|
|
41
|
-
return { settingsPath, shims: [sl, hk], skill: skillPath, warnings, built };
|
|
46
|
+
return { settingsPath, shims: [sl, hk], skill: skillPath, mcp, warnings, built };
|
|
42
47
|
}
|
|
43
48
|
//# sourceMappingURL=init.js.map
|
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,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/claude/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-template.d.ts","sourceRoot":"","sources":["../../src/claude/skill-template.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"skill-template.d.ts","sourceRoot":"","sources":["../../src/claude/skill-template.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,IAAI,MAAM,CAsFtC"}
|
|
@@ -31,12 +31,17 @@ same understanding costs thousands.
|
|
|
31
31
|
ask is top-N and WILL miss instances; grep is the tool for "find them all".
|
|
32
32
|
- **A file's whole API surface** → \`graft skeleton <file>\` — every signature in
|
|
33
33
|
~200 tokens. One call beats several asks when you just need "what's in here".
|
|
34
|
-
- **Who uses / what breaks if I change X** → \`graft callers <symbol>\`
|
|
35
|
-
\`
|
|
36
|
-
|
|
34
|
+
- **Who uses / what breaks if I change X** → \`graft callers <symbol>\` (add
|
|
35
|
+
\`--depth N\` to walk transitively for the full blast radius); what X itself
|
|
36
|
+
depends on → \`graft callers <symbol> --direction out\`. Run one before
|
|
37
|
+
editing a symbol.
|
|
37
38
|
- **First contact with an unfamiliar repo** → \`graft map\` — token-budgeted
|
|
38
39
|
orientation (dir clusters, hubs, hotspots). Read the hub cards it names rather
|
|
39
40
|
than asking per subsystem.
|
|
41
|
+
- **Monorepos / a folder of multiple repos** → graft ranks fairly across
|
|
42
|
+
sub-projects instead of letting the biggest one drown the rest; hits carry
|
|
43
|
+
\`[scope/]\` labels naming which sub-project they're from. Know where you're
|
|
44
|
+
working? Narrow with \`graft ask "<task>" --in <scope>/\`.
|
|
40
45
|
|
|
41
46
|
You can also grep / ls / cat inside \`graft/\` directly (the nodes are plain
|
|
42
47
|
markdown; \`graft/INDEX.md\` lists them) — but the commands above are faster and
|
|
@@ -62,13 +67,19 @@ exhaustive where it matters, so reach for them first.
|
|
|
62
67
|
that is expected, not a graft failure.
|
|
63
68
|
|
|
64
69
|
**Precise graph modes** — for structural questions, skip ranking and go
|
|
65
|
-
straight to precomputed edges
|
|
70
|
+
straight to precomputed edges. It's all one command, \`graft callers\`:
|
|
66
71
|
|
|
67
|
-
- \`graft callers <symbol>\`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
- \`graft callers <symbol>\` — who calls/references this (exact edges, not text);
|
|
73
|
+
structural phrasing inside ask ("who calls X") routes here too.
|
|
74
|
+
- \`graft callers <symbol> --direction out\` — the reverse: what this symbol
|
|
75
|
+
itself calls/depends on (the old \`callees\`).
|
|
76
|
+
- \`graft callers <symbol> --depth N\` — walk transitively out to depth N for the
|
|
77
|
+
full blast radius of a change (the old \`impact\`).
|
|
78
|
+
|
|
79
|
+
When the graft MCP server is connected, the same operations are exposed as
|
|
80
|
+
tools — \`graft_ask\`, \`graft_callers\` (with \`direction\`/\`depth\`),
|
|
81
|
+
\`graft_grep\`, \`graft_skeleton\`, \`graft_map\` — prefer whichever surface is
|
|
82
|
+
available.
|
|
72
83
|
|
|
73
84
|
If a returned span is truncated ("+N more lines"), open the file at that exact
|
|
74
85
|
range before finalizing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-template.js","sourceRoot":"","sources":["../../src/claude/skill-template.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,0FAA0F;AAC1F,sFAAsF;AACtF,uFAAuF;AACvF,oCAAoC;AACpC,MAAM,UAAU,aAAa;IAC3B,OAAO
|
|
1
|
+
{"version":3,"file":"skill-template.js","sourceRoot":"","sources":["../../src/claude/skill-template.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,0FAA0F;AAC1F,sFAAsF;AACtF,uFAAuF;AACvF,oCAAoC;AACpC,MAAM,UAAU,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFR,CAAC;AACF,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `graft` CLI. Commands:
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* ask query the graph ($0, no LLM).
|
|
7
|
-
* check fail if graft/ has drifted from the code — for CI.
|
|
8
|
-
* viz serve the interactive graph viewer.
|
|
9
|
-
* mcp serve the graph over MCP (stdio) for coding agents.
|
|
10
|
-
* callers / callees / impact precise graph traversal for a symbol ($0, no LLM).
|
|
11
|
-
* grep regex search over indexed files, grouped by enclosing symbol, ranked by coupling ($0, no LLM).
|
|
12
|
-
* map token-budgeted repo orientation — dir clusters, hubs, hotspots ($0, no LLM).
|
|
13
|
-
* init set up the Claude Code integration (.claude/ statusline + hooks) in this repo.
|
|
14
|
-
*
|
|
15
|
-
* Git is the sync: commit graft/ and anyone who clones the repo has the
|
|
16
|
-
* graph, with no setup.
|
|
3
|
+
* `graft` CLI. Commands: build, ask, check, viz, mcp, callers, skeleton, grep,
|
|
4
|
+
* map, init. Git is the sync: commit graft/ and a clone has the graph. A
|
|
5
|
+
* workspace parent (≥2 git children) federates query commands across children.
|
|
17
6
|
*/
|
|
18
7
|
import "dotenv/config";
|
|
19
8
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,eAAe,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `graft` CLI. Commands:
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* ask query the graph ($0, no LLM).
|
|
7
|
-
* check fail if graft/ has drifted from the code — for CI.
|
|
8
|
-
* viz serve the interactive graph viewer.
|
|
9
|
-
* mcp serve the graph over MCP (stdio) for coding agents.
|
|
10
|
-
* callers / callees / impact precise graph traversal for a symbol ($0, no LLM).
|
|
11
|
-
* grep regex search over indexed files, grouped by enclosing symbol, ranked by coupling ($0, no LLM).
|
|
12
|
-
* map token-budgeted repo orientation — dir clusters, hubs, hotspots ($0, no LLM).
|
|
13
|
-
* init set up the Claude Code integration (.claude/ statusline + hooks) in this repo.
|
|
14
|
-
*
|
|
15
|
-
* Git is the sync: commit graft/ and anyone who clones the repo has the
|
|
16
|
-
* graph, with no setup.
|
|
3
|
+
* `graft` CLI. Commands: build, ask, check, viz, mcp, callers, skeleton, grep,
|
|
4
|
+
* map, init. Git is the sync: commit graft/ and a clone has the graph. A
|
|
5
|
+
* workspace parent (≥2 git children) federates query commands across children.
|
|
17
6
|
*/
|
|
18
7
|
import "dotenv/config";
|
|
19
8
|
import { Command } from "commander";
|
|
@@ -28,6 +17,8 @@ import { runHostsInit } from "./hosts/init.js";
|
|
|
28
17
|
import { hostIds } from "./hosts/registry.js";
|
|
29
18
|
import { contextDirFor } from "./context/node-file.js";
|
|
30
19
|
import { loadGraphCached } from "./graph/load.js";
|
|
20
|
+
import { isWorkspaceBuildRoot, readWorkspace } from "./graph/workspace.js";
|
|
21
|
+
import { runWorkspaceAsk, runWorkspaceBuild, runWorkspaceCallers, runWorkspaceCheck, runWorkspaceGrep, runWorkspaceMap, } from "./graph/workspace-cli.js";
|
|
31
22
|
import { formatInitEpilogue } from "./cli-epilogue.js";
|
|
32
23
|
import { formatUpgradeReport, formatVersionReport, getNpmViewVersion, readCurrentVersion, runUpgrade } from "./cli-meta.js";
|
|
33
24
|
const program = new Command();
|
|
@@ -52,9 +43,7 @@ function cliConfig() {
|
|
|
52
43
|
baseUrl: o.baseUrl,
|
|
53
44
|
};
|
|
54
45
|
}
|
|
55
|
-
|
|
56
|
-
return new Graft(cliConfig());
|
|
57
|
-
}
|
|
46
|
+
const engineFrom = () => new Graft(cliConfig());
|
|
58
47
|
program
|
|
59
48
|
.command("version")
|
|
60
49
|
.description("Print the installed version and the latest published on npm")
|
|
@@ -90,8 +79,7 @@ program
|
|
|
90
79
|
.sort((a, b) => b[1] - a[1])
|
|
91
80
|
.map(([k, n]) => `${n} ${k}`)
|
|
92
81
|
.join(", ");
|
|
93
|
-
// --deep needs a key; without one, degrade to the $0 structural build
|
|
94
|
-
// rather than failing — the wiring graph is still worth having.
|
|
82
|
+
// --deep needs a key; without one, degrade to the $0 structural build.
|
|
95
83
|
let deep = opts.deep;
|
|
96
84
|
const resolved = resolveConfig(cliConfig());
|
|
97
85
|
if (deep && !resolved.apiKey) {
|
|
@@ -103,8 +91,20 @@ program
|
|
|
103
91
|
if (deep && resolved.usedLegacyEnv) {
|
|
104
92
|
console.error("⚠ using OPENROUTER_API_KEY (deprecated) — prefer GRAFT_API_KEY + GRAFT_BASE_URL.");
|
|
105
93
|
}
|
|
106
|
-
//
|
|
107
|
-
|
|
94
|
+
// Workspace parent: build each child into its OWN graft/ + a workspace index.
|
|
95
|
+
const buildRoot = resolve(dir);
|
|
96
|
+
const buildGlobalDir = program.opts().dir;
|
|
97
|
+
if (isWorkspaceBuildRoot(buildRoot, buildGlobalDir)) {
|
|
98
|
+
await runWorkspaceBuild(buildRoot, {
|
|
99
|
+
deep: !!deep,
|
|
100
|
+
extensions: opts.extensions,
|
|
101
|
+
concurrency,
|
|
102
|
+
childConfig: cliConfig(),
|
|
103
|
+
override: buildGlobalDir,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// --deep: concept nodes first, then the wiring graph links cards up to them.
|
|
108
108
|
if (deep) {
|
|
109
109
|
const c = await engine.init(dir, {
|
|
110
110
|
extensions: opts.extensions,
|
|
@@ -115,7 +115,7 @@ program
|
|
|
115
115
|
for (const e of c.errors)
|
|
116
116
|
console.error(`✗ ${e}`);
|
|
117
117
|
}
|
|
118
|
-
// Wiring graph
|
|
118
|
+
// Wiring graph — always; LLM meaning only with --deep.
|
|
119
119
|
const g = await engine.graph(dir, {
|
|
120
120
|
llm: deep,
|
|
121
121
|
concurrency,
|
|
@@ -141,10 +141,26 @@ program
|
|
|
141
141
|
.option("-n, --limit <n>", "max results", "8")
|
|
142
142
|
.option("--source", "inline the source at each file:line hit (retriever mode — the pack IS the answer, no need to re-open files)")
|
|
143
143
|
.option("--full", "with --source: inline whole definition spans instead of the default ≤8-line crux excerpts")
|
|
144
|
+
.option("--in <path>", "narrow to nodes under this path prefix, filtered before scoring (segment-aware, like scopeOf)")
|
|
144
145
|
.option("--json", "output the result as JSON")
|
|
145
146
|
.action(async (query, dir, opts) => {
|
|
147
|
+
const askGlobalDir = program.opts().dir;
|
|
148
|
+
if (readWorkspace(resolve(dir), askGlobalDir)) {
|
|
149
|
+
runWorkspaceAsk(resolve(dir), askGlobalDir, query, {
|
|
150
|
+
limit: Number(opts.limit), source: opts.source, full: opts.full, in: opts.in, json: opts.json,
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
146
154
|
const engine = engineFrom();
|
|
147
|
-
|
|
155
|
+
let r;
|
|
156
|
+
try {
|
|
157
|
+
r = engine.ask(dir, query, { limit: Number(opts.limit), source: opts.source, full: opts.full, in: opts.in });
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
148
164
|
if (opts.json) {
|
|
149
165
|
console.log(JSON.stringify(r, null, 2));
|
|
150
166
|
}
|
|
@@ -175,14 +191,17 @@ program
|
|
|
175
191
|
.option("-e, --extensions <exts...>", "code extensions to include")
|
|
176
192
|
.option("--json", "output the drift as JSON")
|
|
177
193
|
.action((dir, opts) => {
|
|
194
|
+
const checkGlobalDir = program.opts().dir;
|
|
195
|
+
if (readWorkspace(resolve(dir), checkGlobalDir)) {
|
|
196
|
+
runWorkspaceCheck(resolve(dir), checkGlobalDir);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
178
199
|
const engine = engineFrom();
|
|
179
200
|
const r = engine.check(dir, { extensions: opts.extensions });
|
|
180
201
|
const g = engine.checkGraph(dir); // graph.json is only judged when it exists
|
|
181
|
-
//
|
|
202
|
+
// A layer that IS present must be in sync; a never-built layer (keyless
|
|
203
|
+
// build skips the markdown layer) is informational, not a failure.
|
|
182
204
|
const bothMissing = r.missing && g.missing;
|
|
183
|
-
// A layer that IS present must be in sync; a layer that was never built
|
|
184
|
-
// (keyless `graft build` skips the markdown/concept layer) is informational,
|
|
185
|
-
// not a failure — the wiring graph stands on its own.
|
|
186
205
|
const markdownFail = !r.missing && !r.ok;
|
|
187
206
|
const wiringFail = !g.missing && !g.ok;
|
|
188
207
|
if (opts.json) {
|
|
@@ -224,8 +243,7 @@ program
|
|
|
224
243
|
console.error(`✗ no context graph at ${contextDir} — run \`graft build --deep\` first`);
|
|
225
244
|
process.exit(1);
|
|
226
245
|
}
|
|
227
|
-
|
|
228
|
-
const viewerDir = fileURLToPath(new URL("./viewer/", import.meta.url));
|
|
246
|
+
const viewerDir = fileURLToPath(new URL("./viewer/", import.meta.url)); // prebuilt
|
|
229
247
|
const srv = await startVizServer({
|
|
230
248
|
contextDir,
|
|
231
249
|
viewerDir,
|
|
@@ -240,7 +258,7 @@ program
|
|
|
240
258
|
});
|
|
241
259
|
program
|
|
242
260
|
.command("mcp")
|
|
243
|
-
.description("Serve the graph over MCP (stdio) — exposes graft_ask
|
|
261
|
+
.description("Serve the graph over MCP (stdio) — exposes graft_ask, graft_callers, graft_grep, graft_skeleton, graft_map and graft_check as tools")
|
|
244
262
|
.argument("[dir]", "repository root", ".")
|
|
245
263
|
.action(async (dir) => {
|
|
246
264
|
const { resolve } = await import("node:path");
|
|
@@ -248,38 +266,34 @@ program
|
|
|
248
266
|
const globalOpts = program.opts();
|
|
249
267
|
startMcpServer(resolve(dir), globalOpts.dir);
|
|
250
268
|
});
|
|
251
|
-
function traverseAction(kind) {
|
|
252
|
-
return async (symbol, dir, opts) => {
|
|
253
|
-
const { runTraverseCommand } = await import("./graph/traverse-cli.js");
|
|
254
|
-
const globalOpts = program.opts();
|
|
255
|
-
runTraverseCommand(kind, symbol, dir, { in: opts.in, json: opts.json, depth: opts.depth, globalDir: globalOpts.dir });
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
269
|
program
|
|
259
270
|
.command("callers")
|
|
260
|
-
.description("Who calls/references
|
|
261
|
-
.argument("<symbol>", "bare name, qualified (Class.method), or package-qualified (pkg.Fn)")
|
|
262
|
-
.argument("[dir]", "repository root", ".")
|
|
263
|
-
.option("--in <path>", "narrow matches to nodes whose path contains this substring")
|
|
264
|
-
.option("--json", "output as JSON")
|
|
265
|
-
.action(traverseAction("callers"));
|
|
266
|
-
program
|
|
267
|
-
.command("callees")
|
|
268
|
-
.description("What a symbol calls/references/imports/implements/extends ($0, no LLM)")
|
|
269
|
-
.argument("<symbol>", "bare name, qualified (Class.method), or package-qualified (pkg.Fn)")
|
|
270
|
-
.argument("[dir]", "repository root", ".")
|
|
271
|
-
.option("--in <path>", "narrow matches to nodes whose path contains this substring")
|
|
272
|
-
.option("--json", "output as JSON")
|
|
273
|
-
.action(traverseAction("callees"));
|
|
274
|
-
program
|
|
275
|
-
.command("impact")
|
|
276
|
-
.description("BFS over incoming edges — who breaks if this symbol changes ($0, no LLM)")
|
|
271
|
+
.description("Who calls/references a symbol ($0, no LLM). --direction out gives callees (what it calls); --depth N walks transitively for full blast radius")
|
|
277
272
|
.argument("<symbol>", "bare name, qualified (Class.method), or package-qualified (pkg.Fn)")
|
|
278
273
|
.argument("[dir]", "repository root", ".")
|
|
279
|
-
.option("
|
|
274
|
+
.option("--direction <in|out>", 'edge direction: "in" = callers (default), "out" = callees')
|
|
275
|
+
.option("-d, --depth <n>", "walk transitively up to N hops for blast radius (default 1)")
|
|
280
276
|
.option("--in <path>", "narrow matches to nodes whose path contains this substring")
|
|
281
277
|
.option("--json", "output as JSON")
|
|
282
|
-
.action(
|
|
278
|
+
.action(async (symbol, dir, opts) => {
|
|
279
|
+
const globalOpts = program.opts();
|
|
280
|
+
if (!opts.json && readWorkspace(resolve(dir), globalOpts.dir)) {
|
|
281
|
+
runWorkspaceCallers(resolve(dir), globalOpts.dir, symbol, {
|
|
282
|
+
direction: opts.direction === "out" ? "out" : "in",
|
|
283
|
+
depth: opts.depth ? Number(opts.depth) : undefined,
|
|
284
|
+
in: opts.in,
|
|
285
|
+
});
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const { runCallersCommand } = await import("./graph/traverse-cli.js");
|
|
289
|
+
runCallersCommand(symbol, dir, {
|
|
290
|
+
direction: opts.direction,
|
|
291
|
+
depth: opts.depth,
|
|
292
|
+
in: opts.in,
|
|
293
|
+
json: opts.json,
|
|
294
|
+
globalDir: globalOpts.dir,
|
|
295
|
+
});
|
|
296
|
+
});
|
|
283
297
|
program
|
|
284
298
|
.command("grep")
|
|
285
299
|
.description("Regex search over indexed files, hits grouped by enclosing symbol and ranked by coupling ($0, no LLM)")
|
|
@@ -290,8 +304,14 @@ program
|
|
|
290
304
|
.option("--in <path>", "narrow to files whose path contains this substring")
|
|
291
305
|
.option("--json", "output as JSON")
|
|
292
306
|
.action(async (pattern, dir, opts) => {
|
|
293
|
-
const { runGrepCommand } = await import("./search/grep-cli.js");
|
|
294
307
|
const globalOpts = program.opts();
|
|
308
|
+
if (readWorkspace(resolve(dir), globalOpts.dir)) {
|
|
309
|
+
runWorkspaceGrep(resolve(dir), globalOpts.dir, pattern, {
|
|
310
|
+
ignoreCase: opts.ignoreCase, fixed: opts.fixed, json: opts.json,
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const { runGrepCommand } = await import("./search/grep-cli.js");
|
|
295
315
|
runGrepCommand(pattern, dir, {
|
|
296
316
|
ignoreCase: opts.ignoreCase,
|
|
297
317
|
fixed: opts.fixed,
|
|
@@ -307,17 +327,9 @@ program
|
|
|
307
327
|
.option("--max-dirs <n>", "max directory entries shown, rest counted into dropped (default 16)")
|
|
308
328
|
.option("--json", "output as JSON")
|
|
309
329
|
.action(async (dir, opts) => {
|
|
310
|
-
const { buildRepoMap, formatRepoMap } = await import("./graph/map.js");
|
|
311
330
|
const root = resolve(dir);
|
|
312
331
|
const globalOpts = program.opts();
|
|
313
|
-
|
|
314
|
-
const graph = loadGraphCached(contextDir);
|
|
315
|
-
if (!graph) {
|
|
316
|
-
console.error("✗ no graph — run graft build first");
|
|
317
|
-
process.exit(1);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
let maxDirs;
|
|
332
|
+
let maxDirsW;
|
|
321
333
|
if (opts.maxDirs !== undefined) {
|
|
322
334
|
const n = parseInt(opts.maxDirs, 10);
|
|
323
335
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -325,9 +337,21 @@ program
|
|
|
325
337
|
process.exit(1);
|
|
326
338
|
return;
|
|
327
339
|
}
|
|
328
|
-
|
|
340
|
+
maxDirsW = n;
|
|
341
|
+
}
|
|
342
|
+
if (!opts.json && readWorkspace(root, globalOpts.dir)) {
|
|
343
|
+
runWorkspaceMap(root, globalOpts.dir, { maxDirs: maxDirsW });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const { buildRepoMap, formatRepoMap } = await import("./graph/map.js");
|
|
347
|
+
const contextDir = contextDirFor(root, globalOpts.dir);
|
|
348
|
+
const graph = loadGraphCached(contextDir);
|
|
349
|
+
if (!graph) {
|
|
350
|
+
console.error("✗ no graph — run graft build first");
|
|
351
|
+
process.exit(1);
|
|
352
|
+
return;
|
|
329
353
|
}
|
|
330
|
-
const map = buildRepoMap(graph, { maxDirs });
|
|
354
|
+
const map = buildRepoMap(graph, { maxDirs: maxDirsW });
|
|
331
355
|
if (opts.json) {
|
|
332
356
|
console.log(JSON.stringify(map, null, 2));
|
|
333
357
|
return;
|
|
@@ -336,7 +360,7 @@ program
|
|
|
336
360
|
});
|
|
337
361
|
program
|
|
338
362
|
.command("init")
|
|
339
|
-
.description("Wire Graft into the AI coding agents used with this repo (instruction files; full hooks + statusline for Claude Code)")
|
|
363
|
+
.description("Wire Graft into the AI coding agents used with this repo (instruction files + MCP server; full hooks + statusline + MCP for Claude Code)")
|
|
340
364
|
.argument("[dir]", "target repo directory", ".")
|
|
341
365
|
.option("--no-build", "skip building the graph (wire files only)")
|
|
342
366
|
.option("--agents <ids...>", `only these agents (${hostIds().join(", ")}, claude)`)
|
|
@@ -369,6 +393,12 @@ program
|
|
|
369
393
|
for (const s of res.shims)
|
|
370
394
|
console.error(`✓ wrote ${s}`);
|
|
371
395
|
console.error(`✓ wrote ${res.skill}`);
|
|
396
|
+
if (res.mcp.action === "skipped-unparseable")
|
|
397
|
+
console.error(`⚠ .mcp.json: ${res.mcp.path} left unchanged (not valid JSON) — add the graft server manually`);
|
|
398
|
+
else if (res.mcp.action === "unchanged")
|
|
399
|
+
console.error(`· mcp claude: ${res.mcp.path} (already registered)`);
|
|
400
|
+
else
|
|
401
|
+
console.error(`✓ mcp claude: ${res.mcp.path} (${res.mcp.action}) — restart Claude Code to load the graft MCP server`);
|
|
372
402
|
console.error(res.built ? "✓ built the graph (graft build)" : "· skipped graph build");
|
|
373
403
|
for (const w of res.warnings)
|
|
374
404
|
console.error(`⚠ ${w}`);
|
|
@@ -385,7 +415,6 @@ program
|
|
|
385
415
|
console.error(`✓ ${w.id}: ${w.path} (${w.action})`);
|
|
386
416
|
if (!explicit && !opts.allAgents && r.written.length === 0)
|
|
387
417
|
console.error("· no other agents detected (see --list-agents / --all-agents)");
|
|
388
|
-
// r.unknown is always empty here — ids are validated above, before any writes.
|
|
389
418
|
for (const m of r.mcp)
|
|
390
419
|
console.error(`✓ mcp ${m.id}: ${m.path} (${m.action})`);
|
|
391
420
|
for (const h of r.hooks)
|