stravinsky 0.2.40__py3-none-any.whl → 0.3.4__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.
Files changed (56) hide show
  1. mcp_bridge/__init__.py +1 -1
  2. mcp_bridge/auth/token_refresh.py +130 -0
  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/hooks/HOOKS_SETTINGS.json +175 -0
  7. mcp_bridge/hooks/README.md +215 -0
  8. mcp_bridge/hooks/__init__.py +119 -43
  9. mcp_bridge/hooks/edit_recovery.py +42 -37
  10. mcp_bridge/hooks/git_noninteractive.py +89 -0
  11. mcp_bridge/hooks/keyword_detector.py +30 -0
  12. mcp_bridge/hooks/manager.py +50 -0
  13. mcp_bridge/hooks/notification_hook.py +103 -0
  14. mcp_bridge/hooks/parallel_enforcer.py +127 -0
  15. mcp_bridge/hooks/parallel_execution.py +111 -0
  16. mcp_bridge/hooks/pre_compact.py +123 -0
  17. mcp_bridge/hooks/preemptive_compaction.py +81 -7
  18. mcp_bridge/hooks/rules_injector.py +507 -0
  19. mcp_bridge/hooks/session_idle.py +116 -0
  20. mcp_bridge/hooks/session_notifier.py +125 -0
  21. mcp_bridge/{native_hooks → hooks}/stravinsky_mode.py +51 -16
  22. mcp_bridge/hooks/subagent_stop.py +98 -0
  23. mcp_bridge/hooks/task_validator.py +73 -0
  24. mcp_bridge/hooks/tmux_manager.py +141 -0
  25. mcp_bridge/hooks/todo_continuation.py +90 -0
  26. mcp_bridge/hooks/todo_delegation.py +88 -0
  27. mcp_bridge/hooks/tool_messaging.py +164 -0
  28. mcp_bridge/hooks/truncator.py +21 -17
  29. mcp_bridge/notifications.py +151 -0
  30. mcp_bridge/prompts/__init__.py +3 -1
  31. mcp_bridge/prompts/dewey.py +30 -20
  32. mcp_bridge/prompts/explore.py +46 -8
  33. mcp_bridge/prompts/multimodal.py +24 -3
  34. mcp_bridge/prompts/planner.py +222 -0
  35. mcp_bridge/prompts/stravinsky.py +107 -28
  36. mcp_bridge/server.py +170 -10
  37. mcp_bridge/server_tools.py +554 -32
  38. mcp_bridge/tools/agent_manager.py +316 -106
  39. mcp_bridge/tools/background_tasks.py +2 -1
  40. mcp_bridge/tools/code_search.py +97 -11
  41. mcp_bridge/tools/lsp/__init__.py +7 -0
  42. mcp_bridge/tools/lsp/manager.py +448 -0
  43. mcp_bridge/tools/lsp/tools.py +637 -150
  44. mcp_bridge/tools/model_invoke.py +270 -47
  45. mcp_bridge/tools/semantic_search.py +2492 -0
  46. mcp_bridge/tools/templates.py +32 -18
  47. stravinsky-0.3.4.dist-info/METADATA +420 -0
  48. stravinsky-0.3.4.dist-info/RECORD +79 -0
  49. stravinsky-0.3.4.dist-info/entry_points.txt +5 -0
  50. mcp_bridge/native_hooks/edit_recovery.py +0 -46
  51. mcp_bridge/native_hooks/truncator.py +0 -23
  52. stravinsky-0.2.40.dist-info/METADATA +0 -204
  53. stravinsky-0.2.40.dist-info/RECORD +0 -57
  54. stravinsky-0.2.40.dist-info/entry_points.txt +0 -3
  55. /mcp_bridge/{native_hooks → hooks}/context.py +0 -0
  56. {stravinsky-0.2.40.dist-info → stravinsky-0.3.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ UserPromptSubmit hook: Todo Continuation Enforcer
4
+
5
+ Checks if there are incomplete todos (in_progress or pending) and injects
6
+ a reminder to continue working on them before starting new work.
7
+
8
+ Aligned with oh-my-opencode's [SYSTEM REMINDER - TODO CONTINUATION] pattern.
9
+ """
10
+ import json
11
+ import os
12
+ import sys
13
+ from pathlib import Path
14
+
15
+
16
+ def get_todo_state() -> dict:
17
+ """Try to get current todo state from Claude Code session or local cache."""
18
+ # Claude Code stores todo state - we can check via session files
19
+ # For now, we'll use a simple file-based approach
20
+ cwd = Path(os.environ.get("CLAUDE_CWD", "."))
21
+ todo_cache = cwd / ".claude" / "todo_state.json"
22
+
23
+ if todo_cache.exists():
24
+ try:
25
+ return json.loads(todo_cache.read_text())
26
+ except Exception:
27
+ pass
28
+
29
+ return {"todos": []}
30
+
31
+
32
+ def main():
33
+ try:
34
+ data = json.load(sys.stdin)
35
+ prompt = data.get("prompt", "")
36
+ except Exception:
37
+ return 0
38
+
39
+ # Get current todo state
40
+ state = get_todo_state()
41
+ todos = state.get("todos", [])
42
+
43
+ if not todos:
44
+ # No todos tracked, pass through
45
+ print(prompt)
46
+ return 0
47
+
48
+ # Count incomplete todos
49
+ in_progress = [t for t in todos if t.get("status") == "in_progress"]
50
+ pending = [t for t in todos if t.get("status") == "pending"]
51
+
52
+ if not in_progress and not pending:
53
+ # All todos complete, pass through
54
+ print(prompt)
55
+ return 0
56
+
57
+ # Build reminder
58
+ reminder_parts = ["[SYSTEM REMINDER - TODO CONTINUATION]", ""]
59
+
60
+ if in_progress:
61
+ reminder_parts.append(f"IN PROGRESS ({len(in_progress)} items):")
62
+ for t in in_progress:
63
+ reminder_parts.append(f" - {t.get('content', 'Unknown task')}")
64
+ reminder_parts.append("")
65
+
66
+ if pending:
67
+ reminder_parts.append(f"PENDING ({len(pending)} items):")
68
+ for t in pending[:5]: # Show max 5 pending
69
+ reminder_parts.append(f" - {t.get('content', 'Unknown task')}")
70
+ if len(pending) > 5:
71
+ reminder_parts.append(f" ... and {len(pending) - 5} more")
72
+ reminder_parts.append("")
73
+
74
+ reminder_parts.extend([
75
+ "IMPORTANT: You have incomplete work. Before starting anything new:",
76
+ "1. Continue working on IN_PROGRESS todos first",
77
+ "2. If blocked, explain why and move to next PENDING item",
78
+ "3. Only start NEW work if all todos are complete or explicitly abandoned",
79
+ "",
80
+ "---",
81
+ "",
82
+ ])
83
+
84
+ reminder = "\n".join(reminder_parts)
85
+ print(reminder + prompt)
86
+ return 0
87
+
88
+
89
+ if __name__ == "__main__":
90
+ sys.exit(main())
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PostToolUse hook for TodoWrite: CRITICAL parallel execution enforcer.
4
+
5
+ This hook fires AFTER TodoWrite completes. If there are 2+ pending items,
6
+ it outputs a STRONG reminder that Task agents must be spawned immediately.
7
+
8
+ Exit code 2 is used to signal a HARD BLOCK - Claude should see this as
9
+ a failure condition requiring immediate correction.
10
+
11
+ Works in tandem with:
12
+ - parallel_execution.py (UserPromptSubmit): Pre-emptive instruction injection
13
+ - stravinsky_mode.py (PreToolUse): Hard blocking of Read/Grep/Bash tools
14
+ """
15
+ import json
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ # Check if stravinsky mode is active (hard blocking enabled)
20
+ STRAVINSKY_MODE_FILE = Path.home() / ".stravinsky_mode"
21
+
22
+
23
+ def is_stravinsky_mode():
24
+ """Check if hard blocking mode is active."""
25
+ return STRAVINSKY_MODE_FILE.exists()
26
+
27
+
28
+ def main():
29
+ # Read hook input from stdin
30
+ try:
31
+ hook_input = json.load(sys.stdin)
32
+ except (json.JSONDecodeError, EOFError):
33
+ return 0
34
+
35
+ tool_name = hook_input.get("tool_name", "")
36
+
37
+ if tool_name != "TodoWrite":
38
+ return 0
39
+
40
+ # Get the todos that were just written
41
+ tool_input = hook_input.get("tool_input", {})
42
+ todos = tool_input.get("todos", [])
43
+
44
+ # Count pending todos
45
+ pending_count = sum(1 for t in todos if t.get("status") == "pending")
46
+
47
+ if pending_count < 2:
48
+ return 0
49
+
50
+ # Check if stravinsky mode is active
51
+ stravinsky_active = is_stravinsky_mode()
52
+
53
+ # CRITICAL: Output urgent reminder for parallel Task spawning
54
+ mode_warning = ""
55
+ if stravinsky_active:
56
+ mode_warning = """
57
+ ⚠️ STRAVINSKY MODE ACTIVE - Direct tools (Read, Grep, Bash) are BLOCKED.
58
+ You MUST use Task(subagent_type="explore", ...) for ALL file operations.
59
+ """
60
+
61
+ error_message = f"""
62
+ 🚨 PARALLEL DELEGATION REQUIRED 🚨
63
+
64
+ TodoWrite created {pending_count} pending items.
65
+ {mode_warning}
66
+ You MUST spawn Task agents for ALL independent TODOs in THIS SAME RESPONSE.
67
+
68
+ Required pattern (IMMEDIATELY after this message):
69
+ Task(subagent_type="explore", prompt="TODO 1...", description="TODO 1", run_in_background=true)
70
+ Task(subagent_type="explore", prompt="TODO 2...", description="TODO 2", run_in_background=true)
71
+ ...
72
+
73
+ DO NOT:
74
+ - End your response without spawning Tasks
75
+ - Mark TODOs in_progress before spawning Tasks
76
+ - Use Read/Grep/Bash directly (BLOCKED in stravinsky mode)
77
+
78
+ Your NEXT action MUST be multiple Task() calls, one for each independent TODO.
79
+ """
80
+ print(error_message, file=sys.stderr)
81
+
82
+ # Exit code 2 = HARD BLOCK in stravinsky mode
83
+ # Exit code 1 = WARNING otherwise
84
+ return 2 if stravinsky_active else 1
85
+
86
+
87
+ if __name__ == "__main__":
88
+ sys.exit(main())
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PostToolUse hook for user-friendly tool messaging.
4
+
5
+ Outputs concise messages about which agent/tool was used and what it did.
6
+ Format examples:
7
+ - ast-grep('Searching for authentication patterns')
8
+ - delphi:openai/gpt-5.2-medium('Analyzing architecture trade-offs')
9
+ - explore:gemini-3-flash('Finding all API endpoints')
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+
16
+ # Agent model mappings
17
+ AGENT_MODELS = {
18
+ "explore": "gemini-3-flash",
19
+ "dewey": "gemini-3-flash",
20
+ "code-reviewer": "sonnet",
21
+ "debugger": "sonnet",
22
+ "frontend": "gemini-3-pro-high",
23
+ "delphi": "gpt-5.2-medium",
24
+ }
25
+
26
+ # Tool display names
27
+ TOOL_NAMES = {
28
+ "mcp__stravinsky__ast_grep_search": "ast-grep",
29
+ "mcp__stravinsky__grep_search": "grep",
30
+ "mcp__stravinsky__glob_files": "glob",
31
+ "mcp__stravinsky__lsp_diagnostics": "lsp-diagnostics",
32
+ "mcp__stravinsky__lsp_hover": "lsp-hover",
33
+ "mcp__stravinsky__lsp_goto_definition": "lsp-goto-def",
34
+ "mcp__stravinsky__lsp_find_references": "lsp-find-refs",
35
+ "mcp__stravinsky__lsp_document_symbols": "lsp-symbols",
36
+ "mcp__stravinsky__lsp_workspace_symbols": "lsp-workspace-symbols",
37
+ "mcp__stravinsky__invoke_gemini": "gemini",
38
+ "mcp__stravinsky__invoke_openai": "openai",
39
+ "mcp__grep-app__searchCode": "grep.app",
40
+ "mcp__grep-app__github_file": "github-file",
41
+ }
42
+
43
+
44
+ def extract_description(tool_name: str, params: dict) -> str:
45
+ """Extract a concise description of what the tool did."""
46
+
47
+ # AST-grep
48
+ if "ast_grep" in tool_name:
49
+ pattern = params.get("pattern", "")
50
+ directory = params.get("directory", ".")
51
+ return f"Searching AST in {directory} for '{pattern[:40]}...'"
52
+
53
+ # Grep/search
54
+ if "grep_search" in tool_name or "searchCode" in tool_name:
55
+ pattern = params.get("pattern", params.get("query", ""))
56
+ return f"Searching for '{pattern[:40]}...'"
57
+
58
+ # Glob
59
+ if "glob_files" in tool_name:
60
+ pattern = params.get("pattern", "")
61
+ return f"Finding files matching '{pattern}'"
62
+
63
+ # LSP diagnostics
64
+ if "lsp_diagnostics" in tool_name:
65
+ file_path = params.get("file_path", "")
66
+ filename = os.path.basename(file_path) if file_path else "file"
67
+ return f"Checking {filename} for errors"
68
+
69
+ # LSP hover
70
+ if "lsp_hover" in tool_name:
71
+ file_path = params.get("file_path", "")
72
+ line = params.get("line", "")
73
+ filename = os.path.basename(file_path) if file_path else "file"
74
+ return f"Type info for {filename}:{line}"
75
+
76
+ # LSP goto definition
77
+ if "lsp_goto" in tool_name:
78
+ file_path = params.get("file_path", "")
79
+ filename = os.path.basename(file_path) if file_path else "symbol"
80
+ return f"Finding definition in {filename}"
81
+
82
+ # LSP find references
83
+ if "lsp_find_references" in tool_name:
84
+ file_path = params.get("file_path", "")
85
+ filename = os.path.basename(file_path) if file_path else "symbol"
86
+ return f"Finding all references to symbol in {filename}"
87
+
88
+ # LSP symbols
89
+ if "lsp_symbols" in tool_name or "lsp_document_symbols" in tool_name:
90
+ file_path = params.get("file_path", "")
91
+ filename = os.path.basename(file_path) if file_path else "file"
92
+ return f"Getting symbols from {filename}"
93
+
94
+ if "lsp_workspace_symbols" in tool_name:
95
+ query = params.get("query", "")
96
+ return f"Searching workspace for symbol '{query}'"
97
+
98
+ # Gemini invocation
99
+ if "invoke_gemini" in tool_name:
100
+ prompt = params.get("prompt", "")
101
+ # Extract first meaningful line
102
+ first_line = prompt.split('\n')[0][:50] if prompt else "Processing"
103
+ return first_line
104
+
105
+ # OpenAI invocation
106
+ if "invoke_openai" in tool_name:
107
+ prompt = params.get("prompt", "")
108
+ first_line = prompt.split('\n')[0][:50] if prompt else "Strategic analysis"
109
+ return first_line
110
+
111
+ # GitHub file fetch
112
+ if "github_file" in tool_name:
113
+ path = params.get("path", "")
114
+ repo = params.get("repo", "")
115
+ return f"Fetching {path} from {repo}"
116
+
117
+ # Task delegation
118
+ if tool_name == "Task":
119
+ subagent_type = params.get("subagent_type", "unknown")
120
+ description = params.get("description", "")
121
+ model = AGENT_MODELS.get(subagent_type, "unknown")
122
+ return f"{subagent_type}:{model}('{description}')"
123
+
124
+ return "Processing"
125
+
126
+
127
+ def main():
128
+ try:
129
+ # Read hook input from stdin
130
+ hook_input = json.loads(sys.stdin.read())
131
+
132
+ tool_name = hook_input.get("toolName", hook_input.get("tool_name", ""))
133
+ params = hook_input.get("params", hook_input.get("tool_input", {}))
134
+
135
+ # Only output messages for MCP tools and Task delegations
136
+ if not (tool_name.startswith("mcp__") or tool_name == "Task"):
137
+ sys.exit(0)
138
+
139
+ # Get tool display name
140
+ display_name = TOOL_NAMES.get(tool_name, tool_name)
141
+
142
+ # Special handling for Task delegations
143
+ if tool_name == "Task":
144
+ subagent_type = params.get("subagent_type", "unknown")
145
+ description = params.get("description", "")
146
+ model = AGENT_MODELS.get(subagent_type, "unknown")
147
+
148
+ # Show full agent delegation message
149
+ print(f"🎯 {subagent_type}:{model}('{description}')", file=sys.stderr)
150
+ else:
151
+ # Regular tool usage
152
+ description = extract_description(tool_name, params)
153
+ print(f"🔧 {display_name}('{description}')", file=sys.stderr)
154
+
155
+ sys.exit(0)
156
+
157
+ except Exception as e:
158
+ # On error, fail silently (don't disrupt workflow)
159
+ print(f"Tool messaging hook error: {e}", file=sys.stderr)
160
+ sys.exit(0)
161
+
162
+
163
+ if __name__ == "__main__":
164
+ main()
@@ -1,19 +1,23 @@
1
- """
2
- Tool output truncator hook.
3
- Limits the size of tool outputs to prevent context bloat.
4
- """
1
+ import os
2
+ import sys
3
+ import json
5
4
 
6
- from typing import Any, Dict, Optional
5
+ MAX_CHARS = 30000
7
6
 
8
- async def output_truncator_hook(tool_name: str, arguments: Dict[str, Any], output: str) -> Optional[str]:
9
- """
10
- Truncates tool output if it exceeds a certain length.
11
- """
12
- MAX_LENGTH = 30000 # 30k characters limit
13
-
14
- if len(output) > MAX_LENGTH:
15
- truncated = output[:MAX_LENGTH]
16
- summary = f"\n\n... (Result truncated from {len(output)} chars to {MAX_LENGTH} chars) ..."
17
- return truncated + summary
18
-
19
- return None
7
+ def main():
8
+ try:
9
+ data = json.load(sys.stdin)
10
+ tool_response = data.get("tool_response", "")
11
+ except Exception:
12
+ return
13
+
14
+ if len(tool_response) > MAX_CHARS:
15
+ header = f"[TRUNCATED - {len(tool_response)} chars reduced to {MAX_CHARS}]\n"
16
+ footer = "\n...[TRUNCATED]"
17
+ truncated = tool_response[:MAX_CHARS]
18
+ print(header + truncated + footer)
19
+ else:
20
+ print(tool_response)
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1,151 @@
1
+ """
2
+ Desktop Notifications Manager for Stravinsky.
3
+
4
+ Provides cross-platform desktop notifications (macOS, Linux, Windows)
5
+ for long-running operations like codebase indexing.
6
+
7
+ Supports:
8
+ - Non-blocking async notifications
9
+ - Platform-specific backends
10
+ - Notification queuing
11
+ """
12
+
13
+ import logging
14
+ import platform
15
+ import subprocess
16
+ from pathlib import Path
17
+ from typing import Dict, Optional
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class NotificationManager:
23
+ """
24
+ Cross-platform desktop notification manager.
25
+
26
+ Provides non-blocking notifications with automatic platform detection.
27
+ """
28
+
29
+ def __init__(self, app_name: str = "Stravinsky"):
30
+ self.app_name = app_name
31
+ self.system = platform.system()
32
+
33
+ def _get_notification_command(
34
+ self,
35
+ title: str,
36
+ message: str,
37
+ sound: bool = True
38
+ ) -> Optional[list]:
39
+ """Get platform-specific notification command."""
40
+ if self.system == "Darwin": # macOS
41
+ script = f'display notification "{message}" with title "{title}"'
42
+ if sound:
43
+ script += ' sound name "Glass"'
44
+ return ["osascript", "-e", script]
45
+
46
+ elif self.system == "Linux":
47
+ cmd = ["notify-send", "--app-name", self.app_name, title, message]
48
+ if sound:
49
+ cmd.extend(["--urgency=normal"])
50
+ return cmd
51
+
52
+ elif self.system == "Windows":
53
+ ps_script = f"""
54
+ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
55
+ [Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
56
+ [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
57
+
58
+ $template = @"
59
+ <toast>
60
+ <visual>
61
+ <binding template="ToastGeneric">
62
+ <text>{title}</text>
63
+ <text>{message}</text>
64
+ </binding>
65
+ </visual>
66
+ </toast>
67
+ "@
68
+
69
+ $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
70
+ $xml.LoadXml($template)
71
+ $toast = New-Object Windows.UI.Notifications.ToastNotification $xml
72
+ [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("{self.app_name}").Show($toast)
73
+ """
74
+ return ["powershell", "-Command", ps_script]
75
+
76
+ return None
77
+
78
+ def _send_notification_sync(
79
+ self,
80
+ title: str,
81
+ message: str,
82
+ sound: bool = True
83
+ ) -> bool:
84
+ """Send notification synchronously (blocking)."""
85
+ cmd = self._get_notification_command(title, message, sound)
86
+
87
+ if not cmd:
88
+ logger.warning(
89
+ f"[Notifications] Desktop notifications not supported on {self.system}"
90
+ )
91
+ return False
92
+
93
+ try:
94
+ subprocess.Popen(
95
+ cmd,
96
+ stdout=subprocess.DEVNULL,
97
+ stderr=subprocess.DEVNULL,
98
+ start_new_session=True
99
+ )
100
+ logger.debug(f"[Notifications] Sent: {title}")
101
+ return True
102
+ except FileNotFoundError:
103
+ logger.warning(f"[Notifications] Command not found: {cmd[0]}")
104
+ return False
105
+ except Exception as e:
106
+ logger.error(f"[Notifications] Failed to send notification: {e}")
107
+ return False
108
+
109
+ async def notify_reindex_start(self, project_path: str) -> bool:
110
+ """Notify that codebase reindexing has started."""
111
+ path = Path(project_path).name or Path(project_path).parent.name
112
+ title = "Codebase Indexing Started"
113
+ message = f"Indexing {path}..."
114
+ return self._send_notification_sync(title, message, sound=True)
115
+
116
+ async def notify_reindex_complete(self, stats: Dict) -> bool:
117
+ """Notify that codebase reindexing is complete."""
118
+ indexed = stats.get("indexed", 0)
119
+ pruned = stats.get("pruned", 0)
120
+ time_taken = stats.get("time_taken", 0)
121
+
122
+ title = "Codebase Indexing Complete"
123
+ message = f"Indexed {indexed} chunks, pruned {pruned} stale entries in {time_taken}s"
124
+
125
+ return self._send_notification_sync(title, message, sound=True)
126
+
127
+ async def notify_reindex_error(self, error_message: str) -> bool:
128
+ """Notify that codebase reindexing failed."""
129
+ title = "Codebase Indexing Failed"
130
+ # Truncate long error messages
131
+ message = error_message[:100] + "..." if len(error_message) > 100 else error_message
132
+
133
+ return self._send_notification_sync(title, message, sound=True)
134
+
135
+
136
+ # Global singleton instance
137
+ _notification_manager: Optional[NotificationManager] = None
138
+
139
+
140
+ def get_notification_manager() -> NotificationManager:
141
+ """Get or create the global notification manager instance."""
142
+ global _notification_manager
143
+ if _notification_manager is None:
144
+ _notification_manager = NotificationManager()
145
+ return _notification_manager
146
+
147
+
148
+ def reset_notification_manager() -> None:
149
+ """Reset the global notification manager (for testing)."""
150
+ global _notification_manager
151
+ _notification_manager = None
@@ -6,13 +6,15 @@ from . import explore
6
6
  from . import frontend
7
7
  from . import document_writer
8
8
  from . import multimodal
9
+ from . import planner
9
10
 
10
11
  __all__ = [
11
12
  "stravinsky",
12
- "delphi",
13
+ "delphi",
13
14
  "dewey",
14
15
  "explore",
15
16
  "frontend",
16
17
  "document_writer",
17
18
  "multimodal",
19
+ "planner",
18
20
  ]
@@ -50,8 +50,8 @@ Classify EVERY request into one of these categories before taking action:
50
50
 
51
51
  | Type | Trigger Examples | Tools |
52
52
  |------|------------------|-------|
53
- | **TYPE A: CONCEPTUAL** | "How do I use X?", "Best practice for Y?" | docs + websearch (parallel) |
54
- | **TYPE B: IMPLEMENTATION** | "How does X implement Y?", "Show me source of Z" | gh clone + read + blame |
53
+ | **TYPE A: CONCEPTUAL** | "How do I use X?", "Best practice for Y?" | exa websearch + grep-app GitHub search (parallel) |
54
+ | **TYPE B: IMPLEMENTATION** | "How does X implement Y?", "Show me source of Z" | gh clone + ast-grep + read + blame |
55
55
  | **TYPE C: CONTEXT** | "Why was this changed?", "History of X?" | gh issues/prs + git log/blame |
56
56
  | **TYPE D: COMPREHENSIVE** | Complex/ambiguous requests | ALL tools in parallel |
57
57
 
@@ -64,12 +64,15 @@ Classify EVERY request into one of these categories before taking action:
64
64
 
65
65
  **Execute in parallel (3+ calls)**:
66
66
  ```
67
- Tool 1: Search official documentation
68
- Tool 2: Web search for recent articles/tutorials ("library-name topic 2025")
69
- Tool 3: GitHub code search for usage patterns (grep_search)
67
+ Tool 1: mcp__MCP_DOCKER__web_search_exa(query="library-name topic 2026", num_results=5)
68
+ -> Current articles, blog posts, best practices (ALWAYS use Exa instead of native WebSearch)
69
+ Tool 2: mcp__grep-app__searchCode(query="library-name implementation pattern")
70
+ -> Real GitHub code examples with permalinks
71
+ Tool 3: gh search repos "library-name" --sort stars --limit 5
72
+ -> Popular repositories for reference
70
73
  ```
71
74
 
72
- **Output**: Summarize findings with links to official docs and real-world examples.
75
+ **Output**: Synthesize with evidence links (Exa URLs + GitHub permalinks).
73
76
 
74
77
  ---
75
78
 
@@ -85,8 +88,8 @@ Step 2: Get commit SHA for permalinks
85
88
  cd ${TMPDIR:-/tmp}/repo-name && git rev-parse HEAD
86
89
 
87
90
  Step 3: Find the implementation
88
- - grep_search for function/class
89
- - ast_grep_search for AST patterns
91
+ - mcp__ast-grep__find_code(pattern="function $NAME", language="typescript") for structural search
92
+ - grep_search for function/class names
90
93
  - Read the specific file
91
94
  - git blame for context if needed
92
95
 
@@ -97,9 +100,9 @@ Step 4: Construct permalink
97
100
  **Parallel acceleration (4+ calls)**:
98
101
  ```
99
102
  Tool 1: gh repo clone owner/repo ${TMPDIR:-/tmp}/repo -- --depth 1
100
- Tool 2: GitHub code search for function_name
103
+ Tool 2: mcp__grep-app__searchCode(query="repo:owner/repo function_name")
101
104
  Tool 3: gh api repos/owner/repo/commits/HEAD --jq '.sha'
102
- Tool 4: Documentation search for relevant API
105
+ Tool 4: mcp__MCP_DOCKER__web_search_exa(query="library-name function_name documentation 2026")
103
106
  ```
104
107
 
105
108
  ---
@@ -131,13 +134,15 @@ gh api repos/owner/repo/pulls/<number>/files
131
134
 
132
135
  **Execute ALL in parallel (6+ calls)**:
133
136
  ```
134
- // Documentation & Web
135
- Tool 1: Documentation search
136
- Tool 2: Web search ("topic recent updates 2025")
137
+ // Web Search (ALWAYS use Exa)
138
+ Tool 1: mcp__MCP_DOCKER__web_search_exa(query="topic recent updates 2026", num_results=10)
137
139
 
138
- // Code Search
139
- Tool 3: grep_search(pattern1)
140
- Tool 4: grep_search(pattern2) or ast_grep_search
140
+ // GitHub Code Search
141
+ Tool 2: mcp__grep-app__searchCode(query="topic implementation pattern")
142
+ Tool 3: mcp__grep-app__searchCode(query="topic usage example")
143
+
144
+ // AST Pattern Search
145
+ Tool 4: mcp__ast-grep__find_code(pattern="$PATTERN", language="typescript")
141
146
 
142
147
  // Source Analysis
143
148
  Tool 5: gh repo clone owner/repo ${TMPDIR:-/tmp}/repo -- --depth 1
@@ -182,15 +187,20 @@ https://github.com/tanstack/query/blob/abc123def/packages/react-query/src/useQue
182
187
 
183
188
  ---
184
189
 
185
- ## TOOL REFERENCE (Stravinsky Tools)
190
+ ## TOOL REFERENCE (Stravinsky + MCP DOCKER Tools)
186
191
 
187
192
  ### Primary Tools by Purpose
188
193
 
189
194
  | Purpose | Tool | Usage |
190
195
  |---------|------|-------|
191
- | **Code Search** | grep_search | Pattern-based search in local/cloned repos |
192
- | **AST Search** | ast_grep_search | AST-aware code pattern search |
193
- | **File Glob** | glob_files | Find files by pattern |
196
+ | **Web Search** | `mcp__MCP_DOCKER__web_search_exa` | **ALWAYS use instead of native WebSearch** - Real-time web search for current articles, docs, tutorials |
197
+ | **GitHub Code Search** | `mcp__grep-app__searchCode` | Search across public GitHub repositories - returns permalinks |
198
+ | **GitHub File Fetch** | `mcp__grep-app__github_file` | Fetch specific file from GitHub repo |
199
+ | **AST Pattern Search** | `mcp__ast-grep__find_code` | Structural code search across 25+ languages with AST awareness |
200
+ | **AST Replace** | `mcp__ast-grep__replace` | AST-aware code refactoring and replacement |
201
+ | **Local Code Search** | `grep_search` | Pattern-based search in local/cloned repos (uses ripgrep) |
202
+ | **Local AST Search** | `ast_grep_search` | AST search in cloned repos |
203
+ | **File Glob** | `glob_files` | Find files by pattern |
194
204
  | **Clone Repo** | gh CLI | `gh repo clone owner/repo ${TMPDIR:-/tmp}/name -- --depth 1` |
195
205
  | **Issues/PRs** | gh CLI | `gh search issues/prs "query" --repo owner/repo` |
196
206
  | **View Issue/PR** | gh CLI | `gh issue/pr view <num> --repo owner/repo --comments` |