diff --git a/src/hooks/sisyphus-orchestrator/index.test.ts b/src/hooks/sisyphus-orchestrator/index.test.ts index c5e1f837..73e7d977 100644 --- a/src/hooks/sisyphus-orchestrator/index.test.ts +++ b/src/hooks/sisyphus-orchestrator/index.test.ts @@ -506,6 +506,90 @@ describe("sisyphus-orchestrator hook", () => { // #then expect(output.output).toBe(originalOutput) }) + + describe("cross-platform path validation (Windows support)", () => { + test("should NOT append reminder when orchestrator writes inside .sisyphus\\ (Windows backslash)", async () => { + // #given + const hook = createSisyphusOrchestratorHook(createMockPluginInput()) + const originalOutput = "File written successfully" + const output = { + title: "Write", + output: originalOutput, + metadata: { filePath: ".sisyphus\\plans\\work-plan.md" }, + } + + // #when + await hook["tool.execute.after"]( + { tool: "Write", sessionID: ORCHESTRATOR_SESSION }, + output + ) + + // #then + expect(output.output).toBe(originalOutput) + expect(output.output).not.toContain("DELEGATION REQUIRED") + }) + + test("should NOT append reminder when orchestrator writes inside .sisyphus with mixed separators", async () => { + // #given + const hook = createSisyphusOrchestratorHook(createMockPluginInput()) + const originalOutput = "File written successfully" + const output = { + title: "Write", + output: originalOutput, + metadata: { filePath: ".sisyphus\\plans/work-plan.md" }, + } + + // #when + await hook["tool.execute.after"]( + { tool: "Write", sessionID: ORCHESTRATOR_SESSION }, + output + ) + + // #then + expect(output.output).toBe(originalOutput) + expect(output.output).not.toContain("DELEGATION REQUIRED") + }) + + test("should NOT append reminder for absolute Windows path inside .sisyphus\\", async () => { + // #given + const hook = createSisyphusOrchestratorHook(createMockPluginInput()) + const originalOutput = "File written successfully" + const output = { + title: "Write", + output: originalOutput, + metadata: { filePath: "C:\\Users\\test\\project\\.sisyphus\\plans\\x.md" }, + } + + // #when + await hook["tool.execute.after"]( + { tool: "Write", sessionID: ORCHESTRATOR_SESSION }, + output + ) + + // #then + expect(output.output).toBe(originalOutput) + expect(output.output).not.toContain("DELEGATION REQUIRED") + }) + + test("should append reminder for Windows path outside .sisyphus\\", async () => { + // #given + const hook = createSisyphusOrchestratorHook(createMockPluginInput()) + const output = { + title: "Write", + output: "File written successfully", + metadata: { filePath: "C:\\Users\\test\\project\\src\\code.ts" }, + } + + // #when + await hook["tool.execute.after"]( + { tool: "Write", sessionID: ORCHESTRATOR_SESSION }, + output + ) + + // #then + expect(output.output).toContain("DELEGATION REQUIRED") + }) + }) }) }) diff --git a/src/hooks/sisyphus-orchestrator/index.ts b/src/hooks/sisyphus-orchestrator/index.ts index 570a6747..f50a0e2c 100644 --- a/src/hooks/sisyphus-orchestrator/index.ts +++ b/src/hooks/sisyphus-orchestrator/index.ts @@ -14,7 +14,14 @@ import type { BackgroundManager } from "../../features/background-agent" export const HOOK_NAME = "sisyphus-orchestrator" -const ALLOWED_PATH_PREFIX = ".sisyphus/" +/** + * Cross-platform check if a path is inside .sisyphus/ directory. + * Handles both forward slashes (Unix) and backslashes (Windows). + */ +function isSisyphusPath(filePath: string): boolean { + return /\.sisyphus[/\\]/.test(filePath) +} + const WRITE_EDIT_TOOLS = ["Write", "Edit", "write", "edit"] const DIRECT_WORK_REMINDER = ` @@ -549,7 +556,7 @@ export function createSisyphusOrchestratorHook( // Check Write/Edit tools for orchestrator - inject strong warning if (WRITE_EDIT_TOOLS.includes(input.tool)) { const filePath = (output.args.filePath ?? output.args.path ?? output.args.file) as string | undefined - if (filePath && !filePath.includes(ALLOWED_PATH_PREFIX)) { + if (filePath && !isSisyphusPath(filePath)) { // Store filePath for use in tool.execute.after if (input.callID) { pendingFilePaths.set(input.callID, filePath) @@ -593,7 +600,7 @@ export function createSisyphusOrchestratorHook( if (!filePath) { filePath = output.metadata?.filePath as string | undefined } - if (filePath && !filePath.includes(ALLOWED_PATH_PREFIX)) { + if (filePath && !isSisyphusPath(filePath)) { output.output = (output.output || "") + DIRECT_WORK_REMINDER log(`[${HOOK_NAME}] Direct work reminder appended`, { sessionID: input.sessionID,