@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.
Files changed (212) hide show
  1. package/README.md +105 -116
  2. package/architect-run.sh +431 -0
  3. package/assets/banner-v3.html +561 -0
  4. package/dist/agent-generator/context-enricher.d.ts +58 -0
  5. package/dist/agent-generator/context-enricher.d.ts.map +1 -0
  6. package/dist/agent-generator/context-enricher.js +613 -0
  7. package/dist/agent-generator/context-enricher.js.map +1 -0
  8. package/dist/agent-generator/domain-inferrer.d.ts +52 -0
  9. package/dist/agent-generator/domain-inferrer.d.ts.map +1 -0
  10. package/dist/agent-generator/domain-inferrer.js +585 -0
  11. package/dist/agent-generator/domain-inferrer.js.map +1 -0
  12. package/dist/agent-generator/framework-detector.d.ts +40 -0
  13. package/dist/agent-generator/framework-detector.d.ts.map +1 -0
  14. package/dist/agent-generator/framework-detector.js +611 -0
  15. package/dist/agent-generator/framework-detector.js.map +1 -0
  16. package/dist/agent-generator/index.d.ts +47 -0
  17. package/dist/agent-generator/index.d.ts.map +1 -0
  18. package/dist/agent-generator/index.js +545 -0
  19. package/dist/agent-generator/index.js.map +1 -0
  20. package/dist/agent-generator/stack-detector.d.ts +14 -0
  21. package/dist/agent-generator/stack-detector.d.ts.map +1 -0
  22. package/dist/agent-generator/stack-detector.js +124 -0
  23. package/dist/agent-generator/stack-detector.js.map +1 -0
  24. package/dist/agent-generator/templates/core/agents.d.ts +17 -0
  25. package/dist/agent-generator/templates/core/agents.d.ts.map +1 -0
  26. package/dist/agent-generator/templates/core/agents.js +1256 -0
  27. package/dist/agent-generator/templates/core/agents.js.map +1 -0
  28. package/dist/agent-generator/templates/core/architecture-rules.d.ts +7 -0
  29. package/dist/agent-generator/templates/core/architecture-rules.d.ts.map +1 -0
  30. package/dist/agent-generator/templates/core/architecture-rules.js +274 -0
  31. package/dist/agent-generator/templates/core/architecture-rules.js.map +1 -0
  32. package/dist/agent-generator/templates/core/general-rules.d.ts +8 -0
  33. package/dist/agent-generator/templates/core/general-rules.d.ts.map +1 -0
  34. package/dist/agent-generator/templates/core/general-rules.js +301 -0
  35. package/dist/agent-generator/templates/core/general-rules.js.map +1 -0
  36. package/dist/agent-generator/templates/core/hooks-generator.d.ts +21 -0
  37. package/dist/agent-generator/templates/core/hooks-generator.d.ts.map +1 -0
  38. package/dist/agent-generator/templates/core/hooks-generator.js +233 -0
  39. package/dist/agent-generator/templates/core/hooks-generator.js.map +1 -0
  40. package/dist/agent-generator/templates/core/index-md.d.ts +7 -0
  41. package/dist/agent-generator/templates/core/index-md.d.ts.map +1 -0
  42. package/dist/agent-generator/templates/core/index-md.js +246 -0
  43. package/dist/agent-generator/templates/core/index-md.js.map +1 -0
  44. package/dist/agent-generator/templates/core/orchestrator.d.ts +8 -0
  45. package/dist/agent-generator/templates/core/orchestrator.d.ts.map +1 -0
  46. package/dist/agent-generator/templates/core/orchestrator.js +422 -0
  47. package/dist/agent-generator/templates/core/orchestrator.js.map +1 -0
  48. package/dist/agent-generator/templates/core/preflight.d.ts +8 -0
  49. package/dist/agent-generator/templates/core/preflight.d.ts.map +1 -0
  50. package/dist/agent-generator/templates/core/preflight.js +213 -0
  51. package/dist/agent-generator/templates/core/preflight.js.map +1 -0
  52. package/dist/agent-generator/templates/core/quality-gates.d.ts +11 -0
  53. package/dist/agent-generator/templates/core/quality-gates.d.ts.map +1 -0
  54. package/dist/agent-generator/templates/core/quality-gates.js +254 -0
  55. package/dist/agent-generator/templates/core/quality-gates.js.map +1 -0
  56. package/dist/agent-generator/templates/core/security-rules.d.ts +7 -0
  57. package/dist/agent-generator/templates/core/security-rules.d.ts.map +1 -0
  58. package/dist/agent-generator/templates/core/security-rules.js +528 -0
  59. package/dist/agent-generator/templates/core/security-rules.js.map +1 -0
  60. package/dist/agent-generator/templates/core/skills-generator.d.ts +19 -0
  61. package/dist/agent-generator/templates/core/skills-generator.d.ts.map +1 -0
  62. package/dist/agent-generator/templates/core/skills-generator.js +546 -0
  63. package/dist/agent-generator/templates/core/skills-generator.js.map +1 -0
  64. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts +7 -0
  65. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts.map +1 -0
  66. package/dist/agent-generator/templates/core/workflow-fix-bug.js +237 -0
  67. package/dist/agent-generator/templates/core/workflow-fix-bug.js.map +1 -0
  68. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts +8 -0
  69. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts.map +1 -0
  70. package/dist/agent-generator/templates/core/workflow-new-feature.js +321 -0
  71. package/dist/agent-generator/templates/core/workflow-new-feature.js.map +1 -0
  72. package/dist/agent-generator/templates/core/workflow-review.d.ts +7 -0
  73. package/dist/agent-generator/templates/core/workflow-review.d.ts.map +1 -0
  74. package/dist/agent-generator/templates/core/workflow-review.js +104 -0
  75. package/dist/agent-generator/templates/core/workflow-review.js.map +1 -0
  76. package/dist/agent-generator/templates/domain/index.d.ts +22 -0
  77. package/dist/agent-generator/templates/domain/index.d.ts.map +1 -0
  78. package/dist/agent-generator/templates/domain/index.js +1176 -0
  79. package/dist/agent-generator/templates/domain/index.js.map +1 -0
  80. package/dist/agent-generator/templates/stack/index.d.ts +8 -0
  81. package/dist/agent-generator/templates/stack/index.d.ts.map +1 -0
  82. package/dist/agent-generator/templates/stack/index.js +695 -0
  83. package/dist/agent-generator/templates/stack/index.js.map +1 -0
  84. package/dist/agent-generator/templates/template-helpers.d.ts +75 -0
  85. package/dist/agent-generator/templates/template-helpers.d.ts.map +1 -0
  86. package/dist/agent-generator/templates/template-helpers.js +726 -0
  87. package/dist/agent-generator/templates/template-helpers.js.map +1 -0
  88. package/dist/agent-generator/types.d.ts +196 -0
  89. package/dist/agent-generator/types.d.ts.map +1 -0
  90. package/dist/agent-generator/types.js +27 -0
  91. package/dist/agent-generator/types.js.map +1 -0
  92. package/dist/analyzer.d.ts +5 -0
  93. package/dist/analyzer.d.ts.map +1 -1
  94. package/dist/analyzer.js +46 -5
  95. package/dist/analyzer.js.map +1 -1
  96. package/dist/analyzers/forecast.d.ts +85 -0
  97. package/dist/analyzers/forecast.d.ts.map +1 -0
  98. package/dist/analyzers/forecast.js +337 -0
  99. package/dist/analyzers/forecast.js.map +1 -0
  100. package/dist/analyzers/git-cache.d.ts +7 -0
  101. package/dist/analyzers/git-cache.d.ts.map +1 -0
  102. package/dist/analyzers/git-cache.js +41 -0
  103. package/dist/analyzers/git-cache.js.map +1 -0
  104. package/dist/analyzers/git-history.d.ts +113 -0
  105. package/dist/analyzers/git-history.d.ts.map +1 -0
  106. package/dist/analyzers/git-history.js +333 -0
  107. package/dist/analyzers/git-history.js.map +1 -0
  108. package/dist/analyzers/index.d.ts +10 -0
  109. package/dist/analyzers/index.d.ts.map +1 -0
  110. package/dist/analyzers/index.js +7 -0
  111. package/dist/analyzers/index.js.map +1 -0
  112. package/dist/analyzers/temporal-scorer.d.ts +72 -0
  113. package/dist/analyzers/temporal-scorer.d.ts.map +1 -0
  114. package/dist/analyzers/temporal-scorer.js +140 -0
  115. package/dist/analyzers/temporal-scorer.js.map +1 -0
  116. package/dist/anti-patterns.d.ts +7 -0
  117. package/dist/anti-patterns.d.ts.map +1 -1
  118. package/dist/anti-patterns.js +25 -6
  119. package/dist/anti-patterns.js.map +1 -1
  120. package/dist/cli.d.ts +2 -3
  121. package/dist/cli.d.ts.map +1 -1
  122. package/dist/cli.js +275 -113
  123. package/dist/cli.js.map +1 -1
  124. package/dist/config.d.ts +6 -0
  125. package/dist/config.d.ts.map +1 -1
  126. package/dist/config.js +48 -11
  127. package/dist/config.js.map +1 -1
  128. package/dist/html-reporter.d.ts +3 -1
  129. package/dist/html-reporter.d.ts.map +1 -1
  130. package/dist/html-reporter.js +248 -12
  131. package/dist/html-reporter.js.map +1 -1
  132. package/dist/index.d.ts +16 -3
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +63 -4
  135. package/dist/index.js.map +1 -1
  136. package/dist/project-summarizer.d.ts +38 -0
  137. package/dist/project-summarizer.d.ts.map +1 -0
  138. package/dist/project-summarizer.js +463 -0
  139. package/dist/project-summarizer.js.map +1 -0
  140. package/dist/refactor-reporter.js +1 -1
  141. package/dist/scanner.d.ts +8 -2
  142. package/dist/scanner.d.ts.map +1 -1
  143. package/dist/scanner.js +153 -113
  144. package/dist/scanner.js.map +1 -1
  145. package/dist/scorer.d.ts.map +1 -1
  146. package/dist/scorer.js +24 -11
  147. package/dist/scorer.js.map +1 -1
  148. package/dist/types.d.ts +29 -0
  149. package/dist/types.d.ts.map +1 -1
  150. package/package.json +12 -3
  151. package/src/agent-generator/context-enricher.ts +672 -0
  152. package/src/agent-generator/domain-inferrer.ts +635 -0
  153. package/src/agent-generator/framework-detector.ts +669 -0
  154. package/src/agent-generator/index.ts +634 -0
  155. package/src/agent-generator/stack-detector.ts +115 -0
  156. package/src/agent-generator/templates/core/agents.ts +1296 -0
  157. package/src/agent-generator/templates/core/architecture-rules.ts +287 -0
  158. package/src/agent-generator/templates/core/general-rules.ts +306 -0
  159. package/src/agent-generator/templates/core/hooks-generator.ts +242 -0
  160. package/src/agent-generator/templates/core/index-md.ts +260 -0
  161. package/src/agent-generator/templates/core/orchestrator.ts +459 -0
  162. package/src/agent-generator/templates/core/preflight.ts +215 -0
  163. package/src/agent-generator/templates/core/quality-gates.ts +256 -0
  164. package/src/agent-generator/templates/core/security-rules.ts +543 -0
  165. package/src/agent-generator/templates/core/skills-generator.ts +585 -0
  166. package/src/agent-generator/templates/core/workflow-fix-bug.ts +239 -0
  167. package/src/agent-generator/templates/core/workflow-new-feature.ts +323 -0
  168. package/src/agent-generator/templates/core/workflow-review.ts +106 -0
  169. package/src/agent-generator/templates/domain/index.ts +1201 -0
  170. package/src/agent-generator/templates/stack/index.ts +705 -0
  171. package/src/agent-generator/templates/template-helpers.ts +776 -0
  172. package/src/agent-generator/types.ts +232 -0
  173. package/src/analyzer.ts +51 -5
  174. package/src/analyzers/forecast.ts +496 -0
  175. package/src/analyzers/git-cache.ts +52 -0
  176. package/src/analyzers/git-history.ts +488 -0
  177. package/src/analyzers/index.ts +33 -0
  178. package/src/analyzers/temporal-scorer.ts +227 -0
  179. package/src/anti-patterns.ts +29 -6
  180. package/src/cli.ts +316 -117
  181. package/src/config.ts +52 -11
  182. package/src/html-reporter.ts +263 -13
  183. package/src/index.ts +93 -10
  184. package/src/project-summarizer.ts +521 -0
  185. package/src/refactor-reporter.ts +1 -1
  186. package/src/scanner.ts +136 -90
  187. package/src/scorer.ts +26 -11
  188. package/src/types.ts +27 -0
  189. package/tests/agent-generator.test.ts +427 -0
  190. package/tests/analyzers-integration.test.ts +174 -0
  191. package/tests/architect-adapter-enrichment.test.ts +9 -0
  192. package/tests/context-enricher.test.ts +971 -0
  193. package/tests/fixtures/monorepo/package.json +6 -0
  194. package/tests/fixtures/monorepo/packages/app/package.json +12 -0
  195. package/tests/fixtures/monorepo/packages/app/src/index.ts +6 -0
  196. package/tests/fixtures/monorepo/packages/core/package.json +7 -0
  197. package/tests/fixtures/monorepo/packages/core/src/index.ts +7 -0
  198. package/tests/forecast.test.ts +509 -0
  199. package/tests/framework-detector.test.ts +1172 -0
  200. package/tests/git-history.test.ts +254 -0
  201. package/tests/monorepo-scan.test.ts +170 -0
  202. package/tests/scanner.test.ts +7 -8
  203. package/tests/scorer.test.ts +594 -0
  204. package/tests/stack-detector.test.ts +241 -0
  205. package/tests/template-generation.test.ts +706 -0
  206. package/tests/template-helpers.test.ts +1152 -0
  207. package/tests/temporal-scorer.test.ts +307 -0
  208. package/dist/agent-generator.d.ts +0 -106
  209. package/dist/agent-generator.d.ts.map +0 -1
  210. package/dist/agent-generator.js +0 -1398
  211. package/dist/agent-generator.js.map +0 -1
  212. 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
+ }