Files
dsh_ai1net_server/dsh-server-docs/scripts/docs-index-stats.py
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

131 lines
5.5 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 -*-
"""docs-index-stats.py — 从 INDEX.md §二 清单表**算**出状态分布,并可就地刷新「状态摘要」行。
为什么需要它
────────────
「状态摘要」原本是**人工手写**的硬数字,档案一多必然漂移(2026-09-13 实测:摘要写「档案 72 份」,
而表格实际 76 行)。本脚本把摘要变成**机器生成**:读表 → 统计 → 打印;`--write` 时回写该行,
并顺带做**图例自检**;**完整保持文件原有行尾**(CRLF 文件不会被转成 LF)(表格用到的图标必须在「图例」行里有定义,缺则补)。
用法
────
python3 scripts/docs-index-stats.py # 只打印 + 一致性判定
python3 scripts/docs-index-stats.py --write # 就地刷新「状态摘要」行(必要时补图例)
退出码:0 = 一致(或已刷新);1 = 漂移且未加 --write;2 = 结构异常。
"""
import io
import os
import re
import sys
import collections
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INDEX = os.path.join(ROOT, "INDEX.md")
ORDER = ["✅", "🔄", "🧪", "📝", "🔍", "📋", "🟡", "🗄", "🔧", "⚠️"]
LEGEND_OF = {
"✅": "已落地", "🔄": "维护中", "🧪": "PoC", "📝": "待开发", "🔍": "核查完成",
"📋": "评估", "🟡": "保留兜底", "🗄": "归档", "🔧": "修复", "⚠️": "警示",
}
HEADER_RE = re.compile(r"^\|\s*号\s*\|\s*状态\s*\|")
LEAF_RE = re.compile(r"^0\d$")
def parse(index_path):
"""返回 (lines, arch_rows, leaf_rows, counter, other_count)。"""
# ⚠️ 必须 newline=""(通用换行会把 CRLF 静默转成 LF —— 2026-09-13 踩过两次)
raw = io.open(index_path, encoding="utf-8", newline="").read()
lines = raw.split("\n")
try:
start = next(i for i, l in enumerate(lines) if HEADER_RE.match(l))
except StopIteration:
raise SystemExit("ERROR: INDEX.md 里找不到「| 号 | 状态 | 一句话 |」表头")
arch, leaf, other = [], [], 0
for l in lines[start + 2:]:
if not l.startswith("|"):
break
cells = [x.strip() for x in l.split("|")]
if len(cells) < 4 or not cells[1]:
continue
no, st = cells[1], cells[2]
if no.startswith("04-"):
arch.append((no, st))
elif LEAF_RE.match(no):
leaf.append((no, st))
else:
other += 1
cnt = collections.Counter(st for _, st in arch)
cnt.update(st for _, st in leaf)
eol = "\r\n" if raw.count("\r\n") > 0 else "\n"
return lines, arch, leaf, cnt, other, eol
def summary_text(cnt, n_arch, leaf_names, other):
parts = ["%s %d" % (k, cnt[k]) for k in ORDER if cnt.get(k)]
unmarked = sum(v for k, v in cnt.items() if k not in ORDER)
if unmarked:
parts.append("未标记 %d" % unmarked)
leaf = (",另含根级编号 %d 条(%s)" % (len(leaf_names), "/".join(leaf_names))) if leaf_names else ""
return (
"> **状态摘要**(**机器生成,勿手改**):档案 **%d** 份(`04-*`)%s,"
"另有非编号行 %d 条(README / INDEX / 技能 / poc 等)—— %s。"
"复跑 `python3 scripts/docs-index-stats.py` 取数,`--write` 就地刷新本行。"
% (n_arch, leaf, other, " | ".join(parts))
)
def main():
write = "--write" in sys.argv
lines, arch, leaf, cnt, other, eol = parse(INDEX)
leaf_names = [n for n, _ in leaf]
new = summary_text(cnt, len(arch), leaf_names, other)
print("档案 04-* %d 份 | 根级编号 %s | 非编号行 %d 条" % (len(arch), "/".join(leaf_names) or "—", other))
for k in ORDER:
if cnt.get(k):
print(" %s %-6s %d" % (k, LEGEND_OF.get(k, ""), cnt[k]))
unmarked = sum(v for k, v in cnt.items() if k not in ORDER)
if unmarked:
print(" 未标记 %d" % unmarked)
changed = False
# ── 图例自检 ─────────────────────────────────────────────────────────
li = [i for i, l in enumerate(lines) if l.startswith("> 图例:")]
if li:
legend = lines[li[0]]
missing = [k for k in cnt if k not in legend]
if missing:
print("⚠️ 图例缺图标:%s" % " ".join(missing))
if write:
lines[li[0]] = legend.rstrip().rstrip("|") + "".join(
"|%s%s" % (k, LEGEND_OF.get(k, "")) for k in missing
)
changed = True
print("✓ 已补进图例行")
# ── 摘要行 ───────────────────────────────────────────────────────────
idx = [i for i, l in enumerate(lines) if l.startswith("> **状态摘要**")]
if not idx:
print("ERROR: 未找到「状态摘要」行", file=sys.stderr)
return 2
old = lines[idx[0]]
if old.strip() == new.strip() and not changed:
print("✓ 摘要与表格一致")
return 0
if not write:
print("✗ 摘要与表格不一致(加 --write 刷新)")
print(" 旧: %s" % old.strip()[:90])
print(" 新: %s" % new.strip()[:90])
return 1
lines[idx[0]] = new
io.open(INDEX, "w", encoding="utf-8", newline="").write(eol.join(lines))
print("✓ 已刷新 INDEX.md(摘要行%s)" % ("+图例" if changed else ""))
return 0
if __name__ == "__main__":
sys.exit(main())