@pellux/goodvibes-tui 2.0.15 → 2.0.16

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 CHANGED
@@ -4,6 +4,20 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.0.16] - 2026-08-21
8
+
9
+ ### Changes
10
+
11
+ - **The Planning panel drives the real planning command.** The modal invoked
12
+ `/project-plan`, a command that does not exist; it now runs the registered
13
+ planning command, and a guard test resolves every command the panels emit
14
+ against the live command registry so a phantom fails the build.
15
+ - Platform runtime 2.0.20: the pin rides the payments boot-recovery sdk
16
+ cycle. No TUI code change was needed for it; the reusable-workflow and
17
+ toolchain pins repoint to the 2.0.20 release commit.
18
+ - Docs verified against the code: install steps match a tested clean
19
+ install, and term enumerations became code-verified tables.
20
+
7
21
  ## [2.0.15] - 2026-08-21
8
22
 
9
23
  ### Changes
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  GoodVibes is a terminal console for coding and operations work with an AI model. You run `goodvibes` in a project directory and get a full-screen terminal app where you talk to a model that can read and edit your files, run shell commands, search the web, and hand work off to background agents, asking your permission before anything that writes or executes.
8
8
 
9
- It talks to many model providers (OpenAI, Anthropic, Gemini, Bedrock, Copilot, OpenRouter and other OpenAI-compatible gateways, plus local servers like Ollama and LM Studio that it finds on startup), keeps its settings, sessions, and secrets on your own machine, and shows you the token count and running cost of every turn. Alongside the conversation, panels turn background work into live control rooms: running agents, git state, diffs, tokens, cost.
9
+ It talks to many model providers (OpenAI, Anthropic, Gemini, Bedrock, Copilot, OpenRouter and other OpenAI-compatible gateways, plus local servers like Ollama and LM Studio that `/scan` finds on your machine and LAN), keeps its settings, sessions, and secrets on your own machine, and shows you the token count and running cost of every turn. Alongside the conversation, panels turn background work into live control rooms: running agents, git state, diffs, tokens, cost.
10
10
 
11
11
  <img src="docs/assets/splash.png" alt="GoodVibes starting up in a terminal: a glitch-art GOODVIBES wordmark in cyan-to-magenta gradient, the version and tagline beneath it, the active model and tool count, the working directory, a line offering to resume the last session, and a hint line reading Ctrl+P panels / ? help / F2 fleet. A status footer shows mode, token usage, context usage, and the daemon and web listener addresses." width="900">
12
12
 
@@ -25,11 +25,11 @@ Or install from the npm registry with [Bun](https://bun.sh):
25
25
 
26
26
  ```sh
27
27
  bun add -g @pellux/goodvibes-tui
28
- bun pm trust -g @pellux/goodvibes-tui goodvibes-daemon
28
+ bun pm trust -g @pellux/goodvibes-tui @pellux/goodvibes-daemon
29
29
  goodvibes
30
30
  ```
31
31
 
32
- Bun blocks lifecycle scripts for untrusted global packages, so the second line lets both postinstalls run: `@pellux/goodvibes-tui`'s own, which places the TUI binary, and `goodvibes-daemon`'s, which places the daemon binary. `goodvibes-daemon` is a real dependency of this package, so one install brings both commands. No other dependency needs trusting. If you skip that step, the `goodvibes` launcher still self-heals on first run by fetching and checksum-verifying its own binary. `npm install -g @pellux/goodvibes-tui` also works when `bun` is already on `PATH`.
32
+ Bun blocks lifecycle scripts for untrusted global packages, so the second line lets both postinstalls run: `@pellux/goodvibes-tui`'s own, which places the TUI binary, and `@pellux/goodvibes-daemon`'s, which places the daemon binary (both names must be the full scoped names). `@pellux/goodvibes-daemon` is a real dependency of this package, so one install brings both commands. No other dependency needs trusting for the install to work. If you skip that step, the `goodvibes` launcher still self-heals on first run by fetching and checksum-verifying its own binary. `npm install -g @pellux/goodvibes-tui` also works when `bun` is already on `PATH`.
33
33
 
34
34
  Then point it at a model. An environment variable is the fastest path:
35
35
 
@@ -57,7 +57,7 @@ The footer keeps an honest running total: fresh input tokens separated from cach
57
57
 
58
58
  <img src="docs/assets/workspace-trust.png" alt="A modal titled New workspace, choose a trust level, offering two options: Trust this workspace, full capability, all tools may run; and Keep restricted, read-only, explore safely, writes and commands are denied until trusted. Navigation hints read Up/Down Navigate, Enter Choose, Esc Close." width="820">
59
59
 
60
- The first time you open a directory, GoodVibes asks how much it is allowed to do there. Restricted is read-only: the model can look around, but writes and commands are refused until you trust the workspace.
60
+ The first time you open a directory, GoodVibes asks how much it is allowed to do there. Restricted is read-only. The model can look around, but writes and commands are refused until you trust the workspace.
61
61
 
62
62
  <img src="docs/assets/permission-prompt.png" alt="An execution approval prompt. A header reads EXECUTE, Shell Execution Approval, one more waiting. Fields list the requesting session, the tool exec, the full command mkdir -p build, the working directory, a Risk line reading HIGH write in red, the surface and radius, a summary, the decision class, the effects: process execution, filesystem mutation, possible network access, a reviewer checklist, and the raw tool arguments. Below, four numbered remember options scope an approval to this exact command, to every mkdir command, to every exec call in this project, or to the rest of the session in memory only. The action line reads: Y Allow once, 1-4 Allow and remember, N Deny, or type a reason to deny." width="900">
63
63
 
@@ -70,7 +70,7 @@ Four other modes are available when prompting is not what you want:
70
70
  - `allow-all` approves everything
71
71
  - `custom` takes per-tool `allow` / `prompt` / `deny` overrides
72
72
 
73
- `Shift+Tab` cycles the four session postures: normal, accept-edits, plan, and auto. `/plan` toggles plan mode directly.
73
+ `Shift+Tab` cycles the four session postures: normal (the `prompt` mode), accept-edits, plan, and auto (the `allow-all` mode). `/plan` toggles plan mode directly.
74
74
 
75
75
  ### Models and providers
76
76
 
@@ -109,7 +109,7 @@ Press `?` for a searchable, categorized list of every slash command with its arg
109
109
  | `Esc` | Leave the current mode: search, command, or modal |
110
110
  | `Ctrl+C` | Clear input, cancel a running turn. Press twice to quit |
111
111
 
112
- Most bindings are customizable in `~/.goodvibes/tui/keybindings.json`, and `/keybindings` shows what is currently bound. Five keys are fixed and stay out of that file: `F2`, `Shift+Tab`, `Esc`, `?`, and `@`. The full reference is in [docs/tools-and-commands.md](docs/tools-and-commands.md).
112
+ Most bindings are customizable in `~/.goodvibes/tui/keybindings.json`, and `/keybindings` shows what is currently bound. Five keys are fixed and stay out of that file: `F2` (Fleet), `Shift+Tab` (permission-mode cycle), `Esc` (leave the current mode), `?` (help), and `@` (file picker). The full reference is in [docs/tools-and-commands.md](docs/tools-and-commands.md).
113
113
 
114
114
  ---
115
115
 
@@ -165,18 +165,27 @@ Edit them live with `/settings` or the fullscreen `/config` workspace rather tha
165
165
  | `display.showThinking` | `false` | Show model thinking traces |
166
166
  | `behavior.autoCompactThreshold` | `80` | Context percentage before auto-compact runs |
167
167
  | `helper.enabled` | `false` | Route grunt work to a cheaper helper model |
168
- | `daemon.enabled` | `true` | Spawn or adopt a detached local session daemon, bound to loopback |
168
+ | `daemon.enabled` | `true` | Adopt a local session daemon on loopback; off makes no adoption attempt |
169
169
 
170
170
  The wider key table, the permission modes, and the hand-edited TUI namespaces (checkpoint root guard, scriptable statusline, session behavior, launch-time self-update) are in [docs/configuration.md](docs/configuration.md).
171
171
 
172
172
  ### Where things are stored
173
173
 
174
- - global settings `~/.goodvibes/tui/settings.json`, project settings `.goodvibes/tui/settings.json`
175
- - encrypted secrets `~/.goodvibes/tui/secrets.enc` or `.goodvibes/tui/secrets.enc`
176
- - custom providers `~/.goodvibes/tui/providers/*.json`, keybindings `~/.goodvibes/tui/keybindings.json`
177
- - service registry `.goodvibes/tui/services.json`, schedules `.goodvibes/tui/schedules.json`
178
- - agent archetypes `.goodvibes/agents/*.md`, MCP servers `.goodvibes/mcp.json`, hooks `.goodvibes/hooks.json`
179
- - sessions, artifacts, and other project runtime state under `.goodvibes/` in the working directory
174
+ Everything lives in plain files, split between the per-user `~/.goodvibes/tui/` tree and the project's own `.goodvibes/` directory:
175
+
176
+ | Path | What lives there |
177
+ | --- | --- |
178
+ | `~/.goodvibes/tui/settings.json` | Global settings |
179
+ | `.goodvibes/tui/settings.json` | Project settings overriding the global layer |
180
+ | `~/.goodvibes/tui/secrets.enc` or `.goodvibes/tui/secrets.enc` | Encrypted secrets, global or per project |
181
+ | `~/.goodvibes/tui/providers/*.json` | Custom provider definitions, hot-reloaded |
182
+ | `~/.goodvibes/tui/keybindings.json` | Keybinding overrides |
183
+ | `.goodvibes/tui/services.json` | The service registry |
184
+ | `.goodvibes/tui/automation-*.json` | Scheduled jobs and the rest of the automation store |
185
+ | `.goodvibes/agents/*.md` | Agent archetypes |
186
+ | `.goodvibes/mcp.json` | MCP server definitions |
187
+ | `.goodvibes/hooks.json` | Hooks and hook chains |
188
+ | `.goodvibes/` (rest) | Sessions, artifacts, and other project runtime state in the working directory |
180
189
 
181
190
  ---
182
191
 
@@ -204,10 +213,10 @@ bun run dev
204
213
  | Command | Does |
205
214
  | --- | --- |
206
215
  | `bun run dev` | Run the TUI from source |
207
- | `bun test` | Run the suite through the parallel per-file runner |
216
+ | `bun run test` | Run the suite through the parallel per-file runner |
208
217
  | `bun run build` | Compile `src/main.ts` into `dist/goodvibes` |
209
218
 
210
- The compiled binary is the TUI entrypoint. It adopts a running standalone GoodVibes daemon, or starts one already installed as a stopped service, and hosts the HTTP listener when `danger.httpListener` is enabled. It never spawns a new daemon process itself (`daemon.enabled`, on by default, loopback-bound).
219
+ The compiled binary is the TUI entrypoint. With `daemon.enabled` on (the default) it adopts a running standalone GoodVibes daemon over loopback, and when a daemon is installed as a service but stopped, it starts that service once and waits for it to come online. It never embeds or constructs a daemon of its own. The control plane, HTTP listener (`danger.httpListener`), and web surface are all hosted by the daemon; the TUI configures them and reports their bindings.
211
220
 
212
221
  Tests live under `src/test/`, mirroring the source tree, and cover contract, security, release-gate, runtime, renderer, panel, integration, and anti-regression cases. Several gates run alongside them in CI: byte-exact golden renderer frames, performance budgets for startup and frame composition and line production (`scripts/perf-baseline.json`), and architecture rules for import cycles, layer boundaries, source-file size, and unused renderer exports (`scripts/check-architecture.ts`).
213
222
 
@@ -237,10 +246,10 @@ src/
237
246
  ├── config/ settings layering, surface roots, secrets, credential availability
238
247
  ├── permissions/ approval cards, hunk selection, sandbox exec gate
239
248
  ├── cli/ flag parsing, management verbs, doctor, launch-time self-update
240
- ├── tools/, mcp/, plugins/ TUI-local tool guards, MCP hot reload, plugin loader
241
- ├── audio/, export/ turn playback and speech routing, cost pricing and gist upload
242
- ├── verification/, work-plans/, widget/ live verifier and ledger, work-plan store, widget module
243
- ├── utils/, types/, scripts/ formatting and clipboard helpers, local types, message script
249
+ ├── tools/, mcp/ TUI-local tool guards, MCP runtime reload
250
+ ├── audio/, export/ voice capture, wake word, playback and speech routing; gist upload
251
+ ├── verification/, widget/ live verifier and its ledger, the terminal widget module
252
+ ├── utils/, scripts/ formatting and clipboard helpers, message processing script
244
253
  └── test/ the suite, mirroring the tree above
245
254
  ```
246
255
 
package/docs/README.md CHANGED
@@ -8,13 +8,13 @@ This directory contains the current product documentation for `goodvibes-tui`.
8
8
  Installation, first-run setup, provider configuration, common paths, and basic run/build flows.
9
9
 
10
10
  - [Configuration reference](configuration.md)
11
- The layered settings model, the key-settings table, permission modes and the policy/trust system, plus the TUI-owned namespaces you add by hand to settings.json: checkpoint root guard, the scriptable statusline, session behavior, and launch-time self-update.
11
+ The layered settings model, a curated key-settings table, permission modes and the policy/trust system, plus the TUI-owned namespaces you add by hand to settings.json, covering the checkpoint root guard, the scriptable statusline, session behavior, notification alert classes, and launch-time self-update.
12
12
 
13
13
  - [Deployment and services](deployment-and-services.md)
14
14
  TUI-only mode, compiled binary behavior, adopting or autostarting the daemon, the in-process HTTP listener, inbound TLS, outbound trust configuration, control-plane entrypoints, the service registry and secret references, profiles and setup transfer, and integration helpers.
15
15
 
16
16
  - [Remote access: a home server setup](remote-access.md)
17
- Step-by-step continuity setup: daemon on an always-on box, webui from any browser, TUI over SSH, Tailscale for reachability and TLS, validated by `goodvibes doctor`.
17
+ Step-by-step continuity setup covering the daemon on an always-on box, webui from any browser, TUI over SSH, Tailscale for reachability and TLS, and validation with `goodvibes doctor`.
18
18
 
19
19
  - [Providers and routing](providers-and-routing.md)
20
20
  Native providers, compatible/gateway providers, local discovery, synthetic failover, search providers, voice providers, and media providers.
@@ -38,13 +38,13 @@ This directory contains the current product documentation for `goodvibes-tui`.
38
38
  Omnichannel surfaces, reply routing, remote peers, node-host contracts, and the daemon/control-plane HTTP + streaming surfaces.
39
39
 
40
40
  - [Session durability](session-durability.md)
41
- Two-layer durability: post-turn snapshots plus periodic recovery files, and the fsync-per-record transcript journal replayed at every resume seam.
41
+ Two-layer durability, combining post-turn snapshots plus periodic recovery files with the fsync-per-record transcript journal replayed at every resume seam.
42
42
 
43
43
  - [Share command](share-command.md)
44
44
  `/share` session export to HTML/JSON/Markdown with redaction, upload, clipboard, and open options.
45
45
 
46
46
  - [CLI flags](cli-flags.md)
47
- Global flags reference: session lifecycle (`--continue`, `--resume`, `--fork`), confirmation bypass (`-y`/`--non-interactive`), output format consolidation, `--host` alias, and all other startup and command-specific flags.
47
+ Global flags reference covering session lifecycle (`--continue`, `--resume`, `--fork`), the `-y`/`--non-interactive` commit gate for plugin bundle installs, output format consolidation, the `--host` alias, and all other startup and command-specific flags.
48
48
 
49
49
  - [Commands reference](commands-reference.md)
50
50
  The generated, categorized list of every slash command, its arguments, and its description. It is the same list `?` renders in the product.
@@ -74,4 +74,4 @@ This directory contains the current product documentation for `goodvibes-tui`.
74
74
  Checked-in operator and peer contract manifests plus canonical knowledge GraphQL and SQL artifacts for release-gate and SDK extraction work.
75
75
 
76
76
  - [GitHub Action](github-action.md)
77
- The `run-goodvibes` composite action: installing a pinned, checksum-verified release in CI and running a non-interactive command or prompt against a workspace.
77
+ The `run-goodvibes` composite action, which installs a pinned, checksum-verified release in CI and runs a non-interactive command or prompt against a workspace.
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "2.0.19"
6
+ "version": "2.0.20"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "2.0.15",
3
+ "version": "2.0.16",
4
4
  "description": "Terminal-native GoodVibes client for coding, operations, knowledge and channel workflows; a pure client of the GoodVibes daemon.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -96,8 +96,8 @@
96
96
  "@anthropic-ai/bedrock-sdk": "^0.29.1",
97
97
  "@anthropic-ai/sdk": "^0.92.0",
98
98
  "@ast-grep/napi": "^0.42.0",
99
- "@pellux/goodvibes-sdk": "2.0.19",
100
- "@pellux/goodvibes-terminal-shell": "2.0.19",
99
+ "@pellux/goodvibes-sdk": "2.0.20",
100
+ "@pellux/goodvibes-terminal-shell": "2.0.20",
101
101
  "bash-language-server": "^5.6.0",
102
102
  "fuse.js": "^7.1.0",
103
103
  "@pellux/goodvibes-daemon": "1.28.21",
@@ -121,7 +121,7 @@
121
121
  "zustand": "^5.0.12"
122
122
  },
123
123
  "devDependencies": {
124
- "@pellux/goodvibes-toolchain": "2.0.19",
124
+ "@pellux/goodvibes-toolchain": "2.0.20",
125
125
  "@types/bun": "^1.3.10",
126
126
  "typescript": "^5.9.3"
127
127
  },
@@ -9,7 +9,7 @@ import { openModalCommand, requirePlanManager, requireSessionLineageTracker } fr
9
9
  import { togglePlanMode, permissionModeLabel, type PermissionModeValue } from '../../core/permission-mode.ts';
10
10
 
11
11
  /**
12
- * Single-token verbs that look like a `/plan` subcommand but are not real ones.
12
+ * Single-token verbs that look like a `/project-plan` subcommand but are not real ones.
13
13
  * A lone one of these is refused rather than seeded as a goal, so a stray verb
14
14
  * can never overwrite the project goal with itself.
15
15
  *
@@ -165,7 +165,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
165
165
  if (args[0] === 'show') {
166
166
  const id = args[1];
167
167
  if (!id) {
168
- ctx.print('Usage: /plan show <plan-id>');
168
+ ctx.print('Usage: /project-plan show <plan-id>');
169
169
  return;
170
170
  }
171
171
  const plans = planManager.list();
@@ -178,16 +178,16 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
178
178
  return;
179
179
  }
180
180
 
181
- // /plan dismiss, archive the current plan. Dismisses the active
181
+ // /project-plan dismiss, archive the current plan. Dismisses the active
182
182
  // execution plan (ExecutionPlanManager.dismiss, honest per-state) AND
183
183
  // deactivates the project-planning interview state shown in the modal so a
184
- // later /plan <goal> starts fresh. Mid-execution is refused outright.
184
+ // later /project-plan <goal> starts fresh. Mid-execution is refused outright.
185
185
  if (args[0] === 'dismiss') {
186
186
  const dismissal = planManager.dismiss(ctx.session.runtime.sessionId);
187
187
  if (dismissal.outcome === 'requires-cancel') {
188
188
  ctx.print(
189
189
  `Plan "${dismissal.blockedBy?.title ?? 'active plan'}" is mid-execution and was not dismissed. ` +
190
- `Run /workstream cancel to stop it first, then /plan dismiss.`,
190
+ `Run /workstream cancel to stop it first, then /project-plan dismiss.`,
191
191
  );
192
192
  return;
193
193
  }
@@ -213,7 +213,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
213
213
  if (dismissal.outcome === 'dismissed') {
214
214
  ctx.print(
215
215
  `Dismissed plan "${dismissal.plan?.title ?? 'active plan'}" ` +
216
- `(archived as dismissed; retained in /plan list; /plan <goal> starts fresh).${planningNote}`,
216
+ `(archived as dismissed; retained in /project-plan list; /project-plan <goal> starts fresh).${planningNote}`,
217
217
  );
218
218
  } else if (planningNote) {
219
219
  ctx.print(`No active execution plan to dismiss.${planningNote}`);
@@ -223,7 +223,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
223
223
  return;
224
224
  }
225
225
 
226
- // /plan answer <n|question-id> <text>, record a real answer to an
226
+ // /project-plan answer <n|question-id> <text>, record a real answer to an
227
227
  // open planning question (moves open → answered, consumed on next refine).
228
228
  if (args[0] === 'answer') {
229
229
  if (!projectPlanningService || !projectId) {
@@ -233,7 +233,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
233
233
  const ref = args[1];
234
234
  const answerText = args.slice(2).join(' ').trim();
235
235
  if (!ref || !answerText) {
236
- ctx.print('Usage: /plan answer <question-number|question-id> <your answer>');
236
+ ctx.print('Usage: /project-plan answer <question-number|question-id> <your answer>');
237
237
  return;
238
238
  }
239
239
  const asNum = Number(ref);
@@ -243,7 +243,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
243
243
  const answerResult = await projectPlanningService.answerQuestion({ projectId, ...selector, answer: answerText });
244
244
  if (!answerResult.answered) {
245
245
  if (answerResult.reason === 'no-state') {
246
- ctx.print('No project planning state exists yet. Seed it with /plan <goal>.');
246
+ ctx.print('No project planning state exists yet. Seed it with /project-plan <goal>.');
247
247
  } else if (answerResult.reason === 'question-not-found') {
248
248
  const open = answerResult.openQuestions;
249
249
  const listing = open.length > 0
@@ -251,7 +251,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
251
251
  : ' (no open questions)';
252
252
  ctx.print(`No open question matched "${ref}". Open questions:\n${listing}`);
253
253
  } else {
254
- ctx.print('Usage: /plan answer <question-number|question-id> <your answer>');
254
+ ctx.print('Usage: /project-plan answer <question-number|question-id> <your answer>');
255
255
  }
256
256
  return;
257
257
  }
@@ -273,8 +273,8 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
273
273
  // corrupting the goal.
274
274
  if (args.length === 1 && PSEUDO_SUBCOMMAND_VERBS.has(args[0].toLowerCase())) {
275
275
  ctx.print(
276
- `Unknown /plan subcommand "${args[0]}": did you mean panel, approve, list, show, or status? ` +
277
- `To seed a planning goal, use /plan <a real sentence describing the change>.`,
276
+ `Unknown /project-plan subcommand "${args[0]}": did you mean panel, approve, list, show, or status? ` +
277
+ `To seed a planning goal, use /project-plan <a real sentence describing the change>.`,
278
278
  );
279
279
  return;
280
280
  }
@@ -289,7 +289,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
289
289
  state: {
290
290
  goal: taskDescription,
291
291
  knownContext: [
292
- `Workspace planning was seeded from the TUI /plan command.`,
292
+ `Workspace planning was seeded from the TUI /project-plan command.`,
293
293
  ],
294
294
  metadata: {
295
295
  active: true,
@@ -219,11 +219,11 @@ class PlanningModalSurface implements ConfigModalSurface {
219
219
 
220
220
  onAction(id: string, ctx: ConfigModalActionContext): void {
221
221
  if (id === 'refresh') { this.refresh(); ctx.setStatus('Reloading project planning state…'); return; }
222
- if (id === 'approve') { void ctx.executeCommand?.('plan', ['approve']); ctx.setStatus('Dispatched /plan approve.'); return; }
222
+ if (id === 'approve') { void ctx.executeCommand?.('project-plan', ['approve']); ctx.setStatus('Dispatched /project-plan approve.'); return; }
223
223
  if (id === 'dismiss') {
224
224
  // First-class, confirmed (host two-press) mutating dismiss.
225
- void ctx.executeCommand?.('plan', ['dismiss']);
226
- ctx.setStatus('Dispatched /plan dismiss.');
225
+ void ctx.executeCommand?.('project-plan', ['dismiss']);
226
+ ctx.setStatus('Dispatched /project-plan dismiss.');
227
227
  ctx.close();
228
228
  return;
229
229
  }
@@ -232,18 +232,18 @@ class PlanningModalSurface implements ConfigModalSurface {
232
232
  if (!question || actions.length === 0) return;
233
233
  const action = ctx.row ? actions.find((a) => a.id === ctx.row!.id) : undefined;
234
234
  if (!action || action.disabled) { ctx.print('Choose an answer option.'); return; }
235
- if (action.kind === 'approve') { void ctx.executeCommand?.('plan', ['approve']); ctx.setStatus('Dispatched /plan approve.'); return; }
235
+ if (action.kind === 'approve') { void ctx.executeCommand?.('project-plan', ['approve']); ctx.setStatus('Dispatched /project-plan approve.'); return; }
236
236
 
237
237
  const answerText = action.answer.trim();
238
238
  if (!answerText) { ctx.print('Choose a non-empty answer, or type an answer for the custom row.'); return; }
239
239
 
240
- // A canned answer to a REAL open question records structurally via /plan answer.
240
+ // A canned answer to a REAL open question records structurally via /project-plan answer.
241
241
  const isOpenQuestion = this.snapshot?.state?.openQuestions.some(
242
242
  (q) => q.id === question.id && (q.status ?? 'open') === 'open',
243
243
  ) ?? false;
244
244
  if (action.id !== 'custom' && isOpenQuestion) {
245
- void ctx.executeCommand?.('plan', ['answer', question.id, ...answerText.split(/\s+/)]);
246
- ctx.setStatus('Dispatched /plan answer for the current question.');
245
+ void ctx.executeCommand?.('project-plan', ['answer', question.id, ...answerText.split(/\s+/)]);
246
+ ctx.setStatus('Dispatched /project-plan answer for the current question.');
247
247
  return;
248
248
  }
249
249