stravinsky 0.2.67__py3-none-any.whl → 0.4.18__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/token_store.py +113 -11
- mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/hook_config.py +249 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +222 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/__init__.py +8 -3
- mcp_bridge/hooks/manager.py +8 -0
- mcp_bridge/hooks/tool_messaging.py +113 -10
- mcp_bridge/notifications.py +151 -0
- mcp_bridge/server.py +202 -48
- mcp_bridge/server_tools.py +440 -0
- mcp_bridge/tools/__init__.py +22 -18
- mcp_bridge/tools/agent_manager.py +197 -28
- mcp_bridge/tools/code_search.py +16 -2
- mcp_bridge/tools/lsp/__init__.py +7 -0
- mcp_bridge/tools/lsp/manager.py +448 -0
- mcp_bridge/tools/lsp/tools.py +634 -151
- mcp_bridge/tools/model_invoke.py +186 -159
- mcp_bridge/tools/query_classifier.py +323 -0
- mcp_bridge/tools/semantic_search.py +3042 -0
- mcp_bridge/update_manager.py +589 -0
- mcp_bridge/update_manager_pypi.py +299 -0
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.18.dist-info}/METADATA +209 -25
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.18.dist-info}/RECORD +29 -17
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.18.dist-info}/WHEEL +0 -0
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.18.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyPI Update Manager for Stravinsky MCP server.
|
|
3
|
+
|
|
4
|
+
Checks PyPI for new versions with throttling to prevent excessive API calls.
|
|
5
|
+
Logs all checks to ~/.stravinsky/update.log for debugging and monitoring.
|
|
6
|
+
Non-blocking background update checks on server startup.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
# Get the logger for this module
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# Import version from main module
|
|
21
|
+
from mcp_bridge import __version__
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_stravinsky_home() -> Optional[Path]:
|
|
25
|
+
"""Get or create ~/.stravinsky directory."""
|
|
26
|
+
home_dir = Path.home() / ".stravinsky"
|
|
27
|
+
try:
|
|
28
|
+
home_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
return home_dir
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.warning(f"Failed to create ~/.stravinsky directory: {e}")
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _get_last_check_time() -> Optional[datetime]:
|
|
36
|
+
"""
|
|
37
|
+
Read the last update check time from ~/.stravinsky/update.log.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
datetime of last check, or None if file doesn't exist or is invalid
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
home_dir = _get_stravinsky_home()
|
|
44
|
+
if not home_dir:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
update_log = home_dir / "update.log"
|
|
48
|
+
if not update_log.exists():
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# Read the last line (most recent check)
|
|
52
|
+
with open(update_log, "r") as f:
|
|
53
|
+
lines = f.readlines()
|
|
54
|
+
if not lines:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
last_line = lines[-1].strip()
|
|
58
|
+
if not last_line:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
# Parse format: YYYY-MM-DD HH:MM:SS | VERSION_CHECK | ...
|
|
62
|
+
parts = last_line.split(" | ")
|
|
63
|
+
if len(parts) < 1:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
timestamp_str = parts[0]
|
|
67
|
+
last_check = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
|
|
68
|
+
return last_check
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.debug(f"Failed to read last check time: {e}")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _should_check(last_check_time: Optional[datetime]) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Determine if enough time has passed since the last check.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
last_check_time: datetime of last check, or None
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if 24+ hours have passed or no prior check exists
|
|
84
|
+
"""
|
|
85
|
+
if last_check_time is None:
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
now = datetime.now()
|
|
89
|
+
time_since_last_check = now - last_check_time
|
|
90
|
+
|
|
91
|
+
# Check if 24+ hours have passed
|
|
92
|
+
return time_since_last_check >= timedelta(hours=24)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_pypi_version() -> Optional[str]:
|
|
96
|
+
"""
|
|
97
|
+
Fetch the latest version of stravinsky from PyPI.
|
|
98
|
+
|
|
99
|
+
Uses: pip index versions stravinsky
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Version string (e.g., "0.3.10"), or None if unable to fetch
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
# Run: pip index versions stravinsky
|
|
106
|
+
result = subprocess.run(
|
|
107
|
+
[sys.executable, "-m", "pip", "index", "versions", "stravinsky"],
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
timeout=10,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if result.returncode != 0:
|
|
114
|
+
logger.debug(f"pip index versions failed: {result.stderr}")
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
# Parse output: first line is "Available versions: X.Y.Z, A.B.C, ..."
|
|
118
|
+
output = result.stdout.strip()
|
|
119
|
+
if not output:
|
|
120
|
+
logger.debug("pip index versions returned empty output")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
# Extract available versions line
|
|
124
|
+
lines = output.split("\n")
|
|
125
|
+
for line in lines:
|
|
126
|
+
if line.startswith("Available versions:"):
|
|
127
|
+
# Format: "Available versions: 0.3.10, 0.3.9, 0.3.8, ..."
|
|
128
|
+
versions_part = line.replace("Available versions:", "").strip()
|
|
129
|
+
versions = [v.strip() for v in versions_part.split(",")]
|
|
130
|
+
|
|
131
|
+
if versions:
|
|
132
|
+
latest = versions[0]
|
|
133
|
+
logger.debug(f"Latest version on PyPI: {latest}")
|
|
134
|
+
return latest
|
|
135
|
+
|
|
136
|
+
logger.debug(f"Could not parse pip output: {output}")
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
except subprocess.TimeoutExpired:
|
|
140
|
+
logger.warning("pip index versions timed out after 10 seconds")
|
|
141
|
+
return None
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.debug(f"Failed to fetch PyPI version: {e}")
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _compare_versions(current: str, latest: str) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Compare semantic versions.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
current: Current version string (e.g., "0.3.9")
|
|
153
|
+
latest: Latest version string (e.g., "0.3.10")
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if latest > current, False otherwise
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
# Parse versions as tuples of integers
|
|
160
|
+
current_parts = [int(x) for x in current.split(".")]
|
|
161
|
+
latest_parts = [int(x) for x in latest.split(".")]
|
|
162
|
+
|
|
163
|
+
# Pad shorter version with zeros
|
|
164
|
+
max_len = max(len(current_parts), len(latest_parts))
|
|
165
|
+
current_parts += [0] * (max_len - len(current_parts))
|
|
166
|
+
latest_parts += [0] * (max_len - len(latest_parts))
|
|
167
|
+
|
|
168
|
+
# Tuple comparison works element-by-element
|
|
169
|
+
return tuple(latest_parts) > tuple(current_parts)
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.debug(f"Failed to compare versions '{current}' and '{latest}': {e}")
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _log_check(current: str, latest: Optional[str], status: str) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Log the update check to ~/.stravinsky/update.log.
|
|
179
|
+
|
|
180
|
+
Format: YYYY-MM-DD HH:MM:SS | VERSION_CHECK | current=X.Y.Z pypi=A.B.C | <status>
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
current: Current version
|
|
184
|
+
latest: Latest version from PyPI (or None)
|
|
185
|
+
status: Check status ("new_available", "up_to_date", "error", etc.)
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
home_dir = _get_stravinsky_home()
|
|
189
|
+
if not home_dir:
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
update_log = home_dir / "update.log"
|
|
193
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
194
|
+
|
|
195
|
+
if latest:
|
|
196
|
+
log_entry = f"{timestamp} | VERSION_CHECK | current={current} pypi={latest} | {status}"
|
|
197
|
+
else:
|
|
198
|
+
log_entry = f"{timestamp} | VERSION_CHECK | current={current} pypi=unknown | {status}"
|
|
199
|
+
|
|
200
|
+
with open(update_log, "a") as f:
|
|
201
|
+
f.write(log_entry + "\n")
|
|
202
|
+
|
|
203
|
+
logger.debug(f"Logged update check: {status}")
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.warning(f"Failed to log update check: {e}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
async def check_for_updates(skip_updates: bool = False) -> dict:
|
|
210
|
+
"""
|
|
211
|
+
Check PyPI for new versions of stravinsky.
|
|
212
|
+
|
|
213
|
+
Implements 24-hour throttling to prevent excessive API calls.
|
|
214
|
+
All failures are handled gracefully without raising exceptions.
|
|
215
|
+
Non-blocking - safe to run via asyncio.create_task().
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
skip_updates: If True, skip the check entirely
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
dict with keys:
|
|
222
|
+
- status: "checked" | "skipped" | "error"
|
|
223
|
+
- current: current version (e.g., "0.3.9")
|
|
224
|
+
- latest: latest version (e.g., "0.3.10") or None
|
|
225
|
+
- update_available: bool
|
|
226
|
+
- message: str (optional, for errors)
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
# Get current version
|
|
230
|
+
current_version = __version__
|
|
231
|
+
|
|
232
|
+
# Return early if updates are skipped
|
|
233
|
+
if skip_updates:
|
|
234
|
+
logger.debug("Update check skipped (skip_updates=True)")
|
|
235
|
+
return {
|
|
236
|
+
"status": "skipped",
|
|
237
|
+
"current": current_version,
|
|
238
|
+
"latest": None,
|
|
239
|
+
"update_available": False,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# Check if enough time has passed since last check
|
|
243
|
+
last_check_time = _get_last_check_time()
|
|
244
|
+
if not _should_check(last_check_time):
|
|
245
|
+
logger.debug("Update check throttled (24-hour limit)")
|
|
246
|
+
return {
|
|
247
|
+
"status": "skipped",
|
|
248
|
+
"current": current_version,
|
|
249
|
+
"latest": None,
|
|
250
|
+
"update_available": False,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Fetch latest version from PyPI
|
|
254
|
+
latest_version = _get_pypi_version()
|
|
255
|
+
|
|
256
|
+
if latest_version is None:
|
|
257
|
+
logger.warning("Failed to fetch latest version from PyPI")
|
|
258
|
+
_log_check(current_version, None, "error")
|
|
259
|
+
return {
|
|
260
|
+
"status": "error",
|
|
261
|
+
"current": current_version,
|
|
262
|
+
"latest": None,
|
|
263
|
+
"update_available": False,
|
|
264
|
+
"message": "Failed to fetch version from PyPI",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# Compare versions
|
|
268
|
+
update_available = _compare_versions(current_version, latest_version)
|
|
269
|
+
|
|
270
|
+
# Determine status
|
|
271
|
+
if update_available:
|
|
272
|
+
status = "new_available"
|
|
273
|
+
logger.info(
|
|
274
|
+
f"Update available: {current_version} -> {latest_version}. "
|
|
275
|
+
f"Install with: pip install --upgrade stravinsky"
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
status = "up_to_date"
|
|
279
|
+
logger.debug(f"Stravinsky is up to date ({current_version})")
|
|
280
|
+
|
|
281
|
+
# Log the check
|
|
282
|
+
_log_check(current_version, latest_version, status)
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
"status": "checked",
|
|
286
|
+
"current": current_version,
|
|
287
|
+
"latest": latest_version,
|
|
288
|
+
"update_available": update_available,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.error(f"Unexpected error during update check: {e}", exc_info=True)
|
|
293
|
+
return {
|
|
294
|
+
"status": "error",
|
|
295
|
+
"current": __version__,
|
|
296
|
+
"latest": None,
|
|
297
|
+
"update_available": False,
|
|
298
|
+
"message": f"Update check failed: {str(e)}",
|
|
299
|
+
}
|
|
@@ -1,41 +1,50 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stravinsky
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.18
|
|
4
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/
|
|
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
|
|
8
8
|
License: MIT
|
|
9
9
|
Keywords: claude,gemini,mcp,oauth,openai
|
|
10
|
-
Requires-Python:
|
|
10
|
+
Requires-Python: <3.14,>=3.11
|
|
11
11
|
Requires-Dist: aiofiles>=23.1.0
|
|
12
|
+
Requires-Dist: chromadb>=0.6.0
|
|
12
13
|
Requires-Dist: cryptography>=41.0.0
|
|
13
14
|
Requires-Dist: google-auth-oauthlib>=1.0.0
|
|
14
15
|
Requires-Dist: google-auth>=2.20.0
|
|
15
16
|
Requires-Dist: httpx>=0.24.0
|
|
17
|
+
Requires-Dist: jedi-language-server>=0.41.0
|
|
16
18
|
Requires-Dist: jedi>=0.19.2
|
|
17
19
|
Requires-Dist: keyring>=25.7.0
|
|
20
|
+
Requires-Dist: lsprotocol>=2023.0.0
|
|
18
21
|
Requires-Dist: mcp>=1.2.1
|
|
22
|
+
Requires-Dist: ollama>=0.6.1
|
|
19
23
|
Requires-Dist: openai>=1.0.0
|
|
24
|
+
Requires-Dist: pathspec>=0.12.0
|
|
25
|
+
Requires-Dist: plyer>=2.1.0
|
|
20
26
|
Requires-Dist: psutil>=5.9.0
|
|
21
27
|
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Requires-Dist: pygls>=1.3.0
|
|
22
29
|
Requires-Dist: python-dotenv>=1.0.0
|
|
23
30
|
Requires-Dist: rich>=13.0.0
|
|
24
31
|
Requires-Dist: ruff>=0.14.10
|
|
25
32
|
Requires-Dist: tenacity>=8.5.0
|
|
33
|
+
Requires-Dist: watchdog~=5.0.0
|
|
26
34
|
Provides-Extra: dev
|
|
27
35
|
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
28
36
|
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
29
37
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
38
|
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
39
|
+
Provides-Extra: semantic
|
|
40
|
+
Requires-Dist: chromadb>=0.5.0; extra == 'semantic'
|
|
31
41
|
Description-Content-Type: text/markdown
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
</div>
|
|
43
|
+
# Stravinsky
|
|
44
|
+
|
|
45
|
+
**The Avant-Garde MCP Bridge for Claude Code**
|
|
46
|
+
|
|
47
|
+
*Movement • Rhythm • Precision*
|
|
39
48
|
|
|
40
49
|
---
|
|
41
50
|
|
|
@@ -48,33 +57,55 @@ Description-Content-Type: text/markdown
|
|
|
48
57
|
- 🔐 **OAuth Authentication** - Secure browser-based auth for Google (Gemini) and OpenAI (ChatGPT)
|
|
49
58
|
- 🤖 **Multi-Model Support** - Seamlessly invoke Gemini and GPT models from Claude
|
|
50
59
|
- 🎯 **Native Subagent Orchestration** - Auto-delegating orchestrator with parallel execution (zero CLI overhead)
|
|
51
|
-
- 🛠️ **
|
|
52
|
-
- 🧠 **
|
|
60
|
+
- 🛠️ **40 MCP Tools** - Model invocation, code search, semantic search, LSP refactoring, session management, and more
|
|
61
|
+
- 🧠 **9 Specialized Native Agents** - Stravinsky (orchestrator), Research Lead, Implementation Lead, Delphi (GPT-5.2 advisor), Dewey (documentation), Explore (code search), Frontend (Gemini 3 Pro High UI/UX), Code Reviewer, Debugger
|
|
53
62
|
- 🔄 **Hook-Based Delegation** - PreToolUse hooks enforce delegation patterns with hard boundaries (exit code 2)
|
|
54
|
-
- 📝 **LSP Integration** - Full Language Server Protocol support
|
|
63
|
+
- 📝 **LSP Integration** - Full Language Server Protocol support with persistent servers (35x speedup), code refactoring, and advanced navigation
|
|
55
64
|
- 🔍 **AST-Aware Search** - Structural code search and refactoring with ast-grep
|
|
65
|
+
- 🧠 **Semantic Code Search** - Natural language queries with local embeddings (ChromaDB + Ollama)
|
|
56
66
|
- ⚡ **Cost-Optimized Routing** - Free/cheap agents (explore, dewey) always async, expensive (delphi) only when needed
|
|
57
67
|
|
|
58
68
|
## Quick Start
|
|
59
69
|
|
|
60
70
|
### Installation
|
|
61
71
|
|
|
62
|
-
**
|
|
72
|
+
**CRITICAL: Always use --scope user with @latest for auto-updates and Python 3.13**
|
|
63
73
|
|
|
64
74
|
```bash
|
|
65
|
-
#
|
|
66
|
-
claude mcp add stravinsky -- uvx stravinsky
|
|
75
|
+
# CORRECT: User-level installation with Python 3.13 and automatic updates
|
|
76
|
+
claude mcp add --scope user stravinsky -- uvx --python python3.13 stravinsky@latest
|
|
77
|
+
|
|
78
|
+
# Why this matters:
|
|
79
|
+
# - --scope user: Works across all projects (stored in ~/.claude.json)
|
|
80
|
+
# - @latest: Auto-checks PyPI on every Claude restart (no stale cache)
|
|
81
|
+
# - --python python3.13: Required due to chromadb → onnxruntime dependency (no Python 3.14+ support)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**⚠️ Python 3.13 Required**: Stravinsky requires Python 3.13 or earlier. Python 3.14+ is not supported because chromadb depends on onnxruntime, which lacks wheels for Python 3.14+.
|
|
85
|
+
|
|
86
|
+
**WRONG - Do NOT use these:**
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# ❌ Missing --python python3.13 (will fail on system Python 3.14+)
|
|
90
|
+
claude mcp add --global stravinsky -- uvx stravinsky@latest
|
|
91
|
+
|
|
92
|
+
# ❌ Local scope (only works in one project)
|
|
93
|
+
claude mcp add stravinsky -- uvx --python python3.13 stravinsky@latest
|
|
67
94
|
|
|
68
|
-
#
|
|
95
|
+
# ❌ No @latest (cache never updates, stays on old version)
|
|
96
|
+
claude mcp add --global stravinsky -- uvx --python python3.13 stravinsky
|
|
97
|
+
|
|
98
|
+
# ❌ uv tool install (requires manual upgrades)
|
|
69
99
|
uv tool install stravinsky
|
|
70
|
-
claude mcp add stravinsky -- stravinsky
|
|
71
100
|
```
|
|
72
101
|
|
|
73
|
-
**
|
|
102
|
+
**Development (local editable install):**
|
|
74
103
|
|
|
75
104
|
```bash
|
|
76
|
-
|
|
77
|
-
|
|
105
|
+
# ONLY for active development on stravinsky source code
|
|
106
|
+
cd /path/to/stravinsky
|
|
107
|
+
uv tool install --editable . --force
|
|
108
|
+
claude mcp add --scope user stravinsky -- stravinsky
|
|
78
109
|
```
|
|
79
110
|
|
|
80
111
|
### Authentication
|
|
@@ -93,6 +124,38 @@ stravinsky-auth status
|
|
|
93
124
|
stravinsky-auth logout gemini
|
|
94
125
|
```
|
|
95
126
|
|
|
127
|
+
**Secure Token Storage:**
|
|
128
|
+
|
|
129
|
+
OAuth tokens are stored securely with automatic fallback:
|
|
130
|
+
- **Primary**: System keyring (macOS Keychain, Linux Secret Service, Windows Credential Locker)
|
|
131
|
+
- **Fallback**: Encrypted files at `~/.stravinsky/tokens/` using Fernet symmetric encryption
|
|
132
|
+
- **Encryption**: AES-128-CBC with keys stored at `~/.stravinsky/tokens/.key` (0o600 permissions)
|
|
133
|
+
- **No password prompts**: Seamless authentication across all terminal sessions after initial login
|
|
134
|
+
- **Global access**: Works everywhere after one-time authentication per provider
|
|
135
|
+
|
|
136
|
+
#### Troubleshooting: Password Prompts (macOS)
|
|
137
|
+
|
|
138
|
+
If you experience persistent password prompts when authenticating, configure keyring to bypass macOS Keychain:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# 1. Create keyring config directory
|
|
142
|
+
mkdir -p ~/.config/python_keyring
|
|
143
|
+
|
|
144
|
+
# 2. Configure keyring to use fail backend (bypasses Keychain entirely)
|
|
145
|
+
cat <<EOT > ~/.config/python_keyring/keyringrc.cfg
|
|
146
|
+
[backend]
|
|
147
|
+
default-keyring = keyring.backends.fail.Keyring
|
|
148
|
+
EOT
|
|
149
|
+
|
|
150
|
+
# 3. Verify configuration
|
|
151
|
+
cat ~/.config/python_keyring/keyringrc.cfg
|
|
152
|
+
|
|
153
|
+
# 4. Re-authenticate (tokens will be stored in encrypted files only)
|
|
154
|
+
stravinsky-auth login gemini
|
|
155
|
+
```
|
|
156
|
+
|
|
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.
|
|
158
|
+
|
|
96
159
|
### Slash Commands
|
|
97
160
|
|
|
98
161
|
Slash commands are discovered from:
|
|
@@ -134,27 +197,107 @@ Stravinsky uses **native Claude Code subagents** (.claude/agents/) with automati
|
|
|
134
197
|
|
|
135
198
|
- **ULTRATHINK**: Engage exhaustive deep reasoning with extended thinking budget (32k tokens)
|
|
136
199
|
- **ULTRAWORK**: Maximum parallel execution - spawn all async agents immediately
|
|
137
|
-
````
|
|
138
200
|
|
|
139
|
-
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Tools (40)
|
|
140
204
|
|
|
141
205
|
| Category | Tools |
|
|
142
206
|
| ---------------- | ---------------------------------------------------------------------------------- |
|
|
143
207
|
| **Model Invoke** | `invoke_gemini`, `invoke_openai`, `get_system_health` |
|
|
144
208
|
| **Environment** | `get_project_context`, `task_spawn`, `task_status`, `task_list` |
|
|
145
|
-
| **Agents** | `agent_spawn`, `agent_output`, `agent_cancel`, `agent_list`, `agent_progress`
|
|
209
|
+
| **Agents** | `agent_spawn`, `agent_output`, `agent_cancel`, `agent_list`, `agent_progress`, `agent_retry` |
|
|
146
210
|
| **Code Search** | `ast_grep_search`, `ast_grep_replace`, `grep_search`, `glob_files` |
|
|
147
|
-
| **
|
|
211
|
+
| **Semantic** | `semantic_search`, `semantic_index`, `semantic_stats`, `cancel_indexing`, `delete_index` |
|
|
212
|
+
| **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) |
|
|
148
213
|
| **Sessions** | `session_list`, `session_read`, `session_search` |
|
|
149
214
|
| **Skills** | `skill_list`, `skill_get` |
|
|
150
215
|
|
|
151
|
-
|
|
216
|
+
### LSP Performance & Refactoring
|
|
217
|
+
|
|
218
|
+
The Phase 2 update introduced the `LSPManager`, which maintains persistent language server instances:
|
|
219
|
+
|
|
220
|
+
- **35x Speedup**: Subsequent LSP calls are near-instant because the server no longer needs to re-initialize and re-index the codebase for every request
|
|
221
|
+
- **Code Refactoring**: New support for `lsp_extract_refactor` allows automated code extraction (e.g., extracting a method or variable) with full symbol resolution
|
|
222
|
+
- **Code Actions**: `lsp_code_action_resolve` enables complex, multi-step refactoring workflows with automatic fixes for diagnostics
|
|
223
|
+
|
|
224
|
+
### Semantic Code Search
|
|
225
|
+
|
|
226
|
+
Natural language code search powered by embeddings. Find code by meaning, not just keywords.
|
|
227
|
+
|
|
228
|
+
**Prerequisites:**
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Install Ollama (default provider - free, local)
|
|
232
|
+
brew install ollama
|
|
233
|
+
|
|
234
|
+
# Pull the recommended lightweight embedding model (274MB)
|
|
235
|
+
ollama pull nomic-embed-text
|
|
236
|
+
|
|
237
|
+
# Or for better accuracy (670MB, slower):
|
|
238
|
+
ollama pull mxbai-embed-large
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Recommended model:** `nomic-embed-text` is lightweight (274MB), fast, and works great for code search. It's the best choice for most users, especially on space-constrained systems like MacBooks.
|
|
242
|
+
|
|
243
|
+
**Workflow:**
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# STEP 1: Initial indexing (REQUIRED - run once per project)
|
|
247
|
+
semantic_index(project_path=".", provider="ollama")
|
|
248
|
+
|
|
249
|
+
# STEP 2: Enable auto-updates (OPTIONAL - recommended for active projects)
|
|
250
|
+
start_file_watcher(".", provider="ollama", debounce_seconds=2.0)
|
|
251
|
+
|
|
252
|
+
# STEP 3: Search your codebase with natural language
|
|
253
|
+
semantic_search(query="OAuth authentication logic", n_results=5)
|
|
254
|
+
semantic_stats() # View index statistics
|
|
255
|
+
|
|
256
|
+
# STEP 4: Stop watching when done (optional - watcher cleans up on exit)
|
|
257
|
+
stop_file_watcher(".")
|
|
258
|
+
|
|
259
|
+
# OPTIONAL: Cancel ongoing indexing (for large codebases)
|
|
260
|
+
cancel_indexing(project_path=".", provider="ollama")
|
|
261
|
+
|
|
262
|
+
# OPTIONAL: Delete indexes
|
|
263
|
+
delete_index(project_path=".", provider="ollama") # Delete ollama index for this project
|
|
264
|
+
delete_index(project_path=".") # Delete all provider indexes for this project
|
|
265
|
+
delete_index(delete_all=True) # Delete ALL indexes for ALL projects
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**⚠️ Important:** You MUST run `semantic_index()` first before using semantic search or starting the FileWatcher. If you skip this step:
|
|
269
|
+
- `semantic_search()` will return no results
|
|
270
|
+
- `start_file_watcher()` will show a warning but still work (indexes on first file change)
|
|
271
|
+
|
|
272
|
+
**Example queries:**
|
|
273
|
+
- "find authentication logic"
|
|
274
|
+
- "error handling in API endpoints"
|
|
275
|
+
- "database connection pooling"
|
|
276
|
+
|
|
277
|
+
**Providers:**
|
|
278
|
+
|
|
279
|
+
| Provider | Model | Size | Cost | Setup |
|
|
280
|
+
|----------|-------|------|------|-------|
|
|
281
|
+
| `ollama` (default) | nomic-embed-text (recommended) | 274MB | Free/local | `ollama pull nomic-embed-text` |
|
|
282
|
+
| `ollama` | mxbai-embed-large (advanced) | 670MB | Free/local | `ollama pull mxbai-embed-large` |
|
|
283
|
+
| `gemini` | gemini-embedding-001 | N/A | OAuth/cloud | `stravinsky-auth login gemini` |
|
|
284
|
+
| `openai` | text-embedding-3-small | N/A | OAuth/cloud | `stravinsky-auth login openai` |
|
|
285
|
+
|
|
286
|
+
**Technical details:**
|
|
287
|
+
- **AST-aware chunking**: Python files split by functions/classes for better semantic boundaries
|
|
288
|
+
- **Persistent storage**: ChromaDB at `~/.stravinsky/vectordb/<project>_<provider>/`
|
|
289
|
+
- **Async parallel embeddings**: 10 concurrent for fast indexing
|
|
290
|
+
- **Automatic file watching**: Background reindexing on code changes with configurable debouncing (2s default)
|
|
291
|
+
|
|
292
|
+
## Native Subagents (9)
|
|
152
293
|
|
|
153
294
|
Configured in `.claude/agents/*.md`:
|
|
154
295
|
|
|
155
296
|
| Agent | Purpose | Location |
|
|
156
297
|
| ---------------- | --------------------------------------------------------------------- | -------- |
|
|
157
298
|
| `stravinsky` | Task orchestration with 32k extended thinking (Sonnet 4.5) | .claude/agents/stravinsky.md |
|
|
299
|
+
| `research-lead` | Research coordinator - spawns explore/dewey, synthesizes findings (Gemini Flash) | .claude/agents/research-lead.md |
|
|
300
|
+
| `implementation-lead` | Implementation coordinator - spawns frontend/debugger/reviewer (Haiku) | .claude/agents/implementation-lead.md |
|
|
158
301
|
| `explore` | Codebase search and structural analysis (Gemini 3 Flash) | .claude/agents/explore.md |
|
|
159
302
|
| `dewey` | Documentation research and web search (Gemini 3 Flash) | .claude/agents/dewey.md |
|
|
160
303
|
| `code-reviewer` | Security, quality, and best practice analysis (Claude Sonnet) | .claude/agents/code-reviewer.md |
|
|
@@ -187,7 +330,7 @@ stravinsky/
|
|
|
187
330
|
│ │ ├── delphi.md # Strategic advisor (GPT-5.2)
|
|
188
331
|
│ │ └── HOOKS.md # Hook architecture guide
|
|
189
332
|
│ ├── commands/ # Slash commands (skills)
|
|
190
|
-
│ │ ├── stravinsky.md # /
|
|
333
|
+
│ │ ├── stravinsky.md # /strav orchestrator
|
|
191
334
|
│ │ ├── delphi.md # /delphi strategic advisor
|
|
192
335
|
│ │ ├── dewey.md # /dewey documentation research
|
|
193
336
|
│ │ ├── publish.md # /publish PyPI release
|
|
@@ -222,6 +365,7 @@ stravinsky/
|
|
|
222
365
|
│ │ ├── model_invoke.py # invoke_gemini, invoke_openai
|
|
223
366
|
│ │ ├── agent_manager.py # agent_spawn, agent_output, etc.
|
|
224
367
|
│ │ ├── code_search.py # ast_grep, grep, glob
|
|
368
|
+
│ │ ├── semantic_search.py # semantic_search, semantic_index, semantic_stats
|
|
225
369
|
│ │ ├── session_manager.py # session_list, session_read, etc.
|
|
226
370
|
│ │ ├── skill_loader.py # skill_list, skill_get
|
|
227
371
|
│ │ ├── project_context.py # get_project_context
|
|
@@ -277,6 +421,46 @@ The Codex CLI uses the same port. Stop it with: `killall codex`
|
|
|
277
421
|
- Ensure you have a ChatGPT Plus/Pro subscription
|
|
278
422
|
- Tokens expire occasionally; run `stravinsky-auth login openai` to refresh
|
|
279
423
|
|
|
424
|
+
### Claude Code Not Loading Latest Version After Update
|
|
425
|
+
|
|
426
|
+
**Problem:** After deploying a new version to PyPI, Claude Code still uses the old cached version even after restart.
|
|
427
|
+
|
|
428
|
+
**Root Cause:** `uvx` caches packages and doesn't automatically check for updates. This is a known uvx behavior that affects all packages, not just Stravinsky.
|
|
429
|
+
|
|
430
|
+
**Solution - Force Reinstall:**
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
# Option 1: Run latest version with uvx (if using uvx in .mcp.json)
|
|
434
|
+
uvx stravinsky@latest
|
|
435
|
+
|
|
436
|
+
# Option 2: Upgrade with uv tool (if installed globally)
|
|
437
|
+
uv tool install --upgrade --force stravinsky
|
|
438
|
+
# or reinstall all tools:
|
|
439
|
+
uv tool upgrade --all --reinstall
|
|
440
|
+
|
|
441
|
+
# Option 3: Clear uvx cache entirely (nuclear option)
|
|
442
|
+
uv cache prune
|
|
443
|
+
|
|
444
|
+
# Option 4: Remove and re-add MCP server
|
|
445
|
+
claude mcp remove stravinsky
|
|
446
|
+
claude mcp add stravinsky -- uvx stravinsky@latest
|
|
447
|
+
|
|
448
|
+
# After any option, restart Claude Code
|
|
449
|
+
claude restart
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Verify Version:**
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
# Check installed version
|
|
456
|
+
stravinsky --version
|
|
457
|
+
# or
|
|
458
|
+
python -c "import mcp_bridge; print(mcp_bridge.__version__)"
|
|
459
|
+
|
|
460
|
+
# Check PyPI latest version
|
|
461
|
+
curl -s https://pypi.org/pypi/stravinsky/json | python -c "import sys, json; print(json.load(sys.stdin)['info']['version'])"
|
|
462
|
+
```
|
|
463
|
+
|
|
280
464
|
## License
|
|
281
465
|
|
|
282
466
|
MIT
|