Two defects an independent survey of the tooling surface found in the new helpers, fixed pre-gate: - lane-brief.sh: label filter used jq contains() (substring) — `-l security` wrongly matched label `domain/6-security`. Now exact-token match against tea's space-separated labels string. Verified live: `-l security` -> 0, `-l domain/6-security` -> the real holders. - ci-wait.sh: unknown owner silently defaulted to the `usc` Woodpecker instance (wrong credentials, wrong pipelines). Now fails hard requiring `-a <instance>`, matching lane-brief's FATAL-on-unresolved behavior. Verified: usc owner still infers and exits 0; unknown owner exits 2 with guidance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kt2D8TsnDwhtzEAPijsNmR
102 lines
4.9 KiB
Bash
Executable File
102 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# lane-brief.sh — live dispatch brief for a repo "lane" (milestone/label), straight
|
|
# from current Gitea state. Defeats stale worker self-report: workers brief from
|
|
# static notes and routinely report issues "todo" that are already CLOSED, forcing
|
|
# the orchestrator to re-verify each one before dispatch. This returns the CURRENT
|
|
# open set, classified for dispatch, in one call.
|
|
#
|
|
# Usage:
|
|
# lane-brief.sh -r <owner/repo> [-m <milestone>] [-l <label>] [-L <login>] [-n <limit>]
|
|
# lane-brief.sh -r usc/uconnect -m "M2M Part Search (0.0.45)"
|
|
# lane-brief.sh -r usc/uconnect -l domain/6-security
|
|
#
|
|
# Reliable signals (closed issues are excluded by definition — that's the point):
|
|
# - open-vs-closed : authoritative; this is the stale-intake failure mode.
|
|
# - PR-linkage : an open PR referencing the issue = work underway.
|
|
# Assignees/dependencies are intentionally NOT trusted as "available" signals —
|
|
# fleets that track work-state out-of-band (tmux board, issue text) leave them
|
|
# empty in Gitea. Output therefore partitions by PR presence and the OPEN-NO-PR set
|
|
# is "dispatch candidates to cross-check against the live fleet", not a blind list.
|
|
#
|
|
# Login resolution order: -L flag > $GITEA_LOGIN > owner inference (usc->usc,
|
|
# mosaicstack/mosaic->mosaicstack) > detect-platform.sh default-login fallback.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=/dev/null
|
|
source "$SCRIPT_DIR/detect-platform.sh"
|
|
|
|
REPO="" MILESTONE="" LABEL="" LOGIN="" LIMIT=100
|
|
while getopts "r:m:l:L:n:h" opt; do
|
|
case "$opt" in
|
|
r) REPO="$OPTARG" ;;
|
|
m) MILESTONE="$OPTARG" ;;
|
|
l) LABEL="$OPTARG" ;;
|
|
L) LOGIN="$OPTARG" ;;
|
|
n) LIMIT="$OPTARG" ;;
|
|
h) grep '^#' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
*) echo "see -h" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
[[ -n "$REPO" ]] || { echo "FATAL: -r <owner/repo> required" >&2; exit 2; }
|
|
|
|
# Resolve login: explicit -L, then $GITEA_LOGIN, then owner inference, then the
|
|
# shared default-login resolver. Owner inference comes before the shared fallback
|
|
# because the latter is not owner-aware (picks the default tea login), which is
|
|
# wrong for cross-instance lanes.
|
|
if [[ -z "$LOGIN" ]]; then
|
|
if [[ -n "${GITEA_LOGIN:-}" ]]; then
|
|
LOGIN="$GITEA_LOGIN"
|
|
else
|
|
case "${REPO%%/*}" in
|
|
usc|USC) LOGIN=usc ;;
|
|
mosaicstack|mosaic) LOGIN=mosaicstack ;;
|
|
*) LOGIN="$(get_gitea_login_for_repo_override 2>/dev/null || true)" ;;
|
|
esac
|
|
fi
|
|
fi
|
|
[[ -n "$LOGIN" ]] || { echo "FATAL: could not resolve a Gitea login for $REPO (pass -L or set GITEA_LOGIN)" >&2; exit 2; }
|
|
|
|
command -v tea >/dev/null || { echo "FATAL: tea not found" >&2; exit 1; }
|
|
command -v jq >/dev/null || { echo "FATAL: jq not found" >&2; exit 1; }
|
|
|
|
ISSUES_JSON="$(tea issues list --repo "$REPO" --login "$LOGIN" --state open --limit "$LIMIT" \
|
|
--fields index,title,assignees,milestone,labels --output json 2>/dev/null)" || {
|
|
echo "FATAL: tea issues list failed for $REPO (login=$LOGIN)" >&2; exit 1; }
|
|
|
|
# Open PRs, to cross-ref which issues already have work in flight.
|
|
PRS_TSV="$(tea pulls list --repo "$REPO" --login "$LOGIN" --state open \
|
|
--fields index,title,head --output tsv 2>/dev/null || true)"
|
|
PR_ISSUE_REFS="$(printf '%s\n' "$PRS_TSV" | grep -oE '#[0-9]+|[/-][0-9]{3,}' | grep -oE '[0-9]+' | sort -u || true)"
|
|
|
|
ts="$(date -u '+%Y-%m-%d %H:%MZ' 2>/dev/null || echo '?')"
|
|
filt="$REPO"; [[ -n "$MILESTONE" ]] && filt="$filt · milestone:'$MILESTONE'"; [[ -n "$LABEL" ]] && filt="$filt · label:'$LABEL'"
|
|
echo "LANE BRIEF — $filt · $ts (login=$LOGIN)"
|
|
echo "(open issues only; closed are excluded by definition — that's the point)"
|
|
echo
|
|
|
|
# Label match is exact-token against tea's space-separated labels string (so -l
|
|
# "security" does NOT match label "domain/6-security"). Caveat: label names that
|
|
# themselves contain spaces aren't distinguishable in tea's string form.
|
|
printf '%s' "$ISSUES_JSON" | jq -r --arg ms "$MILESTONE" --arg lb "$LABEL" --arg prs "$PR_ISSUE_REFS" '
|
|
($prs | split("\n") | map(select(length>0))) as $prrefs
|
|
| map(
|
|
select( ($ms=="" or .milestone==$ms)
|
|
and ($lb=="" or ((.labels//"") | split(" ") | index($lb) != null)) )
|
|
| . + { assigned: ((.assignees//"")|length>0),
|
|
haspr: (.index as $ix | ($prrefs | index($ix)) != null) }
|
|
)
|
|
| (map(select(.haspr|not))) as $candidates
|
|
| (map(select(.haspr))) as $inflight
|
|
| "DISPATCH CANDIDATES (open · no open PR) — \($candidates|length) [cross-check vs live fleet]:",
|
|
( $candidates[] | " #\(.index) \(.title[0:90])\(if .assigned then " (gitea-assignee set)" else "" end)" ),
|
|
"",
|
|
"WORK UNDERWAY (open · PR in flight) — \($inflight|length):",
|
|
( $inflight[] | " #\(.index) \(.title[0:80]) [PR open]" )
|
|
'
|
|
echo
|
|
echo "Closed issues are excluded — do NOT take a worker's self-reported 'todo' on faith."
|
|
echo "Candidates = open + no PR; confirm against the live fleet before dispatch"
|
|
echo "(fleets that don't self-assign in Gitea leave 'unassigned' meaningless)."
|