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.
- codeui/__init__.py +23 -0
- codeui/agent/__init__.py +15 -0
- codeui/agent/context.py +207 -0
- codeui/agent/contribution.py +224 -0
- codeui/agent/edit_tools.py +280 -0
- codeui/agent/integrations.py +243 -0
- codeui/agent/mcp_server.py +94 -0
- codeui/analysis/__init__.py +1 -0
- codeui/analysis/api_drift.py +79 -0
- codeui/analysis/base.py +42 -0
- codeui/analysis/circular.py +116 -0
- codeui/analysis/dead_code.py +63 -0
- codeui/analysis/duplicates.py +69 -0
- codeui/analysis/runner.py +57 -0
- codeui/analysis/shadowing.py +49 -0
- codeui/analysis/undefined.py +97 -0
- codeui/analysis/unreachable.py +28 -0
- codeui/analysis/unresolved_import.py +153 -0
- codeui/cli/__init__.py +1 -0
- codeui/cli/main.py +437 -0
- codeui/core/__init__.py +1 -0
- codeui/core/cache.py +135 -0
- codeui/core/graph.py +435 -0
- codeui/core/ir.py +218 -0
- codeui/core/override.py +230 -0
- codeui/core/repo.py +85 -0
- codeui/core/resolver.py +281 -0
- codeui/errors.py +98 -0
- codeui/lang/__init__.py +1 -0
- codeui/lang/base.py +117 -0
- codeui/lang/generic.py +90 -0
- codeui/lang/go.py +167 -0
- codeui/lang/python.py +294 -0
- codeui/lang/registry.py +83 -0
- codeui/lang/rust.py +185 -0
- codeui/lang/ts.py +396 -0
- codeui/plugins/__init__.py +1 -0
- codeui/plugins/registry.py +31 -0
- codeui/report/__init__.py +1 -0
- codeui/report/json_emitter.py +24 -0
- codeui/report/markdown_emitter.py +33 -0
- codeui/report/sarif_emitter.py +56 -0
- codeui/server/__init__.py +1 -0
- codeui/server/server.py +705 -0
- codeui/tracer/__init__.py +1 -0
- codeui/tracer/tracer.py +147 -0
- codeui_python-0.1.0.dist-info/METADATA +191 -0
- codeui_python-0.1.0.dist-info/RECORD +52 -0
- codeui_python-0.1.0.dist-info/WHEEL +5 -0
- codeui_python-0.1.0.dist-info/entry_points.txt +2 -0
- codeui_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- codeui_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""Edit tools allowing agents to propose and apply non-destructive overrides."""
|
|
2
|
+
import json
|
|
3
|
+
from dataclasses import dataclass, asdict
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
|
+
from codeui.core.graph import Graph
|
|
8
|
+
from codeui.core.ir import SymbolKind
|
|
9
|
+
from codeui.core.override import Conflict, Override, OverrideKind, OverrideStore
|
|
10
|
+
from codeui.errors import SymbolNotFoundError, FileNotFoundInGraphError
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class EditProposal:
|
|
14
|
+
"""Dry-run proposal report for edit tools.
|
|
15
|
+
Example:
|
|
16
|
+
>>> p = EditProposal("s1", "rename_symbol", ["s1"], ["a.py"], "diff preview", [])
|
|
17
|
+
>>> p.target_id
|
|
18
|
+
's1'
|
|
19
|
+
"""
|
|
20
|
+
target_id: str
|
|
21
|
+
kind: str
|
|
22
|
+
affected_symbols: List[str]
|
|
23
|
+
affected_files: List[str]
|
|
24
|
+
diff_preview: str
|
|
25
|
+
conflicts: List[Conflict]
|
|
26
|
+
new_value: Any = None
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
29
|
+
"""Convert EditProposal to dictionary.
|
|
30
|
+
Example:
|
|
31
|
+
>>> p = EditProposal("s1", "rename", [], [], "", [])
|
|
32
|
+
>>> p.to_dict()["target_id"]
|
|
33
|
+
's1'
|
|
34
|
+
"""
|
|
35
|
+
return asdict(self)
|
|
36
|
+
|
|
37
|
+
class EditTools:
|
|
38
|
+
"""Provides agent interface to dry-run, apply code overrides, and edit project files directly on disk.
|
|
39
|
+
Example:
|
|
40
|
+
>>> g = Graph()
|
|
41
|
+
>>> store = OverrideStore()
|
|
42
|
+
>>> tools = EditTools(g, store)
|
|
43
|
+
>>> tools.diff()
|
|
44
|
+
''
|
|
45
|
+
"""
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
graph: Graph,
|
|
49
|
+
override_store: Optional[OverrideStore] = None,
|
|
50
|
+
project_root: Optional[Union[str, Path]] = None,
|
|
51
|
+
direct_disk: bool = True,
|
|
52
|
+
) -> None:
|
|
53
|
+
self.graph = graph
|
|
54
|
+
self.override_store = override_store if override_store is not None else OverrideStore()
|
|
55
|
+
self.project_root = Path(project_root).resolve() if project_root else None
|
|
56
|
+
self.direct_disk = direct_disk
|
|
57
|
+
|
|
58
|
+
def rename_symbol(self, symbol_id: str, new_name: str) -> EditProposal:
|
|
59
|
+
"""Propose renaming a symbol and updating references.
|
|
60
|
+
Example:
|
|
61
|
+
>>> from codeui.core.ir import Symbol, Location, Visibility
|
|
62
|
+
>>> g = Graph()
|
|
63
|
+
>>> g.add_symbol(Symbol("s1", "f", "a.f", SymbolKind.FUNCTION, "python", Location("a.py", 1, 0, 2, 0), None, None, Visibility.PUBLIC, (), "h"))
|
|
64
|
+
>>> store = OverrideStore()
|
|
65
|
+
>>> tools = EditTools(g, store)
|
|
66
|
+
>>> prop = tools.rename_symbol("s1", "new_name")
|
|
67
|
+
>>> prop.target_id
|
|
68
|
+
's1'
|
|
69
|
+
"""
|
|
70
|
+
if not self.graph.has_symbol(symbol_id):
|
|
71
|
+
raise SymbolNotFoundError(symbol_id)
|
|
72
|
+
sym = self.graph.get_symbol(symbol_id)
|
|
73
|
+
affected_files = [sym.location.file_id]
|
|
74
|
+
incoming = self.graph.get_incoming_edges(symbol_id)
|
|
75
|
+
affected_symbols = [symbol_id] + [e.source_id for e in incoming]
|
|
76
|
+
diff_preview = f"--- a/{sym.location.file_id}\n+++ b/{sym.location.file_id}\n- {sym.name}\n+ {new_name}\n"
|
|
77
|
+
return EditProposal(
|
|
78
|
+
target_id=symbol_id,
|
|
79
|
+
kind="rename_symbol",
|
|
80
|
+
affected_symbols=affected_symbols,
|
|
81
|
+
affected_files=affected_files,
|
|
82
|
+
diff_preview=diff_preview,
|
|
83
|
+
conflicts=[],
|
|
84
|
+
new_value=new_name,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def retype_symbol(self, symbol_id: str, new_kind: SymbolKind) -> EditProposal:
|
|
88
|
+
"""Propose retyping a symbol's SymbolKind.
|
|
89
|
+
Example:
|
|
90
|
+
>>> from codeui.core.ir import Symbol, Location, Visibility
|
|
91
|
+
>>> g = Graph()
|
|
92
|
+
>>> g.add_symbol(Symbol("s1", "f", "a.f", SymbolKind.FUNCTION, "python", Location("a.py", 1, 0, 2, 0), None, None, Visibility.PUBLIC, (), "h"))
|
|
93
|
+
>>> store = OverrideStore()
|
|
94
|
+
>>> tools = EditTools(g, store)
|
|
95
|
+
>>> prop = tools.retype_symbol("s1", SymbolKind.METHOD)
|
|
96
|
+
>>> prop.kind
|
|
97
|
+
'retype_symbol'
|
|
98
|
+
"""
|
|
99
|
+
if not self.graph.has_symbol(symbol_id):
|
|
100
|
+
raise SymbolNotFoundError(symbol_id)
|
|
101
|
+
sym = self.graph.get_symbol(symbol_id)
|
|
102
|
+
return EditProposal(
|
|
103
|
+
target_id=symbol_id,
|
|
104
|
+
kind="retype_symbol",
|
|
105
|
+
affected_symbols=[symbol_id],
|
|
106
|
+
affected_files=[sym.location.file_id],
|
|
107
|
+
diff_preview=f"Change kind of {sym.name} from {sym.kind} to {new_kind}",
|
|
108
|
+
conflicts=[],
|
|
109
|
+
new_value=new_kind,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def replace_symbol_body(self, symbol_id: str, new_body: str) -> EditProposal:
|
|
113
|
+
"""Propose replacing the implementation body of a single symbol.
|
|
114
|
+
Example:
|
|
115
|
+
>>> from codeui.core.ir import Symbol, Location, Visibility
|
|
116
|
+
>>> g = Graph()
|
|
117
|
+
>>> g.add_symbol(Symbol("s1", "f", "a.f", SymbolKind.FUNCTION, "python", Location("a.py", 1, 0, 2, 0), None, None, Visibility.PUBLIC, (), "h"))
|
|
118
|
+
>>> store = OverrideStore()
|
|
119
|
+
>>> tools = EditTools(g, store)
|
|
120
|
+
>>> prop = tools.replace_symbol_body("s1", "def foo(): pass")
|
|
121
|
+
>>> prop.target_id
|
|
122
|
+
's1'
|
|
123
|
+
"""
|
|
124
|
+
if not self.graph.has_symbol(symbol_id):
|
|
125
|
+
raise SymbolNotFoundError(symbol_id)
|
|
126
|
+
sym = self.graph.get_symbol(symbol_id)
|
|
127
|
+
return EditProposal(
|
|
128
|
+
target_id=symbol_id,
|
|
129
|
+
kind="replace_symbol_body",
|
|
130
|
+
affected_symbols=[symbol_id],
|
|
131
|
+
affected_files=[sym.location.file_id],
|
|
132
|
+
diff_preview=f"Replace body of {sym.name} with:\n{new_body}",
|
|
133
|
+
conflicts=[],
|
|
134
|
+
new_value=new_body,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def replace_file(self, file_id: str, new_content: str, original_content: str = "") -> EditProposal:
|
|
138
|
+
"""Propose replacing entire file content without mutating disk.
|
|
139
|
+
Example:
|
|
140
|
+
>>> g = Graph()
|
|
141
|
+
>>> store = OverrideStore()
|
|
142
|
+
>>> tools = EditTools(g, store)
|
|
143
|
+
>>> prop = tools.replace_file("app.py", "x = 1", "x = 0")
|
|
144
|
+
>>> prop.affected_files
|
|
145
|
+
['app.py']
|
|
146
|
+
"""
|
|
147
|
+
diff = f"--- a/{file_id}\n+++ b/{file_id}\n{new_content}"
|
|
148
|
+
return EditProposal(
|
|
149
|
+
target_id=file_id,
|
|
150
|
+
kind="replace_file",
|
|
151
|
+
affected_symbols=[],
|
|
152
|
+
affected_files=[file_id],
|
|
153
|
+
diff_preview=diff,
|
|
154
|
+
conflicts=[],
|
|
155
|
+
new_value=new_content,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def write_file_to_disk(self, file_path: str, new_content: str) -> Path:
|
|
159
|
+
"""Write content directly to project file on disk.
|
|
160
|
+
Example:
|
|
161
|
+
>>> import tempfile
|
|
162
|
+
>>> with tempfile.TemporaryDirectory() as td:
|
|
163
|
+
... tools = EditTools(Graph(), project_root=td)
|
|
164
|
+
... p = tools.write_file_to_disk("sub/app.py", "x = 1")
|
|
165
|
+
... p.exists()
|
|
166
|
+
True
|
|
167
|
+
"""
|
|
168
|
+
root = (self.project_root if self.project_root is not None else Path(".")).resolve()
|
|
169
|
+
clean_rel = file_path.split("::")[0].replace("module::", "").replace("\\", "/").lstrip("/")
|
|
170
|
+
|
|
171
|
+
target_path: Optional[Path] = None
|
|
172
|
+
cand_direct = (root / clean_rel).resolve()
|
|
173
|
+
if cand_direct.exists() and cand_direct.is_file():
|
|
174
|
+
target_path = cand_direct
|
|
175
|
+
elif root.name and clean_rel.startswith(root.name + "/"):
|
|
176
|
+
sub_cand = (root / clean_rel[len(root.name):].lstrip("/")).resolve()
|
|
177
|
+
if sub_cand.exists() and sub_cand.is_file():
|
|
178
|
+
target_path = sub_cand
|
|
179
|
+
|
|
180
|
+
if target_path is None and self.graph:
|
|
181
|
+
for known_file in self.graph.get_all_files():
|
|
182
|
+
k_clean = known_file.replace("\\", "/").lstrip("./")
|
|
183
|
+
if k_clean == clean_rel or k_clean.endswith("/" + clean_rel) or clean_rel.endswith("/" + k_clean):
|
|
184
|
+
cand_k = (root / k_clean).resolve()
|
|
185
|
+
if cand_k.exists() and cand_k.is_file():
|
|
186
|
+
target_path = cand_k
|
|
187
|
+
break
|
|
188
|
+
|
|
189
|
+
if target_path is None:
|
|
190
|
+
target_path = cand_direct
|
|
191
|
+
|
|
192
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
193
|
+
target_path.write_text(new_content, encoding="utf-8")
|
|
194
|
+
if self.override_store:
|
|
195
|
+
self.override_store.revert(clean_rel)
|
|
196
|
+
try:
|
|
197
|
+
rel = str(target_path.relative_to(root)).replace("\\", "/")
|
|
198
|
+
self.override_store.revert(rel)
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
201
|
+
return target_path
|
|
202
|
+
|
|
203
|
+
def apply_to_disk(self, proposal: EditProposal, author: str = "agent") -> Path:
|
|
204
|
+
"""Apply an EditProposal directly to project files on disk.
|
|
205
|
+
Example:
|
|
206
|
+
>>> import tempfile
|
|
207
|
+
>>> with tempfile.TemporaryDirectory() as td:
|
|
208
|
+
... tools = EditTools(Graph(), project_root=td)
|
|
209
|
+
... prop = tools.replace_file("test.py", "val = 10")
|
|
210
|
+
... path = tools.apply_to_disk(prop)
|
|
211
|
+
... path.read_text()
|
|
212
|
+
'val = 10'
|
|
213
|
+
"""
|
|
214
|
+
content = proposal.new_value if proposal.new_value is not None else (
|
|
215
|
+
proposal.diff_preview.split("\n", 2)[-1] if "\n" in proposal.diff_preview else proposal.diff_preview
|
|
216
|
+
)
|
|
217
|
+
return self.write_file_to_disk(proposal.target_id, content)
|
|
218
|
+
|
|
219
|
+
def apply(self, proposal: EditProposal, author: str = "agent", write_to_disk: Optional[bool] = None) -> None:
|
|
220
|
+
"""Apply an EditProposal into OverrideStore or directly to project file on disk.
|
|
221
|
+
Example:
|
|
222
|
+
>>> g = Graph()
|
|
223
|
+
>>> store = OverrideStore()
|
|
224
|
+
>>> tools = EditTools(g, store, direct_disk=False)
|
|
225
|
+
>>> prop = tools.replace_file("app.py", "x = 1")
|
|
226
|
+
>>> tools.apply(prop)
|
|
227
|
+
>>> store.get("app.py") is not None
|
|
228
|
+
True
|
|
229
|
+
"""
|
|
230
|
+
should_write_disk = write_to_disk if write_to_disk is not None else (self.direct_disk and self.project_root is not None)
|
|
231
|
+
if should_write_disk and proposal.kind == "replace_file":
|
|
232
|
+
content = proposal.new_value if proposal.new_value is not None else (
|
|
233
|
+
proposal.diff_preview.split("\n", 2)[-1] if "\n" in proposal.diff_preview else proposal.diff_preview
|
|
234
|
+
)
|
|
235
|
+
self.write_file_to_disk(proposal.target_id, content)
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
239
|
+
if proposal.kind == "replace_file":
|
|
240
|
+
content = proposal.new_value if proposal.new_value is not None else (
|
|
241
|
+
proposal.diff_preview.split("\n", 2)[-1] if "\n" in proposal.diff_preview else proposal.diff_preview
|
|
242
|
+
)
|
|
243
|
+
self.override_store.set_file_override(
|
|
244
|
+
file_path=proposal.target_id,
|
|
245
|
+
original_content="",
|
|
246
|
+
new_content=content,
|
|
247
|
+
author=author,
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
val = proposal.new_value if proposal.new_value is not None else proposal.diff_preview
|
|
251
|
+
override = Override(
|
|
252
|
+
target_id=proposal.target_id,
|
|
253
|
+
kind=OverrideKind(proposal.kind),
|
|
254
|
+
original_hash="",
|
|
255
|
+
new_value=val,
|
|
256
|
+
author=author,
|
|
257
|
+
created_at=now,
|
|
258
|
+
)
|
|
259
|
+
self.override_store.set(override)
|
|
260
|
+
|
|
261
|
+
def revert(self, target_id: str) -> None:
|
|
262
|
+
"""Revert override for target ID.
|
|
263
|
+
Example:
|
|
264
|
+
>>> g = Graph()
|
|
265
|
+
>>> store = OverrideStore()
|
|
266
|
+
>>> tools = EditTools(g, store)
|
|
267
|
+
>>> tools.revert("app.py")
|
|
268
|
+
"""
|
|
269
|
+
self.override_store.revert(target_id)
|
|
270
|
+
|
|
271
|
+
def diff(self) -> str:
|
|
272
|
+
"""Generate unified diff across all applied overrides.
|
|
273
|
+
Example:
|
|
274
|
+
>>> g = Graph()
|
|
275
|
+
>>> store = OverrideStore()
|
|
276
|
+
>>> tools = EditTools(g, store)
|
|
277
|
+
>>> tools.diff()
|
|
278
|
+
''
|
|
279
|
+
"""
|
|
280
|
+
return self.override_store.to_diff()
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""LangChain, LlamaIndex, and MCP tool integration helpers."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
from codeui.agent.context import ContextBuilder
|
|
5
|
+
from codeui.agent.contribution import ContributionManager
|
|
6
|
+
from codeui.agent.edit_tools import EditProposal, EditTools
|
|
7
|
+
from codeui.core.graph import Graph
|
|
8
|
+
from codeui.core.override import OverrideStore
|
|
9
|
+
from codeui.core.repo import get_library_repo_url
|
|
10
|
+
from codeui.tracer.tracer import Tracer
|
|
11
|
+
|
|
12
|
+
class CodeUITools:
|
|
13
|
+
"""Unified integration tools exposing codeui capabilities to AI agents.
|
|
14
|
+
Example:
|
|
15
|
+
>>> g = Graph()
|
|
16
|
+
>>> tools = CodeUITools(g)
|
|
17
|
+
>>> "matches" in tools.find_symbol("main")
|
|
18
|
+
True
|
|
19
|
+
"""
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
graph: Graph,
|
|
23
|
+
override_store: Optional[OverrideStore] = None,
|
|
24
|
+
project_root: Optional[Union[str, Path]] = None,
|
|
25
|
+
direct_disk: bool = True,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.graph = graph
|
|
28
|
+
self.override_store = override_store or OverrideStore()
|
|
29
|
+
self.project_root = Path(project_root).resolve() if project_root else None
|
|
30
|
+
self.direct_disk = direct_disk
|
|
31
|
+
self.tracer = Tracer(graph)
|
|
32
|
+
self.context_builder = ContextBuilder(graph)
|
|
33
|
+
self.edit_tools = EditTools(
|
|
34
|
+
graph=graph,
|
|
35
|
+
override_store=self.override_store,
|
|
36
|
+
project_root=self.project_root,
|
|
37
|
+
direct_disk=self.direct_disk,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def find_symbol(self, query: str) -> Dict[str, Any]:
|
|
41
|
+
"""Search for symbols by name or qualified name.
|
|
42
|
+
Example:
|
|
43
|
+
>>> g = Graph()
|
|
44
|
+
>>> tools = CodeUITools(g)
|
|
45
|
+
>>> tools.find_symbol("f")["matches"]
|
|
46
|
+
[]
|
|
47
|
+
"""
|
|
48
|
+
matches = [
|
|
49
|
+
s.to_dict() for s in self.graph.get_all_symbols()
|
|
50
|
+
if query.lower() in s.name.lower() or query.lower() in s.qualified_name.lower()
|
|
51
|
+
]
|
|
52
|
+
return {"query": query, "matches": matches, "count": len(matches)}
|
|
53
|
+
|
|
54
|
+
def get_context(self, symbol_id: str, depth: int = 1) -> Dict[str, Any]:
|
|
55
|
+
"""Retrieve graph-based context bundle for symbol.
|
|
56
|
+
Example:
|
|
57
|
+
>>> g = Graph()
|
|
58
|
+
>>> tools = CodeUITools(g)
|
|
59
|
+
>>> "manifest" in tools.get_context("sym_1")
|
|
60
|
+
True
|
|
61
|
+
"""
|
|
62
|
+
bundle = self.context_builder.for_symbol(symbol_id, depth=depth)
|
|
63
|
+
return bundle.to_dict()
|
|
64
|
+
|
|
65
|
+
def find_defects(self, severity: str | None = None) -> Dict[str, Any]:
|
|
66
|
+
"""Fetch all defects filtered by optional severity.
|
|
67
|
+
Example:
|
|
68
|
+
>>> g = Graph()
|
|
69
|
+
>>> tools = CodeUITools(g)
|
|
70
|
+
>>> tools.find_defects()["count"]
|
|
71
|
+
0
|
|
72
|
+
"""
|
|
73
|
+
findings = self.graph.get_findings()
|
|
74
|
+
if severity:
|
|
75
|
+
findings = [f for f in findings if str(f.severity) == severity]
|
|
76
|
+
return {"findings": [f.to_dict() for f in findings], "count": len(findings)}
|
|
77
|
+
|
|
78
|
+
def trace_call_chain(self, symbol_id: str, direction: str = "forward", depth: int = 3) -> Dict[str, Any]:
|
|
79
|
+
"""Trace symbol call chain forward or backward.
|
|
80
|
+
Example:
|
|
81
|
+
>>> g = Graph()
|
|
82
|
+
>>> tools = CodeUITools(g)
|
|
83
|
+
>>> tools.trace_call_chain("sym_1")["chain"]
|
|
84
|
+
[]
|
|
85
|
+
"""
|
|
86
|
+
if direction == "backward":
|
|
87
|
+
chain = [s.to_dict() for s in self.tracer.backward(symbol_id, depth=depth)]
|
|
88
|
+
else:
|
|
89
|
+
chain = [s.to_dict() for s in self.tracer.forward(symbol_id, depth=depth)]
|
|
90
|
+
return {"symbol_id": symbol_id, "direction": direction, "chain": chain}
|
|
91
|
+
|
|
92
|
+
def propose_rename(self, symbol_id: str, new_name: str) -> Dict[str, Any]:
|
|
93
|
+
"""Propose renaming a symbol and preview diff.
|
|
94
|
+
Example:
|
|
95
|
+
>>> from codeui.core.ir import Location, Symbol, SymbolKind, Visibility
|
|
96
|
+
>>> g = Graph()
|
|
97
|
+
>>> loc = Location("a.py", 1, 0, 1, 0)
|
|
98
|
+
>>> g.add_symbol(Symbol("s1", "f", "a.f", SymbolKind.FUNCTION, "python", loc, None, None, Visibility.PUBLIC, (), "h"))
|
|
99
|
+
>>> tools = CodeUITools(g)
|
|
100
|
+
>>> tools.propose_rename("s1", "new_f")["target_id"]
|
|
101
|
+
's1'
|
|
102
|
+
"""
|
|
103
|
+
proposal = self.edit_tools.rename_symbol(symbol_id, new_name)
|
|
104
|
+
return proposal.to_dict()
|
|
105
|
+
|
|
106
|
+
def propose_replace_file(self, file_id: str, new_content: str, original_content: str = "") -> Dict[str, Any]:
|
|
107
|
+
"""Propose replacing file content.
|
|
108
|
+
Example:
|
|
109
|
+
>>> g = Graph()
|
|
110
|
+
>>> tools = CodeUITools(g)
|
|
111
|
+
>>> tools.propose_replace_file("a.py", "x = 1")["target_id"]
|
|
112
|
+
'a.py'
|
|
113
|
+
"""
|
|
114
|
+
proposal = self.edit_tools.replace_file(file_id, new_content, original_content)
|
|
115
|
+
return proposal.to_dict()
|
|
116
|
+
|
|
117
|
+
def write_file_direct(self, file_id: str, new_content: str) -> Dict[str, Any]:
|
|
118
|
+
"""Directly write content to a project file on disk.
|
|
119
|
+
Example:
|
|
120
|
+
>>> import tempfile
|
|
121
|
+
>>> with tempfile.TemporaryDirectory() as td:
|
|
122
|
+
... tools = CodeUITools(Graph(), project_root=td)
|
|
123
|
+
... res = tools.write_file_direct("main.py", "print('hello')")
|
|
124
|
+
... res["status"]
|
|
125
|
+
'saved_to_disk'
|
|
126
|
+
"""
|
|
127
|
+
path = self.edit_tools.write_file_to_disk(file_id, new_content)
|
|
128
|
+
return {"status": "saved_to_disk", "file_id": file_id, "path": str(path)}
|
|
129
|
+
|
|
130
|
+
def apply_proposal(
|
|
131
|
+
self,
|
|
132
|
+
proposal: Dict[str, Any],
|
|
133
|
+
author: str = "agent",
|
|
134
|
+
direct_disk: Optional[bool] = None,
|
|
135
|
+
) -> Dict[str, Any]:
|
|
136
|
+
"""Apply an edit proposal into the active override store or directly to project file on disk.
|
|
137
|
+
Example:
|
|
138
|
+
>>> g = Graph()
|
|
139
|
+
>>> tools = CodeUITools(g, direct_disk=False)
|
|
140
|
+
>>> prop = tools.propose_replace_file("a.py", "x = 1")
|
|
141
|
+
>>> tools.apply_proposal(prop)["status"]
|
|
142
|
+
'applied'
|
|
143
|
+
"""
|
|
144
|
+
prop_obj = EditProposal(
|
|
145
|
+
target_id=proposal["target_id"],
|
|
146
|
+
kind=proposal["kind"],
|
|
147
|
+
affected_symbols=proposal.get("affected_symbols", []),
|
|
148
|
+
affected_files=proposal.get("affected_files", []),
|
|
149
|
+
diff_preview=proposal.get("diff_preview", ""),
|
|
150
|
+
conflicts=[],
|
|
151
|
+
new_value=proposal.get("new_value"),
|
|
152
|
+
)
|
|
153
|
+
self.edit_tools.apply(prop_obj, author=author, write_to_disk=direct_disk)
|
|
154
|
+
status = "written_to_disk" if (direct_disk or (direct_disk is None and self.direct_disk and self.project_root is not None)) else "applied"
|
|
155
|
+
return {"status": status, "target_id": proposal["target_id"]}
|
|
156
|
+
|
|
157
|
+
def revert_override(self, target_id: str) -> Dict[str, Any]:
|
|
158
|
+
"""Revert active override for target symbol or file.
|
|
159
|
+
Example:
|
|
160
|
+
>>> g = Graph()
|
|
161
|
+
>>> tools = CodeUITools(g)
|
|
162
|
+
>>> tools.revert_override("a.py")["status"]
|
|
163
|
+
'reverted'
|
|
164
|
+
"""
|
|
165
|
+
self.edit_tools.revert(target_id)
|
|
166
|
+
return {"status": "reverted", "target_id": target_id}
|
|
167
|
+
|
|
168
|
+
def diff(self) -> str:
|
|
169
|
+
"""Get unified diff across all active overrides.
|
|
170
|
+
Example:
|
|
171
|
+
>>> g = Graph()
|
|
172
|
+
>>> tools = CodeUITools(g)
|
|
173
|
+
>>> tools.get_diff()
|
|
174
|
+
''
|
|
175
|
+
"""
|
|
176
|
+
return self.edit_tools.diff()
|
|
177
|
+
|
|
178
|
+
def get_diff(self) -> str:
|
|
179
|
+
"""Get unified diff across all active overrides.
|
|
180
|
+
Example:
|
|
181
|
+
>>> g = Graph()
|
|
182
|
+
>>> tools = CodeUITools(g)
|
|
183
|
+
>>> tools.get_diff()
|
|
184
|
+
''
|
|
185
|
+
"""
|
|
186
|
+
return self.edit_tools.diff()
|
|
187
|
+
|
|
188
|
+
def get_repository_url(self) -> str:
|
|
189
|
+
"""Retrieve the official codeui GitHub repository URL.
|
|
190
|
+
Example:
|
|
191
|
+
>>> g = Graph()
|
|
192
|
+
>>> tools = CodeUITools(g)
|
|
193
|
+
>>> tools.get_repository_url()
|
|
194
|
+
'https://github.com/AAlsubari/codeUI'
|
|
195
|
+
"""
|
|
196
|
+
return get_library_repo_url()
|
|
197
|
+
|
|
198
|
+
def clone_library_repository(
|
|
199
|
+
self,
|
|
200
|
+
target_dir: Optional[Union[str, Path]] = None,
|
|
201
|
+
token: Optional[str] = None,
|
|
202
|
+
branch: Optional[str] = None,
|
|
203
|
+
) -> Dict[str, Any]:
|
|
204
|
+
"""Clone the codeui repository for inspection, bug fixing, or feature addition.
|
|
205
|
+
Example:
|
|
206
|
+
>>> g = Graph()
|
|
207
|
+
>>> tools = CodeUITools(g)
|
|
208
|
+
>>> tools.get_repository_url()
|
|
209
|
+
'https://github.com/AAlsubari/codeUI'
|
|
210
|
+
"""
|
|
211
|
+
mgr = ContributionManager(token=token)
|
|
212
|
+
cloned_path = mgr.clone_repository(target_dir=target_dir, token=token, branch=branch)
|
|
213
|
+
return {
|
|
214
|
+
"status": "cloned",
|
|
215
|
+
"repo_url": mgr.get_repo_url(),
|
|
216
|
+
"cloned_dir": str(cloned_path),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def submit_library_contribution(
|
|
220
|
+
self,
|
|
221
|
+
title: str,
|
|
222
|
+
description: str,
|
|
223
|
+
file_changes: Dict[str, str],
|
|
224
|
+
branch_name: Optional[str] = None,
|
|
225
|
+
token: Optional[str] = None,
|
|
226
|
+
push: bool = True,
|
|
227
|
+
) -> Dict[str, Any]:
|
|
228
|
+
"""Clone codeui, apply modifications (bug fixes / features), commit, and push to GitHub.
|
|
229
|
+
Example:
|
|
230
|
+
>>> g = Graph()
|
|
231
|
+
>>> tools = CodeUITools(g)
|
|
232
|
+
>>> tools.get_repository_url()
|
|
233
|
+
'https://github.com/AAlsubari/codeUI'
|
|
234
|
+
"""
|
|
235
|
+
mgr = ContributionManager(token=token)
|
|
236
|
+
return mgr.submit_contribution(
|
|
237
|
+
title=title,
|
|
238
|
+
description=description,
|
|
239
|
+
file_changes=file_changes,
|
|
240
|
+
branch_name=branch_name,
|
|
241
|
+
token=token,
|
|
242
|
+
push=push,
|
|
243
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Model Context Protocol (MCP) server exposing codeui edit and analysis tools."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Optional, Union
|
|
5
|
+
from codeui.agent.integrations import CodeUITools
|
|
6
|
+
from codeui.core.graph import Graph
|
|
7
|
+
|
|
8
|
+
class MCPServer:
|
|
9
|
+
"""Model Context Protocol (MCP) server handler for AI agent tool invocations.
|
|
10
|
+
Example:
|
|
11
|
+
>>> g = Graph()
|
|
12
|
+
>>> server = MCPServer(g)
|
|
13
|
+
>>> resp = server.handle_request({"method": "tools/list", "id": 1})
|
|
14
|
+
>>> resp["result"]["tools"][0]["name"]
|
|
15
|
+
'find_symbol'
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, graph: Graph, project_root: Optional[Union[str, Path]] = None, direct_disk: bool = True) -> None:
|
|
18
|
+
self.graph = graph
|
|
19
|
+
self.tools = CodeUITools(graph, project_root=project_root, direct_disk=direct_disk)
|
|
20
|
+
|
|
21
|
+
def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
|
22
|
+
"""Process JSON-RPC style MCP protocol request.
|
|
23
|
+
Example:
|
|
24
|
+
>>> server = MCPServer(Graph())
|
|
25
|
+
>>> resp = server.handle_request({"method": "unknown", "id": 1})
|
|
26
|
+
>>> "error" in resp
|
|
27
|
+
True
|
|
28
|
+
"""
|
|
29
|
+
req_id = request.get("id")
|
|
30
|
+
method = request.get("method")
|
|
31
|
+
params = request.get("params", {})
|
|
32
|
+
|
|
33
|
+
if method == "tools/list":
|
|
34
|
+
return {
|
|
35
|
+
"jsonrpc": "2.0",
|
|
36
|
+
"id": req_id,
|
|
37
|
+
"result": {
|
|
38
|
+
"tools": [
|
|
39
|
+
{"name": "find_symbol", "description": "Search for code symbols by name"},
|
|
40
|
+
{"name": "get_context", "description": "Get graph context bundle for symbol"},
|
|
41
|
+
{"name": "find_defects", "description": "Get defect findings"},
|
|
42
|
+
{"name": "trace_call_chain", "description": "Trace symbol forward/backward call chain"},
|
|
43
|
+
{"name": "propose_rename", "description": "Propose renaming a symbol and preview diff"},
|
|
44
|
+
{"name": "propose_replace_file", "description": "Propose replacing entire file content"},
|
|
45
|
+
{"name": "apply_proposal", "description": "Apply edit proposal into active override store or directly to disk"},
|
|
46
|
+
{"name": "write_file_to_disk", "description": "Directly write new content to project file on disk"},
|
|
47
|
+
{"name": "revert_override", "description": "Revert active override for target"},
|
|
48
|
+
{"name": "get_diff", "description": "Get unified diff across all active overrides"},
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
elif method == "tools/call":
|
|
53
|
+
tool_name = params.get("name")
|
|
54
|
+
args = params.get("arguments", {})
|
|
55
|
+
if tool_name == "find_symbol":
|
|
56
|
+
res = self.tools.find_symbol(args.get("query", ""))
|
|
57
|
+
elif tool_name == "get_context":
|
|
58
|
+
res = self.tools.get_context(args.get("symbol_id", ""), args.get("depth", 1))
|
|
59
|
+
elif tool_name == "find_defects":
|
|
60
|
+
res = self.tools.find_defects(args.get("severity"))
|
|
61
|
+
elif tool_name == "trace_call_chain":
|
|
62
|
+
res = self.tools.trace_call_chain(args.get("symbol_id", ""), args.get("direction", "forward"), args.get("depth", 3))
|
|
63
|
+
elif tool_name == "propose_rename":
|
|
64
|
+
res = self.tools.propose_rename(args.get("symbol_id", ""), args.get("new_name", ""))
|
|
65
|
+
elif tool_name == "propose_replace_file":
|
|
66
|
+
res = self.tools.propose_replace_file(args.get("file_id", ""), args.get("new_content", ""), args.get("original_content", ""))
|
|
67
|
+
elif tool_name == "apply_proposal":
|
|
68
|
+
res = self.tools.apply_proposal(
|
|
69
|
+
args.get("proposal", {}),
|
|
70
|
+
author=args.get("author", "agent"),
|
|
71
|
+
direct_disk=args.get("direct_disk"),
|
|
72
|
+
)
|
|
73
|
+
elif tool_name == "write_file_to_disk":
|
|
74
|
+
res = self.tools.write_file_direct(args.get("file_id", ""), args.get("content", ""))
|
|
75
|
+
elif tool_name == "revert_override":
|
|
76
|
+
res = self.tools.revert_override(args.get("target_id", ""))
|
|
77
|
+
elif tool_name == "get_diff":
|
|
78
|
+
res = {"diff": self.tools.get_diff()}
|
|
79
|
+
else:
|
|
80
|
+
return {
|
|
81
|
+
"jsonrpc": "2.0",
|
|
82
|
+
"id": req_id,
|
|
83
|
+
"error": {"code": -32601, "message": f"Tool not found: {tool_name}"},
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
"jsonrpc": "2.0",
|
|
87
|
+
"id": req_id,
|
|
88
|
+
"result": {"content": [{"type": "text", "text": json.dumps(res, sort_keys=True)}]},
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
"jsonrpc": "2.0",
|
|
92
|
+
"id": req_id,
|
|
93
|
+
"error": {"code": -32601, "message": f"Method not found: {method}"},
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Analysis engine and defect detection modules for codeui."""
|