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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- 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
|
+
"""
|