From 65cf2e887e4272d1ebf1c16bb255551834c46285 Mon Sep 17 00:00:00 2001 From: jon Date: Thu, 23 Jul 2026 16:25:35 -0500 Subject: [PATCH] feat(ncp): doctor --fix applies safe, idempotent repairs Opt-in repair pass: reinstall Python deps on import failure, npm install when node_modules missing, npm run build when dist missing, fetch Ollama binary. Plain doctor stays read-only. Co-Authored-By: Claude Opus 4.8 --- management/ncp.py | 51 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/management/ncp.py b/management/ncp.py index 49cb8fe..e96835a 100644 --- a/management/ncp.py +++ b/management/ncp.py @@ -458,7 +458,50 @@ def cmd_logs(target) -> None: print("Usage: ncp logs [frontend|backend|memory|all]") -def cmd_doctor() -> None: +def _apply_fixes() -> None: + """Opt-in safe repairs (ncp doctor --fix). Only idempotent, non-destructive + actions: build the UI, install deps, fetch the Ollama binary. Anything that + could lose data or needs a decision is left to the user with a hint.""" + print("\nApplying safe fixes...\n") + did = False + web, npm_path = FRONTEND_DIR, npm() + + if not PYTHON.exists(): + print(" ✘ Promethean venv missing — run ./install.sh to build it (skipped: heavy).") + else: + importable = subprocess.run( + [str(PYTHON), "-c", "from synapse.main import sio_app"], + cwd=str(ROOT), capture_output=True).returncode == 0 + if not importable: + print(" Backend import failed — reinstalling Python dependencies...") + sys.path.insert(0, str(ROOT / "bin")) + import importlib.util + spec = importlib.util.spec_from_file_location("sync", ROOT / "bin" / "sync.py") + sync = importlib.util.module_from_spec(spec) + spec.loader.exec_module(sync) + subprocess.run([str(PYTHON), "-m", "pip", "install", "-r", sync.requirements()], cwd=str(ROOT)) + did = True + + if npm_path and web.is_dir() and not (web / "node_modules").is_dir(): + print(" node_modules missing — running npm install...") + subprocess.run([npm_path, "install"], cwd=str(web)) + did = True + + if npm_path and web.is_dir() and not (web / "dist" / "index.html").exists(): + print(" Built UI missing — running npm run build...") + subprocess.run([npm_path, "run", "build"], cwd=str(web)) + did = True + + if not ollama_bin() and os.name != "nt" and (ROOT / "bin" / "fetch-ollama.sh").exists(): + print(" Ollama binary missing — fetching...") + subprocess.run(["bash", str(ROOT / "bin" / "fetch-ollama.sh")]) + did = True + + print("\n" + ("Fixes applied — re-run 'ncp doctor' to confirm." if did + else "Nothing to fix (all safe-repairable checks already pass).")) + + +def cmd_doctor(fix: bool = False) -> None: def mark(ok, good, bad): print(f" {'✔' if ok else '✘'} {good if ok else bad}") @@ -536,6 +579,8 @@ def cmd_doctor() -> None: f"Ollama models directory missing at {OLLAMA_MODELS_DIR}") print() cmd_status() + if fix: + _apply_fixes() def cmd_update() -> None: @@ -707,7 +752,7 @@ Commands: --frontend,-f Frontend logs --backend, -b Backend logs - doctor Run Nexus diagnostics + doctor [--fix] Run Nexus diagnostics (--fix applies safe repairs) update Update Nexus dependencies clean Remove runtime files and caches @@ -753,7 +798,7 @@ def main(argv) -> int: elif cmd == "logs": cmd_logs(arg) elif cmd == "doctor": - cmd_doctor() + cmd_doctor(fix="--fix" in rest) elif cmd == "update": cmd_update() elif cmd == "clean":