@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.
- package/LICENSE +21 -21
- package/README.md +24 -53
- package/lib/TtscGraphApplication.js +59 -0
- package/lib/TtscGraphApplication.js.map +1 -0
- package/lib/index.js +31 -108
- package/lib/index.js.map +1 -1
- package/lib/model/TtscGraphMemory.js +185 -0
- package/lib/model/TtscGraphMemory.js.map +1 -0
- package/lib/model/loadGraph.js +418 -0
- package/lib/model/loadGraph.js.map +1 -0
- package/lib/reduce.js +101 -0
- package/lib/reduce.js.map +1 -0
- package/lib/resolveGraphBinary.js +42 -0
- package/lib/resolveGraphBinary.js.map +1 -0
- package/lib/server/accessAliases.js +53 -0
- package/lib/server/accessAliases.js.map +1 -0
- package/lib/server/createServer.js +1726 -0
- package/lib/server/createServer.js.map +1 -0
- package/lib/server/instructions.js +80 -0
- package/lib/server/instructions.js.map +1 -0
- package/lib/server/resolveHandle.js +25 -0
- package/lib/server/resolveHandle.js.map +1 -0
- package/lib/server/runDetails.js +413 -0
- package/lib/server/runDetails.js.map +1 -0
- package/lib/server/runEntrypoints.js +211 -0
- package/lib/server/runEntrypoints.js.map +1 -0
- package/lib/server/runLookup.js +234 -0
- package/lib/server/runLookup.js.map +1 -0
- package/lib/server/runOverview.js +160 -0
- package/lib/server/runOverview.js.map +1 -0
- package/lib/server/runTrace.js +314 -0
- package/lib/server/runTrace.js.map +1 -0
- package/lib/server/startServer.js +21 -0
- package/lib/server/startServer.js.map +1 -0
- package/lib/structures/ITtscGraphApplication.js +3 -0
- package/lib/structures/ITtscGraphApplication.js.map +1 -0
- package/lib/structures/ITtscGraphDecorator.js +3 -0
- package/lib/structures/ITtscGraphDecorator.js.map +1 -0
- package/lib/structures/ITtscGraphDetails.js +3 -0
- package/lib/structures/ITtscGraphDetails.js.map +1 -0
- package/lib/structures/ITtscGraphDiagnostic.js +3 -0
- package/lib/structures/ITtscGraphDiagnostic.js.map +1 -0
- package/lib/structures/ITtscGraphDump.js +3 -0
- package/lib/structures/ITtscGraphDump.js.map +1 -0
- package/lib/structures/ITtscGraphEdge.js +3 -0
- package/lib/structures/ITtscGraphEdge.js.map +1 -0
- package/lib/structures/ITtscGraphEntrypoints.js +3 -0
- package/lib/structures/ITtscGraphEntrypoints.js.map +1 -0
- package/lib/structures/ITtscGraphEscape.js +3 -0
- package/lib/structures/ITtscGraphEscape.js.map +1 -0
- package/lib/structures/ITtscGraphEvidence.js +3 -0
- package/lib/structures/ITtscGraphEvidence.js.map +1 -0
- package/lib/structures/ITtscGraphLookup.js +3 -0
- package/lib/structures/ITtscGraphLookup.js.map +1 -0
- package/lib/structures/ITtscGraphNode.js +3 -0
- package/lib/structures/ITtscGraphNode.js.map +1 -0
- package/lib/structures/ITtscGraphOverview.js +3 -0
- package/lib/structures/ITtscGraphOverview.js.map +1 -0
- package/lib/structures/ITtscGraphTrace.js +3 -0
- package/lib/structures/ITtscGraphTrace.js.map +1 -0
- package/lib/structures/TtscGraphEdgeKind.js +3 -0
- package/lib/structures/TtscGraphEdgeKind.js.map +1 -0
- package/lib/structures/TtscGraphNodeKind.js +3 -0
- package/lib/structures/TtscGraphNodeKind.js.map +1 -0
- package/lib/structures/TtscGraphNodeModifier.js +3 -0
- package/lib/structures/TtscGraphNodeModifier.js.map +1 -0
- package/lib/structures/index.js +38 -0
- package/lib/structures/index.js.map +1 -0
- package/lib/view.js +140 -0
- package/lib/view.js.map +1 -0
- package/lib/viewer/index.html +123 -0
- package/lib/viewer/viewer.js +4610 -0
- package/package.json +17 -4
- package/src/TtscGraphApplication.ts +64 -0
- package/src/index.ts +40 -126
- package/src/model/TtscGraphMemory.ts +204 -0
- package/src/model/loadGraph.ts +85 -0
- package/src/reduce.ts +165 -0
- package/src/resolveGraphBinary.ts +40 -0
- package/src/server/accessAliases.ts +55 -0
- package/src/server/createServer.ts +101 -0
- package/src/server/instructions.ts +76 -0
- package/src/server/resolveHandle.ts +34 -0
- package/src/server/runDetails.ts +469 -0
- package/src/server/runEntrypoints.ts +256 -0
- package/src/server/runLookup.ts +257 -0
- package/src/server/runOverview.ts +166 -0
- package/src/server/runTrace.ts +359 -0
- package/src/server/startServer.ts +25 -0
- package/src/structures/ITtscGraphApplication.ts +116 -0
- package/src/structures/ITtscGraphDecorator.ts +31 -0
- package/src/structures/ITtscGraphDetails.ts +134 -0
- package/src/structures/ITtscGraphDiagnostic.ts +34 -0
- package/src/structures/ITtscGraphDump.ts +35 -0
- package/src/structures/ITtscGraphEdge.ts +24 -0
- package/src/structures/ITtscGraphEntrypoints.ts +118 -0
- package/src/structures/ITtscGraphEscape.ts +39 -0
- package/src/structures/ITtscGraphEvidence.ts +26 -0
- package/src/structures/ITtscGraphLookup.ts +61 -0
- package/src/structures/ITtscGraphNode.ts +70 -0
- package/src/structures/ITtscGraphOverview.ts +87 -0
- package/src/structures/ITtscGraphTrace.ts +140 -0
- package/src/structures/TtscGraphEdgeKind.ts +23 -0
- package/src/structures/TtscGraphNodeKind.ts +26 -0
- package/src/structures/TtscGraphNodeModifier.ts +19 -0
- package/src/structures/index.ts +22 -0
- package/src/view.ts +155 -0
- package/src/viewer/index.html +123 -0
- package/src/viewer/main.ts +209 -0
- package/lib/bin.d.ts +0 -2
- package/lib/diagnostics.d.ts +0 -23
- package/lib/diagnostics.js +0 -84
- package/lib/diagnostics.js.map +0 -1
- package/lib/index.d.ts +0 -29
- 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
|
+
}
|