fix(hooks/atlas): capture plan snapshot for .sisyphus paths
Oracle review of PR #3943 surfaced that endTaskTimer never fires for real Prometheus plans because their canonical path is .sisyphus/plans/ and the snapshot capture was nested inside the !isSisyphusPath branch intended for direct-work warning suppression. Move the snapshot/path tracking out of the warning gate so all plan-file edits are snapshotted regardless of .sisyphus prefix. Keep the warning branch isSisyphus-gated so Atlas does not yell at legitimate plan edits. Regression test now uses a real .sisyphus/plans/ path and fails against HEAD before the fix.
This commit is contained in:
@@ -230,7 +230,9 @@ describe("createToolExecuteAfterHandler task timers", () => {
|
|||||||
it("ends task timer when plan checkbox flips to checked via edit tool", async () => {
|
it("ends task timer when plan checkbox flips to checked via edit tool", async () => {
|
||||||
// given
|
// given
|
||||||
const parentSessionID = "ses_parent_3"
|
const parentSessionID = "ses_parent_3"
|
||||||
const planPath = join(testDirectory, "task-timer-edit-plan.md")
|
const planDirectory = join(testDirectory, ".sisyphus", "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")
|
writeFileSync(planPath, "# Plan\n\n## TODOs\n- [ ] 1. Implement auth flow\n", "utf-8")
|
||||||
writeBoulderState(testDirectory, {
|
writeBoulderState(testDirectory, {
|
||||||
schema_version: 2,
|
schema_version: 2,
|
||||||
|
|||||||
@@ -80,32 +80,35 @@ export function createToolExecuteBeforeHandler(input: {
|
|||||||
// Warn-only policy: Atlas guides orchestrators toward delegation but doesn't block, allowing flexibility for urgent fixes
|
// Warn-only policy: Atlas guides orchestrators toward delegation but doesn't block, allowing flexibility for urgent fixes
|
||||||
if (isWriteOrEditToolName(toolInput.tool)) {
|
if (isWriteOrEditToolName(toolInput.tool)) {
|
||||||
const filePath = (toolOutput.args.filePath ?? toolOutput.args.path ?? toolOutput.args.file) as string | undefined
|
const filePath = (toolOutput.args.filePath ?? toolOutput.args.path ?? toolOutput.args.file) as string | undefined
|
||||||
if (filePath && !isSisyphusPath(filePath)) {
|
if (!filePath || !toolInput.callID) {
|
||||||
// Store filePath for use in tool.execute.after
|
return
|
||||||
if (toolInput.callID) {
|
}
|
||||||
pendingFilePaths.set(toolInput.callID, filePath)
|
|
||||||
|
|
||||||
const sessionID = toolInput.sessionID
|
// Store filePath for use in tool.execute.after
|
||||||
const sessionWork = sessionID
|
pendingFilePaths.set(toolInput.callID, filePath)
|
||||||
? getWorkForSession(ctx.directory, sessionID)
|
|
||||||
: null
|
|
||||||
const state = sessionWork ? null : readBoulderState(ctx.directory)
|
|
||||||
const planPath = sessionWork
|
|
||||||
? resolveBoulderPlanPathForWork(ctx.directory, sessionWork)
|
|
||||||
: state
|
|
||||||
? resolveBoulderPlanPath(ctx.directory, state)
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (planPath && resolve(filePath) === resolve(planPath) && pendingPlanSnapshots) {
|
const sessionID = toolInput.sessionID
|
||||||
try {
|
const sessionWork = sessionID
|
||||||
if (existsSync(planPath)) {
|
? getWorkForSession(ctx.directory, sessionID)
|
||||||
pendingPlanSnapshots.set(toolInput.callID, readFileSync(planPath, "utf-8"))
|
: null
|
||||||
}
|
const state = sessionWork ? null : readBoulderState(ctx.directory)
|
||||||
} catch {
|
const planPath = sessionWork
|
||||||
pendingPlanSnapshots.delete(toolInput.callID)
|
? resolveBoulderPlanPathForWork(ctx.directory, sessionWork)
|
||||||
}
|
: state
|
||||||
|
? resolveBoulderPlanPath(ctx.directory, state)
|
||||||
|
: null
|
||||||
|
|
||||||
|
if (planPath && resolve(filePath) === resolve(planPath) && pendingPlanSnapshots) {
|
||||||
|
try {
|
||||||
|
if (existsSync(planPath)) {
|
||||||
|
pendingPlanSnapshots.set(toolInput.callID, readFileSync(planPath, "utf-8"))
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
pendingPlanSnapshots.delete(toolInput.callID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSisyphusPath(filePath)) {
|
||||||
const warning = ORCHESTRATOR_DELEGATION_REQUIRED.replace("$FILE_PATH", filePath)
|
const warning = ORCHESTRATOR_DELEGATION_REQUIRED.replace("$FILE_PATH", filePath)
|
||||||
toolOutput.message = (toolOutput.message || "") + warning
|
toolOutput.message = (toolOutput.message || "") + warning
|
||||||
log(`[${HOOK_NAME}] Injected delegation warning for direct file modification`, {
|
log(`[${HOOK_NAME}] Injected delegation warning for direct file modification`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user