/**
 * Post-unit verification gate for autonomous mode.
 *
 * Runs typecheck/lint/test checks, captures runtime errors, performs
 * dependency audits, handles auto-fix retry logic, and writes
 * verification evidence JSON.
 *
 * Extracted from handleAgentEnd() in auto.ts. Returns a sentinel
 * value instead of calling return/pauseAuto directly — the caller
 * checks the result and handles control flow.
 */
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { getErrorMessage } from "../error-utils.js";
import { loadFile } from "../files.js";
import { parseRoadmap } from "../parsers.js";
import { resolveMilestoneFile, resolveSlicePath } from "../paths.js";
import { runPostExecutionChecks } from "../post-execution-checks.js";
import { loadEffectiveSFPreferences } from "../preferences.js";
import {
	getMilestoneSlices,
	getSliceTasks,
	getTask,
	isDbAvailable,
} from "../sf-db.js";
import { isMilestoneComplete } from "../state.js";
import { isClosedStatus } from "../status-guards.js";
import { parseUnitId } from "../unit-id.js";
import { extractVerdict } from "../verdict-parser.js";
import { decideAutoDefer } from "../verification-defer-policy.js";
import { writeVerificationJSON } from "../verification-evidence.js";
import {
	captureRuntimeErrors,
	formatFailureContext,
	runDependencyAudit,
	runVerificationGate,
} from "../verification-gate.js";
import { logError, logWarning } from "../workflow-logger.js";
import {
	applyAdaptiveVerificationPolicy,
	computeAdaptiveVerificationPolicy,
} from "./adaptive-verification-policy.js";
import { ChaosMonkeyGate } from "./chaos-monkey.js";
import { CostGuardGate } from "./cost-guard-gate.js";
import { resolveUokFlags } from "./flags.js";
import { UokGateRunner } from "./gate-runner.js";
import { MultiPackageGate } from "./multi-package-gate.js";
import { OutcomeLearningGate } from "./outcome-learning-gate.js";
import { SecurityGate } from "./security-gate.js";
import {
	formatExecuteTaskRecoveryStatus,
	inspectExecuteTaskDurability,
} from "./unit-runtime.js";

function computeTokenCountFromSession(ctx) {
	const entries = ctx.sessionManager?.getEntries?.() ?? [];
	let total = 0;
	for (const entry of entries) {
		if (entry.type !== "message") continue;
		const msg = entry.message;
		if (!msg || msg.role !== "assistant") continue;
		if (msg.usage?.totalTokens != null) {
			total += msg.usage.totalTokens;
		}
	}
	return total;
}

function getMemoryPressureMB() {
	try {
		const mem = process.memoryUsage();
		return Math.round(mem.heapUsed / 1024 / 1024);
	} catch {
		return undefined;
	}
}

function buildGateOutcomesSummary(gateIds, gateResults) {
	if (!gateIds || !gateResults || gateIds.length === 0) return undefined;
	const outcomes = {};
	for (let i = 0; i < gateIds.length; i++) {
		outcomes[gateIds[i]] = gateResults[i]?.outcome ?? "unknown";
	}
	return outcomes;
}

function isInfraVerificationFailure(stderr) {
	return /\b(ENOENT|ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN|spawn\s+\S+\s+ENOENT|command not found)\b/i.test(
		stderr,
	);
}
/**
 * Post-unit guard for `validate-milestone` units (#4094).
 *
 * When validate-milestone writes verdict=needs-remediation, the agent is
 * expected to also call reassess_roadmap in the same turn to add
 * remediation slices. If they don't, the state machine re-derives
 * `phase: validating-milestone` indefinitely (all slices still complete +
 * verdict still needs-remediation), wasting ~3 dispatches before the stuck
 * detector fires.
 *
 * This guard fires immediately on the first occurrence: if VALIDATION.md
 * verdict is needs-remediation and no incomplete slices exist for the
 * milestone, pause the auto-loop with a clear blocker.
 */
async function runValidateMilestonePostCheck(vctx, pauseAuto) {
	const { s, ctx, pi } = vctx;
	const prefs = loadEffectiveSFPreferences()?.preferences;
	const uokFlags = resolveUokFlags(prefs);
	const persistMilestoneValidationGate = async (
		outcome,
		failureClass,
		rationale,
		findings = "",
		milestoneId,
	) => {
		if (!uokFlags.gates || !s.currentUnit) return;
		const gateRunner = new UokGateRunner();
		gateRunner.register({
			id: "milestone-validation-post-check",
			type: "verification",
			execute: async () => ({
				outcome,
				failureClass,
				rationale,
				findings,
			}),
		});
		await gateRunner.run("milestone-validation-post-check", {
			basePath: s.basePath,
			traceId: `validation-post-check:${s.currentUnit.id}`,
			turnId: s.currentUnit.id,
			milestoneId,
			unitType: s.currentUnit.type,
			unitId: s.currentUnit.id,
		});
	};
	if (!s.currentUnit) return "continue";
	const { milestone: mid } = parseUnitId(s.currentUnit.id);
	if (!mid) return "continue";
	const validationFile = resolveMilestoneFile(s.basePath, mid, "VALIDATION");
	if (!validationFile) return "continue";
	const validationContent = await loadFile(validationFile);
	if (!validationContent) return "continue";
	const verdict = extractVerdict(validationContent);
	if (verdict !== "needs-remediation") {
		await persistMilestoneValidationGate(
			"pass",
			"none",
			`milestone validation verdict is ${verdict}; no remediation loop risk`,
			"",
			mid,
		);
		return "continue";
	}
	const incompleteSliceCount = await countIncompleteSlices(s.basePath, mid);
	// If any non-closed slices exist, the agent successfully queued remediation
	// work — proceed normally. The state machine will execute those slices and
	// re-validate per the #3596/#3670 fix.
	if (incompleteSliceCount > 0) {
		await persistMilestoneValidationGate(
			"pass",
			"none",
			`remediation slices present (${incompleteSliceCount}); validation can continue`,
			"",
			mid,
		);
		return "continue";
	}
	ctx.ui.notify(
		`Milestone ${mid} validation returned verdict=needs-remediation but no remediation slices were added. Pausing for human review.`,
		"error",
	);
	process.stderr.write(
		`validate-milestone: pausing — verdict=needs-remediation with no incomplete slices for ${mid}. ` +
			`The agent must call reassess_roadmap to add remediation slices before re-validation.\n`,
	);
	await persistMilestoneValidationGate(
		"manual-attention",
		"manual-attention",
		"needs-remediation verdict without queued remediation slices",
		`No incomplete slices found for ${mid} while verdict=needs-remediation`,
		mid,
	);
	await pauseAuto(ctx, pi);
	return "pause";
}
/**
 * Count slices for a milestone that are not in a closed status.
 * DB-backed projects are authoritative (#4094 peer review); falls back to
 * roadmap parsing only when the DB is unavailable.
 */
async function countIncompleteSlices(basePath, milestoneId) {
	if (isDbAvailable()) {
		const slices = getMilestoneSlices(milestoneId);
		if (slices.length === 0) {
			// No DB rows — treat as "unknown", do not pause.
			return 1;
		}
		return slices.filter((slice) => !isClosedStatus(slice.status)).length;
	}
	// Filesystem fallback: parse the roadmap markdown.
	try {
		const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
		if (!roadmapFile) return 1;
		const roadmapContent = await loadFile(roadmapFile);
		if (!roadmapContent) return 1;
		const roadmap = parseRoadmap(roadmapContent);
		if (roadmap.slices.length === 0) return 1;
		return isMilestoneComplete(roadmap) ? 0 : 1;
	} catch {
		// Parsing failures should not cause false-positive pauses.
		return 1;
	}
}
/**
 * Run the verification gate for the current execute-task unit.
 * Returns:
 * - "continue" — gate passed (or no checks configured), proceed normally
 * - "retry" — gate failed with retries remaining, s.pendingVerificationRetry set for loop re-iteration
 * - "pause" — gate failed with retries exhausted, pauseAuto already called
 */
export async function runPostUnitVerification(vctx, pauseAuto) {
	const { s, ctx, pi } = vctx;
	if (!s.currentUnit) {
		return "continue";
	}
	if (s.currentUnit.type === "validate-milestone") {
		return await runValidateMilestonePostCheck(vctx, pauseAuto);
	}
	if (s.currentUnit.type !== "execute-task") {
		return "continue";
	}
	// ── Zone 1: Gate machinery (outer try) ──────────────────────────────────
	// Failures here indicate broken infrastructure — pause for human review.
	let prefs;
	let uokFlags;
	let mid;
	let sid;
	let tid;
	let result;
	try {
		const effectivePrefs = loadEffectiveSFPreferences();
		prefs = effectivePrefs?.preferences;
		uokFlags = resolveUokFlags(prefs);
		// Read task plan verify field
		({ milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id));
		let taskPlanVerify;
		if (mid && sid && tid) {
			if (isDbAvailable()) {
				taskPlanVerify = getTask(mid, sid, tid)?.verify;
			}
			// When DB unavailable, taskPlanVerify stays undefined — gate runs without task-specific checks
		}
		result = runVerificationGate({
			cwd: s.basePath,
			preferenceCommands: prefs?.verification_commands,
			taskPlanVerify,
		});
		// Handle skipped gate (no commands discovered) — fail-closed but not a hard failure
		if (result.skipped === true) {
			process.stderr.write(
				"verification-gate: no commands discovered — gate skipped, not passed\n",
			);
			ctx.ui.notify(
				"[verify] SKIP — no verification commands configured",
				"warning",
			);
			return "continue";
		}
		// Capture runtime errors
		const runtimeErrors = await captureRuntimeErrors();
		if (runtimeErrors.length > 0) {
			result.runtimeErrors = runtimeErrors;
			if (runtimeErrors.some((e) => e.blocking)) {
				result.passed = false;
			}
		}
		// Dependency audit
		const auditWarnings = runDependencyAudit(s.basePath);
		if (auditWarnings.length > 0) {
			result.auditWarnings = auditWarnings;
			process.stderr.write(
				`verification-gate: ${auditWarnings.length} audit warning(s)\n`,
			);
			for (const w of auditWarnings) {
				process.stderr.write(`  [${w.severity}] ${w.name}: ${w.title}\n`);
			}
		}
	} catch (machineryErr) {
		logError(
			"engine",
			`verification-gate machinery error — pausing for human review: ${machineryErr.message}`,
		);
		ctx.ui.notify(
			"verification-gate machinery error — pausing for human review",
			"error",
		);
		await pauseAuto(ctx, pi);
		return "pause";
	}
	// ── Zone 2: Ancillary post-gate work (inner try) ─────────────────────────
	// Failures here are non-fatal — evidence writes, UOK gate calls, notifications, retry logic.
	let gateIds = [];
	let gateResults = [];
	try {
		if (uokFlags.gates) {
			const gateRunner = new UokGateRunner();
			gateRunner.register({
				id: "verification-gate",
				type: "verification",
				execute: async () => ({
					outcome: result.passed ? "pass" : "fail",
					failureClass: result.runtimeErrors?.some((e) => e.blocking)
						? "execution"
						: "verification",
					rationale: result.passed
						? "verification checks passed"
						: "verification checks failed",
					findings: result.passed ? "" : formatFailureContext(result),
				}),
			});
			if (uokFlags.securityGuard) {
				gateRunner.register(new SecurityGate());
			}
			if (uokFlags.multiPackageHealing) {
				gateRunner.register(new MultiPackageGate());
			}
			if (uokFlags.autonomousCostGuard) {
				gateRunner.register(new CostGuardGate());
			}
			if (uokFlags.outcomeLearning) {
				gateRunner.register(new OutcomeLearningGate());
			}
			if (uokFlags.chaosMonkey) {
				gateRunner.register(new ChaosMonkeyGate({ active: true }));
			}

			const baseCtx = {
				basePath: s.basePath,
				traceId: `verification:${s.currentUnit.id}`,
				turnId: s.currentUnit.id,
				milestoneId: mid ?? undefined,
				sliceId: sid ?? undefined,
				taskId: tid ?? undefined,
				unitType: s.currentUnit.type,
				unitId: s.currentUnit.id,
				iteration: s.verificationRetryCount.get(s.currentUnit.id) ?? 0,
			};

			gateIds = gateRunner.list().map((g) => g.id);
			gateResults = await Promise.all(
				gateIds.map((id) =>
					gateRunner
						.run(id, {
							...baseCtx,
							traceId: `${id}:${s.currentUnit.id}`,
						})
						.catch((err) => ({
							outcome: "fail",
							failureClass: "unknown",
							rationale: `Gate ${id} threw: ${getErrorMessage(err)}`,
						})),
				),
			);

			for (let i = 0; i < gateIds.length; i++) {
				const id = gateIds[i];
				const res = gateResults[i];
				if (res.outcome !== "fail") continue;
				result.passed = false;
				if (id === "security-guard") {
					result.securityFailure = true;
					result.securityRationale = res.rationale;
					result.securityFindings = res.findings;
				} else if (id === "multi-package-healing") {
					result.multiPackageFailure = true;
					result.multiPackageRationale = res.rationale;
					result.multiPackageFindings = res.findings;
				} else if (id === "cost-guard") {
					result.costGuardFailure = true;
					result.costGuardRationale = res.rationale;
				} else if (id === "chaos-monkey") {
					result.chaosMonkeyFailure = true;
					result.chaosMonkeyRationale = res.rationale;
				}
			}
			// ── Adaptive verification policy ──
			// After OutcomeLearningGate runs, check if failure patterns warrant
			// adjusting retry budgets to avoid wasted cycles or allow more retries.
			if (uokFlags.outcomeLearning) {
				try {
					const adaptivePolicy = computeAdaptiveVerificationPolicy(
						s.basePath,
						prefs,
					);
					if (adaptivePolicy) {
						process.stderr.write(
							`verification-gate: adaptive policy triggered — ` +
								`max_retries=${adaptivePolicy.verification_max_retries}, ` +
								`auto_fix=${adaptivePolicy.verification_auto_fix}\n`,
						);
						await applyAdaptiveVerificationPolicy(s.basePath, adaptivePolicy);
						// Update local prefs so the rest of this function uses the new values
						prefs.verification_max_retries =
							adaptivePolicy.verification_max_retries;
						prefs.verification_auto_fix = adaptivePolicy.verification_auto_fix;
					}
				} catch (adaptiveErr) {
					logWarning(
						"engine",
						`adaptive-verification-policy error: ${adaptiveErr.message}`,
					);
				}
			}
		}
		// Auto-fix retry preferences
		const autoFixEnabled = prefs?.verification_auto_fix !== false;
		const maxRetries =
			typeof prefs?.verification_max_retries === "number"
				? prefs.verification_max_retries
				: 2;
		if (result.checks.length > 0) {
			const passCount = result.checks.filter((c) => c.exitCode === 0).length;
			const total = result.checks.length;
			const commandList = result.checks.map((c) => c.command).join(" | ");
			ctx.ui.notify(`[verify] running: ${commandList}`, "info");
			const attemptSoFar = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
			if (result.passed) {
				ctx.ui.notify(`[verify] PASS - ${passCount}/${total} checks`, "info");
			} else {
				const failures = result.checks.filter((c) => c.exitCode !== 0);
				const failNames = failures.map((f) => f.command).join(", ");
				const nextAttempt = attemptSoFar + 1;
				ctx.ui.notify(
					`[verify] FAIL - ${failNames} (auto-fix attempt ${nextAttempt}/${maxRetries})`,
					"info",
				);
				process.stderr.write(
					`verification-gate: ${total - passCount}/${total} checks failed\n`,
				);
				for (const f of failures) {
					process.stderr.write(`  ${f.command} exited ${f.exitCode}\n`);
					if (f.stderr)
						process.stderr.write(`  stderr: ${f.stderr.slice(0, 500)}\n`);
				}
			}
		}
		// Log blocking runtime errors
		if (result.runtimeErrors?.some((e) => e.blocking)) {
			const blockingErrors = result.runtimeErrors.filter((e) => e.blocking);
			process.stderr.write(
				`verification-gate: ${blockingErrors.length} blocking runtime error(s) detected\n`,
			);
			for (const err of blockingErrors) {
				process.stderr.write(
					`  [${err.source}] ${err.severity}: ${err.message.slice(0, 200)}\n`,
				);
			}
		}
		// Log security failures
		if (result.securityFailure) {
			ctx.ui.notify(
				`[verify] SECURITY FAIL — ${result.securityRationale}`,
				"error",
			);
			process.stderr.write(
				`verification-gate: security failure: ${result.securityRationale}\n`,
			);
			if (result.securityFindings) {
				process.stderr.write(`${result.securityFindings}\n`);
			}
		}
		// Log multi-package failures
		if (result.multiPackageFailure) {
			ctx.ui.notify(
				`[verify] MULTI-PACKAGE FAIL — ${result.multiPackageRationale}`,
				"error",
			);
			process.stderr.write(
				`verification-gate: multi-package healing failure: ${result.multiPackageRationale}\n`,
			);
			if (result.multiPackageFindings) {
				process.stderr.write(`${result.multiPackageFindings}\n`);
			}
		}
		// Log cost-guard failures
		if (result.costGuardFailure) {
			ctx.ui.notify(
				`[verify] COST-GUARD FAIL — ${result.costGuardRationale}`,
				"error",
			);
			process.stderr.write(
				`verification-gate: cost-guard failure: ${result.costGuardRationale}\n`,
			);
		}
		// Log chaos-monkey failures
		if (result.chaosMonkeyFailure) {
			ctx.ui.notify(
				`[verify] CHAOS-MONKEY FAIL — ${result.chaosMonkeyRationale}`,
				"error",
			);
			process.stderr.write(
				`verification-gate: chaos-monkey injected failure: ${result.chaosMonkeyRationale}\n`,
			);
		}
		// Write verification evidence JSON
		const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
		const tokenCount = computeTokenCountFromSession(ctx);
		const memoryPressureMB = getMemoryPressureMB();
		const gateOutcomes = buildGateOutcomesSummary(gateIds, gateResults);
		let recoveryStatus;
		try {
			const durability = await inspectExecuteTaskDurability(
				s.basePath,
				s.currentUnit.id,
			);
			if (durability) {
				recoveryStatus = formatExecuteTaskRecoveryStatus(durability);
			}
		} catch {
			recoveryStatus = undefined;
		}
		if (mid && sid && tid) {
			try {
				const sDir = resolveSlicePath(s.basePath, mid, sid);
				if (sDir) {
					const tasksDir = join(sDir, "tasks");
					if (result.passed) {
						writeVerificationJSON(
							result,
							tasksDir,
							tid,
							s.currentUnit.id,
							undefined,
							undefined,
							tokenCount,
							memoryPressureMB,
							gateOutcomes,
							recoveryStatus,
						);
					} else {
						const nextAttempt = attempt + 1;
						writeVerificationJSON(
							result,
							tasksDir,
							tid,
							s.currentUnit.id,
							nextAttempt,
							maxRetries,
							tokenCount,
							memoryPressureMB,
							gateOutcomes,
							recoveryStatus,
						);
					}
				}
			} catch (evidenceErr) {
				logWarning(
					"engine",
					`verification-evidence write error: ${evidenceErr.message}`,
				);
			}
		}
		const advisoryFailure =
			!result.passed &&
			(result.discoverySource === "package-json" ||
				result.checks.some((check) =>
					isInfraVerificationFailure(check.stderr),
				));
		if (advisoryFailure) {
			s.verificationRetryCount.delete(s.currentUnit.id);
			s.pendingVerificationRetry = null;
			ctx.ui.notify(
				result.discoverySource === "package-json"
					? "Verification failed in auto-discovered package.json checks — treating as advisory."
					: "Verification failed due to infrastructure/runtime environment issues — treating as advisory.",
				"warning",
			);
			return "continue";
		}
		// ── Post-execution checks (run after main verification passes for execute-task units) ──
		let postExecChecks;
		let postExecBlockingFailure = false;
		if (result.passed && mid && sid && tid) {
			// Check preferences — respect enhanced_verification and enhanced_verification_post
			const enhancedEnabled = prefs?.enhanced_verification !== false; // default true
			const postEnabled = prefs?.enhanced_verification_post !== false; // default true
			if (enhancedEnabled && postEnabled && isDbAvailable()) {
				try {
					// Get the completed task from DB
					const taskRow = getTask(mid, sid, tid);
					if (taskRow && taskRow.key_files && taskRow.key_files.length > 0) {
						// Get all tasks in the slice
						const allTasks = getSliceTasks(mid, sid);
						// Filter to prior completed tasks (status = 'complete' or 'done', before current task)
						const priorTasks = allTasks.filter(
							(t) =>
								(t.status === "complete" || t.status === "done") &&
								t.id !== tid &&
								t.sequence < taskRow.sequence,
						);
						// Run post-execution checks
						// Get all tasks across all slices for cross-slice consistency check
						const allSliceTasks = [];
						if (mid) {
							const slices = getMilestoneSlices(mid);
							for (const slice of slices) {
								if (slice.id !== sid) {
									const sliceTasks = getSliceTasks(mid, slice.id);
									allSliceTasks.push(...sliceTasks);
								}
							}
						}
						const postExecResult = runPostExecutionChecks(
							taskRow,
							priorTasks,
							s.basePath,
							mid,
							allSliceTasks,
						);
						// Store checks for evidence JSON
						postExecChecks = postExecResult.checks;
						// Log summary to stderr with sf-post-exec: prefix
						const emoji =
							postExecResult.status === "pass"
								? "✅"
								: postExecResult.status === "warn"
									? "⚠️"
									: "❌";
						process.stderr.write(
							`sf-post-exec: ${emoji} Post-execution checks ${postExecResult.status} for ${mid}/${sid}/${tid} (${postExecResult.durationMs}ms)\n`,
						);
						// Log individual check results
						for (const check of postExecResult.checks) {
							const checkEmoji = check.passed
								? "✓"
								: check.blocking
									? "✗"
									: "⚠";
							process.stderr.write(
								`sf-post-exec:   ${checkEmoji} [${check.category}] ${check.target}: ${check.message}\n`,
							);
						}
						if (uokFlags.gates) {
							const strictMode = prefs?.enhanced_verification_strict === true;
							const warnEscalated =
								postExecResult.status === "warn" && strictMode;
							const blockingFailure =
								postExecResult.status === "fail" || warnEscalated;
							const findings = postExecResult.checks
								.filter((check) => !check.passed)
								.map(
									(check) =>
										`[${check.category}] ${check.target}: ${check.message}`,
								)
								.join("\n");
							const gateRunner = new UokGateRunner();
							gateRunner.register({
								id: "post-execution-checks",
								// type must be one of ADR-0075's 5 (security|policy|verification|
								// learning|chaos). This gate verifies post-execution artifacts
								// exist + pass checks → verification. The "artifact" failureClass
								// below is still correct as the failure CATEGORY (a different field).
								type: "verification",
								execute: async () => ({
									outcome: blockingFailure ? "fail" : "pass",
									failureClass:
										postExecResult.status === "fail"
											? "artifact"
											: warnEscalated
												? "policy"
												: "none",
									rationale: blockingFailure
										? `post-execution checks ${postExecResult.status}${warnEscalated ? " (strict)" : ""}`
										: "post-execution checks passed",
									findings,
								}),
							});
							await gateRunner.run("post-execution-checks", {
								basePath: s.basePath,
								traceId: `verification:${s.currentUnit.id}`,
								turnId: s.currentUnit.id,
								milestoneId: mid,
								sliceId: sid,
								taskId: tid,
								unitType: s.currentUnit.type,
								unitId: s.currentUnit.id,
							});
						}
						// Check for blocking failures
						if (postExecResult.status === "fail") {
							postExecBlockingFailure = true;
							const blockingCount = postExecResult.checks.filter(
								(c) => !c.passed && c.blocking,
							).length;
							ctx.ui.notify(
								`Post-execution checks failed: ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} found`,
								"error",
							);
						} else if (postExecResult.status === "warn") {
							ctx.ui.notify(
								`Post-execution checks passed with warnings`,
								"warning",
							);
							// Strict mode: treat warnings as blocking
							if (prefs?.enhanced_verification_strict === true) {
								postExecBlockingFailure = true;
							}
						}
					}
				} catch (postExecErr) {
					// Post-execution check errors are non-fatal — log and continue
					logWarning("engine", `sf-post-exec: error — ${postExecErr.message}`);
				}
			}
		}
		// Re-write verification evidence JSON with post-execution checks
		if (postExecChecks && postExecChecks.length > 0 && mid && sid && tid) {
			try {
				const sDir = resolveSlicePath(s.basePath, mid, sid);
				if (sDir) {
					const tasksDir = join(sDir, "tasks");
					// Add postExecutionChecks to the result for the JSON write
					const resultWithPostExec = {
						...result,
						// Mark as failed if there was a blocking post-exec failure
						passed: result.passed && !postExecBlockingFailure,
					};
					// Manually write with postExecutionChecks field
					writeVerificationJSONWithPostExec(
						resultWithPostExec,
						tasksDir,
						tid,
						s.currentUnit.id,
						postExecChecks,
						postExecBlockingFailure ? attempt + 1 : undefined,
						postExecBlockingFailure ? maxRetries : undefined,
						tokenCount,
						memoryPressureMB,
						gateOutcomes,
						recoveryStatus,
					);
				}
			} catch (evidenceErr) {
				logWarning(
					"engine",
					`verification-evidence: post-exec write error — ${evidenceErr.message}`,
				);
			}
		}
		// Update result.passed based on post-execution checks
		if (postExecBlockingFailure) {
			result.passed = false;
		}
		// ── Auto-defer confidence check ──
		// Low-risk findings (style, format, deprecation-only) should not block flow.
		const deferThreshold =
			typeof prefs?.verification_auto_defer_threshold === "number"
				? prefs.verification_auto_defer_threshold
				: 0.75;
		const deferDecision = decideAutoDefer(result, deferThreshold);
		if (deferDecision.defer) {
			// Do not consume a retry attempt — the finding is intentionally deferred.
			s.verificationRetryCount.delete(s.currentUnit.id);
			s.pendingVerificationRetry = null;
			ctx.ui.notify(
				`Verification deferred: low-risk findings (${deferDecision.reasons.join("; ")}) confidence=${deferDecision.confidence.toFixed(2)} >= threshold=${deferThreshold}`,
				"info",
			);
			process.stderr.write(
				`verification-gate: deferred ${deferDecision.reasons.length} low-risk check(s) — confidence=${deferDecision.confidence.toFixed(2)}\n`,
			);
			// Write evidence JSON with deferred flag
			if (mid && sid && tid) {
				try {
					const sDir = resolveSlicePath(s.basePath, mid, sid);
					if (sDir) {
						const tasksDir = join(sDir, "tasks");
						const deferredResult = {
							...result,
							passed: false,
							deferred: true,
							deferredReasons: deferDecision.reasons,
							deferConfidence: deferDecision.confidence,
						};
						writeVerificationJSON(
							deferredResult,
							tasksDir,
							tid,
							s.currentUnit.id,
							undefined,
							undefined,
							tokenCount,
							memoryPressureMB,
							gateOutcomes,
							recoveryStatus,
						);
					}
				} catch (evidenceErr) {
					logWarning(
						"engine",
						`verification-evidence write error (defer): ${evidenceErr.message}`,
					);
				}
			}
			return "continue";
		}
		// ── Auto-fix retry logic ──
		if (result.passed) {
			s.verificationRetryCount.delete(s.currentUnit.id);
			s.pendingVerificationRetry = null;
			return "continue";
		} else if (postExecBlockingFailure) {
			// Post-execution failures are cross-task consistency issues — retrying the same task won't fix them.
			// Skip retry and pause immediately for human review.
			s.verificationRetryCount.delete(s.currentUnit.id);
			s.pendingVerificationRetry = null;
			ctx.ui.notify(
				`Post-execution checks failed — cross-task consistency issue detected, pausing for human review`,
				"error",
			);
			await pauseAuto(ctx, pi);
			return "pause";
		} else if (autoFixEnabled && attempt + 1 <= maxRetries) {
			const nextAttempt = attempt + 1;
			s.verificationRetryCount.set(s.currentUnit.id, nextAttempt);
			s.pendingVerificationRetry = {
				unitId: s.currentUnit.id,
				failureContext: formatFailureContext(result),
				attempt: nextAttempt,
			};
			const failedCmds = result.checks
				.filter((c) => c.exitCode !== 0)
				.map((c) => c.command);
			const cmdSummary =
				failedCmds.length <= 3
					? failedCmds.join(", ")
					: `${failedCmds.slice(0, 3).join(", ")}... and ${failedCmds.length - 3} more`;
			ctx.ui.notify(
				`Verification failed (${cmdSummary}) — auto-fix attempt ${nextAttempt}/${maxRetries}`,
				"warning",
			);
			// Return "retry" — the autoLoop while loop will re-iterate with the retry context
			return "retry";
		} else {
			// Gate failed, retries exhausted
			s.verificationRetryCount.delete(s.currentUnit.id);
			s.pendingVerificationRetry = null;
			const exhaustedFails = result.checks
				.filter((c) => c.exitCode !== 0)
				.map((c) => c.command);
			const exhaustedSummary =
				exhaustedFails.length <= 3
					? exhaustedFails.join(", ")
					: `${exhaustedFails.slice(0, 3).join(", ")}... and ${exhaustedFails.length - 3} more`;
			ctx.ui.notify(
				`Verification gate FAILED after ${attempt} ${attempt === 1 ? "retry" : "retries"} (${exhaustedSummary}) — pausing for human review`,
				"error",
			);
			await pauseAuto(ctx, pi);
			return "pause";
		}
	} catch (err) {
		// Ancillary post-gate errors are non-fatal — log warning and continue
		logWarning("engine", `verification-gate error: ${err.message}`);
		return "continue";
	}
}
/**
 * Write verification evidence JSON with post-execution checks included.
 * This is a variant of writeVerificationJSON that adds the postExecutionChecks field.
 */
function writeVerificationJSONWithPostExec(
	result,
	tasksDir,
	taskId,
	unitId,
	postExecutionChecks,
	retryAttempt,
	maxRetries,
	tokenCount,
	memoryPressureMB,
	gateOutcomes,
	recoveryStatus,
) {
	mkdirSync(tasksDir, { recursive: true });
	const evidence = {
		schemaVersion: 1,
		taskId,
		unitId: unitId ?? taskId,
		timestamp: result.timestamp,
		passed: result.passed,
		discoverySource: result.discoverySource,
		checks: result.checks.map((check) => ({
			command: check.command,
			exitCode: check.exitCode,
			durationMs: check.durationMs,
			verdict: check.exitCode === 0 ? "pass" : "fail",
		})),
		...(retryAttempt !== undefined ? { retryAttempt } : {}),
		...(maxRetries !== undefined ? { maxRetries } : {}),
		...(tokenCount !== undefined ? { tokenCount } : {}),
		...(memoryPressureMB !== undefined ? { memoryPressureMB } : {}),
		...(gateOutcomes !== undefined ? { gateOutcomes } : {}),
		...(recoveryStatus !== undefined ? { recoveryStatus } : {}),
		postExecutionChecks,
		...(result.deferred === true ? { deferred: true } : {}),
		...(result.deferredReasons
			? { deferredReasons: result.deferredReasons }
			: {}),
		...(result.deferConfidence !== undefined
			? { deferConfidence: result.deferConfidence }
			: {}),
	};
	if (result.runtimeErrors && result.runtimeErrors.length > 0) {
		evidence.runtimeErrors = result.runtimeErrors.map((e) => ({
			source: e.source,
			severity: e.severity,
			message: e.message,
			blocking: e.blocking,
		}));
	}
	if (result.auditWarnings && result.auditWarnings.length > 0) {
		evidence.auditWarnings = result.auditWarnings.map((w) => ({
			name: w.name,
			severity: w.severity,
			title: w.title,
			url: w.url,
			fixAvailable: w.fixAvailable,
		}));
	}
	const filePath = join(tasksDir, `${taskId}-VERIFY.json`);
	writeFileSync(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
}
