stravinsky 0.2.67__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 +112 -11
- 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/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/__init__.py +2 -2
- mcp_bridge/config/hook_config.py +247 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +317 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
- mcp_bridge/hooks/__init__.py +19 -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 +43 -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/tool_messaging.py +113 -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 +150 -0
- 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 +542 -59
- mcp_bridge/server_tools.py +738 -6
- mcp_bridge/tools/__init__.py +40 -25
- mcp_bridge/tools/agent_manager.py +616 -697
- mcp_bridge/tools/background_tasks.py +13 -17
- mcp_bridge/tools/code_search.py +70 -53
- 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 +12 -5
- mcp_bridge/tools/lsp/manager.py +471 -0
- mcp_bridge/tools/lsp/tools.py +723 -207
- mcp_bridge/tools/model_invoke.py +1195 -273
- mcp_bridge/tools/mux_client.py +75 -0
- mcp_bridge/tools/project_context.py +1 -2
- mcp_bridge/tools/query_classifier.py +406 -0
- 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 +3627 -0
- 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 +585 -0
- mcp_bridge/update_manager_pypi.py +297 -0
- 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.66.dist-info/METADATA +517 -0
- stravinsky-0.4.66.dist-info/RECORD +198 -0
- {stravinsky-0.2.67.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.2.67.dist-info/METADATA +0 -284
- stravinsky-0.2.67.dist-info/RECORD +0 -76
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0.0",
|
|
3
|
+
"manifest_version": "0.3.9",
|
|
4
|
+
"description": "Stravinsky skills for Claude Code command integration",
|
|
5
|
+
"generated_date": "2026-01-08T23:53:08.837988Z",
|
|
6
|
+
"skills": {
|
|
7
|
+
"commit.md": {
|
|
8
|
+
"file_path": "commit.md",
|
|
9
|
+
"description": "Git Master - Intelligent atomic commit orchestration",
|
|
10
|
+
"checksum": "f09aee4cc46e",
|
|
11
|
+
"lines_of_code": 531,
|
|
12
|
+
"updatable": true,
|
|
13
|
+
"priority": "medium"
|
|
14
|
+
},
|
|
15
|
+
"delphi.md": {
|
|
16
|
+
"file_path": "delphi.md",
|
|
17
|
+
"description": "Strategic advisor for architecture and debugging",
|
|
18
|
+
"checksum": "46ce352164a5",
|
|
19
|
+
"lines_of_code": 5,
|
|
20
|
+
"updatable": true,
|
|
21
|
+
"priority": "medium"
|
|
22
|
+
},
|
|
23
|
+
"dewey.md": {
|
|
24
|
+
"file_path": "dewey.md",
|
|
25
|
+
"description": "Research librarian for documentation and examples",
|
|
26
|
+
"checksum": "de4e41fc0e08",
|
|
27
|
+
"lines_of_code": 54,
|
|
28
|
+
"updatable": true,
|
|
29
|
+
"priority": "medium"
|
|
30
|
+
},
|
|
31
|
+
"index.md": {
|
|
32
|
+
"file_path": "str/index.md",
|
|
33
|
+
"description": "Index project for semantic search",
|
|
34
|
+
"checksum": "a37c570b70a5",
|
|
35
|
+
"lines_of_code": 199,
|
|
36
|
+
"updatable": true,
|
|
37
|
+
"priority": "medium"
|
|
38
|
+
},
|
|
39
|
+
"publish.md": {
|
|
40
|
+
"file_path": "publish.md",
|
|
41
|
+
"description": "Publish to PyPI with version bump",
|
|
42
|
+
"checksum": "9e6ed392ebc4",
|
|
43
|
+
"lines_of_code": 66,
|
|
44
|
+
"updatable": true,
|
|
45
|
+
"priority": "medium"
|
|
46
|
+
},
|
|
47
|
+
"review.md": {
|
|
48
|
+
"file_path": "review.md",
|
|
49
|
+
"description": "Code review recent changes",
|
|
50
|
+
"checksum": "47274e796826",
|
|
51
|
+
"lines_of_code": 67,
|
|
52
|
+
"updatable": true,
|
|
53
|
+
"priority": "medium"
|
|
54
|
+
},
|
|
55
|
+
"search.md": {
|
|
56
|
+
"file_path": "str/search.md",
|
|
57
|
+
"description": "Stravinsky skill",
|
|
58
|
+
"checksum": "eb092d567333",
|
|
59
|
+
"lines_of_code": 205,
|
|
60
|
+
"updatable": true,
|
|
61
|
+
"priority": "medium"
|
|
62
|
+
},
|
|
63
|
+
"start_filewatch.md": {
|
|
64
|
+
"file_path": "str/start_filewatch.md",
|
|
65
|
+
"description": "Stravinsky skill",
|
|
66
|
+
"checksum": "23ca1f6a1999",
|
|
67
|
+
"lines_of_code": 136,
|
|
68
|
+
"updatable": true,
|
|
69
|
+
"priority": "medium"
|
|
70
|
+
},
|
|
71
|
+
"stats.md": {
|
|
72
|
+
"file_path": "str/stats.md",
|
|
73
|
+
"description": "Stravinsky skill",
|
|
74
|
+
"checksum": "017f4fc6d099",
|
|
75
|
+
"lines_of_code": 71,
|
|
76
|
+
"updatable": true,
|
|
77
|
+
"priority": "medium"
|
|
78
|
+
},
|
|
79
|
+
"stop_filewatch.md": {
|
|
80
|
+
"file_path": "str/stop_filewatch.md",
|
|
81
|
+
"description": "Stravinsky skill",
|
|
82
|
+
"checksum": "dbe92100d0ba",
|
|
83
|
+
"lines_of_code": 89,
|
|
84
|
+
"updatable": true,
|
|
85
|
+
"priority": "medium"
|
|
86
|
+
},
|
|
87
|
+
"cancel-loop.md": {
|
|
88
|
+
"file_path": "strav/cancel-loop.md",
|
|
89
|
+
"description": "Cancel active continuation loop",
|
|
90
|
+
"checksum": "d811ea8bb0e9",
|
|
91
|
+
"lines_of_code": 128,
|
|
92
|
+
"updatable": true,
|
|
93
|
+
"priority": "medium"
|
|
94
|
+
},
|
|
95
|
+
"loop.md": {
|
|
96
|
+
"file_path": "strav/loop.md",
|
|
97
|
+
"description": "Continuation loop for iterative execution",
|
|
98
|
+
"checksum": "defc1ae0aae4",
|
|
99
|
+
"lines_of_code": 193,
|
|
100
|
+
"updatable": true,
|
|
101
|
+
"priority": "medium"
|
|
102
|
+
},
|
|
103
|
+
"strav.md": {
|
|
104
|
+
"file_path": "strav.md",
|
|
105
|
+
"description": "Stravinsky Orchestrator - Parallel agent execution",
|
|
106
|
+
"checksum": "9c9969cf8c09",
|
|
107
|
+
"lines_of_code": 216,
|
|
108
|
+
"updatable": true,
|
|
109
|
+
"priority": "medium"
|
|
110
|
+
},
|
|
111
|
+
"verify.md": {
|
|
112
|
+
"file_path": "verify.md",
|
|
113
|
+
"description": "Post-implementation verification",
|
|
114
|
+
"checksum": "87894579d5ec",
|
|
115
|
+
"lines_of_code": 60,
|
|
116
|
+
"updatable": true,
|
|
117
|
+
"priority": "medium"
|
|
118
|
+
},
|
|
119
|
+
"version.md": {
|
|
120
|
+
"file_path": "version.md",
|
|
121
|
+
"description": "Stravinsky skill",
|
|
122
|
+
"checksum": "1cf41d5d28da",
|
|
123
|
+
"lines_of_code": 5,
|
|
124
|
+
"updatable": true,
|
|
125
|
+
"priority": "medium"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"type": "command",
|
|
48
48
|
"command": "python3 ~/.claude/hooks/stravinsky_mode.py",
|
|
49
49
|
"description": "Hard blocking of direct tools in stravinsky mode"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "python3 ~/.claude/hooks/pre_tool/agent_spawn_validator.py",
|
|
54
|
+
"description": "Blocks sequential tool usage when parallel delegation is required"
|
|
50
55
|
}
|
|
51
56
|
]
|
|
52
57
|
}
|
|
@@ -111,6 +116,11 @@
|
|
|
111
116
|
"type": "command",
|
|
112
117
|
"command": "python3 ~/.claude/hooks/todo_delegation.py",
|
|
113
118
|
"description": "Parallel execution enforcer after TodoWrite"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"type": "command",
|
|
122
|
+
"command": "python3 ~/.claude/hooks/post_tool/parallel_validation.py",
|
|
123
|
+
"description": "Tracks pending tasks for hard enforcement"
|
|
114
124
|
}
|
|
115
125
|
]
|
|
116
126
|
}
|
|
@@ -139,7 +149,9 @@
|
|
|
139
149
|
"execution_control": {
|
|
140
150
|
"parallel_execution.py": "Detects implementation tasks and injects parallel execution instructions before response generation. Activates stravinsky mode on /stravinsky invocation.",
|
|
141
151
|
"stravinsky_mode.py": "Blocks native file tools (Read, Grep, Bash, Edit) when stravinsky mode is active, forcing Task delegation.",
|
|
142
|
-
"todo_delegation.py": "Hard blocks response completion after TodoWrite if 2+ pending items exist without spawning Task agents."
|
|
152
|
+
"todo_delegation.py": "Hard blocks response completion after TodoWrite if 2+ pending items exist without spawning Task agents.",
|
|
153
|
+
"parallel_validation.py": "Tracks pending tasks count after TodoWrite to enable hard enforcement state.",
|
|
154
|
+
"agent_spawn_validator.py": "Blocks sequential tool usage (Read, Grep, etc.) if parallel delegation is required and enabled."
|
|
143
155
|
},
|
|
144
156
|
"context_management": {
|
|
145
157
|
"context.py": "Auto-injects CLAUDE.md, README.md, or AGENTS.md content into prompts for project-specific context.",
|
|
@@ -166,10 +178,11 @@
|
|
|
166
178
|
"state_files": {
|
|
167
179
|
"~/.stravinsky_mode": "Marker file indicating stravinsky orchestrator mode is active (enables hard blocking)",
|
|
168
180
|
"~/.claude/state/compaction.jsonl": "Audit log of context compaction events with preserved items",
|
|
169
|
-
".claude/todo_state.json": "Cached todo state for continuation enforcement"
|
|
181
|
+
".claude/todo_state.json": "Cached todo state for continuation enforcement",
|
|
182
|
+
".claude/parallel_state.json": "Transient state tracking pending tasks for hard enforcement"
|
|
170
183
|
},
|
|
171
184
|
|
|
172
|
-
"version": "0.2.
|
|
185
|
+
"version": "0.2.64",
|
|
173
186
|
"package": "stravinsky",
|
|
174
187
|
"documentation": "https://github.com/GratefulDave/stravinsky"
|
|
175
|
-
}
|
|
188
|
+
}
|
mcp_bridge/hooks/__init__.py
CHANGED
|
@@ -99,22 +99,37 @@ __all__ = [
|
|
|
99
99
|
"parallel_execution",
|
|
100
100
|
"stravinsky_mode",
|
|
101
101
|
"todo_delegation",
|
|
102
|
-
|
|
103
102
|
# Context & state
|
|
104
103
|
"context",
|
|
105
104
|
"todo_continuation",
|
|
106
105
|
"pre_compact",
|
|
107
|
-
|
|
108
106
|
# Tool enhancement
|
|
109
107
|
"tool_messaging",
|
|
110
108
|
"edit_recovery",
|
|
111
109
|
"truncator",
|
|
112
|
-
|
|
113
110
|
# Agent lifecycle
|
|
114
111
|
"notification_hook",
|
|
115
112
|
"subagent_stop",
|
|
116
113
|
]
|
|
117
114
|
|
|
118
|
-
|
|
115
|
+
|
|
116
|
+
def initialize_hooks():
|
|
117
|
+
"""Initialize and register all hooks with the HookManager."""
|
|
118
|
+
from .delegation_policy import DelegationReminderPolicy
|
|
119
|
+
from .edit_recovery_policy import EditRecoveryPolicy
|
|
120
|
+
from .manager import get_hook_manager
|
|
121
|
+
from .parallel_enforcement_policy import ParallelEnforcementPolicy
|
|
122
|
+
from .truncation_policy import TruncationPolicy
|
|
123
|
+
|
|
124
|
+
manager = get_hook_manager()
|
|
125
|
+
|
|
126
|
+
# Register unified policies
|
|
127
|
+
manager.register_policy(TruncationPolicy())
|
|
128
|
+
manager.register_policy(DelegationReminderPolicy())
|
|
129
|
+
manager.register_policy(EditRecoveryPolicy())
|
|
130
|
+
manager.register_policy(ParallelEnforcementPolicy())
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
__version__ = "0.4.60"
|
|
119
134
|
__author__ = "David Andrews"
|
|
120
135
|
__description__ = "Claude Code hooks for Stravinsky MCP parallel execution"
|
|
@@ -6,7 +6,7 @@ suggests using background agents for more comprehensive results.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
@@ -28,8 +28,8 @@ SEARCH_TOOLS = {"grep", "glob", "rg", "find", "Grep", "Glob", "grep_search", "gl
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
async def agent_reminder_hook(
|
|
31
|
-
tool_name: str, arguments:
|
|
32
|
-
) ->
|
|
31
|
+
tool_name: str, arguments: dict[str, Any], output: str
|
|
32
|
+
) -> str | None:
|
|
33
33
|
"""
|
|
34
34
|
Post-tool call hook that suggests background agents after direct search tool usage.
|
|
35
35
|
"""
|
|
@@ -51,7 +51,7 @@ async def agent_reminder_hook(
|
|
|
51
51
|
return None
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def _extract_search_context(arguments:
|
|
54
|
+
def _extract_search_context(arguments: dict[str, Any]) -> str:
|
|
55
55
|
"""Extract search context from tool arguments."""
|
|
56
56
|
for key in ("pattern", "query", "search", "name", "path"):
|
|
57
57
|
if key in arguments:
|
|
@@ -11,7 +11,7 @@ Detects and auto-processes slash commands in user input:
|
|
|
11
11
|
import logging
|
|
12
12
|
import re
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
SLASH_COMMAND_PATTERN = re.compile(r'(?:^|(?<=\s))\/([a-zA-Z][a-zA-Z0-9_-]*)\b', re.MULTILINE)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def extract_slash_commands(text: str) ->
|
|
22
|
+
def extract_slash_commands(text: str) -> list[str]:
|
|
23
23
|
"""
|
|
24
24
|
Extract all slash command names from text.
|
|
25
25
|
|
|
@@ -40,7 +40,7 @@ def extract_slash_commands(text: str) -> List[str]:
|
|
|
40
40
|
return unique
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def load_skill_content(command_name: str, project_path:
|
|
43
|
+
def load_skill_content(command_name: str, project_path: str | None = None) -> tuple[str, str] | None:
|
|
44
44
|
"""
|
|
45
45
|
Load skill content by command name.
|
|
46
46
|
|
|
@@ -92,7 +92,7 @@ def load_skill_content(command_name: str, project_path: Optional[str] = None) ->
|
|
|
92
92
|
return None
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
def get_project_path_from_prompt(prompt: str) ->
|
|
95
|
+
def get_project_path_from_prompt(prompt: str) -> str | None:
|
|
96
96
|
"""
|
|
97
97
|
Try to extract project path from prompt context.
|
|
98
98
|
Looks for common patterns that indicate the working directory.
|
|
@@ -126,7 +126,7 @@ SKILL_NOT_FOUND_WARNING = """
|
|
|
126
126
|
"""
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
async def auto_slash_command_hook(params:
|
|
129
|
+
async def auto_slash_command_hook(params: dict[str, Any]) -> dict[str, Any] | None:
|
|
130
130
|
"""
|
|
131
131
|
Pre-model invoke hook that detects slash commands and injects skill content.
|
|
132
132
|
|
|
@@ -3,14 +3,14 @@ Thinking budget optimizer hook.
|
|
|
3
3
|
Analyzes prompt complexity and adjusts thinking_budget for models that support it.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
REASONING_KEYWORDS = [
|
|
9
9
|
"architect", "design", "refactor", "debug", "complex", "optimize",
|
|
10
10
|
"summarize", "analyze", "explain", "why", "review", "strangler"
|
|
11
11
|
]
|
|
12
12
|
|
|
13
|
-
async def budget_optimizer_hook(params:
|
|
13
|
+
async def budget_optimizer_hook(params: dict[str, Any]) -> dict[str, Any] | None:
|
|
14
14
|
"""
|
|
15
15
|
Adjusts the thinking_budget based on presence of reasoning-heavy keywords.
|
|
16
16
|
"""
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PostToolUse hook for Claude rate limit detection.
|
|
4
|
+
|
|
5
|
+
Monitors model invocation responses for Claude-specific rate limit indicators
|
|
6
|
+
and updates the provider state tracker accordingly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Add parent directory to path for imports
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from routing import get_provider_tracker
|
|
19
|
+
except ImportError:
|
|
20
|
+
get_provider_tracker = None # type: ignore
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Claude rate limit indicators
|
|
26
|
+
CLAUDE_RATE_LIMIT_PATTERNS = [
|
|
27
|
+
"rate limit",
|
|
28
|
+
"rate_limit_error",
|
|
29
|
+
"too many requests",
|
|
30
|
+
"429",
|
|
31
|
+
"quota exceeded",
|
|
32
|
+
"rate-limited",
|
|
33
|
+
"overloaded_error",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def detect_claude_rate_limit(tool_result: str | dict) -> bool:
|
|
38
|
+
"""
|
|
39
|
+
Detect if a tool result indicates Claude rate limiting.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
tool_result: Tool result string or dict
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
True if rate limit detected, False otherwise
|
|
46
|
+
"""
|
|
47
|
+
# Convert result to searchable string
|
|
48
|
+
if isinstance(tool_result, dict):
|
|
49
|
+
search_text = json.dumps(tool_result).lower()
|
|
50
|
+
else:
|
|
51
|
+
search_text = str(tool_result).lower()
|
|
52
|
+
|
|
53
|
+
# Check for rate limit patterns
|
|
54
|
+
for pattern in CLAUDE_RATE_LIMIT_PATTERNS:
|
|
55
|
+
if pattern in search_text:
|
|
56
|
+
logger.info(f"[ClaudeLimitsHook] Detected rate limit pattern: {pattern}")
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main() -> None:
|
|
63
|
+
"""Process PostToolUse hook event."""
|
|
64
|
+
try:
|
|
65
|
+
# Read hook input from stdin
|
|
66
|
+
hook_input = json.loads(sys.stdin.read())
|
|
67
|
+
|
|
68
|
+
tool_name = hook_input.get("tool_name", "")
|
|
69
|
+
tool_result = hook_input.get("tool_result", "")
|
|
70
|
+
|
|
71
|
+
# Only monitor invoke tools (Claude uses default Claude models)
|
|
72
|
+
# We're looking for rate limits from the main Claude context
|
|
73
|
+
# This is different from invoke_openai/invoke_gemini which are explicit
|
|
74
|
+
if not any(
|
|
75
|
+
keyword in tool_name.lower() for keyword in ["invoke", "chat", "generate", "complete"]
|
|
76
|
+
):
|
|
77
|
+
# Not a model invocation tool - skip
|
|
78
|
+
sys.exit(0)
|
|
79
|
+
|
|
80
|
+
# Check for rate limit indicators
|
|
81
|
+
if detect_claude_rate_limit(tool_result):
|
|
82
|
+
logger.warning("[ClaudeLimitsHook] Claude rate limit detected")
|
|
83
|
+
|
|
84
|
+
if get_provider_tracker and callable(get_provider_tracker):
|
|
85
|
+
tracker = get_provider_tracker()
|
|
86
|
+
if tracker:
|
|
87
|
+
tracker.mark_rate_limited("claude", duration=300, reason="Claude rate limit")
|
|
88
|
+
logger.info("[ClaudeLimitsHook] Marked Claude as rate-limited (300s cooldown)")
|
|
89
|
+
|
|
90
|
+
print(
|
|
91
|
+
"\n⚠️ Claude Rate Limit Detected\n"
|
|
92
|
+
"→ Routing future requests to OpenAI/Gemini for 5 minutes\n",
|
|
93
|
+
file=sys.stderr,
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
logger.debug(
|
|
97
|
+
"[ClaudeLimitsHook] Provider tracker not available, skipping state update"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
sys.exit(0)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
# Log error but don't block
|
|
104
|
+
logger.error(f"[ClaudeLimitsHook] Error: {e}", exc_info=True)
|
|
105
|
+
print(f"[ClaudeLimitsHook] Warning: {e}", file=sys.stderr)
|
|
106
|
+
sys.exit(0) # Exit 0 to not block execution
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
# Configure logging
|
|
111
|
+
logging.basicConfig(
|
|
112
|
+
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
113
|
+
)
|
|
114
|
+
main()
|
|
@@ -6,8 +6,7 @@ Code should be self-documenting; excessive comments indicate AI slop.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
import
|
|
10
|
-
from typing import Any, Dict, Optional
|
|
9
|
+
from typing import Any
|
|
11
10
|
|
|
12
11
|
logger = logging.getLogger(__name__)
|
|
13
12
|
|
|
@@ -53,8 +52,8 @@ CODE_EXTENSIONS = {
|
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
async def comment_checker_hook(
|
|
56
|
-
tool_name: str, arguments:
|
|
57
|
-
) ->
|
|
55
|
+
tool_name: str, arguments: dict[str, Any], output: str
|
|
56
|
+
) -> str | None:
|
|
58
57
|
"""
|
|
59
58
|
Post-tool call hook that checks for excessive comments in code edits.
|
|
60
59
|
"""
|
mcp_bridge/hooks/compaction.py
CHANGED
|
@@ -3,7 +3,7 @@ Preemptive context compaction hook.
|
|
|
3
3
|
Monitors context size and injects optimization reminders.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
THRESHOLD_CHARS = 100000 # Roughly 25k-30k tokens for typical LLM text
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ COMPACTION_REMINDER = """
|
|
|
17
17
|
> 4. Keep your next responses concise and focused only on the current sub-task.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
async def context_compaction_hook(params:
|
|
20
|
+
async def context_compaction_hook(params: dict[str, Any]) -> dict[str, Any] | None:
|
|
21
21
|
"""
|
|
22
22
|
Checks prompt length and injects a compaction reminder if it's too large.
|
|
23
23
|
"""
|
mcp_bridge/hooks/context.py
CHANGED
|
@@ -7,7 +7,7 @@ At 85%, suggests compaction before hitting hard limits.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -32,7 +32,7 @@ CONTEXT_THRESHOLD_WARNING = 0.85
|
|
|
32
32
|
ESTIMATED_MAX_TOKENS = 200000
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
async def context_monitor_hook(params:
|
|
35
|
+
async def context_monitor_hook(params: dict[str, Any]) -> dict[str, Any] | None:
|
|
36
36
|
"""
|
|
37
37
|
Pre-model invoke hook that monitors context window usage.
|
|
38
38
|
"""
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ..utils.session_state import get_current_session_id, update_session_state
|
|
6
|
+
from .events import EventType, HookPolicy, PolicyResult, ToolCallEvent
|
|
7
|
+
|
|
8
|
+
# Check if stravinsky mode is active (hard blocking enabled)
|
|
9
|
+
STRAVINSKY_MODE_FILE = Path.home() / ".stravinsky_mode"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_stravinsky_mode():
|
|
13
|
+
"""Check if hard blocking mode is active."""
|
|
14
|
+
return STRAVINSKY_MODE_FILE.exists()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DelegationReminderPolicy(HookPolicy):
|
|
18
|
+
"""
|
|
19
|
+
Policy for TodoWrite: CRITICAL parallel execution enforcer.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def event_type(self) -> EventType:
|
|
24
|
+
return EventType.POST_TOOL_CALL
|
|
25
|
+
|
|
26
|
+
async def evaluate(self, event: ToolCallEvent) -> PolicyResult:
|
|
27
|
+
if event.tool_name != "TodoWrite":
|
|
28
|
+
return PolicyResult(modified_data=event.output)
|
|
29
|
+
|
|
30
|
+
todos = event.arguments.get("todos", [])
|
|
31
|
+
pending_count = sum(1 for t in todos if t.get("status") == "pending")
|
|
32
|
+
|
|
33
|
+
# Update session state
|
|
34
|
+
session_id = event.metadata.get("session_id") or get_current_session_id()
|
|
35
|
+
update_session_state(
|
|
36
|
+
{
|
|
37
|
+
"last_todo_write_at": time.time(),
|
|
38
|
+
"pending_todo_count": pending_count,
|
|
39
|
+
},
|
|
40
|
+
session_id=session_id,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if pending_count < 2:
|
|
44
|
+
return PolicyResult(modified_data=event.output)
|
|
45
|
+
|
|
46
|
+
stravinsky_active = is_stravinsky_mode()
|
|
47
|
+
|
|
48
|
+
mode_warning = ""
|
|
49
|
+
if stravinsky_active:
|
|
50
|
+
mode_warning = """
|
|
51
|
+
⚠️ STRAVINSKY MODE ACTIVE - Direct tools (Read, Grep, Bash) are BLOCKED.
|
|
52
|
+
You MUST use Task(subagent_type="explore", ...) for ALL file operations.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
error_message = f"""
|
|
56
|
+
╔══════════════════════════════════════════════════════════════════════════╗
|
|
57
|
+
║ 🚨 PARALLEL DELEGATION REQUIRED 🚨 ║
|
|
58
|
+
╠══════════════════════════════════════════════════════════════════════════╣
|
|
59
|
+
║ ║
|
|
60
|
+
║ TodoWrite created {pending_count} pending items. ║
|
|
61
|
+
║ {mode_warning.strip()} ║
|
|
62
|
+
║ ║
|
|
63
|
+
║ You MUST spawn Task agents for ALL independent TODOs in this response. ║
|
|
64
|
+
║ ║
|
|
65
|
+
╠══════════════════════════════════════════════════════════════════════════╣
|
|
66
|
+
║ REQUIRED PATTERN: ║
|
|
67
|
+
║ Task(subagent_type="explore", prompt="TODO 1...", run_in_background=t) ║
|
|
68
|
+
║ Task(subagent_type="explore", prompt="TODO 2...", run_in_background=t) ║
|
|
69
|
+
║ ... ║
|
|
70
|
+
╚══════════════════════════════════════════════════════════════════════════╝
|
|
71
|
+
"""
|
|
72
|
+
# In PostToolUse, we often want to APPEND the reminder to the output
|
|
73
|
+
new_output = (event.output or "") + "\n" + error_message
|
|
74
|
+
|
|
75
|
+
return PolicyResult(
|
|
76
|
+
modified_data=new_output,
|
|
77
|
+
message=error_message, # For native hooks, we might just print the message
|
|
78
|
+
should_block=stravinsky_active,
|
|
79
|
+
exit_code=2 if stravinsky_active else 1,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
policy = DelegationReminderPolicy()
|
|
85
|
+
policy.run_as_native()
|
|
@@ -3,11 +3,11 @@ Directory context injector hook.
|
|
|
3
3
|
Automatically finds and injects local AGENTS.md or README.md content based on the current context.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import os
|
|
7
6
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
async def directory_context_hook(params: dict[str, Any]) -> dict[str, Any] | None:
|
|
11
11
|
"""
|
|
12
12
|
Search for AGENTS.md or README.md in the current working directory and inject them.
|
|
13
13
|
"""
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from .events import EventType, HookPolicy, PolicyResult, ToolCallEvent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EditRecoveryPolicy(HookPolicy):
|
|
7
|
+
"""
|
|
8
|
+
Policy to provide recovery guidance when Edit/MultiEdit tools fail.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def event_type(self) -> EventType:
|
|
13
|
+
return EventType.POST_TOOL_CALL
|
|
14
|
+
|
|
15
|
+
async def evaluate(self, event: ToolCallEvent) -> PolicyResult:
|
|
16
|
+
if event.tool_name not in ["Edit", "MultiEdit", "replace"]:
|
|
17
|
+
return PolicyResult(modified_data=event.output)
|
|
18
|
+
|
|
19
|
+
if not event.output:
|
|
20
|
+
return PolicyResult(modified_data=event.output)
|
|
21
|
+
|
|
22
|
+
# Error patterns
|
|
23
|
+
error_patterns = [
|
|
24
|
+
r"oldString not found",
|
|
25
|
+
r"oldString matched multiple times",
|
|
26
|
+
r"line numbers out of range",
|
|
27
|
+
r"does not match exactly",
|
|
28
|
+
r"failed to find the target string",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
recovery_needed = any(re.search(p, event.output, re.IGNORECASE) for p in error_patterns)
|
|
32
|
+
|
|
33
|
+
if recovery_needed:
|
|
34
|
+
correction = (
|
|
35
|
+
"\n\n[SYSTEM RECOVERY] It appears the Edit tool failed to find the target string. "
|
|
36
|
+
"Please call 'Read' on the file again to verify the current content, "
|
|
37
|
+
"then ensure your 'oldString' is an EXACT match including all whitespace."
|
|
38
|
+
)
|
|
39
|
+
return PolicyResult(
|
|
40
|
+
modified_data=event.output + correction,
|
|
41
|
+
message=correction,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return PolicyResult(modified_data=event.output)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
policy = EditRecoveryPolicy()
|
|
49
|
+
policy.run_as_native()
|
|
@@ -10,7 +10,7 @@ Cleans up empty/malformed messages:
|
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
12
|
import re
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Any
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -181,7 +181,7 @@ def sanitize_message_blocks(prompt: str) -> tuple[str, int]:
|
|
|
181
181
|
return prompt, sanitized_count
|
|
182
182
|
|
|
183
183
|
|
|
184
|
-
async def empty_message_sanitizer_hook(params:
|
|
184
|
+
async def empty_message_sanitizer_hook(params: dict[str, Any]) -> dict[str, Any] | None:
|
|
185
185
|
"""
|
|
186
186
|
Pre-model invoke hook that sanitizes empty and malformed message content.
|
|
187
187
|
|