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.
Files changed (41) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +7 -4
  4. package/bridge/mcp-server.cjs +73 -10
  5. package/dist/__tests__/hooks.test.js +32 -16
  6. package/dist/__tests__/hooks.test.js.map +1 -1
  7. package/dist/__tests__/installer.test.js +1 -1
  8. package/dist/__tests__/installer.test.js.map +1 -1
  9. package/dist/__tests__/lsp-servers.test.d.ts +2 -0
  10. package/dist/__tests__/lsp-servers.test.d.ts.map +1 -0
  11. package/dist/__tests__/lsp-servers.test.js +118 -0
  12. package/dist/__tests__/lsp-servers.test.js.map +1 -0
  13. package/dist/__tests__/task-continuation.test.d.ts +2 -0
  14. package/dist/__tests__/task-continuation.test.d.ts.map +1 -0
  15. package/dist/__tests__/task-continuation.test.js +740 -0
  16. package/dist/__tests__/task-continuation.test.js.map +1 -0
  17. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  18. package/dist/hooks/persistent-mode/index.js +6 -3
  19. package/dist/hooks/persistent-mode/index.js.map +1 -1
  20. package/dist/hooks/todo-continuation/index.d.ts +111 -3
  21. package/dist/hooks/todo-continuation/index.d.ts.map +1 -1
  22. package/dist/hooks/todo-continuation/index.js +204 -23
  23. package/dist/hooks/todo-continuation/index.js.map +1 -1
  24. package/dist/installer/index.d.ts +1 -1
  25. package/dist/installer/index.d.ts.map +1 -1
  26. package/dist/installer/index.js +1 -1
  27. package/dist/installer/index.js.map +1 -1
  28. package/dist/tools/ast-tools.d.ts.map +1 -1
  29. package/dist/tools/ast-tools.js +20 -6
  30. package/dist/tools/ast-tools.js.map +1 -1
  31. package/dist/tools/lsp/client.d.ts.map +1 -1
  32. package/dist/tools/lsp/client.js +15 -1
  33. package/dist/tools/lsp/client.js.map +1 -1
  34. package/dist/tools/lsp/servers.d.ts.map +1 -1
  35. package/dist/tools/lsp/servers.js +62 -1
  36. package/dist/tools/lsp/servers.js.map +1 -1
  37. package/package.json +1 -1
  38. package/templates/hooks/persistent-mode.mjs +57 -7
  39. package/templates/hooks/persistent-mode.sh +62 -5
  40. package/templates/hooks/stop-continuation.mjs +65 -8
  41. 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" ] && [ "$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
@@ -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
- if [ "$INCOMPLETE_COUNT" -gt 0 ]; then
30
- # Output continuation message
31
- cat << EOF
32
- {"continue": false, "reason": "[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"}
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