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
@@ -6,13 +6,11 @@ Logs all checks to ~/.stravinsky/update.log for debugging and monitoring.
6
6
  Non-blocking background update checks on server startup.
7
7
  """
8
8
 
9
- import asyncio
10
9
  import logging
11
10
  import subprocess
12
11
  import sys
13
12
  from datetime import datetime, timedelta
14
13
  from pathlib import Path
15
- from typing import Optional
16
14
 
17
15
  # Get the logger for this module
18
16
  logger = logging.getLogger(__name__)
@@ -21,7 +19,7 @@ logger = logging.getLogger(__name__)
21
19
  from mcp_bridge import __version__
22
20
 
23
21
 
24
- def _get_stravinsky_home() -> Optional[Path]:
22
+ def _get_stravinsky_home() -> Path | None:
25
23
  """Get or create ~/.stravinsky directory."""
26
24
  home_dir = Path.home() / ".stravinsky"
27
25
  try:
@@ -32,7 +30,7 @@ def _get_stravinsky_home() -> Optional[Path]:
32
30
  return None
33
31
 
34
32
 
35
- def _get_last_check_time() -> Optional[datetime]:
33
+ def _get_last_check_time() -> datetime | None:
36
34
  """
37
35
  Read the last update check time from ~/.stravinsky/update.log.
38
36
 
@@ -49,7 +47,7 @@ def _get_last_check_time() -> Optional[datetime]:
49
47
  return None
50
48
 
51
49
  # Read the last line (most recent check)
52
- with open(update_log, "r") as f:
50
+ with open(update_log) as f:
53
51
  lines = f.readlines()
54
52
  if not lines:
55
53
  return None
@@ -72,7 +70,7 @@ def _get_last_check_time() -> Optional[datetime]:
72
70
  return None
73
71
 
74
72
 
75
- def _should_check(last_check_time: Optional[datetime]) -> bool:
73
+ def _should_check(last_check_time: datetime | None) -> bool:
76
74
  """
77
75
  Determine if enough time has passed since the last check.
78
76
 
@@ -92,7 +90,7 @@ def _should_check(last_check_time: Optional[datetime]) -> bool:
92
90
  return time_since_last_check >= timedelta(hours=24)
93
91
 
94
92
 
95
- def _get_pypi_version() -> Optional[str]:
93
+ def _get_pypi_version() -> str | None:
96
94
  """
97
95
  Fetch the latest version of stravinsky from PyPI.
98
96
 
@@ -173,7 +171,7 @@ def _compare_versions(current: str, latest: str) -> bool:
173
171
  return False
174
172
 
175
173
 
176
- def _log_check(current: str, latest: Optional[str], status: str) -> None:
174
+ def _log_check(current: str, latest: str | None, status: str) -> None:
177
175
  """
178
176
  Log the update check to ~/.stravinsky/update.log.
179
177
 
@@ -0,0 +1,82 @@
1
+ import time
2
+ import os
3
+ import threading
4
+ from typing import Any, Dict, Optional, Tuple
5
+
6
+ class IOCache:
7
+ """
8
+ Lightweight, thread-safe in-memory cache for I/O operations.
9
+ Supports TTL-based expiration and manual invalidation.
10
+ """
11
+ _instance = None
12
+ _lock = threading.Lock()
13
+
14
+ def __init__(self, ttl: float = 5.0):
15
+ self.ttl = ttl
16
+ self._cache: Dict[str, Tuple[Any, float]] = {}
17
+ self._cache_lock = threading.Lock()
18
+
19
+ @classmethod
20
+ def get_instance(cls):
21
+ """Get the singleton instance of IOCache."""
22
+ with cls._lock:
23
+ if cls._instance is None:
24
+ cls._instance = cls()
25
+ return cls._instance
26
+
27
+ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None:
28
+ """
29
+ Store a value in the cache.
30
+
31
+ Args:
32
+ key: Cache key (e.g., file path or command).
33
+ value: Data to cache.
34
+ ttl: Optional override for the default TTL.
35
+ """
36
+ expiry = time.time() + (ttl if ttl is not None else self.ttl)
37
+ with self._cache_lock:
38
+ self._cache[key] = (value, expiry)
39
+
40
+ def get(self, key: str) -> Optional[Any]:
41
+ """
42
+ Retrieve a value from the cache if it hasn't expired.
43
+
44
+ Returns:
45
+ The cached value, or None if missing or expired.
46
+ """
47
+ with self._cache_lock:
48
+ if key not in self._cache:
49
+ return None
50
+
51
+ value, expiry = self._cache[key]
52
+ if time.time() > expiry:
53
+ del self._cache[key]
54
+ return None
55
+
56
+ return value
57
+
58
+ def invalidate(self, key: str) -> None:
59
+ """Remove a specific key from the cache."""
60
+ with self._cache_lock:
61
+ if key in self._cache:
62
+ del self._cache[key]
63
+
64
+ def invalidate_path(self, path: str) -> None:
65
+ """
66
+ Invalidate all cache entries related to a specific file path.
67
+ Matches keys for read_file, list_dir, etc.
68
+ """
69
+ # Use realpath to resolve symlinks (crucial for macOS /var -> /private/var)
70
+ abs_path = os.path.realpath(path)
71
+ with self._cache_lock:
72
+ keys_to_del = [
73
+ k for k in self._cache.keys()
74
+ if abs_path in k
75
+ ]
76
+ for k in keys_to_del:
77
+ del self._cache[k]
78
+
79
+ def clear(self) -> None:
80
+ """Clear all cached entries."""
81
+ with self._cache_lock:
82
+ self._cache.clear()
@@ -0,0 +1,71 @@
1
+ import asyncio
2
+ import os
3
+ from dataclasses import dataclass
4
+ from typing import Optional, List, Union
5
+
6
+ @dataclass
7
+ class ProcessResult:
8
+ returncode: int
9
+ stdout: str
10
+ stderr: str
11
+
12
+ async def async_execute(
13
+ cmd: Union[str, List[str]],
14
+ cwd: Optional[str] = None,
15
+ timeout: Optional[float] = None
16
+ ) -> ProcessResult:
17
+ """
18
+ Execute a subprocess asynchronously.
19
+
20
+ Args:
21
+ cmd: Command string or list of arguments.
22
+ cwd: Working directory.
23
+ timeout: Maximum execution time in seconds.
24
+
25
+ Returns:
26
+ ProcessResult containing exit code, stdout, and stderr.
27
+
28
+ Raises:
29
+ asyncio.TimeoutError: If the process exceeds the timeout.
30
+ """
31
+ if isinstance(cmd, str):
32
+ # Use shell wrapper for string commands
33
+ process = await asyncio.create_subprocess_exec(
34
+ "bash", "-c", cmd,
35
+ stdout=asyncio.subprocess.PIPE,
36
+ stderr=asyncio.subprocess.PIPE,
37
+ cwd=cwd
38
+ )
39
+ else:
40
+ # Use direct execution for list of arguments
41
+ process = await asyncio.create_subprocess_exec(
42
+ *cmd,
43
+ stdout=asyncio.subprocess.PIPE,
44
+ stderr=asyncio.subprocess.PIPE,
45
+ cwd=cwd
46
+ )
47
+
48
+ try:
49
+ if timeout:
50
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(process.communicate(), timeout=timeout)
51
+ else:
52
+ stdout_bytes, stderr_bytes = await process.communicate()
53
+
54
+ except asyncio.TimeoutError:
55
+ try:
56
+ # Kill process group if started with bash
57
+ if isinstance(cmd, str):
58
+ process.kill()
59
+ else:
60
+ process.kill()
61
+ except ProcessLookupError:
62
+ pass # Already gone
63
+ # Wait for it to actually die to avoid zombies
64
+ await process.wait()
65
+ raise
66
+
67
+ return ProcessResult(
68
+ returncode=process.returncode if process.returncode is not None else 0,
69
+ stdout=stdout_bytes.decode('utf-8', errors='replace'),
70
+ stderr=stderr_bytes.decode('utf-8', errors='replace')
71
+ )
@@ -0,0 +1,51 @@
1
+ import json
2
+ import os
3
+ import time
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+
7
+ STATE_DIR = Path.home() / ".stravinsky" / "state"
8
+
9
+
10
+ def ensure_state_dir():
11
+ """Ensure the state directory exists."""
12
+ STATE_DIR.mkdir(parents=True, exist_ok=True)
13
+
14
+
15
+ def get_session_state_path(session_id: str) -> Path:
16
+ """Get the path to the state file for a given session."""
17
+ ensure_state_dir()
18
+ # Sanitize session_id to avoid path traversal
19
+ safe_id = "".join(c for c in session_id if c.isalnum() or c in ("-", "_"))
20
+ return STATE_DIR / f"session_{safe_id}.json"
21
+
22
+
23
+ def get_session_state(session_id: Optional[str] = None) -> dict[str, Any]:
24
+ """Get the state for a session."""
25
+ if session_id is None:
26
+ session_id = get_current_session_id()
27
+
28
+ path = get_session_state_path(session_id)
29
+ if not path.exists():
30
+ return {}
31
+ try:
32
+ return json.loads(path.read_text())
33
+ except Exception:
34
+ return {}
35
+
36
+
37
+ def update_session_state(updates: dict[str, Any], session_id: Optional[str] = None):
38
+ """Update the state for a session."""
39
+ if session_id is None:
40
+ session_id = get_current_session_id()
41
+
42
+ state = get_session_state(session_id)
43
+ state.update(updates)
44
+ state["updated_at"] = time.time()
45
+ path = get_session_state_path(session_id)
46
+ path.write_text(json.dumps(state, indent=2))
47
+
48
+
49
+ def get_current_session_id() -> str:
50
+ """Get the current session ID from environment or default."""
51
+ return os.environ.get("CLAUDE_SESSION_ID", "default")
@@ -0,0 +1,76 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+ import logging
4
+
5
+ try:
6
+ import stravinsky_native
7
+ from stravinsky_native import truncator
8
+ NATIVE_AVAILABLE = True
9
+ except ImportError:
10
+ NATIVE_AVAILABLE = False
11
+ logging.warning("stravinsky_native not found. Native truncation unavailable.")
12
+
13
+ class TruncationStrategy(Enum):
14
+ MIDDLE = "middle"
15
+ TAIL = "tail"
16
+ AUTO_TAIL = "auto_tail" # New line-based strategy
17
+
18
+ def truncate_output(
19
+ text: str,
20
+ limit: int = 20000,
21
+ strategy: TruncationStrategy = TruncationStrategy.MIDDLE,
22
+ custom_guidance: Optional[str] = None
23
+ ) -> str:
24
+ """
25
+ Truncates text to a specific limit using the chosen strategy.
26
+
27
+ Args:
28
+ text: The string to truncate.
29
+ limit: Max characters allowed.
30
+ strategy: How to truncate (MIDDLE or TAIL).
31
+ custom_guidance: Optional extra message for the agent.
32
+ """
33
+ if len(text) <= limit:
34
+ return text
35
+
36
+ # Standard guidance messages
37
+ guidance = "\n\n[Output truncated. "
38
+ if custom_guidance:
39
+ guidance += custom_guidance + " "
40
+
41
+ if strategy == TruncationStrategy.TAIL:
42
+ # Show the END of the file
43
+ truncated_text = text[-limit:]
44
+ msg = f"{guidance}Showing last {limit} characters. Use offset/limit parameters to read specific parts of the file.]"
45
+ return f"... [TRUNCATED] ...\n{truncated_text}{msg}"
46
+
47
+ else: # MIDDLE strategy
48
+ # Show start and end, snip the middle
49
+ half_limit = limit // 2
50
+ start_part = text[:half_limit]
51
+ end_part = text[-half_limit:]
52
+ msg = f"{guidance}Showing first and last {half_limit} characters. Use offset/limit parameters to read specific parts of the file.]"
53
+ return f"{start_part}\n\n[... content truncated ...]\n\n{end_part}{msg}"
54
+
55
+ def auto_tail_logs(content: str, head_lines: int = 100, tail_lines: int = 100) -> str:
56
+ """
57
+ Smart truncation for logs using native Rust implementation.
58
+ Keeps the first N lines (setup/config) and last M lines (recent errors).
59
+ """
60
+ if NATIVE_AVAILABLE:
61
+ try:
62
+ return truncator.auto_tail(content, head_lines, tail_lines)
63
+ except Exception as e:
64
+ logging.error(f"Native truncation failed: {e}")
65
+ # Fallback to python implementation
66
+
67
+ # Python fallback
68
+ lines = content.splitlines()
69
+ if len(lines) <= head_lines + tail_lines:
70
+ return content
71
+
72
+ head = "\n".join(lines[:head_lines])
73
+ tail = "\n".join(lines[-tail_lines:])
74
+ hidden = len(lines) - (head_lines + tail_lines)
75
+
76
+ return f"{head}\n\n<... {hidden} lines truncated (fallback) ...>\n\n{tail}"
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stravinsky
3
- Version: 0.4.18
4
- Summary: MCP Bridge for Claude Code with Multi-Model Support. Install globally: claude mcp add --scope user stravinsky -- uvx stravinsky. Add to CLAUDE.md: See https://pypi.org/project/stravinsky/
3
+ Version: 0.4.66
4
+ Summary: A sophisticated MCP bridge for Claude Code orchestration
5
5
  Project-URL: Repository, https://github.com/GratefulDave/stravinsky
6
6
  Project-URL: Issues, https://github.com/GratefulDave/stravinsky/issues
7
7
  Author: Stravinsky Team
@@ -11,8 +11,10 @@ Requires-Python: <3.14,>=3.11
11
11
  Requires-Dist: aiofiles>=23.1.0
12
12
  Requires-Dist: chromadb>=0.6.0
13
13
  Requires-Dist: cryptography>=41.0.0
14
+ Requires-Dist: fastapi>=0.128.0
14
15
  Requires-Dist: google-auth-oauthlib>=1.0.0
15
16
  Requires-Dist: google-auth>=2.20.0
17
+ Requires-Dist: google-genai>=0.2.0
16
18
  Requires-Dist: httpx>=0.24.0
17
19
  Requires-Dist: jedi-language-server>=0.41.0
18
20
  Requires-Dist: jedi>=0.19.2
@@ -27,9 +29,11 @@ Requires-Dist: psutil>=5.9.0
27
29
  Requires-Dist: pydantic>=2.0.0
28
30
  Requires-Dist: pygls>=1.3.0
29
31
  Requires-Dist: python-dotenv>=1.0.0
32
+ Requires-Dist: rank-bm25==0.2.2
30
33
  Requires-Dist: rich>=13.0.0
31
34
  Requires-Dist: ruff>=0.14.10
32
35
  Requires-Dist: tenacity>=8.5.0
36
+ Requires-Dist: uvicorn>=0.40.0
33
37
  Requires-Dist: watchdog~=5.0.0
34
38
  Provides-Extra: dev
35
39
  Requires-Dist: mypy>=1.10.0; extra == 'dev'
@@ -62,6 +66,8 @@ Description-Content-Type: text/markdown
62
66
  - 🔄 **Hook-Based Delegation** - PreToolUse hooks enforce delegation patterns with hard boundaries (exit code 2)
63
67
  - 📝 **LSP Integration** - Full Language Server Protocol support with persistent servers (35x speedup), code refactoring, and advanced navigation
64
68
  - 🔍 **AST-Aware Search** - Structural code search and refactoring with ast-grep
69
+ - 🧠 **Tier-Aware Routing** - Multi-provider fallback system (Claude 4.5, GPT 5.2, Gemini 3) with OAuth-first priority
70
+ - 🧠 **Tier-Aware Routing** - Multi-provider fallback system (Claude 4.5, GPT 5.2, Gemini 3) with OAuth-first priority
65
71
  - 🧠 **Semantic Code Search** - Natural language queries with local embeddings (ChromaDB + Ollama)
66
72
  - ⚡ **Cost-Optimized Routing** - Free/cheap agents (explore, dewey) always async, expensive (delphi) only when needed
67
73
 
@@ -110,6 +116,12 @@ claude mcp add --scope user stravinsky -- stravinsky
110
116
 
111
117
  ### Authentication
112
118
 
119
+ Stravinsky supports **two authentication methods** for Gemini (OAuth-first with automatic API fallback):
120
+
121
+ #### Option 1: OAuth (Recommended - Primary Method)
122
+
123
+ **Full OAuth flow** with automatic token refresh:
124
+
113
125
  ```bash
114
126
  # Login to Google (Gemini)
115
127
  stravinsky-auth login gemini
@@ -124,6 +136,40 @@ stravinsky-auth status
124
136
  stravinsky-auth logout gemini
125
137
  ```
126
138
 
139
+ **When to use:** Primary method for all use cases. Provides automatic fallback to API key on rate limits.
140
+
141
+ **Rate Limit Architecture:**
142
+ - **OAuth**: Lower rate limits, but convenient (no API key management)
143
+ - **API Key**: **Tier 3 high quotas** - automatic fallback for heavy usage
144
+
145
+ **Auth Priority:**
146
+ 1. OAuth is tried FUWT (if configured)
147
+ 2. On OAuth 429 rate limit → **Automatically switch to API key** (Tier 3 quotas) for 5 minutes
148
+ 3. After 5-minute cooldown → Retry OAuth
149
+
150
+ #### Option 2: API Key (**Tier 3 High Quotas** - Automatic Fallback)
151
+
152
+ **CRITICAL**: Use a **Tier 3 API key** for high-volume usage. OAuth has lower limits and automatically falls back to your API key.
153
+
154
+ **Setup** - add your Gemini API key to `.env`:
155
+
156
+ ```bash
157
+ # Add to your .env file (or export in shell)
158
+ GEMINI_API_KEY=your_tier_3_api_key_here
159
+
160
+ # Or use GOOGLE_API_KEY (same effect)
161
+ GOOGLE_API_KEY=your_tier_3_api_key_here
162
+ ```
163
+
164
+ **Get your API key:**
165
+ 1. Visit [Google AI Studio](https://aistudio.google.com/app/apikey)
166
+ 2. Create an API key (ensure it's Tier 3 for high quotas)
167
+ 3. Add to `.env` file in your project root
168
+
169
+ **When to use:**
170
+ - **Primary**: Automatic fallback when OAuth rate limits are exhausted (recommended for all users)
171
+ - Development/testing without OAuth setup
172
+
127
173
  **Secure Token Storage:**
128
174
 
129
175
  OAuth tokens are stored securely with automatic fallback:
@@ -154,7 +200,7 @@ cat ~/.config/python_keyring/keyringrc.cfg
154
200
  stravinsky-auth login gemini
155
201
  ```
156
202
 
157
- This configuration stores tokens in encrypted files at `~/.stravinsky/tokens/` instead of macOS Keychain, eliminating password prompts across terminal sessions. See [docs/KEYRING_AUTH_FIX.md](docs/KEYRING_AUTH_FIX.md) for detailed information.
203
+ This configuration stores tokens in encrypted files at `~/.stravinsky/tokens/` instead of macOS Keychain, eliminating password prompts across terminal sessions. See [docs/reports/KEYRING_AUTH_FIX.md](docs/reports/KEYRING_AUTH_FIX.md) for detailed information.
158
204
 
159
205
  ### Slash Commands
160
206
 
@@ -164,54 +210,57 @@ Slash commands are discovered from:
164
210
 
165
211
  Commands can be organized in subdirectories (e.g., `.claude/commands/strav/stravinsky.md`).
166
212
 
167
- ## Native Subagent Architecture
213
+ ## Native Subagent Architecture (PRIMARY)
168
214
 
169
- Stravinsky uses **native Claude Code subagents** (.claude/agents/) with automatic delegation:
215
+ Stravinsky is architected around the **Native Subagent Pattern**. This design delegates specialized tasks to standalone Claude Code agents, enabling massive parallelism and reducing orchestrator cognitive load.
170
216
 
171
- ### How It Works
217
+ ### Why Native Subagents?
218
+ - **True Parallelism**: Spawn multiple agents (Explore, Dewey, Reviewer) simultaneously using the `Task` tool.
219
+ - **Zero Bridge Latency**: Agents interact directly with the project codebase using native tools.
220
+ - **Specialized Context**: Each agent type has a bespoke system prompt and focused toolset.
172
221
 
173
- 1. **Auto-Delegation**: Claude Code automatically delegates complex tasks to the Stravinsky orchestrator
174
- 2. **Hook-Based Control**: PreToolUse hooks intercept direct tool calls and enforce delegation patterns
175
- 3. **Parallel Execution**: Task tool enables true parallel execution of specialist agents
176
- 4. **Multi-Model Routing**: Specialists use invoke_gemini/openai MCP tools for multi-model access
222
+ ---
177
223
 
178
- ### Agent Types
224
+ ## Model Proxy (NEW in v0.4.58)
179
225
 
180
- | Agent | Model | Cost | Use For |
181
- |-------|-------|------|---------|
182
- | **stravinsky** | Claude Sonnet 4.5 (32k thinking) | Moderate | Auto-delegating orchestrator (primary) |
183
- | **explore** | Gemini 3 Flash (via MCP) | Free | Code search, always async |
184
- | **dewey** | Gemini 3 Flash + WebSearch | Cheap | Documentation research, always async |
185
- | **code-reviewer** | Claude Sonnet (native) | Cheap | Quality analysis, always async |
186
- | **debugger** | Claude Sonnet (native) | Medium | Root cause (after 2+ failures) |
187
- | **frontend** | Gemini 3 Pro High (via MCP) | Medium | ALL visual changes (blocking) |
188
- | **delphi** | GPT-5.2 Medium (via MCP) | Expensive | Architecture (after 3+ failures) |
226
+ To solve the "Head-of-Line Blocking" problem in MCP stdio, Stravinsky now includes a dedicated **Model Proxy**.
189
227
 
190
- ### Delegation Rules (oh-my-opencode Pattern)
228
+ ### The Problem:
229
+ When Claude calls `invoke_gemini` directly through MCP, the entire stdio communication channel is blocked until the model finishes generating. This prevents parallel execution and makes the UI feel sluggish.
191
230
 
192
- - **Always Async**: explore, dewey, code-reviewer (free/cheap)
193
- - **Blocking**: debugger (2+ failures), frontend (ALL visual), delphi (3+ failures or architecture)
194
- - **Never Work Alone**: Orchestrator blocks Read/Grep/Bash via PreToolUse hooks
231
+ ### The Solution:
232
+ The **Stravinsky Model Proxy** runs as a separate FastAPI process. Model requests are offloaded to this process, allowing the MCP bridge to handle other tool calls immediately.
195
233
 
196
- ### ULTRATHINK / ULTRAWORK
234
+ ### Performance Benefits:
235
+ - **Async Concurrency**: Handle 20+ simultaneous model calls with <10ms overhead.
236
+ - **Zero CLI Blocking**: Keep the main Claude CLI responsive during long generations.
237
+ - **Observability**: Real-time request tracking, Process-Time headers, and trace IDs.
197
238
 
198
- - **ULTRATHINK**: Engage exhaustive deep reasoning with extended thinking budget (32k tokens)
199
- - **ULTRAWORK**: Maximum parallel execution - spawn all async agents immediately
239
+ ### Usage:
240
+ 1. **Start the Proxy** in a separate terminal:
241
+ ```bash
242
+ stravinsky-proxy
243
+ ```
244
+ 2. **Enable in Claude**: Set the environment variable:
245
+ ```bash
246
+ export STRAVINSKY_USE_PROXY=true
247
+ ```
200
248
 
201
249
  ---
202
250
 
203
- ## Tools (40)
251
+ ## Features
204
252
 
205
253
  | Category | Tools |
206
254
  | ---------------- | ---------------------------------------------------------------------------------- |
207
- | **Model Invoke** | `invoke_gemini`, `invoke_openai`, `get_system_health` |
208
- | **Environment** | `get_project_context`, `task_spawn`, `task_status`, `task_list` |
209
- | **Agents** | `agent_spawn`, `agent_output`, `agent_cancel`, `agent_list`, `agent_progress`, `agent_retry` |
210
- | **Code Search** | `ast_grep_search`, `ast_grep_replace`, `grep_search`, `glob_files` |
211
- | **Semantic** | `semantic_search`, `semantic_index`, `semantic_stats`, `cancel_indexing`, `delete_index` |
255
+ | **Model Invoke** | `invoke_gemini`, `invoke_gemini_agentic`, `invoke_openai` (3 tools) |
256
+ | **Environment** | `get_project_context`, `get_system_health`, `stravinsky_version`, `system_restart`, `semantic_health`, `lsp_health` (6 tools) |
257
+ | **Background Tasks** | `task_spawn`, `task_status`, `task_list` (3 tools) |
258
+ | **Agents** | `agent_spawn`, `agent_output`, `agent_cancel`, `agent_list`, `agent_progress`, `agent_retry` (6 tools) |
259
+ | **Code Search** | `ast_grep_search`, `ast_grep_replace`, `grep_search`, `glob_files` (4 tools) |
260
+ | **Semantic** | `semantic_search`, `hybrid_search`, `semantic_index`, `semantic_stats`, `start_file_watcher`, `stop_file_watcher`, `cancel_indexing`, `delete_index`, `list_file_watchers`, `multi_query_search`, `decomposed_search`, `enhanced_search` (12 tools) |
212
261
  | **LSP** | `lsp_diagnostics`, `lsp_hover`, `lsp_goto_definition`, `lsp_find_references`, `lsp_document_symbols`, `lsp_workspace_symbols`, `lsp_prepare_rename`, `lsp_rename`, `lsp_code_actions`, `lsp_code_action_resolve`, `lsp_extract_refactor`, `lsp_servers` (12 tools) |
213
- | **Sessions** | `session_list`, `session_read`, `session_search` |
214
- | **Skills** | `skill_list`, `skill_get` |
262
+ | **Sessions** | `session_list`, `session_read`, `session_search` (3 tools) |
263
+ | **Skills** | `skill_list`, `skill_get` (2 tools) |
215
264
 
216
265
  ### LSP Performance & Refactoring
217
266