Archived
fix(loop-engine): 收尾复审二审 —— 兜底卡自己会抛 / 清单兜底不看面孔 / 空转写解闸
复审逐条核实上一波(C2/C3/M1/M2)修法本身引入的三处破坏,全部属实并修死: - I1 二审:配方办不下去时发的那张兜底卡(deps.cards.issue)从前裸调,调人预算 触顶抛 HumanCallBudgetExceededError 会精确复现 C3 那种无限热循环(推进器每 2 秒重来、不烧预算、不换状态、界面纹丝不动)。包进 try/catch,失败留痕、 状态退回 awaiting,不让它抛出去。配了变异验证:拿掉 try/catch 后新用例 真的变红(runTurn 抛出预算触顶错误),还原后转绿。 - C2 二审:decisionCardFace 的 checklist 兜底从前不看 reviewFace —— 一步只声明 acceptanceChecklist 而 cardVariant 不是 review(T_release 那种形状)时,配方发 的普通 decide 卡也会被兜上清单,前端不渲染勾选区、回传门却要求 checked ⊇ checklist,卡按不下去。改成只在 reviewFace 为真时才退回剧本声明的清单。 - M1/M2 二审:project 动作里 localExisting/localUnkeyed 触发的观察痕与真写 (localWrote/localDropped)共用 detail.auto + detail.ledger 这两个键,而 raiseAcceptance 的 revised 判据只认这两个键在不在 —— 一颗料 skipExisting、 或一行主键算不出来,这条"什么都没动"的痕照样能解开重做闸,人打回重做、 配方原样重跑一遍,闸却开了。auto-runner 补 detail.changed(真写了/真撤了 才为真),revised 判据对配方那一路改认这个标记。 另两笔记账: - save-wake.test.ts 的用例标题写着"验收态就是这一种(没有卡在等人)"是错的, pendingCards 把未办结工单也算在内,验收态恰恰有一张开着的验收工单——只改 标题措辞,不动断言与实现。 - docs/debt.md 补两笔:保存后唤醒的收窄几乎不生效(pendingCards 口径过宽); 模型路径的写同样会 un-land、只补了配方那一半(独立起一条,排在 W05 重写前)。 pnpm gates 退出码 0(2410 passed / 23 skipped); git diff --stat b995148...HEAD -- activities/ 为空,九个活动包一字未动。 报告:.superpowers/sdd/2026-08-29-deterministic-steps-auto-recipe/closing-fix-report.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -46,6 +46,29 @@ describe('决策卡的面孔与清单(装配层兜底)', () => {
|
||||
expect(face.checklist).toEqual(RELEASE_STEP.acceptanceChecklist);
|
||||
});
|
||||
|
||||
/**
|
||||
* **清单兜底得看 `reviewFace`**(2026-08-29 复审 I2 二审)。
|
||||
*
|
||||
* `isReleaseStep`(turn-runner.ts)认的形状是:一步声明了 `acceptanceChecklist`(给终局
|
||||
* `AcceptForm` 用)而 `cardVariant` 不是 `review`(T_release 本身就是这种形状)。配方若在
|
||||
* **这一步**发一张普通 `as:'decide'` 卡(没传 variant),从前这里照样把清单兜上去 ——
|
||||
* 前端只在 `variant === 'review'` 时才渲染清单,人看不到勾可打,回传门却要求
|
||||
* `checked ⊇ checklist`:卡按不下去。
|
||||
*/
|
||||
const NON_REVIEW_STEP_WITH_CHECKLIST = { acceptanceChecklist: ['逐条核对差异明细'] };
|
||||
|
||||
it('**非审核面孔的步声明了清单(T_release 那种形状),配方发普通决策卡:清单不兜上去**(I2 的二审正主)', () => {
|
||||
const face = decisionCardFace({}, NON_REVIEW_STEP_WITH_CHECKLIST);
|
||||
expect(face.reviewFace).toBe(false);
|
||||
expect(face.checklist).toBeUndefined();
|
||||
});
|
||||
|
||||
it('同一步:调用方明确递了 variant:review,清单才兜上去 —— reviewFace 是唯一的开关', () => {
|
||||
const face = decisionCardFace({ variant: 'review' }, NON_REVIEW_STEP_WITH_CHECKLIST);
|
||||
expect(face.reviewFace).toBe(true);
|
||||
expect(face.checklist).toEqual(NON_REVIEW_STEP_WITH_CHECKLIST.acceptanceChecklist);
|
||||
});
|
||||
|
||||
it('查不查剧本:没 itemId 查不了;两样都递全了不必查', () => {
|
||||
expect(needsStepLookup({})).toBe(false);
|
||||
expect(needsStepLookup({ itemId: 'T_release' })).toBe(true);
|
||||
|
||||
@@ -36,7 +36,13 @@ describe('件上保存之后', () => {
|
||||
* 人每按一次保存就白烧一步、界面闪一次「AI 正在推进这单」;任务本来 awaiting-human
|
||||
* (等人裁决)时被叫醒,那一拍结束还会把它降级成 awaiting-event:等的明明还是人。
|
||||
*/
|
||||
it('没有卡在等人(验收态就是这一种):件照旧重推,**不叫醒**', async () => {
|
||||
/*
|
||||
* **这条用例测的是 `pendingCards` 现读为空的那一种局面 —— 不是验收态**(2026-08-29 复审
|
||||
* A 项二审:这里原话写着「验收态就是这一种」,是错的)。`pendingCards`
|
||||
* (`human-tool-service.ts:374`)把**未办结工单**也算在内,而验收态恰恰有一张开着的
|
||||
* 验收工单 —— 真在验收态时 `pendingCards` 不空,`wake` 照样会发,不落进这条分支。
|
||||
*/
|
||||
it('没有卡在等人(pendingCards 现读为空):件照旧重推,**不叫醒**', async () => {
|
||||
const { calls, deps } = spy([]);
|
||||
await afterSave(deps, TASK, 3);
|
||||
expect(calls).toEqual(['repush', 'pending']);
|
||||
|
||||
@@ -233,6 +233,14 @@ export function createOnlyViolation(
|
||||
* **一条待确认都没有**:回传门 `checkChatCardReply` 的 `checklistMissing` 在 checklist 缺席时**恒过**,
|
||||
* 「确认通过」当场可按 —— 2026-08-27 用户当面定的那道人工验收闸凭空没了,而屏上还看不出来
|
||||
* (清单区是空的,不是红的)。兜在这一处而不是让每个调用方自己记得:**模型路径将来漏传也一并保住**。
|
||||
*
|
||||
* **清单兜底得看 `reviewFace`**(2026-08-29 复审 I2 二审):`isReleaseStep` 那条口径决定了
|
||||
* 一步可以只声明 `acceptanceChecklist`(给终局那张 `AcceptForm` 用)而不带 `cardVariant:'review'`
|
||||
* (T_release 本身就是这种形状)。配方若在**这一步**发一张普通的 `as:'decide'` 卡(既没传
|
||||
* `variant`,`step.cardVariant` 也不是 `review`),从前这里照样把 `acceptanceChecklist` 兜上去 ——
|
||||
* 前端只在 `variant === 'review'` 时才渲染清单(`chat-bubbles.tsx`),人看不到勾可打,
|
||||
* 而 `checkChatCardReply` 又要求 `checked ⊇ checklist`:卡按不下去。**只在 `reviewFace` 为真时**
|
||||
* 才退回剧本声明的清单 —— 非审核面孔的卡,清单就是没有,不发明一条按不下去的。
|
||||
*/
|
||||
export function decisionCardFace(
|
||||
input: { readonly variant?: 'review' | undefined; readonly checklist?: ReadonlyArray<string> | undefined },
|
||||
@@ -240,7 +248,11 @@ export function decisionCardFace(
|
||||
): { reviewFace: boolean; checklist: ReadonlyArray<string> | undefined } {
|
||||
const reviewFace = input.variant === 'review' || step?.cardVariant === 'review';
|
||||
const checklist =
|
||||
input.checklist !== undefined && input.checklist.length > 0 ? input.checklist : step?.acceptanceChecklist;
|
||||
input.checklist !== undefined && input.checklist.length > 0
|
||||
? input.checklist
|
||||
: reviewFace
|
||||
? step?.acceptanceChecklist
|
||||
: undefined;
|
||||
return { reviewFace, checklist };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user