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.
- package/CHANGELOG.md +59 -0
- package/CONTRIBUTING.md +15 -1
- package/README.md +57 -0
- package/dist/pipeline/artifact-manager.d.ts +47 -0
- package/dist/pipeline/artifact-manager.d.ts.map +1 -0
- package/dist/pipeline/artifact-manager.js +251 -0
- package/dist/pipeline/artifact-manager.js.map +1 -0
- package/dist/pipeline/artifact-validators.d.ts +29 -0
- package/dist/pipeline/artifact-validators.d.ts.map +1 -0
- package/dist/pipeline/artifact-validators.js +173 -0
- package/dist/pipeline/artifact-validators.js.map +1 -0
- package/dist/pipeline/change-request.d.ts +47 -0
- package/dist/pipeline/change-request.d.ts.map +1 -0
- package/dist/pipeline/change-request.js +91 -0
- package/dist/pipeline/change-request.js.map +1 -0
- package/dist/pipeline/check-runner.d.ts +47 -0
- package/dist/pipeline/check-runner.d.ts.map +1 -0
- package/dist/pipeline/check-runner.js +417 -0
- package/dist/pipeline/check-runner.js.map +1 -0
- package/dist/pipeline/command-resolver.d.ts +9 -0
- package/dist/pipeline/command-resolver.d.ts.map +1 -0
- package/dist/pipeline/command-resolver.js +140 -0
- package/dist/pipeline/command-resolver.js.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
- package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.js +212 -0
- package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
- package/dist/pipeline/constitution.d.ts +45 -0
- package/dist/pipeline/constitution.d.ts.map +1 -0
- package/dist/pipeline/constitution.js +82 -0
- package/dist/pipeline/constitution.js.map +1 -0
- package/dist/pipeline/gate-engine.d.ts +55 -0
- package/dist/pipeline/gate-engine.d.ts.map +1 -0
- package/dist/pipeline/gate-engine.js +270 -0
- package/dist/pipeline/gate-engine.js.map +1 -0
- package/dist/pipeline/index.d.ts +26 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +35 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/migration.d.ts +15 -0
- package/dist/pipeline/migration.d.ts.map +1 -0
- package/dist/pipeline/migration.js +76 -0
- package/dist/pipeline/migration.js.map +1 -0
- package/dist/pipeline/orchestrator.d.ts +28 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/pipeline/orchestrator.js +238 -0
- package/dist/pipeline/orchestrator.js.map +1 -0
- package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
- package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/audit-report-builder.js +32 -0
- package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
- package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
- package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
- package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
- package/dist/pipeline/packets/index.d.ts +12 -0
- package/dist/pipeline/packets/index.d.ts.map +1 -0
- package/dist/pipeline/packets/index.js +8 -0
- package/dist/pipeline/packets/index.js.map +1 -0
- package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
- package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/plan-packet-builder.js +27 -0
- package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
- package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
- package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
- package/dist/pipeline/packets/rca-packet-builder.js +22 -0
- package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
- package/dist/pipeline/phases/architecture.d.ts +7 -0
- package/dist/pipeline/phases/architecture.d.ts.map +1 -0
- package/dist/pipeline/phases/architecture.js +60 -0
- package/dist/pipeline/phases/architecture.js.map +1 -0
- package/dist/pipeline/phases/audit.d.ts +8 -0
- package/dist/pipeline/phases/audit.d.ts.map +1 -0
- package/dist/pipeline/phases/audit.js +144 -0
- package/dist/pipeline/phases/audit.js.map +1 -0
- package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
- package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-architecture.js +84 -0
- package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
- package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
- package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-master-plan.js +81 -0
- package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
- package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
- package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
- package/dist/pipeline/phases/consensus-role-plans.js +85 -0
- package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
- package/dist/pipeline/phases/done.d.ts +7 -0
- package/dist/pipeline/phases/done.d.ts.map +1 -0
- package/dist/pipeline/phases/done.js +45 -0
- package/dist/pipeline/phases/done.js.map +1 -0
- package/dist/pipeline/phases/implementation.d.ts +8 -0
- package/dist/pipeline/phases/implementation.d.ts.map +1 -0
- package/dist/pipeline/phases/implementation.js +42 -0
- package/dist/pipeline/phases/implementation.js.map +1 -0
- package/dist/pipeline/phases/index.d.ts +20 -0
- package/dist/pipeline/phases/index.d.ts.map +1 -0
- package/dist/pipeline/phases/index.js +19 -0
- package/dist/pipeline/phases/index.js.map +1 -0
- package/dist/pipeline/phases/intake.d.ts +8 -0
- package/dist/pipeline/phases/intake.d.ts.map +1 -0
- package/dist/pipeline/phases/intake.js +40 -0
- package/dist/pipeline/phases/intake.js.map +1 -0
- package/dist/pipeline/phases/phase-context.d.ts +30 -0
- package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
- package/dist/pipeline/phases/phase-context.js +33 -0
- package/dist/pipeline/phases/phase-context.js.map +1 -0
- package/dist/pipeline/phases/production-gate.d.ts +8 -0
- package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
- package/dist/pipeline/phases/production-gate.js +84 -0
- package/dist/pipeline/phases/production-gate.js.map +1 -0
- package/dist/pipeline/phases/qa-validation.d.ts +7 -0
- package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
- package/dist/pipeline/phases/qa-validation.js +50 -0
- package/dist/pipeline/phases/qa-validation.js.map +1 -0
- package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
- package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
- package/dist/pipeline/phases/recovery-loop.js +91 -0
- package/dist/pipeline/phases/recovery-loop.js.map +1 -0
- package/dist/pipeline/phases/review.d.ts +8 -0
- package/dist/pipeline/phases/review.d.ts.map +1 -0
- package/dist/pipeline/phases/review.js +127 -0
- package/dist/pipeline/phases/review.js.map +1 -0
- package/dist/pipeline/phases/role-planning.d.ts +7 -0
- package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
- package/dist/pipeline/phases/role-planning.js +75 -0
- package/dist/pipeline/phases/role-planning.js.map +1 -0
- package/dist/pipeline/phases/stuck.d.ts +7 -0
- package/dist/pipeline/phases/stuck.d.ts.map +1 -0
- package/dist/pipeline/phases/stuck.js +51 -0
- package/dist/pipeline/phases/stuck.js.map +1 -0
- package/dist/pipeline/repo-snapshot.d.ts +24 -0
- package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
- package/dist/pipeline/repo-snapshot.js +343 -0
- package/dist/pipeline/repo-snapshot.js.map +1 -0
- package/dist/pipeline/role-execution-adapter.d.ts +59 -0
- package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
- package/dist/pipeline/role-execution-adapter.js +159 -0
- package/dist/pipeline/role-execution-adapter.js.map +1 -0
- package/dist/pipeline/skill-loader.d.ts +34 -0
- package/dist/pipeline/skill-loader.d.ts.map +1 -0
- package/dist/pipeline/skill-loader.js +156 -0
- package/dist/pipeline/skill-loader.js.map +1 -0
- package/dist/pipeline/skills/defaults.d.ts +16 -0
- package/dist/pipeline/skills/defaults.d.ts.map +1 -0
- package/dist/pipeline/skills/defaults.js +189 -0
- package/dist/pipeline/skills/defaults.js.map +1 -0
- package/dist/pipeline/type-defs/artifacts.d.ts +202 -0
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
- package/dist/pipeline/type-defs/artifacts.js +66 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -0
- package/dist/pipeline/type-defs/audit.d.ts +256 -0
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
- package/dist/pipeline/type-defs/audit.js +54 -0
- package/dist/pipeline/type-defs/audit.js.map +1 -0
- package/dist/pipeline/type-defs/checks.d.ts +81 -0
- package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
- package/dist/pipeline/type-defs/checks.js +38 -0
- package/dist/pipeline/type-defs/checks.js.map +1 -0
- package/dist/pipeline/type-defs/enums.d.ts +43 -0
- package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
- package/dist/pipeline/type-defs/enums.js +55 -0
- package/dist/pipeline/type-defs/enums.js.map +1 -0
- package/dist/pipeline/type-defs/index.d.ts +12 -0
- package/dist/pipeline/type-defs/index.d.ts.map +1 -0
- package/dist/pipeline/type-defs/index.js +12 -0
- package/dist/pipeline/type-defs/index.js.map +1 -0
- package/dist/pipeline/type-defs/packets.d.ts +806 -0
- package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
- package/dist/pipeline/type-defs/packets.js +109 -0
- package/dist/pipeline/type-defs/packets.js.map +1 -0
- package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
- package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
- package/dist/pipeline/type-defs/snapshot.js +35 -0
- package/dist/pipeline/type-defs/snapshot.js.map +1 -0
- package/dist/pipeline/type-defs/state.d.ts +449 -0
- package/dist/pipeline/type-defs/state.d.ts.map +1 -0
- package/dist/pipeline/type-defs/state.js +88 -0
- package/dist/pipeline/type-defs/state.js.map +1 -0
- package/dist/pipeline/types.d.ts +16 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +16 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/types/audit.d.ts +6 -6
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +48 -0
- package/dist/workflow/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/PHASE_GATE_ENGINE_SPEC.md +113 -20
- package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +66 -13
- package/src/pipeline/artifact-manager.ts +339 -0
- package/src/pipeline/artifact-validators.ts +224 -0
- package/src/pipeline/change-request.ts +119 -0
- package/src/pipeline/check-runner.ts +504 -0
- package/src/pipeline/command-resolver.ts +168 -0
- package/src/pipeline/consensus/consensus-runner.ts +317 -0
- package/src/pipeline/constitution.ts +109 -0
- package/src/pipeline/gate-engine.ts +347 -0
- package/src/pipeline/index.ts +82 -0
- package/src/pipeline/migration.ts +91 -0
- package/src/pipeline/orchestrator.ts +314 -0
- package/src/pipeline/packets/audit-report-builder.ts +47 -0
- package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
- package/src/pipeline/packets/index.ts +15 -0
- package/src/pipeline/packets/plan-packet-builder.ts +52 -0
- package/src/pipeline/packets/rca-packet-builder.ts +38 -0
- package/src/pipeline/phases/architecture.ts +73 -0
- package/src/pipeline/phases/audit.ts +193 -0
- package/src/pipeline/phases/consensus-architecture.ts +104 -0
- package/src/pipeline/phases/consensus-master-plan.ts +100 -0
- package/src/pipeline/phases/consensus-role-plans.ts +105 -0
- package/src/pipeline/phases/done.ts +68 -0
- package/src/pipeline/phases/implementation.ts +48 -0
- package/src/pipeline/phases/index.ts +21 -0
- package/src/pipeline/phases/intake.ts +54 -0
- package/src/pipeline/phases/phase-context.ts +86 -0
- package/src/pipeline/phases/production-gate.ts +113 -0
- package/src/pipeline/phases/qa-validation.ts +63 -0
- package/src/pipeline/phases/recovery-loop.ts +118 -0
- package/src/pipeline/phases/review.ts +149 -0
- package/src/pipeline/phases/role-planning.ts +92 -0
- package/src/pipeline/phases/stuck.ts +62 -0
- package/src/pipeline/repo-snapshot.ts +395 -0
- package/src/pipeline/role-execution-adapter.ts +238 -0
- package/src/pipeline/skill-loader.ts +192 -0
- package/src/pipeline/skills/defaults.ts +215 -0
- package/src/pipeline/type-defs/artifacts.ts +81 -0
- package/src/pipeline/type-defs/audit.ts +67 -0
- package/src/pipeline/type-defs/checks.ts +47 -0
- package/src/pipeline/type-defs/enums.ts +62 -0
- package/src/pipeline/type-defs/index.ts +12 -0
- package/src/pipeline/type-defs/packets.ts +131 -0
- package/src/pipeline/type-defs/snapshot.ts +55 -0
- package/src/pipeline/type-defs/state.ts +165 -0
- package/src/pipeline/types.ts +16 -0
- package/src/workflow/index.ts +48 -0
- package/tests/pipeline/artifact-manager.test.ts +183 -0
- package/tests/pipeline/artifact-validators.test.ts +207 -0
- package/tests/pipeline/change-request.test.ts +180 -0
- package/tests/pipeline/check-runner.test.ts +157 -0
- package/tests/pipeline/command-resolver.test.ts +159 -0
- package/tests/pipeline/consensus-runner.test.ts +206 -0
- package/tests/pipeline/consensus-scoring.test.ts +163 -0
- package/tests/pipeline/constitution.test.ts +122 -0
- package/tests/pipeline/gate-engine.test.ts +195 -0
- package/tests/pipeline/migration.test.ts +133 -0
- package/tests/pipeline/orchestrator.test.ts +614 -0
- package/tests/pipeline/packets/builders.test.ts +347 -0
- package/tests/pipeline/repo-snapshot.test.ts +189 -0
- package/tests/pipeline/role-execution-adapter.test.ts +299 -0
- package/tests/pipeline/skill-loader.test.ts +186 -0
- package/tests/pipeline/start-env-checks.test.ts +123 -0
- package/tests/pipeline/types.test.ts +156 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Popeye Full Autonomy Pipeline Spec
|
|
2
|
-
Version: 1.
|
|
3
|
-
Goal: 1 idea
|
|
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
|
|
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 (FE
|
|
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
|
|
351
|
-
- Audit Report schema (
|
|
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
|
|
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) **
|
|
420
|
-
-
|
|
421
|
-
- If
|
|
422
|
-
-
|
|
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
|
|
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
|
|
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
|
+
}
|