Files
fusion/packages/engine/src/base-commit-capture.ts
gsxdsm 68e52e3c35 fix(engine): capture baseCommitSha against local main, not origin/main
In-review tasks showed other tasks' files in their "files changed"
list. Task branches fork from local main, but the base capture
measured merge-base(HEAD, origin/main) — when local main carried
merged-but-unpushed task commits, the recorded base rewound past
them, and after the post-merge rebase-and-push rewrote those SHAs,
baseCommitSha..HEAD permanently swept the predecessors' files into
the new task's diff.

Extract the capture into base-commit-capture.ts, measure local main
first (origin/main fallback) to match the contamination-base sites,
and add a real-git regression suite covering local-ahead-of-origin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:24:03 -07:00

56 lines
2.0 KiB
TypeScript

import { exec } from "node:child_process";
import { promisify } from "node:util";
const execAsync = promisify(exec);
/**
* Resolve the fork-point base SHA for a freshly acquired task worktree.
*
* Called immediately after worktree acquisition, when the task branch was
* just created/force-reset from the local integration branch
* (`prepareForTask` forks from local `main` via `resolveIntegrationBranch`).
*
* The merge-base MUST be measured against LOCAL main first (origin/main only
* as a fallback), matching the contamination-base sites in
* `worktree-acquisition.ts` and `auto-recovery-handlers/branch-worktree.ts`.
* The merger lands tasks on local main before pushing, so at fork time local
* main can be ahead of origin/main by merged-but-unpushed commits. Measuring
* against origin/main rewinds the base past those commits; once the
* post-merge rebase-and-push rewrites their SHAs, `baseCommitSha..HEAD`
* permanently sweeps the predecessors' files into this task's diff (FN-5937:
* in-review tasks showing 31 "files changed" instead of 12).
*
* Returns `undefined` only when every git invocation fails (caller treats a
* missing base as non-fatal).
*/
export async function resolveCapturedBaseCommitSha(
worktreePath: string,
logger?: { warn: (msg: string) => void },
): Promise<string | undefined> {
let baseCommitSha: string | undefined;
try {
const { stdout } = await execAsync(
"git merge-base HEAD main 2>/dev/null || git merge-base HEAD origin/main",
{ cwd: worktreePath, encoding: "utf-8" },
);
baseCommitSha = stdout.trim() || undefined;
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
logger?.warn(`merge-base failed, falling back to HEAD: ${errorMessage}`);
}
if (!baseCommitSha) {
try {
const { stdout } = await execAsync("git rev-parse HEAD", {
cwd: worktreePath,
encoding: "utf-8",
});
baseCommitSha = stdout.trim() || undefined;
} catch {
return undefined;
}
}
return baseCommitSha;
}