command-code 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,31 +2,31 @@
2
2
 
3
3
  # Permissions
4
4
 
5
- Permissions decide what Command Code is allowed to do before it does it. Every tool call a shell command, a file edit, a web fetch, an MCP tool, a sub-agent passes through **one central permission engine** that answers a single question: **allow, ask, or deny?**
5
+ Permissions decide what Command Code is allowed to do before it does it. Every tool call - a shell command, a file edit, a web fetch, an MCP tool, a sub-agent - passes through **one central permission engine** that answers a single question: **allow, ask, or deny?**
6
6
 
7
- The semantics match **Claude Code permission modes**: `deny`/`ask` rules win for permission-gated tools, `plan` is read-only exploration, `auto-accept` accepts edits plus safe filesystem commands, `bypass` skips normal prompts, and `dont-ask` denies anything that would need a prompt. The `ask_user_question` and `agent` control-flow tools plus `run_command`, which just dispatches a slash command you named are always allowed. This page is the complete reference the pipeline, the modes, the rule syntax, the full decision table, and the known limits.
7
+ The semantics in brief: `deny`/`ask` rules win for permission-gated tools, `plan` is read-only exploration, `auto-accept` accepts edits plus safe filesystem commands, `bypass` skips normal prompts, and `dont-ask` denies anything that would need a prompt. The `ask_user_question` and `agent` control-flow tools - plus `run_command`, which just dispatches a slash command you named - are always allowed. This page is the complete reference - the pipeline, the modes, the rule syntax, the full decision table, and the known limits.
8
8
 
9
9
  ---
10
10
 
11
11
  ## How a decision is made
12
12
 
13
- Every tool call is evaluated in a fixed order. The order **is** the spec tests pin it:
13
+ Every tool call is evaluated in a fixed order. The order **is** the spec - tests pin it:
14
14
 
15
- 1. **Deny rules** blocked in every mode, including bypass.
16
- 2. **Ask rules** always prompt, even under bypass. In `dont-ask` mode the would-be prompt becomes a deny.
17
- 3. **External-directory gate** a read, write, or shell working directory **outside the workspace** first asks to admit the directory into the session's root set (like `/add-dir` / `permissions.additionalDirectories`); then the normal mode rule applies. Bypass grants the directory silently. The **OS temp directory** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode reads there never prompt, and writes skip the escalation but still follow the normal mode rule. Registered skill directories are readable without a grant; plan-file writes in plan mode skip this gate.
18
- 4. **Plan mode gate** operation-based: reads, searches, and read-only shell run; everything that writes or runs a mutating command is denied. The only write allowed is a plan file under `~/.commandcode/plans/`.
19
- 5. **Taste-directory writes** a product workflow guard (allowed when taste learning is on, otherwise redirected to the `taste` tool); not a safety prompt, so bypass skips it.
20
- 6. **Malformed writes** a write/edit with no resolvable file path is denied up front with a correction.
21
- 7. **Read-only fast path** reads and read-only shell commands (`ls`, `cat`, `grep`, `echo`, `head`, …) run without a prompt, in every mode. A compound command counts when **every** part is read-only and nothing redirects `cat file | head -5 || echo none` is free. A read-only command pointed at secret material (`cat .env`, `head id_rsa`) falls out of the fast path and prompts.
22
- 8. **Root/home removal circuit breaker** `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` and spoofed variants **ask even under bypass** (deny in `dont-ask`/`plan`, where nothing can ask).
23
- 9. **Bypass** everything that survived the gates above auto-allows.
24
- 10. **Sensitive-write ask** writing a secret file, a persistence vector (`.bashrc`, `.gitconfig`, …), or a control surface (`.git/**`, `.ssh/**`, `.commandcode` settings) prompts in `default` and `auto-accept` unless a content-specific allow rule opts in.
25
- 11. **Allow rules** pre-approved calls run without a prompt.
26
- 12. **Accept-edits fast path** in `auto-accept` mode, file edits and safe filesystem commands inside the workspace run without a prompt. Recursive deletes are excluded they still prompt.
27
- 13. Otherwise, **ask** and in `dont-ask` mode, **deny**.
15
+ 1. **Deny rules** - blocked in every mode, including bypass.
16
+ 2. **Ask rules** - always prompt, even under bypass. In `dont-ask` mode the would-be prompt becomes a deny.
17
+ 3. **External-directory gate** - a read, write, or shell working directory **outside the workspace** first asks to admit the directory into the session's root set (like `/add-dir` / `permissions.additionalDirectories`); then the normal mode rule applies. Bypass grants the directory silently. The **OS temp directory** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode - reads there never prompt, and writes skip the escalation but still follow the normal mode rule. Registered skill directories are readable without a grant; plan-file writes in plan mode skip this gate.
18
+ 4. **Plan mode gate** - operation-based: reads, searches, and read-only shell run; everything that writes or runs a mutating command is denied. The only write allowed is a plan file under `~/.commandcode/plans/`.
19
+ 5. **Taste-directory writes** - a product workflow guard (allowed when taste learning is on, otherwise redirected to the `taste` tool); not a safety prompt, so bypass skips it.
20
+ 6. **Malformed writes** - a write/edit with no resolvable file path is denied up front with a correction.
21
+ 7. **Read-only fast path** - reads and read-only shell commands (`ls`, `cat`, `grep`, `echo`, `head`, …) run without a prompt, in every mode. A compound command counts when **every** part is read-only and nothing redirects - `cat file | head -5 || echo none` is free. A read-only command pointed at secret material (`cat .env`, `head id_rsa`) falls out of the fast path and prompts.
22
+ 8. **Root/home removal circuit breaker** - `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` and spoofed variants **ask even under bypass** (deny in `dont-ask`/`plan`, where nothing can ask).
23
+ 9. **Bypass** - everything that survived the gates above auto-allows.
24
+ 10. **Sensitive-write ask** - writing a secret file, a persistence vector (`.bashrc`, `.gitconfig`, …), or a control surface (`.git/**`, `.ssh/**`, `.commandcode` settings) prompts in `default` and `auto-accept` unless a content-specific allow rule opts in.
25
+ 11. **Allow rules** - pre-approved calls run without a prompt.
26
+ 12. **Accept-edits fast path** - in `auto-accept` mode, file edits and safe filesystem commands inside the workspace run without a prompt. Recursive deletes are excluded - they still prompt.
27
+ 13. Otherwise, **ask** - and in `dont-ask` mode, **deny**.
28
28
 
29
- Two things follow from the order. First, **deny beats ask, which beats allow**, in every mode. Second, `bypass` sits *after* the deny/ask rules, the external-dir gate, and the circuit breaker so `--yolo` skips ordinary prompts and the sensitive-write prompt, but not your explicit rules and not the root/home breaker.
29
+ Two things follow from the order. First, **deny beats ask, which beats allow**, in every mode. Second, `bypass` sits *after* the deny/ask rules, the external-dir gate, and the circuit breaker - so `--yolo` skips ordinary prompts and the sensitive-write prompt, but not your explicit rules and not the root/home breaker.
30
30
 
31
31
  Rules are enforced by Command Code, not by the model. Instructions in your prompt or `AGENTS.md` shape what the agent *tries* to do, but they don't change what Command Code *allows*. To grant or revoke access, use permission rules, a permission mode, or a `PreToolUse` hook.
32
32
 
@@ -46,26 +46,26 @@ A mode sets the baseline behavior. Cycle modes at any time with **shift+tab**, o
46
46
  | `bypass` | Do basically everything without asking. Deny rules, explicit ask rules, and the root/home removal breaker still apply. Enter with `--yolo` / `--dangerously-skip-permissions`; use only in throwaway environments. |
47
47
  | `dont-ask` | Never ask. Only run things already allowed by policy (reads, read-only shell, `allow` rules); **deny** the rest. The mirror image of bypass: for unattended runs where a wrong prompt should fail, not wait. |
48
48
 
49
- Claude Code mode names are accepted as aliases: `manual` → `default`, `acceptEdits` → `auto-accept`, `dontAsk` → `dont-ask`, `bypassPermissions` → `bypass`. The legacy name `standard` also maps to `default`.
49
+ Alternate mode spellings are accepted as aliases: `manual` → `default`, `acceptEdits` → `auto-accept`, `dontAsk` → `dont-ask`, `bypassPermissions` → `bypass`. The legacy name `standard` also maps to `default`.
50
50
 
51
51
  ### Switching modes
52
52
 
53
53
  Three ways to switch mid-session:
54
54
 
55
- - **shift+tab** cycles `default` → `auto-accept` → `plan` → `default`. When Command Code was launched with `--yolo`, `bypass` joins as a fourth rung after `plan`. (`alt+m` is an equivalent binding for terminals that don't deliver shift+tab.) `dont-ask` is not a rung it's selected via settings or the CLI; pressing shift+tab from it re-enters the normal cycle at `auto-accept`.
56
- - **`/mode`** shows the current mode; **`/mode:default`**, **`/mode:auto-accept`**, and **`/mode:plan`** (or `/mode <name>`, any accepted spelling) switch directly. Bypass is deliberately **not** switchable from a slash command slash commands are agent-invokable, so a mid-session route into bypass would let the model disable its own prompts. `/mode:yolo` no longer exists; the command answers "launch with `--yolo` to use it."
55
+ - **shift+tab** cycles `default` → `auto-accept` → `plan` → `default`. When Command Code was launched with `--yolo`, `bypass` joins as a fourth rung after `plan`. (`alt+m` is an equivalent binding for terminals that don't deliver shift+tab.) `dont-ask` is not a rung - it's selected via settings or the CLI; pressing shift+tab from it re-enters the normal cycle at `auto-accept`.
56
+ - **`/mode`** shows the current mode; **`/mode:default`**, **`/mode:auto-accept`**, and **`/mode:plan`** (or `/mode <name>`, any accepted spelling) switch directly. Bypass is deliberately **not** switchable from a slash command - slash commands are agent-invokable, so a mid-session route into bypass would let the model disable its own prompts. `/mode:yolo` no longer exists; the command answers "launch with `--yolo` to use it."
57
57
  - **CLI flags** pick the starting mode:
58
58
 
59
59
  ```bash
60
60
  cmd --permission-mode auto-accept # default | standard | plan | auto-accept | dont-ask
61
61
  cmd --auto-accept # shorthand for --permission-mode auto-accept
62
62
  cmd --plan # start in plan mode
63
- cmd --yolo # bypass alias for --dangerously-skip-permissions; use with care
63
+ cmd --yolo # bypass - alias for --dangerously-skip-permissions; use with care
64
64
  ```
65
65
 
66
- `--permission-mode` wins over `--plan`. Bypass has no `--permission-mode` value it is **launch-flag-only** (`--yolo` / `--dangerously-skip-permissions`).
66
+ `--permission-mode` wins over `--plan`. Bypass has no `--permission-mode` value - it is **launch-flag-only** (`--yolo` / `--dangerously-skip-permissions`).
67
67
 
68
- Switching modes also resolves a permission prompt already on screen: entering `auto-accept` or `bypass` **approves** the pending prompt (you just said "stop asking"), and entering `plan` **denies** it. A safety-forced prompt a destructive command, a sensitive file, an explicit `ask` rule is never answered by a mode change; it stays on screen for an explicit yes/no.
68
+ Switching modes also resolves a permission prompt already on screen: entering `auto-accept` or `bypass` **approves** the pending prompt (you just said "stop asking"), and entering `plan` **denies** it. A safety-forced prompt - a destructive command, a sensitive file, an explicit `ask` rule - is never answered by a mode change; it stays on screen for an explicit yes/no.
69
69
 
70
70
  ### `dont-ask` is the CI/CD mode
71
71
 
@@ -86,7 +86,7 @@ Those edits run without prompting; anything outside the allowlist is denied.
86
86
 
87
87
  ## The decision table
88
88
 
89
- The complete matrix every kind of operation against every mode. This is the authoritative behavior reference; the permission tests pin each row.
89
+ The complete matrix - every kind of operation against every mode. This is the authoritative behavior reference; the permission tests pin each row.
90
90
 
91
91
  | Operation | `default` | `auto-accept` | `plan` | `bypass` | `dont-ask` | Why |
92
92
  |---|---|---|---|---|---|---|
@@ -95,27 +95,27 @@ The complete matrix — every kind of operation against every mode. This is the
95
95
  | Matches `allow` rule | allow | allow | allow if plan-compatible | allow | allow | Allow rules are honored after deny/ask and mode gates. |
96
96
  | Direct file read/search inside workspace | allow | allow | allow | allow | allow | Reads are free unless rules say otherwise. |
97
97
  | Direct file read/search outside workspace | ask external-dir, then allow | ask external-dir, then allow | ask external-dir, then allow | allow | deny unless pre-approved | Outside dirs are gated separately, then reads are allowed. |
98
- | Read under the OS temp directory (`/tmp`, `$TMPDIR`) | allow (dir granted silently) | allow (dir granted silently) | allow (dir granted silently) | allow | allow | Temp files are disposable working data the external-dir escalation is skipped. Deny/ask rules still outrank; a symlink resolving outside temp takes the normal gate. |
98
+ | Read under the OS temp directory (`/tmp`, `$TMPDIR`) | allow (dir granted silently) | allow (dir granted silently) | allow (dir granted silently) | allow | allow | Temp files are disposable working data - the external-dir escalation is skipped. Deny/ask rules still outrank; a symlink resolving outside temp takes the normal gate. |
99
99
  | Write under the OS temp directory | ask like a workspace write | allow; sensitive still asks | deny | allow | deny unless allow rule | No external-dir escalation, then the normal mode edit/write rule applies (sensitive names like `/tmp/.env` still force the prompt). |
100
100
  | Read under a registered skill directory | allow | allow | allow | allow | allow | Skill resources are readable without a grant; writes into skill dirs still gate. |
101
101
  | Web / read-only native tool | allow | allow | allow | allow | allow | Non-filesystem read-only tools are free unless rules say otherwise. |
102
102
  | `ask_user_question` | allow | allow | allow | allow | allow | Asking the user is the interaction; a permission prompt must never stack in front of the question. |
103
103
  | `agent` | allow | allow | allow | allow | allow | Delegation itself is always allowed. Every tool used by the sub-agent is still checked under its own permission policy. |
104
- | `run_command` | allow | allow | | allow | allow | Dispatches a slash command you named, exactly as if you'd typed it removed from the toolset in plan mode (a write tool) and never granted to sub-agents. |
104
+ | `run_command` | allow | allow | - | allow | allow | Dispatches a slash command you named, exactly as if you'd typed it - removed from the toolset in plan mode (a write tool) and never granted to sub-agents. |
105
105
  | Read-only shell, e.g. `git status`, `cat README.md \| head -5` | allow | allow | allow | allow | allow | Plan may explore with read-only shell. Compound commands qualify when every part is read-only and nothing redirects. Shell cwd follows the external-dir row below. |
106
- | Secret-looking read via a file tool, e.g. `read_file .env` | allow | allow | allow | allow | allow | No built-in secret-read prompt for file tools (Claude parity). Use `ask`/`deny` rules for sensitive reads. |
106
+ | Secret-looking read via a file tool, e.g. `read_file .env` | allow | allow | allow | allow | allow | Reads are free by default; no built-in secret-read prompt for file tools. Use `ask`/`deny` rules for sensitive reads. |
107
107
  | Secret-looking read via shell, e.g. `cat .env` | ask | ask | deny | allow | deny unless allow rule | A read-only shell command with a secret-material argument (`.env`, `id_rsa`, `*.pem`) falls out of the read-only fast path. |
108
108
  | Workspace file edit | ask | allow | deny | allow | deny unless allow rule | `auto-accept` accepts normal edits; `dont-ask` only runs pre-approved edits. |
109
109
  | Protected workspace write, e.g. `.env` or `.git/config` | ask | ask | deny | allow | deny unless pre-approved | Bypass skips normal write-risk prompts; auto-accept does not. |
110
110
  | Direct file write outside workspace | ask external-dir, then ask edit/write | ask external-dir, then allow normal edit/write; sensitive still asks | deny, except plan-file writes | allow | deny unless pre-approved | External-dir gate comes first, then the mode-specific edit/write rule. |
111
111
  | Plan file write under `~/.commandcode/plans/` | ask like outside write | ask external-dir, then allow | allow (no external-dir prompt) | allow | deny unless pre-approved | Plan mode's sanctioned output; never gated in plan mode. |
112
- | Safe filesystem shell inside workspace: `mkdir`, `touch`, `cp`, `mv`, non-recursive `rm`, `rmdir`, `sed` | ask | allow | deny | allow | deny unless allow rule | Claude `acceptEdits` filesystem behavior, constrained to non-sensitive workspace targets. Recursive `rm` is excluded see the recursive-delete row. |
112
+ | Safe filesystem shell inside workspace: `mkdir`, `touch`, `cp`, `mv`, non-recursive `rm`, `rmdir`, `sed` | ask | allow | deny | allow | deny unless allow rule | Accepting edits extends to safe filesystem commands, constrained to non-sensitive workspace targets. Recursive `rm` is excluded - see the recursive-delete row. |
113
113
  | Shell cwd outside workspace | ask external-dir, then apply shell rule/mode | ask external-dir, then apply shell rule/mode | ask external-dir for read-only shell; deny mutating shell | allow | deny unless pre-approved | The shell working directory is external-dir gated. |
114
114
  | Shell command args outside workspace | shell rule/mode | shell rule/mode | shell rule/mode | allow | shell rule/mode | Path arguments inside a command string are never external-dir gated (not a reliable file boundary); use shell `ask`/`deny` rules for sensitive commands. |
115
115
  | Other mutating shell | ask | ask | deny | allow | deny unless allow rule | Shell command arguments are governed by shell rules and the active mode. |
116
116
  | MCP / custom side-effecting or unknown tool | ask | ask | deny | allow | deny unless allow rule | Unknown side effects prompt unless bypass or an explicit rule. |
117
117
  | Root/home removal, e.g. `rm -rf /` or `rm -rf ~` | ask | ask | deny | **ask** | deny | The circuit breaker survives bypass. |
118
- | Recursive workspace delete, e.g. `rm -rf dist` | ask | ask | deny | allow | deny unless allow rule | Recursive deletes prompt outside bypass accepting edits is not accepting bulk irreversible deletion. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows. |
118
+ | Recursive workspace delete, e.g. `rm -rf dist` | ask | ask | deny | allow | deny unless allow rule | Recursive deletes prompt outside bypass - accepting edits is not accepting bulk irreversible deletion. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows. |
119
119
 
120
120
  Reading down a column: a mode only changes the outcome of calls the rules leave **unresolved**. A `deny` rule and an `ask` rule behave the same in every interactive mode; `dont-ask` turns every would-be ask into a deny; `bypass` auto-allows everything except deny rules, ask rules, and the circuit breaker.
121
121
 
@@ -153,22 +153,22 @@ Tool names are case-insensitive. `Tool()` and `Tool(*)` both mean the whole tool
153
153
 
154
154
  A `*` in a **tool name** (not a specifier) matches by name, so you can write broad rules without listing every tool:
155
155
 
156
- - `*` every permission-gated tool (`ask_user_question`, `agent`, and `run_command` remain allowed).
157
- - `mcp__*` every tool from every MCP server.
158
- - `mcp__github__get_*` every read-style tool from one server.
159
- - `edit_*` every tool whose name starts with `edit_`.
156
+ - `*` - every permission-gated tool (`ask_user_question`, `agent`, and `run_command` remain allowed).
157
+ - `mcp__*` - every tool from every MCP server.
158
+ - `mcp__github__get_*` - every read-style tool from one server.
159
+ - `edit_*` - every tool whose name starts with `edit_`.
160
160
 
161
- Tool-name wildcards are honored in `deny` and `ask` lists, where a broad rule is what you want (`deny: ["mcp__*"]` blocks all MCP tools). They are **ignored in `allow` lists** unless the server is named (`mcp__github__get_*` is fine; a bare `*` or `mcp__*` is not) an allow rule should say what it grants, not hand out everything. Specifier wildcards (`Shell(npm *)`, `Edit(src/**)`, `WebFetch(domain:*.example.com)`) work in every list.
161
+ Tool-name wildcards are honored in `deny` and `ask` lists, where a broad rule is what you want (`deny: ["mcp__*"]` blocks all MCP tools). They are **ignored in `allow` lists** unless the server is named (`mcp__github__get_*` is fine; a bare `*` or `mcp__*` is not) - an allow rule should say what it grants, not hand out everything. Specifier wildcards (`Shell(npm *)`, `Edit(src/**)`, `WebFetch(domain:*.example.com)`) work in every list.
162
162
 
163
163
  ### Match by input parameter
164
164
 
165
165
  Beyond a tool's own specifier, a `deny` or `ask` rule can gate on a single **top-level input parameter** with `Tool(param:value)`:
166
166
 
167
- - `Shell(run_in_background:true)` a background shell command.
167
+ - `Shell(run_in_background:true)` - a background shell command.
168
168
 
169
- The value takes a `*` wildcard, and a parameter the model **omits** never matches. Each rule names one parameter to gate on two, write two rules.
169
+ The value takes a `*` wildcard, and a parameter the model **omits** never matches. Each rule names one parameter - to gate on two, write two rules.
170
170
 
171
- Parameter matching is **deny/ask only**. An allow rule for one parameter can't establish that the whole call is safe, so `allow` rules keep to each tool's own specifier. The fields a tool already matches with its own syntax `command` (Shell), `file_path` (Read/Edit/Write), `path` (Grep/Glob), `notebook_path`, and `url` (WebFetch) are **not** parameter-matchable; use the tool's specifier for those (`Shell(rm *)`, not `Shell(command:rm *)`).
171
+ Parameter matching is **deny/ask only**. An allow rule for one parameter can't establish that the whole call is safe, so `allow` rules keep to each tool's own specifier. The fields a tool already matches with its own syntax - `command` (Shell), `file_path` (Read/Edit/Write), `path` (Grep/Glob), `notebook_path`, and `url` (WebFetch) - are **not** parameter-matchable; use the tool's specifier for those (`Shell(rm *)`, not `Shell(command:rm *)`).
172
172
 
173
173
  ### Tool names
174
174
 
@@ -183,16 +183,16 @@ Use the friendly capitalized names, or the exact tool names the model sees:
183
183
  | `WebFetch` | `web_fetch` |
184
184
  | `WebSearch` | `web_search` |
185
185
 
186
- `Shell` is the name for shell commands, because the shell tool is called `shell_command`. `Bash(...)` is accepted as a legacy alias for `Shell(...)`, so older configurations keep working but `Shell` is the name to use.
186
+ `Shell` is the name for shell commands, because the shell tool is called `shell_command`. `Bash(...)` is accepted as a legacy alias for `Shell(...)`, so older configurations keep working - but `Shell` is the name to use.
187
187
 
188
188
  ### Shell patterns
189
189
 
190
190
  Shell specifiers support `*` wildcards:
191
191
 
192
- - `Shell(npm run build)` the exact command.
193
- - `Shell(npm run *)` any command starting with `npm run `.
194
- - `Shell(git * main)` `git checkout main`, `git merge main`, …
195
- - `Shell(git:*)` the `:*` suffix is a trailing wildcard, the same as `Shell(git *)`.
192
+ - `Shell(npm run build)` - the exact command.
193
+ - `Shell(npm run *)` - any command starting with `npm run `.
194
+ - `Shell(git * main)` - `git checkout main`, `git merge main`, …
195
+ - `Shell(git:*)` - the `:*` suffix is a trailing wildcard, the same as `Shell(git *)`.
196
196
 
197
197
  The space before `*` matters: `Shell(ls *)` matches `ls -la` but not `lsof`; `Shell(ls*)` matches both.
198
198
 
@@ -200,7 +200,7 @@ Command Code understands shell operators, so a rule like `Shell(git status)` doe
200
200
 
201
201
  ### Asymmetric matching: deny/ask are aggressive, allow is conservative
202
202
 
203
- The matcher deliberately treats the lists differently failing toward a prompt is always the safe direction:
203
+ The matcher deliberately treats the lists differently - failing toward a prompt is always the safe direction:
204
204
 
205
205
  - **Deny and ask rules match aggressively:** They see through leading environment variables (`FOO=bar rm -rf x` matches `Shell(rm *)`), process wrappers (`timeout 30 …`, `nice …`), and every subcommand of a compound command; a command the parser can't fully understand is matched against the raw string; path matching folds case (on macOS/Windows `.ENV` opens `.env`) and fires when either the given path or its symlink target matches.
206
206
  - **Allow rules match conservatively:** No env stripping (`PATH=/tmp git status` does **not** match an allow for `Shell(git status:*)`); a compound command allows only when *every* subcommand matches some allow pattern; an unparseable command never auto-allows; path matching is case-exact and requires **both** the given path and its symlink target to match.
@@ -218,13 +218,13 @@ The matcher deliberately treats the lists differently — failing toward a promp
218
218
 
219
219
  `*` matches within one path segment; `**` matches across directories. When a rule points at a symlink, both the link and its target are checked. A trailing slash on the tool's input path can't dodge an exact-path rule (`read_directory /repo/secrets/` still matches `Read(//repo/secrets)`).
220
220
 
221
- Reads are allowed by default. `allow: ["Read(src/**)"]` is accepted but does **not** block reads outside `src/**` use `ask`/`deny` rules for sensitive reads. A strict read allowlist would be a separate behavior change (see Known limits).
221
+ Reads are allowed by default. `allow: ["Read(src/**)"]` is accepted but does **not** block reads outside `src/**` - use `ask`/`deny` rules for sensitive reads. A strict read allowlist would be a separate behavior change (see Known limits).
222
222
 
223
223
  ### MCP and sub-agents
224
224
 
225
- MCP tools are named by their canonical `mcp__server__tool` name display names are never permission keys. `mcp__github` and `mcp__github__*` both match every tool from the `github` server; `mcp__github__get_*` matches just its `get_` tools; and `mcp__*` (deny/ask only) matches every tool from every server.
225
+ MCP tools are named by their canonical `mcp__server__tool` name - display names are never permission keys. `mcp__github` and `mcp__github__*` both match every tool from the `github` server; `mcp__github__get_*` matches just its `get_` tools; and `mcp__*` (deny/ask only) matches every tool from every server.
226
226
 
227
- The `agent` tool itself is always allowed and never displays a permission prompt. A sub-agent runs under its own permission policy the **same ordered pipeline** as the main loop, with one difference: where the main loop would draw an interactive prompt, the sub-agent policy **auto-allows**, because a sub-agent runs autonomously and has no human in its loop to answer. The hard guards still hold: a sub-agent can never run a tool your `deny` rules block, never write in plan mode, and anything the main loop would have prompted about for **safety** a destructive command, a sensitive file, an outside-workspace write, an explicit `ask` rule fails **closed** (denied) instead of sailing through.
227
+ The `agent` tool itself is always allowed and never displays a permission prompt. A sub-agent runs under its own permission policy - the **same ordered pipeline** as the main loop, with one difference: where the main loop would draw an interactive prompt, the sub-agent policy **auto-allows**, because a sub-agent runs autonomously and has no human in its loop to answer. The hard guards still hold: a sub-agent can never run a tool your `deny` rules block, never write in plan mode, and anything the main loop would have prompted about for **safety** - a destructive command, a sensitive file, an outside-workspace write, an explicit `ask` rule - fails **closed** (denied) instead of sailing through.
228
228
 
229
229
  ---
230
230
 
@@ -232,9 +232,9 @@ The `agent` tool itself is always allowed and never displays a permission prompt
232
232
 
233
233
  Every permission prompt offers exactly **three** choices: allow once, allow **and remember**, or deny with feedback. The remembered scope matches the subject:
234
234
 
235
- - **File operations** *"Yes, allow all edits this session"* turns on auto-accept for the rest of the session (same as shift+tab).
236
- - **Shell commands** *"Yes, don't ask again for `git` commands in this project"* persists a `Shell(git:*)` allow rule to project settings. For a compound command, one rule is written per non-read-only base. Bases that execute arbitrary code (`bash`, `python`, `curl`, …) persist the exact command instead of a prefix grant.
237
- - **MCP / other tools** *"Yes, don't ask again for `mcp__server__tool` in this project"* persists the tool name to project settings.
235
+ - **File operations** - *"Yes, allow all edits this session"* turns on auto-accept for the rest of the session (same as shift+tab).
236
+ - **Shell commands** - *"Yes, don't ask again for `git` commands in this project"* persists a `Shell(git:*)` allow rule to project settings. For a compound command, one rule is written per non-read-only base. Bases that execute arbitrary code (`bash`, `python`, `curl`, …) persist the exact command instead of a prefix grant.
237
+ - **MCP / other tools** - *"Yes, don't ask again for `mcp__server__tool` in this project"* persists the tool name to project settings.
238
238
 
239
239
  ### Command explanations on demand
240
240
 
@@ -244,21 +244,21 @@ The shell permission prompt can explain the command it's asking about: press **c
244
244
 
245
245
  ## Safety behaviors
246
246
 
247
- Safety-forced prompts only offer one-time **Yes** or **No** choices, plus a banner saying which safety check fired. Session and project choices are intentionally hidden because destructive-command, sensitive-path, outside-workspace, and explicit `ask` approvals are never cached the next risky call must ask again. To stop a recurring safety prompt, add a content-specific allow rule (e.g. `Edit(.commandcode/settings.json)`).
247
+ Safety-forced prompts only offer one-time **Yes** or **No** choices, plus a banner saying which safety check fired. Session and project choices are intentionally hidden because destructive-command, sensitive-path, outside-workspace, and explicit `ask` approvals are never cached - the next risky call must ask again. To stop a recurring safety prompt, add a content-specific allow rule (e.g. `Edit(.commandcode/settings.json)`).
248
248
 
249
249
  ### The root/home removal circuit breaker
250
250
 
251
- The **only** built-in safety prompt that survives `bypass`: a removal targeting the filesystem root or your home directory `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` always stops for confirmation, in every mode that can ask (`dont-ask` and `plan` deny instead, since they can't prompt).
251
+ The **only** built-in safety prompt that survives `bypass`: a removal targeting the filesystem root or your home directory - `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` - always stops for confirmation, in every mode that can ask (`dont-ask` and `plan` deny instead, since they can't prompt).
252
252
 
253
- The breaker is spoof-resistant. It detects removals through environment-variable prefixes, process wrappers (`timeout`, `nice`, …), compound commands, quoted payloads (`bash -c "rm -rf /; echo ok"`), command substitution (`echo $(rm -rf /;)`), glued separators (`/;`), and `$HOME`/`${HOME}` spellings (quoted or not, with `/` or `/*` suffixes `$HOMEDIR` stays literal).
253
+ The breaker is spoof-resistant. It detects removals through environment-variable prefixes, process wrappers (`timeout`, `nice`, …), compound commands, quoted payloads (`bash -c "rm -rf /; echo ok"`), command substitution (`echo $(rm -rf /;)`), glued separators (`/;`), and `$HOME`/`${HOME}` spellings (quoted or not, with `/` or `/*` suffixes - `$HOMEDIR` stays literal).
254
254
 
255
255
  ### Recursive deletes prompt under auto-accept
256
256
 
257
- The `auto-accept` safe-filesystem fast path refuses `rm -r/-R/-rf/--recursive` (and `find … -delete`): a recursive delete like `rm -rf dist` prompts exactly as it would in `default`. Plain single-file `rm` stays fast-pathed. This is a deliberate divergence from strict Claude `acceptEdits` parity, in the safe direction accepting edits is not accepting bulk irreversible deletion. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows, and `bypass` runs it without a prompt.
257
+ The `auto-accept` safe-filesystem fast path refuses `rm -r/-R/-rf/--recursive` (and `find … -delete`): a recursive delete like `rm -rf dist` prompts exactly as it would in `default`. Plain single-file `rm` stays fast-pathed. This is deliberate, and in the safe direction: accepting edits is not accepting bulk irreversible deletion. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows, and `bypass` runs it without a prompt.
258
258
 
259
259
  ### Sensitive writes
260
260
 
261
- Writing these prompts in `default` and `auto-accept` unless a content-specific allow rule opts in (`bypass` skips this gate, like Claude `bypassPermissions`):
261
+ Writing these prompts in `default` and `auto-accept` unless a content-specific allow rule opts in (`bypass` skips this gate):
262
262
 
263
263
  ```
264
264
  Secret material: .env, .env.*, *.pem, *.key, id_rsa, id_ed25519, credentials
@@ -267,20 +267,20 @@ Control surfaces: .git/**, .ssh/**, .aws/**, .gnupg/**, .kube/**, .vscode/**
267
267
  .husky/**, .devcontainer/**, node_modules/.bin/**, .commandcode settings
268
268
  ```
269
269
 
270
- A "content-specific" allow rule must carry a specifier that names the path (`Edit(.env.local)`) a bare whole-tool allow (`Edit`) is deliberately not enough to wave a call past a safety check.
270
+ A "content-specific" allow rule must carry a specifier that names the path (`Edit(.env.local)`) - a bare whole-tool allow (`Edit`) is deliberately not enough to wave a call past a safety check.
271
271
 
272
- Sensitive **reads** through file tools do not prompt. Matching Claude Code, reading `.env` is allowed by default if you want it gated, say so with a rule: `ask: ["Read(.env*)"]` or `deny: ["Read(.env*)", "Read(secrets/**)"]`. The read-only **shell** fast path is stricter: `cat .env` or `head id_rsa` falls out of it and prompts like any other command.
272
+ Sensitive **reads** through file tools do not prompt. Reading `.env` is allowed by default. If you want it gated, say so with a rule: `ask: ["Read(.env*)"]` or `deny: ["Read(.env*)", "Read(secrets/**)"]`. The read-only **shell** fast path is stricter: `cat .env` or `head id_rsa` falls out of it and prompts like any other command.
273
273
 
274
274
  ### External directories
275
275
 
276
- Outside-workspace reads, writes, and shell working directories first ask to admit the directory into the session's root set the same grant as `/add-dir` or `permissions.additionalDirectories` and then the normal mode rule applies. Details:
276
+ Outside-workspace reads, writes, and shell working directories first ask to admit the directory into the session's root set - the same grant as `/add-dir` or `permissions.additionalDirectories` - and then the normal mode rule applies. Details:
277
277
 
278
278
  - `bypass` grants the directory silently; `dont-ask` denies unless the directory is pre-approved in settings.
279
- - The **OS temp directory** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode temp files are disposable working data, so asking to "extend access" to the machine's own temp dir is friction, not protection. Reads there run prompt-free; writes skip the outside-workspace escalation but still confirm like any workspace write (and sensitive names like `/tmp/.env` still force the sensitive-file prompt). Explicit `deny`/`ask` rules still outrank the grant, and a symlink under the temp dir that resolves outside it takes the normal gate. (Claude Code has no temp exemption — its answer is the per-session scratchpad, which Command Code also has; codex auto-approves `/tmp` and `$TMPDIR`.)
279
+ - The **OS temp directory** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode - temp files are disposable working data, so asking to "extend access" to the machine's own temp dir is friction, not protection. Reads there run prompt-free; writes skip the outside-workspace escalation but still confirm like any workspace write (and sensitive names like `/tmp/.env` still force the sensitive-file prompt). Explicit `deny`/`ask` rules still outrank the grant, and a symlink under the temp dir that resolves outside it takes the normal gate.
280
280
  - Approving an `ask` rule on an outside path **also grants the directory**, so the tool-level backstop never rejects a call you just approved.
281
281
  - **Registered skill directories** are readable without a grant (skill resources live outside the workspace by design). Writes into skill directories still gate.
282
- - **Plan-file writes are exempt in plan mode** they are the mode's sanctioned output.
283
- - Absolute paths **inside a shell command string** are *not* external-dir gated a command argument is not a reliable file boundary. Use shell `ask`/`deny` rules for sensitive commands.
282
+ - **Plan-file writes are exempt in plan mode** - they are the mode's sanctioned output.
283
+ - Absolute paths **inside a shell command string** are *not* external-dir gated - a command argument is not a reliable file boundary. Use shell `ask`/`deny` rules for sensitive commands.
284
284
 
285
285
  ### Disabling bypass
286
286
 
@@ -317,14 +317,14 @@ Permission rules live under `permissions` in `.commandcode/settings.json` (share
317
317
  }
318
318
  ```
319
319
 
320
- - **`defaultMode`** the starting mode (`default`, `auto-accept`, `plan`, `bypass`, or `dont-ask`; Claude spellings like `acceptEdits` and the legacy `standard` also work).
321
- - **`allow` / `ask` / `deny`** rule lists. These **accumulate across settings files**: a `deny` rule in your user-global settings can't be erased by a project file.
322
- - **`additionalDirectories`** extra directories that count as inside the workspace for reads and writes (same accumulation).
323
- - **`disableBypass`** set to `"disable"` (or `true`) to make bypass mode unenterable.
320
+ - **`defaultMode`** - the starting mode (`default`, `auto-accept`, `plan`, `bypass`, or `dont-ask`; alternate spellings like `acceptEdits` and the legacy `standard` also work).
321
+ - **`allow` / `ask` / `deny`** - rule lists. These **accumulate across settings files**: a `deny` rule in your user-global settings can't be erased by a project file.
322
+ - **`additionalDirectories`** - extra directories that count as inside the workspace for reads and writes (same accumulation).
323
+ - **`disableBypass`** - set to `"disable"` (or `true`) to make bypass mode unenterable.
324
324
 
325
325
  ### Precedence
326
326
 
327
- If a tool is denied at any level, no other level can allow it. A managed or user-global `deny` beats a project `allow`; deny beats ask; ask beats allow. Specificity doesn't change the order a broad `deny` still blocks a narrower `allow`.
327
+ If a tool is denied at any level, no other level can allow it. A managed or user-global `deny` beats a project `allow`; deny beats ask; ask beats allow. Specificity doesn't change the order - a broad `deny` still blocks a narrower `allow`.
328
328
 
329
329
  ---
330
330
 
@@ -380,33 +380,33 @@ If a tool is denied at any level, no other level can allow it. A managed or user
380
380
 
381
381
  The choices behind the behavior above, so you don't have to reverse-engineer them:
382
382
 
383
- 1. **Claude Code parity over a custom safety matrix.** There is no built-in `.env` file-read prompt and no bypass-surviving recursive-delete prompt. Sensitive file reads are a rules concern (`ask`/`deny`), not a hardcoded prompt. The one stricter carve-out: the read-only **shell** fast path screens secret-material arguments, so `cat .env` prompts instead of riding `cat`'s free pass.
383
+ 1. **Rules over a hardcoded safety matrix.** There is no built-in `.env` file-read prompt and no bypass-surviving recursive-delete prompt. Sensitive file reads are a rules concern (`ask`/`deny`), not a hardcoded prompt, so each project decides what counts as sensitive. The one stricter carve-out: the read-only **shell** fast path screens secret-material arguments, so `cat .env` prompts instead of riding `cat`'s free pass.
384
384
  2. **One ordered pipeline.** The order in [How a decision is made](#how-a-decision-is-made) is the spec; tests pin it.
385
385
  3. **Asymmetric rule matching.** Deny/ask match aggressively; allow matches conservatively. Failing toward a prompt is always the safe direction.
386
386
  4. **External directories are a separate gate**, applied before the mode rule, with skill read roots and plan-file writes exempted where the mode sanctions them.
387
387
  5. **The circuit breaker survives bypass and is spoof-resistant.**
388
- 6. **`disableBypass` is enforced at every layer** engine, CLI entry, TUI mode cycle, and decision store.
388
+ 6. **`disableBypass` is enforced at every layer** - engine, CLI entry, TUI mode cycle, and decision store.
389
389
  7. **Shell command arguments are not a file boundary.** Absolute paths inside a command string are governed by shell rules and the active mode, not the file-tool boundary.
390
390
  8. **Generic tools get a real prompt.** MCP/custom tools render a generic permission prompt instead of a fake file-edit prompt.
391
- 9. **Recursive deletes prompt under auto-accept** a deliberate, safe-direction divergence from strict `acceptEdits` parity.
391
+ 9. **Recursive deletes prompt under auto-accept.** A deliberate choice in the safe direction: bulk irreversible deletion is not an edit.
392
392
  10. **Policy denials guide the model; only human vetoes end the turn.** Every engine denial carries actionable guidance so the model adapts and the turn continues.
393
393
 
394
394
  ---
395
395
 
396
396
  ## Known limits
397
397
 
398
- Documented edges of the current engine behaviors to know about when writing rules:
398
+ Documented edges of the current engine - behaviors to know about when writing rules:
399
399
 
400
400
  - **Wildcard `include` patterns in `read_multiple_files` expand inside the tool**, so an exact-file deny rule (`Read(.env)`) cannot match a wildcard include (`*.env`) before expansion. Directory-tree rules (`Read(//etc/**)`) still cover this, because the tool's `targetDirectory` is matched as a touched path.
401
- - **No strict read allowlist:** `allow: ["Read(src/**)"]` does not deny reads outside `src/**` reads are allowed by default and only `ask`/`deny` rules restrict them. A deny-everything-not-listed read mode is intentionally out of scope as a separate behavior change.
402
- - **Shell path arguments are not external-dir gated:** `cat /outside/file` is governed by shell rules and the mode, not the workspace boundary a command string is not a reliable file boundary. Gate it with `deny: ["Shell(cat /outside/*)"]`-style rules if you need to.
403
- - **An allow rule never matches an opaque command:** If the shell parser can't fully understand a command, it can still be deny/ask-matched against the raw string, but it will never auto-allow it prompts instead.
401
+ - **No strict read allowlist:** `allow: ["Read(src/**)"]` does not deny reads outside `src/**` - reads are allowed by default and only `ask`/`deny` rules restrict them. A deny-everything-not-listed read mode is intentionally out of scope as a separate behavior change.
402
+ - **Shell path arguments are not external-dir gated:** `cat /outside/file` is governed by shell rules and the mode, not the workspace boundary - a command string is not a reliable file boundary. Gate it with `deny: ["Shell(cat /outside/*)"]`-style rules if you need to.
403
+ - **An allow rule never matches an opaque command:** If the shell parser can't fully understand a command, it can still be deny/ask-matched against the raw string, but it will never auto-allow - it prompts instead.
404
404
  - **Parameter rules only see parameters the model sends:** An omitted parameter never matches.
405
405
 
406
406
  ---
407
407
 
408
408
  ## See also
409
409
 
410
- - [Plan Mode](./plan-mode.md) read-only exploration and planning.
411
- - [Hooks](./hooks.md) run your own scripts to allow, deny, or audit tool calls at runtime.
412
- - [MCP](./mcp.md) connect external tools that the permission engine gates by server and tool name.
410
+ - [Plan Mode](./plan-mode.md) - read-only exploration and planning.
411
+ - [Hooks](./hooks.md) - run your own scripts to allow, deny, or audit tool calls at runtime.
412
+ - [MCP](./mcp.md) - connect external tools that the permission engine gates by server and tool name.
@@ -9,6 +9,8 @@ Command Code separates reasoning from execution using permission modes:
9
9
 
10
10
  `Shift + Tab` is your CLI shortcut to switch between plan and auto-accept modes.
11
11
 
12
+ Once a plan is written, [Plan Review](./plan-review.md) is where you read, comment on, revise, and approve it.
13
+
12
14
  ---
13
15
 
14
16
  ## Plan Mode
@@ -0,0 +1,226 @@
1
+ <!-- GENERATED FILE — do not edit. Source: packages/docs/src/app/core-concepts/plan-review/page.mdx. Regenerate: pnpm generate:knowledge -->
2
+
3
+ # Plan Review
4
+
5
+ Plan review is the full-screen surface where Command Code presents a written plan for your sign-off — a code-review experience for plans, in the terminal. New in **v1**, it turns a plan into a first-class, persistent artifact you can read, comment on, revise, and approve, instead of a block of text that scrolls away.
6
+
7
+ If you've reviewed a pull request on GitHub, plan review will feel familiar: one plan line is always selected, you leave inline comments, and you resolve the plan with a verb — **Submit review**, **Approve**, or **Cancel**.
8
+
9
+ Plan review pairs with [Plan Mode](./plan-mode.md). Plan mode is where the agent *writes* the plan; plan review is where you *sign off* on it.
10
+
11
+ ---
12
+
13
+ ## At a glance
14
+
15
+ - **One mode: REVIEW.** Read line by line, comment on any line, resolve with a verb. There is no separate "edit mode" — plan edits go through your `$EDITOR`.
16
+ - **Plans persist.** Every plan is saved to `~/.commandcode/plans/` as markdown and indexed, so canceling a review never throws the plan away.
17
+ - **Comments are review artifacts, not plan text.** They live in a sidecar overlay and only reach the agent inside a prompt — they never get written into the plan document.
18
+ - **GitHub-style verbs.** Submit review (`ctrl+r`), Approve (`ctrl+a`) or Execute plan (`ctrl+e`), Cancel/Back (`esc`).
19
+ - **Review rounds.** When the agent revises a plan, the next round diffs against the last one — changed lines render green so you re-review only what moved.
20
+ - **Deterministic.** A harness backstop guarantees the review is offered even when a weaker model writes a plan and simply stops.
21
+
22
+ ---
23
+
24
+ ## How it works
25
+
26
+ A plan starts in plan mode, lands in review, and loops until you approve it. Submitting a review sends it back to the agent for a revision; approving implements it; canceling saves it for later.
27
+
28
+ ```
29
+ You: "plan a rate limiter"
30
+
31
+
32
+ ┌──────────────┐ agent explores (read-only) and writes
33
+ │ PLAN MODE │ ~/.commandcode/plans/rate-limiter.md
34
+ └──────┬───────┘
35
+ │ exit_plan_mode (or plan_review / /plan-review / the backstop)
36
+
37
+ ┌───────────────────────────┐ revised plan (round N+1)
38
+ │ PLAN REVIEW · REVIEW │◀─────────────────────────┐
39
+ │ read · comment · revise │ │
40
+ └──┬──────────┬──────────┬───┘ ┌──────┴──────┐
41
+ │ │ │ │ agent │
42
+ Submit review │ │ ── comments ─────────▶│ revises the │
43
+ (ctrl+r) ──────┘ │ │ plan │
44
+ ▲ │ └─────────────┘
45
+ └── another round ────┘
46
+
47
+ Approve (ctrl+a) / Execute (ctrl+e)
48
+
49
+
50
+ ┌─────────────┐
51
+ │ IMPLEMENT │
52
+ └─────────────┘
53
+
54
+ Cancel (esc) ──▶ plan saved · status: not-implemented
55
+ reopen anytime with /plans
56
+ ```
57
+
58
+ Inside the reader, one line is always selected, comments pin inline under their line, and the pinned bottom bar holds the review verbs:
59
+
60
+ ```
61
+ ┌ Plan review: Rate limiter · ~/.commandcode/plans/rate-limiter.md · v2 ┐
62
+ │ │
63
+ │ 3 ## Approach ← headings / code styled │
64
+ │ 4 │
65
+ │ 5 ● 1. Add a token-bucket middleware. ← ● marks a commented line │
66
+ │ ↳ why not a sliding window? ← your inline comment │
67
+ │ 6 2. Store buckets in Redis. │
68
+ │ 7 3. Return 429 with Retry-After. ← green if changed (round 2) │
69
+ │ │
70
+ │ ─────────────────────────────────────────────────────────────────── │
71
+ │ REVIEW 1 pending comment · round 2 · 1 line changed │
72
+ │ Submit review (1) ctrl+r agent revises the plan, returns it │
73
+ │ Approve ctrl+a executes the plan · comments go as notes │
74
+ │ Cancel esc │
75
+ │ type + enter to comment · ctrl+n/p jump changes · quick: ? x ! · ^g │
76
+ └───────────────────────────────────────────────────────────────────── ┘
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Opening plan review
82
+
83
+ There are four ways into the same review surface. All of them are scoped to a plan written during the **current session**.
84
+
85
+ ### 1. Finishing plan mode
86
+
87
+ While in [plan mode](./plan-mode.md), the agent writes the plan to `~/.commandcode/plans/<name>.md` and then calls `exit_plan_mode`. That opens plan review as the approval surface — the plan *is* the approval prompt. Approving here can also switch you into [auto-accept mode](./plan-mode.md#auto-accept-mode) so implementation runs without further prompts.
88
+
89
+ ### 2. Asking to review a plan (`plan_review` tool)
90
+
91
+ Outside plan mode, just ask:
92
+
93
+ > "review plan" · "open plan review" · "show me the plan"
94
+
95
+ The agent calls the `plan_review` tool, which opens the review panel for the most recent plan file. This exists so the agent shows you the real review surface instead of pasting the plan back as a wall of text. In plan mode the agent uses `exit_plan_mode` instead — there is exactly one review path per mode.
96
+
97
+ ### 3. The `/plan-review` slash command
98
+
99
+ Run `/plan-review` to jump straight into a review of **this session's latest plan**.
100
+
101
+ ### 4. The `/plans` browser
102
+
103
+ Run `/plans` to open the full-screen plan browser — every plan from this session and past sessions, with status badges, comment counts, and search. Open any plan to read and review it. `/plans <name>` jumps straight into a named plan.
104
+
105
+ ### The automatic backstop (auto plan review)
106
+
107
+ Some models write a plan file and stop without ever presenting it. Command Code makes the review a guarantee, not a courtesy: when a run ends naturally with a plan that was written but never reviewed (in **default** or **plan** mode), the harness presents the same review panel itself. Approving it continues the run with an instruction to implement; declining lets the run end so you can steer. Modes that mean "don't interrupt me" — auto-accept, bypass, and dont-ask — skip the backstop.
108
+
109
+ ---
110
+
111
+ ## Reviewing a plan
112
+
113
+ The reader renders the plan like a document — headings, code, quotes, and tables are styled per line. One line is always highlighted. The bottom of the screen holds a fixed zone: a **REVIEW** badge with the pending-comment count, a single context-sensitive hint line, and the stacked review verbs. Nothing in that zone changes as you scroll — your position lives in the highlight and the gutter line number.
114
+
115
+ ### Navigating
116
+
117
+ | Key | Action |
118
+ |-----|--------|
119
+ | `↑` / `↓` | Move one line; past the last line drops onto the review verbs |
120
+ | `PgUp` / `PgDn` (`Fn+↑/↓` on Mac) | Page up/down one viewport, like `less` |
121
+ | `Home` / `End` (`Fn+←/→` on Mac) | Jump the cursor to the first / last line |
122
+ | `ctrl+n` / `ctrl+p` | Jump between marked lines — your comments, plus changed lines in a revised plan |
123
+
124
+ Long lines **wrap** at a comfortable reading width (80 columns when there's room, otherwise 60) rather than truncating, so you can always read the full paragraph.
125
+
126
+ ### Leaving comments
127
+
128
+ Commenting is Figma-style — just start typing on a line and a draft comment box opens **inline, directly under that line**, exactly where it will sit once pinned.
129
+
130
+ | Key | Action |
131
+ |-----|--------|
132
+ | type + `Enter` | Open a draft on the selected line, then pin it |
133
+ | `Enter` on a commented line | Reopen the comment to edit it |
134
+ | empty + `Enter` | Remove the comment |
135
+ | `?` | Quick comment: "Why? Explain the reasoning behind this." |
136
+ | `x` | Quick comment: "Cut this — remove it from the plan." |
137
+ | `!` | Quick comment: "Risky — double-check this before implementing." |
138
+ | `esc` (while drafting) | Discard the draft |
139
+
140
+ Pinned comments show a `●` gutter marker on the line and a `↳ comment` row beneath it. Comments are saved to disk with the plan, so they survive closing the reader and even ending the session. They never become part of the plan document.
141
+
142
+ ### Editing the plan
143
+
144
+ Plan review has no separate edit mode. Press `ctrl+g` to hand the plan file to your `$EDITOR` (from `$EDITOR`/`$VISUAL`); the reader reloads when you close it. The editor is the edit surface, the reader is the review surface.
145
+
146
+ ---
147
+
148
+ ## Resolving a review
149
+
150
+ The review verbs are stacked in the pinned bottom bar. Trigger them by chord from anywhere, or arrow onto them and press `Enter`.
151
+
152
+ | Verb | Chord | What it does |
153
+ |------|-------|--------------|
154
+ | **Submit review** | `ctrl+r` | The agent takes your pending comments, revises the plan, and re-presents it for another round. Only shown when you have pending comments. |
155
+ | **Approve** *(approval surface)* | `ctrl+a` | Executes the plan and begins implementation. If you have pending comments, they ride along as notes (see below). |
156
+ | **Execute plan** *(browser surface)* | `ctrl+e` | Executes the plan as written — from the `/plans` browser this is itself an approval, so the agent implements directly. |
157
+ | **Cancel** / **Back** | `esc` | Ends the review. In plan mode the plan stays saved and you keep refining; from the browser it returns to the list. |
158
+
159
+ ### Approving with pending comments
160
+
161
+ If you press Approve while comments are still pending, Command Code doesn't guess. It asks:
162
+
163
+ ```
164
+ Approve (•) with N comments as notes ( ) original plan · discard comments
165
+ ```
166
+
167
+ Choose **with comments as notes** and your comments are sent as a follow-up user turn — non-blocking notes the agent implements against. Choose **original plan** to approve it clean and drop the comments. Use `←/→` to switch, `Enter` to confirm, `esc` to back out.
168
+
169
+ ---
170
+
171
+ ## Review rounds and versioning
172
+
173
+ Plan review is iterative. Each time you **Submit review**, Command Code:
174
+
175
+ 1. Snapshots the current plan to `~/.commandcode/plans/versions/<name>-v<N>.md`.
176
+ 2. Bumps the plan's version and clears your pending comments (they were all handed to the agent in one prompt).
177
+ 3. Lets the agent revise the plan, overwriting the live file.
178
+
179
+ When the revised plan comes back (version > 1), the reader **diffs it against the previous round's snapshot**. Changed lines render **green**, and the badge line reports `round N · M lines changed` — so a second review means re-reading only what actually moved, not the whole plan again. `ctrl+n`/`ctrl+p` jump between those changed lines.
180
+
181
+ ---
182
+
183
+ ## Where plans live
184
+
185
+ Plans and their metadata are stored under your home directory:
186
+
187
+ ```
188
+ ~/.commandcode/plans/<descriptive-name>.md ← the plan markdown (written by the agent)
189
+ ~/.commandcode/plans/plans-index.json ← titles, status, comments, versions
190
+ ~/.commandcode/plans/versions/<name>-v<N>.md ← prior review-round snapshots
191
+ ```
192
+
193
+ Every plan carries a status:
194
+
195
+ - **pending** — written but not yet taken through review.
196
+ - **approved** — you approved it and moved to implementation.
197
+ - **not-implemented** — you canceled the review; the plan is kept for later reading, commenting, and revision.
198
+
199
+ Because a canceled plan is recorded rather than discarded, planning never feels throwaway — reopen it any time with `/plans`. Writes to `~/.commandcode/plans/*.md` never prompt for permission (in any mode), and only `.md` files there get that exemption, so the plans directory can't double as a scratchpad.
200
+
201
+ ---
202
+
203
+ ## Slash commands
204
+
205
+ | Command | Description |
206
+ |---------|-------------|
207
+ | `/plan` | Enter plan mode; `/plan <task>` plans that task |
208
+ | `/plans` | Browse, review, and comment on saved plans — `/plans [name]` opens one directly |
209
+ | `/plan-review` | Open plan review on this session's latest plan |
210
+ | `/mode plan` | Switch to plan mode (read-only, no side effects) |
211
+
212
+ ---
213
+
214
+ ## Related tools
215
+
216
+ - **`exit_plan_mode`** — presents the plan for approval when leaving plan mode. Only valid *in* plan mode.
217
+ - **`plan_review`** — opens the review panel on demand *outside* plan mode. Renders as `PLAN(review)` in the activity feed.
218
+ - **`enter_plan_mode`** — switches into plan mode (with your confirmation) for read-only exploration.
219
+
220
+ ---
221
+
222
+ ## Next steps
223
+
224
+ - Learn how [Plan Mode](./plan-mode.md) and Auto-Accept mode split reasoning from execution.
225
+ - Use [Checkpoints](./checkpoints.md) to rewind after an approved plan is implemented.
226
+ - Browse the full [Slash Commands](https://commandcode.ai/docs/reference/slash-commands) and [Tools](https://commandcode.ai/docs/reference/tools) references.