depwire-cli 0.9.0 → 0.9.2

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.
@@ -30,7 +30,8 @@ function scanDirectory(rootDir, baseDir = rootDir) {
30
30
  const isPython = entry.endsWith(".py");
31
31
  const isGo = entry.endsWith(".go") && !entry.endsWith("_test.go");
32
32
  const isRust = entry.endsWith(".rs");
33
- if (isTypeScript || isJavaScript || isPython || isGo || isRust) {
33
+ const isC = entry.endsWith(".c") || entry.endsWith(".h");
34
+ if (isTypeScript || isJavaScript || isPython || isGo || isRust || isC) {
34
35
  files.push(relative(rootDir, fullPath));
35
36
  }
36
37
  }
@@ -61,6 +62,12 @@ function findProjectRoot(startDir = process.cwd()) {
61
62
  // Python (modern)
62
63
  "setup.py",
63
64
  // Python (legacy)
65
+ "Makefile",
66
+ // C/C++ (make-based)
67
+ "CMakeLists.txt",
68
+ // C/C++ (cmake-based)
69
+ "configure.ac",
70
+ // C/C++ (autotools)
64
71
  ".git"
65
72
  // Any git repo
66
73
  ];
@@ -83,8 +90,8 @@ function findProjectRoot(startDir = process.cwd()) {
83
90
  }
84
91
 
85
92
  // src/parser/index.ts
86
- import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
87
- import { join as join7 } from "path";
93
+ import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
94
+ import { join as join8 } from "path";
88
95
 
89
96
  // src/parser/detect.ts
90
97
  import { extname as extname3 } from "path";
@@ -113,7 +120,8 @@ async function initParser() {
113
120
  "javascript": "tree-sitter-javascript.wasm",
114
121
  "python": "tree-sitter-python.wasm",
115
122
  "go": "tree-sitter-go.wasm",
116
- "rust": "tree-sitter-rust.wasm"
123
+ "rust": "tree-sitter-rust.wasm",
124
+ "c": "tree-sitter-c.wasm"
117
125
  };
118
126
  for (const [name, file] of Object.entries(grammarFiles)) {
119
127
  const wasmPath = path.join(grammarsDir, file);
@@ -2392,13 +2400,383 @@ var rustParser = {
2392
2400
  parseFile: parseRustFile
2393
2401
  };
2394
2402
 
2403
+ // src/parser/c.ts
2404
+ import { existsSync as existsSync7 } from "fs";
2405
+ import { join as join7, dirname as dirname6, relative as relative4 } from "path";
2406
+ function parseCFile(filePath, sourceCode, projectRoot) {
2407
+ const parser = getParser("c");
2408
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
2409
+ const context = {
2410
+ filePath,
2411
+ projectRoot,
2412
+ sourceCode,
2413
+ symbols: [],
2414
+ edges: [],
2415
+ currentScope: []
2416
+ };
2417
+ walkNode6(tree.rootNode, context);
2418
+ return {
2419
+ filePath,
2420
+ symbols: context.symbols,
2421
+ edges: context.edges
2422
+ };
2423
+ }
2424
+ function walkNode6(node, context) {
2425
+ processNode6(node, context);
2426
+ for (let i = 0; i < node.childCount; i++) {
2427
+ const child = node.child(i);
2428
+ if (child) {
2429
+ walkNode6(child, context);
2430
+ }
2431
+ }
2432
+ }
2433
+ function processNode6(node, context) {
2434
+ const type = node.type;
2435
+ switch (type) {
2436
+ case "function_definition":
2437
+ processFunctionDefinition2(node, context);
2438
+ break;
2439
+ case "struct_specifier":
2440
+ processStructSpecifier(node, context);
2441
+ break;
2442
+ case "enum_specifier":
2443
+ processEnumSpecifier(node, context);
2444
+ break;
2445
+ case "type_definition":
2446
+ processTypeDefinition(node, context);
2447
+ break;
2448
+ case "declaration":
2449
+ processDeclaration(node, context);
2450
+ break;
2451
+ case "preproc_def":
2452
+ case "preproc_function_def":
2453
+ processMacroDefinition(node, context);
2454
+ break;
2455
+ case "preproc_include":
2456
+ processIncludeDirective(node, context);
2457
+ break;
2458
+ case "call_expression":
2459
+ processCallExpression6(node, context);
2460
+ break;
2461
+ }
2462
+ }
2463
+ function processFunctionDefinition2(node, context) {
2464
+ const declarator = node.childForFieldName("declarator");
2465
+ if (!declarator) return;
2466
+ const nameNode = extractFunctionName(declarator);
2467
+ if (!nameNode) return;
2468
+ const name = nodeText5(nameNode, context);
2469
+ const exported = !hasStorageClass(node, "static", context);
2470
+ const symbolId = `${context.filePath}::${name}`;
2471
+ context.symbols.push({
2472
+ id: symbolId,
2473
+ name,
2474
+ kind: "function",
2475
+ filePath: context.filePath,
2476
+ startLine: node.startPosition.row + 1,
2477
+ endLine: node.endPosition.row + 1,
2478
+ exported
2479
+ });
2480
+ context.currentScope.push(name);
2481
+ const body = node.childForFieldName("body");
2482
+ if (body) {
2483
+ walkNode6(body, context);
2484
+ }
2485
+ context.currentScope.pop();
2486
+ }
2487
+ function processStructSpecifier(node, context) {
2488
+ const parent = node.parent;
2489
+ let name = null;
2490
+ if (parent && parent.type === "type_definition") {
2491
+ const typedefName = parent.childForFieldName("declarator");
2492
+ if (typedefName) {
2493
+ name = extractIdentifierFromDeclarator(typedefName, context);
2494
+ }
2495
+ }
2496
+ if (!name) {
2497
+ const nameNode = node.childForFieldName("name");
2498
+ if (nameNode) {
2499
+ name = nodeText5(nameNode, context);
2500
+ }
2501
+ }
2502
+ if (!name) return;
2503
+ const symbolId = `${context.filePath}::${name}`;
2504
+ context.symbols.push({
2505
+ id: symbolId,
2506
+ name,
2507
+ kind: "class",
2508
+ filePath: context.filePath,
2509
+ startLine: node.startPosition.row + 1,
2510
+ endLine: node.endPosition.row + 1,
2511
+ exported: true
2512
+ });
2513
+ }
2514
+ function processEnumSpecifier(node, context) {
2515
+ const parent = node.parent;
2516
+ let name = null;
2517
+ if (parent && parent.type === "type_definition") {
2518
+ const typedefName = parent.childForFieldName("declarator");
2519
+ if (typedefName) {
2520
+ name = extractIdentifierFromDeclarator(typedefName, context);
2521
+ }
2522
+ }
2523
+ if (!name) {
2524
+ const nameNode = node.childForFieldName("name");
2525
+ if (nameNode) {
2526
+ name = nodeText5(nameNode, context);
2527
+ }
2528
+ }
2529
+ if (!name) return;
2530
+ const symbolId = `${context.filePath}::${name}`;
2531
+ context.symbols.push({
2532
+ id: symbolId,
2533
+ name,
2534
+ kind: "enum",
2535
+ filePath: context.filePath,
2536
+ startLine: node.startPosition.row + 1,
2537
+ endLine: node.endPosition.row + 1,
2538
+ exported: true
2539
+ });
2540
+ }
2541
+ function processTypeDefinition(node, context) {
2542
+ const typeNode = node.childForFieldName("type");
2543
+ if (!typeNode) return;
2544
+ if (typeNode.type === "struct_specifier" || typeNode.type === "enum_specifier") {
2545
+ return;
2546
+ }
2547
+ const declarator = node.childForFieldName("declarator");
2548
+ if (!declarator) return;
2549
+ const name = extractIdentifierFromDeclarator(declarator, context);
2550
+ if (!name) return;
2551
+ const symbolId = `${context.filePath}::${name}`;
2552
+ context.symbols.push({
2553
+ id: symbolId,
2554
+ name,
2555
+ kind: "type_alias",
2556
+ filePath: context.filePath,
2557
+ startLine: node.startPosition.row + 1,
2558
+ endLine: node.endPosition.row + 1,
2559
+ exported: true
2560
+ });
2561
+ }
2562
+ function processDeclaration(node, context) {
2563
+ if (context.currentScope.length > 0) {
2564
+ return;
2565
+ }
2566
+ const parent = node.parent;
2567
+ if (!parent || parent.type !== "translation_unit") {
2568
+ return;
2569
+ }
2570
+ const hasStatic = hasStorageClass(node, "static", context);
2571
+ const declarator = node.childForFieldName("declarator");
2572
+ if (!declarator) return;
2573
+ const name = extractIdentifierFromDeclarator(declarator, context);
2574
+ if (!name) return;
2575
+ const symbolId = `${context.filePath}::${name}`;
2576
+ context.symbols.push({
2577
+ id: symbolId,
2578
+ name,
2579
+ kind: "variable",
2580
+ filePath: context.filePath,
2581
+ startLine: node.startPosition.row + 1,
2582
+ endLine: node.endPosition.row + 1,
2583
+ exported: !hasStatic
2584
+ });
2585
+ }
2586
+ function processMacroDefinition(node, context) {
2587
+ const nameNode = node.childForFieldName("name");
2588
+ if (!nameNode) return;
2589
+ const name = nodeText5(nameNode, context);
2590
+ const kind = node.type === "preproc_function_def" ? "function" : "constant";
2591
+ const symbolId = `${context.filePath}::${name}`;
2592
+ context.symbols.push({
2593
+ id: symbolId,
2594
+ name,
2595
+ kind,
2596
+ filePath: context.filePath,
2597
+ startLine: node.startPosition.row + 1,
2598
+ endLine: node.endPosition.row + 1,
2599
+ exported: true
2600
+ });
2601
+ }
2602
+ function processIncludeDirective(node, context) {
2603
+ const pathNode = node.childForFieldName("path");
2604
+ if (!pathNode) return;
2605
+ const pathText = nodeText5(pathNode, context);
2606
+ const isLocalInclude = pathText.startsWith('"') && pathText.endsWith('"');
2607
+ if (!isLocalInclude) {
2608
+ return;
2609
+ }
2610
+ const includePath = pathText.slice(1, -1);
2611
+ const resolvedFiles = resolveIncludePath(includePath, context.filePath, context.projectRoot);
2612
+ if (resolvedFiles.length === 0) return;
2613
+ const sourceId = `${context.filePath}::__file__`;
2614
+ for (const targetPath of resolvedFiles) {
2615
+ const targetId = `${targetPath}::__file__`;
2616
+ context.edges.push({
2617
+ source: sourceId,
2618
+ target: targetId,
2619
+ kind: "imports",
2620
+ filePath: context.filePath,
2621
+ line: node.startPosition.row + 1
2622
+ });
2623
+ }
2624
+ }
2625
+ function processCallExpression6(node, context) {
2626
+ if (context.currentScope.length === 0) return;
2627
+ const functionNode = node.childForFieldName("function");
2628
+ if (!functionNode) return;
2629
+ const calleeName = nodeText5(functionNode, context);
2630
+ const builtins = /* @__PURE__ */ new Set(["printf", "scanf", "malloc", "free", "memcpy", "strlen", "strcmp", "strcpy", "strcat"]);
2631
+ if (builtins.has(calleeName)) return;
2632
+ const callerId = getCurrentSymbolId6(context);
2633
+ if (!callerId) return;
2634
+ const calleeId = resolveSymbol5(calleeName, context);
2635
+ if (!calleeId) return;
2636
+ context.edges.push({
2637
+ source: callerId,
2638
+ target: calleeId,
2639
+ kind: "calls",
2640
+ filePath: context.filePath,
2641
+ line: node.startPosition.row + 1
2642
+ });
2643
+ }
2644
+ function resolveIncludePath(includePath, currentFile, projectRoot) {
2645
+ const currentFileAbs = join7(projectRoot, currentFile);
2646
+ const currentDir = dirname6(currentFileAbs);
2647
+ const possibleFiles = [
2648
+ join7(currentDir, includePath),
2649
+ join7(projectRoot, includePath)
2650
+ ];
2651
+ const resolvedFiles = [];
2652
+ for (const absPath of possibleFiles) {
2653
+ if (existsSync7(absPath)) {
2654
+ const relPath = relative4(projectRoot, absPath);
2655
+ resolvedFiles.push(relPath);
2656
+ }
2657
+ }
2658
+ return resolvedFiles;
2659
+ }
2660
+ function hasStorageClass(node, className, context) {
2661
+ for (let i = 0; i < node.childCount; i++) {
2662
+ const child = node.child(i);
2663
+ if (child && child.type === "storage_class_specifier") {
2664
+ const text = nodeText5(child, context);
2665
+ if (text === className) {
2666
+ return true;
2667
+ }
2668
+ }
2669
+ }
2670
+ let parent = node.parent;
2671
+ while (parent) {
2672
+ for (let i = 0; i < parent.childCount; i++) {
2673
+ const child = parent.child(i);
2674
+ if (child && child.type === "storage_class_specifier") {
2675
+ const text = nodeText5(child, context);
2676
+ if (text === className) {
2677
+ return true;
2678
+ }
2679
+ }
2680
+ }
2681
+ parent = parent.parent;
2682
+ }
2683
+ return false;
2684
+ }
2685
+ function extractFunctionName(declarator) {
2686
+ if (declarator.type === "identifier") {
2687
+ return declarator;
2688
+ }
2689
+ if (declarator.type === "function_declarator") {
2690
+ const innerDeclarator = declarator.childForFieldName("declarator");
2691
+ if (innerDeclarator) {
2692
+ return extractFunctionName(innerDeclarator);
2693
+ }
2694
+ }
2695
+ if (declarator.type === "pointer_declarator") {
2696
+ const innerDeclarator = declarator.childForFieldName("declarator");
2697
+ if (innerDeclarator) {
2698
+ return extractFunctionName(innerDeclarator);
2699
+ }
2700
+ }
2701
+ for (let i = 0; i < declarator.childCount; i++) {
2702
+ const child = declarator.child(i);
2703
+ if (child && child.type === "identifier") {
2704
+ return child;
2705
+ }
2706
+ }
2707
+ return null;
2708
+ }
2709
+ function extractIdentifierFromDeclarator(declarator, context) {
2710
+ if (declarator.type === "identifier") {
2711
+ return nodeText5(declarator, context);
2712
+ }
2713
+ if (declarator.type === "type_identifier") {
2714
+ return nodeText5(declarator, context);
2715
+ }
2716
+ const identifierNode = findChildByType6(declarator, "identifier");
2717
+ if (identifierNode) {
2718
+ return nodeText5(identifierNode, context);
2719
+ }
2720
+ const typeIdNode = findChildByType6(declarator, "type_identifier");
2721
+ if (typeIdNode) {
2722
+ return nodeText5(typeIdNode, context);
2723
+ }
2724
+ for (let i = 0; i < declarator.childCount; i++) {
2725
+ const child = declarator.child(i);
2726
+ if (child) {
2727
+ const name = extractIdentifierFromDeclarator(child, context);
2728
+ if (name) return name;
2729
+ }
2730
+ }
2731
+ return null;
2732
+ }
2733
+ function resolveSymbol5(name, context) {
2734
+ const currentFileId = `${context.filePath}::__file__`;
2735
+ const symbol = context.symbols.find(
2736
+ (s) => s.name === name && (s.filePath === context.filePath || s.exported)
2737
+ );
2738
+ if (symbol) {
2739
+ return symbol.id;
2740
+ }
2741
+ for (let i = context.currentScope.length - 1; i >= 0; i--) {
2742
+ const scopedId = `${context.filePath}::${context.currentScope.slice(0, i + 1).join("::")}::${name}`;
2743
+ const scopedSymbol = context.symbols.find((s) => s.id === scopedId);
2744
+ if (scopedSymbol) {
2745
+ return scopedSymbol.id;
2746
+ }
2747
+ }
2748
+ return null;
2749
+ }
2750
+ function findChildByType6(node, type) {
2751
+ for (let i = 0; i < node.childCount; i++) {
2752
+ const child = node.child(i);
2753
+ if (child && child.type === type) {
2754
+ return child;
2755
+ }
2756
+ }
2757
+ return null;
2758
+ }
2759
+ function nodeText5(node, context) {
2760
+ return context.sourceCode.slice(node.startIndex, node.endIndex);
2761
+ }
2762
+ function getCurrentSymbolId6(context) {
2763
+ if (context.currentScope.length === 0) return null;
2764
+ return `${context.filePath}::${context.currentScope.join("::")}`;
2765
+ }
2766
+ var cParser = {
2767
+ name: "c",
2768
+ extensions: [".c", ".h"],
2769
+ parseFile: parseCFile
2770
+ };
2771
+
2395
2772
  // src/parser/detect.ts
2396
2773
  var parsers = [
2397
2774
  typescriptParser,
2398
2775
  pythonParser,
2399
2776
  javascriptParser,
2400
2777
  goParser,
2401
- rustParser
2778
+ rustParser,
2779
+ cParser
2402
2780
  ];
2403
2781
  function getParserForFile(filePath) {
2404
2782
  const ext = extname3(filePath).toLowerCase();
@@ -2428,7 +2806,7 @@ async function parseProject(projectRoot, options) {
2428
2806
  let errorFiles = 0;
2429
2807
  for (const file of files) {
2430
2808
  try {
2431
- const fullPath = join7(projectRoot, file);
2809
+ const fullPath = join8(projectRoot, file);
2432
2810
  if (options?.exclude) {
2433
2811
  const shouldExclude2 = options.exclude.some(
2434
2812
  (pattern) => minimatch(file, pattern, { matchBase: true })
@@ -2454,7 +2832,7 @@ async function parseProject(projectRoot, options) {
2454
2832
  skippedFiles++;
2455
2833
  continue;
2456
2834
  }
2457
- const sourceCode = readFileSync4(fullPath, "utf-8");
2835
+ const sourceCode = readFileSync5(fullPath, "utf-8");
2458
2836
  const parsed = parser.parseFile(file, sourceCode, projectRoot);
2459
2837
  parsedFiles.push(parsed);
2460
2838
  } catch (err) {
@@ -2847,7 +3225,7 @@ function watchProject(projectRoot, callbacks) {
2847
3225
  const watcher = chokidar.watch(projectRoot, watcherOptions);
2848
3226
  console.error("[Watcher] Attaching event listeners...");
2849
3227
  watcher.on("change", (absolutePath) => {
2850
- const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs"];
3228
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".c", ".h"];
2851
3229
  if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2852
3230
  if (absolutePath.endsWith("_test.go")) return;
2853
3231
  const relativePath = absolutePath.replace(projectRoot + "/", "");
@@ -2855,7 +3233,7 @@ function watchProject(projectRoot, callbacks) {
2855
3233
  callbacks.onFileChanged(relativePath);
2856
3234
  });
2857
3235
  watcher.on("add", (absolutePath) => {
2858
- const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs"];
3236
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".c", ".h"];
2859
3237
  if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2860
3238
  if (absolutePath.endsWith("_test.go")) return;
2861
3239
  const relativePath = absolutePath.replace(projectRoot + "/", "");
@@ -2863,7 +3241,7 @@ function watchProject(projectRoot, callbacks) {
2863
3241
  callbacks.onFileAdded(relativePath);
2864
3242
  });
2865
3243
  watcher.on("unlink", (absolutePath) => {
2866
- const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs"];
3244
+ const validExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".c", ".h"];
2867
3245
  if (!validExtensions.some((ext) => absolutePath.endsWith(ext))) return;
2868
3246
  if (absolutePath.endsWith("_test.go")) return;
2869
3247
  const relativePath = absolutePath.replace(projectRoot + "/", "");
@@ -2881,10 +3259,10 @@ function watchProject(projectRoot, callbacks) {
2881
3259
  for (const dir of dirs) {
2882
3260
  const files = watched[dir];
2883
3261
  fileCount += files.filter(
2884
- (f) => f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx") || f.endsWith(".mjs") || f.endsWith(".cjs") || f.endsWith(".py") || f.endsWith(".go") && !f.endsWith("_test.go")
3262
+ (f) => f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx") || f.endsWith(".mjs") || f.endsWith(".cjs") || f.endsWith(".py") || f.endsWith(".go") && !f.endsWith("_test.go") || f.endsWith(".rs") || f.endsWith(".c") || f.endsWith(".h")
2885
3263
  ).length;
2886
3264
  }
2887
- console.error(`[Watcher] Watching ${fileCount} TypeScript/JavaScript/Python/Go files in ${dirs.length} directories`);
3265
+ console.error(`[Watcher] Watching ${fileCount} TypeScript/JavaScript/Python/Go/Rust/C files in ${dirs.length} directories`);
2888
3266
  });
2889
3267
  return watcher;
2890
3268
  }
@@ -2893,10 +3271,10 @@ function watchProject(projectRoot, callbacks) {
2893
3271
  import express from "express";
2894
3272
  import open from "open";
2895
3273
  import { fileURLToPath as fileURLToPath2 } from "url";
2896
- import { dirname as dirname6, join as join8 } from "path";
3274
+ import { dirname as dirname7, join as join9 } from "path";
2897
3275
  import { WebSocketServer } from "ws";
2898
3276
  var __filename = fileURLToPath2(import.meta.url);
2899
- var __dirname2 = dirname6(__filename);
3277
+ var __dirname2 = dirname7(__filename);
2900
3278
  var activeServer = null;
2901
3279
  async function findAvailablePort(startPort, maxAttempts = 10) {
2902
3280
  const net = await import("net");
@@ -2934,7 +3312,7 @@ async function startVizServer(initialVizData, graph, projectRoot, port = 3333, s
2934
3312
  const availablePort = await findAvailablePort(port);
2935
3313
  const app = express();
2936
3314
  let vizData = initialVizData;
2937
- const publicDir = join8(__dirname2, "viz", "public");
3315
+ const publicDir = join9(__dirname2, "viz", "public");
2938
3316
  app.use(express.static(publicDir));
2939
3317
  app.get("/api/graph", (req, res) => {
2940
3318
  res.json(vizData);
@@ -3050,7 +3428,7 @@ function isProjectLoaded(state) {
3050
3428
  }
3051
3429
 
3052
3430
  // src/graph/updater.ts
3053
- import { join as join9 } from "path";
3431
+ import { join as join10 } from "path";
3054
3432
  function removeFileFromGraph(graph, filePath) {
3055
3433
  const nodesToRemove = [];
3056
3434
  graph.forEachNode((node, attrs) => {
@@ -3094,7 +3472,7 @@ function addFileToGraph(graph, parsedFile) {
3094
3472
  }
3095
3473
  async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
3096
3474
  removeFileFromGraph(graph, relativeFilePath);
3097
- const absolutePath = join9(projectRoot, relativeFilePath);
3475
+ const absolutePath = join10(projectRoot, relativeFilePath);
3098
3476
  try {
3099
3477
  const parsedFile = parseTypeScriptFile(absolutePath, relativeFilePath);
3100
3478
  addFileToGraph(graph, parsedFile);
@@ -3104,7 +3482,7 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
3104
3482
  }
3105
3483
 
3106
3484
  // src/health/metrics.ts
3107
- import { dirname as dirname7 } from "path";
3485
+ import { dirname as dirname8 } from "path";
3108
3486
  function scoreToGrade(score) {
3109
3487
  if (score >= 90) return "A";
3110
3488
  if (score >= 80) return "B";
@@ -3137,8 +3515,8 @@ function calculateCouplingScore(graph) {
3137
3515
  totalEdges++;
3138
3516
  fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
3139
3517
  fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
3140
- const sourceDir = dirname7(sourceAttrs.filePath).split("/")[0];
3141
- const targetDir = dirname7(targetAttrs.filePath).split("/")[0];
3518
+ const sourceDir = dirname8(sourceAttrs.filePath).split("/")[0];
3519
+ const targetDir = dirname8(targetAttrs.filePath).split("/")[0];
3142
3520
  if (sourceDir !== targetDir) {
3143
3521
  crossDirEdges++;
3144
3522
  }
@@ -3185,8 +3563,8 @@ function calculateCohesionScore(graph) {
3185
3563
  const sourceAttrs = graph.getNodeAttributes(source);
3186
3564
  const targetAttrs = graph.getNodeAttributes(target);
3187
3565
  if (sourceAttrs.filePath !== targetAttrs.filePath) {
3188
- const sourceDir = dirname7(sourceAttrs.filePath);
3189
- const targetDir = dirname7(targetAttrs.filePath);
3566
+ const sourceDir = dirname8(sourceAttrs.filePath);
3567
+ const targetDir = dirname8(targetAttrs.filePath);
3190
3568
  if (!dirEdges.has(sourceDir)) {
3191
3569
  dirEdges.set(sourceDir, { internal: 0, total: 0 });
3192
3570
  }
@@ -3468,8 +3846,8 @@ function calculateDepthScore(graph) {
3468
3846
  }
3469
3847
 
3470
3848
  // src/health/index.ts
3471
- import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync7, mkdirSync } from "fs";
3472
- import { join as join10, dirname as dirname8 } from "path";
3849
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
3850
+ import { join as join11, dirname as dirname9 } from "path";
3473
3851
  function calculateHealthScore(graph, projectRoot) {
3474
3852
  const coupling = calculateCouplingScore(graph);
3475
3853
  const cohesion = calculateCohesionScore(graph);
@@ -3568,7 +3946,7 @@ function getHealthTrend(projectRoot, currentScore) {
3568
3946
  }
3569
3947
  }
3570
3948
  function saveHealthHistory(projectRoot, report) {
3571
- const historyFile = join10(projectRoot, ".depwire", "health-history.json");
3949
+ const historyFile = join11(projectRoot, ".depwire", "health-history.json");
3572
3950
  const entry = {
3573
3951
  timestamp: report.timestamp,
3574
3952
  score: report.overall,
@@ -3580,9 +3958,9 @@ function saveHealthHistory(projectRoot, report) {
3580
3958
  }))
3581
3959
  };
3582
3960
  let history = [];
3583
- if (existsSync7(historyFile)) {
3961
+ if (existsSync8(historyFile)) {
3584
3962
  try {
3585
- const content = readFileSync5(historyFile, "utf-8");
3963
+ const content = readFileSync6(historyFile, "utf-8");
3586
3964
  history = JSON.parse(content);
3587
3965
  } catch {
3588
3966
  }
@@ -3591,16 +3969,16 @@ function saveHealthHistory(projectRoot, report) {
3591
3969
  if (history.length > 50) {
3592
3970
  history = history.slice(-50);
3593
3971
  }
3594
- mkdirSync(dirname8(historyFile), { recursive: true });
3972
+ mkdirSync(dirname9(historyFile), { recursive: true });
3595
3973
  writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
3596
3974
  }
3597
3975
  function loadHealthHistory(projectRoot) {
3598
- const historyFile = join10(projectRoot, ".depwire", "health-history.json");
3599
- if (!existsSync7(historyFile)) {
3976
+ const historyFile = join11(projectRoot, ".depwire", "health-history.json");
3977
+ if (!existsSync8(historyFile)) {
3600
3978
  return [];
3601
3979
  }
3602
3980
  try {
3603
- const content = readFileSync5(historyFile, "utf-8");
3981
+ const content = readFileSync6(historyFile, "utf-8");
3604
3982
  return JSON.parse(content);
3605
3983
  } catch {
3606
3984
  return [];
@@ -3609,21 +3987,89 @@ function loadHealthHistory(projectRoot) {
3609
3987
 
3610
3988
  // src/dead-code/detector.ts
3611
3989
  import path2 from "path";
3612
- function findDeadSymbols(graph, projectRoot, includeTests = false) {
3990
+ import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
3991
+ function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
3613
3992
  const deadSymbols = [];
3614
3993
  const context = { graph, projectRoot };
3994
+ const stats = {
3995
+ total: 0,
3996
+ excludedByTestFile: 0,
3997
+ excludedByEntryPoint: 0,
3998
+ excludedByConfigFile: 0,
3999
+ excludedByTypeDeclaration: 0,
4000
+ excludedByDefaultExport: 0,
4001
+ excludedByFrameworkDir: 0
4002
+ };
4003
+ const packageEntryPoints = getPackageEntryPoints(projectRoot);
4004
+ if (debug) {
4005
+ console.log("\n\u{1F50D} Debug: Graph Structure");
4006
+ console.log(`Total nodes in graph: ${graph.order}`);
4007
+ console.log(`Total edges in graph: ${graph.size}`);
4008
+ let nodesWithZeroInDegree = 0;
4009
+ let nodesWithZeroOutDegree = 0;
4010
+ graph.forEachNode((node) => {
4011
+ if (graph.inDegree(node) === 0) nodesWithZeroInDegree++;
4012
+ if (graph.outDegree(node) === 0) nodesWithZeroOutDegree++;
4013
+ });
4014
+ console.log(`Nodes with inDegree=0: ${nodesWithZeroInDegree}`);
4015
+ console.log(`Nodes with outDegree=0: ${nodesWithZeroOutDegree}`);
4016
+ if (nodesWithZeroInDegree <= 10) {
4017
+ console.log("\nSample nodes with inDegree=0:");
4018
+ let count = 0;
4019
+ graph.forEachNode((node) => {
4020
+ if (graph.inDegree(node) === 0 && count < 10) {
4021
+ const attrs = graph.getNodeAttributes(node);
4022
+ const filePath = attrs.file || attrs.filePath || "unknown";
4023
+ console.log(` - ${attrs.name} (${attrs.kind}) in ${path2.relative(projectRoot, filePath)}`);
4024
+ count++;
4025
+ }
4026
+ });
4027
+ }
4028
+ }
3615
4029
  for (const node of graph.nodes()) {
3616
4030
  const attrs = graph.getNodeAttributes(node);
3617
- if (!attrs.file || !attrs.name) continue;
4031
+ if (!attrs.name) continue;
4032
+ if (!attrs.file && !attrs.filePath) {
4033
+ if (debug) {
4034
+ console.log(`Skipping node ${attrs.name} - no file attribute`);
4035
+ }
4036
+ continue;
4037
+ }
4038
+ const filePath = attrs.file || attrs.filePath;
4039
+ if (!isRelevantForDeadCodeDetection(attrs)) {
4040
+ continue;
4041
+ }
3618
4042
  const inDegree = graph.inDegree(node);
3619
4043
  if (inDegree === 0) {
3620
- if (shouldExclude(attrs, context, includeTests)) {
4044
+ stats.total++;
4045
+ const exclusionReason = shouldExclude(attrs, context, includeTests, packageEntryPoints);
4046
+ if (exclusionReason) {
4047
+ switch (exclusionReason) {
4048
+ case "test":
4049
+ stats.excludedByTestFile++;
4050
+ break;
4051
+ case "entry":
4052
+ stats.excludedByEntryPoint++;
4053
+ break;
4054
+ case "config":
4055
+ stats.excludedByConfigFile++;
4056
+ break;
4057
+ case "types":
4058
+ stats.excludedByTypeDeclaration++;
4059
+ break;
4060
+ case "default":
4061
+ stats.excludedByDefaultExport++;
4062
+ break;
4063
+ case "framework":
4064
+ stats.excludedByFrameworkDir++;
4065
+ break;
4066
+ }
3621
4067
  continue;
3622
4068
  }
3623
4069
  deadSymbols.push({
3624
4070
  name: attrs.name,
3625
4071
  kind: attrs.kind || "unknown",
3626
- file: attrs.file,
4072
+ file: filePath,
3627
4073
  line: attrs.startLine || 0,
3628
4074
  exported: attrs.exported || false,
3629
4075
  dependents: 0,
@@ -3632,38 +4078,115 @@ function findDeadSymbols(graph, projectRoot, includeTests = false) {
3632
4078
  });
3633
4079
  }
3634
4080
  }
3635
- return deadSymbols;
4081
+ if (debug) {
4082
+ console.log("\n\u{1F50D} Debug: Exclusion Statistics");
4083
+ console.log(`Total symbols with 0 incoming edges: ${stats.total}`);
4084
+ console.log(`Excluded by test file: ${stats.excludedByTestFile}`);
4085
+ console.log(`Excluded by entry point: ${stats.excludedByEntryPoint}`);
4086
+ console.log(`Excluded by config file: ${stats.excludedByConfigFile}`);
4087
+ console.log(`Excluded by type declaration: ${stats.excludedByTypeDeclaration}`);
4088
+ console.log(`Excluded by default export: ${stats.excludedByDefaultExport}`);
4089
+ console.log(`Excluded by framework dir: ${stats.excludedByFrameworkDir}`);
4090
+ console.log(`Remaining dead symbols: ${deadSymbols.length}
4091
+ `);
4092
+ }
4093
+ return { symbols: deadSymbols, stats };
4094
+ }
4095
+ function isRelevantForDeadCodeDetection(attrs) {
4096
+ const kind = attrs.kind;
4097
+ const relevantKinds = [
4098
+ "function",
4099
+ "class",
4100
+ "interface",
4101
+ "type",
4102
+ "enum",
4103
+ "const",
4104
+ "let",
4105
+ "var",
4106
+ "method",
4107
+ "property"
4108
+ ];
4109
+ if (!relevantKinds.includes(kind)) {
4110
+ return false;
4111
+ }
4112
+ if (kind === "const" || kind === "let" || kind === "var" || kind === "variable") {
4113
+ return attrs.exported === true;
4114
+ }
4115
+ return true;
4116
+ }
4117
+ function getPackageEntryPoints(projectRoot) {
4118
+ const entryPoints = /* @__PURE__ */ new Set();
4119
+ const packageJsonPath = path2.join(projectRoot, "package.json");
4120
+ if (!existsSync9(packageJsonPath)) {
4121
+ return entryPoints;
4122
+ }
4123
+ try {
4124
+ const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
4125
+ if (packageJson.main) {
4126
+ entryPoints.add(path2.resolve(projectRoot, packageJson.main));
4127
+ }
4128
+ if (packageJson.module) {
4129
+ entryPoints.add(path2.resolve(projectRoot, packageJson.module));
4130
+ }
4131
+ if (packageJson.exports) {
4132
+ const addExports = (exp) => {
4133
+ if (typeof exp === "string") {
4134
+ entryPoints.add(path2.resolve(projectRoot, exp));
4135
+ } else if (typeof exp === "object") {
4136
+ for (const key in exp) {
4137
+ if (typeof exp[key] === "string") {
4138
+ entryPoints.add(path2.resolve(projectRoot, exp[key]));
4139
+ } else if (typeof exp[key] === "object") {
4140
+ addExports(exp[key]);
4141
+ }
4142
+ }
4143
+ }
4144
+ };
4145
+ addExports(packageJson.exports);
4146
+ }
4147
+ } catch (e) {
4148
+ }
4149
+ return entryPoints;
3636
4150
  }
3637
- function shouldExclude(attrs, context, includeTests) {
3638
- const filePath = attrs.file;
4151
+ function shouldExclude(attrs, context, includeTests, packageEntryPoints) {
4152
+ const filePath = attrs.file || attrs.filePath;
4153
+ if (!filePath) {
4154
+ return null;
4155
+ }
3639
4156
  const relativePath = path2.relative(context.projectRoot, filePath);
3640
4157
  if (!includeTests && isTestFile(relativePath)) {
3641
- return true;
4158
+ return "test";
3642
4159
  }
3643
- if (isEntryPoint(relativePath)) {
3644
- return true;
4160
+ if (isRealPackageEntryPoint(filePath, packageEntryPoints)) {
4161
+ return "entry";
3645
4162
  }
3646
4163
  if (isConfigFile(relativePath)) {
3647
- return true;
4164
+ return "config";
3648
4165
  }
3649
4166
  if (isTypeDeclarationFile(relativePath)) {
3650
- return true;
4167
+ return "types";
3651
4168
  }
3652
4169
  if (attrs.kind === "default") {
3653
- return true;
4170
+ return "default";
3654
4171
  }
3655
4172
  if (isFrameworkAutoLoadedFile(relativePath)) {
3656
- return true;
4173
+ return "framework";
4174
+ }
4175
+ return null;
4176
+ }
4177
+ function isRealPackageEntryPoint(filePath, packageEntryPoints) {
4178
+ const normalizedPath = path2.normalize(filePath);
4179
+ for (const entryPoint of packageEntryPoints) {
4180
+ const normalizedEntry = path2.normalize(entryPoint);
4181
+ if (normalizedPath === normalizedEntry || normalizedPath === normalizedEntry.replace(/\.(js|ts)$/, ".ts") || normalizedPath === normalizedEntry.replace(/\.(js|ts)$/, ".js")) {
4182
+ return true;
4183
+ }
3657
4184
  }
3658
4185
  return false;
3659
4186
  }
3660
4187
  function isTestFile(filePath) {
3661
4188
  return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
3662
4189
  }
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
4190
  function isConfigFile(filePath) {
3668
4191
  return filePath.includes(".config.") || filePath.includes("config/") || filePath.includes("vite.config") || filePath.includes("rollup.config") || filePath.includes("webpack.config");
3669
4192
  }
@@ -3874,9 +4397,15 @@ function analyzeDeadCode(graph, projectRoot, options = {}) {
3874
4397
  includeTests: options.includeTests || false,
3875
4398
  verbose: options.verbose || false,
3876
4399
  stats: options.stats || false,
3877
- json: options.json || false
4400
+ json: options.json || false,
4401
+ debug: options.debug || false
3878
4402
  };
3879
- const rawDeadSymbols = findDeadSymbols(graph, projectRoot, opts.includeTests);
4403
+ const { symbols: rawDeadSymbols } = findDeadSymbols(
4404
+ graph,
4405
+ projectRoot,
4406
+ opts.includeTests,
4407
+ opts.debug
4408
+ );
3880
4409
  const classifiedSymbols = classifyDeadSymbols(rawDeadSymbols, graph);
3881
4410
  const filteredSymbols = filterByConfidence(classifiedSymbols, opts.confidence);
3882
4411
  const totalSymbols = graph.order;
@@ -3906,11 +4435,11 @@ function filterByConfidence(symbols, minConfidence) {
3906
4435
  }
3907
4436
 
3908
4437
  // src/docs/generator.ts
3909
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync10 } from "fs";
3910
- import { join as join13 } from "path";
4438
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
4439
+ import { join as join14 } from "path";
3911
4440
 
3912
4441
  // src/docs/architecture.ts
3913
- import { dirname as dirname9 } from "path";
4442
+ import { dirname as dirname10 } from "path";
3914
4443
 
3915
4444
  // src/docs/templates.ts
3916
4445
  function header(text, level = 1) {
@@ -4061,7 +4590,7 @@ function generateModuleStructure(graph) {
4061
4590
  function getDirectoryStats(graph) {
4062
4591
  const dirMap = /* @__PURE__ */ new Map();
4063
4592
  graph.forEachNode((node, attrs) => {
4064
- const dir = dirname9(attrs.filePath);
4593
+ const dir = dirname10(attrs.filePath);
4065
4594
  if (dir === ".") return;
4066
4595
  if (!dirMap.has(dir)) {
4067
4596
  dirMap.set(dir, {
@@ -4086,7 +4615,7 @@ function getDirectoryStats(graph) {
4086
4615
  });
4087
4616
  const filesPerDir = /* @__PURE__ */ new Map();
4088
4617
  graph.forEachNode((node, attrs) => {
4089
- const dir = dirname9(attrs.filePath);
4618
+ const dir = dirname10(attrs.filePath);
4090
4619
  if (!filesPerDir.has(dir)) {
4091
4620
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
4092
4621
  }
@@ -4101,8 +4630,8 @@ function getDirectoryStats(graph) {
4101
4630
  graph.forEachEdge((edge, attrs, source, target) => {
4102
4631
  const sourceAttrs = graph.getNodeAttributes(source);
4103
4632
  const targetAttrs = graph.getNodeAttributes(target);
4104
- const sourceDir = dirname9(sourceAttrs.filePath);
4105
- const targetDir = dirname9(targetAttrs.filePath);
4633
+ const sourceDir = dirname10(sourceAttrs.filePath);
4634
+ const targetDir = dirname10(targetAttrs.filePath);
4106
4635
  if (sourceDir !== targetDir) {
4107
4636
  if (!dirEdges.has(sourceDir)) {
4108
4637
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -5083,7 +5612,7 @@ function detectCyclesDetailed(graph) {
5083
5612
  }
5084
5613
 
5085
5614
  // src/docs/onboarding.ts
5086
- import { dirname as dirname10 } from "path";
5615
+ import { dirname as dirname11 } from "path";
5087
5616
  function generateOnboarding(graph, projectRoot, version) {
5088
5617
  let output = "";
5089
5618
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -5142,7 +5671,7 @@ function generateQuickOrientation(graph) {
5142
5671
  const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
5143
5672
  const dirs = /* @__PURE__ */ new Set();
5144
5673
  graph.forEachNode((node, attrs) => {
5145
- const dir = dirname10(attrs.filePath);
5674
+ const dir = dirname11(attrs.filePath);
5146
5675
  if (dir !== ".") {
5147
5676
  const topLevel = dir.split("/")[0];
5148
5677
  dirs.add(topLevel);
@@ -5246,7 +5775,7 @@ function generateModuleMap(graph) {
5246
5775
  function getDirectoryStats2(graph) {
5247
5776
  const dirMap = /* @__PURE__ */ new Map();
5248
5777
  graph.forEachNode((node, attrs) => {
5249
- const dir = dirname10(attrs.filePath);
5778
+ const dir = dirname11(attrs.filePath);
5250
5779
  if (dir === ".") return;
5251
5780
  if (!dirMap.has(dir)) {
5252
5781
  dirMap.set(dir, {
@@ -5261,7 +5790,7 @@ function getDirectoryStats2(graph) {
5261
5790
  });
5262
5791
  const filesPerDir = /* @__PURE__ */ new Map();
5263
5792
  graph.forEachNode((node, attrs) => {
5264
- const dir = dirname10(attrs.filePath);
5793
+ const dir = dirname11(attrs.filePath);
5265
5794
  if (!filesPerDir.has(dir)) {
5266
5795
  filesPerDir.set(dir, /* @__PURE__ */ new Set());
5267
5796
  }
@@ -5276,8 +5805,8 @@ function getDirectoryStats2(graph) {
5276
5805
  graph.forEachEdge((edge, attrs, source, target) => {
5277
5806
  const sourceAttrs = graph.getNodeAttributes(source);
5278
5807
  const targetAttrs = graph.getNodeAttributes(target);
5279
- const sourceDir = dirname10(sourceAttrs.filePath);
5280
- const targetDir = dirname10(targetAttrs.filePath);
5808
+ const sourceDir = dirname11(sourceAttrs.filePath);
5809
+ const targetDir = dirname11(targetAttrs.filePath);
5281
5810
  if (sourceDir !== targetDir) {
5282
5811
  if (!dirEdges.has(sourceDir)) {
5283
5812
  dirEdges.set(sourceDir, { in: 0, out: 0 });
@@ -5358,7 +5887,7 @@ function detectClusters(graph) {
5358
5887
  const dirFiles = /* @__PURE__ */ new Map();
5359
5888
  const fileEdges = /* @__PURE__ */ new Map();
5360
5889
  graph.forEachNode((node, attrs) => {
5361
- const dir = dirname10(attrs.filePath);
5890
+ const dir = dirname11(attrs.filePath);
5362
5891
  if (!dirFiles.has(dir)) {
5363
5892
  dirFiles.set(dir, /* @__PURE__ */ new Set());
5364
5893
  }
@@ -5412,8 +5941,8 @@ function inferClusterName(files) {
5412
5941
  if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
5413
5942
  return capitalizeFirst2(sortedWords[0][0]);
5414
5943
  }
5415
- const commonDir = dirname10(files[0]);
5416
- if (files.every((f) => dirname10(f) === commonDir)) {
5944
+ const commonDir = dirname11(files[0]);
5945
+ if (files.every((f) => dirname11(f) === commonDir)) {
5417
5946
  return capitalizeFirst2(commonDir.split("/").pop() || "Core");
5418
5947
  }
5419
5948
  return "Core";
@@ -5471,7 +6000,7 @@ function generateDepwireUsage(projectRoot) {
5471
6000
  }
5472
6001
 
5473
6002
  // src/docs/files.ts
5474
- import { dirname as dirname11, basename as basename3 } from "path";
6003
+ import { dirname as dirname12, basename as basename3 } from "path";
5475
6004
  function generateFiles(graph, projectRoot, version) {
5476
6005
  let output = "";
5477
6006
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -5575,7 +6104,7 @@ function generateDirectoryBreakdown(graph) {
5575
6104
  const fileStats = getFileStats2(graph);
5576
6105
  const dirMap = /* @__PURE__ */ new Map();
5577
6106
  for (const file of fileStats) {
5578
- const dir = dirname11(file.filePath);
6107
+ const dir = dirname12(file.filePath);
5579
6108
  const topDir = dir === "." ? "." : dir.split("/")[0];
5580
6109
  if (!dirMap.has(topDir)) {
5581
6110
  dirMap.set(topDir, {
@@ -6218,7 +6747,7 @@ function generateRecommendations(graph) {
6218
6747
  }
6219
6748
 
6220
6749
  // src/docs/tests.ts
6221
- import { basename as basename4, dirname as dirname12 } from "path";
6750
+ import { basename as basename4, dirname as dirname13 } from "path";
6222
6751
  function generateTests(graph, projectRoot, version) {
6223
6752
  let output = "";
6224
6753
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -6247,7 +6776,7 @@ function getFileCount8(graph) {
6247
6776
  }
6248
6777
  function isTestFile3(filePath) {
6249
6778
  const fileName = basename4(filePath).toLowerCase();
6250
- const dirPath = dirname12(filePath).toLowerCase();
6779
+ const dirPath = dirname13(filePath).toLowerCase();
6251
6780
  if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
6252
6781
  return true;
6253
6782
  }
@@ -6306,12 +6835,12 @@ function generateTestFileInventory(graph) {
6306
6835
  }
6307
6836
  function matchTestToSource(testFile) {
6308
6837
  const testFileName = basename4(testFile);
6309
- const testDir = dirname12(testFile);
6838
+ const testDir = dirname13(testFile);
6310
6839
  let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
6311
6840
  const possiblePaths = [];
6312
6841
  possiblePaths.push(testDir + "/" + sourceFileName);
6313
6842
  if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
6314
- const parentDir = dirname12(testDir);
6843
+ const parentDir = dirname13(testDir);
6315
6844
  possiblePaths.push(parentDir + "/" + sourceFileName);
6316
6845
  }
6317
6846
  if (testDir.includes("test")) {
@@ -6508,7 +7037,7 @@ function generateTestStatistics(graph) {
6508
7037
  `;
6509
7038
  const dirTestCoverage = /* @__PURE__ */ new Map();
6510
7039
  for (const sourceFile of sourceFiles) {
6511
- const dir = dirname12(sourceFile).split("/")[0];
7040
+ const dir = dirname13(sourceFile).split("/")[0];
6512
7041
  if (!dirTestCoverage.has(dir)) {
6513
7042
  dirTestCoverage.set(dir, { total: 0, tested: 0 });
6514
7043
  }
@@ -6531,7 +7060,7 @@ function generateTestStatistics(graph) {
6531
7060
  }
6532
7061
 
6533
7062
  // src/docs/history.ts
6534
- import { dirname as dirname13 } from "path";
7063
+ import { dirname as dirname14 } from "path";
6535
7064
  import { execSync } from "child_process";
6536
7065
  function generateHistory(graph, projectRoot, version) {
6537
7066
  let output = "";
@@ -6812,7 +7341,7 @@ function generateFeatureClusters(graph) {
6812
7341
  const dirFiles = /* @__PURE__ */ new Map();
6813
7342
  const fileEdges = /* @__PURE__ */ new Map();
6814
7343
  graph.forEachNode((node, attrs) => {
6815
- const dir = dirname13(attrs.filePath);
7344
+ const dir = dirname14(attrs.filePath);
6816
7345
  if (!dirFiles.has(dir)) {
6817
7346
  dirFiles.set(dir, /* @__PURE__ */ new Set());
6818
7347
  }
@@ -6894,7 +7423,7 @@ function capitalizeFirst3(str) {
6894
7423
  }
6895
7424
 
6896
7425
  // src/docs/current.ts
6897
- import { dirname as dirname14 } from "path";
7426
+ import { dirname as dirname15 } from "path";
6898
7427
  function generateCurrent(graph, projectRoot, version) {
6899
7428
  let output = "";
6900
7429
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7032,7 +7561,7 @@ function generateCompleteFileIndex(graph) {
7032
7561
  fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
7033
7562
  const dirGroups = /* @__PURE__ */ new Map();
7034
7563
  for (const info of fileInfos) {
7035
- const dir = dirname14(info.filePath);
7564
+ const dir = dirname15(info.filePath);
7036
7565
  const topDir = dir === "." ? "root" : dir.split("/")[0];
7037
7566
  if (!dirGroups.has(topDir)) {
7038
7567
  dirGroups.set(topDir, []);
@@ -7243,8 +7772,8 @@ function getTopLevelDir2(filePath) {
7243
7772
  }
7244
7773
 
7245
7774
  // src/docs/status.ts
7246
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
7247
- import { join as join11 } from "path";
7775
+ import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
7776
+ import { join as join12 } from "path";
7248
7777
  function generateStatus(graph, projectRoot, version) {
7249
7778
  let output = "";
7250
7779
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7277,12 +7806,12 @@ function getFileCount11(graph) {
7277
7806
  }
7278
7807
  function extractComments(projectRoot, filePath) {
7279
7808
  const comments = [];
7280
- const fullPath = join11(projectRoot, filePath);
7281
- if (!existsSync8(fullPath)) {
7809
+ const fullPath = join12(projectRoot, filePath);
7810
+ if (!existsSync10(fullPath)) {
7282
7811
  return comments;
7283
7812
  }
7284
7813
  try {
7285
- const content = readFileSync6(fullPath, "utf-8");
7814
+ const content = readFileSync8(fullPath, "utf-8");
7286
7815
  const lines = content.split("\n");
7287
7816
  const patterns = [
7288
7817
  { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
@@ -7844,15 +8373,15 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
7844
8373
  }
7845
8374
 
7846
8375
  // src/docs/metadata.ts
7847
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
7848
- import { join as join12 } from "path";
8376
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "fs";
8377
+ import { join as join13 } from "path";
7849
8378
  function loadMetadata(outputDir) {
7850
- const metadataPath = join12(outputDir, "metadata.json");
7851
- if (!existsSync9(metadataPath)) {
8379
+ const metadataPath = join13(outputDir, "metadata.json");
8380
+ if (!existsSync11(metadataPath)) {
7852
8381
  return null;
7853
8382
  }
7854
8383
  try {
7855
- const content = readFileSync7(metadataPath, "utf-8");
8384
+ const content = readFileSync9(metadataPath, "utf-8");
7856
8385
  return JSON.parse(content);
7857
8386
  } catch (err) {
7858
8387
  console.error("Failed to load metadata:", err);
@@ -7860,7 +8389,7 @@ function loadMetadata(outputDir) {
7860
8389
  }
7861
8390
  }
7862
8391
  function saveMetadata(outputDir, metadata) {
7863
- const metadataPath = join12(outputDir, "metadata.json");
8392
+ const metadataPath = join13(outputDir, "metadata.json");
7864
8393
  writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
7865
8394
  }
7866
8395
  function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
@@ -7903,7 +8432,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7903
8432
  const generated = [];
7904
8433
  const errors = [];
7905
8434
  try {
7906
- if (!existsSync10(options.outputDir)) {
8435
+ if (!existsSync12(options.outputDir)) {
7907
8436
  mkdirSync2(options.outputDir, { recursive: true });
7908
8437
  if (options.verbose) {
7909
8438
  console.log(`Created output directory: ${options.outputDir}`);
@@ -7942,7 +8471,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7942
8471
  try {
7943
8472
  if (options.verbose) console.log("Generating ARCHITECTURE.md...");
7944
8473
  const content = generateArchitecture(graph, projectRoot, version, parseTime);
7945
- const filePath = join13(options.outputDir, "ARCHITECTURE.md");
8474
+ const filePath = join14(options.outputDir, "ARCHITECTURE.md");
7946
8475
  writeFileSync3(filePath, content, "utf-8");
7947
8476
  generated.push("ARCHITECTURE.md");
7948
8477
  } catch (err) {
@@ -7953,7 +8482,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7953
8482
  try {
7954
8483
  if (options.verbose) console.log("Generating CONVENTIONS.md...");
7955
8484
  const content = generateConventions(graph, projectRoot, version);
7956
- const filePath = join13(options.outputDir, "CONVENTIONS.md");
8485
+ const filePath = join14(options.outputDir, "CONVENTIONS.md");
7957
8486
  writeFileSync3(filePath, content, "utf-8");
7958
8487
  generated.push("CONVENTIONS.md");
7959
8488
  } catch (err) {
@@ -7964,7 +8493,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7964
8493
  try {
7965
8494
  if (options.verbose) console.log("Generating DEPENDENCIES.md...");
7966
8495
  const content = generateDependencies(graph, projectRoot, version);
7967
- const filePath = join13(options.outputDir, "DEPENDENCIES.md");
8496
+ const filePath = join14(options.outputDir, "DEPENDENCIES.md");
7968
8497
  writeFileSync3(filePath, content, "utf-8");
7969
8498
  generated.push("DEPENDENCIES.md");
7970
8499
  } catch (err) {
@@ -7975,7 +8504,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7975
8504
  try {
7976
8505
  if (options.verbose) console.log("Generating ONBOARDING.md...");
7977
8506
  const content = generateOnboarding(graph, projectRoot, version);
7978
- const filePath = join13(options.outputDir, "ONBOARDING.md");
8507
+ const filePath = join14(options.outputDir, "ONBOARDING.md");
7979
8508
  writeFileSync3(filePath, content, "utf-8");
7980
8509
  generated.push("ONBOARDING.md");
7981
8510
  } catch (err) {
@@ -7986,7 +8515,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7986
8515
  try {
7987
8516
  if (options.verbose) console.log("Generating FILES.md...");
7988
8517
  const content = generateFiles(graph, projectRoot, version);
7989
- const filePath = join13(options.outputDir, "FILES.md");
8518
+ const filePath = join14(options.outputDir, "FILES.md");
7990
8519
  writeFileSync3(filePath, content, "utf-8");
7991
8520
  generated.push("FILES.md");
7992
8521
  } catch (err) {
@@ -7997,7 +8526,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
7997
8526
  try {
7998
8527
  if (options.verbose) console.log("Generating API_SURFACE.md...");
7999
8528
  const content = generateApiSurface(graph, projectRoot, version);
8000
- const filePath = join13(options.outputDir, "API_SURFACE.md");
8529
+ const filePath = join14(options.outputDir, "API_SURFACE.md");
8001
8530
  writeFileSync3(filePath, content, "utf-8");
8002
8531
  generated.push("API_SURFACE.md");
8003
8532
  } catch (err) {
@@ -8008,7 +8537,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8008
8537
  try {
8009
8538
  if (options.verbose) console.log("Generating ERRORS.md...");
8010
8539
  const content = generateErrors(graph, projectRoot, version);
8011
- const filePath = join13(options.outputDir, "ERRORS.md");
8540
+ const filePath = join14(options.outputDir, "ERRORS.md");
8012
8541
  writeFileSync3(filePath, content, "utf-8");
8013
8542
  generated.push("ERRORS.md");
8014
8543
  } catch (err) {
@@ -8019,7 +8548,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8019
8548
  try {
8020
8549
  if (options.verbose) console.log("Generating TESTS.md...");
8021
8550
  const content = generateTests(graph, projectRoot, version);
8022
- const filePath = join13(options.outputDir, "TESTS.md");
8551
+ const filePath = join14(options.outputDir, "TESTS.md");
8023
8552
  writeFileSync3(filePath, content, "utf-8");
8024
8553
  generated.push("TESTS.md");
8025
8554
  } catch (err) {
@@ -8030,7 +8559,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8030
8559
  try {
8031
8560
  if (options.verbose) console.log("Generating HISTORY.md...");
8032
8561
  const content = generateHistory(graph, projectRoot, version);
8033
- const filePath = join13(options.outputDir, "HISTORY.md");
8562
+ const filePath = join14(options.outputDir, "HISTORY.md");
8034
8563
  writeFileSync3(filePath, content, "utf-8");
8035
8564
  generated.push("HISTORY.md");
8036
8565
  } catch (err) {
@@ -8041,7 +8570,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8041
8570
  try {
8042
8571
  if (options.verbose) console.log("Generating CURRENT.md...");
8043
8572
  const content = generateCurrent(graph, projectRoot, version);
8044
- const filePath = join13(options.outputDir, "CURRENT.md");
8573
+ const filePath = join14(options.outputDir, "CURRENT.md");
8045
8574
  writeFileSync3(filePath, content, "utf-8");
8046
8575
  generated.push("CURRENT.md");
8047
8576
  } catch (err) {
@@ -8052,7 +8581,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8052
8581
  try {
8053
8582
  if (options.verbose) console.log("Generating STATUS.md...");
8054
8583
  const content = generateStatus(graph, projectRoot, version);
8055
- const filePath = join13(options.outputDir, "STATUS.md");
8584
+ const filePath = join14(options.outputDir, "STATUS.md");
8056
8585
  writeFileSync3(filePath, content, "utf-8");
8057
8586
  generated.push("STATUS.md");
8058
8587
  } catch (err) {
@@ -8063,7 +8592,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8063
8592
  try {
8064
8593
  if (options.verbose) console.log("Generating HEALTH.md...");
8065
8594
  const content = generateHealth(graph, projectRoot, version);
8066
- const filePath = join13(options.outputDir, "HEALTH.md");
8595
+ const filePath = join14(options.outputDir, "HEALTH.md");
8067
8596
  writeFileSync3(filePath, content, "utf-8");
8068
8597
  generated.push("HEALTH.md");
8069
8598
  } catch (err) {
@@ -8074,7 +8603,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
8074
8603
  try {
8075
8604
  if (options.verbose) console.log("Generating DEAD_CODE.md...");
8076
8605
  const content = generateDeadCode(graph, projectRoot, version);
8077
- const filePath = join13(options.outputDir, "DEAD_CODE.md");
8606
+ const filePath = join14(options.outputDir, "DEAD_CODE.md");
8078
8607
  writeFileSync3(filePath, content, "utf-8");
8079
8608
  generated.push("DEAD_CODE.md");
8080
8609
  } catch (err) {
@@ -8122,13 +8651,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8122
8651
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8123
8652
 
8124
8653
  // src/mcp/tools.ts
8125
- import { dirname as dirname15, join as join16 } from "path";
8126
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
8654
+ import { dirname as dirname16, join as join17 } from "path";
8655
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
8127
8656
 
8128
8657
  // src/mcp/connect.ts
8129
8658
  import simpleGit from "simple-git";
8130
- import { existsSync as existsSync11 } from "fs";
8131
- import { join as join14, basename as basename5, resolve as resolve2 } from "path";
8659
+ import { existsSync as existsSync13 } from "fs";
8660
+ import { join as join15, basename as basename5, resolve as resolve2 } from "path";
8132
8661
  import { tmpdir, homedir } from "os";
8133
8662
  function validateProjectPath(source) {
8134
8663
  const resolved = resolve2(source);
@@ -8141,11 +8670,11 @@ function validateProjectPath(source) {
8141
8670
  "/boot",
8142
8671
  "/proc",
8143
8672
  "/sys",
8144
- join14(homedir(), ".ssh"),
8145
- join14(homedir(), ".gnupg"),
8146
- join14(homedir(), ".aws"),
8147
- join14(homedir(), ".config"),
8148
- join14(homedir(), ".env")
8673
+ join15(homedir(), ".ssh"),
8674
+ join15(homedir(), ".gnupg"),
8675
+ join15(homedir(), ".aws"),
8676
+ join15(homedir(), ".config"),
8677
+ join15(homedir(), ".env")
8149
8678
  ];
8150
8679
  for (const blocked of blockedPaths) {
8151
8680
  if (resolved.startsWith(blocked)) {
@@ -8168,11 +8697,11 @@ async function connectToRepo(source, subdirectory, state) {
8168
8697
  };
8169
8698
  }
8170
8699
  projectName = match[1];
8171
- const reposDir = join14(tmpdir(), "depwire-repos");
8172
- const cloneDir = join14(reposDir, projectName);
8700
+ const reposDir = join15(tmpdir(), "depwire-repos");
8701
+ const cloneDir = join15(reposDir, projectName);
8173
8702
  console.error(`Connecting to GitHub repo: ${source}`);
8174
8703
  const git = simpleGit();
8175
- if (existsSync11(cloneDir)) {
8704
+ if (existsSync13(cloneDir)) {
8176
8705
  console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
8177
8706
  try {
8178
8707
  await git.cwd(cloneDir).pull();
@@ -8190,7 +8719,7 @@ async function connectToRepo(source, subdirectory, state) {
8190
8719
  };
8191
8720
  }
8192
8721
  }
8193
- projectRoot = subdirectory ? join14(cloneDir, subdirectory) : cloneDir;
8722
+ projectRoot = subdirectory ? join15(cloneDir, subdirectory) : cloneDir;
8194
8723
  } else {
8195
8724
  const validation2 = validateProjectPath(source);
8196
8725
  if (!validation2.valid) {
@@ -8199,13 +8728,13 @@ async function connectToRepo(source, subdirectory, state) {
8199
8728
  message: validation2.error
8200
8729
  };
8201
8730
  }
8202
- if (!existsSync11(source)) {
8731
+ if (!existsSync13(source)) {
8203
8732
  return {
8204
8733
  error: "Directory not found",
8205
8734
  message: `Directory does not exist: ${source}`
8206
8735
  };
8207
8736
  }
8208
- projectRoot = subdirectory ? join14(source, subdirectory) : source;
8737
+ projectRoot = subdirectory ? join15(source, subdirectory) : source;
8209
8738
  projectName = basename5(projectRoot);
8210
8739
  }
8211
8740
  const validation = validateProjectPath(projectRoot);
@@ -8215,7 +8744,7 @@ async function connectToRepo(source, subdirectory, state) {
8215
8744
  message: validation.error
8216
8745
  };
8217
8746
  }
8218
- if (!existsSync11(projectRoot)) {
8747
+ if (!existsSync13(projectRoot)) {
8219
8748
  return {
8220
8749
  error: "Project root not found",
8221
8750
  message: `Directory does not exist: ${projectRoot}`
@@ -8496,24 +9025,24 @@ function getWeekNumber(date) {
8496
9025
  }
8497
9026
 
8498
9027
  // src/temporal/snapshots.ts
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";
9028
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync10, mkdirSync as mkdirSync3, existsSync as existsSync14, readdirSync as readdirSync5 } from "fs";
9029
+ import { join as join16 } from "path";
8501
9030
  function saveSnapshot(snapshot, outputDir) {
8502
- if (!existsSync12(outputDir)) {
9031
+ if (!existsSync14(outputDir)) {
8503
9032
  mkdirSync3(outputDir, { recursive: true });
8504
9033
  }
8505
9034
  const filename = `${snapshot.commitHash.substring(0, 8)}.json`;
8506
- const filepath = join15(outputDir, filename);
9035
+ const filepath = join16(outputDir, filename);
8507
9036
  writeFileSync4(filepath, JSON.stringify(snapshot, null, 2), "utf-8");
8508
9037
  }
8509
9038
  function loadSnapshot(commitHash, outputDir) {
8510
9039
  const shortHash = commitHash.substring(0, 8);
8511
- const filepath = join15(outputDir, `${shortHash}.json`);
8512
- if (!existsSync12(filepath)) {
9040
+ const filepath = join16(outputDir, `${shortHash}.json`);
9041
+ if (!existsSync14(filepath)) {
8513
9042
  return null;
8514
9043
  }
8515
9044
  try {
8516
- const content = readFileSync8(filepath, "utf-8");
9045
+ const content = readFileSync10(filepath, "utf-8");
8517
9046
  return JSON.parse(content);
8518
9047
  } catch {
8519
9048
  return null;
@@ -9200,7 +9729,7 @@ function handleGetArchitectureSummary(graph) {
9200
9729
  const dirMap = /* @__PURE__ */ new Map();
9201
9730
  const languageBreakdown = {};
9202
9731
  fileSummary.forEach((f) => {
9203
- const dir = f.filePath.includes("/") ? dirname15(f.filePath) : ".";
9732
+ const dir = f.filePath.includes("/") ? dirname16(f.filePath) : ".";
9204
9733
  if (!dirMap.has(dir)) {
9205
9734
  dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
9206
9735
  }
@@ -9287,8 +9816,8 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
9287
9816
  };
9288
9817
  }
9289
9818
  async function handleGetProjectDocs(docType, state) {
9290
- const docsDir = join16(state.projectRoot, ".depwire");
9291
- if (!existsSync13(docsDir)) {
9819
+ const docsDir = join17(state.projectRoot, ".depwire");
9820
+ if (!existsSync15(docsDir)) {
9292
9821
  const errorMessage = `Project documentation has not been generated yet.
9293
9822
 
9294
9823
  Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
@@ -9318,12 +9847,12 @@ Available document types:
9318
9847
  missing.push(doc);
9319
9848
  continue;
9320
9849
  }
9321
- const filePath = join16(docsDir, metadata.documents[doc].file);
9322
- if (!existsSync13(filePath)) {
9850
+ const filePath = join17(docsDir, metadata.documents[doc].file);
9851
+ if (!existsSync15(filePath)) {
9323
9852
  missing.push(doc);
9324
9853
  continue;
9325
9854
  }
9326
- const content = readFileSync9(filePath, "utf-8");
9855
+ const content = readFileSync11(filePath, "utf-8");
9327
9856
  if (docsToReturn.length > 1) {
9328
9857
  output += `
9329
9858
 
@@ -9348,16 +9877,16 @@ Available document types:
9348
9877
  }
9349
9878
  async function handleUpdateProjectDocs(docType, state) {
9350
9879
  const startTime = Date.now();
9351
- const docsDir = join16(state.projectRoot, ".depwire");
9880
+ const docsDir = join17(state.projectRoot, ".depwire");
9352
9881
  console.error("Regenerating project documentation...");
9353
9882
  const parsedFiles = await parseProject(state.projectRoot);
9354
9883
  const graph = buildGraph(parsedFiles);
9355
9884
  const parseTime = (Date.now() - startTime) / 1e3;
9356
9885
  state.graph = graph;
9357
- const packageJsonPath = join16(__dirname, "../../package.json");
9358
- const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
9886
+ const packageJsonPath = join17(__dirname, "../../package.json");
9887
+ const packageJson = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
9359
9888
  const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
9360
- const docsExist = existsSync13(docsDir);
9889
+ const docsExist = existsSync15(docsDir);
9361
9890
  const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
9362
9891
  outputDir: docsDir,
9363
9892
  format: "markdown",
@@ -9416,7 +9945,7 @@ async function handleGetTemporalGraph(state, commits, strategy) {
9416
9945
  }
9417
9946
  const sampledCommits = sampleCommits(allCommits, commits, strategy);
9418
9947
  const snapshots = [];
9419
- const outputDir = join16(projectRoot, ".depwire", "temporal");
9948
+ const outputDir = join17(projectRoot, ".depwire", "temporal");
9420
9949
  for (const commit of sampledCommits) {
9421
9950
  const existing = loadSnapshot(commit.hash, outputDir);
9422
9951
  if (existing) {