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.
- mcp_bridge/__init__.py +1 -1
- mcp_bridge/auth/__init__.py +16 -6
- mcp_bridge/auth/cli.py +202 -11
- mcp_bridge/auth/oauth.py +1 -2
- mcp_bridge/auth/openai_oauth.py +4 -7
- mcp_bridge/auth/token_store.py +0 -1
- mcp_bridge/cli/__init__.py +1 -1
- mcp_bridge/cli/install_hooks.py +503 -107
- mcp_bridge/cli/session_report.py +0 -3
- mcp_bridge/config/__init__.py +2 -2
- mcp_bridge/config/hook_config.py +3 -5
- mcp_bridge/config/rate_limits.py +108 -13
- mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
- mcp_bridge/hooks/__init__.py +14 -4
- mcp_bridge/hooks/agent_reminder.py +4 -4
- mcp_bridge/hooks/auto_slash_command.py +5 -5
- mcp_bridge/hooks/budget_optimizer.py +2 -2
- mcp_bridge/hooks/claude_limits_hook.py +114 -0
- mcp_bridge/hooks/comment_checker.py +3 -4
- mcp_bridge/hooks/compaction.py +2 -2
- mcp_bridge/hooks/context.py +2 -1
- mcp_bridge/hooks/context_monitor.py +2 -2
- mcp_bridge/hooks/delegation_policy.py +85 -0
- mcp_bridge/hooks/directory_context.py +3 -3
- mcp_bridge/hooks/edit_recovery.py +3 -2
- mcp_bridge/hooks/edit_recovery_policy.py +49 -0
- mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
- mcp_bridge/hooks/events.py +160 -0
- mcp_bridge/hooks/git_noninteractive.py +4 -4
- mcp_bridge/hooks/keyword_detector.py +8 -10
- mcp_bridge/hooks/manager.py +35 -22
- mcp_bridge/hooks/notification_hook.py +13 -6
- mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
- mcp_bridge/hooks/parallel_enforcer.py +5 -5
- mcp_bridge/hooks/parallel_execution.py +22 -10
- mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
- mcp_bridge/hooks/pre_compact.py +8 -9
- mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
- mcp_bridge/hooks/preemptive_compaction.py +2 -3
- mcp_bridge/hooks/routing_notifications.py +80 -0
- mcp_bridge/hooks/rules_injector.py +11 -19
- mcp_bridge/hooks/session_idle.py +4 -4
- mcp_bridge/hooks/session_notifier.py +4 -4
- mcp_bridge/hooks/session_recovery.py +4 -5
- mcp_bridge/hooks/stravinsky_mode.py +1 -1
- mcp_bridge/hooks/subagent_stop.py +1 -3
- mcp_bridge/hooks/task_validator.py +2 -2
- mcp_bridge/hooks/tmux_manager.py +7 -8
- mcp_bridge/hooks/todo_delegation.py +4 -1
- mcp_bridge/hooks/todo_enforcer.py +180 -10
- mcp_bridge/hooks/truncation_policy.py +37 -0
- mcp_bridge/hooks/truncator.py +1 -2
- mcp_bridge/metrics/cost_tracker.py +115 -0
- mcp_bridge/native_search.py +93 -0
- mcp_bridge/native_watcher.py +118 -0
- mcp_bridge/notifications.py +3 -4
- mcp_bridge/orchestrator/enums.py +11 -0
- mcp_bridge/orchestrator/router.py +165 -0
- mcp_bridge/orchestrator/state.py +32 -0
- mcp_bridge/orchestrator/visualization.py +14 -0
- mcp_bridge/orchestrator/wisdom.py +34 -0
- mcp_bridge/prompts/__init__.py +1 -8
- mcp_bridge/prompts/dewey.py +1 -1
- mcp_bridge/prompts/planner.py +2 -4
- mcp_bridge/prompts/stravinsky.py +53 -31
- mcp_bridge/proxy/__init__.py +0 -0
- mcp_bridge/proxy/client.py +70 -0
- mcp_bridge/proxy/model_server.py +157 -0
- mcp_bridge/routing/__init__.py +43 -0
- mcp_bridge/routing/config.py +250 -0
- mcp_bridge/routing/model_tiers.py +135 -0
- mcp_bridge/routing/provider_state.py +261 -0
- mcp_bridge/routing/task_classifier.py +190 -0
- mcp_bridge/server.py +363 -34
- mcp_bridge/server_tools.py +298 -6
- mcp_bridge/tools/__init__.py +19 -8
- mcp_bridge/tools/agent_manager.py +549 -799
- mcp_bridge/tools/background_tasks.py +13 -17
- mcp_bridge/tools/code_search.py +54 -51
- mcp_bridge/tools/continuous_loop.py +0 -1
- mcp_bridge/tools/dashboard.py +19 -0
- mcp_bridge/tools/find_code.py +296 -0
- mcp_bridge/tools/init.py +1 -0
- mcp_bridge/tools/list_directory.py +42 -0
- mcp_bridge/tools/lsp/__init__.py +8 -8
- mcp_bridge/tools/lsp/manager.py +51 -28
- mcp_bridge/tools/lsp/tools.py +98 -65
- mcp_bridge/tools/model_invoke.py +1047 -152
- mcp_bridge/tools/mux_client.py +75 -0
- mcp_bridge/tools/project_context.py +1 -2
- mcp_bridge/tools/query_classifier.py +132 -49
- mcp_bridge/tools/read_file.py +84 -0
- mcp_bridge/tools/replace.py +45 -0
- mcp_bridge/tools/run_shell_command.py +38 -0
- mcp_bridge/tools/search_enhancements.py +347 -0
- mcp_bridge/tools/semantic_search.py +677 -92
- mcp_bridge/tools/session_manager.py +0 -2
- mcp_bridge/tools/skill_loader.py +0 -1
- mcp_bridge/tools/task_runner.py +5 -7
- mcp_bridge/tools/templates.py +3 -3
- mcp_bridge/tools/tool_search.py +331 -0
- mcp_bridge/tools/write_file.py +29 -0
- mcp_bridge/update_manager.py +33 -37
- mcp_bridge/update_manager_pypi.py +6 -8
- mcp_bridge/utils/cache.py +82 -0
- mcp_bridge/utils/process.py +71 -0
- mcp_bridge/utils/session_state.py +51 -0
- mcp_bridge/utils/truncation.py +76 -0
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/METADATA +84 -35
- stravinsky-0.4.66.dist-info/RECORD +198 -0
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
- stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
- stravinsky_claude_assets/agents/HOOKS.md +437 -0
- stravinsky_claude_assets/agents/code-reviewer.md +210 -0
- stravinsky_claude_assets/agents/comment_checker.md +580 -0
- stravinsky_claude_assets/agents/debugger.md +254 -0
- stravinsky_claude_assets/agents/delphi.md +495 -0
- stravinsky_claude_assets/agents/dewey.md +248 -0
- stravinsky_claude_assets/agents/explore.md +1198 -0
- stravinsky_claude_assets/agents/frontend.md +472 -0
- stravinsky_claude_assets/agents/implementation-lead.md +164 -0
- stravinsky_claude_assets/agents/momus.md +464 -0
- stravinsky_claude_assets/agents/research-lead.md +141 -0
- stravinsky_claude_assets/agents/stravinsky.md +730 -0
- stravinsky_claude_assets/commands/delphi.md +9 -0
- stravinsky_claude_assets/commands/dewey.md +54 -0
- stravinsky_claude_assets/commands/git-master.md +112 -0
- stravinsky_claude_assets/commands/index.md +49 -0
- stravinsky_claude_assets/commands/publish.md +86 -0
- stravinsky_claude_assets/commands/review.md +73 -0
- stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
- stravinsky_claude_assets/commands/str/agent_list.md +56 -0
- stravinsky_claude_assets/commands/str/agent_output.md +92 -0
- stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
- stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
- stravinsky_claude_assets/commands/str/cancel.md +51 -0
- stravinsky_claude_assets/commands/str/clean.md +97 -0
- stravinsky_claude_assets/commands/str/continue.md +38 -0
- stravinsky_claude_assets/commands/str/index.md +199 -0
- stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
- stravinsky_claude_assets/commands/str/search.md +205 -0
- stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
- stravinsky_claude_assets/commands/str/stats.md +71 -0
- stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
- stravinsky_claude_assets/commands/str/unwatch.md +42 -0
- stravinsky_claude_assets/commands/str/watch.md +45 -0
- stravinsky_claude_assets/commands/strav.md +53 -0
- stravinsky_claude_assets/commands/stravinsky.md +292 -0
- stravinsky_claude_assets/commands/verify.md +60 -0
- stravinsky_claude_assets/commands/version.md +5 -0
- stravinsky_claude_assets/hooks/README.md +248 -0
- stravinsky_claude_assets/hooks/comment_checker.py +193 -0
- stravinsky_claude_assets/hooks/context.py +38 -0
- stravinsky_claude_assets/hooks/context_monitor.py +153 -0
- stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
- stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
- stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
- stravinsky_claude_assets/hooks/notification_hook.py +103 -0
- stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
- stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
- stravinsky_claude_assets/hooks/pre_compact.py +123 -0
- stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
- stravinsky_claude_assets/hooks/session_recovery.py +263 -0
- stravinsky_claude_assets/hooks/stop_hook.py +89 -0
- stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
- stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
- stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
- stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
- stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
- stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
- stravinsky_claude_assets/hooks/truncator.py +23 -0
- stravinsky_claude_assets/rules/deployment_safety.md +51 -0
- stravinsky_claude_assets/rules/integration_wiring.md +89 -0
- stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
- stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
- stravinsky_claude_assets/settings.json +152 -0
- stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
- stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
- stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
- stravinsky_claude_assets/task_dependencies.json +34 -0
- stravinsky-0.4.18.dist-info/RECORD +0 -88
- {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())
|