diff --git a/bin/nexus-merge-bot.sh b/bin/nexus-merge-bot.sh deleted file mode 100755 index 34057b6b..00000000 --- a/bin/nexus-merge-bot.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env bash -# nexus-merge-bot.sh — Auto-review and auto-merge for the-nexus -# Polls open PRs. For each: clone, validate (HTML/JS/JSON/size), merge if clean. -# Runs as a loop. Squash-only. Linear history. -# -# Pattern: matches Timmy-time-dashboard merge policy. -# Pre-commit hooks + this bot are the gates. If gates pass, auto-merge. - -set -uo pipefail - -LOG_DIR="$HOME/.hermes/logs" -LOG="$LOG_DIR/nexus-merge-bot.log" -PIDFILE="$LOG_DIR/nexus-merge-bot.pid" -GITEA_URL="http://143.198.27.163:3000" -GITEA_TOKEN=$(cat "$HOME/.hermes/gitea_token_vps" 2>/dev/null) -REPO="Timmy_Foundation/the-nexus" -CHECK_INTERVAL=60 # 2 minutes - -mkdir -p "$LOG_DIR" - -# Single instance guard -if [ -f "$PIDFILE" ]; then - old_pid=$(cat "$PIDFILE") - if kill -0 "$old_pid" 2>/dev/null; then - echo "Merge bot already running (PID $old_pid)" >&2 - exit 0 - fi -fi -echo $$ > "$PIDFILE" -trap 'rm -f "$PIDFILE"' EXIT - -log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] MERGE-BOT: $*" >> "$LOG" -} - -validate_pr() { - local pr_num="$1" - local work_dir="/tmp/nexus-validate-$$" - rm -rf "$work_dir" - - # Get PR head branch - local pr_info - pr_info=$(curl -s --max-time 10 -H "Authorization: token ${GITEA_TOKEN}" \ - "${GITEA_URL}/api/v1/repos/${REPO}/pulls/${pr_num}") - - local head_ref - head_ref=$(echo "$pr_info" | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['head']['ref'])" 2>/dev/null) - local mergeable - mergeable=$(echo "$pr_info" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('mergeable', False))" 2>/dev/null) - - if [ "$mergeable" != "True" ]; then - log "PR #${pr_num}: not mergeable (conflicts), skipping" - echo "CONFLICT" - return 1 - fi - - # Clone and checkout the PR branch - git clone -q --depth 20 \ - "http://Timmy:${GITEA_TOKEN}@143.198.27.163:3000/${REPO}.git" "$work_dir" 2>&1 | tail -5 >> "$LOG" - - if [ ! -d "$work_dir/.git" ]; then - log "PR #${pr_num}: clone failed" - echo "CLONE_FAIL" - return 1 - fi - - cd "$work_dir" || return 1 - - # Fetch and checkout the PR branch - git fetch origin "$head_ref" 2>/dev/null && git checkout "$head_ref" 2>/dev/null - if [ $? -ne 0 ]; then - # Try fetching the PR ref directly - git fetch origin "pull/${pr_num}/head:pr-${pr_num}" 2>/dev/null && git checkout "pr-${pr_num}" 2>/dev/null - fi - local FAIL=0 - - # 1. HTML validation - if [ -f index.html ]; then - python3 -c " -import html.parser -class V(html.parser.HTMLParser): - pass -v = V() -v.feed(open('index.html').read()) -" 2>/dev/null || { log "PR #${pr_num}: HTML validation failed"; FAIL=1; } - fi - - # 2. JS syntax check (node --check) - for f in $(find . -name '*.js' -not -path './node_modules/*' 2>/dev/null); do - if command -v node >/dev/null 2>&1; then - if ! node --check "$f" 2>/dev/null; then - log "PR #${pr_num}: JS syntax error in $f" - FAIL=1 - fi - fi - done - - # 3. JSON validation - for f in $(find . -name '*.json' -not -path './node_modules/*' 2>/dev/null); do - if ! python3 -c "import json; json.load(open('$f'))" 2>/dev/null; then - log "PR #${pr_num}: invalid JSON in $f" - FAIL=1 - fi - done - - # 4. File size budget (500KB per JS file) - for f in $(find . -name '*.js' -not -path './node_modules/*' 2>/dev/null); do - local size - size=$(wc -c < "$f") - if [ "$size" -gt 512000 ]; then - log "PR #${pr_num}: $f exceeds 500KB budget (${size} bytes)" - FAIL=1 - fi - done - - # Cleanup - rm -rf "$work_dir" - - if [ $FAIL -eq 0 ]; then - echo "PASS" - return 0 - else - echo "FAIL" - return 1 - fi -} - -merge_pr() { - local pr_num="$1" - local result - result=$(curl -s --max-time 30 -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{"Do":"squash","delete_branch_after_merge":true}' \ - "${GITEA_URL}/api/v1/repos/${REPO}/pulls/${pr_num}/merge") - - if echo "$result" | grep -q '"sha"'; then - log "PR #${pr_num}: MERGED (squash)" - return 0 - elif echo "$result" | grep -q '"message"'; then - local msg - msg=$(echo "$result" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('message','unknown'))" 2>/dev/null) - log "PR #${pr_num}: merge failed: $msg" - return 1 - fi -} - -comment_pr() { - local pr_num="$1" - local body="$2" - curl -s --max-time 10 -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"body\": \"$body\"}" \ - "${GITEA_URL}/api/v1/repos/${REPO}/issues/${pr_num}/comments" >/dev/null -} - -log "Starting nexus merge bot (PID $$)" - -while true; do - # Get open PRs - prs=$(curl -s --max-time 15 -H "Authorization: token ${GITEA_TOKEN}" \ - "${GITEA_URL}/api/v1/repos/${REPO}/pulls?state=open&sort=newest&limit=20") - - pr_count=$(echo "$prs" | python3 -c "import sys,json; print(len(json.loads(sys.stdin.buffer.read())))" 2>/dev/null || echo "0") - - if [ "$pr_count" = "0" ] || [ -z "$pr_count" ]; then - log "No open PRs. Sleeping ${CHECK_INTERVAL}s" - sleep "$CHECK_INTERVAL" - continue - fi - - log "Found ${pr_count} open PRs, validating..." - - # Process PRs one at a time, oldest first (sequential merge) - pr_nums=$(echo "$prs" | python3 -c " -import sys, json -prs = json.loads(sys.stdin.buffer.read()) -for p in prs: - print(p['number']) -" 2>/dev/null) - - for pr_num in $pr_nums; do - log "Validating PR #${pr_num}..." - result=$(validate_pr "$pr_num") - - case "$result" in - PASS) - log "PR #${pr_num}: validation passed, merging..." - comment_pr "$pr_num" "🤖 **Merge Bot**: CI validation passed (HTML, JS syntax, JSON, size budget). Auto-merging." - merge_pr "$pr_num" - # Wait a beat for Gitea to process - sleep 5 - ;; - CONFLICT) - # Auto-close stale conflicting PRs — don't let them pile up - log "PR #${pr_num}: conflicts, closing" - comment_pr "$pr_num" "🤖 **Merge Bot**: Merge conflicts with main. Closing. The issue remains open — next agent cycle will pick it up fresh." - curl -s --max-time 5 -X PATCH \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d '{"state":"closed"}' \ - "${GITEA_URL}/api/v1/repos/${REPO}/pulls/${pr_num}" >/dev/null 2>&1 - ;; - FAIL) - comment_pr "$pr_num" "🤖 **Merge Bot**: CI validation failed. Check the merge-bot log for details." - ;; - *) - log "PR #${pr_num}: unknown result: $result" - ;; - esac - done - - log "Cycle complete. Sleeping ${CHECK_INTERVAL}s" - sleep "$CHECK_INTERVAL" -done