depwire-cli 0.2.5 → 0.3.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.
@@ -2364,7 +2364,7 @@ import { fileURLToPath } from "url";
2364
2364
  import { dirname as dirname5, join as join7 } from "path";
2365
2365
  import { WebSocketServer } from "ws";
2366
2366
  var __filename = fileURLToPath(import.meta.url);
2367
- var __dirname = dirname5(__filename);
2367
+ var __dirname2 = dirname5(__filename);
2368
2368
  var activeServer = null;
2369
2369
  async function findAvailablePort(startPort, maxAttempts = 10) {
2370
2370
  const net = await import("net");
@@ -2402,7 +2402,7 @@ async function startVizServer(initialVizData, graph, projectRoot, port = 3333, s
2402
2402
  const availablePort = await findAvailablePort(port);
2403
2403
  const app = express();
2404
2404
  let vizData = initialVizData;
2405
- const publicDir = join7(__dirname, "viz", "public");
2405
+ const publicDir = join7(__dirname2, "viz", "public");
2406
2406
  app.use(express.static(publicDir));
2407
2407
  app.get("/api/graph", (req, res) => {
2408
2408
  res.json(vizData);
@@ -2571,17 +2571,1744 @@ async function updateFileInGraph(graph, projectRoot, relativeFilePath) {
2571
2571
  }
2572
2572
  }
2573
2573
 
2574
+ // src/docs/generator.ts
2575
+ import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync6 } from "fs";
2576
+ import { join as join10 } from "path";
2577
+
2578
+ // src/docs/architecture.ts
2579
+ import { dirname as dirname6 } from "path";
2580
+
2581
+ // src/docs/templates.ts
2582
+ function header(text, level = 1) {
2583
+ return `${"#".repeat(level)} ${text}
2584
+
2585
+ `;
2586
+ }
2587
+ function code(text) {
2588
+ return `\`${text}\``;
2589
+ }
2590
+ function codeBlock(code2, lang = "") {
2591
+ return `\`\`\`${lang}
2592
+ ${code2}
2593
+ \`\`\`
2594
+
2595
+ `;
2596
+ }
2597
+ function unorderedList(items) {
2598
+ return items.map((item) => `- ${item}`).join("\n") + "\n\n";
2599
+ }
2600
+ function orderedList(items) {
2601
+ return items.map((item, i) => `${i + 1}. ${item}`).join("\n") + "\n\n";
2602
+ }
2603
+ function table(headers, rows) {
2604
+ const headerRow = `| ${headers.join(" | ")} |`;
2605
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
2606
+ const dataRows = rows.map((row) => `| ${row.join(" | ")} |`).join("\n");
2607
+ return `${headerRow}
2608
+ ${separator}
2609
+ ${dataRows}
2610
+
2611
+ `;
2612
+ }
2613
+ function blockquote(text) {
2614
+ return `> ${text}
2615
+
2616
+ `;
2617
+ }
2618
+ function timestamp(version, date, fileCount, symbolCount) {
2619
+ return blockquote(`Auto-generated by Depwire ${version} on ${date} | ${fileCount.toLocaleString()} files, ${symbolCount.toLocaleString()} symbols`);
2620
+ }
2621
+ function formatNumber(n) {
2622
+ return n.toLocaleString();
2623
+ }
2624
+ function formatPercent(value, total) {
2625
+ if (total === 0) return "0.0%";
2626
+ return `${(value / total * 100).toFixed(1)}%`;
2627
+ }
2628
+ function impactEmoji(count) {
2629
+ if (count >= 20) return "\u{1F534}";
2630
+ if (count >= 10) return "\u{1F7E1}";
2631
+ if (count >= 5) return "\u{1F7E2}";
2632
+ return "\u26AA";
2633
+ }
2634
+
2635
+ // src/docs/architecture.ts
2636
+ function generateArchitecture(graph, projectRoot, version, parseTime) {
2637
+ const startTime = Date.now();
2638
+ let output = "";
2639
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2640
+ output += timestamp(version, now, getFileCount(graph), graph.order);
2641
+ output += header("Architecture Overview");
2642
+ output += header("Project Summary", 2);
2643
+ output += generateProjectSummary(graph, parseTime);
2644
+ output += header("Module Structure", 2);
2645
+ output += generateModuleStructure(graph);
2646
+ output += header("Entry Points", 2);
2647
+ output += generateEntryPoints(graph);
2648
+ output += header("Hub Files", 2);
2649
+ output += generateHubFiles(graph);
2650
+ output += header("Layer Analysis", 2);
2651
+ output += generateLayerAnalysis(graph);
2652
+ output += header("Circular Dependencies", 2);
2653
+ output += generateCircularDependencies(graph);
2654
+ return output;
2655
+ }
2656
+ function getFileCount(graph) {
2657
+ const files = /* @__PURE__ */ new Set();
2658
+ graph.forEachNode((node, attrs) => {
2659
+ files.add(attrs.filePath);
2660
+ });
2661
+ return files.size;
2662
+ }
2663
+ function getLanguageStats(graph) {
2664
+ const stats = {};
2665
+ const files = /* @__PURE__ */ new Set();
2666
+ graph.forEachNode((node, attrs) => {
2667
+ if (!files.has(attrs.filePath)) {
2668
+ files.add(attrs.filePath);
2669
+ const ext = attrs.filePath.toLowerCase();
2670
+ let lang;
2671
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) {
2672
+ lang = "TypeScript";
2673
+ } else if (ext.endsWith(".py")) {
2674
+ lang = "Python";
2675
+ } else if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) {
2676
+ lang = "JavaScript";
2677
+ } else if (ext.endsWith(".go")) {
2678
+ lang = "Go";
2679
+ } else {
2680
+ lang = "Other";
2681
+ }
2682
+ stats[lang] = (stats[lang] || 0) + 1;
2683
+ }
2684
+ });
2685
+ return stats;
2686
+ }
2687
+ function generateProjectSummary(graph, parseTime) {
2688
+ const fileCount = getFileCount(graph);
2689
+ const symbolCount = graph.order;
2690
+ const edgeCount = graph.size;
2691
+ const languages = getLanguageStats(graph);
2692
+ let output = "";
2693
+ output += `- **Total Files:** ${formatNumber(fileCount)}
2694
+ `;
2695
+ output += `- **Total Symbols:** ${formatNumber(symbolCount)}
2696
+ `;
2697
+ output += `- **Total Edges:** ${formatNumber(edgeCount)}
2698
+ `;
2699
+ output += `- **Parse Time:** ${parseTime.toFixed(1)}s
2700
+ `;
2701
+ if (Object.keys(languages).length > 1) {
2702
+ output += "\n**Languages:**\n\n";
2703
+ const totalFiles = fileCount;
2704
+ for (const [lang, count] of Object.entries(languages).sort((a, b) => b[1] - a[1])) {
2705
+ output += `- ${lang}: ${count} files (${formatPercent(count, totalFiles)})
2706
+ `;
2707
+ }
2708
+ }
2709
+ output += "\n";
2710
+ return output;
2711
+ }
2712
+ function generateModuleStructure(graph) {
2713
+ const dirStats = getDirectoryStats(graph);
2714
+ if (dirStats.length === 0) {
2715
+ return "No module structure detected (single file or flat structure).\n\n";
2716
+ }
2717
+ const headers = ["Directory", "Files", "Symbols", "Connections", "Role"];
2718
+ const rows = dirStats.slice(0, 15).map((dir) => [
2719
+ `\`${dir.name}\``,
2720
+ formatNumber(dir.fileCount),
2721
+ formatNumber(dir.symbolCount),
2722
+ formatNumber(dir.connectionCount),
2723
+ dir.role
2724
+ ]);
2725
+ return table(headers, rows);
2726
+ }
2727
+ function getDirectoryStats(graph) {
2728
+ const dirMap = /* @__PURE__ */ new Map();
2729
+ graph.forEachNode((node, attrs) => {
2730
+ const dir = dirname6(attrs.filePath);
2731
+ if (dir === ".") return;
2732
+ if (!dirMap.has(dir)) {
2733
+ dirMap.set(dir, {
2734
+ name: dir,
2735
+ fileCount: 0,
2736
+ symbolCount: 0,
2737
+ connectionCount: 0,
2738
+ role: "",
2739
+ typeCount: 0,
2740
+ functionCount: 0,
2741
+ outboundEdges: 0,
2742
+ inboundEdges: 0
2743
+ });
2744
+ }
2745
+ const dirStat = dirMap.get(dir);
2746
+ dirStat.symbolCount++;
2747
+ if (attrs.kind === "interface" || attrs.kind === "type_alias") {
2748
+ dirStat.typeCount++;
2749
+ } else if (attrs.kind === "function" || attrs.kind === "method") {
2750
+ dirStat.functionCount++;
2751
+ }
2752
+ });
2753
+ const filesPerDir = /* @__PURE__ */ new Map();
2754
+ graph.forEachNode((node, attrs) => {
2755
+ const dir = dirname6(attrs.filePath);
2756
+ if (!filesPerDir.has(dir)) {
2757
+ filesPerDir.set(dir, /* @__PURE__ */ new Set());
2758
+ }
2759
+ filesPerDir.get(dir).add(attrs.filePath);
2760
+ });
2761
+ filesPerDir.forEach((files, dir) => {
2762
+ if (dirMap.has(dir)) {
2763
+ dirMap.get(dir).fileCount = files.size;
2764
+ }
2765
+ });
2766
+ const dirEdges = /* @__PURE__ */ new Map();
2767
+ graph.forEachEdge((edge, attrs, source, target) => {
2768
+ const sourceAttrs = graph.getNodeAttributes(source);
2769
+ const targetAttrs = graph.getNodeAttributes(target);
2770
+ const sourceDir = dirname6(sourceAttrs.filePath);
2771
+ const targetDir = dirname6(targetAttrs.filePath);
2772
+ if (sourceDir !== targetDir) {
2773
+ if (!dirEdges.has(sourceDir)) {
2774
+ dirEdges.set(sourceDir, { in: 0, out: 0 });
2775
+ }
2776
+ if (!dirEdges.has(targetDir)) {
2777
+ dirEdges.set(targetDir, { in: 0, out: 0 });
2778
+ }
2779
+ dirEdges.get(sourceDir).out++;
2780
+ dirEdges.get(targetDir).in++;
2781
+ }
2782
+ });
2783
+ dirEdges.forEach((edges, dir) => {
2784
+ if (dirMap.has(dir)) {
2785
+ const stat = dirMap.get(dir);
2786
+ stat.inboundEdges = edges.in;
2787
+ stat.outboundEdges = edges.out;
2788
+ stat.connectionCount = edges.in + edges.out;
2789
+ }
2790
+ });
2791
+ dirMap.forEach((dir) => {
2792
+ const typeRatio = dir.symbolCount > 0 ? dir.typeCount / dir.symbolCount : 0;
2793
+ const outboundRatio = dir.connectionCount > 0 ? dir.outboundEdges / dir.connectionCount : 0;
2794
+ const inboundRatio = dir.connectionCount > 0 ? dir.inboundEdges / dir.connectionCount : 0;
2795
+ if (typeRatio > 0.7) {
2796
+ dir.role = "Type definitions";
2797
+ } else if (outboundRatio > 0.7) {
2798
+ dir.role = "Orchestration / Entry points";
2799
+ } else if (inboundRatio > 0.7) {
2800
+ dir.role = "Shared utilities / Foundation";
2801
+ } else {
2802
+ dir.role = "Core logic";
2803
+ }
2804
+ });
2805
+ return Array.from(dirMap.values()).sort((a, b) => b.symbolCount - a.symbolCount);
2806
+ }
2807
+ function generateEntryPoints(graph) {
2808
+ const fileStats = getFileStats(graph);
2809
+ const entryPoints = fileStats.filter((f) => f.outgoingRefs > 0).map((f) => ({
2810
+ ...f,
2811
+ ratio: f.incomingRefs === 0 ? Infinity : f.outgoingRefs / (f.incomingRefs + 1)
2812
+ })).sort((a, b) => b.ratio - a.ratio).slice(0, 5);
2813
+ if (entryPoints.length === 0) {
2814
+ return "No clear entry points detected.\n\n";
2815
+ }
2816
+ const headers = ["File", "Outgoing", "Incoming", "Ratio"];
2817
+ const rows = entryPoints.map((f) => [
2818
+ `\`${f.filePath}\``,
2819
+ formatNumber(f.outgoingRefs),
2820
+ formatNumber(f.incomingRefs),
2821
+ f.ratio === Infinity ? "\u221E" : f.ratio.toFixed(1)
2822
+ ]);
2823
+ return table(headers, rows);
2824
+ }
2825
+ function generateHubFiles(graph) {
2826
+ const fileStats = getFileStats(graph);
2827
+ const hubFiles = fileStats.sort((a, b) => b.incomingRefs - a.incomingRefs).slice(0, 10);
2828
+ if (hubFiles.length === 0 || hubFiles[0].incomingRefs === 0) {
2829
+ return "No hub files detected.\n\n";
2830
+ }
2831
+ const headers = ["File", "Dependents", "Symbols"];
2832
+ const rows = hubFiles.map((f) => [
2833
+ `\`${f.filePath}\``,
2834
+ formatNumber(f.incomingRefs),
2835
+ formatNumber(f.symbolCount)
2836
+ ]);
2837
+ return table(headers, rows);
2838
+ }
2839
+ function getFileStats(graph) {
2840
+ const fileMap = /* @__PURE__ */ new Map();
2841
+ graph.forEachNode((node, attrs) => {
2842
+ if (!fileMap.has(attrs.filePath)) {
2843
+ fileMap.set(attrs.filePath, {
2844
+ symbolCount: 0,
2845
+ incomingRefs: /* @__PURE__ */ new Set(),
2846
+ outgoingRefs: /* @__PURE__ */ new Set()
2847
+ });
2848
+ }
2849
+ fileMap.get(attrs.filePath).symbolCount++;
2850
+ });
2851
+ graph.forEachEdge((edge, attrs, source, target) => {
2852
+ const sourceAttrs = graph.getNodeAttributes(source);
2853
+ const targetAttrs = graph.getNodeAttributes(target);
2854
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
2855
+ const sourceFile = fileMap.get(sourceAttrs.filePath);
2856
+ const targetFile = fileMap.get(targetAttrs.filePath);
2857
+ if (sourceFile) {
2858
+ sourceFile.outgoingRefs.add(targetAttrs.filePath);
2859
+ }
2860
+ if (targetFile) {
2861
+ targetFile.incomingRefs.add(sourceAttrs.filePath);
2862
+ }
2863
+ }
2864
+ });
2865
+ const result = [];
2866
+ for (const [filePath, data] of fileMap.entries()) {
2867
+ result.push({
2868
+ filePath,
2869
+ symbolCount: data.symbolCount,
2870
+ incomingRefs: data.incomingRefs.size,
2871
+ outgoingRefs: data.outgoingRefs.size
2872
+ });
2873
+ }
2874
+ return result;
2875
+ }
2876
+ function generateLayerAnalysis(graph) {
2877
+ const dirStats = getDirectoryStats(graph);
2878
+ if (dirStats.length === 0) {
2879
+ return "No layered architecture detected (flat or single-file project).\n\n";
2880
+ }
2881
+ const foundation = dirStats.filter((d) => d.inboundEdges > d.outboundEdges * 2);
2882
+ const orchestration = dirStats.filter((d) => d.outboundEdges > d.inboundEdges * 2);
2883
+ const core = dirStats.filter((d) => !foundation.includes(d) && !orchestration.includes(d));
2884
+ let output = "";
2885
+ if (foundation.length > 0) {
2886
+ output += "**Foundation Layer** (mostly imported by others):\n\n";
2887
+ output += unorderedList(foundation.map((d) => `\`${d.name}\` \u2014 ${d.role}`));
2888
+ }
2889
+ if (core.length > 0) {
2890
+ output += "**Core Layer** (balanced dependencies):\n\n";
2891
+ output += unorderedList(core.map((d) => `\`${d.name}\` \u2014 ${d.role}`));
2892
+ }
2893
+ if (orchestration.length > 0) {
2894
+ output += "**Orchestration Layer** (mostly imports from others):\n\n";
2895
+ output += unorderedList(orchestration.map((d) => `\`${d.name}\` \u2014 ${d.role}`));
2896
+ }
2897
+ return output;
2898
+ }
2899
+ function generateCircularDependencies(graph) {
2900
+ const cycles = detectCycles(graph);
2901
+ if (cycles.length === 0) {
2902
+ return "\u2705 No circular dependencies detected.\n\n";
2903
+ }
2904
+ let output = `\u26A0\uFE0F Found ${cycles.length} circular ${cycles.length === 1 ? "dependency" : "dependencies"}:
2905
+
2906
+ `;
2907
+ for (let i = 0; i < Math.min(cycles.length, 10); i++) {
2908
+ const cycle = cycles[i];
2909
+ output += `**Cycle ${i + 1}:**
2910
+
2911
+ `;
2912
+ output += codeBlock(cycle.path.join(" \u2192\n"), "");
2913
+ output += `**Suggested fix:** ${cycle.suggestion}
2914
+
2915
+ `;
2916
+ }
2917
+ if (cycles.length > 10) {
2918
+ output += `... and ${cycles.length - 10} more cycles.
2919
+
2920
+ `;
2921
+ }
2922
+ return output;
2923
+ }
2924
+ function detectCycles(graph) {
2925
+ const cycles = [];
2926
+ const visited = /* @__PURE__ */ new Set();
2927
+ const recStack = /* @__PURE__ */ new Set();
2928
+ const pathStack = [];
2929
+ const fileGraph = /* @__PURE__ */ new Map();
2930
+ graph.forEachEdge((edge, attrs, source, target) => {
2931
+ const sourceFile = graph.getNodeAttributes(source).filePath;
2932
+ const targetFile = graph.getNodeAttributes(target).filePath;
2933
+ if (sourceFile !== targetFile) {
2934
+ if (!fileGraph.has(sourceFile)) {
2935
+ fileGraph.set(sourceFile, /* @__PURE__ */ new Set());
2936
+ }
2937
+ fileGraph.get(sourceFile).add(targetFile);
2938
+ }
2939
+ });
2940
+ function dfs(file) {
2941
+ visited.add(file);
2942
+ recStack.add(file);
2943
+ pathStack.push(file);
2944
+ const neighbors = fileGraph.get(file);
2945
+ if (neighbors) {
2946
+ for (const neighbor of neighbors) {
2947
+ if (!visited.has(neighbor)) {
2948
+ if (dfs(neighbor)) {
2949
+ return true;
2950
+ }
2951
+ } else if (recStack.has(neighbor)) {
2952
+ const cycleStart = pathStack.indexOf(neighbor);
2953
+ const cyclePath = pathStack.slice(cycleStart);
2954
+ cyclePath.push(neighbor);
2955
+ cycles.push({
2956
+ path: cyclePath,
2957
+ suggestion: "Extract shared types/interfaces to a common file"
2958
+ });
2959
+ return true;
2960
+ }
2961
+ }
2962
+ }
2963
+ recStack.delete(file);
2964
+ pathStack.pop();
2965
+ return false;
2966
+ }
2967
+ for (const file of fileGraph.keys()) {
2968
+ if (!visited.has(file)) {
2969
+ dfs(file);
2970
+ recStack.clear();
2971
+ pathStack.length = 0;
2972
+ }
2973
+ }
2974
+ return cycles;
2975
+ }
2976
+
2977
+ // src/docs/conventions.ts
2978
+ import { basename as basename2, extname as extname4 } from "path";
2979
+ function generateConventions(graph, projectRoot, version) {
2980
+ let output = "";
2981
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2982
+ const fileCount = getFileCount2(graph);
2983
+ output += timestamp(version, now, fileCount, graph.order);
2984
+ output += header("Code Conventions");
2985
+ output += "Auto-detected coding patterns and conventions in this codebase.\n\n";
2986
+ output += header("File Organization", 2);
2987
+ output += generateFileOrganization(graph);
2988
+ output += header("Naming Patterns", 2);
2989
+ output += generateNamingPatterns(graph);
2990
+ output += header("Import Style", 2);
2991
+ output += generateImportStyle(graph);
2992
+ output += header("Export Patterns", 2);
2993
+ output += generateExportPatterns(graph);
2994
+ output += header("Symbol Distribution", 2);
2995
+ output += generateSymbolDistribution(graph);
2996
+ output += header("Detected Design Patterns", 2);
2997
+ output += generateDesignPatterns(graph);
2998
+ return output;
2999
+ }
3000
+ function getFileCount2(graph) {
3001
+ const files = /* @__PURE__ */ new Set();
3002
+ graph.forEachNode((node, attrs) => {
3003
+ files.add(attrs.filePath);
3004
+ });
3005
+ return files.size;
3006
+ }
3007
+ function generateFileOrganization(graph) {
3008
+ const files = /* @__PURE__ */ new Set();
3009
+ let barrelFileCount = 0;
3010
+ let testFileCount = 0;
3011
+ let totalLines = 0;
3012
+ const fileSizes = [];
3013
+ graph.forEachNode((node, attrs) => {
3014
+ if (!files.has(attrs.filePath)) {
3015
+ files.add(attrs.filePath);
3016
+ const fileName = basename2(attrs.filePath);
3017
+ if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
3018
+ barrelFileCount++;
3019
+ }
3020
+ if (fileName.includes(".test.") || fileName.includes(".spec.") || attrs.filePath.includes("__tests__")) {
3021
+ testFileCount++;
3022
+ }
3023
+ const maxLine = getMaxLineNumber(graph, attrs.filePath);
3024
+ if (maxLine > 0) {
3025
+ fileSizes.push(maxLine);
3026
+ totalLines += maxLine;
3027
+ }
3028
+ }
3029
+ });
3030
+ const avgFileSize = fileSizes.length > 0 ? Math.round(totalLines / fileSizes.length) : 0;
3031
+ const medianFileSize = fileSizes.length > 0 ? getMedian(fileSizes) : 0;
3032
+ let output = "";
3033
+ output += `- **Total Files:** ${formatNumber(files.size)}
3034
+ `;
3035
+ output += `- **Barrel Files (index.*):** ${formatNumber(barrelFileCount)} (${formatPercent(barrelFileCount, files.size)})
3036
+ `;
3037
+ output += `- **Test Files:** ${formatNumber(testFileCount)} (${formatPercent(testFileCount, files.size)})
3038
+ `;
3039
+ if (avgFileSize > 0) {
3040
+ output += `- **Average File Size:** ${formatNumber(avgFileSize)} lines
3041
+ `;
3042
+ output += `- **Median File Size:** ${formatNumber(medianFileSize)} lines
3043
+ `;
3044
+ }
3045
+ output += "\n";
3046
+ return output;
3047
+ }
3048
+ function getMaxLineNumber(graph, filePath) {
3049
+ let maxLine = 0;
3050
+ graph.forEachNode((node, attrs) => {
3051
+ if (attrs.filePath === filePath) {
3052
+ maxLine = Math.max(maxLine, attrs.endLine);
3053
+ }
3054
+ });
3055
+ return maxLine;
3056
+ }
3057
+ function getMedian(numbers) {
3058
+ const sorted = [...numbers].sort((a, b) => a - b);
3059
+ const mid = Math.floor(sorted.length / 2);
3060
+ return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid];
3061
+ }
3062
+ function generateNamingPatterns(graph) {
3063
+ const patterns = {
3064
+ files: { camelCase: 0, PascalCase: 0, kebabCase: 0, snakeCase: 0, total: 0 },
3065
+ functions: { camelCase: 0, PascalCase: 0, snakeCase: 0, total: 0 },
3066
+ classes: { PascalCase: 0, other: 0, total: 0 },
3067
+ interfaces: { IPrefixed: 0, PascalCase: 0, other: 0, total: 0 },
3068
+ constants: { UPPER_SNAKE: 0, other: 0, total: 0 },
3069
+ types: { PascalCase: 0, camelCase: 0, other: 0, total: 0 }
3070
+ };
3071
+ const files = /* @__PURE__ */ new Set();
3072
+ graph.forEachNode((node, attrs) => {
3073
+ if (!files.has(attrs.filePath)) {
3074
+ files.add(attrs.filePath);
3075
+ const fileName = basename2(attrs.filePath, extname4(attrs.filePath));
3076
+ if (isCamelCase(fileName)) patterns.files.camelCase++;
3077
+ else if (isPascalCase(fileName)) patterns.files.PascalCase++;
3078
+ else if (isKebabCase(fileName)) patterns.files.kebabCase++;
3079
+ else if (isSnakeCase(fileName)) patterns.files.snakeCase++;
3080
+ patterns.files.total++;
3081
+ }
3082
+ const name = attrs.name;
3083
+ const kind = attrs.kind;
3084
+ if (kind === "function" || kind === "method") {
3085
+ if (isCamelCase(name)) patterns.functions.camelCase++;
3086
+ else if (isPascalCase(name)) patterns.functions.PascalCase++;
3087
+ else if (isSnakeCase(name)) patterns.functions.snakeCase++;
3088
+ patterns.functions.total++;
3089
+ } else if (kind === "class") {
3090
+ if (isPascalCase(name)) patterns.classes.PascalCase++;
3091
+ else patterns.classes.other++;
3092
+ patterns.classes.total++;
3093
+ } else if (kind === "interface") {
3094
+ if (name.startsWith("I") && isPascalCase(name.slice(1))) patterns.interfaces.IPrefixed++;
3095
+ else if (isPascalCase(name)) patterns.interfaces.PascalCase++;
3096
+ else patterns.interfaces.other++;
3097
+ patterns.interfaces.total++;
3098
+ } else if (kind === "constant") {
3099
+ if (isUpperSnakeCase(name)) patterns.constants.UPPER_SNAKE++;
3100
+ else patterns.constants.other++;
3101
+ patterns.constants.total++;
3102
+ } else if (kind === "type_alias") {
3103
+ if (isPascalCase(name)) patterns.types.PascalCase++;
3104
+ else if (isCamelCase(name)) patterns.types.camelCase++;
3105
+ else patterns.types.other++;
3106
+ patterns.types.total++;
3107
+ }
3108
+ });
3109
+ let output = "";
3110
+ if (patterns.files.total > 0) {
3111
+ output += "**File Naming:**\n\n";
3112
+ if (patterns.files.kebabCase > 0) {
3113
+ output += `- kebab-case: ${formatPercent(patterns.files.kebabCase, patterns.files.total)}
3114
+ `;
3115
+ }
3116
+ if (patterns.files.camelCase > 0) {
3117
+ output += `- camelCase: ${formatPercent(patterns.files.camelCase, patterns.files.total)}
3118
+ `;
3119
+ }
3120
+ if (patterns.files.PascalCase > 0) {
3121
+ output += `- PascalCase: ${formatPercent(patterns.files.PascalCase, patterns.files.total)}
3122
+ `;
3123
+ }
3124
+ if (patterns.files.snakeCase > 0) {
3125
+ output += `- snake_case: ${formatPercent(patterns.files.snakeCase, patterns.files.total)}
3126
+ `;
3127
+ }
3128
+ output += "\n";
3129
+ }
3130
+ if (patterns.functions.total > 0) {
3131
+ output += "**Function Naming:**\n\n";
3132
+ if (patterns.functions.camelCase > 0) {
3133
+ output += `- camelCase: ${formatPercent(patterns.functions.camelCase, patterns.functions.total)}
3134
+ `;
3135
+ }
3136
+ if (patterns.functions.snakeCase > 0) {
3137
+ output += `- snake_case: ${formatPercent(patterns.functions.snakeCase, patterns.functions.total)}
3138
+ `;
3139
+ }
3140
+ if (patterns.functions.PascalCase > 0) {
3141
+ output += `- PascalCase: ${formatPercent(patterns.functions.PascalCase, patterns.functions.total)}
3142
+ `;
3143
+ }
3144
+ output += "\n";
3145
+ }
3146
+ if (patterns.classes.total > 0) {
3147
+ output += "**Class Naming:**\n\n";
3148
+ output += `- PascalCase: ${formatPercent(patterns.classes.PascalCase, patterns.classes.total)}
3149
+ `;
3150
+ if (patterns.classes.other > 0) {
3151
+ output += `- Other: ${formatPercent(patterns.classes.other, patterns.classes.total)}
3152
+ `;
3153
+ }
3154
+ output += "\n";
3155
+ }
3156
+ if (patterns.interfaces.total > 0) {
3157
+ output += "**Interface Naming:**\n\n";
3158
+ if (patterns.interfaces.IPrefixed > 0) {
3159
+ output += `- I-prefix (IPerson): ${formatPercent(patterns.interfaces.IPrefixed, patterns.interfaces.total)}
3160
+ `;
3161
+ }
3162
+ if (patterns.interfaces.PascalCase > 0) {
3163
+ output += `- PascalCase (Person): ${formatPercent(patterns.interfaces.PascalCase, patterns.interfaces.total)}
3164
+ `;
3165
+ }
3166
+ if (patterns.interfaces.other > 0) {
3167
+ output += `- Other: ${formatPercent(patterns.interfaces.other, patterns.interfaces.total)}
3168
+ `;
3169
+ }
3170
+ output += "\n";
3171
+ }
3172
+ if (patterns.types.total > 0) {
3173
+ output += "**Type Naming:**\n\n";
3174
+ if (patterns.types.PascalCase > 0) {
3175
+ output += `- PascalCase: ${formatPercent(patterns.types.PascalCase, patterns.types.total)}
3176
+ `;
3177
+ }
3178
+ if (patterns.types.camelCase > 0) {
3179
+ output += `- camelCase: ${formatPercent(patterns.types.camelCase, patterns.types.total)}
3180
+ `;
3181
+ }
3182
+ if (patterns.types.other > 0) {
3183
+ output += `- Other: ${formatPercent(patterns.types.other, patterns.types.total)}
3184
+ `;
3185
+ }
3186
+ output += "\n";
3187
+ }
3188
+ if (patterns.constants.total > 0) {
3189
+ output += "**Constant Naming:**\n\n";
3190
+ output += `- UPPER_SNAKE_CASE: ${formatPercent(patterns.constants.UPPER_SNAKE, patterns.constants.total)}
3191
+ `;
3192
+ if (patterns.constants.other > 0) {
3193
+ output += `- Other: ${formatPercent(patterns.constants.other, patterns.constants.total)}
3194
+ `;
3195
+ }
3196
+ output += "\n";
3197
+ }
3198
+ return output;
3199
+ }
3200
+ function generateImportStyle(graph) {
3201
+ let barrelImportCount = 0;
3202
+ let pathAliasCount = 0;
3203
+ let totalImports = 0;
3204
+ let namedExportCount = 0;
3205
+ let defaultExportCount = 0;
3206
+ graph.forEachEdge((edge, attrs, source, target) => {
3207
+ const sourceAttrs = graph.getNodeAttributes(source);
3208
+ const targetAttrs = graph.getNodeAttributes(target);
3209
+ if (sourceAttrs.filePath !== targetAttrs.filePath && attrs.kind === "imports") {
3210
+ totalImports++;
3211
+ if (targetAttrs.filePath.endsWith("/index.ts") || targetAttrs.filePath.endsWith("/index.js")) {
3212
+ barrelImportCount++;
3213
+ }
3214
+ if (targetAttrs.filePath.startsWith("@/") || targetAttrs.filePath.startsWith("~/") || targetAttrs.filePath.startsWith("src/")) {
3215
+ pathAliasCount++;
3216
+ }
3217
+ }
3218
+ });
3219
+ graph.forEachNode((node, attrs) => {
3220
+ if (attrs.exported) {
3221
+ if (attrs.name === "default") {
3222
+ defaultExportCount++;
3223
+ } else {
3224
+ namedExportCount++;
3225
+ }
3226
+ }
3227
+ });
3228
+ let output = "";
3229
+ if (totalImports > 0) {
3230
+ output += `- **Total Cross-File Imports:** ${formatNumber(totalImports)}
3231
+ `;
3232
+ if (barrelImportCount > 0) {
3233
+ output += `- **Barrel Imports (from index files):** ${formatPercent(barrelImportCount, totalImports)}
3234
+ `;
3235
+ }
3236
+ if (pathAliasCount > 0) {
3237
+ output += `- **Path Alias Usage (@/ or ~/):** ${formatPercent(pathAliasCount, totalImports)}
3238
+ `;
3239
+ }
3240
+ }
3241
+ output += "\n";
3242
+ return output;
3243
+ }
3244
+ function generateExportPatterns(graph) {
3245
+ let namedExportCount = 0;
3246
+ let defaultExportCount = 0;
3247
+ let reExportCount = 0;
3248
+ graph.forEachNode((node, attrs) => {
3249
+ if (attrs.exported) {
3250
+ if (attrs.name === "default") {
3251
+ defaultExportCount++;
3252
+ } else {
3253
+ namedExportCount++;
3254
+ }
3255
+ }
3256
+ if (attrs.kind === "export") {
3257
+ reExportCount++;
3258
+ }
3259
+ });
3260
+ const totalExports = namedExportCount + defaultExportCount;
3261
+ let output = "";
3262
+ if (totalExports > 0) {
3263
+ output += `- **Named Exports:** ${formatNumber(namedExportCount)} (${formatPercent(namedExportCount, totalExports)})
3264
+ `;
3265
+ output += `- **Default Exports:** ${formatNumber(defaultExportCount)} (${formatPercent(defaultExportCount, totalExports)})
3266
+ `;
3267
+ if (reExportCount > 0) {
3268
+ output += `- **Re-exports:** ${formatNumber(reExportCount)}
3269
+ `;
3270
+ }
3271
+ }
3272
+ output += "\n";
3273
+ return output;
3274
+ }
3275
+ function generateSymbolDistribution(graph) {
3276
+ const symbolCounts = {
3277
+ function: 0,
3278
+ class: 0,
3279
+ variable: 0,
3280
+ constant: 0,
3281
+ type_alias: 0,
3282
+ interface: 0,
3283
+ enum: 0,
3284
+ import: 0,
3285
+ export: 0,
3286
+ method: 0,
3287
+ property: 0,
3288
+ decorator: 0,
3289
+ module: 0
3290
+ };
3291
+ graph.forEachNode((node, attrs) => {
3292
+ symbolCounts[attrs.kind]++;
3293
+ });
3294
+ const total = graph.order;
3295
+ const rows = [];
3296
+ for (const [kind, count] of Object.entries(symbolCounts)) {
3297
+ if (count > 0) {
3298
+ rows.push([kind, formatNumber(count), formatPercent(count, total)]);
3299
+ }
3300
+ }
3301
+ rows.sort((a, b) => parseInt(b[1].replace(/,/g, "")) - parseInt(a[1].replace(/,/g, "")));
3302
+ return table(["Symbol Kind", "Count", "Percentage"], rows);
3303
+ }
3304
+ function generateDesignPatterns(graph) {
3305
+ const patterns = {
3306
+ service: 0,
3307
+ factory: 0,
3308
+ hook: 0,
3309
+ middleware: 0,
3310
+ controller: 0,
3311
+ repository: 0,
3312
+ handler: 0
3313
+ };
3314
+ graph.forEachNode((node, attrs) => {
3315
+ const name = attrs.name;
3316
+ const file = attrs.filePath.toLowerCase();
3317
+ if (attrs.kind === "class" && name.endsWith("Service")) {
3318
+ patterns.service++;
3319
+ }
3320
+ if (attrs.kind === "function" && name.startsWith("create")) {
3321
+ patterns.factory++;
3322
+ }
3323
+ if (attrs.kind === "function" && name.startsWith("use") && name.length > 3) {
3324
+ patterns.hook++;
3325
+ }
3326
+ if (file.includes("middleware")) {
3327
+ patterns.middleware++;
3328
+ }
3329
+ if ((attrs.kind === "class" || attrs.kind === "function") && name.endsWith("Controller")) {
3330
+ patterns.controller++;
3331
+ }
3332
+ if ((attrs.kind === "class" || attrs.kind === "function") && name.endsWith("Repository")) {
3333
+ patterns.repository++;
3334
+ }
3335
+ if ((attrs.kind === "class" || attrs.kind === "function") && name.endsWith("Handler")) {
3336
+ patterns.handler++;
3337
+ }
3338
+ });
3339
+ const detected = Object.entries(patterns).filter(([, count]) => count > 0);
3340
+ if (detected.length === 0) {
3341
+ return "No common design patterns detected.\n\n";
3342
+ }
3343
+ let output = "";
3344
+ for (const [pattern, count] of detected) {
3345
+ const description = getPatternDescription(pattern);
3346
+ output += `- **${capitalizeFirst(pattern)} Pattern:** ${count} occurrences \u2014 ${description}
3347
+ `;
3348
+ }
3349
+ output += "\n";
3350
+ return output;
3351
+ }
3352
+ function getPatternDescription(pattern) {
3353
+ switch (pattern) {
3354
+ case "service":
3355
+ return 'Classes ending in "Service"';
3356
+ case "factory":
3357
+ return 'Functions starting with "create"';
3358
+ case "hook":
3359
+ return 'Functions starting with "use" (React hooks)';
3360
+ case "middleware":
3361
+ return "Files in middleware directories";
3362
+ case "controller":
3363
+ return "Controllers for handling requests";
3364
+ case "repository":
3365
+ return "Data access layer pattern";
3366
+ case "handler":
3367
+ return "Event/request handlers";
3368
+ default:
3369
+ return "";
3370
+ }
3371
+ }
3372
+ function capitalizeFirst(str) {
3373
+ return str.charAt(0).toUpperCase() + str.slice(1);
3374
+ }
3375
+ function isCamelCase(name) {
3376
+ return /^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name);
3377
+ }
3378
+ function isPascalCase(name) {
3379
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
3380
+ }
3381
+ function isKebabCase(name) {
3382
+ return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name);
3383
+ }
3384
+ function isSnakeCase(name) {
3385
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3386
+ }
3387
+ function isUpperSnakeCase(name) {
3388
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(name);
3389
+ }
3390
+
3391
+ // src/docs/dependencies.ts
3392
+ function generateDependencies(graph, projectRoot, version) {
3393
+ let output = "";
3394
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3395
+ const fileCount = getFileCount3(graph);
3396
+ output += timestamp(version, now, fileCount, graph.order);
3397
+ output += header("Dependency Map");
3398
+ output += "Complete dependency mapping showing what connects to what.\n\n";
3399
+ output += header("Module Dependency Matrix", 2);
3400
+ output += generateModuleDependencyMatrix(graph);
3401
+ output += header("High-Impact Symbols", 2);
3402
+ output += generateHighImpactSymbols(graph);
3403
+ output += header("Isolated Files", 2);
3404
+ output += generateIsolatedFiles(graph);
3405
+ output += header("Most Connected File Pairs", 2);
3406
+ output += generateConnectedFilePairs(graph);
3407
+ output += header("Longest Dependency Chains", 2);
3408
+ output += generateDependencyChains(graph);
3409
+ output += header("Circular Dependencies (Detailed)", 2);
3410
+ output += generateCircularDependenciesDetailed(graph);
3411
+ return output;
3412
+ }
3413
+ function getFileCount3(graph) {
3414
+ const files = /* @__PURE__ */ new Set();
3415
+ graph.forEachNode((node, attrs) => {
3416
+ files.add(attrs.filePath);
3417
+ });
3418
+ return files.size;
3419
+ }
3420
+ function generateModuleDependencyMatrix(graph) {
3421
+ const dirEdges = /* @__PURE__ */ new Map();
3422
+ const allDirs = /* @__PURE__ */ new Set();
3423
+ graph.forEachEdge((edge, attrs, source, target) => {
3424
+ const sourceAttrs = graph.getNodeAttributes(source);
3425
+ const targetAttrs = graph.getNodeAttributes(target);
3426
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
3427
+ const sourceDir = getTopLevelDir(sourceAttrs.filePath);
3428
+ const targetDir = getTopLevelDir(targetAttrs.filePath);
3429
+ if (sourceDir && targetDir && sourceDir !== targetDir) {
3430
+ allDirs.add(sourceDir);
3431
+ allDirs.add(targetDir);
3432
+ if (!dirEdges.has(sourceDir)) {
3433
+ dirEdges.set(sourceDir, /* @__PURE__ */ new Map());
3434
+ }
3435
+ const targetMap = dirEdges.get(sourceDir);
3436
+ targetMap.set(targetDir, (targetMap.get(targetDir) || 0) + 1);
3437
+ }
3438
+ }
3439
+ });
3440
+ if (allDirs.size === 0) {
3441
+ return "No module structure detected (flat or single-directory project).\n\n";
3442
+ }
3443
+ const dirTotalEdges = /* @__PURE__ */ new Map();
3444
+ for (const [sourceDir, targets] of dirEdges.entries()) {
3445
+ let total = 0;
3446
+ for (const count of targets.values()) {
3447
+ total += count;
3448
+ }
3449
+ dirTotalEdges.set(sourceDir, total);
3450
+ }
3451
+ const sortedDirs = Array.from(allDirs).sort((a, b) => (dirTotalEdges.get(b) || 0) - (dirTotalEdges.get(a) || 0)).slice(0, 15);
3452
+ if (sortedDirs.length === 0) {
3453
+ return "No cross-module dependencies detected.\n\n";
3454
+ }
3455
+ const headers = ["From / To", ...sortedDirs];
3456
+ const rows = [];
3457
+ for (const sourceDir of sortedDirs) {
3458
+ const row = [sourceDir];
3459
+ for (const targetDir of sortedDirs) {
3460
+ if (sourceDir === targetDir) {
3461
+ row.push("-");
3462
+ } else {
3463
+ const count = dirEdges.get(sourceDir)?.get(targetDir) || 0;
3464
+ row.push(count > 0 ? count.toString() : "\u2717");
3465
+ }
3466
+ }
3467
+ rows.push(row);
3468
+ }
3469
+ return table(headers, rows);
3470
+ }
3471
+ function getTopLevelDir(filePath) {
3472
+ const parts = filePath.split("/");
3473
+ if (parts.length < 2) {
3474
+ return null;
3475
+ }
3476
+ if (parts[0] === "src" && parts.length >= 3) {
3477
+ return `${parts[0]}/${parts[1]}`;
3478
+ }
3479
+ if (parts[0] === "src" && parts.length === 2) {
3480
+ return null;
3481
+ }
3482
+ const firstDir = parts[0];
3483
+ if (firstDir.includes("test") || firstDir.includes("fixture") || firstDir.includes("example") || firstDir.includes("__tests__") || firstDir === "node_modules" || firstDir === "dist" || firstDir === "build") {
3484
+ return null;
3485
+ }
3486
+ if (parts.length >= 2) {
3487
+ return `${parts[0]}/${parts[1]}`;
3488
+ }
3489
+ return parts[0];
3490
+ }
3491
+ function generateHighImpactSymbols(graph) {
3492
+ const symbolImpact = [];
3493
+ graph.forEachNode((node, attrs) => {
3494
+ const inDegree = graph.inDegree(node);
3495
+ if (inDegree > 0 && attrs.name !== "__file__") {
3496
+ symbolImpact.push({
3497
+ name: attrs.name,
3498
+ filePath: attrs.filePath,
3499
+ kind: attrs.kind,
3500
+ dependentCount: inDegree
3501
+ });
3502
+ }
3503
+ });
3504
+ symbolImpact.sort((a, b) => b.dependentCount - a.dependentCount);
3505
+ const top = symbolImpact.slice(0, 15);
3506
+ if (top.length === 0) {
3507
+ return "No high-impact symbols detected.\n\n";
3508
+ }
3509
+ const headers = ["Symbol", "File", "Kind", "Dependents", "Impact"];
3510
+ const rows = top.map((s) => {
3511
+ const impact = s.dependentCount >= 20 ? `${impactEmoji(s.dependentCount)} Critical` : s.dependentCount >= 10 ? `${impactEmoji(s.dependentCount)} High` : s.dependentCount >= 5 ? `${impactEmoji(s.dependentCount)} Medium` : `${impactEmoji(s.dependentCount)} Low`;
3512
+ return [
3513
+ `\`${s.name}\``,
3514
+ `\`${s.filePath}\``,
3515
+ s.kind,
3516
+ formatNumber(s.dependentCount),
3517
+ impact
3518
+ ];
3519
+ });
3520
+ return table(headers, rows);
3521
+ }
3522
+ function generateIsolatedFiles(graph) {
3523
+ const fileConnections = /* @__PURE__ */ new Map();
3524
+ graph.forEachNode((node, attrs) => {
3525
+ if (!fileConnections.has(attrs.filePath)) {
3526
+ fileConnections.set(attrs.filePath, { incoming: 0, outgoing: 0 });
3527
+ }
3528
+ });
3529
+ graph.forEachEdge((edge, attrs, source, target) => {
3530
+ const sourceAttrs = graph.getNodeAttributes(source);
3531
+ const targetAttrs = graph.getNodeAttributes(target);
3532
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
3533
+ const sourceConn = fileConnections.get(sourceAttrs.filePath);
3534
+ const targetConn = fileConnections.get(targetAttrs.filePath);
3535
+ if (sourceConn) sourceConn.outgoing++;
3536
+ if (targetConn) targetConn.incoming++;
3537
+ }
3538
+ });
3539
+ const isolated = [];
3540
+ for (const [file, conn] of fileConnections.entries()) {
3541
+ if (conn.incoming === 0) {
3542
+ isolated.push(file);
3543
+ }
3544
+ }
3545
+ if (isolated.length === 0) {
3546
+ return "No isolated files detected. All files are connected.\n\n";
3547
+ }
3548
+ let output = `Found ${isolated.length} file${isolated.length === 1 ? "" : "s"} with no incoming dependencies:
3549
+
3550
+ `;
3551
+ if (isolated.length <= 20) {
3552
+ output += unorderedList(isolated.map((f) => `\`${f}\``));
3553
+ } else {
3554
+ output += unorderedList(isolated.slice(0, 20).map((f) => `\`${f}\``));
3555
+ output += `... and ${isolated.length - 20} more.
3556
+
3557
+ `;
3558
+ }
3559
+ output += "These files could be entry points, standalone scripts, or dead code.\n\n";
3560
+ return output;
3561
+ }
3562
+ function generateConnectedFilePairs(graph) {
3563
+ const filePairEdges = /* @__PURE__ */ new Map();
3564
+ graph.forEachEdge((edge, attrs, source, target) => {
3565
+ const sourceAttrs = graph.getNodeAttributes(source);
3566
+ const targetAttrs = graph.getNodeAttributes(target);
3567
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
3568
+ const pair = [sourceAttrs.filePath, targetAttrs.filePath].sort().join(" <-> ");
3569
+ filePairEdges.set(pair, (filePairEdges.get(pair) || 0) + 1);
3570
+ }
3571
+ });
3572
+ const pairs = Array.from(filePairEdges.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10);
3573
+ if (pairs.length === 0) {
3574
+ return "No cross-file dependencies detected.\n\n";
3575
+ }
3576
+ const headers = ["File 1", "File 2", "Edges"];
3577
+ const rows = pairs.map(([pair, count]) => {
3578
+ const [file1, file2] = pair.split(" <-> ");
3579
+ return [`\`${file1}\``, `\`${file2}\``, formatNumber(count)];
3580
+ });
3581
+ return table(headers, rows);
3582
+ }
3583
+ function generateDependencyChains(graph) {
3584
+ const chains = findLongestPaths(graph, 5);
3585
+ if (chains.length === 0) {
3586
+ return "No significant dependency chains detected.\n\n";
3587
+ }
3588
+ let output = "";
3589
+ for (let i = 0; i < chains.length; i++) {
3590
+ const chain = chains[i];
3591
+ output += `**Chain ${i + 1}** (${chain.length} files):
3592
+
3593
+ `;
3594
+ output += codeBlock(chain.join(" \u2192\n"), "");
3595
+ }
3596
+ return output;
3597
+ }
3598
+ function findLongestPaths(graph, limit) {
3599
+ const fileGraph = /* @__PURE__ */ new Map();
3600
+ const fileInDegree = /* @__PURE__ */ new Map();
3601
+ graph.forEachEdge((edge, attrs, source, target) => {
3602
+ const sourceFile = graph.getNodeAttributes(source).filePath;
3603
+ const targetFile = graph.getNodeAttributes(target).filePath;
3604
+ if (sourceFile !== targetFile) {
3605
+ if (!fileGraph.has(sourceFile)) {
3606
+ fileGraph.set(sourceFile, /* @__PURE__ */ new Set());
3607
+ }
3608
+ fileGraph.get(sourceFile).add(targetFile);
3609
+ fileInDegree.set(targetFile, (fileInDegree.get(targetFile) || 0) + 1);
3610
+ if (!fileInDegree.has(sourceFile)) {
3611
+ fileInDegree.set(sourceFile, 0);
3612
+ }
3613
+ }
3614
+ });
3615
+ const roots = [];
3616
+ for (const [file, inDegree] of fileInDegree.entries()) {
3617
+ if (inDegree === 0) {
3618
+ roots.push(file);
3619
+ }
3620
+ }
3621
+ const allPaths = [];
3622
+ const visited = /* @__PURE__ */ new Set();
3623
+ function dfs(file, path) {
3624
+ visited.add(file);
3625
+ path.push(file);
3626
+ const neighbors = fileGraph.get(file);
3627
+ if (!neighbors || neighbors.size === 0) {
3628
+ allPaths.push([...path]);
3629
+ } else {
3630
+ for (const neighbor of neighbors) {
3631
+ if (!visited.has(neighbor)) {
3632
+ dfs(neighbor, path);
3633
+ }
3634
+ }
3635
+ }
3636
+ path.pop();
3637
+ visited.delete(file);
3638
+ }
3639
+ for (const root of roots.slice(0, 10)) {
3640
+ dfs(root, []);
3641
+ }
3642
+ allPaths.sort((a, b) => b.length - a.length);
3643
+ return allPaths.slice(0, limit);
3644
+ }
3645
+ function generateCircularDependenciesDetailed(graph) {
3646
+ const cycles = detectCyclesDetailed(graph);
3647
+ if (cycles.length === 0) {
3648
+ return "\u2705 No circular dependencies detected.\n\n";
3649
+ }
3650
+ let output = `\u26A0\uFE0F Found ${cycles.length} circular ${cycles.length === 1 ? "dependency" : "dependencies"}:
3651
+
3652
+ `;
3653
+ for (let i = 0; i < Math.min(cycles.length, 5); i++) {
3654
+ const cycle = cycles[i];
3655
+ output += `**Cycle ${i + 1}:**
3656
+
3657
+ `;
3658
+ output += codeBlock(cycle.files.join(" \u2192\n") + " \u2192 " + cycle.files[0], "");
3659
+ if (cycle.symbols.length > 0) {
3660
+ output += "**Symbols involved:**\n\n";
3661
+ output += unorderedList(cycle.symbols.map((s) => `\`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\``));
3662
+ }
3663
+ output += `**Suggested fix:** ${cycle.suggestion}
3664
+
3665
+ `;
3666
+ }
3667
+ if (cycles.length > 5) {
3668
+ output += `... and ${cycles.length - 5} more cycles.
3669
+
3670
+ `;
3671
+ }
3672
+ return output;
3673
+ }
3674
+ function detectCyclesDetailed(graph) {
3675
+ const cycles = [];
3676
+ const visited = /* @__PURE__ */ new Set();
3677
+ const recStack = /* @__PURE__ */ new Set();
3678
+ const pathStack = [];
3679
+ const fileGraph = /* @__PURE__ */ new Map();
3680
+ graph.forEachEdge((edge, attrs, source, target) => {
3681
+ const sourceAttrs = graph.getNodeAttributes(source);
3682
+ const targetAttrs = graph.getNodeAttributes(target);
3683
+ const sourceFile = sourceAttrs.filePath;
3684
+ const targetFile = targetAttrs.filePath;
3685
+ if (sourceFile !== targetFile) {
3686
+ if (!fileGraph.has(sourceFile)) {
3687
+ fileGraph.set(sourceFile, /* @__PURE__ */ new Map());
3688
+ }
3689
+ const targetMap = fileGraph.get(sourceFile);
3690
+ if (!targetMap.has(targetFile)) {
3691
+ targetMap.set(targetFile, []);
3692
+ }
3693
+ targetMap.get(targetFile).push({
3694
+ symbolName: targetAttrs.name,
3695
+ symbolKind: targetAttrs.kind,
3696
+ line: attrs.line || sourceAttrs.startLine
3697
+ });
3698
+ }
3699
+ });
3700
+ function dfs(file) {
3701
+ visited.add(file);
3702
+ recStack.add(file);
3703
+ pathStack.push(file);
3704
+ const neighbors = fileGraph.get(file);
3705
+ if (neighbors) {
3706
+ for (const [neighbor, symbols] of neighbors.entries()) {
3707
+ if (!visited.has(neighbor)) {
3708
+ if (dfs(neighbor)) {
3709
+ return true;
3710
+ }
3711
+ } else if (recStack.has(neighbor)) {
3712
+ const cycleStart = pathStack.indexOf(neighbor);
3713
+ const cyclePath = pathStack.slice(cycleStart);
3714
+ const cycleSymbols = [];
3715
+ for (let i = 0; i < cyclePath.length; i++) {
3716
+ const currentFile = cyclePath[i];
3717
+ const nextFile = cyclePath[(i + 1) % cyclePath.length];
3718
+ const edgeSymbols = fileGraph.get(currentFile)?.get(nextFile) || [];
3719
+ for (const sym of edgeSymbols.slice(0, 3)) {
3720
+ cycleSymbols.push({
3721
+ name: sym.symbolName,
3722
+ kind: sym.symbolKind,
3723
+ filePath: currentFile,
3724
+ line: sym.line
3725
+ });
3726
+ }
3727
+ }
3728
+ cycles.push({
3729
+ files: cyclePath,
3730
+ symbols: cycleSymbols,
3731
+ suggestion: "Extract shared types/interfaces to a common file"
3732
+ });
3733
+ return true;
3734
+ }
3735
+ }
3736
+ }
3737
+ recStack.delete(file);
3738
+ pathStack.pop();
3739
+ return false;
3740
+ }
3741
+ for (const file of fileGraph.keys()) {
3742
+ if (!visited.has(file)) {
3743
+ dfs(file);
3744
+ recStack.clear();
3745
+ pathStack.length = 0;
3746
+ }
3747
+ }
3748
+ return cycles;
3749
+ }
3750
+
3751
+ // src/docs/onboarding.ts
3752
+ import { dirname as dirname7 } from "path";
3753
+ function generateOnboarding(graph, projectRoot, version) {
3754
+ let output = "";
3755
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3756
+ const fileCount = getFileCount4(graph);
3757
+ output += timestamp(version, now, fileCount, graph.order);
3758
+ output += header("Onboarding Guide");
3759
+ output += "A guide for developers new to this codebase.\n\n";
3760
+ output += header("Quick Orientation", 2);
3761
+ output += generateQuickOrientation(graph);
3762
+ output += header("Where to Start Reading", 2);
3763
+ output += generateReadingOrder(graph);
3764
+ output += header("Module Map", 2);
3765
+ output += generateModuleMap(graph);
3766
+ output += header("Key Concepts", 2);
3767
+ output += generateKeyConcepts(graph);
3768
+ output += header("High-Impact Files", 2);
3769
+ output += generateHighImpactWarning(graph);
3770
+ output += header("Using Depwire with This Project", 2);
3771
+ output += generateDepwireUsage(projectRoot);
3772
+ return output;
3773
+ }
3774
+ function getFileCount4(graph) {
3775
+ const files = /* @__PURE__ */ new Set();
3776
+ graph.forEachNode((node, attrs) => {
3777
+ files.add(attrs.filePath);
3778
+ });
3779
+ return files.size;
3780
+ }
3781
+ function getLanguageStats2(graph) {
3782
+ const stats = {};
3783
+ const files = /* @__PURE__ */ new Set();
3784
+ graph.forEachNode((node, attrs) => {
3785
+ if (!files.has(attrs.filePath)) {
3786
+ files.add(attrs.filePath);
3787
+ const ext = attrs.filePath.toLowerCase();
3788
+ let lang;
3789
+ if (ext.endsWith(".ts") || ext.endsWith(".tsx")) {
3790
+ lang = "TypeScript";
3791
+ } else if (ext.endsWith(".py")) {
3792
+ lang = "Python";
3793
+ } else if (ext.endsWith(".js") || ext.endsWith(".jsx") || ext.endsWith(".mjs") || ext.endsWith(".cjs")) {
3794
+ lang = "JavaScript";
3795
+ } else if (ext.endsWith(".go")) {
3796
+ lang = "Go";
3797
+ } else {
3798
+ lang = "Other";
3799
+ }
3800
+ stats[lang] = (stats[lang] || 0) + 1;
3801
+ }
3802
+ });
3803
+ return stats;
3804
+ }
3805
+ function generateQuickOrientation(graph) {
3806
+ const fileCount = getFileCount4(graph);
3807
+ const languages = getLanguageStats2(graph);
3808
+ const primaryLang = Object.entries(languages).sort((a, b) => b[1] - a[1])[0];
3809
+ const dirs = /* @__PURE__ */ new Set();
3810
+ graph.forEachNode((node, attrs) => {
3811
+ const dir = dirname7(attrs.filePath);
3812
+ if (dir !== ".") {
3813
+ const topLevel = dir.split("/")[0];
3814
+ dirs.add(topLevel);
3815
+ }
3816
+ });
3817
+ const mainAreas = Array.from(dirs).sort().join(", ");
3818
+ let output = "";
3819
+ if (primaryLang) {
3820
+ output += `This is a **${primaryLang[0]}** project with **${fileCount} files** and **${graph.order} symbols**. `;
3821
+ } else {
3822
+ output += `This project has **${fileCount} files** and **${graph.order} symbols**. `;
3823
+ }
3824
+ if (dirs.size > 0) {
3825
+ output += `The main areas are: ${mainAreas}.`;
3826
+ } else {
3827
+ output += "The project has a flat file structure.";
3828
+ }
3829
+ output += "\n\n";
3830
+ return output;
3831
+ }
3832
+ function generateReadingOrder(graph) {
3833
+ const fileStats = getFileStatsWithDeps(graph);
3834
+ if (fileStats.length === 0) {
3835
+ return "No files to analyze.\n\n";
3836
+ }
3837
+ const foundation = fileStats.filter((f) => f.incomingRefs > 0 && f.incomingRefs >= f.outgoingRefs * 2).sort((a, b) => b.incomingRefs - a.incomingRefs).slice(0, 3);
3838
+ const core = fileStats.filter((f) => !foundation.includes(f)).filter((f) => f.incomingRefs > 0 && f.outgoingRefs > 0).filter((f) => {
3839
+ const ratio = f.incomingRefs / (f.outgoingRefs + 0.1);
3840
+ return ratio > 0.3 && ratio < 3;
3841
+ }).sort((a, b) => b.incomingRefs + b.outgoingRefs - (a.incomingRefs + a.outgoingRefs)).slice(0, 5);
3842
+ const orchestration = fileStats.filter((f) => !foundation.includes(f) && !core.includes(f)).filter((f) => f.outgoingRefs > 0 && f.outgoingRefs >= f.incomingRefs * 2).sort((a, b) => b.outgoingRefs - a.outgoingRefs).slice(0, 3);
3843
+ if (foundation.length === 0 && core.length === 0 && orchestration.length === 0) {
3844
+ return "No clear reading order detected. Start with any file.\n\n";
3845
+ }
3846
+ let output = "Recommended reading order for understanding the codebase:\n\n";
3847
+ if (foundation.length > 0) {
3848
+ output += "**Foundation** (start here \u2014 these are building blocks):\n\n";
3849
+ output += orderedList(foundation.map((f) => `${code(f.filePath)} \u2014 Shared foundation (${f.incomingRefs} dependents)`));
3850
+ }
3851
+ if (core.length > 0) {
3852
+ output += "**Core Logic** (read these next):\n\n";
3853
+ output += orderedList(core.map((f) => `${code(f.filePath)} \u2014 Core logic (${f.symbolCount} symbols)`));
3854
+ }
3855
+ if (orchestration.length > 0) {
3856
+ output += "**Entry Points** (read these last to see how it all fits together):\n\n";
3857
+ output += orderedList(orchestration.map((f) => `${code(f.filePath)} \u2014 Entry point (imports from ${f.outgoingRefs} files)`));
3858
+ }
3859
+ return output;
3860
+ }
3861
+ function getFileStatsWithDeps(graph) {
3862
+ const fileMap = /* @__PURE__ */ new Map();
3863
+ graph.forEachNode((node, attrs) => {
3864
+ if (!fileMap.has(attrs.filePath)) {
3865
+ fileMap.set(attrs.filePath, {
3866
+ symbolCount: 0,
3867
+ incomingRefs: /* @__PURE__ */ new Set(),
3868
+ outgoingRefs: /* @__PURE__ */ new Set()
3869
+ });
3870
+ }
3871
+ fileMap.get(attrs.filePath).symbolCount++;
3872
+ });
3873
+ graph.forEachEdge((edge, attrs, source, target) => {
3874
+ const sourceAttrs = graph.getNodeAttributes(source);
3875
+ const targetAttrs = graph.getNodeAttributes(target);
3876
+ if (sourceAttrs.filePath !== targetAttrs.filePath) {
3877
+ const sourceFile = fileMap.get(sourceAttrs.filePath);
3878
+ const targetFile = fileMap.get(targetAttrs.filePath);
3879
+ if (sourceFile) {
3880
+ sourceFile.outgoingRefs.add(targetAttrs.filePath);
3881
+ }
3882
+ if (targetFile) {
3883
+ targetFile.incomingRefs.add(sourceAttrs.filePath);
3884
+ }
3885
+ }
3886
+ });
3887
+ const result = [];
3888
+ for (const [filePath, data] of fileMap.entries()) {
3889
+ result.push({
3890
+ filePath,
3891
+ symbolCount: data.symbolCount,
3892
+ incomingRefs: data.incomingRefs.size,
3893
+ outgoingRefs: data.outgoingRefs.size
3894
+ });
3895
+ }
3896
+ return result;
3897
+ }
3898
+ function generateModuleMap(graph) {
3899
+ const dirStats = getDirectoryStats2(graph);
3900
+ if (dirStats.length === 0) {
3901
+ return "Flat file structure (no subdirectories).\n\n";
3902
+ }
3903
+ let output = "";
3904
+ for (const dir of dirStats) {
3905
+ const description = inferDirectoryDescription(dir, graph);
3906
+ output += `- ${code(dir.name)} \u2014 ${description}
3907
+ `;
3908
+ }
3909
+ output += "\n";
3910
+ return output;
3911
+ }
3912
+ function getDirectoryStats2(graph) {
3913
+ const dirMap = /* @__PURE__ */ new Map();
3914
+ graph.forEachNode((node, attrs) => {
3915
+ const dir = dirname7(attrs.filePath);
3916
+ if (dir === ".") return;
3917
+ if (!dirMap.has(dir)) {
3918
+ dirMap.set(dir, {
3919
+ name: dir,
3920
+ fileCount: 0,
3921
+ symbolCount: 0,
3922
+ inboundEdges: 0,
3923
+ outboundEdges: 0
3924
+ });
3925
+ }
3926
+ dirMap.get(dir).symbolCount++;
3927
+ });
3928
+ const filesPerDir = /* @__PURE__ */ new Map();
3929
+ graph.forEachNode((node, attrs) => {
3930
+ const dir = dirname7(attrs.filePath);
3931
+ if (!filesPerDir.has(dir)) {
3932
+ filesPerDir.set(dir, /* @__PURE__ */ new Set());
3933
+ }
3934
+ filesPerDir.get(dir).add(attrs.filePath);
3935
+ });
3936
+ filesPerDir.forEach((files, dir) => {
3937
+ if (dirMap.has(dir)) {
3938
+ dirMap.get(dir).fileCount = files.size;
3939
+ }
3940
+ });
3941
+ const dirEdges = /* @__PURE__ */ new Map();
3942
+ graph.forEachEdge((edge, attrs, source, target) => {
3943
+ const sourceAttrs = graph.getNodeAttributes(source);
3944
+ const targetAttrs = graph.getNodeAttributes(target);
3945
+ const sourceDir = dirname7(sourceAttrs.filePath);
3946
+ const targetDir = dirname7(targetAttrs.filePath);
3947
+ if (sourceDir !== targetDir) {
3948
+ if (!dirEdges.has(sourceDir)) {
3949
+ dirEdges.set(sourceDir, { in: 0, out: 0 });
3950
+ }
3951
+ if (!dirEdges.has(targetDir)) {
3952
+ dirEdges.set(targetDir, { in: 0, out: 0 });
3953
+ }
3954
+ dirEdges.get(sourceDir).out++;
3955
+ dirEdges.get(targetDir).in++;
3956
+ }
3957
+ });
3958
+ dirEdges.forEach((edges, dir) => {
3959
+ if (dirMap.has(dir)) {
3960
+ const stat = dirMap.get(dir);
3961
+ stat.inboundEdges = edges.in;
3962
+ stat.outboundEdges = edges.out;
3963
+ }
3964
+ });
3965
+ return Array.from(dirMap.values()).sort((a, b) => a.name.localeCompare(b.name));
3966
+ }
3967
+ function inferDirectoryDescription(dir, graph) {
3968
+ const name = dir.name.toLowerCase();
3969
+ if (name.includes("types") || name.includes("interfaces")) {
3970
+ return "Type definitions and interfaces";
3971
+ }
3972
+ if (name.includes("utils") || name.includes("helpers")) {
3973
+ return "Utility functions and helpers";
3974
+ }
3975
+ if (name.includes("services")) {
3976
+ return "Business logic and services";
3977
+ }
3978
+ if (name.includes("components")) {
3979
+ return "UI components";
3980
+ }
3981
+ if (name.includes("api") || name.includes("routes")) {
3982
+ return "API routes and endpoints";
3983
+ }
3984
+ if (name.includes("models") || name.includes("entities")) {
3985
+ return "Data models and entities";
3986
+ }
3987
+ if (name.includes("config")) {
3988
+ return "Configuration files";
3989
+ }
3990
+ if (name.includes("test")) {
3991
+ return "Test files";
3992
+ }
3993
+ const totalEdges = dir.inboundEdges + dir.outboundEdges;
3994
+ if (totalEdges === 0) {
3995
+ return "Isolated module";
3996
+ }
3997
+ const inboundRatio = dir.inboundEdges / totalEdges;
3998
+ if (inboundRatio > 0.7) {
3999
+ return "Shared foundation \u2014 heavily imported by other modules";
4000
+ } else if (inboundRatio < 0.3) {
4001
+ return "Orchestration \u2014 imports from many other modules";
4002
+ } else {
4003
+ return `Core logic \u2014 ${dir.fileCount} files, ${dir.symbolCount} symbols`;
4004
+ }
4005
+ }
4006
+ function generateKeyConcepts(graph) {
4007
+ const clusters = detectClusters(graph);
4008
+ if (clusters.length === 0) {
4009
+ return "No distinct concept clusters detected.\n\n";
4010
+ }
4011
+ let output = "The codebase is organized around these key concepts:\n\n";
4012
+ for (const cluster of clusters.slice(0, 5)) {
4013
+ output += `- **${cluster.name}** \u2014 ${cluster.files.length} tightly-connected files: `;
4014
+ output += cluster.files.slice(0, 3).map((f) => code(f)).join(", ");
4015
+ if (cluster.files.length > 3) {
4016
+ output += `, and ${cluster.files.length - 3} more`;
4017
+ }
4018
+ output += "\n";
4019
+ }
4020
+ output += "\n";
4021
+ return output;
4022
+ }
4023
+ function detectClusters(graph) {
4024
+ const dirFiles = /* @__PURE__ */ new Map();
4025
+ const fileEdges = /* @__PURE__ */ new Map();
4026
+ graph.forEachNode((node, attrs) => {
4027
+ const dir = dirname7(attrs.filePath);
4028
+ if (!dirFiles.has(dir)) {
4029
+ dirFiles.set(dir, /* @__PURE__ */ new Set());
4030
+ }
4031
+ dirFiles.get(dir).add(attrs.filePath);
4032
+ });
4033
+ graph.forEachEdge((edge, attrs, source, target) => {
4034
+ const sourceFile = graph.getNodeAttributes(source).filePath;
4035
+ const targetFile = graph.getNodeAttributes(target).filePath;
4036
+ if (sourceFile !== targetFile) {
4037
+ if (!fileEdges.has(sourceFile)) {
4038
+ fileEdges.set(sourceFile, /* @__PURE__ */ new Set());
4039
+ }
4040
+ fileEdges.get(sourceFile).add(targetFile);
4041
+ }
4042
+ });
4043
+ const clusters = [];
4044
+ for (const [dir, files] of dirFiles.entries()) {
4045
+ if (dir === "." || files.size < 2) continue;
4046
+ const fileArray = Array.from(files);
4047
+ let internalEdgeCount = 0;
4048
+ for (const file of fileArray) {
4049
+ const targets = fileEdges.get(file);
4050
+ if (targets) {
4051
+ for (const target of targets) {
4052
+ if (files.has(target)) {
4053
+ internalEdgeCount++;
4054
+ }
4055
+ }
4056
+ }
4057
+ }
4058
+ if (internalEdgeCount >= 2) {
4059
+ const clusterName = inferClusterName(fileArray);
4060
+ clusters.push({
4061
+ name: clusterName,
4062
+ files: fileArray
4063
+ });
4064
+ }
4065
+ }
4066
+ return clusters.sort((a, b) => b.files.length - a.files.length);
4067
+ }
4068
+ function inferClusterName(files) {
4069
+ const words = /* @__PURE__ */ new Map();
4070
+ for (const file of files) {
4071
+ const fileName = file.toLowerCase();
4072
+ const parts = fileName.split(/[\/\-\_\.]/).filter((p) => p.length > 3);
4073
+ for (const part of parts) {
4074
+ words.set(part, (words.get(part) || 0) + 1);
4075
+ }
4076
+ }
4077
+ const sortedWords = Array.from(words.entries()).sort((a, b) => b[1] - a[1]);
4078
+ if (sortedWords.length > 0 && sortedWords[0][1] > 1) {
4079
+ return capitalizeFirst2(sortedWords[0][0]);
4080
+ }
4081
+ const commonDir = dirname7(files[0]);
4082
+ if (files.every((f) => dirname7(f) === commonDir)) {
4083
+ return capitalizeFirst2(commonDir.split("/").pop() || "Core");
4084
+ }
4085
+ return "Core";
4086
+ }
4087
+ function capitalizeFirst2(str) {
4088
+ return str.charAt(0).toUpperCase() + str.slice(1);
4089
+ }
4090
+ function generateHighImpactWarning(graph) {
4091
+ const highImpactFiles = [];
4092
+ const fileInDegree = /* @__PURE__ */ new Map();
4093
+ graph.forEachEdge((edge, attrs, source, target) => {
4094
+ const sourceFile = graph.getNodeAttributes(source).filePath;
4095
+ const targetFile = graph.getNodeAttributes(target).filePath;
4096
+ if (sourceFile !== targetFile) {
4097
+ fileInDegree.set(targetFile, (fileInDegree.get(targetFile) || 0) + 1);
4098
+ }
4099
+ });
4100
+ for (const [file, count] of fileInDegree.entries()) {
4101
+ if (count >= 5) {
4102
+ highImpactFiles.push({ file, dependents: count });
4103
+ }
4104
+ }
4105
+ highImpactFiles.sort((a, b) => b.dependents - a.dependents);
4106
+ if (highImpactFiles.length === 0) {
4107
+ return "No high-impact files detected. Changes should be relatively isolated.\n\n";
4108
+ }
4109
+ let output = "\u26A0\uFE0F **Before modifying these files, check the blast radius:**\n\n";
4110
+ const topFiles = highImpactFiles.slice(0, 5);
4111
+ for (const { file, dependents } of topFiles) {
4112
+ output += `- ${code(file)} \u2014 ${dependents} dependent files (run \`depwire impact_analysis ${file}\`)
4113
+ `;
4114
+ }
4115
+ output += "\n";
4116
+ return output;
4117
+ }
4118
+ function generateDepwireUsage(projectRoot) {
4119
+ let output = "Use Depwire to explore this codebase:\n\n";
4120
+ output += "**Visualize the dependency graph:**\n\n";
4121
+ output += "```bash\n";
4122
+ output += "depwire viz .\n";
4123
+ output += "```\n\n";
4124
+ output += "**Connect to AI coding tools (MCP):**\n\n";
4125
+ output += "```bash\n";
4126
+ output += "depwire mcp .\n";
4127
+ output += "```\n\n";
4128
+ output += "**Analyze impact of changes:**\n\n";
4129
+ output += "```bash\n";
4130
+ output += "depwire query . <symbol-name>\n";
4131
+ output += "```\n\n";
4132
+ output += "**Update documentation:**\n\n";
4133
+ output += "```bash\n";
4134
+ output += "depwire docs . --update\n";
4135
+ output += "```\n\n";
4136
+ return output;
4137
+ }
4138
+
4139
+ // src/docs/metadata.ts
4140
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync } from "fs";
4141
+ import { join as join9 } from "path";
4142
+ function loadMetadata(outputDir) {
4143
+ const metadataPath = join9(outputDir, "metadata.json");
4144
+ if (!existsSync5(metadataPath)) {
4145
+ return null;
4146
+ }
4147
+ try {
4148
+ const content = readFileSync4(metadataPath, "utf-8");
4149
+ return JSON.parse(content);
4150
+ } catch (err) {
4151
+ console.error("Failed to load metadata:", err);
4152
+ return null;
4153
+ }
4154
+ }
4155
+ function saveMetadata(outputDir, metadata) {
4156
+ const metadataPath = join9(outputDir, "metadata.json");
4157
+ writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
4158
+ }
4159
+ function createMetadata(version, projectPath, fileCount, symbolCount, edgeCount, docTypes) {
4160
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4161
+ const documents = {};
4162
+ for (const docType of docTypes) {
4163
+ const fileName = docType === "architecture" ? "ARCHITECTURE.md" : docType === "conventions" ? "CONVENTIONS.md" : docType === "dependencies" ? "DEPENDENCIES.md" : docType === "onboarding" ? "ONBOARDING.md" : `${docType.toUpperCase()}.md`;
4164
+ documents[docType] = {
4165
+ generated_at: now,
4166
+ file: fileName
4167
+ };
4168
+ }
4169
+ return {
4170
+ version,
4171
+ generated_at: now,
4172
+ project_path: projectPath,
4173
+ file_count: fileCount,
4174
+ symbol_count: symbolCount,
4175
+ edge_count: edgeCount,
4176
+ documents
4177
+ };
4178
+ }
4179
+ function updateMetadata(existing, docTypes, fileCount, symbolCount, edgeCount) {
4180
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4181
+ for (const docType of docTypes) {
4182
+ if (existing.documents[docType]) {
4183
+ existing.documents[docType].generated_at = now;
4184
+ }
4185
+ }
4186
+ existing.file_count = fileCount;
4187
+ existing.symbol_count = symbolCount;
4188
+ existing.edge_count = edgeCount;
4189
+ existing.generated_at = now;
4190
+ return existing;
4191
+ }
4192
+
4193
+ // src/docs/generator.ts
4194
+ async function generateDocs(graph, projectRoot, version, parseTime, options) {
4195
+ const startTime = Date.now();
4196
+ const generated = [];
4197
+ const errors = [];
4198
+ try {
4199
+ if (!existsSync6(options.outputDir)) {
4200
+ mkdirSync(options.outputDir, { recursive: true });
4201
+ if (options.verbose) {
4202
+ console.log(`Created output directory: ${options.outputDir}`);
4203
+ }
4204
+ }
4205
+ let docsToGenerate = options.include;
4206
+ if (options.update && options.only) {
4207
+ docsToGenerate = options.only;
4208
+ }
4209
+ if (docsToGenerate.includes("all")) {
4210
+ docsToGenerate = ["architecture", "conventions", "dependencies", "onboarding"];
4211
+ }
4212
+ let metadata = null;
4213
+ if (options.update) {
4214
+ metadata = loadMetadata(options.outputDir);
4215
+ }
4216
+ const fileCount = getFileCount5(graph);
4217
+ const symbolCount = graph.order;
4218
+ const edgeCount = graph.size;
4219
+ if (options.format === "markdown") {
4220
+ if (docsToGenerate.includes("architecture")) {
4221
+ try {
4222
+ if (options.verbose) console.log("Generating ARCHITECTURE.md...");
4223
+ const content = generateArchitecture(graph, projectRoot, version, parseTime);
4224
+ const filePath = join10(options.outputDir, "ARCHITECTURE.md");
4225
+ writeFileSync2(filePath, content, "utf-8");
4226
+ generated.push("ARCHITECTURE.md");
4227
+ } catch (err) {
4228
+ errors.push(`Failed to generate ARCHITECTURE.md: ${err}`);
4229
+ }
4230
+ }
4231
+ if (docsToGenerate.includes("conventions")) {
4232
+ try {
4233
+ if (options.verbose) console.log("Generating CONVENTIONS.md...");
4234
+ const content = generateConventions(graph, projectRoot, version);
4235
+ const filePath = join10(options.outputDir, "CONVENTIONS.md");
4236
+ writeFileSync2(filePath, content, "utf-8");
4237
+ generated.push("CONVENTIONS.md");
4238
+ } catch (err) {
4239
+ errors.push(`Failed to generate CONVENTIONS.md: ${err}`);
4240
+ }
4241
+ }
4242
+ if (docsToGenerate.includes("dependencies")) {
4243
+ try {
4244
+ if (options.verbose) console.log("Generating DEPENDENCIES.md...");
4245
+ const content = generateDependencies(graph, projectRoot, version);
4246
+ const filePath = join10(options.outputDir, "DEPENDENCIES.md");
4247
+ writeFileSync2(filePath, content, "utf-8");
4248
+ generated.push("DEPENDENCIES.md");
4249
+ } catch (err) {
4250
+ errors.push(`Failed to generate DEPENDENCIES.md: ${err}`);
4251
+ }
4252
+ }
4253
+ if (docsToGenerate.includes("onboarding")) {
4254
+ try {
4255
+ if (options.verbose) console.log("Generating ONBOARDING.md...");
4256
+ const content = generateOnboarding(graph, projectRoot, version);
4257
+ const filePath = join10(options.outputDir, "ONBOARDING.md");
4258
+ writeFileSync2(filePath, content, "utf-8");
4259
+ generated.push("ONBOARDING.md");
4260
+ } catch (err) {
4261
+ errors.push(`Failed to generate ONBOARDING.md: ${err}`);
4262
+ }
4263
+ }
4264
+ } else if (options.format === "json") {
4265
+ errors.push("JSON format not yet supported");
4266
+ }
4267
+ if (metadata && options.update) {
4268
+ metadata = updateMetadata(metadata, docsToGenerate, fileCount, symbolCount, edgeCount);
4269
+ } else {
4270
+ metadata = createMetadata(version, projectRoot, fileCount, symbolCount, edgeCount, docsToGenerate);
4271
+ }
4272
+ saveMetadata(options.outputDir, metadata);
4273
+ if (options.verbose) console.log("Saved metadata.json");
4274
+ const totalTime = Date.now() - startTime;
4275
+ return {
4276
+ success: errors.length === 0,
4277
+ generated,
4278
+ errors,
4279
+ stats: options.stats ? {
4280
+ totalTime,
4281
+ filesGenerated: generated.length
4282
+ } : void 0
4283
+ };
4284
+ } catch (err) {
4285
+ return {
4286
+ success: false,
4287
+ generated,
4288
+ errors: [`Fatal error: ${err}`]
4289
+ };
4290
+ }
4291
+ }
4292
+ function getFileCount5(graph) {
4293
+ const files = /* @__PURE__ */ new Set();
4294
+ graph.forEachNode((node, attrs) => {
4295
+ files.add(attrs.filePath);
4296
+ });
4297
+ return files.size;
4298
+ }
4299
+
2574
4300
  // src/mcp/server.ts
2575
4301
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2576
4302
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2577
4303
 
2578
4304
  // src/mcp/tools.ts
2579
- import { dirname as dirname6 } from "path";
4305
+ import { dirname as dirname8, join as join12 } from "path";
4306
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
2580
4307
 
2581
4308
  // src/mcp/connect.ts
2582
4309
  import simpleGit from "simple-git";
2583
- import { existsSync as existsSync5 } from "fs";
2584
- import { join as join9, basename as basename2, resolve as resolve2 } from "path";
4310
+ import { existsSync as existsSync7 } from "fs";
4311
+ import { join as join11, basename as basename3, resolve as resolve2 } from "path";
2585
4312
  import { tmpdir, homedir } from "os";
2586
4313
  function validateProjectPath(source) {
2587
4314
  const resolved = resolve2(source);
@@ -2594,11 +4321,11 @@ function validateProjectPath(source) {
2594
4321
  "/boot",
2595
4322
  "/proc",
2596
4323
  "/sys",
2597
- join9(homedir(), ".ssh"),
2598
- join9(homedir(), ".gnupg"),
2599
- join9(homedir(), ".aws"),
2600
- join9(homedir(), ".config"),
2601
- join9(homedir(), ".env")
4324
+ join11(homedir(), ".ssh"),
4325
+ join11(homedir(), ".gnupg"),
4326
+ join11(homedir(), ".aws"),
4327
+ join11(homedir(), ".config"),
4328
+ join11(homedir(), ".env")
2602
4329
  ];
2603
4330
  for (const blocked of blockedPaths) {
2604
4331
  if (resolved.startsWith(blocked)) {
@@ -2621,11 +4348,11 @@ async function connectToRepo(source, subdirectory, state) {
2621
4348
  };
2622
4349
  }
2623
4350
  projectName = match[1];
2624
- const reposDir = join9(tmpdir(), "depwire-repos");
2625
- const cloneDir = join9(reposDir, projectName);
4351
+ const reposDir = join11(tmpdir(), "depwire-repos");
4352
+ const cloneDir = join11(reposDir, projectName);
2626
4353
  console.error(`Connecting to GitHub repo: ${source}`);
2627
4354
  const git = simpleGit();
2628
- if (existsSync5(cloneDir)) {
4355
+ if (existsSync7(cloneDir)) {
2629
4356
  console.error(`Repo already cloned at ${cloneDir}, pulling latest changes...`);
2630
4357
  try {
2631
4358
  await git.cwd(cloneDir).pull();
@@ -2643,7 +4370,7 @@ async function connectToRepo(source, subdirectory, state) {
2643
4370
  };
2644
4371
  }
2645
4372
  }
2646
- projectRoot = subdirectory ? join9(cloneDir, subdirectory) : cloneDir;
4373
+ projectRoot = subdirectory ? join11(cloneDir, subdirectory) : cloneDir;
2647
4374
  } else {
2648
4375
  const validation2 = validateProjectPath(source);
2649
4376
  if (!validation2.valid) {
@@ -2652,14 +4379,14 @@ async function connectToRepo(source, subdirectory, state) {
2652
4379
  message: validation2.error
2653
4380
  };
2654
4381
  }
2655
- if (!existsSync5(source)) {
4382
+ if (!existsSync7(source)) {
2656
4383
  return {
2657
4384
  error: "Directory not found",
2658
4385
  message: `Directory does not exist: ${source}`
2659
4386
  };
2660
4387
  }
2661
- projectRoot = subdirectory ? join9(source, subdirectory) : source;
2662
- projectName = basename2(projectRoot);
4388
+ projectRoot = subdirectory ? join11(source, subdirectory) : source;
4389
+ projectName = basename3(projectRoot);
2663
4390
  }
2664
4391
  const validation = validateProjectPath(projectRoot);
2665
4392
  if (!validation.valid) {
@@ -2668,7 +4395,7 @@ async function connectToRepo(source, subdirectory, state) {
2668
4395
  message: validation.error
2669
4396
  };
2670
4397
  }
2671
- if (!existsSync5(projectRoot)) {
4398
+ if (!existsSync7(projectRoot)) {
2672
4399
  return {
2673
4400
  error: "Project root not found",
2674
4401
  message: `Directory does not exist: ${projectRoot}`
@@ -2916,6 +4643,32 @@ function getToolsList() {
2916
4643
  }
2917
4644
  }
2918
4645
  }
4646
+ },
4647
+ {
4648
+ name: "get_project_docs",
4649
+ description: "Retrieve auto-generated codebase documentation. Returns architecture overview, code conventions, dependency maps, and onboarding guides. Documentation must be generated first with `depwire docs` command.",
4650
+ inputSchema: {
4651
+ type: "object",
4652
+ properties: {
4653
+ doc_type: {
4654
+ type: "string",
4655
+ description: "Document type to retrieve: 'architecture', 'conventions', 'dependencies', 'onboarding', or 'all' (default: 'all')"
4656
+ }
4657
+ }
4658
+ }
4659
+ },
4660
+ {
4661
+ name: "update_project_docs",
4662
+ description: "Regenerate codebase documentation with the latest changes. If docs don't exist, generates them for the first time. Use this after significant code changes to keep documentation up-to-date.",
4663
+ inputSchema: {
4664
+ type: "object",
4665
+ properties: {
4666
+ doc_type: {
4667
+ type: "string",
4668
+ description: "Document type to update: 'architecture', 'conventions', 'dependencies', 'onboarding', or 'all' (default: 'all')"
4669
+ }
4670
+ }
4671
+ }
2919
4672
  }
2920
4673
  ];
2921
4674
  }
@@ -2942,6 +4695,24 @@ async function handleToolCall(name, args, state) {
2942
4695
  } else {
2943
4696
  result = await handleVisualizeGraph(args.highlight, args.maxFiles, state);
2944
4697
  }
4698
+ } else if (name === "get_project_docs") {
4699
+ if (!isProjectLoaded(state)) {
4700
+ result = {
4701
+ error: "No project loaded",
4702
+ message: "Use connect_repo to connect to a codebase first"
4703
+ };
4704
+ } else {
4705
+ result = await handleGetProjectDocs(args.doc_type || "all", state);
4706
+ }
4707
+ } else if (name === "update_project_docs") {
4708
+ if (!isProjectLoaded(state)) {
4709
+ result = {
4710
+ error: "No project loaded",
4711
+ message: "Use connect_repo to connect to a codebase first"
4712
+ };
4713
+ } else {
4714
+ result = await handleUpdateProjectDocs(args.doc_type || "all", state);
4715
+ }
2945
4716
  } else {
2946
4717
  if (!isProjectLoaded(state)) {
2947
4718
  result = {
@@ -3238,7 +5009,7 @@ function handleGetArchitectureSummary(graph) {
3238
5009
  const dirMap = /* @__PURE__ */ new Map();
3239
5010
  const languageBreakdown = {};
3240
5011
  fileSummary.forEach((f) => {
3241
- const dir = f.filePath.includes("/") ? dirname6(f.filePath) : ".";
5012
+ const dir = f.filePath.includes("/") ? dirname8(f.filePath) : ".";
3242
5013
  if (!dirMap.has(dir)) {
3243
5014
  dirMap.set(dir, { fileCount: 0, symbolCount: 0 });
3244
5015
  }
@@ -3324,6 +5095,112 @@ The server will keep running until you end the MCP session or press Ctrl+C.`;
3324
5095
  content: [{ type: "text", text: message }]
3325
5096
  };
3326
5097
  }
5098
+ async function handleGetProjectDocs(docType, state) {
5099
+ const docsDir = join12(state.projectRoot, ".depwire");
5100
+ if (!existsSync8(docsDir)) {
5101
+ const errorMessage = `Project documentation has not been generated yet.
5102
+
5103
+ Run \`depwire docs ${state.projectRoot}\` to generate codebase documentation.
5104
+
5105
+ Once generated, this tool will return the requested documentation.
5106
+
5107
+ Available document types:
5108
+ - architecture: High-level structural overview
5109
+ - conventions: Auto-detected coding patterns
5110
+ - dependencies: Complete dependency mapping
5111
+ - onboarding: Guide for new developers`;
5112
+ return {
5113
+ content: [{ type: "text", text: errorMessage }]
5114
+ };
5115
+ }
5116
+ const metadata = loadMetadata(docsDir);
5117
+ if (!metadata) {
5118
+ return {
5119
+ content: [{ type: "text", text: "Documentation directory exists but metadata is missing. Please regenerate with `depwire docs`." }]
5120
+ };
5121
+ }
5122
+ const docsToReturn = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
5123
+ let output = "";
5124
+ const missing = [];
5125
+ for (const doc of docsToReturn) {
5126
+ if (!metadata.documents[doc]) {
5127
+ missing.push(doc);
5128
+ continue;
5129
+ }
5130
+ const filePath = join12(docsDir, metadata.documents[doc].file);
5131
+ if (!existsSync8(filePath)) {
5132
+ missing.push(doc);
5133
+ continue;
5134
+ }
5135
+ const content = readFileSync5(filePath, "utf-8");
5136
+ if (docsToReturn.length > 1) {
5137
+ output += `
5138
+
5139
+ ---
5140
+
5141
+ # ${doc.toUpperCase()}
5142
+
5143
+ `;
5144
+ }
5145
+ output += content;
5146
+ }
5147
+ if (missing.length > 0) {
5148
+ output += `
5149
+
5150
+ ---
5151
+
5152
+ **Note:** The following documents are missing: ${missing.join(", ")}. Run \`depwire docs ${state.projectRoot} --update\` to generate them.`;
5153
+ }
5154
+ return {
5155
+ content: [{ type: "text", text: output }]
5156
+ };
5157
+ }
5158
+ async function handleUpdateProjectDocs(docType, state) {
5159
+ const startTime = Date.now();
5160
+ const docsDir = join12(state.projectRoot, ".depwire");
5161
+ console.error("Regenerating project documentation...");
5162
+ const parsedFiles = parseProject(state.projectRoot);
5163
+ const graph = buildGraph(parsedFiles);
5164
+ const parseTime = (Date.now() - startTime) / 1e3;
5165
+ state.graph = graph;
5166
+ const packageJsonPath = join12(__dirname, "../../package.json");
5167
+ const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
5168
+ const docsToGenerate = docType === "all" ? ["architecture", "conventions", "dependencies", "onboarding"] : [docType];
5169
+ const docsExist = existsSync8(docsDir);
5170
+ const result = await generateDocs(graph, state.projectRoot, packageJson.version, parseTime, {
5171
+ outputDir: docsDir,
5172
+ format: "markdown",
5173
+ include: docsToGenerate,
5174
+ update: docsExist,
5175
+ only: docsExist ? docsToGenerate : void 0,
5176
+ verbose: false,
5177
+ stats: false
5178
+ });
5179
+ const elapsed = (Date.now() - startTime) / 1e3;
5180
+ if (result.success) {
5181
+ const fileCount = /* @__PURE__ */ new Set();
5182
+ graph.forEachNode((node, attrs) => {
5183
+ fileCount.add(attrs.filePath);
5184
+ });
5185
+ return {
5186
+ status: "success",
5187
+ message: `Updated ${result.generated.join(", ")} (${fileCount.size} files, ${graph.order} symbols, ${elapsed.toFixed(1)}s)`,
5188
+ generated: result.generated,
5189
+ stats: {
5190
+ files: fileCount.size,
5191
+ symbols: graph.order,
5192
+ edges: graph.size,
5193
+ time: elapsed
5194
+ }
5195
+ };
5196
+ } else {
5197
+ return {
5198
+ status: "error",
5199
+ message: `Failed to update documentation: ${result.errors.join(", ")}`,
5200
+ errors: result.errors
5201
+ };
5202
+ }
5203
+ }
3327
5204
 
3328
5205
  // src/mcp/server.ts
3329
5206
  async function startMcpServer(state) {
@@ -3369,5 +5246,6 @@ export {
3369
5246
  startVizServer,
3370
5247
  createEmptyState,
3371
5248
  updateFileInGraph,
5249
+ generateDocs,
3372
5250
  startMcpServer
3373
5251
  };