Handle merge conflicts as orchestration events
This commit is contained in:
@@ -44,6 +44,11 @@ test("session metadata store persists and updates session metadata", async () =>
|
||||
|
||||
const readBack = await store.readSession("session-abc");
|
||||
assert.equal(readBack?.sessionStatus, "closed");
|
||||
|
||||
const closedWithConflicts = await store.updateSession("session-abc", {
|
||||
sessionStatus: "closed_with_conflicts",
|
||||
});
|
||||
assert.equal(closedWithConflicts.sessionStatus, "closed_with_conflicts");
|
||||
});
|
||||
|
||||
test("session worktree manager provisions and merges task worktrees", async () => {
|
||||
@@ -86,11 +91,12 @@ test("session worktree manager provisions and merges task worktrees", async () =
|
||||
|
||||
await writeFile(resolve(taskWorktreePath, "feature.txt"), "task output\n", "utf8");
|
||||
|
||||
await manager.mergeTaskIntoBase({
|
||||
const mergeOutcome = await manager.mergeTaskIntoBase({
|
||||
taskId: "task-1",
|
||||
baseWorkspacePath,
|
||||
taskWorktreePath,
|
||||
});
|
||||
assert.equal(mergeOutcome.kind, "success");
|
||||
|
||||
const mergedFile = await readFile(resolve(baseWorkspacePath, "feature.txt"), "utf8");
|
||||
assert.equal(mergedFile, "task output\n");
|
||||
@@ -104,13 +110,70 @@ test("session worktree manager provisions and merges task worktrees", async () =
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await manager.closeSession({
|
||||
const closeOutcome = await manager.closeSession({
|
||||
session,
|
||||
taskWorktreePaths: [],
|
||||
mergeBaseIntoProject: false,
|
||||
});
|
||||
assert.equal(closeOutcome.kind, "success");
|
||||
|
||||
await assert.rejects(() => stat(baseWorkspacePath), {
|
||||
code: "ENOENT",
|
||||
});
|
||||
});
|
||||
|
||||
test("session worktree manager returns conflict outcome instead of throwing", async () => {
|
||||
const root = await mkdtemp(resolve(tmpdir(), "ai-ops-session-worktree-conflict-"));
|
||||
const projectPath = resolve(root, "project");
|
||||
const worktreeRoot = resolve(root, "worktrees");
|
||||
|
||||
await mkdir(projectPath, { recursive: true });
|
||||
await git(["init", projectPath]);
|
||||
await git(["-C", projectPath, "config", "user.name", "AI Ops"]);
|
||||
await git(["-C", projectPath, "config", "user.email", "ai-ops@example.local"]);
|
||||
await writeFile(resolve(projectPath, "README.md"), "base\n", "utf8");
|
||||
await git(["-C", projectPath, "add", "README.md"]);
|
||||
await git(["-C", projectPath, "commit", "-m", "initial commit"]);
|
||||
|
||||
const manager = new SessionWorktreeManager({
|
||||
worktreeRoot,
|
||||
baseRef: "HEAD",
|
||||
});
|
||||
|
||||
const sessionId = "session-conflict-1";
|
||||
const baseWorkspacePath = manager.resolveBaseWorkspacePath(sessionId);
|
||||
|
||||
await manager.initializeSessionBaseWorkspace({
|
||||
sessionId,
|
||||
projectPath,
|
||||
baseWorkspacePath,
|
||||
});
|
||||
|
||||
const taskWorktreePath = (
|
||||
await manager.ensureTaskWorktree({
|
||||
sessionId,
|
||||
taskId: "task-conflict",
|
||||
baseWorkspacePath,
|
||||
})
|
||||
).taskWorktreePath;
|
||||
|
||||
await writeFile(resolve(baseWorkspacePath, "README.md"), "base branch change\n", "utf8");
|
||||
await git(["-C", baseWorkspacePath, "add", "README.md"]);
|
||||
await git(["-C", baseWorkspacePath, "commit", "-m", "base update"]);
|
||||
|
||||
await writeFile(resolve(taskWorktreePath, "README.md"), "task branch change\n", "utf8");
|
||||
|
||||
const mergeOutcome = await manager.mergeTaskIntoBase({
|
||||
taskId: "task-conflict",
|
||||
baseWorkspacePath,
|
||||
taskWorktreePath,
|
||||
});
|
||||
|
||||
assert.equal(mergeOutcome.kind, "conflict");
|
||||
if (mergeOutcome.kind !== "conflict") {
|
||||
throw new Error("Expected merge conflict outcome.");
|
||||
}
|
||||
assert.equal(mergeOutcome.taskId, "task-conflict");
|
||||
assert.equal(mergeOutcome.worktreePath, taskWorktreePath);
|
||||
assert.ok(mergeOutcome.conflictFiles.includes("README.md"));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user