feat: live status-board comment + full-fleet dogfood (#1)
Build & push image / build-and-push (push) Successful in 6s
Build & push image / build-and-push (push) Successful in 6s
Phase 3: one consolidated, live-updating PR comment aggregating every model's per-lens progress (queued -> running -> finished + verdict), so the swarm's progress is visible at a glance and a watcher can tell when it's done. Opt-in statusWriter in the binary (atomic writes) + a background status-board.sh renderer wired through entrypoint.sh; default on, GADFLY_STATUS_BOARD=0 to disable. Also restores gadfly's dogfood swarm to the full cloud fleet (9 cloud + M5; M1 dropped as too slow) matching mort, and folds in the 3 real bugs the swarm found on its own PR (skip-binary stuck-waiting, panic-stuck lens, busy-loop on bad poll interval). All 36 findings graded via the gadfly MCP (18 real / 18 false-positive). gofmt clean, go vet quiet, go build + go test -race green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Steve Dudenhoeffer <steve@stevedudenhoeffer.com> Co-committed-by: Steve Dudenhoeffer <steve@stevedudenhoeffer.com>
This commit was merged in pull request #1.
This commit is contained in:
+4
-1
@@ -24,7 +24,9 @@
|
||||
# antigravity: `agy` on PATH with credentials already seeded (~/.gemini)
|
||||
#
|
||||
# Optional:
|
||||
# MAX_DIFF_CHARS diff truncation cap for the prompt (default 60000)
|
||||
# MAX_DIFF_CHARS diff truncation cap for the prompt (default 60000)
|
||||
# GADFLY_STATUS_FILE per-model JSON path for the live status board (set by
|
||||
# entrypoint.sh; empty/unset disables status publishing)
|
||||
#
|
||||
# This script is advisory: it never fails the job for review content. It exits
|
||||
# non-zero only on a usage/configuration error.
|
||||
@@ -161,6 +163,7 @@ case "$PROVIDER" in
|
||||
GADFLY_TITLE="$TITLE" \
|
||||
GADFLY_BODY="$BODY" \
|
||||
GADFLY_MAX_DIFF_CHARS="$MAX_DIFF_CHARS" \
|
||||
GADFLY_STATUS_FILE="${GADFLY_STATUS_FILE:-}" \
|
||||
"$BIN" 2>"$ERR_FILE"
|
||||
)"
|
||||
rc=$?
|
||||
|
||||
Executable
+137
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
# Live status board for a Gadfly review.
|
||||
#
|
||||
# Each model process (the cmd/gadfly binary) publishes its per-lens progress to
|
||||
# $GADFLY_STATUS_DIR/<model>.json as lenses go queued -> running -> finished.
|
||||
# This script polls that directory and upserts ONE consolidated PR comment that
|
||||
# aggregates every model's per-lens status, so a human (or an agent watching the
|
||||
# PR) can see the whole swarm's progress at a glance and know when it's done —
|
||||
# instead of staring at N separate "⏳ Reviewing…" placeholders.
|
||||
#
|
||||
# It is advisory and best-effort: a failed render/post is logged and retried on
|
||||
# the next tick; nothing here can fail the review or block a merge. It runs in
|
||||
# the background from entrypoint.sh and exits once the $GADFLY_STATUS_DIR/.done
|
||||
# sentinel appears (the entrypoint touches it after all model lanes finish),
|
||||
# after one final render.
|
||||
#
|
||||
# Required env:
|
||||
# GITEA_API https://HOST/api/v1/repos/OWNER/REPO
|
||||
# GITEA_TOKEN token with repo write access (posts the comment)
|
||||
# PR pull request number
|
||||
# GADFLY_STATUS_DIR directory holding the per-model <model>.json files
|
||||
# Optional:
|
||||
# GADFLY_STATUS_POLL_SECS render/upsert interval (default 12)
|
||||
set -uo pipefail
|
||||
|
||||
: "${GITEA_API:?GITEA_API required}"
|
||||
: "${GITEA_TOKEN:?GITEA_TOKEN required}"
|
||||
: "${PR:?PR required}"
|
||||
: "${GADFLY_STATUS_DIR:?GADFLY_STATUS_DIR required}"
|
||||
|
||||
POLL="${GADFLY_STATUS_POLL_SECS:-12}"
|
||||
# Guard against a non-numeric poll interval: with `set -uo pipefail` (no set -e)
|
||||
# a bad `sleep "$POLL"` would fail silently and the `while :` loop would spin,
|
||||
# hammering the Gitea API. Coerce anything non-integer (or <1) back to 12.
|
||||
case "$POLL" in ''|*[!0-9]*) POLL=12 ;; esac
|
||||
[ "$POLL" -ge 1 ] 2>/dev/null || POLL=12
|
||||
DONE_FILE="${GADFLY_STATUS_DIR}/.done"
|
||||
MARKER="<!-- gadfly-status-board -->"
|
||||
API_TIMEOUT="--connect-timeout 20 --max-time 30"
|
||||
BOARD_ID="" # cached comment id, so we PATCH in place instead of re-searching
|
||||
|
||||
say() { echo "[gadfly-status-board] $*" >&2; }
|
||||
|
||||
command -v jq >/dev/null 2>&1 || { say "jq not found; status board disabled"; exit 0; }
|
||||
|
||||
# render_section FILE -> markdown for one model (its header + per-lens bullets).
|
||||
# Reads the JSON the binary writes; tolerates a half-written/missing file by
|
||||
# emitting nothing (jq exits non-zero -> caller skips it this tick).
|
||||
render_section() {
|
||||
jq -r '
|
||||
def icon(state; errored):
|
||||
if state == "finished" then (if errored then "⚠️" else "✅" end)
|
||||
elif state == "running" then "🔄"
|
||||
else "⏸️" end;
|
||||
def lensline:
|
||||
"- " + icon(.state; (.errored // false)) + " **" + .name + "** — " +
|
||||
( if .state == "finished" then (if (.errored // false) then "could not complete" else (.verdict // "done") end)
|
||||
elif .state == "running" then "running"
|
||||
else "queued" end );
|
||||
( [.lenses[] | select(.state == "finished")] | length ) as $fin
|
||||
| ( .lenses | length ) as $tot
|
||||
| ( if .done then "✅ done"
|
||||
elif $tot == 0 then "⏳ waiting to start"
|
||||
else "⏳ " + ($fin|tostring) + "/" + ($tot|tostring) + " lenses" end ) as $sum
|
||||
| "#### `" + .model + "` · " + .provider + " — " + $sum + "\n"
|
||||
+ ( if $tot == 0 then "- ⏸️ _no lenses reported yet_"
|
||||
else ([.lenses[] | lensline] | join("\n")) end )
|
||||
' "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# render_body -> the full consolidated comment body (marker + header + sections).
|
||||
render_body() {
|
||||
local f sections="" total=0 done=0 ts
|
||||
shopt -s nullglob
|
||||
local files=("${GADFLY_STATUS_DIR}"/*.json)
|
||||
shopt -u nullglob
|
||||
for f in "${files[@]}"; do
|
||||
local sec
|
||||
sec="$(render_section "$f")" || continue
|
||||
[ -z "$sec" ] && continue
|
||||
total=$((total + 1))
|
||||
if [ "$(jq -r 'if .done then 1 else 0 end' "$f" 2>/dev/null)" = "1" ]; then
|
||||
done=$((done + 1))
|
||||
fi
|
||||
sections="${sections}${sec}"$'\n\n'
|
||||
done
|
||||
ts="$(date -u '+%Y-%m-%d %H:%M:%SZ')"
|
||||
if [ "$total" -eq 0 ]; then
|
||||
sections="_Waiting for reviewers to start…_"$'\n'
|
||||
fi
|
||||
printf '%s\n## 🪰 Gadfly — live review status\n\n%d/%d reviewers finished · updated %s\n\n%s\n<sub>Live status board. Findings are posted in each model'\''s own comment. Advisory only — does not block merge.</sub>' \
|
||||
"$MARKER" "$done" "$total" "$ts" "$sections"
|
||||
}
|
||||
|
||||
# find_existing -> id of the board comment if it already exists (paginate by
|
||||
# marker). Used once, to recover the comment across container restarts.
|
||||
find_existing() {
|
||||
local page=1 cmts id
|
||||
while [ "$page" -le 10 ]; do
|
||||
cmts="$(curl $API_TIMEOUT -fsS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITEA_API}/issues/${PR}/comments?limit=50&page=${page}" 2>/dev/null || echo '[]')"
|
||||
[ "$(echo "$cmts" | jq 'length' 2>/dev/null || echo 0)" = "0" ] && break
|
||||
id="$(echo "$cmts" | jq -r --arg m "$MARKER" \
|
||||
'.[] | select(.body != null and (.body | startswith($m))) | .id' 2>/dev/null | head -n1)"
|
||||
[ -n "$id" ] && { echo "$id"; return; }
|
||||
page=$((page + 1))
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
# upsert BODY — PATCH the cached/known board comment, else POST a new one and
|
||||
# cache its id. A failed PATCH (e.g. comment deleted) clears the cache so the
|
||||
# next tick re-discovers or re-creates it.
|
||||
upsert() {
|
||||
local body="$1" post_body resp
|
||||
post_body="$(jq -n --arg b "$body" '{body:$b}')"
|
||||
[ -z "$BOARD_ID" ] && BOARD_ID="$(find_existing)"
|
||||
if [ -n "$BOARD_ID" ]; then
|
||||
if ! curl $API_TIMEOUT -fsS -X PATCH -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" \
|
||||
"${GITEA_API}/issues/comments/${BOARD_ID}" -d "$post_body" >/dev/null 2>&1; then
|
||||
say "patch of comment ${BOARD_ID} failed; will re-discover"
|
||||
BOARD_ID=""
|
||||
fi
|
||||
else
|
||||
resp="$(curl $API_TIMEOUT -fsS -X POST -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" \
|
||||
"${GITEA_API}/issues/${PR}/comments" -d "$post_body" 2>/dev/null || echo '{}')"
|
||||
BOARD_ID="$(echo "$resp" | jq -r '.id // ""' 2>/dev/null)"
|
||||
fi
|
||||
}
|
||||
|
||||
say "starting (poll ${POLL}s, dir ${GADFLY_STATUS_DIR})"
|
||||
while :; do
|
||||
upsert "$(render_body)"
|
||||
[ -f "$DONE_FILE" ] && break
|
||||
sleep "$POLL"
|
||||
done
|
||||
say "done"
|
||||
Reference in New Issue
Block a user