feat(平台): 补齐服务端持久化和AI拆解契约
This commit is contained in:
186
apps/server/src/modules/ai/prompts/decompose.ts
Normal file
186
apps/server/src/modules/ai/prompts/decompose.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* 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/文档内容 + 关联需求列表 + 版本成员清单
|
||||
输出:开发任务草案 + 测试用例草案 + 对账报告
|
||||
|
||||
【硬规则】
|
||||
|
||||
1. 引用必须真实
|
||||
- 引用 requirement.id 必须在输入需求清单中
|
||||
- 引用 prototype_note 必须是输入原型里真实存在的 QY 编号
|
||||
- 不得编造
|
||||
|
||||
2. 任务来源限定
|
||||
- 只为以下情况拆任务:
|
||||
a) 同时被需求和原型 QY 命中
|
||||
b) 仅需求命中(原型未涉及,按需求文字拆,但工时设小,标记需要后期补充)
|
||||
- 仅 QY 命中、需求未提的批注,不拆任务,只在 noteOnly 报告里列出
|
||||
|
||||
3. 颗粒度(细颗粒)
|
||||
- 一条 QY 涉及前后端时,前端任务和后端任务必须分开
|
||||
- 接口、数据库改动、前端 UI、前端交互、表单校验视为独立任务
|
||||
- 一条 QY 可能产出 3-6 个 DevTask
|
||||
- 测试用例:每条 QY 至少 1 条功能用例 + 1 条边界用例
|
||||
|
||||
4. 任务类型
|
||||
- 每条开发任务和测试用例都必须输出 categoryCode
|
||||
- 开发任务优先使用 frontend_development / frontend_interaction / backend_development / backend_api / database_schema / api_integration
|
||||
- 测试用例优先使用 test_functional / test_api / test_exception / test_compatibility
|
||||
- 不输出数据库 categoryId
|
||||
- 不输出推荐负责人(用户后续手填)
|
||||
|
||||
5. 工时估算(小时,按团队使用 AI 辅助研发/测试估算,必须偏严格)
|
||||
- 简单前端字段、文案、展示调整: 0.25-0.5h
|
||||
- 简单前端交互,如拖拽排序 UI、开关、筛选项: 0.5-1h
|
||||
- 拖拽排序并需要持久化接口: 1-1.5h
|
||||
- 简单 CRUD 接口: 0.75-1.5h
|
||||
- 数据库字段/索引调整: 0.5h
|
||||
- 中等业务规则变更: 1.5-3h
|
||||
- 简单功能测试用例执行: 0.25-0.5h
|
||||
- API/异常/兼容性测试用例执行: 0.5-1h
|
||||
- 只有跨端同步、复杂权限、历史数据迁移、强一致性、复杂兼容性时,才允许超过上述区间
|
||||
|
||||
6. 标题:中文动词开头,简洁
|
||||
✓ "在主题列表实现拖拽排序"
|
||||
✗ "关于 QY0010 主题拖拽排序的优化方案研究与实现"
|
||||
|
||||
7. 不凭空补
|
||||
- 不要因为"通常应该有"就加"权限校验"任务
|
||||
- 只拆需求和原型上明确存在的内容
|
||||
|
||||
【对账报告要求】
|
||||
- matched: 完美对应(哪条需求 ↔ 哪些 QY ↔ 拆出多少任务)
|
||||
- reqOnly: 需求里有但原型未见 → 列出需求 ID
|
||||
- noteOnly: 原型里有但需求未提 → 列出 QY 编号
|
||||
- ambiguous: QY 描述含糊无法转化 → 列出 QY 编号 + 含糊原因
|
||||
|
||||
【特殊情况】
|
||||
- 若原型内容里看不到任何 QY 编号或类似的批注编号 → ambiguous 列表里标注"原型内容无可识别的批注,可能不是 PRD/原型文档",devTaskDrafts/testCaseDrafts 返回空数组
|
||||
- 若原型完全无法解析 → 同上处理
|
||||
|
||||
【输出通道强制要求】
|
||||
- 第一块响应内容必须是 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' } },
|
||||
noteOnly: { type: 'array', items: { type: 'string' } },
|
||||
ambiguous: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
noteId: { type: 'string' },
|
||||
reason: { type: 'string' },
|
||||
},
|
||||
required: ['noteId', 'reason'],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['matched', 'reqOnly', 'noteOnly', 'ambiguous'],
|
||||
},
|
||||
devTaskDrafts: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
categoryCode: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'frontend_development',
|
||||
'frontend_interaction',
|
||||
'backend_development',
|
||||
'backend_api',
|
||||
'database_schema',
|
||||
'api_integration',
|
||||
'data_processing',
|
||||
'implementation_support',
|
||||
'documentation',
|
||||
],
|
||||
},
|
||||
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
|
||||
estimateHours: { type: 'number' },
|
||||
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', 'categoryCode', 'priority', 'estimateHours', 'references'],
|
||||
},
|
||||
},
|
||||
testCaseDrafts: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
categoryCode: {
|
||||
type: 'string',
|
||||
enum: ['test_functional', 'test_api', 'test_exception', 'test_compatibility'],
|
||||
},
|
||||
priority: { type: 'string', enum: ['P0', 'P1', 'P2', 'P3'] },
|
||||
estimateHours: { type: 'number' },
|
||||
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', 'categoryCode', 'priority', 'estimateHours', 'references'],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['report', 'devTaskDrafts', 'testCaseDrafts'],
|
||||
};
|
||||
Reference in New Issue
Block a user