Files
dsh_shenxian/dsh-server-docs/scripts/docs-shrink-guard.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

123 lines
5.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 -*-
"""docs-shrink-guard.py — 防「共享文件被整文件重写抹掉别人的行」
为什么需要(2026-09-14 实测)
* `MEMORY.md` 被并行会话**整文件重写**,抹掉了本会话刚写进去的两行;
* 本库的规矩是「共享文件只用 Edit 精确替换,禁整文件 Write」—— 但**只靠人守纪律**,没有机制兜底。
⇒ 本脚本给"纪律"加一条**机械兜底**:记录每个文件的**行数快照**,下次运行时若某文件
**行数骤降**(默认 >30% 且绝对减少 >20 行),就把它当成"疑似被整文件重写"报警。
覆盖范围(两处共享热区):
* 文档库 `dsh-server-docs/**/*.md`
* 工作区记忆 `.workbuddy/memory/*.md`
快照落在**库外**(`<工作区>/.workbuddy/cache/docs-lines.json`)—— 放库内会污染 `docs-sync-check` 对账。
用法
python3 scripts/docs-shrink-guard.py # 比对并报告(不改快照)
python3 scripts/docs-shrink-guard.py --write # 比对 + 刷新快照(改完文件后跑)
python3 scripts/docs-shrink-guard.py --baseline # 只建档不比对(首次)
python3 scripts/docs-shrink-guard.py --allow-shrink <路径子串> # 合法重构白名单(不报警)
退出码:0 = 无骤降;1 = 发现骤降(需人看一眼 diff);2 = 用法错误
"""
import io
import json
import os
import sys
DOCS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
WS = os.path.dirname(DOCS)
SNAP = os.path.join(WS, '.workbuddy', 'cache', 'docs-lines.json')
DROP_RATIO, DROP_MIN = 0.30, 20
def collect():
out = {}
roots = [(DOCS, 'docs'), (os.path.join(WS, '.workbuddy', 'memory'), 'memory')]
for root, tag in roots:
if not os.path.isdir(root):
continue
for base, dirs, names in os.walk(root):
if '.git' in dirs:
dirs.remove('.git')
if os.sep + 'cache' in base:
continue
for n in sorted(names):
if not n.endswith('.md') or '.bak' in n:
continue
p = os.path.join(base, n)
rel = os.path.relpath(p, WS).replace('\\', '/')
try:
out[rel] = sum(1 for _ in io.open(p, encoding='utf-8', errors='replace'))
except OSError:
continue
return out
def main(argv):
now = collect()
allow = [argv[i + 1] for i, a in enumerate(argv)
if a == '--allow-shrink' and i + 1 < len(argv)]
old = {}
if os.path.exists(SNAP):
try:
old = json.loads(io.open(SNAP, encoding='utf-8').read())
except ValueError:
print('⚠️ 快照损坏,按首次处理')
if '--baseline' in argv or not old:
if '--write' in argv or '--baseline' in argv:
os.makedirs(os.path.dirname(SNAP), exist_ok=True)
io.open(SNAP, 'w', encoding='utf-8', newline='\n').write(
json.dumps(now, ensure_ascii=False, indent=1, sort_keys=True) + '\n')
print('已建基线:%d 个文件 → %s' % (len(now), SNAP))
return 0
print('无快照(首次)。跑 `--baseline` 建档后再用。')
return 2
shrunk, added, removed, grew = [], [], [], []
for rel, n in now.items():
if rel not in old:
added.append(rel)
elif n < old[rel]:
if old[rel] - n > DROP_MIN and (old[rel] - n) / old[rel] > DROP_RATIO:
if any(a in rel for a in allow):
print(' ↳ 合法重构(白名单):%s %d → %d 行' % (rel, old[rel], n))
else:
shrunk.append((rel, old[rel], n))
elif old[rel] - n > 0:
grew.append((rel, old[rel], n, 'down'))
for rel in old:
if rel not in now:
removed.append(rel)
for rel, n in now.items():
if rel in old and n > old[rel]:
grew.append((rel, old[rel], n, 'up'))
print('文件 %d(新增 %d / 删除 %d)| 行数变化 %d' % (len(now), len(added), len(removed), len(grew)))
for rel in added[:8]:
print(' + %s' % rel)
for rel in removed[:8]:
print(' - %s(消失?)' % rel)
for rel, o, n, d in sorted(grew, key=lambda x: -abs(x[1] - x[2]))[:6]:
print(' %s %s %d → %d 行' % ('↑' if d == 'up' else '↓', rel, o, n))
bad = 0
if shrunk:
bad = 1
print('\n⚠️ **疑似被整文件重写(行数骤降)** —— 请看一眼 `git diff` 或与上一位写入者核对:')
for rel, o, n in shrunk:
print(' %s %d → %d 行(-%.0f%%)' % (rel, o, n, (o - n) / o * 100))
print(' 本库规矩:共享文件只用 Edit 精确替换,禁整文件 Write(见 CODEBUDDY.md)')
if '--write' in argv:
os.makedirs(os.path.dirname(SNAP), exist_ok=True)
io.open(SNAP, 'w', encoding='utf-8', newline='\n').write(
json.dumps(now, ensure_ascii=False, indent=1, sort_keys=True) + '\n')
print('已刷新快照。')
elif bad:
print('(未刷新快照:确认无误后跑 `--write`)')
return bad
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))