diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-24 18:54:35 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-24 18:54:35 +0200 |
| commit | c1461b28a13758f3f9934cccaf9fb5b341716aa1 (patch) | |
| tree | ffb5c1e308646dcc206744e3da1719fde4ead6a9 /pi | |
| parent | 636a335e9b96f5fa59a3db63142ae097ada60a8d (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'pi')
| -rw-r--r-- | pi/agent/extensions/agent-plan-mode/index.ts | 18 |
1 files changed, 12 insertions, 6 deletions
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 <uuid> "See <plan-file> 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}/<project>.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 <uuid> "See ${plansDir}/<project>.md for overall context"'. - Read existing started tasks first; if none, inspect the next READY tasks. - Avoid duplicating tasks that already exist. |
