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