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
@@ -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
- **Description**: {description}
512
- **PID**: {pid}
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
- result = f"""{status_emoji} **Agent Progress**
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
- return f"""🚀 Background agent spawned successfully.
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
- **Task ID**: {task_id}
794
- **Agent Type**: {agent_type}
795
- **Description**: {description or prompt[:50]}
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
- The agent is now running. Use:
798
- - `agent_progress(task_id="{task_id}")` to monitor real-time progress
799
- - `agent_output(task_id="{task_id}")` to get final result
800
- - `agent_cancel(task_id="{task_id}")` to stop the agent"""
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 = ["**Background Agent Tasks**", ""]
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
- lines.append(f"- {status_emoji} [{t['id']}] {t['agent_type']}: {desc}")
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
 
@@ -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=text"],
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]
@@ -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
  ]