Files
ftb-project-management/docs/decisions.md
2026-07-01 12:53:20 +08:00

30 KiB
Raw Blame History

关键设计决策记录

每条决策都包含为什么这么做,避免后续模型重新讨论或推翻。

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 状态。

决策:去掉 donesubmitted 是终态。测试通过/失败产生 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
  • DevTask 存在阻塞时不能转为 submitted,必须先解除阻塞再提测。

理由:阻塞和状态正交。工作台筛选 isBlocked=true 一键拉出所有阻塞项,跨状态;但 submitted 代表开发交付完成,仍必须满足“当前无阻塞”的完成条件。

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 输出对账报告,结构化呈现:

  • 完美对应(需求 ↔ 原型注释 ↔ 任务)
  • ⚠️ 需求未见原型
  • 无需求ID分组原型有明确功能但没有匹配到关联需求
  • 注释含糊无法转化

理由:让历史版本入库的成本远高于让用户对账的成本。对账报告本来就是 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只返回 masksk-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% 中转站

  • anthropicAnthropic 官方 / 中转站(兼容 Messages API + tool_use
  • openaiOpenAI 官方 / 中转站(兼容 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_datakey + JSONB value
  • NestJS 增加 DataModule,通过 GET/PUT /api/v1/data/:key 读写允许的业务数据 key
  • 前端 store 保持现有数据形状和业务逻辑,只把持久化从 localStorage 切换到 /data/:key
  • 覆盖 keyproducts-overviewrequirementsversion-plansdev-taskstest-casesbugsmemberstask-categoriestask-worklogsovertime
  • 浏览器只保留登录态,不保存业务主数据

理由

  • 当前模块多、数据形状仍在快速调整,一次性全量关系化风险高、改动面大
  • JSONB 文档表能立刻解决“清浏览器数据导致业务数据丢失”的线上部署问题
  • 保持 store 数据形状不变,能降低迁移对现有 UI、AI 拆解、工作台聚合的冲击
  • 后续等字段稳定后,再把 app_data 中的文档逐步迁入关系表和领域 CRUD API

23. 版本计划完成条件进入工作流引擎,任务类型使用稳定语义码

问题:版本模块里,调研/产品方案/UI 设计的完成动作散在 PlanTabPlanDetailDrawer右上角勾选按钮会绕过子任务和成果提交。开发任务的任务类型太粗测试用例没有任务类型AI 拆解只输出 role,无法稳定映射到业务分类。

决策

  • 新增 version-plan-workflow.ts,统一判断计划是否可勾选子任务、是否可提交成果、是否可完成、缺少什么条件。
  • 取消计划卡片右上角的“直接完成”勾选按钮,完成只能通过提交成果触发。
  • 调研/产品方案/UI 设计都使用子任务清单;完成时必须满足规则引擎要求并提交链接或文件成果。
  • 新增 requirement-selector.ts,所有关联需求候选都从当前版本所属项目下的已采纳需求中取,不从全量需求池取。
  • DevTask 和 TestCase 共用 TaskCategory 字典,TestCase 增加 categoryId
  • TaskCategory 增加稳定 codeAI 输出 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 / plannedEndAtBug 增加 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 编号,也围绕当前版本关联需求的 idcodetitledescription 命中片段。
  • Agent 对账优先按需求编号匹配;原型文本出现 requirement.idrequirement.code 时必须优先归到该需求。
  • 没有需求编号时,再按需求标题和需求概述做语义匹配。
  • 没有 QY 编号但命中了需求编号或需求概述时,允许只引用 requirementmatched.noteIds 返回空数组。
  • 编号和概述都匹配不到、但能形成明确功能名称和任务范围的原型批注,进入 prototypeOnly 无需求ID分组只有无法稳定转化为任务/用例的批注才进入 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 草案允许输出 recommendedAssigneeNamerecommendedAssigneeReason,但姓名只能来自当前版本成员 members[].name
  • 前端用规则 helper 再校验推荐姓名是否属于当前版本成员,非成员姓名直接丢弃。
  • 采纳弹窗默认不写入负责人;用户勾选“采纳推荐负责人”后,才把已校验推荐写入 DevTask/TestCase 的 assigneeId
  • 没有明确角色匹配或成员匹配时AI 不输出推荐字段,任务保持未分配,供成员后续领取或手动分配。

理由:负责人推荐能减少项目经理初次分配成本,但分配本身是团队执行承诺,必须由人确认。把推荐和写入分开,可以复用版本成员上下文,又避免模型幻觉姓名或越权自动派单。

34. AI 草案领取和计划必须绑定

问题AI 生成的 DevTask / TestCase 如果没有负责人,团队需要先领取;如果把“待领取”和“待排期”拆成两个可见状态,会让版本详情列表出现更多标签,且无法体现“领取时就应该承诺计划”的业务动作。

决策

  • 版本详情开发任务和测试用例不显示“待排期”标签。
  • 无负责人时显示“待领取”,领取入口必须同时填写计划起止时间。
  • 用户采纳 AI 推荐负责人后,草案已经有负责人,不再需要领取;但开始开发/测试前仍必须补齐计划起止时间。
  • DevTask 进入 in_progress 前必须具备 assigneeIdexpectedStartAtexpectedEndAt
  • TestCase 进入 running 前必须具备 assigneeIdplannedTestAtplannedEndAt

理由:领取代表成员承诺执行,计划时间代表承诺边界,二者应该在同一个动作里完成。列表层只表达“谁还没接手”,状态机层负责阻止未计划任务进入执行,页面不会被额外标签干扰。

35. 产品/UI 计划需求覆盖必须区分部分完成和完全完成

问题:产品方案和 UI 设计经常跨天推进,同一天可能只完成某条需求的一部分。旧的勾选式“引用需求已完成”只能表达完成/未完成,日报和后续分析无法知道本次完成了什么、还剩什么,也容易把部分完成误算成可提交成果。

决策

  • VersionPlan 增加 requirementCoverage[],每条引用需求记录 not_started / partial / completed、已完成内容、剩余内容、更新人和更新时间。
  • 旧的 completedRequirementIds 继续保留用于兼容历史数据,但当同一需求存在 requirementCoverage 时,以新覆盖状态为准。
  • 产品/UI 计划的成果提交门禁只认 completedpartial 只记录进度,不满足“关联需求全部覆盖”。
  • 计划增加 logs[],记录需求进度更新和 AI 拆解触发/完成/失败,右侧日志时间线消费该数据。

理由:覆盖状态是产品/UI 计划自身的业务事实,不应该用简单 checkbox 表达。显式记录“已完成/剩余”能支撑日报、复盘和需求完成质量分析,同时保留旧字段可避免历史数据迁移成本。

36. 小宝预警规则优先AI 只做解释

问题:如果直接让 AI 判断版本能否发版模型可能忽略系统内的任务、Bug、测试、日报和权限事实结论不可追溯如果只按风险等级触发 AI又会漏掉同等级内风险剧变例如 P1 Bug 从 0 到 3、测试失败、发版日只剩 1 天。

决策

  • 小宝预警先由确定性规则计算 riskScoreriskLevelforecastReleaseDateconfidence、趋势、静默风险和证据摘要。
  • on_track 不触发 AIat_risklikely_delayedblocked 自动触发 AIattention 只有在风险分、趋势、关键 Bug、失败用例、阻塞、静默风险、置信度或预测日期出现明显恶化时触发。
  • AI 解读自动触发不提供人工“AI 解读”按钮。缓存签名必须覆盖趋势、原因、静默风险、日报/活动证据、风险信号和置信度,避免复用过期解读。
  • 快照保存需要节流:同版本同日普通变化 10 分钟内不重复保存;风险等级变化、风险分变化达到阈值、关键 Bug/失败用例/阻塞/静默风险变化或预测日期明显变化时立即保存。
  • AI 解读需要 cooldown同版本最近 6 小时内已有解读时不重复请求;如果风险等级升级,则允许绕过 cooldown。
  • AI 只写入 xiaobao-risk-insights 缓存,不修改 Version、DevTask、TestCase、Bug、Requirement 或 Member。

理由规则结果可测试、可追溯、可复盘AI 文案提升可读性,但不能替代系统事实判断。趋势、静默风险和置信度能弥补“当前风险等级”过于静态的问题。

37. AI 拆解支持无需求ID分组不自动补需求池

问题0 到 1 项目中,需求池可能只录入一条宽泛需求,例如“做一个充值功能”,而原型批注里已经包含更多细粒度功能。旧规则把“原型里有、关联需求里没有”的批注只放进 noteOnly 报告,不拆任务,会漏掉真实开发和测试范围。若让 AI 自动创建 Requirement 又会污染需求池和版本关联需求列表,让“正式需求范围”和“原型推断内容”混在一起。

决策

  • AI 拆解继续优先按关联需求匹配;匹配成功的草案带正式 requirementId
  • 原型中明确可拆、但没有匹配到关联需求的批注,进入 prototypeOnly,也可以生成 DevTask / TestCase 草案。
  • prototypeOnly 草案不创建 Requirement不加入需求池也不加入版本关联需求列表。
  • 这类草案写入开发任务/测试用例时,requirementId 为空,使用 requirementName 作为展示分组名,列表标题为 无需求ID · {requirementName}
  • 不额外展示“原型发现”标记;只有既有 aiDraft 草案视觉状态。
  • DevTask 需要直接归属版本(versionIdrequirementId 改为可选;旧数据可继续通过 requirementId -> Requirement.versionId 兼容推导。
  • 版本级统计、工作台和风险预警统计包含无需求ID分组任务/用例;需求级进度和需求关闭条件只统计带正式 requirementId 的 DevTask。

理由:关联需求代表人为确认的正式范围,不能被 AI 静默扩写;但原型批注也是当前版本真实交付范围的重要证据,不应该因为没有 REQID 被丢掉。无需求ID分组让任务和用例完整进入执行视图同时保持需求池干净、关联需求列表可信。