2026-03-12 12:44:15 +00:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# review-poll.sh — Poll open PRs and review those with green CI
|
|
|
|
|
#
|
2026-03-14 13:49:09 +01:00
|
|
|
# Peek while running: cat /tmp/<project>-review-status
|
|
|
|
|
# Full log: tail -f <factory-root>/review/review.log
|
2026-03-12 12:44:15 +00:00
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
refactor: split supervisor into infra + per-project, make poll scripts config-driven
Supervisor split (#26):
- Layer 1 (infra): P0 memory, P1 disk, P4 housekeeping — runs once, project-agnostic
- Layer 2 (per-project): P2 CI/dev-agent, P3 PRs/deps — iterates projects/*.toml
- Adding a new project requires only a new TOML file, no code changes
Poll scripts accept project TOML arg (#27):
- dev-poll.sh, review-poll.sh, gardener-poll.sh accept optional project TOML as $1
- env.sh loads PROJECT_TOML if set, overriding .env defaults
- Cron: `dev-poll.sh projects/versi.toml` targets that project
New files:
- lib/load-project.sh: TOML to env var loader (Python tomllib)
- projects/versi.toml: current project config extracted from .env
Backwards compatible: scripts without a TOML arg fall back to .env config.
Closes #26, Closes #27
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:57:18 +01:00
|
|
|
# Load shared environment (with optional project TOML override)
|
|
|
|
|
# Usage: review-poll.sh [projects/harb.toml]
|
|
|
|
|
export PROJECT_TOML="${1:-}"
|
2026-03-12 12:44:15 +00:00
|
|
|
source "$(dirname "$0")/../lib/env.sh"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REPO="${CODEBERG_REPO}"
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
|
|
|
|
|
API_BASE="${CODEBERG_API}"
|
|
|
|
|
LOGFILE="$SCRIPT_DIR/review.log"
|
|
|
|
|
MAX_REVIEWS=3
|
|
|
|
|
|
|
|
|
|
log() {
|
|
|
|
|
printf '[%s] %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Log rotation
|
|
|
|
|
if [ -f "$LOGFILE" ]; then
|
|
|
|
|
LOGSIZE=$(stat -c%s "$LOGFILE" 2>/dev/null || echo 0)
|
|
|
|
|
if [ "$LOGSIZE" -gt 102400 ]; then
|
|
|
|
|
mv "$LOGFILE" "$LOGFILE.old"
|
|
|
|
|
log "Log rotated"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log "--- Poll start ---"
|
|
|
|
|
|
|
|
|
|
PRS=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
|
|
|
|
"${API_BASE}/pulls?state=open&limit=20" | \
|
2026-03-14 13:49:09 +01:00
|
|
|
jq -r --arg branch "${PRIMARY_BRANCH}" '.[] | select(.base.ref == $branch) | select(.draft != true) | select(.title | test("^\\[?WIP[\\]:]"; "i") | not) | "\(.number) \(.head.sha)"')
|
2026-03-12 12:44:15 +00:00
|
|
|
|
|
|
|
|
if [ -z "$PRS" ]; then
|
2026-03-14 13:49:09 +01:00
|
|
|
log "No open PRs targeting ${PRIMARY_BRANCH}"
|
2026-03-12 12:44:15 +00:00
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
TOTAL=$(echo "$PRS" | wc -l)
|
|
|
|
|
log "Found ${TOTAL} open PRs"
|
|
|
|
|
|
|
|
|
|
REVIEWED=0
|
|
|
|
|
SKIPPED=0
|
|
|
|
|
|
|
|
|
|
while IFS= read -r line; do
|
|
|
|
|
PR_NUM=$(echo "$line" | awk '{print $1}')
|
|
|
|
|
PR_SHA=$(echo "$line" | awk '{print $2}')
|
|
|
|
|
|
|
|
|
|
CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
|
|
|
|
"${API_BASE}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"')
|
|
|
|
|
|
2026-03-17 09:05:43 +00:00
|
|
|
# Skip if CI is running/failed. Allow "success" or no CI configured (empty/pending with no pipelines)
|
2026-03-12 12:44:15 +00:00
|
|
|
if [ "$CI_STATE" != "success" ]; then
|
2026-03-17 09:05:43 +00:00
|
|
|
# Projects without CI (woodpecker_repo_id=0) treat empty/pending as pass
|
2026-03-17 10:18:39 +00:00
|
|
|
if [ "${WOODPECKER_REPO_ID:-2}" = "0" ] && { [ "$CI_STATE" = "" ] || [ "$CI_STATE" = "pending" ]; }; then
|
2026-03-17 09:05:43 +00:00
|
|
|
: # no CI configured, proceed to review
|
|
|
|
|
else
|
|
|
|
|
log " #${PR_NUM} CI=${CI_STATE}, skip"
|
|
|
|
|
SKIPPED=$((SKIPPED + 1))
|
|
|
|
|
continue
|
|
|
|
|
fi
|
2026-03-12 12:44:15 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Check formal Codeberg reviews (not comment markers)
|
|
|
|
|
HAS_REVIEW=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
|
|
|
|
"${API_BASE}/pulls/${PR_NUM}/reviews" | \
|
|
|
|
|
jq -r --arg sha "$PR_SHA" \
|
|
|
|
|
'[.[] | select(.commit_id == $sha) | select(.state != "COMMENT")] | length')
|
|
|
|
|
|
|
|
|
|
if [ "${HAS_REVIEW:-0}" -gt "0" ]; then
|
|
|
|
|
log " #${PR_NUM} formal review exists for ${PR_SHA:0:7}, skip"
|
|
|
|
|
SKIPPED=$((SKIPPED + 1))
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
log " #${PR_NUM} needs review (CI=success, SHA=${PR_SHA:0:7})"
|
|
|
|
|
|
|
|
|
|
if "${SCRIPT_DIR}/review-pr.sh" "$PR_NUM" 2>&1; then
|
|
|
|
|
REVIEWED=$((REVIEWED + 1))
|
|
|
|
|
else
|
|
|
|
|
log " #${PR_NUM} review failed"
|
2026-03-15 10:27:23 +00:00
|
|
|
matrix_send "review" "❌ PR #${PR_NUM} review failed" 2>/dev/null || true
|
2026-03-12 12:44:15 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$REVIEWED" -ge "$MAX_REVIEWS" ]; then
|
|
|
|
|
log "Hit max reviews (${MAX_REVIEWS}), stopping"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
sleep 2
|
|
|
|
|
|
|
|
|
|
done <<< "$PRS"
|
|
|
|
|
|
|
|
|
|
log "--- Poll done: ${REVIEWED} reviewed, ${SKIPPED} skipped ---"
|