@chappibunny/repolens 1.6.0 → 1.6.1
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/README.md +1 -1
- package/package.json +2 -4
- package/src/ai/generate-sections.js +24 -13
- package/src/analyzers/context-builder.js +45 -19
- package/src/analyzers/domain-inference.js +56 -1
- package/src/core/scan.js +70 -33
- package/src/docs/generate-doc-set.js +1 -1
- package/src/renderers/renderMap.js +93 -12
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ RepoLens scans your repository, generates living architecture documentation, and
|
|
|
25
25
|
|
|
26
26
|
> **Try it now** — no installation required. Run `npx @chappibunny/repolens demo` on any repo for an instant local preview.
|
|
27
27
|
|
|
28
|
-
[](https://youtu.be/Lpyg0dGsiws)
|
|
29
29
|
|
|
30
30
|
▶️ *Click to watch on YouTube*
|
|
31
31
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chappibunny/repolens",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -64,10 +64,8 @@
|
|
|
64
64
|
"js-yaml": "^4.1.0",
|
|
65
65
|
"node-fetch": "^3.3.2"
|
|
66
66
|
},
|
|
67
|
-
"optionalDependencies": {
|
|
68
|
-
"@mermaid-js/mermaid-cli": "^11.12.0"
|
|
69
|
-
},
|
|
70
67
|
"devDependencies": {
|
|
68
|
+
"tinyexec": "1.0.2",
|
|
71
69
|
"vitest": "^4.0.18"
|
|
72
70
|
}
|
|
73
71
|
}
|
|
@@ -736,20 +736,31 @@ function inferFlowsFromDepGraph(depGraph) {
|
|
|
736
736
|
|
|
737
737
|
function describeRoot(root) {
|
|
738
738
|
const lower = root.toLowerCase().replace(/\/$/, "");
|
|
739
|
+
// Check the last segment for nested paths like src/core, src/analyzers
|
|
740
|
+
const lastSeg = lower.split("/").pop();
|
|
739
741
|
if (/^src$|^lib$/.test(lower)) return "Application source code";
|
|
740
|
-
if (/^test|^__test|^spec/.test(
|
|
741
|
-
if (/^doc/.test(
|
|
742
|
-
if (/^bin$|^scripts?$/.test(
|
|
743
|
-
if (/^config/.test(
|
|
744
|
-
if (/^public$|^static$|^assets$/.test(
|
|
745
|
-
if (/^dist$|^build$|^out$/.test(
|
|
746
|
-
if (/^\.github$/.test(
|
|
747
|
-
if (/^api
|
|
748
|
-
if (/^components
|
|
749
|
-
if (/^pages?$|^views?$|^screens?$/.test(
|
|
750
|
-
if (/^utils?$|^helpers?$/.test(
|
|
751
|
-
if (/^services?$/.test(
|
|
752
|
-
if (/^hooks?$/.test(
|
|
742
|
+
if (/^test|^__test|^spec/.test(lastSeg)) return "Test suites";
|
|
743
|
+
if (/^doc/.test(lastSeg)) return "Documentation";
|
|
744
|
+
if (/^bin$|^scripts?$/.test(lastSeg)) return "CLI entry points and scripts";
|
|
745
|
+
if (/^config/.test(lastSeg)) return "Configuration files";
|
|
746
|
+
if (/^public$|^static$|^assets$/.test(lastSeg)) return "Static assets";
|
|
747
|
+
if (/^dist$|^build$|^out$/.test(lastSeg)) return "Build output";
|
|
748
|
+
if (/^\.github$/.test(lastSeg)) return "GitHub Actions and workflows";
|
|
749
|
+
if (/^api$|^endpoint/.test(lastSeg)) return "API definitions";
|
|
750
|
+
if (/^components?$|^ui$/.test(lastSeg)) return "Shared UI components";
|
|
751
|
+
if (/^pages?$|^views?$|^screens?$/.test(lastSeg)) return "Application pages/views";
|
|
752
|
+
if (/^utils?$|^helpers?$/.test(lastSeg)) return "Utility functions";
|
|
753
|
+
if (/^services?$/.test(lastSeg)) return "Service layer";
|
|
754
|
+
if (/^hooks?$/.test(lastSeg)) return "Custom hooks";
|
|
755
|
+
if (/^core$|^kernel$|^engine$/.test(lastSeg)) return "Core logic and foundations";
|
|
756
|
+
if (/^analyz/.test(lastSeg)) return "Code analysis and detection";
|
|
757
|
+
if (/^render/.test(lastSeg)) return "Rendering and output formatting";
|
|
758
|
+
if (/^publish/.test(lastSeg)) return "Publishing and distribution";
|
|
759
|
+
if (/^deliver/.test(lastSeg)) return "Content delivery";
|
|
760
|
+
if (/^integrat/.test(lastSeg)) return "Third-party integrations";
|
|
761
|
+
if (/^plugin/.test(lastSeg)) return "Plugin and extension system";
|
|
762
|
+
if (/^ai$|^ml$|^llm$/.test(lastSeg)) return "AI/ML features and providers";
|
|
763
|
+
if (/^middleware/.test(lastSeg)) return "Middleware pipeline";
|
|
753
764
|
return "Project files";
|
|
754
765
|
}
|
|
755
766
|
|
|
@@ -97,12 +97,28 @@ export function buildAIContext(scanResult, config) {
|
|
|
97
97
|
function inferModuleType(modulePath) {
|
|
98
98
|
const lower = modulePath.toLowerCase();
|
|
99
99
|
|
|
100
|
-
if (lower.includes("
|
|
101
|
-
if (lower.includes("
|
|
102
|
-
if (lower.includes("
|
|
100
|
+
if (lower.includes("test") || lower.includes("spec") || lower.includes("__test")) return "test";
|
|
101
|
+
if (lower.includes("api") || lower.includes("endpoint")) return "api";
|
|
102
|
+
if (lower.includes("component") || lower.includes("widget")) return "ui";
|
|
103
|
+
if (lower.includes("lib") || lower.includes("util") || lower.includes("helper") || lower.includes("common") || lower.includes("shared")) return "library";
|
|
103
104
|
if (lower.includes("hook")) return "hooks";
|
|
104
|
-
if (lower.includes("store") || lower.includes("state")) return "state";
|
|
105
|
-
if (lower.includes("page") || lower.includes("route")) return "route";
|
|
105
|
+
if (lower.includes("store") || lower.includes("state") || lower.includes("redux") || lower.includes("zustand")) return "state";
|
|
106
|
+
if (lower.includes("page") || lower.includes("route") || lower.includes("view")) return "route";
|
|
107
|
+
if (lower.includes("config") || lower.includes("setting") || lower.includes("env")) return "config";
|
|
108
|
+
if (lower.includes("core") || lower.includes("kernel") || lower.includes("foundation")) return "core";
|
|
109
|
+
if (lower.includes("render") || lower.includes("template") || lower.includes("format")) return "renderer";
|
|
110
|
+
if (lower.includes("publish") || lower.includes("output") || lower.includes("export")) return "publisher";
|
|
111
|
+
if (lower.includes("analyz") || lower.includes("inspect") || lower.includes("detect")) return "analyzer";
|
|
112
|
+
if (lower.includes("plugin") || lower.includes("extension") || lower.includes("addon")) return "plugin";
|
|
113
|
+
if (lower.includes("deliver") || lower.includes("dispatch") || lower.includes("send")) return "delivery";
|
|
114
|
+
if (lower.includes("doc") || lower.includes("generate")) return "documentation";
|
|
115
|
+
if (lower.includes("integrat") || lower.includes("connect") || lower.includes("adapter")) return "integration";
|
|
116
|
+
if (lower.includes("cli") || lower.includes("command") || lower.includes("bin")) return "cli";
|
|
117
|
+
if (lower.includes("ai") || lower.includes("ml") || lower.includes("model") || lower.includes("prompt")) return "ai";
|
|
118
|
+
if (lower.includes("auth") || lower.includes("login") || lower.includes("session")) return "auth";
|
|
119
|
+
if (lower.includes("data") || lower.includes("db") || lower.includes("model") || lower.includes("schema")) return "data";
|
|
120
|
+
if (lower.includes("middleware")) return "middleware";
|
|
121
|
+
if (lower.includes("service")) return "service";
|
|
106
122
|
if (lower.includes("app")) return "app";
|
|
107
123
|
|
|
108
124
|
return "other";
|
|
@@ -110,21 +126,31 @@ function inferModuleType(modulePath) {
|
|
|
110
126
|
|
|
111
127
|
function inferArchitecturalPatterns(modules) {
|
|
112
128
|
const patterns = [];
|
|
129
|
+
const keys = modules.map(m => m.key.toLowerCase());
|
|
113
130
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
126
|
-
if (
|
|
127
|
-
if (
|
|
131
|
+
const has = (keyword) => keys.some(k => k.includes(keyword));
|
|
132
|
+
|
|
133
|
+
// Web framework patterns
|
|
134
|
+
if (has("app/")) patterns.push("Next.js App Router");
|
|
135
|
+
if (has("pages/")) patterns.push("Next.js Pages Router");
|
|
136
|
+
if (has("component") && has("lib")) patterns.push("Layered component architecture");
|
|
137
|
+
if (has("hook")) patterns.push("React hooks pattern");
|
|
138
|
+
if (has("store") || has("state") || has("redux") || has("zustand")) patterns.push("Centralized state management");
|
|
139
|
+
|
|
140
|
+
// General patterns
|
|
141
|
+
if (has("api") || has("endpoint")) patterns.push("API route pattern");
|
|
142
|
+
if (has("core") || has("kernel")) patterns.push("Core/kernel architecture");
|
|
143
|
+
if (has("plugin") || has("extension")) patterns.push("Plugin system");
|
|
144
|
+
if (has("middleware")) patterns.push("Middleware pipeline");
|
|
145
|
+
if (has("render") || has("template")) patterns.push("Renderer pipeline");
|
|
146
|
+
if (has("publish") || has("output")) patterns.push("Multi-output publishing");
|
|
147
|
+
if (has("analyz") || has("detect") || has("inspect")) patterns.push("Analysis pipeline");
|
|
148
|
+
if (has("cli") || has("command") || has("bin")) patterns.push("CLI tool architecture");
|
|
149
|
+
if (has("util") || has("helper") || has("lib")) patterns.push("Shared utility layer");
|
|
150
|
+
if (has("integrat") || has("adapter") || has("connect")) patterns.push("Integration adapters");
|
|
151
|
+
if (has("ai") || has("prompt") || has("provider")) patterns.push("AI/LLM integration");
|
|
152
|
+
if (has("deliver") || has("dispatch")) patterns.push("Delivery pipeline");
|
|
153
|
+
if (has("test") || has("spec")) patterns.push("Dedicated test infrastructure");
|
|
128
154
|
|
|
129
155
|
return patterns;
|
|
130
156
|
}
|
|
@@ -32,7 +32,7 @@ const DEFAULT_DOMAIN_HINTS = [
|
|
|
32
32
|
description: "Payment processing and subscription management"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
match: ["api", "endpoint", "
|
|
35
|
+
match: ["api", "endpoint", "handler", "controller", "middleware"],
|
|
36
36
|
domain: "API Layer",
|
|
37
37
|
description: "Backend API endpoints and request handling"
|
|
38
38
|
},
|
|
@@ -75,6 +75,61 @@ const DEFAULT_DOMAIN_HINTS = [
|
|
|
75
75
|
match: ["job", "queue", "worker", "cron", "task", "scheduler", "background"],
|
|
76
76
|
domain: "Background Jobs",
|
|
77
77
|
description: "Background processing, job queues, and scheduled tasks"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
match: ["core", "kernel", "foundation", "engine"],
|
|
81
|
+
domain: "Core Engine",
|
|
82
|
+
description: "Core business logic and foundational modules"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
match: ["render", "template", "format", "output"],
|
|
86
|
+
domain: "Rendering & Output",
|
|
87
|
+
description: "Content rendering, formatting, and output generation"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
match: ["publish", "deploy", "release", "distribute"],
|
|
91
|
+
domain: "Publishing & Delivery",
|
|
92
|
+
description: "Content and artifact publishing, deployment, and distribution"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
match: ["analyz", "inspect", "detect", "lint", "scan", "parse"],
|
|
96
|
+
domain: "Analysis & Detection",
|
|
97
|
+
description: "Code analysis, pattern detection, and static inspection"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
match: ["plugin", "extension", "addon", "module"],
|
|
101
|
+
domain: "Plugin System",
|
|
102
|
+
description: "Extensibility framework, plugins, and add-ons"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
match: ["deliver", "dispatch", "send", "transport"],
|
|
106
|
+
domain: "Delivery",
|
|
107
|
+
description: "Content delivery and distribution channels"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
match: ["doc", "generate", "markdown", "readme"],
|
|
111
|
+
domain: "Documentation",
|
|
112
|
+
description: "Documentation generation and management"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
match: ["integrat", "connect", "adapter", "bridge", "gateway"],
|
|
116
|
+
domain: "Integrations",
|
|
117
|
+
description: "Third-party service integrations and adapters"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
match: ["cli", "command", "bin", "terminal", "shell", "prompt"],
|
|
121
|
+
domain: "CLI & Commands",
|
|
122
|
+
description: "Command-line interface and terminal commands"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
match: ["ai", "ml", "llm", "gpt", "openai", "anthropic", "gemini"],
|
|
126
|
+
domain: "AI & Machine Learning",
|
|
127
|
+
description: "AI/ML integration, LLM providers, and intelligent features"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
match: ["service", "provider", "client", "sdk"],
|
|
131
|
+
domain: "Services",
|
|
132
|
+
description: "Service layer, providers, and external client SDKs"
|
|
78
133
|
}
|
|
79
134
|
];
|
|
80
135
|
|
package/src/core/scan.js
CHANGED
|
@@ -29,13 +29,22 @@ function isExpressRoute(content) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function isReactRouterFile(content) {
|
|
32
|
-
// Detect React Router patterns
|
|
33
|
-
|
|
32
|
+
// Detect React Router patterns — require import evidence, not just string mentions
|
|
33
|
+
const hasImport = /import\s+.*?from\s+['"]react-router/.test(content)
|
|
34
|
+
|| /require\s*\(\s*['"]react-router/.test(content);
|
|
35
|
+
const hasJSX = /<Route\s/.test(content);
|
|
36
|
+
const hasFactory = /createBrowserRouter\s*\(/.test(content)
|
|
37
|
+
|| /createRoutesFromElements\s*\(/.test(content);
|
|
38
|
+
return hasImport || hasJSX || hasFactory;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
function isVueRouterFile(content) {
|
|
37
|
-
// Detect Vue Router patterns
|
|
38
|
-
|
|
42
|
+
// Detect Vue Router patterns — require import evidence, not just string mentions
|
|
43
|
+
const hasImport = /import\s+.*?from\s+['"]vue-router/.test(content)
|
|
44
|
+
|| /require\s*\(\s*['"]vue-router/.test(content);
|
|
45
|
+
const hasConstructor = /new\s+VueRouter\s*\(/.test(content);
|
|
46
|
+
const hasFactory = /createRouter\s*\(/.test(content) && hasImport;
|
|
47
|
+
return hasImport || hasConstructor || hasFactory;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
function isNextPage(file) {
|
|
@@ -159,6 +168,10 @@ async function extractRepoMetadata(repoRoot) {
|
|
|
159
168
|
if (allDeps["nestjs"] || allDeps["@nestjs/core"]) metadata.frameworks.push("NestJS");
|
|
160
169
|
if (allDeps["svelte"]) metadata.frameworks.push("Svelte");
|
|
161
170
|
if (allDeps["solid-js"]) metadata.frameworks.push("Solid");
|
|
171
|
+
if (allDeps["hono"]) metadata.frameworks.push("Hono");
|
|
172
|
+
if (allDeps["koa"]) metadata.frameworks.push("Koa");
|
|
173
|
+
if (allDeps["hapi"] || allDeps["@hapi/hapi"]) metadata.frameworks.push("Hapi");
|
|
174
|
+
if (allDeps["electron"]) metadata.frameworks.push("Electron");
|
|
162
175
|
|
|
163
176
|
// Detect test frameworks
|
|
164
177
|
if (allDeps["vitest"]) metadata.testFrameworks.push("Vitest");
|
|
@@ -166,6 +179,7 @@ async function extractRepoMetadata(repoRoot) {
|
|
|
166
179
|
if (allDeps["mocha"]) metadata.testFrameworks.push("Mocha");
|
|
167
180
|
if (allDeps["playwright"]) metadata.testFrameworks.push("Playwright");
|
|
168
181
|
if (allDeps["cypress"]) metadata.testFrameworks.push("Cypress");
|
|
182
|
+
if (allDeps["ava"]) metadata.testFrameworks.push("Ava");
|
|
169
183
|
|
|
170
184
|
// Detect build tools
|
|
171
185
|
if (allDeps["vite"]) metadata.buildTools.push("Vite");
|
|
@@ -173,9 +187,25 @@ async function extractRepoMetadata(repoRoot) {
|
|
|
173
187
|
if (allDeps["rollup"]) metadata.buildTools.push("Rollup");
|
|
174
188
|
if (allDeps["esbuild"]) metadata.buildTools.push("esbuild");
|
|
175
189
|
if (allDeps["turbo"]) metadata.buildTools.push("Turborepo");
|
|
190
|
+
if (allDeps["tsup"]) metadata.buildTools.push("tsup");
|
|
191
|
+
if (allDeps["swc"] || allDeps["@swc/core"]) metadata.buildTools.push("SWC");
|
|
192
|
+
if (allDeps["parcel"]) metadata.buildTools.push("Parcel");
|
|
176
193
|
|
|
177
|
-
// Detect
|
|
194
|
+
// Detect languages
|
|
178
195
|
if (allDeps["typescript"]) metadata.languages.add("TypeScript");
|
|
196
|
+
|
|
197
|
+
// Infer JavaScript if package.json exists (any npm project uses JS/Node)
|
|
198
|
+
metadata.languages.add("JavaScript");
|
|
199
|
+
|
|
200
|
+
// Detect Node.js runtime indicators
|
|
201
|
+
const hasNodeEngines = pkg.engines && pkg.engines.node;
|
|
202
|
+
const hasBin = pkg.bin != null;
|
|
203
|
+
const hasNodeDeps = allDeps["node-fetch"] || allDeps["fs-extra"] || allDeps["dotenv"]
|
|
204
|
+
|| allDeps["commander"] || allDeps["yargs"] || allDeps["chalk"]
|
|
205
|
+
|| allDeps["inquirer"] || allDeps["ora"] || allDeps["execa"];
|
|
206
|
+
if (hasNodeEngines || hasBin || hasNodeDeps || pkg.type === "module") {
|
|
207
|
+
metadata.languages.add("Node.js");
|
|
208
|
+
}
|
|
179
209
|
} catch {
|
|
180
210
|
// No package.json or invalid JSON
|
|
181
211
|
}
|
|
@@ -241,30 +271,29 @@ function extractExpressRoutes(content) {
|
|
|
241
271
|
|
|
242
272
|
function extractReactRoutes(content, file) {
|
|
243
273
|
const routes = [];
|
|
274
|
+
const lines = content.split("\n");
|
|
244
275
|
|
|
245
276
|
// Match <Route path="..." />
|
|
246
277
|
const routePattern = /<Route\s+[^>]*path\s*=\s*['"`]([^'"`]+)['"`][^>]*\/?>/gi;
|
|
247
278
|
let match;
|
|
248
279
|
|
|
249
280
|
while ((match = routePattern.exec(content)) !== null) {
|
|
250
|
-
const [,
|
|
251
|
-
|
|
252
|
-
file,
|
|
253
|
-
|
|
254
|
-
framework: "React Router"
|
|
255
|
-
});
|
|
281
|
+
const [, routePath] = match;
|
|
282
|
+
if (isValidRoutePath(routePath)) {
|
|
283
|
+
routes.push({ file, path: routePath, framework: "React Router" });
|
|
284
|
+
}
|
|
256
285
|
}
|
|
257
286
|
|
|
258
|
-
// Match path: "..." in route objects
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
framework: "React Router"
|
|
267
|
-
}
|
|
287
|
+
// Match path: "..." in route objects (skip comment lines)
|
|
288
|
+
for (const line of lines) {
|
|
289
|
+
const trimmed = line.trim();
|
|
290
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
|
|
291
|
+
const objectMatch = /path\s*:\s*['"`]([^'"`]+)['"`]/i.exec(trimmed);
|
|
292
|
+
if (objectMatch) {
|
|
293
|
+
const routePath = objectMatch[1];
|
|
294
|
+
if (isValidRoutePath(routePath) && !routes.some(r => r.path === routePath)) {
|
|
295
|
+
routes.push({ file, path: routePath, framework: "React Router" });
|
|
296
|
+
}
|
|
268
297
|
}
|
|
269
298
|
}
|
|
270
299
|
|
|
@@ -273,23 +302,31 @@ function extractReactRoutes(content, file) {
|
|
|
273
302
|
|
|
274
303
|
function extractVueRoutes(content, file) {
|
|
275
304
|
const routes = [];
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
path
|
|
286
|
-
|
|
287
|
-
|
|
305
|
+
const lines = content.split("\n");
|
|
306
|
+
|
|
307
|
+
// Match path: '...' or path: "..." in Vue router definitions (skip comment lines)
|
|
308
|
+
for (const line of lines) {
|
|
309
|
+
const trimmed = line.trim();
|
|
310
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
|
|
311
|
+
const match = /path\s*:\s*['"`]([^'"`]+)['"`]/i.exec(trimmed);
|
|
312
|
+
if (match) {
|
|
313
|
+
const routePath = match[1];
|
|
314
|
+
if (isValidRoutePath(routePath) && !routes.some(r => r.path === routePath)) {
|
|
315
|
+
routes.push({ file, path: routePath, framework: "Vue Router" });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
288
318
|
}
|
|
289
319
|
|
|
290
320
|
return routes;
|
|
291
321
|
}
|
|
292
322
|
|
|
323
|
+
function isValidRoutePath(p) {
|
|
324
|
+
// Filter out placeholder/documentation strings and non-path values
|
|
325
|
+
if (!p || p === "..." || p === "*" || p.length > 200) return false;
|
|
326
|
+
// Must look like a URL path (starts with / or is a relative segment)
|
|
327
|
+
return p.startsWith("/") || /^[a-zA-Z0-9]/.test(p);
|
|
328
|
+
}
|
|
329
|
+
|
|
293
330
|
export async function scanRepo(cfg) {
|
|
294
331
|
const repoRoot = cfg.__repoRoot;
|
|
295
332
|
|
|
@@ -208,7 +208,7 @@ async function generateDocument(docPlan, context) {
|
|
|
208
208
|
return renderApiSurfaceOriginal(config, scanResult);
|
|
209
209
|
|
|
210
210
|
case "data_flows":
|
|
211
|
-
return await generateDataFlows(flows, aiContext, { depGraph, scanResult
|
|
211
|
+
return await generateDataFlows(flows, aiContext, { depGraph, scanResult });
|
|
212
212
|
|
|
213
213
|
case "arch_diff":
|
|
214
214
|
if (!diffData) {
|
|
@@ -90,15 +90,23 @@ function buildModuleGraph(modules, depGraph) {
|
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* Find which module a file belongs to.
|
|
93
|
+
* Edge keys from the dep graph are extensionless (e.g. "src/core/config")
|
|
94
|
+
* while module keys may include extensions (e.g. "src/core/config.js").
|
|
95
|
+
* We try both direct match and extension-stripped match.
|
|
93
96
|
*/
|
|
94
97
|
function findModuleForFile(fileKey, modules) {
|
|
95
98
|
const normalized = fileKey.replace(/\\/g, "/");
|
|
96
|
-
// Find the most specific (longest) matching module key
|
|
97
99
|
let bestMatch = null;
|
|
98
100
|
for (const mod of modules) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
const modKey = mod.key;
|
|
102
|
+
// Direct match (with or without extension)
|
|
103
|
+
if (normalized === modKey || normalized === modKey.replace(/\.[^/.]+$/, "")) {
|
|
104
|
+
return modKey;
|
|
105
|
+
}
|
|
106
|
+
// Prefix match: file is inside this module
|
|
107
|
+
if (normalized.startsWith(modKey + "/") || normalized.startsWith(modKey.replace(/\.[^/.]+$/, "") + "/")) {
|
|
108
|
+
if (!bestMatch || modKey.length > bestMatch.length) {
|
|
109
|
+
bestMatch = modKey;
|
|
102
110
|
}
|
|
103
111
|
}
|
|
104
112
|
}
|
|
@@ -107,13 +115,25 @@ function findModuleForFile(fileKey, modules) {
|
|
|
107
115
|
|
|
108
116
|
function categorizeModule(key) {
|
|
109
117
|
const normalized = key.toLowerCase();
|
|
110
|
-
if (normalized.includes("
|
|
111
|
-
if (normalized.includes("
|
|
112
|
-
if (normalized.includes("
|
|
113
|
-
if (normalized.includes("
|
|
114
|
-
if (normalized.includes("
|
|
115
|
-
if (normalized.includes("
|
|
116
|
-
if (normalized.includes("
|
|
118
|
+
if (normalized.includes("test") || normalized.includes("spec")) return "test";
|
|
119
|
+
if (normalized.includes("core") || normalized.includes("kernel")) return "core";
|
|
120
|
+
if (normalized.includes("analyz") || normalized.includes("detect") || normalized.includes("inspect")) return "analyzer";
|
|
121
|
+
if (normalized.includes("render") || normalized.includes("format") || normalized.includes("template")) return "renderer";
|
|
122
|
+
if (normalized.includes("publish") || normalized.includes("output")) return "publisher";
|
|
123
|
+
if (normalized.includes("deliver") || normalized.includes("dispatch")) return "delivery";
|
|
124
|
+
if (normalized.includes("integrat") || normalized.includes("connect") || normalized.includes("adapter")) return "integration";
|
|
125
|
+
if (normalized.includes("util") || normalized.includes("helper") || normalized.includes("lib") || normalized.includes("common")) return "util";
|
|
126
|
+
if (normalized.includes("ai") || normalized.includes("llm") || normalized.includes("prompt")) return "ai";
|
|
127
|
+
if (normalized.includes("doc") || normalized.includes("generate")) return "docs";
|
|
128
|
+
if (normalized.includes("plugin") || normalized.includes("extension")) return "plugin";
|
|
129
|
+
if (normalized.includes("config") || normalized.includes("setting")) return "config";
|
|
130
|
+
if (normalized.includes("cli") || normalized.includes("bin") || normalized.includes("command")) return "cli";
|
|
131
|
+
if (normalized.includes("api") || normalized.includes("endpoint")) return "api";
|
|
132
|
+
if (normalized.includes("component") || normalized.includes("ui")) return "ui";
|
|
133
|
+
if (normalized.includes("page") || normalized.includes("route") || normalized.includes("view")) return "page";
|
|
134
|
+
if (normalized.includes("store") || normalized.includes("state")) return "state";
|
|
135
|
+
if (normalized.includes("middleware")) return "middleware";
|
|
136
|
+
if (normalized.includes("service")) return "service";
|
|
117
137
|
return "other";
|
|
118
138
|
}
|
|
119
139
|
|
|
@@ -122,8 +142,21 @@ function generateUnicodeArchitectureDiagram(nodes, relationships) {
|
|
|
122
142
|
const categories = {
|
|
123
143
|
cli: { icon: "🎯", label: "CLI Entry", nodes: [] },
|
|
124
144
|
core: { icon: "⚙️", label: "Core Logic", nodes: [] },
|
|
125
|
-
|
|
145
|
+
config: { icon: "🔧", label: "Configuration", nodes: [] },
|
|
146
|
+
analyzer: { icon: "🔍", label: "Analysis", nodes: [] },
|
|
147
|
+
ai: { icon: "🤖", label: "AI / ML", nodes: [] },
|
|
148
|
+
docs: { icon: "📝", label: "Documentation", nodes: [] },
|
|
149
|
+
renderer: { icon: "📋", label: "Rendering", nodes: [] },
|
|
150
|
+
publisher: { icon: "📤", label: "Publishing", nodes: [] },
|
|
151
|
+
delivery: { icon: "📬", label: "Delivery", nodes: [] },
|
|
126
152
|
integration: { icon: "🔌", label: "Integration", nodes: [] },
|
|
153
|
+
plugin: { icon: "🧩", label: "Plugins", nodes: [] },
|
|
154
|
+
api: { icon: "🌐", label: "API Layer", nodes: [] },
|
|
155
|
+
ui: { icon: "🖼️", label: "UI Components", nodes: [] },
|
|
156
|
+
page: { icon: "📄", label: "Pages / Routes", nodes: [] },
|
|
157
|
+
state: { icon: "💾", label: "State Management", nodes: [] },
|
|
158
|
+
middleware: { icon: "🔀", label: "Middleware", nodes: [] },
|
|
159
|
+
service: { icon: "⚡", label: "Services", nodes: [] },
|
|
127
160
|
util: { icon: "🛠️", label: "Utilities", nodes: [] },
|
|
128
161
|
test: { icon: "✅", label: "Testing", nodes: [] },
|
|
129
162
|
other: { icon: "📦", label: "Other", nodes: [] }
|
|
@@ -235,5 +268,53 @@ export function renderSystemMap(scan, config, depGraph) {
|
|
|
235
268
|
""
|
|
236
269
|
];
|
|
237
270
|
|
|
271
|
+
// Add key connections summary when we have relationships
|
|
272
|
+
if (relationships.length > 0) {
|
|
273
|
+
lines.push("---", "", "## Key Connections", "");
|
|
274
|
+
|
|
275
|
+
// Find most-depended-on modules (highest in-degree)
|
|
276
|
+
const inDegree = new Map();
|
|
277
|
+
const outDegree = new Map();
|
|
278
|
+
for (const rel of relationships) {
|
|
279
|
+
inDegree.set(rel.to, (inDegree.get(rel.to) || 0) + (rel.weight || 1));
|
|
280
|
+
outDegree.set(rel.from, (outDegree.get(rel.from) || 0) + (rel.weight || 1));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const topDeps = [...inDegree.entries()]
|
|
284
|
+
.sort((a, b) => b[1] - a[1])
|
|
285
|
+
.slice(0, 5);
|
|
286
|
+
|
|
287
|
+
if (topDeps.length > 0) {
|
|
288
|
+
lines.push("**Most depended-on modules** (highest import count):", "");
|
|
289
|
+
lines.push("| Module | Imported by |");
|
|
290
|
+
lines.push("|--------|------------|");
|
|
291
|
+
for (const [nodeId, count] of topDeps) {
|
|
292
|
+
const node = nodes.find(n => n.id === nodeId);
|
|
293
|
+
if (node) {
|
|
294
|
+
lines.push(`| \`${node.label}\` | ${count} module${count !== 1 ? "s" : ""} |`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
lines.push("");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Find modules with highest out-degree (most dependencies)
|
|
301
|
+
const topConsumers = [...outDegree.entries()]
|
|
302
|
+
.sort((a, b) => b[1] - a[1])
|
|
303
|
+
.slice(0, 5);
|
|
304
|
+
|
|
305
|
+
if (topConsumers.length > 0) {
|
|
306
|
+
lines.push("**Most dependent modules** (highest dependency count):", "");
|
|
307
|
+
lines.push("| Module | Depends on |");
|
|
308
|
+
lines.push("|--------|-----------|");
|
|
309
|
+
for (const [nodeId, count] of topConsumers) {
|
|
310
|
+
const node = nodes.find(n => n.id === nodeId);
|
|
311
|
+
if (node) {
|
|
312
|
+
lines.push(`| \`${node.label}\` | ${count} module${count !== 1 ? "s" : ""} |`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
lines.push("");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
238
319
|
return lines.join("\n");
|
|
239
320
|
}
|