stravinsky 0.1.2__py3-none-any.whl → 0.2.38__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 (42) hide show
  1. mcp_bridge/__init__.py +1 -5
  2. mcp_bridge/auth/cli.py +89 -44
  3. mcp_bridge/auth/oauth.py +88 -63
  4. mcp_bridge/hooks/__init__.py +49 -0
  5. mcp_bridge/hooks/agent_reminder.py +61 -0
  6. mcp_bridge/hooks/auto_slash_command.py +186 -0
  7. mcp_bridge/hooks/budget_optimizer.py +38 -0
  8. mcp_bridge/hooks/comment_checker.py +136 -0
  9. mcp_bridge/hooks/compaction.py +32 -0
  10. mcp_bridge/hooks/context_monitor.py +58 -0
  11. mcp_bridge/hooks/directory_context.py +40 -0
  12. mcp_bridge/hooks/edit_recovery.py +41 -0
  13. mcp_bridge/hooks/empty_message_sanitizer.py +240 -0
  14. mcp_bridge/hooks/keyword_detector.py +122 -0
  15. mcp_bridge/hooks/manager.py +96 -0
  16. mcp_bridge/hooks/preemptive_compaction.py +157 -0
  17. mcp_bridge/hooks/session_recovery.py +186 -0
  18. mcp_bridge/hooks/todo_enforcer.py +75 -0
  19. mcp_bridge/hooks/truncator.py +19 -0
  20. mcp_bridge/native_hooks/context.py +38 -0
  21. mcp_bridge/native_hooks/edit_recovery.py +46 -0
  22. mcp_bridge/native_hooks/stravinsky_mode.py +109 -0
  23. mcp_bridge/native_hooks/truncator.py +23 -0
  24. mcp_bridge/prompts/delphi.py +3 -2
  25. mcp_bridge/prompts/dewey.py +105 -21
  26. mcp_bridge/prompts/stravinsky.py +452 -118
  27. mcp_bridge/server.py +491 -668
  28. mcp_bridge/server_tools.py +547 -0
  29. mcp_bridge/tools/__init__.py +13 -3
  30. mcp_bridge/tools/agent_manager.py +359 -190
  31. mcp_bridge/tools/continuous_loop.py +67 -0
  32. mcp_bridge/tools/init.py +50 -0
  33. mcp_bridge/tools/lsp/tools.py +15 -15
  34. mcp_bridge/tools/model_invoke.py +594 -48
  35. mcp_bridge/tools/skill_loader.py +51 -47
  36. mcp_bridge/tools/task_runner.py +141 -0
  37. mcp_bridge/tools/templates.py +175 -0
  38. {stravinsky-0.1.2.dist-info → stravinsky-0.2.38.dist-info}/METADATA +55 -10
  39. stravinsky-0.2.38.dist-info/RECORD +57 -0
  40. stravinsky-0.1.2.dist-info/RECORD +0 -32
  41. {stravinsky-0.1.2.dist-info → stravinsky-0.2.38.dist-info}/WHEEL +0 -0
  42. {stravinsky-0.1.2.dist-info → stravinsky-0.2.38.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,67 @@
1
+ """
2
+ Continuous Loop (Ralph Loop) for Stravinsky.
3
+
4
+ Allows Stravinsky to operate in an autonomous loop until criteria are met.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Any, Dict
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ async def enable_ralph_loop(goal: str, max_iterations: int = 10) -> str:
15
+ """
16
+ Enable continuous processing until a goal is met.
17
+
18
+ Args:
19
+ goal: The goal to achieve and verify.
20
+ max_iterations: Maximum number of iterations before stopping.
21
+ """
22
+ project_root = Path.cwd()
23
+ settings_file = project_root / ".claude" / "settings.json"
24
+
25
+ settings = {}
26
+ if settings_file.exists():
27
+ try:
28
+ settings = json.loads(settings_file.read_text())
29
+ except:
30
+ pass
31
+
32
+ if "hooks" not in settings:
33
+ settings["hooks"] = {}
34
+
35
+ # Set the Stop hook to re-trigger if goal not met
36
+ # Note: Stravinsky's prompt will handle the internal logic
37
+ # but the presence of this hook signals "Continue" to Claude Code.
38
+ settings["hooks"]["Stop"] = [
39
+ {
40
+ "type": "command",
41
+ "command": f'echo "Looping for goal: {goal}"',
42
+ }
43
+ ]
44
+
45
+ settings_file.parent.mkdir(parents=True, exist_ok=True)
46
+ settings_file.write_text(json.dumps(settings, indent=2))
47
+
48
+ return f"🔄 Ralph Loop ENABLED. Goal: {goal}. Stravinsky will now process continuously until completion."
49
+
50
+ async def disable_ralph_loop() -> str:
51
+ """Disable the autonomous loop."""
52
+ project_root = Path.cwd()
53
+ settings_file = project_root / ".claude" / "settings.json"
54
+
55
+ if not settings_file.exists():
56
+ return "Ralph Loop is already disabled."
57
+
58
+ try:
59
+ settings = json.loads(settings_file.read_text())
60
+ if "hooks" in settings and "Stop" in settings["hooks"]:
61
+ del settings["hooks"]["Stop"]
62
+ settings_file.write_text(json.dumps(settings, indent=2))
63
+ return "✅ Ralph Loop DISABLED."
64
+ except:
65
+ pass
66
+
67
+ return "Failed to disable Ralph Loop or it was not active."
@@ -0,0 +1,50 @@
1
+ """
2
+ Repository bootstrap logic for Stravinsky.
3
+ """
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from .templates import CLAUDE_MD_TEMPLATE, SLASH_COMMANDS
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ def bootstrap_repo(project_path: str | Path | None = None) -> str:
12
+ """
13
+ Bootstrap a repository for Stravinsky MCP usage.
14
+
15
+ Creates:
16
+ - .claude/commands/ (with standard slash commands)
17
+ - Appends/Creates CLAUDE.md
18
+ """
19
+ root = Path(project_path or Path.cwd())
20
+
21
+ # 1. Setup Slash Commands
22
+ commands_dir = root / ".claude" / "commands"
23
+ commands_dir.mkdir(parents=True, exist_ok=True)
24
+
25
+ commands_created = 0
26
+ for filename, content in SLASH_COMMANDS.items():
27
+ cmd_file = commands_dir / filename
28
+ if not cmd_file.exists():
29
+ cmd_file.write_text(content)
30
+ commands_created += 1
31
+
32
+ # 2. Setup CLAUDE.md
33
+ claude_md = root / "CLAUDE.md"
34
+ if not claude_md.exists():
35
+ claude_md.write_text("# Project Notes\n\n" + CLAUDE_MD_TEMPLATE)
36
+ claude_msg = "Created CLAUDE.md"
37
+ else:
38
+ content = claude_md.read_text()
39
+ if "Stravinsky MCP" not in content:
40
+ with open(claude_md, "a") as f:
41
+ f.write("\n\n" + CLAUDE_MD_TEMPLATE)
42
+ claude_msg = "Updated CLAUDE.md with Stravinsky instructions"
43
+ else:
44
+ claude_msg = "CLAUDE.md already configured"
45
+
46
+ return (
47
+ f"✅ Repository Initialized!\n"
48
+ f"- {claude_msg}\n"
49
+ f"- Installed {commands_created} new slash commands to .claude/commands/stra/"
50
+ )
@@ -74,10 +74,10 @@ import jedi
74
74
  script = jedi.Script(path='{file_path}')
75
75
  completions = script.infer({line}, {character})
76
76
  for c in completions[:1]:
77
- print(f"Type: {{c.type}}")
78
- print(f"Name: {{c.full_name}}")
77
+ logger.info(f"Type: {{c.type}}")
78
+ logger.info(f"Name: {{c.full_name}}")
79
79
  if c.docstring():
80
- print(f"\\nDocstring:\\n{{c.docstring()[:500]}}")
80
+ logger.info(f"\\nDocstring:\\n{{c.docstring()[:500]}}")
81
81
  """
82
82
  ],
83
83
  capture_output=True,
@@ -133,7 +133,7 @@ import jedi
133
133
  script = jedi.Script(path='{file_path}')
134
134
  definitions = script.goto({line}, {character})
135
135
  for d in definitions:
136
- print(f"{{d.module_path}}:{{d.line}}:{{d.column}} - {{d.full_name}}")
136
+ logger.info(f"{{d.module_path}}:{{d.line}}:{{d.column}} - {{d.full_name}}")
137
137
  """
138
138
  ],
139
139
  capture_output=True,
@@ -193,9 +193,9 @@ import jedi
193
193
  script = jedi.Script(path='{file_path}')
194
194
  references = script.get_references({line}, {character}, include_builtins=False)
195
195
  for r in references[:30]:
196
- print(f"{{r.module_path}}:{{r.line}}:{{r.column}}")
196
+ logger.info(f"{{r.module_path}}:{{r.line}}:{{r.column}}")
197
197
  if len(references) > 30:
198
- print(f"... and {{len(references) - 30}} more")
198
+ logger.info(f"... and {{len(references) - 30}} more")
199
199
  """
200
200
  ],
201
201
  capture_output=True,
@@ -243,7 +243,7 @@ script = jedi.Script(path='{file_path}')
243
243
  names = script.get_names(all_scopes=True, definitions=True)
244
244
  for n in names:
245
245
  indent = " " * (n.get_line_code().count(" ") if n.get_line_code() else 0)
246
- print(f"{{n.line:4d}} | {{indent}}{{n.type:10}} {{n.name}}")
246
+ logger.info(f"{{n.line:4d}} | {{indent}}{{n.type:10}} {{n.name}}")
247
247
  """
248
248
  ],
249
249
  capture_output=True,
@@ -356,12 +356,12 @@ import jedi
356
356
  script = jedi.Script(path='{file_path}')
357
357
  refs = script.get_references({line}, {character})
358
358
  if refs:
359
- print(f"Symbol: {{refs[0].name}}")
360
- print(f"Type: {{refs[0].type}}")
361
- print(f"References: {{len(refs)}}")
362
- print("✅ Rename is valid")
359
+ logger.info(f"Symbol: {{refs[0].name}}")
360
+ logger.info(f"Type: {{refs[0].type}}")
361
+ logger.info(f"References: {{len(refs)}}")
362
+ logger.info("✅ Rename is valid")
363
363
  else:
364
- print("❌ No symbol found at position")
364
+ logger.info("❌ No symbol found at position")
365
365
  """
366
366
  ],
367
367
  capture_output=True,
@@ -413,9 +413,9 @@ import jedi
413
413
  script = jedi.Script(path='{file_path}')
414
414
  refactoring = script.rename({line}, {character}, new_name='{new_name}')
415
415
  for path, changed in refactoring.get_changed_files().items():
416
- print(f"File: {{path}}")
417
- print(changed[:500])
418
- print("---")
416
+ logger.info(f"File: {{path}}")
417
+ logger.info(changed[:500])
418
+ logger.info("---")
419
419
  """
420
420
  ],
421
421
  capture_output=True,