oh-my-claude-sisyphus 1.9.0 → 1.10.2

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 (53) hide show
  1. package/dist/agents/definitions.js +1 -1
  2. package/dist/agents/orchestrator-sisyphus.js +1 -1
  3. package/dist/features/auto-update.d.ts +20 -0
  4. package/dist/features/auto-update.d.ts.map +1 -1
  5. package/dist/features/auto-update.js +35 -0
  6. package/dist/features/auto-update.js.map +1 -1
  7. package/dist/features/builtin-skills/skills.d.ts.map +1 -1
  8. package/dist/features/builtin-skills/skills.js +146 -13
  9. package/dist/features/builtin-skills/skills.js.map +1 -1
  10. package/dist/hooks/bridge.d.ts +1 -1
  11. package/dist/hooks/bridge.d.ts.map +1 -1
  12. package/dist/hooks/bridge.js +97 -1
  13. package/dist/hooks/bridge.js.map +1 -1
  14. package/dist/hooks/index.d.ts +4 -0
  15. package/dist/hooks/index.d.ts.map +1 -1
  16. package/dist/hooks/index.js +12 -0
  17. package/dist/hooks/index.js.map +1 -1
  18. package/dist/hooks/persistent-mode/index.d.ts +40 -0
  19. package/dist/hooks/persistent-mode/index.d.ts.map +1 -0
  20. package/dist/hooks/persistent-mode/index.js +322 -0
  21. package/dist/hooks/persistent-mode/index.js.map +1 -0
  22. package/dist/hooks/plugin-patterns/index.d.ts +107 -0
  23. package/dist/hooks/plugin-patterns/index.d.ts.map +1 -0
  24. package/dist/hooks/plugin-patterns/index.js +286 -0
  25. package/dist/hooks/plugin-patterns/index.js.map +1 -0
  26. package/dist/hooks/preemptive-compaction/index.js +2 -2
  27. package/dist/hooks/preemptive-compaction/index.js.map +1 -1
  28. package/dist/hooks/ralph-verifier/index.d.ts +72 -0
  29. package/dist/hooks/ralph-verifier/index.d.ts.map +1 -0
  30. package/dist/hooks/ralph-verifier/index.js +223 -0
  31. package/dist/hooks/ralph-verifier/index.js.map +1 -0
  32. package/dist/hooks/thinking-block-validator/index.d.ts +3 -34
  33. package/dist/hooks/thinking-block-validator/index.d.ts.map +1 -1
  34. package/dist/hooks/thinking-block-validator/index.js +21 -70
  35. package/dist/hooks/thinking-block-validator/index.js.map +1 -1
  36. package/dist/hooks/ultrawork-state/index.d.ts +60 -0
  37. package/dist/hooks/ultrawork-state/index.d.ts.map +1 -0
  38. package/dist/hooks/ultrawork-state/index.js +207 -0
  39. package/dist/hooks/ultrawork-state/index.js.map +1 -0
  40. package/dist/installer/hooks.d.ts +38 -2
  41. package/dist/installer/hooks.d.ts.map +1 -1
  42. package/dist/installer/hooks.js +682 -8
  43. package/dist/installer/hooks.js.map +1 -1
  44. package/dist/installer/index.d.ts.map +1 -1
  45. package/dist/installer/index.js +126 -10
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/tools/ast-tools.d.ts +3 -3
  48. package/dist/tools/ast-tools.d.ts.map +1 -1
  49. package/dist/tools/ast-tools.js +205 -104
  50. package/dist/tools/ast-tools.js.map +1 -1
  51. package/package.json +1 -1
  52. package/scripts/install.sh +70 -3
  53. package/scripts/uninstall.sh +116 -3
@@ -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,600 @@ 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 verification state (oracle verification)
704
+ VERIFICATION_STATE=""
705
+ if [ -f "$DIRECTORY/.sisyphus/ralph-verification.json" ]; then
706
+ VERIFICATION_STATE=$(cat "$DIRECTORY/.sisyphus/ralph-verification.json" 2>/dev/null)
707
+ fi
708
+
709
+ # Check for incomplete todos
710
+ INCOMPLETE_COUNT=0
711
+ TODOS_DIR="$HOME/.claude/todos"
712
+ if [ -d "$TODOS_DIR" ]; then
713
+ for todo_file in "$TODOS_DIR"/*.json; do
714
+ if [ -f "$todo_file" ]; then
715
+ if command -v jq &> /dev/null; then
716
+ COUNT=$(jq '[.[] | select(.status != "completed" and .status != "cancelled")] | length' "$todo_file" 2>/dev/null || echo "0")
717
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
718
+ else
719
+ # Fallback: count "pending" or "in_progress" occurrences
720
+ COUNT=$(grep -c '"status"[[:space:]]*:[[:space:]]*"pending\\|in_progress"' "$todo_file" 2>/dev/null) || COUNT=0
721
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
722
+ fi
723
+ fi
724
+ done
725
+ fi
726
+
727
+ # Check project todos as well
728
+ for todo_path in "$DIRECTORY/.sisyphus/todos.json" "$DIRECTORY/.claude/todos.json"; do
729
+ if [ -f "$todo_path" ]; then
730
+ if command -v jq &> /dev/null; then
731
+ COUNT=$(jq 'if type == "array" then [.[] | select(.status != "completed" and .status != "cancelled")] | length else 0 end' "$todo_path" 2>/dev/null || echo "0")
732
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
733
+ else
734
+ # Fallback: count "pending" or "in_progress" occurrences
735
+ COUNT=$(grep -c '"status"[[:space:]]*:[[:space:]]*"pending\\|in_progress"' "$todo_path" 2>/dev/null) || COUNT=0
736
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
737
+ fi
738
+ fi
739
+ done
740
+
741
+ # Priority 1: Ralph Loop with Oracle Verification
742
+ if [ -n "$RALPH_STATE" ]; then
743
+ IS_ACTIVE=$(echo "$RALPH_STATE" | jq -r '.active // false' 2>/dev/null)
744
+ if [ "$IS_ACTIVE" = "true" ]; then
745
+ ITERATION=$(echo "$RALPH_STATE" | jq -r '.iteration // 1' 2>/dev/null)
746
+ MAX_ITER=$(echo "$RALPH_STATE" | jq -r '.max_iterations // 10' 2>/dev/null)
747
+ PROMISE=$(echo "$RALPH_STATE" | jq -r '.completion_promise // "TASK_COMPLETE"' 2>/dev/null)
748
+ PROMPT=$(echo "$RALPH_STATE" | jq -r '.prompt // ""' 2>/dev/null)
749
+
750
+ # Check if oracle verification is pending
751
+ if [ -n "$VERIFICATION_STATE" ]; then
752
+ IS_PENDING=$(echo "$VERIFICATION_STATE" | jq -r '.pending // false' 2>/dev/null)
753
+ if [ "$IS_PENDING" = "true" ]; then
754
+ ATTEMPT=$(echo "$VERIFICATION_STATE" | jq -r '.verification_attempts // 0' 2>/dev/null)
755
+ MAX_ATTEMPTS=$(echo "$VERIFICATION_STATE" | jq -r '.max_verification_attempts // 3' 2>/dev/null)
756
+ ORIGINAL_TASK=$(echo "$VERIFICATION_STATE" | jq -r '.original_task // ""' 2>/dev/null)
757
+ COMPLETION_CLAIM=$(echo "$VERIFICATION_STATE" | jq -r '.completion_claim // ""' 2>/dev/null)
758
+ ORACLE_FEEDBACK=$(echo "$VERIFICATION_STATE" | jq -r '.oracle_feedback // ""' 2>/dev/null)
759
+ NEXT_ATTEMPT=$((ATTEMPT + 1))
760
+
761
+ FEEDBACK_SECTION=""
762
+ if [ -n "$ORACLE_FEEDBACK" ] && [ "$ORACLE_FEEDBACK" != "null" ]; then
763
+ FEEDBACK_SECTION="\\n**Previous Oracle Feedback (rejected):**\\n$ORACLE_FEEDBACK\\n"
764
+ fi
765
+
766
+ cat << EOF
767
+ {"continue": false, "reason": "<ralph-verification>\\n\\n[ORACLE VERIFICATION REQUIRED - Attempt $NEXT_ATTEMPT/$MAX_ATTEMPTS]\\n\\nThe agent claims the task is complete. Before accepting, YOU MUST verify with Oracle.\\n\\n**Original Task:**\\n$ORIGINAL_TASK\\n\\n**Completion Claim:**\\n$COMPLETION_CLAIM\\n$FEEDBACK_SECTION\\n## MANDATORY VERIFICATION STEPS\\n\\n1. **Spawn Oracle Agent** for verification:\\n \\\`\\\`\\\`\\n Task(subagent_type=\\"oracle\\", prompt=\\"Verify this task completion claim...\\")\\n \\\`\\\`\\\`\\n\\n2. **Oracle must check:**\\n - Are ALL requirements from the original task met?\\n - Is the implementation complete, not partial?\\n - Are there any obvious bugs or issues?\\n - Does the code compile/run without errors?\\n - Are tests passing (if applicable)?\\n\\n3. **Based on Oracle's response:**\\n - If APPROVED: Output \\\`<oracle-approved>VERIFIED_COMPLETE</oracle-approved>\\\`\\n - If REJECTED: Continue working on the identified issues\\n\\nDO NOT output the completion promise again until Oracle approves.\\n\\n</ralph-verification>\\n\\n---\\n"}
768
+ EOF
769
+ exit 0
770
+ fi
771
+ fi
772
+
773
+ if [ "$ITERATION" -lt "$MAX_ITER" ]; then
774
+ # Increment iteration
775
+ NEW_ITER=$((ITERATION + 1))
776
+ echo "$RALPH_STATE" | jq ".iteration = $NEW_ITER" > "$DIRECTORY/.sisyphus/ralph-state.json" 2>/dev/null
777
+
778
+ cat << EOF
779
+ {"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"}
780
+ EOF
781
+ exit 0
782
+ fi
783
+ fi
784
+ fi
785
+
786
+ # Priority 2: Ultrawork Mode with incomplete todos
787
+ if [ -n "$ULTRAWORK_STATE" ] && [ "$INCOMPLETE_COUNT" -gt 0 ]; then
788
+ # Check if active (with jq fallback)
789
+ IS_ACTIVE=""
790
+ if command -v jq &> /dev/null; then
791
+ IS_ACTIVE=$(echo "$ULTRAWORK_STATE" | jq -r '.active // false' 2>/dev/null)
792
+ else
793
+ # Fallback: grep for "active": true
794
+ if echo "$ULTRAWORK_STATE" | grep -q '"active"[[:space:]]*:[[:space:]]*true'; then
795
+ IS_ACTIVE="true"
796
+ fi
797
+ fi
798
+
799
+ if [ "$IS_ACTIVE" = "true" ]; then
800
+ # Get reinforcement count (with fallback)
801
+ REINFORCE_COUNT=0
802
+ if command -v jq &> /dev/null; then
803
+ REINFORCE_COUNT=$(echo "$ULTRAWORK_STATE" | jq -r '.reinforcement_count // 0' 2>/dev/null)
804
+ else
805
+ REINFORCE_COUNT=$(echo "$ULTRAWORK_STATE" | grep -oP '"reinforcement_count"[[:space:]]*:[[:space:]]*\\K[0-9]+' 2>/dev/null) || REINFORCE_COUNT=0
806
+ fi
807
+ NEW_COUNT=$((REINFORCE_COUNT + 1))
808
+
809
+ # Get original prompt (with fallback)
810
+ ORIGINAL_PROMPT=""
811
+ if command -v jq &> /dev/null; then
812
+ ORIGINAL_PROMPT=$(echo "$ULTRAWORK_STATE" | jq -r '.original_prompt // ""' 2>/dev/null)
813
+ else
814
+ ORIGINAL_PROMPT=$(echo "$ULTRAWORK_STATE" | grep -oP '"original_prompt"[[:space:]]*:[[:space:]]*"\\K[^"]+' 2>/dev/null) || ORIGINAL_PROMPT=""
815
+ fi
816
+
817
+ # Update state file (best effort)
818
+ if command -v jq &> /dev/null; then
819
+ echo "$ULTRAWORK_STATE" | jq ".reinforcement_count = $NEW_COUNT | .last_checked_at = \\"$(date -Iseconds)\\"" > "$DIRECTORY/.sisyphus/ultrawork-state.json" 2>/dev/null
820
+ fi
821
+
822
+ cat << EOF
823
+ {"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"}
824
+ EOF
825
+ exit 0
826
+ fi
827
+ fi
828
+
829
+ # Priority 3: Todo Continuation (baseline)
830
+ if [ "$INCOMPLETE_COUNT" -gt 0 ]; then
831
+ cat << EOF
832
+ {"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"}
833
+ EOF
834
+ exit 0
835
+ fi
836
+
837
+ # No blocking needed
838
+ echo '{"continue": true}'
839
+ exit 0
840
+ `;
841
+ /**
842
+ * Session Start Bash script
843
+ * Restores persistent mode states when a new session starts
844
+ */
845
+ export const SESSION_START_SCRIPT = `#!/bin/bash
846
+ # Sisyphus Session Start Hook
847
+ # Restores persistent mode states and injects context when session starts
848
+
849
+ # Read stdin
850
+ INPUT=$(cat)
851
+
852
+ # Get directory
853
+ DIRECTORY=""
854
+ if command -v jq &> /dev/null; then
855
+ DIRECTORY=$(echo "$INPUT" | jq -r '.directory // ""' 2>/dev/null)
856
+ fi
857
+
858
+ if [ -z "$DIRECTORY" ]; then
859
+ DIRECTORY=$(pwd)
860
+ fi
861
+
862
+ MESSAGES=""
863
+
864
+ # Check for active ultrawork state
865
+ if [ -f "$DIRECTORY/.sisyphus/ultrawork-state.json" ] || [ -f "$HOME/.claude/ultrawork-state.json" ]; then
866
+ if [ -f "$DIRECTORY/.sisyphus/ultrawork-state.json" ]; then
867
+ ULTRAWORK_STATE=$(cat "$DIRECTORY/.sisyphus/ultrawork-state.json" 2>/dev/null)
868
+ else
869
+ ULTRAWORK_STATE=$(cat "$HOME/.claude/ultrawork-state.json" 2>/dev/null)
870
+ fi
871
+
872
+ IS_ACTIVE=$(echo "$ULTRAWORK_STATE" | jq -r '.active // false' 2>/dev/null)
873
+ if [ "$IS_ACTIVE" = "true" ]; then
874
+ STARTED_AT=$(echo "$ULTRAWORK_STATE" | jq -r '.started_at // ""' 2>/dev/null)
875
+ PROMPT=$(echo "$ULTRAWORK_STATE" | jq -r '.original_prompt // ""' 2>/dev/null)
876
+ 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"
877
+ fi
878
+ fi
879
+
880
+ # Check for incomplete todos
881
+ INCOMPLETE_COUNT=0
882
+ TODOS_DIR="$HOME/.claude/todos"
883
+ if [ -d "$TODOS_DIR" ]; then
884
+ for todo_file in "$TODOS_DIR"/*.json; do
885
+ if [ -f "$todo_file" ]; then
886
+ if command -v jq &> /dev/null; then
887
+ COUNT=$(jq '[.[] | select(.status != "completed" and .status != "cancelled")] | length' "$todo_file" 2>/dev/null || echo "0")
888
+ INCOMPLETE_COUNT=$((INCOMPLETE_COUNT + COUNT))
889
+ fi
890
+ fi
891
+ done
892
+ fi
893
+
894
+ if [ "$INCOMPLETE_COUNT" -gt 0 ]; then
895
+ 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"
896
+ fi
897
+
898
+ # Output message if we have any
899
+ if [ -n "$MESSAGES" ]; then
900
+ # Escape for JSON
901
+ MESSAGES_ESCAPED=$(echo "$MESSAGES" | sed 's/"/\\"/g')
902
+ echo "{\"continue\": true, \"message\": \"$MESSAGES_ESCAPED\"}"
903
+ else
904
+ echo '{"continue": true}'
905
+ fi
906
+ exit 0
907
+ `;
908
+ /**
909
+ * Node.js Persistent Mode Hook Script
910
+ */
911
+ export const PERSISTENT_MODE_SCRIPT_NODE = `#!/usr/bin/env node
912
+ // Sisyphus Persistent Mode Hook (Node.js)
913
+ // Unified handler for ultrawork, ralph-loop, and todo continuation
914
+ // Cross-platform: Windows, macOS, Linux
915
+
916
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
917
+ import { join } from 'path';
918
+ import { homedir } from 'os';
919
+
920
+ async function readStdin() {
921
+ const chunks = [];
922
+ for await (const chunk of process.stdin) {
923
+ chunks.push(chunk);
924
+ }
925
+ return Buffer.concat(chunks).toString('utf-8');
926
+ }
927
+
928
+ function readJsonFile(path) {
929
+ try {
930
+ if (!existsSync(path)) return null;
931
+ return JSON.parse(readFileSync(path, 'utf-8'));
932
+ } catch {
933
+ return null;
934
+ }
935
+ }
936
+
937
+ function writeJsonFile(path, data) {
938
+ try {
939
+ writeFileSync(path, JSON.stringify(data, null, 2));
940
+ return true;
941
+ } catch {
942
+ return false;
943
+ }
944
+ }
945
+
946
+ function countIncompleteTodos(todosDir, projectDir) {
947
+ let count = 0;
948
+
949
+ // Check global todos
950
+ if (existsSync(todosDir)) {
951
+ try {
952
+ const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
953
+ for (const file of files) {
954
+ const todos = readJsonFile(join(todosDir, file));
955
+ if (Array.isArray(todos)) {
956
+ count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
957
+ }
958
+ }
959
+ } catch {}
960
+ }
961
+
962
+ // Check project todos
963
+ for (const path of [
964
+ join(projectDir, '.sisyphus', 'todos.json'),
965
+ join(projectDir, '.claude', 'todos.json')
966
+ ]) {
967
+ const todos = readJsonFile(path);
968
+ if (Array.isArray(todos)) {
969
+ count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
970
+ }
971
+ }
972
+
973
+ return count;
974
+ }
975
+
976
+ async function main() {
977
+ try {
978
+ const input = await readStdin();
979
+ let data = {};
980
+ try { data = JSON.parse(input); } catch {}
981
+
982
+ const directory = data.directory || process.cwd();
983
+ const todosDir = join(homedir(), '.claude', 'todos');
984
+
985
+ // Check for ultrawork state
986
+ let ultraworkState = readJsonFile(join(directory, '.sisyphus', 'ultrawork-state.json'))
987
+ || readJsonFile(join(homedir(), '.claude', 'ultrawork-state.json'));
988
+
989
+ // Check for ralph loop state
990
+ const ralphState = readJsonFile(join(directory, '.sisyphus', 'ralph-state.json'));
991
+
992
+ // Check for verification state (oracle verification)
993
+ const verificationState = readJsonFile(join(directory, '.sisyphus', 'ralph-verification.json'));
994
+
995
+ // Count incomplete todos
996
+ const incompleteCount = countIncompleteTodos(todosDir, directory);
997
+
998
+ // Priority 1: Ralph Loop with Oracle Verification
999
+ if (ralphState?.active) {
1000
+ const iteration = ralphState.iteration || 1;
1001
+ const maxIter = ralphState.max_iterations || 10;
1002
+
1003
+ // Check if oracle verification is pending
1004
+ if (verificationState?.pending) {
1005
+ const attempt = (verificationState.verification_attempts || 0) + 1;
1006
+ const maxAttempts = verificationState.max_verification_attempts || 3;
1007
+
1008
+ console.log(JSON.stringify({
1009
+ continue: false,
1010
+ reason: \`<ralph-verification>
1011
+
1012
+ [ORACLE VERIFICATION REQUIRED - Attempt \${attempt}/\${maxAttempts}]
1013
+
1014
+ The agent claims the task is complete. Before accepting, YOU MUST verify with Oracle.
1015
+
1016
+ **Original Task:**
1017
+ \${verificationState.original_task || ralphState.prompt || 'No task specified'}
1018
+
1019
+ **Completion Claim:**
1020
+ \${verificationState.completion_claim || 'Task marked complete'}
1021
+
1022
+ \${verificationState.oracle_feedback ? \`**Previous Oracle Feedback (rejected):**
1023
+ \${verificationState.oracle_feedback}
1024
+ \` : ''}
1025
+
1026
+ ## MANDATORY VERIFICATION STEPS
1027
+
1028
+ 1. **Spawn Oracle Agent** for verification:
1029
+ \\\`\\\`\\\`
1030
+ Task(subagent_type="oracle", prompt="Verify this task completion claim...")
1031
+ \\\`\\\`\\\`
1032
+
1033
+ 2. **Oracle must check:**
1034
+ - Are ALL requirements from the original task met?
1035
+ - Is the implementation complete, not partial?
1036
+ - Are there any obvious bugs or issues?
1037
+ - Does the code compile/run without errors?
1038
+ - Are tests passing (if applicable)?
1039
+
1040
+ 3. **Based on Oracle's response:**
1041
+ - If APPROVED: Output \\\`<oracle-approved>VERIFIED_COMPLETE</oracle-approved>\\\`
1042
+ - If REJECTED: Continue working on the identified issues
1043
+
1044
+ DO NOT output the completion promise again until Oracle approves.
1045
+
1046
+ </ralph-verification>
1047
+
1048
+ ---
1049
+ \`
1050
+ }));
1051
+ return;
1052
+ }
1053
+
1054
+ if (iteration < maxIter) {
1055
+ const newIter = iteration + 1;
1056
+ ralphState.iteration = newIter;
1057
+ writeJsonFile(join(directory, '.sisyphus', 'ralph-state.json'), ralphState);
1058
+
1059
+ console.log(JSON.stringify({
1060
+ continue: false,
1061
+ reason: \`<ralph-loop-continuation>
1062
+
1063
+ [RALPH LOOP - ITERATION \${newIter}/\${maxIter}]
1064
+
1065
+ Your previous attempt did not output the completion promise. The work is NOT done yet.
1066
+
1067
+ CRITICAL INSTRUCTIONS:
1068
+ 1. Review your progress and the original task
1069
+ 2. Check your todo list - are ALL items marked complete?
1070
+ 3. Continue from where you left off
1071
+ 4. When FULLY complete, output: <promise>\${ralphState.completion_promise || 'TASK_COMPLETE'}</promise>
1072
+ 5. Do NOT stop until the task is truly done
1073
+
1074
+ \${ralphState.prompt ? \`Original task: \${ralphState.prompt}\` : ''}
1075
+
1076
+ </ralph-loop-continuation>
1077
+
1078
+ ---
1079
+ \`
1080
+ }));
1081
+ return;
1082
+ }
1083
+ }
1084
+
1085
+ // Priority 2: Ultrawork with incomplete todos
1086
+ if (ultraworkState?.active && incompleteCount > 0) {
1087
+ const newCount = (ultraworkState.reinforcement_count || 0) + 1;
1088
+ ultraworkState.reinforcement_count = newCount;
1089
+ ultraworkState.last_checked_at = new Date().toISOString();
1090
+
1091
+ writeJsonFile(join(directory, '.sisyphus', 'ultrawork-state.json'), ultraworkState);
1092
+
1093
+ console.log(JSON.stringify({
1094
+ continue: false,
1095
+ reason: \`<ultrawork-persistence>
1096
+
1097
+ [ULTRAWORK MODE STILL ACTIVE - Reinforcement #\${newCount}]
1098
+
1099
+ Your ultrawork session is NOT complete. \${incompleteCount} incomplete todos remain.
1100
+
1101
+ REMEMBER THE ULTRAWORK RULES:
1102
+ - **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
1103
+ - **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)
1104
+ - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each
1105
+ - **VERIFY**: Check ALL requirements met before done
1106
+ - **NO Premature Stopping**: ALL TODOs must be complete
1107
+
1108
+ Continue working on the next pending task. DO NOT STOP until all tasks are marked complete.
1109
+
1110
+ \${ultraworkState.original_prompt ? \`Original task: \${ultraworkState.original_prompt}\` : ''}
1111
+
1112
+ </ultrawork-persistence>
1113
+
1114
+ ---
1115
+ \`
1116
+ }));
1117
+ return;
1118
+ }
1119
+
1120
+ // Priority 3: Todo Continuation
1121
+ if (incompleteCount > 0) {
1122
+ console.log(JSON.stringify({
1123
+ continue: false,
1124
+ reason: \`<todo-continuation>
1125
+
1126
+ [SYSTEM REMINDER - TODO CONTINUATION]
1127
+
1128
+ Incomplete tasks remain in your todo list (\${incompleteCount} remaining). Continue working on the next pending task.
1129
+
1130
+ - Proceed without asking for permission
1131
+ - Mark each task complete when finished
1132
+ - Do not stop until all tasks are done
1133
+
1134
+ </todo-continuation>
1135
+
1136
+ ---
1137
+ \`
1138
+ }));
1139
+ return;
1140
+ }
1141
+
1142
+ // No blocking needed
1143
+ console.log(JSON.stringify({ continue: true }));
1144
+ } catch (error) {
1145
+ console.log(JSON.stringify({ continue: true }));
1146
+ }
1147
+ }
1148
+
1149
+ main();
1150
+ `;
1151
+ /**
1152
+ * Node.js Session Start Hook Script
1153
+ */
1154
+ export const SESSION_START_SCRIPT_NODE = `#!/usr/bin/env node
1155
+ // Sisyphus Session Start Hook (Node.js)
1156
+ // Restores persistent mode states when session starts
1157
+ // Cross-platform: Windows, macOS, Linux
1158
+
1159
+ import { existsSync, readFileSync, readdirSync } from 'fs';
1160
+ import { join } from 'path';
1161
+ import { homedir } from 'os';
1162
+
1163
+ async function readStdin() {
1164
+ const chunks = [];
1165
+ for await (const chunk of process.stdin) {
1166
+ chunks.push(chunk);
1167
+ }
1168
+ return Buffer.concat(chunks).toString('utf-8');
1169
+ }
1170
+
1171
+ function readJsonFile(path) {
1172
+ try {
1173
+ if (!existsSync(path)) return null;
1174
+ return JSON.parse(readFileSync(path, 'utf-8'));
1175
+ } catch {
1176
+ return null;
1177
+ }
1178
+ }
1179
+
1180
+ function countIncompleteTodos(todosDir) {
1181
+ let count = 0;
1182
+ if (existsSync(todosDir)) {
1183
+ try {
1184
+ const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
1185
+ for (const file of files) {
1186
+ const todos = readJsonFile(join(todosDir, file));
1187
+ if (Array.isArray(todos)) {
1188
+ count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
1189
+ }
1190
+ }
1191
+ } catch {}
1192
+ }
1193
+ return count;
1194
+ }
1195
+
1196
+ async function main() {
1197
+ try {
1198
+ const input = await readStdin();
1199
+ let data = {};
1200
+ try { data = JSON.parse(input); } catch {}
1201
+
1202
+ const directory = data.directory || process.cwd();
1203
+ const messages = [];
1204
+
1205
+ // Check for ultrawork state
1206
+ const ultraworkState = readJsonFile(join(directory, '.sisyphus', 'ultrawork-state.json'))
1207
+ || readJsonFile(join(homedir(), '.claude', 'ultrawork-state.json'));
1208
+
1209
+ if (ultraworkState?.active) {
1210
+ messages.push(\`<session-restore>
1211
+
1212
+ [ULTRAWORK MODE RESTORED]
1213
+
1214
+ You have an active ultrawork session from \${ultraworkState.started_at}.
1215
+ Original task: \${ultraworkState.original_prompt}
1216
+
1217
+ Continue working in ultrawork mode until all tasks are complete.
1218
+
1219
+ </session-restore>
1220
+
1221
+ ---
1222
+ \`);
1223
+ }
1224
+
1225
+ // Check for incomplete todos
1226
+ const todosDir = join(homedir(), '.claude', 'todos');
1227
+ const incompleteCount = countIncompleteTodos(todosDir);
1228
+
1229
+ if (incompleteCount > 0) {
1230
+ messages.push(\`<session-restore>
1231
+
1232
+ [PENDING TASKS DETECTED]
1233
+
1234
+ You have \${incompleteCount} incomplete tasks from a previous session.
1235
+ Please continue working on these tasks.
1236
+
1237
+ </session-restore>
1238
+
1239
+ ---
1240
+ \`);
1241
+ }
1242
+
1243
+ if (messages.length > 0) {
1244
+ console.log(JSON.stringify({ continue: true, message: messages.join('\\n') }));
1245
+ } else {
1246
+ console.log(JSON.stringify({ continue: true }));
1247
+ }
1248
+ } catch (error) {
1249
+ console.log(JSON.stringify({ continue: true }));
1250
+ }
1251
+ }
1252
+
605
1253
  main();
606
1254
  `;
607
1255
  // =============================================================================
@@ -623,12 +1271,22 @@ export const HOOKS_SETTINGS_CONFIG_BASH = {
623
1271
  ]
624
1272
  }
625
1273
  ],
1274
+ SessionStart: [
1275
+ {
1276
+ hooks: [
1277
+ {
1278
+ type: "command",
1279
+ command: "bash $HOME/.claude/hooks/session-start.sh"
1280
+ }
1281
+ ]
1282
+ }
1283
+ ],
626
1284
  Stop: [
627
1285
  {
628
1286
  hooks: [
629
1287
  {
630
1288
  type: "command",
631
- command: "bash $HOME/.claude/hooks/stop-continuation.sh"
1289
+ command: "bash $HOME/.claude/hooks/persistent-mode.sh"
632
1290
  }
633
1291
  ]
634
1292
  }
@@ -655,14 +1313,26 @@ export const HOOKS_SETTINGS_CONFIG_NODE = {
655
1313
  ]
656
1314
  }
657
1315
  ],
1316
+ SessionStart: [
1317
+ {
1318
+ hooks: [
1319
+ {
1320
+ type: "command",
1321
+ command: isWindows()
1322
+ ? 'node "%USERPROFILE%\\.claude\\hooks\\session-start.mjs"'
1323
+ : 'node "$HOME/.claude/hooks/session-start.mjs"'
1324
+ }
1325
+ ]
1326
+ }
1327
+ ],
658
1328
  Stop: [
659
1329
  {
660
1330
  hooks: [
661
1331
  {
662
1332
  type: "command",
663
1333
  command: isWindows()
664
- ? 'node "%USERPROFILE%\\.claude\\hooks\\stop-continuation.mjs"'
665
- : 'node "$HOME/.claude/hooks/stop-continuation.mjs"'
1334
+ ? 'node "%USERPROFILE%\\.claude\\hooks\\persistent-mode.mjs"'
1335
+ : 'node "$HOME/.claude/hooks/persistent-mode.mjs"'
666
1336
  }
667
1337
  ]
668
1338
  }
@@ -688,14 +1358,18 @@ export const HOOKS_SETTINGS_CONFIG = HOOKS_SETTINGS_CONFIG_BASH;
688
1358
  */
689
1359
  export const HOOK_SCRIPTS_BASH = {
690
1360
  'keyword-detector.sh': KEYWORD_DETECTOR_SCRIPT,
691
- 'stop-continuation.sh': STOP_CONTINUATION_SCRIPT
1361
+ 'stop-continuation.sh': STOP_CONTINUATION_SCRIPT,
1362
+ 'persistent-mode.sh': PERSISTENT_MODE_SCRIPT,
1363
+ 'session-start.sh': SESSION_START_SCRIPT
692
1364
  };
693
1365
  /**
694
1366
  * Node.js hook scripts (Cross-platform)
695
1367
  */
696
1368
  export const HOOK_SCRIPTS_NODE = {
697
1369
  'keyword-detector.mjs': KEYWORD_DETECTOR_SCRIPT_NODE,
698
- 'stop-continuation.mjs': STOP_CONTINUATION_SCRIPT_NODE
1370
+ 'stop-continuation.mjs': STOP_CONTINUATION_SCRIPT_NODE,
1371
+ 'persistent-mode.mjs': PERSISTENT_MODE_SCRIPT_NODE,
1372
+ 'session-start.mjs': SESSION_START_SCRIPT_NODE
699
1373
  };
700
1374
  /**
701
1375
  * Get the appropriate hook scripts for the current platform