@pixelsnis/pi-plan-mode
v0.1.4
Published
Plan and Build modes for Pi with deny-by-default planning tools and explicit plan approval.
Maintainers
Readme
Plan Mode
Pi extension for read-only planning followed by an explicit, user-selected approval handoff to Build mode.
Install
Install the umbrella Git package to load both extensions and the bundled planning skill:
pi install git:github.com/pixelsnis/pi-extensionsTo install just this extension from npm, after the package has been published:
pi install npm:@pixelsnis/pi-plan-modeRestart Pi or run /reload after changing package settings. To remove an installation, use the same package source with pi remove, for example pi remove npm:@pixelsnis/pi-plan-mode or pi remove git:github.com/pixelsnis/pi-extensions.
Modes and tool gate
Build is the default mode. /plan toggles between Build and Plan and updates the mode badge. /plan-profile displays the selected model profile; /plan-profile <name> changes it.
Plan mode is deny-by-default for agent tool calls. It permits read, grep, find, and ls for inspection; plan_save to write the current generated plan file; and plan_present to open explicit review. A top-level allowedTools list can add exact, case-sensitive agent tool names to this built-in set:
"allowedTools": ["ask_user_question", "web_search"]The list is global and additive across all model profiles. Omit it or use [] to retain the default behavior. Names are accepted before a tool is registered, so a valid unknown name is harmless until a matching third-party tool becomes available; the matching is exact and case-sensitive. Each name must be a non-empty, already-trimmed string, and duplicate names are configuration errors. Unlisted agent tools remain blocked.
Without "bash" in allowedTools, Bash is conditionally permitted only for simple read-only commands from this exact allowlist:
pwd,ls,find,grep,rg,cat,head,tail,wc,file, andstat- Git's
status,diff,log, andshowsubcommands
For example, ls -la, find . -type f -name '*.ts', pwd && ls -la, rg -n 'PLAN_TOOLS\b' extensions/plan-mode/index.ts, rg -n '.*;$' extensions/plan-mode/index.ts, and rg -n 'PLAN_TOOLS|READ_ONLY_BASH_COMMANDS' extensions/plan-mode/index.ts | head -5 && git status --short --branch are accepted. Every command in every pipeline and && branch must independently pass the same checks. Single-quoted text is literal, so quoted regex punctuation such as backslashes and $ is safe; double-quoted text allows literal regex patterns but rejects unescaped $, backticks, and !. The recognizer rejects unlisted commands, other shell chaining/operators (including ;, ||, and lone &), redirection, command/process substitutions, multiline input, wrappers, and known mutating or execution options (including find -delete, find -exec*, file --compile, Git external-diff/textconv/output options, and ripgrep preprocessor options). If a command cannot be parsed or its read-only behavior is uncertain, it is blocked. Listing "bash" explicitly trusts every agent Bash call and removes this read-only filter; Plan Mode does not infer side effects for any allowlisted tool. Interactive !/!! shell commands remain unconditionally blocked by the separate user_bash handler, even when bash is allowlisted.
This is a tool gate, not an OS sandbox: extensions execute with Pi's normal process permissions. Pi still records extension state in the session, and the user-invoked /plan-profile command may update the profile selection in plan-mode.json. Allowlisting a mutating built-in such as write or edit likewise trusts every call to that exact name.
The extension injects a hidden mode-context message, but does not display the plan inline. Its bundled plan-writing skill supplies the generic planning workflow and is included in both this package and the repository's root Pi manifest.
Save and review a plan
- Switch to Plan with
/planand ask Pi to inspect the project and prepare a plan. - The agent reads the
plan-writingskill and saves the plan usingplan_save. That tool accepts Markdown only; it never accepts a destination path. It returns a path relative to the session working directory. - The agent calls
plan_presentwith only the exact relative path string returned byplan_save. Do not substitute an absolute path or another spelling, even if it resolves to the same file. In the Pi TUI, a scrollable review shows the plan and requires an explicit choice:- R — Refine: return to chat in Plan mode without recording approval.
- N — Approve & Execute: start a linked fresh session in Build mode.
- H — Approve & Continue Here: switch this session to Build mode and continue here with the current model and effort.
- Esc — Cancel: keep Plan mode active; no approval is recorded.
Approval uses a one-time handoff. Before execution, the extension checks that the plan still belongs to the source session and is readable, and that the selected profile has not changed. Stale, missing, or invalid plans do not execute. In a non-TUI run, plan_present cannot record approval and returns without executing.
The internal /plan-handoff and /plan-build-start commands carry the approved handoff. They are implementation details; use the review UI rather than invoking them directly.
Plan-file location and safeguards
- In a Git project, the extension stores plans beneath the repository's Git administrative directory in
implementation-plans/(normally.git/implementation-plans/). Plans are not ordinary tracked project files. - Outside a Git project, it creates a unique
<project>-implementation-plans.*directory under the operating system's temporary directory. - The extension creates private directories/files, requires a non-empty plan no larger than 200 KiB, and rejects redirected directories, symlinks, and linked/non-regular plan files. It stores and validates the canonical absolute plan path internally, but
plan_presentaccepts only the exact relative path string returned byplan_savefor the current session working directory.
Keep the path returned by the tool; do not guess or substitute a path. Plans in temporary storage may be removed by the operating system.
Model profiles
Optional configuration is read from $PI_CODING_AGENT_DIR/plan-mode.json, or ~/.pi/agent/plan-mode.json when PI_CODING_AGENT_DIR is unset. A profile contains both Plan and Build model settings:
{
"profiles": {
"work": {
"plan": { "id": "provider/planning-model", "effort": "medium" },
"build": { "id": "provider/build-model", "effort": "high" }
}
},
"selectedProfile": "work",
"allowedTools": ["ask_user_question", "web_search"]
}Replace the example IDs with exact provider/model-id values available in your Pi installation. Configure one to five profiles. Profile names must be safe single tokens beginning with a letter or digit and may contain letters, digits, _, or -; __proto__, constructor, and prototype are reserved and rejected. Each profile must define both plan and build; each effort must be default, low, medium, high, xhigh, or max. Pi may clamp a level to what the selected model supports. allowedTools is a top-level setting shared by every profile, additive to the fixed Plan tools, and is preserved when /plan-profile <name> rewrites the file; it is not profile-specific. Unknown fields, duplicate JSON keys, an invalid selectedProfile, and malformed allowedTools entries are reported as configuration errors. If selectedProfile is omitted, the first profile in file order is selected.
The extension does not validate allowedTools against Pi's current tool registry, which permits third-party tools that register later. This is an explicit trust boundary: Plan Mode cannot determine an allowlisted tool's side effects, so every call to that exact name is allowed. In particular, allowlisting bash removes the agent Bash command filter, and allowlisting mutating built-ins such as write or edit permits them. Interactive !/!! commands are still blocked separately. Restart Pi or run /reload after changing the configuration.
The older top-level format remains supported:
{
"plan": { "id": "provider/planning-model", "effort": "medium" },
"build": { "id": "provider/build-model", "effort": "high" },
"allowedTools": ["ask_user_question", "web_search"]
}allowedTools is also accepted in this legacy top-level form and has the same global semantics. Profile selection converts legacy settings to the profile form without discarding or reordering the list.
effort: "default" does not force a level; a fresh session uses Pi's configured default. Continue Here ignores the Build settings and keeps whatever model and effort are currently active. Missing or unavailable models and invalid configuration are reported. Selecting a profile persists it to the config file, so treat that file as user configuration rather than part of the plan-file write boundary.
Troubleshooting
- Plan tools are missing: Confirm the extension loaded, then restart Pi or run
/reload. - Plan mode appears inactive: Use
/plan; the badge shows the current mode and selected profile. - Approval does not start execution: Review in the interactive TUI. Check that the plan path is still readable and that the configured Build model is available; stale handoffs require a new review.
- A profile will not load: Check JSON syntax, exact provider/model IDs, both
planandbuildentries, valid effort names, and model authentication. - A plan cannot be saved or reviewed: Keep the exact tool-returned path and ensure the plan is non-empty and within 200 KiB. Do not replace its generated directory or file with a symlink.
Local development
From the repository root, load just this package for a local session:
pi --no-extensions -e ./extensions/plan-modeThe bundled skill source is at skills/plan-writing/SKILL.md; the parent repository's root package also exposes it for the Git installation route.
