28137f46d9
TaskTool and the eval agent() bridge each held a private copy of the nested-repo patch eligibility gate and the AI commit-message factory; isolation policy could drift between the two callers. Moved both into task/isolation-runner.ts: - applyEligibleNestedPatches(opts) — single nested-patch gate (skip on patch-mode parent failure, skip on branch-mode unmerged root, fail non-fatally with a system-notification suffix). - makeIsolationCommitMessage(session) — single factory that yields the AI commit-message callback when task.isolation.commits === "ai" and a model registry is wired, undefined otherwise. Both call sites now invoke the helpers; behavior is unchanged. Removed the now-dead generateCommitMessage/applyNestedPatches imports from each caller. Added unit tests for the new helper covering the skip-on-patch-failure, skip-on-unmerged-branch, success, and failure-suffix paths. Fixes #3196
121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "bun:test";
|
|
import { applyEligibleNestedPatches, mergeIsolatedChanges } from "@oh-my-pi/pi-coding-agent/task/isolation-runner";
|
|
import type { SingleResult } from "@oh-my-pi/pi-coding-agent/task/types";
|
|
import * as worktreeModule from "@oh-my-pi/pi-coding-agent/task/worktree";
|
|
|
|
function result(overrides: Partial<SingleResult> = {}): SingleResult {
|
|
return {
|
|
index: 0,
|
|
id: "NestedOnly",
|
|
agent: "task",
|
|
agentSource: "bundled",
|
|
task: "Do nested work",
|
|
assignment: "Do nested work",
|
|
exitCode: 0,
|
|
output: "done",
|
|
stderr: "",
|
|
truncated: false,
|
|
durationMs: 1,
|
|
tokens: 0,
|
|
requests: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("mergeIsolatedChanges", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("allows nested-only branch-mode patches to apply when no root branch was created", async () => {
|
|
const mergeSpy = vi.spyOn(worktreeModule, "mergeTaskBranches");
|
|
const outcome = await mergeIsolatedChanges({
|
|
repoRoot: "/repo",
|
|
mergeMode: "branch",
|
|
result: result({
|
|
nestedPatches: [{ relativePath: "nested", patch: "diff --git a/file b/file\n" }],
|
|
}),
|
|
});
|
|
|
|
expect(mergeSpy).not.toHaveBeenCalled();
|
|
expect(outcome.changesApplied).toBe(true);
|
|
expect(outcome.hadAnyChanges).toBe(true);
|
|
expect(outcome.mergedBranchForNestedPatches).toBe(true);
|
|
expect(outcome.summary).toContain("nested repository patches captured");
|
|
});
|
|
|
|
it("does not mark failed branch-mode runs as nested-patch eligible", async () => {
|
|
const outcome = await mergeIsolatedChanges({
|
|
repoRoot: "/repo",
|
|
mergeMode: "branch",
|
|
result: result({
|
|
exitCode: 1,
|
|
nestedPatches: [{ relativePath: "nested", patch: "diff --git a/file b/file\n" }],
|
|
}),
|
|
});
|
|
|
|
expect(outcome.changesApplied).toBe(true);
|
|
expect(outcome.hadAnyChanges).toBe(false);
|
|
expect(outcome.mergedBranchForNestedPatches).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("applyEligibleNestedPatches", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
const nestedPatch = { relativePath: "nested", patch: "diff --git a/file b/file\n" };
|
|
|
|
it("skips when patch-mode parent merge failed", async () => {
|
|
const applySpy = vi.spyOn(worktreeModule, "applyNestedPatches");
|
|
const suffix = await applyEligibleNestedPatches({
|
|
result: result({ nestedPatches: [nestedPatch] }),
|
|
repoRoot: "/repo",
|
|
mergeMode: "patch",
|
|
changesApplied: false,
|
|
mergedBranchForNestedPatches: false,
|
|
});
|
|
expect(suffix).toBe("");
|
|
expect(applySpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("skips when branch mode did not actually merge the root branch", async () => {
|
|
const applySpy = vi.spyOn(worktreeModule, "applyNestedPatches");
|
|
const suffix = await applyEligibleNestedPatches({
|
|
result: result({ nestedPatches: [nestedPatch] }),
|
|
repoRoot: "/repo",
|
|
mergeMode: "branch",
|
|
changesApplied: true,
|
|
mergedBranchForNestedPatches: false,
|
|
});
|
|
expect(suffix).toBe("");
|
|
expect(applySpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("applies nested patches and returns no warning on success", async () => {
|
|
const applySpy = vi.spyOn(worktreeModule, "applyNestedPatches").mockResolvedValue();
|
|
const suffix = await applyEligibleNestedPatches({
|
|
result: result({ nestedPatches: [nestedPatch] }),
|
|
repoRoot: "/repo",
|
|
mergeMode: "patch",
|
|
changesApplied: true,
|
|
mergedBranchForNestedPatches: false,
|
|
});
|
|
expect(suffix).toBe("");
|
|
expect(applySpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("returns a system-notification suffix on apply failure", async () => {
|
|
vi.spyOn(worktreeModule, "applyNestedPatches").mockRejectedValue(new Error("boom"));
|
|
const suffix = await applyEligibleNestedPatches({
|
|
result: result({ nestedPatches: [nestedPatch] }),
|
|
repoRoot: "/repo",
|
|
mergeMode: "branch",
|
|
changesApplied: true,
|
|
mergedBranchForNestedPatches: true,
|
|
});
|
|
expect(suffix).toContain("Some nested repository patches failed to apply");
|
|
});
|
|
});
|