@chappibunny/repolens 1.5.3 โ 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/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/package.json +2 -4
- package/src/ai/generate-sections.js +470 -51
- 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 +6 -6
- package/src/renderers/renderMap.js +93 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.6.0
|
|
6
|
+
|
|
7
|
+
### โจ Features
|
|
8
|
+
|
|
9
|
+
- **Deterministic enrichment**: All 6 AI-enhanced document types now produce significantly richer output when running without AI integration. Fallback generators receive dependency graph stats, data flows, monorepo info, routes, drift results, and module type classifications.
|
|
10
|
+
- **Executive Summary**: Now includes module composition breakdown, monorepo structure, codebase health metrics (cycles, orphans, hubs), data flow summaries, and test framework info.
|
|
11
|
+
- **System Overview**: Now includes module architecture by type, route summary (pages + APIs), dependency graph stats with hub modules, and monorepo info.
|
|
12
|
+
- **Business Domains**: Now maps routes to domains and shows cross-domain dependency hubs.
|
|
13
|
+
- **Architecture Overview**: Now includes module layers by type, dependency health with strengths/concerns heuristics, hub modules, architecture drift summary, and monorepo architecture.
|
|
14
|
+
- **Data Flows**: Now generates additional flows from dependency graph hub chains when heuristic flows are sparse, shows shared library and external service dependencies per flow, and includes import network summary.
|
|
15
|
+
- **Developer Onboarding**: Now includes monorepo navigation, key routes to explore, data flow overview, integration point warnings, module type classification in the module table, and test framework quickstart.
|
|
16
|
+
- **Activated dead code**: `identifyFlowDependencies()` from `flow-inference.js` is now imported and used in the data flows fallback to enrich each flow with shared library and external dependency context.
|
|
17
|
+
|
|
18
|
+
### ๐งช Test Coverage
|
|
19
|
+
|
|
20
|
+
- **Deterministic enrichment tests** (`tests/deterministic-enrichment.test.js`): 36 tests covering all 6 enriched fallback generators โ module composition, monorepo sections, dependency health, route summaries, data flow generation from dep graph, backward compatibility.
|
|
21
|
+
- **347 tests** passing across **22 test files** (up from 311/21).
|
|
22
|
+
|
|
5
23
|
## 1.5.3
|
|
6
24
|
|
|
7
25
|
### ๐ง Improvements
|
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.
|
|
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
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
AI_SCHEMAS,
|
|
16
16
|
renderStructuredToMarkdown,
|
|
17
17
|
} from "./prompts.js";
|
|
18
|
+
import { identifyFlowDependencies } from "../analyzers/flow-inference.js";
|
|
18
19
|
import { info, warn } from "../utils/logger.js";
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -62,68 +63,70 @@ async function generateWithStructuredFallback(key, promptText, maxTokens, fallba
|
|
|
62
63
|
return result.text;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
export async function generateExecutiveSummary(context) {
|
|
66
|
+
export async function generateExecutiveSummary(context, enrichment = {}) {
|
|
66
67
|
return generateWithStructuredFallback(
|
|
67
68
|
"executive_summary",
|
|
68
69
|
createExecutiveSummaryPrompt(context),
|
|
69
70
|
1500,
|
|
70
|
-
() => getFallbackExecutiveSummary(context),
|
|
71
|
+
() => getFallbackExecutiveSummary(context, enrichment),
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
export async function generateSystemOverview(context) {
|
|
75
|
+
export async function generateSystemOverview(context, enrichment = {}) {
|
|
75
76
|
return generateWithStructuredFallback(
|
|
76
77
|
"system_overview",
|
|
77
78
|
createSystemOverviewPrompt(context),
|
|
78
79
|
1200,
|
|
79
|
-
() => getFallbackSystemOverview(context),
|
|
80
|
+
() => getFallbackSystemOverview(context, enrichment),
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
export async function generateBusinessDomains(context) {
|
|
84
|
+
export async function generateBusinessDomains(context, enrichment = {}) {
|
|
84
85
|
return generateWithStructuredFallback(
|
|
85
86
|
"business_domains",
|
|
86
87
|
createBusinessDomainsPrompt(context),
|
|
87
88
|
2000,
|
|
88
|
-
() => getFallbackBusinessDomains(context),
|
|
89
|
+
() => getFallbackBusinessDomains(context, enrichment),
|
|
89
90
|
);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
export async function generateArchitectureOverview(context) {
|
|
93
|
+
export async function generateArchitectureOverview(context, enrichment = {}) {
|
|
93
94
|
return generateWithStructuredFallback(
|
|
94
95
|
"architecture_overview",
|
|
95
96
|
createArchitectureOverviewPrompt(context),
|
|
96
97
|
1800,
|
|
97
|
-
() => getFallbackArchitectureOverview(context),
|
|
98
|
+
() => getFallbackArchitectureOverview(context, enrichment),
|
|
98
99
|
);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
export async function generateDataFlows(flows, context) {
|
|
102
|
+
export async function generateDataFlows(flows, context, enrichment = {}) {
|
|
102
103
|
return generateWithStructuredFallback(
|
|
103
104
|
"data_flows",
|
|
104
105
|
createDataFlowsPrompt(flows, context),
|
|
105
106
|
1800,
|
|
106
|
-
() => getFallbackDataFlows(flows),
|
|
107
|
+
() => getFallbackDataFlows(flows, context, enrichment),
|
|
107
108
|
);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
export async function generateDeveloperOnboarding(context) {
|
|
111
|
+
export async function generateDeveloperOnboarding(context, enrichment = {}) {
|
|
111
112
|
return generateWithStructuredFallback(
|
|
112
113
|
"developer_onboarding",
|
|
113
114
|
createDeveloperOnboardingPrompt(context),
|
|
114
115
|
2200,
|
|
115
|
-
() => getFallbackDeveloperOnboarding(context),
|
|
116
|
+
() => getFallbackDeveloperOnboarding(context, enrichment),
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
// Fallback generators (deterministic, no AI)
|
|
120
121
|
|
|
121
|
-
function getFallbackExecutiveSummary(context) {
|
|
122
|
+
function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
123
|
+
const { depGraph, flows } = enrichment;
|
|
122
124
|
const frameworkList = context.techStack.frameworks.join(", ") || "general-purpose";
|
|
123
125
|
const languageList = context.techStack.languages.join(", ") || "multiple languages";
|
|
124
126
|
const domainSummary = context.domains.slice(0, 5).map(d => d.name).join(", ");
|
|
127
|
+
const testFrameworks = context.techStack.testFrameworks || [];
|
|
125
128
|
|
|
126
|
-
|
|
129
|
+
let output = `# Executive Summary
|
|
127
130
|
|
|
128
131
|
## What This System Does
|
|
129
132
|
|
|
@@ -146,21 +149,72 @@ ${context.domains.map(d => `| ${d.name} | ${d.moduleCount} | ${d.description ||
|
|
|
146
149
|
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
147
150
|
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
148
151
|
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
152
|
+
${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")} |\n` : ""}`;
|
|
153
|
+
|
|
154
|
+
// Module type breakdown
|
|
155
|
+
const typeGroups = groupModulesByType(context.topModules);
|
|
156
|
+
if (Object.keys(typeGroups).length > 1) {
|
|
157
|
+
output += `\n## Module Composition\n\n`;
|
|
158
|
+
output += `| Layer | Modules | Examples |\n`;
|
|
159
|
+
output += `|-------|---------|----------|\n`;
|
|
160
|
+
for (const [type, mods] of Object.entries(typeGroups)) {
|
|
161
|
+
const examples = mods.slice(0, 3).map(m => `\`${m.key}\``).join(", ");
|
|
162
|
+
output += `| ${formatModuleType(type)} | ${mods.length} | ${examples} |\n`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
149
165
|
|
|
150
|
-
|
|
166
|
+
// Monorepo info
|
|
167
|
+
if (context.monorepo) {
|
|
168
|
+
output += `\n## Monorepo Structure\n\n`;
|
|
169
|
+
output += `This is a **${context.monorepo.tool}** monorepo containing **${context.monorepo.packageCount} package${context.monorepo.packageCount === 1 ? "" : "s"}**:\n\n`;
|
|
170
|
+
output += context.monorepo.packages.slice(0, 10).map(p => `- \`${p.name}\` (${p.path})`).join("\n");
|
|
171
|
+
output += "\n";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Dependency health
|
|
175
|
+
if (depGraph?.stats) {
|
|
176
|
+
const stats = depGraph.stats;
|
|
177
|
+
output += `\n## Codebase Health\n\n`;
|
|
178
|
+
output += `| Metric | Value |\n`;
|
|
179
|
+
output += `|--------|-------|\n`;
|
|
180
|
+
output += `| Internal imports | ${stats.totalEdges} |\n`;
|
|
181
|
+
output += `| External dependencies | ${stats.externalDeps} |\n`;
|
|
182
|
+
output += `| Circular dependencies | ${stats.cycles} |\n`;
|
|
183
|
+
output += `| Orphan files | ${stats.orphanFiles} |\n`;
|
|
184
|
+
if (stats.cycles === 0) {
|
|
185
|
+
output += `\nThe dependency graph is **cycle-free**, indicating clean module boundaries.\n`;
|
|
186
|
+
} else {
|
|
187
|
+
output += `\nโ ๏ธ **${stats.cycles} circular dependenc${stats.cycles === 1 ? "y" : "ies"}** detected โ these may complicate refactoring and testing.\n`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Data flows
|
|
192
|
+
if (flows && flows.length > 0) {
|
|
193
|
+
output += `\n## Key Data Flows\n\n`;
|
|
194
|
+
output += `${flows.length} data flow${flows.length === 1 ? "" : "s"} identified:\n\n`;
|
|
195
|
+
for (const flow of flows) {
|
|
196
|
+
output += `- **${flow.name}**${flow.critical ? " (critical)" : ""} โ ${flow.description}\n`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
output += `\n## Key Observations
|
|
151
201
|
|
|
152
202
|
The codebase follows ${context.patterns.length > 0 ? context.patterns.join(", ") : "standard"} architectural patterns. ${domainSummary ? `The core functional areas โ ${domainSummary} โ account for the majority of the application logic.` : ""}
|
|
153
203
|
|
|
154
204
|
---
|
|
155
205
|
|
|
156
206
|
*This summary is generated deterministically from repository structure. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for natural language insights tailored to non-technical readers.*`;
|
|
207
|
+
|
|
208
|
+
return output;
|
|
157
209
|
}
|
|
158
210
|
|
|
159
|
-
function getFallbackSystemOverview(context) {
|
|
211
|
+
function getFallbackSystemOverview(context, enrichment = {}) {
|
|
212
|
+
const { depGraph } = enrichment;
|
|
160
213
|
const sizeLabel = context.project.modulesDetected > 50 ? "large-scale" :
|
|
161
214
|
context.project.modulesDetected > 20 ? "medium-sized" : "focused";
|
|
215
|
+
const testFrameworks = context.techStack.testFrameworks || [];
|
|
162
216
|
|
|
163
|
-
|
|
217
|
+
let output = `# System Overview
|
|
164
218
|
|
|
165
219
|
## Repository Snapshot
|
|
166
220
|
|
|
@@ -173,24 +227,102 @@ This is a ${sizeLabel} codebase organized into **${context.project.modulesDetect
|
|
|
173
227
|
| Application pages | ${context.project.pagesDetected} |
|
|
174
228
|
| API endpoints | ${context.project.apiRoutesDetected} |
|
|
175
229
|
|
|
230
|
+
## Technology Stack
|
|
231
|
+
|
|
232
|
+
| Category | Technologies |
|
|
233
|
+
|----------|-------------|
|
|
234
|
+
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
235
|
+
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
236
|
+
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
237
|
+
${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")} |\n` : ""}
|
|
176
238
|
## Detected Patterns
|
|
177
239
|
|
|
178
240
|
${context.patterns.length > 0 ? context.patterns.map(p => `- ${p}`).join("\n") : "No specific architectural patterns were detected. This typically means the project uses a straightforward directory-based organization."}
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
// Module type breakdown
|
|
244
|
+
const typeGroups = groupModulesByType(context.topModules);
|
|
245
|
+
if (Object.keys(typeGroups).length > 1) {
|
|
246
|
+
output += `\n## Module Architecture\n\n`;
|
|
247
|
+
output += `The codebase is organized into the following module layers:\n\n`;
|
|
248
|
+
for (const [type, mods] of Object.entries(typeGroups)) {
|
|
249
|
+
const moduleList = mods.slice(0, 5).map(m => `\`${m.key}\` (${m.fileCount} files)`).join(", ");
|
|
250
|
+
output += `- **${formatModuleType(type)}** (${mods.length}): ${moduleList}\n`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
179
253
|
|
|
180
|
-
## Dominant Domains
|
|
254
|
+
output += `\n## Dominant Domains
|
|
181
255
|
|
|
182
256
|
The following domains represent the largest areas of the codebase by file count:
|
|
183
257
|
|
|
184
258
|
| Rank | Domain | Files |
|
|
185
259
|
|------|--------|-------|
|
|
186
260
|
${context.domains.slice(0, 5).map((d, i) => `| ${i + 1} | ${d.name} | ${d.fileCount} |`).join("\n")}
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
// Route highlights
|
|
264
|
+
const routes = context.routes || {};
|
|
265
|
+
const pages = routes.pages || [];
|
|
266
|
+
const apis = routes.apis || [];
|
|
267
|
+
if (pages.length > 0 || apis.length > 0) {
|
|
268
|
+
output += `\n## Route Summary\n\n`;
|
|
269
|
+
if (pages.length > 0) {
|
|
270
|
+
output += `**Application Pages** (${pages.length} total):\n\n`;
|
|
271
|
+
for (const p of pages.slice(0, 8)) {
|
|
272
|
+
output += `- \`${p.path}\`\n`;
|
|
273
|
+
}
|
|
274
|
+
if (pages.length > 8) output += `- โฆ and ${pages.length - 8} more\n`;
|
|
275
|
+
output += "\n";
|
|
276
|
+
}
|
|
277
|
+
if (apis.length > 0) {
|
|
278
|
+
output += `**API Endpoints** (${apis.length} total):\n\n`;
|
|
279
|
+
for (const a of apis.slice(0, 8)) {
|
|
280
|
+
const methods = a.methods ? ` [${a.methods.join(", ")}]` : "";
|
|
281
|
+
output += `- \`${a.path}\`${methods}\n`;
|
|
282
|
+
}
|
|
283
|
+
if (apis.length > 8) output += `- โฆ and ${apis.length - 8} more\n`;
|
|
284
|
+
output += "\n";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
187
287
|
|
|
188
|
-
|
|
288
|
+
// Dependency graph highlights
|
|
289
|
+
if (depGraph?.stats) {
|
|
290
|
+
const stats = depGraph.stats;
|
|
291
|
+
output += `## Dependency Graph\n\n`;
|
|
292
|
+
output += `| Metric | Value |\n`;
|
|
293
|
+
output += `|--------|-------|\n`;
|
|
294
|
+
output += `| Internal imports | ${stats.totalEdges} |\n`;
|
|
295
|
+
output += `| External packages | ${stats.externalDeps} |\n`;
|
|
296
|
+
output += `| Circular dependencies | ${stats.cycles} |\n`;
|
|
297
|
+
output += `| Orphan files | ${stats.orphanFiles} |\n`;
|
|
298
|
+
if (stats.hubs && stats.hubs.length > 0) {
|
|
299
|
+
output += `\n**Hub modules** (most imported):\n\n`;
|
|
300
|
+
for (const hub of stats.hubs.slice(0, 5)) {
|
|
301
|
+
output += `- \`${hub.key}\` โ imported by ${hub.importedBy} files\n`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
output += "\n";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Monorepo
|
|
308
|
+
if (context.monorepo) {
|
|
309
|
+
output += `## Monorepo\n\n`;
|
|
310
|
+
output += `Managed by **${context.monorepo.tool}** with **${context.monorepo.packageCount} packages**.\n\n`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
output += `---
|
|
189
314
|
|
|
190
315
|
*This overview is generated deterministically. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for richer contextual explanations.*`;
|
|
316
|
+
|
|
317
|
+
return output;
|
|
191
318
|
}
|
|
192
319
|
|
|
193
|
-
function getFallbackBusinessDomains(context) {
|
|
320
|
+
function getFallbackBusinessDomains(context, enrichment = {}) {
|
|
321
|
+
const { depGraph } = enrichment;
|
|
322
|
+
const routes = context.routes || {};
|
|
323
|
+
const pages = routes.pages || [];
|
|
324
|
+
const apis = routes.apis || [];
|
|
325
|
+
|
|
194
326
|
let output = `# Business Domains\n\n`;
|
|
195
327
|
output += `> This document maps the codebase into business-oriented functional areas. Each domain represents a distinct area of responsibility.\n\n`;
|
|
196
328
|
output += `**Total domains identified:** ${context.domains.length}\n\n`;
|
|
@@ -206,6 +338,37 @@ function getFallbackBusinessDomains(context) {
|
|
|
206
338
|
if (domain.topModules?.length > 0) {
|
|
207
339
|
output += `**Key modules:** ${domain.topModules.slice(0, 5).map(m => `\`${m}\``).join(", ")}\n\n`;
|
|
208
340
|
}
|
|
341
|
+
|
|
342
|
+
// Match routes to this domain by checking if any domain module paths appear in routes
|
|
343
|
+
const domainModules = domain.topModules || [];
|
|
344
|
+
const domainPages = pages.filter(p =>
|
|
345
|
+
domainModules.some(m => p.path.toLowerCase().includes(m.toLowerCase().split("/").pop()))
|
|
346
|
+
);
|
|
347
|
+
const domainApis = apis.filter(a =>
|
|
348
|
+
domainModules.some(m => a.path.toLowerCase().includes(m.toLowerCase().split("/").pop()))
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (domainPages.length > 0 || domainApis.length > 0) {
|
|
352
|
+
output += `**Routes in this domain:**\n\n`;
|
|
353
|
+
for (const p of domainPages.slice(0, 5)) {
|
|
354
|
+
output += `- ๐ \`${p.path}\`\n`;
|
|
355
|
+
}
|
|
356
|
+
for (const a of domainApis.slice(0, 5)) {
|
|
357
|
+
const methods = a.methods ? ` [${a.methods.join(", ")}]` : "";
|
|
358
|
+
output += `- ๐ \`${a.path}\`${methods}\n`;
|
|
359
|
+
}
|
|
360
|
+
output += "\n";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Cross-domain dependency insights
|
|
365
|
+
if (depGraph?.stats?.hubs && depGraph.stats.hubs.length > 0) {
|
|
366
|
+
output += `## Cross-Domain Dependencies\n\n`;
|
|
367
|
+
output += `The following modules are heavily imported across the codebase, likely serving as shared infrastructure:\n\n`;
|
|
368
|
+
for (const hub of depGraph.stats.hubs.slice(0, 5)) {
|
|
369
|
+
output += `- \`${hub.key}\` โ imported by ${hub.importedBy} modules\n`;
|
|
370
|
+
}
|
|
371
|
+
output += "\n";
|
|
209
372
|
}
|
|
210
373
|
|
|
211
374
|
output += `---\n\n*Domain mapping is based on directory naming conventions. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for natural language descriptions aimed at non-technical stakeholders.*`;
|
|
@@ -213,12 +376,13 @@ function getFallbackBusinessDomains(context) {
|
|
|
213
376
|
return output;
|
|
214
377
|
}
|
|
215
378
|
|
|
216
|
-
function getFallbackArchitectureOverview(context) {
|
|
379
|
+
function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
380
|
+
const { depGraph, driftResult } = enrichment;
|
|
217
381
|
const patternDesc = context.patterns.length > 0
|
|
218
382
|
? `The detected architectural patterns are **${context.patterns.join(", ")}**. These patterns shape how data and control flow through the system.`
|
|
219
383
|
: "No specific architectural patterns were detected. The project appears to follow a straightforward directory-based organization.";
|
|
220
384
|
|
|
221
|
-
|
|
385
|
+
let output = `# Architecture Overview
|
|
222
386
|
|
|
223
387
|
## Architectural Style
|
|
224
388
|
|
|
@@ -231,8 +395,23 @@ The codebase is organized into ${context.domains.length} functional layers, each
|
|
|
231
395
|
| Layer | Description |
|
|
232
396
|
|-------|-------------|
|
|
233
397
|
${context.domains.slice(0, 8).map(d => `| **${d.name}** | ${d.description || "Handles a distinct functional concern"} |`).join("\n")}
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
// Module layer breakdown by type
|
|
401
|
+
const typeGroups = groupModulesByType(context.topModules);
|
|
402
|
+
if (Object.keys(typeGroups).length > 1) {
|
|
403
|
+
output += `\n## Module Layers\n\n`;
|
|
404
|
+
output += `Modules are classified into architectural layers based on their role:\n\n`;
|
|
405
|
+
for (const [type, mods] of Object.entries(typeGroups)) {
|
|
406
|
+
output += `### ${formatModuleType(type)}\n\n`;
|
|
407
|
+
for (const m of mods.slice(0, 5)) {
|
|
408
|
+
output += `- \`${m.key}\` (${m.fileCount} files)\n`;
|
|
409
|
+
}
|
|
410
|
+
output += "\n";
|
|
411
|
+
}
|
|
412
|
+
}
|
|
234
413
|
|
|
235
|
-
|
|
414
|
+
output += `## Technology Stack
|
|
236
415
|
|
|
237
416
|
| Category | Technologies |
|
|
238
417
|
|----------|-------------|
|
|
@@ -243,25 +422,108 @@ ${context.domains.slice(0, 8).map(d => `| **${d.name}** | ${d.description || "Ha
|
|
|
243
422
|
## Scale & Complexity
|
|
244
423
|
|
|
245
424
|
The repository comprises **${context.project.filesScanned} files** organized into **${context.project.modulesDetected} modules**. ${context.project.apiRoutesDetected > 0 ? `It exposes **${context.project.apiRoutesDetected} API endpoint${context.project.apiRoutesDetected === 1 ? "" : "s"}** and` : "It"} serves **${context.project.pagesDetected} application page${context.project.pagesDetected === 1 ? "" : "s"}**.
|
|
425
|
+
`;
|
|
246
426
|
|
|
247
|
-
|
|
427
|
+
// Dependency graph health
|
|
428
|
+
if (depGraph?.stats) {
|
|
429
|
+
const stats = depGraph.stats;
|
|
430
|
+
output += `\n## Dependency Health\n\n`;
|
|
431
|
+
output += `| Metric | Value |\n`;
|
|
432
|
+
output += `|--------|-------|\n`;
|
|
433
|
+
output += `| Import edges | ${stats.totalEdges} |\n`;
|
|
434
|
+
output += `| External packages | ${stats.externalDeps} |\n`;
|
|
435
|
+
output += `| Circular dependencies | ${stats.cycles} |\n`;
|
|
436
|
+
output += `| Orphan files | ${stats.orphanFiles} |\n\n`;
|
|
437
|
+
|
|
438
|
+
// Strengths
|
|
439
|
+
const strengths = [];
|
|
440
|
+
const concerns = [];
|
|
441
|
+
if (stats.cycles === 0) strengths.push("No circular dependencies โ clean module boundaries");
|
|
442
|
+
if (stats.orphanFiles === 0) strengths.push("No orphan files โ all code is connected");
|
|
443
|
+
if (stats.hubs && stats.hubs.length > 0 && stats.hubs[0].importedBy < stats.totalFiles * 0.3)
|
|
444
|
+
strengths.push("No excessively coupled hubs");
|
|
445
|
+
|
|
446
|
+
if (stats.cycles > 0) concerns.push(`${stats.cycles} circular dependenc${stats.cycles === 1 ? "y" : "ies"} โ may hinder testing and refactoring`);
|
|
447
|
+
if (stats.orphanFiles > stats.totalFiles * 0.2)
|
|
448
|
+
concerns.push(`High orphan file ratio (${stats.orphanFiles}/${stats.totalFiles}) โ possible dead code`);
|
|
449
|
+
if (stats.hubs && stats.hubs.length > 0 && stats.hubs[0].importedBy >= stats.totalFiles * 0.3)
|
|
450
|
+
concerns.push(`\`${stats.hubs[0].key}\` is imported by ${stats.hubs[0].importedBy} files โ high coupling risk`);
|
|
451
|
+
|
|
452
|
+
if (strengths.length > 0) {
|
|
453
|
+
output += `**Strengths:**\n\n`;
|
|
454
|
+
for (const s of strengths) output += `- โ
${s}\n`;
|
|
455
|
+
output += "\n";
|
|
456
|
+
}
|
|
457
|
+
if (concerns.length > 0) {
|
|
458
|
+
output += `**Concerns:**\n\n`;
|
|
459
|
+
for (const c of concerns) output += `- โ ๏ธ ${c}\n`;
|
|
460
|
+
output += "\n";
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (stats.hubs && stats.hubs.length > 0) {
|
|
464
|
+
output += `**Hub modules** (most imported):\n\n`;
|
|
465
|
+
for (const hub of stats.hubs.slice(0, 5)) {
|
|
466
|
+
output += `- \`${hub.key}\` โ imported by ${hub.importedBy} files\n`;
|
|
467
|
+
}
|
|
468
|
+
output += "\n";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Drift summary
|
|
473
|
+
if (driftResult?.drifts && driftResult.drifts.length > 0) {
|
|
474
|
+
output += `## Architecture Drift\n\n`;
|
|
475
|
+
output += `${driftResult.drifts.length} drift${driftResult.drifts.length === 1 ? "" : "s"} detected since last baseline:\n\n`;
|
|
476
|
+
for (const drift of driftResult.drifts.slice(0, 5)) {
|
|
477
|
+
output += `- **${drift.type}**: ${drift.description || drift.message || "Change detected"}\n`;
|
|
478
|
+
}
|
|
479
|
+
output += "\n";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Monorepo
|
|
483
|
+
if (context.monorepo) {
|
|
484
|
+
output += `## Monorepo Architecture\n\n`;
|
|
485
|
+
output += `This project is a **${context.monorepo.tool}** monorepo with **${context.monorepo.packageCount} packages**:\n\n`;
|
|
486
|
+
for (const pkg of context.monorepo.packages.slice(0, 10)) {
|
|
487
|
+
output += `- \`${pkg.name}\` โ ${pkg.path}\n`;
|
|
488
|
+
}
|
|
489
|
+
output += "\n";
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
output += `---
|
|
248
493
|
|
|
249
494
|
*This architecture overview is generated deterministically. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for deeper architectural narratives.*`;
|
|
495
|
+
|
|
496
|
+
return output;
|
|
250
497
|
}
|
|
251
498
|
|
|
252
|
-
function getFallbackDataFlows(flows) {
|
|
499
|
+
function getFallbackDataFlows(flows, context, enrichment = {}) {
|
|
500
|
+
const { depGraph, scanResult } = enrichment;
|
|
501
|
+
|
|
253
502
|
let output = `# Data Flows\n\n`;
|
|
254
503
|
output += `> Data flows describe how information moves through the system โ from external inputs through processing layers to storage or presentation.\n\n`;
|
|
255
504
|
|
|
256
|
-
|
|
505
|
+
// Combine heuristic flows with dep-graph-derived flows
|
|
506
|
+
const allFlows = [...(flows || [])];
|
|
507
|
+
|
|
508
|
+
// Generate additional flows from dependency graph hub chains
|
|
509
|
+
if (depGraph?.nodes && depGraph.nodes.length > 0 && allFlows.length < 3) {
|
|
510
|
+
const hubFlows = inferFlowsFromDepGraph(depGraph);
|
|
511
|
+
for (const hf of hubFlows) {
|
|
512
|
+
if (!allFlows.some(f => f.name === hf.name)) {
|
|
513
|
+
allFlows.push(hf);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (allFlows.length === 0) {
|
|
257
519
|
output += `No data flows were detected. This typically means the system uses straightforward requestโresponse patterns without distinct multi-step pipelines.\n\n`;
|
|
258
520
|
output += `---\n\n*Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for heuristic-based flow descriptions.*`;
|
|
259
521
|
return output;
|
|
260
522
|
}
|
|
261
523
|
|
|
262
|
-
output += `**${
|
|
524
|
+
output += `**${allFlows.length} flow${allFlows.length === 1 ? "" : "s"} detected** in the codebase.\n\n---\n\n`;
|
|
263
525
|
|
|
264
|
-
for (const flow of
|
|
526
|
+
for (const flow of allFlows) {
|
|
265
527
|
output += `## ${flow.name}\n\n`;
|
|
266
528
|
output += `${flow.description}\n\n`;
|
|
267
529
|
if (flow.steps && flow.steps.length > 0) {
|
|
@@ -273,6 +535,30 @@ function getFallbackDataFlows(flows) {
|
|
|
273
535
|
if (flow.modules && flow.modules.length > 0) {
|
|
274
536
|
output += `**Involved modules:** ${flow.modules.map(m => `\`${m}\``).join(", ")}\n\n`;
|
|
275
537
|
}
|
|
538
|
+
|
|
539
|
+
// Add dependency context if available
|
|
540
|
+
if (scanResult && flow.modules) {
|
|
541
|
+
const deps = identifyFlowDependencies(flow, scanResult);
|
|
542
|
+
if (deps.sharedLibraries.length > 0) {
|
|
543
|
+
output += `**Shared libraries used:** ${deps.sharedLibraries.map(m => `\`${m}\``).join(", ")}\n\n`;
|
|
544
|
+
}
|
|
545
|
+
if (deps.externalDependencies.length > 0) {
|
|
546
|
+
output += `**External services:** ${deps.externalDependencies.join(", ")}\n\n`;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Import chain summary
|
|
552
|
+
if (depGraph?.stats) {
|
|
553
|
+
output += `## Import Network\n\n`;
|
|
554
|
+
output += `The system has **${depGraph.stats.totalEdges} internal import edges** connecting ${depGraph.stats.totalFiles} source files.\n\n`;
|
|
555
|
+
if (depGraph.stats.hubs && depGraph.stats.hubs.length > 0) {
|
|
556
|
+
output += `Key integration points (most referenced modules):\n\n`;
|
|
557
|
+
for (const hub of depGraph.stats.hubs.slice(0, 5)) {
|
|
558
|
+
output += `- \`${hub.key}\` โ referenced by ${hub.importedBy} files\n`;
|
|
559
|
+
}
|
|
560
|
+
output += "\n";
|
|
561
|
+
}
|
|
276
562
|
}
|
|
277
563
|
|
|
278
564
|
output += `---\n\n*Flow detection is based on naming conventions and import patterns. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for natural language flow narratives.*`;
|
|
@@ -280,11 +566,16 @@ function getFallbackDataFlows(flows) {
|
|
|
280
566
|
return output;
|
|
281
567
|
}
|
|
282
568
|
|
|
283
|
-
function getFallbackDeveloperOnboarding(context) {
|
|
569
|
+
function getFallbackDeveloperOnboarding(context, enrichment = {}) {
|
|
570
|
+
const { flows, depGraph } = enrichment;
|
|
284
571
|
const frameworkList = context.techStack.frameworks.join(", ") || "general-purpose tools";
|
|
285
572
|
const languageList = context.techStack.languages.join(", ") || "standard languages";
|
|
573
|
+
const testFrameworks = context.techStack.testFrameworks || [];
|
|
574
|
+
const routes = context.routes || {};
|
|
575
|
+
const pages = routes.pages || [];
|
|
576
|
+
const apis = routes.apis || [];
|
|
286
577
|
|
|
287
|
-
|
|
578
|
+
let output = `# Developer Onboarding
|
|
288
579
|
|
|
289
580
|
## Welcome
|
|
290
581
|
|
|
@@ -297,51 +588,179 @@ The top-level directory is organized as follows:
|
|
|
297
588
|
| Directory | Purpose |
|
|
298
589
|
|-----------|---------|
|
|
299
590
|
${context.repoRoots.map(root => `| \`${root}\` | ${describeRoot(root)} |`).join("\n")}
|
|
591
|
+
`;
|
|
592
|
+
|
|
593
|
+
// Monorepo navigation
|
|
594
|
+
if (context.monorepo) {
|
|
595
|
+
output += `\n## Monorepo Navigation\n\n`;
|
|
596
|
+
output += `This is a **${context.monorepo.tool}** monorepo. Key packages:\n\n`;
|
|
597
|
+
for (const pkg of context.monorepo.packages.slice(0, 10)) {
|
|
598
|
+
output += `- \`${pkg.name}\` โ \`${pkg.path}\`\n`;
|
|
599
|
+
}
|
|
600
|
+
output += "\n";
|
|
601
|
+
}
|
|
300
602
|
|
|
301
|
-
|
|
603
|
+
output += `## Technology Stack
|
|
302
604
|
|
|
303
605
|
| Category | Technologies |
|
|
304
606
|
|----------|-------------|
|
|
305
607
|
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
306
608
|
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
307
609
|
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
308
|
-
|
|
610
|
+
${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")} |\n` : ""}
|
|
309
611
|
## Largest Modules
|
|
310
612
|
|
|
311
613
|
These are the primary modules by file count โ good starting points for understanding the system:
|
|
312
614
|
|
|
313
|
-
| Module | Files |
|
|
314
|
-
|
|
315
|
-
${context.topModules.slice(0, 10).map(
|
|
615
|
+
| Module | Files | Type |
|
|
616
|
+
|--------|-------|------|
|
|
617
|
+
${context.topModules.slice(0, 10).map(m => `| \`${m.key}\` | ${m.fileCount} | ${formatModuleType(m.type)} |`).join("\n")}
|
|
618
|
+
`;
|
|
619
|
+
|
|
620
|
+
// Key routes to explore
|
|
621
|
+
if (pages.length > 0 || apis.length > 0) {
|
|
622
|
+
output += `\n## Key Routes\n\n`;
|
|
623
|
+
if (pages.length > 0) {
|
|
624
|
+
output += `**Pages to explore:**\n\n`;
|
|
625
|
+
for (const p of pages.slice(0, 5)) {
|
|
626
|
+
output += `- \`${p.path}\` โ ${p.file}\n`;
|
|
627
|
+
}
|
|
628
|
+
output += "\n";
|
|
629
|
+
}
|
|
630
|
+
if (apis.length > 0) {
|
|
631
|
+
output += `**API endpoints:**\n\n`;
|
|
632
|
+
for (const a of apis.slice(0, 5)) {
|
|
633
|
+
const methods = a.methods ? ` [${a.methods.join(", ")}]` : "";
|
|
634
|
+
output += `- \`${a.path}\`${methods} โ ${a.file}\n`;
|
|
635
|
+
}
|
|
636
|
+
output += "\n";
|
|
637
|
+
}
|
|
638
|
+
}
|
|
316
639
|
|
|
317
|
-
|
|
640
|
+
// Data flows overview
|
|
641
|
+
if (flows && flows.length > 0) {
|
|
642
|
+
output += `## How Data Flows\n\n`;
|
|
643
|
+
output += `Understanding these flows will help you see how the system works end-to-end:\n\n`;
|
|
644
|
+
for (const flow of flows) {
|
|
645
|
+
output += `- **${flow.name}** โ ${flow.description}\n`;
|
|
646
|
+
}
|
|
647
|
+
output += "\n";
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Dependency orientation
|
|
651
|
+
if (depGraph?.stats?.hubs && depGraph.stats.hubs.length > 0) {
|
|
652
|
+
output += `## Key Integration Points\n\n`;
|
|
653
|
+
output += `These modules are the most widely imported โ changes to them may have broad impact:\n\n`;
|
|
654
|
+
for (const hub of depGraph.stats.hubs.slice(0, 5)) {
|
|
655
|
+
output += `- \`${hub.key}\` โ imported by ${hub.importedBy} files\n`;
|
|
656
|
+
}
|
|
657
|
+
output += "\n";
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
output += `## Getting Started
|
|
318
661
|
|
|
319
662
|
1. Clone the repository and install dependencies
|
|
320
663
|
2. Review the top-level directories above to understand the project layout
|
|
321
664
|
3. Start with the largest modules listed above โ they contain the core functionality
|
|
322
|
-
4. Check for a \`README.md\` or \`CONTRIBUTING.md\` file for project-specific setup instructions
|
|
665
|
+
4. Check for a \`README.md\` or \`CONTRIBUTING.md\` file for project-specific setup instructions`;
|
|
323
666
|
|
|
324
|
-
|
|
667
|
+
// Test framework quickstart
|
|
668
|
+
if (testFrameworks.length > 0) {
|
|
669
|
+
output += `\n5. Run tests with your ${testFrameworks[0]} setup to verify everything works`;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
output += `\n\n---
|
|
325
673
|
|
|
326
674
|
*This onboarding guide is generated deterministically. Enable AI enhancement (\`REPOLENS_AI_ENABLED=true\`) for a narrative-style guide with tips and common pitfalls.*`;
|
|
675
|
+
|
|
676
|
+
return output;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Helper: group top modules by their inferred type
|
|
680
|
+
function groupModulesByType(topModules) {
|
|
681
|
+
const groups = {};
|
|
682
|
+
for (const m of topModules) {
|
|
683
|
+
const type = m.type || "other";
|
|
684
|
+
if (!groups[type]) groups[type] = [];
|
|
685
|
+
groups[type].push(m);
|
|
686
|
+
}
|
|
687
|
+
return groups;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Helper: human-readable module type label
|
|
691
|
+
function formatModuleType(type) {
|
|
692
|
+
const labels = {
|
|
693
|
+
api: "API Layer",
|
|
694
|
+
ui: "UI Components",
|
|
695
|
+
library: "Shared Libraries",
|
|
696
|
+
hooks: "React Hooks",
|
|
697
|
+
state: "State Management",
|
|
698
|
+
route: "Route/Page Modules",
|
|
699
|
+
app: "Application Core",
|
|
700
|
+
other: "Other Modules",
|
|
701
|
+
};
|
|
702
|
+
return labels[type] || type;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Helper: derive flows from dependency graph hub chains
|
|
706
|
+
function inferFlowsFromDepGraph(depGraph) {
|
|
707
|
+
const flows = [];
|
|
708
|
+
if (!depGraph?.nodes || depGraph.nodes.length === 0) return flows;
|
|
709
|
+
|
|
710
|
+
// Find hub nodes โ modules imported by many others
|
|
711
|
+
const hubThreshold = Math.max(3, Math.floor(depGraph.nodes.length * 0.05));
|
|
712
|
+
const hubs = depGraph.nodes
|
|
713
|
+
.filter(n => n.importedBy.length >= hubThreshold)
|
|
714
|
+
.sort((a, b) => b.importedBy.length - a.importedBy.length)
|
|
715
|
+
.slice(0, 3);
|
|
716
|
+
|
|
717
|
+
for (const hub of hubs) {
|
|
718
|
+
const importers = hub.importedBy.slice(0, 5);
|
|
719
|
+
const downstream = hub.imports.slice(0, 3);
|
|
720
|
+
const shortName = hub.key.split("/").pop();
|
|
721
|
+
|
|
722
|
+
flows.push({
|
|
723
|
+
name: `${shortName} Integration Flow`,
|
|
724
|
+
description: `\`${hub.key}\` is a central module imported by ${hub.importedBy.length} files, acting as an integration point for data and logic`,
|
|
725
|
+
steps: [
|
|
726
|
+
...importers.map(i => `\`${i.split("/").pop()}\` imports \`${shortName}\``),
|
|
727
|
+
...(downstream.length > 0 ? [`\`${shortName}\` depends on ${downstream.map(d => `\`${d.split("/").pop()}\``).join(", ")}`] : []),
|
|
728
|
+
],
|
|
729
|
+
modules: [hub.key, ...importers.slice(0, 3)],
|
|
730
|
+
critical: hub.importedBy.length >= hubThreshold * 2,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return flows;
|
|
327
735
|
}
|
|
328
736
|
|
|
329
737
|
function describeRoot(root) {
|
|
330
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();
|
|
331
741
|
if (/^src$|^lib$/.test(lower)) return "Application source code";
|
|
332
|
-
if (/^test|^__test|^spec/.test(
|
|
333
|
-
if (/^doc/.test(
|
|
334
|
-
if (/^bin$|^scripts?$/.test(
|
|
335
|
-
if (/^config/.test(
|
|
336
|
-
if (/^public$|^static$|^assets$/.test(
|
|
337
|
-
if (/^dist$|^build$|^out$/.test(
|
|
338
|
-
if (/^\.github$/.test(
|
|
339
|
-
if (/^api
|
|
340
|
-
if (/^components
|
|
341
|
-
if (/^pages?$|^views?$|^screens?$/.test(
|
|
342
|
-
if (/^utils?$|^helpers?$/.test(
|
|
343
|
-
if (/^services?$/.test(
|
|
344
|
-
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";
|
|
345
764
|
return "Project files";
|
|
346
765
|
}
|
|
347
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
|
|
|
@@ -184,16 +184,16 @@ async function generateDocument(docPlan, context) {
|
|
|
184
184
|
|
|
185
185
|
switch (key) {
|
|
186
186
|
case "executive_summary":
|
|
187
|
-
return await generateExecutiveSummary(aiContext);
|
|
187
|
+
return await generateExecutiveSummary(aiContext, { depGraph, flows });
|
|
188
188
|
|
|
189
189
|
case "system_overview":
|
|
190
|
-
return await generateSystemOverview(aiContext);
|
|
190
|
+
return await generateSystemOverview(aiContext, { depGraph });
|
|
191
191
|
|
|
192
192
|
case "business_domains":
|
|
193
|
-
return await generateBusinessDomains(aiContext);
|
|
193
|
+
return await generateBusinessDomains(aiContext, { depGraph });
|
|
194
194
|
|
|
195
195
|
case "architecture_overview":
|
|
196
|
-
return await generateArchitectureOverview(aiContext);
|
|
196
|
+
return await generateArchitectureOverview(aiContext, { depGraph, driftResult });
|
|
197
197
|
|
|
198
198
|
case "module_catalog":
|
|
199
199
|
// Hybrid: deterministic skeleton + ownership info
|
|
@@ -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);
|
|
211
|
+
return await generateDataFlows(flows, aiContext, { depGraph, scanResult });
|
|
212
212
|
|
|
213
213
|
case "arch_diff":
|
|
214
214
|
if (!diffData) {
|
|
@@ -221,7 +221,7 @@ async function generateDocument(docPlan, context) {
|
|
|
221
221
|
return renderSystemMap(scanResult, config, depGraph);
|
|
222
222
|
|
|
223
223
|
case "developer_onboarding":
|
|
224
|
-
return await generateDeveloperOnboarding(aiContext);
|
|
224
|
+
return await generateDeveloperOnboarding(aiContext, { flows, depGraph });
|
|
225
225
|
|
|
226
226
|
case "graphql_schema":
|
|
227
227
|
return renderGraphQLSchema(graphqlResult);
|
|
@@ -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
|
}
|