From 0be58b3c547bcb73c9c423f133f4af69b28cf785 Mon Sep 17 00:00:00 2001 From: acamq <179265037+acamq@users.noreply.github.com> Date: Wed, 11 Mar 2026 18:36:07 -0600 Subject: [PATCH] test(hooks): add tests for worktree-disabled scenarios --- src/hooks/start-work/index.test.ts | 158 ++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 26 deletions(-) diff --git a/src/hooks/start-work/index.test.ts b/src/hooks/start-work/index.test.ts index 565b2decb..2fdf77ff7 100644 --- a/src/hooks/start-work/index.test.ts +++ b/src/hooks/start-work/index.test.ts @@ -12,7 +12,6 @@ import { import type { BoulderState } from "../../features/boulder-state" import * as sessionState from "../../features/claude-code-session-state" import * as worktreeDetector from "./worktree-detector" -import * as worktreeDetector from "./worktree-detector" describe("start-work hook", () => { let testDir: string @@ -529,32 +528,139 @@ describe("start-work hook", () => { expect(state?.session_ids).toContain("session-456") }) - test("should show existing worktree on resume when no --worktree flag", async () => { - // given - existing boulder already has worktree_path, no flag given - const planPath = join(testDir, "plan.md") - writeFileSync(planPath, "# Plan\n- [ ] Task 1") - const existingState: BoulderState = { - active_plan: planPath, - started_at: "2026-01-01T00:00:00Z", - session_ids: ["old-session"], - plan_name: "plan", - worktree_path: "/existing/wt", - } - writeBoulderState(testDir, existingState) + test("should show existing worktree on resume when no --worktree flag", async () => { + // given - existing boulder already has worktree_path, no flag given + const planPath = join(testDir, "plan.md") + writeFileSync(planPath, "# Plan\n- [ ] Task 1") + const existingState: BoulderState = { + active_plan: planPath, + started_at: "2026-01-01T00:00:00Z", + session_ids: ["old-session"], + plan_name: "plan", + worktree_path: "/existing/wt", + } + writeBoulderState(testDir, existingState) - const hook = createStartWorkHook(createMockPluginInput()) - const output = { - parts: [{ type: "text", text: "" }], - } + const hook = createStartWorkHook(createMockPluginInput()) + const output = { + parts: [{ type: "text", text: "" }], + } - // when - await hook["chat.message"]({ sessionID: "session-789" }, output) + // when + await hook["chat.message"]({ sessionID: "session-789" }, output) - // then - shows strong worktree active instructions - expect(output.parts[0].text).toContain("Worktree Active") - expect(output.parts[0].text).toContain("/existing/wt") - expect(output.parts[0].text).toContain("subagent") - expect(output.parts[0].text).not.toContain("Worktree Setup Required") - }) - }) + // then - shows strong worktree active instructions + expect(output.parts[0].text).toContain("Worktree Active") + expect(output.parts[0].text).toContain("/existing/wt") + expect(output.parts[0].text).toContain("subagent") + expect(output.parts[0].text).not.toContain("Worktree Setup Required") + }) + }) + + describe("worktree disabled by config", () => { + test("should NOT inject worktree content when worktreeEnabled is false and single plan auto-selected", async () => { + // given - single incomplete plan, worktreeEnabled: false + const plansDir = join(testDir, ".sisyphus", "plans") + mkdirSync(plansDir, { recursive: true }) + writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1") + + const hook = createStartWorkHook(createMockPluginInput(), { worktreeEnabled: false }) + const output = { + parts: [{ type: "text", text: "" }], + } + + // when + await hook["chat.message"]({ sessionID: "session-123" }, output) + + // then - no worktree content should appear + expect(output.parts[0].text).not.toContain("Worktree Active") + expect(output.parts[0].text).not.toContain("git worktree") + expect(output.parts[0].text).toContain("Auto-Selected Plan") + }) + + test("should NOT inject worktree when --worktree flag is passed but worktreeEnabled is false", async () => { + // given - single plan + --worktree flag, but worktreeEnabled: false + const plansDir = join(testDir, ".sisyphus", "plans") + mkdirSync(plansDir, { recursive: true }) + writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1") + + const hook = createStartWorkHook(createMockPluginInput(), { worktreeEnabled: false }) + const output = { + parts: [{ type: "text", text: "\n--worktree /some/path\n" }], + } + + // when + await hook["chat.message"]({ sessionID: "session-123" }, output) + + // then - config overrides flag, no worktree content in injected context + expect(output.parts[0].text).not.toContain("Worktree Active") + expect(output.parts[0].text).not.toContain("CRITICAL — DO NOT FORGET") + expect(output.parts[0].text).not.toContain("git worktree add") + }) + + test("should NOT inject worktree content when resuming with existing worktree_path but worktreeEnabled is false", async () => { + // given - existing boulder with worktree_path, but worktreeEnabled: false + const planPath = join(testDir, "plan.md") + writeFileSync(planPath, "# Plan\n- [ ] Task 1") + const existingState: BoulderState = { + active_plan: planPath, + started_at: "2026-01-01T00:00:00Z", + session_ids: ["old-session"], + plan_name: "plan", + worktree_path: "/existing/wt", + } + writeBoulderState(testDir, existingState) + + const hook = createStartWorkHook(createMockPluginInput(), { worktreeEnabled: false }) + const output = { + parts: [{ type: "text", text: "" }], + } + + // when + await hook["chat.message"]({ sessionID: "session-456" }, output) + + // then - no worktree content despite existing worktree_path + expect(output.parts[0].text).not.toContain("Worktree Active") + expect(output.parts[0].text).not.toContain("/existing/wt") + expect(output.parts[0].text).toContain("RESUMING") + }) + + test("should NOT store worktree_path in boulder.json when worktreeEnabled is false", async () => { + // given - single plan, worktreeEnabled: false + const plansDir = join(testDir, ".sisyphus", "plans") + mkdirSync(plansDir, { recursive: true }) + writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1") + + const hook = createStartWorkHook(createMockPluginInput(), { worktreeEnabled: false }) + const output = { + parts: [{ type: "text", text: "" }], + } + + // when + await hook["chat.message"]({ sessionID: "session-123" }, output) + + // then - boulder.json should not have worktree_path + const state = readBoulderState(testDir) + expect(state?.worktree_path).toBeUndefined() + }) + + test("should enable worktree by default when worktreeEnabled option is undefined (regression)", async () => { + // given - single plan, no worktreeEnabled option (undefined = default enabled) + const plansDir = join(testDir, ".sisyphus", "plans") + mkdirSync(plansDir, { recursive: true }) + writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1") + + const hook = createStartWorkHook(createMockPluginInput()) + const output = { + parts: [{ type: "text", text: "" }], + } + + // when + await hook["chat.message"]({ sessionID: "session-123" }, output) + + // then - should behave like worktree is enabled (no worktree content because no flag, but system ready for it) + expect(output.parts[0].text).toContain("Auto-Selected Plan") + expect(output.parts[0].text).not.toContain("Worktree Active") + }) + }) })