feat: re-review on post-approval changes, container improvements

- Compare git HEAD at reviewer approval vs owner finalize to detect
  new code changes after DONE verdict. Re-trigger reviewer if changed,
  complete if unchanged (commit-only is fine).
- Record approved HEAD in source_ref when reviewer says DONE
- Add pnpm store mount, pre-flight checks, idle timeout for containers
- Clean up orphaned containers on shutdown
This commit is contained in:
Eyejoker
2026-03-29 23:00:56 +09:00
parent b965a7e43d
commit c28fba0478
2 changed files with 43 additions and 10 deletions

View File

@@ -87,7 +87,13 @@ function detectPnpmStorePath(workspaceDir: string): string | null {
/* pnpm not available */
}
// Fallback to default location
const defaultStore = path.join(os.homedir(), '.local', 'share', 'pnpm', 'store');
const defaultStore = path.join(
os.homedir(),
'.local',
'share',
'pnpm',
'store',
);
if (fs.existsSync(defaultStore)) return defaultStore;
return null;
}

View File

@@ -302,14 +302,30 @@ export function completePairedExecutionContext(args: {
if (role === 'owner') {
const now = new Date().toISOString();
// merge_ready → owner finalized after reviewer approval → completed
// merge_ready → reviewer already approved. Check if owner made
// additional changes that need re-review.
if (task.status === 'merge_ready') {
updatePairedTask(taskId, { status: 'completed', updated_at: now });
const workspace = getPairedWorkspace(task.id, 'owner');
const currentHead = workspace?.workspace_dir
? resolveCanonicalHead(workspace.workspace_dir)
: null;
const snapshotRef = task.source_ref;
const hasNewChanges = currentHead && currentHead !== snapshotRef;
if (!hasNewChanges) {
// No code changes since reviewer approved → finalize complete
updatePairedTask(taskId, { status: 'completed', updated_at: now });
logger.info(
{ taskId, summary: args.summary?.slice(0, 100) },
'Owner finalized after reviewer approval — task completed',
);
return;
}
// Owner made changes after approval → needs re-review
logger.info(
{ taskId, summary: args.summary?.slice(0, 100) },
'Owner finalized after reviewer approval — task completed',
{ taskId, previousHead: snapshotRef, currentHead },
'Owner made changes after reviewer approval — re-triggering review',
);
return;
}
// Normal turn → auto-trigger reviewer (if within round trip limit)
@@ -345,14 +361,25 @@ export function completePairedExecutionContext(args: {
const verdict = classifyReviewerVerdict(args.summary);
switch (verdict) {
case 'done':
// Approved → owner gets final turn to commit/push
updatePairedTask(taskId, { status: 'merge_ready', updated_at: now });
case 'done': {
// Approved → owner gets final turn to commit/push.
// Record the current HEAD as source_ref so the finalize turn
// can detect if the owner made additional code changes.
const ownerWs = getPairedWorkspace(taskId, 'owner');
const approvedHead = ownerWs?.workspace_dir
? resolveCanonicalHead(ownerWs.workspace_dir)
: null;
updatePairedTask(taskId, {
status: 'merge_ready',
source_ref: approvedHead ?? task.source_ref,
updated_at: now,
});
logger.info(
{ taskId, verdict, summary: args.summary?.slice(0, 100) },
{ taskId, verdict, approvedHead, summary: args.summary?.slice(0, 100) },
'Reviewer approved — owner gets final turn to finalize',
);
break;
}
case 'blocked':
case 'needs_context':