# TCOC 复盘 SOP

> 使用 Investibook 对 TCOC / O11Y 工单进行复盘的标准操作流程。
> 每次复盘产出结构化评估文档，存放在 `tools/replayer/cases/{issue_id}/` 目录下。

## 目的

1. 评估 Investibook 在真实故障诊断中的质量表现
2. 发现具体的 skill 缺陷，生成可落地的改进项
3. 构建 Auto Replayer 验证集（同一份 `expected.md` 作为回归基线）

## 环境准备

### 前提条件

- 已 clone investibook 仓库
- 已安装 Python 3.12+、uv、Node.js 18+、pnpm
- 拥有 PingCAP AWS SSO 账号

### 1. AWS Bedrock 配置

Investibook 使用 OpenCode + AWS Bedrock 调用 Claude 模型。需要配置 Bedrock bearer token：

```bash
# 方式一：环境变量（推荐）
export AWS_BEARER_TOKEN_BEDROCK="<your-token>"
export AWS_REGION="us-east-1"

# 方式二：AWS SSO 登录后获取临时凭据
aws sso login --profile sso
```

OpenCode 的模型配置在 `~/.config/opencode/opencode.jsonc`，默认使用 Claude Opus 4.6 via Bedrock：

```jsonc
{
  "model": "chat/arn:aws:bedrock:us-east-1:...:application-inference-profile/...",
  "provider": {
    "chat": {
      "npm": "@ai-sdk/amazon-bedrock",
      "options": { "region": "us-east-1" }
    }
  }
}
```

### 2. 后端环境变量

```bash
cd backend

# 首次：从模板创建 .env.local
make setup-env

# 编辑 .env.local，填入：
#   AUTH0_CLIENT_ID    — 前端 SSO（公开值，见 .env.example）
#   AUTH0_DOMAIN       — 后端验证 SSO token
#   ADMIN_EMAILS       — 管理员邮箱白名单

# 编辑 .env，填入（python-dotenv 自动加载）：
#   INVESTIBOOK_JIRA_BASE_URL
#   INVESTIBOOK_JIRA_USERNAME
#   INVESTIBOOK_JIRA_API_TOKEN
#   INVESTIBOOK_CLINIC_ENDPOINT
#   INVESTIBOOK_CLINIC_API_KEY
```

`INVESTIBOOK_CLINIC_API_KEY` 会在启动 agent 子进程时自动注入为 `CLINIC_API_KEY`，skill 里的 `curl` 命令通过 `${CLINIC_API_KEY}` 引用。

### 3. 安装依赖并启动

```bash
# 从项目根目录
make sync-skills   # 同步 skill 文件
make install       # 安装前后端依赖

# 终端 1：启动后端（端口 8000）
make api

# 终端 2：启动前端（端口 3000）
make web
```

### 4. 验证

```bash
# 后端健康检查
curl -s http://localhost:8000/api/config | python3 -m json.tool

# 如果没有 Auth0，可以用脚本创建本地用户
cd backend && .venv/bin/python3 -c "
from investibook.db.queries import _db_init, _db_get_or_create_user, _db_create_user_session, _hash_password, _db_connect, _db_lock
_db_init()
user = _db_get_or_create_user('dev@localhost')
pw_hash = _hash_password('dev123')
with _db_lock:
    conn = _db_connect()
    conn.execute('UPDATE users SET password_hash = ? WHERE id = ?', (pw_hash, user['id']))
    conn.commit()
token = _db_create_user_session(user['id'])
print(f'Login: dev@localhost / dev123')
print(f'Token: {token}')
"
```

## 流程

### 第 0 步：使用 Auto Replayer Skill（推荐）

如果你在 Claude Code 中操作，可以直接使用 `tools/replayer/SKILL.md` 来自动化第 1-3 步：

```
使用 auto-replayer skill 对 O11Y-2328599 进行复盘
```

skill 会自动完成：创建 workspace -> 添加 Jira source -> 等待数据准备 -> 发送诊断 prompt -> 等待完成 -> 提取 tool calls 和诊断文本 -> 按模板生成初始评估。

手动引导（第 2 步）和后续步骤仍需人工完成。

### 第 1 步：运行诊断

**方式一：Web UI**

打开 `http://localhost:3000`，登录后创建 workspace，添加 Jira source，在聊天框发送：

```
Use tidb-clinic-diagnosis to analyze this alert. Provide root cause, evidence chain, and recommendations.
```

**方式二：API（可脚本化）**

```bash
TOKEN="<your-token>"  # 从登录接口获取或用上面的脚本生成

# 1. 创建 workspace
WS_ID=$(curl -s -X POST http://localhost:8000/api/workspaces \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "O11Y-2328599 postmortem"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

# 2. 添加 Jira source
curl -s -X POST "http://localhost:8000/api/workspace/$WS_ID/sources" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type":"jira_issue","name":"O11Y-2328599","issueId":"O11Y-2328599","status":"loading"}'

# 3. 准备数据（获取 Jira + 集群元数据）
TASK_ID=$(curl -s -X POST "http://localhost:8000/api/sources/prepare" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"workspace_id\":\"$WS_ID\",\"issue_id\":\"O11Y-2328599\"}" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['task_id'])")

# 4. 等待准备完成
while true; do
  STATUS=$(curl -s "http://localhost:8000/api/sources/prepare/$TASK_ID" \
    -H "Authorization: Bearer $TOKEN" \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "error" ] && echo "Prepare failed" && exit 1
  sleep 3
done

# 5. 发送诊断 prompt（SSE 流式，最长 10 分钟）
curl -s -N --max-time 600 \
  "http://localhost:8000/api/chat/stream?message=Use%20tidb-clinic-diagnosis%20to%20analyze%20this%20alert.&workspace_id=$WS_ID&token=$TOKEN" \
  > /tmp/replay-$WS_ID.txt

# 6. 读取完整消息
curl -s "http://localhost:8000/api/workspace/$WS_ID/messages" \
  -H "Authorization: Bearer $TOKEN" > /tmp/messages-$WS_ID.json
```

建议将 workspace 设为共享，方便他人 review。

### 第 2 步：人工引导深入分析

Agent 的首轮诊断通常不完整，需要人工介入引导其逐步深入。在对话中持续追问，直到 Agent 给出完整的诊断结论：

1. **检查数据源覆盖**：Agent 是否查询了 Metrics、TopSQL、SlowQuery、Loki 日志？遗漏的数据源需要明确提示 Agent 去查。
2. **纠正错误归因**：如果 Agent 将根因归到次要因素（如 ANALYZE），引导其关注主要负载来源。
3. **要求交叉验证**：单一数据源的结论不可靠，提示 Agent 用其他数据源验证。
4. **推动量化分析**：引导 Agent 从定性描述（"用了全表扫描"）转向定量分析（"表 C 耗时 15 分钟，占总时间 83%"）。
5. **确认可操作建议**：最终诊断需包含具体的优化建议（如添加哪个索引、修改哪个配置）。

> 这一步中所有的人工干预都需要在第 4 步详细记录，作为 skill 改进的输入。

### 第 3 步：评估最终诊断

将 Agent 经引导后的最终诊断结论与实际根因（来自 Jira 评论、oncall 交接或专家分析）进行对比，按以下 5 个维度打分（1-5 分）：

| 维度 | 1 分（不合格） | 3 分（部分达标） | 5 分（优秀） |
|------|---------------|-----------------|-------------|
| **根因准确性** | 根因错误 | 找到了一个贡献因素，但遗漏了主要根因 | 准确定位主要根因 |
| **证据完整性** | 遗漏关键数据源（如未查 TopSQL、未查 SlowQuery） | 采集了大部分数据，但遗漏一个关键来源 | 所有相关数据源均已查询和分析 |
| **因果链** | 无因果推理，仅罗列发现 | 有部分因果关联但链条存在断裂 | 清晰的 A->B->C 链条，从基础设施到用户可见症状 |
| **可操作性** | 无可操作建议 | 泛泛的建议（如"加个索引"） | 具体、可实施的建议，包含 SQL/配置 |
| **效率** | >30 次工具调用，大量重试/死胡同 | 15-30 次工具调用，有部分浪费 | <15 次工具调用，聚焦且系统化 |

### 第 4 步：记录人工引导

列出第 2 步中每一个需要人工干预引导 Agent 的节点。每次干预需记录：

- **你说了什么**（原始 prompt）
- **Agent 当时做错了什么**（错误行为）
- **Agent 应该怎么做**（正确行为）
- **涉及哪个 skill**（tidb-clinic-diagnosis / clinic-api / system prompt / 其他）

### 第 5 步：识别 skill 缺陷

将每个缺陷按类型分类：

| 类型 | 说明 | 示例 |
|------|------|------|
| **FLOW** | 诊断流程中缺失或错误的步骤 | 标准流程中未查询 TopSQL |
| **TEMPLATE** | 查询模板错误或不完整 | TopSQL SQL 使用了不存在的列名 |
| **KNOWLEDGE** | 缺少 schema/API 知识 | 不知道 TopSQL 表中 `timestamps` 字段的含义 |
| **REASONING** | 过早收敛或错误的分析模式 | 聚焦表 A 的问题，而表 C 占了 83% 的执行时间 |
| **CROSS-REF** | 未交叉验证数据源 | 已有 TopSQL 数据但未与 SlowQuery 关联 |

### 第 6 步：与缺陷 Backlog 去重

撰写复盘前，先检查 `docs/plans/2026-03-04-backlog.md`（Milestone #2 下的 **Skill 缺陷 Backlog** 表格）中是否已有相同缺陷。对第 5 步发现的每个缺陷：

- **如果 Backlog 中已存在**：不要重复创建条目，在已有条目的 `Reproduced in` 列表中追加本次 case 的 issue_id，确认其为系统性问题。
- **如果是新缺陷**：分配下一个可用编号（如 `D10`），添加到 Backlog。

此步骤防止多人复盘不同 case 时提交重叠的改进项，并确保跨 case 的可见性。

### 第 7 步：撰写复盘文档

使用模板创建 `tools/replayer/cases/{issue_id}/postmortem.md`。
创建 `tools/replayer/cases/{issue_id}/expected.md`，写入正确的诊断结论（作为 replayer 基线）。
在 `tools/replayer/cases.yaml` 中添加条目。

### 第 8 步：提交 skill 改进 PR

针对每个缺陷，选择以下方式之一：
- 直接修复 skill 并引用复盘文档
- 或创建 GitHub issue，标记 `skill-improvement` 标签，附上缺陷详情

---

## 复盘模板

标准模板位于 `tools/replayer/cases/TEMPLATE.md`，创建新复盘时复制该文件。

```
tools/replayer/cases/{issue_id}/
  postmortem.md   # 完整分析（基于模板）
  expected.md     # 正确诊断（replayer 基线）
```

## cases.yaml 格式

```yaml
cases:
  - issue_id: "O11Y-2328599"
    prompt: "Use tidb-clinic-diagnosis to analyze this alert. Provide root cause, evidence chain, and recommendations."
    cluster_type: "dedicated"
    tags: ["expensive-query", "slow-query", "hotspot"]
    date_added: "2026-04-07"
```
