diff --git a/design/dev/architecture/architecture.md b/design/dev/architecture/architecture.md index 09761f97..06315bf1 100644 --- a/design/dev/architecture/architecture.md +++ b/design/dev/architecture/architecture.md @@ -195,8 +195,8 @@ | 域 | 闸 | 本批改动落在这些路径即属该域 | |---|---|---| -| **代码域** | run_smoke、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan、回放确定性对拍(条件件) | `game/`、`tools/`、`artwork/` | -| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | `design/`、`roadmap.md`、`CLAUDE.md` | +| **代码域** | run_smoke、run_all、probe_gate `--click`、probe_gate `--full`、render_gate、static_scan、回放确定性对拍(条件件) | **唯一触发=`game/`**(游戏工程代码、测试、探针、场景、数据表)。2026-10-18 用户裁「跨领域执法禁绝」(提交 `7bdf53e3`)后,原 `tools/`、`artwork/` 两前缀不再把改动拉进代码域 | +| **文档域** | gdd_lint、doc_link_audit、doc_term_audit、流水线闸 | 一切非 `game/` 的路径:`design/`、根规则文件(`CLAUDE.md`/`roadmap.md`)、`tools/` 工具脚本、`.zcode/` 配置、`sim-data/`、`reports/` | | **两域都含** | repo_structure_audit(熵增闸:顶层目录/根文件/图像白名单/退役路径/design 子目录五查——文档批新增或挪动 `.md` 同样会绊它;纯文本扫描秒级) | — | 口径三条:**域划分的唯一出处是 `tools/run_all_gates.sh`**(`--code`/`--docs`/不带参数=全量),本表是它的现状登记、不是第二份清单;改闸件本身或一次改动跨两域 → 跑全量档(§6.2 共享件例外);合回闸按 merge diff 路径推出应验的域,再校验回执的 `domain` 字段与新增红。代码域跑前先过**素材齐盘预检**——`game/assets/art/**` 与一切 `*.import` 边车都不入 git(2026-09-29 裁定),worktree 冷检出必空盘,空盘态冒烟 V1 成片假红、渲染闸更是一段 golden 都不比。 diff --git a/game/tests/.gates/baseline.json b/game/tests/.gates/baseline.json index b78ea52f..4c091ca6 100644 --- a/game/tests/.gates/baseline.json +++ b/game/tests/.gates/baseline.json @@ -33,10 +33,6 @@ "exit": "0", "fp": "green" }, - "08b active.md 体量": { - "exit": "0", - "fp": "green" - }, "08c 流水线闸": { "exit": "0", "fp": "green" diff --git a/roadmap.md b/roadmap.md index 65371072..4c88fe57 100644 --- a/roadmap.md +++ b/roadmap.md @@ -85,7 +85,7 @@ 2. **T0 开工**:建 worktree + 基线复读记档(冒烟/探针/三闸/MC 对拍基准)+ `git status` 归属核对(并行遗留只标旗不碰)。 3. **循环骨架**:一轮 = 一个任务卡——读卡 → 对事实源 → 实装 → 轻闸全绿 → 提交(代拍板随 commit 记录)→ 状态记录追加(查撞号)→ 下一轮。单 worktree 多点提交一次合并;轮内禁开第二个 worktree;基线每轮开工时点重测;断点续跑 = `git log` 对任务卡定轮次 + 重跑当轮轻闸。 4. **轻闸**(每轮必跑,headless):`tools/run_all_gates.sh --quick`(迭代中段自检档)。 -5. **重闸**(批末 Tn):`tools/run_all_gates.sh` **按本批改动域跑**(只碰 `game/`、`tools/`、`artwork/` → `--code`;只碰 `design/` 与根规则文件 → `--docs`;跨两域或改闸件本身 → 不带参数全量),所选域全绿——闸件清单、顺序与域划分只在该脚本里定义,本文件与 CLAUDE.md/规约均不另列;MC 逐位对拍(涉核心)+ 素材批加机检断言另行。 +5. **重闸**(批末 Tn):`tools/run_all_gates.sh` **按本批改动域跑**(触及 `game/` → `--code`,这是代码域唯一触发;不触及 `game/` 的一切路径 → `--docs`;一次改动同批混触两域 → 不带参数全量),所选域全绿——闸件清单、顺序与域划分只在该脚本里定义,本文件与 CLAUDE.md/规约均不另列;改闸件自身(`tools/` 下机检脚本与闸壳)不占合回全量,质量义务=落批时的变异验证;涉核心的 MC 与基线逐位比对、素材批的机检断言另行。 6. **人闸**(停机交付):真人目检清单 + 代拍板追认清单 + 异步终审触点(样张类)——本循环内唯一必经人审。 7. **异常分级**:L1 自救(一次重试)→ L2 记录继续(连续 3 次同红灯升级)→ L3 停机写报告等用户。 8. **批次完成后的五步登记**:①06 条目状态按 doc-conventions §12.1 推进(做完整条删除)②本文件顶部「当前位」三行换代,涉及里程碑移动时同步里程碑表 ③**实现批禁写本文件与 `design/`**(2026-10-17 用户裁:代码实现批只准写 `reports/`),该改哪一行写进 `reports/iter-反哺点-<日期>.md`,由文档批执行 ④阶段变更改 stage.txt ⑤批末闸序列**按本批改动域**全绿留痕(回执 commit 对齐分支尖)。 diff --git a/tools/loop_dashboard.py b/tools/loop_dashboard.py deleted file mode 100644 index 20f6e144..00000000 --- a/tools/loop_dashboard.py +++ /dev/null @@ -1,529 +0,0 @@ -#!/usr/bin/env python3 -r"""loop_dashboard.py — 迭代闭环仪表盘生成器 - -用法: - python tools/loop_dashboard.py # 生成 sim-data/loop-dashboard.html + 控制台摘要 - python tools/loop_dashboard.py --open # 生成后用系统默认浏览器打开 - python tools/loop_dashboard.py --out - -定位: 项目开发循环 = 「完善文档 → 更新垂直切片游玩体验 → 再完善文档」。 -闭环状态分散在六处——active.md 快照节 / 台账 / GDD 版本史 / 06-待办清单 / -07-体验验证清单 / git 在飞改动——本工具把六处聚合成一个只读视图,每次迭代 -收口后重跑一次即可看清: 走到哪了、什么在飞、什么待拍板、什么验证项已落后于文档改动。 - -只读工具: 不改任何被扫描的文件,产物只有仪表盘 HTML 本身。 - -解析约定(跟随仓库现行格式,格式变了这里要跟着改): - - active.md 台账区 = 文件最后一个二级标题之后(新条目追加在文件末尾) - - GDD 版本 = 「版本历史」表中版本号最大的一行(行按时间追加) - - 06-待办清单条目 = 四级标题,状态 = 其下「**状态**:」行 - - 07-体验验证清单头部「当前批次来源」行内引用的 vX.Y 与 GDD 现行版本比对, - 得出「体验验证清单落后于文档改动」的失效面 -""" -import argparse -import html -import os -import re -import subprocess -import sys -from collections import Counter, OrderedDict -from datetime import datetime -from pathlib import Path - -ROOT = Path(__file__).resolve().parent.parent -GDD_DIR = ROOT / "design" / "gdd" -ACTIVE_MD = ROOT / "active.md" -ROADMAP_MD = ROOT / "roadmap.md" -TODO_MD = GDD_DIR / "06-待办清单.md" -VERIFY_MD = GDD_DIR / "07-体验验证清单.md" -STAGE_TXT = ROOT / "stage.txt" -DEFAULT_OUT = ROOT / "sim-data" / "loop-dashboard.html" - -LOOP_ORDER = ["顶层文档", "排雷循环", "战斗循环", "冒险循环", "表现与支撑"] - -# 状态分类前缀 → 桶。解决即移除(2026-09-18 新口径)下「已解决/已裁决」残留 = 清理债。 -RESOLVED_PREFIX = ("已解决", "已裁决") -PARTIAL_PREFIX = ("部分收口", "部分清偿", "已部分收口", "已部分清偿") -OPEN_PREFIX = ("待办",) -DECIDED_PREFIX = ("方向已拍板", "已拍板") - - -# ---------------------------------------------------------------- git 侧 - -def git(args): - try: - return subprocess.run( - ["git", "-c", "core.quotepath=false"] + args, - capture_output=True, text=True, encoding="utf-8", errors="replace", - cwd=ROOT, check=True, - ).stdout - except (subprocess.CalledProcessError, FileNotFoundError): - return "" - - -def classify_path(rel: str): - rel = rel.replace("\\", "/") - if rel.startswith("design/gdd/"): - return "GDD 文档" - if rel == "active.md" or rel == "roadmap.md": - return "会话与路线" - if rel.startswith("design/qa/") or rel.startswith("design/research/"): - return "验证与调研" - if rel.startswith("design/"): - return "设计侧其他(registry/art/dev)" - if rel.startswith("game/"): - if rel.endswith(".gd"): - return "游戏代码" - if rel.endswith(".tres"): - return "数据表" - return "游戏工程其他" - if rel.startswith("tools/"): - return "工具" - if rel.startswith("sim-data/"): - return "数据产物" - return "其他" - - -def scan_git(): - dirty, untracked = [], [] - for line in git(["status", "--porcelain"]).splitlines(): - if not line.strip(): - continue - state, path = line[:2], line[3:].strip().strip('"') - if state.startswith("??"): - untracked.append(path) - else: - dirty.append((state.strip() or "M", path)) - log = [l for l in git(["log", "--oneline", "-5"]).splitlines() if l.strip()] - grouped = Counter(classify_path(p) for _, p in dirty) - gdd_dirty = [p for s, p in dirty if classify_path(p) == "GDD 文档"] - return { - "dirty": dirty, "untracked": untracked, "log": log, - "grouped": grouped, "gdd_dirty": gdd_dirty, - } - - -# ---------------------------------------------------------------- GDD 清单 - -VERSION_ROW_RE = re.compile(r"^\|\s*v?(\d+)\.(\d+)\s*\|\s*(\d{4}-\d{2}-\d{2})") - - -def parse_doc(path: Path): - try: - text = path.read_text(encoding="utf-8") - except UnicodeDecodeError: - text = path.read_text(encoding="utf-8", errors="replace") - title = next((l.lstrip("# ").strip() for l in text.splitlines() if l.startswith("# ")), path.stem) - rel = path.relative_to(ROOT).as_posix() - parts = path.relative_to(GDD_DIR).parts - group = parts[0] if len(parts) > 1 else "顶层文档" - version, vdate = None, None - in_hist = False - for line in text.splitlines(): - if line.startswith("## "): - in_hist = "版本历史" in line - continue - if in_hist: - m = VERSION_ROW_RE.match(line) - if m: - v = (int(m.group(1)), int(m.group(2))) - if version is None or v > version: - version, vdate = v, m.group(3) - mtime = datetime.fromtimestamp(path.stat().st_mtime).strftime("%m-%d %H:%M") - return { - "path": rel, "name": path.stem, "title": title, "group": group, - "version": f"v{version[0]}.{version[1]}" if version else "—", - "vdate": vdate or "—", "mtime": mtime, - } - - -def scan_gdd(git_info): - dirty_set = {p.replace("\\", "/") for p in git_info["gdd_dirty"]} - docs = [parse_doc(p) for p in sorted(GDD_DIR.rglob("*.md"))] - for d in docs: - d["dirty"] = d["path"] in dirty_set - return docs - - -# ---------------------------------------------------------------- active.md - -LEDGER_ENTRY_RE = re.compile(r"^(\d+)\.\s+\*\*") - - -def scan_active(): - info = {"snapshot_lines": [], "updated_line": "", "next_line": "", - "entries": [], "collisions": [], "next_number": None, "tail_head": ""} - if not ACTIVE_MD.exists(): - info["error"] = "active.md 不存在" - return info - lines = ACTIVE_MD.read_text(encoding="utf-8").splitlines() - # 快照节(标题带纪律后缀,按关键词匹配) - in_snap = False - for line in lines: - if line.startswith("## "): - in_snap = "当前状态快照" in line - if in_snap: - continue - if in_snap and line.strip(): - info["snapshot_lines"].append(line.strip()) - for ln in info["snapshot_lines"]: - if ln.startswith("- **更新于**"): - info["updated_line"] = ln - if ln.startswith("- **下一步顺序**"): - info["next_line"] = ln - # 台账区 = 最后一个二级标题之后 - last_h2 = max((i for i, l in enumerate(lines) if l.startswith("## ")), default=-1) - tail = lines[last_h2 + 1:] - info["tail_head"] = lines[last_h2].lstrip("# ").strip() if last_h2 >= 0 else "" - numbers = [] - for line in tail: - m = LEDGER_ENTRY_RE.match(line) - if m: - numbers.append(int(m.group(1))) - info["entries"] = numbers - seen = Counter(numbers) - info["collisions"] = sorted(n for n, c in seen.items() if c > 1) - if numbers: - info["next_number"] = max(numbers) + 1 - return info - - -# ---------------------------------------------------------------- 06 待办清单 - -def scan_todo(): - items = [] - if not TODO_MD.exists(): - return items - loop = system = "" - cur = None - for line in TODO_MD.read_text(encoding="utf-8").splitlines(): - if line.startswith("## "): - loop, system = line.lstrip("# ").strip(), "" - elif line.startswith("### "): - system = line.lstrip("# ").strip() - elif line.startswith("#### "): - cur = { - "loop": loop, "system": system, - "title": line.lstrip("# ").strip(), - "status": "", - } - items.append(cur) - elif cur is not None and line.startswith("**状态**"): - cur["status"] = line.replace("**状态**", "").lstrip(":: ").strip() - cur = None - for it in items: - s = it["status"] - if s.startswith(RESOLVED_PREFIX): - it["bucket"] = "解决态残留" - elif s.startswith(PARTIAL_PREFIX): - it["bucket"] = "部分收口" - elif s.startswith(OPEN_PREFIX): - it["bucket"] = "待办" - elif s.startswith(DECIDED_PREFIX): - it["bucket"] = "拍板待执行" - else: - it["bucket"] = "其他" - return items - - -# ---------------------------------------------------------------- 07 体验验证清单 - -VERSION_REF_RE = re.compile(r"(战斗结算|增益状态|地图战斗资源|敌人系统|行动经济|棋盘生成|排雷玩法|难度曲线与Boss|元进度|关卡节点地图|HUD框架|战斗表现|数据配置|存档序列化|音频|教学引导|设置与本地化|地形变异|冒险结构|魔法道具)\s*(v\d+\.\d+)") - - -def scan_verify(docs): - info = {"items": [], "obligations": [], "source_line": "", "refs": [], "error": None} - if not VERIFY_MD.exists(): - info["error"] = "07-体验验证清单.md 不存在" - return info - text = VERIFY_MD.read_text(encoding="utf-8") - lines = text.splitlines() - section = "" - for line in lines: - if line.startswith("## "): - section = line.lstrip("# ").strip() - continue - if line.startswith("### ") and "观测项" in section: - info["items"].append(line.lstrip("# ").strip()) - elif line.startswith("- ") and "重测批义务" in section: - info["obligations"].append(line[2:].strip()) - m = re.search(r"当前批次来源:(.+)", text) - if m: - info["source_line"] = m.group(1).strip() - by_name = {d["name"]: d for d in docs} - for name, ver in VERSION_REF_RE.findall(info["source_line"]): - cur = by_name.get(name) - info["refs"].append({ - "doc": name, "ref": ver, "current": cur["version"] if cur else "?", - "stale": bool(cur and cur["version"] != "—" and cur["version"] != ver), - }) - return info - - -# ---------------------------------------------------------------- 路线图 - -def scan_roadmap(): - steps = [] - if not ROADMAP_MD.exists(): - return steps - in_route = False - for line in ROADMAP_MD.read_text(encoding="utf-8").splitlines(): - if line.startswith("## "): - in_route = "路线图" in line and "维护" not in line - continue - if in_route and re.match(r"^\d+\.\s", line): - steps.append(line.strip()) - return steps - - -# ---------------------------------------------------------------- 控制台摘要 - -def print_summary(stage, git_info, docs, active, todos, verify, out_path): - def p(s=""): - print(s) - p("═" * 62) - p(f"迭代闭环仪表盘 · {datetime.now().strftime('%Y-%m-%d %H:%M')} · 阶段: {stage}") - p("═" * 62) - if active.get("updated_line"): - p(active["updated_line"].lstrip("- ")) - g = git_info["grouped"] - p(f"在飞改动: {len(git_info['dirty'])} 文件(" + - " / ".join(f"{k} {v}" for k, v in g.most_common()) + - f")+ 未跟踪 {len(git_info['untracked'])}") - dirty_docs = [d for d in docs if d["dirty"]] - versioned = [d for d in docs if d["version"] != "—"] - newest = max(versioned, key=lambda d: d["vdate"], default=None) - p(f"GDD: {len(docs)} 份,其中在飞 {len(dirty_docs)} 份" - + (f";最近改版 {newest['name']} {newest['version']}({newest['vdate']})" if newest else "")) - buckets = Counter(t["bucket"] for t in todos) - p(f"待办清单: {len(todos)} 条(待办 {buckets.get('待办', 0)} / 部分收口 {buckets.get('部分收口', 0)}" - f" / 拍板待执行 {buckets.get('拍板待执行', 0)} / 解决态残留 {buckets.get('解决态残留', 0)}" - f"←按「解决即移除」口径应移除)") - p(f"体验验证: {len(verify['items'])} 项待真人观测") - if active["collisions"]: - p(f"台账: 最新编号 {active['entries'][-1]},下一编号建议 {active['next_number']}" - f"(撞号: {', '.join(str(n) for n in active['collisions'])})") - else: - p(f"台账: 最新编号 {active['entries'][-1] if active['entries'] else '—'},下一编号建议 {active['next_number']}") - stale = [r for r in verify["refs"] if r["stale"]] - if stale: - p("⚠ 07-体验验证清单引用版本已落后:") - for r in stale: - p(f" {r['doc']}: 07 引 {r['ref']} / 现行 {r['current']}") - if active.get("next_line"): - p(active["next_line"].lstrip("- ")) - p(f"已生成: {out_path.relative_to(ROOT).as_posix()}") - p("═" * 62) - - -# ---------------------------------------------------------------- HTML - -CSS = """ -:root{--bg:#14161a;--card:#1d2026;--line:#2c3038;--tx:#d8dce3;--dim:#8b919c; ---acc:#5b9dd9;--warn:#d9a05b;--bad:#d95b6a;--ok:#6fbf7f;} -*{box-sizing:border-box} -body{margin:0;background:var(--bg);color:var(--tx); -font-family:"Segoe UI","Microsoft YaHei","PingFang SC",sans-serif;font-size:14px;line-height:1.6} -main{max-width:1080px;margin:0 auto;padding:24px 20px 60px} -h1{font-size:22px;margin:0 0 4px} -h2{font-size:17px;margin:36px 0 10px;padding-bottom:6px;border-bottom:1px solid var(--line)} -.meta{color:var(--dim);font-size:12.5px;margin-bottom:8px} -.grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start} -@media(max-width:800px){.grid{grid-template-columns:1fr}} -.card{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:14px 16px} -.card h3{margin:0 0 8px;font-size:14px;color:var(--acc)} -.card ul{margin:0;padding-left:18px} -.card li{margin:3px 0} -table{width:100%;border-collapse:collapse;background:var(--card); -border:1px solid var(--line);border-radius:8px;overflow:hidden;font-size:13px} -th,td{padding:6px 10px;text-align:left;border-bottom:1px solid var(--line);vertical-align:top} -th{color:var(--dim);font-weight:600;background:#22252c} -tr:last-child td{border-bottom:none} -.dirty{color:var(--warn);font-weight:600} -.tag{display:inline-block;padding:0 8px;border-radius:10px;font-size:12px;margin-right:6px} -.tag.open{background:#2b3a4a;color:#9cc7ec} -.tag.partial{background:#463d2b;color:#e0c08a} -.tag.decided{background:#3a3350;color:#c3b3ec} -.tag.resolved{background:#3c2b30;color:#e0999f} -.stale{color:var(--bad);font-weight:600} -.ok{color:var(--ok)} -.dim{color:var(--dim)} -footer{margin-top:40px;color:var(--dim);font-size:12px;border-top:1px solid var(--line);padding-top:12px} -code{background:#262a31;padding:1px 5px;border-radius:4px;font-size:12.5px} -""" - - -def esc(s): - return html.escape(str(s)) - - -def bucket_tag(bucket): - cls = {"待办": "open", "部分收口": "partial", "拍板待执行": "decided", "解决态残留": "resolved"}.get(bucket, "partial") - return f'{esc(bucket)}' - - -def render_html(stage, git_info, docs, active, todos, verify, roadmap, out_path): - now = datetime.now().strftime("%Y-%m-%d %H:%M") - buckets = Counter(t["bucket"] for t in todos) - dirty_docs = [d for d in docs if d["dirty"]] - stale_refs = [r for r in verify["refs"] if r["stale"]] - - # 快照节 - snap = "".join(f"
  • {esc(l.lstrip('- '))}
  • " for l in active["snapshot_lines"]) - # 近期提交 - commits = "".join(f"
  • {esc(l.split()[0])} {esc(l[l.index(' ')+1:][:90])}
  • " for l in git_info["log"]) - # 在飞分组 - fly_rows = "".join( - f"{esc(k)}{v}" for k, v in git_info["grouped"].most_common()) - untracked = "".join(f"
  • {esc(u)}
  • " for u in git_info["untracked"]) or "
  • 无
  • " - # GDD 清单 - doc_rows = [] - for group in LOOP_ORDER: - group_docs = [d for d in docs if d["group"] == group] - if not group_docs: - continue - doc_rows.append( - f"{esc(group)}({len(group_docs)})") - for d in sorted(group_docs, key=lambda x: x["mtime"], reverse=True): - mark = ' 在飞' if d["dirty"] else "" - doc_rows.append( - f"{esc(d['name'])}{mark}" - f"{esc(d['version'])}{esc(d['vdate'])}" - f"{esc(d['mtime'])}" - f"{esc(d['title'][:40])}") - # 06 待办 - todo_by_loop = OrderedDict((g, []) for g in LOOP_ORDER) - for t in todos: - todo_by_loop.setdefault(t["loop"], []).append(t) - todo_cards = [] - for loop, items in todo_by_loop.items(): - if not items: - continue - open_n = sum(1 for i in items if i["bucket"] in ("待办", "部分收口", "拍板待执行")) - lis = "".join( - f"
  • {bucket_tag(i['bucket'])}{esc(i['system'])} · {esc(i['title'][:46])}
  • " - for i in items if i["bucket"] != "解决态残留") - resolved = [i for i in items if i["bucket"] == "解决态残留"] - res_note = (f"

    另有 {len(resolved)} 条解决态残留" - f"(按「解决即移除」口径待清)

    " if resolved else "") - todo_cards.append( - f"

    {esc(loop)} · 开放 {open_n}/{len(items)}

    " - f"
      {lis}
    {res_note}
    ") - resolved_all = [t for t in todos if t["bucket"] == "解决态残留"] - # 循环级计数(四问卡片②的汇总表) - loop_rows = "" - for loop, items in todo_by_loop.items(): - if not items: - continue - open_n = sum(1 for i in items if i["bucket"] in ("待办", "部分收口", "拍板待执行")) - loop_rows += f"{esc(loop)}{open_n}{len(items)}" - # 07 - verify_items = "".join(f"
  • {esc(i)}
  • " for i in verify["items"]) or "
  • —
  • " - obligations = "".join(f"
  • {esc(o)}
  • " for o in verify["obligations"]) or "
  • —
  • " - ref_rows = "" - for r in verify["refs"]: - verdict = '已落后' if r["stale"] else '一致' - ref_rows += (f"{esc(r['doc'])}{esc(r['ref'])}{esc(r['current'])}" - f"{verdict}") - if not ref_rows: - ref_rows = "07 头部未解析出文档版本引用" - # 台账健康 - if active["entries"]: - coll = (f"检出撞号 {', '.join(str(n) for n in active['collisions'])}(编号有效、历史不改写)" - if active["collisions"] else "无撞号") - ledger_html = (f"

    台账区(「{esc(active['tail_head'])}」之后)最新编号 " - f"{active['entries'][-1]},下一编号建议 {active['next_number']};{esc(coll)}。

    ") - else: - ledger_html = "

    未解析出台账条目

    " - # 路线图 - route = "".join(f"
  • {esc(s)}
  • " for s in roadmap) or "
  • —
  • " - - return f""" - - - - -迭代闭环仪表盘 · Dungeon Sweeper - - - -
    -

    迭代闭环仪表盘

    -
    阶段: {esc(stage)} · 生成于 {now} · 只读视图,重跑 python tools/loop_dashboard.py 刷新
    - -

    当前状态快照(active.md)

    -
      {snap}
    - -

    迭代闭环四问

    -
    -

    ① 什么在飞(git 未提交 {len(git_info['dirty'])} + 未跟踪 {len(git_info['untracked'])})

    -{fly_rows}
    分组文件数
    -

    未跟踪:

      {untracked}
    -

    ② 什么待拍板 / 待执行(06 清单 {len(todos)} 条)

    -

    待办 {buckets.get('待办', 0)} · 部分收口 {buckets.get('部分收口', 0)} · 拍板待执行 {buckets.get('拍板待执行', 0)} · -解决态残留 {buckets.get('解决态残留', 0)}(按「解决即移除」口径待清)

    -{"".join(loop_rows)}
    循环开放总条数
    -

    完整清单见下方「待办清单全目」专区。

    -

    ③ 什么要人玩(07 体验验证 {len(verify['items'])} 项)

    -
      {verify_items}
    -

    ④ 验证是否落后于文档(失效面检测)

    -

    07 头部批次来源: {esc(verify['source_line'][:120])}

    -{ref_rows}
    文档07 引用现行版本判定
    -{'

    存在落后引用——07 观测项基于旧行为,验收读数以重测批义务为准。

    ' if stale_refs else '

    引用与现行版本一致。

    '} -

    重测批义务(07 原文)

    -
      {obligations}
    -
    - -

    GDD 文档新鲜度({len(docs)} 份,按改动时间)

    -{"".join(doc_rows)}
    文档版本版本日期文件改动标题
    - -

    待办清单全目(按循环)

    -
    {"".join(todo_cards)}
    -{'

    解决态残留(按「解决即移除」口径应从 06 移除,去向归台账与 git 历史)

      ' + "".join(f"
    • {esc(t['system'])} · {esc(t['title'])}
    • " for t in resolved_all) + '
    ' if resolved_all else ''} - -

    路线图(roadmap.md)

    -
      {route}
    - -

    台账健康

    -
    {ledger_html}
    - -

    近期提交

    -
      {commits}
    - - -
    - -""" - - -def main(): - if hasattr(sys.stdout, "reconfigure"): - sys.stdout.reconfigure(encoding="utf-8", errors="replace") - ap = argparse.ArgumentParser(description="迭代闭环仪表盘生成器") - ap.add_argument("--out", default=str(DEFAULT_OUT)) - ap.add_argument("--open", action="store_true", help="生成后用默认浏览器打开") - args = ap.parse_args() - - stage = STAGE_TXT.read_text(encoding="utf-8").strip() if STAGE_TXT.exists() else "—" - git_info = scan_git() - docs = scan_gdd(git_info) - active = scan_active() - todos = scan_todo() - verify = scan_verify(docs) - roadmap = scan_roadmap() - - out_path = Path(args.out) - out_path.write_text( - render_html(stage, git_info, docs, active, todos, verify, roadmap, out_path), - encoding="utf-8") - print_summary(stage, git_info, docs, active, todos, verify, out_path) - if args.open and sys.platform == "win32": - os.startfile(str(out_path)) # noqa: 仅 Windows 便捷入口 - - -if __name__ == "__main__": - main() diff --git a/tools/path_check.py b/tools/path_check.py index 68a33e2e..655bbacb 100644 --- a/tools/path_check.py +++ b/tools/path_check.py @@ -188,14 +188,14 @@ def main(): print(" · %-46s ← %s" % (ref, where[0])) print("\n=== 退役命名扫描 ===") - # ⚠ 口径纪律:退役命名**只在代码与素材清单里是违规**;文档的版本历史/ - # 台账在「叙述这些名字已退役」时提到它们属正常,不是违规。 - # 故扫描面只含:代码(.gd/.tscn) + 素材清单(design/art/*.md), - # 且排除版本历史/台账类文件(active.md / *checklist* / *memory* / .zcode)。 - # 2026-10-07 初版全仓扫 .md ⇒ active.md/记忆文件里的历史记述全被误判,已修。 + # ⚠ 口径纪律:退役命名**只在代码与素材清单里是违规**;文档的版本历史与叙述性文件 + # 提到这些名字属正常,不是违规。故扫描面只含:代码(.gd/.tscn) + 素材清单 + # (design/art/*.md,且文件名不含「版本历史」),其余一律不扫; + # .git/.workbuddy/.zcode/archive 在目录层就排除。 + # 2026-10-07 初版全仓扫 .md 曾把历史记述全判成违规,故收窄到上述两面(当日还额外跳过 + # 根目录会话状态文件,该文件已于 2026-10-18 用户裁废除,跳过项一并删除)。 hits = 0 scan_exts = (".gd", ".tscn") - skip_names = ("active.md",) for dirpath, dirnames, names in os.walk(repo): dirnames[:] = [d for d in dirnames if d not in (".git", ".godot", "archive", "node_modules", @@ -205,8 +205,6 @@ def main(): rel = os.path.relpath(fp, repo) if any(s in rel for s in (".workbuddy", ".zcode")): continue - if fn in skip_names: - continue is_code = fn.endswith(scan_exts) is_manifest = (fn.endswith(".md") and os.sep + "art" + os.sep in fp