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
@@ -19,13 +19,16 @@ import type {
19
19
  PipelineResult,
20
20
  RCAPacket,
21
21
  } from './types.js';
22
- import { createDefaultPipelineState } from './types.js';
22
+ import { createDefaultPipelineState, toLegacyPhase } from './types.js';
23
23
  import { createGateEngine } from './gate-engine.js';
24
24
  import type { GateResult } from './gate-engine.js';
25
25
  import { createArtifactManager } from './artifact-manager.js';
26
26
  import { createSkillLoader } from './skill-loader.js';
27
27
  import { createConsensusRunner } from './consensus/consensus-runner.js';
28
28
  import { verifyConstitution } from './constitution.js';
29
+ import { SkillUsageRegistry } from './skills/usage-registry.js';
30
+ import { runSkillCoverageCheck } from './check-runner.js';
31
+ import { resolveActiveCR, checkStagnation } from './cr-lifecycle.js';
29
32
 
30
33
  import {
31
34
  runIntake,
@@ -46,6 +49,7 @@ import {
46
49
  import type { PhaseContext, PhaseResult } from './phases/index.js';
47
50
  import type { ProjectState } from '../types/workflow.js';
48
51
  import type { ConsensusConfig } from '../types/consensus.js';
52
+ import { updateState } from '../state/index.js';
49
53
 
50
54
  // ─── Types ───────────────────────────────────────────────
51
55
 
@@ -97,19 +101,24 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
97
101
  } = options;
98
102
 
99
103
  // Initialize pipeline state if needed
100
- const pipeline: PipelineState = (state as unknown as { pipeline?: PipelineState }).pipeline
101
- ?? createDefaultPipelineState();
104
+ const pipeline: PipelineState = state.pipeline ?? createDefaultPipelineState();
105
+ // Attach pipeline to state so persistence includes it
106
+ state.pipeline = pipeline;
102
107
 
103
108
  // Persist user guidance in pipeline state so it survives resume
104
109
  if (additionalContext && !pipeline.sessionGuidance) {
105
110
  pipeline.sessionGuidance = additionalContext;
106
111
  }
107
112
 
113
+ // Initialize skill usage events array (v2.2.1)
114
+ pipeline.skillUsageEvents = pipeline.skillUsageEvents ?? [];
115
+ const skillUsageRegistry = new SkillUsageRegistry(pipeline.skillUsageEvents);
116
+
108
117
  // Create context dependencies
109
118
  const gateEngine = createGateEngine();
110
119
  const artifactManager = createArtifactManager(projectDir);
111
120
  const skillLoader = createSkillLoader(projectDir);
112
- const consensusRunner = createConsensusRunner(projectDir, consensusConfig);
121
+ const consensusRunner = createConsensusRunner(projectDir, consensusConfig, skillLoader, skillUsageRegistry);
113
122
 
114
123
  // Ensure docs structure
115
124
  artifactManager.ensureDocsStructure();
@@ -122,6 +131,7 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
122
131
  artifactManager,
123
132
  gateEngine,
124
133
  consensusRunner,
134
+ skillUsageRegistry,
125
135
  };
126
136
 
127
137
  let phase = pipeline.pipelinePhase;
@@ -159,6 +169,21 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
159
169
 
160
170
  onPhaseComplete?.(phase, result);
161
171
 
172
+ // Log phase outcome — critical for diagnosing pipeline loops
173
+ if (!result.success) {
174
+ onProgress?.(`Phase ${phase} FAILED: ${result.message}${result.error ? ` — ${result.error}` : ''}`);
175
+ }
176
+
177
+ // v2.2.1: Run skill coverage check before gates that require it
178
+ if (phase === 'CONSENSUS_ROLE_PLANS' || phase === 'PRODUCTION_GATE') {
179
+ const coverageResult = runSkillCoverageCheck(pipeline, phase);
180
+ const phaseChecks = pipeline.gateChecks[phase] ?? [];
181
+ // Replace any existing skill_coverage check result
182
+ const filtered = phaseChecks.filter((c) => c.check_type !== 'skill_coverage');
183
+ filtered.push(coverageResult);
184
+ pipeline.gateChecks[phase] = filtered;
185
+ }
186
+
162
187
  // v1.1: Verify constitution integrity before evaluating gate
163
188
  const constitutionCheck = verifyConstitution(pipeline, projectDir);
164
189
 
@@ -174,13 +199,42 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
174
199
  if (gateResult.pass) {
175
200
  // ─── PASS ────────────────────────────────────────
176
201
 
202
+ // v2.7.0: Clear failedPhase when the originally-failed phase now passes.
203
+ // INVARIANT: This is the ONLY place failedPhase is cleared during the main loop.
204
+ // It must remain set throughout the entire recovery traversal to guard budget resets.
205
+ if (pipeline.failedPhase === phase) {
206
+ pipeline.failedPhase = undefined;
207
+ pipeline.recoveryBaselineFailedCheckCount = undefined;
208
+ }
209
+
210
+ // v2.4.9: Resolve the active CR after its routed phase passes
211
+ if (pipeline.activeChangeRequestId) {
212
+ resolveActiveCR(pipeline, onProgress);
213
+ }
214
+
177
215
  // v1.1: Check for pending CRs after REVIEW/AUDIT — route to consensus before continuing
178
216
  if (CR_CHECK_PHASES.has(phase)) {
179
217
  const crRoute = getNextCRRoute(pipeline);
180
218
  if (crRoute) {
181
219
  onProgress?.(`CR ${crRoute.cr_id} routing to ${crRoute.target_phase}`);
220
+ // v2.5.4: CR routing to a new phase — reset recovery budget
221
+ if (crRoute.target_phase !== phase && pipeline.recoveryCount > 0) {
222
+ onProgress?.(`Recovery budget reset: ${pipeline.recoveryCount} -> 0 (CR routing ${phase} -> ${crRoute.target_phase})`);
223
+ pipeline.recoveryCount = 0;
224
+ pipeline.lastRewindTarget = undefined;
225
+ }
226
+ pipeline.activeChangeRequestId = crRoute.cr_id;
182
227
  phase = crRoute.target_phase;
183
228
  pipeline.pipelinePhase = phase;
229
+ // v2.5.0: Stagnation check before continue (otherwise skipped by the continue)
230
+ if (checkStagnation(pipeline, onProgress)) {
231
+ phase = 'STUCK';
232
+ pipeline.pipelinePhase = phase;
233
+ }
234
+ // Persist before continue (otherwise CR-routed phase is lost on crash)
235
+ try {
236
+ await updateState(projectDir, { pipeline, phase: toLegacyPhase(phase) });
237
+ } catch { /* non-fatal */ }
184
238
  continue;
185
239
  }
186
240
  }
@@ -188,22 +242,121 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
188
242
  if (phase === 'RECOVERY_LOOP') {
189
243
  // Recovery succeeded. RCA may specify rewind target.
190
244
  const rca = getLatestRCA(pipeline, projectDir);
191
- if (rca?.requires_phase_rewind_to) {
192
- phase = rca.requires_phase_rewind_to;
193
- } else {
194
- // Retest the phase that failed
195
- phase = failedPhase ?? 'QA_VALIDATION';
245
+ let rewindTarget = rca?.requires_phase_rewind_to;
246
+
247
+ // v2.4.6: Detect repeated same-target rewind — if we already rewound
248
+ // to this target last iteration and it didn't help, skip the rewind
249
+ // and re-test the failed phase directly.
250
+ if (rewindTarget && rewindTarget === pipeline.lastRewindTarget) {
251
+ onProgress?.(
252
+ `Repeated rewind to ${rewindTarget} detected ` +
253
+ `(recovery #${pipeline.recoveryCount}) — re-testing ` +
254
+ `${failedPhase ?? 'QA_VALIDATION'} directly`,
255
+ );
256
+ rewindTarget = undefined;
196
257
  }
258
+
259
+ // Determine effective target
260
+ const effectiveTarget = rewindTarget ?? failedPhase ?? 'QA_VALIDATION';
261
+ phase = effectiveTarget;
262
+
263
+ // Record only the actual rewind target taken (not undefined when skipped)
264
+ pipeline.lastRewindTarget = rewindTarget;
265
+
266
+ // v2.4.6: Clear stale gate data so the re-entered phase evaluates fresh
267
+ if (failedPhase) {
268
+ delete pipeline.gateResults[failedPhase];
269
+ delete pipeline.gateChecks[failedPhase];
270
+ }
271
+
197
272
  failedPhase = null;
198
273
  } else {
199
274
  // Normal progression
200
- phase = gateEngine.getNextPhase(phase, gateResult);
275
+ const nextPhase = gateEngine.getNextPhase(phase, gateResult);
276
+
277
+ // v2.5.4: Reset recovery budget on forward phase change.
278
+ // Each phase gets a fresh budget — prevents a single contentious consensus
279
+ // from consuming all iterations, leaving later phases with zero.
280
+ // v2.7.0: Don't reset budget during recovery traversal.
281
+ // failedPhase stays set until the originally-failed phase passes, so
282
+ // intermediate forward transitions (IMPL→QA→REVIEW→AUDIT) are skipped.
283
+ if (nextPhase !== phase && pipeline.recoveryCount > 0 && !pipeline.failedPhase) {
284
+ onProgress?.(`Recovery budget reset: ${pipeline.recoveryCount} -> 0 (advancing ${phase} -> ${nextPhase})`);
285
+ pipeline.recoveryCount = 0;
286
+ pipeline.lastRewindTarget = undefined;
287
+ }
288
+
289
+ phase = nextPhase;
201
290
  }
202
291
  } else {
203
292
  // ─── FAIL ────────────────────────────────────────
293
+ onProgress?.(`Gate FAILED for ${phase}: ${gateResult.blockers.join('; ')}`);
294
+
295
+ // v2.7.0: Regression detection — recovery made things worse than the original failure
296
+ if (
297
+ pipeline.failedPhase === phase &&
298
+ pipeline.recoveryBaselineFailedCheckCount !== undefined &&
299
+ (gateResult.failedChecks.length + gateResult.missingArtifacts.length) > pipeline.recoveryBaselineFailedCheckCount
300
+ ) {
301
+ onProgress?.(
302
+ `[regression] Recovery worsened ${phase}: ` +
303
+ `${pipeline.recoveryBaselineFailedCheckCount} -> ` +
304
+ `${gateResult.failedChecks.length + gateResult.missingArtifacts.length} failing checks. ` +
305
+ `Treating budget as exhausted.`
306
+ );
307
+ pipeline.recoveryCount = pipeline.maxRecoveryIterations;
308
+ }
309
+
204
310
  if (pipeline.recoveryCount >= pipeline.maxRecoveryIterations) {
205
- phase = 'STUCK';
311
+ const exhaustedPhase = phase; // capture before any reassignment
312
+
313
+ // v2.6.0: One auto-recovery attempt before STUCK
314
+ const arbitratorConfigured = !!(consensusConfig?.arbitrator);
315
+ if (!pipeline.autoRecoveryResult && arbitratorConfigured) {
316
+ onProgress?.(`[auto-recovery] Budget exhausted at ${exhaustedPhase}. Consulting arbitrator...`);
317
+
318
+ try {
319
+ const { attemptAutoRecovery } = await import('./auto-recovery.js');
320
+ const result = await attemptAutoRecovery({
321
+ pipeline, projectDir, artifactManager, consensusConfig,
322
+ });
323
+
324
+ pipeline.autoRecoveryResult = result.success ? 'success' : 'invalid';
325
+ if (result.artifact) pipeline.artifacts.push(result.artifact);
326
+
327
+ if (result.success && result.guidance) {
328
+ onProgress?.(`[auto-recovery] Strategic guidance received (${result.guidance.length} chars). Resetting budget.`);
329
+ pipeline.recoveryCount = 0;
330
+ pipeline.lastRewindTarget = undefined;
331
+ // Stay on the failed phase — let the normal loop re-execute it
332
+ // with the injected strategic guidance
333
+ phase = exhaustedPhase;
334
+ pipeline.pipelinePhase = phase;
335
+ pipeline.failedPhase = exhaustedPhase;
336
+ // Clear stale gate data so re-entry evaluates fresh
337
+ delete pipeline.gateResults[exhaustedPhase];
338
+ delete pipeline.gateChecks[exhaustedPhase];
339
+ // Continue main loop (don't fall through to STUCK)
340
+ } else {
341
+ onProgress?.(`[auto-recovery] No useful guidance. Entering STUCK.`);
342
+ phase = 'STUCK';
343
+ }
344
+ } catch (err) {
345
+ pipeline.autoRecoveryResult = (err as Error)?.message?.includes('timeout') ? 'timeout' : 'error';
346
+ onProgress?.(`[auto-recovery] Failed: ${err instanceof Error ? err.message : 'unknown'}. Entering STUCK.`);
347
+ phase = 'STUCK';
348
+ }
349
+ } else {
350
+ phase = 'STUCK';
351
+ }
206
352
  } else {
353
+ // v2.7.0: Capture baseline for fresh failure or when failure origin changes
354
+ if (
355
+ pipeline.recoveryBaselineFailedCheckCount === undefined ||
356
+ pipeline.failedPhase !== phase // Different phase failing — new recovery origin
357
+ ) {
358
+ pipeline.recoveryBaselineFailedCheckCount = gateResult.failedChecks.length + gateResult.missingArtifacts.length;
359
+ }
207
360
  failedPhase = phase;
208
361
  pipeline.failedPhase = phase;
209
362
  phase = 'RECOVERY_LOOP';
@@ -211,8 +364,22 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
211
364
  }
212
365
  }
213
366
 
367
+ // v2.5.0: Stagnation detection — shared helper checks rolling signature window
368
+ if (checkStagnation(pipeline, onProgress)) {
369
+ phase = 'STUCK';
370
+ }
371
+
214
372
  // Update pipeline phase in state
215
373
  pipeline.pipelinePhase = phase;
374
+
375
+ // v2.4.5b: Persist pipeline state after each phase transition (crash safety)
376
+ try {
377
+ await updateState(projectDir, { pipeline, phase: toLegacyPhase(phase) });
378
+ } catch (persistErr) {
379
+ onProgress?.(`Warning: Failed to persist pipeline state: ${
380
+ persistErr instanceof Error ? persistErr.message : String(persistErr)}`);
381
+ }
382
+
216
383
  onProgress?.(`Transitioning to: ${phase}`);
217
384
  }
218
385
 
@@ -229,6 +396,13 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
229
396
  }
230
397
  }
231
398
 
399
+ // Persist final pipeline state (DONE or STUCK)
400
+ try {
401
+ await updateState(projectDir, { pipeline, phase: toLegacyPhase(phase) });
402
+ } catch {
403
+ // Best-effort for terminal persistence
404
+ }
405
+
232
406
  return {
233
407
  success: phase === 'DONE',
234
408
  finalPhase: phase,
@@ -240,9 +414,185 @@ export async function runPipeline(options: PipelineOptions): Promise<PipelineRes
240
414
  };
241
415
  }
242
416
 
243
- /** Resume pipeline from saved state */
417
+ /** Resume pipeline from saved state — with STUCK recovery (v2.4.5) */
244
418
  export async function resumePipeline(options: PipelineOptions): Promise<PipelineResult> {
245
- // Resume is the same as run — it picks up from pipeline.pipelinePhase
419
+ const pipeline: PipelineState | undefined = options.state.pipeline;
420
+
421
+ // v2.5.3: Normalize stale legacy status when pipeline is STUCK.
422
+ // completeProject() may have set status='complete' before the pipeline entered STUCK,
423
+ // leaving inconsistent state. Fix on load so the display layer never sees the lie.
424
+ if (
425
+ pipeline &&
426
+ (pipeline.pipelinePhase === 'STUCK' || pipeline.pipelinePhase === 'RECOVERY_LOOP') &&
427
+ options.state.status === 'complete'
428
+ ) {
429
+ options.state.status = 'in-progress';
430
+ try {
431
+ await updateState(options.projectDir, { status: 'in-progress' });
432
+ } catch {
433
+ // Best-effort — display layer override in interactive.ts handles it regardless
434
+ }
435
+ }
436
+
437
+ // v2.4.8: RECOVERY_LOOP with remaining attempts — auto-resume without guidance.
438
+ // Must check this BEFORE the STUCK branch because RECOVERY_LOOP with exhausted
439
+ // attempts falls through to the guidance-required path below.
440
+ if (
441
+ pipeline?.pipelinePhase === 'RECOVERY_LOOP' &&
442
+ pipeline?.failedPhase &&
443
+ pipeline.recoveryCount < (pipeline.maxRecoveryIterations ?? 5)
444
+ ) {
445
+ // Reset to the failed phase so the main loop re-enters. Do NOT reset
446
+ // recoveryCount — the pipeline retains its remaining iteration budget.
447
+ // Do NOT clear lastRewindTarget — preserve repeated-rewind detection.
448
+ pipeline.pipelinePhase = pipeline.failedPhase;
449
+ delete pipeline.gateResults[pipeline.failedPhase];
450
+ delete pipeline.gateChecks[pipeline.failedPhase];
451
+ options.onProgress?.(
452
+ `[resume] Auto-resuming from RECOVERY_LOOP: resetting to ${pipeline.failedPhase}, ` +
453
+ `recoveryCount ${pipeline.recoveryCount}/${pipeline.maxRecoveryIterations}`,
454
+ );
455
+ } else if (
456
+ // v2.4.7: STUCK (or RECOVERY_LOOP with exhausted attempts) requires user guidance.
457
+ (pipeline?.pipelinePhase === 'STUCK' ||
458
+ (pipeline?.pipelinePhase === 'RECOVERY_LOOP' &&
459
+ pipeline.recoveryCount >= (pipeline.maxRecoveryIterations ?? 5))) &&
460
+ pipeline?.failedPhase
461
+ ) {
462
+ // v2.5.2: Purge legacy CRs without drift_key (from pre-v2.5.0 infinite loop).
463
+ // These are orphaned — no drift_key means they can never be deduplicated or resolved.
464
+ // Runs unconditionally (before guidance check) so cleanup persists even without guidance.
465
+ if (pipeline.pendingChangeRequests) {
466
+ const before = pipeline.pendingChangeRequests.length;
467
+ pipeline.pendingChangeRequests = pipeline.pendingChangeRequests.filter(
468
+ (cr) => cr.drift_key != null || cr.status === 'proposed',
469
+ );
470
+ const purged = before - pipeline.pendingChangeRequests.length;
471
+ if (purged > 0) {
472
+ options.onProgress?.(`[resume] Purged ${purged} legacy CRs without drift_key`);
473
+ }
474
+ }
475
+
476
+ // Clear stale activeChangeRequestId if the referenced CR was purged
477
+ if (
478
+ pipeline.activeChangeRequestId &&
479
+ !pipeline.pendingChangeRequests?.some((cr) => cr.cr_id === pipeline.activeChangeRequestId)
480
+ ) {
481
+ options.onProgress?.(`[resume] Cleared stale activeChangeRequestId: ${pipeline.activeChangeRequestId}`);
482
+ pipeline.activeChangeRequestId = undefined;
483
+ }
484
+
485
+ const guidance = options.additionalContext?.trim() ?? '';
486
+ if (guidance.length > 0) {
487
+ // User provided guidance — allow one more retry
488
+ const prevRecovery = pipeline.recoveryCount;
489
+ pipeline.pipelinePhase = pipeline.failedPhase;
490
+ pipeline.recoveryCount = 0;
491
+ pipeline.lastRewindTarget = undefined;
492
+ pipeline.autoRecoveryResult = undefined; // v2.6.0: Fresh auto-recovery budget after user guidance
493
+
494
+ // Clear stale gate results and checks for the failed phase
495
+ delete pipeline.gateResults[pipeline.failedPhase];
496
+ delete pipeline.gateChecks[pipeline.failedPhase];
497
+
498
+ options.onProgress?.(
499
+ `[resume] Recovering from ${pipeline.pipelinePhase}: resetting to ${pipeline.failedPhase}, ` +
500
+ `recoveryCount ${prevRecovery} -> 0 (user provided guidance)`,
501
+ );
502
+ } else {
503
+ // No guidance — v2.6.0: attempt auto-recovery before giving up
504
+ const arbitratorConfigured = !!(options.consensusConfig?.arbitrator);
505
+ if (!pipeline.autoRecoveryResult && arbitratorConfigured) {
506
+ options.onProgress?.(
507
+ `[resume] No guidance provided. Attempting auto-recovery via arbitrator...`,
508
+ );
509
+
510
+ try {
511
+ const artifactManager = createArtifactManager(options.projectDir);
512
+ const { attemptAutoRecovery } = await import('./auto-recovery.js');
513
+ const result = await attemptAutoRecovery({
514
+ pipeline,
515
+ projectDir: options.projectDir,
516
+ artifactManager,
517
+ consensusConfig: options.consensusConfig,
518
+ });
519
+
520
+ pipeline.autoRecoveryResult = result.success ? 'success' : 'invalid';
521
+ if (result.artifact) pipeline.artifacts.push(result.artifact);
522
+
523
+ if (result.success && result.guidance) {
524
+ options.onProgress?.(
525
+ `[resume] Auto-recovery guidance received (${result.guidance.length} chars). Resetting budget.`,
526
+ );
527
+ const prevRecovery = pipeline.recoveryCount;
528
+ pipeline.pipelinePhase = pipeline.failedPhase;
529
+ pipeline.recoveryCount = 0;
530
+ pipeline.lastRewindTarget = undefined;
531
+ delete pipeline.gateResults[pipeline.failedPhase];
532
+ delete pipeline.gateChecks[pipeline.failedPhase];
533
+
534
+ options.onProgress?.(
535
+ `[resume] Recovering from STUCK: resetting to ${pipeline.failedPhase}, ` +
536
+ `recoveryCount ${prevRecovery} -> 0 (auto-recovery guidance)`,
537
+ );
538
+ // Fall through to runPipeline() below
539
+ } else {
540
+ options.onProgress?.(`[resume] Auto-recovery produced no useful guidance.`);
541
+ // Fall through to return STUCK below
542
+ }
543
+ } catch (err) {
544
+ pipeline.autoRecoveryResult = (err as Error)?.message?.includes('timeout') ? 'timeout' : 'error';
545
+ options.onProgress?.(
546
+ `[resume] Auto-recovery failed: ${err instanceof Error ? err.message : 'unknown'}`,
547
+ );
548
+ // Fall through to return STUCK below
549
+ }
550
+
551
+ // Persist state (whether auto-recovery succeeded or not)
552
+ try {
553
+ await updateState(options.projectDir, { pipeline });
554
+ } catch { /* best-effort */ }
555
+
556
+ // If auto-recovery succeeded, fall through to runPipeline()
557
+ if (pipeline.autoRecoveryResult === 'success') {
558
+ return runPipeline(options);
559
+ }
560
+ }
561
+
562
+ // No guidance, no auto-recovery (or auto-recovery failed) — return STUCK
563
+ try {
564
+ await updateState(options.projectDir, { pipeline });
565
+ } catch {
566
+ // Best-effort — cleanup is still in memory for next resume
567
+ }
568
+ options.onProgress?.(
569
+ `[resume] Pipeline is stuck at ${pipeline.failedPhase} after ${pipeline.recoveryCount} recovery attempts. ` +
570
+ `Provide guidance to attempt recovery.`,
571
+ );
572
+ return {
573
+ success: false,
574
+ finalPhase: 'STUCK',
575
+ artifacts: pipeline.artifacts,
576
+ recoveryIterations: pipeline.recoveryCount,
577
+ error: `Pipeline stuck at ${pipeline.failedPhase} after ${pipeline.recoveryCount} recovery iterations. Provide guidance to retry.`,
578
+ };
579
+ }
580
+ } else if (
581
+ (pipeline?.pipelinePhase === 'STUCK' || pipeline?.pipelinePhase === 'RECOVERY_LOOP') &&
582
+ !pipeline?.failedPhase
583
+ ) {
584
+ options.onProgress?.(
585
+ `[resume] Pipeline is ${pipeline.pipelinePhase} but failedPhase is missing — cannot auto-recover`,
586
+ );
587
+ return {
588
+ success: false,
589
+ finalPhase: 'STUCK',
590
+ artifacts: pipeline?.artifacts ?? [],
591
+ recoveryIterations: pipeline?.recoveryCount ?? 0,
592
+ error: 'Pipeline is stuck with no failed phase recorded. Manual intervention required.',
593
+ };
594
+ }
595
+
246
596
  return runPipeline(options);
247
597
  }
248
598
 
@@ -259,12 +609,13 @@ function mergeGateResult(
259
609
  gateResult: GateResult,
260
610
  ): void {
261
611
  const existing = pipeline.gateResults[phase];
262
- if (existing?.score !== undefined || existing?.consensusScore !== undefined) {
263
- // Preserve consensus scores from the phase handler
612
+ if (existing?.score !== undefined || existing?.consensusScore !== undefined || existing?.finalStatus !== undefined) {
613
+ // Preserve consensus scores and finalStatus from the phase handler
264
614
  pipeline.gateResults[phase] = {
265
615
  ...gateResult,
266
616
  score: existing.score ?? gateResult.score,
267
617
  consensusScore: existing.consensusScore ?? gateResult.consensusScore,
618
+ finalStatus: existing.finalStatus ?? gateResult.finalStatus, // v2.4.3
268
619
  };
269
620
  } else {
270
621
  pipeline.gateResults[phase] = gateResult;
@@ -284,7 +635,7 @@ function getNextCRRoute(
284
635
  const nextCR = pending.find((cr) => cr.status === 'proposed');
285
636
  if (!nextCR) return undefined;
286
637
 
287
- // Mark as approved (it has been routed to the consensus phase)
638
+ // Mark as routed/in-flight (kept as 'approved' for backward compatibility)
288
639
  nextCR.status = 'approved';
289
640
 
290
641
  return {
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Consensus Packet Builder — aggregates reviewer votes into a ConsensusPacket.
3
3
  * Auto-computes consensus result (score, approval) and final status.
4
- * v1.1: Added confidence-weighted scoring.
4
+ * v2.0: Option B scoring — avg(baseWeight * confidence) per reviewer.
5
+ * REJECT guard: REJECT with true blockers prevents APPROVED (not ARBITRATED).
6
+ * Normalization summary passthrough.
5
7
  */
6
8
 
7
9
  import { randomUUID } from 'node:crypto';
@@ -11,6 +13,7 @@ import type {
11
13
  ReviewerVote,
12
14
  ConsensusPacket,
13
15
  } from '../types.js';
16
+ import { isNoneVariant } from '../../shared/text-utils.js';
14
17
 
15
18
  export interface ConsensusRules {
16
19
  threshold: number;
@@ -18,6 +21,13 @@ export interface ConsensusRules {
18
21
  min_reviewers: number;
19
22
  }
20
23
 
24
+ export interface NormalizationSummary {
25
+ tagged_blockers_demoted_to_suggestions: number;
26
+ tagged_blockers_demoted_to_required: number;
27
+ untagged_from_blocking_routed_to_required: number;
28
+ forced_rejects: number;
29
+ }
30
+
21
31
  export interface BuildConsensusPacketArgs {
22
32
  planPacketRef: ArtifactRef;
23
33
  votes: ReviewerVote[];
@@ -27,6 +37,7 @@ export interface BuildConsensusPacketArgs {
27
37
  merged_patch?: string;
28
38
  artifact_ref?: ArtifactRef;
29
39
  };
40
+ normalizationMoves?: NormalizationSummary;
30
41
  }
31
42
 
32
43
  /** Vote weight mapping: APPROVE=1.0, CONDITIONAL=0.5, REJECT=0.0 */
@@ -40,43 +51,48 @@ const VOTE_WEIGHTS: Record<string, number> = {
40
51
  * Compute both simple and confidence-weighted consensus scores.
41
52
  *
42
53
  * Simple score: approve count / total votes (backward compat).
43
- * Weighted score: each vote's weight (APPROVE=1, CONDITIONAL=0.5, REJECT=0)
44
- * multiplied by voter confidence, then averaged by total confidence.
45
- * If any vote has blocking_issues, weighted_score is forced to 0.
54
+ * Option B weighted score: average of (vote_weight * confidence) per reviewer.
55
+ * v2.4.2: Returns honest rawWeighted score + has_true_blockers flag.
56
+ * Force-zero veto removed governance guard in buildConsensusPacket() is
57
+ * the single enforcement point for blocker-based rejection.
46
58
  */
47
59
  export function computeConsensusScore(
48
60
  votes: ReviewerVote[],
49
- ): { score: number; weighted_score: number } {
50
- if (votes.length === 0) return { score: 0, weighted_score: 0 };
61
+ ): { score: number; weighted_score: number; has_true_blockers: boolean } {
62
+ if (votes.length === 0) return { score: 0, weighted_score: 0, has_true_blockers: false };
51
63
 
52
64
  // Simple score (backward compat): approve ratio
53
65
  const approvedCount = votes.filter((v) => v.vote === 'APPROVE').length;
54
66
  const score = approvedCount / votes.length;
55
67
 
56
- // Weighted score: confidence-weighted vote values
57
- let totalWeight = 0;
68
+ // Option B scoring: average of (vote_weight * confidence) per reviewer
58
69
  let weightedSum = 0;
59
70
  for (const v of votes) {
60
- const w = v.confidence;
61
- weightedSum += (VOTE_WEIGHTS[v.vote] ?? 0) * w;
62
- totalWeight += w;
71
+ const baseWeight = VOTE_WEIGHTS[v.vote] ?? 0;
72
+ weightedSum += baseWeight * v.confidence;
63
73
  }
64
- const rawWeighted = totalWeight > 0 ? weightedSum / totalWeight : 0;
74
+ const rawWeighted = weightedSum / votes.length;
65
75
 
66
- // Override: any vote with blocking_issues forces weighted_score to 0
67
- const hasBlockingIssues = votes.some((v) => v.blocking_issues.length > 0);
76
+ // v2.4.2: Detect true blockers but don't force score to 0.
77
+ // Downstream code uses has_true_blockers for decisions instead.
78
+ const has_true_blockers = votes.some(
79
+ (v) => v.vote === 'REJECT' && v.blocking_issues.some((issue) => !isNoneVariant(issue)),
80
+ );
68
81
 
69
82
  return {
70
83
  score,
71
- weighted_score: hasBlockingIssues ? 0 : rawWeighted,
84
+ weighted_score: rawWeighted,
85
+ has_true_blockers,
72
86
  };
73
87
  }
74
88
 
75
89
  export function buildConsensusPacket(args: BuildConsensusPacketArgs): ConsensusPacket {
76
- const { planPacketRef, votes, rules, arbitratorResult } = args;
90
+ const { planPacketRef, votes, rules, arbitratorResult, normalizationMoves } = args;
91
+
92
+ const { score, weighted_score, has_true_blockers } = computeConsensusScore(votes);
77
93
 
78
- const { score, weighted_score } = computeConsensusScore(votes);
79
- const approved = score >= rules.threshold && votes.length >= rules.quorum;
94
+ // Use weighted_score (not simple score) for approval decision
95
+ const approved = weighted_score >= rules.threshold && votes.length >= rules.quorum;
80
96
 
81
97
  let finalStatus: 'APPROVED' | 'REJECTED' | 'ARBITRATED';
82
98
  if (arbitratorResult) {
@@ -87,6 +103,14 @@ export function buildConsensusPacket(args: BuildConsensusPacketArgs): ConsensusP
87
103
  finalStatus = 'REJECTED';
88
104
  }
89
105
 
106
+ // Governance guard: REJECT with true blockers prevents APPROVED (but not ARBITRATED)
107
+ const hasRejectWithTrueBlockers = votes.some(
108
+ (v) => v.vote === 'REJECT' && v.blocking_issues.some((i) => !isNoneVariant(i)),
109
+ );
110
+ if (hasRejectWithTrueBlockers && finalStatus === 'APPROVED') {
111
+ finalStatus = 'REJECTED';
112
+ }
113
+
90
114
  return {
91
115
  metadata: {
92
116
  packet_id: randomUUID(),
@@ -105,8 +129,10 @@ export function buildConsensusPacket(args: BuildConsensusPacketArgs): ConsensusP
105
129
  score,
106
130
  weighted_score,
107
131
  participating_reviewers: votes.length,
132
+ has_true_blockers,
108
133
  },
109
134
  arbitrator_result: arbitratorResult,
110
135
  final_status: finalStatus,
136
+ normalization_moves: normalizationMoves,
111
137
  };
112
138
  }
@@ -11,12 +11,12 @@ import { successResult, failureResult } from './phase-context.js';
11
11
  import { generateRepoSnapshot, createSnapshotArtifact } from '../repo-snapshot.js';
12
12
 
13
13
  export async function runArchitecture(context: PhaseContext): Promise<PhaseResult> {
14
- const { pipeline, artifactManager, skillLoader, projectDir } = context;
14
+ const { pipeline, artifactManager, skillLoader, skillUsageRegistry, projectDir } = context;
15
15
  const artifacts = [];
16
16
 
17
17
  try {
18
- // 1. Load architect skill
19
- const architectSkill = skillLoader.loadSkill('ARCHITECT');
18
+ // 1. Load architect skill with metadata
19
+ const { definition: architectSkill, meta: architectMeta } = skillLoader.loadSkillWithMeta('ARCHITECT');
20
20
 
21
21
  // 2. Read approved master plan
22
22
  const masterPlanArtifact = pipeline.artifacts.find((a) => a.type === 'master_plan');
@@ -50,6 +50,9 @@ export async function runArchitecture(context: PhaseContext): Promise<PhaseResul
50
50
  const result = await executePrompt(architecturePrompt);
51
51
  const architectureDoc = result.response;
52
52
 
53
+ // Record skill usage — architect skill injected into prompt
54
+ skillUsageRegistry.record('ARCHITECT', 'ARCHITECTURE', 'system_prompt', architectMeta.source, architectMeta.version);
55
+
53
56
  // 4. Store architecture artifact
54
57
  const archEntry = artifactManager.createAndStoreText(
55
58
  'architecture',