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,643 @@
1
+ """Tests for Phase 9 — External AI Cooperation Layer.
2
+
3
+ Covers: bridge protocol, context provider, HTTP bridge server, VSCode
4
+ extension bridge, and CLI commands (serve, context).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import threading
11
+ import time
12
+ import urllib.request
13
+ import urllib.error
14
+ from pathlib import Path
15
+ from typing import Any
16
+ from unittest.mock import MagicMock, patch
17
+
18
+ import pytest
19
+
20
+ # =========================================================================
21
+ # Protocol tests
22
+ # =========================================================================
23
+
24
+ from semantic_code_intelligence.bridge.protocol import (
25
+ AgentRequest,
26
+ AgentResponse,
27
+ BridgeCapabilities,
28
+ RequestKind,
29
+ )
30
+
31
+
32
+ class TestRequestKind:
33
+ def test_all_values(self):
34
+ kinds = [k.value for k in RequestKind]
35
+ assert "semantic_search" in kinds
36
+ assert "explain_symbol" in kinds
37
+ assert "explain_file" in kinds
38
+ assert "get_context" in kinds
39
+ assert "get_dependencies" in kinds
40
+ assert "get_call_graph" in kinds
41
+ assert "summarize_repo" in kinds
42
+ assert "find_references" in kinds
43
+ assert "validate_code" in kinds
44
+ assert "list_capabilities" in kinds
45
+
46
+ def test_count(self):
47
+ assert len(RequestKind) == 12
48
+
49
+ def test_string_enum(self):
50
+ assert RequestKind.SEMANTIC_SEARCH == "semantic_search"
51
+ assert isinstance(RequestKind.SEMANTIC_SEARCH, str)
52
+
53
+
54
+ class TestAgentRequest:
55
+ def test_create(self):
56
+ req = AgentRequest(kind="semantic_search", params={"query": "auth"})
57
+ assert req.kind == "semantic_search"
58
+ assert req.params["query"] == "auth"
59
+ assert req.request_id == ""
60
+ assert req.source == ""
61
+
62
+ def test_to_dict(self):
63
+ req = AgentRequest(
64
+ kind="explain_symbol",
65
+ params={"symbol_name": "foo"},
66
+ request_id="r1",
67
+ source="copilot",
68
+ )
69
+ d = req.to_dict()
70
+ assert d["kind"] == "explain_symbol"
71
+ assert d["params"]["symbol_name"] == "foo"
72
+ assert d["request_id"] == "r1"
73
+ assert d["source"] == "copilot"
74
+
75
+ def test_to_json(self):
76
+ req = AgentRequest(kind="validate_code", params={"code": "print(1)"})
77
+ j = req.to_json()
78
+ parsed = json.loads(j)
79
+ assert parsed["kind"] == "validate_code"
80
+
81
+ def test_from_dict(self):
82
+ d = {"kind": "semantic_search", "params": {"query": "hello"}, "request_id": "x"}
83
+ req = AgentRequest.from_dict(d)
84
+ assert req.kind == "semantic_search"
85
+ assert req.params["query"] == "hello"
86
+ assert req.request_id == "x"
87
+
88
+ def test_from_json(self):
89
+ j = json.dumps({"kind": "explain_file", "params": {"file_path": "a.py"}})
90
+ req = AgentRequest.from_json(j)
91
+ assert req.kind == "explain_file"
92
+ assert req.params["file_path"] == "a.py"
93
+
94
+ def test_from_dict_defaults(self):
95
+ d = {"kind": "list_capabilities"}
96
+ req = AgentRequest.from_dict(d)
97
+ assert req.params == {}
98
+ assert req.request_id == ""
99
+ assert req.source == ""
100
+
101
+ def test_roundtrip(self):
102
+ original = AgentRequest(
103
+ kind="get_dependencies",
104
+ params={"file_path": "src/main.py"},
105
+ request_id="abc-123",
106
+ source="cursor",
107
+ )
108
+ rebuilt = AgentRequest.from_json(original.to_json())
109
+ assert rebuilt.kind == original.kind
110
+ assert rebuilt.params == original.params
111
+ assert rebuilt.request_id == original.request_id
112
+ assert rebuilt.source == original.source
113
+
114
+
115
+ class TestAgentResponse:
116
+ def test_success(self):
117
+ resp = AgentResponse(success=True, data={"count": 5})
118
+ assert resp.success is True
119
+ d = resp.to_dict()
120
+ assert "data" in d
121
+ assert "error" not in d
122
+
123
+ def test_failure(self):
124
+ resp = AgentResponse(success=False, error="not found")
125
+ d = resp.to_dict()
126
+ assert d["success"] is False
127
+ assert "error" in d
128
+ assert "data" not in d
129
+
130
+ def test_elapsed(self):
131
+ resp = AgentResponse(success=True, data={}, elapsed_ms=12.345)
132
+ d = resp.to_dict()
133
+ assert d["elapsed_ms"] == 12.35
134
+
135
+ def test_to_json(self):
136
+ resp = AgentResponse(success=True, data={"key": "val"}, request_id="r1")
137
+ parsed = json.loads(resp.to_json())
138
+ assert parsed["success"] is True
139
+ assert parsed["request_id"] == "r1"
140
+
141
+ def test_to_json_indented(self):
142
+ resp = AgentResponse(success=True, data={})
143
+ text = resp.to_json(indent=2)
144
+ assert "\n" in text
145
+
146
+
147
+ class TestBridgeCapabilities:
148
+ def test_defaults(self):
149
+ cap = BridgeCapabilities()
150
+ assert cap.version == "0.9.0"
151
+ assert cap.name == "CodexA Bridge"
152
+ assert "semantic_search" in cap.supported_requests
153
+ assert len(cap.supported_requests) == 12
154
+
155
+ def test_to_dict(self):
156
+ cap = BridgeCapabilities()
157
+ d = cap.to_dict()
158
+ assert d["version"] == "0.9.0"
159
+ assert isinstance(d["supported_requests"], list)
160
+
161
+ def test_to_json(self):
162
+ cap = BridgeCapabilities()
163
+ parsed = json.loads(cap.to_json())
164
+ assert parsed["name"] == "CodexA Bridge"
165
+
166
+
167
+ # =========================================================================
168
+ # ContextProvider tests (with mocked subsystems)
169
+ # =========================================================================
170
+
171
+ from semantic_code_intelligence.bridge.context_provider import ContextProvider
172
+
173
+
174
+ class TestContextProvider:
175
+ @pytest.fixture
176
+ def provider(self, tmp_path: Path) -> ContextProvider:
177
+ return ContextProvider(tmp_path)
178
+
179
+ def test_init(self, provider: ContextProvider):
180
+ assert provider._indexed is False
181
+ assert provider._builder is None
182
+ assert provider._validator is not None
183
+
184
+ def test_validate_code_safe(self, provider: ContextProvider):
185
+ report = provider.validate_code("x = 1 + 2")
186
+ assert isinstance(report, dict)
187
+ assert "safe" in report
188
+
189
+ def test_validate_code_unsafe(self, provider: ContextProvider):
190
+ report = provider.validate_code("eval(input())")
191
+ assert isinstance(report, dict)
192
+ # Should flag the eval
193
+ assert report.get("is_safe") is False or len(report.get("issues", [])) > 0
194
+
195
+ @patch("semantic_code_intelligence.bridge.context_provider.search_codebase")
196
+ def test_context_for_query_empty(self, mock_search, provider):
197
+ mock_search.return_value = []
198
+ result = provider.context_for_query(query="test")
199
+ assert result["query"] == "test"
200
+ assert result["snippet_count"] == 0
201
+
202
+ @patch("semantic_code_intelligence.bridge.context_provider.search_codebase")
203
+ def test_context_for_query_with_results(self, mock_search, provider):
204
+ mock_result = MagicMock()
205
+ mock_result.to_dict.return_value = {"file": "a.py", "score": 0.9}
206
+ mock_search.return_value = [mock_result]
207
+ result = provider.context_for_query(query="auth")
208
+ assert result["snippet_count"] == 1
209
+ assert result["snippets"][0]["file"] == "a.py"
210
+
211
+ @patch("semantic_code_intelligence.bridge.context_provider.search_codebase")
212
+ def test_context_for_query_exception(self, mock_search, provider):
213
+ mock_search.side_effect = Exception("oops")
214
+ result = provider.context_for_query(query="fail")
215
+ assert result["snippet_count"] == 0
216
+
217
+ def test_context_for_symbol_not_found(self, provider):
218
+ """Symbol not found returns found=False."""
219
+ with patch.object(provider, "_ensure_indexed") as mock_idx:
220
+ mock_builder = MagicMock()
221
+ mock_builder.find_symbol.return_value = []
222
+ mock_idx.return_value = mock_builder
223
+ result = provider.context_for_symbol("nonexistent")
224
+ assert result["found"] is False
225
+
226
+ def test_context_for_repo(self, provider):
227
+ """Repo summary delegates to summarize_repository."""
228
+ with patch.object(provider, "_ensure_indexed") as mock_idx:
229
+ mock_builder = MagicMock()
230
+ mock_idx.return_value = mock_builder
231
+ with patch(
232
+ "semantic_code_intelligence.bridge.context_provider.summarize_repository"
233
+ ) as mock_sum:
234
+ mock_summary = MagicMock()
235
+ mock_summary.to_dict.return_value = {"total_files": 10}
236
+ mock_sum.return_value = mock_summary
237
+ result = provider.context_for_repo()
238
+ assert result["total_files"] == 10
239
+
240
+ def test_get_dependencies(self, provider):
241
+ """get_dependencies returns dict with expected keys."""
242
+ with patch.object(provider, "_ensure_indexed") as mock_idx:
243
+ mock_builder = MagicMock()
244
+ mock_builder._file_contents = {}
245
+ mock_idx.return_value = mock_builder
246
+ result = provider.get_dependencies("missing.py")
247
+ assert "file_path" in result
248
+ assert "dependencies" in result
249
+
250
+ def test_get_call_graph(self, provider):
251
+ """get_call_graph returns expected structure."""
252
+ with patch.object(provider, "_ensure_indexed") as mock_idx:
253
+ mock_builder = MagicMock()
254
+ mock_builder.get_all_symbols.return_value = []
255
+ mock_idx.return_value = mock_builder
256
+ result = provider.get_call_graph("some_func")
257
+ assert result["symbol_name"] == "some_func"
258
+ assert "callers" in result
259
+ assert "callees" in result
260
+
261
+ def test_find_references(self, provider):
262
+ """find_references returns expected structure."""
263
+ with patch.object(provider, "_ensure_indexed") as mock_idx:
264
+ mock_builder = MagicMock()
265
+ mock_builder.get_all_symbols.return_value = []
266
+ mock_idx.return_value = mock_builder
267
+ result = provider.find_references("some_func")
268
+ assert result["symbol_name"] == "some_func"
269
+ assert result["reference_count"] == 0
270
+
271
+
272
+ # =========================================================================
273
+ # BridgeServer tests
274
+ # =========================================================================
275
+
276
+ from semantic_code_intelligence.bridge.server import BridgeServer, _dispatch
277
+
278
+
279
+ class TestDispatch:
280
+ """Direct dispatch (no HTTP round-trip)."""
281
+
282
+ @pytest.fixture
283
+ def provider(self, tmp_path: Path):
284
+ return ContextProvider(tmp_path)
285
+
286
+ @pytest.fixture
287
+ def caps(self):
288
+ return BridgeCapabilities()
289
+
290
+ def test_list_capabilities(self, provider, caps):
291
+ req = AgentRequest(kind=RequestKind.LIST_CAPABILITIES)
292
+ resp = _dispatch(req, provider, caps)
293
+ assert resp.success is True
294
+ assert "supported_requests" in resp.data
295
+
296
+ def test_validate_code(self, provider, caps):
297
+ req = AgentRequest(
298
+ kind=RequestKind.VALIDATE_CODE,
299
+ params={"code": "x = 1"},
300
+ )
301
+ resp = _dispatch(req, provider, caps)
302
+ assert resp.success is True
303
+ assert "safe" in resp.data
304
+
305
+ @patch("semantic_code_intelligence.bridge.context_provider.search_codebase")
306
+ def test_semantic_search(self, mock_search, provider, caps):
307
+ mock_search.return_value = []
308
+ req = AgentRequest(
309
+ kind=RequestKind.SEMANTIC_SEARCH,
310
+ params={"query": "test"},
311
+ )
312
+ resp = _dispatch(req, provider, caps)
313
+ assert resp.success is True
314
+ assert resp.data["query"] == "test"
315
+
316
+ def test_unknown_kind(self, provider, caps):
317
+ req = AgentRequest(kind="totally_unknown")
318
+ resp = _dispatch(req, provider, caps)
319
+ assert resp.success is False
320
+ assert "Unknown" in resp.error
321
+
322
+
323
+ class TestBridgeServer:
324
+ """BridgeServer lifecycle and direct dispatch."""
325
+
326
+ def test_init(self, tmp_path: Path):
327
+ server = BridgeServer(tmp_path)
328
+ assert server.url == "http://127.0.0.1:24842"
329
+
330
+ def test_custom_host_port(self, tmp_path: Path):
331
+ server = BridgeServer(tmp_path, host="0.0.0.0", port=9999)
332
+ assert server.url == "http://0.0.0.0:9999"
333
+
334
+ def test_direct_dispatch(self, tmp_path: Path):
335
+ server = BridgeServer(tmp_path)
336
+ req = AgentRequest(
337
+ kind=RequestKind.LIST_CAPABILITIES,
338
+ request_id="test-1",
339
+ )
340
+ resp = server.dispatch(req)
341
+ assert resp.success is True
342
+ assert resp.request_id == "test-1"
343
+ assert resp.elapsed_ms >= 0
344
+
345
+ def test_direct_dispatch_validate(self, tmp_path: Path):
346
+ server = BridgeServer(tmp_path)
347
+ req = AgentRequest(
348
+ kind=RequestKind.VALIDATE_CODE,
349
+ params={"code": "print('hello')"},
350
+ )
351
+ resp = server.dispatch(req)
352
+ assert resp.success is True
353
+
354
+ def test_background_start_stop(self, tmp_path: Path):
355
+ """Start, query, and stop a background server."""
356
+ server = BridgeServer(tmp_path, port=0)
357
+ # port=0 will fail because HTTPServer needs a real port, pick a high one
358
+ server = BridgeServer(tmp_path, port=39871)
359
+ server.start_background()
360
+ try:
361
+ time.sleep(0.3) # let the server thread start
362
+ # Health check
363
+ url = f"{server.url}/health"
364
+ req = urllib.request.Request(url)
365
+ with urllib.request.urlopen(req, timeout=2) as resp:
366
+ data = json.loads(resp.read())
367
+ assert data["status"] == "ok"
368
+
369
+ # Capabilities
370
+ url2 = f"{server.url}/"
371
+ req2 = urllib.request.Request(url2)
372
+ with urllib.request.urlopen(req2, timeout=2) as resp2:
373
+ data2 = json.loads(resp2.read())
374
+ assert data2["name"] == "CodexA Bridge"
375
+
376
+ # POST /request — list_capabilities
377
+ post_data = json.dumps({
378
+ "kind": "list_capabilities",
379
+ "params": {},
380
+ "request_id": "live-1",
381
+ }).encode()
382
+ req3 = urllib.request.Request(
383
+ f"{server.url}/request",
384
+ data=post_data,
385
+ headers={"Content-Type": "application/json"},
386
+ )
387
+ with urllib.request.urlopen(req3, timeout=2) as resp3:
388
+ data3 = json.loads(resp3.read())
389
+ assert data3["success"] is True
390
+ assert data3["request_id"] == "live-1"
391
+ finally:
392
+ server.stop()
393
+
394
+ def test_background_post_invalid_json(self, tmp_path: Path):
395
+ server = BridgeServer(tmp_path, port=39872)
396
+ server.start_background()
397
+ try:
398
+ time.sleep(0.3)
399
+ post_data = b"this is not json"
400
+ req = urllib.request.Request(
401
+ f"{server.url}/request",
402
+ data=post_data,
403
+ headers={"Content-Type": "application/json"},
404
+ )
405
+ try:
406
+ urllib.request.urlopen(req, timeout=2)
407
+ assert False, "Should have raised"
408
+ except urllib.error.HTTPError as e:
409
+ assert e.code == 400
410
+ finally:
411
+ server.stop()
412
+
413
+ def test_background_404(self, tmp_path: Path):
414
+ server = BridgeServer(tmp_path, port=39873)
415
+ server.start_background()
416
+ try:
417
+ time.sleep(0.3)
418
+ req = urllib.request.Request(f"{server.url}/nonexistent")
419
+ try:
420
+ urllib.request.urlopen(req, timeout=2)
421
+ assert False, "Should have raised"
422
+ except urllib.error.HTTPError as e:
423
+ assert e.code == 404
424
+ finally:
425
+ server.stop()
426
+
427
+
428
+ # =========================================================================
429
+ # VSCodeBridge tests
430
+ # =========================================================================
431
+
432
+ from semantic_code_intelligence.bridge.vscode import (
433
+ VSCodeBridge,
434
+ generate_extension_manifest,
435
+ _to_diagnostic,
436
+ _to_hover,
437
+ _to_completion_items,
438
+ )
439
+
440
+
441
+ class TestVSCodeHelpers:
442
+ def test_to_diagnostic(self):
443
+ issue = {"severity": "high", "description": "eval usage", "line": 5}
444
+ d = _to_diagnostic(issue)
445
+ assert d["severity"] == 1
446
+ assert d["source"] == "CodexA"
447
+ assert d["range"]["start"]["line"] == 5
448
+
449
+ def test_to_diagnostic_default_severity(self):
450
+ issue = {"description": "minor issue"}
451
+ d = _to_diagnostic(issue)
452
+ assert d["severity"] == 2 # medium default
453
+
454
+ def test_to_hover_full(self):
455
+ ctx = {
456
+ "explanation": "Authenticates a user",
457
+ "type": "function",
458
+ "file": "auth.py",
459
+ "callers": ["login", "signup", "reset"],
460
+ }
461
+ h = _to_hover(ctx)
462
+ value = h["contents"]["value"]
463
+ assert "Authenticates a user" in value
464
+ assert "function" in value
465
+ assert "auth.py" in value
466
+ assert "login" in value
467
+
468
+ def test_to_hover_empty(self):
469
+ h = _to_hover({})
470
+ assert h["contents"]["value"] == ""
471
+
472
+ def test_to_completion_items(self):
473
+ results = [
474
+ {"symbol": "auth_check", "explanation": "checks auth", "snippet": "def auth_check():"},
475
+ {"file": "utils.py"},
476
+ ]
477
+ items = _to_completion_items(results)
478
+ assert len(items) == 2
479
+ assert items[0]["label"] == "auth_check"
480
+ assert items[1]["label"] == "utils.py"
481
+
482
+ def test_to_completion_items_limit(self):
483
+ results = [{"symbol": f"sym_{i}"} for i in range(30)]
484
+ items = _to_completion_items(results)
485
+ assert len(items) == 20 # capped at 20
486
+
487
+
488
+ class TestVSCodeBridge:
489
+ @pytest.fixture
490
+ def bridge(self, tmp_path: Path) -> VSCodeBridge:
491
+ provider = ContextProvider(tmp_path)
492
+ return VSCodeBridge(provider=provider)
493
+
494
+ def test_diagnostics_safe(self, bridge):
495
+ diags = bridge.diagnostics("x = 1")
496
+ assert isinstance(diags, list)
497
+
498
+ def test_diagnostics_unsafe(self, bridge):
499
+ diags = bridge.diagnostics("eval(input())")
500
+ assert len(diags) > 0
501
+ assert diags[0]["source"] == "CodexA"
502
+
503
+ @patch("semantic_code_intelligence.bridge.context_provider.search_codebase")
504
+ def test_completions(self, mock_search, bridge):
505
+ mock_search.return_value = []
506
+ items = bridge.completions("auth")
507
+ assert isinstance(items, list)
508
+
509
+ def test_code_actions_safe(self, bridge):
510
+ actions = bridge.code_actions("x = 1")
511
+ assert isinstance(actions, list)
512
+
513
+ def test_code_actions_unsafe(self, bridge):
514
+ actions = bridge.code_actions("eval(input())")
515
+ assert len(actions) > 0
516
+ assert "CodexA" in actions[0]["title"]
517
+
518
+ def test_hover_not_found(self, bridge):
519
+ with patch.object(bridge.provider, "_ensure_indexed") as mock_idx:
520
+ mock_builder = MagicMock()
521
+ mock_builder.find_symbol.return_value = []
522
+ mock_idx.return_value = mock_builder
523
+ h = bridge.hover("nonexistent")
524
+ # Should still return contents (possibly empty)
525
+ assert "contents" in h
526
+
527
+
528
+ class TestExtensionManifest:
529
+ def test_default_manifest(self):
530
+ m = generate_extension_manifest()
531
+ assert m["name"] == "codexa-bridge"
532
+ assert m["version"] == "0.9.0"
533
+ assert m["engines"]["vscode"] == "^1.85.0"
534
+ assert len(m["contributes"]["commands"]) == 4
535
+
536
+ def test_custom_port(self):
537
+ m = generate_extension_manifest(server_port=8080)
538
+ port_conf = m["contributes"]["configuration"]["properties"]["codexa.bridge.port"]
539
+ assert port_conf["default"] == 8080
540
+
541
+ def test_custom_name(self):
542
+ m = generate_extension_manifest(extension_name="my-ext", display_name="My Ext")
543
+ assert m["name"] == "my-ext"
544
+ assert m["displayName"] == "My Ext"
545
+
546
+ def test_json_serialisable(self):
547
+ m = generate_extension_manifest()
548
+ j = json.dumps(m)
549
+ assert "codexa-bridge" in j
550
+
551
+
552
+ # =========================================================================
553
+ # CLI command tests (serve & context)
554
+ # =========================================================================
555
+
556
+ from click.testing import CliRunner
557
+
558
+
559
+ class TestServeCLI:
560
+ def test_serve_help(self):
561
+ from semantic_code_intelligence.cli.commands.serve_cmd import serve_cmd
562
+ runner = CliRunner()
563
+ result = runner.invoke(serve_cmd, ["--help"])
564
+ assert result.exit_code == 0
565
+ assert "bridge server" in result.output.lower()
566
+
567
+ def test_serve_options(self):
568
+ from semantic_code_intelligence.cli.commands.serve_cmd import serve_cmd
569
+ runner = CliRunner()
570
+ result = runner.invoke(serve_cmd, ["--help"])
571
+ assert "--host" in result.output
572
+ assert "--port" in result.output
573
+ assert "--path" in result.output
574
+
575
+
576
+ class TestContextCLI:
577
+ def test_context_help(self):
578
+ from semantic_code_intelligence.cli.commands.context_cmd import context_cmd
579
+ runner = CliRunner()
580
+ result = runner.invoke(context_cmd, ["--help"])
581
+ assert result.exit_code == 0
582
+ assert "query" in result.output
583
+ assert "symbol" in result.output
584
+ assert "file" in result.output
585
+ assert "repo" in result.output
586
+
587
+ def test_context_options(self):
588
+ from semantic_code_intelligence.cli.commands.context_cmd import context_cmd
589
+ runner = CliRunner()
590
+ result = runner.invoke(context_cmd, ["--help"])
591
+ assert "--top-k" in result.output
592
+ assert "--json" in result.output
593
+
594
+ @patch("semantic_code_intelligence.bridge.context_provider.search_codebase")
595
+ def test_context_query_json(self, mock_search, tmp_path):
596
+ mock_search.return_value = []
597
+ from semantic_code_intelligence.cli.commands.context_cmd import context_cmd
598
+ runner = CliRunner()
599
+ result = runner.invoke(
600
+ context_cmd,
601
+ ["query", "test", "--json", "--path", str(tmp_path)],
602
+ )
603
+ assert result.exit_code == 0
604
+ data = json.loads(result.output)
605
+ assert data["query"] == "test"
606
+
607
+ def test_context_repo_json(self, tmp_path):
608
+ from semantic_code_intelligence.cli.commands.context_cmd import context_cmd
609
+ runner = CliRunner()
610
+ with patch(
611
+ "semantic_code_intelligence.bridge.context_provider.ContextProvider.context_for_repo"
612
+ ) as mock_repo:
613
+ mock_repo.return_value = {"total_files": 5}
614
+ result = runner.invoke(
615
+ context_cmd,
616
+ ["repo", "--json", "--path", str(tmp_path)],
617
+ )
618
+ assert result.exit_code == 0
619
+ data = json.loads(result.output)
620
+ assert data["total_files"] == 5
621
+
622
+
623
+ # =========================================================================
624
+ # Bridge __init__ imports
625
+ # =========================================================================
626
+
627
+
628
+ class TestBridgeImports:
629
+ def test_all_exports(self):
630
+ from semantic_code_intelligence.bridge import (
631
+ AgentRequest,
632
+ AgentResponse,
633
+ BridgeCapabilities,
634
+ ContextProvider,
635
+ BridgeServer,
636
+ VSCodeBridge,
637
+ )
638
+ assert AgentRequest is not None
639
+ assert AgentResponse is not None
640
+ assert BridgeCapabilities is not None
641
+ assert ContextProvider is not None
642
+ assert BridgeServer is not None
643
+ assert VSCodeBridge is not None