Files
gplan-2/tools/repo_structure_audit.py
T
DKChan 2b5f1d1a54 docs(art目录边界): art/ 判据定为载体性质——审计报告与调研归 research/,提示词库改名 art-prompts,规约补正文单点维护
art/ 三份审计产物处置:cursor 会话存档与 canvas 转 HTML 两份迁 design/research/(各自头部写明接收方),
2026-10-06 美术资产链路审计报告 HTML 从未入版本库、移入 gitignored archive/ 后从 art/ 清除;
06 与 08 两处裁决录链接、两张需求卡片的四路指针随迁移改指 research/。
comfyui-prompts.md 改名 art-prompts.md(名字指本地 ComfyUI 与实际渠道不符),五处现行指针与链接标签跟随,
版本历史留痕行按文档纪律不改写。
doc-conventions 第 5 节加「正文单点维护」一行(一条规则句只写在 owner 文档,执行侧必须带字面值时豁免但须注明冲突以 owner 为准),
第 11 节「六目录」改「各子目录」;据此把生图操作单里复述封印黑与旗朱色值及沿革的那条注记改成指向 character-design §5.4 的指针。
repo_structure_audit.py 加第六查 design/art/ 内容白名单(只许 .md 与 references/),文首查数与通过行随改;
AGENTS.md Project Layout 的 art/ 与 research/ 两行按新判据改写。
06 新增待裁决一条:candidates 目录在熵增闸退役名单与 asset-path-map 现行目录表里得到相反答案,两份操作单又指它。
变异验证:art/ 放 .html 与放子目录两况均 FAIL,清场后 PASS。
2026-10-20 05:15:02 +08:00

126 lines
6.2 KiB
Python
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 python3
# -*- coding: utf-8 -*-
"""仓库结构审计闸(熵增闸,2026-09-27 用户立项)。
收口第四闸(与 doc_link_audit / doc_term_audit / gdd_lint 同列):
把目录三层裁定变成机器检查——任何会话乱放文件,收口时此处直接 FAIL。
六查:
1. 顶层目录白名单(新增顶层目录 = FAIL,须同批改 AGENTS.md + 本脚本白名单)
2. 根目录文件白名单(根上只许运行文件与规则文件)
3. 图像白名单(图只许在 artwork/ game/assets/ design/art/references/ design/dev/architecture/ game/tests/golden/)
4. 退役路径守卫(production/ docs/ tests/ qa-data/ design/registry/ design/qa/ 旧路径再现 = FAIL)
5. design/ 一级子目录白名单(防临时新目录)
6. design/art/ 内容白名单(只许 .md 与 references/;审计报告属中间文档归 design/research/)
用法: python tools/repo_structure_audit.py # 全查,FAIL 时 exit 1
python tools/repo_structure_audit.py --json # 机器读输出
依据: AGENTS.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 = {"AGENTS.md", "README.md", "roadmap.md", "stage.txt", "review-mode.txt",
".gitignore", ".zcodeignore"}
# 3) 图像允许目录(相对路径前缀;archive/ 为归档 zip 不扫)
# 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/",
"game/tests/golden/")
# 4) 退役路径(目录或文件任一再现即 FAIL)
RETIRED = ["production", "docs", "tests", "qa-data", "design/registry",
"design/art/candidates", "production/mockups",
"design/ux", "game/tools", "design/qa"]
# 5) design/ 一级子目录白名单(防 ad-hoc 新目录)
DESIGN_SUBDIRS = {"art", "dev", "gdd", "research"}
# 6) design/art/ 内容白名单——美术只放 .md 文档与 references/ 参考图;
# 审计报告属中间文档,按 doc-conventions §11 落 design/research/,禁驻 art/
ART_EXTS = {".md"}
ART_SUBDIRS = {"references"}
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(新增顶层目录须同批改 AGENTS.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/(该目录已消亡,内容按 AGENTS.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)))
# 6) design/art/ 内容白名单
art_dir = ROOT / "design" / "art"
art_bad = []
if art_dir.is_dir():
for p in sorted(art_dir.iterdir()):
rel = p.relative_to(ROOT).as_posix()
if p.is_dir():
if p.name not in ART_SUBDIRS:
art_bad.append(rel + "/")
elif p.suffix.lower() not in ART_EXTS:
art_bad.append(rel)
if art_bad:
fails.append("design/art/ 越界内容 %d 项(只许 .md 与 %s): %s" %
(len(art_bad), "、".join(ART_SUBDIRS) + "/", art_bad[:8]))
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处理: 文件归位到合法区;确需新类别 = 同批修订 AGENTS.md Project Layout + 本脚本白名单,commit 留痕。")
sys.exit(1)
print("\n结构审计 PASS(六查全过:顶层目录/根文件/图像白名单/退役路径/design 子目录/art 内容)")
sys.exit(0)
if __name__ == "__main__":
main()