@girardelli/architect-agents 8.1.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 (258) hide show
  1. package/dist/src/core/agent-generator/context-enricher.d.ts +17 -0
  2. package/dist/src/core/agent-generator/context-enricher.js +51 -0
  3. package/dist/src/core/agent-generator/context-enricher.js.map +1 -0
  4. package/dist/src/core/agent-generator/detectors/base-detector.d.ts +8 -0
  5. package/dist/src/core/agent-generator/detectors/base-detector.js +12 -0
  6. package/dist/src/core/agent-generator/detectors/base-detector.js.map +1 -0
  7. package/dist/src/core/agent-generator/detectors/dart-detector.d.ts +5 -0
  8. package/dist/src/core/agent-generator/detectors/dart-detector.js +16 -0
  9. package/dist/src/core/agent-generator/detectors/dart-detector.js.map +1 -0
  10. package/dist/src/core/agent-generator/detectors/framework-registry.d.ts +5 -0
  11. package/dist/src/core/agent-generator/detectors/framework-registry.js +81 -0
  12. package/dist/src/core/agent-generator/detectors/framework-registry.js.map +1 -0
  13. package/dist/src/core/agent-generator/detectors/go-detector.d.ts +5 -0
  14. package/dist/src/core/agent-generator/detectors/go-detector.js +25 -0
  15. package/dist/src/core/agent-generator/detectors/go-detector.js.map +1 -0
  16. package/dist/src/core/agent-generator/detectors/java-detector.d.ts +5 -0
  17. package/dist/src/core/agent-generator/detectors/java-detector.js +44 -0
  18. package/dist/src/core/agent-generator/detectors/java-detector.js.map +1 -0
  19. package/dist/src/core/agent-generator/detectors/node-detector.d.ts +5 -0
  20. package/dist/src/core/agent-generator/detectors/node-detector.js +28 -0
  21. package/dist/src/core/agent-generator/detectors/node-detector.js.map +1 -0
  22. package/dist/src/core/agent-generator/detectors/php-detector.d.ts +5 -0
  23. package/dist/src/core/agent-generator/detectors/php-detector.js +28 -0
  24. package/dist/src/core/agent-generator/detectors/php-detector.js.map +1 -0
  25. package/dist/src/core/agent-generator/detectors/python-detector.d.ts +7 -0
  26. package/dist/src/core/agent-generator/detectors/python-detector.js +116 -0
  27. package/dist/src/core/agent-generator/detectors/python-detector.js.map +1 -0
  28. package/dist/src/core/agent-generator/detectors/ruby-detector.d.ts +5 -0
  29. package/dist/src/core/agent-generator/detectors/ruby-detector.js +23 -0
  30. package/dist/src/core/agent-generator/detectors/ruby-detector.js.map +1 -0
  31. package/dist/src/core/agent-generator/detectors/rust-detector.d.ts +5 -0
  32. package/dist/src/core/agent-generator/detectors/rust-detector.js +18 -0
  33. package/dist/src/core/agent-generator/detectors/rust-detector.js.map +1 -0
  34. package/dist/src/core/agent-generator/detectors/structure-detector.d.ts +4 -0
  35. package/dist/src/core/agent-generator/detectors/structure-detector.js +35 -0
  36. package/dist/src/core/agent-generator/detectors/structure-detector.js.map +1 -0
  37. package/dist/src/core/agent-generator/detectors/toolchain-detector.d.ts +5 -0
  38. package/dist/src/core/agent-generator/detectors/toolchain-detector.js +164 -0
  39. package/dist/src/core/agent-generator/detectors/toolchain-detector.js.map +1 -0
  40. package/dist/src/core/agent-generator/domain-inferrer.d.ts +51 -0
  41. package/dist/src/core/agent-generator/domain-inferrer.js +585 -0
  42. package/dist/src/core/agent-generator/domain-inferrer.js.map +1 -0
  43. package/dist/src/core/agent-generator/engines/audit-engine.d.ts +8 -0
  44. package/dist/src/core/agent-generator/engines/audit-engine.js +84 -0
  45. package/dist/src/core/agent-generator/engines/audit-engine.js.map +1 -0
  46. package/dist/src/core/agent-generator/engines/context-builder.d.ts +12 -0
  47. package/dist/src/core/agent-generator/engines/context-builder.js +84 -0
  48. package/dist/src/core/agent-generator/engines/context-builder.js.map +1 -0
  49. package/dist/src/core/agent-generator/engines/generation-engine.d.ts +7 -0
  50. package/dist/src/core/agent-generator/engines/generation-engine.js +160 -0
  51. package/dist/src/core/agent-generator/engines/generation-engine.js.map +1 -0
  52. package/dist/src/core/agent-generator/engines/generation-engine_deps.d.ts +21 -0
  53. package/dist/src/core/agent-generator/engines/generation-engine_deps.js +17 -0
  54. package/dist/src/core/agent-generator/engines/generation-engine_deps.js.map +1 -0
  55. package/dist/src/core/agent-generator/engines/suggestion-engine.d.ts +13 -0
  56. package/dist/src/core/agent-generator/engines/suggestion-engine.js +171 -0
  57. package/dist/src/core/agent-generator/engines/suggestion-engine.js.map +1 -0
  58. package/dist/src/core/agent-generator/engines/suggestion-engine_deps.d.ts +8 -0
  59. package/dist/src/core/agent-generator/engines/suggestion-engine_deps.js +5 -0
  60. package/dist/src/core/agent-generator/engines/suggestion-engine_deps.js.map +1 -0
  61. package/dist/src/core/agent-generator/enrichers/analysis-helpers.d.ts +9 -0
  62. package/dist/src/core/agent-generator/enrichers/analysis-helpers.js +51 -0
  63. package/dist/src/core/agent-generator/enrichers/analysis-helpers.js.map +1 -0
  64. package/dist/src/core/agent-generator/enrichers/description-generator.d.ts +4 -0
  65. package/dist/src/core/agent-generator/enrichers/description-generator.js +82 -0
  66. package/dist/src/core/agent-generator/enrichers/description-generator.js.map +1 -0
  67. package/dist/src/core/agent-generator/enrichers/endpoint-extractor.d.ts +7 -0
  68. package/dist/src/core/agent-generator/enrichers/endpoint-extractor.js +90 -0
  69. package/dist/src/core/agent-generator/enrichers/endpoint-extractor.js.map +1 -0
  70. package/dist/src/core/agent-generator/enrichers/layer-classifier.d.ts +12 -0
  71. package/dist/src/core/agent-generator/enrichers/layer-classifier.js +152 -0
  72. package/dist/src/core/agent-generator/enrichers/layer-classifier.js.map +1 -0
  73. package/dist/src/core/agent-generator/enrichers/module-extractor.d.ts +10 -0
  74. package/dist/src/core/agent-generator/enrichers/module-extractor.js +173 -0
  75. package/dist/src/core/agent-generator/enrichers/module-extractor.js.map +1 -0
  76. package/dist/src/core/agent-generator/framework-detector.d.ts +17 -0
  77. package/dist/src/core/agent-generator/framework-detector.js +56 -0
  78. package/dist/src/core/agent-generator/framework-detector.js.map +1 -0
  79. package/dist/src/core/agent-generator/index.d.ts +25 -0
  80. package/dist/src/core/agent-generator/index.js +37 -0
  81. package/dist/src/core/agent-generator/index.js.map +1 -0
  82. package/dist/src/core/agent-generator/stack-detector.d.ts +13 -0
  83. package/dist/src/core/agent-generator/stack-detector.js +124 -0
  84. package/dist/src/core/agent-generator/stack-detector.js.map +1 -0
  85. package/dist/src/core/agent-generator/templates/core/agents.d.ts +9 -0
  86. package/dist/src/core/agent-generator/templates/core/agents.js +1127 -0
  87. package/dist/src/core/agent-generator/templates/core/agents.js.map +1 -0
  88. package/dist/src/core/agent-generator/templates/core/architecture-rules.d.ts +6 -0
  89. package/dist/src/core/agent-generator/templates/core/architecture-rules.js +275 -0
  90. package/dist/src/core/agent-generator/templates/core/architecture-rules.js.map +1 -0
  91. package/dist/src/core/agent-generator/templates/core/general-rules.d.ts +7 -0
  92. package/dist/src/core/agent-generator/templates/core/general-rules.js +301 -0
  93. package/dist/src/core/agent-generator/templates/core/general-rules.js.map +1 -0
  94. package/dist/src/core/agent-generator/templates/core/hooks-generator.d.ts +20 -0
  95. package/dist/src/core/agent-generator/templates/core/hooks-generator.js +235 -0
  96. package/dist/src/core/agent-generator/templates/core/hooks-generator.js.map +1 -0
  97. package/dist/src/core/agent-generator/templates/core/index-md.d.ts +6 -0
  98. package/dist/src/core/agent-generator/templates/core/index-md.js +247 -0
  99. package/dist/src/core/agent-generator/templates/core/index-md.js.map +1 -0
  100. package/dist/src/core/agent-generator/templates/core/orchestrator.d.ts +7 -0
  101. package/dist/src/core/agent-generator/templates/core/orchestrator.js +423 -0
  102. package/dist/src/core/agent-generator/templates/core/orchestrator.js.map +1 -0
  103. package/dist/src/core/agent-generator/templates/core/preflight.d.ts +7 -0
  104. package/dist/src/core/agent-generator/templates/core/preflight.js +213 -0
  105. package/dist/src/core/agent-generator/templates/core/preflight.js.map +1 -0
  106. package/dist/src/core/agent-generator/templates/core/quality-gates.d.ts +10 -0
  107. package/dist/src/core/agent-generator/templates/core/quality-gates.js +255 -0
  108. package/dist/src/core/agent-generator/templates/core/quality-gates.js.map +1 -0
  109. package/dist/src/core/agent-generator/templates/core/security-rules.d.ts +6 -0
  110. package/dist/src/core/agent-generator/templates/core/security-rules.js +529 -0
  111. package/dist/src/core/agent-generator/templates/core/security-rules.js.map +1 -0
  112. package/dist/src/core/agent-generator/templates/core/skills-generator.d.ts +18 -0
  113. package/dist/src/core/agent-generator/templates/core/skills-generator.js +547 -0
  114. package/dist/src/core/agent-generator/templates/core/skills-generator.js.map +1 -0
  115. package/dist/src/core/agent-generator/templates/core/workflow-fix-bug.d.ts +6 -0
  116. package/dist/src/core/agent-generator/templates/core/workflow-fix-bug.js +238 -0
  117. package/dist/src/core/agent-generator/templates/core/workflow-fix-bug.js.map +1 -0
  118. package/dist/src/core/agent-generator/templates/core/workflow-new-feature.d.ts +7 -0
  119. package/dist/src/core/agent-generator/templates/core/workflow-new-feature.js +321 -0
  120. package/dist/src/core/agent-generator/templates/core/workflow-new-feature.js.map +1 -0
  121. package/dist/src/core/agent-generator/templates/core/workflow-review.d.ts +6 -0
  122. package/dist/src/core/agent-generator/templates/core/workflow-review.js +105 -0
  123. package/dist/src/core/agent-generator/templates/core/workflow-review.js.map +1 -0
  124. package/dist/src/core/agent-generator/templates/domain/index.d.ts +21 -0
  125. package/dist/src/core/agent-generator/templates/domain/index.js +1179 -0
  126. package/dist/src/core/agent-generator/templates/domain/index.js.map +1 -0
  127. package/dist/src/core/agent-generator/templates/helpers/base-helpers.d.ts +10 -0
  128. package/dist/src/core/agent-generator/templates/helpers/base-helpers.js +20 -0
  129. package/dist/src/core/agent-generator/templates/helpers/base-helpers.js.map +1 -0
  130. package/dist/src/core/agent-generator/templates/helpers/cross-ref-helpers.d.ts +2 -0
  131. package/dist/src/core/agent-generator/templates/helpers/cross-ref-helpers.js +77 -0
  132. package/dist/src/core/agent-generator/templates/helpers/cross-ref-helpers.js.map +1 -0
  133. package/dist/src/core/agent-generator/templates/helpers/security-helpers.d.ts +2 -0
  134. package/dist/src/core/agent-generator/templates/helpers/security-helpers.js +182 -0
  135. package/dist/src/core/agent-generator/templates/helpers/security-helpers.js.map +1 -0
  136. package/dist/src/core/agent-generator/templates/helpers/stack-helpers.d.ts +4 -0
  137. package/dist/src/core/agent-generator/templates/helpers/stack-helpers.js +69 -0
  138. package/dist/src/core/agent-generator/templates/helpers/stack-helpers.js.map +1 -0
  139. package/dist/src/core/agent-generator/templates/helpers/structure-helpers.d.ts +2 -0
  140. package/dist/src/core/agent-generator/templates/helpers/structure-helpers.js +275 -0
  141. package/dist/src/core/agent-generator/templates/helpers/structure-helpers.js.map +1 -0
  142. package/dist/src/core/agent-generator/templates/helpers/summary-helpers.d.ts +6 -0
  143. package/dist/src/core/agent-generator/templates/helpers/summary-helpers.js +56 -0
  144. package/dist/src/core/agent-generator/templates/helpers/summary-helpers.js.map +1 -0
  145. package/dist/src/core/agent-generator/templates/stack/index.d.ts +7 -0
  146. package/dist/src/core/agent-generator/templates/stack/index.js +695 -0
  147. package/dist/src/core/agent-generator/templates/stack/index.js.map +1 -0
  148. package/dist/src/core/agent-generator/templates/template-helpers.d.ts +11 -0
  149. package/dist/src/core/agent-generator/templates/template-helpers.js +12 -0
  150. package/dist/src/core/agent-generator/templates/template-helpers.js.map +1 -0
  151. package/dist/src/core/agent-generator/types/agent.d.ts +39 -0
  152. package/dist/src/core/agent-generator/types/agent.js +27 -0
  153. package/dist/src/core/agent-generator/types/agent.js.map +1 -0
  154. package/dist/src/core/agent-generator/types/domain.d.ts +58 -0
  155. package/dist/src/core/agent-generator/types/domain.js +2 -0
  156. package/dist/src/core/agent-generator/types/domain.js.map +1 -0
  157. package/dist/src/core/agent-generator/types/stack.d.ts +36 -0
  158. package/dist/src/core/agent-generator/types/stack.js +2 -0
  159. package/dist/src/core/agent-generator/types/stack.js.map +1 -0
  160. package/dist/src/core/agent-generator/types/template.d.ts +29 -0
  161. package/dist/src/core/agent-generator/types/template.js +2 -0
  162. package/dist/src/core/agent-generator/types/template.js.map +1 -0
  163. package/dist/src/core/agent-runtime/ai-provider.d.ts +33 -0
  164. package/dist/src/core/agent-runtime/ai-provider.js +146 -0
  165. package/dist/src/core/agent-runtime/ai-provider.js.map +1 -0
  166. package/dist/src/core/agent-runtime/executor.d.ts +13 -0
  167. package/dist/src/core/agent-runtime/executor.js +138 -0
  168. package/dist/src/core/agent-runtime/executor.js.map +1 -0
  169. package/dist/src/core/agent-runtime/human-gate.d.ts +16 -0
  170. package/dist/src/core/agent-runtime/human-gate.js +70 -0
  171. package/dist/src/core/agent-runtime/human-gate.js.map +1 -0
  172. package/dist/tests/agent-generator.test.d.ts +1 -0
  173. package/dist/tests/agent-generator.test.js +349 -0
  174. package/dist/tests/agent-generator.test.js.map +1 -0
  175. package/dist/tests/agent-runtime.test.d.ts +1 -0
  176. package/dist/tests/agent-runtime.test.js +107 -0
  177. package/dist/tests/agent-runtime.test.js.map +1 -0
  178. package/dist/tests/context-enricher.test.d.ts +1 -0
  179. package/dist/tests/context-enricher.test.js +875 -0
  180. package/dist/tests/context-enricher.test.js.map +1 -0
  181. package/dist/tests/framework-detector.test.d.ts +1 -0
  182. package/dist/tests/framework-detector.test.js +882 -0
  183. package/dist/tests/framework-detector.test.js.map +1 -0
  184. package/dist/tests/stack-detector.test.d.ts +1 -0
  185. package/dist/tests/stack-detector.test.js +183 -0
  186. package/dist/tests/stack-detector.test.js.map +1 -0
  187. package/dist/tests/template-generation.test.d.ts +1 -0
  188. package/dist/tests/template-generation.test.js +571 -0
  189. package/dist/tests/template-generation.test.js.map +1 -0
  190. package/dist/tests/template-helpers.test.d.ts +1 -0
  191. package/dist/tests/template-helpers.test.js +967 -0
  192. package/dist/tests/template-helpers.test.js.map +1 -0
  193. package/package.json +24 -0
  194. package/src/core/agent-generator/context-enricher.ts +67 -0
  195. package/src/core/agent-generator/detectors/base-detector.ts +18 -0
  196. package/src/core/agent-generator/detectors/dart-detector.ts +17 -0
  197. package/src/core/agent-generator/detectors/framework-registry.ts +82 -0
  198. package/src/core/agent-generator/detectors/go-detector.ts +26 -0
  199. package/src/core/agent-generator/detectors/java-detector.ts +46 -0
  200. package/src/core/agent-generator/detectors/node-detector.ts +28 -0
  201. package/src/core/agent-generator/detectors/php-detector.ts +28 -0
  202. package/src/core/agent-generator/detectors/python-detector.ts +125 -0
  203. package/src/core/agent-generator/detectors/ruby-detector.ts +24 -0
  204. package/src/core/agent-generator/detectors/rust-detector.ts +19 -0
  205. package/src/core/agent-generator/detectors/structure-detector.ts +38 -0
  206. package/src/core/agent-generator/detectors/toolchain-detector.ts +181 -0
  207. package/src/core/agent-generator/domain-inferrer.ts +630 -0
  208. package/src/core/agent-generator/engines/audit-engine.ts +98 -0
  209. package/src/core/agent-generator/engines/context-builder.ts +96 -0
  210. package/src/core/agent-generator/engines/generation-engine.ts +184 -0
  211. package/src/core/agent-generator/engines/generation-engine_deps.ts +21 -0
  212. package/src/core/agent-generator/engines/suggestion-engine.ts +202 -0
  213. package/src/core/agent-generator/engines/suggestion-engine_deps.ts +8 -0
  214. package/src/core/agent-generator/enrichers/analysis-helpers.ts +58 -0
  215. package/src/core/agent-generator/enrichers/description-generator.ts +91 -0
  216. package/src/core/agent-generator/enrichers/endpoint-extractor.ts +114 -0
  217. package/src/core/agent-generator/enrichers/layer-classifier.ts +156 -0
  218. package/src/core/agent-generator/enrichers/module-extractor.ts +203 -0
  219. package/src/core/agent-generator/framework-detector.ts +66 -0
  220. package/src/core/agent-generator/index.ts +55 -0
  221. package/src/core/agent-generator/stack-detector.ts +115 -0
  222. package/src/core/agent-generator/templates/core/agents.ts +1168 -0
  223. package/src/core/agent-generator/templates/core/architecture-rules.ts +288 -0
  224. package/src/core/agent-generator/templates/core/general-rules.ts +306 -0
  225. package/src/core/agent-generator/templates/core/hooks-generator.ts +244 -0
  226. package/src/core/agent-generator/templates/core/index-md.ts +261 -0
  227. package/src/core/agent-generator/templates/core/orchestrator.ts +462 -0
  228. package/src/core/agent-generator/templates/core/preflight.ts +216 -0
  229. package/src/core/agent-generator/templates/core/quality-gates.ts +257 -0
  230. package/src/core/agent-generator/templates/core/security-rules.ts +544 -0
  231. package/src/core/agent-generator/templates/core/skills-generator.ts +586 -0
  232. package/src/core/agent-generator/templates/core/workflow-fix-bug.ts +240 -0
  233. package/src/core/agent-generator/templates/core/workflow-new-feature.ts +323 -0
  234. package/src/core/agent-generator/templates/core/workflow-review.ts +107 -0
  235. package/src/core/agent-generator/templates/domain/index.ts +1204 -0
  236. package/src/core/agent-generator/templates/helpers/base-helpers.ts +33 -0
  237. package/src/core/agent-generator/templates/helpers/cross-ref-helpers.ts +79 -0
  238. package/src/core/agent-generator/templates/helpers/security-helpers.ts +198 -0
  239. package/src/core/agent-generator/templates/helpers/stack-helpers.ts +80 -0
  240. package/src/core/agent-generator/templates/helpers/structure-helpers.ts +293 -0
  241. package/src/core/agent-generator/templates/helpers/summary-helpers.ts +67 -0
  242. package/src/core/agent-generator/templates/stack/index.ts +705 -0
  243. package/src/core/agent-generator/templates/template-helpers.ts +12 -0
  244. package/src/core/agent-generator/types/agent.ts +65 -0
  245. package/src/core/agent-generator/types/domain.ts +63 -0
  246. package/src/core/agent-generator/types/stack.ts +38 -0
  247. package/src/core/agent-generator/types/template.ts +31 -0
  248. package/src/core/agent-runtime/ai-provider.ts +178 -0
  249. package/src/core/agent-runtime/executor.ts +148 -0
  250. package/src/core/agent-runtime/human-gate.ts +69 -0
  251. package/tests/agent-generator.test.ts +428 -0
  252. package/tests/agent-runtime.test.ts +125 -0
  253. package/tests/context-enricher.test.ts +972 -0
  254. package/tests/framework-detector.test.ts +1172 -0
  255. package/tests/stack-detector.test.ts +241 -0
  256. package/tests/template-generation.test.ts +709 -0
  257. package/tests/template-helpers.test.ts +1130 -0
  258. package/tsconfig.json +14 -0
@@ -0,0 +1,630 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { AnalysisReport } from '@girardelli/architect-core/src/core/types/core.js';
4
+ import { DomainInsights, BusinessEntity, ComplianceRequirement, ExternalIntegration } from './types/domain.js';
5
+
6
+ /**
7
+ * DomainInferrer — Analyzes project metadata, file names, module structure,
8
+ * README content, and keywords to infer the business domain and context.
9
+ *
10
+ * This enables context-aware agent generation that understands WHAT the
11
+ * project does, not just HOW it's built.
12
+ */
13
+ export class DomainInferrer {
14
+ // Domain keyword mappings - patterns that indicate specific domains
15
+ private static readonly DOMAIN_PATTERNS: Record<string, { keywords: string[]; subDomains: Record<string, string[]> }> = {
16
+ fintech: {
17
+ keywords: ['payment', 'transaction', 'invoice', 'billing', 'ledger', 'account', 'transfer', 'wallet', 'bank', 'loan', 'credit', 'debit', 'finance', 'fiscal', 'tax', 'irpf', 'imposto', 'receita', 'declaracao', 'contribuinte', 'pix', 'boleto', 'nfe', 'nota-fiscal'],
18
+ subDomains: {
19
+ 'tax-processing': ['irpf', 'tax', 'imposto', 'fiscal', 'receita', 'declaracao', 'contribuinte', 'deducao', 'rendimento'],
20
+ 'payment-gateway': ['payment', 'pix', 'boleto', 'stripe', 'checkout', 'charge'],
21
+ 'banking': ['bank', 'account', 'transfer', 'balance', 'statement', 'ledger'],
22
+ 'invoicing': ['invoice', 'billing', 'nfe', 'nota-fiscal', 'fatura'],
23
+ 'lending': ['loan', 'credit', 'installment', 'interest', 'amortization'],
24
+ },
25
+ },
26
+ healthtech: {
27
+ keywords: ['patient', 'medical', 'health', 'diagnosis', 'prescription', 'clinical', 'hospital', 'doctor', 'appointment', 'ehr', 'fhir', 'dicom', 'sus', 'prontuario', 'consulta', 'exame'],
28
+ subDomains: {
29
+ 'ehr': ['patient', 'record', 'prontuario', 'clinical', 'ehr'],
30
+ 'telemedicine': ['appointment', 'consulta', 'video', 'teleconsulta'],
31
+ 'diagnostics': ['diagnosis', 'exame', 'lab', 'result'],
32
+ },
33
+ },
34
+ 'e-commerce': {
35
+ keywords: ['product', 'cart', 'order', 'catalog', 'shipping', 'checkout', 'inventory', 'price', 'discount', 'coupon', 'store', 'shop', 'marketplace', 'seller', 'buyer', 'carrinho', 'pedido', 'estoque', 'frete'],
36
+ subDomains: {
37
+ 'marketplace': ['seller', 'buyer', 'marketplace', 'commission'],
38
+ 'retail': ['product', 'catalog', 'inventory', 'store', 'shop'],
39
+ 'logistics': ['shipping', 'delivery', 'frete', 'tracking', 'warehouse'],
40
+ },
41
+ },
42
+ edtech: {
43
+ keywords: ['student', 'course', 'lesson', 'quiz', 'grade', 'enrollment', 'teacher', 'classroom', 'curriculum', 'lms', 'aluno', 'aula', 'nota', 'matricula', 'professor'],
44
+ subDomains: {
45
+ 'lms': ['course', 'lesson', 'enrollment', 'lms', 'aula'],
46
+ 'assessment': ['quiz', 'grade', 'exam', 'prova', 'nota'],
47
+ },
48
+ },
49
+ devtools: {
50
+ keywords: ['analyzer', 'analysis', 'architect', 'lint', 'linter', 'ast', 'parser', 'scanner', 'refactor', 'mcp', 'cli', 'plugin', 'extension', 'sdk', 'compiler', 'transpiler', 'bundler', 'formatter', 'debugger', 'profiler', 'intelligence', 'code-review', 'anti-pattern', 'score', 'metric', 'dependency-graph'],
51
+ subDomains: {
52
+ 'code-intelligence': ['analyzer', 'analysis', 'architect', 'anti-pattern', 'score', 'metric', 'intelligence', 'refactor'],
53
+ 'mcp-server': ['mcp', 'perception', 'reasoning', 'validation', 'tool-calling'],
54
+ 'cli-tool': ['cli', 'command', 'terminal', 'argv', 'yargs', 'commander'],
55
+ 'ide-extension': ['extension', 'plugin', 'language-server', 'lsp', 'vscode'],
56
+ 'build-tool': ['bundler', 'compiler', 'transpiler', 'webpack', 'esbuild', 'vite'],
57
+ },
58
+ },
59
+ saas: {
60
+ keywords: ['tenant', 'subscription', 'plan', 'workspace', 'organization', 'team', 'member', 'role', 'permission', 'billing', 'api-key', 'webhook', 'dashboard'],
61
+ subDomains: {
62
+ 'multi-tenant': ['tenant', 'workspace', 'organization'],
63
+ 'subscription': ['subscription', 'plan', 'billing', 'trial'],
64
+ },
65
+ },
66
+ iot: {
67
+ keywords: ['device', 'sensor', 'telemetry', 'mqtt', 'gateway', 'firmware', 'signal', 'measurement', 'monitoring', 'alert', 'threshold'],
68
+ subDomains: {
69
+ 'monitoring': ['sensor', 'telemetry', 'monitoring', 'measurement'],
70
+ 'device-management': ['device', 'firmware', 'gateway', 'provision'],
71
+ },
72
+ },
73
+ 'content-management': {
74
+ keywords: ['article', 'post', 'blog', 'page', 'cms', 'content', 'media', 'publish', 'author', 'category', 'tag', 'comment'],
75
+ subDomains: {
76
+ 'cms': ['article', 'post', 'publish', 'cms', 'content'],
77
+ 'media': ['media', 'image', 'video', 'upload', 'gallery'],
78
+ },
79
+ },
80
+ 'real-estate': {
81
+ keywords: ['property', 'listing', 'tenant', 'landlord', 'rent', 'lease', 'imovel', 'aluguel', 'inquilino', 'condominio'],
82
+ subDomains: {
83
+ 'property-management': ['property', 'tenant', 'rent', 'lease', 'condominio'],
84
+ 'listings': ['listing', 'search', 'filter', 'imovel'],
85
+ },
86
+ },
87
+ logistics: {
88
+ keywords: ['shipment', 'route', 'delivery', 'warehouse', 'fleet', 'driver', 'tracking', 'dispatch', 'load', 'cargo', 'entrega', 'rastreamento', 'frota', 'motorista'],
89
+ subDomains: {
90
+ 'fleet-management': ['fleet', 'driver', 'vehicle', 'frota', 'motorista'],
91
+ 'delivery': ['delivery', 'shipment', 'tracking', 'entrega', 'rastreamento'],
92
+ 'warehouse': ['warehouse', 'inventory', 'stock', 'storage'],
93
+ },
94
+ },
95
+ 'hr-management': {
96
+ keywords: ['employee', 'payroll', 'leave', 'attendance', 'hiring', 'candidate', 'department', 'salary', 'benefit', 'funcionario', 'folha', 'ferias', 'ponto', 'contratacao'],
97
+ subDomains: {
98
+ 'payroll': ['payroll', 'salary', 'folha', 'benefit', 'compensation'],
99
+ 'recruitment': ['hiring', 'candidate', 'interview', 'contratacao'],
100
+ 'time-tracking': ['attendance', 'leave', 'ponto', 'ferias'],
101
+ },
102
+ },
103
+ };
104
+
105
+ // Compliance mappings based on domain and geography
106
+ private static readonly COMPLIANCE_MAP: Record<string, ComplianceRequirement[]> = {
107
+ fintech: [
108
+ {
109
+ name: 'PCI-DSS',
110
+ reason: 'Handles financial/payment data',
111
+ mandatoryChecks: [
112
+ 'Credit card data never stored in plaintext',
113
+ 'Payment tokens used instead of raw card numbers',
114
+ 'Audit trail for all financial transactions',
115
+ 'Encryption at rest for financial data',
116
+ 'Access controls with principle of least privilege',
117
+ ],
118
+ },
119
+ ],
120
+ 'tax-processing': [
121
+ {
122
+ name: 'LGPD',
123
+ reason: 'Processes Brazilian taxpayer personal data (CPF, income, assets)',
124
+ mandatoryChecks: [
125
+ 'CPF/CNPJ data encrypted at rest and in transit',
126
+ 'Consent management for data processing',
127
+ 'Right to deletion (direito ao esquecimento)',
128
+ 'Data minimization — only collect necessary fields',
129
+ 'Data breach notification procedures',
130
+ 'DPO (Data Protection Officer) contact documented',
131
+ 'Cross-border data transfer restrictions',
132
+ ],
133
+ },
134
+ {
135
+ name: 'RFB-Compliance',
136
+ reason: 'Integrates with Receita Federal do Brasil systems',
137
+ mandatoryChecks: [
138
+ 'Digital certificate (e-CPF/e-CNPJ) handling',
139
+ 'XML schema validation against RFB specifications',
140
+ 'Audit log for all tax calculation modifications',
141
+ 'Retention period compliance (5 years minimum)',
142
+ 'Hash validation for submitted declarations',
143
+ ],
144
+ },
145
+ ],
146
+ healthtech: [
147
+ {
148
+ name: 'HIPAA',
149
+ reason: 'Handles protected health information (PHI)',
150
+ mandatoryChecks: [
151
+ 'PHI encryption at rest and in transit',
152
+ 'Access audit logging for all PHI access',
153
+ 'Minimum necessary access principle',
154
+ 'Business Associate Agreements (BAA) tracking',
155
+ 'Breach notification within 60 days',
156
+ ],
157
+ },
158
+ {
159
+ name: 'LGPD-Health',
160
+ reason: 'Processes sensitive health data under Brazilian law',
161
+ mandatoryChecks: [
162
+ 'Explicit consent for health data processing',
163
+ 'Data anonymization where possible',
164
+ 'Restricted access to health records',
165
+ 'CFM/CRM compliance for medical data',
166
+ ],
167
+ },
168
+ ],
169
+ 'e-commerce': [
170
+ {
171
+ name: 'CDC',
172
+ reason: 'Consumer Defense Code (Código de Defesa do Consumidor)',
173
+ mandatoryChecks: [
174
+ 'Clear pricing display (no hidden fees)',
175
+ 'Right of withdrawal (7 days for online purchases)',
176
+ 'Order tracking transparency',
177
+ 'Invoice generation for all transactions',
178
+ ],
179
+ },
180
+ ],
181
+ 'hr-management': [
182
+ {
183
+ name: 'LGPD-Employment',
184
+ reason: 'Processes employee personal and financial data',
185
+ mandatoryChecks: [
186
+ 'Employee data encrypted at rest',
187
+ 'Salary data access restricted to HR and payroll',
188
+ 'Data retention aligned with labor law (5 years after termination)',
189
+ 'Consent for data processing beyond employment',
190
+ ],
191
+ },
192
+ ],
193
+ };
194
+
195
+ /**
196
+ * Infer domain insights from the analysis report.
197
+ */
198
+ infer(report: AnalysisReport, projectPath: string): DomainInsights {
199
+ const allKeywords = this.extractAllKeywords(report);
200
+
201
+ // Boost keywords from project files (pyproject.toml, README, package.json)
202
+ const fileKeywords = this.extractKeywordsFromProjectFiles(projectPath);
203
+ for (const kw of fileKeywords) {
204
+ if (!allKeywords.includes(kw)) allKeywords.push(kw);
205
+ }
206
+
207
+ const { domain, subDomain, confidence } = this.classifyDomain(allKeywords);
208
+ const businessEntities = this.extractBusinessEntities(report);
209
+ const compliance = this.inferCompliance(domain, subDomain, allKeywords);
210
+ const integrations = this.detectIntegrations(report, allKeywords);
211
+ const description = this.buildDescription(report, domain, subDomain);
212
+
213
+ return {
214
+ domain,
215
+ subDomain,
216
+ description,
217
+ businessEntities,
218
+ compliance,
219
+ integrations,
220
+ keywords: allKeywords,
221
+ confidence,
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Extract all relevant keywords from the project.
227
+ */
228
+ private extractAllKeywords(report: AnalysisReport): string[] {
229
+ const keywords = new Set<string>();
230
+
231
+ // From ProjectSummary keywords
232
+ if (report.projectSummary?.keywords) {
233
+ for (const kw of report.projectSummary.keywords) {
234
+ keywords.add(kw.toLowerCase());
235
+ }
236
+ }
237
+
238
+ // From module names
239
+ if (report.projectSummary?.modules) {
240
+ for (const mod of report.projectSummary.modules) {
241
+ keywords.add(mod.name.toLowerCase());
242
+ // Split camelCase/snake_case module names
243
+ for (const part of this.splitIdentifier(mod.name)) {
244
+ keywords.add(part.toLowerCase());
245
+ }
246
+ }
247
+ }
248
+
249
+ // From file paths — extract meaningful segments
250
+ for (const node of report.dependencyGraph.nodes) {
251
+ const segments = node.split('/').filter(s =>
252
+ !['src', 'lib', 'app', 'core', 'common', 'shared', 'utils', 'helpers',
253
+ 'index', 'main', '__init__', 'test', 'tests', 'spec', '__tests__',
254
+ 'node_modules', 'dist', 'build', '.git'].includes(s.toLowerCase())
255
+ );
256
+ for (const seg of segments) {
257
+ const name = seg.replace(/\.[^.]+$/, ''); // remove extension
258
+ for (const part of this.splitIdentifier(name)) {
259
+ if (part.length > 2) {
260
+ keywords.add(part.toLowerCase());
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ // From project name
267
+ if (report.projectInfo.name) {
268
+ for (const part of this.splitIdentifier(report.projectInfo.name)) {
269
+ keywords.add(part.toLowerCase());
270
+ }
271
+ }
272
+
273
+ // From description/purpose
274
+ if (report.projectSummary?.description) {
275
+ const words = report.projectSummary.description.toLowerCase().split(/\s+/);
276
+ for (const w of words) {
277
+ if (w.length > 3) keywords.add(w.replace(/[^a-z0-9]/g, ''));
278
+ }
279
+ }
280
+
281
+ return [...keywords].filter(k => k.length > 0);
282
+ }
283
+
284
+ /**
285
+ * Classify project domain based on keyword matching.
286
+ */
287
+ private classifyDomain(keywords: string[]): { domain: string; subDomain: string; confidence: number } {
288
+ let bestDomain = 'general';
289
+ let bestSubDomain = 'general';
290
+ let bestScore = 0;
291
+ let bestMaxPossible = 1;
292
+
293
+ for (const [domain, config] of Object.entries(DomainInferrer.DOMAIN_PATTERNS)) {
294
+ const matchedKeywords = config.keywords.filter(kw =>
295
+ keywords.some(k => k.includes(kw) || kw.includes(k))
296
+ );
297
+ const score = matchedKeywords.length;
298
+
299
+ if (score > bestScore) {
300
+ bestScore = score;
301
+ bestDomain = domain;
302
+ bestMaxPossible = config.keywords.length;
303
+
304
+ // Find best sub-domain
305
+ let bestSubScore = 0;
306
+ for (const [subDomain, subKeywords] of Object.entries(config.subDomains)) {
307
+ const subMatched = subKeywords.filter(kw =>
308
+ keywords.some(k => k.includes(kw) || kw.includes(k))
309
+ ).length;
310
+ if (subMatched > bestSubScore) {
311
+ bestSubScore = subMatched;
312
+ bestSubDomain = subDomain;
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ // Confidence formula:
319
+ // - Base: matched keywords / min(total possible, 8) (lower denominator = easier to reach high confidence)
320
+ // - Boost: +0.1 if sub-domain also matched (cross-validation)
321
+ // - Boost: +0.1 if multiple entities or integrations hint at the same domain
322
+ // - Cap at 0.95 (never 100% without manual confirmation)
323
+ let confidence = bestScore > 0 ? Math.min(1, bestScore / Math.min(bestMaxPossible, 8)) : 0;
324
+
325
+ // Sub-domain match boost
326
+ if (bestSubDomain !== 'general' && bestSubDomain !== bestDomain) {
327
+ confidence = Math.min(1, confidence + 0.1);
328
+ }
329
+
330
+ // Second-best domain gap boost (high gap = more certain)
331
+ let secondBestScore = 0;
332
+ for (const [domain, config] of Object.entries(DomainInferrer.DOMAIN_PATTERNS)) {
333
+ if (domain === bestDomain) continue;
334
+ const matchedKeywords = config.keywords.filter(kw =>
335
+ keywords.some(k => k.includes(kw) || kw.includes(k))
336
+ );
337
+ if (matchedKeywords.length > secondBestScore) {
338
+ secondBestScore = matchedKeywords.length;
339
+ }
340
+ }
341
+ if (bestScore > 0 && bestScore >= secondBestScore * 2) {
342
+ confidence = Math.min(1, confidence + 0.1);
343
+ }
344
+
345
+ // Cap at 0.95
346
+ confidence = Math.min(0.95, confidence);
347
+
348
+ return { domain: bestDomain, subDomain: bestSubDomain, confidence };
349
+ }
350
+
351
+ /**
352
+ * Extract business entities from model/entity/schema file names.
353
+ */
354
+ private extractBusinessEntities(report: AnalysisReport): BusinessEntity[] {
355
+ const entities: BusinessEntity[] = [];
356
+ const entityPatterns = [
357
+ /(?:models?|entities|entity|schemas?)\/([^/]+)\./i,
358
+ /([^/]+)\.(model|entity|schema)\./i,
359
+ /([^/]+)\.models?\./i,
360
+ ];
361
+
362
+ for (const filePath of report.dependencyGraph.nodes) {
363
+ for (const pattern of entityPatterns) {
364
+ const match = filePath.match(pattern);
365
+ if (match) {
366
+ const name = match[1].replace(/[-_]/g, ' ').replace(/\b\w/g, l => l.toUpperCase()).trim();
367
+ if (name && name !== 'Index' && name !== 'Base' && name !== 'Init') {
368
+ const layer = this.inferEntityLayer(filePath);
369
+ entities.push({
370
+ name,
371
+ source: filePath,
372
+ fields: [], // Would need AST parsing for fields
373
+ relationships: [],
374
+ layer,
375
+ });
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ // Deduplicate by name
382
+ const seen = new Set<string>();
383
+ return entities.filter(e => {
384
+ const key = e.name.toLowerCase();
385
+ if (seen.has(key)) return false;
386
+ seen.add(key);
387
+ return true;
388
+ });
389
+ }
390
+
391
+ private inferEntityLayer(filePath: string): BusinessEntity['layer'] {
392
+ const lower = filePath.toLowerCase();
393
+ if (lower.includes('/model')) return 'model';
394
+ if (lower.includes('/entity') || lower.includes('/entities')) return 'entity';
395
+ if (lower.includes('/schema')) return 'schema';
396
+ if (lower.includes('/dto')) return 'dto';
397
+ return 'unknown';
398
+ }
399
+
400
+ /**
401
+ * Infer compliance requirements from domain and keywords.
402
+ */
403
+ private inferCompliance(domain: string, subDomain: string, keywords: string[]): ComplianceRequirement[] {
404
+ const requirements: ComplianceRequirement[] = [];
405
+
406
+ // Domain-level compliance
407
+ const domainCompliance = DomainInferrer.COMPLIANCE_MAP[domain];
408
+ if (domainCompliance) {
409
+ requirements.push(...domainCompliance);
410
+ }
411
+
412
+ // Sub-domain specific compliance
413
+ const subCompliance = DomainInferrer.COMPLIANCE_MAP[subDomain];
414
+ if (subCompliance) {
415
+ requirements.push(...subCompliance);
416
+ }
417
+
418
+ // Generic LGPD if Brazilian indicators found
419
+ const brIndicators = ['cpf', 'cnpj', 'cep', 'lgpd', 'brasil', 'brazil', 'pt-br', 'ptbr', 'receita'];
420
+ const hasBrazilianContext = keywords.some(k => brIndicators.some(br => k.includes(br)));
421
+ if (hasBrazilianContext && !requirements.some(r => r.name.includes('LGPD'))) {
422
+ requirements.push({
423
+ name: 'LGPD',
424
+ reason: 'Brazilian context detected — personal data processing requires LGPD compliance',
425
+ mandatoryChecks: [
426
+ 'Personal data encrypted at rest and in transit',
427
+ 'Consent management for data processing',
428
+ 'Right to deletion implementation',
429
+ 'Data breach notification procedures',
430
+ ],
431
+ });
432
+ }
433
+
434
+ // PII detection regardless of domain
435
+ const piiIndicators = ['email', 'password', 'phone', 'address', 'ssn', 'cpf', 'passport', 'birth'];
436
+ const hasPII = keywords.some(k => piiIndicators.some(p => k.includes(p)));
437
+ if (hasPII && !requirements.some(r => r.name === 'LGPD' || r.name === 'GDPR')) {
438
+ requirements.push({
439
+ name: 'PII-Protection',
440
+ reason: 'Personal Identifiable Information (PII) detected in project entities',
441
+ mandatoryChecks: [
442
+ 'PII fields encrypted at rest',
443
+ 'Access logging for PII data',
444
+ 'Data masking in logs and error messages',
445
+ 'Secure deletion procedures',
446
+ ],
447
+ });
448
+ }
449
+
450
+ return requirements;
451
+ }
452
+
453
+ /**
454
+ * Detect external integrations from file names, imports, and keywords.
455
+ */
456
+ private detectIntegrations(report: AnalysisReport, keywords: string[]): ExternalIntegration[] {
457
+ const integrations: ExternalIntegration[] = [];
458
+ const allFiles = report.dependencyGraph.nodes.join(' ').toLowerCase();
459
+
460
+ const integrationPatterns: { name: string; type: ExternalIntegration['type']; signals: string[] }[] = [
461
+ // Payment
462
+ { name: 'Stripe', type: 'payment', signals: ['stripe'] },
463
+ { name: 'PagSeguro', type: 'payment', signals: ['pagseguro'] },
464
+ { name: 'Mercado Pago', type: 'payment', signals: ['mercadopago', 'mercado-pago'] },
465
+ { name: 'PIX', type: 'payment', signals: ['pix'] },
466
+ // Auth
467
+ { name: 'OAuth2/OIDC', type: 'auth', signals: ['oauth', 'oidc', 'openid'] },
468
+ { name: 'JWT', type: 'auth', signals: ['jwt', 'jsonwebtoken'] },
469
+ { name: 'Keycloak', type: 'auth', signals: ['keycloak'] },
470
+ { name: 'Auth0', type: 'auth', signals: ['auth0'] },
471
+ // Database
472
+ { name: 'PostgreSQL', type: 'database', signals: ['postgres', 'postgresql', 'pg'] },
473
+ { name: 'MongoDB', type: 'database', signals: ['mongo', 'mongodb', 'mongoose'] },
474
+ { name: 'Redis', type: 'database', signals: ['redis', 'ioredis'] },
475
+ { name: 'MySQL', type: 'database', signals: ['mysql', 'mysql2'] },
476
+ { name: 'SQLite', type: 'database', signals: ['sqlite'] },
477
+ // Queue/Messaging
478
+ { name: 'RabbitMQ', type: 'queue', signals: ['rabbitmq', 'amqp'] },
479
+ { name: 'Kafka', type: 'queue', signals: ['kafka'] },
480
+ { name: 'AWS SQS', type: 'queue', signals: ['sqs'] },
481
+ { name: 'BullMQ', type: 'queue', signals: ['bull', 'bullmq'] },
482
+ // Storage
483
+ { name: 'AWS S3', type: 'storage', signals: ['s3', 'aws-sdk'] },
484
+ { name: 'MinIO', type: 'storage', signals: ['minio'] },
485
+ // Government APIs
486
+ { name: 'Receita Federal', type: 'government', signals: ['receita', 'rfb', 'ecac', 'sefaz'] },
487
+ { name: 'SERPRO', type: 'government', signals: ['serpro'] },
488
+ { name: 'eSocial', type: 'government', signals: ['esocial'] },
489
+ // Other
490
+ { name: 'Email (SMTP)', type: 'other', signals: ['smtp', 'nodemailer', 'sendgrid', 'ses'] },
491
+ { name: 'ElasticSearch', type: 'other', signals: ['elastic', 'elasticsearch'] },
492
+ { name: 'Sentry', type: 'other', signals: ['sentry'] },
493
+ ];
494
+
495
+ for (const pattern of integrationPatterns) {
496
+ const matched = pattern.signals.find(s =>
497
+ allFiles.includes(s) || keywords.some(k => k.includes(s))
498
+ );
499
+ if (matched) {
500
+ integrations.push({
501
+ name: pattern.name,
502
+ type: pattern.type,
503
+ detectedFrom: matched,
504
+ });
505
+ }
506
+ }
507
+
508
+ return integrations;
509
+ }
510
+
511
+ /**
512
+ * Build a human-readable description of the project.
513
+ */
514
+ private buildDescription(report: AnalysisReport, domain: string, subDomain: string): string {
515
+ const summary = report.projectSummary;
516
+ if (summary?.description && summary.description.length > 20) {
517
+ return summary.description;
518
+ }
519
+
520
+ const name = report.projectInfo.name || 'Project';
521
+ const langs = report.projectInfo.primaryLanguages.join('/');
522
+ const files = report.projectInfo.totalFiles;
523
+ const lines = report.projectInfo.totalLines.toLocaleString();
524
+
525
+ if (domain !== 'general') {
526
+ return `${name} — ${domain}/${subDomain} application built with ${langs}. ${files} files, ${lines} lines.`;
527
+ }
528
+
529
+ return `${name} — ${langs} application with ${files} files and ${lines} lines.`;
530
+ }
531
+
532
+ /**
533
+ * Split camelCase, PascalCase, snake_case, kebab-case identifiers into parts.
534
+ */
535
+ /**
536
+ * Extract keywords from project files on disk (pyproject.toml, README.md, package.json).
537
+ * These provide high-value domain signals that the scanner may miss.
538
+ */
539
+ private extractKeywordsFromProjectFiles(projectPath: string): string[] {
540
+ const keywords: string[] = [];
541
+
542
+ // pyproject.toml — description, classifiers, project name
543
+ const pyprojectPath = join(projectPath, 'pyproject.toml');
544
+ if (existsSync(pyprojectPath)) {
545
+ try {
546
+ const content = readFileSync(pyprojectPath, 'utf-8');
547
+
548
+ // Extract description
549
+ const descMatch = content.match(/description\s*=\s*"([^"]+)"/);
550
+ if (descMatch) {
551
+ const words = descMatch[1].toLowerCase().split(/\s+/);
552
+ for (const w of words) {
553
+ if (w.length > 3) keywords.push(w.replace(/[^a-z0-9]/g, ''));
554
+ }
555
+ }
556
+
557
+ // Extract classifiers (e.g., "Topic :: Office/Business :: Financial")
558
+ const classifiers = content.match(/classifiers\s*=\s*\[([\s\S]*?)\]/);
559
+ if (classifiers) {
560
+ const topics = classifiers[1].match(/"Topic\s*::\s*([^"]+)"/g);
561
+ if (topics) {
562
+ for (const topic of topics) {
563
+ const parts = topic.replace(/"/g, '').split('::').map(p => p.trim().toLowerCase());
564
+ for (const p of parts) {
565
+ if (p.length > 3 && p !== 'topic') keywords.push(p.replace(/[^a-z0-9]/g, ''));
566
+ }
567
+ }
568
+ }
569
+ }
570
+
571
+ // Extract project name
572
+ const nameMatch = content.match(/\[project\]\s*\n(?:[\s\S]*?)name\s*=\s*"([^"]+)"/);
573
+ if (nameMatch) {
574
+ for (const part of this.splitIdentifier(nameMatch[1])) {
575
+ keywords.push(part.toLowerCase());
576
+ }
577
+ }
578
+ } catch { /* ignore read errors */ }
579
+ }
580
+
581
+ // README.md — first 500 chars for domain keywords
582
+ const readmePaths = ['README.md', 'readme.md', 'README.rst'];
583
+ for (const readmeName of readmePaths) {
584
+ const readmePath = join(projectPath, readmeName);
585
+ if (existsSync(readmePath)) {
586
+ try {
587
+ const content = readFileSync(readmePath, 'utf-8').slice(0, 1500);
588
+ // Extract heading and first paragraph
589
+ const lines = content.split('\n').filter(l => l.trim().length > 0).slice(0, 10);
590
+ for (const line of lines) {
591
+ const words = line.replace(/[#*_`\[\]()]/g, '').toLowerCase().split(/\s+/);
592
+ for (const w of words) {
593
+ if (w.length > 3) keywords.push(w.replace(/[^a-z0-9]/g, ''));
594
+ }
595
+ }
596
+ } catch { /* ignore */ }
597
+ break;
598
+ }
599
+ }
600
+
601
+ // package.json — description and keywords
602
+ const pkgPath = join(projectPath, 'package.json');
603
+ if (existsSync(pkgPath)) {
604
+ try {
605
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
606
+ if (pkg.description) {
607
+ const words = pkg.description.toLowerCase().split(/\s+/);
608
+ for (const w of words) {
609
+ if (w.length > 3) keywords.push(w.replace(/[^a-z0-9]/g, ''));
610
+ }
611
+ }
612
+ if (Array.isArray(pkg.keywords)) {
613
+ for (const kw of pkg.keywords) {
614
+ keywords.push(kw.toLowerCase());
615
+ }
616
+ }
617
+ } catch { /* ignore */ }
618
+ }
619
+
620
+ return keywords.filter(k => k.length > 0);
621
+ }
622
+
623
+ private splitIdentifier(name: string): string[] {
624
+ return name
625
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
626
+ .replace(/[-_./\\]/g, ' ')
627
+ .split(/\s+/)
628
+ .filter(p => p.length > 0);
629
+ }
630
+ }