@nanonets/graft 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +8 -7
  2. package/dist/ask/ask.d.ts.map +1 -1
  3. package/dist/ask/ask.js +78 -36
  4. package/dist/ask/ask.js.map +1 -1
  5. package/dist/claude/settings-merge.d.ts.map +1 -1
  6. package/dist/claude/settings-merge.js +10 -0
  7. package/dist/claude/settings-merge.js.map +1 -1
  8. package/dist/claude/shim-template.js +1 -1
  9. package/dist/claude/skill-template.d.ts.map +1 -1
  10. package/dist/claude/skill-template.js +32 -14
  11. package/dist/claude/skill-template.js.map +1 -1
  12. package/dist/cli-epilogue.d.ts +18 -0
  13. package/dist/cli-epilogue.d.ts.map +1 -0
  14. package/dist/cli-epilogue.js +61 -0
  15. package/dist/cli-epilogue.js.map +1 -0
  16. package/dist/cli-meta.d.ts +38 -0
  17. package/dist/cli-meta.d.ts.map +1 -0
  18. package/dist/cli-meta.js +127 -0
  19. package/dist/cli-meta.js.map +1 -0
  20. package/dist/cli.d.ts +1 -0
  21. package/dist/cli.d.ts.map +1 -1
  22. package/dist/cli.js +81 -5
  23. package/dist/cli.js.map +1 -1
  24. package/dist/graph/build.d.ts.map +1 -1
  25. package/dist/graph/build.js +5 -0
  26. package/dist/graph/build.js.map +1 -1
  27. package/dist/graph/load.d.ts +18 -0
  28. package/dist/graph/load.d.ts.map +1 -0
  29. package/dist/graph/load.js +83 -0
  30. package/dist/graph/load.js.map +1 -0
  31. package/dist/graph/traverse-cli.d.ts +28 -0
  32. package/dist/graph/traverse-cli.d.ts.map +1 -0
  33. package/dist/graph/traverse-cli.js +112 -0
  34. package/dist/graph/traverse-cli.js.map +1 -0
  35. package/dist/graph/traverse.d.ts +89 -0
  36. package/dist/graph/traverse.d.ts.map +1 -0
  37. package/dist/graph/traverse.js +158 -0
  38. package/dist/graph/traverse.js.map +1 -0
  39. package/dist/graph/write.d.ts.map +1 -1
  40. package/dist/graph/write.js +16 -1
  41. package/dist/graph/write.js.map +1 -1
  42. package/dist/hosts/instructions.d.ts.map +1 -1
  43. package/dist/hosts/instructions.js +14 -4
  44. package/dist/hosts/instructions.js.map +1 -1
  45. package/dist/mcp/tools.d.ts.map +1 -1
  46. package/dist/mcp/tools.js +94 -12
  47. package/dist/mcp/tools.js.map +1 -1
  48. package/dist/viewer/style.css +12 -12
  49. package/package.json +1 -1
@@ -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,EAAE,MAAM,YAAY,CAAC;AAElD,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"}
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"}
@@ -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) ||
@@ -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;QAChE,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,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
+ {"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,CAmBxC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAOnC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC"}
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. The node usually IS the answer; stop there.
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
- Only open source files when a node genuinely lacks a needed detail, and then at
20
- the exact file:line the node points to — never re-read whole files.
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;;;;;;;;;;;;;;;;;iBAiBQ,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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,KAAK,EAAE,OAAO,EA4B1B,CAAC;AAEF,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,CAiCpC"}
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 three MCP tools, as pure functions over the existing engine.
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 { formatBlastRadius } from '../claude/format.js';
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: 'List what depends on a file — who breaks if it changes.',
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: { file: { type: 'string', description: 'repo-relative path, e.g. src/cache.ts' } },
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 file = String(args.file ?? '');
63
- if (!file)
64
- return { text: 'graft_blast_radius requires a file', isError: true };
65
- const w = readGraph(wiringPath(contextDirFor(root)));
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: 'no graph found — run `graft build` first', isError: true };
68
- const br = formatBlastRadius(w, file);
69
- return { text: br ?? `no known dependents of ${file}`, isError: false };
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 };