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 一律写「远程服务器」。
This commit is contained in:
1 parent
c70d5d860e
commit
5ad755116e
173 files changed
+27632
No files matched your search
@@ -0,0 +1,122 @@
|
||||
#!/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:]))
|
||||
Reference in new issue
Block a user