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
@@ -17,16 +17,12 @@ Hooks provide enhanced Claude Code behavior including:
17
17
  """
18
18
 
19
19
  import json
20
- import os
21
- import shutil
22
20
  import sys
23
21
  from pathlib import Path
24
- from typing import Dict, List, Any
25
-
26
22
 
27
23
  # Hook file contents - these will be written to ~/.claude/hooks/
28
24
  HOOKS = {
29
- "truncator.py": '''import os
25
+ "truncator.py": """import os
30
26
  import sys
31
27
  import json
32
28
 
@@ -49,8 +45,7 @@ def main():
49
45
 
50
46
  if __name__ == "__main__":
51
47
  main()
52
- ''',
53
-
48
+ """,
54
49
  "edit_recovery.py": '''#!/usr/bin/env python3
55
50
  """Edit error recovery hook - detects edit failures and forces file reading."""
56
51
  import json
@@ -94,8 +89,7 @@ def main():
94
89
  if __name__ == "__main__":
95
90
  sys.exit(main())
96
91
  ''',
97
-
98
- "context.py": '''import os
92
+ "context.py": """import os
99
93
  import sys
100
94
  import json
101
95
  from pathlib import Path
@@ -133,8 +127,7 @@ def main():
133
127
 
134
128
  if __name__ == "__main__":
135
129
  main()
136
- ''',
137
-
130
+ """,
138
131
  "parallel_execution.py": '''#!/usr/bin/env python3
139
132
  """
140
133
  UserPromptSubmit hook: Pre-emptive parallel execution enforcement.
@@ -157,11 +150,10 @@ STRAVINSKY_MODE_FILE = Path.home() / ".stravinsky_mode"
157
150
  def detect_stravinsky_invocation(prompt):
158
151
  """Detect if /stravinsky skill is being invoked."""
159
152
  patterns = [
160
- r'/stravinsky',
161
- r'<command-name>/stravinsky</command-name>',
162
- r'stravinsky orchestrator',
163
- r'ultrawork',
164
- r'ultrathink',
153
+ r"/stravinsky",
154
+ r"<command-name>/stravinsky</command-name>",
155
+ r"stravinsky orchestrator",
156
+ r"\bultrawork\b",
165
157
  ]
166
158
  prompt_lower = prompt.lower()
167
159
  return any(re.search(p, prompt_lower) for p in patterns)
@@ -247,7 +239,6 @@ Task(subagent_type="Explore", prompt="Task 3 details", description="Task 3", run
247
239
  if __name__ == "__main__":
248
240
  sys.exit(main())
249
241
  ''',
250
-
251
242
  "todo_continuation.py": '''#!/usr/bin/env python3
252
243
  """
253
244
  UserPromptSubmit hook: Todo Continuation Enforcer
@@ -339,7 +330,6 @@ def main():
339
330
  if __name__ == "__main__":
340
331
  sys.exit(main())
341
332
  ''',
342
-
343
333
  "todo_delegation.py": '''#!/usr/bin/env python3
344
334
  """
345
335
  PostToolUse hook for TodoWrite: CRITICAL parallel execution enforcer.
@@ -429,7 +419,6 @@ Your NEXT action MUST be multiple Task() calls, one for each independent TODO.
429
419
  if __name__ == "__main__":
430
420
  sys.exit(main())
431
421
  ''',
432
-
433
422
  "stravinsky_mode.py": '''#!/usr/bin/env python3
434
423
  """
435
424
  Stravinsky Mode Enforcer Hook
@@ -575,7 +564,6 @@ To exit stravinsky mode, run:
575
564
  if __name__ == "__main__":
576
565
  main()
577
566
  ''',
578
-
579
567
  "tool_messaging.py": '''#!/usr/bin/env python3
580
568
  """
581
569
  PostToolUse hook for user-friendly tool messaging.
@@ -741,7 +729,6 @@ def main():
741
729
  if __name__ == "__main__":
742
730
  main()
743
731
  ''',
744
-
745
732
  "notification_hook.py": '''#!/usr/bin/env python3
746
733
  """
747
734
  Notification hook for agent spawn messages.
@@ -846,7 +833,6 @@ def main():
846
833
  if __name__ == "__main__":
847
834
  sys.exit(main())
848
835
  ''',
849
-
850
836
  "subagent_stop.py": '''#!/usr/bin/env python3
851
837
  """
852
838
  SubagentStop hook: Handler for agent/subagent completion events.
@@ -946,7 +932,6 @@ def main():
946
932
  if __name__ == "__main__":
947
933
  sys.exit(main())
948
934
  ''',
949
-
950
935
  "pre_compact.py": '''#!/usr/bin/env python3
951
936
  """
952
937
  PreCompact hook: Context preservation before compaction.
@@ -1024,7 +1009,7 @@ def log_compaction(preserved: List[str], stravinsky_active: bool):
1024
1009
  ensure_state_dir()
1025
1010
 
1026
1011
  entry = {
1027
- "timestamp": datetime.utcnow().isoformat(),
1012
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1028
1013
  "preserved_count": len(preserved),
1029
1014
  "stravinsky_mode": stravinsky_active,
1030
1015
  "preview": [p[:50] for p in preserved[:3]],
@@ -1070,6 +1055,456 @@ def main():
1070
1055
 
1071
1056
  if __name__ == "__main__":
1072
1057
  sys.exit(main())
1058
+ ''',
1059
+ "stop_hook.py": '''#!/usr/bin/env -S uv run --script
1060
+
1061
+ # /// script
1062
+ # requires-python = ">=3.8"
1063
+ # dependencies = [
1064
+ # "anthropic",
1065
+ # "python-dotenv",
1066
+ # ]
1067
+
1068
+ """
1069
+ Fix Stop Hook - Calls stravinsky_metrics.py on Stop events.
1070
+
1071
+ This hook is triggered by .claude/settings.json on Stop/SubagentStop.
1072
+ It queries Stravinsky's cost tracker and sends a StravinskyMetrics event to the dashboard.
1073
+ """
1074
+
1075
+ import os
1076
+ import sys
1077
+ import subprocess
1078
+ from pathlib import Path
1079
+
1080
+ # Add hooks directory to path for script imports
1081
+ hooks_dir = Path(__file__).parent
1082
+ sys.path.insert(0, str(hooks_dir))
1083
+
1084
+
1085
+ def send_stravinsky_metrics(session_id: str) -> bool:
1086
+ """Call stravinsky_metrics.py to query and send metrics to dashboard."""
1087
+ script_path = hooks_dir / "stravinsky_metrics.py"
1088
+
1089
+ if not script_path.exists():
1090
+ print(f"Error: stravinsky_metrics.py not found at {script_path}", file=sys.stderr)
1091
+ return False
1092
+
1093
+ try:
1094
+ # Build command
1095
+ cmd = [
1096
+ "uv",
1097
+ "run",
1098
+ "--script",
1099
+ str(script_path),
1100
+ "--session-id",
1101
+ session_id,
1102
+ "--summarize", # Generate summary and send as event
1103
+ ]
1104
+
1105
+ # Run command
1106
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10, check=False)
1107
+
1108
+ if result.returncode != 0:
1109
+ print(
1110
+ f"Error: stravinsky_metrics.py failed with exit code {result.returncode}",
1111
+ file=sys.stderr,
1112
+ )
1113
+ print(f"stderr: {result.stderr}", file=sys.stderr)
1114
+ return False
1115
+
1116
+ return True
1117
+
1118
+ except subprocess.TimeoutExpired:
1119
+ print(f"Error: stravinsky_metrics.py timed out after 10 seconds", file=sys.stderr)
1120
+ return False
1121
+
1122
+ except Exception as e:
1123
+ print(f"Error: stravinsky_metrics.py execution failed: {e}", file=sys.stderr)
1124
+ return False
1125
+
1126
+
1127
+ def main():
1128
+ # Get session_id from environment or use default
1129
+ session_id = os.environ.get("CLAUDE_SESSION_ID", "default")
1130
+
1131
+ # Determine hook type
1132
+ hook_type = os.environ.get("CLAUDE_HOOK_EVENT_TYPE", "Stop")
1133
+
1134
+ # Send metrics to dashboard
1135
+ success = send_stravinsky_metrics(session_id)
1136
+
1137
+ if not success:
1138
+ sys.exit(1)
1139
+
1140
+ print(
1141
+ f"āœ“ Successfully sent Stravinsky metrics for session {session_id} to dashboard",
1142
+ file=sys.stderr,
1143
+ )
1144
+
1145
+
1146
+ if __name__ == "__main__":
1147
+ main()
1148
+ ''',
1149
+ "stravinsky_metrics.py": '''#!/usr/bin/env python3
1150
+ """
1151
+ Stravinsky Metrics Hook Script
1152
+
1153
+ Queries Stravinsky's internal metrics and sends them to the observability dashboard.
1154
+ This hook is triggered on Stop/SubagentStop events.
1155
+
1156
+ Usage:
1157
+ stravinsky_metrics.py --session-id <session_id>
1158
+
1159
+ Environment Variables:
1160
+ CLAUDE_SESSION_ID: Fallback session ID if --session-id not provided
1161
+
1162
+ Output:
1163
+ Sends StravinskyMetrics event to dashboard via send_event.py
1164
+
1165
+ Metrics Collected:
1166
+ - Total session cost (USD)
1167
+ - Total tokens used
1168
+ - Per-agent cost and token breakdown
1169
+ - Per-model cost and token breakdown (TODO)
1170
+ - Agent count (active/total) (TODO)
1171
+
1172
+ Error Handling:
1173
+ Returns non-zero exit code on error to prevent blocking Claude Code operations.
1174
+ """
1175
+
1176
+ import sys
1177
+ import os
1178
+ import json
1179
+ import argparse
1180
+ import subprocess
1181
+ from pathlib import Path
1182
+ from datetime import datetime
1183
+ from typing import Dict, Any
1184
+ from mcp_bridge.metrics.cost_tracker import CostTracker
1185
+
1186
+
1187
+ def load_usage_data(session_id: str) -> Dict[str, Any]:
1188
+ """
1189
+ Load usage data from Stravinsky's cost tracker.
1190
+
1191
+ Uses CostTracker.get_session_summary() to get metrics for a session.
1192
+
1193
+ Args:
1194
+ session_id: Claude Code session ID
1195
+
1196
+ Returns:
1197
+ Dictionary with metrics data
1198
+ """
1199
+ summary = CostTracker.get_instance().get_session_summary(session_id)
1200
+
1201
+ # Transform CostTracker summary to expected format
1202
+ metrics = {
1203
+ "total_cost": summary.get("total_cost", 0),
1204
+ "total_tokens": summary.get("total_tokens", 0),
1205
+ "by_agent": summary.get("by_agent", {}),
1206
+ "by_model": {}, # Extract from summary if available
1207
+ }
1208
+
1209
+ return metrics
1210
+
1211
+
1212
+ def send_metrics_event(session_id: str, metrics: Dict[str, Any]) -> bool:
1213
+ """
1214
+ Send metrics event to observability dashboard via send_event.py.
1215
+
1216
+ Args:
1217
+ session_id: Claude Code session ID
1218
+ metrics: Metrics data dictionary
1219
+
1220
+ Returns:
1221
+ True if successful, False otherwise
1222
+ """
1223
+ hook_dir = Path(__file__).parent
1224
+ send_event_script = hook_dir / "send_event.py"
1225
+
1226
+ if not send_event_script.exists():
1227
+ print(f"Error: send_event.py not found at {send_event_script}", file=sys.stderr)
1228
+ return False
1229
+
1230
+ event_data = {
1231
+ "session_id": session_id,
1232
+ "metrics": metrics,
1233
+ "timestamp": datetime.now().isoformat(),
1234
+ }
1235
+
1236
+ cmd = [
1237
+ "uv",
1238
+ "run",
1239
+ str(send_event_script),
1240
+ "--source-app",
1241
+ "stravinsky",
1242
+ "--event-type",
1243
+ "StravinskyMetrics",
1244
+ "--summarize",
1245
+ ]
1246
+
1247
+ try:
1248
+ result = subprocess.run(
1249
+ cmd, input=json.dumps(event_data), capture_output=True, text=True, timeout=10
1250
+ )
1251
+
1252
+ if result.returncode != 0:
1253
+ print(f"Error from send_event.py: {result.stderr}", file=sys.stderr)
1254
+ return False
1255
+
1256
+ return True
1257
+
1258
+ except subprocess.TimeoutExpired:
1259
+ print("Error: send_event.py timed out", file=sys.stderr)
1260
+ return False
1261
+ except Exception as e:
1262
+ print(f"Error running send_event.py: {e}", file=sys.stderr)
1263
+ return False
1264
+
1265
+
1266
+ def main():
1267
+ parser = argparse.ArgumentParser(description="Query Stravinsky metrics and send to dashboard")
1268
+ parser.add_argument(
1269
+ "--session-id", help="Claude Code session ID (falls back to CLAUDE_SESSION_ID env var)"
1270
+ )
1271
+ parser.add_argument(
1272
+ "--dry-run",
1273
+ action="store_true",
1274
+ help="Print metrics without sending to dashboard (for testing)",
1275
+ )
1276
+
1277
+ args = parser.parse_args()
1278
+
1279
+ session_id = args.session_id or os.environ.get("CLAUDE_SESSION_ID", "default")
1280
+
1281
+ if not session_id or session_id == "default":
1282
+ print("Warning: No session ID provided, using 'default'", file=sys.stderr)
1283
+
1284
+ metrics = load_usage_data(session_id)
1285
+
1286
+ if metrics is None:
1287
+ print("Error: Failed to load metrics", file=sys.stderr)
1288
+ sys.exit(1)
1289
+
1290
+ if args.dry_run:
1291
+ print(json.dumps(metrics, indent=2))
1292
+ sys.exit(0)
1293
+
1294
+ print(f"Stravinsky Metrics for session {session_id}:", file=sys.stderr)
1295
+ print(f" Total Cost: ${metrics['total_cost']:.6f}", file=sys.stderr)
1296
+ print(f" Total Tokens: {metrics['total_tokens']:,}", file=sys.stderr)
1297
+ print(f" Agents: {len(metrics['by_agent'])}", file=sys.stderr)
1298
+
1299
+ success = send_metrics_event(session_id, metrics)
1300
+
1301
+ if success:
1302
+ print("Metrics sent to dashboard successfully", file=sys.stderr)
1303
+ sys.exit(0)
1304
+ else:
1305
+ print("Failed to send metrics to dashboard", file=sys.stderr)
1306
+ sys.exit(1)
1307
+
1308
+
1309
+ if __name__ == "__main__":
1310
+ main()
1311
+ ''',
1312
+ "send_event.py": '''#!/usr/bin/env -S uv run --script
1313
+ # /// script
1314
+ # requires-python = ">=3.8"
1315
+ # dependencies = [
1316
+ # "anthropic",
1317
+ # "python-dotenv",
1318
+ # ]
1319
+ # ///
1320
+
1321
+ """
1322
+ Multi-Agent Observability Hook Script
1323
+ Sends Claude Code hook events to the observability server.
1324
+ """
1325
+
1326
+ import json
1327
+ import sys
1328
+ import os
1329
+ import argparse
1330
+ import urllib.request
1331
+ import urllib.error
1332
+ import logging
1333
+ from datetime import datetime
1334
+ from pathlib import Path
1335
+ # from utils.summarizer import generate_event_summary
1336
+ # from utils.model_extractor import get_model_from_transcript
1337
+ # Note: commented out utils imports to prevent failure if utils are missing
1338
+ # If you need full functionality, ensure utils/ directory is also installed.
1339
+
1340
+ def generate_event_summary(event_data):
1341
+ return None
1342
+
1343
+ def get_model_from_transcript(session_id, transcript_path):
1344
+ return ""
1345
+
1346
+ log_dir = Path.home() / ".claude/hooks/logs"
1347
+ log_dir.mkdir(parents=True, exist_ok=True)
1348
+ logging.basicConfig(
1349
+ filename=log_dir / "event_sender.log",
1350
+ level=logging.DEBUG,
1351
+ format="%(asctime)s - %(levelname)s - %(message)s",
1352
+ )
1353
+
1354
+
1355
+ def send_event_to_server(event_data, server_url="http://localhost:4000/events"):
1356
+ """Send event data to the observability server."""
1357
+ try:
1358
+ # Prepare the request
1359
+ req = urllib.request.Request(
1360
+ server_url,
1361
+ data=json.dumps(event_data).encode("utf-8"),
1362
+ headers={"Content-Type": "application/json", "User-Agent": "Claude-Code-Hook/1.0"},
1363
+ )
1364
+
1365
+ # Send the request
1366
+ with urllib.request.urlopen(req, timeout=5) as response:
1367
+ if response.status == 200:
1368
+ return True
1369
+ else:
1370
+ logging.debug(f"Server returned status: {response.status}")
1371
+ return False
1372
+
1373
+ except urllib.error.URLError as e:
1374
+ logging.error(f"Failed to send event: {e}")
1375
+ return False
1376
+ except Exception as e:
1377
+ logging.error(f"Unexpected error: {e}")
1378
+ return False
1379
+
1380
+
1381
+ def main():
1382
+ # Parse command line arguments
1383
+ parser = argparse.ArgumentParser(
1384
+ description="Send Claude Code hook events to observability server"
1385
+ )
1386
+ parser.add_argument("--source-app", required=True, help="Source application name")
1387
+ parser.add_argument(
1388
+ "--event-type", required=True, help="Hook event type (PreToolUse, PostToolUse, etc.)"
1389
+ )
1390
+ parser.add_argument("--server-url", default="http://localhost:4000/events", help="Server URL")
1391
+ parser.add_argument(
1392
+ "--add-chat", action="store_true", help="Include chat transcript if available"
1393
+ )
1394
+ parser.add_argument("--summarize", action="store_true", help="Generate AI summary of the event")
1395
+
1396
+ # Stravinsky-specific metrics (optional)
1397
+ parser.add_argument("--cost", type=float, help="Cost in USD (Stravinsky metrics)")
1398
+ parser.add_argument(
1399
+ "--prompt-tokens", type=int, help="Prompt token count (Stravinsky metrics)"
1400
+ )
1401
+ parser.add_argument(
1402
+ "--completion-tokens", type=int, help="Completion token count (Stravinsky metrics)"
1403
+ )
1404
+ parser.add_argument("--agent-type", type=str, help="Agent type (Stravinsky metrics)")
1405
+ parser.add_argument("--task-id", type=str, help="Task tracking ID (Stravinsky metrics)")
1406
+ parser.add_argument(
1407
+ "--hierarchy-level", type=int, help="Nesting depth (Stravinsky metrics)")
1408
+ parser.add_argument(
1409
+ "--parent-session-id", type=str, help="Parent session ID for hierarchy (Stravinsky metrics)")
1410
+
1411
+ args = parser.parse_args()
1412
+
1413
+ try:
1414
+ # Read hook data from stdin
1415
+ if not sys.stdin.isatty():
1416
+ input_data = json.load(sys.stdin)
1417
+ else:
1418
+ input_data = {}
1419
+ except json.JSONDecodeError as e:
1420
+ # print(f"Failed to parse JSON input: {e}", file=sys.stderr)
1421
+ # Fail gracefully if no input
1422
+ input_data = {}
1423
+
1424
+ # Extract model name from transcript (with caching)
1425
+ session_id = input_data.get("session_id", "unknown")
1426
+ transcript_path = input_data.get("transcript_path", "")
1427
+ model_name = ""
1428
+ if transcript_path:
1429
+ model_name = get_model_from_transcript(session_id, transcript_path)
1430
+
1431
+ # Build Stravinsky metrics (if any flags provided)
1432
+ stravinsky_metrics = {}
1433
+ if any(
1434
+ [
1435
+ args.cost,
1436
+ args.prompt_tokens,
1437
+ args.completion_tokens,
1438
+ args.agent_type,
1439
+ args.task_id,
1440
+ args.parent_session_id,
1441
+ args.hierarchy_level,
1442
+ ]
1443
+ ):
1444
+ stravinsky_metrics = {
1445
+ "cost_usd": args.cost,
1446
+ "prompt_tokens": args.prompt_tokens,
1447
+ "completion_tokens": args.completion_tokens,
1448
+ "total_tokens": (args.prompt_tokens or 0) + (args.completion_tokens or 0),
1449
+ "model": model_name,
1450
+ "provider": "anthropic",
1451
+ "agent_type": args.agent_type,
1452
+ "task_id": args.task_id,
1453
+ "parent_session_id": args.parent_session_id,
1454
+ "hierarchy_level": args.hierarchy_level or 0,
1455
+ }
1456
+
1457
+ # Prepare event data for server
1458
+ event_data = {
1459
+ "source_app": args.source_app,
1460
+ "session_id": session_id,
1461
+ "hook_event_type": args.event_type,
1462
+ "payload": input_data,
1463
+ "timestamp": int(datetime.now().timestamp() * 1000),
1464
+ "model_name": model_name,
1465
+ }
1466
+
1467
+ # Add Stravinsky metrics if provided
1468
+ if stravinsky_metrics:
1469
+ event_data["stravinsky"] = stravinsky_metrics
1470
+
1471
+ # Handle --add-chat option
1472
+ if args.add_chat and "transcript_path" in input_data:
1473
+ transcript_path = input_data["transcript_path"]
1474
+ if os.path.exists(transcript_path):
1475
+ # Read .jsonl file and convert to JSON array
1476
+ chat_data = []
1477
+ try:
1478
+ with open(transcript_path, "r") as f:
1479
+ for line in f:
1480
+ line = line.strip()
1481
+ if line:
1482
+ try:
1483
+ chat_data.append(json.loads(line))
1484
+ except json.JSONDecodeError:
1485
+ pass # Skip invalid lines
1486
+
1487
+ # Add chat to event data
1488
+ event_data["chat"] = chat_data
1489
+ except Exception as e:
1490
+ print(f"Failed to read transcript: {e}", file=sys.stderr)
1491
+
1492
+ # Generate summary if requested
1493
+ if args.summarize:
1494
+ summary = generate_event_summary(event_data)
1495
+ if summary:
1496
+ event_data["summary"] = summary
1497
+ # Continue even if summary generation fails
1498
+
1499
+ # Send to server
1500
+ success = send_event_to_server(event_data, args.server_url)
1501
+
1502
+ # Always exit with 0 to not block Claude Code operations
1503
+ sys.exit(0)
1504
+
1505
+
1506
+ if __name__ == "__main__":
1507
+ main()
1073
1508
  ''',
1074
1509
  }
1075
1510
 
@@ -1077,176 +1512,136 @@ if __name__ == "__main__":
1077
1512
  # Hook registration configuration for settings.json
1078
1513
  # IMPORTANT: Uses ~/.claude/hooks/ (global) paths so hooks work in ANY project
1079
1514
  HOOK_REGISTRATIONS = {
1515
+ "Stop": [
1516
+ {
1517
+ "matcher": "*",
1518
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/stop_hook.py"}],
1519
+ }
1520
+ ],
1080
1521
  "Notification": [
1081
1522
  {
1082
1523
  "matcher": "*",
1083
1524
  "hooks": [
1084
- {
1085
- "type": "command",
1086
- "command": "python3 ~/.claude/hooks/notification_hook.py"
1087
- }
1088
- ]
1525
+ {"type": "command", "command": "python3 ~/.claude/hooks/notification_hook.py"}
1526
+ ],
1089
1527
  }
1090
1528
  ],
1091
1529
  "SubagentStop": [
1092
1530
  {
1093
1531
  "matcher": "*",
1094
- "hooks": [
1095
- {
1096
- "type": "command",
1097
- "command": "python3 ~/.claude/hooks/subagent_stop.py"
1098
- }
1099
- ]
1532
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/subagent_stop.py"}],
1100
1533
  }
1101
1534
  ],
1102
1535
  "PreCompact": [
1103
1536
  {
1104
1537
  "matcher": "*",
1105
- "hooks": [
1106
- {
1107
- "type": "command",
1108
- "command": "python3 ~/.claude/hooks/pre_compact.py"
1109
- }
1110
- ]
1538
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/pre_compact.py"}],
1111
1539
  }
1112
1540
  ],
1113
1541
  "PreToolUse": [
1114
1542
  {
1115
1543
  "matcher": "Read,Search,Grep,Bash,Edit,MultiEdit",
1116
- "hooks": [
1117
- {
1118
- "type": "command",
1119
- "command": "python3 ~/.claude/hooks/stravinsky_mode.py"
1120
- }
1121
- ]
1544
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/stravinsky_mode.py"}],
1122
1545
  }
1123
1546
  ],
1124
1547
  "UserPromptSubmit": [
1125
1548
  {
1126
1549
  "matcher": "*",
1127
1550
  "hooks": [
1128
- {
1129
- "type": "command",
1130
- "command": "python3 ~/.claude/hooks/parallel_execution.py"
1131
- },
1132
- {
1133
- "type": "command",
1134
- "command": "python3 ~/.claude/hooks/context.py"
1135
- },
1136
- {
1137
- "type": "command",
1138
- "command": "python3 ~/.claude/hooks/todo_continuation.py"
1139
- }
1140
- ]
1551
+ {"type": "command", "command": "python3 ~/.claude/hooks/parallel_execution.py"},
1552
+ {"type": "command", "command": "python3 ~/.claude/hooks/context.py"},
1553
+ {"type": "command", "command": "python3 ~/.claude/hooks/todo_continuation.py"},
1554
+ ],
1141
1555
  }
1142
1556
  ],
1143
1557
  "PostToolUse": [
1144
1558
  {
1145
1559
  "matcher": "*",
1146
- "hooks": [
1147
- {
1148
- "type": "command",
1149
- "command": "python3 ~/.claude/hooks/truncator.py"
1150
- }
1151
- ]
1560
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/truncator.py"}],
1152
1561
  },
1153
1562
  {
1154
1563
  "matcher": "mcp__stravinsky__*,mcp__grep-app__*,Task",
1155
- "hooks": [
1156
- {
1157
- "type": "command",
1158
- "command": "python3 ~/.claude/hooks/tool_messaging.py"
1159
- }
1160
- ]
1564
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/tool_messaging.py"}],
1161
1565
  },
1162
1566
  {
1163
1567
  "matcher": "Edit,MultiEdit",
1164
- "hooks": [
1165
- {
1166
- "type": "command",
1167
- "command": "python3 ~/.claude/hooks/edit_recovery.py"
1168
- }
1169
- ]
1568
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/edit_recovery.py"}],
1170
1569
  },
1171
1570
  {
1172
1571
  "matcher": "TodoWrite",
1173
- "hooks": [
1174
- {
1175
- "type": "command",
1176
- "command": "python3 ~/.claude/hooks/todo_delegation.py"
1177
- }
1178
- ]
1179
- }
1180
- ]
1572
+ "hooks": [{"type": "command", "command": "python3 ~/.claude/hooks/todo_delegation.py"}],
1573
+ },
1574
+ ],
1181
1575
  }
1182
1576
 
1183
1577
 
1184
1578
  def install_hooks():
1185
1579
  """Install Stravinsky hooks to ~/.claude/hooks/"""
1186
-
1580
+
1187
1581
  # Get home directory
1188
1582
  home = Path.home()
1189
1583
  claude_dir = home / ".claude"
1190
1584
  hooks_dir = claude_dir / "hooks"
1191
1585
  settings_file = claude_dir / "settings.json"
1192
-
1586
+
1193
1587
  print("šŸš€ Stravinsky Hook Installer")
1194
1588
  print("=" * 60)
1195
-
1589
+
1196
1590
  # Create hooks directory if it doesn't exist
1197
1591
  if not hooks_dir.exists():
1198
1592
  print(f"šŸ“ Creating hooks directory: {hooks_dir}")
1199
1593
  hooks_dir.mkdir(parents=True, exist_ok=True)
1200
1594
  else:
1201
1595
  print(f"šŸ“ Hooks directory exists: {hooks_dir}")
1202
-
1596
+
1203
1597
  # Install each hook file
1204
1598
  print(f"\\nšŸ“ Installing {len(HOOKS)} hook files...")
1205
1599
  for filename, content in HOOKS.items():
1206
1600
  hook_path = hooks_dir / filename
1601
+ hook_path.parent.mkdir(parents=True, exist_ok=True)
1207
1602
  print(f" āœ“ {filename}")
1208
1603
  hook_path.write_text(content)
1209
1604
  hook_path.chmod(0o755) # Make executable
1210
-
1605
+
1211
1606
  # Merge hook registrations into settings.json
1212
- print(f"\\nāš™ļø Updating settings.json...")
1213
-
1607
+ print("\\nāš™ļø Updating settings.json...")
1608
+
1214
1609
  # Load existing settings or create new
1215
1610
  if settings_file.exists():
1216
1611
  print(f" šŸ“– Reading existing settings: {settings_file}")
1217
- with settings_file.open('r') as f:
1612
+ with settings_file.open("r") as f:
1218
1613
  settings = json.load(f)
1219
1614
  else:
1220
1615
  print(f" šŸ“ Creating new settings file: {settings_file}")
1221
1616
  settings = {}
1222
-
1617
+
1223
1618
  # Merge hooks configuration
1224
1619
  if "hooks" not in settings:
1225
1620
  settings["hooks"] = {}
1226
-
1621
+
1227
1622
  for hook_type, registrations in HOOK_REGISTRATIONS.items():
1228
1623
  settings["hooks"][hook_type] = registrations
1229
1624
  print(f" āœ“ Registered {hook_type} hooks")
1230
-
1625
+
1231
1626
  # Write updated settings
1232
- with settings_file.open('w') as f:
1627
+ with settings_file.open("w") as f:
1233
1628
  json.dump(settings, f, indent=2)
1234
-
1235
- print(f"\\nāœ… Installation complete!")
1629
+
1630
+ print("\\nāœ… Installation complete!")
1236
1631
  print("=" * 60)
1237
- print(f"\\nšŸ“‹ Installed hooks:")
1238
- for filename in HOOKS.keys():
1632
+ print("\\nšŸ“‹ Installed hooks:")
1633
+ for filename in HOOKS:
1239
1634
  print(f" • {filename}")
1240
-
1241
- print(f"\\nšŸ”§ Hook types registered:")
1242
- for hook_type in HOOK_REGISTRATIONS.keys():
1635
+
1636
+ print("\\nšŸ”§ Hook types registered:")
1637
+ for hook_type in HOOK_REGISTRATIONS:
1243
1638
  print(f" • {hook_type}")
1244
-
1639
+
1245
1640
  print(f"\\nšŸ“ Installation directory: {hooks_dir}")
1246
1641
  print(f"āš™ļø Settings file: {settings_file}")
1247
1642
  print("\\nšŸŽ‰ Stravinsky hooks are now active!")
1248
1643
  print("\\nšŸ’” Tip: Run '/stravinsky' to activate orchestrator mode")
1249
-
1644
+
1250
1645
  return 0
1251
1646
 
1252
1647
 
@@ -1257,6 +1652,7 @@ def main():
1257
1652
  except Exception as e:
1258
1653
  print(f"\\nāŒ Installation failed: {e}", file=sys.stderr)
1259
1654
  import traceback
1655
+
1260
1656
  traceback.print_exc()
1261
1657
  return 1
1262
1658