stravinsky 0.2.67__py3-none-any.whl → 0.4.66__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/__init__.py +16 -6
- mcp_bridge/auth/cli.py +202 -11
- mcp_bridge/auth/oauth.py +1 -2
- mcp_bridge/auth/openai_oauth.py +4 -7
- mcp_bridge/auth/token_store.py +112 -11
- mcp_bridge/cli/__init__.py +1 -1
- mcp_bridge/cli/install_hooks.py +503 -107
- mcp_bridge/cli/session_report.py +0 -3
- mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/__init__.py +2 -2
- mcp_bridge/config/hook_config.py +247 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +317 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
- mcp_bridge/hooks/__init__.py +19 -4
- mcp_bridge/hooks/agent_reminder.py +4 -4
- mcp_bridge/hooks/auto_slash_command.py +5 -5
- mcp_bridge/hooks/budget_optimizer.py +2 -2
- mcp_bridge/hooks/claude_limits_hook.py +114 -0
- mcp_bridge/hooks/comment_checker.py +3 -4
- mcp_bridge/hooks/compaction.py +2 -2
- mcp_bridge/hooks/context.py +2 -1
- mcp_bridge/hooks/context_monitor.py +2 -2
- mcp_bridge/hooks/delegation_policy.py +85 -0
- mcp_bridge/hooks/directory_context.py +3 -3
- mcp_bridge/hooks/edit_recovery.py +3 -2
- mcp_bridge/hooks/edit_recovery_policy.py +49 -0
- mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
- mcp_bridge/hooks/events.py +160 -0
- mcp_bridge/hooks/git_noninteractive.py +4 -4
- mcp_bridge/hooks/keyword_detector.py +8 -10
- mcp_bridge/hooks/manager.py +43 -22
- mcp_bridge/hooks/notification_hook.py +13 -6
- mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
- mcp_bridge/hooks/parallel_enforcer.py +5 -5
- mcp_bridge/hooks/parallel_execution.py +22 -10
- mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
- mcp_bridge/hooks/pre_compact.py +8 -9
- mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
- mcp_bridge/hooks/preemptive_compaction.py +2 -3
- mcp_bridge/hooks/routing_notifications.py +80 -0
- mcp_bridge/hooks/rules_injector.py +11 -19
- mcp_bridge/hooks/session_idle.py +4 -4
- mcp_bridge/hooks/session_notifier.py +4 -4
- mcp_bridge/hooks/session_recovery.py +4 -5
- mcp_bridge/hooks/stravinsky_mode.py +1 -1
- mcp_bridge/hooks/subagent_stop.py +1 -3
- mcp_bridge/hooks/task_validator.py +2 -2
- mcp_bridge/hooks/tmux_manager.py +7 -8
- mcp_bridge/hooks/todo_delegation.py +4 -1
- mcp_bridge/hooks/todo_enforcer.py +180 -10
- mcp_bridge/hooks/tool_messaging.py +113 -10
- mcp_bridge/hooks/truncation_policy.py +37 -0
- mcp_bridge/hooks/truncator.py +1 -2
- mcp_bridge/metrics/cost_tracker.py +115 -0
- mcp_bridge/native_search.py +93 -0
- mcp_bridge/native_watcher.py +118 -0
- mcp_bridge/notifications.py +150 -0
- mcp_bridge/orchestrator/enums.py +11 -0
- mcp_bridge/orchestrator/router.py +165 -0
- mcp_bridge/orchestrator/state.py +32 -0
- mcp_bridge/orchestrator/visualization.py +14 -0
- mcp_bridge/orchestrator/wisdom.py +34 -0
- mcp_bridge/prompts/__init__.py +1 -8
- mcp_bridge/prompts/dewey.py +1 -1
- mcp_bridge/prompts/planner.py +2 -4
- mcp_bridge/prompts/stravinsky.py +53 -31
- mcp_bridge/proxy/__init__.py +0 -0
- mcp_bridge/proxy/client.py +70 -0
- mcp_bridge/proxy/model_server.py +157 -0
- mcp_bridge/routing/__init__.py +43 -0
- mcp_bridge/routing/config.py +250 -0
- mcp_bridge/routing/model_tiers.py +135 -0
- mcp_bridge/routing/provider_state.py +261 -0
- mcp_bridge/routing/task_classifier.py +190 -0
- mcp_bridge/server.py +542 -59
- mcp_bridge/server_tools.py +738 -6
- mcp_bridge/tools/__init__.py +40 -25
- mcp_bridge/tools/agent_manager.py +616 -697
- mcp_bridge/tools/background_tasks.py +13 -17
- mcp_bridge/tools/code_search.py +70 -53
- mcp_bridge/tools/continuous_loop.py +0 -1
- mcp_bridge/tools/dashboard.py +19 -0
- mcp_bridge/tools/find_code.py +296 -0
- mcp_bridge/tools/init.py +1 -0
- mcp_bridge/tools/list_directory.py +42 -0
- mcp_bridge/tools/lsp/__init__.py +12 -5
- mcp_bridge/tools/lsp/manager.py +471 -0
- mcp_bridge/tools/lsp/tools.py +723 -207
- mcp_bridge/tools/model_invoke.py +1195 -273
- mcp_bridge/tools/mux_client.py +75 -0
- mcp_bridge/tools/project_context.py +1 -2
- mcp_bridge/tools/query_classifier.py +406 -0
- mcp_bridge/tools/read_file.py +84 -0
- mcp_bridge/tools/replace.py +45 -0
- mcp_bridge/tools/run_shell_command.py +38 -0
- mcp_bridge/tools/search_enhancements.py +347 -0
- mcp_bridge/tools/semantic_search.py +3627 -0
- mcp_bridge/tools/session_manager.py +0 -2
- mcp_bridge/tools/skill_loader.py +0 -1
- mcp_bridge/tools/task_runner.py +5 -7
- mcp_bridge/tools/templates.py +3 -3
- mcp_bridge/tools/tool_search.py +331 -0
- mcp_bridge/tools/write_file.py +29 -0
- mcp_bridge/update_manager.py +585 -0
- mcp_bridge/update_manager_pypi.py +297 -0
- mcp_bridge/utils/cache.py +82 -0
- mcp_bridge/utils/process.py +71 -0
- mcp_bridge/utils/session_state.py +51 -0
- mcp_bridge/utils/truncation.py +76 -0
- stravinsky-0.4.66.dist-info/METADATA +517 -0
- stravinsky-0.4.66.dist-info/RECORD +198 -0
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
- stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
- stravinsky_claude_assets/agents/HOOKS.md +437 -0
- stravinsky_claude_assets/agents/code-reviewer.md +210 -0
- stravinsky_claude_assets/agents/comment_checker.md +580 -0
- stravinsky_claude_assets/agents/debugger.md +254 -0
- stravinsky_claude_assets/agents/delphi.md +495 -0
- stravinsky_claude_assets/agents/dewey.md +248 -0
- stravinsky_claude_assets/agents/explore.md +1198 -0
- stravinsky_claude_assets/agents/frontend.md +472 -0
- stravinsky_claude_assets/agents/implementation-lead.md +164 -0
- stravinsky_claude_assets/agents/momus.md +464 -0
- stravinsky_claude_assets/agents/research-lead.md +141 -0
- stravinsky_claude_assets/agents/stravinsky.md +730 -0
- stravinsky_claude_assets/commands/delphi.md +9 -0
- stravinsky_claude_assets/commands/dewey.md +54 -0
- stravinsky_claude_assets/commands/git-master.md +112 -0
- stravinsky_claude_assets/commands/index.md +49 -0
- stravinsky_claude_assets/commands/publish.md +86 -0
- stravinsky_claude_assets/commands/review.md +73 -0
- stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
- stravinsky_claude_assets/commands/str/agent_list.md +56 -0
- stravinsky_claude_assets/commands/str/agent_output.md +92 -0
- stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
- stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
- stravinsky_claude_assets/commands/str/cancel.md +51 -0
- stravinsky_claude_assets/commands/str/clean.md +97 -0
- stravinsky_claude_assets/commands/str/continue.md +38 -0
- stravinsky_claude_assets/commands/str/index.md +199 -0
- stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
- stravinsky_claude_assets/commands/str/search.md +205 -0
- stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
- stravinsky_claude_assets/commands/str/stats.md +71 -0
- stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
- stravinsky_claude_assets/commands/str/unwatch.md +42 -0
- stravinsky_claude_assets/commands/str/watch.md +45 -0
- stravinsky_claude_assets/commands/strav.md +53 -0
- stravinsky_claude_assets/commands/stravinsky.md +292 -0
- stravinsky_claude_assets/commands/verify.md +60 -0
- stravinsky_claude_assets/commands/version.md +5 -0
- stravinsky_claude_assets/hooks/README.md +248 -0
- stravinsky_claude_assets/hooks/comment_checker.py +193 -0
- stravinsky_claude_assets/hooks/context.py +38 -0
- stravinsky_claude_assets/hooks/context_monitor.py +153 -0
- stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
- stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
- stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
- stravinsky_claude_assets/hooks/notification_hook.py +103 -0
- stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
- stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
- stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
- stravinsky_claude_assets/hooks/pre_compact.py +123 -0
- stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
- stravinsky_claude_assets/hooks/session_recovery.py +263 -0
- stravinsky_claude_assets/hooks/stop_hook.py +89 -0
- stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
- stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
- stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
- stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
- stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
- stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
- stravinsky_claude_assets/hooks/truncator.py +23 -0
- stravinsky_claude_assets/rules/deployment_safety.md +51 -0
- stravinsky_claude_assets/rules/integration_wiring.md +89 -0
- stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
- stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
- stravinsky_claude_assets/settings.json +152 -0
- stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
- stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
- stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
- stravinsky_claude_assets/task_dependencies.json +34 -0
- stravinsky-0.2.67.dist-info/METADATA +0 -284
- stravinsky-0.2.67.dist-info/RECORD +0 -76
- {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import logging
|
|
3
|
+
import asyncio
|
|
4
|
+
from mcp_bridge.tools.semantic_search import semantic_search, get_store, EmbeddingProvider
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
def _check_index_exists(store) -> bool:
|
|
9
|
+
"""Check if index exists (duplicated from semantic_search.py helper)"""
|
|
10
|
+
try:
|
|
11
|
+
stats = store.get_stats()
|
|
12
|
+
return stats.get("chunks_indexed", 0) > 0
|
|
13
|
+
except Exception:
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
async def multi_query_search(
|
|
17
|
+
query: str,
|
|
18
|
+
project_path: str = ".",
|
|
19
|
+
n_results: int = 10,
|
|
20
|
+
num_expansions: int = 3,
|
|
21
|
+
language: str | None = None,
|
|
22
|
+
node_type: str | None = None,
|
|
23
|
+
provider: EmbeddingProvider = "ollama",
|
|
24
|
+
) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Search with LLM-expanded query variations for better recall.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
from mcp_bridge.auth.token_store import TokenStore
|
|
30
|
+
from mcp_bridge.tools.model_invoke import invoke_gemini
|
|
31
|
+
except ImportError:
|
|
32
|
+
return "Error: Dependencies for query expansion not available."
|
|
33
|
+
|
|
34
|
+
print(f"🔄 Expanding query: '{query}'...", file=sys.stderr)
|
|
35
|
+
|
|
36
|
+
# 1. Generate variations
|
|
37
|
+
token_store = TokenStore()
|
|
38
|
+
prompt = f"""You are a query expansion specialist.
|
|
39
|
+
Generate {num_expansions} different semantic variations of this search query:
|
|
40
|
+
"{query}"
|
|
41
|
+
|
|
42
|
+
Focus on synonyms, related concepts, and technical terminology.
|
|
43
|
+
Return ONLY the variations, one per line. No numbering, no preamble."""
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
variations_text = await invoke_gemini(
|
|
47
|
+
token_store=token_store,
|
|
48
|
+
prompt=prompt,
|
|
49
|
+
model="gemini-3-flash",
|
|
50
|
+
temperature=0.7,
|
|
51
|
+
agent_context={"agent_type": "explore", "description": "Expanding search query"}
|
|
52
|
+
)
|
|
53
|
+
variations = [v.strip() for v in variations_text.split("\n") if v.strip()]
|
|
54
|
+
# Remove any "Here are..." prefixes if model ignores instructions
|
|
55
|
+
variations = [v for v in variations if not v.lower().startswith("here")]
|
|
56
|
+
# Limit to num_expansions
|
|
57
|
+
variations = variations[:num_expansions]
|
|
58
|
+
print(f"✅ Generated {len(variations)} variations", file=sys.stderr)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.warning(f"Query expansion failed: {e}")
|
|
61
|
+
variations = []
|
|
62
|
+
|
|
63
|
+
queries = [query] + variations
|
|
64
|
+
|
|
65
|
+
# 2. Run searches in parallel
|
|
66
|
+
store = get_store(project_path, provider)
|
|
67
|
+
|
|
68
|
+
if not _check_index_exists(store):
|
|
69
|
+
return "Index required. Run semantic_search first to trigger creation."
|
|
70
|
+
|
|
71
|
+
# Use store.search directly to get raw results
|
|
72
|
+
tasks = [
|
|
73
|
+
store.search(
|
|
74
|
+
q, n_results, language, node_type, is_async=None, decorator=None, base_class=None
|
|
75
|
+
) for q in queries
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
results_lists = await asyncio.gather(*tasks)
|
|
79
|
+
|
|
80
|
+
# 3. Reciprocal Rank Fusion (RRF)
|
|
81
|
+
# RRF score = 1 / (k + rank)
|
|
82
|
+
k = 60
|
|
83
|
+
rrf_scores = {}
|
|
84
|
+
|
|
85
|
+
for results in results_lists:
|
|
86
|
+
if not results or isinstance(results, dict) and "error" in results:
|
|
87
|
+
# store.search might return dict with error or list of dicts
|
|
88
|
+
if isinstance(results, list) and len(results) > 0 and "error" in results[0]:
|
|
89
|
+
continue
|
|
90
|
+
if isinstance(results, dict) and "error" in results:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if not isinstance(results, list):
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
for rank, item in enumerate(results):
|
|
97
|
+
# Create a unique key for the code chunk
|
|
98
|
+
key = f"{item['file']}:{item['lines']}"
|
|
99
|
+
|
|
100
|
+
if key not in rrf_scores:
|
|
101
|
+
rrf_scores[key] = {
|
|
102
|
+
"item": item,
|
|
103
|
+
"score": 0.0,
|
|
104
|
+
"matched_queries": set()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
rrf_scores[key]["score"] += 1.0 / (k + rank + 1)
|
|
108
|
+
|
|
109
|
+
# Sort by score
|
|
110
|
+
sorted_items = sorted(rrf_scores.values(), key=lambda x: x["score"], reverse=True)
|
|
111
|
+
top_results = sorted_items[:n_results]
|
|
112
|
+
|
|
113
|
+
if not top_results:
|
|
114
|
+
return "No results found"
|
|
115
|
+
|
|
116
|
+
lines = [f"Found {len(top_results)} results for '{query}' (expanded to {len(queries)} queries)\n"]
|
|
117
|
+
for i, entry in enumerate(top_results, 1):
|
|
118
|
+
r = entry["item"]
|
|
119
|
+
lines.append(f"{i}. {r['file']}:{r['lines']} (score: {entry['score']:.4f})")
|
|
120
|
+
lines.append(f"`{{r['language']}}`")
|
|
121
|
+
lines.append(r["code_preview"])
|
|
122
|
+
lines.append("`\n`")
|
|
123
|
+
|
|
124
|
+
return "\n".join(lines)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def decomposed_search(
|
|
128
|
+
query: str,
|
|
129
|
+
project_path: str = ".",
|
|
130
|
+
n_results: int = 10,
|
|
131
|
+
language: str | None = None,
|
|
132
|
+
node_type: str | None = None,
|
|
133
|
+
provider: EmbeddingProvider = "ollama",
|
|
134
|
+
) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Search by decomposing complex queries into focused sub-questions.
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
from mcp_bridge.auth.token_store import TokenStore
|
|
140
|
+
from mcp_bridge.tools.model_invoke import invoke_gemini
|
|
141
|
+
except ImportError:
|
|
142
|
+
return "Error: Dependencies for query decomposition not available."
|
|
143
|
+
|
|
144
|
+
print(f"🔄 Decomposing query: '{query}'...", file=sys.stderr)
|
|
145
|
+
|
|
146
|
+
token_store = TokenStore()
|
|
147
|
+
prompt = f"""You are a query decomposition specialist.
|
|
148
|
+
Break this complex code search query into 2-4 focused, atomic sub-queries:
|
|
149
|
+
"{query}"
|
|
150
|
+
|
|
151
|
+
Each sub-query should look for a specific part of the system or concept.
|
|
152
|
+
Return ONLY the sub-queries, one per line. No numbering."""
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
subqueries_text = await invoke_gemini(
|
|
156
|
+
token_store=token_store,
|
|
157
|
+
prompt=prompt,
|
|
158
|
+
model="gemini-3-flash",
|
|
159
|
+
temperature=0.7,
|
|
160
|
+
agent_context={"agent_type": "explore", "description": "Decomposing search query"}
|
|
161
|
+
)
|
|
162
|
+
subqueries = [q.strip() for q in subqueries_text.split("\n") if q.strip()]
|
|
163
|
+
subqueries = [q for q in subqueries if not q.lower().startswith("here")]
|
|
164
|
+
print(f"✅ Decomposed into {len(subqueries)} sub-queries", file=sys.stderr)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.warning(f"Decomposition failed: {e}")
|
|
167
|
+
return await semantic_search(query, project_path, n_results, language, node_type, provider=provider)
|
|
168
|
+
|
|
169
|
+
if not subqueries:
|
|
170
|
+
return await semantic_search(query, project_path, n_results, language, node_type, provider=provider)
|
|
171
|
+
|
|
172
|
+
store = get_store(project_path, provider)
|
|
173
|
+
if not _check_index_exists(store):
|
|
174
|
+
return "Index required. Run semantic_search first to trigger creation."
|
|
175
|
+
|
|
176
|
+
tasks = [
|
|
177
|
+
store.search(
|
|
178
|
+
q, n_results, language, node_type, is_async=None, decorator=None, base_class=None
|
|
179
|
+
) for q in subqueries
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
results_lists = await asyncio.gather(*tasks)
|
|
183
|
+
|
|
184
|
+
lines = [f"Decomposed '{query}' into {len(subqueries)} parts:\n"]
|
|
185
|
+
|
|
186
|
+
for q, results in zip(subqueries, results_lists):
|
|
187
|
+
lines.append(f"### Sub-query: {q}")
|
|
188
|
+
if not results:
|
|
189
|
+
lines.append("No results found.\n")
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
if isinstance(results, dict) and "error" in results:
|
|
193
|
+
lines.append(f"Error: {results['error']}\n")
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
if isinstance(results, list) and len(results) > 0 and "error" in results[0]:
|
|
197
|
+
lines.append(f"Error: {results[0]['error']}\n")
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
# Just show top 3-5 per subquery to save tokens
|
|
201
|
+
for i, r in enumerate(results[:5], 1):
|
|
202
|
+
lines.append(f"{i}. {r['file']}:{r['lines']} (relevance: {r['relevance']})")
|
|
203
|
+
lines.append(f"`{{r['language']}}`")
|
|
204
|
+
lines.append(r["code_preview"])
|
|
205
|
+
lines.append("`\n`")
|
|
206
|
+
|
|
207
|
+
return "\n".join(lines)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def enhanced_search(
|
|
211
|
+
query: str,
|
|
212
|
+
project_path: str = ".",
|
|
213
|
+
n_results: int = 10,
|
|
214
|
+
mode: str = "auto",
|
|
215
|
+
language: str | None = None,
|
|
216
|
+
node_type: str | None = None,
|
|
217
|
+
provider: EmbeddingProvider = "ollama",
|
|
218
|
+
) -> str:
|
|
219
|
+
"""
|
|
220
|
+
Unified enhanced search.
|
|
221
|
+
mode: 'auto', 'expand', 'decompose', 'both'
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
if mode == "expand":
|
|
225
|
+
return await multi_query_search(query, project_path, n_results, 3, language, node_type, provider)
|
|
226
|
+
elif mode == "decompose":
|
|
227
|
+
return await decomposed_search(query, project_path, n_results, language, node_type, provider)
|
|
228
|
+
elif mode == "auto":
|
|
229
|
+
# Simple heuristic: if query has "and" or is long (> 10 words), decompose. Else expand.
|
|
230
|
+
if " and " in query.lower() or len(query.split()) > 10:
|
|
231
|
+
return await decomposed_search(query, project_path, n_results, language, node_type, provider)
|
|
232
|
+
else:
|
|
233
|
+
return await multi_query_search(query, project_path, n_results, 3, language, node_type, provider)
|
|
234
|
+
elif mode == "both":
|
|
235
|
+
return await multi_query_search(query, project_path, n_results, 3, language, node_type, provider)
|
|
236
|
+
|
|
237
|
+
return await semantic_search(query, project_path, n_results, language, node_type, provider=provider)
|
|
238
|
+
|
|
239
|
+
async def git_context_search(
|
|
240
|
+
|
|
241
|
+
target_file: str,
|
|
242
|
+
|
|
243
|
+
project_path: str = ".",
|
|
244
|
+
|
|
245
|
+
limit: int = 10,
|
|
246
|
+
|
|
247
|
+
) -> str:
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
Find files that frequently change together with the target file using git history and static analysis.
|
|
252
|
+
|
|
253
|
+
Uses 'stravinsky_native.get_hybrid_context' for scoring.
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
# 1. Check Configuration
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
|
|
261
|
+
import yaml
|
|
262
|
+
|
|
263
|
+
from pathlib import Path
|
|
264
|
+
|
|
265
|
+
config_path = Path(".stravinsky/model_config.yaml")
|
|
266
|
+
|
|
267
|
+
if config_path.exists():
|
|
268
|
+
|
|
269
|
+
config = yaml.safe_load(config_path.read_text())
|
|
270
|
+
|
|
271
|
+
features = config.get("features", {}).get("context_graph", {})
|
|
272
|
+
|
|
273
|
+
if not features.get("enabled", True):
|
|
274
|
+
|
|
275
|
+
return "Context graph search is disabled in configuration."
|
|
276
|
+
|
|
277
|
+
threshold = features.get("threshold_score", 0.4)
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
|
|
281
|
+
threshold = 0.4
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
|
|
285
|
+
logger.warning(f"Failed to read context graph config: {e}")
|
|
286
|
+
|
|
287
|
+
threshold = 0.4
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
|
|
294
|
+
import stravinsky_native
|
|
295
|
+
|
|
296
|
+
except ImportError:
|
|
297
|
+
|
|
298
|
+
return "Error: stravinsky_native extension not available."
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
|
|
305
|
+
# 2. Call Hybrid Logic (Git + Import Verification)
|
|
306
|
+
|
|
307
|
+
# Returns list of (file, score, reasons)
|
|
308
|
+
|
|
309
|
+
related_files = stravinsky_native.get_hybrid_context(
|
|
310
|
+
|
|
311
|
+
target_file,
|
|
312
|
+
|
|
313
|
+
project_path,
|
|
314
|
+
|
|
315
|
+
limit,
|
|
316
|
+
|
|
317
|
+
float(threshold)
|
|
318
|
+
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if not related_files:
|
|
324
|
+
|
|
325
|
+
return f"No related files found for '{target_file}' (threshold={threshold})."
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
lines = [f"Files related to '{target_file}' (Hybrid Context Graph):\n"]
|
|
330
|
+
|
|
331
|
+
lines.append(f"threshold: {threshold} | reasons: imported, git-history\n")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
for i, (f, score, reason) in enumerate(related_files, 1):
|
|
336
|
+
|
|
337
|
+
lines.append(f"{i}. {f} (score: {score:.2f}) [{reason}]")
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
return "\n".join(lines)
|
|
342
|
+
|
|
343
|
+
except Exception as e:
|
|
344
|
+
|
|
345
|
+
logger.error(f"Context graph search failed: {e}")
|
|
346
|
+
|
|
347
|
+
return f"Error performing context search: {e}"
|