用户裁定(2026-10-17)五条:①代码批只跑代码闸、文档批只跑文档闸;②迭代批是代码实现批,
禁写 design/,文档只读、是实现批写测试与代码的输入依据;③迭代实现报告不写在 design/ 内;
④不设文档编写 skill——文档由用户在会话中讨论后落地;⑤本批属工具/规则面,不占迭代号。
一、tools/run_all_gates.sh(域划分唯一出处)
- 新增 `--code`(01 冒烟/02 单元/03 探针 --click/04 探针 --full/05 渲染闸/09 静态扫描/11 回放对拍)
与 `--docs`(06 gdd_lint/07 doc_link/08 doc_term/08b active.md 体量);10 熵增闸两域都含
(文档批新增或挪动 .md 同样会绊它,且纯文本秒级);不带参数=全量,改闸件本身或跨两域时用。
- 域外闸不入 NAMES,不产生假变化;同时修「基线绿 → 现 SKIP」被误判为闸况变化(--quick 回执
此前永远自带三条假红)。
- 回执增 domain 与 not_run 字段。
- 导出 PYTHONIOENCODING=utf-8:GBK 控制台下 06/10 两闸打印 ✅/✗ 直接崩(假红)、09 的 FAIL 行
写成乱码字节致指纹漂移(假「红项清单变化」)、末尾汇总自身崩打不出结论——2026-10-16 实踩。
- 新增代码域素材齐盘预检:game/assets/art/** 与一切 *.import 边车都不入 git(2026-09-29 裁定),
worktree 冷检出必空盘 → main_theme.tres 解析失败 → 冒烟 V1 七项假红。缺位即拒跑并列缺哪件,
附处置命令(cp 主区素材 + --editor --quit 刷导入;--import 不生成新素材边车)。
二、tools/render_gate.sh(空跑判绿改判红)
- 棋盘/菜单两段 golden 在素材缺位时原为 SKIP,而闸末照样打印「全绿」exit 0——一个像素都没比
却报通过。2026-10-16 菜单废基线事故正是靠这个信号盲区混过 worktree 自检的。现改判红。
三、reports/ 新顶层目录(实现批唯一可写目录)
- 收迭代实现报告/代拍板追认单/GDD 反哺点清单;repo_structure_audit.py TOP_DIRS 与 CLAUDE.md
Project Layout 两处白名单同批改齐(熵增闸自陈的强制口径);reports/README.md 立范围与生命周期。
四、规则文本五处同步
- CLAUDE.md:验收口径改按域;硬性红线新增「两类批」条(迭代批禁写 design/ 与根规则文件、
产出只落 reports/、只跑代码域闸;非迭代批不占迭代号);Project Layout 登记 reports/。
- process-conventions:§3.10 批末按域验收;§3.12 补 active.md 例外(会话状态唯一源,钩子本就
豁免);§3.13 重写为「实现批禁写文档、产出物落 reports/、反哺点由文档批裁决后写入」;
§4 分级周期口径改三档;§2 三段流程「三·实现」行加禁写面。
- doc-conventions 载体分工表:迭代实现报告落位由 design/qa/ 改 reports/。
- architecture.md「测试与质量域」:新增「改动域划分」节(域表+唯一出处声明+素材预检缘由),
本表是现状登记非第二份清单。
- 06-待办清单:登记本批两处代拍板(active.md 例外读法、reports/ 新顶层目录)与旧实现报告
iter27/iter28 两份仍在 design/qa/ 未迁位的欠账。
五、连带(不在本仓库,gitignored 本地 harness 配置)
- .zcode/hooks/worktree-guard.sh:合回校验由「不碰 game/ 即全免」改为按 diff 路径推定所需域、
要求回执 domain 覆盖之;未登记路径按最严处理,禁默认放行。旧豁免的漏洞:改闸件本身反而零验证。
- ~/.zcode/skills/iter/SKILL.md:顶部加批性质与闸域两条强制声明;批末重闸移出 doc 三闸;
总收口产出物改落 reports/、roadmap 换代移出本批;定义面入库注明是搬运非撰写。
验证:--docs 域全绿(06 由基线红改善为绿,因 fcb8c3f8 已补 01 篇流程图);素材预检反例实测
(移走 button_stone.png → 拒跑 exit 1,还原后正常);渲染闸缺盘反例实测(exit 1 并报 FAIL);
--code --docs 互斥与非法参数 exit 2;guard 内嵌 python 编译通过并按 docs 域回执放行 docs 批。
106 lines
5.2 KiB
Python
106 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""仓库结构审计闸(熵增闸,2026-09-27 用户立项)。
|
||
|
||
收口第四闸(与 doc_link_audit / doc_term_audit / gdd_lint 同列):
|
||
把目录三层裁定变成机器检查——任何会话乱放文件,收口时此处直接 FAIL。
|
||
|
||
四查:
|
||
1. 顶层目录白名单(新增顶层目录 = FAIL,须同批改 CLAUDE.md + 本脚本白名单)
|
||
2. 根目录文件白名单(根上只许运行文件与规则文件)
|
||
3. 图像白名单(图只许在 artwork/ game/assets/ design/art/references/ design/dev/architecture/)
|
||
4. 退役路径守卫(production/ docs/ tests/ qa-data/ candidates/mockups 旧路径再现 = FAIL)
|
||
|
||
用法: python tools/repo_structure_audit.py # 全查,FAIL 时 exit 1
|
||
python tools/repo_structure_audit.py --json # 机器读输出
|
||
依据: CLAUDE.md「Project Layout」节(2026-09-27 目录三层收敛终态)。
|
||
"""
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
|
||
# 1) 顶层目录白名单(点开头目录一律忽略)
|
||
# reports/ 收编(2026-10-17 用户裁「迭代批禁写 design/」):迭代实现报告/代拍板追认单/GDD 反哺点
|
||
# 清单的家——实现批只准往这里写,design/ 全目录对它只读。只放 .md,不放图(图像白名单不含此目录)。
|
||
TOP_DIRS = {"archive", "artwork", "design", "game", "sim-data", "tools", "reports"}
|
||
# 2) 根目录文件白名单
|
||
ROOT_FILES = {"CLAUDE.md", "active.md", "roadmap.md", "stage.txt", "review-mode.txt",
|
||
".gitignore", ".zcodeignore"}
|
||
# 3) 图像允许目录(相对路径前缀;archive/ 为归档 zip 不扫)
|
||
# design/qa/ 收编(迭代 5 返工批 2026-09-29——U2 目检预览图/QA 呈现产物归位,commit 留痕)
|
||
# game/tests/golden/ 收编(迭代 19 卡① T2/T6 2026-10-08——棋盘 golden 基线=机检测试资产非美术素材,
|
||
# 素材齐盘态生成入库【基线换代权限 Q6】;T1 落库时漏改本白名单致熵增闸红,同批修订 commit 留痕)
|
||
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", ".pxo", ".psd", ".bmp"}
|
||
IMAGE_PREFIXES = ("artwork/", "game/assets/", "design/art/references/", "design/dev/architecture/",
|
||
"design/qa/", "game/tests/golden/")
|
||
# 4) 退役路径(目录或文件任一再现即 FAIL)
|
||
RETIRED = ["production", "docs", "tests", "qa-data",
|
||
"design/art/candidates", "production/mockups",
|
||
"design/ux", "game/tools"]
|
||
# design/ 一级子目录白名单(防 ad-hoc 新目录)
|
||
DESIGN_SUBDIRS = {"art", "dev", "gdd", "qa", "registry", "research"}
|
||
|
||
|
||
def scan():
|
||
fails, notes = [], []
|
||
|
||
# 1) 顶层目录
|
||
top_dirs = {p.name for p in ROOT.iterdir() if p.is_dir() and not p.name.startswith(".")}
|
||
extra = top_dirs - TOP_DIRS
|
||
if extra:
|
||
fails.append("顶层目录越界: %s(新增顶层目录须同批改 CLAUDE.md Project Layout + 本脚本 TOP_DIRS)" % sorted(extra))
|
||
|
||
# 2) 根目录文件(点开头忽略:.gitignore/.zcodeignore 白名单收编,.git 指针等本就不可见)
|
||
top_files = {p.name for p in ROOT.iterdir() if p.is_file() and not p.name.startswith(".")}
|
||
extra_f = top_files - {f for f in ROOT_FILES if not f.startswith(".")}
|
||
if extra_f:
|
||
fails.append("根目录文件越界: %s(运行文件与规则文件之外不放根上;新类别须同批改两处白名单)" % sorted(extra_f))
|
||
|
||
# 3) 图像白名单
|
||
stray = []
|
||
for ext in IMAGE_EXTS:
|
||
for p in ROOT.rglob("*" + ext):
|
||
rel = p.relative_to(ROOT).as_posix()
|
||
if rel.startswith((".git", ".zcode", ".qoder", "archive/")):
|
||
continue
|
||
if not rel.startswith(IMAGE_PREFIXES):
|
||
stray.append(rel)
|
||
if stray:
|
||
fails.append("图像越界 %d 个(只许 %s): %s" %
|
||
(len(stray), "、".join(IMAGE_PREFIXES), stray[:8]))
|
||
|
||
# 4) 退役路径
|
||
for rel in RETIRED:
|
||
if (ROOT / rel).exists():
|
||
fails.append("退役路径再现: %s/(该目录已消亡,内容按 CLAUDE.md 终态归属;此路径出现即结构回潮)" % rel)
|
||
|
||
# 5) design/ 一级子目录
|
||
design_subs = {p.name for p in (ROOT / "design").iterdir() if p.is_dir()} if (ROOT / "design").is_dir() else set()
|
||
extra_d = design_subs - DESIGN_SUBDIRS
|
||
if extra_d:
|
||
fails.append("design/ 子目录越界: %s(合法: %s;新分类先过用户拍板再改两处白名单)" % (sorted(extra_d), sorted(DESIGN_SUBDIRS)))
|
||
|
||
notes.append("顶层目录 %d 合法 · 根文件 %d 合法 · design/ 子目录 %s · 图像白名单命中 %s" %
|
||
(len(top_dirs & TOP_DIRS), len(top_files & ROOT_FILES),
|
||
sorted(design_subs), "、".join(IMAGE_PREFIXES)))
|
||
return fails, notes
|
||
|
||
|
||
def main():
|
||
fails, notes = scan()
|
||
for n in notes:
|
||
print("✓ " + n)
|
||
if fails:
|
||
print("\n【结构违例】×%d" % len(fails))
|
||
for f in fails:
|
||
print(" ! " + f)
|
||
print("\n处理: 文件归位到合法区;确需新类别 = 同批修订 CLAUDE.md Project Layout + 本脚本白名单,commit 留痕。")
|
||
sys.exit(1)
|
||
print("\n结构审计 PASS(四查全过:顶层目录/根文件/图像白名单/退役路径)")
|
||
sys.exit(0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|