codeui-python 0.1.0__py3-none-any.whl → 0.1.1__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.
- codeui/__init__.py +1 -1
- codeui/agent/context.py +19 -3
- codeui/analysis/dead_code.py +13 -2
- codeui/analysis/undefined.py +70 -0
- codeui/analysis/unresolved_import.py +34 -4
- codeui/cli/main.py +101 -7
- codeui/core/cache.py +54 -0
- codeui/core/graph.py +233 -3
- codeui/core/override.py +1 -0
- codeui/core/resolver.py +51 -37
- codeui/errors.py +10 -0
- codeui/lang/base.py +5 -2
- codeui/lang/cpp.py +271 -0
- codeui/lang/csharp.py +262 -0
- codeui/lang/dart.py +278 -0
- codeui/lang/generic.py +6 -4
- codeui/lang/go.py +5 -2
- codeui/lang/java.py +320 -0
- codeui/lang/kotlin.py +303 -0
- codeui/lang/php.py +275 -0
- codeui/lang/python.py +49 -7
- codeui/lang/registry.py +22 -2
- codeui/lang/ruby.py +270 -0
- codeui/lang/rust.py +5 -2
- codeui/lang/scala.py +290 -0
- codeui/lang/swift.py +276 -0
- codeui/lang/ts.py +99 -8
- codeui/report/sarif_emitter.py +2 -1
- codeui/server/server.py +156 -85
- codeui/server/static/css/styles.css +1609 -0
- codeui/server/static/index.html +199 -0
- codeui/server/static/js/api.js +485 -0
- codeui/server/static/js/app.js +1044 -0
- codeui/server/static/js/graph_canvas.js +1485 -0
- codeui/server/static/js/minimap.js +383 -0
- codeui/server/static/js/sidebar.js +268 -0
- codeui/server/static/js/state.js +83 -0
- codeui/server/static/js/subgraph_window.js +1208 -0
- codeui/server/static/js/theme.js +332 -0
- codeui/tracer/__init__.py +4 -1
- codeui/tracer/tracer.py +109 -118
- codeui_python-0.1.1.dist-info/METADATA +171 -0
- codeui_python-0.1.1.dist-info/RECORD +71 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/licenses/LICENSE +1 -1
- codeui_python-0.1.0.dist-info/METADATA +0 -191
- codeui_python-0.1.0.dist-info/RECORD +0 -52
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/WHEEL +0 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/entry_points.txt +0 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/top_level.txt +0 -0
codeui/server/server.py
CHANGED
|
@@ -158,85 +158,21 @@ class CodeUIHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
158
158
|
|
|
159
159
|
if path == "/api/v1/subgraph":
|
|
160
160
|
file_id = query.get("file", [""])[0] or query.get("path", [""])[0]
|
|
161
|
-
symbol_id = query.get("symbol_id", [""])[0]
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if not file_id and symbol_id:
|
|
165
|
-
file_id = symbol_id
|
|
166
|
-
if "::" in file_id:
|
|
167
|
-
if not symbol_id:
|
|
168
|
-
symbol_id = file_id
|
|
169
|
-
file_id = file_id.split("::")[0]
|
|
170
|
-
if file_id.startswith("module::"):
|
|
171
|
-
file_id = file_id[len("module::"):]
|
|
172
|
-
if not file_id:
|
|
161
|
+
symbol_id = query.get("symbol_id", [""])[0] or query.get("symbol", [""])[0]
|
|
162
|
+
target = file_id or symbol_id
|
|
163
|
+
if not target:
|
|
173
164
|
self._send_error(400, "Parameter 'file' or 'symbol_id' is required")
|
|
174
165
|
return
|
|
175
166
|
try:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
ext_outgoing = []
|
|
186
|
-
for s_id in sym_ids:
|
|
187
|
-
for e in (self.graph.get_outgoing_edges(s_id) if self.graph else []):
|
|
188
|
-
if e.target_id not in sym_ids:
|
|
189
|
-
ext_outgoing.append(e.to_dict())
|
|
190
|
-
ext_incoming = []
|
|
191
|
-
for s_id in sym_ids:
|
|
192
|
-
for e in (self.graph.get_incoming_edges(s_id) if self.graph else []):
|
|
193
|
-
if e.source_id not in sym_ids:
|
|
194
|
-
ext_incoming.append(e.to_dict())
|
|
195
|
-
ext_edges = ext_outgoing + ext_incoming
|
|
196
|
-
related_symbols = {}
|
|
197
|
-
for edge in ext_edges:
|
|
198
|
-
for endpoint_id in (edge["source_id"], edge["target_id"]):
|
|
199
|
-
if endpoint_id not in sym_ids and endpoint_id not in related_symbols:
|
|
200
|
-
if self.graph and self.graph.has_symbol(endpoint_id):
|
|
201
|
-
other_sym = self.graph.get_symbol(endpoint_id)
|
|
202
|
-
other_file = other_sym.location.file_id if other_sym.location else ""
|
|
203
|
-
other_cls = self.graph.get_file_classification(other_file) if other_file else None
|
|
204
|
-
related_symbols[endpoint_id] = {
|
|
205
|
-
"id": other_sym.id,
|
|
206
|
-
"name": other_sym.name,
|
|
207
|
-
"qualified_name": other_sym.qualified_name,
|
|
208
|
-
"kind": str(other_sym.kind),
|
|
209
|
-
"file_id": other_file,
|
|
210
|
-
"layer": str(other_cls.layer) if other_cls else "shared",
|
|
211
|
-
"feature": other_cls.feature if other_cls else "external",
|
|
212
|
-
"is_external": True,
|
|
213
|
-
"location": other_sym.location.to_dict() if other_sym.location else None,
|
|
214
|
-
"signature": other_sym.signature,
|
|
215
|
-
}
|
|
216
|
-
else:
|
|
217
|
-
raw_clean = endpoint_id.replace("module::", "")
|
|
218
|
-
other_file = raw_clean.split("::")[0] if "::" in raw_clean else raw_clean
|
|
219
|
-
other_name = raw_clean.split("::")[1] if "::" in raw_clean else (other_file.split("/")[-1] if "/" in other_file else other_file)
|
|
220
|
-
other_cls = self.graph.get_file_classification(other_file) if (self.graph and other_file) else None
|
|
221
|
-
related_symbols[endpoint_id] = {
|
|
222
|
-
"id": endpoint_id,
|
|
223
|
-
"name": other_name,
|
|
224
|
-
"qualified_name": endpoint_id,
|
|
225
|
-
"kind": "module" if endpoint_id.startswith("module::") else "symbol",
|
|
226
|
-
"file_id": other_file,
|
|
227
|
-
"layer": str(other_cls.layer) if other_cls else "shared",
|
|
228
|
-
"feature": other_cls.feature if other_cls else "external",
|
|
229
|
-
"is_external": True,
|
|
230
|
-
"location": {"file_id": other_file, "start_line": 1, "end_line": 1} if other_file else None,
|
|
231
|
-
"signature": other_name,
|
|
232
|
-
}
|
|
233
|
-
self._send_json({
|
|
234
|
-
"file_id": file_id,
|
|
235
|
-
"classification": cls.to_dict(),
|
|
236
|
-
"symbols": [s.to_dict() for s in symbols],
|
|
237
|
-
"related_symbols": list(related_symbols.values()),
|
|
238
|
-
"edges": intra_edges + ext_outgoing + ext_incoming,
|
|
239
|
-
})
|
|
167
|
+
if self.graph:
|
|
168
|
+
try:
|
|
169
|
+
data = self.graph.get_file_subgraph(target)
|
|
170
|
+
self._send_json(data)
|
|
171
|
+
return
|
|
172
|
+
except Exception as err:
|
|
173
|
+
self._send_error(404, str(err))
|
|
174
|
+
return
|
|
175
|
+
self._send_error(404, f"Graph not loaded")
|
|
240
176
|
except Exception as e:
|
|
241
177
|
self._send_error(400, str(e))
|
|
242
178
|
return
|
|
@@ -247,15 +183,73 @@ class CodeUIHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
247
183
|
return
|
|
248
184
|
|
|
249
185
|
if path == "/api/v1/file":
|
|
250
|
-
rel_path = query.get("path", [""])[0]
|
|
186
|
+
rel_path = query.get("path", [""])[0] or query.get("file", [""])[0]
|
|
187
|
+
symbol_id = query.get("symbol_id", [""])[0] or query.get("symbol", [""])[0]
|
|
188
|
+
if "::" in rel_path and not symbol_id:
|
|
189
|
+
symbol_id = rel_path
|
|
251
190
|
if "::" in rel_path:
|
|
252
191
|
rel_path = rel_path.split("::")[0]
|
|
253
192
|
if rel_path.startswith("module::"):
|
|
254
193
|
rel_path = rel_path[len("module::"):]
|
|
194
|
+
if symbol_id.startswith("module::"):
|
|
195
|
+
symbol_id = symbol_id[len("module::"):]
|
|
255
196
|
try:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
197
|
+
target_sym = None
|
|
198
|
+
if self.graph:
|
|
199
|
+
if symbol_id and self.graph.has_symbol(symbol_id):
|
|
200
|
+
target_sym = self.graph.get_symbol(symbol_id)
|
|
201
|
+
elif self.graph.has_symbol(rel_path):
|
|
202
|
+
target_sym = self.graph.get_symbol(rel_path)
|
|
203
|
+
elif symbol_id:
|
|
204
|
+
sym_name = symbol_id.split("::")[-1].split(".")[-1]
|
|
205
|
+
for s in self.graph.get_all_symbols():
|
|
206
|
+
if s.id == symbol_id or s.name == sym_name or s.qualified_name == symbol_id or s.id.endswith("::" + sym_name):
|
|
207
|
+
target_sym = s
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
if target_sym and target_sym.location and target_sym.location.file_id:
|
|
211
|
+
real_file = target_sym.location.file_id
|
|
212
|
+
try:
|
|
213
|
+
content = self._read_project_file(real_file)
|
|
214
|
+
overridden_content = self.override_store.get_file_content(real_file) if self.override_store else None
|
|
215
|
+
self._send_json({
|
|
216
|
+
"path": real_file,
|
|
217
|
+
"resolved_symbol": target_sym.id,
|
|
218
|
+
"target_line": target_sym.location.start_line,
|
|
219
|
+
"content": overridden_content if overridden_content is not None else content,
|
|
220
|
+
})
|
|
221
|
+
return
|
|
222
|
+
except FileNotFoundError:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
content = self._read_project_file(rel_path)
|
|
227
|
+
disk_path = self._resolve_disk_path(rel_path)
|
|
228
|
+
norm_path = rel_path
|
|
229
|
+
if disk_path and self.project_root:
|
|
230
|
+
try:
|
|
231
|
+
norm_path = str(disk_path.relative_to(self.project_root.resolve())).replace("\\", "/")
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
overridden_content = self.override_store.get_file_content(norm_path) if self.override_store else None
|
|
235
|
+
if overridden_content is None and self.override_store:
|
|
236
|
+
overridden_content = self.override_store.get_file_content(rel_path)
|
|
237
|
+
self._send_json({
|
|
238
|
+
"path": norm_path,
|
|
239
|
+
"content": overridden_content if overridden_content is not None else content,
|
|
240
|
+
"resolved_symbol": target_sym.id if target_sym else None,
|
|
241
|
+
"target_line": target_sym.location.start_line if (target_sym and target_sym.location) else None
|
|
242
|
+
})
|
|
243
|
+
return
|
|
244
|
+
except FileNotFoundError:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
ext_content = self._synthesize_external_symbol_spec(symbol_id or rel_path)
|
|
248
|
+
self._send_json({
|
|
249
|
+
"path": rel_path,
|
|
250
|
+
"content": ext_content,
|
|
251
|
+
"is_external": True,
|
|
252
|
+
})
|
|
259
253
|
except Exception as e:
|
|
260
254
|
self._send_error(400, str(e))
|
|
261
255
|
return
|
|
@@ -277,9 +271,10 @@ class CodeUIHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
277
271
|
|
|
278
272
|
if path in ("/api/v1/repo/info", "/api/v1/library/repo"):
|
|
279
273
|
from codeui.core.repo import get_library_repo_url
|
|
274
|
+
import codeui
|
|
280
275
|
self._send_json({
|
|
281
276
|
"repo_url": get_library_repo_url(),
|
|
282
|
-
"version":
|
|
277
|
+
"version": codeui.__version__,
|
|
283
278
|
})
|
|
284
279
|
return
|
|
285
280
|
|
|
@@ -376,6 +371,36 @@ class CodeUIHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
376
371
|
self._send_json({"status": "ok", "files_count": len(self.graph.get_all_files()) if self.graph else 0})
|
|
377
372
|
return
|
|
378
373
|
|
|
374
|
+
if path == "/api/v1/api_drift/baseline":
|
|
375
|
+
if not self.graph or not self.project_root:
|
|
376
|
+
self._send_error(400, "Graph or project root not available")
|
|
377
|
+
return
|
|
378
|
+
baseline_data = {}
|
|
379
|
+
for sym in self.graph.get_all_symbols():
|
|
380
|
+
if str(sym.visibility) == "public":
|
|
381
|
+
baseline_data[sym.id] = {
|
|
382
|
+
"name": sym.name,
|
|
383
|
+
"file_id": sym.location.file_id if sym.location else "",
|
|
384
|
+
"visibility": "public",
|
|
385
|
+
"signature": sym.signature or sym.name,
|
|
386
|
+
"kind": str(sym.kind)
|
|
387
|
+
}
|
|
388
|
+
dot_codeui = self.project_root / ".codeui"
|
|
389
|
+
dot_codeui.mkdir(parents=True, exist_ok=True)
|
|
390
|
+
baseline_file = dot_codeui / "api_baseline.json"
|
|
391
|
+
baseline_file.write_text(json.dumps(baseline_data, indent=2), encoding="utf-8")
|
|
392
|
+
|
|
393
|
+
from codeui.analysis.runner import AnalysisRunner
|
|
394
|
+
from codeui.analysis.base import AnalysisContext
|
|
395
|
+
ctx = AnalysisContext(self.project_root)
|
|
396
|
+
findings = AnalysisRunner().run_all(self.graph, ctx)
|
|
397
|
+
self._send_json({
|
|
398
|
+
"status": "ok",
|
|
399
|
+
"baseline_count": len(baseline_data),
|
|
400
|
+
"findings_count": len(findings)
|
|
401
|
+
})
|
|
402
|
+
return
|
|
403
|
+
|
|
379
404
|
if path == "/api/v1/edit":
|
|
380
405
|
kind = payload.get("kind", "replace_file")
|
|
381
406
|
target_id = payload.get("target_id")
|
|
@@ -623,6 +648,55 @@ class CodeUIHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
623
648
|
return full_path.read_text(encoding="utf-8", errors="replace")
|
|
624
649
|
raise FileNotFoundError(f"File not found: {rel_path}")
|
|
625
650
|
|
|
651
|
+
def _synthesize_external_symbol_spec(self, symbol_name: str) -> str:
|
|
652
|
+
"""Synthesize interactive documentation and usage report for external symbols."""
|
|
653
|
+
clean = symbol_name.strip()
|
|
654
|
+
parts = clean.split(".")
|
|
655
|
+
mod_name = parts[0] if parts else clean
|
|
656
|
+
base_name = parts[-1] if parts else clean
|
|
657
|
+
|
|
658
|
+
callers = []
|
|
659
|
+
if self.graph:
|
|
660
|
+
for e in self.graph.get_edges():
|
|
661
|
+
if e.target_id == clean or e.target_id.endswith("." + base_name) or e.source_id == clean:
|
|
662
|
+
callers.append(e)
|
|
663
|
+
|
|
664
|
+
lines = [
|
|
665
|
+
f'"""',
|
|
666
|
+
f"======================================================================",
|
|
667
|
+
f"External Dependency Specification: {clean}",
|
|
668
|
+
f"Package / Module: {mod_name}",
|
|
669
|
+
f"Symbol: {base_name}",
|
|
670
|
+
f"======================================================================",
|
|
671
|
+
f"",
|
|
672
|
+
f"Usages in project ({len(callers)} reference{'s' if len(callers) != 1 else ''}):",
|
|
673
|
+
]
|
|
674
|
+
if callers:
|
|
675
|
+
for c in callers[:50]:
|
|
676
|
+
k_val = c.kind.value if hasattr(c.kind, "value") else str(c.kind)
|
|
677
|
+
lines.append(f" • {c.source_id} -> [{k_val}] {clean}")
|
|
678
|
+
if len(callers) > 50:
|
|
679
|
+
lines.append(f" • ... and {len(callers) - 50} more call-sites.")
|
|
680
|
+
else:
|
|
681
|
+
lines.append(" (No direct callers recorded; referenced dynamically or via wildcard import)")
|
|
682
|
+
lines.append('"""')
|
|
683
|
+
lines.append("")
|
|
684
|
+
|
|
685
|
+
is_class = bool(base_name and base_name[0].isupper())
|
|
686
|
+
if is_class:
|
|
687
|
+
lines.append(f"class {base_name}:")
|
|
688
|
+
lines.append(f' """Interface definition for external {clean}."""')
|
|
689
|
+
lines.append(f" def __init__(self, *args, **kwargs):")
|
|
690
|
+
lines.append(f" pass")
|
|
691
|
+
lines.append(f"")
|
|
692
|
+
else:
|
|
693
|
+
lines.append(f"def {base_name}(*args, **kwargs):")
|
|
694
|
+
lines.append(f' """Interface definition for external {clean}."""')
|
|
695
|
+
lines.append(f" pass")
|
|
696
|
+
lines.append(f"")
|
|
697
|
+
|
|
698
|
+
return "\n".join(lines)
|
|
699
|
+
|
|
626
700
|
def _send_json(self, data: Dict[str, Any]) -> None:
|
|
627
701
|
"""Send JSON HTTP 200 response with security headers."""
|
|
628
702
|
body = json.dumps(data, sort_keys=True, indent=2)
|
|
@@ -664,9 +738,6 @@ class CodeUIHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
664
738
|
sys.stderr.write(f"{self.address_string()} - - [{self.log_date_time_string()}] {format % args}\n")
|
|
665
739
|
sys.stderr.flush()
|
|
666
740
|
|
|
667
|
-
class ReusableThreadingHTTPServer(ThreadingHTTPServer):
|
|
668
|
-
allow_reuse_address = True
|
|
669
|
-
|
|
670
741
|
def create_server(
|
|
671
742
|
graph: Graph,
|
|
672
743
|
override_store: OverrideStore,
|
|
@@ -702,4 +773,4 @@ def create_server(
|
|
|
702
773
|
CodeUIHTTPRequestHandler.layer_rules = dict(graph._layer_rules)
|
|
703
774
|
else:
|
|
704
775
|
CodeUIHTTPRequestHandler.layer_rules = None
|
|
705
|
-
return
|
|
776
|
+
return ThreadingHTTPServer((host, port), CodeUIHTTPRequestHandler)
|