@girardelli/architect 5.0.0 → 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 (335) hide show
  1. package/dist/{cli.d.ts → src/adapters/cli.d.ts} +1 -2
  2. package/dist/{cli.js → src/adapters/cli.js} +191 -213
  3. package/dist/src/adapters/cli.js.map +1 -0
  4. package/dist/src/adapters/github-action.d.ts +9 -0
  5. package/dist/src/adapters/github-action.js +94 -0
  6. package/dist/src/adapters/github-action.js.map +1 -0
  7. package/dist/src/adapters/html-reporter/scripts.d.ts +5 -0
  8. package/dist/src/adapters/html-reporter/scripts.js +400 -0
  9. package/dist/src/adapters/html-reporter/scripts.js.map +1 -0
  10. package/dist/src/adapters/html-reporter/sections/agents.d.ts +2 -0
  11. package/dist/src/adapters/html-reporter/sections/agents.js +260 -0
  12. package/dist/src/adapters/html-reporter/sections/agents.js.map +1 -0
  13. package/dist/src/adapters/html-reporter/sections/anti-patterns.d.ts +13 -0
  14. package/dist/src/adapters/html-reporter/sections/anti-patterns.js +64 -0
  15. package/dist/src/adapters/html-reporter/sections/anti-patterns.js.map +1 -0
  16. package/dist/src/adapters/html-reporter/sections/header.d.ts +3 -0
  17. package/dist/src/adapters/html-reporter/sections/header.js +30 -0
  18. package/dist/src/adapters/html-reporter/sections/header.js.map +1 -0
  19. package/dist/src/adapters/html-reporter/sections/layers.d.ts +9 -0
  20. package/dist/src/adapters/html-reporter/sections/layers.js +143 -0
  21. package/dist/src/adapters/html-reporter/sections/layers.js.map +1 -0
  22. package/dist/src/adapters/html-reporter/sections/overview.d.ts +2 -0
  23. package/dist/src/adapters/html-reporter/sections/overview.js +58 -0
  24. package/dist/src/adapters/html-reporter/sections/overview.js.map +1 -0
  25. package/dist/src/adapters/html-reporter/sections/refactoring-plan.d.ts +3 -0
  26. package/dist/src/adapters/html-reporter/sections/refactoring-plan.js +151 -0
  27. package/dist/src/adapters/html-reporter/sections/refactoring-plan.js.map +1 -0
  28. package/dist/src/adapters/html-reporter/sections/score.d.ts +7 -0
  29. package/dist/src/adapters/html-reporter/sections/score.js +70 -0
  30. package/dist/src/adapters/html-reporter/sections/score.js.map +1 -0
  31. package/dist/src/adapters/html-reporter/sections/suggestions.d.ts +7 -0
  32. package/dist/src/adapters/html-reporter/sections/suggestions.js +34 -0
  33. package/dist/src/adapters/html-reporter/sections/suggestions.js.map +1 -0
  34. package/dist/src/adapters/html-reporter/styles.d.ts +1 -0
  35. package/dist/src/adapters/html-reporter/styles.js +526 -0
  36. package/dist/src/adapters/html-reporter/styles.js.map +1 -0
  37. package/dist/src/adapters/html-reporter/utils_adapters.d.ts +20 -0
  38. package/dist/src/adapters/html-reporter/utils_adapters.js +32 -0
  39. package/dist/src/adapters/html-reporter/utils_adapters.js.map +1 -0
  40. package/dist/src/adapters/html-reporter/utils_sections.d.ts +7 -0
  41. package/dist/src/adapters/html-reporter/utils_sections.js +58 -0
  42. package/dist/src/adapters/html-reporter/utils_sections.js.map +1 -0
  43. package/dist/src/adapters/html-reporter.d.ts +10 -0
  44. package/dist/src/adapters/html-reporter.js +97 -0
  45. package/dist/src/adapters/html-reporter.js.map +1 -0
  46. package/dist/src/adapters/progress-logger.d.ts +55 -0
  47. package/dist/src/adapters/progress-logger.js +200 -0
  48. package/dist/src/adapters/progress-logger.js.map +1 -0
  49. package/dist/{refactor-reporter.d.ts → src/adapters/refactor-reporter.d.ts} +1 -2
  50. package/dist/{refactor-reporter.js → src/adapters/refactor-reporter.js} +1 -1
  51. package/dist/src/adapters/refactor-reporter.js.map +1 -0
  52. package/dist/{reporter.d.ts → src/adapters/reporter.d.ts} +1 -2
  53. package/dist/src/adapters/reporter.js.map +1 -0
  54. package/dist/src/core/GenesisTerminal.d.ts +8 -0
  55. package/dist/src/core/GenesisTerminal.js +105 -0
  56. package/dist/src/core/GenesisTerminal.js.map +1 -0
  57. package/dist/{index.d.ts → src/core/architect.d.ts} +4 -18
  58. package/dist/{index.js → src/core/architect.js} +22 -21
  59. package/dist/src/core/architect.js.map +1 -0
  60. package/dist/tests/architect-adapter-enrichment.test.d.ts +1 -0
  61. package/dist/tests/architect-adapter-enrichment.test.js +11 -0
  62. package/dist/tests/architect-adapter-enrichment.test.js.map +1 -0
  63. package/dist/tests/github-action.test.d.ts +1 -0
  64. package/dist/tests/github-action.test.js +92 -0
  65. package/dist/tests/github-action.test.js.map +1 -0
  66. package/package.json +15 -65
  67. package/src/adapters/cli.ts +492 -0
  68. package/src/adapters/github-action.ts +109 -0
  69. package/src/adapters/html-reporter/scripts.ts +402 -0
  70. package/src/adapters/html-reporter/sections/agents.ts +267 -0
  71. package/src/adapters/html-reporter/sections/anti-patterns.ts +81 -0
  72. package/src/adapters/html-reporter/sections/header.ts +35 -0
  73. package/src/adapters/html-reporter/sections/layers.ts +165 -0
  74. package/src/adapters/html-reporter/sections/overview.ts +64 -0
  75. package/src/adapters/html-reporter/sections/refactoring-plan.ts +166 -0
  76. package/src/adapters/html-reporter/sections/score.ts +80 -0
  77. package/src/adapters/html-reporter/sections/suggestions.ts +39 -0
  78. package/src/adapters/html-reporter/styles.ts +525 -0
  79. package/src/adapters/html-reporter/utils_adapters.ts +39 -0
  80. package/src/adapters/html-reporter/utils_sections.ts +55 -0
  81. package/src/adapters/html-reporter.ts +102 -0
  82. package/src/adapters/progress-logger.ts +236 -0
  83. package/src/{refactor-reporter.ts → adapters/refactor-reporter.ts} +2 -2
  84. package/src/{reporter.ts → adapters/reporter.ts} +1 -1
  85. package/src/core/GenesisTerminal.ts +127 -0
  86. package/src/{index.ts → core/architect.ts} +27 -45
  87. package/tests/github-action.test.ts +109 -0
  88. package/tsconfig.json +12 -19
  89. package/CONTRIBUTING.md +0 -140
  90. package/LICENSE +0 -21
  91. package/PROJECT_STRUCTURE.txt +0 -168
  92. package/README.md +0 -257
  93. package/architect-run.sh +0 -431
  94. package/assets/banner-v3.html +0 -561
  95. package/dist/agent-generator/context-enricher.d.ts +0 -58
  96. package/dist/agent-generator/context-enricher.d.ts.map +0 -1
  97. package/dist/agent-generator/context-enricher.js +0 -613
  98. package/dist/agent-generator/context-enricher.js.map +0 -1
  99. package/dist/agent-generator/domain-inferrer.d.ts +0 -52
  100. package/dist/agent-generator/domain-inferrer.d.ts.map +0 -1
  101. package/dist/agent-generator/domain-inferrer.js +0 -585
  102. package/dist/agent-generator/domain-inferrer.js.map +0 -1
  103. package/dist/agent-generator/framework-detector.d.ts +0 -40
  104. package/dist/agent-generator/framework-detector.d.ts.map +0 -1
  105. package/dist/agent-generator/framework-detector.js +0 -611
  106. package/dist/agent-generator/framework-detector.js.map +0 -1
  107. package/dist/agent-generator/index.d.ts +0 -47
  108. package/dist/agent-generator/index.d.ts.map +0 -1
  109. package/dist/agent-generator/index.js +0 -545
  110. package/dist/agent-generator/index.js.map +0 -1
  111. package/dist/agent-generator/stack-detector.d.ts +0 -14
  112. package/dist/agent-generator/stack-detector.d.ts.map +0 -1
  113. package/dist/agent-generator/stack-detector.js +0 -124
  114. package/dist/agent-generator/stack-detector.js.map +0 -1
  115. package/dist/agent-generator/templates/core/agents.d.ts +0 -17
  116. package/dist/agent-generator/templates/core/agents.d.ts.map +0 -1
  117. package/dist/agent-generator/templates/core/agents.js +0 -1256
  118. package/dist/agent-generator/templates/core/agents.js.map +0 -1
  119. package/dist/agent-generator/templates/core/architecture-rules.d.ts +0 -7
  120. package/dist/agent-generator/templates/core/architecture-rules.d.ts.map +0 -1
  121. package/dist/agent-generator/templates/core/architecture-rules.js +0 -274
  122. package/dist/agent-generator/templates/core/architecture-rules.js.map +0 -1
  123. package/dist/agent-generator/templates/core/general-rules.d.ts +0 -8
  124. package/dist/agent-generator/templates/core/general-rules.d.ts.map +0 -1
  125. package/dist/agent-generator/templates/core/general-rules.js +0 -301
  126. package/dist/agent-generator/templates/core/general-rules.js.map +0 -1
  127. package/dist/agent-generator/templates/core/hooks-generator.d.ts +0 -21
  128. package/dist/agent-generator/templates/core/hooks-generator.d.ts.map +0 -1
  129. package/dist/agent-generator/templates/core/hooks-generator.js +0 -233
  130. package/dist/agent-generator/templates/core/hooks-generator.js.map +0 -1
  131. package/dist/agent-generator/templates/core/index-md.d.ts +0 -7
  132. package/dist/agent-generator/templates/core/index-md.d.ts.map +0 -1
  133. package/dist/agent-generator/templates/core/index-md.js +0 -246
  134. package/dist/agent-generator/templates/core/index-md.js.map +0 -1
  135. package/dist/agent-generator/templates/core/orchestrator.d.ts +0 -8
  136. package/dist/agent-generator/templates/core/orchestrator.d.ts.map +0 -1
  137. package/dist/agent-generator/templates/core/orchestrator.js +0 -422
  138. package/dist/agent-generator/templates/core/orchestrator.js.map +0 -1
  139. package/dist/agent-generator/templates/core/preflight.d.ts +0 -8
  140. package/dist/agent-generator/templates/core/preflight.d.ts.map +0 -1
  141. package/dist/agent-generator/templates/core/preflight.js +0 -213
  142. package/dist/agent-generator/templates/core/preflight.js.map +0 -1
  143. package/dist/agent-generator/templates/core/quality-gates.d.ts +0 -11
  144. package/dist/agent-generator/templates/core/quality-gates.d.ts.map +0 -1
  145. package/dist/agent-generator/templates/core/quality-gates.js +0 -254
  146. package/dist/agent-generator/templates/core/quality-gates.js.map +0 -1
  147. package/dist/agent-generator/templates/core/security-rules.d.ts +0 -7
  148. package/dist/agent-generator/templates/core/security-rules.d.ts.map +0 -1
  149. package/dist/agent-generator/templates/core/security-rules.js +0 -528
  150. package/dist/agent-generator/templates/core/security-rules.js.map +0 -1
  151. package/dist/agent-generator/templates/core/skills-generator.d.ts +0 -19
  152. package/dist/agent-generator/templates/core/skills-generator.d.ts.map +0 -1
  153. package/dist/agent-generator/templates/core/skills-generator.js +0 -546
  154. package/dist/agent-generator/templates/core/skills-generator.js.map +0 -1
  155. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts +0 -7
  156. package/dist/agent-generator/templates/core/workflow-fix-bug.d.ts.map +0 -1
  157. package/dist/agent-generator/templates/core/workflow-fix-bug.js +0 -237
  158. package/dist/agent-generator/templates/core/workflow-fix-bug.js.map +0 -1
  159. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts +0 -8
  160. package/dist/agent-generator/templates/core/workflow-new-feature.d.ts.map +0 -1
  161. package/dist/agent-generator/templates/core/workflow-new-feature.js +0 -321
  162. package/dist/agent-generator/templates/core/workflow-new-feature.js.map +0 -1
  163. package/dist/agent-generator/templates/core/workflow-review.d.ts +0 -7
  164. package/dist/agent-generator/templates/core/workflow-review.d.ts.map +0 -1
  165. package/dist/agent-generator/templates/core/workflow-review.js +0 -104
  166. package/dist/agent-generator/templates/core/workflow-review.js.map +0 -1
  167. package/dist/agent-generator/templates/domain/index.d.ts +0 -22
  168. package/dist/agent-generator/templates/domain/index.d.ts.map +0 -1
  169. package/dist/agent-generator/templates/domain/index.js +0 -1176
  170. package/dist/agent-generator/templates/domain/index.js.map +0 -1
  171. package/dist/agent-generator/templates/stack/index.d.ts +0 -8
  172. package/dist/agent-generator/templates/stack/index.d.ts.map +0 -1
  173. package/dist/agent-generator/templates/stack/index.js +0 -695
  174. package/dist/agent-generator/templates/stack/index.js.map +0 -1
  175. package/dist/agent-generator/templates/template-helpers.d.ts +0 -75
  176. package/dist/agent-generator/templates/template-helpers.d.ts.map +0 -1
  177. package/dist/agent-generator/templates/template-helpers.js +0 -726
  178. package/dist/agent-generator/templates/template-helpers.js.map +0 -1
  179. package/dist/agent-generator/types.d.ts +0 -196
  180. package/dist/agent-generator/types.d.ts.map +0 -1
  181. package/dist/agent-generator/types.js +0 -27
  182. package/dist/agent-generator/types.js.map +0 -1
  183. package/dist/analyzer.d.ts +0 -38
  184. package/dist/analyzer.d.ts.map +0 -1
  185. package/dist/analyzer.js +0 -383
  186. package/dist/analyzer.js.map +0 -1
  187. package/dist/analyzers/forecast.d.ts +0 -85
  188. package/dist/analyzers/forecast.d.ts.map +0 -1
  189. package/dist/analyzers/forecast.js +0 -337
  190. package/dist/analyzers/forecast.js.map +0 -1
  191. package/dist/analyzers/git-cache.d.ts +0 -7
  192. package/dist/analyzers/git-cache.d.ts.map +0 -1
  193. package/dist/analyzers/git-cache.js +0 -41
  194. package/dist/analyzers/git-cache.js.map +0 -1
  195. package/dist/analyzers/git-history.d.ts +0 -113
  196. package/dist/analyzers/git-history.d.ts.map +0 -1
  197. package/dist/analyzers/git-history.js +0 -333
  198. package/dist/analyzers/git-history.js.map +0 -1
  199. package/dist/analyzers/index.d.ts +0 -10
  200. package/dist/analyzers/index.d.ts.map +0 -1
  201. package/dist/analyzers/index.js +0 -7
  202. package/dist/analyzers/index.js.map +0 -1
  203. package/dist/analyzers/temporal-scorer.d.ts +0 -72
  204. package/dist/analyzers/temporal-scorer.d.ts.map +0 -1
  205. package/dist/analyzers/temporal-scorer.js +0 -140
  206. package/dist/analyzers/temporal-scorer.js.map +0 -1
  207. package/dist/anti-patterns.d.ts +0 -24
  208. package/dist/anti-patterns.d.ts.map +0 -1
  209. package/dist/anti-patterns.js +0 -230
  210. package/dist/anti-patterns.js.map +0 -1
  211. package/dist/cli.d.ts.map +0 -1
  212. package/dist/cli.js.map +0 -1
  213. package/dist/config.d.ts +0 -12
  214. package/dist/config.d.ts.map +0 -1
  215. package/dist/config.js +0 -110
  216. package/dist/config.js.map +0 -1
  217. package/dist/diagram.d.ts +0 -9
  218. package/dist/diagram.d.ts.map +0 -1
  219. package/dist/diagram.js +0 -116
  220. package/dist/diagram.js.map +0 -1
  221. package/dist/html-reporter.d.ts +0 -47
  222. package/dist/html-reporter.d.ts.map +0 -1
  223. package/dist/html-reporter.js +0 -1747
  224. package/dist/html-reporter.js.map +0 -1
  225. package/dist/index.d.ts.map +0 -1
  226. package/dist/index.js.map +0 -1
  227. package/dist/project-summarizer.d.ts +0 -38
  228. package/dist/project-summarizer.d.ts.map +0 -1
  229. package/dist/project-summarizer.js +0 -463
  230. package/dist/project-summarizer.js.map +0 -1
  231. package/dist/refactor-engine.d.ts +0 -18
  232. package/dist/refactor-engine.d.ts.map +0 -1
  233. package/dist/refactor-engine.js +0 -86
  234. package/dist/refactor-engine.js.map +0 -1
  235. package/dist/refactor-reporter.d.ts.map +0 -1
  236. package/dist/refactor-reporter.js.map +0 -1
  237. package/dist/reporter.d.ts.map +0 -1
  238. package/dist/reporter.js.map +0 -1
  239. package/dist/rules/barrel-optimizer.d.ts +0 -13
  240. package/dist/rules/barrel-optimizer.d.ts.map +0 -1
  241. package/dist/rules/barrel-optimizer.js +0 -77
  242. package/dist/rules/barrel-optimizer.js.map +0 -1
  243. package/dist/rules/dead-code-detector.d.ts +0 -21
  244. package/dist/rules/dead-code-detector.d.ts.map +0 -1
  245. package/dist/rules/dead-code-detector.js +0 -117
  246. package/dist/rules/dead-code-detector.js.map +0 -1
  247. package/dist/rules/hub-splitter.d.ts +0 -13
  248. package/dist/rules/hub-splitter.d.ts.map +0 -1
  249. package/dist/rules/hub-splitter.js +0 -110
  250. package/dist/rules/hub-splitter.js.map +0 -1
  251. package/dist/rules/import-organizer.d.ts +0 -13
  252. package/dist/rules/import-organizer.d.ts.map +0 -1
  253. package/dist/rules/import-organizer.js +0 -85
  254. package/dist/rules/import-organizer.js.map +0 -1
  255. package/dist/rules/module-grouper.d.ts +0 -13
  256. package/dist/rules/module-grouper.d.ts.map +0 -1
  257. package/dist/rules/module-grouper.js +0 -110
  258. package/dist/rules/module-grouper.js.map +0 -1
  259. package/dist/scanner.d.ts +0 -31
  260. package/dist/scanner.d.ts.map +0 -1
  261. package/dist/scanner.js +0 -328
  262. package/dist/scanner.js.map +0 -1
  263. package/dist/scorer.d.ts +0 -27
  264. package/dist/scorer.d.ts.map +0 -1
  265. package/dist/scorer.js +0 -229
  266. package/dist/scorer.js.map +0 -1
  267. package/dist/types.d.ts +0 -186
  268. package/dist/types.d.ts.map +0 -1
  269. package/dist/types.js +0 -2
  270. package/dist/types.js.map +0 -1
  271. package/examples/sample-report.md +0 -207
  272. package/jest.config.js +0 -18
  273. package/src/agent-generator/context-enricher.ts +0 -672
  274. package/src/agent-generator/domain-inferrer.ts +0 -635
  275. package/src/agent-generator/framework-detector.ts +0 -669
  276. package/src/agent-generator/index.ts +0 -634
  277. package/src/agent-generator/stack-detector.ts +0 -115
  278. package/src/agent-generator/templates/core/agents.ts +0 -1296
  279. package/src/agent-generator/templates/core/architecture-rules.ts +0 -287
  280. package/src/agent-generator/templates/core/general-rules.ts +0 -306
  281. package/src/agent-generator/templates/core/hooks-generator.ts +0 -242
  282. package/src/agent-generator/templates/core/index-md.ts +0 -260
  283. package/src/agent-generator/templates/core/orchestrator.ts +0 -459
  284. package/src/agent-generator/templates/core/preflight.ts +0 -215
  285. package/src/agent-generator/templates/core/quality-gates.ts +0 -256
  286. package/src/agent-generator/templates/core/security-rules.ts +0 -543
  287. package/src/agent-generator/templates/core/skills-generator.ts +0 -585
  288. package/src/agent-generator/templates/core/workflow-fix-bug.ts +0 -239
  289. package/src/agent-generator/templates/core/workflow-new-feature.ts +0 -323
  290. package/src/agent-generator/templates/core/workflow-review.ts +0 -106
  291. package/src/agent-generator/templates/domain/index.ts +0 -1201
  292. package/src/agent-generator/templates/stack/index.ts +0 -705
  293. package/src/agent-generator/templates/template-helpers.ts +0 -776
  294. package/src/agent-generator/types.ts +0 -232
  295. package/src/analyzer.ts +0 -447
  296. package/src/analyzers/forecast.ts +0 -496
  297. package/src/analyzers/git-cache.ts +0 -52
  298. package/src/analyzers/git-history.ts +0 -488
  299. package/src/analyzers/index.ts +0 -33
  300. package/src/analyzers/temporal-scorer.ts +0 -227
  301. package/src/anti-patterns.ts +0 -287
  302. package/src/cli.ts +0 -517
  303. package/src/config.ts +0 -123
  304. package/src/diagram.ts +0 -144
  305. package/src/html-reporter.ts +0 -1830
  306. package/src/project-summarizer.ts +0 -521
  307. package/src/refactor-engine.ts +0 -117
  308. package/src/rules/barrel-optimizer.ts +0 -97
  309. package/src/rules/dead-code-detector.ts +0 -132
  310. package/src/rules/hub-splitter.ts +0 -123
  311. package/src/rules/import-organizer.ts +0 -98
  312. package/src/rules/module-grouper.ts +0 -124
  313. package/src/scanner.ts +0 -344
  314. package/src/scorer.ts +0 -254
  315. package/src/types.ts +0 -193
  316. package/tests/agent-generator.test.ts +0 -427
  317. package/tests/analyzers-integration.test.ts +0 -174
  318. package/tests/anti-patterns.test.ts +0 -94
  319. package/tests/context-enricher.test.ts +0 -971
  320. package/tests/fixtures/monorepo/package.json +0 -6
  321. package/tests/fixtures/monorepo/packages/app/package.json +0 -12
  322. package/tests/fixtures/monorepo/packages/app/src/index.ts +0 -6
  323. package/tests/fixtures/monorepo/packages/core/package.json +0 -7
  324. package/tests/fixtures/monorepo/packages/core/src/index.ts +0 -7
  325. package/tests/forecast.test.ts +0 -509
  326. package/tests/framework-detector.test.ts +0 -1172
  327. package/tests/git-history.test.ts +0 -254
  328. package/tests/monorepo-scan.test.ts +0 -170
  329. package/tests/scanner.test.ts +0 -54
  330. package/tests/scorer.test.ts +0 -674
  331. package/tests/stack-detector.test.ts +0 -241
  332. package/tests/template-generation.test.ts +0 -706
  333. package/tests/template-helpers.test.ts +0 -1152
  334. package/tests/temporal-scorer.test.ts +0 -307
  335. /package/dist/{reporter.js → src/adapters/reporter.js} +0 -0
@@ -1,1172 +0,0 @@
1
- import { FrameworkDetector } from '../src/agent-generator/framework-detector.js';
2
- import { AnalysisReport } from '../src/types.js';
3
- import { existsSync, mkdtempSync, writeFileSync, rmSync } from 'fs';
4
- import { join } from 'path';
5
-
6
- // ── Test Data Factories ──
7
-
8
- /**
9
- * Creates a mock AnalysisReport for testing purposes.
10
- */
11
- function makeReport(overrides: Partial<AnalysisReport> = {}): AnalysisReport {
12
- return {
13
- timestamp: new Date().toISOString(),
14
- projectInfo: {
15
- path: '/test',
16
- name: 'test-project',
17
- frameworks: [],
18
- totalFiles: 50,
19
- totalLines: 5000,
20
- primaryLanguages: ['Unknown'],
21
- },
22
- score: {
23
- overall: 72,
24
- components: [],
25
- breakdown: { modularity: 80, coupling: 65, cohesion: 70, layering: 75 },
26
- },
27
- antiPatterns: [],
28
- layers: [],
29
- dependencyGraph: {
30
- nodes: ['src/index.ts'],
31
- edges: [],
32
- },
33
- suggestions: [],
34
- diagram: { mermaid: '', type: 'layer' },
35
- ...overrides,
36
- };
37
- }
38
-
39
- // ── Test Suite ──
40
-
41
- describe('FrameworkDetector', () => {
42
- const detector = new FrameworkDetector();
43
- let tempDir: string;
44
-
45
- beforeEach(() => {
46
- tempDir = mkdtempSync(join(process.cwd(), '__test_framework__'));
47
- });
48
-
49
- afterEach(() => {
50
- if (existsSync(tempDir)) {
51
- rmSync(tempDir, { recursive: true, force: true });
52
- }
53
- });
54
-
55
- // ═══════════════════════════════════════════════════════════════════════
56
- // PYTHON DETECTION
57
- // ═══════════════════════════════════════════════════════════════════════
58
-
59
- describe('Python detection', () => {
60
- it('should detect FastAPI, Django, Flask from requirements.txt with versions', () => {
61
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] } });
62
- writeFileSync(join(tempDir, 'requirements.txt'), 'fastapi==0.109.0\ndjango>=4.0\nflask~=2.3.0\n');
63
-
64
- const result = detector.detect(tempDir, report);
65
-
66
- expect(result.frameworks.length).toBeGreaterThanOrEqual(3);
67
- expect(result.frameworks.map(f => f.name)).toContain('FastAPI');
68
- expect(result.frameworks.map(f => f.name)).toContain('Django');
69
- expect(result.frameworks.map(f => f.name)).toContain('Flask');
70
- expect(result.primaryFramework).toBeDefined();
71
- expect(result.primaryFramework?.category).toBe('web');
72
- });
73
-
74
- it('should detect dependencies from pyproject.toml PEP 621 format with [project] section', () => {
75
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] } });
76
- writeFileSync(
77
- join(tempDir, 'pyproject.toml'),
78
- `[project]
79
- dependencies = [
80
- "fastapi>=0.109.0",
81
- "sqlalchemy>=2.0.0"
82
- ]
83
- `,
84
- );
85
-
86
- const result = detector.detect(tempDir, report);
87
-
88
- expect(result.frameworks.map(f => f.name)).toContain('FastAPI');
89
- expect(result.frameworks.map(f => f.name)).toContain('SQLAlchemy');
90
- });
91
-
92
- it('should detect dependencies from pyproject.toml with [project.optional-dependencies]', () => {
93
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] } });
94
- writeFileSync(
95
- join(tempDir, 'pyproject.toml'),
96
- `[project]
97
- dependencies = ["fastapi>=0.109.0"]
98
-
99
- [project.optional-dependencies]
100
- dev = ["pytest>=7.0", "ruff>=0.1.0"]
101
- test = ["hypothesis>=6.0"]
102
- `,
103
- );
104
-
105
- const result = detector.detect(tempDir, report);
106
-
107
- expect(result.frameworks.map(f => f.name)).toContain('FastAPI');
108
- expect(result.frameworks.map(f => f.name)).toContain('pytest');
109
- expect(result.frameworks.map(f => f.name)).toContain('Ruff');
110
- expect(result.frameworks.map(f => f.name)).toContain('Hypothesis');
111
- });
112
-
113
- it('should detect dependencies from pyproject.toml with [tool.poetry.dependencies]', () => {
114
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] } });
115
- writeFileSync(
116
- join(tempDir, 'pyproject.toml'),
117
- `[tool.poetry.dependencies]
118
- python = "^3.11"
119
- django = "~4.2.0"
120
- sqlalchemy = "^2.0"
121
- `,
122
- );
123
-
124
- const result = detector.detect(tempDir, report);
125
-
126
- expect(result.frameworks.map(f => f.name)).toContain('Django');
127
- expect(result.frameworks.map(f => f.name)).toContain('SQLAlchemy');
128
- });
129
-
130
- it('should detect setup.py as fallback Python dependencies', () => {
131
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] } });
132
- // parsePythonRequirements parses line-by-line, so setup.py needs one dep per line
133
- writeFileSync(
134
- join(tempDir, 'setup.py'),
135
- `flask>=2.0
136
- sqlalchemy>=2.0
137
- `,
138
- );
139
-
140
- const result = detector.detect(tempDir, report);
141
-
142
- expect(result.frameworks.map(f => f.name)).toContain('Flask');
143
- expect(result.frameworks.map(f => f.name)).toContain('SQLAlchemy');
144
- });
145
-
146
- it('should detect Pipfile as fallback Python dependencies', () => {
147
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] } });
148
- writeFileSync(
149
- join(tempDir, 'Pipfile'),
150
- `[packages]
151
- tornado = "*"
152
- peewee = ">=3.0"
153
-
154
- [dev-packages]
155
- pytest = "*"
156
- `,
157
- );
158
-
159
- const result = detector.detect(tempDir, report);
160
-
161
- expect(result.frameworks.map(f => f.name)).toContain('Tornado');
162
- expect(result.frameworks.map(f => f.name)).toContain('Peewee');
163
- expect(result.frameworks.map(f => f.name)).toContain('pytest');
164
- });
165
- });
166
-
167
- // ═══════════════════════════════════════════════════════════════════════
168
- // NODE.JS DETECTION
169
- // ═══════════════════════════════════════════════════════════════════════
170
-
171
- describe('Node.js detection', () => {
172
- it('should detect Express, NestJS, Jest, ESLint from package.json', () => {
173
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] } });
174
- writeFileSync(
175
- join(tempDir, 'package.json'),
176
- JSON.stringify({
177
- name: 'test-app',
178
- dependencies: {
179
- express: '^4.18.0',
180
- '@nestjs/core': '^10.0.0',
181
- },
182
- devDependencies: {
183
- jest: '^29.0.0',
184
- eslint: '^8.0.0',
185
- },
186
- }),
187
- );
188
-
189
- const result = detector.detect(tempDir, report);
190
-
191
- expect(result.frameworks.map(f => f.name)).toContain('Express');
192
- expect(result.frameworks.map(f => f.name)).toContain('NestJS');
193
- expect(result.frameworks.map(f => f.name)).toContain('Jest');
194
- expect(result.frameworks.map(f => f.name)).toContain('ESLint');
195
- });
196
-
197
- it('should handle invalid JSON gracefully', () => {
198
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] } });
199
- writeFileSync(join(tempDir, 'package.json'), 'invalid json {]');
200
-
201
- const result = detector.detect(tempDir, report);
202
-
203
- expect(result.frameworks.length).toBe(0);
204
- expect(result.primaryFramework).toBeNull();
205
- });
206
- });
207
-
208
- // ═══════════════════════════════════════════════════════════════════════
209
- // JAVA DETECTION
210
- // ═══════════════════════════════════════════════════════════════════════
211
-
212
- describe('Java detection', () => {
213
- it('should detect Spring Boot from pom.xml with spring-boot-starter-web artifactId', () => {
214
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Java'] } });
215
- writeFileSync(
216
- join(tempDir, 'pom.xml'),
217
- `<project>
218
- <dependencies>
219
- <dependency>
220
- <artifactId>spring-boot-starter-web</artifactId>
221
- </dependency>
222
- </dependencies>
223
- </project>`,
224
- );
225
-
226
- const result = detector.detect(tempDir, report);
227
-
228
- expect(result.frameworks.map(f => f.name)).toContain('Spring Boot');
229
- });
230
-
231
- it('should detect Spring Boot, Quarkus, Micronaut, Ktor from build.gradle', () => {
232
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Java'] } });
233
- writeFileSync(
234
- join(tempDir, 'build.gradle'),
235
- `plugins {
236
- id 'java'
237
- }
238
-
239
- dependencies {
240
- implementation 'io.quarkus:quarkus-core'
241
- }`,
242
- );
243
-
244
- const result = detector.detect(tempDir, report);
245
-
246
- expect(result.frameworks.map(f => f.name)).toContain('Quarkus');
247
- });
248
-
249
- it('should detect frameworks from build.gradle.kts', () => {
250
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Kotlin'] } });
251
- writeFileSync(
252
- join(tempDir, 'build.gradle.kts'),
253
- `dependencies {
254
- implementation("io.micronaut:micronaut-core")
255
- implementation("io.ktor:ktor-server-core")
256
- }`,
257
- );
258
-
259
- const result = detector.detect(tempDir, report);
260
-
261
- expect(result.frameworks.map(f => f.name)).toContain('Micronaut');
262
- expect(result.frameworks.map(f => f.name)).toContain('Ktor');
263
- });
264
- });
265
-
266
- // ═══════════════════════════════════════════════════════════════════════
267
- // PHP DETECTION
268
- // ═══════════════════════════════════════════════════════════════════════
269
-
270
- describe('PHP detection', () => {
271
- it('should detect Laravel from composer.json', () => {
272
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['PHP'] } });
273
- writeFileSync(
274
- join(tempDir, 'composer.json'),
275
- JSON.stringify({
276
- name: 'myapp',
277
- require: {
278
- 'laravel/framework': '^10.0',
279
- php: '^8.1',
280
- },
281
- }),
282
- );
283
-
284
- const result = detector.detect(tempDir, report);
285
-
286
- expect(result.frameworks.map(f => f.name)).toContain('Laravel');
287
- });
288
- });
289
-
290
- // ═══════════════════════════════════════════════════════════════════════
291
- // GO DETECTION
292
- // ═══════════════════════════════════════════════════════════════════════
293
-
294
- describe('Go detection', () => {
295
- it('should detect Gin from go.mod', () => {
296
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Go'] } });
297
- writeFileSync(
298
- join(tempDir, 'go.mod'),
299
- `module myapp
300
-
301
- go 1.21
302
-
303
- require (
304
- github.com/gin-gonic/gin v1.9.0
305
- github.com/labstack/echo v3.3.0
306
- )`,
307
- );
308
-
309
- const result = detector.detect(tempDir, report);
310
-
311
- expect(result.frameworks.map(f => f.name)).toContain('Gin');
312
- expect(result.frameworks.map(f => f.name)).toContain('Echo');
313
- });
314
- });
315
-
316
- // ═══════════════════════════════════════════════════════════════════════
317
- // RUBY DETECTION
318
- // ═══════════════════════════════════════════════════════════════════════
319
-
320
- describe('Ruby detection', () => {
321
- it('should detect Rails, Sinatra, RSpec from Gemfile', () => {
322
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Ruby'] } });
323
- writeFileSync(
324
- join(tempDir, 'Gemfile'),
325
- `source "https://rubygems.org"
326
-
327
- gem "rails", "~> 7.0.0"
328
- gem "sinatra"
329
- gem "rspec"`,
330
- );
331
-
332
- const result = detector.detect(tempDir, report);
333
-
334
- expect(result.frameworks.map(f => f.name)).toContain('Ruby on Rails');
335
- expect(result.frameworks.map(f => f.name)).toContain('Sinatra');
336
- expect(result.frameworks.map(f => f.name)).toContain('RSpec');
337
- });
338
- });
339
-
340
- // ═══════════════════════════════════════════════════════════════════════
341
- // DART DETECTION
342
- // ═══════════════════════════════════════════════════════════════════════
343
-
344
- describe('Dart detection', () => {
345
- it('should detect Flutter from pubspec.yaml with flutter: section', () => {
346
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Dart'] } });
347
- writeFileSync(
348
- join(tempDir, 'pubspec.yaml'),
349
- `name: myapp
350
- description: A Flutter app
351
-
352
- flutter:
353
- uses-material-design: true
354
-
355
- dev_dependencies:
356
- flutter_test:
357
- sdk: flutter`,
358
- );
359
-
360
- const result = detector.detect(tempDir, report);
361
-
362
- expect(result.frameworks.map(f => f.name)).toContain('Flutter');
363
- });
364
- });
365
-
366
- // ═══════════════════════════════════════════════════════════════════════
367
- // RUST DETECTION
368
- // ═══════════════════════════════════════════════════════════════════════
369
-
370
- describe('Rust detection', () => {
371
- it('should detect Actix Web and Axum from Cargo.toml', () => {
372
- const report = makeReport({ projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Rust'] } });
373
- writeFileSync(
374
- join(tempDir, 'Cargo.toml'),
375
- `[package]
376
- name = "myapp"
377
- version = "0.1.0"
378
-
379
- [dependencies]
380
- actix-web = "4.4"
381
- axum = "0.7"`,
382
- );
383
-
384
- const result = detector.detect(tempDir, report);
385
-
386
- expect(result.frameworks.map(f => f.name)).toContain('Actix Web');
387
- expect(result.frameworks.map(f => f.name)).toContain('Axum');
388
- });
389
- });
390
-
391
- // ═══════════════════════════════════════════════════════════════════════
392
- // TOOLCHAIN DETECTION
393
- // ═══════════════════════════════════════════════════════════════════════
394
-
395
- describe('Toolchain detection', () => {
396
- it('should detect Python + FastAPI toolchain with uvicorn, pytest, ruff', () => {
397
- const report = makeReport({
398
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] },
399
- dependencyGraph: { nodes: ['src/main.py'], edges: [] },
400
- });
401
- writeFileSync(
402
- join(tempDir, 'pyproject.toml'),
403
- `[project]
404
- dependencies = [
405
- "fastapi>=0.109.0",
406
- "uvicorn[standard]>=0.27.0"
407
- ]
408
-
409
- [project.optional-dependencies]
410
- dev = ["pytest>=7.0", "ruff>=0.1.0"]`,
411
- );
412
-
413
- const result = detector.detect(tempDir, report);
414
-
415
- expect(result.toolchain.runCmd).toContain('uvicorn');
416
- expect(result.toolchain.testCmd).toBe('pytest');
417
- expect(result.toolchain.lintCmd).toContain('ruff');
418
- // depsFile defaults to requirements.txt unless poetry.lock or Pipfile.lock exists
419
- expect(result.toolchain.depsFile).toBe('requirements.txt');
420
- });
421
-
422
- it('should detect Python + Django toolchain with manage.py commands', () => {
423
- const report = makeReport({
424
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] },
425
- dependencyGraph: { nodes: ['manage.py', 'app/models.py'], edges: [] },
426
- });
427
- writeFileSync(
428
- join(tempDir, 'requirements.txt'),
429
- `django>=4.0
430
- pytest>=7.0`,
431
- );
432
- writeFileSync(join(tempDir, 'manage.py'), 'import django\n');
433
-
434
- const result = detector.detect(tempDir, report);
435
-
436
- expect(result.toolchain.runCmd).toBe('python manage.py runserver');
437
- expect(result.toolchain.testCmd).toBe('pytest');
438
- expect(result.toolchain.migrateCmd).toBe('python manage.py migrate');
439
- });
440
-
441
- it('should detect TypeScript with npm by default', () => {
442
- const report = makeReport({
443
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] },
444
- dependencyGraph: { nodes: ['src/index.ts'], edges: [] },
445
- });
446
- writeFileSync(
447
- join(tempDir, 'package.json'),
448
- JSON.stringify({
449
- name: 'test',
450
- dependencies: { express: '^4.18.0' },
451
- devDependencies: { jest: '^29.0.0' },
452
- }),
453
- );
454
-
455
- const result = detector.detect(tempDir, report);
456
-
457
- expect(result.toolchain.buildCmd).toContain('npm');
458
- expect(result.toolchain.testCmd).toContain('npm');
459
- expect(result.toolchain.runCmd).toContain('npm');
460
- });
461
-
462
- it('should detect TypeScript with yarn when yarn.lock exists', () => {
463
- const report = makeReport({
464
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] },
465
- dependencyGraph: { nodes: ['src/index.ts'], edges: [] },
466
- });
467
- writeFileSync(
468
- join(tempDir, 'package.json'),
469
- JSON.stringify({
470
- name: 'test',
471
- dependencies: { express: '^4.18.0' },
472
- }),
473
- );
474
- writeFileSync(join(tempDir, 'yarn.lock'), '# yarn lockfile v1\n');
475
-
476
- const result = detector.detect(tempDir, report);
477
-
478
- expect(result.toolchain.buildCmd).toContain('yarn');
479
- expect(result.toolchain.testCmd).toContain('yarn');
480
- });
481
-
482
- it('should detect TypeScript with pnpm when pnpm-lock.yaml exists', () => {
483
- const report = makeReport({
484
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] },
485
- dependencyGraph: { nodes: ['src/index.ts'], edges: [] },
486
- });
487
- writeFileSync(
488
- join(tempDir, 'package.json'),
489
- JSON.stringify({
490
- name: 'test',
491
- dependencies: { express: '^4.18.0' },
492
- }),
493
- );
494
- writeFileSync(join(tempDir, 'pnpm-lock.yaml'), 'lockfileVersion: 5.4\n');
495
-
496
- const result = detector.detect(tempDir, report);
497
-
498
- expect(result.toolchain.buildCmd).toContain('pnpm');
499
- expect(result.toolchain.testCmd).toContain('pnpm');
500
- });
501
-
502
- it('should detect Java + pom.xml toolchain with mvn commands', () => {
503
- const report = makeReport({
504
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Java'] },
505
- dependencyGraph: { nodes: ['src/main/java/App.java'], edges: [] },
506
- });
507
- writeFileSync(
508
- join(tempDir, 'pom.xml'),
509
- `<project>
510
- <dependencies>
511
- <dependency>
512
- <artifactId>spring-boot-starter-web</artifactId>
513
- </dependency>
514
- </dependencies>
515
- </project>`,
516
- );
517
-
518
- const result = detector.detect(tempDir, report);
519
-
520
- expect(result.toolchain.buildCmd).toBe('mvn clean package');
521
- expect(result.toolchain.testCmd).toBe('mvn test');
522
- expect(result.toolchain.runCmd).toBe('mvn spring-boot:run');
523
- expect(result.toolchain.depsFile).toBe('pom.xml');
524
- });
525
-
526
- it('should detect Java + build.gradle toolchain with gradlew commands', () => {
527
- const report = makeReport({
528
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Java'] },
529
- dependencyGraph: { nodes: ['src/main/java/App.java'], edges: [] },
530
- });
531
- writeFileSync(
532
- join(tempDir, 'build.gradle'),
533
- `plugins { id 'java' }
534
- dependencies { implementation 'org.springframework.boot:spring-boot' }`,
535
- );
536
-
537
- const result = detector.detect(tempDir, report);
538
-
539
- expect(result.toolchain.buildCmd).toBe('./gradlew build');
540
- expect(result.toolchain.testCmd).toBe('./gradlew test');
541
- expect(result.toolchain.runCmd).toBe('./gradlew bootRun');
542
- expect(result.toolchain.depsFile).toBe('build.gradle');
543
- });
544
-
545
- it('should detect PHP + Laravel toolchain with artisan commands', () => {
546
- const report = makeReport({
547
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['PHP'] },
548
- dependencyGraph: { nodes: ['app/Http/Controllers/AppController.php'], edges: [] },
549
- });
550
- writeFileSync(
551
- join(tempDir, 'composer.json'),
552
- JSON.stringify({
553
- require: { 'laravel/framework': '^10.0' },
554
- }),
555
- );
556
-
557
- const result = detector.detect(tempDir, report);
558
-
559
- expect(result.toolchain.runCmd).toBe('php artisan serve');
560
- expect(result.toolchain.testCmd).toBe('php artisan test');
561
- expect(result.toolchain.migrateCmd).toBe('php artisan migrate');
562
- });
563
-
564
- it('should detect Go toolchain with go commands', () => {
565
- const report = makeReport({
566
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Go'] },
567
- dependencyGraph: { nodes: ['main.go'], edges: [] },
568
- });
569
- writeFileSync(
570
- join(tempDir, 'go.mod'),
571
- `module myapp
572
- go 1.21
573
- require github.com/gin-gonic/gin v1.9.0`,
574
- );
575
-
576
- const result = detector.detect(tempDir, report);
577
-
578
- expect(result.toolchain.buildCmd).toBe('go build ./...');
579
- expect(result.toolchain.testCmd).toBe('go test ./...');
580
- expect(result.toolchain.lintCmd).toBe('golangci-lint run');
581
- expect(result.toolchain.runCmd).toBe('go run .');
582
- expect(result.toolchain.depsFile).toBe('go.mod');
583
- });
584
-
585
- it('should detect Ruby + Rails toolchain with rails commands', () => {
586
- const report = makeReport({
587
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Ruby'] },
588
- dependencyGraph: { nodes: ['app/controllers/application_controller.rb'], edges: [] },
589
- });
590
- writeFileSync(
591
- join(tempDir, 'Gemfile'),
592
- `source "https://rubygems.org"
593
- gem "rails", "~> 7.0.0"
594
- gem "rspec"`,
595
- );
596
-
597
- const result = detector.detect(tempDir, report);
598
-
599
- expect(result.toolchain.runCmd).toBe('rails server');
600
- expect(result.toolchain.testCmd).toBe('bundle exec rspec');
601
- expect(result.toolchain.migrateCmd).toBe('rails db:migrate');
602
- expect(result.toolchain.depsFile).toBe('Gemfile');
603
- });
604
-
605
- it('should detect Dart + Flutter toolchain with flutter commands', () => {
606
- const report = makeReport({
607
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Dart'] },
608
- dependencyGraph: { nodes: ['lib/main.dart'], edges: [] },
609
- });
610
- writeFileSync(
611
- join(tempDir, 'pubspec.yaml'),
612
- `name: myapp
613
- flutter:
614
- uses-material-design: true`,
615
- );
616
-
617
- const result = detector.detect(tempDir, report);
618
-
619
- expect(result.toolchain.buildCmd).toBe('flutter build');
620
- expect(result.toolchain.testCmd).toBe('flutter test');
621
- expect(result.toolchain.runCmd).toBe('flutter run');
622
- expect(result.toolchain.depsFile).toBe('pubspec.yaml');
623
- });
624
-
625
- it('should detect Rust toolchain with cargo commands', () => {
626
- const report = makeReport({
627
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Rust'] },
628
- dependencyGraph: { nodes: ['src/main.rs'], edges: [] },
629
- });
630
- writeFileSync(
631
- join(tempDir, 'Cargo.toml'),
632
- `[package]
633
- name = "myapp"
634
- [dependencies]
635
- actix-web = "4.4"`,
636
- );
637
-
638
- const result = detector.detect(tempDir, report);
639
-
640
- expect(result.toolchain.buildCmd).toBe('cargo build');
641
- expect(result.toolchain.testCmd).toBe('cargo test');
642
- expect(result.toolchain.lintCmd).toBe('cargo clippy');
643
- expect(result.toolchain.runCmd).toBe('cargo run');
644
- expect(result.toolchain.depsFile).toBe('Cargo.toml');
645
- });
646
-
647
- it('should fallback to Makefile commands when available', () => {
648
- const report = makeReport({
649
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Unknown'] },
650
- dependencyGraph: { nodes: [], edges: [] },
651
- });
652
- writeFileSync(
653
- join(tempDir, 'Makefile'),
654
- `build:
655
- @echo "Building..."
656
-
657
- test:
658
- @echo "Testing..."
659
-
660
- lint:
661
- @echo "Linting..."
662
-
663
- run:
664
- @echo "Running..."`,
665
- );
666
-
667
- const result = detector.detect(tempDir, report);
668
-
669
- expect(result.toolchain.buildCmd).toBe('make build');
670
- expect(result.toolchain.testCmd).toBe('make test');
671
- expect(result.toolchain.lintCmd).toBe('make lint');
672
- expect(result.toolchain.runCmd).toBe('make run');
673
- });
674
-
675
- it('should fallback to generic commands when no build tool detected', () => {
676
- const report = makeReport({
677
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Unknown'] },
678
- dependencyGraph: { nodes: [], edges: [] },
679
- });
680
-
681
- const result = detector.detect(tempDir, report);
682
-
683
- expect(result.toolchain.buildCmd).toContain('No build command detected');
684
- expect(result.toolchain.testCmd).toContain('No test command detected');
685
- expect(result.toolchain.depsFile).toBe('unknown');
686
- });
687
- });
688
-
689
- // ═══════════════════════════════════════════════════════════════════════
690
- // PROJECT STRUCTURE DETECTION
691
- // ═══════════════════════════════════════════════════════════════════════
692
-
693
- describe('Project structure detection', () => {
694
- it('should detect clean-architecture with domain/ and infrastructure/ directories', () => {
695
- const report = makeReport({
696
- dependencyGraph: {
697
- nodes: [
698
- 'src/domain/entities/user.ts',
699
- 'src/domain/repositories/user.repository.ts',
700
- 'src/infrastructure/database/connection.ts',
701
- 'src/application/services/user.service.ts',
702
- 'src/presentation/controllers/user.controller.ts',
703
- ],
704
- edges: [],
705
- },
706
- });
707
-
708
- const result = detector.detect(tempDir, report);
709
-
710
- expect(result.projectStructure).toBe('clean-architecture');
711
- });
712
-
713
- it('should detect mvc with models/, views/, controllers/ directories', () => {
714
- const report = makeReport({
715
- dependencyGraph: {
716
- nodes: [
717
- 'app/models/user.php',
718
- 'app/views/user/show.html',
719
- 'app/controllers/user_controller.php',
720
- ],
721
- edges: [],
722
- },
723
- });
724
-
725
- const result = detector.detect(tempDir, report);
726
-
727
- expect(result.projectStructure).toBe('mvc');
728
- });
729
-
730
- it('should detect modular with modules/ or features/ directories', () => {
731
- const report = makeReport({
732
- dependencyGraph: {
733
- nodes: [
734
- 'src/modules/auth/auth.module.ts',
735
- 'src/modules/users/users.module.ts',
736
- 'src/modules/auth/controllers/auth.controller.ts',
737
- ],
738
- edges: [],
739
- },
740
- });
741
-
742
- const result = detector.detect(tempDir, report);
743
-
744
- expect(result.projectStructure).toBe('modular');
745
- });
746
-
747
- it('should detect modular with features directory', () => {
748
- const report = makeReport({
749
- dependencyGraph: {
750
- nodes: [
751
- 'src/features/auth/auth.ts',
752
- 'src/features/users/users.ts',
753
- ],
754
- edges: [],
755
- },
756
- });
757
-
758
- const result = detector.detect(tempDir, report);
759
-
760
- expect(result.projectStructure).toBe('modular');
761
- });
762
-
763
- it('should detect monorepo with packages/ or apps/ directories', () => {
764
- const report = makeReport({
765
- dependencyGraph: {
766
- nodes: [
767
- 'src/packages/api/src/index.ts',
768
- 'src/packages/web/src/index.ts',
769
- 'src/packages/shared/src/types.ts',
770
- ],
771
- edges: [],
772
- },
773
- });
774
-
775
- const result = detector.detect(tempDir, report);
776
-
777
- expect(result.projectStructure).toBe('monorepo');
778
- });
779
-
780
- it('should detect monorepo with apps directory', () => {
781
- const report = makeReport({
782
- dependencyGraph: {
783
- nodes: [
784
- 'src/apps/api/src/index.ts',
785
- 'src/apps/web/src/index.ts',
786
- ],
787
- edges: [],
788
- },
789
- });
790
-
791
- const result = detector.detect(tempDir, report);
792
-
793
- expect(result.projectStructure).toBe('monorepo');
794
- });
795
-
796
- it('should detect flat structure with shallow directory depth', () => {
797
- const report = makeReport({
798
- dependencyGraph: {
799
- nodes: [
800
- 'src/index.ts',
801
- 'src/util.ts',
802
- 'src/config.ts',
803
- ],
804
- edges: [],
805
- },
806
- });
807
-
808
- const result = detector.detect(tempDir, report);
809
-
810
- expect(result.projectStructure).toBe('flat');
811
- });
812
-
813
- it('should return unknown for unmatched structures', () => {
814
- const report = makeReport({
815
- dependencyGraph: {
816
- nodes: [
817
- 'src/a/b/c/d/e/f/component.ts',
818
- 'src/x/y/z/util.ts',
819
- ],
820
- edges: [],
821
- },
822
- });
823
-
824
- const result = detector.detect(tempDir, report);
825
-
826
- expect(result.projectStructure).toBe('unknown');
827
- });
828
- });
829
-
830
- // ═══════════════════════════════════════════════════════════════════════
831
- // EDGE CASES
832
- // ═══════════════════════════════════════════════════════════════════════
833
-
834
- describe('Edge cases', () => {
835
- it('should handle empty project with no dependency files', () => {
836
- const report = makeReport();
837
-
838
- const result = detector.detect(tempDir, report);
839
-
840
- expect(result.frameworks.length).toBe(0);
841
- expect(result.primaryFramework).toBeNull();
842
- expect(result.toolchain).toBeDefined();
843
- });
844
-
845
- it('should deduplicate frameworks detected from multiple sources', () => {
846
- const report = makeReport({
847
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] },
848
- dependencyGraph: { nodes: ['package.json', 'yarn.lock'], edges: [] },
849
- });
850
- writeFileSync(
851
- join(tempDir, 'package.json'),
852
- JSON.stringify({
853
- name: 'test',
854
- dependencies: { jest: '^29.0.0', express: '^4.18.0' },
855
- devDependencies: { jest: '^29.0.0' }, // Jest in both deps and devDeps
856
- }),
857
- );
858
-
859
- const result = detector.detect(tempDir, report);
860
-
861
- const jestCount = result.frameworks.filter(f => f.name === 'Jest').length;
862
- expect(jestCount).toBe(1); // Should be deduplicated
863
- });
864
-
865
- it('should pick web framework as primaryFramework', () => {
866
- const report = makeReport({
867
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] },
868
- dependencyGraph: { nodes: [], edges: [] },
869
- });
870
- writeFileSync(
871
- join(tempDir, 'package.json'),
872
- JSON.stringify({
873
- name: 'test',
874
- dependencies: {
875
- express: '^4.18.0',
876
- jest: '^29.0.0',
877
- eslint: '^8.0.0',
878
- },
879
- }),
880
- );
881
-
882
- const result = detector.detect(tempDir, report);
883
-
884
- expect(result.primaryFramework?.name).toBe('Express');
885
- expect(result.primaryFramework?.category).toBe('web');
886
- });
887
-
888
- it('should sort frameworks: web first, then by confidence', () => {
889
- const report = makeReport({
890
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] },
891
- dependencyGraph: { nodes: [], edges: [] },
892
- });
893
- writeFileSync(
894
- join(tempDir, 'requirements.txt'),
895
- `pytest>=7.0
896
- fastapi>=0.100.0
897
- flask>=2.0
898
- black>=23.0`,
899
- );
900
-
901
- const result = detector.detect(tempDir, report);
902
-
903
- const frameworks = result.frameworks;
904
- // Web frameworks (FastAPI, Flask) should come before test/lint (pytest, Black)
905
- const webFrameworksIndices = frameworks
906
- .map((f, i) => (f.category === 'web' ? i : -1))
907
- .filter(i => i !== -1);
908
- const nonWebIndices = frameworks
909
- .map((f, i) => (f.category !== 'web' ? i : -1))
910
- .filter(i => i !== -1);
911
-
912
- if (webFrameworksIndices.length > 0 && nonWebIndices.length > 0) {
913
- expect(Math.max(...webFrameworksIndices)).toBeLessThan(Math.min(...nonWebIndices));
914
- }
915
- });
916
-
917
- it('should handle missing primaryLanguages gracefully', () => {
918
- const report = makeReport({
919
- projectInfo: {
920
- ...makeReport().projectInfo,
921
- primaryLanguages: [],
922
- },
923
- dependencyGraph: { nodes: [], edges: [] },
924
- });
925
-
926
- const result = detector.detect(tempDir, report);
927
-
928
- expect(result.toolchain).toBeDefined();
929
- });
930
-
931
- it('should handle files in subdirectories (e.g., requirements/prod.txt)', () => {
932
- const report = makeReport({
933
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] },
934
- dependencyGraph: { nodes: [], edges: [] },
935
- });
936
-
937
- // Create subdirectory
938
- const reqDir = join(tempDir, 'requirements');
939
- writeFileSync(
940
- join(tempDir, 'requirements'),
941
- 'placeholder',
942
- );
943
- rmSync(join(tempDir, 'requirements'), { force: true });
944
-
945
- // Instead, just test that reading non-existent file doesn't crash
946
- const result = detector.detect(tempDir, report);
947
-
948
- expect(result).toBeDefined();
949
- });
950
-
951
- it('should detect Vitest over Jest when both are present', () => {
952
- const report = makeReport({
953
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['TypeScript'] },
954
- dependencyGraph: { nodes: [], edges: [] },
955
- });
956
- writeFileSync(
957
- join(tempDir, 'package.json'),
958
- JSON.stringify({
959
- name: 'test',
960
- devDependencies: {
961
- jest: '^29.0.0',
962
- vitest: '^1.0.0',
963
- },
964
- }),
965
- );
966
-
967
- const result = detector.detect(tempDir, report);
968
-
969
- const frameworks = result.frameworks.map(f => f.name);
970
- expect(frameworks).toContain('Jest');
971
- expect(frameworks).toContain('Vitest');
972
- });
973
-
974
- it('should handle poetry.lock indicator for Poetry package manager', () => {
975
- const report = makeReport({
976
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] },
977
- dependencyGraph: { nodes: [], edges: [] },
978
- });
979
- writeFileSync(
980
- join(tempDir, 'pyproject.toml'),
981
- `[project]
982
- dependencies = ["fastapi>=0.100.0"]`,
983
- );
984
- writeFileSync(join(tempDir, 'poetry.lock'), '# poetry lock file\n');
985
-
986
- const result = detector.detect(tempDir, report);
987
-
988
- expect(result.toolchain.installCmd).toBe('poetry install');
989
- expect(result.toolchain.depsFile).toBe('pyproject.toml');
990
- });
991
-
992
- it('should handle Pipenv Pipfile.lock indicator', () => {
993
- const report = makeReport({
994
- projectInfo: { ...makeReport().projectInfo, primaryLanguages: ['Python'] },
995
- dependencyGraph: { nodes: [], edges: [] },
996
- });
997
- writeFileSync(
998
- join(tempDir, 'requirements.txt'),
999
- 'flask>=2.0',
1000
- );
1001
- writeFileSync(join(tempDir, 'Pipfile.lock'), '{}');
1002
-
1003
- const result = detector.detect(tempDir, report);
1004
-
1005
- expect(result.toolchain.installCmd).toBe('pipenv install');
1006
- });
1007
- });
1008
-
1009
- // ═══════════════════════════════════════════════════════════════════════
1010
- // INTEGRATION TESTS
1011
- // ═══════════════════════════════════════════════════════════════════════
1012
-
1013
- describe('Integration tests', () => {
1014
- it('should detect full Python FastAPI stack with clean-architecture structure', () => {
1015
- const report = makeReport({
1016
- projectInfo: {
1017
- ...makeReport().projectInfo,
1018
- primaryLanguages: ['Python'],
1019
- name: 'fastapi-app',
1020
- },
1021
- dependencyGraph: {
1022
- nodes: [
1023
- 'src/domain/entities/user.py',
1024
- 'src/infrastructure/database.py',
1025
- 'src/application/services/user.py',
1026
- 'src/main.py',
1027
- ],
1028
- edges: [],
1029
- },
1030
- });
1031
- writeFileSync(
1032
- join(tempDir, 'pyproject.toml'),
1033
- `[project]
1034
- dependencies = [
1035
- "fastapi>=0.109.0",
1036
- "sqlalchemy>=2.0",
1037
- "uvicorn[standard]>=0.27"
1038
- ]
1039
-
1040
- [project.optional-dependencies]
1041
- dev = ["pytest>=7.0", "ruff>=0.1.0"]`,
1042
- );
1043
-
1044
- const result = detector.detect(tempDir, report);
1045
-
1046
- expect(result.frameworks.map(f => f.name)).toContain('FastAPI');
1047
- expect(result.frameworks.map(f => f.name)).toContain('SQLAlchemy');
1048
- expect(result.primaryFramework?.name).toBe('FastAPI');
1049
- expect(result.toolchain.runCmd).toContain('uvicorn');
1050
- expect(result.toolchain.testCmd).toBe('pytest');
1051
- expect(result.projectStructure).toBe('clean-architecture');
1052
- });
1053
-
1054
- it('should detect full TypeScript NestJS stack with modular structure', () => {
1055
- const report = makeReport({
1056
- projectInfo: {
1057
- ...makeReport().projectInfo,
1058
- primaryLanguages: ['TypeScript'],
1059
- name: 'nestjs-app',
1060
- },
1061
- dependencyGraph: {
1062
- nodes: [
1063
- 'src/modules/auth/auth.module.ts',
1064
- 'src/modules/users/users.module.ts',
1065
- 'src/modules/auth/services/auth.service.ts',
1066
- 'src/app.module.ts',
1067
- 'src/main.ts',
1068
- ],
1069
- edges: [],
1070
- },
1071
- });
1072
- writeFileSync(
1073
- join(tempDir, 'package.json'),
1074
- JSON.stringify({
1075
- name: 'nestjs-app',
1076
- dependencies: {
1077
- '@nestjs/core': '^10.0.0',
1078
- '@nestjs/common': '^10.0.0',
1079
- typeorm: '^0.3.0',
1080
- },
1081
- devDependencies: {
1082
- jest: '^29.0.0',
1083
- eslint: '^8.0.0',
1084
- },
1085
- }),
1086
- );
1087
- writeFileSync(
1088
- join(tempDir, 'yarn.lock'),
1089
- '# yarn lockfile v1\n',
1090
- );
1091
-
1092
- const result = detector.detect(tempDir, report);
1093
-
1094
- expect(result.primaryFramework?.name).toBe('NestJS');
1095
- expect(result.frameworks.map(f => f.name)).toContain('TypeORM');
1096
- expect(result.toolchain.buildCmd).toContain('yarn');
1097
- expect(result.projectStructure).toBe('modular');
1098
- });
1099
-
1100
- it('should detect full Java Spring Boot stack', () => {
1101
- const report = makeReport({
1102
- projectInfo: {
1103
- ...makeReport().projectInfo,
1104
- primaryLanguages: ['Java'],
1105
- name: 'spring-app',
1106
- },
1107
- dependencyGraph: {
1108
- nodes: [
1109
- 'src/main/java/com/example/controller/UserController.java',
1110
- 'src/main/java/com/example/service/UserService.java',
1111
- 'src/main/java/com/example/repository/UserRepository.java',
1112
- 'pom.xml',
1113
- ],
1114
- edges: [],
1115
- },
1116
- });
1117
- writeFileSync(
1118
- join(tempDir, 'pom.xml'),
1119
- `<project>
1120
- <dependencies>
1121
- <dependency>
1122
- <artifactId>spring-boot-starter-web</artifactId>
1123
- </dependency>
1124
- <dependency>
1125
- <artifactId>spring-boot-starter-data-jpa</artifactId>
1126
- </dependency>
1127
- </dependencies>
1128
- </project>`,
1129
- );
1130
-
1131
- const result = detector.detect(tempDir, report);
1132
-
1133
- expect(result.primaryFramework?.name).toBe('Spring Boot');
1134
- expect(result.toolchain.buildCmd).toBe('mvn clean package');
1135
- expect(result.toolchain.runCmd).toBe('mvn spring-boot:run');
1136
- expect(result.projectStructure).not.toBe('clean-architecture');
1137
- });
1138
-
1139
- it('should detect full PHP Laravel stack with mvc structure', () => {
1140
- const report = makeReport({
1141
- projectInfo: {
1142
- ...makeReport().projectInfo,
1143
- primaryLanguages: ['PHP'],
1144
- name: 'laravel-app',
1145
- },
1146
- dependencyGraph: {
1147
- nodes: [
1148
- 'app/models/User.php',
1149
- 'app/views/users/show.blade.php',
1150
- 'app/Http/Controllers/UserController.php',
1151
- ],
1152
- edges: [],
1153
- },
1154
- });
1155
- writeFileSync(
1156
- join(tempDir, 'composer.json'),
1157
- JSON.stringify({
1158
- require: {
1159
- 'laravel/framework': '^10.0',
1160
- },
1161
- }),
1162
- );
1163
-
1164
- const result = detector.detect(tempDir, report);
1165
-
1166
- expect(result.primaryFramework?.name).toBe('Laravel');
1167
- expect(result.toolchain.runCmd).toBe('php artisan serve');
1168
- expect(result.toolchain.migrateCmd).toBe('php artisan migrate');
1169
- expect(result.projectStructure).toBe('mvc');
1170
- });
1171
- });
1172
- });