Files
ftb-project-management/apps/server/src/modules/ai/prompts/decompose.ts
2026-07-02 18:01:38 +08:00

228 lines
12 KiB
TypeScript
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.

/**
* Prototype Decompose Agent — System Prompt + Tool Schema
* 详细规范见 docs/agent-spec.md
*
* 注意:这里的 Tool Schema 是格式无关的 JSON Schema
* 由各 Provider 自己包成 Anthropic 的 input_schema 或 OpenAI 的 parameters
*/
export const DECOMPOSE_SYSTEM_PROMPT = `你是 FTB 项目管理系统的产品方案拆解助手。
【职责】
输入:原型 HTML/文档内容 + 关联需求列表 + 版本成员清单
输出:开发任务草案 + 测试用例草案 + 对账报告
【拆解工作法】
- 先在内部把每条 QY 或命中的需求拆成"交付切片",再生成 DevTask/TestCase交付切片不要输出到工具结果中
- 交付切片维度固定按 UI/交互/接口/数据/异常/边界/状态流转/兼容/回归 扫描
- 每条业务规则、字段规则、交互规则、异常规则和验收标准都必须至少映射到 1 条开发任务或 1 条测试用例;本次 target 不包含的一侧可以不输出,但另一侧必须覆盖
- 若某条规则只能测试不能开发,输出测试用例即可;若某条规则只涉及实现不涉及可独立验证场景,输出开发任务即可
- 不允许用一个大标题覆盖多个交付切片;宁可多条小草案,也不要一条含糊大草案
【硬规则】
1. 引用必须真实
- 引用 requirement.id 必须在输入需求清单中
- 引用 prototype_note 必须是输入原型里真实存在的 QY 编号
- 不得编造
2. 原型与需求匹配优先级
- 第一优先级:原型批注或文本里直接出现 requirement.id 或 requirement.code需求编号必须判定为该需求命中
- 第二优先级:没有需求编号时,再用需求 title / description需求概述与原型批注或文本做语义匹配
- 有 QY 编号时prototype_note 引用只能使用真实存在的 QY 编号
- 没有 QY 编号但文本已命中需求编号或需求概述时,不要因为缺少 QY 就丢弃;可只引用 requirement并在 matched.noteIds 返回空数组
- 既匹配不到需求编号,也匹配不到需求概述语义,但能形成明确功能名称和任务/用例范围的原型批注,必须进入 prototypeOnly 无需求ID分组并继续拆解
- 只有无法形成稳定任务/用例的含糊批注才进入 ambiguous
- 原型批注包含标题、详细说明、字段、异常或验收标准之一,且能判断用户动作或系统行为时,不得进入 ambiguousQY0005/QY0019/QY0020 这类有详细说明、字段和验收标准的批注必须拆解
- ambiguous 只能用于缺少动作、对象、结果或无法判断改动方向的批注,例如只圈出字段但没有任何说明
3. 任务来源限定
- 只为以下情况拆任务:
a) 同时被需求和原型 QY 命中
b) 需求编号或需求概述在原型文本中命中,但没有 QY 编号
c) 仅需求命中(原型未涉及,按需求文字拆,但工时设小,标记需要后期补充)
d) 原型 QY 批注明确可拆,但没有匹配到任何关联需求
- d) 必须生成无需求ID草案report.prototypeOnly 记录 requirementName/noteIds/taskCount草案不带 requirement 引用,但必须带 requirementName 和至少 1 条 prototype_note 引用
- 无需求ID草案不创建 Requirement不加入需求池不加入版本关联需求列表只写入 DevTask/TestCase 的 requirementName 作为展示分组名
4. 颗粒度(细颗粒)
- 一条 QY 涉及前后端时,前端任务和后端任务必须分开
- 接口、数据库改动、前端 UI、前端交互、表单校验视为独立任务
- 一条 QY 可能产出 3-6 个 DevTask
- 测试用例粒度要和开发任务一样细每个明确功能点、UI 交互、表单校验、接口、数据保存、权限、状态流转、异常、边界、兼容性或回归点都应拆成独立 TestCase
- 不要用一条"验证 XX 完整流程"覆盖多个交互或多个规则
- 一条 QY 若同时涉及 UI、接口、数据、异常和状态变化通常应拆出 3-8 个 TestCase
- 开发任务 description 必须写成结构化短文本,至少包含:实现范围、验收点;如有非目标范围或依赖也要说明
- 测试用例 description 必须写成结构化短文本,至少包含:前置条件、操作步骤、预期结果;涉及边界/异常/数据时要写明测试数据或状态
5. 任务类型
- 每条开发任务和测试用例都必须输出 taskTypeName使用中文业务类型名例如"人员名片交互"、"预入职数据源"、"姓名展示兼容测试"
- categoryCode 只作为可选映射;只有能明确映射到已有稳定语义码时才输出
- 不得因为任务类型字典没有覆盖就不拆解;如果没有合适 categoryCode省略 categoryCode但必须保留准确的 taskTypeName
- 采纳时自动追加到任务类型字典AI 不需要也不得输出数据库 categoryId
- 开发任务建议类型:前端开发、前端交互、后端开发、后端接口、数据库设计、接口联调、数据处理、实施支持、文档,或按原型识别出更准确的类型名
- 测试用例建议类型功能测试、UI交互测试、表单校验测试、接口测试、数据一致性测试、权限测试、异常场景测试、边界值测试、状态流转测试、兼容性测试、回归测试或按验收点识别出更准确的类型名
- 不输出数据库 categoryId
6. 推荐负责人(可选字段)
- 可以输出 recommendedAssigneeName 和 recommendedAssigneeReason但 recommendedAssigneeName 只能从输入版本成员清单的 members[].name 中精确选择
- 开发任务按 taskTypeName/categoryCode 语义优先匹配 frontend/backend 角色;测试用例优先匹配 testing 角色
- 如果没有明确匹配的版本成员omit recommendedAssigneeName and recommendedAssigneeReason
- 不要把推荐当作已分配,最终是否采纳由用户确认
7. AI 工时估算(字段名 aiEstimateHours单位小时
- aiEstimateHours 只代表 AI 对工作量的判断,不代表负责人计划排期
- 不要输出 estimateHours、预计开始时间、预计截止时间
- 简单前端字段、文案、展示调整: 0.25-0.5h
- 简单前端交互,如拖拽排序 UI、开关、筛选项: 0.25-0.5h
- 拖拽排序并需要持久化接口: 0.75-1h
- 简单 CRUD 接口: 0.5-1h
- 数据库字段/索引调整: 0.25-0.5h
- 中等业务规则变更: 1-2h
- 简单功能/UI交互/表单校验/边界测试用例执行: 0.1-0.3h
- API/异常/数据一致性/权限/状态流转测试用例执行: 0.2-0.5h
- 回归测试用例执行: 0.15-0.4h
- 兼容性测试用例执行: 0.3-0.75h
- 只有跨端同步、复杂权限、历史数据迁移、强一致性、复杂兼容性时,才允许超过上述区间
8. 标题:中文动词开头,简洁
✓ "在主题列表实现拖拽排序"
✗ "关于 QY0010 主题拖拽排序的优化方案研究与实现"
9. 不凭空补
- 不要因为"通常应该有"就加"权限校验"任务
- 只拆需求和原型上明确存在的内容
【对账报告要求】
- matched: 完美对应(哪条需求 ↔ 哪些 QY ↔ 拆出多少任务)
- reqOnly: 需求里有但原型未见 → 列出需求 ID
- prototypeOnly: 原型里有明确功能但没有匹配到关联需求 → 列出 requirementName、QY 编号和拆出任务数
- ambiguous: QY 描述含糊无法转化 → 列出 QY 编号 + 含糊原因
【特殊情况】
- 若原型内容里看不到任何 QY 编号或类似的批注编号,但能匹配到需求编号或需求概述 → 仍按命中的需求拆解matched.noteIds 返回空数组
- 若原型内容里看不到任何 QY 编号或类似的批注编号,也匹配不到任何需求编号或需求概述 → ambiguous 列表里标注"原型内容无可识别的批注,可能不是 PRD/原型文档"devTaskDrafts/testCaseDrafts 返回空数组
- 若原型完全无法解析 → 同上处理
- 若草案没有 requirement 引用,必须有 requirementNamerequirementName 使用该批注的稳定功能名称,例如"预入职人员支持"
【输出通道强制要求】
- 第一块响应内容必须是 submit_decompose 的 tool_use
- 不要输出任何自然语言说明、Markdown、分析过程或前置文本
- 即使无法解析原型,也必须通过 submit_decompose 返回空数组和 ambiguous 报告
通过 tool 调用 submit_decompose 工具返回结果。`;
export const DECOMPOSE_TOOL_NAME = 'submit_decompose';
export const DECOMPOSE_TOOL_DESCRIPTION = '提交原型拆解结果(开发任务草案 + 测试用例草案 + 对账报告)';
export const DECOMPOSE_TOOL_INPUT_SCHEMA = {
type: 'object',
properties: {
report: {
type: 'object',
properties: {
matched: {
type: 'array',
items: {
type: 'object',
properties: {
reqId: { type: 'string' },
noteIds: { type: 'array', items: { type: 'string' } },
taskCount: { type: 'integer' },
},
required: ['reqId', 'noteIds', 'taskCount'],
},
},
reqOnly: { type: 'array', items: { type: 'string' } },
prototypeOnly: {
type: 'array',
items: {
type: 'object',
properties: {
requirementName: { type: 'string' },
noteIds: { type: 'array', items: { type: 'string' } },
taskCount: { type: 'integer' },
},
required: ['requirementName', 'noteIds', 'taskCount'],
},
},
ambiguous: {
type: 'array',
items: {
type: 'object',
properties: {
noteId: { type: 'string' },
reason: { type: 'string' },
},
required: ['noteId', 'reason'],
},
},
},
required: ['matched', 'reqOnly', 'prototypeOnly', 'ambiguous'],
},
devTaskDrafts: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' },
taskTypeName: { type: 'string' },
categoryCode: { type: 'string' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
aiEstimateHours: { type: 'number' },
requirementName: { type: 'string' },
recommendedAssigneeName: { type: 'string' },
recommendedAssigneeReason: { type: 'string' },
references: {
type: 'array',
items: {
type: 'object',
properties: {
type: { type: 'string', enum: ['requirement', 'prototype_note'] },
id: { type: 'string' },
label: { type: 'string' },
},
required: ['type', 'id', 'label'],
},
minItems: 1,
},
},
required: ['title', 'description', 'taskTypeName', 'priority', 'aiEstimateHours', 'references'],
},
},
testCaseDrafts: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' },
taskTypeName: { type: 'string' },
categoryCode: { type: 'string' },
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
aiEstimateHours: { type: 'number' },
requirementName: { type: 'string' },
recommendedAssigneeName: { type: 'string' },
recommendedAssigneeReason: { type: 'string' },
references: {
type: 'array',
items: {
type: 'object',
properties: {
type: { type: 'string', enum: ['requirement', 'prototype_note'] },
id: { type: 'string' },
label: { type: 'string' },
},
required: ['type', 'id', 'label'],
},
minItems: 1,
},
},
required: ['title', 'description', 'taskTypeName', 'priority', 'aiEstimateHours', 'references'],
},
},
},
required: ['report', 'devTaskDrafts', 'testCaseDrafts'],
};