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
|
@@ -36,6 +36,8 @@ AGENT_MODEL_ROUTING = {
|
|
|
36
36
|
"multimodal": None,
|
|
37
37
|
"frontend": None,
|
|
38
38
|
"delphi": None,
|
|
39
|
+
"research-lead": None, # Hierarchical orchestrator using gemini-3-flash
|
|
40
|
+
"implementation-lead": None, # Hierarchical orchestrator using haiku
|
|
39
41
|
# Planner uses Opus for superior reasoning about dependencies and parallelization
|
|
40
42
|
"planner": "opus",
|
|
41
43
|
# Default for unknown agent types (coding tasks) - use Sonnet 4.5
|
|
@@ -48,12 +50,116 @@ AGENT_COST_TIERS = {
|
|
|
48
50
|
"dewey": "CHEAP", # Uses gemini-3-flash
|
|
49
51
|
"document_writer": "CHEAP", # Uses gemini-3-flash
|
|
50
52
|
"multimodal": "CHEAP", # Uses gemini-3-flash
|
|
53
|
+
"research-lead": "CHEAP", # Uses gemini-3-flash
|
|
54
|
+
"implementation-lead": "CHEAP", # Uses haiku
|
|
51
55
|
"frontend": "MEDIUM", # Uses gemini-3-pro-high
|
|
52
56
|
"delphi": "EXPENSIVE", # Uses gpt-5.2 (OpenAI GPT)
|
|
53
57
|
"planner": "EXPENSIVE", # Uses Claude Opus 4.5
|
|
54
58
|
"_default": "EXPENSIVE", # Claude Sonnet 4.5 via CLI
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
# Display model names for output formatting (user-visible)
|
|
62
|
+
AGENT_DISPLAY_MODELS = {
|
|
63
|
+
"explore": "gemini-3-flash",
|
|
64
|
+
"dewey": "gemini-3-flash",
|
|
65
|
+
"document_writer": "gemini-3-flash",
|
|
66
|
+
"multimodal": "gemini-3-flash",
|
|
67
|
+
"research-lead": "gemini-3-flash",
|
|
68
|
+
"implementation-lead": "haiku",
|
|
69
|
+
"frontend": "gemini-3-pro-high",
|
|
70
|
+
"delphi": "gpt-5.2",
|
|
71
|
+
"planner": "opus-4.5",
|
|
72
|
+
"_default": "sonnet-4.5",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Cost tier emoji indicators for visual differentiation
|
|
76
|
+
# Colors indicate cost: 🟢 cheap/free, 🔵 medium, 🟣 expensive (GPT), 🟠 Claude
|
|
77
|
+
COST_TIER_EMOJI = {
|
|
78
|
+
"CHEAP": "🟢", # Free/cheap models (gemini-3-flash, haiku)
|
|
79
|
+
"MEDIUM": "🔵", # Medium cost (gemini-3-pro-high)
|
|
80
|
+
"EXPENSIVE": "🟣", # Expensive models (gpt-5.2, opus)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Model family indicators
|
|
84
|
+
MODEL_FAMILY_EMOJI = {
|
|
85
|
+
"gemini-3-flash": "🟢",
|
|
86
|
+
"gemini-3-pro-high": "🔵",
|
|
87
|
+
"haiku": "🟢",
|
|
88
|
+
"sonnet-4.5": "🟠",
|
|
89
|
+
"opus-4.5": "🟣",
|
|
90
|
+
"gpt-5.2": "🟣",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# ANSI color codes for terminal output
|
|
94
|
+
class Colors:
|
|
95
|
+
"""ANSI color codes for colorized terminal output."""
|
|
96
|
+
RESET = "\033[0m"
|
|
97
|
+
BOLD = "\033[1m"
|
|
98
|
+
DIM = "\033[2m"
|
|
99
|
+
|
|
100
|
+
# Foreground colors
|
|
101
|
+
BLACK = "\033[30m"
|
|
102
|
+
RED = "\033[31m"
|
|
103
|
+
GREEN = "\033[32m"
|
|
104
|
+
YELLOW = "\033[33m"
|
|
105
|
+
BLUE = "\033[34m"
|
|
106
|
+
MAGENTA = "\033[35m"
|
|
107
|
+
CYAN = "\033[36m"
|
|
108
|
+
WHITE = "\033[37m"
|
|
109
|
+
|
|
110
|
+
# Bright foreground colors
|
|
111
|
+
BRIGHT_BLACK = "\033[90m"
|
|
112
|
+
BRIGHT_RED = "\033[91m"
|
|
113
|
+
BRIGHT_GREEN = "\033[92m"
|
|
114
|
+
BRIGHT_YELLOW = "\033[93m"
|
|
115
|
+
BRIGHT_BLUE = "\033[94m"
|
|
116
|
+
BRIGHT_MAGENTA = "\033[95m"
|
|
117
|
+
BRIGHT_CYAN = "\033[96m"
|
|
118
|
+
BRIGHT_WHITE = "\033[97m"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_agent_emoji(agent_type: str) -> str:
|
|
122
|
+
"""Get the colored emoji indicator for an agent based on its cost tier."""
|
|
123
|
+
cost_tier = AGENT_COST_TIERS.get(agent_type, AGENT_COST_TIERS["_default"])
|
|
124
|
+
return COST_TIER_EMOJI.get(cost_tier, "⚪")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_model_emoji(model_name: str) -> str:
|
|
128
|
+
"""Get the colored emoji indicator for a model."""
|
|
129
|
+
return MODEL_FAMILY_EMOJI.get(model_name, "⚪")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def colorize_agent_spawn_message(
|
|
133
|
+
cost_emoji: str,
|
|
134
|
+
agent_type: str,
|
|
135
|
+
display_model: str,
|
|
136
|
+
description: str,
|
|
137
|
+
task_id: str,
|
|
138
|
+
) -> str:
|
|
139
|
+
"""
|
|
140
|
+
Create a colorized agent spawn message with ANSI color codes.
|
|
141
|
+
|
|
142
|
+
Format:
|
|
143
|
+
🟢 explore:gemini-3-flash('Find auth...') ⏳
|
|
144
|
+
task_id=agent_abc123
|
|
145
|
+
|
|
146
|
+
With colors:
|
|
147
|
+
🟢 {CYAN}explore{RESET}:{YELLOW}gemini-3-flash{RESET}('{BOLD}Find auth...{RESET}') ⏳
|
|
148
|
+
task_id={BRIGHT_BLACK}agent_abc123{RESET}
|
|
149
|
+
"""
|
|
150
|
+
short_desc = (description or "")[:50].strip()
|
|
151
|
+
|
|
152
|
+
# Build colorized message
|
|
153
|
+
colored_message = (
|
|
154
|
+
f"{cost_emoji} "
|
|
155
|
+
f"{Colors.CYAN}{agent_type}{Colors.RESET}:"
|
|
156
|
+
f"{Colors.YELLOW}{display_model}{Colors.RESET}"
|
|
157
|
+
f"('{Colors.BOLD}{short_desc}{Colors.RESET}') "
|
|
158
|
+
f"{Colors.BRIGHT_GREEN}⏳{Colors.RESET}\n"
|
|
159
|
+
f"task_id={Colors.BRIGHT_BLACK}{task_id}{Colors.RESET}"
|
|
160
|
+
)
|
|
161
|
+
return colored_message
|
|
162
|
+
|
|
57
163
|
|
|
58
164
|
@dataclass
|
|
59
165
|
class AgentTask:
|
|
@@ -472,45 +578,45 @@ class AgentManager:
|
|
|
472
578
|
description = task.get("description", "")
|
|
473
579
|
agent_type = task.get("agent_type", "unknown")
|
|
474
580
|
|
|
581
|
+
# Get cost-tier emoji for visual differentiation
|
|
582
|
+
cost_emoji = get_agent_emoji(agent_type)
|
|
583
|
+
display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
|
|
584
|
+
|
|
475
585
|
if status == "completed":
|
|
476
586
|
result = task.get("result", "(no output)")
|
|
477
|
-
return f"""✅ Agent Task Completed
|
|
587
|
+
return f"""{cost_emoji} {Colors.BRIGHT_GREEN}✅ Agent Task Completed{Colors.RESET}
|
|
478
588
|
|
|
479
|
-
**Task ID**: {task_id}
|
|
480
|
-
**Agent**: {agent_type}
|
|
481
|
-
**Description**: {description}
|
|
589
|
+
**Task ID**: {Colors.BRIGHT_BLACK}{task_id}{Colors.RESET}
|
|
590
|
+
**Agent**: {Colors.CYAN}{agent_type}{Colors.RESET}:{Colors.YELLOW}{display_model}{Colors.RESET}('{Colors.BOLD}{description}{Colors.RESET}')
|
|
482
591
|
|
|
483
592
|
**Result**:
|
|
484
593
|
{result}"""
|
|
485
594
|
|
|
486
595
|
elif status == "failed":
|
|
487
596
|
error = task.get("error", "(no error details)")
|
|
488
|
-
return f"""❌ Agent Task Failed
|
|
597
|
+
return f"""{cost_emoji} {Colors.BRIGHT_RED}❌ Agent Task Failed{Colors.RESET}
|
|
489
598
|
|
|
490
|
-
**Task ID**: {task_id}
|
|
491
|
-
**Agent**: {agent_type}
|
|
492
|
-
**Description**: {description}
|
|
599
|
+
**Task ID**: {Colors.BRIGHT_BLACK}{task_id}{Colors.RESET}
|
|
600
|
+
**Agent**: {Colors.CYAN}{agent_type}{Colors.RESET}:{Colors.YELLOW}{display_model}{Colors.RESET}('{Colors.BOLD}{description}{Colors.RESET}')
|
|
493
601
|
|
|
494
602
|
**Error**:
|
|
495
603
|
{error}"""
|
|
496
604
|
|
|
497
605
|
elif status == "cancelled":
|
|
498
|
-
return f"""⚠️ Agent Task Cancelled
|
|
606
|
+
return f"""{cost_emoji} {Colors.BRIGHT_YELLOW}⚠️ Agent Task Cancelled{Colors.RESET}
|
|
499
607
|
|
|
500
|
-
**Task ID**: {task_id}
|
|
501
|
-
**Agent**: {agent_type}
|
|
502
|
-
**Description**: {description}"""
|
|
608
|
+
**Task ID**: {Colors.BRIGHT_BLACK}{task_id}{Colors.RESET}
|
|
609
|
+
**Agent**: {Colors.CYAN}{agent_type}{Colors.RESET}:{Colors.YELLOW}{display_model}{Colors.RESET}('{Colors.BOLD}{description}{Colors.RESET}')"""
|
|
503
610
|
|
|
504
611
|
else: # pending or running
|
|
505
612
|
pid = task.get("pid", "N/A")
|
|
506
613
|
started = task.get("started_at", "N/A")
|
|
507
|
-
return f"""⏳ Agent Task Running
|
|
614
|
+
return f"""{cost_emoji} {Colors.BRIGHT_YELLOW}⏳ Agent Task Running{Colors.RESET}
|
|
508
615
|
|
|
509
|
-
**Task ID**: {task_id}
|
|
510
|
-
**Agent**: {agent_type}
|
|
511
|
-
**
|
|
512
|
-
**
|
|
513
|
-
**Started**: {started}
|
|
616
|
+
**Task ID**: {Colors.BRIGHT_BLACK}{task_id}{Colors.RESET}
|
|
617
|
+
**Agent**: {Colors.CYAN}{agent_type}{Colors.RESET}:{Colors.YELLOW}{display_model}{Colors.RESET}('{Colors.BOLD}{description}{Colors.RESET}')
|
|
618
|
+
**PID**: {Colors.DIM}{pid}{Colors.RESET}
|
|
619
|
+
**Started**: {Colors.DIM}{started}{Colors.RESET}
|
|
514
620
|
|
|
515
621
|
Use `agent_output` with block=true to wait for completion."""
|
|
516
622
|
|
|
@@ -583,11 +689,14 @@ Use `agent_output` with block=true to wait for completion."""
|
|
|
583
689
|
"cancelled": "⚠️",
|
|
584
690
|
}.get(status, "❓")
|
|
585
691
|
|
|
586
|
-
|
|
692
|
+
# Get cost-tier emoji for visual differentiation
|
|
693
|
+
cost_emoji = get_agent_emoji(agent_type)
|
|
694
|
+
display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
|
|
695
|
+
|
|
696
|
+
result = f"""{cost_emoji} {status_emoji} **Agent Progress**
|
|
587
697
|
|
|
588
698
|
**Task ID**: {task_id}
|
|
589
|
-
**Agent**: {agent_type}
|
|
590
|
-
**Description**: {description}
|
|
699
|
+
**Agent**: {agent_type}:{display_model}('{description}')
|
|
591
700
|
**Status**: {status}
|
|
592
701
|
"""
|
|
593
702
|
|
|
@@ -631,6 +740,7 @@ async def agent_spawn(
|
|
|
631
740
|
model: str = "gemini-3-flash",
|
|
632
741
|
thinking_budget: int = 0,
|
|
633
742
|
timeout: int = 300,
|
|
743
|
+
blocking: bool = False,
|
|
634
744
|
) -> str:
|
|
635
745
|
"""
|
|
636
746
|
Spawn a background agent.
|
|
@@ -642,9 +752,10 @@ async def agent_spawn(
|
|
|
642
752
|
model: Model to use (gemini-3-flash, gemini-2.0-flash, claude)
|
|
643
753
|
thinking_budget: Reserved reasoning tokens
|
|
644
754
|
timeout: Execution timeout in seconds
|
|
755
|
+
blocking: If True, wait for completion and return result directly (use for delphi)
|
|
645
756
|
|
|
646
757
|
Returns:
|
|
647
|
-
Task ID and instructions
|
|
758
|
+
Task ID and instructions, or full result if blocking=True
|
|
648
759
|
"""
|
|
649
760
|
manager = get_manager()
|
|
650
761
|
|
|
@@ -762,6 +873,61 @@ CONSTRAINTS:
|
|
|
762
873
|
- Every task must have a clear agent assignment
|
|
763
874
|
- Parallel phases must be truly independent
|
|
764
875
|
- Include ready-to-use agent_spawn commands""",
|
|
876
|
+
"research-lead": """You coordinate research tasks by spawning explore and dewey agents in parallel.
|
|
877
|
+
|
|
878
|
+
## Your Role
|
|
879
|
+
1. Receive research objective from Stravinsky
|
|
880
|
+
2. Decompose into parallel search tasks
|
|
881
|
+
3. Spawn explore/dewey agents for each task
|
|
882
|
+
4. Collect and SYNTHESIZE results
|
|
883
|
+
5. Return structured findings (not raw outputs)
|
|
884
|
+
|
|
885
|
+
## Output Format
|
|
886
|
+
Always return a Research Brief:
|
|
887
|
+
```json
|
|
888
|
+
{
|
|
889
|
+
"objective": "Original research goal",
|
|
890
|
+
"findings": [
|
|
891
|
+
{"source": "agent_id", "summary": "Key finding", "confidence": "high/medium/low"},
|
|
892
|
+
...
|
|
893
|
+
],
|
|
894
|
+
"synthesis": "Combined analysis of all findings",
|
|
895
|
+
"gaps": ["Information we couldn't find"],
|
|
896
|
+
"recommendations": ["Suggested next steps"]
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
MODEL ROUTING:
|
|
901
|
+
Use invoke_gemini with model="gemini-3-flash" for ALL synthesis work.
|
|
902
|
+
""",
|
|
903
|
+
"implementation-lead": """You coordinate implementation based on research findings.
|
|
904
|
+
|
|
905
|
+
## Your Role
|
|
906
|
+
1. Receive Research Brief from Stravinsky
|
|
907
|
+
2. Create implementation plan
|
|
908
|
+
3. Delegate to specialists:
|
|
909
|
+
- frontend: UI/visual work
|
|
910
|
+
- debugger: Fix failures
|
|
911
|
+
- code-reviewer: Quality checks
|
|
912
|
+
4. Verify with lsp_diagnostics
|
|
913
|
+
5. Return Implementation Report
|
|
914
|
+
|
|
915
|
+
## Output Format
|
|
916
|
+
```json
|
|
917
|
+
{
|
|
918
|
+
"objective": "What was implemented",
|
|
919
|
+
"files_changed": ["path/to/file.py"],
|
|
920
|
+
"tests_status": "pass/fail/skipped",
|
|
921
|
+
"diagnostics": "clean/warnings/errors",
|
|
922
|
+
"blockers": ["Issues preventing completion"]
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
## Escalation Rules
|
|
927
|
+
- After 2 failed attempts → spawn debugger
|
|
928
|
+
- After debugger fails → escalate to Stravinsky with context
|
|
929
|
+
- NEVER call delphi directly
|
|
930
|
+
""",
|
|
765
931
|
}
|
|
766
932
|
|
|
767
933
|
system_prompt = system_prompts.get(agent_type, None)
|
|
@@ -788,16 +954,25 @@ CONSTRAINTS:
|
|
|
788
954
|
timeout=timeout,
|
|
789
955
|
)
|
|
790
956
|
|
|
791
|
-
|
|
957
|
+
# Get display model and cost tier emoji for concise output
|
|
958
|
+
display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
|
|
959
|
+
cost_emoji = get_agent_emoji(agent_type)
|
|
960
|
+
short_desc = (description or prompt[:50]).strip()
|
|
792
961
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
962
|
+
# If blocking mode (recommended for delphi), wait for completion
|
|
963
|
+
if blocking:
|
|
964
|
+
result = manager.get_output(task_id, block=True, timeout=timeout)
|
|
965
|
+
blocking_msg = colorize_agent_spawn_message(
|
|
966
|
+
cost_emoji, agent_type, display_model, short_desc, task_id
|
|
967
|
+
)
|
|
968
|
+
return f"{blocking_msg} {Colors.BOLD}[BLOCKING]{Colors.RESET}\n\n{result}"
|
|
796
969
|
|
|
797
|
-
|
|
798
|
-
-
|
|
799
|
-
|
|
800
|
-
|
|
970
|
+
# Enhanced format with ANSI colors: cost_emoji agent:model('description') status_emoji
|
|
971
|
+
# 🟢 explore:gemini-3-flash('Find auth...') ⏳
|
|
972
|
+
# With colors: agent type in cyan, model in yellow, description bold
|
|
973
|
+
return colorize_agent_spawn_message(
|
|
974
|
+
cost_emoji, agent_type, display_model, short_desc, task_id
|
|
975
|
+
)
|
|
801
976
|
|
|
802
977
|
|
|
803
978
|
async def agent_output(task_id: str, block: bool = False) -> str:
|
|
@@ -887,7 +1062,7 @@ async def agent_list() -> str:
|
|
|
887
1062
|
if not tasks:
|
|
888
1063
|
return "No background agent tasks found."
|
|
889
1064
|
|
|
890
|
-
lines = [
|
|
1065
|
+
lines = []
|
|
891
1066
|
|
|
892
1067
|
for t in sorted(tasks, key=lambda x: x.get("created_at", ""), reverse=True):
|
|
893
1068
|
status_emoji = {
|
|
@@ -898,8 +1073,21 @@ async def agent_list() -> str:
|
|
|
898
1073
|
"cancelled": "⚠️",
|
|
899
1074
|
}.get(t["status"], "❓")
|
|
900
1075
|
|
|
1076
|
+
agent_type = t.get("agent_type", "unknown")
|
|
1077
|
+
display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
|
|
1078
|
+
cost_emoji = get_agent_emoji(agent_type)
|
|
901
1079
|
desc = t.get("description", t.get("prompt", "")[:40])
|
|
902
|
-
|
|
1080
|
+
task_id = t["id"]
|
|
1081
|
+
|
|
1082
|
+
# Concise format with colors: cost_emoji status agent:model('desc') id=xxx
|
|
1083
|
+
# Agent type in cyan, model in yellow, task_id in dim
|
|
1084
|
+
lines.append(
|
|
1085
|
+
f"{cost_emoji} {status_emoji} "
|
|
1086
|
+
f"{Colors.CYAN}{agent_type}{Colors.RESET}:"
|
|
1087
|
+
f"{Colors.YELLOW}{display_model}{Colors.RESET}"
|
|
1088
|
+
f"('{Colors.BOLD}{desc}{Colors.RESET}') "
|
|
1089
|
+
f"id={Colors.BRIGHT_BLACK}{task_id}{Colors.RESET}"
|
|
1090
|
+
)
|
|
903
1091
|
|
|
904
1092
|
return "\n".join(lines)
|
|
905
1093
|
|
mcp_bridge/tools/code_search.py
CHANGED
|
@@ -15,17 +15,21 @@ from pathlib import Path
|
|
|
15
15
|
async def lsp_diagnostics(file_path: str, severity: str = "all") -> str:
|
|
16
16
|
"""
|
|
17
17
|
Get diagnostics (errors, warnings) for a file using language server.
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
For TypeScript/JavaScript, uses `tsc` or `biome`.
|
|
20
20
|
For Python, uses `pyright` or `ruff`.
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
Args:
|
|
23
23
|
file_path: Path to the file to analyze
|
|
24
24
|
severity: Filter by severity (error, warning, information, hint, all)
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
Returns:
|
|
27
27
|
Formatted diagnostics output.
|
|
28
28
|
"""
|
|
29
|
+
# USER-VISIBLE NOTIFICATION
|
|
30
|
+
import sys
|
|
31
|
+
print(f"🩺 LSP-DIAG: file={file_path} severity={severity}", file=sys.stderr)
|
|
32
|
+
|
|
29
33
|
path = Path(file_path)
|
|
30
34
|
if not path.exists():
|
|
31
35
|
return f"Error: File not found: {file_path}"
|
|
@@ -49,7 +53,7 @@ async def lsp_diagnostics(file_path: str, severity: str = "all") -> str:
|
|
|
49
53
|
elif suffix == ".py":
|
|
50
54
|
# Use ruff for Python diagnostics
|
|
51
55
|
result = subprocess.run(
|
|
52
|
-
["ruff", "check", str(path), "--output-format=
|
|
56
|
+
["ruff", "check", str(path), "--output-format=concise"],
|
|
53
57
|
capture_output=True,
|
|
54
58
|
text=True,
|
|
55
59
|
timeout=30,
|
|
@@ -70,21 +74,88 @@ async def lsp_diagnostics(file_path: str, severity: str = "all") -> str:
|
|
|
70
74
|
return f"Error: {str(e)}"
|
|
71
75
|
|
|
72
76
|
|
|
77
|
+
async def check_ai_comment_patterns(file_path: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Detect AI-generated or placeholder comment patterns that indicate incomplete work.
|
|
80
|
+
|
|
81
|
+
Patterns detected:
|
|
82
|
+
- # TODO: implement, # FIXME, # placeholder
|
|
83
|
+
- // TODO, // FIXME, // placeholder
|
|
84
|
+
- AI-style verbose comments: "This function handles...", "This method is responsible for..."
|
|
85
|
+
- Placeholder phrases: "implement this", "add logic here", "your code here"
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
file_path: Path to the file to check
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of detected AI-style patterns with line numbers, or "No AI patterns detected"
|
|
92
|
+
"""
|
|
93
|
+
# USER-VISIBLE NOTIFICATION
|
|
94
|
+
import sys
|
|
95
|
+
print(f"🤖 AI-CHECK: {file_path}", file=sys.stderr)
|
|
96
|
+
|
|
97
|
+
path = Path(file_path)
|
|
98
|
+
if not path.exists():
|
|
99
|
+
return f"Error: File not found: {file_path}"
|
|
100
|
+
|
|
101
|
+
# Patterns that indicate AI-generated or placeholder code
|
|
102
|
+
ai_patterns = [
|
|
103
|
+
# Placeholder comments
|
|
104
|
+
r"#\s*(TODO|FIXME|XXX|HACK):\s*(implement|add|placeholder|your code)",
|
|
105
|
+
r"//\s*(TODO|FIXME|XXX|HACK):\s*(implement|add|placeholder|your code)",
|
|
106
|
+
# AI-style verbose descriptions
|
|
107
|
+
r"#\s*This (function|method|class) (handles|is responsible for|manages|processes)",
|
|
108
|
+
r"//\s*This (function|method|class) (handles|is responsible for|manages|processes)",
|
|
109
|
+
r'"""This (function|method|class) (handles|is responsible for|manages|processes)',
|
|
110
|
+
# Placeholder implementations
|
|
111
|
+
r"pass\s*#\s*(TODO|implement|placeholder)",
|
|
112
|
+
r"raise NotImplementedError.*implement",
|
|
113
|
+
# Common AI filler phrases
|
|
114
|
+
r"#.*\b(as needed|as required|as appropriate|if necessary)\b",
|
|
115
|
+
r"//.*\b(as needed|as required|as appropriate|if necessary)\b",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
import re
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
content = path.read_text()
|
|
122
|
+
lines = content.split("\n")
|
|
123
|
+
findings = []
|
|
124
|
+
|
|
125
|
+
for i, line in enumerate(lines, 1):
|
|
126
|
+
for pattern in ai_patterns:
|
|
127
|
+
if re.search(pattern, line, re.IGNORECASE):
|
|
128
|
+
findings.append(f" Line {i}: {line.strip()[:80]}")
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
if findings:
|
|
132
|
+
return f"AI/Placeholder patterns detected in {file_path}:\n" + "\n".join(findings)
|
|
133
|
+
return "No AI patterns detected"
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return f"Error reading file: {str(e)}"
|
|
137
|
+
|
|
138
|
+
|
|
73
139
|
async def ast_grep_search(pattern: str, directory: str = ".", language: str = "") -> str:
|
|
74
140
|
"""
|
|
75
141
|
Search codebase using ast-grep for structural patterns.
|
|
76
|
-
|
|
142
|
+
|
|
77
143
|
ast-grep uses AST-aware pattern matching, finding code by structure
|
|
78
144
|
rather than just text. More precise than regex for code search.
|
|
79
|
-
|
|
145
|
+
|
|
80
146
|
Args:
|
|
81
147
|
pattern: ast-grep pattern to search for
|
|
82
148
|
directory: Directory to search in
|
|
83
149
|
language: Filter by language (typescript, python, rust, etc.)
|
|
84
|
-
|
|
150
|
+
|
|
85
151
|
Returns:
|
|
86
152
|
Matched code locations and snippets.
|
|
87
153
|
"""
|
|
154
|
+
# USER-VISIBLE NOTIFICATION
|
|
155
|
+
import sys
|
|
156
|
+
lang_info = f" lang={language}" if language else ""
|
|
157
|
+
print(f"🔍 AST-GREP: pattern='{pattern[:50]}...'{lang_info}", file=sys.stderr)
|
|
158
|
+
|
|
88
159
|
try:
|
|
89
160
|
cmd = ["sg", "run", "-p", pattern, directory]
|
|
90
161
|
if language:
|
|
@@ -129,15 +200,20 @@ async def ast_grep_search(pattern: str, directory: str = ".", language: str = ""
|
|
|
129
200
|
async def grep_search(pattern: str, directory: str = ".", file_pattern: str = "") -> str:
|
|
130
201
|
"""
|
|
131
202
|
Fast text search using ripgrep.
|
|
132
|
-
|
|
203
|
+
|
|
133
204
|
Args:
|
|
134
205
|
pattern: Search pattern (supports regex)
|
|
135
206
|
directory: Directory to search in
|
|
136
207
|
file_pattern: Glob pattern to filter files (e.g., "*.py", "*.ts")
|
|
137
|
-
|
|
208
|
+
|
|
138
209
|
Returns:
|
|
139
210
|
Matched lines with file paths and line numbers.
|
|
140
211
|
"""
|
|
212
|
+
# USER-VISIBLE NOTIFICATION
|
|
213
|
+
import sys
|
|
214
|
+
glob_info = f" glob={file_pattern}" if file_pattern else ""
|
|
215
|
+
print(f"🔎 GREP: pattern='{pattern[:50]}'{glob_info} dir={directory}", file=sys.stderr)
|
|
216
|
+
|
|
141
217
|
try:
|
|
142
218
|
cmd = ["rg", "--line-number", "--max-count=50", pattern, directory]
|
|
143
219
|
if file_pattern:
|
|
@@ -173,14 +249,18 @@ async def grep_search(pattern: str, directory: str = ".", file_pattern: str = ""
|
|
|
173
249
|
async def glob_files(pattern: str, directory: str = ".") -> str:
|
|
174
250
|
"""
|
|
175
251
|
Find files matching a glob pattern.
|
|
176
|
-
|
|
252
|
+
|
|
177
253
|
Args:
|
|
178
254
|
pattern: Glob pattern (e.g., "**/*.py", "src/**/*.ts")
|
|
179
255
|
directory: Base directory for search
|
|
180
|
-
|
|
256
|
+
|
|
181
257
|
Returns:
|
|
182
258
|
List of matching file paths.
|
|
183
259
|
"""
|
|
260
|
+
# USER-VISIBLE NOTIFICATION
|
|
261
|
+
import sys
|
|
262
|
+
print(f"📁 GLOB: pattern='{pattern}' dir={directory}", file=sys.stderr)
|
|
263
|
+
|
|
184
264
|
try:
|
|
185
265
|
cmd = ["fd", "--type", "f", "--glob", pattern, directory]
|
|
186
266
|
|
|
@@ -234,6 +314,12 @@ async def ast_grep_replace(
|
|
|
234
314
|
Returns:
|
|
235
315
|
Preview of changes or confirmation of applied changes.
|
|
236
316
|
"""
|
|
317
|
+
# USER-VISIBLE NOTIFICATION
|
|
318
|
+
import sys
|
|
319
|
+
mode = "dry-run" if dry_run else "APPLY"
|
|
320
|
+
lang_info = f" lang={language}" if language else ""
|
|
321
|
+
print(f"🔄 AST-REPLACE: '{pattern[:30]}' → '{replacement[:30]}'{lang_info} [{mode}]", file=sys.stderr)
|
|
322
|
+
|
|
237
323
|
try:
|
|
238
324
|
# Build command
|
|
239
325
|
cmd = ["sg", "run", "-p", pattern, "-r", replacement, directory]
|
mcp_bridge/tools/lsp/__init__.py
CHANGED
|
@@ -13,8 +13,11 @@ from .tools import (
|
|
|
13
13
|
lsp_prepare_rename,
|
|
14
14
|
lsp_rename,
|
|
15
15
|
lsp_code_actions,
|
|
16
|
+
lsp_code_action_resolve,
|
|
17
|
+
lsp_extract_refactor,
|
|
16
18
|
lsp_servers,
|
|
17
19
|
)
|
|
20
|
+
from .manager import LSPManager, get_lsp_manager
|
|
18
21
|
|
|
19
22
|
__all__ = [
|
|
20
23
|
"lsp_hover",
|
|
@@ -25,5 +28,9 @@ __all__ = [
|
|
|
25
28
|
"lsp_prepare_rename",
|
|
26
29
|
"lsp_rename",
|
|
27
30
|
"lsp_code_actions",
|
|
31
|
+
"lsp_code_action_resolve",
|
|
32
|
+
"lsp_extract_refactor",
|
|
28
33
|
"lsp_servers",
|
|
34
|
+
"LSPManager",
|
|
35
|
+
"get_lsp_manager",
|
|
29
36
|
]
|