oh-my-claude-sisyphus 3.7.14 → 3.7.16
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 +7 -4
- package/bridge/mcp-server.cjs +73 -10
- package/dist/__tests__/hooks.test.js +32 -16
- package/dist/__tests__/hooks.test.js.map +1 -1
- package/dist/__tests__/installer.test.js +1 -1
- 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/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/ast-tools.d.ts.map +1 -1
- package/dist/tools/ast-tools.js +20 -6
- package/dist/tools/ast-tools.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/package.json +1 -1
- 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
|
@@ -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
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
# Checks for incomplete todos and injects continuation prompt
|
|
4
4
|
# Ported from oh-my-opencode's todo-continuation-enforcer
|
|
5
5
|
|
|
6
|
+
# Validate session ID to prevent path traversal attacks
|
|
7
|
+
is_valid_session_id() {
|
|
8
|
+
local id="$1"
|
|
9
|
+
if [ -z "$id" ]; then
|
|
10
|
+
return 1
|
|
11
|
+
fi
|
|
12
|
+
if echo "$id" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$'; then
|
|
13
|
+
return 0
|
|
14
|
+
fi
|
|
15
|
+
return 1
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
# Read stdin
|
|
7
19
|
INPUT=$(cat)
|
|
8
20
|
|
|
@@ -12,6 +24,38 @@ if command -v jq &> /dev/null; then
|
|
|
12
24
|
SESSION_ID=$(echo "$INPUT" | jq -r '.sessionId // .session_id // ""' 2>/dev/null)
|
|
13
25
|
fi
|
|
14
26
|
|
|
27
|
+
# Check for incomplete tasks in new Task system
|
|
28
|
+
TASKS_DIR="$HOME/.claude/tasks"
|
|
29
|
+
TASK_COUNT=0
|
|
30
|
+
JQ_AVAILABLE=false
|
|
31
|
+
if command -v jq &> /dev/null; then
|
|
32
|
+
JQ_AVAILABLE=true
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [ -n "$SESSION_ID" ] && is_valid_session_id "$SESSION_ID" && [ -d "$TASKS_DIR/$SESSION_ID" ]; then
|
|
36
|
+
for task_file in "$TASKS_DIR/$SESSION_ID"/*.json; do
|
|
37
|
+
if [ -f "$task_file" ] && [ "$(basename "$task_file")" != ".lock" ]; then
|
|
38
|
+
if [ "$JQ_AVAILABLE" = "true" ]; then
|
|
39
|
+
STATUS=$(jq -r '.status // "pending"' "$task_file" 2>/dev/null)
|
|
40
|
+
# Match TypeScript isTaskIncomplete(): only pending/in_progress are incomplete
|
|
41
|
+
# 'deleted' and 'completed' are both treated as done
|
|
42
|
+
if [ "$STATUS" = "pending" ] || [ "$STATUS" = "in_progress" ]; then
|
|
43
|
+
TASK_COUNT=$((TASK_COUNT + 1))
|
|
44
|
+
fi
|
|
45
|
+
else
|
|
46
|
+
# Fallback: grep for incomplete status values (pending or in_progress)
|
|
47
|
+
if grep -qE '"status"[[:space:]]*:[[:space:]]*"(pending|in_progress)"' "$task_file" 2>/dev/null; then
|
|
48
|
+
TASK_COUNT=$((TASK_COUNT + 1))
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
if [ "$JQ_AVAILABLE" = "false" ] && [ "$TASK_COUNT" -gt 0 ]; then
|
|
55
|
+
echo "[OMC WARNING] jq not installed - Task counting may be less accurate." >&2
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
|
|
15
59
|
# Check for incomplete todos in the Claude todos directory
|
|
16
60
|
TODOS_DIR="$HOME/.claude/todos"
|
|
17
61
|
if [ -d "$TODOS_DIR" ]; then
|
|
@@ -26,11 +70,20 @@ if [ -d "$TODOS_DIR" ]; then
|
|
|
26
70
|
fi
|
|
27
71
|
done
|
|
28
72
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
73
|
+
# Combine task and todo counts
|
|
74
|
+
TOTAL_INCOMPLETE=$((TASK_COUNT + INCOMPLETE_COUNT))
|
|
75
|
+
|
|
76
|
+
if [ "$TOTAL_INCOMPLETE" -gt 0 ]; then
|
|
77
|
+
# Use Task terminology if we have tasks, otherwise todos
|
|
78
|
+
if [ "$TASK_COUNT" -gt 0 ]; then
|
|
79
|
+
cat << EOF
|
|
80
|
+
{"continue": false, "reason": "[SYSTEM REMINDER - TASK CONTINUATION]\\n\\nIncomplete Tasks remain ($TOTAL_INCOMPLETE 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"}
|
|
33
81
|
EOF
|
|
82
|
+
else
|
|
83
|
+
cat << EOF
|
|
84
|
+
{"continue": false, "reason": "[SYSTEM REMINDER - TODO CONTINUATION]\\n\\nIncomplete tasks remain in your todo list ($TOTAL_INCOMPLETE 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"}
|
|
85
|
+
EOF
|
|
86
|
+
fi
|
|
34
87
|
exit 0
|
|
35
88
|
fi
|
|
36
89
|
fi
|