@chappibunny/repolens 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/package.json +5 -4
- package/src/ai/generate-sections.js +63 -36
- package/src/analyzers/context-builder.js +224 -26
- package/src/analyzers/domain-inference.js +56 -1
- package/src/analyzers/flow-inference.js +117 -10
- package/src/analyzers/graphql-analyzer.js +7 -2
- package/src/core/scan.js +70 -33
- package/src/docs/generate-doc-set.js +14 -9
- package/src/renderers/render.js +104 -54
- package/src/renderers/renderDiff.js +23 -3
- package/src/renderers/renderMap.js +93 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.7.0
|
|
6
|
+
|
|
7
|
+
### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **Dependency-weighted module roles**: Module catalog now shows import-based role labels (e.g. "Orchestrator — coordinates 54 modules", "Critical shared infrastructure — imported by 55 modules") derived from real dependency graph metrics.
|
|
10
|
+
- **Context-aware route map**: CLI tools now get a helpful explanation ("This project is a CLI tool — it does not expose HTTP routes") instead of an empty route table with a stale footer.
|
|
11
|
+
- **Verified architecture patterns**: Detected patterns now include confidence labels (e.g. "naming only" vs evidence-backed) based on dependency graph verification.
|
|
12
|
+
- **Import-chain data flows**: Data flows are now traced from real entry points via import chains, replacing hardcoded template flows. Shows actual module dependencies and downstream traces.
|
|
13
|
+
- **CLI-aware tech stack labels**: All 4 key documents (Executive Summary, System Overview, Architecture Overview, Developer Onboarding) now show "N/A (CLI tool)" instead of "Not detected" for Frameworks and Build Tools when the project is a CLI tool.
|
|
14
|
+
- **Test flow filtering**: Test file flows are now filtered from Executive Summary, Data Flows, and Developer Onboarding documents, keeping output focused on production code paths.
|
|
15
|
+
- **Hub flow cleanup**: Integration flows derived from dependency graph hubs no longer list test files as importers in their step chains.
|
|
16
|
+
|
|
17
|
+
### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- **GraphQL false positives**: The GraphQL analyzer now excludes test files and its own analysis/rendering source files from scanning, preventing self-detection of library patterns (e.g. detecting "Apollo Server" from regex pattern strings).
|
|
20
|
+
- **GraphQL pattern precision**: The `graphql-js` detection pattern now uses word boundaries (`\bGraphQLSchema\b`) to avoid false matches from function names like `renderGraphQLSchema`.
|
|
21
|
+
- **Change impact noise filtering**: Architecture diff now filters `node_modules/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `.repolens/`, and `.git/` from both the Impacted Modules list and the file lists (Added/Removed/Modified).
|
|
22
|
+
- **Zero-value suppression**: Documents no longer show "serves 0 application pages" or "0 API endpoints" for CLI tools — these rows are conditionally hidden when values are zero.
|
|
23
|
+
- **Module label enrichment**: `describeModule()` now recognizes 6 additional module types (plugin, prompt, provider, generate/section, ai/ml), reducing generic "Application module" fallbacks.
|
|
24
|
+
|
|
5
25
|
## 1.6.0
|
|
6
26
|
|
|
7
27
|
### ✨ Features
|
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.7.0",
|
|
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,11 @@
|
|
|
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"
|
|
70
|
+
},
|
|
71
|
+
"overrides": {
|
|
72
|
+
"yauzl": ">=3.2.1"
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -125,6 +125,18 @@ function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
|
125
125
|
const languageList = context.techStack.languages.join(", ") || "multiple languages";
|
|
126
126
|
const domainSummary = context.domains.slice(0, 5).map(d => d.name).join(", ");
|
|
127
127
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
128
|
+
const isCLI = (context.patterns || []).some(p => p.toLowerCase().includes("cli"));
|
|
129
|
+
|
|
130
|
+
// Build the "what it does" line based on project type
|
|
131
|
+
let interfaceLine;
|
|
132
|
+
if (isCLI) {
|
|
133
|
+
interfaceLine = "It operates as a **command-line tool**, interacting through terminal commands rather than a web interface.";
|
|
134
|
+
} else {
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (context.project.apiRoutesDetected > 0) parts.push(`exposes **${context.project.apiRoutesDetected} API endpoint${context.project.apiRoutesDetected === 1 ? "" : "s"}**`);
|
|
137
|
+
if (context.project.pagesDetected > 0) parts.push(`serves **${context.project.pagesDetected} application page${context.project.pagesDetected === 1 ? "" : "s"}** to end users`);
|
|
138
|
+
interfaceLine = parts.length > 0 ? `It ${parts.join(" and ")}.` : "";
|
|
139
|
+
}
|
|
128
140
|
|
|
129
141
|
let output = `# Executive Summary
|
|
130
142
|
|
|
@@ -132,7 +144,7 @@ function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
|
132
144
|
|
|
133
145
|
${context.project.name} is a ${frameworkList} application built with ${languageList}. The codebase contains **${context.project.modulesDetected} modules** spread across **${context.project.filesScanned} files**, organized into ${context.domains.length} functional domain${context.domains.length === 1 ? "" : "s"}.
|
|
134
146
|
|
|
135
|
-
${
|
|
147
|
+
${interfaceLine}
|
|
136
148
|
|
|
137
149
|
## Primary Functional Areas
|
|
138
150
|
|
|
@@ -146,9 +158,9 @@ ${context.domains.map(d => `| ${d.name} | ${d.moduleCount} | ${d.description ||
|
|
|
146
158
|
|
|
147
159
|
| Category | Details |
|
|
148
160
|
|----------|---------|
|
|
149
|
-
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
161
|
+
| Frameworks | ${context.techStack.frameworks.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
150
162
|
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
151
|
-
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
163
|
+
| Build Tools | ${context.techStack.buildTools.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
152
164
|
${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")} |\n` : ""}`;
|
|
153
165
|
|
|
154
166
|
// Module type breakdown
|
|
@@ -188,11 +200,12 @@ ${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")}
|
|
|
188
200
|
}
|
|
189
201
|
}
|
|
190
202
|
|
|
191
|
-
// Data flows
|
|
192
|
-
|
|
203
|
+
// Data flows (filter out test file flows for exec summary)
|
|
204
|
+
const summaryFlows = (flows || []).filter(f => !f.name?.toLowerCase().includes('test'));
|
|
205
|
+
if (summaryFlows.length > 0) {
|
|
193
206
|
output += `\n## Key Data Flows\n\n`;
|
|
194
|
-
output += `${
|
|
195
|
-
for (const flow of
|
|
207
|
+
output += `${summaryFlows.length} data flow${summaryFlows.length === 1 ? "" : "s"} identified:\n\n`;
|
|
208
|
+
for (const flow of summaryFlows) {
|
|
196
209
|
output += `- **${flow.name}**${flow.critical ? " (critical)" : ""} — ${flow.description}\n`;
|
|
197
210
|
}
|
|
198
211
|
}
|
|
@@ -213,6 +226,7 @@ function getFallbackSystemOverview(context, enrichment = {}) {
|
|
|
213
226
|
const sizeLabel = context.project.modulesDetected > 50 ? "large-scale" :
|
|
214
227
|
context.project.modulesDetected > 20 ? "medium-sized" : "focused";
|
|
215
228
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
229
|
+
const isCLI = (context.patterns || []).some(p => p.toLowerCase().includes("cli"));
|
|
216
230
|
|
|
217
231
|
let output = `# System Overview
|
|
218
232
|
|
|
@@ -224,16 +238,14 @@ This is a ${sizeLabel} codebase organized into **${context.project.modulesDetect
|
|
|
224
238
|
|--------|-------|
|
|
225
239
|
| Files scanned | ${context.project.filesScanned} |
|
|
226
240
|
| Modules | ${context.project.modulesDetected} |
|
|
227
|
-
|
|
228
|
-
| API endpoints | ${context.project.apiRoutesDetected} |
|
|
229
|
-
|
|
241
|
+
${context.project.pagesDetected > 0 ? `| Application pages | ${context.project.pagesDetected} |\n` : ""}${context.project.apiRoutesDetected > 0 ? `| API endpoints | ${context.project.apiRoutesDetected} |\n` : ""}
|
|
230
242
|
## Technology Stack
|
|
231
243
|
|
|
232
244
|
| Category | Technologies |
|
|
233
245
|
|----------|-------------|
|
|
234
|
-
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
246
|
+
| Frameworks | ${context.techStack.frameworks.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
235
247
|
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
236
|
-
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
248
|
+
| Build Tools | ${context.techStack.buildTools.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
237
249
|
${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")} |\n` : ""}
|
|
238
250
|
## Detected Patterns
|
|
239
251
|
|
|
@@ -378,6 +390,7 @@ function getFallbackBusinessDomains(context, enrichment = {}) {
|
|
|
378
390
|
|
|
379
391
|
function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
380
392
|
const { depGraph, driftResult } = enrichment;
|
|
393
|
+
const isCLI = (context.patterns || []).some(p => p.toLowerCase().includes("cli"));
|
|
381
394
|
const patternDesc = context.patterns.length > 0
|
|
382
395
|
? `The detected architectural patterns are **${context.patterns.join(", ")}**. These patterns shape how data and control flow through the system.`
|
|
383
396
|
: "No specific architectural patterns were detected. The project appears to follow a straightforward directory-based organization.";
|
|
@@ -415,13 +428,13 @@ ${context.domains.slice(0, 8).map(d => `| **${d.name}** | ${d.description || "Ha
|
|
|
415
428
|
|
|
416
429
|
| Category | Technologies |
|
|
417
430
|
|----------|-------------|
|
|
418
|
-
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
431
|
+
| Frameworks | ${context.techStack.frameworks.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
419
432
|
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
420
|
-
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
433
|
+
| Build Tools | ${context.techStack.buildTools.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
421
434
|
|
|
422
435
|
## Scale & Complexity
|
|
423
436
|
|
|
424
|
-
The repository comprises **${context.project.filesScanned} files** organized into **${context.project.modulesDetected} modules
|
|
437
|
+
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"}**.` : ""}${context.project.pagesDetected > 0 ? ` It serves **${context.project.pagesDetected} application page${context.project.pagesDetected === 1 ? "" : "s"}**.` : ""}${isCLI ? " It operates as a command-line tool." : ""}
|
|
425
438
|
`;
|
|
426
439
|
|
|
427
440
|
// Dependency graph health
|
|
@@ -502,8 +515,8 @@ function getFallbackDataFlows(flows, context, enrichment = {}) {
|
|
|
502
515
|
let output = `# Data Flows\n\n`;
|
|
503
516
|
output += `> Data flows describe how information moves through the system — from external inputs through processing layers to storage or presentation.\n\n`;
|
|
504
517
|
|
|
505
|
-
// Combine heuristic flows with dep-graph-derived flows
|
|
506
|
-
const allFlows = [...(flows || [])];
|
|
518
|
+
// Combine heuristic flows with dep-graph-derived flows, filtering out test file flows
|
|
519
|
+
const allFlows = [...(flows || [])].filter(f => !f.name?.toLowerCase().includes('test'));
|
|
507
520
|
|
|
508
521
|
// Generate additional flows from dependency graph hub chains
|
|
509
522
|
if (depGraph?.nodes && depGraph.nodes.length > 0 && allFlows.length < 3) {
|
|
@@ -571,6 +584,7 @@ function getFallbackDeveloperOnboarding(context, enrichment = {}) {
|
|
|
571
584
|
const frameworkList = context.techStack.frameworks.join(", ") || "general-purpose tools";
|
|
572
585
|
const languageList = context.techStack.languages.join(", ") || "standard languages";
|
|
573
586
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
587
|
+
const isCLI = (context.patterns || []).some(p => p.toLowerCase().includes("cli"));
|
|
574
588
|
const routes = context.routes || {};
|
|
575
589
|
const pages = routes.pages || [];
|
|
576
590
|
const apis = routes.apis || [];
|
|
@@ -604,9 +618,9 @@ ${context.repoRoots.map(root => `| \`${root}\` | ${describeRoot(root)} |`).join(
|
|
|
604
618
|
|
|
605
619
|
| Category | Technologies |
|
|
606
620
|
|----------|-------------|
|
|
607
|
-
| Frameworks | ${context.techStack.frameworks.join(", ") || "Not detected"} |
|
|
621
|
+
| Frameworks | ${context.techStack.frameworks.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
608
622
|
| Languages | ${context.techStack.languages.join(", ") || "Not detected"} |
|
|
609
|
-
| Build Tools | ${context.techStack.buildTools.join(", ") || "Not detected"} |
|
|
623
|
+
| Build Tools | ${context.techStack.buildTools.join(", ") || (isCLI ? "N/A (CLI tool)" : "Not detected")} |
|
|
610
624
|
${testFrameworks.length > 0 ? `| Test Frameworks | ${testFrameworks.join(", ")} |\n` : ""}
|
|
611
625
|
## Largest Modules
|
|
612
626
|
|
|
@@ -637,11 +651,12 @@ ${context.topModules.slice(0, 10).map(m => `| \`${m.key}\` | ${m.fileCount} | ${
|
|
|
637
651
|
}
|
|
638
652
|
}
|
|
639
653
|
|
|
640
|
-
// Data flows overview
|
|
641
|
-
|
|
654
|
+
// Data flows overview (filter out test flows)
|
|
655
|
+
const onboardingFlows = (flows || []).filter(f => !f.name?.toLowerCase().includes('test'));
|
|
656
|
+
if (onboardingFlows.length > 0) {
|
|
642
657
|
output += `## How Data Flows\n\n`;
|
|
643
658
|
output += `Understanding these flows will help you see how the system works end-to-end:\n\n`;
|
|
644
|
-
for (const flow of
|
|
659
|
+
for (const flow of onboardingFlows) {
|
|
645
660
|
output += `- **${flow.name}** — ${flow.description}\n`;
|
|
646
661
|
}
|
|
647
662
|
output += "\n";
|
|
@@ -715,7 +730,8 @@ function inferFlowsFromDepGraph(depGraph) {
|
|
|
715
730
|
.slice(0, 3);
|
|
716
731
|
|
|
717
732
|
for (const hub of hubs) {
|
|
718
|
-
const
|
|
733
|
+
const testPattern = /(?:^|\/)(?:tests?|__tests?__|spec|__spec__)\/|\.(test|spec)\.[jt]sx?$/i;
|
|
734
|
+
const importers = hub.importedBy.filter(i => !testPattern.test(i)).slice(0, 5);
|
|
719
735
|
const downstream = hub.imports.slice(0, 3);
|
|
720
736
|
const shortName = hub.key.split("/").pop();
|
|
721
737
|
|
|
@@ -736,20 +752,31 @@ function inferFlowsFromDepGraph(depGraph) {
|
|
|
736
752
|
|
|
737
753
|
function describeRoot(root) {
|
|
738
754
|
const lower = root.toLowerCase().replace(/\/$/, "");
|
|
755
|
+
// Check the last segment for nested paths like src/core, src/analyzers
|
|
756
|
+
const lastSeg = lower.split("/").pop();
|
|
739
757
|
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(
|
|
758
|
+
if (/^test|^__test|^spec/.test(lastSeg)) return "Test suites";
|
|
759
|
+
if (/^doc/.test(lastSeg)) return "Documentation";
|
|
760
|
+
if (/^bin$|^scripts?$/.test(lastSeg)) return "CLI entry points and scripts";
|
|
761
|
+
if (/^config/.test(lastSeg)) return "Configuration files";
|
|
762
|
+
if (/^public$|^static$|^assets$/.test(lastSeg)) return "Static assets";
|
|
763
|
+
if (/^dist$|^build$|^out$/.test(lastSeg)) return "Build output";
|
|
764
|
+
if (/^\.github$/.test(lastSeg)) return "GitHub Actions and workflows";
|
|
765
|
+
if (/^api$|^endpoint/.test(lastSeg)) return "API definitions";
|
|
766
|
+
if (/^components?$|^ui$/.test(lastSeg)) return "Shared UI components";
|
|
767
|
+
if (/^pages?$|^views?$|^screens?$/.test(lastSeg)) return "Application pages/views";
|
|
768
|
+
if (/^utils?$|^helpers?$/.test(lastSeg)) return "Utility functions";
|
|
769
|
+
if (/^services?$/.test(lastSeg)) return "Service layer";
|
|
770
|
+
if (/^hooks?$/.test(lastSeg)) return "Custom hooks";
|
|
771
|
+
if (/^core$|^kernel$|^engine$/.test(lastSeg)) return "Core logic and foundations";
|
|
772
|
+
if (/^analyz/.test(lastSeg)) return "Code analysis and detection";
|
|
773
|
+
if (/^render/.test(lastSeg)) return "Rendering and output formatting";
|
|
774
|
+
if (/^publish/.test(lastSeg)) return "Publishing and distribution";
|
|
775
|
+
if (/^deliver/.test(lastSeg)) return "Content delivery";
|
|
776
|
+
if (/^integrat/.test(lastSeg)) return "Third-party integrations";
|
|
777
|
+
if (/^plugin/.test(lastSeg)) return "Plugin and extension system";
|
|
778
|
+
if (/^ai$|^ml$|^llm$/.test(lastSeg)) return "AI/ML features and providers";
|
|
779
|
+
if (/^middleware/.test(lastSeg)) return "Middleware pipeline";
|
|
753
780
|
return "Project files";
|
|
754
781
|
}
|
|
755
782
|
|
|
@@ -2,7 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
import { groupModulesByDomain } from "./domain-inference.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Compute per-module dependency metrics from the dep graph.
|
|
7
|
+
* Returns a Map<normalizedKey, { fanIn, fanOut, isHub, isOrphan, isLeaf, isOrchestrator }>.
|
|
8
|
+
*/
|
|
9
|
+
export function computeModuleDepMetrics(depGraph) {
|
|
10
|
+
const metrics = new Map();
|
|
11
|
+
if (!depGraph?.nodes) return metrics;
|
|
12
|
+
|
|
13
|
+
const hubThreshold = Math.max(3, Math.floor(depGraph.nodes.length * 0.05));
|
|
14
|
+
|
|
15
|
+
for (const node of depGraph.nodes) {
|
|
16
|
+
const fanIn = node.importedBy.length;
|
|
17
|
+
const fanOut = node.imports.length;
|
|
18
|
+
metrics.set(node.key, {
|
|
19
|
+
fanIn,
|
|
20
|
+
fanOut,
|
|
21
|
+
isHub: fanIn >= hubThreshold,
|
|
22
|
+
isOrphan: fanIn === 0 && fanOut === 0,
|
|
23
|
+
isLeaf: fanOut === 0 && fanIn > 0,
|
|
24
|
+
isOrchestrator: fanOut >= 5 && fanIn < fanOut,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return metrics;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build a module-level dep metrics map (aggregating file-level metrics).
|
|
32
|
+
* moduleKey → { fanIn, fanOut, isHub, isOrphan, isLeaf, isOrchestrator }
|
|
33
|
+
*/
|
|
34
|
+
export function computeModuleLevelMetrics(depGraph, modules) {
|
|
35
|
+
const fileMetrics = computeModuleDepMetrics(depGraph);
|
|
36
|
+
if (fileMetrics.size === 0 || !modules) return new Map();
|
|
37
|
+
|
|
38
|
+
const moduleLevelMap = new Map();
|
|
39
|
+
|
|
40
|
+
for (const mod of modules) {
|
|
41
|
+
const moduleKey = mod.key;
|
|
42
|
+
const lowerKey = moduleKey.toLowerCase();
|
|
43
|
+
|
|
44
|
+
// Find all file-level nodes that belong to this module
|
|
45
|
+
let totalFanIn = 0;
|
|
46
|
+
let totalFanOut = 0;
|
|
47
|
+
let fileCount = 0;
|
|
48
|
+
|
|
49
|
+
for (const [nodeKey, m] of fileMetrics) {
|
|
50
|
+
if (nodeKey === lowerKey || nodeKey.startsWith(lowerKey + "/") || nodeKey === moduleKey || nodeKey.startsWith(moduleKey + "/")) {
|
|
51
|
+
// Count only external edges (crossing module boundary)
|
|
52
|
+
if (depGraph?.nodes) {
|
|
53
|
+
const node = depGraph.nodes.find(n => n.key === nodeKey);
|
|
54
|
+
if (node) {
|
|
55
|
+
const externalIn = node.importedBy.filter(imp => !imp.startsWith(moduleKey + "/") && imp !== moduleKey).length;
|
|
56
|
+
const externalOut = node.imports.filter(imp => !imp.startsWith(moduleKey + "/") && imp !== moduleKey).length;
|
|
57
|
+
totalFanIn += externalIn;
|
|
58
|
+
totalFanOut += externalOut;
|
|
59
|
+
fileCount++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (fileCount > 0) {
|
|
66
|
+
const hubThreshold = Math.max(5, Math.floor(modules.length * 0.15));
|
|
67
|
+
moduleLevelMap.set(moduleKey, {
|
|
68
|
+
fanIn: totalFanIn,
|
|
69
|
+
fanOut: totalFanOut,
|
|
70
|
+
isHub: totalFanIn >= hubThreshold,
|
|
71
|
+
isOrphan: totalFanIn === 0 && totalFanOut === 0,
|
|
72
|
+
isLeaf: totalFanOut === 0 && totalFanIn > 0,
|
|
73
|
+
isOrchestrator: totalFanOut >= 5 && totalFanIn < totalFanOut,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return moduleLevelMap;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildAIContext(scanResult, config, depGraph = null) {
|
|
6
82
|
const { filesCount, modules, api, pages, metadata } = scanResult;
|
|
7
83
|
|
|
8
84
|
// Get domain hints from config if available
|
|
@@ -17,13 +93,17 @@ export function buildAIContext(scanResult, config) {
|
|
|
17
93
|
// Group modules by business domain
|
|
18
94
|
const domainGroups = groupModulesByDomain(modules, customHints);
|
|
19
95
|
|
|
20
|
-
//
|
|
96
|
+
// Compute module-level dependency metrics
|
|
97
|
+
const moduleMetrics = computeModuleLevelMetrics(depGraph, modules);
|
|
98
|
+
|
|
99
|
+
// Identify top modules with enriched types
|
|
21
100
|
const topModules = modules
|
|
22
101
|
.slice(0, 15)
|
|
23
102
|
.map(m => ({
|
|
24
103
|
key: m.key,
|
|
25
104
|
fileCount: m.fileCount,
|
|
26
|
-
type: inferModuleType(m.key)
|
|
105
|
+
type: inferModuleType(m.key),
|
|
106
|
+
depRole: describeModuleDepRole(m.key, moduleMetrics),
|
|
27
107
|
}));
|
|
28
108
|
|
|
29
109
|
// Categorize routes
|
|
@@ -48,8 +128,8 @@ export function buildAIContext(scanResult, config) {
|
|
|
48
128
|
testFrameworks: metadata?.testFrameworks || []
|
|
49
129
|
};
|
|
50
130
|
|
|
51
|
-
// Identify key architectural patterns
|
|
52
|
-
const patterns = inferArchitecturalPatterns(modules);
|
|
131
|
+
// Identify key architectural patterns (verified against dep graph when available)
|
|
132
|
+
const patterns = inferArchitecturalPatterns(modules, depGraph);
|
|
53
133
|
|
|
54
134
|
// Build compact context object
|
|
55
135
|
return {
|
|
@@ -97,39 +177,155 @@ export function buildAIContext(scanResult, config) {
|
|
|
97
177
|
function inferModuleType(modulePath) {
|
|
98
178
|
const lower = modulePath.toLowerCase();
|
|
99
179
|
|
|
100
|
-
if (lower.includes("
|
|
101
|
-
if (lower.includes("
|
|
102
|
-
if (lower.includes("
|
|
180
|
+
if (lower.includes("test") || lower.includes("spec") || lower.includes("__test")) return "test";
|
|
181
|
+
if (lower.includes("api") || lower.includes("endpoint")) return "api";
|
|
182
|
+
if (lower.includes("component") || lower.includes("widget")) return "ui";
|
|
183
|
+
if (lower.includes("lib") || lower.includes("util") || lower.includes("helper") || lower.includes("common") || lower.includes("shared")) return "library";
|
|
103
184
|
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";
|
|
185
|
+
if (lower.includes("store") || lower.includes("state") || lower.includes("redux") || lower.includes("zustand")) return "state";
|
|
186
|
+
if (lower.includes("page") || lower.includes("route") || lower.includes("view")) return "route";
|
|
187
|
+
if (lower.includes("config") || lower.includes("setting") || lower.includes("env")) return "config";
|
|
188
|
+
if (lower.includes("core") || lower.includes("kernel") || lower.includes("foundation")) return "core";
|
|
189
|
+
if (lower.includes("render") || lower.includes("template") || lower.includes("format")) return "renderer";
|
|
190
|
+
if (lower.includes("publish") || lower.includes("output") || lower.includes("export")) return "publisher";
|
|
191
|
+
if (lower.includes("analyz") || lower.includes("inspect") || lower.includes("detect")) return "analyzer";
|
|
192
|
+
if (lower.includes("plugin") || lower.includes("extension") || lower.includes("addon")) return "plugin";
|
|
193
|
+
if (lower.includes("deliver") || lower.includes("dispatch") || lower.includes("send")) return "delivery";
|
|
194
|
+
if (lower.includes("doc") || lower.includes("generate")) return "documentation";
|
|
195
|
+
if (lower.includes("integrat") || lower.includes("connect") || lower.includes("adapter")) return "integration";
|
|
196
|
+
if (lower.includes("cli") || lower.includes("command") || lower.includes("bin")) return "cli";
|
|
197
|
+
if (lower.includes("ai") || lower.includes("ml") || lower.includes("model") || lower.includes("prompt")) return "ai";
|
|
198
|
+
if (lower.includes("auth") || lower.includes("login") || lower.includes("session")) return "auth";
|
|
199
|
+
if (lower.includes("data") || lower.includes("db") || lower.includes("model") || lower.includes("schema")) return "data";
|
|
200
|
+
if (lower.includes("middleware")) return "middleware";
|
|
201
|
+
if (lower.includes("service")) return "service";
|
|
106
202
|
if (lower.includes("app")) return "app";
|
|
107
203
|
|
|
108
204
|
return "other";
|
|
109
205
|
}
|
|
110
206
|
|
|
111
|
-
function inferArchitecturalPatterns(modules) {
|
|
207
|
+
function inferArchitecturalPatterns(modules, depGraph = null) {
|
|
112
208
|
const patterns = [];
|
|
209
|
+
const keys = modules.map(m => m.key.toLowerCase());
|
|
113
210
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const hasStore = modules.some(m => m.key.includes("store") || m.key.includes("state"));
|
|
120
|
-
const hasApi = modules.some(m => m.key.includes("api"));
|
|
121
|
-
|
|
122
|
-
if (hasAppRouter) patterns.push("Next.js App Router");
|
|
123
|
-
if (hasPagesRouter) patterns.push("Next.js Pages Router");
|
|
124
|
-
if (hasComponents && hasLib) patterns.push("Layered component architecture");
|
|
125
|
-
if (hasHooks) patterns.push("React hooks pattern");
|
|
126
|
-
if (hasStore) patterns.push("Centralized state management");
|
|
127
|
-
if (hasApi) patterns.push("API route pattern");
|
|
211
|
+
const has = (keyword) => keys.some(k => k.includes(keyword));
|
|
212
|
+
|
|
213
|
+
// Compute module-level metrics for verification
|
|
214
|
+
const moduleMetrics = depGraph ? computeModuleLevelMetrics(depGraph, modules) : new Map();
|
|
215
|
+
const hasCycles = depGraph?.cycles?.length > 0;
|
|
128
216
|
|
|
217
|
+
// Verify pattern: a module matching `keyword` has high fan-in (truly central)
|
|
218
|
+
const verifyHub = (keyword) => {
|
|
219
|
+
for (const [mKey, m] of moduleMetrics) {
|
|
220
|
+
if (mKey.toLowerCase().includes(keyword) && m.fanIn >= 3) return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Verify pattern: modules matching keyword have mostly outbound deps (orchestrators)
|
|
226
|
+
const verifyOrchestrator = (keyword) => {
|
|
227
|
+
for (const [mKey, m] of moduleMetrics) {
|
|
228
|
+
if (mKey.toLowerCase().includes(keyword) && m.fanOut >= 3) return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Web framework patterns
|
|
234
|
+
if (has("app/")) patterns.push("Next.js App Router");
|
|
235
|
+
if (has("pages/")) patterns.push("Next.js Pages Router");
|
|
236
|
+
if (has("component") && has("lib")) patterns.push("Layered component architecture");
|
|
237
|
+
if (has("hook")) patterns.push("React hooks pattern");
|
|
238
|
+
if (has("store") || has("state") || has("redux") || has("zustand")) patterns.push("Centralized state management");
|
|
239
|
+
|
|
240
|
+
// General patterns — verified against dep graph when available
|
|
241
|
+
if (has("api") || has("endpoint")) patterns.push("API route pattern");
|
|
242
|
+
|
|
243
|
+
if (has("core") || has("kernel")) {
|
|
244
|
+
if (moduleMetrics.size > 0 && verifyHub("core")) {
|
|
245
|
+
patterns.push("Core/kernel architecture (verified — high fan-in)");
|
|
246
|
+
} else if (moduleMetrics.size > 0) {
|
|
247
|
+
patterns.push("Core/kernel architecture (naming only)");
|
|
248
|
+
} else {
|
|
249
|
+
patterns.push("Core/kernel architecture");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (has("plugin") || has("extension")) {
|
|
254
|
+
if (moduleMetrics.size > 0 && (verifyHub("plugin") || verifyOrchestrator("plugin") || verifyHub("loader"))) {
|
|
255
|
+
patterns.push("Plugin system (verified — loader/registry detected)");
|
|
256
|
+
} else if (moduleMetrics.size > 0) {
|
|
257
|
+
patterns.push("Plugin system (naming only)");
|
|
258
|
+
} else {
|
|
259
|
+
patterns.push("Plugin system");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (has("middleware")) patterns.push("Middleware pipeline");
|
|
264
|
+
|
|
265
|
+
if (has("render") || has("template")) {
|
|
266
|
+
if (moduleMetrics.size > 0 && verifyOrchestrator("render")) {
|
|
267
|
+
patterns.push("Renderer pipeline (verified — multi-output)");
|
|
268
|
+
} else {
|
|
269
|
+
patterns.push("Renderer pipeline");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (has("publish") || has("output")) {
|
|
274
|
+
if (moduleMetrics.size > 0 && verifyOrchestrator("publish")) {
|
|
275
|
+
patterns.push("Multi-output publishing (verified — multiple targets)");
|
|
276
|
+
} else {
|
|
277
|
+
patterns.push("Multi-output publishing");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (has("analyz") || has("detect") || has("inspect")) patterns.push("Analysis pipeline");
|
|
282
|
+
if (has("cli") || has("command") || has("bin")) patterns.push("CLI tool architecture");
|
|
283
|
+
|
|
284
|
+
if (has("util") || has("helper") || has("lib")) {
|
|
285
|
+
if (moduleMetrics.size > 0 && verifyHub("util") || verifyHub("helper") || verifyHub("lib")) {
|
|
286
|
+
patterns.push("Shared utility layer (verified — high fan-in)");
|
|
287
|
+
} else {
|
|
288
|
+
patterns.push("Shared utility layer");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (has("integrat") || has("adapter") || has("connect")) patterns.push("Integration adapters");
|
|
293
|
+
if (has("ai") || has("prompt") || has("provider")) patterns.push("AI/LLM integration");
|
|
294
|
+
if (has("deliver") || has("dispatch")) patterns.push("Delivery pipeline");
|
|
295
|
+
if (has("test") || has("spec")) patterns.push("Dedicated test infrastructure");
|
|
296
|
+
|
|
297
|
+
// Structural patterns from dep graph (not keyword-based)
|
|
298
|
+
if (depGraph?.stats) {
|
|
299
|
+
if (depGraph.stats.cycles === 0 && depGraph.stats.totalEdges > 10) {
|
|
300
|
+
patterns.push("Acyclic dependency graph — clean layering");
|
|
301
|
+
}
|
|
302
|
+
if (hasCycles) {
|
|
303
|
+
patterns.push(`⚠️ ${depGraph.stats.cycles} circular dependency cycle(s) detected`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
129
307
|
return patterns;
|
|
130
308
|
}
|
|
131
309
|
|
|
132
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Generate a dependency-aware role description for a module.
|
|
312
|
+
*/
|
|
313
|
+
function describeModuleDepRole(moduleKey, moduleMetrics) {
|
|
314
|
+
const m = moduleMetrics.get(moduleKey);
|
|
315
|
+
if (!m) return null;
|
|
316
|
+
|
|
317
|
+
if (m.isOrphan) return "Isolated (no imports or importers)";
|
|
318
|
+
if (m.isHub && m.fanIn >= 15) return `Critical shared infrastructure (imported by ${m.fanIn} modules)`;
|
|
319
|
+
if (m.isHub) return `Shared infrastructure (imported by ${m.fanIn} modules)`;
|
|
320
|
+
if (m.isOrchestrator) return `Orchestrator (coordinates ${m.fanOut} modules)`;
|
|
321
|
+
if (m.isLeaf) return `Leaf module (consumed only, ${m.fanIn} importer${m.fanIn === 1 ? "" : "s"})`;
|
|
322
|
+
if (m.fanIn > 0 && m.fanOut > 0) return `Connector (${m.fanIn} in, ${m.fanOut} out)`;
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export { describeModuleDepRole };
|
|
327
|
+
|
|
328
|
+
export function buildModuleContext(modules, config, depGraph = null) {
|
|
133
329
|
const customHints = config.domains
|
|
134
330
|
? Object.entries(config.domains).map(([key, domain]) => ({
|
|
135
331
|
match: domain.match || [],
|
|
@@ -139,6 +335,7 @@ export function buildModuleContext(modules, config) {
|
|
|
139
335
|
: [];
|
|
140
336
|
|
|
141
337
|
const domainGroups = groupModulesByDomain(modules, customHints);
|
|
338
|
+
const moduleMetrics = computeModuleLevelMetrics(depGraph, modules);
|
|
142
339
|
|
|
143
340
|
return modules.map(module => {
|
|
144
341
|
const domain = domainGroups.find(d =>
|
|
@@ -149,6 +346,7 @@ export function buildModuleContext(modules, config) {
|
|
|
149
346
|
key: module.key,
|
|
150
347
|
fileCount: module.fileCount,
|
|
151
348
|
type: inferModuleType(module.key),
|
|
349
|
+
depRole: describeModuleDepRole(module.key, moduleMetrics),
|
|
152
350
|
domain: domain?.name || "Other",
|
|
153
351
|
domainDescription: domain?.description || "Uncategorized"
|
|
154
352
|
};
|
|
@@ -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
|
|