Files
workbuddy_skills/dsh-opensource-release/references/03-实测坑.md
T
admin e03465c398 按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills  提交仓库是指的这里」——
即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。

本次入库(9 个技能,46 个文件):
1、`AI HOT`
2、`draw-ui`
3、`dsh-diagnose`
4、`dsh-knowledge`
5、`dsh-local-env`
6、`dsh-opensource-release`
7、`dsh-workflow`
8、`oil-motion`
9、`skills-security-check`

提交前核对:
· **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓;
· 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 ——
  仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库);
· 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
2026-10-08 22:29:08 +08:00

16 KiB
Raw Blame History

实测坑(全部踩过,别再踩)

归属:技能 dsh-opensource-release 的详情档(按需读,不是每次都要读)。 本档覆盖:实测坑(全部踩过,别再踩)(原行 L621–L703)。 主文件 / 判据与流程主干 = ../SKILL.md(§0.5 事故清单 · §0 事实 · §1 硬规则 R-O1–R-O17 · §1.5 用户当场纠正的硬口径 · §5 授权结构 · §6 发布 SOP 主干 · §7 验证八件套 · §相关)。 来源:2026-09-22「技能重组线」把 ../SKILL.md 的 L621–L703 段下沉到本文件;同日按 humanizer 技能做了一轮「人话化」措辞修订,技术标识、判据与条目数未动。 跨档引用:正文里的「§N / 见 §8 坑 N / 见下表」等编号,用 ../SKILL.md 末节「详情索引」的原章节列定位。 维护:本文件与 ../SKILL.md 的指针行成对;改内容时同时核对主文件的指针描述是否仍准确。


8. 实测坑(全部踩过,别再踩)

  1. text 被重新赋值导致替换未落盘:脱敏函数里 text = text.replace(...) 之后再用 if text2 != text 判「是否需要写盘」⇒ 全局替换永远不落盘(只落行级改动)。必须留 orig = text 做比较基准。
  2. 行级重写的 key 必须写「全局替换之后的文本」:例:原行 (档案 70 的 anysearch 事故…),key 要写 (档案 70 的 该插件 事故…)。用替换前的原文当 key ⇒ 静默不命中,留下半吊子句子。
  3. 行级重写会丢缩进:按 strip() 匹配就必须把原行的 indent 补回去(多行替换值还要给后续行也加 indent),否则 TS 缩进错乱。
  4. 全局替换顺序敏感:wake.html 的专用规则必须在笼统的 ai1net → <baseDomain> 之前,否则专用规则永远不命中(会被先替换成 <baseDomain>.com 这种半成品)。
  5. 重建会抹掉手写文件:所以有 OVERLAY(先快照 _overlay/ 后恢复)+ --force 安全闸。验证过一次:重建后 5/5 个 overlay 文件逐字节一致。
  6. .git 绝不能带过去:历史里含全部内部信息;必须是全新仓库。
  7. Windows 下 install.sh 没有执行位:README 一律写 sudo bash install.sh;要执行位就 chmod +x 后再提交。
  8. 别信「探针没命中」的直觉:探针必须在重建之后、还原 overlay 之后跑(overlay 里也可能带泄漏)。
  9. ⛔ 绝不对 _build_export.py 本身做批量替换:脚本里同时存在「源模式」与「目标值」(例如规则 ("dshs", "dsh-web-platform")),盲替会把源模式一起改掉 ⇒ 改名规则静默失效(2026-09-13 实际误伤 4 处)。改脚本只用精确 Edit,改完必须重建一次并用 grep 复查。
  10. Dockerfile / Dockerfile.dsh 曾被整份跳过脱敏:is_text() 按扩展名判断,二者无扩展名(或 .dsh 不在白名单)⇒ 漏掉。已在 is_text() 里加特判。
  11. package.json 属「源派生」而非 OVERLAY:手改 version / description / repository / license 会在下次 --force 被覆盖。这类项目自有字段必须写成 GLOBAL 规则。
  12. dshs.db 是无前缀写法:src/config.ts 里默认库文件名不带 dsh- 前缀,只替换 dshs 会漏掉它;要单独一条规则。
  13. 改名要连导出目录名一起改,并同步两个辅助脚本里的绝对路径(_build_export.py 的 DST、_verify_tsc.mjs 的 EX),改完重跑一次 tsc 验证路径仍对。
  14. 废弃的旧候选名要进阻断性探针:改名改了两轮时(dshs → 中间候选 → 终选),如果探针只查最早那个名字,中间名会静默残留(_overlay 里最容易中招)。做法:把所有曾用名都加进 LEAK_PROBES。
  15. 改 overlay 的自指标识可以做批量替换(overlay 里只有本项目自己的标识,上游出处名是另一个字符串),但必须先用 grep 确认「旧名出现处全是自指」再替换;_build_export.py 本身绝不能这么干(见坑 9)。
  16. 🔴 --claim-exec 的输出必须当场看;claim 与 release 绝不能写进同一条命令(2026-09-13 实际闯祸):把 claim 结果重定向到文件、只显示 OWNER 首行 ⇒ 没发现抢锁失败;命令末尾的 --release-exec 是 rm -rf 语义,于是把另一个会话(R1-注入层-1104)的锁删掉了。正确姿势:
    • 抢锁单独一条命令,且当场看它的输出(✓ 已持全局执行锁 vs ✗ 抢锁失败);
    • 看到「占用者」不是自己就立即停手,不碰文档库/代码库;
    • 放锁前再 cat 05-交接单/.exec-lock/OWNER 确认首行是自己;
    • 万一误放:脚本实现是 rm -rf "$LOCKEXEC"、不留档,只能按 handoff-guard.sh 第 43 行的格式原样重建(OWNER / 开始:MM-DD HH:MM / 在做:…,值取自抢锁失败时的输出),再用 bash 07-scripts/handoff-guard.sh 的信息模式验证「占用者」已回到原会话,并在回复里明确告知用户;若该会话其实已结束,请用户自行撤锁(处置权只属于用户)。
  17. 🔴 迁移工作根:先改脚本常量,再搬目录(2026-09-13 实际踩):顺序颠倒(先 mv 后改 OUT)时,任何一次重建都会在旧位置把整个仓库重新建出来(那次建出 135 个文件),而且因为旧位置没有 _overlay,6 个手工层文件(README / LICENSE / NOTICE / PLUGIN-PORTING / install.sh / LICENSE-UPSTREAM-MIT)全部缺席;若此时旧目录被当成交付物,就是一次「文档凭空消失」的事故。 正确顺序:① 改 _build_export.py 的 OUT 和 _verify_tsc.mjs 的 EX → ② 复制/搬运目录 → ③ 在新位置重建 → ④ 核对 文件数 与手工层 6/6 + 跑 tsc → ⑤ ls 确认旧位置不存在(没被重建复活)→ ⑥ 同步本技能与项目 MEMORY 里的路径引用。 ⚠️ 附带教训:_build_export.py 的安全闸与 OVERLAY 机制都依赖 OUT 指向真实工作根;OUT 指错时它不会报错,只是在别处默默新建一套。
  18. 🔴 INCLUDE_DIRS / INCLUDE_FILES 是白名单:漏了不报错,只是少文件(2026-09-13 实际踩:assets/ 漏收录 ⇒ 导出的仓库缺注入脚本,平台会 fail-fast、自带校验脚本也会失败)。两条防线:
    • REQUIRED_EXPORT 清单(脚本内,26 项):构建时逐个 os.path.exists,缺一即 return 1;新增"运行时要读的文件"必须加进去。
    • 用被导出仓库自带的校验脚本验收:node scripts/verify-inject.cjs(读 assets/inject/*.js + 检查 proxy.ts 走 loadInject)。这比"我看了一眼目录"可靠得多。 ⚠️ 同类风险点:package.json 的 files(npm/git 安装按它过滤,与 INCLUDE_* 是两套名单,都要补)。
  19. 🔴🔴 cp _overlay/<f> <仓>/ 会把「下一版内容」整份带进当前版(2026-09-18 血证,已误发上公网):_overlay/ 在改名窗口期是「未来版」,导出仓是「当前版」;为修一处残留引用而 cp _overlay/README.md 覆盖旧仓,就把 新名 + 新中文名 + git clone …/<新仓>.git + cd <新仓> + systemctl is-active <新仓> 一并推了上去。 ⛔ 真实危害不是"名字不对"而是功能性缺陷:README 让用户 clone 的那个仓库当时是空的(只有建仓提交)⇒ 照 README 做的人克隆到空仓库,部署直接断。 ✅ 规矩:_overlay 处于下一版状态时绝不整文件 cp 到导出仓,只准逐处精确 Edit,只落本次要发的那几段。 ⚠️ 最阴的一点:这种误覆盖会让「导出仓 vs _overlay 的 diff 变空」。diff 为空本身就是"新版被覆盖进去"的证据,不是"一致"的好消息。看到意外的一致要停下来查原因。 ✅ 推前必做两条复核:① grep -rn "<新名>\\|<新中文名>" . ⇒ 须为空;② 部署命令自洽 —— git clone 指向的仓库真有内容、cd 的目录名、systemctl 的服务名三者一致。
  20. 🔴 新增「中英文件对」时必须同步登记(2026-09-18 实证;2026-09-19 扩充为四处): 这份清单是白名单:没登记的对象不会报错,只是静默跳过检查。所以新增 manual/X.md + manual/X.zh-CN.md 时,四处一起改: ① _build_export.py 的 OVERLAY:漏了 ⇒ 静默少带,文件根本不在导出物里; ② _build_export.py 的 REQUIRED_EXPORT:漏了 ⇒ 缺文件不报错(它只查"清单里的在不在"); ③ _check_parity.mjs 的 MANUAL/PAIRS:漏了 ⇒ 该对静默跳过对等检查(标题数 / 表格行数 / 图数 / 英文汉字数全不检); ④ _check_links.mjs 的 MANUAL:漏了 ⇒ 该文档整份不参与链接与锚点检查。 🔴🔴 ③ 与 ④ 是两份独立实现(不 import 同一常量)⇒ 必须各改一次,改一处不算改。 ✅ 验收判据(别只看 exit code):回读检查器自己报告的文档数有没有 +N,本轮实测 links 24 → 26、parity 输出里多出 manual/contributing.md 一行(headings 4/4)。 ⚠️ 同类静默跳过已出现四次(坑 18 INCLUDE_*/REQUIRED_EXPORT、坑 20 的两份 MANUAL)⇒ 判据统一为:凡"清单式"机制,新增对象后必须回读清单计数是否 +N(AST 实读,别数文件)。
  21. COMMERCIAL-LICENSE.* 的落位规矩(2026-09-18 立):① 只在四份 README(仓 + _overlay/ 各中英)授权节尾加一句链接,⛔ 不在正文铺开;② ⛔ 不得把「商用须授权」的句子写进 LICENSE 或 README 的 AGPL 段落(那才是 §10 追加限制);③ 英文版汉字数仍须恒为 4(只在顶部入口行出现「中文文档」)。
  22. 🔴🔴 sanitize() 不作用于 OVERLAY,所以「干跑预演通过」≠「构建能过」(2026-09-19 血证)—— restore_overlay() 排在 sanitize() 之后 ⇒ 手工层(README / manual / LICENSE / install.sh …)永远不会被脱敏,上面任何字面量都会原样进公开仓。 实测:manual/decisions.{md,zh-CN.md} 里一行目录树写着 poc/(该目录从未收录)⇒ 重建时被阻断探针抓出 blocking hits: 2。 ⇒ 纪律:① 改完手工层必须重建(或至少跑整树探针);② 若写了「干跑预演」脚本,它只覆盖源派生文件,不能当手工层的通行证;③ 命中后按「先改仓库同名文件 → 再 Copy-Item 进 _overlay」的顺序修(反了会被 stash_overlay() 覆盖回去)。
  23. 🔴 裸替换「纯字母数字」的内部标识会改坏 package-lock.json(2026-09-19): 内部主机号 w106/w47 全由 base64 字母表字符组成 ⇒ 裸替会命中 integrity 哈希,npm ci 校验失败,且症状与改动毫无关联、极难排查。 ⇒ 只替带引号或**作对象键(后跟冒号)**的形态;纯数字标识(如 15432)也要先扫一遍上下文再决定(本轮实测 11 处全在 127.0.0.1:15432 这类 URL 里,安全)。
  24. 🔴 改名之后,新项目名可能与旧脱敏规则「撞名」(2026-09-19): 新名 dsh_ai1net 含 ai1net,而 ai1net 同时是真实域名的词根 ⇒ 若照旧写一条笼统的 ai1net → <baseDomain>,会把新名打成 dsh_<baseDomain>。 ⇒ 只打带 .com 的域名形态(dsh_ai1net 永不含 .com)。同理:任何"新名包含旧敏感词根"的情形,规则都必须精确到可区分的形态,并规则 + 探针同时加。
  25. 内部运维 / 演练 / 诊断脚本族的排除判据(2026-09-19 定型):新增一整批 *-probe / *-drill / *-jitter / *-entropy / bootstrap-*.sh / dshlog.mjs / find-ui* / check-layering* 时按四条判: ① README / manual 从不引用它;② 含内部主机号 / 测试机 IP / 内部隧道端口;③ 引用内部文档(交接单 / 调整方案 / poc/);④ 是「把节点接进本平台自研编排」的一次性手册。 ⇒ 四条全中 = 与 K8s 那一族同处置(整批 drop),别试图逐条脱敏(脱完也不可用,且留下一堆 <> 占位符)。 ⚠️ .py 这类脚本进来时先看 TEXT_EXT:不在白名单的扩展名整份跳过脱敏且不被任何探针扫(本轮 .py 就踩了,已补)。
  26. 🔴🔴 只改手工层(OVERLAY)文字时,不必整树 --force 重建(2026-09-19 定型,是坑 22 的推论): 坑 22 已证 restore_overlay()(纯 shutil.copy2)排在 sanitize() 之后 ⇒ 手工层不过脱敏、逐字节直落。 ⇒ 「改 _overlay/<f> + 跑 _sync_overlay.py(方向 _overlay → 导出仓)」得到的产物,与整树重建逐字节相同。 ✅ 收益:风险面从整棵树缩到改的那几个文件,不会顺带把源仓当前状态(别人的未提交改动 / 新提交 / ?? 目录)带进公开仓, 也就不必先过 dirty_src_files() 那道闸门(省掉一轮「等源仓干净」的阻塞)。 ⚠️ 前提与边界:① 改动必须落在 OVERLAY 清单内;非手工层文件只能「改规则 + 重建」; ② 仍须 _sync_overlay.py --check 报**「逐字节一致」再提交(方向搞反 = 事故 #27); ③ 交付前仍跑全套门禁**(_check_dst 整树探针 / _check_public 两仓 / links / parity / tsc), 其中 _verify_tsc.mjs 与 _check_public.py 必须串行(junction 假阳性)。 ④ 若这轮同时还有规则改动,就必须回到整树重建 —— 捷径只适用于「纯手工层文字」。
  27. 🔴🔴 写文档用词之前,先 grep 整个代码库,确认这个词有没有被别的意思占用(2026-09-19 用户点出): 实测:manual/project*.md 里用「水位」表达「上下文已占用量」⇒ ① 对读者是类比(要先自己换算); ② 更硬的是术语撞车 —— 水位 在本项目已有两个别的含义:src/net/relay/client.ts:150 = WS 出向水位(字节,默认 256 KiB)、 src/db/schema.ts:301 = worker 容量水位。同一个词在文档 + 代码里指三样东西,这是歧义,不是风格。 ⇒ 改法:贴回同段前一句已经引入的项目词(该段前半句是「上下文被当作有上限的预算」)⇒ ZH 其次是**上下文已经占了多少** | EN then **how much of the context is already used**。⛔ 不另造新比喻。 🔑 为什么必须单独立一条:探针体系对这类问题完全无感,该文件所在 manual/ 没有任何探针, blocking hits: 0、required 全在、两仓 0 命中 ⇒ 门禁全绿,而词依然是坏的。 判据:探针只答「有没有不该出现的敏感信息」,不答「这个词会不会产生歧义」。 ⛔ 同一段里另有 5 处类比,用户只点了一处 ⇒ 一律不动,列条目报上去等拍板(背着这份重量 / 腐坏 / 悄悄搭车 / 淹没 / EN 独有 blast radius)。 同段同病也不许自行扩大改动面:报清单的成本是一个回合,擅改的成本是信任。