oh-my-claude-sisyphus 1.9.0 → 1.10.0

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 (84) hide show
  1. package/dist/cli/index.js +0 -0
  2. package/dist/features/builtin-skills/skills.d.ts.map +1 -1
  3. package/dist/features/builtin-skills/skills.js +144 -11
  4. package/dist/features/builtin-skills/skills.js.map +1 -1
  5. package/dist/hooks/bridge.d.ts +1 -1
  6. package/dist/hooks/bridge.d.ts.map +1 -1
  7. package/dist/hooks/bridge.js +71 -0
  8. package/dist/hooks/bridge.js.map +1 -1
  9. package/dist/hooks/index.d.ts +4 -0
  10. package/dist/hooks/index.d.ts.map +1 -1
  11. package/dist/hooks/index.js +12 -0
  12. package/dist/hooks/index.js.map +1 -1
  13. package/dist/hooks/persistent-mode/index.d.ts +40 -0
  14. package/dist/hooks/persistent-mode/index.d.ts.map +1 -0
  15. package/dist/hooks/persistent-mode/index.js +200 -0
  16. package/dist/hooks/persistent-mode/index.js.map +1 -0
  17. package/dist/hooks/plugin-patterns/index.d.ts +107 -0
  18. package/dist/hooks/plugin-patterns/index.d.ts.map +1 -0
  19. package/dist/hooks/plugin-patterns/index.js +286 -0
  20. package/dist/hooks/plugin-patterns/index.js.map +1 -0
  21. package/dist/hooks/ralph-verifier/index.d.ts +72 -0
  22. package/dist/hooks/ralph-verifier/index.d.ts.map +1 -0
  23. package/dist/hooks/ralph-verifier/index.js +223 -0
  24. package/dist/hooks/ralph-verifier/index.js.map +1 -0
  25. package/dist/hooks/ultrawork-state/index.d.ts +60 -0
  26. package/dist/hooks/ultrawork-state/index.d.ts.map +1 -0
  27. package/dist/hooks/ultrawork-state/index.js +207 -0
  28. package/dist/hooks/ultrawork-state/index.js.map +1 -0
  29. package/dist/installer/hooks.d.ts +38 -2
  30. package/dist/installer/hooks.d.ts.map +1 -1
  31. package/dist/installer/hooks.js +599 -8
  32. package/dist/installer/hooks.js.map +1 -1
  33. package/dist/installer/index.d.ts.map +1 -1
  34. package/dist/installer/index.js +123 -7
  35. package/dist/installer/index.js.map +1 -1
  36. package/package.json +1 -1
  37. package/dist/agents/model-lists.d.ts +0 -26
  38. package/dist/agents/model-lists.d.ts.map +0 -1
  39. package/dist/agents/model-lists.js +0 -62
  40. package/dist/agents/model-lists.js.map +0 -1
  41. package/dist/auth/index.d.ts +0 -10
  42. package/dist/auth/index.d.ts.map +0 -1
  43. package/dist/auth/index.js +0 -13
  44. package/dist/auth/index.js.map +0 -1
  45. package/dist/auth/manager.d.ts +0 -54
  46. package/dist/auth/manager.d.ts.map +0 -1
  47. package/dist/auth/manager.js +0 -248
  48. package/dist/auth/manager.js.map +0 -1
  49. package/dist/auth/oauth-google.d.ts +0 -47
  50. package/dist/auth/oauth-google.d.ts.map +0 -1
  51. package/dist/auth/oauth-google.js +0 -280
  52. package/dist/auth/oauth-google.js.map +0 -1
  53. package/dist/auth/oauth-openai.d.ts +0 -46
  54. package/dist/auth/oauth-openai.d.ts.map +0 -1
  55. package/dist/auth/oauth-openai.js +0 -264
  56. package/dist/auth/oauth-openai.js.map +0 -1
  57. package/dist/auth/pkce.d.ts +0 -14
  58. package/dist/auth/pkce.d.ts.map +0 -1
  59. package/dist/auth/pkce.js +0 -35
  60. package/dist/auth/pkce.js.map +0 -1
  61. package/dist/auth/storage.d.ts +0 -52
  62. package/dist/auth/storage.d.ts.map +0 -1
  63. package/dist/auth/storage.js +0 -230
  64. package/dist/auth/storage.js.map +0 -1
  65. package/dist/auth/types.d.ts +0 -76
  66. package/dist/auth/types.d.ts.map +0 -1
  67. package/dist/auth/types.js +0 -5
  68. package/dist/auth/types.js.map +0 -1
  69. package/dist/providers/index.d.ts +0 -8
  70. package/dist/providers/index.d.ts.map +0 -1
  71. package/dist/providers/index.js +0 -10
  72. package/dist/providers/index.js.map +0 -1
  73. package/dist/providers/registry.d.ts +0 -29
  74. package/dist/providers/registry.d.ts.map +0 -1
  75. package/dist/providers/registry.js +0 -162
  76. package/dist/providers/registry.js.map +0 -1
  77. package/dist/providers/router.d.ts +0 -40
  78. package/dist/providers/router.d.ts.map +0 -1
  79. package/dist/providers/router.js +0 -88
  80. package/dist/providers/router.js.map +0 -1
  81. package/dist/providers/types.d.ts +0 -92
  82. package/dist/providers/types.d.ts.map +0 -1
  83. package/dist/providers/types.js +0 -27
  84. package/dist/providers/types.js.map +0 -1
@@ -219,11 +219,20 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
219
219
  export const KEYWORD_DETECTOR_SCRIPT = `#!/bin/bash
220
220
  # Sisyphus Keyword Detector Hook
221
221
  # Detects ultrawork/ultrathink/search/analyze keywords and injects enhanced mode messages
222
- # Ported from oh-my-opencode's keyword-detector hook
222
+ # Also activates persistent ultrawork state when ultrawork keyword is detected
223
223
 
224
224
  # Read stdin (JSON input from Claude Code)
225
225
  INPUT=$(cat)
226
226
 
227
+ # Extract directory from input
228
+ DIRECTORY=""
229
+ if command -v jq &> /dev/null; then
230
+ DIRECTORY=$(echo "$INPUT" | jq -r '.directory // ""' 2>/dev/null)
231
+ fi
232
+ if [ -z "$DIRECTORY" ] || [ "$DIRECTORY" = "null" ]; then
233
+ DIRECTORY=$(pwd)
234
+ fi
235
+
227
236
  # Extract the prompt text - try multiple JSON paths
228
237
  PROMPT=""
229
238
  if command -v jq &> /dev/null; then
@@ -255,7 +264,26 @@ PROMPT_NO_CODE=$(echo "$PROMPT" | sed 's/\`\`\`[^\`]*\`\`\`//g' | sed 's/\`[^\`]
255
264
  PROMPT_LOWER=$(echo "$PROMPT_NO_CODE" | tr '[:upper:]' '[:lower:]')
256
265
 
257
266
  # Check for ultrawork keywords (highest priority)
258
- if echo "$PROMPT_LOWER" | grep -qE '\\b(ultrawork|ulw)\\b'; then
267
+ if echo "$PROMPT_LOWER" | grep -qE '\\b(ultrawork|ulw|uw)\\b'; then
268
+ # Create persistent ultrawork state
269
+ mkdir -p "$DIRECTORY/.sisyphus" 2>/dev/null
270
+ mkdir -p "$HOME/.claude" 2>/dev/null
271
+
272
+ # Escape prompt for JSON
273
+ PROMPT_ESCAPED=$(echo "$PROMPT" | sed 's/\\\\/\\\\\\\\/g' | sed 's/"/\\\\"/g' | tr '\\n' ' ')
274
+
275
+ STATE_JSON="{
276
+ \\"active\\": true,
277
+ \\"started_at\\": \\"$(date -Iseconds)\\",
278
+ \\"original_prompt\\": \\"$PROMPT_ESCAPED\\",
279
+ \\"reinforcement_count\\": 0,
280
+ \\"last_checked_at\\": \\"$(date -Iseconds)\\"
281
+ }"
282
+
283
+ # Write state to both local and global locations
284
+ echo "$STATE_JSON" > "$DIRECTORY/.sisyphus/ultrawork-state.json" 2>/dev/null
285
+ echo "$STATE_JSON" > "$HOME/.claude/ultrawork-state.json" 2>/dev/null
286
+
259
287
  # Return ultrawork mode injection
260
288
  cat << 'EOF'
261
289
  {"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"}
@@ -467,6 +495,27 @@ function removeCodeBlocks(text) {
467
495
  .replace(/\`[^\`]+\`/g, '');
468
496
  }
469
497
 
498
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
499
+ import { join } from 'path';
500
+ import { homedir } from 'os';
501
+
502
+ // Create ultrawork state file
503
+ function activateUltraworkState(directory, prompt) {
504
+ const state = {
505
+ active: true,
506
+ started_at: new Date().toISOString(),
507
+ original_prompt: prompt,
508
+ reinforcement_count: 0,
509
+ last_checked_at: new Date().toISOString()
510
+ };
511
+ const localDir = join(directory, '.sisyphus');
512
+ if (!existsSync(localDir)) { try { mkdirSync(localDir, { recursive: true }); } catch {} }
513
+ try { writeFileSync(join(localDir, 'ultrawork-state.json'), JSON.stringify(state, null, 2)); } catch {}
514
+ const globalDir = join(homedir(), '.claude');
515
+ if (!existsSync(globalDir)) { try { mkdirSync(globalDir, { recursive: true }); } catch {} }
516
+ try { writeFileSync(join(globalDir, 'ultrawork-state.json'), JSON.stringify(state, null, 2)); } catch {}
517
+ }
518
+
470
519
  // Main
471
520
  async function main() {
472
521
  try {
@@ -476,6 +525,10 @@ async function main() {
476
525
  return;
477
526
  }
478
527
 
528
+ let data = {};
529
+ try { data = JSON.parse(input); } catch {}
530
+ const directory = data.directory || process.cwd();
531
+
479
532
  const prompt = extractPrompt(input);
480
533
  if (!prompt) {
481
534
  console.log(JSON.stringify({ continue: true }));
@@ -485,7 +538,8 @@ async function main() {
485
538
  const cleanPrompt = removeCodeBlocks(prompt).toLowerCase();
486
539
 
487
540
  // Check for ultrawork keywords (highest priority)
488
- if (/\\b(ultrawork|ulw)\\b/.test(cleanPrompt)) {
541
+ if (/\\b(ultrawork|ulw|uw)\\b/.test(cleanPrompt)) {
542
+ activateUltraworkState(directory, prompt);
489
543
  console.log(JSON.stringify({ continue: true, message: ULTRAWORK_MESSAGE }));
490
544
  return;
491
545
  }
@@ -602,6 +656,517 @@ Incomplete tasks remain in your todo list (\${incompleteCount} remaining). Conti
602
656
  }
603
657
  }
604
658
 
659
+ main();
660
+ `;
661
+ // =============================================================================
662
+ // PERSISTENT MODE HOOK SCRIPTS
663
+ // =============================================================================
664
+ /**
665
+ * Persistent Mode Bash script
666
+ * Enhanced stop hook that handles ultrawork, ralph-loop, and todo continuation
667
+ */
668
+ export const PERSISTENT_MODE_SCRIPT = `#!/bin/bash
669
+ # Sisyphus Persistent Mode Hook
670
+ # Unified handler for ultrawork, ralph-loop, and todo continuation
671
+ # Prevents stopping when work remains incomplete
672
+
673
+ # Read stdin
674
+ INPUT=$(cat)
675
+
676
+ # Get session ID and directory
677
+ SESSION_ID=""
678
+ DIRECTORY=""
679
+ if command -v jq &> /dev/null; then
680
+ SESSION_ID=$(echo "$INPUT" | jq -r '.sessionId // .session_id // ""' 2>/dev/null)
681
+ DIRECTORY=$(echo "$INPUT" | jq -r '.directory // ""' 2>/dev/null)
682
+ fi
683
+
684
+ # Default to current directory
685
+ if [ -z "$DIRECTORY" ]; then
686
+ DIRECTORY=$(pwd)
687
+ fi
688
+
689
+ # Check for active ultrawork state
690
+ ULTRAWORK_STATE=""
691
+ if [ -f "$DIRECTORY/.sisyphus/ultrawork-state.json" ]; then
692
+ ULTRAWORK_STATE=$(cat "$DIRECTORY/.sisyphus/ultrawork-state.json" 2>/dev/null)
693
+ elif [ -f "$HOME/.claude/ultrawork-state.json" ]; then
694
+ ULTRAWORK_STATE=$(cat "$HOME/.claude/ultrawork-state.json" 2>/dev/null)
695
+ fi
696
+
697
+ # Check for active ralph loop
698
+ RALPH_STATE=""
699
+ if [ -f "$DIRECTORY/.sisyphus/ralph-state.json" ]; then
700
+ RALPH_STATE=$(cat "$DIRECTORY/.sisyphus/ralph-state.json" 2>/dev/null)
701
+ fi
702
+
703
+ # Check for incomplete todos
704
+ INCOMPLETE_COUNT=0
705
+ TODOS_DIR="$HOME/.claude/todos"
706
+ if [ -d "$TODOS_DIR" ]; then
707
+ for todo_file in "$TODOS_DIR"/*.json; do
708
+ if [ -f "$todo_file" ]; then
709
+ if command -v jq &> /dev/null; then
710
+ COUNT=$(jq '[.[] | select(.status != "completed" and .status != "cancelled")] | length' "$todo_file" 2>/dev/null || echo "0")
711
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
712
+ else
713
+ # Fallback: count "pending" or "in_progress" occurrences
714
+ COUNT=$(grep -c '"status"[[:space:]]*:[[:space:]]*"pending\\|in_progress"' "$todo_file" 2>/dev/null) || COUNT=0
715
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
716
+ fi
717
+ fi
718
+ done
719
+ fi
720
+
721
+ # Check project todos as well
722
+ for todo_path in "$DIRECTORY/.sisyphus/todos.json" "$DIRECTORY/.claude/todos.json"; do
723
+ if [ -f "$todo_path" ]; then
724
+ if command -v jq &> /dev/null; then
725
+ COUNT=$(jq 'if type == "array" then [.[] | select(.status != "completed" and .status != "cancelled")] | length else 0 end' "$todo_path" 2>/dev/null || echo "0")
726
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
727
+ else
728
+ # Fallback: count "pending" or "in_progress" occurrences
729
+ COUNT=$(grep -c '"status"[[:space:]]*:[[:space:]]*"pending\\|in_progress"' "$todo_path" 2>/dev/null) || COUNT=0
730
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
731
+ fi
732
+ fi
733
+ done
734
+
735
+ # Priority 1: Ralph Loop
736
+ if [ -n "$RALPH_STATE" ]; then
737
+ IS_ACTIVE=$(echo "$RALPH_STATE" | jq -r '.active // false' 2>/dev/null)
738
+ if [ "$IS_ACTIVE" = "true" ]; then
739
+ ITERATION=$(echo "$RALPH_STATE" | jq -r '.iteration // 1' 2>/dev/null)
740
+ MAX_ITER=$(echo "$RALPH_STATE" | jq -r '.max_iterations // 10' 2>/dev/null)
741
+ PROMISE=$(echo "$RALPH_STATE" | jq -r '.completion_promise // "TASK_COMPLETE"' 2>/dev/null)
742
+ PROMPT=$(echo "$RALPH_STATE" | jq -r '.prompt // ""' 2>/dev/null)
743
+
744
+ if [ "$ITERATION" -lt "$MAX_ITER" ]; then
745
+ # Increment iteration
746
+ NEW_ITER=$((ITERATION + 1))
747
+ echo "$RALPH_STATE" | jq ".iteration = $NEW_ITER" > "$DIRECTORY/.sisyphus/ralph-state.json" 2>/dev/null
748
+
749
+ cat << EOF
750
+ {"continue": false, "reason": "<ralph-loop-continuation>\\n\\n[RALPH LOOP - ITERATION $NEW_ITER/$MAX_ITER]\\n\\nYour previous attempt did not output the completion promise. The work is NOT done yet.\\n\\nCRITICAL INSTRUCTIONS:\\n1. Review your progress and the original task\\n2. Check your todo list - are ALL items marked complete?\\n3. Continue from where you left off\\n4. When FULLY complete, output: <promise>$PROMISE</promise>\\n5. Do NOT stop until the task is truly done\\n\\nOriginal task: $PROMPT\\n\\n</ralph-loop-continuation>\\n\\n---\\n"}
751
+ EOF
752
+ exit 0
753
+ fi
754
+ fi
755
+ fi
756
+
757
+ # Priority 2: Ultrawork Mode with incomplete todos
758
+ if [ -n "$ULTRAWORK_STATE" ] && [ "$INCOMPLETE_COUNT" -gt 0 ]; then
759
+ # Check if active (with jq fallback)
760
+ IS_ACTIVE=""
761
+ if command -v jq &> /dev/null; then
762
+ IS_ACTIVE=$(echo "$ULTRAWORK_STATE" | jq -r '.active // false' 2>/dev/null)
763
+ else
764
+ # Fallback: grep for "active": true
765
+ if echo "$ULTRAWORK_STATE" | grep -q '"active"[[:space:]]*:[[:space:]]*true'; then
766
+ IS_ACTIVE="true"
767
+ fi
768
+ fi
769
+
770
+ if [ "$IS_ACTIVE" = "true" ]; then
771
+ # Get reinforcement count (with fallback)
772
+ REINFORCE_COUNT=0
773
+ if command -v jq &> /dev/null; then
774
+ REINFORCE_COUNT=$(echo "$ULTRAWORK_STATE" | jq -r '.reinforcement_count // 0' 2>/dev/null)
775
+ else
776
+ REINFORCE_COUNT=$(echo "$ULTRAWORK_STATE" | grep -oP '"reinforcement_count"[[:space:]]*:[[:space:]]*\\K[0-9]+' 2>/dev/null) || REINFORCE_COUNT=0
777
+ fi
778
+ NEW_COUNT=$((REINFORCE_COUNT + 1))
779
+
780
+ # Get original prompt (with fallback)
781
+ ORIGINAL_PROMPT=""
782
+ if command -v jq &> /dev/null; then
783
+ ORIGINAL_PROMPT=$(echo "$ULTRAWORK_STATE" | jq -r '.original_prompt // ""' 2>/dev/null)
784
+ else
785
+ ORIGINAL_PROMPT=$(echo "$ULTRAWORK_STATE" | grep -oP '"original_prompt"[[:space:]]*:[[:space:]]*"\\K[^"]+' 2>/dev/null) || ORIGINAL_PROMPT=""
786
+ fi
787
+
788
+ # Update state file (best effort)
789
+ if command -v jq &> /dev/null; then
790
+ echo "$ULTRAWORK_STATE" | jq ".reinforcement_count = $NEW_COUNT | .last_checked_at = \\"$(date -Iseconds)\\"" > "$DIRECTORY/.sisyphus/ultrawork-state.json" 2>/dev/null
791
+ fi
792
+
793
+ cat << EOF
794
+ {"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"}
795
+ EOF
796
+ exit 0
797
+ fi
798
+ fi
799
+
800
+ # Priority 3: Todo Continuation (baseline)
801
+ if [ "$INCOMPLETE_COUNT" -gt 0 ]; then
802
+ cat << EOF
803
+ {"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"}
804
+ EOF
805
+ exit 0
806
+ fi
807
+
808
+ # No blocking needed
809
+ echo '{"continue": true}'
810
+ exit 0
811
+ `;
812
+ /**
813
+ * Session Start Bash script
814
+ * Restores persistent mode states when a new session starts
815
+ */
816
+ export const SESSION_START_SCRIPT = `#!/bin/bash
817
+ # Sisyphus Session Start Hook
818
+ # Restores persistent mode states and injects context when session starts
819
+
820
+ # Read stdin
821
+ INPUT=$(cat)
822
+
823
+ # Get directory
824
+ DIRECTORY=""
825
+ if command -v jq &> /dev/null; then
826
+ DIRECTORY=$(echo "$INPUT" | jq -r '.directory // ""' 2>/dev/null)
827
+ fi
828
+
829
+ if [ -z "$DIRECTORY" ]; then
830
+ DIRECTORY=$(pwd)
831
+ fi
832
+
833
+ MESSAGES=""
834
+
835
+ # Check for active ultrawork state
836
+ if [ -f "$DIRECTORY/.sisyphus/ultrawork-state.json" ] || [ -f "$HOME/.claude/ultrawork-state.json" ]; then
837
+ if [ -f "$DIRECTORY/.sisyphus/ultrawork-state.json" ]; then
838
+ ULTRAWORK_STATE=$(cat "$DIRECTORY/.sisyphus/ultrawork-state.json" 2>/dev/null)
839
+ else
840
+ ULTRAWORK_STATE=$(cat "$HOME/.claude/ultrawork-state.json" 2>/dev/null)
841
+ fi
842
+
843
+ IS_ACTIVE=$(echo "$ULTRAWORK_STATE" | jq -r '.active // false' 2>/dev/null)
844
+ if [ "$IS_ACTIVE" = "true" ]; then
845
+ STARTED_AT=$(echo "$ULTRAWORK_STATE" | jq -r '.started_at // ""' 2>/dev/null)
846
+ PROMPT=$(echo "$ULTRAWORK_STATE" | jq -r '.original_prompt // ""' 2>/dev/null)
847
+ MESSAGES="$MESSAGES<session-restore>\\n\\n[ULTRAWORK MODE RESTORED]\\n\\nYou have an active ultrawork session from $STARTED_AT.\\nOriginal task: $PROMPT\\n\\nContinue working in ultrawork mode until all tasks are complete.\\n\\n</session-restore>\\n\\n---\\n\\n"
848
+ fi
849
+ fi
850
+
851
+ # Check for incomplete todos
852
+ INCOMPLETE_COUNT=0
853
+ TODOS_DIR="$HOME/.claude/todos"
854
+ if [ -d "$TODOS_DIR" ]; then
855
+ for todo_file in "$TODOS_DIR"/*.json; do
856
+ if [ -f "$todo_file" ]; then
857
+ if command -v jq &> /dev/null; then
858
+ COUNT=$(jq '[.[] | select(.status != "completed" and .status != "cancelled")] | length' "$todo_file" 2>/dev/null || echo "0")
859
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
860
+ fi
861
+ fi
862
+ done
863
+ fi
864
+
865
+ if [ "$INCOMPLETE_COUNT" -gt 0 ]; then
866
+ MESSAGES="$MESSAGES<session-restore>\\n\\n[PENDING TASKS DETECTED]\\n\\nYou have $INCOMPLETE_COUNT incomplete tasks from a previous session.\\nPlease continue working on these tasks.\\n\\n</session-restore>\\n\\n---\\n\\n"
867
+ fi
868
+
869
+ # Output message if we have any
870
+ if [ -n "$MESSAGES" ]; then
871
+ # Escape for JSON
872
+ MESSAGES_ESCAPED=$(echo "$MESSAGES" | sed 's/"/\\"/g')
873
+ echo "{\"continue\": true, \"message\": \"$MESSAGES_ESCAPED\"}"
874
+ else
875
+ echo '{"continue": true}'
876
+ fi
877
+ exit 0
878
+ `;
879
+ /**
880
+ * Node.js Persistent Mode Hook Script
881
+ */
882
+ export const PERSISTENT_MODE_SCRIPT_NODE = `#!/usr/bin/env node
883
+ // Sisyphus Persistent Mode Hook (Node.js)
884
+ // Unified handler for ultrawork, ralph-loop, and todo continuation
885
+ // Cross-platform: Windows, macOS, Linux
886
+
887
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
888
+ import { join } from 'path';
889
+ import { homedir } from 'os';
890
+
891
+ async function readStdin() {
892
+ const chunks = [];
893
+ for await (const chunk of process.stdin) {
894
+ chunks.push(chunk);
895
+ }
896
+ return Buffer.concat(chunks).toString('utf-8');
897
+ }
898
+
899
+ function readJsonFile(path) {
900
+ try {
901
+ if (!existsSync(path)) return null;
902
+ return JSON.parse(readFileSync(path, 'utf-8'));
903
+ } catch {
904
+ return null;
905
+ }
906
+ }
907
+
908
+ function writeJsonFile(path, data) {
909
+ try {
910
+ writeFileSync(path, JSON.stringify(data, null, 2));
911
+ return true;
912
+ } catch {
913
+ return false;
914
+ }
915
+ }
916
+
917
+ function countIncompleteTodos(todosDir, projectDir) {
918
+ let count = 0;
919
+
920
+ // Check global todos
921
+ if (existsSync(todosDir)) {
922
+ try {
923
+ const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
924
+ for (const file of files) {
925
+ const todos = readJsonFile(join(todosDir, file));
926
+ if (Array.isArray(todos)) {
927
+ count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
928
+ }
929
+ }
930
+ } catch {}
931
+ }
932
+
933
+ // Check project todos
934
+ for (const path of [
935
+ join(projectDir, '.sisyphus', 'todos.json'),
936
+ join(projectDir, '.claude', 'todos.json')
937
+ ]) {
938
+ const todos = readJsonFile(path);
939
+ if (Array.isArray(todos)) {
940
+ count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
941
+ }
942
+ }
943
+
944
+ return count;
945
+ }
946
+
947
+ async function main() {
948
+ try {
949
+ const input = await readStdin();
950
+ let data = {};
951
+ try { data = JSON.parse(input); } catch {}
952
+
953
+ const directory = data.directory || process.cwd();
954
+ const todosDir = join(homedir(), '.claude', 'todos');
955
+
956
+ // Check for ultrawork state
957
+ let ultraworkState = readJsonFile(join(directory, '.sisyphus', 'ultrawork-state.json'))
958
+ || readJsonFile(join(homedir(), '.claude', 'ultrawork-state.json'));
959
+
960
+ // Check for ralph loop state
961
+ const ralphState = readJsonFile(join(directory, '.sisyphus', 'ralph-state.json'));
962
+
963
+ // Count incomplete todos
964
+ const incompleteCount = countIncompleteTodos(todosDir, directory);
965
+
966
+ // Priority 1: Ralph Loop
967
+ if (ralphState?.active) {
968
+ const iteration = ralphState.iteration || 1;
969
+ const maxIter = ralphState.max_iterations || 10;
970
+
971
+ if (iteration < maxIter) {
972
+ const newIter = iteration + 1;
973
+ ralphState.iteration = newIter;
974
+ writeJsonFile(join(directory, '.sisyphus', 'ralph-state.json'), ralphState);
975
+
976
+ console.log(JSON.stringify({
977
+ continue: false,
978
+ reason: \`<ralph-loop-continuation>
979
+
980
+ [RALPH LOOP - ITERATION \${newIter}/\${maxIter}]
981
+
982
+ Your previous attempt did not output the completion promise. The work is NOT done yet.
983
+
984
+ CRITICAL INSTRUCTIONS:
985
+ 1. Review your progress and the original task
986
+ 2. Check your todo list - are ALL items marked complete?
987
+ 3. Continue from where you left off
988
+ 4. When FULLY complete, output: <promise>\${ralphState.completion_promise || 'TASK_COMPLETE'}</promise>
989
+ 5. Do NOT stop until the task is truly done
990
+
991
+ \${ralphState.prompt ? \`Original task: \${ralphState.prompt}\` : ''}
992
+
993
+ </ralph-loop-continuation>
994
+
995
+ ---
996
+ \`
997
+ }));
998
+ return;
999
+ }
1000
+ }
1001
+
1002
+ // Priority 2: Ultrawork with incomplete todos
1003
+ if (ultraworkState?.active && incompleteCount > 0) {
1004
+ const newCount = (ultraworkState.reinforcement_count || 0) + 1;
1005
+ ultraworkState.reinforcement_count = newCount;
1006
+ ultraworkState.last_checked_at = new Date().toISOString();
1007
+
1008
+ writeJsonFile(join(directory, '.sisyphus', 'ultrawork-state.json'), ultraworkState);
1009
+
1010
+ console.log(JSON.stringify({
1011
+ continue: false,
1012
+ reason: \`<ultrawork-persistence>
1013
+
1014
+ [ULTRAWORK MODE STILL ACTIVE - Reinforcement #\${newCount}]
1015
+
1016
+ Your ultrawork session is NOT complete. \${incompleteCount} incomplete todos remain.
1017
+
1018
+ REMEMBER THE ULTRAWORK RULES:
1019
+ - **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
1020
+ - **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)
1021
+ - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each
1022
+ - **VERIFY**: Check ALL requirements met before done
1023
+ - **NO Premature Stopping**: ALL TODOs must be complete
1024
+
1025
+ Continue working on the next pending task. DO NOT STOP until all tasks are marked complete.
1026
+
1027
+ \${ultraworkState.original_prompt ? \`Original task: \${ultraworkState.original_prompt}\` : ''}
1028
+
1029
+ </ultrawork-persistence>
1030
+
1031
+ ---
1032
+ \`
1033
+ }));
1034
+ return;
1035
+ }
1036
+
1037
+ // Priority 3: Todo Continuation
1038
+ if (incompleteCount > 0) {
1039
+ console.log(JSON.stringify({
1040
+ continue: false,
1041
+ reason: \`<todo-continuation>
1042
+
1043
+ [SYSTEM REMINDER - TODO CONTINUATION]
1044
+
1045
+ Incomplete tasks remain in your todo list (\${incompleteCount} remaining). Continue working on the next pending task.
1046
+
1047
+ - Proceed without asking for permission
1048
+ - Mark each task complete when finished
1049
+ - Do not stop until all tasks are done
1050
+
1051
+ </todo-continuation>
1052
+
1053
+ ---
1054
+ \`
1055
+ }));
1056
+ return;
1057
+ }
1058
+
1059
+ // No blocking needed
1060
+ console.log(JSON.stringify({ continue: true }));
1061
+ } catch (error) {
1062
+ console.log(JSON.stringify({ continue: true }));
1063
+ }
1064
+ }
1065
+
1066
+ main();
1067
+ `;
1068
+ /**
1069
+ * Node.js Session Start Hook Script
1070
+ */
1071
+ export const SESSION_START_SCRIPT_NODE = `#!/usr/bin/env node
1072
+ // Sisyphus Session Start Hook (Node.js)
1073
+ // Restores persistent mode states when session starts
1074
+ // Cross-platform: Windows, macOS, Linux
1075
+
1076
+ import { existsSync, readFileSync, readdirSync } from 'fs';
1077
+ import { join } from 'path';
1078
+ import { homedir } from 'os';
1079
+
1080
+ async function readStdin() {
1081
+ const chunks = [];
1082
+ for await (const chunk of process.stdin) {
1083
+ chunks.push(chunk);
1084
+ }
1085
+ return Buffer.concat(chunks).toString('utf-8');
1086
+ }
1087
+
1088
+ function readJsonFile(path) {
1089
+ try {
1090
+ if (!existsSync(path)) return null;
1091
+ return JSON.parse(readFileSync(path, 'utf-8'));
1092
+ } catch {
1093
+ return null;
1094
+ }
1095
+ }
1096
+
1097
+ function countIncompleteTodos(todosDir) {
1098
+ let count = 0;
1099
+ if (existsSync(todosDir)) {
1100
+ try {
1101
+ const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
1102
+ for (const file of files) {
1103
+ const todos = readJsonFile(join(todosDir, file));
1104
+ if (Array.isArray(todos)) {
1105
+ count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
1106
+ }
1107
+ }
1108
+ } catch {}
1109
+ }
1110
+ return count;
1111
+ }
1112
+
1113
+ async function main() {
1114
+ try {
1115
+ const input = await readStdin();
1116
+ let data = {};
1117
+ try { data = JSON.parse(input); } catch {}
1118
+
1119
+ const directory = data.directory || process.cwd();
1120
+ const messages = [];
1121
+
1122
+ // Check for ultrawork state
1123
+ const ultraworkState = readJsonFile(join(directory, '.sisyphus', 'ultrawork-state.json'))
1124
+ || readJsonFile(join(homedir(), '.claude', 'ultrawork-state.json'));
1125
+
1126
+ if (ultraworkState?.active) {
1127
+ messages.push(\`<session-restore>
1128
+
1129
+ [ULTRAWORK MODE RESTORED]
1130
+
1131
+ You have an active ultrawork session from \${ultraworkState.started_at}.
1132
+ Original task: \${ultraworkState.original_prompt}
1133
+
1134
+ Continue working in ultrawork mode until all tasks are complete.
1135
+
1136
+ </session-restore>
1137
+
1138
+ ---
1139
+ \`);
1140
+ }
1141
+
1142
+ // Check for incomplete todos
1143
+ const todosDir = join(homedir(), '.claude', 'todos');
1144
+ const incompleteCount = countIncompleteTodos(todosDir);
1145
+
1146
+ if (incompleteCount > 0) {
1147
+ messages.push(\`<session-restore>
1148
+
1149
+ [PENDING TASKS DETECTED]
1150
+
1151
+ You have \${incompleteCount} incomplete tasks from a previous session.
1152
+ Please continue working on these tasks.
1153
+
1154
+ </session-restore>
1155
+
1156
+ ---
1157
+ \`);
1158
+ }
1159
+
1160
+ if (messages.length > 0) {
1161
+ console.log(JSON.stringify({ continue: true, message: messages.join('\\n') }));
1162
+ } else {
1163
+ console.log(JSON.stringify({ continue: true }));
1164
+ }
1165
+ } catch (error) {
1166
+ console.log(JSON.stringify({ continue: true }));
1167
+ }
1168
+ }
1169
+
605
1170
  main();
606
1171
  `;
607
1172
  // =============================================================================
@@ -623,12 +1188,22 @@ export const HOOKS_SETTINGS_CONFIG_BASH = {
623
1188
  ]
624
1189
  }
625
1190
  ],
1191
+ SessionStart: [
1192
+ {
1193
+ hooks: [
1194
+ {
1195
+ type: "command",
1196
+ command: "bash $HOME/.claude/hooks/session-start.sh"
1197
+ }
1198
+ ]
1199
+ }
1200
+ ],
626
1201
  Stop: [
627
1202
  {
628
1203
  hooks: [
629
1204
  {
630
1205
  type: "command",
631
- command: "bash $HOME/.claude/hooks/stop-continuation.sh"
1206
+ command: "bash $HOME/.claude/hooks/persistent-mode.sh"
632
1207
  }
633
1208
  ]
634
1209
  }
@@ -655,14 +1230,26 @@ export const HOOKS_SETTINGS_CONFIG_NODE = {
655
1230
  ]
656
1231
  }
657
1232
  ],
1233
+ SessionStart: [
1234
+ {
1235
+ hooks: [
1236
+ {
1237
+ type: "command",
1238
+ command: isWindows()
1239
+ ? 'node "%USERPROFILE%\\.claude\\hooks\\session-start.mjs"'
1240
+ : 'node "$HOME/.claude/hooks/session-start.mjs"'
1241
+ }
1242
+ ]
1243
+ }
1244
+ ],
658
1245
  Stop: [
659
1246
  {
660
1247
  hooks: [
661
1248
  {
662
1249
  type: "command",
663
1250
  command: isWindows()
664
- ? 'node "%USERPROFILE%\\.claude\\hooks\\stop-continuation.mjs"'
665
- : 'node "$HOME/.claude/hooks/stop-continuation.mjs"'
1251
+ ? 'node "%USERPROFILE%\\.claude\\hooks\\persistent-mode.mjs"'
1252
+ : 'node "$HOME/.claude/hooks/persistent-mode.mjs"'
666
1253
  }
667
1254
  ]
668
1255
  }
@@ -688,14 +1275,18 @@ export const HOOKS_SETTINGS_CONFIG = HOOKS_SETTINGS_CONFIG_BASH;
688
1275
  */
689
1276
  export const HOOK_SCRIPTS_BASH = {
690
1277
  'keyword-detector.sh': KEYWORD_DETECTOR_SCRIPT,
691
- 'stop-continuation.sh': STOP_CONTINUATION_SCRIPT
1278
+ 'stop-continuation.sh': STOP_CONTINUATION_SCRIPT,
1279
+ 'persistent-mode.sh': PERSISTENT_MODE_SCRIPT,
1280
+ 'session-start.sh': SESSION_START_SCRIPT
692
1281
  };
693
1282
  /**
694
1283
  * Node.js hook scripts (Cross-platform)
695
1284
  */
696
1285
  export const HOOK_SCRIPTS_NODE = {
697
1286
  'keyword-detector.mjs': KEYWORD_DETECTOR_SCRIPT_NODE,
698
- 'stop-continuation.mjs': STOP_CONTINUATION_SCRIPT_NODE
1287
+ 'stop-continuation.mjs': STOP_CONTINUATION_SCRIPT_NODE,
1288
+ 'persistent-mode.mjs': PERSISTENT_MODE_SCRIPT_NODE,
1289
+ 'session-start.mjs': SESSION_START_SCRIPT_NODE
699
1290
  };
700
1291
  /**
701
1292
  * Get the appropriate hook scripts for the current platform