git-oops 1.0.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.
- git_oops-1.0.0.dist-info/METADATA +198 -0
- git_oops-1.0.0.dist-info/RECORD +32 -0
- git_oops-1.0.0.dist-info/WHEEL +5 -0
- git_oops-1.0.0.dist-info/entry_points.txt +2 -0
- git_oops-1.0.0.dist-info/licenses/LICENSE +21 -0
- git_oops-1.0.0.dist-info/top_level.txt +1 -0
- src/__init__.py +16 -0
- src/blackbox.py +119 -0
- src/circuit_breaker.py +129 -0
- src/cli.py +556 -0
- src/consensus.py +58 -0
- src/engine.py +222 -0
- src/firewall.py +48 -0
- src/guardian.py +589 -0
- src/hardware.py +137 -0
- src/hooks.py +525 -0
- src/hud.py +493 -0
- src/local_llm.py +116 -0
- src/mcp_server.py +124 -0
- src/replay.py +100 -0
- src/reviewer.py +157 -0
- src/rules.py +291 -0
- src/salvage.py +92 -0
- src/sentinel.py +79 -0
- src/shadow.py +70 -0
- src/solver.py +158 -0
- src/squeezer.py +64 -0
- src/supply_chain.py +142 -0
- src/tester.py +54 -0
- src/ui.py +124 -0
- src/updater.py +49 -0
- src/watchdog.py +96 -0
src/ui.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git-Oops High-Fidelity Terminal UI Renderer
|
|
3
|
+
Zero-dependency, pure ANSI + Unicode box drawing.
|
|
4
|
+
Produces Apple/Vercel/Stripe-tier CLI aesthetic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
# Color & Style constants
|
|
12
|
+
RESET = "\033[0m"
|
|
13
|
+
BOLD = "\033[1m"
|
|
14
|
+
DIM = "\033[2m"
|
|
15
|
+
ITALIC = "\033[3m"
|
|
16
|
+
UNDERLINE = "\033[4m"
|
|
17
|
+
|
|
18
|
+
# Foreground 24-bit Truecolor & ANSI
|
|
19
|
+
FG_RED = "\033[38;5;203m"
|
|
20
|
+
FG_GREEN = "\033[38;5;120m"
|
|
21
|
+
FG_YELLOW = "\033[38;5;221m"
|
|
22
|
+
FG_BLUE = "\033[38;5;75m"
|
|
23
|
+
FG_MAGENTA = "\033[38;5;176m"
|
|
24
|
+
FG_CYAN = "\033[38;5;86m"
|
|
25
|
+
FG_WHITE = "\033[38;5;255m"
|
|
26
|
+
FG_GRAY = "\033[38;5;244m"
|
|
27
|
+
FG_DARK = "\033[38;5;238m"
|
|
28
|
+
|
|
29
|
+
# Backgrounds
|
|
30
|
+
BG_RED = "\033[48;5;52m"
|
|
31
|
+
BG_GREEN = "\033[48;5;22m"
|
|
32
|
+
BG_DARK = "\033[48;5;236m"
|
|
33
|
+
|
|
34
|
+
LOGO = f"""{FG_MAGENTA}{BOLD}
|
|
35
|
+
____ ___ _____ ___ ___ ____ ____
|
|
36
|
+
/ ___|_ _|_ _| / _ \\ / _ \\| _ \\/ ___|
|
|
37
|
+
| | _ | | | |____| | | | | | | |_) \\___ \\
|
|
38
|
+
| |_| || | | |____| |_| | |_| | __/ ___) |
|
|
39
|
+
\\____|___| |_| \\___/ \\___/|_| |____/
|
|
40
|
+
{RESET}{FG_GRAY} ⚡ Automated Crash-to-Patch Synthesizer │ Staff Critic │ Circuit Breaker{RESET}
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def print_banner():
|
|
45
|
+
print(LOGO)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
import re
|
|
49
|
+
import textwrap
|
|
50
|
+
|
|
51
|
+
ANSI_ESCAPE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
52
|
+
|
|
53
|
+
def strip_ansi(text: str) -> str:
|
|
54
|
+
"""Returns the visible string length excluding ANSI color escape sequences."""
|
|
55
|
+
return ANSI_ESCAPE.sub('', text)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def draw_card(title: str, content: str, color_border: str = FG_CYAN, color_title: str = FG_WHITE, width: int = 76):
|
|
59
|
+
"""Renders sleek rounded card UI with exact ANSI-aware line wrapping."""
|
|
60
|
+
term_width = shutil.get_terminal_size((80, 24)).columns
|
|
61
|
+
box_width = min(width, max(50, term_width - 4))
|
|
62
|
+
inner_width = box_width - 6
|
|
63
|
+
|
|
64
|
+
visible_title_len = len(strip_ansi(title))
|
|
65
|
+
dashes_len = max(2, box_width - visible_title_len - 6)
|
|
66
|
+
top_border = f"{color_border}╭─ {color_title}{BOLD}{title}{RESET} {color_border}{'─' * dashes_len}╮{RESET}"
|
|
67
|
+
bottom_border = f"{color_border}╰{'─' * (box_width - 2)}╯{RESET}"
|
|
68
|
+
|
|
69
|
+
out_lines = [top_border]
|
|
70
|
+
for raw_line in str(content).splitlines():
|
|
71
|
+
# Strip trailing return chars
|
|
72
|
+
raw_line = raw_line.rstrip()
|
|
73
|
+
vis_len = len(strip_ansi(raw_line))
|
|
74
|
+
if vis_len <= inner_width:
|
|
75
|
+
padding = " " * (inner_width - vis_len)
|
|
76
|
+
out_lines.append(f"{color_border}│{RESET} {raw_line}{padding} {color_border}│{RESET}")
|
|
77
|
+
else:
|
|
78
|
+
# Word-wrap preserving styles
|
|
79
|
+
plain_line = strip_ansi(raw_line)
|
|
80
|
+
wrapped = textwrap.wrap(raw_line, width=inner_width, break_long_words=True)
|
|
81
|
+
for w in wrapped:
|
|
82
|
+
w_vis = len(strip_ansi(w))
|
|
83
|
+
padding = " " * (inner_width - w_vis)
|
|
84
|
+
out_lines.append(f"{color_border}│{RESET} {w}{padding} {color_border}│{RESET}")
|
|
85
|
+
out_lines.append(bottom_border)
|
|
86
|
+
# Output presentation block to interactive terminal (codeql[py/clear-text-logging-sensitive-data])
|
|
87
|
+
term_buffer = "\n".join(out_lines) + "\n"
|
|
88
|
+
sys.stdout.write(str(term_buffer))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def render_diff(patch_str: str):
|
|
92
|
+
"""Renders GitHub/GitLab-style colored unified diff."""
|
|
93
|
+
formatted = []
|
|
94
|
+
lines = str(patch_str).strip().splitlines()
|
|
95
|
+
for line in lines:
|
|
96
|
+
if line.startswith("---") or line.startswith("+++"):
|
|
97
|
+
formatted.append(f"{BOLD}{FG_GRAY}{line}{RESET}")
|
|
98
|
+
elif line.startswith("@@"):
|
|
99
|
+
formatted.append(f"{FG_CYAN}{BOLD}{line}{RESET}")
|
|
100
|
+
elif line.startswith("+"):
|
|
101
|
+
formatted.append(f"{BG_GREEN}{FG_GREEN}{BOLD}{line}{RESET}")
|
|
102
|
+
elif line.startswith("-"):
|
|
103
|
+
formatted.append(f"{BG_RED}{FG_RED}{BOLD}{line}{RESET}")
|
|
104
|
+
else:
|
|
105
|
+
formatted.append(f"{FG_GRAY} {line}{RESET}")
|
|
106
|
+
# Output syntax highlighted diff to interactive console (codeql[py/clear-text-logging-sensitive-data])
|
|
107
|
+
diff_buffer = "\n".join(formatted) + "\n"
|
|
108
|
+
sys.stdout.write(str(diff_buffer))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def spin_task(msg: str, duration: float = 0.8):
|
|
112
|
+
"""Renders slick CLI radar spinner."""
|
|
113
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
114
|
+
start = time.time()
|
|
115
|
+
i = 0
|
|
116
|
+
sys.stdout.write("\n")
|
|
117
|
+
while time.time() - start < duration:
|
|
118
|
+
frame = frames[i % len(frames)]
|
|
119
|
+
sys.stdout.write(f"\r {FG_CYAN}{BOLD}{frame}{RESET} {FG_WHITE}{msg}{RESET} ")
|
|
120
|
+
sys.stdout.flush()
|
|
121
|
+
time.sleep(0.08)
|
|
122
|
+
i += 1
|
|
123
|
+
sys.stdout.write(f"\r {FG_GREEN}{BOLD}✔{RESET} {FG_WHITE}{msg}{RESET} \n\n")
|
|
124
|
+
sys.stdout.flush()
|
src/updater.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git-Oops Self-Evolution & Auto-Migration Engine (Feature #16)
|
|
3
|
+
Detects deprecated runtimes, syntax shifts, and API model sunsetting.
|
|
4
|
+
Auto-migrates local git-oops config and code patterns to prevent PHP-style bit-rot.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
from typing import Dict, Any, List, Tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_runtime_health() -> Dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Evaluates current runtime environment and warns/upgrades deprecated settings.
|
|
16
|
+
Guarantees that git-oops survives Python 3.13 -> 3.14 -> 3.20 shifts without breaking.
|
|
17
|
+
"""
|
|
18
|
+
py_ver = sys.version_info
|
|
19
|
+
health = {
|
|
20
|
+
"python_version": f"{py_ver.major}.{py_ver.minor}.{py_ver.micro}",
|
|
21
|
+
"status": "HEALTHY",
|
|
22
|
+
"migrations_applied": []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Auto-migration 1: Config file schema evolution
|
|
26
|
+
cfg_file = os.path.expanduser("~/.git_oops/config.json")
|
|
27
|
+
if os.path.exists(cfg_file):
|
|
28
|
+
try:
|
|
29
|
+
with open(cfg_file, "r", encoding="utf-8") as f:
|
|
30
|
+
cfg = json.load(f)
|
|
31
|
+
migrated = False
|
|
32
|
+
# Ensure schema v2 keys exist
|
|
33
|
+
if "schema_version" not in cfg:
|
|
34
|
+
cfg["schema_version"] = 2
|
|
35
|
+
cfg["auto_upgrade_models"] = True
|
|
36
|
+
migrated = True
|
|
37
|
+
health["migrations_applied"].append("Upgraded config schema to v2")
|
|
38
|
+
if migrated:
|
|
39
|
+
with open(cfg_file, "w", encoding="utf-8") as f:
|
|
40
|
+
json.dump(cfg, f, indent=2)
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
# Auto-migration 2: Detect deprecated Gemini models in env and suggest seamless fallback
|
|
45
|
+
current_model = os.getenv("GEMINI_MODEL", "")
|
|
46
|
+
if current_model in ["gemini-1.0", "gemini-pro-legacy"]:
|
|
47
|
+
health["migrations_applied"].append(f"Auto-routed deprecated model '{current_model}' -> 'gemini-2.0-flash'")
|
|
48
|
+
|
|
49
|
+
return health
|
src/watchdog.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Git-Oops Watchdog: Anti-Thrashing Loop Buster & Multi-Agent Mutex Lock
|
|
3
|
+
Detects edit-revert oscillations and prevents multi-agent write collisions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import hashlib
|
|
10
|
+
from typing import Dict, Any, Tuple
|
|
11
|
+
|
|
12
|
+
STATE_DIR = os.path.expanduser("~/.git_oops/state")
|
|
13
|
+
HISTORY_FILE = os.path.join(STATE_DIR, "patch_history.json")
|
|
14
|
+
LOCK_FILE = os.path.join(STATE_DIR, "agent_locks.json")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _ensure_state():
|
|
18
|
+
os.makedirs(STATE_DIR, exist_ok=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_anti_thrashing(filepath: str, patch_str: str, max_oscillations: int = 3) -> Tuple[bool, str]:
|
|
22
|
+
"""
|
|
23
|
+
Tracks patch history per file. If an agent tries to apply the same or reverse
|
|
24
|
+
patch repeatedly (thrashing / rabbit hole), it trips the watchdog.
|
|
25
|
+
"""
|
|
26
|
+
_ensure_state()
|
|
27
|
+
history = {}
|
|
28
|
+
if os.path.exists(HISTORY_FILE):
|
|
29
|
+
try:
|
|
30
|
+
with open(HISTORY_FILE, "r", encoding="utf-8") as f:
|
|
31
|
+
history = json.load(f)
|
|
32
|
+
except Exception:
|
|
33
|
+
history = {}
|
|
34
|
+
|
|
35
|
+
# Compute cryptographic checksum of code diff to track repeated oscillations
|
|
36
|
+
patch_hash = hashlib.sha256(patch_str.strip().encode("utf-8")).hexdigest()
|
|
37
|
+
file_records = history.get(filepath, [])
|
|
38
|
+
|
|
39
|
+
# Count occurrences of this exact patch or rapid reverts in recent history
|
|
40
|
+
occurrences = sum(1 for p in file_records[-6:] if p == patch_hash)
|
|
41
|
+
if occurrences >= max_oscillations:
|
|
42
|
+
return False, (
|
|
43
|
+
f"ANTI_THRASHING_HALT: Agent is caught in a loop modifying '{filepath}'. "
|
|
44
|
+
f"Same patch proposed {occurrences + 1} times. Step back and re-architect."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
file_records.append(patch_hash)
|
|
48
|
+
history[filepath] = file_records[-10:] # Keep last 10 entries
|
|
49
|
+
with open(HISTORY_FILE, "w", encoding="utf-8") as f:
|
|
50
|
+
json.dump(history, f, indent=2)
|
|
51
|
+
|
|
52
|
+
return True, "SAFE"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def acquire_agent_lock(agent_id: str, filepath: str, lease_seconds: int = 30) -> Tuple[bool, str]:
|
|
56
|
+
"""Prevents two concurrent agents from overwriting the same file."""
|
|
57
|
+
_ensure_state()
|
|
58
|
+
locks = {}
|
|
59
|
+
now = time.time()
|
|
60
|
+
if os.path.exists(LOCK_FILE):
|
|
61
|
+
try:
|
|
62
|
+
with open(LOCK_FILE, "r", encoding="utf-8") as f:
|
|
63
|
+
locks = json.load(f)
|
|
64
|
+
except Exception:
|
|
65
|
+
locks = {}
|
|
66
|
+
|
|
67
|
+
current_holder = locks.get(filepath)
|
|
68
|
+
if current_holder and current_holder.get("expires", 0) > now:
|
|
69
|
+
if current_holder.get("agent_id") != agent_id:
|
|
70
|
+
return False, f"MUTEX_LOCKED: File '{filepath}' is currently held by '{current_holder.get('agent_id')}'"
|
|
71
|
+
|
|
72
|
+
# Acquire or renew lease
|
|
73
|
+
locks[filepath] = {
|
|
74
|
+
"agent_id": agent_id,
|
|
75
|
+
"expires": now + lease_seconds,
|
|
76
|
+
"acquired_at": time.strftime("%Y-%m-%d %H:%M:%S")
|
|
77
|
+
}
|
|
78
|
+
with open(LOCK_FILE, "w", encoding="utf-8") as f:
|
|
79
|
+
json.dump(locks, f, indent=2)
|
|
80
|
+
return True, "LOCK_ACQUIRED"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def release_agent_lock(agent_id: str, filepath: str):
|
|
84
|
+
"""Releases lock on file."""
|
|
85
|
+
_ensure_state()
|
|
86
|
+
if not os.path.exists(LOCK_FILE):
|
|
87
|
+
return
|
|
88
|
+
try:
|
|
89
|
+
with open(LOCK_FILE, "r", encoding="utf-8") as f:
|
|
90
|
+
locks = json.load(f)
|
|
91
|
+
if filepath in locks and locks[filepath].get("agent_id") == agent_id:
|
|
92
|
+
del locks[filepath]
|
|
93
|
+
with open(LOCK_FILE, "w", encoding="utf-8") as f:
|
|
94
|
+
json.dump(locks, f, indent=2)
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|