remove deprecated nexus-merge-bot.sh — replaced by sovereign-orchestration
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user