oh-my-claude-sisyphus 3.7.15 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +9 -4
  4. package/agents/AGENTS.md +8 -8
  5. package/agents/architect.md +32 -0
  6. package/agents/build-fixer.md +35 -0
  7. package/agents/code-reviewer.md +51 -0
  8. package/agents/executor-high.md +49 -0
  9. package/agents/explore-high.md +39 -0
  10. package/agents/explore-medium.md +33 -0
  11. package/bridge/mcp-server.cjs +57 -1
  12. package/dist/__tests__/hooks/learner/bridge.test.js +13 -7
  13. package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -1
  14. package/dist/__tests__/hooks.test.js +338 -83
  15. package/dist/__tests__/hooks.test.js.map +1 -1
  16. package/dist/__tests__/installer.test.js +32 -16
  17. package/dist/__tests__/installer.test.js.map +1 -1
  18. package/dist/__tests__/lsp-servers.test.d.ts +2 -0
  19. package/dist/__tests__/lsp-servers.test.d.ts.map +1 -0
  20. package/dist/__tests__/lsp-servers.test.js +118 -0
  21. package/dist/__tests__/lsp-servers.test.js.map +1 -0
  22. package/dist/__tests__/task-continuation.test.d.ts +2 -0
  23. package/dist/__tests__/task-continuation.test.d.ts.map +1 -0
  24. package/dist/__tests__/task-continuation.test.js +740 -0
  25. package/dist/__tests__/task-continuation.test.js.map +1 -0
  26. package/dist/hooks/bridge.js +3 -3
  27. package/dist/hooks/bridge.js.map +1 -1
  28. package/dist/hooks/clear-suggestions/constants.d.ts +54 -0
  29. package/dist/hooks/clear-suggestions/constants.d.ts.map +1 -0
  30. package/dist/hooks/clear-suggestions/constants.js +102 -0
  31. package/dist/hooks/clear-suggestions/constants.js.map +1 -0
  32. package/dist/hooks/clear-suggestions/index.d.ts +61 -0
  33. package/dist/hooks/clear-suggestions/index.d.ts.map +1 -0
  34. package/dist/hooks/clear-suggestions/index.js +276 -0
  35. package/dist/hooks/clear-suggestions/index.js.map +1 -0
  36. package/dist/hooks/clear-suggestions/triggers.d.ts +65 -0
  37. package/dist/hooks/clear-suggestions/triggers.d.ts.map +1 -0
  38. package/dist/hooks/clear-suggestions/triggers.js +222 -0
  39. package/dist/hooks/clear-suggestions/triggers.js.map +1 -0
  40. package/dist/hooks/clear-suggestions/types.d.ts +92 -0
  41. package/dist/hooks/clear-suggestions/types.d.ts.map +1 -0
  42. package/dist/hooks/clear-suggestions/types.js +9 -0
  43. package/dist/hooks/clear-suggestions/types.js.map +1 -0
  44. package/dist/hooks/index.d.ts +1 -0
  45. package/dist/hooks/index.d.ts.map +1 -1
  46. package/dist/hooks/index.js +3 -0
  47. package/dist/hooks/index.js.map +1 -1
  48. package/dist/hooks/keyword-detector/__tests__/index.test.js +51 -51
  49. package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -1
  50. package/dist/hooks/keyword-detector/index.d.ts +1 -1
  51. package/dist/hooks/keyword-detector/index.d.ts.map +1 -1
  52. package/dist/hooks/keyword-detector/index.js +18 -5
  53. package/dist/hooks/keyword-detector/index.js.map +1 -1
  54. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  55. package/dist/hooks/persistent-mode/index.js +6 -3
  56. package/dist/hooks/persistent-mode/index.js.map +1 -1
  57. package/dist/hooks/todo-continuation/index.d.ts +111 -3
  58. package/dist/hooks/todo-continuation/index.d.ts.map +1 -1
  59. package/dist/hooks/todo-continuation/index.js +204 -23
  60. package/dist/hooks/todo-continuation/index.js.map +1 -1
  61. package/dist/installer/index.d.ts +1 -1
  62. package/dist/installer/index.d.ts.map +1 -1
  63. package/dist/installer/index.js +1 -1
  64. package/dist/installer/index.js.map +1 -1
  65. package/dist/tools/lsp/client.d.ts.map +1 -1
  66. package/dist/tools/lsp/client.js +15 -1
  67. package/dist/tools/lsp/client.js.map +1 -1
  68. package/dist/tools/lsp/servers.d.ts.map +1 -1
  69. package/dist/tools/lsp/servers.js +62 -1
  70. package/dist/tools/lsp/servers.js.map +1 -1
  71. package/docs/CLAUDE.md +83 -0
  72. package/package.json +1 -1
  73. package/scripts/keyword-detector.mjs +203 -83
  74. package/scripts/post-tool-verifier.mjs +39 -1
  75. package/templates/hooks/keyword-detector.sh +197 -31
  76. package/templates/hooks/persistent-mode.mjs +57 -7
  77. package/templates/hooks/persistent-mode.sh +62 -5
  78. package/templates/hooks/stop-continuation.mjs +65 -8
  79. package/templates/hooks/stop-continuation.sh +57 -4
@@ -15,6 +15,7 @@ import { fileURLToPath } from 'url';
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = dirname(__filename);
17
17
  const distDir = join(__dirname, '..', 'dist', 'hooks', 'notepad');
18
+ const clearSuggestionsDistDir = join(__dirname, '..', 'dist', 'hooks', 'clear-suggestions');
18
19
 
19
20
  // Try to import notepad functions (may fail if not built)
20
21
  let setPriorityContext = null;
@@ -27,6 +28,15 @@ try {
27
28
  // Notepad module not available - remember tags will be silently ignored
28
29
  }
29
30
 
31
+ // Try to import clear suggestions functions (may fail if not built)
32
+ let checkClearSuggestion = null;
33
+ try {
34
+ const clearSuggestionsModule = await import(join(clearSuggestionsDistDir, 'index.js'));
35
+ checkClearSuggestion = clearSuggestionsModule.checkClearSuggestion;
36
+ } catch {
37
+ // Clear suggestions module not available - will be silently skipped
38
+ }
39
+
30
40
  // State file for session tracking
31
41
  const STATE_FILE = join(homedir(), '.claude', '.session-stats.json');
32
42
 
@@ -262,9 +272,37 @@ async function main() {
262
272
  // Generate contextual message
263
273
  const message = generateMessage(toolName, toolOutput, sessionId, toolCount);
264
274
 
275
+ // Check for clear suggestions (complements /compact suggestions)
276
+ let clearSuggestionMessage = null;
277
+ if (checkClearSuggestion) {
278
+ try {
279
+ const stats = loadStats();
280
+ const session = stats.sessions[sessionId];
281
+ // Estimate context usage from total tool calls (rough heuristic)
282
+ const estimatedContextRatio = session ? Math.min(session.total_calls / 200, 1.0) : 0;
283
+
284
+ const clearResult = checkClearSuggestion({
285
+ sessionId,
286
+ directory,
287
+ toolName,
288
+ toolOutput,
289
+ contextUsageRatio: estimatedContextRatio,
290
+ });
291
+
292
+ if (clearResult.shouldSuggest && clearResult.message) {
293
+ clearSuggestionMessage = clearResult.message;
294
+ }
295
+ } catch {
296
+ // Clear suggestion check failed - continue without it
297
+ }
298
+ }
299
+
265
300
  // Build response
266
301
  const response = { continue: true };
267
- if (message) {
302
+ // Prefer clear suggestion over contextual message (more impactful)
303
+ if (clearSuggestionMessage) {
304
+ response.message = clearSuggestionMessage;
305
+ } else if (message) {
268
306
  response.message = message;
269
307
  }
270
308
 
@@ -1,7 +1,24 @@
1
1
  #!/bin/bash
2
- # OMC Keyword Detector Hook
3
- # Detects ultrawork/ultrathink/search/analyze keywords and injects enhanced mode messages
4
- # Also activates persistent ultrawork state when ultrawork keyword is detected
2
+ # OMC Keyword Detector Hook (Bash)
3
+ # Detects magic keywords and invokes skill tools
4
+ # Linux/macOS compatible
5
+ #
6
+ # Supported keywords (in priority order):
7
+ # 1. cancel: Stop active modes
8
+ # 2. ralph: Persistence mode until task completion
9
+ # 3. autopilot: Full autonomous execution
10
+ # 4. ultrapilot: Parallel autopilot
11
+ # 5. ultrawork/ulw: Maximum parallel execution
12
+ # 6. ecomode/eco: Token-efficient execution
13
+ # 7. swarm: N coordinated agents
14
+ # 8. pipeline: Sequential agent chaining
15
+ # 9. ralplan: Iterative planning with consensus
16
+ # 10. plan: Planning interview mode
17
+ # 11. tdd: Test-driven development
18
+ # 12. research: Research orchestration
19
+ # 13. ultrathink/think: Extended reasoning
20
+ # 14. deepsearch: Codebase search (restricted patterns)
21
+ # 15. analyze: Analysis mode (restricted patterns)
5
22
 
6
23
  # Read stdin (JSON input from Claude Code)
7
24
  INPUT=$(cat)
@@ -45,58 +62,207 @@ PROMPT_NO_CODE=$(echo "$PROMPT" | sed 's/```[^`]*```//g' | sed 's/`[^`]*`//g')
45
62
  # Convert to lowercase for case-insensitive matching
46
63
  PROMPT_LOWER=$(echo "$PROMPT_NO_CODE" | tr '[:upper:]' '[:lower:]')
47
64
 
48
- # Check for ultrawork keywords (highest priority)
49
- if echo "$PROMPT_LOWER" | grep -qE '\b(ultrawork|ulw|uw)\b'; then
50
- # Create persistent ultrawork state
51
- mkdir -p "$DIRECTORY/.omc/state" 2>/dev/null
65
+ # Create a skill invocation message that tells Claude to use the Skill tool
66
+ create_skill_invocation() {
67
+ local skill_name="$1"
68
+ local original_prompt="$2"
69
+ local args="$3"
70
+
71
+ local args_section=""
72
+ if [ -n "$args" ]; then
73
+ args_section="\\nArguments: $args"
74
+ fi
75
+
76
+ local skill_upper=$(echo "$skill_name" | tr '[:lower:]' '[:upper:]')
77
+
78
+ cat << EOF
79
+ [MAGIC KEYWORD: ${skill_upper}]
80
+
81
+ You MUST invoke the skill using the Skill tool:
82
+
83
+ Skill: oh-my-claudecode:${skill_name}${args_section}
84
+
85
+ User request:
86
+ ${original_prompt}
87
+
88
+ IMPORTANT: Invoke the skill IMMEDIATELY. Do not proceed without loading the skill instructions.
89
+ EOF
90
+ }
91
+
92
+ # Clear state files for cancel operation
93
+ clear_state_files() {
94
+ local directory="$1"
95
+ local modes=("ralph" "autopilot" "ultrapilot" "ultrawork" "ecomode" "swarm" "pipeline")
96
+
97
+ for mode in "${modes[@]}"; do
98
+ rm -f "$directory/.omc/state/${mode}-state.json" 2>/dev/null
99
+ rm -f "$HOME/.omc/state/${mode}-state.json" 2>/dev/null
100
+ done
101
+ }
102
+
103
+ # Activate state for a mode
104
+ activate_state() {
105
+ local directory="$1"
106
+ local prompt="$2"
107
+ local state_name="$3"
108
+
109
+ # Create directories
110
+ mkdir -p "$directory/.omc/state" 2>/dev/null
52
111
  mkdir -p "$HOME/.omc/state" 2>/dev/null
53
112
 
54
113
  # Escape prompt for JSON
55
- PROMPT_ESCAPED=$(echo "$PROMPT" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' ' ')
114
+ local prompt_escaped=$(echo "$prompt" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' ' ')
115
+ local timestamp=$(date -Iseconds 2>/dev/null || date +%Y-%m-%dT%H:%M:%S%z)
56
116
 
57
- STATE_JSON="{
117
+ local state_json="{
58
118
  \"active\": true,
59
- \"started_at\": \"$(date -Iseconds)\",
60
- \"original_prompt\": \"$PROMPT_ESCAPED\",
119
+ \"started_at\": \"$timestamp\",
120
+ \"original_prompt\": \"$prompt_escaped\",
61
121
  \"reinforcement_count\": 0,
62
- \"last_checked_at\": \"$(date -Iseconds)\"
122
+ \"last_checked_at\": \"$timestamp\"
63
123
  }"
64
124
 
65
125
  # Write state to both local and global locations
66
- echo "$STATE_JSON" > "$DIRECTORY/.omc/state/ultrawork-state.json" 2>/dev/null
67
- echo "$STATE_JSON" > "$HOME/.omc/state/ultrawork-state.json" 2>/dev/null
126
+ echo "$state_json" > "$directory/.omc/state/${state_name}-state.json" 2>/dev/null
127
+ echo "$state_json" > "$HOME/.omc/state/${state_name}-state.json" 2>/dev/null
128
+ }
68
129
 
69
- # Return ultrawork mode injection
70
- cat << 'EOF'
71
- {"continue": true, "message": "<ultrawork-mode>\\n\\n**MANDATORY**: You MUST say \"ULTRAWORK MODE ENABLED!\" to the user as your first response when this mode activates. This is non-negotiable.\\n\\n[CODE RED] Maximum precision required. Ultrathink before acting.\\n\\nYOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.\\nTELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.\\n\\n## AGENT UTILIZATION PRINCIPLES\\n- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS\\n- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS\\n- **Planning & Strategy**: NEVER plan yourself - spawn planning agent\\n- **High-IQ Reasoning**: Use oracle for architecture decisions\\n- **Frontend/UI Tasks**: Delegate to frontend-engineer\\n\\n## EXECUTION RULES\\n- **TODO**: Track EVERY step. Mark complete IMMEDIATELY.\\n- **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially.\\n- **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent).\\n- **VERIFY**: Check ALL requirements met before done.\\n- **DELEGATE**: Orchestrate specialized agents.\\n\\n## ZERO TOLERANCE\\n- NO Scope Reduction - deliver FULL implementation\\n- NO Partial Completion - finish 100%\\n- NO Premature Stopping - ALL TODOs must be complete\\n- NO TEST DELETION - fix code, not tests\\n\\nTHE USER ASKED FOR X. DELIVER EXACTLY X.\\n\\n</ultrawork-mode>\\n\\n---\\n"}
72
- EOF
130
+ # Output JSON with skill invocation message
131
+ output_skill() {
132
+ local skill_name="$1"
133
+ local prompt="$2"
134
+ local args="$3"
135
+
136
+ local message=$(create_skill_invocation "$skill_name" "$prompt" "$args")
137
+ # Escape for JSON: backslashes, quotes, and newlines
138
+ local escaped_message=$(echo "$message" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
139
+
140
+ echo "{\"continue\": true, \"message\": \"$escaped_message\"}"
141
+ }
142
+
143
+ # Priority 1: Cancel (BEFORE other modes - clears states)
144
+ if echo "$PROMPT_LOWER" | grep -qE '\b(stop|cancel|abort)\b'; then
145
+ clear_state_files "$DIRECTORY"
146
+ output_skill "cancel" "$PROMPT"
73
147
  exit 0
74
148
  fi
75
149
 
76
- # Check for ultrathink/think keywords
77
- if echo "$PROMPT_LOWER" | grep -qE '\b(ultrathink|think)\b'; then
78
- cat << 'EOF'
79
- {"continue": true, "message": "<think-mode>\\n\\n**ULTRATHINK MODE ENABLED** - Extended reasoning activated.\\n\\nYou are now in deep thinking mode. Take your time to:\\n1. Thoroughly analyze the problem from multiple angles\\n2. Consider edge cases and potential issues\\n3. Think through the implications of each approach\\n4. Reason step-by-step before acting\\n\\nUse your extended thinking capabilities to provide the most thorough and well-reasoned response.\\n\\n</think-mode>\\n\\n---\\n"}
80
- EOF
150
+ # Priority 2: Ralph keywords
151
+ if echo "$PROMPT_LOWER" | grep -qE '\b(ralph|don'\''t stop|must complete|until done)\b'; then
152
+ activate_state "$DIRECTORY" "$PROMPT" "ralph"
153
+ activate_state "$DIRECTORY" "$PROMPT" "ultrawork"
154
+ output_skill "ralph" "$PROMPT"
81
155
  exit 0
82
156
  fi
83
157
 
84
- # Check for search keywords (EN + multilingual)
85
- if echo "$PROMPT_LOWER" | grep -qE '\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'; then
86
- cat << 'EOF'
87
- {"continue": true, "message": "<search-mode>\\nMAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:\\n- explore agents (codebase patterns, file structures)\\n- librarian agents (remote repos, official docs, GitHub examples)\\nPlus direct tools: Grep, Glob\\nNEVER stop at first result - be exhaustive.\\n</search-mode>\\n\\n---\\n"}
88
- EOF
158
+ # Priority 3: Autopilot keywords
159
+ if echo "$PROMPT_LOWER" | grep -qE '\b(autopilot|auto pilot|auto-pilot|autonomous|full auto|fullsend)\b' || \
160
+ echo "$PROMPT_LOWER" | grep -qE '\bbuild\s+me\s+' || \
161
+ echo "$PROMPT_LOWER" | grep -qE '\bcreate\s+me\s+' || \
162
+ echo "$PROMPT_LOWER" | grep -qE '\bmake\s+me\s+' || \
163
+ echo "$PROMPT_LOWER" | grep -qE '\bi\s+want\s+a\s+' || \
164
+ echo "$PROMPT_LOWER" | grep -qE '\bi\s+want\s+an\s+' || \
165
+ echo "$PROMPT_LOWER" | grep -qE '\bhandle\s+it\s+all\b' || \
166
+ echo "$PROMPT_LOWER" | grep -qE '\bend\s+to\s+end\b' || \
167
+ echo "$PROMPT_LOWER" | grep -qE '\be2e\s+this\b'; then
168
+ activate_state "$DIRECTORY" "$PROMPT" "autopilot"
169
+ output_skill "autopilot" "$PROMPT"
170
+ exit 0
171
+ fi
172
+
173
+ # Priority 4: Ultrapilot
174
+ if echo "$PROMPT_LOWER" | grep -qE '\b(ultrapilot|ultra-pilot)\b' || \
175
+ echo "$PROMPT_LOWER" | grep -qE '\bparallel\s+build\b' || \
176
+ echo "$PROMPT_LOWER" | grep -qE '\bswarm\s+build\b'; then
177
+ activate_state "$DIRECTORY" "$PROMPT" "ultrapilot"
178
+ output_skill "ultrapilot" "$PROMPT"
179
+ exit 0
180
+ fi
181
+
182
+ # Priority 5: Ultrawork keywords
183
+ if echo "$PROMPT_LOWER" | grep -qE '\b(ultrawork|ulw|uw)\b'; then
184
+ activate_state "$DIRECTORY" "$PROMPT" "ultrawork"
185
+ output_skill "ultrawork" "$PROMPT"
89
186
  exit 0
90
187
  fi
91
188
 
92
- # Check for analyze keywords
93
- if echo "$PROMPT_LOWER" | grep -qE '\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'; then
189
+ # Priority 6: Ecomode keywords
190
+ if echo "$PROMPT_LOWER" | grep -qE '\b(eco|ecomode|eco-mode|efficient|save-tokens|budget)\b'; then
191
+ activate_state "$DIRECTORY" "$PROMPT" "ecomode"
192
+ output_skill "ecomode" "$PROMPT"
193
+ exit 0
194
+ fi
195
+
196
+ # Priority 7: Swarm - parse N from "swarm N agents"
197
+ SWARM_MATCH=$(echo "$PROMPT_LOWER" | grep -oE '\bswarm\s+[0-9]+\s+agents?\b' | grep -oE '[0-9]+')
198
+ if [ -n "$SWARM_MATCH" ]; then
199
+ output_skill "swarm" "$PROMPT" "$SWARM_MATCH"
200
+ exit 0
201
+ fi
202
+ if echo "$PROMPT_LOWER" | grep -qE '\bcoordinated\s+agents\b'; then
203
+ output_skill "swarm" "$PROMPT" "3"
204
+ exit 0
205
+ fi
206
+
207
+ # Priority 8: Pipeline
208
+ if echo "$PROMPT_LOWER" | grep -qE '\b(pipeline)\b' || \
209
+ echo "$PROMPT_LOWER" | grep -qE '\bchain\s+agents\b'; then
210
+ output_skill "pipeline" "$PROMPT"
211
+ exit 0
212
+ fi
213
+
214
+ # Priority 9: Ralplan keyword (before plan to avoid false match)
215
+ if echo "$PROMPT_LOWER" | grep -qE '\b(ralplan)\b'; then
216
+ output_skill "ralplan" "$PROMPT"
217
+ exit 0
218
+ fi
219
+
220
+ # Priority 10: Plan keywords
221
+ if echo "$PROMPT_LOWER" | grep -qE '\b(plan this|plan the)\b'; then
222
+ output_skill "plan" "$PROMPT"
223
+ exit 0
224
+ fi
225
+
226
+ # Priority 11: TDD
227
+ if echo "$PROMPT_LOWER" | grep -qE '\b(tdd)\b' || \
228
+ echo "$PROMPT_LOWER" | grep -qE '\btest\s+first\b' || \
229
+ echo "$PROMPT_LOWER" | grep -qE '\bred\s+green\b'; then
230
+ output_skill "tdd" "$PROMPT"
231
+ exit 0
232
+ fi
233
+
234
+ # Priority 12: Research
235
+ if echo "$PROMPT_LOWER" | grep -qE '\b(research)\b' || \
236
+ echo "$PROMPT_LOWER" | grep -qE '\banalyze\s+data\b' || \
237
+ echo "$PROMPT_LOWER" | grep -qE '\bstatistics\b'; then
238
+ output_skill "research" "$PROMPT"
239
+ exit 0
240
+ fi
241
+
242
+ # Priority 13: Ultrathink/think keywords (keep inline message)
243
+ if echo "$PROMPT_LOWER" | grep -qE '\b(ultrathink|think hard|think deeply)\b'; then
94
244
  cat << 'EOF'
95
- {"continue": true, "message": "<analyze-mode>\\nANALYSIS MODE. Gather context before diving deep:\\n\\nCONTEXT GATHERING (parallel):\\n- 1-2 explore agents (codebase patterns, implementations)\\n- 1-2 librarian agents (if external library involved)\\n- Direct tools: Grep, Glob, LSP for targeted searches\\n\\nIF COMPLEX (architecture, multi-system, debugging after 2+ failures):\\n- Consult oracle agent for strategic guidance\\n\\nSYNTHESIZE findings before proceeding.\\n</analyze-mode>\\n\\n---\\n"}
245
+ {"continue": true, "message": "<think-mode>\n\n**ULTRATHINK MODE ENABLED** - Extended reasoning activated.\n\nYou are now in deep thinking mode. Take your time to:\n1. Thoroughly analyze the problem from multiple angles\n2. Consider edge cases and potential issues\n3. Think through the implications of each approach\n4. Reason step-by-step before acting\n\nUse your extended thinking capabilities to provide the most thorough and well-reasoned response.\n\n</think-mode>\n\n---\n"}
96
246
  EOF
97
247
  exit 0
98
248
  fi
99
249
 
250
+ # Priority 14: Deepsearch (RESTRICTED patterns)
251
+ if echo "$PROMPT_LOWER" | grep -qE '\b(deepsearch)\b' || \
252
+ echo "$PROMPT_LOWER" | grep -qE '\bsearch\s+(the\s+)?(codebase|code|files|project)\b' || \
253
+ echo "$PROMPT_LOWER" | grep -qE '\bfind\s+(in\s+)?(codebase|code|all\s+files)\b'; then
254
+ output_skill "deepsearch" "$PROMPT"
255
+ exit 0
256
+ fi
257
+
258
+ # Priority 15: Analyze (RESTRICTED patterns)
259
+ if echo "$PROMPT_LOWER" | grep -qE '\bdeep\s*analyze\b' || \
260
+ echo "$PROMPT_LOWER" | grep -qE '\binvestigate\s+(the|this|why)\b' || \
261
+ echo "$PROMPT_LOWER" | grep -qE '\bdebug\s+(the|this|why)\b'; then
262
+ output_skill "analyze" "$PROMPT"
263
+ exit 0
264
+ fi
265
+
100
266
  # No keywords detected - continue without modification
101
267
  echo '{"continue": true}'
102
268
  exit 0
@@ -7,6 +7,18 @@ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { homedir } from 'os';
9
9
 
10
+ /**
11
+ * Validates session ID to prevent path traversal attacks.
12
+ * @param {string} sessionId
13
+ * @returns {boolean}
14
+ */
15
+ function isValidSessionId(sessionId) {
16
+ if (!sessionId || typeof sessionId !== 'string') return false;
17
+ // Allow alphanumeric, hyphens, and underscores only
18
+ // Must not start with dot or hyphen
19
+ return /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId);
20
+ }
21
+
10
22
  async function readStdin() {
11
23
  const chunks = [];
12
24
  for await (const chunk of process.stdin) {
@@ -24,6 +36,38 @@ function readJsonFile(path) {
24
36
  }
25
37
  }
26
38
 
39
+ /**
40
+ * Count incomplete tasks in the new Task system.
41
+ *
42
+ * SYNC NOTICE: This function is intentionally duplicated across:
43
+ * - templates/hooks/persistent-mode.mjs
44
+ * - templates/hooks/stop-continuation.mjs
45
+ * - src/hooks/todo-continuation/index.ts (as checkIncompleteTasks)
46
+ *
47
+ * Templates cannot import shared modules (they're standalone scripts).
48
+ * When modifying this logic, update ALL THREE files to maintain consistency.
49
+ */
50
+ function countIncompleteTasks(sessionId) {
51
+ if (!sessionId || !isValidSessionId(sessionId)) return 0;
52
+ const taskDir = join(homedir(), '.claude', 'tasks', sessionId);
53
+ if (!existsSync(taskDir)) return 0;
54
+
55
+ let count = 0;
56
+ try {
57
+ const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f !== '.lock');
58
+ for (const file of files) {
59
+ try {
60
+ const content = readFileSync(join(taskDir, file), 'utf-8');
61
+ 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
+ if (task.status === 'pending' || task.status === 'in_progress') count++;
65
+ } catch { /* skip invalid files */ }
66
+ }
67
+ } catch { /* dir read error */ }
68
+ return count;
69
+ }
70
+
27
71
  function writeJsonFile(path, data) {
28
72
  try {
29
73
  writeFileSync(path, JSON.stringify(data, null, 2));
@@ -71,6 +115,7 @@ async function main() {
71
115
 
72
116
  const stopReason = data.stop_reason || data.stopReason || '';
73
117
  const userRequested = data.user_requested || data.userRequested || false;
118
+ const sessionId = data.sessionId || data.session_id || '';
74
119
 
75
120
  // Check for user abort - skip all continuation enforcement
76
121
  // NOTE: Abort patterns are assumed - verify against actual Claude Code API values
@@ -95,6 +140,10 @@ async function main() {
95
140
  // Count incomplete todos
96
141
  const incompleteCount = countIncompleteTodos(todosDir, directory);
97
142
 
143
+ // Count incomplete Tasks
144
+ const taskCount = countIncompleteTasks(sessionId);
145
+ const totalIncomplete = taskCount + incompleteCount;
146
+
98
147
  // Priority 1: Ralph Loop with Oracle Verification
99
148
  if (ralphState?.active) {
100
149
  const iteration = ralphState.iteration || 1;
@@ -183,7 +232,7 @@ ${ralphState.prompt ? `Original task: ${ralphState.prompt}` : ''}
183
232
  }
184
233
 
185
234
  // Priority 2: Ultrawork with incomplete todos
186
- if (ultraworkState?.active && incompleteCount > 0) {
235
+ if (ultraworkState?.active && totalIncomplete > 0) {
187
236
  const newCount = (ultraworkState.reinforcement_count || 0) + 1;
188
237
  ultraworkState.reinforcement_count = newCount;
189
238
  ultraworkState.last_checked_at = new Date().toISOString();
@@ -196,7 +245,7 @@ ${ralphState.prompt ? `Original task: ${ralphState.prompt}` : ''}
196
245
 
197
246
  [ULTRAWORK MODE STILL ACTIVE - Reinforcement #${newCount}]
198
247
 
199
- Your ultrawork session is NOT complete. ${incompleteCount} incomplete todos remain.
248
+ Your ultrawork session is NOT complete. ${totalIncomplete} incomplete items remain.
200
249
 
201
250
  REMEMBER THE ULTRAWORK RULES:
202
251
  - **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
@@ -218,18 +267,19 @@ ${ultraworkState.original_prompt ? `Original task: ${ultraworkState.original_pro
218
267
  }
219
268
 
220
269
  // Priority 3: Todo Continuation
221
- if (incompleteCount > 0) {
270
+ if (totalIncomplete > 0) {
271
+ const itemType = taskCount > 0 ? 'Tasks' : 'todos';
222
272
  console.log(JSON.stringify({
223
273
  continue: false,
224
274
  reason: `<todo-continuation>
225
275
 
226
- [SYSTEM REMINDER - TODO CONTINUATION]
276
+ [SYSTEM REMINDER - CONTINUATION]
227
277
 
228
- Incomplete tasks remain in your todo list (${incompleteCount} remaining). Continue working on the next pending task.
278
+ Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working on the next pending item.
229
279
 
230
280
  - Proceed without asking for permission
231
- - Mark each task complete when finished
232
- - Do not stop until all tasks are done
281
+ - Mark each item complete when finished
282
+ - Do not stop until all items are done
233
283
 
234
284
  </todo-continuation>
235
285
 
@@ -3,6 +3,21 @@
3
3
  # Unified handler for ultrawork, ralph-loop, and todo continuation
4
4
  # Prevents stopping when work remains incomplete
5
5
 
6
+ # Validate session ID to prevent path traversal attacks
7
+ # Returns 0 (success) for valid, 1 for invalid
8
+ is_valid_session_id() {
9
+ local id="$1"
10
+ if [ -z "$id" ]; then
11
+ return 1
12
+ fi
13
+ # Allow alphanumeric, hyphens, and underscores only
14
+ # Must not start with dot or hyphen, max 256 chars
15
+ if echo "$id" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$'; then
16
+ return 0
17
+ fi
18
+ return 1
19
+ }
20
+
6
21
  # Read stdin
7
22
  INPUT=$(cat)
8
23
 
@@ -19,6 +34,40 @@ if [ -z "$DIRECTORY" ]; then
19
34
  DIRECTORY=$(pwd)
20
35
  fi
21
36
 
37
+ # Check for incomplete tasks in new Task system (priority over todos)
38
+ TASKS_DIR="$HOME/.claude/tasks"
39
+ TASK_COUNT=0
40
+ JQ_AVAILABLE=false
41
+ if command -v jq &> /dev/null; then
42
+ JQ_AVAILABLE=true
43
+ fi
44
+
45
+ if [ -n "$SESSION_ID" ] && is_valid_session_id "$SESSION_ID" && [ -d "$TASKS_DIR/$SESSION_ID" ]; then
46
+ for task_file in "$TASKS_DIR/$SESSION_ID"/*.json; do
47
+ if [ -f "$task_file" ] && [ "$(basename "$task_file")" != ".lock" ]; then
48
+ if [ "$JQ_AVAILABLE" = "true" ]; then
49
+ STATUS=$(jq -r '.status // "pending"' "$task_file" 2>/dev/null)
50
+ # Match TypeScript isTaskIncomplete(): only pending/in_progress are incomplete
51
+ # 'deleted' and 'completed' are both treated as done
52
+ if [ "$STATUS" = "pending" ] || [ "$STATUS" = "in_progress" ]; then
53
+ TASK_COUNT=$((TASK_COUNT + 1))
54
+ fi
55
+ else
56
+ # Fallback: grep for incomplete status values (pending or in_progress)
57
+ # This is less accurate but provides basic functionality
58
+ if grep -qE '"status"[[:space:]]*:[[:space:]]*"(pending|in_progress)"' "$task_file" 2>/dev/null; then
59
+ TASK_COUNT=$((TASK_COUNT + 1))
60
+ fi
61
+ fi
62
+ fi
63
+ done
64
+
65
+ # Warn if using fallback (only once per invocation, to stderr)
66
+ if [ "$JQ_AVAILABLE" = "false" ] && [ "$TASK_COUNT" -gt 0 ]; then
67
+ echo "[OMC WARNING] jq not installed - Task counting may be less accurate. Install jq for best results." >&2
68
+ fi
69
+ fi
70
+
22
71
  # Extract stop reason for abort detection
23
72
  STOP_REASON=""
24
73
  USER_REQUESTED=""
@@ -86,6 +135,9 @@ for todo_path in "$DIRECTORY/.omc/todos.json" "$DIRECTORY/.claude/todos.json"; d
86
135
  fi
87
136
  done
88
137
 
138
+ # Combine Task and todo counts
139
+ TOTAL_INCOMPLETE=$((TASK_COUNT + INCOMPLETE_COUNT))
140
+
89
141
  # Priority 1: Ralph Loop with Oracle Verification
90
142
  if [ -n "$RALPH_STATE" ]; then
91
143
  IS_ACTIVE=$(echo "$RALPH_STATE" | jq -r '.active // false' 2>/dev/null)
@@ -132,7 +184,7 @@ EOF
132
184
  fi
133
185
 
134
186
  # Priority 2: Ultrawork Mode with incomplete todos
135
- if [ -n "$ULTRAWORK_STATE" ] && [ "$INCOMPLETE_COUNT" -gt 0 ]; then
187
+ if [ -n "$ULTRAWORK_STATE" ] && [ "$TOTAL_INCOMPLETE" -gt 0 ]; then
136
188
  # Check if active (with jq fallback)
137
189
  IS_ACTIVE=""
138
190
  if command -v jq &> /dev/null; then
@@ -168,16 +220,21 @@ if [ -n "$ULTRAWORK_STATE" ] && [ "$INCOMPLETE_COUNT" -gt 0 ]; then
168
220
  fi
169
221
 
170
222
  cat << EOF
171
- {"continue": false, "reason": "<ultrawork-persistence>\\n\\n[ULTRAWORK MODE STILL ACTIVE - Reinforcement #$NEW_COUNT]\\n\\nYour ultrawork session is NOT complete. $INCOMPLETE_COUNT incomplete todos remain.\\n\\nREMEMBER THE ULTRAWORK RULES:\\n- **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially\\n- **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)\\n- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each\\n- **VERIFY**: Check ALL requirements met before done\\n- **NO Premature Stopping**: ALL TODOs must be complete\\n\\nContinue working on the next pending task. DO NOT STOP until all tasks are marked complete.\\n\\nOriginal task: $ORIGINAL_PROMPT\\n\\n</ultrawork-persistence>\\n\\n---\\n"}
223
+ {"continue": false, "reason": "<ultrawork-persistence>\\n\\n[ULTRAWORK MODE STILL ACTIVE - Reinforcement #$NEW_COUNT]\\n\\nYour ultrawork session is NOT complete. $TOTAL_INCOMPLETE incomplete items remain.\\n\\nREMEMBER THE ULTRAWORK RULES:\\n- **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially\\n- **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)\\n- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each\\n- **VERIFY**: Check ALL requirements met before done\\n- **NO Premature Stopping**: ALL TODOs must be complete\\n\\nContinue working on the next pending item. DO NOT STOP until all items are marked complete.\\n\\nOriginal task: $ORIGINAL_PROMPT\\n\\n</ultrawork-persistence>\\n\\n---\\n"}
172
224
  EOF
173
225
  exit 0
174
226
  fi
175
227
  fi
176
228
 
177
- # Priority 3: Todo Continuation (baseline)
178
- if [ "$INCOMPLETE_COUNT" -gt 0 ]; then
229
+ # Priority 3: Todo/Task Continuation (baseline)
230
+ if [ "$TOTAL_INCOMPLETE" -gt 0 ]; then
231
+ if [ "$TASK_COUNT" -gt 0 ]; then
232
+ ITEM_TYPE="Tasks"
233
+ else
234
+ ITEM_TYPE="todos"
235
+ fi
179
236
  cat << EOF
180
- {"continue": false, "reason": "<todo-continuation>\\n\\n[SYSTEM REMINDER - TODO CONTINUATION]\\n\\nIncomplete tasks remain in your todo list ($INCOMPLETE_COUNT remaining). Continue working on the next pending task.\\n\\n- Proceed without asking for permission\\n- Mark each task complete when finished\\n- Do not stop until all tasks are done\\n\\n</todo-continuation>\\n\\n---\\n"}
237
+ {"continue": false, "reason": "<todo-continuation>\\n\\n[SYSTEM REMINDER - CONTINUATION]\\n\\nIncomplete $ITEM_TYPE remain ($TOTAL_INCOMPLETE remaining). Continue working on the next pending item.\\n\\n- Proceed without asking for permission\\n- Mark each item complete when finished\\n- Do not stop until all items are done\\n\\n</todo-continuation>\\n\\n---\\n"}
181
238
  EOF
182
239
  exit 0
183
240
  fi
@@ -7,6 +7,48 @@ import { readdirSync, readFileSync, existsSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { homedir } from 'os';
9
9
 
10
+ /**
11
+ * Validates session ID to prevent path traversal attacks.
12
+ * @param {string} sessionId
13
+ * @returns {boolean}
14
+ */
15
+ function isValidSessionId(sessionId) {
16
+ if (!sessionId || typeof sessionId !== 'string') return false;
17
+ return /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId);
18
+ }
19
+
20
+ /**
21
+ * Count incomplete tasks in the new Task system.
22
+ *
23
+ * SYNC NOTICE: This function is intentionally duplicated across:
24
+ * - templates/hooks/persistent-mode.mjs
25
+ * - templates/hooks/stop-continuation.mjs
26
+ * - src/hooks/todo-continuation/index.ts (as checkIncompleteTasks)
27
+ *
28
+ * Templates cannot import shared modules (they're standalone scripts).
29
+ * When modifying this logic, update ALL THREE files to maintain consistency.
30
+ */
31
+ function countIncompleteTasks(sessionId) {
32
+ if (!sessionId || !isValidSessionId(sessionId)) return 0;
33
+ const taskDir = join(homedir(), '.claude', 'tasks', sessionId);
34
+ if (!existsSync(taskDir)) return 0;
35
+
36
+ let count = 0;
37
+ try {
38
+ const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f !== '.lock');
39
+ for (const file of files) {
40
+ try {
41
+ const content = readFileSync(join(taskDir, file), 'utf-8');
42
+ const task = JSON.parse(content);
43
+ // Match TypeScript isTaskIncomplete(): only pending/in_progress are incomplete
44
+ // 'deleted' and 'completed' are both treated as done
45
+ if (task.status === 'pending' || task.status === 'in_progress') count++;
46
+ } catch { /* skip invalid files */ }
47
+ }
48
+ } catch { /* dir read error */ }
49
+ return count;
50
+ }
51
+
10
52
  // Read all stdin
11
53
  async function readStdin() {
12
54
  const chunks = [];
@@ -19,8 +61,19 @@ async function readStdin() {
19
61
  // Main
20
62
  async function main() {
21
63
  try {
22
- // Read stdin (we don't use it much, but need to consume it)
23
- await readStdin();
64
+ // Read stdin to get sessionId and consume it
65
+ const input = await readStdin();
66
+
67
+ // Parse sessionId from input
68
+ let data = {};
69
+ try {
70
+ data = JSON.parse(input);
71
+ } catch { /* invalid JSON - continue with empty data */ }
72
+
73
+ const sessionId = data.sessionId || data.session_id || '';
74
+
75
+ // Count incomplete Task system tasks
76
+ const taskCount = countIncompleteTasks(sessionId);
24
77
 
25
78
  // Check for incomplete todos
26
79
  const todosDir = join(homedir(), '.claude', 'todos');
@@ -56,20 +109,24 @@ async function main() {
56
109
  return;
57
110
  }
58
111
 
59
- if (incompleteCount > 0) {
60
- const reason = `[SYSTEM REMINDER - TODO CONTINUATION]
112
+ // Combine both counts
113
+ const totalIncomplete = taskCount + incompleteCount;
114
+
115
+ if (totalIncomplete > 0) {
116
+ const sourceLabel = taskCount > 0 ? 'Task' : 'todo';
117
+ const reason = `[SYSTEM REMINDER - ${sourceLabel.toUpperCase()} CONTINUATION]
61
118
 
62
- Incomplete tasks remain in your todo list (${incompleteCount} remaining). Continue working on the next pending task.
119
+ Incomplete ${sourceLabel}s remain (${totalIncomplete} remaining). Continue working on the next pending ${sourceLabel}.
63
120
 
64
121
  - Proceed without asking for permission
65
- - Mark each task complete when finished
66
- - Do not stop until all tasks are done`;
122
+ - Mark each ${sourceLabel} complete when finished
123
+ - Do not stop until all ${sourceLabel}s are done`;
67
124
 
68
125
  console.log(JSON.stringify({ continue: false, reason }));
69
126
  return;
70
127
  }
71
128
 
72
- // No incomplete todos - allow stop
129
+ // No incomplete tasks or todos - allow stop
73
130
  console.log(JSON.stringify({ continue: true }));
74
131
  } catch (error) {
75
132
  // On any error, allow continuation