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.
Files changed (46) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/features/continuation-enforcement.js +1 -1
  4. package/dist/features/continuation-enforcement.js.map +1 -1
  5. package/dist/hooks/bridge.d.ts.map +1 -1
  6. package/dist/hooks/bridge.js +62 -78
  7. package/dist/hooks/bridge.js.map +1 -1
  8. package/dist/hooks/keyword-detector/__tests__/index.test.js +89 -1
  9. package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -1
  10. package/dist/hooks/keyword-detector/index.d.ts +5 -1
  11. package/dist/hooks/keyword-detector/index.d.ts.map +1 -1
  12. package/dist/hooks/keyword-detector/index.js +32 -11
  13. package/dist/hooks/keyword-detector/index.js.map +1 -1
  14. package/dist/hooks/persistent-mode/index.d.ts +2 -1
  15. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  16. package/dist/hooks/persistent-mode/index.js +7 -17
  17. package/dist/hooks/persistent-mode/index.js.map +1 -1
  18. package/dist/hooks/todo-continuation/index.d.ts.map +1 -1
  19. package/dist/hooks/todo-continuation/index.js +2 -15
  20. package/dist/hooks/todo-continuation/index.js.map +1 -1
  21. package/dist/installer/hooks.d.ts +8 -111
  22. package/dist/installer/hooks.d.ts.map +1 -1
  23. package/dist/installer/hooks.js +11 -124
  24. package/dist/installer/hooks.js.map +1 -1
  25. package/dist/installer/index.d.ts +2 -10
  26. package/dist/installer/index.d.ts.map +1 -1
  27. package/dist/installer/index.js +10 -23
  28. package/dist/installer/index.js.map +1 -1
  29. package/package.json +1 -1
  30. package/scripts/keyword-detector.mjs +140 -59
  31. package/scripts/persistent-mode.mjs +26 -53
  32. package/templates/hooks/keyword-detector.mjs +280 -118
  33. package/templates/hooks/persistent-mode.mjs +26 -53
  34. package/templates/hooks/stop-continuation.mjs +6 -158
  35. package/hooks/keyword-detector.sh +0 -102
  36. package/hooks/persistent-mode.sh +0 -172
  37. package/hooks/session-start.sh +0 -62
  38. package/hooks/stop-continuation.sh +0 -40
  39. package/scripts/claude-sisyphus.sh +0 -9
  40. package/scripts/install.sh +0 -1673
  41. package/scripts/keyword-detector.sh +0 -71
  42. package/scripts/persistent-mode.sh +0 -311
  43. package/scripts/post-tool-verifier.sh +0 -196
  44. package/scripts/pre-tool-enforcer.sh +0 -76
  45. package/scripts/sisyphus-aliases.sh +0 -18
  46. 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
- const ULTRAWORK_MESSAGE = `<ultrawork-mode>
7
-
8
- **MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable.
9
-
10
- [CODE RED] Maximum precision required. Ultrathink before acting.
11
-
12
- YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
13
- TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
14
-
15
- ## AGENT UTILIZATION PRINCIPLES
16
- - **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS
17
- - **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS
18
- - **Planning & Strategy**: NEVER plan yourself - spawn planning agent
19
- - **High-IQ Reasoning**: Use oracle for architecture decisions
20
- - **Frontend/UI Tasks**: Delegate to frontend-engineer
21
-
22
- ## EXECUTION RULES
23
- - **TODO**: Track EVERY step. Mark complete IMMEDIATELY.
24
- - **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially.
25
- - **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent).
26
- - **VERIFY**: Check ALL requirements met before done.
27
- - **DELEGATE**: Orchestrate specialized agents.
28
-
29
- ## ZERO TOLERANCE
30
- - NO Scope Reduction - deliver FULL implementation
31
- - NO Partial Completion - finish 100%
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
- import { writeFileSync, mkdirSync, existsSync } from 'fs';
124
- import { join } from 'path';
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
- const localDir = join(directory, '.omc');
137
- if (!existsSync(localDir)) { try { mkdirSync(localDir, { recursive: true }); } catch {} }
138
- try { writeFileSync(join(localDir, 'ultrawork-state.json'), JSON.stringify(state, null, 2)); } catch {}
139
- const globalDir = join(homedir(), '.claude');
140
- if (!existsSync(globalDir)) { try { mkdirSync(globalDir, { recursive: true }); } catch {} }
141
- try { writeFileSync(join(globalDir, 'ultrawork-state.json'), JSON.stringify(state, null, 2)); } catch {}
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
- // Check for ultrawork keywords (highest priority)
166
- if (/\b(ultrawork|ulw|uw)\b/.test(cleanPrompt)) {
167
- activateUltraworkState(directory, prompt);
168
- console.log(JSON.stringify({
169
- continue: true,
170
- hookSpecificOutput: {
171
- hookEventName: 'UserPromptSubmit',
172
- additionalContext: ULTRAWORK_MESSAGE
173
- }
174
- }));
175
- return;
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
- // Check for ultrathink/think keywords
179
- if (/\b(ultrathink|think)\b/.test(cleanPrompt)) {
180
- console.log(JSON.stringify({
181
- continue: true,
182
- hookSpecificOutput: {
183
- hookEventName: 'UserPromptSubmit',
184
- additionalContext: ULTRATHINK_MESSAGE
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
- // Check for search keywords
191
- if (/\b(search|find|locate|lookup|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all/.test(cleanPrompt)) {
192
- console.log(JSON.stringify({
193
- continue: true,
194
- hookSpecificOutput: {
195
- hookEventName: 'UserPromptSubmit',
196
- additionalContext: SEARCH_MESSAGE
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
- // Check for analyze keywords
203
- if (/\b(analyze|analyse|investigate|examine|research|study|deep.?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to/.test(cleanPrompt)) {
204
- console.log(JSON.stringify({
205
- continue: true,
206
- hookSpecificOutput: {
207
- hookEventName: 'UserPromptSubmit',
208
- additionalContext: ANALYZE_MESSAGE
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
- // No keywords detected
215
- console.log(JSON.stringify({ continue: true }));
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(todosDir, projectDir) {
86
+ function countIncompleteTodos(sessionId, projectDir) {
87
87
  let count = 0;
88
88
 
89
- if (existsSync(todosDir)) {
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 files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
92
- for (const file of files) {
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 : []);
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(todosDir, directory);
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: false,
221
- 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}` : ''}`
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: false,
238
- reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working.`
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: false,
257
- reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue.`
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: false,
275
- reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working.`
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: false,
294
- reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue.`
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: false,
311
- reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing.`
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: false,
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: false,
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
  }