oh-my-claude-sisyphus 3.8.6 → 3.8.7
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/plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/persistent-mode.mjs +202 -317
- package/templates/hooks/persistent-mode.mjs +220 -184
package/package.json
CHANGED
|
@@ -1,30 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* OMC Persistent Mode Hook (Node.js)
|
|
5
|
+
* Minimal continuation enforcer for all OMC modes.
|
|
6
|
+
* Stripped down for reliability — no optional imports, no PRD, no notepad pruning.
|
|
7
|
+
*
|
|
8
|
+
* Supported modes: ralph, autopilot, ultrapilot, swarm, ultrawork, ecomode, ultraqa, pipeline
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
10
|
-
import { join
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
11
13
|
import { homedir } from 'os';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
13
|
-
|
|
14
|
-
// Dynamic import for notepad with fallback
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = dirname(__filename);
|
|
17
|
-
let pruneOldEntries = null;
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const notepadModule = await import(join(__dirname, '../dist/hooks/notepad/index.js'));
|
|
21
|
-
pruneOldEntries = notepadModule.pruneOldEntries;
|
|
22
|
-
} catch (err) {
|
|
23
|
-
// Notepad module not available - pruning will be skipped
|
|
24
|
-
// This can happen in older versions or if build failed
|
|
25
|
-
}
|
|
26
14
|
|
|
27
|
-
// Read all stdin
|
|
28
15
|
async function readStdin() {
|
|
29
16
|
const chunks = [];
|
|
30
17
|
for await (const chunk of process.stdin) {
|
|
@@ -33,7 +20,6 @@ async function readStdin() {
|
|
|
33
20
|
return Buffer.concat(chunks).toString('utf-8');
|
|
34
21
|
}
|
|
35
22
|
|
|
36
|
-
// Read JSON file safely
|
|
37
23
|
function readJsonFile(path) {
|
|
38
24
|
try {
|
|
39
25
|
if (!existsSync(path)) return null;
|
|
@@ -43,9 +29,13 @@ function readJsonFile(path) {
|
|
|
43
29
|
}
|
|
44
30
|
}
|
|
45
31
|
|
|
46
|
-
// Write JSON file safely
|
|
47
32
|
function writeJsonFile(path, data) {
|
|
48
33
|
try {
|
|
34
|
+
// Ensure directory exists
|
|
35
|
+
const dir = path.substring(0, path.lastIndexOf('/'));
|
|
36
|
+
if (dir && !existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
49
39
|
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
50
40
|
return true;
|
|
51
41
|
} catch {
|
|
@@ -53,104 +43,72 @@ function writeJsonFile(path, data) {
|
|
|
53
43
|
}
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
for (const prdPath of paths) {
|
|
65
|
-
const prd = readJsonFile(prdPath);
|
|
66
|
-
if (prd && Array.isArray(prd.userStories)) {
|
|
67
|
-
const stories = prd.userStories;
|
|
68
|
-
const completed = stories.filter(s => s.passes === true);
|
|
69
|
-
const pending = stories.filter(s => s.passes !== true);
|
|
70
|
-
const sortedPending = [...pending].sort((a, b) => (a.priority || 999) - (b.priority || 999));
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
hasPrd: true,
|
|
74
|
-
total: stories.length,
|
|
75
|
-
completed: completed.length,
|
|
76
|
-
pending: pending.length,
|
|
77
|
-
allComplete: pending.length === 0,
|
|
78
|
-
nextStory: sortedPending[0] || null,
|
|
79
|
-
incompleteIds: pending.map(s => s.id)
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
}
|
|
46
|
+
/**
|
|
47
|
+
* Read state file from local or global location, tracking the source.
|
|
48
|
+
*/
|
|
49
|
+
function readStateFile(stateDir, globalStateDir, filename) {
|
|
50
|
+
const localPath = join(stateDir, filename);
|
|
51
|
+
const globalPath = join(globalStateDir, filename);
|
|
83
52
|
|
|
84
|
-
|
|
53
|
+
let state = readJsonFile(localPath);
|
|
54
|
+
if (state) return { state, path: localPath };
|
|
55
|
+
|
|
56
|
+
state = readJsonFile(globalPath);
|
|
57
|
+
if (state) return { state, path: globalPath };
|
|
58
|
+
|
|
59
|
+
return { state: null, path: localPath }; // Default to local for new writes
|
|
85
60
|
}
|
|
86
61
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
];
|
|
62
|
+
/**
|
|
63
|
+
* Count incomplete Tasks from Claude Code's native Task system.
|
|
64
|
+
*/
|
|
65
|
+
function countIncompleteTasks(sessionId) {
|
|
66
|
+
if (!sessionId || typeof sessionId !== 'string') return 0;
|
|
67
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) return 0;
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const content = readFileSync(progressPath, 'utf-8');
|
|
98
|
-
const patterns = [];
|
|
99
|
-
let inPatterns = false;
|
|
100
|
-
|
|
101
|
-
for (const line of content.split('\n')) {
|
|
102
|
-
const trimmed = line.trim();
|
|
103
|
-
if (trimmed === '## Codebase Patterns') {
|
|
104
|
-
inPatterns = true;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (trimmed === '---') {
|
|
108
|
-
inPatterns = false;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
if (inPatterns && trimmed.startsWith('-')) {
|
|
112
|
-
const pattern = trimmed.slice(1).trim();
|
|
113
|
-
if (pattern && !pattern.includes('No patterns')) {
|
|
114
|
-
patterns.push(pattern);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
69
|
+
const taskDir = join(homedir(), '.claude', 'tasks', sessionId);
|
|
70
|
+
if (!existsSync(taskDir)) return 0;
|
|
118
71
|
|
|
119
|
-
|
|
120
|
-
|
|
72
|
+
let count = 0;
|
|
73
|
+
try {
|
|
74
|
+
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f !== '.lock');
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
try {
|
|
77
|
+
const content = readFileSync(join(taskDir, file), 'utf-8');
|
|
78
|
+
const task = JSON.parse(content);
|
|
79
|
+
if (task.status === 'pending' || task.status === 'in_progress') count++;
|
|
80
|
+
} catch { /* skip */ }
|
|
121
81
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return [];
|
|
82
|
+
} catch { /* skip */ }
|
|
83
|
+
return count;
|
|
125
84
|
}
|
|
126
85
|
|
|
127
|
-
// Count incomplete todos
|
|
128
86
|
function countIncompleteTodos(todosDir, projectDir) {
|
|
129
87
|
let count = 0;
|
|
130
88
|
|
|
131
|
-
// Check global todos
|
|
132
89
|
if (existsSync(todosDir)) {
|
|
133
90
|
try {
|
|
134
91
|
const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
|
|
135
92
|
for (const file of files) {
|
|
136
|
-
|
|
137
|
-
|
|
93
|
+
try {
|
|
94
|
+
const content = readFileSync(join(todosDir, file), 'utf-8');
|
|
95
|
+
const data = JSON.parse(content);
|
|
96
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
138
97
|
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
139
|
-
}
|
|
98
|
+
} catch { /* skip */ }
|
|
140
99
|
}
|
|
141
|
-
} catch {}
|
|
100
|
+
} catch { /* skip */ }
|
|
142
101
|
}
|
|
143
102
|
|
|
144
|
-
// Check project todos
|
|
145
103
|
for (const path of [
|
|
146
104
|
join(projectDir, '.omc', 'todos.json'),
|
|
147
105
|
join(projectDir, '.claude', 'todos.json')
|
|
148
106
|
]) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
107
|
+
try {
|
|
108
|
+
const data = readJsonFile(path);
|
|
109
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
152
110
|
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
153
|
-
}
|
|
111
|
+
} catch { /* skip */ }
|
|
154
112
|
}
|
|
155
113
|
|
|
156
114
|
return count;
|
|
@@ -163,286 +121,213 @@ async function main() {
|
|
|
163
121
|
try { data = JSON.parse(input); } catch {}
|
|
164
122
|
|
|
165
123
|
const directory = data.directory || process.cwd();
|
|
124
|
+
const sessionId = data.sessionId || data.session_id || '';
|
|
166
125
|
const todosDir = join(homedir(), '.claude', 'todos');
|
|
126
|
+
const stateDir = join(directory, '.omc', 'state');
|
|
127
|
+
const globalStateDir = join(homedir(), '.omc', 'state');
|
|
128
|
+
|
|
129
|
+
// Read all mode states (local-first with fallback to global)
|
|
130
|
+
const ralph = readStateFile(stateDir, globalStateDir, 'ralph-state.json');
|
|
131
|
+
const autopilot = readStateFile(stateDir, globalStateDir, 'autopilot-state.json');
|
|
132
|
+
const ultrapilot = readStateFile(stateDir, globalStateDir, 'ultrapilot-state.json');
|
|
133
|
+
const ultrawork = readStateFile(stateDir, globalStateDir, 'ultrawork-state.json');
|
|
134
|
+
const ecomode = readStateFile(stateDir, globalStateDir, 'ecomode-state.json');
|
|
135
|
+
const ultraqa = readStateFile(stateDir, globalStateDir, 'ultraqa-state.json');
|
|
136
|
+
const pipeline = readStateFile(stateDir, globalStateDir, 'pipeline-state.json');
|
|
137
|
+
|
|
138
|
+
// Swarm uses swarm-summary.json (not swarm-state.json) + marker file
|
|
139
|
+
const swarmMarker = existsSync(join(stateDir, 'swarm-active.marker'));
|
|
140
|
+
const swarmSummary = readJsonFile(join(stateDir, 'swarm-summary.json'));
|
|
141
|
+
|
|
142
|
+
// Count incomplete items
|
|
143
|
+
const taskCount = countIncompleteTasks(sessionId);
|
|
144
|
+
const todoCount = countIncompleteTodos(todosDir, directory);
|
|
145
|
+
const totalIncomplete = taskCount + todoCount;
|
|
146
|
+
|
|
147
|
+
// Priority 1: Ralph Loop (explicit persistence mode)
|
|
148
|
+
if (ralph.state?.active) {
|
|
149
|
+
const iteration = ralph.state.iteration || 1;
|
|
150
|
+
const maxIter = ralph.state.max_iterations || 100;
|
|
167
151
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Check for ralph loop state
|
|
173
|
-
const ralphState = readJsonFile(join(directory, '.omc', 'state', 'ralph-state.json'));
|
|
174
|
-
|
|
175
|
-
// Check for verification state
|
|
176
|
-
const verificationState = readJsonFile(join(directory, '.omc', 'state', 'ralph-verification.json'));
|
|
177
|
-
|
|
178
|
-
// Count incomplete todos
|
|
179
|
-
const incompleteCount = countIncompleteTodos(todosDir, directory);
|
|
180
|
-
|
|
181
|
-
// Check PRD status
|
|
182
|
-
const prdStatus = getPrdStatus(directory);
|
|
183
|
-
const progressPatterns = getProgressPatterns(directory);
|
|
184
|
-
|
|
185
|
-
// Priority 1: Ralph Loop with PRD and Oracle Verification
|
|
186
|
-
if (ralphState?.active) {
|
|
187
|
-
const iteration = ralphState.iteration || 1;
|
|
188
|
-
const maxIter = ralphState.max_iterations || 100; // Increased for PRD mode
|
|
189
|
-
|
|
190
|
-
// If PRD exists and all stories are complete, allow completion
|
|
191
|
-
if (prdStatus.hasPrd && prdStatus.allComplete) {
|
|
192
|
-
// Prune old notepad entries on clean session stop
|
|
193
|
-
try {
|
|
194
|
-
if (pruneOldEntries) {
|
|
195
|
-
const pruneResult = pruneOldEntries(directory, 7);
|
|
196
|
-
if (pruneResult.pruned > 0) {
|
|
197
|
-
// Optionally log: console.error(`Pruned ${pruneResult.pruned} old notepad entries`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
} catch (err) {
|
|
201
|
-
// Silently ignore prune errors
|
|
202
|
-
}
|
|
152
|
+
if (iteration < maxIter) {
|
|
153
|
+
ralph.state.iteration = iteration + 1;
|
|
154
|
+
writeJsonFile(ralph.path, ralph.state);
|
|
203
155
|
|
|
204
156
|
console.log(JSON.stringify({
|
|
205
|
-
continue:
|
|
206
|
-
reason: `[RALPH LOOP
|
|
157
|
+
continue: false,
|
|
158
|
+
reason: `[RALPH LOOP - ITERATION ${iteration + 1}/${maxIter}] Work is NOT done. Continue. When complete, output: <promise>${ralph.state.completion_promise || 'DONE'}</promise>\n${ralph.state.prompt ? `Task: ${ralph.state.prompt}` : ''}`
|
|
207
159
|
}));
|
|
208
160
|
return;
|
|
209
161
|
}
|
|
162
|
+
}
|
|
210
163
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
${verificationState.completion_claim || 'Task marked complete'}
|
|
229
|
-
|
|
230
|
-
${verificationState.oracle_feedback ? `**Previous Oracle Feedback (rejected):**
|
|
231
|
-
${verificationState.oracle_feedback}
|
|
232
|
-
` : ''}
|
|
233
|
-
|
|
234
|
-
## MANDATORY VERIFICATION STEPS
|
|
235
|
-
|
|
236
|
-
1. **Spawn Oracle Agent** for verification
|
|
237
|
-
2. **Oracle must check:**
|
|
238
|
-
- Are ALL requirements from the original task met?
|
|
239
|
-
- Is the implementation complete, not partial?
|
|
240
|
-
- Are there any obvious bugs or issues?
|
|
241
|
-
- Does the code compile/run without errors?
|
|
242
|
-
- Are tests passing (if applicable)?
|
|
243
|
-
|
|
244
|
-
3. **Based on Oracle's response:**
|
|
245
|
-
- If APPROVED: Output \`<oracle-approved>VERIFIED_COMPLETE</oracle-approved>\`
|
|
246
|
-
- If REJECTED: Continue working on the identified issues
|
|
247
|
-
|
|
248
|
-
</ralph-verification>
|
|
164
|
+
// Priority 2: Autopilot (high-level orchestration)
|
|
165
|
+
if (autopilot.state?.active) {
|
|
166
|
+
const phase = autopilot.state.phase || 'unknown';
|
|
167
|
+
if (phase !== 'complete') {
|
|
168
|
+
const newCount = (autopilot.state.reinforcement_count || 0) + 1;
|
|
169
|
+
if (newCount <= 20) {
|
|
170
|
+
autopilot.state.reinforcement_count = newCount;
|
|
171
|
+
writeJsonFile(autopilot.path, autopilot.state);
|
|
172
|
+
|
|
173
|
+
console.log(JSON.stringify({
|
|
174
|
+
continue: false,
|
|
175
|
+
reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working.`
|
|
176
|
+
}));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
249
181
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
182
|
+
// Priority 3: Ultrapilot (parallel autopilot)
|
|
183
|
+
if (ultrapilot.state?.active) {
|
|
184
|
+
const workers = ultrapilot.state.workers || [];
|
|
185
|
+
const incomplete = workers.filter(w => w.status !== 'complete' && w.status !== 'failed').length;
|
|
186
|
+
if (incomplete > 0) {
|
|
187
|
+
const newCount = (ultrapilot.state.reinforcement_count || 0) + 1;
|
|
188
|
+
if (newCount <= 20) {
|
|
189
|
+
ultrapilot.state.reinforcement_count = newCount;
|
|
190
|
+
writeJsonFile(ultrapilot.path, ultrapilot.state);
|
|
191
|
+
|
|
192
|
+
console.log(JSON.stringify({
|
|
193
|
+
continue: false,
|
|
194
|
+
reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue.`
|
|
195
|
+
}));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
254
198
|
}
|
|
199
|
+
}
|
|
255
200
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
## CURRENT STORY: ${prdStatus.nextStory.id} - ${prdStatus.nextStory.title}
|
|
271
|
-
|
|
272
|
-
${prdStatus.nextStory.description || ''}
|
|
273
|
-
|
|
274
|
-
**Acceptance Criteria:**
|
|
275
|
-
${(prdStatus.nextStory.acceptanceCriteria || []).map((c, i) => `${i + 1}. ${c}`).join('\n')}
|
|
276
|
-
|
|
277
|
-
**Instructions:**
|
|
278
|
-
1. Implement this story completely
|
|
279
|
-
2. Verify ALL acceptance criteria are met
|
|
280
|
-
3. Run quality checks (tests, typecheck, lint)
|
|
281
|
-
4. Update prd.json to set passes: true for ${prdStatus.nextStory.id}
|
|
282
|
-
5. Append progress to progress.txt
|
|
283
|
-
6. Move to next story or output completion promise
|
|
284
|
-
`;
|
|
285
|
-
}
|
|
201
|
+
// Priority 4: Swarm (coordinated agents with SQLite)
|
|
202
|
+
if (swarmMarker && swarmSummary?.active) {
|
|
203
|
+
const pending = (swarmSummary.tasks_pending || 0) + (swarmSummary.tasks_claimed || 0);
|
|
204
|
+
if (pending > 0) {
|
|
205
|
+
const newCount = (swarmSummary.reinforcement_count || 0) + 1;
|
|
206
|
+
if (newCount <= 15) {
|
|
207
|
+
swarmSummary.reinforcement_count = newCount;
|
|
208
|
+
writeJsonFile(join(stateDir, 'swarm-summary.json'), swarmSummary);
|
|
209
|
+
|
|
210
|
+
console.log(JSON.stringify({
|
|
211
|
+
continue: false,
|
|
212
|
+
reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working.`
|
|
213
|
+
}));
|
|
214
|
+
return;
|
|
286
215
|
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
287
218
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
219
|
+
// Priority 5: Pipeline (sequential stages)
|
|
220
|
+
if (pipeline.state?.active) {
|
|
221
|
+
const currentStage = pipeline.state.current_stage || 0;
|
|
222
|
+
const totalStages = pipeline.state.stages?.length || 0;
|
|
223
|
+
if (currentStage < totalStages) {
|
|
224
|
+
const newCount = (pipeline.state.reinforcement_count || 0) + 1;
|
|
225
|
+
if (newCount <= 15) {
|
|
226
|
+
pipeline.state.reinforcement_count = newCount;
|
|
227
|
+
writeJsonFile(pipeline.path, pipeline.state);
|
|
228
|
+
|
|
229
|
+
console.log(JSON.stringify({
|
|
230
|
+
continue: false,
|
|
231
|
+
reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue.`
|
|
232
|
+
}));
|
|
233
|
+
return;
|
|
295
234
|
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Priority 6: UltraQA (QA cycling)
|
|
239
|
+
if (ultraqa.state?.active) {
|
|
240
|
+
const cycle = ultraqa.state.cycle || 1;
|
|
241
|
+
const maxCycles = ultraqa.state.max_cycles || 10;
|
|
242
|
+
if (cycle < maxCycles && !ultraqa.state.all_passing) {
|
|
243
|
+
ultraqa.state.cycle = cycle + 1;
|
|
244
|
+
writeJsonFile(ultraqa.path, ultraqa.state);
|
|
296
245
|
|
|
297
246
|
console.log(JSON.stringify({
|
|
298
247
|
continue: false,
|
|
299
|
-
reason:
|
|
300
|
-
|
|
301
|
-
[RALPH LOOP - ITERATION ${newIter}/${maxIter}]
|
|
302
|
-
|
|
303
|
-
Your previous attempt did not output the completion promise. The work is NOT done yet.
|
|
304
|
-
${prdContext}${patternsContext}
|
|
305
|
-
CRITICAL INSTRUCTIONS:
|
|
306
|
-
1. Review your progress and the original task
|
|
307
|
-
2. ${prdStatus.hasPrd ? 'Check prd.json - are ALL stories marked passes: true?' : 'Check your todo list - are ALL items marked complete?'}
|
|
308
|
-
3. Continue from where you left off
|
|
309
|
-
4. When FULLY complete, output: <promise>${ralphState.completion_promise || 'DONE'}</promise>
|
|
310
|
-
5. Do NOT stop until the task is truly done
|
|
311
|
-
|
|
312
|
-
${ralphState.prompt ? `Original task: ${ralphState.prompt}` : ''}
|
|
313
|
-
|
|
314
|
-
</ralph-loop-continuation>
|
|
315
|
-
|
|
316
|
-
---
|
|
317
|
-
`
|
|
248
|
+
reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing.`
|
|
318
249
|
}));
|
|
319
250
|
return;
|
|
320
251
|
}
|
|
321
252
|
}
|
|
322
253
|
|
|
323
|
-
// Priority
|
|
324
|
-
if (
|
|
325
|
-
const newCount = (
|
|
326
|
-
const maxReinforcements =
|
|
254
|
+
// Priority 7: Ultrawork with incomplete todos/tasks
|
|
255
|
+
if (ultrawork.state?.active && totalIncomplete > 0) {
|
|
256
|
+
const newCount = (ultrawork.state.reinforcement_count || 0) + 1;
|
|
257
|
+
const maxReinforcements = ultrawork.state.max_reinforcements || 15;
|
|
327
258
|
|
|
328
|
-
// Escape mechanism: after max reinforcements, allow stopping
|
|
329
259
|
if (newCount > maxReinforcements) {
|
|
330
|
-
// Prune old notepad entries on clean session stop
|
|
331
|
-
try {
|
|
332
|
-
if (pruneOldEntries) {
|
|
333
|
-
const pruneResult = pruneOldEntries(directory, 7);
|
|
334
|
-
if (pruneResult.pruned > 0) {
|
|
335
|
-
// Optionally log: console.error(`Pruned ${pruneResult.pruned} old notepad entries`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
} catch (err) {
|
|
339
|
-
// Silently ignore prune errors
|
|
340
|
-
}
|
|
341
|
-
|
|
342
260
|
console.log(JSON.stringify({
|
|
343
261
|
continue: true,
|
|
344
|
-
reason: `[ULTRAWORK ESCAPE]
|
|
262
|
+
reason: `[ULTRAWORK ESCAPE] Max reinforcements reached. Allowing stop.`
|
|
345
263
|
}));
|
|
346
264
|
return;
|
|
347
265
|
}
|
|
348
266
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
writeJsonFile(join(directory, '.omc', 'state', 'ultrawork-state.json'), ultraworkState);
|
|
267
|
+
ultrawork.state.reinforcement_count = newCount;
|
|
268
|
+
ultrawork.state.last_checked_at = new Date().toISOString();
|
|
269
|
+
writeJsonFile(ultrawork.path, ultrawork.state);
|
|
353
270
|
|
|
271
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
354
272
|
console.log(JSON.stringify({
|
|
355
273
|
continue: false,
|
|
356
|
-
reason:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
Your ultrawork session is NOT complete. ${incompleteCount} incomplete todos remain.
|
|
361
|
-
|
|
362
|
-
REMEMBER THE ULTRAWORK RULES:
|
|
363
|
-
- **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
|
|
364
|
-
- **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)
|
|
365
|
-
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each
|
|
366
|
-
- **VERIFY**: Check ALL requirements met before done
|
|
367
|
-
- **NO Premature Stopping**: ALL TODOs must be complete
|
|
274
|
+
reason: `[ULTRAWORK #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.\n${ultrawork.state.original_prompt ? `Task: ${ultrawork.state.original_prompt}` : ''}`
|
|
275
|
+
}));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
368
278
|
|
|
369
|
-
|
|
279
|
+
// Priority 8: Ecomode with incomplete todos/tasks
|
|
280
|
+
if (ecomode.state?.active && totalIncomplete > 0) {
|
|
281
|
+
const newCount = (ecomode.state.reinforcement_count || 0) + 1;
|
|
282
|
+
const maxReinforcements = ecomode.state.max_reinforcements || 15;
|
|
370
283
|
|
|
371
|
-
|
|
284
|
+
if (newCount > maxReinforcements) {
|
|
285
|
+
console.log(JSON.stringify({
|
|
286
|
+
continue: true,
|
|
287
|
+
reason: `[ECOMODE ESCAPE] Max reinforcements reached. Allowing stop.`
|
|
288
|
+
}));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
372
291
|
|
|
373
|
-
|
|
292
|
+
ecomode.state.reinforcement_count = newCount;
|
|
293
|
+
writeJsonFile(ecomode.path, ecomode.state);
|
|
374
294
|
|
|
375
|
-
|
|
376
|
-
|
|
295
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
296
|
+
console.log(JSON.stringify({
|
|
297
|
+
continue: false,
|
|
298
|
+
reason: `[ECOMODE #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
377
299
|
}));
|
|
378
300
|
return;
|
|
379
301
|
}
|
|
380
302
|
|
|
381
|
-
// Priority
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
const contFile = join(directory, '.omc', 'continuation-count.json');
|
|
303
|
+
// Priority 9: Generic Task/Todo continuation (no specific mode)
|
|
304
|
+
if (totalIncomplete > 0) {
|
|
305
|
+
const contFile = join(stateDir, 'continuation-count.json');
|
|
385
306
|
let contState = readJsonFile(contFile) || { count: 0 };
|
|
386
307
|
contState.count = (contState.count || 0) + 1;
|
|
387
|
-
contState.last_checked_at = new Date().toISOString();
|
|
388
308
|
writeJsonFile(contFile, contState);
|
|
389
309
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
// Escape mechanism: after max continuations, allow stopping
|
|
393
|
-
if (contState.count > maxContinuations) {
|
|
394
|
-
// Prune old notepad entries on clean session stop
|
|
395
|
-
try {
|
|
396
|
-
if (pruneOldEntries) {
|
|
397
|
-
const pruneResult = pruneOldEntries(directory, 7);
|
|
398
|
-
if (pruneResult.pruned > 0) {
|
|
399
|
-
// Optionally log: console.error(`Pruned ${pruneResult.pruned} old notepad entries`);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
} catch (err) {
|
|
403
|
-
// Silently ignore prune errors
|
|
404
|
-
}
|
|
405
|
-
|
|
310
|
+
if (contState.count > 15) {
|
|
406
311
|
console.log(JSON.stringify({
|
|
407
312
|
continue: true,
|
|
408
|
-
reason: `[
|
|
313
|
+
reason: `[CONTINUATION ESCAPE] Max continuations reached. Allowing stop.`
|
|
409
314
|
}));
|
|
410
315
|
return;
|
|
411
316
|
}
|
|
412
317
|
|
|
318
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
413
319
|
console.log(JSON.stringify({
|
|
414
320
|
continue: false,
|
|
415
|
-
reason:
|
|
416
|
-
|
|
417
|
-
[SYSTEM REMINDER - TODO CONTINUATION ${contState.count}/${maxContinuations}]
|
|
418
|
-
|
|
419
|
-
Incomplete tasks remain in your todo list (${incompleteCount} remaining). Continue working on the next pending task.
|
|
420
|
-
|
|
421
|
-
- Proceed without asking for permission
|
|
422
|
-
- Mark each task complete when finished
|
|
423
|
-
- Do not stop until all tasks are done
|
|
424
|
-
|
|
425
|
-
</todo-continuation>
|
|
426
|
-
|
|
427
|
-
---
|
|
428
|
-
`
|
|
321
|
+
reason: `[CONTINUATION ${contState.count}/15] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
429
322
|
}));
|
|
430
323
|
return;
|
|
431
324
|
}
|
|
432
325
|
|
|
433
|
-
// No blocking needed
|
|
434
|
-
// Prune old notepad entries on clean session stop
|
|
435
|
-
try {
|
|
436
|
-
const pruneResult = pruneOldEntries(directory, 7);
|
|
437
|
-
if (pruneResult.pruned > 0) {
|
|
438
|
-
// Optionally log: console.error(`Pruned ${pruneResult.pruned} old notepad entries`);
|
|
439
|
-
}
|
|
440
|
-
} catch (err) {
|
|
441
|
-
// Silently ignore prune errors
|
|
442
|
-
}
|
|
443
|
-
|
|
326
|
+
// No blocking needed
|
|
444
327
|
console.log(JSON.stringify({ continue: true }));
|
|
445
328
|
} catch (error) {
|
|
329
|
+
// On any error, allow stop rather than blocking forever
|
|
330
|
+
console.error(`[persistent-mode] Error: ${error.message}`);
|
|
446
331
|
console.log(JSON.stringify({ continue: true }));
|
|
447
332
|
}
|
|
448
333
|
}
|
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// OMC Persistent Mode Hook (Node.js)
|
|
3
|
-
// Unified handler for ultrawork, ralph-loop, and todo continuation
|
|
4
|
-
// Cross-platform: Windows, macOS, Linux
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
4
|
+
* OMC Persistent Mode Hook (Node.js)
|
|
5
|
+
* Minimal continuation enforcer for all OMC modes.
|
|
6
|
+
* Stripped down for reliability — no optional imports, no PRD, no notepad pruning.
|
|
7
|
+
*
|
|
8
|
+
* Supported modes: ralph, autopilot, ultrapilot, swarm, ultrawork, ecomode, ultraqa, pipeline
|
|
14
9
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId);
|
|
20
|
-
}
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
21
14
|
|
|
22
15
|
async function readStdin() {
|
|
23
16
|
const chunks = [];
|
|
@@ -36,19 +29,43 @@ function readJsonFile(path) {
|
|
|
36
29
|
}
|
|
37
30
|
}
|
|
38
31
|
|
|
32
|
+
function writeJsonFile(path, data) {
|
|
33
|
+
try {
|
|
34
|
+
// Ensure directory exists
|
|
35
|
+
const dir = path.substring(0, path.lastIndexOf('/'));
|
|
36
|
+
if (dir && !existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
/**
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
* Read state file from local or global location, tracking the source.
|
|
48
|
+
*/
|
|
49
|
+
function readStateFile(stateDir, globalStateDir, filename) {
|
|
50
|
+
const localPath = join(stateDir, filename);
|
|
51
|
+
const globalPath = join(globalStateDir, filename);
|
|
52
|
+
|
|
53
|
+
let state = readJsonFile(localPath);
|
|
54
|
+
if (state) return { state, path: localPath };
|
|
55
|
+
|
|
56
|
+
state = readJsonFile(globalPath);
|
|
57
|
+
if (state) return { state, path: globalPath };
|
|
58
|
+
|
|
59
|
+
return { state: null, path: localPath }; // Default to local for new writes
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Count incomplete Tasks from Claude Code's native Task system.
|
|
49
64
|
*/
|
|
50
65
|
function countIncompleteTasks(sessionId) {
|
|
51
|
-
if (!sessionId ||
|
|
66
|
+
if (!sessionId || typeof sessionId !== 'string') return 0;
|
|
67
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) return 0;
|
|
68
|
+
|
|
52
69
|
const taskDir = join(homedir(), '.claude', 'tasks', sessionId);
|
|
53
70
|
if (!existsSync(taskDir)) return 0;
|
|
54
71
|
|
|
@@ -59,49 +76,39 @@ function countIncompleteTasks(sessionId) {
|
|
|
59
76
|
try {
|
|
60
77
|
const content = readFileSync(join(taskDir, file), 'utf-8');
|
|
61
78
|
const task = JSON.parse(content);
|
|
62
|
-
// Match TypeScript isTaskIncomplete(): only pending/in_progress are incomplete
|
|
63
|
-
// 'deleted' and 'completed' are both treated as done
|
|
64
79
|
if (task.status === 'pending' || task.status === 'in_progress') count++;
|
|
65
|
-
} catch { /* skip
|
|
80
|
+
} catch { /* skip */ }
|
|
66
81
|
}
|
|
67
|
-
} catch { /*
|
|
82
|
+
} catch { /* skip */ }
|
|
68
83
|
return count;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
function writeJsonFile(path, data) {
|
|
72
|
-
try {
|
|
73
|
-
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
74
|
-
return true;
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
86
|
function countIncompleteTodos(todosDir, projectDir) {
|
|
81
87
|
let count = 0;
|
|
82
88
|
|
|
83
|
-
// Check global todos
|
|
84
89
|
if (existsSync(todosDir)) {
|
|
85
90
|
try {
|
|
86
91
|
const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
|
|
87
92
|
for (const file of files) {
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
try {
|
|
94
|
+
const content = readFileSync(join(todosDir, file), 'utf-8');
|
|
95
|
+
const data = JSON.parse(content);
|
|
96
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
90
97
|
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
91
|
-
}
|
|
98
|
+
} catch { /* skip */ }
|
|
92
99
|
}
|
|
93
|
-
} catch {}
|
|
100
|
+
} catch { /* skip */ }
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
// Check project todos
|
|
97
103
|
for (const path of [
|
|
98
104
|
join(projectDir, '.omc', 'todos.json'),
|
|
99
105
|
join(projectDir, '.claude', 'todos.json')
|
|
100
106
|
]) {
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
try {
|
|
108
|
+
const data = readJsonFile(path);
|
|
109
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
103
110
|
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
104
|
-
}
|
|
111
|
+
} catch { /* skip */ }
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
return count;
|
|
@@ -113,178 +120,205 @@ async function main() {
|
|
|
113
120
|
let data = {};
|
|
114
121
|
try { data = JSON.parse(input); } catch {}
|
|
115
122
|
|
|
116
|
-
const stopReason = data.stop_reason || data.stopReason || '';
|
|
117
|
-
const userRequested = data.user_requested || data.userRequested || false;
|
|
118
|
-
const sessionId = data.sessionId || data.session_id || '';
|
|
119
|
-
|
|
120
|
-
// Check for user abort - skip all continuation enforcement
|
|
121
|
-
// NOTE: Abort patterns are assumed - verify against actual Claude Code API values
|
|
122
|
-
if (userRequested || /abort|cancel|interrupt|ctrl_c|manual_stop/i.test(stopReason)) {
|
|
123
|
-
console.log(JSON.stringify({ continue: true }));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
123
|
const directory = data.directory || process.cwd();
|
|
124
|
+
const sessionId = data.sessionId || data.session_id || '';
|
|
128
125
|
const todosDir = join(homedir(), '.claude', 'todos');
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
const stateDir = join(directory, '.omc', 'state');
|
|
127
|
+
const globalStateDir = join(homedir(), '.omc', 'state');
|
|
128
|
+
|
|
129
|
+
// Read all mode states (local-first with fallback to global)
|
|
130
|
+
const ralph = readStateFile(stateDir, globalStateDir, 'ralph-state.json');
|
|
131
|
+
const autopilot = readStateFile(stateDir, globalStateDir, 'autopilot-state.json');
|
|
132
|
+
const ultrapilot = readStateFile(stateDir, globalStateDir, 'ultrapilot-state.json');
|
|
133
|
+
const ultrawork = readStateFile(stateDir, globalStateDir, 'ultrawork-state.json');
|
|
134
|
+
const ecomode = readStateFile(stateDir, globalStateDir, 'ecomode-state.json');
|
|
135
|
+
const ultraqa = readStateFile(stateDir, globalStateDir, 'ultraqa-state.json');
|
|
136
|
+
const pipeline = readStateFile(stateDir, globalStateDir, 'pipeline-state.json');
|
|
137
|
+
|
|
138
|
+
// Swarm uses swarm-summary.json (not swarm-state.json) + marker file
|
|
139
|
+
const swarmMarker = existsSync(join(stateDir, 'swarm-active.marker'));
|
|
140
|
+
const swarmSummary = readJsonFile(join(stateDir, 'swarm-summary.json'));
|
|
141
|
+
|
|
142
|
+
// Count incomplete items
|
|
144
143
|
const taskCount = countIncompleteTasks(sessionId);
|
|
145
|
-
const
|
|
144
|
+
const todoCount = countIncompleteTodos(todosDir, directory);
|
|
145
|
+
const totalIncomplete = taskCount + todoCount;
|
|
146
146
|
|
|
147
|
-
// Priority 1: Ralph Loop
|
|
148
|
-
if (
|
|
149
|
-
const iteration =
|
|
150
|
-
const maxIter =
|
|
147
|
+
// Priority 1: Ralph Loop (explicit persistence mode)
|
|
148
|
+
if (ralph.state?.active) {
|
|
149
|
+
const iteration = ralph.state.iteration || 1;
|
|
150
|
+
const maxIter = ralph.state.max_iterations || 100;
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const maxAttempts = verificationState.max_verification_attempts || 3;
|
|
152
|
+
if (iteration < maxIter) {
|
|
153
|
+
ralph.state.iteration = iteration + 1;
|
|
154
|
+
writeJsonFile(ralph.path, ralph.state);
|
|
156
155
|
|
|
157
156
|
console.log(JSON.stringify({
|
|
158
|
-
|
|
159
|
-
reason:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
**Original Task:**
|
|
166
|
-
${verificationState.original_task || ralphState.prompt || 'No task specified'}
|
|
167
|
-
|
|
168
|
-
**Completion Claim:**
|
|
169
|
-
${verificationState.completion_claim || 'Task marked complete'}
|
|
170
|
-
|
|
171
|
-
${verificationState.oracle_feedback ? `**Previous Oracle Feedback (rejected):**
|
|
172
|
-
${verificationState.oracle_feedback}
|
|
173
|
-
` : ''}
|
|
174
|
-
|
|
175
|
-
## MANDATORY VERIFICATION STEPS
|
|
157
|
+
continue: false,
|
|
158
|
+
reason: `[RALPH LOOP - ITERATION ${iteration + 1}/${maxIter}] Work is NOT done. Continue. When complete, output: <promise>${ralph.state.completion_promise || 'DONE'}</promise>\n${ralph.state.prompt ? `Task: ${ralph.state.prompt}` : ''}`
|
|
159
|
+
}));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
176
163
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
164
|
+
// Priority 2: Autopilot (high-level orchestration)
|
|
165
|
+
if (autopilot.state?.active) {
|
|
166
|
+
const phase = autopilot.state.phase || 'unknown';
|
|
167
|
+
if (phase !== 'complete') {
|
|
168
|
+
const newCount = (autopilot.state.reinforcement_count || 0) + 1;
|
|
169
|
+
if (newCount <= 20) {
|
|
170
|
+
autopilot.state.reinforcement_count = newCount;
|
|
171
|
+
writeJsonFile(autopilot.path, autopilot.state);
|
|
172
|
+
|
|
173
|
+
console.log(JSON.stringify({
|
|
174
|
+
continue: false,
|
|
175
|
+
reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working.`
|
|
176
|
+
}));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
// Priority 3: Ultrapilot (parallel autopilot)
|
|
183
|
+
if (ultrapilot.state?.active) {
|
|
184
|
+
const workers = ultrapilot.state.workers || [];
|
|
185
|
+
const incomplete = workers.filter(w => w.status !== 'complete' && w.status !== 'failed').length;
|
|
186
|
+
if (incomplete > 0) {
|
|
187
|
+
const newCount = (ultrapilot.state.reinforcement_count || 0) + 1;
|
|
188
|
+
if (newCount <= 20) {
|
|
189
|
+
ultrapilot.state.reinforcement_count = newCount;
|
|
190
|
+
writeJsonFile(ultrapilot.path, ultrapilot.state);
|
|
191
|
+
|
|
192
|
+
console.log(JSON.stringify({
|
|
193
|
+
continue: false,
|
|
194
|
+
reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue.`
|
|
195
|
+
}));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
188
200
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
// Priority 4: Swarm (coordinated agents with SQLite)
|
|
202
|
+
if (swarmMarker && swarmSummary?.active) {
|
|
203
|
+
const pending = (swarmSummary.tasks_pending || 0) + (swarmSummary.tasks_claimed || 0);
|
|
204
|
+
if (pending > 0) {
|
|
205
|
+
const newCount = (swarmSummary.reinforcement_count || 0) + 1;
|
|
206
|
+
if (newCount <= 15) {
|
|
207
|
+
swarmSummary.reinforcement_count = newCount;
|
|
208
|
+
writeJsonFile(join(stateDir, 'swarm-summary.json'), swarmSummary);
|
|
209
|
+
|
|
210
|
+
console.log(JSON.stringify({
|
|
211
|
+
continue: false,
|
|
212
|
+
reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working.`
|
|
213
|
+
}));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
192
218
|
|
|
193
|
-
|
|
219
|
+
// Priority 5: Pipeline (sequential stages)
|
|
220
|
+
if (pipeline.state?.active) {
|
|
221
|
+
const currentStage = pipeline.state.current_stage || 0;
|
|
222
|
+
const totalStages = pipeline.state.stages?.length || 0;
|
|
223
|
+
if (currentStage < totalStages) {
|
|
224
|
+
const newCount = (pipeline.state.reinforcement_count || 0) + 1;
|
|
225
|
+
if (newCount <= 15) {
|
|
226
|
+
pipeline.state.reinforcement_count = newCount;
|
|
227
|
+
writeJsonFile(pipeline.path, pipeline.state);
|
|
228
|
+
|
|
229
|
+
console.log(JSON.stringify({
|
|
230
|
+
continue: false,
|
|
231
|
+
reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue.`
|
|
232
|
+
}));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
194
237
|
|
|
195
|
-
|
|
238
|
+
// Priority 6: UltraQA (QA cycling)
|
|
239
|
+
if (ultraqa.state?.active) {
|
|
240
|
+
const cycle = ultraqa.state.cycle || 1;
|
|
241
|
+
const maxCycles = ultraqa.state.max_cycles || 10;
|
|
242
|
+
if (cycle < maxCycles && !ultraqa.state.all_passing) {
|
|
243
|
+
ultraqa.state.cycle = cycle + 1;
|
|
244
|
+
writeJsonFile(ultraqa.path, ultraqa.state);
|
|
196
245
|
|
|
197
|
-
|
|
198
|
-
|
|
246
|
+
console.log(JSON.stringify({
|
|
247
|
+
continue: false,
|
|
248
|
+
reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing.`
|
|
199
249
|
}));
|
|
200
250
|
return;
|
|
201
251
|
}
|
|
252
|
+
}
|
|
202
253
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
254
|
+
// Priority 7: Ultrawork with incomplete todos/tasks
|
|
255
|
+
if (ultrawork.state?.active && totalIncomplete > 0) {
|
|
256
|
+
const newCount = (ultrawork.state.reinforcement_count || 0) + 1;
|
|
257
|
+
const maxReinforcements = ultrawork.state.max_reinforcements || 15;
|
|
207
258
|
|
|
259
|
+
if (newCount > maxReinforcements) {
|
|
208
260
|
console.log(JSON.stringify({
|
|
209
|
-
|
|
210
|
-
reason:
|
|
211
|
-
|
|
212
|
-
[RALPH LOOP - ITERATION ${newIter}/${maxIter}]
|
|
213
|
-
|
|
214
|
-
Your previous attempt did not output the completion promise. The work is NOT done yet.
|
|
215
|
-
|
|
216
|
-
CRITICAL INSTRUCTIONS:
|
|
217
|
-
1. Review your progress and the original task
|
|
218
|
-
2. Check your todo list - are ALL items marked complete?
|
|
219
|
-
3. Continue from where you left off
|
|
220
|
-
4. When FULLY complete, output: <promise>${ralphState.completion_promise || 'TASK_COMPLETE'}</promise>
|
|
221
|
-
5. Do NOT stop until the task is truly done
|
|
222
|
-
|
|
223
|
-
${ralphState.prompt ? `Original task: ${ralphState.prompt}` : ''}
|
|
224
|
-
|
|
225
|
-
</ralph-loop-continuation>
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
`
|
|
261
|
+
continue: true,
|
|
262
|
+
reason: `[ULTRAWORK ESCAPE] Max reinforcements reached. Allowing stop.`
|
|
229
263
|
}));
|
|
230
264
|
return;
|
|
231
265
|
}
|
|
232
|
-
}
|
|
233
266
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
ultraworkState.reinforcement_count = newCount;
|
|
238
|
-
ultraworkState.last_checked_at = new Date().toISOString();
|
|
239
|
-
|
|
240
|
-
writeJsonFile(join(directory, '.omc', 'ultrawork-state.json'), ultraworkState);
|
|
267
|
+
ultrawork.state.reinforcement_count = newCount;
|
|
268
|
+
ultrawork.state.last_checked_at = new Date().toISOString();
|
|
269
|
+
writeJsonFile(ultrawork.path, ultrawork.state);
|
|
241
270
|
|
|
271
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
242
272
|
console.log(JSON.stringify({
|
|
243
|
-
|
|
244
|
-
reason:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
Your ultrawork session is NOT complete. ${totalIncomplete} incomplete items remain.
|
|
249
|
-
|
|
250
|
-
REMEMBER THE ULTRAWORK RULES:
|
|
251
|
-
- **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
|
|
252
|
-
- **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)
|
|
253
|
-
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each
|
|
254
|
-
- **VERIFY**: Check ALL requirements met before done
|
|
255
|
-
- **NO Premature Stopping**: ALL TODOs must be complete
|
|
273
|
+
continue: false,
|
|
274
|
+
reason: `[ULTRAWORK #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.\n${ultrawork.state.original_prompt ? `Task: ${ultrawork.state.original_prompt}` : ''}`
|
|
275
|
+
}));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
256
278
|
|
|
257
|
-
|
|
279
|
+
// Priority 8: Ecomode with incomplete todos/tasks
|
|
280
|
+
if (ecomode.state?.active && totalIncomplete > 0) {
|
|
281
|
+
const newCount = (ecomode.state.reinforcement_count || 0) + 1;
|
|
282
|
+
const maxReinforcements = ecomode.state.max_reinforcements || 15;
|
|
258
283
|
|
|
259
|
-
|
|
284
|
+
if (newCount > maxReinforcements) {
|
|
285
|
+
console.log(JSON.stringify({
|
|
286
|
+
continue: true,
|
|
287
|
+
reason: `[ECOMODE ESCAPE] Max reinforcements reached. Allowing stop.`
|
|
288
|
+
}));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
260
291
|
|
|
261
|
-
|
|
292
|
+
ecomode.state.reinforcement_count = newCount;
|
|
293
|
+
writeJsonFile(ecomode.path, ecomode.state);
|
|
262
294
|
|
|
263
|
-
|
|
264
|
-
|
|
295
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
296
|
+
console.log(JSON.stringify({
|
|
297
|
+
continue: false,
|
|
298
|
+
reason: `[ECOMODE #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
265
299
|
}));
|
|
266
300
|
return;
|
|
267
301
|
}
|
|
268
302
|
|
|
269
|
-
// Priority
|
|
303
|
+
// Priority 9: Generic Task/Todo continuation (no specific mode)
|
|
270
304
|
if (totalIncomplete > 0) {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
[SYSTEM REMINDER - CONTINUATION]
|
|
277
|
-
|
|
278
|
-
Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working on the next pending item.
|
|
279
|
-
|
|
280
|
-
- Proceed without asking for permission
|
|
281
|
-
- Mark each item complete when finished
|
|
282
|
-
- Do not stop until all items are done
|
|
305
|
+
const contFile = join(stateDir, 'continuation-count.json');
|
|
306
|
+
let contState = readJsonFile(contFile) || { count: 0 };
|
|
307
|
+
contState.count = (contState.count || 0) + 1;
|
|
308
|
+
writeJsonFile(contFile, contState);
|
|
283
309
|
|
|
284
|
-
|
|
310
|
+
if (contState.count > 15) {
|
|
311
|
+
console.log(JSON.stringify({
|
|
312
|
+
continue: true,
|
|
313
|
+
reason: `[CONTINUATION ESCAPE] Max continuations reached. Allowing stop.`
|
|
314
|
+
}));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
285
317
|
|
|
286
|
-
|
|
287
|
-
|
|
318
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
319
|
+
console.log(JSON.stringify({
|
|
320
|
+
continue: false,
|
|
321
|
+
reason: `[CONTINUATION ${contState.count}/15] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
288
322
|
}));
|
|
289
323
|
return;
|
|
290
324
|
}
|
|
@@ -292,6 +326,8 @@ Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working o
|
|
|
292
326
|
// No blocking needed
|
|
293
327
|
console.log(JSON.stringify({ continue: true }));
|
|
294
328
|
} catch (error) {
|
|
329
|
+
// On any error, allow stop rather than blocking forever
|
|
330
|
+
console.error(`[persistent-mode] Error: ${error.message}`);
|
|
295
331
|
console.log(JSON.stringify({ continue: true }));
|
|
296
332
|
}
|
|
297
333
|
}
|