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