发布于 更新于 约 8 分钟读完 · 2,929 字

本文目录 18 节
  1. 事实记录(不可修改区)
  2. 现象
  3. 我的初判(后被证伪)
  4. 逐步收集的事实
  5. 真凶:自家的同步脚本,且是设计行为
  6. 为什么必然复发
  7. 派生物损坏 ≠ 源损坏
  8. 处置
  9. 一句话总结
  10. 五条可复用 insight
  11. 1. 复发 + 上次已修 = 上次归因错了 ⚠️首次
  12. 2. 自己写下的错误归因,会成为下一次的加速陷阱 ⚠️首次
  13. 3. 查”谁干的”要点名册,不要从形态反推 ⚠️首次
  14. 4. 派生物损坏 ≠ 源损坏,喊”废了”之前先查源 ⚠️首次
  15. 5. 问题在展示层时,别去修数据层
  16. 顺手教训
  17. 下次改进
  18. 关联文档

构建产物的脏改动不是事故 · 复发是归因错了的信号

入档:2026-07-28 来源:蛛网之上(above-the-web)源代码管理面板里 kb-content 反复出现 305 个未提交改动,作者问「为什么又出现了,这是第二次了」 状态:已定位并处置(提交 1ead950,作者重载窗口后确认消失);本篇同时是对 2026-07-27 一次错误归因的推翻——那次把同一现象归到 Obsidian 头上,并据此写进了记忆与归档分支

事实记录(不可修改区)

现象

E:\above-the-web\kb-content 稳定呈现 305 个 M(modified)。作者明确指出「第二次了」。

我的初判(后被证伪)

初判为「Obsidian 属性面板批量重写 frontmatter」。依据两条,都不是直接证据:

  • 我的记忆文件 repo-sweep-loose-ends 里写着「kb-content 有损格式化 = Obsidian,已归档到 archive/obsidian-lossy-reformat-20260727」;
  • knowledge-base 仓库当天有一条提交 df79c76 配置:Obsidian 属性显示改为源码模式,阻断 frontmatter 批量重写。

diff 形态也「很像」:tags: [类型/档案] 展开成带引号的 YAML 块列表、BOM 被删。我当时说了「确诊了」。

逐步收集的事实

  1. 不是行尾问题:git diff --ignore-cr-at-eol / -w 后改动全在(+926 / −414),是真实内容改动。
  2. 改动范围极干净:305 个 hunk 的起始行分别是 1(56 个)、2(248 个)、3(1 个),起始行 >12 的 hunk 为 0,正文零改动。
  3. 唯一一行”非 frontmatter 新增”是假象:+# Stay alive Case Notes 实为 -\ufeff# … → +# …,即 BOM 被删。
  4. song-caption-mv-workflow/SKILL.md 的 frontmatter 被清空:name 与 description 两行消失,只剩 ---\n---。我当时说「这个 skill 已经废了」——也是错的,见下。
  5. 时间戳比对(决定性):305 个文件的 mtime 全部落在 2026-07-27 18:00,而 .obsidian/app.json 那次”修复”改于 16:44。改动发生在修复之后——上次的归因与修复被同时证伪。
  6. 名册排除(决定性):%APPDATA%\obsidian\obsidian.json 里 Obsidian 只注册了一个库 E:\AIGC工作站\年轮平台测试\docs,kb-content 根本不在其中。Obsidian 不会自动扫描磁盘上的目录。

真凶:自家的同步脚本,且是设计行为

E:\above-the-web\scripts\sync-content.mjs 的 normalizeFrontmatter(),三个动作正好对应三种 diff:

脚本行为观察到的 diff
stripBom()-\ufeff--- → +---
重建 tags:\n - "x"行内数组展开成带引号块列表
只保留 tags / title,丢弃其余键SKILL.md 的 name / description 消失

源码注释写明了意图:「knowledge-base 用 Obsidian 风味 frontmatter(值里可能含 [[]]、未引号冒号等非法 YAML)。平台只消费 tags / title,故重建为最小安全 YAML,丢弃其余专有键,杜绝解析崩溃。」

为什么必然复发

  • kb-content/ 在 above-the-web 的 .gitignore 第 11 行,注释即「内容源:构建时从 knowledge-base 仓库克隆,不入库」;
  • package.json 里 dev 与 build 都以 npm run sync 开头;
  • sync 先 git reset --hard FETCH_HEAD,再遍历所有 md 就地 sanitize() 写回。

即:每次开发或构建都必然产生这批脏改动,且下一次 sync 会把它们冲掉重来。 它是流水线的稳定产物,不是事故。

派生物损坏 ≠ 源损坏

核实 SKILL.md:E:\knowledge-base 源仓库那份、以及本机 C:\Users\Administrator\.claude\skills\ 装载的那份,name / description 均完好,源仓库 git status 干净。kb-content 里那份只是会被反复重建的副本。

处置

问题不在数据层——文件被改是设计内的;问题在展示层:VSCode 自动检测嵌套 git 仓库,把这个构建产物单列进源代码管理面板,逼人每次重新判断一遍「我是不是漏提交了」。

加 E:\above-the-web\.vscode\settings.json:

{ "git.ignoredRepositories": ["kb-content", "E:\\above-the-web\\kb-content"] }

提交 1ead950。作者 Developer: Reload Window 后确认面板里不再出现。

一句话总结

同一个现象第二次出现,而上次”修复”之后它照样发生——这不是”又坏了一次”,这是上次归因错了的证据。最快的证伪手段是比时间戳:改动发生在修复之后,归因就不成立。

五条可复用 insight

1. 复发 + 上次已修 = 上次归因错了 ⚠️首次

人的默认反应是「又犯了,再修一次同样的地方」——这个反应默认了上次归因是对的。但「修了却照犯」本身就是对那次归因最强的反证。

最快的证伪动作是比时间戳,两个数字就够:

改动发生:2026-07-27 18:00
上次修复:2026-07-27 16:44   ← 修复在前,改动在后 → 归因不成立

这一步花不到一分钟,却直接推翻了一整条被写进记忆的结论。遇到”第二次”,先做这个减法,再谈修。

2. 自己写下的错误归因,会成为下一次的加速陷阱 ⚠️首次

我不是慢慢误判的,是很快误判的——因为记忆文件里明明白白写着「kb-content 有损格式化 = Obsidian」。它让我跳过了取证,直接说出「确诊了」。

错误的结论一旦被写进记忆 / 文档 / commit message,就不再是一个待检验的假设,而变成了下次的默认前提。 它的危害不是”存了个错东西”,而是它会主动加速你第二次走错,且比第一次更自信(“上次就是这个原因”)。

有意改动不是故障_v1 已记过「归因被推翻要连经验一起改」;本次补上更前一步的判据:任何被写下的归因,都要带着”它可能是错的”这个标签被读取。尤其当它来自你自己——自己写的东西最不会被质疑。

推论:归因类的记忆条目,最好连”证据是什么”一起写。我那条记忆只写了结论(“是 Obsidian”),没写”凭什么”,于是无从复核。写上”证据:obsidian.json 里注册了该 vault”,这次一查就露馅了。

3. 查”谁干的”要点名册,不要从形态反推 ⚠️首次

diff 的形态(tags 重排 + 去 BOM)确实非常像 Obsidian 的手笔——这正是它骗人的地方。形态相似只能提出嫌疑人,不能定罪。

定罪靠的是名册:obsidian.json 里 Obsidian 到底注册了哪些库。一查,kb-content 不在其中,嫌疑当场排除。

判据:怀疑某个程序改了文件,先去查那个程序的”作用域清单”(它管哪些目录 / 打开了哪些项目),而不是从改动长什么样往回猜。 作用域是二元的、可证伪的;形态相似是连续的、可自圆其说的。

同族反面:有意改动不是故障_v1 那次是「用一个真实的模式去套了一个不属于它的现象」(formatter 确实重排过表格,于是把删图也归给它)。本次一模一样——Obsidian 确实会重写 frontmatter,于是把这批也归给它。真实存在的模式,是最容易被过度套用的模式。

4. 派生物损坏 ≠ 源损坏,喊”废了”之前先查源 ⚠️首次

看到 SKILL.md 的 name / description 被删,我脱口而出「这个 skill 已经废了」。实际上那是个每次构建都会被重建的副本,源仓库和本机装载的那份都完好。

判据:在一个可重建的目录里发现”数据损坏”,第一件事是确认它是源还是派生物。 派生物的损坏不是数据事故,只是产物形态——它甚至可能是设计要的(本例中丢弃 name/description 正是脚本刻意为之,因为站点只消费 tags/title)。

识别派生物的三个信号,本例全中:在 .gitignore 里 / 由脚本自动克隆 / 同步时会 reset --hard。

5. 问题在展示层时,别去修数据层

文件被改是对的、不该拦;git status 显示脏也是对的、没说谎。唯一错的是 VSCode 把一个构建产物摆进了”等你提交”的位置,让人每次都要重新做一遍判断。

如果只盯着数据层,能做的只有”每次手动 checkout 一遍”——治标且徒劳(下次 build 又来)。真正的解法在展示层:git.ignoredRepositories 一行配置,从此不再出现在待办位。

判据:当”数据是正确的,但它出现在错误的位置上”时,改的是位置,不是数据。 反复出现的、需要人做同一个判断的东西,都该问一句:能不能让它压根别出现在需要判断的地方。

顺手教训

  • 构建产物目录同时是 git clone 时,会被 VSCode 当独立仓库单列。父仓库的 .gitignore 拦不住它——.gitignore 管的是”父仓库不跟踪它”,而 VSCode 的嵌套仓库检测是另一套逻辑。要用 git.ignoredRepositories(配在项目 .vscode/settings.json 里,只影响该工作区)。
  • git diff --name-only 的中文路径默认被转义成 \345\275\225 形式,直接喂给 PowerShell 的 Test-Path 会报 “Illegal characters in path”。加 git -c core.quotepath=false 即可。
  • 判断”是不是纯格式化”的快法:git diff -U0 | grep '^@@' 取所有 hunk 起始行,看是否全部落在文件头几行。本次 305 个 hunk 全在第 1–3 行,正文零改动,一眼定性。比逐个文件看 diff 快一个量级。
  • git diff --ignore-cr-at-eol / -w 是排除行尾/空白噪声的第一道筛。本次一开始满屏 “LF will be replaced by CRLF” 警告,很容易误以为是行尾问题——加这两个开关一比,改动数一点没少,当场排除。
  • PowerShell 的 ConvertFrom-Json 读 obsidian.json 会因编码报错(中文被按 GBK 读成乱码 + 反斜杠转义非法),但报错信息里把原始 JSON 整段打出来了,反而直接给出了答案。歪打正着不可复制,正规做法是 [IO.File]::ReadAllText($p, [Text.Encoding]::UTF8) 再解析。

下次改进

  • 遇到”第二次发生”,第一个动作固定为比时间戳(现象 mtime vs 上次修复时间),而不是复用上次结论。
  • 写归因类记忆时,连证据一起写(“凭什么这么判”),让它可被复核;只写结论的归因等于给未来的自己埋雷。
  • 怀疑某程序改了文件,先查该程序的作用域清单(vault 列表 / 打开的项目 / 监听的路径),再看 diff 形态。
  • 在可重建目录里看到”损坏”,先确认它是源还是派生物;三个信号:在 .gitignore 里、脚本自动克隆、同步时 reset --hard。
  • 反复要求人做同一个判断的界面元素,优先考虑”让它别出现”,而不是”每次判断得更快”。

关联文档

  • ⭐ 有意改动不是故障_v1 —— 直系母本,本篇是它的再验与扩容:那篇的三类归因(用户有意改 / 协作方越界 / 无害格式化)没覆盖本次这类——自动化流水线的设计产物,已回补为第四类;同时那篇「用一个真实的模式去套了一个不属于它的现象」在本次原样复现(Obsidian 确实会重写 frontmatter,于是这批也被归给它)
  • ⭐ 全站文字截断体检_检测工具本身要先被证伪_v1 —— 同族第三次现形:那篇是「检测工具的输出要先证伪」,ping通不等于路通_fake-ip假信号与节点带宽实测选型_v1 是「诊断信号要打在真实链路上」,本篇是「你自己写下的历史归因要先证伪」——三条合起来覆盖了工具、信号、记忆三个信息源
  • ⚠️ ping通不等于路通_fake-ip假信号与节点带宽实测选型_v1 —— 前一天的同族案例:那次是 ping 永远绿导致误判链路健康,本次是记忆永远在导致误判故障来源;共同点是最方便取用的那个信号最不可信
  • 生成物不入git_v1 —— 相邻的 git 资产纪律:那条管「可重建的生成物不进版本库」,本篇管「可重建的生成物出现在版本控制界面里时,别把它当待办」;本例的 kb-content 正是被正确 gitignore 了,却仍被编辑器摆进提交位
  • 变通方案不等于故障点_v1 —— 同族错误归因:那条管「别从产物形态反推故障位置」,与本篇 insight 3「别从 diff 形态反推是谁改的」是同一条在不同场景的投影
  • 复盘事实先行原则_v1 —— 本篇顶部事实冻结区按它执行;本次尤其重要,因为要冻结的事实里包含「我自己上一条结论是错的」
  • 09_平台工程索引 —— 平台工程区入口
类型/平台工程主题/git主题/错误归因