depwire-cli 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,9 +54,50 @@ function fileExists(filePath) {
54
54
  // src/parser/detect.ts
55
55
  import { extname as extname3 } from "path";
56
56
 
57
- // src/parser/typescript.ts
58
- import Parser from "tree-sitter";
59
- import TypeScript from "tree-sitter-typescript";
57
+ // src/parser/wasm-init.ts
58
+ import { Parser, Language } from "web-tree-sitter";
59
+ import path from "path";
60
+ import { fileURLToPath } from "url";
61
+ import { existsSync as existsSync2 } from "fs";
62
+ var initialized = false;
63
+ var languages = /* @__PURE__ */ new Map();
64
+ async function initParser() {
65
+ if (initialized) return;
66
+ await Parser.init();
67
+ const __dirname3 = path.dirname(fileURLToPath(import.meta.url));
68
+ let grammarsDir = path.join(__dirname3, "parser", "grammars");
69
+ if (!existsSync2(grammarsDir)) {
70
+ grammarsDir = path.join(path.dirname(__dirname3), "parser", "grammars");
71
+ }
72
+ if (!existsSync2(grammarsDir)) {
73
+ grammarsDir = path.join(__dirname3, "grammars");
74
+ }
75
+ const grammarFiles = {
76
+ "typescript": "tree-sitter-typescript.wasm",
77
+ "tsx": "tree-sitter-tsx.wasm",
78
+ "javascript": "tree-sitter-javascript.wasm",
79
+ "python": "tree-sitter-python.wasm",
80
+ "go": "tree-sitter-go.wasm"
81
+ };
82
+ for (const [name, file] of Object.entries(grammarFiles)) {
83
+ const wasmPath = path.join(grammarsDir, file);
84
+ const lang = await Language.load(wasmPath);
85
+ languages.set(name, lang);
86
+ }
87
+ initialized = true;
88
+ }
89
+ function getParser(language) {
90
+ if (!initialized) {
91
+ throw new Error("Parser not initialized. Call initParser() first.");
92
+ }
93
+ const lang = languages.get(language);
94
+ if (!lang) {
95
+ throw new Error(`Language '${language}' not loaded.`);
96
+ }
97
+ const parser = new Parser();
98
+ parser.setLanguage(lang);
99
+ return parser;
100
+ }
60
101
 
61
102
  // src/parser/resolver.ts
62
103
  import { join as join2, dirname, resolve, relative as relative2 } from "path";
@@ -152,13 +193,10 @@ function resolveImportPath(importPath, fromFile, projectRoot) {
152
193
  }
153
194
 
154
195
  // src/parser/typescript.ts
155
- var tsParser = new Parser();
156
- tsParser.setLanguage(TypeScript.typescript);
157
- var tsxParser = new Parser();
158
- tsxParser.setLanguage(TypeScript.tsx);
159
196
  function parseTypeScriptFile(filePath, sourceCode, projectRoot) {
160
- const parser2 = filePath.endsWith(".tsx") ? tsxParser : tsParser;
161
- const tree = parser2.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
197
+ const languageType = filePath.endsWith(".tsx") ? "tsx" : "typescript";
198
+ const parser = getParser(languageType);
199
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
162
200
  const context = {
163
201
  filePath,
164
202
  projectRoot,
@@ -613,14 +651,11 @@ var typescriptParser = {
613
651
  };
614
652
 
615
653
  // src/parser/python.ts
616
- import Parser2 from "tree-sitter";
617
- import Python from "tree-sitter-python";
618
654
  import { dirname as dirname2, join as join3 } from "path";
619
- import { existsSync as existsSync2 } from "fs";
620
- var pyParser = new Parser2();
621
- pyParser.setLanguage(Python);
655
+ import { existsSync as existsSync3 } from "fs";
622
656
  function parsePythonFile(filePath, sourceCode, projectRoot) {
623
- const tree = pyParser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
657
+ const parser = getParser("python");
658
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
624
659
  const context = {
625
660
  filePath,
626
661
  projectRoot,
@@ -907,13 +942,13 @@ function resolveImportPath2(moduleName, currentFile, projectRoot) {
907
942
  join3(targetDir, modulePath2, "__init__.py")
908
943
  ];
909
944
  for (const candidate of candidates2) {
910
- if (existsSync2(candidate)) {
945
+ if (existsSync3(candidate)) {
911
946
  return candidate.substring(projectRoot.length + 1);
912
947
  }
913
948
  }
914
949
  } else {
915
950
  const initPath = join3(targetDir, "__init__.py");
916
- if (existsSync2(initPath)) {
951
+ if (existsSync3(initPath)) {
917
952
  return initPath.substring(projectRoot.length + 1);
918
953
  }
919
954
  }
@@ -925,7 +960,7 @@ function resolveImportPath2(moduleName, currentFile, projectRoot) {
925
960
  join3(projectRoot, modulePath, "__init__.py")
926
961
  ];
927
962
  for (const candidate of candidates) {
928
- if (existsSync2(candidate)) {
963
+ if (existsSync3(candidate)) {
929
964
  return candidate.substring(projectRoot.length + 1);
930
965
  }
931
966
  }
@@ -982,14 +1017,11 @@ var pythonParser = {
982
1017
  };
983
1018
 
984
1019
  // src/parser/javascript.ts
985
- import Parser3 from "tree-sitter";
986
- import JavaScript from "tree-sitter-javascript";
987
- import { existsSync as existsSync3 } from "fs";
1020
+ import { existsSync as existsSync4 } from "fs";
988
1021
  import { join as join4, dirname as dirname3, extname as extname2 } from "path";
989
- var jsParser = new Parser3();
990
- jsParser.setLanguage(JavaScript);
991
1022
  function parseJavaScriptFile(filePath, sourceCode, projectRoot) {
992
- const tree = jsParser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
1023
+ const parser = getParser("javascript");
1024
+ const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
993
1025
  const context = {
994
1026
  filePath,
995
1027
  projectRoot,
@@ -1402,20 +1434,20 @@ function resolveJavaScriptImport(importPath, currentFile, projectRoot) {
1402
1434
  const indexFiles = ["index.js", "index.jsx", "index.mjs"];
1403
1435
  if (extname2(importPath)) {
1404
1436
  const fullPath = targetPath;
1405
- if (existsSync3(fullPath)) {
1437
+ if (existsSync4(fullPath)) {
1406
1438
  return fullPath.substring(projectRoot.length + 1);
1407
1439
  }
1408
1440
  return null;
1409
1441
  }
1410
1442
  for (const ext of extensions) {
1411
1443
  const candidate = `${targetPath}${ext}`;
1412
- if (existsSync3(candidate)) {
1444
+ if (existsSync4(candidate)) {
1413
1445
  return candidate.substring(projectRoot.length + 1);
1414
1446
  }
1415
1447
  }
1416
1448
  for (const indexFile of indexFiles) {
1417
1449
  const candidate = join4(targetPath, indexFile);
1418
- if (existsSync3(candidate)) {
1450
+ if (existsSync4(candidate)) {
1419
1451
  return candidate.substring(projectRoot.length + 1);
1420
1452
  }
1421
1453
  }
@@ -1486,13 +1518,10 @@ var javascriptParser = {
1486
1518
  };
1487
1519
 
1488
1520
  // src/parser/go.ts
1489
- import Parser4 from "tree-sitter";
1490
- import Go from "tree-sitter-go";
1491
- import { existsSync as existsSync4, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1521
+ import { existsSync as existsSync5, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1492
1522
  import { join as join5, dirname as dirname4 } from "path";
1493
- var parser = new Parser4();
1494
- parser.setLanguage(Go);
1495
1523
  function parseGoFile(filePath, sourceCode, projectRoot) {
1524
+ const parser = getParser("go");
1496
1525
  const tree = parser.parse(sourceCode, null, { bufferSize: 1024 * 1024 });
1497
1526
  const moduleName = readGoModuleName(projectRoot);
1498
1527
  const context = {
@@ -1773,7 +1802,7 @@ function readGoModuleName(projectRoot) {
1773
1802
  let currentDir = projectRoot;
1774
1803
  for (let i = 0; i < 5; i++) {
1775
1804
  const goModPath = join5(currentDir, "go.mod");
1776
- if (existsSync4(goModPath)) {
1805
+ if (existsSync5(goModPath)) {
1777
1806
  try {
1778
1807
  const content = readFileSync2(goModPath, "utf-8");
1779
1808
  const lines = content.split("\n");
@@ -1804,13 +1833,13 @@ function resolveGoImport(importPath, projectRoot, moduleName) {
1804
1833
  }
1805
1834
  const segments = importPath.split("/");
1806
1835
  const packageDir = join5(projectRoot, ...segments);
1807
- if (existsSync4(packageDir)) {
1836
+ if (existsSync5(packageDir)) {
1808
1837
  return findGoFilesInDir(packageDir, projectRoot);
1809
1838
  }
1810
1839
  return [];
1811
1840
  }
1812
1841
  function findGoFilesInDir(dir, projectRoot) {
1813
- if (!existsSync4(dir)) return [];
1842
+ if (!existsSync5(dir)) return [];
1814
1843
  try {
1815
1844
  const files = readdirSync2(dir);
1816
1845
  const goFiles = files.filter((f) => f.endsWith(".go") && !f.endsWith("_test.go"));
@@ -1938,7 +1967,8 @@ function shouldParseFile(fullPath) {
1938
1967
  return false;
1939
1968
  }
1940
1969
  }
1941
- function parseProject(projectRoot, options) {
1970
+ async function parseProject(projectRoot, options) {
1971
+ await initParser();
1942
1972
  const files = scanDirectory(projectRoot);
1943
1973
  const parsedFiles = [];
1944
1974
  let skippedFiles = 0;
@@ -1965,14 +1995,14 @@ function parseProject(projectRoot, options) {
1965
1995
  if (options?.verbose) {
1966
1996
  console.error(`[Parser] Parsing: ${file}`);
1967
1997
  }
1968
- const parser2 = getParserForFile(file);
1969
- if (!parser2) {
1998
+ const parser = getParserForFile(file);
1999
+ if (!parser) {
1970
2000
  console.error(`No parser found for file: ${file}`);
1971
2001
  skippedFiles++;
1972
2002
  continue;
1973
2003
  }
1974
2004
  const sourceCode = readFileSync3(fullPath, "utf-8");
1975
- const parsed = parser2.parseFile(file, sourceCode, projectRoot);
2005
+ const parsed = parser.parseFile(file, sourceCode, projectRoot);
1976
2006
  parsedFiles.push(parsed);
1977
2007
  } catch (err) {
1978
2008
  errorFiles++;
@@ -2397,10 +2427,10 @@ function watchProject(projectRoot, callbacks) {
2397
2427
  // src/viz/server.ts
2398
2428
  import express from "express";
2399
2429
  import open from "open";
2400
- import { fileURLToPath } from "url";
2430
+ import { fileURLToPath as fileURLToPath2 } from "url";
2401
2431
  import { dirname as dirname5, join as join7 } from "path";
2402
2432
  import { WebSocketServer } from "ws";
2403
- var __filename = fileURLToPath(import.meta.url);
2433
+ var __filename = fileURLToPath2(import.meta.url);
2404
2434
  var __dirname2 = dirname5(__filename);
2405
2435
  var activeServer = null;
2406
2436
  async function findAvailablePort(startPort, maxAttempts = 10) {
@@ -2473,7 +2503,7 @@ Depwire visualization running at ${url2}`);
2473
2503
  onFileChanged: async (filePath) => {
2474
2504
  console.error(`File changed: ${filePath} \u2014 re-parsing project...`);
2475
2505
  try {
2476
- const parsedFiles = parseProject(projectRoot, options);
2506
+ const parsedFiles = await parseProject(projectRoot, options);
2477
2507
  const newGraph = buildGraph(parsedFiles);
2478
2508
  graph.clear();
2479
2509
  newGraph.forEachNode((node, attrs) => {
@@ -2492,7 +2522,7 @@ Depwire visualization running at ${url2}`);
2492
2522
  onFileAdded: async (filePath) => {
2493
2523
  console.error(`File added: ${filePath} \u2014 re-parsing project...`);
2494
2524
  try {
2495
- const parsedFiles = parseProject(projectRoot, options);
2525
+ const parsedFiles = await parseProject(projectRoot, options);
2496
2526
  const newGraph = buildGraph(parsedFiles);
2497
2527
  graph.clear();
2498
2528
  newGraph.forEachNode((node, attrs) => {
@@ -2508,10 +2538,10 @@ Depwire visualization running at ${url2}`);
2508
2538
  console.error(`Failed to update graph for ${filePath}:`, error);
2509
2539
  }
2510
2540
  },
2511
- onFileDeleted: (filePath) => {
2541
+ onFileDeleted: async (filePath) => {
2512
2542
  console.error(`File deleted: ${filePath} \u2014 re-parsing project...`);
2513
2543
  try {
2514
- const parsedFiles = parseProject(projectRoot, options);
2544
+ const parsedFiles = await parseProject(projectRoot, options);
2515
2545
  const newGraph = buildGraph(parsedFiles);
2516
2546
  graph.clear();
2517
2547
  newGraph.forEachNode((node, attrs) => {
@@ -2609,8 +2639,8 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2609
2639
  }
2610
2640
 
2611
2641
  // src/docs/generator.ts
2612
- import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync6 } from "fs";
2613
- import { join as join10 } from "path";
2642
+ import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync8 } from "fs";
2643
+ import { join as join11 } from "path";
2614
2644
 
2615
2645
  // src/docs/architecture.ts
2616
2646
  import { dirname as dirname6 } from "path";
@@ -2725,7 +2755,7 @@ function generateProjectSummary(graph, parseTime) {
2725
2755
  const fileCount = getFileCount(graph);
2726
2756
  const symbolCount = graph.order;
2727
2757
  const edgeCount = graph.size;
2728
- const languages = getLanguageStats(graph);
2758
+ const languages2 = getLanguageStats(graph);
2729
2759
  let output = "";
2730
2760
  output += `- **Total Files:** ${formatNumber(fileCount)}
2731
2761
  `;
@@ -2735,10 +2765,10 @@ function generateProjectSummary(graph, parseTime) {
2735
2765
  `;
2736
2766
  output += `- **Parse Time:** ${parseTime.toFixed(1)}s
2737
2767
  `;
2738
- if (Object.keys(languages).length > 1) {
2768
+ if (Object.keys(languages2).length > 1) {
2739
2769
  output += "\n**Languages:**\n\n";
2740
2770
  const totalFiles = fileCount;
2741
- for (const [lang, count] of Object.entries(languages).sort((a, b) => b[1] - a[1])) {
2771
+ for (const [lang, count] of Object.entries(languages2).sort((a, b) => b[1] - a[1])) {
2742
2772
  output += `- ${lang}: ${count} files (${formatPercent(count, totalFiles)})
2743
2773
  `;
2744
2774
  }
@@ -3657,20 +3687,20 @@ function findLongestPaths(graph, limit) {
3657
3687
  }
3658
3688
  const allPaths = [];
3659
3689
  const visited = /* @__PURE__ */ new Set();
3660
- function dfs(file, path) {
3690
+ function dfs(file, path2) {
3661
3691
  visited.add(file);
3662
- path.push(file);
3692
+ path2.push(file);
3663
3693
  const neighbors = fileGraph.get(file);
3664
3694
  if (!neighbors || neighbors.size === 0) {
3665
- allPaths.push([...path]);
3695
+ allPaths.push([...path2]);
3666
3696
  } else {
3667
3697
  for (const neighbor of neighbors) {
3668
3698
  if (!visited.has(neighbor)) {
3669
- dfs(neighbor, path);
3699
+ dfs(neighbor, path2);
3670
3700
  }
3671
3701
  }
3672
3702
  }
3673
- path.pop();
3703
+ path2.pop();
3674
3704
  visited.delete(file);
3675
3705
  }
3676
3706
  for (const root of roots.slice(0, 10)) {
@@ -3841,8 +3871,8 @@ function getLanguageStats2(graph) {
3841
3871
  }
3842
3872
  function generateQuickOrientation(graph) {
3843
3873
  const fileCount = getFileCount4(graph);
3844
- const languages = getLanguageStats2(graph);
3845
- const primaryLang = Object.entries(languages).sort((a, b) => b[1] - a[1])[0];
3874
+ const languages2 = getLanguageStats2(graph);
3875
+ const primaryLang = Object.entries(languages2).sort((a, b) => b[1] - a[1])[0];
3846
3876
  const dirs = /* @__PURE__ */ new Set();
3847
3877
  graph.forEachNode((node, attrs) => {
3848
3878
  const dir = dirname7(attrs.filePath);
@@ -4173,138 +4203,2353 @@ function generateDepwireUsage(projectRoot) {
4173
4203
  return output;
4174
4204
  }
4175
4205
 
4176
- // src/docs/metadata.ts
4177
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync } from "fs";
4178
- import { join as join9 } from "path";
4179
- function loadMetadata(outputDir) {
4180
- const metadataPath = join9(outputDir, "metadata.json");
4181
- if (!existsSync5(metadataPath)) {
4182
- return null;
4206
+ // src/docs/files.ts
4207
+ import { dirname as dirname8, basename as basename3 } from "path";
4208
+ function generateFiles(graph, projectRoot, version) {
4209
+ let output = "";
4210
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4211
+ const fileCount = getFileCount5(graph);
4212
+ output += timestamp(version, now, fileCount, graph.order);
4213
+ output += header("File Catalog");
4214
+ output += "Complete catalog of every file in the project with key metrics.\n\n";
4215
+ output += header("File Summary", 2);
4216
+ output += generateFileSummaryTable(graph);
4217
+ output += header("Directory Breakdown", 2);
4218
+ output += generateDirectoryBreakdown(graph);
4219
+ output += header("File Size Distribution", 2);
4220
+ output += generateFileSizeDistribution(graph);
4221
+ output += header("Orphan Files", 2);
4222
+ output += generateOrphanFiles(graph);
4223
+ output += header("Hub Files", 2);
4224
+ output += generateHubFiles2(graph);
4225
+ return output;
4226
+ }
4227
+ function getFileCount5(graph) {
4228
+ const files = /* @__PURE__ */ new Set();
4229
+ graph.forEachNode((node, attrs) => {
4230
+ files.add(attrs.filePath);
4231
+ });
4232
+ return files.size;
4233
+ }
4234
+ function getFileStats2(graph) {
4235
+ const fileMap = /* @__PURE__ */ new Map();
4236
+ graph.forEachNode((node, attrs) => {
4237
+ if (!fileMap.has(attrs.filePath)) {
4238
+ fileMap.set(attrs.filePath, {
4239
+ filePath: attrs.filePath,
4240
+ language: getLanguageFromPath(attrs.filePath),
4241
+ symbolCount: 0,
4242
+ importCount: 0,
4243
+ exportedSymbolCount: 0,
4244
+ incomingConnections: 0,
4245
+ outgoingConnections: 0,
4246
+ totalConnections: 0,
4247
+ maxLine: 0
4248
+ });
4249
+ }
4250
+ const stats = fileMap.get(attrs.filePath);
4251
+ stats.symbolCount++;
4252
+ if (attrs.exported && attrs.name !== "default") {
4253
+ stats.exportedSymbolCount++;
4254
+ }
4255
+ if (attrs.kind === "import") {
4256
+ stats.importCount++;
4257
+ }
4258
+ if (attrs.endLine > stats.maxLine) {
4259
+ stats.maxLine = attrs.endLine;
4260
+ }
4261
+ });
4262
+ graph.forEachEdge((edge, attrs, source, target) => {
4263
+ const sourceAttrs = graph.getNodeAttributes(source);
4264
+ const targetAttrs = graph.getNodeAttributes(target);
4265
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
4266
+ const sourceStats = fileMap.get(sourceAttrs.filePath);
4267
+ const targetStats = fileMap.get(targetAttrs.filePath);
4268
+ if (sourceStats) {
4269
+ sourceStats.outgoingConnections++;
4270
+ }
4271
+ if (targetStats) {
4272
+ targetStats.incomingConnections++;
4273
+ }
4274
+ }
4275
+ });
4276
+ fileMap.forEach((stats) => {
4277
+ stats.totalConnections = stats.incomingConnections + stats.outgoingConnections;
4278
+ });
4279
+ return Array.from(fileMap.values());
4280
+ }
4281
+ function getLanguageFromPath(filePath) {
4282
+ const ext = filePath.toLowerCase();
4283
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) return "TypeScript";
4284
+ if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) return "JavaScript";
4285
+ if (ext.endsWith(".py")) return "Python";
4286
+ if (ext.endsWith(".go")) return "Go";
4287
+ return "Other";
4288
+ }
4289
+ function generateFileSummaryTable(graph) {
4290
+ const fileStats = getFileStats2(graph);
4291
+ if (fileStats.length === 0) {
4292
+ return "No files detected.\n\n";
4183
4293
  }
4184
- try {
4185
- const content = readFileSync4(metadataPath, "utf-8");
4186
- return JSON.parse(content);
4187
- } catch (err) {
4188
- console.error("Failed to load metadata:", err);
4189
- return null;
4294
+ fileStats.sort((a, b) => a.filePath.localeCompare(b.filePath));
4295
+ const headers = ["File", "Language", "Symbols", "Imports", "Exports", "Connections", "Lines"];
4296
+ const rows = fileStats.map((f) => [
4297
+ `\`${f.filePath}\``,
4298
+ f.language,
4299
+ formatNumber(f.symbolCount),
4300
+ formatNumber(f.importCount),
4301
+ formatNumber(f.exportedSymbolCount),
4302
+ formatNumber(f.totalConnections),
4303
+ formatNumber(f.maxLine)
4304
+ ]);
4305
+ return table(headers, rows);
4306
+ }
4307
+ function generateDirectoryBreakdown(graph) {
4308
+ const fileStats = getFileStats2(graph);
4309
+ const dirMap = /* @__PURE__ */ new Map();
4310
+ for (const file of fileStats) {
4311
+ const dir = dirname8(file.filePath);
4312
+ const topDir = dir === "." ? "." : dir.split("/")[0];
4313
+ if (!dirMap.has(topDir)) {
4314
+ dirMap.set(topDir, {
4315
+ fileCount: 0,
4316
+ symbolCount: 0,
4317
+ mostConnectedFile: "",
4318
+ maxConnections: 0
4319
+ });
4320
+ }
4321
+ const dirStats = dirMap.get(topDir);
4322
+ dirStats.fileCount++;
4323
+ dirStats.symbolCount += file.symbolCount;
4324
+ if (file.totalConnections > dirStats.maxConnections) {
4325
+ dirStats.maxConnections = file.totalConnections;
4326
+ dirStats.mostConnectedFile = basename3(file.filePath);
4327
+ }
4328
+ }
4329
+ if (dirMap.size === 0) {
4330
+ return "No directories detected.\n\n";
4331
+ }
4332
+ let output = "";
4333
+ const sortedDirs = Array.from(dirMap.entries()).sort((a, b) => b[1].fileCount - a[1].fileCount);
4334
+ for (const [dir, stats] of sortedDirs) {
4335
+ output += `**${dir === "." ? "Root" : dir}/**
4336
+
4337
+ `;
4338
+ output += `- **Files:** ${formatNumber(stats.fileCount)}
4339
+ `;
4340
+ output += `- **Symbols:** ${formatNumber(stats.symbolCount)}
4341
+ `;
4342
+ output += `- **Most Connected:** \`${stats.mostConnectedFile}\` (${formatNumber(stats.maxConnections)} connections)
4343
+
4344
+ `;
4190
4345
  }
4346
+ return output;
4191
4347
  }
4192
- function saveMetadata(outputDir, metadata) {
4193
- const metadataPath = join9(outputDir, "metadata.json");
4194
- writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
4348
+ function generateFileSizeDistribution(graph) {
4349
+ const fileStats = getFileStats2(graph);
4350
+ if (fileStats.length === 0) {
4351
+ return "No files detected.\n\n";
4352
+ }
4353
+ const bySymbols = [...fileStats].sort((a, b) => b.symbolCount - a.symbolCount);
4354
+ let output = "";
4355
+ output += "**Largest Files (by symbol count):**\n\n";
4356
+ const largest = bySymbols.slice(0, 10);
4357
+ const headers1 = ["File", "Symbols", "Lines"];
4358
+ const rows1 = largest.map((f) => [
4359
+ `\`${f.filePath}\``,
4360
+ formatNumber(f.symbolCount),
4361
+ formatNumber(f.maxLine)
4362
+ ]);
4363
+ output += table(headers1, rows1);
4364
+ if (bySymbols.length > 10) {
4365
+ output += "**Smallest Files (by symbol count):**\n\n";
4366
+ const smallest = bySymbols.slice(-10).reverse();
4367
+ const headers2 = ["File", "Symbols", "Lines"];
4368
+ const rows2 = smallest.map((f) => [
4369
+ `\`${f.filePath}\``,
4370
+ formatNumber(f.symbolCount),
4371
+ formatNumber(f.maxLine)
4372
+ ]);
4373
+ output += table(headers2, rows2);
4374
+ }
4375
+ const avgSymbols = Math.round(fileStats.reduce((sum, f) => sum + f.symbolCount, 0) / fileStats.length);
4376
+ const avgLines = Math.round(fileStats.reduce((sum, f) => sum + f.maxLine, 0) / fileStats.length);
4377
+ output += `**Average File Size:**
4378
+
4379
+ `;
4380
+ output += `- Symbols per file: ${formatNumber(avgSymbols)}
4381
+ `;
4382
+ output += `- Lines per file: ${formatNumber(avgLines)}
4383
+
4384
+ `;
4385
+ return output;
4195
4386
  }
4196
- function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
4197
- const now = (/* @__PURE__ */ new Date()).toISOString();
4198
- const documents = {};
4199
- for (const docType of docTypes) {
4200
- const fileName = docType === "architecture" ? "ARCHITECTURE.md" : docType === "conventions" ? "CONVENTIONS.md" : docType === "dependencies" ? "DEPENDENCIES.md" : docType === "onboarding" ? "ONBOARDING.md" : `${docType.toUpperCase()}.md`;
4201
- documents[docType] = {
4202
- generated_at: now,
4203
- file: fileName
4204
- };
4387
+ function generateOrphanFiles(graph) {
4388
+ const fileStats = getFileStats2(graph);
4389
+ const orphans = fileStats.filter((f) => f.totalConnections === 0);
4390
+ if (orphans.length === 0) {
4391
+ return "\u2705 No orphan files detected. All files are connected.\n\n";
4205
4392
  }
4206
- return {
4207
- version,
4208
- generated_at: now,
4209
- project_path: projectPath,
4210
- file_count: fileCount,
4211
- symbol_count: symbolCount,
4212
- edge_count: edgeCount,
4213
- documents
4214
- };
4393
+ let output = `Found ${orphans.length} file${orphans.length === 1 ? "" : "s"} with zero connections:
4394
+
4395
+ `;
4396
+ output += unorderedList(orphans.map((f) => `\`${f.filePath}\` (${f.symbolCount} symbols)`));
4397
+ output += "These files may be entry points, standalone scripts, or dead code.\n\n";
4398
+ return output;
4215
4399
  }
4216
- function updateMetadata(existing, docTypes, fileCount, symbolCount, edgeCount) {
4217
- const now = (/* @__PURE__ */ new Date()).toISOString();
4218
- for (const docType of docTypes) {
4219
- if (existing.documents[docType]) {
4220
- existing.documents[docType].generated_at = now;
4400
+ function generateHubFiles2(graph) {
4401
+ const fileStats = getFileStats2(graph);
4402
+ const hubs = fileStats.filter((f) => f.totalConnections > 0).sort((a, b) => b.totalConnections - a.totalConnections).slice(0, 10);
4403
+ if (hubs.length === 0) {
4404
+ return "No hub files detected.\n\n";
4405
+ }
4406
+ let output = "Files with the most connections (changing these breaks the most things):\n\n";
4407
+ const headers = ["File", "Total Connections", "Incoming", "Outgoing", "Symbols"];
4408
+ const rows = hubs.map((f) => [
4409
+ `\`${f.filePath}\``,
4410
+ formatNumber(f.totalConnections),
4411
+ formatNumber(f.incomingConnections),
4412
+ formatNumber(f.outgoingConnections),
4413
+ formatNumber(f.symbolCount)
4414
+ ]);
4415
+ output += table(headers, rows);
4416
+ return output;
4417
+ }
4418
+
4419
+ // src/docs/api-surface.ts
4420
+ function generateApiSurface(graph, projectRoot, version) {
4421
+ let output = "";
4422
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4423
+ const fileCount = getFileCount6(graph);
4424
+ output += timestamp(version, now, fileCount, graph.order);
4425
+ output += header("API Surface");
4426
+ output += "Every exported symbol in the project \u2014 the public API.\n\n";
4427
+ output += header("Exports by File", 2);
4428
+ output += generateExportsByFile(graph);
4429
+ output += header("Exports by Kind", 2);
4430
+ output += generateExportsByKind(graph);
4431
+ output += header("Most-Used Exports", 2);
4432
+ output += generateMostUsedExports(graph);
4433
+ output += header("Unused Exports", 2);
4434
+ output += generateUnusedExports(graph);
4435
+ output += header("Re-exports / Barrel Files", 2);
4436
+ output += generateReExports(graph);
4437
+ return output;
4438
+ }
4439
+ function getFileCount6(graph) {
4440
+ const files = /* @__PURE__ */ new Set();
4441
+ graph.forEachNode((node, attrs) => {
4442
+ files.add(attrs.filePath);
4443
+ });
4444
+ return files.size;
4445
+ }
4446
+ function getExportedSymbols(graph) {
4447
+ const exports = [];
4448
+ graph.forEachNode((node, attrs) => {
4449
+ if (attrs.exported && attrs.name !== "__file__") {
4450
+ const dependentCount = graph.inDegree(node);
4451
+ exports.push({
4452
+ name: attrs.name,
4453
+ kind: attrs.kind,
4454
+ filePath: attrs.filePath,
4455
+ line: attrs.startLine,
4456
+ dependentCount
4457
+ });
4458
+ }
4459
+ });
4460
+ return exports;
4461
+ }
4462
+ function generateExportsByFile(graph) {
4463
+ const exports = getExportedSymbols(graph);
4464
+ if (exports.length === 0) {
4465
+ return "No exported symbols detected.\n\n";
4466
+ }
4467
+ const fileExports = /* @__PURE__ */ new Map();
4468
+ for (const exp of exports) {
4469
+ if (!fileExports.has(exp.filePath)) {
4470
+ fileExports.set(exp.filePath, []);
4221
4471
  }
4472
+ fileExports.get(exp.filePath).push(exp);
4222
4473
  }
4223
- existing.file_count = fileCount;
4224
- existing.symbol_count = symbolCount;
4225
- existing.edge_count = edgeCount;
4226
- existing.generated_at = now;
4227
- return existing;
4474
+ const sortedFiles = Array.from(fileExports.entries()).sort((a, b) => b[1].length - a[1].length);
4475
+ let output = "";
4476
+ for (const [filePath, fileExports2] of sortedFiles) {
4477
+ output += header(filePath, 3);
4478
+ const sorted = fileExports2.sort((a, b) => b.dependentCount - a.dependentCount);
4479
+ const items = sorted.map((exp) => {
4480
+ const depInfo = exp.dependentCount > 0 ? ` \u2014 ${formatNumber(exp.dependentCount)} dependents` : "";
4481
+ return `${code(exp.name)} (${exp.kind}, line ${exp.line})${depInfo}`;
4482
+ });
4483
+ output += unorderedList(items);
4484
+ }
4485
+ return output;
4486
+ }
4487
+ function generateExportsByKind(graph) {
4488
+ const exports = getExportedSymbols(graph);
4489
+ if (exports.length === 0) {
4490
+ return "No exported symbols detected.\n\n";
4491
+ }
4492
+ const kindGroups = /* @__PURE__ */ new Map();
4493
+ for (const exp of exports) {
4494
+ if (!kindGroups.has(exp.kind)) {
4495
+ kindGroups.set(exp.kind, []);
4496
+ }
4497
+ kindGroups.get(exp.kind).push(exp);
4498
+ }
4499
+ let output = "";
4500
+ const sortedKinds = Array.from(kindGroups.entries()).sort((a, b) => b[1].length - a[1].length);
4501
+ for (const [kind, kindExports] of sortedKinds) {
4502
+ if (kind === "import" || kind === "export") continue;
4503
+ output += `**${capitalizeKind(kind)}s (${kindExports.length}):**
4504
+
4505
+ `;
4506
+ const sorted = kindExports.sort((a, b) => b.dependentCount - a.dependentCount).slice(0, 20);
4507
+ const items = sorted.map((exp) => {
4508
+ return `${code(exp.name)} \u2014 ${code(exp.filePath)}:${exp.line}`;
4509
+ });
4510
+ output += unorderedList(items);
4511
+ }
4512
+ return output;
4513
+ }
4514
+ function capitalizeKind(kind) {
4515
+ const map = {
4516
+ function: "Function",
4517
+ class: "Class",
4518
+ variable: "Variable",
4519
+ constant: "Constant",
4520
+ type_alias: "Type",
4521
+ interface: "Interface",
4522
+ enum: "Enum",
4523
+ import: "Import",
4524
+ export: "Export",
4525
+ method: "Method",
4526
+ property: "Property",
4527
+ decorator: "Decorator",
4528
+ module: "Module"
4529
+ };
4530
+ return map[kind] || kind;
4228
4531
  }
4532
+ function generateMostUsedExports(graph) {
4533
+ const exports = getExportedSymbols(graph);
4534
+ if (exports.length === 0) {
4535
+ return "No exported symbols detected.\n\n";
4536
+ }
4537
+ const sorted = exports.filter((exp) => exp.dependentCount > 0).sort((a, b) => b.dependentCount - a.dependentCount).slice(0, 20);
4538
+ if (sorted.length === 0) {
4539
+ return "No exports with dependents detected.\n\n";
4540
+ }
4541
+ let output = "Top 20 exports by dependent count \u2014 these are the most critical symbols:\n\n";
4542
+ const items = sorted.map((exp) => {
4543
+ return `${code(exp.name)} (${exp.kind}) \u2014 ${formatNumber(exp.dependentCount)} dependents \u2014 ${code(exp.filePath)}:${exp.line}`;
4544
+ });
4545
+ output += unorderedList(items);
4546
+ return output;
4547
+ }
4548
+ function generateUnusedExports(graph) {
4549
+ const exports = getExportedSymbols(graph);
4550
+ if (exports.length === 0) {
4551
+ return "No exported symbols detected.\n\n";
4552
+ }
4553
+ const unused = exports.filter((exp) => exp.dependentCount === 0 && exp.kind !== "export");
4554
+ if (unused.length === 0) {
4555
+ return "\u2705 No unused exports detected. All exports are used.\n\n";
4556
+ }
4557
+ let output = `Found ${unused.length} exported symbol${unused.length === 1 ? "" : "s"} with zero dependents:
4229
4558
 
4230
- // src/docs/generator.ts
4231
- async function generateDocs(graph, projectRoot, version, parseTime, options) {
4232
- const startTime = Date.now();
4233
- const generated = [];
4234
- const errors = [];
4235
- try {
4236
- if (!existsSync6(options.outputDir)) {
4237
- mkdirSync(options.outputDir, { recursive: true });
4238
- if (options.verbose) {
4239
- console.log(`Created output directory: ${options.outputDir}`);
4240
- }
4559
+ `;
4560
+ const fileGroups = /* @__PURE__ */ new Map();
4561
+ for (const exp of unused) {
4562
+ if (!fileGroups.has(exp.filePath)) {
4563
+ fileGroups.set(exp.filePath, []);
4241
4564
  }
4242
- let docsToGenerate = options.include;
4243
- if (options.update && options.only) {
4244
- docsToGenerate = options.only;
4565
+ fileGroups.get(exp.filePath).push(exp);
4566
+ }
4567
+ for (const [filePath, fileExports] of fileGroups.entries()) {
4568
+ output += `**${filePath}:**
4569
+
4570
+ `;
4571
+ const items = fileExports.map((exp) => `${code(exp.name)} (${exp.kind}, line ${exp.line})`);
4572
+ output += unorderedList(items);
4573
+ }
4574
+ output += "These symbols may be part of the intended public API but are not currently used, or they may be dead code.\n\n";
4575
+ return output;
4576
+ }
4577
+ function generateReExports(graph) {
4578
+ const fileStats = /* @__PURE__ */ new Map();
4579
+ graph.forEachNode((node, attrs) => {
4580
+ if (!fileStats.has(attrs.filePath)) {
4581
+ fileStats.set(attrs.filePath, {
4582
+ exportCount: 0,
4583
+ reExportCount: 0,
4584
+ reExportSources: /* @__PURE__ */ new Set()
4585
+ });
4245
4586
  }
4246
- if (docsToGenerate.includes("all")) {
4247
- docsToGenerate = ["architecture", "conventions", "dependencies", "onboarding"];
4587
+ const stats = fileStats.get(attrs.filePath);
4588
+ if (attrs.exported) {
4589
+ stats.exportCount++;
4248
4590
  }
4249
- let metadata = null;
4250
- if (options.update) {
4251
- metadata = loadMetadata(options.outputDir);
4591
+ if (attrs.kind === "export") {
4592
+ stats.reExportCount++;
4252
4593
  }
4253
- const fileCount = getFileCount5(graph);
4254
- const symbolCount = graph.order;
4255
- const edgeCount = graph.size;
4256
- if (options.format === "markdown") {
4257
- if (docsToGenerate.includes("architecture")) {
4258
- try {
4259
- if (options.verbose) console.log("Generating ARCHITECTURE.md...");
4260
- const content = generateArchitecture(graph, projectRoot, version, parseTime);
4261
- const filePath = join10(options.outputDir, "ARCHITECTURE.md");
4262
- writeFileSync2(filePath, content, "utf-8");
4263
- generated.push("ARCHITECTURE.md");
4264
- } catch (err) {
4265
- errors.push(`Failed to generate ARCHITECTURE.md: ${err}`);
4266
- }
4267
- }
4268
- if (docsToGenerate.includes("conventions")) {
4269
- try {
4270
- if (options.verbose) console.log("Generating CONVENTIONS.md...");
4271
- const content = generateConventions(graph, projectRoot, version);
4272
- const filePath = join10(options.outputDir, "CONVENTIONS.md");
4273
- writeFileSync2(filePath, content, "utf-8");
4274
- generated.push("CONVENTIONS.md");
4275
- } catch (err) {
4276
- errors.push(`Failed to generate CONVENTIONS.md: ${err}`);
4277
- }
4278
- }
4279
- if (docsToGenerate.includes("dependencies")) {
4280
- try {
4281
- if (options.verbose) console.log("Generating DEPENDENCIES.md...");
4282
- const content = generateDependencies(graph, projectRoot, version);
4283
- const filePath = join10(options.outputDir, "DEPENDENCIES.md");
4284
- writeFileSync2(filePath, content, "utf-8");
4285
- generated.push("DEPENDENCIES.md");
4286
- } catch (err) {
4287
- errors.push(`Failed to generate DEPENDENCIES.md: ${err}`);
4288
- }
4289
- }
4290
- if (docsToGenerate.includes("onboarding")) {
4291
- try {
4292
- if (options.verbose) console.log("Generating ONBOARDING.md...");
4293
- const content = generateOnboarding(graph, projectRoot, version);
4294
- const filePath = join10(options.outputDir, "ONBOARDING.md");
4295
- writeFileSync2(filePath, content, "utf-8");
4296
- generated.push("ONBOARDING.md");
4297
- } catch (err) {
4298
- errors.push(`Failed to generate ONBOARDING.md: ${err}`);
4299
- }
4594
+ });
4595
+ graph.forEachEdge((edge, attrs, source, target) => {
4596
+ const sourceAttrs = graph.getNodeAttributes(source);
4597
+ const targetAttrs = graph.getNodeAttributes(target);
4598
+ if (sourceAttrs.kind === "export" && sourceAttrs.filePath !== targetAttrs.filePath) {
4599
+ const stats = fileStats.get(sourceAttrs.filePath);
4600
+ if (stats) {
4601
+ stats.reExportSources.add(targetAttrs.filePath);
4300
4602
  }
4301
- } else if (options.format === "json") {
4302
- errors.push("JSON format not yet supported");
4303
- }
4304
- if (metadata && options.update) {
4305
- metadata = updateMetadata(metadata, docsToGenerate, fileCount, symbolCount, edgeCount);
4306
- } else {
4307
- metadata = createMetadata(version, projectRoot, fileCount, symbolCount, edgeCount, docsToGenerate);
4603
+ }
4604
+ });
4605
+ const barrels = [];
4606
+ for (const [filePath, stats] of fileStats.entries()) {
4607
+ if (stats.reExportCount > 0 && stats.reExportCount >= stats.exportCount * 0.5) {
4608
+ barrels.push({
4609
+ filePath,
4610
+ exportCount: stats.exportCount,
4611
+ reExportCount: stats.reExportCount,
4612
+ sources: Array.from(stats.reExportSources)
4613
+ });
4614
+ }
4615
+ }
4616
+ if (barrels.length === 0) {
4617
+ return "No barrel files detected.\n\n";
4618
+ }
4619
+ let output = `Found ${barrels.length} barrel file${barrels.length === 1 ? "" : "s"} (files that primarily re-export from other files):
4620
+
4621
+ `;
4622
+ for (const barrel of barrels) {
4623
+ output += header(barrel.filePath, 3);
4624
+ output += `- **Total exports:** ${formatNumber(barrel.exportCount)}
4625
+ `;
4626
+ output += `- **Re-exports:** ${formatNumber(barrel.reExportCount)}
4627
+ `;
4628
+ if (barrel.sources.length > 0) {
4629
+ output += `- **Sources:**
4630
+
4631
+ `;
4632
+ output += unorderedList(barrel.sources.map((s) => code(s)));
4633
+ } else {
4634
+ output += "\n";
4635
+ }
4636
+ }
4637
+ return output;
4638
+ }
4639
+
4640
+ // src/docs/errors.ts
4641
+ function generateErrors(graph, projectRoot, version) {
4642
+ let output = "";
4643
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4644
+ const fileCount = getFileCount7(graph);
4645
+ output += timestamp(version, now, fileCount, graph.order);
4646
+ output += header("Error Handling Analysis");
4647
+ output += "Analysis of error handling patterns and error-prone areas in the codebase.\n\n";
4648
+ output += header("Error-Related Symbols", 2);
4649
+ output += generateErrorRelatedSymbols(graph);
4650
+ output += header("Custom Error Classes", 2);
4651
+ output += generateCustomErrorClasses(graph);
4652
+ output += header("Error-Prone Files", 2);
4653
+ output += generateErrorProneFiles(graph);
4654
+ output += header("Detected Patterns", 2);
4655
+ output += generateErrorHandlingPatterns(graph);
4656
+ output += header("Recommendations", 2);
4657
+ output += generateRecommendations(graph);
4658
+ return output;
4659
+ }
4660
+ function getFileCount7(graph) {
4661
+ const files = /* @__PURE__ */ new Set();
4662
+ graph.forEachNode((node, attrs) => {
4663
+ files.add(attrs.filePath);
4664
+ });
4665
+ return files.size;
4666
+ }
4667
+ function getErrorRelatedSymbols(graph) {
4668
+ const errorKeywords = [
4669
+ "error",
4670
+ "err",
4671
+ "exception",
4672
+ "throw",
4673
+ "fail",
4674
+ "invalid",
4675
+ "not_found",
4676
+ "notfound",
4677
+ "unauthorized",
4678
+ "forbidden",
4679
+ "timeout",
4680
+ "retry",
4681
+ "catch",
4682
+ "try"
4683
+ ];
4684
+ const symbols = [];
4685
+ graph.forEachNode((node, attrs) => {
4686
+ if (attrs.name === "__file__") return;
4687
+ const nameLower = attrs.name.toLowerCase();
4688
+ for (const keyword of errorKeywords) {
4689
+ if (nameLower.includes(keyword)) {
4690
+ let category = "error_handling";
4691
+ if (nameLower.includes("retry") || nameLower.includes("timeout")) {
4692
+ category = "retry_timeout";
4693
+ } else if (nameLower.includes("invalid") || nameLower.includes("validate")) {
4694
+ category = "validation";
4695
+ } else if (nameLower.includes("unauthorized") || nameLower.includes("forbidden")) {
4696
+ category = "auth_error";
4697
+ } else if (nameLower.includes("notfound") || nameLower.includes("not_found")) {
4698
+ category = "not_found";
4699
+ }
4700
+ symbols.push({
4701
+ name: attrs.name,
4702
+ kind: attrs.kind,
4703
+ filePath: attrs.filePath,
4704
+ line: attrs.startLine,
4705
+ category
4706
+ });
4707
+ break;
4708
+ }
4709
+ }
4710
+ });
4711
+ return symbols;
4712
+ }
4713
+ function generateErrorRelatedSymbols(graph) {
4714
+ const symbols = getErrorRelatedSymbols(graph);
4715
+ if (symbols.length === 0) {
4716
+ return "No error-related symbols detected.\n\n";
4717
+ }
4718
+ let output = `Found ${symbols.length} error-related symbol${symbols.length === 1 ? "" : "s"}:
4719
+
4720
+ `;
4721
+ const categories = /* @__PURE__ */ new Map();
4722
+ for (const sym of symbols) {
4723
+ if (!categories.has(sym.category)) {
4724
+ categories.set(sym.category, []);
4725
+ }
4726
+ categories.get(sym.category).push(sym);
4727
+ }
4728
+ for (const [category, syms] of categories.entries()) {
4729
+ output += `**${formatCategory(category)} (${syms.length}):**
4730
+
4731
+ `;
4732
+ const items = syms.slice(0, 10).map((s) => {
4733
+ return `${code(s.name)} (${s.kind}) \u2014 ${code(s.filePath)}:${s.line}`;
4734
+ });
4735
+ output += unorderedList(items);
4736
+ if (syms.length > 10) {
4737
+ output += `... and ${syms.length - 10} more.
4738
+
4739
+ `;
4740
+ }
4741
+ }
4742
+ return output;
4743
+ }
4744
+ function formatCategory(category) {
4745
+ const map = {
4746
+ "error_handling": "Error Handling",
4747
+ "retry_timeout": "Retry / Timeout",
4748
+ "validation": "Validation",
4749
+ "auth_error": "Authentication Errors",
4750
+ "not_found": "Not Found Errors"
4751
+ };
4752
+ return map[category] || category;
4753
+ }
4754
+ function generateCustomErrorClasses(graph) {
4755
+ const errorClasses = [];
4756
+ graph.forEachNode((node, attrs) => {
4757
+ if (attrs.kind === "class") {
4758
+ const nameLower = attrs.name.toLowerCase();
4759
+ if (nameLower.includes("error") || nameLower.includes("exception")) {
4760
+ errorClasses.push({
4761
+ name: attrs.name,
4762
+ filePath: attrs.filePath,
4763
+ line: attrs.startLine
4764
+ });
4765
+ }
4766
+ }
4767
+ });
4768
+ if (errorClasses.length === 0) {
4769
+ return "No custom error classes detected.\n\n";
4770
+ }
4771
+ let output = `Found ${errorClasses.length} custom error class${errorClasses.length === 1 ? "" : "es"}:
4772
+
4773
+ `;
4774
+ const items = errorClasses.map((c) => {
4775
+ return `${code(c.name)} \u2014 ${code(c.filePath)}:${c.line}`;
4776
+ });
4777
+ output += unorderedList(items);
4778
+ return output;
4779
+ }
4780
+ function generateErrorProneFiles(graph) {
4781
+ const fileStats = /* @__PURE__ */ new Map();
4782
+ graph.forEachNode((node, attrs) => {
4783
+ if (!fileStats.has(attrs.filePath)) {
4784
+ fileStats.set(attrs.filePath, {
4785
+ connectionCount: 0,
4786
+ errorSymbolCount: 0,
4787
+ symbolCount: 0
4788
+ });
4789
+ }
4790
+ fileStats.get(attrs.filePath).symbolCount++;
4791
+ });
4792
+ const errorSymbols = getErrorRelatedSymbols(graph);
4793
+ for (const sym of errorSymbols) {
4794
+ const stats = fileStats.get(sym.filePath);
4795
+ if (stats) {
4796
+ stats.errorSymbolCount++;
4797
+ }
4798
+ }
4799
+ graph.forEachEdge((edge, attrs, source, target) => {
4800
+ const sourceAttrs = graph.getNodeAttributes(source);
4801
+ const targetAttrs = graph.getNodeAttributes(target);
4802
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
4803
+ const sourceStats = fileStats.get(sourceAttrs.filePath);
4804
+ const targetStats = fileStats.get(targetAttrs.filePath);
4805
+ if (sourceStats) sourceStats.connectionCount++;
4806
+ if (targetStats) targetStats.connectionCount++;
4807
+ }
4808
+ });
4809
+ const errorProneFiles = [];
4810
+ for (const [filePath, stats] of fileStats.entries()) {
4811
+ if (stats.connectionCount > 5) {
4812
+ const riskScore = stats.connectionCount * (1 + stats.errorSymbolCount * 0.5);
4813
+ errorProneFiles.push({
4814
+ filePath,
4815
+ connectionCount: stats.connectionCount,
4816
+ errorSymbolCount: stats.errorSymbolCount,
4817
+ riskScore
4818
+ });
4819
+ }
4820
+ }
4821
+ errorProneFiles.sort((a, b) => b.riskScore - a.riskScore);
4822
+ if (errorProneFiles.length === 0) {
4823
+ return "No high-risk files detected.\n\n";
4824
+ }
4825
+ let output = "Files with high complexity and error-related code (riskiest to modify):\n\n";
4826
+ const headers = ["File", "Connections", "Error Symbols", "Risk Score"];
4827
+ const rows = errorProneFiles.slice(0, 15).map((f) => [
4828
+ `\`${f.filePath}\``,
4829
+ formatNumber(f.connectionCount),
4830
+ formatNumber(f.errorSymbolCount),
4831
+ f.riskScore.toFixed(1)
4832
+ ]);
4833
+ output += table(headers, rows);
4834
+ return output;
4835
+ }
4836
+ function generateErrorHandlingPatterns(graph) {
4837
+ const patterns = {
4838
+ custom_errors: 0,
4839
+ retry: 0,
4840
+ timeout: 0,
4841
+ validation: 0,
4842
+ guard: 0
4843
+ };
4844
+ graph.forEachNode((node, attrs) => {
4845
+ const nameLower = attrs.name.toLowerCase();
4846
+ if (attrs.kind === "class" && (nameLower.includes("error") || nameLower.includes("exception"))) {
4847
+ patterns.custom_errors++;
4848
+ }
4849
+ if (nameLower.includes("retry") || nameLower.includes("attempt")) {
4850
+ patterns.retry++;
4851
+ }
4852
+ if (nameLower.includes("timeout")) {
4853
+ patterns.timeout++;
4854
+ }
4855
+ if (nameLower.includes("validate") || nameLower.includes("validator") || nameLower.includes("check")) {
4856
+ patterns.validation++;
4857
+ }
4858
+ if (nameLower.includes("guard") || nameLower.startsWith("is") || nameLower.startsWith("has")) {
4859
+ patterns.guard++;
4860
+ }
4861
+ });
4862
+ const detectedPatterns = Object.entries(patterns).filter(([, count]) => count > 0);
4863
+ if (detectedPatterns.length === 0) {
4864
+ return "No error handling patterns detected.\n\n";
4865
+ }
4866
+ let output = "";
4867
+ for (const [pattern, count] of detectedPatterns) {
4868
+ const description = getPatternDescription2(pattern);
4869
+ output += `- **${formatPatternName(pattern)}:** ${count} occurrences \u2014 ${description}
4870
+ `;
4871
+ }
4872
+ output += "\n";
4873
+ return output;
4874
+ }
4875
+ function formatPatternName(pattern) {
4876
+ const map = {
4877
+ custom_errors: "Custom Error Hierarchy",
4878
+ retry: "Retry Pattern",
4879
+ timeout: "Timeout Handling",
4880
+ validation: "Input Validation",
4881
+ guard: "Guard Clauses"
4882
+ };
4883
+ return map[pattern] || pattern;
4884
+ }
4885
+ function getPatternDescription2(pattern) {
4886
+ const map = {
4887
+ custom_errors: "Custom error classes for domain-specific exceptions",
4888
+ retry: "Retry logic for transient failures",
4889
+ timeout: "Timeout handling for long-running operations",
4890
+ validation: "Input validation to prevent errors",
4891
+ guard: "Guard clauses to check preconditions"
4892
+ };
4893
+ return map[pattern] || "";
4894
+ }
4895
+ function generateRecommendations(graph) {
4896
+ const recommendations = [];
4897
+ const fileStats = /* @__PURE__ */ new Map();
4898
+ graph.forEachNode((node, attrs) => {
4899
+ if (!fileStats.has(attrs.filePath)) {
4900
+ fileStats.set(attrs.filePath, {
4901
+ connectionCount: 0,
4902
+ errorSymbolCount: 0
4903
+ });
4904
+ }
4905
+ });
4906
+ const errorSymbols = getErrorRelatedSymbols(graph);
4907
+ for (const sym of errorSymbols) {
4908
+ const stats = fileStats.get(sym.filePath);
4909
+ if (stats) {
4910
+ stats.errorSymbolCount++;
4911
+ }
4912
+ }
4913
+ graph.forEachEdge((edge, attrs, source, target) => {
4914
+ const sourceAttrs = graph.getNodeAttributes(source);
4915
+ const targetAttrs = graph.getNodeAttributes(target);
4916
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
4917
+ const sourceStats = fileStats.get(sourceAttrs.filePath);
4918
+ const targetStats = fileStats.get(targetAttrs.filePath);
4919
+ if (sourceStats) sourceStats.connectionCount++;
4920
+ if (targetStats) targetStats.connectionCount++;
4921
+ }
4922
+ });
4923
+ const needsErrorHandling = [];
4924
+ for (const [filePath, stats] of fileStats.entries()) {
4925
+ if (stats.connectionCount > 10 && stats.errorSymbolCount === 0) {
4926
+ needsErrorHandling.push(filePath);
4927
+ }
4928
+ }
4929
+ if (needsErrorHandling.length > 0) {
4930
+ recommendations.push(`**Add error handling to high-connection files:** ${needsErrorHandling.slice(0, 5).map((f) => code(f)).join(", ")}`);
4931
+ }
4932
+ const errorClasses = [];
4933
+ graph.forEachNode((node, attrs) => {
4934
+ if (attrs.kind === "class") {
4935
+ const nameLower = attrs.name.toLowerCase();
4936
+ if (nameLower.includes("error") || nameLower.includes("exception")) {
4937
+ const dependents = graph.inDegree(node);
4938
+ if (dependents === 0) {
4939
+ errorClasses.push(attrs.name);
4940
+ }
4941
+ }
4942
+ }
4943
+ });
4944
+ if (errorClasses.length > 0) {
4945
+ recommendations.push(`**Unused error classes detected:** ${errorClasses.slice(0, 5).map((c) => code(c)).join(", ")} \u2014 Consider removing or documenting why they exist`);
4946
+ }
4947
+ if (recommendations.length === 0) {
4948
+ return "\u2705 No specific recommendations. Error handling appears well-distributed.\n\n";
4949
+ }
4950
+ return unorderedList(recommendations);
4951
+ }
4952
+
4953
+ // src/docs/tests.ts
4954
+ import { basename as basename4, dirname as dirname9 } from "path";
4955
+ function generateTests(graph, projectRoot, version) {
4956
+ let output = "";
4957
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4958
+ const fileCount = getFileCount8(graph);
4959
+ output += timestamp(version, now, fileCount, graph.order);
4960
+ output += header("Test Analysis");
4961
+ output += "Test file inventory and coverage mapping.\n\n";
4962
+ output += header("Test File Inventory", 2);
4963
+ output += generateTestFileInventory(graph);
4964
+ output += header("Test-to-Source Mapping", 2);
4965
+ output += generateTestToSourceMapping(graph);
4966
+ output += header("Untested Files", 2);
4967
+ output += generateUntestedFiles(graph);
4968
+ output += header("Test Coverage Map", 2);
4969
+ output += generateTestCoverageMap(graph);
4970
+ output += header("Test Statistics", 2);
4971
+ output += generateTestStatistics(graph);
4972
+ return output;
4973
+ }
4974
+ function getFileCount8(graph) {
4975
+ const files = /* @__PURE__ */ new Set();
4976
+ graph.forEachNode((node, attrs) => {
4977
+ files.add(attrs.filePath);
4978
+ });
4979
+ return files.size;
4980
+ }
4981
+ function isTestFile(filePath) {
4982
+ const fileName = basename4(filePath).toLowerCase();
4983
+ const dirPath = dirname9(filePath).toLowerCase();
4984
+ if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
4985
+ return true;
4986
+ }
4987
+ if (fileName.includes(".test.") || fileName.includes(".spec.") || fileName.includes("_test.") || fileName.includes("_spec.")) {
4988
+ return true;
4989
+ }
4990
+ return false;
4991
+ }
4992
+ function getTestFiles(graph) {
4993
+ const testFiles = /* @__PURE__ */ new Map();
4994
+ graph.forEachNode((node, attrs) => {
4995
+ if (isTestFile(attrs.filePath)) {
4996
+ if (!testFiles.has(attrs.filePath)) {
4997
+ testFiles.set(attrs.filePath, {
4998
+ filePath: attrs.filePath,
4999
+ language: getLanguageFromPath2(attrs.filePath),
5000
+ symbolCount: 0,
5001
+ functionCount: 0
5002
+ });
5003
+ }
5004
+ const info = testFiles.get(attrs.filePath);
5005
+ info.symbolCount++;
5006
+ if (attrs.kind === "function" || attrs.kind === "method") {
5007
+ info.functionCount++;
5008
+ }
5009
+ }
5010
+ });
5011
+ return Array.from(testFiles.values());
5012
+ }
5013
+ function getLanguageFromPath2(filePath) {
5014
+ const ext = filePath.toLowerCase();
5015
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) return "TypeScript";
5016
+ if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) return "JavaScript";
5017
+ if (ext.endsWith(".py")) return "Python";
5018
+ if (ext.endsWith(".go")) return "Go";
5019
+ return "Other";
5020
+ }
5021
+ function generateTestFileInventory(graph) {
5022
+ const testFiles = getTestFiles(graph);
5023
+ if (testFiles.length === 0) {
5024
+ return "No test files detected.\n\n";
5025
+ }
5026
+ let output = `Found ${testFiles.length} test file${testFiles.length === 1 ? "" : "s"}:
5027
+
5028
+ `;
5029
+ testFiles.sort((a, b) => a.filePath.localeCompare(b.filePath));
5030
+ const headers = ["Test File", "Language", "Symbols", "Functions"];
5031
+ const rows = testFiles.map((t) => [
5032
+ `\`${t.filePath}\``,
5033
+ t.language,
5034
+ formatNumber(t.symbolCount),
5035
+ formatNumber(t.functionCount)
5036
+ ]);
5037
+ output += table(headers, rows);
5038
+ return output;
5039
+ }
5040
+ function matchTestToSource(testFile) {
5041
+ const testFileName = basename4(testFile);
5042
+ const testDir = dirname9(testFile);
5043
+ let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
5044
+ const possiblePaths = [];
5045
+ possiblePaths.push(testDir + "/" + sourceFileName);
5046
+ if (testDir.endsWith("/test") || testDir.endsWith("/tests") || testDir.endsWith("/__tests__")) {
5047
+ const parentDir = dirname9(testDir);
5048
+ possiblePaths.push(parentDir + "/" + sourceFileName);
5049
+ }
5050
+ if (testDir.includes("test")) {
5051
+ const srcDir = testDir.replace(/test[s]?/g, "src");
5052
+ possiblePaths.push(srcDir + "/" + sourceFileName);
5053
+ }
5054
+ for (const path2 of possiblePaths) {
5055
+ if (!isTestFile(path2)) {
5056
+ return path2;
5057
+ }
5058
+ }
5059
+ return null;
5060
+ }
5061
+ function generateTestToSourceMapping(graph) {
5062
+ const testFiles = getTestFiles(graph);
5063
+ if (testFiles.length === 0) {
5064
+ return "No test files detected.\n\n";
5065
+ }
5066
+ const allFiles = /* @__PURE__ */ new Set();
5067
+ graph.forEachNode((node, attrs) => {
5068
+ allFiles.add(attrs.filePath);
5069
+ });
5070
+ let output = "";
5071
+ let mappedCount = 0;
5072
+ const mappings = [];
5073
+ for (const testFile of testFiles) {
5074
+ const sourceFile = matchTestToSource(testFile.filePath);
5075
+ const exists = sourceFile && allFiles.has(sourceFile);
5076
+ mappings.push({
5077
+ test: testFile.filePath,
5078
+ source: exists ? sourceFile : null
5079
+ });
5080
+ if (exists) {
5081
+ mappedCount++;
5082
+ }
5083
+ }
5084
+ output += `Matched ${mappedCount} of ${testFiles.length} test files to source files:
5085
+
5086
+ `;
5087
+ for (const mapping of mappings) {
5088
+ if (mapping.source) {
5089
+ output += `- ${code(mapping.source)} \u2190 ${code(mapping.test)}
5090
+ `;
5091
+ }
5092
+ }
5093
+ output += "\n";
5094
+ const unmapped = mappings.filter((m) => !m.source);
5095
+ if (unmapped.length > 0) {
5096
+ output += `**Unmapped test files (${unmapped.length}):**
5097
+
5098
+ `;
5099
+ output += unorderedList(unmapped.map((m) => code(m.test)));
5100
+ }
5101
+ return output;
5102
+ }
5103
+ function generateUntestedFiles(graph) {
5104
+ const testFiles = getTestFiles(graph);
5105
+ const sourceFiles = [];
5106
+ const allFiles = /* @__PURE__ */ new Set();
5107
+ graph.forEachNode((node, attrs) => {
5108
+ allFiles.add(attrs.filePath);
5109
+ });
5110
+ for (const file of allFiles) {
5111
+ if (!isTestFile(file)) {
5112
+ sourceFiles.push(file);
5113
+ }
5114
+ }
5115
+ if (sourceFiles.length === 0) {
5116
+ return "No source files detected.\n\n";
5117
+ }
5118
+ const testedFiles = /* @__PURE__ */ new Set();
5119
+ for (const testFile of testFiles) {
5120
+ const sourceFile = matchTestToSource(testFile.filePath);
5121
+ if (sourceFile && allFiles.has(sourceFile)) {
5122
+ testedFiles.add(sourceFile);
5123
+ }
5124
+ }
5125
+ const untested = sourceFiles.filter((f) => !testedFiles.has(f));
5126
+ if (untested.length === 0) {
5127
+ return "\u2705 All source files have matching test files.\n\n";
5128
+ }
5129
+ const fileConnections = /* @__PURE__ */ new Map();
5130
+ graph.forEachEdge((edge, attrs, source, target) => {
5131
+ const sourceAttrs = graph.getNodeAttributes(source);
5132
+ const targetAttrs = graph.getNodeAttributes(target);
5133
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
5134
+ fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
5135
+ fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
5136
+ }
5137
+ });
5138
+ const untestedWithConnections = untested.map((f) => ({
5139
+ filePath: f,
5140
+ connections: fileConnections.get(f) || 0
5141
+ })).sort((a, b) => b.connections - a.connections);
5142
+ let output = `\u26A0\uFE0F Found ${untested.length} source file${untested.length === 1 ? "" : "s"} without matching test files:
5143
+
5144
+ `;
5145
+ const headers = ["File", "Connections", "Priority"];
5146
+ const rows = untestedWithConnections.slice(0, 20).map((f) => {
5147
+ const priority = f.connections > 10 ? "\u{1F534} High" : f.connections > 5 ? "\u{1F7E1} Medium" : "\u{1F7E2} Low";
5148
+ return [
5149
+ `\`${f.filePath}\``,
5150
+ formatNumber(f.connections),
5151
+ priority
5152
+ ];
5153
+ });
5154
+ output += table(headers, rows);
5155
+ if (untested.length > 20) {
5156
+ output += `... and ${untested.length - 20} more.
5157
+
5158
+ `;
5159
+ }
5160
+ return output;
5161
+ }
5162
+ function generateTestCoverageMap(graph) {
5163
+ const testFiles = getTestFiles(graph);
5164
+ const allFiles = /* @__PURE__ */ new Set();
5165
+ const sourceFiles = [];
5166
+ graph.forEachNode((node, attrs) => {
5167
+ allFiles.add(attrs.filePath);
5168
+ });
5169
+ for (const file of allFiles) {
5170
+ if (!isTestFile(file)) {
5171
+ sourceFiles.push(file);
5172
+ }
5173
+ }
5174
+ if (sourceFiles.length === 0) {
5175
+ return "No source files detected.\n\n";
5176
+ }
5177
+ const mappings = [];
5178
+ const testedFiles = /* @__PURE__ */ new Map();
5179
+ for (const testFile of testFiles) {
5180
+ const sourceFile = matchTestToSource(testFile.filePath);
5181
+ if (sourceFile && allFiles.has(sourceFile)) {
5182
+ testedFiles.set(sourceFile, testFile.filePath);
5183
+ }
5184
+ }
5185
+ const fileSymbols = /* @__PURE__ */ new Map();
5186
+ graph.forEachNode((node, attrs) => {
5187
+ fileSymbols.set(attrs.filePath, (fileSymbols.get(attrs.filePath) || 0) + 1);
5188
+ });
5189
+ for (const sourceFile of sourceFiles) {
5190
+ const testFile = testedFiles.get(sourceFile);
5191
+ mappings.push({
5192
+ sourceFile,
5193
+ hasTest: !!testFile,
5194
+ testFile: testFile || null,
5195
+ symbolCount: fileSymbols.get(sourceFile) || 0
5196
+ });
5197
+ }
5198
+ mappings.sort((a, b) => a.sourceFile.localeCompare(b.sourceFile));
5199
+ const headers = ["Source File", "Has Test?", "Test File", "Symbols"];
5200
+ const rows = mappings.slice(0, 30).map((m) => [
5201
+ `\`${m.sourceFile}\``,
5202
+ m.hasTest ? "\u2705" : "\u274C",
5203
+ m.testFile ? `\`${basename4(m.testFile)}\`` : "-",
5204
+ formatNumber(m.symbolCount)
5205
+ ]);
5206
+ let output = table(headers, rows);
5207
+ if (mappings.length > 30) {
5208
+ output += `... and ${mappings.length - 30} more files.
5209
+
5210
+ `;
5211
+ }
5212
+ return output;
5213
+ }
5214
+ function generateTestStatistics(graph) {
5215
+ const testFiles = getTestFiles(graph);
5216
+ const allFiles = /* @__PURE__ */ new Set();
5217
+ const sourceFiles = [];
5218
+ graph.forEachNode((node, attrs) => {
5219
+ allFiles.add(attrs.filePath);
5220
+ });
5221
+ for (const file of allFiles) {
5222
+ if (!isTestFile(file)) {
5223
+ sourceFiles.push(file);
5224
+ }
5225
+ }
5226
+ const testedFiles = /* @__PURE__ */ new Set();
5227
+ for (const testFile of testFiles) {
5228
+ const sourceFile = matchTestToSource(testFile.filePath);
5229
+ if (sourceFile && allFiles.has(sourceFile)) {
5230
+ testedFiles.add(sourceFile);
5231
+ }
5232
+ }
5233
+ let output = "";
5234
+ output += `- **Total test files:** ${formatNumber(testFiles.length)}
5235
+ `;
5236
+ output += `- **Total source files:** ${formatNumber(sourceFiles.length)}
5237
+ `;
5238
+ output += `- **Source files with tests:** ${formatNumber(testedFiles.size)} (${formatPercent(testedFiles.size, sourceFiles.length)})
5239
+ `;
5240
+ output += `- **Source files without tests:** ${formatNumber(sourceFiles.length - testedFiles.size)} (${formatPercent(sourceFiles.length - testedFiles.size, sourceFiles.length)})
5241
+ `;
5242
+ const dirTestCoverage = /* @__PURE__ */ new Map();
5243
+ for (const sourceFile of sourceFiles) {
5244
+ const dir = dirname9(sourceFile).split("/")[0];
5245
+ if (!dirTestCoverage.has(dir)) {
5246
+ dirTestCoverage.set(dir, { total: 0, tested: 0 });
5247
+ }
5248
+ dirTestCoverage.get(dir).total++;
5249
+ if (testedFiles.has(sourceFile)) {
5250
+ dirTestCoverage.get(dir).tested++;
5251
+ }
5252
+ }
5253
+ if (dirTestCoverage.size > 1) {
5254
+ output += "\n**Coverage by directory:**\n\n";
5255
+ const sortedDirs = Array.from(dirTestCoverage.entries()).sort((a, b) => b[1].total - a[1].total);
5256
+ for (const [dir, coverage] of sortedDirs) {
5257
+ const percent = formatPercent(coverage.tested, coverage.total);
5258
+ output += `- **${dir}/**: ${coverage.tested}/${coverage.total} files (${percent})
5259
+ `;
5260
+ }
5261
+ }
5262
+ output += "\n";
5263
+ return output;
5264
+ }
5265
+
5266
+ // src/docs/history.ts
5267
+ import { dirname as dirname10 } from "path";
5268
+ import { execSync } from "child_process";
5269
+ function generateHistory(graph, projectRoot, version) {
5270
+ let output = "";
5271
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5272
+ const fileCount = getFileCount9(graph);
5273
+ output += timestamp(version, now, fileCount, graph.order);
5274
+ output += header("Development History");
5275
+ output += "Git history combined with graph analysis showing feature evolution.\n\n";
5276
+ const hasGit = isGitAvailable(projectRoot);
5277
+ if (!hasGit) {
5278
+ output += "\u26A0\uFE0F **Git history not available.** This project is not a git repository or git is not installed.\n\n";
5279
+ output += "Showing graph-based analysis only:\n\n";
5280
+ }
5281
+ if (hasGit) {
5282
+ output += header("Development Timeline", 2);
5283
+ output += generateDevelopmentTimeline(projectRoot);
5284
+ }
5285
+ if (hasGit) {
5286
+ output += header("File Change Frequency (Churn)", 2);
5287
+ output += generateFileChurn(projectRoot, graph);
5288
+ }
5289
+ if (hasGit) {
5290
+ output += header("Feature Timeline", 2);
5291
+ output += generateFeatureTimeline(projectRoot);
5292
+ }
5293
+ if (hasGit) {
5294
+ output += header("File Age Analysis", 2);
5295
+ output += generateFileAgeAnalysis(projectRoot, graph);
5296
+ }
5297
+ if (hasGit) {
5298
+ output += header("Contributors", 2);
5299
+ output += generateContributors(projectRoot);
5300
+ }
5301
+ output += header("Feature Clusters (Graph-Based)", 2);
5302
+ output += generateFeatureClusters(graph);
5303
+ return output;
5304
+ }
5305
+ function getFileCount9(graph) {
5306
+ const files = /* @__PURE__ */ new Set();
5307
+ graph.forEachNode((node, attrs) => {
5308
+ files.add(attrs.filePath);
5309
+ });
5310
+ return files.size;
5311
+ }
5312
+ function isGitAvailable(projectRoot) {
5313
+ try {
5314
+ execSync("git rev-parse --git-dir", {
5315
+ cwd: projectRoot,
5316
+ encoding: "utf-8",
5317
+ timeout: 5e3,
5318
+ stdio: "pipe"
5319
+ });
5320
+ return true;
5321
+ } catch {
5322
+ return false;
5323
+ }
5324
+ }
5325
+ function executeGitCommand(projectRoot, command) {
5326
+ try {
5327
+ return execSync(command, {
5328
+ cwd: projectRoot,
5329
+ encoding: "utf-8",
5330
+ timeout: 1e4,
5331
+ stdio: "pipe"
5332
+ }).trim();
5333
+ } catch {
5334
+ return "";
5335
+ }
5336
+ }
5337
+ function generateDevelopmentTimeline(projectRoot) {
5338
+ const log = executeGitCommand(projectRoot, 'git log --format="%ai" --all --no-merges');
5339
+ if (!log) {
5340
+ return "Unable to retrieve git log.\n\n";
5341
+ }
5342
+ const dates = log.split("\n").filter((d) => d.length > 0);
5343
+ if (dates.length === 0) {
5344
+ return "No commits found.\n\n";
5345
+ }
5346
+ const firstCommit = new Date(dates[dates.length - 1]);
5347
+ const lastCommit = new Date(dates[0]);
5348
+ const ageInDays = Math.floor((lastCommit.getTime() - firstCommit.getTime()) / (1e3 * 60 * 60 * 24));
5349
+ const ageInMonths = Math.floor(ageInDays / 30);
5350
+ let output = "";
5351
+ output += `- **First commit:** ${firstCommit.toISOString().split("T")[0]}
5352
+ `;
5353
+ output += `- **Last commit:** ${lastCommit.toISOString().split("T")[0]}
5354
+ `;
5355
+ output += `- **Project age:** ${ageInMonths} months (${ageInDays} days)
5356
+ `;
5357
+ output += `- **Total commits:** ${formatNumber(dates.length)}
5358
+ `;
5359
+ const commitsPerMonth = ageInMonths > 0 ? (dates.length / ageInMonths).toFixed(1) : dates.length.toString();
5360
+ output += `- **Average activity:** ${commitsPerMonth} commits/month
5361
+ `;
5362
+ output += "\n";
5363
+ return output;
5364
+ }
5365
+ function generateFileChurn(projectRoot, graph) {
5366
+ const churnOutput = executeGitCommand(
5367
+ projectRoot,
5368
+ 'git log --all --name-only --format="" | sort | uniq -c | sort -rn | head -20'
5369
+ );
5370
+ if (!churnOutput) {
5371
+ return "Unable to retrieve file churn data.\n\n";
5372
+ }
5373
+ const lines = churnOutput.split("\n").filter((l) => l.trim().length > 0);
5374
+ if (lines.length === 0) {
5375
+ return "No file churn data available.\n\n";
5376
+ }
5377
+ const churnData = [];
5378
+ for (const line of lines) {
5379
+ const match = line.trim().match(/^(\d+)\s+(.+)$/);
5380
+ if (match) {
5381
+ const changes = parseInt(match[1], 10);
5382
+ const file = match[2].trim();
5383
+ if (file && file.length > 0 && !file.startsWith(".")) {
5384
+ churnData.push({ file, changes });
5385
+ }
5386
+ }
5387
+ }
5388
+ if (churnData.length === 0) {
5389
+ return "No valid file churn data.\n\n";
5390
+ }
5391
+ const fileConnections = /* @__PURE__ */ new Map();
5392
+ graph.forEachEdge((edge, attrs, source, target) => {
5393
+ const sourceAttrs = graph.getNodeAttributes(source);
5394
+ const targetAttrs = graph.getNodeAttributes(target);
5395
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
5396
+ fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
5397
+ fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
5398
+ }
5399
+ });
5400
+ let output = "Top 20 most-changed files:\n\n";
5401
+ const headers = ["File", "Changes", "Connections", "Risk"];
5402
+ const rows = churnData.slice(0, 20).map((item) => {
5403
+ const connections = fileConnections.get(item.file) || 0;
5404
+ let risk = "\u{1F7E2} Low";
5405
+ if (item.changes > 50 && connections > 10) {
5406
+ risk = "\u{1F534} High";
5407
+ } else if (item.changes > 20 && connections > 5) {
5408
+ risk = "\u{1F7E1} Medium";
5409
+ } else if (item.changes > 50 || connections > 10) {
5410
+ risk = "\u{1F7E1} Medium";
5411
+ }
5412
+ return [
5413
+ `\`${item.file}\``,
5414
+ formatNumber(item.changes),
5415
+ formatNumber(connections),
5416
+ risk
5417
+ ];
5418
+ });
5419
+ output += table(headers, rows);
5420
+ output += "**Risk levels:**\n\n";
5421
+ output += "- \u{1F534} High churn + high connections = risky hotspot (break often, affect many)\n";
5422
+ output += "- \u{1F7E1} High churn + low connections = actively developed but isolated\n";
5423
+ output += "- \u{1F7E2} Low churn + high connections = stable foundation\n\n";
5424
+ return output;
5425
+ }
5426
+ function generateFeatureTimeline(projectRoot) {
5427
+ const log = executeGitCommand(projectRoot, "git log --oneline --all --no-merges");
5428
+ if (!log) {
5429
+ return "Unable to retrieve commit log.\n\n";
5430
+ }
5431
+ const commits = log.split("\n").filter((c) => c.length > 0);
5432
+ if (commits.length === 0) {
5433
+ return "No commits found.\n\n";
5434
+ }
5435
+ const categories = {
5436
+ features: 0,
5437
+ fixes: 0,
5438
+ refactors: 0,
5439
+ other: 0
5440
+ };
5441
+ const featureKeywords = ["feat", "add", "new", "implement", "create"];
5442
+ const fixKeywords = ["fix", "bug", "patch", "resolve"];
5443
+ const refactorKeywords = ["refactor", "cleanup", "restructure", "improve"];
5444
+ for (const commit of commits) {
5445
+ const messageLower = commit.toLowerCase();
5446
+ if (featureKeywords.some((kw) => messageLower.includes(kw))) {
5447
+ categories.features++;
5448
+ } else if (fixKeywords.some((kw) => messageLower.includes(kw))) {
5449
+ categories.fixes++;
5450
+ } else if (refactorKeywords.some((kw) => messageLower.includes(kw))) {
5451
+ categories.refactors++;
5452
+ } else {
5453
+ categories.other++;
5454
+ }
5455
+ }
5456
+ let output = "Commit breakdown by type:\n\n";
5457
+ output += `- **Features:** ${formatNumber(categories.features)} commits (${(categories.features / commits.length * 100).toFixed(1)}%)
5458
+ `;
5459
+ output += `- **Bug fixes:** ${formatNumber(categories.fixes)} commits (${(categories.fixes / commits.length * 100).toFixed(1)}%)
5460
+ `;
5461
+ output += `- **Refactors:** ${formatNumber(categories.refactors)} commits (${(categories.refactors / commits.length * 100).toFixed(1)}%)
5462
+ `;
5463
+ output += `- **Other:** ${formatNumber(categories.other)} commits (${(categories.other / commits.length * 100).toFixed(1)}%)
5464
+ `;
5465
+ output += "\n";
5466
+ return output;
5467
+ }
5468
+ function generateFileAgeAnalysis(projectRoot, graph) {
5469
+ const files = /* @__PURE__ */ new Set();
5470
+ graph.forEachNode((node, attrs) => {
5471
+ files.add(attrs.filePath);
5472
+ });
5473
+ if (files.size === 0) {
5474
+ return "No files to analyze.\n\n";
5475
+ }
5476
+ const fileAges = [];
5477
+ const sampleFiles = Array.from(files).slice(0, 20);
5478
+ for (const file of sampleFiles) {
5479
+ const dateStr = executeGitCommand(
5480
+ projectRoot,
5481
+ `git log --format="%ai" --diff-filter=A -- "${file}" | tail -1`
5482
+ );
5483
+ if (dateStr) {
5484
+ fileAges.push({
5485
+ file,
5486
+ date: new Date(dateStr)
5487
+ });
5488
+ }
5489
+ }
5490
+ if (fileAges.length === 0) {
5491
+ return "Unable to determine file ages.\n\n";
5492
+ }
5493
+ fileAges.sort((a, b) => a.date.getTime() - b.date.getTime());
5494
+ let output = "";
5495
+ output += "**Oldest files (foundation):**\n\n";
5496
+ const oldest = fileAges.slice(0, 5);
5497
+ output += unorderedList(oldest.map((f) => {
5498
+ return `${code(f.file)} \u2014 added ${f.date.toISOString().split("T")[0]}`;
5499
+ }));
5500
+ output += "**Newest files (recent features):**\n\n";
5501
+ const newest = fileAges.slice(-5).reverse();
5502
+ output += unorderedList(newest.map((f) => {
5503
+ return `${code(f.file)} \u2014 added ${f.date.toISOString().split("T")[0]}`;
5504
+ }));
5505
+ return output;
5506
+ }
5507
+ function generateContributors(projectRoot) {
5508
+ const contributors = executeGitCommand(projectRoot, "git shortlog -sn --all");
5509
+ if (!contributors) {
5510
+ return "Unable to retrieve contributor data.\n\n";
5511
+ }
5512
+ const lines = contributors.split("\n").filter((l) => l.trim().length > 0);
5513
+ if (lines.length === 0) {
5514
+ return "No contributors found.\n\n";
5515
+ }
5516
+ let output = `Found ${lines.length} contributor${lines.length === 1 ? "" : "s"}:
5517
+
5518
+ `;
5519
+ const headers = ["Contributor", "Commits", "Percentage"];
5520
+ const contributorData = [];
5521
+ let totalCommits = 0;
5522
+ for (const line of lines) {
5523
+ const match = line.trim().match(/^(\d+)\s+(.+)$/);
5524
+ if (match) {
5525
+ const commits = parseInt(match[1], 10);
5526
+ const name = match[2].trim();
5527
+ contributorData.push({ name, commits });
5528
+ totalCommits += commits;
5529
+ }
5530
+ }
5531
+ const rows = contributorData.slice(0, 10).map((c) => [
5532
+ c.name,
5533
+ formatNumber(c.commits),
5534
+ `${(c.commits / totalCommits * 100).toFixed(1)}%`
5535
+ ]);
5536
+ output += table(headers, rows);
5537
+ if (contributorData.length > 10) {
5538
+ output += `... and ${contributorData.length - 10} more contributors.
5539
+
5540
+ `;
5541
+ }
5542
+ return output;
5543
+ }
5544
+ function generateFeatureClusters(graph) {
5545
+ const dirFiles = /* @__PURE__ */ new Map();
5546
+ const fileEdges = /* @__PURE__ */ new Map();
5547
+ graph.forEachNode((node, attrs) => {
5548
+ const dir = dirname10(attrs.filePath);
5549
+ if (!dirFiles.has(dir)) {
5550
+ dirFiles.set(dir, /* @__PURE__ */ new Set());
5551
+ }
5552
+ dirFiles.get(dir).add(attrs.filePath);
5553
+ });
5554
+ graph.forEachEdge((edge, attrs, source, target) => {
5555
+ const sourceFile = graph.getNodeAttributes(source).filePath;
5556
+ const targetFile = graph.getNodeAttributes(target).filePath;
5557
+ if (sourceFile !== targetFile) {
5558
+ if (!fileEdges.has(sourceFile)) {
5559
+ fileEdges.set(sourceFile, /* @__PURE__ */ new Set());
5560
+ }
5561
+ fileEdges.get(sourceFile).add(targetFile);
5562
+ }
5563
+ });
5564
+ const clusters = [];
5565
+ for (const [dir, files] of dirFiles.entries()) {
5566
+ if (dir === "." || files.size < 2) continue;
5567
+ const fileArray = Array.from(files);
5568
+ let internalEdgeCount = 0;
5569
+ for (const file of fileArray) {
5570
+ const targets = fileEdges.get(file);
5571
+ if (targets) {
5572
+ for (const target of targets) {
5573
+ if (files.has(target)) {
5574
+ internalEdgeCount++;
5575
+ }
5576
+ }
5577
+ }
5578
+ }
5579
+ if (internalEdgeCount >= 2) {
5580
+ const clusterName = inferClusterName2(fileArray, dir);
5581
+ clusters.push({
5582
+ name: clusterName,
5583
+ files: fileArray,
5584
+ internalEdges: internalEdgeCount
5585
+ });
5586
+ }
5587
+ }
5588
+ if (clusters.length === 0) {
5589
+ return "No distinct feature clusters detected.\n\n";
5590
+ }
5591
+ clusters.sort((a, b) => b.internalEdges - a.internalEdges);
5592
+ let output = `Detected ${clusters.length} feature cluster${clusters.length === 1 ? "" : "s"} (tightly-connected file groups):
5593
+
5594
+ `;
5595
+ for (const cluster of clusters.slice(0, 10)) {
5596
+ output += `**${cluster.name}** (${cluster.files.length} files, ${cluster.internalEdges} internal connections):
5597
+
5598
+ `;
5599
+ const items = cluster.files.slice(0, 5).map((f) => code(f));
5600
+ output += unorderedList(items);
5601
+ if (cluster.files.length > 5) {
5602
+ output += `... and ${cluster.files.length - 5} more files.
5603
+
5604
+ `;
5605
+ }
5606
+ }
5607
+ return output;
5608
+ }
5609
+ function inferClusterName2(files, dir) {
5610
+ const words = /* @__PURE__ */ new Map();
5611
+ for (const file of files) {
5612
+ const fileName = file.toLowerCase();
5613
+ const parts = fileName.split(/[\/\-\_\.]/).filter((p) => p.length > 3);
5614
+ for (const part of parts) {
5615
+ words.set(part, (words.get(part) || 0) + 1);
5616
+ }
5617
+ }
5618
+ const sortedWords = Array.from(words.entries()).sort((a, b) => b[1] - a[1]);
5619
+ if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
5620
+ return capitalizeFirst3(sortedWords[0][0]);
5621
+ }
5622
+ const dirName = dir.split("/").pop() || "Core";
5623
+ return capitalizeFirst3(dirName);
5624
+ }
5625
+ function capitalizeFirst3(str) {
5626
+ return str.charAt(0).toUpperCase() + str.slice(1);
5627
+ }
5628
+
5629
+ // src/docs/current.ts
5630
+ import { dirname as dirname11 } from "path";
5631
+ function generateCurrent(graph, projectRoot, version) {
5632
+ let output = "";
5633
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5634
+ const fileCount = getFileCount10(graph);
5635
+ output += timestamp(version, now, fileCount, graph.order);
5636
+ output += header("Complete Codebase Snapshot");
5637
+ output += "> **Note:** This is a complete snapshot of the entire codebase. For a high-level overview, see ARCHITECTURE.md.\n\n";
5638
+ output += header("Project Overview", 2);
5639
+ output += generateProjectOverview(graph);
5640
+ output += header("Complete File Index", 2);
5641
+ output += generateCompleteFileIndex(graph);
5642
+ output += header("Complete Symbol Index", 2);
5643
+ output += generateCompleteSymbolIndex(graph);
5644
+ output += header("Complete Edge List", 2);
5645
+ output += generateCompleteEdgeList(graph);
5646
+ output += header("Connection Matrix", 2);
5647
+ output += generateConnectionMatrix(graph);
5648
+ return output;
5649
+ }
5650
+ function getFileCount10(graph) {
5651
+ const files = /* @__PURE__ */ new Set();
5652
+ graph.forEachNode((node, attrs) => {
5653
+ files.add(attrs.filePath);
5654
+ });
5655
+ return files.size;
5656
+ }
5657
+ function getLanguageStats3(graph) {
5658
+ const stats = {};
5659
+ const files = /* @__PURE__ */ new Set();
5660
+ graph.forEachNode((node, attrs) => {
5661
+ if (!files.has(attrs.filePath)) {
5662
+ files.add(attrs.filePath);
5663
+ const ext = attrs.filePath.toLowerCase();
5664
+ let lang;
5665
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) {
5666
+ lang = "TypeScript";
5667
+ } else if (ext.endsWith(".py")) {
5668
+ lang = "Python";
5669
+ } else if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) {
5670
+ lang = "JavaScript";
5671
+ } else if (ext.endsWith(".go")) {
5672
+ lang = "Go";
5673
+ } else {
5674
+ lang = "Other";
5675
+ }
5676
+ stats[lang] = (stats[lang] || 0) + 1;
5677
+ }
5678
+ });
5679
+ return stats;
5680
+ }
5681
+ function generateProjectOverview(graph) {
5682
+ const fileCount = getFileCount10(graph);
5683
+ const symbolCount = graph.order;
5684
+ const edgeCount = graph.size;
5685
+ const languages2 = getLanguageStats3(graph);
5686
+ let output = "";
5687
+ output += `- **Total files:** ${formatNumber(fileCount)}
5688
+ `;
5689
+ output += `- **Total symbols:** ${formatNumber(symbolCount)}
5690
+ `;
5691
+ output += `- **Total edges:** ${formatNumber(edgeCount)}
5692
+ `;
5693
+ if (Object.keys(languages2).length > 0) {
5694
+ output += "\n**Language breakdown:**\n\n";
5695
+ for (const [lang, count] of Object.entries(languages2).sort((a, b) => b[1] - a[1])) {
5696
+ output += `- ${lang}: ${count} files
5697
+ `;
5698
+ }
5699
+ }
5700
+ output += "\n";
5701
+ return output;
5702
+ }
5703
+ function getFileInfo(graph) {
5704
+ const fileMap = /* @__PURE__ */ new Map();
5705
+ graph.forEachNode((node, attrs) => {
5706
+ if (!fileMap.has(attrs.filePath)) {
5707
+ fileMap.set(attrs.filePath, {
5708
+ filePath: attrs.filePath,
5709
+ language: getLanguageFromPath3(attrs.filePath),
5710
+ symbols: [],
5711
+ importsFrom: [],
5712
+ importedBy: [],
5713
+ incomingEdges: 0,
5714
+ outgoingEdges: 0
5715
+ });
5716
+ }
5717
+ const info = fileMap.get(attrs.filePath);
5718
+ if (attrs.name !== "__file__") {
5719
+ info.symbols.push({
5720
+ name: attrs.name,
5721
+ kind: attrs.kind,
5722
+ line: attrs.startLine
5723
+ });
5724
+ }
5725
+ });
5726
+ const fileEdges = /* @__PURE__ */ new Map();
5727
+ const fileEdgesReverse = /* @__PURE__ */ new Map();
5728
+ graph.forEachEdge((edge, attrs, source, target) => {
5729
+ const sourceAttrs = graph.getNodeAttributes(source);
5730
+ const targetAttrs = graph.getNodeAttributes(target);
5731
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
5732
+ if (!fileEdges.has(sourceAttrs.filePath)) {
5733
+ fileEdges.set(sourceAttrs.filePath, /* @__PURE__ */ new Set());
5734
+ }
5735
+ fileEdges.get(sourceAttrs.filePath).add(targetAttrs.filePath);
5736
+ if (!fileEdgesReverse.has(targetAttrs.filePath)) {
5737
+ fileEdgesReverse.set(targetAttrs.filePath, /* @__PURE__ */ new Set());
5738
+ }
5739
+ fileEdgesReverse.get(targetAttrs.filePath).add(sourceAttrs.filePath);
5740
+ }
5741
+ });
5742
+ for (const [filePath, info] of fileMap.entries()) {
5743
+ const importsFrom = fileEdges.get(filePath);
5744
+ const importedBy = fileEdgesReverse.get(filePath);
5745
+ info.importsFrom = importsFrom ? Array.from(importsFrom) : [];
5746
+ info.importedBy = importedBy ? Array.from(importedBy) : [];
5747
+ info.outgoingEdges = info.importsFrom.length;
5748
+ info.incomingEdges = info.importedBy.length;
5749
+ }
5750
+ return Array.from(fileMap.values());
5751
+ }
5752
+ function getLanguageFromPath3(filePath) {
5753
+ const ext = filePath.toLowerCase();
5754
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) return "TypeScript";
5755
+ if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) return "JavaScript";
5756
+ if (ext.endsWith(".py")) return "Python";
5757
+ if (ext.endsWith(".go")) return "Go";
5758
+ return "Other";
5759
+ }
5760
+ function generateCompleteFileIndex(graph) {
5761
+ const fileInfos = getFileInfo(graph);
5762
+ if (fileInfos.length === 0) {
5763
+ return "No files detected.\n\n";
5764
+ }
5765
+ fileInfos.sort((a, b) => a.filePath.localeCompare(b.filePath));
5766
+ const dirGroups = /* @__PURE__ */ new Map();
5767
+ for (const info of fileInfos) {
5768
+ const dir = dirname11(info.filePath);
5769
+ const topDir = dir === "." ? "root" : dir.split("/")[0];
5770
+ if (!dirGroups.has(topDir)) {
5771
+ dirGroups.set(topDir, []);
5772
+ }
5773
+ dirGroups.get(topDir).push(info);
5774
+ }
5775
+ let output = "";
5776
+ for (const [dir, files] of Array.from(dirGroups.entries()).sort()) {
5777
+ output += header(dir === "root" ? "Root Directory" : `${dir}/`, 3);
5778
+ for (const file of files) {
5779
+ output += header(file.filePath, 4);
5780
+ output += `- **Language:** ${file.language}
5781
+ `;
5782
+ output += `- **Symbols (${file.symbols.length}):** `;
5783
+ if (file.symbols.length === 0) {
5784
+ output += "None\n";
5785
+ } else if (file.symbols.length <= 10) {
5786
+ output += file.symbols.map((s) => s.name).join(", ") + "\n";
5787
+ } else {
5788
+ output += file.symbols.slice(0, 10).map((s) => s.name).join(", ");
5789
+ output += `, ... and ${file.symbols.length - 10} more
5790
+ `;
5791
+ }
5792
+ if (file.importsFrom.length > 0) {
5793
+ output += `- **Imports from (${file.importsFrom.length}):** `;
5794
+ if (file.importsFrom.length <= 5) {
5795
+ output += file.importsFrom.map((f) => code(f)).join(", ") + "\n";
5796
+ } else {
5797
+ output += file.importsFrom.slice(0, 5).map((f) => code(f)).join(", ");
5798
+ output += `, ... and ${file.importsFrom.length - 5} more
5799
+ `;
5800
+ }
5801
+ }
5802
+ if (file.importedBy.length > 0) {
5803
+ output += `- **Imported by (${file.importedBy.length}):** `;
5804
+ if (file.importedBy.length <= 5) {
5805
+ output += file.importedBy.map((f) => code(f)).join(", ") + "\n";
5806
+ } else {
5807
+ output += file.importedBy.slice(0, 5).map((f) => code(f)).join(", ");
5808
+ output += `, ... and ${file.importedBy.length - 5} more
5809
+ `;
5810
+ }
5811
+ }
5812
+ output += `- **Connections:** ${file.incomingEdges} inbound, ${file.outgoingEdges} outbound
5813
+
5814
+ `;
5815
+ }
5816
+ }
5817
+ return output;
5818
+ }
5819
+ function generateCompleteSymbolIndex(graph) {
5820
+ const symbolsByKind = /* @__PURE__ */ new Map();
5821
+ graph.forEachNode((node, attrs) => {
5822
+ if (attrs.name === "__file__") return;
5823
+ if (!symbolsByKind.has(attrs.kind)) {
5824
+ symbolsByKind.set(attrs.kind, []);
5825
+ }
5826
+ symbolsByKind.get(attrs.kind).push({
5827
+ name: attrs.name,
5828
+ filePath: attrs.filePath,
5829
+ line: attrs.startLine
5830
+ });
5831
+ });
5832
+ if (symbolsByKind.size === 0) {
5833
+ return "No symbols detected.\n\n";
5834
+ }
5835
+ let output = "";
5836
+ const sortedKinds = Array.from(symbolsByKind.entries()).sort((a, b) => a[0].localeCompare(b[0]));
5837
+ for (const [kind, symbols] of sortedKinds) {
5838
+ output += header(`${capitalizeKind2(kind)}s (${symbols.length})`, 3);
5839
+ const sorted = symbols.sort((a, b) => a.name.localeCompare(b.name));
5840
+ const limit = 100;
5841
+ const items = sorted.slice(0, limit).map((s) => {
5842
+ return `${code(s.name)} \u2014 ${code(s.filePath)}:${s.line}`;
5843
+ });
5844
+ output += unorderedList(items);
5845
+ if (symbols.length > limit) {
5846
+ output += `... and ${symbols.length - limit} more.
5847
+
5848
+ `;
5849
+ }
5850
+ }
5851
+ return output;
5852
+ }
5853
+ function capitalizeKind2(kind) {
5854
+ const map = {
5855
+ function: "Function",
5856
+ class: "Class",
5857
+ variable: "Variable",
5858
+ constant: "Constant",
5859
+ type_alias: "Type",
5860
+ interface: "Interface",
5861
+ enum: "Enum",
5862
+ import: "Import",
5863
+ export: "Export",
5864
+ method: "Method",
5865
+ property: "Property",
5866
+ decorator: "Decorator",
5867
+ module: "Module"
5868
+ };
5869
+ return map[kind] || kind;
5870
+ }
5871
+ function generateCompleteEdgeList(graph) {
5872
+ const fileEdges = /* @__PURE__ */ new Map();
5873
+ graph.forEachEdge((edge, attrs, source, target) => {
5874
+ const sourceAttrs = graph.getNodeAttributes(source);
5875
+ const targetAttrs = graph.getNodeAttributes(target);
5876
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
5877
+ if (!fileEdges.has(sourceAttrs.filePath)) {
5878
+ fileEdges.set(sourceAttrs.filePath, []);
5879
+ }
5880
+ const edgeDesc = `${sourceAttrs.filePath} \u2192 ${targetAttrs.filePath}`;
5881
+ if (!fileEdges.get(sourceAttrs.filePath).includes(edgeDesc)) {
5882
+ fileEdges.get(sourceAttrs.filePath).push(edgeDesc);
5883
+ }
5884
+ }
5885
+ });
5886
+ if (fileEdges.size === 0) {
5887
+ return "No cross-file edges detected.\n\n";
5888
+ }
5889
+ let output = `Total cross-file edges: ${graph.size}
5890
+
5891
+ `;
5892
+ const sortedEdges = Array.from(fileEdges.entries()).sort((a, b) => a[0].localeCompare(b[0]));
5893
+ const limit = 50;
5894
+ for (const [sourceFile, edges] of sortedEdges.slice(0, limit)) {
5895
+ output += header(sourceFile, 3);
5896
+ output += unorderedList(edges.map((e) => e.replace(`${sourceFile} \u2192 `, "")));
5897
+ }
5898
+ if (sortedEdges.length > limit) {
5899
+ output += `... and ${sortedEdges.length - limit} more source files with edges.
5900
+
5901
+ `;
5902
+ }
5903
+ return output;
5904
+ }
5905
+ function generateConnectionMatrix(graph) {
5906
+ const dirEdges = /* @__PURE__ */ new Map();
5907
+ const allDirs = /* @__PURE__ */ new Set();
5908
+ graph.forEachEdge((edge, attrs, source, target) => {
5909
+ const sourceAttrs = graph.getNodeAttributes(source);
5910
+ const targetAttrs = graph.getNodeAttributes(target);
5911
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
5912
+ const sourceDir = getTopLevelDir2(sourceAttrs.filePath);
5913
+ const targetDir = getTopLevelDir2(targetAttrs.filePath);
5914
+ if (sourceDir && targetDir) {
5915
+ allDirs.add(sourceDir);
5916
+ allDirs.add(targetDir);
5917
+ if (!dirEdges.has(sourceDir)) {
5918
+ dirEdges.set(sourceDir, /* @__PURE__ */ new Map());
5919
+ }
5920
+ const targetMap = dirEdges.get(sourceDir);
5921
+ targetMap.set(targetDir, (targetMap.get(targetDir) || 0) + 1);
5922
+ }
5923
+ }
5924
+ });
5925
+ if (allDirs.size === 0) {
5926
+ return "No directory structure detected.\n\n";
5927
+ }
5928
+ const sortedDirs = Array.from(allDirs).sort();
5929
+ let output = "Compact matrix showing which directories depend on which:\n\n";
5930
+ output += codeBlock(buildMatrixString(sortedDirs, dirEdges), "");
5931
+ return output;
5932
+ }
5933
+ function buildMatrixString(dirs, edges) {
5934
+ if (dirs.length === 0) return "No directories";
5935
+ let result = " ";
5936
+ for (const dir of dirs) {
5937
+ result += dir.padEnd(10, " ").substring(0, 10);
5938
+ }
5939
+ result += "\n";
5940
+ for (const sourceDir of dirs) {
5941
+ result += sourceDir.padEnd(10, " ").substring(0, 10) + " ";
5942
+ for (const targetDir of dirs) {
5943
+ if (sourceDir === targetDir) {
5944
+ result += "- ";
5945
+ } else {
5946
+ const count = edges.get(sourceDir)?.get(targetDir) || 0;
5947
+ if (count > 0) {
5948
+ result += "\u2192 ";
5949
+ } else {
5950
+ const reverseCount = edges.get(targetDir)?.get(sourceDir) || 0;
5951
+ if (reverseCount > 0) {
5952
+ result += "\u2190 ";
5953
+ } else {
5954
+ result += " ";
5955
+ }
5956
+ }
5957
+ }
5958
+ }
5959
+ result += "\n";
5960
+ }
5961
+ return result;
5962
+ }
5963
+ function getTopLevelDir2(filePath) {
5964
+ const parts = filePath.split("/");
5965
+ if (parts.length < 2) {
5966
+ return null;
5967
+ }
5968
+ if (parts[0] === "src" && parts.length >= 2) {
5969
+ return parts.length >= 3 ? `${parts[0]}/${parts[1]}` : parts[0];
5970
+ }
5971
+ const firstDir = parts[0];
5972
+ if (firstDir.includes("test") || firstDir.includes("__tests__") || firstDir === "node_modules" || firstDir === "dist" || firstDir === "build") {
5973
+ return null;
5974
+ }
5975
+ return parts[0];
5976
+ }
5977
+
5978
+ // src/docs/status.ts
5979
+ import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
5980
+ import { join as join9 } from "path";
5981
+ function generateStatus(graph, projectRoot, version) {
5982
+ let output = "";
5983
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5984
+ const fileCount = getFileCount11(graph);
5985
+ output += timestamp(version, now, fileCount, graph.order);
5986
+ output += header("Project Status");
5987
+ output += "TODO/FIXME/HACK inventory showing what's implemented vs pending.\n\n";
5988
+ output += header("Status Summary", 2);
5989
+ output += generateStatusSummary(projectRoot, graph);
5990
+ output += header("TODOs by File", 2);
5991
+ output += generateTodosByFile(projectRoot, graph);
5992
+ output += header("FIXMEs (Urgent)", 2);
5993
+ output += generateFixmes(projectRoot, graph);
5994
+ output += header("HACKs (Technical Debt)", 2);
5995
+ output += generateHacks(projectRoot, graph);
5996
+ output += header("Priority Matrix", 2);
5997
+ output += generatePriorityMatrix(projectRoot, graph);
5998
+ output += header("Deprecated Items", 2);
5999
+ output += generateDeprecated(projectRoot, graph);
6000
+ output += header("Implementation Completeness", 2);
6001
+ output += generateCompleteness(projectRoot, graph);
6002
+ return output;
6003
+ }
6004
+ function getFileCount11(graph) {
6005
+ const files = /* @__PURE__ */ new Set();
6006
+ graph.forEachNode((node, attrs) => {
6007
+ files.add(attrs.filePath);
6008
+ });
6009
+ return files.size;
6010
+ }
6011
+ function extractComments(projectRoot, filePath) {
6012
+ const comments = [];
6013
+ const fullPath = join9(projectRoot, filePath);
6014
+ if (!existsSync6(fullPath)) {
6015
+ return comments;
6016
+ }
6017
+ try {
6018
+ const content = readFileSync4(fullPath, "utf-8");
6019
+ const lines = content.split("\n");
6020
+ const patterns = [
6021
+ { type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
6022
+ { type: "FIXME", regex: /(?:\/\/|#|\/\*)\s*FIXME:?\s*(.+)/i },
6023
+ { type: "HACK", regex: /(?:\/\/|#|\/\*)\s*HACK:?\s*(.+)/i },
6024
+ { type: "XXX", regex: /(?:\/\/|#|\/\*)\s*XXX:?\s*(.+)/i },
6025
+ { type: "NOTE", regex: /(?:\/\/|#|\/\*)\s*NOTE:?\s*(.+)/i },
6026
+ { type: "OPTIMIZE", regex: /(?:\/\/|#|\/\*)\s*OPTIMIZE:?\s*(.+)/i },
6027
+ { type: "DEPRECATED", regex: /(?:\/\/|#|\/\*)\s*DEPRECATED:?\s*(.+)/i }
6028
+ ];
6029
+ for (let i = 0; i < lines.length; i++) {
6030
+ const line = lines[i];
6031
+ for (const pattern of patterns) {
6032
+ const match = line.match(pattern.regex);
6033
+ if (match) {
6034
+ comments.push({
6035
+ type: pattern.type,
6036
+ file: filePath,
6037
+ line: i + 1,
6038
+ text: match[1].trim().replace(/\*\/.*$/, "").trim()
6039
+ });
6040
+ break;
6041
+ }
6042
+ }
6043
+ }
6044
+ } catch (err) {
6045
+ return comments;
6046
+ }
6047
+ return comments;
6048
+ }
6049
+ function getAllComments(projectRoot, graph) {
6050
+ const allComments = [];
6051
+ const files = /* @__PURE__ */ new Set();
6052
+ graph.forEachNode((node, attrs) => {
6053
+ files.add(attrs.filePath);
6054
+ });
6055
+ for (const file of files) {
6056
+ const comments = extractComments(projectRoot, file);
6057
+ allComments.push(...comments);
6058
+ }
6059
+ return allComments;
6060
+ }
6061
+ function generateStatusSummary(projectRoot, graph) {
6062
+ const comments = getAllComments(projectRoot, graph);
6063
+ const counts = {
6064
+ TODO: 0,
6065
+ FIXME: 0,
6066
+ HACK: 0,
6067
+ XXX: 0,
6068
+ NOTE: 0,
6069
+ OPTIMIZE: 0,
6070
+ DEPRECATED: 0
6071
+ };
6072
+ for (const comment of comments) {
6073
+ counts[comment.type]++;
6074
+ }
6075
+ let output = "";
6076
+ output += `- **Total TODOs:** ${formatNumber(counts.TODO)}
6077
+ `;
6078
+ output += `- **Total FIXMEs:** ${formatNumber(counts.FIXME)}
6079
+ `;
6080
+ output += `- **Total HACKs:** ${formatNumber(counts.HACK)}
6081
+ `;
6082
+ if (counts.XXX > 0) {
6083
+ output += `- **Total XXXs:** ${formatNumber(counts.XXX)}
6084
+ `;
6085
+ }
6086
+ if (counts.NOTE > 0) {
6087
+ output += `- **Total NOTEs:** ${formatNumber(counts.NOTE)}
6088
+ `;
6089
+ }
6090
+ if (counts.OPTIMIZE > 0) {
6091
+ output += `- **Total OPTIMIZEs:** ${formatNumber(counts.OPTIMIZE)}
6092
+ `;
6093
+ }
6094
+ if (counts.DEPRECATED > 0) {
6095
+ output += `- **Total DEPRECATEDs:** ${formatNumber(counts.DEPRECATED)}
6096
+ `;
6097
+ }
6098
+ output += "\n";
6099
+ return output;
6100
+ }
6101
+ function generateTodosByFile(projectRoot, graph) {
6102
+ const comments = getAllComments(projectRoot, graph);
6103
+ const todos = comments.filter((c) => c.type === "TODO");
6104
+ if (todos.length === 0) {
6105
+ return "\u2705 No TODOs found.\n\n";
6106
+ }
6107
+ const fileGroups = /* @__PURE__ */ new Map();
6108
+ for (const todo of todos) {
6109
+ if (!fileGroups.has(todo.file)) {
6110
+ fileGroups.set(todo.file, []);
6111
+ }
6112
+ fileGroups.get(todo.file).push(todo);
6113
+ }
6114
+ let output = `Found ${todos.length} TODO${todos.length === 1 ? "" : "s"} across ${fileGroups.size} file${fileGroups.size === 1 ? "" : "s"}:
6115
+
6116
+ `;
6117
+ const sortedFiles = Array.from(fileGroups.entries()).sort((a, b) => a[0].localeCompare(b[0]));
6118
+ for (const [file, fileTodos] of sortedFiles) {
6119
+ output += header(file, 3);
6120
+ const items = fileTodos.map((t) => `[ ] TODO: ${t.text} (line ${t.line})`);
6121
+ output += unorderedList(items);
6122
+ }
6123
+ return output;
6124
+ }
6125
+ function generateFixmes(projectRoot, graph) {
6126
+ const comments = getAllComments(projectRoot, graph);
6127
+ const fixmes = comments.filter((c) => c.type === "FIXME");
6128
+ if (fixmes.length === 0) {
6129
+ return "\u2705 No FIXMEs found.\n\n";
6130
+ }
6131
+ let output = `\u26A0\uFE0F Found ${fixmes.length} FIXME${fixmes.length === 1 ? "" : "s"} (known broken or urgent issues):
6132
+
6133
+ `;
6134
+ fixmes.sort((a, b) => a.file.localeCompare(b.file));
6135
+ const items = fixmes.map((f) => {
6136
+ return `[ ] FIXME: ${f.text} (${code(f.file)}:${f.line})`;
6137
+ });
6138
+ output += unorderedList(items);
6139
+ return output;
6140
+ }
6141
+ function generateHacks(projectRoot, graph) {
6142
+ const comments = getAllComments(projectRoot, graph);
6143
+ const hacks = comments.filter((c) => c.type === "HACK");
6144
+ if (hacks.length === 0) {
6145
+ return "\u2705 No HACKs found.\n\n";
6146
+ }
6147
+ let output = `Found ${hacks.length} HACK${hacks.length === 1 ? "" : "s"} (technical debt - works but needs proper implementation):
6148
+
6149
+ `;
6150
+ hacks.sort((a, b) => a.file.localeCompare(b.file));
6151
+ const items = hacks.map((h) => {
6152
+ return `[ ] HACK: ${h.text} (${code(h.file)}:${h.line})`;
6153
+ });
6154
+ output += unorderedList(items);
6155
+ return output;
6156
+ }
6157
+ function generatePriorityMatrix(projectRoot, graph) {
6158
+ const comments = getAllComments(projectRoot, graph);
6159
+ const fileConnections = /* @__PURE__ */ new Map();
6160
+ graph.forEachEdge((edge, attrs, source, target) => {
6161
+ const sourceAttrs = graph.getNodeAttributes(source);
6162
+ const targetAttrs = graph.getNodeAttributes(target);
6163
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
6164
+ fileConnections.set(sourceAttrs.filePath, (fileConnections.get(sourceAttrs.filePath) || 0) + 1);
6165
+ fileConnections.set(targetAttrs.filePath, (fileConnections.get(targetAttrs.filePath) || 0) + 1);
6166
+ }
6167
+ });
6168
+ const items = [];
6169
+ for (const comment of comments) {
6170
+ if (comment.type === "TODO" || comment.type === "FIXME" || comment.type === "HACK") {
6171
+ const connections = fileConnections.get(comment.file) || 0;
6172
+ let priority = "Low";
6173
+ let priorityScore = 1;
6174
+ if (comment.type === "FIXME") {
6175
+ if (connections > 10) {
6176
+ priority = "\u{1F534} Critical";
6177
+ priorityScore = 4;
6178
+ } else if (connections > 5) {
6179
+ priority = "\u{1F7E1} High";
6180
+ priorityScore = 3;
6181
+ } else {
6182
+ priority = "\u{1F7E2} Medium";
6183
+ priorityScore = 2;
6184
+ }
6185
+ } else if (comment.type === "TODO") {
6186
+ if (connections > 10) {
6187
+ priority = "\u{1F7E1} High";
6188
+ priorityScore = 3;
6189
+ } else if (connections > 5) {
6190
+ priority = "\u{1F7E2} Medium";
6191
+ priorityScore = 2;
6192
+ } else {
6193
+ priority = "\u26AA Low";
6194
+ priorityScore = 1;
6195
+ }
6196
+ } else if (comment.type === "HACK") {
6197
+ if (connections > 10) {
6198
+ priority = "\u{1F7E1} High";
6199
+ priorityScore = 3;
6200
+ } else {
6201
+ priority = "\u{1F7E2} Medium";
6202
+ priorityScore = 2;
6203
+ }
6204
+ }
6205
+ items.push({
6206
+ comment,
6207
+ connections,
6208
+ priority
6209
+ });
6210
+ }
6211
+ }
6212
+ if (items.length === 0) {
6213
+ return "No items to prioritize.\n\n";
6214
+ }
6215
+ items.sort((a, b) => {
6216
+ const priorityOrder = { "\u{1F534} Critical": 4, "\u{1F7E1} High": 3, "\u{1F7E2} Medium": 2, "\u26AA Low": 1 };
6217
+ const aPriority = priorityOrder[a.priority] || 0;
6218
+ const bPriority = priorityOrder[b.priority] || 0;
6219
+ if (aPriority !== bPriority) {
6220
+ return bPriority - aPriority;
6221
+ }
6222
+ return b.connections - a.connections;
6223
+ });
6224
+ let output = "Items prioritized by type and file connections:\n\n";
6225
+ const headers = ["Type", "File", "Line", "Connections", "Priority"];
6226
+ const rows = items.slice(0, 20).map((item) => [
6227
+ item.comment.type,
6228
+ `\`${item.comment.file}\``,
6229
+ item.comment.line.toString(),
6230
+ formatNumber(item.connections),
6231
+ item.priority
6232
+ ]);
6233
+ output += table(headers, rows);
6234
+ if (items.length > 20) {
6235
+ output += `... and ${items.length - 20} more items.
6236
+
6237
+ `;
6238
+ }
6239
+ return output;
6240
+ }
6241
+ function generateDeprecated(projectRoot, graph) {
6242
+ const comments = getAllComments(projectRoot, graph);
6243
+ const deprecated = comments.filter((c) => c.type === "DEPRECATED");
6244
+ if (deprecated.length === 0) {
6245
+ return "\u2705 No deprecated items found.\n\n";
6246
+ }
6247
+ let output = `Found ${deprecated.length} deprecated item${deprecated.length === 1 ? "" : "s"}:
6248
+
6249
+ `;
6250
+ deprecated.sort((a, b) => a.file.localeCompare(b.file));
6251
+ const items = deprecated.map((d) => {
6252
+ return `DEPRECATED: ${d.text} (${code(d.file)}:${d.line})`;
6253
+ });
6254
+ output += unorderedList(items);
6255
+ return output;
6256
+ }
6257
+ function generateCompleteness(projectRoot, graph) {
6258
+ const comments = getAllComments(projectRoot, graph);
6259
+ const fileTodos = /* @__PURE__ */ new Map();
6260
+ const fileSymbols = /* @__PURE__ */ new Map();
6261
+ for (const comment of comments) {
6262
+ if (comment.type === "TODO") {
6263
+ fileTodos.set(comment.file, (fileTodos.get(comment.file) || 0) + 1);
6264
+ }
6265
+ }
6266
+ graph.forEachNode((node, attrs) => {
6267
+ fileSymbols.set(attrs.filePath, (fileSymbols.get(attrs.filePath) || 0) + 1);
6268
+ });
6269
+ const allFiles = /* @__PURE__ */ new Set();
6270
+ graph.forEachNode((node, attrs) => {
6271
+ allFiles.add(attrs.filePath);
6272
+ });
6273
+ const inProgress = [];
6274
+ const complete = [];
6275
+ for (const file of allFiles) {
6276
+ const todoCount = fileTodos.get(file) || 0;
6277
+ const symbolCount = fileSymbols.get(file) || 0;
6278
+ if (symbolCount === 0) continue;
6279
+ const todoRatio = todoCount / symbolCount;
6280
+ if (todoRatio > 0.1) {
6281
+ inProgress.push(file);
6282
+ } else if (todoCount === 0) {
6283
+ complete.push(file);
6284
+ }
6285
+ }
6286
+ let output = "";
6287
+ const totalFiles = allFiles.size;
6288
+ const completePercent = totalFiles > 0 ? (complete.length / totalFiles * 100).toFixed(1) : "0.0";
6289
+ output += `- **Complete files (no TODOs):** ${formatNumber(complete.length)} (${completePercent}%)
6290
+ `;
6291
+ output += `- **In-progress files (many TODOs):** ${formatNumber(inProgress.length)}
6292
+
6293
+ `;
6294
+ if (inProgress.length > 0) {
6295
+ output += "**Files in progress (high TODO ratio):**\n\n";
6296
+ const items = inProgress.slice(0, 10).map((f) => {
6297
+ const todoCount = fileTodos.get(f) || 0;
6298
+ return `${code(f)} (${todoCount} TODOs)`;
6299
+ });
6300
+ output += unorderedList(items);
6301
+ if (inProgress.length > 10) {
6302
+ output += `... and ${inProgress.length - 10} more.
6303
+
6304
+ `;
6305
+ }
6306
+ }
6307
+ const dirTodos = /* @__PURE__ */ new Map();
6308
+ const dirFiles = /* @__PURE__ */ new Map();
6309
+ for (const file of allFiles) {
6310
+ const dir = file.split("/")[0];
6311
+ dirFiles.set(dir, (dirFiles.get(dir) || 0) + 1);
6312
+ const todoCount = fileTodos.get(file) || 0;
6313
+ if (todoCount > 0) {
6314
+ dirTodos.set(dir, (dirTodos.get(dir) || 0) + 1);
6315
+ }
6316
+ }
6317
+ if (dirFiles.size > 1) {
6318
+ output += "**Completeness by directory:**\n\n";
6319
+ const sortedDirs = Array.from(dirFiles.entries()).sort((a, b) => b[1] - a[1]);
6320
+ for (const [dir, fileCount] of sortedDirs) {
6321
+ const todosInDir = dirTodos.get(dir) || 0;
6322
+ const completeInDir = fileCount - todosInDir;
6323
+ const percent = (completeInDir / fileCount * 100).toFixed(1);
6324
+ output += `- **${dir}/**: ${completeInDir}/${fileCount} files complete (${percent}%)
6325
+ `;
6326
+ }
6327
+ output += "\n";
6328
+ }
6329
+ return output;
6330
+ }
6331
+
6332
+ // src/docs/metadata.ts
6333
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync } from "fs";
6334
+ import { join as join10 } from "path";
6335
+ function loadMetadata(outputDir) {
6336
+ const metadataPath = join10(outputDir, "metadata.json");
6337
+ if (!existsSync7(metadataPath)) {
6338
+ return null;
6339
+ }
6340
+ try {
6341
+ const content = readFileSync5(metadataPath, "utf-8");
6342
+ return JSON.parse(content);
6343
+ } catch (err) {
6344
+ console.error("Failed to load metadata:", err);
6345
+ return null;
6346
+ }
6347
+ }
6348
+ function saveMetadata(outputDir, metadata) {
6349
+ const metadataPath = join10(outputDir, "metadata.json");
6350
+ writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
6351
+ }
6352
+ function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
6353
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6354
+ const documents = {};
6355
+ for (const docType of docTypes) {
6356
+ const fileName = docType === "architecture" ? "ARCHITECTURE.md" : docType === "conventions" ? "CONVENTIONS.md" : docType === "dependencies" ? "DEPENDENCIES.md" : docType === "onboarding" ? "ONBOARDING.md" : docType === "files" ? "FILES.md" : docType === "api_surface" ? "API_SURFACE.md" : docType === "errors" ? "ERRORS.md" : docType === "tests" ? "TESTS.md" : docType === "history" ? "HISTORY.md" : docType === "current" ? "CURRENT.md" : docType === "status" ? "STATUS.md" : `${docType.toUpperCase()}.md`;
6357
+ documents[docType] = {
6358
+ generated_at: now,
6359
+ file: fileName
6360
+ };
6361
+ }
6362
+ return {
6363
+ version,
6364
+ generated_at: now,
6365
+ project_path: projectPath,
6366
+ file_count: fileCount,
6367
+ symbol_count: symbolCount,
6368
+ edge_count: edgeCount,
6369
+ documents
6370
+ };
6371
+ }
6372
+ function updateMetadata(existing, docTypes, fileCount, symbolCount, edgeCount) {
6373
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6374
+ for (const docType of docTypes) {
6375
+ if (existing.documents[docType]) {
6376
+ existing.documents[docType].generated_at = now;
6377
+ }
6378
+ }
6379
+ existing.file_count = fileCount;
6380
+ existing.symbol_count = symbolCount;
6381
+ existing.edge_count = edgeCount;
6382
+ existing.generated_at = now;
6383
+ return existing;
6384
+ }
6385
+
6386
+ // src/docs/generator.ts
6387
+ async function generateDocs(graph, projectRoot, version, parseTime, options) {
6388
+ const startTime = Date.now();
6389
+ const generated = [];
6390
+ const errors = [];
6391
+ try {
6392
+ if (!existsSync8(options.outputDir)) {
6393
+ mkdirSync(options.outputDir, { recursive: true });
6394
+ if (options.verbose) {
6395
+ console.log(`Created output directory: ${options.outputDir}`);
6396
+ }
6397
+ }
6398
+ let docsToGenerate = options.include;
6399
+ if (options.update && options.only) {
6400
+ docsToGenerate = options.only;
6401
+ }
6402
+ if (docsToGenerate.includes("all")) {
6403
+ docsToGenerate = [
6404
+ "architecture",
6405
+ "conventions",
6406
+ "dependencies",
6407
+ "onboarding",
6408
+ "files",
6409
+ "api_surface",
6410
+ "errors",
6411
+ "tests",
6412
+ "history",
6413
+ "current",
6414
+ "status"
6415
+ ];
6416
+ }
6417
+ let metadata = null;
6418
+ if (options.update) {
6419
+ metadata = loadMetadata(options.outputDir);
6420
+ }
6421
+ const fileCount = getFileCount12(graph);
6422
+ const symbolCount = graph.order;
6423
+ const edgeCount = graph.size;
6424
+ if (options.format === "markdown") {
6425
+ if (docsToGenerate.includes("architecture")) {
6426
+ try {
6427
+ if (options.verbose) console.log("Generating ARCHITECTURE.md...");
6428
+ const content = generateArchitecture(graph, projectRoot, version, parseTime);
6429
+ const filePath = join11(options.outputDir, "ARCHITECTURE.md");
6430
+ writeFileSync2(filePath, content, "utf-8");
6431
+ generated.push("ARCHITECTURE.md");
6432
+ } catch (err) {
6433
+ errors.push(`Failed to generate ARCHITECTURE.md: ${err}`);
6434
+ }
6435
+ }
6436
+ if (docsToGenerate.includes("conventions")) {
6437
+ try {
6438
+ if (options.verbose) console.log("Generating CONVENTIONS.md...");
6439
+ const content = generateConventions(graph, projectRoot, version);
6440
+ const filePath = join11(options.outputDir, "CONVENTIONS.md");
6441
+ writeFileSync2(filePath, content, "utf-8");
6442
+ generated.push("CONVENTIONS.md");
6443
+ } catch (err) {
6444
+ errors.push(`Failed to generate CONVENTIONS.md: ${err}`);
6445
+ }
6446
+ }
6447
+ if (docsToGenerate.includes("dependencies")) {
6448
+ try {
6449
+ if (options.verbose) console.log("Generating DEPENDENCIES.md...");
6450
+ const content = generateDependencies(graph, projectRoot, version);
6451
+ const filePath = join11(options.outputDir, "DEPENDENCIES.md");
6452
+ writeFileSync2(filePath, content, "utf-8");
6453
+ generated.push("DEPENDENCIES.md");
6454
+ } catch (err) {
6455
+ errors.push(`Failed to generate DEPENDENCIES.md: ${err}`);
6456
+ }
6457
+ }
6458
+ if (docsToGenerate.includes("onboarding")) {
6459
+ try {
6460
+ if (options.verbose) console.log("Generating ONBOARDING.md...");
6461
+ const content = generateOnboarding(graph, projectRoot, version);
6462
+ const filePath = join11(options.outputDir, "ONBOARDING.md");
6463
+ writeFileSync2(filePath, content, "utf-8");
6464
+ generated.push("ONBOARDING.md");
6465
+ } catch (err) {
6466
+ errors.push(`Failed to generate ONBOARDING.md: ${err}`);
6467
+ }
6468
+ }
6469
+ if (docsToGenerate.includes("files")) {
6470
+ try {
6471
+ if (options.verbose) console.log("Generating FILES.md...");
6472
+ const content = generateFiles(graph, projectRoot, version);
6473
+ const filePath = join11(options.outputDir, "FILES.md");
6474
+ writeFileSync2(filePath, content, "utf-8");
6475
+ generated.push("FILES.md");
6476
+ } catch (err) {
6477
+ errors.push(`Failed to generate FILES.md: ${err}`);
6478
+ }
6479
+ }
6480
+ if (docsToGenerate.includes("api_surface")) {
6481
+ try {
6482
+ if (options.verbose) console.log("Generating API_SURFACE.md...");
6483
+ const content = generateApiSurface(graph, projectRoot, version);
6484
+ const filePath = join11(options.outputDir, "API_SURFACE.md");
6485
+ writeFileSync2(filePath, content, "utf-8");
6486
+ generated.push("API_SURFACE.md");
6487
+ } catch (err) {
6488
+ errors.push(`Failed to generate API_SURFACE.md: ${err}`);
6489
+ }
6490
+ }
6491
+ if (docsToGenerate.includes("errors")) {
6492
+ try {
6493
+ if (options.verbose) console.log("Generating ERRORS.md...");
6494
+ const content = generateErrors(graph, projectRoot, version);
6495
+ const filePath = join11(options.outputDir, "ERRORS.md");
6496
+ writeFileSync2(filePath, content, "utf-8");
6497
+ generated.push("ERRORS.md");
6498
+ } catch (err) {
6499
+ errors.push(`Failed to generate ERRORS.md: ${err}`);
6500
+ }
6501
+ }
6502
+ if (docsToGenerate.includes("tests")) {
6503
+ try {
6504
+ if (options.verbose) console.log("Generating TESTS.md...");
6505
+ const content = generateTests(graph, projectRoot, version);
6506
+ const filePath = join11(options.outputDir, "TESTS.md");
6507
+ writeFileSync2(filePath, content, "utf-8");
6508
+ generated.push("TESTS.md");
6509
+ } catch (err) {
6510
+ errors.push(`Failed to generate TESTS.md: ${err}`);
6511
+ }
6512
+ }
6513
+ if (docsToGenerate.includes("history")) {
6514
+ try {
6515
+ if (options.verbose) console.log("Generating HISTORY.md...");
6516
+ const content = generateHistory(graph, projectRoot, version);
6517
+ const filePath = join11(options.outputDir, "HISTORY.md");
6518
+ writeFileSync2(filePath, content, "utf-8");
6519
+ generated.push("HISTORY.md");
6520
+ } catch (err) {
6521
+ errors.push(`Failed to generate HISTORY.md: ${err}`);
6522
+ }
6523
+ }
6524
+ if (docsToGenerate.includes("current")) {
6525
+ try {
6526
+ if (options.verbose) console.log("Generating CURRENT.md...");
6527
+ const content = generateCurrent(graph, projectRoot, version);
6528
+ const filePath = join11(options.outputDir, "CURRENT.md");
6529
+ writeFileSync2(filePath, content, "utf-8");
6530
+ generated.push("CURRENT.md");
6531
+ } catch (err) {
6532
+ errors.push(`Failed to generate CURRENT.md: ${err}`);
6533
+ }
6534
+ }
6535
+ if (docsToGenerate.includes("status")) {
6536
+ try {
6537
+ if (options.verbose) console.log("Generating STATUS.md...");
6538
+ const content = generateStatus(graph, projectRoot, version);
6539
+ const filePath = join11(options.outputDir, "STATUS.md");
6540
+ writeFileSync2(filePath, content, "utf-8");
6541
+ generated.push("STATUS.md");
6542
+ } catch (err) {
6543
+ errors.push(`Failed to generate STATUS.md: ${err}`);
6544
+ }
6545
+ }
6546
+ } else if (options.format === "json") {
6547
+ errors.push("JSON format not yet supported");
6548
+ }
6549
+ if (metadata && options.update) {
6550
+ metadata = updateMetadata(metadata, docsToGenerate, fileCount, symbolCount, edgeCount);
6551
+ } else {
6552
+ metadata = createMetadata(version, projectRoot, fileCount, symbolCount, edgeCount, docsToGenerate);
4308
6553
  }
4309
6554
  saveMetadata(options.outputDir, metadata);
4310
6555
  if (options.verbose) console.log("Saved metadata.json");
@@ -4326,7 +6571,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
4326
6571
  };
4327
6572
  }
4328
6573
  }
4329
- function getFileCount5(graph) {
6574
+ function getFileCount12(graph) {
4330
6575
  const files = /* @__PURE__ */ new Set();
4331
6576
  graph.forEachNode((node, attrs) => {
4332
6577
  files.add(attrs.filePath);
@@ -4339,13 +6584,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4339
6584
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4340
6585
 
4341
6586
  // src/mcp/tools.ts
4342
- import { dirname as dirname8, join as join12 } from "path";
4343
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
6587
+ import { dirname as dirname12, join as join13 } from "path";
6588
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
4344
6589
 
4345
6590
  // src/mcp/connect.ts
4346
6591
  import simpleGit from "simple-git";
4347
- import { existsSync as existsSync7 } from "fs";
4348
- import { join as join11, basename as basename3, resolve as resolve2 } from "path";
6592
+ import { existsSync as existsSync9 } from "fs";
6593
+ import { join as join12, basename as basename5, resolve as resolve2 } from "path";
4349
6594
  import { tmpdir, homedir } from "os";
4350
6595
  function validateProjectPath(source) {
4351
6596
  const resolved = resolve2(source);
@@ -4358,11 +6603,11 @@ function validateProjectPath(source) {
4358
6603
  "/boot",
4359
6604
  "/proc",
4360
6605
  "/sys",
4361
- join11(homedir(), ".ssh"),
4362
- join11(homedir(), ".gnupg"),
4363
- join11(homedir(), ".aws"),
4364
- join11(homedir(), ".config"),
4365
- join11(homedir(), ".env")
6606
+ join12(homedir(), ".ssh"),
6607
+ join12(homedir(), ".gnupg"),
6608
+ join12(homedir(), ".aws"),
6609
+ join12(homedir(), ".config"),
6610
+ join12(homedir(), ".env")
4366
6611
  ];
4367
6612
  for (const blocked of blockedPaths) {
4368
6613
  if (resolved.startsWith(blocked)) {
@@ -4385,11 +6630,11 @@ async function connectToRepo(source, subdirectory, state) {
4385
6630
  };
4386
6631
  }
4387
6632
  projectName = match[1];
4388
- const reposDir = join11(tmpdir(), "depwire-repos");
4389
- const cloneDir = join11(reposDir, projectName);
6633
+ const reposDir = join12(tmpdir(), "depwire-repos");
6634
+ const cloneDir = join12(reposDir, projectName);
4390
6635
  console.error(`Connecting to GitHub repo: ${source}`);
4391
6636
  const git = simpleGit();
4392
- if (existsSync7(cloneDir)) {
6637
+ if (existsSync9(cloneDir)) {
4393
6638
  console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
4394
6639
  try {
4395
6640
  await git.cwd(cloneDir).pull();
@@ -4407,7 +6652,7 @@ async function connectToRepo(source, subdirectory, state) {
4407
6652
  };
4408
6653
  }
4409
6654
  }
4410
- projectRoot = subdirectory ? join11(cloneDir, subdirectory) : cloneDir;
6655
+ projectRoot = subdirectory ? join12(cloneDir, subdirectory) : cloneDir;
4411
6656
  } else {
4412
6657
  const validation2 = validateProjectPath(source);
4413
6658
  if (!validation2.valid) {
@@ -4416,14 +6661,14 @@ async function connectToRepo(source, subdirectory, state) {
4416
6661
  message: validation2.error
4417
6662
  };
4418
6663
  }
4419
- if (!existsSync7(source)) {
6664
+ if (!existsSync9(source)) {
4420
6665
  return {
4421
6666
  error: "Directory not found",
4422
6667
  message: `Directory does not exist: ${source}`
4423
6668
  };
4424
6669
  }
4425
- projectRoot = subdirectory ? join11(source, subdirectory) : source;
4426
- projectName = basename3(projectRoot);
6670
+ projectRoot = subdirectory ? join12(source, subdirectory) : source;
6671
+ projectName = basename5(projectRoot);
4427
6672
  }
4428
6673
  const validation = validateProjectPath(projectRoot);
4429
6674
  if (!validation.valid) {
@@ -4432,7 +6677,7 @@ async function connectToRepo(source, subdirectory, state) {
4432
6677
  message: validation.error
4433
6678
  };
4434
6679
  }
4435
- if (!existsSync7(projectRoot)) {
6680
+ if (!existsSync9(projectRoot)) {
4436
6681
  return {
4437
6682
  error: "Project root not found",
4438
6683
  message: `Directory does not exist: ${projectRoot}`
@@ -5087,7 +7332,7 @@ function handleGetArchitectureSummary(graph) {
5087
7332
  const dirMap = /* @__PURE__ */ new Map();
5088
7333
  const languageBreakdown = {};
5089
7334
  fileSummary.forEach((f) => {
5090
- const dir = f.filePath.includes("/") ? dirname8(f.filePath) : ".";
7335
+ const dir = f.filePath.includes("/") ? dirname12(f.filePath) : ".";
5091
7336
  if (!dirMap.has(dir)) {
5092
7337
  dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
5093
7338
  }
@@ -5174,8 +7419,8 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
5174
7419
  };
5175
7420
  }
5176
7421
  async function handleGetProjectDocs(docType, state) {
5177
- const docsDir = join12(state.projectRoot, ".depwire");
5178
- if (!existsSync8(docsDir)) {
7422
+ const docsDir = join13(state.projectRoot, ".depwire");
7423
+ if (!existsSync10(docsDir)) {
5179
7424
  const errorMessage = `Project documentation has not been generated yet.
5180
7425
 
5181
7426
  Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
@@ -5205,12 +7450,12 @@ Available document types:
5205
7450
  missing.push(doc);
5206
7451
  continue;
5207
7452
  }
5208
- const filePath = join12(docsDir, metadata.documents[doc].file);
5209
- if (!existsSync8(filePath)) {
7453
+ const filePath = join13(docsDir, metadata.documents[doc].file);
7454
+ if (!existsSync10(filePath)) {
5210
7455
  missing.push(doc);
5211
7456
  continue;
5212
7457
  }
5213
- const content = readFileSync5(filePath, "utf-8");
7458
+ const content = readFileSync6(filePath, "utf-8");
5214
7459
  if (docsToReturn.length > 1) {
5215
7460
  output += `
5216
7461
 
@@ -5235,16 +7480,16 @@ Available document types:
5235
7480
  }
5236
7481
  async function handleUpdateProjectDocs(docType, state) {
5237
7482
  const startTime = Date.now();
5238
- const docsDir = join12(state.projectRoot, ".depwire");
7483
+ const docsDir = join13(state.projectRoot, ".depwire");
5239
7484
  console.error("Regenerating project documentation...");
5240
- const parsedFiles = parseProject(state.projectRoot);
7485
+ const parsedFiles = await parseProject(state.projectRoot);
5241
7486
  const graph = buildGraph(parsedFiles);
5242
7487
  const parseTime = (Date.now() - startTime) / 1e3;
5243
7488
  state.graph = graph;
5244
- const packageJsonPath = join12(__dirname, "../../package.json");
5245
- const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
7489
+ const packageJsonPath = join13(__dirname, "../../package.json");
7490
+ const packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
5246
7491
  const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
5247
- const docsExist = existsSync8(docsDir);
7492
+ const docsExist = existsSync10(docsDir);
5248
7493
  const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
5249
7494
  outputDir: docsDir,
5250
7495
  format: "markdown",