feat(boulder-state): add session-aware multi-work storage helpers

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-11 13:28:58 +09:00
parent 246e0dca80
commit 9f500743d1
2 changed files with 651 additions and 5 deletions
+157
View File
@@ -3,17 +3,28 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
import { dirname, join } from "node:path"
import { tmpdir } from "node:os"
import {
addBoulderWork,
appendSessionIdForWork,
getActiveWorks,
getBoulderWorks,
readBoulderState,
writeBoulderState,
appendSessionId,
clearBoulderState,
getWorkById,
getWorkByPlanName,
getWorkForSession,
getWorkResumeOptions,
getPlanProgress,
getPlanName,
createBoulderState,
findPrometheusPlans,
getTaskSessionState,
resolveBoulderPlanPath,
resolveBoulderPlanPathForWork,
selectActiveWork,
upsertTaskSessionState,
upsertTaskSessionStateForWork,
} from "./storage"
import type { BoulderState } from "./types"
import { readCurrentTopLevelTask } from "./top-level-task"
@@ -39,6 +50,31 @@ describe("boulder-state", () => {
})
describe("readBoulderState", () => {
test("should preserve legacy boulder.json fields during round-trip", () => {
// given
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const legacyRawState = {
active_plan: "/path/to/legacy-plan.md",
started_at: "2026-01-01T00:00:00.000Z",
session_ids: ["legacy-session"],
plan_name: "legacy-plan",
}
writeFileSync(boulderFile, JSON.stringify(legacyRawState, null, 2), "utf-8")
// when
const state = readBoulderState(TEST_DIR)
expect(state).not.toBeNull()
const writeSucceeded = writeBoulderState(TEST_DIR, state!)
const roundTripState = readBoulderState(TEST_DIR)
// then
expect(writeSucceeded).toBe(true)
expect(roundTripState?.active_plan).toBe(legacyRawState.active_plan)
expect(roundTripState?.started_at).toBe(legacyRawState.started_at)
expect(roundTripState?.session_ids).toEqual(legacyRawState.session_ids)
expect(roundTripState?.plan_name).toBe(legacyRawState.plan_name)
})
test("should return null when no boulder.json exists", () => {
// given - no boulder.json file
// when
@@ -387,6 +423,127 @@ describe("boulder-state", () => {
})
})
describe("multi-work helpers", () => {
test("should add second work and keep both active works", () => {
// given
const firstState = createBoulderState(
join(TEST_DIR, ".sisyphus/plans/plan-a.md"),
"session-a",
"atlas",
"/worktree-a",
)
writeBoulderState(TEST_DIR, firstState)
const firstWorkId = firstState.active_work_id
// when
const updatedState = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
sessionId: "session-b",
agent: "atlas",
worktreePath: "/worktree-b",
})
// then
expect(updatedState).not.toBeNull()
const works = updatedState?.works ?? {}
expect(Object.keys(works).length).toBe(2)
expect(firstWorkId).toBeDefined()
expect(works[firstWorkId!]).toBeDefined()
expect(updatedState?.active_plan).toContain("plan-b.md")
expect(getActiveWorks(TEST_DIR).length).toBe(2)
})
test("should resolve work for session using updated_at tie-break", () => {
// given
const baseState = createBoulderState(
join(TEST_DIR, ".sisyphus/plans/plan-a.md"),
"session-a",
)
writeBoulderState(TEST_DIR, baseState)
const stateWithSecond = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
sessionId: "session-b",
})
expect(stateWithSecond).not.toBeNull()
const workIds = Object.keys(stateWithSecond!.works ?? {})
expect(workIds.length).toBe(2)
const firstWorkId = workIds.find((workId) => (stateWithSecond!.works?.[workId]?.plan_name ?? "") === "plan-a")!
const secondWorkId = workIds.find((workId) => (stateWithSecond!.works?.[workId]?.plan_name ?? "") === "plan-b")!
appendSessionIdForWork(TEST_DIR, secondWorkId, "session-a", "appended")
appendSessionIdForWork(TEST_DIR, firstWorkId, "session-a", "appended")
// when
const resolvedWork = getWorkForSession(TEST_DIR, "session-a")
// then
expect(resolvedWork?.work_id).toBe(firstWorkId)
})
test("should support selecting active work and read helpers", () => {
// given
const initialState = createBoulderState(join(TEST_DIR, ".sisyphus/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const added = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
sessionId: "session-b",
worktreePath: "/tmp/worktree-b",
})
expect(added).not.toBeNull()
const firstWork = getWorkByPlanName(TEST_DIR, "plan-a")
expect(firstWork).not.toBeNull()
// when
const selected = selectActiveWork(TEST_DIR, firstWork!.work_id)
const selectedById = getWorkById(TEST_DIR, firstWork!.work_id)
const byPlanNameWithWorktree = getWorkByPlanName(TEST_DIR, "plan-b", { worktreePath: "/tmp/worktree-b" })
const byPlanPath = resolveBoulderPlanPathForWork(TEST_DIR, firstWork!)
const resumeOptions = getWorkResumeOptions(TEST_DIR)
const worksFromState = getBoulderWorks(selected!)
// then
expect(selected?.active_work_id).toBe(firstWork!.work_id)
expect(selectedById?.work_id).toBe(firstWork!.work_id)
expect(byPlanNameWithWorktree?.plan_name).toBe("plan-b")
expect(byPlanPath.endsWith("plan-a.md")).toBe(true)
expect(resumeOptions.length).toBe(2)
expect(worksFromState.length).toBe(2)
})
test("should upsert task session for specific work and keep first started_at", () => {
// given
const initialState = createBoulderState(join(TEST_DIR, ".sisyphus/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const workId = initialState.active_work_id!
upsertTaskSessionStateForWork(TEST_DIR, workId, {
taskKey: "todo:1",
taskLabel: "1",
taskTitle: "task one",
sessionId: "task-session-a",
})
const seededState = readBoulderState(TEST_DIR)!
seededState.works![workId]!.task_sessions!["todo:1"]!.started_at = "2026-01-01T00:00:00.000Z"
writeBoulderState(TEST_DIR, seededState)
// when
const updated = upsertTaskSessionStateForWork(TEST_DIR, workId, {
taskKey: "todo:1",
taskLabel: "1",
taskTitle: "task one",
sessionId: "task-session-b",
})
// then
expect(updated).not.toBeNull()
const taskSession = updated?.works?.[workId]?.task_sessions?.["todo:1"]
expect(taskSession?.session_id).toBe("task-session-b")
expect(taskSession?.started_at).toBe("2026-01-01T00:00:00.000Z")
})
})
describe("readCurrentTopLevelTask", () => {
test("should return the first unchecked top-level task in TODOs", () => {
// given - plan with nested and top-level unchecked tasks