From 079a2cd65a4d1ccba12ff9053da2cfa267334613 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 11 May 2026 14:25:52 +0900 Subject: [PATCH] fix(start-work): preserve existing works when starting an explicit new plan --- .../start-work/context-info-builder.test.ts | 48 +++++++++++++++++++ src/hooks/start-work/context-info-builder.ts | 37 +++++++++----- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/hooks/start-work/context-info-builder.test.ts b/src/hooks/start-work/context-info-builder.test.ts index ffc08978b..139cc179e 100644 --- a/src/hooks/start-work/context-info-builder.test.ts +++ b/src/hooks/start-work/context-info-builder.test.ts @@ -168,4 +168,52 @@ describe("buildStartWorkContextInfo", () => { expect(existsSync(getBoulderFilePath(testDirectory))).toBe(true) expect(clearSpy).toHaveBeenCalledTimes(0) }) + + test("keeps existing works when explicit new plan is started", () => { + // given + writePlan("work-a", "## TODOs\n- [ ] 1. Work A") + const workBPath = writePlan("work-b", "## TODOs\n- [ ] 1. Work B") + writePlan("new-plan-c", "## TODOs\n- [ ] 1. Work C") + + const initialState = createBoulderState( + join(testDirectory, ".sisyphus", "plans", "work-a.md"), + "session-a", + "atlas", + "/tmp/worktree-a", + ) + writeBoulderState(testDirectory, initialState) + + const workAId = initialState.active_work_id! + const withSecondWork = addBoulderWork(testDirectory, { + planPath: workBPath, + sessionId: "session-b", + agent: "atlas", + worktreePath: "/tmp/worktree-b", + }) + expect(withSecondWork).not.toBeNull() + const workBId = Object.keys(withSecondWork!.works!).find((workId) => workId !== workAId) + expect(workBId).toBeDefined() + + // when + buildStartWorkContextInfo({ + ctx: createPluginInput(), + explicitPlanName: "new-plan-c", + existingState: readExistingState(), + sessionId: "session-c", + timestamp: "2026-05-11T00:00:00.000Z", + activeAgent: "atlas", + worktreePath: undefined, + worktreeBlock: "", + }) + + // then + const nextState = readBoulderState(testDirectory) + const workIds = Object.keys(nextState?.works ?? {}) + expect(workIds.length).toBe(3) + expect(workIds).toContain(workAId) + expect(workIds).toContain(workBId!) + const workC = getWorkByPlanName(testDirectory, "new-plan-c") + expect(workC).not.toBeNull() + expect(workIds).toContain(workC!.work_id) + }) }) diff --git a/src/hooks/start-work/context-info-builder.ts b/src/hooks/start-work/context-info-builder.ts index 5ea4d8fce..9fc8e0fd4 100644 --- a/src/hooks/start-work/context-info-builder.ts +++ b/src/hooks/start-work/context-info-builder.ts @@ -48,19 +48,14 @@ function findPlanByName(plans: string[], requestedName: string): string | null { return normalizedPartialMatch || null } -function buildAutoSelectedPlanContext(params: { +function buildAutoSelectedPlanContextInfoOnly(params: { planPath: string sessionId: string timestamp: string - activeAgent: string - worktreePath: string | undefined worktreeBlock: string - directory: string }): string { - const { planPath, sessionId, timestamp, activeAgent, worktreePath, worktreeBlock, directory } = params + const { planPath, sessionId, timestamp, worktreeBlock } = params const progress = getPlanProgress(planPath) - const newState = createBoulderState(planPath, sessionId, activeAgent, worktreePath) - writeBoulderState(directory, newState) return ` ## Auto-Selected Plan @@ -75,6 +70,27 @@ ${worktreeBlock} boulder.json has been created. Read the plan and begin execution.` } +function buildAutoSelectedPlanContextWithStateInit(params: { + planPath: string + sessionId: string + timestamp: string + activeAgent: string + worktreePath: string | undefined + worktreeBlock: string + directory: string +}): string { + const { planPath, sessionId, timestamp, activeAgent, worktreePath, worktreeBlock, directory } = params + const newState = createBoulderState(planPath, sessionId, activeAgent, worktreePath) + writeBoulderState(directory, newState) + + return buildAutoSelectedPlanContextInfoOnly({ + planPath, + sessionId, + timestamp, + worktreeBlock, + }) +} + function buildMissingPlanContext(explicitPlanName: string, allPlans: string[]): string { const incompletePlans = allPlans.filter((p) => !getPlanProgress(p).isComplete) if (incompletePlans.length > 0) { @@ -218,14 +234,11 @@ function buildExplicitPlanContext(params: { worktreePath, }) - return buildAutoSelectedPlanContext({ + return buildAutoSelectedPlanContextInfoOnly({ planPath: matchedPlan, sessionId, timestamp, - activeAgent, - worktreePath, worktreeBlock, - directory, }) } @@ -328,7 +341,7 @@ function buildPlanDiscoveryContext(params: { } if (incompletePlans.length === 1) { - return contextInfo + buildAutoSelectedPlanContext({ + return contextInfo + buildAutoSelectedPlanContextWithStateInit({ planPath: incompletePlans[0], sessionId, timestamp,