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.
Files changed (49) hide show
  1. codeui/__init__.py +1 -1
  2. codeui/agent/context.py +19 -3
  3. codeui/analysis/dead_code.py +13 -2
  4. codeui/analysis/undefined.py +70 -0
  5. codeui/analysis/unresolved_import.py +34 -4
  6. codeui/cli/main.py +101 -7
  7. codeui/core/cache.py +54 -0
  8. codeui/core/graph.py +233 -3
  9. codeui/core/override.py +1 -0
  10. codeui/core/resolver.py +51 -37
  11. codeui/errors.py +10 -0
  12. codeui/lang/base.py +5 -2
  13. codeui/lang/cpp.py +271 -0
  14. codeui/lang/csharp.py +262 -0
  15. codeui/lang/dart.py +278 -0
  16. codeui/lang/generic.py +6 -4
  17. codeui/lang/go.py +5 -2
  18. codeui/lang/java.py +320 -0
  19. codeui/lang/kotlin.py +303 -0
  20. codeui/lang/php.py +275 -0
  21. codeui/lang/python.py +49 -7
  22. codeui/lang/registry.py +22 -2
  23. codeui/lang/ruby.py +270 -0
  24. codeui/lang/rust.py +5 -2
  25. codeui/lang/scala.py +290 -0
  26. codeui/lang/swift.py +276 -0
  27. codeui/lang/ts.py +99 -8
  28. codeui/report/sarif_emitter.py +2 -1
  29. codeui/server/server.py +156 -85
  30. codeui/server/static/css/styles.css +1609 -0
  31. codeui/server/static/index.html +199 -0
  32. codeui/server/static/js/api.js +485 -0
  33. codeui/server/static/js/app.js +1044 -0
  34. codeui/server/static/js/graph_canvas.js +1485 -0
  35. codeui/server/static/js/minimap.js +383 -0
  36. codeui/server/static/js/sidebar.js +268 -0
  37. codeui/server/static/js/state.js +83 -0
  38. codeui/server/static/js/subgraph_window.js +1208 -0
  39. codeui/server/static/js/theme.js +332 -0
  40. codeui/tracer/__init__.py +4 -1
  41. codeui/tracer/tracer.py +109 -118
  42. codeui_python-0.1.1.dist-info/METADATA +171 -0
  43. codeui_python-0.1.1.dist-info/RECORD +71 -0
  44. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/licenses/LICENSE +1 -1
  45. codeui_python-0.1.0.dist-info/METADATA +0 -191
  46. codeui_python-0.1.0.dist-info/RECORD +0 -52
  47. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/WHEEL +0 -0
  48. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/entry_points.txt +0 -0
  49. {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
- if not file_id and symbol_id and "::" in symbol_id:
163
- file_id = symbol_id.split("::")[0]
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
- raw_symbols = self.graph.get_symbols_by_file(file_id) if self.graph else []
177
- excluded_kinds = {SymbolKind.VARIABLE}
178
- symbols = [s for s in raw_symbols if s.kind not in excluded_kinds]
179
- cls = self.graph.get_file_classification(file_id) if self.graph else classify_file(file_id)
180
- sym_ids = {s.id for s in symbols}
181
- intra_edges = [
182
- e.to_dict() for e in (self.graph.get_edges() if self.graph else [])
183
- if (e.source_id in sym_ids) and (e.target_id in sym_ids)
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
- content = self._read_project_file(rel_path)
257
- overridden_content = self.override_store.get_file_content(rel_path) if self.override_store else None
258
- self._send_json({"path": rel_path, "content": overridden_content if overridden_content is not None else content})
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": "0.1.0",
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 ReusableThreadingHTTPServer((host, port), CodeUIHTTPRequestHandler)
776
+ return ThreadingHTTPServer((host, port), CodeUIHTTPRequestHandler)