@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// The canonical graph data model and tool I/O types: the wire contract
|
|
2
|
+
// `ttscgraph dump` emits and the MCP server loads, plus the schemas typia
|
|
3
|
+
// derives the tool surface from. Pure types so typia can build validators and
|
|
4
|
+
// tool schemas at build time, and so the Go `dump.go` writer has one TypeScript
|
|
5
|
+
// source of truth to mirror.
|
|
6
|
+
|
|
7
|
+
export * from "./ITtscGraphApplication";
|
|
8
|
+
export * from "./ITtscGraphDecorator";
|
|
9
|
+
export * from "./ITtscGraphDiagnostic";
|
|
10
|
+
export * from "./ITtscGraphDump";
|
|
11
|
+
export * from "./ITtscGraphEdge";
|
|
12
|
+
export * from "./ITtscGraphEvidence";
|
|
13
|
+
export * from "./ITtscGraphEscape";
|
|
14
|
+
export * from "./ITtscGraphDetails";
|
|
15
|
+
export * from "./ITtscGraphEntrypoints";
|
|
16
|
+
export * from "./ITtscGraphNode";
|
|
17
|
+
export * from "./ITtscGraphOverview";
|
|
18
|
+
export * from "./ITtscGraphLookup";
|
|
19
|
+
export * from "./ITtscGraphTrace";
|
|
20
|
+
export * from "./TtscGraphEdgeKind";
|
|
21
|
+
export * from "./TtscGraphNodeKind";
|
|
22
|
+
export * from "./TtscGraphNodeModifier";
|
package/src/view.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { type RawDump, reduce } from "./reduce";
|
|
7
|
+
import { resolveGraphBinary } from "./resolveGraphBinary";
|
|
8
|
+
|
|
9
|
+
interface ViewOptions {
|
|
10
|
+
cwd: string;
|
|
11
|
+
tsconfig: string;
|
|
12
|
+
port: number;
|
|
13
|
+
open: boolean;
|
|
14
|
+
maxNodes: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseViewArgs(argv: readonly string[]): ViewOptions {
|
|
18
|
+
const opts: ViewOptions = {
|
|
19
|
+
cwd: process.cwd(),
|
|
20
|
+
tsconfig: "tsconfig.json",
|
|
21
|
+
port: 0,
|
|
22
|
+
open: true,
|
|
23
|
+
maxNodes: 1200,
|
|
24
|
+
};
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const arg = argv[i]!;
|
|
27
|
+
if (arg === "--cwd") opts.cwd = argv[++i] ?? opts.cwd;
|
|
28
|
+
else if (arg.startsWith("--cwd=")) opts.cwd = arg.slice("--cwd=".length);
|
|
29
|
+
else if (arg === "--tsconfig" || arg === "-p")
|
|
30
|
+
opts.tsconfig = argv[++i] ?? opts.tsconfig;
|
|
31
|
+
else if (arg.startsWith("--tsconfig="))
|
|
32
|
+
opts.tsconfig = arg.slice("--tsconfig=".length);
|
|
33
|
+
else if (arg === "--port") opts.port = Number(argv[++i]);
|
|
34
|
+
else if (arg.startsWith("--port="))
|
|
35
|
+
opts.port = Number(arg.slice("--port=".length));
|
|
36
|
+
else if (arg === "--no-open") opts.open = false;
|
|
37
|
+
else if (arg === "--max-nodes") opts.maxNodes = Number(argv[++i]);
|
|
38
|
+
else if (arg.startsWith("--max-nodes="))
|
|
39
|
+
opts.maxNodes = Number(arg.slice("--max-nodes=".length));
|
|
40
|
+
}
|
|
41
|
+
return opts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `ttsc-graph view`: build the project's code graph, reduce it, and serve a
|
|
46
|
+
* self-contained 3D viewer on a localhost port, opening the browser. The native
|
|
47
|
+
* binary produces the graph (the same `dump` the docs document); everything
|
|
48
|
+
* else is local and offline. The process stays alive serving until Ctrl+C.
|
|
49
|
+
*/
|
|
50
|
+
export function runView(argv: readonly string[]): number | void {
|
|
51
|
+
const opts = parseViewArgs(argv);
|
|
52
|
+
|
|
53
|
+
const binary = resolveGraphBinary();
|
|
54
|
+
if (binary === null) {
|
|
55
|
+
process.stderr.write(
|
|
56
|
+
"@ttsc/graph: could not resolve the ttscgraph binary. " +
|
|
57
|
+
"Install `ttsc` so its platform package is present, " +
|
|
58
|
+
"or set TTSC_GRAPH_BINARY to an absolute path.\n",
|
|
59
|
+
);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.stderr.write(
|
|
64
|
+
`@ttsc/graph: building the graph for ${opts.cwd} (${opts.tsconfig})...\n`,
|
|
65
|
+
);
|
|
66
|
+
const dump = spawnSync(
|
|
67
|
+
binary,
|
|
68
|
+
["dump", "--cwd", opts.cwd, "--tsconfig", opts.tsconfig],
|
|
69
|
+
{ encoding: "utf8", maxBuffer: 1024 * 1024 * 1024 },
|
|
70
|
+
);
|
|
71
|
+
if (dump.error) {
|
|
72
|
+
process.stderr.write(`@ttsc/graph: ${dump.error.message}\n`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
if (dump.status !== 0) {
|
|
76
|
+
process.stderr.write(dump.stderr || "@ttsc/graph: dump failed\n");
|
|
77
|
+
return dump.status ?? 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let raw: RawDump;
|
|
81
|
+
try {
|
|
82
|
+
raw = JSON.parse(dump.stdout) as RawDump;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
process.stderr.write(
|
|
85
|
+
`@ttsc/graph: could not parse the graph dump: ${String(err)}\n`,
|
|
86
|
+
);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const payload = reduce(raw, { maxNodes: opts.maxNodes });
|
|
91
|
+
payload.project = path.basename(path.resolve(opts.cwd));
|
|
92
|
+
const graphJson = JSON.stringify(payload);
|
|
93
|
+
|
|
94
|
+
const viewerDir = path.join(__dirname, "viewer");
|
|
95
|
+
let indexHtml: Buffer;
|
|
96
|
+
let viewerJs: Buffer;
|
|
97
|
+
try {
|
|
98
|
+
indexHtml = fs.readFileSync(path.join(viewerDir, "index.html"));
|
|
99
|
+
viewerJs = fs.readFileSync(path.join(viewerDir, "viewer.js"));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
process.stderr.write(
|
|
102
|
+
`@ttsc/graph: the bundled viewer is missing (${String(err)}). ` +
|
|
103
|
+
"Reinstall @ttsc/graph.\n",
|
|
104
|
+
);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const server = http.createServer((req, res) => {
|
|
109
|
+
const url = (req.url ?? "/").split("?")[0];
|
|
110
|
+
if (url === "/graph.json") {
|
|
111
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
112
|
+
res.end(graphJson);
|
|
113
|
+
} else if (url === "/viewer.js") {
|
|
114
|
+
res.writeHead(200, {
|
|
115
|
+
"content-type": "application/javascript; charset=utf-8",
|
|
116
|
+
});
|
|
117
|
+
res.end(viewerJs);
|
|
118
|
+
} else {
|
|
119
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
120
|
+
res.end(indexHtml);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
server.listen(opts.port, "127.0.0.1", () => {
|
|
125
|
+
const address = server.address();
|
|
126
|
+
const port =
|
|
127
|
+
typeof address === "object" && address ? address.port : opts.port;
|
|
128
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
129
|
+
const counts = payload.counts;
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
`@ttsc/graph: ${counts.nodes.toLocaleString()} nodes / ${counts.links.toLocaleString()} edges` +
|
|
132
|
+
` (from ${counts.rawNodes.toLocaleString()} / ${counts.rawEdges.toLocaleString()})\n`,
|
|
133
|
+
);
|
|
134
|
+
process.stderr.write(`@ttsc/graph: serving the 3D viewer at ${url}\n`);
|
|
135
|
+
process.stderr.write("@ttsc/graph: press Ctrl+C to stop.\n");
|
|
136
|
+
if (opts.open) openBrowser(url);
|
|
137
|
+
});
|
|
138
|
+
// No return: the listening server keeps the process alive until Ctrl+C.
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Best-effort open the URL in the default browser; the URL is printed anyway. */
|
|
142
|
+
function openBrowser(url: string): void {
|
|
143
|
+
try {
|
|
144
|
+
if (process.platform === "win32")
|
|
145
|
+
spawn("cmd", ["/c", "start", "", url], {
|
|
146
|
+
stdio: "ignore",
|
|
147
|
+
detached: true,
|
|
148
|
+
}).unref();
|
|
149
|
+
else if (process.platform === "darwin")
|
|
150
|
+
spawn("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
151
|
+
else spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
|
|
152
|
+
} catch {
|
|
153
|
+
/* the URL is printed; opening is a convenience */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>ttsc · code graph</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
}
|
|
11
|
+
* {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
html,
|
|
15
|
+
body {
|
|
16
|
+
margin: 0;
|
|
17
|
+
height: 100%;
|
|
18
|
+
background: #0a0c10;
|
|
19
|
+
color: #e6edf3;
|
|
20
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
#app {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
height: 100vh;
|
|
27
|
+
}
|
|
28
|
+
header {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-wrap: wrap;
|
|
31
|
+
align-items: baseline;
|
|
32
|
+
gap: 0 14px;
|
|
33
|
+
padding: 10px 16px;
|
|
34
|
+
border-bottom: 1px solid #222834;
|
|
35
|
+
background: linear-gradient(to bottom, #13171f, #0e1116);
|
|
36
|
+
}
|
|
37
|
+
header .brand {
|
|
38
|
+
font-size: 13px;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
color: #f5f5f5;
|
|
41
|
+
}
|
|
42
|
+
header .brand b {
|
|
43
|
+
color: #36e2ee;
|
|
44
|
+
}
|
|
45
|
+
header #project {
|
|
46
|
+
font-size: 12px;
|
|
47
|
+
color: #9ca3af;
|
|
48
|
+
}
|
|
49
|
+
header #counts {
|
|
50
|
+
margin-left: auto;
|
|
51
|
+
font-size: 11px;
|
|
52
|
+
color: #6b7280;
|
|
53
|
+
}
|
|
54
|
+
#graph {
|
|
55
|
+
position: relative;
|
|
56
|
+
flex: 1;
|
|
57
|
+
min-height: 0;
|
|
58
|
+
}
|
|
59
|
+
#graph .tooltip {
|
|
60
|
+
position: absolute;
|
|
61
|
+
display: none;
|
|
62
|
+
pointer-events: none;
|
|
63
|
+
z-index: 10;
|
|
64
|
+
max-width: 24rem;
|
|
65
|
+
padding: 4px 7px;
|
|
66
|
+
border-radius: 6px;
|
|
67
|
+
background: rgba(12, 14, 19, 0.93);
|
|
68
|
+
border: 1px solid #2a313e;
|
|
69
|
+
font-size: 11px;
|
|
70
|
+
line-height: 1.4;
|
|
71
|
+
}
|
|
72
|
+
#graph .tooltip .muted {
|
|
73
|
+
color: #8b97a8;
|
|
74
|
+
}
|
|
75
|
+
footer {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-wrap: wrap;
|
|
78
|
+
gap: 6px 16px;
|
|
79
|
+
padding: 8px 16px;
|
|
80
|
+
border-top: 1px solid #222834;
|
|
81
|
+
font-size: 10px;
|
|
82
|
+
color: #6b7280;
|
|
83
|
+
}
|
|
84
|
+
footer .dot {
|
|
85
|
+
display: inline-flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 6px;
|
|
88
|
+
}
|
|
89
|
+
footer .swatch {
|
|
90
|
+
display: inline-block;
|
|
91
|
+
width: 16px;
|
|
92
|
+
height: 2px;
|
|
93
|
+
border-radius: 2px;
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
96
|
+
</head>
|
|
97
|
+
<body>
|
|
98
|
+
<div id="app">
|
|
99
|
+
<header>
|
|
100
|
+
<span class="brand"><b>ttsc</b> · code graph</span>
|
|
101
|
+
<span id="project"></span>
|
|
102
|
+
<span id="counts"></span>
|
|
103
|
+
</header>
|
|
104
|
+
<div id="graph"></div>
|
|
105
|
+
<footer>
|
|
106
|
+
<span class="dot"
|
|
107
|
+
><span class="swatch" style="background: #3fb950"></span
|
|
108
|
+
>value-call</span
|
|
109
|
+
>
|
|
110
|
+
<span class="dot"
|
|
111
|
+
><span class="swatch" style="background: #f5b042"></span
|
|
112
|
+
>type-ref</span
|
|
113
|
+
>
|
|
114
|
+
<span class="dot"
|
|
115
|
+
><span class="swatch" style="background: #6ea8ff"></span
|
|
116
|
+
>heritage</span
|
|
117
|
+
>
|
|
118
|
+
<span>node size = connection count · color = declaration kind</span>
|
|
119
|
+
</footer>
|
|
120
|
+
</div>
|
|
121
|
+
<script src="viewer.js"></script>
|
|
122
|
+
</body>
|
|
123
|
+
</html>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Vanilla 3D code-graph viewer bundled into @ttsc/graph by esbuild and served by
|
|
2
|
+
// `ttsc-graph view`. Mirrors website/src/components/graph/GraphViewer3D.tsx, but
|
|
3
|
+
// standalone (no React): fetch the reduced graph the CLI serves, render it on
|
|
4
|
+
// three.js + three-forcegraph, and let the user orbit it.
|
|
5
|
+
import * as THREE from "three";
|
|
6
|
+
import ThreeForceGraph from "three-forcegraph";
|
|
7
|
+
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
8
|
+
|
|
9
|
+
interface GNode {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
kind: string;
|
|
13
|
+
file: string;
|
|
14
|
+
degree: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface GLink {
|
|
18
|
+
source: string;
|
|
19
|
+
target: string;
|
|
20
|
+
kind: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Payload {
|
|
24
|
+
project: string;
|
|
25
|
+
counts: { nodes: number; links: number };
|
|
26
|
+
nodes: GNode[];
|
|
27
|
+
links: GLink[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const NODE_COLORS: Record<string, string> = {
|
|
31
|
+
class: "#36e2ee",
|
|
32
|
+
interface: "#6ea8ff",
|
|
33
|
+
function: "#3fb950",
|
|
34
|
+
method: "#2bb673",
|
|
35
|
+
type: "#f5b042",
|
|
36
|
+
enum: "#c792ea",
|
|
37
|
+
variable: "#8b97a8",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const LINK_COLORS: Record<string, string> = {
|
|
41
|
+
"value-call": "#3fb950",
|
|
42
|
+
"type-ref": "#f5b042",
|
|
43
|
+
heritage: "#6ea8ff",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function escapeHtml(value: string): string {
|
|
47
|
+
return value
|
|
48
|
+
.replace(/&/g, "&")
|
|
49
|
+
.replace(/</g, "<")
|
|
50
|
+
.replace(/>/g, ">");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function setText(id: string, text: string): void {
|
|
54
|
+
const el = document.getElementById(id);
|
|
55
|
+
if (el) el.textContent = text;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main(): Promise<void> {
|
|
59
|
+
const container = document.getElementById("graph");
|
|
60
|
+
if (!container) return;
|
|
61
|
+
|
|
62
|
+
let data: Payload;
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch("graph.json");
|
|
65
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
66
|
+
data = (await res.json()) as Payload;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
container.textContent = `could not load the graph (${String(err)})`;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (data.project) setText("project", data.project);
|
|
73
|
+
setText(
|
|
74
|
+
"counts",
|
|
75
|
+
`${data.nodes.length.toLocaleString()} nodes · ${data.links.length.toLocaleString()} edges`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const width = container.clientWidth || 800;
|
|
79
|
+
const height = container.clientHeight || 600;
|
|
80
|
+
|
|
81
|
+
const scene = new THREE.Scene();
|
|
82
|
+
scene.background = new THREE.Color(0x0a0c10);
|
|
83
|
+
scene.add(new THREE.AmbientLight(0xffffff, 2));
|
|
84
|
+
const keyLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
85
|
+
keyLight.position.set(1, 1, 1);
|
|
86
|
+
scene.add(keyLight);
|
|
87
|
+
|
|
88
|
+
const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1e6);
|
|
89
|
+
camera.position.set(0, 0, 320);
|
|
90
|
+
|
|
91
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
92
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
93
|
+
renderer.setSize(width, height);
|
|
94
|
+
container.appendChild(renderer.domElement);
|
|
95
|
+
|
|
96
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
97
|
+
controls.enableDamping = true;
|
|
98
|
+
controls.dampingFactor = 0.18;
|
|
99
|
+
|
|
100
|
+
const graph = new ThreeForceGraph<GNode, GLink>()
|
|
101
|
+
.nodeId("id")
|
|
102
|
+
.nodeRelSize(4)
|
|
103
|
+
.nodeResolution(12)
|
|
104
|
+
.nodeOpacity(0.95)
|
|
105
|
+
.nodeVal((node) => 1 + Math.sqrt(node.degree))
|
|
106
|
+
.nodeColor((node) => NODE_COLORS[node.kind] ?? "#8b97a8")
|
|
107
|
+
.linkColor((link) => LINK_COLORS[link.kind] ?? "#ffffff55")
|
|
108
|
+
.linkOpacity(0.4)
|
|
109
|
+
.linkWidth(0)
|
|
110
|
+
.warmupTicks(20)
|
|
111
|
+
.cooldownTicks(160);
|
|
112
|
+
scene.add(graph);
|
|
113
|
+
|
|
114
|
+
const fit = (): void => {
|
|
115
|
+
const b = graph.getGraphBbox();
|
|
116
|
+
if (!b) return;
|
|
117
|
+
const cx = (b.x[0] + b.x[1]) / 2;
|
|
118
|
+
const cy = (b.y[0] + b.y[1]) / 2;
|
|
119
|
+
const cz = (b.z[0] + b.z[1]) / 2;
|
|
120
|
+
const radius = Math.max(
|
|
121
|
+
(b.x[1] - b.x[0]) / 2,
|
|
122
|
+
(b.y[1] - b.y[0]) / 2,
|
|
123
|
+
(b.z[1] - b.z[0]) / 2,
|
|
124
|
+
10,
|
|
125
|
+
);
|
|
126
|
+
const dist = radius * 2.6;
|
|
127
|
+
camera.position.set(cx + dist * 0.5, cy + dist * 0.32, cz + dist * 0.8);
|
|
128
|
+
camera.near = Math.max(0.1, dist / 200);
|
|
129
|
+
camera.far = dist * 20;
|
|
130
|
+
camera.updateProjectionMatrix();
|
|
131
|
+
controls.target.set(cx, cy, cz);
|
|
132
|
+
controls.update();
|
|
133
|
+
};
|
|
134
|
+
// `?static` settles the layout, frames it, then stops the render loop, for a
|
|
135
|
+
// low-CPU snapshot (and headless screenshots). Default mode keeps animating.
|
|
136
|
+
const staticMode = new URLSearchParams(window.location.search).has("static");
|
|
137
|
+
let stopAtFrame = -1;
|
|
138
|
+
let frame = 0;
|
|
139
|
+
graph.onEngineStop(() => {
|
|
140
|
+
fit();
|
|
141
|
+
if (staticMode && stopAtFrame < 0) stopAtFrame = frame + 4;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Hover tooltip via raycasting the node objects (each carries __data).
|
|
145
|
+
const tooltip = document.createElement("div");
|
|
146
|
+
tooltip.className = "tooltip";
|
|
147
|
+
container.appendChild(tooltip);
|
|
148
|
+
const raycaster = new THREE.Raycaster();
|
|
149
|
+
const pointer = new THREE.Vector2();
|
|
150
|
+
renderer.domElement.addEventListener("pointermove", (event) => {
|
|
151
|
+
const rect = renderer.domElement.getBoundingClientRect();
|
|
152
|
+
pointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
153
|
+
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
154
|
+
raycaster.setFromCamera(pointer, camera);
|
|
155
|
+
const hits = raycaster.intersectObjects(graph.children, true);
|
|
156
|
+
let node: GNode | null = null;
|
|
157
|
+
for (const hit of hits) {
|
|
158
|
+
let obj: THREE.Object3D | null = hit.object;
|
|
159
|
+
while (obj) {
|
|
160
|
+
const meta = obj as { __graphObjType?: string; __data?: unknown };
|
|
161
|
+
if (meta.__graphObjType === "node" && meta.__data) {
|
|
162
|
+
node = meta.__data as GNode;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
obj = obj.parent;
|
|
166
|
+
}
|
|
167
|
+
if (node) break;
|
|
168
|
+
}
|
|
169
|
+
if (!node) {
|
|
170
|
+
tooltip.style.display = "none";
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
tooltip.style.display = "block";
|
|
174
|
+
tooltip.style.left = `${event.clientX - rect.left + 12}px`;
|
|
175
|
+
tooltip.style.top = `${event.clientY - rect.top + 12}px`;
|
|
176
|
+
tooltip.innerHTML =
|
|
177
|
+
`${escapeHtml(node.name)}<br/>` +
|
|
178
|
+
`<span class="muted">${escapeHtml(node.kind)} · ${escapeHtml(node.file)}</span>`;
|
|
179
|
+
});
|
|
180
|
+
renderer.domElement.addEventListener("pointerleave", () => {
|
|
181
|
+
tooltip.style.display = "none";
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const animate = (): void => {
|
|
185
|
+
if (stopAtFrame >= 0 && frame > stopAtFrame) {
|
|
186
|
+
renderer.render(scene, camera);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
requestAnimationFrame(animate);
|
|
190
|
+
graph.tickFrame();
|
|
191
|
+
controls.update();
|
|
192
|
+
renderer.render(scene, camera);
|
|
193
|
+
frame++;
|
|
194
|
+
};
|
|
195
|
+
animate();
|
|
196
|
+
|
|
197
|
+
window.addEventListener("resize", () => {
|
|
198
|
+
const w = container.clientWidth || width;
|
|
199
|
+
const h = container.clientHeight || height;
|
|
200
|
+
camera.aspect = w / h;
|
|
201
|
+
camera.updateProjectionMatrix();
|
|
202
|
+
renderer.setSize(w, h);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
graph.graphData({ nodes: data.nodes, links: data.links });
|
|
206
|
+
window.setTimeout(fit, 700);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void main();
|
package/lib/bin.d.ts
DELETED
package/lib/diagnostics.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Background worker that computes the project's plugin diagnostics and writes
|
|
3
|
-
* them where the ttscgraph server reads them.
|
|
4
|
-
*
|
|
5
|
-
* The graph binary runs only the TypeScript semantic pass. Everything a plugin
|
|
6
|
-
* adds — `@ttsc/lint` rule violations and transform-plugin (typia, nestia, …)
|
|
7
|
-
* findings — comes from `ttsc`'s own check, which runs whatever plugins the
|
|
8
|
-
* project configured. This worker invokes that check through the public
|
|
9
|
-
* `TtscCompiler`, so nothing here is specific to any plugin: it forwards
|
|
10
|
-
* whatever diagnostics ttsc produces.
|
|
11
|
-
*
|
|
12
|
-
* A native plugin reports its findings with a string `code` (tsc uses numeric
|
|
13
|
-
* codes), so the string-coded diagnostics are exactly the plugin/lint set the
|
|
14
|
-
* graph does not already have. They are serialized with code 0 and the rule
|
|
15
|
-
* folded into the message, the shape ttscgraph's injected-diagnostics provider
|
|
16
|
-
* consumes.
|
|
17
|
-
*
|
|
18
|
-
* Every failure is swallowed: a missing `ttsc`, a go toolchain that cannot
|
|
19
|
-
* build a plugin, a project that does not compile — any of these simply leaves
|
|
20
|
-
* no file, and the graph shows its tsc diagnostics alone. The worker must never
|
|
21
|
-
* be able to break the server it feeds.
|
|
22
|
-
*/
|
|
23
|
-
export declare function runDiagnosticsWorker(argv?: readonly string[]): void;
|
package/lib/diagnostics.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.runDiagnosticsWorker = runDiagnosticsWorker;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_module_1 = require("node:module");
|
|
9
|
-
/**
|
|
10
|
-
* Background worker that computes the project's plugin diagnostics and writes
|
|
11
|
-
* them where the ttscgraph server reads them.
|
|
12
|
-
*
|
|
13
|
-
* The graph binary runs only the TypeScript semantic pass. Everything a plugin
|
|
14
|
-
* adds — `@ttsc/lint` rule violations and transform-plugin (typia, nestia, …)
|
|
15
|
-
* findings — comes from `ttsc`'s own check, which runs whatever plugins the
|
|
16
|
-
* project configured. This worker invokes that check through the public
|
|
17
|
-
* `TtscCompiler`, so nothing here is specific to any plugin: it forwards
|
|
18
|
-
* whatever diagnostics ttsc produces.
|
|
19
|
-
*
|
|
20
|
-
* A native plugin reports its findings with a string `code` (tsc uses numeric
|
|
21
|
-
* codes), so the string-coded diagnostics are exactly the plugin/lint set the
|
|
22
|
-
* graph does not already have. They are serialized with code 0 and the rule
|
|
23
|
-
* folded into the message, the shape ttscgraph's injected-diagnostics provider
|
|
24
|
-
* consumes.
|
|
25
|
-
*
|
|
26
|
-
* Every failure is swallowed: a missing `ttsc`, a go toolchain that cannot
|
|
27
|
-
* build a plugin, a project that does not compile — any of these simply leaves
|
|
28
|
-
* no file, and the graph shows its tsc diagnostics alone. The worker must never
|
|
29
|
-
* be able to break the server it feeds.
|
|
30
|
-
*/
|
|
31
|
-
function runDiagnosticsWorker(argv = process.argv.slice(2)) {
|
|
32
|
-
const [cwd, tsconfig, outPath] = argv;
|
|
33
|
-
if (!cwd || !outPath) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const ttscPackageJson = require.resolve("ttsc/package.json", {
|
|
38
|
-
paths: [cwd],
|
|
39
|
-
});
|
|
40
|
-
const fromProject = (0, node_module_1.createRequire)(ttscPackageJson);
|
|
41
|
-
const { TtscCompiler } = fromProject("ttsc");
|
|
42
|
-
const result = new TtscCompiler({
|
|
43
|
-
cwd,
|
|
44
|
-
tsconfig: tsconfig || undefined,
|
|
45
|
-
}).compile();
|
|
46
|
-
const raw = result &&
|
|
47
|
-
typeof result === "object" &&
|
|
48
|
-
Array.isArray(result.diagnostics)
|
|
49
|
-
? (result.diagnostics)
|
|
50
|
-
: [];
|
|
51
|
-
const out = raw
|
|
52
|
-
.filter((d) => typeof d.file === "string" &&
|
|
53
|
-
(typeof d.line === "number" || typeof d.start === "number"))
|
|
54
|
-
.map((d) => ({
|
|
55
|
-
file: d.file,
|
|
56
|
-
// A byte offset when the structured lane gives one; otherwise null and
|
|
57
|
-
// the server attributes by line. @ttsc/lint and transform plugins reach
|
|
58
|
-
// the result through ttsc's text banner, which carries a line but no
|
|
59
|
-
// offset.
|
|
60
|
-
start: typeof d.start === "number" ? d.start : null,
|
|
61
|
-
line: typeof d.line === "number" ? d.line : 1,
|
|
62
|
-
column: typeof d.character === "number" ? d.character : 1,
|
|
63
|
-
// tsc diagnostics use numeric codes; @ttsc/lint and native plugins hash
|
|
64
|
-
// their rule to a code >= 9000. A rare string id is marked non-tsc (the
|
|
65
|
-
// server then drops the "TS" prefix); the rule name travels in the
|
|
66
|
-
// message regardless.
|
|
67
|
-
code: typeof d.code === "number" ? d.code : 9000,
|
|
68
|
-
message: String(d.messageText ?? ""),
|
|
69
|
-
}));
|
|
70
|
-
// Atomic publish: write to a sibling temp file and rename, so the server
|
|
71
|
-
// never reads a half-written file (a partial read would drop every finding
|
|
72
|
-
// for that query).
|
|
73
|
-
const tmp = `${outPath}.${process.pid}.tmp`;
|
|
74
|
-
node_fs_1.default.writeFileSync(tmp, JSON.stringify(out));
|
|
75
|
-
node_fs_1.default.renameSync(tmp, outPath);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
// Resilient by contract: no file means the graph shows tsc-only diagnostics.
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (require.main === module) {
|
|
82
|
-
runDiagnosticsWorker();
|
|
83
|
-
}
|
|
84
|
-
//# sourceMappingURL=diagnostics.js.map
|
package/lib/diagnostics.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":";;;;;;AAAA,sDAAyB;AACzB,6CAA4C;AAE5C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,8BACE,IAAI,GAAsB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/C,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE;YAC3D,KAAK,EAAE,CAAC,GAAG,CAAC;SACb,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,IAAA,2BAAa,EAAC,eAAe,CAAC,CAAC;QACnD,MAAM,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,MAAM,CAK1C,CAAC;QAEF,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC;YAC9B,GAAG;YACH,QAAQ,EAAE,QAAQ,IAAI,SAAS;SAChC,CAAC,CAAC,OAAO,EAAE,CAAC;QAEb,MAAM,GAAG,GACP,MAAM;YACN,OAAO,MAAM,KAAK,QAAQ;YAC1B,KAAK,CAAC,OAAO,CAAE,MAAoC,CAAC,WAAW,CAAC;YAC9D,CAAC,CAAC,CAAE,MAAqD,CAAC,WAAW,CAAC;YACtE,CAAC,CAAC,EAAE,CAAC;QAET,MAAM,GAAG,GAAG,GAAG;aACZ,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;YAC1B,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAC9D;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,IAAc;YACtB,uEAAuE;YACvE,wEAAwE;YACxE,qEAAqE;YACrE,UAAU;YACV,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,KAAgB,CAAC,CAAC,CAAC,IAAI;YAC/D,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,IAAe,CAAC,CAAC,CAAC,CAAC;YACzD,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,SAAoB,CAAC,CAAC,CAAC,CAAC;YACrE,wEAAwE;YACxE,wEAAwE;YACxE,mEAAmE;YACnE,sBAAsB;YACtB,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,IAAe,CAAC,CAAC,CAAC,IAAI;YAC5D,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC;SACrC,CAAC,CAAC,CAAC;QAEN,yEAAyE;QACzE,2EAA2E;QAC3E,mBAAmB;QACnB,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;QAC5C,iBAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3C,iBAAE,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;IAC/E,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,oBAAoB,EAAE,CAAC;AACzB,CAAC"}
|
package/lib/index.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve the per-platform `ttscgraph` MCP server binary, or `null` when it
|
|
3
|
-
* cannot be located.
|
|
4
|
-
*
|
|
5
|
-
* `ttsc` is a peer the user installs alongside `@ttsc/graph` (not a dependency
|
|
6
|
-
* of this launcher), so resolution starts from the user's project, not from
|
|
7
|
-
* this package's own tree.
|
|
8
|
-
*
|
|
9
|
-
* Resolution order:
|
|
10
|
-
*
|
|
11
|
-
* 1. `TTSC_GRAPH_BINARY` env var, when set to an absolute path.
|
|
12
|
-
* 2. The per-platform npm package `@ttsc/<platform>-<arch>/bin/ttscgraph[.exe]`.
|
|
13
|
-
* That package carries `ttsc`, `ttscserver`, and `ttscgraph` together and is
|
|
14
|
-
* an `optionalDependency` of `ttsc`, so it is resolved from `ttsc`'s
|
|
15
|
-
* location — found from `process.cwd()` (the project where the agent ran the
|
|
16
|
-
* server).
|
|
17
|
-
*/
|
|
18
|
-
export declare function resolveGraphBinary(env?: NodeJS.ProcessEnv, cwd?: string): string | null;
|
|
19
|
-
/**
|
|
20
|
-
* Spawn the resident MCP server, inheriting stdio so the agent's MCP client
|
|
21
|
-
* speaks JSON-RPC to it directly over this process's stdin/stdout. Returns the
|
|
22
|
-
* child's exit code.
|
|
23
|
-
*
|
|
24
|
-
* Before spawning, it kicks off the background diagnostics worker (except in
|
|
25
|
-
* `--connect` proxy mode) and points the server at its output file, so a
|
|
26
|
-
* plugin-using project's lint and transform-plugin diagnostics fuse onto the
|
|
27
|
-
* graph without blocking startup.
|
|
28
|
-
*/
|
|
29
|
-
export declare function runGraph(argv?: readonly string[]): number;
|