44752dc87b
The Phase 2 fork-rollout exposed three bugs that all only manifest when targeting a non-canonical Forgejo repo: 1. tools/duplicate_prs_to_fork.py — Forgejo silently ignores the ?head=owner:branch query parameter that the lookup relied on, so every PR after the first was reported as already_exists. Replace with a paginated client-side filter on head.ref. 2. tools/preflight_phase01.sh — the cursor-existence check used the canonical filename even when targeting a fork, so step 1.4a was SKIPped against the fork's empty state, leaving step 1.4 to FAIL on 50 historical commits. Mirror verify_invariant.py's per-<owner>.<repo> partitioning in shell. 3. tests/auto_agents — three tests hard-coded /repos/cleveragents/cleveragents-core URLs and broke under FORGEJO_OWNER=drew. Derive expected URLs from mod.REPO_OWNER / mod.REPO_NAME so they're independent of the operator's shell env. Also add tests/auto_agents/conftest.py with an autouse fixture that clears every FORGEJO_* env var before each test, locking the suite to a deterministic canonical baseline (env-override tests that use monkeypatch.setenv still take precedence). Two new unit tests cover the head-ref filter mismatch and pagination paths of existing_fork_pr (157 tests passing, up from 155). Documents the Forgejo API quirks observed (head=owner:branch ignored, fork creation returns 202 without immediate availability, forks don't inherit org-level labels, upstream labels not auto-created in forks) in AGENTS.md so future maintainers don't repeat them. Co-authored-by: Cursor <cursoragent@cursor.com>
488 lines
18 KiB
Bash
Executable File
488 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# tools/preflight_phase01.sh — Pre-production validation: Phases 0 & 1.
|
|
#
|
|
# Runs the full read-only validation suite for the deterministic
|
|
# auto-agents pipeline (the commit on `dmpipeline`) and emits a single
|
|
# pass/fail report.
|
|
#
|
|
# Phase 0 — Unit tests (offline, no network)
|
|
# 0.1 pytest tests/auto_agents/ ─ all unit tests for the new tools.
|
|
#
|
|
# Phase 1 — Read-only smoke tests (production Forgejo, no mutations)
|
|
# 1.1 tools/forgejo_audit.py — Tier 0A audit
|
|
# 1.2 tools/audit_branch_protection.py — read-only
|
|
# 1.3 tools/merge_drive.py --status — driver config dump
|
|
# 1.4a tools/verify_invariant.py --seed-cursor — first-run only
|
|
# 1.4 tools/verify_invariant.py --mode observe --dry-run
|
|
# 1.5 tools/flag_stale_prs.py --dry-run — stale-PR planner
|
|
# 1.6 tools/setup_auto_labels.py --dry-run — label provisioning
|
|
# 1.7 tools/setup_branch_protection.py --dry-run
|
|
# 1.8 tools/migrate_to_new_driver.py --dry-run — claim/branch sweep
|
|
# 1.9 list_prs.ts --exclude-claimed — TS-side filter check
|
|
#
|
|
# Step 1.4a runs only on the very first preflight (when the verifier's
|
|
# cursor file does not yet exist), and writes a single local file —
|
|
# no Forgejo mutations. Subsequent runs SKIP it as a no-op.
|
|
#
|
|
# Mutating actions (setup_*, migrate_to_new_driver --apply, the driver
|
|
# itself) are intentionally NOT run here — those are Phase 2+ in the
|
|
# pre-production plan.
|
|
#
|
|
# Usage
|
|
# -----
|
|
#
|
|
# tools/preflight_phase01.sh # full Phase 0 + Phase 1
|
|
# tools/preflight_phase01.sh --phase 0 # only unit tests
|
|
# tools/preflight_phase01.sh --phase 1 # only smoke tests
|
|
# tools/preflight_phase01.sh --skip-ts # skip the TS smoke test
|
|
# tools/preflight_phase01.sh --keep-going # run every step on failure
|
|
# (default; here for symmetry)
|
|
# tools/preflight_phase01.sh --fail-fast # stop after first failure
|
|
# tools/preflight_phase01.sh --help
|
|
#
|
|
# Output
|
|
# ------
|
|
#
|
|
# Per-step logs are written to:
|
|
# tools/.cache/preflight/<UTC_TS>/NN.<step>.log
|
|
# A machine-readable summary is written to:
|
|
# tools/.cache/preflight/<UTC_TS>/summary.json
|
|
# A human-readable summary is printed to stdout at the end.
|
|
#
|
|
# Exit codes
|
|
# ----------
|
|
#
|
|
# 0 — every selected step PASSed (SKIPped steps don't fail the run)
|
|
# 1 — at least one step FAILed
|
|
# 2 — argument error / missing prerequisite
|
|
set -uo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$REPO_ROOT"
|
|
|
|
# ─── Argument parsing ─────────────────────────────────────────────────────
|
|
phase_filter="all" # all | 0 | 1
|
|
skip_ts=0
|
|
fail_fast=0
|
|
|
|
usage() {
|
|
sed -n '2,49p' "$0" | sed 's/^# \{0,1\}//'
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--phase) phase_filter="${2:-}"; shift 2 || { echo "ERROR: --phase needs an argument" >&2; exit 2; } ;;
|
|
--phase=*) phase_filter="${1#*=}"; shift ;;
|
|
--skip-ts) skip_ts=1; shift ;;
|
|
--fail-fast) fail_fast=1; shift ;;
|
|
--keep-going) fail_fast=0; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "ERROR: unknown flag: $1" >&2; usage >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
case "$phase_filter" in
|
|
all|0|1) ;;
|
|
*) echo "ERROR: --phase must be 'all', '0', or '1' (got: $phase_filter)" >&2; exit 2 ;;
|
|
esac
|
|
|
|
# ─── Workspace ────────────────────────────────────────────────────────────
|
|
RUN_TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
RUN_DIR="$REPO_ROOT/tools/.cache/preflight/$RUN_TS"
|
|
mkdir -p "$RUN_DIR"
|
|
|
|
# Pick the best Python interpreter we can find.
|
|
if [[ -x "$REPO_ROOT/.venv/bin/python3" ]]; then
|
|
PY="$REPO_ROOT/.venv/bin/python3"
|
|
elif command -v python3 >/dev/null 2>&1; then
|
|
PY="$(command -v python3)"
|
|
else
|
|
echo "ERROR: no python3 interpreter found (.venv/bin/python3 or PATH)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# ─── Token resolution (mirrors the helpers in tools/*.py) ─────────────────
|
|
resolve_gitea_token() {
|
|
if [[ -n "${GITEA_TOKEN:-}" ]]; then
|
|
printf '%s' "$GITEA_TOKEN"
|
|
return 0
|
|
fi
|
|
for f in "$REPO_ROOT/.devcontainer/.env" "$REPO_ROOT/.env"; do
|
|
[[ -f "$f" ]] || continue
|
|
local val
|
|
val="$(awk -F= '
|
|
/^[[:space:]]*GITEA_TOKEN[[:space:]]*=/ {
|
|
sub(/^[[:space:]]*GITEA_TOKEN[[:space:]]*=[[:space:]]*"?/, "")
|
|
sub(/[" \t#].*$/, "")
|
|
print
|
|
exit
|
|
}' "$f" 2>/dev/null || true)"
|
|
if [[ -n "$val" ]]; then
|
|
printf '%s' "$val"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
GITEA_TOKEN_RESOLVED=""
|
|
if GITEA_TOKEN_RESOLVED="$(resolve_gitea_token)"; then
|
|
export GITEA_TOKEN="$GITEA_TOKEN_RESOLVED"
|
|
fi
|
|
|
|
# ─── Step harness ─────────────────────────────────────────────────────────
|
|
# We track every step's result in parallel arrays and emit a final table.
|
|
STEP_IDS=()
|
|
STEP_NAMES=()
|
|
STEP_PHASES=()
|
|
STEP_RESULTS=() # PASS | FAIL | SKIP
|
|
STEP_DURATIONS=()
|
|
STEP_LOGFILES=()
|
|
STEP_DETAILS=() # short human-readable explanation
|
|
|
|
step_index=0
|
|
|
|
# Print a colourised label if stdout is a TTY.
|
|
if [[ -t 1 ]]; then
|
|
C_PASS=$'\e[32m'; C_FAIL=$'\e[31m'; C_SKIP=$'\e[33m'; C_DIM=$'\e[2m'; C_BOLD=$'\e[1m'; C_OFF=$'\e[0m'
|
|
else
|
|
C_PASS=""; C_FAIL=""; C_SKIP=""; C_DIM=""; C_BOLD=""; C_OFF=""
|
|
fi
|
|
|
|
label_for() {
|
|
case "$1" in
|
|
PASS) printf '%sPASS%s' "$C_PASS" "$C_OFF" ;;
|
|
FAIL) printf '%sFAIL%s' "$C_FAIL" "$C_OFF" ;;
|
|
SKIP) printf '%sSKIP%s' "$C_SKIP" "$C_OFF" ;;
|
|
*) printf '%s' "$1" ;;
|
|
esac
|
|
}
|
|
|
|
# Run a single step. Args:
|
|
# $1 step id (e.g. "0.1", "1.3")
|
|
# $2 step phase ("0" or "1")
|
|
# $3 step name (short)
|
|
# $4 step short detail (printed in the summary table)
|
|
# $5+ command and args to run
|
|
run_step() {
|
|
local id="$1"; shift
|
|
local phase="$1"; shift
|
|
local name="$1"; shift
|
|
local detail="$1"; shift
|
|
|
|
step_index=$((step_index + 1))
|
|
local pad_id; printf -v pad_id '%02d' "$step_index"
|
|
local logfile="$RUN_DIR/${pad_id}.${id}.log"
|
|
|
|
# Phase filter.
|
|
if [[ "$phase_filter" != "all" && "$phase_filter" != "$phase" ]]; then
|
|
STEP_IDS+=("$id"); STEP_NAMES+=("$name"); STEP_PHASES+=("$phase")
|
|
STEP_RESULTS+=("SKIP"); STEP_DURATIONS+=("0")
|
|
STEP_LOGFILES+=("$logfile"); STEP_DETAILS+=("filtered out by --phase")
|
|
printf ' %s [%s] %-44s %s\n' "$(label_for SKIP)" "$id" "$name" \
|
|
"${C_DIM}filtered out by --phase=$phase_filter${C_OFF}"
|
|
: >"$logfile"
|
|
echo "[skipped] phase $phase not selected (filter=$phase_filter)" >"$logfile"
|
|
return 0
|
|
fi
|
|
|
|
printf ' %s [%s] %-44s %s\n' \
|
|
"${C_BOLD}RUN${C_OFF} " "$id" "$name" "${C_DIM}$detail${C_OFF}"
|
|
|
|
local t0 t1 dur status="FAIL"
|
|
t0="$(date +%s)"
|
|
if "$@" >"$logfile" 2>&1; then
|
|
status="PASS"
|
|
fi
|
|
t1="$(date +%s)"
|
|
dur=$((t1 - t0))
|
|
|
|
# Move the cursor up + redraw the line so the user sees PASS/FAIL inline.
|
|
if [[ -t 1 ]]; then
|
|
printf '\e[1A\e[2K'
|
|
fi
|
|
printf ' %s [%s] %-44s %s (%ss, log: %s)\n' \
|
|
"$(label_for "$status")" "$id" "$name" "${C_DIM}$detail${C_OFF}" \
|
|
"$dur" "$(realpath --relative-to="$REPO_ROOT" "$logfile" 2>/dev/null || echo "$logfile")"
|
|
|
|
STEP_IDS+=("$id"); STEP_NAMES+=("$name"); STEP_PHASES+=("$phase")
|
|
STEP_RESULTS+=("$status"); STEP_DURATIONS+=("$dur")
|
|
STEP_LOGFILES+=("$logfile"); STEP_DETAILS+=("$detail")
|
|
|
|
if [[ "$status" == "FAIL" && $fail_fast -eq 1 ]]; then
|
|
echo
|
|
echo "${C_FAIL}--fail-fast: stopping at first failure (step $id).${C_OFF}" >&2
|
|
finalize_and_exit
|
|
fi
|
|
}
|
|
|
|
# Mark a step SKIPped (e.g. missing prerequisite).
|
|
skip_step() {
|
|
local id="$1" phase="$2" name="$3" detail="$4"
|
|
step_index=$((step_index + 1))
|
|
local pad_id; printf -v pad_id '%02d' "$step_index"
|
|
local logfile="$RUN_DIR/${pad_id}.${id}.log"
|
|
: >"$logfile"
|
|
echo "[skipped] $detail" >"$logfile"
|
|
|
|
STEP_IDS+=("$id"); STEP_NAMES+=("$name"); STEP_PHASES+=("$phase")
|
|
STEP_RESULTS+=("SKIP"); STEP_DURATIONS+=("0")
|
|
STEP_LOGFILES+=("$logfile"); STEP_DETAILS+=("$detail")
|
|
|
|
printf ' %s [%s] %-44s %s\n' "$(label_for SKIP)" "$id" "$name" \
|
|
"${C_DIM}$detail${C_OFF}"
|
|
}
|
|
|
|
# ─── Final summary ────────────────────────────────────────────────────────
|
|
finalize_and_exit() {
|
|
local pass=0 fail=0 skip=0
|
|
local i
|
|
for i in "${!STEP_RESULTS[@]}"; do
|
|
case "${STEP_RESULTS[$i]}" in
|
|
PASS) pass=$((pass + 1)) ;;
|
|
FAIL) fail=$((fail + 1)) ;;
|
|
SKIP) skip=$((skip + 1)) ;;
|
|
esac
|
|
done
|
|
|
|
echo
|
|
echo "=========================================================================="
|
|
printf '%sPreflight summary%s — %s\n' "$C_BOLD" "$C_OFF" "$RUN_TS"
|
|
echo " run dir: $RUN_DIR"
|
|
echo "--------------------------------------------------------------------------"
|
|
printf ' %-6s %-6s %-44s %-6s %s\n' "STEP" "PHASE" "NAME" "TIME" "RESULT"
|
|
for i in "${!STEP_RESULTS[@]}"; do
|
|
local status="${STEP_RESULTS[$i]}"
|
|
printf ' %-6s %-6s %-44s %-6s %s\n' \
|
|
"${STEP_IDS[$i]}" "${STEP_PHASES[$i]}" "${STEP_NAMES[$i]}" \
|
|
"${STEP_DURATIONS[$i]}s" "$(label_for "$status")"
|
|
done
|
|
echo "--------------------------------------------------------------------------"
|
|
printf ' total: %d %sPASS%s: %d %sFAIL%s: %d %sSKIP%s: %d\n' \
|
|
"${#STEP_RESULTS[@]}" "$C_PASS" "$C_OFF" "$pass" \
|
|
"$C_FAIL" "$C_OFF" "$fail" "$C_SKIP" "$C_OFF" "$skip"
|
|
echo "=========================================================================="
|
|
|
|
# Failures: dump the last 30 lines of each failing log inline.
|
|
if [[ $fail -gt 0 ]]; then
|
|
echo
|
|
echo "${C_BOLD}Failure tails${C_OFF} (last 30 lines of each FAILed step's log):"
|
|
for i in "${!STEP_RESULTS[@]}"; do
|
|
[[ "${STEP_RESULTS[$i]}" == "FAIL" ]] || continue
|
|
echo
|
|
echo "── [${STEP_IDS[$i]}] ${STEP_NAMES[$i]} — ${STEP_LOGFILES[$i]}"
|
|
tail -n 30 "${STEP_LOGFILES[$i]}" 2>/dev/null | sed 's/^/ /'
|
|
done
|
|
echo
|
|
fi
|
|
|
|
# Machine-readable summary.
|
|
{
|
|
printf '{\n'
|
|
printf ' "run_ts": "%s",\n' "$RUN_TS"
|
|
printf ' "run_dir": "%s",\n' "$RUN_DIR"
|
|
printf ' "totals": { "pass": %d, "fail": %d, "skip": %d, "total": %d },\n' \
|
|
"$pass" "$fail" "$skip" "${#STEP_RESULTS[@]}"
|
|
printf ' "steps": [\n'
|
|
local last=$((${#STEP_RESULTS[@]} - 1))
|
|
for i in "${!STEP_RESULTS[@]}"; do
|
|
printf ' { "id": "%s", "phase": "%s", "name": %s, "result": "%s", "duration_s": %s, "logfile": %s, "detail": %s }' \
|
|
"${STEP_IDS[$i]}" \
|
|
"${STEP_PHASES[$i]}" \
|
|
"$(json_str "${STEP_NAMES[$i]}")" \
|
|
"${STEP_RESULTS[$i]}" \
|
|
"${STEP_DURATIONS[$i]}" \
|
|
"$(json_str "${STEP_LOGFILES[$i]}")" \
|
|
"$(json_str "${STEP_DETAILS[$i]}")"
|
|
if [[ "$i" -lt "$last" ]]; then printf ',\n'; else printf '\n'; fi
|
|
done
|
|
printf ' ]\n'
|
|
printf '}\n'
|
|
} >"$RUN_DIR/summary.json"
|
|
|
|
if [[ $fail -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
}
|
|
|
|
# Tiny JSON string escaper for the summary file.
|
|
json_str() {
|
|
local s="$1"
|
|
s="${s//\\/\\\\}"
|
|
s="${s//\"/\\\"}"
|
|
s="${s//$'\n'/\\n}"
|
|
s="${s//$'\t'/\\t}"
|
|
printf '"%s"' "$s"
|
|
}
|
|
|
|
# Trap so partial runs still emit a summary.
|
|
trap 'finalize_and_exit' INT TERM
|
|
|
|
# ─── Banner ───────────────────────────────────────────────────────────────
|
|
echo "=========================================================================="
|
|
printf '%sPreflight Phase 0 + Phase 1%s — auto-agents pipeline\n' "$C_BOLD" "$C_OFF"
|
|
echo " repo: $REPO_ROOT"
|
|
echo " run timestamp: $RUN_TS"
|
|
echo " python: $PY"
|
|
echo " log dir: $RUN_DIR"
|
|
if [[ -n "${GITEA_TOKEN:-}" ]]; then
|
|
# Don't print the token, just confirm presence + length.
|
|
echo " GITEA_TOKEN: set (${#GITEA_TOKEN} chars)"
|
|
else
|
|
echo " GITEA_TOKEN: NOT SET — Phase 1 steps that need it will be SKIPped"
|
|
fi
|
|
# Surface the Forgejo target so it's obvious when the user is pointing
|
|
# the harness at a fork instead of the canonical repo.
|
|
TARGET_OWNER="${FORGEJO_OWNER:-cleveragents}"
|
|
TARGET_REPO="${FORGEJO_REPO:-cleveragents-core}"
|
|
TARGET_API="${FORGEJO_API_BASE:-https://git.cleverthis.com/api/v1}"
|
|
echo " forgejo target: $TARGET_OWNER/$TARGET_REPO (API: $TARGET_API)"
|
|
if [[ "$TARGET_OWNER/$TARGET_REPO" != "cleveragents/cleveragents-core" ]]; then
|
|
echo " ⚠ NON-DEFAULT TARGET — sentinel/fork mode"
|
|
fi
|
|
echo " phase filter: $phase_filter"
|
|
echo " fail mode: $([[ $fail_fast -eq 1 ]] && echo fail-fast || echo keep-going)"
|
|
echo "=========================================================================="
|
|
echo
|
|
|
|
# ─── Phase 0 — Unit tests ─────────────────────────────────────────────────
|
|
echo "${C_BOLD}Phase 0 — unit tests${C_OFF}"
|
|
run_step "0.1" "0" "pytest tests/auto_agents" \
|
|
"all auto-agents pipeline unit tests" \
|
|
"$PY" -m pytest -q tests/auto_agents
|
|
|
|
# ─── Phase 1 — Read-only smoke tests ──────────────────────────────────────
|
|
echo
|
|
echo "${C_BOLD}Phase 1 — read-only smoke tests against production Forgejo${C_OFF}"
|
|
|
|
needs_token() {
|
|
if [[ -z "${GITEA_TOKEN:-}" ]]; then return 1; fi
|
|
return 0
|
|
}
|
|
|
|
# 1.1 forgejo_audit.py — read-only audit, JSON output.
|
|
if needs_token; then
|
|
run_step "1.1" "1" "forgejo_audit.py" \
|
|
"Tier 0A: branch protection, bot identity, merge config" \
|
|
"$PY" tools/forgejo_audit.py --format json
|
|
else
|
|
skip_step "1.1" "1" "forgejo_audit.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.2 audit_branch_protection.py — dismiss_stale_approvals audit (no --apply).
|
|
if needs_token; then
|
|
run_step "1.2" "1" "audit_branch_protection.py" \
|
|
"Tier 1A: dismiss_stale_approvals state (read-only)" \
|
|
"$PY" tools/audit_branch_protection.py --format json
|
|
else
|
|
skip_step "1.2" "1" "audit_branch_protection.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.3 merge_drive.py --status — driver config + heartbeat snapshot.
|
|
if needs_token; then
|
|
run_step "1.3" "1" "merge_drive.py --status" \
|
|
"driver config dump (no API mutations, no lock acquired)" \
|
|
"$PY" tools/merge_drive.py --status
|
|
else
|
|
skip_step "1.3" "1" "merge_drive.py --status" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.4a Seed the verify_invariant cursor on first run so 1.4 audits a
|
|
# well-defined window (current master HEAD onward) instead of the
|
|
# last 50 historical commits — those pre-date the new merge
|
|
# invariant and would surface as expected-but-noisy violations.
|
|
# Idempotent: SKIPped on every subsequent run.
|
|
# Mirror the per-repo cursor partitioning in tools/verify_invariant.py so
|
|
# fork-mode preflight resolves to the fork's own cursor file. Default
|
|
# canonical cleveragents/cleveragents-core retains the unsuffixed name for
|
|
# backward compatibility.
|
|
__VC_OWNER="${FORGEJO_OWNER:-cleveragents}"
|
|
__VC_REPO="${FORGEJO_REPO:-cleveragents-core}"
|
|
if [[ "$__VC_OWNER" == "cleveragents" && "$__VC_REPO" == "cleveragents-core" ]]; then
|
|
VERIFY_CURSOR_PATH="$REPO_ROOT/tools/.cache/verify_invariant_cursor.txt"
|
|
else
|
|
__VC_SAFE_OWNER="$(printf '%s' "$__VC_OWNER" | tr -c 'a-zA-Z0-9._-' '-')"
|
|
__VC_SAFE_REPO="$(printf '%s' "$__VC_REPO" | tr -c 'a-zA-Z0-9._-' '-')"
|
|
VERIFY_CURSOR_PATH="$REPO_ROOT/tools/.cache/verify_invariant_cursor.${__VC_SAFE_OWNER}.${__VC_SAFE_REPO}.txt"
|
|
fi
|
|
if needs_token; then
|
|
if [[ -f "$VERIFY_CURSOR_PATH" ]]; then
|
|
skip_step "1.4a" "1" "verify_invariant.py --seed-cursor" \
|
|
"cursor already exists; first-run-only step"
|
|
else
|
|
run_step "1.4a" "1" "verify_invariant.py --seed-cursor" \
|
|
"first-run: pin cursor to current master HEAD" \
|
|
"$PY" tools/verify_invariant.py --seed-cursor --format json
|
|
fi
|
|
else
|
|
skip_step "1.4a" "1" "verify_invariant.py --seed-cursor" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.4 verify_invariant.py --mode observe --dry-run.
|
|
if needs_token; then
|
|
run_step "1.4" "1" "verify_invariant.py (observe, dry-run)" \
|
|
"Tier 2E: master-window audit, log-only" \
|
|
"$PY" tools/verify_invariant.py --mode observe --dry-run --format json
|
|
else
|
|
skip_step "1.4" "1" "verify_invariant.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.5 flag_stale_prs.py --dry-run.
|
|
if needs_token; then
|
|
run_step "1.5" "1" "flag_stale_prs.py --dry-run" \
|
|
"Tier 1C: stale-PR planner, no labels written" \
|
|
"$PY" tools/flag_stale_prs.py --dry-run --format json
|
|
else
|
|
skip_step "1.5" "1" "flag_stale_prs.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.6 setup_auto_labels.py --dry-run.
|
|
if needs_token; then
|
|
run_step "1.6" "1" "setup_auto_labels.py --dry-run" \
|
|
"Tier 0B label inventory diff (no creations)" \
|
|
"$PY" tools/setup_auto_labels.py --dry-run --format json
|
|
else
|
|
skip_step "1.6" "1" "setup_auto_labels.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.7 setup_branch_protection.py --dry-run.
|
|
if needs_token; then
|
|
run_step "1.7" "1" "setup_branch_protection.py --dry-run" \
|
|
"Tier 0C push whitelist check (no PATCH)" \
|
|
"$PY" tools/setup_branch_protection.py --dry-run --format json
|
|
else
|
|
skip_step "1.7" "1" "setup_branch_protection.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.8 migrate_to_new_driver.py --dry-run.
|
|
if needs_token; then
|
|
run_step "1.8" "1" "migrate_to_new_driver.py --dry-run" \
|
|
"Tier 0B.5 claim/branch sweep plan (no mutations)" \
|
|
"$PY" tools/migrate_to_new_driver.py --dry-run --format json
|
|
else
|
|
skip_step "1.8" "1" "migrate_to_new_driver.py" "needs GITEA_TOKEN"
|
|
fi
|
|
|
|
# 1.9 list_prs.ts --exclude-claimed (TS smoke; optional).
|
|
if [[ $skip_ts -eq 1 ]]; then
|
|
skip_step "1.9" "1" "list_prs.ts --exclude-claimed" "skipped via --skip-ts"
|
|
elif ! command -v npx >/dev/null 2>&1; then
|
|
skip_step "1.9" "1" "list_prs.ts --exclude-claimed" "npx not available"
|
|
elif ! needs_token; then
|
|
skip_step "1.9" "1" "list_prs.ts --exclude-claimed" "needs GITEA_TOKEN"
|
|
else
|
|
FORGEJO_URL="${FORGEJO_URL:-https://git.cleverthis.com}"
|
|
FORGEJO_OWNER="${FORGEJO_OWNER:-cleveragents}"
|
|
FORGEJO_REPO="${FORGEJO_REPO:-cleveragents-core}"
|
|
run_step "1.9" "1" "list_prs.ts --exclude-claimed" \
|
|
"TS Tier 2B filter; reads PRs, no writes" \
|
|
npx --yes tsx \
|
|
.opencode/skills/auto-agents-system/scripts/list_prs.ts \
|
|
--url "$FORGEJO_URL" --pat "$GITEA_TOKEN" \
|
|
--owner "$FORGEJO_OWNER" --repo "$FORGEJO_REPO" \
|
|
--exclude-claimed --state open
|
|
fi
|
|
|
|
finalize_and_exit
|