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.
- package/CHANGELOG.md +4 -3
- package/dist/bundled/command-code-knowledge/SKILL.md +2 -1
- package/dist/bundled/command-code-knowledge/reference/custom-agents.md +7 -7
- package/dist/bundled/command-code-knowledge/reference/custom-slash-commands.md +5 -5
- package/dist/bundled/command-code-knowledge/reference/headless.md +7 -7
- package/dist/bundled/command-code-knowledge/reference/hooks.md +7 -7
- package/dist/bundled/command-code-knowledge/reference/mcp.md +8 -8
- package/dist/bundled/command-code-knowledge/reference/permissions.md +75 -75
- package/dist/bundled/command-code-knowledge/reference/plan-mode.md +2 -0
- package/dist/bundled/command-code-knowledge/reference/plan-review.md +226 -0
- package/dist/bundled/command-code-knowledge/reference/skills.md +27 -27
- package/dist/bundled/mod-builder/reference/api.md +21 -21
- package/dist/bundled/mod-builder/reference/hooks-and-events.md +48 -48
- package/dist/bundled/mod-builder/reference/overview.md +19 -17
- package/dist/bundled/mod-builder/reference/packaging.md +6 -6
- package/dist/bundled/mod-builder/reference/ui.md +9 -9
- package/dist/bundled/mod-builder/reference/verify.md +11 -11
- package/dist/cli.mjs +2 -2
- package/package.json +5 -5
- package/vsix/commandcode-vscode.vsix +0 -0
|
@@ -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
|
|
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
|
|
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
|
|
13
|
+
Every tool call is evaluated in a fixed order. The order **is** the spec - tests pin it:
|
|
14
14
|
|
|
15
|
-
1. **Deny rules**
|
|
16
|
-
2. **Ask rules**
|
|
17
|
-
3. **External-directory gate**
|
|
18
|
-
4. **Plan mode gate**
|
|
19
|
-
5. **Taste-directory writes**
|
|
20
|
-
6. **Malformed writes**
|
|
21
|
-
7. **Read-only fast path**
|
|
22
|
-
8. **Root/home removal circuit breaker**
|
|
23
|
-
9. **Bypass**
|
|
24
|
-
10. **Sensitive-write ask**
|
|
25
|
-
11. **Allow rules**
|
|
26
|
-
12. **Accept-edits fast path**
|
|
27
|
-
13. Otherwise, **ask**
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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
|
|
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
|
-
- `*`
|
|
157
|
-
- `mcp__*`
|
|
158
|
-
- `mcp__github__get_*`
|
|
159
|
-
- `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)
|
|
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)`
|
|
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
|
|
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
|
|
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
|
|
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)`
|
|
193
|
-
- `Shell(npm run *)`
|
|
194
|
-
- `Shell(git * main)`
|
|
195
|
-
- `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
|
|
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/**`
|
|
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
|
|
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
|
|
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**
|
|
236
|
-
- **Shell commands**
|
|
237
|
-
- **MCP / other tools**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)`)
|
|
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.
|
|
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
|
|
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
|
|
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**
|
|
283
|
-
- Absolute paths **inside a shell command string** are *not* external-dir gated
|
|
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`**
|
|
321
|
-
- **`allow` / `ask` / `deny`**
|
|
322
|
-
- **`additionalDirectories`**
|
|
323
|
-
- **`disableBypass`**
|
|
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
|
|
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. **
|
|
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**
|
|
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
|
|
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
|
|
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/**`
|
|
402
|
-
- **Shell path arguments are not external-dir gated:** `cat /outside/file` is governed by shell rules and the mode, not the workspace boundary
|
|
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
|
|
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)
|
|
411
|
-
- [Hooks](./hooks.md)
|
|
412
|
-
- [MCP](./mcp.md)
|
|
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.
|