stravinsky 0.2.67__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.

@@ -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,6 +50,8 @@ 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
@@ -60,12 +64,102 @@ AGENT_DISPLAY_MODELS = {
60
64
  "dewey": "gemini-3-flash",
61
65
  "document_writer": "gemini-3-flash",
62
66
  "multimodal": "gemini-3-flash",
67
+ "research-lead": "gemini-3-flash",
68
+ "implementation-lead": "haiku",
63
69
  "frontend": "gemini-3-pro-high",
64
70
  "delphi": "gpt-5.2",
65
71
  "planner": "opus-4.5",
66
72
  "_default": "sonnet-4.5",
67
73
  }
68
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
+
69
163
 
70
164
  @dataclass
71
165
  class AgentTask:
@@ -484,45 +578,45 @@ class AgentManager:
484
578
  description = task.get("description", "")
485
579
  agent_type = task.get("agent_type", "unknown")
486
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
+
487
585
  if status == "completed":
488
586
  result = task.get("result", "(no output)")
489
- return f"""✅ Agent Task Completed
587
+ return f"""{cost_emoji} {Colors.BRIGHT_GREEN}✅ Agent Task Completed{Colors.RESET}
490
588
 
491
- **Task ID**: {task_id}
492
- **Agent**: {agent_type}
493
- **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}')
494
591
 
495
592
  **Result**:
496
593
  {result}"""
497
594
 
498
595
  elif status == "failed":
499
596
  error = task.get("error", "(no error details)")
500
- return f"""❌ Agent Task Failed
597
+ return f"""{cost_emoji} {Colors.BRIGHT_RED}❌ Agent Task Failed{Colors.RESET}
501
598
 
502
- **Task ID**: {task_id}
503
- **Agent**: {agent_type}
504
- **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}')
505
601
 
506
602
  **Error**:
507
603
  {error}"""
508
604
 
509
605
  elif status == "cancelled":
510
- return f"""⚠️ Agent Task Cancelled
606
+ return f"""{cost_emoji} {Colors.BRIGHT_YELLOW}⚠️ Agent Task Cancelled{Colors.RESET}
511
607
 
512
- **Task ID**: {task_id}
513
- **Agent**: {agent_type}
514
- **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}')"""
515
610
 
516
611
  else: # pending or running
517
612
  pid = task.get("pid", "N/A")
518
613
  started = task.get("started_at", "N/A")
519
- return f"""⏳ Agent Task Running
614
+ return f"""{cost_emoji} {Colors.BRIGHT_YELLOW}⏳ Agent Task Running{Colors.RESET}
520
615
 
521
- **Task ID**: {task_id}
522
- **Agent**: {agent_type}
523
- **Description**: {description}
524
- **PID**: {pid}
525
- **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}
526
620
 
527
621
  Use `agent_output` with block=true to wait for completion."""
528
622
 
@@ -595,11 +689,14 @@ Use `agent_output` with block=true to wait for completion."""
595
689
  "cancelled": "⚠️",
596
690
  }.get(status, "❓")
597
691
 
598
- 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**
599
697
 
600
698
  **Task ID**: {task_id}
601
- **Agent**: {agent_type}
602
- **Description**: {description}
699
+ **Agent**: {agent_type}:{display_model}('{description}')
603
700
  **Status**: {status}
604
701
  """
605
702
 
@@ -776,6 +873,61 @@ CONSTRAINTS:
776
873
  - Every task must have a clear agent assignment
777
874
  - Parallel phases must be truly independent
778
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
+ """,
779
931
  }
780
932
 
781
933
  system_prompt = system_prompts.get(agent_type, None)
@@ -802,18 +954,25 @@ CONSTRAINTS:
802
954
  timeout=timeout,
803
955
  )
804
956
 
805
- # Get display model for concise output
957
+ # Get display model and cost tier emoji for concise output
806
958
  display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
959
+ cost_emoji = get_agent_emoji(agent_type)
807
960
  short_desc = (description or prompt[:50]).strip()
808
961
 
809
962
  # If blocking mode (recommended for delphi), wait for completion
810
963
  if blocking:
811
964
  result = manager.get_output(task_id, block=True, timeout=timeout)
812
- return f"{agent_type}:{display_model}('{short_desc}') [BLOCKING]\n\n{result}"
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}"
813
969
 
814
- # Concise format: AgentType:model('description')
815
- return f"""{agent_type}:{display_model}('{short_desc}')
816
- task_id={task_id}"""
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
+ )
817
976
 
818
977
 
819
978
  async def agent_output(task_id: str, block: bool = False) -> str:
@@ -916,9 +1075,19 @@ async def agent_list() -> str:
916
1075
 
917
1076
  agent_type = t.get("agent_type", "unknown")
918
1077
  display_model = AGENT_DISPLAY_MODELS.get(agent_type, AGENT_DISPLAY_MODELS["_default"])
1078
+ cost_emoji = get_agent_emoji(agent_type)
919
1079
  desc = t.get("description", t.get("prompt", "")[:40])
920
- # Concise format: status agent:model('desc') id=xxx
921
- lines.append(f"{status_emoji} {agent_type}:{display_model}('{desc}') id={t['id']}")
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
+ )
922
1091
 
923
1092
  return "\n".join(lines)
924
1093
 
@@ -90,6 +90,10 @@ async def check_ai_comment_patterns(file_path: str) -> str:
90
90
  Returns:
91
91
  List of detected AI-style patterns with line numbers, or "No AI patterns detected"
92
92
  """
93
+ # USER-VISIBLE NOTIFICATION
94
+ import sys
95
+ print(f"🤖 AI-CHECK: {file_path}", file=sys.stderr)
96
+
93
97
  path = Path(file_path)
94
98
  if not path.exists():
95
99
  return f"Error: File not found: {file_path}"
@@ -245,14 +249,18 @@ async def grep_search(pattern: str, directory: str = ".", file_pattern: str = ""
245
249
  async def glob_files(pattern: str, directory: str = ".") -> str:
246
250
  """
247
251
  Find files matching a glob pattern.
248
-
252
+
249
253
  Args:
250
254
  pattern: Glob pattern (e.g., "**/*.py", "src/**/*.ts")
251
255
  directory: Base directory for search
252
-
256
+
253
257
  Returns:
254
258
  List of matching file paths.
255
259
  """
260
+ # USER-VISIBLE NOTIFICATION
261
+ import sys
262
+ print(f"📁 GLOB: pattern='{pattern}' dir={directory}", file=sys.stderr)
263
+
256
264
  try:
257
265
  cmd = ["fd", "--type", "f", "--glob", pattern, directory]
258
266
 
@@ -306,6 +314,12 @@ async def ast_grep_replace(
306
314
  Returns:
307
315
  Preview of changes or confirmation of applied changes.
308
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
+
309
323
  try:
310
324
  # Build command
311
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
  ]