popeye-cli 2.1.0 → 2.7.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 (356) hide show
  1. package/dist/adapters/gemini.d.ts +14 -0
  2. package/dist/adapters/gemini.d.ts.map +1 -1
  3. package/dist/adapters/gemini.js +41 -6
  4. package/dist/adapters/gemini.js.map +1 -1
  5. package/dist/adapters/grok.d.ts +14 -0
  6. package/dist/adapters/grok.d.ts.map +1 -1
  7. package/dist/adapters/grok.js +42 -6
  8. package/dist/adapters/grok.js.map +1 -1
  9. package/dist/adapters/openai.d.ts +10 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +44 -5
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/cli/commands/create.js +1 -1
  14. package/dist/cli/commands/create.js.map +1 -1
  15. package/dist/cli/interactive.d.ts.map +1 -1
  16. package/dist/cli/interactive.js +328 -21
  17. package/dist/cli/interactive.js.map +1 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +25 -2
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +21 -6
  22. package/dist/generators/doc-parser.d.ts.map +1 -1
  23. package/dist/generators/doc-parser.js +55 -4
  24. package/dist/generators/doc-parser.js.map +1 -1
  25. package/dist/generators/templates/fullstack.js +1 -1
  26. package/dist/generators/templates/website-components.js +1 -1
  27. package/dist/generators/templates/website-components.js.map +1 -1
  28. package/dist/generators/templates/website-config.d.ts +4 -1
  29. package/dist/generators/templates/website-config.d.ts.map +1 -1
  30. package/dist/generators/templates/website-config.js +17 -11
  31. package/dist/generators/templates/website-config.js.map +1 -1
  32. package/dist/generators/templates/website-conversion.js +1 -1
  33. package/dist/generators/templates/website-conversion.js.map +1 -1
  34. package/dist/generators/templates/website-landing.js +1 -1
  35. package/dist/generators/templates/website-landing.js.map +1 -1
  36. package/dist/generators/templates/website-layout.d.ts +36 -4
  37. package/dist/generators/templates/website-layout.d.ts.map +1 -1
  38. package/dist/generators/templates/website-layout.js +466 -23
  39. package/dist/generators/templates/website-layout.js.map +1 -1
  40. package/dist/generators/templates/website-pricing.js +1 -1
  41. package/dist/generators/templates/website-pricing.js.map +1 -1
  42. package/dist/generators/templates/website-sections.js +1 -1
  43. package/dist/generators/templates/website-sections.js.map +1 -1
  44. package/dist/generators/templates/website-seo.d.ts.map +1 -1
  45. package/dist/generators/templates/website-seo.js +4 -1
  46. package/dist/generators/templates/website-seo.js.map +1 -1
  47. package/dist/generators/templates/website.d.ts +1 -1
  48. package/dist/generators/templates/website.d.ts.map +1 -1
  49. package/dist/generators/templates/website.js +1 -1
  50. package/dist/generators/templates/website.js.map +1 -1
  51. package/dist/generators/website-content-ai.d.ts +52 -0
  52. package/dist/generators/website-content-ai.d.ts.map +1 -0
  53. package/dist/generators/website-content-ai.js +141 -0
  54. package/dist/generators/website-content-ai.js.map +1 -0
  55. package/dist/generators/website-content-scanner.d.ts +1 -1
  56. package/dist/generators/website-content-scanner.d.ts.map +1 -1
  57. package/dist/generators/website-content-scanner.js +98 -1
  58. package/dist/generators/website-content-scanner.js.map +1 -1
  59. package/dist/generators/website-context.d.ts +34 -1
  60. package/dist/generators/website-context.d.ts.map +1 -1
  61. package/dist/generators/website-context.js +131 -9
  62. package/dist/generators/website-context.js.map +1 -1
  63. package/dist/generators/website-debug.d.ts +12 -0
  64. package/dist/generators/website-debug.d.ts.map +1 -1
  65. package/dist/generators/website-debug.js +16 -0
  66. package/dist/generators/website-debug.js.map +1 -1
  67. package/dist/generators/website.d.ts.map +1 -1
  68. package/dist/generators/website.js +26 -4
  69. package/dist/generators/website.js.map +1 -1
  70. package/dist/pipeline/artifact-manager.d.ts.map +1 -1
  71. package/dist/pipeline/artifact-manager.js +3 -0
  72. package/dist/pipeline/artifact-manager.js.map +1 -1
  73. package/dist/pipeline/auto-recovery.d.ts +56 -0
  74. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  75. package/dist/pipeline/auto-recovery.js +185 -0
  76. package/dist/pipeline/auto-recovery.js.map +1 -0
  77. package/dist/pipeline/change-request.d.ts +39 -0
  78. package/dist/pipeline/change-request.d.ts.map +1 -1
  79. package/dist/pipeline/change-request.js +40 -1
  80. package/dist/pipeline/change-request.js.map +1 -1
  81. package/dist/pipeline/check-runner.d.ts +30 -1
  82. package/dist/pipeline/check-runner.d.ts.map +1 -1
  83. package/dist/pipeline/check-runner.js +122 -1
  84. package/dist/pipeline/check-runner.js.map +1 -1
  85. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  86. package/dist/pipeline/command-resolver.js +33 -2
  87. package/dist/pipeline/command-resolver.js.map +1 -1
  88. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  89. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  90. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  91. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  92. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  93. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  94. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  95. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  96. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  97. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  98. package/dist/pipeline/cr-lifecycle.js +89 -0
  99. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  100. package/dist/pipeline/gate-engine.d.ts +1 -0
  101. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  102. package/dist/pipeline/gate-engine.js +27 -8
  103. package/dist/pipeline/gate-engine.js.map +1 -1
  104. package/dist/pipeline/migration.d.ts.map +1 -1
  105. package/dist/pipeline/migration.js +3 -26
  106. package/dist/pipeline/migration.js.map +1 -1
  107. package/dist/pipeline/orchestrator.d.ts +1 -1
  108. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  109. package/dist/pipeline/orchestrator.js +311 -16
  110. package/dist/pipeline/orchestrator.js.map +1 -1
  111. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  112. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  113. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  114. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  115. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/architecture.js +5 -3
  117. package/dist/pipeline/phases/architecture.js.map +1 -1
  118. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  119. package/dist/pipeline/phases/audit.js +5 -3
  120. package/dist/pipeline/phases/audit.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  123. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  124. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  125. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  126. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  127. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  128. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  129. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  130. package/dist/pipeline/phases/done.d.ts.map +1 -1
  131. package/dist/pipeline/phases/done.js +9 -4
  132. package/dist/pipeline/phases/done.js.map +1 -1
  133. package/dist/pipeline/phases/intake.d.ts +1 -0
  134. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  135. package/dist/pipeline/phases/intake.js +56 -13
  136. package/dist/pipeline/phases/intake.js.map +1 -1
  137. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  138. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  139. package/dist/pipeline/phases/phase-context.js +3 -1
  140. package/dist/pipeline/phases/phase-context.js.map +1 -1
  141. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  142. package/dist/pipeline/phases/production-gate.js +28 -3
  143. package/dist/pipeline/phases/production-gate.js.map +1 -1
  144. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  145. package/dist/pipeline/phases/qa-validation.js +38 -5
  146. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  147. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  148. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  149. package/dist/pipeline/phases/recovery-loop.js +200 -6
  150. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  151. package/dist/pipeline/phases/review.d.ts.map +1 -1
  152. package/dist/pipeline/phases/review.js +58 -28
  153. package/dist/pipeline/phases/review.js.map +1 -1
  154. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  155. package/dist/pipeline/phases/role-planning.js +20 -5
  156. package/dist/pipeline/phases/role-planning.js.map +1 -1
  157. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  158. package/dist/pipeline/phases/stuck.js +10 -0
  159. package/dist/pipeline/phases/stuck.js.map +1 -1
  160. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  161. package/dist/pipeline/repo-snapshot.js +3 -0
  162. package/dist/pipeline/repo-snapshot.js.map +1 -1
  163. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  164. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  165. package/dist/pipeline/role-execution-adapter.js +22 -7
  166. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  167. package/dist/pipeline/skill-loader.d.ts +19 -0
  168. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  169. package/dist/pipeline/skill-loader.js +22 -0
  170. package/dist/pipeline/skill-loader.js.map +1 -1
  171. package/dist/pipeline/skills/constitution-generator.d.ts +51 -0
  172. package/dist/pipeline/skills/constitution-generator.d.ts.map +1 -0
  173. package/dist/pipeline/skills/constitution-generator.js +210 -0
  174. package/dist/pipeline/skills/constitution-generator.js.map +1 -0
  175. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  176. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  177. package/dist/pipeline/skills/coverage-gate.js +143 -0
  178. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  179. package/dist/pipeline/skills/generator.d.ts +65 -0
  180. package/dist/pipeline/skills/generator.d.ts.map +1 -0
  181. package/dist/pipeline/skills/generator.js +221 -0
  182. package/dist/pipeline/skills/generator.js.map +1 -0
  183. package/dist/pipeline/skills/role-map.d.ts +38 -0
  184. package/dist/pipeline/skills/role-map.d.ts.map +1 -0
  185. package/dist/pipeline/skills/role-map.js +234 -0
  186. package/dist/pipeline/skills/role-map.js.map +1 -0
  187. package/dist/pipeline/skills/types.d.ts +47 -0
  188. package/dist/pipeline/skills/types.d.ts.map +1 -0
  189. package/dist/pipeline/skills/types.js +5 -0
  190. package/dist/pipeline/skills/types.js.map +1 -0
  191. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  192. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  193. package/dist/pipeline/skills/usage-registry.js +55 -0
  194. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  195. package/dist/pipeline/strategy-context.d.ts +20 -0
  196. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  197. package/dist/pipeline/strategy-context.js +55 -0
  198. package/dist/pipeline/strategy-context.js.map +1 -0
  199. package/dist/pipeline/type-defs/artifacts.d.ts +30 -5
  200. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  201. package/dist/pipeline/type-defs/artifacts.js +5 -0
  202. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  203. package/dist/pipeline/type-defs/audit.d.ts +28 -13
  204. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  205. package/dist/pipeline/type-defs/checks.d.ts +19 -8
  206. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  207. package/dist/pipeline/type-defs/checks.js +4 -0
  208. package/dist/pipeline/type-defs/checks.js.map +1 -1
  209. package/dist/pipeline/type-defs/packets.d.ts +119 -18
  210. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  211. package/dist/pipeline/type-defs/packets.js +17 -1
  212. package/dist/pipeline/type-defs/packets.js.map +1 -1
  213. package/dist/pipeline/type-defs/state.d.ts +165 -16
  214. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  215. package/dist/pipeline/type-defs/state.js +26 -1
  216. package/dist/pipeline/type-defs/state.js.map +1 -1
  217. package/dist/shared/text-utils.d.ts +23 -0
  218. package/dist/shared/text-utils.d.ts.map +1 -0
  219. package/dist/shared/text-utils.js +66 -0
  220. package/dist/shared/text-utils.js.map +1 -0
  221. package/dist/shared/website-strategy-format.d.ts +18 -0
  222. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  223. package/dist/shared/website-strategy-format.js +47 -0
  224. package/dist/shared/website-strategy-format.js.map +1 -0
  225. package/dist/state/index.d.ts +2 -0
  226. package/dist/state/index.d.ts.map +1 -1
  227. package/dist/state/index.js +57 -8
  228. package/dist/state/index.js.map +1 -1
  229. package/dist/types/consensus.d.ts +1 -0
  230. package/dist/types/consensus.d.ts.map +1 -1
  231. package/dist/types/consensus.js.map +1 -1
  232. package/dist/types/website-strategy.d.ts +1 -1
  233. package/dist/types/workflow.d.ts +447 -0
  234. package/dist/types/workflow.d.ts.map +1 -1
  235. package/dist/types/workflow.js +3 -0
  236. package/dist/types/workflow.js.map +1 -1
  237. package/dist/upgrade/handlers.d.ts.map +1 -1
  238. package/dist/upgrade/handlers.js +6 -3
  239. package/dist/upgrade/handlers.js.map +1 -1
  240. package/dist/workflow/consensus.d.ts.map +1 -1
  241. package/dist/workflow/consensus.js +1 -0
  242. package/dist/workflow/consensus.js.map +1 -1
  243. package/dist/workflow/website-strategy.d.ts.map +1 -1
  244. package/dist/workflow/website-strategy.js +2 -29
  245. package/dist/workflow/website-strategy.js.map +1 -1
  246. package/dist/workflow/website-updater.d.ts.map +1 -1
  247. package/dist/workflow/website-updater.js +3 -2
  248. package/dist/workflow/website-updater.js.map +1 -1
  249. package/package.json +1 -1
  250. package/src/adapters/gemini.ts +51 -6
  251. package/src/adapters/grok.ts +51 -6
  252. package/src/adapters/openai.ts +53 -5
  253. package/src/cli/commands/create.ts +1 -1
  254. package/src/cli/interactive.ts +337 -20
  255. package/src/generators/all.ts +25 -2
  256. package/src/generators/doc-parser.ts +75 -15
  257. package/src/generators/templates/fullstack.ts +1 -1
  258. package/src/generators/templates/website-components.ts +1 -1
  259. package/src/generators/templates/website-config.ts +23 -11
  260. package/src/generators/templates/website-conversion.ts +1 -1
  261. package/src/generators/templates/website-landing.ts +1 -1
  262. package/src/generators/templates/website-layout.ts +491 -23
  263. package/src/generators/templates/website-pricing.ts +1 -1
  264. package/src/generators/templates/website-sections.ts +1 -1
  265. package/src/generators/templates/website-seo.ts +4 -1
  266. package/src/generators/templates/website.ts +3 -0
  267. package/src/generators/website-content-ai.ts +186 -0
  268. package/src/generators/website-content-scanner.ts +113 -1
  269. package/src/generators/website-context.ts +151 -12
  270. package/src/generators/website-debug.ts +26 -0
  271. package/src/generators/website.ts +28 -3
  272. package/src/pipeline/artifact-manager.ts +3 -0
  273. package/src/pipeline/auto-recovery.ts +283 -0
  274. package/src/pipeline/change-request.ts +63 -1
  275. package/src/pipeline/check-runner.ts +141 -2
  276. package/src/pipeline/command-resolver.ts +34 -2
  277. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  278. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  279. package/src/pipeline/cr-lifecycle.ts +103 -0
  280. package/src/pipeline/gate-engine.ts +36 -8
  281. package/src/pipeline/migration.ts +5 -30
  282. package/src/pipeline/orchestrator.ts +367 -16
  283. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  284. package/src/pipeline/phases/architecture.ts +6 -3
  285. package/src/pipeline/phases/audit.ts +6 -3
  286. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  287. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  288. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  289. package/src/pipeline/phases/done.ts +15 -4
  290. package/src/pipeline/phases/intake.ts +67 -14
  291. package/src/pipeline/phases/phase-context.ts +6 -1
  292. package/src/pipeline/phases/production-gate.ts +41 -3
  293. package/src/pipeline/phases/qa-validation.ts +51 -5
  294. package/src/pipeline/phases/recovery-loop.ts +229 -7
  295. package/src/pipeline/phases/review.ts +73 -30
  296. package/src/pipeline/phases/role-planning.ts +23 -5
  297. package/src/pipeline/phases/stuck.ts +10 -0
  298. package/src/pipeline/repo-snapshot.ts +3 -0
  299. package/src/pipeline/role-execution-adapter.ts +30 -4
  300. package/src/pipeline/skill-loader.ts +33 -0
  301. package/src/pipeline/skills/constitution-generator.ts +236 -0
  302. package/src/pipeline/skills/coverage-gate.ts +199 -0
  303. package/src/pipeline/skills/generator.ts +287 -0
  304. package/src/pipeline/skills/role-map.ts +248 -0
  305. package/src/pipeline/skills/types.ts +53 -0
  306. package/src/pipeline/skills/usage-registry.ts +87 -0
  307. package/src/pipeline/strategy-context.ts +60 -0
  308. package/src/pipeline/type-defs/artifacts.ts +5 -0
  309. package/src/pipeline/type-defs/checks.ts +4 -0
  310. package/src/pipeline/type-defs/packets.ts +18 -1
  311. package/src/pipeline/type-defs/state.ts +26 -1
  312. package/src/shared/text-utils.ts +70 -0
  313. package/src/shared/website-strategy-format.ts +56 -0
  314. package/src/state/index.ts +60 -8
  315. package/src/types/consensus.ts +1 -0
  316. package/src/types/workflow.ts +6 -0
  317. package/src/upgrade/handlers.ts +9 -3
  318. package/src/workflow/consensus.ts +1 -0
  319. package/src/workflow/website-strategy.ts +2 -36
  320. package/src/workflow/website-updater.ts +4 -2
  321. package/tests/adapters/gemini.test.ts +165 -0
  322. package/tests/adapters/grok.test.ts +137 -0
  323. package/tests/adapters/openai.test.ts +128 -0
  324. package/tests/generators/doc-parser.test.ts +88 -9
  325. package/tests/generators/quality-gate.test.ts +19 -3
  326. package/tests/generators/website-components.test.ts +34 -0
  327. package/tests/generators/website-content-ai.test.ts +308 -0
  328. package/tests/generators/website-content-scanner.test.ts +86 -0
  329. package/tests/generators/website-context.test.ts +3 -2
  330. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  331. package/tests/pipeline/auto-recovery.test.ts +337 -0
  332. package/tests/pipeline/change-request.test.ts +70 -0
  333. package/tests/pipeline/command-resolver.test.ts +42 -0
  334. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  335. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  336. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  337. package/tests/pipeline/gate-engine.test.ts +34 -0
  338. package/tests/pipeline/install-check.test.ts +261 -0
  339. package/tests/pipeline/migration.test.ts +4 -3
  340. package/tests/pipeline/orchestrator.test.ts +1506 -15
  341. package/tests/pipeline/packets/builders.test.ts +29 -6
  342. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  343. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  344. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  345. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  346. package/tests/pipeline/skills/constitution-generator.test.ts +201 -0
  347. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  348. package/tests/pipeline/skills/generator.test.ts +213 -0
  349. package/tests/pipeline/skills/role-map.test.ts +198 -0
  350. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  351. package/tests/pipeline/strategy-context.test.ts +148 -0
  352. package/tests/shared/text-utils.test.ts +155 -0
  353. package/tests/state/progress-analysis.test.ts +375 -0
  354. package/tests/upgrade/handlers.test.ts +33 -2
  355. package/tests/workflow/consensus.test.ts +6 -0
  356. package/tsconfig.json +1 -1
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Role map tests — role selection, tech stack inference, template constraints.
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import {
7
+ getActiveRoles,
8
+ inferTechStack,
9
+ getTemplateConstraints,
10
+ SUPPORT_ROLES,
11
+ IMPLEMENTATION_ROLES,
12
+ } from '../../../src/pipeline/skills/role-map.js';
13
+ import type { RepoSnapshot } from '../../../src/pipeline/types.js';
14
+ import type { OutputLanguage } from '../../../src/types/project.js';
15
+
16
+ function makeSnapshot(overrides: Partial<RepoSnapshot> = {}): RepoSnapshot {
17
+ return {
18
+ snapshot_id: 'test-snap',
19
+ timestamp: new Date().toISOString(),
20
+ tree_summary: '',
21
+ config_files: [],
22
+ languages_detected: [],
23
+ scripts: {},
24
+ env_files: [],
25
+ migrations_present: false,
26
+ ports_entrypoints: [],
27
+ total_files: 0,
28
+ total_lines: 0,
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ describe('role-map', () => {
34
+ describe('getActiveRoles', () => {
35
+ it('should include support roles for all languages', () => {
36
+ const languages: OutputLanguage[] = ['python', 'typescript', 'fullstack', 'website', 'all'];
37
+ for (const lang of languages) {
38
+ const roles = getActiveRoles(lang);
39
+ for (const support of SUPPORT_ROLES) {
40
+ expect(roles).toContain(support);
41
+ }
42
+ }
43
+ });
44
+
45
+ it('should include DB_EXPERT for python', () => {
46
+ const roles = getActiveRoles('python');
47
+ expect(roles).toContain('DB_EXPERT');
48
+ expect(roles).toContain('BACKEND_PROGRAMMER');
49
+ expect(roles).not.toContain('FRONTEND_PROGRAMMER');
50
+ });
51
+
52
+ it('should include FRONTEND_PROGRAMMER for typescript (not BACKEND_PROGRAMMER)', () => {
53
+ const roles = getActiveRoles('typescript');
54
+ expect(roles).toContain('FRONTEND_PROGRAMMER');
55
+ expect(roles).toContain('UI_UX_SPECIALIST');
56
+ expect(roles).not.toContain('BACKEND_PROGRAMMER');
57
+ expect(roles).not.toContain('DB_EXPERT');
58
+ });
59
+
60
+ it('should include both FE and BE for fullstack', () => {
61
+ const roles = getActiveRoles('fullstack');
62
+ expect(roles).toContain('DB_EXPERT');
63
+ expect(roles).toContain('BACKEND_PROGRAMMER');
64
+ expect(roles).toContain('FRONTEND_PROGRAMMER');
65
+ expect(roles).toContain('UI_UX_SPECIALIST');
66
+ expect(roles).not.toContain('WEBSITE_PROGRAMMER');
67
+ });
68
+
69
+ it('should include website and marketing roles for website', () => {
70
+ const roles = getActiveRoles('website');
71
+ expect(roles).toContain('WEBSITE_PROGRAMMER');
72
+ expect(roles).toContain('UI_UX_SPECIALIST');
73
+ expect(roles).toContain('MARKETING_EXPERT');
74
+ expect(roles).toContain('SOCIAL_EXPERT');
75
+ expect(roles).not.toContain('BACKEND_PROGRAMMER');
76
+ });
77
+
78
+ it('should include all implementation roles for all', () => {
79
+ const roles = getActiveRoles('all');
80
+ for (const impl of IMPLEMENTATION_ROLES) {
81
+ expect(roles).toContain(impl);
82
+ }
83
+ });
84
+ });
85
+
86
+ describe('inferTechStack', () => {
87
+ it('should return language defaults when no signals', () => {
88
+ const ts = inferTechStack('python');
89
+ expect(ts.backend).toBe('FastAPI');
90
+ expect(ts.database).toBe('PostgreSQL');
91
+ expect(ts.orm).toBe('SQLAlchemy');
92
+ expect(ts.testing).toBe('Pytest');
93
+ expect(ts.language).toBe('Python 3.11+');
94
+ });
95
+
96
+ it('should detect FastAPI from snapshot key_fields', () => {
97
+ const snapshot = makeSnapshot({
98
+ config_files: [{
99
+ path: 'pyproject.toml',
100
+ type: 'toml',
101
+ content_hash: 'abc',
102
+ key_fields: { dependencies: ['fastapi', 'uvicorn'] },
103
+ }],
104
+ });
105
+ const ts = inferTechStack('python', snapshot);
106
+ expect(ts.backend).toBe('FastAPI');
107
+ });
108
+
109
+ it('should detect Django from snapshot key_fields', () => {
110
+ const snapshot = makeSnapshot({
111
+ config_files: [{
112
+ path: 'requirements.txt',
113
+ type: 'txt',
114
+ content_hash: 'abc',
115
+ key_fields: { packages: ['django', 'django-rest-framework'] },
116
+ }],
117
+ });
118
+ const ts = inferTechStack('python', snapshot);
119
+ expect(ts.backend).toBe('Django');
120
+ });
121
+
122
+ it('should detect framework from expanded spec when snapshot has no deps', () => {
123
+ const ts = inferTechStack('python', makeSnapshot(), 'Build a Django REST API');
124
+ expect(ts.backend).toBe('Django');
125
+ });
126
+
127
+ it('should prioritize snapshot over spec mentions', () => {
128
+ const snapshot = makeSnapshot({
129
+ config_files: [{
130
+ path: 'pyproject.toml',
131
+ type: 'toml',
132
+ content_hash: 'abc',
133
+ key_fields: { dependencies: ['fastapi'] },
134
+ }],
135
+ });
136
+ const ts = inferTechStack('python', snapshot, 'Build a Django API');
137
+ // Snapshot has fastapi, spec mentions Django — snapshot wins
138
+ expect(ts.backend).toBe('FastAPI');
139
+ });
140
+
141
+ it('should return typescript defaults', () => {
142
+ const ts = inferTechStack('typescript');
143
+ expect(ts.frontend).toBe('React + Vite');
144
+ expect(ts.testing).toBe('Vitest');
145
+ expect(ts.language).toBe('TypeScript 5.x');
146
+ });
147
+
148
+ it('should detect Next.js from snapshot', () => {
149
+ const snapshot = makeSnapshot({
150
+ config_files: [{
151
+ path: 'package.json',
152
+ type: 'json',
153
+ content_hash: 'abc',
154
+ key_fields: { dependencies: { next: '^14.0.0', react: '^18.0.0' } },
155
+ }],
156
+ });
157
+ const ts = inferTechStack('typescript', snapshot);
158
+ expect(ts.frontend).toBe('Next.js');
159
+ });
160
+ });
161
+
162
+ describe('getTemplateConstraints', () => {
163
+ it('should always include governance constraints', () => {
164
+ const constraints = getTemplateConstraints('BACKEND_PROGRAMMER', {});
165
+ expect(constraints).toContain('must_follow_master_plan');
166
+ expect(constraints).toContain('must_follow_architecture');
167
+ expect(constraints).toContain('conflicts_require_change_request');
168
+ });
169
+
170
+ it('should add FastAPI constraints for backend with FastAPI', () => {
171
+ const constraints = getTemplateConstraints('BACKEND_PROGRAMMER', {
172
+ backend: 'FastAPI',
173
+ testing: 'Pytest',
174
+ });
175
+ expect(constraints).toContain('fastapi_async_required');
176
+ expect(constraints).toContain('pydantic_validation');
177
+ expect(constraints).toContain('pytest_testing');
178
+ });
179
+
180
+ it('should add React constraints for frontend', () => {
181
+ const constraints = getTemplateConstraints('FRONTEND_PROGRAMMER', {
182
+ frontend: 'React + Vite',
183
+ testing: 'Vitest',
184
+ });
185
+ expect(constraints).toContain('react_component_pattern');
186
+ expect(constraints).toContain('component_testing');
187
+ });
188
+
189
+ it('should return only governance constraints for roles without tech constraints', () => {
190
+ const constraints = getTemplateConstraints('DISPATCHER', {});
191
+ expect(constraints).toEqual([
192
+ 'must_follow_master_plan',
193
+ 'must_follow_architecture',
194
+ 'conflicts_require_change_request',
195
+ ]);
196
+ });
197
+ });
198
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * SkillUsageRegistry tests — recording, querying, and array reference sharing.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach } from 'vitest';
6
+ import { SkillUsageRegistry } from '../../../src/pipeline/skills/usage-registry.js';
7
+ import type { SkillUsageEvent } from '../../../src/pipeline/skills/usage-registry.js';
8
+
9
+ describe('SkillUsageRegistry', () => {
10
+ let events: SkillUsageEvent[];
11
+ let registry: SkillUsageRegistry;
12
+
13
+ beforeEach(() => {
14
+ events = [];
15
+ registry = new SkillUsageRegistry(events);
16
+ });
17
+
18
+ describe('record', () => {
19
+ it('should record a usage event with all fields', () => {
20
+ registry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', 'defaults', '1.0');
21
+
22
+ const recorded = registry.getEvents();
23
+ expect(recorded).toHaveLength(1);
24
+ expect(recorded[0].role).toBe('ARCHITECT');
25
+ expect(recorded[0].phase).toBe('ARCHITECTURE');
26
+ expect(recorded[0].used_as).toBe('system_prompt');
27
+ expect(recorded[0].skill_source).toBe('defaults');
28
+ expect(recorded[0].skill_version).toBe('1.0');
29
+ expect(recorded[0].timestamp).toBeTruthy();
30
+ });
31
+
32
+ it('should record event without optional version', () => {
33
+ registry.record('REVIEWER', 'REVIEW', 'review_prompt', 'project_override');
34
+
35
+ const recorded = registry.getEvents();
36
+ expect(recorded).toHaveLength(1);
37
+ expect(recorded[0].skill_version).toBeUndefined();
38
+ });
39
+
40
+ it('should push into the shared array reference', () => {
41
+ registry.record('AUDITOR', 'AUDIT', 'system_prompt', 'defaults');
42
+
43
+ // The underlying events array should be mutated directly
44
+ expect(events).toHaveLength(1);
45
+ expect(events[0].role).toBe('AUDITOR');
46
+ });
47
+ });
48
+
49
+ describe('getEvents', () => {
50
+ it('should return a copy of events', () => {
51
+ registry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', 'defaults');
52
+ const copy = registry.getEvents();
53
+
54
+ // Mutating the copy should not affect the original
55
+ copy.push({
56
+ role: 'DEBUGGER',
57
+ phase: 'RECOVERY_LOOP',
58
+ used_as: 'system_prompt',
59
+ skill_source: 'defaults',
60
+ timestamp: new Date().toISOString(),
61
+ });
62
+
63
+ expect(registry.getEvents()).toHaveLength(1);
64
+ });
65
+ });
66
+
67
+ describe('getEventsForRole', () => {
68
+ it('should filter events by role', () => {
69
+ registry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', 'defaults');
70
+ registry.record('REVIEWER', 'REVIEW', 'review_prompt', 'defaults');
71
+ registry.record('ARCHITECT', 'CONSENSUS_ARCHITECTURE', 'other', 'defaults');
72
+
73
+ const architectEvents = registry.getEventsForRole('ARCHITECT');
74
+ expect(architectEvents).toHaveLength(2);
75
+ expect(architectEvents.every((e) => e.role === 'ARCHITECT')).toBe(true);
76
+ });
77
+
78
+ it('should return empty array for role with no events', () => {
79
+ registry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', 'defaults');
80
+
81
+ const debuggerEvents = registry.getEventsForRole('DEBUGGER');
82
+ expect(debuggerEvents).toHaveLength(0);
83
+ });
84
+ });
85
+
86
+ describe('hasUsage', () => {
87
+ it('should return true for role with recorded events', () => {
88
+ registry.record('AUDITOR', 'AUDIT', 'system_prompt', 'defaults');
89
+
90
+ expect(registry.hasUsage('AUDITOR')).toBe(true);
91
+ });
92
+
93
+ it('should return false for role with no events', () => {
94
+ expect(registry.hasUsage('DEBUGGER')).toBe(false);
95
+ });
96
+ });
97
+
98
+ describe('state serialization', () => {
99
+ it('should round-trip through JSON correctly', () => {
100
+ registry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', 'defaults', '1.0');
101
+ registry.record('REVIEWER', 'REVIEW', 'review_prompt', 'project_override', '2.0');
102
+
103
+ // Simulate state serialization
104
+ const serialized = JSON.stringify(events);
105
+ const deserialized = JSON.parse(serialized) as SkillUsageEvent[];
106
+
107
+ // Create new registry from deserialized data
108
+ const newRegistry = new SkillUsageRegistry(deserialized);
109
+ expect(newRegistry.getEvents()).toHaveLength(2);
110
+ expect(newRegistry.hasUsage('ARCHITECT')).toBe(true);
111
+ expect(newRegistry.hasUsage('REVIEWER')).toBe(true);
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Strategy context loader tests — loadStrategyForRole, STRATEGY_ROLES.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { STRATEGY_ROLES, loadStrategyForRole } from '../../src/pipeline/strategy-context.js';
9
+
10
+ const TEST_DIR = join(process.cwd(), 'tmp-strategy-context-test');
11
+
12
+ function makeValidStrategy() {
13
+ return {
14
+ icp: { primaryPersona: 'developers', painPoints: ['slow builds'], goals: ['fast CI'], objections: ['cost'] },
15
+ positioning: { category: 'DevTools', differentiators: ['speed'], valueProposition: 'Build faster', proofPoints: ['10x'] },
16
+ messaging: { headline: 'Build Faster', subheadline: 'CI that works', elevatorPitch: 'Fast CI', longDescription: 'A fast CI platform' },
17
+ seoStrategy: { primaryKeywords: ['CI'], secondaryKeywords: ['build'], longTailKeywords: ['fast CI'], titleTemplates: {}, metaDescriptions: {} },
18
+ siteArchitecture: {
19
+ pages: [{ path: '/', title: 'Home', purpose: 'Landing', pageType: 'landing', sections: ['hero'], seoKeywords: ['CI'], conversionGoal: 'signup' }],
20
+ navigation: [{ label: 'Home', href: '/' }],
21
+ footerSections: [{ title: 'Links', links: [{ label: 'Home', href: '/' }] }],
22
+ },
23
+ conversionStrategy: {
24
+ primaryCta: { text: 'Get Started', href: '/signup' },
25
+ secondaryCta: { text: 'Learn More', href: '/docs' },
26
+ trustSignals: ['SOC2'],
27
+ socialProof: ['1000+ teams'],
28
+ leadCapture: 'webhook',
29
+ },
30
+ competitiveContext: { category: 'CI/CD', competitors: ['CircleCI'], differentiators: ['speed'] },
31
+ };
32
+ }
33
+
34
+ beforeEach(() => {
35
+ mkdirSync(join(TEST_DIR, '.popeye'), { recursive: true });
36
+ });
37
+
38
+ afterEach(() => {
39
+ if (existsSync(TEST_DIR)) {
40
+ rmSync(TEST_DIR, { recursive: true, force: true });
41
+ }
42
+ });
43
+
44
+ describe('STRATEGY_ROLES', () => {
45
+ it('includes exactly WEBSITE_PROGRAMMER, MARKETING_EXPERT, SOCIAL_EXPERT', () => {
46
+ expect(STRATEGY_ROLES).toContain('WEBSITE_PROGRAMMER');
47
+ expect(STRATEGY_ROLES).toContain('MARKETING_EXPERT');
48
+ expect(STRATEGY_ROLES).toContain('SOCIAL_EXPERT');
49
+ expect(STRATEGY_ROLES).toHaveLength(3);
50
+ });
51
+ });
52
+
53
+ describe('loadStrategyForRole', () => {
54
+ it('returns undefined when no strategy file exists', () => {
55
+ const result = loadStrategyForRole(TEST_DIR);
56
+ expect(result).toBeUndefined();
57
+ });
58
+
59
+ it('returns formatted string when valid .popeye/website-strategy.json exists', () => {
60
+ const strategy = makeValidStrategy();
61
+ writeFileSync(
62
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
63
+ JSON.stringify(strategy),
64
+ );
65
+
66
+ const result = loadStrategyForRole(TEST_DIR);
67
+ expect(result).toBeDefined();
68
+ expect(result).toContain('Target Customer');
69
+ expect(result).toContain('developers');
70
+ expect(result).toContain('Build Faster');
71
+ expect(result).toContain('SEO Keywords');
72
+ expect(result).toContain('Conversion Strategy');
73
+ });
74
+
75
+ it('handles nested strategy field in JSON', () => {
76
+ const strategy = makeValidStrategy();
77
+ writeFileSync(
78
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
79
+ JSON.stringify({ strategy, metadata: { inputHash: 'abc', generatedAt: '2025-01-01', version: 1 } }),
80
+ );
81
+
82
+ const result = loadStrategyForRole(TEST_DIR);
83
+ expect(result).toBeDefined();
84
+ expect(result).toContain('developers');
85
+ });
86
+
87
+ it('returns undefined for malformed JSON', () => {
88
+ writeFileSync(
89
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
90
+ '{ invalid json!!!',
91
+ );
92
+
93
+ const result = loadStrategyForRole(TEST_DIR);
94
+ expect(result).toBeUndefined();
95
+ });
96
+
97
+ it('returns undefined for JSON that fails schema validation', () => {
98
+ writeFileSync(
99
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
100
+ JSON.stringify({ icp: { primaryPersona: '' } }),
101
+ );
102
+
103
+ const result = loadStrategyForRole(TEST_DIR);
104
+ expect(result).toBeUndefined();
105
+ });
106
+
107
+ it('falls back to .popeye/website-strategy.md when JSON absent', () => {
108
+ const mdContent = '# Strategy\n\nTarget: developers\nKeywords: CI, build';
109
+ writeFileSync(
110
+ join(TEST_DIR, '.popeye', 'website-strategy.md'),
111
+ mdContent,
112
+ );
113
+
114
+ const result = loadStrategyForRole(TEST_DIR);
115
+ expect(result).toBe(mdContent);
116
+ });
117
+
118
+ it('prefers JSON over MD when both exist', () => {
119
+ const strategy = makeValidStrategy();
120
+ writeFileSync(
121
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
122
+ JSON.stringify(strategy),
123
+ );
124
+ writeFileSync(
125
+ join(TEST_DIR, '.popeye', 'website-strategy.md'),
126
+ '# Fallback MD',
127
+ );
128
+
129
+ const result = loadStrategyForRole(TEST_DIR);
130
+ // JSON is checked first, should get formatted strategy not raw MD
131
+ expect(result).toContain('Target Customer');
132
+ expect(result).not.toContain('Fallback MD');
133
+ });
134
+
135
+ it('falls back to MD when JSON is malformed', () => {
136
+ writeFileSync(
137
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
138
+ '{ bad json }',
139
+ );
140
+ writeFileSync(
141
+ join(TEST_DIR, '.popeye', 'website-strategy.md'),
142
+ '# Good MD Strategy',
143
+ );
144
+
145
+ const result = loadStrategyForRole(TEST_DIR);
146
+ expect(result).toBe('# Good MD Strategy');
147
+ });
148
+ });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Tests for isNoneVariant() and normalizeIssueList()
3
+ * Ensures false-positive blocking issues are filtered while real issues are preserved.
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import { isNoneVariant, normalizeIssueList } from '../../src/shared/text-utils.js';
8
+
9
+ describe('isNoneVariant', () => {
10
+ describe('true positives (should be filtered out)', () => {
11
+ it('should detect exact "none" variants', () => {
12
+ expect(isNoneVariant('None')).toBe(true);
13
+ expect(isNoneVariant('none')).toBe(true);
14
+ expect(isNoneVariant('NONE')).toBe(true);
15
+ expect(isNoneVariant('None.')).toBe(true);
16
+ expect(isNoneVariant('N/A')).toBe(true);
17
+ expect(isNoneVariant('n/a')).toBe(true);
18
+ expect(isNoneVariant('NA')).toBe(true);
19
+ expect(isNoneVariant('Nil')).toBe(true);
20
+ expect(isNoneVariant('Nothing')).toBe(true);
21
+ });
22
+
23
+ it('should detect bullet-prefixed none-variants', () => {
24
+ expect(isNoneVariant('- None')).toBe(true);
25
+ expect(isNoneVariant('* N/A')).toBe(true);
26
+ expect(isNoneVariant('+ Nothing')).toBe(true);
27
+ expect(isNoneVariant('1) Nothing')).toBe(true);
28
+ expect(isNoneVariant('1. None')).toBe(true);
29
+ });
30
+
31
+ it('should detect "no issues" phrases', () => {
32
+ expect(isNoneVariant('No blocking issues')).toBe(true);
33
+ expect(isNoneVariant('No blocking issues found')).toBe(true);
34
+ expect(isNoneVariant('No issues')).toBe(true);
35
+ expect(isNoneVariant('No issues found')).toBe(true);
36
+ expect(isNoneVariant('No critical issues')).toBe(true);
37
+ expect(isNoneVariant('No blockers')).toBe(true);
38
+ expect(isNoneVariant('No showstoppers')).toBe(true);
39
+ expect(isNoneVariant('No concerns')).toBe(true);
40
+ expect(isNoneVariant('No significant issues')).toBe(true);
41
+ expect(isNoneVariant('No major blocking issues')).toBe(true);
42
+ });
43
+
44
+ it('should detect "none identified/found" phrases', () => {
45
+ expect(isNoneVariant('None identified')).toBe(true);
46
+ expect(isNoneVariant('None found')).toBe(true);
47
+ expect(isNoneVariant('None detected')).toBe(true);
48
+ expect(isNoneVariant('None at this time')).toBe(true);
49
+ expect(isNoneVariant('None noted')).toBe(true);
50
+ expect(isNoneVariant('None observed')).toBe(true);
51
+ expect(isNoneVariant('None reported')).toBe(true);
52
+ expect(isNoneVariant('None applicable')).toBe(true);
53
+ });
54
+
55
+ it('should detect "there are no" phrases', () => {
56
+ expect(isNoneVariant('There are no blocking issues')).toBe(true);
57
+ expect(isNoneVariant('There are no significant issues')).toBe(true);
58
+ expect(isNoneVariant('There are no major blocking issues')).toBe(true);
59
+ expect(isNoneVariant('There are no critical blockers')).toBe(true);
60
+ });
61
+
62
+ it('should handle empty and whitespace strings', () => {
63
+ expect(isNoneVariant('')).toBe(true);
64
+ expect(isNoneVariant(' ')).toBe(true);
65
+ expect(isNoneVariant('\n')).toBe(true);
66
+ expect(isNoneVariant('\t')).toBe(true);
67
+ });
68
+
69
+ it('should strip trailing punctuation before matching', () => {
70
+ expect(isNoneVariant('None.')).toBe(true);
71
+ expect(isNoneVariant('N/A.')).toBe(true);
72
+ expect(isNoneVariant('No issues!')).toBe(true);
73
+ expect(isNoneVariant('No blocking issues found.')).toBe(true);
74
+ });
75
+ });
76
+
77
+ describe('true negatives (must keep as real issues)', () => {
78
+ it('should keep real blocking issues', () => {
79
+ expect(isNoneVariant('Missing authentication layer')).toBe(false);
80
+ expect(isNoneVariant('SQL injection vulnerability')).toBe(false);
81
+ expect(isNoneVariant('The API has no rate limiting')).toBe(false);
82
+ expect(isNoneVariant('Authentication is not implemented')).toBe(false);
83
+ });
84
+
85
+ it('should keep "No rollback plan" (not issues/concerns/problems/blockers)', () => {
86
+ expect(isNoneVariant('No rollback plan defined')).toBe(false);
87
+ });
88
+
89
+ it('should keep "None of the..." sentences (classic false positive)', () => {
90
+ expect(isNoneVariant('None of the database migrations handle rollback')).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('edge cases', () => {
95
+ it('should reject long plain text (>80 chars) as real issues', () => {
96
+ const longIssue = 'The authentication system does not properly validate JWT tokens which could lead to unauthorized access to protected endpoints';
97
+ expect(longIssue.length).toBeGreaterThan(80);
98
+ expect(isNoneVariant(longIssue)).toBe(false);
99
+ });
100
+
101
+ it('should still detect long none-variant phrases', () => {
102
+ // A "no issues" phrase that happens to be long due to qualifiers
103
+ expect(isNoneVariant('No significant blocking issues or critical concerns identified in this review.')).toBe(true);
104
+ });
105
+ });
106
+ });
107
+
108
+ describe('normalizeIssueList', () => {
109
+ it('should filter out all none-variants from a list', () => {
110
+ const input = [
111
+ 'None',
112
+ 'No blocking issues found',
113
+ 'Missing authentication layer',
114
+ 'N/A',
115
+ 'SQL injection vulnerability',
116
+ ];
117
+ const result = normalizeIssueList(input);
118
+ expect(result).toEqual([
119
+ 'Missing authentication layer',
120
+ 'SQL injection vulnerability',
121
+ ]);
122
+ });
123
+
124
+ it('should handle empty lists', () => {
125
+ expect(normalizeIssueList([])).toEqual([]);
126
+ });
127
+
128
+ it('should filter out empty and whitespace-only items', () => {
129
+ const input = ['', ' ', 'Real issue', '\t'];
130
+ const result = normalizeIssueList(input);
131
+ expect(result).toEqual(['Real issue']);
132
+ });
133
+
134
+ it('should trim items before checking', () => {
135
+ const input = [' None ', ' Missing auth '];
136
+ const result = normalizeIssueList(input);
137
+ expect(result).toEqual(['Missing auth']);
138
+ });
139
+
140
+ it('should preserve order of real issues', () => {
141
+ const input = [
142
+ 'None',
143
+ 'First real issue',
144
+ 'N/A',
145
+ 'Second real issue',
146
+ 'Third real issue',
147
+ ];
148
+ const result = normalizeIssueList(input);
149
+ expect(result).toEqual([
150
+ 'First real issue',
151
+ 'Second real issue',
152
+ 'Third real issue',
153
+ ]);
154
+ });
155
+ });