feat(FN-4132): sync task ownership on agent reassignment
Adds a task reassignment sync helper to the core store, wires it into the system, and ships regression tests; includes a patch changeset for `@runfusion/fusion`. Fusion-Task-Id: FN-4132 Fusion-Task-Lineage: 80ec3c62-7c09-49dd-9e49-74b203e48bf4
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { execSync } from "node:child_process";
|
||||
import { join, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
@@ -151,17 +151,42 @@ function assertIsolatedWorkspace(dir: string): void {
|
||||
expect(resolve(dir).startsWith(resolve(repoRoot))).toBe(false);
|
||||
}
|
||||
|
||||
const createdDirs = new Set<string>();
|
||||
|
||||
function cleanupTempDir(dir?: string): void {
|
||||
if (!dir) return;
|
||||
createdDirs.delete(dir);
|
||||
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
||||
try {
|
||||
if (!existsSync(dir)) return;
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
if (attempt === 3) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
for (const dir of Array.from(createdDirs)) {
|
||||
cleanupTempDir(dir);
|
||||
}
|
||||
});
|
||||
|
||||
describe("merger overlap guard", () => {
|
||||
let dir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), "fusion-test-overlap-guard-"));
|
||||
createdDirs.add(dir);
|
||||
assertIsolatedWorkspace(dir);
|
||||
initRepo(dir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
cleanupTempDir(dir);
|
||||
});
|
||||
|
||||
it("detects overlap when branch and recent main commits touch the same file", async () => {
|
||||
@@ -302,12 +327,13 @@ describe("aiMergeTask overlap-aware fallback integration", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), "fusion-test-overlap-merge-"));
|
||||
createdDirs.add(dir);
|
||||
assertIsolatedWorkspace(dir);
|
||||
initRepo(dir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
cleanupTempDir(dir);
|
||||
});
|
||||
|
||||
it("defaults to restoring the branch version for overlapping files under smart-prefer-main", async () => {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* 6. Untracked file pre-existing in working tree (user WIP) — NOT staged
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs";
|
||||
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from "vitest";
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { execSync } from "node:child_process";
|
||||
@@ -85,6 +85,30 @@ const STUB_SETTINGS = {
|
||||
commitAuthorEnabled: false, // skip --author flag to avoid user config issues
|
||||
};
|
||||
|
||||
const createdDirs = new Set<string>();
|
||||
|
||||
function cleanupTempDir(dir?: string): void {
|
||||
if (!dir) return;
|
||||
createdDirs.delete(dir);
|
||||
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
||||
try {
|
||||
if (!existsSync(dir)) return;
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
if (attempt === 3) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterAll(() => {
|
||||
for (const dir of Array.from(createdDirs)) {
|
||||
cleanupTempDir(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -94,12 +118,13 @@ describe("snapshotDirtyFiles", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), "fusion-test-merger-snapshot-"));
|
||||
createdDirs.add(dir);
|
||||
assertIsolatedWorkspace(dir);
|
||||
initRepo(dir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
cleanupTempDir(dir);
|
||||
});
|
||||
|
||||
it("returns empty set when working tree is clean", async () => {
|
||||
@@ -159,14 +184,18 @@ describe("commitOrAmendMergeWithFixes — staging allowlist", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), "fusion-test-merger-allowlist-"));
|
||||
createdDirs.add(dir);
|
||||
assertIsolatedWorkspace(dir);
|
||||
initRepo(dir);
|
||||
warnSpy = vi.spyOn(mergerLog, "warn");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
warnSpy.mockRestore();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
try {
|
||||
warnSpy.mockRestore();
|
||||
} finally {
|
||||
cleanupTempDir(dir);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Scenario 1: Unrelated dirty file is excluded ───────────────────────
|
||||
@@ -543,14 +572,18 @@ describe("commitOrAmendMergeWithFixes — embedded-space paths round-trip", () =
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), "fusion-test-merger-allowlist-spaces-"));
|
||||
createdDirs.add(dir);
|
||||
assertIsolatedWorkspace(dir);
|
||||
initRepo(dir);
|
||||
warnSpy = vi.spyOn(mergerLog, "warn");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
warnSpy.mockRestore();
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
try {
|
||||
warnSpy.mockRestore();
|
||||
} finally {
|
||||
cleanupTempDir(dir);
|
||||
}
|
||||
});
|
||||
|
||||
it("stages and commits a tracked file edited by the fix agent whose path contains spaces", async () => {
|
||||
|
||||
Reference in New Issue
Block a user