stravinsky 0.2.52__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/cli/__init__.py +6 -0
- mcp_bridge/cli/install_hooks.py +1265 -0
- mcp_bridge/cli/session_report.py +585 -0
- 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/HOOKS_SETTINGS.json +175 -0
- mcp_bridge/hooks/README.md +215 -0
- mcp_bridge/hooks/__init__.py +119 -60
- mcp_bridge/hooks/edit_recovery.py +42 -37
- mcp_bridge/hooks/git_noninteractive.py +89 -0
- mcp_bridge/hooks/keyword_detector.py +30 -0
- mcp_bridge/hooks/manager.py +8 -0
- mcp_bridge/hooks/notification_hook.py +103 -0
- mcp_bridge/hooks/parallel_execution.py +111 -0
- mcp_bridge/hooks/pre_compact.py +82 -183
- mcp_bridge/hooks/rules_injector.py +507 -0
- mcp_bridge/hooks/session_notifier.py +125 -0
- mcp_bridge/{native_hooks → hooks}/stravinsky_mode.py +51 -16
- mcp_bridge/hooks/subagent_stop.py +98 -0
- mcp_bridge/hooks/task_validator.py +73 -0
- mcp_bridge/hooks/tmux_manager.py +141 -0
- mcp_bridge/hooks/todo_continuation.py +90 -0
- mcp_bridge/hooks/todo_delegation.py +88 -0
- mcp_bridge/hooks/tool_messaging.py +267 -0
- mcp_bridge/hooks/truncator.py +21 -17
- mcp_bridge/notifications.py +151 -0
- mcp_bridge/prompts/multimodal.py +24 -3
- mcp_bridge/server.py +214 -49
- mcp_bridge/server_tools.py +445 -0
- mcp_bridge/tools/__init__.py +22 -18
- mcp_bridge/tools/agent_manager.py +220 -32
- mcp_bridge/tools/code_search.py +97 -11
- mcp_bridge/tools/lsp/__init__.py +7 -0
- mcp_bridge/tools/lsp/manager.py +448 -0
- mcp_bridge/tools/lsp/tools.py +637 -150
- mcp_bridge/tools/model_invoke.py +208 -106
- mcp_bridge/tools/query_classifier.py +323 -0
- mcp_bridge/tools/semantic_search.py +3042 -0
- mcp_bridge/tools/templates.py +32 -18
- mcp_bridge/update_manager.py +589 -0
- mcp_bridge/update_manager_pypi.py +299 -0
- stravinsky-0.4.18.dist-info/METADATA +468 -0
- stravinsky-0.4.18.dist-info/RECORD +88 -0
- stravinsky-0.4.18.dist-info/entry_points.txt +5 -0
- mcp_bridge/native_hooks/edit_recovery.py +0 -46
- mcp_bridge/native_hooks/todo_delegation.py +0 -54
- mcp_bridge/native_hooks/truncator.py +0 -23
- stravinsky-0.2.52.dist-info/METADATA +0 -204
- stravinsky-0.2.52.dist-info/RECORD +0 -63
- stravinsky-0.2.52.dist-info/entry_points.txt +0 -3
- /mcp_bridge/{native_hooks → hooks}/context.py +0 -0
- {stravinsky-0.2.52.dist-info → stravinsky-0.4.18.dist-info}/WHEEL +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
|
+
}
|