---
share_worthy: yes
audience: UX, Dev
one_liner: grep 的读数有两种含义——得 0 可能是「字段根本不会出现」，得 N 可能是「N 处全是别的意思」，分辨它们只需回去读一次原文，不做就会给一个正确结论配上假论据。
evidence: 0d3cd6e1, fe5555a3, 499d32e2, 80fe9c89, d06f3d77, 723d6cf7, 8b226335, docs/internal/figma-technical-reference.md §Q30
---

# 「grep 得 0」的两种含义 —— 2026-09-15

> 本轮做了两件事：修掉 master 上一条别的 session 留下的红（acceptance 棘轮 +1）；
> 把 `INFRA-F106` 从「等 owner 去 Figma 重绑 75 个节点」一路查成「owner 终裁不改、整条删档」。
> 产出的通用经验集中在**取证**，不在产品代码——这已经是连续第三轮如此。

---

## 一 · 核心：同样是「grep 得 0」，一次骗了我，一次救了我

本轮出现两次「查某个东西、得 0」，形态一模一样，结论方向相反。

### 骗我的那次

要判断「M43.3 单独登记豁免自不自洽」，我去数同一份文件里已有多少条先例：

```js
const a = require('./…/acceptance-not-machine-checkable.json')
const e = a.entries || a.allow || a        // ← 这里
```

`a` 是数组，`a.entries` 是 `Array.prototype.entries` —— **函数，真值**。于是 `e` 成了那个函数，`.filter` 出空数组，打印「mockup-conventions 先例 **0** 条」。

**危险在方向**：交接 prompt 里已经写着「同族 M43 / M43.2 一样未分类，单独分类它不自洽」。「0 条先例」正好**印证**了这句话。如果我照它走，就会得出「不该登记 ⇒ 留红 ⇒ 报 owner 等着」，而真相是**同一份文件里躺着 13 条段级先例**，登记才是这个仓库的既有惯例。

⇒ 这是 [`一次性脚本才是不可靠的那环`] 那族的又一个实例，且再次命中它最贵的那个方向：**假读数朝着「去否定一个其实正确的东西」**。

### 救我的那次

后来要判断「那 75 个 TEXT 节点是不是共用一个 text style」（若共用，owner 改 1 次就够，不必改 75 次）。我在提取产物里查 style 引用字段，**得 0**。

这次我多问了一句：**「这个字段本来会出现吗？」**

```
grep -rlo 'styleId\|textStyleId\|styles"' figma-data/raw/components/ | wc -l
→ 0        # 全部 702 份组件文件，一次都没出现过
```

⇒ extractor **根本不导出 style 引用**。所以「没查到共用 style」**不携带任何信息**。

没这一步，我就会告诉 owner「它们不共用 style，只能一个个改」—— 一个**听起来有依据、实际凭空**的结论，而且会直接影响他愿不愿意动手。

### 判据（可直接照做）

> **任何「查 X，得 0」，在当成结论用之前，先跑一次阴性对照：
> 「X 这个东西，在这份数据里本来会出现吗？」**

两次的差别只有这一步。它很便宜——一条命令。

### 同族第三个实例：「查 X，得 **N**」也有两种含义（2026-09-15 下半场补）

上面两次都是「得 0」。同日下半场另一条 session 线踩到的是它的**镜像形态 —— 得 N**。

要判断「§M33 的单语违规是不是反复发生」，`grep -l 单语 work-logs/*.md` 得 **7 天命中**，
据此写下「双语违例反复踩坑、值得上闸」。**回去读原文后全部推翻**：7 处里 6 处是 M33
**要求的**产品 UI 正确单语（「通知文案改英文单语」「实际用英文单语 `Skip this release?`」），
真违规只有 05-26 一次，且当轮就改 skill 起手协议修掉了。

同一轮还踩了一次同形态的：看了**一个**消费仓有 symlink，就外推「其余仓都没接入」——
实际 6 个仓里 4 个早就接了，**两头都不对**。

⇒ **判据扩写**：
> 「查 X **得 0**」要问「**X 本来会出现吗**」；
> 「查 X **得 N**」要问「**这 N 处，每一处真的是我以为的那个意思吗**」。

两者是同一个病：**把检索读数直接当成语义结论，跳过了读原文那一步**。代价方向也一样危险 ——
它让一个凭空的前提**听起来有证据**（「7 天都在踩」比「我觉得该上闸」有说服力得多），
于是没人会回头查它。

⚠️ **上闸这件事本身没错，错的是给它编了一个假理由**：§M33 零覆盖是真的（backlog 逐字记着
「没有任何闸自陈它」），**那才是**该写进 commit message 的依据。⇒ 结论对，不等于论据对；
论据错了，下一个人会照着那个错论据去扩用途。

---

## 二 · 提取产物反推真实结构：我把影响面多数了一倍

我先量出游离变量在 `figma-data/raw/components/` 出现 **100 次**，把「100 处 / 3 组件」写进 entry 并**已经 commit 了**（`fe5555a3`）。

下一轮要给 owner 出可执行清单时才发现：extractor 会把子 TEXT 节点的 fill **复制一份**到祖先 COMPONENT 的 `text` 聚合字段。实证：

```
slider 4934:7182 (COMPONENT).text.fills   hex #cccccc  v …/3010:117
  └ 后代 4934:7187 (TEXT).fills            hex #cccccc  v …/3010:117   ← 逐字相同
```

⇒ **real 75 + 派生镜像 25 = 100**。真实要动的是 75 个，不是 100 个。

两条教训：

1. **判据粒度要与现象粒度对齐**：我数的是**字符串出现次数**，而要回答的问题是**有几个节点**。两者在这份数据里差 1.33 倍，且不报错。
2. **这正是 `提取产物不是 Figma 真源` 那条的形态**，只是换了个面：以前踩的是「用 hex 反推变量身份」，这次是「用字符串计数反推节点数」。同一条规则，两个入口。

⇒ 已把两条一起写进 `figma-technical-reference.md` §Q30，附机械判据（变量 id 带 40 位 hex 前缀 = 外部库变量）。

---

## 三 · 举证责任的方向，我一开始搞反了

owner 的常设默认是「**Figma 现状即设计意图、维持现状**」，要动已发布资产必须答出**具体受损方**。

而我这轮的实际路径是：查出影响面 → 记进 entry → 报「等你分诊」→ 他答「全改」→ 我就去出 75 个节点的清单。**整个过程里我没有一次停下来问「我能说出谁受损吗」。**

直到他反问「有绑定的必要吗？这只是梳理规则」，我回去检验，才发现三件事：

- code 侧早就是对的（`--text-2`），consumer 无感；
- 重绑库里那 75 个**修不好**产品 mockup 文件里的 1186 处引用（不同文件）—— 所以「防扩散」这个我心里默认的理由**根本不支持库侧改动**；
- 仓库里 `variable-map.mjs` 早有一个标着 `External / legacy` 的块，同族 3 条 —— 「外部游离变量，code 侧映射掉」**本来就是既有的、有意的处理方式**，F106 只是这个已知类别里的一个实例。

⇒ **该问的那句话不是「影响面多大」，是「谁因此受损」。** 前者我量得很起劲，后者一次没问。量级大小不构成改的理由。

---

## 四 · commit message 里写了一个我没验的数

`d06f3d77` 的 message 写着「grep 复核：backlog.md 里 INFRA-F106 命中数 1 → 0」。

实际：`[[INFRA-F106]]` **wiki-link 形态** 1 → 0（这才是我修的）；**纯文本** `INFRA-F106` 仍是 1（我自己新写的那句「已删档、别去找」）。

结论没错，但那行数字描述的不是我跑过的那条命令。硬规则 #9 的形态：**写进 message 的每个数字，必须是我刚执行的那条命令的输出，不是我以为它会输出的**。已推送，不改写历史，在对话里更正。

---

## 五 · 本轮验证有效、以后直接用的做法

1. **一次性量具自带已知向量自证。** 拆分脚本里写死 `real + mirror === 100`（100 来自独立的 `grep -o`），对不上就整批作废退出。它保证了 75/25 这个拆分没算漏。
2. **删除类操作走带前后断言的脚本，不用 sed 猜行号。** 删 F106 时断言了：起始行是 F106 标题 · 结束行的下一行是 `## Triggered` · 删完 Active 从 3 变 2 · `## Triggered` 上方仍是前一条 entry 的收尾行（防误删邻居）。
3. **写进文档的每条命令，先逐字跑一遍。** entry 里那两条「影响面」命令是先跑出 100 / 8+32+60，再抄进文档的。
4. **闸红先看它挂在哪几层。** `acceptance-gate-coverage` 在 `gate-chain` 里 ⇒ 同时是 `prepublishOnly` + master-push CI ⇒ 那条红不只是 commit 时烦人，是**发不了版**。这个事实改变了「要不要现在处理」的答案。

---

## 六 · 一件没做完的、如实留痕

**Progress 可能是红的，本轮未验。** 它有 16 个游离绑定节点、16 条 manifest、比对 `textColorHex`，但 `render-excused-fields.json` 里 **0 条**豁免（Slider 有 `figma-schema-gap`、Chart 有 `harness-verifier`）。判它现在红没红要跑全量 render-verification，那要起 dev server、会污染并行 session 的共享 `.vite` 缓存 ⇒ 没跑。

已如实写进 `figma-sync/variable-map.mjs` 的封路注释里标「未核」，**未立 backlog entry**（owner 2026-09-15 口径：不做的任务别留在 backlog）。

**另一件：`[[ID]]` 悬挂指针没有任何闸在守。** 删 F106 后六个闸全绿，那个悬挂引用是人工 grep 抓到的。同样未立项，只留记录。
