feat(workspace): store runtime state under omo

This commit is contained in:
YeonGyu-Kim
2026-05-16 17:40:11 +09:00
parent 5dca1a5742
commit a86221b1a9
13 changed files with 137 additions and 137 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ function createTempDirectory(): string {
}
function seedPlanAndState(directory: string): void {
const planDirectory = join(directory, ".sisyphus", "plans")
const planDirectory = join(directory, ".omo", "plans")
mkdirSync(planDirectory, { recursive: true })
const planAPath = join(planDirectory, "alpha.md")
@@ -35,7 +35,7 @@ function seedPlanAndState(directory: string): void {
"utf-8",
)
const boulderDirectory = join(directory, ".sisyphus")
const boulderDirectory = join(directory, ".omo")
mkdirSync(boulderDirectory, { recursive: true })
writeFileSync(
+38 -38
View File
@@ -53,10 +53,10 @@ function writeBoulderStateFile(
sessionIDs: string[],
sessionOrigins?: Record<string, "direct" | "appended">,
): void {
const sisyphusDir = join(directory, ".sisyphus")
mkdirSync(sisyphusDir, { recursive: true })
const omoDir = join(directory, ".omo")
mkdirSync(omoDir, { recursive: true })
writeFileSync(
join(sisyphusDir, "boulder.json"),
join(omoDir, "boulder.json"),
JSON.stringify({
active_plan: activePlanPath,
started_at: new Date().toISOString(),
@@ -74,8 +74,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "active-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "active-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] incomplete task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["test-session"])
const ctx = createMockContext(directory)
@@ -92,8 +92,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "done-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "done-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [x] completed task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["test-session"])
const ctx = createMockContext(directory)
@@ -110,17 +110,17 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const mainPlanPath = join(directory, ".sisyphus", "plans", "done-in-worktree-plan.md")
const mainPlanPath = join(directory, ".omo", "plans", "done-in-worktree-plan.md")
const worktreeDirectory = createTempDir()
const worktreePlanPath = join(worktreeDirectory, ".sisyphus", "plans", "done-in-worktree-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
mkdirSync(join(worktreeDirectory, ".sisyphus", "plans"), { recursive: true })
const worktreePlanPath = join(worktreeDirectory, ".omo", "plans", "done-in-worktree-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
mkdirSync(join(worktreeDirectory, ".omo", "plans"), { recursive: true })
writeFileSync(mainPlanPath, "- [ ] stale main repo task\n", "utf-8")
writeFileSync(worktreePlanPath, "- [x] completed worktree task\n", "utf-8")
const sisyphusDir = join(directory, ".sisyphus")
mkdirSync(sisyphusDir, { recursive: true })
const omoDir = join(directory, ".omo")
mkdirSync(omoDir, { recursive: true })
writeFileSync(
join(sisyphusDir, "boulder.json"),
join(omoDir, "boulder.json"),
JSON.stringify({
active_plan: mainPlanPath,
started_at: new Date().toISOString(),
@@ -145,8 +145,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "active-descendant-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "active-descendant-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session", "child-session"], {
"root-session": "direct",
@@ -181,8 +181,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "lineage-non-subagent-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "lineage-non-subagent-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session"])
@@ -209,8 +209,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "lineage-agent-mismatch-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "lineage-agent-mismatch-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session", "mismatch-subagent-session"], {
"root-session": "direct",
@@ -244,8 +244,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "appended-mismatch-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "appended-mismatch-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session", "appended-mismatch-session"], {
"root-session": "direct",
@@ -279,8 +279,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "appended-unresolved-lineage-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "appended-unresolved-lineage-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session", "ses_appended_descendant"], {
"root-session": "direct",
@@ -311,8 +311,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "direct-tracked-child-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "direct-tracked-child-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["ses_direct_child"])
@@ -338,8 +338,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "multi-tracked-direct-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "multi-tracked-direct-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["ses_other_tracked", "ses_direct_tracked"], {
"ses_other_tracked": "direct",
@@ -368,8 +368,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "unknown-origin-multi-session-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "unknown-origin-multi-session-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["ses_root_tracked", "ses_unknown_child"])
@@ -392,8 +392,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "multi-tracked-direct-child-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "multi-tracked-direct-child-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["ses_root_tracked", "ses_direct_child"], {
"ses_root_tracked": "direct",
@@ -427,8 +427,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "compaction-descendant-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "compaction-descendant-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session", "ses_child_after_compaction"], {
"root-session": "direct",
@@ -466,8 +466,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "sqlite-ordered-descendant-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "sqlite-ordered-descendant-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["root-session"])
@@ -502,8 +502,8 @@ describe("checkCompletionConditions continuation coverage", () => {
// given
spyOn(console, "log").mockImplementation(() => {})
const directory = createTempDir()
const planPath = join(directory, ".sisyphus", "plans", "session-agent-fallback-plan.md")
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
const planPath = join(directory, ".omo", "plans", "session-agent-fallback-plan.md")
mkdirSync(join(directory, ".omo", "plans"), { recursive: true })
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
writeBoulderStateFile(directory, planPath, ["ses_root_tracked", "ses_appended_child"], {
"ses_root_tracked": "direct",
@@ -68,12 +68,12 @@ describe("getContinuationState JSON backend descendant coverage", () => {
test("returns active boulder for explicitly tracked appended descendant on JSON message storage backend", async () => {
// given
const directory = createTempDir()
const plansDir = join(directory, ".sisyphus", "plans")
const plansDir = join(directory, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "json-descendant-plan.md")
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
mkdirSync(join(directory, ".sisyphus"), { recursive: true })
writeFileSync(join(directory, ".sisyphus", "boulder.json"), JSON.stringify({
mkdirSync(join(directory, ".omo"), { recursive: true })
writeFileSync(join(directory, ".omo", "boulder.json"), JSON.stringify({
active_plan: planPath,
started_at: new Date().toISOString(),
session_ids: ["ses_root_session", "ses_child_session"],
@@ -134,12 +134,12 @@ describe("getContinuationState JSON backend descendant coverage", () => {
test("prefers newest JSON agent by time.created even when filenames look reversed and timestamps tie-break by filename only", async () => {
// given
const directory = createTempDir()
const plansDir = join(directory, ".sisyphus", "plans")
const plansDir = join(directory, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "json-random-id-plan.md")
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
mkdirSync(join(directory, ".sisyphus"), { recursive: true })
writeFileSync(join(directory, ".sisyphus", "boulder.json"), JSON.stringify({
mkdirSync(join(directory, ".omo"), { recursive: true })
writeFileSync(join(directory, ".omo", "boulder.json"), JSON.stringify({
active_plan: planPath,
started_at: new Date().toISOString(),
session_ids: ["ses_root_random"],
+2 -2
View File
@@ -34,7 +34,7 @@ interface BoulderState {
| File | Purpose |
|------|---------|
| `types.ts` | `BoulderState`, `BoulderWorkState`, `TaskSessionState`, status enums |
| `storage.ts` | Atomic CRUD on `.sisyphus/boulder-state.json`. Writes via temp file + rename; file lock per work_id |
| `storage.ts` | Atomic CRUD on `.omo/boulder-state.json`. Writes via temp file + rename; file lock per work_id |
| `constants.ts` | Path resolution + schema version constant |
| `top-level-task.ts` | Helpers to identify the current top-level plan task and resolve its reusable subagent session |
| `format-duration.ts` | `formatDurationHuman(ms)` — "1h 23m 5s" formatting for boulder duration |
@@ -67,7 +67,7 @@ session.completed
## STORAGE
```
<worktree-root>/.sisyphus/boulder-state.json # gitignored; one file per worktree
<worktree-root>/.omo/boulder-state.json # gitignored; one file per worktree
```
Atomic writes: temp file → fsync (where supported) → rename. File lock prevents concurrent corruption. Schema migrations between versions handled inline in `storage.ts`.
+2 -2
View File
@@ -2,7 +2,7 @@
* Boulder State Constants
*/
export const BOULDER_DIR = ".sisyphus"
export const BOULDER_DIR = ".omo"
export const BOULDER_FILE = "boulder.json"
export const BOULDER_STATE_PATH = `${BOULDER_DIR}/${BOULDER_FILE}`
@@ -10,4 +10,4 @@ export const NOTEPAD_DIR = "notepads"
export const NOTEPAD_BASE_PATH = `${BOULDER_DIR}/${NOTEPAD_DIR}`
/** Prometheus plan directory pattern */
export const PROMETHEUS_PLANS_DIR = ".sisyphus/plans"
export const PROMETHEUS_PLANS_DIR = ".omo/plans"
+31 -31
View File
@@ -34,14 +34,14 @@ import { readCurrentTopLevelTask } from "./top-level-task"
describe("boulder-state", () => {
const TEST_DIR = join(tmpdir(), "boulder-state-test-" + Date.now())
const SISYPHUS_DIR = join(TEST_DIR, ".sisyphus")
const OMO_DIR = join(TEST_DIR, ".omo")
beforeEach(() => {
if (!existsSync(TEST_DIR)) {
mkdirSync(TEST_DIR, { recursive: true })
}
if (!existsSync(SISYPHUS_DIR)) {
mkdirSync(SISYPHUS_DIR, { recursive: true })
if (!existsSync(OMO_DIR)) {
mkdirSync(OMO_DIR, { recursive: true })
}
clearBoulderState(TEST_DIR)
})
@@ -55,7 +55,7 @@ describe("boulder-state", () => {
describe("readBoulderState", () => {
test("should preserve legacy boulder.json fields during round-trip", () => {
// given
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
const legacyRawState = {
active_plan: "/path/to/legacy-plan.md",
started_at: "2026-01-01T00:00:00.000Z",
@@ -88,7 +88,7 @@ describe("boulder-state", () => {
test("should return null for JSON null value", () => {
//#given - boulder.json containing null
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, "null")
//#when
@@ -100,7 +100,7 @@ describe("boulder-state", () => {
test("should return null for JSON primitive value", () => {
//#given - boulder.json containing a string
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, '"just a string"')
//#when
@@ -112,7 +112,7 @@ describe("boulder-state", () => {
test("should default session_ids to [] when missing from JSON", () => {
//#given - boulder.json without session_ids field
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/path/to/plan.md",
started_at: "2026-01-01T00:00:00Z",
@@ -129,7 +129,7 @@ describe("boulder-state", () => {
test("should default session_ids to [] when not an array", () => {
//#given - boulder.json with session_ids as a string
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/path/to/plan.md",
started_at: "2026-01-01T00:00:00Z",
@@ -147,7 +147,7 @@ describe("boulder-state", () => {
test("should default session_ids to [] for empty object", () => {
//#given - boulder.json with empty object
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({}))
//#when
@@ -160,7 +160,7 @@ describe("boulder-state", () => {
test("should backfill missing origin as direct only for a single tracked session", () => {
// given
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/path/to/plan.md",
started_at: "2026-01-01T00:00:00Z",
@@ -177,7 +177,7 @@ describe("boulder-state", () => {
test("should keep missing origins empty when multiple sessions are tracked", () => {
// given
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/path/to/plan.md",
started_at: "2026-01-01T00:00:00Z",
@@ -213,7 +213,7 @@ describe("boulder-state", () => {
test("should default task_sessions to empty object when missing from JSON", () => {
// given - boulder.json without task_sessions field
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/path/to/plan.md",
started_at: "2026-01-01T00:00:00Z",
@@ -231,7 +231,7 @@ describe("boulder-state", () => {
})
describe("writeBoulderState", () => {
test("should write state and create .sisyphus directory if needed", () => {
test("should write state and create .omo directory if needed", () => {
// given - state to write
const state: BoulderState = {
active_plan: "/test/plan.md",
@@ -298,7 +298,7 @@ describe("boulder-state", () => {
test("should not crash when boulder.json has no session_ids field", () => {
//#given - boulder.json without session_ids
const boulderFile = join(SISYPHUS_DIR, "boulder.json")
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/plan.md",
started_at: "2026-01-01T00:00:00Z",
@@ -430,7 +430,7 @@ describe("boulder-state", () => {
test("should add second work and keep both active works", () => {
// given
const firstState = createBoulderState(
join(TEST_DIR, ".sisyphus/plans/plan-a.md"),
join(TEST_DIR, ".omo/plans/plan-a.md"),
"session-a",
"atlas",
"/worktree-a",
@@ -440,7 +440,7 @@ describe("boulder-state", () => {
// when
const updatedState = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
planPath: join(TEST_DIR, ".omo/plans/plan-b.md"),
sessionId: "session-b",
agent: "atlas",
worktreePath: "/worktree-b",
@@ -459,12 +459,12 @@ describe("boulder-state", () => {
test("should resolve work for session using updated_at tie-break", () => {
// given
const baseState = createBoulderState(
join(TEST_DIR, ".sisyphus/plans/plan-a.md"),
join(TEST_DIR, ".omo/plans/plan-a.md"),
"session-a",
)
writeBoulderState(TEST_DIR, baseState)
const stateWithSecond = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
planPath: join(TEST_DIR, ".omo/plans/plan-b.md"),
sessionId: "session-b",
})
expect(stateWithSecond).not.toBeNull()
@@ -486,10 +486,10 @@ describe("boulder-state", () => {
test("should support selecting active work and read helpers", () => {
// given
const initialState = createBoulderState(join(TEST_DIR, ".sisyphus/plans/plan-a.md"), "session-a")
const initialState = createBoulderState(join(TEST_DIR, ".omo/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const added = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
planPath: join(TEST_DIR, ".omo/plans/plan-b.md"),
sessionId: "session-b",
worktreePath: "/tmp/worktree-b",
})
@@ -516,7 +516,7 @@ describe("boulder-state", () => {
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")
const initialState = createBoulderState(join(TEST_DIR, ".omo/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const workId = initialState.active_work_id!
@@ -550,7 +550,7 @@ describe("boulder-state", () => {
describe("task timer and completion helpers", () => {
test("should keep started_at stable when starting timer repeatedly", () => {
// given
const initialState = createBoulderState(join(TEST_DIR, ".sisyphus/plans/plan-a.md"), "session-a")
const initialState = createBoulderState(join(TEST_DIR, ".omo/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const workId = initialState.active_work_id!
@@ -578,7 +578,7 @@ describe("boulder-state", () => {
test("should compute elapsed_ms when ending task timer", () => {
// given
const initialState = createBoulderState(join(TEST_DIR, ".sisyphus/plans/plan-a.md"), "session-a")
const initialState = createBoulderState(join(TEST_DIR, ".omo/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const workId = initialState.active_work_id!
startTaskTimer(TEST_DIR, workId, {
@@ -601,11 +601,11 @@ describe("boulder-state", () => {
test("should complete one work and keep other work untouched", () => {
// given
const initialState = createBoulderState(join(TEST_DIR, ".sisyphus/plans/plan-a.md"), "session-a")
const initialState = createBoulderState(join(TEST_DIR, ".omo/plans/plan-a.md"), "session-a")
writeBoulderState(TEST_DIR, initialState)
const firstWorkId = initialState.active_work_id!
const withSecond = addBoulderWork(TEST_DIR, {
planPath: join(TEST_DIR, ".sisyphus/plans/plan-b.md"),
planPath: join(TEST_DIR, ".omo/plans/plan-b.md"),
sessionId: "session-b",
})
const secondWorkId = Object.keys(withSecond!.works!).find((workId) => workId !== firstWorkId)!
@@ -620,13 +620,13 @@ describe("boulder-state", () => {
Date.parse("2026-01-01T01:00:00.000Z") - Date.parse(completedState!.works![firstWorkId]!.started_at),
)
expect(completedState?.works?.[secondWorkId]?.status).not.toBe("completed")
expect(existsSync(join(SISYPHUS_DIR, "boulder.json"))).toBe(true)
expect(existsSync(join(OMO_DIR, "boulder.json"))).toBe(true)
})
test("should keep first completion timing when completeBoulder is called repeatedly", () => {
// given
const initialState = createBoulderState(
join(TEST_DIR, ".sisyphus/plans/plan-idempotent.md"),
join(TEST_DIR, ".omo/plans/plan-idempotent.md"),
"session-a",
)
writeBoulderState(TEST_DIR, initialState)
@@ -974,7 +974,7 @@ describe("boulder-state", () => {
describe("getPlanName", () => {
test("should extract plan name from path", () => {
// given
const path = "/home/user/.sisyphus/plans/project/my-feature.md"
const path = "/home/user/.omo/plans/project/my-feature.md"
// when
const name = getPlanName(path)
// then
@@ -1042,9 +1042,9 @@ describe("boulder-state", () => {
describe("resolveBoulderPlanPath", () => {
test("should prefer the mirrored worktree plan when it exists", () => {
// given
const planPath = join(TEST_DIR, ".sisyphus", "plans", "worktree-plan.md")
const planPath = join(TEST_DIR, ".omo", "plans", "worktree-plan.md")
const worktreeDir = join(tmpdir(), `boulder-state-worktree-${Date.now()}`)
const worktreePlanPath = join(worktreeDir, ".sisyphus", "plans", "worktree-plan.md")
const worktreePlanPath = join(worktreeDir, ".omo", "plans", "worktree-plan.md")
mkdirSync(dirname(planPath), { recursive: true })
mkdirSync(dirname(worktreePlanPath), { recursive: true })
writeFileSync(planPath, "# Plan\n- [ ] Main repo task\n")
@@ -1066,7 +1066,7 @@ describe("boulder-state", () => {
test("should fall back to the tracked plan when the mirrored worktree plan is missing", () => {
// given
const planPath = join(TEST_DIR, ".sisyphus", "plans", "fallback-plan.md")
const planPath = join(TEST_DIR, ".omo", "plans", "fallback-plan.md")
mkdirSync(dirname(planPath), { recursive: true })
writeFileSync(planPath, "# Plan\n- [ ] Main repo task\n")
+1 -1
View File
@@ -348,7 +348,7 @@ export function upsertTaskSessionState(
/**
* Find Prometheus plan files for this project.
* Prometheus stores plans at: {project}/.sisyphus/plans/{name}.md
* Prometheus stores plans at: {project}/.omo/plans/{name}.md
*/
export function findPrometheusPlans(directory: string): string[] {
const plansDir = join(directory, PROMETHEUS_PLANS_DIR)
@@ -11,9 +11,9 @@ export const START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
## WHAT TO DO
1. **Find available plans**: Search for Prometheus-generated plan files at \`.sisyphus/plans/\`
1. **Find available plans**: Search for Prometheus-generated plan files at \`.omo/plans/\`
2. **Check for active boulder state**: Read \`.sisyphus/boulder.json\` if it exists
2. **Check for active boulder state**: Read \`.omo/boulder.json\` if it exists
3. **Decision logic**:
- If multiple active works are listed in your context:
@@ -119,10 +119,10 @@ Register these as task/todo items so progress is tracked and visible throughout
When working in a worktree (\`worktree_path\` is set in boulder.json) and ALL plan tasks are complete:
1. Commit all remaining changes in the worktree
2. **Sync .sisyphus state back**: Copy \`.sisyphus/\` from the worktree to the main repo before removal.
This is CRITICAL when \`.sisyphus/\` is gitignored - state written during worktree execution would otherwise be lost.
2. **Sync .omo state back**: Copy \`.omo/\` from the worktree to the main repo before removal.
This is CRITICAL when \`.omo/\` is gitignored - state written during worktree execution would otherwise be lost.
\`\`\`bash
cp -r <worktree-path>/.sisyphus/* <main-repo>/.sisyphus/ 2>/dev/null || true
cp -r <worktree-path>/.omo/* <main-repo>/.omo/ 2>/dev/null || true
\`\`\`
3. Switch to the main working directory (the original repo, NOT the worktree)
4. Merge the worktree branch into the current branch: \`git merge <worktree-branch>\`
@@ -1 +1 @@
export const CONTINUATION_MARKER_DIR = ".sisyphus/run-continuation"
export const CONTINUATION_MARKER_DIR = ".omo/run-continuation"
@@ -26,7 +26,7 @@ describe("buildStartWorkContextInfo", () => {
}
function writePlan(planName: string, content: string): string {
const plansDirectory = join(testDirectory, ".sisyphus", "plans")
const plansDirectory = join(testDirectory, ".omo", "plans")
mkdirSync(plansDirectory, { recursive: true })
const planPath = join(plansDirectory, `${planName}.md`)
writeFileSync(planPath, content)
@@ -176,7 +176,7 @@ describe("buildStartWorkContextInfo", () => {
writePlan("new-plan-c", "## TODOs\n- [ ] 1. Work C")
const initialState = createBoulderState(
join(testDirectory, ".sisyphus", "plans", "work-a.md"),
join(testDirectory, ".omo", "plans", "work-a.md"),
"session-a",
"atlas",
"/tmp/worktree-a",
+1 -1
View File
@@ -328,7 +328,7 @@ function buildPlanDiscoveryContext(params: {
return contextInfo + `
## No Plans Found
No Prometheus plan files found in the .sisyphus plans directory.
No Prometheus plan files found in the .omo plans directory.
Use the Prometheus agent to create a work plan first.`
}
+25 -25
View File
@@ -20,7 +20,7 @@ import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
describe("start-work hook", () => {
let testDir: string
let sisyphusDir: string
let omoDir: string
function createMockPluginInput() {
return {
@@ -50,12 +50,12 @@ You are starting a Sisyphus work session.
sessionState.registerAgentName("atlas")
sessionState.registerAgentName("sisyphus")
testDir = join(tmpdir(), `start-work-test-${randomUUID()}`)
sisyphusDir = join(testDir, ".sisyphus")
omoDir = join(testDir, ".omo")
if (!existsSync(testDir)) {
mkdirSync(testDir, { recursive: true })
}
if (!existsSync(sisyphusDir)) {
mkdirSync(sisyphusDir, { recursive: true })
if (!existsSync(omoDir)) {
mkdirSync(omoDir, { recursive: true })
}
clearBoulderState(testDir)
})
@@ -224,7 +224,7 @@ You are starting a Sisyphus work session.
test("should auto-select when only one incomplete plan among multiple plans", async () => {
// given - multiple plans but only one incomplete
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
// Plan 1: complete (all checked)
@@ -254,7 +254,7 @@ You are starting a Sisyphus work session.
test("should wrap multiple plans message in system-reminder tag", async () => {
// given - multiple incomplete plans
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const plan1Path = join(plansDir, "plan-a.md")
@@ -282,7 +282,7 @@ You are starting a Sisyphus work session.
test("should use 'ask user' prompt style for multiple plans", async () => {
// given - multiple incomplete plans
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const plan1Path = join(plansDir, "plan-x.md")
@@ -309,7 +309,7 @@ You are starting a Sisyphus work session.
test("should select explicitly specified plan name from user-request, ignoring existing boulder state", async () => {
// given - existing boulder state pointing to old plan
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
// Old plan (in boulder state)
@@ -353,7 +353,7 @@ You are starting a Sisyphus work session.
test("should strip ultrawork/ulw keywords from plan name argument", async () => {
// given - plan with ultrawork keyword in user-request
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "my-feature-plan.md")
@@ -382,7 +382,7 @@ You are starting a Sisyphus work session.
test("should strip ulw keyword from plan name argument", async () => {
// given - plan with ulw keyword in user-request
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "api-refactor.md")
@@ -411,7 +411,7 @@ You are starting a Sisyphus work session.
test("should match plan by partial name", async () => {
// given - user specifies partial plan name
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "2026-01-15-feature-implementation.md")
@@ -440,7 +440,7 @@ You are starting a Sisyphus work session.
test("should match quoted human-readable plan names to slugged filenames", async () => {
// given - saved plan uses a slugged filename
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "my-feature-plan.md")
@@ -469,7 +469,7 @@ You are starting a Sisyphus work session.
test("should match Korean plan names after Unicode-aware normalization", async () => {
// given
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "결제-플로우.md")
@@ -498,7 +498,7 @@ You are starting a Sisyphus work session.
test("should match Japanese plan names after Unicode-aware normalization", async () => {
// given
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "支払い-フロー.md")
@@ -527,7 +527,7 @@ You are starting a Sisyphus work session.
test("should keep ASCII plan name matching behavior unchanged", async () => {
// given
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "checkout-flow.md")
@@ -556,7 +556,7 @@ You are starting a Sisyphus work session.
test("should match mixed ASCII and non-ASCII plan names", async () => {
// given
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
const planPath = join(plansDir, "v2-결제-flow.md")
@@ -674,7 +674,7 @@ You are starting a Sisyphus work session.
sessionState.registerAgentName("sisyphus")
sessionState.updateSessionAgent("ses-prometheus-to-worker", "prometheus")
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "worker-plan.md"), "# Plan\n- [ ] Task 1")
@@ -732,7 +732,7 @@ You are starting a Sisyphus work session.
test("#given start-work hands the session to Atlas #when Atlas later receives session.idle #then the same session continues the selected plan", async () => {
// given
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "atlas-plan.md"), "# Plan\n- [ ] Task 1\n- [ ] Task 2")
@@ -770,7 +770,7 @@ You are starting a Sisyphus work session.
test("#given start-work hands the session to Atlas but background work is still running #when that work finishes #then Atlas resumes via retry for the same session", async () => {
// given
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "atlas-plan.md"), "# Plan\n- [ ] Task 1\n- [ ] Task 2")
@@ -877,7 +877,7 @@ You are starting a Sisyphus work session.
test("should NOT inject worktree instructions when no --worktree flag", async () => {
// given - single plan, no worktree flag
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1")
@@ -897,7 +897,7 @@ You are starting a Sisyphus work session.
test("should inject worktree path when --worktree flag is valid", async () => {
// given - single plan + valid worktree path
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1")
detectSpy.mockReturnValue("/validated/worktree")
@@ -919,7 +919,7 @@ You are starting a Sisyphus work session.
test("should store worktree_path in boulder when --worktree is valid", async () => {
// given - plan + valid worktree
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1")
detectSpy.mockReturnValue("/valid/wt")
@@ -939,7 +939,7 @@ You are starting a Sisyphus work session.
test("should NOT store worktree_path when --worktree path is invalid", async () => {
// given - plan + invalid worktree path (detectWorktreePath returns null)
const plansDir = join(testDir, ".sisyphus", "plans")
const plansDir = join(testDir, ".omo", "plans")
mkdirSync(plansDir, { recursive: true })
writeFileSync(join(plansDir, "my-plan.md"), "# Plan\n- [ ] Task 1")
// detectSpy already returns null by default
@@ -1017,9 +1017,9 @@ You are starting a Sisyphus work session.
test("should show worktree plan progress and path when the mirrored plan exists", async () => {
// given
const mainPlanPath = join(testDir, ".sisyphus", "plans", "resume-worktree-plan.md")
const mainPlanPath = join(testDir, ".omo", "plans", "resume-worktree-plan.md")
const worktreeDir = join(testDir, "..", `resume-worktree-${randomUUID()}`)
const worktreePlanPath = join(worktreeDir, ".sisyphus", "plans", "resume-worktree-plan.md")
const worktreePlanPath = join(worktreeDir, ".omo", "plans", "resume-worktree-plan.md")
mkdirSync(dirname(mainPlanPath), { recursive: true })
mkdirSync(dirname(worktreePlanPath), { recursive: true })
writeFileSync(mainPlanPath, "# Plan\n- [ ] Main repo task\n")
+21 -21
View File
@@ -11,7 +11,7 @@ describe("createTaskList", () => {
let taskDir: string
beforeEach(() => {
taskDir = join(testProjectDir, ".sisyphus/tasks")
taskDir = join(testProjectDir, ".omo/tasks")
if (existsSync(taskDir)) {
rmSync(taskDir, { recursive: true })
}
@@ -28,7 +28,7 @@ describe("createTaskList", () => {
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -64,13 +64,13 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task1)
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-2.json"), task2)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-1.json"), task1)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-2.json"), task2)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -107,13 +107,13 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task1)
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-2.json"), task2)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-1.json"), task1)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-2.json"), task2)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -142,12 +142,12 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-1.json"), task)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -204,14 +204,14 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-blocker-completed.json"), blockerCompleted)
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-blocker-pending.json"), blockerPending)
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-main.json"), mainTask)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-blocker-completed.json"), blockerCompleted)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-blocker-pending.json"), blockerPending)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-main.json"), mainTask)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -248,13 +248,13 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task1)
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-2.json"), task2)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-1.json"), task1)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-2.json"), task2)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -281,12 +281,12 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-1.json"), task)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},
@@ -313,12 +313,12 @@ describe("createTaskList", () => {
threadID: "test-session",
}
writeJsonAtomic(join(testProjectDir, ".sisyphus/tasks", "T-1.json"), task)
writeJsonAtomic(join(testProjectDir, ".omo/tasks", "T-1.json"), task)
const config = {
sisyphus: {
tasks: {
storage_path: join(testProjectDir, ".sisyphus/tasks"),
storage_path: join(testProjectDir, ".omo/tasks"),
claude_code_compat: false,
},
},