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.

Files changed (190) hide show
  1. mcp_bridge/__init__.py +1 -1
  2. mcp_bridge/auth/__init__.py +16 -6
  3. mcp_bridge/auth/cli.py +202 -11
  4. mcp_bridge/auth/oauth.py +1 -2
  5. mcp_bridge/auth/openai_oauth.py +4 -7
  6. mcp_bridge/auth/token_store.py +112 -11
  7. mcp_bridge/cli/__init__.py +1 -1
  8. mcp_bridge/cli/install_hooks.py +503 -107
  9. mcp_bridge/cli/session_report.py +0 -3
  10. mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
  11. mcp_bridge/config/README.md +276 -0
  12. mcp_bridge/config/__init__.py +2 -2
  13. mcp_bridge/config/hook_config.py +247 -0
  14. mcp_bridge/config/hooks_manifest.json +138 -0
  15. mcp_bridge/config/rate_limits.py +317 -0
  16. mcp_bridge/config/skills_manifest.json +128 -0
  17. mcp_bridge/hooks/HOOKS_SETTINGS.json +17 -4
  18. mcp_bridge/hooks/__init__.py +19 -4
  19. mcp_bridge/hooks/agent_reminder.py +4 -4
  20. mcp_bridge/hooks/auto_slash_command.py +5 -5
  21. mcp_bridge/hooks/budget_optimizer.py +2 -2
  22. mcp_bridge/hooks/claude_limits_hook.py +114 -0
  23. mcp_bridge/hooks/comment_checker.py +3 -4
  24. mcp_bridge/hooks/compaction.py +2 -2
  25. mcp_bridge/hooks/context.py +2 -1
  26. mcp_bridge/hooks/context_monitor.py +2 -2
  27. mcp_bridge/hooks/delegation_policy.py +85 -0
  28. mcp_bridge/hooks/directory_context.py +3 -3
  29. mcp_bridge/hooks/edit_recovery.py +3 -2
  30. mcp_bridge/hooks/edit_recovery_policy.py +49 -0
  31. mcp_bridge/hooks/empty_message_sanitizer.py +2 -2
  32. mcp_bridge/hooks/events.py +160 -0
  33. mcp_bridge/hooks/git_noninteractive.py +4 -4
  34. mcp_bridge/hooks/keyword_detector.py +8 -10
  35. mcp_bridge/hooks/manager.py +43 -22
  36. mcp_bridge/hooks/notification_hook.py +13 -6
  37. mcp_bridge/hooks/parallel_enforcement_policy.py +67 -0
  38. mcp_bridge/hooks/parallel_enforcer.py +5 -5
  39. mcp_bridge/hooks/parallel_execution.py +22 -10
  40. mcp_bridge/hooks/post_tool/parallel_validation.py +103 -0
  41. mcp_bridge/hooks/pre_compact.py +8 -9
  42. mcp_bridge/hooks/pre_tool/agent_spawn_validator.py +115 -0
  43. mcp_bridge/hooks/preemptive_compaction.py +2 -3
  44. mcp_bridge/hooks/routing_notifications.py +80 -0
  45. mcp_bridge/hooks/rules_injector.py +11 -19
  46. mcp_bridge/hooks/session_idle.py +4 -4
  47. mcp_bridge/hooks/session_notifier.py +4 -4
  48. mcp_bridge/hooks/session_recovery.py +4 -5
  49. mcp_bridge/hooks/stravinsky_mode.py +1 -1
  50. mcp_bridge/hooks/subagent_stop.py +1 -3
  51. mcp_bridge/hooks/task_validator.py +2 -2
  52. mcp_bridge/hooks/tmux_manager.py +7 -8
  53. mcp_bridge/hooks/todo_delegation.py +4 -1
  54. mcp_bridge/hooks/todo_enforcer.py +180 -10
  55. mcp_bridge/hooks/tool_messaging.py +113 -10
  56. mcp_bridge/hooks/truncation_policy.py +37 -0
  57. mcp_bridge/hooks/truncator.py +1 -2
  58. mcp_bridge/metrics/cost_tracker.py +115 -0
  59. mcp_bridge/native_search.py +93 -0
  60. mcp_bridge/native_watcher.py +118 -0
  61. mcp_bridge/notifications.py +150 -0
  62. mcp_bridge/orchestrator/enums.py +11 -0
  63. mcp_bridge/orchestrator/router.py +165 -0
  64. mcp_bridge/orchestrator/state.py +32 -0
  65. mcp_bridge/orchestrator/visualization.py +14 -0
  66. mcp_bridge/orchestrator/wisdom.py +34 -0
  67. mcp_bridge/prompts/__init__.py +1 -8
  68. mcp_bridge/prompts/dewey.py +1 -1
  69. mcp_bridge/prompts/planner.py +2 -4
  70. mcp_bridge/prompts/stravinsky.py +53 -31
  71. mcp_bridge/proxy/__init__.py +0 -0
  72. mcp_bridge/proxy/client.py +70 -0
  73. mcp_bridge/proxy/model_server.py +157 -0
  74. mcp_bridge/routing/__init__.py +43 -0
  75. mcp_bridge/routing/config.py +250 -0
  76. mcp_bridge/routing/model_tiers.py +135 -0
  77. mcp_bridge/routing/provider_state.py +261 -0
  78. mcp_bridge/routing/task_classifier.py +190 -0
  79. mcp_bridge/server.py +542 -59
  80. mcp_bridge/server_tools.py +738 -6
  81. mcp_bridge/tools/__init__.py +40 -25
  82. mcp_bridge/tools/agent_manager.py +616 -697
  83. mcp_bridge/tools/background_tasks.py +13 -17
  84. mcp_bridge/tools/code_search.py +70 -53
  85. mcp_bridge/tools/continuous_loop.py +0 -1
  86. mcp_bridge/tools/dashboard.py +19 -0
  87. mcp_bridge/tools/find_code.py +296 -0
  88. mcp_bridge/tools/init.py +1 -0
  89. mcp_bridge/tools/list_directory.py +42 -0
  90. mcp_bridge/tools/lsp/__init__.py +12 -5
  91. mcp_bridge/tools/lsp/manager.py +471 -0
  92. mcp_bridge/tools/lsp/tools.py +723 -207
  93. mcp_bridge/tools/model_invoke.py +1195 -273
  94. mcp_bridge/tools/mux_client.py +75 -0
  95. mcp_bridge/tools/project_context.py +1 -2
  96. mcp_bridge/tools/query_classifier.py +406 -0
  97. mcp_bridge/tools/read_file.py +84 -0
  98. mcp_bridge/tools/replace.py +45 -0
  99. mcp_bridge/tools/run_shell_command.py +38 -0
  100. mcp_bridge/tools/search_enhancements.py +347 -0
  101. mcp_bridge/tools/semantic_search.py +3627 -0
  102. mcp_bridge/tools/session_manager.py +0 -2
  103. mcp_bridge/tools/skill_loader.py +0 -1
  104. mcp_bridge/tools/task_runner.py +5 -7
  105. mcp_bridge/tools/templates.py +3 -3
  106. mcp_bridge/tools/tool_search.py +331 -0
  107. mcp_bridge/tools/write_file.py +29 -0
  108. mcp_bridge/update_manager.py +585 -0
  109. mcp_bridge/update_manager_pypi.py +297 -0
  110. mcp_bridge/utils/cache.py +82 -0
  111. mcp_bridge/utils/process.py +71 -0
  112. mcp_bridge/utils/session_state.py +51 -0
  113. mcp_bridge/utils/truncation.py +76 -0
  114. stravinsky-0.4.66.dist-info/METADATA +517 -0
  115. stravinsky-0.4.66.dist-info/RECORD +198 -0
  116. {stravinsky-0.2.67.dist-info → stravinsky-0.4.66.dist-info}/entry_points.txt +1 -0
  117. stravinsky_claude_assets/HOOKS_INTEGRATION.md +316 -0
  118. stravinsky_claude_assets/agents/HOOKS.md +437 -0
  119. stravinsky_claude_assets/agents/code-reviewer.md +210 -0
  120. stravinsky_claude_assets/agents/comment_checker.md +580 -0
  121. stravinsky_claude_assets/agents/debugger.md +254 -0
  122. stravinsky_claude_assets/agents/delphi.md +495 -0
  123. stravinsky_claude_assets/agents/dewey.md +248 -0
  124. stravinsky_claude_assets/agents/explore.md +1198 -0
  125. stravinsky_claude_assets/agents/frontend.md +472 -0
  126. stravinsky_claude_assets/agents/implementation-lead.md +164 -0
  127. stravinsky_claude_assets/agents/momus.md +464 -0
  128. stravinsky_claude_assets/agents/research-lead.md +141 -0
  129. stravinsky_claude_assets/agents/stravinsky.md +730 -0
  130. stravinsky_claude_assets/commands/delphi.md +9 -0
  131. stravinsky_claude_assets/commands/dewey.md +54 -0
  132. stravinsky_claude_assets/commands/git-master.md +112 -0
  133. stravinsky_claude_assets/commands/index.md +49 -0
  134. stravinsky_claude_assets/commands/publish.md +86 -0
  135. stravinsky_claude_assets/commands/review.md +73 -0
  136. stravinsky_claude_assets/commands/str/agent_cancel.md +70 -0
  137. stravinsky_claude_assets/commands/str/agent_list.md +56 -0
  138. stravinsky_claude_assets/commands/str/agent_output.md +92 -0
  139. stravinsky_claude_assets/commands/str/agent_progress.md +74 -0
  140. stravinsky_claude_assets/commands/str/agent_retry.md +94 -0
  141. stravinsky_claude_assets/commands/str/cancel.md +51 -0
  142. stravinsky_claude_assets/commands/str/clean.md +97 -0
  143. stravinsky_claude_assets/commands/str/continue.md +38 -0
  144. stravinsky_claude_assets/commands/str/index.md +199 -0
  145. stravinsky_claude_assets/commands/str/list_watchers.md +96 -0
  146. stravinsky_claude_assets/commands/str/search.md +205 -0
  147. stravinsky_claude_assets/commands/str/start_filewatch.md +136 -0
  148. stravinsky_claude_assets/commands/str/stats.md +71 -0
  149. stravinsky_claude_assets/commands/str/stop_filewatch.md +89 -0
  150. stravinsky_claude_assets/commands/str/unwatch.md +42 -0
  151. stravinsky_claude_assets/commands/str/watch.md +45 -0
  152. stravinsky_claude_assets/commands/strav.md +53 -0
  153. stravinsky_claude_assets/commands/stravinsky.md +292 -0
  154. stravinsky_claude_assets/commands/verify.md +60 -0
  155. stravinsky_claude_assets/commands/version.md +5 -0
  156. stravinsky_claude_assets/hooks/README.md +248 -0
  157. stravinsky_claude_assets/hooks/comment_checker.py +193 -0
  158. stravinsky_claude_assets/hooks/context.py +38 -0
  159. stravinsky_claude_assets/hooks/context_monitor.py +153 -0
  160. stravinsky_claude_assets/hooks/dependency_tracker.py +73 -0
  161. stravinsky_claude_assets/hooks/edit_recovery.py +46 -0
  162. stravinsky_claude_assets/hooks/execution_state_tracker.py +68 -0
  163. stravinsky_claude_assets/hooks/notification_hook.py +103 -0
  164. stravinsky_claude_assets/hooks/notification_hook_v2.py +96 -0
  165. stravinsky_claude_assets/hooks/parallel_execution.py +241 -0
  166. stravinsky_claude_assets/hooks/parallel_reinforcement.py +106 -0
  167. stravinsky_claude_assets/hooks/parallel_reinforcement_v2.py +112 -0
  168. stravinsky_claude_assets/hooks/pre_compact.py +123 -0
  169. stravinsky_claude_assets/hooks/ralph_loop.py +173 -0
  170. stravinsky_claude_assets/hooks/session_recovery.py +263 -0
  171. stravinsky_claude_assets/hooks/stop_hook.py +89 -0
  172. stravinsky_claude_assets/hooks/stravinsky_metrics.py +164 -0
  173. stravinsky_claude_assets/hooks/stravinsky_mode.py +146 -0
  174. stravinsky_claude_assets/hooks/subagent_stop.py +98 -0
  175. stravinsky_claude_assets/hooks/todo_continuation.py +111 -0
  176. stravinsky_claude_assets/hooks/todo_delegation.py +96 -0
  177. stravinsky_claude_assets/hooks/tool_messaging.py +281 -0
  178. stravinsky_claude_assets/hooks/truncator.py +23 -0
  179. stravinsky_claude_assets/rules/deployment_safety.md +51 -0
  180. stravinsky_claude_assets/rules/integration_wiring.md +89 -0
  181. stravinsky_claude_assets/rules/pypi_deployment.md +220 -0
  182. stravinsky_claude_assets/rules/stravinsky_orchestrator.md +32 -0
  183. stravinsky_claude_assets/settings.json +152 -0
  184. stravinsky_claude_assets/skills/chrome-devtools/SKILL.md +81 -0
  185. stravinsky_claude_assets/skills/sqlite/SKILL.md +77 -0
  186. stravinsky_claude_assets/skills/supabase/SKILL.md +74 -0
  187. stravinsky_claude_assets/task_dependencies.json +34 -0
  188. stravinsky-0.2.67.dist-info/METADATA +0 -284
  189. stravinsky-0.2.67.dist-info/RECORD +0 -76
  190. {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}"