@ttsc/graph 0.16.4 → 0.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +24 -53
  3. package/lib/TtscGraphApplication.js +46 -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/createServer.js +1227 -0
  16. package/lib/server/createServer.js.map +1 -0
  17. package/lib/server/instructions.js +32 -0
  18. package/lib/server/instructions.js.map +1 -0
  19. package/lib/server/resolveHandle.js +25 -0
  20. package/lib/server/resolveHandle.js.map +1 -0
  21. package/lib/server/runExpand.js +177 -0
  22. package/lib/server/runExpand.js.map +1 -0
  23. package/lib/server/runIndex.js +146 -0
  24. package/lib/server/runIndex.js.map +1 -0
  25. package/lib/server/runOverview.js +150 -0
  26. package/lib/server/runOverview.js.map +1 -0
  27. package/lib/server/runQuery.js +147 -0
  28. package/lib/server/runQuery.js.map +1 -0
  29. package/lib/server/runTrace.js +219 -0
  30. package/lib/server/runTrace.js.map +1 -0
  31. package/lib/server/startServer.js +21 -0
  32. package/lib/server/startServer.js.map +1 -0
  33. package/lib/structures/ITtscGraphApplication.js +3 -0
  34. package/lib/structures/ITtscGraphApplication.js.map +1 -0
  35. package/lib/structures/ITtscGraphDecorator.js +3 -0
  36. package/lib/structures/ITtscGraphDecorator.js.map +1 -0
  37. package/lib/structures/ITtscGraphDiagnostic.js +3 -0
  38. package/lib/structures/ITtscGraphDiagnostic.js.map +1 -0
  39. package/lib/structures/ITtscGraphDump.js +3 -0
  40. package/lib/structures/ITtscGraphDump.js.map +1 -0
  41. package/lib/structures/ITtscGraphEdge.js +3 -0
  42. package/lib/structures/ITtscGraphEdge.js.map +1 -0
  43. package/lib/structures/ITtscGraphEvidence.js +3 -0
  44. package/lib/structures/ITtscGraphEvidence.js.map +1 -0
  45. package/lib/structures/ITtscGraphExpand.js +3 -0
  46. package/lib/structures/ITtscGraphExpand.js.map +1 -0
  47. package/lib/structures/ITtscGraphIndex.js +3 -0
  48. package/lib/structures/ITtscGraphIndex.js.map +1 -0
  49. package/lib/structures/ITtscGraphNode.js +3 -0
  50. package/lib/structures/ITtscGraphNode.js.map +1 -0
  51. package/lib/structures/ITtscGraphOverview.js +3 -0
  52. package/lib/structures/ITtscGraphOverview.js.map +1 -0
  53. package/lib/structures/ITtscGraphQuery.js +3 -0
  54. package/lib/structures/ITtscGraphQuery.js.map +1 -0
  55. package/lib/structures/ITtscGraphTrace.js +3 -0
  56. package/lib/structures/ITtscGraphTrace.js.map +1 -0
  57. package/lib/structures/TtscGraphEdgeKind.js +3 -0
  58. package/lib/structures/TtscGraphEdgeKind.js.map +1 -0
  59. package/lib/structures/TtscGraphNodeKind.js +3 -0
  60. package/lib/structures/TtscGraphNodeKind.js.map +1 -0
  61. package/lib/structures/TtscGraphNodeModifier.js +3 -0
  62. package/lib/structures/TtscGraphNodeModifier.js.map +1 -0
  63. package/lib/structures/index.js +37 -0
  64. package/lib/structures/index.js.map +1 -0
  65. package/lib/view.js +140 -0
  66. package/lib/view.js.map +1 -0
  67. package/lib/viewer/index.html +123 -0
  68. package/lib/viewer/viewer.js +4610 -0
  69. package/package.json +17 -4
  70. package/src/TtscGraphApplication.ts +57 -0
  71. package/src/index.ts +40 -126
  72. package/src/model/TtscGraphMemory.ts +204 -0
  73. package/src/model/loadGraph.ts +85 -0
  74. package/src/reduce.ts +165 -0
  75. package/src/resolveGraphBinary.ts +40 -0
  76. package/src/server/createServer.ts +104 -0
  77. package/src/server/instructions.ts +28 -0
  78. package/src/server/resolveHandle.ts +34 -0
  79. package/src/server/runExpand.ts +186 -0
  80. package/src/server/runIndex.ts +181 -0
  81. package/src/server/runOverview.ts +154 -0
  82. package/src/server/runQuery.ts +150 -0
  83. package/src/server/runTrace.ts +233 -0
  84. package/src/server/startServer.ts +25 -0
  85. package/src/structures/ITtscGraphApplication.ts +68 -0
  86. package/src/structures/ITtscGraphDecorator.ts +33 -0
  87. package/src/structures/ITtscGraphDiagnostic.ts +34 -0
  88. package/src/structures/ITtscGraphDump.ts +35 -0
  89. package/src/structures/ITtscGraphEdge.ts +24 -0
  90. package/src/structures/ITtscGraphEvidence.ts +29 -0
  91. package/src/structures/ITtscGraphExpand.ts +85 -0
  92. package/src/structures/ITtscGraphIndex.ts +100 -0
  93. package/src/structures/ITtscGraphNode.ts +64 -0
  94. package/src/structures/ITtscGraphOverview.ts +79 -0
  95. package/src/structures/ITtscGraphQuery.ts +49 -0
  96. package/src/structures/ITtscGraphTrace.ts +97 -0
  97. package/src/structures/TtscGraphEdgeKind.ts +23 -0
  98. package/src/structures/TtscGraphNodeKind.ts +26 -0
  99. package/src/structures/TtscGraphNodeModifier.ts +19 -0
  100. package/src/structures/index.ts +21 -0
  101. package/src/view.ts +155 -0
  102. package/src/viewer/index.html +123 -0
  103. package/src/viewer/main.ts +209 -0
  104. package/lib/bin.d.ts +0 -2
  105. package/lib/diagnostics.d.ts +0 -23
  106. package/lib/diagnostics.js +0 -84
  107. package/lib/diagnostics.js.map +0 -1
  108. package/lib/index.d.ts +0 -29
  109. package/src/diagnostics.ts +0 -93
@@ -0,0 +1,64 @@
1
+ import { ITtscGraphDecorator } from "./ITtscGraphDecorator";
2
+ import { ITtscGraphEvidence } from "./ITtscGraphEvidence";
3
+ import { TtscGraphNodeKind } from "./TtscGraphNodeKind";
4
+ import { TtscGraphNodeModifier } from "./TtscGraphNodeModifier";
5
+
6
+ /**
7
+ * One node in the graph: a declared symbol or a structural container (file,
8
+ * package).
9
+ *
10
+ * The `id` is position-invariant: `path#qualifiedName:kind` (e.g.
11
+ * `src/order.ts#OrderService.create:method`), so inserting a line above a
12
+ * declaration does not re-key it. Line and span live in `evidence` and are
13
+ * never part of identity.
14
+ */
15
+ export interface ITtscGraphNode {
16
+ /** Position-invariant identity (see the interface doc for the id grammar). */
17
+ id: string;
18
+
19
+ /** What this node represents. */
20
+ kind: TtscGraphNodeKind;
21
+
22
+ /** The simple, unqualified declared name (`create`, `OrderService`, `App`). */
23
+ name: string;
24
+
25
+ /**
26
+ * The owner-qualified name, when the node lives inside another declaration —
27
+ * `OrderService.create`, `Shopping.ISale`. Absent for a top-level
28
+ * declaration.
29
+ */
30
+ qualifiedName?: string;
31
+
32
+ /** Project-relative path of the file that declares this node. */
33
+ file: string;
34
+
35
+ /**
36
+ * True when the declaration lives outside the workspace (a dependency). The
37
+ * graph keeps the leaf as a named endpoint but does not expand its
38
+ * internals.
39
+ */
40
+ external: boolean;
41
+
42
+ /**
43
+ * True when `file` is git-ignored generated code (a Prisma client, a codegen
44
+ * output). Projections desurface these so generated nodes do not bury the
45
+ * authored graph.
46
+ */
47
+ ignored?: boolean;
48
+
49
+ /** True when the symbol is part of its module's export surface. */
50
+ exported?: boolean;
51
+
52
+ /** Declaration modifiers, when the declaration pass recorded any. */
53
+ modifiers?: TtscGraphNodeModifier[];
54
+
55
+ /**
56
+ * The decorators written on this declaration, in source order, when it has
57
+ * any — raw decorator facts (`@Controller`, `@Get`) a consumer can interpret
58
+ * without re-parsing source.
59
+ */
60
+ decorators?: ITtscGraphDecorator[];
61
+
62
+ /** The declaration span, for display and source expansion. */
63
+ evidence?: ITtscGraphEvidence;
64
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * A compact, source-read-free architecture map of the project — the result of
3
+ * the `graph_overview` tool.
4
+ */
5
+ export interface ITtscGraphOverview {
6
+ /** Absolute project root. */
7
+ project: string;
8
+
9
+ /** Size of the graph. */
10
+ counts: ITtscGraphOverview.ICounts;
11
+
12
+ /** Folder layering, largest first. */
13
+ layers?: ITtscGraphOverview.ILayer[];
14
+
15
+ /** Highest-dependency symbols, busiest first. */
16
+ hotspots?: ITtscGraphOverview.IHotspot[];
17
+
18
+ /** Exported API symbols, most-depended-on first. */
19
+ publicApi?: ITtscGraphOverview.IPublicApi[];
20
+ }
21
+ export namespace ITtscGraphOverview {
22
+ /** Which architecture facets `graph_overview` should return. */
23
+ export interface IProps {
24
+ /**
25
+ * The facet to project, or `all` for every facet. `layers` is the folder
26
+ * layering, `hotspots` the highest-dependency symbols, `publicApi` the
27
+ * exported API symbols ranked by how depended-on they are.
28
+ *
29
+ * @default "all"
30
+ */
31
+ aspect?: "all" | "layers" | "hotspots" | "publicApi";
32
+ }
33
+
34
+ /** Size of the graph by node/edge totals and per-kind node counts. */
35
+ export interface ICounts {
36
+ files: number;
37
+ nodes: number;
38
+ edges: number;
39
+ /** Node count per kind. */
40
+ byKind: Record<string, number>;
41
+ }
42
+
43
+ /** One folder layer: its source files and export surface. */
44
+ export interface ILayer {
45
+ /** Directory, project-relative. */
46
+ dir: string;
47
+ /** Distinct source files under it. */
48
+ files: number;
49
+ /** Exported symbols declared under it. */
50
+ exported: number;
51
+ }
52
+
53
+ /** A high-dependency symbol with its non-structural fan-in and fan-out. */
54
+ export interface IHotspot {
55
+ id: string;
56
+ name: string;
57
+ kind: string;
58
+ file: string;
59
+ /** Non-structural edges pointing at this symbol. */
60
+ fanIn: number;
61
+ /** Non-structural edges leaving this symbol. */
62
+ fanOut: number;
63
+ }
64
+
65
+ /**
66
+ * One symbol on the project's exported public API surface. The list is ranked
67
+ * by how depended-on the symbol is, with test, typings, and generated files
68
+ * excluded, so the names a consumer of the project would reach for surface
69
+ * first — not whichever file happens to declare the most exports.
70
+ */
71
+ export interface IPublicApi {
72
+ /** The exported symbol's name. */
73
+ name: string;
74
+ /** Its declaration kind (`class`, `interface`, `function`, …). */
75
+ kind: string;
76
+ /** Project-relative path of the file that declares it. */
77
+ file: string;
78
+ }
79
+ }
@@ -0,0 +1,49 @@
1
+ /** The ranked hits the `graph_query` tool returns for a natural code query. */
2
+ export interface ITtscGraphQuery {
3
+ hits: ITtscGraphQuery.IHit[];
4
+
5
+ /** Follow-up handles for source or member details. */
6
+ next: ITtscGraphQuery.INext;
7
+ }
8
+ export namespace ITtscGraphQuery {
9
+ /** Find the symbols and clusters most relevant to a natural code query. */
10
+ export interface IProps {
11
+ /**
12
+ * What to find, in natural language and code vocabulary mixed freely — a
13
+ * symbol name, a dotted member (`OrderService.create`), or a phrase
14
+ * (`shopping order create`, `repository find relations`). Exact names are
15
+ * not required; subword and CamelCase matches rank too.
16
+ */
17
+ query: string;
18
+
19
+ /**
20
+ * Maximum hits to return.
21
+ *
22
+ * @default 12
23
+ */
24
+ limit?: number;
25
+ }
26
+
27
+ /** One ranked hit with a handle to follow via `graph_expand` or `graph_trace`. */
28
+ export interface IHit {
29
+ id: string;
30
+ name: string;
31
+ kind: string;
32
+ file: string;
33
+ /** 1-based declaration line, when known. */
34
+ line?: number;
35
+ /**
36
+ * The hit's declaration signature, so you can often answer without
37
+ * expanding.
38
+ */
39
+ signature?: string;
40
+ /** Relative relevance; higher is a better match. */
41
+ score: number;
42
+ }
43
+
44
+ /** Tool-call handles suggested by this index result. */
45
+ export interface INext {
46
+ /** Pass these ids to `graph_expand`, with `source: true` only when needed. */
47
+ expand: string[];
48
+ }
49
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The ordered dependency flow the `graph_trace` tool returns from a start
3
+ * symbol.
4
+ */
5
+ export interface ITtscGraphTrace {
6
+ /** The resolved start node, or undefined when `from` matched nothing. */
7
+ start?: ITtscGraphTrace.INode;
8
+
9
+ direction: string;
10
+
11
+ /** Edges traversed, in breadth-first order. */
12
+ hops: ITtscGraphTrace.IHop[];
13
+
14
+ /** Unique nodes reached (excluding the start), each with its depth and roles. */
15
+ reached: ITtscGraphTrace.INode[];
16
+
17
+ /** True when the trace hit maxNodes or maxDepth and more flow exists. */
18
+ truncated: boolean;
19
+
20
+ /** The resolved `to` target, when a path was requested. */
21
+ target?: ITtscGraphTrace.INode;
22
+
23
+ /**
24
+ * When `to` was given: the ordered dependency path from `from` to `to`
25
+ * (`from` first, `to` last), or empty when `to` is not reachable from
26
+ * `from`.
27
+ */
28
+ path?: ITtscGraphTrace.INode[];
29
+
30
+ /** When `from` was an ambiguous name, the matches to disambiguate with. */
31
+ candidates?: ITtscGraphTrace.INode[];
32
+ }
33
+ export namespace ITtscGraphTrace {
34
+ /** Where and how far to trace dependency flow. */
35
+ export interface IProps {
36
+ /**
37
+ * Where to start: a node id from another tool, a simple symbol name, or a
38
+ * dotted member name such as `OrderService.create`. An ambiguous name
39
+ * returns its candidates instead of a trace.
40
+ */
41
+ from: string;
42
+
43
+ /**
44
+ * A target symbol: node id, simple symbol name, or dotted member name. When
45
+ * given, the tool returns the dependency path from `from` to this target —
46
+ * the one-call answer for "how does A reach B" — instead of an open-ended
47
+ * trace.
48
+ */
49
+ to?: string;
50
+
51
+ /**
52
+ * `forward` follows what the start uses (callees, instantiations, renders);
53
+ * `reverse` follows what uses the start (callers); `impact` is a reverse
54
+ * trace that flags the public API and tests a change would reach.
55
+ *
56
+ * @default "forward"
57
+ */
58
+ direction?: "forward" | "reverse" | "impact";
59
+
60
+ /**
61
+ * How many hops deep to follow.
62
+ *
63
+ * @default 6
64
+ */
65
+ maxDepth?: number;
66
+
67
+ /**
68
+ * Cap on reached nodes; the trace stops and marks itself truncated past it.
69
+ *
70
+ * @default 60
71
+ */
72
+ maxNodes?: number;
73
+ }
74
+
75
+ /** One traversed edge, with its depth from the start. */
76
+ export interface IHop {
77
+ from: string;
78
+ to: string;
79
+ kind: string;
80
+ /** Hops from the start (1 = direct). */
81
+ depth: number;
82
+ }
83
+
84
+ /** A node on the trace: the start, a reached node, or a candidate. */
85
+ export interface INode {
86
+ id: string;
87
+ name: string;
88
+ kind: string;
89
+ file: string;
90
+ /** Hops from the start, on a reached node. */
91
+ depth?: number;
92
+ /** The node's signature — carried on path nodes so the path explains itself. */
93
+ signature?: string;
94
+ /** Why this node matters to an impact trace: `exported`, `test`. */
95
+ roles?: string[];
96
+ }
97
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The relationship a directed edge encodes between two {@link ITtscGraphNode}s.
3
+ *
4
+ * Structural edges (`contains`, `exports`, `imports`) come from the declaration
5
+ * pass. Value and type edges (`calls`, `accesses`, `instantiates`, `type_ref`,
6
+ * `extends`, `implements`, `overrides`, `renders`) are resolved by the checker
7
+ * — `renders` is a JSX component use. `decorates` carries a decorator fact and
8
+ * `tests` a test-to-subject relationship.
9
+ */
10
+ export type TtscGraphEdgeKind =
11
+ | "contains"
12
+ | "exports"
13
+ | "imports"
14
+ | "calls"
15
+ | "accesses"
16
+ | "instantiates"
17
+ | "type_ref"
18
+ | "extends"
19
+ | "implements"
20
+ | "overrides"
21
+ | "decorates"
22
+ | "renders"
23
+ | "tests";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What a graph node represents.
3
+ *
4
+ * The symbol kinds (`file` through `parameter`) are declarations the TypeScript
5
+ * program owns and the checker resolves. `external_symbol` is a
6
+ * dependency-boundary leaf the workspace references but does not declare — the
7
+ * graph keeps it as a named endpoint without expanding the dependency's
8
+ * internals.
9
+ *
10
+ * Used as the `kind` discriminant on {@link ITtscGraphNode}.
11
+ */
12
+ export type TtscGraphNodeKind =
13
+ | "file"
14
+ | "package"
15
+ | "namespace"
16
+ | "module"
17
+ | "function"
18
+ | "class"
19
+ | "interface"
20
+ | "type"
21
+ | "enum"
22
+ | "variable"
23
+ | "method"
24
+ | "property"
25
+ | "parameter"
26
+ | "external_symbol";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A declaration modifier carried on a symbol {@link ITtscGraphNode}, when the
3
+ * declaration pass records it. Used by projections that reason about visibility
4
+ * and shape — e.g. a public-API overview filters on `export`, a class outline
5
+ * separates `static` members.
6
+ */
7
+ export type TtscGraphNodeModifier =
8
+ | "export"
9
+ | "default"
10
+ | "declare"
11
+ | "abstract"
12
+ | "static"
13
+ | "readonly"
14
+ | "async"
15
+ | "const"
16
+ | "public"
17
+ | "private"
18
+ | "protected"
19
+ | "optional";
@@ -0,0 +1,21 @@
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 "./ITtscGraphExpand";
14
+ export * from "./ITtscGraphIndex";
15
+ export * from "./ITtscGraphNode";
16
+ export * from "./ITtscGraphOverview";
17
+ export * from "./ITtscGraphQuery";
18
+ export * from "./ITtscGraphTrace";
19
+ export * from "./TtscGraphEdgeKind";
20
+ export * from "./TtscGraphNodeKind";
21
+ 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>