depwire-cli 0.4.0 → 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.
@@ -2639,8 +2639,8 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2639
2639
  }
2640
2640
 
2641
2641
  // src/docs/generator.ts
2642
- import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync7 } from "fs";
2643
- import { join as join10 } from "path";
2642
+ import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync8 } from "fs";
2643
+ import { join as join11 } from "path";
2644
2644
 
2645
2645
  // src/docs/architecture.ts
2646
2646
  import { dirname as dirname6 } from "path";
@@ -4203,146 +4203,2361 @@ function generateDepwireUsage(projectRoot) {
4203
4203
  return output;
4204
4204
  }
4205
4205
 
4206
- // src/docs/metadata.ts
4207
- import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync } from "fs";
4208
- import { join as join9 } from "path";
4209
- function loadMetadata(outputDir) {
4210
- const metadataPath = join9(outputDir, "metadata.json");
4211
- if (!existsSync6(metadataPath)) {
4212
- 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";
4213
4293
  }
4214
- try {
4215
- const content = readFileSync4(metadataPath, "utf-8");
4216
- return JSON.parse(content);
4217
- } catch (err) {
4218
- console.error("Failed to load metadata:", err);
4219
- 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
+ `;
4220
4345
  }
4346
+ return output;
4221
4347
  }
4222
- function saveMetadata(outputDir, metadata) {
4223
- const metadataPath = join9(outputDir, "metadata.json");
4224
- 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;
4225
4386
  }
4226
- function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
4227
- const now = (/* @__PURE__ */ new Date()).toISOString();
4228
- const documents = {};
4229
- for (const docType of docTypes) {
4230
- const fileName = docType === "architecture" ? "ARCHITECTURE.md" : docType === "conventions" ? "CONVENTIONS.md" : docType === "dependencies" ? "DEPENDENCIES.md" : docType === "onboarding" ? "ONBOARDING.md" : `${docType.toUpperCase()}.md`;
4231
- documents[docType] = {
4232
- generated_at: now,
4233
- file: fileName
4234
- };
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";
4235
4392
  }
4236
- return {
4237
- version,
4238
- generated_at: now,
4239
- project_path: projectPath,
4240
- file_count: fileCount,
4241
- symbol_count: symbolCount,
4242
- edge_count: edgeCount,
4243
- documents
4244
- };
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;
4245
4399
  }
4246
- function updateMetadata(existing, docTypes, fileCount, symbolCount, edgeCount) {
4247
- const now = (/* @__PURE__ */ new Date()).toISOString();
4248
- for (const docType of docTypes) {
4249
- if (existing.documents[docType]) {
4250
- 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, []);
4251
4471
  }
4472
+ fileExports.get(exp.filePath).push(exp);
4252
4473
  }
4253
- existing.file_count = fileCount;
4254
- existing.symbol_count = symbolCount;
4255
- existing.edge_count = edgeCount;
4256
- existing.generated_at = now;
4257
- 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;
4258
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}):**
4259
4504
 
4260
- // src/docs/generator.ts
4261
- async function generateDocs(graph, projectRoot, version, parseTime, options) {
4262
- const startTime = Date.now();
4263
- const generated = [];
4264
- const errors = [];
4265
- try {
4266
- if (!existsSync7(options.outputDir)) {
4267
- mkdirSync(options.outputDir, { recursive: true });
4268
- if (options.verbose) {
4269
- console.log(`Created output directory: ${options.outputDir}`);
4270
- }
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;
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:
4558
+
4559
+ `;
4560
+ const fileGroups = /* @__PURE__ */ new Map();
4561
+ for (const exp of unused) {
4562
+ if (!fileGroups.has(exp.filePath)) {
4563
+ fileGroups.set(exp.filePath, []);
4271
4564
  }
4272
- let docsToGenerate = options.include;
4273
- if (options.update && options.only) {
4274
- 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
+ });
4275
4586
  }
4276
- if (docsToGenerate.includes("all")) {
4277
- docsToGenerate = ["architecture", "conventions", "dependencies", "onboarding"];
4587
+ const stats = fileStats.get(attrs.filePath);
4588
+ if (attrs.exported) {
4589
+ stats.exportCount++;
4278
4590
  }
4279
- let metadata = null;
4280
- if (options.update) {
4281
- metadata = loadMetadata(options.outputDir);
4591
+ if (attrs.kind === "export") {
4592
+ stats.reExportCount++;
4282
4593
  }
4283
- const fileCount = getFileCount5(graph);
4284
- const symbolCount = graph.order;
4285
- const edgeCount = graph.size;
4286
- if (options.format === "markdown") {
4287
- if (docsToGenerate.includes("architecture")) {
4288
- try {
4289
- if (options.verbose) console.log("Generating ARCHITECTURE.md...");
4290
- const content = generateArchitecture(graph, projectRoot, version, parseTime);
4291
- const filePath = join10(options.outputDir, "ARCHITECTURE.md");
4292
- writeFileSync2(filePath, content, "utf-8");
4293
- generated.push("ARCHITECTURE.md");
4294
- } catch (err) {
4295
- errors.push(`Failed to generate ARCHITECTURE.md: ${err}`);
4296
- }
4297
- }
4298
- if (docsToGenerate.includes("conventions")) {
4299
- try {
4300
- if (options.verbose) console.log("Generating CONVENTIONS.md...");
4301
- const content = generateConventions(graph, projectRoot, version);
4302
- const filePath = join10(options.outputDir, "CONVENTIONS.md");
4303
- writeFileSync2(filePath, content, "utf-8");
4304
- generated.push("CONVENTIONS.md");
4305
- } catch (err) {
4306
- errors.push(`Failed to generate CONVENTIONS.md: ${err}`);
4307
- }
4308
- }
4309
- if (docsToGenerate.includes("dependencies")) {
4310
- try {
4311
- if (options.verbose) console.log("Generating DEPENDENCIES.md...");
4312
- const content = generateDependencies(graph, projectRoot, version);
4313
- const filePath = join10(options.outputDir, "DEPENDENCIES.md");
4314
- writeFileSync2(filePath, content, "utf-8");
4315
- generated.push("DEPENDENCIES.md");
4316
- } catch (err) {
4317
- errors.push(`Failed to generate DEPENDENCIES.md: ${err}`);
4318
- }
4319
- }
4320
- if (docsToGenerate.includes("onboarding")) {
4321
- try {
4322
- if (options.verbose) console.log("Generating ONBOARDING.md...");
4323
- const content = generateOnboarding(graph, projectRoot, version);
4324
- const filePath = join10(options.outputDir, "ONBOARDING.md");
4325
- writeFileSync2(filePath, content, "utf-8");
4326
- generated.push("ONBOARDING.md");
4327
- } catch (err) {
4328
- errors.push(`Failed to generate ONBOARDING.md: ${err}`);
4329
- }
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);
4330
4602
  }
4331
- } else if (options.format === "json") {
4332
- errors.push("JSON format not yet supported");
4333
4603
  }
4334
- if (metadata && options.update) {
4335
- metadata = updateMetadata(metadata, docsToGenerate, fileCount, symbolCount, edgeCount);
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)));
4336
4633
  } else {
4337
- metadata = createMetadata(version, projectRoot, fileCount, symbolCount, edgeCount, docsToGenerate);
4634
+ output += "\n";
4338
4635
  }
4339
- saveMetadata(options.outputDir, metadata);
4340
- if (options.verbose) console.log("Saved metadata.json");
4341
- const totalTime = Date.now() - startTime;
4342
- return {
4343
- success: errors.length === 0,
4344
- generated,
4345
- errors,
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);
6553
+ }
6554
+ saveMetadata(options.outputDir, metadata);
6555
+ if (options.verbose) console.log("Saved metadata.json");
6556
+ const totalTime = Date.now() - startTime;
6557
+ return {
6558
+ success: errors.length === 0,
6559
+ generated,
6560
+ errors,
4346
6561
  stats: options.stats ? {
4347
6562
  totalTime,
4348
6563
  filesGenerated: generated.length
@@ -4356,7 +6571,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
4356
6571
  };
4357
6572
  }
4358
6573
  }
4359
- function getFileCount5(graph) {
6574
+ function getFileCount12(graph) {
4360
6575
  const files = /* @__PURE__ */ new Set();
4361
6576
  graph.forEachNode((node, attrs) => {
4362
6577
  files.add(attrs.filePath);
@@ -4369,13 +6584,13 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4369
6584
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4370
6585
 
4371
6586
  // src/mcp/tools.ts
4372
- import { dirname as dirname8, join as join12 } from "path";
4373
- import { existsSync as existsSync9, 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";
4374
6589
 
4375
6590
  // src/mcp/connect.ts
4376
6591
  import simpleGit from "simple-git";
4377
- import { existsSync as existsSync8 } from "fs";
4378
- 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";
4379
6594
  import { tmpdir, homedir } from "os";
4380
6595
  function validateProjectPath(source) {
4381
6596
  const resolved = resolve2(source);
@@ -4388,11 +6603,11 @@ function validateProjectPath(source) {
4388
6603
  "/boot",
4389
6604
  "/proc",
4390
6605
  "/sys",
4391
- join11(homedir(), ".ssh"),
4392
- join11(homedir(), ".gnupg"),
4393
- join11(homedir(), ".aws"),
4394
- join11(homedir(), ".config"),
4395
- join11(homedir(), ".env")
6606
+ join12(homedir(), ".ssh"),
6607
+ join12(homedir(), ".gnupg"),
6608
+ join12(homedir(), ".aws"),
6609
+ join12(homedir(), ".config"),
6610
+ join12(homedir(), ".env")
4396
6611
  ];
4397
6612
  for (const blocked of blockedPaths) {
4398
6613
  if (resolved.startsWith(blocked)) {
@@ -4415,11 +6630,11 @@ async function connectToRepo(source, subdirectory, state) {
4415
6630
  };
4416
6631
  }
4417
6632
  projectName = match[1];
4418
- const reposDir = join11(tmpdir(), "depwire-repos");
4419
- const cloneDir = join11(reposDir, projectName);
6633
+ const reposDir = join12(tmpdir(), "depwire-repos");
6634
+ const cloneDir = join12(reposDir, projectName);
4420
6635
  console.error(`Connecting to GitHub repo: ${source}`);
4421
6636
  const git = simpleGit();
4422
- if (existsSync8(cloneDir)) {
6637
+ if (existsSync9(cloneDir)) {
4423
6638
  console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
4424
6639
  try {
4425
6640
  await git.cwd(cloneDir).pull();
@@ -4437,7 +6652,7 @@ async function connectToRepo(source, subdirectory, state) {
4437
6652
  };
4438
6653
  }
4439
6654
  }
4440
- projectRoot = subdirectory ? join11(cloneDir, subdirectory) : cloneDir;
6655
+ projectRoot = subdirectory ? join12(cloneDir, subdirectory) : cloneDir;
4441
6656
  } else {
4442
6657
  const validation2 = validateProjectPath(source);
4443
6658
  if (!validation2.valid) {
@@ -4446,14 +6661,14 @@ async function connectToRepo(source, subdirectory, state) {
4446
6661
  message: validation2.error
4447
6662
  };
4448
6663
  }
4449
- if (!existsSync8(source)) {
6664
+ if (!existsSync9(source)) {
4450
6665
  return {
4451
6666
  error: "Directory not found",
4452
6667
  message: `Directory does not exist: ${source}`
4453
6668
  };
4454
6669
  }
4455
- projectRoot = subdirectory ? join11(source, subdirectory) : source;
4456
- projectName = basename3(projectRoot);
6670
+ projectRoot = subdirectory ? join12(source, subdirectory) : source;
6671
+ projectName = basename5(projectRoot);
4457
6672
  }
4458
6673
  const validation = validateProjectPath(projectRoot);
4459
6674
  if (!validation.valid) {
@@ -4462,7 +6677,7 @@ async function connectToRepo(source, subdirectory, state) {
4462
6677
  message: validation.error
4463
6678
  };
4464
6679
  }
4465
- if (!existsSync8(projectRoot)) {
6680
+ if (!existsSync9(projectRoot)) {
4466
6681
  return {
4467
6682
  error: "Project root not found",
4468
6683
  message: `Directory does not exist: ${projectRoot}`
@@ -5117,7 +7332,7 @@ function handleGetArchitectureSummary(graph) {
5117
7332
  const dirMap = /* @__PURE__ */ new Map();
5118
7333
  const languageBreakdown = {};
5119
7334
  fileSummary.forEach((f) => {
5120
- const dir = f.filePath.includes("/") ? dirname8(f.filePath) : ".";
7335
+ const dir = f.filePath.includes("/") ? dirname12(f.filePath) : ".";
5121
7336
  if (!dirMap.has(dir)) {
5122
7337
  dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
5123
7338
  }
@@ -5204,8 +7419,8 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
5204
7419
  };
5205
7420
  }
5206
7421
  async function handleGetProjectDocs(docType, state) {
5207
- const docsDir = join12(state.projectRoot, ".depwire");
5208
- if (!existsSync9(docsDir)) {
7422
+ const docsDir = join13(state.projectRoot, ".depwire");
7423
+ if (!existsSync10(docsDir)) {
5209
7424
  const errorMessage = `Project documentation has not been generated yet.
5210
7425
 
5211
7426
  Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
@@ -5235,12 +7450,12 @@ Available document types:
5235
7450
  missing.push(doc);
5236
7451
  continue;
5237
7452
  }
5238
- const filePath = join12(docsDir, metadata.documents[doc].file);
5239
- if (!existsSync9(filePath)) {
7453
+ const filePath = join13(docsDir, metadata.documents[doc].file);
7454
+ if (!existsSync10(filePath)) {
5240
7455
  missing.push(doc);
5241
7456
  continue;
5242
7457
  }
5243
- const content = readFileSync5(filePath, "utf-8");
7458
+ const content = readFileSync6(filePath, "utf-8");
5244
7459
  if (docsToReturn.length > 1) {
5245
7460
  output += `
5246
7461
 
@@ -5265,16 +7480,16 @@ Available document types:
5265
7480
  }
5266
7481
  async function handleUpdateProjectDocs(docType, state) {
5267
7482
  const startTime = Date.now();
5268
- const docsDir = join12(state.projectRoot, ".depwire");
7483
+ const docsDir = join13(state.projectRoot, ".depwire");
5269
7484
  console.error("Regenerating project documentation...");
5270
7485
  const parsedFiles = await parseProject(state.projectRoot);
5271
7486
  const graph = buildGraph(parsedFiles);
5272
7487
  const parseTime = (Date.now() - startTime) / 1e3;
5273
7488
  state.graph = graph;
5274
- const packageJsonPath = join12(__dirname, "../../package.json");
5275
- const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
7489
+ const packageJsonPath = join13(__dirname, "../../package.json");
7490
+ const packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
5276
7491
  const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
5277
- const docsExist = existsSync9(docsDir);
7492
+ const docsExist = existsSync10(docsDir);
5278
7493
  const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
5279
7494
  outputDir: docsDir,
5280
7495
  format: "markdown",