popeye-cli 1.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/.env.example +25 -0
- package/.prettierrc +8 -0
- package/README.md +320 -0
- package/dist/adapters/claude.d.ts +82 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +230 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/openai.d.ts +48 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +257 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/auth/claude.d.ts +44 -0
- package/dist/auth/claude.d.ts.map +1 -0
- package/dist/auth/claude.js +139 -0
- package/dist/auth/claude.js.map +1 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +141 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keychain.d.ts +66 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +125 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/openai-entry.d.ts +9 -0
- package/dist/auth/openai-entry.d.ts.map +1 -0
- package/dist/auth/openai-entry.js +410 -0
- package/dist/auth/openai-entry.js.map +1 -0
- package/dist/auth/openai.d.ts +71 -0
- package/dist/auth/openai.d.ts.map +1 -0
- package/dist/auth/openai.js +212 -0
- package/dist/auth/openai.js.map +1 -0
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +213 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +162 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/config.d.ts +10 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +215 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +10 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +240 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +18 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +241 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/status.d.ts +18 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +154 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +71 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interactive.d.ts +9 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +330 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/output.d.ts +182 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +355 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/defaults.d.ts +57 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +103 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +138 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +244 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +220 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +141 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/generators/index.d.ts +101 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +200 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/python.d.ts +48 -0
- package/dist/generators/python.d.ts.map +1 -0
- package/dist/generators/python.js +262 -0
- package/dist/generators/python.js.map +1 -0
- package/dist/generators/templates/index.d.ts +6 -0
- package/dist/generators/templates/index.d.ts.map +1 -0
- package/dist/generators/templates/index.js +6 -0
- package/dist/generators/templates/index.js.map +1 -0
- package/dist/generators/templates/python.d.ts +53 -0
- package/dist/generators/templates/python.d.ts.map +1 -0
- package/dist/generators/templates/python.js +454 -0
- package/dist/generators/templates/python.js.map +1 -0
- package/dist/generators/templates/typescript.d.ts +53 -0
- package/dist/generators/templates/typescript.d.ts.map +1 -0
- package/dist/generators/templates/typescript.js +394 -0
- package/dist/generators/templates/typescript.js.map +1 -0
- package/dist/generators/typescript.d.ts +64 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +271 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/state/index.d.ts +168 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +338 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/persistence.d.ts +91 -0
- package/dist/state/persistence.d.ts.map +1 -0
- package/dist/state/persistence.js +201 -0
- package/dist/state/persistence.js.map +1 -0
- package/dist/types/cli.d.ts +132 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +17 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/consensus.d.ts +111 -0
- package/dist/types/consensus.d.ts.map +1 -0
- package/dist/types/consensus.js +29 -0
- package/dist/types/consensus.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +73 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +55 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/workflow.d.ts +236 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +74 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/workflow/consensus.d.ts +89 -0
- package/dist/workflow/consensus.d.ts.map +1 -0
- package/dist/workflow/consensus.js +220 -0
- package/dist/workflow/consensus.js.map +1 -0
- package/dist/workflow/execution-mode.d.ts +82 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -0
- package/dist/workflow/execution-mode.js +346 -0
- package/dist/workflow/execution-mode.js.map +1 -0
- package/dist/workflow/index.d.ts +110 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +283 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +83 -0
- package/dist/workflow/plan-mode.d.ts.map +1 -0
- package/dist/workflow/plan-mode.js +241 -0
- package/dist/workflow/plan-mode.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +87 -0
- package/dist/workflow/test-runner.d.ts.map +1 -0
- package/dist/workflow/test-runner.js +273 -0
- package/dist/workflow/test-runner.js.map +1 -0
- package/eslint.config.js +25 -0
- package/package.json +66 -0
- package/src/adapters/claude.ts +298 -0
- package/src/adapters/openai.ts +300 -0
- package/src/auth/claude.ts +166 -0
- package/src/auth/index.ts +171 -0
- package/src/auth/keychain.ts +138 -0
- package/src/auth/openai-entry.ts +410 -0
- package/src/auth/openai.ts +260 -0
- package/src/auth/server.ts +252 -0
- package/src/cli/commands/auth.ts +194 -0
- package/src/cli/commands/config.ts +241 -0
- package/src/cli/commands/create.ts +308 -0
- package/src/cli/commands/index.ts +10 -0
- package/src/cli/commands/resume.ts +304 -0
- package/src/cli/commands/status.ts +189 -0
- package/src/cli/index.ts +90 -0
- package/src/cli/interactive.ts +418 -0
- package/src/cli/output.ts +410 -0
- package/src/config/defaults.ts +114 -0
- package/src/config/index.ts +315 -0
- package/src/config/schema.ts +164 -0
- package/src/generators/index.ts +251 -0
- package/src/generators/python.ts +318 -0
- package/src/generators/templates/index.ts +6 -0
- package/src/generators/templates/python.ts +465 -0
- package/src/generators/templates/typescript.ts +417 -0
- package/src/generators/typescript.ts +340 -0
- package/src/index.ts +13 -0
- package/src/state/index.ts +454 -0
- package/src/state/persistence.ts +230 -0
- package/src/types/cli.ts +146 -0
- package/src/types/consensus.ts +116 -0
- package/src/types/index.ts +64 -0
- package/src/types/project.ts +85 -0
- package/src/types/workflow.ts +149 -0
- package/src/workflow/consensus.ts +299 -0
- package/src/workflow/execution-mode.ts +517 -0
- package/src/workflow/index.ts +396 -0
- package/src/workflow/plan-mode.ts +356 -0
- package/src/workflow/test-runner.ts +345 -0
- package/tests/adapters/openai.test.ts +145 -0
- package/tests/config/config.test.ts +208 -0
- package/tests/generators/generators.test.ts +185 -0
- package/tests/types/consensus.test.ts +152 -0
- package/tests/types/project.test.ts +134 -0
- package/tests/workflow/consensus.test.ts +221 -0
- package/tests/workflow/test-runner.test.ts +214 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State management module
|
|
3
|
+
* Provides high-level API for managing project state
|
|
4
|
+
*/
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import { loadState, saveState, stateExists, deleteState, backupState, } from './persistence.js';
|
|
7
|
+
// Re-export persistence utilities
|
|
8
|
+
export * from './persistence.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a new project state
|
|
11
|
+
*
|
|
12
|
+
* @param spec - The project specification
|
|
13
|
+
* @param projectDir - The project root directory
|
|
14
|
+
* @returns The newly created project state
|
|
15
|
+
*/
|
|
16
|
+
export async function createProject(spec, projectDir) {
|
|
17
|
+
// Check if project already exists
|
|
18
|
+
if (await stateExists(projectDir)) {
|
|
19
|
+
throw new Error(`Project already exists at ${projectDir}. Use loadProject() instead.`);
|
|
20
|
+
}
|
|
21
|
+
const now = new Date().toISOString();
|
|
22
|
+
const state = {
|
|
23
|
+
id: uuidv4(),
|
|
24
|
+
name: spec.name || 'untitled-project',
|
|
25
|
+
idea: spec.idea,
|
|
26
|
+
language: spec.language,
|
|
27
|
+
openaiModel: spec.openaiModel,
|
|
28
|
+
phase: 'plan',
|
|
29
|
+
status: 'pending',
|
|
30
|
+
milestones: [],
|
|
31
|
+
currentMilestone: null,
|
|
32
|
+
currentTask: null,
|
|
33
|
+
consensusHistory: [],
|
|
34
|
+
createdAt: now,
|
|
35
|
+
updatedAt: now,
|
|
36
|
+
};
|
|
37
|
+
await saveState(projectDir, state);
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load an existing project
|
|
42
|
+
*
|
|
43
|
+
* @param projectDir - The project root directory
|
|
44
|
+
* @returns The project state
|
|
45
|
+
* @throws Error if project doesn't exist
|
|
46
|
+
*/
|
|
47
|
+
export async function loadProject(projectDir) {
|
|
48
|
+
const state = await loadState(projectDir);
|
|
49
|
+
if (!state) {
|
|
50
|
+
throw new Error(`No project found at ${projectDir}. Use createProject() first.`);
|
|
51
|
+
}
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if a project exists at the given directory
|
|
56
|
+
*
|
|
57
|
+
* @param projectDir - The project root directory
|
|
58
|
+
* @returns True if project exists
|
|
59
|
+
*/
|
|
60
|
+
export async function projectExists(projectDir) {
|
|
61
|
+
return stateExists(projectDir);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update project state with partial updates
|
|
65
|
+
*
|
|
66
|
+
* @param projectDir - The project root directory
|
|
67
|
+
* @param updates - Partial state updates
|
|
68
|
+
* @returns The updated state
|
|
69
|
+
*/
|
|
70
|
+
export async function updateState(projectDir, updates) {
|
|
71
|
+
const current = await loadProject(projectDir);
|
|
72
|
+
const updated = {
|
|
73
|
+
...current,
|
|
74
|
+
...updates,
|
|
75
|
+
updatedAt: new Date().toISOString(),
|
|
76
|
+
};
|
|
77
|
+
await saveState(projectDir, updated);
|
|
78
|
+
return updated;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Set the current workflow phase
|
|
82
|
+
*
|
|
83
|
+
* @param projectDir - The project root directory
|
|
84
|
+
* @param phase - The new phase
|
|
85
|
+
* @returns The updated state
|
|
86
|
+
*/
|
|
87
|
+
export async function setPhase(projectDir, phase) {
|
|
88
|
+
return updateState(projectDir, { phase });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Add milestones to the project
|
|
92
|
+
*
|
|
93
|
+
* @param projectDir - The project root directory
|
|
94
|
+
* @param milestones - Milestones to add
|
|
95
|
+
* @returns The updated state
|
|
96
|
+
*/
|
|
97
|
+
export async function addMilestones(projectDir, milestones) {
|
|
98
|
+
const current = await loadProject(projectDir);
|
|
99
|
+
const newMilestones = milestones.map((m, index) => ({
|
|
100
|
+
...m,
|
|
101
|
+
id: `milestone-${current.milestones.length + index + 1}`,
|
|
102
|
+
}));
|
|
103
|
+
return updateState(projectDir, {
|
|
104
|
+
milestones: [...current.milestones, ...newMilestones],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Add tasks to a milestone
|
|
109
|
+
*
|
|
110
|
+
* @param projectDir - The project root directory
|
|
111
|
+
* @param milestoneId - The milestone ID
|
|
112
|
+
* @param tasks - Tasks to add
|
|
113
|
+
* @returns The updated state
|
|
114
|
+
*/
|
|
115
|
+
export async function addTasks(projectDir, milestoneId, tasks) {
|
|
116
|
+
const current = await loadProject(projectDir);
|
|
117
|
+
const milestoneIndex = current.milestones.findIndex((m) => m.id === milestoneId);
|
|
118
|
+
if (milestoneIndex === -1) {
|
|
119
|
+
throw new Error(`Milestone ${milestoneId} not found`);
|
|
120
|
+
}
|
|
121
|
+
const milestone = current.milestones[milestoneIndex];
|
|
122
|
+
const newTasks = tasks.map((t, index) => ({
|
|
123
|
+
...t,
|
|
124
|
+
id: `${milestoneId}-task-${milestone.tasks.length + index + 1}`,
|
|
125
|
+
status: 'pending',
|
|
126
|
+
}));
|
|
127
|
+
const updatedMilestones = [...current.milestones];
|
|
128
|
+
updatedMilestones[milestoneIndex] = {
|
|
129
|
+
...milestone,
|
|
130
|
+
tasks: [...milestone.tasks, ...newTasks],
|
|
131
|
+
};
|
|
132
|
+
return updateState(projectDir, { milestones: updatedMilestones });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Update a task's status
|
|
136
|
+
*
|
|
137
|
+
* @param projectDir - The project root directory
|
|
138
|
+
* @param taskId - The task ID
|
|
139
|
+
* @param status - The new status
|
|
140
|
+
* @param additionalUpdates - Additional task updates
|
|
141
|
+
* @returns The updated state
|
|
142
|
+
*/
|
|
143
|
+
export async function updateTaskStatus(projectDir, taskId, status, additionalUpdates = {}) {
|
|
144
|
+
const current = await loadProject(projectDir);
|
|
145
|
+
const updatedMilestones = current.milestones.map((milestone) => ({
|
|
146
|
+
...milestone,
|
|
147
|
+
tasks: milestone.tasks.map((task) => task.id === taskId ? { ...task, status, ...additionalUpdates } : task),
|
|
148
|
+
}));
|
|
149
|
+
// Update milestone status if all tasks are complete
|
|
150
|
+
const updatedMilestonesWithStatus = updatedMilestones.map((milestone) => {
|
|
151
|
+
const allComplete = milestone.tasks.every((t) => t.status === 'complete');
|
|
152
|
+
const anyInProgress = milestone.tasks.some((t) => t.status === 'in-progress');
|
|
153
|
+
let milestoneStatus = 'pending';
|
|
154
|
+
if (allComplete) {
|
|
155
|
+
milestoneStatus = 'complete';
|
|
156
|
+
}
|
|
157
|
+
else if (anyInProgress || milestone.tasks.some((t) => t.status === 'complete')) {
|
|
158
|
+
milestoneStatus = 'in-progress';
|
|
159
|
+
}
|
|
160
|
+
return { ...milestone, status: milestoneStatus };
|
|
161
|
+
});
|
|
162
|
+
return updateState(projectDir, { milestones: updatedMilestonesWithStatus });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Set the current milestone being worked on
|
|
166
|
+
*
|
|
167
|
+
* @param projectDir - The project root directory
|
|
168
|
+
* @param milestoneId - The milestone ID (or null)
|
|
169
|
+
* @returns The updated state
|
|
170
|
+
*/
|
|
171
|
+
export async function setCurrentMilestone(projectDir, milestoneId) {
|
|
172
|
+
if (milestoneId) {
|
|
173
|
+
const current = await loadProject(projectDir);
|
|
174
|
+
const milestone = current.milestones.find((m) => m.id === milestoneId);
|
|
175
|
+
if (!milestone) {
|
|
176
|
+
throw new Error(`Milestone ${milestoneId} not found`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return updateState(projectDir, { currentMilestone: milestoneId });
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Set the current task being worked on
|
|
183
|
+
*
|
|
184
|
+
* @param projectDir - The project root directory
|
|
185
|
+
* @param taskId - The task ID (or null)
|
|
186
|
+
* @returns The updated state
|
|
187
|
+
*/
|
|
188
|
+
export async function setCurrentTask(projectDir, taskId) {
|
|
189
|
+
return updateState(projectDir, { currentTask: taskId });
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Record a consensus iteration
|
|
193
|
+
*
|
|
194
|
+
* @param projectDir - The project root directory
|
|
195
|
+
* @param iteration - The consensus iteration to record
|
|
196
|
+
* @returns The updated state
|
|
197
|
+
*/
|
|
198
|
+
export async function recordConsensusIteration(projectDir, iteration) {
|
|
199
|
+
const current = await loadProject(projectDir);
|
|
200
|
+
return updateState(projectDir, {
|
|
201
|
+
consensusHistory: [...current.consensusHistory, iteration],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Store the approved plan
|
|
206
|
+
*
|
|
207
|
+
* @param projectDir - The project root directory
|
|
208
|
+
* @param plan - The approved plan content
|
|
209
|
+
* @returns The updated state
|
|
210
|
+
*/
|
|
211
|
+
export async function storePlan(projectDir, plan) {
|
|
212
|
+
return updateState(projectDir, { plan });
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Store the expanded specification
|
|
216
|
+
*
|
|
217
|
+
* @param projectDir - The project root directory
|
|
218
|
+
* @param specification - The expanded specification
|
|
219
|
+
* @returns The updated state
|
|
220
|
+
*/
|
|
221
|
+
export async function storeSpecification(projectDir, specification) {
|
|
222
|
+
return updateState(projectDir, { specification });
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Mark the project as complete
|
|
226
|
+
*
|
|
227
|
+
* @param projectDir - The project root directory
|
|
228
|
+
* @returns The updated state
|
|
229
|
+
*/
|
|
230
|
+
export async function completeProject(projectDir) {
|
|
231
|
+
return updateState(projectDir, {
|
|
232
|
+
status: 'complete',
|
|
233
|
+
phase: 'complete',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Mark the project as failed
|
|
238
|
+
*
|
|
239
|
+
* @param projectDir - The project root directory
|
|
240
|
+
* @param error - The error message
|
|
241
|
+
* @returns The updated state
|
|
242
|
+
*/
|
|
243
|
+
export async function failProject(projectDir, error) {
|
|
244
|
+
return updateState(projectDir, {
|
|
245
|
+
status: 'failed',
|
|
246
|
+
error,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get project progress summary
|
|
251
|
+
*
|
|
252
|
+
* @param projectDir - The project root directory
|
|
253
|
+
* @returns Progress summary
|
|
254
|
+
*/
|
|
255
|
+
export async function getProgress(projectDir) {
|
|
256
|
+
const state = await loadProject(projectDir);
|
|
257
|
+
const totalMilestones = state.milestones.length;
|
|
258
|
+
const completedMilestones = state.milestones.filter((m) => m.status === 'complete').length;
|
|
259
|
+
const allTasks = state.milestones.flatMap((m) => m.tasks);
|
|
260
|
+
const totalTasks = allTasks.length;
|
|
261
|
+
const completedTasks = allTasks.filter((t) => t.status === 'complete').length;
|
|
262
|
+
const percentComplete = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
|
263
|
+
return {
|
|
264
|
+
totalMilestones,
|
|
265
|
+
completedMilestones,
|
|
266
|
+
totalTasks,
|
|
267
|
+
completedTasks,
|
|
268
|
+
percentComplete,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get the next pending task
|
|
273
|
+
*
|
|
274
|
+
* @param projectDir - The project root directory
|
|
275
|
+
* @returns The next task to work on, or null if none
|
|
276
|
+
*/
|
|
277
|
+
export async function getNextTask(projectDir) {
|
|
278
|
+
const state = await loadProject(projectDir);
|
|
279
|
+
for (const milestone of state.milestones) {
|
|
280
|
+
if (milestone.status === 'complete')
|
|
281
|
+
continue;
|
|
282
|
+
const pendingTask = milestone.tasks.find((t) => t.status === 'pending');
|
|
283
|
+
if (pendingTask) {
|
|
284
|
+
return { milestone, task: pendingTask };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Reset project to a specific phase
|
|
291
|
+
*
|
|
292
|
+
* @param projectDir - The project root directory
|
|
293
|
+
* @param phase - The phase to reset to
|
|
294
|
+
* @returns The updated state
|
|
295
|
+
*/
|
|
296
|
+
export async function resetToPhase(projectDir, phase) {
|
|
297
|
+
// Create backup before reset
|
|
298
|
+
await backupState(projectDir);
|
|
299
|
+
const current = await loadProject(projectDir);
|
|
300
|
+
const updates = {
|
|
301
|
+
phase,
|
|
302
|
+
status: 'pending',
|
|
303
|
+
error: undefined,
|
|
304
|
+
};
|
|
305
|
+
if (phase === 'plan') {
|
|
306
|
+
// Reset everything
|
|
307
|
+
updates.milestones = [];
|
|
308
|
+
updates.currentMilestone = null;
|
|
309
|
+
updates.currentTask = null;
|
|
310
|
+
updates.plan = undefined;
|
|
311
|
+
}
|
|
312
|
+
else if (phase === 'execution') {
|
|
313
|
+
// Reset task progress but keep milestones
|
|
314
|
+
updates.milestones = current.milestones.map((m) => ({
|
|
315
|
+
...m,
|
|
316
|
+
status: 'pending',
|
|
317
|
+
tasks: m.tasks.map((t) => ({
|
|
318
|
+
...t,
|
|
319
|
+
status: 'pending',
|
|
320
|
+
testsPassed: undefined,
|
|
321
|
+
error: undefined,
|
|
322
|
+
})),
|
|
323
|
+
}));
|
|
324
|
+
updates.currentMilestone = null;
|
|
325
|
+
updates.currentTask = null;
|
|
326
|
+
}
|
|
327
|
+
return updateState(projectDir, updates);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Delete a project
|
|
331
|
+
*
|
|
332
|
+
* @param projectDir - The project root directory
|
|
333
|
+
* @returns True if project was deleted
|
|
334
|
+
*/
|
|
335
|
+
export async function deleteProject(projectDir) {
|
|
336
|
+
return deleteState(projectDir);
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State persistence module
|
|
3
|
+
* Handles atomic read/write operations for project state
|
|
4
|
+
*/
|
|
5
|
+
import { type ProjectState } from '../types/workflow.js';
|
|
6
|
+
/**
|
|
7
|
+
* Default state directory name
|
|
8
|
+
*/
|
|
9
|
+
export declare const STATE_DIR = ".popeye";
|
|
10
|
+
/**
|
|
11
|
+
* State file name
|
|
12
|
+
*/
|
|
13
|
+
export declare const STATE_FILE = "state.json";
|
|
14
|
+
/**
|
|
15
|
+
* Get the state directory path for a project
|
|
16
|
+
*
|
|
17
|
+
* @param projectDir - The project root directory
|
|
18
|
+
* @returns The state directory path
|
|
19
|
+
*/
|
|
20
|
+
export declare function getStateDir(projectDir: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get the state file path for a project
|
|
23
|
+
*
|
|
24
|
+
* @param projectDir - The project root directory
|
|
25
|
+
* @returns The state file path
|
|
26
|
+
*/
|
|
27
|
+
export declare function getStatePath(projectDir: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Ensure the state directory exists
|
|
30
|
+
*
|
|
31
|
+
* @param projectDir - The project root directory
|
|
32
|
+
*/
|
|
33
|
+
export declare function ensureStateDir(projectDir: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a state file exists for a project
|
|
36
|
+
*
|
|
37
|
+
* @param projectDir - The project root directory
|
|
38
|
+
* @returns True if state file exists
|
|
39
|
+
*/
|
|
40
|
+
export declare function stateExists(projectDir: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Load project state from disk
|
|
43
|
+
*
|
|
44
|
+
* @param projectDir - The project root directory
|
|
45
|
+
* @returns The project state or null if not found
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadState(projectDir: string): Promise<ProjectState | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Save project state to disk using atomic write
|
|
50
|
+
* Uses temp file + rename pattern to prevent corruption
|
|
51
|
+
*
|
|
52
|
+
* @param projectDir - The project root directory
|
|
53
|
+
* @param state - The state to save
|
|
54
|
+
*/
|
|
55
|
+
export declare function saveState(projectDir: string, state: ProjectState): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Delete project state
|
|
58
|
+
*
|
|
59
|
+
* @param projectDir - The project root directory
|
|
60
|
+
* @returns True if state was deleted
|
|
61
|
+
*/
|
|
62
|
+
export declare function deleteState(projectDir: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Create a backup of the current state
|
|
65
|
+
*
|
|
66
|
+
* @param projectDir - The project root directory
|
|
67
|
+
* @returns The backup file path or null if no state exists
|
|
68
|
+
*/
|
|
69
|
+
export declare function backupState(projectDir: string): Promise<string | null>;
|
|
70
|
+
/**
|
|
71
|
+
* List all state backups
|
|
72
|
+
*
|
|
73
|
+
* @param projectDir - The project root directory
|
|
74
|
+
* @returns List of backup file paths
|
|
75
|
+
*/
|
|
76
|
+
export declare function listBackups(projectDir: string): Promise<string[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Restore state from a backup
|
|
79
|
+
*
|
|
80
|
+
* @param backupPath - Path to the backup file
|
|
81
|
+
* @param projectDir - The project root directory
|
|
82
|
+
*/
|
|
83
|
+
export declare function restoreFromBackup(backupPath: string, projectDir: string): Promise<ProjectState>;
|
|
84
|
+
/**
|
|
85
|
+
* Clean up old backups, keeping only the most recent N
|
|
86
|
+
*
|
|
87
|
+
* @param projectDir - The project root directory
|
|
88
|
+
* @param keepCount - Number of backups to keep (default 5)
|
|
89
|
+
*/
|
|
90
|
+
export declare function cleanupBackups(projectDir: string, keepCount?: number): Promise<void>;
|
|
91
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../src/state/persistence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE7E;;GAEG;AACH,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC;;GAEG;AACH,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQtE;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqBhF;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBtF;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWtE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe5E;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAavE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,MAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAU7F"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State persistence module
|
|
3
|
+
* Handles atomic read/write operations for project state
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { ProjectStateSchema } from '../types/workflow.js';
|
|
8
|
+
/**
|
|
9
|
+
* Default state directory name
|
|
10
|
+
*/
|
|
11
|
+
export const STATE_DIR = '.popeye';
|
|
12
|
+
/**
|
|
13
|
+
* State file name
|
|
14
|
+
*/
|
|
15
|
+
export const STATE_FILE = 'state.json';
|
|
16
|
+
/**
|
|
17
|
+
* Get the state directory path for a project
|
|
18
|
+
*
|
|
19
|
+
* @param projectDir - The project root directory
|
|
20
|
+
* @returns The state directory path
|
|
21
|
+
*/
|
|
22
|
+
export function getStateDir(projectDir) {
|
|
23
|
+
return path.join(projectDir, STATE_DIR);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get the state file path for a project
|
|
27
|
+
*
|
|
28
|
+
* @param projectDir - The project root directory
|
|
29
|
+
* @returns The state file path
|
|
30
|
+
*/
|
|
31
|
+
export function getStatePath(projectDir) {
|
|
32
|
+
return path.join(getStateDir(projectDir), STATE_FILE);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Ensure the state directory exists
|
|
36
|
+
*
|
|
37
|
+
* @param projectDir - The project root directory
|
|
38
|
+
*/
|
|
39
|
+
export async function ensureStateDir(projectDir) {
|
|
40
|
+
const stateDir = getStateDir(projectDir);
|
|
41
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if a state file exists for a project
|
|
45
|
+
*
|
|
46
|
+
* @param projectDir - The project root directory
|
|
47
|
+
* @returns True if state file exists
|
|
48
|
+
*/
|
|
49
|
+
export async function stateExists(projectDir) {
|
|
50
|
+
try {
|
|
51
|
+
const statePath = getStatePath(projectDir);
|
|
52
|
+
await fs.access(statePath);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Load project state from disk
|
|
61
|
+
*
|
|
62
|
+
* @param projectDir - The project root directory
|
|
63
|
+
* @returns The project state or null if not found
|
|
64
|
+
*/
|
|
65
|
+
export async function loadState(projectDir) {
|
|
66
|
+
try {
|
|
67
|
+
const statePath = getStatePath(projectDir);
|
|
68
|
+
const content = await fs.readFile(statePath, 'utf-8');
|
|
69
|
+
const data = JSON.parse(content);
|
|
70
|
+
// Validate with Zod schema
|
|
71
|
+
const result = ProjectStateSchema.safeParse(data);
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
console.error('Invalid state file format:', result.error.message);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return result.data;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code === 'ENOENT') {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Save project state to disk using atomic write
|
|
87
|
+
* Uses temp file + rename pattern to prevent corruption
|
|
88
|
+
*
|
|
89
|
+
* @param projectDir - The project root directory
|
|
90
|
+
* @param state - The state to save
|
|
91
|
+
*/
|
|
92
|
+
export async function saveState(projectDir, state) {
|
|
93
|
+
// Ensure state directory exists
|
|
94
|
+
await ensureStateDir(projectDir);
|
|
95
|
+
const statePath = getStatePath(projectDir);
|
|
96
|
+
const tempPath = `${statePath}.tmp.${Date.now()}`;
|
|
97
|
+
// Update the updatedAt timestamp
|
|
98
|
+
const stateToSave = {
|
|
99
|
+
...state,
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
// Validate before saving
|
|
103
|
+
const result = ProjectStateSchema.safeParse(stateToSave);
|
|
104
|
+
if (!result.success) {
|
|
105
|
+
throw new Error(`Invalid state: ${result.error.message}`);
|
|
106
|
+
}
|
|
107
|
+
// Write to temp file
|
|
108
|
+
const content = JSON.stringify(stateToSave, null, 2);
|
|
109
|
+
await fs.writeFile(tempPath, content, 'utf-8');
|
|
110
|
+
// Atomic rename
|
|
111
|
+
await fs.rename(tempPath, statePath);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Delete project state
|
|
115
|
+
*
|
|
116
|
+
* @param projectDir - The project root directory
|
|
117
|
+
* @returns True if state was deleted
|
|
118
|
+
*/
|
|
119
|
+
export async function deleteState(projectDir) {
|
|
120
|
+
try {
|
|
121
|
+
const statePath = getStatePath(projectDir);
|
|
122
|
+
await fs.unlink(statePath);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code === 'ENOENT') {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a backup of the current state
|
|
134
|
+
*
|
|
135
|
+
* @param projectDir - The project root directory
|
|
136
|
+
* @returns The backup file path or null if no state exists
|
|
137
|
+
*/
|
|
138
|
+
export async function backupState(projectDir) {
|
|
139
|
+
const state = await loadState(projectDir);
|
|
140
|
+
if (!state) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const stateDir = getStateDir(projectDir);
|
|
144
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
145
|
+
const backupPath = path.join(stateDir, `state.backup.${timestamp}.json`);
|
|
146
|
+
const content = JSON.stringify(state, null, 2);
|
|
147
|
+
await fs.writeFile(backupPath, content, 'utf-8');
|
|
148
|
+
return backupPath;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* List all state backups
|
|
152
|
+
*
|
|
153
|
+
* @param projectDir - The project root directory
|
|
154
|
+
* @returns List of backup file paths
|
|
155
|
+
*/
|
|
156
|
+
export async function listBackups(projectDir) {
|
|
157
|
+
try {
|
|
158
|
+
const stateDir = getStateDir(projectDir);
|
|
159
|
+
const files = await fs.readdir(stateDir);
|
|
160
|
+
return files
|
|
161
|
+
.filter((f) => f.startsWith('state.backup.') && f.endsWith('.json'))
|
|
162
|
+
.map((f) => path.join(stateDir, f))
|
|
163
|
+
.sort()
|
|
164
|
+
.reverse(); // Most recent first
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Restore state from a backup
|
|
172
|
+
*
|
|
173
|
+
* @param backupPath - Path to the backup file
|
|
174
|
+
* @param projectDir - The project root directory
|
|
175
|
+
*/
|
|
176
|
+
export async function restoreFromBackup(backupPath, projectDir) {
|
|
177
|
+
const content = await fs.readFile(backupPath, 'utf-8');
|
|
178
|
+
const data = JSON.parse(content);
|
|
179
|
+
const result = ProjectStateSchema.safeParse(data);
|
|
180
|
+
if (!result.success) {
|
|
181
|
+
throw new Error(`Invalid backup file: ${result.error.message}`);
|
|
182
|
+
}
|
|
183
|
+
await saveState(projectDir, result.data);
|
|
184
|
+
return result.data;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Clean up old backups, keeping only the most recent N
|
|
188
|
+
*
|
|
189
|
+
* @param projectDir - The project root directory
|
|
190
|
+
* @param keepCount - Number of backups to keep (default 5)
|
|
191
|
+
*/
|
|
192
|
+
export async function cleanupBackups(projectDir, keepCount = 5) {
|
|
193
|
+
const backups = await listBackups(projectDir);
|
|
194
|
+
if (backups.length <= keepCount) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Delete old backups
|
|
198
|
+
const toDelete = backups.slice(keepCount);
|
|
199
|
+
await Promise.all(toDelete.map((backup) => fs.unlink(backup)));
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/state/persistence.ts"],"names":[],"mappings":"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"}
|