# INFRA-F96 扫描面扩展 —— 把生命周期闸从 `superpowers/plans/` 扩到三个指令型一次性产物目录

> **状态**：设计已 owner 认可（2026-08-05），转 `writing-plans`。
> **前身**：[`2026-08-03-infra-f96-plan-spec-lifecycle-design.md`](./2026-08-03-infra-f96-plan-spec-lifecycle-design.md) —— 本文只改**扫描面与形态判据**，不重新论证「为什么判据是可达性」（那条的逃逸方向分析在前身 §2，仍然成立且被本文复用）。
> **⚠️ 数字口径**：下列实测取自 **2026-08-04**（`git ls-tree` / `git grep` / 闸自印）。这些文件每工作日都在动，**引用前先自己重跑**，别抄本文（[[INFRA-F95]] 治的正是这个病）。

---

## 1. 缘起：它是 [[INFRA-F95]] 待做③ 的目的测试倒推出来的结论，不是「顺手再加个闸」

F95 待做③ 原措辞是「`docs/` 278 份，无用的、累赘的、重复的、冲突的全清」。2026-08-04 先按纪律测「这活能达到什么目的」，六个交付面里五个收到 **0**：

| 交付面 | 随 `docs/` 体量变化收到什么 |
|---|---|
| npm tarball | `package.json` `files[]` 只含 3 份指定 docs → **0** |
| `claude-design-bundle` → claude.ai | 16 份策展 + 生成物，三大件均不在内 → **0** |
| L-core onboarding（65.2 KB） | 三大件均不在内 → **0** |
| docs 站 | 构建链不读、不打包任何 `docs/*.md`（无 copy 步骤、无 `publicDir`）→ **0**。⚠️ 措辞要精确：`OverviewPage.vue` **链出** 7 个 `/docs/*.md`，但那 7 条今天是**死链**（实测 404，见 §1.2）—— 与体量无关，是另一条独立缺陷 |
| git clone | `.git` 152 MB；删除不缩历史。三份最大生成报告的**全部历史** diff 仅 3471 / 1023 / 6500 行 → **≈0** |
| AI grep 噪声 | 全仓分母下命中行占比 `token` 3.8% · `disabled` 6.8% · `Notification` 11% · `combobox` 19% → **唯一非零**，且其中最大一块 `docs/_archive/`（1.49 MB）**已可按路径前缀排除** |

并且**「再扫一遍」这条路已经走过一次并被填平**：`8237d609`（2026-07-23，`docs(archive): move historical documents out of live tree`）+ 配套 `_archive/superpowers/plans/2026-07-23-repo-slim-execution-brief.md` 做过全树人工清扫，建出 `docs/_archive/`。此后 live tree（除 `_archive`）：

| 日期 | live 份数 / 字节 |
|---|---|
| 2026-07-23（清扫当日） | 212 份 / 5.00 MB |
| 2026-08-04 | **229 份 / 5.75 MB（+15%，~93 KB/工作日）** |

真正零入站引用、可动的只有 **3 份 ≈ 27 KB = `docs/` 的 0.4%**（其中 2 份在本 spec 的扫描面内，第 3 份 `_spikes/t3-execution-report.md` 是记录型、按 §2 不收）。⇒ **字节维度的清理没有收货方**；唯一真实的病是 F96 已证的那一类：**已完成的东西留在 live tree 会继续提供指令**。治它的器械是闸，而闸已经存在，只差扫描面。

### 1.1 今天就有一个活病例（本设计的直接触发）

- `docs/_archive/_prompts/2026-07-23-history-archive-prune.prompt.md`（7.4 KB，**全仓 0 入站引用**）
- 它是**唯一**引用 `docs/_archive/superpowers/plans/2026-07-23-repo-slim-execution-brief.md` 的文件；
- 而那份 brief **已 100% 执行完**（范围 A 六项实测全部 gone · 范围 B 已落 `docs/PROJECT_MAP.md` §9 · §5 归档策略已执行成 `docs/_archive/`），却因为「有人指向」被 F96 判为在飞，留在 live tree 继续发指令：*「Codex：执行完范围 A/B 后，在此停下等 Owner 选定 §5 的策略」*。

一个死文件把一份已完成的指令钉在 live tree 上 —— F96 的病，发生在 F96 扫不到的目录里。

### 1.2 顺带查出的独立缺陷（**不在本 spec 的实施范围**，只登记在此避免丢失）

核 §1 那张表的「docs 站」一行时实测到：`playground/docs/pages/OverviewPage.vue` 的 Overview 卡片链出 **7 个 `/docs/*.md`**（`component-review-rules` · `component-generation-pitfalls` · `checkbox-pixel-review` · `radio-pixel-review` · `input-select-first-pass` · `figma-provenance-audit` · `2026-04-27-component-completeness-audit`，中英双语卡各一份）。7 个源文件在 `docs/` 里**都存在**，但：

- `playground-dist/docs/` **不存在**，`vite.playground.config.ts` 无 copy 步骤、无 `publicDir` → 站点产物里根本没有这些文件；
- href 写的是**绝对根路径** `/docs/…`，而站点部署在 `/tvu-design-system/playground-dist/` 子路径下 → 即使拷进去也解析不到。

**实测 2026-08-05（带阴阳对照，不是推断）**：站点首页 `200` · `index.html` `200`（阳性对照）· `…/playground-dist/docs/component-review-rules.md` **`404`** · 域名根 `/docs/component-review-rules.md` **`404`** · 故意不存在的路径 `404`（阴性对照）。

⇒ 两个叠加的 bug（缺 copy 步骤 + base path 写错），**7 条链接在已发布站点上全是死链**。修它要动 `.vue`（需 `VISUAL_COMMIT_APPROVED=1`）且与生命周期闸正交 → 按「根因牵出大缺口 ≠ 顺手做完」，标独立项报 owner，不并入本轮。

---

## 2. 扫描面：只收指令型，不收记录型

2026-08-04 全仓 basename 反查（排除自身与 `_archive`）：

| 目录 | 份数 | 零引用 | 性质 | 收不收 |
|---|---|---|---|---|
| `docs/internal/_prompts/` | 6 | **2** | 指令型（prompt 就是指令） | ✅ |
| `docs/internal/_plans/` | 8 | 0 | 指令型（pickup = 续跑指令） | ✅ |
| `docs/internal/_handoffs/` | 2 | 0 | 指令型（交接说明） | ✅ |
| `docs/internal/_spikes/` | 3 | 1 | 记录型（实验报告） | ❌ |
| `docs/internal/_reports/` | 2 | 0 | 记录型 | ❌ |
| `docs/internal/retrospection/` | 13 | **4** | 记录型（教训档） | ❌ |

**判据是「发不发指令」，不是「有没有人引用」。** 记录型文档不发指令，读它是 opt-in；`retrospection/` 那 4 份零引用**恰恰是正常态** —— 教训档本来就靠 grep 命中而非链接可达，把可达性判据套上去会逼人给每份教训档补一个假指针，是判据用错地方。

`docs/internal/_plans/README.md`（2026-07-17 立，REDUN-05）**逐字写着同一条规则**且判据字面相同（全仓 0 引用 → `git mv` 到 `_archive/_plans/`），纯 L1；F96 前身 spec §7 已裁定**不合并、不改写**它，本轮沿用 —— 本文只是把它的 L1 升到 L4+L5。

---

## 3. 核心问题：闸的两处机械都硬绑日期

2026-08-04 对真实文件名实测（`node -e` 直接调闸的导出函数）：

| 文件名形态 | S1 `parseDatePrefix` | 引用扫描 `PLAN_MENTION_RE` 能否发现指向它的引用 |
|---|---|---|
| `2026-07-27-p2-rubric-batch-retest.md`（日期前缀） | ✓ `2026-07-27` | ✓ |
| `next-session-pickup-2026-08-03-….md`（日期在**中间**；`_plans/` 8 份的形态分布 = 日期前缀 3 · 日期在中间 3 · 无日期 2） | ✗ null → **S1 红** | ✓（正则前导字符类吃得下） |
| `upstream-gate-codex.prompt.md` / `README.md`（无日期） | ✗ null → **S1 红** | ✗ **恒 0 ref** |

所以分歧**不是**「常青模板 vs 一次性产物」，而是 **S1 硬要一个日期**。而日期只在一处有用：判 grace 窗口。**一份有入站引用的文件根本不需要年龄** —— 三份常青模板（`upstream-gate-codex` / `design-spec-process-review` / `system-design-review`）今天各有 1/2/3 个引用，纯按可达性全绿，只被 S1 的命名形态误伤。

### 3.1 为什么不走另外两条路

| 路线 | 否决理由 |
|---|---|
| **保留严格 S1 + 给常青模板一张具名 `EVERGREEN` allowlist** | 逃逸虽可见具名，但每新增一份常青模板就要改闸代码；且**解决不了 `_plans/` 那 5 份 date-infix**（它们是一次性产物、不该进 allowlist）→ 仍需重命名，动 STATUS 固定指针 + L-core 行 |
| **只扩 `_prompts/` 并强制全部带日期前缀** | 要重命名 3 份**正在被引用**的常青模板；且与已拍的扫描面（含 `_plans/` + `_handoffs/`）矛盾 |

---

## 4. 判据（相对 F96 现状的 delta）

| 判据 | 改动 | 内容 |
|---|---|---|
| **S1** 形态 | **改** | 不再强制日期。名字里**含** `\d{4}-\d{2}-\d{2}` 的 → 必须解析成真日期（`2026-13-45-foo.md` 判红，形态错 fail closed 不变）；**不含**日期的 → 合法，但**不享 grace**。 |
| **S2** 可达性 | 判据不变，输入面扩大 | 到期文件（有日期且 `age > GRACE_DAYS`）**与全部无日期文件**必须有 ≥1 条仓库内入站引用。修法仍是二选一：`git mv` 归档（已完成）/ 从 STATUS·backlog·pickup 指向它（仍在飞）。 |
| **S3** fail closed | **微调** | 受管目录不存在 / 扫不到 `.md` → 红（不变）。**归档落点缺失只在「本轮该目录真有文件到期需归档」时判红**，否则 report-only。 |
| **S4** 豁免 shrink-only | 不变 | 表空 = 终态，不是待办。 |
| **S5** report-only | 扩 | 逐目录印 live/archived 份数、字节、每份入站引用数。 |

**对无日期文件，本设计是收紧不是放松**：它们永远没有 grace 窗口，必须始终可达。

**逃逸方向**（沿用 F96 §2 的选型方法）：想让一份文件免检，唯一路径是**让别的文件指向它 = 声明它在飞**，正是 owner「唯一真源、别处一律引用」裁定要的动作。去掉日期不能免检，反而失去 grace。

### 4.1 `GRACE_DAYS` 沿用 1（全闸单一参数，不搞 per-dir 分叉）

按 F96「从既有合规样本推」的同一方法，对三个新目录的 11 份带日期文件实测「文件名日期 → 首次被引用日期」的滞后：

- 8 份 **lag 0 天** · 1 份 lag 1 天 · 1 份 **lag 6 天**（`2026-07-28-INFRA-F71-gitea-self-hosted-publish-ci.md`）
- 第 11 份 `2026-07-23-ds-merge-phase3-component-cards.prompt.md` 查不到首引日 —— 因为它**从未被引用过**，正是今天两份死件之一。

**诚实标注**：那份 lag 6 天的 F71 plan 在 `GRACE_DAYS = 1` 下会在第 2 天变红。**这是预期行为，不是误报** —— 一份在飞的 plan 就该被声明在飞（S2 的修法②）。

**余量检查**（避免立一个不可满足的闸）：扩展后受管共 **26 份**（`superpowers/plans` 10 · `_prompts` 6 · `_plans` 8 · `_handoffs` 2）。今天状态下 `GRACE_DAYS = 1` 可满足 —— 唯二会红的就是那两份零引用的死 prompt，其余 **24 份**全部有引用。

---

## 5. 引用扫描改两遍

两遍**共用同一次文件读取**（现有 `collectRefFiles` + 单次 `readFileSync`），不重复 IO。

- **第一遍**（现有，不改）`PLAN_MENTION_RE` 单遍正则抓「名字里带日期的 `.md`」，按 basename 归位。已同时覆盖日期前缀与 date-infix 两种形态。
- **第二遍**（新）对**全部受管目录**里的**无日期**文件，用字面 **`<父目录名最后一段>/<文件名>`** 匹配（如 `_prompts/upstream-gate-codex.prompt.md`）。

  ⚠️ **只取最后一段，不是完整仓库路径** —— 实际引用里既有 `docs/internal/_prompts/foo.md` 也有相对写法 `../_prompts/foo.md`，用完整路径会漏掉后者。第二遍对 `superpowers/plans/` 同样生效（今天那里没有无日期文件，但新 S1 已允许出现，判据必须一致）。

### 5.1 为什么第二遍必须带路径前缀，不能用裸 basename

2026-08-04 实测：

| 无日期文件 | 带父目录前缀命中 | 裸名命中 |
|---|---|---|
| `upstream-gate-codex.prompt.md` | 1 | 2 |
| `design-spec-process-review.prompt.md` | 1 | 1 |
| `system-design-review.prompt.md` | 2 | 3 |
| `new-session-prompt-consumer-product-verification.md` | 1 | 1 |
| `_plans/README.md` | **7** | **109** |

裸名对 `README.md` 这类通用名会命中全仓一切 README 提及 → 闸恒绿、判据失效。带前缀是精确的，且实测**今天 5 份无日期文件全部至少被这样引用过一次**（人写 markdown 链接天然带路径）。

### 5.2 第二遍的两个 bug 源（必须各有单测钉住）

1. **归档路径字面包含 live 路径** —— 归档后是 `docs/_archive/_prompts/foo.md`，它**字面上包含** `_prompts/foo.md`。不剔除的话，一份已归档文档会把活文件永久钉住（正是 F96 `isProvenanceMention` 防的自锁，在第二遍要重新实现一次）。判据：匹配位置所在的引用串含 `_archive/` → 不算活指针。
2. **自引用** —— 第一遍靠 basename 排除自身；第二遍要显式排除「文件自己包含自己的路径」。不排则每份自引用、闸恒绿。

### 5.3 性能

pre-commit 每次都付这个成本（F96 头注释记着单遍正则把全仓扫描从 4.2s 降到 2.3s）。第二遍是对无日期文件的逐条 `String.includes`，今天 N = 5。**实施时实测 pre-commit 耗时，不得劣化**；若 N 增长到需要关心，改用一次性构造的多模式匹配，但现在 YAGNI。

---

## 6. 受管目录表（1 → 4 行）

| dir | archiveDir | label | 落点今天存在？ |
|---|---|---|---|
| `docs/superpowers/plans` | `docs/_archive/superpowers/plans` | superpowers plans | ✅ 20 份 |
| `docs/internal/_prompts` | `docs/_archive/_prompts` | prompts | ❌ |
| `docs/internal/_plans` | `docs/_archive/_plans` | session pickups | ✅ 9 份 |
| `docs/internal/_handoffs` | `docs/_archive/_handoffs` | handoffs | ❌ |

`_prompts/` 落点由本轮那两份死件的**真 `git mv`** 建出来（不塞空 `.gitkeep`）。`_handoffs/` 今天无到期文件 → 落点不建，由 S3 的微调规则降级为 report-only。

---

## 7. 落地当刻的预测（实施时以闸自印为准，不以本节为准）

1. 两份零引用死 prompt 被 S2 点名 → **先做抢救扫描**（判据 = 「这条 live 约束在仓库别处有没有第二家」，不是「看起来重不重要」）→ 再 `git mv` 到 `docs/_archive/_prompts/`。
2. 归档后 `_archive/superpowers/plans/2026-07-23-repo-slim-execution-brief.md` 掉到 0 ref → **下一跑**被 S2 点名 → 一并归档。这是 F96 自述的**迭代收敛**，是正确行为不是 bug。
3. `_plans/` 与 `_handoffs/` 本轮预计零点名。

---

## 8. 验收（每条先造故障看它变红，再采信「零违例」）

| # | 故障注入 | 期望 |
|---|---|---|
| 1 | 无日期文件零引用 | S2 红 |
| 2 | 无日期文件只被 `docs/_archive/**` 里的文档引用 | **仍 S2 红**（不被归档引用钉住） |
| 3 | 无日期文件只被**裸名**引用（无路径前缀） | **S2 红**（判据是带前缀） |
| 4 | 无日期文件被 `<父目录>/<名>` 引用 | 绿 |
| 5 | `2026-13-45-foo.md` | S1 红 |
| 6 | date-infix 文件到期且零引用 | S2 红 |
| 7 | 受管目录被清空 | S3 红 |
| 8 | 有文件到期 + 归档落点不存在 | S3 红 |
| 9 | 无文件到期 + 归档落点不存在 | **不红**（report-only） |
| 10 | 豁免表留 stale 行 | S4 红 |
| 11 | 空 / 畸形输入（含 `pnpm -- ` 透传把 `--` 当参数） | fail closed |
| 12 | 闸真的挂上了 | pre-commit 条件触发 + `prepublishOnly` 实测各触发一次 |

第 2、3 条对应 §5.2 的两个 bug 源，是本次新增机制自己的终审 —— **把刚建的机制放进终审范围**，不只测被它管的对象。

---

## 9. 明确不做（逐条给理由）

| 不做 | 理由 |
|---|---|
| 扩到 `_spikes/` · `_reports/` · `retrospection/` | 记录型，靠 grep 发现不靠链接；套可达性会逼人补假指针（§2） |
| 动 `specs/` 无限期保留的裁定 | F96 前身 spec §3 已论证，本轮无新证据推翻 |
| 重命名任何文件 | date-infix 与无日期都已被判据吸收；重命名要动 STATUS 固定指针 + L-core 行，收益为零 |
| 动 `audit:rule-inventory` 的 S4/S6 硬编码扫描面 | 本轮不拆、不移动任何规则文件（[[INFRA-F95]] ⛔④ 的触发条件未命中） |
| 合并 `docs/internal/_plans/README.md` | F96 前身 spec §7 已裁定不合并；本轮只把它的 L1 升到 L4+L5 |
| 把 `docs/` 体量做成闸 | §1 已测：体量维度没有收货方；F96 前身 spec §6 亦记「归档不降仓库字节」 |
| 并进 [[INFRA-F95]] 或 [[INFRA-F58]] | F95 ⛔② 明令禁止与 F58 混做；本条是 F96 的扫描面扩展，与 F95 的 STATUS 段形态契约互不重叠 |
