工程(本地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
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# 红态追绿钩子(本地 CI 触发钩子,2026-10-30 用户裁)——仅当最近一次 CI 判红时,本提交触发
|
||||
# 后台复验(tools/ci_runner.sh commit,判定在跑批件内);绿态与无状态零触发——
|
||||
# 小批直提不重复跑全量,同一份字节禁止第二次人手跑。主工作区限定。
|
||||
set -u
|
||||
_GD="$(cd "$(git rev-parse --git-dir)" && pwd)"
|
||||
_CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)"
|
||||
[ "$_GD" = "$_CD" ] || exit 0
|
||||
REPO="$(git rev-parse --show-toplevel)"
|
||||
( nohup bash "$REPO/tools/ci_runner.sh" commit >/dev/null 2>&1 & )
|
||||
exit 0
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# 合回验证点(本地 CI 触发钩子,2026-10-30 用户裁)——主工作区 git merge 落地后无条件触发
|
||||
# 全量域闸后台跑批(tools/ci_runner.sh merge,约 4 分钟,不阻塞合并动作本身)。
|
||||
# 任务 worktree 内的合并不触发(CI 只认 master;worktree 批要提前验证就手动跑 ci_runner.sh)。
|
||||
# CI 红了不回滚合并——红项记入 game/tests/.gates/ci-status.json,修复提交经 post-commit 追绿。
|
||||
set -u
|
||||
_GD="$(cd "$(git rev-parse --git-dir)" && pwd)"
|
||||
_CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)"
|
||||
[ "$_GD" = "$_CD" ] || exit 0
|
||||
REPO="$(git rev-parse --show-toplevel)"
|
||||
( nohup bash "$REPO/tools/ci_runner.sh" merge >/dev/null 2>&1 & )
|
||||
exit 0
|
||||
+8
-24
@@ -1,31 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# 直改车道闸(git pre-commit,2026-10-26 车道铁律)——staged 不含代码面的提交,提交点现场跑
|
||||
# 文档域闸(tools/run_all_gates.sh --docs,秒级文本扫描):绿=落地,红=中止。
|
||||
# 代码面=game/ 下非 .md 的行为载体(判据与改动域划分同一)。staged 含代码面时:
|
||||
# 主工作区=拦(代码面唯一合法路径=worktree 批合回);任务 worktree=跳过(归批末闸+合回闸管,
|
||||
# 不打扰迭代批中间提交)。--no-verify 绕过仅属用户(agent 侧由 worktree-guard 拦截)。
|
||||
# 直改车道闸(git pre-commit,2026-10-30 本地 CI 化换代)——提交点现场跑文档域闸
|
||||
# (tools/run_all_gates.sh --docs,秒级文本扫描):绿=落地,红=中止。
|
||||
# 代码面提交不再拦(2026-10-30 用户裁:主区直改全面放开)——代码验证归合回后的本地 CI
|
||||
# (tools/hooks/post-merge 全量跑批;CI 红态下 tools/hooks/post-commit 追绿复验)。
|
||||
# 合并收尾提交跳过(MERGE_HEAD 在=冲突合并的收尾 commit)——内容归合回后的 CI 全量验。
|
||||
# --no-verify 绕过仅属用户(agent 侧由 worktree-guard 拦截)。
|
||||
set -u
|
||||
REPO="$(git rev-parse --show-toplevel)"
|
||||
|
||||
# staged 路径集(首提交无 HEAD 时对 --root 比);空集放行,交回 git 自身报「无改动可提」
|
||||
if git rev-parse --verify -q HEAD >/dev/null 2>&1; then
|
||||
STAGED="$(git diff --cached --name-only)"
|
||||
else
|
||||
STAGED="$(git diff --cached --name-only --root)"
|
||||
fi
|
||||
[ -z "$STAGED" ] && exit 0
|
||||
|
||||
# 代码面判定:staged 里任一路径落在 game/ 且非 .md
|
||||
if printf '%s\n' "$STAGED" | grep '^game/' | grep -viE '\.md$' | grep -q .; then
|
||||
# 主区判定:git-dir 与 common-dir 同一=主工作区(非链接 worktree)
|
||||
_GD="$(cd "$(git rev-parse --git-dir)" && pwd)"
|
||||
_CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)"
|
||||
if [ "$_GD" = "$_CD" ]; then
|
||||
echo "[直改闸] 主工作区禁止直提代码面(game/ 下非 .md)——代码面改动走迭代批独立 worktree(git worktree add ../g-plan2-<任务名> -b <任务分支>),收尾在主工作区 git merge --no-ff 合回。" >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
# 合并收尾提交:跳过文档闸,交合回后 CI
|
||||
[ -f "$(git rev-parse --git-dir)/MERGE_HEAD" ] && exit 0
|
||||
|
||||
RC=0
|
||||
bash "$REPO/tools/run_all_gates.sh" --docs || RC=$?
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# 验证闸唯一入口(全部验证闸的清单与域划分,批末与合回按改动域跑)——
|
||||
# 闸清单的唯一定义处。文档只写「收口跑本脚本」,禁在文档里另列闸件清单(那正是 iter27
|
||||
# 验证闸唯一入口(全部验证闸的清单与域划分)——
|
||||
# 闸清单的唯一定义处。文档只写「验证跑本脚本」,禁在文档里另列闸件清单(那正是 iter27
|
||||
# 漏跑 probe_gate --full 的成因:清单散落三份文档、彼此不一致、且无机器校验)。
|
||||
#
|
||||
# 跑完写回执 game/tests/.gates/receipt.json(commit+内容树哈希+各闸退出码+存量基线比对+时间戳);
|
||||
# .zcode/hooks/worktree-guard.sh 在主工作区 git merge 时校验本回执,缺或红即拦截。
|
||||
#
|
||||
# 本脚本是合回前的终局动作:跑之前先冻结(代码与断言不再改)并把 master 并入本分支;每批只跑
|
||||
# 一次。回执必须覆盖住分支尖的代码面(game/ 下非 .md 的行为载体)——闸后再改一个代码面文件,
|
||||
# 回执即作废须重跑;跑闸时若代码面还带着未提交改动,回执会标 dirty_game,合回闸拒绝采信。
|
||||
# 本地 CI(tools/ci_runner.sh,2026-10-30 用户裁)读回执写 ci-status.json——强制验证点=合回 master
|
||||
# 后 post-merge 钩子触发的全量跑批;本脚本手动调用(--code/--docs/全量档)只服务开发期按域跑批。
|
||||
# 跑闸时代码面带着未提交改动,回执会标 dirty_game(验的不是 HEAD 字节),CI 判定照常落账留痕。
|
||||
#
|
||||
# 用法:tools/run_all_gates.sh [--code|--docs|--quick]
|
||||
# --code 只跑代码域闸(00 冒烟测试/01 系统测试/02 单元与集成测试/03 场景探针/
|
||||
@@ -98,7 +96,7 @@ fi
|
||||
# 每次跑批前自愈重建,避免「清目录重跑」把 .gitignore 一起带走。
|
||||
mkdir -p "$STAMP_DIR"
|
||||
if [ ! -f "$STAMP_DIR/.gitignore" ]; then
|
||||
printf '# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。\n# receipt.json 是每跑批覆写的当次回执、*.log 是跑批当场日志——两者都是中间产物,不入库。\nreceipt.json\n*.log\n' \
|
||||
printf '# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。\n# receipt.json 是每跑批覆写的当次回执、ci-status.json/ci-history.jsonl 是本地 CI 判定与流水账、\n# ci.lock/ci.rerun 是跑批件锁与补跑标记、*.log 是跑批当场日志——均为中间产物,不入库。\nreceipt.json\nci-status.json\nci-history.jsonl\nci.lock\nci.rerun\n*.log\n' \
|
||||
> "$STAMP_DIR/.gitignore"
|
||||
echo "[gates] 已自愈重建 $STAMP_DIR/.gitignore"
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user