import { describe, expect, it } from "vitest"; import { buildWorktreeMergePlan, parseWorktreeMergeScopes } from "../commands/worktree-merge-history-lib.js"; function makeIssue(overrides: Record = {}) { return { id: "issue-1", companyId: "company-1", projectId: null, projectWorkspaceId: null, goalId: "goal-1", parentId: null, title: "Issue", description: null, status: "todo", priority: "medium", assigneeAgentId: null, assigneeUserId: null, checkoutRunId: null, executionRunId: null, executionAgentNameKey: null, executionLockedAt: null, createdByAgentId: null, createdByUserId: "local-board", issueNumber: 1, identifier: "PAP-1", requestDepth: 0, billingCode: null, assigneeAdapterOverrides: null, executionWorkspaceId: null, executionWorkspacePreference: null, executionWorkspaceSettings: null, startedAt: null, completedAt: null, cancelledAt: null, hiddenAt: null, createdAt: new Date("2026-03-20T00:00:00.000Z"), updatedAt: new Date("2026-03-20T00:00:00.000Z"), ...overrides, } as any; } function makeComment(overrides: Record = {}) { return { id: "comment-1", companyId: "company-1", issueId: "issue-1", authorAgentId: null, authorUserId: "local-board", body: "hello", createdAt: new Date("2026-03-20T00:00:00.000Z"), updatedAt: new Date("2026-03-20T00:00:00.000Z"), ...overrides, } as any; } function makeIssueDocument(overrides: Record = {}) { return { id: "issue-document-1", companyId: "company-1", issueId: "issue-1", documentId: "document-1", key: "plan", linkCreatedAt: new Date("2026-03-20T00:00:00.000Z"), linkUpdatedAt: new Date("2026-03-20T00:00:00.000Z"), title: "Plan", format: "markdown", latestBody: "# Plan", latestRevisionId: "revision-1", latestRevisionNumber: 1, createdByAgentId: null, createdByUserId: "local-board", updatedByAgentId: null, updatedByUserId: "local-board", documentCreatedAt: new Date("2026-03-20T00:00:00.000Z"), documentUpdatedAt: new Date("2026-03-20T00:00:00.000Z"), ...overrides, } as any; } function makeDocumentRevision(overrides: Record = {}) { return { id: "revision-1", companyId: "company-1", documentId: "document-1", revisionNumber: 1, body: "# Plan", changeSummary: null, createdByAgentId: null, createdByUserId: "local-board", createdAt: new Date("2026-03-20T00:00:00.000Z"), ...overrides, } as any; } function makeAttachment(overrides: Record = {}) { return { id: "attachment-1", companyId: "company-1", issueId: "issue-1", issueCommentId: null, assetId: "asset-1", provider: "local_disk", objectKey: "company-1/issues/issue-1/2026/03/20/asset.png", contentType: "image/png", byteSize: 12, sha256: "deadbeef", originalFilename: "asset.png", createdByAgentId: null, createdByUserId: "local-board", assetCreatedAt: new Date("2026-03-20T00:00:00.000Z"), assetUpdatedAt: new Date("2026-03-20T00:00:00.000Z"), attachmentCreatedAt: new Date("2026-03-20T00:00:00.000Z"), attachmentUpdatedAt: new Date("2026-03-20T00:00:00.000Z"), ...overrides, } as any; } describe("worktree merge history planner", () => { it("parses default scopes", () => { expect(parseWorktreeMergeScopes(undefined)).toEqual(["issues", "comments"]); expect(parseWorktreeMergeScopes("issues")).toEqual(["issues"]); }); it("dedupes nested worktree issues by preserved source uuid", () => { const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10", title: "Shared" }); const branchOneIssue = makeIssue({ id: "issue-b", identifier: "PAP-22", title: "Branch one issue", createdAt: new Date("2026-03-20T01:00:00.000Z"), }); const branchTwoIssue = makeIssue({ id: "issue-c", identifier: "PAP-23", title: "Branch two issue", createdAt: new Date("2026-03-20T02:00:00.000Z"), }); const plan = buildWorktreeMergePlan({ companyId: "company-1", companyName: "Paperclip", issuePrefix: "PAP", previewIssueCounterStart: 500, scopes: ["issues", "comments"], sourceIssues: [sharedIssue, branchOneIssue, branchTwoIssue], targetIssues: [sharedIssue, branchOneIssue], sourceComments: [], targetComments: [], targetAgents: [], targetProjects: [], targetProjectWorkspaces: [], targetGoals: [{ id: "goal-1" }] as any, }); expect(plan.counts.issuesToInsert).toBe(1); expect(plan.issuePlans.filter((item) => item.action === "insert").map((item) => item.source.id)).toEqual(["issue-c"]); expect(plan.issuePlans.find((item) => item.source.id === "issue-c" && item.action === "insert")).toMatchObject({ previewIdentifier: "PAP-501", }); }); it("clears missing references and coerces in_progress without an assignee", () => { const plan = buildWorktreeMergePlan({ companyId: "company-1", companyName: "Paperclip", issuePrefix: "PAP", previewIssueCounterStart: 10, scopes: ["issues"], sourceIssues: [ makeIssue({ id: "issue-x", identifier: "PAP-99", status: "in_progress", assigneeAgentId: "agent-missing", projectId: "project-missing", projectWorkspaceId: "workspace-missing", goalId: "goal-missing", }), ], targetIssues: [], sourceComments: [], targetComments: [], targetAgents: [], targetProjects: [], targetProjectWorkspaces: [], targetGoals: [], }); const insert = plan.issuePlans[0] as any; expect(insert.targetStatus).toBe("todo"); expect(insert.targetAssigneeAgentId).toBeNull(); expect(insert.targetProjectId).toBeNull(); expect(insert.targetProjectWorkspaceId).toBeNull(); expect(insert.targetGoalId).toBeNull(); expect(insert.adjustments).toEqual([ "clear_assignee_agent", "clear_project", "clear_project_workspace", "clear_goal", "coerce_in_progress_to_todo", ]); }); it("applies an explicit project mapping override instead of clearing the project", () => { const plan = buildWorktreeMergePlan({ companyId: "company-1", companyName: "Paperclip", issuePrefix: "PAP", previewIssueCounterStart: 10, scopes: ["issues"], sourceIssues: [ makeIssue({ id: "issue-project-map", identifier: "PAP-77", projectId: "source-project-1", projectWorkspaceId: "source-workspace-1", }), ], targetIssues: [], sourceComments: [], targetComments: [], targetAgents: [], targetProjects: [{ id: "target-project-1", name: "Mapped project", status: "in_progress" }] as any, targetProjectWorkspaces: [], targetGoals: [{ id: "goal-1" }] as any, projectIdOverrides: { "source-project-1": "target-project-1", }, }); const insert = plan.issuePlans[0] as any; expect(insert.targetProjectId).toBe("target-project-1"); expect(insert.projectResolution).toBe("mapped"); expect(insert.mappedProjectName).toBe("Mapped project"); expect(insert.targetProjectWorkspaceId).toBeNull(); expect(insert.adjustments).toEqual(["clear_project_workspace"]); }); it("imports comments onto shared or newly imported issues while skipping existing comments", () => { const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" }); const newIssue = makeIssue({ id: "issue-b", identifier: "PAP-11", createdAt: new Date("2026-03-20T01:00:00.000Z"), }); const existingComment = makeComment({ id: "comment-existing", issueId: "issue-a" }); const sharedIssueComment = makeComment({ id: "comment-shared", issueId: "issue-a" }); const newIssueComment = makeComment({ id: "comment-new-issue", issueId: "issue-b", authorAgentId: "missing-agent", createdAt: new Date("2026-03-20T01:05:00.000Z"), }); const plan = buildWorktreeMergePlan({ companyId: "company-1", companyName: "Paperclip", issuePrefix: "PAP", previewIssueCounterStart: 10, scopes: ["issues", "comments"], sourceIssues: [sharedIssue, newIssue], targetIssues: [sharedIssue], sourceComments: [existingComment, sharedIssueComment, newIssueComment], targetComments: [existingComment], targetAgents: [], targetProjects: [], targetProjectWorkspaces: [], targetGoals: [{ id: "goal-1" }] as any, }); expect(plan.counts.commentsToInsert).toBe(2); expect(plan.counts.commentsExisting).toBe(1); expect(plan.commentPlans.filter((item) => item.action === "insert").map((item) => item.source.id)).toEqual([ "comment-shared", "comment-new-issue", ]); expect(plan.adjustments.clear_author_agent).toBe(1); }); it("merges document revisions onto an existing shared document and renumbers conflicts", () => { const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" }); const sourceDocument = makeIssueDocument({ issueId: "issue-a", documentId: "document-a", latestBody: "# Branch plan", latestRevisionId: "revision-branch-2", latestRevisionNumber: 2, documentUpdatedAt: new Date("2026-03-20T02:00:00.000Z"), linkUpdatedAt: new Date("2026-03-20T02:00:00.000Z"), }); const targetDocument = makeIssueDocument({ issueId: "issue-a", documentId: "document-a", latestBody: "# Main plan", latestRevisionId: "revision-main-2", latestRevisionNumber: 2, documentUpdatedAt: new Date("2026-03-20T01:00:00.000Z"), linkUpdatedAt: new Date("2026-03-20T01:00:00.000Z"), }); const sourceRevisionOne = makeDocumentRevision({ documentId: "document-a", id: "revision-1" }); const sourceRevisionTwo = makeDocumentRevision({ documentId: "document-a", id: "revision-branch-2", revisionNumber: 2, body: "# Branch plan", createdAt: new Date("2026-03-20T02:00:00.000Z"), }); const targetRevisionOne = makeDocumentRevision({ documentId: "document-a", id: "revision-1" }); const targetRevisionTwo = makeDocumentRevision({ documentId: "document-a", id: "revision-main-2", revisionNumber: 2, body: "# Main plan", createdAt: new Date("2026-03-20T01:00:00.000Z"), }); const plan = buildWorktreeMergePlan({ companyId: "company-1", companyName: "Paperclip", issuePrefix: "PAP", previewIssueCounterStart: 10, scopes: ["issues", "comments"], sourceIssues: [sharedIssue], targetIssues: [sharedIssue], sourceComments: [], targetComments: [], sourceDocuments: [sourceDocument], targetDocuments: [targetDocument], sourceDocumentRevisions: [sourceRevisionOne, sourceRevisionTwo], targetDocumentRevisions: [targetRevisionOne, targetRevisionTwo], sourceAttachments: [], targetAttachments: [], targetAgents: [], targetProjects: [], targetProjectWorkspaces: [], targetGoals: [{ id: "goal-1" }] as any, }); expect(plan.counts.documentsToMerge).toBe(1); expect(plan.counts.documentRevisionsToInsert).toBe(1); expect(plan.documentPlans[0]).toMatchObject({ action: "merge_existing", latestRevisionId: "revision-branch-2", latestRevisionNumber: 3, }); const mergePlan = plan.documentPlans[0] as any; expect(mergePlan.revisionsToInsert).toHaveLength(1); expect(mergePlan.revisionsToInsert[0]).toMatchObject({ source: { id: "revision-branch-2" }, targetRevisionNumber: 3, }); }); it("imports attachments while clearing missing comment and author references", () => { const sharedIssue = makeIssue({ id: "issue-a", identifier: "PAP-10" }); const attachment = makeAttachment({ issueId: "issue-a", issueCommentId: "comment-missing", createdByAgentId: "agent-missing", }); const plan = buildWorktreeMergePlan({ companyId: "company-1", companyName: "Paperclip", issuePrefix: "PAP", previewIssueCounterStart: 10, scopes: ["issues"], sourceIssues: [sharedIssue], targetIssues: [sharedIssue], sourceComments: [], targetComments: [], sourceDocuments: [], targetDocuments: [], sourceDocumentRevisions: [], targetDocumentRevisions: [], sourceAttachments: [attachment], targetAttachments: [], targetAgents: [], targetProjects: [], targetProjectWorkspaces: [], targetGoals: [{ id: "goal-1" }] as any, }); expect(plan.counts.attachmentsToInsert).toBe(1); expect(plan.adjustments.clear_attachment_agent).toBe(1); expect(plan.attachmentPlans[0]).toMatchObject({ action: "insert", targetIssueCommentId: null, targetCreatedByAgentId: null, }); }); });