feat(workspace): point planning guardrails at omo
This commit is contained in:
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user