fix(FN-5345): address third-pass review findings

Follow-up to c64884c24 addressing three review findings, including one
real interaction bug caught by a new test.

MEDIUM

- Re-indented and rewrote 'if (directReuseEligible) try { ... } catch'
  as 'if (directReuseEligible) { try { ... } catch { ... } }' with the
  whole body at one consistent indent level. No behavior change \u2014 fixes
  the mismatched indentation from c64884c24 where the body sat one level
  deeper than its containing block.

LOW

- Two new backstop tests in merge-reuse-task-worktree.test.ts:
  * 'preserves worktrees with uncommitted tracked changes' \u2014 asserts the
    fast-path leaves a tracked-dirty worktree alone (result.worktreeRemoved
    is false, dir still exists). Without this, a future refactor could
    silently re-enable destructive cleanup.
  * 'cleans up worktrees with only untracked noise' \u2014 asserts untracked
    junk (.DS_Store, editor swap files) does NOT block cleanup. Also caught
    a real interaction bug: 'git worktree remove' without --force refuses
    on untracked files, so the LOW finding's intent (drop noise, preserve
    tracked dirt) needs --force on the removal call. Restored --force with
    a comment explaining why it's safe (the tracked-only dirty check above
    already refused if there was real work to preserve).

- Switched 'git status' check from '--untracked-files=normal' to
  '--untracked-files=no'. Tracked modifications and staged changes still
  block cleanup; untracked junk is correctly ignored. Dirty-skip warn log
  now includes the first 5 dirty paths for operator diagnosability.

Tests
  - Full @fusion/engine suite: 448 files / 5883 tests / 9 skipped, all green
  - pnpm lint green, pnpm build green
This commit is contained in:
Fusion (runfusion.ai)
2026-05-20 18:21:14 -07:00
parent c64884c24a
commit e814ba9508
2 changed files with 145 additions and 14 deletions

View File

@@ -1,4 +1,5 @@
import { mkdir } from "node:fs/promises";
import { mkdir, writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
@@ -653,4 +654,120 @@ describe("FN-5279 reliability interactions: merge reuse task worktree", () => {
},
30_000,
);
// FN-5345/FN-5377 cleanup-safety backstop: the fast-path's worktree removal
// MUST preserve a worktree that has uncommitted tracked changes. We run
// `git status --porcelain --untracked-files=no` and skip the removal when
// tracked dirt is present. Untracked junk does not count (operator noise
// like .DS_Store should not block cleanup).
it.skipIf(!hasGit)(
"FN-5345: empty-own-diff fast-path preserves worktrees with uncommitted tracked changes",
async () => {
const fixture = await makeReliabilityFixture({
taskId: "FN-5279-RI-DIRTY-PRESERVE",
settings: {
baseBranch: "master",
mergeIntegrationWorktree: "reuse-task-worktree",
} as any,
});
try {
const { rootDir, store, task } = fixture;
const actualTask = await store.getTask(task.id);
const branch = `fusion/${actualTask!.id.toLowerCase()}`;
const worktreeRoot = `${rootDir}-worktrees`;
const worktreePath = join(worktreeRoot, actualTask!.id.toLowerCase());
git(rootDir, "git branch -m main master");
const completedSteps = (actualTask?.steps ?? []).map((step) => ({ ...step, status: "done" as const }));
await store.updateTask(task.id, {
baseBranch: "master",
branch,
steps: completedSteps,
currentStep: completedSteps.length,
} as any);
await fixture.createBranch(branch);
// Empty-own-diff handoff commit on the branch.
git(rootDir, `git commit --allow-empty -m 'test(${actualTask!.id}): verification-only handoff'`);
await fixture.checkout("master");
// Create the worktree at branch tip, then introduce a tracked
// modification (not committed) to simulate agent scratch.
await mkdir(worktreeRoot, { recursive: true });
git(rootDir, `git worktree add ${JSON.stringify(worktreePath)} ${JSON.stringify(branch)}`);
// README.md is created by the fixture as a tracked file. Modify it to
// produce tracked-dirty status.
await writeFile(join(worktreePath, "README.md"), "agent scratch: uncommitted edits\n");
await store.updateTask(task.id, { worktree: worktreePath, branch } as any);
store.enqueueMergeQueue(task.id);
const result = await aiMergeTask(store, rootDir, task.id);
expect(result.merged).toBe(true);
expect(result.noOp).toBe(true);
// Critical: the worktree must NOT be removed because it has tracked
// uncommitted changes. result.worktreeRemoved reflects that.
expect(result.worktreeRemoved).toBe(false);
expect(existsSync(worktreePath)).toBe(true);
expect(existsSync(join(worktreePath, "README.md"))).toBe(true);
} finally {
await fixture.cleanup();
}
},
30_000,
);
// FN-5345/FN-5377 cleanup-noise backstop: untracked junk (e.g. .DS_Store,
// editor swap files, build artifacts that are not gitignored at the task
// worktree level) must NOT block fast-path cleanup. Only tracked dirt does.
it.skipIf(!hasGit)(
"FN-5345: empty-own-diff fast-path cleans up worktrees with only untracked noise",
async () => {
const fixture = await makeReliabilityFixture({
taskId: "FN-5279-RI-UNTRACKED-OK",
settings: {
baseBranch: "master",
mergeIntegrationWorktree: "reuse-task-worktree",
} as any,
});
try {
const { rootDir, store, task } = fixture;
const actualTask = await store.getTask(task.id);
const branch = `fusion/${actualTask!.id.toLowerCase()}`;
const worktreeRoot = `${rootDir}-worktrees`;
const worktreePath = join(worktreeRoot, actualTask!.id.toLowerCase());
git(rootDir, "git branch -m main master");
const completedSteps = (actualTask?.steps ?? []).map((step) => ({ ...step, status: "done" as const }));
await store.updateTask(task.id, {
baseBranch: "master",
branch,
steps: completedSteps,
currentStep: completedSteps.length,
} as any);
await fixture.createBranch(branch);
git(rootDir, `git commit --allow-empty -m 'test(${actualTask!.id}): verification-only handoff'`);
await fixture.checkout("master");
await mkdir(worktreeRoot, { recursive: true });
git(rootDir, `git worktree add ${JSON.stringify(worktreePath)} ${JSON.stringify(branch)}`);
// Sprinkle untracked-only noise into the worktree.
await writeFile(join(worktreePath, ".DS_Store"), "binary junk\n");
await writeFile(join(worktreePath, "editor.swp"), "swap file\n");
await store.updateTask(task.id, { worktree: worktreePath, branch } as any);
store.enqueueMergeQueue(task.id);
const result = await aiMergeTask(store, rootDir, task.id);
expect(result.merged).toBe(true);
expect(result.noOp).toBe(true);
// Untracked-only is treated as clean — cleanup proceeds.
expect(result.worktreeRemoved).toBe(true);
expect(existsSync(worktreePath)).toBe(false);
} finally {
await fixture.cleanup();
}
},
30_000,
);
});