Files
dsh_shenxian/dsh-server-docs/07-scripts/docs-shrink-guard.py
T
admin e6207aa691
build / build-and-scan (push) Waiting to run
chore(仓库对齐): 文档库结构治理 + IM/插件线落地
文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。

IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。

插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。

仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
2026-09-24 07:25:16 +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 07-scripts/docs-shrink-guard.py # 比对并报告(不改快照)
python3 07-scripts/docs-shrink-guard.py --write # 比对 + 刷新快照(改完文件后跑)
python3 07-scripts/docs-shrink-guard.py --baseline # 只建档不比对(首次)
python3 07-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:]))