@ttsc/graph 0.16.5 → 0.16.7
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 +22 -0
- package/lib/TtscGraphApplication.js +58 -22
- package/lib/TtscGraphApplication.js.map +1 -1
- package/lib/model/TtscGraphMemory.js +43 -4
- package/lib/model/TtscGraphMemory.js.map +1 -1
- package/lib/model/loadGraph.js +17 -17
- package/lib/server/accessAliases.js +53 -0
- package/lib/server/accessAliases.js.map +1 -0
- package/lib/server/createServer.js +1490 -657
- package/lib/server/createServer.js.map +1 -1
- package/lib/server/instructions.js +81 -21
- package/lib/server/instructions.js.map +1 -1
- package/lib/server/pathPolicy.js +35 -0
- package/lib/server/pathPolicy.js.map +1 -0
- package/lib/server/resultGuide.js +16 -0
- package/lib/server/resultGuide.js.map +1 -0
- package/lib/server/runDetails.js +474 -0
- package/lib/server/runDetails.js.map +1 -0
- package/lib/server/{runIndex.js → runEntrypoints.js} +92 -28
- package/lib/server/runEntrypoints.js.map +1 -0
- package/lib/server/runLookup.js +246 -0
- package/lib/server/runLookup.js.map +1 -0
- package/lib/server/runOverview.js +44 -27
- package/lib/server/runOverview.js.map +1 -1
- package/lib/server/runTour.js +737 -0
- package/lib/server/runTour.js.map +1 -0
- package/lib/server/runTrace.js +224 -43
- package/lib/server/runTrace.js.map +1 -1
- package/lib/structures/ITtscGraphDetails.js +3 -0
- package/lib/structures/ITtscGraphDetails.js.map +1 -0
- package/lib/structures/ITtscGraphEntrypoints.js +3 -0
- package/lib/structures/ITtscGraphEntrypoints.js.map +1 -0
- package/lib/structures/{ITtscGraphExpand.js → ITtscGraphEscape.js} +1 -1
- package/lib/structures/ITtscGraphEscape.js.map +1 -0
- package/lib/structures/ITtscGraphLookup.js +3 -0
- package/lib/structures/ITtscGraphLookup.js.map +1 -0
- package/lib/structures/{ITtscGraphIndex.js → ITtscGraphNext.js} +1 -1
- package/lib/structures/ITtscGraphNext.js.map +1 -0
- package/lib/structures/{ITtscGraphQuery.js → ITtscGraphTour.js} +1 -1
- package/lib/structures/ITtscGraphTour.js.map +1 -0
- package/lib/structures/index.js +6 -3
- package/lib/structures/index.js.map +1 -1
- package/package.json +2 -2
- package/src/TtscGraphApplication.ts +70 -30
- package/src/model/TtscGraphMemory.ts +46 -4
- package/src/server/accessAliases.ts +55 -0
- package/src/server/createServer.ts +4 -7
- package/src/server/instructions.ts +81 -21
- package/src/server/pathPolicy.ts +43 -0
- package/src/server/resultGuide.ts +20 -0
- package/src/server/runDetails.ts +553 -0
- package/src/server/{runIndex.ts → runEntrypoints.ts} +116 -36
- package/src/server/runLookup.ts +284 -0
- package/src/server/runOverview.ts +59 -31
- package/src/server/runTour.ts +881 -0
- package/src/server/runTrace.ts +299 -45
- package/src/structures/ITtscGraphApplication.ts +75 -56
- package/src/structures/ITtscGraphDecorator.ts +12 -14
- package/src/structures/ITtscGraphDetails.ts +197 -0
- package/src/structures/ITtscGraphEntrypoints.ts +149 -0
- package/src/structures/ITtscGraphEscape.ts +49 -0
- package/src/structures/ITtscGraphEvidence.ts +5 -8
- package/src/structures/ITtscGraphLookup.ts +82 -0
- package/src/structures/ITtscGraphNext.ts +23 -0
- package/src/structures/ITtscGraphNode.ts +10 -4
- package/src/structures/ITtscGraphOverview.ts +41 -18
- package/src/structures/ITtscGraphTour.ts +150 -0
- package/src/structures/ITtscGraphTrace.ts +93 -13
- package/src/structures/TtscGraphNodeKind.ts +2 -2
- package/src/structures/index.ts +6 -3
- package/lib/server/runExpand.js +0 -177
- package/lib/server/runExpand.js.map +0 -1
- package/lib/server/runIndex.js.map +0 -1
- package/lib/server/runQuery.js +0 -147
- package/lib/server/runQuery.js.map +0 -1
- package/lib/structures/ITtscGraphExpand.js.map +0 -1
- package/lib/structures/ITtscGraphIndex.js.map +0 -1
- package/lib/structures/ITtscGraphQuery.js.map +0 -1
- package/src/server/runExpand.ts +0 -186
- package/src/server/runQuery.ts +0 -150
- package/src/structures/ITtscGraphExpand.ts +0 -85
- package/src/structures/ITtscGraphIndex.ts +0 -100
- package/src/structures/ITtscGraphQuery.ts +0 -49
package/src/server/runTrace.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { TtscGraphMemory } from "../model/TtscGraphMemory";
|
|
2
|
+
import { ITtscGraphEdge } from "../structures/ITtscGraphEdge";
|
|
3
|
+
import { ITtscGraphEvidence } from "../structures/ITtscGraphEvidence";
|
|
2
4
|
import { ITtscGraphNode } from "../structures/ITtscGraphNode";
|
|
3
5
|
import { ITtscGraphTrace } from "../structures/ITtscGraphTrace";
|
|
6
|
+
import { accessAliasesFor } from "./accessAliases";
|
|
7
|
+
import { isExternalNode, isTestPath } from "./pathPolicy";
|
|
4
8
|
import { resolveGraphHandle } from "./resolveHandle";
|
|
5
|
-
import {
|
|
9
|
+
import { resultGuide, resultNext } from "./resultGuide";
|
|
10
|
+
import { edgeEvidenceOf, edgeEvidenceTextOf, signatureOf } from "./runDetails";
|
|
6
11
|
|
|
7
|
-
const DEFAULT_DEPTH =
|
|
8
|
-
const DEFAULT_MAX_NODES =
|
|
12
|
+
const DEFAULT_DEPTH = 2;
|
|
13
|
+
const DEFAULT_MAX_NODES = 6;
|
|
14
|
+
const MAX_OPEN_DEPTH = 2;
|
|
15
|
+
const MAX_OPEN_NODES = 8;
|
|
16
|
+
const MAX_IMPACT_DEPTH = 4;
|
|
17
|
+
const MAX_IMPACT_NODES = 16;
|
|
18
|
+
const MAX_HOPS_PER_NODE = 2;
|
|
19
|
+
const MAX_STEPS = 6;
|
|
9
20
|
|
|
10
21
|
/**
|
|
11
22
|
* Breadth-first trace along the dependency graph. Structural
|
|
@@ -16,12 +27,26 @@ const DEFAULT_MAX_NODES = 60;
|
|
|
16
27
|
*/
|
|
17
28
|
export function runTrace(
|
|
18
29
|
graph: TtscGraphMemory,
|
|
19
|
-
props: ITtscGraphTrace.
|
|
30
|
+
props: ITtscGraphTrace.IRequest,
|
|
20
31
|
): ITtscGraphTrace {
|
|
21
32
|
const direction = props.direction ?? "forward";
|
|
22
|
-
const
|
|
23
|
-
const
|
|
33
|
+
const focus = props.focus ?? "all";
|
|
34
|
+
const impact = direction === "impact";
|
|
35
|
+
const maxDepth = bound(
|
|
36
|
+
props.maxDepth,
|
|
37
|
+
DEFAULT_DEPTH,
|
|
38
|
+
1,
|
|
39
|
+
impact ? MAX_IMPACT_DEPTH : MAX_OPEN_DEPTH,
|
|
40
|
+
);
|
|
41
|
+
const maxNodes = bound(
|
|
42
|
+
props.maxNodes,
|
|
43
|
+
DEFAULT_MAX_NODES,
|
|
44
|
+
1,
|
|
45
|
+
impact ? MAX_IMPACT_NODES : MAX_OPEN_NODES,
|
|
46
|
+
);
|
|
47
|
+
const maxHops = maxNodes * MAX_HOPS_PER_NODE;
|
|
24
48
|
const reverse = direction === "reverse" || direction === "impact";
|
|
49
|
+
const includeExternal = props.includeExternal === true;
|
|
25
50
|
// Only an impact trace tags reached nodes with their public-surface role; for
|
|
26
51
|
// forward/reverse the role is noise.
|
|
27
52
|
const withRoles = direction === "impact";
|
|
@@ -29,49 +54,83 @@ export function runTrace(
|
|
|
29
54
|
const start = resolveGraphHandle(graph, props.from);
|
|
30
55
|
if (start.candidates) {
|
|
31
56
|
return {
|
|
57
|
+
type: "trace",
|
|
32
58
|
direction,
|
|
33
59
|
hops: [],
|
|
34
60
|
reached: [],
|
|
35
61
|
truncated: false,
|
|
36
|
-
|
|
62
|
+
next: resultNext(
|
|
63
|
+
"clarify",
|
|
64
|
+
"The start handle is ambiguous; choose one returned candidate.",
|
|
65
|
+
),
|
|
66
|
+
guide: resultGuide(
|
|
67
|
+
"Disambiguate with the returned candidates, or ask the user for the intended symbol.",
|
|
68
|
+
),
|
|
69
|
+
candidates: start.candidates.map((n) => summary(graph, n)),
|
|
37
70
|
};
|
|
38
71
|
}
|
|
39
72
|
if (start.node === undefined) {
|
|
40
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
type: "trace",
|
|
75
|
+
direction,
|
|
76
|
+
hops: [],
|
|
77
|
+
reached: [],
|
|
78
|
+
truncated: false,
|
|
79
|
+
next: resultNext(
|
|
80
|
+
"clarify",
|
|
81
|
+
"The start handle did not resolve in the compiler graph.",
|
|
82
|
+
),
|
|
83
|
+
guide: resultGuide(
|
|
84
|
+
"The start symbol was not resolved; answer that the graph has no trace from this handle.",
|
|
85
|
+
),
|
|
86
|
+
};
|
|
41
87
|
}
|
|
42
88
|
|
|
43
|
-
// Path mode: with `to`, return the dependency path from `from` to `to
|
|
44
|
-
// one-call answer for "how does A reach B"
|
|
89
|
+
// Path mode: with `to`, return the dependency path from `from` to `to`, the
|
|
90
|
+
// one-call answer for "how does A reach B", instead of an open-ended trace.
|
|
45
91
|
if (props.to !== undefined && props.to !== "") {
|
|
46
|
-
const base = {
|
|
92
|
+
const base = {
|
|
93
|
+
type: "trace" as const,
|
|
94
|
+
direction: "path",
|
|
95
|
+
hops: [],
|
|
96
|
+
reached: [],
|
|
97
|
+
truncated: false,
|
|
98
|
+
next: resultNext(
|
|
99
|
+
"answer",
|
|
100
|
+
"The path result is the structural flow answer; cite path nodes and evidence ranges.",
|
|
101
|
+
),
|
|
102
|
+
guide: resultGuide(
|
|
103
|
+
"Use the returned path, hops, and evidence ranges as the flow answer.",
|
|
104
|
+
),
|
|
105
|
+
};
|
|
47
106
|
const target = resolveGraphHandle(graph, props.to);
|
|
48
107
|
if (target.candidates) {
|
|
49
108
|
return {
|
|
50
109
|
...base,
|
|
51
|
-
start: summary(start.node),
|
|
52
|
-
candidates: target.candidates.map((n) => summary(n)),
|
|
110
|
+
start: summary(graph, start.node),
|
|
111
|
+
candidates: target.candidates.map((n) => summary(graph, n)),
|
|
53
112
|
};
|
|
54
113
|
}
|
|
55
114
|
if (target.node === undefined) {
|
|
56
|
-
return { ...base, start: summary(start.node) };
|
|
115
|
+
return { ...base, start: summary(graph, start.node) };
|
|
57
116
|
}
|
|
58
117
|
const found = findPath(
|
|
59
118
|
graph,
|
|
60
119
|
start.node.id,
|
|
61
120
|
target.node.id,
|
|
62
|
-
|
|
121
|
+
bound(props.maxDepth, 12, 1, 12),
|
|
122
|
+
focus,
|
|
123
|
+
includeExternal,
|
|
63
124
|
);
|
|
125
|
+
const path = found?.path ?? [];
|
|
126
|
+
const hops = found?.hops ?? [];
|
|
64
127
|
return {
|
|
65
128
|
...base,
|
|
66
|
-
start: summary(start.node),
|
|
67
|
-
target: summary(target.node),
|
|
68
|
-
hops
|
|
69
|
-
path:
|
|
70
|
-
|
|
71
|
-
const sig = signatureOf(graph.project, node);
|
|
72
|
-
if (sig !== undefined) node_.signature = sig;
|
|
73
|
-
return node_;
|
|
74
|
-
}),
|
|
129
|
+
start: summary(graph, start.node),
|
|
130
|
+
target: summary(graph, target.node),
|
|
131
|
+
hops,
|
|
132
|
+
path: path.map((node, i) => summary(graph, node, i, false, true)),
|
|
133
|
+
steps: traceSteps(graph, hops),
|
|
75
134
|
};
|
|
76
135
|
}
|
|
77
136
|
|
|
@@ -90,32 +149,50 @@ export function runTrace(
|
|
|
90
149
|
truncated = true;
|
|
91
150
|
continue;
|
|
92
151
|
}
|
|
93
|
-
const edges =
|
|
152
|
+
const edges = orderedEdges(
|
|
153
|
+
graph,
|
|
154
|
+
reverse ? graph.incoming(id) : graph.outgoing(id),
|
|
155
|
+
direction,
|
|
156
|
+
reverse,
|
|
157
|
+
);
|
|
94
158
|
for (const edge of edges) {
|
|
95
|
-
if (!traversable(edge.kind)) continue;
|
|
159
|
+
if (!traversable(edge.kind, focus)) continue;
|
|
96
160
|
const otherId = reverse ? edge.from : edge.to;
|
|
97
161
|
const other = graph.node(otherId);
|
|
98
162
|
if (other === undefined || other.kind === "file") continue;
|
|
99
|
-
|
|
163
|
+
if (!includeExternal && isExternalNode(other)) continue;
|
|
164
|
+
const hop: ITtscGraphTrace.IHop = {
|
|
100
165
|
from: edge.from,
|
|
101
166
|
to: edge.to,
|
|
102
167
|
kind: edge.kind,
|
|
103
168
|
depth: depth + 1,
|
|
104
169
|
};
|
|
170
|
+
const evidence = edgeEvidenceOf(edge);
|
|
171
|
+
if (evidence !== undefined) hop.evidence = evidence;
|
|
172
|
+
const aliases = accessAliasesFor(
|
|
173
|
+
graph.node(edge.to),
|
|
174
|
+
edgeEvidenceTextOf(edge),
|
|
175
|
+
);
|
|
176
|
+
if (aliases !== undefined) hop.aliases = aliases;
|
|
105
177
|
// A back-edge to the start or an already-reached node: record the hop;
|
|
106
178
|
// its endpoints are already represented.
|
|
107
179
|
if (visited.has(otherId)) {
|
|
108
|
-
hops.
|
|
180
|
+
if (hops.length >= maxHops) truncated = true;
|
|
181
|
+
else hops.push(hop);
|
|
109
182
|
continue;
|
|
110
183
|
}
|
|
111
|
-
// A new node past the cap is left unrepresented, so drop its hop too
|
|
184
|
+
// A new node past the cap is left unrepresented, so drop its hop too:
|
|
112
185
|
// every hop's endpoints stay resolvable in `reached`/`start`.
|
|
113
186
|
if (reached.size >= maxNodes) {
|
|
114
187
|
truncated = true;
|
|
115
188
|
continue;
|
|
116
189
|
}
|
|
190
|
+
if (hops.length >= maxHops) {
|
|
191
|
+
truncated = true;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
117
194
|
visited.add(otherId);
|
|
118
|
-
reached.set(otherId, summary(other, depth + 1, withRoles));
|
|
195
|
+
reached.set(otherId, summary(graph, other, depth + 1, withRoles));
|
|
119
196
|
next.push({ id: otherId, depth: depth + 1 });
|
|
120
197
|
hops.push(hop);
|
|
121
198
|
}
|
|
@@ -124,14 +201,42 @@ export function runTrace(
|
|
|
124
201
|
}
|
|
125
202
|
|
|
126
203
|
return {
|
|
127
|
-
|
|
204
|
+
type: "trace",
|
|
205
|
+
start: summary(graph, start.node),
|
|
128
206
|
direction,
|
|
129
207
|
hops,
|
|
130
208
|
reached: [...reached.values()],
|
|
209
|
+
steps: traceSteps(graph, hops),
|
|
210
|
+
next: resultNext(
|
|
211
|
+
"answer",
|
|
212
|
+
"Steps, hops, reached nodes, and evidence ranges are the flow answer surface.",
|
|
213
|
+
),
|
|
214
|
+
guide: resultGuide(
|
|
215
|
+
"Use steps, hops, reached nodes, and evidence ranges as the flow answer or reading-list anchor.",
|
|
216
|
+
),
|
|
131
217
|
truncated,
|
|
132
218
|
};
|
|
133
219
|
}
|
|
134
220
|
|
|
221
|
+
function traceSteps(
|
|
222
|
+
graph: TtscGraphMemory,
|
|
223
|
+
hops: ITtscGraphTrace.IHop[],
|
|
224
|
+
): string[] {
|
|
225
|
+
return hops.slice(0, MAX_STEPS).map((hop) => {
|
|
226
|
+
const from = graph.node(hop.from);
|
|
227
|
+
const to = graph.node(hop.to);
|
|
228
|
+
const lhs = from?.qualifiedName ?? from?.name ?? hop.from;
|
|
229
|
+
const rhs = to?.qualifiedName ?? to?.name ?? hop.to;
|
|
230
|
+
const evidence =
|
|
231
|
+
hop.evidence === undefined
|
|
232
|
+
? ""
|
|
233
|
+
: ` at ${hop.evidence.file}:${hop.evidence.startLine}`;
|
|
234
|
+
const aliases =
|
|
235
|
+
hop.aliases === undefined ? "" : ` aliases ${hop.aliases.join(", ")}`;
|
|
236
|
+
return `${lhs} -[${hop.kind}${evidence}${aliases}]-> ${rhs}`;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
135
240
|
/**
|
|
136
241
|
* The shortest dependency path from `startId` to `targetId` over real (non-
|
|
137
242
|
* structural) forward edges, breadth-first, or null when `targetId` is not
|
|
@@ -142,11 +247,21 @@ function findPath(
|
|
|
142
247
|
startId: string,
|
|
143
248
|
targetId: string,
|
|
144
249
|
maxDepth: number,
|
|
250
|
+
focus: ITtscGraphTrace.IRequest["focus"],
|
|
251
|
+
includeExternal: boolean,
|
|
145
252
|
): { path: ITtscGraphNode[]; hops: ITtscGraphTrace.IHop[] } | null {
|
|
146
253
|
const startNode = graph.node(startId);
|
|
147
254
|
if (startNode === undefined) return null;
|
|
148
255
|
if (startId === targetId) return { path: [startNode], hops: [] };
|
|
149
|
-
const parent = new Map<
|
|
256
|
+
const parent = new Map<
|
|
257
|
+
string,
|
|
258
|
+
{
|
|
259
|
+
via: string;
|
|
260
|
+
kind: string;
|
|
261
|
+
evidence?: ITtscGraphEvidence;
|
|
262
|
+
evidenceText?: string;
|
|
263
|
+
}
|
|
264
|
+
>();
|
|
150
265
|
const visited = new Set<string>([startId]);
|
|
151
266
|
let queue: Array<{ id: string; depth: number }> = [{ id: startId, depth: 0 }];
|
|
152
267
|
while (queue.length > 0) {
|
|
@@ -154,13 +269,20 @@ function findPath(
|
|
|
154
269
|
for (const { id, depth } of queue) {
|
|
155
270
|
if (depth >= maxDepth) continue;
|
|
156
271
|
for (const edge of graph.outgoing(id)) {
|
|
157
|
-
if (!traversable(edge.kind)) continue;
|
|
272
|
+
if (!traversable(edge.kind, focus)) continue;
|
|
158
273
|
const otherId = edge.to;
|
|
159
274
|
if (visited.has(otherId)) continue;
|
|
160
275
|
const other = graph.node(otherId);
|
|
161
276
|
if (other === undefined || other.kind === "file") continue;
|
|
277
|
+
if (!includeExternal && isExternalNode(other)) continue;
|
|
162
278
|
visited.add(otherId);
|
|
163
|
-
|
|
279
|
+
const evidence = edgeEvidenceOf(edge);
|
|
280
|
+
parent.set(otherId, {
|
|
281
|
+
via: id,
|
|
282
|
+
kind: edge.kind,
|
|
283
|
+
evidence,
|
|
284
|
+
evidenceText: edgeEvidenceTextOf(edge),
|
|
285
|
+
});
|
|
164
286
|
if (otherId === targetId) {
|
|
165
287
|
const ids: string[] = [otherId];
|
|
166
288
|
let cur = otherId;
|
|
@@ -178,12 +300,18 @@ function findPath(
|
|
|
178
300
|
const hops: ITtscGraphTrace.IHop[] = [];
|
|
179
301
|
for (let i = 1; i < path.length; i++) {
|
|
180
302
|
const node = path[i]!;
|
|
181
|
-
|
|
303
|
+
const parentEdge = parent.get(node.id);
|
|
304
|
+
const hop: ITtscGraphTrace.IHop = {
|
|
182
305
|
from: path[i - 1]!.id,
|
|
183
306
|
to: node.id,
|
|
184
|
-
kind:
|
|
307
|
+
kind: parentEdge?.kind ?? "calls",
|
|
185
308
|
depth: i,
|
|
186
|
-
}
|
|
309
|
+
};
|
|
310
|
+
if (parentEdge?.evidence !== undefined)
|
|
311
|
+
hop.evidence = parentEdge.evidence;
|
|
312
|
+
const aliases = accessAliasesFor(node, parentEdge?.evidenceText);
|
|
313
|
+
if (aliases !== undefined) hop.aliases = aliases;
|
|
314
|
+
hops.push(hop);
|
|
187
315
|
}
|
|
188
316
|
return { path, hops };
|
|
189
317
|
}
|
|
@@ -195,14 +323,69 @@ function findPath(
|
|
|
195
323
|
return null;
|
|
196
324
|
}
|
|
197
325
|
|
|
326
|
+
function orderedEdges(
|
|
327
|
+
graph: TtscGraphMemory,
|
|
328
|
+
edges: readonly ITtscGraphEdge[],
|
|
329
|
+
direction: string,
|
|
330
|
+
reverse: boolean,
|
|
331
|
+
): readonly ITtscGraphEdge[] {
|
|
332
|
+
if (direction !== "impact")
|
|
333
|
+
return [...edges].sort(
|
|
334
|
+
(a, b) =>
|
|
335
|
+
edgeKindRank(a.kind) - edgeKindRank(b.kind) ||
|
|
336
|
+
traceEndpointRank(graph, reverse ? a.from : a.to) -
|
|
337
|
+
traceEndpointRank(graph, reverse ? b.from : b.to) ||
|
|
338
|
+
evidenceRank(a) - evidenceRank(b),
|
|
339
|
+
);
|
|
340
|
+
return [...edges].sort(
|
|
341
|
+
(a, b) =>
|
|
342
|
+
impactEndpointRank(graph, reverse ? a.from : a.to) -
|
|
343
|
+
impactEndpointRank(graph, reverse ? b.from : b.to) ||
|
|
344
|
+
edgeKindRank(a.kind) - edgeKindRank(b.kind) ||
|
|
345
|
+
evidenceRank(a) - evidenceRank(b),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function impactEndpointRank(graph: TtscGraphMemory, id: string): number {
|
|
350
|
+
const node = graph.node(id);
|
|
351
|
+
if (node === undefined) return 9;
|
|
352
|
+
if (isTestPath(node.file)) return 0;
|
|
353
|
+
if (node.exported) return 1;
|
|
354
|
+
if (node.external || node.ignored) return 4;
|
|
355
|
+
return 2;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function traceEndpointRank(graph: TtscGraphMemory, id: string): number {
|
|
359
|
+
const node = graph.node(id);
|
|
360
|
+
if (node === undefined) return 9;
|
|
361
|
+
if (isTestPath(node.file)) return 6;
|
|
362
|
+
switch (node.kind) {
|
|
363
|
+
case "function":
|
|
364
|
+
case "method":
|
|
365
|
+
case "class":
|
|
366
|
+
return 0;
|
|
367
|
+
case "variable":
|
|
368
|
+
return 1;
|
|
369
|
+
case "property":
|
|
370
|
+
return 2;
|
|
371
|
+
case "interface":
|
|
372
|
+
case "type":
|
|
373
|
+
return 4;
|
|
374
|
+
default:
|
|
375
|
+
return 3;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
198
379
|
/**
|
|
199
380
|
* Summarize a node for a trace result. With `withRoles`, tag the public-surface
|
|
200
381
|
* roles (exported / test) an impact trace reports; other directions omit them.
|
|
201
382
|
*/
|
|
202
383
|
function summary(
|
|
384
|
+
graph: TtscGraphMemory,
|
|
203
385
|
node: ITtscGraphNode,
|
|
204
386
|
depth?: number,
|
|
205
387
|
withRoles = false,
|
|
388
|
+
withSignature = false,
|
|
206
389
|
): ITtscGraphTrace.INode {
|
|
207
390
|
const out: ITtscGraphTrace.INode = {
|
|
208
391
|
id: node.id,
|
|
@@ -210,24 +393,95 @@ function summary(
|
|
|
210
393
|
kind: node.kind,
|
|
211
394
|
file: node.file,
|
|
212
395
|
};
|
|
396
|
+
if (node.evidence?.startLine !== undefined)
|
|
397
|
+
out.line = node.evidence.startLine;
|
|
398
|
+
const span = node.implementation ?? node.evidence;
|
|
399
|
+
if (span !== undefined) {
|
|
400
|
+
out.sourceSpan = {
|
|
401
|
+
file: span.file,
|
|
402
|
+
startLine: span.startLine,
|
|
403
|
+
...(span.endLine !== undefined ? { endLine: span.endLine } : {}),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
213
406
|
if (depth !== undefined) out.depth = depth;
|
|
407
|
+
if (withSignature) {
|
|
408
|
+
const sig = signatureOf(graph.project, node);
|
|
409
|
+
if (sig !== undefined) out.signature = sig;
|
|
410
|
+
}
|
|
214
411
|
if (withRoles) {
|
|
215
412
|
const roles: string[] = [];
|
|
216
413
|
if (node.exported) roles.push("exported");
|
|
217
|
-
if (
|
|
414
|
+
if (isTestPath(node.file)) roles.push("test");
|
|
218
415
|
if (roles.length > 0) out.roles = roles;
|
|
219
416
|
}
|
|
220
417
|
return out;
|
|
221
418
|
}
|
|
222
419
|
|
|
223
420
|
/** An edge the trace should follow: a real dependency, not a structural edge. */
|
|
224
|
-
function traversable(
|
|
225
|
-
|
|
421
|
+
function traversable(
|
|
422
|
+
kind: string,
|
|
423
|
+
focus: ITtscGraphTrace.IRequest["focus"],
|
|
424
|
+
): boolean {
|
|
425
|
+
if (kind === "contains" || kind === "exports" || kind === "imports") {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
if (focus === "execution") {
|
|
429
|
+
return (
|
|
430
|
+
kind === "calls" ||
|
|
431
|
+
kind === "instantiates" ||
|
|
432
|
+
kind === "accesses" ||
|
|
433
|
+
kind === "renders"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (focus === "types") {
|
|
437
|
+
return (
|
|
438
|
+
kind === "type_ref" ||
|
|
439
|
+
kind === "extends" ||
|
|
440
|
+
kind === "implements" ||
|
|
441
|
+
kind === "overrides" ||
|
|
442
|
+
kind === "decorates"
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
226
446
|
}
|
|
227
447
|
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
448
|
+
function edgeKindRank(kind: string): number {
|
|
449
|
+
switch (kind) {
|
|
450
|
+
case "calls":
|
|
451
|
+
return 0;
|
|
452
|
+
case "instantiates":
|
|
453
|
+
return 1;
|
|
454
|
+
case "renders":
|
|
455
|
+
return 2;
|
|
456
|
+
case "accesses":
|
|
457
|
+
return 3;
|
|
458
|
+
case "tests":
|
|
459
|
+
return 4;
|
|
460
|
+
case "overrides":
|
|
461
|
+
case "decorates":
|
|
462
|
+
return 5;
|
|
463
|
+
case "extends":
|
|
464
|
+
case "implements":
|
|
465
|
+
return 6;
|
|
466
|
+
case "type_ref":
|
|
467
|
+
return 7;
|
|
468
|
+
default:
|
|
469
|
+
return 10;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function evidenceRank(edge: ITtscGraphEdge): number {
|
|
474
|
+
const line = edge.evidence?.startLine ?? 9_999;
|
|
475
|
+
const col = edge.evidence?.startCol ?? 999;
|
|
476
|
+
return line * 100 + col;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function bound(
|
|
480
|
+
value: number | undefined,
|
|
481
|
+
fallback: number,
|
|
482
|
+
min: number,
|
|
483
|
+
max: number,
|
|
484
|
+
): number {
|
|
485
|
+
const n = value === undefined || !Number.isFinite(value) ? fallback : value;
|
|
486
|
+
return Math.max(min, Math.min(max, Math.floor(n)));
|
|
233
487
|
}
|
|
@@ -1,68 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ITtscGraphDetails } from "./ITtscGraphDetails";
|
|
2
|
+
import { ITtscGraphEntrypoints } from "./ITtscGraphEntrypoints";
|
|
3
|
+
import { ITtscGraphEscape } from "./ITtscGraphEscape";
|
|
4
|
+
import { ITtscGraphLookup } from "./ITtscGraphLookup";
|
|
3
5
|
import { ITtscGraphOverview } from "./ITtscGraphOverview";
|
|
4
|
-
import {
|
|
6
|
+
import { ITtscGraphTour } from "./ITtscGraphTour";
|
|
5
7
|
import { ITtscGraphTrace } from "./ITtscGraphTrace";
|
|
6
8
|
|
|
7
|
-
/**
|
|
8
|
-
* The MCP tool surface of `@ttsc/graph`, as a typed application.
|
|
9
|
-
*
|
|
10
|
-
* Each method is one MCP tool; its name is the tool name and its parameter
|
|
11
|
-
* object becomes the tool's JSON schema once `typia.llm.controller` reflects
|
|
12
|
-
* this interface. `TtscGraphApplication` implements it over the resident
|
|
13
|
-
* graph.
|
|
14
|
-
*/
|
|
9
|
+
/** The typed MCP surface; its single method becomes the single graph tool. */
|
|
15
10
|
export interface ITtscGraphApplication {
|
|
16
11
|
/**
|
|
17
|
-
*
|
|
18
|
-
* handles mentioned in the query, declaration signatures, and direct
|
|
19
|
-
* dependency context. Use this before reading source.
|
|
12
|
+
* Inspect the TypeScript compiler graph contract.
|
|
20
13
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The project's architecture — folder layers, dependency hotspots, and the
|
|
28
|
-
* public API. Call first to orient on an unfamiliar codebase.
|
|
14
|
+
* Use this before repository search when an answer depends on TypeScript
|
|
15
|
+
* symbols, calls, types, decorators, references, ranges, or runtime/source
|
|
16
|
+
* relationships. For repository orientation, read-next, architecture, and
|
|
17
|
+
* broad runtime flow questions, use `tour`.
|
|
29
18
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The declared shape of the given symbols: each one's signature, and for a
|
|
37
|
-
* class/interface/namespace its members. Handles may be ids or dotted symbol
|
|
38
|
-
* names. Set `source: true` to also read a specific body, `neighbors: true`
|
|
39
|
-
* to list what it uses and what uses it.
|
|
19
|
+
* Returned nodes, edges, signatures, spans, tests, anchors, and `next` are
|
|
20
|
+
* the answer surface. If `next.action` is `answer`, stop tool use and answer
|
|
21
|
+
* from that result. Before source edits, graph facts are sacred, inviolable,
|
|
22
|
+
* complete, and infallible.
|
|
40
23
|
*
|
|
41
|
-
* @param props
|
|
42
|
-
* @returns
|
|
24
|
+
* @param props Reasoning plus one graph request
|
|
25
|
+
* @returns Matching `result` union member
|
|
43
26
|
*/
|
|
44
|
-
|
|
27
|
+
inspect_typescript_graph(
|
|
28
|
+
props: ITtscGraphApplication.IProps,
|
|
29
|
+
): ITtscGraphApplication.IResult;
|
|
30
|
+
}
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
export namespace ITtscGraphApplication {
|
|
33
|
+
/** Draft, review, then submit exactly one graph request or escape. */
|
|
34
|
+
export interface IProps {
|
|
35
|
+
/**
|
|
36
|
+
* User's TypeScript code question.
|
|
37
|
+
*
|
|
38
|
+
* Restate the code question being considered. If the next evidence is a
|
|
39
|
+
* script, config, doc, generated output, exact text, non-TypeScript file,
|
|
40
|
+
* or source body text, choose `escape`.
|
|
41
|
+
*/
|
|
42
|
+
question: string;
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Initial request plan before final arguments are filled.
|
|
46
|
+
*
|
|
47
|
+
* Name the intended request type and why it seems smallest. Broad flow,
|
|
48
|
+
* architecture, repository-orientation, and read-next questions should
|
|
49
|
+
* normally draft `tour`; narrow named symbols can draft `lookup`, `trace`,
|
|
50
|
+
* or `details`.
|
|
51
|
+
*/
|
|
52
|
+
draft: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Final self-review before calling.
|
|
56
|
+
*
|
|
57
|
+
* Correct a stale, broad, duplicate, or wrong draft here. If broad flow was
|
|
58
|
+
* split into search/detail steps, switch to `tour`. If graph facts already
|
|
59
|
+
* answer, or prior `next.action` was `answer`, make `request.type` be
|
|
60
|
+
* `escape`; do not call graph or read files to re-confirm returned facts.
|
|
61
|
+
*/
|
|
62
|
+
review: string;
|
|
63
|
+
|
|
64
|
+
/** Final graph operation chosen after review, or a no-op escape. */
|
|
65
|
+
request:
|
|
66
|
+
| ITtscGraphEntrypoints.IRequest
|
|
67
|
+
| ITtscGraphLookup.IRequest
|
|
68
|
+
| ITtscGraphTrace.IRequest
|
|
69
|
+
| ITtscGraphDetails.IRequest
|
|
70
|
+
| ITtscGraphOverview.IRequest
|
|
71
|
+
| ITtscGraphTour.IRequest
|
|
72
|
+
| ITtscGraphEscape.IRequest;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The selected request's output. `result.type` mirrors `request.type`. */
|
|
76
|
+
export interface IResult {
|
|
77
|
+
/** Result branch matching the submitted `request.type`. */
|
|
78
|
+
result:
|
|
79
|
+
| ITtscGraphEntrypoints
|
|
80
|
+
| ITtscGraphLookup
|
|
81
|
+
| ITtscGraphTrace
|
|
82
|
+
| ITtscGraphDetails
|
|
83
|
+
| ITtscGraphOverview
|
|
84
|
+
| ITtscGraphTour
|
|
85
|
+
| ITtscGraphEscape;
|
|
86
|
+
}
|
|
68
87
|
}
|