FN-5782: wire branch groups into merge routing
Connect branch_group metadata through planning and merge execution for grouped integration branches. - propagate `branchGroup` and `branchGroupName` through core store types, merge metadata, and CLI task lifecycle APIs - add merge coordination that computes branch-group plans and routes grouped tasks via `group-merge-coordinator` - extend finalize-plan, merger, and reliability tests to cover grouped merge routing and integration behavior - document the architecture update and add a patch changeset for `@runfusion/fusion` Files changed: .changeset/fn-5782-branch-group-merge.md | 7 + docs/architecture.md | 2 + packages/cli/src/commands/__tests__/task-lifecycle.test.ts | 64 ++++++++- packages/cli/src/commands/task-lifecycle.ts | 4 + packages/core/src/__tests__/branch-group-store.test.ts | 20 +++ packages/core/src/__tests__/task-merge.test.ts | 64 +++++++++ packages/core/src/store.ts | 22 ++- packages/core/src/task-merge.ts | 26 +++- packages/core/src/types.ts | 2 +- packages/engine/src/__tests__/experiment-finalize-plan.test.ts | 33 ++++- packages/engine/src/__tests__/group-merge-coordinator.test.ts | 62 +++++++++ packages/engine/src/__tests__/reliability-interactions/branch-group-merge-routing.test.ts | 153 +++++++++++++++++++++ packages/engine/src/experiment/finalize-plan.ts | 62 +++++++-- packages/engine/src/group-merge-coordinator.ts | 51 +++++++ packages/engine/src/index.ts | 4 + packages/engine/src/merger.ts | 47 ++++++- 16 files changed, 601 insertions(+), 22 deletions(-) Fusion-Task-Id: FN-5782 Fusion-Task-Lineage: 0a70cfaa-2379-4955-b295-64de1f3093c8
This commit is contained in:
@@ -115,6 +115,26 @@ describe("TaskStore branch groups", () => {
|
||||
expect(slim.find((entry) => entry.id === task.id)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("lists tasks by branch group and records landed member metadata", async () => {
|
||||
const group = store.createBranchGroup({ sourceType: "planning", sourceId: "PS-9", branchName: "fn/grouped" });
|
||||
const taskA = await store.createTask({ description: "group-a" });
|
||||
const taskB = await store.createTask({ description: "group-b" });
|
||||
const taskC = await store.createTask({ description: "group-c" });
|
||||
await store.setTaskBranchGroup(taskA.id, group.id);
|
||||
await store.setTaskBranchGroup(taskC.id, group.id);
|
||||
|
||||
const groupedTasks = await store.listTasksByBranchGroup(group.id);
|
||||
expect(groupedTasks.map((task) => task.id)).toEqual([taskA.id, taskC.id]);
|
||||
expect(groupedTasks.find((task) => task.id === taskB.id)).toBeUndefined();
|
||||
|
||||
const landed = store.recordBranchGroupMemberLanded(group.id, {
|
||||
worktreePath: "/tmp/fusion/grouped",
|
||||
status: "open",
|
||||
});
|
||||
expect(landed.worktreePath).toBe("/tmp/fusion/grouped");
|
||||
expect(landed.status).toBe("open");
|
||||
});
|
||||
|
||||
it("preserves autoMerge + branchContext in slim list/search/modifiedSince and archived slim", async () => {
|
||||
const task = await store.createTask({ description: "slim check" });
|
||||
const group = store.createBranchGroup({ sourceType: "mission", sourceId: "M-2", branchName: "fn/mission" });
|
||||
|
||||
@@ -52,6 +52,25 @@ describe("resolveTaskMergeTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("routes shared branch-group members to branch group integration branch", () => {
|
||||
expect(resolveTaskMergeTarget({
|
||||
baseBranch: undefined,
|
||||
branchContext: {
|
||||
groupId: "G-1",
|
||||
source: "planning",
|
||||
assignmentMode: "shared",
|
||||
inheritedBaseBranch: "develop",
|
||||
},
|
||||
}, {
|
||||
branchGroup: {
|
||||
branchName: "fusion/groups/planning-g-1",
|
||||
},
|
||||
})).toEqual({
|
||||
branch: "fusion/groups/planning-g-1",
|
||||
source: "branch-group-integration",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to inherited branch context", () => {
|
||||
expect(resolveTaskMergeTarget({
|
||||
baseBranch: undefined,
|
||||
@@ -82,6 +101,25 @@ describe("resolveTaskMergeTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps per-task-derived grouped members on inherited branch context", () => {
|
||||
expect(resolveTaskMergeTarget({
|
||||
baseBranch: undefined,
|
||||
branchContext: {
|
||||
groupId: "G-2",
|
||||
source: "planning",
|
||||
assignmentMode: "per-task-derived",
|
||||
inheritedBaseBranch: "develop",
|
||||
},
|
||||
}, {
|
||||
branchGroup: {
|
||||
branchName: "fusion/groups/planning-g-2",
|
||||
},
|
||||
})).toEqual({
|
||||
branch: "develop",
|
||||
source: "task-branch-context",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses project default branch when task has no explicit target", () => {
|
||||
expect(resolveTaskMergeTarget(
|
||||
{ baseBranch: undefined, branchContext: undefined },
|
||||
@@ -117,6 +155,32 @@ describe("resolveTaskMergeTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects branch-group integration branch when it points at a sibling fusion/fn-* branch", () => {
|
||||
const result = resolveTaskMergeTarget(
|
||||
{
|
||||
baseBranch: undefined,
|
||||
branchContext: {
|
||||
groupId: "G-1",
|
||||
source: "planning",
|
||||
assignmentMode: "shared",
|
||||
},
|
||||
},
|
||||
{
|
||||
branchGroup: {
|
||||
branchName: "fusion/fn-1234",
|
||||
},
|
||||
projectDefaultBranch: "main",
|
||||
},
|
||||
);
|
||||
expect(result.branch).toBe("main");
|
||||
expect(result.source).toBe("project-default");
|
||||
expect(result.rejected).toEqual({
|
||||
branch: "fusion/fn-1234",
|
||||
source: "branch-group-integration",
|
||||
reason: "fusion-sibling-branch",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects inherited branch context that points at a sibling fusion/fn-* branch", () => {
|
||||
const result = resolveTaskMergeTarget(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user