feat(FN-1036): add mergeDetails collection and storage in merger
- Add MergeDetails type import and mergeDetails field to TaskStore.updateTask signature - Store merge details (commitSha, filesChanged, insertions, deletions, mergeCommitMessage, resolution info) after successful squash merge - Capture best-effort commitSha even when branch is not found (mergeConfirmed: false) - Add comprehensive tests for store updateTask with mergeDetails and merger detail collection
This commit is contained in:
@@ -5648,4 +5648,62 @@ Task with acceptance criteria
|
||||
expect(branchCommands).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mergeDetails via updateTask", () => {
|
||||
it("can set mergeDetails on a task", async () => {
|
||||
const task = await store.createTask({ description: "test merge details" });
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
|
||||
const mergeDetails = {
|
||||
commitSha: "abc123",
|
||||
filesChanged: 5,
|
||||
insertions: 10,
|
||||
deletions: 3,
|
||||
mergeCommitMessage: "Merge task",
|
||||
mergedAt: new Date().toISOString(),
|
||||
mergeConfirmed: true,
|
||||
};
|
||||
|
||||
const updated = await store.updateTask(task.id, { mergeDetails });
|
||||
expect(updated.mergeDetails).toEqual(mergeDetails);
|
||||
|
||||
// Verify it persists
|
||||
const reloaded = await store.getTask(task.id);
|
||||
expect(reloaded.mergeDetails).toEqual(mergeDetails);
|
||||
});
|
||||
|
||||
it("can clear mergeDetails by passing null", async () => {
|
||||
const task = await store.createTask({ description: "test merge details clear" });
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
|
||||
await store.updateTask(task.id, {
|
||||
mergeDetails: { commitSha: "abc123", mergeConfirmed: true },
|
||||
});
|
||||
|
||||
const cleared = await store.updateTask(task.id, { mergeDetails: null });
|
||||
expect(cleared.mergeDetails).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not modify mergeDetails when not included in updates", async () => {
|
||||
const task = await store.createTask({ description: "test merge details no-op" });
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
|
||||
await store.updateTask(task.id, {
|
||||
mergeDetails: { commitSha: "def456", mergeConfirmed: true },
|
||||
});
|
||||
|
||||
// Update something unrelated
|
||||
const updated = await store.updateTask(task.id, { summary: "some summary" });
|
||||
expect(updated.mergeDetails).toEqual({ commitSha: "def456", mergeConfirmed: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1095,7 +1095,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
|
||||
async updateTask(
|
||||
id: string,
|
||||
updates: { title?: string; description?: string; prompt?: string; worktree?: string | null; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string | null; branch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; stuckKillCount?: number | null; recoveryRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
|
||||
updates: { title?: string; description?: string; prompt?: string; worktree?: string | null; status?: string | null; dependencies?: string[]; blockedBy?: string | null; paused?: boolean; baseBranch?: string | null; branch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; mergeRetries?: number; stuckKillCount?: number | null; recoveryRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; modifiedFiles?: string[] | null; missionId?: string | null; sliceId?: string | null },
|
||||
): Promise<Task> {
|
||||
return this.withTaskLock(id, async () => {
|
||||
// Validate that task doesn't depend on itself
|
||||
@@ -1223,6 +1223,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
} else if (updates.workflowStepResults !== undefined) {
|
||||
task.workflowStepResults = updates.workflowStepResults;
|
||||
}
|
||||
if (updates.mergeDetails === null) {
|
||||
task.mergeDetails = undefined;
|
||||
} else if (updates.mergeDetails !== undefined) {
|
||||
task.mergeDetails = updates.mergeDetails;
|
||||
}
|
||||
if (updates.modifiedFiles === null) {
|
||||
task.modifiedFiles = undefined;
|
||||
} else if (updates.modifiedFiles !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user