codeui-python 0.1.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 (52) hide show
  1. codeui/__init__.py +23 -0
  2. codeui/agent/__init__.py +15 -0
  3. codeui/agent/context.py +207 -0
  4. codeui/agent/contribution.py +224 -0
  5. codeui/agent/edit_tools.py +280 -0
  6. codeui/agent/integrations.py +243 -0
  7. codeui/agent/mcp_server.py +94 -0
  8. codeui/analysis/__init__.py +1 -0
  9. codeui/analysis/api_drift.py +79 -0
  10. codeui/analysis/base.py +42 -0
  11. codeui/analysis/circular.py +116 -0
  12. codeui/analysis/dead_code.py +63 -0
  13. codeui/analysis/duplicates.py +69 -0
  14. codeui/analysis/runner.py +57 -0
  15. codeui/analysis/shadowing.py +49 -0
  16. codeui/analysis/undefined.py +97 -0
  17. codeui/analysis/unreachable.py +28 -0
  18. codeui/analysis/unresolved_import.py +153 -0
  19. codeui/cli/__init__.py +1 -0
  20. codeui/cli/main.py +437 -0
  21. codeui/core/__init__.py +1 -0
  22. codeui/core/cache.py +135 -0
  23. codeui/core/graph.py +435 -0
  24. codeui/core/ir.py +218 -0
  25. codeui/core/override.py +230 -0
  26. codeui/core/repo.py +85 -0
  27. codeui/core/resolver.py +281 -0
  28. codeui/errors.py +98 -0
  29. codeui/lang/__init__.py +1 -0
  30. codeui/lang/base.py +117 -0
  31. codeui/lang/generic.py +90 -0
  32. codeui/lang/go.py +167 -0
  33. codeui/lang/python.py +294 -0
  34. codeui/lang/registry.py +83 -0
  35. codeui/lang/rust.py +185 -0
  36. codeui/lang/ts.py +396 -0
  37. codeui/plugins/__init__.py +1 -0
  38. codeui/plugins/registry.py +31 -0
  39. codeui/report/__init__.py +1 -0
  40. codeui/report/json_emitter.py +24 -0
  41. codeui/report/markdown_emitter.py +33 -0
  42. codeui/report/sarif_emitter.py +56 -0
  43. codeui/server/__init__.py +1 -0
  44. codeui/server/server.py +705 -0
  45. codeui/tracer/__init__.py +1 -0
  46. codeui/tracer/tracer.py +147 -0
  47. codeui_python-0.1.0.dist-info/METADATA +191 -0
  48. codeui_python-0.1.0.dist-info/RECORD +52 -0
  49. codeui_python-0.1.0.dist-info/WHEEL +5 -0
  50. codeui_python-0.1.0.dist-info/entry_points.txt +2 -0
  51. codeui_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  52. codeui_python-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,153 @@
1
+ """Unresolved import defect analyzer."""
2
+ import json
3
+ import re
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import ClassVar, Iterable, List, Set
7
+ from codeui.analysis.base import AnalysisContext, Analyzer
8
+ from codeui.core.graph import Graph
9
+ from codeui.core.ir import EdgeKind, Finding, Location, Severity
10
+
11
+ class UnresolvedImportAnalyzer(Analyzer):
12
+ """Detects imports targeting non-existent modules or files.
13
+ Example:
14
+ >>> analyzer = UnresolvedImportAnalyzer()
15
+ >>> g = Graph()
16
+ >>> ctx = AnalysisContext(Path("."))
17
+ >>> list(analyzer.run(g, ctx))
18
+ []
19
+ """
20
+ id: ClassVar[str] = "unresolved_import"
21
+ severity_default: ClassVar[Severity] = Severity.ERROR
22
+
23
+ STD_MODULES: Set[str] = (
24
+ set(getattr(sys, "stdlib_module_names", set()))
25
+ | set(getattr(sys, "builtin_module_names", set()))
26
+ | {
27
+ "fs", "path", "http", "https", "os", "sys", "re", "json", "ast", "hashlib",
28
+ "sqlite3", "math", "fmt", "io", "net", "std", "core", "alloc", "url", "events",
29
+ "util", "stream", "buffer", "crypto", "child_process", "cluster", "dgram",
30
+ "dns", "tls", "readline", "zlib", "codeui", "react", "react-dom", "lucide-react",
31
+ "vite", "d3", "recharts", "motion", "clsx", "tailwind-merge", "express",
32
+ "typing", "dataclasses", "enum", "argparse", "shutil", "tempfile", "unittest",
33
+ "subprocess", "urllib", "concurrent", "threading", "time", "datetime", "collections",
34
+ "itertools", "functools", "inspect", "traceback", "copy", "glob", "fnmatch",
35
+ "typing_extensions", "pydantic", "pytest", "fastapi", "flask", "requests", "aiohttp",
36
+ "jinja2", "yaml", "toml", "dotenv", "sync", "strings", "bytes", "context", "errors",
37
+ "testing", "encoding", "log", "builtin", "runtime", "syscall"
38
+ }
39
+ )
40
+
41
+ def run(self, graph: Graph, ctx: AnalysisContext) -> Iterable[Finding]:
42
+ """Find unresolved import edges.
43
+ Example:
44
+ >>> analyzer = UnresolvedImportAnalyzer()
45
+ >>> g = Graph()
46
+ >>> list(analyzer.run(g, AnalysisContext(Path("."))))
47
+ []
48
+ """
49
+ findings: List[Finding] = []
50
+ all_files = set(graph.get_all_files())
51
+ known_deps = set(self.STD_MODULES)
52
+
53
+ for f in all_files:
54
+ parts = f.replace("\\", "/").split("/")
55
+ for p in parts:
56
+ stem = p.split(".")[0]
57
+ if stem:
58
+ known_deps.add(stem)
59
+ known_deps.add(p)
60
+
61
+ if ctx and ctx.root_dir:
62
+ root = ctx.root_dir
63
+ for manifest_name in ("package.json", "requirements.txt", "pyproject.toml", "setup.py", "Pipfile", "go.mod", "Cargo.toml"):
64
+ mpath = root / manifest_name
65
+ if mpath.exists():
66
+ try:
67
+ content = mpath.read_text(encoding="utf-8")
68
+ if manifest_name == "package.json":
69
+ pkg_data = json.loads(content)
70
+ deps = pkg_data.get("dependencies", {})
71
+ dev_deps = pkg_data.get("devDependencies", {})
72
+ known_deps.update(deps.keys())
73
+ known_deps.update(dev_deps.keys())
74
+ elif manifest_name == "requirements.txt":
75
+ for line in content.splitlines():
76
+ line = line.strip().split("#")[0].strip()
77
+ if line:
78
+ pkg = re.split(r"[=<>!~]", line)[0].strip()
79
+ if pkg:
80
+ known_deps.add(pkg)
81
+ elif manifest_name in ("pyproject.toml", "setup.py", "Cargo.toml", "go.mod"):
82
+ for match in re.finditer(r'["\']([a-zA-Z0-9_-]+)["\']', content):
83
+ known_deps.add(match.group(1))
84
+ except Exception:
85
+ pass
86
+
87
+ for edge in graph.get_edges():
88
+ if edge.kind == EdgeKind.IMPORTS:
89
+ raw_mod = edge.target_id.replace("module::", "").strip()
90
+ if not raw_mod or raw_mod.startswith("."):
91
+ continue
92
+ if raw_mod.startswith("node:"):
93
+ continue
94
+ if raw_mod.startswith("@/") or raw_mod == "@":
95
+ base_mod = "@"
96
+ elif raw_mod.startswith("@"):
97
+ base_mod = "/".join(raw_mod.split("/")[:2])
98
+ else:
99
+ base_mod = raw_mod.split("/")[0].split(".")[0]
100
+
101
+ is_known = (
102
+ raw_mod in known_deps
103
+ or base_mod in known_deps
104
+ or any(raw_mod in f or base_mod in f for f in all_files)
105
+ )
106
+
107
+ if not is_known and raw_mod.startswith("@/"):
108
+ as_src = "src/" + raw_mod[2:]
109
+ as_root = raw_mod[2:]
110
+ if any(as_src in f or as_root in f for f in all_files):
111
+ is_known = True
112
+
113
+ if not is_known and hasattr(ctx, "resolve_path"):
114
+ src_file = edge.source_id.split("::")[0]
115
+ resolved = ctx.resolve_path(src_file, raw_mod)
116
+ if resolved:
117
+ is_known = True
118
+
119
+ if not is_known and hasattr(ctx, "alias_mappings"):
120
+ for alias, target in getattr(ctx, "alias_mappings", {}).items():
121
+ if raw_mod.startswith(alias):
122
+ sub = raw_mod[len(alias):].lstrip("/")
123
+ target_path = Path(target) / sub
124
+ if target_path.exists() or any(target_path.with_suffix(ext).exists() for ext in (".ts", ".tsx", ".js", ".jsx", ".py")):
125
+ is_known = True
126
+ break
127
+
128
+ if not is_known and ctx and ctx.root_dir:
129
+ candidate_rel = ctx.root_dir / raw_mod.replace(".", "/")
130
+ candidate_norm = ctx.root_dir / raw_mod
131
+ if (
132
+ candidate_rel.exists()
133
+ or candidate_norm.exists()
134
+ or any((candidate_rel.parent / f"{candidate_rel.name}{ext}").exists() for ext in (".ts", ".tsx", ".js", ".jsx", ".py"))
135
+ or any((candidate_norm.parent / f"{candidate_norm.name}{ext}").exists() for ext in (".ts", ".tsx", ".js", ".jsx", ".py"))
136
+ or any((candidate_rel / idx).exists() for idx in ("index.ts", "index.tsx", "index.js", "__init__.py"))
137
+ ):
138
+ is_known = True
139
+
140
+ if not is_known:
141
+ loc = edge.location or Location(edge.source_id, 1, 0, 1, 0)
142
+ finding = Finding(
143
+ id=f"unresolved_import::{edge.source_id}::{raw_mod}",
144
+ rule_id="unresolved_import",
145
+ message=f"Unresolved import module '{raw_mod}'",
146
+ severity=self.severity_default,
147
+ location=loc,
148
+ symbol_id=edge.source_id,
149
+ fix_hint=f"Check import path '{raw_mod}' or install required dependency",
150
+ confidence=0.9,
151
+ )
152
+ findings.append(finding)
153
+ return findings
codeui/cli/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """CLI interface package for codeui."""
codeui/cli/main.py ADDED
@@ -0,0 +1,437 @@
1
+ """Command line interface entrypoint for codeui."""
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ import logging
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Sequence, Set, Union
10
+ from codeui.agent.context import ContextBuilder, TaskSpec
11
+ from codeui.analysis.base import AnalysisContext
12
+ from codeui.analysis.runner import AnalysisRunner
13
+ from codeui.core.cache import AnalysisCache
14
+ from codeui.core.graph import Graph
15
+ from codeui.core.ir import Edge, EdgeKind, Location
16
+ from codeui.core.override import Override, OverrideKind, OverrideStore
17
+ from codeui.core.repo import clone_git_repo, is_git_url
18
+ from codeui.core.resolver import ResolveContext
19
+ from codeui.lang.registry import LanguageRegistry
20
+ from codeui.report.json_emitter import JSONEmitter
21
+ from codeui.report.markdown_emitter import MarkdownEmitter
22
+ from codeui.report.sarif_emitter import SARIFEmitter
23
+ from codeui.server.server import create_server
24
+ from codeui.tracer.tracer import Tracer
25
+
26
+ def scan_project(
27
+ project_path: Union[str, Path],
28
+ entry_points: Optional[Sequence[str]] = None,
29
+ layer_rules: Optional[Dict[str, Any]] = None,
30
+ ) -> Graph:
31
+ """Scan a project directory or remote Git repository and build the universal graph.
32
+ Example:
33
+ >>> g = scan_project(Path("."))
34
+ >>> isinstance(g, Graph)
35
+ True
36
+ """
37
+ path_str = str(project_path)
38
+ temp_clone_obj = None
39
+ if is_git_url(path_str):
40
+ resolved_project_path, temp_clone_obj = clone_git_repo(path_str)
41
+ else:
42
+ resolved_project_path = Path(project_path).resolve()
43
+
44
+ cache = None
45
+ try:
46
+ graph = Graph()
47
+ registry = LanguageRegistry()
48
+ ctx = ResolveContext(resolved_project_path)
49
+ cache_db_path = resolved_project_path / ".codeui" / "cache.db"
50
+ try:
51
+ cache = AnalysisCache(cache_db_path)
52
+ except Exception:
53
+ cache = None
54
+
55
+ all_entries: List[str] = list(ctx.entry_points)
56
+ if entry_points:
57
+ all_entries.extend(entry_points)
58
+ all_rules: Dict[str, Any] = dict(ctx.layer_rules)
59
+ if layer_rules:
60
+ all_rules.update(layer_rules)
61
+
62
+ if all_entries:
63
+ graph.set_entry_points(all_entries)
64
+ if all_rules:
65
+ graph.set_layer_rules(all_rules)
66
+
67
+ ignored_dir_names = {
68
+ "node_modules", ".venv", "venv", "env", "dist", "build", ".git", ".codeui",
69
+ "__pycache__", ".pytest_cache", ".egg-info", ".mypy_cache", ".tox", ".idea",
70
+ ".vscode", "target", "out", "vendor", ".next", ".nuxt", "coverage", "site-packages"
71
+ }
72
+ ignored_exts = {
73
+ ".pyc", ".pyo", ".pyd", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg",
74
+ ".zip", ".tar", ".gz", ".lock", ".md", ".markdown", ".txt", ".rst",
75
+ ".json", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".map", ".log"
76
+ }
77
+ valid_code_exts = registry.get_supported_extensions()
78
+
79
+ all_files: List[Path] = []
80
+ for root, dirs, files in os.walk(resolved_project_path):
81
+ dirs[:] = [
82
+ d for d in dirs
83
+ if d not in ignored_dir_names and not (d.startswith(".") and d not in (".", ".."))
84
+ ]
85
+ for f in files:
86
+ p = Path(root) / f
87
+ try:
88
+ resolved_p = p.resolve()
89
+ resolved_p.relative_to(resolved_project_path)
90
+ except (ValueError, RuntimeError):
91
+ continue
92
+ ext = p.suffix.lower()
93
+ if ext in ignored_exts or any(p.name.endswith(e) for e in ignored_exts):
94
+ continue
95
+ if ext not in valid_code_exts and p.name.lower() not in ("dockerfile", "makefile"):
96
+ continue
97
+ all_files.append(p)
98
+
99
+ parsed_files: Set[str] = set()
100
+ raw_edges: List[Edge] = []
101
+ for p in all_files:
102
+ analyzer = registry.get_analyzer(p)
103
+ if not getattr(analyzer, "has_ast_support", True):
104
+ continue
105
+ try:
106
+ rel_path = str(p.relative_to(resolved_project_path)).replace("\\", "/")
107
+ source = p.read_text(encoding="utf-8", errors="replace")
108
+ content_hash = hashlib.sha256(source.encode("utf-8")).hexdigest()
109
+ cached_data = cache.get(rel_path, content_hash) if cache else None
110
+ if cached_data is not None:
111
+ symbols = cache.get_symbols(rel_path, content_hash) or []
112
+ edges = [
113
+ Edge(
114
+ source_id=e["source_id"],
115
+ target_id=e["target_id"],
116
+ kind=EdgeKind(e["kind"]),
117
+ weight=e.get("weight", 1.0),
118
+ confidence=e.get("confidence", 1.0),
119
+ location=Location(
120
+ file_id=e["location"]["file_id"],
121
+ start_line=e["location"]["start_line"],
122
+ start_col=e["location"]["start_col"],
123
+ end_line=e["location"]["end_line"],
124
+ end_col=e["location"]["end_col"],
125
+ ) if e.get("location") else None,
126
+ )
127
+ for e in cached_data.get("edges", [])
128
+ ]
129
+ else:
130
+ parse_res = analyzer.parse(Path(rel_path), source)
131
+ symbols = list(analyzer.extract_symbols(parse_res))
132
+ edges = list(analyzer.extract_edges(parse_res, symbols))
133
+ if cache:
134
+ cache.put(rel_path, content_hash, {
135
+ "symbols": [s.to_dict() for s in symbols],
136
+ "edges": [e.to_dict() for e in edges],
137
+ })
138
+ for s in symbols:
139
+ graph.add_symbol(s)
140
+ for e in edges:
141
+ graph.add_edge(e)
142
+ raw_edges.append(e)
143
+ parsed_files.add(rel_path)
144
+ except Exception as ex:
145
+ logging.warning(f"Error analyzing {p}: {ex}")
146
+
147
+ ctx.set_known_files(parsed_files)
148
+
149
+ for edge in raw_edges:
150
+ if edge.kind == EdgeKind.CONTAINS:
151
+ continue
152
+ src_file = edge.source_id.split("::")[0]
153
+ target_raw = edge.target_id.replace("module::", "").replace("import:", "").strip()
154
+ target_file_cand = target_raw.split("::")[0]
155
+ target_sym_name = target_raw.split("::")[1] if "::" in target_raw else (target_raw.split(".")[-1] if "." in target_raw else "")
156
+
157
+ if not (target_file_cand.startswith(".") or "/" in target_file_cand or "." in target_file_cand or edge.kind == EdgeKind.IMPORTS or target_file_cand in parsed_files):
158
+ continue
159
+
160
+ resolved_targets = ctx.resolve_path(src_file, target_file_cand)
161
+ if not resolved_targets and not target_file_cand.startswith("."):
162
+ dot_candidates = ctx.resolve_path(src_file, f"./{target_file_cand}")
163
+ if dot_candidates:
164
+ resolved_targets = dot_candidates
165
+
166
+ for rt in resolved_targets:
167
+ if rt != src_file and rt in parsed_files:
168
+ file_edge = Edge(
169
+ source_id=src_file,
170
+ target_id=rt,
171
+ kind=edge.kind if edge.kind != EdgeKind.CONTAINS else EdgeKind.IMPORTS,
172
+ weight=edge.weight,
173
+ confidence=edge.confidence,
174
+ location=edge.location,
175
+ )
176
+ graph.add_edge(file_edge)
177
+ if target_sym_name:
178
+ cand_sym_id = f"{rt}::{target_sym_name}"
179
+ if graph.has_symbol(cand_sym_id):
180
+ graph.add_edge(Edge(
181
+ source_id=edge.source_id,
182
+ target_id=cand_sym_id,
183
+ kind=edge.kind,
184
+ weight=edge.weight,
185
+ confidence=edge.confidence,
186
+ location=edge.location,
187
+ ))
188
+
189
+ runner = AnalysisRunner()
190
+ actx = AnalysisContext(root_dir=resolved_project_path, resolve_context=ctx)
191
+ runner.run_all(graph, ctx=actx)
192
+ return graph
193
+ finally:
194
+ if cache:
195
+ cache.close()
196
+ if temp_clone_obj:
197
+ temp_clone_obj.cleanup()
198
+
199
+ def main(args: Optional[List[str]] = None) -> int:
200
+ """Main CLI entrypoint.
201
+ Example:
202
+ >>> main(["langs", "--json"]) # doctest: +ELLIPSIS
203
+ {...}
204
+ 0
205
+ """
206
+ parent_parser = argparse.ArgumentParser(add_help=False)
207
+ parent_parser.add_argument("--json", action="store_true", help="Output results in JSON format")
208
+
209
+ parser = argparse.ArgumentParser(prog="codeui", description="Universal code-intelligence engine", parents=[parent_parser])
210
+ subparsers = parser.add_subparsers(dest="command")
211
+
212
+ subparsers.add_parser("init", parents=[parent_parser], help="Initialize .codeui configuration")
213
+
214
+ scan_p = subparsers.add_parser("scan", parents=[parent_parser], help="Scan project and build code graph")
215
+ scan_p.add_argument("project", nargs="?", default=".", help="Project path")
216
+ scan_p.add_argument("--entry", action="append", help="Explicit application entry point(s)")
217
+ scan_p.add_argument("--out", default=".codeui", help="Output cache directory")
218
+
219
+ serve_p = subparsers.add_parser("serve", parents=[parent_parser], help="Start localhost web UI server")
220
+ serve_p.add_argument("project", nargs="?", default=".", help="Project path")
221
+ serve_p.add_argument("--entry", action="append", help="Explicit application entry point(s)")
222
+ serve_p.add_argument("--port", type=int, default=3000, help="Port number")
223
+
224
+ trace_p = subparsers.add_parser("trace", parents=[parent_parser], help="Trace symbol logic")
225
+ trace_p.add_argument("symbol", help="Target symbol ID")
226
+ trace_p.add_argument("project", nargs="?", default=".", help="Project path")
227
+ trace_p.add_argument("--direction", choices=["forward", "backward"], default="forward")
228
+ trace_p.add_argument("--depth", type=int, default=5)
229
+
230
+ context_p = subparsers.add_parser("context", parents=[parent_parser], help="Build dynamic AI agent context bundle")
231
+ context_p.add_argument("project", nargs="?", default=".", help="Project path")
232
+ context_p.add_argument("--symbol", help="Target symbol ID")
233
+ context_p.add_argument("--file", help="Target file ID")
234
+ context_p.add_argument("--query", default="Analyze code", help="Task query description")
235
+ context_p.add_argument("--defect", help="Target defect finding ID")
236
+ context_p.add_argument("--depth", type=int, default=1)
237
+
238
+ diff_p = subparsers.add_parser("diff", parents=[parent_parser], help="Display unified diff of active session overrides")
239
+ diff_p.add_argument("--overrides", default=".codeui/overrides.json", help="Path to overrides.json file")
240
+
241
+ apply_p = subparsers.add_parser("apply", parents=[parent_parser], help="Apply file edits directly to project files on disk")
242
+ apply_p.add_argument("file", help="Target file path to edit")
243
+ apply_p.add_argument("--content", help="New content to write to file")
244
+ apply_p.add_argument("--from-file", help="Path to source file containing new content")
245
+ apply_p.add_argument("--project", default=".", help="Project root directory")
246
+ apply_p.add_argument("--disk", action="store_true", default=True, help="Write directly to project file on disk (default)")
247
+ apply_p.add_argument("--no-disk", action="store_false", dest="disk", help="Store only in overrides without mutating disk")
248
+
249
+ defects_p = subparsers.add_parser("defects", parents=[parent_parser], help="List defect findings")
250
+ defects_p.add_argument("project", nargs="?", default=".", help="Project path")
251
+ defects_p.add_argument("--severity", choices=["error", "warning", "info"])
252
+ defects_p.add_argument("--format", choices=["json", "sarif", "markdown"], default="json")
253
+
254
+ subparsers.add_parser("langs", parents=[parent_parser], help="List supported programming languages")
255
+ subparsers.add_parser("plugins", parents=[parent_parser], help="List loaded plugins")
256
+ subparsers.add_parser("repo", parents=[parent_parser], help="Display official codeui GitHub repository URL")
257
+
258
+ contrib_p = subparsers.add_parser("contribute", parents=[parent_parser], help="Clone codeui, apply modifications, and push contribution to GitHub")
259
+ contrib_p.add_argument("--title", required=True, help="Contribution or bug fix title")
260
+ contrib_p.add_argument("--description", default="", help="Detailed description of the change")
261
+ contrib_p.add_argument("--file", action="append", nargs=2, metavar=("PATH", "CONTENT"), help="File relative path and new content")
262
+ contrib_p.add_argument("--branch", help="Target git branch name")
263
+ contrib_p.add_argument("--token", help="GitHub authentication token")
264
+ contrib_p.add_argument("--no-push", action="store_false", dest="push", help="Commit locally without pushing to GitHub")
265
+
266
+ parsed = parser.parse_args(args)
267
+
268
+ if parsed.command == "init":
269
+ Path(".codeui").mkdir(exist_ok=True)
270
+ if parsed.json:
271
+ print(json.dumps({"status": "initialized", "dir": ".codeui"}))
272
+ else:
273
+ print("Initialized .codeui workspace directory")
274
+ return 0
275
+ elif parsed.command == "scan":
276
+ p = Path(parsed.project).resolve()
277
+ g = scan_project(p, entry_points=parsed.entry)
278
+ out_dir = Path(parsed.out)
279
+ out_dir.mkdir(parents=True, exist_ok=True)
280
+ (out_dir / "graph.json").write_text(JSONEmitter().emit(g), encoding="utf-8")
281
+ if parsed.json:
282
+ print(JSONEmitter().emit(g))
283
+ else:
284
+ print(f"Scanned {len(g.get_all_files())} files, {len(g.get_all_symbols())} symbols, {len(g.get_findings())} defects.")
285
+ return 0
286
+ elif parsed.command == "serve":
287
+ p = Path(parsed.project).resolve()
288
+ g = scan_project(p, entry_points=parsed.entry)
289
+ store = OverrideStore()
290
+ server = create_server(g, store, p, port=parsed.port, rescan_fn=scan_project, entry_points=parsed.entry)
291
+ print(f"codeui UI server running on http://127.0.0.1:{parsed.port}")
292
+ try:
293
+ server.serve_forever()
294
+ except KeyboardInterrupt:
295
+ server.server_close()
296
+ return 0
297
+ elif parsed.command == "trace":
298
+ p = Path(parsed.project).resolve()
299
+ g = scan_project(p)
300
+ tracer = Tracer(g)
301
+ results = []
302
+ if parsed.direction == "forward":
303
+ results = list(tracer.forward(parsed.symbol, depth=parsed.depth))
304
+ else:
305
+ results = list(tracer.backward(parsed.symbol, depth=parsed.depth))
306
+ if parsed.json:
307
+ print(json.dumps([s.to_dict() for s in results], sort_keys=True, indent=2))
308
+ else:
309
+ print(f"Trace {parsed.direction} for {parsed.symbol} ({len(results)} symbols found):")
310
+ for sym in results:
311
+ print(f" - {sym.kind} {sym.id} ({sym.location.file_id}:{sym.location.start_line})")
312
+ return 0
313
+ elif parsed.command == "context":
314
+ p = Path(parsed.project).resolve()
315
+ g = scan_project(p)
316
+ builder = ContextBuilder(g)
317
+ if parsed.defect:
318
+ bundle = builder.for_defect(parsed.defect)
319
+ elif parsed.symbol:
320
+ bundle = builder.for_symbol(parsed.symbol, depth=parsed.depth)
321
+ elif parsed.file:
322
+ bundle = builder.for_file(parsed.file, depth=parsed.depth)
323
+ else:
324
+ task = TaskSpec(query=parsed.query, target_symbols=[s for s in [parsed.symbol] if s], target_files=[f for f in [parsed.file] if f])
325
+ bundle = builder.for_task(task, depth=parsed.depth)
326
+ print(bundle.to_json())
327
+ return 0
328
+ elif parsed.command == "diff":
329
+ store = OverrideStore()
330
+ overrides_path = Path(parsed.overrides)
331
+ if overrides_path.exists():
332
+ try:
333
+ data = json.loads(overrides_path.read_text(encoding="utf-8"))
334
+ items = data if isinstance(data, list) else data.get("overrides", [])
335
+ for item in items:
336
+ if item.get("kind") == "replace_file":
337
+ store.set_file_override(
338
+ file_path=item["target_id"],
339
+ original_content=item.get("original_content", ""),
340
+ new_content=item.get("new_value", ""),
341
+ author=item.get("author", "user"),
342
+ note=item.get("note"),
343
+ )
344
+ except Exception as ex:
345
+ logging.warning(f"Error loading overrides from {overrides_path}: {ex}")
346
+ diff_text = store.to_diff()
347
+ if parsed.json:
348
+ print(json.dumps({"diff": diff_text, "overrides_count": len(list(store.list()))}))
349
+ else:
350
+ if diff_text:
351
+ print(diff_text)
352
+ else:
353
+ print("No active file differences in overrides.")
354
+ return 0
355
+ elif parsed.command == "apply":
356
+ proj_path = Path(parsed.project).resolve()
357
+ new_content = parsed.content
358
+ if new_content is None and parsed.from_file:
359
+ new_content = Path(parsed.from_file).read_text(encoding="utf-8")
360
+ if new_content is None:
361
+ new_content = sys.stdin.read()
362
+ g = Graph()
363
+ store = OverrideStore()
364
+ from codeui.agent.edit_tools import EditTools
365
+ tools = EditTools(g, store, project_root=proj_path, direct_disk=parsed.disk)
366
+ prop = tools.replace_file(parsed.file, new_content)
367
+ if parsed.disk:
368
+ saved_path = tools.apply_to_disk(prop)
369
+ if parsed.json:
370
+ print(json.dumps({"status": "written_to_disk", "file": parsed.file, "path": str(saved_path)}))
371
+ else:
372
+ print(f"Applied changes directly to file on disk: {saved_path}")
373
+ else:
374
+ tools.apply(prop, write_to_disk=False)
375
+ if parsed.json:
376
+ print(json.dumps({"status": "stored_override", "file": parsed.file}))
377
+ else:
378
+ print(f"Stored non-destructive override for: {parsed.file}")
379
+ return 0
380
+ elif parsed.command == "langs":
381
+ langs = LanguageRegistry().supported_languages()
382
+ if parsed.json:
383
+ print(json.dumps({"languages": langs}))
384
+ else:
385
+ print("Supported languages: " + ", ".join(langs))
386
+ return 0
387
+ elif parsed.command == "defects":
388
+ p = Path(parsed.project).resolve()
389
+ g = scan_project(p)
390
+ if parsed.format == "sarif":
391
+ print(SARIFEmitter().emit(g))
392
+ elif parsed.format == "markdown":
393
+ print(MarkdownEmitter().emit(g))
394
+ else:
395
+ print(JSONEmitter().emit(g))
396
+ return 0
397
+ elif parsed.command == "plugins":
398
+ if parsed.json:
399
+ print(json.dumps({"plugins": []}))
400
+ else:
401
+ print("Loaded plugins: none")
402
+ return 0
403
+ elif parsed.command == "repo":
404
+ from codeui.core.repo import get_library_repo_url
405
+ repo_url = get_library_repo_url()
406
+ if parsed.json:
407
+ print(json.dumps({"repo_url": repo_url}))
408
+ else:
409
+ print(f"codeui GitHub Repository: {repo_url}")
410
+ return 0
411
+ elif parsed.command == "contribute":
412
+ from codeui.agent.contribution import ContributionManager
413
+ changes = {f[0]: f[1] for f in (parsed.file or [])}
414
+ mgr = ContributionManager(token=parsed.token)
415
+ res = mgr.submit_contribution(
416
+ title=parsed.title,
417
+ description=parsed.description,
418
+ file_changes=changes,
419
+ branch_name=parsed.branch,
420
+ token=parsed.token,
421
+ push=parsed.push,
422
+ )
423
+ if parsed.json:
424
+ print(json.dumps(res, indent=2))
425
+ else:
426
+ print(f"Contribution status: {res['status']}")
427
+ print(f"Target Branch: {res['branch']}")
428
+ print(f"Repository: {res['repo_url']}")
429
+ if res.get("changed_files"):
430
+ print("Changed files: " + ", ".join(res["changed_files"]))
431
+ return 0
432
+
433
+ parser.print_help()
434
+ return 0
435
+
436
+ if __name__ == "__main__":
437
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """Core components for codeui."""