git-oops 1.0.0__tar.gz → 1.0.1__tar.gz
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.
- {git_oops-1.0.0 → git_oops-1.0.1}/PKG-INFO +5 -3
- {git_oops-1.0.0 → git_oops-1.0.1}/README.md +4 -2
- git_oops-1.0.1/git_oops/__init__.py +16 -0
- git_oops-1.0.1/git_oops/__main__.py +9 -0
- git_oops-1.0.1/git_oops/cli.py +556 -0
- git_oops-1.0.1/git_oops/consensus.py +58 -0
- git_oops-1.0.1/git_oops/engine.py +222 -0
- git_oops-1.0.1/git_oops/hooks.py +517 -0
- git_oops-1.0.1/git_oops/hud.py +493 -0
- git_oops-1.0.1/git_oops/mcp_server.py +124 -0
- git_oops-1.0.1/git_oops/reviewer.py +157 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/git_oops.egg-info/PKG-INFO +5 -3
- {git_oops-1.0.0 → git_oops-1.0.1}/git_oops.egg-info/SOURCES.txt +27 -0
- git_oops-1.0.1/git_oops.egg-info/entry_points.txt +2 -0
- git_oops-1.0.1/git_oops.egg-info/top_level.txt +2 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/pyproject.toml +2 -2
- {git_oops-1.0.0 → git_oops-1.0.1}/setup.py +3 -3
- {git_oops-1.0.0 → git_oops-1.0.1}/src/__init__.py +2 -2
- git_oops-1.0.1/src/blackbox.py +119 -0
- git_oops-1.0.1/src/circuit_breaker.py +129 -0
- git_oops-1.0.1/src/firewall.py +48 -0
- git_oops-1.0.1/src/guardian.py +589 -0
- git_oops-1.0.1/src/hardware.py +137 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/hooks.py +6 -14
- git_oops-1.0.1/src/local_llm.py +116 -0
- git_oops-1.0.1/src/replay.py +100 -0
- git_oops-1.0.1/src/rules.py +291 -0
- git_oops-1.0.1/src/salvage.py +92 -0
- git_oops-1.0.1/src/sentinel.py +79 -0
- git_oops-1.0.1/src/shadow.py +70 -0
- git_oops-1.0.1/src/solver.py +158 -0
- git_oops-1.0.1/src/squeezer.py +64 -0
- git_oops-1.0.1/src/supply_chain.py +142 -0
- git_oops-1.0.1/src/tester.py +54 -0
- git_oops-1.0.1/src/ui.py +124 -0
- git_oops-1.0.1/src/updater.py +49 -0
- git_oops-1.0.1/src/watchdog.py +96 -0
- git_oops-1.0.0/git_oops.egg-info/entry_points.txt +0 -2
- git_oops-1.0.0/git_oops.egg-info/top_level.txt +0 -1
- {git_oops-1.0.0 → git_oops-1.0.1}/LICENSE +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/blackbox.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/circuit_breaker.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/firewall.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/guardian.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/hardware.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/local_llm.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/replay.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/rules.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/salvage.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/sentinel.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/shadow.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/solver.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/squeezer.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/supply_chain.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/tester.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/ui.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/updater.py +0 -0
- {git_oops-1.0.0/src → git_oops-1.0.1/git_oops}/watchdog.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/git_oops.egg-info/dependency_links.txt +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/git_oops.egg-info/requires.txt +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/setup.cfg +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/cli.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/consensus.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/engine.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/hud.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/mcp_server.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/src/reviewer.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/tests/test_agent_harness.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/tests/test_oops.py +0 -0
- {git_oops-1.0.0 → git_oops-1.0.1}/tests/test_v1_legend.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-oops
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: The Dual-Mode Terminal Crash Fixer & Autonomous AI Agent Safety Harness (The Final Legend Edition).
|
|
5
5
|
Author: Hao
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,7 +14,7 @@ Dynamic: requires-python
|
|
|
14
14
|
|
|
15
15
|
<div align="center">
|
|
16
16
|
|
|
17
|
-
# ⚡ git-oops v1.0.
|
|
17
|
+
# ⚡ git-oops v1.0.1 (The Final Legend Edition)
|
|
18
18
|
|
|
19
19
|
### *The Zero-Step Autonomous AI Agent Safety Harness & Terminal Crash Interceptor.*
|
|
20
20
|
**Human Mode: Roasted by a Staff Engineer │ Agent Mode: The Headless Shield for Coding Agents**
|
|
@@ -24,7 +24,9 @@ Dynamic: requires-python
|
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
26
|
[](https://github.com/Hao610/git-oops/actions/workflows/ci.yml)
|
|
27
|
-
[](https://github.com/Hao610/git-oops/releases)
|
|
28
|
+
[](https://pypi.org/project/git-oops/)
|
|
29
|
+
[](https://pypi.org/project/git-oops/)
|
|
28
30
|
[](LICENSE)
|
|
29
31
|
[](https://www.python.org/)
|
|
30
32
|
[]()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# ⚡ git-oops v1.0.
|
|
3
|
+
# ⚡ git-oops v1.0.1 (The Final Legend Edition)
|
|
4
4
|
|
|
5
5
|
### *The Zero-Step Autonomous AI Agent Safety Harness & Terminal Crash Interceptor.*
|
|
6
6
|
**Human Mode: Roasted by a Staff Engineer │ Agent Mode: The Headless Shield for Coding Agents**
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
[](https://github.com/Hao610/git-oops/actions/workflows/ci.yml)
|
|
13
|
-
[](https://github.com/Hao610/git-oops/releases)
|
|
14
|
+
[](https://pypi.org/project/git-oops/)
|
|
15
|
+
[](https://pypi.org/project/git-oops/)
|
|
14
16
|
[](LICENSE)
|
|
15
17
|
[](https://www.python.org/)
|
|
16
18
|
[]()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
git-oops: The Autonomous Terminal Crash Fixer & AI Agent Safety Harness.
|
|
3
|
+
Version 1.0.1 (The Final Legend Edition)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "1.0.1"
|
|
7
|
+
|
|
8
|
+
from git_oops.hooks import ensure_auto_hooks
|
|
9
|
+
from git_oops.rules import trigger_ota_update_background
|
|
10
|
+
|
|
11
|
+
# Auto-bootstrap zero-step hooks & OTA rules on package import
|
|
12
|
+
try:
|
|
13
|
+
ensure_auto_hooks()
|
|
14
|
+
trigger_ota_update_background()
|
|
15
|
+
except Exception:
|
|
16
|
+
pass
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git-Oops CLI Entrypoint
|
|
3
|
+
Interactive terminal UX with rich color support and safety controls.
|
|
4
|
+
Usage:
|
|
5
|
+
git-oops --file src/app.py --error "TypeError: ..."
|
|
6
|
+
git-oops --status
|
|
7
|
+
git-oops --set-budget 2.0
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import argparse
|
|
14
|
+
import subprocess
|
|
15
|
+
|
|
16
|
+
# Ensure standard UTF-8 output on Windows consoles to prevent cp1252 charmap crashes
|
|
17
|
+
if sys.platform == "win32":
|
|
18
|
+
try:
|
|
19
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
20
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
21
|
+
except Exception:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
from git_oops.circuit_breaker import get_daily_spend, get_default_config, set_config_value
|
|
25
|
+
from git_oops.engine import diagnose_and_patch, apply_patch_to_file, rollback_file
|
|
26
|
+
from git_oops.firewall import check_command_safety
|
|
27
|
+
from git_oops.squeezer import compress_traceback, redact_secrets
|
|
28
|
+
from git_oops.watchdog import check_anti_thrashing, acquire_agent_lock, release_agent_lock
|
|
29
|
+
from git_oops.guardian import verify_patch_in_sandbox, check_ghost_imports
|
|
30
|
+
from git_oops.blackbox import record_step, rewind_step, list_flight_events, register_antibody, match_antibody
|
|
31
|
+
from git_oops.mcp_server import run_mcp_server
|
|
32
|
+
from git_oops.shadow import ShadowWorkspace
|
|
33
|
+
from git_oops.consensus import arbitrate_consensus
|
|
34
|
+
from git_oops.sentinel import check_egress_security, check_license_taint, check_code_complexity, IntentPrefetchEngine
|
|
35
|
+
from git_oops.hud import start_hud_server
|
|
36
|
+
from git_oops.hooks import run_l3_preflight, run_pre_push_check, setup_global_hooks, ensure_auto_hooks
|
|
37
|
+
from git_oops.rules import trigger_ota_update_background, sync_security_rules_foreground
|
|
38
|
+
from git_oops.replay import inspect_crash_snapshot
|
|
39
|
+
from git_oops.salvage import run_reflog_autopsy
|
|
40
|
+
from git_oops.solver import solve_with_ast
|
|
41
|
+
from git_oops.supply_chain import scan_dependencies_file
|
|
42
|
+
from git_oops.reviewer import review_pull_request, format_pr_review_markdown
|
|
43
|
+
from git_oops.hardware import get_hardware_telemetry
|
|
44
|
+
from git_oops.tester import synthesize_regression_test
|
|
45
|
+
from git_oops.local_llm import probe_local_llm, call_local_llm
|
|
46
|
+
from git_oops.ui import (
|
|
47
|
+
print_banner,
|
|
48
|
+
draw_card,
|
|
49
|
+
render_diff,
|
|
50
|
+
spin_task,
|
|
51
|
+
FG_CYAN,
|
|
52
|
+
FG_MAGENTA,
|
|
53
|
+
FG_YELLOW,
|
|
54
|
+
FG_GREEN,
|
|
55
|
+
FG_RED,
|
|
56
|
+
FG_WHITE,
|
|
57
|
+
FG_GRAY,
|
|
58
|
+
BOLD,
|
|
59
|
+
ITALIC,
|
|
60
|
+
RESET
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main():
|
|
65
|
+
saved_cfg = get_default_config()
|
|
66
|
+
|
|
67
|
+
parser = argparse.ArgumentParser(
|
|
68
|
+
prog="git-oops",
|
|
69
|
+
description="Brutally honest terminal crash fixer with daily spend-protection circuit breaker."
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument("-f", "--file", help="Path to the file that crashed", default=None)
|
|
72
|
+
parser.add_argument("-e", "--error", help="Error message or traceback string", default=None)
|
|
73
|
+
parser.add_argument("-b", "--budget", help="Daily API spend ceiling in USD (default: $1.00)", type=float, default=saved_cfg.get("daily_budget", 1.0))
|
|
74
|
+
parser.add_argument("-p", "--personality", help="Roast persona: 'staff', 'kernel', 'chef', or 'passive_aggressive'", default=saved_cfg.get("personality", "staff"), choices=["staff", "kernel", "chef", "passive_aggressive"])
|
|
75
|
+
parser.add_argument("--set-personality", help="Permanently save default roast persona", choices=["staff", "kernel", "chef", "passive_aggressive"])
|
|
76
|
+
parser.add_argument("--set-budget", help="Permanently save default daily spend budget in USD", type=float)
|
|
77
|
+
parser.add_argument("-y", "--yes", help="Auto-apply patch without interactive prompt", action="store_true")
|
|
78
|
+
parser.add_argument("--undo", help="Roll back the last applied patch on target file", default=None)
|
|
79
|
+
parser.add_argument("--run", help="Execute a command and auto-catch its crash (e.g. --run 'python app.py')", default=None)
|
|
80
|
+
parser.add_argument("--status", help="Show today's accumulated API spend", action="store_true")
|
|
81
|
+
parser.add_argument("--agent", "--json", dest="agent_mode", help="Headless Agent mode: emits machine-readable JSON without ANSI codes", action="store_true")
|
|
82
|
+
parser.add_argument("--mcp", help="Start git-oops as a native Model Context Protocol (MCP) server over stdio", action="store_true")
|
|
83
|
+
parser.add_argument("--rewind", help="Flight Recorder: rewind codebase to a previous step ID", type=int, default=None)
|
|
84
|
+
parser.add_argument("--flight-log", help="Display recent blackbox flight recorder steps", action="store_true")
|
|
85
|
+
parser.add_argument("--shadow", help="Run repair verification inside an isolated temporary shadow workspace", action="store_true")
|
|
86
|
+
parser.add_argument("--consensus", help="Multi-LLM cross-arbitration: require two models to reach consensus before patching", action="store_true")
|
|
87
|
+
|
|
88
|
+
parser.add_argument("--hud", help="Start Obsidian Glassmorphism Web HUD server", action="store_true")
|
|
89
|
+
parser.add_argument("--pre-commit", help="Execute sub-second L3 pre-commit security pre-flight", action="store_true")
|
|
90
|
+
parser.add_argument("--pre-push", help="Execute pre-push branch and outgoing commit verification", action="store_true")
|
|
91
|
+
parser.add_argument("--install-hooks", help="Force install machine-wide git hooks via core.hooksPath", action="store_true")
|
|
92
|
+
parser.add_argument("--replay", help="Inspect crash variable snapshot (e.g. --replay crash_123.json)", default=None)
|
|
93
|
+
parser.add_argument("--salvage", help="Execute git reflog autopsy to rescue lost commits", action="store_true")
|
|
94
|
+
parser.add_argument("--solver", help="Zero-AI 1ms deterministic AST symbolic crash solver", action="store_true")
|
|
95
|
+
parser.add_argument("--supply-chain", help="Audit dependencies file for typosquatting & vulnerabilities", default=None)
|
|
96
|
+
parser.add_argument("--review-pr", help="Pre-PR gatekeeper audit against target branch (default: origin/main)", nargs="?", const="DEFAULT", default=None)
|
|
97
|
+
parser.add_argument("--hardware", help="Display hardware telemetry and CPU throttling metrics", action="store_true")
|
|
98
|
+
parser.add_argument("--test-gen", help="Generate permanent regression test for error", action="store_true")
|
|
99
|
+
parser.add_argument("--local", "--ollama", dest="local_mode", help="Use local offline LLM (Ollama/vLLM) instead of cloud APIs", action="store_true")
|
|
100
|
+
parser.add_argument("--update-rules", help="Force OTA update of L3 dynamic security rules", action="store_true")
|
|
101
|
+
|
|
102
|
+
args = parser.parse_args()
|
|
103
|
+
|
|
104
|
+
# Zero-step background bootstrap: auto-hooks & OTA rules
|
|
105
|
+
ensure_auto_hooks()
|
|
106
|
+
trigger_ota_update_background()
|
|
107
|
+
|
|
108
|
+
# Force OTA rule update
|
|
109
|
+
if args.update_rules:
|
|
110
|
+
ok, msg = sync_security_rules_foreground(force=True)
|
|
111
|
+
if args.agent_mode:
|
|
112
|
+
print(json.dumps({"success": ok, "message": msg}))
|
|
113
|
+
else:
|
|
114
|
+
print_banner()
|
|
115
|
+
draw_card("DYNAMIC OTA SECURITY RULES", msg, color_border=FG_GREEN if ok else FG_YELLOW, color_title=FG_GREEN if ok else FG_YELLOW)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Obsidian Web HUD Mode
|
|
119
|
+
if args.hud:
|
|
120
|
+
start_hud_server()
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# L3 Pre-Commit Hook Mode
|
|
124
|
+
if args.pre_commit:
|
|
125
|
+
passed, violations, report = run_l3_preflight()
|
|
126
|
+
if args.agent_mode:
|
|
127
|
+
print(json.dumps({"passed": passed, "violations_count": len(violations), "violations": violations}))
|
|
128
|
+
else:
|
|
129
|
+
print(report)
|
|
130
|
+
sys.exit(0 if passed else 1)
|
|
131
|
+
|
|
132
|
+
# Pre-Push Hook Mode
|
|
133
|
+
if args.pre_push:
|
|
134
|
+
passed, report = run_pre_push_check()
|
|
135
|
+
if args.agent_mode:
|
|
136
|
+
print(json.dumps({"passed": passed, "report": report}))
|
|
137
|
+
else:
|
|
138
|
+
print(report)
|
|
139
|
+
sys.exit(0 if passed else 1)
|
|
140
|
+
|
|
141
|
+
# Force Install Hooks
|
|
142
|
+
if args.install_hooks:
|
|
143
|
+
ok, msg = setup_global_hooks(force=True)
|
|
144
|
+
if args.agent_mode:
|
|
145
|
+
print(json.dumps({"success": ok, "message": msg}))
|
|
146
|
+
else:
|
|
147
|
+
print_banner()
|
|
148
|
+
draw_card("GLOBAL GIT HOOKS", msg, color_border=FG_GREEN if ok else FG_RED, color_title=FG_GREEN if ok else FG_RED)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Replay Crash Snapshot
|
|
152
|
+
if args.replay:
|
|
153
|
+
snap = inspect_crash_snapshot(args.replay)
|
|
154
|
+
if args.agent_mode:
|
|
155
|
+
print(json.dumps(snap))
|
|
156
|
+
else:
|
|
157
|
+
print_banner()
|
|
158
|
+
if snap.get("status") == "NOT_FOUND":
|
|
159
|
+
draw_card("CRASH REPLAY", f"Snapshot '{args.replay}' not found.", color_border=FG_RED, color_title=FG_RED)
|
|
160
|
+
else:
|
|
161
|
+
body = f"Timestamp: {snap.get('timestamp')}\nTarget: {snap.get('target_file')}\nException: {snap.get('exception_type')}: {snap.get('exception_message')}\n\nLocal Variables:\n{json.dumps(snap.get('local_variables', {}), indent=2)}"
|
|
162
|
+
draw_card("CRASH REPLAY TIME MACHINE", body, color_border=FG_CYAN, color_title=FG_CYAN)
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
# Reflog Salvage Autopsy
|
|
166
|
+
if args.salvage:
|
|
167
|
+
recovered = run_reflog_autopsy()
|
|
168
|
+
if args.agent_mode:
|
|
169
|
+
print(json.dumps({"recovered_count": len(recovered), "commits": recovered}))
|
|
170
|
+
else:
|
|
171
|
+
print_banner()
|
|
172
|
+
if recovered:
|
|
173
|
+
commits_str = "\n".join([f"• [{c['sha'][:7]}] {c['subject']} (Lost at: {c['reflog_action']})" for c in recovered[:10]])
|
|
174
|
+
draw_card("REFLOG AUTOPSY RESCUE", f"Rescued {len(recovered)} lost commit snapshots:\n\n{commits_str}", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
175
|
+
else:
|
|
176
|
+
draw_card("REFLOG AUTOPSY", "No lost or orphaned commits detected in reflog.", color_border=FG_CYAN, color_title=FG_CYAN)
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Supply Chain Audit
|
|
180
|
+
if args.supply_chain:
|
|
181
|
+
results = scan_dependencies_file(args.supply_chain)
|
|
182
|
+
if args.agent_mode:
|
|
183
|
+
print(json.dumps(results))
|
|
184
|
+
else:
|
|
185
|
+
print_banner()
|
|
186
|
+
res_str = f"Scanned {results['scanned_packages_count']} dependencies in '{args.supply_chain}'.\n\n"
|
|
187
|
+
if results["violations"]:
|
|
188
|
+
res_str += "🚨 VIOLATIONS DETECTED:\n" + "\n".join([f"• {v['package']}: {v['details']}" for v in results["violations"]])
|
|
189
|
+
draw_card("SUPPLY CHAIN SECURITY ALERT", res_str, color_border=FG_RED, color_title=FG_RED)
|
|
190
|
+
else:
|
|
191
|
+
res_str += "✅ Zero typosquatting or malicious packages detected."
|
|
192
|
+
draw_card("SUPPLY CHAIN SECURITY PASSED", res_str, color_border=FG_GREEN, color_title=FG_GREEN)
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
# Pre-PR Gatekeeper Review
|
|
196
|
+
if args.review_pr is not None:
|
|
197
|
+
target_b = None if args.review_pr == "DEFAULT" else args.review_pr
|
|
198
|
+
report = review_pull_request(target_b)
|
|
199
|
+
if args.agent_mode:
|
|
200
|
+
print(json.dumps(report))
|
|
201
|
+
else:
|
|
202
|
+
print_banner()
|
|
203
|
+
md = format_pr_review_markdown(report)
|
|
204
|
+
border_c = FG_GREEN if report["verdict"] == "APPROVED" else (FG_YELLOW if report["verdict"] == "WARNINGS" else FG_RED)
|
|
205
|
+
draw_card("PRE-PR GATEKEEPER AUDIT", md, color_border=border_c, color_title=border_c)
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# Hardware Telemetry
|
|
209
|
+
if args.hardware:
|
|
210
|
+
hw = get_hardware_telemetry()
|
|
211
|
+
if args.agent_mode:
|
|
212
|
+
print(json.dumps(hw))
|
|
213
|
+
else:
|
|
214
|
+
print_banner()
|
|
215
|
+
b_text = f"{hw['battery_percent']}%" if hw['battery_percent'] else "N/A"
|
|
216
|
+
hw_str = (
|
|
217
|
+
f"Operating System : {hw['platform']}\n"
|
|
218
|
+
f"Battery Level : {b_text} (Plugged in: {hw['power_plugged']})\n"
|
|
219
|
+
f"Process Priority : {hw['process_priority']}\n"
|
|
220
|
+
f"Throttle Active : {'YES' if hw['is_throttled'] else 'NO'}"
|
|
221
|
+
)
|
|
222
|
+
draw_card("HARDWARE & THROTTLE TELEMETRY", hw_str, color_border=FG_CYAN, color_title=FG_CYAN)
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# MCP Server Mode
|
|
226
|
+
if args.mcp:
|
|
227
|
+
run_mcp_server()
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
# Flight Recorder Rewind
|
|
231
|
+
if args.rewind is not None:
|
|
232
|
+
ok = rewind_step(args.rewind)
|
|
233
|
+
if args.agent_mode:
|
|
234
|
+
print(json.dumps({"success": ok, "rewound_to_step": args.rewind}))
|
|
235
|
+
else:
|
|
236
|
+
print_banner()
|
|
237
|
+
if ok:
|
|
238
|
+
draw_card("FLIGHT RECORDER REWIND", f"Successfully rewound file to snapshot from step {args.rewind}.", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
239
|
+
else:
|
|
240
|
+
draw_card("REWIND FAILED", f"Could not find snapshot for step {args.rewind}.", color_border=FG_RED, color_title=FG_RED)
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
# Flight Log display
|
|
244
|
+
if args.flight_log:
|
|
245
|
+
events = list_flight_events(limit=10)
|
|
246
|
+
if args.agent_mode:
|
|
247
|
+
print(json.dumps({"events": events}))
|
|
248
|
+
else:
|
|
249
|
+
print_banner()
|
|
250
|
+
log_str = "\n".join([f"• [{e['step_id']}] {e['timestamp']} - {e['action']} on {e['target_file']}: {e['motivation']}" for e in events]) if events else "No events recorded yet."
|
|
251
|
+
draw_card("BLACKBOX FLIGHT RECORDER LOG", log_str, color_border=FG_CYAN, color_title=FG_CYAN)
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
# Save permanent config
|
|
255
|
+
if args.set_personality:
|
|
256
|
+
set_config_value("personality", args.set_personality)
|
|
257
|
+
print_banner()
|
|
258
|
+
draw_card(
|
|
259
|
+
"CONFIGURATION SAVED",
|
|
260
|
+
f"Default persona updated to: {FG_YELLOW}{BOLD}{args.set_personality}{RESET}\nAll future crashes will be roasted in this style.",
|
|
261
|
+
color_border=FG_GREEN,
|
|
262
|
+
color_title=FG_GREEN
|
|
263
|
+
)
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
if args.set_budget is not None:
|
|
267
|
+
set_config_value("daily_budget", args.set_budget)
|
|
268
|
+
print_banner()
|
|
269
|
+
draw_card(
|
|
270
|
+
"CIRCUIT BREAKER UPDATED",
|
|
271
|
+
f"Daily spending limit hard-capped at: {FG_GREEN}{BOLD}${args.set_budget:.2f} USD{RESET}\nRequests will automatically abort if spend exceeds this threshold.",
|
|
272
|
+
color_border=FG_GREEN,
|
|
273
|
+
color_title=FG_GREEN
|
|
274
|
+
)
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
# Budget status check
|
|
278
|
+
if args.status:
|
|
279
|
+
spent = get_daily_spend()
|
|
280
|
+
print_banner()
|
|
281
|
+
status_text = (
|
|
282
|
+
f"Accumulated Spend Today : {FG_GREEN}{BOLD}${spent:.5f} USD{RESET}\n"
|
|
283
|
+
f"Daily Safety Ceiling : {FG_YELLOW}{BOLD}${args.budget:.2f} USD{RESET}\n"
|
|
284
|
+
f"Remaining Budget Safe : {FG_CYAN}{BOLD}${max(0.0, args.budget - spent):.5f} USD{RESET}\n"
|
|
285
|
+
f"Active Roast Persona : {FG_MAGENTA}{BOLD}{args.personality}{RESET}"
|
|
286
|
+
)
|
|
287
|
+
draw_card("CIRCUIT BREAKER METRICS", status_text, color_border=FG_CYAN, color_title=FG_WHITE)
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
# Undo / Rollback
|
|
291
|
+
if args.undo:
|
|
292
|
+
success = rollback_file(args.undo)
|
|
293
|
+
print_banner()
|
|
294
|
+
if success:
|
|
295
|
+
draw_card("SAFETY ROLLBACK", f"Successfully restored {BOLD}'{args.undo}'{RESET} from its previous backup snapshot.\nOriginal code state reinstated.", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
296
|
+
else:
|
|
297
|
+
draw_card("ROLLBACK FAILED", f"No safety backup found for {BOLD}'{args.undo}'{RESET}.\nExpected snapshot: '{args.undo}.oops.bak'", color_border=FG_RED, color_title=FG_RED)
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# Auto-run execution
|
|
301
|
+
if args.run:
|
|
302
|
+
# 1. Check Blast-Radius Firewall
|
|
303
|
+
safe, reason = check_command_safety(args.run)
|
|
304
|
+
if not safe:
|
|
305
|
+
if args.agent_mode:
|
|
306
|
+
print(json.dumps({"status": "BLOCKED", "reason": reason}))
|
|
307
|
+
else:
|
|
308
|
+
print_banner()
|
|
309
|
+
draw_card("BLAST-RADIUS FIREWALL VIOLATION", reason, color_border=FG_RED, color_title=FG_RED)
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
if not args.agent_mode:
|
|
313
|
+
print_banner()
|
|
314
|
+
draw_card("COMMAND RUNNER", f"Executing: {args.run}\nInterception active.", color_border=FG_CYAN, color_title=FG_CYAN)
|
|
315
|
+
|
|
316
|
+
proc = subprocess.run(args.run, shell=True, capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
317
|
+
if proc.returncode == 0:
|
|
318
|
+
if args.agent_mode:
|
|
319
|
+
print(json.dumps({"status": "SUCCESS", "exit_code": 0, "stdout": proc.stdout}))
|
|
320
|
+
else:
|
|
321
|
+
print(proc.stdout)
|
|
322
|
+
draw_card("EXECUTION SUCCESS", "Command completed cleanly without errors.", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
# Captured an error -> Compress and Redact Secrets
|
|
326
|
+
raw_err = (proc.stderr or "") + "\n" + (proc.stdout or "")
|
|
327
|
+
compressed_err = compress_traceback(raw_err)
|
|
328
|
+
|
|
329
|
+
if not args.file:
|
|
330
|
+
import re
|
|
331
|
+
file_matches = re.findall(r'File "([^"]+)", line \d+', raw_err)
|
|
332
|
+
if file_matches:
|
|
333
|
+
for cand in reversed(file_matches):
|
|
334
|
+
if os.path.exists(cand):
|
|
335
|
+
args.file = cand
|
|
336
|
+
break
|
|
337
|
+
|
|
338
|
+
args.error = compressed_err
|
|
339
|
+
|
|
340
|
+
# Check inputs
|
|
341
|
+
if not args.file or not args.error:
|
|
342
|
+
# Check if piped from stdin
|
|
343
|
+
if not sys.stdin.isatty():
|
|
344
|
+
piped_error = sys.stdin.read().strip()
|
|
345
|
+
args.error = piped_error
|
|
346
|
+
|
|
347
|
+
if not args.file or not args.error:
|
|
348
|
+
print(f"{RED}Error: Must provide --file and --error (or pipe error via stdin).{RESET}")
|
|
349
|
+
print(f"Example: {CYAN}git-oops --file app.py --error \"NameError: name 'x' is not defined\"{RESET}")
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
filepath = args.file
|
|
353
|
+
if not os.path.exists(filepath):
|
|
354
|
+
if args.agent_mode:
|
|
355
|
+
print(json.dumps({"error": f"File '{filepath}' not found"}))
|
|
356
|
+
else:
|
|
357
|
+
print(f"{FG_RED}Error: File '{filepath}' not found.{RESET}")
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
361
|
+
file_content = f.read()
|
|
362
|
+
|
|
363
|
+
# Check for Antibody Match
|
|
364
|
+
antibody = match_antibody(args.error)
|
|
365
|
+
if antibody and not args.agent_mode:
|
|
366
|
+
draw_card("ANTIBODY IMMUNITY RECALL", f"Recognized known architectural pattern:\n{antibody['explanation']}", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
367
|
+
|
|
368
|
+
if not args.agent_mode:
|
|
369
|
+
print_banner()
|
|
370
|
+
spin_task("Analyzing stack trace & scanning codebase boundaries...", duration=0.6)
|
|
371
|
+
spin_task(f"Invoking critic persona '{args.personality.upper()}'...", duration=0.5)
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
# 1. Check Zero-AI Deterministic AST Solver first if requested or applicable
|
|
375
|
+
solved_ast = False
|
|
376
|
+
if args.solver:
|
|
377
|
+
solved_ast, sym_diag = solve_with_ast(file_content, args.error)
|
|
378
|
+
if solved_ast and sym_diag:
|
|
379
|
+
diagnosis = sym_diag
|
|
380
|
+
if not args.agent_mode:
|
|
381
|
+
draw_card("1MS DETERMINISTIC AST SYMBOLIC SOLVER", "Resolved crash deterministically in 1ms with ZERO LLM queries & zero token cost.", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
382
|
+
|
|
383
|
+
if not solved_ast:
|
|
384
|
+
if args.consensus:
|
|
385
|
+
reached, diagnosis, arb_summary = arbitrate_consensus(
|
|
386
|
+
error_log=args.error,
|
|
387
|
+
file_context=file_content,
|
|
388
|
+
target_filepath=filepath,
|
|
389
|
+
budget_limit_usd=args.budget
|
|
390
|
+
)
|
|
391
|
+
if not args.agent_mode:
|
|
392
|
+
border_c = FG_GREEN if reached else FG_YELLOW
|
|
393
|
+
draw_card("CROSS-MODEL ARBITRATION CONSENSUS", arb_summary, color_border=border_c, color_title=border_c)
|
|
394
|
+
else:
|
|
395
|
+
diagnosis = diagnose_and_patch(
|
|
396
|
+
error_log=args.error,
|
|
397
|
+
file_context=file_content,
|
|
398
|
+
target_filepath=filepath,
|
|
399
|
+
personality=args.personality,
|
|
400
|
+
budget_limit_usd=args.budget
|
|
401
|
+
)
|
|
402
|
+
except PermissionError as pe:
|
|
403
|
+
if args.agent_mode:
|
|
404
|
+
print(json.dumps({"error": "CIRCUIT_BREAKER_TRIGGERED", "details": str(pe)}))
|
|
405
|
+
else:
|
|
406
|
+
draw_card("CIRCUIT BREAKER TRIGGERED", str(pe), color_border=FG_RED, color_title=FG_RED)
|
|
407
|
+
return
|
|
408
|
+
except Exception as ex:
|
|
409
|
+
if args.agent_mode:
|
|
410
|
+
print(json.dumps({"error": "DIAGNOSTIC_FAILED", "details": str(ex)}))
|
|
411
|
+
else:
|
|
412
|
+
draw_card("DIAGNOSTIC ERROR", str(ex), color_border=FG_RED, color_title=FG_RED)
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
# Check Anti-Thrashing Guard
|
|
416
|
+
patch_str = str(diagnosis.get("git_patch", ""))
|
|
417
|
+
safe_from_loop, thrash_msg = check_anti_thrashing(filepath, patch_str)
|
|
418
|
+
if not safe_from_loop:
|
|
419
|
+
if args.agent_mode:
|
|
420
|
+
print(json.dumps({"error": "ANTI_THRASHING_HALT", "message": thrash_msg}))
|
|
421
|
+
else:
|
|
422
|
+
draw_card("ANTI-THRASHING WATCHDOG TRIGGERED", thrash_msg, color_border=FG_RED, color_title=FG_RED)
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
# Check Ghost Imports on fixed snippet across all languages
|
|
426
|
+
ghost_ok, ghost_err = check_ghost_imports(diagnosis['fixed_snippet'], filepath=filepath)
|
|
427
|
+
if not ghost_ok:
|
|
428
|
+
if args.agent_mode:
|
|
429
|
+
print(json.dumps({"error": "GHOST_IMPORT_REJECTED", "details": ghost_err}))
|
|
430
|
+
else:
|
|
431
|
+
draw_card("GHOST IMPORT LINTER HALT", ghost_err, color_border=FG_RED, color_title=FG_RED)
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
# Regression Guard Verification
|
|
435
|
+
reg_ok, reg_reason = verify_patch_in_sandbox(filepath, diagnosis['original_snippet'], diagnosis['fixed_snippet'])
|
|
436
|
+
if not reg_ok:
|
|
437
|
+
if args.agent_mode:
|
|
438
|
+
print(json.dumps({"error": "REGRESSION_GUARD_REJECTED", "details": reg_reason}))
|
|
439
|
+
else:
|
|
440
|
+
draw_card("REGRESSION GUARD REJECTION", f"Patch rejected to prevent test regression:\n{reg_reason}", color_border=FG_RED, color_title=FG_RED)
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
# Egress Network Safety Probe
|
|
444
|
+
egress_ok, egress_err = check_egress_security(diagnosis['fixed_snippet'])
|
|
445
|
+
if not egress_ok:
|
|
446
|
+
if args.agent_mode:
|
|
447
|
+
print(json.dumps({"error": "EGRESS_SECURITY_BLOCKED", "details": egress_err}))
|
|
448
|
+
else:
|
|
449
|
+
draw_card("EGRESS NETWORK SECURITY ALERT", egress_err, color_border=FG_RED, color_title=FG_RED)
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
# License & Copyright Taint Guard
|
|
453
|
+
license_ok, license_err = check_license_taint(diagnosis['fixed_snippet'])
|
|
454
|
+
if not license_ok:
|
|
455
|
+
if args.agent_mode:
|
|
456
|
+
print(json.dumps({"error": "LICENSE_TAINT_BLOCKED", "details": license_err}))
|
|
457
|
+
else:
|
|
458
|
+
draw_card("LICENSE TAINT DETECTED", license_err, color_border=FG_RED, color_title=FG_RED)
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
# Anti-Bloat Complexity Linter
|
|
462
|
+
bloat_ok, bloat_err = check_code_complexity(diagnosis['fixed_snippet'], max_allowed_complexity=8)
|
|
463
|
+
if not bloat_ok:
|
|
464
|
+
if args.agent_mode:
|
|
465
|
+
print(json.dumps({"error": "ANTI_BLOAT_VIOLATION", "details": bloat_err}))
|
|
466
|
+
else:
|
|
467
|
+
draw_card("ANTI-BLOAT COMPLEXITY WARNING", bloat_err, color_border=FG_YELLOW, color_title=FG_YELLOW)
|
|
468
|
+
|
|
469
|
+
# Shadow Sandbox execution if requested
|
|
470
|
+
if args.shadow:
|
|
471
|
+
with ShadowWorkspace() as shadow:
|
|
472
|
+
if not args.agent_mode:
|
|
473
|
+
draw_card("SHADOW WORKSPACE ISOLATION", f"Dry-running patch inside temporary shadow container: {shadow.shadow_dir}", color_border=FG_CYAN, color_title=FG_CYAN)
|
|
474
|
+
shadow_applied = apply_patch_to_file(os.path.join(shadow.shadow_dir, filepath), diagnosis['original_snippet'], diagnosis['fixed_snippet'])
|
|
475
|
+
# Only commit back if verified
|
|
476
|
+
if shadow_applied:
|
|
477
|
+
shadow.commit_file_to_real(filepath)
|
|
478
|
+
|
|
479
|
+
# Headless Agent Output
|
|
480
|
+
if args.agent_mode:
|
|
481
|
+
if args.yes:
|
|
482
|
+
applied = apply_patch_to_file(filepath, diagnosis['original_snippet'], diagnosis['fixed_snippet'])
|
|
483
|
+
step_id = record_step("agent", "apply_patch", filepath, diagnosis['root_cause'], patch_str)
|
|
484
|
+
register_antibody(args.error[:100], diagnosis['fixed_snippet'], diagnosis['root_cause'])
|
|
485
|
+
print(json.dumps({
|
|
486
|
+
"status": "APPLIED" if applied else "PATCH_FAILED",
|
|
487
|
+
"target_file": filepath,
|
|
488
|
+
"step_id": step_id,
|
|
489
|
+
"roast": diagnosis['roast'],
|
|
490
|
+
"root_cause": diagnosis['root_cause'],
|
|
491
|
+
"cost_usd": diagnosis.get("cost_usd", 0.0),
|
|
492
|
+
"is_cached": diagnosis.get("is_cached", False)
|
|
493
|
+
}))
|
|
494
|
+
else:
|
|
495
|
+
print(json.dumps({
|
|
496
|
+
"status": "PROPOSED",
|
|
497
|
+
"target_file": filepath,
|
|
498
|
+
"diff": patch_str,
|
|
499
|
+
"roast": diagnosis['roast'],
|
|
500
|
+
"root_cause": diagnosis['root_cause'],
|
|
501
|
+
"original_snippet": diagnosis['original_snippet'],
|
|
502
|
+
"fixed_snippet": diagnosis['fixed_snippet'],
|
|
503
|
+
"cost_usd": diagnosis.get("cost_usd", 0.0),
|
|
504
|
+
"is_cached": diagnosis.get("is_cached", False)
|
|
505
|
+
}))
|
|
506
|
+
return
|
|
507
|
+
|
|
508
|
+
# Print Brutal Roast Card
|
|
509
|
+
persona_title = f"CRITIC ROAST ({args.personality.upper()})"
|
|
510
|
+
draw_card(persona_title, f"{FG_YELLOW}{ITALIC}\"{diagnosis['roast']}\"{RESET}", color_border=FG_MAGENTA, color_title=FG_MAGENTA)
|
|
511
|
+
|
|
512
|
+
# Diagnostic metadata card
|
|
513
|
+
cost_str = f"${diagnosis.get('cost_usd', 0.0):.5f} USD" if not diagnosis.get("is_cached") else "FREE (Deterministic Cache Hit)"
|
|
514
|
+
meta_text = (
|
|
515
|
+
f"Root Cause : {BOLD}{diagnosis['root_cause']}{RESET}\n"
|
|
516
|
+
f"Target File : {FG_CYAN}{filepath}{RESET}\n"
|
|
517
|
+
f"Query Cost : {FG_GREEN}{cost_str}{RESET} │ Today's Total: ${get_daily_spend():.4f} USD\n"
|
|
518
|
+
f"Safety Check : {FG_GREEN}Circuit Breaker Passed (Limit: ${args.budget:.2f}){RESET}"
|
|
519
|
+
)
|
|
520
|
+
draw_card("DIAGNOSTIC ANALYSIS & FINANCIAL METRICS", meta_text, color_border=FG_CYAN, color_title=FG_CYAN)
|
|
521
|
+
|
|
522
|
+
# Show Diff
|
|
523
|
+
print(f"\n{BOLD}{FG_WHITE}PROPOSED SURGICAL GIT PATCH:{RESET}\n")
|
|
524
|
+
patch_str = str(diagnosis.get("git_patch", ""))
|
|
525
|
+
render_diff(patch_str)
|
|
526
|
+
print("")
|
|
527
|
+
|
|
528
|
+
# Apply Prompt
|
|
529
|
+
if args.yes:
|
|
530
|
+
applied = apply_patch_to_file(filepath, diagnosis['original_snippet'], diagnosis['fixed_snippet'])
|
|
531
|
+
if applied:
|
|
532
|
+
draw_card("PATCH APPLIED SUCCESSFULLY", f"File '{filepath}' updated safely.\nRun `git-oops --undo {filepath}` if you wish to roll back.", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
533
|
+
if args.test_gen:
|
|
534
|
+
tpath = synthesize_regression_test(filepath, args.error, diagnosis['fixed_snippet'])
|
|
535
|
+
draw_card("REGRESSION TEST SYNTHESIZED", f"Synthesized permanent regression test: {tpath}", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
536
|
+
else:
|
|
537
|
+
draw_card("PATCH APPLICATION FAILED", "Original snippet did not match cleanly.", color_border=FG_RED, color_title=FG_RED)
|
|
538
|
+
else:
|
|
539
|
+
user_input = input(f" {FG_YELLOW}{BOLD}?{RESET} {BOLD}Apply this patch to '{filepath}' right now? [y/N]: {RESET}").strip().lower()
|
|
540
|
+
if user_input in ["y", "yes"]:
|
|
541
|
+
applied = apply_patch_to_file(filepath, diagnosis['original_snippet'], diagnosis['fixed_snippet'])
|
|
542
|
+
if applied:
|
|
543
|
+
print("")
|
|
544
|
+
draw_card("PATCH APPLIED SUCCESSFULLY", f"File '{filepath}' updated safely.\nRun `git-oops --undo {filepath}` if you wish to roll back.", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
545
|
+
if args.test_gen:
|
|
546
|
+
tpath = synthesize_regression_test(filepath, args.error, diagnosis['fixed_snippet'])
|
|
547
|
+
draw_card("REGRESSION TEST SYNTHESIZED", f"Synthesized permanent regression test: {tpath}", color_border=FG_GREEN, color_title=FG_GREEN)
|
|
548
|
+
else:
|
|
549
|
+
print("")
|
|
550
|
+
draw_card("PATCH APPLICATION FAILED", "Original snippet could not be matched cleanly in file.", color_border=FG_RED, color_title=FG_RED)
|
|
551
|
+
else:
|
|
552
|
+
print(f"\n {FG_GRAY}Aborted. No files were modified.{RESET}\n")
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
if __name__ == "__main__":
|
|
556
|
+
main()
|