@shvmgyl15/tsgraph 0.1.0 → 0.1.1

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 (42) hide show
  1. package/package.json +16 -1
  2. package/AGENTS.md +0 -64
  3. package/TODOS.md +0 -61
  4. package/opencode.json +0 -24
  5. package/src/analysis/analysis.test.ts +0 -405
  6. package/src/analysis/complexity.ts +0 -107
  7. package/src/analysis/coupling.ts +0 -106
  8. package/src/analysis/hotspot.ts +0 -52
  9. package/src/analysis/index.ts +0 -17
  10. package/src/boundaries/index.test.ts +0 -335
  11. package/src/boundaries/index.ts +0 -137
  12. package/src/changes/index.test.ts +0 -114
  13. package/src/changes/index.ts +0 -95
  14. package/src/cli/index.ts +0 -736
  15. package/src/git/index.test.ts +0 -92
  16. package/src/git/index.ts +0 -86
  17. package/src/graph/types.test.ts +0 -383
  18. package/src/graph/types.ts +0 -353
  19. package/src/mcp/mcp.test.ts +0 -176
  20. package/src/mcp/server.ts +0 -217
  21. package/src/nextjs/index.ts +0 -23
  22. package/src/nextjs/nextjs.test.ts +0 -233
  23. package/src/nextjs/pages.ts +0 -43
  24. package/src/nextjs/react.ts +0 -100
  25. package/src/nextjs/router.ts +0 -102
  26. package/src/nextjs/routes.ts +0 -69
  27. package/src/opencode/index.test.ts +0 -90
  28. package/src/opencode/index.ts +0 -83
  29. package/src/parser/index.ts +0 -339
  30. package/src/parser/parser.test.ts +0 -282
  31. package/src/plan/index.test.ts +0 -162
  32. package/src/plan/index.ts +0 -161
  33. package/src/report/index.ts +0 -128
  34. package/src/scanner/index.ts +0 -97
  35. package/src/scanner/scanner.test.ts +0 -135
  36. package/src/search/index.ts +0 -163
  37. package/src/search/search.test.ts +0 -512
  38. package/src/traversal/index.ts +0 -5
  39. package/src/traversal/traversal.test.ts +0 -266
  40. package/src/traversal/traversal.ts +0 -185
  41. package/tsconfig.json +0 -20
  42. package/vitest.config.ts +0 -7
@@ -1,353 +0,0 @@
1
- export const GRAPH_VERSION = "1";
2
-
3
- export type SymbolKind =
4
- | "function"
5
- | "method"
6
- | "class"
7
- | "interface"
8
- | "type_alias"
9
- | "enum"
10
- | "var"
11
- | "const";
12
-
13
- export type ConcurrencyKind =
14
- | "promise_all"
15
- | "promise_settled"
16
- | "set_timeout"
17
- | "set_interval"
18
- | "abort_signal"
19
- | "async_function";
20
-
21
- export interface StructField {
22
- name: string;
23
- type: string;
24
- tag?: string;
25
- }
26
-
27
- export interface SymbolNode {
28
- id: string;
29
- kind: SymbolKind;
30
- name: string;
31
- receiver?: string;
32
- packageName: string;
33
- file: string;
34
- line: number;
35
- endLine: number;
36
- doc?: string;
37
- signature?: string;
38
- methodSignature?: string;
39
- interfaceMethods?: Record<string, string>;
40
- structFields?: StructField[];
41
- embeddedTypes?: string[];
42
- arity?: number;
43
- isExported: boolean;
44
- isClientComponent?: boolean;
45
- isServerComponent?: boolean;
46
- }
47
-
48
- export interface PackageNode {
49
- id: string;
50
- name: string;
51
- importPathBestEffort: string;
52
- dir: string;
53
- files: string[];
54
- }
55
-
56
- export interface FileNode {
57
- id: string;
58
- path: string;
59
- packageName: string;
60
- lines: number;
61
- generated: boolean;
62
- }
63
-
64
- export interface CallEdge {
65
- callerSymbolId: string;
66
- callerName: string;
67
- calleeRaw: string;
68
- file: string;
69
- line: number;
70
- }
71
-
72
- export interface ImportEdge {
73
- fromFile: string;
74
- fromPackage: string;
75
- importPath: string;
76
- alias?: string;
77
- isDefault: boolean;
78
- }
79
-
80
- export interface Dependency {
81
- module: string;
82
- version: string;
83
- }
84
-
85
- export interface HTTPRoute {
86
- method: string;
87
- path: string;
88
- handler: string;
89
- file: string;
90
- line: number;
91
- }
92
-
93
- export interface EnvRead {
94
- key: string;
95
- accessor: string;
96
- file: string;
97
- line: number;
98
- functionName?: string;
99
- }
100
-
101
- export interface ConcurrencyNode {
102
- kind: ConcurrencyKind;
103
- functionName: string;
104
- file: string;
105
- line: number;
106
- detail?: string;
107
- }
108
-
109
- export interface TestEdge {
110
- testFunc: string;
111
- target: string;
112
- file: string;
113
- line: number;
114
- }
115
-
116
- export interface ImplementsEdge {
117
- interface: string;
118
- concrete: string;
119
- }
120
-
121
- export interface MutationEdge {
122
- field: string;
123
- functionName: string;
124
- file: string;
125
- line: number;
126
- }
127
-
128
- export interface ErrorEdge {
129
- message: string;
130
- functionName: string;
131
- file: string;
132
- line: number;
133
- }
134
-
135
- export interface AppRouterNode {
136
- path: string;
137
- dir: string;
138
- files: {
139
- page?: string;
140
- layout?: string;
141
- loading?: string;
142
- error?: string;
143
- notFound?: string;
144
- route?: string;
145
- template?: string;
146
- default?: string;
147
- };
148
- children: AppRouterNode[];
149
- }
150
-
151
- export interface Graph {
152
- version: string;
153
- generatedAt: string;
154
- root: string;
155
- packages: PackageNode[];
156
- files: FileNode[];
157
- symbols: SymbolNode[];
158
- imports: ImportEdge[];
159
- calls: CallEdge[];
160
- envReads: EnvRead[];
161
- dependencies: Dependency[];
162
- routes: HTTPRoute[];
163
- concurrency: ConcurrencyNode[];
164
- testEdges: TestEdge[];
165
- implements: ImplementsEdge[];
166
- mutations: MutationEdge[];
167
- errors: ErrorEdge[];
168
- appRouter?: AppRouterNode;
169
- }
170
-
171
- let _nextId = 0;
172
- function nextId(): string {
173
- return `gen_${++_nextId}`;
174
- }
175
-
176
- export function makeStructField(overrides?: Partial<StructField>): StructField {
177
- return { name: "", type: "", ...overrides };
178
- }
179
-
180
- export function makeSymbolNode(overrides?: Partial<SymbolNode>): SymbolNode {
181
- return {
182
- id: nextId(),
183
- kind: "function",
184
- name: "",
185
- packageName: "",
186
- file: "",
187
- line: 0,
188
- endLine: 0,
189
- isExported: false,
190
- ...overrides,
191
- };
192
- }
193
-
194
- export function makePackageNode(overrides?: Partial<PackageNode>): PackageNode {
195
- return {
196
- id: nextId(),
197
- name: "",
198
- importPathBestEffort: "",
199
- dir: "",
200
- files: [],
201
- ...overrides,
202
- };
203
- }
204
-
205
- export function makeFileNode(overrides?: Partial<FileNode>): FileNode {
206
- return {
207
- id: nextId(),
208
- path: "",
209
- packageName: "",
210
- lines: 0,
211
- generated: false,
212
- ...overrides,
213
- };
214
- }
215
-
216
- export function makeCallEdge(overrides?: Partial<CallEdge>): CallEdge {
217
- return {
218
- callerSymbolId: "",
219
- callerName: "",
220
- calleeRaw: "",
221
- file: "",
222
- line: 0,
223
- ...overrides,
224
- };
225
- }
226
-
227
- export function makeImportEdge(overrides?: Partial<ImportEdge>): ImportEdge {
228
- return {
229
- fromFile: "",
230
- fromPackage: "",
231
- importPath: "",
232
- isDefault: false,
233
- ...overrides,
234
- };
235
- }
236
-
237
- export function makeDependency(overrides?: Partial<Dependency>): Dependency {
238
- return { module: "", version: "", ...overrides };
239
- }
240
-
241
- export function makeHTTPRoute(overrides?: Partial<HTTPRoute>): HTTPRoute {
242
- return { method: "", path: "", handler: "", file: "", line: 0, ...overrides };
243
- }
244
-
245
- export function makeEnvRead(overrides?: Partial<EnvRead>): EnvRead {
246
- return { key: "", accessor: "", file: "", line: 0, ...overrides };
247
- }
248
-
249
- export function makeConcurrencyNode(overrides?: Partial<ConcurrencyNode>): ConcurrencyNode {
250
- return {
251
- kind: "async_function",
252
- functionName: "",
253
- file: "",
254
- line: 0,
255
- ...overrides,
256
- };
257
- }
258
-
259
- export function makeTestEdge(overrides?: Partial<TestEdge>): TestEdge {
260
- return { testFunc: "", target: "", file: "", line: 0, ...overrides };
261
- }
262
-
263
- export function makeImplementsEdge(overrides?: Partial<ImplementsEdge>): ImplementsEdge {
264
- return { interface: "", concrete: "", ...overrides };
265
- }
266
-
267
- export function makeMutationEdge(overrides?: Partial<MutationEdge>): MutationEdge {
268
- return { field: "", functionName: "", file: "", line: 0, ...overrides };
269
- }
270
-
271
- export function makeErrorEdge(overrides?: Partial<ErrorEdge>): ErrorEdge {
272
- return { message: "", functionName: "", file: "", line: 0, ...overrides };
273
- }
274
-
275
- export function makeAppRouterNode(overrides?: Partial<AppRouterNode>): AppRouterNode {
276
- return {
277
- path: "",
278
- dir: "",
279
- files: {},
280
- children: [],
281
- ...overrides,
282
- };
283
- }
284
-
285
- export function makeGraph(overrides?: Partial<Graph>): Graph {
286
- return {
287
- version: GRAPH_VERSION,
288
- generatedAt: new Date().toISOString(),
289
- root: "",
290
- packages: [],
291
- files: [],
292
- symbols: [],
293
- imports: [],
294
- calls: [],
295
- envReads: [],
296
- dependencies: [],
297
- routes: [],
298
- concurrency: [],
299
- testEdges: [],
300
- implements: [],
301
- mutations: [],
302
- errors: [],
303
- ...overrides,
304
- };
305
- }
306
-
307
- const ARRAY_KEYS: (keyof Graph)[] = [
308
- "packages", "files", "symbols", "imports", "calls",
309
- "envReads", "dependencies", "routes", "concurrency",
310
- "testEdges", "implements", "mutations", "errors",
311
- ];
312
-
313
- function isGraph(value: unknown): value is Graph {
314
- if (value === null || value === undefined) return false;
315
- if (typeof value !== "object") return false;
316
- const obj = value as Record<string, unknown>;
317
- if (typeof obj.version !== "string") return false;
318
- if (typeof obj.generatedAt !== "string") return false;
319
- if (typeof obj.root !== "string") return false;
320
- for (const key of ARRAY_KEYS) {
321
- if (!Array.isArray(obj[key])) return false;
322
- }
323
- return true;
324
- }
325
-
326
- export function serialize(graph: Graph): string {
327
- if (graph.version !== GRAPH_VERSION) {
328
- throw new Error(
329
- `Graph version mismatch: expected ${GRAPH_VERSION}, got ${graph.version}`,
330
- );
331
- }
332
- return JSON.stringify(graph, null, 2);
333
- }
334
-
335
- export function deserialize(json: string): Graph {
336
- let parsed: unknown;
337
- try {
338
- parsed = JSON.parse(json);
339
- } catch (e) {
340
- throw new Error(`Invalid JSON: ${(e as Error).message}`);
341
- }
342
- if (!isGraph(parsed)) {
343
- throw new Error(
344
- "Invalid graph structure: missing or invalid required fields",
345
- );
346
- }
347
- if (parsed.version !== GRAPH_VERSION) {
348
- throw new Error(
349
- `Graph version mismatch: expected ${GRAPH_VERSION}, got ${parsed.version}`,
350
- );
351
- }
352
- return parsed;
353
- }
@@ -1,176 +0,0 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import path from "node:path";
3
- import fs from "node:fs";
4
- import os from "node:os";
5
- import { fileURLToPath } from "node:url";
6
- import { spawn, type ChildProcess } from "node:child_process";
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const projectRoot = path.resolve(__dirname, "../..");
10
- const cliEntry = path.join(projectRoot, "src/cli/index.ts");
11
-
12
- function createTempDir(): string {
13
- return fs.mkdtempSync(path.join(os.tmpdir(), "tsgraph-test-"));
14
- }
15
-
16
- function writeFile(dir: string, relativePath: string, content: string) {
17
- const fullPath = path.join(dir, relativePath);
18
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
19
- fs.writeFileSync(fullPath, content, "utf-8");
20
- }
21
-
22
- let server: ChildProcess | null = null;
23
- let requestId = 0;
24
- let stderrBuf = "";
25
-
26
- function sendRequest(proc: ChildProcess, method: string, params?: unknown): Promise<unknown> {
27
- return new Promise((resolve, reject) => {
28
- const id = ++requestId;
29
- const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} }) + "\n";
30
-
31
- const responses: string[] = [];
32
-
33
- const onData = (chunk: Buffer) => {
34
- const text = chunk.toString("utf-8");
35
- responses.push(text);
36
- const full = responses.join("");
37
- const lines = full.split("\n").filter(Boolean);
38
- // Keep incomplete last chunk
39
- if (!full.endsWith("\n")) {
40
- responses.length = 0;
41
- responses.push(lines.pop() ?? "");
42
- }
43
- for (const line of lines) {
44
- try {
45
- const resp = JSON.parse(line);
46
- if (resp.id === id) {
47
- proc.stdout?.off("data", onData);
48
- resolve(resp);
49
- }
50
- } catch {
51
- // partial or notification, keep waiting
52
- }
53
- }
54
- };
55
-
56
- const onStderr = (chunk: Buffer) => {
57
- stderrBuf += chunk.toString("utf-8");
58
- };
59
-
60
- proc.stdout?.on("data", onData);
61
- proc.stderr?.on("data", onStderr);
62
- proc.stdin?.write(msg);
63
-
64
- setTimeout(() => {
65
- proc.stdout?.off("data", onData);
66
- reject(new Error(`Timeout. stderr: ${stderrBuf.slice(-500)}`));
67
- }, 10000);
68
- });
69
- }
70
-
71
- async function mcpHandshake(proc: ChildProcess): Promise<void> {
72
- const initResp = await sendRequest(proc, "initialize", {
73
- protocolVersion: "2024-11-05",
74
- capabilities: {},
75
- clientInfo: { name: "tsgraph-test", version: "1.0.0" },
76
- }) as Record<string, unknown>;
77
- expect((initResp as { result?: Record<string, unknown> }).result?.protocolVersion).toBeTruthy();
78
- // Send initialized notification
79
- proc.stdin?.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
80
- }
81
-
82
- describe("MCP server", () => {
83
- afterEach(() => {
84
- if (server && !server.killed) {
85
- server.kill();
86
- server = null;
87
- }
88
- });
89
-
90
- it("lists all registered tools", async () => {
91
- const dir = createTempDir();
92
- writeFile(dir, "package.json", JSON.stringify({ name: "test" }));
93
- writeFile(dir, "index.ts", "export function foo() { return 1; }");
94
- fs.mkdirSync(path.join(dir, ".tsgraph"), { recursive: true });
95
-
96
- // Build graph first
97
- const { scanFiles } = await import("../scanner/index.js");
98
- const { parseProject } = await import("../parser/index.js");
99
- const { serialize } = await import("../graph/types.js");
100
- const scanned = scanFiles(dir);
101
- const graph = parseProject(dir, scanned.files);
102
- fs.writeFileSync(path.join(dir, ".tsgraph", "graph.json"), serialize(graph), "utf-8");
103
-
104
- server = spawn("npx", ["tsx", cliEntry, "mcp"], {
105
- cwd: dir,
106
- stdio: ["pipe", "pipe", "pipe"],
107
- });
108
-
109
- await new Promise((r) => setTimeout(r, 500));
110
-
111
- await mcpHandshake(server);
112
-
113
- const resp = await sendRequest(server, "tools/list") as Record<string, unknown>;
114
- expect(resp).toBeTruthy();
115
- expect((resp as { result?: { tools?: unknown[] } }).result?.tools).toBeTruthy();
116
- const tools = (resp as { result: { tools: { name: string }[] } }).result.tools;
117
- const toolNames = tools.map((t) => t.name).sort();
118
- expect(toolNames).toContain("callers");
119
- expect(toolNames).toContain("callees");
120
- expect(toolNames).toContain("node");
121
- expect(toolNames).toContain("query");
122
- expect(toolNames).toContain("context");
123
- expect(toolNames).toContain("imports");
124
- expect(toolNames).toContain("public");
125
- expect(toolNames).toContain("impact");
126
- expect(toolNames).toContain("path");
127
- expect(toolNames).toContain("orphans");
128
- expect(toolNames).toContain("trace");
129
- expect(toolNames).toContain("complexity");
130
- expect(toolNames).toContain("hotspot");
131
- expect(toolNames).toContain("coupling");
132
- expect(toolNames).toHaveLength(14);
133
-
134
- server.kill();
135
- fs.rmSync(dir, { recursive: true });
136
- }, 30000);
137
-
138
- it("calls orphans tool and returns results", async () => {
139
- const dir = createTempDir();
140
- writeFile(dir, "package.json", JSON.stringify({ name: "test" }));
141
- writeFile(dir, "index.ts", "export function foo() { return 1; }");
142
- fs.mkdirSync(path.join(dir, ".tsgraph"), { recursive: true });
143
-
144
- const { scanFiles } = await import("../scanner/index.js");
145
- const { parseProject } = await import("../parser/index.js");
146
- const { serialize } = await import("../graph/types.js");
147
- const scanned = scanFiles(dir);
148
- const graph = parseProject(dir, scanned.files);
149
- fs.writeFileSync(path.join(dir, ".tsgraph", "graph.json"), serialize(graph), "utf-8");
150
-
151
- server = spawn("npx", ["tsx", cliEntry, "mcp"], {
152
- cwd: dir,
153
- stdio: ["pipe", "pipe", "pipe"],
154
- });
155
-
156
- await new Promise((r) => setTimeout(r, 500));
157
-
158
- await mcpHandshake(server);
159
-
160
- const resp = await sendRequest(server, "tools/call", {
161
- name: "orphans",
162
- arguments: {},
163
- }) as Record<string, unknown>;
164
-
165
- expect(resp).toBeTruthy();
166
- const result = (resp as { result?: { content?: { text?: string }[] } }).result;
167
- expect(result).toBeTruthy();
168
- const textContent = result?.content?.[0]?.text;
169
- expect(textContent).toBeTruthy();
170
- const parsed = JSON.parse(textContent!);
171
- expect(Array.isArray(parsed)).toBe(true);
172
-
173
- server.kill();
174
- fs.rmSync(dir, { recursive: true });
175
- }, 30000);
176
- });