@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.
- package/CHANGELOG.md +36 -1
- package/README.md +95 -1109
- package/RELEASE.md +1 -1
- package/package.json +1 -1
- package/src/ai/prompts.js +53 -10
- package/src/analyzers/domain-inference.js +37 -22
- package/src/cli.js +2 -2
- package/src/docs/generate-doc-set.js +1 -1
- package/src/doctor.js +51 -0
- package/src/integrations/discord.js +3 -3
- package/src/publishers/confluence.js +9 -2
- package/src/publishers/markdown.js +10 -1
- package/src/publishers/notion.js +95 -20
- package/src/renderers/render.js +8 -0
- package/src/renderers/renderDiff.js +18 -0
- package/src/renderers/renderMap.js +75 -60
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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:
|
|
58
|
-
to:
|
|
59
|
-
type: "
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
from: source.id,
|
|
68
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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",
|