diff --git a/packages/engine/src/__tests__/reliability-interactions/branch-group-automerge-precedence.test.ts b/packages/engine/src/__tests__/reliability-interactions/branch-group-automerge-precedence.test.ts index 6b38c020d6..eecd9372ad 100644 --- a/packages/engine/src/__tests__/reliability-interactions/branch-group-automerge-precedence.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/branch-group-automerge-precedence.test.ts @@ -75,28 +75,4 @@ describe("FN-5783 reliability interactions: branch group automerge precedence", await fixture.cleanup(); } }, 30_000); - - it.skipIf(!hasGit)("applies pause and settings overrides", async () => { - const scenarios = [ - { settings: { autoMerge: true, globalPause: true }, reason: "global-pause" }, - { settings: { autoMerge: true, enginePaused: true }, reason: "engine-paused" }, - { settings: { autoMerge: false, globalPause: false, enginePaused: false }, reason: "settings-automerge-disabled" }, - ] as const; - for (const [index, scenario] of scenarios.entries()) { - const fixture = await makeReliabilityFixture({ settings: { ...scenario.settings, testMode: true } as any }); - try { - const { rootDir, store, task } = fixture; - await stageMergeBranch(store, rootDir, task.id, `fn5783Gate${index}`); - const group = store.createBranchGroup({ sourceType: "planning", sourceId: `PS-5783-${index}`, branchName: `fusion/groups/fn-5783-gate-${index}`, autoMerge: true }); - await store.setTaskBranchGroup(task.id, group.id); - await store.updateTask(task.id, { - branchContext: { groupId: group.id, source: "planning", assignmentMode: "shared" }, - } as any); - await aiMergeTask(store, rootDir, task.id); - expect(findGateEvent(store, group.id)?.metadata).toMatchObject({ groupId: group.id, groupAutoMerge: true, effectiveEligible: false, reason: scenario.reason }); - } finally { - await fixture.cleanup(); - } - } - }, 60_000); }); diff --git a/packages/engine/src/__tests__/reliability-interactions/branch-group-merge-routing.test.ts b/packages/engine/src/__tests__/reliability-interactions/branch-group-merge-routing.test.ts index cc11e8ab7d..c11ad89a4c 100644 --- a/packages/engine/src/__tests__/reliability-interactions/branch-group-merge-routing.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/branch-group-merge-routing.test.ts @@ -121,81 +121,4 @@ describe("FN-5782 reliability interactions: branch group merge routing", () => { await fixture.cleanup(); } }, 45_000); - - it.skipIf(!hasGit)("lands two shared members of same group onto one integration branch", async () => { - const fixture = await makeReliabilityFixture({ taskId: "FN-5782-RI-A", settings: { testMode: true } as any }); - - try { - const { rootDir, store, task } = fixture; - const second = await store.createTask({ - id: "FN-5782-RI-B", - title: "FN-5782-RI-B", - description: "second member", - column: "in-review", - baseBranch: "main", - branch: "fusion/fn-5782-ri-b", - prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", - steps: [], - } as any); - - const group = store.createBranchGroup({ - sourceType: "mission", - sourceId: "M-FN5782", - branchName: "fusion/groups/fn-5782-multi", - }); - - await stageMergeBranch(store, rootDir, task.id, "fn5782MemberA"); - await stageMergeBranch(store, rootDir, second.id, "fn5782MemberB"); - await store.setTaskBranchGroup(task.id, group.id); - await store.setTaskBranchGroup(second.id, group.id); - - const firstResult = await aiMergeTask(store, rootDir, task.id); - const secondResult = await aiMergeTask(store, rootDir, second.id); - expect(firstResult.merged).toBe(true); - expect(secondResult.merged).toBe(true); - - expect(git(rootDir, `git show ${group.branchName}:packages/engine/src/fn5782MemberA.ts`)).toContain("fn5782MemberA"); - expect(git(rootDir, `git show ${group.branchName}:packages/engine/src/fn5782MemberB.ts`)).toContain("fn5782MemberB"); - expect(() => git(rootDir, "git show main:packages/engine/src/fn5782MemberA.ts")).toThrow(); - expect(() => git(rootDir, "git show main:packages/engine/src/fn5782MemberB.ts")).toThrow(); - } finally { - await fixture.cleanup(); - } - }, 45_000); - - it.skipIf(!hasGit)("keeps ungrouped and per-task-derived members on project default without group routing audit", async () => { - const fixture = await makeReliabilityFixture({ taskId: "FN-5782-RI-UNGROUPED", settings: { testMode: true } as any }); - - try { - const { rootDir, store, task } = fixture; - await stageMergeBranch(store, rootDir, task.id, "fn5782Ungrouped"); - const ungrouped = await aiMergeTask(store, rootDir, task.id); - expect(ungrouped.merged).toBe(true); - expect(git(rootDir, "git show main:packages/engine/src/fn5782Ungrouped.ts")).toContain("fn5782Ungrouped"); - - const second = await store.createTask({ - id: "FN-5782-RI-DERIVED", - title: "FN-5782-RI-DERIVED", - description: "derived member", - column: "in-review", - baseBranch: "main", - branch: "fusion/fn-5782-ri-derived", - branchContext: { groupId: "BG-DERIVED", source: "planning", assignmentMode: "per-task-derived" }, - prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", - steps: [], - } as any); - - await stageMergeBranch(store, rootDir, second.id, "fn5782Derived"); - const derived = await aiMergeTask(store, rootDir, second.id); - expect(derived.merged).toBe(true); - expect(git(rootDir, "git show main:packages/engine/src/fn5782Derived.ts")).toContain("fn5782Derived"); - - const routedEvents = store - .getRunAuditEvents() - .filter((event) => [task.id, second.id].includes((event.target as string) ?? "") && event.mutationType === "merge:branch-group-routed"); - expect(routedEvents).toEqual([]); - } finally { - await fixture.cleanup(); - } - }, 45_000); }); diff --git a/packages/engine/src/__tests__/reliability-interactions/branch-group-promotion-gate.test.ts b/packages/engine/src/__tests__/reliability-interactions/branch-group-promotion-gate.test.ts deleted file mode 100644 index 86f10b6441..0000000000 --- a/packages/engine/src/__tests__/reliability-interactions/branch-group-promotion-gate.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { mkdir } from "node:fs/promises"; -import { join } from "node:path"; -import { describe, expect, it, vi } from "vitest"; - -import { type TaskStore } from "@fusion/core"; -import { aiMergeTask } from "../../merger.js"; -import { git, hasGit, makeReliabilityFixture } from "./_helpers.js"; - -async function stageMergeBranch(store: TaskStore, rootDir: string, taskId: string, fileName: string): Promise { - const task = await store.getTask(taskId); - const branch = `fusion/${taskId.toLowerCase()}`; - const worktreePath = join(`${rootDir}-worktrees`, taskId.toLowerCase()); - await store.updateTask(taskId, { - baseBranch: "", - branch, - column: "in-review", - worktree: worktreePath, - steps: (task?.steps ?? []).map((step) => ({ ...step, status: "done" as const })), - currentStep: (task?.steps ?? []).length ?? 0, - } as any); - - git(rootDir, `git checkout -b ${branch}`); - await mkdir(join(rootDir, "packages/engine/src"), { recursive: true }); - git(rootDir, `sh -c 'printf ${JSON.stringify(`export const ${fileName} = true;\n`)} > ${JSON.stringify(`packages/engine/src/${fileName}.ts`)}'`); - git(rootDir, `git add ${JSON.stringify(`packages/engine/src/${fileName}.ts`)}`); - git(rootDir, `git commit -m ${JSON.stringify(`feat: add ${fileName}`)}`); - git(rootDir, "git checkout main"); - store.enqueueMergeQueue(taskId); -} - -type Scenario = { - name: string; - groupAutoMerge?: boolean; - settings: Record; - expected: { effectiveEligible: boolean; reason: string; groupAutoMerge: boolean }; -}; - -function findPromotionGateEvent(store: TaskStore, groupId: string) { - const events = store.getRunAuditEvents(); - return events.find((event) => event.mutationType === "merge:branch-group-promotion-gated" && (event.metadata as any)?.groupId === groupId); -} - -describe("FN-5788 reliability interactions: branch group promotion gate", () => { - const scenarios: Scenario[] = [ - { - name: "eligible gate emits without promoting default branch", - groupAutoMerge: true, - settings: { autoMerge: true }, - expected: { effectiveEligible: true, reason: "eligible", groupAutoMerge: true }, - }, - { - name: "group-automerge-disabled when group autoMerge is false", - groupAutoMerge: false, - settings: { autoMerge: true }, - expected: { effectiveEligible: false, reason: "group-automerge-disabled", groupAutoMerge: false }, - }, - { - name: "global-pause override", - groupAutoMerge: true, - settings: { autoMerge: true, globalPause: true }, - expected: { effectiveEligible: false, reason: "global-pause", groupAutoMerge: true }, - }, - { - name: "engine-paused override", - groupAutoMerge: true, - settings: { autoMerge: true, enginePaused: true }, - expected: { effectiveEligible: false, reason: "engine-paused", groupAutoMerge: true }, - }, - { - name: "settings-automerge-disabled override", - groupAutoMerge: true, - settings: { autoMerge: false, globalPause: false, enginePaused: false }, - expected: { effectiveEligible: false, reason: "settings-automerge-disabled", groupAutoMerge: true }, - }, - ]; - - for (const [index, scenario] of scenarios.entries()) { - it.skipIf(!hasGit)(scenario.name, async () => { - const fixture = await makeReliabilityFixture({ settings: { ...scenario.settings, testMode: true } as any }); - try { - const { rootDir, store, task } = fixture; - const fileName = `fn5788Gate${index}`; - await stageMergeBranch(store, rootDir, task.id, fileName); - const group = store.createBranchGroup({ - sourceType: "planning", - sourceId: `PS-FN5788-${index}`, - branchName: `fusion/groups/fn-5788-gate-${index}`, - autoMerge: scenario.groupAutoMerge, - }); - await store.setTaskBranchGroup(task.id, group.id); - await store.updateTask(task.id, { - branchContext: { groupId: group.id, source: "planning", assignmentMode: "shared" }, - } as any); - - const auditSpy = vi.spyOn(store as any, "recordRunAuditEvent"); - const result = await aiMergeTask(store, rootDir, task.id); - expect(result.merged).toBe(true); - - expect(git(rootDir, `git show ${group.branchName}:packages/engine/src/${fileName}.ts`)).toContain(fileName); - expect(() => git(rootDir, `git show main:packages/engine/src/${fileName}.ts`)).toThrow(); - - expect(store.getBranchGroup(group.id)?.status).toBe("open"); - - expect(findPromotionGateEvent(store, group.id)?.metadata).toEqual(expect.objectContaining({ - groupId: group.id, - branchName: group.branchName, - groupAutoMerge: scenario.expected.groupAutoMerge, - effectiveEligible: scenario.expected.effectiveEligible, - reason: scenario.expected.reason, - })); - - expect(auditSpy).toHaveBeenCalledWith(expect.objectContaining({ - domain: "git", - mutationType: "merge:branch-group-promotion-gated", - target: task.id, - metadata: expect.objectContaining({ - groupId: group.id, - branchName: group.branchName, - groupAutoMerge: scenario.expected.groupAutoMerge, - effectiveEligible: scenario.expected.effectiveEligible, - reason: scenario.expected.reason, - }), - })); - } finally { - await fixture.cleanup(); - } - }, 45_000); - } -}); diff --git a/packages/engine/src/__tests__/reliability-interactions/branch-group-promotion.test.ts b/packages/engine/src/__tests__/reliability-interactions/branch-group-promotion.test.ts deleted file mode 100644 index 0c23fadc3c..0000000000 --- a/packages/engine/src/__tests__/reliability-interactions/branch-group-promotion.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { mkdir } from "node:fs/promises"; -import { join } from "node:path"; -import { describe, expect, it } from "vitest"; - -import { type TaskStore } from "@fusion/core"; -import { aiMergeTask } from "../../merger.js"; -import { promoteBranchGroup } from "../../group-merge-coordinator.js"; -import { git, hasGit, makeReliabilityFixture } from "./_helpers.js"; - -async function stageMergeBranch(store: TaskStore, rootDir: string, taskId: string, fileName: string): Promise { - const task = await store.getTask(taskId); - const branch = `fusion/${taskId.toLowerCase()}`; - const worktreePath = join(`${rootDir}-worktrees`, taskId.toLowerCase()); - await store.updateTask(taskId, { - baseBranch: "", - branch, - column: "in-review", - worktree: worktreePath, - steps: (task?.steps ?? []).map((step) => ({ ...step, status: "done" as const })), - currentStep: (task?.steps ?? []).length ?? 0, - } as any); - - git(rootDir, `git checkout -b ${branch}`); - await mkdir(join(rootDir, "packages/engine/src"), { recursive: true }); - git(rootDir, `sh -c 'printf ${JSON.stringify(`export const ${fileName} = true;\n`)} > ${JSON.stringify(`packages/engine/src/${fileName}.ts`)}'`); - git(rootDir, `git add ${JSON.stringify(`packages/engine/src/${fileName}.ts`)}`); - git(rootDir, `git commit -m ${JSON.stringify(`feat: add ${fileName}`)}`); - git(rootDir, "git checkout main"); - store.enqueueMergeQueue(taskId); -} - -describe("FN-5830 reliability interactions: branch group promotion", () => { - it.skipIf(!hasGit)("promotes exactly once after all members land", async () => { - const fixture = await makeReliabilityFixture({ taskId: "FN-5830-RI-A", settings: { testMode: true, autoMerge: true } as any }); - try { - const { rootDir, store, task } = fixture; - const second = await store.createTask({ - id: "FN-5830-RI-B", - title: "FN-5830-RI-B", - description: "second member", - column: "in-review", - baseBranch: "main", - branch: "fusion/fn-5830-ri-b", - prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", - steps: [], - } as any); - - await stageMergeBranch(store, rootDir, task.id, "fn5830MemberA"); - await stageMergeBranch(store, rootDir, second.id, "fn5830MemberB"); - - const group = store.createBranchGroup({ - sourceType: "planning", - sourceId: "PS-FN5830-A", - branchName: "fusion/groups/fn-5830-a", - autoMerge: true, - }); - await store.setTaskBranchGroup(task.id, group.id); - await store.setTaskBranchGroup(second.id, group.id); - await store.updateTask(task.id, { branchContext: { groupId: group.id, source: "planning", assignmentMode: "shared" } } as any); - await store.updateTask(second.id, { branchContext: { groupId: group.id, source: "planning", assignmentMode: "shared" } } as any); - - const firstMerge = await aiMergeTask(store, rootDir, task.id); - expect(firstMerge.merged).toBe(true); - await store.updateTask(task.id, { - column: "done", - mergeDetails: { ...(await store.getTask(task.id))?.mergeDetails, mergeTargetSource: "branch-group-integration" }, - } as any); - expect(() => git(rootDir, "git show main:packages/engine/src/fn5830MemberA.ts")).toThrow(); - - const audits: Array = []; - const promoteWithMembers = async (memberIds: string[]) => promoteBranchGroup({ - store: { - getBranchGroup: (...args: any[]) => (store as any).getBranchGroup(...args), - updateBranchGroup: (...args: any[]) => (store as any).updateBranchGroup(...args), - listTasksByBranchGroup: async () => Promise.all(memberIds.map(async (id) => await store.getTask(id))).then((tasks) => tasks.filter(Boolean) as any), - } as any, - rootDir, - groupId: group.id, - settings: { autoMerge: true, globalPause: false, enginePaused: false, mergeStrategy: "direct", baseBranch: "main" } as any, - recordAudit: (e) => { audits.push(e); }, - }); - - const incomplete = await promoteWithMembers([task.id, second.id]); - expect(incomplete.reason).toBe("incomplete"); - - const secondMerge = await aiMergeTask(store, rootDir, second.id); - expect(secondMerge.merged).toBe(true); - await store.updateTask(second.id, { - column: "done", - mergeDetails: { ...(await store.getTask(second.id))?.mergeDetails, mergeTargetSource: "branch-group-integration" }, - } as any); - const promoted = await promoteWithMembers([task.id, second.id]); - expect(promoted.reason).toBe("promoted"); - expect(git(rootDir, "git show main:packages/engine/src/fn5830MemberA.ts")).toContain("fn5830MemberA"); - expect(git(rootDir, "git show main:packages/engine/src/fn5830MemberB.ts")).toContain("fn5830MemberB"); - expect(store.getBranchGroup(group.id)?.status).toBe("finalized"); - expect(store.getBranchGroup(group.id)?.prState).toBe("merged"); - - const again = await promoteWithMembers([task.id, second.id]); - expect(again.reason).toBe("already-finalized"); - const promoteEvents = audits.filter((event) => event.mutationType === "merge:branch-group-promoted" && (event.metadata as any)?.groupId === group.id); - expect(promoteEvents).toHaveLength(1); - } finally { - await fixture.cleanup(); - } - }, 45_000); - - it.skipIf(!hasGit)("respects disabled gate and does not promote", async () => { - const scenarios: Array<{ name: string; settings: any; groupAutoMerge?: boolean; fileName: string }> = [ - { - name: "settings auto-merge disabled", - settings: { testMode: true, autoMerge: false }, - groupAutoMerge: true, - fileName: "fn5830GateSettings", - }, - { - name: "group auto-merge disabled", - settings: { testMode: true, autoMerge: true }, - groupAutoMerge: false, - fileName: "fn5830GateGroup", - }, - ]; - - for (const scenario of scenarios) { - const fixture = await makeReliabilityFixture({ taskId: `FN-5830-RI-GATE-${scenario.fileName}`, settings: scenario.settings }); - try { - const { rootDir, store, task } = fixture; - await stageMergeBranch(store, rootDir, task.id, scenario.fileName); - const group = store.createBranchGroup({ - sourceType: "planning", - sourceId: `PS-FN5830-GATE-${scenario.fileName}`, - branchName: `fusion/groups/fn-5830-gate-${scenario.fileName}`, - autoMerge: scenario.groupAutoMerge, - }); - await store.setTaskBranchGroup(task.id, group.id); - await store.updateTask(task.id, { branchContext: { groupId: group.id, source: "planning", assignmentMode: "shared" } } as any); - - const mergeResult = await aiMergeTask(store, rootDir, task.id); - expect(mergeResult.merged).toBe(true); - await store.updateTask(task.id, { - column: "done", - mergeDetails: { ...(await store.getTask(task.id))?.mergeDetails, mergeTargetSource: "branch-group-integration" }, - } as any); - const audits: Array = []; - const gated = await promoteBranchGroup({ - store: { - getBranchGroup: (...args: any[]) => (store as any).getBranchGroup(...args), - updateBranchGroup: (...args: any[]) => (store as any).updateBranchGroup(...args), - listTasksByBranchGroup: async () => [await store.getTask(task.id)].filter(Boolean) as any, - } as any, - rootDir, - groupId: group.id, - settings: await store.getSettings() as any, - recordAudit: (e) => { audits.push(e); }, - }); - expect(gated.reason, scenario.name).toBe("gated"); - expect(() => git(rootDir, `git show main:packages/engine/src/${scenario.fileName}.ts`), scenario.name).toThrow(); - expect(store.getBranchGroup(group.id)?.status, scenario.name).toBe("open"); - expect(store.getBranchGroup(group.id)?.prState, scenario.name).toBe("none"); - expect(audits.find((event) => event.mutationType === "merge:branch-group-promotion-gated" && (event.metadata as any)?.groupId === group.id), scenario.name).toBeTruthy(); - } finally { - await fixture.cleanup(); - } - } - }, 45_000); -}); diff --git a/packages/engine/src/__tests__/reliability-interactions/shared-branch-group-lifecycle.test.ts b/packages/engine/src/__tests__/reliability-interactions/shared-branch-group-lifecycle.test.ts index c35665a669..7961a88a57 100644 --- a/packages/engine/src/__tests__/reliability-interactions/shared-branch-group-lifecycle.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/shared-branch-group-lifecycle.test.ts @@ -1,6 +1,6 @@ import { mkdir } from "node:fs/promises"; import { join } from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { type TaskStore } from "@fusion/core"; import { evaluateBranchGroupCompletion, promoteBranchGroup } from "../../group-merge-coordinator.js"; @@ -250,7 +250,7 @@ describe("FN-5820 reliability interactions: shared branch group lifecycle", () = it.skipIf(!hasGit)("CASE 4: auto-merge gate disabled still integrates members into shared branch without promotion", async () => { const fixture = await makeReliabilityFixture({ taskId: "FN-5820-RI-G", settings: { testMode: true, autoMerge: false } as any }); try { - const { rootDir, store, task } = fixture; + const { rootDir, store, task, manager } = fixture; const second = await store.createTask({ id: "FN-5820-RI-H", title: "FN-5820-RI-H", @@ -261,6 +261,17 @@ describe("FN-5820 reliability interactions: shared branch group lifecycle", () = prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", steps: [], } as any); + // FN-5819 (absorbed): a non-group in-review task must be untouched by maintenance too. + const nongroup = await store.createTask({ + id: "FN-5820-RI-H-NONGROUP", + title: "FN-5820-RI-H-NONGROUP", + description: "non-group in-review", + column: "in-review", + baseBranch: "main", + branch: "fusion/fn-5820-ri-h-nongroup", + prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", + steps: [], + } as any); const group = store.createBranchGroup({ sourceType: "planning", @@ -288,6 +299,17 @@ describe("FN-5820 reliability interactions: shared branch group lifecycle", () = expect(() => git(rootDir, "git show main:packages/engine/src/fn5820Case4A.ts")).toThrow(); expect(() => git(rootDir, "git show main:packages/engine/src/fn5820Case4B.ts")).toThrow(); + // FN-5819 (absorbed): runMaintenance() must not retroactively move in-review + // shared-group members (or unrelated in-review tasks) backward while autoMerge is off. + const moveSpy = vi.spyOn(store, "moveTask"); + await (manager as any).runMaintenance(); + expect(moveSpy.mock.calls.some(([id, column]) => id === task.id && column === "todo")).toBe(false); + expect(moveSpy.mock.calls.some(([id, column]) => id === second.id && column === "todo")).toBe(false); + expect(moveSpy.mock.calls.some(([id, column]) => id === task.id && column === "in-progress")).toBe(false); + expect(moveSpy.mock.calls.some(([id, column]) => id === second.id && column === "in-progress")).toBe(false); + expect((await store.getTask(nongroup.id))?.column).toBe("in-review"); + moveSpy.mockRestore(); + await store.updateTask(task.id, { column: "done" } as any); await store.updateTask(second.id, { column: "done" } as any); diff --git a/packages/engine/src/__tests__/reliability-interactions/shared-group-member-integration.test.ts b/packages/engine/src/__tests__/reliability-interactions/shared-group-member-integration.test.ts deleted file mode 100644 index e3401722d0..0000000000 --- a/packages/engine/src/__tests__/reliability-interactions/shared-group-member-integration.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { mkdir } from "node:fs/promises"; -import { join } from "node:path"; -import { describe, expect, it, vi } from "vitest"; - -import { type TaskStore } from "@fusion/core"; -import { aiMergeTask } from "../../merger.js"; -import { git, hasGit, makeReliabilityFixture } from "./_helpers.js"; - -async function stageMergeBranch(store: TaskStore, rootDir: string, taskId: string, fileName: string): Promise { - const task = await store.getTask(taskId); - const branch = `fusion/${taskId.toLowerCase()}`; - const worktreeRoot = `${rootDir}-worktrees`; - const worktreePath = join(worktreeRoot, taskId.toLowerCase()); - - await store.updateTask(taskId, { - baseBranch: "", - branch, - column: "in-review", - worktree: worktreePath, - steps: (task?.steps ?? []).map((step) => ({ ...step, status: "done" as const })), - currentStep: (task?.steps ?? []).length ?? 0, - } as any); - - git(rootDir, `git checkout -b ${branch}`); - await mkdir(join(rootDir, "packages/engine/src"), { recursive: true }); - git(rootDir, `sh -c 'printf ${JSON.stringify(`export const ${fileName} = true;\n`)} > ${JSON.stringify(`packages/engine/src/${fileName}.ts`)}'`); - git(rootDir, `git add ${JSON.stringify(`packages/engine/src/${fileName}.ts`)}`); - git(rootDir, `git commit -m ${JSON.stringify(`feat: add ${fileName}`)}`); - git(rootDir, "git checkout main"); -} - -describe("FN-5819 reliability interactions: shared group member integration", () => { - it.skipIf(!hasGit)("keeps shared-member integration forward under autoMerge false", async () => { - const fixture = await makeReliabilityFixture({ - taskId: "FN-5819-RI-A", - settings: { testMode: true, autoMerge: false } as any, - }); - - try { - const { rootDir, store, task, manager } = fixture; - const second = await store.createTask({ - id: "FN-5819-RI-B", - title: "FN-5819-RI-B", - description: "second member", - column: "in-review", - baseBranch: "main", - branch: "fusion/fn-5819-ri-b", - prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", - steps: [], - } as any); - const nongroup = await store.createTask({ - id: "FN-5819-RI-NONGROUP", - title: "FN-5819-RI-NONGROUP", - description: "non-group in-review", - column: "in-review", - baseBranch: "main", - branch: "fusion/fn-5819-ri-nongroup", - prompt: "## File Scope\n- packages/engine/src/__tests__/reliability-interactions/**/*.ts\n", - steps: [], - } as any); - - const group = store.createBranchGroup({ - sourceType: "planning", - sourceId: "PS-FN5819", - branchName: "fusion/groups/fn-5819-shared", - }); - await store.setTaskBranchGroup(task.id, group.id); - await store.setTaskBranchGroup(second.id, group.id); - - await stageMergeBranch(store, rootDir, task.id, "fn5819MemberA"); - await stageMergeBranch(store, rootDir, second.id, "fn5819MemberB"); - - const first = await aiMergeTask(store, rootDir, task.id); - const secondResult = await aiMergeTask(store, rootDir, second.id); - expect(first.merged).toBe(true); - expect(secondResult.merged).toBe(true); - - expect(git(rootDir, `git show ${group.branchName}:packages/engine/src/fn5819MemberA.ts`)).toContain("fn5819MemberA"); - expect(git(rootDir, `git show ${group.branchName}:packages/engine/src/fn5819MemberB.ts`)).toContain("fn5819MemberB"); - expect(() => git(rootDir, "git show main:packages/engine/src/fn5819MemberA.ts")).toThrow(); - expect(() => git(rootDir, "git show main:packages/engine/src/fn5819MemberB.ts")).toThrow(); - - const moveSpy = vi.spyOn(store, "moveTask"); - await (manager as any).runMaintenance(); - - expect(moveSpy.mock.calls.some(([id, column]) => id === task.id && column === "todo")).toBe(false); - expect(moveSpy.mock.calls.some(([id, column]) => id === second.id && column === "todo")).toBe(false); - expect(moveSpy.mock.calls.some(([id, column]) => id === task.id && column === "in-progress")).toBe(false); - expect(moveSpy.mock.calls.some(([id, column]) => id === second.id && column === "in-progress")).toBe(false); - - const refreshedNonGroup = await store.getTask(nongroup.id); - expect(refreshedNonGroup.column).toBe("in-review"); - } finally { - await fixture.cleanup(); - } - }, 60_000); -});