2658deee94
Adds a long-lived sidecar (pr_state_warmer.py) that polls Forgejo's /pulls endpoint every 30s and writes the full PR snapshot to a shared SQLite store, eliminating the dispatcher's per-cycle cold-cache stalls (24-30s rebuilds on flaky cycles) and the silent 50-PR pagination cap on the legacy single-page fetch. Substrate - tools/_pr_state_cache.py — SQLite store with (owner, repo) PK, WAL mode, additive v2→v3 migration (comments_refreshed_updated_at), bounded fcntl.flock migration lock, threading.Lock for per-process init, @_with_reheal decorator (catches OperationalError no-such- table + DatabaseError corruption with file quarantine), atomic TEMP-table chunking for >32k seen-set, _normalize_updated_at to canonicalize Forgejo tz-marker drift - tools/pr_state_warmer.py — poll/upsert/vanish/comments-refresh loop with fcntl.flock singleton (rejects second warmer), bounded comments-refresh cap, persistent deferral via SQL pending query, PermissionError-tolerant lock setup, cold-start log suppression - tools/_pr_classification_cache.py — three-layer fall-through (warmer cache → list cache → live fetch) with staleness gate (PR_STATE_WARMER_STALE_AFTER_S floored at 30s in prod) Comments cache hardening - Bot-filter at write time drops bot status/claim/release/sentinel while preserving **Implementation Attempt** markers (94.6% reduction on bot-heavy PRs like #30's 19k-comment thread) - _normalize_since_cursor strips microsecond precision before building ?since= query (fixes the live-observed Forgejo HTTP 422 bug on PRs #25 + #28); handles uppercase Z, lowercase z, ±HH:MM offsets (including non-zero like +05:30), naive ISO - Lazy migration of legacy null-key by_author entries on _read_cache - _newest_cursor walks tail-back skipping malformed entries Supporting infrastructure (cumulative dmpipeline-v2 work) - Telemetry server: SSE live tail, run-sessions enumeration, cost/token tracking, app.js UI rewrite with collapsible sections - MCP servers (mcp_ci_server, mcp_forgejo_server, mcp_git_server, mcp_handoff_server, mcp_graphify_server) for opencode worker context access - Live log writer (tools/live_log_writer.py) — SSE-streaming dispatcher event log - Tier-dispatcher escalation flow with prompts trimmed for budget - Shared bot-logins resolver (tools/_bot_logins.py) replacing two drift-prone copies - token_usage_audit.py for opencode cost analysis Tests - 2259 passing across 65 changed/new files - New suites: test_pr_state_cache, test_pr_state_warmer, test_pr_state_warmer_integration, test_pr_classification_cache, test_pr_list_cache_backoff, test_mcp_* (5 servers), test_live_log_writer_sse, test_telemetry_run_sessions, test_review_post_ready_label - Test_pr_comments_cache expanded with bot-filter coverage, cursor-normalization regression pins, format-drift, atomicity, failed-comments-not-stamped (silent-data-loss class) - Parametrized @_with_reheal coverage across 7 wrapped APIs - Real fault-inject atomicity test for chunked mark_vanished path via Connection wrapper class - Subprocess-based singleton flock test (cross-process contract) - Event-driven SIGTERM-mid-poll test (no fixed-sleep flake) Architecture notes - Schema v3 migration is additive (ALTER ADD COLUMN); v0/v1 still need destructive rebuild because pre-v2 column shape lacks owner/repo. Cross-process drop-table-ping-pong prevented by the fcntl migration lock + per-process _initialized flag. - Comments-refresh deferral is persistent via comments_refreshed_updated_at column — survives warmer restart, picks up next cycle even if PR didn't change again. Replaces in-memory changed_numbers list. - Rollback path: PR_STATE_WARMER_PREFER=0 bypasses the warmer cache and reverts to live-fetch behavior. PR_STATE_CACHE_DISABLE=1 short-circuits the warmer process at startup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
468 lines
20 KiB
Bash
Executable File
468 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# tools/launch_fork.sh — Pin fork-mode env for the auto-agents pipeline.
|
|
#
|
|
# Why this exists
|
|
# ---------------
|
|
#
|
|
# The auto-agents pipeline has three complementary host-level entry
|
|
# points:
|
|
#
|
|
# 1. `scripts/dispatchers-launcher.sh` — runs the deterministic
|
|
# Python dispatchers (`tools/dispatch_review.py` and
|
|
# `tools/dispatch_implementer.py`) that own all reviewer /
|
|
# implementer orchestration. They read `FORGEJO_*` env vars
|
|
# directly and invoke the LLM workers (`pr-review-worker`,
|
|
# `implementation-worker`) per work item via OpenCode HTTP.
|
|
# These daemons replaced the legacy `pr-review-supervisor` /
|
|
# `implementation-supervisor` LLM agents that were
|
|
# decommissioned 2026-05-09.
|
|
#
|
|
# 2. `scripts/opencode-builder.sh` — boots the OpenCode server (or
|
|
# attaches to one already running on `127.0.0.1:4096`) and starts
|
|
# the `auto-agents` watchdog session. The watchdog reads
|
|
# dispatcher heartbeat files and logs WARNINGs; it does NOT
|
|
# restart anything. The OpenCode server it boots is also what
|
|
# the dispatchers above use for their HTTP worker invocations.
|
|
# Pass `OPENCODE_BUILDER_SERVER_ONLY=1` to skip the watchdog
|
|
# session and run server-only.
|
|
#
|
|
# 3. `tools/merge_drive.py` — the deterministic merge driver that
|
|
# enforces the train-build CI invariant. It reads `FORGEJO_OWNER`,
|
|
# `FORGEJO_REPO`, `FORGEJO_API_BASE`, and `GITEA_TOKEN` directly.
|
|
#
|
|
# Both entry points fall through to the canonical
|
|
# `cleveragents/cleveragents-core` when env vars aren't set, so a
|
|
# fork-mode test launched without a preamble will SILENTLY operate
|
|
# against the canonical repo. This script is the source-able preamble
|
|
# that pins everything to the operator's fork in one place.
|
|
#
|
|
# What gets exported (canonical names per auto-agents.md)
|
|
# -------------------------------------------------------
|
|
#
|
|
# Required by the auto-agents env contract:
|
|
# GIT_USER_NAME — git user.name for HAL9000's commits
|
|
# GIT_USER_EMAIL — git user.email for HAL9000's commits
|
|
# FORGEJO_PAT — HAL9000 PAT (LLM worker + driver identity)
|
|
# FORGEJO_USERNAME — HAL9000's login (resolved via whoami)
|
|
# FORGEJO_PASSWORD — HAL9000 web password (CI-log scraping)
|
|
# FORGEJO_REVIEWER_PAT — HAL9001 PAT (umbrella-PR auto-approver)
|
|
# FORGEJO_REVIEWER_USERNAME — HAL9001's login (resolved via whoami)
|
|
# FORGEJO_REVIEWER_PASSWORD — HAL9001 web password
|
|
#
|
|
# Driver-side compat:
|
|
# GITEA_TOKEN — synthesized from FORGEJO_PAT for the
|
|
# deterministic Python tools that still
|
|
# read this name (merge_drive.py,
|
|
# verify_invariant.py, audit_branch_protection.py,
|
|
# setup_*.py, etc.). They authenticate as
|
|
# HAL9000 — same identity as production runs.
|
|
# Pre-export GITEA_TOKEN before sourcing if
|
|
# you want to use a different identity (e.g.
|
|
# drew's admin PAT for one-time setup).
|
|
#
|
|
# Optional / driver-side:
|
|
# FORGEJO_URL — base URL (default: https://git.cleverthis.com)
|
|
# FORGEJO_OWNER — fork owner (default: drew)
|
|
# FORGEJO_REPO — fork repo (default: cleveragents-core)
|
|
# FORGEJO_API_BASE — derived from FORGEJO_URL
|
|
# FORGEJO_DEFAULT_BRANCH — default: master
|
|
# FORGEJO_ORG — org-label fallback only (default: cleveragents)
|
|
# CA_MAX_PARALLEL_WORKERS — historical knob; the deterministic
|
|
# dispatchers don't read it today, but
|
|
# the value is still re-exported so any
|
|
# future opt-in worker-pool implementation
|
|
# picks it up without operator changes.
|
|
# (Originally a supervisor-pool size for
|
|
# the now-decommissioned LLM supervisors.)
|
|
#
|
|
# Resolution priority for each name:
|
|
# 1. value already in the environment
|
|
# 2. canonical name in .devcontainer/.env or .env
|
|
# 3. HAL_* alias in .devcontainer/.env (PAT/password)
|
|
# 4. derive via API whoami (USERNAME only) or hard-coded default
|
|
#
|
|
# Usage
|
|
# -----
|
|
#
|
|
# source tools/launch_fork.sh
|
|
# tools/preflight_phase01.sh
|
|
# .venv/bin/python3 tools/merge_drive.py --status
|
|
# scripts/opencode-builder.sh
|
|
#
|
|
# # Override fork target:
|
|
# FORK_OWNER=otheruser FORK_REPO=myfork source tools/launch_fork.sh
|
|
#
|
|
# Safety guarantees
|
|
# -----------------
|
|
#
|
|
# - Validates the fork via a single read-only `GET /repos/<owner>/<repo>`
|
|
# (must exist, must be a fork, calling user must have push).
|
|
# - All resolved values stay in LOCAL variables until validation passes.
|
|
# On any failure, the parent shell's environment is left untouched —
|
|
# no FORGEJO_*, no GIT_*, no GITEA_TOKEN leakage.
|
|
# - A `printenv FORGEJO_PASSWORD | head -c 4` style probe in the parent
|
|
# shell is the operator's audit primitive; values are never echoed
|
|
# verbatim in the banner.
|
|
#
|
|
# Exit / return codes
|
|
# -------------------
|
|
#
|
|
# 0 — env exported, fork validated
|
|
# 1 — validation failed; nothing exported
|
|
# 2 — argument error / missing prerequisite
|
|
|
|
# Detect sourced vs executed once, up front. ${BASH_SOURCE[0]} == ${0}
|
|
# means "executed directly"; anything else means "sourced".
|
|
__lf_sourced=0
|
|
if [[ "${BASH_SOURCE[0]:-}" != "${0}" ]]; then
|
|
__lf_sourced=1
|
|
fi
|
|
|
|
# Wrap all logic in a function so we can `return` at any point without
|
|
# falling through into the export block. The function only exports
|
|
# anything on its successful path; failure paths print an error and
|
|
# return non-zero.
|
|
__lf_main() {
|
|
local script_path="${BASH_SOURCE[0]:-$0}"
|
|
local repo_root
|
|
repo_root="$(cd "$(dirname "$script_path")/.." && pwd)"
|
|
|
|
# ─── Defaults (overridable via FORK_* env vars) ───────────────────
|
|
local fork_owner="${FORK_OWNER:-drew}"
|
|
local fork_repo="${FORK_REPO:-cleveragents-core}"
|
|
local fork_url="${FORK_URL:-https://git.cleverthis.com}"
|
|
local fork_api_base="${FORK_API_BASE:-${fork_url%/}/api/v1}"
|
|
local fork_default_branch="${FORK_DEFAULT_BRANCH:-master}"
|
|
|
|
# ─── Step 0 — pick a python3 we can rely on ───────────────────────
|
|
local py
|
|
if [[ -x "$repo_root/.venv/bin/python3" ]]; then
|
|
py="$repo_root/.venv/bin/python3"
|
|
else
|
|
py="$(command -v python3 || true)"
|
|
fi
|
|
if [[ -z "$py" ]]; then
|
|
echo "ERROR: python3 not found (needed for fork validation and JSON parsing)" >&2
|
|
return 2
|
|
fi
|
|
|
|
# ─── Step 1 — small .env reader ───────────────────────────────────
|
|
# Reads KEY=value or KEY="value" from `.devcontainer/.env` then `.env`,
|
|
# returning the first match. Single-quoted values are also handled.
|
|
__lf_resolve_from_env() {
|
|
local key="$1"
|
|
local val=""
|
|
for f in "$repo_root/.devcontainer/.env" "$repo_root/.env"; do
|
|
[[ -f "$f" ]] || continue
|
|
val="$(awk -v k="$key" -F= '
|
|
$0 ~ "^[[:space:]]*"k"[[:space:]]*=" {
|
|
# Strip key + leading whitespace and `=`
|
|
sub("^[[:space:]]*"k"[[:space:]]*=[[:space:]]*", "")
|
|
# Strip optional surrounding quotes
|
|
if (substr($0,1,1) == "\"" || substr($0,1,1) == "'\''") {
|
|
q = substr($0,1,1)
|
|
sub("^"q, "")
|
|
sub(q".*$", "")
|
|
} else {
|
|
# Unquoted: stop at first whitespace or `#`
|
|
sub(/[[:space:]#].*$/, "")
|
|
}
|
|
print
|
|
exit
|
|
}' "$f" 2>/dev/null || true)"
|
|
if [[ -n "$val" ]]; then
|
|
printf '%s' "$val"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Resolve <CANONICAL_NAME> with fallback to one or more aliases.
|
|
# Echoes the resolved value and returns 0; returns 1 if nothing found.
|
|
__lf_resolve_with_aliases() {
|
|
local canonical="$1"; shift
|
|
local existing="${!canonical:-}"
|
|
if [[ -n "$existing" ]]; then
|
|
printf '%s' "$existing"
|
|
return 0
|
|
fi
|
|
local v
|
|
if v="$(__lf_resolve_from_env "$canonical")"; then
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
local alias
|
|
for alias in "$@"; do
|
|
if v="$(__lf_resolve_from_env "$alias")"; then
|
|
printf '%s' "$v"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Helper: resolve a Forgejo login from a PAT via /api/v1/user.
|
|
__lf_whoami() {
|
|
local token="$1"
|
|
[[ -n "$token" ]] || return 1
|
|
curl -sf -H "Authorization: token $token" "$fork_api_base/user" 2>/dev/null \
|
|
| "$py" -c 'import json,sys
|
|
try:
|
|
print(json.load(sys.stdin).get("login") or "")
|
|
except Exception:
|
|
pass' 2>/dev/null || true
|
|
}
|
|
|
|
# ─── Step 2 — resolve every value into local variables ────────────
|
|
# Primary bot (HAL9000) PAT + password.
|
|
local resolved_pat
|
|
resolved_pat="$(__lf_resolve_with_aliases FORGEJO_PAT HAL_9000_API_KEY)" || resolved_pat=""
|
|
local resolved_pwd
|
|
resolved_pwd="$(__lf_resolve_with_aliases FORGEJO_PASSWORD HAL_9000_FORGEJO_PASSWORD)" || resolved_pwd=""
|
|
|
|
# GITEA_TOKEN is synthesized from FORGEJO_PAT (HAL9000) by default — that's
|
|
# the production-fidelity identity for the deterministic Python tools.
|
|
# Operators who want to use a different identity (e.g. drew's admin PAT
|
|
# for one-time fork setup) can pre-export GITEA_TOKEN before sourcing.
|
|
local resolved_gitea="${GITEA_TOKEN:-$resolved_pat}"
|
|
if [[ -z "$resolved_gitea" ]]; then
|
|
echo "ERROR: cannot resolve a token for GITEA_TOKEN — neither GITEA_TOKEN" >&2
|
|
echo " nor FORGEJO_PAT (or HAL_9000_API_KEY) is set." >&2
|
|
return 2
|
|
fi
|
|
|
|
# Reviewer bot (HAL9001) PAT + password.
|
|
local resolved_rev_pat
|
|
resolved_rev_pat="$(__lf_resolve_with_aliases FORGEJO_REVIEWER_PAT HAL_9001_API_KEY)" || resolved_rev_pat=""
|
|
local resolved_rev_pwd
|
|
resolved_rev_pwd="$(__lf_resolve_with_aliases FORGEJO_REVIEWER_PASSWORD HAL_9001_FORGEJO_PASSWORD)" || resolved_rev_pwd=""
|
|
|
|
# Git identity for HAL9000's commits. Defaults match Jeff's confirmed
|
|
# production env (`CleverThis` / `hal9000@cleverthis.com`) — also the
|
|
# author of every existing HAL9000 commit on the canonical repo.
|
|
local resolved_git_name
|
|
resolved_git_name="$(__lf_resolve_with_aliases GIT_USER_NAME)" || resolved_git_name="CleverThis"
|
|
local resolved_git_email
|
|
resolved_git_email="$(__lf_resolve_with_aliases GIT_USER_EMAIL)" || resolved_git_email="hal9000@cleverthis.com"
|
|
|
|
# CA_MAX_PARALLEL_WORKERS is optional; we pass it through if it exists.
|
|
local resolved_workers
|
|
resolved_workers="$(__lf_resolve_with_aliases CA_MAX_PARALLEL_WORKERS)" || resolved_workers=""
|
|
|
|
# FORGEJO_USERNAME / FORGEJO_REVIEWER_USERNAME come from .env or whoami.
|
|
local resolved_user
|
|
resolved_user="$(__lf_resolve_with_aliases FORGEJO_USERNAME)" || resolved_user=""
|
|
if [[ -z "$resolved_user" && -n "$resolved_pat" ]]; then
|
|
resolved_user="$(__lf_whoami "$resolved_pat")"
|
|
fi
|
|
local resolved_rev_user
|
|
resolved_rev_user="$(__lf_resolve_with_aliases FORGEJO_REVIEWER_USERNAME)" || resolved_rev_user=""
|
|
if [[ -z "$resolved_rev_user" && -n "$resolved_rev_pat" ]]; then
|
|
resolved_rev_user="$(__lf_whoami "$resolved_rev_pat")"
|
|
fi
|
|
|
|
# ─── Step 3 — required-var validation ─────────────────────────────
|
|
# The auto-agents agent will exit on startup if any of these are
|
|
# missing, so we surface that failure here instead of letting it crash
|
|
# 30 minutes into a run.
|
|
local -a missing=()
|
|
[[ -n "$resolved_pat" ]] || missing+=("FORGEJO_PAT (or HAL_9000_API_KEY)")
|
|
[[ -n "$resolved_pwd" ]] || missing+=("FORGEJO_PASSWORD (or HAL_9000_FORGEJO_PASSWORD)")
|
|
[[ -n "$resolved_user" ]] || missing+=("FORGEJO_USERNAME (no whoami match — PAT invalid?)")
|
|
[[ -n "$resolved_rev_pat" ]] || missing+=("FORGEJO_REVIEWER_PAT (or HAL_9001_API_KEY)")
|
|
[[ -n "$resolved_rev_pwd" ]] || missing+=("FORGEJO_REVIEWER_PASSWORD (or HAL_9001_FORGEJO_PASSWORD)")
|
|
[[ -n "$resolved_rev_user" ]] || missing+=("FORGEJO_REVIEWER_USERNAME (no whoami match — reviewer PAT invalid?)")
|
|
if (( ${#missing[@]} > 0 )); then
|
|
echo "ERROR: required env vars per .opencode/agents/auto-agents.md missing or unresolvable:" >&2
|
|
local m
|
|
for m in "${missing[@]}"; do
|
|
echo " - $m" >&2
|
|
done
|
|
echo "Refusing to launch fork-mode pipeline; nothing exported." >&2
|
|
return 1
|
|
fi
|
|
|
|
# ─── Step 4 — validate fork repo + driver permission ─────────────
|
|
local validation_json
|
|
if ! validation_json="$(
|
|
GITEA_TOKEN="$resolved_gitea" \
|
|
FORK_OWNER="$fork_owner" \
|
|
FORK_REPO="$fork_repo" \
|
|
FORK_API_BASE="$fork_api_base" \
|
|
"$py" - <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
token = os.environ["GITEA_TOKEN"]
|
|
owner = os.environ["FORK_OWNER"]
|
|
repo = os.environ["FORK_REPO"]
|
|
base = os.environ["FORK_API_BASE"].rstrip("/")
|
|
url = f"{base}/repos/{owner}/{repo}"
|
|
req = urllib.request.Request(
|
|
url,
|
|
headers={"Authorization": f"token {token}", "Accept": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
body = json.loads(resp.read())
|
|
except urllib.error.HTTPError as e:
|
|
msg = e.read().decode("utf-8", "replace")[:200]
|
|
print(f"ERROR: GET {url} returned HTTP {e.code}: {msg}", file=sys.stderr)
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"ERROR: GET {url} failed: {e!r}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
problems: list[str] = []
|
|
if not body.get("fork"):
|
|
problems.append(
|
|
f"target {owner}/{repo} is not a fork (fork={body.get('fork')!r}); "
|
|
"refusing to launch fork-mode pipeline against a non-fork."
|
|
)
|
|
parent_full = (body.get("parent") or {}).get("full_name")
|
|
perms = body.get("permissions") or {}
|
|
if not perms.get("push"):
|
|
problems.append(
|
|
f"calling user lacks push permission on {owner}/{repo} "
|
|
f"(permissions={perms!r}); driver cannot push train branches."
|
|
)
|
|
if problems:
|
|
for p in problems:
|
|
print(f"ERROR: {p}", file=sys.stderr)
|
|
sys.exit(1)
|
|
print(json.dumps({
|
|
"owner": owner,
|
|
"repo": repo,
|
|
"full_name": body.get("full_name"),
|
|
"parent_full_name": parent_full,
|
|
"default_branch": body.get("default_branch"),
|
|
}))
|
|
PY
|
|
)"; then
|
|
echo "ERROR: fork validation failed; nothing exported." >&2
|
|
return 1
|
|
fi
|
|
|
|
# ─── Step 5 — commit resolved values to the parent shell ─────────
|
|
# Driver-side
|
|
export GITEA_TOKEN="$resolved_gitea"
|
|
export FORGEJO_OWNER="$fork_owner"
|
|
export FORGEJO_REPO="$fork_repo"
|
|
export FORGEJO_URL="$fork_url"
|
|
export FORGEJO_API_BASE="$fork_api_base"
|
|
export FORGEJO_DEFAULT_BRANCH="$fork_default_branch"
|
|
export FORGEJO_ORG="${FORGEJO_ORG:-cleveragents}"
|
|
|
|
# Auto-agents env contract (8 required + 1 optional)
|
|
export GIT_USER_NAME="$resolved_git_name"
|
|
export GIT_USER_EMAIL="$resolved_git_email"
|
|
export FORGEJO_PAT="$resolved_pat"
|
|
export FORGEJO_USERNAME="$resolved_user"
|
|
export FORGEJO_PASSWORD="$resolved_pwd"
|
|
export FORGEJO_REVIEWER_PAT="$resolved_rev_pat"
|
|
export FORGEJO_REVIEWER_USERNAME="$resolved_rev_user"
|
|
export FORGEJO_REVIEWER_PASSWORD="$resolved_rev_pwd"
|
|
if [[ -n "$resolved_workers" ]]; then
|
|
export CA_MAX_PARALLEL_WORKERS="$resolved_workers"
|
|
fi
|
|
|
|
# G11 (2026-05-15): default the implementer estimator ON for fork-mode runs
|
|
# so the dispatcher exercises the new confidence-driven tier selection
|
|
# rather than the Tier-0 short-circuit. Operators can opt out for a single
|
|
# cycle by pre-exporting IMPLEMENTER_ESTIMATOR_ENABLED=0 before sourcing.
|
|
export IMPLEMENTER_ESTIMATOR_ENABLED="${IMPLEMENTER_ESTIMATOR_ENABLED:-1}"
|
|
|
|
# Merge driver is normally silent on idle cycles (only WARN/ERROR surface
|
|
# at default INFO level). Fork-mode runs are operator-driven debugging
|
|
# sessions where the cycle-by-cycle "what did the driver check / why did
|
|
# it skip this PR" trace is the load-bearing signal. Default to DEBUG so
|
|
# the loop-merge log is useful out of the box; pre-export
|
|
# MERGE_DRIVER_LOG_LEVEL=INFO before sourcing if you want production-style
|
|
# quiet output for a one-off test.
|
|
export MERGE_DRIVER_LOG_LEVEL="${MERGE_DRIVER_LOG_LEVEL:-DEBUG}"
|
|
|
|
# Phase 2 of the .drew/planning/fix list_prs_by_filter.md cutover
|
|
# (2026-05-16): reviewer dispatcher uses the Python-side delta-cached
|
|
# PR enumeration (`_pr_classification_cache.refresh_then_filter`)
|
|
# for the 5 work-group filters instead of subprocessing the
|
|
# `list_prs_*.ts` scripts. Eliminates the recurring 120s
|
|
# subprocess.TimeoutExpired on `list_prs_missing_ci_checks.ts`.
|
|
# Default-ON in fork-mode; pre-export `=0` for emergency rollback
|
|
# to the legacy TS-script path.
|
|
export REVIEW_DISPATCHER_USE_PYTHON_FILTERS="${REVIEW_DISPATCHER_USE_PYTHON_FILTERS:-1}"
|
|
|
|
# ─── Step 6 — banner ─────────────────────────────────────────────
|
|
local parent_full
|
|
parent_full="$(printf '%s' "$validation_json" | "$py" -c '
|
|
import json,sys
|
|
d=json.load(sys.stdin)
|
|
print(d.get("parent_full_name") or "(none)")')"
|
|
|
|
# Identity check on the synthesized GITEA_TOKEN (typically same as
|
|
# FORGEJO_PAT, but operators can override with an admin PAT).
|
|
local gitea_login
|
|
gitea_login="$(__lf_whoami "$resolved_gitea")"
|
|
local gitea_note=""
|
|
if [[ "$resolved_gitea" == "$resolved_pat" ]]; then
|
|
gitea_note="(== FORGEJO_PAT)"
|
|
else
|
|
gitea_note="(operator-overridden)"
|
|
fi
|
|
|
|
echo "=================================================================="
|
|
echo " Fork-mode env locked for the auto-agents pipeline"
|
|
echo " ----------------------------------------------------------------"
|
|
echo " Repo target"
|
|
echo " FORGEJO_OWNER = $FORGEJO_OWNER"
|
|
echo " FORGEJO_REPO = $FORGEJO_REPO"
|
|
echo " FORGEJO_URL = $FORGEJO_URL"
|
|
echo " FORGEJO_API_BASE = $FORGEJO_API_BASE"
|
|
echo " FORGEJO_DEFAULT_BRANCH = $FORGEJO_DEFAULT_BRANCH"
|
|
echo " FORGEJO_ORG = $FORGEJO_ORG (org-label fallback only)"
|
|
echo " Fork parent = $parent_full"
|
|
echo
|
|
echo " Identities (production-fidelity: HAL9000 + HAL9001 only)"
|
|
echo " FORGEJO_PAT -> ${FORGEJO_USERNAME} (driver + LLM workers, ${#FORGEJO_PAT} chars)"
|
|
echo " FORGEJO_PASSWORD = set (${#FORGEJO_PASSWORD} chars)"
|
|
echo " FORGEJO_REVIEWER_PAT -> ${FORGEJO_REVIEWER_USERNAME} (umbrella approver, ${#FORGEJO_REVIEWER_PAT} chars)"
|
|
echo " FORGEJO_REVIEWER_PASSWORD = set (${#FORGEJO_REVIEWER_PASSWORD} chars)"
|
|
echo " GITEA_TOKEN -> ${gitea_login:-<unknown>} $gitea_note"
|
|
echo
|
|
echo " Git author for HAL9000 commits"
|
|
echo " GIT_USER_NAME = $GIT_USER_NAME"
|
|
echo " GIT_USER_EMAIL = $GIT_USER_EMAIL"
|
|
if [[ -n "${CA_MAX_PARALLEL_WORKERS:-}" ]]; then
|
|
echo
|
|
echo " Parallelism"
|
|
echo " CA_MAX_PARALLEL_WORKERS = $CA_MAX_PARALLEL_WORKERS"
|
|
fi
|
|
echo "=================================================================="
|
|
return 0
|
|
}
|
|
|
|
__lf_main "$@"
|
|
__lf_rc=$?
|
|
|
|
if [[ $__lf_sourced -eq 0 && $__lf_rc -eq 0 ]]; then
|
|
echo
|
|
echo "WARNING: this script was EXECUTED, not sourced. The exports above"
|
|
echo "will not persist into your shell. Re-run as:"
|
|
echo
|
|
echo " source tools/launch_fork.sh"
|
|
echo
|
|
fi
|
|
|
|
# Bash treats `return` outside a function as an error when EXECUTED, so
|
|
# we have to branch on sourced-vs-executed here. When sourced we MUST
|
|
# `return` (an `exit` would terminate the parent shell). When executed
|
|
# we MUST `exit`.
|
|
if [[ $__lf_sourced -eq 1 ]]; then
|
|
return $__lf_rc
|
|
else
|
|
exit $__lf_rc
|
|
fi
|