diff --git a/tasks.py b/tasks.py index afc5f228..41be0c06 100644 --- a/tasks.py +++ b/tasks.py @@ -2126,3 +2126,47 @@ def cross_review_prs(): continue return {"reviews": len(results), "details": results} + +@huey.periodic_task(crontab(minute="*/45")) +def automerge_tick(): + """Force Multiplier 14: The "Green Light" Auto-Merge. + + Automatically merges low-risk PRs that have passed the Quality Gate (FM 10). + """ + gitea = get_gitea_client() + repos = ["Timmy_Foundation/timmy-config", "Timmy_Foundation/timmy-home", "Timmy_Foundation/the-nexus"] + sensitive_files = ["SOUL.md", "config.yaml", "tasks.py", "deploy.sh", "fallback-portfolios.yaml"] + + for repo in repos: + prs = gitea.get_open_prs(repo) + for pr in prs: + labels = [l['name'] for l in pr.get('labels', [])] + + # Condition 1: Must be verified by automated quality gate + if "verified-automated" not in labels: + continue + + # Condition 2: Must NOT touch sensitive files + files = gitea.get_pull_files(repo, pr['number']) + touches_sensitive = any(f['filename'] in sensitive_files for f in files) + if touches_sensitive: + continue + + # Condition 3: Must be from a trusted agent + author = pr.get('user', {}).get('login', '') + trusted_agents = ["gemini", "claude", "codex-agent"] + if author not in trusted_agents: + continue + + # Condition 4: No "needs-verification" or "blocked" labels + if "needs-verification" in labels or "blocked" in labels: + continue + + # AUTO-MERGE + try: + audit_log("automerge_start", "system", {"repo": repo, "pr": pr['number']}, confidence="High") + gitea.merge_pull_request(repo, pr['number']) + gitea.create_issue_comment(repo, pr['number'], "✅ **Auto-Merge Triggered**: This PR passed all automated quality gates and touched no sensitive files. Merging for velocity.") + audit_log("automerge_complete", "system", {"repo": repo, "pr": pr['number']}, confidence="High") + except Exception as e: + audit_log("automerge_failed", "system", {"repo": repo, "pr": pr['number'], "error": str(e)}, confidence="Low")