157 lines
5.7 KiB
Python
157 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Agentic-OS Codebase Integrity Check (run hourly via cron)
|
|
|
|
Detects STALE state in the codebase — references that contradict the
|
|
single source of truth:
|
|
|
|
SOURCE OF TRUTH
|
|
- dashboard port ........ data/settings.json -> dashboard.port (currently 8080)
|
|
- canonical repo ........ github.com/zumayaaustin-creator/agentic-os
|
|
- canonical location .... /home/austin/agentic-os
|
|
- terminal endpoint ..... /ws/terminal on the MAIN port (NOT a separate 8082)
|
|
|
|
Any contradiction of the above is reported as a finding. This script is
|
|
read-only: it never edits files. It prints a report and exits 0 if clean,
|
|
1 if any finding, 2 on a check error (so the cron can alert).
|
|
|
|
Run: python3 scripts/integrity_check.py
|
|
"""
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
BASE = Path(__file__).resolve().parent.parent
|
|
SETTINGS = BASE / "data" / "settings.json"
|
|
|
|
# ── Source of truth ────────────────────────────────────────────────
|
|
TRUTH = {
|
|
"expected_port": 8080, # overwritten below from settings.json if present
|
|
"canonical_repo": "zumayaaustin-creator/agentic-os",
|
|
"canonical_location": "/home/austin/agentic-os",
|
|
"terminal_endpoint": "/ws/terminal",
|
|
}
|
|
STALE_PATTERNS = [
|
|
(r"modimihir07", "stale GitHub repo (should be zumayaaustin-creator/agentic-os)"),
|
|
(r"Desktop/Agentic\s*OS\s*Project", "stale project location (should be /home/austin/agentic-os)"),
|
|
(r"127\.0\.0\.1:8082", "dead port 8082 referenced (terminal consolidated to /ws/terminal on main port)"),
|
|
(r"port\s*8082", "dead port 8082 referenced"),
|
|
(r":8081", "stale port 8081 referenced as the server/terminal port"),
|
|
(r"wsPort\s*=\s*8082", "terminal.js still targets dead 8082"),
|
|
(r"action:\s*'(input|resize|output)'", "terminal.js uses wrong 'action:' protocol (backend expects 'type:')"),
|
|
]
|
|
# Files to skip (build artifacts, venv, node_modules)
|
|
SKIP_DIRS = {"venv", "node_modules", "__pycache__", ".git", "graphify-out"}
|
|
SKIP_EXT = {".pyc"}
|
|
|
|
findings = []
|
|
|
|
|
|
def add(file, line_no, detail, excerpt):
|
|
findings.append(f"[{file}:{line_no}] {detail}\n > {excerpt.strip()[:120]}")
|
|
|
|
|
|
def load_truth():
|
|
if SETTINGS.exists():
|
|
try:
|
|
s = json.loads(SETTINGS.read_text(encoding="utf-8"))
|
|
p = int(s.get("dashboard", {}).get("port", 8080))
|
|
TRUTH["expected_port"] = p
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def scan_text_files():
|
|
for p in BASE.rglob("*"):
|
|
if not p.is_file():
|
|
continue
|
|
if any(part in SKIP_DIRS for part in p.relative_to(BASE).parts):
|
|
continue
|
|
if p.name == "integrity_check.py":
|
|
continue # don't flag our own pattern strings
|
|
# only scan text-like files
|
|
if p.suffix.lower() not in {
|
|
".md", ".py", ".js", ".ts", ".html", ".json", ".sh", ".yml", ".yaml", ".toml", ".ini", ".txt"
|
|
}:
|
|
continue
|
|
try:
|
|
text = p.read_text(encoding="utf-8", errors="ignore")
|
|
except Exception:
|
|
continue
|
|
rel = p.relative_to(BASE)
|
|
for pat, desc in STALE_PATTERNS:
|
|
for m in re.finditer(pat, text, re.IGNORECASE):
|
|
# find line number
|
|
line_no = text.count("\n", 0, m.start()) + 1
|
|
excerpt = text.splitlines()[line_no - 1] if line_no <= len(text.splitlines()) else ""
|
|
add(str(rel), line_no, desc, excerpt)
|
|
|
|
|
|
def check_live_server():
|
|
"""Confirm the running server is on the expected port and serving."""
|
|
import urllib.request
|
|
port = TRUTH["expected_port"]
|
|
url = f"http://127.0.0.1:{port}/"
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=4) as r:
|
|
if r.status != 200:
|
|
findings.append(f"[LIVE] server on :{port} returned HTTP {r.status} (expected 200)")
|
|
except Exception as e:
|
|
findings.append(f"[LIVE] server NOT reachable on :{port} ({type(e).__name__}: {e})")
|
|
|
|
|
|
def check_terminal_endpoint():
|
|
"""The terminal page must reference /ws/terminal and the main host."""
|
|
tjs = BASE / "dashboard" / "pages" / "terminal.js"
|
|
if not tjs.exists():
|
|
findings.append("[terminal.js] file missing — Terminal page broken")
|
|
return
|
|
txt = tjs.read_text(encoding="utf-8", errors="ignore")
|
|
if "/ws/terminal" not in txt:
|
|
findings.append("[terminal.js] does NOT connect to /ws/terminal (terminal disconnected from OS)")
|
|
if "wsPort" in txt or ":8082" in txt or "port 8082" in txt:
|
|
findings.append("[terminal.js] still references dead 8082 port")
|
|
|
|
|
|
def check_agent_enabled():
|
|
"""opencode should be enabled (it is the code/DevOps agent)."""
|
|
if not SETTINGS.exists():
|
|
return
|
|
try:
|
|
s = json.loads(SETTINGS.read_text(encoding="utf-8"))
|
|
oc = s.get("agent_preferences", {}).get("opencode", {})
|
|
if oc.get("enabled") is not True:
|
|
findings.append(f"[settings.json] opencode.enabled={oc.get('enabled')!r} (expected true)")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def main():
|
|
load_truth()
|
|
scan_text_files()
|
|
check_terminal_endpoint()
|
|
check_agent_enabled()
|
|
check_live_server()
|
|
|
|
print("=" * 64)
|
|
print(" Agentic-OS Integrity Check")
|
|
print(f" port(source of truth) = {TRUTH['expected_port']}")
|
|
print("=" * 64)
|
|
if not findings:
|
|
print(" ✓ CLEAN — no stale references detected.")
|
|
return 0
|
|
print(f" ✗ {len(findings)} FINDING(S):\n")
|
|
for f in findings:
|
|
print(" - " + f)
|
|
print()
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except Exception as e:
|
|
print(f"CHECK ERROR: {type(e).__name__}: {e}")
|
|
sys.exit(2)
|