From 4c09186f3635f2fad5542a0d9820b7356cb3c362 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 24 Jun 2026 23:51:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(ingest):=20autoApproveExec=20=E4=BC=A0?= =?UTF-8?q?=E9=94=99=20id=20=E8=87=B4=E8=87=AA=E5=8A=A8=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E4=B8=8D=E8=A7=A6=E5=8F=91=EF=BC=88=E7=BA=BF=E4=B8=8A=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E6=8D=95=E8=8E=B7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit condition 误把 taskId 传给 autoApproveExecOn(projectId) → getProject(taskId)=null → 永远 false,自动合并从不触发(任务卡 exec_review)。 重构为 shouldAutoApproveExec(store, task, codeV, secV),内部读 task.projectId 自取项目, 杜绝传错 id;补单测覆盖。 Co-Authored-By: Claude Opus 4.8 --- src/daemon/ingest.ts | 33 ++++++++++++++++++++------------- test/ingest.test.ts | 20 +++++++++++++++++++- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/daemon/ingest.ts b/src/daemon/ingest.ts index c2ba299..fa9bc68 100644 --- a/src/daemon/ingest.ts +++ b/src/daemon/ingest.ts @@ -41,9 +41,18 @@ function shouldAutoApprovePlan( return subtasks.every((s) => s.complexity === 'easy'); } -/** exec 自动放行开关:项目 auto-approved 且 autoApproveExec 打开(默认关)。 */ -function autoApproveExecOn(store: Store, projectId: string): boolean { - const project = store.getProject(projectId); +/** + * 是否对该执行结果自动放行(跳过 exec_review 自动合并):双复审均 approve + + * 项目 auto-approved + autoApproveExec 开。读 task.projectId 自取项目(杜绝传错 id)。默认关。 + */ +export function shouldAutoApproveExec( + store: Store, + task: { projectId: string }, + codeVerdict: string | null, + securityVerdict: string | null, +): boolean { + if (codeVerdict !== 'approve' || securityVerdict !== 'approve') return false; + const project = store.getProject(task.projectId); return !!project && project.autonomy === 'auto-approved' && !!project.autoApproveExec; } @@ -212,16 +221,14 @@ function applyRecord(store: Store, log: IngestLogger, taskId: string, runId: str // 自动放行(默认关):auto-approved + autoApproveExec + 双复审均 approve → 自动合并。 // 合并是 git 异步操作,fire-and-forget 不阻塞 tick;冲突则补救任务 + 留 exec_review 人审。 - if (rec.code.verdict === 'approve' && rec.security.verdict === 'approve' && autoApproveExecOn(store, taskId)) { - const reviewed = store.getTask(taskId); - if (reviewed) { - void acceptAndMerge(store, log, reviewed, 'auto-approve-exec') - .then((o) => { - if (o.ok) log.info(`task=${taskId} 双复审 approve + 自动放行 → 已自动合并(${o.mergeCommit})`); - else log.info(`task=${taskId} 自动合并未成(${o.error})→ 留 exec_review 人审${o.remediationTaskId ? `(补救任务 ${o.remediationTaskId})` : ''}`); - }) - .catch((e) => log.error(`task=${taskId} 自动合并异常:${(e as Error).message}`)); - } + const reviewed = store.getTask(taskId); + if (reviewed && shouldAutoApproveExec(store, reviewed, rec.code.verdict, rec.security.verdict)) { + void acceptAndMerge(store, log, reviewed, 'auto-approve-exec') + .then((o) => { + if (o.ok) log.info(`task=${taskId} 双复审 approve + 自动放行 → 已自动合并(${o.mergeCommit})`); + else log.info(`task=${taskId} 自动合并未成(${o.error})→ 留 exec_review 人审${o.remediationTaskId ? `(补救任务 ${o.remediationTaskId})` : ''}`); + }) + .catch((e) => log.error(`task=${taskId} 自动合并异常:${(e as Error).message}`)); } return; } diff --git a/test/ingest.test.ts b/test/ingest.test.ts index acc5c76..ffabae1 100644 --- a/test/ingest.test.ts +++ b/test/ingest.test.ts @@ -4,7 +4,7 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Store } from '../src/store/index.js'; -import { ingestRun } from '../src/daemon/ingest.js'; +import { ingestRun, shouldAutoApproveExec } from '../src/daemon/ingest.js'; import { appendOutbox, type OutboxPayload } from '../src/executor/protocol.js'; import { branchFor, worktreeDirFor } from '../src/executor/worktree.js'; @@ -304,6 +304,24 @@ test('autoApprovePlan:含 medium 子任务 → 不放行(改动面/复杂度 }); }); +test('shouldAutoApproveExec:双 approve + auto-approved + 开关 → true;任一不满足 → false', () => { + const s = new Store(':memory:'); + const p = s.createProject({ name: 'aae', repoPath: '/tmp/aae-' + Math.random(), autonomy: 'auto-approved' }); + const t = s.createTask({ projectId: p.id, title: 'x', complexity: 'easy' }); + // 开关默认关 → false + assert.equal(shouldAutoApproveExec(s, t, 'approve', 'approve'), false, '开关关'); + s.patchProject(p.id, { autoApproveExec: true }); + // 现在满足三条件 → true(函数读 task.projectId 自取项目,传错 id 不可能) + assert.equal(shouldAutoApproveExec(s, t, 'approve', 'approve'), true); + // verdict 任一非 approve → false + assert.equal(shouldAutoApproveExec(s, t, 'approve', 'reject'), false); + assert.equal(shouldAutoApproveExec(s, t, null, 'approve'), false); + // 非 auto-approved → false + s.patchProject(p.id, { autonomy: 'auto-easy' }); + assert.equal(shouldAutoApproveExec(s, t, 'approve', 'approve'), false); + s.close(); +}); + test('ingest planner failed:走 failPlanAttempt(退避,任务留 speccing,不进 failed)', () => { withTmpDataDir(() => { const { store, taskId, runId } = setupPlanning('medium');