popeye-cli 1.10.0 → 2.0.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 (253) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/CONTRIBUTING.md +15 -1
  3. package/README.md +57 -0
  4. package/dist/pipeline/artifact-manager.d.ts +47 -0
  5. package/dist/pipeline/artifact-manager.d.ts.map +1 -0
  6. package/dist/pipeline/artifact-manager.js +251 -0
  7. package/dist/pipeline/artifact-manager.js.map +1 -0
  8. package/dist/pipeline/artifact-validators.d.ts +29 -0
  9. package/dist/pipeline/artifact-validators.d.ts.map +1 -0
  10. package/dist/pipeline/artifact-validators.js +173 -0
  11. package/dist/pipeline/artifact-validators.js.map +1 -0
  12. package/dist/pipeline/change-request.d.ts +47 -0
  13. package/dist/pipeline/change-request.d.ts.map +1 -0
  14. package/dist/pipeline/change-request.js +91 -0
  15. package/dist/pipeline/change-request.js.map +1 -0
  16. package/dist/pipeline/check-runner.d.ts +47 -0
  17. package/dist/pipeline/check-runner.d.ts.map +1 -0
  18. package/dist/pipeline/check-runner.js +417 -0
  19. package/dist/pipeline/check-runner.js.map +1 -0
  20. package/dist/pipeline/command-resolver.d.ts +9 -0
  21. package/dist/pipeline/command-resolver.d.ts.map +1 -0
  22. package/dist/pipeline/command-resolver.js +140 -0
  23. package/dist/pipeline/command-resolver.js.map +1 -0
  24. package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
  25. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
  26. package/dist/pipeline/consensus/consensus-runner.js +212 -0
  27. package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
  28. package/dist/pipeline/constitution.d.ts +45 -0
  29. package/dist/pipeline/constitution.d.ts.map +1 -0
  30. package/dist/pipeline/constitution.js +82 -0
  31. package/dist/pipeline/constitution.js.map +1 -0
  32. package/dist/pipeline/gate-engine.d.ts +55 -0
  33. package/dist/pipeline/gate-engine.d.ts.map +1 -0
  34. package/dist/pipeline/gate-engine.js +270 -0
  35. package/dist/pipeline/gate-engine.js.map +1 -0
  36. package/dist/pipeline/index.d.ts +26 -0
  37. package/dist/pipeline/index.d.ts.map +1 -0
  38. package/dist/pipeline/index.js +35 -0
  39. package/dist/pipeline/index.js.map +1 -0
  40. package/dist/pipeline/migration.d.ts +15 -0
  41. package/dist/pipeline/migration.d.ts.map +1 -0
  42. package/dist/pipeline/migration.js +76 -0
  43. package/dist/pipeline/migration.js.map +1 -0
  44. package/dist/pipeline/orchestrator.d.ts +28 -0
  45. package/dist/pipeline/orchestrator.d.ts.map +1 -0
  46. package/dist/pipeline/orchestrator.js +238 -0
  47. package/dist/pipeline/orchestrator.js.map +1 -0
  48. package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
  49. package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
  50. package/dist/pipeline/packets/audit-report-builder.js +32 -0
  51. package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
  52. package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
  53. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
  54. package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
  55. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
  56. package/dist/pipeline/packets/index.d.ts +12 -0
  57. package/dist/pipeline/packets/index.d.ts.map +1 -0
  58. package/dist/pipeline/packets/index.js +8 -0
  59. package/dist/pipeline/packets/index.js.map +1 -0
  60. package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
  61. package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
  62. package/dist/pipeline/packets/plan-packet-builder.js +27 -0
  63. package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
  64. package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
  65. package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
  66. package/dist/pipeline/packets/rca-packet-builder.js +22 -0
  67. package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
  68. package/dist/pipeline/phases/architecture.d.ts +7 -0
  69. package/dist/pipeline/phases/architecture.d.ts.map +1 -0
  70. package/dist/pipeline/phases/architecture.js +60 -0
  71. package/dist/pipeline/phases/architecture.js.map +1 -0
  72. package/dist/pipeline/phases/audit.d.ts +8 -0
  73. package/dist/pipeline/phases/audit.d.ts.map +1 -0
  74. package/dist/pipeline/phases/audit.js +144 -0
  75. package/dist/pipeline/phases/audit.js.map +1 -0
  76. package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
  77. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
  78. package/dist/pipeline/phases/consensus-architecture.js +84 -0
  79. package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
  80. package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
  81. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
  82. package/dist/pipeline/phases/consensus-master-plan.js +81 -0
  83. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
  84. package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
  85. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
  86. package/dist/pipeline/phases/consensus-role-plans.js +85 -0
  87. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
  88. package/dist/pipeline/phases/done.d.ts +7 -0
  89. package/dist/pipeline/phases/done.d.ts.map +1 -0
  90. package/dist/pipeline/phases/done.js +45 -0
  91. package/dist/pipeline/phases/done.js.map +1 -0
  92. package/dist/pipeline/phases/implementation.d.ts +8 -0
  93. package/dist/pipeline/phases/implementation.d.ts.map +1 -0
  94. package/dist/pipeline/phases/implementation.js +42 -0
  95. package/dist/pipeline/phases/implementation.js.map +1 -0
  96. package/dist/pipeline/phases/index.d.ts +20 -0
  97. package/dist/pipeline/phases/index.d.ts.map +1 -0
  98. package/dist/pipeline/phases/index.js +19 -0
  99. package/dist/pipeline/phases/index.js.map +1 -0
  100. package/dist/pipeline/phases/intake.d.ts +8 -0
  101. package/dist/pipeline/phases/intake.d.ts.map +1 -0
  102. package/dist/pipeline/phases/intake.js +40 -0
  103. package/dist/pipeline/phases/intake.js.map +1 -0
  104. package/dist/pipeline/phases/phase-context.d.ts +30 -0
  105. package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
  106. package/dist/pipeline/phases/phase-context.js +33 -0
  107. package/dist/pipeline/phases/phase-context.js.map +1 -0
  108. package/dist/pipeline/phases/production-gate.d.ts +8 -0
  109. package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
  110. package/dist/pipeline/phases/production-gate.js +84 -0
  111. package/dist/pipeline/phases/production-gate.js.map +1 -0
  112. package/dist/pipeline/phases/qa-validation.d.ts +7 -0
  113. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
  114. package/dist/pipeline/phases/qa-validation.js +50 -0
  115. package/dist/pipeline/phases/qa-validation.js.map +1 -0
  116. package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
  117. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
  118. package/dist/pipeline/phases/recovery-loop.js +91 -0
  119. package/dist/pipeline/phases/recovery-loop.js.map +1 -0
  120. package/dist/pipeline/phases/review.d.ts +8 -0
  121. package/dist/pipeline/phases/review.d.ts.map +1 -0
  122. package/dist/pipeline/phases/review.js +127 -0
  123. package/dist/pipeline/phases/review.js.map +1 -0
  124. package/dist/pipeline/phases/role-planning.d.ts +7 -0
  125. package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
  126. package/dist/pipeline/phases/role-planning.js +75 -0
  127. package/dist/pipeline/phases/role-planning.js.map +1 -0
  128. package/dist/pipeline/phases/stuck.d.ts +7 -0
  129. package/dist/pipeline/phases/stuck.d.ts.map +1 -0
  130. package/dist/pipeline/phases/stuck.js +51 -0
  131. package/dist/pipeline/phases/stuck.js.map +1 -0
  132. package/dist/pipeline/repo-snapshot.d.ts +24 -0
  133. package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
  134. package/dist/pipeline/repo-snapshot.js +343 -0
  135. package/dist/pipeline/repo-snapshot.js.map +1 -0
  136. package/dist/pipeline/role-execution-adapter.d.ts +59 -0
  137. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
  138. package/dist/pipeline/role-execution-adapter.js +159 -0
  139. package/dist/pipeline/role-execution-adapter.js.map +1 -0
  140. package/dist/pipeline/skill-loader.d.ts +34 -0
  141. package/dist/pipeline/skill-loader.d.ts.map +1 -0
  142. package/dist/pipeline/skill-loader.js +156 -0
  143. package/dist/pipeline/skill-loader.js.map +1 -0
  144. package/dist/pipeline/skills/defaults.d.ts +16 -0
  145. package/dist/pipeline/skills/defaults.d.ts.map +1 -0
  146. package/dist/pipeline/skills/defaults.js +189 -0
  147. package/dist/pipeline/skills/defaults.js.map +1 -0
  148. package/dist/pipeline/type-defs/artifacts.d.ts +202 -0
  149. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
  150. package/dist/pipeline/type-defs/artifacts.js +66 -0
  151. package/dist/pipeline/type-defs/artifacts.js.map +1 -0
  152. package/dist/pipeline/type-defs/audit.d.ts +256 -0
  153. package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
  154. package/dist/pipeline/type-defs/audit.js +54 -0
  155. package/dist/pipeline/type-defs/audit.js.map +1 -0
  156. package/dist/pipeline/type-defs/checks.d.ts +81 -0
  157. package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
  158. package/dist/pipeline/type-defs/checks.js +38 -0
  159. package/dist/pipeline/type-defs/checks.js.map +1 -0
  160. package/dist/pipeline/type-defs/enums.d.ts +43 -0
  161. package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
  162. package/dist/pipeline/type-defs/enums.js +55 -0
  163. package/dist/pipeline/type-defs/enums.js.map +1 -0
  164. package/dist/pipeline/type-defs/index.d.ts +12 -0
  165. package/dist/pipeline/type-defs/index.d.ts.map +1 -0
  166. package/dist/pipeline/type-defs/index.js +12 -0
  167. package/dist/pipeline/type-defs/index.js.map +1 -0
  168. package/dist/pipeline/type-defs/packets.d.ts +806 -0
  169. package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
  170. package/dist/pipeline/type-defs/packets.js +109 -0
  171. package/dist/pipeline/type-defs/packets.js.map +1 -0
  172. package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
  173. package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
  174. package/dist/pipeline/type-defs/snapshot.js +35 -0
  175. package/dist/pipeline/type-defs/snapshot.js.map +1 -0
  176. package/dist/pipeline/type-defs/state.d.ts +449 -0
  177. package/dist/pipeline/type-defs/state.d.ts.map +1 -0
  178. package/dist/pipeline/type-defs/state.js +88 -0
  179. package/dist/pipeline/type-defs/state.js.map +1 -0
  180. package/dist/pipeline/types.d.ts +16 -0
  181. package/dist/pipeline/types.d.ts.map +1 -0
  182. package/dist/pipeline/types.js +16 -0
  183. package/dist/pipeline/types.js.map +1 -0
  184. package/dist/types/audit.d.ts +6 -6
  185. package/dist/workflow/index.d.ts.map +1 -1
  186. package/dist/workflow/index.js +48 -0
  187. package/dist/workflow/index.js.map +1 -1
  188. package/package.json +1 -1
  189. package/skills/PHASE_GATE_ENGINE_SPEC.md +113 -20
  190. package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +66 -13
  191. package/src/pipeline/artifact-manager.ts +339 -0
  192. package/src/pipeline/artifact-validators.ts +224 -0
  193. package/src/pipeline/change-request.ts +119 -0
  194. package/src/pipeline/check-runner.ts +504 -0
  195. package/src/pipeline/command-resolver.ts +168 -0
  196. package/src/pipeline/consensus/consensus-runner.ts +317 -0
  197. package/src/pipeline/constitution.ts +109 -0
  198. package/src/pipeline/gate-engine.ts +347 -0
  199. package/src/pipeline/index.ts +82 -0
  200. package/src/pipeline/migration.ts +91 -0
  201. package/src/pipeline/orchestrator.ts +314 -0
  202. package/src/pipeline/packets/audit-report-builder.ts +47 -0
  203. package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
  204. package/src/pipeline/packets/index.ts +15 -0
  205. package/src/pipeline/packets/plan-packet-builder.ts +52 -0
  206. package/src/pipeline/packets/rca-packet-builder.ts +38 -0
  207. package/src/pipeline/phases/architecture.ts +73 -0
  208. package/src/pipeline/phases/audit.ts +193 -0
  209. package/src/pipeline/phases/consensus-architecture.ts +104 -0
  210. package/src/pipeline/phases/consensus-master-plan.ts +100 -0
  211. package/src/pipeline/phases/consensus-role-plans.ts +105 -0
  212. package/src/pipeline/phases/done.ts +68 -0
  213. package/src/pipeline/phases/implementation.ts +48 -0
  214. package/src/pipeline/phases/index.ts +21 -0
  215. package/src/pipeline/phases/intake.ts +54 -0
  216. package/src/pipeline/phases/phase-context.ts +86 -0
  217. package/src/pipeline/phases/production-gate.ts +113 -0
  218. package/src/pipeline/phases/qa-validation.ts +63 -0
  219. package/src/pipeline/phases/recovery-loop.ts +118 -0
  220. package/src/pipeline/phases/review.ts +149 -0
  221. package/src/pipeline/phases/role-planning.ts +92 -0
  222. package/src/pipeline/phases/stuck.ts +62 -0
  223. package/src/pipeline/repo-snapshot.ts +395 -0
  224. package/src/pipeline/role-execution-adapter.ts +238 -0
  225. package/src/pipeline/skill-loader.ts +192 -0
  226. package/src/pipeline/skills/defaults.ts +215 -0
  227. package/src/pipeline/type-defs/artifacts.ts +81 -0
  228. package/src/pipeline/type-defs/audit.ts +67 -0
  229. package/src/pipeline/type-defs/checks.ts +47 -0
  230. package/src/pipeline/type-defs/enums.ts +62 -0
  231. package/src/pipeline/type-defs/index.ts +12 -0
  232. package/src/pipeline/type-defs/packets.ts +131 -0
  233. package/src/pipeline/type-defs/snapshot.ts +55 -0
  234. package/src/pipeline/type-defs/state.ts +165 -0
  235. package/src/pipeline/types.ts +16 -0
  236. package/src/workflow/index.ts +48 -0
  237. package/tests/pipeline/artifact-manager.test.ts +183 -0
  238. package/tests/pipeline/artifact-validators.test.ts +207 -0
  239. package/tests/pipeline/change-request.test.ts +180 -0
  240. package/tests/pipeline/check-runner.test.ts +157 -0
  241. package/tests/pipeline/command-resolver.test.ts +159 -0
  242. package/tests/pipeline/consensus-runner.test.ts +206 -0
  243. package/tests/pipeline/consensus-scoring.test.ts +163 -0
  244. package/tests/pipeline/constitution.test.ts +122 -0
  245. package/tests/pipeline/gate-engine.test.ts +195 -0
  246. package/tests/pipeline/migration.test.ts +133 -0
  247. package/tests/pipeline/orchestrator.test.ts +614 -0
  248. package/tests/pipeline/packets/builders.test.ts +347 -0
  249. package/tests/pipeline/repo-snapshot.test.ts +189 -0
  250. package/tests/pipeline/role-execution-adapter.test.ts +299 -0
  251. package/tests/pipeline/skill-loader.test.ts +186 -0
  252. package/tests/pipeline/start-env-checks.test.ts +123 -0
  253. package/tests/pipeline/types.test.ts +156 -0
@@ -1,6 +1,6 @@
1
1
  # Popeye Full Autonomy Pipeline Spec
2
- Version: 1.0
3
- Goal: 1 idea 1 prompt Popeye production-ready system (PASS Production Gate)
2
+ Version: 1.1 (Autonomy Hardening Gap Fixes)
3
+ Goal: 1 idea -> 1 prompt -> Popeye -> production-ready system (PASS Production Gate)
4
4
 
5
5
  This spec defines the end-to-end autonomous workflow Popeye must execute, including:
6
6
  - Phase sequencing (no skipping)
@@ -90,6 +90,27 @@ Artifacts are never overwritten. New versions create new files.
90
90
  - Each phase may only transition to the next if all required artifacts exist and the gate passes.
91
91
  - Any failure routes to RECOVERY_LOOP, except missing inputs which route back to the phase that needs them.
92
92
 
93
+ ### v1.1 Gate Behaviors (Autonomy Hardening)
94
+
95
+ Between phase execution and transition, the orchestrator now applies three additional checks:
96
+
97
+ 1. **Constitution Verification**: Before every `evaluateGate()` call, verifies that `skills/POPEYE_CONSTITUTION.md` has not been modified since pipeline start (SHA-256 hash comparison). If the constitution is invalid, the gate is blocked.
98
+
99
+ 2. **Gate Result Merge**: After gate evaluation, preserves `score`/`consensusScore` values stored by consensus phase handlers while updating `pass`/`blockers` from the gate engine. Prevents overwriting of consensus scores.
100
+
101
+ 3. **Change Request (CR) Routing**: After REVIEW and AUDIT phases pass their gates, checks `pipeline.pendingChangeRequests` for any `proposed` CRs. If found, deterministically routes to the appropriate consensus phase (based on change type) rather than continuing normal progression. CR lifecycle: `proposed` -> `approved` (when routed) or `rejected`.
102
+
103
+ ### v1.1 Pipeline State Additions
104
+
105
+ ```
106
+ pendingChangeRequests?: Array<{
107
+ cr_id: string;
108
+ change_type: 'scope' | 'architecture' | 'dependency' | 'config' | 'requirement';
109
+ target_phase: PipelinePhase;
110
+ status: 'proposed' | 'approved' | 'rejected';
111
+ }>;
112
+ ```
113
+
93
114
  ---
94
115
 
95
116
  ## 3) Phase-by-Phase Specification
@@ -321,15 +342,24 @@ Artifacts are never overwritten. New versions create new files.
321
342
  - evidence references
322
343
  - no shortcuts
323
344
  - integration wiring
345
+ - Generate fresh repo snapshot and diff against baseline (role-plan-approval snapshot)
346
+ - Detect implementation drift (config changes, significant line deltas)
324
347
 
325
348
  **Outputs**
326
- - Review decision doc `/docs/consensus/review_<id>_<date>.md`
349
+ - Review decision doc -> `/docs/consensus/review_<id>_<date>.md`
327
350
  - If rejected: structured blocking list
351
+ - v1.1: Change Request artifacts for detected drift (stored as `change_request` artifacts)
352
+ - v1.1: Pending CRs registered in `pipeline.pendingChangeRequests` for orchestrator routing
328
353
 
329
354
  **Gate conditions (PASS)**
330
355
  - Reviewer approves with evidence
331
356
  - No Constitution violations
332
357
 
358
+ **v1.1 Post-Gate Behavior**
359
+ - After REVIEW gate passes, orchestrator checks `pendingChangeRequests` for proposed CRs
360
+ - If CRs exist, routes to the appropriate consensus phase (e.g., CONSENSUS_MASTER_PLAN for scope drift)
361
+ - If no CRs, continues to AUDIT normally
362
+
333
363
  ---
334
364
 
335
365
  ### Phase 10 — AUDIT
@@ -339,7 +369,7 @@ Artifacts are never overwritten. New versions create new files.
339
369
  - Everything above + repo snapshot + logs
340
370
 
341
371
  **Actions (Auditor)**
342
- - Integration audit (FEBE, BEDB)
372
+ - Integration audit (FE<->BE, BE<->DB)
343
373
  - Config/env audit
344
374
  - Tests/coverage audit
345
375
  - Migration audit
@@ -347,8 +377,10 @@ Artifacts are never overwritten. New versions create new files.
347
377
  - Deployment readiness audit
348
378
 
349
379
  **Outputs (required)**
350
- - Audit Report `/docs/audit/audit_<id>_<date>.md`
351
- - Audit Report schema (optional json)
380
+ - Audit Report -> `/docs/audit/audit_<id>_<date>.md`
381
+ - Audit Report schema (JSON)
382
+ - v1.1: Change Request artifacts for blocking architectural/security findings
383
+ - v1.1: Pending CRs registered in `pipeline.pendingChangeRequests` for orchestrator routing
352
384
 
353
385
  **Gate conditions (PASS)**
354
386
  - No P0/P1 findings open
@@ -356,6 +388,13 @@ Artifacts are never overwritten. New versions create new files.
356
388
  - No hardcoded secrets
357
389
  - End-to-end wiring verified
358
390
 
391
+ **v1.1 Post-Gate Behavior**
392
+ - After AUDIT gate passes, orchestrator checks `pendingChangeRequests` for proposed CRs
393
+ - Architectural findings (integration/schema blocking issues) create CRs targeting CONSENSUS_ARCHITECTURE
394
+ - Security findings create CRs targeting CONSENSUS_MASTER_PLAN
395
+ - If CRs exist, routes to the appropriate consensus phase before PRODUCTION_GATE
396
+ - If no CRs, continues to PRODUCTION_GATE normally
397
+
359
398
  **If FAIL**
360
399
  - Enter RECOVERY_LOOP
361
400
 
@@ -389,7 +428,7 @@ Artifacts are never overwritten. New versions create new files.
389
428
 
390
429
  ---
391
430
 
392
- ## 4) Recovery Loop (FAIL Debugger Plan Fix Retest)
431
+ ## 4) Recovery Loop (FAIL -> Debugger -> Plan -> Fix -> Retest)
393
432
 
394
433
  ### Phase 12 — RECOVERY_LOOP (conditional)
395
434
  **Purpose**: Self-heal deterministically using RCA, not guesswork.
@@ -406,6 +445,7 @@ Artifacts are never overwritten. New versions create new files.
406
445
  - ownership (which role must fix)
407
446
  - recommended corrective actions
408
447
  - prevention suggestion
448
+ - v1.1: `requires_phase_rewind_to` field specifying which phase to rewind to
409
449
 
410
450
  2) **Dispatcher generates Recovery Plan**
411
451
  - targeted tasks mapped to responsible role(s)
@@ -416,19 +456,23 @@ Artifacts are never overwritten. New versions create new files.
416
456
 
417
457
  4) **Implement fixes** (by responsible role)
418
458
 
419
- 5) **Retest only the failed gate first**, then proceed forward:
420
- - If failed at Audit rerun Audit
421
- - If failed at Production Gate rerun Production checks
422
- - If failed tests rerun tests + QA validation
459
+ 5) **Post-recovery routing** (v1.1 RCA-driven rewind):
460
+ - Orchestrator reads latest RCA JSON artifact from disk
461
+ - If RCA specifies `requires_phase_rewind_to`, pipeline rewinds to that phase
462
+ - Otherwise, retests the originally failed phase, then proceeds forward:
463
+ - If failed at Audit -> rerun Audit
464
+ - If failed at Production Gate -> rerun Production checks
465
+ - If failed tests -> rerun tests + QA validation
466
+ - If no failed phase is tracked, defaults to QA_VALIDATION
423
467
 
424
468
  **Outputs (required)**
425
- - RCA report `/docs/incidents/rca_<id>_<date>.md`
469
+ - RCA report -> `/docs/incidents/rca_<id>_<date>.md` (both markdown and JSON, JSON includes `requires_phase_rewind_to`)
426
470
  - Recovery plan packet + consensus packet
427
471
  - Updated artifacts if scope/contracts changed
428
472
 
429
473
  **Stop conditions**
430
474
  - Max recovery iterations (default: 5)
431
- - If exceeded STUCK state with “Stuck Report”
475
+ - If exceeded -> STUCK state with “Stuck Report”
432
476
 
433
477
  ---
434
478
 
@@ -479,6 +523,15 @@ If recovery iterations exceed max, Popeye must stop and output:
479
523
  - Every plan claim must be evidenced against repo snapshot
480
524
  - Production readiness is binary, not “looks good”
481
525
 
526
+ ### v1.1 Additions
527
+
528
+ - Constitution integrity is verified at every gate (SHA-256 hash comparison). A modified constitution blocks all gate progression until resolved.
529
+ - Consensus scores from phase handlers are never overwritten by gate engine evaluation (gate result merge rule).
530
+ - REVIEW and AUDIT phases must register detected issues as Change Requests in `pipeline.pendingChangeRequests`.
531
+ - CRs are routed deterministically by the orchestrator (not advisory). The routing is a deterministic transition, not a suggestion.
532
+ - RCA packets must include a `requires_phase_rewind_to` field when the root cause originates in a phase earlier than the one that failed. The orchestrator reads this from the JSON artifact on disk.
533
+ - CRs are processed one at a time (first `proposed` CR is routed and marked `approved` before the next is considered).
534
+
482
535
  ---
483
536
 
484
537
  End of Spec.
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Artifact Manager — manages immutable versioned artifacts under /docs/.
3
+ * Supports both Markdown and JSON content types (P0-C).
4
+ * Implements version chains via group_id + previous_id (P1-2).
5
+ */
6
+
7
+ import { randomUUID } from 'node:crypto';
8
+ import { createHash } from 'node:crypto';
9
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
10
+ import { join, relative } from 'node:path';
11
+
12
+ import type {
13
+ ArtifactType,
14
+ ArtifactEntry,
15
+ ArtifactRef,
16
+ ContentType,
17
+ PipelinePhase,
18
+ } from './types.js';
19
+
20
+ // ─── Constants ───────────────────────────────────────────
21
+
22
+ /** Directory mappings: artifact type -> subdirectory under /docs/ */
23
+ const ARTIFACT_DIRS: Record<string, string> = {
24
+ master_plan: 'master-plan',
25
+ architecture: 'architecture',
26
+ role_plan: 'role-plans',
27
+ consensus: 'consensus',
28
+ arbitration: 'arbitration',
29
+ audit_report: 'audit',
30
+ rca_report: 'incidents',
31
+ production_readiness: 'production',
32
+ release_notes: 'release',
33
+ deployment: 'release',
34
+ rollback: 'release',
35
+ repo_snapshot: 'snapshots',
36
+ build_check: 'checks',
37
+ test_check: 'checks',
38
+ lint_check: 'checks',
39
+ typecheck_check: 'checks',
40
+ placeholder_scan: 'checks',
41
+ qa_validation: 'role-plans',
42
+ review_decision: 'consensus',
43
+ stuck_report: 'incidents',
44
+ journalist_trace: 'journal',
45
+ resolved_commands: 'checks',
46
+ constitution: 'governance',
47
+ change_request: 'governance',
48
+ };
49
+
50
+ /** All required subdirectories under /docs/ */
51
+ const DOCS_SUBDIRS = [
52
+ 'master-plan',
53
+ 'architecture',
54
+ 'role-plans',
55
+ 'consensus',
56
+ 'arbitration',
57
+ 'audit',
58
+ 'incidents',
59
+ 'production',
60
+ 'release',
61
+ 'snapshots',
62
+ 'checks',
63
+ 'journal',
64
+ 'governance',
65
+ ];
66
+
67
+ // ─── Helper Functions ────────────────────────────────────
68
+
69
+ function computeSha256(content: string): string {
70
+ return createHash('sha256').update(content, 'utf-8').digest('hex');
71
+ }
72
+
73
+ function shortId(): string {
74
+ return randomUUID().split('-')[0];
75
+ }
76
+
77
+ function formatDate(): string {
78
+ return new Date().toISOString().split('T')[0];
79
+ }
80
+
81
+ function getExtension(contentType: ContentType): string {
82
+ return contentType === 'json' ? '.json' : '.md';
83
+ }
84
+
85
+ // ─── Artifact Manager ────────────────────────────────────
86
+
87
+ export interface ArtifactManagerOptions {
88
+ projectDir: string;
89
+ }
90
+
91
+ export class ArtifactManager {
92
+ private readonly projectDir: string;
93
+ private readonly docsDir: string;
94
+
95
+ constructor(options: ArtifactManagerOptions) {
96
+ this.projectDir = options.projectDir;
97
+ this.docsDir = join(options.projectDir, 'docs');
98
+ }
99
+
100
+ /** Ensure all /docs/ subdirectories exist */
101
+ ensureDocsStructure(): void {
102
+ if (!existsSync(this.docsDir)) {
103
+ mkdirSync(this.docsDir, { recursive: true });
104
+ }
105
+ for (const subdir of DOCS_SUBDIRS) {
106
+ const dirPath = join(this.docsDir, subdir);
107
+ if (!existsSync(dirPath)) {
108
+ mkdirSync(dirPath, { recursive: true });
109
+ }
110
+ }
111
+ }
112
+
113
+ /** Create an artifact from Markdown content */
114
+ createArtifactText(
115
+ type: ArtifactType,
116
+ markdown: string,
117
+ phase: PipelinePhase,
118
+ groupId?: string,
119
+ ): ArtifactEntry {
120
+ return this.createArtifact(type, markdown, phase, 'markdown', groupId);
121
+ }
122
+
123
+ /** Create an artifact from a JSON-serializable object */
124
+ createArtifactJson(
125
+ type: ArtifactType,
126
+ jsonObject: unknown,
127
+ phase: PipelinePhase,
128
+ groupId?: string,
129
+ ): ArtifactEntry {
130
+ const content = JSON.stringify(jsonObject, null, 2);
131
+ return this.createArtifact(type, content, phase, 'json', groupId);
132
+ }
133
+
134
+ /** Core artifact creation logic */
135
+ private createArtifact(
136
+ type: ArtifactType,
137
+ content: string,
138
+ phase: PipelinePhase,
139
+ contentType: ContentType,
140
+ groupId?: string,
141
+ ): ArtifactEntry {
142
+ this.ensureDocsStructure();
143
+
144
+ const resolvedGroupId = groupId ?? randomUUID();
145
+ const existingArtifacts = this.listArtifacts(type);
146
+ const version = this.getNextVersion(resolvedGroupId, existingArtifacts);
147
+ const previousEntry = this.getLatestInGroup(resolvedGroupId, existingArtifacts);
148
+
149
+ const id = randomUUID();
150
+ const date = formatDate();
151
+ const sid = shortId();
152
+ const ext = getExtension(contentType);
153
+ const filename = `${type}_${sid}_v${version}_${date}${ext}`;
154
+
155
+ const subdir = ARTIFACT_DIRS[type] ?? 'misc';
156
+ const dirPath = join(this.docsDir, subdir);
157
+ if (!existsSync(dirPath)) {
158
+ mkdirSync(dirPath, { recursive: true });
159
+ }
160
+
161
+ const filePath = join(dirPath, filename);
162
+ const sha256 = computeSha256(content);
163
+
164
+ writeFileSync(filePath, content, 'utf-8');
165
+
166
+ const relativePath = relative(this.projectDir, filePath);
167
+
168
+ const entry: ArtifactEntry = {
169
+ id,
170
+ type,
171
+ phase,
172
+ version,
173
+ path: relativePath,
174
+ sha256,
175
+ timestamp: new Date().toISOString(),
176
+ immutable: true,
177
+ content_type: contentType,
178
+ group_id: resolvedGroupId,
179
+ previous_id: previousEntry?.id,
180
+ };
181
+
182
+ return entry;
183
+ }
184
+
185
+ /** Get the file path for a given artifact type and naming components */
186
+ getArtifactPath(
187
+ type: ArtifactType,
188
+ sid: string,
189
+ version: number,
190
+ date: string,
191
+ contentType: ContentType,
192
+ ): string {
193
+ const ext = getExtension(contentType);
194
+ const subdir = ARTIFACT_DIRS[type] ?? 'misc';
195
+ return join(this.docsDir, subdir, `${type}_${sid}_v${version}_${date}${ext}`);
196
+ }
197
+
198
+ /** List all artifacts, optionally filtered by type */
199
+ listArtifacts(type?: ArtifactType): ArtifactEntry[] {
200
+ // Scan for artifact JSON metadata files in a .artifacts/ dir
201
+ const metaDir = join(this.docsDir, '.artifacts');
202
+ if (!existsSync(metaDir)) {
203
+ return [];
204
+ }
205
+
206
+ const entries: ArtifactEntry[] = [];
207
+ const files = readdirSync(metaDir).filter((f) => f.endsWith('.json'));
208
+
209
+ for (const file of files) {
210
+ try {
211
+ const raw = readFileSync(join(metaDir, file), 'utf-8');
212
+ const parsed = JSON.parse(raw) as ArtifactEntry;
213
+ if (!type || parsed.type === type) {
214
+ entries.push(parsed);
215
+ }
216
+ } catch {
217
+ // Skip malformed metadata files
218
+ }
219
+ }
220
+
221
+ return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
222
+ }
223
+
224
+ /** Verify an artifact's SHA-256 matches its stored content */
225
+ verifyArtifact(entry: ArtifactEntry): boolean {
226
+ const fullPath = join(this.projectDir, entry.path);
227
+ if (!existsSync(fullPath)) {
228
+ return false;
229
+ }
230
+
231
+ const content = readFileSync(fullPath, 'utf-8');
232
+ const currentHash = computeSha256(content);
233
+ return currentHash === entry.sha256;
234
+ }
235
+
236
+ /** Get the latest artifact of a given type */
237
+ getLatestArtifact(type: ArtifactType): ArtifactEntry | null {
238
+ const all = this.listArtifacts(type);
239
+ if (all.length === 0) return null;
240
+ return all[all.length - 1];
241
+ }
242
+
243
+ /** Get next version number for a group across existing artifacts */
244
+ getNextVersion(groupId: string, existingArtifacts: ArtifactEntry[]): number {
245
+ const groupArtifacts = existingArtifacts.filter((a) => a.group_id === groupId);
246
+ if (groupArtifacts.length === 0) return 1;
247
+ const maxVersion = Math.max(...groupArtifacts.map((a) => a.version));
248
+ return maxVersion + 1;
249
+ }
250
+
251
+ /** Convert an ArtifactEntry to an ArtifactRef */
252
+ toArtifactRef(entry: ArtifactEntry): ArtifactRef {
253
+ return {
254
+ artifact_id: entry.id,
255
+ path: entry.path,
256
+ sha256: entry.sha256,
257
+ version: entry.version,
258
+ type: entry.type,
259
+ };
260
+ }
261
+
262
+ /** Store artifact metadata for later retrieval */
263
+ storeArtifactMetadata(entry: ArtifactEntry): void {
264
+ const metaDir = join(this.docsDir, '.artifacts');
265
+ if (!existsSync(metaDir)) {
266
+ mkdirSync(metaDir, { recursive: true });
267
+ }
268
+
269
+ const metaPath = join(metaDir, `${entry.id}.json`);
270
+ writeFileSync(metaPath, JSON.stringify(entry, null, 2), 'utf-8');
271
+ }
272
+
273
+ /** Create an artifact and store its metadata in one step */
274
+ createAndStoreText(
275
+ type: ArtifactType,
276
+ markdown: string,
277
+ phase: PipelinePhase,
278
+ groupId?: string,
279
+ ): ArtifactEntry {
280
+ const entry = this.createArtifactText(type, markdown, phase, groupId);
281
+ this.storeArtifactMetadata(entry);
282
+ return entry;
283
+ }
284
+
285
+ /** Create a JSON artifact and store its metadata in one step */
286
+ createAndStoreJson(
287
+ type: ArtifactType,
288
+ jsonObject: unknown,
289
+ phase: PipelinePhase,
290
+ groupId?: string,
291
+ ): ArtifactEntry {
292
+ const entry = this.createArtifactJson(type, jsonObject, phase, groupId);
293
+ this.storeArtifactMetadata(entry);
294
+ return entry;
295
+ }
296
+
297
+ /** Update /docs/INDEX.md with current artifact listing */
298
+ updateIndex(artifacts: ArtifactEntry[]): void {
299
+ this.ensureDocsStructure();
300
+
301
+ const lines: string[] = [
302
+ '# Documentation Index',
303
+ '',
304
+ `> Auto-generated by Popeye Pipeline — ${new Date().toISOString()}`,
305
+ '',
306
+ '## Artifacts',
307
+ '',
308
+ '| Type | Version | Path | Phase | Timestamp |',
309
+ '|------|---------|------|-------|-----------|',
310
+ ];
311
+
312
+ const sorted = [...artifacts].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
313
+
314
+ for (const a of sorted) {
315
+ lines.push(`| ${a.type} | v${a.version} | ${a.path} | ${a.phase} | ${a.timestamp} |`);
316
+ }
317
+
318
+ lines.push('');
319
+ const indexPath = join(this.docsDir, 'INDEX.md');
320
+ writeFileSync(indexPath, lines.join('\n'), 'utf-8');
321
+ }
322
+
323
+ /** Get the latest artifact entry in a specific group */
324
+ private getLatestInGroup(
325
+ groupId: string,
326
+ existingArtifacts: ArtifactEntry[],
327
+ ): ArtifactEntry | null {
328
+ const groupArtifacts = existingArtifacts
329
+ .filter((a) => a.group_id === groupId)
330
+ .sort((a, b) => a.version - b.version);
331
+ if (groupArtifacts.length === 0) return null;
332
+ return groupArtifacts[groupArtifacts.length - 1];
333
+ }
334
+ }
335
+
336
+ /** Factory function */
337
+ export function createArtifactManager(projectDir: string): ArtifactManager {
338
+ return new ArtifactManager({ projectDir });
339
+ }