平台工程

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

入档: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」。依据两条,都不是直接证据:

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 实为 -# …+# …,即 BOM 被删。
  4. song-caption-mv-workflow/SKILL.md 的 frontmatter 被清空namedescription 两行消失,只剩 ---\n---。我当时说「这个 skill 已经废了」——也是错的,见下。
  5. 时间戳比对(决定性):305 个文件的 mtime 全部落在 2026-07-27 18:00,而 .obsidian/app.json 那次”修复”改于 16:44改动发生在修复之后——上次的归因与修复被同时证伪。
  6. 名册排除(决定性)%APPDATA%\obsidian\obsidian.json 里 Obsidian 只注册了一个库 E:\AIGC工作站\年轮平台测试\docskb-content 根本不在其中。Obsidian 不会自动扫描磁盘上的目录。

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

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

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

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

为什么必然复发

即:每次开发或构建都必然产生这批脏改动,且下一次 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主题/错误归因