fix(web): prefer named deliverable over negated 需求
Explicit aliases (including 系统设计输入) must win before keyword fallback so UI-11 does not route to intake. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -203,3 +203,23 @@ test('no active doc does not invent a pin', () => {
|
||||
assert.equal(resolved?.docId, undefined);
|
||||
assert.equal(resolved?.pinnedToActiveDoc, undefined);
|
||||
});
|
||||
|
||||
const UI11_SECOND_DOC_PROMPT =
|
||||
'请另行生成一份《系统设计输入》受控文档(第二份),不要覆盖上一份需求文档,并给出可打开的交付物。';
|
||||
|
||||
test('UI-11 named 系统设计输入 beats negated 需求 background', () => {
|
||||
assert.equal(resolveTaskKeyFromMessage(UI11_SECOND_DOC_PROMPT), 'prelim_design');
|
||||
assert.equal(resolveTaskTemplateForMessage(UI11_SECOND_DOC_PROMPT, bothPool, activeIntake)?.taskKey, 'prelim_design');
|
||||
});
|
||||
|
||||
test('mixed generate-design + do-not-cover-requirements stays prelim_design', () => {
|
||||
const text = '请生成《系统设计输入》,不要覆盖上一份需求文档';
|
||||
assert.equal(resolveTaskKeyFromMessage(text), 'prelim_design');
|
||||
});
|
||||
|
||||
test('current-doc pin still wins when no other deliverable is named', () => {
|
||||
const text = '请改写当前受控文档中的风险管理章节,不要覆盖上一份需求文档';
|
||||
const resolved = resolveTaskTemplateForMessage(text, bothPool, activeIntake);
|
||||
assert.equal(resolved?.taskKey, 'intake');
|
||||
assert.equal(resolved?.pinnedToActiveDoc, true);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ const NAMED_DELIVERABLE_ALIASES: { taskKey: string; patterns: RegExp[] }[] = [
|
||||
{ taskKey: 'research', patterns: [/调研报告/, /竞品调研/] },
|
||||
{ taskKey: 'feasibility_reg', patterns: [/注册路径可行性/, /法规可行性评估/, /注册可行性评估/] },
|
||||
{ taskKey: 'feasibility_dev', patterns: [/设计开发可行性/, /研发可行性评估/] },
|
||||
{ taskKey: 'prelim_design', patterns: [/初步设计说明书/, /系统设计规格/] },
|
||||
{ taskKey: 'prelim_design', patterns: [/系统设计输入/, /初步设计说明书/, /系统设计规格/] },
|
||||
{ taskKey: 'cdo_quote', patterns: [/CDO报价/, /报价核算表/] },
|
||||
];
|
||||
|
||||
@@ -66,11 +66,7 @@ function explicitNamedDeliverableTaskKey(
|
||||
for (const d of titled) {
|
||||
if (text.includes(d.title)) return d.taskKey;
|
||||
}
|
||||
for (const alias of NAMED_DELIVERABLE_ALIASES) {
|
||||
if (alias.taskKey === excludeTaskKey) continue;
|
||||
if (alias.patterns.some((p) => p.test(text))) return alias.taskKey;
|
||||
}
|
||||
return undefined;
|
||||
return namedAliasTaskKey(text, excludeTaskKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,12 +114,24 @@ function pickRequirementsTask(text: string, pool: DeliverableItem[]): Deliverabl
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function namedAliasTaskKey(text: string, excludeTaskKey?: string): string | undefined {
|
||||
for (const alias of NAMED_DELIVERABLE_ALIASES) {
|
||||
if (alias.taskKey === excludeTaskKey) continue;
|
||||
if (alias.patterns.some((p) => p.test(text))) return alias.taskKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** 根据用户输入解析应触发的文档任务键(不依赖交付物池顺序)。 */
|
||||
export function resolveTaskKeyFromMessage(text: string): string | undefined {
|
||||
const normalized = text.trim();
|
||||
// 模板名只是操作对象,不代表用户授权写入。只读动词优先;只有明确写动作才走任务 API。
|
||||
if (!hasDocumentWriteIntent(normalized)) return undefined;
|
||||
|
||||
// 明确点名的交付物优先于否定背景/旧文档里的「需求」等关键词。
|
||||
const named = namedAliasTaskKey(normalized);
|
||||
if (named) return named;
|
||||
|
||||
if (normalized.includes('需求') || normalized.includes('摸底') || normalized.includes('用户') || normalized.includes('首轮')) {
|
||||
return resolveRequirementsTaskKey(normalized);
|
||||
}
|
||||
@@ -185,6 +193,12 @@ export function resolveTaskTemplateForMessage(
|
||||
}
|
||||
}
|
||||
|
||||
const named = explicitNamedDeliverableTaskKey(text, deliverables);
|
||||
if (named) {
|
||||
const template = matchTemplateDeliverableByKey(named, deliverables, text);
|
||||
return { taskKey: named, template };
|
||||
}
|
||||
|
||||
const taskKey = resolveTaskKeyFromMessage(text);
|
||||
if (!taskKey) return undefined;
|
||||
const template = matchTemplateDeliverable(text, deliverables);
|
||||
|
||||
Reference in New Issue
Block a user