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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -4
- package/agents/AGENTS.md +8 -8
- package/agents/architect.md +32 -0
- package/agents/build-fixer.md +35 -0
- package/agents/code-reviewer.md +51 -0
- package/agents/executor-high.md +49 -0
- package/agents/explore-high.md +39 -0
- package/agents/explore-medium.md +33 -0
- package/bridge/mcp-server.cjs +57 -1
- package/dist/__tests__/hooks/learner/bridge.test.js +13 -7
- package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -1
- package/dist/__tests__/hooks.test.js +338 -83
- package/dist/__tests__/hooks.test.js.map +1 -1
- package/dist/__tests__/installer.test.js +32 -16
- package/dist/__tests__/installer.test.js.map +1 -1
- package/dist/__tests__/lsp-servers.test.d.ts +2 -0
- package/dist/__tests__/lsp-servers.test.d.ts.map +1 -0
- package/dist/__tests__/lsp-servers.test.js +118 -0
- package/dist/__tests__/lsp-servers.test.js.map +1 -0
- package/dist/__tests__/task-continuation.test.d.ts +2 -0
- package/dist/__tests__/task-continuation.test.d.ts.map +1 -0
- package/dist/__tests__/task-continuation.test.js +740 -0
- package/dist/__tests__/task-continuation.test.js.map +1 -0
- package/dist/hooks/bridge.js +3 -3
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/clear-suggestions/constants.d.ts +54 -0
- package/dist/hooks/clear-suggestions/constants.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/constants.js +102 -0
- package/dist/hooks/clear-suggestions/constants.js.map +1 -0
- package/dist/hooks/clear-suggestions/index.d.ts +61 -0
- package/dist/hooks/clear-suggestions/index.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/index.js +276 -0
- package/dist/hooks/clear-suggestions/index.js.map +1 -0
- package/dist/hooks/clear-suggestions/triggers.d.ts +65 -0
- package/dist/hooks/clear-suggestions/triggers.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/triggers.js +222 -0
- package/dist/hooks/clear-suggestions/triggers.js.map +1 -0
- package/dist/hooks/clear-suggestions/types.d.ts +92 -0
- package/dist/hooks/clear-suggestions/types.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/types.js +9 -0
- package/dist/hooks/clear-suggestions/types.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/keyword-detector/__tests__/index.test.js +51 -51
- package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -1
- package/dist/hooks/keyword-detector/index.d.ts +1 -1
- package/dist/hooks/keyword-detector/index.d.ts.map +1 -1
- package/dist/hooks/keyword-detector/index.js +18 -5
- package/dist/hooks/keyword-detector/index.js.map +1 -1
- package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
- package/dist/hooks/persistent-mode/index.js +6 -3
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/todo-continuation/index.d.ts +111 -3
- package/dist/hooks/todo-continuation/index.d.ts.map +1 -1
- package/dist/hooks/todo-continuation/index.js +204 -23
- package/dist/hooks/todo-continuation/index.js.map +1 -1
- package/dist/installer/index.d.ts +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +1 -1
- package/dist/installer/index.js.map +1 -1
- package/dist/tools/lsp/client.d.ts.map +1 -1
- package/dist/tools/lsp/client.js +15 -1
- package/dist/tools/lsp/client.js.map +1 -1
- package/dist/tools/lsp/servers.d.ts.map +1 -1
- package/dist/tools/lsp/servers.js +62 -1
- package/dist/tools/lsp/servers.js.map +1 -1
- package/docs/CLAUDE.md +83 -0
- package/package.json +1 -1
- package/scripts/keyword-detector.mjs +203 -83
- package/scripts/post-tool-verifier.mjs +39 -1
- package/templates/hooks/keyword-detector.sh +197 -31
- package/templates/hooks/persistent-mode.mjs +57 -7
- package/templates/hooks/persistent-mode.sh +62 -5
- package/templates/hooks/stop-continuation.mjs +65 -8
- 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
|
-
|
|
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
|
|
4
|
-
#
|
|
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
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
local state_json="{
|
|
58
118
|
\"active\": true,
|
|
59
|
-
\"started_at\": \"$
|
|
60
|
-
\"original_prompt\": \"$
|
|
119
|
+
\"started_at\": \"$timestamp\",
|
|
120
|
+
\"original_prompt\": \"$prompt_escaped\",
|
|
61
121
|
\"reinforcement_count\": 0,
|
|
62
|
-
\"last_checked_at\": \"$
|
|
122
|
+
\"last_checked_at\": \"$timestamp\"
|
|
63
123
|
}"
|
|
64
124
|
|
|
65
125
|
# Write state to both local and global locations
|
|
66
|
-
echo "$
|
|
67
|
-
echo "$
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
#
|
|
77
|
-
if echo "$PROMPT_LOWER" | grep -qE '\b(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
#
|
|
85
|
-
if echo "$PROMPT_LOWER" | grep -qE '\b(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
#
|
|
93
|
-
if echo "$PROMPT_LOWER" | grep -qE '\b(
|
|
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": "<
|
|
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 &&
|
|
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. ${
|
|
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 (
|
|
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 -
|
|
276
|
+
[SYSTEM REMINDER - CONTINUATION]
|
|
227
277
|
|
|
228
|
-
Incomplete
|
|
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
|
|
232
|
-
- Do not stop until all
|
|
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" ] && [ "$
|
|
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. $
|
|
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 [ "$
|
|
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 -
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
66
|
-
- Do not stop until all
|
|
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
|