- 注册表:docs/architecture/README.md §2,37 份 canonical(frontmatter topic+canonical:true)与表双向机器对账
- 门:.agents/tools/docs-gate.py 六检(品牌根/canonical唯一/死链/入口卫生/留痕隔离/设计档申报),挂 .githooks/pre-commit(本仓已激活)+ .gitea/workflows/docs-gate.yml(待runner)+ wave-close 第8步;旧 check-deadlinks.sh 退役并入 G3
- 清缩:删 54 项历史档(plans 16/agent-specs 设计与spike 20/brainstorms 5/memorys 3/goals+作战清单完成史归档/王蓝莓design/SAA现状html/add-game-template);channel-spike 564K 代码资产迁仓级 spikes/;六件删前蒸馏已迁(代码评审16条open项→进度总账§5、prefix-cache字段表→cheap-model skill、意图基线29条→需求清单附录、九门降级rationale→验收门、A11 TODO→tech-decisions、3layer边界→littlejs-game-dev 指针)
- 修口径约90处:六处SAA『现行主线』旧标、Nacos/RocketMQ『未部署』旧述(07-01反转)、gameDefinition残留、全部死链改 git show 定位;AGENTS.md 249→136行(决策史归tech-decisions);_index 改纯在飞板;对外演示版 md→html
- 依据:docs/agent-specs/2026-07-02-文档治理-{全量普查与裁决-report,SoT注册表与治理门-设计}.md(四路普查191份md+Codex/Opus双评审必修项已折入);恢复基线 8ea97234(单档 git checkout 8ea97234 -- <路径>)
127 lines
6.5 KiB
Bash
Executable File
127 lines
6.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 文档整理助手 · 机械底层(检测 + 归档 + 记时)
|
||
# 判断(KEEP/ARCHIVE/COMPRESS、措辞对齐、总账回填)一律留 agent —— 见 .agents/skills/doc-organizer.md
|
||
# 设计:脚本只做"可机械化的检测与可逆搬运",绝不自删/自改正文。全程 core.quotePath=false 防中文路径转义坑。
|
||
set -uo pipefail
|
||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # 仓根
|
||
SPECS="docs/agent-specs"
|
||
STATE="$ROOT/.agents/tools/doc-organizer-state.json"
|
||
G(){ git -C "$ROOT" -c core.quotePath=false "$@"; }
|
||
|
||
# 最新日期前缀(今日在飞保护锚点):动态取目录内最大 dated 档,免硬编码过期
|
||
# (2026-06-17 改:旧硬编码 2026-06-16 一天即过期——致 06-16 档被误当在飞拒归档、真在飞的 06-17 档反没保护)
|
||
latest_date(){ ls -1 "$ROOT/$SPECS"/2026-*.md 2>/dev/null | sed -E 's#.*/(2026-[0-9]{2}-[0-9]{2}).*#\1#' | sort -u | tail -1; }
|
||
|
||
# 从 state 读上次清理锚点(无 jq 也能跑:python 解析)
|
||
last_field(){ python3 -c "import json,sys;print(json.load(open('$STATE'))['last_cleanup'].get('$1',''))" 2>/dev/null; }
|
||
|
||
cmd="${1:-scan}"
|
||
case "$cmd" in
|
||
since)
|
||
echo "上次清理 commit = $(last_field commit)"
|
||
echo "上次清理 时间 = $(last_field at)"
|
||
echo "当前 UTC = $(date -u +%FT%TZ) | HEAD = $(G rev-parse --short HEAD)"
|
||
;;
|
||
|
||
delta) # 窗口内改动/新增的文档
|
||
lc="$(last_field commit)"; [ -z "$lc" ] && lc="$(G rev-list --max-parents=0 HEAD | tail -1)"
|
||
echo "## 窗口内改动的 .md(${lc}..HEAD,docs/ + .agents/)"
|
||
G diff --name-only "${lc}..HEAD" -- docs .agents 2>/dev/null | grep -E '\.md"?$' || echo " (无)"
|
||
echo "## 窗口内新增未跟踪 .md"
|
||
G ls-files --others --exclude-standard -- docs .agents 2>/dev/null | grep -E '\.md"?$' || echo " (无)"
|
||
;;
|
||
|
||
health) # 热目录健康度(阈值据 engineering-conventions §10)
|
||
cd "$ROOT/$SPECS"
|
||
LD="$(latest_date)"; n=$(ls -1 2026-*.md 2>/dev/null | grep -v "^$LD" | wc -l)
|
||
sz=$(du -sm "$ROOT/$SPECS" | cut -f1)
|
||
echo "## 热目录健康度"
|
||
echo " 顶层 dated .md(非最新日): $n $([ "$n" -gt 30 ] && echo '⚠>30 该归档' || echo 'OK')"
|
||
echo " agent-specs 体积: ${sz}M $([ "$sz" -gt 6 ] && echo '⚠>6M 该清证据' || echo 'OK')"
|
||
echo "## 带 SHIPPED/SUPERSEDED 横幅却仍在热目录(应 archive)"
|
||
grep -lE '状态: ?(SHIPPED|SUPERSEDED|SPIKE-DONE)' 2026-*.md 2>/dev/null | grep -v "^$LD" | sed 's/^/ /' || true
|
||
echo "## execution/edit-plan >150 行仍在热目录(应 compress 留桩)"
|
||
for f in 2026-*-execution.md 2026-*-edit-plan.md; do
|
||
[ -f "$f" ] || continue; case "$f" in "$LD"-*) continue;; esac
|
||
l=$(wc -l <"$f"); [ "$l" -gt 150 ] && echo " $l 行 $f"
|
||
done
|
||
echo "## doc 树混入的非 md(白名单 orchestrator/channel-spike/generation-spike/_archive 除外)"
|
||
G ls-files "$SPECS" | grep -vE '\.md"?$|orchestrator/|channel-spike/|generation-spike/|_archive/' | sed 's/^/ /' | head -20 || true
|
||
;;
|
||
|
||
stale) # 核心设计档残留废弃选型词(人工判:当现行=改 / 已标降级=放过)
|
||
echo "## 核心设计档残留废弃选型词(总数 / 疑似当现行=无降级标注的行)"
|
||
for d in "$ROOT"/docs/architecture/*.md "$ROOT"/.agents/knowledge/*.md; do
|
||
[ -f "$d" ] || continue
|
||
tot=$(grep -icE 'Dify|OpenGame|Canvas Runtime|ComfyUI|AgentScope' "$d")
|
||
[ "$tot" -eq 0 ] && continue
|
||
sus=$(grep -iE 'Dify|OpenGame|Canvas Runtime|ComfyUI|AgentScope' "$d" | grep -ivE '降级|废弃|废除|future-state|supersede|备选|moot|不部署|~~|已退役|历史' | wc -l)
|
||
echo " 共${tot} 疑似${sus} $(basename "$d")"
|
||
done
|
||
echo " 现行真相基准 = .agents/knowledge/tech-decisions.md + AGENTS.md §3(已对齐,权威源)"
|
||
;;
|
||
|
||
ledger) # 总账新鲜度
|
||
L="$ROOT/docs/mvp/MVP进度总账.md"
|
||
lc="$(last_field commit)"
|
||
echo "## 总账新鲜度"
|
||
grep -m1 '最近更新' "$L" 2>/dev/null | sed 's/^/ 总账: /' || echo " (未找到表头)"
|
||
echo " 窗口内收口报告/验收报告(可能需回填总账 §6):"
|
||
G diff --name-only "${lc}..HEAD" -- "$SPECS" 2>/dev/null | grep -E '收口报告|验收|report|报告' | sed 's/^/ /' || echo " (无)"
|
||
;;
|
||
|
||
scan) # 一次拿全部信号
|
||
"$0" since; echo; "$0" delta; echo; "$0" health; echo; "$0" stale; echo; "$0" ledger
|
||
;;
|
||
|
||
archive) # 可逆搬运: git mv 入 _archive + 同步修引用。用法: archive <basename...>
|
||
shift; [ $# -eq 0 ] && { echo "用法: archive <文件名...>"; exit 1; }
|
||
mkdir -p "$ROOT/$SPECS/_archive"
|
||
LD="$(latest_date)"
|
||
for f in "$@"; do
|
||
b="$(basename "$f")"
|
||
case "$b" in "$LD"-*) echo "拒绝: $b 是最新日($LD)在飞档"; continue;; esac
|
||
G mv "$SPECS/$b" "$SPECS/_archive/$b" && echo "归档: $b"
|
||
done
|
||
# 同步修引用: agent-specs/<已归档名> -> agent-specs/_archive/<名>(字面替换,幂等)
|
||
python3 - "$ROOT" <<'PY'
|
||
import os,sys,glob
|
||
root=sys.argv[1]
|
||
# Claude auto-memory 目录按用户/机器不同,不硬编码:从环境变量 CLAUDE_MEMORY_DIR 读,缺省则跳过该段(只修 .agents/docs 内引用)
|
||
mem=os.environ.get("CLAUDE_MEMORY_DIR","")
|
||
arch=[os.path.basename(p) for p in glob.glob(root+"/docs/agent-specs/_archive/*.md")]
|
||
tgt=set()
|
||
bases=[root+"/.agents",root+"/docs"]
|
||
if mem and os.path.isdir(mem): bases.append(mem)
|
||
for base in bases:
|
||
for dp,_,fns in os.walk(base):
|
||
if "/_archive" in dp: continue
|
||
for fn in fns:
|
||
if fn.endswith(".md"): tgt.add(os.path.join(dp,fn))
|
||
fixed=0
|
||
for t in tgt:
|
||
try: s=open(t,encoding="utf-8").read()
|
||
except: continue
|
||
o=s
|
||
for b in arch: s=s.replace("agent-specs/"+b,"agent-specs/_archive/"+b)
|
||
if s!=o: open(t,"w",encoding="utf-8").write(s); fixed+=1
|
||
print(f"引用修复文件数: {fixed}")
|
||
PY
|
||
;;
|
||
|
||
record) # 记本次清理时间. 用法: record <commit> <一句话摘要...>
|
||
shift; commit="${1:-$(G rev-parse --short HEAD)}"; shift || true; summary="${*:-清理}"
|
||
python3 - "$STATE" "$(date -u +%FT%TZ)" "$commit" "$summary" <<'PY'
|
||
import json,sys
|
||
p,now,commit,summary=sys.argv[1:5]
|
||
d=json.load(open(p))
|
||
d["last_cleanup"]={"at":now,"commit":commit,"note":summary}
|
||
d.setdefault("history",[]).append({"at":now,"commit":commit,"summary":summary})
|
||
json.dump(d,open(p,"w"),ensure_ascii=False,indent=2)
|
||
print("已记本次清理:",now,commit)
|
||
PY
|
||
;;
|
||
|
||
*) echo "用法: doc-organizer.sh {scan|since|delta|health|stale|ledger|archive <文件名...>|record <commit> <摘要>}";;
|
||
esac
|