Skip to content
Open
2 changes: 1 addition & 1 deletion src/hooks/atlas/atlas-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function createAtlasHook(ctx: PluginInput, options?: AtlasHookOptions) {

return {
handler: createAtlasEventHandler({ ctx, options, sessions, getState }),
"tool.execute.before": createToolExecuteBeforeHandler({ ctx, pendingFilePaths }),
"tool.execute.before": createToolExecuteBeforeHandler({ ctx, pendingFilePaths, autoCommit }),
"tool.execute.after": createToolExecuteAfterHandler({ ctx, pendingFilePaths, autoCommit }),
}
}
116 changes: 116 additions & 0 deletions src/hooks/atlas/tool-execute-before.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { describe, expect, test } from "bun:test"
import { transformPlanCommitFields } from "./tool-execute-before"

describe("transformPlanCommitFields", () => {
describe("#given content with Commit: YES", () => {
test("#when transformed #then becomes Commit: NO (user disabled auto-commits)", () => {
const content = "Commit: YES"
const result = transformPlanCommitFields(content)
expect(result).toBe("Commit: NO (user disabled auto-commits)")
})
})

describe("#given content with Commit: NO", () => {
test("#when transformed #then becomes Commit: NO (user disabled auto-commits)", () => {
const content = "Commit: NO"
const result = transformPlanCommitFields(content)
expect(result).toBe("Commit: NO (user disabled auto-commits)")
})
})

describe("#given content with Commit fields with extra whitespace", () => {
test("#when transformed #then handles whitespace variations", () => {
const content = "Commit: YES"
const result = transformPlanCommitFields(content)
expect(result).toBe("Commit: NO (user disabled auto-commits)")
})
})

describe("#given content with multiple Commit fields", () => {
test("#when transformed #then all Commit fields are transformed", () => {
const content = `
## Task 1
Commit: YES

## Task 2
Commit: NO

## Task 3
Commit: YES
`
const result = transformPlanCommitFields(content)
expect(result).toBe(`
## Task 1
Commit: NO (user disabled auto-commits)

## Task 2
Commit: NO (user disabled auto-commits)

## Task 3
Commit: NO (user disabled auto-commits)
`)
})
})

describe("#given content without Commit fields", () => {
test("#when transformed #then content is unchanged", () => {
const content = `
## Task
- [ ] Do something
- [ ] Do another thing
`
const result = transformPlanCommitFields(content)
expect(result).toBe(content)
})
})

describe("#given already transformed content", () => {
test("#when transformed again #then remains idempotent", () => {
const originalContent = "Commit: YES"
const firstTransform = transformPlanCommitFields(originalContent)
const secondTransform = transformPlanCommitFields(firstTransform)
expect(secondTransform).toBe(firstTransform)
expect(secondTransform).toBe("Commit: NO (user disabled auto-commits)")
})
})

describe("#given complex plan content with mixed fields", () => {
test("#when transformed #then only Commit fields are changed", () => {
const content = `
# Plan

## 1. TASK
Some task description

## 2. EXPECTED OUTCOME
- [ ] Files created
- [ ] Tests pass

## 3. REQUIRED TOOLS
- Write
- Bash

## 4. Commit: YES
This should be committed after completion.
`
const result = transformPlanCommitFields(content)
expect(result).toBe(`
# Plan

## 1. TASK
Some task description

## 2. EXPECTED OUTCOME
- [ ] Files created
- [ ] Tests pass

## 3. REQUIRED TOOLS
- Write
- Bash

## 4. Commit: NO (user disabled auto-commits)
This should be committed after completion.
`)
})
})
})
35 changes: 34 additions & 1 deletion src/hooks/atlas/tool-execute-before.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,31 @@ import { ORCHESTRATOR_DELEGATION_REQUIRED, SINGLE_TASK_DIRECTIVE } from "./syste
import { isSisyphusPath } from "./sisyphus-path"
import { isWriteOrEditToolName } from "./write-edit-tool-policy"

/**
* Check if a path is a plan file inside .sisyphus/plans/
*/
function isPlanPath(filePath: string): boolean {
return /\.sisyphus[/\\]plans[/\\].*\.md$/.test(filePath)
}

/**
* Transform Commit fields in plan content when autoCommit is disabled.
* Converts "Commit: YES" and "Commit: NO" to "Commit: NO (user disabled auto-commits)"
* Uses negative lookahead to ensure idempotency (won't re-transform already-transformed content).
*/
export function transformPlanCommitFields(content: string): string {
return content.replace(/Commit:\s*(YES|NO)(?!\s*\(user disabled auto-commits\))/g, "Commit: NO (user disabled auto-commits)")
}

export function createToolExecuteBeforeHandler(input: {
ctx: PluginInput
pendingFilePaths: Map<string, string>
autoCommit: boolean
}): (
toolInput: { tool: string; sessionID?: string; callID?: string },
toolOutput: { args: Record<string, unknown>; message?: string }
) => Promise<void> {
const { ctx, pendingFilePaths } = input
const { ctx, pendingFilePaths, autoCommit } = input

return async (toolInput, toolOutput): Promise<void> => {
if (!(await isCallerOrchestrator(toolInput.sessionID, ctx.client))) {
Expand Down Expand Up @@ -50,6 +67,22 @@ export function createToolExecuteBeforeHandler(input: {
sessionID: toolInput.sessionID,
})
}
return
}

// Transform plan content when autoCommit is disabled
if (!autoCommit && toolInput.tool === "read") {
const filePath = toolOutput.args.filePath as string | undefined
if (filePath && isPlanPath(filePath)) {
const content = toolOutput.args.content as string | undefined
if (content) {
toolOutput.args.content = transformPlanCommitFields(content)
log(`[${HOOK_NAME}] Transformed plan Commit fields for read`, {
sessionID: toolInput.sessionID,
filePath,
})
}
}
}
}
}
9 changes: 7 additions & 2 deletions src/hooks/atlas/verification-reminders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ export function buildOrchestratorReminder(
- Stage ONLY the verified changes
- Commit with clear message describing what was done
`
: ""
: `
**STEP 8: COMMITS DISABLED**

const nextStepNumber = autoCommit ? 9 : 8
The user has disabled auto-commits (\`start_work.auto_commit: false\`).
Changes will NOT be committed automatically.
`

const nextStepNumber = 9

return `
---
Expand Down
Loading