chore(zcode): 移除 .zcode 工具配置并更新忽略规则
删除 .zcode 下的 hooks 与 plans 配置,将 .zcode/ 和 .zcodeignore 加入 .gitignore;同时重写试玩问题记录文档, 用新条目替换垂直切片 v1 的旧内容。
This commit is contained in:
1 parent
d3356985a8
commit
aad2999aba
7 files changed
+12
-341
No files matched your search
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 会话开始:快照工作区状态(status 哈希 + diff 内容哈希)与 active.md 内容哈希,供 Stop 收口检查对比
|
||||
# 2026-09-18 强判据升级:active.md 判据由 mtime 改内容哈希——touch/意外改时间不再蒙混,内容真变才算登记
|
||||
proj="${ZCODE_PROJECT_DIR:-$PWD}"
|
||||
sid="${CLAUDE_SESSION_ID:-default}"
|
||||
state="/tmp/zcode-active-md-guard"
|
||||
mkdir -p "$state"
|
||||
{
|
||||
date +%s
|
||||
git -C "$proj" status --porcelain 2>/dev/null | sort | sha1sum | cut -d' ' -f1
|
||||
git -C "$proj" diff HEAD 2>/dev/null | sha1sum | cut -d' ' -f1
|
||||
sha1sum "$proj/active.md" 2>/dev/null | cut -d' ' -f1 || echo 0
|
||||
} > "$state/$sid.snap"
|
||||
rm -f "$state/$sid.reminded"
|
||||
exit 0
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 会话结束收口检查:工作区相对会话开始有改动(status 行或 diff 内容任一变化)、
|
||||
# 且 active.md 内容哈希未变(本会话内未真更新——mtime 不可信,touch 也算未登记)时,
|
||||
# 以 exit 2 阻止停止并经 stderr 注入登记提醒(每会话至多一次)
|
||||
# 2026-09-18 强判据升级:active.md 由 mtime 比对改内容哈希比对
|
||||
proj="${ZCODE_PROJECT_DIR:-$PWD}"
|
||||
sid="${CLAUDE_SESSION_ID:-default}"
|
||||
state="/tmp/zcode-active-md-guard"
|
||||
snap="$state/$sid.snap"
|
||||
active_md="$proj/active.md"
|
||||
[ -f "$snap" ] || exit 0
|
||||
[ -f "$state/$sid.reminded" ] && exit 0
|
||||
start_status=$(sed -n 2p "$snap")
|
||||
start_diff=$(sed -n 3p "$snap")
|
||||
start_content=$(sed -n 4p "$snap")
|
||||
now_status=$(git -C "$proj" status --porcelain 2>/dev/null | sort | sha1sum | cut -d' ' -f1)
|
||||
now_diff=$(git -C "$proj" diff HEAD 2>/dev/null | sha1sum | cut -d' ' -f1)
|
||||
[ "$now_status" = "$start_status" ] && [ "$now_diff" = "$start_diff" ] && exit 0
|
||||
now_content=$(sha1sum "$active_md" 2>/dev/null | cut -d' ' -f1 || echo 0)
|
||||
[ "$now_content" = "$start_content" ] || exit 0
|
||||
touch "$state/$sid.reminded"
|
||||
cat >&2 <<'MSG'
|
||||
[会话收口检查] 本会话产生了工作区改动,但 根目录 active.md 内容未变(本会话内未真更新)。若本会话包含实质工作(代码/文档/数值/设计拍板/缺陷修复),请:① 先更新根目录 active.md 顶部「当前状态快照」节(在飞/可做/阻塞/锚点/下一步——先改快照再记账);② 再在根目录 active.md 末尾追加一条编号批次条目(做了什么·拍板了什么·验证结果·遗留与下一步)。完成后结束;若本次确无可登记事项(纯问答、只读核查),忽略本提醒直接结束。另:待办事项登记 design/gdd/06-待办清单.md(裁决后改状态列留痕),不要写进 active.md 条目表。
|
||||
MSG
|
||||
exit 2
|
||||
@@ -1,209 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# worktree 铁律守卫(PreToolUse,2026-09-19 台账 87 用户立规;2026-10-15 扩合回闸)
|
||||
# 拦两路:①Edit/Write 类落在主工作区(白名单外)且主工作区检出 master/main
|
||||
# ②Bash 在主工作区 git commit
|
||||
# ③Bash 在主工作区 git merge/rebase/cherry-pick 进 master 且无 tools/run_all_gates.sh 全量回执
|
||||
# 退出码 2 = 拦截(stderr 回传模型);0 = 放行;任何解析异常一律放行(守卫是兜底,不能挡死会话收口)
|
||||
proj="${ZCODE_PROJECT_DIR:-$PWD}"
|
||||
tmp="$(mktemp)"
|
||||
cat > "$tmp"
|
||||
projw="$(cygpath -w "$proj" 2>/dev/null || echo "$proj")"
|
||||
python - "$projw" "$(cygpath -w "$tmp" 2>/dev/null || echo "$tmp")" <<'PY'
|
||||
import json, os, re, subprocess, sys
|
||||
|
||||
try:
|
||||
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def winpath(p):
|
||||
# MSYS 风格路径(/d/...)原生 Windows Python 不识别,统一转 Windows 形式
|
||||
if isinstance(p, str) and re.match(r"^/[A-Za-z](/|$)", p):
|
||||
try:
|
||||
r = subprocess.run(["cygpath", "-w", p], capture_output=True, text=True, timeout=5)
|
||||
return r.stdout.strip() or p
|
||||
except Exception:
|
||||
return p
|
||||
return p
|
||||
|
||||
proj = os.path.abspath(winpath(sys.argv[1]))
|
||||
try:
|
||||
with open(sys.argv[2], encoding='utf-8') as f:
|
||||
d = json.load(f)
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
tool = d.get("tool_name") or d.get("tool") or ""
|
||||
ti = d.get("tool_input") or {}
|
||||
cwd = os.path.abspath(winpath(d.get("cwd") or proj))
|
||||
|
||||
def in_dir(p, root):
|
||||
p = os.path.normcase(os.path.abspath(p))
|
||||
root = os.path.normcase(os.path.abspath(root))
|
||||
return p == root or p.startswith(root.rstrip('\\/') + os.sep)
|
||||
|
||||
def trunk_branch(repo):
|
||||
try:
|
||||
r = subprocess.run(["git", "-C", repo, "branch", "--show-current"],
|
||||
capture_output=True, text=True, timeout=5)
|
||||
return r.stdout.strip()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
TRUNK = {"master", "main"}
|
||||
WHITELIST = [os.path.abspath(winpath(os.path.join(proj, "active.md"))),
|
||||
os.path.abspath(winpath(os.path.join(proj, ".zcode")))]
|
||||
|
||||
if tool in ("Edit", "Write", "MultiEdit", "NotebookEdit"):
|
||||
fp = ti.get("file_path") or ti.get("notebook_path") or ""
|
||||
if not fp:
|
||||
sys.exit(0)
|
||||
fp = os.path.abspath(winpath(fp))
|
||||
if not in_dir(fp, proj) or any(in_dir(fp, w) for w in WHITELIST):
|
||||
sys.exit(0)
|
||||
b = trunk_branch(proj)
|
||||
if b in TRUNK:
|
||||
sys.stderr.write(
|
||||
"[worktree 铁律] 主工作区(%s 分支)禁止直接改动:%s\n"
|
||||
"先建独立 worktree 再改:git worktree add ../g-plan2-<任务名> -b <任务分支>;"
|
||||
"收尾在主工作区 git merge --no-ff 合回。豁免面仅根目录 active.md 与 .zcode/。\n" % (b, fp))
|
||||
sys.exit(2)
|
||||
|
||||
elif tool == "Bash":
|
||||
cmd = ti.get("command", "") or ""
|
||||
toks = cmd.split()
|
||||
has_commit = False
|
||||
has_merge = False
|
||||
for i, t in enumerate(toks):
|
||||
if t.rstrip(';') != 'git':
|
||||
continue
|
||||
j = i + 1
|
||||
while j < len(toks) and j - i <= 5:
|
||||
u = toks[j].rstrip(';')
|
||||
if u == "commit":
|
||||
has_commit = True
|
||||
break
|
||||
if u in ("merge", "rebase", "cherry-pick"):
|
||||
has_merge = True
|
||||
break
|
||||
if u.startswith("-"):
|
||||
if u in ("-C", "--git-dir", "-c") and j + 1 < len(toks):
|
||||
j += 2
|
||||
continue
|
||||
j += 1
|
||||
continue
|
||||
break
|
||||
# 主工作区判定:cwd 在内,或命令文本里显式指到主工作区路径(不含 worktree 兄弟目录)
|
||||
norm = cmd.replace("\\", "/").lower()
|
||||
ref_main = re.search(r"/d/code/g-plan2(?![\w-])", norm) or re.search(r"d:/code/g-plan2(?![\w-])", norm)
|
||||
# 兄弟 worktree 豁免(2026-10-03 用户授权修复):harness 上报 cwd 恒为项目根,原判定把
|
||||
# git -C <worktree> commit 一并误拦,与「提交在任务 worktree 分支上完成」的放行语义相抵;
|
||||
# 命令文本以路径形态(斜杠前缀)显式指向兄弟 worktree 且不含主区路径时放行——显式主区
|
||||
# 路径引用优先走下方拦截判定(ref_main 命中即不豁免,防「主区提交夹带 worktree 提及」绕行)。
|
||||
ref_wt = re.search(r"/g-plan2-[\w-]+", norm)
|
||||
sibling = bool(ref_wt) and not ref_main
|
||||
on_trunk = trunk_branch(proj) in TRUNK
|
||||
in_main = (in_dir(cwd, proj) or bool(ref_main)) and on_trunk
|
||||
if not in_main or sibling:
|
||||
sys.exit(0)
|
||||
if has_commit:
|
||||
sys.stderr.write(
|
||||
"[worktree 铁律] 主工作区(master)禁止直接 git commit——提交在任务 worktree 分支上完成,"
|
||||
"收尾在主工作区 git merge --no-ff 合回 master。\n")
|
||||
sys.exit(2)
|
||||
# 合回闸(2026-10-15 用户令「迭代完成前必须全量跑一遍所有测试」):merge/rebase/cherry-pick
|
||||
# 进 master 前校验 tools/run_all_gates.sh 的回执——回执缺失、quick 模式、或含红项即拦。
|
||||
# 起因:iter27 台账只记 probe --click 未记 --full,t17_defer_probe 红了整个迭代无人发现,
|
||||
# 根因是闸清单散落三份文档彼此不一致且无机器校验(清单唯一事实源已收敛为 run_all_gates.sh)。
|
||||
if has_merge:
|
||||
# 回执在「跑闸的那个 worktree」里(闸在 worktree 跑、回执随 worktree 落地),
|
||||
# 主工作区自己那份是上一次合回前的旧件。故按 merge 的目标分支反查 worktree 路径,
|
||||
# 找不到就在所有 sibling worktree 里找「回执 commit == 分支 tip」的那一份。
|
||||
def git_out(*args, **kw):
|
||||
return subprocess.run(["git", "-C", proj] + list(args),
|
||||
capture_output=True, text=True, timeout=kw.get("timeout", 8)).stdout.strip()
|
||||
|
||||
merge_branch = None
|
||||
for t in toks:
|
||||
u = t.rstrip(';')
|
||||
if u and not u.startswith("-") and u not in ("git", "merge", "rebase", "cherry-pick"):
|
||||
merge_branch = u
|
||||
break
|
||||
tip = git_out("rev-parse", merge_branch) if merge_branch else ""
|
||||
if not tip:
|
||||
tip = git_out("rev-parse", "HEAD")
|
||||
|
||||
# 纯文档合入豁免(2026-10-16 用户令「改了代码的合入才做测试验证,文档的合入不验」):
|
||||
# merge-base..分支尖 的改动不触碰 game/(游戏工程本体)=纯文档/开发工具面合入,
|
||||
# 跑游戏测试无意义,直接放行;触碰 game/ 的代码合入仍走下方回执校验。
|
||||
if merge_branch:
|
||||
_base = git_out("merge-base", "HEAD", merge_branch)
|
||||
_touched_game = git_out("diff", "--name-only", _base, merge_branch, "--", "game/") if _base else "x"
|
||||
if not _touched_game:
|
||||
sys.exit(0)
|
||||
|
||||
cands = []
|
||||
if merge_branch:
|
||||
try:
|
||||
wl = git_out("worktree", "list", "--porcelain")
|
||||
blocks = [b for b in wl.split("\n\n") if b.strip()]
|
||||
for b in blocks:
|
||||
if ("branch refs/heads/%s" % merge_branch) in b:
|
||||
head_line = [l for l in b.split("\n") if l.startswith("worktree ")]
|
||||
if head_line:
|
||||
cands.append(os.path.abspath(winpath(head_line[0][9:])))
|
||||
except Exception:
|
||||
pass
|
||||
# 兜底:主区 + 所有 sibling worktree
|
||||
cands.append(proj)
|
||||
parent = os.path.dirname(proj)
|
||||
try:
|
||||
for name in os.listdir(parent):
|
||||
p = os.path.join(parent, name)
|
||||
if os.path.isdir(os.path.join(p, ".git")) or os.path.isfile(os.path.join(p, ".git")):
|
||||
cands.append(os.path.abspath(p))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
receipt = None
|
||||
for base in cands:
|
||||
p = os.path.join(base, "game", "tests", ".gates", "receipt.json")
|
||||
if not os.path.exists(p):
|
||||
continue
|
||||
try:
|
||||
import json
|
||||
with open(p, encoding="utf-8") as f:
|
||||
r = json.load(f)
|
||||
except Exception:
|
||||
continue
|
||||
# 只认「回执 commit == 将要合入的分支 tip」的那一份
|
||||
if r.get("commit") == tip:
|
||||
receipt = (base, r)
|
||||
break
|
||||
why = None
|
||||
if receipt is None:
|
||||
found = any(os.path.exists(os.path.join(b, "game", "tests", ".gates", "receipt.json"))
|
||||
for b in cands)
|
||||
why = ("无与分支 %s(tip %s)匹配的回执%s"
|
||||
% (merge_branch or "?", tip[:8],
|
||||
"(worktree 内存在回执但 commit 对不上=闸跑完后代码又变了)" if found else ""))
|
||||
else:
|
||||
base, r = receipt
|
||||
if r.get("quick_mode"):
|
||||
why = "回执是 --quick 模式(跳过渲染闸/静态扫描),收口出库必须跑全量"
|
||||
elif not r.get("all_green"):
|
||||
reds = [g["name"] for g in r.get("gates", []) if g.get("exit") != 0]
|
||||
why = "回执含新增红项:" + ("、".join(reds) if reds else "见 receipt.json")
|
||||
if why:
|
||||
sys.stderr.write(
|
||||
"[合回闸] 拦截 %s 进 master——%s。\n"
|
||||
"在任务 worktree 内跑全量闸:tools/run_all_gates.sh(不带 --quick),"
|
||||
"跑完回执自动写入该 worktree 的 game/tests/.gates/receipt.json,再合。\n"
|
||||
% (cmd.split()[0], why))
|
||||
sys.exit(2)
|
||||
|
||||
sys.exit(0)
|
||||
PY
|
||||
rc=$?
|
||||
rm -f "$tmp"
|
||||
exit $rc
|
||||
Reference in new issue
Block a user