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.
- mcp_bridge/__init__.py +1 -1
- mcp_bridge/auth/__init__.py +16 -6
- mcp_bridge/auth/cli.py +202 -11
- mcp_bridge/auth/oauth.py +1 -2
- mcp_bridge/auth/openai_oauth.py +4 -7
- mcp_bridge/auth/token_store.py +0 -1
- mcp_bridge/cli/__init__.py +1 -1
- mcp_bridge/cli/install_hooks.py +503 -107
- mcp_bridge/cli/session_report.py +0 -3
- mcp_bridge/config/__init__.py +2 -2
- mcp_bridge/config/hook_config.py +3 -5
- mcp_bridge/config/rate_limits.py +108 -13
- mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
- mcp_bridge/hooks/__init__.py +14 -4
- mcp_bridge/hooks/agent_reminder.py +4 -4
- mcp_bridge/hooks/auto_slash_command.py +5 -5
- mcp_bridge/hooks/budget_optimizer.py +2 -2
- mcp_bridge/hooks/claude_limits_hook.py +114 -0
- mcp_bridge/hooks/comment_checker.py +3 -4
- mcp_bridge/hooks/compaction.py +2 -2
- mcp_bridge/hooks/context.py +2 -1
- mcp_bridge/hooks/context_monitor.py +2 -2
- mcp_bridge/hooks/delegation_policy.py +85 -0
- mcp_bridge/hooks/directory_context.py +3 -3
- mcp_bridge/hooks/edit_recovery.py +3 -2
- mcp_bridge/hooks/edit_recovery_policy.py +49 -0
- mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
- mcp_bridge/hooks/events.py +160 -0
- mcp_bridge/hooks/git_noninteractive.py +4 -4
- mcp_bridge/hooks/keyword_detector.py +8 -10
- mcp_bridge/hooks/manager.py +35 -22
- mcp_bridge/hooks/notification_hook.py +13 -6
- mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
- mcp_bridge/hooks/parallel_enforcer.py +5 -5
- mcp_bridge/hooks/parallel_execution.py +22 -10
- mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
- mcp_bridge/hooks/pre_compact.py +8 -9
- mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
- mcp_bridge/hooks/preemptive_compaction.py +2 -3
- mcp_bridge/hooks/routing_notifications.py +80 -0
- mcp_bridge/hooks/rules_injector.py +11 -19
- mcp_bridge/hooks/session_idle.py +4 -4
- mcp_bridge/hooks/session_notifier.py +4 -4
- mcp_bridge/hooks/session_recovery.py +4 -5
- mcp_bridge/hooks/stravinsky_mode.py +1 -1
- mcp_bridge/hooks/subagent_stop.py +1 -3
- mcp_bridge/hooks/task_validator.py +2 -2
- mcp_bridge/hooks/tmux_manager.py +7 -8
- mcp_bridge/hooks/todo_delegation.py +4 -1
- mcp_bridge/hooks/todo_enforcer.py +180 -10
- mcp_bridge/hooks/truncation_policy.py +37 -0
- mcp_bridge/hooks/truncator.py +1 -2
- mcp_bridge/metrics/cost_tracker.py +115 -0
- mcp_bridge/native_search.py +93 -0
- mcp_bridge/native_watcher.py +118 -0
- mcp_bridge/notifications.py +3 -4
- mcp_bridge/orchestrator/enums.py +11 -0
- mcp_bridge/orchestrator/router.py +165 -0
- mcp_bridge/orchestrator/state.py +32 -0
- mcp_bridge/orchestrator/visualization.py +14 -0
- mcp_bridge/orchestrator/wisdom.py +34 -0
- mcp_bridge/prompts/__init__.py +1 -8
- mcp_bridge/prompts/dewey.py +1 -1
- mcp_bridge/prompts/planner.py +2 -4
- mcp_bridge/prompts/stravinsky.py +53 -31
- mcp_bridge/proxy/__init__.py +0 -0
- mcp_bridge/proxy/client.py +70 -0
- mcp_bridge/proxy/model_server.py +157 -0
- mcp_bridge/routing/__init__.py +43 -0
- mcp_bridge/routing/config.py +250 -0
- mcp_bridge/routing/model_tiers.py +135 -0
- mcp_bridge/routing/provider_state.py +261 -0
- mcp_bridge/routing/task_classifier.py +190 -0
- mcp_bridge/server.py +363 -34
- mcp_bridge/server_tools.py +298 -6
- mcp_bridge/tools/__init__.py +19 -8
- mcp_bridge/tools/agent_manager.py +549 -799
- mcp_bridge/tools/background_tasks.py +13 -17
- mcp_bridge/tools/code_search.py +54 -51
- mcp_bridge/tools/continuous_loop.py +0 -1
- mcp_bridge/tools/dashboard.py +19 -0
- mcp_bridge/tools/find_code.py +296 -0
- mcp_bridge/tools/init.py +1 -0
- mcp_bridge/tools/list_directory.py +42 -0
- mcp_bridge/tools/lsp/__init__.py +8 -8
- mcp_bridge/tools/lsp/manager.py +51 -28
- mcp_bridge/tools/lsp/tools.py +98 -65
- mcp_bridge/tools/model_invoke.py +1047 -152
- mcp_bridge/tools/mux_client.py +75 -0
- mcp_bridge/tools/project_context.py +1 -2
- mcp_bridge/tools/query_classifier.py +132 -49
- mcp_bridge/tools/read_file.py +84 -0
- mcp_bridge/tools/replace.py +45 -0
- mcp_bridge/tools/run_shell_command.py +38 -0
- mcp_bridge/tools/search_enhancements.py +347 -0
- mcp_bridge/tools/semantic_search.py +677 -92
- mcp_bridge/tools/session_manager.py +0 -2
- mcp_bridge/tools/skill_loader.py +0 -1
- mcp_bridge/tools/task_runner.py +5 -7
- mcp_bridge/tools/templates.py +3 -3
- mcp_bridge/tools/tool_search.py +331 -0
- mcp_bridge/tools/write_file.py +29 -0
- mcp_bridge/update_manager.py +33 -37
- mcp_bridge/update_manager_pypi.py +6 -8
- mcp_bridge/utils/cache.py +82 -0
- mcp_bridge/utils/process.py +71 -0
- mcp_bridge/utils/session_state.py +51 -0
- mcp_bridge/utils/truncation.py +76 -0
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/METADATA +84 -35
- stravinsky-0.4.66.dist-info/RECORD +198 -0
- {stravinsky-0.4.18.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
- stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
- stravinsky_claude_assets/agents/HOOKS.md +437 -0
- stravinsky_claude_assets/agents/code-reviewer.md +210 -0
- stravinsky_claude_assets/agents/comment_checker.md +580 -0
- stravinsky_claude_assets/agents/debugger.md +254 -0
- stravinsky_claude_assets/agents/delphi.md +495 -0
- stravinsky_claude_assets/agents/dewey.md +248 -0
- stravinsky_claude_assets/agents/explore.md +1198 -0
- stravinsky_claude_assets/agents/frontend.md +472 -0
- stravinsky_claude_assets/agents/implementation-lead.md +164 -0
- stravinsky_claude_assets/agents/momus.md +464 -0
- stravinsky_claude_assets/agents/research-lead.md +141 -0
- stravinsky_claude_assets/agents/stravinsky.md +730 -0
- stravinsky_claude_assets/commands/delphi.md +9 -0
- stravinsky_claude_assets/commands/dewey.md +54 -0
- stravinsky_claude_assets/commands/git-master.md +112 -0
- stravinsky_claude_assets/commands/index.md +49 -0
- stravinsky_claude_assets/commands/publish.md +86 -0
- stravinsky_claude_assets/commands/review.md +73 -0
- stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
- stravinsky_claude_assets/commands/str/agent_list.md +56 -0
- stravinsky_claude_assets/commands/str/agent_output.md +92 -0
- stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
- stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
- stravinsky_claude_assets/commands/str/cancel.md +51 -0
- stravinsky_claude_assets/commands/str/clean.md +97 -0
- stravinsky_claude_assets/commands/str/continue.md +38 -0
- stravinsky_claude_assets/commands/str/index.md +199 -0
- stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
- stravinsky_claude_assets/commands/str/search.md +205 -0
- stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
- stravinsky_claude_assets/commands/str/stats.md +71 -0
- stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
- stravinsky_claude_assets/commands/str/unwatch.md +42 -0
- stravinsky_claude_assets/commands/str/watch.md +45 -0
- stravinsky_claude_assets/commands/strav.md +53 -0
- stravinsky_claude_assets/commands/stravinsky.md +292 -0
- stravinsky_claude_assets/commands/verify.md +60 -0
- stravinsky_claude_assets/commands/version.md +5 -0
- stravinsky_claude_assets/hooks/README.md +248 -0
- stravinsky_claude_assets/hooks/comment_checker.py +193 -0
- stravinsky_claude_assets/hooks/context.py +38 -0
- stravinsky_claude_assets/hooks/context_monitor.py +153 -0
- stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
- stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
- stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
- stravinsky_claude_assets/hooks/notification_hook.py +103 -0
- stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
- stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
- stravinsky_claude_assets/hooks/pre_compact.py +123 -0
- stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
- stravinsky_claude_assets/hooks/session_recovery.py +263 -0
- stravinsky_claude_assets/hooks/stop_hook.py +89 -0
- stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
- stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
- stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
- stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
- stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
- stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
- stravinsky_claude_assets/hooks/truncator.py +23 -0
- stravinsky_claude_assets/rules/deployment_safety.md +51 -0
- stravinsky_claude_assets/rules/integration_wiring.md +89 -0
- stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
- stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
- stravinsky_claude_assets/settings.json +152 -0
- stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
- stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
- stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
- stravinsky_claude_assets/task_dependencies.json +34 -0
- stravinsky-0.4.18.dist-info/RECORD +0 -88
- {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() ->
|
|
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() ->
|
|
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
|
|
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:
|
|
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() ->
|
|
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:
|
|
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.
|
|
4
|
-
Summary: MCP
|
|
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
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
224
|
+
## Model Proxy (NEW in v0.4.58)
|
|
179
225
|
|
|
180
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
##
|
|
251
|
+
## Features
|
|
204
252
|
|
|
205
253
|
| Category | Tools |
|
|
206
254
|
| ---------------- | ---------------------------------------------------------------------------------- |
|
|
207
|
-
| **Model Invoke** | `invoke_gemini`, `
|
|
208
|
-
| **Environment** | `get_project_context`, `
|
|
209
|
-
| **
|
|
210
|
-
| **
|
|
211
|
-
| **
|
|
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
|
|