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,348 @@
1
+ """Lightweight HTTP bridge server — exposes CodexA tools over JSON/HTTP.
2
+
3
+ Uses only the Python standard library (``http.server``) so there are zero
4
+ additional dependencies. The server is designed to be started via
5
+ ``codexa serve`` and consumed by IDE extensions, editor plugins, or any
6
+ HTTP client.
7
+
8
+ Endpoints
9
+ ---------
10
+ GET / → capabilities manifest
11
+ POST /request → handle an AgentRequest, return an AgentResponse
12
+ GET /health → simple health check (``{"status": "ok"}``)
13
+
14
+ All request/response bodies are JSON.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import time
21
+ import threading
22
+ from http.server import HTTPServer, BaseHTTPRequestHandler
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ from semantic_code_intelligence.bridge.context_provider import ContextProvider
27
+ from semantic_code_intelligence.bridge.protocol import (
28
+ AgentRequest,
29
+ AgentResponse,
30
+ BridgeCapabilities,
31
+ RequestKind,
32
+ )
33
+ from semantic_code_intelligence.tools.executor import ToolExecutor
34
+ from semantic_code_intelligence.tools.protocol import ToolInvocation
35
+ from semantic_code_intelligence.utils.logging import get_logger
36
+
37
+ logger = get_logger("bridge.server")
38
+
39
+
40
+ class _BridgeHandler(BaseHTTPRequestHandler):
41
+ """HTTP request handler for the bridge server."""
42
+
43
+ # Assigned by BridgeServer before the HTTPServer starts.
44
+ context_provider: ContextProvider
45
+ capabilities: BridgeCapabilities
46
+ tool_executor: ToolExecutor | None = None
47
+
48
+ # Silence default stderr logging — we use our own logger.
49
+ def log_message(self, fmt: str, *args: Any) -> None: # noqa: D102
50
+ logger.debug(fmt, *args)
51
+
52
+ # --- routing ---
53
+
54
+ def do_GET(self) -> None: # noqa: N802
55
+ if self.path == "/" or self.path == "/capabilities":
56
+ self._json_response(200, self.capabilities.to_dict())
57
+ elif self.path == "/health":
58
+ self._json_response(200, {"status": "ok"})
59
+ elif self.path == "/tools/list":
60
+ self._handle_list_tools()
61
+ elif self.path == "/tools/stream":
62
+ self._handle_tool_stream()
63
+ else:
64
+ self._json_response(404, {"error": "Not found"})
65
+
66
+ def do_POST(self) -> None: # noqa: N802
67
+ if self.path == "/request":
68
+ self._handle_request()
69
+ elif self.path == "/tools/invoke":
70
+ self._handle_tool_invoke()
71
+ else:
72
+ self._json_response(404, {"error": "Not found"})
73
+
74
+ # --- core dispatch ---
75
+
76
+ def _handle_request(self) -> None:
77
+ content_length = int(self.headers.get("Content-Length", 0))
78
+ if content_length == 0:
79
+ self._json_response(400, {"error": "Empty body"})
80
+ return
81
+
82
+ raw = self.rfile.read(content_length)
83
+ try:
84
+ req = AgentRequest.from_json(raw.decode("utf-8"))
85
+ except (json.JSONDecodeError, KeyError, TypeError) as exc:
86
+ self._json_response(400, {"error": f"Invalid JSON: {exc}"})
87
+ return
88
+
89
+ start = time.monotonic()
90
+ resp = _dispatch(req, self.context_provider, self.capabilities, self.tool_executor)
91
+ resp.elapsed_ms = (time.monotonic() - start) * 1000
92
+ resp.request_id = req.request_id
93
+
94
+ status = 200 if resp.success else 422
95
+ self._json_response(status, resp.to_dict())
96
+
97
+ # --- tool endpoints (Phase 19) ---
98
+
99
+ def _handle_list_tools(self) -> None:
100
+ """GET /tools/list — return schemas of all available tools."""
101
+ if self.tool_executor is None:
102
+ self._json_response(503, {"error": "Tool executor not initialized"})
103
+ return
104
+ tools = self.tool_executor.available_tools
105
+ self._json_response(200, {"tools": tools, "count": len(tools)})
106
+
107
+ def _handle_tool_invoke(self) -> None:
108
+ """POST /tools/invoke — execute a ToolInvocation and return result."""
109
+ if self.tool_executor is None:
110
+ self._json_response(503, {"error": "Tool executor not initialized"})
111
+ return
112
+
113
+ content_length = int(self.headers.get("Content-Length", 0))
114
+ if content_length == 0:
115
+ self._json_response(400, {"error": "Empty body"})
116
+ return
117
+
118
+ raw = self.rfile.read(content_length)
119
+ try:
120
+ data = json.loads(raw.decode("utf-8"))
121
+ invocation = ToolInvocation.from_dict(data)
122
+ except (json.JSONDecodeError, KeyError, TypeError) as exc:
123
+ self._json_response(400, {"error": f"Invalid JSON: {exc}"})
124
+ return
125
+
126
+ result = self.tool_executor.execute(invocation)
127
+ status = 200 if result.success else 422
128
+ self._json_response(status, result.to_dict())
129
+
130
+ def _handle_tool_stream(self) -> None:
131
+ """GET /tools/stream — SSE endpoint for tool execution events.
132
+
133
+ Sends a heartbeat followed by tool list in SSE format.
134
+ Agents can use this to discover tools via a streaming connection.
135
+ """
136
+ if self.tool_executor is None:
137
+ self._json_response(503, {"error": "Tool executor not initialized"})
138
+ return
139
+
140
+ self.send_response(200)
141
+ self.send_header("Content-Type", "text/event-stream; charset=utf-8")
142
+ self.send_header("Cache-Control", "no-cache")
143
+ self.send_header("Connection", "keep-alive")
144
+ self.send_header("Access-Control-Allow-Origin", "*")
145
+ self.end_headers()
146
+
147
+ import time as _time
148
+
149
+ # Send discovery event
150
+ tools = self.tool_executor.available_tools
151
+ discovery_event = {
152
+ "kind": "tool_discovery",
153
+ "content": "",
154
+ "metadata": {"tools": [t["name"] for t in tools], "count": len(tools)},
155
+ }
156
+ self.wfile.write(f"data: {json.dumps(discovery_event)}\n\n".encode("utf-8"))
157
+ self.wfile.flush()
158
+
159
+ # Send heartbeat and close
160
+ heartbeat = {"kind": "heartbeat", "content": "", "metadata": {"timestamp": _time.time()}}
161
+ self.wfile.write(f"data: {json.dumps(heartbeat)}\n\n".encode("utf-8"))
162
+ self.wfile.flush()
163
+
164
+ # --- helpers ---
165
+
166
+ def _json_response(self, status: int, body: dict[str, Any]) -> None:
167
+ payload = json.dumps(body, indent=2).encode("utf-8")
168
+ self.send_response(status)
169
+ self.send_header("Content-Type", "application/json; charset=utf-8")
170
+ self.send_header("Content-Length", str(len(payload)))
171
+ # CORS headers — allow local IDE extensions to call the bridge.
172
+ self.send_header("Access-Control-Allow-Origin", "*")
173
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
174
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
175
+ self.end_headers()
176
+ self.wfile.write(payload)
177
+
178
+ def do_OPTIONS(self) -> None: # noqa: N802
179
+ """Handle CORS preflight."""
180
+ self.send_response(204)
181
+ self.send_header("Access-Control-Allow-Origin", "*")
182
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
183
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
184
+ self.end_headers()
185
+
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # Dispatcher
189
+ # ---------------------------------------------------------------------------
190
+
191
+ def _dispatch(
192
+ req: AgentRequest,
193
+ provider: ContextProvider,
194
+ capabilities: BridgeCapabilities,
195
+ executor: ToolExecutor | None = None,
196
+ ) -> AgentResponse:
197
+ """Route an AgentRequest to the appropriate ContextProvider method."""
198
+ kind = req.kind
199
+ params = req.params
200
+
201
+ try:
202
+ if kind == RequestKind.SEMANTIC_SEARCH:
203
+ data = provider.context_for_query(
204
+ query=params.get("query", ""),
205
+ top_k=params.get("top_k", 5),
206
+ threshold=params.get("threshold", 0.2),
207
+ include_repo_summary=params.get("include_repo_summary", False),
208
+ )
209
+ elif kind == RequestKind.EXPLAIN_SYMBOL:
210
+ data = provider.context_for_symbol(
211
+ symbol_name=params.get("symbol_name", ""),
212
+ file_path=params.get("file_path"),
213
+ include_call_graph=params.get("include_call_graph", True),
214
+ include_dependencies=params.get("include_dependencies", True),
215
+ )
216
+ elif kind == RequestKind.EXPLAIN_FILE:
217
+ data = provider.context_for_file(file_path=params.get("file_path", ""))
218
+ elif kind == RequestKind.GET_CONTEXT:
219
+ data = provider.context_for_symbol(
220
+ symbol_name=params.get("symbol_name", ""),
221
+ )
222
+ elif kind == RequestKind.GET_DEPENDENCIES:
223
+ data = provider.get_dependencies(file_path=params.get("file_path", ""))
224
+ elif kind == RequestKind.GET_CALL_GRAPH:
225
+ data = provider.get_call_graph(symbol_name=params.get("symbol_name", ""))
226
+ elif kind == RequestKind.SUMMARIZE_REPO:
227
+ data = provider.context_for_repo()
228
+ elif kind == RequestKind.FIND_REFERENCES:
229
+ data = provider.find_references(symbol_name=params.get("symbol_name", ""))
230
+ elif kind == RequestKind.VALIDATE_CODE:
231
+ data = provider.validate_code(code=params.get("code", ""))
232
+ elif kind == RequestKind.LIST_CAPABILITIES:
233
+ data = capabilities.to_dict()
234
+ elif kind == RequestKind.INVOKE_TOOL:
235
+ # Delegate to ToolExecutor if available
236
+ tool_name = params.get("tool_name", "")
237
+ arguments = params.get("arguments", {})
238
+ invocation = ToolInvocation(tool_name=tool_name, arguments=arguments)
239
+ _executor = executor or getattr(_BridgeHandler, "tool_executor", None)
240
+ if _executor is None:
241
+ return AgentResponse(success=False, error="Tool executor not initialized")
242
+ result = _executor.execute(invocation)
243
+ data = result.to_dict()
244
+ if not result.success:
245
+ return AgentResponse(success=False, error=data.get("error", {}).get("error_message", "Tool execution failed"))
246
+ elif kind == RequestKind.LIST_TOOLS:
247
+ _executor = executor or getattr(_BridgeHandler, "tool_executor", None)
248
+ if _executor is None:
249
+ return AgentResponse(success=False, error="Tool executor not initialized")
250
+ data = {"tools": _executor.available_tools, "count": len(_executor.available_tools)}
251
+ else:
252
+ return AgentResponse(success=False, error=f"Unknown request kind: {kind}")
253
+
254
+ return AgentResponse(success=True, data=data)
255
+
256
+ except Exception as exc:
257
+ logger.exception("Error handling request %s", kind)
258
+ return AgentResponse(success=False, error=str(exc))
259
+
260
+
261
+ # ---------------------------------------------------------------------------
262
+ # Public server class
263
+ # ---------------------------------------------------------------------------
264
+
265
+ class BridgeServer:
266
+ """Lightweight HTTP server that exposes CodexA tools for external agents.
267
+
268
+ Usage::
269
+
270
+ server = BridgeServer(Path("."), host="127.0.0.1", port=24842)
271
+ server.start() # blocks
272
+ # or
273
+ server.start_background() # runs in a daemon thread
274
+ server.stop()
275
+ """
276
+
277
+ DEFAULT_PORT = 24842
278
+
279
+ def __init__(
280
+ self,
281
+ project_root: Path,
282
+ host: str = "127.0.0.1",
283
+ port: int = DEFAULT_PORT,
284
+ ) -> None:
285
+ self._root = project_root.resolve()
286
+ self._host = host
287
+ self._port = port
288
+ self._httpd: HTTPServer | None = None
289
+ self._thread: threading.Thread | None = None
290
+
291
+ self._provider = ContextProvider(self._root)
292
+ self._capabilities = BridgeCapabilities()
293
+ self._executor = ToolExecutor(self._root)
294
+
295
+ # Populate capabilities with tool schemas
296
+ self._capabilities.tools = self._executor.available_tools
297
+
298
+ @property
299
+ def url(self) -> str:
300
+ return f"http://{self._host}:{self._port}"
301
+
302
+ def _make_server(self) -> HTTPServer:
303
+ # Inject dependencies into the handler class.
304
+ _BridgeHandler.context_provider = self._provider
305
+ _BridgeHandler.capabilities = self._capabilities
306
+ _BridgeHandler.tool_executor = self._executor
307
+ httpd = HTTPServer((self._host, self._port), _BridgeHandler)
308
+ return httpd
309
+
310
+ def start(self) -> None:
311
+ """Start the server (blocking)."""
312
+ self._httpd = self._make_server()
313
+ logger.info("Bridge server listening on %s", self.url)
314
+ try:
315
+ self._httpd.serve_forever()
316
+ except KeyboardInterrupt:
317
+ pass
318
+ finally:
319
+ self._httpd.server_close()
320
+
321
+ def start_background(self) -> None:
322
+ """Start the server in a background daemon thread."""
323
+ self._httpd = self._make_server()
324
+ self._thread = threading.Thread(target=self._httpd.serve_forever, daemon=True)
325
+ self._thread.start()
326
+ logger.info("Bridge server started in background on %s", self.url)
327
+
328
+ def stop(self) -> None:
329
+ """Shut down the background server."""
330
+ if self._httpd:
331
+ self._httpd.shutdown()
332
+ self._httpd.server_close()
333
+ self._httpd = None
334
+ if self._thread:
335
+ self._thread.join(timeout=5)
336
+ self._thread = None
337
+
338
+ def dispatch(self, request: AgentRequest) -> AgentResponse:
339
+ """Dispatch a request directly (no HTTP round-trip).
340
+
341
+ Useful for in-process testing or when the server is embedded
342
+ inside a larger application.
343
+ """
344
+ start = time.monotonic()
345
+ resp = _dispatch(request, self._provider, self._capabilities, self._executor)
346
+ resp.elapsed_ms = (time.monotonic() - start) * 1000
347
+ resp.request_id = request.request_id
348
+ return resp
@@ -0,0 +1,271 @@
1
+ """VSCode extension bridge — helpers for integrating with VS Code.
2
+
3
+ This module provides:
4
+
5
+ * **VSCodeBridge** — high-level façade that packages CodexA results into
6
+ shapes understood by VS Code extensions (completions, diagnostics, hovers,
7
+ code-actions, etc.).
8
+ * **Manifest helpers** — generate ``package.json`` fragments for a companion
9
+ VS Code extension that talks to the CodexA bridge server.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from semantic_code_intelligence.bridge.context_provider import ContextProvider
20
+ from semantic_code_intelligence.bridge.protocol import (
21
+ AgentRequest,
22
+ AgentResponse,
23
+ BridgeCapabilities,
24
+ RequestKind,
25
+ )
26
+ from semantic_code_intelligence.utils.logging import get_logger
27
+
28
+ logger = get_logger("bridge.vscode")
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Format adapters — translate ContextProvider dicts into VSCode-friendly shapes
33
+ # ---------------------------------------------------------------------------
34
+
35
+ def _to_diagnostic(issue: dict[str, Any]) -> dict[str, Any]:
36
+ """Convert a SafetyValidator issue dict into a VS Code Diagnostic shape."""
37
+ severity_map = {"critical": 1, "high": 1, "medium": 2, "low": 3}
38
+ return {
39
+ "severity": severity_map.get(issue.get("severity", "medium"), 2),
40
+ "message": issue.get("description", issue.get("pattern", "")),
41
+ "source": "CodexA",
42
+ "range": {
43
+ "start": {"line": issue.get("line", 0), "character": 0},
44
+ "end": {"line": issue.get("line", 0), "character": 999},
45
+ },
46
+ }
47
+
48
+
49
+ def _to_hover(context: dict[str, Any]) -> dict[str, Any]:
50
+ """Package symbol context as a VS Code hover tooltip payload."""
51
+ parts: list[str] = []
52
+ if context.get("explanation"):
53
+ parts.append(context["explanation"])
54
+ if context.get("type"):
55
+ parts.append(f"**Type:** `{context['type']}`")
56
+ if context.get("file"):
57
+ parts.append(f"Defined in `{context['file']}`")
58
+ if context.get("callers"):
59
+ callers = ", ".join(f"`{c}`" for c in context["callers"][:5])
60
+ parts.append(f"**Callers:** {callers}")
61
+ return {"contents": {"kind": "markdown", "value": "\n\n".join(parts)}}
62
+
63
+
64
+ def _to_completion_items(results: list[dict[str, Any]]) -> list[dict[str, Any]]:
65
+ """Translate semantic search results into VS Code CompletionItem shapes."""
66
+ items: list[dict[str, Any]] = []
67
+ for idx, r in enumerate(results[:20]):
68
+ items.append({
69
+ "label": r.get("symbol", r.get("file", f"result_{idx}")),
70
+ "kind": 1, # Text
71
+ "detail": r.get("explanation", ""),
72
+ "documentation": {
73
+ "kind": "markdown",
74
+ "value": r.get("snippet", ""),
75
+ },
76
+ "sortText": f"{idx:04d}",
77
+ })
78
+ return items
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # VSCodeBridge
83
+ # ---------------------------------------------------------------------------
84
+
85
+ @dataclass
86
+ class VSCodeBridge:
87
+ """Façade that adapts ContextProvider output to VS Code extension shapes.
88
+
89
+ This is *not* a running server — it's a formatting layer. Pair it with
90
+ ``BridgeServer`` for HTTP access, or call methods directly from a
91
+ VS Code extension host written in Python.
92
+ """
93
+
94
+ provider: ContextProvider
95
+
96
+ # --- public methods ---------------------------------------------------
97
+
98
+ def hover(self, symbol_name: str, file_path: str | None = None) -> dict[str, Any]:
99
+ """Return VS Code hover content for *symbol_name*."""
100
+ ctx = self.provider.context_for_symbol(
101
+ symbol_name=symbol_name, file_path=file_path,
102
+ )
103
+ return _to_hover(ctx)
104
+
105
+ def diagnostics(self, code: str) -> list[dict[str, Any]]:
106
+ """Run safety/validation and return VS Code diagnostics."""
107
+ report = self.provider.validate_code(code)
108
+ diagnostics: list[dict[str, Any]] = []
109
+ for issue in report.get("issues", []):
110
+ diagnostics.append(_to_diagnostic(issue))
111
+ return diagnostics
112
+
113
+ def completions(self, query: str, top_k: int = 10) -> list[dict[str, Any]]:
114
+ """Return completion items from semantic search results."""
115
+ ctx = self.provider.context_for_query(query=query, top_k=top_k)
116
+ return _to_completion_items(ctx.get("results", []))
117
+
118
+ def code_actions(self, code: str) -> list[dict[str, Any]]:
119
+ """Return VS Code code-actions derived from safety diagnostics."""
120
+ report = self.provider.validate_code(code)
121
+ actions: list[dict[str, Any]] = []
122
+ for issue in report.get("issues", []):
123
+ actions.append({
124
+ "title": f"CodexA: {issue.get('description', 'Fix issue')}",
125
+ "kind": "quickfix",
126
+ "diagnostics": [_to_diagnostic(issue)],
127
+ })
128
+ return actions
129
+
130
+ def file_summary(self, file_path: str) -> dict[str, Any]:
131
+ """Return a summary of *file_path* suitable for an editor tooltip."""
132
+ ctx = self.provider.context_for_file(file_path)
133
+ return {
134
+ "contents": {
135
+ "kind": "markdown",
136
+ "value": _format_file_summary(ctx),
137
+ }
138
+ }
139
+
140
+
141
+ def _format_file_summary(ctx: dict[str, Any]) -> str:
142
+ lines: list[str] = []
143
+ if ctx.get("explanation"):
144
+ lines.append(ctx["explanation"])
145
+ symbols = ctx.get("symbols", [])
146
+ if symbols:
147
+ sym_list = ", ".join(f"`{s}`" for s in symbols[:10])
148
+ lines.append(f"**Symbols:** {sym_list}")
149
+ deps = ctx.get("dependencies", [])
150
+ if deps:
151
+ dep_list = ", ".join(f"`{d}`" for d in deps[:10])
152
+ lines.append(f"**Dependencies:** {dep_list}")
153
+ return "\n\n".join(lines) if lines else "No information available."
154
+
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Extension manifest helpers
158
+ # ---------------------------------------------------------------------------
159
+
160
+ def generate_extension_manifest(
161
+ server_port: int = 24842,
162
+ extension_name: str = "codexa-bridge",
163
+ display_name: str = "CodexA Bridge",
164
+ version: str = "0.9.0",
165
+ ) -> dict[str, Any]:
166
+ """Generate a ``package.json`` fragment for a companion VS Code extension."""
167
+ return {
168
+ "name": extension_name,
169
+ "displayName": display_name,
170
+ "description": "Bridge to CodexA semantic code intelligence",
171
+ "version": version,
172
+ "publisher": "codexa",
173
+ "engines": {"vscode": "^1.85.0"},
174
+ "categories": ["Other"],
175
+ "activationEvents": ["onStartupFinished"],
176
+ "main": "./out/extension.js",
177
+ "contributes": {
178
+ "commands": [
179
+ {
180
+ "command": "codexa.explainSymbol",
181
+ "title": "CodexA: Explain Symbol",
182
+ },
183
+ {
184
+ "command": "codexa.searchContext",
185
+ "title": "CodexA: Search Context",
186
+ },
187
+ {
188
+ "command": "codexa.validateCode",
189
+ "title": "CodexA: Validate Code",
190
+ },
191
+ {
192
+ "command": "codexa.fileSummary",
193
+ "title": "CodexA: File Summary",
194
+ },
195
+ ],
196
+ "configuration": {
197
+ "title": "CodexA Bridge",
198
+ "properties": {
199
+ "codexa.bridge.port": {
200
+ "type": "number",
201
+ "default": server_port,
202
+ "description": "Port where the CodexA bridge server is running.",
203
+ },
204
+ "codexa.bridge.host": {
205
+ "type": "string",
206
+ "default": "127.0.0.1",
207
+ "description": "Host where the CodexA bridge server is running.",
208
+ },
209
+ },
210
+ },
211
+ },
212
+ }
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Streaming context (Phase 12)
217
+ # ---------------------------------------------------------------------------
218
+
219
+ @dataclass
220
+ class StreamChunk:
221
+ """A single chunk in a streaming response from VSCode bridge."""
222
+
223
+ kind: str # "token", "context", "done", "error"
224
+ content: str = ""
225
+ metadata: dict[str, Any] = field(default_factory=dict)
226
+
227
+ def to_dict(self) -> dict[str, Any]:
228
+ return {"kind": self.kind, "content": self.content, "metadata": self.metadata}
229
+
230
+ def to_sse(self) -> str:
231
+ """Format as a Server-Sent Event line."""
232
+ return f"data: {json.dumps(self.to_dict())}\n\n"
233
+
234
+
235
+ def build_streaming_context(
236
+ query: str,
237
+ provider: ContextProvider,
238
+ *,
239
+ top_k: int = 5,
240
+ ) -> list[StreamChunk]:
241
+ """Build a sequence of StreamChunks suitable for SSE delivery.
242
+
243
+ Produces an initial context chunk (with search results) followed by a
244
+ done chunk. This can be extended to interleave LLM token chunks when
245
+ streaming LLM responses are available.
246
+ """
247
+ chunks: list[StreamChunk] = []
248
+
249
+ # 1. Emit context from semantic search
250
+ ctx = provider.context_for_query(query=query, top_k=top_k)
251
+ results = ctx.get("results", [])
252
+ chunks.append(StreamChunk(
253
+ kind="context",
254
+ content=f"Found {len(results)} relevant snippets.",
255
+ metadata={"result_count": len(results), "query": query},
256
+ ))
257
+
258
+ # 2. Emit each search result as a token-shaped chunk
259
+ for r in results:
260
+ chunks.append(StreamChunk(
261
+ kind="token",
262
+ content=r.get("content", r.get("snippet", "")),
263
+ metadata={
264
+ "file_path": r.get("file_path", ""),
265
+ "score": r.get("score", 0),
266
+ },
267
+ ))
268
+
269
+ # 3. Done sentinel
270
+ chunks.append(StreamChunk(kind="done", content="", metadata={}))
271
+ return chunks
@@ -0,0 +1,13 @@
1
+ """CI/CD integration and contribution safety pipeline.
2
+
3
+ Submodules
4
+ ----------
5
+ - ``quality``: Code quality analyzers (dead code, duplicates, complexity, security).
6
+ - ``metrics``: Maintainability index, metric snapshots, trend tracking, quality gates.
7
+ - ``pr``: Pull request intelligence (change summary, impact, risk scoring).
8
+ - ``hooks``: Pre-commit validation hook support.
9
+ - ``templates``: GitHub Actions workflow template generation.
10
+ - ``hotspots``: Hotspot detection engine (complexity, duplication, fan-in/out, churn).
11
+ - ``impact``: Impact analysis engine (blast radius prediction via call graph / deps).
12
+ - ``trace``: Symbol trace tool (upstream callers, downstream callees, cross-file paths).
13
+ """