popeye-cli 2.2.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 (323) 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 +324 -20
  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 +3 -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/auto-recovery.d.ts +56 -0
  71. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  72. package/dist/pipeline/auto-recovery.js +185 -0
  73. package/dist/pipeline/auto-recovery.js.map +1 -0
  74. package/dist/pipeline/change-request.d.ts +39 -0
  75. package/dist/pipeline/change-request.d.ts.map +1 -1
  76. package/dist/pipeline/change-request.js +40 -1
  77. package/dist/pipeline/change-request.js.map +1 -1
  78. package/dist/pipeline/check-runner.d.ts +30 -1
  79. package/dist/pipeline/check-runner.d.ts.map +1 -1
  80. package/dist/pipeline/check-runner.js +122 -1
  81. package/dist/pipeline/check-runner.js.map +1 -1
  82. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  83. package/dist/pipeline/command-resolver.js +33 -2
  84. package/dist/pipeline/command-resolver.js.map +1 -1
  85. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  86. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  87. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  88. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  89. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  90. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  91. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  92. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  93. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  94. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  95. package/dist/pipeline/cr-lifecycle.js +89 -0
  96. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  97. package/dist/pipeline/gate-engine.d.ts +1 -0
  98. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  99. package/dist/pipeline/gate-engine.js +26 -7
  100. package/dist/pipeline/gate-engine.js.map +1 -1
  101. package/dist/pipeline/orchestrator.d.ts +1 -1
  102. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  103. package/dist/pipeline/orchestrator.js +306 -16
  104. package/dist/pipeline/orchestrator.js.map +1 -1
  105. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  106. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  107. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  108. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  109. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  110. package/dist/pipeline/phases/architecture.js +5 -3
  111. package/dist/pipeline/phases/architecture.js.map +1 -1
  112. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  113. package/dist/pipeline/phases/audit.js +5 -3
  114. package/dist/pipeline/phases/audit.js.map +1 -1
  115. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  117. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  118. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  119. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  120. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  123. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  124. package/dist/pipeline/phases/done.d.ts.map +1 -1
  125. package/dist/pipeline/phases/done.js +9 -4
  126. package/dist/pipeline/phases/done.js.map +1 -1
  127. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  128. package/dist/pipeline/phases/intake.js +7 -3
  129. package/dist/pipeline/phases/intake.js.map +1 -1
  130. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  131. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  132. package/dist/pipeline/phases/phase-context.js +3 -1
  133. package/dist/pipeline/phases/phase-context.js.map +1 -1
  134. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  135. package/dist/pipeline/phases/production-gate.js +28 -3
  136. package/dist/pipeline/phases/production-gate.js.map +1 -1
  137. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  138. package/dist/pipeline/phases/qa-validation.js +38 -5
  139. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  140. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  141. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  142. package/dist/pipeline/phases/recovery-loop.js +200 -6
  143. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  144. package/dist/pipeline/phases/review.d.ts.map +1 -1
  145. package/dist/pipeline/phases/review.js +58 -28
  146. package/dist/pipeline/phases/review.js.map +1 -1
  147. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  148. package/dist/pipeline/phases/role-planning.js +18 -2
  149. package/dist/pipeline/phases/role-planning.js.map +1 -1
  150. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  151. package/dist/pipeline/phases/stuck.js +10 -0
  152. package/dist/pipeline/phases/stuck.js.map +1 -1
  153. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  154. package/dist/pipeline/repo-snapshot.js +3 -0
  155. package/dist/pipeline/repo-snapshot.js.map +1 -1
  156. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  157. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  158. package/dist/pipeline/role-execution-adapter.js +22 -7
  159. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  160. package/dist/pipeline/skill-loader.d.ts +19 -0
  161. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  162. package/dist/pipeline/skill-loader.js +22 -0
  163. package/dist/pipeline/skill-loader.js.map +1 -1
  164. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  165. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  166. package/dist/pipeline/skills/coverage-gate.js +143 -0
  167. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  168. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  169. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  170. package/dist/pipeline/skills/usage-registry.js +55 -0
  171. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  172. package/dist/pipeline/strategy-context.d.ts +20 -0
  173. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  174. package/dist/pipeline/strategy-context.js +55 -0
  175. package/dist/pipeline/strategy-context.js.map +1 -0
  176. package/dist/pipeline/type-defs/artifacts.d.ts +25 -5
  177. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  178. package/dist/pipeline/type-defs/artifacts.js +4 -0
  179. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  180. package/dist/pipeline/type-defs/audit.d.ts +25 -13
  181. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  182. package/dist/pipeline/type-defs/checks.d.ts +18 -8
  183. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  184. package/dist/pipeline/type-defs/checks.js +4 -0
  185. package/dist/pipeline/type-defs/checks.js.map +1 -1
  186. package/dist/pipeline/type-defs/packets.d.ts +104 -18
  187. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  188. package/dist/pipeline/type-defs/packets.js +17 -1
  189. package/dist/pipeline/type-defs/packets.js.map +1 -1
  190. package/dist/pipeline/type-defs/state.d.ts +160 -16
  191. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  192. package/dist/pipeline/type-defs/state.js +26 -1
  193. package/dist/pipeline/type-defs/state.js.map +1 -1
  194. package/dist/shared/text-utils.d.ts +23 -0
  195. package/dist/shared/text-utils.d.ts.map +1 -0
  196. package/dist/shared/text-utils.js +66 -0
  197. package/dist/shared/text-utils.js.map +1 -0
  198. package/dist/shared/website-strategy-format.d.ts +18 -0
  199. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  200. package/dist/shared/website-strategy-format.js +47 -0
  201. package/dist/shared/website-strategy-format.js.map +1 -0
  202. package/dist/state/index.d.ts +2 -0
  203. package/dist/state/index.d.ts.map +1 -1
  204. package/dist/state/index.js +57 -8
  205. package/dist/state/index.js.map +1 -1
  206. package/dist/types/consensus.d.ts +1 -0
  207. package/dist/types/consensus.d.ts.map +1 -1
  208. package/dist/types/consensus.js.map +1 -1
  209. package/dist/types/website-strategy.d.ts +1 -1
  210. package/dist/types/workflow.d.ts +447 -0
  211. package/dist/types/workflow.d.ts.map +1 -1
  212. package/dist/types/workflow.js +3 -0
  213. package/dist/types/workflow.js.map +1 -1
  214. package/dist/upgrade/handlers.d.ts.map +1 -1
  215. package/dist/upgrade/handlers.js +6 -3
  216. package/dist/upgrade/handlers.js.map +1 -1
  217. package/dist/workflow/consensus.d.ts.map +1 -1
  218. package/dist/workflow/consensus.js +1 -0
  219. package/dist/workflow/consensus.js.map +1 -1
  220. package/dist/workflow/website-strategy.d.ts.map +1 -1
  221. package/dist/workflow/website-strategy.js +2 -29
  222. package/dist/workflow/website-strategy.js.map +1 -1
  223. package/dist/workflow/website-updater.d.ts.map +1 -1
  224. package/dist/workflow/website-updater.js +3 -2
  225. package/dist/workflow/website-updater.js.map +1 -1
  226. package/package.json +1 -1
  227. package/src/adapters/gemini.ts +51 -6
  228. package/src/adapters/grok.ts +51 -6
  229. package/src/adapters/openai.ts +53 -5
  230. package/src/cli/commands/create.ts +1 -1
  231. package/src/cli/interactive.ts +333 -19
  232. package/src/generators/all.ts +3 -2
  233. package/src/generators/doc-parser.ts +75 -15
  234. package/src/generators/templates/fullstack.ts +1 -1
  235. package/src/generators/templates/website-components.ts +1 -1
  236. package/src/generators/templates/website-config.ts +23 -11
  237. package/src/generators/templates/website-conversion.ts +1 -1
  238. package/src/generators/templates/website-landing.ts +1 -1
  239. package/src/generators/templates/website-layout.ts +491 -23
  240. package/src/generators/templates/website-pricing.ts +1 -1
  241. package/src/generators/templates/website-sections.ts +1 -1
  242. package/src/generators/templates/website-seo.ts +4 -1
  243. package/src/generators/templates/website.ts +3 -0
  244. package/src/generators/website-content-ai.ts +186 -0
  245. package/src/generators/website-content-scanner.ts +113 -1
  246. package/src/generators/website-context.ts +151 -12
  247. package/src/generators/website-debug.ts +26 -0
  248. package/src/generators/website.ts +28 -3
  249. package/src/pipeline/auto-recovery.ts +283 -0
  250. package/src/pipeline/change-request.ts +63 -1
  251. package/src/pipeline/check-runner.ts +141 -2
  252. package/src/pipeline/command-resolver.ts +34 -2
  253. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  254. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  255. package/src/pipeline/cr-lifecycle.ts +103 -0
  256. package/src/pipeline/gate-engine.ts +35 -7
  257. package/src/pipeline/orchestrator.ts +361 -16
  258. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  259. package/src/pipeline/phases/architecture.ts +6 -3
  260. package/src/pipeline/phases/audit.ts +6 -3
  261. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  262. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  263. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  264. package/src/pipeline/phases/done.ts +15 -4
  265. package/src/pipeline/phases/intake.ts +7 -3
  266. package/src/pipeline/phases/phase-context.ts +6 -1
  267. package/src/pipeline/phases/production-gate.ts +41 -3
  268. package/src/pipeline/phases/qa-validation.ts +51 -5
  269. package/src/pipeline/phases/recovery-loop.ts +229 -7
  270. package/src/pipeline/phases/review.ts +73 -30
  271. package/src/pipeline/phases/role-planning.ts +21 -2
  272. package/src/pipeline/phases/stuck.ts +10 -0
  273. package/src/pipeline/repo-snapshot.ts +3 -0
  274. package/src/pipeline/role-execution-adapter.ts +30 -4
  275. package/src/pipeline/skill-loader.ts +33 -0
  276. package/src/pipeline/skills/coverage-gate.ts +199 -0
  277. package/src/pipeline/skills/usage-registry.ts +87 -0
  278. package/src/pipeline/strategy-context.ts +60 -0
  279. package/src/pipeline/type-defs/artifacts.ts +4 -0
  280. package/src/pipeline/type-defs/checks.ts +4 -0
  281. package/src/pipeline/type-defs/packets.ts +18 -1
  282. package/src/pipeline/type-defs/state.ts +26 -1
  283. package/src/shared/text-utils.ts +70 -0
  284. package/src/shared/website-strategy-format.ts +56 -0
  285. package/src/state/index.ts +60 -8
  286. package/src/types/consensus.ts +1 -0
  287. package/src/types/workflow.ts +6 -0
  288. package/src/upgrade/handlers.ts +9 -3
  289. package/src/workflow/consensus.ts +1 -0
  290. package/src/workflow/website-strategy.ts +2 -36
  291. package/src/workflow/website-updater.ts +4 -2
  292. package/tests/adapters/gemini.test.ts +165 -0
  293. package/tests/adapters/grok.test.ts +137 -0
  294. package/tests/adapters/openai.test.ts +128 -0
  295. package/tests/generators/doc-parser.test.ts +88 -9
  296. package/tests/generators/quality-gate.test.ts +19 -3
  297. package/tests/generators/website-components.test.ts +34 -0
  298. package/tests/generators/website-content-ai.test.ts +308 -0
  299. package/tests/generators/website-content-scanner.test.ts +86 -0
  300. package/tests/generators/website-context.test.ts +3 -2
  301. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  302. package/tests/pipeline/auto-recovery.test.ts +337 -0
  303. package/tests/pipeline/change-request.test.ts +70 -0
  304. package/tests/pipeline/command-resolver.test.ts +42 -0
  305. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  306. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  307. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  308. package/tests/pipeline/gate-engine.test.ts +34 -0
  309. package/tests/pipeline/install-check.test.ts +261 -0
  310. package/tests/pipeline/orchestrator.test.ts +1506 -15
  311. package/tests/pipeline/packets/builders.test.ts +29 -6
  312. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  313. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  314. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  315. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  316. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  317. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  318. package/tests/pipeline/strategy-context.test.ts +148 -0
  319. package/tests/shared/text-utils.test.ts +155 -0
  320. package/tests/state/progress-analysis.test.ts +375 -0
  321. package/tests/upgrade/handlers.test.ts +33 -2
  322. package/tests/workflow/consensus.test.ts +6 -0
  323. package/tsconfig.json +1 -1
@@ -100,13 +100,17 @@ describe('PacketBuilders', () => {
100
100
  });
101
101
 
102
102
  describe('buildConsensusPacket', () => {
103
- it('should auto-compute APPROVED when all approve', () => {
103
+ it('should auto-compute APPROVED when all approve with high confidence', () => {
104
104
  const packet = buildConsensusPacket({
105
105
  planPacketRef: makeRef(),
106
- votes: [makeVote('APPROVE', 'r1'), makeVote('APPROVE', 'r2')],
106
+ votes: [
107
+ { ...makeVote('APPROVE', 'r1'), confidence: 0.96 },
108
+ { ...makeVote('APPROVE', 'r2'), confidence: 0.97 },
109
+ ],
107
110
  rules: { threshold: 0.95, quorum: 2, min_reviewers: 2 },
108
111
  });
109
112
 
113
+ // Option B: (1.0*0.96 + 1.0*0.97)/2 = 0.965 >= 0.95
110
114
  expect(packet.consensus_result.approved).toBe(true);
111
115
  expect(packet.consensus_result.score).toBe(1.0);
112
116
  expect(packet.consensus_result.participating_reviewers).toBe(2);
@@ -169,18 +173,37 @@ describe('PacketBuilders', () => {
169
173
 
170
174
  expect(packet.consensus_result.weighted_score).toBeDefined();
171
175
  expect(typeof packet.consensus_result.weighted_score).toBe('number');
172
- expect(packet.consensus_result.weighted_score).toBe(1.0);
176
+ // Option B: (1.0*0.9 + 1.0*0.9)/2 = 0.9
177
+ expect(packet.consensus_result.weighted_score).toBeCloseTo(0.9, 3);
173
178
  });
174
179
 
175
- it('should have weighted_score < 1 for mixed votes (v1.1)', () => {
180
+ it('should have weighted_score < 1 for mixed votes (v1.1, v2.4.2 honest scoring)', () => {
176
181
  const packet = buildConsensusPacket({
177
182
  planPacketRef: makeRef(),
178
183
  votes: [makeVote('APPROVE', 'r1'), makeVote('REJECT', 'r2')],
179
184
  rules: { threshold: 0.5, quorum: 1, min_reviewers: 1 },
180
185
  });
181
186
 
182
- // weighted_score should be 0 since REJECT vote has blocking_issues
183
- expect(packet.consensus_result.weighted_score).toBe(0);
187
+ // v2.4.2: honest score REJECT has blocking_issues but score is not force-zeroed
188
+ // Option B: (1.0*0.9 + 0.0*0.9) / 2 = 0.45
189
+ expect(packet.consensus_result.weighted_score).toBeCloseTo(0.45, 3);
190
+ expect(packet.consensus_result.has_true_blockers).toBe(true);
191
+ });
192
+
193
+ it('consensus_result includes has_true_blockers field (v2.4.2)', () => {
194
+ const packet = buildConsensusPacket({
195
+ planPacketRef: makeRef(),
196
+ votes: [makeVote('APPROVE', 'r1'), makeVote('APPROVE', 'r2')],
197
+ rules: { threshold: 0.5, quorum: 1, min_reviewers: 1 },
198
+ });
199
+ expect(packet.consensus_result.has_true_blockers).toBe(false);
200
+
201
+ const packetWithBlockers = buildConsensusPacket({
202
+ planPacketRef: makeRef(),
203
+ votes: [makeVote('APPROVE', 'r1'), makeVote('REJECT', 'r2')],
204
+ rules: { threshold: 0.5, quorum: 1, min_reviewers: 1 },
205
+ });
206
+ expect(packetWithBlockers.consensus_result.has_true_blockers).toBe(true);
184
207
  });
185
208
 
186
209
  it('should link to plan packet', () => {
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Role planning strategy injection tests.
3
+ * Verifies that WEBSITE_PROGRAMMER, MARKETING_EXPERT, SOCIAL_EXPERT
4
+ * get strategy context in their planning prompts, and other roles do not.
5
+ */
6
+
7
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
+ import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { runRolePlanning } from '../../../src/pipeline/phases/role-planning.js';
11
+ import type { PhaseContext } from '../../../src/pipeline/phases/phase-context.js';
12
+ import type { PipelineRole, ArtifactEntry } from '../../../src/pipeline/types.js';
13
+ import { createDefaultPipelineState } from '../../../src/pipeline/types.js';
14
+ import { SkillUsageRegistry } from '../../../src/pipeline/skills/usage-registry.js';
15
+
16
+ const TEST_DIR = join(process.cwd(), 'tmp-role-planning-strategy-test');
17
+
18
+ function makeValidStrategy() {
19
+ return {
20
+ icp: { primaryPersona: 'developers', painPoints: ['slow builds'], goals: ['fast CI'], objections: ['cost'] },
21
+ positioning: { category: 'DevTools', differentiators: ['speed'], valueProposition: 'Build faster', proofPoints: ['10x'] },
22
+ messaging: { headline: 'Build Faster', subheadline: 'CI that works', elevatorPitch: 'Fast CI', longDescription: 'A fast CI platform' },
23
+ seoStrategy: { primaryKeywords: ['CI'], secondaryKeywords: ['build'], longTailKeywords: ['fast CI'], titleTemplates: {}, metaDescriptions: {} },
24
+ siteArchitecture: {
25
+ pages: [{ path: '/', title: 'Home', purpose: 'Landing', pageType: 'landing', sections: ['hero'], seoKeywords: ['CI'], conversionGoal: 'signup' }],
26
+ navigation: [{ label: 'Home', href: '/' }],
27
+ footerSections: [{ title: 'Links', links: [{ label: 'Home', href: '/' }] }],
28
+ },
29
+ conversionStrategy: {
30
+ primaryCta: { text: 'Get Started', href: '/signup' },
31
+ secondaryCta: { text: 'Learn More', href: '/docs' },
32
+ trustSignals: ['SOC2'],
33
+ socialProof: ['1000+ teams'],
34
+ leadCapture: 'webhook',
35
+ },
36
+ competitiveContext: { category: 'CI/CD', competitors: ['CircleCI'], differentiators: ['speed'] },
37
+ };
38
+ }
39
+
40
+ // Capture all prompts passed to executePrompt
41
+ let capturedPrompts: string[] = [];
42
+
43
+ vi.mock('../../../src/adapters/claude.js', () => ({
44
+ executePrompt: vi.fn(async (prompt: string) => {
45
+ capturedPrompts.push(prompt);
46
+ return { response: 'Mocked plan output' };
47
+ }),
48
+ }));
49
+
50
+ function makeMockSkill(role: string) {
51
+ return {
52
+ role,
53
+ version: '1.0',
54
+ systemPrompt: `You are ${role}. Follow best practices.`,
55
+ required_outputs: [],
56
+ constraints: ['Stay in scope'],
57
+ tools: [],
58
+ };
59
+ }
60
+
61
+ function makeArtifact(type: string, path: string): ArtifactEntry {
62
+ return {
63
+ id: `art-${type}`,
64
+ type: type as any,
65
+ phase: 'ARCHITECTURE',
66
+ version: 1,
67
+ path,
68
+ sha256: 'abc',
69
+ timestamp: new Date().toISOString(),
70
+ immutable: true,
71
+ content_type: 'markdown',
72
+ group_id: 'g1',
73
+ };
74
+ }
75
+
76
+ function makePhaseContext(activeRoles: PipelineRole[]): PhaseContext {
77
+ const pipeline = createDefaultPipelineState();
78
+ pipeline.activeRoles = activeRoles;
79
+
80
+ // Add architecture and master plan artifacts
81
+ const archPath = 'docs/architecture.md';
82
+ const planPath = 'docs/master-plan.md';
83
+ pipeline.artifacts.push(makeArtifact('architecture', archPath));
84
+ pipeline.artifacts.push(makeArtifact('master_plan', planPath));
85
+
86
+ // Write artifact files
87
+ writeFileSync(join(TEST_DIR, archPath), '# Architecture\nMicroservices');
88
+ writeFileSync(join(TEST_DIR, planPath), '# Master Plan\nBuild a CI tool');
89
+
90
+ const events: any[] = [];
91
+ const registry = new SkillUsageRegistry(events);
92
+
93
+ let artifactCounter = 0;
94
+
95
+ return {
96
+ pipeline,
97
+ projectDir: TEST_DIR,
98
+ state: {} as any,
99
+ skillLoader: {
100
+ loadSkill: (role: string) => makeMockSkill(role),
101
+ loadSkillWithMeta: (role: string) => ({
102
+ definition: makeMockSkill(role),
103
+ meta: { source: 'defaults' as const, version: '1.0' },
104
+ }),
105
+ listSkills: () => [],
106
+ } as any,
107
+ artifactManager: {
108
+ createAndStoreText: (_type: string, content: string, _phase: string) => {
109
+ artifactCounter++;
110
+ return makeArtifact('role_plan', `docs/role-plans/plan-${artifactCounter}.md`);
111
+ },
112
+ } as any,
113
+ gateEngine: {} as any,
114
+ consensusRunner: {} as any,
115
+ skillUsageRegistry: registry,
116
+ };
117
+ }
118
+
119
+ beforeEach(() => {
120
+ capturedPrompts = [];
121
+ mkdirSync(join(TEST_DIR, 'docs', 'role-plans'), { recursive: true });
122
+ mkdirSync(join(TEST_DIR, '.popeye'), { recursive: true });
123
+ });
124
+
125
+ afterEach(() => {
126
+ if (existsSync(TEST_DIR)) {
127
+ rmSync(TEST_DIR, { recursive: true, force: true });
128
+ }
129
+ vi.clearAllMocks();
130
+ });
131
+
132
+ describe('role-planning strategy injection', () => {
133
+ it('WEBSITE_PROGRAMMER planning prompt includes strategy when file exists', async () => {
134
+ writeFileSync(
135
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
136
+ JSON.stringify(makeValidStrategy()),
137
+ );
138
+
139
+ const ctx = makePhaseContext(['WEBSITE_PROGRAMMER']);
140
+ await runRolePlanning(ctx);
141
+
142
+ expect(capturedPrompts).toHaveLength(1);
143
+ expect(capturedPrompts[0]).toContain('Website Marketing Strategy');
144
+ expect(capturedPrompts[0]).toContain('Target Customer');
145
+ expect(capturedPrompts[0]).toContain('developers');
146
+ });
147
+
148
+ it('MARKETING_EXPERT planning prompt includes strategy', async () => {
149
+ writeFileSync(
150
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
151
+ JSON.stringify(makeValidStrategy()),
152
+ );
153
+
154
+ const ctx = makePhaseContext(['MARKETING_EXPERT']);
155
+ await runRolePlanning(ctx);
156
+
157
+ expect(capturedPrompts).toHaveLength(1);
158
+ expect(capturedPrompts[0]).toContain('Website Marketing Strategy');
159
+ });
160
+
161
+ it('DB_EXPERT planning prompt does NOT include strategy', async () => {
162
+ writeFileSync(
163
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
164
+ JSON.stringify(makeValidStrategy()),
165
+ );
166
+
167
+ const ctx = makePhaseContext(['DB_EXPERT']);
168
+ await runRolePlanning(ctx);
169
+
170
+ expect(capturedPrompts).toHaveLength(1);
171
+ expect(capturedPrompts[0]).not.toContain('Website Marketing Strategy');
172
+ expect(capturedPrompts[0]).not.toContain('Target Customer');
173
+ });
174
+
175
+ it('missing strategy file does not crash planning', async () => {
176
+ // No strategy file written
177
+ const ctx = makePhaseContext(['WEBSITE_PROGRAMMER', 'BACKEND_PROGRAMMER']);
178
+ const result = await runRolePlanning(ctx);
179
+
180
+ expect(result.success).toBe(true);
181
+ expect(capturedPrompts).toHaveLength(2);
182
+ // Neither prompt should contain strategy
183
+ for (const prompt of capturedPrompts) {
184
+ expect(prompt).not.toContain('Website Marketing Strategy');
185
+ }
186
+ });
187
+
188
+ it('records strategy_context usage for strategy roles only', async () => {
189
+ writeFileSync(
190
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
191
+ JSON.stringify(makeValidStrategy()),
192
+ );
193
+
194
+ const ctx = makePhaseContext(['WEBSITE_PROGRAMMER', 'BACKEND_PROGRAMMER']);
195
+ await runRolePlanning(ctx);
196
+
197
+ const events = ctx.skillUsageRegistry.getEvents();
198
+ const strategyEvents = events.filter((e) => e.used_as === 'strategy_context');
199
+ expect(strategyEvents).toHaveLength(1);
200
+ expect(strategyEvents[0].role).toBe('WEBSITE_PROGRAMMER');
201
+ expect(strategyEvents[0].phase).toBe('ROLE_PLANNING');
202
+ expect(strategyEvents[0].skill_source).toBe('disk');
203
+ });
204
+ });
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Pipeline Persistence tests — verify PipelineState round-trips through
3
+ * ProjectStateSchema, survives saveState/loadState, and merges via updateState.
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import { promises as fs } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { tmpdir } from 'node:os';
10
+
11
+ import { ProjectStateSchema } from '../../src/types/workflow.js';
12
+ import type { ProjectState } from '../../src/types/workflow.js';
13
+ import { PipelineStateSchema, createDefaultPipelineState } from '../../src/pipeline/types.js';
14
+ import type { PipelineState } from '../../src/pipeline/types.js';
15
+ import { saveState, loadState } from '../../src/state/persistence.js';
16
+ import { updateState } from '../../src/state/index.js';
17
+
18
+ /** Minimal valid ProjectState for testing (no pipeline field) */
19
+ function makeBaseState(overrides: Partial<ProjectState> = {}): ProjectState {
20
+ const now = new Date().toISOString();
21
+ return {
22
+ id: 'test-id',
23
+ name: 'test-project',
24
+ idea: 'A test idea',
25
+ language: 'python',
26
+ openaiModel: 'gpt-4o',
27
+ phase: 'plan',
28
+ status: 'pending',
29
+ milestones: [],
30
+ currentMilestone: null,
31
+ currentTask: null,
32
+ consensusHistory: [],
33
+ createdAt: now,
34
+ updatedAt: now,
35
+ ...overrides,
36
+ };
37
+ }
38
+
39
+ /** Build a populated PipelineState for round-trip testing */
40
+ function makePopulatedPipeline(): PipelineState {
41
+ const base = createDefaultPipelineState();
42
+ return {
43
+ ...base,
44
+ pipelinePhase: 'CONSENSUS_ROLE_PLANS',
45
+ recoveryCount: 2,
46
+ artifacts: [
47
+ {
48
+ id: 'art-1',
49
+ type: 'master_plan',
50
+ phase: 'CONSENSUS_MASTER_PLAN',
51
+ version: 1,
52
+ path: 'docs/plans/master-plan.md',
53
+ sha256: 'abc123def456',
54
+ timestamp: new Date().toISOString(),
55
+ immutable: true,
56
+ content_type: 'markdown',
57
+ group_id: 'group-master',
58
+ },
59
+ ],
60
+ gateResults: {
61
+ INTAKE: {
62
+ phase: 'INTAKE',
63
+ pass: true,
64
+ blockers: [],
65
+ missingArtifacts: [],
66
+ failedChecks: [],
67
+ timestamp: new Date().toISOString(),
68
+ },
69
+ },
70
+ gateChecks: {
71
+ INTAKE: [
72
+ {
73
+ check_type: 'build',
74
+ status: 'pass',
75
+ command: 'npm run build',
76
+ exit_code: 0,
77
+ duration_ms: 1200,
78
+ timestamp: new Date().toISOString(),
79
+ },
80
+ ],
81
+ },
82
+ activeRoles: ['DISPATCHER', 'ARCHITECT'],
83
+ failedPhase: 'ARCHITECTURE',
84
+ skillUsageEvents: [
85
+ {
86
+ role: 'ARCHITECT',
87
+ phase: 'ARCHITECTURE',
88
+ used_as: 'system_prompt',
89
+ skill_source: 'defaults',
90
+ timestamp: new Date().toISOString(),
91
+ },
92
+ ],
93
+ };
94
+ }
95
+
96
+ describe('Pipeline Persistence', () => {
97
+ // ─── Test 1: Round-trip through ProjectStateSchema ──────────────
98
+
99
+ it('should round-trip pipeline state through ProjectStateSchema', () => {
100
+ const pipeline = makePopulatedPipeline();
101
+ const state = makeBaseState({ pipeline });
102
+
103
+ const result = ProjectStateSchema.safeParse(state);
104
+ expect(result.success).toBe(true);
105
+ if (!result.success) return;
106
+
107
+ expect(result.data.pipeline).toBeDefined();
108
+ expect(result.data.pipeline!.pipelinePhase).toBe('CONSENSUS_ROLE_PLANS');
109
+ expect(result.data.pipeline!.artifacts).toHaveLength(1);
110
+ expect(result.data.pipeline!.recoveryCount).toBe(2);
111
+ });
112
+
113
+ // ─── Test 2: Backward compatibility — legacy state without pipeline ─
114
+
115
+ it('should load legacy state without pipeline as undefined', () => {
116
+ const state = makeBaseState();
117
+ // Explicitly ensure no pipeline field
118
+ delete (state as Record<string, unknown>).pipeline;
119
+
120
+ const result = ProjectStateSchema.safeParse(state);
121
+ expect(result.success).toBe(true);
122
+ if (!result.success) return;
123
+
124
+ expect(result.data.pipeline).toBeUndefined();
125
+ });
126
+
127
+ // ─── Test 3: All subfields survive Zod round-trip ──────────────
128
+
129
+ it('should preserve all pipeline subfields through Zod round-trip', () => {
130
+ const pipeline = makePopulatedPipeline();
131
+ const state = makeBaseState({ pipeline });
132
+
133
+ const result = ProjectStateSchema.safeParse(state);
134
+ expect(result.success).toBe(true);
135
+ if (!result.success) return;
136
+
137
+ const p = result.data.pipeline!;
138
+
139
+ // Gate results preserved
140
+ expect(p.gateResults['INTAKE']).toBeDefined();
141
+ expect(p.gateResults['INTAKE'].pass).toBe(true);
142
+
143
+ // Gate checks preserved
144
+ expect(p.gateChecks['INTAKE']).toHaveLength(1);
145
+ expect(p.gateChecks['INTAKE'][0].check_type).toBe('build');
146
+ expect(p.gateChecks['INTAKE'][0].status).toBe('pass');
147
+
148
+ // Active roles preserved
149
+ expect(p.activeRoles).toEqual(['DISPATCHER', 'ARCHITECT']);
150
+
151
+ // Skill usage events preserved
152
+ expect(p.skillUsageEvents).toHaveLength(1);
153
+ expect(p.skillUsageEvents![0].role).toBe('ARCHITECT');
154
+ expect(p.skillUsageEvents![0].used_as).toBe('system_prompt');
155
+
156
+ // Failed phase preserved
157
+ expect(p.failedPhase).toBe('ARCHITECTURE');
158
+
159
+ // No inflation — constitutionHash stays empty string
160
+ expect(p.constitutionHash).toBe('');
161
+ });
162
+
163
+ // ─── Test 4 & 5: saveState/loadState and updateState ───────────
164
+ // These use disk I/O, so we create a temp dir
165
+
166
+ let tmpDir: string;
167
+
168
+ beforeEach(async () => {
169
+ tmpDir = await fs.mkdtemp(path.join(tmpdir(), 'popeye-persist-test-'));
170
+ });
171
+
172
+ afterEach(async () => {
173
+ try {
174
+ await fs.rm(tmpDir, { recursive: true, force: true });
175
+ } catch {
176
+ // cleanup best-effort
177
+ }
178
+ });
179
+
180
+ it('should persist and load pipeline state via saveState/loadState', async () => {
181
+ const pipeline = makePopulatedPipeline();
182
+ const state = makeBaseState({ pipeline });
183
+
184
+ await saveState(tmpDir, state);
185
+ const loaded = await loadState(tmpDir);
186
+
187
+ expect(loaded).not.toBeNull();
188
+ expect(loaded!.pipeline).toBeDefined();
189
+ expect(loaded!.pipeline!.pipelinePhase).toBe('CONSENSUS_ROLE_PLANS');
190
+ expect(loaded!.pipeline!.artifacts).toHaveLength(1);
191
+ expect(loaded!.pipeline!.artifacts[0].type).toBe('master_plan');
192
+ expect(loaded!.pipeline!.recoveryCount).toBe(2);
193
+ expect(loaded!.pipeline!.failedPhase).toBe('ARCHITECTURE');
194
+ });
195
+
196
+ it('should merge pipeline update via updateState without losing other fields', async () => {
197
+ const pipeline = makePopulatedPipeline();
198
+ const state = makeBaseState({
199
+ pipeline,
200
+ specification: 'original spec',
201
+ });
202
+
203
+ // Save initial state
204
+ await saveState(tmpDir, state);
205
+
206
+ // Update only the pipeline (advance phase, bump recovery)
207
+ const updatedPipeline: PipelineState = {
208
+ ...pipeline,
209
+ pipelinePhase: 'IMPLEMENTATION',
210
+ recoveryCount: 0,
211
+ };
212
+
213
+ const result = await updateState(tmpDir, { pipeline: updatedPipeline });
214
+
215
+ // Pipeline was updated
216
+ expect(result.pipeline).toBeDefined();
217
+ expect(result.pipeline!.pipelinePhase).toBe('IMPLEMENTATION');
218
+ expect(result.pipeline!.recoveryCount).toBe(0);
219
+
220
+ // Other fields preserved
221
+ expect(result.specification).toBe('original spec');
222
+ expect(result.name).toBe('test-project');
223
+ expect(result.id).toBe('test-id');
224
+
225
+ // Verify on disk too
226
+ const loaded = await loadState(tmpDir);
227
+ expect(loaded!.pipeline!.pipelinePhase).toBe('IMPLEMENTATION');
228
+ expect(loaded!.specification).toBe('original spec');
229
+ });
230
+ });