stravinsky 0.4.18__py3-none-any.whl → 0.4.66__py3-none-any.whl

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.

Potentially problematic release.


This version of stravinsky might be problematic. Click here for more details.

Files changed (184) hide show
  1. mcp_bridge/__init__.py +1 -1
  2. mcp_bridge/auth/__init__.py +16 -6
  3. mcp_bridge/auth/cli.py +202 -11
  4. mcp_bridge/auth/oauth.py +1 -2
  5. mcp_bridge/auth/openai_oauth.py +4 -7
  6. mcp_bridge/auth/token_store.py +0 -1
  7. mcp_bridge/cli/__init__.py +1 -1
  8. mcp_bridge/cli/install_hooks.py +503 -107
  9. mcp_bridge/cli/session_report.py +0 -3
  10. mcp_bridge/config/__init__.py +2 -2
  11. mcp_bridge/config/hook_config.py +3 -5
  12. mcp_bridge/config/rate_limits.py +108 -13
  13. mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
  14. mcp_bridge/hooks/__init__.py +14 -4
  15. mcp_bridge/hooks/agent_reminder.py +4 -4
  16. mcp_bridge/hooks/auto_slash_command.py +5 -5
  17. mcp_bridge/hooks/budget_optimizer.py +2 -2
  18. mcp_bridge/hooks/claude_limits_hook.py +114 -0
  19. mcp_bridge/hooks/comment_checker.py +3 -4
  20. mcp_bridge/hooks/compaction.py +2 -2
  21. mcp_bridge/hooks/context.py +2 -1
  22. mcp_bridge/hooks/context_monitor.py +2 -2
  23. mcp_bridge/hooks/delegation_policy.py +85 -0
  24. mcp_bridge/hooks/directory_context.py +3 -3
  25. mcp_bridge/hooks/edit_recovery.py +3 -2
  26. mcp_bridge/hooks/edit_recovery_policy.py +49 -0
  27. mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
  28. mcp_bridge/hooks/events.py +160 -0
  29. mcp_bridge/hooks/git_noninteractive.py +4 -4
  30. mcp_bridge/hooks/keyword_detector.py +8 -10
  31. mcp_bridge/hooks/manager.py +35 -22
  32. mcp_bridge/hooks/notification_hook.py +13 -6
  33. mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
  34. mcp_bridge/hooks/parallel_enforcer.py +5 -5
  35. mcp_bridge/hooks/parallel_execution.py +22 -10
  36. mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
  37. mcp_bridge/hooks/pre_compact.py +8 -9
  38. mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
  39. mcp_bridge/hooks/preemptive_compaction.py +2 -3
  40. mcp_bridge/hooks/routing_notifications.py +80 -0
  41. mcp_bridge/hooks/rules_injector.py +11 -19
  42. mcp_bridge/hooks/session_idle.py +4 -4
  43. mcp_bridge/hooks/session_notifier.py +4 -4
  44. mcp_bridge/hooks/session_recovery.py +4 -5
  45. mcp_bridge/hooks/stravinsky_mode.py +1 -1
  46. mcp_bridge/hooks/subagent_stop.py +1 -3
  47. mcp_bridge/hooks/task_validator.py +2 -2
  48. mcp_bridge/hooks/tmux_manager.py +7 -8
  49. mcp_bridge/hooks/todo_delegation.py +4 -1
  50. mcp_bridge/hooks/todo_enforcer.py +180 -10
  51. mcp_bridge/hooks/truncation_policy.py +37 -0
  52. mcp_bridge/hooks/truncator.py +1 -2
  53. mcp_bridge/metrics/cost_tracker.py +115 -0
  54. mcp_bridge/native_search.py +93 -0
  55. mcp_bridge/native_watcher.py +118 -0
  56. mcp_bridge/notifications.py +3 -4
  57. mcp_bridge/orchestrator/enums.py +11 -0
  58. mcp_bridge/orchestrator/router.py +165 -0
  59. mcp_bridge/orchestrator/state.py +32 -0
  60. mcp_bridge/orchestrator/visualization.py +14 -0
  61. mcp_bridge/orchestrator/wisdom.py +34 -0
  62. mcp_bridge/prompts/__init__.py +1 -8
  63. mcp_bridge/prompts/dewey.py +1 -1
  64. mcp_bridge/prompts/planner.py +2 -4
  65. mcp_bridge/prompts/stravinsky.py +53 -31
  66. mcp_bridge/proxy/__init__.py +0 -0
  67. mcp_bridge/proxy/client.py +70 -0
  68. mcp_bridge/proxy/model_server.py +157 -0
  69. mcp_bridge/routing/__init__.py +43 -0
  70. mcp_bridge/routing/config.py +250 -0
  71. mcp_bridge/routing/model_tiers.py +135 -0
  72. mcp_bridge/routing/provider_state.py +261 -0
  73. mcp_bridge/routing/task_classifier.py +190 -0
  74. mcp_bridge/server.py +363 -34
  75. mcp_bridge/server_tools.py +298 -6
  76. mcp_bridge/tools/__init__.py +19 -8
  77. mcp_bridge/tools/agent_manager.py +549 -799
  78. mcp_bridge/tools/background_tasks.py +13 -17
  79. mcp_bridge/tools/code_search.py +54 -51
  80. mcp_bridge/tools/continuous_loop.py +0 -1
  81. mcp_bridge/tools/dashboard.py +19 -0
  82. mcp_bridge/tools/find_code.py +296 -0
  83. mcp_bridge/tools/init.py +1 -0
  84. mcp_bridge/tools/list_directory.py +42 -0
  85. mcp_bridge/tools/lsp/__init__.py +8 -8
  86. mcp_bridge/tools/lsp/manager.py +51 -28
  87. mcp_bridge/tools/lsp/tools.py +98 -65
  88. mcp_bridge/tools/model_invoke.py +1047 -152
  89. mcp_bridge/tools/mux_client.py +75 -0
  90. mcp_bridge/tools/project_context.py +1 -2
  91. mcp_bridge/tools/query_classifier.py +132 -49
  92. mcp_bridge/tools/read_file.py +84 -0
  93. mcp_bridge/tools/replace.py +45 -0
  94. mcp_bridge/tools/run_shell_command.py +38 -0
  95. mcp_bridge/tools/search_enhancements.py +347 -0
  96. mcp_bridge/tools/semantic_search.py +677 -92
  97. mcp_bridge/tools/session_manager.py +0 -2
  98. mcp_bridge/tools/skill_loader.py +0 -1
  99. mcp_bridge/tools/task_runner.py +5 -7
  100. mcp_bridge/tools/templates.py +3 -3
  101. mcp_bridge/tools/tool_search.py +331 -0
  102. mcp_bridge/tools/write_file.py +29 -0
  103. mcp_bridge/update_manager.py +33 -37
  104. mcp_bridge/update_manager_pypi.py +6 -8
  105. mcp_bridge/utils/cache.py +82 -0
  106. mcp_bridge/utils/process.py +71 -0
  107. mcp_bridge/utils/session_state.py +51 -0
  108. mcp_bridge/utils/truncation.py +76 -0
  109. {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/METADATA +84 -35
  110. stravinsky-0.4.66.dist-info/RECORD +198 -0
  111. {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
  112. stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
  113. stravinsky_claude_assets/agents/HOOKS.md +437 -0
  114. stravinsky_claude_assets/agents/code-reviewer.md +210 -0
  115. stravinsky_claude_assets/agents/comment_checker.md +580 -0
  116. stravinsky_claude_assets/agents/debugger.md +254 -0
  117. stravinsky_claude_assets/agents/delphi.md +495 -0
  118. stravinsky_claude_assets/agents/dewey.md +248 -0
  119. stravinsky_claude_assets/agents/explore.md +1198 -0
  120. stravinsky_claude_assets/agents/frontend.md +472 -0
  121. stravinsky_claude_assets/agents/implementation-lead.md +164 -0
  122. stravinsky_claude_assets/agents/momus.md +464 -0
  123. stravinsky_claude_assets/agents/research-lead.md +141 -0
  124. stravinsky_claude_assets/agents/stravinsky.md +730 -0
  125. stravinsky_claude_assets/commands/delphi.md +9 -0
  126. stravinsky_claude_assets/commands/dewey.md +54 -0
  127. stravinsky_claude_assets/commands/git-master.md +112 -0
  128. stravinsky_claude_assets/commands/index.md +49 -0
  129. stravinsky_claude_assets/commands/publish.md +86 -0
  130. stravinsky_claude_assets/commands/review.md +73 -0
  131. stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
  132. stravinsky_claude_assets/commands/str/agent_list.md +56 -0
  133. stravinsky_claude_assets/commands/str/agent_output.md +92 -0
  134. stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
  135. stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
  136. stravinsky_claude_assets/commands/str/cancel.md +51 -0
  137. stravinsky_claude_assets/commands/str/clean.md +97 -0
  138. stravinsky_claude_assets/commands/str/continue.md +38 -0
  139. stravinsky_claude_assets/commands/str/index.md +199 -0
  140. stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
  141. stravinsky_claude_assets/commands/str/search.md +205 -0
  142. stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
  143. stravinsky_claude_assets/commands/str/stats.md +71 -0
  144. stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
  145. stravinsky_claude_assets/commands/str/unwatch.md +42 -0
  146. stravinsky_claude_assets/commands/str/watch.md +45 -0
  147. stravinsky_claude_assets/commands/strav.md +53 -0
  148. stravinsky_claude_assets/commands/stravinsky.md +292 -0
  149. stravinsky_claude_assets/commands/verify.md +60 -0
  150. stravinsky_claude_assets/commands/version.md +5 -0
  151. stravinsky_claude_assets/hooks/README.md +248 -0
  152. stravinsky_claude_assets/hooks/comment_checker.py +193 -0
  153. stravinsky_claude_assets/hooks/context.py +38 -0
  154. stravinsky_claude_assets/hooks/context_monitor.py +153 -0
  155. stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
  156. stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
  157. stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
  158. stravinsky_claude_assets/hooks/notification_hook.py +103 -0
  159. stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
  160. stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
  161. stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
  162. stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
  163. stravinsky_claude_assets/hooks/pre_compact.py +123 -0
  164. stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
  165. stravinsky_claude_assets/hooks/session_recovery.py +263 -0
  166. stravinsky_claude_assets/hooks/stop_hook.py +89 -0
  167. stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
  168. stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
  169. stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
  170. stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
  171. stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
  172. stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
  173. stravinsky_claude_assets/hooks/truncator.py +23 -0
  174. stravinsky_claude_assets/rules/deployment_safety.md +51 -0
  175. stravinsky_claude_assets/rules/integration_wiring.md +89 -0
  176. stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
  177. stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
  178. stravinsky_claude_assets/settings.json +152 -0
  179. stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
  180. stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
  181. stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
  182. stravinsky_claude_assets/task_dependencies.json +34 -0
  183. stravinsky-0.4.18.dist-info/RECORD +0 -88
  184. {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/WHEEL +0 -0
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Stravinsky Mode Enforcer Hook
4
+
5
+ This PreToolUse hook blocks native file reading tools (Read, Search, Grep, Bash)
6
+ when stravinsky orchestrator mode is active, forcing use of Task tool for native
7
+ subagent delegation.
8
+
9
+ Stravinsky mode is activated by creating a marker file:
10
+ ~/.stravinsky_mode
11
+
12
+ The /stravinsky command should create this file, and it should be
13
+ removed when the task is complete.
14
+
15
+ Exit codes:
16
+ 0 = Allow the tool to execute
17
+ 2 = Block the tool (reason sent via stderr)
18
+ """
19
+
20
+ import json
21
+ import os
22
+ import sys
23
+ from pathlib import Path
24
+
25
+ # Marker file that indicates stravinsky mode is active
26
+ STRAVINSKY_MODE_FILE = Path.home() / ".stravinsky_mode"
27
+
28
+ # Tools to block when in stravinsky mode
29
+ BLOCKED_TOOLS = {
30
+ "Read",
31
+ "Search",
32
+ "Grep",
33
+ "Bash",
34
+ "MultiEdit",
35
+ "Edit",
36
+ }
37
+
38
+ # Tools that are always allowed
39
+ ALLOWED_TOOLS = {
40
+ "TodoRead",
41
+ "TodoWrite",
42
+ "Task", # Native subagent delegation
43
+ "Agent", # MCP agent tools
44
+ "mcp__stravinsky__invoke_gemini", # Direct Gemini invocation
45
+ "mcp__stravinsky__invoke_openai", # Direct OpenAI invocation
46
+ }
47
+
48
+ # Agent routing recommendations
49
+ AGENT_ROUTES = {
50
+ "Read": "explore",
51
+ "Grep": "explore",
52
+ "Search": "explore",
53
+ "Bash": "explore",
54
+ "Edit": "code-reviewer",
55
+ "MultiEdit": "code-reviewer",
56
+ }
57
+
58
+
59
+ def is_stravinsky_mode_active() -> bool:
60
+ """Check if stravinsky orchestrator mode is active."""
61
+ return STRAVINSKY_MODE_FILE.exists()
62
+
63
+
64
+ def read_stravinsky_mode_config() -> dict:
65
+ """Read the stravinsky mode configuration if it exists."""
66
+ if not STRAVINSKY_MODE_FILE.exists():
67
+ return {}
68
+ try:
69
+ return json.loads(STRAVINSKY_MODE_FILE.read_text())
70
+ except (json.JSONDecodeError, IOError):
71
+ return {"active": True}
72
+
73
+
74
+ def main():
75
+ # Read hook input from stdin
76
+ try:
77
+ hook_input = json.loads(sys.stdin.read())
78
+ except json.JSONDecodeError:
79
+ # If we can't parse input, allow the tool
80
+ sys.exit(0)
81
+
82
+ tool_name = hook_input.get("toolName", hook_input.get("tool_name", ""))
83
+ params = hook_input.get("params", {})
84
+
85
+ # Always allow certain tools
86
+ if tool_name in ALLOWED_TOOLS:
87
+ sys.exit(0)
88
+
89
+ # Check if stravinsky mode is active
90
+ if not is_stravinsky_mode_active():
91
+ # Not in stravinsky mode, allow all tools
92
+ sys.exit(0)
93
+
94
+ config = read_stravinsky_mode_config()
95
+
96
+ # Check if this tool should be blocked
97
+ if tool_name in BLOCKED_TOOLS:
98
+ # Determine which agent to delegate to
99
+ agent = AGENT_ROUTES.get(tool_name, "explore")
100
+
101
+ # Get tool context for better messaging
102
+ context = ""
103
+ if tool_name == "Grep":
104
+ pattern = params.get("pattern", "")
105
+ context = f" (searching for '{pattern[:30]}')"
106
+ elif tool_name == "Read":
107
+ file_path = params.get("file_path", "")
108
+ context = f" (reading {os.path.basename(file_path)})" if file_path else ""
109
+
110
+ # User-friendly delegation message
111
+ print(f"🎭 {agent}('Delegating {tool_name}{context}')", file=sys.stderr)
112
+
113
+ # Block the tool and tell Claude why
114
+ reason = f"""⚠️ STRAVINSKY MODE ACTIVE - {tool_name} BLOCKED
115
+
116
+ You are in Stravinsky orchestrator mode. Native tools are disabled.
117
+
118
+ Instead of using {tool_name}, you MUST use Task tool for native subagent delegation:
119
+ - Task(subagent_type="explore", ...) for file reading/searching
120
+ - Task(subagent_type="dewey", ...) for documentation research
121
+ - Task(subagent_type="code-reviewer", ...) for code analysis
122
+ - Task(subagent_type="debugger", ...) for error investigation
123
+ - Task(subagent_type="frontend", ...) for UI/UX work
124
+ - Task(subagent_type="delphi", ...) for strategic architecture decisions
125
+
126
+ Example:
127
+ Task(
128
+ subagent_type="explore",
129
+ prompt="Read and analyze the authentication module",
130
+ description="Analyze auth"
131
+ )
132
+
133
+ To exit stravinsky mode, run:
134
+ rm ~/.stravinsky_mode
135
+ """
136
+ # Send reason to stderr (Claude sees this)
137
+ print(reason, file=sys.stderr)
138
+ # Exit with code 2 to block the tool
139
+ sys.exit(2)
140
+
141
+ # Tool not in block list, allow it
142
+ sys.exit(0)
143
+
144
+
145
+ if __name__ == "__main__":
146
+ main()
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SubagentStop hook: Handler for agent/subagent completion events.
4
+
5
+ Fires when a Claude Code subagent (Task tool) finishes to:
6
+ 1. Output completion status messages
7
+ 2. Verify agent produced expected output
8
+ 3. Block completion if critical validation fails
9
+ 4. Integrate with TODO tracking
10
+
11
+ Exit codes:
12
+ 0 = Allow completion
13
+ 2 = Block completion (force continuation)
14
+ """
15
+
16
+ import json
17
+ import sys
18
+ from pathlib import Path
19
+ from typing import Optional, Tuple
20
+
21
+
22
+ STRAVINSKY_MODE_FILE = Path.home() / ".stravinsky_mode"
23
+
24
+
25
+ def is_stravinsky_mode() -> bool:
26
+ """Check if stravinsky mode is active."""
27
+ return STRAVINSKY_MODE_FILE.exists()
28
+
29
+
30
+ def extract_subagent_info(hook_input: dict) -> Tuple[str, str, str]:
31
+ """
32
+ Extract subagent information from hook input.
33
+
34
+ Returns: (agent_type, description, status)
35
+ """
36
+ # Try to get from tool parameters or response
37
+ params = hook_input.get("tool_input", hook_input.get("params", {}))
38
+ response = hook_input.get("tool_response", "")
39
+
40
+ agent_type = params.get("subagent_type", "unknown")
41
+ description = params.get("description", "")[:50]
42
+
43
+ # Determine status from response
44
+ status = "completed"
45
+ response_lower = response.lower() if isinstance(response, str) else ""
46
+ if "error" in response_lower or "failed" in response_lower:
47
+ status = "failed"
48
+ elif "timeout" in response_lower:
49
+ status = "timeout"
50
+
51
+ return agent_type, description, status
52
+
53
+
54
+ def format_completion_message(agent_type: str, description: str, status: str) -> str:
55
+ """Format user-friendly completion message."""
56
+ icon = "✓" if status == "completed" else "✗"
57
+ return f"{icon} Subagent {agent_type} {status}: {description}"
58
+
59
+
60
+ def should_block(status: str, agent_type: str) -> bool:
61
+ """
62
+ Determine if we should block completion.
63
+
64
+ Block if:
65
+ - Agent failed AND stravinsky mode active AND critical agent type
66
+ """
67
+ if status != "completed" and is_stravinsky_mode():
68
+ critical_agents = {"delphi", "code-reviewer", "debugger"}
69
+ if agent_type in critical_agents:
70
+ return True
71
+ return False
72
+
73
+
74
+ def main():
75
+ """Main hook entry point."""
76
+ try:
77
+ hook_input = json.load(sys.stdin)
78
+ except (json.JSONDecodeError, EOFError):
79
+ return 0
80
+
81
+ # Extract subagent info
82
+ agent_type, description, status = extract_subagent_info(hook_input)
83
+
84
+ # Output completion message
85
+ message = format_completion_message(agent_type, description, status)
86
+ print(message, file=sys.stderr)
87
+
88
+ # Check if we should block
89
+ if should_block(status, agent_type):
90
+ print(f"\n⚠️ CRITICAL SUBAGENT FAILURE - {agent_type} failed", file=sys.stderr)
91
+ print("Review the error and retry or delegate to delphi.", file=sys.stderr)
92
+ return 2
93
+
94
+ return 0
95
+
96
+
97
+ if __name__ == "__main__":
98
+ sys.exit(main())
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ UserPromptSubmit hook: Todo Continuation Enforcer
4
+
5
+ Checks if there are incomplete todos (in_progress or pending) and injects
6
+ a reminder to continue working on them before starting new work.
7
+
8
+ Aligned with oh-my-opencode's [SYSTEM REMINDER - TODO CONTINUATION] pattern.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+
16
+
17
+ def get_todo_state() -> dict:
18
+ """Try to get current todo state from Claude Code session or local cache."""
19
+ # Claude Code stores todo state - we can check via session files
20
+ # For now, we'll use a simple file-based approach
21
+ cwd = Path(os.environ.get("CLAUDE_CWD", "."))
22
+ todo_cache = cwd / ".claude" / "todo_state.json"
23
+
24
+ if todo_cache.exists():
25
+ try:
26
+ return json.loads(todo_cache.read_text())
27
+ except Exception:
28
+ pass
29
+
30
+ return {"todos": []}
31
+
32
+
33
+ def main():
34
+ try:
35
+ data = json.load(sys.stdin)
36
+ prompt = data.get("prompt", "")
37
+ except Exception:
38
+ return 0
39
+
40
+ # Get current todo state
41
+ state = get_todo_state()
42
+ todos = state.get("todos", [])
43
+
44
+ if not todos:
45
+ # No todos tracked, pass through
46
+ print(prompt)
47
+ return 0
48
+
49
+ # Count incomplete todos
50
+ in_progress = [t for t in todos if t.get("status") == "in_progress"]
51
+ pending = [t for t in todos if t.get("status") == "pending"]
52
+
53
+ if not in_progress and not pending:
54
+ # All todos complete, pass through
55
+ print(prompt)
56
+ return 0
57
+
58
+ # Build AGGRESSIVE reminder with RALPH loop activation
59
+ reminder_parts = [
60
+ "",
61
+ "=" * 80,
62
+ "🔄 RALPH LOOP: INCOMPLETE WORK DETECTED",
63
+ "=" * 80,
64
+ "",
65
+ ]
66
+
67
+ if in_progress:
68
+ reminder_parts.append(f"📌 IN_PROGRESS ({len(in_progress)} items):")
69
+ for t in in_progress:
70
+ reminder_parts.append(f" • {t.get('content', 'Unknown task')}")
71
+ reminder_parts.append("")
72
+
73
+ if pending:
74
+ reminder_parts.append(f"📋 PENDING ({len(pending)} items):")
75
+ for t in pending[:5]: # Show max 5 pending
76
+ reminder_parts.append(f" • {t.get('content', 'Unknown task')}")
77
+ if len(pending) > 5:
78
+ reminder_parts.append(f" ... and {len(pending) - 5} more")
79
+ reminder_parts.append("")
80
+
81
+ reminder_parts.extend(
82
+ [
83
+ "⚡ MANDATORY CONTINUATION PROTOCOL:",
84
+ "",
85
+ "YOU MUST CONTINUE WORKING IMMEDIATELY. DO NOT WAIT FOR USER INPUT.",
86
+ "",
87
+ "1. If IN_PROGRESS todo exists → Complete it NOW",
88
+ "2. If blocked → Explain why and mark as pending, move to next",
89
+ "3. Pick next PENDING todo → Mark as in_progress → Execute",
90
+ "4. Repeat until ALL todos are completed",
91
+ "",
92
+ "🚫 DO NOT:",
93
+ ' - Ask user "Should I continue?" (YES, ALWAYS CONTINUE)',
94
+ " - Wait for user confirmation (CONTINUE AUTOMATICALLY)",
95
+ " - Stop with pending work (COMPLETE EVERYTHING)",
96
+ " - Start new work before finishing todos (FINISH FIRST)",
97
+ "",
98
+ "THIS IS AN AUTO-CONTINUATION. PROCEED IMMEDIATELY.",
99
+ "",
100
+ "=" * 80,
101
+ "",
102
+ ]
103
+ )
104
+
105
+ reminder = "\n".join(reminder_parts)
106
+ print(reminder + prompt)
107
+ return 0
108
+
109
+
110
+ if __name__ == "__main__":
111
+ sys.exit(main())
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PostToolUse hook for TodoWrite: CRITICAL parallel execution enforcer.
4
+
5
+ This hook fires AFTER TodoWrite completes. If there are 2+ pending items,
6
+ it outputs a STRONG reminder that Task tool must be used for delegation.
7
+
8
+ Exit code 2 is used to signal a HARD BLOCK - Claude should see this as
9
+ a failure condition requiring immediate correction.
10
+
11
+ NOTE: This hook enforces the NATIVE SUBAGENT PATTERN.
12
+ Use Task() tool for Claude Code native agents, not agent_spawn (legacy MCP tool).
13
+
14
+ Works in tandem with:
15
+ - parallel_execution.py (UserPromptSubmit): Pre-emptive instruction injection
16
+ - stravinsky_mode.py (PreToolUse): Hard blocking of Read/Grep/Bash tools
17
+ """
18
+
19
+ import json
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ # Check if stravinsky mode is active (hard blocking enabled)
24
+ STRAVINSKY_MODE_FILE = Path.home() / ".stravinsky_mode"
25
+
26
+
27
+ def is_stravinsky_mode():
28
+ """Check if hard blocking mode is active."""
29
+ return STRAVINSKY_MODE_FILE.exists()
30
+
31
+
32
+ def main():
33
+ # Read hook input from stdin
34
+ try:
35
+ hook_input = json.load(sys.stdin)
36
+ except (json.JSONDecodeError, EOFError):
37
+ return 0
38
+
39
+ tool_name = hook_input.get("tool_name", "")
40
+
41
+ if tool_name != "TodoWrite":
42
+ return 0
43
+
44
+ # Get the todos that were just written
45
+ tool_input = hook_input.get("tool_input", {})
46
+ todos = tool_input.get("todos", [])
47
+
48
+ # Count pending todos
49
+ pending_count = sum(1 for t in todos if t.get("status") == "pending")
50
+
51
+ if pending_count < 2:
52
+ return 0
53
+
54
+ # Check if stravinsky mode is active
55
+ stravinsky_active = is_stravinsky_mode()
56
+
57
+ # CRITICAL: Output urgent reminder for parallel delegation via Task tool
58
+ # Native subagent pattern: Always use Task() for delegation
59
+ mode_warning = ""
60
+ if stravinsky_active:
61
+ mode_warning = """
62
+ ⚠️ STRAVINSKY MODE ACTIVE - Direct tools (Read, Grep, Bash) are BLOCKED.
63
+ You MUST use Task(subagent_type="explore", ...) for ALL file operations.
64
+ """
65
+
66
+ error_message = f"""
67
+ 🚨 PARALLEL DELEGATION REQUIRED 🚨
68
+
69
+ TodoWrite created {pending_count} pending items.
70
+ {mode_warning}
71
+ You MUST delegate to Task tool for ALL independent TODOs in THIS SAME RESPONSE.
72
+
73
+ Required pattern (IMMEDIATELY after this message):
74
+ Task(subagent_type="explore", prompt="TODO 1...", description="TODO 1")
75
+ Task(subagent_type="dewey", prompt="TODO 2...", description="TODO 2")
76
+ Task(subagent_type="code-reviewer", prompt="TODO 3...", description="TODO 3")
77
+ ...
78
+
79
+ DO NOT:
80
+ - End your response without Task() calls
81
+ - Mark TODOs in_progress before delegating
82
+ - Use Read/Grep/Bash directly (BLOCKED in stravinsky mode)
83
+ - Work on TODOs sequentially yourself
84
+
85
+ Your NEXT action MUST be multiple Task() calls, one for each independent TODO.
86
+ Task tool returns results directly - synthesize and mark complete.
87
+ """
88
+ print(error_message)
89
+
90
+ # Exit code 2 = HARD BLOCK in stravinsky mode
91
+ # Exit code 1 = WARNING otherwise
92
+ return 2 if stravinsky_active else 1
93
+
94
+
95
+ if __name__ == "__main__":
96
+ sys.exit(main())