fix: improve merge verification and dashboard behavior

This commit is contained in:
gsxdsm
2026-04-10 18:27:44 -07:00
parent c56e44bff8
commit f7322ab541
20 changed files with 411 additions and 43 deletions

View File

@@ -2152,6 +2152,57 @@ describe("aiMergeTask — deterministic merge verification", () => {
);
});
it("does not fail verification when verbose test output exceeds buffer after exit 0", async () => {
mockedExecSync.mockImplementation((cmd: any) => {
const cmdStr = String(cmd);
if (cmdStr.includes("rev-parse --verify")) return Buffer.from("abc123");
if (cmdStr.includes("git log")) return "- feat: something" as any;
if (cmdStr.includes("merge-base")) return Buffer.from("abc123");
if (cmdStr.includes("git diff") && cmdStr.includes("--stat")) return "1 file changed" as any;
if (cmdStr.includes("merge --squash")) return Buffer.from("");
if (cmdStr.includes("vitest run")) {
const error = new Error("stdout maxBuffer length exceeded") as any;
error.code = "ENOBUFS";
error.status = 0;
error.stdout = "tests passed but output was verbose";
error.stderr = "";
throw error;
}
if (cmdStr.includes("diff --cached --quiet")) return "1" as any;
if (cmdStr.includes("diff --cached") && !cmdStr.includes("--quiet")) return "" as any;
if (cmdStr.includes("branch -d") || cmdStr.includes("branch -D")) return Buffer.from("");
if (cmdStr.includes("worktree remove")) return Buffer.from("");
if (cmdStr === "git rev-parse HEAD" || cmdStr.startsWith("git rev-parse HEAD ")) return "mergedcommit123";
if (cmdStr.includes("show --shortstat")) return "1 file changed, 1 insertion(+)" as any;
return Buffer.from("");
});
mockedCreateHaiAgent.mockImplementation(async () => ({
session: {
prompt: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn(),
},
}) as any);
const store = createMockStore(
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
);
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
...DEFAULT_SETTINGS,
testCommand: "vitest run",
});
const result = await aiMergeTask(store, "/tmp/root", "FN-050");
expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.logEntry).toHaveBeenCalledWith(
"FN-050",
"[verification] test command succeeded (exit 0, output exceeded buffer)",
);
});
it("fails merge when buildCommand fails and does not move task to done", async () => {
// Setup exec mock that will be updated after agent commits
mockedExecSync.mockImplementation((cmd: any) => {

View File

@@ -63,6 +63,14 @@ const DEPENDENCY_SYNC_TRIGGER_PATTERNS = [
"packages/*/package.json",
];
const VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024;
const VERIFICATION_LOG_MAX_CHARS = 20_000;
function truncateVerificationOutput(output: string): string {
if (output.length <= VERIFICATION_LOG_MAX_CHARS) return output;
return `... output truncated to last ${VERIFICATION_LOG_MAX_CHARS} characters ...\n${output.slice(-VERIFICATION_LOG_MAX_CHARS)}`;
}
/** Check if a path matches a glob pattern (simple glob support: * and **) */
function matchGlob(path: string, pattern: string): boolean {
// Handle ** which matches across directory boundaries (must do before single *)
@@ -306,6 +314,7 @@ async function runVerificationCommand(
const output = execSync(command, {
cwd: rootDir,
encoding: "utf-8",
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
timeout: 300_000, // 5 minute timeout for verification commands
stdio: ["pipe", "pipe", "pipe"],
});
@@ -318,10 +327,22 @@ async function runVerificationCommand(
result.stdout = error.stdout?.toString() || "";
result.stderr = error.stderr?.toString() || "";
result.exitCode = error.status ?? null;
result.success = false;
const maxBufferExceeded = error.code === "ENOBUFS"
|| error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
|| error.message?.includes("maxBuffer");
result.success = maxBufferExceeded && result.exitCode === 0;
if (result.success) {
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer)`);
await store.logEntry(
taskId,
`[verification] ${type} command succeeded (exit 0, output exceeded buffer)`,
);
return result;
}
// Build a useful error summary
const summary = result.stderr || result.stdout || error.message || "Unknown error";
const summary = truncateVerificationOutput(result.stderr || result.stdout || error.message || "Unknown error");
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}): ${summary.trim()}`);
await store.logEntry(
taskId,