@girardelli/architect 2.2.0 → 5.0.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/README.md +105 -116
- package/architect-run.sh +431 -0
- package/assets/banner-v3.html +561 -0
- package/dist/agent-generator/context-enricher.d.ts +58 -0
- package/dist/agent-generator/context-enricher.d.ts.map +1 -0
- package/dist/agent-generator/context-enricher.js +613 -0
- package/dist/agent-generator/context-enricher.js.map +1 -0
- package/dist/agent-generator/domain-inferrer.d.ts +52 -0
- package/dist/agent-generator/domain-inferrer.d.ts.map +1 -0
- package/dist/agent-generator/domain-inferrer.js +585 -0
- package/dist/agent-generator/domain-inferrer.js.map +1 -0
- package/dist/agent-generator/framework-detector.d.ts +40 -0
- package/dist/agent-generator/framework-detector.d.ts.map +1 -0
- package/dist/agent-generator/framework-detector.js +611 -0
- package/dist/agent-generator/framework-detector.js.map +1 -0
- package/dist/agent-generator/index.d.ts +47 -0
- package/dist/agent-generator/index.d.ts.map +1 -0
- package/dist/agent-generator/index.js +545 -0
- package/dist/agent-generator/index.js.map +1 -0
- package/dist/agent-generator/stack-detector.d.ts +14 -0
- package/dist/agent-generator/stack-detector.d.ts.map +1 -0
- package/dist/agent-generator/stack-detector.js +124 -0
- package/dist/agent-generator/stack-detector.js.map +1 -0
- package/dist/agent-generator/templates/core/agents.d.ts +17 -0
- package/dist/agent-generator/templates/core/agents.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/agents.js +1256 -0
- package/dist/agent-generator/templates/core/agents.js.map +1 -0
- package/dist/agent-generator/templates/core/architecture-rules.d.ts +7 -0
- package/dist/agent-generator/templates/core/architecture-rules.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/architecture-rules.js +274 -0
- package/dist/agent-generator/templates/core/architecture-rules.js.map +1 -0
- package/dist/agent-generator/templates/core/general-rules.d.ts +8 -0
- package/dist/agent-generator/templates/core/general-rules.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/general-rules.js +301 -0
- package/dist/agent-generator/templates/core/general-rules.js.map +1 -0
- package/dist/agent-generator/templates/core/hooks-generator.d.ts +21 -0
- package/dist/agent-generator/templates/core/hooks-generator.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/hooks-generator.js +233 -0
- package/dist/agent-generator/templates/core/hooks-generator.js.map +1 -0
- package/dist/agent-generator/templates/core/index-md.d.ts +7 -0
- package/dist/agent-generator/templates/core/index-md.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/index-md.js +246 -0
- package/dist/agent-generator/templates/core/index-md.js.map +1 -0
- package/dist/agent-generator/templates/core/orchestrator.d.ts +8 -0
- package/dist/agent-generator/templates/core/orchestrator.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/orchestrator.js +422 -0
- package/dist/agent-generator/templates/core/orchestrator.js.map +1 -0
- package/dist/agent-generator/templates/core/preflight.d.ts +8 -0
- package/dist/agent-generator/templates/core/preflight.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/preflight.js +213 -0
- package/dist/agent-generator/templates/core/preflight.js.map +1 -0
- package/dist/agent-generator/templates/core/quality-gates.d.ts +11 -0
- package/dist/agent-generator/templates/core/quality-gates.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/quality-gates.js +254 -0
- package/dist/agent-generator/templates/core/quality-gates.js.map +1 -0
- package/dist/agent-generator/templates/core/security-rules.d.ts +7 -0
- package/dist/agent-generator/templates/core/security-rules.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/security-rules.js +528 -0
- package/dist/agent-generator/templates/core/security-rules.js.map +1 -0
- package/dist/agent-generator/templates/core/skills-generator.d.ts +19 -0
- package/dist/agent-generator/templates/core/skills-generator.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/skills-generator.js +546 -0
- package/dist/agent-generator/templates/core/skills-generator.js.map +1 -0
- package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts +7 -0
- package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/workflow-fix-bug.js +237 -0
- package/dist/agent-generator/templates/core/workflow-fix-bug.js.map +1 -0
- package/dist/agent-generator/templates/core/workflow-new-feature.d.ts +8 -0
- package/dist/agent-generator/templates/core/workflow-new-feature.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/workflow-new-feature.js +321 -0
- package/dist/agent-generator/templates/core/workflow-new-feature.js.map +1 -0
- package/dist/agent-generator/templates/core/workflow-review.d.ts +7 -0
- package/dist/agent-generator/templates/core/workflow-review.d.ts.map +1 -0
- package/dist/agent-generator/templates/core/workflow-review.js +104 -0
- package/dist/agent-generator/templates/core/workflow-review.js.map +1 -0
- package/dist/agent-generator/templates/domain/index.d.ts +22 -0
- package/dist/agent-generator/templates/domain/index.d.ts.map +1 -0
- package/dist/agent-generator/templates/domain/index.js +1176 -0
- package/dist/agent-generator/templates/domain/index.js.map +1 -0
- package/dist/agent-generator/templates/stack/index.d.ts +8 -0
- package/dist/agent-generator/templates/stack/index.d.ts.map +1 -0
- package/dist/agent-generator/templates/stack/index.js +695 -0
- package/dist/agent-generator/templates/stack/index.js.map +1 -0
- package/dist/agent-generator/templates/template-helpers.d.ts +75 -0
- package/dist/agent-generator/templates/template-helpers.d.ts.map +1 -0
- package/dist/agent-generator/templates/template-helpers.js +726 -0
- package/dist/agent-generator/templates/template-helpers.js.map +1 -0
- package/dist/agent-generator/types.d.ts +196 -0
- package/dist/agent-generator/types.d.ts.map +1 -0
- package/dist/agent-generator/types.js +27 -0
- package/dist/agent-generator/types.js.map +1 -0
- package/dist/analyzer.d.ts +5 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +46 -5
- package/dist/analyzer.js.map +1 -1
- package/dist/analyzers/forecast.d.ts +85 -0
- package/dist/analyzers/forecast.d.ts.map +1 -0
- package/dist/analyzers/forecast.js +337 -0
- package/dist/analyzers/forecast.js.map +1 -0
- package/dist/analyzers/git-cache.d.ts +7 -0
- package/dist/analyzers/git-cache.d.ts.map +1 -0
- package/dist/analyzers/git-cache.js +41 -0
- package/dist/analyzers/git-cache.js.map +1 -0
- package/dist/analyzers/git-history.d.ts +113 -0
- package/dist/analyzers/git-history.d.ts.map +1 -0
- package/dist/analyzers/git-history.js +333 -0
- package/dist/analyzers/git-history.js.map +1 -0
- package/dist/analyzers/index.d.ts +10 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +7 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/temporal-scorer.d.ts +72 -0
- package/dist/analyzers/temporal-scorer.d.ts.map +1 -0
- package/dist/analyzers/temporal-scorer.js +140 -0
- package/dist/analyzers/temporal-scorer.js.map +1 -0
- package/dist/anti-patterns.d.ts +7 -0
- package/dist/anti-patterns.d.ts.map +1 -1
- package/dist/anti-patterns.js +25 -6
- package/dist/anti-patterns.js.map +1 -1
- package/dist/cli.d.ts +2 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +275 -113
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -11
- package/dist/config.js.map +1 -1
- package/dist/html-reporter.d.ts +3 -1
- package/dist/html-reporter.d.ts.map +1 -1
- package/dist/html-reporter.js +248 -12
- package/dist/html-reporter.js.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -4
- package/dist/index.js.map +1 -1
- package/dist/project-summarizer.d.ts +38 -0
- package/dist/project-summarizer.d.ts.map +1 -0
- package/dist/project-summarizer.js +463 -0
- package/dist/project-summarizer.js.map +1 -0
- package/dist/refactor-reporter.js +1 -1
- package/dist/scanner.d.ts +8 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +153 -113
- package/dist/scanner.js.map +1 -1
- package/dist/scorer.d.ts.map +1 -1
- package/dist/scorer.js +24 -11
- package/dist/scorer.js.map +1 -1
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -3
- package/src/agent-generator/context-enricher.ts +672 -0
- package/src/agent-generator/domain-inferrer.ts +635 -0
- package/src/agent-generator/framework-detector.ts +669 -0
- package/src/agent-generator/index.ts +634 -0
- package/src/agent-generator/stack-detector.ts +115 -0
- package/src/agent-generator/templates/core/agents.ts +1296 -0
- package/src/agent-generator/templates/core/architecture-rules.ts +287 -0
- package/src/agent-generator/templates/core/general-rules.ts +306 -0
- package/src/agent-generator/templates/core/hooks-generator.ts +242 -0
- package/src/agent-generator/templates/core/index-md.ts +260 -0
- package/src/agent-generator/templates/core/orchestrator.ts +459 -0
- package/src/agent-generator/templates/core/preflight.ts +215 -0
- package/src/agent-generator/templates/core/quality-gates.ts +256 -0
- package/src/agent-generator/templates/core/security-rules.ts +543 -0
- package/src/agent-generator/templates/core/skills-generator.ts +585 -0
- package/src/agent-generator/templates/core/workflow-fix-bug.ts +239 -0
- package/src/agent-generator/templates/core/workflow-new-feature.ts +323 -0
- package/src/agent-generator/templates/core/workflow-review.ts +106 -0
- package/src/agent-generator/templates/domain/index.ts +1201 -0
- package/src/agent-generator/templates/stack/index.ts +705 -0
- package/src/agent-generator/templates/template-helpers.ts +776 -0
- package/src/agent-generator/types.ts +232 -0
- package/src/analyzer.ts +51 -5
- package/src/analyzers/forecast.ts +496 -0
- package/src/analyzers/git-cache.ts +52 -0
- package/src/analyzers/git-history.ts +488 -0
- package/src/analyzers/index.ts +33 -0
- package/src/analyzers/temporal-scorer.ts +227 -0
- package/src/anti-patterns.ts +29 -6
- package/src/cli.ts +316 -117
- package/src/config.ts +52 -11
- package/src/html-reporter.ts +263 -13
- package/src/index.ts +93 -10
- package/src/project-summarizer.ts +521 -0
- package/src/refactor-reporter.ts +1 -1
- package/src/scanner.ts +136 -90
- package/src/scorer.ts +26 -11
- package/src/types.ts +27 -0
- package/tests/agent-generator.test.ts +427 -0
- package/tests/analyzers-integration.test.ts +174 -0
- package/tests/architect-adapter-enrichment.test.ts +9 -0
- package/tests/context-enricher.test.ts +971 -0
- package/tests/fixtures/monorepo/package.json +6 -0
- package/tests/fixtures/monorepo/packages/app/package.json +12 -0
- package/tests/fixtures/monorepo/packages/app/src/index.ts +6 -0
- package/tests/fixtures/monorepo/packages/core/package.json +7 -0
- package/tests/fixtures/monorepo/packages/core/src/index.ts +7 -0
- package/tests/forecast.test.ts +509 -0
- package/tests/framework-detector.test.ts +1172 -0
- package/tests/git-history.test.ts +254 -0
- package/tests/monorepo-scan.test.ts +170 -0
- package/tests/scanner.test.ts +7 -8
- package/tests/scorer.test.ts +594 -0
- package/tests/stack-detector.test.ts +241 -0
- package/tests/template-generation.test.ts +706 -0
- package/tests/template-helpers.test.ts +1152 -0
- package/tests/temporal-scorer.test.ts +307 -0
- package/dist/agent-generator.d.ts +0 -106
- package/dist/agent-generator.d.ts.map +0 -1
- package/dist/agent-generator.js +0 -1398
- package/dist/agent-generator.js.map +0 -1
- package/src/agent-generator.ts +0 -1526
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { AnalysisReport, RefactoringPlan } from '../types.js';
|
|
4
|
+
import {
|
|
5
|
+
StackInfo,
|
|
6
|
+
EnrichedTemplateContext,
|
|
7
|
+
ModuleDetail,
|
|
8
|
+
DetectedEndpoint,
|
|
9
|
+
AgentGeneratorConfig,
|
|
10
|
+
DEFAULT_AGENT_CONFIG,
|
|
11
|
+
} from './types.js';
|
|
12
|
+
import { DomainInferrer } from './domain-inferrer.js';
|
|
13
|
+
import { FrameworkDetector } from './framework-detector.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* ContextEnricher — Builds an EnrichedTemplateContext from AnalysisReport.
|
|
17
|
+
*
|
|
18
|
+
* Multi-stack: detects modules and endpoints for Python, TypeScript, Java, PHP,
|
|
19
|
+
* Go, Ruby, Dart — supports NestJS modules, Django apps, FastAPI routers,
|
|
20
|
+
* Spring controllers, Laravel controllers, Flask blueprints, and more.
|
|
21
|
+
*
|
|
22
|
+
* v3.1: Now includes framework detection, line counting, smart descriptions,
|
|
23
|
+
* and project structure detection for truly context-aware agent generation.
|
|
24
|
+
*/
|
|
25
|
+
export class ContextEnricher {
|
|
26
|
+
private domainInferrer = new DomainInferrer();
|
|
27
|
+
private frameworkDetector = new FrameworkDetector();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build a fully enriched context for template generation.
|
|
31
|
+
*/
|
|
32
|
+
enrich(
|
|
33
|
+
report: AnalysisReport,
|
|
34
|
+
plan: RefactoringPlan,
|
|
35
|
+
stack: StackInfo,
|
|
36
|
+
projectPath: string,
|
|
37
|
+
config: AgentGeneratorConfig = DEFAULT_AGENT_CONFIG,
|
|
38
|
+
): EnrichedTemplateContext {
|
|
39
|
+
const modules = this.extractModules(report, projectPath);
|
|
40
|
+
const endpoints = this.extractEndpoints(report, modules);
|
|
41
|
+
const untestedModules = this.findUntestedModules(modules);
|
|
42
|
+
const criticalPaths = this.findCriticalPaths(report);
|
|
43
|
+
const projectDepth = this.classifyProjectDepth(report);
|
|
44
|
+
const domain = this.domainInferrer.infer(report, projectPath);
|
|
45
|
+
|
|
46
|
+
// v3.1: Framework detection
|
|
47
|
+
const fwResult = this.frameworkDetector.detect(projectPath, report);
|
|
48
|
+
|
|
49
|
+
// Build stack label: languages + web frameworks only (no test/lint tools)
|
|
50
|
+
const webFrameworks = (report.projectInfo?.frameworks || [])
|
|
51
|
+
.filter(f => !['Jest', 'Vitest', 'Mocha', 'ESLint', 'Prettier', 'Biome',
|
|
52
|
+
'pytest', 'Ruff', 'mypy', 'Black', 'Flake8', 'RSpec',
|
|
53
|
+
'@jest/globals', '@types/jest', 'ts-jest'].includes(f));
|
|
54
|
+
const stackLabel = [...new Set([...stack.languages, ...webFrameworks])].join(' + ');
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
report,
|
|
58
|
+
plan,
|
|
59
|
+
stack,
|
|
60
|
+
projectName: report.projectInfo.name || 'Project',
|
|
61
|
+
stackLabel,
|
|
62
|
+
config,
|
|
63
|
+
domain,
|
|
64
|
+
modules,
|
|
65
|
+
endpoints,
|
|
66
|
+
untestedModules,
|
|
67
|
+
criticalPaths,
|
|
68
|
+
projectDepth,
|
|
69
|
+
detectedFrameworks: fwResult.frameworks,
|
|
70
|
+
primaryFramework: fwResult.primaryFramework,
|
|
71
|
+
toolchain: fwResult.toolchain,
|
|
72
|
+
projectStructure: fwResult.projectStructure,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
77
|
+
// MODULE EXTRACTION — Multi-stack, multi-architecture
|
|
78
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
79
|
+
|
|
80
|
+
private extractModules(report: AnalysisReport, projectPath: string): ModuleDetail[] {
|
|
81
|
+
const modules = new Map<string, ModuleDetail>();
|
|
82
|
+
const nodes = report.dependencyGraph.nodes;
|
|
83
|
+
|
|
84
|
+
for (const filePath of nodes) {
|
|
85
|
+
const moduleName = this.inferModuleName(filePath);
|
|
86
|
+
if (!moduleName) continue;
|
|
87
|
+
|
|
88
|
+
if (!modules.has(moduleName)) {
|
|
89
|
+
modules.set(moduleName, {
|
|
90
|
+
name: moduleName,
|
|
91
|
+
path: this.inferModulePath(filePath, moduleName),
|
|
92
|
+
files: [],
|
|
93
|
+
fileCount: 0,
|
|
94
|
+
lineCount: 0,
|
|
95
|
+
description: '',
|
|
96
|
+
hasTests: false,
|
|
97
|
+
testFiles: [],
|
|
98
|
+
entities: [],
|
|
99
|
+
controllers: [],
|
|
100
|
+
services: [],
|
|
101
|
+
layer: this.inferFileLayer(filePath),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const mod = modules.get(moduleName)!;
|
|
106
|
+
mod.files.push(filePath);
|
|
107
|
+
mod.fileCount++;
|
|
108
|
+
|
|
109
|
+
// v3.1: Count real lines
|
|
110
|
+
mod.lineCount += this.countFileLines(projectPath, filePath);
|
|
111
|
+
|
|
112
|
+
const lower = filePath.toLowerCase();
|
|
113
|
+
if (this.isTestFile(lower)) {
|
|
114
|
+
mod.hasTests = true;
|
|
115
|
+
mod.testFiles.push(filePath);
|
|
116
|
+
}
|
|
117
|
+
if (this.isEntityFile(lower)) {
|
|
118
|
+
const entityName = this.extractEntityName(filePath);
|
|
119
|
+
if (entityName) mod.entities.push(entityName);
|
|
120
|
+
}
|
|
121
|
+
if (this.isControllerOrRouteFile(lower)) {
|
|
122
|
+
mod.controllers.push(filePath);
|
|
123
|
+
}
|
|
124
|
+
if (this.isServiceFile(lower)) {
|
|
125
|
+
mod.services.push(filePath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Enrich with descriptions from project summary
|
|
130
|
+
if (report.projectSummary?.modules) {
|
|
131
|
+
for (const summaryMod of report.projectSummary.modules) {
|
|
132
|
+
const key = summaryMod.name.toLowerCase();
|
|
133
|
+
for (const [modName, mod] of modules) {
|
|
134
|
+
if (modName.toLowerCase().includes(key) || key.includes(modName.toLowerCase())) {
|
|
135
|
+
mod.description = summaryMod.description;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// v3.1: Smart descriptions for modules without one
|
|
143
|
+
for (const mod of modules.values()) {
|
|
144
|
+
if (!mod.description) {
|
|
145
|
+
mod.description = this.generateSmartDescription(mod);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return [...modules.values()].sort((a, b) => b.fileCount - a.fileCount);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
153
|
+
// v3.1: LINE COUNTING
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Count actual lines in a file. Uses a fast approach:
|
|
158
|
+
* reads file size and estimates if too large, counts \n for smaller files.
|
|
159
|
+
*/
|
|
160
|
+
private countFileLines(projectPath: string, filePath: string): number {
|
|
161
|
+
try {
|
|
162
|
+
const fullPath = join(projectPath, filePath);
|
|
163
|
+
if (!existsSync(fullPath)) return 0;
|
|
164
|
+
|
|
165
|
+
const stats = statSync(fullPath);
|
|
166
|
+
// Skip files larger than 1MB — estimate based on average line length
|
|
167
|
+
if (stats.size > 1_000_000) {
|
|
168
|
+
return Math.round(stats.size / 45); // ~45 chars avg per line
|
|
169
|
+
}
|
|
170
|
+
if (stats.size === 0) return 0;
|
|
171
|
+
|
|
172
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
173
|
+
return content.split('\n').length;
|
|
174
|
+
} catch {
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
180
|
+
// v3.1: SMART MODULE DESCRIPTIONS
|
|
181
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Generate intelligent descriptions based on module name patterns,
|
|
185
|
+
* file composition, and layer context.
|
|
186
|
+
*/
|
|
187
|
+
private generateSmartDescription(mod: ModuleDetail): string {
|
|
188
|
+
const name = mod.name.toLowerCase();
|
|
189
|
+
const parts: string[] = [];
|
|
190
|
+
|
|
191
|
+
// Pattern-based descriptions
|
|
192
|
+
const descriptionPatterns: Record<string, string> = {
|
|
193
|
+
'extractors': 'Extração e parsing de dados de documentos',
|
|
194
|
+
'ocr': 'Reconhecimento óptico de caracteres (OCR)',
|
|
195
|
+
'guards': 'Validação e proteção de fluxos de dados',
|
|
196
|
+
'confidence': 'Cálculo de confiança e scoring de dados extraídos',
|
|
197
|
+
'routes': 'Definição de rotas e endpoints da API',
|
|
198
|
+
'workers': 'Processamento assíncrono e workers de background',
|
|
199
|
+
'persistence': 'Camada de persistência e acesso a dados',
|
|
200
|
+
'storage': 'Gerenciamento de armazenamento de arquivos',
|
|
201
|
+
'agents': 'Agentes de processamento e automação',
|
|
202
|
+
'auth': 'Autenticação e gerenciamento de sessão',
|
|
203
|
+
'users': 'Gerenciamento de usuários e perfis',
|
|
204
|
+
'notifications': 'Sistema de notificações e alertas',
|
|
205
|
+
'payments': 'Processamento de pagamentos e transações',
|
|
206
|
+
'reports': 'Geração de relatórios e dashboards',
|
|
207
|
+
'search': 'Motor de busca e indexação',
|
|
208
|
+
'cache': 'Camada de cache e otimização de performance',
|
|
209
|
+
'queue': 'Filas de mensagens e processamento assíncrono',
|
|
210
|
+
'email': 'Envio e gerenciamento de e-mails',
|
|
211
|
+
'upload': 'Upload e processamento de arquivos',
|
|
212
|
+
'migration': 'Migrações de banco de dados',
|
|
213
|
+
'seed': 'Dados iniciais e seed do banco',
|
|
214
|
+
'fixtures': 'Fixtures e dados de teste',
|
|
215
|
+
'middleware': 'Middleware de requisição/resposta',
|
|
216
|
+
'dependencies': 'Injeção de dependências e configuração',
|
|
217
|
+
'exceptions': 'Tratamento de erros e exceções customizadas',
|
|
218
|
+
'enums': 'Enumerações e constantes de domínio',
|
|
219
|
+
'events': 'Sistema de eventos e event handlers',
|
|
220
|
+
'value_objects': 'Value Objects do domínio (imutáveis)',
|
|
221
|
+
'entities': 'Entidades de domínio com identidade',
|
|
222
|
+
'services': 'Serviços de domínio e lógica de negócio',
|
|
223
|
+
'interfaces': 'Contratos e interfaces de abstração',
|
|
224
|
+
'repositories': 'Repositórios de acesso a dados',
|
|
225
|
+
'mappers': 'Mapeamento entre camadas (DTO ↔ Entity)',
|
|
226
|
+
'validators': 'Validação de dados e regras de negócio',
|
|
227
|
+
'serializers': 'Serialização/deserialização de dados',
|
|
228
|
+
'schemas': 'Schemas de validação e contratos',
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Try exact match first
|
|
232
|
+
for (const [pattern, desc] of Object.entries(descriptionPatterns)) {
|
|
233
|
+
if (name === pattern || name.endsWith(`/${pattern}`)) {
|
|
234
|
+
parts.push(desc);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Try partial match
|
|
240
|
+
if (parts.length === 0) {
|
|
241
|
+
for (const [pattern, desc] of Object.entries(descriptionPatterns)) {
|
|
242
|
+
if (name.includes(pattern)) {
|
|
243
|
+
parts.push(desc);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Add composition info
|
|
250
|
+
const composition: string[] = [];
|
|
251
|
+
if (mod.controllers.length > 0) composition.push(`${mod.controllers.length} endpoint(s)`);
|
|
252
|
+
if (mod.services.length > 0) composition.push(`${mod.services.length} service(s)`);
|
|
253
|
+
if (mod.entities.length > 0) composition.push(`entities: ${mod.entities.join(', ')}`);
|
|
254
|
+
|
|
255
|
+
if (composition.length > 0) {
|
|
256
|
+
parts.push(composition.join(' · '));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Fallback: file count + line count
|
|
260
|
+
if (parts.length === 0) {
|
|
261
|
+
parts.push(`${mod.fileCount} arquivo(s)`);
|
|
262
|
+
}
|
|
263
|
+
if (mod.lineCount > 0) {
|
|
264
|
+
parts.push(`${mod.lineCount.toLocaleString()} linhas`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return parts.join(' — ');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Multi-strategy module name inference.
|
|
272
|
+
* Strategy 1: Explicit module markers (modules/, features/, apps/, etc.)
|
|
273
|
+
* Strategy 2: Clean Architecture layers — use meaningful subdirectory
|
|
274
|
+
* Strategy 3: Django apps pattern (app_name/models.py, app_name/views.py)
|
|
275
|
+
* Strategy 4: Package-based (Java/PHP namespaces)
|
|
276
|
+
* Strategy 5: Go package
|
|
277
|
+
* Strategy 6: Fallback — first non-generic directory from the end
|
|
278
|
+
*/
|
|
279
|
+
private inferModuleName(filePath: string): string | null {
|
|
280
|
+
const parts = filePath.split('/');
|
|
281
|
+
const lower = filePath.toLowerCase();
|
|
282
|
+
|
|
283
|
+
// Skip __init__.py / index files as standalone modules
|
|
284
|
+
const fileName = parts[parts.length - 1].toLowerCase();
|
|
285
|
+
if (fileName === '__init__.py' || fileName === 'index.ts' || fileName === 'index.js' || fileName === 'index.py') {
|
|
286
|
+
// Only process if it's in a meaningful directory
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Strategy 1: Explicit module markers ──
|
|
290
|
+
const moduleMarkers = ['modules', 'features', 'apps', 'domains', 'packages', 'components', 'pages', 'bundles'];
|
|
291
|
+
for (let i = 0; i < parts.length; i++) {
|
|
292
|
+
if (moduleMarkers.includes(parts[i].toLowerCase()) && parts[i + 1]) {
|
|
293
|
+
return parts[i + 1];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ── Strategy 2: Clean Architecture / DDD layers ──
|
|
298
|
+
const cleanArchLayers = new Set([
|
|
299
|
+
'domain', 'application', 'infrastructure', 'presentation',
|
|
300
|
+
'core', 'adapters', 'ports', 'usecases', 'use_cases',
|
|
301
|
+
]);
|
|
302
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
303
|
+
if (cleanArchLayers.has(parts[i].toLowerCase()) && parts[i + 1]) {
|
|
304
|
+
const nextDir = parts[i + 1].toLowerCase();
|
|
305
|
+
const subLayers = new Set([
|
|
306
|
+
'services', 'entities', 'models', 'repositories', 'interfaces',
|
|
307
|
+
'value_objects', 'events', 'exceptions', 'enums', 'dto', 'dtos',
|
|
308
|
+
'controllers', 'views', 'routes', 'api', 'workers', 'consumers',
|
|
309
|
+
'persistence', 'storage', 'extraction', 'agents', 'adapters',
|
|
310
|
+
'mappers', 'factories', 'providers', 'guards', 'middleware',
|
|
311
|
+
'dependencies', 'schemas', 'serializers', 'signals', 'tasks',
|
|
312
|
+
'commands', 'queries', 'handlers', 'listeners',
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
if (subLayers.has(nextDir)) {
|
|
316
|
+
if (parts[i + 2] && !parts[i + 2].includes('.')) {
|
|
317
|
+
return parts[i + 2]; // e.g., infrastructure/extraction/extractors → extractors
|
|
318
|
+
}
|
|
319
|
+
return `${parts[i]}/${parts[i + 1]}`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!this.isGenericDir(nextDir) && nextDir.length > 2 && !nextDir.startsWith('__')) {
|
|
323
|
+
return parts[i + 1];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ── Strategy 3: Django apps pattern ──
|
|
329
|
+
const djangoMarkers = ['views.py', 'models.py', 'urls.py', 'serializers.py', 'admin.py', 'apps.py', 'forms.py'];
|
|
330
|
+
if (djangoMarkers.some(m => lower.endsWith(m))) {
|
|
331
|
+
const parentDir = parts[parts.length - 2];
|
|
332
|
+
if (parentDir && !this.isGenericDir(parentDir.toLowerCase())) {
|
|
333
|
+
return parentDir;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── Strategy 4: Java/PHP package-based ──
|
|
338
|
+
const javaPackageMarkers = ['com', 'org', 'net', 'io', 'br'];
|
|
339
|
+
for (let i = 0; i < parts.length; i++) {
|
|
340
|
+
if (javaPackageMarkers.includes(parts[i].toLowerCase()) && parts[i + 2]) {
|
|
341
|
+
return parts[i + 2];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// PHP namespace: App/Http/Controllers/UserController.php → user
|
|
346
|
+
if (lower.includes('/http/controllers/') || lower.includes('/http/requests/')) {
|
|
347
|
+
const parentDir = parts[parts.length - 2];
|
|
348
|
+
if (parentDir && parentDir.toLowerCase() !== 'controllers' && parentDir.toLowerCase() !== 'requests') {
|
|
349
|
+
return parentDir;
|
|
350
|
+
}
|
|
351
|
+
const baseName = parts[parts.length - 1]
|
|
352
|
+
.replace(/\.[^.]+$/, '')
|
|
353
|
+
.replace(/(Controller|Request|Resource|Policy|Observer|Event)$/i, '');
|
|
354
|
+
if (baseName.length > 2) return baseName;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ── Strategy 5: Go package ──
|
|
358
|
+
if (lower.endsWith('.go')) {
|
|
359
|
+
const parentDir = parts[parts.length - 2];
|
|
360
|
+
if (parentDir && !this.isGenericDir(parentDir.toLowerCase()) && parentDir !== 'cmd' && parentDir !== 'internal' && parentDir !== 'pkg') {
|
|
361
|
+
return parentDir;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── Strategy 6: Fallback — walk from end, find first non-generic dir ──
|
|
366
|
+
const extraGenericForFallback = new Set(['__pycache__', 'node_modules', 'dist', 'build', '.git', 'vendor', 'target']);
|
|
367
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
368
|
+
const dir = parts[i].toLowerCase();
|
|
369
|
+
if (
|
|
370
|
+
!this.isGenericDir(dir) &&
|
|
371
|
+
!extraGenericForFallback.has(dir) &&
|
|
372
|
+
dir.length > 2 &&
|
|
373
|
+
!dir.startsWith('.') &&
|
|
374
|
+
!dir.startsWith('__')
|
|
375
|
+
) {
|
|
376
|
+
return parts[i];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Check if a directory name is too generic to be a module */
|
|
384
|
+
private isGenericDir(name: string): boolean {
|
|
385
|
+
const generic = new Set([
|
|
386
|
+
'src', 'lib', 'app', 'main', 'common', 'shared', 'utils', 'helpers',
|
|
387
|
+
'config', 'configuration', 'middleware', 'middlewares', 'guards', 'pipes',
|
|
388
|
+
'interceptors', 'filters', 'decorators',
|
|
389
|
+
'tests', 'test', '__tests__', 'spec', 'specs', 'e2e', 'migrations', 'seeds',
|
|
390
|
+
'dto', 'dtos', 'entities', 'models', 'schemas', 'interfaces', 'types', 'typings',
|
|
391
|
+
'controllers', 'services', 'repositories', 'providers', 'factories',
|
|
392
|
+
'data', 'domain', 'presentation', 'infrastructure', 'application',
|
|
393
|
+
'core', 'base', 'abstract', 'generics', 'constants', 'enums',
|
|
394
|
+
'public', 'static', 'assets', 'resources', 'templates', 'views',
|
|
395
|
+
'internal', 'pkg', 'cmd', 'vendor', 'node_modules',
|
|
396
|
+
'adapters', 'ports', 'usecases', 'use_cases', 'value_objects',
|
|
397
|
+
'events', 'exceptions', 'errors', 'signals', 'hooks',
|
|
398
|
+
]);
|
|
399
|
+
return generic.has(name);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private inferModulePath(filePath: string, moduleName: string): string {
|
|
403
|
+
const idx = filePath.toLowerCase().indexOf(moduleName.toLowerCase());
|
|
404
|
+
if (idx >= 0) {
|
|
405
|
+
return filePath.substring(0, idx + moduleName.length);
|
|
406
|
+
}
|
|
407
|
+
return filePath.split('/').slice(0, -1).join('/');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private inferFileLayer(filePath: string): string {
|
|
411
|
+
const lower = filePath.toLowerCase();
|
|
412
|
+
|
|
413
|
+
// Monorepo package-level classification
|
|
414
|
+
const packagesMatch = lower.match(/packages\/([^/]+)/);
|
|
415
|
+
if (packagesMatch) {
|
|
416
|
+
const pkgName = packagesMatch[1];
|
|
417
|
+
// Classify by package name semantics
|
|
418
|
+
if (['dashboard', 'web', 'frontend', 'app', 'ui'].includes(pkgName)) return 'UI';
|
|
419
|
+
if (['api', 'cloud', 'server', 'backend'].includes(pkgName)) return 'API';
|
|
420
|
+
if (['core', 'bridge', 'engine'].includes(pkgName)) return 'Service';
|
|
421
|
+
if (['cli', 'command'].includes(pkgName)) return 'CLI';
|
|
422
|
+
if (['types', 'events', 'mcp', 'autonomy'].includes(pkgName)) return 'Infrastructure';
|
|
423
|
+
return 'Package';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (lower.includes('/route') || lower.includes('/controller') || lower.includes('/endpoint')
|
|
427
|
+
|| lower.includes('/api/') || lower.includes('/presentation/') || lower.includes('/handler')
|
|
428
|
+
|| (lower.includes('/view') && !lower.includes('/review'))
|
|
429
|
+
|| lower.includes('/urls') || lower.includes('/blueprint')
|
|
430
|
+
|| lower.includes('/http/')) return 'API';
|
|
431
|
+
|
|
432
|
+
if (lower.includes('/service') || lower.includes('/usecase') || lower.includes('/use_case')
|
|
433
|
+
|| lower.includes('/use-case') || lower.includes('/application/')) return 'Service';
|
|
434
|
+
|
|
435
|
+
if (lower.includes('/model') || lower.includes('/entity') || lower.includes('/entities')
|
|
436
|
+
|| lower.includes('/schema') || lower.includes('/repository') || lower.includes('/persistence')
|
|
437
|
+
|| lower.includes('/migration') || lower.includes('/domain/')
|
|
438
|
+
|| lower.includes('/value_object') || lower.includes('/serializer')) return 'Data';
|
|
439
|
+
|
|
440
|
+
if (lower.includes('/component') || lower.includes('/page') || lower.includes('/screen')
|
|
441
|
+
|| lower.includes('/widget') || lower.includes('/template')
|
|
442
|
+
|| lower.includes('/partial') || lower.includes('/layout')) return 'UI';
|
|
443
|
+
|
|
444
|
+
if (lower.includes('/config') || lower.includes('/middleware') || lower.includes('/infra')
|
|
445
|
+
|| lower.includes('/infrastructure/') || lower.includes('/storage')
|
|
446
|
+
|| lower.includes('/extraction') || lower.includes('/adapter')
|
|
447
|
+
|| lower.includes('/worker') || lower.includes('/consumer')
|
|
448
|
+
|| lower.includes('/queue') || lower.includes('/cache')
|
|
449
|
+
|| lower.includes('/email') || lower.includes('/notification')) return 'Infrastructure';
|
|
450
|
+
|
|
451
|
+
if (lower.includes('/cli/') || lower.includes('/command') || lower.includes('/script')
|
|
452
|
+
|| lower.includes('/management/commands')) return 'CLI';
|
|
453
|
+
|
|
454
|
+
return 'Other';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
458
|
+
// ENDPOINT EXTRACTION — Multi-framework
|
|
459
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
460
|
+
|
|
461
|
+
private extractEndpoints(report: AnalysisReport, modules: ModuleDetail[]): DetectedEndpoint[] {
|
|
462
|
+
const endpoints: DetectedEndpoint[] = [];
|
|
463
|
+
const nodes = report.dependencyGraph.nodes;
|
|
464
|
+
|
|
465
|
+
for (const filePath of nodes) {
|
|
466
|
+
const lower = filePath.toLowerCase();
|
|
467
|
+
if (!this.isControllerOrRouteFile(lower)) continue;
|
|
468
|
+
|
|
469
|
+
const resourceName = this.extractResourceFromFile(filePath);
|
|
470
|
+
if (!resourceName || resourceName === 'init' || resourceName === 'index') continue;
|
|
471
|
+
|
|
472
|
+
const isAuthRoute = lower.includes('auth');
|
|
473
|
+
const isHealthRoute = lower.includes('health') || lower.includes('status');
|
|
474
|
+
const isSearchRoute = lower.includes('search') || lower.includes('query');
|
|
475
|
+
const isMetricsRoute = lower.includes('metric') || lower.includes('monitor');
|
|
476
|
+
|
|
477
|
+
if (isHealthRoute) {
|
|
478
|
+
endpoints.push({
|
|
479
|
+
method: 'GET', path: `/health`, file: filePath,
|
|
480
|
+
handler: 'health_check', hasAuth: false, hasValidation: false,
|
|
481
|
+
});
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (isMetricsRoute) {
|
|
486
|
+
endpoints.push({
|
|
487
|
+
method: 'GET', path: `/metrics`, file: filePath,
|
|
488
|
+
handler: 'get_metrics', hasAuth: this.fileReferencesAuth(filePath, report), hasValidation: false,
|
|
489
|
+
});
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (isAuthRoute) {
|
|
494
|
+
endpoints.push(
|
|
495
|
+
{ method: 'POST', path: `/auth/login`, file: filePath, handler: 'login', hasAuth: false, hasValidation: true },
|
|
496
|
+
{ method: 'POST', path: `/auth/register`, file: filePath, handler: 'register', hasAuth: false, hasValidation: true },
|
|
497
|
+
{ method: 'POST', path: `/auth/refresh`, file: filePath, handler: 'refresh_token', hasAuth: true, hasValidation: false },
|
|
498
|
+
);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (isSearchRoute) {
|
|
503
|
+
endpoints.push({
|
|
504
|
+
method: 'GET', path: `/${resourceName}/search`, file: filePath,
|
|
505
|
+
handler: `search_${resourceName}`, hasAuth: this.fileReferencesAuth(filePath, report), hasValidation: true,
|
|
506
|
+
});
|
|
507
|
+
endpoints.push({
|
|
508
|
+
method: 'POST', path: `/${resourceName}/search`, file: filePath,
|
|
509
|
+
handler: `advanced_search_${resourceName}`, hasAuth: this.fileReferencesAuth(filePath, report), hasValidation: true,
|
|
510
|
+
});
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Standard CRUD
|
|
515
|
+
const hasAuth = this.fileReferencesAuth(filePath, report);
|
|
516
|
+
const hasValidation = this.fileReferencesValidation(filePath, report);
|
|
517
|
+
const crud = [
|
|
518
|
+
{ method: 'GET', path: `/${resourceName}`, handler: `list_${resourceName}` },
|
|
519
|
+
{ method: 'GET', path: `/${resourceName}/{id}`, handler: `get_${resourceName}` },
|
|
520
|
+
{ method: 'POST', path: `/${resourceName}`, handler: `create_${resourceName}` },
|
|
521
|
+
{ method: 'PUT', path: `/${resourceName}/{id}`, handler: `update_${resourceName}` },
|
|
522
|
+
{ method: 'DELETE', path: `/${resourceName}/{id}`, handler: `delete_${resourceName}` },
|
|
523
|
+
];
|
|
524
|
+
for (const c of crud) {
|
|
525
|
+
endpoints.push({ method: c.method, path: c.path, file: filePath, handler: c.handler, hasAuth, hasValidation });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Deduplicate endpoints — same method+path from .ts and .js variants
|
|
530
|
+
const seen = new Set<string>();
|
|
531
|
+
const deduped = endpoints.filter(ep => {
|
|
532
|
+
const key = `${ep.method}:${ep.path}`;
|
|
533
|
+
if (seen.has(key)) return false;
|
|
534
|
+
seen.add(key);
|
|
535
|
+
return true;
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
return deduped;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private extractResourceFromFile(filePath: string): string {
|
|
542
|
+
const parts = filePath.split('/');
|
|
543
|
+
const fileName = parts[parts.length - 1]
|
|
544
|
+
.replace(/\.[^.]+$/, '')
|
|
545
|
+
.replace(/(Controller|Router|Route|View|Handler|Endpoint|Resource|Blueprint)$/i, '')
|
|
546
|
+
.replace(/[-_]/g, '')
|
|
547
|
+
.toLowerCase();
|
|
548
|
+
|
|
549
|
+
if (fileName && fileName !== '__init__' && fileName !== 'index' && fileName !== 'base' && fileName.length > 1) {
|
|
550
|
+
return fileName;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const parent = parts[parts.length - 2];
|
|
554
|
+
if (parent && parent.toLowerCase() !== 'routes' && parent.toLowerCase() !== 'controllers') {
|
|
555
|
+
return parent.toLowerCase().replace(/[-_]/g, '');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return '';
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
562
|
+
// TEST & COUPLING ANALYSIS
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
564
|
+
|
|
565
|
+
private findUntestedModules(modules: ModuleDetail[]): string[] {
|
|
566
|
+
return modules
|
|
567
|
+
.filter(m => !m.hasTests && m.fileCount > 1)
|
|
568
|
+
.map(m => m.name);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private findCriticalPaths(report: AnalysisReport): string[] {
|
|
572
|
+
const coupling = new Map<string, number>();
|
|
573
|
+
for (const edge of report.dependencyGraph.edges) {
|
|
574
|
+
coupling.set(edge.from, (coupling.get(edge.from) || 0) + edge.weight);
|
|
575
|
+
coupling.set(edge.to, (coupling.get(edge.to) || 0) + edge.weight);
|
|
576
|
+
}
|
|
577
|
+
return [...coupling.entries()]
|
|
578
|
+
.sort((a, b) => b[1] - a[1])
|
|
579
|
+
.slice(0, 10)
|
|
580
|
+
.map(([file]) => file);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private classifyProjectDepth(report: AnalysisReport): EnrichedTemplateContext['projectDepth'] {
|
|
584
|
+
const files = report.projectInfo.totalFiles;
|
|
585
|
+
const lines = report.projectInfo.totalLines;
|
|
586
|
+
if (files > 500 || lines > 100000) return 'enterprise';
|
|
587
|
+
if (files > 200 || lines > 50000) return 'large';
|
|
588
|
+
if (files > 50 || lines > 10000) return 'medium';
|
|
589
|
+
return 'small';
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
593
|
+
// FILE CLASSIFICATION HELPERS — Multi-stack
|
|
594
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
595
|
+
|
|
596
|
+
private isTestFile(lower: string): boolean {
|
|
597
|
+
return lower.includes('/test') || lower.includes('/spec') || lower.includes('__tests__')
|
|
598
|
+
|| lower.includes('.test.') || lower.includes('.spec.')
|
|
599
|
+
|| lower.includes('_test.py') || lower.includes('_test.go')
|
|
600
|
+
|| lower.includes('test_') && lower.endsWith('.py')
|
|
601
|
+
|| lower.includes('/tests/');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private isEntityFile(lower: string): boolean {
|
|
605
|
+
return lower.includes('/model') || lower.includes('/entity') || lower.includes('/entities')
|
|
606
|
+
|| lower.includes('/schema') || lower.includes('/value_object')
|
|
607
|
+
|| lower.includes('.model.') || lower.includes('.entity.')
|
|
608
|
+
|| lower.endsWith('models.py')
|
|
609
|
+
|| lower.includes('/domain/') && (lower.endsWith('.java') || lower.endsWith('.kt')) && !lower.includes('service') && !lower.includes('repository')
|
|
610
|
+
|| lower.includes('/models/') && lower.endsWith('.php');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private isControllerOrRouteFile(lower: string): boolean {
|
|
614
|
+
if (lower.includes('.controller.')) return true;
|
|
615
|
+
if (lower.includes('.router.')) return true;
|
|
616
|
+
if (lower.includes('/routes/') && !lower.includes('__init__')) return true;
|
|
617
|
+
if (lower.includes('/route/') && !lower.includes('__init__')) return true;
|
|
618
|
+
if (lower.endsWith('views.py') || lower.endsWith('_view.py')) return true;
|
|
619
|
+
if (lower.endsWith('urls.py')) return true;
|
|
620
|
+
if (lower.includes('blueprint')) return true;
|
|
621
|
+
if ((lower.endsWith('.java') || lower.endsWith('.kt')) && lower.includes('controller')) return true;
|
|
622
|
+
if (lower.includes('/controllers/') && lower.endsWith('.php')) return true;
|
|
623
|
+
if (lower.endsWith('.go') && (lower.includes('handler') || lower.includes('router'))) return true;
|
|
624
|
+
if (lower.includes('/controllers/') && lower.endsWith('.rb')) return true;
|
|
625
|
+
if (lower.includes('/endpoint') || lower.includes('/handler')) return true;
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private isServiceFile(lower: string): boolean {
|
|
630
|
+
return lower.includes('service') || lower.includes('usecase') || lower.includes('use_case')
|
|
631
|
+
|| lower.includes('use-case') || lower.includes('interactor')
|
|
632
|
+
|| lower.includes('/application/') && !lower.includes('interface') && !lower.includes('__init__');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private extractEntityName(filePath: string): string | null {
|
|
636
|
+
const parts = filePath.split('/');
|
|
637
|
+
const fileName = parts[parts.length - 1];
|
|
638
|
+
const name = fileName
|
|
639
|
+
.replace(/\.[^.]+$/, '')
|
|
640
|
+
.replace(/\.(model|entity|schema|dto)$/i, '')
|
|
641
|
+
.replace(/(Model|Entity|Schema|Dto)$/i, '')
|
|
642
|
+
.replace(/[-_]/g, ' ')
|
|
643
|
+
.replace(/\b\w/g, l => l.toUpperCase())
|
|
644
|
+
.trim();
|
|
645
|
+
|
|
646
|
+
const skip = new Set(['Index', 'Base', 'Init', '__Init__', 'Abstract', 'Models', 'Entities', 'Schemas']);
|
|
647
|
+
if (name && !skip.has(name)) {
|
|
648
|
+
return name;
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private fileReferencesAuth(filePath: string, report: AnalysisReport): boolean {
|
|
654
|
+
const edges = report.dependencyGraph.edges.filter(e => e.from === filePath);
|
|
655
|
+
return edges.some(e => {
|
|
656
|
+
const to = e.to.toLowerCase();
|
|
657
|
+
return to.includes('auth') || to.includes('guard') || to.includes('permission')
|
|
658
|
+
|| to.includes('jwt') || to.includes('token') || to.includes('security')
|
|
659
|
+
|| to.includes('dependencies/auth') || to.includes('middleware/auth');
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private fileReferencesValidation(filePath: string, report: AnalysisReport): boolean {
|
|
664
|
+
const edges = report.dependencyGraph.edges.filter(e => e.from === filePath);
|
|
665
|
+
return edges.some(e => {
|
|
666
|
+
const to = e.to.toLowerCase();
|
|
667
|
+
return to.includes('dto') || to.includes('schema') || to.includes('validator')
|
|
668
|
+
|| to.includes('pipe') || to.includes('serializer') || to.includes('form')
|
|
669
|
+
|| to.includes('pydantic') || to.includes('marshmallow');
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|