oh-my-claude-sisyphus 3.8.10 → 3.8.12
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/features/continuation-enforcement.js +1 -1
- package/dist/features/continuation-enforcement.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +62 -78
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/keyword-detector/__tests__/index.test.js +89 -1
- package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -1
- package/dist/hooks/keyword-detector/index.d.ts +5 -1
- package/dist/hooks/keyword-detector/index.d.ts.map +1 -1
- package/dist/hooks/keyword-detector/index.js +32 -11
- package/dist/hooks/keyword-detector/index.js.map +1 -1
- package/dist/hooks/persistent-mode/index.d.ts +2 -1
- package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
- package/dist/hooks/persistent-mode/index.js +7 -17
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/todo-continuation/index.d.ts.map +1 -1
- package/dist/hooks/todo-continuation/index.js +2 -15
- package/dist/hooks/todo-continuation/index.js.map +1 -1
- package/dist/installer/hooks.d.ts +8 -111
- package/dist/installer/hooks.d.ts.map +1 -1
- package/dist/installer/hooks.js +11 -124
- package/dist/installer/hooks.js.map +1 -1
- package/dist/installer/index.d.ts +2 -10
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +10 -23
- package/dist/installer/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/keyword-detector.mjs +140 -59
- package/scripts/persistent-mode.mjs +26 -53
- package/templates/hooks/keyword-detector.mjs +280 -118
- package/templates/hooks/persistent-mode.mjs +26 -53
- package/templates/hooks/stop-continuation.mjs +6 -158
- package/hooks/keyword-detector.sh +0 -102
- package/hooks/persistent-mode.sh +0 -172
- package/hooks/session-start.sh +0 -62
- package/hooks/stop-continuation.sh +0 -40
- package/scripts/claude-sisyphus.sh +0 -9
- package/scripts/install.sh +0 -1673
- package/scripts/keyword-detector.sh +0 -71
- package/scripts/persistent-mode.sh +0 -311
- package/scripts/post-tool-verifier.sh +0 -196
- package/scripts/pre-tool-enforcer.sh +0 -76
- package/scripts/sisyphus-aliases.sh +0 -18
- package/scripts/stop-continuation.sh +0 -31
|
@@ -1,43 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// OMC Keyword Detector Hook (Node.js)
|
|
3
|
-
// Detects ultrawork/ultrathink/search/analyze keywords and injects enhanced mode messages
|
|
4
|
-
// Cross-platform: Windows, macOS, Linux
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- NO Premature Stopping - ALL TODOs must be complete
|
|
33
|
-
- NO TEST DELETION - fix code, not tests
|
|
34
|
-
|
|
35
|
-
THE USER ASKED FOR X. DELIVER EXACTLY X.
|
|
36
|
-
|
|
37
|
-
</ultrawork-mode>
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
`;
|
|
3
|
+
/**
|
|
4
|
+
* OMC Keyword Detector Hook (Node.js)
|
|
5
|
+
* Detects magic keywords and invokes skill tools
|
|
6
|
+
* Cross-platform: Windows, macOS, Linux
|
|
7
|
+
*
|
|
8
|
+
* Supported keywords (in priority order):
|
|
9
|
+
* 1. cancelomc/stopomc: Stop active modes
|
|
10
|
+
* 2. ralph: Persistence mode until task completion
|
|
11
|
+
* 3. autopilot: Full autonomous execution
|
|
12
|
+
* 4. ultrapilot: Parallel autopilot
|
|
13
|
+
* 5. ultrawork/ulw: Maximum parallel execution
|
|
14
|
+
* 6. ecomode/eco: Token-efficient execution
|
|
15
|
+
* 7. swarm: N coordinated agents
|
|
16
|
+
* 8. pipeline: Sequential agent chaining
|
|
17
|
+
* 9. ralplan: Iterative planning with consensus
|
|
18
|
+
* 10. plan: Planning interview mode
|
|
19
|
+
* 11. tdd: Test-driven development
|
|
20
|
+
* 12. research: Research orchestration
|
|
21
|
+
* 13. ultrathink/think: Extended reasoning
|
|
22
|
+
* 14. deepsearch: Codebase search (restricted patterns)
|
|
23
|
+
* 15. analyze: Analysis mode (restricted patterns)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
|
|
27
|
+
import { join } from 'path';
|
|
28
|
+
import { homedir } from 'os';
|
|
41
29
|
|
|
42
30
|
const ULTRATHINK_MESSAGE = `<think-mode>
|
|
43
31
|
|
|
@@ -56,34 +44,6 @@ Use your extended thinking capabilities to provide the most thorough and well-re
|
|
|
56
44
|
---
|
|
57
45
|
`;
|
|
58
46
|
|
|
59
|
-
const SEARCH_MESSAGE = `<search-mode>
|
|
60
|
-
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
|
|
61
|
-
- explore agents (codebase patterns, file structures)
|
|
62
|
-
- librarian agents (remote repos, official docs, GitHub examples)
|
|
63
|
-
Plus direct tools: Grep, Glob
|
|
64
|
-
NEVER stop at first result - be exhaustive.
|
|
65
|
-
</search-mode>
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
`;
|
|
69
|
-
|
|
70
|
-
const ANALYZE_MESSAGE = `<analyze-mode>
|
|
71
|
-
ANALYSIS MODE. Gather context before diving deep:
|
|
72
|
-
|
|
73
|
-
CONTEXT GATHERING (parallel):
|
|
74
|
-
- 1-2 explore agents (codebase patterns, implementations)
|
|
75
|
-
- 1-2 librarian agents (if external library involved)
|
|
76
|
-
- Direct tools: Grep, Glob, LSP for targeted searches
|
|
77
|
-
|
|
78
|
-
IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
|
|
79
|
-
- Consult oracle agent for strategic guidance
|
|
80
|
-
|
|
81
|
-
SYNTHESIZE findings before proceeding.
|
|
82
|
-
</analyze-mode>
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
47
|
// Read all stdin
|
|
88
48
|
async function readStdin() {
|
|
89
49
|
const chunks = [];
|
|
@@ -120,12 +80,8 @@ function removeCodeBlocks(text) {
|
|
|
120
80
|
.replace(/`[^`]+`/g, '');
|
|
121
81
|
}
|
|
122
82
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
import { homedir } from 'os';
|
|
126
|
-
|
|
127
|
-
// Create ultrawork state file
|
|
128
|
-
function activateUltraworkState(directory, prompt) {
|
|
83
|
+
// Create state file for a mode
|
|
84
|
+
function activateState(directory, prompt, stateName) {
|
|
129
85
|
const state = {
|
|
130
86
|
active: true,
|
|
131
87
|
started_at: new Date().toISOString(),
|
|
@@ -133,12 +89,121 @@ function activateUltraworkState(directory, prompt) {
|
|
|
133
89
|
reinforcement_count: 0,
|
|
134
90
|
last_checked_at: new Date().toISOString()
|
|
135
91
|
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
92
|
+
|
|
93
|
+
// Write to local .omc/state directory
|
|
94
|
+
const localDir = join(directory, '.omc', 'state');
|
|
95
|
+
if (!existsSync(localDir)) {
|
|
96
|
+
try { mkdirSync(localDir, { recursive: true }); } catch {}
|
|
97
|
+
}
|
|
98
|
+
try { writeFileSync(join(localDir, `${stateName}-state.json`), JSON.stringify(state, null, 2)); } catch {}
|
|
99
|
+
|
|
100
|
+
// Write to global .omc/state directory
|
|
101
|
+
const globalDir = join(homedir(), '.omc', 'state');
|
|
102
|
+
if (!existsSync(globalDir)) {
|
|
103
|
+
try { mkdirSync(globalDir, { recursive: true }); } catch {}
|
|
104
|
+
}
|
|
105
|
+
try { writeFileSync(join(globalDir, `${stateName}-state.json`), JSON.stringify(state, null, 2)); } catch {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Clear state files for cancel operation
|
|
110
|
+
*/
|
|
111
|
+
function clearStateFiles(directory, modeNames) {
|
|
112
|
+
for (const name of modeNames) {
|
|
113
|
+
const localPath = join(directory, '.omc', 'state', `${name}-state.json`);
|
|
114
|
+
const globalPath = join(homedir(), '.omc', 'state', `${name}-state.json`);
|
|
115
|
+
try { if (existsSync(localPath)) unlinkSync(localPath); } catch {}
|
|
116
|
+
try { if (existsSync(globalPath)) unlinkSync(globalPath); } catch {}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a skill invocation message that tells Claude to use the Skill tool
|
|
122
|
+
*/
|
|
123
|
+
function createSkillInvocation(skillName, originalPrompt, args = '') {
|
|
124
|
+
const argsSection = args ? `\nArguments: ${args}` : '';
|
|
125
|
+
return `[MAGIC KEYWORD: ${skillName.toUpperCase()}]
|
|
126
|
+
|
|
127
|
+
You MUST invoke the skill using the Skill tool:
|
|
128
|
+
|
|
129
|
+
Skill: oh-my-claudecode:${skillName}${argsSection}
|
|
130
|
+
|
|
131
|
+
User request:
|
|
132
|
+
${originalPrompt}
|
|
133
|
+
|
|
134
|
+
IMPORTANT: Invoke the skill IMMEDIATELY. Do not proceed without loading the skill instructions.`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create multi-skill invocation message for combined keywords
|
|
139
|
+
*/
|
|
140
|
+
function createMultiSkillInvocation(skills, originalPrompt) {
|
|
141
|
+
if (skills.length === 0) return '';
|
|
142
|
+
if (skills.length === 1) {
|
|
143
|
+
return createSkillInvocation(skills[0].name, originalPrompt, skills[0].args);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const skillBlocks = skills.map((s, i) => {
|
|
147
|
+
const argsSection = s.args ? `\nArguments: ${s.args}` : '';
|
|
148
|
+
return `### Skill ${i + 1}: ${s.name.toUpperCase()}
|
|
149
|
+
Skill: oh-my-claudecode:${s.name}${argsSection}`;
|
|
150
|
+
}).join('\n\n');
|
|
151
|
+
|
|
152
|
+
return `[MAGIC KEYWORDS DETECTED: ${skills.map(s => s.name.toUpperCase()).join(', ')}]
|
|
153
|
+
|
|
154
|
+
You MUST invoke ALL of the following skills using the Skill tool, in order:
|
|
155
|
+
|
|
156
|
+
${skillBlocks}
|
|
157
|
+
|
|
158
|
+
User request:
|
|
159
|
+
${originalPrompt}
|
|
160
|
+
|
|
161
|
+
IMPORTANT: Invoke ALL skills listed above. Start with the first skill IMMEDIATELY. After it completes, invoke the next skill in order. Do not skip any skill.`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Resolve conflicts between detected keywords
|
|
166
|
+
*/
|
|
167
|
+
function resolveConflicts(matches) {
|
|
168
|
+
const names = matches.map(m => m.name);
|
|
169
|
+
|
|
170
|
+
// Cancel is exclusive
|
|
171
|
+
if (names.includes('cancel')) {
|
|
172
|
+
return [matches.find(m => m.name === 'cancel')];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let resolved = [...matches];
|
|
176
|
+
|
|
177
|
+
// Ecomode beats ultrawork
|
|
178
|
+
if (names.includes('ecomode') && names.includes('ultrawork')) {
|
|
179
|
+
resolved = resolved.filter(m => m.name !== 'ultrawork');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Ultrapilot beats autopilot
|
|
183
|
+
if (names.includes('ultrapilot') && names.includes('autopilot')) {
|
|
184
|
+
resolved = resolved.filter(m => m.name !== 'autopilot');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Sort by priority order
|
|
188
|
+
const priorityOrder = ['cancel','ralph','autopilot','ultrapilot','ultrawork','ecomode',
|
|
189
|
+
'swarm','pipeline','ralplan','plan','tdd','research','ultrathink','deepsearch','analyze'];
|
|
190
|
+
resolved.sort((a, b) => priorityOrder.indexOf(a.name) - priorityOrder.indexOf(b.name));
|
|
191
|
+
|
|
192
|
+
return resolved;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create proper hook output with additionalContext (Claude Code hooks API)
|
|
197
|
+
* The 'message' field is NOT a valid hook output - use hookSpecificOutput.additionalContext
|
|
198
|
+
*/
|
|
199
|
+
function createHookOutput(additionalContext) {
|
|
200
|
+
return {
|
|
201
|
+
continue: true,
|
|
202
|
+
hookSpecificOutput: {
|
|
203
|
+
hookEventName: 'UserPromptSubmit',
|
|
204
|
+
additionalContext
|
|
205
|
+
}
|
|
206
|
+
};
|
|
142
207
|
}
|
|
143
208
|
|
|
144
209
|
// Main
|
|
@@ -162,57 +227,154 @@ async function main() {
|
|
|
162
227
|
|
|
163
228
|
const cleanPrompt = removeCodeBlocks(prompt).toLowerCase();
|
|
164
229
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
230
|
+
// Collect all matching keywords
|
|
231
|
+
const matches = [];
|
|
232
|
+
|
|
233
|
+
// Cancel keywords
|
|
234
|
+
if (/\b(cancelomc|stopomc)\b/i.test(cleanPrompt)) {
|
|
235
|
+
matches.push({ name: 'cancel', args: '' });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Ralph keywords
|
|
239
|
+
if (/\b(ralph|don't stop|must complete|until done)\b/i.test(cleanPrompt)) {
|
|
240
|
+
matches.push({ name: 'ralph', args: '' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Autopilot keywords
|
|
244
|
+
if (/\b(autopilot|auto pilot|auto-pilot|autonomous|full auto|fullsend)\b/i.test(cleanPrompt) ||
|
|
245
|
+
/\bbuild\s+me\s+/i.test(cleanPrompt) ||
|
|
246
|
+
/\bcreate\s+me\s+/i.test(cleanPrompt) ||
|
|
247
|
+
/\bmake\s+me\s+/i.test(cleanPrompt) ||
|
|
248
|
+
/\bi\s+want\s+a\s+/i.test(cleanPrompt) ||
|
|
249
|
+
/\bi\s+want\s+an\s+/i.test(cleanPrompt) ||
|
|
250
|
+
/\bhandle\s+it\s+all\b/i.test(cleanPrompt) ||
|
|
251
|
+
/\bend\s+to\s+end\b/i.test(cleanPrompt) ||
|
|
252
|
+
/\be2e\s+this\b/i.test(cleanPrompt)) {
|
|
253
|
+
matches.push({ name: 'autopilot', args: '' });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Ultrapilot keywords
|
|
257
|
+
if (/\b(ultrapilot|ultra-pilot)\b/i.test(cleanPrompt) ||
|
|
258
|
+
/\bparallel\s+build\b/i.test(cleanPrompt) ||
|
|
259
|
+
/\bswarm\s+build\b/i.test(cleanPrompt)) {
|
|
260
|
+
matches.push({ name: 'ultrapilot', args: '' });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Ultrawork keywords
|
|
264
|
+
if (/\b(ultrawork|ulw|uw)\b/i.test(cleanPrompt)) {
|
|
265
|
+
matches.push({ name: 'ultrawork', args: '' });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Ecomode keywords
|
|
269
|
+
if (/\b(eco|ecomode|eco-mode|efficient|save-tokens|budget)\b/i.test(cleanPrompt)) {
|
|
270
|
+
matches.push({ name: 'ecomode', args: '' });
|
|
176
271
|
}
|
|
177
272
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
273
|
+
// Swarm - parse N from "swarm N agents"
|
|
274
|
+
const swarmMatch = cleanPrompt.match(/\bswarm\s+(\d+)\s+agents?\b/i);
|
|
275
|
+
if (swarmMatch || /\bcoordinated\s+agents\b/i.test(cleanPrompt)) {
|
|
276
|
+
const agentCount = swarmMatch ? swarmMatch[1] : '3';
|
|
277
|
+
matches.push({ name: 'swarm', args: agentCount });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Pipeline keywords
|
|
281
|
+
if (/\b(pipeline)\b/i.test(cleanPrompt) || /\bchain\s+agents\b/i.test(cleanPrompt)) {
|
|
282
|
+
matches.push({ name: 'pipeline', args: '' });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Ralplan keyword
|
|
286
|
+
if (/\b(ralplan)\b/i.test(cleanPrompt)) {
|
|
287
|
+
matches.push({ name: 'ralplan', args: '' });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Plan keywords
|
|
291
|
+
if (/\b(plan this|plan the)\b/i.test(cleanPrompt)) {
|
|
292
|
+
matches.push({ name: 'plan', args: '' });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// TDD keywords
|
|
296
|
+
if (/\b(tdd)\b/i.test(cleanPrompt) ||
|
|
297
|
+
/\btest\s+first\b/i.test(cleanPrompt) ||
|
|
298
|
+
/\bred\s+green\b/i.test(cleanPrompt)) {
|
|
299
|
+
matches.push({ name: 'tdd', args: '' });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Research keywords
|
|
303
|
+
if (/\b(research)\b/i.test(cleanPrompt) ||
|
|
304
|
+
/\banalyze\s+data\b/i.test(cleanPrompt) ||
|
|
305
|
+
/\bstatistics\b/i.test(cleanPrompt)) {
|
|
306
|
+
matches.push({ name: 'research', args: '' });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Ultrathink keywords
|
|
310
|
+
if (/\b(ultrathink|think hard|think deeply)\b/i.test(cleanPrompt)) {
|
|
311
|
+
matches.push({ name: 'ultrathink', args: '' });
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Deepsearch keywords
|
|
315
|
+
if (/\b(deepsearch)\b/i.test(cleanPrompt) ||
|
|
316
|
+
/\bsearch\s+(the\s+)?(codebase|code|files?|project)\b/i.test(cleanPrompt) ||
|
|
317
|
+
/\bfind\s+(in\s+)?(codebase|code|all\s+files?)\b/i.test(cleanPrompt)) {
|
|
318
|
+
matches.push({ name: 'deepsearch', args: '' });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Analyze keywords
|
|
322
|
+
if (/\b(deep\s*analyze)\b/i.test(cleanPrompt) ||
|
|
323
|
+
/\binvestigate\s+(the|this|why)\b/i.test(cleanPrompt) ||
|
|
324
|
+
/\bdebug\s+(the|this|why)\b/i.test(cleanPrompt)) {
|
|
325
|
+
matches.push({ name: 'analyze', args: '' });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// No matches - pass through
|
|
329
|
+
if (matches.length === 0) {
|
|
330
|
+
console.log(JSON.stringify({ continue: true }));
|
|
187
331
|
return;
|
|
188
332
|
}
|
|
189
333
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
}));
|
|
334
|
+
// Resolve conflicts
|
|
335
|
+
const resolved = resolveConflicts(matches);
|
|
336
|
+
|
|
337
|
+
// Handle cancel specially - clear states and emit
|
|
338
|
+
if (resolved.length > 0 && resolved[0].name === 'cancel') {
|
|
339
|
+
clearStateFiles(directory, ['ralph', 'autopilot', 'ultrapilot', 'ultrawork', 'ecomode', 'swarm', 'pipeline']);
|
|
340
|
+
console.log(JSON.stringify(createHookOutput(createSkillInvocation('cancel', prompt))));
|
|
199
341
|
return;
|
|
200
342
|
}
|
|
201
343
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
344
|
+
// Activate states for modes that need them
|
|
345
|
+
const stateModes = resolved.filter(m => ['ralph', 'autopilot', 'ultrapilot', 'ultrawork', 'ecomode'].includes(m.name));
|
|
346
|
+
for (const mode of stateModes) {
|
|
347
|
+
activateState(directory, prompt, mode.name);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Special: Ralph with ultrawork (only if ecomode NOT present)
|
|
351
|
+
const hasRalph = resolved.some(m => m.name === 'ralph');
|
|
352
|
+
const hasEcomode = resolved.some(m => m.name === 'ecomode');
|
|
353
|
+
const hasUltrawork = resolved.some(m => m.name === 'ultrawork');
|
|
354
|
+
if (hasRalph && !hasEcomode && !hasUltrawork) {
|
|
355
|
+
activateState(directory, prompt, 'ultrawork');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Handle ultrathink specially - prepend message instead of skill invocation
|
|
359
|
+
const ultrathinkIndex = resolved.findIndex(m => m.name === 'ultrathink');
|
|
360
|
+
if (ultrathinkIndex !== -1) {
|
|
361
|
+
// Remove ultrathink from skill list
|
|
362
|
+
resolved.splice(ultrathinkIndex, 1);
|
|
363
|
+
|
|
364
|
+
// If ultrathink was the only match, emit message
|
|
365
|
+
if (resolved.length === 0) {
|
|
366
|
+
console.log(JSON.stringify(createHookOutput(ULTRATHINK_MESSAGE)));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Otherwise, prepend ultrathink message to skill invocation
|
|
371
|
+
const skillMessage = createMultiSkillInvocation(resolved, prompt);
|
|
372
|
+
console.log(JSON.stringify(createHookOutput(ULTRATHINK_MESSAGE + skillMessage)));
|
|
211
373
|
return;
|
|
212
374
|
}
|
|
213
375
|
|
|
214
|
-
//
|
|
215
|
-
console.log(JSON.stringify(
|
|
376
|
+
// Emit skill invocation(s)
|
|
377
|
+
console.log(JSON.stringify(createHookOutput(createMultiSkillInvocation(resolved, prompt))));
|
|
216
378
|
} catch (error) {
|
|
217
379
|
// On any error, allow continuation
|
|
218
380
|
console.log(JSON.stringify({ continue: true }));
|
|
@@ -83,23 +83,20 @@ function countIncompleteTasks(sessionId) {
|
|
|
83
83
|
return count;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function countIncompleteTodos(
|
|
86
|
+
function countIncompleteTodos(sessionId, projectDir) {
|
|
87
87
|
let count = 0;
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// Session-specific todos only (no global scan)
|
|
90
|
+
if (sessionId && typeof sessionId === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) {
|
|
91
|
+
const sessionTodoPath = join(homedir(), '.claude', 'todos', `${sessionId}.json`);
|
|
90
92
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
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 : []);
|
|
97
|
-
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
98
|
-
} catch { /* skip */ }
|
|
99
|
-
}
|
|
93
|
+
const data = readJsonFile(sessionTodoPath);
|
|
94
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
95
|
+
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
100
96
|
} catch { /* skip */ }
|
|
101
97
|
}
|
|
102
98
|
|
|
99
|
+
// Project-local todos only
|
|
103
100
|
for (const path of [
|
|
104
101
|
join(projectDir, '.omc', 'todos.json'),
|
|
105
102
|
join(projectDir, '.claude', 'todos.json')
|
|
@@ -171,7 +168,6 @@ async function main() {
|
|
|
171
168
|
|
|
172
169
|
const directory = data.directory || process.cwd();
|
|
173
170
|
const sessionId = data.sessionId || data.session_id || '';
|
|
174
|
-
const todosDir = join(homedir(), '.claude', 'todos');
|
|
175
171
|
const stateDir = join(directory, '.omc', 'state');
|
|
176
172
|
const globalStateDir = join(homedir(), '.omc', 'state');
|
|
177
173
|
|
|
@@ -202,9 +198,9 @@ async function main() {
|
|
|
202
198
|
const swarmMarker = existsSync(join(stateDir, 'swarm-active.marker'));
|
|
203
199
|
const swarmSummary = readJsonFile(join(stateDir, 'swarm-summary.json'));
|
|
204
200
|
|
|
205
|
-
// Count incomplete items
|
|
201
|
+
// Count incomplete items (session-specific + project-local only)
|
|
206
202
|
const taskCount = countIncompleteTasks(sessionId);
|
|
207
|
-
const todoCount = countIncompleteTodos(
|
|
203
|
+
const todoCount = countIncompleteTodos(sessionId, directory);
|
|
208
204
|
const totalIncomplete = taskCount + todoCount;
|
|
209
205
|
|
|
210
206
|
// Priority 1: Ralph Loop (explicit persistence mode)
|
|
@@ -217,8 +213,8 @@ async function main() {
|
|
|
217
213
|
writeJsonFile(ralph.path, ralph.state);
|
|
218
214
|
|
|
219
215
|
console.log(JSON.stringify({
|
|
220
|
-
continue:
|
|
221
|
-
|
|
216
|
+
continue: true,
|
|
217
|
+
message: `[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}` : ''}`
|
|
222
218
|
}));
|
|
223
219
|
return;
|
|
224
220
|
}
|
|
@@ -234,8 +230,8 @@ async function main() {
|
|
|
234
230
|
writeJsonFile(autopilot.path, autopilot.state);
|
|
235
231
|
|
|
236
232
|
console.log(JSON.stringify({
|
|
237
|
-
continue:
|
|
238
|
-
|
|
233
|
+
continue: true,
|
|
234
|
+
message: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working.`
|
|
239
235
|
}));
|
|
240
236
|
return;
|
|
241
237
|
}
|
|
@@ -253,8 +249,8 @@ async function main() {
|
|
|
253
249
|
writeJsonFile(ultrapilot.path, ultrapilot.state);
|
|
254
250
|
|
|
255
251
|
console.log(JSON.stringify({
|
|
256
|
-
continue:
|
|
257
|
-
|
|
252
|
+
continue: true,
|
|
253
|
+
message: `[ULTRAPILOT] ${incomplete} workers still running. Continue.`
|
|
258
254
|
}));
|
|
259
255
|
return;
|
|
260
256
|
}
|
|
@@ -271,8 +267,8 @@ async function main() {
|
|
|
271
267
|
writeJsonFile(join(stateDir, 'swarm-summary.json'), swarmSummary);
|
|
272
268
|
|
|
273
269
|
console.log(JSON.stringify({
|
|
274
|
-
continue:
|
|
275
|
-
|
|
270
|
+
continue: true,
|
|
271
|
+
message: `[SWARM ACTIVE] ${pending} tasks remain. Continue working.`
|
|
276
272
|
}));
|
|
277
273
|
return;
|
|
278
274
|
}
|
|
@@ -290,8 +286,8 @@ async function main() {
|
|
|
290
286
|
writeJsonFile(pipeline.path, pipeline.state);
|
|
291
287
|
|
|
292
288
|
console.log(JSON.stringify({
|
|
293
|
-
continue:
|
|
294
|
-
|
|
289
|
+
continue: true,
|
|
290
|
+
message: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue.`
|
|
295
291
|
}));
|
|
296
292
|
return;
|
|
297
293
|
}
|
|
@@ -307,8 +303,8 @@ async function main() {
|
|
|
307
303
|
writeJsonFile(ultraqa.path, ultraqa.state);
|
|
308
304
|
|
|
309
305
|
console.log(JSON.stringify({
|
|
310
|
-
continue:
|
|
311
|
-
|
|
306
|
+
continue: true,
|
|
307
|
+
message: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing.`
|
|
312
308
|
}));
|
|
313
309
|
return;
|
|
314
310
|
}
|
|
@@ -342,8 +338,8 @@ async function main() {
|
|
|
342
338
|
}
|
|
343
339
|
|
|
344
340
|
console.log(JSON.stringify({
|
|
345
|
-
continue:
|
|
346
|
-
reason
|
|
341
|
+
continue: true,
|
|
342
|
+
message: reason
|
|
347
343
|
}));
|
|
348
344
|
return;
|
|
349
345
|
}
|
|
@@ -371,31 +367,8 @@ async function main() {
|
|
|
371
367
|
}
|
|
372
368
|
|
|
373
369
|
console.log(JSON.stringify({
|
|
374
|
-
continue:
|
|
375
|
-
reason
|
|
376
|
-
}));
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Priority 9: Generic Task/Todo continuation (no specific mode)
|
|
381
|
-
if (totalIncomplete > 0) {
|
|
382
|
-
const contFile = join(stateDir, 'continuation-count.json');
|
|
383
|
-
let contState = readJsonFile(contFile) || { count: 0 };
|
|
384
|
-
contState.count = (contState.count || 0) + 1;
|
|
385
|
-
writeJsonFile(contFile, contState);
|
|
386
|
-
|
|
387
|
-
if (contState.count > 15) {
|
|
388
|
-
console.log(JSON.stringify({
|
|
389
|
-
continue: true,
|
|
390
|
-
reason: `[CONTINUATION ESCAPE] Max continuations reached. Allowing stop.`
|
|
391
|
-
}));
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
396
|
-
console.log(JSON.stringify({
|
|
397
|
-
continue: false,
|
|
398
|
-
reason: `[CONTINUATION ${contState.count}/15] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
370
|
+
continue: true,
|
|
371
|
+
message: reason
|
|
399
372
|
}));
|
|
400
373
|
return;
|
|
401
374
|
}
|