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
codeui/core/ir.py ADDED
@@ -0,0 +1,218 @@
1
+ """Intermediate Representation (IR) data structures for codeui."""
2
+ from dataclasses import dataclass, asdict
3
+ from enum import Enum
4
+ from typing import Any, Dict
5
+
6
+ class StrEnum(str, Enum):
7
+ """String enum for Python 3.10+ compatibility.
8
+ Example:
9
+ >>> class Color(StrEnum):
10
+ ... RED = "red"
11
+ >>> str(Color.RED)
12
+ 'red'
13
+ """
14
+ def __str__(self) -> str:
15
+ return str(self.value)
16
+
17
+ class Visibility(StrEnum):
18
+ """Symbol visibility scope."""
19
+ PUBLIC = "public"
20
+ PROTECTED = "protected"
21
+ PRIVATE = "private"
22
+ INTERNAL = "internal"
23
+
24
+ class LayerKind(StrEnum):
25
+ """Architectural classification layer for files and modules.
26
+ Example:
27
+ >>> str(LayerKind.FRONTEND)
28
+ 'frontend'
29
+ """
30
+ ENTRY = "entry"
31
+ FRONTEND = "frontend"
32
+ BACKEND = "backend"
33
+ HOOKS = "hooks"
34
+ SHARED = "shared"
35
+ TEST = "test"
36
+ CONFIG = "config"
37
+ OTHER = "other"
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class FileClassification:
41
+ """Architectural classification for a source file.
42
+ Example:
43
+ >>> c = FileClassification(LayerKind.FRONTEND, "auth", "components")
44
+ >>> str(c.layer)
45
+ 'frontend'
46
+ """
47
+ layer: LayerKind
48
+ feature: str
49
+ module: str
50
+
51
+ def to_dict(self) -> Dict[str, Any]:
52
+ """Convert classification to dictionary.
53
+ Example:
54
+ >>> c = FileClassification(LayerKind.BACKEND, "api", "server")
55
+ >>> c.to_dict()["layer"]
56
+ 'backend'
57
+ """
58
+ return {
59
+ "layer": str(self.layer),
60
+ "feature": self.feature,
61
+ "module": self.module,
62
+ }
63
+
64
+ class SymbolKind(StrEnum):
65
+ """Kind of programming symbol."""
66
+ FILE = "file"
67
+ MODULE = "module"
68
+ NAMESPACE = "namespace"
69
+ CLASS = "class"
70
+ INTERFACE = "interface"
71
+ STRUCT = "struct"
72
+ ENUM = "enum"
73
+ FUNCTION = "function"
74
+ METHOD = "method"
75
+ PROPERTY = "property"
76
+ VARIABLE = "variable"
77
+ CONSTANT = "constant"
78
+ TYPE_ALIAS = "type_alias"
79
+ IMPORT = "import"
80
+ EXPORT = "export"
81
+
82
+ @dataclass(frozen=True, slots=True)
83
+ class Location:
84
+ """Source code range location.
85
+ Example:
86
+ >>> loc = Location("app.py", 1, 0, 10, 5)
87
+ >>> loc.file_id
88
+ 'app.py'
89
+ """
90
+ file_id: str
91
+ start_line: int
92
+ start_col: int
93
+ end_line: int
94
+ end_col: int
95
+
96
+ def to_dict(self) -> Dict[str, Any]:
97
+ """Convert Location to dictionary.
98
+ Example:
99
+ >>> loc = Location("a.py", 1, 0, 1, 10)
100
+ >>> loc.to_dict()["file_id"]
101
+ 'a.py'
102
+ """
103
+ return asdict(self)
104
+
105
+ @dataclass(frozen=True, slots=True)
106
+ class Symbol:
107
+ """Code symbol node in graph.
108
+ Example:
109
+ >>> loc = Location("a.py", 1, 0, 2, 0)
110
+ >>> sym = Symbol("s1", "foo", "a.foo", SymbolKind.FUNCTION, "python", loc, None, "def foo():", Visibility.PUBLIC, (), "hash1")
111
+ >>> sym.name
112
+ 'foo'
113
+ """
114
+ id: str
115
+ name: str
116
+ qualified_name: str
117
+ kind: SymbolKind
118
+ language: str
119
+ location: Location
120
+ parent_id: str | None
121
+ signature: str | None
122
+ visibility: Visibility
123
+ modifiers: tuple[str, ...]
124
+ content_hash: str
125
+
126
+ def to_dict(self) -> Dict[str, Any]:
127
+ """Convert Symbol to dictionary representation.
128
+ Example:
129
+ >>> loc = Location("a.py", 1, 0, 2, 0)
130
+ >>> sym = Symbol("s1", "foo", "a.foo", SymbolKind.FUNCTION, "python", loc, None, "def foo():", Visibility.PUBLIC, (), "hash1")
131
+ >>> sym.to_dict()["name"]
132
+ 'foo'
133
+ """
134
+ d = asdict(self)
135
+ d["kind"] = str(self.kind)
136
+ d["visibility"] = str(self.visibility)
137
+ return d
138
+
139
+ class EdgeKind(StrEnum):
140
+ """Kinds of directed relationship edges."""
141
+ CONTAINS = "contains"
142
+ IMPORTS = "imports"
143
+ EXPORTS = "exports"
144
+ CALLS = "calls"
145
+ REFERENCES = "references"
146
+ INHERITS = "inherits"
147
+ IMPLEMENTS = "implements"
148
+ OVERRIDES = "overrides"
149
+ READS = "reads"
150
+ WRITES = "writes"
151
+ RETURNS = "returns"
152
+ INSTANTIATES = "instantiates"
153
+ DEPENDS_ON = "depends_on"
154
+ TESTS = "tests"
155
+ DOCUMENTS = "documents"
156
+ DUPLICATES = "duplicates"
157
+
158
+ @dataclass(frozen=True, slots=True)
159
+ class Edge:
160
+ """Directed edge between two symbols or nodes.
161
+ Example:
162
+ >>> edge = Edge("s1", "s2", EdgeKind.CALLS, 1.0, 1.0, None)
163
+ >>> edge.kind
164
+ <EdgeKind.CALLS: 'calls'>
165
+ """
166
+ source_id: str
167
+ target_id: str
168
+ kind: EdgeKind
169
+ weight: float
170
+ confidence: float
171
+ location: Location | None
172
+
173
+ def to_dict(self) -> Dict[str, Any]:
174
+ """Convert Edge to dictionary representation.
175
+ Example:
176
+ >>> edge = Edge("s1", "s2", EdgeKind.CALLS, 1.0, 1.0, None)
177
+ >>> edge.to_dict()["kind"]
178
+ 'calls'
179
+ """
180
+ d = asdict(self)
181
+ d["kind"] = str(self.kind)
182
+ return d
183
+
184
+ class Severity(StrEnum):
185
+ """Finding severity level."""
186
+ ERROR = "error"
187
+ WARNING = "warning"
188
+ INFO = "info"
189
+
190
+ @dataclass(frozen=True, slots=True)
191
+ class Finding:
192
+ """Defect finding report.
193
+ Example:
194
+ >>> loc = Location("a.py", 1, 0, 1, 10)
195
+ >>> f = Finding("f1", "undefined_symbol", "Symbol x undefined", Severity.ERROR, loc, "s1", "Import x or define it", 1.0)
196
+ >>> f.rule_id
197
+ 'undefined_symbol'
198
+ """
199
+ id: str
200
+ rule_id: str
201
+ message: str
202
+ severity: Severity
203
+ location: Location
204
+ symbol_id: str | None
205
+ fix_hint: str | None
206
+ confidence: float
207
+
208
+ def to_dict(self) -> Dict[str, Any]:
209
+ """Convert Finding to dictionary.
210
+ Example:
211
+ >>> loc = Location("a.py", 1, 0, 1, 10)
212
+ >>> f = Finding("f1", "undefined_symbol", "Symbol x undefined", Severity.ERROR, loc, "s1", "Import x", 1.0)
213
+ >>> f.to_dict()["severity"]
214
+ 'error'
215
+ """
216
+ d = asdict(self)
217
+ d["severity"] = str(self.severity)
218
+ return d
@@ -0,0 +1,230 @@
1
+ """In-memory OverrideStore for non-destructive code editing."""
2
+ import difflib
3
+ import hashlib
4
+ from dataclasses import dataclass, field, asdict
5
+ from datetime import datetime, timezone
6
+ from typing import Any, Dict, Iterable, List, Optional
7
+ from codeui.core.ir import StrEnum
8
+ from codeui.errors import OverrideConflictError
9
+
10
+ class OverrideKind(StrEnum):
11
+ """Kind of override modification."""
12
+ RENAME_SYMBOL = "rename_symbol"
13
+ RETYPE_SYMBOL = "retype_symbol"
14
+ REPLACE_SYMBOL_BODY = "replace_symbol_body"
15
+ REPLACE_FILE = "replace_file"
16
+ TOGGLE_EDGE = "toggle_edge"
17
+ TRIAGE_FINDING = "triage_finding"
18
+
19
+ @dataclass
20
+ class Override:
21
+ """Represents an active override on a target symbol, file, or edge.
22
+ Example:
23
+ >>> o = Override("sym_1", OverrideKind.RENAME_SYMBOL, "hash123", "new_name", "user", "2026-01-01T00:00:00Z")
24
+ >>> o.target_id
25
+ 'sym_1'
26
+ """
27
+ target_id: str
28
+ kind: OverrideKind
29
+ original_hash: str
30
+ new_value: Any
31
+ author: str
32
+ created_at: str
33
+ note: str | None = None
34
+
35
+ def to_dict(self) -> Dict[str, Any]:
36
+ """Convert override to serializable dict.
37
+ Example:
38
+ >>> o = Override("sym_1", OverrideKind.RENAME_SYMBOL, "hash123", "new_name", "user", "2026-01-01T00:00:00Z")
39
+ >>> o.to_dict()["kind"]
40
+ 'rename_symbol'
41
+ """
42
+ d = asdict(self)
43
+ d["kind"] = str(self.kind)
44
+ return d
45
+
46
+ @dataclass(frozen=True)
47
+ class Conflict:
48
+ """Override conflict descriptor.
49
+ Example:
50
+ >>> c = Conflict("src/main.py", "hash1", "hash2", "File on disk changed")
51
+ >>> c.file_path
52
+ 'src/main.py'
53
+ """
54
+ file_path: str
55
+ expected_hash: str
56
+ actual_hash: str
57
+ reason: str
58
+
59
+ class OverrideStore:
60
+ """Manages active overrides without writing to disk.
61
+ Example:
62
+ >>> store = OverrideStore()
63
+ >>> o = Override("file.py", OverrideKind.REPLACE_FILE, "h1", "print('hello')", "agent", "2026-01-01")
64
+ >>> store.set(o)
65
+ >>> store.get("file.py").new_value
66
+ "print('hello')"
67
+ """
68
+ def __init__(self) -> None:
69
+ self._overrides: Dict[str, Override] = {}
70
+ self._file_contents: Dict[str, str] = {}
71
+ self._original_file_contents: Dict[str, str] = {}
72
+
73
+ def get(self, target_id: str) -> Override | None:
74
+ """Retrieve active override for target ID.
75
+ Example:
76
+ >>> store = OverrideStore()
77
+ >>> store.get("nonexistent") is None
78
+ True
79
+ """
80
+ return self._overrides.get(target_id)
81
+
82
+ def has_override(self, target_id: str) -> bool:
83
+ """Check if target has an active override.
84
+ Example:
85
+ >>> store = OverrideStore()
86
+ >>> store.has_override("test.py")
87
+ False
88
+ """
89
+ return target_id in self._overrides
90
+
91
+ def set(self, override: Override) -> None:
92
+ """Set or update an override.
93
+ Example:
94
+ >>> store = OverrideStore()
95
+ >>> o = Override("t1", OverrideKind.RENAME_SYMBOL, "h1", "new", "user", "2026")
96
+ >>> store.set(o)
97
+ >>> store.get("t1").new_value
98
+ 'new'
99
+ """
100
+ self._overrides[override.target_id] = override
101
+
102
+ def set_file_override(self, file_path: str, original_content: str, new_content: str, author: str = "user", note: str | None = None) -> None:
103
+ """Register full file replacement override.
104
+ Example:
105
+ >>> store = OverrideStore()
106
+ >>> store.set_file_override("a.py", "x = 1", "x = 2")
107
+ >>> store.get_file_content("a.py")
108
+ 'x = 2'
109
+ """
110
+ now = datetime.now(timezone.utc).isoformat()
111
+ self._original_file_contents[file_path] = original_content
112
+ self._file_contents[file_path] = new_content
113
+ override = Override(
114
+ target_id=file_path,
115
+ kind=OverrideKind.REPLACE_FILE,
116
+ original_hash=hashlib.sha256(original_content.encode("utf-8")).hexdigest(),
117
+ new_value=new_content,
118
+ author=author,
119
+ created_at=now,
120
+ note=note,
121
+ )
122
+ self._overrides[file_path] = override
123
+
124
+ def get_file_content(self, file_path: str) -> str | None:
125
+ """Return overridden file content if present.
126
+ Example:
127
+ >>> store = OverrideStore()
128
+ >>> store.set_file_override("a.py", "old", "new")
129
+ >>> store.get_file_content("a.py")
130
+ 'new'
131
+ """
132
+ return self._file_contents.get(file_path)
133
+
134
+ def revert(self, target_id: str) -> None:
135
+ """Revert override for specific target ID or file path across all key variations.
136
+ Example:
137
+ >>> store = OverrideStore()
138
+ >>> store.set_file_override("a/b.py", "old", "new")
139
+ >>> store.revert("b.py")
140
+ >>> store.get("a/b.py") is None
141
+ True
142
+ """
143
+ if not target_id:
144
+ return
145
+ clean_target = target_id.split("::")[0].replace("module::", "").replace("\\", "/").strip().lstrip("./")
146
+ keys_to_remove = set()
147
+ for k in list(self._overrides.keys()) + list(self._file_contents.keys()):
148
+ k_clean = k.split("::")[0].replace("module::", "").replace("\\", "/").strip().lstrip("./")
149
+ if k == target_id or k_clean == clean_target or k_clean.endswith("/" + clean_target) or clean_target.endswith("/" + k_clean):
150
+ keys_to_remove.add(k)
151
+ for k in keys_to_remove:
152
+ self._overrides.pop(k, None)
153
+ self._file_contents.pop(k, None)
154
+ self._original_file_contents.pop(k, None)
155
+
156
+ def revert_all(self) -> None:
157
+ """Revert all overrides in the store.
158
+ Example:
159
+ >>> store = OverrideStore()
160
+ >>> store.set_file_override("a.py", "old", "new")
161
+ >>> store.revert_all()
162
+ >>> len(list(store.list()))
163
+ 0
164
+ """
165
+ self._overrides.clear()
166
+ self._file_contents.clear()
167
+
168
+ def list(self) -> Iterable[Override]:
169
+ """List all current overrides in deterministic order.
170
+ Example:
171
+ >>> store = OverrideStore()
172
+ >>> list(store.list())
173
+ []
174
+ """
175
+ return sorted(self._overrides.values(), key=lambda o: o.target_id)
176
+
177
+ def to_patch_files(self) -> Dict[str, str]:
178
+ """Export map of path to modified file content.
179
+ Example:
180
+ >>> store = OverrideStore()
181
+ >>> store.set_file_override("a.py", "old", "new")
182
+ >>> store.to_patch_files()["a.py"]
183
+ 'new'
184
+ """
185
+ return dict(self._file_contents)
186
+
187
+ def to_diff(self) -> str:
188
+ """Generate unified diff string across all overridden files.
189
+ Example:
190
+ >>> store = OverrideStore()
191
+ >>> store.set_file_override("a.py", "old", "new")
192
+ >>> '--- a/a.py' in store.to_diff()
193
+ True
194
+ """
195
+ diff_chunks: List[str] = []
196
+ for file_path in sorted(self._file_contents.keys()):
197
+ orig = self._original_file_contents.get(file_path, "").splitlines(keepends=True)
198
+ mod = self._file_contents[file_path].splitlines(keepends=True)
199
+ lines = list(difflib.unified_diff(
200
+ orig,
201
+ mod,
202
+ fromfile=f"a/{file_path}",
203
+ tofile=f"b/{file_path}",
204
+ ))
205
+ if lines:
206
+ diff_chunks.extend(lines)
207
+ return "".join(diff_chunks)
208
+
209
+ def check_conflicts(self, disk_state: Dict[str, str]) -> List[Conflict]:
210
+ """Check for conflicts between overrides and current disk files.
211
+ Example:
212
+ >>> store = OverrideStore()
213
+ >>> store.set_file_override("a.py", "v1", "v2")
214
+ >>> conflicts = store.check_conflicts({"a.py": "v1_changed"})
215
+ >>> len(conflicts)
216
+ 1
217
+ """
218
+ conflicts: List[Conflict] = []
219
+ for file_path, override in self._overrides.items():
220
+ if override.kind == OverrideKind.REPLACE_FILE and file_path in disk_state:
221
+ current_disk_content = disk_state[file_path]
222
+ expected_original = self._original_file_contents.get(file_path, "")
223
+ if current_disk_content != expected_original:
224
+ conflicts.append(Conflict(
225
+ file_path=file_path,
226
+ expected_hash=override.original_hash,
227
+ actual_hash=hashlib.sha256(current_disk_content.encode("utf-8")).hexdigest(),
228
+ reason="File content on disk changed since override creation",
229
+ ))
230
+ return conflicts
codeui/core/repo.py ADDED
@@ -0,0 +1,85 @@
1
+ """Git repository cloning and remote URL resolution helper."""
2
+ import os
3
+ import shutil
4
+ import subprocess
5
+ import tempfile
6
+ from pathlib import Path
7
+ from typing import Optional, Tuple
8
+ from codeui.errors import RepositoryCloneError
9
+
10
+ DEFAULT_REPOSITORY_URL = "https://github.com/AAlsubari/codeUI"
11
+
12
+ def get_library_repo_url() -> str:
13
+ """Retrieve the official GitHub repository URL for the codeui library.
14
+ Example:
15
+ >>> get_library_repo_url()
16
+ 'https://github.com/AAlsubari/codeUI'
17
+ """
18
+ return os.environ.get("CODEUI_GITHUB_REPO_URL", DEFAULT_REPOSITORY_URL)
19
+
20
+ def is_git_url(target: str) -> bool:
21
+ """Check if target string represents a remote Git repository URL.
22
+ Example:
23
+ >>> is_git_url("https://github.com/psf/black")
24
+ True
25
+ >>> is_git_url("git@github.com:psf/black.git")
26
+ True
27
+ >>> is_git_url("./local/path")
28
+ False
29
+ """
30
+ if not isinstance(target, str):
31
+ return False
32
+ t = target.strip()
33
+ if t.startswith(("http://", "https://", "git@", "git://", "ssh://")):
34
+ return True
35
+ if t.startswith("github.com/") or t.startswith("gitlab.com/") or t.startswith("bitbucket.org/"):
36
+ return True
37
+ if t.endswith(".git") and (":" in t or "/" in t):
38
+ return True
39
+ return False
40
+
41
+ def clone_git_repo(repo_url: str, dest_dir: Optional[Path] = None) -> Tuple[Path, Optional[tempfile.TemporaryDirectory]]:
42
+ """Clone a remote Git repository with depth 1 into destination or temporary directory.
43
+ Example:
44
+ >>> is_git_url("https://github.com/example/repo")
45
+ True
46
+ """
47
+ git_bin = shutil.which("git")
48
+ if not git_bin:
49
+ raise RepositoryCloneError(repo_url, "Git executable not found in system PATH")
50
+
51
+ url = repo_url.strip()
52
+ if url.startswith("github.com/"):
53
+ url = f"https://{url}"
54
+ elif url.startswith("gitlab.com/"):
55
+ url = f"https://{url}"
56
+ elif url.startswith("bitbucket.org/"):
57
+ url = f"https://{url}"
58
+
59
+ temp_obj: Optional[tempfile.TemporaryDirectory] = None
60
+ if dest_dir is None:
61
+ temp_obj = tempfile.TemporaryDirectory(prefix="codeui_repo_")
62
+ target_path = Path(temp_obj.name).resolve()
63
+ else:
64
+ target_path = dest_dir.resolve()
65
+ target_path.mkdir(parents=True, exist_ok=True)
66
+
67
+ cmd = [git_bin, "clone", "--depth", "1", "--quiet", url, str(target_path)]
68
+ try:
69
+ res = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
70
+ if res.returncode != 0:
71
+ if temp_obj:
72
+ temp_obj.cleanup()
73
+ raise RepositoryCloneError(repo_url, res.stderr.strip() or f"Git clone failed with code {res.returncode}")
74
+ except subprocess.TimeoutExpired:
75
+ if temp_obj:
76
+ temp_obj.cleanup()
77
+ raise RepositoryCloneError(repo_url, "Git clone timed out after 120s")
78
+ except Exception as e:
79
+ if temp_obj:
80
+ temp_obj.cleanup()
81
+ if isinstance(e, RepositoryCloneError):
82
+ raise
83
+ raise RepositoryCloneError(repo_url, str(e))
84
+
85
+ return target_path, temp_obj