stravinsky 0.1.2__py3-none-any.whl → 0.2.7__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 -5
- mcp_bridge/auth/cli.py +7 -0
- mcp_bridge/hooks/__init__.py +28 -0
- mcp_bridge/hooks/budget_optimizer.py +38 -0
- mcp_bridge/hooks/compaction.py +32 -0
- mcp_bridge/hooks/directory_context.py +40 -0
- mcp_bridge/hooks/edit_recovery.py +41 -0
- mcp_bridge/hooks/manager.py +77 -0
- mcp_bridge/hooks/truncator.py +19 -0
- mcp_bridge/native_hooks/context.py +38 -0
- mcp_bridge/native_hooks/edit_recovery.py +46 -0
- mcp_bridge/native_hooks/truncator.py +23 -0
- mcp_bridge/prompts/stravinsky.py +29 -19
- mcp_bridge/server.py +225 -668
- mcp_bridge/server_tools.py +529 -0
- mcp_bridge/tools/__init__.py +11 -2
- mcp_bridge/tools/agent_manager.py +99 -7
- mcp_bridge/tools/continuous_loop.py +67 -0
- mcp_bridge/tools/init.py +50 -0
- mcp_bridge/tools/lsp/tools.py +15 -15
- mcp_bridge/tools/model_invoke.py +64 -0
- mcp_bridge/tools/task_runner.py +97 -0
- mcp_bridge/tools/templates.py +86 -0
- {stravinsky-0.1.2.dist-info → stravinsky-0.2.7.dist-info}/METADATA +61 -10
- stravinsky-0.2.7.dist-info/RECORD +47 -0
- stravinsky-0.1.2.dist-info/RECORD +0 -32
- {stravinsky-0.1.2.dist-info → stravinsky-0.2.7.dist-info}/WHEEL +0 -0
- {stravinsky-0.1.2.dist-info → stravinsky-0.2.7.dist-info}/entry_points.txt +0 -0
mcp_bridge/__init__.py
CHANGED
mcp_bridge/auth/cli.py
CHANGED
|
@@ -9,6 +9,7 @@ import sys
|
|
|
9
9
|
import time
|
|
10
10
|
|
|
11
11
|
from .token_store import TokenStore
|
|
12
|
+
from ..tools.init import bootstrap_repo
|
|
12
13
|
from .oauth import perform_oauth_flow as gemini_oauth, refresh_access_token as gemini_refresh
|
|
13
14
|
from .openai_oauth import (
|
|
14
15
|
perform_oauth_flow as openai_oauth,
|
|
@@ -184,6 +185,9 @@ def main():
|
|
|
184
185
|
help="Provider to refresh token for",
|
|
185
186
|
)
|
|
186
187
|
|
|
188
|
+
# init command
|
|
189
|
+
subparsers.add_parser("init", help="Bootstrap current repository for Stravinsky")
|
|
190
|
+
|
|
187
191
|
args = parser.parse_args()
|
|
188
192
|
|
|
189
193
|
if not args.command:
|
|
@@ -200,6 +204,9 @@ def main():
|
|
|
200
204
|
return cmd_status(token_store)
|
|
201
205
|
elif args.command == "refresh":
|
|
202
206
|
return cmd_refresh(args.provider, token_store)
|
|
207
|
+
elif args.command == "init":
|
|
208
|
+
print(bootstrap_repo())
|
|
209
|
+
return 0
|
|
203
210
|
|
|
204
211
|
return 0
|
|
205
212
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hooks initialization.
|
|
3
|
+
Registers all Tier 1-3 hooks into the HookManager.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .manager import get_hook_manager
|
|
7
|
+
from .truncator import output_truncator_hook
|
|
8
|
+
from .edit_recovery import edit_error_recovery_hook
|
|
9
|
+
from .directory_context import directory_context_hook
|
|
10
|
+
from .compaction import context_compaction_hook
|
|
11
|
+
from .budget_optimizer import budget_optimizer_hook
|
|
12
|
+
|
|
13
|
+
def initialize_hooks():
|
|
14
|
+
"""Register all available hooks."""
|
|
15
|
+
manager = get_hook_manager()
|
|
16
|
+
|
|
17
|
+
# Tier 1
|
|
18
|
+
manager.register_post_tool_call(output_truncator_hook)
|
|
19
|
+
manager.register_post_tool_call(edit_error_recovery_hook)
|
|
20
|
+
|
|
21
|
+
# Tier 2
|
|
22
|
+
manager.register_pre_model_invoke(directory_context_hook)
|
|
23
|
+
manager.register_pre_model_invoke(context_compaction_hook)
|
|
24
|
+
|
|
25
|
+
# Tier 3
|
|
26
|
+
manager.register_pre_model_invoke(budget_optimizer_hook)
|
|
27
|
+
|
|
28
|
+
# initialize_hooks()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thinking budget optimizer hook.
|
|
3
|
+
Analyzes prompt complexity and adjusts thinking_budget for models that support it.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
REASONING_KEYWORDS = [
|
|
9
|
+
"architect", "design", "refactor", "debug", "complex", "optimize",
|
|
10
|
+
"summarize", "analyze", "explain", "why", "review", "strangler"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
async def budget_optimizer_hook(params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
14
|
+
"""
|
|
15
|
+
Adjusts the thinking_budget based on presence of reasoning-heavy keywords.
|
|
16
|
+
"""
|
|
17
|
+
model = params.get("model", "")
|
|
18
|
+
# Only applies to models that typically support reasoning budgets (Gemini 2.0 Thinking, GPT-o1, etc.)
|
|
19
|
+
if not any(m in model for m in ["thinking", "flash-thinking", "o1", "o3"]):
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
prompt = params.get("prompt", "").lower()
|
|
23
|
+
|
|
24
|
+
# Simple heuristic
|
|
25
|
+
is_complex = any(keyword in prompt for keyword in REASONING_KEYWORDS)
|
|
26
|
+
|
|
27
|
+
current_budget = params.get("thinking_budget", 0)
|
|
28
|
+
|
|
29
|
+
if is_complex and current_budget < 4000:
|
|
30
|
+
# Increase budget for complex tasks
|
|
31
|
+
params["thinking_budget"] = 16000
|
|
32
|
+
return params
|
|
33
|
+
elif not is_complex and current_budget > 2000:
|
|
34
|
+
# Lower budget for simple tasks to save time/cost
|
|
35
|
+
params["thinking_budget"] = 2000
|
|
36
|
+
return params
|
|
37
|
+
|
|
38
|
+
return None
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Preemptive context compaction hook.
|
|
3
|
+
Monitors context size and injects optimization reminders.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
THRESHOLD_CHARS = 100000 # Roughly 25k-30k tokens for typical LLM text
|
|
9
|
+
|
|
10
|
+
COMPACTION_REMINDER = """
|
|
11
|
+
> **[SYSTEM ALERT - CONTEXT WINDOW NEAR LIMIT]**
|
|
12
|
+
> The current conversation history is reaching its limits. Performance may degrade.
|
|
13
|
+
> Please **STOP** and perform a **Session Compaction**:
|
|
14
|
+
> 1. Summarize all work completed so far in a `TASK_STATE.md` (if not already done).
|
|
15
|
+
> 2. List all pending todos.
|
|
16
|
+
> 3. Clear unnecessary tool outputs from your reasoning.
|
|
17
|
+
> 4. Keep your next responses concise and focused only on the current sub-task.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
async def context_compaction_hook(params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
21
|
+
"""
|
|
22
|
+
Checks prompt length and injects a compaction reminder if it's too large.
|
|
23
|
+
"""
|
|
24
|
+
prompt = params.get("prompt", "")
|
|
25
|
+
|
|
26
|
+
if len(prompt) > THRESHOLD_CHARS:
|
|
27
|
+
# Check if we haven't already injected the reminder recently
|
|
28
|
+
if "CONTEXT WINDOW NEAR LIMIT" not in prompt:
|
|
29
|
+
params["prompt"] = COMPACTION_REMINDER + prompt
|
|
30
|
+
return params
|
|
31
|
+
|
|
32
|
+
return None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Directory context injector hook.
|
|
3
|
+
Automatically finds and injects local AGENTS.md or README.md content based on the current context.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
async def directory_context_hook(params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
11
|
+
"""
|
|
12
|
+
Search for AGENTS.md or README.md in the current working directory and inject them.
|
|
13
|
+
"""
|
|
14
|
+
cwd = Path.cwd()
|
|
15
|
+
|
|
16
|
+
# Check for AGENTS.md or README.md
|
|
17
|
+
target_files = ["AGENTS.md", "README.md"]
|
|
18
|
+
found_file = None
|
|
19
|
+
for filename in target_files:
|
|
20
|
+
if (cwd / filename).exists():
|
|
21
|
+
found_file = cwd / filename
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
if not found_file:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
content = found_file.read_text()
|
|
29
|
+
# Injects as a special system reminder
|
|
30
|
+
injection = f"\n\n### Local Directory Context ({found_file.name}):\n{content}\n"
|
|
31
|
+
|
|
32
|
+
# Modify the prompt if it exists in params
|
|
33
|
+
if "prompt" in params:
|
|
34
|
+
# Add to the beginning of the prompt as a context block
|
|
35
|
+
params["prompt"] = injection + params["prompt"]
|
|
36
|
+
return params
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
return None
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Edit error recovery hook.
|
|
3
|
+
Detects common mistakes in file editing and injects high-priority corrective directives.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
EDIT_ERROR_PATTERNS = [
|
|
10
|
+
r"oldString and newString must be different",
|
|
11
|
+
r"oldString not found",
|
|
12
|
+
r"oldString found multiple times",
|
|
13
|
+
r"Target content not found",
|
|
14
|
+
r"Multiple occurrences of target content found",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
EDIT_RECOVERY_PROMPT = """
|
|
18
|
+
> **[EDIT ERROR - IMMEDIATE ACTION REQUIRED]**
|
|
19
|
+
> You made an Edit mistake. STOP and do this NOW:
|
|
20
|
+
> 1. **READ** the file immediately to see its ACTUAL current state.
|
|
21
|
+
> 2. **VERIFY** what the content really looks like (your assumption was wrong).
|
|
22
|
+
> 3. **APOLOGIZE** briefly to the user for the error.
|
|
23
|
+
> 4. **CONTINUE** with corrected action based on the real file content.
|
|
24
|
+
> **DO NOT** attempt another edit until you've read and verified the file state.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
async def edit_error_recovery_hook(tool_name: str, arguments: Dict[str, Any], output: str) -> Optional[str]:
|
|
28
|
+
"""
|
|
29
|
+
Analyzes tool output for edit errors and appends corrective directives.
|
|
30
|
+
"""
|
|
31
|
+
# Check if this is an edit-related tool (handling both built-in and common MCP tools)
|
|
32
|
+
edit_tools = ["replace_file_content", "multi_replace_file_content", "write_to_file", "edit_file", "Edit"]
|
|
33
|
+
|
|
34
|
+
# We also check the output content for common patterns even if the tool name doesn't match perfectly
|
|
35
|
+
is_edit_error = any(re.search(pattern, output, re.IGNORECASE) for pattern in EDIT_ERROR_PATTERNS)
|
|
36
|
+
|
|
37
|
+
if is_edit_error or any(tool in tool_name for tool in edit_tools):
|
|
38
|
+
if any(re.search(pattern, output, re.IGNORECASE) for pattern in EDIT_ERROR_PATTERNS):
|
|
39
|
+
return output + EDIT_RECOVERY_PROMPT
|
|
40
|
+
|
|
41
|
+
return None
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modular Hook System for Stravinsky.
|
|
3
|
+
Provides interception points for tool calls and model invocations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Awaitable
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class HookManager:
|
|
12
|
+
"""
|
|
13
|
+
Manages the registration and execution of hooks.
|
|
14
|
+
"""
|
|
15
|
+
_instance = None
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.pre_tool_call_hooks: List[Callable[[str, Dict[str, Any]], Awaitable[Optional[Dict[str, Any]]]]] = []
|
|
19
|
+
self.post_tool_call_hooks: List[Callable[[str, Dict[str, Any], str], Awaitable[Optional[str]]]] = []
|
|
20
|
+
self.pre_model_invoke_hooks: List[Callable[[Dict[str, Any]], Awaitable[Optional[Dict[str, Any]]]]] = []
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get_instance(cls):
|
|
24
|
+
if cls._instance is None:
|
|
25
|
+
cls._instance = cls()
|
|
26
|
+
return cls._instance
|
|
27
|
+
|
|
28
|
+
def register_pre_tool_call(self, hook: Callable[[str, Dict[str, Any]], Awaitable[Optional[Dict[str, Any]]]]):
|
|
29
|
+
"""Run before a tool is called. Can modify arguments or return early result."""
|
|
30
|
+
self.pre_tool_call_hooks.append(hook)
|
|
31
|
+
|
|
32
|
+
def register_post_tool_call(self, hook: Callable[[str, Dict[str, Any], str], Awaitable[Optional[str]]]):
|
|
33
|
+
"""Run after a tool call. Can modify or recover from tool output/error."""
|
|
34
|
+
self.post_tool_call_hooks.append(hook)
|
|
35
|
+
|
|
36
|
+
def register_pre_model_invoke(self, hook: Callable[[Dict[str, Any]], Awaitable[Optional[Dict[str, Any]]]]):
|
|
37
|
+
"""Run before model invocation. Can modify prompt or parameters."""
|
|
38
|
+
self.pre_model_invoke_hooks.append(hook)
|
|
39
|
+
|
|
40
|
+
async def execute_pre_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
41
|
+
"""Executes all pre-tool call hooks."""
|
|
42
|
+
current_args = arguments
|
|
43
|
+
for hook in self.pre_tool_call_hooks:
|
|
44
|
+
try:
|
|
45
|
+
modified_args = await hook(tool_name, current_args)
|
|
46
|
+
if modified_args is not None:
|
|
47
|
+
current_args = modified_args
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.error(f"[HookManager] Error in pre_tool_call hook {hook.__name__}: {e}")
|
|
50
|
+
return current_args
|
|
51
|
+
|
|
52
|
+
async def execute_post_tool_call(self, tool_name: str, arguments: Dict[str, Any], output: str) -> str:
|
|
53
|
+
"""Executes all post-tool call hooks."""
|
|
54
|
+
current_output = output
|
|
55
|
+
for hook in self.post_tool_call_hooks:
|
|
56
|
+
try:
|
|
57
|
+
modified_output = await hook(tool_name, arguments, current_output)
|
|
58
|
+
if modified_output is not None:
|
|
59
|
+
current_output = modified_output
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error(f"[HookManager] Error in post_tool_call hook {hook.__name__}: {e}")
|
|
62
|
+
return current_output
|
|
63
|
+
|
|
64
|
+
async def execute_pre_model_invoke(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
65
|
+
"""Executes all pre-model invoke hooks."""
|
|
66
|
+
current_params = params
|
|
67
|
+
for hook in self.pre_model_invoke_hooks:
|
|
68
|
+
try:
|
|
69
|
+
modified_params = await hook(current_params)
|
|
70
|
+
if modified_params is not None:
|
|
71
|
+
current_params = modified_params
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"[HookManager] Error in pre_model_invoke hook {hook.__name__}: {e}")
|
|
74
|
+
return current_params
|
|
75
|
+
|
|
76
|
+
def get_hook_manager() -> HookManager:
|
|
77
|
+
return HookManager.get_instance()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool output truncator hook.
|
|
3
|
+
Limits the size of tool outputs to prevent context bloat.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
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 = 10000 # 10k 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
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
try:
|
|
8
|
+
data = json.load(sys.stdin)
|
|
9
|
+
prompt = data.get("prompt", "")
|
|
10
|
+
except Exception:
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
cwd = Path(os.environ.get("CLAUDE_CWD", "."))
|
|
14
|
+
|
|
15
|
+
# Files to look for
|
|
16
|
+
context_files = ["AGENTS.md", "README.md", "CLAUDE.md"]
|
|
17
|
+
found_context = ""
|
|
18
|
+
|
|
19
|
+
for f in context_files:
|
|
20
|
+
path = cwd / f
|
|
21
|
+
if path.exists():
|
|
22
|
+
try:
|
|
23
|
+
content = path.read_text()
|
|
24
|
+
found_context += f"\n\n--- LOCAL CONTEXT: {f} ---\n{content}\n"
|
|
25
|
+
break # Only use one for brevity
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
if found_context:
|
|
30
|
+
# Prepend context to prompt
|
|
31
|
+
# We wrap the user prompt to distinguish it
|
|
32
|
+
new_prompt = f"{found_context}\n\n[USER PROMPT]\n{prompt}"
|
|
33
|
+
print(new_prompt)
|
|
34
|
+
else:
|
|
35
|
+
print(prompt)
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
# Claude Code PostToolUse inputs via Environment Variables
|
|
8
|
+
tool_name = os.environ.get("CLAUDE_TOOL_NAME")
|
|
9
|
+
|
|
10
|
+
# We only care about Edit/MultiEdit
|
|
11
|
+
if tool_name not in ["Edit", "MultiEdit"]:
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
# Read from stdin (Claude Code passes the tool response via stdin for some hook types,
|
|
15
|
+
# but for PostToolUse it's often better to check the environment variable if available.
|
|
16
|
+
# Actually, the summary says input is a JSON payload.
|
|
17
|
+
try:
|
|
18
|
+
data = json.load(sys.stdin)
|
|
19
|
+
tool_response = data.get("tool_response", "")
|
|
20
|
+
except Exception:
|
|
21
|
+
# Fallback to direct string if not JSON
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
# Error patterns
|
|
25
|
+
error_patterns = [
|
|
26
|
+
r"oldString not found",
|
|
27
|
+
r"oldString matched multiple times",
|
|
28
|
+
r"line numbers out of range"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
recovery_needed = any(re.search(p, tool_response, re.IGNORECASE) for p in error_patterns)
|
|
32
|
+
|
|
33
|
+
if recovery_needed:
|
|
34
|
+
correction = (
|
|
35
|
+
"\n\n[SYSTEM RECOVERY] It appears the Edit tool failed to find the target string. "
|
|
36
|
+
"Please call 'Read' on the file again to verify the current content, "
|
|
37
|
+
"then ensure your 'oldString' is an EXACT match including all whitespace."
|
|
38
|
+
)
|
|
39
|
+
# For PostToolUse, stdout is captured and appended/replaces output
|
|
40
|
+
print(tool_response + correction)
|
|
41
|
+
else:
|
|
42
|
+
# No change
|
|
43
|
+
print(tool_response)
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
MAX_CHARS = 10000
|
|
6
|
+
|
|
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()
|
mcp_bridge/prompts/stravinsky.py
CHANGED
|
@@ -18,8 +18,9 @@ You are "Stravinsky" - Powerful AI Agent with orchestration capabilities.
|
|
|
18
18
|
- Parsing implicit requirements from explicit requests
|
|
19
19
|
- Adapting to codebase maturity (disciplined vs chaotic)
|
|
20
20
|
- Delegating specialized work to the right subagents
|
|
21
|
-
|
|
22
|
-
-
|
|
21
|
+
**AGGRESSIVE PARALLEL EXECUTION (ULTRAWORK)** - spawn multiple subagents simultaneously for research, implementation, and testing.
|
|
22
|
+
- **LSP-FIRST RESEARCH**: You MUST use LSP tools (`lsp_hover`, `lsp_goto_definition`, etc.) before falling back to `grep` or `rg` for Python/TypeScript.
|
|
23
|
+
- Follows user instructions. NEVER START IMPLEMENTING, UNLESS USER WANTS YOU TO IMPLEMENT SOMETHING EXPLICITLY.
|
|
23
24
|
|
|
24
25
|
**Operating Mode**: You NEVER work alone when specialists are available.
|
|
25
26
|
**DEFAULT: SPAWN PARALLEL AGENTS for any task with 2+ independent components.**
|
|
@@ -29,13 +30,14 @@ You are "Stravinsky" - Powerful AI Agent with orchestration capabilities.
|
|
|
29
30
|
- Deep research → `agent_spawn` parallel background agents with full tool access
|
|
30
31
|
- Complex tasks → Break into components and spawn agents IN PARALLEL
|
|
31
32
|
|
|
32
|
-
## ULTRATHINK Protocol
|
|
33
|
+
## ULTRAWORK & ULTRATHINK Protocol
|
|
33
34
|
|
|
34
|
-
When the user says **"ULTRATHINK"**, **"think harder"**, or **"think hard"**:
|
|
35
|
-
1. **
|
|
36
|
-
2. **
|
|
37
|
-
3. **
|
|
38
|
-
4. **
|
|
35
|
+
When the user says **"ULTRAWORK"**, **"ULTRATHINK"**, **"think harder"**, or **"think hard"**:
|
|
36
|
+
1. **Engage ULTRAWORK** - Immediately spawn 2-4 sub-agents to handle different aspects of the task (research, plan, implementation, verification) in parallel.
|
|
37
|
+
2. **Override brevity** - engage in exhaustive, deep-level reasoning
|
|
38
|
+
3. **Multi-dimensional analysis** - examine through psychological, technical, accessibility, scalability lenses
|
|
39
|
+
4. **Maximum depth** - if reasoning feels easy, dig deeper until logic is irrefutable
|
|
40
|
+
5. **Extended thinking budget** - take the time needed for thorough deliberation
|
|
39
41
|
|
|
40
42
|
</Role>"""
|
|
41
43
|
|
|
@@ -99,18 +101,19 @@ Before following existing patterns, assess whether they're worth following.
|
|
|
99
101
|
|
|
100
102
|
STRAVINSKY_DELEGATION = """## Phase 2 - Parallel Agents & Delegation (DEFAULT BEHAVIOR)
|
|
101
103
|
|
|
102
|
-
### DEFAULT: Spawn Parallel Agents
|
|
104
|
+
### DEFAULT: Spawn Parallel Agents (ULTRAWORK)
|
|
103
105
|
|
|
104
106
|
For ANY task with 2+ independent components:
|
|
105
|
-
1. **Immediately spawn parallel agents** using `agent_spawn
|
|
106
|
-
2.
|
|
107
|
-
3.
|
|
108
|
-
4.
|
|
107
|
+
1. **Immediately spawn parallel agents** using `agent_spawn`.
|
|
108
|
+
2. **LSP ALWAYS**: For code tasks, ensure at least one agent is dedicated to LSP-based symbol resolution.
|
|
109
|
+
3. Continue working on the main task while agents execute.
|
|
110
|
+
4. Use `agent_progress` to monitor running agents.
|
|
111
|
+
5. Collect results with `agent_output` when ready.
|
|
109
112
|
|
|
110
|
-
**Examples of parallel spawning:**
|
|
111
|
-
- "Add feature X" → Spawn: 1)
|
|
112
|
-
- "Fix bug in Y" → Spawn: 1) debug agent
|
|
113
|
-
- "Build component Z" → Spawn: 1)
|
|
113
|
+
**Examples of ULTRAWORK parallel spawning:**
|
|
114
|
+
- "Add feature X" → Spawn: 1) `explore` agent for LSP/Symbol research, 2) `librarian` for external docs, 3) `delphi` for architecture plan.
|
|
115
|
+
- "Fix bug in Y" → Spawn: 1) `debug` agent for log analysis, 2) `explore` agent with LSP to trace call stack.
|
|
116
|
+
- "Build component Z" → Spawn: 1) `frontend` agent for UI, 2) `explore` for backend integration patterns.
|
|
114
117
|
|
|
115
118
|
### Agent Types:
|
|
116
119
|
| Type | Purpose |
|
|
@@ -151,9 +154,16 @@ When delegating to external models or agents:
|
|
|
151
154
|
```
|
|
152
155
|
|
|
153
156
|
### After Delegation:
|
|
154
|
-
- VERIFY the results work as expected
|
|
155
|
-
- VERIFY it follows existing codebase patterns
|
|
156
157
|
- VERIFY expected result came out
|
|
158
|
+
|
|
159
|
+
### Agent Reliability Protocol (CRITICAL)
|
|
160
|
+
|
|
161
|
+
If a background agent fails or times out:
|
|
162
|
+
1. **Analyze Failure**: Use `agent_progress` to see the last available output and logs.
|
|
163
|
+
2. **Handle Timout**: If status is `failed` with a timeout error, break the task into smaller sub-tasks and respawn multiple agents. Increasing `timeout` is a secondary option.
|
|
164
|
+
3. **Handle Error**: If the agent errored, refine the prompt to be more specific or provide more context, then use `agent_retry`.
|
|
165
|
+
4. **Zombie Recovery**: If `agent_progress` detects a "Zombie" (process died), immediately `agent_retry`.
|
|
166
|
+
5. **Escalation**: If a task fails 2 consecutive times, stop and ask the user or consult Delphi.
|
|
157
167
|
"""
|
|
158
168
|
|
|
159
169
|
|