fix: stop fabricating auto-chain hook evidence

This commit is contained in:
Eve
2026-04-24 06:53:24 +08:00
parent 13bc748a83
commit c7a7b4098d
3 changed files with 126 additions and 6 deletions

View File

@@ -65,6 +65,10 @@ async function main() {
'I need you to review the behavior, choose the final accept/reject decision,',
'and continue in background with a follow-up later.',
].join(' ');
const plannerOnlyRequestText = [
'Please inspect the workspace files and verify the hook injection path.',
'Summarize the current dry-run planner state for technical inspection only.',
].join(' ');
const injected = await runScenario(forceRecall, requestText);
@@ -252,6 +256,79 @@ async function main() {
assert.match(neutralInjected, /autoChainAllowed=false/, 'hook neutral-path should keep auto-chain disabled');
assert.doesNotMatch(neutralInjected, /reason=explicit auto-chain next action requires dispatched-action evidence/, 'hook neutral-path should not fail on auto-chain evidence when no explicit tool action exists');
const specReviewWithoutEvidenceInjected = await withPatchedWrapper(buildWrapperScript({
classification: 'long_task',
silentCandidate: false,
needsCheckpoint: false,
needsSubagent: false,
needsOwnerDecision: false,
silentLaunchOk: true,
requiredNextAction: 'dispatch_code_quality_review',
autoChainDispatchEvidence: {
action: 'dispatch_code_quality_review',
dispatched: true,
event: 'dispatch',
},
taskRecord: { task_name: 'task-spec-review-missing-evidence' },
handoff: { mode: 'direct_reply' },
}), async () => runScenario(forceRecall, plannerOnlyRequestText));
assert.match(specReviewWithoutEvidenceInjected, /\[LONG_TASK_AUTO_CHAIN_PLAN\]/, 'hook spec-review missing-evidence path should emit auto-chain plan block');
assert.match(specReviewWithoutEvidenceInjected, /plannerStatus=blocked_by_evidence/, 'hook spec-review missing-evidence path should block on missing evidence');
assert.match(specReviewWithoutEvidenceInjected, /derivedAction=none/, 'hook spec-review missing-evidence path should not derive a dry-run action');
assert.match(specReviewWithoutEvidenceInjected, /dispatchMode=no_dispatch/, 'hook spec-review missing-evidence path should stay no-dispatch');
assert.match(specReviewWithoutEvidenceInjected, /autoChainAllowed=false/, 'hook spec-review missing-evidence path should not allow auto-chain');
assert.match(specReviewWithoutEvidenceInjected, /reason=review pass evidence missing for code quality review transition/, 'hook spec-review missing-evidence path should mention missing review evidence');
assert.match(specReviewWithoutEvidenceInjected, /requiredEvidence=reviewEvidence/, 'hook spec-review missing-evidence path should require reviewEvidence');
const fixSliceWithoutEvidenceInjected = await withPatchedWrapper(buildWrapperScript({
classification: 'long_task',
silentCandidate: false,
needsCheckpoint: false,
needsSubagent: false,
needsOwnerDecision: false,
silentLaunchOk: true,
silentLaunchReason: 'review blocked by findings',
requiredNextAction: 'dispatch_fix_slice',
autoChainDispatchEvidence: {
action: 'dispatch_fix_slice',
dispatched: true,
event: 'dispatch',
},
taskRecord: { task_name: 'task-fix-slice-missing-evidence' },
handoff: { mode: 'direct_reply' },
}), async () => runScenario(forceRecall, plannerOnlyRequestText));
assert.match(fixSliceWithoutEvidenceInjected, /\[LONG_TASK_AUTO_CHAIN_PLAN\]/, 'hook fix-slice missing-evidence path should emit auto-chain plan block');
assert.match(fixSliceWithoutEvidenceInjected, /plannerStatus=blocked_by_evidence/, 'hook fix-slice missing-evidence path should block on missing evidence');
assert.match(fixSliceWithoutEvidenceInjected, /derivedAction=none/, 'hook fix-slice missing-evidence path should not derive a dry-run action');
assert.match(fixSliceWithoutEvidenceInjected, /dispatchMode=no_dispatch/, 'hook fix-slice missing-evidence path should stay no-dispatch');
assert.match(fixSliceWithoutEvidenceInjected, /autoChainAllowed=false/, 'hook fix-slice missing-evidence path should not allow auto-chain');
assert.match(fixSliceWithoutEvidenceInjected, /reason=blocker evidence missing for retry\/fix transition/, 'hook fix-slice missing-evidence path should mention missing blocker evidence');
assert.match(fixSliceWithoutEvidenceInjected, /requiredEvidence=blockerEvidence/, 'hook fix-slice missing-evidence path should require blockerEvidence');
const specReviewWithoutImplementationEvidenceInjected = await withPatchedWrapper(buildWrapperScript({
classification: 'long_task',
silentCandidate: false,
needsCheckpoint: false,
needsSubagent: false,
needsOwnerDecision: false,
silentLaunchOk: true,
requiredNextAction: 'dispatch_spec_review',
autoChainDispatchEvidence: {
action: 'dispatch_spec_review',
dispatched: true,
event: 'dispatch',
},
taskRecord: { task_name: 'task-implementation-missing-evidence' },
handoff: { mode: 'direct_reply' },
}), async () => runScenario(forceRecall, plannerOnlyRequestText));
assert.match(specReviewWithoutImplementationEvidenceInjected, /\[LONG_TASK_AUTO_CHAIN_PLAN\]/, 'hook implementation missing-evidence path should emit auto-chain plan block');
assert.match(specReviewWithoutImplementationEvidenceInjected, /plannerStatus=blocked_by_evidence/, 'hook implementation missing-evidence path should block on missing evidence');
assert.match(specReviewWithoutImplementationEvidenceInjected, /derivedAction=none/, 'hook implementation missing-evidence path should not derive a dry-run action');
assert.match(specReviewWithoutImplementationEvidenceInjected, /dispatchMode=no_dispatch/, 'hook implementation missing-evidence path should stay no-dispatch');
assert.match(specReviewWithoutImplementationEvidenceInjected, /autoChainAllowed=false/, 'hook implementation missing-evidence path should not allow auto-chain');
assert.match(specReviewWithoutImplementationEvidenceInjected, /reason=implementation evidence missing for review-required next action/, 'hook implementation missing-evidence path should mention missing implementation evidence');
assert.match(specReviewWithoutImplementationEvidenceInjected, /requiredEvidence=executionEvidence/, 'hook implementation missing-evidence path should require executionEvidence');
const originalGateLock = await fs.readFile(gateLockPath, 'utf8');
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'force-recall-gate-lock-'));
const backupPath = path.join(tempDir, path.basename(gateLockPath));