From 36b42d5c238f442ce351d3a18f37786af55121ac Mon Sep 17 00:00:00 2001 From: Alex Payne Date: Fri, 31 Jul 2026 04:50:02 +0000 Subject: [PATCH] Add self-improvement agent script Scans targets for safe mechanical improvements: prepend missing titles, flag TODOs, add viewport meta tags. No destructive edits, no model calls. Co-authored-by: Hermes --- .hermes-scripts/self-improvement/README.md | 20 ++++ .hermes-scripts/self-improvement/agent.py | 107 +++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 .hermes-scripts/self-improvement/README.md create mode 100755 .hermes-scripts/self-improvement/agent.py diff --git a/.hermes-scripts/self-improvement/README.md b/.hermes-scripts/self-improvement/README.md new file mode 100644 index 0000000..79d45d6 --- /dev/null +++ b/.hermes-scripts/self-improvement/README.md @@ -0,0 +1,20 @@ +# Self-Improvement Agent + +Runs on a cron schedule. Scans targets for safe, mechanical improvements: +- Prepends missing markdown titles +- Flags TODOs/FIXMEs +- Adds missing viewport meta tags + +No rewrites. No model calls. No autonomous destructive edits. + +## Targets +- sovereign-stack-pwa README, HTML +- Hermes skills directory + +## State +- `state.json` tracks runs, patches proposed/applied +- Logs to `/root/.hermes/logs/self-improvement.log` + +## Safety +- Only additive, reversible edits +- No deletions, no logic changes, no config rewrites diff --git a/.hermes-scripts/self-improvement/agent.py b/.hermes-scripts/self-improvement/agent.py new file mode 100755 index 0000000..b389a69 --- /dev/null +++ b/.hermes-scripts/self-improvement/agent.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Self-improvement agent. Reviews recent logs, proposes improvements, applies safe patches.""" +import json, re, sys, os +from datetime import datetime, timedelta + +LOG_PATH = "/root/.hermes/logs/self-improvement.log" +STATE_PATH = "/root/.hermes/scripts/self-improvement/state.json" +TARGETS = [ + "/root/sovereign-stack-pwa/README.md", + "/root/sovereign-stack-pwa/index.html", + "/root/.hermes/skills/", +] + +def log(msg): + with open(LOG_PATH, "a") as f: + f.write(f"[{datetime.utcnow().isoformat()}Z] {msg}\n") + +def load_state(): + if os.path.exists(STATE_PATH): + return json.load(open(STATE_PATH)) + return {"last_run": None, "improvements_applied": 0, "patches_proposed": 0} + +def save_state(state): + json.dump(state, open(STATE_PATH, "w"), indent=2) + +def review_targets(): + """Scan targets for obvious improvement candidates: TODOs, FIXMEs, missing meta tags.""" + candidates = [] + for target in TARGETS: + if os.path.isdir(target): + for root, _, files in os.walk(target): + for name in files: + if name.endswith((".md", ".py", ".html", ".sh", ".json")): + candidates.append(os.path.join(root, name)) + elif os.path.exists(target): + candidates.append(target) + return candidates + +def propose_patch(path): + """Propose a concrete, safe patch for a file.""" + if not os.path.exists(path): + return None + with open(path, "r") as f: + content = f.read() + + # Safe improvements only: no rewrites + patches = [] + if path.endswith(".md") and not content.startswith("#"): + patches.append(("prepend-title", f"# {os.path.basename(path)}\n\n")) + if "TODO" in content or "FIXME" in content: + patches.append(("flag-todos", "\n")) + if path.endswith(".html") and '\n')) + return patches + +def apply_patch(path, patches): + applied = [] + if not patches: + return applied + with open(path, "r") as f: + content = f.read() + + for kind, snippet in patches: + if kind == "prepend-title" and not content.startswith("#"): + content = snippet + content + applied.append(f"Prepended title to {path}") + elif kind == "flag-todos" and "REVIEW_NEEDED" not in content: + content = snippet + content + applied.append(f"Flagged TODOs in {path}") + elif kind == "add-viewport" and '", f"\n {snippet}") + applied.append(f"Added viewport meta to {path}") + + if applied: + with open(path, "w") as f: + f.write(content) + return applied + +def run(): + state = load_state() + now = datetime.utcnow() + log("Self-improvement run start") + + candidates = review_targets() + total_patches = 0 + total_applied = 0 + + for path in candidates[:20]: # Cap to avoid thrashing + patches = propose_patch(path) + if patches: + state["patches_proposed"] += len(patches) + applied = apply_patch(path, patches) + if applied: + state["improvements_applied"] += len(applied) + total_applied += len(applied) + for a in applied: + log(f"APPLIED: {a}") + else: + log(f"PROPOSED but not applied: {path}") + total_patches += len(patches) + + state["last_run"] = now.isoformat() + "Z" + save_state(state) + log(f"Run complete. Patches proposed: {total_patches}, applied: {total_applied}") + +if __name__ == "__main__": + run()