Files
gplan-2/tools/ci_check.sh
T
DKChan 3c1e3ddf07 工程+文档(CI 合回零等待): 轮询等待件与本地回执判定退役,改按需现查 run 终局态
- 缘由(实证): ci_watch.sh 以「commit status 离开 pending」作终局判据——run 37 开跑 36 秒即写
  verdict=red,该 run 三分半后 conclusion=success(现查 API),同 commit 先红后绿;远端时代 16 条
  红回执所引 run 现查 15 条真红 1 条假红。另两条稳定假红源: 超时档(零触发批远端不建任务——现查
  90abdd4a 的 statuses=null,只要挂了轮询必等满 3000s 写红;runner 排队同形)与多路轮询重复覆写
  (fb27dc2f 三条红挤在 15 秒内)。修判据不如撤等待: 判据移到开工前一次现查,竞态与超时两类假红
  结构性消失。
- tools/ci_check.sh(新): 单次读 run 自身 status/conclusion,未 completed 与零触发一律呈报未落定
  不写红;红态列失败 job 与失败步;只呈报不落盘。退出码 0绿/1红/2未落定/3参数与令牌。
  孤立验证四形态: fb27dc2f→红(run 51 conclusion=failure) / 90abdd4a→未落定(零触发) /
  f1840fbc→未落定(run 55 queued) / 39acdcaa→绿(run 48 success)。
- tools/hooks/post-merge: 只推送不再挂后台轮询;push_failed 判定不再落 ci-status.json,改大声呈报。
- 删 tools/ci_watch.sh 与 .zcode/hooks/ci-status-banner.sh(不入 git 面,主区直改)并从 .zcode/config.json
  注销——该横幅在当前 harness 只挂 worktree-guard 的配置下本就不投递(本会话 ci-status 为 red 全程零横幅)。
- 口径同步: AGENTS.md 运行与验证首条、process-conventions §1 第3条(回执等待纪律→合回零等待纪律)与
  第4条执法分层②、§8 第8条、test-conventions §3 与 §8.7 审计证据行(中/低档证据由落盘回执改为
  可复核的现查读数=commit+run号+conclusion)、run_all_gates.sh 头注与 .gates gitignore 模板句、
  迭代技能两镜像三处(入 git 面外另记 .zcode/.qoder)。
- 待办清单条目「ci_watch 中间态误写终局红回执」整条删除(以退役方式了结,非按原判据修复)。
- 在案真红一条随本批呈报未处理: run 51(迭代 51 合回 fb27dc2f) 失败步=导入缓存刷新+跑闸,
  归因待取 job 日志——红未修不开新批的门按用户发令特批越过。
2026-11-04 06:14:44 +08:00

101 lines
4.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# CI 现查件——按需读远端 run 的终局态,只呈报、不落盘(2026-11-04 用户裁:回执等待件与本地缓存判定退役)。
# 用法: tools/ci_check.sh [<提交hash>](缺省=当前 HEAD)
# 判据:Gitea commit status 只用来取本次任务的 run 链接,绿红一律认 run 自身的 status/conclusion——
# run 未 completed(排队/在跑/尝试级中间态)与零触发批(纯闸件/管线批不建任务)一律呈报「未落定」,不写红。
# 调用面:开工前读态(红未修不开新批)、合回后想知道结果时手动查——合回流程不等待、不唤醒,呈报即回合止。
# 授权:GITEA_TOKEN 环境变量优先,否则读 git 凭据库(host=git.kuaimeo.com)。
# 退出码: 0=绿 1=红 2=未落定/零触发/查不到 3=参数或令牌错误
set -u
COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \
|| { echo "不在 git 仓库内"; exit 3; }
REPO="$(dirname "$COMMON")"
BASE="${1:-}"
[ -n "$BASE" ] || BASE="$(git -C "$REPO" rev-parse HEAD)"
BASE="$(git -C "$REPO" rev-parse --verify "${BASE}^{commit}")" || { echo "提交不存在: $BASE"; exit 3; }
_origin="$(git -C "$REPO" remote get-url origin 2>/dev/null || true)"
REPO_SLUG="$(printf '%s' "$_origin" | grep -oE '[^/:]+/[^/]+\.git$' | sed 's/\.git$//')"
[ -n "$REPO_SLUG" ] || { echo "无法从 origin 解析 owner/repo: $_origin"; exit 3; }
TOKEN="${GITEA_TOKEN:-}"
if [ -z "$TOKEN" ]; then
TOKEN="$(printf 'protocol=https\nhost=git.kuaimeo.com\n\n' | git credential fill 2>/dev/null | sed -n 's/^password=//p' | head -1)"
fi
[ -n "$TOKEN" ] || { echo "取不到 Gitea 令牌(设 GITEA_TOKEN 或先 git credential approve 存储)"; exit 3; }
API="https://git.kuaimeo.com/api/v1/repos/$REPO_SLUG"
WEB="https://git.kuaimeo.com/$REPO_SLUG"
export PYTHONIOENCODING=utf-8
python -I - "$API" "$WEB" "$BASE" "$TOKEN" <<'PYEOF'
import json, re, sys, urllib.request, urllib.error
api, web, base, token = sys.argv[1:5]
H = {"Authorization": "token " + token}
def get(url, t=20):
req = urllib.request.Request(url, headers=H)
return json.load(urllib.request.urlopen(req, timeout=t))
def short(sha):
return sha[:8]
def undetermined(msg):
print("CI现查: 未落定 | commit %s | %s" % (short(base), msg))
sys.exit(2)
try:
s = get(api + "/commits/%s/status" % base)
except urllib.error.HTTPError as e:
undetermined("查 commit status 失败 HTTP %s" % e.code)
except Exception as e:
undetermined("查 commit status 失败:%s" % e)
statuses = s.get("statuses") or []
target = next((x.get("target_url", "") for x in statuses if x.get("target_url")), "")
if not statuses:
undetermined("远端无该提交的任务——零触发批(纯闸件/管线批不建任务),或推送未触发 CI")
m = re.search(r"/actions/runs/(\d+)", target or "")
if not m:
undetermined("commit status 无 run 链接(state=%s),查 %s/actions" % (s.get("state"), web))
run_id = m.group(1)
try:
run = get(api + "/actions/runs/%s" % run_id)
except Exception as e:
undetermined("查 run %s 失败:%s" % (run_id, e))
rstatus, rconclusion = run.get("status"), run.get("conclusion")
if rstatus != "completed":
undetermined("run %s status=%s(runner 需开机在线;队列与日志见 %s/actions)" % (run_id, rstatus, web))
if rconclusion == "success":
print("CI现查: 绿 | commit %s | run %s conclusion=success" % (short(base), run_id))
sys.exit(0)
red_jobs = []
try:
jobs = get(api + "/actions/runs/%s/jobs" % run_id)
for j in jobs.get("jobs", []):
if j.get("conclusion") in ("success", "skipped", None):
continue
bad_steps = [x.get("name") for x in (j.get("steps") or []) if x.get("conclusion") not in ("success", "skipped")]
red_jobs.append((j.get("name"), bad_steps))
except Exception as e:
red_jobs.append(("<job明细获取失败:%s>" % e, []))
print("CI现查: 红 | commit %s | run %s conclusion=%s" % (short(base), run_id, rconclusion))
for name, steps in red_jobs:
print(" 红 job:%s" % name)
for st in steps:
print(" 失败步:%s" % st)
print(" 日志:%s" % (target or "%s/actions/runs/%s" % (web, run_id)))
sys.exit(1)
PYEOF