feat(loop-engine): card 与 submit 两个动词;审核卡放宽到单选项

ADR-014 §8 原写「不发决策卡、不 submit」—— 那是假设还有 agent 在场时的保守话。
全程无 agent 的活动没有别人来干这两件事,不开口最后两步就没人做。
护栏没松:submit 仍要过提交门(判据全绿),系统提交不比模型提交宽松。
发过的卡不再发第二张;发了卡就收拍,排在后面的笔一律不跑。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-29 04:08:59 +08:00
co-authored by Claude Opus 5
parent fe8de5319c
commit e9da8d25f5
4 changed files with 185 additions and 5 deletions
@@ -333,3 +333,21 @@ describe('人工验收清单:点全才能通过', () => {
expect(checkChatCardReply(card, reply('custom:意见', undefined, '意见'))).toEqual([]);
});
});
describe('审核卡的选项数', () => {
it('普通决策卡仍要两个分支 —— 一个选项的岔路口不是岔路口', () => {
const findings = checkChatCard({ k: 'decision', ask: '往哪走', basis: [{ k: 'a', v: 'b' }], options: [{ value: 'a', label: 'A' }] } as never);
expect(findings.some((f) => f.code === 'options-missing')).toBe(true);
});
it('审核卡(variant:review)只有「确认通过」一个选项也发得出去 —— 改在件上,卡上只签字', () => {
const findings = checkChatCard({
k: 'decision',
variant: 'review',
ask: '核对过了吗',
basis: [{ k: 'a', v: 'b' }],
options: [{ value: 'approve', label: '确认通过' }],
} as never);
expect(findings.some((f) => f.code === 'options-missing')).toBe(false);
});
});
@@ -89,7 +89,17 @@ export function checkChatCard(card: ChatCard): CardViolation[] {
switch (card.k) {
case 'decision':
if (card.basis.length === 0) out.push({ code: 'bare-call', message: '决策卡必附依据快照,基座拒发' });
if (card.options.length < 2) out.push({ code: 'options-missing', message: '决策卡至少两个分支' });
/*
* **审核卡是例外**(2026-08-29):它不是岔路口,是一道签字关 ——
* 改全在件上就地做,卡上只有「确认通过」一个动作。
* 普通决策卡照旧要两个分支:一个选项的岔路口不是岔路口。
*/
{
const floor = card.variant === 'review' ? 1 : 2;
if (card.options.length < floor) {
out.push({ code: 'options-missing', message: floor === 1 ? '审核卡至少一个选项' : '决策卡至少两个分支' });
}
}
break;
case 'elicitation':
if (card.fields.length === 0) out.push({ code: 'fields-missing', message: '补全卡必须给出要填的字段' });