@nanonets/graft 0.4.2 → 0.4.4
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 +4 -0
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +139 -78
- package/dist/ask/ask.js.map +1 -1
- package/dist/ask/graphrank.d.ts.map +1 -1
- package/dist/ask/graphrank.js +10 -4
- package/dist/ask/graphrank.js.map +1 -1
- package/dist/ask/index-file.d.ts +43 -0
- package/dist/ask/index-file.d.ts.map +1 -0
- package/dist/ask/index-file.js +128 -0
- package/dist/ask/index-file.js.map +1 -0
- package/dist/claude/settings-merge.d.ts.map +1 -1
- package/dist/claude/settings-merge.js +10 -0
- package/dist/claude/settings-merge.js.map +1 -1
- package/dist/claude/shim-template.js +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +32 -14
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +50 -3
- package/dist/cli.js.map +1 -1
- package/dist/graph/build.d.ts.map +1 -1
- package/dist/graph/build.js +18 -0
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/load.d.ts +18 -0
- package/dist/graph/load.d.ts.map +1 -0
- package/dist/graph/load.js +83 -0
- package/dist/graph/load.js.map +1 -0
- package/dist/graph/traverse-cli.d.ts +28 -0
- package/dist/graph/traverse-cli.d.ts.map +1 -0
- package/dist/graph/traverse-cli.js +112 -0
- package/dist/graph/traverse-cli.js.map +1 -0
- package/dist/graph/traverse.d.ts +89 -0
- package/dist/graph/traverse.d.ts.map +1 -0
- package/dist/graph/traverse.js +158 -0
- package/dist/graph/traverse.js.map +1 -0
- package/dist/graph/write.d.ts.map +1 -1
- package/dist/graph/write.js +16 -1
- package/dist/graph/write.js.map +1 -1
- package/dist/hosts/instructions.d.ts.map +1 -1
- package/dist/hosts/instructions.js +14 -4
- package/dist/hosts/instructions.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +94 -12
- package/dist/mcp/tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traverse-cli.d.ts","sourceRoot":"","sources":["../../src/graph/traverse-cli.ts"],"names":[],"mappings":"AAaA,OAAO,EAAiD,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,KAAK,EAAW,MAAM,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD;;;mEAGmE;AACnE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAKhE;AAED;6EAC6E;AAC7E,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrE;AAyCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAkDjH"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI wiring for `graft callers` / `callees` / `impact`.
|
|
3
|
+
*
|
|
4
|
+
* The three commands are one implementation with a direction flag: they all
|
|
5
|
+
* resolve a symbol via `resolveSymbol`, walk edges via `callersOf` /
|
|
6
|
+
* `calleesOf` / `impactOf`, and share the same resolve-or-die flow, human
|
|
7
|
+
* formatter, and --json shape. Kept out of cli.ts so that file stays thin
|
|
8
|
+
* (argument wiring only) and this logic stays unit-testable without shelling
|
|
9
|
+
* out to the CLI on every case.
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { contextDirFor } from "../context/node-file.js";
|
|
13
|
+
import { loadGraphCached } from "./load.js";
|
|
14
|
+
import { resolveSymbol, callersOf, calleesOf, impactOf } from "./traverse.js";
|
|
15
|
+
const ARROW = { callers: "←", impact: "←", callees: "→" };
|
|
16
|
+
const DEFAULT_IMPACT_DEPTH = 2;
|
|
17
|
+
function edgesFor(kind, graph, node, depth) {
|
|
18
|
+
if (kind === "callers")
|
|
19
|
+
return callersOf(graph, node);
|
|
20
|
+
if (kind === "callees")
|
|
21
|
+
return calleesOf(graph, node);
|
|
22
|
+
return impactOf(graph, node, depth);
|
|
23
|
+
}
|
|
24
|
+
/** Exported so the MCP tools (`graft_callers`/`graft_callees`/`graft_blast_radius`
|
|
25
|
+
* in `src/mcp/tools.ts`) can render the same human report format as the CLI,
|
|
26
|
+
* rather than re-implementing it — both surfaces walk the same edges via the
|
|
27
|
+
* same `resolveSymbol`/`callersOf`/`calleesOf`/`impactOf` core. */
|
|
28
|
+
export function headerOf(n) {
|
|
29
|
+
return `${n.name} · ${n.kind} · ${n.path}:${n.span}`;
|
|
30
|
+
}
|
|
31
|
+
export function hitLine(kind, hit) {
|
|
32
|
+
const arrow = ARROW[kind];
|
|
33
|
+
const depthTag = kind === "impact" ? ` [depth ${hit.depth}]` : "";
|
|
34
|
+
const label = hit.node ? `${hit.node.name} (${hit.node.path}:${hit.node.span})` : `${hit.id} (unresolved import)`;
|
|
35
|
+
return ` ${hit.relation} ${arrow} ${label}${depthTag}`;
|
|
36
|
+
}
|
|
37
|
+
/** Loud, actionable empty-result note — never a bare empty list. `callers` and
|
|
38
|
+
* `impact` both walk incoming edges, so they share the "callers" wording. */
|
|
39
|
+
export function looseNoteFor(kind, name) {
|
|
40
|
+
const label = kind === "callees" ? "callees" : "callers";
|
|
41
|
+
const direction = kind === "callees" ? "outgoing" : "incoming";
|
|
42
|
+
return ` no indexed ${label} — the graph has no ${direction} call/reference edges for this symbol; try grep -rn "${name}"`;
|
|
43
|
+
}
|
|
44
|
+
function symbolJson(n) {
|
|
45
|
+
return { id: n.id, name: n.name, kind: n.kind, path: n.path, span: n.span };
|
|
46
|
+
}
|
|
47
|
+
function hitJson(hit) {
|
|
48
|
+
const out = { id: hit.id, relation: hit.relation, depth: hit.depth };
|
|
49
|
+
if (hit.node) {
|
|
50
|
+
out.name = hit.node.name;
|
|
51
|
+
out.kind = hit.node.kind;
|
|
52
|
+
out.path = hit.node.path;
|
|
53
|
+
out.span = hit.node.span;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve `query` in the graph at `dir` (respecting `--dir`/`--in`), walk
|
|
59
|
+
* edges per `kind`, and print either the human report or `--json`. Exits the
|
|
60
|
+
* process (code 1) when there's no graph at all or the symbol is unknown —
|
|
61
|
+
* both are caller-facing mistakes, not recoverable states.
|
|
62
|
+
*/
|
|
63
|
+
export function runTraverseCommand(kind, query, dir, opts) {
|
|
64
|
+
const root = resolve(dir);
|
|
65
|
+
const contextDir = contextDirFor(root, opts.globalDir);
|
|
66
|
+
const graph = loadGraphCached(contextDir);
|
|
67
|
+
if (!graph) {
|
|
68
|
+
console.error(`✗ no graph found at ${contextDir} — run \`graft build\` first`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const matches = resolveSymbol(graph, query, opts.in ? { in: opts.in } : {});
|
|
72
|
+
if (matches.length === 0) {
|
|
73
|
+
console.error(`✗ no symbol "${query}" in the graph — check spelling or run graft build`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
let depth = DEFAULT_IMPACT_DEPTH;
|
|
77
|
+
if (kind === "impact" && opts.depth !== undefined) {
|
|
78
|
+
const d = Number(opts.depth);
|
|
79
|
+
if (!Number.isFinite(d) || d < 1) {
|
|
80
|
+
console.error(`✗ --depth must be a positive number, got "${opts.depth}"`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
depth = d;
|
|
84
|
+
}
|
|
85
|
+
const results = matches.map((symbol) => ({ symbol, hits: edgesFor(kind, graph, symbol, depth) }));
|
|
86
|
+
if (opts.json) {
|
|
87
|
+
const payload = {
|
|
88
|
+
query,
|
|
89
|
+
matches: results.map((r) => {
|
|
90
|
+
const m = { symbol: symbolJson(r.symbol), hits: r.hits.map(hitJson) };
|
|
91
|
+
if (r.hits.length === 0) {
|
|
92
|
+
m.note = looseNoteFor(kind, r.symbol.name);
|
|
93
|
+
}
|
|
94
|
+
return m;
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (const { symbol, hits } of results) {
|
|
102
|
+
lines.push(headerOf(symbol));
|
|
103
|
+
if (hits.length === 0)
|
|
104
|
+
lines.push(looseNoteFor(kind, symbol.name));
|
|
105
|
+
else
|
|
106
|
+
for (const h of hits)
|
|
107
|
+
lines.push(hitLine(kind, h));
|
|
108
|
+
lines.push("");
|
|
109
|
+
}
|
|
110
|
+
process.stdout.write(lines.join("\n").replace(/\n+$/, "\n"));
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=traverse-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traverse-cli.js","sourceRoot":"","sources":["../../src/graph/traverse-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure graph-traversal core shared by `graft callers` / `callees` / `impact`,
|
|
3
|
+
* their MCP equivalents, and `ask`'s structural intent path.
|
|
4
|
+
*
|
|
5
|
+
* Centralizes the symbol-resolution contract (bare name, qualified id-suffix,
|
|
6
|
+
* last-segment fallback, `--in` narrowing) so all three surfaces agree on what
|
|
7
|
+
* a query like `Cache.get` or `hashstructure.Hash` means, and the depth-1
|
|
8
|
+
* (callers/callees) and BFS (impact) edge walks over the wiring graph.
|
|
9
|
+
*
|
|
10
|
+
* No I/O here: callers pass in an already-loaded `GraphV1` (via
|
|
11
|
+
* `loadGraphCached`), which keeps this module trivially unit-testable against
|
|
12
|
+
* hand-built fixture graphs.
|
|
13
|
+
*/
|
|
14
|
+
import type { GraphV1, NodeV1, Relation } from "./types.js";
|
|
15
|
+
/** A resolved symbol-search hit. Reserved for future disambiguation metadata
|
|
16
|
+
* (e.g. why a query matched); today it wraps the node 1:1. */
|
|
17
|
+
export interface SymbolMatch {
|
|
18
|
+
node: NodeV1;
|
|
19
|
+
}
|
|
20
|
+
export interface ResolveSymbolOptions {
|
|
21
|
+
/** Narrow candidates to nodes whose `path` contains this substring. */
|
|
22
|
+
in?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a query string to every matching node in the graph.
|
|
26
|
+
*
|
|
27
|
+
* Matching, in order:
|
|
28
|
+
* 1. Non-file nodes whose `name` equals the query case-insensitively, OR
|
|
29
|
+
* whose `id` ends with `#<query>` or `.<query>` case-insensitively — this
|
|
30
|
+
* is what makes a qualified name like `Cache.get` resolve against an id
|
|
31
|
+
* like `src/cache.ts#Cache.get`.
|
|
32
|
+
* 2. If that yields nothing and the query contains a `.`, retry with just
|
|
33
|
+
* the last dot-segment as a bare name (`hashstructure.Hash` → `Hash`) —
|
|
34
|
+
* covers package-qualified names (e.g. Go) whose id never contains the
|
|
35
|
+
* package prefix.
|
|
36
|
+
* 3. If *that* still yields nothing and the query looks like a filename
|
|
37
|
+
* (contains `.`, no `#`), fall back to `kind: 'file'` nodes matched by
|
|
38
|
+
* name or path.
|
|
39
|
+
*
|
|
40
|
+
* `opts.in` then filters whichever candidate set was produced, by path
|
|
41
|
+
* substring. Multiple matches are not an error — every match is returned.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveSymbol(graph: GraphV1, query: string, opts?: ResolveSymbolOptions): NodeV1[];
|
|
44
|
+
/** One traversed edge. `node` is null when the edge's other endpoint isn't a
|
|
45
|
+
* real node in the graph (e.g. an unresolved import module string) — such
|
|
46
|
+
* hits are kept, labeled by the raw id, rather than dropped. */
|
|
47
|
+
export interface EdgeHit {
|
|
48
|
+
node: NodeV1 | null;
|
|
49
|
+
id: string;
|
|
50
|
+
relation: Relation;
|
|
51
|
+
depth: number;
|
|
52
|
+
}
|
|
53
|
+
/** Depth-1: nodes with a walk-relation edge whose target is `symbol`. */
|
|
54
|
+
export declare function callersOf(graph: GraphV1, symbol: NodeV1): EdgeHit[];
|
|
55
|
+
/** Depth-1: walk-relation edges whose source is `symbol`. */
|
|
56
|
+
export declare function calleesOf(graph: GraphV1, symbol: NodeV1): EdgeHit[];
|
|
57
|
+
/**
|
|
58
|
+
* BFS over INCOMING walk-relation edges from `symbol`, up to `maxDepth` hops
|
|
59
|
+
* — "who breaks if this changes". Each reached node is deduped by id and
|
|
60
|
+
* reported once, at the depth it was first reached (a diamond-shaped
|
|
61
|
+
* dependency graph counts its convergence node exactly once).
|
|
62
|
+
*/
|
|
63
|
+
export declare function impactOf(graph: GraphV1, symbol: NodeV1, maxDepth?: number): EdgeHit[];
|
|
64
|
+
/**
|
|
65
|
+
* BFS over INCOMING walk-relation edges from *multiple* seed nodes at once —
|
|
66
|
+
* the multi-seed generalization of {@link impactOf} (`impactOf(g, n, d)` is
|
|
67
|
+
* exactly `impactOfMany(g, [n], d)`). Used when one logical unit spans several
|
|
68
|
+
* graph node ids — e.g. a file plus every symbol defined in it, since a
|
|
69
|
+
* `calls`/`references`/etc. edge from another file always targets the SYMBOL
|
|
70
|
+
* id, never the FILE id, so walking the file node alone misses dependents that
|
|
71
|
+
* call into it rather than merely importing it.
|
|
72
|
+
*
|
|
73
|
+
* Every seed is pre-marked visited (so a seed can never appear as its own
|
|
74
|
+
* hit, and an edge between two seeds is never reported), then the walk
|
|
75
|
+
* proceeds exactly like `impactOf`'s: each reached node deduped by id and
|
|
76
|
+
* reported once, at the depth it was first reached from *any* seed.
|
|
77
|
+
*/
|
|
78
|
+
export declare function impactOfMany(graph: GraphV1, seeds: NodeV1[], maxDepth?: number): EdgeHit[];
|
|
79
|
+
/**
|
|
80
|
+
* `impactOf`, generalized for a `kind: 'file'` seed: aggregates the BFS over
|
|
81
|
+
* the file node AND every symbol node defined in that file (via
|
|
82
|
+
* {@link impactOfMany} — dedup by id, min depth, seeds excluded). Fixes a file
|
|
83
|
+
* query silently dropping dependents that `calls`/`references`/etc. into a
|
|
84
|
+
* symbol the file defines, rather than merely `imports`-ing the file itself —
|
|
85
|
+
* see this module's header and `impactOfMany`'s doc for why. Symbol-kind
|
|
86
|
+
* queries keep using plain `impactOf`; this is only for file-kind matches.
|
|
87
|
+
*/
|
|
88
|
+
export declare function impactOfFile(graph: GraphV1, fileNode: NodeV1, maxDepth?: number): EdgeHit[];
|
|
89
|
+
//# sourceMappingURL=traverse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traverse.d.ts","sourceRoot":"","sources":["../../src/graph/traverse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAU,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEpE;8DAC8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,MAAM,EAAE,CAyBtG;AAaD;;gEAEgE;AAChE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAQnE;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAQnE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,OAAO,EAAE,CAEhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,SAAI,GAAG,OAAO,EAAE,CA+BrF;AAQD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,OAAO,EAAE,CAEtF"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edges that carry meaning-flow for a walk. `contains` is deliberately
|
|
3
|
+
* excluded — see graphrank.ts's identical WALK_RELATIONS for why (a file
|
|
4
|
+
* "contains" every symbol in it, which would make the file a false hub).
|
|
5
|
+
*/
|
|
6
|
+
const WALK_RELATIONS = new Set([
|
|
7
|
+
"calls",
|
|
8
|
+
"references",
|
|
9
|
+
"imports",
|
|
10
|
+
"implements",
|
|
11
|
+
"extends",
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a query string to every matching node in the graph.
|
|
15
|
+
*
|
|
16
|
+
* Matching, in order:
|
|
17
|
+
* 1. Non-file nodes whose `name` equals the query case-insensitively, OR
|
|
18
|
+
* whose `id` ends with `#<query>` or `.<query>` case-insensitively — this
|
|
19
|
+
* is what makes a qualified name like `Cache.get` resolve against an id
|
|
20
|
+
* like `src/cache.ts#Cache.get`.
|
|
21
|
+
* 2. If that yields nothing and the query contains a `.`, retry with just
|
|
22
|
+
* the last dot-segment as a bare name (`hashstructure.Hash` → `Hash`) —
|
|
23
|
+
* covers package-qualified names (e.g. Go) whose id never contains the
|
|
24
|
+
* package prefix.
|
|
25
|
+
* 3. If *that* still yields nothing and the query looks like a filename
|
|
26
|
+
* (contains `.`, no `#`), fall back to `kind: 'file'` nodes matched by
|
|
27
|
+
* name or path.
|
|
28
|
+
*
|
|
29
|
+
* `opts.in` then filters whichever candidate set was produced, by path
|
|
30
|
+
* substring. Multiple matches are not an error — every match is returned.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveSymbol(graph, query, opts = {}) {
|
|
33
|
+
const lowerQuery = query.toLowerCase();
|
|
34
|
+
const looksLikeFilename = query.includes(".") && !query.includes("#");
|
|
35
|
+
let matches = symbolMatches(graph.nodes, lowerQuery);
|
|
36
|
+
if (matches.length === 0 && query.includes(".")) {
|
|
37
|
+
const lastSegment = query.slice(query.lastIndexOf(".") + 1).toLowerCase();
|
|
38
|
+
if (lastSegment) {
|
|
39
|
+
matches = graph.nodes.filter((n) => n.kind !== "file" && n.name.toLowerCase() === lastSegment);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (matches.length === 0 && looksLikeFilename) {
|
|
43
|
+
matches = graph.nodes.filter((n) => n.kind === "file" &&
|
|
44
|
+
(n.name.toLowerCase() === lowerQuery ||
|
|
45
|
+
n.path.toLowerCase() === lowerQuery ||
|
|
46
|
+
n.path.toLowerCase().endsWith("/" + lowerQuery)));
|
|
47
|
+
}
|
|
48
|
+
if (opts.in)
|
|
49
|
+
matches = matches.filter((n) => n.path.includes(opts.in));
|
|
50
|
+
return matches;
|
|
51
|
+
}
|
|
52
|
+
function symbolMatches(nodes, lowerQuery) {
|
|
53
|
+
const suffixHash = "#" + lowerQuery;
|
|
54
|
+
const suffixDot = "." + lowerQuery;
|
|
55
|
+
return nodes.filter((n) => {
|
|
56
|
+
if (n.kind === "file")
|
|
57
|
+
return false;
|
|
58
|
+
if (n.name.toLowerCase() === lowerQuery)
|
|
59
|
+
return true;
|
|
60
|
+
const lowerId = n.id.toLowerCase();
|
|
61
|
+
return lowerId.endsWith(suffixHash) || lowerId.endsWith(suffixDot);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Depth-1: nodes with a walk-relation edge whose target is `symbol`. */
|
|
65
|
+
export function callersOf(graph, symbol) {
|
|
66
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
67
|
+
const hits = [];
|
|
68
|
+
for (const e of graph.edges) {
|
|
69
|
+
if (!WALK_RELATIONS.has(e.relation) || e.target !== symbol.id)
|
|
70
|
+
continue;
|
|
71
|
+
hits.push({ node: byId.get(e.source) ?? null, id: e.source, relation: e.relation, depth: 1 });
|
|
72
|
+
}
|
|
73
|
+
return hits;
|
|
74
|
+
}
|
|
75
|
+
/** Depth-1: walk-relation edges whose source is `symbol`. */
|
|
76
|
+
export function calleesOf(graph, symbol) {
|
|
77
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
78
|
+
const hits = [];
|
|
79
|
+
for (const e of graph.edges) {
|
|
80
|
+
if (!WALK_RELATIONS.has(e.relation) || e.source !== symbol.id)
|
|
81
|
+
continue;
|
|
82
|
+
hits.push({ node: byId.get(e.target) ?? null, id: e.target, relation: e.relation, depth: 1 });
|
|
83
|
+
}
|
|
84
|
+
return hits;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* BFS over INCOMING walk-relation edges from `symbol`, up to `maxDepth` hops
|
|
88
|
+
* — "who breaks if this changes". Each reached node is deduped by id and
|
|
89
|
+
* reported once, at the depth it was first reached (a diamond-shaped
|
|
90
|
+
* dependency graph counts its convergence node exactly once).
|
|
91
|
+
*/
|
|
92
|
+
export function impactOf(graph, symbol, maxDepth = 2) {
|
|
93
|
+
return impactOfMany(graph, [symbol], maxDepth);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* BFS over INCOMING walk-relation edges from *multiple* seed nodes at once —
|
|
97
|
+
* the multi-seed generalization of {@link impactOf} (`impactOf(g, n, d)` is
|
|
98
|
+
* exactly `impactOfMany(g, [n], d)`). Used when one logical unit spans several
|
|
99
|
+
* graph node ids — e.g. a file plus every symbol defined in it, since a
|
|
100
|
+
* `calls`/`references`/etc. edge from another file always targets the SYMBOL
|
|
101
|
+
* id, never the FILE id, so walking the file node alone misses dependents that
|
|
102
|
+
* call into it rather than merely importing it.
|
|
103
|
+
*
|
|
104
|
+
* Every seed is pre-marked visited (so a seed can never appear as its own
|
|
105
|
+
* hit, and an edge between two seeds is never reported), then the walk
|
|
106
|
+
* proceeds exactly like `impactOf`'s: each reached node deduped by id and
|
|
107
|
+
* reported once, at the depth it was first reached from *any* seed.
|
|
108
|
+
*/
|
|
109
|
+
export function impactOfMany(graph, seeds, maxDepth = 2) {
|
|
110
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
111
|
+
// target id → incoming {source, relation} pairs, restricted to walk relations.
|
|
112
|
+
const incoming = new Map();
|
|
113
|
+
for (const e of graph.edges) {
|
|
114
|
+
if (!WALK_RELATIONS.has(e.relation))
|
|
115
|
+
continue;
|
|
116
|
+
const arr = incoming.get(e.target);
|
|
117
|
+
const entry = { source: e.source, relation: e.relation };
|
|
118
|
+
if (arr)
|
|
119
|
+
arr.push(entry);
|
|
120
|
+
else
|
|
121
|
+
incoming.set(e.target, [entry]);
|
|
122
|
+
}
|
|
123
|
+
const visited = new Set(seeds.map((s) => s.id));
|
|
124
|
+
const hits = [];
|
|
125
|
+
let frontier = [...visited];
|
|
126
|
+
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
127
|
+
const next = [];
|
|
128
|
+
for (const current of frontier) {
|
|
129
|
+
for (const { source, relation } of incoming.get(current) ?? []) {
|
|
130
|
+
if (visited.has(source))
|
|
131
|
+
continue;
|
|
132
|
+
visited.add(source);
|
|
133
|
+
hits.push({ node: byId.get(source) ?? null, id: source, relation, depth });
|
|
134
|
+
next.push(source);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
frontier = next;
|
|
138
|
+
}
|
|
139
|
+
return hits;
|
|
140
|
+
}
|
|
141
|
+
/** Every non-file node whose `path` equals `fileNode.path` — the symbols
|
|
142
|
+
* defined in that file. */
|
|
143
|
+
function symbolsInFile(graph, fileNode) {
|
|
144
|
+
return graph.nodes.filter((n) => n.kind !== "file" && n.path === fileNode.path);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* `impactOf`, generalized for a `kind: 'file'` seed: aggregates the BFS over
|
|
148
|
+
* the file node AND every symbol node defined in that file (via
|
|
149
|
+
* {@link impactOfMany} — dedup by id, min depth, seeds excluded). Fixes a file
|
|
150
|
+
* query silently dropping dependents that `calls`/`references`/etc. into a
|
|
151
|
+
* symbol the file defines, rather than merely `imports`-ing the file itself —
|
|
152
|
+
* see this module's header and `impactOfMany`'s doc for why. Symbol-kind
|
|
153
|
+
* queries keep using plain `impactOf`; this is only for file-kind matches.
|
|
154
|
+
*/
|
|
155
|
+
export function impactOfFile(graph, fileNode, maxDepth = 2) {
|
|
156
|
+
return impactOfMany(graph, [fileNode, ...symbolsInFile(graph, fileNode)], maxDepth);
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=traverse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traverse.js","sourceRoot":"","sources":["../../src/graph/traverse.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/graph/write.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAU,OAAO,
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/graph/write.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAU,OAAO,EAAU,MAAM,YAAY,CAAC;AAE1D,mFAAmF;AACnF,eAAO,MAAM,SAAS,WAAW,CAAC;AAClC,eAAO,MAAM,UAAU,gBAAgB,CAAC;AAExC,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAMtD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAUjE"}
|
package/dist/graph/write.js
CHANGED
|
@@ -32,7 +32,7 @@ export function readGraph(path) {
|
|
|
32
32
|
export function writeGraph(graph, outDir) {
|
|
33
33
|
const sorted = {
|
|
34
34
|
...graph,
|
|
35
|
-
nodes: [...graph.nodes].sort((a, b) => a.id.localeCompare(b.id)),
|
|
35
|
+
nodes: [...graph.nodes].sort((a, b) => a.id.localeCompare(b.id)).map(stripBodyText),
|
|
36
36
|
edges: [...graph.edges].sort(edgeOrder),
|
|
37
37
|
};
|
|
38
38
|
const path = wiringPath(outDir);
|
|
@@ -40,6 +40,21 @@ export function writeGraph(graph, outDir) {
|
|
|
40
40
|
writeFileSync(path, JSON.stringify(sorted, null, 2) + "\n");
|
|
41
41
|
return path;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Drop `body_text` from the SERIALIZED copy of a node — it is ~65% of
|
|
45
|
+
* wiring.json's bytes on a large graph, and every byte of it is already
|
|
46
|
+
* duplicated in the `ask` sidecar (`.cache/ask-index.json`), tokenized, which
|
|
47
|
+
* is the only place anything reads it from. Callers must pass this the
|
|
48
|
+
* in-memory graph BEFORE this stripped copy is produced (see `build.ts`:
|
|
49
|
+
* `writeAskIndex` runs on the original `graph` object, never on a re-read of
|
|
50
|
+
* this slimmed file) — this function never mutates the input node.
|
|
51
|
+
*/
|
|
52
|
+
function stripBodyText(node) {
|
|
53
|
+
if (node.body_text === undefined)
|
|
54
|
+
return node;
|
|
55
|
+
const { body_text: _body_text, ...rest } = node;
|
|
56
|
+
return rest;
|
|
57
|
+
}
|
|
43
58
|
function edgeOrder(a, b) {
|
|
44
59
|
return (a.source.localeCompare(b.source) ||
|
|
45
60
|
a.relation.localeCompare(b.relation) ||
|
package/dist/graph/write.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/graph/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,mFAAmF;AACnF,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC;AAClC,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AAExC,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc,EAAE,MAAc;IACvD,MAAM,MAAM,GAAY;QACtB,GAAG,KAAK;QACR,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/graph/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C,mFAAmF;AACnF,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC;AAClC,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AAExC,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc,EAAE,MAAc;IACvD,MAAM,MAAM,GAAY;QACtB,GAAG,KAAK;QACR,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC;QACnF,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;KACxC,CAAC;IACF,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAChD,OAAO,IAAc,CAAC;AACxB,CAAC;AAED,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS;IACrC,OAAO,CACL,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;QAChC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC;QACpC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CACjC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/hosts/instructions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,eAAe,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/hosts/instructions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,eAAe,IAAI,MAAM,CA6BxC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAOnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC"}
|
|
@@ -10,14 +10,24 @@ system and carry exact file:line spans, kept in sync with the code through git.
|
|
|
10
10
|
|
|
11
11
|
For ANY task here — understanding how something works, finding where code lives,
|
|
12
12
|
or scoping a change — get context from the graph before grepping or opening
|
|
13
|
-
source files
|
|
13
|
+
source files. Re-ask freely (it's cheap) and reuse literal identifiers you
|
|
14
|
+
already have (symbol, error string, file name) as the query.
|
|
14
15
|
|
|
15
16
|
- Run \`graft ask "<your question>" --source\` → ranked nodes with the relevant
|
|
16
|
-
code spans inlined.
|
|
17
|
+
code spans inlined. Match the tool to the task shape: for understanding or
|
|
18
|
+
editing, the top node IS the answer — cite its \`covers:\` file:line spans and
|
|
19
|
+
edit straight from \`--source\`. For exhaustive tasks ("every occurrence /
|
|
20
|
+
every caller of this pattern"), ranked results are top-N, not complete — use
|
|
21
|
+
graft to orient, then enumerate with grep over the source and verify each hit.
|
|
22
|
+
- \`graft callers/callees <symbol>\` and \`graft impact <symbol> [-d N]\` give
|
|
23
|
+
precomputed, exact edges — who calls/is called by a symbol, and the full
|
|
24
|
+
blast radius — for structural questions, skip ranking and use these directly.
|
|
17
25
|
- Or browse: \`graft/INDEX.md\` lists every node; follow the links.
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
If a returned span is truncated ("+N more lines"), open the file at that exact
|
|
28
|
+
range before finalizing. Only open source files when a node genuinely lacks a
|
|
29
|
+
needed detail, and then at the exact file:line the node points to — never
|
|
30
|
+
re-read whole files.
|
|
21
31
|
|
|
22
32
|
After big code changes, refresh the graph with \`graft build\` (deterministic,
|
|
23
33
|
no API key, $0).`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../src/hosts/instructions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,eAAe;IAC7B,OAAO
|
|
1
|
+
{"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../src/hosts/instructions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO;;;;EAIP,eAAe,EAAE;CAClB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO;;;EAGP,eAAe,EAAE;CAClB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,GAAG,eAAe,EAAE;CAC5B,CAAC;AACF,CAAC"}
|
package/dist/mcp/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAWD,eAAO,MAAM,KAAK,EAAE,OAAO,EAyD1B,CAAC;AAiBF,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CA8DpC"}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The MCP tools, as pure functions over the existing engine.
|
|
3
3
|
* `callTool` never throws — hosts get soft errors as isError content.
|
|
4
4
|
*/
|
|
5
5
|
import { Graft } from '../engine.js';
|
|
6
6
|
import { formatAsk } from '../ask/ask.js';
|
|
7
7
|
import { formatCheckReport } from '../context/check.js';
|
|
8
8
|
import { formatGraphCheckReport } from '../graph/check.js';
|
|
9
|
-
import {
|
|
10
|
-
import { readGraph, wiringPath } from '../graph/write.js';
|
|
9
|
+
import { loadGraphCached } from '../graph/load.js';
|
|
11
10
|
import { contextDirFor } from '../context/node-file.js';
|
|
11
|
+
import { resolveSymbol, callersOf, calleesOf, impactOf, impactOfFile } from '../graph/traverse.js';
|
|
12
|
+
import { headerOf, hitLine, looseNoteFor } from '../graph/traverse-cli.js';
|
|
13
|
+
/** Default BFS depth for `graft_blast_radius`, same default as `graft impact`. */
|
|
14
|
+
const DEFAULT_BLAST_DEPTH = 2;
|
|
15
|
+
const NO_GRAPH = 'no graph found — run `graft build` first';
|
|
16
|
+
function unknownSymbolText(query) {
|
|
17
|
+
return `no symbol "${query}" in the graph — check spelling or run \`graft build\``;
|
|
18
|
+
}
|
|
12
19
|
export const TOOLS = [
|
|
13
20
|
{
|
|
14
21
|
name: 'graft_ask',
|
|
@@ -29,14 +36,59 @@ export const TOOLS = [
|
|
|
29
36
|
},
|
|
30
37
|
{
|
|
31
38
|
name: 'graft_blast_radius',
|
|
32
|
-
description: '
|
|
39
|
+
description: 'BFS over incoming call/reference/import/implements/extends edges from a file or symbol — who breaks if it changes ($0, no LLM).',
|
|
33
40
|
inputSchema: {
|
|
34
41
|
type: 'object',
|
|
35
|
-
properties: {
|
|
42
|
+
properties: {
|
|
43
|
+
file: { type: 'string', description: 'repo-relative file path, or a symbol name (bare, qualified, or package-qualified)' },
|
|
44
|
+
symbol: { type: 'string', description: 'alternative to `file` — a symbol name (bare, qualified, or package-qualified)' },
|
|
45
|
+
depth: { type: 'number', description: 'max BFS depth (default 2)' },
|
|
46
|
+
},
|
|
36
47
|
required: ['file'],
|
|
37
48
|
},
|
|
38
49
|
},
|
|
50
|
+
{
|
|
51
|
+
name: 'graft_callers',
|
|
52
|
+
description: 'Who calls/references/imports/implements/extends a symbol (depth 1, $0, no LLM).',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
symbol: { type: 'string', description: 'bare name, qualified (Class.method), or package-qualified (pkg.Fn)' },
|
|
57
|
+
in: { type: 'string', description: 'narrow matches to nodes whose path contains this substring' },
|
|
58
|
+
},
|
|
59
|
+
required: ['symbol'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'graft_callees',
|
|
64
|
+
description: 'What a symbol calls/references/imports/implements/extends (depth 1, $0, no LLM).',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
symbol: { type: 'string', description: 'bare name, qualified (Class.method), or package-qualified (pkg.Fn)' },
|
|
69
|
+
in: { type: 'string', description: 'narrow matches to nodes whose path contains this substring' },
|
|
70
|
+
},
|
|
71
|
+
required: ['symbol'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
39
74
|
];
|
|
75
|
+
/** Render every resolved match's header + edge report (or the loud zero-edge
|
|
76
|
+
* note), one block per match, joined with a blank line — the same grouping
|
|
77
|
+
* `graft callers`/`callees`/`impact` use for multi-match symbols. */
|
|
78
|
+
function renderMatches(kind, matches, hitsFor) {
|
|
79
|
+
return matches
|
|
80
|
+
.map((m) => {
|
|
81
|
+
const hits = hitsFor(m);
|
|
82
|
+
const lines = [headerOf(m)];
|
|
83
|
+
if (hits.length === 0)
|
|
84
|
+
lines.push(looseNoteFor(kind, m.name));
|
|
85
|
+
else
|
|
86
|
+
for (const h of hits)
|
|
87
|
+
lines.push(hitLine(kind, h));
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
})
|
|
90
|
+
.join('\n\n');
|
|
91
|
+
}
|
|
40
92
|
export function callTool(root, name, args) {
|
|
41
93
|
try {
|
|
42
94
|
switch (name) {
|
|
@@ -59,14 +111,44 @@ export function callTool(root, name, args) {
|
|
|
59
111
|
return { text: parts.join('\n\n'), isError: false };
|
|
60
112
|
}
|
|
61
113
|
case 'graft_blast_radius': {
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
return { text: 'graft_blast_radius requires a file', isError: true };
|
|
65
|
-
const w =
|
|
114
|
+
const query = String(args.symbol ?? args.file ?? '');
|
|
115
|
+
if (!query)
|
|
116
|
+
return { text: 'graft_blast_radius requires a file or symbol', isError: true };
|
|
117
|
+
const w = loadGraphCached(contextDirFor(root));
|
|
118
|
+
if (!w)
|
|
119
|
+
return { text: NO_GRAPH, isError: true };
|
|
120
|
+
const matches = resolveSymbol(w, query);
|
|
121
|
+
if (matches.length === 0)
|
|
122
|
+
return { text: unknownSymbolText(query), isError: true };
|
|
123
|
+
const depth = typeof args.depth === 'number' && Number.isFinite(args.depth) && args.depth >= 1
|
|
124
|
+
? args.depth
|
|
125
|
+
: DEFAULT_BLAST_DEPTH;
|
|
126
|
+
// impactOf's BFS is over incoming edges — same wording family as "callers".
|
|
127
|
+
// A file-kind match must aggregate impact over the file node AND every
|
|
128
|
+
// symbol it defines: a `calls`/`references`/etc. edge into a function
|
|
129
|
+
// defined in the file targets the SYMBOL id, never the FILE id, so
|
|
130
|
+
// walking the file node alone silently drops dependents that call
|
|
131
|
+
// into it rather than merely importing it. Symbol-kind matches keep
|
|
132
|
+
// the plain single-seed walk.
|
|
133
|
+
const text = renderMatches('impact', matches, (m) => m.kind === 'file' ? impactOfFile(w, m, depth) : impactOf(w, m, depth));
|
|
134
|
+
return { text, isError: false };
|
|
135
|
+
}
|
|
136
|
+
case 'graft_callers':
|
|
137
|
+
case 'graft_callees': {
|
|
138
|
+
const symbol = String(args.symbol ?? '');
|
|
139
|
+
if (!symbol)
|
|
140
|
+
return { text: `${name} requires a symbol`, isError: true };
|
|
141
|
+
const w = loadGraphCached(contextDirFor(root));
|
|
66
142
|
if (!w)
|
|
67
|
-
return { text:
|
|
68
|
-
const
|
|
69
|
-
|
|
143
|
+
return { text: NO_GRAPH, isError: true };
|
|
144
|
+
const inOpt = typeof args.in === 'string' && args.in ? { in: args.in } : {};
|
|
145
|
+
const matches = resolveSymbol(w, symbol, inOpt);
|
|
146
|
+
if (matches.length === 0)
|
|
147
|
+
return { text: unknownSymbolText(symbol), isError: true };
|
|
148
|
+
const kind = name === 'graft_callers' ? 'callers' : 'callees';
|
|
149
|
+
const walk = kind === 'callers' ? callersOf : calleesOf;
|
|
150
|
+
const text = renderMatches(kind, matches, (m) => walk(w, m));
|
|
151
|
+
return { text, isError: false };
|
|
70
152
|
}
|
|
71
153
|
default:
|
|
72
154
|
return { text: `unknown tool: ${name}`, isError: true };
|