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.

Files changed (190) 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 +112 -11
  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/MANIFEST_SCHEMA.md +305 -0
  11. mcp_bridge/config/README.md +276 -0
  12. mcp_bridge/config/__init__.py +2 -2
  13. mcp_bridge/config/hook_config.py +247 -0
  14. mcp_bridge/config/hooks_manifest.json +138 -0
  15. mcp_bridge/config/rate_limits.py +317 -0
  16. mcp_bridge/config/skills_manifest.json +128 -0
  17. mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
  18. mcp_bridge/hooks/__init__.py +19 -4
  19. mcp_bridge/hooks/agent_reminder.py +4 -4
  20. mcp_bridge/hooks/auto_slash_command.py +5 -5
  21. mcp_bridge/hooks/budget_optimizer.py +2 -2
  22. mcp_bridge/hooks/claude_limits_hook.py +114 -0
  23. mcp_bridge/hooks/comment_checker.py +3 -4
  24. mcp_bridge/hooks/compaction.py +2 -2
  25. mcp_bridge/hooks/context.py +2 -1
  26. mcp_bridge/hooks/context_monitor.py +2 -2
  27. mcp_bridge/hooks/delegation_policy.py +85 -0
  28. mcp_bridge/hooks/directory_context.py +3 -3
  29. mcp_bridge/hooks/edit_recovery.py +3 -2
  30. mcp_bridge/hooks/edit_recovery_policy.py +49 -0
  31. mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
  32. mcp_bridge/hooks/events.py +160 -0
  33. mcp_bridge/hooks/git_noninteractive.py +4 -4
  34. mcp_bridge/hooks/keyword_detector.py +8 -10
  35. mcp_bridge/hooks/manager.py +43 -22
  36. mcp_bridge/hooks/notification_hook.py +13 -6
  37. mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
  38. mcp_bridge/hooks/parallel_enforcer.py +5 -5
  39. mcp_bridge/hooks/parallel_execution.py +22 -10
  40. mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
  41. mcp_bridge/hooks/pre_compact.py +8 -9
  42. mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
  43. mcp_bridge/hooks/preemptive_compaction.py +2 -3
  44. mcp_bridge/hooks/routing_notifications.py +80 -0
  45. mcp_bridge/hooks/rules_injector.py +11 -19
  46. mcp_bridge/hooks/session_idle.py +4 -4
  47. mcp_bridge/hooks/session_notifier.py +4 -4
  48. mcp_bridge/hooks/session_recovery.py +4 -5
  49. mcp_bridge/hooks/stravinsky_mode.py +1 -1
  50. mcp_bridge/hooks/subagent_stop.py +1 -3
  51. mcp_bridge/hooks/task_validator.py +2 -2
  52. mcp_bridge/hooks/tmux_manager.py +7 -8
  53. mcp_bridge/hooks/todo_delegation.py +4 -1
  54. mcp_bridge/hooks/todo_enforcer.py +180 -10
  55. mcp_bridge/hooks/tool_messaging.py +113 -10
  56. mcp_bridge/hooks/truncation_policy.py +37 -0
  57. mcp_bridge/hooks/truncator.py +1 -2
  58. mcp_bridge/metrics/cost_tracker.py +115 -0
  59. mcp_bridge/native_search.py +93 -0
  60. mcp_bridge/native_watcher.py +118 -0
  61. mcp_bridge/notifications.py +150 -0
  62. mcp_bridge/orchestrator/enums.py +11 -0
  63. mcp_bridge/orchestrator/router.py +165 -0
  64. mcp_bridge/orchestrator/state.py +32 -0
  65. mcp_bridge/orchestrator/visualization.py +14 -0
  66. mcp_bridge/orchestrator/wisdom.py +34 -0
  67. mcp_bridge/prompts/__init__.py +1 -8
  68. mcp_bridge/prompts/dewey.py +1 -1
  69. mcp_bridge/prompts/planner.py +2 -4
  70. mcp_bridge/prompts/stravinsky.py +53 -31
  71. mcp_bridge/proxy/__init__.py +0 -0
  72. mcp_bridge/proxy/client.py +70 -0
  73. mcp_bridge/proxy/model_server.py +157 -0
  74. mcp_bridge/routing/__init__.py +43 -0
  75. mcp_bridge/routing/config.py +250 -0
  76. mcp_bridge/routing/model_tiers.py +135 -0
  77. mcp_bridge/routing/provider_state.py +261 -0
  78. mcp_bridge/routing/task_classifier.py +190 -0
  79. mcp_bridge/server.py +542 -59
  80. mcp_bridge/server_tools.py +738 -6
  81. mcp_bridge/tools/__init__.py +40 -25
  82. mcp_bridge/tools/agent_manager.py +616 -697
  83. mcp_bridge/tools/background_tasks.py +13 -17
  84. mcp_bridge/tools/code_search.py +70 -53
  85. mcp_bridge/tools/continuous_loop.py +0 -1
  86. mcp_bridge/tools/dashboard.py +19 -0
  87. mcp_bridge/tools/find_code.py +296 -0
  88. mcp_bridge/tools/init.py +1 -0
  89. mcp_bridge/tools/list_directory.py +42 -0
  90. mcp_bridge/tools/lsp/__init__.py +12 -5
  91. mcp_bridge/tools/lsp/manager.py +471 -0
  92. mcp_bridge/tools/lsp/tools.py +723 -207
  93. mcp_bridge/tools/model_invoke.py +1195 -273
  94. mcp_bridge/tools/mux_client.py +75 -0
  95. mcp_bridge/tools/project_context.py +1 -2
  96. mcp_bridge/tools/query_classifier.py +406 -0
  97. mcp_bridge/tools/read_file.py +84 -0
  98. mcp_bridge/tools/replace.py +45 -0
  99. mcp_bridge/tools/run_shell_command.py +38 -0
  100. mcp_bridge/tools/search_enhancements.py +347 -0
  101. mcp_bridge/tools/semantic_search.py +3627 -0
  102. mcp_bridge/tools/session_manager.py +0 -2
  103. mcp_bridge/tools/skill_loader.py +0 -1
  104. mcp_bridge/tools/task_runner.py +5 -7
  105. mcp_bridge/tools/templates.py +3 -3
  106. mcp_bridge/tools/tool_search.py +331 -0
  107. mcp_bridge/tools/write_file.py +29 -0
  108. mcp_bridge/update_manager.py +585 -0
  109. mcp_bridge/update_manager_pypi.py +297 -0
  110. mcp_bridge/utils/cache.py +82 -0
  111. mcp_bridge/utils/process.py +71 -0
  112. mcp_bridge/utils/session_state.py +51 -0
  113. mcp_bridge/utils/truncation.py +76 -0
  114. stravinsky-0.4.66.dist-info/METADATA +517 -0
  115. stravinsky-0.4.66.dist-info/RECORD +198 -0
  116. {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
  117. stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
  118. stravinsky_claude_assets/agents/HOOKS.md +437 -0
  119. stravinsky_claude_assets/agents/code-reviewer.md +210 -0
  120. stravinsky_claude_assets/agents/comment_checker.md +580 -0
  121. stravinsky_claude_assets/agents/debugger.md +254 -0
  122. stravinsky_claude_assets/agents/delphi.md +495 -0
  123. stravinsky_claude_assets/agents/dewey.md +248 -0
  124. stravinsky_claude_assets/agents/explore.md +1198 -0
  125. stravinsky_claude_assets/agents/frontend.md +472 -0
  126. stravinsky_claude_assets/agents/implementation-lead.md +164 -0
  127. stravinsky_claude_assets/agents/momus.md +464 -0
  128. stravinsky_claude_assets/agents/research-lead.md +141 -0
  129. stravinsky_claude_assets/agents/stravinsky.md +730 -0
  130. stravinsky_claude_assets/commands/delphi.md +9 -0
  131. stravinsky_claude_assets/commands/dewey.md +54 -0
  132. stravinsky_claude_assets/commands/git-master.md +112 -0
  133. stravinsky_claude_assets/commands/index.md +49 -0
  134. stravinsky_claude_assets/commands/publish.md +86 -0
  135. stravinsky_claude_assets/commands/review.md +73 -0
  136. stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
  137. stravinsky_claude_assets/commands/str/agent_list.md +56 -0
  138. stravinsky_claude_assets/commands/str/agent_output.md +92 -0
  139. stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
  140. stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
  141. stravinsky_claude_assets/commands/str/cancel.md +51 -0
  142. stravinsky_claude_assets/commands/str/clean.md +97 -0
  143. stravinsky_claude_assets/commands/str/continue.md +38 -0
  144. stravinsky_claude_assets/commands/str/index.md +199 -0
  145. stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
  146. stravinsky_claude_assets/commands/str/search.md +205 -0
  147. stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
  148. stravinsky_claude_assets/commands/str/stats.md +71 -0
  149. stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
  150. stravinsky_claude_assets/commands/str/unwatch.md +42 -0
  151. stravinsky_claude_assets/commands/str/watch.md +45 -0
  152. stravinsky_claude_assets/commands/strav.md +53 -0
  153. stravinsky_claude_assets/commands/stravinsky.md +292 -0
  154. stravinsky_claude_assets/commands/verify.md +60 -0
  155. stravinsky_claude_assets/commands/version.md +5 -0
  156. stravinsky_claude_assets/hooks/README.md +248 -0
  157. stravinsky_claude_assets/hooks/comment_checker.py +193 -0
  158. stravinsky_claude_assets/hooks/context.py +38 -0
  159. stravinsky_claude_assets/hooks/context_monitor.py +153 -0
  160. stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
  161. stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
  162. stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
  163. stravinsky_claude_assets/hooks/notification_hook.py +103 -0
  164. stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
  165. stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
  166. stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
  167. stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
  168. stravinsky_claude_assets/hooks/pre_compact.py +123 -0
  169. stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
  170. stravinsky_claude_assets/hooks/session_recovery.py +263 -0
  171. stravinsky_claude_assets/hooks/stop_hook.py +89 -0
  172. stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
  173. stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
  174. stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
  175. stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
  176. stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
  177. stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
  178. stravinsky_claude_assets/hooks/truncator.py +23 -0
  179. stravinsky_claude_assets/rules/deployment_safety.md +51 -0
  180. stravinsky_claude_assets/rules/integration_wiring.md +89 -0
  181. stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
  182. stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
  183. stravinsky_claude_assets/settings.json +152 -0
  184. stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
  185. stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
  186. stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
  187. stravinsky_claude_assets/task_dependencies.json +34 -0
  188. stravinsky-0.2.67.dist-info/METADATA +0 -284
  189. stravinsky-0.2.67.dist-info/RECORD +0 -76
  190. {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.63",
185
+ "version": "0.2.64",
173
186
  "package": "stravinsky",
174
187
  "documentation": "https://github.com/GratefulDave/stravinsky"
175
- }
188
+ }
@@ -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
- __version__ = "0.2.63"
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, Dict, Optional
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: Dict[str, Any], output: str
32
- ) -> Optional[str]:
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: Dict[str, Any]) -> str:
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, Dict, List, Optional, Tuple
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) -> List[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: Optional[str] = None) -> Optional[Tuple[str, str]]:
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) -> Optional[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: Dict[str, Any]) -> Optional[Dict[str, Any]]:
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, Dict, Optional
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: Dict[str, Any]) -> Optional[Dict[str, Any]]:
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 re
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: Dict[str, Any], output: str
57
- ) -> Optional[str]:
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
  """
@@ -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, Dict, Optional
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: Dict[str, Any]) -> Optional[Dict[str, Any]]:
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
  """
@@ -1,8 +1,9 @@
1
+ import json
1
2
  import os
2
3
  import sys
3
- import json
4
4
  from pathlib import Path
5
5
 
6
+
6
7
  def main():
7
8
  try:
8
9
  data = json.load(sys.stdin)
@@ -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, Dict, Optional
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: Dict[str, Any]) -> Optional[Dict[str, Any]]:
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, Dict, Optional
7
+ from typing import Any
9
8
 
10
- async def directory_context_hook(params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
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
  """
@@ -1,7 +1,8 @@
1
- import os
2
- import sys
3
1
  import json
2
+ import os
4
3
  import re
4
+ import sys
5
+
5
6
 
6
7
  def main():
7
8
  # Claude Code PostToolUse inputs via Environment Variables
@@ -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, Dict, List, Optional
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: Dict[str, Any]) -> Optional[Dict[str, Any]]:
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