@ttsc/graph 0.16.4 → 0.16.6

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 (115) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +24 -53
  3. package/lib/TtscGraphApplication.js +59 -0
  4. package/lib/TtscGraphApplication.js.map +1 -0
  5. package/lib/index.js +31 -108
  6. package/lib/index.js.map +1 -1
  7. package/lib/model/TtscGraphMemory.js +185 -0
  8. package/lib/model/TtscGraphMemory.js.map +1 -0
  9. package/lib/model/loadGraph.js +418 -0
  10. package/lib/model/loadGraph.js.map +1 -0
  11. package/lib/reduce.js +101 -0
  12. package/lib/reduce.js.map +1 -0
  13. package/lib/resolveGraphBinary.js +42 -0
  14. package/lib/resolveGraphBinary.js.map +1 -0
  15. package/lib/server/accessAliases.js +53 -0
  16. package/lib/server/accessAliases.js.map +1 -0
  17. package/lib/server/createServer.js +1726 -0
  18. package/lib/server/createServer.js.map +1 -0
  19. package/lib/server/instructions.js +80 -0
  20. package/lib/server/instructions.js.map +1 -0
  21. package/lib/server/resolveHandle.js +25 -0
  22. package/lib/server/resolveHandle.js.map +1 -0
  23. package/lib/server/runDetails.js +413 -0
  24. package/lib/server/runDetails.js.map +1 -0
  25. package/lib/server/runEntrypoints.js +211 -0
  26. package/lib/server/runEntrypoints.js.map +1 -0
  27. package/lib/server/runLookup.js +234 -0
  28. package/lib/server/runLookup.js.map +1 -0
  29. package/lib/server/runOverview.js +160 -0
  30. package/lib/server/runOverview.js.map +1 -0
  31. package/lib/server/runTrace.js +314 -0
  32. package/lib/server/runTrace.js.map +1 -0
  33. package/lib/server/startServer.js +21 -0
  34. package/lib/server/startServer.js.map +1 -0
  35. package/lib/structures/ITtscGraphApplication.js +3 -0
  36. package/lib/structures/ITtscGraphApplication.js.map +1 -0
  37. package/lib/structures/ITtscGraphDecorator.js +3 -0
  38. package/lib/structures/ITtscGraphDecorator.js.map +1 -0
  39. package/lib/structures/ITtscGraphDetails.js +3 -0
  40. package/lib/structures/ITtscGraphDetails.js.map +1 -0
  41. package/lib/structures/ITtscGraphDiagnostic.js +3 -0
  42. package/lib/structures/ITtscGraphDiagnostic.js.map +1 -0
  43. package/lib/structures/ITtscGraphDump.js +3 -0
  44. package/lib/structures/ITtscGraphDump.js.map +1 -0
  45. package/lib/structures/ITtscGraphEdge.js +3 -0
  46. package/lib/structures/ITtscGraphEdge.js.map +1 -0
  47. package/lib/structures/ITtscGraphEntrypoints.js +3 -0
  48. package/lib/structures/ITtscGraphEntrypoints.js.map +1 -0
  49. package/lib/structures/ITtscGraphEscape.js +3 -0
  50. package/lib/structures/ITtscGraphEscape.js.map +1 -0
  51. package/lib/structures/ITtscGraphEvidence.js +3 -0
  52. package/lib/structures/ITtscGraphEvidence.js.map +1 -0
  53. package/lib/structures/ITtscGraphLookup.js +3 -0
  54. package/lib/structures/ITtscGraphLookup.js.map +1 -0
  55. package/lib/structures/ITtscGraphNode.js +3 -0
  56. package/lib/structures/ITtscGraphNode.js.map +1 -0
  57. package/lib/structures/ITtscGraphOverview.js +3 -0
  58. package/lib/structures/ITtscGraphOverview.js.map +1 -0
  59. package/lib/structures/ITtscGraphTrace.js +3 -0
  60. package/lib/structures/ITtscGraphTrace.js.map +1 -0
  61. package/lib/structures/TtscGraphEdgeKind.js +3 -0
  62. package/lib/structures/TtscGraphEdgeKind.js.map +1 -0
  63. package/lib/structures/TtscGraphNodeKind.js +3 -0
  64. package/lib/structures/TtscGraphNodeKind.js.map +1 -0
  65. package/lib/structures/TtscGraphNodeModifier.js +3 -0
  66. package/lib/structures/TtscGraphNodeModifier.js.map +1 -0
  67. package/lib/structures/index.js +38 -0
  68. package/lib/structures/index.js.map +1 -0
  69. package/lib/view.js +140 -0
  70. package/lib/view.js.map +1 -0
  71. package/lib/viewer/index.html +123 -0
  72. package/lib/viewer/viewer.js +4610 -0
  73. package/package.json +17 -4
  74. package/src/TtscGraphApplication.ts +64 -0
  75. package/src/index.ts +40 -126
  76. package/src/model/TtscGraphMemory.ts +204 -0
  77. package/src/model/loadGraph.ts +85 -0
  78. package/src/reduce.ts +165 -0
  79. package/src/resolveGraphBinary.ts +40 -0
  80. package/src/server/accessAliases.ts +55 -0
  81. package/src/server/createServer.ts +101 -0
  82. package/src/server/instructions.ts +76 -0
  83. package/src/server/resolveHandle.ts +34 -0
  84. package/src/server/runDetails.ts +469 -0
  85. package/src/server/runEntrypoints.ts +256 -0
  86. package/src/server/runLookup.ts +257 -0
  87. package/src/server/runOverview.ts +166 -0
  88. package/src/server/runTrace.ts +359 -0
  89. package/src/server/startServer.ts +25 -0
  90. package/src/structures/ITtscGraphApplication.ts +116 -0
  91. package/src/structures/ITtscGraphDecorator.ts +31 -0
  92. package/src/structures/ITtscGraphDetails.ts +134 -0
  93. package/src/structures/ITtscGraphDiagnostic.ts +34 -0
  94. package/src/structures/ITtscGraphDump.ts +35 -0
  95. package/src/structures/ITtscGraphEdge.ts +24 -0
  96. package/src/structures/ITtscGraphEntrypoints.ts +118 -0
  97. package/src/structures/ITtscGraphEscape.ts +39 -0
  98. package/src/structures/ITtscGraphEvidence.ts +26 -0
  99. package/src/structures/ITtscGraphLookup.ts +61 -0
  100. package/src/structures/ITtscGraphNode.ts +70 -0
  101. package/src/structures/ITtscGraphOverview.ts +87 -0
  102. package/src/structures/ITtscGraphTrace.ts +140 -0
  103. package/src/structures/TtscGraphEdgeKind.ts +23 -0
  104. package/src/structures/TtscGraphNodeKind.ts +26 -0
  105. package/src/structures/TtscGraphNodeModifier.ts +19 -0
  106. package/src/structures/index.ts +22 -0
  107. package/src/view.ts +155 -0
  108. package/src/viewer/index.html +123 -0
  109. package/src/viewer/main.ts +209 -0
  110. package/lib/bin.d.ts +0 -2
  111. package/lib/diagnostics.d.ts +0 -23
  112. package/lib/diagnostics.js +0 -84
  113. package/lib/diagnostics.js.map +0 -1
  114. package/lib/index.d.ts +0 -29
  115. package/src/diagnostics.ts +0 -93
package/src/reduce.ts ADDED
@@ -0,0 +1,165 @@
1
+ // Reduce a raw `ttscgraph dump` to the payload the bundled 3D viewer renders.
2
+ // This mirrors website/src/components/graph/graphReduce.ts (the same pure
3
+ // transform); keep the two in sync. The CLI reduces in Node before serving, so
4
+ // the browser viewer only ever renders a ready `{ nodes, links }`.
5
+
6
+ export interface RawNode {
7
+ id: string;
8
+ name: string;
9
+ kind: string;
10
+ file: string;
11
+ external?: boolean;
12
+ }
13
+
14
+ export interface RawEdge {
15
+ from: string;
16
+ to: string;
17
+ kind: string;
18
+ }
19
+
20
+ export interface RawDump {
21
+ project?: string;
22
+ nodes: RawNode[];
23
+ edges: RawEdge[];
24
+ }
25
+
26
+ export interface ViewerNode {
27
+ id: string;
28
+ name: string;
29
+ kind: string;
30
+ file: string;
31
+ degree: number;
32
+ }
33
+
34
+ export interface ViewerLink {
35
+ source: string;
36
+ target: string;
37
+ kind: string;
38
+ }
39
+
40
+ export interface ViewerPayload {
41
+ project: string;
42
+ counts: {
43
+ rawNodes: number;
44
+ rawEdges: number;
45
+ nodes: number;
46
+ links: number;
47
+ droppedExternal: number;
48
+ droppedByCap: number;
49
+ };
50
+ nodes: ViewerNode[];
51
+ links: ViewerLink[];
52
+ }
53
+
54
+ function posix(p: string): string {
55
+ return p.replace(/\\/g, "/");
56
+ }
57
+
58
+ function commonRoot(files: string[]): string {
59
+ if (files.length === 0) return "";
60
+ let parts = posix(files[0]!).split("/");
61
+ for (const file of files.slice(1)) {
62
+ const other = posix(file).split("/");
63
+ let i = 0;
64
+ while (i < parts.length && i < other.length && parts[i] === other[i]) i++;
65
+ parts = parts.slice(0, i);
66
+ if (parts.length === 0) break;
67
+ }
68
+ return parts.join("/");
69
+ }
70
+
71
+ function relativize(abs: string, root: string): string {
72
+ const a = posix(abs);
73
+ const r = posix(root).replace(/\/+$/, "");
74
+ if (r && (a === r || a.startsWith(r + "/")))
75
+ return a.slice(r.length).replace(/^\/+/, "");
76
+ const nm = a.lastIndexOf("node_modules/");
77
+ if (nm >= 0) return a.slice(nm);
78
+ const slash = a.lastIndexOf("/");
79
+ return slash >= 0 ? a.slice(slash + 1) : a;
80
+ }
81
+
82
+ function rewriteId(id: string, root: string): string {
83
+ const hash = id.indexOf("#");
84
+ if (hash < 0) return id;
85
+ return relativize(id.slice(0, hash), root) + id.slice(hash);
86
+ }
87
+
88
+ function degreeOf(
89
+ nodes: { id: string }[],
90
+ edges: { from: string; to: string }[],
91
+ ): Map<string, number> {
92
+ const degree = new Map<string, number>(nodes.map((n) => [n.id, 0]));
93
+ for (const e of edges) {
94
+ if (degree.has(e.from)) degree.set(e.from, degree.get(e.from)! + 1);
95
+ if (degree.has(e.to)) degree.set(e.to, degree.get(e.to)! + 1);
96
+ }
97
+ return degree;
98
+ }
99
+
100
+ export function reduce(
101
+ raw: RawDump,
102
+ {
103
+ maxNodes = 1200,
104
+ keepExternal = false,
105
+ }: { maxNodes?: number; keepExternal?: boolean } = {},
106
+ ): ViewerPayload {
107
+ const keptByExternal = raw.nodes.filter((n) => keepExternal || !n.external);
108
+ const root = commonRoot(
109
+ raw.nodes.filter((n) => !n.external).map((n) => n.file),
110
+ );
111
+
112
+ const liveIds = new Set(keptByExternal.map((n) => n.id));
113
+ const liveEdges = raw.edges.filter(
114
+ (e) => liveIds.has(e.from) && liveIds.has(e.to),
115
+ );
116
+
117
+ const degree = degreeOf(keptByExternal, liveEdges);
118
+ let kept = keptByExternal;
119
+ let droppedByCap = 0;
120
+ if (kept.length > maxNodes) {
121
+ kept = [...kept]
122
+ .sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0))
123
+ .slice(0, maxNodes);
124
+ droppedByCap = keptByExternal.length - kept.length;
125
+ }
126
+
127
+ const keptIds = new Set(kept.map((n) => n.id));
128
+ const edges = liveEdges.filter(
129
+ (e) => keptIds.has(e.from) && keptIds.has(e.to),
130
+ );
131
+ const finalDegree = degreeOf(kept, edges);
132
+
133
+ const nodes: ViewerNode[] = kept
134
+ .filter((n) => (finalDegree.get(n.id) ?? 0) > 0)
135
+ .map((n) => ({
136
+ id: rewriteId(n.id, root),
137
+ name: n.name,
138
+ kind: n.kind,
139
+ file: relativize(n.file, root),
140
+ degree: finalDegree.get(n.id) ?? 0,
141
+ }));
142
+
143
+ const nodeIds = new Set(nodes.map((n) => n.id));
144
+ const links: ViewerLink[] = edges
145
+ .map((e) => ({
146
+ source: rewriteId(e.from, root),
147
+ target: rewriteId(e.to, root),
148
+ kind: e.kind,
149
+ }))
150
+ .filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
151
+
152
+ return {
153
+ project: raw.project ?? "",
154
+ counts: {
155
+ rawNodes: raw.nodes.length,
156
+ rawEdges: raw.edges.length,
157
+ nodes: nodes.length,
158
+ links: links.length,
159
+ droppedExternal: raw.nodes.length - keptByExternal.length,
160
+ droppedByCap,
161
+ },
162
+ nodes,
163
+ links,
164
+ };
165
+ }
@@ -0,0 +1,40 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * Resolve the per-platform `ttscgraph` binary, or `null` when it cannot be
6
+ * located.
7
+ *
8
+ * `ttsc` is a peer the user installs alongside `@ttsc/graph` (not a dependency
9
+ * of this launcher), so resolution starts from the user's project, not from
10
+ * this package's own tree.
11
+ *
12
+ * Resolution order:
13
+ *
14
+ * 1. `TTSC_GRAPH_BINARY` env var, when set to an absolute path.
15
+ * 2. The per-platform npm package `@ttsc/<platform>-<arch>/bin/ttscgraph[.exe]`.
16
+ * That package carries `ttsc`, `ttscserver`, and `ttscgraph` together and is
17
+ * an `optionalDependency` of `ttsc`, so it is resolved from `ttsc`'s
18
+ * location — found from `process.cwd()` (the project where the agent ran the
19
+ * server).
20
+ */
21
+ export function resolveGraphBinary(
22
+ env: NodeJS.ProcessEnv = process.env,
23
+ cwd: string = process.cwd(),
24
+ ): string | null {
25
+ if (env.TTSC_GRAPH_BINARY && path.isAbsolute(env.TTSC_GRAPH_BINARY)) {
26
+ return env.TTSC_GRAPH_BINARY;
27
+ }
28
+ const exe = process.platform === "win32" ? "ttscgraph.exe" : "ttscgraph";
29
+ try {
30
+ const ttscPackageJson = require.resolve("ttsc/package.json", {
31
+ paths: [cwd],
32
+ });
33
+ const fromTtsc = createRequire(ttscPackageJson);
34
+ return fromTtsc.resolve(
35
+ `@ttsc/${process.platform}-${process.arch}/bin/${exe}`,
36
+ );
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
@@ -0,0 +1,55 @@
1
+ import { ITtscGraphNode } from "../structures/ITtscGraphNode";
2
+
3
+ /**
4
+ * Derive stable access-path aliases for a member edge from its source
5
+ * expression. If a hop reaches `Owner.member` through `obj.path.member`, the
6
+ * alias `Owner.path.member` keeps both the resolved owner and the concrete
7
+ * access path visible without reopening source.
8
+ */
9
+ export function accessAliasesFor(
10
+ node: ITtscGraphNode | undefined,
11
+ evidenceText: string | undefined,
12
+ ): string[] | undefined {
13
+ if (
14
+ node === undefined ||
15
+ node.qualifiedName === undefined ||
16
+ evidenceText === undefined
17
+ ) {
18
+ return undefined;
19
+ }
20
+ const segments = dottedSegments(evidenceText);
21
+ if (segments.length < 2 || segments[segments.length - 1] !== node.name) {
22
+ return undefined;
23
+ }
24
+ const dot = node.qualifiedName.lastIndexOf(".");
25
+ if (dot < 0) return undefined;
26
+
27
+ const owner = node.qualifiedName.slice(0, dot);
28
+ const suffix = segments.slice(1).join(".");
29
+ const aliases = new Set<string>();
30
+ for (const candidate of [...ownerDisplayAliases(owner), owner]) {
31
+ const alias = `${candidate}.${suffix}`;
32
+ if (alias !== node.qualifiedName) aliases.add(alias);
33
+ }
34
+ return aliases.size > 0 ? [...aliases] : undefined;
35
+ }
36
+
37
+ function dottedSegments(text: string): string[] {
38
+ const normalized = text.trim().replace(/\?\./g, ".");
39
+ const parts = normalized.split(".");
40
+ if (parts.length < 2) return [];
41
+ return parts.every((part) => /^[A-Za-z_$][\w$]*$/.test(part)) ? parts : [];
42
+ }
43
+
44
+ function ownerDisplayAliases(owner: string): string[] {
45
+ const display = owner.replace(/^_+/, "");
46
+ const out = new Set<string>();
47
+ for (const suffix of ["Internals", "Internal"]) {
48
+ if (display.length > suffix.length && display.endsWith(suffix)) {
49
+ out.add(display.slice(0, -suffix.length));
50
+ }
51
+ }
52
+ if (out.size === 0 && display !== owner) out.add(display);
53
+ out.delete(owner);
54
+ return [...out];
55
+ }
@@ -0,0 +1,101 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import {
3
+ CallToolRequestSchema,
4
+ ListToolsRequestSchema,
5
+ } from "@modelcontextprotocol/sdk/types.js";
6
+ import typia from "typia";
7
+
8
+ import { TtscGraphApplication, TtscGraphSource } from "../TtscGraphApplication";
9
+ import { TtscGraphMemory } from "../model/TtscGraphMemory";
10
+ import { ITtscGraphApplication } from "../structures/ITtscGraphApplication";
11
+ import { instructions } from "./instructions";
12
+
13
+ /**
14
+ * Build the MCP server for a graph. `typia.llm.controller` reflects
15
+ * {@link ITtscGraphApplication} into a validated tool application. Every tool's
16
+ * JSON schema and argument validator is generated from the method's TypeScript
17
+ * types and JSDoc, so there is no hand-written schema. The list/call handlers
18
+ * below are the minimal standalone registration: list the generated functions,
19
+ * and on a call validate the arguments (returning typia's errors for the model
20
+ * to self-correct) before invoking the method.
21
+ *
22
+ * Registration is inlined rather than pulled from `@typia/mcp` to keep the
23
+ * dependency surface to `typia` plus the MCP SDK and avoid version-pinning the
24
+ * wider typia ecosystem; the shape it relies on is `typia.llm.controller`'s
25
+ * public output.
26
+ */
27
+ export function createServer(
28
+ graph: TtscGraphSource,
29
+ version: string,
30
+ ): McpServer {
31
+ const controller = typia.llm.controller<ITtscGraphApplication>(
32
+ "graph",
33
+ new TtscGraphApplication(graph),
34
+ );
35
+ const functions = controller.application.functions;
36
+ const execute = controller.execute as unknown as Record<
37
+ string,
38
+ (input: unknown) => unknown
39
+ >;
40
+
41
+ const server = new McpServer(
42
+ { name: "ttsc-graph", version },
43
+ { capabilities: { tools: {} }, instructions },
44
+ );
45
+ const raw = server.server;
46
+
47
+ raw.setRequestHandler(ListToolsRequestSchema, async () => ({
48
+ tools: functions.map((func) => ({
49
+ name: func.name,
50
+ description: func.description,
51
+ inputSchema: {
52
+ type: "object" as const,
53
+ properties: func.parameters.properties,
54
+ required: func.parameters.required,
55
+ additionalProperties: false,
56
+ $defs: func.parameters.$defs,
57
+ },
58
+ })),
59
+ }));
60
+
61
+ raw.setRequestHandler(CallToolRequestSchema, async (request) => {
62
+ const func = functions.find((f) => f.name === request.params.name);
63
+ const method = execute[request.params.name];
64
+ if (func === undefined || method === undefined) {
65
+ return error(`Unknown tool: ${request.params.name}`);
66
+ }
67
+ // Validate an empty object when a client omits `arguments`, so typia can
68
+ // return field-level errors instead of a generic "expected object".
69
+ const validation = func.validate(request.params.arguments ?? {});
70
+ if (!validation.success) {
71
+ // Hand typia's validation errors back so the model can correct its call.
72
+ return error(JSON.stringify(validation.errors, null, 2));
73
+ }
74
+ try {
75
+ const result = await method.call(execute, validation.data);
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text" as const,
80
+ text: result === undefined ? "Success" : JSON.stringify(result),
81
+ },
82
+ ],
83
+ };
84
+ } catch (exception) {
85
+ return error(
86
+ exception instanceof Error
87
+ ? `${exception.name}: ${exception.message}`
88
+ : String(exception),
89
+ );
90
+ }
91
+ });
92
+
93
+ return server;
94
+ }
95
+
96
+ function error(text: string): {
97
+ isError: true;
98
+ content: { type: "text"; text: string }[];
99
+ } {
100
+ return { isError: true, content: [{ type: "text", text }] };
101
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The guidance delivered in the MCP initialize response. It is the only place
3
+ * the agent is told how to use the graph; nothing is written to its config
4
+ * files. Keep it short; the per-tool descriptions carry the detail.
5
+ */
6
+ export const instructions = `
7
+ Before answering a TypeScript codebase question, call query. It is the
8
+ code-evidence path:
9
+ symbols, dependency paths, edge evidence ranges, and sourceSpan line anchors
10
+ from the resident project graph. Do not answer from assumptions, ls, rg, cat,
11
+ or Get-Content when graph evidence can answer the code question.
12
+
13
+ The graph is a resident TypeScript fact map, not an answer writer. Fill
14
+ arguments in order: question, graphNeed, draft, review, request. Write
15
+ draft.reason before draft.type, review it for overfetch and non-graph fallback,
16
+ then choose one final request.type:
17
+ entrypoints, lookup, trace, details, overview, or escape. If the review shows the
18
+ question is about scripts, config, generated output, prose docs, or evidence
19
+ already in hand, choose escape instead of spending a graph
20
+ operation. If more TypeScript evidence is needed, make another graph request
21
+ instead of switching to shell search.
22
+
23
+ Budget graph calls before the first request. Most answers need 1-3 calls, and
24
+ four calls is the hard stop for one answer. A fifth graph call means the tool is
25
+ being used as a source reader; answer from returned handles/ranges or choose
26
+ escape and report the missing span.
27
+
28
+ For behavior, lifecycle, request-flow, rendering-flow, or validation-flow
29
+ questions, start with one default entrypoints call, then one trace from the best
30
+ handle, then answer. Use overview only for broad architecture or public API
31
+ orientation.
32
+
33
+ The graph already knows resolved symbols, dependency edges, evidence spans,
34
+ decorators, stable handles, and sourceSpan line anchors. Use returned ranges and
35
+ handles as the evidence. If implementation text is required to decide a detail,
36
+ report the gap and the smallest sourceSpan instead of opening files during the
37
+ graph answer.
38
+
39
+ For caller or call-site questions, do not use rg. Use trace with direction:
40
+ "reverse" or details with neighbors: true; both return edge evidence and line
41
+ anchors for the call expression.
42
+
43
+ Request types:
44
+
45
+ - entrypoints: compact shortlist for behavior-specific code questions. It returns
46
+ ranked symbols, direct mentions, and small dependency orientation without
47
+ implementation text. Do not use it as the first broad public API map.
48
+ - lookup: targeted symbol search for a class, method, function, property, or
49
+ type when you do not already have its handle.
50
+ - trace: call/type/dependency flow for "how A reaches B", lifecycle,
51
+ request-flow, rendering-flow, validation-flow, and impact questions.
52
+ - details: signatures, members, direct calls, direct types, dependency
53
+ neighbors, and sourceSpan anchors for selected handles.
54
+ - overview: source-free architecture map for layers, hotspots, counts, and
55
+ public API. Use it to choose a central exported TypeScript API or entry point
56
+ without reading package scripts.
57
+ - escape: no-op route when the review decides this tool was the wrong evidence
58
+ source or the previous graph result is enough.
59
+
60
+ For a flow question, use entrypoints once, then trace before details. Keep
61
+ dependency maps compact: default limits first, one to three handles in details,
62
+ and no larger limits unless the previous result was truncated and the missing
63
+ piece is named. Do not spend graph calls only to find tests; mention tests only
64
+ when the returned graph slice already exposes them. Stop once file/symbol/range
65
+ evidence is enough to answer.
66
+
67
+ Copy exact names from returned nodes, references, aliases, evidence ranges,
68
+ sourceSpan anchors, and trace steps. Do not use shell to recover TypeScript line
69
+ numbers, call targets, or branch details not already returned by graph evidence;
70
+ name the missing detail and give the returned range.
71
+
72
+ Package scripts, config files, generated output, prose documentation, and exact
73
+ text searches are separate evidence sources. Use them only when the user asks
74
+ about those sources directly; do not use them to answer a TypeScript API or
75
+ call-path question.
76
+ `.trim();
@@ -0,0 +1,34 @@
1
+ import { TtscGraphMemory } from "../model/TtscGraphMemory";
2
+ import { ITtscGraphNode } from "../structures/ITtscGraphNode";
3
+
4
+ export interface IResolvedGraphHandle {
5
+ node?: ITtscGraphNode;
6
+ candidates?: ITtscGraphNode[];
7
+ }
8
+
9
+ /** Resolve a tool handle as an id, exact symbol name, or dotted suffix. */
10
+ export function resolveGraphHandle(
11
+ graph: TtscGraphMemory,
12
+ handle: string,
13
+ candidateLimit = 12,
14
+ ): IResolvedGraphHandle {
15
+ const byId = graph.node(handle);
16
+ if (byId !== undefined) return { node: byId };
17
+
18
+ const exact = graph.symbols(handle);
19
+ if (exact.length === 1) return { node: exact[0] };
20
+ if (exact.length > 1) return { candidates: exact.slice(0, candidateLimit) };
21
+
22
+ if (handle.includes(".")) {
23
+ const suffix = `.${handle}`;
24
+ const suffixMatches = graph.nodes.filter(
25
+ (node) =>
26
+ node.kind !== "file" && node.qualifiedName?.endsWith(suffix) === true,
27
+ );
28
+ if (suffixMatches.length === 1) return { node: suffixMatches[0] };
29
+ if (suffixMatches.length > 1) {
30
+ return { candidates: suffixMatches.slice(0, candidateLimit) };
31
+ }
32
+ }
33
+ return {};
34
+ }