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
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Recovery guidance + micro-plan tests (v2.4.6).
3
+ * Tests QA fix directive injection, marker replacement, and
4
+ * buildRecoveryMicroPlan risk heuristics without LLM calls.
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { createDefaultPipelineState } from '../../src/pipeline/types.js';
9
+ import type { PipelinePhase, PipelineState, GateCheckResult } from '../../src/pipeline/types.js';
10
+
11
+ // ─── Helpers: replicate the guidance injection logic from recovery-loop.ts ──
12
+
13
+ /**
14
+ * Simulates the QA fix directive injection (step 2c in recovery-loop.ts).
15
+ * Extracted here so we can test it without running the full phase handler.
16
+ */
17
+ function injectQaFixDirective(
18
+ pipeline: PipelineState,
19
+ failedPhase: PipelinePhase | undefined,
20
+ failedChecks: GateCheckResult[],
21
+ ): void {
22
+ if (failedPhase === 'QA_VALIDATION' || failedPhase === 'PRODUCTION_GATE'
23
+ || failedPhase === 'IMPLEMENTATION') {
24
+ const failedCheckDetails = failedChecks
25
+ .filter((c) => c.status === 'fail')
26
+ .map((c) => [
27
+ `**${c.check_type}** (exit code ${c.exit_code}):`,
28
+ `Command: \`${c.command}\``,
29
+ c.stderr_summary ? c.stderr_summary.slice(0, 500) : 'No stderr captured',
30
+ ].join('\n'))
31
+ .join('\n\n');
32
+
33
+ if (failedCheckDetails) {
34
+ const existing = pipeline.sessionGuidance ?? '';
35
+ const marker = '--- QA FIX DIRECTIVE ---';
36
+ const base = existing.includes(marker)
37
+ ? existing.slice(0, existing.indexOf(marker)).trim()
38
+ : existing;
39
+ const directive = [
40
+ `Fix the following failures (recovery iteration ${pipeline.recoveryCount}):`,
41
+ '',
42
+ failedCheckDetails,
43
+ '',
44
+ 'Apply targeted fixes only. Do not rewrite code that already works.',
45
+ ].join('\n');
46
+ pipeline.sessionGuidance = [base, '', marker, directive.slice(0, 3000)]
47
+ .join('\n').trim();
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Replicates buildRecoveryMicroPlan from recovery-loop.ts for testing.
54
+ */
55
+ function buildRecoveryMicroPlan(
56
+ rcaResponse: string,
57
+ failedChecks: GateCheckResult[],
58
+ recoveryCount: number,
59
+ ): { plan: string; needsConsensus: boolean; riskLevel: 'low' | 'medium' | 'high' } {
60
+ const stderrLines = failedChecks
61
+ .filter((c) => c.status === 'fail')
62
+ .map((c) => c.stderr_summary ?? '')
63
+ .join('\n');
64
+
65
+ const combined = rcaResponse + '\n' + stderrLines;
66
+ const combinedLower = combined.toLowerCase();
67
+
68
+ const touchesSchema = /schema|migration|prisma|drizzle|typeorm|knex/.test(combinedLower);
69
+ const touchesDeps = /package\.json|requirements\.txt|dependency|npm install|pip install/
70
+ .test(combinedLower);
71
+ const touchesConfig =
72
+ /tsconfig\.json|vite\.config|jest\.config|vitest\.config|docker-compose|\.github\/workflows/
73
+ .test(combinedLower);
74
+ const touchesApi = /api route|endpoint|public api|breaking change/.test(combinedLower);
75
+
76
+ const mentionedFiles = combined.match(/[\w/.-]+\.(ts|tsx|js|jsx|py|sql|prisma)/gi) ?? [];
77
+ const uniqueFiles = [...new Set(mentionedFiles)];
78
+
79
+ let riskLevel: 'low' | 'medium' | 'high' = 'low';
80
+ if (touchesApi || uniqueFiles.length > 5) riskLevel = 'high';
81
+ else if (touchesSchema || touchesDeps || touchesConfig) riskLevel = 'medium';
82
+
83
+ const needsConsensus = riskLevel !== 'low' && recoveryCount >= 2;
84
+
85
+ const plan = [
86
+ `# Recovery Fix Plan (iteration ${recoveryCount})`,
87
+ '',
88
+ `**Risk level:** ${riskLevel}`,
89
+ `**Files likely affected:** ${uniqueFiles.length > 0 ? uniqueFiles.join(', ') : 'unknown'}`,
90
+ `**Consensus required:** ${needsConsensus ? 'yes' : 'no'}`,
91
+ '',
92
+ '## Root Cause Summary',
93
+ rcaResponse.slice(0, 800),
94
+ '',
95
+ '## Test Failures',
96
+ stderrLines.slice(0, 1000) || 'No stderr captured',
97
+ '',
98
+ '## Fix Checklist',
99
+ '- [ ] Address root cause identified above',
100
+ '- [ ] Fix failing test assertions',
101
+ '- [ ] Verify no regressions in passing tests',
102
+ touchesSchema ? '- [ ] Review schema/migration changes for correctness' : '',
103
+ touchesDeps ? '- [ ] Verify dependency changes are necessary and compatible' : '',
104
+ ].filter(Boolean).join('\n');
105
+
106
+ return { plan, needsConsensus, riskLevel };
107
+ }
108
+
109
+ /**
110
+ * Simulates the micro-plan injection (step 3d in recovery-loop.ts).
111
+ */
112
+ function injectMicroPlan(pipeline: PipelineState, microPlanText: string): void {
113
+ const existing = pipeline.sessionGuidance ?? '';
114
+ const markers = ['--- QA FIX DIRECTIVE ---', '--- RECOVERY FIX PLAN ---'];
115
+ let base = existing;
116
+ for (const m of markers) {
117
+ if (base.includes(m)) base = base.slice(0, base.indexOf(m)).trim();
118
+ }
119
+ pipeline.sessionGuidance = [
120
+ base, '', '--- RECOVERY FIX PLAN ---', microPlanText.slice(0, 3000),
121
+ ].join('\n').trim();
122
+ }
123
+
124
+ // ─── Tests ──────────────────────────────────────────────
125
+
126
+ describe('Recovery guidance (v2.4.6)', () => {
127
+ describe('QA fix directive injection', () => {
128
+ it('should inject QA FIX DIRECTIVE marker with stderr into sessionGuidance', () => {
129
+ const pipeline = createDefaultPipelineState();
130
+ pipeline.recoveryCount = 1;
131
+
132
+ const failedChecks: GateCheckResult[] = [
133
+ {
134
+ check_type: 'test',
135
+ status: 'fail',
136
+ command: 'npm test',
137
+ exit_code: 1,
138
+ stderr_summary: 'FAIL src/utils.test.ts\nExpected 3, received 5',
139
+ duration_ms: 1200,
140
+ timestamp: new Date().toISOString(),
141
+ },
142
+ ];
143
+
144
+ injectQaFixDirective(pipeline, 'QA_VALIDATION', failedChecks);
145
+
146
+ expect(pipeline.sessionGuidance).toContain('--- QA FIX DIRECTIVE ---');
147
+ expect(pipeline.sessionGuidance).toContain('FAIL src/utils.test.ts');
148
+ expect(pipeline.sessionGuidance).toContain('exit code 1');
149
+ expect(pipeline.sessionGuidance).toContain('Apply targeted fixes only');
150
+ });
151
+
152
+ it('should replace (not accumulate) directive on repeated failures', () => {
153
+ const pipeline = createDefaultPipelineState();
154
+ pipeline.recoveryCount = 1;
155
+
156
+ const firstChecks: GateCheckResult[] = [
157
+ {
158
+ check_type: 'test',
159
+ status: 'fail',
160
+ command: 'npm test',
161
+ exit_code: 1,
162
+ stderr_summary: 'First error',
163
+ duration_ms: 500,
164
+ timestamp: '',
165
+ },
166
+ ];
167
+
168
+ const secondChecks: GateCheckResult[] = [
169
+ {
170
+ check_type: 'test',
171
+ status: 'fail',
172
+ command: 'npm test',
173
+ exit_code: 1,
174
+ stderr_summary: 'Second error',
175
+ duration_ms: 500,
176
+ timestamp: '',
177
+ },
178
+ ];
179
+
180
+ // Inject first
181
+ injectQaFixDirective(pipeline, 'QA_VALIDATION', firstChecks);
182
+ expect(pipeline.sessionGuidance).toContain('First error');
183
+
184
+ // Inject second — should replace, not accumulate
185
+ pipeline.recoveryCount = 2;
186
+ injectQaFixDirective(pipeline, 'QA_VALIDATION', secondChecks);
187
+
188
+ const markerCount = (pipeline.sessionGuidance ?? '').split('--- QA FIX DIRECTIVE ---').length - 1;
189
+ expect(markerCount).toBe(1);
190
+ expect(pipeline.sessionGuidance).toContain('Second error');
191
+ });
192
+ });
193
+
194
+ describe('buildRecoveryMicroPlan risk heuristics', () => {
195
+ it('should return low risk for simple test failures', () => {
196
+ const result = buildRecoveryMicroPlan(
197
+ 'The test assertion in utils.ts expects 3 but received 5. Fix the calculation.',
198
+ [{ check_type: 'test', status: 'fail', command: 'npm test', exit_code: 1, stderr_summary: 'Expected 3, got 5', duration_ms: 500, timestamp: '' }],
199
+ 1,
200
+ );
201
+
202
+ expect(result.riskLevel).toBe('low');
203
+ expect(result.needsConsensus).toBe(false);
204
+ expect(result.plan).toContain('iteration 1');
205
+ expect(result.plan).toContain('utils.ts');
206
+ });
207
+
208
+ it('should return medium risk for schema/migration changes', () => {
209
+ const result = buildRecoveryMicroPlan(
210
+ 'The schema migration for the users table is missing the new column. Update the prisma schema.',
211
+ [{ check_type: 'test', status: 'fail', command: 'npm test', exit_code: 1, stderr_summary: 'Column not found', duration_ms: 500, timestamp: '' }],
212
+ 1,
213
+ );
214
+
215
+ expect(result.riskLevel).toBe('medium');
216
+ // recoveryCount=1 < 2, so no consensus yet
217
+ expect(result.needsConsensus).toBe(false);
218
+ });
219
+
220
+ it('should return high risk for API/endpoint changes or >5 files', () => {
221
+ const result = buildRecoveryMicroPlan(
222
+ 'The public API endpoint /api/users returns wrong status. This is a breaking change affecting a.ts, b.ts, c.ts, d.ts, e.ts, f.ts',
223
+ [{ check_type: 'test', status: 'fail', command: 'npm test', exit_code: 1, stderr_summary: 'API test failed', duration_ms: 500, timestamp: '' }],
224
+ 3,
225
+ );
226
+
227
+ expect(result.riskLevel).toBe('high');
228
+ // High risk + recoveryCount=3 >= 2 => consensus needed
229
+ expect(result.needsConsensus).toBe(true);
230
+ });
231
+
232
+ it('should require consensus for medium risk on second+ attempt', () => {
233
+ const result = buildRecoveryMicroPlan(
234
+ 'Need to update the schema and migration files.',
235
+ [{ check_type: 'test', status: 'fail', command: 'npm test', exit_code: 1, duration_ms: 500, timestamp: '' }],
236
+ 2,
237
+ );
238
+
239
+ expect(result.riskLevel).toBe('medium');
240
+ expect(result.needsConsensus).toBe(true);
241
+ });
242
+ });
243
+
244
+ describe('Micro-plan marker replaces QA marker', () => {
245
+ it('should replace QA FIX DIRECTIVE with RECOVERY FIX PLAN after both run', () => {
246
+ const pipeline = createDefaultPipelineState();
247
+ pipeline.recoveryCount = 1;
248
+ pipeline.sessionGuidance = 'Some initial guidance';
249
+
250
+ // Step 1: QA fix directive is injected (simulating step 2c)
251
+ const failedChecks: GateCheckResult[] = [
252
+ {
253
+ check_type: 'test',
254
+ status: 'fail',
255
+ command: 'npm test',
256
+ exit_code: 1,
257
+ stderr_summary: 'Test failed',
258
+ duration_ms: 500,
259
+ timestamp: '',
260
+ },
261
+ ];
262
+ injectQaFixDirective(pipeline, 'QA_VALIDATION', failedChecks);
263
+ expect(pipeline.sessionGuidance).toContain('--- QA FIX DIRECTIVE ---');
264
+
265
+ // Step 2: Micro-plan replaces it (simulating step 3d)
266
+ const microPlan = buildRecoveryMicroPlan(
267
+ 'Root cause: off-by-one error in utils.ts',
268
+ failedChecks,
269
+ 1,
270
+ );
271
+ injectMicroPlan(pipeline, microPlan.plan);
272
+
273
+ // Final guidance should have RECOVERY FIX PLAN, not QA FIX DIRECTIVE
274
+ expect(pipeline.sessionGuidance).toContain('--- RECOVERY FIX PLAN ---');
275
+ expect(pipeline.sessionGuidance).not.toContain('--- QA FIX DIRECTIVE ---');
276
+ // Initial guidance should be preserved
277
+ expect(pipeline.sessionGuidance).toContain('Some initial guidance');
278
+ });
279
+ });
280
+ });
@@ -5,6 +5,7 @@
5
5
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
6
  import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
+ import type { PipelineRole } from '../../src/pipeline/types.js';
8
9
  import {
9
10
  buildRoleExecutionContext,
10
11
  executeWithRoleContext,
@@ -252,6 +253,7 @@ describe('buildAllRoleContexts', () => {
252
253
 
253
254
  const mockSkillLoader = {
254
255
  loadSkill: (role: string) => makeMockSkill(role),
256
+ loadSkillWithMeta: (role: string) => ({ definition: makeMockSkill(role), meta: { source: 'defaults' as const } }),
255
257
  listSkills: () => [],
256
258
  };
257
259
 
@@ -276,6 +278,7 @@ describe('buildAllRoleContexts', () => {
276
278
 
277
279
  const mockSkillLoader = {
278
280
  loadSkill: (role: string) => makeMockSkill(role),
281
+ loadSkillWithMeta: (role: string) => ({ definition: makeMockSkill(role), meta: { source: 'defaults' as const } }),
279
282
  listSkills: () => [],
280
283
  };
281
284
 
@@ -290,6 +293,7 @@ describe('buildAllRoleContexts', () => {
290
293
 
291
294
  const mockSkillLoader = {
292
295
  loadSkill: (role: string) => makeMockSkill(role),
296
+ loadSkillWithMeta: (role: string) => ({ definition: makeMockSkill(role), meta: { source: 'defaults' as const } }),
293
297
  listSkills: () => [],
294
298
  };
295
299
 
@@ -297,3 +301,87 @@ describe('buildAllRoleContexts', () => {
297
301
  expect(contexts.size).toBe(0);
298
302
  });
299
303
  });
304
+
305
+ describe('strategy injection into execution prompts', () => {
306
+ function makeValidStrategy() {
307
+ return {
308
+ icp: { primaryPersona: 'developers', painPoints: ['slow builds'], goals: ['fast CI'], objections: ['cost'] },
309
+ positioning: { category: 'DevTools', differentiators: ['speed'], valueProposition: 'Build faster', proofPoints: ['10x'] },
310
+ messaging: { headline: 'Build Faster', subheadline: 'CI that works', elevatorPitch: 'Fast CI', longDescription: 'A fast CI platform' },
311
+ seoStrategy: { primaryKeywords: ['CI'], secondaryKeywords: ['build'], longTailKeywords: ['fast CI'], titleTemplates: {}, metaDescriptions: {} },
312
+ siteArchitecture: {
313
+ pages: [{ path: '/', title: 'Home', purpose: 'Landing', pageType: 'landing', sections: ['hero'], seoKeywords: ['CI'], conversionGoal: 'signup' }],
314
+ navigation: [{ label: 'Home', href: '/' }],
315
+ footerSections: [{ title: 'Links', links: [{ label: 'Home', href: '/' }] }],
316
+ },
317
+ conversionStrategy: {
318
+ primaryCta: { text: 'Get Started', href: '/signup' },
319
+ secondaryCta: { text: 'Learn More', href: '/docs' },
320
+ trustSignals: ['SOC2'],
321
+ socialProof: ['1000+ teams'],
322
+ leadCapture: 'webhook',
323
+ },
324
+ competitiveContext: { category: 'CI/CD', competitors: ['CircleCI'], differentiators: ['speed'] },
325
+ };
326
+ }
327
+
328
+ it('WEBSITE_PROGRAMMER system prompt includes strategy when file exists', () => {
329
+ mkdirSync(join(TEST_DIR, '.popeye'), { recursive: true });
330
+ writeFileSync(
331
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
332
+ JSON.stringify(makeValidStrategy()),
333
+ );
334
+
335
+ const artifact = makeRolePlanArtifact('WEBSITE_PROGRAMMER');
336
+ writeFileSync(join(TEST_DIR, artifact.path), '# WEBSITE_PROGRAMMER Plan\n## Tasks\n- Build site');
337
+
338
+ const ctx = buildRoleExecutionContext(
339
+ 'WEBSITE_PROGRAMMER',
340
+ makeMockSkill('WEBSITE_PROGRAMMER'),
341
+ artifact,
342
+ TEST_DIR,
343
+ );
344
+
345
+ expect(ctx.systemPrompt).toContain('Website Marketing Strategy');
346
+ expect(ctx.systemPrompt).toContain('Target Customer');
347
+ expect(ctx.systemPrompt).toContain('developers');
348
+ });
349
+
350
+ it('BACKEND_PROGRAMMER system prompt does NOT include strategy', () => {
351
+ mkdirSync(join(TEST_DIR, '.popeye'), { recursive: true });
352
+ writeFileSync(
353
+ join(TEST_DIR, '.popeye', 'website-strategy.json'),
354
+ JSON.stringify(makeValidStrategy()),
355
+ );
356
+
357
+ const artifact = makeRolePlanArtifact('BACKEND_PROGRAMMER');
358
+ writeFileSync(join(TEST_DIR, artifact.path), '# BACKEND_PROGRAMMER Plan\n## Tasks\n- Build API');
359
+
360
+ const ctx = buildRoleExecutionContext(
361
+ 'BACKEND_PROGRAMMER',
362
+ makeMockSkill('BACKEND_PROGRAMMER'),
363
+ artifact,
364
+ TEST_DIR,
365
+ );
366
+
367
+ expect(ctx.systemPrompt).not.toContain('Website Marketing Strategy');
368
+ expect(ctx.systemPrompt).not.toContain('Target Customer');
369
+ });
370
+
371
+ it('handles missing strategy file gracefully (no crash, prompt still valid)', () => {
372
+ // No .popeye directory, no strategy file
373
+ const artifact = makeRolePlanArtifact('WEBSITE_PROGRAMMER');
374
+ writeFileSync(join(TEST_DIR, artifact.path), '# WEBSITE_PROGRAMMER Plan\n## Tasks\n- Build site');
375
+
376
+ const ctx = buildRoleExecutionContext(
377
+ 'WEBSITE_PROGRAMMER',
378
+ makeMockSkill('WEBSITE_PROGRAMMER'),
379
+ artifact,
380
+ TEST_DIR,
381
+ );
382
+
383
+ expect(ctx.role).toBe('WEBSITE_PROGRAMMER');
384
+ expect(ctx.systemPrompt).toContain('WEBSITE_PROGRAMMER');
385
+ expect(ctx.systemPrompt).not.toContain('Website Marketing Strategy');
386
+ });
387
+ });