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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (184) hide show
  1. mcp_bridge/__init__.py +1 -1
  2. mcp_bridge/auth/__init__.py +16 -6
  3. mcp_bridge/auth/cli.py +202 -11
  4. mcp_bridge/auth/oauth.py +1 -2
  5. mcp_bridge/auth/openai_oauth.py +4 -7
  6. mcp_bridge/auth/token_store.py +0 -1
  7. mcp_bridge/cli/__init__.py +1 -1
  8. mcp_bridge/cli/install_hooks.py +503 -107
  9. mcp_bridge/cli/session_report.py +0 -3
  10. mcp_bridge/config/__init__.py +2 -2
  11. mcp_bridge/config/hook_config.py +3 -5
  12. mcp_bridge/config/rate_limits.py +108 -13
  13. mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
  14. mcp_bridge/hooks/__init__.py +14 -4
  15. mcp_bridge/hooks/agent_reminder.py +4 -4
  16. mcp_bridge/hooks/auto_slash_command.py +5 -5
  17. mcp_bridge/hooks/budget_optimizer.py +2 -2
  18. mcp_bridge/hooks/claude_limits_hook.py +114 -0
  19. mcp_bridge/hooks/comment_checker.py +3 -4
  20. mcp_bridge/hooks/compaction.py +2 -2
  21. mcp_bridge/hooks/context.py +2 -1
  22. mcp_bridge/hooks/context_monitor.py +2 -2
  23. mcp_bridge/hooks/delegation_policy.py +85 -0
  24. mcp_bridge/hooks/directory_context.py +3 -3
  25. mcp_bridge/hooks/edit_recovery.py +3 -2
  26. mcp_bridge/hooks/edit_recovery_policy.py +49 -0
  27. mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
  28. mcp_bridge/hooks/events.py +160 -0
  29. mcp_bridge/hooks/git_noninteractive.py +4 -4
  30. mcp_bridge/hooks/keyword_detector.py +8 -10
  31. mcp_bridge/hooks/manager.py +35 -22
  32. mcp_bridge/hooks/notification_hook.py +13 -6
  33. mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
  34. mcp_bridge/hooks/parallel_enforcer.py +5 -5
  35. mcp_bridge/hooks/parallel_execution.py +22 -10
  36. mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
  37. mcp_bridge/hooks/pre_compact.py +8 -9
  38. mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
  39. mcp_bridge/hooks/preemptive_compaction.py +2 -3
  40. mcp_bridge/hooks/routing_notifications.py +80 -0
  41. mcp_bridge/hooks/rules_injector.py +11 -19
  42. mcp_bridge/hooks/session_idle.py +4 -4
  43. mcp_bridge/hooks/session_notifier.py +4 -4
  44. mcp_bridge/hooks/session_recovery.py +4 -5
  45. mcp_bridge/hooks/stravinsky_mode.py +1 -1
  46. mcp_bridge/hooks/subagent_stop.py +1 -3
  47. mcp_bridge/hooks/task_validator.py +2 -2
  48. mcp_bridge/hooks/tmux_manager.py +7 -8
  49. mcp_bridge/hooks/todo_delegation.py +4 -1
  50. mcp_bridge/hooks/todo_enforcer.py +180 -10
  51. mcp_bridge/hooks/truncation_policy.py +37 -0
  52. mcp_bridge/hooks/truncator.py +1 -2
  53. mcp_bridge/metrics/cost_tracker.py +115 -0
  54. mcp_bridge/native_search.py +93 -0
  55. mcp_bridge/native_watcher.py +118 -0
  56. mcp_bridge/notifications.py +3 -4
  57. mcp_bridge/orchestrator/enums.py +11 -0
  58. mcp_bridge/orchestrator/router.py +165 -0
  59. mcp_bridge/orchestrator/state.py +32 -0
  60. mcp_bridge/orchestrator/visualization.py +14 -0
  61. mcp_bridge/orchestrator/wisdom.py +34 -0
  62. mcp_bridge/prompts/__init__.py +1 -8
  63. mcp_bridge/prompts/dewey.py +1 -1
  64. mcp_bridge/prompts/planner.py +2 -4
  65. mcp_bridge/prompts/stravinsky.py +53 -31
  66. mcp_bridge/proxy/__init__.py +0 -0
  67. mcp_bridge/proxy/client.py +70 -0
  68. mcp_bridge/proxy/model_server.py +157 -0
  69. mcp_bridge/routing/__init__.py +43 -0
  70. mcp_bridge/routing/config.py +250 -0
  71. mcp_bridge/routing/model_tiers.py +135 -0
  72. mcp_bridge/routing/provider_state.py +261 -0
  73. mcp_bridge/routing/task_classifier.py +190 -0
  74. mcp_bridge/server.py +363 -34
  75. mcp_bridge/server_tools.py +298 -6
  76. mcp_bridge/tools/__init__.py +19 -8
  77. mcp_bridge/tools/agent_manager.py +549 -799
  78. mcp_bridge/tools/background_tasks.py +13 -17
  79. mcp_bridge/tools/code_search.py +54 -51
  80. mcp_bridge/tools/continuous_loop.py +0 -1
  81. mcp_bridge/tools/dashboard.py +19 -0
  82. mcp_bridge/tools/find_code.py +296 -0
  83. mcp_bridge/tools/init.py +1 -0
  84. mcp_bridge/tools/list_directory.py +42 -0
  85. mcp_bridge/tools/lsp/__init__.py +8 -8
  86. mcp_bridge/tools/lsp/manager.py +51 -28
  87. mcp_bridge/tools/lsp/tools.py +98 -65
  88. mcp_bridge/tools/model_invoke.py +1047 -152
  89. mcp_bridge/tools/mux_client.py +75 -0
  90. mcp_bridge/tools/project_context.py +1 -2
  91. mcp_bridge/tools/query_classifier.py +132 -49
  92. mcp_bridge/tools/read_file.py +84 -0
  93. mcp_bridge/tools/replace.py +45 -0
  94. mcp_bridge/tools/run_shell_command.py +38 -0
  95. mcp_bridge/tools/search_enhancements.py +347 -0
  96. mcp_bridge/tools/semantic_search.py +677 -92
  97. mcp_bridge/tools/session_manager.py +0 -2
  98. mcp_bridge/tools/skill_loader.py +0 -1
  99. mcp_bridge/tools/task_runner.py +5 -7
  100. mcp_bridge/tools/templates.py +3 -3
  101. mcp_bridge/tools/tool_search.py +331 -0
  102. mcp_bridge/tools/write_file.py +29 -0
  103. mcp_bridge/update_manager.py +33 -37
  104. mcp_bridge/update_manager_pypi.py +6 -8
  105. mcp_bridge/utils/cache.py +82 -0
  106. mcp_bridge/utils/process.py +71 -0
  107. mcp_bridge/utils/session_state.py +51 -0
  108. mcp_bridge/utils/truncation.py +76 -0
  109. {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/METADATA +84 -35
  110. stravinsky-0.4.66.dist-info/RECORD +198 -0
  111. {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
  112. stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
  113. stravinsky_claude_assets/agents/HOOKS.md +437 -0
  114. stravinsky_claude_assets/agents/code-reviewer.md +210 -0
  115. stravinsky_claude_assets/agents/comment_checker.md +580 -0
  116. stravinsky_claude_assets/agents/debugger.md +254 -0
  117. stravinsky_claude_assets/agents/delphi.md +495 -0
  118. stravinsky_claude_assets/agents/dewey.md +248 -0
  119. stravinsky_claude_assets/agents/explore.md +1198 -0
  120. stravinsky_claude_assets/agents/frontend.md +472 -0
  121. stravinsky_claude_assets/agents/implementation-lead.md +164 -0
  122. stravinsky_claude_assets/agents/momus.md +464 -0
  123. stravinsky_claude_assets/agents/research-lead.md +141 -0
  124. stravinsky_claude_assets/agents/stravinsky.md +730 -0
  125. stravinsky_claude_assets/commands/delphi.md +9 -0
  126. stravinsky_claude_assets/commands/dewey.md +54 -0
  127. stravinsky_claude_assets/commands/git-master.md +112 -0
  128. stravinsky_claude_assets/commands/index.md +49 -0
  129. stravinsky_claude_assets/commands/publish.md +86 -0
  130. stravinsky_claude_assets/commands/review.md +73 -0
  131. stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
  132. stravinsky_claude_assets/commands/str/agent_list.md +56 -0
  133. stravinsky_claude_assets/commands/str/agent_output.md +92 -0
  134. stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
  135. stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
  136. stravinsky_claude_assets/commands/str/cancel.md +51 -0
  137. stravinsky_claude_assets/commands/str/clean.md +97 -0
  138. stravinsky_claude_assets/commands/str/continue.md +38 -0
  139. stravinsky_claude_assets/commands/str/index.md +199 -0
  140. stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
  141. stravinsky_claude_assets/commands/str/search.md +205 -0
  142. stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
  143. stravinsky_claude_assets/commands/str/stats.md +71 -0
  144. stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
  145. stravinsky_claude_assets/commands/str/unwatch.md +42 -0
  146. stravinsky_claude_assets/commands/str/watch.md +45 -0
  147. stravinsky_claude_assets/commands/strav.md +53 -0
  148. stravinsky_claude_assets/commands/stravinsky.md +292 -0
  149. stravinsky_claude_assets/commands/verify.md +60 -0
  150. stravinsky_claude_assets/commands/version.md +5 -0
  151. stravinsky_claude_assets/hooks/README.md +248 -0
  152. stravinsky_claude_assets/hooks/comment_checker.py +193 -0
  153. stravinsky_claude_assets/hooks/context.py +38 -0
  154. stravinsky_claude_assets/hooks/context_monitor.py +153 -0
  155. stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
  156. stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
  157. stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
  158. stravinsky_claude_assets/hooks/notification_hook.py +103 -0
  159. stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
  160. stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
  161. stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
  162. stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
  163. stravinsky_claude_assets/hooks/pre_compact.py +123 -0
  164. stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
  165. stravinsky_claude_assets/hooks/session_recovery.py +263 -0
  166. stravinsky_claude_assets/hooks/stop_hook.py +89 -0
  167. stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
  168. stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
  169. stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
  170. stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
  171. stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
  172. stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
  173. stravinsky_claude_assets/hooks/truncator.py +23 -0
  174. stravinsky_claude_assets/rules/deployment_safety.md +51 -0
  175. stravinsky_claude_assets/rules/integration_wiring.md +89 -0
  176. stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
  177. stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
  178. stravinsky_claude_assets/settings.json +152 -0
  179. stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
  180. stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
  181. stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
  182. stravinsky_claude_assets/task_dependencies.json +34 -0
  183. stravinsky-0.4.18.dist-info/RECORD +0 -88
  184. {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/WHEEL +0 -0
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PreToolUse hook: Comment Quality Enforcer (oh-my-opencode parity)
4
+
5
+ Fires BEFORE git commit/push operations to check for low-quality comments.
6
+ Challenges comments that just restate what the code does.
7
+
8
+ Exit codes:
9
+ - 0: Allow the operation to proceed
10
+ - 2: Block the operation (hard block)
11
+
12
+ Trigger: PreToolUse on Bash tool when command contains 'git commit' or 'git push'
13
+ """
14
+
15
+ import json
16
+ import sys
17
+ import re
18
+ from pathlib import Path
19
+ from typing import List, Tuple, Optional
20
+
21
+ # Patterns that indicate low-quality comments
22
+ LOW_QUALITY_PATTERNS = [
23
+ # Comments that just describe what code literally does
24
+ r"#\s*(?:set|get|return|call|create|initialize|init)\s+\w+",
25
+ # Comments that are just variable/function names repeated
26
+ r"#\s*\w+\s*$",
27
+ # Empty or trivial comments
28
+ r"#\s*(?:TODO|FIXME|XXX|HACK)?\s*$",
29
+ # Comments that state the obvious
30
+ r"#\s*(?:loop|iterate|check|if|else|for|while)\s+(?:through|over|if|the)?\s*\w*\s*$",
31
+ # Comments like "# increment i" or "# add 1 to x"
32
+ r"#\s*(?:increment|decrement|add|subtract|multiply|divide)\s+\w+",
33
+ ]
34
+
35
+ # Patterns for GOOD comments we should NOT flag
36
+ GOOD_COMMENT_PATTERNS = [
37
+ # Docstrings and multi-line comments explaining WHY
38
+ r'""".*"""',
39
+ r"'''.*'''",
40
+ # Comments explaining business logic or reasoning
41
+ r"#\s*(?:because|since|note|important|warning|caution|reason|why|rationale)",
42
+ # Comments with URLs or references
43
+ r"#\s*(?:see|ref|https?://|link)",
44
+ # Type hints or type comments
45
+ r"#\s*type:",
46
+ # Pragma or directive comments
47
+ r"#\s*(?:noqa|type:|pragma|pylint|flake8)",
48
+ ]
49
+
50
+
51
+ def is_low_quality_comment(comment: str) -> bool:
52
+ """Check if a comment is low-quality (just restates code)."""
53
+ comment_lower = comment.lower().strip()
54
+
55
+ # Skip if it matches a good pattern
56
+ for pattern in GOOD_COMMENT_PATTERNS:
57
+ if re.search(pattern, comment_lower, re.IGNORECASE):
58
+ return False
59
+
60
+ # Check against low-quality patterns
61
+ for pattern in LOW_QUALITY_PATTERNS:
62
+ if re.search(pattern, comment_lower, re.IGNORECASE):
63
+ return True
64
+
65
+ # Very short comments (< 10 chars after #) are often low quality
66
+ content = comment.replace("#", "").strip()
67
+ if len(content) < 10 and not any(c in content for c in ["!", "?", ":", "TODO", "FIXME"]):
68
+ return True
69
+
70
+ return False
71
+
72
+
73
+ def extract_added_comments_from_diff(diff_text: str) -> List[Tuple[str, str]]:
74
+ """
75
+ Extract newly added comments from a git diff.
76
+ Returns list of (filename, comment) tuples.
77
+ """
78
+ added_comments = []
79
+ current_file = None
80
+
81
+ for line in diff_text.split("\n"):
82
+ # Track which file we're in
83
+ if line.startswith("+++ b/"):
84
+ current_file = line[6:]
85
+ # Only look at added lines (starting with +, but not +++)
86
+ elif line.startswith("+") and not line.startswith("+++"):
87
+ content = line[1:] # Remove the leading +
88
+ # Check for Python comments
89
+ if "#" in content and not content.strip().startswith("#!"):
90
+ # Extract the comment part
91
+ comment_match = re.search(r"#.*$", content)
92
+ if comment_match:
93
+ comment = comment_match.group(0)
94
+ if current_file:
95
+ added_comments.append((current_file, comment))
96
+
97
+ return added_comments
98
+
99
+
100
+ def check_staged_diff() -> Optional[str]:
101
+ """
102
+ Check the staged diff for low-quality comments.
103
+ Returns a warning message if issues found, None otherwise.
104
+ """
105
+ import subprocess
106
+
107
+ try:
108
+ # Get the staged diff
109
+ result = subprocess.run(
110
+ ["git", "diff", "--cached", "--unified=0"], capture_output=True, text=True, timeout=10
111
+ )
112
+
113
+ if result.returncode != 0:
114
+ return None # Can't get diff, allow commit
115
+
116
+ diff_text = result.stdout
117
+ if not diff_text:
118
+ return None # No staged changes
119
+
120
+ # Extract and check comments
121
+ added_comments = extract_added_comments_from_diff(diff_text)
122
+ low_quality = []
123
+
124
+ for filename, comment in added_comments:
125
+ if is_low_quality_comment(comment):
126
+ low_quality.append((filename, comment))
127
+
128
+ if low_quality:
129
+ warning = "⚠️ **Comment Quality Check Failed**\n\n"
130
+ warning += "The following comments appear to just restate the code:\n\n"
131
+ for filename, comment in low_quality[:5]: # Limit to 5 examples
132
+ warning += f"- `{filename}`: `{comment[:50]}...`\n"
133
+ warning += "\n**Good comments explain WHY, not WHAT.**\n"
134
+ warning += "Consider removing or improving these comments.\n\n"
135
+ warning += "To proceed anyway, use: `git commit --no-verify`"
136
+ return warning
137
+
138
+ return None
139
+
140
+ except subprocess.TimeoutExpired:
141
+ return None # Timeout, allow commit
142
+ except FileNotFoundError:
143
+ return None # Git not found, allow commit
144
+ except Exception:
145
+ return None # Any other error, allow commit
146
+
147
+
148
+ def is_git_commit_command(command: str) -> bool:
149
+ """Check if the command is a git commit or push."""
150
+ command_lower = command.lower()
151
+ return any(
152
+ pattern in command_lower
153
+ for pattern in [
154
+ "git commit",
155
+ "git push",
156
+ ]
157
+ )
158
+
159
+
160
+ def main():
161
+ try:
162
+ hook_input = json.load(sys.stdin)
163
+ except (json.JSONDecodeError, EOFError):
164
+ return 0
165
+
166
+ tool_name = hook_input.get("tool_name", "")
167
+ tool_input = hook_input.get("tool_input", {})
168
+
169
+ # Only check Bash commands
170
+ if tool_name != "Bash":
171
+ return 0
172
+
173
+ command = tool_input.get("command", "")
174
+
175
+ # Only check git commit/push commands
176
+ if not is_git_commit_command(command):
177
+ return 0
178
+
179
+ # Check for low-quality comments in staged diff
180
+ warning = check_staged_diff()
181
+
182
+ if warning:
183
+ # Output warning to stderr (shown to user)
184
+ print(warning, file=sys.stderr)
185
+ # Return 0 to allow but warn, or 2 to block
186
+ # We'll warn but allow - users can use --no-verify to skip
187
+ return 0
188
+
189
+ return 0
190
+
191
+
192
+ if __name__ == "__main__":
193
+ sys.exit(main())
@@ -0,0 +1,38 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ from pathlib import Path
5
+
6
+ def main():
7
+ try:
8
+ data = json.load(sys.stdin)
9
+ prompt = data.get("prompt", "")
10
+ except Exception:
11
+ return
12
+
13
+ cwd = Path(os.environ.get("CLAUDE_CWD", "."))
14
+
15
+ # Files to look for
16
+ context_files = ["AGENTS.md", "README.md", "CLAUDE.md"]
17
+ found_context = ""
18
+
19
+ for f in context_files:
20
+ path = cwd / f
21
+ if path.exists():
22
+ try:
23
+ content = path.read_text()
24
+ found_context += f"\n\n--- LOCAL CONTEXT: {f} ---\n{content}\n"
25
+ break # Only use one for brevity
26
+ except Exception:
27
+ pass
28
+
29
+ if found_context:
30
+ # Prepend context to prompt
31
+ # We wrap the user prompt to distinguish it
32
+ new_prompt = f"{found_context}\n\n[USER PROMPT]\n{prompt}"
33
+ print(new_prompt)
34
+ else:
35
+ print(prompt)
36
+
37
+ if __name__ == "__main__":
38
+ main()
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Context Monitor Hook - Pre-emptive Compact at 70%
4
+
5
+ Fires on UserPromptSubmit to monitor context usage.
6
+ At 70%: Generates a pre-emptive compact summary.
7
+ At 85%: Critical warning.
8
+
9
+ This hook provides PROACTIVE context management, not reactive.
10
+ """
11
+
12
+ import json
13
+ import sys
14
+ from pathlib import Path
15
+ from datetime import datetime
16
+
17
+ # Context estimation constants
18
+ MAX_CONTEXT_TOKENS = 200000
19
+ CHARS_PER_TOKEN = 4
20
+ PREEMPTIVE_THRESHOLD = 0.70 # 70%
21
+ CRITICAL_THRESHOLD = 0.85 # 85%
22
+
23
+ # State tracking
24
+ STATE_DIR = Path.home() / ".claude" / "state"
25
+ CONTEXT_STATE_FILE = STATE_DIR / "context_monitor.json"
26
+
27
+
28
+ def estimate_tokens(text: str) -> int:
29
+ """Estimate token count from character count."""
30
+ return len(text) // CHARS_PER_TOKEN
31
+
32
+
33
+ def get_usage_percentage(text: str) -> float:
34
+ """Calculate context window usage as percentage."""
35
+ tokens = estimate_tokens(text)
36
+ return tokens / MAX_CONTEXT_TOKENS
37
+
38
+
39
+ def ensure_state_dir():
40
+ """Ensure state directory exists."""
41
+ STATE_DIR.mkdir(parents=True, exist_ok=True)
42
+
43
+
44
+ def get_last_compact_tokens() -> int:
45
+ """Get token count at last compact recommendation."""
46
+ try:
47
+ if CONTEXT_STATE_FILE.exists():
48
+ state = json.loads(CONTEXT_STATE_FILE.read_text())
49
+ return state.get("last_compact_tokens", 0)
50
+ except (json.JSONDecodeError, IOError):
51
+ pass
52
+ return 0
53
+
54
+
55
+ def set_last_compact_tokens(tokens: int):
56
+ """Record token count at compact recommendation."""
57
+ ensure_state_dir()
58
+ try:
59
+ state = {"last_compact_tokens": tokens, "timestamp": datetime.now(timezone.utc).isoformat()}
60
+ CONTEXT_STATE_FILE.write_text(json.dumps(state))
61
+ except IOError:
62
+ pass
63
+
64
+
65
+ def generate_compact_summary_prompt(conversation: str, usage_pct: float) -> str:
66
+ """Generate the summary request to inject into response."""
67
+ return f"""
68
+ <system-reminder>
69
+ **[PRE-EMPTIVE CONTEXT OPTIMIZATION - {usage_pct:.1%} USAGE]**
70
+
71
+ Context window at {usage_pct:.1%} capacity. To prevent forced truncation, generate a summary NOW.
72
+
73
+ REQUIRED: Before continuing, output a structured summary:
74
+
75
+ ```
76
+ ## Context Summary (Pre-emptive Compact)
77
+
78
+ ### Current Task
79
+ [What are we working on right now?]
80
+
81
+ ### Key Decisions Made
82
+ - [Decision 1]
83
+ - [Decision 2]
84
+
85
+ ### Files Modified/Created
86
+ - [file1.py]: [what changed]
87
+ - [file2.py]: [what changed]
88
+
89
+ ### Critical Constraints
90
+ - [Any MUST NOT or NEVER rules]
91
+ - [Architectural decisions]
92
+
93
+ ### Next Steps
94
+ 1. [Immediate next action]
95
+ 2. [Following action]
96
+ ```
97
+
98
+ This summary will be preserved when automatic compaction occurs.
99
+ </system-reminder>
100
+ """
101
+
102
+
103
+ def main():
104
+ """Main hook entry point."""
105
+ try:
106
+ hook_input = json.load(sys.stdin)
107
+ except (json.JSONDecodeError, EOFError):
108
+ return 0
109
+
110
+ # Get the full conversation/prompt
111
+ conversation = hook_input.get("prompt", "")
112
+ if not conversation:
113
+ return 0
114
+
115
+ # Calculate usage
116
+ tokens = estimate_tokens(conversation)
117
+ usage = get_usage_percentage(conversation)
118
+
119
+ # Check if we already triggered at this token level (avoid spam)
120
+ last_compact = get_last_compact_tokens()
121
+
122
+ # Only trigger once per ~10% increase
123
+ token_threshold = int(MAX_CONTEXT_TOKENS * 0.10) # ~20k tokens
124
+ already_triggered = (tokens - last_compact) < token_threshold
125
+
126
+ if usage >= CRITICAL_THRESHOLD:
127
+ # Critical warning - always show
128
+ print(f"\n⚠️ **CRITICAL: Context at {usage:.1%}** - Forced compaction imminent!", file=sys.stderr)
129
+ print(" Generate summary NOW or context will be truncated.", file=sys.stderr)
130
+
131
+ if not already_triggered:
132
+ set_last_compact_tokens(tokens)
133
+ # Inject summary request
134
+ summary_prompt = generate_compact_summary_prompt(conversation, usage)
135
+ print(summary_prompt, file=sys.stderr)
136
+
137
+ elif usage >= PREEMPTIVE_THRESHOLD:
138
+ # Pre-emptive optimization
139
+ if not already_triggered:
140
+ print(f"\n📊 **Context at {usage:.1%}** - Pre-emptive optimization recommended", file=sys.stderr)
141
+ print(f" Estimated tokens: {tokens:,} / {MAX_CONTEXT_TOKENS:,}", file=sys.stderr)
142
+ print(" Headroom remaining: ~{:.0f}%".format((1 - usage) * 100), file=sys.stderr)
143
+
144
+ set_last_compact_tokens(tokens)
145
+ # Inject summary request
146
+ summary_prompt = generate_compact_summary_prompt(conversation, usage)
147
+ print(summary_prompt, file=sys.stderr)
148
+
149
+ return 0
150
+
151
+
152
+ if __name__ == "__main__":
153
+ sys.exit(main())
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ def get_project_dir():
9
+ return Path(os.environ.get("CLAUDE_CWD", "."))
10
+
11
+
12
+ def get_dependency_graph():
13
+ graph_file = get_project_dir() / ".claude/task_dependencies.json"
14
+ if graph_file.exists():
15
+ try:
16
+ return json.loads(graph_file.read_text())
17
+ except Exception:
18
+ pass
19
+ return {"dependencies": {}}
20
+
21
+
22
+ def save_dependency_graph(graph):
23
+ graph_file = get_project_dir() / ".claude/task_dependencies.json"
24
+ graph_file.parent.mkdir(parents=True, exist_ok=True)
25
+ graph_file.write_text(json.dumps(graph, indent=2))
26
+
27
+
28
+ DEPENDENCY_KEYWORDS = ["after", "depends on", "requires", "once", "when", "then"]
29
+ PARALLEL_KEYWORDS = ["also", "meanwhile", "simultaneously", "and", "plus"]
30
+
31
+
32
+ def parse_todo_dependencies(todos):
33
+ dependencies = {}
34
+
35
+ for todo in todos:
36
+ todo_id = todo.get("id")
37
+ content = todo.get("content", "").lower()
38
+
39
+ has_dependency = any(kw in content for kw in DEPENDENCY_KEYWORDS)
40
+ is_parallel = any(kw in content for kw in PARALLEL_KEYWORDS)
41
+
42
+ dependencies[todo_id] = {
43
+ "deps": [],
44
+ "independent": not has_dependency,
45
+ "parallel_safe": is_parallel or not has_dependency,
46
+ }
47
+
48
+ return dependencies
49
+
50
+
51
+ def main():
52
+ try:
53
+ hook_input = json.load(sys.stdin)
54
+ except (json.JSONDecodeError, EOFError):
55
+ return 0
56
+
57
+ tool_name = hook_input.get("tool_name", "")
58
+ if tool_name != "TodoWrite":
59
+ return 0
60
+
61
+ tool_input = hook_input.get("tool_input", {})
62
+ todos = tool_input.get("todos", [])
63
+
64
+ graph = get_dependency_graph()
65
+ dependencies = parse_todo_dependencies(todos)
66
+ graph["dependencies"] = dependencies
67
+ save_dependency_graph(graph)
68
+
69
+ return 0
70
+
71
+
72
+ if __name__ == "__main__":
73
+ sys.exit(main())
@@ -0,0 +1,46 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import re
5
+
6
+ def main():
7
+ # Claude Code PostToolUse inputs via Environment Variables
8
+ tool_name = os.environ.get("CLAUDE_TOOL_NAME")
9
+
10
+ # We only care about Edit/MultiEdit
11
+ if tool_name not in ["Edit", "MultiEdit"]:
12
+ return
13
+
14
+ # Read from stdin (Claude Code passes the tool response via stdin for some hook types,
15
+ # but for PostToolUse it's often better to check the environment variable if available.
16
+ # Actually, the summary says input is a JSON payload.
17
+ try:
18
+ data = json.load(sys.stdin)
19
+ tool_response = data.get("tool_response", "")
20
+ except Exception:
21
+ # Fallback to direct string if not JSON
22
+ return
23
+
24
+ # Error patterns
25
+ error_patterns = [
26
+ r"oldString not found",
27
+ r"oldString matched multiple times",
28
+ r"line numbers out of range"
29
+ ]
30
+
31
+ recovery_needed = any(re.search(p, tool_response, 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
+ # For PostToolUse, stdout is captured and appended/replaces output
40
+ print(tool_response + correction)
41
+ else:
42
+ # No change
43
+ print(tool_response)
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+
8
+
9
+ def get_project_dir():
10
+ return Path(os.environ.get("CLAUDE_CWD", "."))
11
+
12
+
13
+ def get_execution_state():
14
+ state_file = get_project_dir() / ".claude/execution_state.json"
15
+ if state_file.exists():
16
+ try:
17
+ return json.loads(state_file.read_text())
18
+ except Exception:
19
+ pass
20
+
21
+ return {
22
+ "last_10_tools": [],
23
+ "last_task_spawn_index": -1,
24
+ "pending_todos": 0,
25
+ "parallel_mode_active": False,
26
+ "last_updated": None,
27
+ }
28
+
29
+
30
+ def save_execution_state(state):
31
+ state_file = get_project_dir() / ".claude/execution_state.json"
32
+ state_file.parent.mkdir(parents=True, exist_ok=True)
33
+ state["last_updated"] = datetime.now().isoformat()
34
+ state_file.write_text(json.dumps(state, indent=2))
35
+
36
+
37
+ def main():
38
+ try:
39
+ hook_input = json.load(sys.stdin)
40
+ except (json.JSONDecodeError, EOFError):
41
+ return 0
42
+
43
+ tool_name = hook_input.get("tool_name", "")
44
+
45
+ state = get_execution_state()
46
+ state["last_10_tools"].append(tool_name)
47
+ state["last_10_tools"] = state["last_10_tools"][-10:]
48
+
49
+ if tool_name == "Task":
50
+ try:
51
+ index = len(state["last_10_tools"]) - 1
52
+ state["last_task_spawn_index"] = index
53
+ except:
54
+ pass
55
+
56
+ if tool_name == "TodoWrite":
57
+ tool_input = hook_input.get("tool_input", {})
58
+ todos = tool_input.get("todos", [])
59
+ pending = sum(1 for t in todos if t.get("status") == "pending")
60
+ state["pending_todos"] = pending
61
+ state["parallel_mode_active"] = pending >= 2
62
+
63
+ save_execution_state(state)
64
+ return 0
65
+
66
+
67
+ if __name__ == "__main__":
68
+ sys.exit(main())
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Notification hook for agent spawn messages.
4
+
5
+ Fires on Notification events to output user-friendly messages about
6
+ which agent was spawned, what model it uses, and what task it's doing.
7
+
8
+ Format: spawned {agent_type}:{model}('{description}')
9
+ Example: spawned delphi:gpt-5.2-medium('Debug xyz code')
10
+ """
11
+
12
+ import json
13
+ import sys
14
+ from typing import Optional, Dict, Any
15
+
16
+
17
+ # Agent display model mappings
18
+ AGENT_DISPLAY_MODELS = {
19
+ "explore": "gemini-3-flash",
20
+ "dewey": "gemini-3-flash",
21
+ "document_writer": "gemini-3-flash",
22
+ "multimodal": "gemini-3-flash",
23
+ "frontend": "gemini-3-pro-high",
24
+ "delphi": "gpt-5.2-medium",
25
+ "planner": "opus-4.5",
26
+ "code-reviewer": "sonnet-4.5",
27
+ "debugger": "sonnet-4.5",
28
+ "_default": "sonnet-4.5",
29
+ }
30
+
31
+
32
+ def extract_agent_info(message: str) -> Optional[Dict[str, str]]:
33
+ """
34
+ Extract agent spawn information from notification message.
35
+
36
+ Looks for patterns like:
37
+ - "Agent explore spawned for task..."
38
+ - "Spawned delphi agent: description"
39
+ - Task tool delegation messages
40
+ """
41
+ message_lower = message.lower()
42
+
43
+ # Try to extract agent type from message
44
+ agent_type = None
45
+ description = ""
46
+
47
+ for agent in AGENT_DISPLAY_MODELS.keys():
48
+ if agent == "_default":
49
+ continue
50
+ if agent in message_lower:
51
+ agent_type = agent
52
+ # Extract description after agent name
53
+ idx = message_lower.find(agent)
54
+ description = message[idx + len(agent):].strip()[:60]
55
+ break
56
+
57
+ if not agent_type:
58
+ return None
59
+
60
+ # Clean up description
61
+ description = description.strip(":-() ")
62
+ if not description:
63
+ description = "task delegated"
64
+
65
+ display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
66
+
67
+ return {
68
+ "agent_type": agent_type,
69
+ "model": display_model,
70
+ "description": description,
71
+ }
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
+ # Get notification message
82
+ message = hook_input.get("message", "")
83
+ notification_type = hook_input.get("notification_type", "")
84
+
85
+ # Only process agent-related notifications
86
+ agent_keywords = ["agent", "spawn", "delegat", "task"]
87
+ if not any(kw in message.lower() for kw in agent_keywords):
88
+ return 0
89
+
90
+ # Extract agent info
91
+ agent_info = extract_agent_info(message)
92
+ if not agent_info:
93
+ return 0
94
+
95
+ # Format and output
96
+ output = f"spawned {agent_info['agent_type']}:{agent_info['model']}('{agent_info['description']}')"
97
+ print(output, file=sys.stderr)
98
+
99
+ return 0
100
+
101
+
102
+ if __name__ == "__main__":
103
+ sys.exit(main())