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.
Files changed (40) hide show
  1. code_oracle/__init__.py +30 -0
  2. code_oracle/cli.py +795 -0
  3. code_oracle/config.py +145 -0
  4. code_oracle/dataset.py +5325 -0
  5. code_oracle/dead_code/__init__.py +32 -0
  6. code_oracle/dead_code/detector.py +379 -0
  7. code_oracle/dead_code/entrypoints.py +333 -0
  8. code_oracle/dead_code/models.py +255 -0
  9. code_oracle/dead_code/semantics.py +416 -0
  10. code_oracle/decision.py +906 -0
  11. code_oracle/engine.py +430 -0
  12. code_oracle/export_onnx.py +436 -0
  13. code_oracle/hook.py +531 -0
  14. code_oracle/indexer.py +894 -0
  15. code_oracle/languages/__init__.py +114 -0
  16. code_oracle/languages/common.py +127 -0
  17. code_oracle/languages/go.py +395 -0
  18. code_oracle/languages/python.py +336 -0
  19. code_oracle/languages/rust.py +474 -0
  20. code_oracle/languages/typescript.py +775 -0
  21. code_oracle/linearizer.py +166 -0
  22. code_oracle/locator.py +301 -0
  23. code_oracle/models.py +237 -0
  24. code_oracle/perf_lint/__init__.py +38 -0
  25. code_oracle/perf_lint/engine.py +234 -0
  26. code_oracle/perf_lint/models.py +229 -0
  27. code_oracle/perf_lint/rules/__init__.py +31 -0
  28. code_oracle/perf_lint/rules/async_blocking.py +143 -0
  29. code_oracle/perf_lint/rules/n_plus_one.py +232 -0
  30. code_oracle/perf_lint/rules/nested_loops.py +137 -0
  31. code_oracle/perf_lint/rules/unclosed_res.py +494 -0
  32. code_oracle/perf_lint/visitor.py +299 -0
  33. code_oracle/server.py +184 -0
  34. code_oracle/slicer.py +225 -0
  35. code_oracle/symbolic.py +459 -0
  36. code_oracle-0.1.0.dist-info/METADATA +225 -0
  37. code_oracle-0.1.0.dist-info/RECORD +40 -0
  38. code_oracle-0.1.0.dist-info/WHEEL +4 -0
  39. code_oracle-0.1.0.dist-info/entry_points.txt +2 -0
  40. code_oracle-0.1.0.dist-info/licenses/LICENSE +190 -0
code_oracle/hook.py ADDED
@@ -0,0 +1,531 @@
1
+ """
2
+ Git Pre-Commit Hook & Toggle System for Code Oracle.
3
+ Enforces invariant verification on staged files with atomic multi-file evaluation,
4
+ staged diff isolation, rollback resilience, and sub-50ms latency.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import stat
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional, Tuple
14
+
15
+ from code_oracle.config import find_git_root, get_config_path, load_config, resolve_workspace_root, save_config
16
+
17
+ HOOK_MARKER_BEGIN = "### BEGIN CODE ORACLE HOOK ###"
18
+ HOOK_MARKER_END = "### END CODE ORACLE HOOK ###"
19
+
20
+ HOOK_SCRIPT_TEMPLATE = """{begin_marker}
21
+ # Code Oracle Git Hook - Sub-50ms Neuro-Symbolic Verification Gate
22
+ # Fast bypass if skipped via environment (< 1ms)
23
+ case "${{CODE_ORACLE_SKIP:-0}}" in
24
+ 1|[tT][rR][uU][eE]|[yY][eE][sS])
25
+ exit 0
26
+ ;;
27
+ esac
28
+
29
+ # Fast shell bypass if disabled in config (< 1ms)
30
+ GIT_DIR_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
31
+ for CFG in "$GIT_DIR_ROOT/.code_oracle/config.json" "$GIT_DIR_ROOT/.code_oracle/hook_config.json"; do
32
+ if [ -f "$CFG" ]; then
33
+ if grep -q -i '"enabled"[[:space:]]*:[[:space:]]*false' "$CFG" 2>/dev/null; then
34
+ exit 0
35
+ fi
36
+ fi
37
+ done
38
+
39
+ # Execute Code Oracle hook verification
40
+ if [ -x "$GIT_DIR_ROOT/.venv/bin/code-oracle" ]; then
41
+ exec "$GIT_DIR_ROOT/.venv/bin/code-oracle" hook run "$@"
42
+ elif [ -x "$GIT_DIR_ROOT/venv/bin/code-oracle" ]; then
43
+ exec "$GIT_DIR_ROOT/venv/bin/code-oracle" hook run "$@"
44
+ elif command -v code-oracle >/dev/null 2>&1; then
45
+ exec code-oracle hook run "$@"
46
+ elif command -v python3 >/dev/null 2>&1; then
47
+ exec python3 -m code_oracle.cli hook run "$@"
48
+ elif command -v python >/dev/null 2>&1; then
49
+ exec python -m code_oracle.cli hook run "$@"
50
+ else
51
+ echo "Code Oracle: neither code-oracle nor python found in PATH." >&2
52
+ exit 0
53
+ fi
54
+ {end_marker}"""
55
+
56
+
57
+ def find_git_root(workspace_root: Optional[Path] = None) -> Optional[Path]:
58
+ """Find root directory of the git repository."""
59
+ ws = Path(workspace_root or Path.cwd()).resolve()
60
+ try:
61
+ res = subprocess.run(
62
+ ["git", "rev-parse", "--show-toplevel"],
63
+ cwd=str(ws),
64
+ capture_output=True,
65
+ text=True,
66
+ check=False,
67
+ )
68
+ if res.returncode == 0 and res.stdout.strip():
69
+ return Path(res.stdout.strip()).resolve()
70
+ except Exception:
71
+ pass
72
+
73
+ if (ws / ".git").exists():
74
+ return ws
75
+ for parent in ws.parents:
76
+ if (parent / ".git").exists():
77
+ return parent
78
+ return None
79
+
80
+
81
+ def get_git_hooks_dir(workspace_root: Optional[Path] = None) -> Optional[Path]:
82
+ """Locate the git hooks directory, respecting core.hooksPath if set."""
83
+ ws = Path(workspace_root or Path.cwd()).resolve()
84
+ try:
85
+ res = subprocess.run(
86
+ ["git", "rev-parse", "--git-path", "hooks"],
87
+ cwd=str(ws),
88
+ capture_output=True,
89
+ text=True,
90
+ check=False,
91
+ )
92
+ if res.returncode == 0 and res.stdout.strip():
93
+ p = Path(res.stdout.strip())
94
+ return p if p.is_absolute() else (ws / p).resolve()
95
+ except Exception:
96
+ pass
97
+
98
+ git_root = find_git_root(ws)
99
+ if git_root:
100
+ return git_root / ".git" / "hooks"
101
+ return None
102
+
103
+
104
+ def get_staged_files(git_root: Path) -> List[str]:
105
+ """Extract staged files via git diff --cached --name-only --diff-filter=ACMR."""
106
+ try:
107
+ res = subprocess.run(
108
+ ["git", "diff", "--cached", "--name-only", "--diff-filter=ACMR"],
109
+ cwd=str(git_root),
110
+ capture_output=True,
111
+ text=True,
112
+ check=False,
113
+ )
114
+ if res.returncode == 0:
115
+ return [line.strip().replace("\\", "/") for line in res.stdout.splitlines() if line.strip()]
116
+ except Exception:
117
+ pass
118
+ return []
119
+
120
+
121
+ def get_git_show_content(git_root: Path, ref_path: str) -> Optional[str]:
122
+ """Retrieve content from git object database (e.g. ':file' or 'HEAD:file')."""
123
+ try:
124
+ res = subprocess.run(
125
+ ["git", "show", ref_path],
126
+ cwd=str(git_root),
127
+ capture_output=True,
128
+ text=True,
129
+ check=False,
130
+ )
131
+ if res.returncode == 0:
132
+ return res.stdout
133
+ except Exception:
134
+ pass
135
+ return None
136
+
137
+
138
+ def get_unstaged_dirty_files(git_root: Path) -> List[str]:
139
+ """List files modified in the working tree relative to the git index."""
140
+ try:
141
+ res = subprocess.run(
142
+ ["git", "diff", "--name-only"],
143
+ cwd=str(git_root),
144
+ capture_output=True,
145
+ text=True,
146
+ check=False,
147
+ )
148
+ if res.returncode == 0:
149
+ return [line.strip().replace("\\", "/") for line in res.stdout.splitlines() if line.strip()]
150
+ except Exception:
151
+ pass
152
+ return []
153
+
154
+
155
+ def install_hook(
156
+ workspace_root: Optional[Path] = None,
157
+ hook_name: str = "pre-commit",
158
+ mode: Optional[str] = None,
159
+ ) -> Tuple[bool, str]:
160
+ """
161
+ Safely installs git hook non-destructively, preserving existing hook logic
162
+ by wrapping code within BEGIN/END markers.
163
+ """
164
+ git_root = find_git_root(workspace_root)
165
+ if not git_root:
166
+ return False, "Not a git repository (no .git directory found)."
167
+
168
+ hooks_dir = get_git_hooks_dir(git_root)
169
+ if not hooks_dir:
170
+ return False, "Could not locate git hooks directory."
171
+
172
+ hooks_dir.mkdir(parents=True, exist_ok=True)
173
+ hook_file = hooks_dir / hook_name
174
+
175
+ block = HOOK_SCRIPT_TEMPLATE.format(
176
+ begin_marker=HOOK_MARKER_BEGIN,
177
+ end_marker=HOOK_MARKER_END,
178
+ )
179
+
180
+ if hook_file.exists():
181
+ content = hook_file.read_text(encoding="utf-8")
182
+ if HOOK_MARKER_BEGIN in content and HOOK_MARKER_END in content:
183
+ # Replace existing block
184
+ pattern = re.compile(
185
+ re.escape(HOOK_MARKER_BEGIN) + r".*?" + re.escape(HOOK_MARKER_END),
186
+ re.DOTALL,
187
+ )
188
+ new_content = pattern.sub(block, content)
189
+ else:
190
+ prefix = content.rstrip()
191
+ if not prefix.startswith("#!"):
192
+ prefix = "#!/bin/sh\n\n" + prefix
193
+ new_content = prefix + "\n\n" + block + "\n"
194
+ else:
195
+ new_content = "#!/bin/sh\n\n" + block + "\n"
196
+
197
+ hook_file.write_text(new_content, encoding="utf-8")
198
+
199
+ # Make executable
200
+ current_mode = hook_file.stat().st_mode
201
+ hook_file.chmod(current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
202
+
203
+ # Update config: ensure enabled=True and set mode if specified
204
+ cfg = load_config(git_root)
205
+ cfg["enabled"] = True
206
+ if mode in ("block", "warn"):
207
+ cfg["mode"] = mode
208
+ save_config(git_root, cfg)
209
+
210
+ rel_hook = str(hook_file.relative_to(git_root))
211
+ return True, f"Code Oracle hook installed successfully to {rel_hook} (mode: {cfg['mode']})."
212
+
213
+
214
+ def uninstall_hook(
215
+ workspace_root: Optional[Path] = None,
216
+ hook_name: Optional[str] = None,
217
+ ) -> Tuple[bool, str]:
218
+ """
219
+ Cleanly removes only the Code Oracle hook block from .git/hooks/
220
+ (Rollback Resilience).
221
+ """
222
+ git_root = find_git_root(workspace_root)
223
+ if not git_root:
224
+ return False, "Not a git repository."
225
+
226
+ hooks_dir = get_git_hooks_dir(git_root)
227
+ if not hooks_dir or not hooks_dir.exists():
228
+ return False, "Git hooks directory not found."
229
+
230
+ target_hooks = [hook_name] if hook_name else ["pre-commit", "pre-push"]
231
+ uninstalled_from = []
232
+
233
+ for name in target_hooks:
234
+ h_file = hooks_dir / name
235
+ if not h_file.exists():
236
+ continue
237
+ content = h_file.read_text(encoding="utf-8")
238
+ if HOOK_MARKER_BEGIN in content:
239
+ pattern = re.compile(
240
+ r"\n?" + re.escape(HOOK_MARKER_BEGIN) + r".*?(?:" + re.escape(HOOK_MARKER_END) + r"|$)\n?",
241
+ re.DOTALL,
242
+ )
243
+ cleaned = pattern.sub("", content).strip()
244
+ non_comment_lines = [
245
+ line for line in cleaned.splitlines()
246
+ if line.strip() and not line.strip().startswith("#")
247
+ ]
248
+ if not non_comment_lines:
249
+ h_file.unlink()
250
+ else:
251
+ h_file.write_text(cleaned + "\n", encoding="utf-8")
252
+ uninstalled_from.append(name)
253
+
254
+ if uninstalled_from:
255
+ return True, f"Code Oracle hook uninstalled successfully from: {', '.join(uninstalled_from)}"
256
+ return False, f"Notice: Code Oracle hook block not found in {', '.join(target_hooks)}."
257
+
258
+
259
+ def get_hook_status(workspace_root: Optional[Path] = None) -> Dict[str, Any]:
260
+ """Retrieve current hook installation and toggle status."""
261
+ git_root = find_git_root(workspace_root)
262
+ cfg = load_config(git_root or workspace_root)
263
+
264
+ pre_commit_installed = False
265
+ pre_push_installed = False
266
+
267
+ if git_root:
268
+ hooks_dir = get_git_hooks_dir(git_root)
269
+ if hooks_dir and hooks_dir.exists():
270
+ pc = hooks_dir / "pre-commit"
271
+ if pc.exists() and HOOK_MARKER_BEGIN in pc.read_text(encoding="utf-8", errors="ignore"):
272
+ pre_commit_installed = True
273
+ pp = hooks_dir / "pre-push"
274
+ if pp.exists() and HOOK_MARKER_BEGIN in pp.read_text(encoding="utf-8", errors="ignore"):
275
+ pre_push_installed = True
276
+
277
+ installed = pre_commit_installed or pre_push_installed
278
+ ws_path = str(git_root or Path(workspace_root or Path.cwd()).resolve())
279
+ cfg_file = str(get_config_path(git_root or workspace_root))
280
+
281
+ return {
282
+ "is_git_repo": git_root is not None,
283
+ "workspace": ws_path,
284
+ "installed": installed,
285
+ "pre_commit_installed": pre_commit_installed,
286
+ "pre_push_installed": pre_push_installed,
287
+ "enabled": cfg.get("enabled", True),
288
+ "mode": cfg.get("mode", "block"),
289
+ "config_file": cfg_file,
290
+ }
291
+
292
+
293
+ def format_hook_output(result: Dict[str, Any]) -> str:
294
+ """Format hook result into clear terminal output with actionable unblock instructions."""
295
+ status = result.get("status", "APPROVED")
296
+ mode = result.get("mode", "block")
297
+ latency = result.get("latency_ms", 0.0)
298
+ staged_files = result.get("staged_files", [])
299
+ violations = result.get("violations", [])
300
+ cycles = result.get("cycles", [])
301
+
302
+ color_red = "\033[91m"
303
+ color_green = "\033[92m"
304
+ color_yellow = "\033[93m"
305
+ color_reset = "\033[0m"
306
+
307
+ border = "=" * 70
308
+ lines = []
309
+
310
+ if status == "SKIPPED":
311
+ reason = result.get("reason", "")
312
+ lines.append(f"{color_yellow}Code Oracle Pre-Commit Gate: Skipped ({reason}){color_reset}")
313
+ return "\n".join(lines)
314
+
315
+ if status == "APPROVED":
316
+ file_count = len(staged_files)
317
+ file_str = f"{file_count} staged {'file' if file_count == 1 else 'files'}"
318
+ lines.append(f"{color_green}{border}{color_reset}")
319
+ lines.append(f"{color_green}✔ CODE ORACLE PRE-COMMIT GATE: APPROVED ({file_str} verified in {latency:.1f} ms){color_reset}")
320
+ lines.append(f"{color_green}{border}{color_reset}")
321
+ return "\n".join(lines)
322
+
323
+ if status == "WARNING" or (violations and mode == "warn"):
324
+ v_count = len(violations)
325
+ v_str = f"{v_count} {'violation' if v_count == 1 else 'violations'}"
326
+ lines.append(f"{color_yellow}{border}{color_reset}")
327
+ lines.append(f"{color_yellow}⚠ CODE ORACLE PRE-COMMIT GATE: WARNING ({v_str}, {latency:.1f} ms){color_reset}")
328
+ lines.append(f"{color_yellow}{border}{color_reset}")
329
+
330
+ if violations:
331
+ lines.append("\nViolations:")
332
+ for v in violations:
333
+ lines.append(f" {color_yellow}⚠{color_reset} {v}")
334
+
335
+ if cycles:
336
+ lines.append("\nCycles Detected:")
337
+ for c in cycles:
338
+ lines.append(f" {color_yellow}↺{color_reset} {' -> '.join(c)} -> {c[0] if c else ''}")
339
+
340
+ lines.append("\nNotice: Commit proceeding because hook mode is set to 'warn'.")
341
+ lines.append("To switch to strict blocking mode:")
342
+ lines.append(" code-oracle hook mode block")
343
+ lines.append(f"{color_yellow}{border}{color_reset}")
344
+ return "\n".join(lines)
345
+
346
+ # REJECTED (mode == 'block')
347
+ v_count = len(violations)
348
+ v_str = f"{v_count} {'violation' if v_count == 1 else 'violations'}"
349
+ lines.append(f"{color_red}{border}{color_reset}")
350
+ lines.append(f"{color_red}✖ CODE ORACLE PRE-COMMIT GATE: REJECTED ({v_str}, {latency:.1f} ms){color_reset}")
351
+ lines.append(f"{color_red}{border}{color_reset}")
352
+
353
+ if violations:
354
+ lines.append("\nViolations:")
355
+ for v in violations:
356
+ lines.append(f" {color_red}✖{color_reset} {v}")
357
+
358
+ if cycles:
359
+ lines.append("\nCycles Detected:")
360
+ for c in cycles:
361
+ lines.append(f" {color_yellow}↺{color_reset} {' -> '.join(c)} -> {c[0] if c else ''}")
362
+
363
+ lines.append("\nTo unblock / bypass:")
364
+ lines.append(" • Bypass for current commit:")
365
+ lines.append(" git commit -n (or git commit --no-verify)")
366
+ lines.append(" CODE_ORACLE_SKIP=1 git commit")
367
+ lines.append(" • Toggle hook off:")
368
+ lines.append(" code-oracle hook off")
369
+ lines.append(" • Switch to warn mode:")
370
+ lines.append(" code-oracle hook mode warn")
371
+ lines.append(f"{color_red}{border}{color_reset}")
372
+
373
+ return "\n".join(lines)
374
+
375
+
376
+ def run_hook_verification(
377
+ workspace_root: Optional[Path] = None,
378
+ mode_override: Optional[str] = None,
379
+ k: int = 1,
380
+ files: Optional[List[str]] = None,
381
+ ) -> Dict[str, Any]:
382
+ """
383
+ Core runner called by pre-commit hooks.
384
+ Extracts staged diffs, isolates working tree dirty changes,
385
+ and runs atomic batch evaluation.
386
+ """
387
+ # Fast bypass check (< 1ms)
388
+ skip = os.environ.get("CODE_ORACLE_SKIP", "").strip().lower()
389
+ if skip in ("1", "true", "yes"):
390
+ return {
391
+ "status": "SKIPPED",
392
+ "mode": mode_override or "block",
393
+ "enabled": True,
394
+ "bypassed": True,
395
+ "reason": "CODE_ORACLE_SKIP",
396
+ "staged_files": [],
397
+ "violations": [],
398
+ "cycles": [],
399
+ "affected_symbols": [],
400
+ "latency_ms": 0.0,
401
+ "exit_code": 0,
402
+ }
403
+
404
+ git_root = find_git_root(workspace_root)
405
+ cfg = load_config(git_root or workspace_root)
406
+
407
+ if not cfg.get("enabled", True):
408
+ return {
409
+ "status": "SKIPPED",
410
+ "mode": cfg.get("mode", "block"),
411
+ "enabled": False,
412
+ "bypassed": True,
413
+ "reason": "hook disabled in configuration",
414
+ "staged_files": [],
415
+ "violations": [],
416
+ "cycles": [],
417
+ "affected_symbols": [],
418
+ "latency_ms": 0.0,
419
+ "exit_code": 0,
420
+ }
421
+
422
+ mode = mode_override or cfg.get("mode", "block")
423
+ if mode not in ("block", "warn"):
424
+ mode = "block"
425
+
426
+ if not git_root:
427
+ return {
428
+ "status": "APPROVED",
429
+ "mode": mode,
430
+ "enabled": True,
431
+ "bypassed": False,
432
+ "reason": "not a git repository",
433
+ "staged_files": [],
434
+ "violations": [],
435
+ "cycles": [],
436
+ "affected_symbols": [],
437
+ "latency_ms": 0.0,
438
+ "exit_code": 0,
439
+ }
440
+
441
+ staged_all = get_staged_files(git_root)
442
+ staged_py = [f for f in staged_all if f.endswith(".py")]
443
+
444
+ if files:
445
+ # Only filter if files contains actual Python file targets
446
+ # Non-Python arguments (such as git pre-push remote name/url) are ignored
447
+ py_candidates = [f for f in files if f.endswith(".py")]
448
+ if py_candidates:
449
+ resolved_files = set()
450
+ ws = Path(workspace_root or Path.cwd()).resolve()
451
+ for f in py_candidates:
452
+ p = Path(f)
453
+ if not p.is_absolute():
454
+ p = (ws / f).resolve()
455
+ try:
456
+ resolved_files.add(str(p.relative_to(git_root)).replace("\\", "/"))
457
+ except ValueError:
458
+ resolved_files.add(f.replace("\\", "/"))
459
+ staged_py = [f for f in staged_py if f in resolved_files]
460
+
461
+ if not staged_py:
462
+ return {
463
+ "status": "APPROVED",
464
+ "mode": mode,
465
+ "enabled": True,
466
+ "bypassed": False,
467
+ "staged_files": [],
468
+ "violations": [],
469
+ "cycles": [],
470
+ "affected_symbols": [],
471
+ "latency_ms": 0.0,
472
+ "exit_code": 0,
473
+ }
474
+
475
+ # Extract staged contents and HEAD contents for diff isolation
476
+ file_patches = []
477
+ for f in staged_py:
478
+ staged_content = get_git_show_content(git_root, f":{f}")
479
+ if staged_content is None:
480
+ continue
481
+ head_content = get_git_show_content(git_root, f"HEAD:{f}")
482
+ file_patches.append({
483
+ "file_path": f,
484
+ "patch_content": staged_content,
485
+ "original_content": head_content if head_content is not None else "",
486
+ "is_replacement": True,
487
+ })
488
+
489
+ # Unstaged dirty isolation: extract staged index contents for unstaged dirty files
490
+ unstaged_dirty = get_unstaged_dirty_files(git_root)
491
+ dirty_overlays: Dict[str, str] = {}
492
+ for uf in unstaged_dirty:
493
+ if uf.endswith(".py") and uf not in staged_py:
494
+ idx_content = get_git_show_content(git_root, f":{uf}")
495
+ if idx_content is not None:
496
+ dirty_overlays[uf] = idx_content
497
+
498
+ from code_oracle.engine import TopoSliceEngine
499
+
500
+ engine = TopoSliceEngine(workspace_root=git_root)
501
+ report = engine.verify_batch(
502
+ file_patches=file_patches,
503
+ dirty_overlays=dirty_overlays,
504
+ k=k,
505
+ )
506
+
507
+ has_violations = bool(report.invariant_violations or report.cycles_detected)
508
+
509
+ if has_violations:
510
+ if mode == "block":
511
+ status = "REJECTED"
512
+ exit_code = 1
513
+ else:
514
+ status = "WARNING"
515
+ exit_code = 0
516
+ else:
517
+ status = "APPROVED"
518
+ exit_code = 0
519
+
520
+ return {
521
+ "status": status,
522
+ "mode": mode,
523
+ "enabled": True,
524
+ "bypassed": False,
525
+ "exit_code": exit_code,
526
+ "staged_files": staged_py,
527
+ "violations": report.invariant_violations,
528
+ "cycles": report.cycles_detected,
529
+ "affected_symbols": report.affected_symbols,
530
+ "latency_ms": report.latency_ms,
531
+ }