When deliverables only expose user_requirements templates, requirement phrases still must POST intake on V0.1. Re-resolve task_key on retry and skip auto-retry for stage gate errors. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
import { DeliverableItem } from '../types/workbench';
|
||
|
||
function templatePool(deliverables: DeliverableItem[]): DeliverableItem[] {
|
||
return deliverables.filter((d) => d.isTemplateOnly);
|
||
}
|
||
|
||
/** 从文案判定 intake(V0.1)还是 user_requirements(V0.5),与交付物池无关。 */
|
||
export function resolveRequirementsTaskKey(text: string): 'intake' | 'user_requirements' {
|
||
const wantsUrs =
|
||
text.includes('用户需求表') ||
|
||
text.includes('URS') ||
|
||
(text.includes('用户需求') && !text.includes('初始'));
|
||
if (wantsUrs) return 'user_requirements';
|
||
|
||
const wantsIntake =
|
||
text.includes('初始需求') ||
|
||
text.includes('需求摸底') ||
|
||
text.includes('摸底') ||
|
||
text.includes('初始需求表');
|
||
if (wantsIntake) return 'intake';
|
||
|
||
// 含糊表述默认 V0.1 摸底表,避免误触 V0.5 用户需求表导致阶段门禁 403
|
||
return 'intake';
|
||
}
|
||
|
||
function findTemplateByTaskKey(
|
||
pool: DeliverableItem[],
|
||
taskKey: string,
|
||
): DeliverableItem | undefined {
|
||
return pool.find((d) => d.taskKey === taskKey);
|
||
}
|
||
|
||
/** 在 intake / user_requirements 等同语候选里按文案选择任务键,再匹配模板交付物。 */
|
||
function pickRequirementsTask(text: string, pool: DeliverableItem[]): DeliverableItem | undefined {
|
||
const taskKey = resolveRequirementsTaskKey(text);
|
||
const hit = findTemplateByTaskKey(pool, taskKey);
|
||
if (hit) return hit;
|
||
// 池里可能只有 URS 模板;仍返回虚拟条目以便上层使用正确 taskKey
|
||
const fallback = findTemplateByTaskKey(pool, taskKey === 'intake' ? 'user_requirements' : 'intake');
|
||
if (fallback) {
|
||
return { ...fallback, taskKey };
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/** 根据用户输入解析应触发的文档任务键(不依赖交付物池顺序)。 */
|
||
export function resolveTaskKeyFromMessage(text: string): string | undefined {
|
||
if (text.includes('需求') || text.includes('摸底') || text.includes('用户')) {
|
||
return resolveRequirementsTaskKey(text);
|
||
}
|
||
if (text.includes('调研') || text.includes('竞品')) return 'research';
|
||
if (text.includes('可行性') || text.includes('注册') || text.includes('报告')) {
|
||
if (text.includes('研发') || text.includes('dev')) return 'feasibility_dev';
|
||
return 'feasibility_reg';
|
||
}
|
||
if (text.includes('设计') || text.includes('规格') || text.includes('架构')) return 'prelim_design';
|
||
if (text.includes('报价') || text.includes('工时') || text.includes('预算')) return 'cdo_quote';
|
||
return undefined;
|
||
}
|
||
|
||
/** 根据用户输入在模板交付物中解析应触发的文档任务。 */
|
||
export function matchTemplateDeliverable(
|
||
text: string,
|
||
deliverables: DeliverableItem[],
|
||
): DeliverableItem | undefined {
|
||
const pool = templatePool(deliverables);
|
||
const taskKey = resolveTaskKeyFromMessage(text);
|
||
if (!taskKey) return undefined;
|
||
|
||
const hit = findTemplateByTaskKey(pool, taskKey);
|
||
if (hit) return hit;
|
||
|
||
if (taskKey === 'intake' || taskKey === 'user_requirements') {
|
||
return pickRequirementsTask(text, pool);
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
/** 合并文案解析与模板匹配,保证 API 使用正确 task_key。 */
|
||
export function resolveTaskTemplateForMessage(
|
||
text: string,
|
||
deliverables: DeliverableItem[],
|
||
): { taskKey: string; template?: DeliverableItem } | undefined {
|
||
const taskKey = resolveTaskKeyFromMessage(text);
|
||
if (!taskKey) return undefined;
|
||
const template = matchTemplateDeliverable(text, deliverables);
|
||
return { taskKey, template };
|
||
}
|