LinkMed-Claude-Code-自动研发使用指南.md 32.1 KB

LinkMed Claude Code 自动研发使用指南

借助 Claude Code 实现从需求描述到代码实现、自动验证的完整研发流程,减少人工手动验证环节。


目录

  1. 快速开始
  2. 整体工作流程
  3. 多端协作架构
  4. 使用方式
  5. 从 TAPD 获取任务详情
  6. TAPD 脚本安装配置
  7. 测试体系
  8. 单元测试规范(Vitest)
  9. E2E 测试规范(Playwright)
  10. 提交规范
  11. dev-sessions 文档规范
  12. 后续规划

1. 快速开始

启动方式

# 普通模式(每步操作需手动确认)
claude

# 全自动模式(跳过所有权限确认,适合批量任务)
claude --dangerously-skip-permissions

# 全自动模式 + 直接传入任务
claude --dangerously-skip-permissions -p "帮我打包并提交代码"

⚠️ --dangerously-skip-permissions高风险模式,Claude 可直接执行文件读写、Git 操作、终端命令,不会弹出确认框,包括删除文件、强制推送、覆盖数据等破坏性操作。仅在本地受信任环境中使用,不要在生产服务器或共享环境中开启。建议配合 CLAUDE.md 中的约束说明,明确告知 Claude 哪些操作是禁止的。


2. 整体工作流程

                    ┌─────────────────────────────────────────────────────┐
                    │  1. 获取任务(需求 / 缺陷,含图片和评论)                 │
                    │     · 直接描述需求/缺陷内容                             │
                    │     · 或提供 TAPD ID,脚本自动拉取详情+评论              │
                    │       需求:node ~/.claude/tapd.mjs story <ID>         │
                    │       缺陷:node ~/.claude/tapd.mjs bug <ID>           │
                    │     · 从描述/评论中提取图片 URL,下载并阅读              │
                    │       COOKIE=$(cat ~/.claude/tapd-cookie.txt)         │
                    │       curl -s -L -H "Cookie: $COOKIE" \               │
                    │         -H "Referer: https://www.tapd.cn/" <URL>      │
                    │         -o /tmp/req_img.png                           │
                    │     · 评论中若有关键信息(设计说明、补充要求等)          │
                    │       必须纳入分析,写入 dev-sessions 文档              │
                    └─────────────────────┬───────────────────────────────┘
                                          │ 逐个取出
                    ┌─────────────────────▼───────────────────────────────┐
                    │  2. 分析与规划                                        │
                    │     · 理解需求目标和验收标准                            │
                    │     · 阅读相关代码,理解现有架构和模块                   │
                    │     · 制定实现方案,必要时与开发者确认                   │
                    └─────────────────────┬───────────────────────────────┘
                                          ↓
                    ┌─────────────────────────────────────────────────────┐
                    │  3. 实现代码                                          │
                    │     · 每修改完一个文件,立刻运行 lint 和 type-check      │
                    │       npm run lint && npm run type-check              │
                    │     · 有错误自动修复,无需人工干预,修完再继续            │
                    │     · 遵循项目现有代码规范和架构模式                     │
                    └─────────────────────┬───────────────────────────────┘
                                          ↓
                    ┌─────────────────────────────────────────────────────┐
                    │  4. 单元测试(Vitest)                                 │
                    │     · 针对 Store 逻辑、工具函数编写/更新测试            │
                    │     · npm run test:unit                              │
                    │     · 测试文件位于 src/test/**/*.test.ts               │
                    └──────────┬──────────────────────────┬───────────────┘
                               ↓ 全部通过                  ↓ 有失败
                               │                  ┌───────────────────────┐
                               │                  │  定位失败原因           │
                               │                  │  → 修复代码或测试用例   │
                               │                  │  → 重新运行测试         │
                               │                  └──────────┬────────────┘
                               │                             ↑
                               │                             └── 循环直到全部通过
                    ┌─────────────────────────────────────────────────────┐
                    │  5. E2E 测试(Playwright)                             │
                    │     · 针对需要真实浏览器的交互场景编写测试               │
                    │     · 存放至 e2e/tests/{模块}/{需求ID}-描述.spec.ts     │
                    │     · npm run test:e2e 自动运行验证                    │
                    └──────────┬──────────────────────────┬───────────────┘
                               ↓ 全部通过                  ↓ 有失败
                    ┌──────────────────────┐   ┌──────────────────────────┐
                    │  6. 逐条核查验收标准   │   │  定位失败原因              │
                    │  · 对照需求验收标准    │   │  → 修复代码或测试用例       │
                    │    逐条确认代码实现    │   │  → 重新运行测试             │
                    │  · 发现遗漏则补充实现  │   └────────────┬─────────────┘
                    └──────────┬───────────┘                ↑
                               ↓                            └── 循环直到全部通过
                    ┌──────────────────────────────────────────────────────┐
                    │  7. 提交并推送代码(每次必做,无需等待用户提醒)          │
                    │     · git commit(含功能代码、测试、文档)               │
                    │     · git push                                        │
                    │     · 更新 dev-sessions 过程记录文档                    │
                    └──────────┬───────────────────────────────────────────┘
                               │                            ↑
                               │                            └── 循环直到全部通过
                               ↓ 还有下一个需求
                    ┌──────────────────────┐
                    │  返回步骤 1,处理      │
                    │  下一个需求           │
                    └──────────────────────┘
                               ↓ 所有需求完成
                            全部完成 ✅

多个需求串行执行,每个需求测试通过并提交后才开始下一个,保证每次提交都是可验证的最小单元。

全自动执行,任何情况不打断用户,不询问确认,自行决策完成任务。

⚠️ 测试通过 ≠ 验收标准全部满足。E2E 测试通常只能拦截 30~40% 的潜在 bug(结构性问题),功能细节(如插入块的类型是否正确)需要步骤 6 的人工核查来补充。


3. 多端协作架构

每个端由独立的 Claude Code 机器人负责,各自在自己的工作目录中完成实现。

                  ┌──────────────────────────────────────────────────────┐
                  │                    需求 / 缺陷                        │
                  │            (TAPD 需求描述 + 验收标准)                │
                  └──────────┬──────────────────┬────────────────┬───────┘
                             │                  │                │ 按涉及端分配
                             ▼                  ▼                ▼
           ┌─────────────────────┐  ┌──────────────────────┐  ┌──────────────────────┐
           │   🤖 后端机器人      │  │    🤖 前端机器人       │  │  🤖 管理后台机器人    │
           │                     │  │                      │  │                      │
           │   backend/          │  │   linkmed-vue3/      │  │   linkmed-admin/     │
           │   Java 21           │  │   Vue 3 + Vite       │  │   Java 21 + Vue 3    │
           │   Spring Boot 8181  │  │   TypeScript 5173    │  │   Spring Boot / 5174 │
           └──────────┬──────────┘  └──────────┬───────────┘  └──────────┬───────────┘
                      │                         │   ↑                     │   ↑
                      │   涉及多端时,            │   │ 仅在后端就绪后        │   │ 仅在后端就绪后
                      ├──── 接口就绪,通知 ───────┘   │ 才接入联调           │   │ 才接入联调
                      └──── 接口就绪,通知 ────────────────────────────────┘
                      │                         │                         │
                      ▼                         ▼                         ▼
                   提交推送                   提交推送                  提交推送

核心原则:涉及前后端联调的需求,后端先行。 后端机器人完成接口开发并推送后,前端/管理后台机器人再接入联调,避免接口未就绪时空转。


4. 使用方式

向 Claude Code 描述需求/缺陷时附带验证意图,Claude Code 会自动完成实现 + 测试:

示例:"实现文件上传大小限制为 100MB,完成后帮我验证"
示例:"修复切换深度检索历史触发 cancel 请求的 bug,验证修复正确"
示例:"关闭 Dig Paper 知识库入口,确认页面上不再显示"

也可以直接提供 TAPD 任务 ID,Claude Code 会通过脚本自动拉取详情(需求或缺陷均可):

示例:"处理需求 1008229"
示例:"处理缺陷 1008906"
示例:"按顺序完成需求 1008251、1008252、1008253"

5. 从 TAPD 获取任务详情

查看需求列表

# LinkMed 项目最新需求(默认)
node ~/.claude/tapd.mjs stories

# 关键词搜索
node ~/.claude/tapd.mjs stories 67139335 知识库

输出示例:

Total: 614 | Showing: 20
[1008229] 知识库-对解析失败的文件新增重试按钮和功能
  status=developing owner=张倩如;尹帮会; iteration=0.6.26.0(当前迭代)
[1008263] 【工作台】对话记录的优化
  status=planning owner=尹帮会; iteration=0.6.27.0

查看单个需求详情

通过需求的 short_id(TAPD 列表中括号内的编号)获取完整信息:

node ~/.claude/tapd.mjs story 1008229

输出包含需求标题、描述、验收标准、状态、负责人等字段。

查看缺陷(Bug)详情

node ~/.claude/tapd.mjs bug 1008906

输出包含缺陷标题、描述、重现步骤、严重程度、优先级、状态,以及所有评论和回复

评论的重要性

评论中常包含以下关键信息,必须阅读

  • 产品/后端对实现方案的补充说明(如 TOS 路径、接口格式)
  • 对原始描述的修正或追加要求
  • 评论中的截图(格式同描述图片,需下载阅读)
  • 关键约束(如「仅预览,不要支持修改!!!!」)

脚本已自动输出评论内容,格式:

--- 评论(N 条)---
[时间] 作者:内容
  ↳ [时间] 作者:回复内容(缩进表示回复)
  [图片: URL](评论中的截图)

在工作流中的用法

告知 Claude Code 任务 ID 后,Claude Code 会自动判断类型并调用对应命令:

# Claude Code 内部自动调用,无需手动执行
node ~/.claude/tapd.mjs story <需求ID>   # 需求
node ~/.claude/tapd.mjs bug <缺陷ID>     # 缺陷

下载需求中的图片

TAPD 需求描述中常含有设计图,格式为 [图片: https://file.tapd.cn/...]必须下载并阅读,不能仅凭文字描述实现。

COOKIE=$(cat ~/.claude/tapd-cookie.txt)
curl -s -L \
  -H "Cookie: $COOKIE" \
  -H "Referer: https://www.tapd.cn/" \
  -H "User-Agent: Mozilla/5.0" \
  "https://file.tapd.cn//tfl/captures/..." \
  -o /tmp/req_<需求ID>_1.png
# 然后用 Read 工具读取图片内容

Cookie 过期处理

登录态通常可持续数周。若脚本报错或返回空数据,重新登录即可:

node ~/.claude/tapd.mjs login your@linkingmed.com yourpassword

常用项目 ID

项目名 workspace_id
LinkMed 67139335
AI公共平台(AiPlan) 21580481
科研项目管理 38189866
专病数据库 59607085
RAIC.OIS信息管理系统 58951789

完整列表通过 node ~/.claude/tapd.mjs projects 获取。


6. TAPD 脚本安装配置

前置要求

Node.js 18+(使用内置 fetchcrypto,无需安装任何 npm 包):

node -v   # 需要 v18.0.0 以上

如果版本过低,可通过 fnmnvm 升级:

# fnm
fnm install 20 && fnm use 20

# nvm
nvm install 20 && nvm use 20

安装脚本

mkdir -p ~/.claude
nano ~/.claude/tapd.mjs   # 或用 VS Code: code ~/.claude/tapd.mjs

将以下内容保存为 ~/.claude/tapd.mjs

#!/usr/bin/env node
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import { randomBytes, createCipheriv } from 'crypto';

const COOKIE_FILE = join(homedir(), '.claude', 'tapd-cookie.txt');
const DEFAULT_WS = '67139335'; // LinkMed 项目
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';

const jar = {};
function loadCookie() {
  if (existsSync(COOKIE_FILE)) {
    for (const kv of readFileSync(COOKIE_FILE, 'utf8').trim().split('; ')) {
      const [k, ...v] = kv.split('=');
      if (k) jar[k.trim()] = v.join('=').trim();
    }
  }
}
function saveCookie() {
  writeFileSync(COOKIE_FILE, Object.entries(jar).map(([k, v]) => `${k}=${v}`).join('; '));
}
function getCookieStr() { return Object.entries(jar).map(([k, v]) => `${k}=${v}`).join('; '); }
function parseCookies(resp) {
  for (const c of (resp.headers.getSetCookie?.() || [])) {
    const eqIdx = c.indexOf('=');
    const key = c.slice(0, eqIdx).trim();
    const val = c.slice(eqIdx + 1).split(';')[0].trim();
    if (val && val !== 'deleted') jar[key] = val;
    else delete jar[key];
  }
}

async function req(url, opts = {}) {
  const resp = await fetch(url, {
    ...opts,
    headers: { 'User-Agent': UA, 'Cookie': getCookieStr(), ...(opts.headers || {}) },
    redirect: 'manual',
  });
  parseCookies(resp);
  return resp;
}
async function get(path) {
  const r = await req(`https://www.tapd.cn${path}`, {
    headers: { 'X-Requested-With': 'XMLHttpRequest', Accept: 'application/json,*/*' }
  });
  return r.json();
}
async function post(path, body, wsId = DEFAULT_WS) {
  const r = await req(`https://www.tapd.cn${path}`, {
    method: 'POST',
    headers: {
      'X-Requested-With': 'XMLHttpRequest', Accept: 'application/json,*/*',
      'Content-Type': 'application/json',
      Referer: `https://www.tapd.cn/tapd_fe/${wsId}/prong/stories`,
      Origin: 'https://www.tapd.cn',
    },
    body: JSON.stringify(body),
  });
  return r.json();
}

function aesEncrypt(password) {
  const key = randomBytes(32), iv = randomBytes(16);
  const buf = Buffer.from(password, 'utf8');
  const pad = 16 - (buf.length % 16);
  const padded = pad === 16 ? buf : Buffer.concat([buf, Buffer.alloc(pad, 0)]);
  const cipher = createCipheriv('aes-256-cbc', key, iv);
  cipher.setAutoPadding(false);
  const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]);
  return { ciphertext: encrypted.toString('base64'), key: key.toString('base64'), iv: iv.toString('base64') };
}

async function login(email, password) {
  await req('https://www.tapd.cn/cloud_logins/login?site=TAPD&ref=https%3A%2F%2Fwww.tapd.cn%2F');
  const enc = aesEncrypt(password);
  const form = new URLSearchParams({
    'data[Login][email]': email, 'data[Login][password]': enc.ciphertext,
    'data[Login][encrypt_key]': enc.key, 'data[Login][encrypt_iv]': enc.iv,
    'data[Login][via]': 'encrypt_password', 'data[Login][type]': '2',
    'data[Login][ref]': 'https://www.tapd.cn/', 'data[Login][site]': 'TAPD',
    'data[Login][login]': 'login', 'data[protocol]': '1',
  });
  const p2 = await req('https://www.tapd.cn/cloud_logins/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded', Origin: 'https://www.tapd.cn', Referer: 'https://www.tapd.cn/cloud_logins/login' },
    body: form.toString(),
  });
  let location = p2.headers.get('location');
  while (location) {
    const r = await req(location);
    location = r.headers.get('location');
  }
  saveCookie();
  return !!jar['_wt'];
}

const [,, cmd, arg1, arg2] = process.argv;
loadCookie();

if (cmd === 'login') {
  const ok = await login(arg1, arg2);
  console.log(ok ? 'Login successful' : 'Login failed');
} else if (cmd === 'projects') {
  const data = await get('/api/workspace/workspaces/get_all_my_projects');
  const projects = data.data?.all_my_projects || [];
  projects.forEach(p => console.log(`${p.id}\t${p.project_name}\t${p.status}`));
} else if (cmd === 'stories') {
  const wsId = arg1 || DEFAULT_WS;
  const keyword = arg2 || '';
  const body = { workspace_ids: [wsId], page: 1, page_count: 20 };
  if (keyword) body.search_data = { keyword };
  const data = await post('/api/entity/stories/story_list_by_condition', body, wsId);
  const list = data.data?.list || [];
  console.log(`Total: ${data.data?.total} | Showing: ${list.length}`);
  list.forEach(s => console.log(`[${s.short_id}] ${s.name}\n  status=${s.status} owner=${s.owner} iteration=${s.iteration_name}`));
} else if (cmd === 'story') {
  if (!arg1) { console.error('Usage: tapd.mjs story <story_id>'); process.exit(1); }
  const data = await get(`/api/entity/stories/stories/get_info?workspace_id=${arg2 || DEFAULT_WS}&story_id=${arg1}`);
  console.log(JSON.stringify(data, null, 2));
} else if (cmd === 'bugs') {
  const data = await post('/api/entity/bugs/bug_list_by_condition', { workspace_ids: [arg1 || DEFAULT_WS], page: 1, page_count: 20 }, arg1 || DEFAULT_WS);
  const list = data.data?.list || [];
  console.log(`Total: ${data.data?.total} | Showing: ${list.length}`);
  list.forEach(b => console.log(`[${b.id}] ${b.title}\n  status=${b.status} owner=${b.owner}`));
} else {
  console.log('Usage:');
  console.log('  node ~/.claude/tapd.mjs login <email> <password>');
  console.log('  node ~/.claude/tapd.mjs projects');
  console.log(`  node ~/.claude/tapd.mjs stories [workspace_id=${DEFAULT_WS}] [keyword]`);
  console.log('  node ~/.claude/tapd.mjs story <story_id> [workspace_id]');
  console.log('  node ~/.claude/tapd.mjs bugs [workspace_id]');
}

首次登录

node ~/.claude/tapd.mjs login your@linkingmed.com yourpassword
# 输出 "Login successful" 表示成功

# 验证是否正常工作
node ~/.claude/tapd.mjs projects

可选:设置别名

echo "alias tapd='node ~/.claude/tapd.mjs'" >> ~/.zshrc
source ~/.zshrc

# 之后可直接使用
tapd stories
tapd story 1008229

技术实现说明

TAPD 没有开放的公共 API,脚本通过逆向前端内部 API 实现:

登录流程: TAPD 登录页用 AES-256-CBC + ZeroPadding 加密密码,key 和 iv 连同密文一起发给服务器。脚本用 Node.js 内置 crypto 复现,无需 CAPTCHA 即可完成登录。

关键请求字段:

data[Login][email]        用户邮箱
data[Login][password]     AES 加密后的密码(base64)
data[Login][encrypt_key]  AES key(base64)
data[Login][encrypt_iv]   AES IV(base64)
data[Login][via]          encrypt_password

主要 API 接口:

接口 方法 说明
/api/workspace/workspaces/get_all_my_projects GET 获取我的项目列表
/api/entity/stories/story_list_by_condition POST 查询需求列表
/api/entity/stories/stories/get_info GET 获取需求详情
/api/entity/bugs/bug_list_by_condition POST 查询 Bug 列表

所有接口均需携带登录 cookie 并设置 X-Requested-With: XMLHttpRequest

内部 ID 构造规则:

story 命令不依赖关键词搜索,而是直接构造内部 ID:

内部 ID = '11' + workspaceId + shortId.padStart(9, '0')
示例:'11' + '67139335' + '001008260' = '1167139335001008260'

这些接口通过分析 TAPD 前端 JS bundle 发现,不在官方文档中,未来版本可能变更。


7. 测试体系

本项目采用两层测试策略,分工明确:

层级 工具 测什么 运行速度 命令
单元测试 Vitest Store 逻辑、工具函数、数据处理 毫秒级 npm run test:unit
E2E 测试 Playwright 需要真实浏览器的交互流程 分钟级 npm run test:e2e

Vitest — 把 API 替换为 mock,直接测 Store 内部的业务逻辑,不依赖浏览器和测试账号数据。适合测响应格式适配、状态变更、错误处理等纯逻辑。测试文件位于 src/test/**/*.test.ts

Playwright(E2E) — 驱动真实浏览器执行完整用户操作,适合测纯 DOM 交互(如截图拖拽、编辑器焦点)。测试文件位于 e2e/tests/


8. 单元测试规范(Vitest)

文件位置

src/test/stores/   # Store 测试
src/test/utils/    # 工具函数测试

适合写单元测试的场景

  • Store action 的成功/失败分支逻辑
  • 响应数据的格式适配(多种格式兼容)
  • 复杂的纯函数(如数学公式预处理)
  • 状态变更后 computed 是否正确

不适合写单元测试的场景

  • 需要真实浏览器渲染的 UI 交互
  • 依赖第三方富文本编辑器(BlockSuite)的行为

9. E2E 测试规范(Playwright)

文件命名

e2e/tests/{模块}/{需求ID}-{简短描述}.spec.ts

示例:

e2e/tests/welcome/1008878-cancel-api.spec.ts
e2e/tests/agent/1008229-file-upload-limit.spec.ts

文件头部模板

/**
 * 需求 ID:1008878
 * 需求描述:切换深度检索历史不应触发 cancel 请求
 *
 * 验收标准:
 * 1. 在 Welcome 页面点击历史记录,POST /cancel 不被调用
 * 2. 主动点击终止按钮时,POST /cancel 正常调用
 */

断言强度要求

E2E 测试断言应尽量验证功能正确性,而不只是结构存在性

场景 弱断言(不推荐) 强断言(推荐)
插入块 afterCount > beforeCount 验证新块的 data-block-flavour / data-level 等属性
按钮点击 expect(btn).toBeVisible() 验证点击后产生的实际效果
表单提交 无错误即通过 验证提交后页面状态/数据变化

运行前检查清单

# 1. 确认 dev server 指向当前项目(pc1 用 5175,避免与 pc/ 的 5173 冲突)
lsof -i :5175 | grep node
# 若没有,启动:node node_modules/.bin/vite --port 5175 &

# 2. 确认 playwright.config.ts 中 baseURL 与上面端口一致
grep baseURL playwright.config.ts

工作台(Workspace)测试注意事项

工作台使用堆叠 tab 模式,所有已打开文件的组件同时存在于 DOM,只用 CSS 控制显示:

// ❌ 错误:会匹配所有 tab 中的元素
page.locator(".icon-toolbar .icon-btn")

// ✅ 正确:限定到当前激活的 tab
page.locator(".tab-content-layer.active .icon-toolbar .icon-btn")

打开编辑器时应点击已有文件,不要用"新建文件"按钮(新建会触发 AI 文档生成面板,AffineEditor 不渲染):

const mdFile = page.locator(".node-content.is-file").filter({
  has: page.locator(".node-name").filter({ hasText: /\.md$/i }),
}).first();
await mdFile.click();
await page.waitForSelector(".tab-content-layer.active .editor-toolbar", { timeout: 20000 });

需要在编辑器中建立光标位置时,要点击 rich-text 元素并按 End 键(仅点击容器不能建立 BlockSuite TextSelection):

const richText = page.locator(".tab-content-layer.active rich-text, .tab-content-layer.active affine-paragraph").first();
await richText.click();
await page.keyboard.press("End");

测试完成后的处理

情况 处理方式
核心逻辑,后续可能被改动 保留,防止回归
简单 UI 变更,一次性验证 验证后可删除
不稳定、依赖特定数据 .skip 或删除

10. 提交规范

每次完成任务后,不等用户提醒,自动执行:

# 1. 提交所有相关改动(功能代码 + E2E 测试 + 文档)
git add <相关文件>
git commit -m "feat/fix/test/docs: <需求ID>【模块】描述"

# 2. 推送到远端
git push origin <当前分支>

# 3. 更新 dev-sessions 过程记录(按具体日期分目录)
# claude-code/dev-sessions/{YYYY-MM-DD}/{需求ID}-{描述}.md
# 示例:claude-code/dev-sessions/2026-03-19/1008906-Safari登录报错.md

提交信息格式:

类型 前缀 示例
需求 feat: feat: 1008265【工作台】编辑器新增格式化工具栏
缺陷修复 fix: fix: 1008906【Safari兼容】修复旧版Safari命名捕获组报错
测试 test: test: 1008265 E2E测试全部通过(15/15)
文档 docs: docs: 新增 1008906 缺陷执行记录

11. dev-sessions 文档规范

每个任务(需求或缺陷)执行完成后,需在 claude-code/dev-sessions/{YYYY-MM-DD}/{ID}-{简短描述}.md 创建或更新过程记录。

具体日期分目录管理(如 2026-03-19/),避免单目录文件过多。

需求文档结构

# 需求 {ID} - {标题}

## 原始需求(一字不差)
  - 需求标题、状态、负责人、优先级、创建者、时间
  - 原始描述:直接引用 TAPD 中的原文,包含图片 URL

## 图片理解
  - 每张图片单独一节,注明尺寸
  - 画面内容:客观描述看到了什么
  - 关键细节:对实现有指导意义的 UI 细节
  - 悬停/交互行为(需推断):图片未直接展示但可推断的行为

## 评论(若有)
  - 每条评论的作者、时间、内容
  - 评论中的图片需下载阅读,记录关键信息
  - 对实现有约束或补充的评论要重点标注

## 实现方案
  - 数据流图(用代码块 ASCII 表示)
  - 各修改文件的具体改动点

## 修改的文件
  - 文件路径列表

## 测试结果
  - type-check / lint 结果

## 测试覆盖
  - 单元测试(Vitest):测试文件、用例数、覆盖的测试组
  - E2E 测试(Playwright):测试文件、主要用例

缺陷(Bug)文档结构

缺陷记录侧重根因分析和修复,而非功能实现:

# 缺陷 {ID} - {标题}

## 原始需求(一字不差)
  - 缺陷标题、状态、负责人、严重程度、优先级、创建者、时间
  - 原始描述:直接引用 TAPD 中的原文,包含图片 URL(控制台截图等)
  - 重现步骤:原文引用

## 图片理解
  - 每张截图单独一节,注明尺寸
  - 画面内容:控制台报错信息、调用栈、错误来源等关键信息
  - 关键细节:对定位问题有指导意义的信息(文件名、行号、错误类型)

## 评论(若有)
  - 每条评论的作者、时间、内容
  - 评论中的图片需下载阅读,记录关键信息
  - 对修复方案有约束的评论要重点标注(如「仅预览,不要支持修改」)

## 根因分析
  - 错误类型及含义
  - 为什么会产生这个错误(配置/依赖/版本问题等)
  - 涉及文件(配置文件、出错 chunk、依赖包等)

## 修复方案
  - 修复思路(修改什么、为什么这样修)
  - 修改的文件列表及具体改动说明

## 测试结果
  - type-check / lint 结果
  - 验收条件(修复后如何验证)

图片理解要求

必须

  • 下载 TAPD 图片并用 Read 工具阅读(不能只凭图片 URL 推测)
  • 记录每张图的像素尺寸
  • 区分「图片明确展示」和「需推断」的内容

图片下载方式

COOKIE=$(cat ~/.claude/tapd-cookie.txt)
curl -s -L \
  -H "Cookie: $COOKIE" \
  -H "Referer: https://www.tapd.cn/" \
  -H "User-Agent: Mozilla/5.0" \
  "<TAPD图片URL>" -o /tmp/req_<需求ID>_<序号>.png
# 然后用 Read 工具读取图片

范例文档

参考 claude-code/workflow/dev-sessions-范例-1008257.md——该需求包含两张设计图,分别展示不同交互阶段,是目前图片理解最完整的范例:

  • 图片1:PDF 文本选中后弹出菜单(展示「Quote in Chat」入口,菜单样式、图标、定位方式)
  • 图片2:引用标签出现在对话输入框上方(展示「PDF Quote ×」chip 样式 + tooltip 行为)
  • 每张图单独分析,明确标注「需推断」的部分(如 tooltip 的触发行为)
  • 图片细节直接指导实现(chip 的颜色、按钮标签文字、删除方式)

12. 后续规划

  • 接入 TAPD,自动读取需求描述
  • 接入 GitLab CI,push 时自动触发测试
  • 测试通过后自动提交并创建 MR