depwire-cli 0.3.0 → 0.4.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.
@@ -54,9 +54,50 @@ function fileExists(filePath) {
54
54
  // src/parser/detect.ts
55
55
  import { extname as extname3 } from "path";
56
56
 
57
- // src/parser/typescript.ts
58
- import Parser from "tree-sitter";
59
- import TypeScript from "tree-sitter-typescript";
57
+ // src/parser/wasm-init.ts
58
+ import { Parser, Language } from "web-tree-sitter";
59
+ import path from "path";
60
+ import { fileURLToPath } from "url";
61
+ import { existsSync as existsSync2 } from "fs";
62
+ var initialized = false;
63
+ var languages = /* @__PURE__ */ new Map();
64
+ async function initParser() {
65
+ if (initialized) return;
66
+ await Parser.init();
67
+ const __dirname3 = path.dirname(fileURLToPath(import.meta.url));
68
+ let grammarsDir = path.join(__dirname3, "parser", "grammars");
69
+ if (!existsSync2(grammarsDir)) {
70
+ grammarsDir = path.join(path.dirname(__dirname3), "parser", "grammars");
71
+ }
72
+ if (!existsSync2(grammarsDir)) {
73
+ grammarsDir = path.join(__dirname3, "grammars");
74
+ }
75
+ const grammarFiles = {
76
+ "typescript": "tree-sitter-typescript.wasm",
77
+ "tsx": "tree-sitter-tsx.wasm",
78
+ "javascript": "tree-sitter-javascript.wasm",
79
+ "python": "tree-sitter-python.wasm",
80
+ "go": "tree-sitter-go.wasm"
81
+ };
82
+ for (const [name, file] of Object.entries(grammarFiles)) {
83
+ const wasmPath = path.join(grammarsDir, file);
84
+ const lang = await Language.load(wasmPath);
85
+ languages.set(name, lang);
86
+ }
87
+ initialized = true;
88
+ }
89
+ function getParser(language) {
90
+ if (!initialized) {
91
+ throw new Error("Parser not initialized. Call initParser() first.");
92
+ }
93
+ const lang = languages.get(language);
94
+ if (!lang) {
95
+ throw new Error(`Language '${language}' not loaded.`);
96
+ }
97
+ const parser = new Parser();
98
+ parser.setLanguage(lang);
99
+ return parser;
100
+ }
60
101
 
61
102
  // src/parser/resolver.ts
62
103
  import { join as join2, dirname, resolve, relative as relative2 } from "path";
@@ -152,13 +193,10 @@ function resolveImportPath(importPath, fromFile, projectRoot) {
152
193
  }
153
194
 
154
195
  // 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
196
  function parseTypeScriptFile(filePath, sourceCode, projectRoot) {
160
- const parser2 = filePath.endsWith(".tsx") ? tsxParser : tsParser;
161
- const tree = parser2.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
197
+ const languageType = filePath.endsWith(".tsx") ? "tsx" : "typescript";
198
+ const parser = getParser(languageType);
199
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
162
200
  const context = {
163
201
  filePath,
164
202
  projectRoot,
@@ -613,14 +651,11 @@ var typescriptParser = {
613
651
  };
614
652
 
615
653
  // src/parser/python.ts
616
- import Parser2 from "tree-sitter";
617
- import Python from "tree-sitter-python";
618
654
  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);
655
+ import { existsSync as existsSync3 } from "fs";
622
656
  function parsePythonFile(filePath, sourceCode, projectRoot) {
623
- const tree = pyParser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
657
+ const parser = getParser("python");
658
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
624
659
  const context = {
625
660
  filePath,
626
661
  projectRoot,
@@ -907,13 +942,13 @@ function resolveImportPath2(moduleName, currentFile, projectRoot) {
907
942
  join3(targetDir, modulePath2, "__init__.py")
908
943
  ];
909
944
  for (const candidate of candidates2) {
910
- if (existsSync2(candidate)) {
945
+ if (existsSync3(candidate)) {
911
946
  return candidate.substring(projectRoot.length + 1);
912
947
  }
913
948
  }
914
949
  } else {
915
950
  const initPath = join3(targetDir, "__init__.py");
916
- if (existsSync2(initPath)) {
951
+ if (existsSync3(initPath)) {
917
952
  return initPath.substring(projectRoot.length + 1);
918
953
  }
919
954
  }
@@ -925,7 +960,7 @@ function resolveImportPath2(moduleName, currentFile, projectRoot) {
925
960
  join3(projectRoot, modulePath, "__init__.py")
926
961
  ];
927
962
  for (const candidate of candidates) {
928
- if (existsSync2(candidate)) {
963
+ if (existsSync3(candidate)) {
929
964
  return candidate.substring(projectRoot.length + 1);
930
965
  }
931
966
  }
@@ -982,14 +1017,11 @@ var pythonParser = {
982
1017
  };
983
1018
 
984
1019
  // src/parser/javascript.ts
985
- import Parser3 from "tree-sitter";
986
- import JavaScript from "tree-sitter-javascript";
987
- import { existsSync as existsSync3 } from "fs";
1020
+ import { existsSync as existsSync4 } from "fs";
988
1021
  import { join as join4, dirname as dirname3, extname as extname2 } from "path";
989
- var jsParser = new Parser3();
990
- jsParser.setLanguage(JavaScript);
991
1022
  function parseJavaScriptFile(filePath, sourceCode, projectRoot) {
992
- const tree = jsParser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
1023
+ const parser = getParser("javascript");
1024
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
993
1025
  const context = {
994
1026
  filePath,
995
1027
  projectRoot,
@@ -1402,20 +1434,20 @@ function resolveJavaScriptImport(importPath, currentFile, projectRoot) {
1402
1434
  const indexFiles = ["index.js", "index.jsx", "index.mjs"];
1403
1435
  if (extname2(importPath)) {
1404
1436
  const fullPath = targetPath;
1405
- if (existsSync3(fullPath)) {
1437
+ if (existsSync4(fullPath)) {
1406
1438
  return fullPath.substring(projectRoot.length + 1);
1407
1439
  }
1408
1440
  return null;
1409
1441
  }
1410
1442
  for (const ext of extensions) {
1411
1443
  const candidate = `${targetPath}${ext}`;
1412
- if (existsSync3(candidate)) {
1444
+ if (existsSync4(candidate)) {
1413
1445
  return candidate.substring(projectRoot.length + 1);
1414
1446
  }
1415
1447
  }
1416
1448
  for (const indexFile of indexFiles) {
1417
1449
  const candidate = join4(targetPath, indexFile);
1418
- if (existsSync3(candidate)) {
1450
+ if (existsSync4(candidate)) {
1419
1451
  return candidate.substring(projectRoot.length + 1);
1420
1452
  }
1421
1453
  }
@@ -1486,13 +1518,10 @@ var javascriptParser = {
1486
1518
  };
1487
1519
 
1488
1520
  // 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";
1521
+ import { existsSync as existsSync5, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1492
1522
  import { join as join5, dirname as dirname4 } from "path";
1493
- var parser = new Parser4();
1494
- parser.setLanguage(Go);
1495
1523
  function parseGoFile(filePath, sourceCode, projectRoot) {
1524
+ const parser = getParser("go");
1496
1525
  const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
1497
1526
  const moduleName = readGoModuleName(projectRoot);
1498
1527
  const context = {
@@ -1773,7 +1802,7 @@ function readGoModuleName(projectRoot) {
1773
1802
  let currentDir = projectRoot;
1774
1803
  for (let i = 0; i < 5; i++) {
1775
1804
  const goModPath = join5(currentDir, "go.mod");
1776
- if (existsSync4(goModPath)) {
1805
+ if (existsSync5(goModPath)) {
1777
1806
  try {
1778
1807
  const content = readFileSync2(goModPath, "utf-8");
1779
1808
  const lines = content.split("\n");
@@ -1804,13 +1833,13 @@ function resolveGoImport(importPath, projectRoot, moduleName) {
1804
1833
  }
1805
1834
  const segments = importPath.split("/");
1806
1835
  const packageDir = join5(projectRoot, ...segments);
1807
- if (existsSync4(packageDir)) {
1836
+ if (existsSync5(packageDir)) {
1808
1837
  return findGoFilesInDir(packageDir, projectRoot);
1809
1838
  }
1810
1839
  return [];
1811
1840
  }
1812
1841
  function findGoFilesInDir(dir, projectRoot) {
1813
- if (!existsSync4(dir)) return [];
1842
+ if (!existsSync5(dir)) return [];
1814
1843
  try {
1815
1844
  const files = readdirSync2(dir);
1816
1845
  const goFiles = files.filter((f) => f.endsWith(".go") && !f.endsWith("_test.go"));
@@ -1938,7 +1967,8 @@ function shouldParseFile(fullPath) {
1938
1967
  return false;
1939
1968
  }
1940
1969
  }
1941
- function parseProject(projectRoot, options) {
1970
+ async function parseProject(projectRoot, options) {
1971
+ await initParser();
1942
1972
  const files = scanDirectory(projectRoot);
1943
1973
  const parsedFiles = [];
1944
1974
  let skippedFiles = 0;
@@ -1965,14 +1995,14 @@ function parseProject(projectRoot, options) {
1965
1995
  if (options?.verbose) {
1966
1996
  console.error(`[Parser] Parsing: ${file}`);
1967
1997
  }
1968
- const parser2 = getParserForFile(file);
1969
- if (!parser2) {
1998
+ const parser = getParserForFile(file);
1999
+ if (!parser) {
1970
2000
  console.error(`No parser found for file: ${file}`);
1971
2001
  skippedFiles++;
1972
2002
  continue;
1973
2003
  }
1974
2004
  const sourceCode = readFileSync3(fullPath, "utf-8");
1975
- const parsed = parser2.parseFile(file, sourceCode, projectRoot);
2005
+ const parsed = parser.parseFile(file, sourceCode, projectRoot);
1976
2006
  parsedFiles.push(parsed);
1977
2007
  } catch (err) {
1978
2008
  errorFiles++;
@@ -2044,6 +2074,43 @@ function buildGraph(parsedFiles) {
2044
2074
  }
2045
2075
 
2046
2076
  // src/graph/queries.ts
2077
+ function findSymbols(graph, query) {
2078
+ if (query.includes("::")) {
2079
+ if (graph.hasNode(query)) {
2080
+ const attrs = graph.getNodeAttributes(query);
2081
+ return [{
2082
+ id: query,
2083
+ name: attrs.name,
2084
+ kind: attrs.kind,
2085
+ filePath: attrs.filePath,
2086
+ startLine: attrs.startLine,
2087
+ endLine: attrs.endLine,
2088
+ exported: attrs.exported,
2089
+ scope: attrs.scope,
2090
+ dependentCount: graph.inDegree(query)
2091
+ }];
2092
+ }
2093
+ }
2094
+ const queryLower = query.toLowerCase();
2095
+ const results = [];
2096
+ graph.forEachNode((nodeId, attrs) => {
2097
+ if (attrs.name.toLowerCase() === queryLower) {
2098
+ results.push({
2099
+ id: nodeId,
2100
+ name: attrs.name,
2101
+ kind: attrs.kind,
2102
+ filePath: attrs.filePath,
2103
+ startLine: attrs.startLine,
2104
+ endLine: attrs.endLine,
2105
+ exported: attrs.exported,
2106
+ scope: attrs.scope,
2107
+ dependentCount: graph.inDegree(nodeId)
2108
+ });
2109
+ }
2110
+ });
2111
+ results.sort((a, b) => b.dependentCount - a.dependentCount);
2112
+ return results;
2113
+ }
2047
2114
  function getDependencies(graph, symbolId) {
2048
2115
  if (!graph.hasNode(symbolId)) return [];
2049
2116
  const dependencies = [];
@@ -2360,10 +2427,10 @@ function watchProject(projectRoot, callbacks) {
2360
2427
  // src/viz/server.ts
2361
2428
  import express from "express";
2362
2429
  import open from "open";
2363
- import { fileURLToPath } from "url";
2430
+ import { fileURLToPath as fileURLToPath2 } from "url";
2364
2431
  import { dirname as dirname5, join as join7 } from "path";
2365
2432
  import { WebSocketServer } from "ws";
2366
- var __filename = fileURLToPath(import.meta.url);
2433
+ var __filename = fileURLToPath2(import.meta.url);
2367
2434
  var __dirname2 = dirname5(__filename);
2368
2435
  var activeServer = null;
2369
2436
  async function findAvailablePort(startPort, maxAttempts = 10) {
@@ -2436,7 +2503,7 @@ Depwire visualization running at ${url2}`);
2436
2503
  onFileChanged: async (filePath) => {
2437
2504
  console.error(`File changed: ${filePath} \u2014 re-parsing project...`);
2438
2505
  try {
2439
- const parsedFiles = parseProject(projectRoot, options);
2506
+ const parsedFiles = await parseProject(projectRoot, options);
2440
2507
  const newGraph = buildGraph(parsedFiles);
2441
2508
  graph.clear();
2442
2509
  newGraph.forEachNode((node, attrs) => {
@@ -2455,7 +2522,7 @@ Depwire visualization running at ${url2}`);
2455
2522
  onFileAdded: async (filePath) => {
2456
2523
  console.error(`File added: ${filePath} \u2014 re-parsing project...`);
2457
2524
  try {
2458
- const parsedFiles = parseProject(projectRoot, options);
2525
+ const parsedFiles = await parseProject(projectRoot, options);
2459
2526
  const newGraph = buildGraph(parsedFiles);
2460
2527
  graph.clear();
2461
2528
  newGraph.forEachNode((node, attrs) => {
@@ -2471,10 +2538,10 @@ Depwire visualization running at ${url2}`);
2471
2538
  console.error(`Failed to update graph for ${filePath}:`, error);
2472
2539
  }
2473
2540
  },
2474
- onFileDeleted: (filePath) => {
2541
+ onFileDeleted: async (filePath) => {
2475
2542
  console.error(`File deleted: ${filePath} \u2014 re-parsing project...`);
2476
2543
  try {
2477
- const parsedFiles = parseProject(projectRoot, options);
2544
+ const parsedFiles = await parseProject(projectRoot, options);
2478
2545
  const newGraph = buildGraph(parsedFiles);
2479
2546
  graph.clear();
2480
2547
  newGraph.forEachNode((node, attrs) => {
@@ -2572,7 +2639,7 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2572
2639
  }
2573
2640
 
2574
2641
  // src/docs/generator.ts
2575
- import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync6 } from "fs";
2642
+ import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync7 } from "fs";
2576
2643
  import { join as join10 } from "path";
2577
2644
 
2578
2645
  // src/docs/architecture.ts
@@ -2688,7 +2755,7 @@ function generateProjectSummary(graph, parseTime) {
2688
2755
  const fileCount = getFileCount(graph);
2689
2756
  const symbolCount = graph.order;
2690
2757
  const edgeCount = graph.size;
2691
- const languages = getLanguageStats(graph);
2758
+ const languages2 = getLanguageStats(graph);
2692
2759
  let output = "";
2693
2760
  output += `- **Total Files:** ${formatNumber(fileCount)}
2694
2761
  `;
@@ -2698,10 +2765,10 @@ function generateProjectSummary(graph, parseTime) {
2698
2765
  `;
2699
2766
  output += `- **Parse Time:** ${parseTime.toFixed(1)}s
2700
2767
  `;
2701
- if (Object.keys(languages).length > 1) {
2768
+ if (Object.keys(languages2).length > 1) {
2702
2769
  output += "\n**Languages:**\n\n";
2703
2770
  const totalFiles = fileCount;
2704
- for (const [lang, count] of Object.entries(languages).sort((a, b) => b[1] - a[1])) {
2771
+ for (const [lang, count] of Object.entries(languages2).sort((a, b) => b[1] - a[1])) {
2705
2772
  output += `- ${lang}: ${count} files (${formatPercent(count, totalFiles)})
2706
2773
  `;
2707
2774
  }
@@ -3620,20 +3687,20 @@ function findLongestPaths(graph, limit) {
3620
3687
  }
3621
3688
  const allPaths = [];
3622
3689
  const visited = /* @__PURE__ */ new Set();
3623
- function dfs(file, path) {
3690
+ function dfs(file, path2) {
3624
3691
  visited.add(file);
3625
- path.push(file);
3692
+ path2.push(file);
3626
3693
  const neighbors = fileGraph.get(file);
3627
3694
  if (!neighbors || neighbors.size === 0) {
3628
- allPaths.push([...path]);
3695
+ allPaths.push([...path2]);
3629
3696
  } else {
3630
3697
  for (const neighbor of neighbors) {
3631
3698
  if (!visited.has(neighbor)) {
3632
- dfs(neighbor, path);
3699
+ dfs(neighbor, path2);
3633
3700
  }
3634
3701
  }
3635
3702
  }
3636
- path.pop();
3703
+ path2.pop();
3637
3704
  visited.delete(file);
3638
3705
  }
3639
3706
  for (const root of roots.slice(0, 10)) {
@@ -3804,8 +3871,8 @@ function getLanguageStats2(graph) {
3804
3871
  }
3805
3872
  function generateQuickOrientation(graph) {
3806
3873
  const fileCount = getFileCount4(graph);
3807
- const languages = getLanguageStats2(graph);
3808
- const primaryLang = Object.entries(languages).sort((a, b) => b[1] - a[1])[0];
3874
+ const languages2 = getLanguageStats2(graph);
3875
+ const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
3809
3876
  const dirs = /* @__PURE__ */ new Set();
3810
3877
  graph.forEachNode((node, attrs) => {
3811
3878
  const dir = dirname7(attrs.filePath);
@@ -4137,11 +4204,11 @@ function generateDepwireUsage(projectRoot) {
4137
4204
  }
4138
4205
 
4139
4206
  // src/docs/metadata.ts
4140
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync } from "fs";
4207
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync } from "fs";
4141
4208
  import { join as join9 } from "path";
4142
4209
  function loadMetadata(outputDir) {
4143
4210
  const metadataPath = join9(outputDir, "metadata.json");
4144
- if (!existsSync5(metadataPath)) {
4211
+ if (!existsSync6(metadataPath)) {
4145
4212
  return null;
4146
4213
  }
4147
4214
  try {
@@ -4196,7 +4263,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
4196
4263
  const generated = [];
4197
4264
  const errors = [];
4198
4265
  try {
4199
- if (!existsSync6(options.outputDir)) {
4266
+ if (!existsSync7(options.outputDir)) {
4200
4267
  mkdirSync(options.outputDir, { recursive: true });
4201
4268
  if (options.verbose) {
4202
4269
  console.log(`Created output directory: ${options.outputDir}`);
@@ -4303,11 +4370,11 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4303
4370
 
4304
4371
  // src/mcp/tools.ts
4305
4372
  import { dirname as dirname8, join as join12 } from "path";
4306
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
4373
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
4307
4374
 
4308
4375
  // src/mcp/connect.ts
4309
4376
  import simpleGit from "simple-git";
4310
- import { existsSync as existsSync7 } from "fs";
4377
+ import { existsSync as existsSync8 } from "fs";
4311
4378
  import { join as join11, basename as basename3, resolve as resolve2 } from "path";
4312
4379
  import { tmpdir, homedir } from "os";
4313
4380
  function validateProjectPath(source) {
@@ -4352,7 +4419,7 @@ async function connectToRepo(source, subdirectory, state) {
4352
4419
  const cloneDir = join11(reposDir, projectName);
4353
4420
  console.error(`Connecting to GitHub repo: ${source}`);
4354
4421
  const git = simpleGit();
4355
- if (existsSync7(cloneDir)) {
4422
+ if (existsSync8(cloneDir)) {
4356
4423
  console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
4357
4424
  try {
4358
4425
  await git.cwd(cloneDir).pull();
@@ -4379,7 +4446,7 @@ async function connectToRepo(source, subdirectory, state) {
4379
4446
  message: validation2.error
4380
4447
  };
4381
4448
  }
4382
- if (!existsSync7(source)) {
4449
+ if (!existsSync8(source)) {
4383
4450
  return {
4384
4451
  error: "Directory not found",
4385
4452
  message: `Directory does not exist: ${source}`
@@ -4395,7 +4462,7 @@ async function connectToRepo(source, subdirectory, state) {
4395
4462
  message: validation.error
4396
4463
  };
4397
4464
  }
4398
- if (!existsSync7(projectRoot)) {
4465
+ if (!existsSync8(projectRoot)) {
4399
4466
  return {
4400
4467
  error: "Project root not found",
4401
4468
  message: `Directory does not exist: ${projectRoot}`
@@ -4520,13 +4587,13 @@ function getToolsList() {
4520
4587
  },
4521
4588
  {
4522
4589
  name: "get_symbol_info",
4523
- description: "Look up detailed information about a symbol (function, class, variable, type, etc.) by name. Returns file location, type, line numbers, and export status.",
4590
+ description: "Look up detailed information about a symbol (function, class, variable, type, etc.) by name. Pass a symbol name (e.g., 'Router') or a fully qualified ID (e.g., 'src/router.ts::Router') for exact matching. If multiple symbols share the same name, returns all matches for disambiguation.",
4524
4591
  inputSchema: {
4525
4592
  type: "object",
4526
4593
  properties: {
4527
4594
  name: {
4528
4595
  type: "string",
4529
- description: "The symbol name to look up (e.g., 'UserService', 'handleAuth')"
4596
+ description: "The symbol name to look up (e.g., 'UserService') or full ID (e.g., 'src/services/UserService.ts::UserService')"
4530
4597
  }
4531
4598
  },
4532
4599
  required: ["name"]
@@ -4534,13 +4601,13 @@ function getToolsList() {
4534
4601
  },
4535
4602
  {
4536
4603
  name: "get_dependencies",
4537
- description: "Get all symbols that a given symbol depends on (what does this symbol use/import/call?).",
4604
+ description: "Get all symbols that a given symbol depends on (what does this symbol use/import/call?). Pass a symbol name (e.g., 'Router') or a fully qualified ID (e.g., 'src/router.ts::Router') for exact matching. If multiple symbols share the same name, returns all matches for disambiguation.",
4538
4605
  inputSchema: {
4539
4606
  type: "object",
4540
4607
  properties: {
4541
4608
  symbol: {
4542
4609
  type: "string",
4543
- description: "Symbol name or ID to analyze"
4610
+ description: "Symbol name (e.g., 'Router') or full ID (e.g., 'src/router.ts::Router')"
4544
4611
  }
4545
4612
  },
4546
4613
  required: ["symbol"]
@@ -4548,13 +4615,13 @@ function getToolsList() {
4548
4615
  },
4549
4616
  {
4550
4617
  name: "get_dependents",
4551
- description: "Get all symbols that depend on a given symbol (what uses this symbol?).",
4618
+ description: "Get all symbols that depend on a given symbol (what uses this symbol?). Pass a symbol name (e.g., 'Router') or a fully qualified ID (e.g., 'src/router.ts::Router') for exact matching. If multiple symbols share the same name, returns all matches for disambiguation.",
4552
4619
  inputSchema: {
4553
4620
  type: "object",
4554
4621
  properties: {
4555
4622
  symbol: {
4556
4623
  type: "string",
4557
- description: "Symbol name or ID to analyze"
4624
+ description: "Symbol name (e.g., 'Router') or full ID (e.g., 'src/router.ts::Router')"
4558
4625
  }
4559
4626
  },
4560
4627
  required: ["symbol"]
@@ -4562,13 +4629,13 @@ function getToolsList() {
4562
4629
  },
4563
4630
  {
4564
4631
  name: "impact_analysis",
4565
- 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.",
4632
+ description: "Analyze what would break if a symbol is changed, renamed, or removed. Shows direct dependents, transitive dependents (chain reaction), and all affected files. Pass a symbol name (e.g., 'Router') or a fully qualified ID (e.g., 'src/router.ts::Router') for exact matching. If multiple symbols share the same name, returns all matches for disambiguation. Use this before making changes to understand the blast radius.",
4566
4633
  inputSchema: {
4567
4634
  type: "object",
4568
4635
  properties: {
4569
4636
  symbol: {
4570
4637
  type: "string",
4571
- description: "The symbol name or ID to analyze"
4638
+ description: "Symbol name (e.g., 'Router') or full ID (e.g., 'src/router.ts::Router')"
4572
4639
  }
4573
4640
  },
4574
4641
  required: ["symbol"]
@@ -4790,12 +4857,39 @@ async function handleToolCall(name, args, state) {
4790
4857
  };
4791
4858
  }
4792
4859
  }
4860
+ function createDisambiguationResponse(matches, queryName) {
4861
+ const suggestion = matches.length > 0 ? matches[0].id : "";
4862
+ return {
4863
+ ambiguous: true,
4864
+ message: `Found ${matches.length} symbols named '${queryName}'. Please specify which one by using the full ID (e.g., '${suggestion}').`,
4865
+ matches: matches.map((m, index) => ({
4866
+ id: m.id,
4867
+ kind: m.kind,
4868
+ filePath: m.filePath,
4869
+ line: m.startLine,
4870
+ dependents: m.dependentCount,
4871
+ hint: index === 0 && m.dependentCount > 0 ? "Most dependents \u2014 likely the one you want" : ""
4872
+ })),
4873
+ suggestion
4874
+ };
4875
+ }
4793
4876
  function handleGetSymbolInfo(name, graph) {
4794
- const matches = searchSymbols(graph, name);
4795
- const exactMatches = matches.filter((m) => m.name.toLowerCase() === name.toLowerCase());
4796
- const results = exactMatches.length > 0 ? exactMatches : matches.slice(0, 10);
4877
+ const matches = findSymbols(graph, name);
4878
+ if (matches.length === 0) {
4879
+ const fuzzyMatches = searchSymbols(graph, name).slice(0, 10);
4880
+ return {
4881
+ error: `Symbol '${name}' not found`,
4882
+ suggestion: fuzzyMatches.length > 0 ? `Did you mean: ${fuzzyMatches.map((m) => m.name).join(", ")}?` : "Try using search_symbols to find available symbols",
4883
+ fuzzyMatches: fuzzyMatches.map((m) => ({
4884
+ id: m.id,
4885
+ name: m.name,
4886
+ kind: m.kind,
4887
+ filePath: m.filePath
4888
+ }))
4889
+ };
4890
+ }
4797
4891
  return {
4798
- matches: results.map((m) => ({
4892
+ matches: matches.map((m) => ({
4799
4893
  id: m.id,
4800
4894
  name: m.name,
4801
4895
  kind: m.kind,
@@ -4803,19 +4897,24 @@ function handleGetSymbolInfo(name, graph) {
4803
4897
  startLine: m.startLine,
4804
4898
  endLine: m.endLine,
4805
4899
  exported: m.exported,
4806
- scope: m.scope
4900
+ scope: m.scope,
4901
+ dependents: m.dependentCount
4807
4902
  })),
4808
- count: results.length
4903
+ count: matches.length
4809
4904
  };
4810
4905
  }
4811
4906
  function handleGetDependencies(symbol, graph) {
4812
- const matches = searchSymbols(graph, symbol);
4907
+ const matches = findSymbols(graph, symbol);
4813
4908
  if (matches.length === 0) {
4909
+ const fuzzyMatches = searchSymbols(graph, symbol).slice(0, 10);
4814
4910
  return {
4815
4911
  error: `Symbol '${symbol}' not found`,
4816
- suggestion: "Try using search_symbols to find available symbols"
4912
+ suggestion: fuzzyMatches.length > 0 ? `Did you mean: ${fuzzyMatches.map((m) => m.name).join(", ")}?` : "Try using search_symbols to find available symbols"
4817
4913
  };
4818
4914
  }
4915
+ if (matches.length > 1) {
4916
+ return createDisambiguationResponse(matches, symbol);
4917
+ }
4819
4918
  const target = matches[0];
4820
4919
  const deps = getDependencies(graph, target.id);
4821
4920
  const grouped = {};
@@ -4833,19 +4932,23 @@ function handleGetDependencies(symbol, graph) {
4833
4932
  });
4834
4933
  const totalCount = Object.values(grouped).reduce((sum, arr) => sum + arr.length, 0);
4835
4934
  return {
4836
- symbol: `${target.filePath}::${target.name}`,
4935
+ symbol: target.id,
4837
4936
  dependencies: grouped,
4838
4937
  totalCount
4839
4938
  };
4840
4939
  }
4841
4940
  function handleGetDependents(symbol, graph) {
4842
- const matches = searchSymbols(graph, symbol);
4941
+ const matches = findSymbols(graph, symbol);
4843
4942
  if (matches.length === 0) {
4943
+ const fuzzyMatches = searchSymbols(graph, symbol).slice(0, 10);
4844
4944
  return {
4845
4945
  error: `Symbol '${symbol}' not found`,
4846
- suggestion: "Try using search_symbols to find available symbols"
4946
+ suggestion: fuzzyMatches.length > 0 ? `Did you mean: ${fuzzyMatches.map((m) => m.name).join(", ")}?` : "Try using search_symbols to find available symbols"
4847
4947
  };
4848
4948
  }
4949
+ if (matches.length > 1) {
4950
+ return createDisambiguationResponse(matches, symbol);
4951
+ }
4849
4952
  const target = matches[0];
4850
4953
  const deps = getDependents(graph, target.id);
4851
4954
  const grouped = {};
@@ -4863,19 +4966,23 @@ function handleGetDependents(symbol, graph) {
4863
4966
  });
4864
4967
  const totalCount = Object.values(grouped).reduce((sum, arr) => sum + arr.length, 0);
4865
4968
  return {
4866
- symbol: `${target.filePath}::${target.name}`,
4969
+ symbol: target.id,
4867
4970
  dependents: grouped,
4868
4971
  totalCount
4869
4972
  };
4870
4973
  }
4871
4974
  function handleImpactAnalysis(symbol, graph) {
4872
- const matches = searchSymbols(graph, symbol);
4975
+ const matches = findSymbols(graph, symbol);
4873
4976
  if (matches.length === 0) {
4977
+ const fuzzyMatches = searchSymbols(graph, symbol).slice(0, 10);
4874
4978
  return {
4875
4979
  error: `Symbol '${symbol}' not found`,
4876
- suggestion: "Try using search_symbols to find available symbols"
4980
+ suggestion: fuzzyMatches.length > 0 ? `Did you mean: ${fuzzyMatches.map((m) => m.name).join(", ")}?` : "Try using search_symbols to find available symbols"
4877
4981
  };
4878
4982
  }
4983
+ if (matches.length > 1) {
4984
+ return createDisambiguationResponse(matches, symbol);
4985
+ }
4879
4986
  const target = matches[0];
4880
4987
  const impact = getImpact(graph, target.id);
4881
4988
  const directWithKinds = impact.directDependents.map((dep) => {
@@ -4898,6 +5005,7 @@ function handleImpactAnalysis(symbol, graph) {
4898
5005
  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).`;
4899
5006
  return {
4900
5007
  symbol: {
5008
+ id: target.id,
4901
5009
  name: target.name,
4902
5010
  filePath: target.filePath,
4903
5011
  kind: target.kind
@@ -5097,7 +5205,7 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
5097
5205
  }
5098
5206
  async function handleGetProjectDocs(docType, state) {
5099
5207
  const docsDir = join12(state.projectRoot, ".depwire");
5100
- if (!existsSync8(docsDir)) {
5208
+ if (!existsSync9(docsDir)) {
5101
5209
  const errorMessage = `Project documentation has not been generated yet.
5102
5210
 
5103
5211
  Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
@@ -5128,7 +5236,7 @@ Available document types:
5128
5236
  continue;
5129
5237
  }
5130
5238
  const filePath = join12(docsDir, metadata.documents[doc].file);
5131
- if (!existsSync8(filePath)) {
5239
+ if (!existsSync9(filePath)) {
5132
5240
  missing.push(doc);
5133
5241
  continue;
5134
5242
  }
@@ -5159,14 +5267,14 @@ async function handleUpdateProjectDocs(docType, state) {
5159
5267
  const startTime = Date.now();
5160
5268
  const docsDir = join12(state.projectRoot, ".depwire");
5161
5269
  console.error("Regenerating project documentation...");
5162
- const parsedFiles = parseProject(state.projectRoot);
5270
+ const parsedFiles = await parseProject(state.projectRoot);
5163
5271
  const graph = buildGraph(parsedFiles);
5164
5272
  const parseTime = (Date.now() - startTime) / 1e3;
5165
5273
  state.graph = graph;
5166
5274
  const packageJsonPath = join12(__dirname, "../../package.json");
5167
5275
  const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
5168
5276
  const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
5169
- const docsExist = existsSync8(docsDir);
5277
+ const docsExist = existsSync9(docsDir);
5170
5278
  const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
5171
5279
  outputDir: docsDir,
5172
5280
  format: "markdown",
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  startVizServer,
13
13
  updateFileInGraph,
14
14
  watchProject
15
- } from "./chunk-C3LAKUAJ.js";
15
+ } from "./chunk-X2DOGIIG.js";
16
16
 
17
17
  // src/index.ts
18
18
  import { Command } from "commander";
@@ -100,7 +100,7 @@ program.command("parse").description("Parse a TypeScript project and build depen
100
100
  try {
101
101
  const projectRoot = resolve(directory);
102
102
  console.log(`Parsing project: ${projectRoot}`);
103
- const parsedFiles = parseProject(projectRoot, {
103
+ const parsedFiles = await parseProject(projectRoot, {
104
104
  exclude: options.exclude,
105
105
  verbose: options.verbose
106
106
  });
@@ -145,7 +145,7 @@ program.command("query").description("Query impact analysis for a symbol").argum
145
145
  graph = importFromJSON(json);
146
146
  } else {
147
147
  console.log("Parsing project...");
148
- const parsedFiles = parseProject(projectRoot);
148
+ const parsedFiles = await parseProject(projectRoot);
149
149
  graph = buildGraph(parsedFiles);
150
150
  }
151
151
  const matches = searchSymbols(graph, symbolName);
@@ -186,7 +186,7 @@ program.command("viz").description("Launch interactive arc diagram visualization
186
186
  try {
187
187
  const projectRoot = resolve(directory);
188
188
  console.log(`Parsing project: ${projectRoot}`);
189
- const parsedFiles = parseProject(projectRoot, {
189
+ const parsedFiles = await parseProject(projectRoot, {
190
190
  exclude: options.exclude,
191
191
  verbose: options.verbose
192
192
  });
@@ -210,7 +210,7 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
210
210
  if (directory) {
211
211
  const projectRoot = resolve(directory);
212
212
  console.error(`Parsing project: ${projectRoot}`);
213
- const parsedFiles = parseProject(projectRoot);
213
+ const parsedFiles = await parseProject(projectRoot);
214
214
  console.error(`Parsed ${parsedFiles.length} files`);
215
215
  const graph = buildGraph(parsedFiles);
216
216
  console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
@@ -273,7 +273,7 @@ program.command("docs").description("Generate comprehensive codebase documentati
273
273
  addToGitignore(projectRoot, ".depwire/");
274
274
  }
275
275
  console.log(`Parsing project: ${projectRoot}`);
276
- const parsedFiles = parseProject(projectRoot, {
276
+ const parsedFiles = await parseProject(projectRoot, {
277
277
  exclude: options.exclude,
278
278
  verbose: options.verbose
279
279
  });
@@ -6,7 +6,7 @@ import {
6
6
  startMcpServer,
7
7
  updateFileInGraph,
8
8
  watchProject
9
- } from "./chunk-C3LAKUAJ.js";
9
+ } from "./chunk-X2DOGIIG.js";
10
10
 
11
11
  // src/mcpb-entry.ts
12
12
  import { resolve } from "path";
@@ -17,7 +17,7 @@ async function main() {
17
17
  try {
18
18
  const projectRoot = resolve(projectPath);
19
19
  console.error(`[MCPB] Parsing project: ${projectRoot}`);
20
- const parsedFiles = parseProject(projectRoot);
20
+ const parsedFiles = await parseProject(projectRoot);
21
21
  console.error(`[MCPB] Parsed ${parsedFiles.length} files`);
22
22
  const graph = buildGraph(parsedFiles);
23
23
  console.error(`[MCPB] Built graph: ${graph.order} symbols, ${graph.size} edges`);
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "depwire-cli",
3
- "version": "0.3.0",
4
- "description": "Code cross-reference visualization and AI context engine for TypeScript. Analyzes codebases to show dependencies, enables AI tools with MCP, and renders beautiful arc diagrams.",
3
+ "version": "0.4.0",
4
+ "description": "Code cross-reference visualization and AI context engine for TypeScript, JavaScript, Python, and Go. Zero native dependencies works on Windows, macOS, and Linux.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "depwire": "dist/index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "build": "tsup src/index.ts src/mcpb-entry.ts --format esm --dts --clean && npm run copy-static",
11
- "copy-static": "mkdir -p dist/viz/public && cp -r src/viz/public/* dist/viz/public/",
11
+ "copy-static": "mkdir -p dist/viz/public dist/parser/grammars && cp -r src/viz/public/* dist/viz/public/ && cp src/parser/grammars/*.wasm dist/parser/grammars/",
12
12
  "dev": "tsup src/index.ts --format esm --watch",
13
13
  "start": "node dist/index.js",
14
14
  "build:mcpb": "npm run build && ./scripts/build-mcpb.sh"
@@ -59,11 +59,7 @@
59
59
  "minimatch": "^10.2.4",
60
60
  "open": "11.0.0",
61
61
  "simple-git": "3.31.1",
62
- "tree-sitter": "0.21.1",
63
- "tree-sitter-go": "0.21.2",
64
- "tree-sitter-javascript": "0.21.4",
65
- "tree-sitter-python": "0.21.0",
66
- "tree-sitter-typescript": "0.23.2",
62
+ "web-tree-sitter": "^0.26.6",
67
63
  "ws": "8.19.0",
68
64
  "zod": "4.3.6"
69
65
  },