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

15 KiB
Raw Blame History

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)

新增:

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 示例

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 调用格式

// 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_objectiveownerId = AI 指定的 userIdcreate_kr 同理
  • 跳过 userId 为 null 的 commits作者未映射到系统用户

示例:仓库 rtc_backend 有张三、李四两个人提交代码

  • 张三的 commit 涉及"用户登录" → AI 创建 KR 归属张三
  • 李四的 commit 涉及"支付模块" → AI 创建 KR 归属李四
  • 同一个 Objective 下可以有不同负责人的 KR

时间节点(由 AI 判断)

  • 不再固定取当前季度,而是由 AI 根据以下信息决定:
    • commit 的提交时间
    • 功能的复杂度(从 commit 内容推断)
    • 已有 OKR 的时间范围(避免冲突)
  • AI 返回的 startDateendDate 直接写入 Objective 和 KR
  • period 根据 endDate 自动推算(复用已有 dateToPeriod()
  • Objective 创建后,其日期范围由 AI 决定,后续也可由 AI 在新的分析中调整

完成判定

AI 返回 complete_kr action 时:

  1. currentValue = targetValue(即 100
  2. status = 'completed'
  3. 写 krLogaction='completed', detail='AI 根据 commit {sha} 判定完成:{reasoning}'
  4. recalcObjectiveProgress() 时 completed 的 KR 按 100% 计入

第四阶段:集成到同步流程

4a. 修改 backend/src/sync/scheduler.ts

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

-- 新表:增量标记(已分析过的 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. 配置 .envAI_ENABLED=trueAI_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