popeye-cli 2.0.0 → 2.1.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 +55 -0
- package/CONTRIBUTING.md +23 -2
- package/README.md +47 -18
- package/dist/adapters/gemini.js +3 -3
- package/dist/adapters/openai.js +2 -2
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/gemini.js +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +11 -5
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +9 -1
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +29 -3
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +7 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +1 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/popeye-md.d.ts +32 -0
- package/dist/config/popeye-md.d.ts.map +1 -0
- package/dist/config/popeye-md.js +111 -0
- package/dist/config/popeye-md.js.map +1 -0
- package/dist/config/schema.d.ts +3 -21
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +21 -8
- package/dist/config/schema.js.map +1 -1
- package/dist/pipeline/bridges/review-bridge.d.ts +70 -0
- package/dist/pipeline/bridges/review-bridge.d.ts.map +1 -0
- package/dist/pipeline/bridges/review-bridge.js +266 -0
- package/dist/pipeline/bridges/review-bridge.js.map +1 -0
- package/dist/pipeline/consensus/consensus-runner.js +3 -3
- package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts +2 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +5 -1
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/phases/implementation.d.ts.map +1 -1
- package/dist/pipeline/phases/implementation.js +5 -2
- package/dist/pipeline/phases/implementation.js.map +1 -1
- package/dist/pipeline/phases/intake.d.ts.map +1 -1
- package/dist/pipeline/phases/intake.js +13 -4
- package/dist/pipeline/phases/intake.js.map +1 -1
- package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
- package/dist/pipeline/phases/recovery-loop.js +2 -0
- package/dist/pipeline/phases/recovery-loop.js.map +1 -1
- package/dist/pipeline/type-defs/artifacts.d.ts +5 -0
- package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
- package/dist/pipeline/type-defs/artifacts.js +1 -0
- package/dist/pipeline/type-defs/artifacts.js.map +1 -1
- package/dist/pipeline/type-defs/audit.d.ts +3 -0
- package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
- package/dist/pipeline/type-defs/checks.d.ts +1 -0
- package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
- package/dist/pipeline/type-defs/packets.d.ts +15 -0
- package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
- package/dist/pipeline/type-defs/state.d.ts +6 -0
- package/dist/pipeline/type-defs/state.d.ts.map +1 -1
- package/dist/pipeline/type-defs/state.js +2 -0
- package/dist/pipeline/type-defs/state.js.map +1 -1
- package/dist/types/consensus.d.ts +5 -1
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +15 -4
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/project.d.ts +1 -1
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +39 -10
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +1 -7
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +1 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/upgrade/handlers.js +5 -5
- package/dist/upgrade/handlers.js.map +1 -1
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +18 -14
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/website-strategy.js +1 -1
- package/dist/workflow/website-strategy.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/gemini.ts +3 -3
- package/src/adapters/openai.ts +2 -2
- package/src/auth/gemini.ts +1 -1
- package/src/cli/commands/create.ts +12 -6
- package/src/cli/commands/resume.ts +9 -1
- package/src/cli/interactive.ts +32 -3
- package/src/config/defaults.ts +7 -2
- package/src/config/popeye-md.ts +139 -0
- package/src/config/schema.ts +21 -8
- package/src/pipeline/bridges/review-bridge.ts +371 -0
- package/src/pipeline/consensus/consensus-runner.ts +3 -3
- package/src/pipeline/orchestrator.ts +8 -0
- package/src/pipeline/phases/implementation.ts +6 -2
- package/src/pipeline/phases/intake.ts +18 -4
- package/src/pipeline/phases/recovery-loop.ts +2 -0
- package/src/pipeline/type-defs/artifacts.ts +1 -0
- package/src/pipeline/type-defs/state.ts +2 -0
- package/src/types/consensus.ts +16 -4
- package/src/types/index.ts +1 -0
- package/src/types/project.ts +39 -10
- package/src/types/workflow.ts +1 -1
- package/src/upgrade/handlers.ts +5 -5
- package/src/workflow/index.ts +18 -14
- package/src/workflow/website-strategy.ts +1 -1
- package/tests/cli/model-command.test.ts +19 -9
- package/tests/config/config.test.ts +3 -3
- package/tests/config/popeye-md.test.ts +168 -0
- package/tests/pipeline/bridges/review-bridge.test.ts +243 -0
- package/tests/pipeline/session-guidance.test.ts +205 -0
- package/tests/types/consensus.test.ts +1 -1
- package/tests/workflow/pipeline-bootstrap.test.ts +162 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix A tests — sessionGuidance threading through pipeline.
|
|
3
|
+
* Verifies additionalContext persists in PipelineState and reaches phases.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import {
|
|
8
|
+
createDefaultPipelineState,
|
|
9
|
+
PipelineStateSchema,
|
|
10
|
+
ArtifactTypeSchema,
|
|
11
|
+
} from '../../src/pipeline/types.js';
|
|
12
|
+
|
|
13
|
+
describe('Fix A: sessionGuidance threading', () => {
|
|
14
|
+
describe('PipelineState.sessionGuidance', () => {
|
|
15
|
+
it('should accept sessionGuidance in pipeline state', () => {
|
|
16
|
+
const state = createDefaultPipelineState();
|
|
17
|
+
state.sessionGuidance = 'Upgrade from v1 to v2: preserve API backwards compat';
|
|
18
|
+
|
|
19
|
+
const result = PipelineStateSchema.safeParse(state);
|
|
20
|
+
expect(result.success).toBe(true);
|
|
21
|
+
expect(result.data?.sessionGuidance).toBe(
|
|
22
|
+
'Upgrade from v1 to v2: preserve API backwards compat',
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should allow omitting sessionGuidance (backward compat)', () => {
|
|
27
|
+
const state = createDefaultPipelineState();
|
|
28
|
+
// No sessionGuidance set
|
|
29
|
+
|
|
30
|
+
const result = PipelineStateSchema.safeParse(state);
|
|
31
|
+
expect(result.success).toBe(true);
|
|
32
|
+
expect(result.data?.sessionGuidance).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should allow empty string for sessionGuidance', () => {
|
|
36
|
+
const state = createDefaultPipelineState();
|
|
37
|
+
state.sessionGuidance = '';
|
|
38
|
+
|
|
39
|
+
const result = PipelineStateSchema.safeParse(state);
|
|
40
|
+
expect(result.success).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('additional_context artifact type', () => {
|
|
45
|
+
it('should accept additional_context as valid artifact type', () => {
|
|
46
|
+
const result = ArtifactTypeSchema.safeParse('additional_context');
|
|
47
|
+
expect(result.success).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should still accept all existing artifact types', () => {
|
|
51
|
+
const existingTypes = [
|
|
52
|
+
'master_plan', 'architecture', 'role_plan', 'consensus',
|
|
53
|
+
'arbitration', 'audit_report', 'rca_report', 'production_readiness',
|
|
54
|
+
'change_request',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const type of existingTypes) {
|
|
58
|
+
expect(ArtifactTypeSchema.safeParse(type).success).toBe(true);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('PipelineOptions additionalContext -> sessionGuidance', () => {
|
|
64
|
+
it('should store additionalContext in pipeline state when not already set', () => {
|
|
65
|
+
const pipeline = createDefaultPipelineState();
|
|
66
|
+
const additionalContext = 'Focus on mobile-first responsive design';
|
|
67
|
+
|
|
68
|
+
// Simulates orchestrator logic
|
|
69
|
+
if (additionalContext && !pipeline.sessionGuidance) {
|
|
70
|
+
pipeline.sessionGuidance = additionalContext;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
expect(pipeline.sessionGuidance).toBe('Focus on mobile-first responsive design');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should not overwrite existing sessionGuidance on resume', () => {
|
|
77
|
+
const pipeline = createDefaultPipelineState();
|
|
78
|
+
pipeline.sessionGuidance = 'Original guidance from first run';
|
|
79
|
+
const additionalContext = 'New guidance on resume';
|
|
80
|
+
|
|
81
|
+
// Simulates orchestrator logic
|
|
82
|
+
if (additionalContext && !pipeline.sessionGuidance) {
|
|
83
|
+
pipeline.sessionGuidance = additionalContext;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
expect(pipeline.sessionGuidance).toBe('Original guidance from first run');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should leave sessionGuidance undefined when no additionalContext', () => {
|
|
90
|
+
const pipeline = createDefaultPipelineState();
|
|
91
|
+
const additionalContext: string | undefined = undefined;
|
|
92
|
+
|
|
93
|
+
if (additionalContext && !pipeline.sessionGuidance) {
|
|
94
|
+
pipeline.sessionGuidance = additionalContext;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
expect(pipeline.sessionGuidance).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('INTAKE phase guidance injection', () => {
|
|
102
|
+
it('should prepend guidance to plan input when provided', () => {
|
|
103
|
+
const guidance = 'Upgrade context: migrate from Express to Fastify';
|
|
104
|
+
const expandedIdea = 'Build a REST API with user authentication';
|
|
105
|
+
|
|
106
|
+
const planInput = guidance
|
|
107
|
+
? `${guidance}\n\n---\n\n${expandedIdea}`
|
|
108
|
+
: expandedIdea;
|
|
109
|
+
|
|
110
|
+
expect(planInput).toContain(guidance);
|
|
111
|
+
expect(planInput).toContain('---');
|
|
112
|
+
expect(planInput).toContain(expandedIdea);
|
|
113
|
+
expect(planInput.indexOf(guidance)).toBeLessThan(planInput.indexOf(expandedIdea));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should pass through expandedIdea unchanged when no guidance', () => {
|
|
117
|
+
const guidance = '';
|
|
118
|
+
const expandedIdea = 'Build a REST API with user authentication';
|
|
119
|
+
|
|
120
|
+
const planInput = guidance
|
|
121
|
+
? `${guidance}\n\n---\n\n${expandedIdea}`
|
|
122
|
+
: expandedIdea;
|
|
123
|
+
|
|
124
|
+
expect(planInput).toBe(expandedIdea);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('IMPLEMENTATION phase guidance injection', () => {
|
|
129
|
+
it('should merge role prompt with guidance', () => {
|
|
130
|
+
const combinedRolePrompt = '## BACKEND_PROGRAMMER\nScope: API endpoints';
|
|
131
|
+
const guidance = 'Preserve backwards compatibility with v1 API';
|
|
132
|
+
|
|
133
|
+
const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
|
|
134
|
+
|
|
135
|
+
expect(systemPrompt).toContain(combinedRolePrompt);
|
|
136
|
+
expect(systemPrompt).toContain(guidance);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should use only role prompt when no guidance', () => {
|
|
140
|
+
const combinedRolePrompt = '## BACKEND_PROGRAMMER\nScope: API endpoints';
|
|
141
|
+
const guidance: string | undefined = undefined;
|
|
142
|
+
|
|
143
|
+
const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
|
|
144
|
+
|
|
145
|
+
expect(systemPrompt).toBe(combinedRolePrompt);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should use only guidance when no role prompt', () => {
|
|
149
|
+
const combinedRolePrompt = '';
|
|
150
|
+
const guidance = 'Focus on performance';
|
|
151
|
+
|
|
152
|
+
const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
|
|
153
|
+
|
|
154
|
+
expect(systemPrompt).toBe(guidance);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should return undefined when neither role prompt nor guidance', () => {
|
|
158
|
+
const combinedRolePrompt = '';
|
|
159
|
+
const guidance: string | undefined = undefined;
|
|
160
|
+
|
|
161
|
+
const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
|
|
162
|
+
|
|
163
|
+
expect(systemPrompt).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('RECOVERY_LOOP guidance in RCA prompt', () => {
|
|
168
|
+
it('should include user guidance in RCA prompt when available', () => {
|
|
169
|
+
const debuggerPrompt = 'You are a debugger agent...';
|
|
170
|
+
const guidance = 'User wants API backwards compat preserved';
|
|
171
|
+
const failureEvidence = 'Failed phase: QA_VALIDATION';
|
|
172
|
+
|
|
173
|
+
const rcaPrompt = [
|
|
174
|
+
debuggerPrompt,
|
|
175
|
+
'',
|
|
176
|
+
...(guidance ? ['## User Guidance', guidance, ''] : []),
|
|
177
|
+
'## Failure Evidence',
|
|
178
|
+
failureEvidence,
|
|
179
|
+
].join('\n');
|
|
180
|
+
|
|
181
|
+
expect(rcaPrompt).toContain('## User Guidance');
|
|
182
|
+
expect(rcaPrompt).toContain(guidance);
|
|
183
|
+
expect(rcaPrompt.indexOf('User Guidance')).toBeLessThan(
|
|
184
|
+
rcaPrompt.indexOf('Failure Evidence'),
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should omit User Guidance section when no guidance', () => {
|
|
189
|
+
const debuggerPrompt = 'You are a debugger agent...';
|
|
190
|
+
const guidance: string | undefined = undefined;
|
|
191
|
+
const failureEvidence = 'Failed phase: QA_VALIDATION';
|
|
192
|
+
|
|
193
|
+
const rcaPrompt = [
|
|
194
|
+
debuggerPrompt,
|
|
195
|
+
'',
|
|
196
|
+
...(guidance ? ['## User Guidance', guidance, ''] : []),
|
|
197
|
+
'## Failure Evidence',
|
|
198
|
+
failureEvidence,
|
|
199
|
+
].join('\n');
|
|
200
|
+
|
|
201
|
+
expect(rcaPrompt).not.toContain('## User Guidance');
|
|
202
|
+
expect(rcaPrompt).toContain('## Failure Evidence');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -141,7 +141,7 @@ describe('DEFAULT_CONSENSUS_CONFIG', () => {
|
|
|
141
141
|
it('should have valid default values', () => {
|
|
142
142
|
expect(DEFAULT_CONSENSUS_CONFIG.threshold).toBe(95);
|
|
143
143
|
expect(DEFAULT_CONSENSUS_CONFIG.maxIterations).toBe(10);
|
|
144
|
-
expect(DEFAULT_CONSENSUS_CONFIG.openaiModel).toBe('gpt-
|
|
144
|
+
expect(DEFAULT_CONSENSUS_CONFIG.openaiModel).toBe('gpt-4.1');
|
|
145
145
|
expect(DEFAULT_CONSENSUS_CONFIG.temperature).toBe(0.3);
|
|
146
146
|
expect(DEFAULT_CONSENSUS_CONFIG.maxTokens).toBe(4096);
|
|
147
147
|
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fix B tests — new projects use pipeline from start.
|
|
3
|
+
* Verifies that runWorkflow() bootstraps state before pipeline.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
|
|
8
|
+
// Mock the modules before importing
|
|
9
|
+
vi.mock('../../src/state/index.js', () => ({
|
|
10
|
+
loadProject: vi.fn(),
|
|
11
|
+
projectExists: vi.fn(),
|
|
12
|
+
getProgress: vi.fn(),
|
|
13
|
+
resetToPhase: vi.fn(),
|
|
14
|
+
deleteProject: vi.fn(),
|
|
15
|
+
verifyProjectCompletion: vi.fn(),
|
|
16
|
+
resetIncompleteProject: vi.fn(),
|
|
17
|
+
createProject: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('../../src/pipeline/orchestrator.js', () => ({
|
|
21
|
+
runPipeline: vi.fn(),
|
|
22
|
+
resumePipeline: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('../../src/workflow/plan-mode.js', () => ({
|
|
26
|
+
runPlanMode: vi.fn(),
|
|
27
|
+
resumePlanMode: vi.fn(),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock('../../src/workflow/execution-mode.js', () => ({
|
|
31
|
+
runExecutionMode: vi.fn(),
|
|
32
|
+
resumeExecutionMode: vi.fn(),
|
|
33
|
+
executeSingleTask: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
vi.mock('../../src/workflow/workflow-logger.js', () => ({
|
|
37
|
+
getWorkflowLogger: () => ({
|
|
38
|
+
stageStart: vi.fn(),
|
|
39
|
+
stageComplete: vi.fn(),
|
|
40
|
+
stageFailed: vi.fn(),
|
|
41
|
+
info: vi.fn(),
|
|
42
|
+
}),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
describe('Fix B: New projects use pipeline from start', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.resetAllMocks();
|
|
48
|
+
// Default: pipeline mode enabled
|
|
49
|
+
delete process.env.POPEYE_LEGACY_WORKFLOW;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should create state and run pipeline when state does not exist', async () => {
|
|
53
|
+
const { loadProject, createProject } = await import('../../src/state/index.js');
|
|
54
|
+
const { runPipeline } = await import('../../src/pipeline/orchestrator.js');
|
|
55
|
+
|
|
56
|
+
const mockState = {
|
|
57
|
+
id: 'test-id',
|
|
58
|
+
name: 'test-project',
|
|
59
|
+
idea: 'Build something',
|
|
60
|
+
language: 'python' as const,
|
|
61
|
+
phase: 'plan' as const,
|
|
62
|
+
status: 'pending' as const,
|
|
63
|
+
milestones: [],
|
|
64
|
+
currentMilestone: null,
|
|
65
|
+
currentTask: null,
|
|
66
|
+
consensusHistory: [],
|
|
67
|
+
createdAt: new Date().toISOString(),
|
|
68
|
+
updatedAt: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// loadProject fails (no state.json) on first call, succeeds after create
|
|
72
|
+
vi.mocked(loadProject)
|
|
73
|
+
.mockRejectedValueOnce(new Error('No project found'))
|
|
74
|
+
.mockResolvedValue(mockState as any);
|
|
75
|
+
|
|
76
|
+
vi.mocked(createProject).mockResolvedValue(mockState as any);
|
|
77
|
+
vi.mocked(runPipeline).mockResolvedValue({
|
|
78
|
+
success: true,
|
|
79
|
+
finalPhase: 'DONE',
|
|
80
|
+
artifacts: [],
|
|
81
|
+
recoveryIterations: 0,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const { runWorkflow } = await import('../../src/workflow/index.js');
|
|
85
|
+
|
|
86
|
+
const result = await runWorkflow(
|
|
87
|
+
{ idea: 'Build something', name: 'test-project', language: 'python', openaiModel: 'gpt-4o' },
|
|
88
|
+
{ projectDir: '/tmp/test-project' },
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(createProject).toHaveBeenCalledWith(
|
|
92
|
+
expect.objectContaining({ idea: 'Build something' }),
|
|
93
|
+
'/tmp/test-project',
|
|
94
|
+
);
|
|
95
|
+
expect(runPipeline).toHaveBeenCalledWith(
|
|
96
|
+
expect.objectContaining({
|
|
97
|
+
projectDir: '/tmp/test-project',
|
|
98
|
+
state: mockState,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
expect(result.success).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should use existing state when state already exists', async () => {
|
|
105
|
+
const { loadProject, createProject } = await import('../../src/state/index.js');
|
|
106
|
+
const { runPipeline } = await import('../../src/pipeline/orchestrator.js');
|
|
107
|
+
|
|
108
|
+
const existingState = {
|
|
109
|
+
id: 'existing-id',
|
|
110
|
+
name: 'existing-project',
|
|
111
|
+
phase: 'execution' as const,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
vi.mocked(loadProject).mockResolvedValue(existingState as any);
|
|
115
|
+
vi.mocked(runPipeline).mockResolvedValue({
|
|
116
|
+
success: true,
|
|
117
|
+
finalPhase: 'DONE',
|
|
118
|
+
artifacts: [],
|
|
119
|
+
recoveryIterations: 0,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const { runWorkflow } = await import('../../src/workflow/index.js');
|
|
123
|
+
|
|
124
|
+
await runWorkflow(
|
|
125
|
+
{ idea: 'Build something', name: 'existing-project', language: 'python', openaiModel: 'gpt-4o' },
|
|
126
|
+
{ projectDir: '/tmp/existing-project' },
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Should NOT call createProject since state exists
|
|
130
|
+
expect(createProject).not.toHaveBeenCalled();
|
|
131
|
+
expect(runPipeline).toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should fall through to legacy workflow when POPEYE_LEGACY_WORKFLOW=1', async () => {
|
|
135
|
+
process.env.POPEYE_LEGACY_WORKFLOW = '1';
|
|
136
|
+
|
|
137
|
+
const { runPipeline } = await import('../../src/pipeline/orchestrator.js');
|
|
138
|
+
const { runPlanMode } = await import('../../src/workflow/plan-mode.js');
|
|
139
|
+
const { runExecutionMode } = await import('../../src/workflow/execution-mode.js');
|
|
140
|
+
|
|
141
|
+
vi.mocked(runPlanMode).mockResolvedValue({
|
|
142
|
+
success: true,
|
|
143
|
+
state: { phase: 'execution' } as any,
|
|
144
|
+
});
|
|
145
|
+
vi.mocked(runExecutionMode).mockResolvedValue({
|
|
146
|
+
success: true,
|
|
147
|
+
state: { phase: 'complete' } as any,
|
|
148
|
+
completedTasks: 5,
|
|
149
|
+
failedTasks: 0,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const { runWorkflow } = await import('../../src/workflow/index.js');
|
|
153
|
+
|
|
154
|
+
await runWorkflow(
|
|
155
|
+
{ idea: 'Build something', name: 'test', language: 'python', openaiModel: 'gpt-4o' },
|
|
156
|
+
{ projectDir: '/tmp/test' },
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Pipeline should NOT be called in legacy mode
|
|
160
|
+
expect(runPipeline).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
});
|