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