工程(本地CI化改造批·验证模型换代): 合回后跑测试制落地——①机件四件: tools/ci_runner.sh(merge=合回验证点对master尖无条件全量/commit=仅CI红态追绿;锁mkdir+在跑尖端判定+rerun标记=跑完补跑coalesce连发触发只测最新尖端;起手--editor --quit刷类名缓存接管原合并后零复跑动作;读闸回执写ci-status.json+ci-history.jsonl红闸与关键日志行在案)+hooks/post-merge(主工作区merge落地即触发,worktree内合并不触发)+hooks/post-commit(红态追绿触发)+pre-commit换代(撤代码面拦截=主区直改全面放开,合并收尾提交MERGE_HEAD豁免,保留秒级文档闸);②guard瘦身: 合回闸回执六况校验与主区代码面Edit/commit拦截退役,留--no-verify拦截+iter数卡校验+规则闸;③run_all_gates.sh头注换代(强制验证点=合回后CI全量,手动档降开发期)+.gates/.gitignore收CI产物;④规约四份换代: AGENTS(运行验证首条/规范导航行/车道铁律/交付验收红线/改动四类)、process-conventions(§1四条重写=主区直改车道+合回后CI+worktree降为大批约定+执法三层,§3第8/9条=CI验证载体+重跑语义「同一份字节禁止第二次人手跑」)、test-conventions §4(开发期窄测+合回CI+追绿)、iter技能五处(闸域/T0/闸序列/总收口/坑速查);⑤待办条目登记。设计讨论会话内闭合零待裁点,用户已发令;收口=迭代44合回CI首跑
This commit is contained in:
1 parent
7a2d19fd12
commit
c91bbdddee
10 files changed
+159
-49
No files matched your search
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地 CI 跑批件——合回 master 后的全量验证执行器(git 钩子触发、后台运行,2026-10-30 用户裁)。
|
||||
# 触发语义(钩子只负责拉起,判定在本件):
|
||||
# merge 模式=合回验证点:主工作区 git merge 落地后(tools/hooks/post-merge)对 master 尖无条件
|
||||
# 跑一次全量域闸;任务 worktree 内的合并不触发(钩子里限主工作区)。
|
||||
# commit 模式=红态追绿:仅当最近一次 CI 判红时(tools/hooks/post-commit)本提交触发复验;
|
||||
# 绿态与无状态零触发——小批直提不重复跑全量,有意义的重跑只剩「验证修复」一类。
|
||||
# 并跑语义=跑完补跑(coalesce,2026-10-30 用户裁):跑批期间新触发不中断当前轮——锁被占时,
|
||||
# 在跑实例的尖端就是当前尖端则本触发无事可做,否则落 ci.rerun 标记;当前轮跑完见标记或尖端
|
||||
# 前移,对最新尖端自动补跑一轮。绝不逐笔触发各跑一轮,最终只测最新尖端。
|
||||
# 产物(均不入库,本目录 .gitignore 已收):ci-status.json=最新一跑判定(commit/verdict/红闸/
|
||||
# 关键日志行/耗时),ci-history.jsonl=逐次流水账,ci.log=当轮全量闸完整输出,
|
||||
# ci.lock=锁目录(内含在跑尖端),ci.rerun=补跑标记。
|
||||
# 用法: tools/ci_runner.sh merge|commit
|
||||
set -u
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
GATES_DIR="$REPO/game/tests/.gates"
|
||||
LOCK="$GATES_DIR/ci.lock"
|
||||
RERUN="$GATES_DIR/ci.rerun"
|
||||
STATUS="$GATES_DIR/ci-status.json"
|
||||
HISTORY="$GATES_DIR/ci-history.jsonl"
|
||||
LOG="$GATES_DIR/ci.log"
|
||||
GODOT="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}"
|
||||
export PYTHONIOENCODING=utf-8
|
||||
|
||||
MODE="${1:-commit}"
|
||||
|
||||
# ── 触发判定 ──
|
||||
if [ "$MODE" = "commit" ]; then
|
||||
[ -f "$STATUS" ] || exit 0
|
||||
grep -q '"verdict": "red"' "$STATUS" || exit 0
|
||||
fi
|
||||
|
||||
# ── 锁(mkdir 原子);被占时按在跑尖端判定:同尖=已在验这份字节,异尖=落补跑标记 ──
|
||||
TIP="$(git -C "$REPO" rev-parse HEAD)"
|
||||
if ! mkdir "$LOCK" 2>/dev/null; then
|
||||
if [ "$(cat "$LOCK/tip" 2>/dev/null)" = "$TIP" ]; then
|
||||
exit 0
|
||||
fi
|
||||
touch "$RERUN"
|
||||
exit 0
|
||||
fi
|
||||
trap 'rm -rf "$LOCK"' EXIT
|
||||
|
||||
# ── 跑批主循环(跑完补跑)──
|
||||
while :; do
|
||||
rm -f "$RERUN"
|
||||
TIP="$(git -C "$REPO" rev-parse HEAD)"
|
||||
echo "$TIP" > "$LOCK/tip"
|
||||
_start="$(date +%s)"
|
||||
|
||||
# 起手刷类名缓存:.godot 缓存不入 git,与合并后代码失配会假红假绿(在册坑)——
|
||||
# 原「合并后零复跑」的秒级动作由本件接管,全量闸跑的就是刷新后的字节
|
||||
"$GODOT" --headless --path "$REPO/game" --editor --quit >>"$LOG" 2>&1
|
||||
|
||||
bash "$REPO/tools/run_all_gates.sh" >"$LOG" 2>&1
|
||||
RC=$?
|
||||
_end="$(date +%s)"
|
||||
|
||||
# 判定与记录:读闸脚本写的回执(all_green/各闸退出码),红项关键行取自本轮 ci.log
|
||||
python - "$REPO" "$TIP" "$RC" "$((_end - _start))" <<'PY'
|
||||
import json, re, sys, datetime
|
||||
repo, tip, rc, dur = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4])
|
||||
gates_dir = repo + "/game/tests/.gates"
|
||||
entry = {"commit": tip,
|
||||
"timestamp": datetime.datetime.now().astimezone().isoformat(timespec="seconds"),
|
||||
"exit_code": rc, "verdict": "green" if rc == 0 else "red",
|
||||
"duration_s": dur, "red_gates": [], "key_lines": []}
|
||||
try:
|
||||
with open(gates_dir + "/receipt.json", encoding="utf-8") as f:
|
||||
r = json.load(f)
|
||||
entry["red_gates"] = [g["name"] for g in r.get("gates", []) if str(g.get("exit")) != "0"]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
with open(gates_dir + "/ci.log", encoding="utf-8", errors="replace") as f:
|
||||
lines = [l.rstrip() for l in f]
|
||||
entry["key_lines"] = [l for l in lines if re.search(r"FAIL|红项|Error|error", l)][-12:]
|
||||
except Exception:
|
||||
pass
|
||||
with open(gates_dir + "/ci-status.json", "w", encoding="utf-8") as f:
|
||||
json.dump(entry, f, ensure_ascii=False, indent=1)
|
||||
with open(gates_dir + "/ci-history.jsonl", "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
||||
PY
|
||||
|
||||
# 轮尾补跑判定:跑批期间有新触发(标记在)或尖端前移 → 对最新尖端再来一轮
|
||||
_newtip="$(git -C "$REPO" rev-parse HEAD)"
|
||||
if [ ! -f "$RERUN" ] && [ "$_newtip" = "$TIP" ]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
Reference in new issue
Block a user