feat(workspace): point planning guardrails at omo

This commit is contained in:
YeonGyu-Kim
2026-05-16 17:41:35 +09:00
parent a86221b1a9
commit 36e373cdbb
48 changed files with 244 additions and 244 deletions
@@ -113,7 +113,7 @@ describe("Atlas final-wave approval gate regressions", () => {
beforeEach(() => {
testDirectory = join(tmpdir(), `atlas-final-wave-regression-${randomUUID()}`)
mkdirSync(join(testDirectory, ".sisyphus"), { recursive: true })
mkdirSync(join(testDirectory, ".omo"), { recursive: true })
clearBoulderState(testDirectory)
})
@@ -66,7 +66,7 @@ describe("Atlas final verification approval gate", () => {
beforeEach(() => {
testDirectory = join(tmpdir(), `atlas-final-wave-test-${randomUUID()}`)
mkdirSync(join(testDirectory, ".sisyphus"), { recursive: true })
mkdirSync(join(testDirectory, ".omo"), { recursive: true })
clearBoulderState(testDirectory)
})
+20 -20
View File
@@ -25,7 +25,7 @@ type MockAtlasInput = Parameters<typeof createAtlasHook>[0] & {
describe("atlas hook", () => {
let TEST_DIR: string
let SISYPHUS_DIR: string
let OMO_DIR: string
function createMockPluginInput(overrides?: {
promptMock?: ReturnType<typeof mock>
@@ -81,12 +81,12 @@ describe("atlas hook", () => {
registerAgentName("atlas")
registerAgentName("sisyphus")
TEST_DIR = join(tmpdir(), `atlas-test-${randomUUID()}`)
SISYPHUS_DIR = join(TEST_DIR, ".sisyphus")
OMO_DIR = join(TEST_DIR, ".omo")
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)
callerAgentBySession.clear()
@@ -1082,7 +1082,7 @@ session_id: ses_untrusted_999
cleanupMessageStorage(ORCHESTRATOR_SESSION)
})
test("should append delegation reminder when orchestrator writes outside .sisyphus/", async () => {
test("should append delegation reminder when orchestrator writes outside .omo/", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const output = {
@@ -1103,7 +1103,7 @@ session_id: ses_untrusted_999
expect(output.output).toContain("task")
})
test("should append delegation reminder when orchestrator edits outside .sisyphus/", async () => {
test("should append delegation reminder when orchestrator edits outside .omo/", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const output = {
@@ -1122,14 +1122,14 @@ session_id: ses_untrusted_999
expect(output.output).toContain("DELEGATION REQUIRED")
})
test("should NOT append reminder when orchestrator writes inside .sisyphus/", async () => {
test("should NOT append reminder when orchestrator writes inside .omo/", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const originalOutput = "File written successfully"
const output = {
title: "Write",
output: originalOutput,
metadata: { filePath: "/project/.sisyphus/plans/work-plan.md" },
metadata: { filePath: "/project/.omo/plans/work-plan.md" },
}
// when
@@ -1143,7 +1143,7 @@ session_id: ses_untrusted_999
expect(output.output).not.toContain("DELEGATION REQUIRED")
})
test("should NOT append reminder when non-orchestrator writes outside .sisyphus/", async () => {
test("should NOT append reminder when non-orchestrator writes outside .omo/", async () => {
// given
const nonOrchestratorSession = "non-orchestrator-session"
setupMessageStorage(nonOrchestratorSession, "sisyphus-junior")
@@ -1210,14 +1210,14 @@ session_id: ses_untrusted_999
})
describe("cross-platform path validation (Windows support)", () => {
test("should NOT append reminder when orchestrator writes inside .sisyphus\\ (Windows backslash)", async () => {
test("should NOT append reminder when orchestrator writes inside .omo\\ (Windows backslash)", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const originalOutput = "File written successfully"
const output = {
title: "Write",
output: originalOutput,
metadata: { filePath: ".sisyphus\\plans\\work-plan.md" },
metadata: { filePath: ".omo\\plans\\work-plan.md" },
}
// when
@@ -1231,14 +1231,14 @@ session_id: ses_untrusted_999
expect(output.output).not.toContain("DELEGATION REQUIRED")
})
test("should NOT append reminder when orchestrator writes inside .sisyphus with mixed separators", async () => {
test("should NOT append reminder when orchestrator writes inside .omo with mixed separators", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const originalOutput = "File written successfully"
const output = {
title: "Write",
output: originalOutput,
metadata: { filePath: ".sisyphus\\plans/work-plan.md" },
metadata: { filePath: ".omo\\plans/work-plan.md" },
}
// when
@@ -1252,14 +1252,14 @@ session_id: ses_untrusted_999
expect(output.output).not.toContain("DELEGATION REQUIRED")
})
test("should NOT append reminder for absolute Windows path inside .sisyphus\\", async () => {
test("should NOT append reminder for absolute Windows path inside .omo\\", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const originalOutput = "File written successfully"
const output = {
title: "Write",
output: originalOutput,
metadata: { filePath: "C:\\Users\\test\\project\\.sisyphus\\plans\\x.md" },
metadata: { filePath: "C:\\Users\\test\\project\\.omo\\plans\\x.md" },
}
// when
@@ -1273,7 +1273,7 @@ session_id: ses_untrusted_999
expect(output.output).not.toContain("DELEGATION REQUIRED")
})
test("should append reminder for Windows path outside .sisyphus\\", async () => {
test("should append reminder for Windows path outside .omo\\", async () => {
// given
const hook = createTestAtlasHook(createMockPluginInput())
const output = {
@@ -1553,11 +1553,11 @@ session_id: ses_untrusted_999
test("should inject completion nudge when mirrored worktree plan is complete even if the main repo plan is stale", async () => {
// given
const mainPlanPath = join(TEST_DIR, ".sisyphus", "plans", "worktree-complete-plan.md")
const mainPlanPath = join(TEST_DIR, ".omo", "plans", "worktree-complete-plan.md")
const worktreeDir = join(tmpdir(), `atlas-worktree-${randomUUID()}`)
const worktreePlanPath = join(worktreeDir, ".sisyphus", "plans", "worktree-complete-plan.md")
mkdirSync(join(TEST_DIR, ".sisyphus", "plans"), { recursive: true })
mkdirSync(join(worktreeDir, ".sisyphus", "plans"), { recursive: true })
const worktreePlanPath = join(worktreeDir, ".omo", "plans", "worktree-complete-plan.md")
mkdirSync(join(TEST_DIR, ".omo", "plans"), { recursive: true })
mkdirSync(join(worktreeDir, ".omo", "plans"), { recursive: true })
writeFileSync(mainPlanPath, "# Plan\n- [ ] Main repo task\n")
writeFileSync(worktreePlanPath, "# Plan\n- [x] Worktree task\n")
+8
View File
@@ -0,0 +1,8 @@
/**
* Cross-platform check if a path is inside .omo/ directory.
* Handles both forward slashes (Unix) and backslashes (Windows).
* Uses path segment matching instead of substring matching.
*/
export function isOmoPath(filePath: string): boolean {
return /\.omo[/\\]/.test(filePath)
}
@@ -99,9 +99,9 @@ describe("resolveActiveBoulderSession", () => {
test("returns complete progress when a mirrored worktree plan is complete", async () => {
// given
const mainPlanPath = join(testDirectory, ".sisyphus", "plans", "worktree-plan.md")
const mainPlanPath = join(testDirectory, ".omo", "plans", "worktree-plan.md")
const worktreeDirectory = join(tmpdir(), `resolve-active-boulder-worktree-${randomUUID()}`)
const worktreePlanPath = join(worktreeDirectory, ".sisyphus", "plans", "worktree-plan.md")
const worktreePlanPath = join(worktreeDirectory, ".omo", "plans", "worktree-plan.md")
mkdirSync(dirname(mainPlanPath), { recursive: true })
mkdirSync(dirname(worktreePlanPath), { recursive: true })
writeFileSync(mainPlanPath, "# Plan\n- [ ] Main repo task\n", "utf-8")
-8
View File
@@ -1,8 +0,0 @@
/**
* Cross-platform check if a path is inside .sisyphus/ directory.
* Handles both forward slashes (Unix) and backslashes (Windows).
* Uses path segment matching (not substring) to avoid false positives like "not-sisyphus/file.txt"
*/
export function isSisyphusPath(filePath: string): boolean {
return /\.sisyphus[/\\]/.test(filePath)
}
+2 -2
View File
@@ -29,7 +29,7 @@ You have an active work plan with incomplete tasks. Continue working.
RULES:
- **FIRST**: Read the plan file NOW. If the last completed task is still unchecked, mark it \`- [x]\` IMMEDIATELY before anything else
- Proceed without asking for permission
- Use the notepad at .sisyphus/notepads/{PLAN_NAME}/ to record learnings
- Use the notepad at .omo/notepads/{PLAN_NAME}/ to record learnings
- Do not stop until all tasks are complete
- If blocked, document the blocker and move to the next task`
@@ -203,7 +203,7 @@ task(
\`\`\`
Allowed direct operations:
- \`.sisyphus/\` files (plans, notepads)
- \`.omo/\` files (plans, notepads)
- Reading any file (verification)
- Running commands (verification)
@@ -227,7 +227,7 @@ describe("createToolExecuteAfterHandler task timers", () => {
it("ends task timer when plan checkbox flips to checked via edit tool", async () => {
// given
const parentSessionID = "ses_parent_3"
const planDirectory = join(testDirectory, ".sisyphus", "plans")
const planDirectory = join(testDirectory, ".omo", "plans")
mkdirSync(planDirectory, { recursive: true })
const planPath = join(planDirectory, "task-timer-edit-plan.md")
writeFileSync(planPath, "# Plan\n\n## TODOs\n- [ ] 1. Implement auth flow\n", "utf-8")
+2 -2
View File
@@ -19,7 +19,7 @@ import { collectGitDiffStats, formatFileChanges } from "../../shared/git-worktre
import { shouldPauseForFinalWaveApproval } from "./final-wave-approval-gate"
import { HOOK_NAME } from "./hook-name"
import { DIRECT_WORK_REMINDER } from "./system-reminder-templates"
import { isSisyphusPath } from "./sisyphus-path"
import { isOmoPath } from "./omo-path"
import { resolvePreferredSessionId, resolveTaskContext } from "./task-context"
import { extractSessionIdFromMetadata, extractSessionIdFromOutput, validateSubagentSessionId } from "./subagent-session-id"
import {
@@ -175,7 +175,7 @@ export function createToolExecuteAfterHandler(input: {
}
}
if (filePath && !isSisyphusPath(filePath)) {
if (filePath && !isOmoPath(filePath)) {
toolOutput.output = (toolOutput.output || "") + DIRECT_WORK_REMINDER
log(`[${HOOK_NAME}] Direct work reminder appended`, {
sessionID: toolInput.sessionID,
+2 -2
View File
@@ -7,7 +7,7 @@ import { resolve } from "node:path"
import { getWorkForSession, readBoulderState, readCurrentTopLevelTask, resolveBoulderPlanPath, resolveBoulderPlanPathForWork } from "../../features/boulder-state"
import { HOOK_NAME } from "./hook-name"
import { ORCHESTRATOR_DELEGATION_REQUIRED, SINGLE_TASK_DIRECTIVE } from "./system-reminder-templates"
import { isSisyphusPath } from "./sisyphus-path"
import { isOmoPath } from "./omo-path"
import type { PendingTaskRef, TrackedTopLevelTaskRef } from "./types"
import { isWriteOrEditToolName } from "./write-edit-tool-policy"
@@ -108,7 +108,7 @@ export function createToolExecuteBeforeHandler(input: {
}
}
if (!isSisyphusPath(filePath)) {
if (!isOmoPath(filePath)) {
const warning = ORCHESTRATOR_DELEGATION_REQUIRED.replace("$FILE_PATH", filePath)
toolOutput.message = (toolOutput.message || "") + warning
log(`[${HOOK_NAME}] Injected delegation warning for direct file modification`, {
@@ -26,7 +26,7 @@ describe("buildCompletionGate", () => {
then("gate interpolates the plan name path", () => {
expect(gate).toContain(planName)
expect(gate).toContain(`.sisyphus/plans/${planName}.md`)
expect(gate).toContain(`.omo/plans/${planName}.md`)
})
then("gate includes Edit instructions", () => {
+5 -5
View File
@@ -15,13 +15,13 @@ export function buildCompletionGate(planName: string, sessionId: string): string
Your completion will NOT be recorded until you complete ALL of the following:
1. **Edit** the plan file \`.sisyphus/plans/${planName}.md\`:
1. **Edit** the plan file \`.omo/plans/${planName}.md\`:
- Change \`- [ ]\` to \`- [x]\` for the completed task
- Use \`Edit\` tool to modify the checkbox
2. **Read** the plan file AGAIN:
\`\`\`
Read(".sisyphus/plans/${planName}.md")
Read(".omo/plans/${planName}.md")
\`\`\`
- Verify the checkbox count changed (more \`- [x]\` than before)
@@ -88,7 +88,7 @@ ${includeCompletionGate ? `${buildCompletionGate(planName, sessionId)}
The subagent was instructed to record findings in notepad files. Read them NOW:
\`\`\`
Glob(".sisyphus/notepads/${planName}/*.md")
Glob(".omo/notepads/${planName}/*.md")
\`\`\`
Then \`Read\` each file found - especially:
- **learnings.md**: Patterns, conventions, successful approaches discovered
@@ -104,7 +104,7 @@ Then \`Read\` each file found - especially:
Do NOT rely on cached progress. Read the plan file NOW:
\`\`\`
Read(".sisyphus/plans/${planName}.md")
Read(".omo/plans/${planName}.md")
\`\`\`
Count exactly: how many \`- [ ]\` remain? How many \`- [x]\` completed?
This is YOUR ground truth. Use it to decide what comes next.
@@ -143,7 +143,7 @@ The last Final Verification Wave result just passed.
This is the ONLY point where approval-style user interaction is required.
1. Read \
\`.sisyphus/plans/${planName}.md\` again and confirm every remaining unchecked **top-level** task belongs to F1-F4.
\`.omo/plans/${planName}.md\` again and confirm every remaining unchecked **top-level** task belongs to F1-F4.
Ignore nested checkboxes under Acceptance Criteria, Evidence, or Final Checklist sections.
2. Consolidate the F1-F4 verdicts into a short summary for the user.
3. Tell the user all final reviewers approved.