@chappibunny/repolens 1.3.0 → 1.4.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.
@@ -10,7 +10,7 @@ function normalizeLabel(value) {
10
10
  .replace(/\/+/g, "/");
11
11
  }
12
12
 
13
- function buildModuleGraph(modules) {
13
+ function buildModuleGraph(modules, depGraph) {
14
14
  // Create nodes with module details
15
15
  const nodes = modules.map(mod => ({
16
16
  id: sanitizeNodeId(mod.key),
@@ -20,74 +20,66 @@ function buildModuleGraph(modules) {
20
20
  category: categorizeModule(mod.key)
21
21
  }));
22
22
 
23
- // Infer relationships based on common patterns
24
23
  const relationships = [];
25
24
 
26
- for (const source of nodes) {
27
- for (const target of nodes) {
28
- if (source.id === target.id) continue;
29
-
30
- // CLI imports from core, publishers, renderers, utils
31
- if (source.label === "bin" || source.label.startsWith("bin/")) {
32
- if (target.label.startsWith("src/")) {
33
- relationships.push({
34
- from: source.id,
35
- to: target.id,
36
- type: "uses"
37
- });
38
- }
39
- }
25
+ // Use real import edges from dependency graph when available
26
+ if (depGraph && depGraph.edges && depGraph.edges.length > 0) {
27
+ // Map file-level edges to module-level edges
28
+ const moduleEdges = new Map(); // "sourceModule->targetModule" → count
40
29
 
41
- // Core modules are foundational - others depend on them
42
- if (target.label.startsWith("src/core")) {
43
- if (source.label.startsWith("src/publishers") ||
44
- source.label.startsWith("src/renderers") ||
45
- source.label.startsWith("src/delivery")) {
46
- relationships.push({
47
- from: source.id,
48
- to: target.id,
49
- type: "depends-on"
50
- });
51
- }
30
+ for (const edge of depGraph.edges) {
31
+ const sourceModule = findModuleForFile(edge.from, modules);
32
+ const targetModule = findModuleForFile(edge.to, modules);
33
+
34
+ if (sourceModule && targetModule && sourceModule !== targetModule) {
35
+ const edgeKey = `${sanitizeNodeId(sourceModule)}:${sanitizeNodeId(targetModule)}`;
36
+ moduleEdges.set(edgeKey, (moduleEdges.get(edgeKey) || 0) + 1);
52
37
  }
38
+ }
53
39
 
54
- // Publishers use renderers
55
- if (source.label.startsWith("src/publishers") && target.label.startsWith("src/renderers")) {
40
+ for (const [edgeKey, count] of moduleEdges) {
41
+ const [fromId, toId] = edgeKey.split(":");
42
+ const sourceNode = nodes.find(n => n.id === fromId);
43
+ const targetNode = nodes.find(n => n.id === toId);
44
+ if (sourceNode && targetNode) {
56
45
  relationships.push({
57
- from: source.id,
58
- to: target.id,
59
- type: "renders"
46
+ from: fromId,
47
+ to: toId,
48
+ type: targetNode.category === "test" ? "tests" : "imports",
49
+ weight: count
60
50
  });
61
51
  }
52
+ }
53
+ } else {
54
+ // Fallback: infer relationships based on common patterns
55
+ for (const source of nodes) {
56
+ for (const target of nodes) {
57
+ if (source.id === target.id) continue;
58
+
59
+ if (source.label === "bin" || source.label.startsWith("bin/")) {
60
+ if (target.label.startsWith("src/")) {
61
+ relationships.push({ from: source.id, to: target.id, type: "uses" });
62
+ }
63
+ }
62
64
 
63
- // Everything uses utils
64
- if (target.label.startsWith("src/utils")) {
65
- if (source.label.startsWith("src/") && source.label !== target.label) {
66
- relationships.push({
67
- from: source.id,
68
- to: target.id,
69
- type: "uses"
70
- });
65
+ if (target.label.startsWith("src/core")) {
66
+ if (source.label.startsWith("src/publishers") ||
67
+ source.label.startsWith("src/renderers") ||
68
+ source.label.startsWith("src/delivery")) {
69
+ relationships.push({ from: source.id, to: target.id, type: "depends-on" });
70
+ }
71
71
  }
72
- }
73
72
 
74
- // Delivery uses publishers
75
- if (source.label.startsWith("src/delivery") && target.label.startsWith("src/publishers")) {
76
- relationships.push({
77
- from: source.id,
78
- to: target.id,
79
- type: "publishes-via"
80
- });
81
- }
73
+ if (target.label.startsWith("src/utils")) {
74
+ if (source.label.startsWith("src/") && source.label !== target.label) {
75
+ relationships.push({ from: source.id, to: target.id, type: "uses" });
76
+ }
77
+ }
82
78
 
83
- // Tests test everything
84
- if (source.label.startsWith("tests/") || source.label.startsWith("test/")) {
85
- if (!target.label.startsWith("tests/") && !target.label.startsWith("test/")) {
86
- relationships.push({
87
- from: source.id,
88
- to: target.id,
89
- type: "tests"
90
- });
79
+ if (source.label.startsWith("tests/") || source.label.startsWith("test/")) {
80
+ if (!target.label.startsWith("tests/") && !target.label.startsWith("test/")) {
81
+ relationships.push({ from: source.id, to: target.id, type: "tests" });
82
+ }
91
83
  }
92
84
  }
93
85
  }
@@ -96,6 +88,23 @@ function buildModuleGraph(modules) {
96
88
  return { nodes, relationships };
97
89
  }
98
90
 
91
+ /**
92
+ * Find which module a file belongs to.
93
+ */
94
+ function findModuleForFile(fileKey, modules) {
95
+ const normalized = fileKey.replace(/\\/g, "/");
96
+ // Find the most specific (longest) matching module key
97
+ let bestMatch = null;
98
+ for (const mod of modules) {
99
+ if (normalized.startsWith(mod.key) || normalized.startsWith(mod.key + "/")) {
100
+ if (!bestMatch || mod.key.length > bestMatch.length) {
101
+ bestMatch = mod.key;
102
+ }
103
+ }
104
+ }
105
+ return bestMatch;
106
+ }
107
+
99
108
  function categorizeModule(key) {
100
109
  const normalized = key.toLowerCase();
101
110
  if (normalized.includes("core")) return "core";
@@ -180,14 +189,14 @@ function generateUnicodeArchitectureDiagram(nodes, relationships) {
180
189
 
181
190
  lines.push("");
182
191
  lines.push("Legend:");
183
- lines.push(" → depends on / uses");
192
+ lines.push(" → imports / depends on");
184
193
  lines.push(" ╌→ tests");
185
194
  lines.push("```");
186
195
 
187
196
  return lines.join("\n");
188
197
  }
189
198
 
190
- export function renderSystemMap(scan) {
199
+ export function renderSystemMap(scan, config, depGraph) {
191
200
  const modules = (scan.modules || []).slice(0, 30); // Limit for readability
192
201
 
193
202
  if (modules.length === 0) {
@@ -201,9 +210,13 @@ export function renderSystemMap(scan) {
201
210
  ].join("\n");
202
211
  }
203
212
 
204
- const { nodes, relationships } = buildModuleGraph(modules);
213
+ const { nodes, relationships } = buildModuleGraph(modules, depGraph);
205
214
  const architectureDiagram = generateUnicodeArchitectureDiagram(nodes, relationships);
206
215
 
216
+ const sourceLabel = depGraph && depGraph.edges && depGraph.edges.length > 0
217
+ ? "**Source:** Real import analysis"
218
+ : "**Source:** Heuristic inference (run full publish for import-based analysis)";
219
+
207
220
  // Build markdown output
208
221
  const lines = [
209
222
  "# 🏗️ System Map",
@@ -212,6 +225,8 @@ export function renderSystemMap(scan) {
212
225
  "",
213
226
  `Showing: ${nodes.length} modules and ${relationships.length} relationships`,
214
227
  "",
228
+ sourceLabel,
229
+ "",
215
230
  "---",
216
231
  "",
217
232
  "## Architecture Diagram",