# 提案 · 61 个脚本仍手写入口守卫，而仓里的共用真源逐字说这些写法是错的

> 🔴 **标题从「36 个」改成「61 个」（2026-09-11 §32 重取）** —— ⛔ 不是原读数编了，
> 是**判据的召回缺口**：原判据的正则只写了单方向、形态表只有三种。见下方执行状态。
>
> 🔴 **⛔ 但「61 个」≠「61 个待修」（2026-09-14 定案，见 §3.0）** —— 这 61 个**全部**落在
> DS 侧 2026-08-27/08-28 明文「**刻意不含**」在 `is-cli-entry` 治理面内的那批（非随包脚本）。
> **标题这个数是一个计数，⛔ 不是一个工作量。** 本处方剩下的可做项只有 §3.1 的 **P3′**。

- **执行状态（2026-09-14 · 最新）**：🔴 **P1/P2 判定不做**（§3.0，理由是它们的存在理由被
  DS 侧三处明文裁定推翻，而 lab 此前**零处知情** —— `AGENTS` `lab:N54` 形态又一次命中）。
  ✅ **P4 已做**：DS 真源头注释补上 F4–F6 + 把「六种」下沉成 F1–F6 逐一端到端见证
  （DS `3ac0d15a`，`tests/is-cli-entry.test.ts` 10 → 16 passed）⇒ 见 §2.5。
  ✅ **P3′ 已做**（DS `ddebae9d`，2026-09-14）：`tests/packaged-cli-entry-guards.test.ts`，11 条内建控制 ·
  五臂造故障各真红过 · 全量 vitest 206 files / 3472 passed。⇒ 见 §3.2。
  🔴 **⛔ 但「存量 0、上闸当天绿」那半句是错的** —— 存量按 `files[]` **字面**口径是 **3**，
  详见 §3.2 的订正段。**本处方到此全部执行完毕，⛔ 无剩余可做项。**
- **执行状态（2026-09-11 · 上一轮）**：✅ **已重取（§32，DS pin `47f80084`）** —— 读数见 §0，
  量具 = lab `metrics/cli-entry-guard-forms.mjs`（20 条内建控制，两 pin 各跑一次，delta 逐位闭合）。
  - 🔴 **真实缺口 61，⛔ 不是 36 —— 低估 25 个（+69%）**。两处原因，⛔ 分开记：
    - **+5** = 原判据正则**只写了一个方向**，漏掉左右颠倒的 `fileURLToPath(import.meta.url) === process.argv[1]`
      （5 处：`figma-sync/generate-composition-exports.mjs:141` · `figma-sync/generate-icon-metadata-export.mjs:54` ·
      `figma-sync/generate-token-exports.mjs:508` · `scripts/audit-composition-exports.mjs:72` ·
      `scripts/audit-token-exports.mjs:77`）⇒ 同口径真值 **41**。
    - **+20** = **三种共用真源头注释没登记的错写法**（新增 §2.5），造故障实测同病。
  - ✅ **原登记的 36 没编** —— 把原 §4 那段判据在 @`8791091f` 上重跑，逐位复现
    `{total:156, shared:24, hand:36, none:96}`。⇒ 按 `AGENTS §2.24`，这只证「报的数没编」，
    ⛔ 不证判据对；判据的召回缺口由 lab 侧独立量具查出。
  - ✅ **§5 那句「这个提案的分母一条都没缩」实测为真**：§2 点名的 14 个「已被 fixture 跑过」
    的脚本，**14/14 仍在手写桶**，逐条带形态与行号（见 lab
    `docs/2026-09-09-ds-slim-optimize-review.md` §32.3 ④）。
  - 🟢 **新增面零缺口**：`8791091f..47f80084` 新增的 9 个 `scripts/` 顶层脚本
    = 5 个用共用真源 · 4 个无守卫 · **0 个手写错写法**。
    ⛔ **但这不等于「机制已守住」** —— §3 的 **P3 那条闸至今没上**，现在的零缺口靠自觉。
  - 🔴 **上一轮那次重取的 96 已作废，⛔ 别再引用**（是单位错：数的是 `import.meta.url` 的出现，
    而它大量用于 `fileURLToPath()` 取 `__dirname`、与入口守卫无关）。
    ⚠️ **巧合提醒**：下方 §4 的 `none` 在 @`8791091f` 恰好**也是 96** ⇒ 两个 96 含义完全不同
    （`AGENTS §2.14`：数值接近最危险，同值必须带限定词）。

> **来路**：本轮修 `audit-gate-mount-declaration.mjs` 的裸 `main()`（第三例，DS `8791091f`）时
> 顺手普查出来的。⛔ 不在那一批的承诺内，独立立案。
>
> ⚠️ **先说清这个提案不是什么**：⛔ 它**不是**「36 条闸现在是坏的」。
> 我第一版框架就是那么写的，量完随包子集后**自己推翻了**（见 §1 的 B）。

---

## 0 现取读数（⛔ 全部本轮单跑，判据在 §4）

> 🔴 **A 行已被 2026-09-11 §32 重取取代**（原文保留在下方 A-old，⛔ 不删，留痕）。
> 现行读数 = **A-new**，量具 `metrics/cli-entry-guard-forms.mjs`，同一量具在两 pin 各跑一次。

| # | 读数 |
|---|---|
| **A-new** | **@`47f80084`（2026-09-11 现取）· `scripts/`+`figma-sync/` 顶层 `.mjs` 共 165**：用共用 `isCliEntry` 的 **29** · **手写的 61**（其中命中原三形态的 41 · 只命中新三形态的 20）· 完全无守卫的 **75** · 既用共用又留手写的 **0**。<br>@`8791091f`（原取数时点）同量具 = `165→156 / 29→24 / 61 / 75→71`，**手写数两 pin 都是 61、零离场零新进** ⇒ ⛔ 这个缺口本轮没动过。<br>逐条清单（61 行，各带形态与行号）见 lab `docs/2026-09-09-ds-slim-optimize-review.md` §32.4 |
| **A-old** | ~~`scripts/` + `figma-sync/` 共 **156** 个 `.mjs`：用共用 `isCliEntry` 的 **24** · 手写那三种被点名为错的写法之一的 **36** · 完全无守卫的 **96**~~ ⇒ **数没编**（旧 pin 逐位复现），但**召回有缺口**：只算原三形态且补上左右颠倒写法后同口径是 **41**，⛔ 不是 36 |
| **B** | 🔴 **那 36 个里，随包（命中 `package.json` `files[]`）的是 0** —— 16 条随包脚本早已全部迁到共用真源（`shared = 24` 已含它们）⇒ **`is-cli-entry.mjs` 头注释描述的那条 pnpm-symlink 咬人路径对这 36 个不适用** |
| **C** | 36 个里有 **14** 个被 fixture 测试真跑过（逐个点名见 §2） |
| **D** | 🔴 那 14 个**现在能工作，靠的是测试 harness 替它们 realpath** —— `tests/lib/gate-fixture-root.ts:29` 逐字：「**fixture root 必须 realpath，否则手写入口守卫的闸恒假、整个不执行**」；`:37` 逐字点出病根「`mkdtempSync(tmpdir())` 在 macOS 返回 `/var/folders/…`，而 **`/var` 自己是 symlink**」；`:42` 逐字给了实测「`scripts/audit-consumer-contract.mjs`，2026-08-27：不 realpath ⇒ `EXIT=0` 且 `STDOUT_LEN=0`；realpath 后 ⇒ 5 行自印 + `✅ PASS`」 |
| **E** | 守卫写错的失效形态实测（本轮在 `audit-gate-mount-declaration.mjs` 上造故障）：**经 symlink 调用 ⇒ `EXIT=0` 且 stdout+stderr 共 0 行**；**直接调用 ⇒ 正常 2 行** ⇒ **只在 symlink 路径上现形，直接调用完全看不见** |
| **F** | `is-cli-entry.mjs` 头注释逐字：「本仓 16 条随包脚本各自手写过这个守卫，**实测 15 条是错的**」+ 「`node --preserve-symlinks` **不能**修它」 |

---

## 1 这个提案的真实命题（⛔ 别放大也别缩小）

**A ⇒ 补偿放错了层。** 那 14 个脚本的守卫本身是错的，让它们能跑的是 harness 里一行
`realpathSync`。⇒ 判据是「**谁写新 harness 忘了 realpath，那批闸就整片静默恒绿**」——
而 D 的读数说这**已经真发生过一次**（2026-08-27，`audit-consumer-contract.mjs`，
`STDOUT_LEN=0`）。它被修在 harness 侧，⛔ 没修在脚本侧。

**B ⇒ 紧迫性比第一眼低。** 随包的 0 个 ⇒ consumer 侧不受影响，
`is-cli-entry.mjs` 头注释里那条「下游 CI 绿了 19 天」的代价对这 36 个不成立。

**E ⇒ 但它的失效形态是本仓最危险的那一类。** 不是红、不是崩，是
**退出码 0 + 输出全空**。按 `AGENTS.md` 第 25 条的族谱，这是「恒绿 = 零信息」，
且比恒红更难发现 —— 它长得像「这条闸这次没什么要说的」。

---

## 2 那 14 个「已被 fixture 跑过」的（= 优先批）

```
scripts/audit-consumer-contract.mjs              ← tests/audit-consumer-contract-cli.test.ts
scripts/audit-deliverable-open-questions.mjs     ← tests/audit-handoff-deliverable-sections-cli.test.ts
scripts/audit-demo-css-page-scope.mjs            ← tests/audit-demo-css-page-scope-cli.test.ts
scripts/audit-doc-shape.mjs                      ← tests/audit-doc-shape-cli.test.ts, gate-regression-face-inventory
scripts/audit-exports-types-contract.mjs         ← tests/audit-exports-types-contract-cli.test.ts
scripts/audit-figma-env-single-source.mjs        ← tests/gate-regression-face-inventory.test.ts
scripts/audit-gate-ci-parity.mjs                 ← tests/audit-gate-ci-parity-cli.test.ts
scripts/audit-handoff-deliverable-sections.mjs   ← tests/audit-handoff-deliverable-sections-cli.test.ts
scripts/audit-layout-tokens.mjs                  ← tests/audit-layout-tokens-cli.test.ts
scripts/audit-page-recipes.mjs                   ← tests/audit-page-recipes-cli.test.ts
scripts/audit-prop-naming.mjs                    ← tests/audit-prop-naming-cli.test.ts
scripts/audit-rule-inventory.mjs                 ← tests/audit-rule-inventory-cli.test.ts
scripts/audit-style-contract.mjs                 ← tests/gate-regression-face-inventory.test.ts
scripts/generate-component-affordances.mjs       ← tests/audit-component-affordances.test.ts
```

⚠️ **`audit-doc-shape` / `audit-rule-inventory` / `audit-handoff-deliverable-sections` 三条
就在「新建/改判据后必跑」那张清单里** ⇒ 它们静默恒绿的后果是「必跑闸报了绿，而它其实没跑」。

---

## 2.5 🔴 共用真源头注释【没登记】的三种错写法（2026-09-11 §32 新增，共 20 个文件）

`scripts/lib/is-cli-entry.mjs` 头注释逐字枚举了**三种**错写法。lab 侧量具做双向夹逼
（一个刻意从宽、一个刻意从严，差集逐条解释）时，从差集里逼出**另外三种**：

| 形态 | 写法 | 顶层面命中 |
|---|---|---:|
| **F4** | `process.argv[1] === new URL(import.meta.url).pathname` | **16** |
| **F5** | `import.meta.url === pathToFileURL(process.argv[1]).href` | 2 |
| **F6** | `resolve(process.argv[1]) === fileURLToPath(import.meta.url)`（原 F3 少一层 `resolve`） | 2 |

**同病是造故障实测的，⛔ 不是推理**（六形态各建最小复现，直接调用 vs 经真 symlink 调用）：

| 形态 | 直接调用 | 经 symlink 调用 |
|---|---|---|
| 原三形态（任一） | 正常跑 | **EXIT=0 + 输出全空** |
| **F4 / F5 / F6** | 正常跑 | **EXIT=0 + 输出全空** |
| 共用真源的正确写法（**阳性对照**） | 正常跑 | **正常跑** ✅ |

⇒ 阳性对照两臂都跑 ⇒ 装置活着，那些「空」⛔ 不是装置死值。
⇒ **病根与原三形态完全相同**：`argv[1]` 不解析 symlink，而 `import.meta.url` 已过 realpath；
`new URL().pathname` 与 `pathToFileURL().href` **同样不解析 symlink**。
⚠️ **F4 还多一层病**：`.pathname` 保留 URL 百分号编码（`%20`）⇒ 路径含空格时
**即使完全没有 symlink 也会失配**。

⇒ ✅ **已回改 DS 真源（2026-09-14，DS `3ac0d15a`，已推 Gitea + GitHub）**：
头注释补上 F4–F6，并**显式写清「上面那三种不因此过期」** —— 仓内多处逐字写着「三种」，
指的是 F1–F3 那条**历史事实**（本仓 16 条随包脚本实测真踩过的），⛔ 不是「错写法总共三种」
⇒ 那 5 处引用（`audit-gate-regression-ratchet.mjs:114` · `tests/audit-consumer-contract-cli.test.ts:20` ·
`tests/is-cli-entry.test.ts:58/:199` · `tests/lib/gate-fixture-root.ts:39`）**一处没改**。

⇒ 🟢 **同批把「六种」下沉成机制，⛔ 没留在注释里**：DS 侧原本只有**一个**阴性对照用例
（写死 F3），⇒ 头注释若说六种而执行面只见证一种，就是「登记了但没机制」。
现参数化成 **F1–F6 逐一**端到端见证（真 symlink · 两臂：symlink 侧退化 + 直连侧照常跑
⇒ 挡住「替身写崩 ⇒ 两侧都不跑」被误读成「形态同病」），
另加一条 **F4 独有的第二层病**：`.pathname` 保留 `%20` ⇒ **路径含空格时零 symlink 也失配**
（含空格 fixture 实证 + 真守卫阳性对照）。`tests/is-cli-entry.test.ts` **10 → 16 passed**。
⇒ **F4/F5/F6 的同病结论现在有 DS 侧自己的可复核读数**，⛔ 不再只是 lab 的造故障转述。

---

## 3 处方（⛔ 分批，别一次改 61 个）

### 🔴 3.0 P1/P2 的存在理由已被推翻（2026-09-14 实测）—— ⛔ 别照 P1 动手

**接手前跑五行块，第四行当场命中**：DS 侧 **2026-08-27/08-28** 就对这批脚本出过明文裁定，
**早于本处方 11 天**，而 **lab 零处知情**。三处独立登记，口径一致：

| 出处 | 逐字 |
|---|---|
| `tests/lib/gate-fixture-root.ts:44-47` | 「[[INFRA-F140]] **只覆盖 16 条随包脚本**，而本 harness 的候选池里 **14 条非随包闸仍用原始守卫**……⛔ 那 14 条**不是缺陷待修**：它们不随包、只从仓库根跑，原始守卫在那儿不会假」 |
| `docs/internal/backlog.md:749`（INFRA-F138 entry） | 同句；同段上文另有 「⛔ **别为了让 harness 能用去改活闸取 root 的方式**」 |
| `docs/internal/STATUS-CHANGELOG.md:3415` | 同句 **＋** 「**[[INFRA-F140]] 的治理面（16 条随包 CLI）刻意不含它们**」 |
| `tests/audit-handoff-deliverable-sections-cli.test.ts:42-43` | 「属 [[INFRA-F138]] 记的『14 条非随包闸仍用原始守卫』那一档（**⛔ 那不是缺陷** —— 它们只从仓库根跑）」 |

⇒ **owner 2026-08-26 拍的射程逐字也是「15/16 条随包 CLI」**（`STATUS-CHANGELOG.md:3710`）
⇒ 非随包那批**从未被拍过**，是被**刻意**排除的。

**lab 侧独立复核那条理由（⛔ 不因为「对方写了理由」就采信，`AGENTS` 第 20 条推论一）**：

| DS 的理由 | lab 现取 | 成不成立 |
|---|---|---|
| 「不随包」 | `files[]` glob 口径 **14/14 不在**；consumer 面 3 处命中**全是自包含镜像/提及**（`templates/consumer-product/.githooks/` 那份是零依赖镜像），⛔ 无一调用 DS 真源脚本 | ✅ 成立 |
| 「只从仓库根跑」 | 14/14 由 `package.json` scripts 调用（仓库根相对路径）· 11/14 另挂 `.husky/` · consumer / CI workflow 侧**零调用路径** | ✅ 成立 |

🟢 **并补一个本处方零处回答过的读数**：新发现的 **F4–F6 在随包面（治理面内）零命中**
—— `scripts/lib/` 23 份 + `scripts/mockup-rules/` 18 份里 `argv[1]` 只命中 `is-cli-entry.mjs` 自己；
随包顶层 23 份全部 `isCliEntry` 或无守卫（量具 `recursiveExtra.extraHand = []`，
并用独立 grep + 阳性对照证过判定面非空）⇒ **治理面内无缺口**。
🔴 **⛔ 但这句必须带口径名 —— 它量的是「`scripts/` 随包子集」口径，⛔ 不是 `files[]` 口径**
（后者还含一整行 `"templates"`，那里有 3 处手写守卫）。2026-09-14 落地 P3′ 时实测推翻，**详见 §3.2**。

🔴 **但 ⛔ 别把这读成「处方错了」——两句射程不同，都成立**：
DS 说的是「**生产路径**上不会假」；本处方 §1 说的是「**测试路径**上已经假过一次
（2026-08-27 `audit-consumer-contract`，`EXIT=0` + `STDOUT_LEN=0`），补偿被放在 harness 层」。
⇒ 真实分歧只在**应然**：值不值得为「未来写 harness 的人再忘一次 realpath」去改 14 条活闸。
**DS 侧已经答了（刻意不含）+ owner 拍过射程 ⇒ 默认执行 DS 的裁定。**

⇒ 🔴 **裁定：P1 / P2 不做**（lab 2026-09-14 自拍 —— ⛔ 不进 `decision-queue`：
不扩权 · 不需 owner 独有的东西 · ⛔ 非真源冲突，DS 真源明确，偏离的是本处方的前提）。

**重开条件（可机检，⛔ 不是「以后再看」）** —— 逐字取自 DS 自己写的那条理由的两个前件：
1. 那 14 条里**任何一条被加进 `package.json` 的 `files[]`**
   （DS `gate-fixture-root.ts:81` 逐字先写过：「一条原始守卫的闸若哪天被加进 `files[]`，
   本 harness 的绿**不能**当它的凭据」）；或
2. 出现**任何经 symlink 的调用路径**（consumer 模板 / CI / 本地联调软链）。
⇒ 任一成立 ⇒ 那一条当场从「刻意不治理」变成真缺陷，按 P1 的四条纪律换。

### 3.1 仍然成立的那一批

| 批 | 动什么 | 为什么这样切 |
|---|---|---|
| ⛔ ~~**P1**~~ | ~~那 **14** 个（§2）换成 `isCliEntry`~~ | 🔴 **2026-09-14 判定不做，见 §3.0** —— DS 侧 08-27 明文「刻意不含」。重开条件在 §3.0 末尾，可机检 |
| ⛔ ~~**P2**~~ | ~~余 **47** 个~~ | 🔴 同上。⚠️ P2 那 47 个**同样全在非随包顶层面**，落在同一条裁定内 |
| ✅ **P3′** | **只守随包面**：`files[]` 里的脚本不许手写六形态之一 | **已做**（DS `ddebae9d`，2026-09-14）⇒ 见 §3.2。🔴 **原 P3（扫 61 条存量）随 §3.0 一起作废** —— 那 61 条是刻意不治理的，拿闸去扫它们等于给一条已被裁定的路上棘轮。改守治理面本身 |
| ✅ **P4** | ~~补 DS 真源头注释的形态表~~ | **已做**（DS `3ac0d15a`，2026-09-14）⇒ 见 §2.5 |

**P1/P2 的单条纪律（⛔ 每一条都要，缺一条就不算做完）**：
1. 改前先取**默认模式 + 每个 flag** 的输出到文件（这批闸大多有 `--list` / `--json`）
2. 改后逐字 `diff` ⇒ 必须 `EXIT=0`
3. **经 symlink 调用一次**（`ln -sf <abs> /tmp/x.mjs && node /tmp/x.mjs`）⇒ 输出必须非空
   —— 这一条是**改对了才会有的读数**，⛔ 改之前它是空的
4. 该脚本自有的测试全绿

### P3′ 的判据草案（🔴 2026-09-14 改写 —— 分母从 61 换成 0）

**断言**：`package.json` 的 `files[]` 匹配到的 `.mjs` 里，出现六种手写守卫形态之一 ⇒ 红。

🟢 **为什么这个版本能上而原 P3 不能**：
- **分母是治理面本身**（随包脚本），⛔ 不是那 61 条刻意不治理的；
- ~~**存量实测 0**（2026-09-14，见 §3.0 那条新读数）⇒ **上闸当天就绿、⛔ 不需要具名豁免表**~~
  🔴 **这半句错了，⛔ 别再引用 —— 真值是 3**（2026-09-14 落地时实测推翻，见 §3.2）。
  ⇒ 摩擦仍≈0（3 条具名豁免、上闸当天绿），按 `decision-queue` §1.1 仍不构成取舍 ⇒ agent 自拍这一步成立；
- **它守的风险是实证过的**：§0 读数 F 逐字记着随包假绿让**下游 CI 绿了 19 天**
  ⇒ 与「本仓多红一条」不是一个量级；
- ⚠️ 它同时**自动承载 §3.0 的重开条件 1** —— 那 14 条里哪条被加进 `files[]`，这条闸当场红
  ⇒ 重开条件从「靠下一个人记得」变成机制（`AGENTS` 第 26 条：没有栏位的纪律 = 每轮重赌一次自觉）。

<details><summary>原 P3 草案（⛔ 已作废，留痕备查）</summary>

**断言**：`scripts/` + `figma-sync/` 下 `.mjs` 里出现**那六种**手写守卫形态之一 ⇒ 红，
除具名豁免表里的存量。表**只许缩**（同 `KNOWN_SILENT` / `audit-layout-tokens` 范式）。

- 判据串**逐字取自** `is-cli-entry.mjs` 头注释里写死的三个 ❌ 形态，**外加 §2.5 的 F4–F6**
  （那三种造故障实测同病，⛔ 不是发明的形态）。🔴 **每个形态都要写成双向**
  （`a === b` 与 `b === a`）—— 原 §4 判据只写单方向，就是这么漏了 5 个。
- 🔴 **存量基线用 61，⛔ 不是 36** ⇒ 否则上闸当天就红 25 条（那 25 条不在旧基线里）。
  P1/P2 每做一条就删一行。
- ⚠️ 落点要先答一个问题：**放测试层还是 npm 闸层？** 这批脚本已有 `gate-regression-face`
  一族在数覆盖面 ⇒ ⛔ 先查它是否已有位置可挂，别新造一条闸（本仓已点名过
  「加规则不加闸会让 `prose:gate` 更差」）
- ⚠️ **判据面要去注释再切**，且**只删纯注释行、⛔ 不删行尾注释** —— 共用真源
  `is-cli-entry.mjs` 自己的头注释里逐字写着那三个错形态（不去注释它会被自己的闸判红），
  而形态 F2 的判据本体含 `file://`（按 `//` 切行尾会把判据自己截断 ⇒ 假阴）。
  ⛔ 也别用「跨行块注释状态机」：lab 实测它会被 `// … demos/*.tsx` 这类**行注释里的 `/*`**
  永久卡住，之后整个文件被清空、分类静默给出一个「看起来合理」的结果。
- ⛔ **别把 P3 先做**：61 条存量在的时候它只是把现状拍成照片；先做 P1 让表能真的缩起来

</details>

### 3.2 P3′ 已落地（DS `ddebae9d`，2026-09-14）—— 并带回一条把自己前提推翻的读数

**产物**：DS `tests/packaged-cli-entry-guards.test.ts`（313 行，11 条内建控制）。

🔴 **先说被推翻的那半句：「存量实测 0」是在一个比判据面窄的面上量的。**

| | 口径 | 存量 |
|---|---|---|
| §3.0 那条读数 | `scripts/` 顶层 + `scripts/lib/` + `scripts/mockup-rules/` | **0** ✅ 那个面上确实是 0 |
| P3′ 断言的字面 | `files[]` **glob 字面展开** ∩ tracked ∩ `.mjs` = **53 份** | 🔴 **3** |

差的是 **`files[]` 里有一整行 `"templates"`** —— 而 `templates/` 从没被那次普查量过。
命中三处（逐行现取）：`templates/consumer-product/.githooks/audit-handoff-deliverable-sections.mjs:144`（F1）·
`.githooks/audit-mockup-handoff-evidence.mjs:121`（F1）· `docs/scripts/build-request-index.mjs:206`（F5）。

⇒ **`AGENTS §2.17 推论一`（面的枚举必须穷举）+ `§2.4`（引用分母必须带口径名）同时命中**：
那句「存量 0」**没标口径名**，于是被下一格（我自己）当成了 `files[]` 口径的读数。
⚠️ **⛔ 别把它读成「§3.0 那条读数错了」** —— 它在它自己的口径内精确正确，
错的是**它被写成了一个不带口径名的断言**，而 P3′ 的断言面比它宽。

**处置：3 条进具名豁免表（只许缩），⛔ 不是待修。** 理由逐条实测（⛔ 不是「看起来没事」）：
这 3 份**随包但不从 `node_modules` 跑** —— `SETUP_GUIDE.md:231`/`:353` 逐字 `cp` 到消费仓，
`:262` 逐字 `node .githooks/<x>.mjs`、`docs/README.md:13` 逐字 `node docs/scripts/<x>.mjs`
⇒ **仓根相对调用、零 symlink** ⇒ 与 §3.0 判「那 14 条非随包闸不是缺陷待修」**同构的理由**。
⛔ **没把 `templates/` 整个砍出判定面** —— 砍掉则该目录将来新增的手写守卫**看不见**（fail-open）；
留在面内 + 具名豁免 ⇒ 新增的会被抓到，而表只许缩（`MN6` 钉着：某条改好了就必须当场删表行）。
**重开条件**：这 3 份里任一出现经 symlink 的调用路径（scaffold 把 `cp` 改成 `ln -s`，
或 consumer 改成直接跑 `node_modules/.../templates/…`）⇒ 当场从豁免转真缺陷。

**落点定在测试层**（处方 §3 原问题的答案，⛔ 不是「测试写起来省事」）：
- `gate-regression-face` 一族现有三条语义各是**挂载声明 / 接线覆盖 / 量具**，**没有一条管守卫形态**
  ⇒ 无现成位置；
- 本断言是**纯仓库形态断言** —— 没有 `main()`、没有 CLI 入口、没有「接线」可言，
  而 `audit-gate-regression-ratchet` 的分母正是「blocking 闸的接线零覆盖条数」
  ⇒ 造一条没有接线的 `audit-*` 去进那个分母，是塞给棘轮一个它度量不了的东西；
- 挂载实测**比条件块闸更强**：`.husky/pre-commit:8-9` 的 `pnpm test` **不在任何条件块里**
  （每次 commit 无条件跑）+ `pr-checks.yml:67-68`。
- ✅ 落地后现取：`audit:gate-mount-declaration` `EXIT=0`（本文件不在其扫描面）·
  `audit:gate-regression-ratchet` `EXIT=0`（持平 40）⇒ 上面两条推理**有读数了，⛔ 不只是推理**。

**验证（⛔ 跑一遍绿不算验证 —— 五臂造故障各真红过一次）**：

| 臂 | 注入 | 期望 | 实测 |
|---|---|---|---|
| A | 非豁免随包脚本插 F1 **正向** | 红并点名 | `EXIT=1` · `scripts/audit-stale-anchors.mjs → F1` · 其余 10 passed |
| B | 同处插 F5 **反向**（`b === a`） | 红并点名 F5 | `EXIT=1` · `→ F5` ⇒ **双向判据真的生效** |
| C | 同处插进**纯注释行** | **绿** | `EXIT=0` / 11 passed ⇒ 去注释的阴性对照，判据**⛔ 不是恒红** |
| D | 豁免项改成 `isCliEntry` | `MN6` 红并要求删表行 | `EXIT=1` · 逐字点名 ⇒ 只许缩成立 |
| E | `files[]` 置空 | **抛**（fail closed） | 3 条依赖判定面的断言全抛「判定面塌了」⇒ ⛔ 不降级成「0 命中 ⇒ 绿」 |

- **双向夹逼收敛**（`AGENTS §3.7`）：宽口径（任何 `argv[1]` × `import.meta.url` 同行比较）**3 处**
  = 严口径（六形态双向）**3 处**，**差集空** ⇒ 形态枚举在这个面上穷尽。
- 🟢 **`MN1`/`MN2` 用真源自身做了一对完美对照**：`is-cli-entry.mjs` **去注释前 F1–F6 六种全中**
  （= 它头注释里那六行字面）、**去注释后 0** ⇒ 一次同时钉住「六个形态串都写对了」与
  「去注释这一步有区分力」。⛔ 只留 `MN1`（零命中）分不清「去注释生效了」与「六串全写错了」。
- 全量 `vitest` **206 files / 3472 passed / 0 failed**，`EXIT=0`。
- 触发块**机械算**（52 块逐个真 `grep -E` 判 + 阳性/阴性双对照 + **「待人工」桶当场读完**
  —— 那条含 shell 变量的 `$FIGMA_DATA_BLOCK_PATTERN` 实判不命中）⇒ 命中 3 块，全部 `EXIT=0`。
- 还原全走 `cp` 备份 + `cmp`，⛔ 未用 `git checkout --`；`FXINJECT` 探针零残留。

🔴 **本格自己踩的两次「装置失败伪装成读数」（⛔ 两次都靠阳性对照抓到，不是靠复盘想出来）**：
1. `perl -0pi` 清 NUL 字节 —— **`-0` 正是把 NUL 设成记录分隔符**，替换结构上不可能命中。
   改后 NUL 数与备份**都是 2**，若无阳性对照就会写成「已清干净」。修法 `-0777`。
2. `grep -c $'\0'` 在 zsh 下 `$'\0'` 是**空串** ⇒ 走 `|| echo 0` 打出一个「看起来合理」的 `0`
   —— **第四种 0：命令失败的 0**（`AGENTS §2.28 推论一`）。改用 `tr -dc '\000' | wc -c` + 备份做阳性对照。

⚠️ 上面那段里「判据面要去注释再切」「每个形态写成双向」两条**对 P3′ 同样适用，⛔ 别丢**
（DS 真源头注释 2026-09-14 也已逐字写进这两条）。

---

## 4 复核命令（⛔ 判据别抄第二份，就跑这个）

🔴 **2026-09-11 §32 起换成受控量具，⛔ 别再用下方 §4-old 那段一次性 `node -e`**
（它只有单方向正则 + 只有三种形态 + 不去注释 ⇒ 已实测低估 25 个）：

```bash
# 在 lab 仓跑，参数是 DS 树的根（⛔ 建议用 git archive 导出的 pin，不读活工作树）
cd <lab 仓>
node metrics/cli-entry-guard-forms.mjs <DS 树根>
```

输出是 JSON，含 **20 条内建控制**（任一条失败即 `throw`、⛔ 不静默降级）、两栏形态口径
（`registeredOnly` 与处方原分母可比 · `allForms` 是真实缺口）、以及 61 条**逐行证据**。

```
# 2026-09-11 现取 @47f80084 ⇒ allForms:       { total:165, shared:29, handOnly:61, none:75, both:0 }
#                          ⇒ registeredOnly: { total:165, shared:29, handOnly:41, none:95, both:0 }
# 同量具 @8791091f        ⇒ allForms:       { total:156, shared:24, handOnly:61, none:71, both:0 }
```

<details><summary>§4-old：原一次性判据（⛔ 已停用，留痕备查）</summary>

```bash
cd <DS 仓>
node -e '
const fs=require("fs");
const BAD=[/process\.argv\[1\]\s*===\s*fileURLToPath\(import\.meta\.url\)/,
           /import\.meta\.url\s*===\s*`file:\/\/\$\{process\.argv\[1\]\}`/,
           /resolve\(process\.argv\[1\]\)\s*===\s*resolve\(fileURLToPath\(import\.meta\.url\)\)/];
let shared=0,hand=[],none=0,total=0;
for(const d of ["scripts","figma-sync"]) for(const f of fs.readdirSync(d)){
  if(!f.endsWith(".mjs"))continue; total++;
  const s=fs.readFileSync(d+"/"+f,"utf8");
  if(/isCliEntry\(/.test(s))shared++; else if(BAD.some(r=>r.test(s)))hand.push(d+"/"+f); else none++;}
console.log({total,shared,hand:hand.length,none});'
# 2026-09-08 现取 ⇒ { total: 156, shared: 24, hand: 36, none: 96 }
# 2026-09-11 在 @8791091f 上重跑，逐位复现同一组数 ⇒ 原读数没编，缺的是召回
```

</details>

⚠️ `none` **⛔ 不是缺陷** —— 一条从不给人 import 的纯 CLI 不需要守卫。
把它们算进来会把这个提案的分母吹成 140，那是假分母。

---

## 5 ⛔ 这个提案不能说的话

- ⛔ 不许说「61 条闸现在是坏的」—— 读数 B/D：随包 0 个，那 14 个 fixture 面
  **现在是工作的**（靠 harness 的 realpath）。命题是**补偿放错了层**，不是「现在就在漏」
- ⛔ 不许说「共用 `isCliEntry` 就万无一失」—— `gate-fixture-root.ts:78` 逐字提醒
  「realpath 买到的是什么、⛔ 不是什么：它只消除**测试环境**引入的 symlink」
- ⛔ 不许把 `none` 算进缺口（见 §4 末）
- ⛔ 不许说「本轮已修 61 个里的 1 个」—— 2026-09-08 那轮修的
  `audit-gate-mount-declaration.mjs` **原本连守卫都没有**（是裸顶层 + `process.exit`），
  它不在这 61 个里。⇒ 这个提案的分母**一条都没缩**（2026-09-11 §32 实测确认：
  §2 那 14 个 **14/14 仍在手写桶**，且手写数两 pin 都是 61、零离场零新进）
- 🔴 ⛔ **不许说「原登记的 36 是错的 / 编的」** —— 它在 @`8791091f` 上逐位复现。
  那是**判据的召回缺口**（正则只写单方向 + 形态表只有三种），⛔ 不是数据问题。
- 🔴 ⛔ **不许把两栏形态并池报一个数** —— 与原分母可比的是 **41**（只算原三形态），
  全形态是 **61**。说「现在有 61 个」而不标形态栏，读者会以为原来那个 36 差了 25。
- 🟢 ⛔ **也不许说「新增脚本零缺口 ⇒ 已经守住了」** —— P3 那条闸至今没上，
  现在的零缺口靠写代码的人自觉，**⛔ 不是机制**。
- 🔴 ⛔ **不许把「61」当成待修工作量**（2026-09-14 加）—— 见标题下那条与 §3.0。
  它是一个**计数**：61 条全在「刻意不治理」的非随包面上。
- 🔴 ⛔ **不许说「DS 那条裁定是错的」** —— lab 逐条复核过它的两个理由（不随包 / 只从仓库根跑），
  **两条都实测成立**。本处方 §1 的命题与它**射程不同**（测试路径 vs 生产路径），⛔ 不是互相反驳。
- 🔴 ⛔ **也不许说「这个提案白做了」** —— 它交付了三样：① F4–F6 三种此前无人登记的错写法
  （已进 DS 真源 + 有了 DS 侧自己的执行面）② 一条「六种形态逐一见证」的回归面
  ③ P3′ 那条闸（**已落地** DS `ddebae9d`；⛔ 别再说它「分母为 0」—— 真值 3，见 §3.2）。
  ⛔ 但也别反过来夸大：**P1/P2 那 61 条，一条没动、
  也不该动**。
