awesome-slash 2.4.2
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/.claude-plugin/marketplace.json +54 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +261 -0
- package/LICENSE +21 -0
- package/README.md +363 -0
- package/SECURITY.md +101 -0
- package/adapters/README.md +256 -0
- package/adapters/codex/README.md +272 -0
- package/adapters/codex/install.sh +179 -0
- package/adapters/opencode/README.md +301 -0
- package/adapters/opencode/install.sh +223 -0
- package/lib/patterns/review-patterns.js +511 -0
- package/lib/patterns/slop-patterns.js +647 -0
- package/lib/platform/detect-platform.js +535 -0
- package/lib/platform/verify-tools.js +235 -0
- package/lib/state/workflow-state.js +635 -0
- package/lib/state/workflow-state.schema.json +282 -0
- package/lib/utils/context-optimizer.js +227 -0
- package/mcp-server/index.js +303 -0
- package/mcp-server/package.json +23 -0
- package/package.json +63 -0
- package/plugins/deslop-around/.claude-plugin/plugin.json +20 -0
- package/plugins/deslop-around/commands/deslop-around.md +220 -0
- package/plugins/deslop-around/lib/patterns/review-patterns.js +511 -0
- package/plugins/deslop-around/lib/patterns/slop-patterns.js +641 -0
- package/plugins/deslop-around/lib/platform/detect-platform.js +514 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +235 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +635 -0
- package/plugins/deslop-around/lib/state/workflow-state.schema.json +282 -0
- package/plugins/deslop-around/lib/utils/context-optimizer.js +222 -0
- package/plugins/next-task/.claude-plugin/plugin.json +24 -0
- package/plugins/next-task/agents/ci-fixer.md +236 -0
- package/plugins/next-task/agents/ci-monitor.md +291 -0
- package/plugins/next-task/agents/delivery-validator.md +451 -0
- package/plugins/next-task/agents/deslop-work.md +272 -0
- package/plugins/next-task/agents/docs-updater.md +506 -0
- package/plugins/next-task/agents/exploration-agent.md +277 -0
- package/plugins/next-task/agents/implementation-agent.md +427 -0
- package/plugins/next-task/agents/planning-agent.md +236 -0
- package/plugins/next-task/agents/policy-selector.md +248 -0
- package/plugins/next-task/agents/review-orchestrator.md +521 -0
- package/plugins/next-task/agents/simple-fixer.md +136 -0
- package/plugins/next-task/agents/task-discoverer.md +357 -0
- package/plugins/next-task/agents/test-coverage-checker.md +447 -0
- package/plugins/next-task/agents/worktree-manager.md +419 -0
- package/plugins/next-task/commands/delivery-approval.md +331 -0
- package/plugins/next-task/commands/next-task.md +627 -0
- package/plugins/next-task/commands/update-docs-around.md +418 -0
- package/plugins/next-task/hooks/hooks.json +14 -0
- package/plugins/next-task/lib/patterns/review-patterns.js +511 -0
- package/plugins/next-task/lib/patterns/slop-patterns.js +641 -0
- package/plugins/next-task/lib/platform/detect-platform.js +514 -0
- package/plugins/next-task/lib/platform/verify-tools.js +235 -0
- package/plugins/next-task/lib/state/tasks-registry.schema.json +85 -0
- package/plugins/next-task/lib/state/workflow-state.js +635 -0
- package/plugins/next-task/lib/state/workflow-state.schema.json +282 -0
- package/plugins/next-task/lib/state/worktree-status.schema.json +219 -0
- package/plugins/next-task/lib/utils/context-optimizer.js +222 -0
- package/plugins/project-review/.claude-plugin/plugin.json +20 -0
- package/plugins/project-review/commands/project-review-agents.md +286 -0
- package/plugins/project-review/commands/project-review-github.md +142 -0
- package/plugins/project-review/commands/project-review.md +273 -0
- package/plugins/project-review/lib/patterns/review-patterns.js +511 -0
- package/plugins/project-review/lib/patterns/slop-patterns.js +641 -0
- package/plugins/project-review/lib/platform/detect-platform.js +514 -0
- package/plugins/project-review/lib/platform/verify-tools.js +235 -0
- package/plugins/project-review/lib/state/workflow-state.js +635 -0
- package/plugins/project-review/lib/state/workflow-state.schema.json +282 -0
- package/plugins/project-review/lib/utils/context-optimizer.js +222 -0
- package/plugins/reality-check/.claude-plugin/plugin.json +23 -0
- package/plugins/reality-check/README.md +156 -0
- package/plugins/reality-check/agents/code-explorer.md +353 -0
- package/plugins/reality-check/agents/doc-analyzer.md +337 -0
- package/plugins/reality-check/agents/issue-scanner.md +231 -0
- package/plugins/reality-check/agents/plan-synthesizer.md +479 -0
- package/plugins/reality-check/commands/scan.md +242 -0
- package/plugins/reality-check/commands/set.md +203 -0
- package/plugins/reality-check/lib/state/reality-check-state.js +509 -0
- package/plugins/reality-check/skills/reality-analysis/SKILL.md +317 -0
- package/plugins/ship/.claude-plugin/plugin.json +21 -0
- package/plugins/ship/commands/ship-ci-review-loop.md +443 -0
- package/plugins/ship/commands/ship-deployment.md +330 -0
- package/plugins/ship/commands/ship-error-handling.md +254 -0
- package/plugins/ship/commands/ship.md +370 -0
- package/plugins/ship/lib/patterns/review-patterns.js +511 -0
- package/plugins/ship/lib/patterns/slop-patterns.js +641 -0
- package/plugins/ship/lib/platform/detect-platform.js +514 -0
- package/plugins/ship/lib/platform/verify-tools.js +235 -0
- package/plugins/ship/lib/state/workflow-state.js +635 -0
- package/plugins/ship/lib/state/workflow-state.schema.json +282 -0
- package/plugins/ship/lib/utils/context-optimizer.js +222 -0
- package/scripts/install/claude.sh +50 -0
- package/scripts/install/codex.sh +181 -0
- package/scripts/install/opencode.sh +211 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow State Management
|
|
3
|
+
*
|
|
4
|
+
* Persistent state management for next-task workflow orchestration.
|
|
5
|
+
* Enables resume capability, multi-agent coordination, and progress tracking.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
|
|
12
|
+
const SCHEMA_VERSION = '2.0.0';
|
|
13
|
+
const STATE_DIR = '.claude';
|
|
14
|
+
const STATE_FILE = 'workflow-state.json';
|
|
15
|
+
|
|
16
|
+
const PHASES = [
|
|
17
|
+
'policy-selection',
|
|
18
|
+
'task-discovery',
|
|
19
|
+
'worktree-setup',
|
|
20
|
+
'exploration',
|
|
21
|
+
'planning',
|
|
22
|
+
'user-approval',
|
|
23
|
+
'implementation',
|
|
24
|
+
'review-loop',
|
|
25
|
+
'delivery-approval',
|
|
26
|
+
'ship-prep',
|
|
27
|
+
'create-pr',
|
|
28
|
+
'ci-wait',
|
|
29
|
+
'comment-fix',
|
|
30
|
+
'merge',
|
|
31
|
+
'production-ci',
|
|
32
|
+
'deploy',
|
|
33
|
+
'production-release',
|
|
34
|
+
'complete'
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const DEFAULT_POLICY = {
|
|
38
|
+
taskSource: 'gh-issues',
|
|
39
|
+
priorityFilter: 'continue',
|
|
40
|
+
platform: 'detected',
|
|
41
|
+
stoppingPoint: 'merged',
|
|
42
|
+
mergeStrategy: 'squash',
|
|
43
|
+
autoFix: true,
|
|
44
|
+
maxReviewIterations: 3
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate a unique workflow ID
|
|
49
|
+
* @returns {string} Workflow ID in format: workflow-YYYYMMDD-HHMMSS-random
|
|
50
|
+
*/
|
|
51
|
+
function generateWorkflowId() {
|
|
52
|
+
const now = new Date();
|
|
53
|
+
const date = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
54
|
+
const time = now.toISOString().slice(11, 19).replace(/:/g, '');
|
|
55
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
56
|
+
return `workflow-${date}-${time}-${random}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the state file path
|
|
61
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
62
|
+
* @returns {string} Full path to state file
|
|
63
|
+
*/
|
|
64
|
+
function getStatePath(baseDir = process.cwd()) {
|
|
65
|
+
return path.join(baseDir, STATE_DIR, STATE_FILE);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Ensure state directory exists
|
|
70
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
71
|
+
*/
|
|
72
|
+
function ensureStateDir(baseDir = process.cwd()) {
|
|
73
|
+
const stateDir = path.join(baseDir, STATE_DIR);
|
|
74
|
+
if (!fs.existsSync(stateDir)) {
|
|
75
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a new workflow state
|
|
81
|
+
* @param {string} [type='next-task'] - Workflow type
|
|
82
|
+
* @param {Object} [policy={}] - Policy overrides
|
|
83
|
+
* @returns {Object} New workflow state
|
|
84
|
+
*/
|
|
85
|
+
function createState(type = 'next-task', policy = {}) {
|
|
86
|
+
const now = new Date().toISOString();
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
version: SCHEMA_VERSION,
|
|
90
|
+
workflow: {
|
|
91
|
+
id: generateWorkflowId(),
|
|
92
|
+
type,
|
|
93
|
+
status: 'pending',
|
|
94
|
+
startedAt: now,
|
|
95
|
+
lastUpdatedAt: now,
|
|
96
|
+
completedAt: null
|
|
97
|
+
},
|
|
98
|
+
policy: { ...DEFAULT_POLICY, ...policy },
|
|
99
|
+
task: null,
|
|
100
|
+
git: null,
|
|
101
|
+
pr: null,
|
|
102
|
+
phases: {
|
|
103
|
+
current: 'policy-selection',
|
|
104
|
+
currentIteration: 0,
|
|
105
|
+
history: []
|
|
106
|
+
},
|
|
107
|
+
agents: null,
|
|
108
|
+
checkpoints: {
|
|
109
|
+
canResume: true,
|
|
110
|
+
resumeFrom: null,
|
|
111
|
+
resumeContext: null
|
|
112
|
+
},
|
|
113
|
+
metrics: {
|
|
114
|
+
totalDuration: 0,
|
|
115
|
+
tokensUsed: 0,
|
|
116
|
+
toolCalls: 0,
|
|
117
|
+
filesModified: 0,
|
|
118
|
+
linesAdded: 0,
|
|
119
|
+
linesRemoved: 0
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Read workflow state from file
|
|
126
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
127
|
+
* @returns {Object|Error|null} Workflow state, Error if corrupted, or null if not found
|
|
128
|
+
*/
|
|
129
|
+
function readState(baseDir = process.cwd()) {
|
|
130
|
+
const statePath = getStatePath(baseDir);
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(statePath)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const content = fs.readFileSync(statePath, 'utf8');
|
|
138
|
+
const state = JSON.parse(content);
|
|
139
|
+
|
|
140
|
+
// Version check
|
|
141
|
+
if (state.version !== SCHEMA_VERSION) {
|
|
142
|
+
console.warn(`State version mismatch: ${state.version} vs ${SCHEMA_VERSION}`);
|
|
143
|
+
// Future: Add migration logic here
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return state;
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const corrupted = new Error(`Corrupted workflow state: ${error.message}`);
|
|
149
|
+
corrupted.code = 'ERR_STATE_CORRUPTED';
|
|
150
|
+
corrupted.cause = error;
|
|
151
|
+
console.error(corrupted.message);
|
|
152
|
+
return corrupted;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Write workflow state to file
|
|
158
|
+
* @param {Object} state - Workflow state
|
|
159
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
160
|
+
* @returns {boolean} Success status
|
|
161
|
+
*/
|
|
162
|
+
function writeState(state, baseDir = process.cwd()) {
|
|
163
|
+
ensureStateDir(baseDir);
|
|
164
|
+
const statePath = getStatePath(baseDir);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Update timestamp
|
|
168
|
+
state.workflow.lastUpdatedAt = new Date().toISOString();
|
|
169
|
+
|
|
170
|
+
const content = JSON.stringify(state, null, 2);
|
|
171
|
+
fs.writeFileSync(statePath, content, 'utf8');
|
|
172
|
+
return true;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`Error writing state: ${error.message}`);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Update specific fields in workflow state
|
|
181
|
+
* @param {Object} updates - Fields to update (deep merge)
|
|
182
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
183
|
+
* @returns {Object|null} Updated state or null on error
|
|
184
|
+
*/
|
|
185
|
+
function updateState(updates, baseDir = process.cwd()) {
|
|
186
|
+
let state = readState(baseDir);
|
|
187
|
+
|
|
188
|
+
if (state instanceof Error) {
|
|
189
|
+
console.error(`Cannot update state: ${state.message}`);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (!state) {
|
|
193
|
+
console.error('No existing state to update');
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Deep merge updates
|
|
198
|
+
state = deepMerge(state, updates);
|
|
199
|
+
|
|
200
|
+
if (writeState(state, baseDir)) {
|
|
201
|
+
return state;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Deep merge two objects (with prototype pollution protection)
|
|
209
|
+
* @param {Object} target - Target object
|
|
210
|
+
* @param {Object} source - Source object
|
|
211
|
+
* @returns {Object} Merged object
|
|
212
|
+
*/
|
|
213
|
+
function deepMerge(target, source) {
|
|
214
|
+
// Handle null/undefined cases
|
|
215
|
+
if (!source || typeof source !== 'object') return target;
|
|
216
|
+
if (!target || typeof target !== 'object') return source;
|
|
217
|
+
|
|
218
|
+
const result = { ...target };
|
|
219
|
+
|
|
220
|
+
for (const key of Object.keys(source)) {
|
|
221
|
+
// Protect against prototype pollution
|
|
222
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const sourceVal = source[key];
|
|
227
|
+
const targetVal = result[key];
|
|
228
|
+
|
|
229
|
+
// Handle Date objects - preserve as-is
|
|
230
|
+
if (sourceVal instanceof Date) {
|
|
231
|
+
result[key] = new Date(sourceVal.getTime());
|
|
232
|
+
}
|
|
233
|
+
// Handle null explicitly - allow overwriting with null
|
|
234
|
+
else if (sourceVal === null) {
|
|
235
|
+
result[key] = null;
|
|
236
|
+
}
|
|
237
|
+
// Recursively merge plain objects
|
|
238
|
+
else if (sourceVal && typeof sourceVal === 'object' && !Array.isArray(sourceVal)) {
|
|
239
|
+
result[key] = deepMerge(targetVal || {}, sourceVal);
|
|
240
|
+
}
|
|
241
|
+
// Replace arrays and primitives
|
|
242
|
+
else {
|
|
243
|
+
result[key] = sourceVal;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Start a new phase
|
|
252
|
+
* @param {string} phaseName - Phase name
|
|
253
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
254
|
+
* @returns {Object|null} Updated state or null on error
|
|
255
|
+
*/
|
|
256
|
+
function startPhase(phaseName, baseDir = process.cwd()) {
|
|
257
|
+
if (!PHASES.includes(phaseName)) {
|
|
258
|
+
console.error(`Invalid phase: ${phaseName}`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const state = readState(baseDir);
|
|
263
|
+
if (state instanceof Error) {
|
|
264
|
+
console.error(`Cannot start phase: ${state.message}`);
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (!state) {
|
|
268
|
+
console.error('No workflow state exists. Create a workflow first.');
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const history = state.phases?.history || [];
|
|
273
|
+
|
|
274
|
+
history.push({
|
|
275
|
+
phase: phaseName,
|
|
276
|
+
status: 'in_progress',
|
|
277
|
+
startedAt: new Date().toISOString(),
|
|
278
|
+
completedAt: null,
|
|
279
|
+
duration: null,
|
|
280
|
+
result: null
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return updateState({
|
|
284
|
+
workflow: { status: 'in_progress' },
|
|
285
|
+
phases: { current: phaseName, history },
|
|
286
|
+
checkpoints: { canResume: true, resumeFrom: phaseName, resumeContext: null }
|
|
287
|
+
}, baseDir);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Update the current phase entry with completion data
|
|
292
|
+
* @param {Object} state - Current state
|
|
293
|
+
* @param {string} status - New status (completed/failed)
|
|
294
|
+
* @param {Object} result - Result data
|
|
295
|
+
* @returns {Object} Updated history
|
|
296
|
+
*/
|
|
297
|
+
function finalizePhaseEntry(state, status, result) {
|
|
298
|
+
const history = state.phases.history || [];
|
|
299
|
+
const entry = history[history.length - 1];
|
|
300
|
+
|
|
301
|
+
if (entry) {
|
|
302
|
+
const now = new Date().toISOString();
|
|
303
|
+
entry.status = status;
|
|
304
|
+
entry.completedAt = now;
|
|
305
|
+
entry.duration = new Date(now).getTime() - new Date(entry.startedAt).getTime();
|
|
306
|
+
entry.result = result;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return history;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Complete the current phase
|
|
314
|
+
* @param {Object} [result={}] - Phase result data
|
|
315
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
316
|
+
* @returns {Object|null} Updated state or null on error
|
|
317
|
+
*/
|
|
318
|
+
function completePhase(result = {}, baseDir = process.cwd()) {
|
|
319
|
+
const state = readState(baseDir);
|
|
320
|
+
if (state instanceof Error) {
|
|
321
|
+
console.error(`Cannot complete phase: ${state.message}`);
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
if (!state) return null;
|
|
325
|
+
|
|
326
|
+
const history = finalizePhaseEntry(state, 'completed', result);
|
|
327
|
+
const currentIndex = PHASES.indexOf(state.phases.current);
|
|
328
|
+
const nextPhase = currentIndex < PHASES.length - 1 ? PHASES[currentIndex + 1] : 'complete';
|
|
329
|
+
|
|
330
|
+
return updateState({
|
|
331
|
+
phases: { current: nextPhase, history },
|
|
332
|
+
checkpoints: { resumeFrom: nextPhase, resumeContext: null }
|
|
333
|
+
}, baseDir);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Fail the current phase
|
|
338
|
+
* @param {string} reason - Failure reason
|
|
339
|
+
* @param {Object} [context={}] - Context for resume
|
|
340
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
341
|
+
* @returns {Object|null} Updated state or null on error
|
|
342
|
+
*/
|
|
343
|
+
function failPhase(reason, context = {}, baseDir = process.cwd()) {
|
|
344
|
+
const state = readState(baseDir);
|
|
345
|
+
if (state instanceof Error) {
|
|
346
|
+
console.error(`Cannot fail phase: ${state.message}`);
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
if (!state) return null;
|
|
350
|
+
|
|
351
|
+
const history = finalizePhaseEntry(state, 'failed', { error: reason });
|
|
352
|
+
|
|
353
|
+
return updateState({
|
|
354
|
+
workflow: { status: 'failed' },
|
|
355
|
+
phases: { history },
|
|
356
|
+
checkpoints: {
|
|
357
|
+
canResume: true,
|
|
358
|
+
resumeFrom: state.phases.current,
|
|
359
|
+
resumeContext: { reason, ...context }
|
|
360
|
+
}
|
|
361
|
+
}, baseDir);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Skip to a specific phase
|
|
366
|
+
* @param {string} phaseName - Phase to skip to
|
|
367
|
+
* @param {string} [reason='manual skip'] - Skip reason
|
|
368
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
369
|
+
* @returns {Object|null} Updated state or null on error
|
|
370
|
+
*/
|
|
371
|
+
function skipToPhase(phaseName, reason = 'manual skip', baseDir = process.cwd()) {
|
|
372
|
+
if (!PHASES.includes(phaseName)) {
|
|
373
|
+
console.error(`Invalid phase: ${phaseName}`);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const state = readState(baseDir);
|
|
378
|
+
if (state instanceof Error) {
|
|
379
|
+
console.error(`Cannot skip to phase: ${state.message}`);
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
if (!state) return null;
|
|
383
|
+
|
|
384
|
+
const currentIndex = PHASES.indexOf(state.phases.current);
|
|
385
|
+
const targetIndex = PHASES.indexOf(phaseName);
|
|
386
|
+
|
|
387
|
+
// Add skipped entries for phases we're jumping over
|
|
388
|
+
const history = [...(state.phases.history || [])];
|
|
389
|
+
const now = new Date().toISOString();
|
|
390
|
+
|
|
391
|
+
for (let i = currentIndex; i < targetIndex; i++) {
|
|
392
|
+
history.push({
|
|
393
|
+
phase: PHASES[i],
|
|
394
|
+
status: 'skipped',
|
|
395
|
+
startedAt: now,
|
|
396
|
+
completedAt: now,
|
|
397
|
+
duration: 0,
|
|
398
|
+
result: { skippedReason: reason }
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return updateState({
|
|
403
|
+
phases: {
|
|
404
|
+
current: phaseName,
|
|
405
|
+
history
|
|
406
|
+
},
|
|
407
|
+
checkpoints: {
|
|
408
|
+
resumeFrom: phaseName
|
|
409
|
+
}
|
|
410
|
+
}, baseDir);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Complete the entire workflow
|
|
415
|
+
* @param {Object} [result={}] - Final result data
|
|
416
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
417
|
+
* @returns {Object|null} Updated state or null on error
|
|
418
|
+
*/
|
|
419
|
+
function completeWorkflow(result = {}, baseDir = process.cwd()) {
|
|
420
|
+
const state = readState(baseDir);
|
|
421
|
+
if (state instanceof Error) {
|
|
422
|
+
console.error(`Cannot complete workflow: ${state.message}`);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
if (!state) return null;
|
|
426
|
+
|
|
427
|
+
const now = new Date().toISOString();
|
|
428
|
+
const startTime = new Date(state.workflow.startedAt).getTime();
|
|
429
|
+
const endTime = new Date(now).getTime();
|
|
430
|
+
|
|
431
|
+
return updateState({
|
|
432
|
+
workflow: {
|
|
433
|
+
status: 'completed',
|
|
434
|
+
completedAt: now
|
|
435
|
+
},
|
|
436
|
+
phases: {
|
|
437
|
+
current: 'complete'
|
|
438
|
+
},
|
|
439
|
+
checkpoints: {
|
|
440
|
+
canResume: false,
|
|
441
|
+
resumeFrom: null
|
|
442
|
+
},
|
|
443
|
+
metrics: {
|
|
444
|
+
totalDuration: endTime - startTime,
|
|
445
|
+
...result.metrics
|
|
446
|
+
}
|
|
447
|
+
}, baseDir);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Abort the workflow
|
|
452
|
+
* @param {string} [reason='user aborted'] - Abort reason
|
|
453
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
454
|
+
* @returns {Object|null} Updated state or null on error
|
|
455
|
+
*/
|
|
456
|
+
function abortWorkflow(reason = 'user aborted', baseDir = process.cwd()) {
|
|
457
|
+
return updateState({
|
|
458
|
+
workflow: {
|
|
459
|
+
status: 'aborted',
|
|
460
|
+
completedAt: new Date().toISOString()
|
|
461
|
+
},
|
|
462
|
+
checkpoints: {
|
|
463
|
+
canResume: false,
|
|
464
|
+
resumeFrom: null,
|
|
465
|
+
resumeContext: { abortReason: reason }
|
|
466
|
+
}
|
|
467
|
+
}, baseDir);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Delete workflow state (cleanup)
|
|
472
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
473
|
+
* @returns {boolean} Success status
|
|
474
|
+
*/
|
|
475
|
+
function deleteState(baseDir = process.cwd()) {
|
|
476
|
+
const statePath = getStatePath(baseDir);
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
if (fs.existsSync(statePath)) {
|
|
480
|
+
fs.unlinkSync(statePath);
|
|
481
|
+
}
|
|
482
|
+
return true;
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error(`Error deleting state: ${error.message}`);
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Check if a workflow is in progress
|
|
491
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
492
|
+
* @returns {boolean} True if workflow is active
|
|
493
|
+
*/
|
|
494
|
+
function hasActiveWorkflow(baseDir = process.cwd()) {
|
|
495
|
+
const state = readState(baseDir);
|
|
496
|
+
if (state instanceof Error) return false;
|
|
497
|
+
if (!state) return false;
|
|
498
|
+
|
|
499
|
+
return ['pending', 'in_progress', 'paused'].includes(state.workflow.status);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Get workflow summary for display
|
|
504
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
505
|
+
* @returns {Object|null} Summary object or null
|
|
506
|
+
*/
|
|
507
|
+
function getWorkflowSummary(baseDir = process.cwd()) {
|
|
508
|
+
const state = readState(baseDir);
|
|
509
|
+
if (state instanceof Error) {
|
|
510
|
+
return { error: state.message, code: state.code };
|
|
511
|
+
}
|
|
512
|
+
if (!state) return null;
|
|
513
|
+
|
|
514
|
+
const completedPhases = state.phases.history?.filter(p => p.status === 'completed').length || 0;
|
|
515
|
+
const totalPhases = PHASES.length - 1; // Exclude 'complete'
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
id: state.workflow.id,
|
|
519
|
+
type: state.workflow.type,
|
|
520
|
+
status: state.workflow.status,
|
|
521
|
+
currentPhase: state.phases.current,
|
|
522
|
+
progress: `${completedPhases}/${totalPhases}`,
|
|
523
|
+
progressPercent: Math.round((completedPhases / totalPhases) * 100),
|
|
524
|
+
task: state.task ? {
|
|
525
|
+
id: state.task.id,
|
|
526
|
+
title: state.task.title,
|
|
527
|
+
source: state.task.source
|
|
528
|
+
} : null,
|
|
529
|
+
pr: state.pr ? {
|
|
530
|
+
number: state.pr.number,
|
|
531
|
+
url: state.pr.url,
|
|
532
|
+
ciStatus: state.pr.ciStatus
|
|
533
|
+
} : null,
|
|
534
|
+
canResume: state.checkpoints.canResume,
|
|
535
|
+
resumeFrom: state.checkpoints.resumeFrom,
|
|
536
|
+
startedAt: state.workflow.startedAt,
|
|
537
|
+
duration: state.metrics?.totalDuration || 0
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Update agent results
|
|
543
|
+
* @param {string} agentName - Agent identifier
|
|
544
|
+
* @param {Object} result - Agent result
|
|
545
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
546
|
+
* @returns {Object|null} Updated state or null on error
|
|
547
|
+
*/
|
|
548
|
+
function updateAgentResult(agentName, result, baseDir = process.cwd()) {
|
|
549
|
+
const state = readState(baseDir);
|
|
550
|
+
if (state instanceof Error) {
|
|
551
|
+
console.error(`Cannot update agent result: ${state.message}`);
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
if (!state) return null;
|
|
555
|
+
|
|
556
|
+
const agents = state.agents || {
|
|
557
|
+
lastRun: {},
|
|
558
|
+
totalIterations: 0,
|
|
559
|
+
totalIssuesFound: 0,
|
|
560
|
+
totalIssuesFixed: 0
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
agents.lastRun[agentName] = result;
|
|
564
|
+
agents.totalIssuesFound += result.issues || 0;
|
|
565
|
+
|
|
566
|
+
return updateState({ agents }, baseDir);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Increment review iteration
|
|
571
|
+
* @param {Object} [result={}] - Iteration result
|
|
572
|
+
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
573
|
+
* @returns {Object|null} Updated state or null on error
|
|
574
|
+
*/
|
|
575
|
+
function incrementIteration(result = {}, baseDir = process.cwd()) {
|
|
576
|
+
const state = readState(baseDir);
|
|
577
|
+
if (state instanceof Error) {
|
|
578
|
+
console.error(`Cannot increment iteration: ${state.message}`);
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
if (!state) return null;
|
|
582
|
+
|
|
583
|
+
const agents = state.agents || {
|
|
584
|
+
lastRun: {},
|
|
585
|
+
totalIterations: 0,
|
|
586
|
+
totalIssuesFound: 0,
|
|
587
|
+
totalIssuesFixed: 0
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
agents.totalIterations += 1;
|
|
591
|
+
agents.totalIssuesFixed += result.fixed || 0;
|
|
592
|
+
|
|
593
|
+
return updateState({
|
|
594
|
+
phases: {
|
|
595
|
+
currentIteration: state.phases.currentIteration + 1
|
|
596
|
+
},
|
|
597
|
+
agents
|
|
598
|
+
}, baseDir);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Export all functions
|
|
602
|
+
module.exports = {
|
|
603
|
+
// Constants
|
|
604
|
+
SCHEMA_VERSION,
|
|
605
|
+
PHASES,
|
|
606
|
+
DEFAULT_POLICY,
|
|
607
|
+
|
|
608
|
+
// Core functions
|
|
609
|
+
generateWorkflowId,
|
|
610
|
+
getStatePath,
|
|
611
|
+
ensureStateDir,
|
|
612
|
+
|
|
613
|
+
// CRUD operations
|
|
614
|
+
createState,
|
|
615
|
+
readState,
|
|
616
|
+
writeState,
|
|
617
|
+
updateState,
|
|
618
|
+
deleteState,
|
|
619
|
+
|
|
620
|
+
// Phase management
|
|
621
|
+
startPhase,
|
|
622
|
+
completePhase,
|
|
623
|
+
failPhase,
|
|
624
|
+
skipToPhase,
|
|
625
|
+
|
|
626
|
+
// Workflow lifecycle
|
|
627
|
+
completeWorkflow,
|
|
628
|
+
abortWorkflow,
|
|
629
|
+
hasActiveWorkflow,
|
|
630
|
+
getWorkflowSummary,
|
|
631
|
+
|
|
632
|
+
// Agent management
|
|
633
|
+
updateAgentResult,
|
|
634
|
+
incrementIteration
|
|
635
|
+
};
|