codexa 0.4.0__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.
Files changed (189) hide show
  1. codexa-0.4.0.dist-info/METADATA +650 -0
  2. codexa-0.4.0.dist-info/RECORD +189 -0
  3. codexa-0.4.0.dist-info/WHEEL +5 -0
  4. codexa-0.4.0.dist-info/entry_points.txt +2 -0
  5. codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
  6. codexa-0.4.0.dist-info/top_level.txt +1 -0
  7. semantic_code_intelligence/__init__.py +5 -0
  8. semantic_code_intelligence/analysis/__init__.py +21 -0
  9. semantic_code_intelligence/analysis/ai_features.py +351 -0
  10. semantic_code_intelligence/bridge/__init__.py +28 -0
  11. semantic_code_intelligence/bridge/context_provider.py +245 -0
  12. semantic_code_intelligence/bridge/protocol.py +167 -0
  13. semantic_code_intelligence/bridge/server.py +348 -0
  14. semantic_code_intelligence/bridge/vscode.py +271 -0
  15. semantic_code_intelligence/ci/__init__.py +13 -0
  16. semantic_code_intelligence/ci/hooks.py +98 -0
  17. semantic_code_intelligence/ci/hotspots.py +272 -0
  18. semantic_code_intelligence/ci/impact.py +246 -0
  19. semantic_code_intelligence/ci/metrics.py +591 -0
  20. semantic_code_intelligence/ci/pr.py +412 -0
  21. semantic_code_intelligence/ci/quality.py +557 -0
  22. semantic_code_intelligence/ci/templates.py +164 -0
  23. semantic_code_intelligence/ci/trace.py +224 -0
  24. semantic_code_intelligence/cli/__init__.py +0 -0
  25. semantic_code_intelligence/cli/commands/__init__.py +0 -0
  26. semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
  27. semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
  28. semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
  29. semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
  30. semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
  31. semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
  32. semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
  33. semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
  34. semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
  35. semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
  36. semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
  37. semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
  38. semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
  39. semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
  40. semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
  41. semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
  42. semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
  43. semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
  44. semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
  45. semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
  46. semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
  47. semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
  48. semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
  49. semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
  50. semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
  51. semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
  52. semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
  53. semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
  54. semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
  55. semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
  56. semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
  57. semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
  58. semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
  59. semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
  60. semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
  61. semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
  62. semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
  63. semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
  64. semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
  65. semantic_code_intelligence/cli/main.py +65 -0
  66. semantic_code_intelligence/cli/router.py +92 -0
  67. semantic_code_intelligence/config/__init__.py +0 -0
  68. semantic_code_intelligence/config/settings.py +260 -0
  69. semantic_code_intelligence/context/__init__.py +19 -0
  70. semantic_code_intelligence/context/engine.py +429 -0
  71. semantic_code_intelligence/context/memory.py +253 -0
  72. semantic_code_intelligence/daemon/__init__.py +1 -0
  73. semantic_code_intelligence/daemon/watcher.py +515 -0
  74. semantic_code_intelligence/docs/__init__.py +1080 -0
  75. semantic_code_intelligence/embeddings/__init__.py +0 -0
  76. semantic_code_intelligence/embeddings/enhanced.py +131 -0
  77. semantic_code_intelligence/embeddings/generator.py +149 -0
  78. semantic_code_intelligence/embeddings/model_registry.py +100 -0
  79. semantic_code_intelligence/evolution/__init__.py +1 -0
  80. semantic_code_intelligence/evolution/budget_guard.py +111 -0
  81. semantic_code_intelligence/evolution/commit_manager.py +88 -0
  82. semantic_code_intelligence/evolution/context_builder.py +131 -0
  83. semantic_code_intelligence/evolution/engine.py +249 -0
  84. semantic_code_intelligence/evolution/patch_generator.py +229 -0
  85. semantic_code_intelligence/evolution/task_selector.py +214 -0
  86. semantic_code_intelligence/evolution/test_runner.py +111 -0
  87. semantic_code_intelligence/indexing/__init__.py +0 -0
  88. semantic_code_intelligence/indexing/chunker.py +174 -0
  89. semantic_code_intelligence/indexing/parallel.py +86 -0
  90. semantic_code_intelligence/indexing/scanner.py +146 -0
  91. semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
  92. semantic_code_intelligence/llm/__init__.py +62 -0
  93. semantic_code_intelligence/llm/cache.py +219 -0
  94. semantic_code_intelligence/llm/cached_provider.py +145 -0
  95. semantic_code_intelligence/llm/conversation.py +190 -0
  96. semantic_code_intelligence/llm/cross_refactor.py +272 -0
  97. semantic_code_intelligence/llm/investigation.py +274 -0
  98. semantic_code_intelligence/llm/mock_provider.py +77 -0
  99. semantic_code_intelligence/llm/ollama_provider.py +122 -0
  100. semantic_code_intelligence/llm/openai_provider.py +100 -0
  101. semantic_code_intelligence/llm/provider.py +92 -0
  102. semantic_code_intelligence/llm/rate_limiter.py +164 -0
  103. semantic_code_intelligence/llm/reasoning.py +438 -0
  104. semantic_code_intelligence/llm/safety.py +110 -0
  105. semantic_code_intelligence/llm/streaming.py +251 -0
  106. semantic_code_intelligence/lsp/__init__.py +609 -0
  107. semantic_code_intelligence/mcp/__init__.py +393 -0
  108. semantic_code_intelligence/parsing/__init__.py +19 -0
  109. semantic_code_intelligence/parsing/parser.py +375 -0
  110. semantic_code_intelligence/plugins/__init__.py +255 -0
  111. semantic_code_intelligence/plugins/examples/__init__.py +1 -0
  112. semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
  113. semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
  114. semantic_code_intelligence/scalability/__init__.py +205 -0
  115. semantic_code_intelligence/search/__init__.py +0 -0
  116. semantic_code_intelligence/search/formatter.py +123 -0
  117. semantic_code_intelligence/search/grep.py +361 -0
  118. semantic_code_intelligence/search/hybrid_search.py +170 -0
  119. semantic_code_intelligence/search/keyword_search.py +311 -0
  120. semantic_code_intelligence/search/section_expander.py +103 -0
  121. semantic_code_intelligence/services/__init__.py +0 -0
  122. semantic_code_intelligence/services/indexing_service.py +630 -0
  123. semantic_code_intelligence/services/search_service.py +269 -0
  124. semantic_code_intelligence/storage/__init__.py +0 -0
  125. semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
  126. semantic_code_intelligence/storage/hash_store.py +66 -0
  127. semantic_code_intelligence/storage/index_manifest.py +85 -0
  128. semantic_code_intelligence/storage/index_stats.py +138 -0
  129. semantic_code_intelligence/storage/query_history.py +160 -0
  130. semantic_code_intelligence/storage/symbol_registry.py +209 -0
  131. semantic_code_intelligence/storage/vector_store.py +297 -0
  132. semantic_code_intelligence/tests/__init__.py +0 -0
  133. semantic_code_intelligence/tests/test_ai_features.py +351 -0
  134. semantic_code_intelligence/tests/test_chunker.py +119 -0
  135. semantic_code_intelligence/tests/test_cli.py +188 -0
  136. semantic_code_intelligence/tests/test_config.py +154 -0
  137. semantic_code_intelligence/tests/test_context.py +381 -0
  138. semantic_code_intelligence/tests/test_embeddings.py +73 -0
  139. semantic_code_intelligence/tests/test_endtoend.py +1142 -0
  140. semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
  141. semantic_code_intelligence/tests/test_hash_store.py +79 -0
  142. semantic_code_intelligence/tests/test_logging.py +55 -0
  143. semantic_code_intelligence/tests/test_new_cli.py +138 -0
  144. semantic_code_intelligence/tests/test_parser.py +495 -0
  145. semantic_code_intelligence/tests/test_phase10.py +355 -0
  146. semantic_code_intelligence/tests/test_phase11.py +593 -0
  147. semantic_code_intelligence/tests/test_phase12.py +375 -0
  148. semantic_code_intelligence/tests/test_phase13.py +663 -0
  149. semantic_code_intelligence/tests/test_phase14.py +568 -0
  150. semantic_code_intelligence/tests/test_phase15.py +814 -0
  151. semantic_code_intelligence/tests/test_phase16.py +792 -0
  152. semantic_code_intelligence/tests/test_phase17.py +815 -0
  153. semantic_code_intelligence/tests/test_phase18.py +934 -0
  154. semantic_code_intelligence/tests/test_phase19.py +986 -0
  155. semantic_code_intelligence/tests/test_phase20.py +2753 -0
  156. semantic_code_intelligence/tests/test_phase20b.py +2058 -0
  157. semantic_code_intelligence/tests/test_phase20c.py +962 -0
  158. semantic_code_intelligence/tests/test_phase21.py +428 -0
  159. semantic_code_intelligence/tests/test_phase22.py +799 -0
  160. semantic_code_intelligence/tests/test_phase23.py +783 -0
  161. semantic_code_intelligence/tests/test_phase24.py +715 -0
  162. semantic_code_intelligence/tests/test_phase25.py +496 -0
  163. semantic_code_intelligence/tests/test_phase26.py +251 -0
  164. semantic_code_intelligence/tests/test_phase27.py +531 -0
  165. semantic_code_intelligence/tests/test_phase8.py +592 -0
  166. semantic_code_intelligence/tests/test_phase9.py +643 -0
  167. semantic_code_intelligence/tests/test_plugins.py +293 -0
  168. semantic_code_intelligence/tests/test_priority_features.py +727 -0
  169. semantic_code_intelligence/tests/test_router.py +41 -0
  170. semantic_code_intelligence/tests/test_scalability.py +138 -0
  171. semantic_code_intelligence/tests/test_scanner.py +125 -0
  172. semantic_code_intelligence/tests/test_search.py +160 -0
  173. semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
  174. semantic_code_intelligence/tests/test_tools.py +182 -0
  175. semantic_code_intelligence/tests/test_vector_store.py +151 -0
  176. semantic_code_intelligence/tests/test_watcher.py +211 -0
  177. semantic_code_intelligence/tools/__init__.py +442 -0
  178. semantic_code_intelligence/tools/executor.py +232 -0
  179. semantic_code_intelligence/tools/protocol.py +200 -0
  180. semantic_code_intelligence/tui/__init__.py +454 -0
  181. semantic_code_intelligence/utils/__init__.py +0 -0
  182. semantic_code_intelligence/utils/logging.py +112 -0
  183. semantic_code_intelligence/version.py +3 -0
  184. semantic_code_intelligence/web/__init__.py +11 -0
  185. semantic_code_intelligence/web/api.py +289 -0
  186. semantic_code_intelligence/web/server.py +397 -0
  187. semantic_code_intelligence/web/ui.py +659 -0
  188. semantic_code_intelligence/web/visualize.py +226 -0
  189. semantic_code_intelligence/workspace/__init__.py +427 -0
@@ -0,0 +1,375 @@
1
+ """Tests for Phase 12 — Platform Enhancements.
2
+
3
+ Covers: new plugin hooks (ON_STREAM, CUSTOM_VALIDATION), reasoning engine
4
+ improvements (context pruning, priority scoring, explainability), enhanced
5
+ security validator patterns, and VSCode streaming context.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any
14
+ from unittest.mock import MagicMock, patch
15
+
16
+ import pytest
17
+
18
+ # =========================================================================
19
+ # Plugin hook tests
20
+ # =========================================================================
21
+
22
+ from semantic_code_intelligence.plugins import (
23
+ PluginBase,
24
+ PluginHook,
25
+ PluginManager,
26
+ PluginMetadata,
27
+ )
28
+
29
+
30
+ class TestNewPluginHooks:
31
+ def test_on_stream_exists(self):
32
+ assert PluginHook.ON_STREAM == "on_stream"
33
+
34
+ def test_custom_validation_exists(self):
35
+ assert PluginHook.CUSTOM_VALIDATION == "custom_validation"
36
+
37
+ def test_all_hooks_count(self):
38
+ # 11 original + 2 new = 13
39
+ assert len(PluginHook) == 22
40
+
41
+ def test_hook_registry_has_new_hooks(self):
42
+ mgr = PluginManager()
43
+ assert PluginHook.ON_STREAM in mgr._hook_registry
44
+ assert PluginHook.CUSTOM_VALIDATION in mgr._hook_registry
45
+
46
+ def test_dispatch_on_stream(self):
47
+ """ON_STREAM hook should dispatch to active plugins."""
48
+
49
+ class StreamPlugin(PluginBase):
50
+ def metadata(self):
51
+ return PluginMetadata(name="streamer", hooks=[PluginHook.ON_STREAM])
52
+
53
+ def on_hook(self, hook, data):
54
+ data["streamed"] = True
55
+ return data
56
+
57
+ mgr = PluginManager()
58
+ plugin = StreamPlugin()
59
+ mgr.register(plugin)
60
+ mgr.activate("streamer")
61
+ result = mgr.dispatch(PluginHook.ON_STREAM, {"token": "hello"})
62
+ assert result["streamed"] is True
63
+ assert result["token"] == "hello"
64
+
65
+ def test_dispatch_custom_validation(self):
66
+ """CUSTOM_VALIDATION hook should allow custom validation rules."""
67
+
68
+ class ValidatorPlugin(PluginBase):
69
+ def metadata(self):
70
+ return PluginMetadata(name="validator", hooks=[PluginHook.CUSTOM_VALIDATION])
71
+
72
+ def on_hook(self, hook, data):
73
+ code = data.get("code", "")
74
+ issues = data.get("issues", [])
75
+ if "TODO" in code:
76
+ issues.append({"description": "TODO found", "severity": "info"})
77
+ data["issues"] = issues
78
+ return data
79
+
80
+ mgr = PluginManager()
81
+ plugin = ValidatorPlugin()
82
+ mgr.register(plugin)
83
+ mgr.activate("validator")
84
+ result = mgr.dispatch(PluginHook.CUSTOM_VALIDATION, {"code": "x = 1 # TODO fix", "issues": []})
85
+ assert len(result["issues"]) == 1
86
+ assert result["issues"][0]["description"] == "TODO found"
87
+
88
+
89
+ # =========================================================================
90
+ # Reasoning engine tests
91
+ # =========================================================================
92
+
93
+ from semantic_code_intelligence.llm.reasoning import (
94
+ AskResult,
95
+ ReasoningEngine,
96
+ RefactorResult,
97
+ ReviewResult,
98
+ SuggestResult,
99
+ )
100
+
101
+
102
+ class TestExplainabilityMetadata:
103
+ def test_ask_result_has_explainability(self):
104
+ r = AskResult(question="q", answer="a", explainability={"method": "test"})
105
+ d = r.to_dict()
106
+ assert "explainability" in d
107
+ assert d["explainability"]["method"] == "test"
108
+
109
+ def test_review_result_has_explainability(self):
110
+ r = ReviewResult(file_path="f.py", explainability={"x": 1})
111
+ assert r.to_dict()["explainability"] == {"x": 1}
112
+
113
+ def test_refactor_result_has_explainability(self):
114
+ r = RefactorResult(file_path="f.py", explainability={"x": 2})
115
+ assert r.to_dict()["explainability"] == {"x": 2}
116
+
117
+ def test_suggest_result_has_explainability(self):
118
+ r = SuggestResult(target="t", explainability={"x": 3})
119
+ assert r.to_dict()["explainability"] == {"x": 3}
120
+
121
+ def test_default_empty_explainability(self):
122
+ r = AskResult(question="q", answer="a")
123
+ assert r.explainability == {}
124
+ assert r.to_dict()["explainability"] == {}
125
+
126
+
127
+ class TestContextPruning:
128
+ def test_score_snippet_base(self):
129
+ snip = {"score": 0.85, "content": "def hello(): pass"}
130
+ score = ReasoningEngine._score_snippet(snip, "hello function")
131
+ assert score >= 0.85
132
+
133
+ def test_score_snippet_keyword_bonus(self):
134
+ snip = {"score": 0.5, "content": "def authenticate_user(username): pass"}
135
+ score_relevant = ReasoningEngine._score_snippet(snip, "authenticate user")
136
+ score_irrelevant = ReasoningEngine._score_snippet(snip, "database migration")
137
+ assert score_relevant > score_irrelevant
138
+
139
+ def test_score_snippet_no_content(self):
140
+ snip = {"score": 0.3, "content": ""}
141
+ score = ReasoningEngine._score_snippet(snip, "query")
142
+ assert score == 0.3
143
+
144
+ def test_prune_context_limits_chars(self):
145
+ provider = MagicMock()
146
+ engine = ReasoningEngine(provider, Path("."), max_context_chars=100)
147
+ snippets = [
148
+ {"score": 0.9, "content": "A" * 60},
149
+ {"score": 0.8, "content": "B" * 60},
150
+ {"score": 0.7, "content": "C" * 60},
151
+ ]
152
+ pruned = engine._prune_context(snippets, "query")
153
+ total = sum(len(s["content"]) for s in pruned)
154
+ assert total <= 120 # first snippet always kept; second may push over
155
+
156
+ def test_prune_context_preserves_order(self):
157
+ provider = MagicMock()
158
+ engine = ReasoningEngine(provider, Path("."), max_context_chars=10000)
159
+ snippets = [
160
+ {"score": 0.5, "content": "low"},
161
+ {"score": 0.9, "content": "high"},
162
+ {"score": 0.7, "content": "mid"},
163
+ ]
164
+ pruned = engine._prune_context(snippets, "test")
165
+ scores = [s.get("priority_score", 0) for s in pruned]
166
+ assert scores == sorted(scores, reverse=True)
167
+
168
+ def test_prune_context_adds_priority_score(self):
169
+ provider = MagicMock()
170
+ engine = ReasoningEngine(provider, Path("."))
171
+ snippets = [{"score": 0.6, "content": "test data here"}]
172
+ pruned = engine._prune_context(snippets, "test")
173
+ assert "priority_score" in pruned[0]
174
+
175
+ def test_prune_context_empty(self):
176
+ provider = MagicMock()
177
+ engine = ReasoningEngine(provider, Path("."))
178
+ assert engine._prune_context([], "query") == []
179
+
180
+
181
+ class TestReasoningEngineMaxContext:
182
+ def test_default_max_context(self):
183
+ provider = MagicMock()
184
+ engine = ReasoningEngine(provider, Path("."))
185
+ assert engine._max_ctx == ReasoningEngine.DEFAULT_MAX_CONTEXT_CHARS
186
+
187
+ def test_custom_max_context(self):
188
+ provider = MagicMock()
189
+ engine = ReasoningEngine(provider, Path("."), max_context_chars=2000)
190
+ assert engine._max_ctx == 2000
191
+
192
+
193
+ # =========================================================================
194
+ # Security validator tests
195
+ # =========================================================================
196
+
197
+ from semantic_code_intelligence.llm.safety import SafetyValidator
198
+
199
+
200
+ class TestEnhancedSafetyPatterns:
201
+ def setup_method(self):
202
+ self.validator = SafetyValidator()
203
+
204
+ # Original patterns still work
205
+ def test_os_system(self):
206
+ assert not self.validator.is_safe("os.system('ls')")
207
+
208
+ def test_eval(self):
209
+ assert not self.validator.is_safe("result = eval(user_input)")
210
+
211
+ def test_subprocess_shell(self):
212
+ assert not self.validator.is_safe("subprocess.run(cmd, shell=True)")
213
+
214
+ def test_drop_table(self):
215
+ assert not self.validator.is_safe("DROP TABLE users")
216
+
217
+ # New Phase 12 patterns
218
+ def test_path_traversal(self):
219
+ assert not self.validator.is_safe("open('../../etc/passwd')")
220
+
221
+ def test_hardcoded_password(self):
222
+ assert not self.validator.is_safe('password = "mysecretpassword123"')
223
+
224
+ def test_hardcoded_api_key(self):
225
+ assert not self.validator.is_safe('api_key = "sk-proj-1234567890abcdef"')
226
+
227
+ def test_innerhtml_xss(self):
228
+ assert not self.validator.is_safe('element.innerHTML = userInput')
229
+
230
+ def test_document_write_xss(self):
231
+ assert not self.validator.is_safe('document.write(data)')
232
+
233
+ def test_md5_insecure(self):
234
+ assert not self.validator.is_safe("hash = MD5(data)")
235
+
236
+ def test_sha1_insecure(self):
237
+ assert not self.validator.is_safe("hash = sha1(data)")
238
+
239
+ def test_http_insecure(self):
240
+ assert not self.validator.is_safe("url = 'http://example.com/api'")
241
+
242
+ def test_http_localhost_allowed(self):
243
+ assert self.validator.is_safe("url = 'http://localhost:8080'")
244
+
245
+ def test_ssl_verify_disabled(self):
246
+ assert not self.validator.is_safe("requests.get(url, verify=False)")
247
+
248
+ def test_safe_code_passes(self):
249
+ safe = """
250
+ import hashlib
251
+ from pathlib import Path
252
+
253
+ def get_hash(data: bytes) -> str:
254
+ return hashlib.sha256(data).hexdigest()
255
+
256
+ config = Path("config.yaml").read_text()
257
+ """
258
+ assert self.validator.is_safe(safe)
259
+
260
+ def test_validate_report_details(self):
261
+ report = self.validator.validate("el.innerHTML = x\nMD5(y)")
262
+ assert not report.safe
263
+ assert len(report.issues) >= 2
264
+ descs = [i.description for i in report.issues]
265
+ assert any("XSS" in d for d in descs)
266
+ assert any("MD5" in d for d in descs)
267
+
268
+
269
+ # =========================================================================
270
+ # VSCode streaming context tests
271
+ # =========================================================================
272
+
273
+ from semantic_code_intelligence.bridge.vscode import (
274
+ StreamChunk,
275
+ VSCodeBridge,
276
+ build_streaming_context,
277
+ )
278
+
279
+
280
+ class TestStreamChunk:
281
+ def test_to_dict(self):
282
+ chunk = StreamChunk(kind="token", content="hello")
283
+ d = chunk.to_dict()
284
+ assert d["kind"] == "token"
285
+ assert d["content"] == "hello"
286
+ assert d["metadata"] == {}
287
+
288
+ def test_to_dict_with_metadata(self):
289
+ chunk = StreamChunk(kind="context", content="info", metadata={"count": 3})
290
+ d = chunk.to_dict()
291
+ assert d["metadata"]["count"] == 3
292
+
293
+ def test_to_sse(self):
294
+ chunk = StreamChunk(kind="done", content="")
295
+ sse = chunk.to_sse()
296
+ assert sse.startswith("data: ")
297
+ assert sse.endswith("\n\n")
298
+ parsed = json.loads(sse[6:].strip())
299
+ assert parsed["kind"] == "done"
300
+
301
+ def test_kind_values(self):
302
+ for kind in ("token", "context", "done", "error"):
303
+ chunk = StreamChunk(kind=kind)
304
+ assert chunk.kind == kind
305
+
306
+
307
+ class TestBuildStreamingContext:
308
+ def test_basic_structure(self):
309
+ provider = MagicMock()
310
+ provider.context_for_query.return_value = {
311
+ "results": [
312
+ {"content": "def foo(): pass", "file_path": "a.py", "score": 0.9},
313
+ {"content": "def bar(): pass", "file_path": "b.py", "score": 0.7},
314
+ ]
315
+ }
316
+ chunks = build_streaming_context("test query", provider, top_k=5)
317
+ # First chunk should be context
318
+ assert chunks[0].kind == "context"
319
+ assert "2" in chunks[0].content # "Found 2 relevant snippets"
320
+ # Middle chunks should be tokens
321
+ assert chunks[1].kind == "token"
322
+ assert chunks[2].kind == "token"
323
+ # Last chunk should be done
324
+ assert chunks[-1].kind == "done"
325
+
326
+ def test_empty_results(self):
327
+ provider = MagicMock()
328
+ provider.context_for_query.return_value = {"results": []}
329
+ chunks = build_streaming_context("nothing", provider)
330
+ assert len(chunks) == 2 # context + done
331
+ assert chunks[0].kind == "context"
332
+ assert chunks[1].kind == "done"
333
+
334
+ def test_token_metadata_has_file_path(self):
335
+ provider = MagicMock()
336
+ provider.context_for_query.return_value = {
337
+ "results": [{"content": "code", "file_path": "x.py", "score": 0.8}]
338
+ }
339
+ chunks = build_streaming_context("q", provider)
340
+ token_chunk = chunks[1]
341
+ assert token_chunk.metadata["file_path"] == "x.py"
342
+ assert token_chunk.metadata["score"] == 0.8
343
+
344
+ def test_all_chunks_serializable(self):
345
+ provider = MagicMock()
346
+ provider.context_for_query.return_value = {
347
+ "results": [{"content": "test", "file_path": "f.py", "score": 0.5}]
348
+ }
349
+ chunks = build_streaming_context("q", provider)
350
+ for chunk in chunks:
351
+ # Must be JSON-serializable
352
+ serialized = json.dumps(chunk.to_dict())
353
+ assert serialized # non-empty
354
+
355
+
356
+ # =========================================================================
357
+ # Integration: existing Phase 9 features still work
358
+ # =========================================================================
359
+
360
+
361
+ class TestExistingBridgeFeaturesIntact:
362
+ def test_vscode_bridge_hover_method_exists(self):
363
+ assert hasattr(VSCodeBridge, "hover")
364
+
365
+ def test_vscode_bridge_diagnostics_method_exists(self):
366
+ assert hasattr(VSCodeBridge, "diagnostics")
367
+
368
+ def test_vscode_bridge_completions_method_exists(self):
369
+ assert hasattr(VSCodeBridge, "completions")
370
+
371
+ def test_vscode_bridge_code_actions_method_exists(self):
372
+ assert hasattr(VSCodeBridge, "code_actions")
373
+
374
+ def test_vscode_bridge_file_summary_method_exists(self):
375
+ assert hasattr(VSCodeBridge, "file_summary")