@pellux/goodvibes-tui 2.1.0 → 2.1.1

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,12 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.1.1] - 2026-11-02
8
+
9
+ ### Fixed
10
+
11
+ - Restore local llama.cpp replies, including DeepSeek responses after tool calls, by excluding the active assistant rendering block from model requests. This release uses SDK 2.1.1 and daemon 1.29.1 and adds a terminal regression check for the outgoing conversation.
12
+
7
13
  ## [2.1.0] - 2026-09-30
8
14
 
9
15
  ### Added
@@ -1754,7 +1760,7 @@ First release of the best-in-class program: a full UX-first review of the codeba
1754
1760
  - `bun run tsc --noEmit --pretty false`
1755
1761
  - `bun run smoke:daemon`
1756
1762
  - local `npm pack` install smoke with `GOODVIBES_ASSET_SOURCE_DIR=dist`
1757
- - `/home/buzzkill/Projects/goodvibes-tui/dist/goodvibes-linux-x64 --version` from `/home/buzzkill/Projects/ttest1`
1763
+ - The compiled `goodvibes-linux-x64 --version` command from a separate test workspace.
1758
1764
 
1759
1765
  ---
1760
1766
 
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-2.1.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-2.1.1-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
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
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. Background work opens in large modals over the conversation: running agents, changed files and diffs, tokens and cost.
10
10
 
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">
11
+ <img src="docs/assets/splash.png" alt="GoodVibes 2.1.0 starting up in a terminal. A thin header reads GoodVibes 2.1.0 on the left and the git branch main with a changes dot and the model gpt-6.1-sol on the right. Centered below is a block-letter GOODVIBES wordmark in a cyan-to-violet gradient between two rules, the tagline, the version v2.1.0 with terminal AI assistant, the active model openai:gpt-6.1-sol with 29 tools, the working directory /tmp/orbit-watch, and a hint line reading Ctrl+P commands / ? help / F2 agents. At the bottom is an empty input box reading Ask anything, or type / for commands and @ for files, and a status line showing the normal permission mode, the directory and branch, a cost of zero dollars, an empty context bar at 0 percent of 1.1M, and ctrl+p menu." width="900">
12
12
 
13
13
  ---
14
14
 
@@ -47,21 +47,21 @@ Deeper install notes are in [docs/getting-started.md](docs/getting-started.md):
47
47
 
48
48
  ### The conversation loop
49
49
 
50
- <img src="docs/assets/conversation.png" alt="Two conversation turns in the terminal. The tail of an earlier answer describes the project as a minimal ISS pass tracker whose calculation is still a stub. Below it the user asks the model to grep for nextPass and list every file that references it; an assistant block shows one find tool call with a green checkmark, and its result is expanded as syntax-highlighted JSON with line numbers listing the two matching file paths and a count of 2. The final answer names src/passes.ts and src/index.ts as a bulleted list. The footer reads Token Usage: Input em dash, Output 264, Cache Read 0, Cache Write 0, Total 264, approximately 0.0040 dollars, above a green context usage bar at 4 percent." width="900">
50
+ <img src="docs/assets/conversation.png" alt="Two conversation turns. The user asks the model to search for nextPass and list every file that references it; the reply shows one collapsed find nextPass tool row with a green checkmark and the summary 2 matches in 2 files, then the answer listing src/index.ts and src/passes.ts. In the second turn the user asks, without running anything, for three things the tracker still needs, and the model answers with three short bullets about orbital-data refresh, pass-prediction validation, and alerts before upcoming passes. Each user message sits in a shaded block and each reply carries a small header with the model name, tool count, and elapsed time. The status line below the input shows the normal mode, the directory /tmp/orbit-watch, an estimated cost of about 3 cents, and the context bar at 1 percent, 14.4k of 1.1M tokens." width="900">
51
51
 
52
52
  Tool calls stream inline as the model makes them, then their results fold into a collapsible group so a long working session stays readable. Expand any group to see what actually came back. Assistant messages render markdown, syntax-highlighted code, and inline diffs, and any block can be collapsed, bookmarked, copied, or saved to a file.
53
53
 
54
- The footer keeps an honest running total: fresh input tokens separated from cache reads, output tokens, the estimated cost so far, and how much of the context window is used. When a model's price is not in the catalog, the cost is reported as unavailable rather than guessed.
54
+ The status line under the input keeps a running total: the estimated cost so far and how much of the context window is used. `/usage` breaks that down into fresh input tokens, cache reads, and output tokens. When a model's price is not in the catalog, the cost is reported as unavailable rather than guessed.
55
55
 
56
56
  ### Permissions and workspace trust
57
57
 
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">
58
+ <img src="docs/assets/workspace-trust.png" alt="A modal over the dimmed conversation, titled New workspace: choose a trust level, with an esc key on its right. Two options are listed: Trust this workspace, full capability, all tools may run, which is highlighted; and Keep restricted (read-only), explore safely, writes and commands are denied until trusted. The hint row reads up and down to move, Enter to choose. Behind it, the transcript shows the model starting exec mkdir -p build and waiting." 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 the model wants to write or run something in a new 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
- <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">
62
+ <img src="docs/assets/permission-prompt.png" alt="A Shell Execution Approval modal with an amber top edge over the dimmed conversation. The header reads EXECUTE and the session id. A shaded row shows the command mkdir -p build, followed by a red high risk tag beside Execute shell command. Three choices sit in a row, Allow once highlighted, Allow for session, and Deny, with d details on the right. The hint row reads: arrows to choose, Enter to confirm, y allow once, 1-4 remember, n deny, or type a reason to deny. Behind the modal the transcript line reads Waiting for your approval, exec, mkdir -p build." width="900">
63
63
 
64
- After that, the default permission mode is `prompt`: writes, edits, shell commands, network fetches, agent spawns, and MCP calls each stop and ask. The prompt shows what will run, in which directory, its assessed risk, and what it can affect. You can allow it once, deny it (optionally typing a reason the model sees), or remember the decision at whichever scope fits: this exact command, this command shape, this tool for the whole project, or just for the rest of the session.
64
+ After that, the default permission mode is `prompt`: writes, edits, shell commands, network fetches, agent spawns, and MCP calls each stop and ask. The prompt shows what will run and its assessed risk; `d` expands the details, including the working directory, what it can affect, and the raw arguments. You can allow it once, deny it (optionally typing a reason the model sees), or remember the decision at whichever scope fits: this exact command, this command shape, this tool for the whole project, or just for the rest of the session.
65
65
 
66
66
  Four other modes are available when prompting is not what you want:
67
67
 
@@ -74,7 +74,7 @@ Four other modes are available when prompting is not what you want:
74
74
 
75
75
  ### Models and providers
76
76
 
77
- <img src="docs/assets/model-picker.png" alt="A fullscreen model workspace. A left column lists routing targets: Main Chat set, Helper Model off, Tool LLM off, TTS LLM inherit, and Embeddings set. The right pane shows the selected target with its current model and a filter row for search, price, capability, grouping, and availability. Below, a table of models lists model key, display name, provider, context window, tier, and capability flags across 2030 catalogued entries, with a footer of list shortcuts." width="900">
77
+ <img src="docs/assets/model-picker.png" alt="The Models modal opened with /model. Tabs across the top name the routing targets: Main Chat, selected, then Helper (off), Tool LLM (off), TTS, and Embeddings. Below is a search field and a count reading available only, 2,574 of 3,214. The left column lists models grouped by provider, here abacusai, with each model's context window; the right pane describes the highlighted model, its provider, the current Main Chat model openai:gpt-6.1-sol, its status configured, its context window and capability tags, and the filter shortcuts for price, capability, availability, benchmark sort, and grouping. The footer reads Enter use for Main Chat, tab next target, ctrl+f pin, ctrl+r refresh catalog, and 2550 more." width="900">
78
78
 
79
79
  Models come from a live catalog, so the picker lists far more than a hardcoded set, filterable by search, price, capability, and availability. Separate roles route independently: your main chat model, a cheaper helper model for grunt work, a tool model, a TTS model, and an embeddings model can each point somewhere different.
80
80
 
@@ -82,13 +82,13 @@ The `synthetic` provider groups the same model across every backend that serves
82
82
 
83
83
  ### The Agents control room
84
84
 
85
- <img src="docs/assets/fleet-panel.png" alt="The Fleet panel open beside the conversation. It lists background work as rows with a kind, a name, elapsed time, token count, and cost: a CI watch poller and a runtime heartbeat watcher at 4 minutes 23 seconds each, and an idle code indexer. The detail region below shows the selected watcher's id, state idle, elapsed time, model, tokens, cost, a headline reading CI watch poller, healthy, an activity phase, and an approvals line. The action hints read j/k navigate, Enter attach, K kill, A archive finished, v archived, n host agent." width="900">
85
+ <img src="docs/assets/fleet-panel.png" alt="The Agents modal opened with F2, headed 0 running, the session's own cost, and the fleet cost. The filter field holds the word healthy and shows 3 of 8 rows. Under a running heading the list shows three watchers, CI watch poller, Daemon heartbeat, and Runtime heartbeat, each marked healthy with its elapsed time. The detail pane on the right describes the selected CI watch poller: watcher id ci-watch-poller, state idle, elapsed time, model unknown, tokens not applicable, cost unpriced, the headline CI watch poller, healthy, its activity phase, and approvals. The hint row reads Enter open full screen, s steer, ctrl+x stop, n new agent, v archive, a archive one, f follow." width="900">
86
86
 
87
- Work that is not conversation opens in modals instead of scrolling past in the transcript. Agents, reached with `F2`, is the live control room for agents, WRFC chains, workstreams, watchers, scheduled jobs and hosted sessions: what is running, for how long, at what token cost, and what it is waiting on. You can open a running agent to watch its live tail or steer it, back out with `Esc` without stopping it, stop it with `Ctrl+X` (it asks first), pause and resume, and archive finished work. Usage (`/usage`) shows context pressure, tokens and cost; Changes (`/changes`) shows changed files with a tinted diff, a semantic summary, hunk staging and review comments; Notifications (`/notifications`) keeps the history. The screenshot above predates the modal redesign.
87
+ Work that is not conversation opens in modals instead of scrolling past in the transcript. Agents, reached with `F2`, is the live control room for agents, WRFC chains, workstreams, watchers, scheduled jobs and hosted sessions: what is running, for how long, at what token cost, and what it is waiting on. You can open a running agent to watch its live tail or steer it, back out with `Esc` without stopping it, stop it with `Ctrl+X` (it asks first), pause and resume, and archive finished work. Usage (`/usage`) shows context pressure, tokens and cost; Changes (`/changes`) shows changed files with a tinted diff, a semantic summary, hunk staging and review comments; Notifications (`/notifications`) keeps the history.
88
88
 
89
89
  ### Everything else is a command
90
90
 
91
- <img src="docs/assets/help-overlay.png" alt="A searchable help overlay titled Help, Commands, with a search field and results grouped by category. Under Provider Accounts is the /accounts command with review, panel, show, routes, and repair subcommands and a one-line description. Under Fleet is /agents, described as hosting third-party coding agents as fleet rows over ACP. Under Experience is /approval, for reviewing action-specific approval classes. A line reads 147 below, and the hints read Up/Down Navigate, Enter Run, / Search, Esc Close." width="820">
91
+ <img src="docs/assets/help-overlay.png" alt="The Help: Commands modal opened with the question-mark key, with a Type to filter field and a count of 160 items. Commands are grouped under category headings: provider accounts with /accounts and its review, panel, show, routes, and repair subcommands, highlighted; agents with /agents, which opens Agents and lists or hosts third-party coding agents over ACP; experience with /approval; platform access with /auth and its long list of subcommands; local runtime with /bookmarks; experience with /bootstrap; and branches with /branch. The hint row reads up and down to move, Enter to run, and 153 more." width="820">
92
92
 
93
93
  Press `?` for a searchable, categorized list of every slash command with its arguments and description. The same list is generated into [docs/commands-reference.md](docs/commands-reference.md).
94
94
 
@@ -109,7 +109,7 @@ Press `?` for a searchable, categorized list of every slash command with its arg
109
109
  | `Ctrl+X` | Inside an agent or process view: stop it (press twice to confirm) |
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` (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).
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` (Agents), `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
 
@@ -213,12 +213,13 @@ bun run dev
213
213
  | Command | Does |
214
214
  | --- | --- |
215
215
  | `bun run dev` | Run the TUI from source |
216
- | `bun run test` | Run the suite through the parallel per-file runner |
216
+ | `bun run test:changed` | Run the test files your change affects (since `origin/main`) |
217
+ | `bun run test` | Run the whole suite through the parallel per-file runner (CI runs it on every push) |
217
218
  | `bun run build` | Compile `src/main.ts` into `dist/goodvibes` |
218
219
 
219
220
  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.
220
221
 
221
- Tests live under `src/test/`, mirroring the source tree, and cover contract, security, release-gate, runtime, renderer, view, 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`).
222
+ Tests live under `src/test/`, mirroring the source tree; byte-exact golden renderer frames are part of that suite. End-to-end tests under `src/test/e2e/` drive the compiled binary in a real terminal against a scripted model. CI also checks import cycles and layer boundaries (`scripts/check-architecture.ts`), and the release gates hold the performance budgets (`scripts/perf-baseline.json`). [Testing and validation](docs/testing-and-validation.md) says what runs where.
222
223
 
223
224
  Some decisions worth knowing before you read the source:
224
225
 
@@ -248,7 +249,7 @@ src/
248
249
  ├── cli/ flag parsing, management verbs, doctor, launch-time self-update
249
250
  ├── tools/, mcp/ TUI-local tool guards, MCP runtime reload
250
251
  ├── 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
+ ├── verification/, widget/ live verifier, the terminal widget module
252
253
  ├── utils/, scripts/ formatting and clipboard helpers, message processing script
253
254
  └── test/ the suite, mirroring the tree above
254
255
  ```
package/docs/README.md CHANGED
@@ -64,8 +64,11 @@ This directory contains the current product documentation for `goodvibes-tui`.
64
64
  - [Release and publishing](release-and-publishing.md)
65
65
  Release validation, GitHub CD, compiled binary releases, npm distribution, and the GitHub Packages mirror.
66
66
 
67
+ - [Testing and validation](testing-and-validation.md)
68
+ What runs while you work, on every push, and before a release; the test layers; the end-to-end tests against the built binary.
69
+
67
70
  - [Local verification](verification/local-verification.md)
68
- Inventory coverage, GoodVibes home audits, compiled CLI probes, authenticated daemon probes, and release-oriented local gates.
71
+ GoodVibes home audits, compiled CLI probes, and authenticated daemon probes against your own machine.
69
72
 
70
73
  - [Changelog](../CHANGELOG.md)
71
74
  Current release history. Older per-version release-note files remain under `docs/releases/` for historical releases, but `CHANGELOG.md` is the canonical current stream.
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "2.1.0"
6
+ "version": "2.1.1"
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.1.0",
3
+ "version": "2.1.1",
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",
@@ -36,10 +36,13 @@
36
36
  "build:all-shell": "bun run build:linux-x64 && bun run build:linux-arm64 && bun run build:macos-x64 && bun run build:macos-arm64 && bun run build:windows",
37
37
  "typecheck": "bunx tsc --noEmit",
38
38
  "typecheck:test": "bunx tsc --noEmit -p tsconfig.test.json",
39
- "typecheck:coverage": "bun run scripts/typecheck-coverage-rule.ts",
40
39
  "test": "bun run scripts/run-tests.ts",
41
- "test:coverage": "bun run scripts/coverage-gate.ts",
42
- "version": "bun run scripts/prebuild.ts",
40
+ "test:changed": "bun run scripts/run-tests.ts --changed=origin/main",
41
+ "test:e2e": "bun test --timeout=300000 src/test/e2e",
42
+ "test:e2e:fast": "bun test --timeout=120000 src/test/e2e/startup.e2e.test.ts src/test/e2e/first-turn.e2e.test.ts",
43
+ "test:e2e:release": "bun test --timeout=300000 src/test/e2e/daemon-adoption.e2e.test.ts src/test/e2e/wrfc-chain.e2e.test.ts src/test/e2e/turn-notification.e2e.test.ts src/test/e2e/resize.e2e.test.ts",
44
+ "version": "bun run scripts/release-prepare.ts --no-bump",
45
+ "release:prepare": "bun run scripts/release-prepare.ts",
43
46
  "preinstall": "sh scripts/check-bun.sh",
44
47
  "postinstall": "bun scripts/postinstall.js",
45
48
  "postbuild": "bun scripts/postinstall.js --no-download",
@@ -63,11 +66,7 @@
63
66
  "audit:home": "bun run scripts/audit-goodvibes-home.ts",
64
67
  "foundation:artifacts": "bun run scripts/export-foundation-artifacts.ts",
65
68
  "docs:commands": "bun run scripts/generate-command-reference.ts",
66
- "verification:ledger": "bun run scripts/verification-ledger.ts",
67
- "verification:live": "bun run scripts/verify-live.ts",
68
- "eval:gate": "bun run scripts/eval-gate.ts",
69
- "eval:gate:verbose": "bun run scripts/eval-gate.ts --verbose",
70
- "eval:baseline": "bun run scripts/eval-gate.ts --save-baseline"
69
+ "verification:live": "bun run scripts/verify-live.ts"
71
70
  },
72
71
  "license": "MIT",
73
72
  "repository": {
@@ -96,18 +95,18 @@
96
95
  "@anthropic-ai/bedrock-sdk": "^0.29.1",
97
96
  "@anthropic-ai/sdk": "^0.92.0",
98
97
  "@ast-grep/napi": "^0.42.0",
99
- "@pellux/goodvibes-sdk": "2.1.0",
100
- "@pellux/goodvibes-terminal-shell": "2.1.0",
98
+ "@pellux/goodvibes-sdk": "2.1.1",
99
+ "@pellux/goodvibes-terminal-shell": "2.1.1",
101
100
  "bash-language-server": "^5.6.0",
102
101
  "fuse.js": "^7.1.0",
103
- "@pellux/goodvibes-daemon": "1.29.0",
102
+ "@pellux/goodvibes-daemon": "1.29.1",
104
103
  "graphql": "^16.13.2",
105
104
  "jszip": "^3.10.1",
106
105
  "node-edge-tts": "^1.2.10",
107
106
  "onnxruntime-web": "^1.27.0",
108
107
  "openai": "6.46.0",
109
108
  "pyright": "^1.1.408",
110
- "simple-git": "^3.33.0",
109
+ "simple-git": "^4.0.2",
111
110
  "sql.js": "^1.14.1",
112
111
  "sqlite-vec": "^0.1.9",
113
112
  "tree-sitter-css": "^0.25.0",
@@ -121,15 +120,15 @@
121
120
  "zustand": "^5.0.12"
122
121
  },
123
122
  "devDependencies": {
124
- "@pellux/goodvibes-toolchain": "2.1.0",
123
+ "@pellux/goodvibes-toolchain": "2.1.1",
125
124
  "@types/bun": "^1.3.10",
126
125
  "typescript": "^5.9.3"
127
126
  },
128
127
  "optionalDependencies": {
129
- "@pellux/goodvibes-tui-linux-x64": "2.0.6",
130
- "@pellux/goodvibes-tui-linux-arm64": "2.0.6",
131
- "@pellux/goodvibes-tui-darwin-x64": "2.0.6",
132
- "@pellux/goodvibes-tui-darwin-arm64": "2.0.6"
128
+ "@pellux/goodvibes-tui-linux-x64": "2.1.1",
129
+ "@pellux/goodvibes-tui-linux-arm64": "2.1.1",
130
+ "@pellux/goodvibes-tui-darwin-x64": "2.1.1",
131
+ "@pellux/goodvibes-tui-darwin-arm64": "2.1.1"
133
132
  },
134
133
  "trustedDependencies": [],
135
134
  "overrides": {
@@ -53,8 +53,8 @@ export function renderCommandReferenceMarkdown(entries: readonly CategorizedComm
53
53
  lines.push('<!-- GENERATED FILE: DO NOT EDIT BY HAND.');
54
54
  lines.push(' Regenerate with `bun run docs:commands`.');
55
55
  lines.push(' Source of truth: the slash-command registry (src/input/commands.ts).');
56
- lines.push(' A drift check (src/test/release-gates/command-reference-gate.test.ts)');
57
- lines.push(' fails CI if this file is stale. -->');
56
+ lines.push(' Every build (scripts/prebuild.ts) and `bun run release:prepare`');
57
+ lines.push(' rewrite it, so it is current at each version bump. -->');
58
58
  lines.push('');
59
59
  lines.push('# Command Reference');
60
60
  lines.push('');
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { auditGoodVibesHome } from '../config/goodvibes-home-audit.ts';
5
- import { buildVerificationLedger } from './verification-ledger.ts';
6
5
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
7
6
 
8
7
  export type LiveVerificationStatus = 'pass' | 'warn' | 'fail' | 'skip';
@@ -232,15 +231,6 @@ export async function buildLiveVerificationReport(options: LiveVerificationOptio
232
231
  const token = options.token ?? readDaemonToken(homeDir);
233
232
  const checks: LiveVerificationCheck[] = [];
234
233
 
235
- const ledger = buildVerificationLedger(projectRoot);
236
- checks.push({
237
- id: 'verification-ledger',
238
- title: 'Verification inventory ledger',
239
- status: ledger.totals.localSignalPercent >= 90 ? 'pass' : 'fail',
240
- summary: `${ledger.totals.localSignalPercent}% local verification signal across ${ledger.totals.total} inventory items.`,
241
- detail: `${ledger.totals.localBehaviorPercent}% local behavior verified; ${ledger.totals.externalOutcomeRequired} item(s) require external outcomes.`,
242
- });
243
-
244
234
  const audit = await auditGoodVibesHome({ homeDir });
245
235
  const staleCandidates = audit.settings?.staleCandidates?.length ?? 0;
246
236
  checks.push({
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '2.0.15';
9
+ let _version = '2.1.1';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  // Only trust a version read from OUR OWN package.json. A Bun single-file
@@ -1,709 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { CONFIG_SCHEMA } from '@pellux/goodvibes-sdk/platform/config';
4
- import { FEATURE_SETTINGS } from '@pellux/goodvibes-sdk/platform/runtime/state';
5
- import { featureEnablementWrite, GOODVIBES_CLI_CATALOG } from '@pellux/goodvibes-terminal-shell';
6
- import { CommandRegistry } from '../input/command-registry.ts';
7
- import { registerBuiltinCommands } from '../input/commands.ts';
8
-
9
- export interface VerificationLedgerArea {
10
- readonly area: string;
11
- readonly total: number;
12
- readonly localSignalVerified: number;
13
- readonly localBehaviorVerified: number;
14
- readonly externalOutcomeRequired: number;
15
- readonly notes: string;
16
- }
17
-
18
- export interface VerificationLedger {
19
- readonly generatedAt: string;
20
- readonly areas: readonly VerificationLedgerArea[];
21
- readonly totals: {
22
- readonly total: number;
23
- readonly localSignalVerified: number;
24
- readonly localBehaviorVerified: number;
25
- readonly externalOutcomeRequired: number;
26
- readonly localSignalPercent: number;
27
- readonly localBehaviorPercent: number;
28
- };
29
- }
30
-
31
- /**
32
- * Baseline count of `Settings schema and persistence` items with real local
33
- * behavior verification, as measured when this ledger was first authored. New
34
- * schema keys added since then are accounted for explicitly (see
35
- * FEATURE_KNOB_LOCAL_SETTINGS) rather than folded into this number, so the
36
- * baseline stays an auditable fixed point.
37
- */
38
- const SETTINGS_BEHAVIOR_BASELINE = 184;
39
-
40
- /**
41
- * Config keys promoted into CONFIG_SCHEMA for the flag-gated feature knobs
42
- * (SDK commit f5c4af31, "config: add schema keys for flag-gated feature
43
- * knobs"). Adding these 28 keys raised the settings inventory `total` without a
44
- * matching rise in behavior-verified coverage, which dropped
45
- * `localBehaviorPercent` below its floor. Each key is a local, non-external
46
- * setting whose full persistence behavior, schema default, set → write →
47
- * reload round-trip, and reset-to-default, is exercised by
48
- * `src/test/verification/feature-knob-settings-persistence.test.ts`. They are
49
- * therefore genuinely behavior-verified locally and counted as such here.
50
- */
51
- /**
52
- * The daemon's own mailbox and calendar keys (`surfaces.email.*`,
53
- * `surfaces.calendar.*`), promoted into CONFIG_SCHEMA so the settings modal,
54
- * which renders from that schema, can actually show them. Before, the
55
- * daemon's handlers named these keys in their own error messages
56
- * ("Set surfaces.calendar.caldavUrl and surfaces.calendar.caldavUser") while
57
- * the UI that told an operator to set them could not display one of them.
58
- *
59
- * Declaring 25 keys raised the settings inventory `total` with no matching
60
- * behavior coverage, which dropped `localBehaviorPercent` below its floor,
61
- * the same arrival shape as the two lists above.
62
- *
63
- * THIS CONSTANT IS NOT A DIAL. Every key below is counted because
64
- * `src/test/verification/daemon-mailbox-settings-persistence.test.ts` runs the
65
- * same four-part persistence contract the other counted sets are counted for:
66
- * schema default exposure, `set()` through the key's own validator to disk,
67
- * reload into a fresh ConfigManager with read-back equality, and
68
- * reset-to-default that also survives reload. That test also asserts this list
69
- * is exactly the `surfaces.email.*` + `surfaces.calendar.*` schema key set and
70
- * overlaps neither other counted set, so nothing is double-counted and nothing
71
- * drifts in uncounted.
72
- *
73
- * PER-KEY EVIDENCE. All 42 are read by a LIVE consumer, which is why they were
74
- * declared at all: the SDK's mail and calendar gateway compositions
75
- * (platform/email/surface-config.ts and platform/calendar/caldav-gateway-config.ts)
76
- * resolve every one of them when the daemon serves `email.*` and `calendar.*`,
77
- * and the `surfaces.email.inbound.*` block is read by the daemon's inbound mail
78
- * reader, the poller/IDLE path that makes incoming mail visible at all.
79
- * The five password keys (email.password, email.imapPassword,
80
- * email.imap.password, email.smtp.password, calendar.caldavPassword)
81
- * additionally resolve through the daemon secret tier rather than from config,
82
- * and carry no secret value in config themselves. That is now enforced on the
83
- * write side too: all five are in `config/secret-config.ts`'s
84
- * SECRET_CONFIG_KEYS, so the settings modal and `/config set` route an entered
85
- * value into the secret store and leave only a `goodvibes://` reference in
86
- * config, rather than writing the password itself into a settings file.
87
- */
88
- export const DAEMON_MAILBOX_LOCAL_SETTINGS = [
89
- 'surfaces.email.host',
90
- 'surfaces.email.user',
91
- 'surfaces.email.username',
92
- 'surfaces.email.from',
93
- 'surfaces.email.password',
94
- 'surfaces.email.imapHost',
95
- 'surfaces.email.imapPort',
96
- 'surfaces.email.imapUser',
97
- 'surfaces.email.imapPassword',
98
- 'surfaces.email.imap.host',
99
- 'surfaces.email.imap.port',
100
- 'surfaces.email.imap.user',
101
- 'surfaces.email.imap.password',
102
- 'surfaces.email.imap.secure',
103
- 'surfaces.email.imap.mailbox',
104
- 'surfaces.email.imap.draftsMailbox',
105
- 'surfaces.email.smtp.host',
106
- 'surfaces.email.smtp.port',
107
- 'surfaces.email.smtp.password',
108
- 'surfaces.email.smtp.secure',
109
- // Inbound mail, the poller/IDLE reader the daemon runs to actually READ mail,
110
- // which arrived with the platform runtime. These are counted for exactly the
111
- // reason the keys above are: the same four-part persistence contract runs over
112
- // every one of them in
113
- // src/test/verification/daemon-mailbox-settings-persistence.test.ts, and the
114
- // list is asserted there to be the complete surfaces.email.*/surfaces.calendar.*
115
- // schema set. Arriving uncounted is what dropped localBehaviorPercent through
116
- // its floor when the schema grew.
117
- 'surfaces.email.inbound.enabled',
118
- 'surfaces.email.inbound.accounts',
119
- 'surfaces.email.inbound.source',
120
- 'surfaces.email.inbound.gmailPollSecondsExpecting',
121
- 'surfaces.email.inbound.gmailPollSecondsIdle',
122
- 'surfaces.email.inbound.mode',
123
- 'surfaces.email.inbound.pollIntervalSeconds',
124
- 'surfaces.email.inbound.idleReissueMinutes',
125
- 'surfaces.email.inbound.reconnect.maxBackoffSeconds',
126
- 'surfaces.email.inbound.notice.route',
127
- 'surfaces.email.inbound.notice.mode',
128
- 'surfaces.email.inbound.expectationWindowMinutes',
129
- 'surfaces.email.inbound.dedupTtlMinutes',
130
- 'surfaces.email.inbound.retentionDays',
131
- 'surfaces.email.inbound.maxRecords',
132
- 'surfaces.email.inbound.capabilityRecheckMinutes',
133
- 'surfaces.email.inbound.onInsufficientCapability',
134
- 'surfaces.calendar.caldavUrl',
135
- 'surfaces.calendar.caldavUser',
136
- 'surfaces.calendar.caldavPassword',
137
- 'surfaces.calendar.defaultCalendarId',
138
- 'surfaces.calendar.calendars',
139
- ] as const;
140
-
141
- export const FEATURE_KNOB_LOCAL_SETTINGS = [
142
- 'provider.optimizerMode',
143
- 'provider.optimizerPinnedModel',
144
- 'permissions.divergenceThreshold',
145
- 'permissions.maxDivergenceRecords',
146
- 'tools.overflowSpillBackend',
147
- 'notifications.burstWindowMs',
148
- 'notifications.burstThreshold',
149
- 'notifications.burstCooldownMs',
150
- 'fetch.sanitizeMode',
151
- 'fetch.trustedHosts',
152
- 'fetch.blockedHosts',
153
- 'security.tokenAudit.rotationCadenceDays',
154
- 'security.tokenAudit.rotationWarningDays',
155
- 'security.tokenAudit.managed',
156
- 'integrations.delivery.maxRetries',
157
- 'integrations.delivery.initialDelayMs',
158
- 'integrations.delivery.maxDelayMs',
159
- 'integrations.delivery.maxDlqSize',
160
- 'integrations.delivery.sloEnforced',
161
- 'policy.bundleSource',
162
- 'policy.bundlePath',
163
- 'agents.passiveInjection.budgetTokens',
164
- 'agents.passiveInjection.relevanceFloor',
165
- 'agents.passiveInjection.codeLimit',
166
- 'agents.contextCompactThreshold',
167
- 'runtime.toolBudget.maxMs',
168
- 'runtime.toolBudget.maxTokens',
169
- 'runtime.toolBudget.maxCostUsd',
170
- ] as const;
171
-
172
- /**
173
- * Config keys added by the paired-device and trigger-family domains in SDK
174
- * 1.14.0. Like FEATURE_KNOB_LOCAL_SETTINGS above, these arrived as new
175
- * CONFIG_SCHEMA entries that raised the settings inventory `total` with no
176
- * matching behavior coverage, dropping `localBehaviorPercent` below its floor.
177
- *
178
- * THIS CONSTANT IS NOT A DIAL. Each key below is counted because a test that
179
- * exercises it was actually written:
180
- * `src/test/verification/device-and-trigger-settings-persistence.test.ts` runs,
181
- * for every key in this list, the same four-part persistence contract the
182
- * feature-knob keys are counted for, schema default exposure, `set()` through
183
- * the key's own validator to disk, reload into a fresh ConfigManager with
184
- * read-back equality, and reset-to-default that also survives reload. That test
185
- * additionally asserts this list is exactly the non-enablement key set of both
186
- * domains and overlaps neither of the other counted sets, so nothing here is
187
- * double-counted and nothing drifts in uncounted.
188
- *
189
- * PER-KEY EVIDENCE, what is verified, and how live the key is TODAY.
190
- * "persistence" below means the four-part contract above; the second column
191
- * records whether anything in this tree (TUI or the pinned SDK) reads the key,
192
- * audited by tracing each key to its consumers.
193
- *
194
- * watchers.triggers.backoffLadderMs persistence · live (trigger supervisor retry ladder)
195
- * watchers.triggers.breakerStrikes persistence · live (strike breaker)
196
- * watchers.triggers.defaultCheckIntervalMs persistence · live (condition-check scheduler)
197
- * watchers.triggers.probeTimeoutMs persistence · live (probe execution)
198
- * watchers.triggers.maxConcurrentChecks persistence · live (check concurrency cap)
199
- * watchers.triggers.observationRingSize persistence · live (observation ring)
200
- * watchers.triggers.runHistoryLimit persistence · live (run history retention)
201
- * watchers.triggers.runHistoryTtlHours persistence · live (run history sweep)
202
- * watchers.triggers.eventLogLimit persistence · live (event log retention)
203
- * watchers.triggers.eventLogTtlHours persistence · live (event log sweep)
204
- * watchers.triggers.sweepIntervalMs persistence · live (housekeeping cadence)
205
- * watchers.triggers.supervisionTickMs persistence · live (supervision loop tick)
206
- * watchers.triggers.streamQueueLimit persistence · live (stream watcher queue)
207
- * watchers.triggers.streamBatchLines persistence · live (stream batching)
208
- * watchers.triggers.streamBatchIntervalMs persistence · live (stream batching)
209
- * watchers.triggers.onExitMaxDurationMs persistence · live (on-exit trigger duration cap)
210
- * watchers.triggers.onExitStdin persistence · live (on-exit stdin posture)
211
- * watchers.triggers.outputTailBytes persistence · live (captured output tail)
212
- * device.capabilities.allowAlwaysOffer persistence · behavior (durable-grant offer by sensitivity)
213
- * device.capabilities.requestTimeoutSeconds persistence · behavior (dispatch, wire payload and prompt deadline)
214
- * device.location.precision persistence · behavior (precise fix refused / never granted)
215
- * device.clipboard.readMode persistence · behavior (clipboard read refused / never granted)
216
- * device.capture.retentionHours persistence · behavior (capture TTL, swept off disk at expiry)
217
- * device.capture.maxArtifacts persistence · behavior (capture count cap at the sweep)
218
- * device.capture.sweepIntervalMinutes persistence · behavior (period of the sweep that reaps)
219
- * device.grants.expiryDays persistence · behavior (grant TTL, then asks again)
220
- * device.grants.maxPerNode persistence · behavior (per-node grant cap at the sweep)
221
- * device.grants.auditRetentionDays persistence · behavior (grant ledger retention)
222
- * device.nodes.maxPaired persistence · live (enforced at the pairing path)
223
- *
224
- * On `device.nodes.maxPaired`: the SDK enforces it where a device pairs,
225
- * PairingTokenManager.mint refuses a NEW node at the cap with
226
- * PairingLimitReachedError (code DEVICE_NODES_MAX_PAIRED, carrying the setting
227
- * name, the cap and the live count), mapped to HTTP 409 by the pairing and
228
- * pairing-handoff routes. An already-paired node supersedes its own record
229
- * instead of being refused, migration mints off the legacy shared token are
230
- * deliberately exempt, lowering the cap unpairs nobody, and a non-positive or
231
- * non-finite value reads as no cap so a broken setting cannot lock the owner
232
- * out. The cap is read per mint, so a change applies without a restart. What is
233
- * counted HERE is still only the persistence contract this repo's test
234
- * exercises, the enforcement itself is SDK-side and SDK-tested, and is recorded
235
- * in this column so the "how live is it" audit stays accurate, not to claim
236
- * coverage this repo did not write.
237
- *
238
- * On the other ten `device.*` rows: they are live now. This app composes the
239
- * platform's device posture runtime (src/runtime/device-posture-composition.ts)
240
- * on the same distributed runtime phones pair onto and the same approval broker
241
- * every other confirmation rides, registers the `phone` tool on it, and binds the
242
- * devices.* verbs to it, so each of these keys is read, per request and per
243
- * sweep, by the DeviceCapabilityService / DeviceGrantStore /
244
- * DeviceCaptureArtifactStore / DeviceHousekeeper that serve them. They were dark
245
- * before that: the policy struct carried matching defaults and named the keys in
246
- * its refusal messages, but nothing mapped configuration into it in either tree.
247
- * The mapping now lives in the SDK, so every daemon host honours it rather than
248
- * only whichever consumer had written its own.
249
- *
250
- * The second column above is upgraded to "behavior" on the strength of
251
- * `src/test/verification/device-posture-behavior.test.ts`, which drives THIS
252
- * app's composition with a real ConfigManager over a temp home and the platform's
253
- * real stores, stubs only the peer transport and the approval bridge, and asserts
254
- * per key at two values that the observable outcome differs, refusal code, how
255
- * authority was established, what reached the transport, what the prompt was
256
- * given, and what survives on disk after a sweep. It also invokes the `phone`
257
- * tool and the devices.* verbs off the composed runtime graph, because a mapping
258
- * nothing composes is the same defect in a different place.
259
- *
260
- * STILL NOT COUNTED, FOR A DIFFERENT REASON NOW: the 24 non-enablement
261
- * `voice.wake.*` keys from the same release. When this list was written they were
262
- * excluded because nothing captured audio at all. That is no longer true, this
263
- * terminal opens a recorder subprocess (src/audio/capture.ts), runs the SDK
264
- * detector over it through onnxruntime-web (src/audio/wake-inference.ts), and
265
- * hands both a confirmed wake's utterance and a push-to-talk recording to the
266
- * daemon's `voice.stt` verb. Every one of those rows is read by
267
- * resolveWakeRuntimeSettings and reaches this surface's runtime.
268
- *
269
- * They stay out of the NUMERATOR here because this constant counts exactly one
270
- * thing, keys whose four-part PERSISTENCE contract is exercised by
271
- * device-and-trigger-settings-persistence.test.ts, and that test does not cover
272
- * them. The behaviour they now drive is covered instead by the capture and wake
273
- * tests under src/test/audio/, which assert the frame path, the detection
274
- * chain, the disabled-means-no-capture rule, the supervisor's restart and latch,
275
- * and the recorder argv. Counting them in both places would double-count; adding
276
- * them here without writing their persistence round-trip would overclaim. So the
277
- * number this list raises coverage by is unchanged, and the reason it excludes
278
- * them is recorded above rather than left reading as "the feature does nothing".
279
- *
280
- * Every wake row is now in force on this surface, with one condition stated at
281
- * every surfacing: `voice.wake.vadThreshold` above 0 needs the speech gate's own
282
- * pinned artifact provisioned (`/voice wake setup` fetches it with the models),
283
- * and until it is, startup BLOCKS with that reason rather than scoring frames
284
- * ungated behind a row that says they are screened. `voice.wake.noiseSuppression:
285
- * speex` runs here: the filter is a WebAssembly module the SDK carries, applied
286
- * by the wrapper the listener and the push-to-talk session put around this
287
- * surface's capture opener.
288
- */
289
- export const DEVICE_AND_TRIGGER_LOCAL_SETTINGS = [
290
- 'watchers.triggers.backoffLadderMs',
291
- 'watchers.triggers.breakerStrikes',
292
- 'watchers.triggers.defaultCheckIntervalMs',
293
- 'watchers.triggers.probeTimeoutMs',
294
- 'watchers.triggers.maxConcurrentChecks',
295
- 'watchers.triggers.observationRingSize',
296
- 'watchers.triggers.runHistoryLimit',
297
- 'watchers.triggers.runHistoryTtlHours',
298
- 'watchers.triggers.eventLogLimit',
299
- 'watchers.triggers.eventLogTtlHours',
300
- 'watchers.triggers.sweepIntervalMs',
301
- 'watchers.triggers.supervisionTickMs',
302
- 'watchers.triggers.streamQueueLimit',
303
- 'watchers.triggers.streamBatchLines',
304
- 'watchers.triggers.streamBatchIntervalMs',
305
- 'watchers.triggers.onExitMaxDurationMs',
306
- 'watchers.triggers.onExitStdin',
307
- 'watchers.triggers.outputTailBytes',
308
- 'device.capabilities.allowAlwaysOffer',
309
- 'device.capabilities.requestTimeoutSeconds',
310
- 'device.location.precision',
311
- 'device.clipboard.readMode',
312
- 'device.capture.retentionHours',
313
- 'device.capture.maxArtifacts',
314
- 'device.capture.sweepIntervalMinutes',
315
- 'device.grants.expiryDays',
316
- 'device.grants.maxPerNode',
317
- 'device.grants.auditRetentionDays',
318
- 'device.nodes.maxPaired',
319
- ] as const;
320
-
321
- /**
322
- * Every feature's enablement settings key whose on/off writes round-trip
323
- * through a REAL on-disk ConfigManager in
324
- * `src/test/verification/../input/settings-modal-flag-persistence.test.ts`
325
- * ("every feature with an off position round-trips both directions"). These
326
- * are first-class schema keys under the dissolved feature model, so they are
327
- * genuinely behavior-verified locally. Keys already counted in the
328
- * feature-knob list are excluded to keep the sum honest.
329
- */
330
- const ENABLEMENT_KEYS_BEHAVIOR_VERIFIED = new Set(
331
- FEATURE_SETTINGS
332
- .filter((feature) => featureEnablementWrite(feature.id, true) !== null && featureEnablementWrite(feature.id, false) !== null)
333
- .map((feature) => feature.enablement.key)
334
- .filter((key) => !(FEATURE_KNOB_LOCAL_SETTINGS as readonly string[]).includes(key)),
335
- ).size;
336
-
337
- /** Settings with real local behavior verification: the authored baseline plus the persistence-tested feature-knob, device/trigger, and enablement keys. */
338
- /**
339
- * Config keys added by the LAN group-key layer: the automatic group-key
340
- * rotation interval, the dual-generation acceptance window around a rotation,
341
- * the discovery beacon interval, and the roster gossip interval.
342
- *
343
- * THIS CONSTANT IS NOT A DIAL, for the same reason as the two above. Each key
344
- * is counted because a test that exercises it was actually written:
345
- * `src/test/verification/cluster-group-settings-persistence.test.ts` runs the
346
- * same four-part persistence contract, schema default exposure, `set()`
347
- * through the key's own validator to disk, reload into a fresh ConfigManager
348
- * with read-back equality, and reset-to-default that survives reload.
349
- *
350
- * PER-KEY EVIDENCE. All four have a LIVE consumer in this build, which is more
351
- * than several of the keys counted above can claim:
352
- * cluster.keyRotationHours , read by resolveClusterGroupSettings and
353
- * compared against the current key's age in
354
- * the group runtime's rotation check.
355
- * cluster.keyRotationGraceMinutes, sets how long the previous generation
356
- * stays accepted after a scheduled
357
- * rotation; drives the keyring's accepted
358
- * generation set.
359
- * cluster.beaconSeconds , the discovery beacon timer interval, and
360
- * the basis of the "recently heard from"
361
- * window that decides which member mints a
362
- * rotation.
363
- * cluster.rosterGossipSeconds , how often the member list is shared.
364
- *
365
- * `cluster.enabled` is NOT here: it is a feature enablement key and the ledger
366
- * counts those in its own set.
367
- */
368
- export const CLUSTER_GROUP_LOCAL_SETTINGS = [
369
- 'cluster.keyRotationHours',
370
- 'cluster.keyRotationGraceMinutes',
371
- 'cluster.beaconSeconds',
372
- 'cluster.rosterGossipSeconds',
373
- ] as const;
374
-
375
- /**
376
- * Config keys for the daemon's payment capability: the master switch, which
377
- * card to use by default, the settlement currency, CVV handling, the six
378
- * budget knobs, the shipping-tier preference, the fourteen billing/shipping
379
- * address sub-fields, the two approval/veto windows, and the notify-channel
380
- * list. Card MATERIAL (number, expiry, CVV, cardholder name) is intentionally
381
- * NOT here, see the SDK's schema-domain-payments.ts header and this repo's
382
- * own input/payments-config.ts: that material lives write-only in the daemon
383
- * secret store, never in CONFIG_SCHEMA, so there is nothing for a persistence
384
- * contract to count.
385
- *
386
- * THIS CONSTANT IS NOT A DIAL, for the same reason as the sets above. Every
387
- * key below is counted because
388
- * `src/test/verification/payments-settings-persistence.test.ts` runs the same
389
- * four-part persistence contract the other counted sets are counted for:
390
- * schema default exposure, `set()` through the key's own validator to disk,
391
- * reload into a fresh ConfigManager with read-back equality, and
392
- * reset-to-default that also survives reload. That test also asserts this
393
- * list is exactly the CONFIG_SCHEMA's `payments.*` key set, so a future SDK
394
- * addition under this domain is caught (fails the inventory-integrity test)
395
- * rather than silently uncounted.
396
- *
397
- * PER-KEY EVIDENCE. All 28 are read by the daemon's own payment capability
398
- * (platform/payments, platform/control-plane/routes/payments.ts): the budget
399
- * keys size the daily/overage/tolerance pools, the window keys size the
400
- * veto/approval timers, cvvHandling and shipping.preferredTier select the
401
- * enum behaviors `platform/payments` documents, and the address sub-fields
402
- * are read directly by the daemon's shipping-address-required refusal and the
403
- * card-issuer address-verification path. `enabled` and `defaultCardId` gate
404
- * and select, respectively, whether/which card a purchase uses.
405
- */
406
- export const PAYMENTS_LOCAL_SETTINGS = [
407
- 'payments.enabled',
408
- 'payments.defaultCardId',
409
- 'payments.currency',
410
- 'payments.cvvHandling',
411
- 'payments.budget.dailyItem',
412
- 'payments.budget.dailyOverage',
413
- 'payments.budget.perPurchaseCeilingEnabled',
414
- 'payments.budget.perPurchaseCeiling',
415
- 'payments.budget.overageToleranceEnabled',
416
- 'payments.budget.overageToleranceDailyAllowance',
417
- 'payments.shipping.preferredTier',
418
- 'payments.billingAddress.name',
419
- 'payments.billingAddress.line1',
420
- 'payments.billingAddress.line2',
421
- 'payments.billingAddress.city',
422
- 'payments.billingAddress.region',
423
- 'payments.billingAddress.postalCode',
424
- 'payments.billingAddress.country',
425
- 'payments.shippingAddress.name',
426
- 'payments.shippingAddress.line1',
427
- 'payments.shippingAddress.line2',
428
- 'payments.shippingAddress.city',
429
- 'payments.shippingAddress.region',
430
- 'payments.shippingAddress.postalCode',
431
- 'payments.shippingAddress.country',
432
- 'payments.windows.vetoMinutes',
433
- 'payments.windows.approvalMinutes',
434
- 'payments.majorRetailersAdditional',
435
- 'payments.majorRetailersExcluded',
436
- 'payments.ebayMinSellerFeedbackCount',
437
- 'payments.ebayMinSellerPositivePercent',
438
- 'payments.notifyChannels',
439
- ] as const;
440
-
441
- /**
442
- * Connector settings, the daemon's Google-backed mail/calendar keys plus the
443
- * connected-host dial switch and the hosted-turn routing switch, declared as
444
- * described schema rows by platform runtime 2.0.8 (before that the connector
445
- * keys were cast onto the live config invisibly and the settings surface
446
- * answered "Unknown setting" for keys the daemon really reads). Counted for
447
- * exactly the reason DAEMON_MAILBOX_LOCAL_SETTINGS is: the same four-part
448
- * persistence contract runs over every one of them in
449
- * src/test/verification/connector-settings-persistence.test.ts, and that test
450
- * asserts this list is the complete schema set for the email, calendar and
451
- * google prefixes
452
- * plus the two switches. Arriving uncounted is what dropped
453
- * localBehaviorPercent through its floor when the schema grew. The three
454
- * secret-valued keys hold goodvibes://secrets/ references, never values,
455
- * same note as the mailbox passwords above.
456
- */
457
- export const CONNECTOR_LOCAL_SETTINGS = [
458
- 'email.enabled',
459
- 'email.imapHost',
460
- 'email.imapPort',
461
- 'email.imapSecurity',
462
- 'email.smtpHost',
463
- 'email.smtpPort',
464
- 'email.smtpSecurity',
465
- 'email.username',
466
- 'email.passwordRef',
467
- 'email.smtpPasswordRef',
468
- 'email.fromAddress',
469
- 'email.mailbox',
470
- 'email.draftsMailbox',
471
- 'calendar.google.clientId',
472
- 'calendar.google.clientSecretRef',
473
- 'calendar.google.icsUrl',
474
- 'calendar.microsoft.clientId',
475
- 'calendar.microsoft.clientSecretRef',
476
- 'google.oauth.projectId',
477
- 'google.oauth.publishingStatus',
478
- 'google.oauth.refreshToken',
479
- 'google.credentials.migratedFrom',
480
- 'daemon.connectedHost.enabled',
481
- 'hostedSessions.routeConversationTurns',
482
- ] as const;
483
-
484
- const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE
485
- + FEATURE_KNOB_LOCAL_SETTINGS.length
486
- + DEVICE_AND_TRIGGER_LOCAL_SETTINGS.length
487
- + CLUSTER_GROUP_LOCAL_SETTINGS.length
488
- + DAEMON_MAILBOX_LOCAL_SETTINGS.length
489
- + PAYMENTS_LOCAL_SETTINGS.length
490
- + CONNECTOR_LOCAL_SETTINGS.length
491
- + ENABLEMENT_KEYS_BEHAVIOR_VERIFIED;
492
-
493
- const EXTERNAL_SLASH_COMMANDS = new Set([
494
- 'auth',
495
- 'bridge',
496
- 'cloudflare',
497
- 'health',
498
- 'listener',
499
- 'login',
500
- 'logout',
501
- 'mcp',
502
- 'notify',
503
- 'pair',
504
- 'qrcode',
505
- 'remote',
506
- 'remote-env',
507
- 'remote-setup',
508
- 'runner-pool',
509
- 'scan',
510
- 'secrets',
511
- 'services',
512
- 'subscription',
513
- 'teleport',
514
- 'tts',
515
- 'tunnel',
516
- 'voice',
517
- ]);
518
-
519
- const EXTERNAL_CLI_COMMANDS = new Set([
520
- 'bridge',
521
- 'listener',
522
- 'pair',
523
- 'remote',
524
- 'run',
525
- 'serve',
526
- 'service',
527
- 'tui',
528
- 'web',
529
- ]);
530
-
531
- const ONBOARDING_CAPABILITIES = [
532
- 'local-tui-only',
533
- 'browser-access',
534
- 'network-access',
535
- 'webhook-events',
536
- 'external-integrations',
537
- 'cloudflare-batch',
538
- ] as const;
539
-
540
- const EXTERNAL_SURFACES = [
541
- 'bluebubbles',
542
- 'discord',
543
- 'googleChat',
544
- 'homeassistant',
545
- 'imessage',
546
- 'matrix',
547
- 'mattermost',
548
- 'msteams',
549
- 'ntfy',
550
- 'signal',
551
- 'slack',
552
- 'telegram',
553
- 'webhook',
554
- 'whatsapp',
555
- ] as const;
556
-
557
- function percent(numerator: number, denominator: number): number {
558
- return denominator === 0 ? 0 : Math.round((numerator / denominator) * 1000) / 10;
559
- }
560
-
561
- function listSlashCommands(): string[] {
562
- const registry = new CommandRegistry();
563
- registerBuiltinCommands(registry);
564
- return registry.getAll().map((command) => command.name);
565
- }
566
-
567
- /**
568
- * Built-in modals and views: every config-modal surface builtin-modals.ts
569
- * registers, plus every kit view modal the shell's view openers construct
570
- * (Agents, Usage, Changes, Notifications, the masked password prompt).
571
- */
572
- function countBuiltinModals(root: string): number {
573
- const surfaces = readFileSync(join(root, 'src', 'views', 'builtin-modals.ts'), 'utf8');
574
- const openers = readFileSync(join(root, 'src', 'shell', 'view-openers.ts'), 'utf8');
575
- const surfaceCount = [...surfaces.matchAll(/registerModalSurface\(/g)].length;
576
- const viewModals = new Set([...openers.matchAll(/new ([A-Za-z]+Modal)\(/g)].map((m) => m[1]));
577
- return surfaceCount + viewModals.size;
578
- }
579
-
580
- /**
581
- * The CLI command words this terminal actually accepts, read off the catalog
582
- * the parser is driven by rather than scraped out of a source file, the
583
- * ledger counts what ships, and a catalog entry IS what ships.
584
- */
585
- function listCliCommands(): string[] {
586
- return GOODVIBES_CLI_CATALOG.commands
587
- .map((spec) => spec.name as string)
588
- .filter((command) => command !== 'unknown');
589
- }
590
-
591
- export function buildVerificationLedger(root: string): VerificationLedger {
592
- const slashCommandNames = listSlashCommands();
593
- const cliCommandNames = listCliCommands();
594
- const slashCommands = slashCommandNames.length;
595
- const modals = countBuiltinModals(root);
596
- const cliCommands = cliCommandNames.length;
597
- const featureFlags = FEATURE_SETTINGS.length;
598
- const settings = CONFIG_SCHEMA.length;
599
- const externalSlashCommands = slashCommandNames.filter((command) => EXTERNAL_SLASH_COMMANDS.has(command)).length;
600
- const externalCliCommands = cliCommandNames.filter((command) => EXTERNAL_CLI_COMMANDS.has(command)).length;
601
-
602
- const areas: VerificationLedgerArea[] = [
603
- {
604
- area: 'Settings schema and persistence',
605
- total: settings,
606
- localSignalVerified: settings,
607
- localBehaviorVerified: SETTINGS_BEHAVIOR_VERIFIED,
608
- externalOutcomeRequired: settings - SETTINGS_BEHAVIOR_VERIFIED,
609
- notes: 'Every schema setting can be validated for schema/default/load/write/location; external side effects remain separate.',
610
- },
611
- {
612
- area: 'Feature flags',
613
- total: featureFlags,
614
- localSignalVerified: featureFlags,
615
- localBehaviorVerified: featureFlags - 4,
616
- externalOutcomeRequired: 4,
617
- notes: 'All flags can be loaded/toggled; a small surface/service subset still requires live external behavior.',
618
- },
619
- {
620
- area: 'Slash commands',
621
- total: slashCommands,
622
- localSignalVerified: slashCommands,
623
- localBehaviorVerified: slashCommands - externalSlashCommands,
624
- externalOutcomeRequired: externalSlashCommands,
625
- notes: 'Every command can be routed and invoked with a fake context; external/provider/device commands need live outcome checks.',
626
- },
627
- {
628
- area: 'Built-in modals and views',
629
- total: modals,
630
- localSignalVerified: modals,
631
- localBehaviorVerified: modals,
632
- externalOutcomeRequired: 0,
633
- notes: 'Modals and views can be rendered and input-tested against fake read models and real cached state.',
634
- },
635
- {
636
- area: 'Top-level CLI commands',
637
- total: cliCommands,
638
- localSignalVerified: cliCommands,
639
- localBehaviorVerified: cliCommands - externalCliCommands,
640
- externalOutcomeRequired: externalCliCommands,
641
- notes: 'Parser/help/status/package behavior is local; long-running TUI/service/remote flows require process or external checks.',
642
- },
643
- {
644
- area: 'External surfaces',
645
- total: EXTERNAL_SURFACES.length,
646
- localSignalVerified: EXTERNAL_SURFACES.length,
647
- localBehaviorVerified: 2,
648
- externalOutcomeRequired: EXTERNAL_SURFACES.length - 2,
649
- notes: 'Config/readiness can be local for all surfaces; real message delivery is external for most surfaces.',
650
- },
651
- {
652
- area: 'Onboarding capability bundles',
653
- total: ONBOARDING_CAPABILITIES.length,
654
- localSignalVerified: ONBOARDING_CAPABILITIES.length,
655
- localBehaviorVerified: 5,
656
- externalOutcomeRequired: 1,
657
- notes: 'Wizard state derivation/apply can be local; Cloudflare provisioning remains external.',
658
- },
659
- ];
660
-
661
- const total = areas.reduce((sum, area) => sum + area.total, 0);
662
- const localSignalVerified = areas.reduce((sum, area) => sum + area.localSignalVerified, 0);
663
- const localBehaviorVerified = areas.reduce((sum, area) => sum + area.localBehaviorVerified, 0);
664
- const externalOutcomeRequired = areas.reduce((sum, area) => sum + area.externalOutcomeRequired, 0);
665
-
666
- return {
667
- generatedAt: new Date().toISOString(),
668
- areas,
669
- totals: {
670
- total,
671
- localSignalVerified,
672
- localBehaviorVerified,
673
- externalOutcomeRequired,
674
- localSignalPercent: percent(localSignalVerified, total),
675
- localBehaviorPercent: percent(localBehaviorVerified, total),
676
- },
677
- };
678
- }
679
-
680
- export function renderVerificationLedgerMarkdown(ledger: VerificationLedger): string {
681
- const lines = [
682
- '# GoodVibes Verification Ledger',
683
- '',
684
- `Generated: ${ledger.generatedAt}`,
685
- '',
686
- '| Area | Total | Local verification signal | Local behavior | External outcome required | Notes |',
687
- '|---|---:|---:|---:|---:|---|',
688
- ...ledger.areas.map((area) => [
689
- `| ${area.area}`,
690
- area.total,
691
- area.localSignalVerified,
692
- area.localBehaviorVerified,
693
- area.externalOutcomeRequired,
694
- area.notes,
695
- ].join(' | ') + ' |'),
696
- '',
697
- '## Totals',
698
- '',
699
- `- Total inventory items: ${ledger.totals.total}`,
700
- `- Local verification signal: ${ledger.totals.localSignalVerified} (${ledger.totals.localSignalPercent}%)`,
701
- `- Local behavior verified: ${ledger.totals.localBehaviorVerified} (${ledger.totals.localBehaviorPercent}%)`,
702
- `- External outcome required: ${ledger.totals.externalOutcomeRequired}`,
703
- '',
704
- 'Local verification signal means the item can be exercised through schema, routing, persistence, render, readiness, daemon, CLI, or real-state checks without relying on an external SaaS/device outcome.',
705
- 'Local behavior verified means the behavior can be completed locally with in-process, CLI, daemon, tmux, or real persisted state.',
706
- '',
707
- ];
708
- return `${lines.join('\n')}\n`;
709
- }