From c1461b28a13758f3f9934cccaf9fb5b341716aa1 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Tue, 24 Mar 2026 18:54:35 +0200 Subject: plan-mode: restrict file writes to ~/.pi/plans only Plan mode previously allowed writing any .md file anywhere in the project tree. Now write/edit tool calls are blocked unless the target path is inside ~/.pi/plans (created on demand with mkdir -p). The agent context prompt and task annotations are updated to match, so the agent knows exactly where to put plan files and never touches the project directory. Co-Authored-By: Claude Sonnet 4.6 --- pi/agent/extensions/agent-plan-mode/index.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'pi/agent/extensions/agent-plan-mode') diff --git a/pi/agent/extensions/agent-plan-mode/index.ts b/pi/agent/extensions/agent-plan-mode/index.ts index b7b4473..1724920 100644 --- a/pi/agent/extensions/agent-plan-mode/index.ts +++ b/pi/agent/extensions/agent-plan-mode/index.ts @@ -270,7 +270,7 @@ export default function agentPlanModeExtension(pi: ExtensionAPI): void { continue; } - const annotation = `Pi plan mode step ${item.step}. See PLAN.md for overall context`; + const annotation = `Pi plan mode step ${item.step}. See ${plansDir}/ for overall context`; const uuid = await createTask(item.text, ctx, { dependsOn: mode === "sequential" ? previousUuid : undefined, annotation, @@ -641,14 +641,19 @@ Begin with the current focused task now. Do not re-check the task list immediate handler: async (ctx) => togglePlanMode(ctx), }); + // Resolve the plans directory once; expandable via HOME env var. + const plansDir = (process.env.HOME ?? "~").replace(/\/$/, "") + "/.pi/plans"; + pi.on("tool_call", async (event) => { if (planModeEnabled) { if (event.toolName === "write" || event.toolName === "edit") { const filePath = String(event.input.file_path ?? event.input.filePath ?? event.input.path ?? ""); - if (!filePath.endsWith(".md")) { + // Only allow writes inside ~/.pi/plans — never inside the project directory. + const normalised = filePath.replace(/\/$/, ""); + if (!normalised.startsWith(plansDir + "/") && normalised !== plansDir) { return { block: true, - reason: `Plan mode only allows writing markdown (.md) files.\nFile: ${filePath}`, + reason: `Plan mode only allows writing files inside ${plansDir}.\nFile: ${filePath}\nCreate the directory with: mkdir -p ${plansDir}`, }; } return; @@ -725,9 +730,10 @@ You are in planning mode for project ${projectName}. Rules: - Use read, bash, grep, find, ls for exploration. - For task operations, always use 'ask ...'. Never use raw 'task'. All ask operations are allowed (add, annotate, modify, done, start, stop, etc.). -- You may write or edit markdown (.md) files only. Use these to document the overall plan, architecture decisions, or task breakdowns. -- Write one plan markdown file (e.g. PLAN.md or docs/plan.md) that describes the overall picture, goals, and task structure. -- For every task created with 'ask add', immediately annotate it with a reference to the plan file: 'ask annotate "See for overall context"'. +- You may write or edit files only inside ${plansDir}. Create that directory first if it does not exist: mkdir -p ${plansDir} +- Write one plan markdown file there (e.g. ${plansDir}/.md) describing the overall picture, goals, and task structure. +- Do NOT write any files inside the current project directory. +- For every task created with 'ask add', immediately annotate it with a reference to the plan file: 'ask annotate "See ${plansDir}/.md for overall context"'. - Read existing started tasks first; if none, inspect the next READY tasks. - Avoid duplicating tasks that already exist. -- cgit v1.2.3