# 激活层硬化 Phase 3（路由覆盖 audit + trigger-drift 复盘）Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 让激活层本身被机器审计——每个 canonical 制品都断言有「① 一等定义 ② jump 表关键词路由 ③ 唤醒词」三件齐全（跨文件），新盲区一出现即 pre-commit 报；并立 trigger-drift 复盘协议，使返工只修激活层、不再无脑加内容规则。

**Architecture:** 新增一个 manifest-driven 的 `audit-artifact-routing.mjs`（与既有 `audit-rule-load-map.mjs` 互补——后者管 M-rule heading 进 jump 表，前者管 canonical 制品三件齐全且跨文件），挂 pre-commit；再加一段 trigger-drift 复盘协议进 AGENTS.md。

**Tech Stack:** Native Node 18+（`node:fs`），vitest，pnpm，husky pre-commit，Markdown。

## Global Constraints

- **三件齐全定义**：canonical 制品 = ① 在某真源文件有一等定义（标题锚点存在）；② `mockup-conventions.md` jump 表（或等价路由表）有一行以关键词触发它；③ `docs/WAKE-WORDS.md` A 区有用户唤醒词行。缺任一 → 报。
- **跨文件**：manifest 里制品的"定义文件"可以是 `design-process.md` / `mockup-conventions.md` / 任意真源——audit 必须能跨文件校验（这正是 `audit-rule-load-map.mjs` 当前做不到、PRD 缺口得以静默的原因）。
- **不替代**既有 `audit-rule-load-map.mjs`（它管 M-rule 文件内覆盖）；本脚本是制品级补充。
- **Native Node only**，无新依赖；exit 0 pass / 1 缺三件之一 / 2 bad usage。
- Commit 默认含 push；commit 只 stage 自己改的文件（禁 `-A`）；message 英文 + `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`。
- AGENTS.md 可能被并行 session 占用 → 改它的 task 前先 `git status` 确认 clean，否则等待。

---

### Task 1: `audit-artifact-routing.mjs` — canonical 制品三件齐全 audit

**Files:**
- Create: `scripts/audit-artifact-routing.mjs`
- Create: `tests/artifact-routing.test.ts`

**Interfaces:**
- Produces: `export const ARTIFACTS`（manifest）+ `export function checkArtifact(artifact, files) → { name, hasDef, hasRoute, hasWake, ok, missing: [...] }`，`files` 是 `{ [relPath]: content }`。

- [ ] **Step 1: 写失败测试**

```ts
// tests/artifact-routing.test.ts
import { describe, it, expect } from 'vitest'
import { checkArtifact } from '../scripts/audit-artifact-routing.mjs'

const PRD = {
  name: 'PRD',
  defFile: 'docs/internal/design-process.md', defAnchor: 'Step B',
  routeFile: 'docs/internal/mockup-conventions.md', routeKeyword: '起草 / 更新 PRD',
  wakeFile: 'docs/WAKE-WORDS.md', wakeWord: '@TVU PRD',
}
describe('checkArtifact', () => {
  it('passes when def + route + wake all present', () => {
    const files = {
      'docs/internal/design-process.md': '### Step B — PRD 起草',
      'docs/internal/mockup-conventions.md': '| Step B | 起草 / 更新 PRD 时 |',
      'docs/WAKE-WORDS.md': '| 起草/更新 PRD | @TVU PRD | ... |',
    }
    const r = checkArtifact(PRD, files)
    expect(r.ok).toBe(true); expect(r.missing).toHaveLength(0)
  })
  it('fails when wake word missing (the historical PRD gap)', () => {
    const files = {
      'docs/internal/design-process.md': '### Step B — PRD 起草',
      'docs/internal/mockup-conventions.md': '| Step B | 起草 / 更新 PRD 时 |',
      'docs/WAKE-WORDS.md': '（没有 PRD 唤醒词）',
    }
    const r = checkArtifact(PRD, files)
    expect(r.ok).toBe(false); expect(r.missing).toContain('wake')
  })
  it('fails when route keyword missing', () => {
    const files = {
      'docs/internal/design-process.md': '### Step B — PRD 起草',
      'docs/internal/mockup-conventions.md': '（没有 PRD 路由行）',
      'docs/WAKE-WORDS.md': '@TVU PRD',
    }
    const r = checkArtifact(PRD, files)
    expect(r.ok).toBe(false); expect(r.missing).toContain('route')
  })
})
```

- [ ] **Step 2: 跑测试看失败**

Run: `pnpm vitest run tests/artifact-routing.test.ts`
Expected: FAIL（未导出）

- [ ] **Step 3: 写实现**

```js
#!/usr/bin/env node
// audit-artifact-routing.mjs — canonical 制品三件齐全 audit（跨文件）。
// 每个制品断言：① 定义文件含 defAnchor；② 路由文件含 routeKeyword；③ WAKE-WORDS 含 wakeWord。
// 缺任一 → 报。补 audit-rule-load-map 的跨文件 + 唤醒词盲区（PRD 缺口曾静默存在）。
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'

export const ARTIFACTS = [
  { name: 'PRD',
    defFile: 'docs/internal/design-process.md', defAnchor: 'Step B',
    routeFile: 'docs/internal/mockup-conventions.md', routeKeyword: '起草 / 更新 PRD',
    wakeFile: 'docs/WAKE-WORDS.md', wakeWord: '@TVU PRD' },
  { name: 'UX 交付卡结构',
    defFile: 'docs/internal/mockup-conventions.md', defAnchor: 'UX 交付卡 Canonical 结构',
    routeFile: 'docs/internal/mockup-conventions.md', routeKeyword: 'canonical UX 卡结构',
    wakeFile: 'docs/WAKE-WORDS.md', wakeWord: '@TVU UX说明' },
  { name: '交付物同步',
    defFile: 'docs/internal/mockup-conventions.md', defAnchor: 'M-DISCIPLINE.SYNC',
    routeFile: 'docs/internal/mockup-conventions.md', routeKeyword: '编辑 confirmed 后',
    wakeFile: 'docs/WAKE-WORDS.md', wakeWord: '同步交付物' },
]

export function checkArtifact(a, files) {
  const has = (path, needle) => (files[path] || '').includes(needle)
  const hasDef = has(a.defFile, a.defAnchor)
  const hasRoute = has(a.routeFile, a.routeKeyword)
  const hasWake = has(a.wakeFile, a.wakeWord)
  const missing = []
  if (!hasDef) missing.push('def')
  if (!hasRoute) missing.push('route')
  if (!hasWake) missing.push('wake')
  return { name: a.name, hasDef, hasRoute, hasWake, ok: missing.length === 0, missing }
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const root = join(dirname(fileURLToPath(import.meta.url)), '..')
  const needed = [...new Set(ARTIFACTS.flatMap(a => [a.defFile, a.routeFile, a.wakeFile]))]
  const files = {}
  for (const p of needed) { try { files[p] = readFileSync(join(root, p), 'utf8') } catch { files[p] = '' } }
  let failed = 0
  for (const a of ARTIFACTS) {
    const r = checkArtifact(a, files)
    if (r.ok) console.log(`✅ ${r.name}: def+route+wake 三件齐全`)
    else { failed++; console.error(`❌ ${r.name}: 缺 ${r.missing.join(' / ')}`) }
  }
  console.log(failed ? `\n❌ artifact-routing: ${failed} 制品三件不全` : '\n✅ artifact-routing OK')
  process.exit(failed ? 1 : 0)
}
```

- [ ] **Step 4: 跑测试看通过 + 跑真实 audit**

Run: `pnpm vitest run tests/artifact-routing.test.ts` → PASS
Run: `node scripts/audit-artifact-routing.mjs` → 期望全 ✅（Plan 1 已落 PRD/UX 卡唤醒词与路由；若 M-DISCIPLINE.SYNC 那条因 Plan 1 Task 4 未完而缺 def，会报 ❌ "交付物同步: 缺 def"——这正是它该报的，说明 Task 4 还没做）。

> 注：若此时 Plan 1 Task 4 尚未完成，`audit-artifact-routing` 会如实报"交付物同步缺 def"。这是**正确行为**（盲区被机器抓到）。待 Task 4 落地后该项转绿。

- [ ] **Step 5: 加 npm script + Commit**

先 Read `package.json` 的 audit:* 区（注意：可能被并行 session 占用 → 先 `git status package.json`，dirty 则**暂缓本 step 的 package.json 改动**，只提交脚本 + 测试，npm script 等其 clean 再补）。clean 时加：
```json
"audit:artifact-routing": "node scripts/audit-artifact-routing.mjs",
```

```bash
git add scripts/audit-artifact-routing.mjs tests/artifact-routing.test.ts
# package.json 若 clean 一并 add；否则单独留待
git commit -m "feat(audit): artifact-routing gate — assert each canonical artifact has def+route+wakeword (cross-file, WS5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

### Task 2: pre-commit 挂 artifact-routing（相关文件改动时触发）

**Files:**
- Modify: `.husky/pre-commit`

**Interfaces:**
- Consumes: Task 1 的 `audit-artifact-routing.mjs`。

- [ ] **Step 1: 加 gate（仅当 staged 含相关真源/cheat-sheet 时跑）**

先 Read `.husky/pre-commit`（**先 `git status .husky/pre-commit` 确认未被并行 session 占用**）。加：

```bash
# artifact-routing gate — 改了制品真源/路由/唤醒词时，校验三件齐全
staged_routing=$(git diff --cached --name-only --diff-filter=ACM | grep -E 'docs/internal/(mockup-conventions|design-process)\.md|docs/WAKE-WORDS\.md' || true)
if [ -n "$staged_routing" ]; then
  echo "▶ pre-commit: artifact-routing (canonical 制品三件齐全)"
  node scripts/audit-artifact-routing.mjs || exit 1
fi
```

- [ ] **Step 2: 验证（手动触发一次）**

Run: `node scripts/audit-artifact-routing.mjs; echo "exit=$?"`
Expected: 打印各制品状态 + exit 码（全绿=0）。

- [ ] **Step 3: Commit**

```bash
git add .husky/pre-commit
git commit -m "feat(gate): run artifact-routing on changes to routing sources (WS5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

### Task 3: trigger-drift 复盘协议（写进 AGENTS.md）

**Files:**
- Modify: `AGENTS.md`（加一段 trigger-drift 复盘协议）

**Interfaces:**
- 纯协议文档；引用 `docs/WAKE-WORDS.md` + `audit-artifact-routing.mjs`。

- [ ] **Step 1: 加协议段（先 `git status AGENTS.md` 确认未被并行 session 占用；dirty 则等其 clean）**

Read `AGENTS.md` 找合适位置（如 §Sprint 收尾 Self-Audit 协议 附近），加：

```markdown
## Trigger-Drift 复盘协议（激活层硬化）

每次用户矫正 / 返工后，固定追问一句：**「哪个触发没点亮？」**

- 定位失败发生在**激活层**还是内容层：规则/模板存在但没被加载 = 激活层（绝大多数返工属此类）。
- 激活层失败的修法**只允许三类**，禁止以"加一条内容规则"收尾：
  1. 给制品/场景加**唤醒词**（登记进 `docs/WAKE-WORDS.md` A 区）；
  2. 给制品加 **jump 表关键词路由**；
  3. 把埋藏的 canonical 模板**提为一等定义**。
- 修完跑 `node scripts/audit-artifact-routing.mjs` 确认该制品三件齐全转绿。
- **反模式**：返工后新增第 N 条内容硬规则——只会加重激活层负担（规则越多越易漏触发），是"越自查越频繁"的根因。内容规则只在"真缺规则"（而非"有规则没触发"）时才加。

**真源**：诊断与 5 实例见 `docs/superpowers/specs/2026-06-18-design-system-activation-layer-hardening-design.md`。
```

- [ ] **Step 2: 验证**

Run: `grep -n "Trigger-Drift 复盘协议\|哪个触发没点亮" AGENTS.md`
Expected: 命中协议段。

- [ ] **Step 3: Commit**

```bash
git add AGENTS.md
git commit -m "docs(AGENTS): add trigger-drift retro protocol — fix activation layer, not add content rules (WS5)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Self-Review

**1. Spec coverage（对 spec §3 WS5）：**
- 路由覆盖 audit（跨文件 + 三件齐全）→ Task 1 ✅
- 挂 pre-commit → Task 2 ✅
- trigger-drift 复盘机制 → Task 3 ✅

**2. Placeholder scan：** 实现/测试/命令均完整，无 TBD。

**3. Type 一致性：** `checkArtifact(a, files)` 返回 `{name,hasDef,hasRoute,hasWake,ok,missing}`，测试断言 `.ok` / `.missing` 一致；`ARTIFACTS` manifest 字段（defFile/defAnchor/routeFile/routeKeyword/wakeFile/wakeWord）在实现与 manifest 一致。

**4. 依赖顺序：** Task 1 的真实 audit（Step 4）在 Plan 1 Task 4 未完时会如实报"交付物同步缺 def"——这是**预期正确行为**，不是 plan 缺陷；Task 4 落地后转绿。建议 Phase 3 在 Plan 1 全部完成后执行，避免噪声。

**5. 并行避让：** Task 1 Step 5（package.json）、Task 2（.husky/pre-commit）、Task 3（AGENTS.md）都可能撞并行 session → 各自 step 已要求先 `git status` 确认 clean，dirty 则等待。
