devperf/AI-OKR-SYNC-PLAN.md
zyc e1396b1479
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m4s
feat(okr): 接入豆包AI自动分析Git提交生成OKR
基于豆包(Doubao) LLM 分析 git commit messages,按仓库维度自动为每个
提交人生成、更新、标记完成 OKR:

- 新增 ai_analyzed_commits 表实现增量标记,每条 commit 只分析一次
- objectives/keyResults 新增 source、sourceKey 字段区分 AI 生成与手动创建
- keyResults.status 扩展支持 completed 状态
- 新增 llm-client.ts 封装豆包 Ark API 调用(原生 fetch,零依赖)
- 新增 okr-ai-sync.ts 核心服务:按仓库分组 → 构建 prompt → 调用 AI → 执行 actions
- scheduler 在 Git 同步后自动触发 AI 分析(受 AI_ENABLED 开关控制)
- 新增 POST /api/okr/ai-analyze 手动触发和 preview 预览端点
- 防重复三层保障:commit SHA 标记 + sourceKey 去重 + 项目 OKR 上下文

已验证:501 条 commits 全量分析,生成 37 个 Objectives、164 个 Key Results,
增量去重机制正常(重复调用返回 0 actions)。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 13:29:36 +08:00

422 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AI 驱动的 OKR 自动生成与同步
## Context
DevPerf 已具备完整的 Git 同步管线Gitea → gitCommits 表)和 OKR CRUD 系统。
当前 OKR 需人工创建,缺少与 Git 提交的联动。
**目标**:每次 Git 同步后AI 分析新增 commit messages按仓库维度
- 新增 KR — commit 提到的功能点尚无对应 KR
- 更新进度 — commit 涉及已有 KR 的功能
- 标记完成 — commit 明确表示功能已完成
**分析粒度**:按仓库(每个仓库的新增 commits 做一次 AI 分析prompt 中包含提交人信息)
**OKR 归属**commit 的提交人 = KR/Objective 的负责人ownerId通过 gitCommits.userId 关联
**增量机制**:每条 commit 只分析一次,分析后打标记,后续同步不再重复处理
---
## 第一阶段Schema 变更
**文件**: `backend/src/db/schema.ts`
### 1a. 新增 `ai_analyzed_commits` 表(增量标记,防止重复分析)
```
aiAnalyzedCommits:
id varchar(50) PK
commitSha varchar(200) NOT NULL UNIQUE -- 对应 gitCommits.sha
batchId varchar(50) NOT NULL -- 一次 AI 调用的批次 ID
createdAt datetime NOT NULL
```
**核心作用**:每次同步后,只提取 `gitCommits` 中尚未出现在此表的 commit 送给 AI。分析完成后立即写入标记确保该 commit 不会被重复处理。
### 1b. objectives 表加字段
- `source` varchar(50) default `'manual'` — 值: `'manual'` | `'ai_generated'`
### 1c. keyResults 表加字段
- `source` varchar(50) default `'manual'` — 值: `'manual'` | `'ai_generated'`
- `sourceKey` varchar(300) nullable — AI 分配的功能标识符(如 `"user-login"`),用于语义去重
### 1d. keyResults.status 扩展
现有 `'active' | 'paused' | 'cancelled'`,新增 `'completed'`
字段类型是 varchar无需改 DDL只需让 `recalcObjectiveProgress` 正确处理 completed。
### 1e. syncLogs.source 枚举扩展
`mysqlEnum('source', ['plane', 'gitea'])` 改为 `['plane', 'gitea', 'ai_okr']`
---
## 第二阶段AI 配置与 LLM 客户端(豆包 Doubao
### 2a. 环境变量 (`backend/src/config.ts`)
新增:
```typescript
AI_ENABLED: z.coerce.boolean().default(false),
AI_API_KEY: z.string().default(''),
AI_MODEL: z.string().default('doubao-seed-2-0-pro-260215'),
AI_BASE_URL: z.string().default('https://ark.cn-beijing.volces.com/api/v3'),
```
**.env 示例**
```env
AI_ENABLED=true
AI_API_KEY=846b6981-9954-4c58-bb39-63079393bdb8
AI_MODEL=doubao-seed-2-0-pro-260215
AI_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
```
### 2b. 新建 `backend/src/services/llm-client.ts`
使用豆包(火山引擎 ArkAPI原生 fetch 实现,零额外依赖。
**豆包 API 调用格式**
```typescript
// POST https://ark.cn-beijing.volces.com/api/v3/chat/completions
// Header: Authorization: Bearer {AI_API_KEY}
// Header: Content-Type: application/json
export async function callLLM(systemPrompt: string, userPrompt: string): Promise<string> {
const url = `${config.AI_BASE_URL}/chat/completions`;
const body = {
model: config.AI_MODEL, // "doubao-seed-2-0-pro-260215"
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt }
],
temperature: 0.3, // 低温度保证输出稳定
response_format: { type: "json_object" } // 强制 JSON 输出
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.AI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(60000), // 60s 超时
});
if (!response.ok) {
throw new Error(`Doubao API error: ${response.status} ${await response.text()}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
```
**要点**
- 豆包 Ark API 兼容 OpenAI `/chat/completions` 格式
- 使用 `response_format: { type: "json_object" }` 强制返回 JSON
- 超时 60s失败重试 1 次(间隔 3s
- `temperature: 0.3` 保证分析结果稳定一致
---
## 第三阶段:核心服务 — `backend/src/services/okr-ai-sync.ts`
### 整体流程
```
analyzeCommitsForOKR()
├─ 1. gatherUnanalyzedCommits()
│ SELECT gc.* FROM git_commits gc
│ LEFT JOIN ai_analyzed_commits aac ON gc.sha = aac.commit_sha
│ WHERE aac.id IS NULL
│ → 按 repoName 分组(每个仓库一批)
├─ 2. 遍历每个仓库的未分析 commits
│ a. 通过 projectRepos 找到该仓库关联的 projectId
│ b. 获取该项目当前所有 Objectives + KRs含 sourceKey
│ c. 构建 AI prompt该仓库的新 commits含每条 commit 的提交人信息 + 项目现有 OKR
│ d. 调用豆包 AI → 解析 JSON 响应
│ e. 执行 AI 返回的 actions
│ - create_objective → 创建时 ownerId = commit 提交人的 userIdAI 返回 authorUserId
│ - create_kr → 先查 sourceKey 防重 → 不存在才创建,归属到对应提交人
│ - update_progress → 更新 currentValue + 写 krLog
│ - complete_kr → status='completed' + 写 krLog
│ f. 重算 Objective progress
│ g. 标记这批 commits 为已分析(写入 aiAnalyzedCommits
└─ 3. 写入 syncLogs (source='ai_okr')
```
### 增量更新机制(核心)
```
第一次同步:
gitCommits: [A, B, C, D, E]
aiAnalyzedCommits: []
→ 未分析: [A, B, C, D, E] → 全部送 AI → 分析后标记 [A, B, C, D, E]
第二次同步(新增了 F, G
gitCommits: [A, B, C, D, E, F, G]
aiAnalyzedCommits: [A, B, C, D, E]
→ 未分析: [F, G] → 只送 F, G 给 AI → 分析后标记 [F, G]
第三次同步(无新增):
→ 未分析: [] → 跳过,不调用 AI
```
### AI Prompt 设计
**System Prompt**:
```
你是一个开发团队的 OKR 管理助手。你的任务是分析 git commit 记录,管理项目的 OKR目标与关键成果
你需要分析提交记录,输出 JSON 格式的操作指令:
判断逻辑:
- commit 包含 "完成"、"done"、"finished"、"实现完毕" 等完成语义 → 对应 KR 标记完成
- commit 是 feat/feature 类型但未完成 → 更新对应 KR 进度(根据描述估算百分比)
- commit 涉及的功能在现有 KR 中不存在 → 创建新 KR需同时提供时间节点
- fix/refactor/chore/docs 类 commit → 只在涉及明确功能时才操作
- 不要为同一个功能创建重复的 KR已有相同 sourceKey 的直接更新
新建 Objective 规则:
- 如果提交涉及的功能没有归属到任何已有 Objective → 可以创建新 Objective
- Objective 的 startDate 和 endDate 由你根据提交内容和功能复杂度判断
- period 格式为 "YYYY-Qn"(如 "2026-Q2"),根据 endDate 推算
新建 KR 规则:
- targetValue = 100, unit = "%"
- 根据 commit 语义估算 currentValue初步实现=30, 基本完成=70, 完成=100
- sourceKey 用小写英文短横线格式(如 "user-login", "payment-module"
- title 用中文描述
- startDate 和 endDate 由你根据功能复杂度和提交时间判断
```
**User Prompt 模板**:
```
仓库:{{repoName}}
所属项目:{{projectName}}
当前日期:{{today}}
该项目已有的 OKR
{{#each existingObjectives}}
Objective: id={{id}}, title="{{title}}", period={{period}}, 时间={{startDate}}~{{endDate}}, 进度={{progress}}%
Key Results:
{{#each keyResults}}
- id: {{id}}, title: "{{title}}", sourceKey: "{{sourceKey}}", status: {{status}}, 进度: {{currentValue}}/{{targetValue}}, 时间: {{startDate}}~{{endDate}}
{{/each}}
{{/each}}
{{如果没有已有 OKR: "该项目暂无 OKR 记录。"}}
该仓库的开发人员commit 提交人 → 系统用户映射):
{{#each authors}}
- userId: "{{userId}}", 姓名: {{displayName}}
{{/each}}
该仓库新增的提交记录(按时间排序,均为增量,之前的已处理过):
{{#each commits}}
- [{{committedAt}}] 提交人: {{authorName}}(userId={{userId}}) {{sha前7位}}: {{message}} (+{{additions}}/-{{deletions}})
{{/each}}
请分析以上提交,返回 JSON。注意每个 action 都必须带 ownerId 字段,值为提交人的 userId表示该 OKR 归谁负责。
{
"actions": [
{
"type": "create_objective",
"ownerId": "提交人的userId",
"title": "目标标题",
"startDate": "YYYY-MM-DD",
"endDate": "YYYY-MM-DD",
"reasoning": "为什么要创建这个目标",
"keyResults": [
{ "title": "...", "sourceKey": "...", "currentValue": number, "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD" }
]
},
{
"type": "create_kr",
"ownerId": "提交人的userId",
"objectiveId": "已有目标的id",
"title": "...",
"sourceKey": "...",
"currentValue": number,
"startDate": "YYYY-MM-DD",
"endDate": "YYYY-MM-DD",
"reasoning": "..."
},
{
"type": "update_progress",
"krId": "已有KR的id",
"newCurrentValue": number,
"reasoning": "..."
},
{
"type": "complete_kr",
"krId": "已有KR的id",
"reasoning": "..."
}
],
"summary": "一句话总结该仓库近期开发动态"
}
如果没有需要操作的内容,返回 {"actions": [], "summary": "..."}
```
### 防重复策略(三层保障)
| 层级 | 机制 | 说明 |
|------|------|------|
| **Commit 级** | `aiAnalyzedCommits` 表 | 已分析的 SHA 打标记,下次同步时过滤掉,不再送给 AI |
| **KR 级** | `sourceKey` 唯一性检查 | 创建前查 DB同 Objective 下同 sourceKey 只允许一条,重复则转为 update |
| **Objective 级** | 项目下已有 OKR 传入 prompt | AI 看到完整上下文,避免建议创建重复目标 |
### OKR 负责人归属
- commit 有 `userId`(通过 author-matching 映射到系统用户)
- AI prompt 中包含每条 commit 的提交人信息userId + displayName
- AI 返回 action 时必须携带 `ownerId`,即该 OKR 归哪个提交人负责
- 执行时:`create_objective``ownerId` = AI 指定的 userId`create_kr` 同理
- 跳过 `userId` 为 null 的 commits作者未映射到系统用户
**示例**:仓库 rtc_backend 有张三、李四两个人提交代码
- 张三的 commit 涉及"用户登录" → AI 创建 KR 归属张三
- 李四的 commit 涉及"支付模块" → AI 创建 KR 归属李四
- 同一个 Objective 下可以有不同负责人的 KR
### 时间节点(由 AI 判断)
- **不再固定取当前季度**,而是由 AI 根据以下信息决定:
- commit 的提交时间
- 功能的复杂度(从 commit 内容推断)
- 已有 OKR 的时间范围(避免冲突)
- AI 返回的 `startDate``endDate` 直接写入 Objective 和 KR
- `period` 根据 endDate 自动推算(复用已有 `dateToPeriod()`
- Objective 创建后,其日期范围由 AI 决定,后续也可由 AI 在新的分析中调整
### 完成判定
AI 返回 `complete_kr` action 时:
1. `currentValue = targetValue`(即 100
2. `status = 'completed'`
3. 写 krLog`action='completed'`, `detail='AI 根据 commit {sha} 判定完成:{reasoning}'`
4. `recalcObjectiveProgress()` 时 completed 的 KR 按 100% 计入
---
## 第四阶段:集成到同步流程
### 4a. 修改 `backend/src/sync/scheduler.ts`
```typescript
import { analyzeCommitsForOKR } from '../services/okr-ai-sync';
// 在 syncGitea() 之后调用
giteaJob = new Cron('0 2,19 * * *', async () => {
await syncGitea();
if (config.AI_ENABLED) {
console.info('[SCHEDULER] AI OKR 分析开始...');
await analyzeCommitsForOKR().catch(e =>
console.error('[SCHEDULER] AI OKR analysis failed:', e)
);
}
});
```
### 4b. 新增 API 端点 (`backend/src/routes/okr.ts`)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/okr/ai-analyze` | 手动触发 AI 分析admin/manager |
| POST | `/api/okr/ai-analyze/preview` | 预览模式:返回 AI 建议但不执行 |
### 4c. 修改 `backend/src/services/okr.ts`
- 导出 `dateToPeriod()` 函数供 okr-ai-sync 使用
- `recalcObjectiveProgress()` 增加对 `status === 'completed'` 的处理(按 100% 计入进度)
---
## 第五阶段DB 迁移
新增迁移文件 `backend/drizzle/0002_add_ai_okr_fields.sql`
```sql
-- 新表:增量标记(已分析过的 commit 不再重复处理)
CREATE TABLE ai_analyzed_commits (
id VARCHAR(50) PRIMARY KEY,
commit_sha VARCHAR(200) NOT NULL,
batch_id VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL,
UNIQUE INDEX uniq_analyzed_sha (commit_sha)
);
-- objectives 加字段
ALTER TABLE objectives ADD COLUMN source VARCHAR(50) DEFAULT 'manual';
-- key_results 加字段
ALTER TABLE key_results ADD COLUMN source VARCHAR(50) DEFAULT 'manual';
ALTER TABLE key_results ADD COLUMN source_key VARCHAR(300) NULL;
-- syncLogs source 枚举扩展
ALTER TABLE sync_logs MODIFY COLUMN source ENUM('plane', 'gitea', 'ai_okr') NOT NULL;
```
---
## 关键文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `backend/src/db/schema.ts` | 修改 | 新表 + 新字段 |
| `backend/src/config.ts` | 修改 | 豆包 AI 环境变量 |
| `backend/src/services/llm-client.ts` | **新建** | 豆包 API 调用封装 |
| `backend/src/services/okr-ai-sync.ts` | **新建** | 核心 AI 分析 + 增量更新逻辑 |
| `backend/src/services/okr.ts` | 修改 | 导出 dateToPeriod, completed 状态处理 |
| `backend/src/sync/scheduler.ts` | 修改 | 同步后触发 AI 分析 |
| `backend/src/routes/okr.ts` | 修改 | 新增手动触发端点 |
| `backend/drizzle/0002_*.sql` | **新建** | DB 迁移 |
---
## 实施顺序
1. Schema 变更 + 迁移文件(第一、五阶段)
2. config.ts 加豆包 AI 环境变量(第二阶段 2a
3. llm-client.ts — 豆包 API 封装(第二阶段 2b
4. okr.ts 小改(导出 dateToPeriod + completed 处理)
5. okr-ai-sync.ts 核心服务(第三阶段)
6. scheduler.ts 集成 + 新 API 端点(第四阶段)
7. 端到端测试
---
## 验证方案
1. 配置 `.env``AI_ENABLED=true``AI_API_KEY=...``AI_MODEL=doubao-seed-2-0-pro-260215`
2. 确保数据库中已有项目、用户、项目-仓库绑定、git commits 数据
3. 调用 `POST /api/okr/ai-analyze/preview` 查看 AI 返回的 actions不写库
4. 确认 actions 合理后,调用 `POST /api/okr/ai-analyze` 执行
5. 通过 `GET /api/okr` 验证 OKR 数据已生成,时间节点由 AI 合理设定
6. 再次调用 `POST /api/okr/ai-analyze`,确认不会重复生成(增量标记生效)
7. 手动触发 Git 同步拉到新 commits再次分析确认只处理新增 commits
8. 检查 krLogs 中有 AI 操作记录
9. 前端 OKR 页面查看显示是否正常
---
## 错误处理
| 场景 | 处理 |
|------|------|
| AI_ENABLED=false 或 API key 为空 | 静默跳过,不影响 Git 同步 |
| 豆包 API 返回非 JSON | 尝试用正则提取 ```json 块,失败则记 syncLogs error |
| 豆包 API 超时60s | 记错误日志commits 保持"未分析"状态,下次同步重试 |
| AI 建议更新不存在的 KR | 跳过该 action记 warning |
| AI 建议创建重复 sourceKey 的 KR | 转为 update_progress |
| commit 的 repo 没有绑定到任何项目 | 跳过该仓库的分析 |
| 某个仓库无新增 commits | 跳过,不调用 AI |