awesome-slash 2.4.4 → 2.5.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/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +88 -1
- package/README.md +173 -161
- package/SECURITY.md +25 -81
- package/adapters/codex/install.sh +58 -16
- package/adapters/opencode/install.sh +92 -23
- package/lib/index.js +47 -4
- package/lib/patterns/review-patterns.js +58 -11
- package/lib/patterns/slop-patterns.js +154 -147
- package/lib/platform/detect-platform.js +99 -350
- package/lib/platform/detection-configs.js +93 -0
- package/lib/platform/verify-tools.js +10 -78
- package/lib/schemas/README.md +195 -0
- package/lib/schemas/validator.js +247 -0
- package/lib/sources/custom-handler.js +199 -0
- package/lib/sources/policy-questions.js +239 -0
- package/lib/sources/source-cache.js +149 -0
- package/lib/state/workflow-state.js +363 -665
- package/lib/types/README.md +292 -0
- package/lib/types/agent-frontmatter.d.ts +134 -0
- package/lib/types/command-frontmatter.d.ts +107 -0
- package/lib/types/hook-frontmatter.d.ts +115 -0
- package/lib/types/index.d.ts +84 -0
- package/lib/types/plugin-manifest.d.ts +102 -0
- package/lib/types/skill-frontmatter.d.ts +89 -0
- package/lib/utils/cache-manager.js +154 -0
- package/lib/utils/context-optimizer.js +5 -36
- package/lib/utils/deprecation.js +37 -0
- package/lib/utils/shell-escape.js +88 -0
- package/mcp-server/index.js +513 -18
- package/package.json +6 -2
- package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop-around/lib/index.js +170 -0
- package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
- package/plugins/deslop-around/lib/patterns/slop-patterns.js +170 -129
- package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
- package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
- package/plugins/deslop-around/lib/schemas/README.md +195 -0
- package/plugins/deslop-around/lib/schemas/validator.js +205 -0
- package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
- package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
- package/plugins/deslop-around/lib/sources/source-cache.js +149 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +382 -484
- package/plugins/deslop-around/lib/types/README.md +292 -0
- package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/deslop-around/lib/types/index.d.ts +84 -0
- package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
- package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
- package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
- package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/delivery-validator.md +2 -2
- package/plugins/next-task/agents/implementation-agent.md +3 -4
- package/plugins/next-task/agents/planning-agent.md +77 -19
- package/plugins/next-task/agents/review-orchestrator.md +21 -122
- package/plugins/next-task/agents/task-discoverer.md +164 -23
- package/plugins/next-task/commands/next-task.md +180 -14
- package/plugins/next-task/lib/index.js +170 -0
- package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
- package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
- package/plugins/next-task/lib/platform/detect-platform.js +212 -123
- package/plugins/next-task/lib/platform/detection-configs.js +93 -0
- package/plugins/next-task/lib/platform/verify-tools.js +10 -1
- package/plugins/next-task/lib/schemas/README.md +195 -0
- package/plugins/next-task/lib/schemas/validator.js +205 -0
- package/plugins/next-task/lib/sources/custom-handler.js +199 -0
- package/plugins/next-task/lib/sources/policy-questions.js +239 -0
- package/plugins/next-task/lib/sources/source-cache.js +149 -0
- package/plugins/next-task/lib/state/workflow-state.js +382 -484
- package/plugins/next-task/lib/types/README.md +292 -0
- package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/next-task/lib/types/index.d.ts +84 -0
- package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/next-task/lib/utils/cache-manager.js +154 -0
- package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
- package/plugins/next-task/lib/utils/deprecation.js +37 -0
- package/plugins/next-task/lib/utils/shell-escape.js +88 -0
- package/plugins/project-review/.claude-plugin/plugin.json +1 -1
- package/plugins/project-review/lib/index.js +170 -0
- package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
- package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
- package/plugins/project-review/lib/platform/detect-platform.js +212 -123
- package/plugins/project-review/lib/platform/detection-configs.js +93 -0
- package/plugins/project-review/lib/platform/verify-tools.js +10 -1
- package/plugins/project-review/lib/schemas/README.md +195 -0
- package/plugins/project-review/lib/schemas/validator.js +205 -0
- package/plugins/project-review/lib/sources/custom-handler.js +199 -0
- package/plugins/project-review/lib/sources/policy-questions.js +239 -0
- package/plugins/project-review/lib/sources/source-cache.js +149 -0
- package/plugins/project-review/lib/state/workflow-state.js +382 -484
- package/plugins/project-review/lib/types/README.md +292 -0
- package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/project-review/lib/types/index.d.ts +84 -0
- package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/project-review/lib/utils/cache-manager.js +154 -0
- package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
- package/plugins/project-review/lib/utils/deprecation.js +37 -0
- package/plugins/project-review/lib/utils/shell-escape.js +88 -0
- package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
- package/plugins/reality-check/agents/code-explorer.md +1 -1
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/index.js +170 -0
- package/plugins/ship/lib/patterns/review-patterns.js +58 -11
- package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
- package/plugins/ship/lib/platform/detect-platform.js +212 -123
- package/plugins/ship/lib/platform/detection-configs.js +93 -0
- package/plugins/ship/lib/platform/verify-tools.js +10 -1
- package/plugins/ship/lib/schemas/README.md +195 -0
- package/plugins/ship/lib/schemas/validator.js +205 -0
- package/plugins/ship/lib/sources/custom-handler.js +199 -0
- package/plugins/ship/lib/sources/policy-questions.js +239 -0
- package/plugins/ship/lib/sources/source-cache.js +149 -0
- package/plugins/ship/lib/state/workflow-state.js +382 -484
- package/plugins/ship/lib/types/README.md +292 -0
- package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/ship/lib/types/index.d.ts +84 -0
- package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/ship/lib/utils/cache-manager.js +154 -0
- package/plugins/ship/lib/utils/context-optimizer.js +115 -37
- package/plugins/ship/lib/utils/deprecation.js +37 -0
- package/plugins/ship/lib/utils/shell-escape.js +88 -0
- package/lib/state/workflow-state.schema.json +0 -282
- package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
- package/plugins/next-task/agents/policy-selector.md +0 -248
- package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
- package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
- package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
- package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
- package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
|
@@ -1,18 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Simplified workflow state management
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Two files:
|
|
5
|
+
* - Main project: .claude/tasks.json (tracks active worktree/task)
|
|
6
|
+
* - Worktree: .claude/flow.json (tracks workflow progress)
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
const fs = require('fs');
|
|
9
10
|
const path = require('path');
|
|
10
11
|
const crypto = require('crypto');
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
13
|
+
// File paths
|
|
14
|
+
const CLAUDE_DIR = '.claude';
|
|
15
|
+
const TASKS_FILE = 'tasks.json';
|
|
16
|
+
const FLOW_FILE = 'flow.json';
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Validate and resolve path to prevent path traversal attacks
|
|
20
|
+
* @param {string} basePath - Base directory path
|
|
21
|
+
* @returns {string} Validated absolute path
|
|
22
|
+
* @throws {Error} If path is invalid
|
|
23
|
+
*/
|
|
24
|
+
function validatePath(basePath) {
|
|
25
|
+
if (typeof basePath !== 'string' || basePath.length === 0) {
|
|
26
|
+
throw new Error('Path must be a non-empty string');
|
|
27
|
+
}
|
|
28
|
+
const resolved = path.resolve(basePath);
|
|
29
|
+
if (resolved.includes('\0')) {
|
|
30
|
+
throw new Error('Path contains invalid null byte');
|
|
31
|
+
}
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate that target path is within base directory
|
|
37
|
+
* @param {string} targetPath - Target file path
|
|
38
|
+
* @param {string} basePath - Base directory
|
|
39
|
+
* @throws {Error} If path traversal detected
|
|
40
|
+
*/
|
|
41
|
+
function validatePathWithinBase(targetPath, basePath) {
|
|
42
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
43
|
+
const resolvedBase = path.resolve(basePath);
|
|
44
|
+
if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
|
|
45
|
+
throw new Error('Path traversal detected');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a unique workflow ID
|
|
51
|
+
* @returns {string} Workflow ID
|
|
52
|
+
*/
|
|
53
|
+
function generateWorkflowId() {
|
|
54
|
+
const now = new Date();
|
|
55
|
+
const date = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
56
|
+
const time = now.toISOString().slice(11, 19).replace(/:/g, '');
|
|
57
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
58
|
+
return `workflow-${date}-${time}-${random}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Valid phases for the workflow
|
|
16
62
|
const PHASES = [
|
|
17
63
|
'policy-selection',
|
|
18
64
|
'task-discovery',
|
|
@@ -22,614 +68,466 @@ const PHASES = [
|
|
|
22
68
|
'user-approval',
|
|
23
69
|
'implementation',
|
|
24
70
|
'review-loop',
|
|
25
|
-
'delivery-
|
|
26
|
-
'
|
|
27
|
-
'create-pr',
|
|
28
|
-
'ci-wait',
|
|
29
|
-
'comment-fix',
|
|
30
|
-
'merge',
|
|
31
|
-
'production-ci',
|
|
32
|
-
'deploy',
|
|
33
|
-
'production-release',
|
|
71
|
+
'delivery-validation',
|
|
72
|
+
'shipping',
|
|
34
73
|
'complete'
|
|
35
74
|
];
|
|
36
75
|
|
|
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
76
|
/**
|
|
48
|
-
*
|
|
49
|
-
* @returns {string} Workflow ID in format: workflow-YYYYMMDD-HHMMSS-random
|
|
77
|
+
* Ensure .claude directory exists
|
|
50
78
|
*/
|
|
51
|
-
function
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
79
|
+
function ensureClaudeDir(basePath) {
|
|
80
|
+
const claudeDir = path.join(basePath, CLAUDE_DIR);
|
|
81
|
+
if (!fs.existsSync(claudeDir)) {
|
|
82
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
return claudeDir;
|
|
57
85
|
}
|
|
58
86
|
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// TASKS.JSON - Main project directory
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
59
91
|
/**
|
|
60
|
-
* Get
|
|
61
|
-
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
62
|
-
* @returns {string} Full path to state file
|
|
92
|
+
* Get path to tasks.json with validation
|
|
63
93
|
*/
|
|
64
|
-
function
|
|
65
|
-
|
|
94
|
+
function getTasksPath(projectPath = process.cwd()) {
|
|
95
|
+
const validatedBase = validatePath(projectPath);
|
|
96
|
+
const tasksPath = path.join(validatedBase, CLAUDE_DIR, TASKS_FILE);
|
|
97
|
+
validatePathWithinBase(tasksPath, validatedBase);
|
|
98
|
+
return tasksPath;
|
|
66
99
|
}
|
|
67
100
|
|
|
68
101
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
102
|
+
* Read tasks.json from main project
|
|
103
|
+
* Returns { active: null } if file doesn't exist or is corrupted
|
|
104
|
+
* Logs critical error on corruption to prevent silent data loss
|
|
71
105
|
*/
|
|
72
|
-
function
|
|
73
|
-
const
|
|
74
|
-
if (!fs.existsSync(
|
|
75
|
-
|
|
106
|
+
function readTasks(projectPath = process.cwd()) {
|
|
107
|
+
const tasksPath = getTasksPath(projectPath);
|
|
108
|
+
if (!fs.existsSync(tasksPath)) {
|
|
109
|
+
return { active: null };
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const data = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
|
|
113
|
+
// Normalize legacy format that may not have 'active' field
|
|
114
|
+
if (!Object.prototype.hasOwnProperty.call(data, 'active')) {
|
|
115
|
+
return { active: null };
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error(`[CRITICAL] Corrupted tasks.json at ${tasksPath}: ${e.message}`);
|
|
120
|
+
return { active: null };
|
|
76
121
|
}
|
|
77
122
|
}
|
|
78
123
|
|
|
79
124
|
/**
|
|
80
|
-
*
|
|
81
|
-
* @param {string} [type='next-task'] - Workflow type
|
|
82
|
-
* @param {Object} [policy={}] - Policy overrides
|
|
83
|
-
* @returns {Object} New workflow state
|
|
125
|
+
* Write tasks.json to main project
|
|
84
126
|
*/
|
|
85
|
-
function
|
|
86
|
-
|
|
127
|
+
function writeTasks(tasks, projectPath = process.cwd()) {
|
|
128
|
+
ensureClaudeDir(projectPath);
|
|
129
|
+
const tasksPath = getTasksPath(projectPath);
|
|
130
|
+
fs.writeFileSync(tasksPath, JSON.stringify(tasks, null, 2), 'utf8');
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
87
133
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
}
|
|
134
|
+
/**
|
|
135
|
+
* Set active task in main project
|
|
136
|
+
*/
|
|
137
|
+
function setActiveTask(task, projectPath = process.cwd()) {
|
|
138
|
+
const tasks = readTasks(projectPath);
|
|
139
|
+
tasks.active = {
|
|
140
|
+
...task,
|
|
141
|
+
startedAt: new Date().toISOString()
|
|
121
142
|
};
|
|
143
|
+
return writeTasks(tasks, projectPath);
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
/**
|
|
125
|
-
*
|
|
126
|
-
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
127
|
-
* @returns {Object|Error|null} Workflow state, Error if corrupted, or null if not found
|
|
147
|
+
* Clear active task
|
|
128
148
|
*/
|
|
129
|
-
function
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
}
|
|
149
|
+
function clearActiveTask(projectPath = process.cwd()) {
|
|
150
|
+
const tasks = readTasks(projectPath);
|
|
151
|
+
tasks.active = null;
|
|
152
|
+
return writeTasks(tasks, projectPath);
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
160
|
-
* @returns {boolean} Success status
|
|
156
|
+
* Check if there's an active task
|
|
157
|
+
* Uses != null to catch both null and undefined (legacy format safety)
|
|
161
158
|
*/
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
159
|
+
function hasActiveTask(projectPath = process.cwd()) {
|
|
160
|
+
const tasks = readTasks(projectPath);
|
|
161
|
+
return tasks.active != null;
|
|
162
|
+
}
|
|
165
163
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// FLOW.JSON - Worktree directory
|
|
166
|
+
// =============================================================================
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Get path to flow.json with validation
|
|
170
|
+
*/
|
|
171
|
+
function getFlowPath(worktreePath = process.cwd()) {
|
|
172
|
+
const validatedBase = validatePath(worktreePath);
|
|
173
|
+
const flowPath = path.join(validatedBase, CLAUDE_DIR, FLOW_FILE);
|
|
174
|
+
validatePathWithinBase(flowPath, validatedBase);
|
|
175
|
+
return flowPath;
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* @returns {Object|null} Updated state or null on error
|
|
179
|
+
* Read flow.json from worktree
|
|
180
|
+
* Returns null if file doesn't exist or is corrupted
|
|
181
|
+
* Logs critical error on corruption to prevent silent data loss
|
|
184
182
|
*/
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (state instanceof Error) {
|
|
189
|
-
console.error(`Cannot update state: ${state.message}`);
|
|
183
|
+
function readFlow(worktreePath = process.cwd()) {
|
|
184
|
+
const flowPath = getFlowPath(worktreePath);
|
|
185
|
+
if (!fs.existsSync(flowPath)) {
|
|
190
186
|
return null;
|
|
191
187
|
}
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
try {
|
|
189
|
+
return JSON.parse(fs.readFileSync(flowPath, 'utf8'));
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.error(`[CRITICAL] Corrupted flow.json at ${flowPath}: ${e.message}`);
|
|
194
192
|
return null;
|
|
195
193
|
}
|
|
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
194
|
}
|
|
206
195
|
|
|
207
196
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* @param {Object} source - Source object
|
|
211
|
-
* @returns {Object} Merged object
|
|
197
|
+
* Write flow.json to worktree
|
|
198
|
+
* Creates a copy to avoid mutating the original object
|
|
212
199
|
*/
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
200
|
+
function writeFlow(flow, worktreePath = process.cwd()) {
|
|
201
|
+
ensureClaudeDir(worktreePath);
|
|
202
|
+
// Clone to avoid mutating the original object
|
|
203
|
+
const flowCopy = JSON.parse(JSON.stringify(flow));
|
|
204
|
+
flowCopy.lastUpdate = new Date().toISOString();
|
|
205
|
+
const flowPath = getFlowPath(worktreePath);
|
|
206
|
+
fs.writeFileSync(flowPath, JSON.stringify(flowCopy, null, 2), 'utf8');
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
225
209
|
|
|
226
|
-
|
|
227
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Update flow.json with partial updates
|
|
212
|
+
* Handles null values correctly (null overwrites existing values)
|
|
213
|
+
* Deep merges nested objects when both exist
|
|
214
|
+
*/
|
|
215
|
+
function updateFlow(updates, worktreePath = process.cwd()) {
|
|
216
|
+
const flow = readFlow(worktreePath) || {};
|
|
228
217
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Handle null explicitly - allow overwriting with null
|
|
234
|
-
else if (sourceVal === null) {
|
|
235
|
-
result[key] = null;
|
|
218
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
219
|
+
// Null explicitly overwrites
|
|
220
|
+
if (value === null) {
|
|
221
|
+
flow[key] = null;
|
|
236
222
|
}
|
|
237
|
-
//
|
|
238
|
-
else if (
|
|
239
|
-
|
|
223
|
+
// Deep merge if both source and target are non-null objects
|
|
224
|
+
else if (
|
|
225
|
+
value && typeof value === 'object' && !Array.isArray(value) &&
|
|
226
|
+
flow[key] && typeof flow[key] === 'object' && !Array.isArray(flow[key])
|
|
227
|
+
) {
|
|
228
|
+
flow[key] = { ...flow[key], ...value };
|
|
240
229
|
}
|
|
241
|
-
//
|
|
230
|
+
// Otherwise direct assignment
|
|
242
231
|
else {
|
|
243
|
-
|
|
232
|
+
flow[key] = value;
|
|
244
233
|
}
|
|
245
234
|
}
|
|
246
235
|
|
|
247
|
-
return
|
|
236
|
+
return writeFlow(flow, worktreePath);
|
|
248
237
|
}
|
|
249
238
|
|
|
250
239
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* @param {
|
|
254
|
-
* @
|
|
240
|
+
* Create initial flow for a new task
|
|
241
|
+
* Also registers the task as active in the main project's tasks.json
|
|
242
|
+
* @param {Object} task - Task object with id, title, source, url
|
|
243
|
+
* @param {Object} policy - Policy object with stoppingPoint
|
|
244
|
+
* @param {string} worktreePath - Path to worktree
|
|
245
|
+
* @param {string} projectPath - Path to main project (for tasks.json registration)
|
|
255
246
|
*/
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
247
|
+
function createFlow(task, policy, worktreePath = process.cwd(), projectPath = null) {
|
|
248
|
+
const flow = {
|
|
249
|
+
task: {
|
|
250
|
+
id: task.id,
|
|
251
|
+
title: task.title,
|
|
252
|
+
source: task.source,
|
|
253
|
+
url: task.url || null
|
|
254
|
+
},
|
|
255
|
+
policy: {
|
|
256
|
+
stoppingPoint: policy.stoppingPoint || 'merged'
|
|
257
|
+
},
|
|
258
|
+
phase: 'policy-selection',
|
|
259
|
+
status: 'in_progress',
|
|
260
|
+
lastUpdate: new Date().toISOString(),
|
|
261
|
+
userNotes: '',
|
|
262
|
+
git: {
|
|
263
|
+
branch: null,
|
|
264
|
+
baseBranch: 'main'
|
|
265
|
+
},
|
|
266
|
+
pr: null,
|
|
267
|
+
exploration: null,
|
|
268
|
+
plan: null,
|
|
269
|
+
// Store projectPath so completeWorkflow knows where to clear the task
|
|
270
|
+
projectPath: projectPath
|
|
271
|
+
};
|
|
261
272
|
|
|
262
|
-
|
|
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
|
-
}
|
|
273
|
+
writeFlow(flow, worktreePath);
|
|
271
274
|
|
|
272
|
-
|
|
275
|
+
// Register task as active in main project
|
|
276
|
+
if (projectPath) {
|
|
277
|
+
setActiveTask({
|
|
278
|
+
taskId: task.id,
|
|
279
|
+
title: task.title,
|
|
280
|
+
worktree: worktreePath,
|
|
281
|
+
branch: flow.git.branch
|
|
282
|
+
}, projectPath);
|
|
283
|
+
}
|
|
273
284
|
|
|
274
|
-
|
|
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);
|
|
285
|
+
return flow;
|
|
288
286
|
}
|
|
289
287
|
|
|
290
288
|
/**
|
|
291
|
-
*
|
|
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
|
|
289
|
+
* Delete flow.json
|
|
296
290
|
*/
|
|
297
|
-
function
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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;
|
|
291
|
+
function deleteFlow(worktreePath = process.cwd()) {
|
|
292
|
+
const flowPath = getFlowPath(worktreePath);
|
|
293
|
+
if (fs.existsSync(flowPath)) {
|
|
294
|
+
fs.unlinkSync(flowPath);
|
|
295
|
+
return true;
|
|
307
296
|
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
308
299
|
|
|
309
|
-
|
|
300
|
+
// =============================================================================
|
|
301
|
+
// PHASE MANAGEMENT
|
|
302
|
+
// =============================================================================
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Check if phase is valid
|
|
306
|
+
*/
|
|
307
|
+
function isValidPhase(phase) {
|
|
308
|
+
return PHASES.includes(phase);
|
|
310
309
|
}
|
|
311
310
|
|
|
312
311
|
/**
|
|
313
|
-
*
|
|
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
|
|
312
|
+
* Set current phase
|
|
317
313
|
*/
|
|
318
|
-
function
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.error(`Cannot complete phase: ${state.message}`);
|
|
322
|
-
return null;
|
|
314
|
+
function setPhase(phase, worktreePath = process.cwd()) {
|
|
315
|
+
if (!isValidPhase(phase)) {
|
|
316
|
+
throw new Error(`Invalid phase: ${phase}`);
|
|
323
317
|
}
|
|
324
|
-
|
|
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';
|
|
318
|
+
return updateFlow({ phase, status: 'in_progress' }, worktreePath);
|
|
319
|
+
}
|
|
329
320
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Start a phase (alias for setPhase, for backwards compatibility)
|
|
323
|
+
*/
|
|
324
|
+
function startPhase(phase, worktreePath = process.cwd()) {
|
|
325
|
+
return setPhase(phase, worktreePath);
|
|
334
326
|
}
|
|
335
327
|
|
|
336
328
|
/**
|
|
337
329
|
* 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
330
|
*/
|
|
343
|
-
function failPhase(reason, context = {},
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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);
|
|
331
|
+
function failPhase(reason, context = {}, worktreePath = process.cwd()) {
|
|
332
|
+
const flow = readFlow(worktreePath);
|
|
333
|
+
if (!flow) return null;
|
|
334
|
+
|
|
335
|
+
return updateFlow({
|
|
336
|
+
status: 'failed',
|
|
337
|
+
error: reason,
|
|
338
|
+
failContext: context
|
|
339
|
+
}, worktreePath);
|
|
362
340
|
}
|
|
363
341
|
|
|
364
342
|
/**
|
|
365
343
|
* 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
344
|
*/
|
|
371
|
-
function skipToPhase(
|
|
372
|
-
if (!
|
|
373
|
-
|
|
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
|
-
});
|
|
345
|
+
function skipToPhase(phase, reason = 'manual skip', worktreePath = process.cwd()) {
|
|
346
|
+
if (!isValidPhase(phase)) {
|
|
347
|
+
throw new Error(`Invalid phase: ${phase}`);
|
|
400
348
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
},
|
|
407
|
-
checkpoints: {
|
|
408
|
-
resumeFrom: phaseName
|
|
409
|
-
}
|
|
410
|
-
}, baseDir);
|
|
349
|
+
return updateFlow({
|
|
350
|
+
phase,
|
|
351
|
+
status: 'in_progress',
|
|
352
|
+
skipReason: reason
|
|
353
|
+
}, worktreePath);
|
|
411
354
|
}
|
|
412
355
|
|
|
413
356
|
/**
|
|
414
|
-
* Complete
|
|
415
|
-
*
|
|
416
|
-
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
417
|
-
* @returns {Object|null} Updated state or null on error
|
|
357
|
+
* Complete current phase and move to next
|
|
358
|
+
* Uses updateFlow pattern to avoid direct mutation issues
|
|
418
359
|
*/
|
|
419
|
-
function
|
|
420
|
-
const
|
|
421
|
-
if (
|
|
422
|
-
console.error(`Cannot complete workflow: ${state.message}`);
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
if (!state) return null;
|
|
360
|
+
function completePhase(result = null, worktreePath = process.cwd()) {
|
|
361
|
+
const flow = readFlow(worktreePath);
|
|
362
|
+
if (!flow) return null;
|
|
426
363
|
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
const endTime = new Date(now).getTime();
|
|
364
|
+
const currentIndex = PHASES.indexOf(flow.phase);
|
|
365
|
+
const nextPhase = PHASES[currentIndex + 1] || 'complete';
|
|
430
366
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
},
|
|
443
|
-
metrics: {
|
|
444
|
-
totalDuration: endTime - startTime,
|
|
445
|
-
...result.metrics
|
|
367
|
+
// Build updates object
|
|
368
|
+
const updates = {
|
|
369
|
+
phase: nextPhase,
|
|
370
|
+
status: nextPhase === 'complete' ? 'completed' : 'in_progress'
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Store result in appropriate field
|
|
374
|
+
if (result) {
|
|
375
|
+
const resultField = getResultField(flow.phase);
|
|
376
|
+
if (resultField) {
|
|
377
|
+
updates[resultField] = result;
|
|
446
378
|
}
|
|
447
|
-
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
updateFlow(updates, worktreePath);
|
|
382
|
+
return readFlow(worktreePath);
|
|
448
383
|
}
|
|
449
384
|
|
|
450
385
|
/**
|
|
451
|
-
*
|
|
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
|
|
386
|
+
* Map phase to result field
|
|
455
387
|
*/
|
|
456
|
-
function
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
canResume: false,
|
|
464
|
-
resumeFrom: null,
|
|
465
|
-
resumeContext: { abortReason: reason }
|
|
466
|
-
}
|
|
467
|
-
}, baseDir);
|
|
388
|
+
function getResultField(phase) {
|
|
389
|
+
const mapping = {
|
|
390
|
+
'exploration': 'exploration',
|
|
391
|
+
'planning': 'plan',
|
|
392
|
+
'review-loop': 'reviewResult'
|
|
393
|
+
};
|
|
394
|
+
return mapping[phase] || null;
|
|
468
395
|
}
|
|
469
396
|
|
|
470
397
|
/**
|
|
471
|
-
*
|
|
472
|
-
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
473
|
-
* @returns {boolean} Success status
|
|
398
|
+
* Mark workflow as failed
|
|
474
399
|
*/
|
|
475
|
-
function
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
fs.unlinkSync(statePath);
|
|
481
|
-
}
|
|
482
|
-
return true;
|
|
483
|
-
} catch (error) {
|
|
484
|
-
console.error(`Error deleting state: ${error.message}`);
|
|
485
|
-
return false;
|
|
486
|
-
}
|
|
400
|
+
function failWorkflow(error, worktreePath = process.cwd()) {
|
|
401
|
+
return updateFlow({
|
|
402
|
+
status: 'failed',
|
|
403
|
+
error: error?.message || String(error)
|
|
404
|
+
}, worktreePath);
|
|
487
405
|
}
|
|
488
406
|
|
|
489
407
|
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
* @
|
|
408
|
+
* Mark workflow as complete
|
|
409
|
+
* Automatically clears the active task from tasks.json using stored projectPath
|
|
410
|
+
* @param {string} worktreePath - Path to worktree
|
|
493
411
|
*/
|
|
494
|
-
function
|
|
495
|
-
const
|
|
496
|
-
if (state instanceof Error) return false;
|
|
497
|
-
if (!state) return false;
|
|
412
|
+
function completeWorkflow(worktreePath = process.cwd()) {
|
|
413
|
+
const flow = readFlow(worktreePath);
|
|
498
414
|
|
|
499
|
-
|
|
415
|
+
// Clear active task from main project if projectPath is stored
|
|
416
|
+
if (flow && flow.projectPath) {
|
|
417
|
+
clearActiveTask(flow.projectPath);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return updateFlow({
|
|
421
|
+
phase: 'complete',
|
|
422
|
+
status: 'completed',
|
|
423
|
+
completedAt: new Date().toISOString()
|
|
424
|
+
}, worktreePath);
|
|
500
425
|
}
|
|
501
426
|
|
|
502
427
|
/**
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
* @returns {Object|null} Summary object or null
|
|
428
|
+
* Abort workflow
|
|
429
|
+
* Also clears the active task from tasks.json using stored projectPath
|
|
506
430
|
*/
|
|
507
|
-
function
|
|
508
|
-
const
|
|
509
|
-
if (state instanceof Error) {
|
|
510
|
-
return { error: state.message, code: state.code };
|
|
511
|
-
}
|
|
512
|
-
if (!state) return null;
|
|
431
|
+
function abortWorkflow(reason, worktreePath = process.cwd()) {
|
|
432
|
+
const flow = readFlow(worktreePath);
|
|
513
433
|
|
|
514
|
-
|
|
515
|
-
|
|
434
|
+
// Clear active task from main project if projectPath is stored
|
|
435
|
+
if (flow && flow.projectPath) {
|
|
436
|
+
clearActiveTask(flow.projectPath);
|
|
437
|
+
}
|
|
516
438
|
|
|
517
|
-
return {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
};
|
|
439
|
+
return updateFlow({
|
|
440
|
+
status: 'aborted',
|
|
441
|
+
abortReason: reason,
|
|
442
|
+
abortedAt: new Date().toISOString()
|
|
443
|
+
}, worktreePath);
|
|
539
444
|
}
|
|
540
445
|
|
|
446
|
+
// =============================================================================
|
|
447
|
+
// CONVENIENCE FUNCTIONS
|
|
448
|
+
// =============================================================================
|
|
449
|
+
|
|
541
450
|
/**
|
|
542
|
-
*
|
|
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
|
|
451
|
+
* Get workflow summary for display
|
|
547
452
|
*/
|
|
548
|
-
function
|
|
549
|
-
const
|
|
550
|
-
if (
|
|
551
|
-
console.error(`Cannot update agent result: ${state.message}`);
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
if (!state) return null;
|
|
453
|
+
function getFlowSummary(worktreePath = process.cwd()) {
|
|
454
|
+
const flow = readFlow(worktreePath);
|
|
455
|
+
if (!flow) return null;
|
|
555
456
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
457
|
+
return {
|
|
458
|
+
task: flow.task?.title || 'Unknown',
|
|
459
|
+
taskId: flow.task?.id,
|
|
460
|
+
phase: flow.phase,
|
|
461
|
+
status: flow.status,
|
|
462
|
+
lastUpdate: flow.lastUpdate,
|
|
463
|
+
pr: flow.pr?.number ? `#${flow.pr.number}` : null
|
|
561
464
|
};
|
|
562
|
-
|
|
563
|
-
agents.lastRun[agentName] = result;
|
|
564
|
-
agents.totalIssuesFound += result.issues || 0;
|
|
565
|
-
|
|
566
|
-
return updateState({ agents }, baseDir);
|
|
567
465
|
}
|
|
568
466
|
|
|
569
467
|
/**
|
|
570
|
-
*
|
|
571
|
-
* @param {Object} [result={}] - Iteration result
|
|
572
|
-
* @param {string} [baseDir=process.cwd()] - Base directory
|
|
573
|
-
* @returns {Object|null} Updated state or null on error
|
|
468
|
+
* Check if workflow can be resumed
|
|
574
469
|
*/
|
|
575
|
-
function
|
|
576
|
-
const
|
|
577
|
-
if (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
if (!state) return null;
|
|
582
|
-
|
|
583
|
-
const agents = state.agents || {
|
|
584
|
-
lastRun: {},
|
|
585
|
-
totalIterations: 0,
|
|
586
|
-
totalIssuesFound: 0,
|
|
587
|
-
totalIssuesFixed: 0
|
|
588
|
-
};
|
|
470
|
+
function canResume(worktreePath = process.cwd()) {
|
|
471
|
+
const flow = readFlow(worktreePath);
|
|
472
|
+
if (!flow) return false;
|
|
473
|
+
return flow.status === 'in_progress' && flow.phase !== 'complete';
|
|
474
|
+
}
|
|
589
475
|
|
|
590
|
-
|
|
591
|
-
|
|
476
|
+
// =============================================================================
|
|
477
|
+
// BACKWARDS COMPATIBILITY ALIASES
|
|
478
|
+
// =============================================================================
|
|
592
479
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
480
|
+
// These maintain compatibility with existing agent code
|
|
481
|
+
const readState = readFlow;
|
|
482
|
+
const writeState = writeFlow;
|
|
483
|
+
const updateState = updateFlow;
|
|
484
|
+
const createState = (type, policy) => createFlow({ id: 'manual', title: 'Manual task', source: 'manual' }, policy);
|
|
485
|
+
const deleteState = deleteFlow;
|
|
486
|
+
const hasActiveWorkflow = hasActiveTask;
|
|
487
|
+
const getWorkflowSummary = getFlowSummary;
|
|
600
488
|
|
|
601
|
-
// Export all functions
|
|
602
489
|
module.exports = {
|
|
603
490
|
// Constants
|
|
604
|
-
SCHEMA_VERSION,
|
|
605
491
|
PHASES,
|
|
606
|
-
DEFAULT_POLICY,
|
|
607
|
-
|
|
608
|
-
// Core functions
|
|
609
|
-
generateWorkflowId,
|
|
610
|
-
getStatePath,
|
|
611
|
-
ensureStateDir,
|
|
612
492
|
|
|
613
|
-
//
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
493
|
+
// Tasks (main project)
|
|
494
|
+
getTasksPath,
|
|
495
|
+
readTasks,
|
|
496
|
+
writeTasks,
|
|
497
|
+
setActiveTask,
|
|
498
|
+
clearActiveTask,
|
|
499
|
+
hasActiveTask,
|
|
500
|
+
|
|
501
|
+
// Flow (worktree)
|
|
502
|
+
getFlowPath,
|
|
503
|
+
readFlow,
|
|
504
|
+
writeFlow,
|
|
505
|
+
updateFlow,
|
|
506
|
+
createFlow,
|
|
507
|
+
deleteFlow,
|
|
619
508
|
|
|
620
509
|
// Phase management
|
|
510
|
+
isValidPhase,
|
|
511
|
+
setPhase,
|
|
621
512
|
startPhase,
|
|
622
513
|
completePhase,
|
|
623
514
|
failPhase,
|
|
624
515
|
skipToPhase,
|
|
625
|
-
|
|
626
|
-
// Workflow lifecycle
|
|
516
|
+
failWorkflow,
|
|
627
517
|
completeWorkflow,
|
|
628
518
|
abortWorkflow,
|
|
629
|
-
hasActiveWorkflow,
|
|
630
|
-
getWorkflowSummary,
|
|
631
519
|
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
520
|
+
// Convenience
|
|
521
|
+
getFlowSummary,
|
|
522
|
+
canResume,
|
|
523
|
+
generateWorkflowId,
|
|
524
|
+
|
|
525
|
+
// Backwards compatibility
|
|
526
|
+
readState,
|
|
527
|
+
writeState,
|
|
528
|
+
updateState,
|
|
529
|
+
createState,
|
|
530
|
+
deleteState,
|
|
531
|
+
hasActiveWorkflow,
|
|
532
|
+
getWorkflowSummary
|
|
635
533
|
};
|