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.

Files changed (58) hide show
  1. mcp_bridge/__init__.py +1 -1
  2. mcp_bridge/auth/token_store.py +113 -11
  3. mcp_bridge/cli/__init__.py +6 -0
  4. mcp_bridge/cli/install_hooks.py +1265 -0
  5. mcp_bridge/cli/session_report.py +585 -0
  6. mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
  7. mcp_bridge/config/README.md +276 -0
  8. mcp_bridge/config/hook_config.py +249 -0
  9. mcp_bridge/config/hooks_manifest.json +138 -0
  10. mcp_bridge/config/rate_limits.py +222 -0
  11. mcp_bridge/config/skills_manifest.json +128 -0
  12. mcp_bridge/hooks/HOOKS_SETTINGS.json +175 -0
  13. mcp_bridge/hooks/README.md +215 -0
  14. mcp_bridge/hooks/__init__.py +119 -60
  15. mcp_bridge/hooks/edit_recovery.py +42 -37
  16. mcp_bridge/hooks/git_noninteractive.py +89 -0
  17. mcp_bridge/hooks/keyword_detector.py +30 -0
  18. mcp_bridge/hooks/manager.py +8 -0
  19. mcp_bridge/hooks/notification_hook.py +103 -0
  20. mcp_bridge/hooks/parallel_execution.py +111 -0
  21. mcp_bridge/hooks/pre_compact.py +82 -183
  22. mcp_bridge/hooks/rules_injector.py +507 -0
  23. mcp_bridge/hooks/session_notifier.py +125 -0
  24. mcp_bridge/{native_hooks → hooks}/stravinsky_mode.py +51 -16
  25. mcp_bridge/hooks/subagent_stop.py +98 -0
  26. mcp_bridge/hooks/task_validator.py +73 -0
  27. mcp_bridge/hooks/tmux_manager.py +141 -0
  28. mcp_bridge/hooks/todo_continuation.py +90 -0
  29. mcp_bridge/hooks/todo_delegation.py +88 -0
  30. mcp_bridge/hooks/tool_messaging.py +267 -0
  31. mcp_bridge/hooks/truncator.py +21 -17
  32. mcp_bridge/notifications.py +151 -0
  33. mcp_bridge/prompts/multimodal.py +24 -3
  34. mcp_bridge/server.py +214 -49
  35. mcp_bridge/server_tools.py +445 -0
  36. mcp_bridge/tools/__init__.py +22 -18
  37. mcp_bridge/tools/agent_manager.py +220 -32
  38. mcp_bridge/tools/code_search.py +97 -11
  39. mcp_bridge/tools/lsp/__init__.py +7 -0
  40. mcp_bridge/tools/lsp/manager.py +448 -0
  41. mcp_bridge/tools/lsp/tools.py +637 -150
  42. mcp_bridge/tools/model_invoke.py +208 -106
  43. mcp_bridge/tools/query_classifier.py +323 -0
  44. mcp_bridge/tools/semantic_search.py +3042 -0
  45. mcp_bridge/tools/templates.py +32 -18
  46. mcp_bridge/update_manager.py +589 -0
  47. mcp_bridge/update_manager_pypi.py +299 -0
  48. stravinsky-0.4.18.dist-info/METADATA +468 -0
  49. stravinsky-0.4.18.dist-info/RECORD +88 -0
  50. stravinsky-0.4.18.dist-info/entry_points.txt +5 -0
  51. mcp_bridge/native_hooks/edit_recovery.py +0 -46
  52. mcp_bridge/native_hooks/todo_delegation.py +0 -54
  53. mcp_bridge/native_hooks/truncator.py +0 -23
  54. stravinsky-0.2.52.dist-info/METADATA +0 -204
  55. stravinsky-0.2.52.dist-info/RECORD +0 -63
  56. stravinsky-0.2.52.dist-info/entry_points.txt +0 -3
  57. /mcp_bridge/{native_hooks → hooks}/context.py +0 -0
  58. {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
+ }