Files
ftb-project-management/docs/decisions.md
2026-06-30 09:28:12 +08:00

421 lines
26 KiB
Markdown
Raw 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.

# 关键设计决策记录
每条决策都包含**为什么这么做**,避免后续模型重新讨论或推翻。
## 1. 进度计算 — 由状态推导,不要手填百分比
**问题**Jira/TAPD 等系统让开发手填进度20%/35%/50%),数据完全主观,不可靠。
**决策**DevTask 进度由 `status` 推导:
- `todo` → 0%
- `in_progress` → 50%
- `testing` → 80%
- `submitted` → 100%
**理由**:客观、不允许造假。需求级/版本级进度 = `Σ(估算耗时 × 状态推导%) / Σ(估算耗时)`。估算耗时优先取执行人填写的 `estimateHours`,没有时才用 AI 草案的 `aiEstimateHours` 兜底。
## 2. 实际工时 — 由开始/完成时间戳计算
**问题**:让人手填工时太粗糙(一天 8h 真的全部花在这个任务吗?)。
**决策**:精确到 0.5h 的时间差计算
```
实际工时 = (completedAt - startDate) / 3600000精确到 0.5h
```
**理由**:状态流转时自动记录精确时间戳,工时直接派生。
## 3. DevTask "已提测" = 终态
**问题**:原本有 `submitted → done`,但开发提测之后就交付完成了,后续测试结果不应该回过来改 DevTask 状态。
**决策**:去掉 `done``submitted` 是终态。测试通过/失败产生 Bug不影响 DevTask。
**理由**DevTask 的责任是"开发到提测",验收是 TestCase 的职责。
## 4. 调研/产品/UI — 自动开始 + 手动提前开始
**问题**:到了计划开始日期,状态还是 pending 不合理。
**决策**
- 当前时间 ≥ `startTime` 时自动视为 in_progress
- 用户可在计划日期前手动点"开始"
- 自动开始时记录 `actualStartAt`(精确到分钟)
**理由**:减少冗余操作但保留灵活性。
## 5. TestCase 主归属版本,需求是可选标签
**问题**:早期把测试用例挂在需求上,但同一需求跨版本迭代时用例难以管理。
**决策**
- TestCase.versionId 必填(执行归属)
- TestCase.requirementId 可选(语义标签)
- 测试人员从"我的测试用例"看到的就是当前版本的用例
**理由**:测试是按版本执行的,不是按需求;但需求作为思考维度仍然有用。
## 6. Bug 直接挂版本
**问题**:早期通过 `Bug → TestCase → Requirement → Version` 四级反查,太绕。
**决策**Bug 增加 `versionId` 字段,直接挂版本。`testCaseId` 保留作为来源追溯。
**理由**:判断"能不能发布"的唯一维度是版本,必须 O(1) 查询。
## 7. 跨模块通信 — 引擎层,不是事件总线
**问题**模块多了A store 改动影响 B 的派生数据,散落各处难维护。
**决策**:建立**纯函数引擎**
- `linkage-engine.ts`:从 DevTask 状态派生需求"实际进度"
- `workspace-engine.ts`:聚合所有 store 数据为统一 WorkItem
**理由**:事件总线过度设计,纯函数派生更可预测。新增模块只需在引擎中添加聚合逻辑。
## 8. 时间字段统一 ISO 时间戳到分钟
**问题**:日期(`YYYY-MM-DD`) vs 时间戳混用UI 展示不一致。
**决策**:所有"实际开始/完成"用 `new Date().toISOString()`,展示统一 `slice(0, 16).replace('T', ' ')`
**理由**:时间戳到分钟够精确,不像秒/毫秒那么噪。
## 9. 任务类型字典化 + 分组
**问题**:早期 enum 写死类型(前端/后端/大数据/其他),新增类型需改代码。
**决策**:建 TaskCategory 字典表 + groupdevelopment/implementation/other管理员可增删。
**理由**:业务变化快,配置 > 写死。group 方便统计(开发工时 vs 实施工时)。
## 10. 阻塞正交标记,不是状态值
**问题**`status: blocked` 会丢信息——`开发中+阻塞``自测+阻塞`
**决策**
- `status: todo|in_progress|testing|submitted`
- `isBlocked: boolean` + `blockReason: string` + `blockedById: string`
**理由**:阻塞和状态正交。工作台筛选 `isBlocked=true` 一键拉出所有阻塞项,跨状态。
## 11. 加班原因可选,且去掉"其他"
**问题**:可选"其他"会让人逃避思考具体原因。
**决策**:变更原因/加班原因严格定义有限选项,**没有"其他"**。
**理由**:强制具体化,统计才有意义。
## 12. 项目维度 vs 个人维度耗时分开计算
**问题**:多个开发并行做任务,简单加总会重复计算时间。
**决策**
- **个人耗时**:每个任务独立累加(`Σ(end - start)`
- **项目维度阶段耗时**`min(start)``max(end)` 日历跨度(不重复)
**理由**:两个维度回答不同问题——"谁花了多少时间" vs "这个阶段拖了多久"。
## 13. 删除版本时清理孤儿数据
**问题**:删版本只删 version 实体,留下 PlanTask/DevTask/TestCase/Bug 变成孤儿数据。
**决策**:删除版本时级联清理:
1. 释放需求 versionId回需求池
2. 删除 PlanTask
3. 删除 DevTask通过需求 ID
4. 删除 TestCase
5. 删除 Bug
**理由**localStorage 没外键约束,必须手动级联。
## 14. 工作台数据点击 → 侧边详情 + 可操作
**问题**:早期工作台只显示信息,操作要跳转到版本详情。
**决策**:点击卡片打开 Drawer复用版本详情用的同一组件可在 Drawer 内完成状态流转、转交、提 Bug 等。
**理由**减少跳转工作台一站式处理。Drawer 顶部显示"产品/项目/版本"上下文,不会迷失。
## 15. UI 阴影统一 shadow-2xl
**问题**:各 Drawer 阴影不一致(有些 `shadow-lg` 有些 `shadow-2xl`)。
**决策**:全局 Drawer 用 `shadow-2xl`
**理由**:视觉权重明显,且一致。
## 16. 不进入 Brainstorm 模式的判定
**约定**:以下情况直接修复,**不强制走 brainstorming 流程**
1. Bug fix明确 bug方案直接
2. 已批准方案的 in-progress 延续
3. 用户已通过 AskUserQuestion 选择了方案
4. 简单文案/样式调整
**理由**brainstorming 适合新功能设计bug fix 强行套流程浪费时间。
## 17. AI 拆解不存历史原型,靠对账报告兜底
**问题**FTB 接入时,禅道里项目可能已经迭代到 V1.6前面历史原型不在系统里。AI 拆解 V1.6 时如果做"V1.5 → V1.6 diff",需要用户额外提供历史原型 URL。
**决策****不要求用户提供历史原型 URL**。AI 拆解只用三样输入:当前版本原型 + 关联需求 + 版本成员。
**对应风险**AI 可能把"上版本就有的功能"也当成"本次新做"。
**应对**:要求 Agent 输出**对账报告**,三段:
- ✅ 完美对应(需求 ↔ 原型注释 ↔ 任务)
- ⚠️ 仅需求未见原型 / 仅原型未见需求
- ❓ 注释含糊无法转化
**理由**:让历史版本入库的成本远高于让用户对账的成本。对账报告本来就是 AI 拆解的标配,能同时兜住"拆偏"和"拆漏"两类问题。让团队补需求清单,比让团队补历史原型容易。
## 18. AI 写入必须带 aiDraft 标记 + 引用来源
**问题**AI 直接写入开发任务/用例,团队无法分辨"这是 AI 拆的还是人手填的",出错时找不到来源。
**决策**
- DevTask / TestCase 加 `aiDraft: boolean` 字段
- AI 写入时强制 `aiDraft: true`
- 列表中 AI 草案视觉区分(紫色左边线 + AI 草案徽章)
- 用户编辑保存后 `aiDraft` 自动清除(在 store 的 update 方法里实现)
- 同时加 `references: Reference[]` 字段,记录引用来源(需求 / 原型批注)
**理由**
- 可识别性:团队一眼分辨 AI 产出 vs 人工产出
- 可追溯性:每条任务知道是从哪条需求 + 哪条原型批注来的
- 可信任:用户编辑即认可,自动转正常状态
## 19. 引用机制对 AI 和人工一致
**问题**:如果只有 AI 任务带 references人工任务不带会出现两套规则用户看到 AI 任务有引用、人工没有,体验割裂;做"任务可追溯"功能时数据缺失。
**决策**:人工创建 DevTask / TestCase 时也强制带 references。
- 表单加「关联需求」(从版本已加需求里多选,自动转 requirement 类型 reference
- 表单加「原型批注」(手填编号或自由文本,逗号/空格分隔,自动转 prototype_note 类型 reference
- AI 草案唯一区别只剩 `aiDraft: true` 这一个状态字段
**理由**单一数据形状UI 复用;可追溯性是任务本身的属性,不该由"谁创建"决定。
## 20. AI 配置走页面不走 .env
**问题**:早期 ANTHROPIC_API_KEY 只能写到 `apps/server/.env`,配置流程对非技术人员不友好;切换 key 要重启 NestJS。
**决策**
-`apps/server/src/modules/config/` 模块,提供 GET/PATCH/POST `/api/v1/config/ai` 接口
- 配置存到 `apps/server/data/ai-config.json`(在 .gitignore 里)
- 前端 `/admin/ai-config` 页面(仅超管可见)支持修改 Key、切换模型、测试连接
- AiGateway 优先读文件,其次读环境变量;切换 key 后客户端单例自动重建
- 前端 GET 接口永远不返回完整 Key只返回 mask`sk-ant...xxxx`
**理由**
- 运营/管理可以自助维护,不依赖工程师改代码
- Key 在服务器存储,前端永远拿不到完整内容,安全等同 .env
- 走 .env 的兜底保留,给 CI / 早期部署留路径
## 21. 多 AI 提供商抽象,支持中转站
**问题**:早期 AiGateway 硬绑定 Anthropic 官方 SDK无法走 ikuncode 等中转站,也不能切换到 OpenAI 格式提供商。
**决策**:引入 Provider 抽象层。
- `apps/server/src/modules/ai/providers/` 下两个实现:`AnthropicProvider` / `OpenAIProvider`
- 共同实现 `AiProvider` 接口(`callTool` / `ping`),把厂商差异封装在 provider 内部
- `prompts/decompose.ts` 用格式无关的 JSON Schema由各 provider 包装成 Anthropic 的 `input_schema` 或 OpenAI 的 `parameters`
- 配置数据结构升级为 `{ providers: [], activeProviderId }`,支持配置多个 provider 运行时切换
- 旧的单 key 配置自动迁移成 `providers[0]`
**支持范围**:当前两类格式覆盖 99% 中转站
- `anthropic`Anthropic 官方 / 中转站(兼容 Messages API + tool_use
- `openai`OpenAI 官方 / 中转站(兼容 Chat Completions + tool calling
**理由**
- 用户可以自由选择厂家或中转站,不被一家锁死
- 每个 provider 独立 model 字段,因为不同提供商上同名模型不一定可用
- "激活"概念让多套配置同时存在,方便 A/B 切换
- 抽象层让上层 AiService 完全不感知厂商差异,未来加 Risk Watch / Schedule Suggest Agent 时复用
## 22. 业务数据先迁到 PostgreSQL AppData再逐步关系化
**问题**V1 业务数据存在浏览器 localStorage 中,用户在 Application Storage 执行 Clear site data 后,产品、项目、版本、需求池、版本详情里的调研/产品方案/UI/开发任务/测试用例/Bug以及成员、任务类型等都会丢失。系统未来要部署到服务器浏览器不能作为业务数据主存储。
**决策**:先引入服务端通用数据文档层:
- Prisma 增加 `AppData` 模型(表 `app_data``key` + JSONB `value`
- NestJS 增加 `DataModule`,通过 `GET/PUT /api/v1/data/:key` 读写允许的业务数据 key
- 前端 store 保持现有数据形状和业务逻辑,只把持久化从 localStorage 切换到 `/data/:key`
- 覆盖 key`products-overview``requirements``version-plans``dev-tasks``test-cases``bugs``members``task-categories``task-worklogs``overtime`
- 浏览器只保留登录态,不保存业务主数据
**理由**
- 当前模块多、数据形状仍在快速调整,一次性全量关系化风险高、改动面大
- JSONB 文档表能立刻解决“清浏览器数据导致业务数据丢失”的线上部署问题
- 保持 store 数据形状不变,能降低迁移对现有 UI、AI 拆解、工作台聚合的冲击
- 后续等字段稳定后,再把 `app_data` 中的文档逐步迁入关系表和领域 CRUD API
## 23. 版本计划完成条件进入工作流引擎,任务类型使用稳定语义码
**问题**:版本模块里,调研/产品方案/UI 设计的完成动作散在 `PlanTab``PlanDetailDrawer`右上角勾选按钮会绕过子任务和成果提交。开发任务的任务类型太粗测试用例没有任务类型AI 拆解只输出 `role`,无法稳定映射到业务分类。
**决策**
- 新增 `version-plan-workflow.ts`,统一判断计划是否可勾选子任务、是否可提交成果、是否可完成、缺少什么条件。
- 取消计划卡片右上角的“直接完成”勾选按钮,完成只能通过提交成果触发。
- 调研/产品方案/UI 设计都使用子任务清单;完成时必须满足规则引擎要求并提交链接或文件成果。
- 新增 `requirement-selector.ts`,所有关联需求候选都从当前版本所属项目下的已采纳需求中取,不从全量需求池取。
- DevTask 和 TestCase 共用 `TaskCategory` 字典,`TestCase` 增加 `categoryId`
- `TaskCategory` 增加稳定 `code`AI 输出 `categoryCode`,前端再映射为当前系统的 `categoryId`
**理由**
- 完成条件属于业务规则,不属于按钮组件;集中到工作流引擎后,列表和抽屉能保持一致。
- 需求候选来源属于领域边界;统一 selector 能避免从需求池、项目需求、版本需求之间误取数据。
- AI 不应该猜数据库 id稳定语义码能兼容管理员调整展示名称也方便后续扩展更多模型。
- 测试用例带任务类型后才能知道测试覆盖范围AI 拆解和人工创建的数据形状也一致。
## 24. AI 预估和执行人预估分离
**问题**AI 拆解写入 `estimateHours` 会让系统误以为负责人已经确认了工时和排期;同时 AI 会根据原型自动反推预计开始/截止,造成“计划已填”的错觉。
**决策**
- AI 输出字段统一改为 `aiEstimateHours`,不再输出/写入 `estimateHours`
- `estimateHours` 只表示执行人或负责人确认后的执行预估。
- 进度和统计口径优先使用 `estimateHours`;缺失时使用 `aiEstimateHours` 兜底;两者都没有时才从计划时间推导或按无估时处理。
- AI 采纳开发任务时不写预计开始/截止时间,由负责人后续填写计划排期。
- AI 重新拆解入口拆成“重新拆解开发任务”和“重新拆解测试用例”,请求通过 `target` 控制本次只生成哪类草案。
- AI 估时允许小于 0.5h,汇总时保留小数精度;实际耗时仍按状态时间戳计算并保持 0.5h 口径。
**理由**
- AI 给的是建议,不是负责人承诺。
- 排期属于执行人计划,不能由拆解 Agent 代填。
- 独立重拆能避免修改开发任务时顺手覆盖测试用例,降低误采纳风险。
## 25. AI 重新拆解先过滤已采纳重复草案
**问题**:第一次 AI 拆解的 DevTask/TestCase 已经被采纳后,用户再次重新拆解时,模型可能再次输出同一批草案。若直接展示给用户采纳,会造成开发任务和测试用例重复。
**决策**
- AI 返回结果后,前端在打开采纳弹窗前先做确定性去重。
- 开发任务比对范围是当前版本已关联需求下的现有 DevTask测试用例比对范围是当前版本下的现有 TestCase。
- 重复判定使用:任务类型 code + 标题规范化 + 引用来源(需求 / 原型批注)一致。
- 重复草案不进入采纳列表,只在弹窗提示已过滤数量。
**理由**
- 去重不能只依赖模型提示,必须有系统规则兜底。
- 标题也参与签名,避免同一需求/QY 下不同真实工作项被误过滤。
## 26. 系统内置超级管理员账号不可删除
**问题**:全新系统需要一个稳定可登录的超级管理员账号。早期默认成员曾以普通姓名展示,用户手动改名后,登录态和 Bug 单据中的人员姓名可能仍保留旧快照,造成“提交人是旧姓名、修复人是新姓名”的错位。
**决策**
- 固定 `m-8` 为系统内置超级管理员账号,姓名、部门、角色由系统保护,不允许删除。
- 读取历史成员数据时自动迁移旧默认账号为“超级管理员”,保留手机号、邮箱、密码。
- 登录态以成员 `id` 为稳定身份,页面启动和编辑当前用户后都按成员表刷新姓名、角色和联系方式。
- Bug 展示和筛选对历史旧名做兼容解析,旧单据中的默认账号旧姓名显示为当前超级管理员。
**理由**
- 账号身份不能依赖可编辑姓名;姓名只是展示字段。
- 内置超管账号避免新系统初始化后被误删或降权导致无法管理。
- 历史单据兼容能修复已有数据的显示错位,同时不需要批量改写业务记录。
## 27. 测试轮次以第一轮用例为母版
**问题**:同一版本测试失败或修复后,经常需要重新跑一遍同一批测试用例。如果直接复用原用例,会覆盖第一轮执行记录;如果手工重建,又容易漏掉 AI 生成或人工补充的用例。
**决策**
- TestCase 增加 `roundNo`,旧数据和未显式写入的数据统一视为第 1 轮。
- 点击“开启新一轮测试”时,只从第 1 轮复制用例到下一轮AI 创建和人工创建的用例都复制。
- 只有最新一轮测试用例全部进入 `passed/failed/blocked` 后,才能开启下一轮;第 1 轮未测完时不能开启第 2 轮。
- 复制时保留需求、标题、描述、类型、优先级、负责人、引用来源、AI 草稿标记、AI 预估和执行预估。
- 复制时清空执行状态、开始/完成时间、执行人、失败原因和阻塞原因,新轮次从 `pending` 开始。
- 测试进度、无 Bug 通过率和列表按当前轮次查看;顶部 AI 预估、执行预估、实际耗时、人力投入按版本全部轮次累计。
- 测试用例按所属需求分组时展示“已提测/待提测”标签;只有该需求下所有开发任务都 `submitted` 才展示“已提测”,否则展示“待提测”。
**理由**:第一轮承载完整测试范围,后续轮次应复跑同一范围而不是临时拼装;执行记录按轮次隔离,整体投入按版本累计,能同时回答“这一轮测得怎么样”和“这个版本测试总共花了多少”。
## 28. Daily report uses work activity log, not a generic rules engine
**Problem**: A daily report based only on manual `task-worklogs` misses important actions such as submitting a product plan, starting a development task, submitting code to test, fixing bugs, or marking blockers.
**Decision**: Add a lightweight `work-activities` document key and a typed `work-activity-factory.ts`. Business stores append activity records after successful operations. The workspace daily report derives a personal report from activities, legacy worklogs, and current work items.
**Why**:
- Automatic activity records provide evidence that work happened today.
- Manual progress notes explain multi-day work when no status changed today.
- A generic rules engine is too heavy for the current AppData stage and would hide business rules behind configuration.
- A pure aggregation function keeps report behavior testable and predictable.
**Rule**: Key status changes count as daily evidence. Multi-day in-progress work without today's activity or progress note is flagged as needing a progress update.
## 29. 计划日期使用中国节假日日历提示,不做硬阻断
**问题**调研、产品方案、UI 设计、开发任务、测试用例和 Bug 修复都需要填写计划时间。原生日期控件样式不一致,也无法提示中国法定节假日和调休工作日。
**决策**
- 新建统一的工作日日期时间选择组件,创建任务时复用同一套交互。
- 内置国务院办公厅发布的 2026 年中国法定节假日和调休工作日;未知年份按周末/工作日兜底。
- 选择节假日或周末时只提示,不阻止保存;选择调休工作日时按工作日提示。
- TestCase 增加 `plannedTestAt`Bug 增加 `plannedFixAt`,保存为 ISO 时间戳。
**理由**:项目排期需要贴近中国工作日,但研发和线上 Bug 可能确实安排在非工作日处理,所以系统负责提醒,最终是否保存交给用户判断。
## 30. 正常工时走中国工作日历,加班单独计入
**问题**:接入中国节假日后,如果任务从节假日前开始、节假日后结束,直接按自然时间差会把休息日误算为实际耗时。
**决策**
- `calcWorkHours` 和正常任务的 `calcActualElapsedHours` 都按中国工作日历和工作时段计算。
- 法定节假日、周末不计入正常工时;调休上班日计入正常工时。
- AI 预估工时是工作量建议,不按日期跳过;执行预估只有从计划起止时间自动推导时才走工作日历。
- 加班记录不受工作日历过滤,但不按自然小时连续打卡计算;使用项目管理口径:每个自然日默认 9:00-12:00、13:00-18:00 计 8h中间完整日期按 8h首尾日期按填写的开始/结束时间裁剪,结束晚于 18:00 的当天额外计入超出时长。
**理由**:正常任务耗时回答“工作时间里实际投入了多少”,加班记录回答“额外投入覆盖了多少项目工作量”。项目管理不做打卡,跨天记录不能把夜间空档算成工时;但节假日和周末仍允许记录,不受中国工作日历过滤。
## 31. AI 原型拆解优先按需求编号匹配,再按需求概述兜底
**问题**:原型文件里有些批注并不总是稳定写成 QY 编号,或者 QY 片段和需求之间没有显式绑定。仅按 QY 批注匹配会漏掉“原型里实际已有注释”的需求。
**决策**
- 原型上下文提取不只围绕 QY 编号,也围绕当前版本关联需求的 `id``code``title``description` 命中片段。
- Agent 对账优先按需求编号匹配;原型文本出现 `requirement.id``requirement.code` 时必须优先归到该需求。
- 没有需求编号时,再按需求标题和需求概述做语义匹配。
- 没有 QY 编号但命中了需求编号或需求概述时,允许只引用 `requirement``matched.noteIds` 返回空数组。
- 只有编号和概述都匹配不到的原型批注,才进入 `noteOnly``ambiguous`
**理由**:需求编号是最稳定的对账锚点,需求概述是编号缺失时的业务语义兜底。把匹配证据先送进上下文,再用 prompt 明确优先级,比只依赖模型从截断文本里自由联想更稳定。
## 32. 测试用例类型细分AI 拆解按测试点而不是大流程输出
**问题**:测试用例原先只有功能/API/异常/兼容性四类AI 容易把多个交互、接口、数据状态和边界场景合并成一条“大用例”,导致测试范围不够细。
**决策**
- 在测试分组增加细分类UI 交互、表单校验、数据一致性、权限、边界值、状态流转、回归测试。
- AI tool schema、shared 类型和前端任务类型字典同步允许这些 `categoryCode`
- Prompt 明确要求 TestCase 按功能点、交互、接口、数据、权限、异常、边界、状态流转和回归点拆细。
- 一条 QY 如果同时涉及 UI、接口、数据、异常和状态变化通常应拆出 3-8 条 TestCase不允许用单条“完整流程验证”兜住。
**理由**:测试用例的分类粒度会反过来影响 AI 输出粒度。更细的稳定语义码能让模型把测试范围拆开,也让后续统计、筛选和负责人评估更准确。
## 33. AI 可推荐负责人,但必须由用户确认后才写入
**问题**版本成员已经维护完成后AI 拆解任务时完全不看成员会浪费上下文;但如果 AI 直接分配负责人,容易把模型建议误认为团队承诺,也可能编造成员姓名造成脏数据。
**决策**
- AI 草案允许输出 `recommendedAssigneeName``recommendedAssigneeReason`,但姓名只能来自当前版本成员 `members[].name`
- 前端用规则 helper 再校验推荐姓名是否属于当前版本成员,非成员姓名直接丢弃。
- 采纳弹窗默认不写入负责人;用户勾选“采纳推荐负责人”后,才把已校验推荐写入 DevTask/TestCase 的 `assigneeId`
- 没有明确角色匹配或成员匹配时AI 不输出推荐字段,任务保持未分配,供成员后续领取或手动分配。
**理由**:负责人推荐能减少项目经理初次分配成本,但分配本身是团队执行承诺,必须由人确认。把推荐和写入分开,可以复用版本成员上下文,又避免模型幻觉姓名或越权自动派单。
## 36. 小宝预警规则优先AI 只做解释
**问题**:如果直接让 AI 判断版本能否发版模型可能忽略系统内的任务、Bug、测试、日报和权限事实结论不可追溯如果只按风险等级触发 AI又会漏掉同等级内风险剧变例如 P1 Bug 从 0 到 3、测试失败、发版日只剩 1 天。
**决策**
- 小宝预警先由确定性规则计算 `riskScore``riskLevel``forecastReleaseDate``confidence`、趋势、静默风险和证据摘要。
- `on_track` 不触发 AI`at_risk``likely_delayed``blocked` 自动触发 AI`attention` 只有在风险分、趋势、关键 Bug、失败用例、阻塞、静默风险、置信度或预测日期出现明显恶化时触发。
- AI 解读自动触发不提供人工“AI 解读”按钮。缓存签名必须覆盖趋势、原因、静默风险、日报/活动证据、风险信号和置信度,避免复用过期解读。
- 快照保存需要节流:同版本同日普通变化 10 分钟内不重复保存;风险等级变化、风险分变化达到阈值、关键 Bug/失败用例/阻塞/静默风险变化或预测日期明显变化时立即保存。
- AI 解读需要 cooldown同版本最近 6 小时内已有解读时不重复请求;如果风险等级升级,则允许绕过 cooldown。
- AI 只写入 `xiaobao-risk-insights` 缓存,不修改 Version、DevTask、TestCase、Bug、Requirement 或 Member。
**理由**规则结果可测试、可追溯、可复盘AI 文案提升可读性,但不能替代系统事实判断。趋势、静默风险和置信度能弥补“当前风险等级”过于静态的问题。