depwire-cli 0.7.1 → 0.9.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.
@@ -29,7 +29,8 @@ function scanDirectory(rootDir, baseDir = rootDir) {
29
29
  const isJavaScript = entry.endsWith(".js") || entry.endsWith(".jsx") || entry.endsWith(".mjs") || entry.endsWith(".cjs");
30
30
  const isPython = entry.endsWith(".py");
31
31
  const isGo = entry.endsWith(".go") && !entry.endsWith("_test.go");
32
- if (isTypeScript || isJavaScript || isPython || isGo) {
32
+ const isRust = entry.endsWith(".rs");
33
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust) {
33
34
  files.push(relative(rootDir, fullPath));
34
35
  }
35
36
  }
@@ -54,6 +55,8 @@ function findProjectRoot(startDir = process.cwd()) {
54
55
  // TypeScript
55
56
  "go.mod",
56
57
  // Go
58
+ "Cargo.toml",
59
+ // Rust
57
60
  "pyproject.toml",
58
61
  // Python (modern)
59
62
  "setup.py",
@@ -80,8 +83,8 @@ function findProjectRoot(startDir = process.cwd()) {
80
83
  }
81
84
 
82
85
  // src/parser/index.ts
83
- import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
84
- import { join as join6 } from "path";
86
+ import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
87
+ import { join as join7 } from "path";
85
88
 
86
89
  // src/parser/detect.ts
87
90
  import { extname as extname3 } from "path";
@@ -109,7 +112,8 @@ async function initParser() {
109
112
  "tsx": "tree-sitter-tsx.wasm",
110
113
  "javascript": "tree-sitter-javascript.wasm",
111
114
  "python": "tree-sitter-python.wasm",
112
- "go": "tree-sitter-go.wasm"
115
+ "go": "tree-sitter-go.wasm",
116
+ "rust": "tree-sitter-rust.wasm"
113
117
  };
114
118
  for (const [name, file] of Object.entries(grammarFiles)) {
115
119
  const wasmPath = path.join(grammarsDir, file);
@@ -1972,12 +1976,429 @@ var goParser = {
1972
1976
  parseFile: parseGoFile
1973
1977
  };
1974
1978
 
1979
+ // src/parser/rust.ts
1980
+ import { existsSync as existsSync6 } from "fs";
1981
+ import { join as join6, dirname as dirname5, relative as relative3 } from "path";
1982
+ function parseRustFile(filePath, sourceCode, projectRoot) {
1983
+ const parser = getParser("rust");
1984
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
1985
+ const context = {
1986
+ filePath,
1987
+ projectRoot,
1988
+ sourceCode,
1989
+ symbols: [],
1990
+ edges: [],
1991
+ currentScope: [],
1992
+ currentModule: []
1993
+ };
1994
+ walkNode5(tree.rootNode, context);
1995
+ return {
1996
+ filePath,
1997
+ symbols: context.symbols,
1998
+ edges: context.edges
1999
+ };
2000
+ }
2001
+ function walkNode5(node, context) {
2002
+ processNode5(node, context);
2003
+ for (let i = 0; i < node.childCount; i++) {
2004
+ const child = node.child(i);
2005
+ if (child) {
2006
+ walkNode5(child, context);
2007
+ }
2008
+ }
2009
+ }
2010
+ function processNode5(node, context) {
2011
+ const type = node.type;
2012
+ switch (type) {
2013
+ case "function_item":
2014
+ processFunctionItem(node, context);
2015
+ break;
2016
+ case "struct_item":
2017
+ processStructItem(node, context);
2018
+ break;
2019
+ case "enum_item":
2020
+ processEnumItem(node, context);
2021
+ break;
2022
+ case "trait_item":
2023
+ processTraitItem(node, context);
2024
+ break;
2025
+ case "impl_item":
2026
+ processImplItem(node, context);
2027
+ break;
2028
+ case "const_item":
2029
+ processConstItem(node, context);
2030
+ break;
2031
+ case "type_item":
2032
+ processTypeItem(node, context);
2033
+ break;
2034
+ case "use_declaration":
2035
+ processUseDeclaration(node, context);
2036
+ break;
2037
+ case "mod_item":
2038
+ processModItem(node, context);
2039
+ break;
2040
+ case "call_expression":
2041
+ processCallExpression5(node, context);
2042
+ break;
2043
+ }
2044
+ }
2045
+ function processFunctionItem(node, context) {
2046
+ const nameNode = node.childForFieldName("name");
2047
+ if (!nameNode) return;
2048
+ const name = nodeText4(nameNode, context);
2049
+ const exported = hasVisibility(node, "pub");
2050
+ const symbolId = `${context.filePath}::${name}`;
2051
+ context.symbols.push({
2052
+ id: symbolId,
2053
+ name,
2054
+ kind: "function",
2055
+ filePath: context.filePath,
2056
+ startLine: node.startPosition.row + 1,
2057
+ endLine: node.endPosition.row + 1,
2058
+ exported
2059
+ });
2060
+ context.currentScope.push(name);
2061
+ const body = node.childForFieldName("body");
2062
+ if (body) {
2063
+ walkNode5(body, context);
2064
+ }
2065
+ context.currentScope.pop();
2066
+ }
2067
+ function processStructItem(node, context) {
2068
+ const nameNode = node.childForFieldName("name");
2069
+ if (!nameNode) return;
2070
+ const name = nodeText4(nameNode, context);
2071
+ const exported = hasVisibility(node, "pub");
2072
+ const symbolId = `${context.filePath}::${name}`;
2073
+ context.symbols.push({
2074
+ id: symbolId,
2075
+ name,
2076
+ kind: "class",
2077
+ // Consistent with other parsers
2078
+ filePath: context.filePath,
2079
+ startLine: node.startPosition.row + 1,
2080
+ endLine: node.endPosition.row + 1,
2081
+ exported
2082
+ });
2083
+ }
2084
+ function processEnumItem(node, context) {
2085
+ const nameNode = node.childForFieldName("name");
2086
+ if (!nameNode) return;
2087
+ const name = nodeText4(nameNode, context);
2088
+ const exported = hasVisibility(node, "pub");
2089
+ const symbolId = `${context.filePath}::${name}`;
2090
+ context.symbols.push({
2091
+ id: symbolId,
2092
+ name,
2093
+ kind: "enum",
2094
+ filePath: context.filePath,
2095
+ startLine: node.startPosition.row + 1,
2096
+ endLine: node.endPosition.row + 1,
2097
+ exported
2098
+ });
2099
+ }
2100
+ function processTraitItem(node, context) {
2101
+ const nameNode = node.childForFieldName("name");
2102
+ if (!nameNode) return;
2103
+ const name = nodeText4(nameNode, context);
2104
+ const exported = hasVisibility(node, "pub");
2105
+ const symbolId = `${context.filePath}::${name}`;
2106
+ context.symbols.push({
2107
+ id: symbolId,
2108
+ name,
2109
+ kind: "interface",
2110
+ filePath: context.filePath,
2111
+ startLine: node.startPosition.row + 1,
2112
+ endLine: node.endPosition.row + 1,
2113
+ exported
2114
+ });
2115
+ }
2116
+ function processImplItem(node, context) {
2117
+ const typeNode = node.childForFieldName("type");
2118
+ if (!typeNode) return;
2119
+ const typeName = extractTypeName2(typeNode, context);
2120
+ if (!typeName) return;
2121
+ for (let i = 0; i < node.childCount; i++) {
2122
+ const child = node.child(i);
2123
+ if (child && child.type === "function_item") {
2124
+ const nameNode = child.childForFieldName("name");
2125
+ if (!nameNode) continue;
2126
+ const name = nodeText4(nameNode, context);
2127
+ const exported = hasVisibility(child, "pub");
2128
+ const symbolId = `${context.filePath}::${typeName}.${name}`;
2129
+ context.symbols.push({
2130
+ id: symbolId,
2131
+ name,
2132
+ kind: "method",
2133
+ filePath: context.filePath,
2134
+ startLine: child.startPosition.row + 1,
2135
+ endLine: child.endPosition.row + 1,
2136
+ exported,
2137
+ scope: typeName
2138
+ });
2139
+ context.currentScope.push(`${typeName}.${name}`);
2140
+ const body = child.childForFieldName("body");
2141
+ if (body) {
2142
+ walkNode5(body, context);
2143
+ }
2144
+ context.currentScope.pop();
2145
+ }
2146
+ }
2147
+ }
2148
+ function processConstItem(node, context) {
2149
+ const nameNode = node.childForFieldName("name");
2150
+ if (!nameNode) return;
2151
+ const name = nodeText4(nameNode, context);
2152
+ const exported = hasVisibility(node, "pub");
2153
+ const symbolId = `${context.filePath}::${name}`;
2154
+ context.symbols.push({
2155
+ id: symbolId,
2156
+ name,
2157
+ kind: "constant",
2158
+ filePath: context.filePath,
2159
+ startLine: node.startPosition.row + 1,
2160
+ endLine: node.endPosition.row + 1,
2161
+ exported
2162
+ });
2163
+ }
2164
+ function processTypeItem(node, context) {
2165
+ const nameNode = node.childForFieldName("name");
2166
+ if (!nameNode) return;
2167
+ const name = nodeText4(nameNode, context);
2168
+ const exported = hasVisibility(node, "pub");
2169
+ const symbolId = `${context.filePath}::${name}`;
2170
+ context.symbols.push({
2171
+ id: symbolId,
2172
+ name,
2173
+ kind: "type_alias",
2174
+ filePath: context.filePath,
2175
+ startLine: node.startPosition.row + 1,
2176
+ endLine: node.endPosition.row + 1,
2177
+ exported
2178
+ });
2179
+ }
2180
+ function processUseDeclaration(node, context) {
2181
+ let pathNode = findChildByType5(node, "scoped_identifier");
2182
+ if (!pathNode) {
2183
+ pathNode = findChildByType5(node, "identifier");
2184
+ }
2185
+ if (!pathNode) {
2186
+ pathNode = findChildByType5(node, "use_as_clause");
2187
+ if (pathNode) {
2188
+ pathNode = pathNode.childForFieldName("path");
2189
+ }
2190
+ }
2191
+ if (!pathNode) {
2192
+ return;
2193
+ }
2194
+ let pathText = nodeText4(pathNode, context);
2195
+ if (!pathText.startsWith("crate::") && !pathText.startsWith("super::") && !pathText.startsWith("self::")) {
2196
+ return;
2197
+ }
2198
+ const segments = pathText.split("::");
2199
+ if (segments.length > 1) {
2200
+ segments.pop();
2201
+ pathText = segments.join("::");
2202
+ }
2203
+ const resolvedFiles = resolveRustImport(pathText, context);
2204
+ if (resolvedFiles.length === 0) return;
2205
+ const sourceId = `${context.filePath}::__file__`;
2206
+ for (const targetFile of resolvedFiles) {
2207
+ const targetId = `${targetFile}::__file__`;
2208
+ context.edges.push({
2209
+ source: sourceId,
2210
+ target: targetId,
2211
+ kind: "imports",
2212
+ filePath: context.filePath,
2213
+ line: node.startPosition.row + 1
2214
+ });
2215
+ }
2216
+ }
2217
+ function processModItem(node, context) {
2218
+ const nameNode = node.childForFieldName("name");
2219
+ if (!nameNode) return;
2220
+ const name = nodeText4(nameNode, context);
2221
+ const body = node.childForFieldName("body");
2222
+ if (!body) {
2223
+ const resolvedFiles = resolveModuleFile(name, context);
2224
+ if (resolvedFiles.length > 0) {
2225
+ const sourceId = `${context.filePath}::__file__`;
2226
+ for (const targetFile of resolvedFiles) {
2227
+ const targetId = `${targetFile}::__file__`;
2228
+ context.edges.push({
2229
+ source: sourceId,
2230
+ target: targetId,
2231
+ kind: "imports",
2232
+ filePath: context.filePath,
2233
+ line: node.startPosition.row + 1
2234
+ });
2235
+ }
2236
+ }
2237
+ }
2238
+ const exported = hasVisibility(node, "pub");
2239
+ const symbolId = `${context.filePath}::${name}`;
2240
+ context.symbols.push({
2241
+ id: symbolId,
2242
+ name,
2243
+ kind: "module",
2244
+ filePath: context.filePath,
2245
+ startLine: node.startPosition.row + 1,
2246
+ endLine: node.endPosition.row + 1,
2247
+ exported
2248
+ });
2249
+ }
2250
+ function processCallExpression5(node, context) {
2251
+ const functionNode = node.childForFieldName("function");
2252
+ if (!functionNode) return;
2253
+ const calleeName = extractCalleeNameFromNode(functionNode, context);
2254
+ if (!calleeName) return;
2255
+ const builtins = ["println!", "print!", "eprintln!", "eprint!", "format!", "panic!", "assert!", "assert_eq!", "assert_ne!", "vec!"];
2256
+ if (builtins.includes(calleeName)) return;
2257
+ const callerId = getCurrentSymbolId5(context);
2258
+ if (!callerId) return;
2259
+ const calleeId = resolveSymbol4(calleeName, context);
2260
+ if (!calleeId) return;
2261
+ context.edges.push({
2262
+ source: callerId,
2263
+ target: calleeId,
2264
+ kind: "calls",
2265
+ filePath: context.filePath,
2266
+ line: node.startPosition.row + 1
2267
+ });
2268
+ }
2269
+ function resolveRustImport(importPath, context) {
2270
+ if (importPath.startsWith("crate::")) {
2271
+ const relativePath = importPath.replace("crate::", "").replace(/::/g, "/");
2272
+ const possibleFiles = [
2273
+ join6(context.projectRoot, "src", `${relativePath}.rs`),
2274
+ join6(context.projectRoot, "src", relativePath, "mod.rs")
2275
+ ];
2276
+ return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
2277
+ }
2278
+ if (importPath.startsWith("super::")) {
2279
+ const currentFileAbs = join6(context.projectRoot, context.filePath);
2280
+ const currentDir = dirname5(currentFileAbs);
2281
+ const parentDir = dirname5(currentDir);
2282
+ const relativePath = importPath.replace("super::", "").replace(/::/g, "/");
2283
+ const possibleFiles = [
2284
+ join6(parentDir, `${relativePath}.rs`),
2285
+ join6(parentDir, relativePath, "mod.rs")
2286
+ ];
2287
+ return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
2288
+ }
2289
+ if (importPath.startsWith("self::")) {
2290
+ const currentFileAbs = join6(context.projectRoot, context.filePath);
2291
+ const currentDir = dirname5(currentFileAbs);
2292
+ const relativePath = importPath.replace("self::", "").replace(/::/g, "/");
2293
+ const possibleFiles = [
2294
+ join6(currentDir, `${relativePath}.rs`),
2295
+ join6(currentDir, relativePath, "mod.rs")
2296
+ ];
2297
+ return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
2298
+ }
2299
+ return [];
2300
+ }
2301
+ function resolveModuleFile(moduleName, context) {
2302
+ const currentFileAbs = join6(context.projectRoot, context.filePath);
2303
+ const currentDir = dirname5(currentFileAbs);
2304
+ const possibleFiles = [
2305
+ join6(currentDir, `${moduleName}.rs`),
2306
+ join6(currentDir, moduleName, "mod.rs")
2307
+ ];
2308
+ return possibleFiles.filter((f) => existsSync6(f)).map((f) => relative3(context.projectRoot, f));
2309
+ }
2310
+ function hasVisibility(node, visibility) {
2311
+ for (let i = 0; i < node.childCount; i++) {
2312
+ const child = node.child(i);
2313
+ if (child && child.type === "visibility_modifier") {
2314
+ const text = nodeText4(child, { sourceCode: node.text });
2315
+ return text === visibility;
2316
+ }
2317
+ }
2318
+ return false;
2319
+ }
2320
+ function extractTypeName2(typeNode, context) {
2321
+ if (typeNode.type === "type_identifier") {
2322
+ return nodeText4(typeNode, context);
2323
+ }
2324
+ if (typeNode.type === "generic_type") {
2325
+ const typeId = findChildByType5(typeNode, "type_identifier");
2326
+ if (typeId) {
2327
+ return nodeText4(typeId, context);
2328
+ }
2329
+ }
2330
+ for (let i = 0; i < typeNode.childCount; i++) {
2331
+ const child = typeNode.child(i);
2332
+ if (child && child.type === "type_identifier") {
2333
+ return nodeText4(child, context);
2334
+ }
2335
+ }
2336
+ return null;
2337
+ }
2338
+ function extractCalleeNameFromNode(functionNode, context) {
2339
+ if (functionNode.type === "identifier") {
2340
+ return nodeText4(functionNode, context);
2341
+ }
2342
+ if (functionNode.type === "field_expression") {
2343
+ const field = functionNode.childForFieldName("field");
2344
+ if (field) {
2345
+ return nodeText4(field, context);
2346
+ }
2347
+ }
2348
+ if (functionNode.type === "scoped_identifier") {
2349
+ const name = functionNode.childForFieldName("name");
2350
+ if (name) {
2351
+ return nodeText4(name, context);
2352
+ }
2353
+ }
2354
+ return null;
2355
+ }
2356
+ function resolveSymbol4(name, context) {
2357
+ const currentFileId = context.filePath;
2358
+ const symbol = context.symbols.find((s) => s.name === name && s.filePath === currentFileId);
2359
+ if (symbol) {
2360
+ return symbol.id;
2361
+ }
2362
+ if (context.currentScope.length > 0) {
2363
+ for (let i = context.currentScope.length - 1; i >= 0; i--) {
2364
+ const scopedId = `${currentFileId}::${context.currentScope[i]}.${name}`;
2365
+ const scopedSymbol = context.symbols.find((s) => s.id === scopedId);
2366
+ if (scopedSymbol) {
2367
+ return scopedId;
2368
+ }
2369
+ }
2370
+ }
2371
+ return null;
2372
+ }
2373
+ function findChildByType5(node, type) {
2374
+ for (let i = 0; i < node.childCount; i++) {
2375
+ const child = node.child(i);
2376
+ if (child && child.type === type) {
2377
+ return child;
2378
+ }
2379
+ }
2380
+ return null;
2381
+ }
2382
+ function nodeText4(node, context) {
2383
+ return context.sourceCode.slice(node.startIndex, node.endIndex);
2384
+ }
2385
+ function getCurrentSymbolId5(context) {
2386
+ if (context.currentScope.length === 0) return null;
2387
+ return `${context.filePath}::${context.currentScope.join(".")}`;
2388
+ }
2389
+ var rustParser = {
2390
+ language: "rust",
2391
+ extensions: [".rs"],
2392
+ parseFile: parseRustFile
2393
+ };
2394
+
1975
2395
  // src/parser/detect.ts
1976
2396
  var parsers = [
1977
2397
  typescriptParser,
1978
2398
  pythonParser,
1979
2399
  javascriptParser,
1980
- goParser
2400
+ goParser,
2401
+ rustParser
1981
2402
  ];
1982
2403
  function getParserForFile(filePath) {
1983
2404
  const ext = extname3(filePath).toLowerCase();
@@ -2007,12 +2428,12 @@ async function parseProject(projectRoot, options) {
2007
2428
  let errorFiles = 0;
2008
2429
  for (const file of files) {
2009
2430
  try {
2010
- const fullPath = join6(projectRoot, file);
2431
+ const fullPath = join7(projectRoot, file);
2011
2432
  if (options?.exclude) {
2012
- const shouldExclude = options.exclude.some(
2433
+ const shouldExclude2 = options.exclude.some(
2013
2434
  (pattern) => minimatch(file, pattern, { matchBase: true })
2014
2435
  );
2015
- if (shouldExclude) {
2436
+ if (shouldExclude2) {
2016
2437
  if (options.verbose) {
2017
2438
  console.error(`[Parser] Excluded: ${file}`);
2018
2439
  }
@@ -2033,7 +2454,7 @@ async function parseProject(projectRoot, options) {
2033
2454
  skippedFiles++;
2034
2455
  continue;
2035
2456
  }
2036
- const sourceCode = readFileSync3(fullPath, "utf-8");
2457
+ const sourceCode = readFileSync4(fullPath, "utf-8");
2037
2458
  const parsed = parser.parseFile(file, sourceCode, projectRoot);
2038
2459
  parsedFiles.push(parsed);
2039
2460
  } catch (err) {
@@ -2089,6 +2510,18 @@ function buildGraph(parsedFiles) {
2089
2510
  exported: false
2090
2511
  });
2091
2512
  }
2513
+ if (edge.target.endsWith("::__file__") && !fileNodes.has(edge.target)) {
2514
+ fileNodes.add(edge.target);
2515
+ const filePath = edge.target.replace("::__file__", "");
2516
+ graph.addNode(edge.target, {
2517
+ name: "__file__",
2518
+ kind: "import",
2519
+ filePath,
2520
+ startLine: 1,
2521
+ endLine: 1,
2522
+ exported: false
2523
+ });
2524
+ }
2092
2525
  }
2093
2526
  }
2094
2527
  for (const file of parsedFiles) {
@@ -2414,7 +2847,7 @@ function watchProject(projectRoot, callbacks) {
2414
2847
  const watcher = chokidar.watch(projectRoot, watcherOptions);
2415
2848
  console.error("[Watcher] Attaching event listeners...");
2416
2849
  watcher.on("change", (absolutePath) => {
2417
- const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
2850
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs"];
2418
2851
  if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2419
2852
  if (absolutePath.endsWith("_test.go")) return;
2420
2853
  const relativePath = absolutePath.replace(projectRoot + "/", "");
@@ -2422,7 +2855,7 @@ function watchProject(projectRoot, callbacks) {
2422
2855
  callbacks.onFileChanged(relativePath);
2423
2856
  });
2424
2857
  watcher.on("add", (absolutePath) => {
2425
- const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
2858
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs"];
2426
2859
  if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2427
2860
  if (absolutePath.endsWith("_test.go")) return;
2428
2861
  const relativePath = absolutePath.replace(projectRoot + "/", "");
@@ -2430,7 +2863,7 @@ function watchProject(projectRoot, callbacks) {
2430
2863
  callbacks.onFileAdded(relativePath);
2431
2864
  });
2432
2865
  watcher.on("unlink", (absolutePath) => {
2433
- const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go"];
2866
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs"];
2434
2867
  if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2435
2868
  if (absolutePath.endsWith("_test.go")) return;
2436
2869
  const relativePath = absolutePath.replace(projectRoot + "/", "");
@@ -2460,10 +2893,10 @@ function watchProject(projectRoot, callbacks) {
2460
2893
  import express from "express";
2461
2894
  import open from "open";
2462
2895
  import { fileURLToPath as fileURLToPath2 } from "url";
2463
- import { dirname as dirname5, join as join7 } from "path";
2896
+ import { dirname as dirname6, join as join8 } from "path";
2464
2897
  import { WebSocketServer } from "ws";
2465
2898
  var __filename = fileURLToPath2(import.meta.url);
2466
- var __dirname2 = dirname5(__filename);
2899
+ var __dirname2 = dirname6(__filename);
2467
2900
  var activeServer = null;
2468
2901
  async function findAvailablePort(startPort, maxAttempts = 10) {
2469
2902
  const net = await import("net");
@@ -2501,7 +2934,7 @@ async function startVizServer(initialVizData, graph, projectRoot, port = 3333, s
2501
2934
  const availablePort = await findAvailablePort(port);
2502
2935
  const app = express();
2503
2936
  let vizData = initialVizData;
2504
- const publicDir = join7(__dirname2, "viz", "public");
2937
+ const publicDir = join8(__dirname2, "viz", "public");
2505
2938
  app.use(express.static(publicDir));
2506
2939
  app.get("/api/graph", (req, res) => {
2507
2940
  res.json(vizData);
@@ -2617,7 +3050,7 @@ function isProjectLoaded(state) {
2617
3050
  }
2618
3051
 
2619
3052
  // src/graph/updater.ts
2620
- import { join as join8 } from "path";
3053
+ import { join as join9 } from "path";
2621
3054
  function removeFileFromGraph(graph, filePath) {
2622
3055
  const nodesToRemove = [];
2623
3056
  graph.forEachNode((node, attrs) => {
@@ -2661,7 +3094,7 @@ function addFileToGraph(graph, parsedFile) {
2661
3094
  }
2662
3095
  async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2663
3096
  removeFileFromGraph(graph, relativeFilePath);
2664
- const absolutePath = join8(projectRoot, relativeFilePath);
3097
+ const absolutePath = join9(projectRoot, relativeFilePath);
2665
3098
  try {
2666
3099
  const parsedFile = parseTypeScriptFile(absolutePath, relativeFilePath);
2667
3100
  addFileToGraph(graph, parsedFile);
@@ -2671,7 +3104,7 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2671
3104
  }
2672
3105
 
2673
3106
  // src/health/metrics.ts
2674
- import { dirname as dirname6 } from "path";
3107
+ import { dirname as dirname7 } from "path";
2675
3108
  function scoreToGrade(score) {
2676
3109
  if (score >= 90) return "A";
2677
3110
  if (score >= 80) return "B";
@@ -2704,8 +3137,8 @@ function calculateCouplingScore(graph) {
2704
3137
  totalEdges++;
2705
3138
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
2706
3139
  fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
2707
- const sourceDir = dirname6(sourceAttrs.filePath).split("/")[0];
2708
- const targetDir = dirname6(targetAttrs.filePath).split("/")[0];
3140
+ const sourceDir = dirname7(sourceAttrs.filePath).split("/")[0];
3141
+ const targetDir = dirname7(targetAttrs.filePath).split("/")[0];
2709
3142
  if (sourceDir !== targetDir) {
2710
3143
  crossDirEdges++;
2711
3144
  }
@@ -2752,8 +3185,8 @@ function calculateCohesionScore(graph) {
2752
3185
  const sourceAttrs = graph.getNodeAttributes(source);
2753
3186
  const targetAttrs = graph.getNodeAttributes(target);
2754
3187
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
2755
- const sourceDir = dirname6(sourceAttrs.filePath);
2756
- const targetDir = dirname6(targetAttrs.filePath);
3188
+ const sourceDir = dirname7(sourceAttrs.filePath);
3189
+ const targetDir = dirname7(targetAttrs.filePath);
2757
3190
  if (!dirEdges.has(sourceDir)) {
2758
3191
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
2759
3192
  }
@@ -2820,11 +3253,11 @@ function calculateCircularDepsScore(graph) {
2820
3253
  const visited = /* @__PURE__ */ new Set();
2821
3254
  const recStack = /* @__PURE__ */ new Set();
2822
3255
  const cycles = [];
2823
- function dfs(node, path2) {
3256
+ function dfs(node, path6) {
2824
3257
  if (recStack.has(node)) {
2825
- const cycleStart = path2.indexOf(node);
3258
+ const cycleStart = path6.indexOf(node);
2826
3259
  if (cycleStart >= 0) {
2827
- cycles.push(path2.slice(cycleStart));
3260
+ cycles.push(path6.slice(cycleStart));
2828
3261
  }
2829
3262
  return;
2830
3263
  }
@@ -2833,11 +3266,11 @@ function calculateCircularDepsScore(graph) {
2833
3266
  }
2834
3267
  visited.add(node);
2835
3268
  recStack.add(node);
2836
- path2.push(node);
3269
+ path6.push(node);
2837
3270
  const neighbors = fileGraph.get(node);
2838
3271
  if (neighbors) {
2839
3272
  for (const neighbor of neighbors) {
2840
- dfs(neighbor, [...path2]);
3273
+ dfs(neighbor, [...path6]);
2841
3274
  }
2842
3275
  }
2843
3276
  recStack.delete(node);
@@ -2939,25 +3372,40 @@ function calculateOrphansScore(graph) {
2939
3372
  });
2940
3373
  const orphanCount = files.size - connectedFiles.size;
2941
3374
  const orphanPercent = files.size > 0 ? orphanCount / files.size * 100 : 0;
3375
+ const totalSymbols = graph.order;
3376
+ let deadSymbolCount = 0;
3377
+ graph.forEachNode((node) => {
3378
+ const inDegree = graph.inDegree(node);
3379
+ if (inDegree === 0) {
3380
+ deadSymbolCount++;
3381
+ }
3382
+ });
3383
+ const deadCodePercent = totalSymbols > 0 ? deadSymbolCount / totalSymbols * 100 : 0;
3384
+ const combinedScore = (orphanPercent + deadCodePercent) / 2;
2942
3385
  let score = 100;
2943
- if (orphanPercent === 0) {
3386
+ if (combinedScore === 0) {
2944
3387
  score = 100;
2945
- } else if (orphanPercent <= 5) {
3388
+ } else if (combinedScore <= 5) {
2946
3389
  score = 80;
2947
- } else if (orphanPercent <= 10) {
3390
+ } else if (combinedScore <= 10) {
2948
3391
  score = 60;
2949
- } else if (orphanPercent <= 20) {
3392
+ } else if (combinedScore <= 20) {
2950
3393
  score = 40;
2951
3394
  } else {
2952
3395
  score = 20;
2953
3396
  }
2954
3397
  return {
2955
- name: "Orphan Files",
3398
+ name: "Orphans & Dead Code",
2956
3399
  score,
2957
3400
  weight: 0.1,
2958
3401
  grade: scoreToGrade(score),
2959
- details: orphanCount === 0 ? "No orphan files" : `${orphanCount} orphan file${orphanCount === 1 ? "" : "s"} (${orphanPercent.toFixed(0)}%)`,
2960
- metrics: { orphans: orphanCount, percentage: parseFloat(orphanPercent.toFixed(1)) }
3402
+ details: `${orphanCount} orphan file${orphanCount === 1 ? "" : "s"} (${orphanPercent.toFixed(0)}%), ${deadSymbolCount} dead symbols (${deadCodePercent.toFixed(1)}%)`,
3403
+ metrics: {
3404
+ orphans: orphanCount,
3405
+ orphanPercentage: parseFloat(orphanPercent.toFixed(1)),
3406
+ deadSymbols: deadSymbolCount,
3407
+ deadCodePercentage: parseFloat(deadCodePercent.toFixed(1))
3408
+ }
2961
3409
  };
2962
3410
  }
2963
3411
  function calculateDepthScore(graph) {
@@ -3020,8 +3468,8 @@ function calculateDepthScore(graph) {
3020
3468
  }
3021
3469
 
3022
3470
  // src/health/index.ts
3023
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync6, mkdirSync } from "fs";
3024
- import { join as join9, dirname as dirname7 } from "path";
3471
+ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7, mkdirSync } from "fs";
3472
+ import { join as join10, dirname as dirname8 } from "path";
3025
3473
  function calculateHealthScore(graph, projectRoot) {
3026
3474
  const coupling = calculateCouplingScore(graph);
3027
3475
  const cohesion = calculateCohesionScore(graph);
@@ -3120,7 +3568,7 @@ function getHealthTrend(projectRoot, currentScore) {
3120
3568
  }
3121
3569
  }
3122
3570
  function saveHealthHistory(projectRoot, report) {
3123
- const historyFile = join9(projectRoot, ".depwire", "health-history.json");
3571
+ const historyFile = join10(projectRoot, ".depwire", "health-history.json");
3124
3572
  const entry = {
3125
3573
  timestamp: report.timestamp,
3126
3574
  score: report.overall,
@@ -3132,9 +3580,9 @@ function saveHealthHistory(projectRoot, report) {
3132
3580
  }))
3133
3581
  };
3134
3582
  let history = [];
3135
- if (existsSync6(historyFile)) {
3583
+ if (existsSync7(historyFile)) {
3136
3584
  try {
3137
- const content = readFileSync4(historyFile, "utf-8");
3585
+ const content = readFileSync5(historyFile, "utf-8");
3138
3586
  history = JSON.parse(content);
3139
3587
  } catch {
3140
3588
  }
@@ -3143,28 +3591,326 @@ function saveHealthHistory(projectRoot, report) {
3143
3591
  if (history.length > 50) {
3144
3592
  history = history.slice(-50);
3145
3593
  }
3146
- mkdirSync(dirname7(historyFile), { recursive: true });
3594
+ mkdirSync(dirname8(historyFile), { recursive: true });
3147
3595
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
3148
3596
  }
3149
3597
  function loadHealthHistory(projectRoot) {
3150
- const historyFile = join9(projectRoot, ".depwire", "health-history.json");
3151
- if (!existsSync6(historyFile)) {
3598
+ const historyFile = join10(projectRoot, ".depwire", "health-history.json");
3599
+ if (!existsSync7(historyFile)) {
3152
3600
  return [];
3153
3601
  }
3154
3602
  try {
3155
- const content = readFileSync4(historyFile, "utf-8");
3603
+ const content = readFileSync5(historyFile, "utf-8");
3156
3604
  return JSON.parse(content);
3157
3605
  } catch {
3158
3606
  return [];
3159
3607
  }
3160
3608
  }
3161
3609
 
3610
+ // src/dead-code/detector.ts
3611
+ import path2 from "path";
3612
+ function findDeadSymbols(graph, projectRoot, includeTests = false) {
3613
+ const deadSymbols = [];
3614
+ const context = { graph, projectRoot };
3615
+ for (const node of graph.nodes()) {
3616
+ const attrs = graph.getNodeAttributes(node);
3617
+ if (!attrs.file || !attrs.name) continue;
3618
+ const inDegree = graph.inDegree(node);
3619
+ if (inDegree === 0) {
3620
+ if (shouldExclude(attrs, context, includeTests)) {
3621
+ continue;
3622
+ }
3623
+ deadSymbols.push({
3624
+ name: attrs.name,
3625
+ kind: attrs.kind || "unknown",
3626
+ file: attrs.file,
3627
+ line: attrs.startLine || 0,
3628
+ exported: attrs.exported || false,
3629
+ dependents: 0,
3630
+ confidence: "high",
3631
+ reason: "Zero dependents"
3632
+ });
3633
+ }
3634
+ }
3635
+ return deadSymbols;
3636
+ }
3637
+ function shouldExclude(attrs, context, includeTests) {
3638
+ const filePath = attrs.file;
3639
+ const relativePath = path2.relative(context.projectRoot, filePath);
3640
+ if (!includeTests && isTestFile(relativePath)) {
3641
+ return true;
3642
+ }
3643
+ if (isEntryPoint(relativePath)) {
3644
+ return true;
3645
+ }
3646
+ if (isConfigFile(relativePath)) {
3647
+ return true;
3648
+ }
3649
+ if (isTypeDeclarationFile(relativePath)) {
3650
+ return true;
3651
+ }
3652
+ if (attrs.kind === "default") {
3653
+ return true;
3654
+ }
3655
+ if (isFrameworkAutoLoadedFile(relativePath)) {
3656
+ return true;
3657
+ }
3658
+ return false;
3659
+ }
3660
+ function isTestFile(filePath) {
3661
+ return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
3662
+ }
3663
+ function isEntryPoint(filePath) {
3664
+ const basename6 = path2.basename(filePath);
3665
+ return basename6 === "index.ts" || basename6 === "index.js" || basename6 === "main.ts" || basename6 === "main.js" || basename6 === "app.ts" || basename6 === "app.js" || basename6 === "server.ts" || basename6 === "server.js";
3666
+ }
3667
+ function isConfigFile(filePath) {
3668
+ return filePath.includes(".config.") || filePath.includes("config/") || filePath.includes("vite.config") || filePath.includes("rollup.config") || filePath.includes("webpack.config");
3669
+ }
3670
+ function isTypeDeclarationFile(filePath) {
3671
+ return filePath.endsWith(".d.ts");
3672
+ }
3673
+ function isFrameworkAutoLoadedFile(filePath) {
3674
+ return filePath.includes("/pages/") || filePath.includes("/routes/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/api/") || filePath.includes("/app/");
3675
+ }
3676
+
3677
+ // src/dead-code/classifier.ts
3678
+ import path3 from "path";
3679
+ function classifyDeadSymbols(symbols, graph) {
3680
+ return symbols.map((symbol) => {
3681
+ const confidence = calculateConfidence(symbol, graph);
3682
+ const reason = generateReason(symbol, confidence);
3683
+ return {
3684
+ ...symbol,
3685
+ confidence,
3686
+ reason
3687
+ };
3688
+ });
3689
+ }
3690
+ function calculateConfidence(symbol, graph) {
3691
+ if (!symbol.exported && symbol.dependents === 0) {
3692
+ return "high";
3693
+ }
3694
+ if (symbol.exported && symbol.dependents === 0 && !isBarrelFile(symbol.file)) {
3695
+ return "high";
3696
+ }
3697
+ if (symbol.exported && symbol.dependents === 0 && isBarrelFile(symbol.file)) {
3698
+ return "medium";
3699
+ }
3700
+ const dependents = getSymbolDependents(symbol, graph);
3701
+ if (dependents.length === 1 && isTestFile2(dependents[0])) {
3702
+ return "medium";
3703
+ }
3704
+ if (symbol.exported && isPackageEntryPoint(symbol.file)) {
3705
+ return "low";
3706
+ }
3707
+ if ((symbol.kind === "interface" || symbol.kind === "type") && symbol.dependents === 0) {
3708
+ return "low";
3709
+ }
3710
+ if (isLikelyDynamicUsage(symbol)) {
3711
+ return "low";
3712
+ }
3713
+ return "medium";
3714
+ }
3715
+ function generateReason(symbol, confidence) {
3716
+ if (!symbol.exported && symbol.dependents === 0) {
3717
+ return "Not exported, zero references";
3718
+ }
3719
+ if (symbol.exported && symbol.dependents === 0 && !isBarrelFile(symbol.file)) {
3720
+ return "Exported, zero dependents";
3721
+ }
3722
+ if (symbol.exported && symbol.dependents === 0 && isBarrelFile(symbol.file)) {
3723
+ return "Exported from barrel file, zero dependents (might be used externally)";
3724
+ }
3725
+ if (confidence === "medium") {
3726
+ return "Low usage, might be dead";
3727
+ }
3728
+ if (confidence === "low") {
3729
+ if (symbol.kind === "interface" || symbol.kind === "type") {
3730
+ return "Type with zero dependents (might be used via import type)";
3731
+ }
3732
+ if (isPackageEntryPoint(symbol.file)) {
3733
+ return "Exported from package entry point (might be public API)";
3734
+ }
3735
+ if (isLikelyDynamicUsage(symbol)) {
3736
+ return "In dynamic-use pattern directory (might be auto-loaded)";
3737
+ }
3738
+ }
3739
+ return "Potentially unused";
3740
+ }
3741
+ function isBarrelFile(filePath) {
3742
+ const basename6 = path3.basename(filePath);
3743
+ return basename6 === "index.ts" || basename6 === "index.js";
3744
+ }
3745
+ function isTestFile2(filePath) {
3746
+ return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
3747
+ }
3748
+ function isPackageEntryPoint(filePath) {
3749
+ return filePath.includes("/src/index.") || filePath.includes("/lib/index.") || filePath.endsWith("/index.ts") || filePath.endsWith("/index.js");
3750
+ }
3751
+ function isLikelyDynamicUsage(symbol) {
3752
+ const filePath = symbol.file;
3753
+ return filePath.includes("/routes/") || filePath.includes("/pages/") || filePath.includes("/middleware/") || filePath.includes("/commands/") || filePath.includes("/handlers/") || filePath.includes("/api/");
3754
+ }
3755
+ function getSymbolDependents(symbol, graph) {
3756
+ const dependents = [];
3757
+ for (const node of graph.nodes()) {
3758
+ const attrs = graph.getNodeAttributes(node);
3759
+ if (attrs.file === symbol.file && attrs.name === symbol.name) {
3760
+ const inNeighbors = graph.inNeighbors(node);
3761
+ for (const neighbor of inNeighbors) {
3762
+ const neighborAttrs = graph.getNodeAttributes(neighbor);
3763
+ if (neighborAttrs.file) {
3764
+ dependents.push(neighborAttrs.file);
3765
+ }
3766
+ }
3767
+ break;
3768
+ }
3769
+ }
3770
+ return dependents;
3771
+ }
3772
+
3773
+ // src/dead-code/display.ts
3774
+ import chalk from "chalk";
3775
+ import path4 from "path";
3776
+ function displayDeadCodeReport(report, options, projectRoot) {
3777
+ if (options.json) {
3778
+ console.log(JSON.stringify(report, null, 2));
3779
+ return;
3780
+ }
3781
+ console.log(chalk.cyan.bold("\n\u{1F50D} Dead Code Analysis\n"));
3782
+ const { high, medium, low } = report.byConfidence;
3783
+ console.log(
3784
+ `Found ${chalk.yellow.bold(report.deadSymbols)} potentially dead symbols (${chalk.red(high)} high, ${chalk.yellow(medium)} medium, ${chalk.gray(low)} low confidence)
3785
+ `
3786
+ );
3787
+ const symbolsByConfidence = groupByConfidence(report.symbols);
3788
+ if (symbolsByConfidence.high.length > 0) {
3789
+ displayConfidenceGroup("HIGH", symbolsByConfidence.high, options.verbose, projectRoot);
3790
+ }
3791
+ if (symbolsByConfidence.medium.length > 0) {
3792
+ displayConfidenceGroup("MEDIUM", symbolsByConfidence.medium, options.verbose, projectRoot);
3793
+ }
3794
+ if (symbolsByConfidence.low.length > 0) {
3795
+ displayConfidenceGroup("LOW", symbolsByConfidence.low, options.verbose, projectRoot);
3796
+ }
3797
+ if (options.stats) {
3798
+ displayStats(report);
3799
+ }
3800
+ }
3801
+ function groupByConfidence(symbols) {
3802
+ return symbols.reduce(
3803
+ (acc, symbol) => {
3804
+ acc[symbol.confidence].push(symbol);
3805
+ return acc;
3806
+ },
3807
+ { high: [], medium: [], low: [] }
3808
+ );
3809
+ }
3810
+ function displayConfidenceGroup(level, symbols, verbose, projectRoot) {
3811
+ const emoji = level === "HIGH" ? "\u{1F534}" : level === "MEDIUM" ? "\u{1F7E1}" : "\u26AA";
3812
+ const color = level === "HIGH" ? chalk.red : level === "MEDIUM" ? chalk.yellow : chalk.gray;
3813
+ console.log(
3814
+ color.bold(`
3815
+ ${emoji} ${level} CONFIDENCE `) + chalk.gray(`(${level === "HIGH" ? "definitely" : level === "MEDIUM" ? "probably" : "might be"} dead)`)
3816
+ );
3817
+ const headers = ["Symbol", "Kind", "File", verbose ? "Reason" : ""];
3818
+ const rows = symbols.map((symbol) => {
3819
+ const relativePath = path4.relative(projectRoot, symbol.file);
3820
+ return [
3821
+ chalk.bold(symbol.name),
3822
+ symbol.kind,
3823
+ `${relativePath}:${symbol.line}`,
3824
+ verbose ? symbol.reason : ""
3825
+ ];
3826
+ });
3827
+ displayTable(headers, rows.filter((row) => row[3] !== ""));
3828
+ }
3829
+ function displayTable(headers, rows) {
3830
+ if (rows.length === 0) return;
3831
+ const columnWidths = headers.map((header2, i) => {
3832
+ const maxRowWidth = Math.max(...rows.map((row) => stripAnsi(row[i]).length));
3833
+ return Math.max(header2.length, maxRowWidth);
3834
+ });
3835
+ const separator = "\u250C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510";
3836
+ const headerRow = "\u2502 " + headers.map((h, i) => h.padEnd(columnWidths[i])).join(" \u2502 ") + " \u2502";
3837
+ const divider = "\u251C" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
3838
+ const footer = "\u2514" + columnWidths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518";
3839
+ console.log(separator);
3840
+ console.log(headerRow);
3841
+ console.log(divider);
3842
+ for (const row of rows) {
3843
+ const formattedRow = "\u2502 " + row.map((cell, i) => {
3844
+ const stripped = stripAnsi(cell);
3845
+ const padding = columnWidths[i] - stripped.length;
3846
+ return cell + " ".repeat(padding);
3847
+ }).join(" \u2502 ") + " \u2502";
3848
+ console.log(formattedRow);
3849
+ }
3850
+ console.log(footer);
3851
+ }
3852
+ function displayStats(report) {
3853
+ console.log(chalk.cyan.bold("\n\u{1F4CA} Summary\n"));
3854
+ console.log(` Total symbols analyzed: ${chalk.bold(report.totalSymbols.toLocaleString())}`);
3855
+ console.log(` Potentially dead: ${chalk.yellow.bold(report.deadSymbols)} (${report.deadPercentage.toFixed(1)}%)`);
3856
+ console.log(
3857
+ ` By confidence: ${chalk.red(report.byConfidence.high)} high, ${chalk.yellow(report.byConfidence.medium)} medium, ${chalk.gray(report.byConfidence.low)} low`
3858
+ );
3859
+ const estimatedLines = report.deadSymbols * 18;
3860
+ console.log(` Estimated dead code: ${chalk.gray(`~${estimatedLines.toLocaleString()} lines`)}
3861
+ `);
3862
+ }
3863
+ function stripAnsi(str) {
3864
+ return str.replace(
3865
+ /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
3866
+ ""
3867
+ );
3868
+ }
3869
+
3870
+ // src/dead-code/index.ts
3871
+ function analyzeDeadCode(graph, projectRoot, options = {}) {
3872
+ const opts = {
3873
+ confidence: options.confidence || "medium",
3874
+ includeTests: options.includeTests || false,
3875
+ verbose: options.verbose || false,
3876
+ stats: options.stats || false,
3877
+ json: options.json || false
3878
+ };
3879
+ const rawDeadSymbols = findDeadSymbols(graph, projectRoot, opts.includeTests);
3880
+ const classifiedSymbols = classifyDeadSymbols(rawDeadSymbols, graph);
3881
+ const filteredSymbols = filterByConfidence(classifiedSymbols, opts.confidence);
3882
+ const totalSymbols = graph.order;
3883
+ const byConfidence = {
3884
+ high: classifiedSymbols.filter((s) => s.confidence === "high").length,
3885
+ medium: classifiedSymbols.filter((s) => s.confidence === "medium").length,
3886
+ low: classifiedSymbols.filter((s) => s.confidence === "low").length
3887
+ };
3888
+ const report = {
3889
+ totalSymbols,
3890
+ deadSymbols: filteredSymbols.length,
3891
+ deadPercentage: filteredSymbols.length / totalSymbols * 100,
3892
+ byConfidence,
3893
+ symbols: filteredSymbols
3894
+ };
3895
+ if (!opts.json) {
3896
+ displayDeadCodeReport(report, opts, projectRoot);
3897
+ }
3898
+ return report;
3899
+ }
3900
+ function filterByConfidence(symbols, minConfidence) {
3901
+ const confidenceLevels = { high: 3, medium: 2, low: 1 };
3902
+ const minLevel = confidenceLevels[minConfidence];
3903
+ return symbols.filter(
3904
+ (s) => confidenceLevels[s.confidence] >= minLevel
3905
+ );
3906
+ }
3907
+
3162
3908
  // src/docs/generator.ts
3163
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync9 } from "fs";
3164
- import { join as join12 } from "path";
3909
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync10 } from "fs";
3910
+ import { join as join13 } from "path";
3165
3911
 
3166
3912
  // src/docs/architecture.ts
3167
- import { dirname as dirname8 } from "path";
3913
+ import { dirname as dirname9 } from "path";
3168
3914
 
3169
3915
  // src/docs/templates.ts
3170
3916
  function header(text, level = 1) {
@@ -3315,7 +4061,7 @@ function generateModuleStructure(graph) {
3315
4061
  function getDirectoryStats(graph) {
3316
4062
  const dirMap = /* @__PURE__ */ new Map();
3317
4063
  graph.forEachNode((node, attrs) => {
3318
- const dir = dirname8(attrs.filePath);
4064
+ const dir = dirname9(attrs.filePath);
3319
4065
  if (dir === ".") return;
3320
4066
  if (!dirMap.has(dir)) {
3321
4067
  dirMap.set(dir, {
@@ -3340,7 +4086,7 @@ function getDirectoryStats(graph) {
3340
4086
  });
3341
4087
  const filesPerDir = /* @__PURE__ */ new Map();
3342
4088
  graph.forEachNode((node, attrs) => {
3343
- const dir = dirname8(attrs.filePath);
4089
+ const dir = dirname9(attrs.filePath);
3344
4090
  if (!filesPerDir.has(dir)) {
3345
4091
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
3346
4092
  }
@@ -3355,8 +4101,8 @@ function getDirectoryStats(graph) {
3355
4101
  graph.forEachEdge((edge, attrs, source, target) => {
3356
4102
  const sourceAttrs = graph.getNodeAttributes(source);
3357
4103
  const targetAttrs = graph.getNodeAttributes(target);
3358
- const sourceDir = dirname8(sourceAttrs.filePath);
3359
- const targetDir = dirname8(targetAttrs.filePath);
4104
+ const sourceDir = dirname9(sourceAttrs.filePath);
4105
+ const targetDir = dirname9(targetAttrs.filePath);
3360
4106
  if (sourceDir !== targetDir) {
3361
4107
  if (!dirEdges.has(sourceDir)) {
3362
4108
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -4208,20 +4954,20 @@ function findLongestPaths(graph, limit) {
4208
4954
  }
4209
4955
  const allPaths = [];
4210
4956
  const visited = /* @__PURE__ */ new Set();
4211
- function dfs(file, path2) {
4957
+ function dfs(file, path6) {
4212
4958
  visited.add(file);
4213
- path2.push(file);
4959
+ path6.push(file);
4214
4960
  const neighbors = fileGraph.get(file);
4215
4961
  if (!neighbors || neighbors.size === 0) {
4216
- allPaths.push([...path2]);
4962
+ allPaths.push([...path6]);
4217
4963
  } else {
4218
4964
  for (const neighbor of neighbors) {
4219
4965
  if (!visited.has(neighbor)) {
4220
- dfs(neighbor, path2);
4966
+ dfs(neighbor, path6);
4221
4967
  }
4222
4968
  }
4223
4969
  }
4224
- path2.pop();
4970
+ path6.pop();
4225
4971
  visited.delete(file);
4226
4972
  }
4227
4973
  for (const root of roots.slice(0, 10)) {
@@ -4337,7 +5083,7 @@ function detectCyclesDetailed(graph) {
4337
5083
  }
4338
5084
 
4339
5085
  // src/docs/onboarding.ts
4340
- import { dirname as dirname9 } from "path";
5086
+ import { dirname as dirname10 } from "path";
4341
5087
  function generateOnboarding(graph, projectRoot, version) {
4342
5088
  let output = "";
4343
5089
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -4396,7 +5142,7 @@ function generateQuickOrientation(graph) {
4396
5142
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
4397
5143
  const dirs = /* @__PURE__ */ new Set();
4398
5144
  graph.forEachNode((node, attrs) => {
4399
- const dir = dirname9(attrs.filePath);
5145
+ const dir = dirname10(attrs.filePath);
4400
5146
  if (dir !== ".") {
4401
5147
  const topLevel = dir.split("/")[0];
4402
5148
  dirs.add(topLevel);
@@ -4500,7 +5246,7 @@ function generateModuleMap(graph) {
4500
5246
  function getDirectoryStats2(graph) {
4501
5247
  const dirMap = /* @__PURE__ */ new Map();
4502
5248
  graph.forEachNode((node, attrs) => {
4503
- const dir = dirname9(attrs.filePath);
5249
+ const dir = dirname10(attrs.filePath);
4504
5250
  if (dir === ".") return;
4505
5251
  if (!dirMap.has(dir)) {
4506
5252
  dirMap.set(dir, {
@@ -4515,7 +5261,7 @@ function getDirectoryStats2(graph) {
4515
5261
  });
4516
5262
  const filesPerDir = /* @__PURE__ */ new Map();
4517
5263
  graph.forEachNode((node, attrs) => {
4518
- const dir = dirname9(attrs.filePath);
5264
+ const dir = dirname10(attrs.filePath);
4519
5265
  if (!filesPerDir.has(dir)) {
4520
5266
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
4521
5267
  }
@@ -4530,8 +5276,8 @@ function getDirectoryStats2(graph) {
4530
5276
  graph.forEachEdge((edge, attrs, source, target) => {
4531
5277
  const sourceAttrs = graph.getNodeAttributes(source);
4532
5278
  const targetAttrs = graph.getNodeAttributes(target);
4533
- const sourceDir = dirname9(sourceAttrs.filePath);
4534
- const targetDir = dirname9(targetAttrs.filePath);
5279
+ const sourceDir = dirname10(sourceAttrs.filePath);
5280
+ const targetDir = dirname10(targetAttrs.filePath);
4535
5281
  if (sourceDir !== targetDir) {
4536
5282
  if (!dirEdges.has(sourceDir)) {
4537
5283
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -4612,7 +5358,7 @@ function detectClusters(graph) {
4612
5358
  const dirFiles = /* @__PURE__ */ new Map();
4613
5359
  const fileEdges = /* @__PURE__ */ new Map();
4614
5360
  graph.forEachNode((node, attrs) => {
4615
- const dir = dirname9(attrs.filePath);
5361
+ const dir = dirname10(attrs.filePath);
4616
5362
  if (!dirFiles.has(dir)) {
4617
5363
  dirFiles.set(dir, /* @__PURE__ */ new Set());
4618
5364
  }
@@ -4666,8 +5412,8 @@ function inferClusterName(files) {
4666
5412
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
4667
5413
  return capitalizeFirst2(sortedWords[0][0]);
4668
5414
  }
4669
- const commonDir = dirname9(files[0]);
4670
- if (files.every((f) => dirname9(f) === commonDir)) {
5415
+ const commonDir = dirname10(files[0]);
5416
+ if (files.every((f) => dirname10(f) === commonDir)) {
4671
5417
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
4672
5418
  }
4673
5419
  return "Core";
@@ -4725,7 +5471,7 @@ function generateDepwireUsage(projectRoot) {
4725
5471
  }
4726
5472
 
4727
5473
  // src/docs/files.ts
4728
- import { dirname as dirname10, basename as basename3 } from "path";
5474
+ import { dirname as dirname11, basename as basename3 } from "path";
4729
5475
  function generateFiles(graph, projectRoot, version) {
4730
5476
  let output = "";
4731
5477
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -4829,7 +5575,7 @@ function generateDirectoryBreakdown(graph) {
4829
5575
  const fileStats = getFileStats2(graph);
4830
5576
  const dirMap = /* @__PURE__ */ new Map();
4831
5577
  for (const file of fileStats) {
4832
- const dir = dirname10(file.filePath);
5578
+ const dir = dirname11(file.filePath);
4833
5579
  const topDir = dir === "." ? "." : dir.split("/")[0];
4834
5580
  if (!dirMap.has(topDir)) {
4835
5581
  dirMap.set(topDir, {
@@ -5472,7 +6218,7 @@ function generateRecommendations(graph) {
5472
6218
  }
5473
6219
 
5474
6220
  // src/docs/tests.ts
5475
- import { basename as basename4, dirname as dirname11 } from "path";
6221
+ import { basename as basename4, dirname as dirname12 } from "path";
5476
6222
  function generateTests(graph, projectRoot, version) {
5477
6223
  let output = "";
5478
6224
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -5499,9 +6245,9 @@ function getFileCount8(graph) {
5499
6245
  });
5500
6246
  return files.size;
5501
6247
  }
5502
- function isTestFile(filePath) {
6248
+ function isTestFile3(filePath) {
5503
6249
  const fileName = basename4(filePath).toLowerCase();
5504
- const dirPath = dirname11(filePath).toLowerCase();
6250
+ const dirPath = dirname12(filePath).toLowerCase();
5505
6251
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
5506
6252
  return true;
5507
6253
  }
@@ -5513,7 +6259,7 @@ function isTestFile(filePath) {
5513
6259
  function getTestFiles(graph) {
5514
6260
  const testFiles = /* @__PURE__ */ new Map();
5515
6261
  graph.forEachNode((node, attrs) => {
5516
- if (isTestFile(attrs.filePath)) {
6262
+ if (isTestFile3(attrs.filePath)) {
5517
6263
  if (!testFiles.has(attrs.filePath)) {
5518
6264
  testFiles.set(attrs.filePath, {
5519
6265
  filePath: attrs.filePath,
@@ -5560,21 +6306,21 @@ function generateTestFileInventory(graph) {
5560
6306
  }
5561
6307
  function matchTestToSource(testFile) {
5562
6308
  const testFileName = basename4(testFile);
5563
- const testDir = dirname11(testFile);
6309
+ const testDir = dirname12(testFile);
5564
6310
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
5565
6311
  const possiblePaths = [];
5566
6312
  possiblePaths.push(testDir + "/" + sourceFileName);
5567
6313
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
5568
- const parentDir = dirname11(testDir);
6314
+ const parentDir = dirname12(testDir);
5569
6315
  possiblePaths.push(parentDir + "/" + sourceFileName);
5570
6316
  }
5571
6317
  if (testDir.includes("test")) {
5572
6318
  const srcDir = testDir.replace(/test[s]?/g, "src");
5573
6319
  possiblePaths.push(srcDir + "/" + sourceFileName);
5574
6320
  }
5575
- for (const path2 of possiblePaths) {
5576
- if (!isTestFile(path2)) {
5577
- return path2;
6321
+ for (const path6 of possiblePaths) {
6322
+ if (!isTestFile3(path6)) {
6323
+ return path6;
5578
6324
  }
5579
6325
  }
5580
6326
  return null;
@@ -5629,7 +6375,7 @@ function generateUntestedFiles(graph) {
5629
6375
  allFiles.add(attrs.filePath);
5630
6376
  });
5631
6377
  for (const file of allFiles) {
5632
- if (!isTestFile(file)) {
6378
+ if (!isTestFile3(file)) {
5633
6379
  sourceFiles.push(file);
5634
6380
  }
5635
6381
  }
@@ -5688,7 +6434,7 @@ function generateTestCoverageMap(graph) {
5688
6434
  allFiles.add(attrs.filePath);
5689
6435
  });
5690
6436
  for (const file of allFiles) {
5691
- if (!isTestFile(file)) {
6437
+ if (!isTestFile3(file)) {
5692
6438
  sourceFiles.push(file);
5693
6439
  }
5694
6440
  }
@@ -5740,7 +6486,7 @@ function generateTestStatistics(graph) {
5740
6486
  allFiles.add(attrs.filePath);
5741
6487
  });
5742
6488
  for (const file of allFiles) {
5743
- if (!isTestFile(file)) {
6489
+ if (!isTestFile3(file)) {
5744
6490
  sourceFiles.push(file);
5745
6491
  }
5746
6492
  }
@@ -5762,7 +6508,7 @@ function generateTestStatistics(graph) {
5762
6508
  `;
5763
6509
  const dirTestCoverage = /* @__PURE__ */ new Map();
5764
6510
  for (const sourceFile of sourceFiles) {
5765
- const dir = dirname11(sourceFile).split("/")[0];
6511
+ const dir = dirname12(sourceFile).split("/")[0];
5766
6512
  if (!dirTestCoverage.has(dir)) {
5767
6513
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
5768
6514
  }
@@ -5785,7 +6531,7 @@ function generateTestStatistics(graph) {
5785
6531
  }
5786
6532
 
5787
6533
  // src/docs/history.ts
5788
- import { dirname as dirname12 } from "path";
6534
+ import { dirname as dirname13 } from "path";
5789
6535
  import { execSync } from "child_process";
5790
6536
  function generateHistory(graph, projectRoot, version) {
5791
6537
  let output = "";
@@ -6066,7 +6812,7 @@ function generateFeatureClusters(graph) {
6066
6812
  const dirFiles = /* @__PURE__ */ new Map();
6067
6813
  const fileEdges = /* @__PURE__ */ new Map();
6068
6814
  graph.forEachNode((node, attrs) => {
6069
- const dir = dirname12(attrs.filePath);
6815
+ const dir = dirname13(attrs.filePath);
6070
6816
  if (!dirFiles.has(dir)) {
6071
6817
  dirFiles.set(dir, /* @__PURE__ */ new Set());
6072
6818
  }
@@ -6148,7 +6894,7 @@ function capitalizeFirst3(str) {
6148
6894
  }
6149
6895
 
6150
6896
  // src/docs/current.ts
6151
- import { dirname as dirname13 } from "path";
6897
+ import { dirname as dirname14 } from "path";
6152
6898
  function generateCurrent(graph, projectRoot, version) {
6153
6899
  let output = "";
6154
6900
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6286,7 +7032,7 @@ function generateCompleteFileIndex(graph) {
6286
7032
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
6287
7033
  const dirGroups = /* @__PURE__ */ new Map();
6288
7034
  for (const info of fileInfos) {
6289
- const dir = dirname13(info.filePath);
7035
+ const dir = dirname14(info.filePath);
6290
7036
  const topDir = dir === "." ? "root" : dir.split("/")[0];
6291
7037
  if (!dirGroups.has(topDir)) {
6292
7038
  dirGroups.set(topDir, []);
@@ -6497,8 +7243,8 @@ function getTopLevelDir2(filePath) {
6497
7243
  }
6498
7244
 
6499
7245
  // src/docs/status.ts
6500
- import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
6501
- import { join as join10 } from "path";
7246
+ import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
7247
+ import { join as join11 } from "path";
6502
7248
  function generateStatus(graph, projectRoot, version) {
6503
7249
  let output = "";
6504
7250
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6531,12 +7277,12 @@ function getFileCount11(graph) {
6531
7277
  }
6532
7278
  function extractComments(projectRoot, filePath) {
6533
7279
  const comments = [];
6534
- const fullPath = join10(projectRoot, filePath);
6535
- if (!existsSync7(fullPath)) {
7280
+ const fullPath = join11(projectRoot, filePath);
7281
+ if (!existsSync8(fullPath)) {
6536
7282
  return comments;
6537
7283
  }
6538
7284
  try {
6539
- const content = readFileSync5(fullPath, "utf-8");
7285
+ const content = readFileSync6(fullPath, "utf-8");
6540
7286
  const lines = content.split("\n");
6541
7287
  const patterns = [
6542
7288
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -7005,16 +7751,108 @@ function generateDetailedMetrics(dimensions) {
7005
7751
  return output;
7006
7752
  }
7007
7753
 
7754
+ // src/docs/dead-code.ts
7755
+ import path5 from "path";
7756
+ function generateDeadCode(graph, projectRoot, projectName) {
7757
+ const report = analyzeDeadCode(graph, projectRoot, {
7758
+ confidence: "low",
7759
+ includeTests: false,
7760
+ verbose: true,
7761
+ stats: true,
7762
+ json: true
7763
+ });
7764
+ let output = "";
7765
+ output += header(`${projectName} - Dead Code Analysis`, 1);
7766
+ output += timestamp();
7767
+ output += "\n";
7768
+ output += header("Summary", 2);
7769
+ output += `Total symbols analyzed: **${formatNumber(report.totalSymbols)}**
7770
+
7771
+ `;
7772
+ output += `Potentially dead symbols: **${formatNumber(report.deadSymbols)}** (${report.deadPercentage.toFixed(1)}%)
7773
+
7774
+ `;
7775
+ output += `- \u{1F534} High confidence (definitely dead): **${report.byConfidence.high}**
7776
+ `;
7777
+ output += `- \u{1F7E1} Medium confidence (probably dead): **${report.byConfidence.medium}**
7778
+ `;
7779
+ output += `- \u26AA Low confidence (might be dead): **${report.byConfidence.low}**
7780
+
7781
+ `;
7782
+ const estimatedLines = report.deadSymbols * 18;
7783
+ output += `Estimated dead code: **~${formatNumber(estimatedLines)} lines**
7784
+
7785
+ `;
7786
+ const symbolsByConfidence = groupByConfidence2(report.symbols);
7787
+ if (symbolsByConfidence.high.length > 0) {
7788
+ output += generateConfidenceSection(
7789
+ "High Confidence",
7790
+ "definitely dead",
7791
+ symbolsByConfidence.high,
7792
+ projectRoot
7793
+ );
7794
+ }
7795
+ if (symbolsByConfidence.medium.length > 0) {
7796
+ output += generateConfidenceSection(
7797
+ "Medium Confidence",
7798
+ "probably dead",
7799
+ symbolsByConfidence.medium,
7800
+ projectRoot
7801
+ );
7802
+ }
7803
+ if (symbolsByConfidence.low.length > 0) {
7804
+ output += generateConfidenceSection(
7805
+ "Low Confidence",
7806
+ "might be dead",
7807
+ symbolsByConfidence.low,
7808
+ projectRoot
7809
+ );
7810
+ }
7811
+ output += "\n---\n\n";
7812
+ output += "_This document was auto-generated by Depwire. It reflects the current state of the codebase and should be reviewed before taking action._\n";
7813
+ return output;
7814
+ }
7815
+ function groupByConfidence2(symbols) {
7816
+ return symbols.reduce(
7817
+ (acc, symbol) => {
7818
+ acc[symbol.confidence].push(symbol);
7819
+ return acc;
7820
+ },
7821
+ { high: [], medium: [], low: [] }
7822
+ );
7823
+ }
7824
+ function generateConfidenceSection(title, description, symbols, projectRoot) {
7825
+ let output = "";
7826
+ output += header(`${title} (${description})`, 2);
7827
+ output += `Found **${symbols.length}** symbol${symbols.length === 1 ? "" : "s"}.
7828
+
7829
+ `;
7830
+ const headers = ["Symbol", "Kind", "File", "Exported", "Reason"];
7831
+ const rows = symbols.map((s) => {
7832
+ const relativePath = path5.relative(projectRoot, s.file);
7833
+ return [
7834
+ code(s.name),
7835
+ s.kind,
7836
+ `${relativePath}:${s.line}`,
7837
+ s.exported ? "Yes" : "No",
7838
+ s.reason
7839
+ ];
7840
+ });
7841
+ output += table(headers, rows);
7842
+ output += "\n";
7843
+ return output;
7844
+ }
7845
+
7008
7846
  // src/docs/metadata.ts
7009
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
7010
- import { join as join11 } from "path";
7847
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
7848
+ import { join as join12 } from "path";
7011
7849
  function loadMetadata(outputDir) {
7012
- const metadataPath = join11(outputDir, "metadata.json");
7013
- if (!existsSync8(metadataPath)) {
7850
+ const metadataPath = join12(outputDir, "metadata.json");
7851
+ if (!existsSync9(metadataPath)) {
7014
7852
  return null;
7015
7853
  }
7016
7854
  try {
7017
- const content = readFileSync6(metadataPath, "utf-8");
7855
+ const content = readFileSync7(metadataPath, "utf-8");
7018
7856
  return JSON.parse(content);
7019
7857
  } catch (err) {
7020
7858
  console.error("Failed to load metadata:", err);
@@ -7022,7 +7860,7 @@ function loadMetadata(outputDir) {
7022
7860
  }
7023
7861
  }
7024
7862
  function saveMetadata(outputDir, metadata) {
7025
- const metadataPath = join11(outputDir, "metadata.json");
7863
+ const metadataPath = join12(outputDir, "metadata.json");
7026
7864
  writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
7027
7865
  }
7028
7866
  function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
@@ -7065,7 +7903,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7065
7903
  const generated = [];
7066
7904
  const errors = [];
7067
7905
  try {
7068
- if (!existsSync9(options.outputDir)) {
7906
+ if (!existsSync10(options.outputDir)) {
7069
7907
  mkdirSync2(options.outputDir, { recursive: true });
7070
7908
  if (options.verbose) {
7071
7909
  console.log(`Created output directory: ${options.outputDir}`);
@@ -7088,7 +7926,8 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7088
7926
  "history",
7089
7927
  "current",
7090
7928
  "status",
7091
- "health"
7929
+ "health",
7930
+ "dead_code"
7092
7931
  ];
7093
7932
  }
7094
7933
  let metadata = null;
@@ -7103,7 +7942,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7103
7942
  try {
7104
7943
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
7105
7944
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
7106
- const filePath = join12(options.outputDir, "ARCHITECTURE.md");
7945
+ const filePath = join13(options.outputDir, "ARCHITECTURE.md");
7107
7946
  writeFileSync3(filePath, content, "utf-8");
7108
7947
  generated.push("ARCHITECTURE.md");
7109
7948
  } catch (err) {
@@ -7114,7 +7953,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7114
7953
  try {
7115
7954
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
7116
7955
  const content = generateConventions(graph, projectRoot, version);
7117
- const filePath = join12(options.outputDir, "CONVENTIONS.md");
7956
+ const filePath = join13(options.outputDir, "CONVENTIONS.md");
7118
7957
  writeFileSync3(filePath, content, "utf-8");
7119
7958
  generated.push("CONVENTIONS.md");
7120
7959
  } catch (err) {
@@ -7125,7 +7964,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7125
7964
  try {
7126
7965
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
7127
7966
  const content = generateDependencies(graph, projectRoot, version);
7128
- const filePath = join12(options.outputDir, "DEPENDENCIES.md");
7967
+ const filePath = join13(options.outputDir, "DEPENDENCIES.md");
7129
7968
  writeFileSync3(filePath, content, "utf-8");
7130
7969
  generated.push("DEPENDENCIES.md");
7131
7970
  } catch (err) {
@@ -7136,7 +7975,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7136
7975
  try {
7137
7976
  if (options.verbose) console.log("Generating ONBOARDING.md...");
7138
7977
  const content = generateOnboarding(graph, projectRoot, version);
7139
- const filePath = join12(options.outputDir, "ONBOARDING.md");
7978
+ const filePath = join13(options.outputDir, "ONBOARDING.md");
7140
7979
  writeFileSync3(filePath, content, "utf-8");
7141
7980
  generated.push("ONBOARDING.md");
7142
7981
  } catch (err) {
@@ -7147,7 +7986,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7147
7986
  try {
7148
7987
  if (options.verbose) console.log("Generating FILES.md...");
7149
7988
  const content = generateFiles(graph, projectRoot, version);
7150
- const filePath = join12(options.outputDir, "FILES.md");
7989
+ const filePath = join13(options.outputDir, "FILES.md");
7151
7990
  writeFileSync3(filePath, content, "utf-8");
7152
7991
  generated.push("FILES.md");
7153
7992
  } catch (err) {
@@ -7158,7 +7997,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7158
7997
  try {
7159
7998
  if (options.verbose) console.log("Generating API_SURFACE.md...");
7160
7999
  const content = generateApiSurface(graph, projectRoot, version);
7161
- const filePath = join12(options.outputDir, "API_SURFACE.md");
8000
+ const filePath = join13(options.outputDir, "API_SURFACE.md");
7162
8001
  writeFileSync3(filePath, content, "utf-8");
7163
8002
  generated.push("API_SURFACE.md");
7164
8003
  } catch (err) {
@@ -7169,7 +8008,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7169
8008
  try {
7170
8009
  if (options.verbose) console.log("Generating ERRORS.md...");
7171
8010
  const content = generateErrors(graph, projectRoot, version);
7172
- const filePath = join12(options.outputDir, "ERRORS.md");
8011
+ const filePath = join13(options.outputDir, "ERRORS.md");
7173
8012
  writeFileSync3(filePath, content, "utf-8");
7174
8013
  generated.push("ERRORS.md");
7175
8014
  } catch (err) {
@@ -7180,7 +8019,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7180
8019
  try {
7181
8020
  if (options.verbose) console.log("Generating TESTS.md...");
7182
8021
  const content = generateTests(graph, projectRoot, version);
7183
- const filePath = join12(options.outputDir, "TESTS.md");
8022
+ const filePath = join13(options.outputDir, "TESTS.md");
7184
8023
  writeFileSync3(filePath, content, "utf-8");
7185
8024
  generated.push("TESTS.md");
7186
8025
  } catch (err) {
@@ -7191,7 +8030,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7191
8030
  try {
7192
8031
  if (options.verbose) console.log("Generating HISTORY.md...");
7193
8032
  const content = generateHistory(graph, projectRoot, version);
7194
- const filePath = join12(options.outputDir, "HISTORY.md");
8033
+ const filePath = join13(options.outputDir, "HISTORY.md");
7195
8034
  writeFileSync3(filePath, content, "utf-8");
7196
8035
  generated.push("HISTORY.md");
7197
8036
  } catch (err) {
@@ -7202,7 +8041,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7202
8041
  try {
7203
8042
  if (options.verbose) console.log("Generating CURRENT.md...");
7204
8043
  const content = generateCurrent(graph, projectRoot, version);
7205
- const filePath = join12(options.outputDir, "CURRENT.md");
8044
+ const filePath = join13(options.outputDir, "CURRENT.md");
7206
8045
  writeFileSync3(filePath, content, "utf-8");
7207
8046
  generated.push("CURRENT.md");
7208
8047
  } catch (err) {
@@ -7213,7 +8052,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7213
8052
  try {
7214
8053
  if (options.verbose) console.log("Generating STATUS.md...");
7215
8054
  const content = generateStatus(graph, projectRoot, version);
7216
- const filePath = join12(options.outputDir, "STATUS.md");
8055
+ const filePath = join13(options.outputDir, "STATUS.md");
7217
8056
  writeFileSync3(filePath, content, "utf-8");
7218
8057
  generated.push("STATUS.md");
7219
8058
  } catch (err) {
@@ -7224,13 +8063,24 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7224
8063
  try {
7225
8064
  if (options.verbose) console.log("Generating HEALTH.md...");
7226
8065
  const content = generateHealth(graph, projectRoot, version);
7227
- const filePath = join12(options.outputDir, "HEALTH.md");
8066
+ const filePath = join13(options.outputDir, "HEALTH.md");
7228
8067
  writeFileSync3(filePath, content, "utf-8");
7229
8068
  generated.push("HEALTH.md");
7230
8069
  } catch (err) {
7231
8070
  errors.push(`Failed to generate HEALTH.md: ${err}`);
7232
8071
  }
7233
8072
  }
8073
+ if (docsToGenerate.includes("dead_code")) {
8074
+ try {
8075
+ if (options.verbose) console.log("Generating DEAD_CODE.md...");
8076
+ const content = generateDeadCode(graph, projectRoot, version);
8077
+ const filePath = join13(options.outputDir, "DEAD_CODE.md");
8078
+ writeFileSync3(filePath, content, "utf-8");
8079
+ generated.push("DEAD_CODE.md");
8080
+ } catch (err) {
8081
+ errors.push(`Failed to generate DEAD_CODE.md: ${err}`);
8082
+ }
8083
+ }
7234
8084
  } else if (options.format === "json") {
7235
8085
  errors.push("JSON format not yet supported");
7236
8086
  }
@@ -7272,13 +8122,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7272
8122
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7273
8123
 
7274
8124
  // src/mcp/tools.ts
7275
- import { dirname as dirname14, join as join15 } from "path";
7276
- import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
8125
+ import { dirname as dirname15, join as join16 } from "path";
8126
+ import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
7277
8127
 
7278
8128
  // src/mcp/connect.ts
7279
8129
  import simpleGit from "simple-git";
7280
- import { existsSync as existsSync10 } from "fs";
7281
- import { join as join13, basename as basename5, resolve as resolve2 } from "path";
8130
+ import { existsSync as existsSync11 } from "fs";
8131
+ import { join as join14, basename as basename5, resolve as resolve2 } from "path";
7282
8132
  import { tmpdir, homedir } from "os";
7283
8133
  function validateProjectPath(source) {
7284
8134
  const resolved = resolve2(source);
@@ -7291,11 +8141,11 @@ function validateProjectPath(source) {
7291
8141
  "/boot",
7292
8142
  "/proc",
7293
8143
  "/sys",
7294
- join13(homedir(), ".ssh"),
7295
- join13(homedir(), ".gnupg"),
7296
- join13(homedir(), ".aws"),
7297
- join13(homedir(), ".config"),
7298
- join13(homedir(), ".env")
8144
+ join14(homedir(), ".ssh"),
8145
+ join14(homedir(), ".gnupg"),
8146
+ join14(homedir(), ".aws"),
8147
+ join14(homedir(), ".config"),
8148
+ join14(homedir(), ".env")
7299
8149
  ];
7300
8150
  for (const blocked of blockedPaths) {
7301
8151
  if (resolved.startsWith(blocked)) {
@@ -7318,11 +8168,11 @@ async function connectToRepo(source, subdirectory, state) {
7318
8168
  };
7319
8169
  }
7320
8170
  projectName = match[1];
7321
- const reposDir = join13(tmpdir(), "depwire-repos");
7322
- const cloneDir = join13(reposDir, projectName);
8171
+ const reposDir = join14(tmpdir(), "depwire-repos");
8172
+ const cloneDir = join14(reposDir, projectName);
7323
8173
  console.error(`Connecting to GitHub repo: ${source}`);
7324
8174
  const git = simpleGit();
7325
- if (existsSync10(cloneDir)) {
8175
+ if (existsSync11(cloneDir)) {
7326
8176
  console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
7327
8177
  try {
7328
8178
  await git.cwd(cloneDir).pull();
@@ -7340,7 +8190,7 @@ async function connectToRepo(source, subdirectory, state) {
7340
8190
  };
7341
8191
  }
7342
8192
  }
7343
- projectRoot = subdirectory ? join13(cloneDir, subdirectory) : cloneDir;
8193
+ projectRoot = subdirectory ? join14(cloneDir, subdirectory) : cloneDir;
7344
8194
  } else {
7345
8195
  const validation2 = validateProjectPath(source);
7346
8196
  if (!validation2.valid) {
@@ -7349,13 +8199,13 @@ async function connectToRepo(source, subdirectory, state) {
7349
8199
  message: validation2.error
7350
8200
  };
7351
8201
  }
7352
- if (!existsSync10(source)) {
8202
+ if (!existsSync11(source)) {
7353
8203
  return {
7354
8204
  error: "Directory not found",
7355
8205
  message: `Directory does not exist: ${source}`
7356
8206
  };
7357
8207
  }
7358
- projectRoot = subdirectory ? join13(source, subdirectory) : source;
8208
+ projectRoot = subdirectory ? join14(source, subdirectory) : source;
7359
8209
  projectName = basename5(projectRoot);
7360
8210
  }
7361
8211
  const validation = validateProjectPath(projectRoot);
@@ -7365,7 +8215,7 @@ async function connectToRepo(source, subdirectory, state) {
7365
8215
  message: validation.error
7366
8216
  };
7367
8217
  }
7368
- if (!existsSync10(projectRoot)) {
8218
+ if (!existsSync11(projectRoot)) {
7369
8219
  return {
7370
8220
  error: "Project root not found",
7371
8221
  message: `Directory does not exist: ${projectRoot}`
@@ -7646,24 +8496,24 @@ function getWeekNumber(date) {
7646
8496
  }
7647
8497
 
7648
8498
  // src/temporal/snapshots.ts
7649
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
7650
- import { join as join14 } from "path";
8499
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync8, mkdirSync as mkdirSync3, existsSync as existsSync12, readdirSync as readdirSync4 } from "fs";
8500
+ import { join as join15 } from "path";
7651
8501
  function saveSnapshot(snapshot, outputDir) {
7652
- if (!existsSync11(outputDir)) {
8502
+ if (!existsSync12(outputDir)) {
7653
8503
  mkdirSync3(outputDir, { recursive: true });
7654
8504
  }
7655
8505
  const filename = `${snapshot.commitHash.substring(0, 8)}.json`;
7656
- const filepath = join14(outputDir, filename);
8506
+ const filepath = join15(outputDir, filename);
7657
8507
  writeFileSync4(filepath, JSON.stringify(snapshot, null, 2), "utf-8");
7658
8508
  }
7659
8509
  function loadSnapshot(commitHash, outputDir) {
7660
8510
  const shortHash = commitHash.substring(0, 8);
7661
- const filepath = join14(outputDir, `${shortHash}.json`);
7662
- if (!existsSync11(filepath)) {
8511
+ const filepath = join15(outputDir, `${shortHash}.json`);
8512
+ if (!existsSync12(filepath)) {
7663
8513
  return null;
7664
8514
  }
7665
8515
  try {
7666
- const content = readFileSync7(filepath, "utf-8");
8516
+ const content = readFileSync8(filepath, "utf-8");
7667
8517
  return JSON.parse(content);
7668
8518
  } catch {
7669
8519
  return null;
@@ -7689,8 +8539,8 @@ function createSnapshot(graph, commitHash, commitDate, commitMessage, commitAuth
7689
8539
  }
7690
8540
  }
7691
8541
  }
7692
- const files = Array.from(fileMap.entries()).map(([path2, data]) => ({
7693
- path: path2,
8542
+ const files = Array.from(fileMap.entries()).map(([path6, data]) => ({
8543
+ path: path6,
7694
8544
  symbols: data.symbols,
7695
8545
  connections: data.inbound + data.outbound
7696
8546
  }));
@@ -7927,6 +8777,21 @@ function getToolsList() {
7927
8777
  }
7928
8778
  }
7929
8779
  }
8780
+ },
8781
+ {
8782
+ name: "find_dead_code",
8783
+ description: "Find potentially dead code \u2014 symbols that are defined but never referenced anywhere in the codebase. Returns symbols categorized by confidence level (high, medium, low). High confidence means definitely unused. Use this to identify cleanup opportunities.",
8784
+ inputSchema: {
8785
+ type: "object",
8786
+ properties: {
8787
+ confidence: {
8788
+ type: "string",
8789
+ enum: ["high", "medium", "low"],
8790
+ description: "Minimum confidence level to return (default: medium)",
8791
+ default: "medium"
8792
+ }
8793
+ }
8794
+ }
7930
8795
  }
7931
8796
  ];
7932
8797
  }
@@ -7989,6 +8854,15 @@ async function handleToolCall(name, args, state) {
7989
8854
  } else {
7990
8855
  result = await handleGetTemporalGraph(state, args.commits || 10, args.strategy || "even");
7991
8856
  }
8857
+ } else if (name === "find_dead_code") {
8858
+ if (!isProjectLoaded(state)) {
8859
+ result = {
8860
+ error: "No project loaded",
8861
+ message: "Use connect_repo to connect to a codebase first"
8862
+ };
8863
+ } else {
8864
+ result = handleFindDeadCode(state, args.confidence || "medium");
8865
+ }
7992
8866
  } else {
7993
8867
  if (!isProjectLoaded(state)) {
7994
8868
  result = {
@@ -8326,7 +9200,7 @@ function handleGetArchitectureSummary(graph) {
8326
9200
  const dirMap = /* @__PURE__ */ new Map();
8327
9201
  const languageBreakdown = {};
8328
9202
  fileSummary.forEach((f) => {
8329
- const dir = f.filePath.includes("/") ? dirname14(f.filePath) : ".";
9203
+ const dir = f.filePath.includes("/") ? dirname15(f.filePath) : ".";
8330
9204
  if (!dirMap.has(dir)) {
8331
9205
  dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
8332
9206
  }
@@ -8413,8 +9287,8 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
8413
9287
  };
8414
9288
  }
8415
9289
  async function handleGetProjectDocs(docType, state) {
8416
- const docsDir = join15(state.projectRoot, ".depwire");
8417
- if (!existsSync12(docsDir)) {
9290
+ const docsDir = join16(state.projectRoot, ".depwire");
9291
+ if (!existsSync13(docsDir)) {
8418
9292
  const errorMessage = `Project documentation has not been generated yet.
8419
9293
 
8420
9294
  Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
@@ -8444,12 +9318,12 @@ Available document types:
8444
9318
  missing.push(doc);
8445
9319
  continue;
8446
9320
  }
8447
- const filePath = join15(docsDir, metadata.documents[doc].file);
8448
- if (!existsSync12(filePath)) {
9321
+ const filePath = join16(docsDir, metadata.documents[doc].file);
9322
+ if (!existsSync13(filePath)) {
8449
9323
  missing.push(doc);
8450
9324
  continue;
8451
9325
  }
8452
- const content = readFileSync8(filePath, "utf-8");
9326
+ const content = readFileSync9(filePath, "utf-8");
8453
9327
  if (docsToReturn.length > 1) {
8454
9328
  output += `
8455
9329
 
@@ -8474,16 +9348,16 @@ Available document types:
8474
9348
  }
8475
9349
  async function handleUpdateProjectDocs(docType, state) {
8476
9350
  const startTime = Date.now();
8477
- const docsDir = join15(state.projectRoot, ".depwire");
9351
+ const docsDir = join16(state.projectRoot, ".depwire");
8478
9352
  console.error("Regenerating project documentation...");
8479
9353
  const parsedFiles = await parseProject(state.projectRoot);
8480
9354
  const graph = buildGraph(parsedFiles);
8481
9355
  const parseTime = (Date.now() - startTime) / 1e3;
8482
9356
  state.graph = graph;
8483
- const packageJsonPath = join15(__dirname, "../../package.json");
8484
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
9357
+ const packageJsonPath = join16(__dirname, "../../package.json");
9358
+ const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
8485
9359
  const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
8486
- const docsExist = existsSync12(docsDir);
9360
+ const docsExist = existsSync13(docsDir);
8487
9361
  const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
8488
9362
  outputDir: docsDir,
8489
9363
  format: "markdown",
@@ -8542,7 +9416,7 @@ async function handleGetTemporalGraph(state, commits, strategy) {
8542
9416
  }
8543
9417
  const sampledCommits = sampleCommits(allCommits, commits, strategy);
8544
9418
  const snapshots = [];
8545
- const outputDir = join15(projectRoot, ".depwire", "temporal");
9419
+ const outputDir = join16(projectRoot, ".depwire", "temporal");
8546
9420
  for (const commit of sampledCommits) {
8547
9421
  const existing = loadSnapshot(commit.hash, outputDir);
8548
9422
  if (existing) {
@@ -8604,6 +9478,46 @@ async function handleGetTemporalGraph(state, commits, strategy) {
8604
9478
  };
8605
9479
  }
8606
9480
  }
9481
+ function handleFindDeadCode(state, confidence) {
9482
+ if (!state.graph || !state.projectRoot) {
9483
+ return {
9484
+ error: "No project loaded",
9485
+ message: "Use connect_repo to connect to a codebase first"
9486
+ };
9487
+ }
9488
+ try {
9489
+ const report = analyzeDeadCode(state.graph, state.projectRoot, {
9490
+ confidence,
9491
+ includeTests: false,
9492
+ verbose: false,
9493
+ stats: false,
9494
+ json: true
9495
+ });
9496
+ return {
9497
+ status: "success",
9498
+ totalSymbols: report.totalSymbols,
9499
+ deadSymbols: report.deadSymbols,
9500
+ deadPercentage: report.deadPercentage,
9501
+ byConfidence: report.byConfidence,
9502
+ symbols: report.symbols.map((s) => ({
9503
+ name: s.name,
9504
+ kind: s.kind,
9505
+ file: s.file,
9506
+ line: s.line,
9507
+ exported: s.exported,
9508
+ dependents: s.dependents,
9509
+ confidence: s.confidence,
9510
+ reason: s.reason
9511
+ })),
9512
+ summary: `Found ${report.deadSymbols} potentially dead symbols (${report.byConfidence.high} high, ${report.byConfidence.medium} medium, ${report.byConfidence.low} low confidence) out of ${report.totalSymbols} total symbols (${report.deadPercentage.toFixed(1)}% dead code).`
9513
+ };
9514
+ } catch (error) {
9515
+ return {
9516
+ error: "Failed to analyze dead code",
9517
+ message: String(error)
9518
+ };
9519
+ }
9520
+ }
8607
9521
 
8608
9522
  // src/mcp/server.ts
8609
9523
  async function startMcpServer(state) {
@@ -8652,6 +9566,7 @@ export {
8652
9566
  updateFileInGraph,
8653
9567
  calculateHealthScore,
8654
9568
  getHealthTrend,
9569
+ analyzeDeadCode,
8655
9570
  generateDocs,
8656
9571
  getCommitLog,
8657
9572
  getCurrentBranch,