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/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
|
+
}
|