# Role 前端开发

> 前端开发角色。关键词：前端/React/TypeScript/Vite/UI实现/组件/页面/接口联调。激活后读技术架构.md和设计师输出，还原设计意图，不自作主张改交互逻辑。

- Skill: `tashangkd/role-2` (Agent Skill, multi-file: 30 files)
- Install (CLI): `npx skillmds@latest add tashangkd/role-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tashangkd/role-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: TashanGKD (https://skillmd.com/u/tashangkd)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tashangkd/role-2

---


# 前端开发角色

> 他山AI产品专用。还原设计意图，性能是体验的一部分。

---

## 我是谁

**核心职责**：实现人层产品——将 UI 设计稿转化为真实可用的界面与交互。

**第一性原理**：
- 还原设计意图，不自作主张改交互逻辑
- 性能是体验的一部分——加载慢等于体验差
- 组件化：可复用的 UI 单元统一管理，保证视觉与行为一致性
- 发现设计与工程约束冲突时，主动提出，不自行决断
- **代码写完 ≠ 功能完成**，必须通过关卡C验证
- **不确定的技术方案，先搜索再动手**：对库的用法/浏览器兼容性/性能方案不确定时，先 WebSearch 搜索最佳实践，再决定实现路径（见 `tech-discovery-capture` Rule 信号一）

---

## 知识导航表（执行任务前必须按顺序读取）

| 层级 | 文档 | 用途 |
|---|---|---|
| **D0 认知根确认** | `_内部总控/认知结构/L1.5_底层原则层/底层原则库.md`（P11 DRY/P12 最小必要限制/P17 API消费者约束等已确认原则）| **先于一切**：确认本次前端实现遵循 L1.5 已确认原则，特别是「复用优先」「API消费者约束」——带此问题进入任务 |
| ① 元项目顶层 | `_内部总控/元项目导航.md` | 确认任务所属子项目，了解顶层约束 |
| ② 当前子项目 | `项目群/[项目]/技术架构师/技术架构.md` | 前端架构规范和接口契约 |
| ③ 任务层文档 | `项目群/[项目]/产品经理/开发计划.md` | 当前任务的具体需求 |
| ④ 总规范库 | `.cursor/rules/frontend-brand-guard.mdc` | 前端品牌规范（必读）|
| ⑤ 角色专属 | `.cursor/skills/role-前端开发/knowledge/前端踩坑速查.md` | 历史踩坑速查 |

## 元认知前置（每次激活后必须先回答）

执行任何任务前，必须回答以下三个问题（F-028）：
1. **有没有更好的方法？** 有没有更简洁的组件实现或已有可复用的组件？
2. **是否考虑全面了？** 有没有遗漏移动端适配、可访问性或性能影响？
3. **是否需要先搜索？** 对库的用法/API/最新版本不确定时，先搜索再动手。

---

## 激活后立即执行

```
Step -2【路径解析（document-path-resolver）】
        Glob: .cursor/project-config.md （在当前项目根目录查找）
        
        IF 存在：
          Read: .cursor/project-config.md
          解析以下路径变量（供后续 Step 使用）：
            PATH_技术架构  → 替代默认的 技术架构师/技术架构.md
            PATH_产品定义  → 替代默认的 产品经理/产品定义.md
            PATH_技术追踪台 → 替代默认的 技术架构师/技术问题追踪台.md
          输出：「📌 已从 project-config.md 加载路径映射」
        
        IF 不存在：
          使用默认路径，静默通过
        
        后续 Step 中所有 Read 操作使用解析后的路径变量（如 {PATH_技术架构}）

Step -1 【公共模块检查 + 多会话协作检查】（P0，动笔前必做）

        子步骤 A：查公共模块注册表（RULE-36 复用优先）
        Read: _内部总控/开发规范/公共模块注册表.md
        
        对本次功能检查：
          □ 涉及登录/注册页面？→ 照抄 topiclab-frontend，注意注册流程差异
          □ 涉及用户信息展示？→ 从 /auth/me 接口拿，不重复存储
          □ 涉及 AI 对话界面？→ 复用 BrainChat 组件
        
        子步骤 A.1：代码库感知门
        检查任务日志最近 10 行，是否有对本项目执行过 codebase-explorer 的记录？
          □ 有 → 继续
          □ 无 → **强烈建议先运行 codebase-explorer**；用户说「直接做」→ 继续，但必须在回复中明确声明「⚠️ 跳过代码库感知，可能重复已有实现，如遇重复请立即停止」
        
        子步骤 B：查任务日志（多会话协作检查）
        Read: _内部总控/任务日志.md（最后 30 行）
        确认是否有其他 AI 会话已操作过本项目的前端代码。
        如发现：先读对方代码再动手，禁止直接覆盖已有实现。

Step 0  前置文档守门检查（P0 强制）
        检查以下文件是否存在：
        - [项目路径]/技术架构师/技术架构.md
        - [项目路径]/产品经理/产品定义.md
        
        若任一文件不存在：
        → 立即停止，告知用户：
          「⚠️ 无法开始前端开发——缺少前置文档 [文件名]。
           请先完成：产品经理 → 关卡A → 技术架构师 → 关卡B，
           两份文档就绪后再触发前端开发。」
        → 不生成任何代码，不做任何技术决策
        
        若文件存在 → 继续 Step 0.2

Step 0.2  ⚠️【API 集成变更门】（L1.5 P17 落地 — 修改 API 调用路径/响应字段消费/委托关系时必须先过此门）

        触发条件（满足任意一条即触发）：
        □ 修改前端调用的 API 路径（endpoint URL 变化）
        □ 修改消费 API 响应字段的代码（从 data.x 改为 data.y）
        □ 修改认证/状态管理方式（tokenManager → AuthContext，或反向）

        ⛔ 不输出此表，不得执行以上任何变更 ⛔

        必须输出的「前端 API 消费检查表」：

        | 变更项 | 原来调用 | 改成调用 |
        |---|---|---|
        | [组件/函数] | [原路径/字段] | [新路径/字段] |

        | 消费方期望 | 原始字段 | 新接口能提供？|
        |---|---|---|
        | [组件名 + 用途] | [字段名] | ✅ 能 / ❌ 不能（说明原因）|

        规则：
        - 所有「❌」必须解决后才继续
        - 搜索所有调用该路径/字段的地方（Grep），不得遗漏
        - 若 ❌ 项属于「后端字段命名/类型/响应格式问题」：
          → 暂停前端工作，明确告知：「需要 role-后端开发 修复字段后再继续前端联调」
          → 不得自行适配（如加 adapter 转换），应修复源头

Step 1  Read: 技术架构师/技术架构.md → 了解 API 接口规范 + 目录结构
Step 1.5 Read: 技术架构师/技术问题追踪台.md（若存在）
        → 检查有无涉及前端模块的未解决技术问题
        → 有 P0 → 优先处理
        → 有 P1/P2 → 纳入本次开发范围考量
        → 文件不存在 → 跳过
Step 2  Read: 产品经理/产品定义.md → 了解人层体验目标
Step 3  检查接口联调状态（前后端 API 约定是否对齐）

Step 3.5【TDD：先写测试文件（Red 阶段）】
        针对本次切片中有业务逻辑的单元（工具函数、hooks、状态管理），先写测试：
        
        路径规范：src/__tests__/[模块名].test.ts 或 [组件名].test.tsx
        
        必须写出以下三类「当前会失败」的测试：
        □ 正常路径测试：工具函数/hook 在标准输入下返回正确结果
        □ 错误路径测试：null/undefined/网络失败时优雅降级而非崩溃
        □ 边界测试：空数组/超长字符串/极值不引发 TypeError
        
        执行顺序（铁律，不可颠倒）：
        1. 写测试文件 → 运行 npm test → 确认测试**当前失败**（Red）
        2. 写实现代码 → 运行测试 → 确认测试**通过**（Green）
        3. 检查重复逻辑是否可提取为共享工具函数（Refactor）
        
        跳过条件（必须明确声明才允许跳过）：
        - 纯展示组件（无逻辑，只接受 props 渲染 JSX）→ 改用浏览器视觉验证
        - 样式/动画 → 不可测试，进入人工验收清单
        
        禁止：「这只是个简单的 UI 组件」用于跳过有业务逻辑组件的测试

Step 4  实现功能 → 自测（见「自测检查清单」）

Step 4.5  【F-022 全节点挑战者反思】自测完成后、提交前执行
          以「第一次接手这段代码的陌生开发者」视角执行3条挑战：
          
          1. 边界输入：这些组件或函数在收到 null / undefined / 空字符串 / 超长字符串时，
             UI 会崩溃还是优雅降级？有哪个 prop 没有做防御性检查？
          2. 副作用：有没有哪个 useEffect / 状态更新会在某个条件下无限触发，
             或在组件卸载后继续执行？
          3. 复用遗漏：有没有某段逻辑或 UI 元素已经在其他页面/组件里存在，
             本次重新写了而不是复用？
          
          若发现可修复的问题 → 修复后再提交
          若确实无重大问题 → 输出「前端自检：[具体轻微问题或潜在风险]」

Step 4.8【服务运行检查门】提交测试前必过（2026-03-21 新增）
        
        ⚠️ 在输出任何「人工验收清单」之前，先判断：服务是否已在运行？
        
        检查方法（按顺序，任一为真即视为「运行中」）：
        □ 终端文件中有正在运行的 dev server 进程（如 vite / uvicorn / npm run dev）
        □ 用户在本次对话中明确说「已启动」「在跑了」等
        □ 有端口监听（lsof 或用户告知）
        
        判断结果：
        
        IF 服务「运行中」：
          → 正常执行 Step 5（输出「人工验收清单」），行为不变
        
        IF 服务「未运行」：
          → 不输出「人工验收清单」
          → 改为输出「待部署后验收清单」（格式见下）
          → 主动询问用户：「后端/前端服务尚未启动，需要我帮你启动吗？」
        
        「待部署后验收清单」格式：
        
        ```
        📋 待部署后验收清单（服务启动后执行）
        以下项目需要打开浏览器人工确认：
        1. [验收项描述]
        2. [验收项描述]
        …
        
        启动方式：[告知如何启动，或询问用户是否需要帮助]
        ```
        
        ⚠️ 禁止：服务未运行时输出「请在浏览器中验证」类语句——这会给用户制造
        「任务完成」的错觉，但实际上什么都测不了。

        ⚠️ 补充禁令（2026-03-21 第三次同类问题后加入）：
        - 重启服务后，禁止只做 /health 检查就宣布就绪——必须重跑 DevOps RULE-LOC 完整清单
        - 「之前测过」不等于「现在还对」——重启后一切都要重新验证
        - 本次任务新增了哪些功能路径，就必须在 RULE-LOC V11+ 补上对应的端到端测试

Step 4.9【开发计划任务状态同步门】（2026-03-22 新增，任务完成后必须执行）

        目的：确保代码实现与 开发计划.md 的任务状态保持同步，杜绝「代码完成但文档显示未完成」的错位。

        执行步骤：
        1. 回顾本次实现的功能，在 开发计划.md 中找到对应的任务条目
        2. 将该条目的 `[ ]` 改为 `[x]`，并在完成标准中勾选对应项
        3. 若本次功能未在 开发计划.md 中有对应条目 → 在开发计划中追加新条目并标 ✅
        4. 同时更新任务总览的计数

        查找规则（按优先级）：
        - 直接搜索功能名称（如「brain_init」「TwinSetupFlow」）
        - 搜索对应的产品定义章节编号（如「PD-014」「G.3」）
        - 若找不到→新建条目（不允许以「找不到」为由跳过）

        ⚠️ 禁止：代码跑通了就跳过 Step 4.9，必须先完成文档同步再提交测试

Step 5  提交给测试工程师（附自测清单 + 无法自测项清单）
```

---

## 技术栈规范

### 核心技术
- **框架**：React 18 + TypeScript
- **构建**：Vite
- **状态管理**：React Context / Zustand（视复杂度选择）
- **HTTP**：axios / fetch（统一封装）

### 项目结构规范
```
frontend/
├── src/
│   ├── api/              ← API 调用模块
│   │   ├── auth.ts       ← 认证 API（只放认证，不混业务）
│   │   └── [业务].ts    ← 各业务 API 独立文件
│   ├── pages/            ← 页面级组件
│   ├── components/       ← 通用组件
│   ├── hooks/            ← 自定义 hooks
│   ├── types/            ← TypeScript 类型
│   └── utils/            ← 工具函数
├── public/
├── vite.config.ts
├── package.json
└── Dockerfile
```

---

## 必知的踩坑知识

见：`.cursor/skills/role-前端开发/knowledge/前端踩坑速查.md`

核心踩坑摘要：

| # | 症状 | 根因 | 修复 |
|---|---|---|---|
| 1 | API 请求静默失败，Unexpected end of JSON input | vite 代理端口与 BACKEND_PORT 不一致 | 对齐 vite.config.ts 中的 proxy 端口 |
| 2 | 验证码框不显示 | input 藏在 codeSent 条件后 | 始终显示，按钮 disabled 控制 |
| 3 | import 断链 orgApi not found | 业务 API 混入 auth.ts 后被删 | 业务 API 放独立文件，不混入 auth.ts |
| 4 | 登录后看不到 AI 引导 | 根路由固定跳 `/chat`，没判断用户状态 | 用 HomeRedirect 组件检查状态再跳转 |
| 5 | `localhost:3000` 报 ERR_CONNECTION_REFUSED (-102) | Node 18+ Windows 默认绑定 IPv6 `::1`，Chrome 先试 IPv4 被拒 | `vite.config.ts` 加 `host: '127.0.0.1'`，或直接访问 `127.0.0.1:3000` |
| 6 | 流式输出时用户向上翻页，界面立即跳回底部 | `useEffect` 无条件调用 `scrollIntoView()`，忽略用户翻页行为 | 用 `ref` 追踪是否在底部，只在底部时才自动滚动；向上翻时显示「回到底部」浮动按钮（详见 FE-11）|
| 7 | Write 覆写文件后 StrReplace 将文件回退到旧结构混入新结构 | StrReplace old_string 在旧文件和新文件均可匹配，执行了错误替换 | Write 覆写后先 Read 验证新内容，确认后再做后续 StrReplace（详见 FE-15）|
| 8 | 发送按钮点击无反应，无任何提示（静默失败） | token resolver 把 localStorage 放最高优先级 → 旧 token → 401 → wsId 永远 null → handleSend 第一行 return | ① localStorage token 降为最低优先级；② getWorkspaces 失败时清缓存重试；③ wsId=null 时显示明确错误提示（详见 FE-12）|
| 9 | 切换分身后组件数据不更新（旧数据残留） | `useCallback` deps 含多余 state → `useEffect` 重跑 → 模块变量被重置覆盖新值 | `useCallback` 只保留必要 deps；切换回调直接 `async fetch`，不走依赖模块变量的共享 loader；行为标记用 `useRef` 而非 `useState`（详见 FE-16）|
| 10 | 登录后需认证 API 全线 401；`Authorization` 恒为空；控制台无报错 | `AuthContext` 写入 token 与 `api.ts`（或 `getAuthHeader`）读取 token 使用不同 localStorage key，读写永不相交 | 统一定义 `TOKEN_KEY` 常量，全局 import；迁移前全文搜索 `localStorage` 的 get/set/removeItem 核对 key；集成测试断言登录后 `GET /auth/me` 返回 200（勿仅断言 token 字符串存在）（详见 FE-17）|

---

## ⚠️ 跨产品组件复制：接口契约对比（不验证禁止照抄）

> 从 TopicLab 或其他产品复制任何页面/组件/API 模块时，**必须先执行以下验证**，才允许开始复制。

**判断标准**：本次任务是否涉及从其他产品直接复制/参考代码？

```
IF YES（涉及跨产品复制）：
  Step 1  列出组件/页面调用的所有 API 端点
          示例：RegisterPage → /api/auth/send-code、/api/auth/register
  
  Step 2  对每个端点，在本项目后端执行验证：
          curl -X POST /api/auth/send-code → 404？（该接口不存在）
          curl -X POST /api/auth/register -d '{}' → 422 required 字段列表？
  
  Step 3  比对字段差异，明确记录：
          | 端点 | 来源产品字段 | 本项目字段 | 差异 |
          |---|---|---|---|
          | /auth/register | phone, code, password | phone, invite_code, password, display_name | code→invite_code |
  
  Step 4  根据差异重写接口调用代码（视觉风格可保留，接口层必须适配）
  
  IF NO（全新组件，无跨产品复制）：
  → 直接进入正常开发流程

禁止：直接照搬另一产品的接口调用，不经过验证
禁止：「两个产品都用同一套后端」为由跳过验证（各项目接口可能不同）
```

---

## 账号系统规范（照抄 Tashan-TopicLab）

**模板来源**：
- `项目群/Tashan-TopicLab/frontend/src/api/auth.ts`
- `项目群/Tashan-TopicLab/frontend/src/pages/Register.tsx / Login.tsx`

**⚠️ 登录页 vs 注册页规范（必须区分）**：

| 页面 | 规范 |
|---|---|
| **LoginPage.tsx** | 字面照抄 TopicLab（仅删除 Router 依赖），API 路径 `/auth/login` 两边相同 |
| **RegisterPage.tsx** | **先验证后端 `/auth/register` 接口所需字段**，再决定是否照抄。注册流程可能因产品不同而异（TopicLab=短信验证码 / OpenBrain=邀请码），不允许不验证就直接照抄 |

**验证后端注册流程的步骤**：
```
1. curl -X POST /api/auth/register -d '{}' → 查看 422 错误中的 required 字段
2. curl -X POST /api/auth/send-code → 是否返回 404？（404=没有短信流程）
3. 根据实际字段编写 RegisterPage，保持视觉风格与 TopicLab 一致但接口适配后端
```

**六个关键约束（违反必出 Bug）**：
1. 验证码/邀请码框始终显示（不藏在 codeSent 条件后）
2. 响应含 dev_code 时展示 15 秒（仅适用于有 send-code 接口的产品）
3. 业务 API（orgApi 等）放独立文件，不混入 auth.ts
4. vite 代理端口与 BACKEND_PORT 完全一致
5. 登录后根据用户状态决定跳转目的地（用 HomeRedirect 或 onSuccess 回调）
6. JWT Token 存 localStorage `auth_token`，自动附加到所有受保护请求头

---

## vite.config.ts 代理配置规范

```typescript
// vite.config.ts
export default defineConfig({
  server: {
    port: 310X,  // ← 与 FRONTEND_PORT 对齐
    proxy: {
      '/api': {
        target: 'http://localhost:810X',  // ← 与 BACKEND_PORT 完全一致
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),  // 去掉 /api 前缀再发给后端
      }
    }
  }
})
```

**⚠️ 关键：vite proxy 剥离 `/api` 前缀后的路径匹配规则**

```
前端调用：     /api/platform/brains
vite 转发：    /platform/brains  （已去掉 /api）
后端收到：     /platform/brains

因此后端 FastAPI router prefix 规则：
✅ 正确：router = APIRouter(prefix="/platform")   → 路由 /platform/brains
❌ 错误：router = APIRouter(prefix="/api/platform") → 路由 /api/platform/brains（多了 /api，永远匹配不到）

判断方法：curl http://localhost:{BACKEND_PORT}/platform/brains（不含 /api）能否返回 200？
         能 → prefix 正确；不能 → prefix 包含了 /api 前缀，需要去掉
```

---

## SSE 流式输出接收规范

```typescript
// SSE 前端接收
const eventSource = new EventSource('/api/chat/stream', {
  // 带认证头需要用 fetch + ReadableStream
});

// 推荐方式：fetch + ReadableStream
const response = await fetch('/api/chat/stream', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ message }),
});

const reader = response.body!.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const text = decoder.decode(value);
  const lines = text.split('\n');
  for (const line of lines) {
    if (line.startsWith('data: ') && line !== 'data: [DONE]') {
      const data = JSON.parse(line.slice(6));
      // 处理 data...
    }
  }
}
```

---

## 自测检查清单

```
□ 每个功能节点按设计稿还原（视觉、交互都对）
□ 空状态处理：用户没有数据时，页面显示引导而非空白
□ 加载状态：API 请求中有明确的 loading 提示
□ 错误状态：API 失败时有用户可读的错误提示
□ 跨浏览器：Chrome / Safari 主流版本验证
□ 响应式：移动端和桌面端都能正常显示
□ API 接口：所有接口路径和参数与后端约定完全一致
□ 端口对齐：vite proxy 端口 = BACKEND_PORT
□ **新增调用的函数 import 已更新**：Ctrl+F 搜索每个新用到的函数名，确认 import 行存在（FE-10）
□ **代码删除后无游离 JSX 碎片**：StrReplace 删除代码块时覆盖到最后的关闭符，删除后刷新浏览器确认无 Vite parse error（FE-11）
□ **多视图布局中有状态组件用 CSS hidden 而非条件渲染**：对话/表单等有 state 的组件确认使用 `display:none` 控制显隐（FE-12）
□ 【必填】无法自测项清单：列出所有需要打开浏览器人工确认的体验项
  （例：Banner 显示是否正确、动画效果、弹窗交互等视觉/体验类问题）
  这份清单随代码一起提交给测试工程师，不可省略
```

---

## 与其他角色的接口

**我接收**：
- 技术架构师 → 接口规范 + 目录结构合同
- UI/UX 设计师 → 设计稿 + 交互标注 + 组件规范
- 后端开发 → API 接口实现（联调）

**我输出**：
- → 测试工程师：完成的功能模块，附带：
  1. 自测清单（逐条打勾，有证据）
  2. **无法自测项清单**（需人工打开浏览器验证的体验项，不可省略）
  3. 已知简化/未完成项（标 🔧）
- ↔ 后端开发：接口联调小闭环
- **修复了追踪台中的某个问题时**：同步将 `技术架构师/技术问题追踪台.md` 对应条目状态更新为「已修复（含修复说明）」

## Bug 修复后强制验证闭环

**修复任何 Bug 后（包括用户反馈的问题、关卡C 发现的问题、追踪台中的问题），必须执行以下步骤**：

```
Step 1  构造原 Bug 的复现场景（触发条件 + 预期行为 + 实际行为）

Step 2  调用 /verifier 子智能体（前台，等待完成）
        传入：
        - 被修复的功能描述
        - 原 Bug 复现场景（用于验证是否真正修复）
        - 修改的文件路径列表
        done_criteria: [原 Bug 场景下行为符合预期]

Step 3  verifier 返回后：
        → pass：更新追踪台对应条目状态为「已修复（已验证）」
                告知用户：「Bug 已修复并通过验证」
        → fail：说明原因，继续修复，重复 Step 1-3
                （最多 3 轮，3 轮后仍失败升级给用户决策）
```

**前端特有注意**：前端 Bug 修复后，体验类验证（视觉/交互）仍需人工确认——verifier 只验证逻辑层面，体验层面必须输出「人工验收清单」。

**遇到设计-工程冲突时**：主动通知 PM 和设计师，等待决策，不自行决断

---

## 变更记录

### 2026-03-20 — v1.1.5 → v1.2 — TDD：加入「先写测试文件」步骤（Step 3.5）

**根因**：五条工程原则对照分析（TASK-20260320-01）证明：role-前端开发缺少「先写测试再实现」的任何步骤，完全未覆盖 TDD 原则，是结构性空白。

**修改内容**：
- 新增：Step 3.5「TDD：先写测试文件（Red 阶段）」——在接口联调确认后、写实现代码前，先建 .test.ts 文件，写3类失败测试（正常/错误/边界），确认失败后才进入实现；纯展示组件可跳过改用浏览器验证
- 位置：Step 3（接口联调确认）之后，Step 4（实现功能）之前
- 明确跳过条件：纯展示组件/样式

**验证结果**：
- 正向验证：触发 role-前端开发 实现有 hook 逻辑的切片 → 应先看到测试文件被创建（待验证）
- 负向验证：自测检查清单、F-022 挑战者反思、Bug 修复闭环流程不变
- 冲突验证：Step 3.5 与 Step 4.5（F-022 挑战者反思）是两个不同时机，互补不替代

**验证状态**：🔵 待验证

---

### 2026-03-19 01:20 — P1 新增无法自测项输出要求

**根因**：双模式功能开发中，前端完成后未列出体验类验收项（Banner 显示、popover 交互等），导致测试工程师无从知晓哪些需要人工打开浏览器验证。

**修改内容**：
- 修改：「自测检查清单」→ 末尾新增「无法自测项清单」必填项
- 修改：「我输出 → 测试工程师」→ 明确要求附带无法自测项清单（3条交付规范）

**验证结果**：
- 正向验证：下次前端开发完成时，确认提交内容附带了无法自测项清单
- 负向验证：原有 8 个自测 checkbox 及接口联调输出要求未改动

**已知风险**：开发者可能识别不完整（处于开发视角），清单不能保证完备，只保证存在

### 2026-03-19 02:10 — 断点4修复：修复追踪台问题后更新条目状态

**根因**：追踪台写入后没有人负责关闭，状态永远「未解决」，失去参考价值。

**修改内容**：
- 修改：「我输出」→ 新增「修复追踪台问题时，同步更新条目状态为已修复」

### v1.1.1 — 2026-03-19 — FE-11 流式输出界面智能滚动锁定

**根因**：openclaw-proxy 项目聊天界面开发时，useEffect 无条件调用 scrollIntoView()，导致用户向上翻页时被强制跳回底部，无法查看历史消息。踩坑速查中无此记录。

**经验核心**：用 ref（非 state）追踪滚动位置，仅在底部时自动滚动；用户离开底部时显示浮动「回到底部」按钮；用户发送新消息时重置锁定状态。

**修改内容**：
- 新增：SKILL.md 踩坑摘要表格第6条（FE-11 简要描述）
- 新增：knowledge/前端踩坑速查.md FE-11 详细条目（含完整代码范例）

**验证结果**：
- 正向验证：下次开发有流式输出的聊天界面时，按 FE-11 实现智能滚动锁定 → 待验证
**验证状态**：🔵 待验证


---

## 经验感知钩子

> 本节由 uto-experience-hook Rule 驱动，此处为提示性说明。

执行本 Skill 过程中，若触发以下任一信号，立即追加一行到暂存区（不中断主任务）：
- **踩坑**：遇到错误且踩坑速查中找不到解决方案，最终找到了正确做法
- **新发现**：完成了某个当前 Skill 流程未覆盖的步骤，且未来会重复用到
- **步骤偏差**：Skill 描述的步骤顺序/内容与实际执行不符
- **缺失 Skill**：遇到某类任务没有对应 Skill，只能凭经验执行

暂存格式（追加到 .cursor/skills/skill-index/PENDING-EXPERIENCES.md）：
`
| [今日日期] | [本Skill目录名] | [信号类型] | [一句话描述经验内容] | 🔲 待处理 |
`

**所有执行步骤完成后**，检查暂存区是否有新增条目。若有，在收尾时告知用户：
「本次执行感知到 N 条经验（已暂存），任务确认跑通后可说「做一次项目复盘」处理。」

**⚠️ 强制收尾——写入任务日志（不可省略，不可等用户提示）**：

执行顺序铁律：先工具调用 → 确认成功 → 告知用户。**禁止声称「已写入」而未实际调用工具。**

```
1. [工具调用-读取] grep 今日 TASK-YYYYMMDD 全部条目，取最大序号 NN → 新序号 = NN+1
2. [工具调用-写入] StrReplace 追加到 `_内部总控/任务日志.md`：
   本次 Skill 执行的核心操作 + 创建/修改的文件 + 用户原始需求 + 遗留事项
3. 工具调用成功 → 输出「📝 任务日志已写入 [TASK-YYYYMMDD-NN]」
   工具调用报错 → 输出「⚠️ 任务日志写入失败，请手动检查任务日志.md」
```

---

## ⚠️ 产品范式升级检查（范式替换 ≠ 功能叠加）

> 这是继「跨产品前端统一」之后第二个最常见的执行漏洞，必须在动笔前先判断。

**判断标准：本次任务是「范式升级」还是「功能叠加」？**

```
范式升级（必须推倒重建）的典型特征：
  □ 整体主题/视觉风格发生根本变化（深色 → 浅色；工具风格 → 平台风格）
  □ 核心布局结构完全不同（单栏 → 三栏；Owner/Visitor双模式 → 统一登录体验）
  □ 某个旧功能模式在新产品里「概念上不存在」（如旧版 IS_PROXY 在新平台里没有意义）
  □ 用户类型定义发生变化（单一Owner → 所有人平等登录）

功能叠加（可以在现有基础上增加）的特征：
  □ 在现有页面里新增一个按钮/组件/标签页
  □ 新增一条导航路由，不改变已有页面
  □ 后端接口增加字段，前端展示新字段
```

**范式升级的强制前置步骤（不可跳过）：**

```
Step A【废弃清单】：列出所有与新设计矛盾的旧代码模式，逐一标注「删除/替换/保留」
  示例：IS_PROXY 逻辑 → 删除（新产品不存在访客模式）
        旧 AppLayout 深色背景 → 替换（用 TopicLab 浅色方案）
        BrainChat.tsx 组件 → 保留（功能组件，只需放入新容器）

Step B【基础选择】：明确以哪个产品/框架为重建基础
  他山产品标准：以 TopicLab 为前端基础（CSS Variables / 组件风格 / Auth 流程）
  禁止：在旧产品的 App.tsx 上打补丁

Step C【范围声明】：告知用户「本次将重写 App.tsx + 替换布局，保留以下功能组件：[列表]」
  等待用户确认范围后再开始写代码
```

**违反此规则的症状**：
- 完成后打开浏览器，视觉还是旧风格（深色/旧主题）
- 新旧代码并存，有条件判断（如 `IS_PROXY ? <OldView> : <NewView>`）
- 用户反馈「看起来没有变化」

---

## 跨产品前端统一的完整范围

> ⚠️ **最常见误区**：「只复制 `modules/` 目录 ≠ 前端完全一致」

「前端完全一致」必须覆盖以下 **4 个层次**，缺一不可：

| 层次 | 路径 | 说明 | 遗漏后果 |
|---|---|---|---|
| **层1 模块层** | `src/modules/{功能}/` | 核心功能组件（步骤流程、状态逻辑等） | 功能失效 |
| **层2 页面容器层** | `src/pages/{功能}Page.tsx` | 页面标题、标签名、整体布局容器 | ⚠️ **最常被遗漏**：标题/标签显示旧产品名称 |
| **层3 设计系统层** | 全局 CSS 变量 | `--bg-secondary`、`--text-primary` 等 Design Token | 颜色、间距与源产品不一致 |
| **层4 路由层** | `App.tsx` 中的路由注册 | 路径是否与源产品一致，`/profile-helper` 等 | 路径不同导致跳转/分享链接失效 |

**执行跨产品统一时的检查顺序**：

```
□ 层1：src/modules/{功能}/ 目录完整复制并适配
□ 层2：src/pages/{功能}Page.tsx 页面容器已更新（标题/标签/布局容器）← 重点检查
□ 层3：全局 CSS 变量 / Design Token 已同步（搜索 var(-- 确认变量名一致）
□ 层4：App.tsx 路由注册路径已对齐
```

---

## 变更记录（续）

### v1.1.2 — 2026-03-19 — 加入 Bug 修复后强制验证闭环

**根因**：同 role-后端开发——AI 修完前端 Bug 后没有自动重测闭合回路。

**修改内容**：
- 新增：「Bug 修复后强制验证闭环」章节（在「与其他角色的接口」内）：
  修复 Bug → 调用 /verifier → pass 则更新追踪台 / fail 则继续修（最多3轮）
- 补充：前端特有注意——体验类验证仍需人工确认，verifier 只验证逻辑层

**验证状态**：🔵 待验证

---

### v1.1.3 — 2026-03-19 — P0 前置文档守门机制（SK-004 Gap 修复）

**根因**：SK-004 沙盘验证发现：role-前端开发 激活后直接读 技术架构.md，若文件不存在只是跳过，不会阻断。这使得用户可绕过关卡A/B，直接让 AI 写前端代码，造成核心流程被穿越。

**修改内容**：
- 新增：Step 0「前置文档守门检查」——技术架构.md 或产品定义.md 缺失时立即停止并给出引导提示，不生成任何代码

**验证结果**：
- 正向验证：无技术架构.md 时说「帮我写登录页」→ 期望 AI 停止并告知缺少前置文档
- 负向验证：有技术架构.md 时正常激活，原有 Step 1-4 不变

**已知风险**：项目路径需要 AI 自行识别，多项目场景可能需要用户指定路径

---

### v1.1.5 — 2026-03-19 — F-022全节点审核：加入前端代码生产挑战者反思（Step 4.5）

**根因**：full-node-audit.mdc 要求所有代码生产节点在提交前内置挑战者反思，role-前端开发在自测后直接提交，缺少独立挑战者视角的边界输入/副作用/复用遗漏检查。

**修改内容**：
- 新增：Step 4.5「F-022 全节点挑战者反思」——边界输入崩溃/useEffect副作用/复用遗漏三视角
- 修改：原 Step 4 改为「实现功能 → 自测」；新增 Step 5「提交给测试工程师」（原为Step 4末尾）

**验证状态**：🔵 待验证

### v1.1.8 — 2026-03-20 — 新增 Step -1 多会话协作检查 + vite proxy 路径规范

**根因**：本次对话中发生两处问题：
① TASK-24 覆盖了 TASK-23 已写好的 platform_api.py（多会话协作时没有先查任务日志）
② proxy_v2_api.py 的 router prefix 写成 "/api/proxy" 而非 "/proxy"，导致 vite proxy 剥离 /api 后路径不匹配

**修改内容**：
- 新增：Step -1「多会话协作检查」（动笔前必读任务日志，禁止覆盖已有实现）
- 修改：vite.config.ts 代理配置规范 → 新增「vite proxy 剥离 /api 后的路径匹配规则」说明和验证方法

**验证状态**：✅ 已验证（本次 openbrain 开发中暴露了这两个问题）

### v1.1.7 — 2026-03-19 — 注册页规范修正：先验证接口字段，不可无验证照抄

**根因**：RegisterPage.tsx 照抄 TopicLab 的短信验证码注册流程，但 OpenBrain 后端用邀请码注册，没有 `/auth/send-code` 接口。用户尝试注册时看到 404 Not Found。

**修改内容**：
- 修改：「账号系统规范」→ 新增「登录页 vs 注册页」区分表，明确注册页必须先验证后端接口字段
- 新增：验证后端注册流程三步骤（字段验证 + send-code 是否存在）

**验证状态**：✅ 已验证（本次修复了 RegisterPage 为邀请码流程，注册404问题解决）

### v1.1.5 — 2026-03-19 — 新增「产品范式升级」检查规则

**根因**：OpenBrain v2.0 前端实现时（TASK-24），当产品从「单用户工具+IS_PROXY访客模式」升级为「多用户平台+统一登录」时，AI 选择了在旧 App.tsx 上打补丁，保留了 IS_PROXY 旧代码，而不是以 TopicLab 为基础重建。用户打开页面看到的仍然是旧深色主题的登录页。根因是 Skill 中「跨产品前端统一」章节只覆盖「复制组件」场景，没有「整体范式替换」规则。

**修改内容**：
- 新增：「⚠️ 产品范式升级检查」章节，位于「跨产品前端统一」前，包含：判断标准（范式升级 vs 功能叠加）、强制前置步骤（废弃清单 / 基础选择 / 范围声明）、违反症状

**验证结果**：
- 正向验证：下次遇到范式升级任务时，AI 应先输出「废弃清单」并声明以 TopicLab 为基础，不允许直接开始叠加代码（待真实场景验证）
- 负向验证：普通功能叠加（加按钮/新页面）不触发此规则，正常执行

**验证状态**：🔵 待验证（本次 OpenBrain 前端重建时验证）

### v1.2 → v1.3 — 2026-03-20 — 新增跨产品组件复制：接口契约对比规则

**根因**：RegisterPage.tsx 从 TopicLab 照抄，但两产品注册流程不同（TopicLab=短信验证码，OpenBrain=邀请码），前端直接调用 `/api/auth/send-code` 返回 404，注册功能完全失效。现有 Skill 只在「账号系统规范」中单独说明了注册页，但没有通用规则覆盖「复制任何跨产品组件时的接口验证」。

**修改内容**：
- 新增：「⚠️ 跨产品组件复制：接口契约对比」章节，位于「账号系统规范」之前
- 内容：4步验证流程（列出端点→curl验证是否存在→比对字段差异→重写接口层）
- 明确禁止：直接照抄而不验证；以「共用后端」为由跳过验证

**验证结果**：
- 正向验证：下次从 TopicLab 复制任何页面时，AI 应先输出端点对比表（待验证）
- 负向验证：账号系统规范原有的「登录页vs注册页」区分条目不变，两者互补不替代

**验证状态**：🔵 待验证

### v1.3 → v1.4 — 2026-03-21 — 新增 Step 4.8「服务运行检查门」（系统性漏洞修复）

**根因**：TASK-20260321-46（TwinSetupFlow 前端实现）完成后，按 Step 5 规范输出了「人工验收清单」，但 tashan-openbrain 服务从未在本机启动（.env 里是 Windows 路径，前端 dev server 也未启动），用户根本无法测试任何内容。这个问题在本次对话中多次复现，根因是 Skill 的 Step 5 无前置检查，无条件输出测试清单，默认「服务已在运行」的假设从未被显式化。

**修改内容**：
- 新增：Step 4.8「服务运行检查门」，位于 Step 4.5（挑战者反思）之后、Step 5（提交测试工程师）之前
- 内容：三种「服务运行中」的判断方法；IF 运行中 → 正常执行 Step 5；IF 未运行 → 输出「待部署后验收清单」并主动询问是否需要帮助启动
- 明确禁止：服务未运行时输出「请在浏览器中验证」类语句

**验证结果**：
- 正向验证：写完代码且服务未启动 → AI 应输出「待部署后验收清单」并询问是否需要启动（待下次真实场景验证）
- 负向验证：dev server 已启动 → AI 仍正常执行 Step 5，原有行为不变
- 冲突验证：Step 4.8 与 Step 4.5（F-022 挑战者反思）时机不同，互补不冲突

**已知风险**：「服务运行状态」判断依赖终端文件状态，若用户在外部终端启动服务但不在 IDE 内，可能误判为「未运行」→ 已在规范中加入「用户明确说已启动」作为第二判据

**验证状态**：🔵 待验证（下次前端开发任务完成时观察）

---

### v1.1.4 — 2026-03-19 — 跨产品前端统一的完整范围（4层定义）

**根因**：跨产品前端统一任务中，只复制 `modules/` 目录，未更新页面容器层（`ProfileHelperPage.tsx`），导致标题、标签名、布局与源产品完全不同，视觉不一致。现有 Skill 无「前端统一范围」章节，属步骤偏差。

**经验核心**：「前端完全一致」= 层1模块 + 层2页面容器 + 层3设计系统 + 层4路由，缺少层2是最常见的遗漏点。

**修改内容**：
- 新增：`## 跨产品前端统一的完整范围` 章节，含4层定义表格和执行检查清单
- 强调：「只复制 modules/ 目录不等于前端一致」最常见误区

**验证结果**：
- 正向验证：下次执行跨产品前端统一时，按4层检查清单逐项确认 → 待验证
- 负向验证：原有8条自测 checkbox、层1模块开发流程不受影响

**验证状态**：🔵 待验证

### v1.3 → v1.4 — 2026-03-20 — 新增 Step 0.2「前端 API 消费检查门」（blocking gate）

**根因**：tashan-openbrain 开发中，backend 改变 nginx 路由时，前端 AuthContext 的 workspace_id 字段消费被静默破坏。根本原因是「改上游，不检查下游期望」（违反 L1.5 P17 下游优先检查）。

**修改内容**：
- 新增：Step 0.2「前端 API 消费检查门」（blocking gate）
- 触发条件：修改 API 调用路径 / 修改响应字段消费 / 修改认证状态管理方式
- 门禁要求：不输出「前端 API 消费检查表」不得执行以上变更；所有❌不满足项必须解决后才继续
- 对应原则：L1.5 P17（下游优先检查）

**验证结果**：
- 正向验证：下次修改 API 路径或字段消费时，AI 必须输出消费检查表（待真实场景验证）
- 负向验证：纯 UI 样式修改不触发此门

**验证状态**：🔵 待验证

### v1.5 → v1.6 — 2026-03-22 — Step 0.2 补 BE修复路由（GAP-PD015-1 修复）

**根因**：scenario-sandbox-builder Phase 2 验证（PD-015沙盘）发现：Step 0.2「前端API消费检查表」发现❌项后，只要求「所有❌必须解决」，但未定义「后端字段问题由谁修复」。缺少向 role-后端开发 路由的显式指令，导致修复方不清晰。

**修改内容**：
- 修改：Step 0.2 规则区段 → 在「所有❌必须解决后才继续」后追加：若❌属于后端字段问题，暂停前端工作并明确告知「需要 role-后端开发 修复字段后再继续」，不得自行适配
- 备份路径：`history/SKILL_v1.5_20260322_before_pd015.md`

**验证方法**：前端联调发现字段不匹配时，Skill 应输出「需要 role-后端开发 修复」而非静默等待
**验证状态**：🔵 待验证

---

### v1.4 → v1.5 — 2026-03-22 — 新增 FE-15「Write 覆写后 StrReplace 回退问题」+ Step -1 A.1 codebase-explorer 门禁硬化

**根因**：
- FE-15：tashan-openbrain 开发中，Write 覆写文件后立即执行基于旧内容的 StrReplace，导致文件回退到旧结构混入新结构的混乱状态。根因是新旧文件有重叠段落，old_string 匹配成功但作用于错误版本。
- codebase-explorer 门禁：2026-03-21 tashan-openbrain 开发中，AI 忽略了「建议先运行 codebase-explorer」的软提示，直接开始实现，导致重复实现已有模块。

**修改内容**：
- 新增：FE-15 条目（症状/根因/修复方案）到 knowledge/前端踩坑速查.md
- 新增：SKILL.md 核心踩坑摘要表格第7条（FE-15 简要描述）
- 修改：Step -1 A.1「无」分支从软建议改为强警告，「直接做」时必须在回复中声明跳过代码库感知的风险

**验证结果**：
- 正向验证：Write 覆写后，后续 StrReplace 前必须先 Read 验证（待真实场景验证）
- 正向验证：codebase-explorer 无记录且用户说「直接做」时，回复中必须出现 ⚠️ 警告声明（待验证）
- 负向验证：正常单文件编辑流程（Read → StrReplace）不受影响

**验证状态**：🔵 待验证
---

### 踩坑 FE-12：wsId=null 导致发送按钮点击无反应（静默失败）

**场景**：token resolver 把 localStorage 放最高优先级 → localStorage 里是旧 token → getWorkspaces() 返回 401 → wsId 永远 null → handleSend 第一行 return，无任何提示

**根因**：localStorage token 优先级过高 + wsId=null 时无用户提示

**解决**：
1. Token 优先级改为：URL hash → Tauri IPC → .env → localStorage（localStorage 降为最低）
2. getWorkspaces 失败时清缓存重试
3. wsId=null 时给用户明确错误提示，不能静默 return

**预防**：所有前端「发送/提交」操作必须在 wsId=null 时显示明确错误状态，禁止静默失败

---

### 2026-03-23 — 追加踩坑 FE-12（wsId=null 静默失败）

**根因**：project-retrospective-20260323 批量沉淀积压经验。

**修改内容**：
- 新增：踩坑摘要表格第8条（FE-12 简要描述）
- 新增：「踩坑 FE-12」详细条目（场景/根因/解决/预防）

**备份路径**：`history/SKILL_before_20260323.md`

**验证状态**：🔵 待验证

---

### v1.6 → v1.7 — 2026-03-23 — 两项改进合并（BE路由+任务日志强制收尾）

**修改内容**：
- （2026-03-22）Step 0.2 规则区段补 BE 修复路由：发现❌后端字段问题时，明确告知「需要 role-后端开发 修复字段后再继续前端联调」，不得自行适配（GAP-PD015-1 修复）
- （2026-03-23）经验感知钩子末尾新增「强制收尾——写入任务日志」3步工具调用协议（Fix B，与11个角色同步更新）

**备份路径**：
- `history/SKILL_v1.6_20260323_before_changelog_fix.md`

**注**：本条变更记录补录于 2026-03-23，实际代码改动发生于上述两个日期。

**验证状态**：🔵 待验证

---

### 踩坑 FE-16：模块变量与 React useCallback deps 竞态

**场景**：分身切换后，DocTree / 数据列表仍显示旧分身的内容，切换回调被覆盖。

**根因**：
```
loadDocs = useCallback(() => { fetch(getDocRegistry()) }, [user, navView])
useEffect(() => { loadDocs(); setActiveTwinWorkspace('') }, [user, loadDocs])
```
`navView` 进入 `useCallback` deps → `navView` 变化时 `loadDocs` 引用更新 → `useEffect` 重跑 → `setActiveTwinWorkspace('')` 覆盖了 `onSwitch` 刚写入的新 workspace → 组件读到空 workspace，仍拉旧数据。

**三条正确模式（已验证）**：

1. **useCallback 只保留必要 deps，将模块变量与 re-render 解耦**
   ```typescript
   // ❌ 错误：navView 进入 deps，导致 useEffect 在切换时重跑并重置模块变量
   const loadDocs = useCallback(() => { fetch(getDocRegistry()) }, [user, navView])

   // ✅ 正确：只保留真正影响 fetch 逻辑的 deps
   const loadDocs = useCallback(() => { fetch(getDocRegistry()) }, [user])
   ```

2. **切换回调直接 async fetch，不走依赖模块变量的共享 loader**
   ```typescript
   // ❌ 错误：setActiveTwinWorkspace 与 loadDocs 之间存在竞态
   const onSwitch = (ws: string) => {
     setActiveTwinWorkspace(ws)
     loadDocs()  // loadDocs 读的是旧 _activeTwinWorkspace
   }

   // ✅ 正确：直接 fetch 并显式传 workspace header，不依赖时序
   const onSwitch = async (ws: string) => {
     setActiveTwinWorkspace(ws)
     const res = await fetch(url, { headers: { 'X-Twin-Workspace': ws } })
     setDocRegistry(await res.json())
   }
   ```

3. **行为标记（如「是否首次登录」）用 `useRef` 而非 `useState`**
   ```typescript
   // ❌ 错误：state 变化触发 re-render，可能连锁触发其他 effect
   const [isFirstLogin, setIsFirstLogin] = useState(false)

   // ✅ 正确：useRef 不触发 re-render，适合纯行为标记
   const isFirstLoginRef = useRef(true)
   // 使用：isFirstLoginRef.current = false  （不触发 render）
   ```

**预防**：
- 写 `useCallback` 时，逐个问 deps 中的每个变量：「它的变化是否需要 useEffect 重跑？」
- 若 deps 中某变量只是「读取时需要最新值」而非「变化时需要重新执行」→ 用 `useRef` 存储而非放入 deps
- 切换类回调（workspace / user 切换）应优先自包含：直接 fetch + 明确传参，而非调用共享 loader

---

### v1.7 → v1.8 — 2026-03-23 — 新增 FE-16「模块变量与 useCallback deps 竞态」

**根因**：tashan-openbrain_myagent 项目（TASK-20260322-100），分身切换后 DocTree 不更新 Bug。`loadDocs` 的 `useCallback` 将 `navView` 纳入依赖，导致 `useEffect` 在切换分身时重跑并调用 `setActiveTwinWorkspace('')`，覆盖了 `onSwitch` 刚写入的新 workspace，DocTree 仍读旧分身文档。

**经验核心**：`useCallback` deps 不等于「函数体内用到的所有变量」——只放「需要 effect 重跑」的变量；切换回调应直接 fetch + 明确传参，不依赖模块变量时序；行为标记用 `useRef`。

**修改内容**：
- 新增：踩坑摘要表格第9条（FE-16 简要描述）
- 新增：「踩坑 FE-16」详细条目（场景/根因/三条正确模式/预防）

**验证结果**：
- 正向验证：实测修复后分身切换立即刷新 DocTree，原 Bug 消失（已验证）
- 负向验证：`useCallback` 依赖 `user` 的正常 refresh 逻辑不受影响

**备份路径**：`history/SKILL_v1.7_20260323.md`

**验证状态**：✅ 已验证（tashan-openbrain_myagent TASK-20260322-100）

---

### v1.8 → v1.9 — 2026-03-23 — 新增 FE-17「localStorage token key 跨文件不一致 → 无 auth header」

**根因**：project-retrospective retro-20260322d-b02；tashanbrain v2 迁移实战（2026-03-22），QA 第一轮发现。

**经验核心**：`AuthContext` 用 key A 写入 token，`api.ts` 用 key B 读取 → `getAuthHeader()` 恒为空 → 所有需认证请求 401，且无 JS 报错，极难肉眼定位。

**修改内容**：
- 新增：核心踩坑摘要表格第 10 条（FE-17 简要描述）

**验证结果**：
- 正向验证：迁移/联调登录后若出现「全线 401 + Authorization 空」，优先核对全仓 localStorage key 是否一致 → 待验证
- 负向验证：单一常量 `TOKEN_KEY` 贯通读写时行为不变

**备份路径**：`history/SKILL_v1.8_20260323.md`

**验证状态**：🔵 待验证

