code-oracle 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.
- code_oracle/__init__.py +30 -0
- code_oracle/cli.py +795 -0
- code_oracle/config.py +145 -0
- code_oracle/dataset.py +5325 -0
- code_oracle/dead_code/__init__.py +32 -0
- code_oracle/dead_code/detector.py +379 -0
- code_oracle/dead_code/entrypoints.py +333 -0
- code_oracle/dead_code/models.py +255 -0
- code_oracle/dead_code/semantics.py +416 -0
- code_oracle/decision.py +906 -0
- code_oracle/engine.py +430 -0
- code_oracle/export_onnx.py +436 -0
- code_oracle/hook.py +531 -0
- code_oracle/indexer.py +894 -0
- code_oracle/languages/__init__.py +114 -0
- code_oracle/languages/common.py +127 -0
- code_oracle/languages/go.py +395 -0
- code_oracle/languages/python.py +336 -0
- code_oracle/languages/rust.py +474 -0
- code_oracle/languages/typescript.py +775 -0
- code_oracle/linearizer.py +166 -0
- code_oracle/locator.py +301 -0
- code_oracle/models.py +237 -0
- code_oracle/perf_lint/__init__.py +38 -0
- code_oracle/perf_lint/engine.py +234 -0
- code_oracle/perf_lint/models.py +229 -0
- code_oracle/perf_lint/rules/__init__.py +31 -0
- code_oracle/perf_lint/rules/async_blocking.py +143 -0
- code_oracle/perf_lint/rules/n_plus_one.py +232 -0
- code_oracle/perf_lint/rules/nested_loops.py +137 -0
- code_oracle/perf_lint/rules/unclosed_res.py +494 -0
- code_oracle/perf_lint/visitor.py +299 -0
- code_oracle/server.py +184 -0
- code_oracle/slicer.py +225 -0
- code_oracle/symbolic.py +459 -0
- code_oracle-0.1.0.dist-info/METADATA +225 -0
- code_oracle-0.1.0.dist-info/RECORD +40 -0
- code_oracle-0.1.0.dist-info/WHEEL +4 -0
- code_oracle-0.1.0.dist-info/entry_points.txt +2 -0
- code_oracle-0.1.0.dist-info/licenses/LICENSE +190 -0
code_oracle/cli.py
ADDED
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command Line Interface for Code Oracle (TopoSlice).
|
|
3
|
+
Entrypoint for the `code-oracle` command.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from code_oracle import __version__
|
|
13
|
+
from code_oracle.config import load_config, resolve_workspace_root, set_enabled, set_mode
|
|
14
|
+
from code_oracle.engine import TopoSliceEngine
|
|
15
|
+
from code_oracle.hook import (
|
|
16
|
+
format_hook_output,
|
|
17
|
+
get_hook_status,
|
|
18
|
+
install_hook,
|
|
19
|
+
run_hook_verification,
|
|
20
|
+
uninstall_hook,
|
|
21
|
+
)
|
|
22
|
+
from code_oracle.indexer import WorkspaceIndexer
|
|
23
|
+
from code_oracle.linearizer import linearize_subgraph
|
|
24
|
+
from code_oracle.locator import locate_affected_symbols
|
|
25
|
+
from code_oracle.models import GateResult, PatchResult
|
|
26
|
+
from code_oracle.server import run_server
|
|
27
|
+
from code_oracle.slicer import slice_neighborhood
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def format_report_pretty(report_dict: dict) -> str:
|
|
31
|
+
"""Format verification report into clean, readable terminal output."""
|
|
32
|
+
status = report_dict["status"]
|
|
33
|
+
conf = report_dict["confidence"]
|
|
34
|
+
latency = report_dict["latency_ms"]
|
|
35
|
+
|
|
36
|
+
color_prefix = "\033[92m" if status == "APPROVED" else "\033[91m"
|
|
37
|
+
color_reset = "\033[0m"
|
|
38
|
+
|
|
39
|
+
risk = report_dict.get("risk_score")
|
|
40
|
+
risk_str = f" | Risk Score: {risk:.4f}" if risk is not None else ""
|
|
41
|
+
unc = report_dict.get("epistemic_uncertainty")
|
|
42
|
+
unc_str = f" | Uncertainty: {unc:.4f}" if unc is not None else ""
|
|
43
|
+
engine_mode = report_dict.get("engine_mode")
|
|
44
|
+
engine_str = f" [Engine: {engine_mode}]" if engine_mode else ""
|
|
45
|
+
|
|
46
|
+
lines = [
|
|
47
|
+
f"{color_prefix}===================================================={color_reset}",
|
|
48
|
+
f"{color_prefix} VERDICT: {status} (Confidence: {conf}{risk_str}{unc_str}){engine_str} in {latency} ms{color_reset}",
|
|
49
|
+
f"{color_prefix}===================================================={color_reset}",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
active_cats = report_dict.get("active_risk_categories", [])
|
|
53
|
+
if active_cats:
|
|
54
|
+
lines.append("\nActive Risk Categories:")
|
|
55
|
+
for cat in active_cats:
|
|
56
|
+
lines.append(f" \033[93m⚠\033[0m {cat}")
|
|
57
|
+
|
|
58
|
+
violations = report_dict.get("invariant_violations", [])
|
|
59
|
+
if violations:
|
|
60
|
+
lines.append("\nViolations:")
|
|
61
|
+
for v in violations:
|
|
62
|
+
lines.append(f" \033[91m✖\033[0m {v}")
|
|
63
|
+
|
|
64
|
+
cycles = report_dict.get("cycles_detected", [])
|
|
65
|
+
if cycles:
|
|
66
|
+
lines.append("\nCycles Detected:")
|
|
67
|
+
for c in cycles:
|
|
68
|
+
lines.append(f" \033[93m↺\033[0m {' -> '.join(c)} -> {c[0] if c else ''}")
|
|
69
|
+
|
|
70
|
+
dsl = report_dict.get("linearized_subgraph", "")
|
|
71
|
+
if dsl:
|
|
72
|
+
lines.append("\nLinearized Subgraph (< 400 tokens):")
|
|
73
|
+
lines.append("----------------------------------------------------")
|
|
74
|
+
lines.append(dsl)
|
|
75
|
+
lines.append("----------------------------------------------------")
|
|
76
|
+
|
|
77
|
+
return "\n".join(lines)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cmd_verify(args: argparse.Namespace) -> int:
|
|
81
|
+
"""Execute patch verification."""
|
|
82
|
+
file_path = args.file
|
|
83
|
+
patch_content = ""
|
|
84
|
+
|
|
85
|
+
if args.patch == "-":
|
|
86
|
+
patch_content = sys.stdin.read()
|
|
87
|
+
elif args.patch:
|
|
88
|
+
# Check if patch is a file path or direct string
|
|
89
|
+
p_path = Path(args.patch)
|
|
90
|
+
if p_path.is_file():
|
|
91
|
+
patch_content = p_path.read_text(encoding="utf-8")
|
|
92
|
+
elif args.workspace and (Path(args.workspace) / args.patch).is_file():
|
|
93
|
+
patch_content = (Path(args.workspace) / args.patch).read_text(encoding="utf-8")
|
|
94
|
+
else:
|
|
95
|
+
patch_content = args.patch
|
|
96
|
+
elif not sys.stdin.isatty():
|
|
97
|
+
# Read from stdin
|
|
98
|
+
patch_content = sys.stdin.read()
|
|
99
|
+
else:
|
|
100
|
+
print("Error: No patch content provided. Use --patch <file_or_diff> or pipe via stdin.", file=sys.stderr)
|
|
101
|
+
return 2
|
|
102
|
+
|
|
103
|
+
engine = TopoSliceEngine(
|
|
104
|
+
workspace_root=Path(args.workspace) if args.workspace else None,
|
|
105
|
+
enable_neural=getattr(args, "neural", None),
|
|
106
|
+
)
|
|
107
|
+
report = engine.verify(
|
|
108
|
+
file_path=file_path,
|
|
109
|
+
patch_content=patch_content,
|
|
110
|
+
k=args.k,
|
|
111
|
+
taxonomy_threshold=getattr(args, "taxonomy_threshold", 0.5),
|
|
112
|
+
)
|
|
113
|
+
report_dict = report.to_dict()
|
|
114
|
+
|
|
115
|
+
if args.json:
|
|
116
|
+
print(json.dumps(report_dict, indent=2))
|
|
117
|
+
else:
|
|
118
|
+
print(format_report_pretty(report_dict))
|
|
119
|
+
|
|
120
|
+
return 0 if report.status == "APPROVED" else 1
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def cmd_index(args: argparse.Namespace) -> int:
|
|
124
|
+
"""Index workspace symbols into .code_oracle/index.json."""
|
|
125
|
+
ws = Path(args.workspace) if args.workspace else Path.cwd()
|
|
126
|
+
indexer = WorkspaceIndexer(workspace_root=ws)
|
|
127
|
+
stats = indexer.scan_workspace(force=args.force)
|
|
128
|
+
|
|
129
|
+
if args.json:
|
|
130
|
+
print(json.dumps(stats, indent=2))
|
|
131
|
+
else:
|
|
132
|
+
print(f"Workspace indexed successfully: {ws}")
|
|
133
|
+
print(f" Scanned files: {stats['scanned']}")
|
|
134
|
+
print(f" Reindexed files: {stats['reindexed']}")
|
|
135
|
+
print(f" Symbols indexed: {stats['symbols_indexed']}")
|
|
136
|
+
print(f" Latency: {stats['latency_ms']:.2f} ms")
|
|
137
|
+
print(f" Index location: {indexer.index_file}")
|
|
138
|
+
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def cmd_slice(args: argparse.Namespace) -> int:
|
|
143
|
+
"""Extract k-hop subgraph slice for a symbol."""
|
|
144
|
+
ws = Path(args.workspace) if args.workspace else Path.cwd()
|
|
145
|
+
indexer = WorkspaceIndexer(workspace_root=ws)
|
|
146
|
+
indexer.scan_workspace()
|
|
147
|
+
|
|
148
|
+
symbol = indexer.get_definition(args.symbol)
|
|
149
|
+
if not symbol:
|
|
150
|
+
# Search by file and name
|
|
151
|
+
matches = [
|
|
152
|
+
s for s in indexer.get_file_symbols(args.file)
|
|
153
|
+
if s.name == args.symbol or s.qualname == args.symbol
|
|
154
|
+
]
|
|
155
|
+
if matches:
|
|
156
|
+
symbol = matches[0]
|
|
157
|
+
|
|
158
|
+
if not symbol:
|
|
159
|
+
err_msg = f"Error: Symbol '{args.symbol}' not found in {args.file} or workspace."
|
|
160
|
+
if getattr(args, "json", False):
|
|
161
|
+
print(json.dumps({"error": err_msg}, indent=2))
|
|
162
|
+
else:
|
|
163
|
+
print(err_msg, file=sys.stderr)
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
graph = slice_neighborhood(seeds=[symbol], indexer=indexer, k=args.k)
|
|
167
|
+
dummy_patch = PatchResult(
|
|
168
|
+
file_path=args.file,
|
|
169
|
+
original_content="",
|
|
170
|
+
patched_content="",
|
|
171
|
+
affected_symbols=[symbol],
|
|
172
|
+
)
|
|
173
|
+
dummy_gate = GateResult(status="APPROVED", confidence=1.0)
|
|
174
|
+
dsl = linearize_subgraph(dummy_patch, graph, dummy_gate)
|
|
175
|
+
|
|
176
|
+
if getattr(args, "json", False):
|
|
177
|
+
slice_data = {
|
|
178
|
+
"symbol": symbol.qualname,
|
|
179
|
+
"file": symbol.file_path,
|
|
180
|
+
"k": args.k,
|
|
181
|
+
"truncated": graph.truncated,
|
|
182
|
+
"nodes": [
|
|
183
|
+
{
|
|
184
|
+
"id": n.id,
|
|
185
|
+
"name": n.name,
|
|
186
|
+
"file_path": n.file_path,
|
|
187
|
+
"kind": n.kind,
|
|
188
|
+
"signature": n.signature,
|
|
189
|
+
"is_seed": n.is_seed,
|
|
190
|
+
"is_modified": n.is_modified,
|
|
191
|
+
}
|
|
192
|
+
for n in graph.nodes.values()
|
|
193
|
+
],
|
|
194
|
+
"edges": [
|
|
195
|
+
{"source": e.source, "target": e.target, "relation": e.relation}
|
|
196
|
+
for e in graph.edges
|
|
197
|
+
],
|
|
198
|
+
"linearized_subgraph": dsl,
|
|
199
|
+
}
|
|
200
|
+
print(json.dumps(slice_data, indent=2))
|
|
201
|
+
else:
|
|
202
|
+
print(dsl)
|
|
203
|
+
|
|
204
|
+
return 0
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def cmd_clean(args: argparse.Namespace) -> int:
|
|
208
|
+
"""Safely remove .code_oracle/ cache directory (Rollback Resilience)."""
|
|
209
|
+
ws = Path(args.workspace) if args.workspace else Path.cwd()
|
|
210
|
+
indexer = WorkspaceIndexer(workspace_root=ws)
|
|
211
|
+
cleaned = indexer.clean()
|
|
212
|
+
if getattr(args, "json", False):
|
|
213
|
+
print(json.dumps({"cleaned": cleaned, "workspace": str(ws)}, indent=2))
|
|
214
|
+
else:
|
|
215
|
+
if cleaned:
|
|
216
|
+
print(f"Code Oracle cache cleaned successfully from {ws}")
|
|
217
|
+
else:
|
|
218
|
+
print(f"Notice: Cache directory not found or already clean in {ws}")
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def cmd_dead_code(args: argparse.Namespace) -> int:
|
|
223
|
+
"""Execute dead code and orphan symbol detection."""
|
|
224
|
+
from code_oracle.dead_code import detect_dead_code
|
|
225
|
+
|
|
226
|
+
ws = Path(args.workspace) if args.workspace else Path.cwd()
|
|
227
|
+
report = detect_dead_code(
|
|
228
|
+
workspace_root=ws,
|
|
229
|
+
paths=args.paths if args.paths else None,
|
|
230
|
+
min_lines=args.min_lines,
|
|
231
|
+
include_unexported=args.include_unexported,
|
|
232
|
+
semantic=getattr(args, "semantic", False),
|
|
233
|
+
suppress_api=getattr(args, "suppress_api", False),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
fmt = "json" if getattr(args, "json", False) else args.format
|
|
237
|
+
|
|
238
|
+
if fmt == "json":
|
|
239
|
+
print(json.dumps(report.to_dict(), indent=2))
|
|
240
|
+
elif fmt == "text":
|
|
241
|
+
print(report.format_text())
|
|
242
|
+
else: # "table"
|
|
243
|
+
print(report.format_table())
|
|
244
|
+
|
|
245
|
+
return 1 if report.dead_symbols_count > 0 else 0
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def cmd_perf_lint(args: argparse.Namespace) -> int:
|
|
249
|
+
"""Execute performance anti-pattern and resource leak scan."""
|
|
250
|
+
from code_oracle.perf_lint import lint_performance
|
|
251
|
+
|
|
252
|
+
ws = Path(args.workspace) if args.workspace else Path.cwd()
|
|
253
|
+
max_depth = getattr(args, "max_loop_depth", None)
|
|
254
|
+
if max_depth is None:
|
|
255
|
+
max_depth = getattr(args, "max_depth", 2)
|
|
256
|
+
|
|
257
|
+
report = lint_performance(
|
|
258
|
+
workspace_root=ws,
|
|
259
|
+
paths=args.paths if args.paths else None,
|
|
260
|
+
severity=args.severity,
|
|
261
|
+
max_depth=max_depth,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
fmt = "json" if getattr(args, "json", False) else args.format
|
|
265
|
+
|
|
266
|
+
if fmt == "json":
|
|
267
|
+
print(json.dumps(report.to_dict(), indent=2))
|
|
268
|
+
elif fmt == "text":
|
|
269
|
+
print(report.format_text())
|
|
270
|
+
else: # "table"
|
|
271
|
+
print(report.format_table())
|
|
272
|
+
|
|
273
|
+
fail_on = args.fail_on.lower() if args.fail_on else "error"
|
|
274
|
+
if fail_on == "none":
|
|
275
|
+
return 0
|
|
276
|
+
elif fail_on == "warn":
|
|
277
|
+
return 1 if (report.warnings_count > 0 or report.errors_count > 0) else 0
|
|
278
|
+
else: # "error"
|
|
279
|
+
return 1 if report.errors_count > 0 else 0
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def cmd_serve(args: argparse.Namespace) -> int:
|
|
283
|
+
"""Launch the FastMCP server."""
|
|
284
|
+
run_server()
|
|
285
|
+
return 0
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def cmd_hook_install(args: argparse.Namespace) -> int:
|
|
289
|
+
"""Safely install git pre-commit hook non-destructively."""
|
|
290
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
291
|
+
success, msg = install_hook(
|
|
292
|
+
workspace_root=ws,
|
|
293
|
+
hook_name=args.hook,
|
|
294
|
+
mode=args.mode,
|
|
295
|
+
)
|
|
296
|
+
if getattr(args, "json", False):
|
|
297
|
+
status_info = get_hook_status(ws)
|
|
298
|
+
status_info["success"] = success
|
|
299
|
+
status_info["message"] = msg
|
|
300
|
+
print(json.dumps(status_info, indent=2))
|
|
301
|
+
else:
|
|
302
|
+
print(msg)
|
|
303
|
+
return 0 if success else 1
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def cmd_hook_uninstall(args: argparse.Namespace) -> int:
|
|
307
|
+
"""Safely uninstall git hook (Rollback Resilience)."""
|
|
308
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
309
|
+
success, msg = uninstall_hook(
|
|
310
|
+
workspace_root=ws,
|
|
311
|
+
hook_name=args.hook,
|
|
312
|
+
)
|
|
313
|
+
if getattr(args, "json", False):
|
|
314
|
+
status_info = get_hook_status(ws)
|
|
315
|
+
status_info["success"] = success
|
|
316
|
+
status_info["message"] = msg
|
|
317
|
+
print(json.dumps(status_info, indent=2))
|
|
318
|
+
else:
|
|
319
|
+
print(msg)
|
|
320
|
+
return 0
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def cmd_hook_on(args: argparse.Namespace) -> int:
|
|
324
|
+
"""Enable Code Oracle pre-commit hook."""
|
|
325
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
326
|
+
cfg = set_enabled(ws, True)
|
|
327
|
+
target_ws = resolve_workspace_root(ws)
|
|
328
|
+
if getattr(args, "json", False):
|
|
329
|
+
print(json.dumps({"enabled": True, "mode": cfg.get("mode", "block"), "workspace": str(target_ws)}, indent=2))
|
|
330
|
+
else:
|
|
331
|
+
print("Code Oracle hook enabled.")
|
|
332
|
+
return 0
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def cmd_hook_off(args: argparse.Namespace) -> int:
|
|
336
|
+
"""Disable Code Oracle pre-commit hook."""
|
|
337
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
338
|
+
cfg = set_enabled(ws, False)
|
|
339
|
+
target_ws = resolve_workspace_root(ws)
|
|
340
|
+
if getattr(args, "json", False):
|
|
341
|
+
print(json.dumps({"enabled": False, "mode": cfg.get("mode", "block"), "workspace": str(target_ws)}, indent=2))
|
|
342
|
+
else:
|
|
343
|
+
print("Code Oracle hook disabled.")
|
|
344
|
+
return 0
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def cmd_hook_mode(args: argparse.Namespace) -> int:
|
|
348
|
+
"""Switch hook mode between 'block' and 'warn'."""
|
|
349
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
350
|
+
cfg = set_mode(ws, args.mode)
|
|
351
|
+
target_ws = resolve_workspace_root(ws)
|
|
352
|
+
if getattr(args, "json", False):
|
|
353
|
+
print(json.dumps({"enabled": cfg.get("enabled", True), "mode": args.mode, "workspace": str(target_ws)}, indent=2))
|
|
354
|
+
else:
|
|
355
|
+
print(f"Code Oracle hook mode set to '{args.mode}'.")
|
|
356
|
+
return 0
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def cmd_hook_status(args: argparse.Namespace) -> int:
|
|
360
|
+
"""Show hook installation and configuration status."""
|
|
361
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
362
|
+
status_info = get_hook_status(ws)
|
|
363
|
+
if getattr(args, "json", False):
|
|
364
|
+
print(json.dumps(status_info, indent=2))
|
|
365
|
+
else:
|
|
366
|
+
git_str = f"Yes ({status_info['workspace']})" if status_info["is_git_repo"] else "No"
|
|
367
|
+
inst_list = []
|
|
368
|
+
if status_info["pre_commit_installed"]:
|
|
369
|
+
inst_list.append("pre-commit: installed")
|
|
370
|
+
else:
|
|
371
|
+
inst_list.append("pre-commit: not installed")
|
|
372
|
+
if status_info["pre_push_installed"]:
|
|
373
|
+
inst_list.append("pre-push: installed")
|
|
374
|
+
else:
|
|
375
|
+
inst_list.append("pre-push: not installed")
|
|
376
|
+
inst_str = f"Yes ({', '.join(inst_list)})" if status_info["installed"] else f"No ({', '.join(inst_list)})"
|
|
377
|
+
state_str = "ENABLED" if status_info["enabled"] else "DISABLED"
|
|
378
|
+
mode_desc = (
|
|
379
|
+
f"{status_info['mode']} (strict exit code 1)"
|
|
380
|
+
if status_info["mode"] == "block"
|
|
381
|
+
else f"{status_info['mode']} (advisory exit code 0)"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
print("Code Oracle Hook Status:")
|
|
385
|
+
print(f" Git Repository: {git_str}")
|
|
386
|
+
print(f" Hook Installed: {inst_str}")
|
|
387
|
+
print(f" Hook State: {state_str}")
|
|
388
|
+
print(f" Hook Mode: {mode_desc}")
|
|
389
|
+
print(f" Config File: {status_info['config_file']}")
|
|
390
|
+
return 0
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def cmd_hook_run(args: argparse.Namespace) -> int:
|
|
394
|
+
"""Execute pre-commit verification on staged files."""
|
|
395
|
+
ws = Path(args.workspace) if args.workspace else None
|
|
396
|
+
result = run_hook_verification(
|
|
397
|
+
workspace_root=ws,
|
|
398
|
+
mode_override=args.mode,
|
|
399
|
+
k=args.k,
|
|
400
|
+
files=args.files,
|
|
401
|
+
)
|
|
402
|
+
if getattr(args, "json", False):
|
|
403
|
+
print(json.dumps(result, indent=2))
|
|
404
|
+
else:
|
|
405
|
+
print(format_hook_output(result))
|
|
406
|
+
return result["exit_code"]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def cmd_dataset(args: argparse.Namespace) -> int:
|
|
410
|
+
"""Execute dataset mining and synthetic generation."""
|
|
411
|
+
from code_oracle.dataset import DatasetGenerator
|
|
412
|
+
|
|
413
|
+
lang_list = [l.strip().lower() for l in args.languages.split(",") if l.strip()]
|
|
414
|
+
generator = DatasetGenerator(
|
|
415
|
+
languages=lang_list,
|
|
416
|
+
seed=args.seed,
|
|
417
|
+
positive_label=args.positive_label,
|
|
418
|
+
negative_label=args.negative_label,
|
|
419
|
+
filter_symbolic_gate=getattr(args, "filter_symbolic_gate", False),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
out_dir = Path(args.output_dir)
|
|
423
|
+
train_count, val_count = generator.generate_and_export(
|
|
424
|
+
output_dir=out_dir,
|
|
425
|
+
num_samples=args.num_samples,
|
|
426
|
+
val_ratio=args.val_ratio,
|
|
427
|
+
repo_path=Path(args.repo) if args.repo else None,
|
|
428
|
+
include_subtle=getattr(args, "include_subtle", True),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
if args.json:
|
|
432
|
+
print(json.dumps({
|
|
433
|
+
"status": "SUCCESS",
|
|
434
|
+
"output_dir": str(out_dir),
|
|
435
|
+
"train_samples": train_count,
|
|
436
|
+
"val_samples": val_count,
|
|
437
|
+
"total_samples": train_count + val_count,
|
|
438
|
+
"languages": lang_list,
|
|
439
|
+
}, indent=2))
|
|
440
|
+
else:
|
|
441
|
+
print(f"Generated {train_count} train samples, {val_count} val samples into {out_dir}")
|
|
442
|
+
|
|
443
|
+
return 0
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def cmd_export_onnx(args: argparse.Namespace) -> int:
|
|
447
|
+
"""Export ModernBERT PyTorch weights to ONNX FP32 and dynamic INT8 formats."""
|
|
448
|
+
from code_oracle.export_onnx import export_and_quantize
|
|
449
|
+
|
|
450
|
+
weights_dir = args.weights
|
|
451
|
+
if not weights_dir:
|
|
452
|
+
cand = Path.cwd() / "weights_base"
|
|
453
|
+
if cand.exists():
|
|
454
|
+
weights_dir = str(cand)
|
|
455
|
+
else:
|
|
456
|
+
from code_oracle.decision import LayaDecisionHead
|
|
457
|
+
head = LayaDecisionHead(enabled=True)
|
|
458
|
+
if head.weights_path:
|
|
459
|
+
weights_dir = str(head.weights_path)
|
|
460
|
+
else:
|
|
461
|
+
print("Error: Could not resolve model weights directory. Use --weights <path>.", file=sys.stderr)
|
|
462
|
+
return 1
|
|
463
|
+
|
|
464
|
+
out_dir = args.output_dir or weights_dir
|
|
465
|
+
|
|
466
|
+
try:
|
|
467
|
+
summary = export_and_quantize(
|
|
468
|
+
weights_path=weights_dir,
|
|
469
|
+
output_dir=out_dir,
|
|
470
|
+
quantize_int8=args.quantize_int8,
|
|
471
|
+
verify_parity=args.verify_parity,
|
|
472
|
+
opset_version=args.opset,
|
|
473
|
+
)
|
|
474
|
+
if args.json:
|
|
475
|
+
print(json.dumps(summary, indent=2))
|
|
476
|
+
else:
|
|
477
|
+
print("====================================================")
|
|
478
|
+
print(" ONNX EXPORT & QUANTIZATION REPORT")
|
|
479
|
+
print("====================================================")
|
|
480
|
+
print(f" Output Directory: {summary['output_dir']}")
|
|
481
|
+
print(f" Model ONNX (FP32): {summary['model_onnx']} ({summary['fp32_size_mb']:.2f} MB)")
|
|
482
|
+
if summary.get("model_int8_onnx"):
|
|
483
|
+
print(f" Model ONNX (INT8): {summary['model_int8_onnx']} ({summary['int8_size_mb']:.2f} MB)")
|
|
484
|
+
print(f" Total Elapsed Time: {summary['elapsed_seconds']:.2f} s")
|
|
485
|
+
parity = summary.get("parity")
|
|
486
|
+
if parity:
|
|
487
|
+
print("----------------------------------------------------")
|
|
488
|
+
print(f" Parity Status: {parity.get('status', 'UNKNOWN')}")
|
|
489
|
+
print(f" FP32 Parity Pass: {parity.get('fp32_parity_pass')}")
|
|
490
|
+
print(f" Max FP32 Risk Diff: {parity.get('max_fp32_risk_diff'):.6e}")
|
|
491
|
+
if summary.get("model_int8_onnx"):
|
|
492
|
+
print(f" INT8 Parity Pass: {parity.get('int8_parity_pass')}")
|
|
493
|
+
print(f" Max INT8 Risk Diff: {parity.get('max_int8_risk_diff'):.6f}")
|
|
494
|
+
print(f" Samples Evaluated: {parity.get('samples_tested')}")
|
|
495
|
+
print("====================================================")
|
|
496
|
+
return 0
|
|
497
|
+
except Exception as exc:
|
|
498
|
+
if args.json:
|
|
499
|
+
print(json.dumps({"status": "FAILED", "error": str(exc)}, indent=2))
|
|
500
|
+
else:
|
|
501
|
+
print(f"Error during ONNX export: {exc}", file=sys.stderr)
|
|
502
|
+
return 1
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
506
|
+
"""Build the CLI argument parser."""
|
|
507
|
+
parser = argparse.ArgumentParser(
|
|
508
|
+
prog="code-oracle",
|
|
509
|
+
description="Sub-50ms Neuro-Symbolic Verification Oracle for AI Coding Agents.",
|
|
510
|
+
)
|
|
511
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
512
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
513
|
+
|
|
514
|
+
# verify
|
|
515
|
+
p_verify = subparsers.add_parser("verify", help="Verify a code patch proposal")
|
|
516
|
+
p_verify.add_argument("file", help="Target source file being patched")
|
|
517
|
+
p_verify.add_argument("--patch", "-p", help="Patch diff string or path to diff file (use '-' for stdin)")
|
|
518
|
+
p_verify.add_argument("--k", type=int, default=1, help="k-hop neighborhood radius (default: 1)")
|
|
519
|
+
p_verify.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
520
|
+
p_verify.add_argument(
|
|
521
|
+
"--neural",
|
|
522
|
+
dest="neural",
|
|
523
|
+
action="store_true",
|
|
524
|
+
default=None,
|
|
525
|
+
help="Enable Laya ModernBERT neural decision head and risk calibration",
|
|
526
|
+
)
|
|
527
|
+
p_verify.add_argument(
|
|
528
|
+
"--no-neural",
|
|
529
|
+
dest="neural",
|
|
530
|
+
action="store_false",
|
|
531
|
+
help="Disable Laya ModernBERT neural decision head (pure symbolic mode)",
|
|
532
|
+
)
|
|
533
|
+
p_verify.add_argument(
|
|
534
|
+
"--taxonomy-threshold",
|
|
535
|
+
type=float,
|
|
536
|
+
default=0.5,
|
|
537
|
+
help="Probability threshold to activate risk taxonomy categories (default: 0.5)",
|
|
538
|
+
)
|
|
539
|
+
p_verify.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
540
|
+
p_verify.set_defaults(func=cmd_verify)
|
|
541
|
+
|
|
542
|
+
# index
|
|
543
|
+
p_index = subparsers.add_parser("index", help="Index workspace symbols into .code_oracle/index.json")
|
|
544
|
+
p_index.add_argument("workspace", nargs="?", default=".", help="Workspace root directory")
|
|
545
|
+
p_index.add_argument("--force", "-f", action="store_true", help="Force re-indexing of all files")
|
|
546
|
+
p_index.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
547
|
+
p_index.set_defaults(func=cmd_index)
|
|
548
|
+
|
|
549
|
+
# slice
|
|
550
|
+
p_slice = subparsers.add_parser("slice", help="Extract k-hop neighborhood slice for a symbol")
|
|
551
|
+
p_slice.add_argument("file", help="Source file containing the symbol")
|
|
552
|
+
p_slice.add_argument("--symbol", "-s", required=True, help="Target symbol name")
|
|
553
|
+
p_slice.add_argument("--k", type=int, default=1, help="k-hop depth (1 or 2)")
|
|
554
|
+
p_slice.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
555
|
+
p_slice.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
556
|
+
p_slice.set_defaults(func=cmd_slice)
|
|
557
|
+
|
|
558
|
+
# clean
|
|
559
|
+
p_clean = subparsers.add_parser("clean", help="Clean .code_oracle/ cache (Rollback Resilience)")
|
|
560
|
+
p_clean.add_argument("workspace", nargs="?", default=".", help="Workspace root directory")
|
|
561
|
+
p_clean.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
562
|
+
p_clean.set_defaults(func=cmd_clean)
|
|
563
|
+
|
|
564
|
+
# dead-code
|
|
565
|
+
p_dead = subparsers.add_parser(
|
|
566
|
+
"dead-code",
|
|
567
|
+
help="Detect unreachable and orphan symbols across workspace",
|
|
568
|
+
)
|
|
569
|
+
p_dead.add_argument(
|
|
570
|
+
"paths",
|
|
571
|
+
nargs="*",
|
|
572
|
+
default=[],
|
|
573
|
+
help="Optional target files or directories to filter report (default: workspace root)",
|
|
574
|
+
)
|
|
575
|
+
p_dead.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
576
|
+
p_dead.add_argument(
|
|
577
|
+
"--format",
|
|
578
|
+
"-f",
|
|
579
|
+
choices=["table", "json", "text"],
|
|
580
|
+
default="table",
|
|
581
|
+
help="Output format (table, json, text)",
|
|
582
|
+
)
|
|
583
|
+
p_dead.add_argument(
|
|
584
|
+
"--min-lines",
|
|
585
|
+
type=int,
|
|
586
|
+
default=0,
|
|
587
|
+
help="Minimum line count threshold for reporting dead code (default: 0)",
|
|
588
|
+
)
|
|
589
|
+
p_dead.add_argument(
|
|
590
|
+
"--include-unexported",
|
|
591
|
+
action="store_true",
|
|
592
|
+
default=False,
|
|
593
|
+
help="Include unexported (private) symbols in dead code detection",
|
|
594
|
+
)
|
|
595
|
+
p_dead.add_argument(
|
|
596
|
+
"--semantic",
|
|
597
|
+
action="store_true",
|
|
598
|
+
default=False,
|
|
599
|
+
help="Enable embedded dead code semantics classification (Stage 1 pruner + Stage 2 classifier)",
|
|
600
|
+
)
|
|
601
|
+
p_dead.add_argument(
|
|
602
|
+
"--suppress-api",
|
|
603
|
+
"--suppress-public-api",
|
|
604
|
+
dest="suppress_api",
|
|
605
|
+
action="store_true",
|
|
606
|
+
default=False,
|
|
607
|
+
help="Suppress public library API surfaces from dead code report",
|
|
608
|
+
)
|
|
609
|
+
p_dead.add_argument(
|
|
610
|
+
"--json",
|
|
611
|
+
action="store_true",
|
|
612
|
+
help="Output machine-readable JSON (alias for --format json)",
|
|
613
|
+
)
|
|
614
|
+
p_dead.set_defaults(func=cmd_dead_code)
|
|
615
|
+
|
|
616
|
+
# perf-lint
|
|
617
|
+
p_perf = subparsers.add_parser(
|
|
618
|
+
"perf-lint",
|
|
619
|
+
help="Detect performance anti-patterns and resource leaks across workspace",
|
|
620
|
+
)
|
|
621
|
+
p_perf.add_argument(
|
|
622
|
+
"paths",
|
|
623
|
+
nargs="*",
|
|
624
|
+
default=[],
|
|
625
|
+
help="Optional target files or directories to lint (default: workspace root)",
|
|
626
|
+
)
|
|
627
|
+
p_perf.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
628
|
+
p_perf.add_argument(
|
|
629
|
+
"--format",
|
|
630
|
+
"-f",
|
|
631
|
+
choices=["table", "json", "text"],
|
|
632
|
+
default="table",
|
|
633
|
+
help="Output format (table, json, text)",
|
|
634
|
+
)
|
|
635
|
+
p_perf.add_argument(
|
|
636
|
+
"--severity",
|
|
637
|
+
choices=["warn", "error"],
|
|
638
|
+
default="warn",
|
|
639
|
+
help="Minimum severity threshold to report (warn or error, default: warn)",
|
|
640
|
+
)
|
|
641
|
+
p_perf.add_argument(
|
|
642
|
+
"--max-loop-depth",
|
|
643
|
+
"--max-depth",
|
|
644
|
+
dest="max_loop_depth",
|
|
645
|
+
type=int,
|
|
646
|
+
default=2,
|
|
647
|
+
help="Loop depth threshold for PERF001 reporting (default: 2)",
|
|
648
|
+
)
|
|
649
|
+
p_perf.add_argument(
|
|
650
|
+
"--fail-on",
|
|
651
|
+
choices=["warn", "error", "none"],
|
|
652
|
+
default="error",
|
|
653
|
+
help="Exit with non-zero exit code if findings meet threshold (default: error)",
|
|
654
|
+
)
|
|
655
|
+
p_perf.add_argument(
|
|
656
|
+
"--json",
|
|
657
|
+
action="store_true",
|
|
658
|
+
help="Output machine-readable JSON (alias for --format json)",
|
|
659
|
+
)
|
|
660
|
+
p_perf.set_defaults(func=cmd_perf_lint)
|
|
661
|
+
|
|
662
|
+
# serve
|
|
663
|
+
p_serve = subparsers.add_parser("serve", help="Run the Lean FastMCP server")
|
|
664
|
+
p_serve.set_defaults(func=cmd_serve)
|
|
665
|
+
|
|
666
|
+
# dataset
|
|
667
|
+
p_dataset = subparsers.add_parser("dataset", help="Mine and generate multi-language training datasets")
|
|
668
|
+
p_dataset.add_argument("--repo", "-r", help="Path to existing repository to mine")
|
|
669
|
+
p_dataset.add_argument("--output-dir", "-o", default="./dataset_output", help="Output directory for JSONL datasets")
|
|
670
|
+
p_dataset.add_argument("--num-samples", "-n", type=int, default=100, help="Target total samples")
|
|
671
|
+
p_dataset.add_argument("--val-ratio", type=float, default=0.2, help="Validation ratio")
|
|
672
|
+
p_dataset.add_argument("--languages", "-l", default="python,typescript,go,rust", help="Comma-separated languages")
|
|
673
|
+
p_dataset.add_argument("--seed", type=int, default=42, help="Random seed")
|
|
674
|
+
p_dataset.add_argument("--positive-label", type=int, default=1, help="Positive label (default: 1)")
|
|
675
|
+
p_dataset.add_argument("--negative-label", type=int, default=0, help="Negative label (default: 0)")
|
|
676
|
+
p_dataset.add_argument("--filter-symbolic-gate", action="store_true", default=False, help="Filter out mutations that fail symbolic gate")
|
|
677
|
+
p_dataset.add_argument("--include-subtle", action="store_true", default=True, help="Include subtle gray-area semantic mutations across risk taxonomy")
|
|
678
|
+
p_dataset.add_argument("--no-subtle", dest="include_subtle", action="store_false", help="Disable subtle gray-area semantic mutations")
|
|
679
|
+
p_dataset.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
680
|
+
p_dataset.set_defaults(func=cmd_dataset)
|
|
681
|
+
|
|
682
|
+
# hook
|
|
683
|
+
p_hook = subparsers.add_parser("hook", help="Git pre-commit hook and toggle system")
|
|
684
|
+
p_hook_sub = p_hook.add_subparsers(dest="hook_command", help="Hook subcommands")
|
|
685
|
+
|
|
686
|
+
# hook install
|
|
687
|
+
p_h_install = p_hook_sub.add_parser("install", help="Safely install git pre-commit hook")
|
|
688
|
+
p_h_install.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
689
|
+
p_h_install.add_argument("--mode", choices=["block", "warn"], default=None, help="Hook mode (block or warn)")
|
|
690
|
+
p_h_install.add_argument("--hook", choices=["pre-commit", "pre-push"], default="pre-commit", help="Target hook (default: pre-commit)")
|
|
691
|
+
p_h_install.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
692
|
+
p_h_install.set_defaults(func=cmd_hook_install)
|
|
693
|
+
|
|
694
|
+
# hook uninstall
|
|
695
|
+
p_h_uninstall = p_hook_sub.add_parser("uninstall", help="Safely uninstall git hook (Rollback Resilience)")
|
|
696
|
+
p_h_uninstall.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
697
|
+
p_h_uninstall.add_argument("--hook", choices=["pre-commit", "pre-push"], default=None, help="Target hook (default: all installed)")
|
|
698
|
+
p_h_uninstall.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
699
|
+
p_h_uninstall.set_defaults(func=cmd_hook_uninstall)
|
|
700
|
+
|
|
701
|
+
# hook on / enable
|
|
702
|
+
p_h_on = p_hook_sub.add_parser("on", aliases=["enable"], help="Enable pre-commit verification")
|
|
703
|
+
p_h_on.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
704
|
+
p_h_on.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
705
|
+
p_h_on.set_defaults(func=cmd_hook_on)
|
|
706
|
+
|
|
707
|
+
# hook off / disable
|
|
708
|
+
p_h_off = p_hook_sub.add_parser("off", aliases=["disable"], help="Disable pre-commit verification")
|
|
709
|
+
p_h_off.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
710
|
+
p_h_off.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
711
|
+
p_h_off.set_defaults(func=cmd_hook_off)
|
|
712
|
+
|
|
713
|
+
# hook mode
|
|
714
|
+
p_h_mode = p_hook_sub.add_parser("mode", help="Switch mode between block and warn")
|
|
715
|
+
p_h_mode.add_argument("mode", choices=["block", "warn"], help="Hook mode: 'block' (strict exit 1) or 'warn' (advisory exit 0)")
|
|
716
|
+
p_h_mode.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
717
|
+
p_h_mode.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
718
|
+
p_h_mode.set_defaults(func=cmd_hook_mode)
|
|
719
|
+
|
|
720
|
+
# hook status
|
|
721
|
+
p_h_status = p_hook_sub.add_parser("status", help="Show hook installation and configuration status")
|
|
722
|
+
p_h_status.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
723
|
+
p_h_status.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
724
|
+
p_h_status.set_defaults(func=cmd_hook_status)
|
|
725
|
+
|
|
726
|
+
# hook run
|
|
727
|
+
p_h_run = p_hook_sub.add_parser("run", help="Run pre-commit hook verification")
|
|
728
|
+
p_h_run.add_argument("files", nargs="*", default=[], help="Optional files to verify")
|
|
729
|
+
p_h_run.add_argument("--workspace", "-w", help="Workspace root directory")
|
|
730
|
+
p_h_run.add_argument("--mode", choices=["block", "warn"], default=None, help="Override mode (block or warn)")
|
|
731
|
+
p_h_run.add_argument("--k", type=int, default=1, help="k-hop neighborhood radius (default: 1)")
|
|
732
|
+
p_h_run.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
733
|
+
p_h_run.set_defaults(func=cmd_hook_run)
|
|
734
|
+
|
|
735
|
+
# export-onnx
|
|
736
|
+
p_export = subparsers.add_parser(
|
|
737
|
+
"export-onnx",
|
|
738
|
+
help="Export ModernBERT PyTorch weights to ONNX FP32 and dynamic INT8 formats",
|
|
739
|
+
)
|
|
740
|
+
p_export.add_argument(
|
|
741
|
+
"--weights",
|
|
742
|
+
"-w",
|
|
743
|
+
default=None,
|
|
744
|
+
help="Source directory containing PyTorch model weights (default: weights_base or auto-resolved)",
|
|
745
|
+
)
|
|
746
|
+
p_export.add_argument(
|
|
747
|
+
"--output-dir",
|
|
748
|
+
"-o",
|
|
749
|
+
default=None,
|
|
750
|
+
help="Destination directory for exported ONNX models (default: weights directory)",
|
|
751
|
+
)
|
|
752
|
+
p_export.add_argument(
|
|
753
|
+
"--no-int8",
|
|
754
|
+
dest="quantize_int8",
|
|
755
|
+
action="store_false",
|
|
756
|
+
default=True,
|
|
757
|
+
help="Skip dynamic INT8 quantization",
|
|
758
|
+
)
|
|
759
|
+
p_export.add_argument(
|
|
760
|
+
"--no-verify",
|
|
761
|
+
dest="verify_parity",
|
|
762
|
+
action="store_false",
|
|
763
|
+
default=True,
|
|
764
|
+
help="Skip numeric parity verification against PyTorch",
|
|
765
|
+
)
|
|
766
|
+
p_export.add_argument(
|
|
767
|
+
"--opset",
|
|
768
|
+
type=int,
|
|
769
|
+
default=17,
|
|
770
|
+
help="ONNX opset version (default: 17)",
|
|
771
|
+
)
|
|
772
|
+
p_export.add_argument(
|
|
773
|
+
"--json",
|
|
774
|
+
action="store_true",
|
|
775
|
+
help="Output machine-readable JSON",
|
|
776
|
+
)
|
|
777
|
+
p_export.set_defaults(func=cmd_export_onnx)
|
|
778
|
+
|
|
779
|
+
return parser
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def main() -> None:
|
|
783
|
+
"""CLI entrypoint."""
|
|
784
|
+
parser = build_parser()
|
|
785
|
+
args = parser.parse_args()
|
|
786
|
+
|
|
787
|
+
if not hasattr(args, "func"):
|
|
788
|
+
parser.print_help(sys.stderr)
|
|
789
|
+
sys.exit(2)
|
|
790
|
+
|
|
791
|
+
sys.exit(args.func(args))
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
if __name__ == "__main__":
|
|
795
|
+
main()
|