depwire-cli 0.1.0

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.
@@ -0,0 +1,3302 @@
1
+ // src/parser/index.ts
2
+ import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
3
+ import { join as join6 } from "path";
4
+
5
+ // src/utils/files.ts
6
+ import { readdirSync, statSync, existsSync, lstatSync } from "fs";
7
+ import { join, relative } from "path";
8
+ function scanDirectory(rootDir, baseDir = rootDir) {
9
+ const files = [];
10
+ try {
11
+ const entries = readdirSync(baseDir);
12
+ for (const entry of entries) {
13
+ const fullPath = join(baseDir, entry);
14
+ if (entry.startsWith(".")) {
15
+ continue;
16
+ }
17
+ if (entry === "node_modules" || entry === "vendor" || entry === "dist" || entry === "build") {
18
+ continue;
19
+ }
20
+ try {
21
+ const stats2 = lstatSync(fullPath);
22
+ if (stats2.isSymbolicLink()) {
23
+ continue;
24
+ }
25
+ } catch (err) {
26
+ continue;
27
+ }
28
+ const stats = statSync(fullPath);
29
+ if (stats.isDirectory()) {
30
+ files.push(...scanDirectory(rootDir, fullPath));
31
+ } else if (stats.isFile()) {
32
+ const isTypeScript = (entry.endsWith(".ts") || entry.endsWith(".tsx")) && !entry.endsWith(".d.ts");
33
+ const isJavaScript = entry.endsWith(".js") || entry.endsWith(".jsx") || entry.endsWith(".mjs") || entry.endsWith(".cjs");
34
+ const isPython = entry.endsWith(".py");
35
+ const isGo = entry.endsWith(".go") && !entry.endsWith("_test.go");
36
+ if (isTypeScript || isJavaScript || isPython || isGo) {
37
+ files.push(relative(rootDir, fullPath));
38
+ }
39
+ }
40
+ }
41
+ } catch (err) {
42
+ console.error(`Error scanning directory ${baseDir}:`, err);
43
+ }
44
+ return files;
45
+ }
46
+ function fileExists(filePath) {
47
+ try {
48
+ return existsSync(filePath) && statSync(filePath).isFile();
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ // src/parser/detect.ts
55
+ import { extname as extname3 } from "path";
56
+
57
+ // src/parser/typescript.ts
58
+ import Parser from "tree-sitter";
59
+ import TypeScript from "tree-sitter-typescript";
60
+
61
+ // src/parser/resolver.ts
62
+ import { join as join2, dirname, resolve, relative as relative2 } from "path";
63
+ import { readFileSync } from "fs";
64
+ var tsconfigCache = /* @__PURE__ */ new Map();
65
+ function loadTsConfig(projectRoot) {
66
+ if (tsconfigCache.has(projectRoot)) {
67
+ return tsconfigCache.get(projectRoot);
68
+ }
69
+ let config = {};
70
+ let currentDir = projectRoot;
71
+ while (currentDir !== dirname(currentDir)) {
72
+ const tsconfigPath = join2(currentDir, "tsconfig.json");
73
+ try {
74
+ const raw = readFileSync(tsconfigPath, "utf-8");
75
+ const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([\]}])/g, "$1");
76
+ const parsed = JSON.parse(stripped);
77
+ if (parsed.compilerOptions) {
78
+ config.baseUrl = parsed.compilerOptions.baseUrl;
79
+ config.paths = parsed.compilerOptions.paths;
80
+ if (config.baseUrl) {
81
+ config.baseUrl = resolve(currentDir, config.baseUrl);
82
+ }
83
+ }
84
+ break;
85
+ } catch (err) {
86
+ currentDir = dirname(currentDir);
87
+ }
88
+ }
89
+ tsconfigCache.set(projectRoot, config);
90
+ return config;
91
+ }
92
+ function expandPathAlias(importPath, tsconfig) {
93
+ if (!tsconfig.paths) return null;
94
+ for (const [pattern, mappings] of Object.entries(tsconfig.paths)) {
95
+ const patternRegex = new RegExp(
96
+ "^" + pattern.replace(/\*/g, "(.*)") + "$"
97
+ );
98
+ const match = importPath.match(patternRegex);
99
+ if (match) {
100
+ const captured = match[1] || "";
101
+ for (const mapping of mappings) {
102
+ const expanded = mapping.replace(/\*/g, captured);
103
+ const baseUrl = tsconfig.baseUrl || ".";
104
+ return join2(baseUrl, expanded);
105
+ }
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ function tryResolve(basePath, projectRoot) {
111
+ const candidates = [];
112
+ if (basePath.endsWith(".js")) {
113
+ candidates.push(basePath.replace(/\.js$/, ".ts"));
114
+ candidates.push(basePath.replace(/\.js$/, ".tsx"));
115
+ candidates.push(basePath);
116
+ } else if (basePath.endsWith(".jsx")) {
117
+ candidates.push(basePath.replace(/\.jsx$/, ".tsx"));
118
+ candidates.push(basePath);
119
+ } else if (basePath.endsWith(".ts") || basePath.endsWith(".tsx")) {
120
+ candidates.push(basePath);
121
+ } else {
122
+ candidates.push(basePath + ".ts");
123
+ candidates.push(basePath + ".tsx");
124
+ candidates.push(join2(basePath, "index.ts"));
125
+ candidates.push(join2(basePath, "index.tsx"));
126
+ candidates.push(basePath);
127
+ }
128
+ for (const candidate of candidates) {
129
+ if (fileExists(candidate)) {
130
+ return relative2(projectRoot, candidate);
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ function resolveImportPath(importPath, fromFile, projectRoot) {
136
+ const tsconfig = loadTsConfig(projectRoot);
137
+ if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
138
+ const expanded = expandPathAlias(importPath, tsconfig);
139
+ if (expanded) {
140
+ return tryResolve(expanded, projectRoot);
141
+ }
142
+ return null;
143
+ }
144
+ const fromDir = dirname(join2(projectRoot, fromFile));
145
+ let resolvedPath;
146
+ if (importPath.startsWith(".")) {
147
+ resolvedPath = resolve(fromDir, importPath);
148
+ } else {
149
+ resolvedPath = resolve(projectRoot, importPath.substring(1));
150
+ }
151
+ return tryResolve(resolvedPath, projectRoot);
152
+ }
153
+
154
+ // src/parser/typescript.ts
155
+ var tsParser = new Parser();
156
+ tsParser.setLanguage(TypeScript.typescript);
157
+ var tsxParser = new Parser();
158
+ tsxParser.setLanguage(TypeScript.tsx);
159
+ function parseTypeScriptFile(filePath, sourceCode, projectRoot) {
160
+ const parser2 = filePath.endsWith(".tsx") ? tsxParser : tsParser;
161
+ const tree = parser2.parse(sourceCode);
162
+ const context = {
163
+ filePath,
164
+ projectRoot,
165
+ sourceCode,
166
+ symbols: [],
167
+ edges: [],
168
+ currentScope: [],
169
+ imports: /* @__PURE__ */ new Map()
170
+ };
171
+ walkNode(tree.rootNode, context);
172
+ return {
173
+ filePath,
174
+ symbols: context.symbols,
175
+ edges: context.edges
176
+ };
177
+ }
178
+ function walkNode(node, context) {
179
+ processNode(node, context);
180
+ for (let i = 0; i < node.childCount; i++) {
181
+ const child = node.child(i);
182
+ if (child) {
183
+ walkNode(child, context);
184
+ }
185
+ }
186
+ }
187
+ function processNode(node, context) {
188
+ const type = node.type;
189
+ switch (type) {
190
+ case "function_declaration":
191
+ processFunctionDeclaration(node, context);
192
+ break;
193
+ case "class_declaration":
194
+ processClassDeclaration(node, context);
195
+ break;
196
+ case "variable_declaration":
197
+ case "lexical_declaration":
198
+ processVariableDeclaration(node, context);
199
+ break;
200
+ case "type_alias_declaration":
201
+ processTypeAliasDeclaration(node, context);
202
+ break;
203
+ case "interface_declaration":
204
+ processInterfaceDeclaration(node, context);
205
+ break;
206
+ case "enum_declaration":
207
+ processEnumDeclaration(node, context);
208
+ break;
209
+ case "import_statement":
210
+ processImportStatement(node, context);
211
+ break;
212
+ case "export_statement":
213
+ processExportStatement(node, context);
214
+ break;
215
+ case "call_expression":
216
+ processCallExpression(node, context);
217
+ break;
218
+ case "new_expression":
219
+ processNewExpression(node, context);
220
+ break;
221
+ }
222
+ }
223
+ function processFunctionDeclaration(node, context) {
224
+ const nameNode = node.childForFieldName("name");
225
+ if (!nameNode) return;
226
+ const name = nameNode.text;
227
+ const exported = isExported(node);
228
+ const startLine = node.startPosition.row + 1;
229
+ const endLine = node.endPosition.row + 1;
230
+ const scope = context.currentScope.length > 0 ? context.currentScope.join(".") : void 0;
231
+ const symbolId = `${context.filePath}::${scope ? scope + "." : ""}${name}`;
232
+ context.symbols.push({
233
+ id: symbolId,
234
+ name,
235
+ kind: "function",
236
+ filePath: context.filePath,
237
+ startLine,
238
+ endLine,
239
+ exported,
240
+ scope
241
+ });
242
+ context.currentScope.push(name);
243
+ const body = node.childForFieldName("body");
244
+ if (body) {
245
+ walkNode(body, context);
246
+ }
247
+ context.currentScope.pop();
248
+ }
249
+ function processClassDeclaration(node, context) {
250
+ const nameNode = node.childForFieldName("name");
251
+ if (!nameNode) return;
252
+ const name = nameNode.text;
253
+ const exported = isExported(node);
254
+ const startLine = node.startPosition.row + 1;
255
+ const endLine = node.endPosition.row + 1;
256
+ const symbolId = `${context.filePath}::${name}`;
257
+ context.symbols.push({
258
+ id: symbolId,
259
+ name,
260
+ kind: "class",
261
+ filePath: context.filePath,
262
+ startLine,
263
+ endLine,
264
+ exported
265
+ });
266
+ for (let i = 0; i < node.childCount; i++) {
267
+ const child = node.child(i);
268
+ if (child && child.type === "class_heritage") {
269
+ const extendsClause = child.childForFieldName("extends");
270
+ if (extendsClause) {
271
+ for (let j = 0; j < extendsClause.childCount; j++) {
272
+ const typeNode = extendsClause.child(j);
273
+ if (typeNode && typeNode.type === "identifier") {
274
+ const targetName = typeNode.text;
275
+ const targetId = `${context.filePath}::${targetName}`;
276
+ context.edges.push({
277
+ source: symbolId,
278
+ target: targetId,
279
+ kind: "extends",
280
+ filePath: context.filePath,
281
+ line: typeNode.startPosition.row + 1
282
+ });
283
+ }
284
+ }
285
+ }
286
+ const implementsClause = child.childForFieldName("implements");
287
+ if (implementsClause) {
288
+ for (let j = 0; j < implementsClause.childCount; j++) {
289
+ const typeNode = implementsClause.child(j);
290
+ if (typeNode && typeNode.type === "type_identifier") {
291
+ const targetName = typeNode.text;
292
+ const targetId = `${context.filePath}::${targetName}`;
293
+ context.edges.push({
294
+ source: symbolId,
295
+ target: targetId,
296
+ kind: "implements",
297
+ filePath: context.filePath,
298
+ line: typeNode.startPosition.row + 1
299
+ });
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+ context.currentScope.push(name);
306
+ const body = node.childForFieldName("body");
307
+ if (body) {
308
+ for (let i = 0; i < body.childCount; i++) {
309
+ const child = body.child(i);
310
+ if (child) {
311
+ if (child.type === "method_definition") {
312
+ processMethodDefinition(child, context);
313
+ } else if (child.type === "public_field_definition" || child.type === "field_definition") {
314
+ processPropertyDefinition(child, context);
315
+ }
316
+ }
317
+ }
318
+ }
319
+ context.currentScope.pop();
320
+ }
321
+ function processMethodDefinition(node, context) {
322
+ const nameNode = node.childForFieldName("name");
323
+ if (!nameNode) return;
324
+ const name = nameNode.text;
325
+ const className = context.currentScope[context.currentScope.length - 1];
326
+ const startLine = node.startPosition.row + 1;
327
+ const endLine = node.endPosition.row + 1;
328
+ const symbolId = `${context.filePath}::${className}.${name}`;
329
+ context.symbols.push({
330
+ id: symbolId,
331
+ name,
332
+ kind: "method",
333
+ filePath: context.filePath,
334
+ startLine,
335
+ endLine,
336
+ exported: false,
337
+ scope: className
338
+ });
339
+ context.currentScope.push(name);
340
+ const body = node.childForFieldName("body");
341
+ if (body) {
342
+ walkNode(body, context);
343
+ }
344
+ context.currentScope.pop();
345
+ }
346
+ function processPropertyDefinition(node, context) {
347
+ const nameNode = node.childForFieldName("name");
348
+ if (!nameNode) return;
349
+ const name = nameNode.text;
350
+ const className = context.currentScope[context.currentScope.length - 1];
351
+ const startLine = node.startPosition.row + 1;
352
+ const endLine = node.endPosition.row + 1;
353
+ const symbolId = `${context.filePath}::${className}.${name}`;
354
+ context.symbols.push({
355
+ id: symbolId,
356
+ name,
357
+ kind: "property",
358
+ filePath: context.filePath,
359
+ startLine,
360
+ endLine,
361
+ exported: false,
362
+ scope: className
363
+ });
364
+ }
365
+ function processVariableDeclaration(node, context) {
366
+ for (let i = 0; i < node.childCount; i++) {
367
+ const child = node.child(i);
368
+ if (child && child.type === "variable_declarator") {
369
+ const nameNode = child.childForFieldName("name");
370
+ if (!nameNode) continue;
371
+ const name = nameNode.text;
372
+ const exported = isExported(node.parent);
373
+ const startLine = child.startPosition.row + 1;
374
+ const endLine = child.endPosition.row + 1;
375
+ const scope = context.currentScope.length > 0 ? context.currentScope.join(".") : void 0;
376
+ const value = child.childForFieldName("value");
377
+ const kind = value && value.type === "arrow_function" ? "function" : "variable";
378
+ const symbolId = `${context.filePath}::${scope ? scope + "." : ""}${name}`;
379
+ context.symbols.push({
380
+ id: symbolId,
381
+ name,
382
+ kind,
383
+ filePath: context.filePath,
384
+ startLine,
385
+ endLine,
386
+ exported,
387
+ scope
388
+ });
389
+ if (kind === "function" && value) {
390
+ context.currentScope.push(name);
391
+ walkNode(value, context);
392
+ context.currentScope.pop();
393
+ }
394
+ }
395
+ }
396
+ }
397
+ function processTypeAliasDeclaration(node, context) {
398
+ const nameNode = node.childForFieldName("name");
399
+ if (!nameNode) return;
400
+ const name = nameNode.text;
401
+ const exported = isExported(node);
402
+ const startLine = node.startPosition.row + 1;
403
+ const endLine = node.endPosition.row + 1;
404
+ const symbolId = `${context.filePath}::${name}`;
405
+ context.symbols.push({
406
+ id: symbolId,
407
+ name,
408
+ kind: "type_alias",
409
+ filePath: context.filePath,
410
+ startLine,
411
+ endLine,
412
+ exported
413
+ });
414
+ }
415
+ function processInterfaceDeclaration(node, context) {
416
+ const nameNode = node.childForFieldName("name");
417
+ if (!nameNode) return;
418
+ const name = nameNode.text;
419
+ const exported = isExported(node);
420
+ const startLine = node.startPosition.row + 1;
421
+ const endLine = node.endPosition.row + 1;
422
+ const symbolId = `${context.filePath}::${name}`;
423
+ context.symbols.push({
424
+ id: symbolId,
425
+ name,
426
+ kind: "interface",
427
+ filePath: context.filePath,
428
+ startLine,
429
+ endLine,
430
+ exported
431
+ });
432
+ }
433
+ function processEnumDeclaration(node, context) {
434
+ const nameNode = node.childForFieldName("name");
435
+ if (!nameNode) return;
436
+ const name = nameNode.text;
437
+ const exported = isExported(node);
438
+ const startLine = node.startPosition.row + 1;
439
+ const endLine = node.endPosition.row + 1;
440
+ const symbolId = `${context.filePath}::${name}`;
441
+ context.symbols.push({
442
+ id: symbolId,
443
+ name,
444
+ kind: "enum",
445
+ filePath: context.filePath,
446
+ startLine,
447
+ endLine,
448
+ exported
449
+ });
450
+ }
451
+ function processImportStatement(node, context) {
452
+ const source = node.childForFieldName("source");
453
+ if (!source) return;
454
+ const importPath = source.text.slice(1, -1);
455
+ const resolvedPath = resolveImportPath(importPath, context.filePath, context.projectRoot);
456
+ const importClause = node.child(1);
457
+ if (!importClause) return;
458
+ const importedNames = [];
459
+ const namedImports = findChildByType(importClause, "named_imports");
460
+ if (namedImports) {
461
+ for (let i = 0; i < namedImports.childCount; i++) {
462
+ const child = namedImports.child(i);
463
+ if (child && child.type === "import_specifier") {
464
+ const identifier2 = findChildByType(child, "identifier");
465
+ if (identifier2) {
466
+ importedNames.push(identifier2.text);
467
+ }
468
+ }
469
+ }
470
+ }
471
+ const identifier = findChildByType(importClause, "identifier");
472
+ if (identifier) {
473
+ importedNames.push(identifier.text);
474
+ }
475
+ const namespaceImport = findChildByType(importClause, "namespace_import");
476
+ if (namespaceImport) {
477
+ const alias = findChildByType(namespaceImport, "identifier");
478
+ if (alias) {
479
+ importedNames.push(alias.text);
480
+ }
481
+ }
482
+ if (resolvedPath) {
483
+ const currentSymbolId = getCurrentSymbolId(context);
484
+ for (const importedName of importedNames) {
485
+ const targetId = `${resolvedPath}::${importedName}`;
486
+ context.imports.set(importedName, targetId);
487
+ context.edges.push({
488
+ source: currentSymbolId || `${context.filePath}::__file__`,
489
+ target: targetId,
490
+ kind: "imports",
491
+ filePath: context.filePath,
492
+ line: node.startPosition.row + 1
493
+ });
494
+ }
495
+ }
496
+ }
497
+ function processExportStatement(node, context) {
498
+ const source = node.childForFieldName("source");
499
+ if (source) {
500
+ const importPath = source.text.slice(1, -1);
501
+ const resolvedPath = resolveImportPath(importPath, context.filePath, context.projectRoot);
502
+ const exportClause = node.child(1);
503
+ if (exportClause && resolvedPath) {
504
+ const exportedNames = [];
505
+ for (let i = 0; i < exportClause.childCount; i++) {
506
+ const child = exportClause.child(i);
507
+ if (child && child.type === "export_specifier") {
508
+ const identifier = findChildByType(child, "identifier");
509
+ if (identifier) {
510
+ exportedNames.push(identifier.text);
511
+ }
512
+ }
513
+ }
514
+ const currentSymbolId = getCurrentSymbolId(context);
515
+ const startLine = node.startPosition.row + 1;
516
+ const endLine = node.endPosition.row + 1;
517
+ for (const exportedName of exportedNames) {
518
+ const symbolId = `${context.filePath}::${exportedName}`;
519
+ context.symbols.push({
520
+ id: symbolId,
521
+ name: exportedName,
522
+ kind: "export",
523
+ filePath: context.filePath,
524
+ startLine,
525
+ endLine,
526
+ exported: true
527
+ });
528
+ const targetId = `${resolvedPath}::${exportedName}`;
529
+ context.edges.push({
530
+ source: symbolId,
531
+ target: targetId,
532
+ kind: "imports",
533
+ filePath: context.filePath,
534
+ line: startLine
535
+ });
536
+ }
537
+ }
538
+ }
539
+ }
540
+ function processCallExpression(node, context) {
541
+ const functionNode = node.childForFieldName("function");
542
+ if (!functionNode) return;
543
+ let functionName = null;
544
+ if (functionNode.type === "identifier") {
545
+ functionName = functionNode.text;
546
+ } else if (functionNode.type === "member_expression") {
547
+ const property = functionNode.childForFieldName("property");
548
+ if (property) {
549
+ functionName = property.text;
550
+ }
551
+ }
552
+ if (functionName) {
553
+ const currentSymbolId = getCurrentSymbolId(context);
554
+ if (currentSymbolId) {
555
+ let targetId;
556
+ if (context.imports.has(functionName)) {
557
+ targetId = context.imports.get(functionName);
558
+ } else {
559
+ targetId = `${context.filePath}::${functionName}`;
560
+ }
561
+ context.edges.push({
562
+ source: currentSymbolId,
563
+ target: targetId,
564
+ kind: "calls",
565
+ filePath: context.filePath,
566
+ line: node.startPosition.row + 1
567
+ });
568
+ }
569
+ }
570
+ }
571
+ function processNewExpression(node, context) {
572
+ const classNode = node.child(1);
573
+ if (!classNode || classNode.type !== "identifier") return;
574
+ const className = classNode.text;
575
+ const currentSymbolId = getCurrentSymbolId(context);
576
+ if (currentSymbolId) {
577
+ const targetId = `${context.filePath}::${className}`;
578
+ context.edges.push({
579
+ source: currentSymbolId,
580
+ target: targetId,
581
+ kind: "calls",
582
+ filePath: context.filePath,
583
+ line: node.startPosition.row + 1
584
+ });
585
+ }
586
+ }
587
+ function isExported(node) {
588
+ if (!node) return false;
589
+ if (node.type === "export_statement") return true;
590
+ for (let i = 0; i < node.childCount; i++) {
591
+ const child = node.child(i);
592
+ if (child && child.type === "export") return true;
593
+ }
594
+ return isExported(node.parent);
595
+ }
596
+ function findChildByType(node, type) {
597
+ for (let i = 0; i < node.childCount; i++) {
598
+ const child = node.child(i);
599
+ if (child && child.type === type) {
600
+ return child;
601
+ }
602
+ }
603
+ return null;
604
+ }
605
+ function getCurrentSymbolId(context) {
606
+ if (context.currentScope.length === 0) return null;
607
+ return `${context.filePath}::${context.currentScope.join(".")}`;
608
+ }
609
+ var typescriptParser = {
610
+ name: "typescript",
611
+ extensions: [".ts", ".tsx"],
612
+ parseFile: parseTypeScriptFile
613
+ };
614
+
615
+ // src/parser/python.ts
616
+ import Parser2 from "tree-sitter";
617
+ import Python from "tree-sitter-python";
618
+ import { dirname as dirname2, join as join3 } from "path";
619
+ import { existsSync as existsSync2 } from "fs";
620
+ var pyParser = new Parser2();
621
+ pyParser.setLanguage(Python);
622
+ function parsePythonFile(filePath, sourceCode, projectRoot) {
623
+ const tree = pyParser.parse(sourceCode);
624
+ const context = {
625
+ filePath,
626
+ projectRoot,
627
+ sourceCode,
628
+ symbols: [],
629
+ edges: [],
630
+ currentScope: [],
631
+ currentClass: null,
632
+ imports: /* @__PURE__ */ new Map()
633
+ };
634
+ walkNode2(tree.rootNode, context);
635
+ return {
636
+ filePath,
637
+ symbols: context.symbols,
638
+ edges: context.edges
639
+ };
640
+ }
641
+ function walkNode2(node, context) {
642
+ processNode2(node, context);
643
+ for (let i = 0; i < node.childCount; i++) {
644
+ const child = node.child(i);
645
+ if (child) {
646
+ walkNode2(child, context);
647
+ }
648
+ }
649
+ }
650
+ function processNode2(node, context) {
651
+ const type = node.type;
652
+ switch (type) {
653
+ case "function_definition":
654
+ processFunctionDefinition(node, context);
655
+ break;
656
+ case "class_definition":
657
+ processClassDefinition(node, context);
658
+ break;
659
+ case "expression_statement":
660
+ processExpressionStatement(node, context);
661
+ break;
662
+ case "import_statement":
663
+ processImportStatement2(node, context);
664
+ break;
665
+ case "import_from_statement":
666
+ processImportFromStatement(node, context);
667
+ break;
668
+ case "decorated_definition":
669
+ processDecoratedDefinition(node, context);
670
+ break;
671
+ case "call":
672
+ processCallExpression2(node, context);
673
+ break;
674
+ }
675
+ }
676
+ function processFunctionDefinition(node, context) {
677
+ const nameNode = findChildByType2(node, "identifier");
678
+ if (!nameNode) return;
679
+ const name = nodeText(nameNode, context);
680
+ const isAsync = node.text.startsWith("async ");
681
+ const kind = context.currentClass ? "method" : "function";
682
+ const scope = context.currentClass || void 0;
683
+ const exported = context.currentScope.length === 0 && !context.currentClass;
684
+ const symbolId = `${context.filePath}::${name}`;
685
+ context.symbols.push({
686
+ id: symbolId,
687
+ name,
688
+ kind,
689
+ filePath: context.filePath,
690
+ startLine: node.startPosition.row + 1,
691
+ endLine: node.endPosition.row + 1,
692
+ exported,
693
+ scope
694
+ });
695
+ context.currentScope.push(name);
696
+ const body = findChildByType2(node, "block");
697
+ if (body) {
698
+ walkNode2(body, context);
699
+ }
700
+ context.currentScope.pop();
701
+ }
702
+ function processClassDefinition(node, context) {
703
+ const nameNode = findChildByType2(node, "identifier");
704
+ if (!nameNode) return;
705
+ const name = nodeText(nameNode, context);
706
+ const exported = context.currentScope.length === 0;
707
+ const symbolId = `${context.filePath}::${name}`;
708
+ context.symbols.push({
709
+ id: symbolId,
710
+ name,
711
+ kind: "class",
712
+ filePath: context.filePath,
713
+ startLine: node.startPosition.row + 1,
714
+ endLine: node.endPosition.row + 1,
715
+ exported
716
+ });
717
+ const argumentList = findChildByType2(node, "argument_list");
718
+ if (argumentList) {
719
+ for (let i = 0; i < argumentList.childCount; i++) {
720
+ const arg = argumentList.child(i);
721
+ if (arg && (arg.type === "identifier" || arg.type === "attribute")) {
722
+ const baseName = nodeText(arg, context);
723
+ const baseId = resolveSymbol(baseName, context);
724
+ if (baseId) {
725
+ context.edges.push({
726
+ source: symbolId,
727
+ target: baseId,
728
+ kind: "inherits",
729
+ filePath: context.filePath,
730
+ line: arg.startPosition.row + 1
731
+ });
732
+ }
733
+ }
734
+ }
735
+ }
736
+ const oldClass = context.currentClass;
737
+ context.currentClass = name;
738
+ context.currentScope.push(name);
739
+ const body = findChildByType2(node, "block");
740
+ if (body) {
741
+ walkNode2(body, context);
742
+ }
743
+ context.currentScope.pop();
744
+ context.currentClass = oldClass;
745
+ }
746
+ function processExpressionStatement(node, context) {
747
+ if (context.currentScope.length > 0) return;
748
+ const assignment = findChildByType2(node, "assignment");
749
+ if (!assignment) return;
750
+ const left = assignment.child(0);
751
+ if (!left || left.type !== "identifier") return;
752
+ const name = nodeText(left, context);
753
+ const isConstant = name === name.toUpperCase() && name.length > 1;
754
+ const kind = isConstant ? "constant" : "variable";
755
+ const symbolId = `${context.filePath}::${name}`;
756
+ context.symbols.push({
757
+ id: symbolId,
758
+ name,
759
+ kind,
760
+ filePath: context.filePath,
761
+ startLine: node.startPosition.row + 1,
762
+ endLine: node.endPosition.row + 1,
763
+ exported: true
764
+ // Module-level variables are exported
765
+ });
766
+ }
767
+ function processImportStatement2(node, context) {
768
+ const dottedName = findChildByType2(node, "dotted_name");
769
+ const identifier = findChildByType2(node, "identifier");
770
+ const moduleName = dottedName ? nodeText(dottedName, context) : identifier ? nodeText(identifier, context) : null;
771
+ if (!moduleName) return;
772
+ const aliasedImport = findChildByType2(node, "aliased_import");
773
+ let importedName = moduleName;
774
+ if (aliasedImport) {
775
+ const asNode = aliasedImport.childForFieldName("alias");
776
+ if (asNode) {
777
+ importedName = nodeText(asNode, context);
778
+ }
779
+ }
780
+ const resolvedPath = resolveImportPath2(moduleName, context.filePath, context.projectRoot);
781
+ if (resolvedPath) {
782
+ const targetId = `${resolvedPath}::__module__`;
783
+ const sourceId = `${context.filePath}::__file__`;
784
+ context.imports.set(importedName, targetId);
785
+ context.edges.push({
786
+ source: sourceId,
787
+ target: targetId,
788
+ kind: "imports",
789
+ filePath: context.filePath,
790
+ line: node.startPosition.row + 1
791
+ });
792
+ }
793
+ }
794
+ function processImportFromStatement(node, context) {
795
+ const moduleNode = node.childForFieldName("module_name");
796
+ if (!moduleNode) return;
797
+ const moduleName = nodeText(moduleNode, context);
798
+ const importedNames = [];
799
+ for (let i = 0; i < node.childCount; i++) {
800
+ const child = node.child(i);
801
+ if (!child) continue;
802
+ if (child.type === "dotted_name" || child.type === "identifier") {
803
+ const prevSibling = node.child(i - 1);
804
+ if (prevSibling && prevSibling.text === "import") {
805
+ importedNames.push(nodeText(child, context));
806
+ }
807
+ }
808
+ if (child.type === "aliased_import") {
809
+ const nameNode = child.childForFieldName("name");
810
+ if (nameNode) {
811
+ importedNames.push(nodeText(nameNode, context));
812
+ }
813
+ }
814
+ }
815
+ const resolvedPath = resolveImportPath2(moduleName, context.filePath, context.projectRoot);
816
+ if (resolvedPath) {
817
+ const sourceId = `${context.filePath}::__file__`;
818
+ for (const importedName of importedNames) {
819
+ if (importedName === "*") continue;
820
+ const targetId = `${resolvedPath}::${importedName}`;
821
+ context.imports.set(importedName, targetId);
822
+ context.edges.push({
823
+ source: sourceId,
824
+ target: targetId,
825
+ kind: "imports",
826
+ filePath: context.filePath,
827
+ line: node.startPosition.row + 1
828
+ });
829
+ }
830
+ }
831
+ }
832
+ function processDecoratedDefinition(node, context) {
833
+ const decorators = [];
834
+ for (let i = 0; i < node.childCount; i++) {
835
+ const child = node.child(i);
836
+ if (child && child.type === "decorator") {
837
+ const decoratorName = extractDecoratorName(child, context);
838
+ if (decoratorName) {
839
+ decorators.push(decoratorName);
840
+ }
841
+ }
842
+ }
843
+ const definition = findChildByType2(node, "function_definition") || findChildByType2(node, "class_definition");
844
+ if (definition) {
845
+ processNode2(definition, context);
846
+ const nameNode = findChildByType2(definition, "identifier");
847
+ if (nameNode) {
848
+ const targetName = nodeText(nameNode, context);
849
+ const targetId = `${context.filePath}::${targetName}`;
850
+ for (const decoratorName of decorators) {
851
+ const decoratorId = resolveSymbol(decoratorName, context);
852
+ if (decoratorId) {
853
+ context.edges.push({
854
+ source: decoratorId,
855
+ target: targetId,
856
+ kind: "decorates",
857
+ filePath: context.filePath,
858
+ line: node.startPosition.row + 1
859
+ });
860
+ }
861
+ }
862
+ }
863
+ }
864
+ }
865
+ function processCallExpression2(node, context) {
866
+ const functionNode = node.childForFieldName("function");
867
+ if (!functionNode) return;
868
+ let calleeName;
869
+ if (functionNode.type === "identifier") {
870
+ calleeName = nodeText(functionNode, context);
871
+ } else if (functionNode.type === "attribute") {
872
+ const attrNode = functionNode.childForFieldName("attribute");
873
+ if (!attrNode) return;
874
+ calleeName = nodeText(attrNode, context);
875
+ } else {
876
+ return;
877
+ }
878
+ const builtins = ["print", "len", "str", "int", "float", "list", "dict", "set", "tuple", "range", "enumerate", "zip", "map", "filter", "open", "type", "isinstance", "hasattr", "getattr", "setattr"];
879
+ if (builtins.includes(calleeName)) return;
880
+ const callerId = getCurrentSymbolId2(context);
881
+ if (!callerId) return;
882
+ const calleeId = resolveSymbol(calleeName, context);
883
+ if (calleeId) {
884
+ context.edges.push({
885
+ source: callerId,
886
+ target: calleeId,
887
+ kind: "calls",
888
+ filePath: context.filePath,
889
+ line: node.startPosition.row + 1
890
+ });
891
+ }
892
+ }
893
+ function resolveImportPath2(moduleName, currentFile, projectRoot) {
894
+ if (moduleName.startsWith(".")) {
895
+ const currentDir = dirname2(join3(projectRoot, currentFile));
896
+ let level = 0;
897
+ while (moduleName[level] === ".") level++;
898
+ let targetDir = currentDir;
899
+ for (let i = 0; i < level - 1; i++) {
900
+ targetDir = dirname2(targetDir);
901
+ }
902
+ const relativeModule = moduleName.substring(level);
903
+ if (relativeModule) {
904
+ const modulePath2 = relativeModule.replace(/\./g, "/");
905
+ const candidates2 = [
906
+ join3(targetDir, `${modulePath2}.py`),
907
+ join3(targetDir, modulePath2, "__init__.py")
908
+ ];
909
+ for (const candidate of candidates2) {
910
+ if (existsSync2(candidate)) {
911
+ return candidate.substring(projectRoot.length + 1);
912
+ }
913
+ }
914
+ } else {
915
+ const initPath = join3(targetDir, "__init__.py");
916
+ if (existsSync2(initPath)) {
917
+ return initPath.substring(projectRoot.length + 1);
918
+ }
919
+ }
920
+ return null;
921
+ }
922
+ const modulePath = moduleName.replace(/\./g, "/");
923
+ const candidates = [
924
+ join3(projectRoot, `${modulePath}.py`),
925
+ join3(projectRoot, modulePath, "__init__.py")
926
+ ];
927
+ for (const candidate of candidates) {
928
+ if (existsSync2(candidate)) {
929
+ return candidate.substring(projectRoot.length + 1);
930
+ }
931
+ }
932
+ return null;
933
+ }
934
+ function resolveSymbol(name, context) {
935
+ if (context.imports.has(name)) {
936
+ return context.imports.get(name) || null;
937
+ }
938
+ const currentFileId = `${context.filePath}::${name}`;
939
+ const symbol = context.symbols.find((s) => s.id === currentFileId);
940
+ if (symbol) {
941
+ return currentFileId;
942
+ }
943
+ if (context.currentClass) {
944
+ const classMethodId = `${context.filePath}::${context.currentClass}.${name}`;
945
+ const classMethod = context.symbols.find((s) => s.id === classMethodId);
946
+ if (classMethod) {
947
+ return classMethodId;
948
+ }
949
+ }
950
+ return null;
951
+ }
952
+ function extractDecoratorName(node, context) {
953
+ const identifier = findChildByType2(node, "identifier");
954
+ const attribute = findChildByType2(node, "attribute");
955
+ if (attribute) {
956
+ return nodeText(attribute, context);
957
+ } else if (identifier) {
958
+ return nodeText(identifier, context);
959
+ }
960
+ return null;
961
+ }
962
+ function findChildByType2(node, type) {
963
+ for (let i = 0; i < node.childCount; i++) {
964
+ const child = node.child(i);
965
+ if (child && child.type === type) {
966
+ return child;
967
+ }
968
+ }
969
+ return null;
970
+ }
971
+ function nodeText(node, context) {
972
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
973
+ }
974
+ function getCurrentSymbolId2(context) {
975
+ if (context.currentScope.length === 0) return null;
976
+ return `${context.filePath}::${context.currentScope.join(".")}`;
977
+ }
978
+ var pythonParser = {
979
+ name: "python",
980
+ extensions: [".py"],
981
+ parseFile: parsePythonFile
982
+ };
983
+
984
+ // src/parser/javascript.ts
985
+ import Parser3 from "tree-sitter";
986
+ import JavaScript from "tree-sitter-javascript";
987
+ import { existsSync as existsSync3 } from "fs";
988
+ import { join as join4, dirname as dirname3, extname as extname2 } from "path";
989
+ var jsParser = new Parser3();
990
+ jsParser.setLanguage(JavaScript);
991
+ function parseJavaScriptFile(filePath, sourceCode, projectRoot) {
992
+ const tree = jsParser.parse(sourceCode);
993
+ const context = {
994
+ filePath,
995
+ projectRoot,
996
+ sourceCode,
997
+ symbols: [],
998
+ edges: [],
999
+ currentScope: [],
1000
+ imports: /* @__PURE__ */ new Map(),
1001
+ isJSX: filePath.endsWith(".jsx")
1002
+ };
1003
+ walkNode3(tree.rootNode, context);
1004
+ return {
1005
+ filePath,
1006
+ symbols: context.symbols,
1007
+ edges: context.edges
1008
+ };
1009
+ }
1010
+ function walkNode3(node, context) {
1011
+ processNode3(node, context);
1012
+ for (let i = 0; i < node.childCount; i++) {
1013
+ const child = node.child(i);
1014
+ if (child) {
1015
+ walkNode3(child, context);
1016
+ }
1017
+ }
1018
+ }
1019
+ function processNode3(node, context) {
1020
+ const type = node.type;
1021
+ switch (type) {
1022
+ case "function_declaration":
1023
+ processFunctionDeclaration2(node, context);
1024
+ break;
1025
+ case "function":
1026
+ processFunctionExpression(node, context);
1027
+ break;
1028
+ case "class_declaration":
1029
+ processClassDeclaration2(node, context);
1030
+ break;
1031
+ case "method_definition":
1032
+ processMethodDefinition2(node, context);
1033
+ break;
1034
+ case "lexical_declaration":
1035
+ case "variable_declaration":
1036
+ processVariableDeclaration2(node, context);
1037
+ break;
1038
+ case "import_statement":
1039
+ processImportStatement3(node, context);
1040
+ break;
1041
+ case "export_statement":
1042
+ processExportStatement2(node, context);
1043
+ break;
1044
+ case "call_expression":
1045
+ processCallExpression3(node, context);
1046
+ break;
1047
+ case "new_expression":
1048
+ processNewExpression2(node, context);
1049
+ break;
1050
+ case "jsx_element":
1051
+ case "jsx_self_closing_element":
1052
+ if (context.isJSX) {
1053
+ processJSXElement(node, context);
1054
+ }
1055
+ break;
1056
+ }
1057
+ }
1058
+ function processFunctionDeclaration2(node, context) {
1059
+ const nameNode = findChildByType3(node, "identifier");
1060
+ if (!nameNode) return;
1061
+ const name = nodeText2(nameNode, context);
1062
+ const exported = isExported2(node.parent);
1063
+ const symbolId = `${context.filePath}::${name}`;
1064
+ context.symbols.push({
1065
+ id: symbolId,
1066
+ name,
1067
+ kind: "function",
1068
+ filePath: context.filePath,
1069
+ startLine: node.startPosition.row + 1,
1070
+ endLine: node.endPosition.row + 1,
1071
+ exported
1072
+ });
1073
+ context.currentScope.push(name);
1074
+ const body = findChildByType3(node, "statement_block");
1075
+ if (body) {
1076
+ walkNode3(body, context);
1077
+ }
1078
+ context.currentScope.pop();
1079
+ }
1080
+ function processFunctionExpression(node, context) {
1081
+ if (node.parent && node.parent.type === "variable_declarator") {
1082
+ const nameNode = node.parent.childForFieldName("name");
1083
+ if (nameNode && nameNode.type === "identifier") {
1084
+ const name = nodeText2(nameNode, context);
1085
+ const exported = isExported2(node.parent.parent?.parent || null);
1086
+ const symbolId = `${context.filePath}::${name}`;
1087
+ context.symbols.push({
1088
+ id: symbolId,
1089
+ name,
1090
+ kind: "function",
1091
+ filePath: context.filePath,
1092
+ startLine: node.startPosition.row + 1,
1093
+ endLine: node.endPosition.row + 1,
1094
+ exported
1095
+ });
1096
+ context.currentScope.push(name);
1097
+ const body = findChildByType3(node, "statement_block");
1098
+ if (body) {
1099
+ walkNode3(body, context);
1100
+ }
1101
+ context.currentScope.pop();
1102
+ }
1103
+ }
1104
+ }
1105
+ function processClassDeclaration2(node, context) {
1106
+ const nameNode = findChildByType3(node, "identifier");
1107
+ if (!nameNode) return;
1108
+ const name = nodeText2(nameNode, context);
1109
+ const exported = isExported2(node.parent);
1110
+ const symbolId = `${context.filePath}::${name}`;
1111
+ context.symbols.push({
1112
+ id: symbolId,
1113
+ name,
1114
+ kind: "class",
1115
+ filePath: context.filePath,
1116
+ startLine: node.startPosition.row + 1,
1117
+ endLine: node.endPosition.row + 1,
1118
+ exported
1119
+ });
1120
+ const heritage = node.childForFieldName("heritage");
1121
+ if (heritage) {
1122
+ for (let i = 0; i < heritage.childCount; i++) {
1123
+ const child = heritage.child(i);
1124
+ if (child && child.type === "extends_clause") {
1125
+ const baseClass = findChildByType3(child, "identifier");
1126
+ if (baseClass) {
1127
+ const baseName = nodeText2(baseClass, context);
1128
+ const baseId = resolveSymbol2(baseName, context);
1129
+ if (baseId) {
1130
+ context.edges.push({
1131
+ source: symbolId,
1132
+ target: baseId,
1133
+ kind: "extends",
1134
+ filePath: context.filePath,
1135
+ line: child.startPosition.row + 1
1136
+ });
1137
+ }
1138
+ }
1139
+ }
1140
+ }
1141
+ }
1142
+ context.currentScope.push(name);
1143
+ const body = findChildByType3(node, "class_body");
1144
+ if (body) {
1145
+ walkNode3(body, context);
1146
+ }
1147
+ context.currentScope.pop();
1148
+ }
1149
+ function processMethodDefinition2(node, context) {
1150
+ const nameNode = node.childForFieldName("name");
1151
+ if (!nameNode) return;
1152
+ const name = nodeText2(nameNode, context);
1153
+ const scope = context.currentScope.length > 0 ? context.currentScope[context.currentScope.length - 1] : void 0;
1154
+ const symbolId = scope ? `${context.filePath}::${scope}.${name}` : `${context.filePath}::${name}`;
1155
+ context.symbols.push({
1156
+ id: symbolId,
1157
+ name,
1158
+ kind: "method",
1159
+ filePath: context.filePath,
1160
+ startLine: node.startPosition.row + 1,
1161
+ endLine: node.endPosition.row + 1,
1162
+ exported: false,
1163
+ scope
1164
+ });
1165
+ context.currentScope.push(name);
1166
+ const body = findChildByType3(node, "statement_block");
1167
+ if (body) {
1168
+ walkNode3(body, context);
1169
+ }
1170
+ context.currentScope.pop();
1171
+ }
1172
+ function processVariableDeclaration2(node, context) {
1173
+ const declarators = node.children.filter((c) => c.type === "variable_declarator");
1174
+ for (const declarator of declarators) {
1175
+ const nameNode = declarator.childForFieldName("name");
1176
+ const valueNode = declarator.childForFieldName("value");
1177
+ if (!nameNode) continue;
1178
+ if (valueNode && valueNode.type === "call_expression") {
1179
+ const functionNode = valueNode.childForFieldName("function");
1180
+ if (functionNode && nodeText2(functionNode, context) === "require") {
1181
+ processRequireCall(declarator, valueNode, context);
1182
+ continue;
1183
+ }
1184
+ }
1185
+ if (context.currentScope.length === 0) {
1186
+ const name = extractIdentifierName(nameNode, context);
1187
+ if (name) {
1188
+ const exported = isExported2(node.parent);
1189
+ const symbolId = `${context.filePath}::${name}`;
1190
+ context.symbols.push({
1191
+ id: symbolId,
1192
+ name,
1193
+ kind: "variable",
1194
+ filePath: context.filePath,
1195
+ startLine: node.startPosition.row + 1,
1196
+ endLine: node.endPosition.row + 1,
1197
+ exported
1198
+ });
1199
+ }
1200
+ }
1201
+ }
1202
+ }
1203
+ function processRequireCall(declarator, callNode, context) {
1204
+ const nameNode = declarator.childForFieldName("name");
1205
+ const args = callNode.childForFieldName("arguments");
1206
+ if (!args) return;
1207
+ const stringArg = findChildByType3(args, "string");
1208
+ if (!stringArg) return;
1209
+ const modulePath = nodeText2(stringArg, context).slice(1, -1);
1210
+ const resolvedPath = resolveJavaScriptImport(modulePath, context.filePath, context.projectRoot);
1211
+ if (!resolvedPath) return;
1212
+ if (nameNode) {
1213
+ if (nameNode.type === "identifier") {
1214
+ const name = nodeText2(nameNode, context);
1215
+ const targetId = `${resolvedPath}::${name}`;
1216
+ const sourceId = `${context.filePath}::__file__`;
1217
+ context.imports.set(name, targetId);
1218
+ context.edges.push({
1219
+ source: sourceId,
1220
+ target: targetId,
1221
+ kind: "imports",
1222
+ filePath: context.filePath,
1223
+ line: callNode.startPosition.row + 1
1224
+ });
1225
+ } else if (nameNode.type === "object_pattern") {
1226
+ const properties = nameNode.children.filter((c) => c.type === "pair_pattern" || c.type === "shorthand_property_identifier_pattern");
1227
+ for (const prop of properties) {
1228
+ let importedName;
1229
+ if (prop.type === "shorthand_property_identifier_pattern") {
1230
+ importedName = nodeText2(prop, context);
1231
+ } else {
1232
+ const keyNode = prop.childForFieldName("key");
1233
+ if (keyNode) {
1234
+ importedName = nodeText2(keyNode, context);
1235
+ } else {
1236
+ continue;
1237
+ }
1238
+ }
1239
+ const targetId = `${resolvedPath}::${importedName}`;
1240
+ const sourceId = `${context.filePath}::__file__`;
1241
+ context.imports.set(importedName, targetId);
1242
+ context.edges.push({
1243
+ source: sourceId,
1244
+ target: targetId,
1245
+ kind: "imports",
1246
+ filePath: context.filePath,
1247
+ line: callNode.startPosition.row + 1
1248
+ });
1249
+ }
1250
+ }
1251
+ }
1252
+ }
1253
+ function processImportStatement3(node, context) {
1254
+ const source = node.childForFieldName("source");
1255
+ if (!source) return;
1256
+ const importPath = nodeText2(source, context).slice(1, -1);
1257
+ const resolvedPath = resolveJavaScriptImport(importPath, context.filePath, context.projectRoot);
1258
+ if (!resolvedPath) return;
1259
+ const importClause = findChildByType3(node, "import_clause");
1260
+ if (!importClause) {
1261
+ return;
1262
+ }
1263
+ const namedImports = findChildByType3(importClause, "named_imports");
1264
+ const defaultImport = findChildByType3(importClause, "identifier");
1265
+ const namespaceImport = findChildByType3(importClause, "namespace_import");
1266
+ const sourceId = `${context.filePath}::__file__`;
1267
+ if (defaultImport) {
1268
+ const name = nodeText2(defaultImport, context);
1269
+ const targetId = `${resolvedPath}::default`;
1270
+ context.imports.set(name, targetId);
1271
+ context.edges.push({
1272
+ source: sourceId,
1273
+ target: targetId,
1274
+ kind: "imports",
1275
+ filePath: context.filePath,
1276
+ line: node.startPosition.row + 1
1277
+ });
1278
+ }
1279
+ if (namedImports) {
1280
+ const specifiers = namedImports.children.filter((c) => c.type === "import_specifier");
1281
+ for (const specifier of specifiers) {
1282
+ const nameNode = specifier.childForFieldName("name");
1283
+ const aliasNode = specifier.childForFieldName("alias");
1284
+ if (nameNode) {
1285
+ const importedName = nodeText2(nameNode, context);
1286
+ const localName = aliasNode ? nodeText2(aliasNode, context) : importedName;
1287
+ const targetId = `${resolvedPath}::${importedName}`;
1288
+ context.imports.set(localName, targetId);
1289
+ context.edges.push({
1290
+ source: sourceId,
1291
+ target: targetId,
1292
+ kind: "imports",
1293
+ filePath: context.filePath,
1294
+ line: node.startPosition.row + 1
1295
+ });
1296
+ }
1297
+ }
1298
+ }
1299
+ if (namespaceImport) {
1300
+ const aliasNode = findChildByType3(namespaceImport, "identifier");
1301
+ if (aliasNode) {
1302
+ const localName = nodeText2(aliasNode, context);
1303
+ const targetId = `${resolvedPath}::*`;
1304
+ context.imports.set(localName, targetId);
1305
+ context.edges.push({
1306
+ source: sourceId,
1307
+ target: targetId,
1308
+ kind: "imports",
1309
+ filePath: context.filePath,
1310
+ line: node.startPosition.row + 1
1311
+ });
1312
+ }
1313
+ }
1314
+ }
1315
+ function processExportStatement2(node, context) {
1316
+ const declaration = findChildByType3(node, "lexical_declaration") || findChildByType3(node, "variable_declaration") || findChildByType3(node, "function_declaration") || findChildByType3(node, "class_declaration");
1317
+ if (declaration) {
1318
+ processNode3(declaration, context);
1319
+ }
1320
+ }
1321
+ function processCallExpression3(node, context) {
1322
+ const functionNode = node.childForFieldName("function");
1323
+ if (!functionNode) return;
1324
+ let calleeName = null;
1325
+ if (functionNode.type === "identifier") {
1326
+ calleeName = nodeText2(functionNode, context);
1327
+ } else if (functionNode.type === "member_expression") {
1328
+ const property = functionNode.childForFieldName("property");
1329
+ if (property) {
1330
+ calleeName = nodeText2(property, context);
1331
+ }
1332
+ }
1333
+ if (!calleeName) return;
1334
+ const builtins = ["console", "require", "setTimeout", "setInterval", "parseInt", "parseFloat", "JSON", "Object", "Array", "String", "Number", "Boolean"];
1335
+ if (builtins.includes(calleeName)) return;
1336
+ const callerId = getCurrentSymbolId3(context);
1337
+ if (!callerId) return;
1338
+ const calleeId = resolveSymbol2(calleeName, context);
1339
+ if (calleeId) {
1340
+ context.edges.push({
1341
+ source: callerId,
1342
+ target: calleeId,
1343
+ kind: "calls",
1344
+ filePath: context.filePath,
1345
+ line: node.startPosition.row + 1
1346
+ });
1347
+ }
1348
+ }
1349
+ function processNewExpression2(node, context) {
1350
+ const constructorNode = findChildByType3(node, "identifier");
1351
+ if (!constructorNode) return;
1352
+ const className = nodeText2(constructorNode, context);
1353
+ const callerId = getCurrentSymbolId3(context);
1354
+ if (!callerId) return;
1355
+ const classId = resolveSymbol2(className, context);
1356
+ if (classId) {
1357
+ context.edges.push({
1358
+ source: callerId,
1359
+ target: classId,
1360
+ kind: "calls",
1361
+ filePath: context.filePath,
1362
+ line: node.startPosition.row + 1
1363
+ });
1364
+ }
1365
+ }
1366
+ function processJSXElement(node, context) {
1367
+ let tagName = null;
1368
+ if (node.type === "jsx_self_closing_element") {
1369
+ const nameNode = node.childForFieldName("name");
1370
+ if (nameNode) {
1371
+ tagName = nodeText2(nameNode, context);
1372
+ }
1373
+ } else if (node.type === "jsx_element") {
1374
+ const openingElement = findChildByType3(node, "jsx_opening_element");
1375
+ if (openingElement) {
1376
+ const nameNode = openingElement.childForFieldName("name");
1377
+ if (nameNode) {
1378
+ tagName = nodeText2(nameNode, context);
1379
+ }
1380
+ }
1381
+ }
1382
+ if (!tagName) return;
1383
+ if (!/^[A-Z]/.test(tagName)) return;
1384
+ const callerId = getCurrentSymbolId3(context);
1385
+ if (!callerId) return;
1386
+ const componentId = resolveSymbol2(tagName, context);
1387
+ if (componentId) {
1388
+ context.edges.push({
1389
+ source: callerId,
1390
+ target: componentId,
1391
+ kind: "references",
1392
+ filePath: context.filePath,
1393
+ line: node.startPosition.row + 1
1394
+ });
1395
+ }
1396
+ }
1397
+ function resolveJavaScriptImport(importPath, currentFile, projectRoot) {
1398
+ if (importPath.startsWith(".")) {
1399
+ const currentDir = dirname3(join4(projectRoot, currentFile));
1400
+ const targetPath = join4(currentDir, importPath);
1401
+ const extensions = [".js", ".jsx", ".mjs", ".cjs"];
1402
+ const indexFiles = ["index.js", "index.jsx", "index.mjs"];
1403
+ if (extname2(importPath)) {
1404
+ const fullPath = targetPath;
1405
+ if (existsSync3(fullPath)) {
1406
+ return fullPath.substring(projectRoot.length + 1);
1407
+ }
1408
+ return null;
1409
+ }
1410
+ for (const ext of extensions) {
1411
+ const candidate = `${targetPath}${ext}`;
1412
+ if (existsSync3(candidate)) {
1413
+ return candidate.substring(projectRoot.length + 1);
1414
+ }
1415
+ }
1416
+ for (const indexFile of indexFiles) {
1417
+ const candidate = join4(targetPath, indexFile);
1418
+ if (existsSync3(candidate)) {
1419
+ return candidate.substring(projectRoot.length + 1);
1420
+ }
1421
+ }
1422
+ return null;
1423
+ }
1424
+ return null;
1425
+ }
1426
+ function resolveSymbol2(name, context) {
1427
+ if (context.imports.has(name)) {
1428
+ return context.imports.get(name) || null;
1429
+ }
1430
+ const currentFileId = `${context.filePath}::${name}`;
1431
+ const symbol = context.symbols.find((s) => s.id === currentFileId);
1432
+ if (symbol) {
1433
+ return currentFileId;
1434
+ }
1435
+ if (context.currentScope.length > 0) {
1436
+ const scopedId = `${context.filePath}::${context.currentScope.join(".")}.${name}`;
1437
+ const scopedSymbol = context.symbols.find((s) => s.id === scopedId);
1438
+ if (scopedSymbol) {
1439
+ return scopedId;
1440
+ }
1441
+ }
1442
+ return null;
1443
+ }
1444
+ function isExported2(node) {
1445
+ if (!node) return false;
1446
+ let current = node;
1447
+ while (current) {
1448
+ if (current.type === "export_statement") {
1449
+ return true;
1450
+ }
1451
+ current = current.parent;
1452
+ }
1453
+ return false;
1454
+ }
1455
+ function extractIdentifierName(node, context) {
1456
+ if (node.type === "identifier") {
1457
+ return nodeText2(node, context);
1458
+ } else if (node.type === "object_pattern") {
1459
+ const properties = node.children.filter((c) => c.type === "shorthand_property_identifier_pattern");
1460
+ if (properties.length > 0) {
1461
+ return nodeText2(properties[0], context);
1462
+ }
1463
+ }
1464
+ return null;
1465
+ }
1466
+ function findChildByType3(node, type) {
1467
+ for (let i = 0; i < node.childCount; i++) {
1468
+ const child = node.child(i);
1469
+ if (child && child.type === type) {
1470
+ return child;
1471
+ }
1472
+ }
1473
+ return null;
1474
+ }
1475
+ function nodeText2(node, context) {
1476
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
1477
+ }
1478
+ function getCurrentSymbolId3(context) {
1479
+ if (context.currentScope.length === 0) return null;
1480
+ return `${context.filePath}::${context.currentScope.join(".")}`;
1481
+ }
1482
+ var javascriptParser = {
1483
+ name: "javascript",
1484
+ extensions: [".js", ".jsx", ".mjs", ".cjs"],
1485
+ parseFile: parseJavaScriptFile
1486
+ };
1487
+
1488
+ // src/parser/go.ts
1489
+ import Parser4 from "tree-sitter";
1490
+ import Go from "tree-sitter-go";
1491
+ import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1492
+ import { join as join5, dirname as dirname4 } from "path";
1493
+ var parser = new Parser4();
1494
+ parser.setLanguage(Go);
1495
+ function parseGoFile(filePath, sourceCode, projectRoot) {
1496
+ const tree = parser.parse(sourceCode);
1497
+ const moduleName = readGoModuleName(projectRoot);
1498
+ const context = {
1499
+ filePath,
1500
+ projectRoot,
1501
+ sourceCode,
1502
+ symbols: [],
1503
+ edges: [],
1504
+ currentScope: [],
1505
+ packageName: "",
1506
+ imports: /* @__PURE__ */ new Map(),
1507
+ moduleName
1508
+ };
1509
+ extractPackageName(tree.rootNode, context);
1510
+ walkNode4(tree.rootNode, context);
1511
+ return {
1512
+ filePath,
1513
+ symbols: context.symbols,
1514
+ edges: context.edges
1515
+ };
1516
+ }
1517
+ function extractPackageName(node, context) {
1518
+ for (let i = 0; i < node.childCount; i++) {
1519
+ const child = node.child(i);
1520
+ if (child && child.type === "package_clause") {
1521
+ const pkgIdentifier = findChildByType4(child, "package_identifier");
1522
+ if (pkgIdentifier) {
1523
+ context.packageName = nodeText3(pkgIdentifier, context);
1524
+ }
1525
+ break;
1526
+ }
1527
+ }
1528
+ }
1529
+ function walkNode4(node, context) {
1530
+ processNode4(node, context);
1531
+ for (let i = 0; i < node.childCount; i++) {
1532
+ const child = node.child(i);
1533
+ if (child) {
1534
+ walkNode4(child, context);
1535
+ }
1536
+ }
1537
+ }
1538
+ function processNode4(node, context) {
1539
+ const type = node.type;
1540
+ switch (type) {
1541
+ case "function_declaration":
1542
+ processFunctionDeclaration3(node, context);
1543
+ break;
1544
+ case "method_declaration":
1545
+ processMethodDeclaration(node, context);
1546
+ break;
1547
+ case "type_declaration":
1548
+ processTypeDeclaration(node, context);
1549
+ break;
1550
+ case "const_declaration":
1551
+ processConstDeclaration(node, context);
1552
+ break;
1553
+ case "var_declaration":
1554
+ processVarDeclaration(node, context);
1555
+ break;
1556
+ case "import_declaration":
1557
+ processImportDeclaration(node, context);
1558
+ break;
1559
+ case "call_expression":
1560
+ processCallExpression4(node, context);
1561
+ break;
1562
+ }
1563
+ }
1564
+ function processFunctionDeclaration3(node, context) {
1565
+ const nameNode = node.childForFieldName("name");
1566
+ if (!nameNode) return;
1567
+ const name = nodeText3(nameNode, context);
1568
+ const exported = isExported3(name);
1569
+ const symbolId = `${context.filePath}::${name}`;
1570
+ context.symbols.push({
1571
+ id: symbolId,
1572
+ name,
1573
+ kind: "function",
1574
+ filePath: context.filePath,
1575
+ startLine: node.startPosition.row + 1,
1576
+ endLine: node.endPosition.row + 1,
1577
+ exported
1578
+ });
1579
+ context.currentScope.push(name);
1580
+ const body = node.childForFieldName("body");
1581
+ if (body) {
1582
+ walkNode4(body, context);
1583
+ }
1584
+ context.currentScope.pop();
1585
+ }
1586
+ function processMethodDeclaration(node, context) {
1587
+ const nameNode = node.childForFieldName("name");
1588
+ const receiverNode = node.childForFieldName("receiver");
1589
+ if (!nameNode || !receiverNode) return;
1590
+ const name = nodeText3(nameNode, context);
1591
+ const receiverType = extractReceiverType(receiverNode, context);
1592
+ if (!receiverType) return;
1593
+ const exported = isExported3(name);
1594
+ const symbolId = `${context.filePath}::${receiverType}.${name}`;
1595
+ context.symbols.push({
1596
+ id: symbolId,
1597
+ name,
1598
+ kind: "method",
1599
+ filePath: context.filePath,
1600
+ startLine: node.startPosition.row + 1,
1601
+ endLine: node.endPosition.row + 1,
1602
+ exported,
1603
+ scope: receiverType
1604
+ });
1605
+ context.currentScope.push(`${receiverType}.${name}`);
1606
+ const body = node.childForFieldName("body");
1607
+ if (body) {
1608
+ walkNode4(body, context);
1609
+ }
1610
+ context.currentScope.pop();
1611
+ }
1612
+ function processTypeDeclaration(node, context) {
1613
+ const typeSpecs = findChildrenByType(node, "type_spec");
1614
+ for (const typeSpec of typeSpecs) {
1615
+ const nameNode = typeSpec.childForFieldName("name");
1616
+ const typeNode = typeSpec.childForFieldName("type");
1617
+ if (!nameNode || !typeNode) continue;
1618
+ const name = nodeText3(nameNode, context);
1619
+ const exported = isExported3(name);
1620
+ let kind = "type_alias";
1621
+ if (typeNode.type === "struct_type") {
1622
+ kind = "class";
1623
+ const fieldList = findChildByType4(typeNode, "field_declaration_list");
1624
+ if (fieldList) {
1625
+ for (let i = 0; i < fieldList.childCount; i++) {
1626
+ const field = fieldList.child(i);
1627
+ if (field && field.type === "field_declaration") {
1628
+ const fieldName = field.childForFieldName("name");
1629
+ const fieldType = field.childForFieldName("type");
1630
+ if (!fieldName && fieldType) {
1631
+ const embeddedTypeName = extractTypeName(fieldType, context);
1632
+ if (embeddedTypeName) {
1633
+ const embeddedId = resolveSymbol3(embeddedTypeName, context);
1634
+ if (embeddedId) {
1635
+ const symbolId2 = `${context.filePath}::${name}`;
1636
+ context.edges.push({
1637
+ source: symbolId2,
1638
+ target: embeddedId,
1639
+ kind: "inherits",
1640
+ filePath: context.filePath,
1641
+ line: field.startPosition.row + 1
1642
+ });
1643
+ }
1644
+ }
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ } else if (typeNode.type === "interface_type") {
1650
+ kind = "interface";
1651
+ }
1652
+ const symbolId = `${context.filePath}::${name}`;
1653
+ context.symbols.push({
1654
+ id: symbolId,
1655
+ name,
1656
+ kind,
1657
+ filePath: context.filePath,
1658
+ startLine: typeSpec.startPosition.row + 1,
1659
+ endLine: typeSpec.endPosition.row + 1,
1660
+ exported
1661
+ });
1662
+ }
1663
+ }
1664
+ function processConstDeclaration(node, context) {
1665
+ const constSpecs = findChildrenByType(node, "const_spec");
1666
+ for (const constSpec of constSpecs) {
1667
+ const nameNode = constSpec.childForFieldName("name");
1668
+ if (!nameNode) continue;
1669
+ const names = extractIdentifierNames(nameNode, context);
1670
+ for (const name of names) {
1671
+ const exported = isExported3(name);
1672
+ const symbolId = `${context.filePath}::${name}`;
1673
+ context.symbols.push({
1674
+ id: symbolId,
1675
+ name,
1676
+ kind: "constant",
1677
+ filePath: context.filePath,
1678
+ startLine: constSpec.startPosition.row + 1,
1679
+ endLine: constSpec.endPosition.row + 1,
1680
+ exported
1681
+ });
1682
+ }
1683
+ }
1684
+ }
1685
+ function processVarDeclaration(node, context) {
1686
+ if (context.currentScope.length > 0) return;
1687
+ const varSpecs = findChildrenByType(node, "var_spec");
1688
+ for (const varSpec of varSpecs) {
1689
+ const nameNode = varSpec.childForFieldName("name");
1690
+ if (!nameNode) continue;
1691
+ const names = extractIdentifierNames(nameNode, context);
1692
+ for (const name of names) {
1693
+ const exported = isExported3(name);
1694
+ const symbolId = `${context.filePath}::${name}`;
1695
+ context.symbols.push({
1696
+ id: symbolId,
1697
+ name,
1698
+ kind: "variable",
1699
+ filePath: context.filePath,
1700
+ startLine: varSpec.startPosition.row + 1,
1701
+ endLine: varSpec.endPosition.row + 1,
1702
+ exported
1703
+ });
1704
+ }
1705
+ }
1706
+ }
1707
+ function processImportDeclaration(node, context) {
1708
+ let importSpecs = [];
1709
+ const importSpecList = findChildByType4(node, "import_spec_list");
1710
+ if (importSpecList) {
1711
+ importSpecs = findChildrenByType(importSpecList, "import_spec");
1712
+ } else {
1713
+ importSpecs = findChildrenByType(node, "import_spec");
1714
+ }
1715
+ for (const importSpec of importSpecs) {
1716
+ const pathNode = importSpec.childForFieldName("path");
1717
+ if (!pathNode) continue;
1718
+ const importPath = nodeText3(pathNode, context).slice(1, -1);
1719
+ const nameNode = importSpec.childForFieldName("name");
1720
+ let alias = "";
1721
+ if (nameNode) {
1722
+ alias = nodeText3(nameNode, context);
1723
+ } else {
1724
+ const segments = importPath.split("/");
1725
+ alias = segments[segments.length - 1];
1726
+ }
1727
+ context.imports.set(alias, importPath);
1728
+ const resolvedFiles = resolveGoImport(importPath, context.projectRoot, context.moduleName);
1729
+ if (resolvedFiles.length > 0) {
1730
+ const sourceId = `${context.filePath}::__file__`;
1731
+ for (const targetFile of resolvedFiles) {
1732
+ const targetId = `${targetFile}::__file__`;
1733
+ context.edges.push({
1734
+ source: sourceId,
1735
+ target: targetId,
1736
+ kind: "imports",
1737
+ filePath: context.filePath,
1738
+ line: importSpec.startPosition.row + 1
1739
+ });
1740
+ }
1741
+ }
1742
+ }
1743
+ }
1744
+ function processCallExpression4(node, context) {
1745
+ const functionNode = node.childForFieldName("function");
1746
+ if (!functionNode) return;
1747
+ let calleeName = null;
1748
+ if (functionNode.type === "identifier") {
1749
+ calleeName = nodeText3(functionNode, context);
1750
+ } else if (functionNode.type === "selector_expression") {
1751
+ const field = functionNode.childForFieldName("field");
1752
+ if (field) {
1753
+ calleeName = nodeText3(field, context);
1754
+ }
1755
+ }
1756
+ if (!calleeName) return;
1757
+ const builtins = ["make", "len", "cap", "append", "copy", "delete", "panic", "recover", "print", "println", "new"];
1758
+ if (builtins.includes(calleeName)) return;
1759
+ const callerId = getCurrentSymbolId4(context);
1760
+ if (!callerId) return;
1761
+ const calleeId = resolveSymbol3(calleeName, context);
1762
+ if (calleeId) {
1763
+ context.edges.push({
1764
+ source: callerId,
1765
+ target: calleeId,
1766
+ kind: "calls",
1767
+ filePath: context.filePath,
1768
+ line: node.startPosition.row + 1
1769
+ });
1770
+ }
1771
+ }
1772
+ function readGoModuleName(projectRoot) {
1773
+ let currentDir = projectRoot;
1774
+ for (let i = 0; i < 5; i++) {
1775
+ const goModPath = join5(currentDir, "go.mod");
1776
+ if (existsSync4(goModPath)) {
1777
+ try {
1778
+ const content = readFileSync2(goModPath, "utf-8");
1779
+ const lines = content.split("\n");
1780
+ for (const line of lines) {
1781
+ const trimmed = line.trim();
1782
+ if (trimmed.startsWith("module ")) {
1783
+ return trimmed.substring(7).trim();
1784
+ }
1785
+ }
1786
+ } catch (error) {
1787
+ console.error(`Error reading go.mod: ${error}`);
1788
+ }
1789
+ }
1790
+ const parentDir = dirname4(currentDir);
1791
+ if (parentDir === currentDir) break;
1792
+ currentDir = parentDir;
1793
+ }
1794
+ return null;
1795
+ }
1796
+ function resolveGoImport(importPath, projectRoot, moduleName) {
1797
+ if (!importPath.includes(".") && !importPath.includes("/")) {
1798
+ return [];
1799
+ }
1800
+ if (moduleName && importPath.startsWith(moduleName)) {
1801
+ const relativePath = importPath.substring(moduleName.length + 1);
1802
+ const packageDir2 = join5(projectRoot, relativePath);
1803
+ return findGoFilesInDir(packageDir2, projectRoot);
1804
+ }
1805
+ const segments = importPath.split("/");
1806
+ const packageDir = join5(projectRoot, ...segments);
1807
+ if (existsSync4(packageDir)) {
1808
+ return findGoFilesInDir(packageDir, projectRoot);
1809
+ }
1810
+ return [];
1811
+ }
1812
+ function findGoFilesInDir(dir, projectRoot) {
1813
+ if (!existsSync4(dir)) return [];
1814
+ try {
1815
+ const files = readdirSync2(dir);
1816
+ const goFiles = files.filter((f) => f.endsWith(".go") && !f.endsWith("_test.go"));
1817
+ return goFiles.map((f) => {
1818
+ const fullPath = join5(dir, f);
1819
+ return fullPath.substring(projectRoot.length + 1);
1820
+ });
1821
+ } catch (error) {
1822
+ console.error(`[findGoFilesInDir] Error:`, error);
1823
+ return [];
1824
+ }
1825
+ }
1826
+ function isExported3(name) {
1827
+ return name.length > 0 && name[0] === name[0].toUpperCase();
1828
+ }
1829
+ function extractReceiverType(receiverNode, context) {
1830
+ const paramDecl = findChildByType4(receiverNode, "parameter_declaration");
1831
+ if (!paramDecl) return null;
1832
+ const typeNode = paramDecl.childForFieldName("type");
1833
+ if (!typeNode) return null;
1834
+ return extractTypeName(typeNode, context);
1835
+ }
1836
+ function extractTypeName(typeNode, context) {
1837
+ if (typeNode.type === "pointer_type") {
1838
+ for (let i = 0; i < typeNode.childCount; i++) {
1839
+ const child = typeNode.child(i);
1840
+ if (child && child.type === "type_identifier") {
1841
+ return nodeText3(child, context);
1842
+ }
1843
+ }
1844
+ return null;
1845
+ } else if (typeNode.type === "type_identifier") {
1846
+ return nodeText3(typeNode, context);
1847
+ }
1848
+ return null;
1849
+ }
1850
+ function extractIdentifierNames(node, context) {
1851
+ if (node.type === "identifier") {
1852
+ return [nodeText3(node, context)];
1853
+ } else if (node.type === "identifier_list") {
1854
+ const names = [];
1855
+ for (let i = 0; i < node.childCount; i++) {
1856
+ const child = node.child(i);
1857
+ if (child && child.type === "identifier") {
1858
+ names.push(nodeText3(child, context));
1859
+ }
1860
+ }
1861
+ return names;
1862
+ }
1863
+ return [];
1864
+ }
1865
+ function resolveSymbol3(name, context) {
1866
+ const currentFileId = `${context.filePath}::${name}`;
1867
+ const symbol = context.symbols.find((s) => s.id === currentFileId);
1868
+ if (symbol) {
1869
+ return currentFileId;
1870
+ }
1871
+ if (context.currentScope.length > 0) {
1872
+ for (let i = context.currentScope.length - 1; i >= 0; i--) {
1873
+ const scopedId = `${context.filePath}::${context.currentScope[i]}.${name}`;
1874
+ const scopedSymbol = context.symbols.find((s) => s.id === scopedId);
1875
+ if (scopedSymbol) {
1876
+ return scopedId;
1877
+ }
1878
+ }
1879
+ }
1880
+ return null;
1881
+ }
1882
+ function findChildByType4(node, type) {
1883
+ for (let i = 0; i < node.childCount; i++) {
1884
+ const child = node.child(i);
1885
+ if (child && child.type === type) {
1886
+ return child;
1887
+ }
1888
+ }
1889
+ return null;
1890
+ }
1891
+ function findChildrenByType(node, type) {
1892
+ const results = [];
1893
+ for (let i = 0; i < node.childCount; i++) {
1894
+ const child = node.child(i);
1895
+ if (child && child.type === type) {
1896
+ results.push(child);
1897
+ }
1898
+ }
1899
+ return results;
1900
+ }
1901
+ function nodeText3(node, context) {
1902
+ return context.sourceCode.substring(node.startIndex, node.endIndex);
1903
+ }
1904
+ function getCurrentSymbolId4(context) {
1905
+ if (context.currentScope.length === 0) return null;
1906
+ return `${context.filePath}::${context.currentScope[context.currentScope.length - 1]}`;
1907
+ }
1908
+ var goParser = {
1909
+ name: "go",
1910
+ extensions: [".go"],
1911
+ parseFile: parseGoFile
1912
+ };
1913
+
1914
+ // src/parser/detect.ts
1915
+ var parsers = [
1916
+ typescriptParser,
1917
+ pythonParser,
1918
+ javascriptParser,
1919
+ goParser
1920
+ ];
1921
+ function getParserForFile(filePath) {
1922
+ const ext = extname3(filePath).toLowerCase();
1923
+ return parsers.find((p) => p.extensions.includes(ext)) || null;
1924
+ }
1925
+
1926
+ // src/parser/index.ts
1927
+ var MAX_FILE_SIZE = 1e6;
1928
+ function shouldParseFile(fullPath) {
1929
+ try {
1930
+ const stats = statSync2(fullPath);
1931
+ if (stats.size > MAX_FILE_SIZE) {
1932
+ console.error(`[Parser] Skipping ${fullPath} \u2014 file too large (${(stats.size / 1024).toFixed(0)}KB)`);
1933
+ return false;
1934
+ }
1935
+ return true;
1936
+ } catch (error) {
1937
+ return false;
1938
+ }
1939
+ }
1940
+ function parseProject(projectRoot) {
1941
+ const files = scanDirectory(projectRoot);
1942
+ const parsedFiles = [];
1943
+ for (const file of files) {
1944
+ try {
1945
+ const fullPath = join6(projectRoot, file);
1946
+ if (!shouldParseFile(fullPath)) {
1947
+ continue;
1948
+ }
1949
+ const parser2 = getParserForFile(file);
1950
+ if (!parser2) {
1951
+ console.error(`No parser found for file: ${file}`);
1952
+ continue;
1953
+ }
1954
+ const sourceCode = readFileSync3(fullPath, "utf-8");
1955
+ const parsed = parser2.parseFile(file, sourceCode, projectRoot);
1956
+ parsedFiles.push(parsed);
1957
+ } catch (err) {
1958
+ console.error(`Error parsing file ${file}:`, err instanceof Error ? err.message : err);
1959
+ }
1960
+ }
1961
+ return parsedFiles;
1962
+ }
1963
+
1964
+ // src/graph/index.ts
1965
+ import { DirectedGraph } from "graphology";
1966
+ function buildGraph(parsedFiles) {
1967
+ const graph = new DirectedGraph();
1968
+ for (const file of parsedFiles) {
1969
+ for (const symbol of file.symbols) {
1970
+ if (!graph.hasNode(symbol.id)) {
1971
+ graph.addNode(symbol.id, {
1972
+ name: symbol.name,
1973
+ kind: symbol.kind,
1974
+ filePath: symbol.filePath,
1975
+ startLine: symbol.startLine,
1976
+ endLine: symbol.endLine,
1977
+ exported: symbol.exported,
1978
+ scope: symbol.scope
1979
+ });
1980
+ }
1981
+ }
1982
+ }
1983
+ const fileNodes = /* @__PURE__ */ new Set();
1984
+ for (const file of parsedFiles) {
1985
+ for (const edge of file.edges) {
1986
+ if (edge.source.endsWith("::__file__") && !fileNodes.has(edge.source)) {
1987
+ fileNodes.add(edge.source);
1988
+ const filePath = edge.source.replace("::__file__", "");
1989
+ graph.addNode(edge.source, {
1990
+ name: "__file__",
1991
+ kind: "import",
1992
+ filePath,
1993
+ startLine: 1,
1994
+ endLine: 1,
1995
+ exported: false
1996
+ });
1997
+ }
1998
+ }
1999
+ }
2000
+ for (const file of parsedFiles) {
2001
+ for (const edge of file.edges) {
2002
+ if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
2003
+ graph.mergeEdge(edge.source, edge.target, {
2004
+ kind: edge.kind,
2005
+ filePath: edge.filePath,
2006
+ line: edge.line
2007
+ });
2008
+ }
2009
+ }
2010
+ }
2011
+ return graph;
2012
+ }
2013
+
2014
+ // src/graph/queries.ts
2015
+ function getDependencies(graph, symbolId) {
2016
+ if (!graph.hasNode(symbolId)) return [];
2017
+ const dependencies = [];
2018
+ const neighbors = graph.outNeighbors(symbolId);
2019
+ for (const neighborId of neighbors) {
2020
+ const attrs = graph.getNodeAttributes(neighborId);
2021
+ dependencies.push({
2022
+ id: neighborId,
2023
+ name: attrs.name,
2024
+ kind: attrs.kind,
2025
+ filePath: attrs.filePath,
2026
+ startLine: attrs.startLine,
2027
+ endLine: attrs.endLine,
2028
+ exported: attrs.exported,
2029
+ scope: attrs.scope
2030
+ });
2031
+ }
2032
+ return dependencies;
2033
+ }
2034
+ function getDependents(graph, symbolId) {
2035
+ if (!graph.hasNode(symbolId)) return [];
2036
+ const dependents = [];
2037
+ const neighbors = graph.inNeighbors(symbolId);
2038
+ for (const neighborId of neighbors) {
2039
+ const attrs = graph.getNodeAttributes(neighborId);
2040
+ dependents.push({
2041
+ id: neighborId,
2042
+ name: attrs.name,
2043
+ kind: attrs.kind,
2044
+ filePath: attrs.filePath,
2045
+ startLine: attrs.startLine,
2046
+ endLine: attrs.endLine,
2047
+ exported: attrs.exported,
2048
+ scope: attrs.scope
2049
+ });
2050
+ }
2051
+ return dependents;
2052
+ }
2053
+ function getImpact(graph, symbolId) {
2054
+ if (!graph.hasNode(symbolId)) {
2055
+ return {
2056
+ directDependents: [],
2057
+ transitiveDependents: [],
2058
+ affectedFiles: []
2059
+ };
2060
+ }
2061
+ const directDependents = getDependents(graph, symbolId);
2062
+ const visited = /* @__PURE__ */ new Set([symbolId]);
2063
+ const queue = [symbolId];
2064
+ const allDependents = [];
2065
+ const fileSet = /* @__PURE__ */ new Set();
2066
+ while (queue.length > 0) {
2067
+ const current = queue.shift();
2068
+ const neighbors = graph.inNeighbors(current);
2069
+ for (const neighborId of neighbors) {
2070
+ if (!visited.has(neighborId)) {
2071
+ visited.add(neighborId);
2072
+ queue.push(neighborId);
2073
+ const attrs = graph.getNodeAttributes(neighborId);
2074
+ allDependents.push({
2075
+ id: neighborId,
2076
+ name: attrs.name,
2077
+ kind: attrs.kind,
2078
+ filePath: attrs.filePath,
2079
+ startLine: attrs.startLine,
2080
+ endLine: attrs.endLine,
2081
+ exported: attrs.exported,
2082
+ scope: attrs.scope
2083
+ });
2084
+ fileSet.add(attrs.filePath);
2085
+ }
2086
+ }
2087
+ }
2088
+ return {
2089
+ directDependents,
2090
+ transitiveDependents: allDependents,
2091
+ affectedFiles: Array.from(fileSet).sort()
2092
+ };
2093
+ }
2094
+ function getCrossFileEdges(graph) {
2095
+ const crossFileEdges = [];
2096
+ graph.forEachEdge((edge, attrs, source, target) => {
2097
+ const sourceAttrs = graph.getNodeAttributes(source);
2098
+ const targetAttrs = graph.getNodeAttributes(target);
2099
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
2100
+ crossFileEdges.push({
2101
+ source,
2102
+ target,
2103
+ sourceFile: sourceAttrs.filePath,
2104
+ targetFile: targetAttrs.filePath,
2105
+ kind: attrs.kind
2106
+ });
2107
+ }
2108
+ });
2109
+ return crossFileEdges;
2110
+ }
2111
+ function getFileSummary(graph) {
2112
+ const fileMap = /* @__PURE__ */ new Map();
2113
+ graph.forEachNode((node, attrs) => {
2114
+ if (!fileMap.has(attrs.filePath)) {
2115
+ fileMap.set(attrs.filePath, {
2116
+ symbolCount: 0,
2117
+ incomingRefs: /* @__PURE__ */ new Set(),
2118
+ outgoingRefs: /* @__PURE__ */ new Set()
2119
+ });
2120
+ }
2121
+ fileMap.get(attrs.filePath).symbolCount++;
2122
+ });
2123
+ graph.forEachEdge((edge, attrs, source, target) => {
2124
+ const sourceAttrs = graph.getNodeAttributes(source);
2125
+ const targetAttrs = graph.getNodeAttributes(target);
2126
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
2127
+ const sourceFile = fileMap.get(sourceAttrs.filePath);
2128
+ const targetFile = fileMap.get(targetAttrs.filePath);
2129
+ if (sourceFile) {
2130
+ sourceFile.outgoingRefs.add(targetAttrs.filePath);
2131
+ }
2132
+ if (targetFile) {
2133
+ targetFile.incomingRefs.add(sourceAttrs.filePath);
2134
+ }
2135
+ }
2136
+ });
2137
+ const result = [];
2138
+ for (const [filePath, data] of fileMap.entries()) {
2139
+ result.push({
2140
+ filePath,
2141
+ symbolCount: data.symbolCount,
2142
+ incomingRefs: data.incomingRefs.size,
2143
+ outgoingRefs: data.outgoingRefs.size
2144
+ });
2145
+ }
2146
+ return result.sort((a, b) => a.filePath.localeCompare(b.filePath));
2147
+ }
2148
+ function searchSymbols(graph, query) {
2149
+ const queryLower = query.toLowerCase();
2150
+ const results = [];
2151
+ graph.forEachNode((nodeId, attrs) => {
2152
+ if (attrs.name.toLowerCase().includes(queryLower)) {
2153
+ results.push({
2154
+ id: nodeId,
2155
+ name: attrs.name,
2156
+ kind: attrs.kind,
2157
+ filePath: attrs.filePath,
2158
+ startLine: attrs.startLine,
2159
+ endLine: attrs.endLine,
2160
+ exported: attrs.exported,
2161
+ scope: attrs.scope
2162
+ });
2163
+ }
2164
+ });
2165
+ return results;
2166
+ }
2167
+ function getArchitectureSummary(graph) {
2168
+ const fileSummary = getFileSummary(graph);
2169
+ const fileSet = /* @__PURE__ */ new Set();
2170
+ graph.forEachNode((node, attrs) => {
2171
+ fileSet.add(attrs.filePath);
2172
+ });
2173
+ const fileConnections = fileSummary.map((f) => ({
2174
+ filePath: f.filePath,
2175
+ connections: f.incomingRefs + f.outgoingRefs
2176
+ }));
2177
+ fileConnections.sort((a, b) => b.connections - a.connections);
2178
+ const orphanFiles = fileSummary.filter((f) => f.incomingRefs === 0 && f.outgoingRefs === 0).map((f) => f.filePath);
2179
+ return {
2180
+ fileCount: fileSet.size,
2181
+ symbolCount: graph.order,
2182
+ edgeCount: graph.size,
2183
+ mostConnectedFiles: fileConnections.slice(0, 5),
2184
+ orphanFiles
2185
+ };
2186
+ }
2187
+
2188
+ // src/viz/data.ts
2189
+ import { basename } from "path";
2190
+ function prepareVizData(graph, projectRoot) {
2191
+ const fileSummary = getFileSummary(graph);
2192
+ const crossFileEdges = getCrossFileEdges(graph);
2193
+ const files = fileSummary.map((f) => ({
2194
+ path: f.filePath,
2195
+ directory: f.filePath.includes("/") ? f.filePath.substring(0, f.filePath.lastIndexOf("/")) : ".",
2196
+ symbolCount: f.symbolCount,
2197
+ incomingCount: f.incomingRefs,
2198
+ outgoingCount: f.outgoingRefs
2199
+ }));
2200
+ files.sort((a, b) => {
2201
+ if (a.directory !== b.directory) {
2202
+ return a.directory.localeCompare(b.directory);
2203
+ }
2204
+ return a.path.localeCompare(b.path);
2205
+ });
2206
+ const arcMap = /* @__PURE__ */ new Map();
2207
+ for (const edge of crossFileEdges) {
2208
+ const key = `${edge.sourceFile}::${edge.targetFile}`;
2209
+ if (arcMap.has(key)) {
2210
+ const arc = arcMap.get(key);
2211
+ arc.edgeCount++;
2212
+ if (!arc.edgeKinds.includes(edge.kind)) {
2213
+ arc.edgeKinds.push(edge.kind);
2214
+ }
2215
+ } else {
2216
+ arcMap.set(key, {
2217
+ sourceFile: edge.sourceFile,
2218
+ targetFile: edge.targetFile,
2219
+ edgeCount: 1,
2220
+ edgeKinds: [edge.kind]
2221
+ });
2222
+ }
2223
+ }
2224
+ const arcs = Array.from(arcMap.values());
2225
+ const projectName = basename(projectRoot);
2226
+ return {
2227
+ files,
2228
+ arcs,
2229
+ stats: {
2230
+ totalFiles: files.length,
2231
+ totalSymbols: graph.order,
2232
+ totalEdges: graph.size,
2233
+ totalCrossFileEdges: arcs.reduce((sum, arc) => sum + arc.edgeCount, 0)
2234
+ },
2235
+ projectName
2236
+ };
2237
+ }
2238
+
2239
+ // src/watcher.ts
2240
+ import chokidar from "chokidar";
2241
+ function watchProject(projectRoot, callbacks) {
2242
+ console.error(`[Watcher] Creating watcher for: ${projectRoot}`);
2243
+ const watcher = chokidar.watch(projectRoot, {
2244
+ ignored: [
2245
+ "**/node_modules/**",
2246
+ "**/vendor/**",
2247
+ // Go dependencies
2248
+ "**/.git/**",
2249
+ "**/dist/**",
2250
+ "**/build/**",
2251
+ "**/coverage/**",
2252
+ "**/.next/**",
2253
+ "**/.turbo/**",
2254
+ "**/.*"
2255
+ // Hidden files and directories
2256
+ ],
2257
+ ignoreInitial: true,
2258
+ // Don't fire events for existing files
2259
+ persistent: true,
2260
+ followSymlinks: false,
2261
+ awaitWriteFinish: {
2262
+ stabilityThreshold: 300,
2263
+ // Wait 300ms after last change before firing
2264
+ pollInterval: 100
2265
+ }
2266
+ });
2267
+ console.error("[Watcher] Attaching event listeners...");
2268
+ watcher.on("change", (absolutePath) => {
2269
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
2270
+ if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2271
+ if (absolutePath.endsWith("_test.go")) return;
2272
+ const relativePath = absolutePath.replace(projectRoot + "/", "");
2273
+ console.error(`[Watcher] Change event: ${relativePath}`);
2274
+ callbacks.onFileChanged(relativePath);
2275
+ });
2276
+ watcher.on("add", (absolutePath) => {
2277
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
2278
+ if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2279
+ if (absolutePath.endsWith("_test.go")) return;
2280
+ const relativePath = absolutePath.replace(projectRoot + "/", "");
2281
+ console.error(`[Watcher] Add event: ${relativePath}`);
2282
+ callbacks.onFileAdded(relativePath);
2283
+ });
2284
+ watcher.on("unlink", (absolutePath) => {
2285
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
2286
+ if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2287
+ if (absolutePath.endsWith("_test.go")) return;
2288
+ const relativePath = absolutePath.replace(projectRoot + "/", "");
2289
+ console.error(`[Watcher] Unlink event: ${relativePath}`);
2290
+ callbacks.onFileDeleted(relativePath);
2291
+ });
2292
+ watcher.on("error", (error) => {
2293
+ console.error("[Watcher] Error:", error);
2294
+ });
2295
+ watcher.on("ready", () => {
2296
+ console.error("[Watcher] Ready \u2014 watching for changes");
2297
+ const watched = watcher.getWatched();
2298
+ const dirs = Object.keys(watched);
2299
+ let fileCount = 0;
2300
+ for (const dir of dirs) {
2301
+ const files = watched[dir];
2302
+ fileCount += files.filter(
2303
+ (f) => f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx") || f.endsWith(".mjs") || f.endsWith(".cjs") || f.endsWith(".py") || f.endsWith(".go") && !f.endsWith("_test.go")
2304
+ ).length;
2305
+ }
2306
+ console.error(`[Watcher] Watching ${fileCount} TypeScript/JavaScript/Python/Go files in ${dirs.length} directories`);
2307
+ });
2308
+ watcher.on("all", (event, path) => {
2309
+ console.error(`[Watcher] ALL event: ${event} ${path}`);
2310
+ });
2311
+ return watcher;
2312
+ }
2313
+
2314
+ // src/viz/server.ts
2315
+ import express from "express";
2316
+ import open from "open";
2317
+ import { fileURLToPath } from "url";
2318
+ import { dirname as dirname5, join as join7 } from "path";
2319
+ import { WebSocketServer } from "ws";
2320
+ var __filename = fileURLToPath(import.meta.url);
2321
+ var __dirname = dirname5(__filename);
2322
+ var activeServer = null;
2323
+ function startVizServer(initialVizData, graph, projectRoot, port = 3333, shouldOpen = true) {
2324
+ if (activeServer) {
2325
+ console.error(`Visualization server already running at ${activeServer.url}`);
2326
+ return {
2327
+ server: activeServer.server,
2328
+ url: activeServer.url,
2329
+ alreadyRunning: true
2330
+ };
2331
+ }
2332
+ const app = express();
2333
+ let vizData = initialVizData;
2334
+ const publicDir = join7(__dirname, "viz", "public");
2335
+ app.use(express.static(publicDir));
2336
+ app.get("/api/graph", (req, res) => {
2337
+ res.json(vizData);
2338
+ });
2339
+ const server = app.listen(port, "127.0.0.1", () => {
2340
+ const url2 = `http://127.0.0.1:${port}`;
2341
+ console.error(`
2342
+ Depwire visualization running at ${url2}`);
2343
+ console.error("Press Ctrl+C to stop\n");
2344
+ activeServer = { server, port, url: url2 };
2345
+ if (shouldOpen) {
2346
+ open(url2);
2347
+ }
2348
+ });
2349
+ const wss = new WebSocketServer({ server });
2350
+ wss.on("connection", (ws) => {
2351
+ console.error("Browser connected to WebSocket");
2352
+ ws.on("close", () => {
2353
+ console.error("Browser disconnected from WebSocket");
2354
+ });
2355
+ });
2356
+ function broadcastRefresh() {
2357
+ wss.clients.forEach((client) => {
2358
+ if (client.readyState === 1) {
2359
+ client.send(JSON.stringify({ type: "refresh" }));
2360
+ }
2361
+ });
2362
+ }
2363
+ console.error("Starting file watcher...");
2364
+ const watcher = watchProject(projectRoot, {
2365
+ onFileChanged: async (filePath) => {
2366
+ console.error(`File changed: ${filePath} \u2014 re-parsing project...`);
2367
+ try {
2368
+ const parsedFiles = parseProject(projectRoot);
2369
+ const newGraph = buildGraph(parsedFiles);
2370
+ graph.clear();
2371
+ newGraph.forEachNode((node, attrs) => {
2372
+ graph.addNode(node, attrs);
2373
+ });
2374
+ newGraph.forEachEdge((edge, attrs, source, target) => {
2375
+ graph.addEdge(source, target, attrs);
2376
+ });
2377
+ vizData = prepareVizData(graph, projectRoot);
2378
+ broadcastRefresh();
2379
+ console.error(`Graph updated (${vizData.stats.totalSymbols} symbols, ${vizData.stats.totalCrossFileEdges} edges)`);
2380
+ } catch (error) {
2381
+ console.error(`Failed to update graph for ${filePath}:`, error);
2382
+ }
2383
+ },
2384
+ onFileAdded: async (filePath) => {
2385
+ console.error(`File added: ${filePath} \u2014 re-parsing project...`);
2386
+ try {
2387
+ const parsedFiles = parseProject(projectRoot);
2388
+ const newGraph = buildGraph(parsedFiles);
2389
+ graph.clear();
2390
+ newGraph.forEachNode((node, attrs) => {
2391
+ graph.addNode(node, attrs);
2392
+ });
2393
+ newGraph.forEachEdge((edge, attrs, source, target) => {
2394
+ graph.addEdge(source, target, attrs);
2395
+ });
2396
+ vizData = prepareVizData(graph, projectRoot);
2397
+ broadcastRefresh();
2398
+ console.error(`Graph updated (${vizData.stats.totalSymbols} symbols, ${vizData.stats.totalCrossFileEdges} edges)`);
2399
+ } catch (error) {
2400
+ console.error(`Failed to update graph for ${filePath}:`, error);
2401
+ }
2402
+ },
2403
+ onFileDeleted: (filePath) => {
2404
+ console.error(`File deleted: ${filePath} \u2014 re-parsing project...`);
2405
+ try {
2406
+ const parsedFiles = parseProject(projectRoot);
2407
+ const newGraph = buildGraph(parsedFiles);
2408
+ graph.clear();
2409
+ newGraph.forEachNode((node, attrs) => {
2410
+ graph.addNode(node, attrs);
2411
+ });
2412
+ newGraph.forEachEdge((edge, attrs, source, target) => {
2413
+ graph.addEdge(source, target, attrs);
2414
+ });
2415
+ vizData = prepareVizData(graph, projectRoot);
2416
+ broadcastRefresh();
2417
+ console.error(`Graph updated (${vizData.stats.totalSymbols} symbols, ${vizData.stats.totalCrossFileEdges} edges)`);
2418
+ } catch (error) {
2419
+ console.error(`Failed to remove ${filePath} from graph:`, error);
2420
+ }
2421
+ }
2422
+ });
2423
+ process.on("SIGINT", () => {
2424
+ console.error("\nShutting down visualization server...");
2425
+ activeServer = null;
2426
+ watcher.close();
2427
+ wss.close();
2428
+ server.close(() => {
2429
+ process.exit(0);
2430
+ });
2431
+ });
2432
+ const url = `http://127.0.0.1:${port}`;
2433
+ return { server, url, alreadyRunning: false };
2434
+ }
2435
+
2436
+ // src/mcp/state.ts
2437
+ function createEmptyState() {
2438
+ return {
2439
+ graph: null,
2440
+ projectRoot: null,
2441
+ projectName: null,
2442
+ watcher: null
2443
+ };
2444
+ }
2445
+ function isProjectLoaded(state) {
2446
+ return state.graph !== null && state.projectRoot !== null;
2447
+ }
2448
+
2449
+ // src/graph/updater.ts
2450
+ import { join as join8 } from "path";
2451
+ function removeFileFromGraph(graph, filePath) {
2452
+ const nodesToRemove = [];
2453
+ graph.forEachNode((node, attrs) => {
2454
+ if (attrs.filePath === filePath) {
2455
+ nodesToRemove.push(node);
2456
+ }
2457
+ });
2458
+ nodesToRemove.forEach((node) => {
2459
+ try {
2460
+ graph.dropNode(node);
2461
+ } catch (error) {
2462
+ }
2463
+ });
2464
+ }
2465
+ function addFileToGraph(graph, parsedFile) {
2466
+ for (const symbol of parsedFile.symbols) {
2467
+ const nodeId = `${parsedFile.filePath}::${symbol.name}`;
2468
+ try {
2469
+ graph.addNode(nodeId, {
2470
+ name: symbol.name,
2471
+ kind: symbol.kind,
2472
+ filePath: parsedFile.filePath,
2473
+ startLine: symbol.location.startLine,
2474
+ endLine: symbol.location.endLine,
2475
+ exported: symbol.exported,
2476
+ scope: symbol.scope
2477
+ });
2478
+ } catch (error) {
2479
+ }
2480
+ }
2481
+ for (const edge of parsedFile.edges) {
2482
+ try {
2483
+ graph.mergeEdge(edge.source, edge.target, {
2484
+ kind: edge.kind,
2485
+ sourceFile: edge.sourceFile,
2486
+ targetFile: edge.targetFile
2487
+ });
2488
+ } catch (error) {
2489
+ }
2490
+ }
2491
+ }
2492
+ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2493
+ removeFileFromGraph(graph, relativeFilePath);
2494
+ const absolutePath = join8(projectRoot, relativeFilePath);
2495
+ try {
2496
+ const parsedFile = parseTypeScriptFile(absolutePath, relativeFilePath);
2497
+ addFileToGraph(graph, parsedFile);
2498
+ } catch (error) {
2499
+ console.error(`Failed to parse file ${relativeFilePath}:`, error);
2500
+ }
2501
+ }
2502
+
2503
+ // src/mcp/server.ts
2504
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2505
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2506
+
2507
+ // src/mcp/tools.ts
2508
+ import { dirname as dirname6 } from "path";
2509
+
2510
+ // src/mcp/connect.ts
2511
+ import simpleGit from "simple-git";
2512
+ import { existsSync as existsSync5 } from "fs";
2513
+ import { join as join9, basename as basename2, resolve as resolve2 } from "path";
2514
+ import { tmpdir, homedir } from "os";
2515
+ function validateProjectPath(source) {
2516
+ const resolved = resolve2(source);
2517
+ const blockedPaths = [
2518
+ "/etc",
2519
+ "/var",
2520
+ "/usr",
2521
+ "/bin",
2522
+ "/sbin",
2523
+ "/boot",
2524
+ "/proc",
2525
+ "/sys",
2526
+ join9(homedir(), ".ssh"),
2527
+ join9(homedir(), ".gnupg"),
2528
+ join9(homedir(), ".aws"),
2529
+ join9(homedir(), ".config"),
2530
+ join9(homedir(), ".env")
2531
+ ];
2532
+ for (const blocked of blockedPaths) {
2533
+ if (resolved.startsWith(blocked)) {
2534
+ return { valid: false, error: `Access denied: ${blocked} is a protected path` };
2535
+ }
2536
+ }
2537
+ return { valid: true };
2538
+ }
2539
+ async function connectToRepo(source, subdirectory, state) {
2540
+ try {
2541
+ let projectRoot;
2542
+ let projectName;
2543
+ const isGitHub = source.startsWith("https://github.com/") || source.startsWith("git@github.com:");
2544
+ if (isGitHub) {
2545
+ const match = source.match(/[\/:]([^\/]+?)(?:\.git)?$/);
2546
+ if (!match) {
2547
+ return {
2548
+ error: "Invalid GitHub URL",
2549
+ message: "Could not parse repository name from URL"
2550
+ };
2551
+ }
2552
+ projectName = match[1];
2553
+ const reposDir = join9(tmpdir(), "depwire-repos");
2554
+ const cloneDir = join9(reposDir, projectName);
2555
+ console.error(`Connecting to GitHub repo: ${source}`);
2556
+ const git = simpleGit();
2557
+ if (existsSync5(cloneDir)) {
2558
+ console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
2559
+ try {
2560
+ await git.cwd(cloneDir).pull();
2561
+ } catch (error) {
2562
+ console.error(`Pull failed, using existing clone: ${error}`);
2563
+ }
2564
+ } else {
2565
+ console.error(`Cloning ${source} to ${cloneDir}...`);
2566
+ try {
2567
+ await git.clone(source, cloneDir, ["--depth", "1", "--no-recurse-submodules", "--single-branch"]);
2568
+ } catch (error) {
2569
+ return {
2570
+ error: "Failed to clone repository",
2571
+ message: `Git clone failed: ${error}. Ensure git is installed and the URL is correct.`
2572
+ };
2573
+ }
2574
+ }
2575
+ projectRoot = subdirectory ? join9(cloneDir, subdirectory) : cloneDir;
2576
+ } else {
2577
+ const validation2 = validateProjectPath(source);
2578
+ if (!validation2.valid) {
2579
+ return {
2580
+ error: "Access denied",
2581
+ message: validation2.error
2582
+ };
2583
+ }
2584
+ if (!existsSync5(source)) {
2585
+ return {
2586
+ error: "Directory not found",
2587
+ message: `Directory does not exist: ${source}`
2588
+ };
2589
+ }
2590
+ projectRoot = subdirectory ? join9(source, subdirectory) : source;
2591
+ projectName = basename2(projectRoot);
2592
+ }
2593
+ const validation = validateProjectPath(projectRoot);
2594
+ if (!validation.valid) {
2595
+ return {
2596
+ error: "Access denied",
2597
+ message: validation.error
2598
+ };
2599
+ }
2600
+ if (!existsSync5(projectRoot)) {
2601
+ return {
2602
+ error: "Project root not found",
2603
+ message: `Directory does not exist: ${projectRoot}`
2604
+ };
2605
+ }
2606
+ console.error(`Parsing project at ${projectRoot}...`);
2607
+ if (state.watcher) {
2608
+ console.error("Stopping previous file watcher...");
2609
+ await state.watcher.close();
2610
+ state.watcher = null;
2611
+ }
2612
+ const parsedFiles = await parseProject(projectRoot);
2613
+ if (parsedFiles.length === 0) {
2614
+ return {
2615
+ error: "No source files found",
2616
+ message: `No supported source files (.ts, .tsx, .js, .jsx, .py, .go) found in ${projectRoot}`
2617
+ };
2618
+ }
2619
+ const graph = buildGraph(parsedFiles);
2620
+ state.graph = graph;
2621
+ state.projectRoot = projectRoot;
2622
+ state.projectName = projectName;
2623
+ console.error(`Parsed ${parsedFiles.length} files`);
2624
+ console.error("Starting file watcher...");
2625
+ state.watcher = watchProject(projectRoot, {
2626
+ onFileChanged: async (filePath) => {
2627
+ console.error(`File changed: ${filePath}`);
2628
+ try {
2629
+ await updateFileInGraph(state.graph, projectRoot, filePath);
2630
+ console.error(`Graph updated for ${filePath}`);
2631
+ } catch (error) {
2632
+ console.error(`Failed to update graph for ${filePath}: ${error}`);
2633
+ }
2634
+ },
2635
+ onFileAdded: async (filePath) => {
2636
+ console.error(`File added: ${filePath}`);
2637
+ try {
2638
+ await updateFileInGraph(state.graph, projectRoot, filePath);
2639
+ console.error(`Graph updated for ${filePath}`);
2640
+ } catch (error) {
2641
+ console.error(`Failed to update graph for ${filePath}: ${error}`);
2642
+ }
2643
+ },
2644
+ onFileDeleted: (filePath) => {
2645
+ console.error(`File deleted: ${filePath}`);
2646
+ try {
2647
+ const fileNodes = state.graph.filterNodes(
2648
+ (node, attrs) => attrs.filePath === filePath
2649
+ );
2650
+ fileNodes.forEach((node) => state.graph.dropNode(node));
2651
+ console.error(`Removed ${filePath} from graph`);
2652
+ } catch (error) {
2653
+ console.error(`Failed to remove ${filePath} from graph: ${error}`);
2654
+ }
2655
+ }
2656
+ });
2657
+ const summary = getArchitectureSummary(graph);
2658
+ const mostConnected = summary.mostConnectedFiles.slice(0, 3);
2659
+ const languageBreakdown = {};
2660
+ parsedFiles.forEach((file) => {
2661
+ const ext = file.filePath.toLowerCase();
2662
+ let lang;
2663
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) {
2664
+ lang = "typescript";
2665
+ } else if (ext.endsWith(".py")) {
2666
+ lang = "python";
2667
+ } else if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) {
2668
+ lang = "javascript";
2669
+ } else if (ext.endsWith(".go")) {
2670
+ lang = "go";
2671
+ } else {
2672
+ lang = "other";
2673
+ }
2674
+ languageBreakdown[lang] = (languageBreakdown[lang] || 0) + 1;
2675
+ });
2676
+ return {
2677
+ connected: true,
2678
+ projectRoot,
2679
+ projectName,
2680
+ stats: {
2681
+ files: summary.totalFiles,
2682
+ symbols: summary.totalSymbols,
2683
+ edges: summary.totalEdges,
2684
+ crossFileEdges: summary.crossFileEdges,
2685
+ languages: languageBreakdown
2686
+ },
2687
+ mostConnectedFiles: mostConnected.map((f) => ({
2688
+ path: f.filePath,
2689
+ connections: f.incomingCount + f.outgoingCount
2690
+ })),
2691
+ summary: `Connected to ${projectName}. Found ${summary.totalFiles} files with ${summary.totalSymbols} symbols and ${summary.crossFileEdges} cross-file edges.`
2692
+ };
2693
+ } catch (error) {
2694
+ console.error("Error in connectToRepo:", error);
2695
+ return {
2696
+ error: "Connection failed",
2697
+ message: String(error)
2698
+ };
2699
+ }
2700
+ }
2701
+
2702
+ // src/mcp/tools.ts
2703
+ function getToolsList() {
2704
+ return [
2705
+ {
2706
+ name: "connect_repo",
2707
+ description: "Connect Depwire to a codebase for analysis. Accepts a local directory path or a GitHub repository URL. If a GitHub URL is provided, the repo will be cloned automatically. This replaces the currently loaded project.",
2708
+ inputSchema: {
2709
+ type: "object",
2710
+ properties: {
2711
+ source: {
2712
+ type: "string",
2713
+ description: "Local directory path (e.g., '/Users/me/project') or GitHub URL (e.g., 'https://github.com/vercel/next.js')"
2714
+ },
2715
+ subdirectory: {
2716
+ type: "string",
2717
+ description: "Subdirectory within the repo to analyze (optional, e.g., 'packages/core/src')"
2718
+ }
2719
+ },
2720
+ required: ["source"]
2721
+ }
2722
+ },
2723
+ {
2724
+ name: "get_symbol_info",
2725
+ description: "Look up detailed information about a symbol (function, class, variable, type, etc.) by name. Returns file location, type, line numbers, and export status.",
2726
+ inputSchema: {
2727
+ type: "object",
2728
+ properties: {
2729
+ name: {
2730
+ type: "string",
2731
+ description: "The symbol name to look up (e.g., 'UserService', 'handleAuth')"
2732
+ }
2733
+ },
2734
+ required: ["name"]
2735
+ }
2736
+ },
2737
+ {
2738
+ name: "get_dependencies",
2739
+ description: "Get all symbols that a given symbol depends on (what does this symbol use/import/call?).",
2740
+ inputSchema: {
2741
+ type: "object",
2742
+ properties: {
2743
+ symbol: {
2744
+ type: "string",
2745
+ description: "Symbol name or ID to analyze"
2746
+ }
2747
+ },
2748
+ required: ["symbol"]
2749
+ }
2750
+ },
2751
+ {
2752
+ name: "get_dependents",
2753
+ description: "Get all symbols that depend on a given symbol (what uses this symbol?).",
2754
+ inputSchema: {
2755
+ type: "object",
2756
+ properties: {
2757
+ symbol: {
2758
+ type: "string",
2759
+ description: "Symbol name or ID to analyze"
2760
+ }
2761
+ },
2762
+ required: ["symbol"]
2763
+ }
2764
+ },
2765
+ {
2766
+ name: "impact_analysis",
2767
+ description: "Analyze what would break if a symbol is changed, renamed, or removed. Shows direct dependents, transitive dependents (chain reaction), and all affected files. Use this before making changes to understand the blast radius.",
2768
+ inputSchema: {
2769
+ type: "object",
2770
+ properties: {
2771
+ symbol: {
2772
+ type: "string",
2773
+ description: "The symbol name or ID to analyze"
2774
+ }
2775
+ },
2776
+ required: ["symbol"]
2777
+ }
2778
+ },
2779
+ {
2780
+ name: "get_file_context",
2781
+ description: "Get complete context about a file \u2014 all symbols defined in it, all imports, all exports, and all files that import from it.",
2782
+ inputSchema: {
2783
+ type: "object",
2784
+ properties: {
2785
+ filePath: {
2786
+ type: "string",
2787
+ description: "Relative file path (e.g., 'services/UserService.ts')"
2788
+ }
2789
+ },
2790
+ required: ["filePath"]
2791
+ }
2792
+ },
2793
+ {
2794
+ name: "search_symbols",
2795
+ description: "Search for symbols by name across the entire codebase. Supports partial matching.",
2796
+ inputSchema: {
2797
+ type: "object",
2798
+ properties: {
2799
+ query: {
2800
+ type: "string",
2801
+ description: "Search query (case-insensitive substring match)"
2802
+ },
2803
+ limit: {
2804
+ type: "number",
2805
+ description: "Maximum results to return (default: 20)"
2806
+ }
2807
+ },
2808
+ required: ["query"]
2809
+ }
2810
+ },
2811
+ {
2812
+ name: "get_architecture_summary",
2813
+ description: "Get a high-level overview of the project's architecture \u2014 file count, symbol count, most connected files, dependency hotspots, and orphan files.",
2814
+ inputSchema: {
2815
+ type: "object",
2816
+ properties: {}
2817
+ }
2818
+ },
2819
+ {
2820
+ name: "list_files",
2821
+ description: "List all files in the project with basic stats.",
2822
+ inputSchema: {
2823
+ type: "object",
2824
+ properties: {
2825
+ directory: {
2826
+ type: "string",
2827
+ description: "Filter to a specific subdirectory (optional)"
2828
+ }
2829
+ }
2830
+ }
2831
+ },
2832
+ {
2833
+ name: "visualize_graph",
2834
+ description: "Render an interactive arc diagram visualization of the current codebase's cross-reference graph. Shows files as bars along the bottom and dependency arcs connecting them, colored by distance. The visualization appears inline in the conversation.",
2835
+ inputSchema: {
2836
+ type: "object",
2837
+ properties: {
2838
+ highlight: {
2839
+ type: "string",
2840
+ description: "File or symbol name to highlight in the visualization (optional)"
2841
+ },
2842
+ maxFiles: {
2843
+ type: "number",
2844
+ description: "Limit to top N most connected files (optional, default: all)"
2845
+ }
2846
+ }
2847
+ }
2848
+ }
2849
+ ];
2850
+ }
2851
+ async function handleToolCall(name, args, state) {
2852
+ try {
2853
+ let result;
2854
+ if (name === "connect_repo") {
2855
+ result = await connectToRepo(args.source, args.subdirectory, state);
2856
+ } else if (name === "get_architecture_summary") {
2857
+ if (!isProjectLoaded(state)) {
2858
+ result = {
2859
+ status: "no_project",
2860
+ message: "No project loaded. Use connect_repo to analyze a codebase."
2861
+ };
2862
+ } else {
2863
+ result = handleGetArchitectureSummary(state.graph);
2864
+ }
2865
+ } else if (name === "visualize_graph") {
2866
+ if (!isProjectLoaded(state)) {
2867
+ result = {
2868
+ error: "No project loaded",
2869
+ message: "Use connect_repo to connect to a codebase first"
2870
+ };
2871
+ } else {
2872
+ result = await handleVisualizeGraph(args.highlight, args.maxFiles, state);
2873
+ }
2874
+ } else {
2875
+ if (!isProjectLoaded(state)) {
2876
+ result = {
2877
+ error: "No project loaded",
2878
+ message: "Use connect_repo to connect to a codebase first"
2879
+ };
2880
+ } else {
2881
+ const graph = state.graph;
2882
+ switch (name) {
2883
+ case "get_symbol_info":
2884
+ result = handleGetSymbolInfo(args.name, graph);
2885
+ break;
2886
+ case "get_dependencies":
2887
+ result = handleGetDependencies(args.symbol, graph);
2888
+ break;
2889
+ case "get_dependents":
2890
+ result = handleGetDependents(args.symbol, graph);
2891
+ break;
2892
+ case "impact_analysis":
2893
+ result = handleImpactAnalysis(args.symbol, graph);
2894
+ break;
2895
+ case "get_file_context":
2896
+ result = handleGetFileContext(args.filePath, graph);
2897
+ break;
2898
+ case "search_symbols":
2899
+ result = handleSearchSymbols(args.query, args.limit || 20, graph);
2900
+ break;
2901
+ case "list_files":
2902
+ result = handleListFiles(args.directory, graph);
2903
+ break;
2904
+ default:
2905
+ result = { error: `Unknown tool: ${name}` };
2906
+ }
2907
+ }
2908
+ }
2909
+ if (result && typeof result === "object" && "_mcpAppResponse" in result) {
2910
+ const appResult = result;
2911
+ return {
2912
+ content: [
2913
+ {
2914
+ type: "text",
2915
+ text: appResult.text
2916
+ },
2917
+ {
2918
+ type: "resource",
2919
+ resource: {
2920
+ uri: "ui://depwire/arc-diagram",
2921
+ mimeType: "text/html;profile=mcp-app",
2922
+ text: appResult.html
2923
+ }
2924
+ }
2925
+ ]
2926
+ };
2927
+ }
2928
+ if (result && typeof result === "object" && "content" in result && Array.isArray(result.content)) {
2929
+ return result;
2930
+ }
2931
+ return {
2932
+ content: [
2933
+ {
2934
+ type: "text",
2935
+ text: JSON.stringify(result, null, 2)
2936
+ }
2937
+ ]
2938
+ };
2939
+ } catch (error) {
2940
+ console.error("Error handling tool call:", error);
2941
+ return {
2942
+ content: [
2943
+ {
2944
+ type: "text",
2945
+ text: JSON.stringify({ error: String(error) }, null, 2)
2946
+ }
2947
+ ]
2948
+ };
2949
+ }
2950
+ }
2951
+ function handleGetSymbolInfo(name, graph) {
2952
+ const matches = searchSymbols(graph, name);
2953
+ const exactMatches = matches.filter((m) => m.name.toLowerCase() === name.toLowerCase());
2954
+ const results = exactMatches.length > 0 ? exactMatches : matches.slice(0, 10);
2955
+ return {
2956
+ matches: results.map((m) => ({
2957
+ id: m.id,
2958
+ name: m.name,
2959
+ kind: m.kind,
2960
+ filePath: m.filePath,
2961
+ startLine: m.startLine,
2962
+ endLine: m.endLine,
2963
+ exported: m.exported,
2964
+ scope: m.scope
2965
+ })),
2966
+ count: results.length
2967
+ };
2968
+ }
2969
+ function handleGetDependencies(symbol, graph) {
2970
+ const matches = searchSymbols(graph, symbol);
2971
+ if (matches.length === 0) {
2972
+ return {
2973
+ error: `Symbol '${symbol}' not found`,
2974
+ suggestion: "Try using search_symbols to find available symbols"
2975
+ };
2976
+ }
2977
+ const target = matches[0];
2978
+ const deps = getDependencies(graph, target.id);
2979
+ const grouped = {};
2980
+ graph.forEachOutEdge(target.id, (edge, attrs, source, targetNode) => {
2981
+ const kind = attrs.kind;
2982
+ if (!grouped[kind]) {
2983
+ grouped[kind] = [];
2984
+ }
2985
+ const targetAttrs = graph.getNodeAttributes(targetNode);
2986
+ grouped[kind].push({
2987
+ name: targetAttrs.name,
2988
+ filePath: targetAttrs.filePath,
2989
+ kind: targetAttrs.kind
2990
+ });
2991
+ });
2992
+ const totalCount = Object.values(grouped).reduce((sum, arr) => sum + arr.length, 0);
2993
+ return {
2994
+ symbol: `${target.filePath}::${target.name}`,
2995
+ dependencies: grouped,
2996
+ totalCount
2997
+ };
2998
+ }
2999
+ function handleGetDependents(symbol, graph) {
3000
+ const matches = searchSymbols(graph, symbol);
3001
+ if (matches.length === 0) {
3002
+ return {
3003
+ error: `Symbol '${symbol}' not found`,
3004
+ suggestion: "Try using search_symbols to find available symbols"
3005
+ };
3006
+ }
3007
+ const target = matches[0];
3008
+ const deps = getDependents(graph, target.id);
3009
+ const grouped = {};
3010
+ graph.forEachInEdge(target.id, (edge, attrs, source, targetNode) => {
3011
+ const kind = attrs.kind;
3012
+ if (!grouped[kind]) {
3013
+ grouped[kind] = [];
3014
+ }
3015
+ const sourceAttrs = graph.getNodeAttributes(source);
3016
+ grouped[kind].push({
3017
+ name: sourceAttrs.name,
3018
+ filePath: sourceAttrs.filePath,
3019
+ kind: sourceAttrs.kind
3020
+ });
3021
+ });
3022
+ const totalCount = Object.values(grouped).reduce((sum, arr) => sum + arr.length, 0);
3023
+ return {
3024
+ symbol: `${target.filePath}::${target.name}`,
3025
+ dependents: grouped,
3026
+ totalCount
3027
+ };
3028
+ }
3029
+ function handleImpactAnalysis(symbol, graph) {
3030
+ const matches = searchSymbols(graph, symbol);
3031
+ if (matches.length === 0) {
3032
+ return {
3033
+ error: `Symbol '${symbol}' not found`,
3034
+ suggestion: "Try using search_symbols to find available symbols"
3035
+ };
3036
+ }
3037
+ const target = matches[0];
3038
+ const impact = getImpact(graph, target.id);
3039
+ const directWithKinds = impact.directDependents.map((dep) => {
3040
+ let relationship = "unknown";
3041
+ graph.forEachEdge(dep.id, target.id, (edge, attrs) => {
3042
+ relationship = attrs.kind;
3043
+ });
3044
+ return {
3045
+ name: dep.name,
3046
+ filePath: dep.filePath,
3047
+ kind: dep.kind,
3048
+ relationship
3049
+ };
3050
+ });
3051
+ const transitiveFormatted = impact.transitiveDependents.filter((dep) => !impact.directDependents.some((d) => d.id === dep.id)).map((dep) => ({
3052
+ name: dep.name,
3053
+ filePath: dep.filePath,
3054
+ kind: dep.kind
3055
+ }));
3056
+ const summary = `Changing ${target.name} would directly affect ${impact.directDependents.length} symbol(s) and transitively affect ${transitiveFormatted.length} more, across ${impact.affectedFiles.length} file(s).`;
3057
+ return {
3058
+ symbol: {
3059
+ name: target.name,
3060
+ filePath: target.filePath,
3061
+ kind: target.kind
3062
+ },
3063
+ impact: {
3064
+ directDependents: directWithKinds,
3065
+ transitiveDependents: transitiveFormatted,
3066
+ affectedFiles: impact.affectedFiles,
3067
+ summary
3068
+ }
3069
+ };
3070
+ }
3071
+ function handleGetFileContext(filePath, graph) {
3072
+ const fileSymbols = [];
3073
+ graph.forEachNode((nodeId, attrs) => {
3074
+ if (attrs.filePath === filePath) {
3075
+ fileSymbols.push({
3076
+ name: attrs.name,
3077
+ kind: attrs.kind,
3078
+ exported: attrs.exported,
3079
+ startLine: attrs.startLine,
3080
+ endLine: attrs.endLine,
3081
+ scope: attrs.scope
3082
+ });
3083
+ }
3084
+ });
3085
+ if (fileSymbols.length === 0) {
3086
+ return {
3087
+ error: `File '${filePath}' not found`,
3088
+ suggestion: "Use list_files to see available files"
3089
+ };
3090
+ }
3091
+ const importsMap = /* @__PURE__ */ new Map();
3092
+ graph.forEachNode((nodeId, attrs) => {
3093
+ if (attrs.filePath === filePath) {
3094
+ graph.forEachOutEdge(nodeId, (edge, edgeAttrs, source, target) => {
3095
+ const targetAttrs = graph.getNodeAttributes(target);
3096
+ if (targetAttrs.filePath !== filePath) {
3097
+ if (!importsMap.has(targetAttrs.filePath)) {
3098
+ importsMap.set(targetAttrs.filePath, /* @__PURE__ */ new Set());
3099
+ }
3100
+ importsMap.get(targetAttrs.filePath).add(targetAttrs.name);
3101
+ }
3102
+ });
3103
+ }
3104
+ });
3105
+ const imports = Array.from(importsMap.entries()).map(([file, symbols]) => ({
3106
+ from: file,
3107
+ symbols: Array.from(symbols)
3108
+ }));
3109
+ const importedByMap = /* @__PURE__ */ new Map();
3110
+ graph.forEachNode((nodeId, attrs) => {
3111
+ if (attrs.filePath === filePath) {
3112
+ graph.forEachInEdge(nodeId, (edge, edgeAttrs, source, target) => {
3113
+ const sourceAttrs = graph.getNodeAttributes(source);
3114
+ if (sourceAttrs.filePath !== filePath) {
3115
+ if (!importedByMap.has(sourceAttrs.filePath)) {
3116
+ importedByMap.set(sourceAttrs.filePath, /* @__PURE__ */ new Set());
3117
+ }
3118
+ importedByMap.get(sourceAttrs.filePath).add(attrs.name);
3119
+ }
3120
+ });
3121
+ }
3122
+ });
3123
+ const importedBy = Array.from(importedByMap.entries()).map(([file, symbols]) => ({
3124
+ file,
3125
+ symbols: Array.from(symbols)
3126
+ }));
3127
+ const summary = `${filePath} defines ${fileSymbols.length} symbol(s), imports from ${imports.length} file(s), and is imported by ${importedBy.length} file(s).`;
3128
+ return {
3129
+ filePath,
3130
+ symbols: fileSymbols,
3131
+ imports,
3132
+ importedBy,
3133
+ summary
3134
+ };
3135
+ }
3136
+ function handleSearchSymbols(query, limit, graph) {
3137
+ const results = searchSymbols(graph, query);
3138
+ const queryLower = query.toLowerCase();
3139
+ results.sort((a, b) => {
3140
+ const aName = a.name.toLowerCase();
3141
+ const bName = b.name.toLowerCase();
3142
+ if (aName === queryLower && bName !== queryLower) return -1;
3143
+ if (bName === queryLower && aName !== queryLower) return 1;
3144
+ const aStarts = aName.startsWith(queryLower);
3145
+ const bStarts = bName.startsWith(queryLower);
3146
+ if (aStarts && !bStarts) return -1;
3147
+ if (bStarts && !aStarts) return 1;
3148
+ return aName.localeCompare(bName);
3149
+ });
3150
+ const showing = Math.min(limit, results.length);
3151
+ return {
3152
+ query,
3153
+ results: results.slice(0, limit).map((r) => ({
3154
+ name: r.name,
3155
+ kind: r.kind,
3156
+ filePath: r.filePath,
3157
+ exported: r.exported,
3158
+ scope: r.scope
3159
+ })),
3160
+ totalMatches: results.length,
3161
+ showing
3162
+ };
3163
+ }
3164
+ function handleGetArchitectureSummary(graph) {
3165
+ const summary = getArchitectureSummary(graph);
3166
+ const fileSummary = getFileSummary(graph);
3167
+ const dirMap = /* @__PURE__ */ new Map();
3168
+ const languageBreakdown = {};
3169
+ fileSummary.forEach((f) => {
3170
+ const dir = f.filePath.includes("/") ? dirname6(f.filePath) : ".";
3171
+ if (!dirMap.has(dir)) {
3172
+ dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
3173
+ }
3174
+ const entry = dirMap.get(dir);
3175
+ entry.fileCount++;
3176
+ entry.symbolCount += f.symbolCount;
3177
+ const ext = f.filePath.toLowerCase();
3178
+ let lang;
3179
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) {
3180
+ lang = "typescript";
3181
+ } else if (ext.endsWith(".py")) {
3182
+ lang = "python";
3183
+ } else if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) {
3184
+ lang = "javascript";
3185
+ } else {
3186
+ lang = "other";
3187
+ }
3188
+ languageBreakdown[lang] = (languageBreakdown[lang] || 0) + 1;
3189
+ });
3190
+ const directories = Array.from(dirMap.entries()).map(([name, stats]) => ({ name, ...stats })).sort((a, b) => b.symbolCount - a.symbolCount);
3191
+ const summaryText = `Project has ${summary.fileCount} files with ${summary.symbolCount} symbols and ${summary.edgeCount} edges. The most connected file is ${summary.mostConnectedFiles[0]?.filePath || "N/A"} with ${summary.mostConnectedFiles[0]?.connections || 0} connections.`;
3192
+ return {
3193
+ overview: {
3194
+ totalFiles: summary.fileCount,
3195
+ totalSymbols: summary.symbolCount,
3196
+ totalEdges: summary.edgeCount,
3197
+ languages: languageBreakdown
3198
+ },
3199
+ mostConnectedFiles: summary.mostConnectedFiles.slice(0, 10),
3200
+ directories: directories.slice(0, 10),
3201
+ orphanFiles: summary.orphanFiles,
3202
+ summary: summaryText
3203
+ };
3204
+ }
3205
+ function handleListFiles(directory, graph) {
3206
+ const fileSummary = getFileSummary(graph);
3207
+ let filtered = fileSummary;
3208
+ if (directory) {
3209
+ filtered = fileSummary.filter((f) => f.filePath.startsWith(directory));
3210
+ }
3211
+ const files = filtered.map((f) => ({
3212
+ path: f.filePath,
3213
+ symbolCount: f.symbolCount,
3214
+ connections: f.incomingRefs + f.outgoingRefs
3215
+ }));
3216
+ return {
3217
+ files,
3218
+ totalFiles: files.length
3219
+ };
3220
+ }
3221
+ async function handleVisualizeGraph(highlight, maxFiles, state) {
3222
+ const vizData = prepareVizData(state.graph, state.projectRoot);
3223
+ const { url, alreadyRunning } = startVizServer(
3224
+ vizData,
3225
+ state.graph,
3226
+ state.projectRoot,
3227
+ 3456,
3228
+ // Use different port from CLI default to avoid conflicts
3229
+ false
3230
+ // Don't auto-open browser from MCP
3231
+ );
3232
+ const fileCount = maxFiles && maxFiles < vizData.files.length ? maxFiles : vizData.files.length;
3233
+ const arcCount = vizData.arcs.filter((a) => {
3234
+ if (!maxFiles || maxFiles >= vizData.files.length) return true;
3235
+ const topFiles = vizData.files.sort((a2, b) => b.incomingCount + b.outgoingCount - (a2.incomingCount + a2.outgoingCount)).slice(0, maxFiles).map((f) => f.path);
3236
+ return topFiles.includes(a.sourceFile) && topFiles.includes(a.targetFile);
3237
+ }).length;
3238
+ const statusMessage = alreadyRunning ? "Visualization server is already running." : "Visualization server started.";
3239
+ const message = `${statusMessage}
3240
+
3241
+ Interactive arc diagram: ${url}
3242
+
3243
+ The diagram shows ${fileCount} files and ${arcCount} cross-file dependencies.${highlight ? ` Highlighted: ${highlight}` : ""}
3244
+
3245
+ Features:
3246
+ \u2022 Hover over arcs to see source \u2192 target details
3247
+ \u2022 Click files to filter connections
3248
+ \u2022 Search for specific files
3249
+ \u2022 Export as SVG or PNG
3250
+
3251
+ The server will keep running until you end the MCP session or press Ctrl+C.`;
3252
+ return {
3253
+ content: [{ type: "text", text: message }]
3254
+ };
3255
+ }
3256
+
3257
+ // src/mcp/server.ts
3258
+ async function startMcpServer(state) {
3259
+ const server = new Server(
3260
+ {
3261
+ name: "depwire",
3262
+ version: "0.1.0"
3263
+ },
3264
+ {
3265
+ capabilities: {
3266
+ tools: {}
3267
+ }
3268
+ }
3269
+ );
3270
+ const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
3271
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
3272
+ return {
3273
+ tools: getToolsList()
3274
+ };
3275
+ });
3276
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3277
+ const { name, arguments: args } = request.params;
3278
+ return await handleToolCall(name, args || {}, state);
3279
+ });
3280
+ const transport = new StdioServerTransport();
3281
+ await server.connect(transport);
3282
+ console.error("Depwire MCP server started");
3283
+ if (state.projectRoot) {
3284
+ console.error(`Project: ${state.projectRoot}`);
3285
+ } else {
3286
+ console.error("No project loaded. Use connect_repo to connect to a codebase.");
3287
+ }
3288
+ }
3289
+
3290
+ export {
3291
+ parseProject,
3292
+ buildGraph,
3293
+ getImpact,
3294
+ searchSymbols,
3295
+ getArchitectureSummary,
3296
+ prepareVizData,
3297
+ watchProject,
3298
+ startVizServer,
3299
+ createEmptyState,
3300
+ updateFileInGraph,
3301
+ startMcpServer
3302
+ };