#!/usr/bin/env bash # 回执等待件——合回后等 CI 回执的后台轮询器(2026-11-02 用户裁;规则面=process-conventions §1 第 3 条「回执等待纪律」)。 # 用法: tools/ci_watch.sh <合并提交hash> [超时秒,默认 900] # 调用面: 合回落地先向用户呈报本批总结,随即以 run_in_background 后台任务运行本件;命令结束时 # harness 唤醒会话——绿→删区收口;红→呈报红闸名单走修复批;超时→呈报「回执未落」(CI 可能崩溃)。 # 「本次回执已落」认领判据(三条全满足,防认错轮): # ① 回执可整读——CI 写回执非原子,半截/读空静默重试; # ② 回执 commit 为传入提交或其后代——另一会话合回推进尖端触发补跑覆盖时按后代认领(coalesce 语义见 ci_runner.sh); # ③ 回执 timestamp 晚于合并提交时刻——排除合并前盘上的旧回执(旧尖端是合并提交的祖先,只查②会误领旧回执)。 # 退出码: 0=绿 1=红 2=超时/回执未落 3=参数错误 set -u # 主工作区定位:CI 只在主工作区跑(post-merge 钩子限主区),从任务 worktree 内调用也要读主区回执—— # 经 git 公共目录(.git)的上级定位主仓根,主区调用与 worktree 调用同一读数 COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \ || { echo "不在 git 仓库内"; exit 3; } REPO="$(dirname "$COMMON")" STATUS="$REPO/game/tests/.gates/ci-status.json" BASE="${1:-}" TIMEOUT="${2:-900}" INTERVAL=30 [ -n "$BASE" ] || { echo "用法: ci_watch.sh <合并提交hash> [超时秒]"; exit 3; } BASE="$(git -C "$REPO" rev-parse --verify "${BASE}^{commit}")" || { echo "提交不存在: $BASE"; exit 3; } BASE_CT="$(git -C "$REPO" log -1 --format=%ct "$BASE")" _deadline=$(( $(date +%s) + TIMEOUT )) while :; do _read="$(python -I -c " import json, sys, datetime try: d = json.load(open(sys.argv[1], encoding='utf-8')) ts = int(datetime.datetime.fromisoformat(d['timestamp']).timestamp()) print(d.get('commit', ''), d.get('verdict', ''), ts) except Exception: pass " "$STATUS")" _com=""; _verdict=""; _ts="" if [ -n "$_read" ]; then set -- $_read [ $# -eq 3 ] && { _com="$1"; _verdict="$2"; _ts="$3"; } fi if [ -n "$_com" ] && [ -n "$_verdict" ] && [ -n "$_ts" ] \ && [ "$_ts" -gt "$BASE_CT" ] \ && git -C "$REPO" merge-base --is-ancestor "$BASE" "$_com" 2>/dev/null; then python -I -c " import json, sys d = json.load(open(sys.argv[1], encoding='utf-8')) print('CI回执:', d.get('verdict'), '| commit', d.get('commit', '')[:8], '| 耗时', d.get('duration_s'), 's') if d.get('note'): print('note:', d['note']) for g in d.get('red_gates', []): print('红闸:', g) for l in d.get('key_lines', []): print('key:', l) " "$STATUS" [ "$_verdict" = "green" ] && exit 0 exit 1 fi _now=$(date +%s); _left=$((_deadline - _now)) [ "$_left" -le 0 ] && { echo "超时(${TIMEOUT}s)回执未落——CI 可能崩溃或未触发,查 game/tests/.gates/ 的 ci.lock 与 ci.log"; exit 2; } sleep $(( _left < INTERVAL ? _left : INTERVAL )) done