6 Commits
Author SHA1 Message Date
CodeWhale BotandClaude Sonnet 5.5 5502f0a682 docs, scripts and CI: follow the host move; document stdio/websocket controls and usage statistics
Updates moved-file paths in the CI path filters, scripts, vitest config and editor/bridge sources; documents the Runtime API stdio/websocket controls and usage statistics (en and zh_hans); adds the usage-statistics and cache-call-receipts features to features.toml with regenerated web data; updates coarse-split design docs and crate maps, split scripts, the blocking-calls budget and release smoke scripts.

Verification: whole-tree evidence only, not per group. A native21 build of the full dirty tree completed (patch sha ea25c782...), and an unsealed nextest rerun of all 389 selected existing checks passed 389/389 on it. The earlier sealed native20 run failed 369/20 before fixes and was not re-sealed. No CI run.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-11-03 01:04:42 -08:00
CodeWhale Bot e34f207d21 refactor(engine): extract the shared runtime from the TUI [skip ci]
Move the Engine band, extension host, fixtures and shared test support into
codewhale-engine; wire TUI/CLI to that owner. Preserve human-decision
construction boundaries and lower reusable test helpers. Remove unused
copied dependencies and repair path-based guards, scripts and crate maps.

Correct the blocking scanner external-module scope leak; raised counts equal
the corrected pre-move source. Refresh embedded built-in hashes after path-only
bundle regeneration. Restore the provider-free prompt budget to the exact
pre-b984e31290 macOS bytes and representative identities; tool catalogs are
unchanged. Portable prompt-fixture pinning remains follow-up work.

Local validation: Engine/TUI/CLI clippy --tests --no-deps -D warnings passed
with the existing allow-list; production CLI check passed; fmt passed.
Focused native run: 705 passed / 83 failed initially; after hash/path repairs,
81 affected cases plus 2 existing build-relocation cases passed (83/83).
Thus 786/788 original cases pass; 2 compiled-release-image cases lack input
and are not qualified. Frozen input inventories unchanged. macOS test-link
unwind-table warnings remain. Four metric tests pass; all 55 budgets pass.
Host Node: 413 pass, 7 skip; Bun: 414 pass, 6 skip. Existing scanner checks:
34 + 33 pass. Compiled-delivery synthetic fixtures: 7 pass. Web: 42 pass.
Import, blocking, dead-code, coarse-map, version and facts checks pass.

CW-7701. No hosted CI, provider call, release build or publication.
Stage A part 2 and the old App Server/ratatui dependencies remain separate.
2026-10-31 18:55:14 -07:00
CodeWhale BotandClaude Sonnet 5.5 3b35f098f7 tooling(split): split tooling v2 (S01/W0): journalled mover, re-export resolver, G11 preflight, widen v2, strict+file counters
Scripts only under scripts/split/. No Rust, no manifest, no budget touched.
Base 00be3d2e4f. tui strict production [M] 634,761 -> 634,761, file level [F]
943,686 -> 943,686, largest SCC 79 -> 79 (partition_report --non-test: [79, 8,
1, 1, 1]). Nothing was compiled: this is python; the rewrites it produces were
checked with rustfmt parse/format, module_graph.py, the path-keyed budget
scripts and counts, on a scratch `git init` export of this base (never the
shared tree). A compile of a rewritten tree is the first Lane A window.

What changed and why (reasoning lives here, not in comments):

move-modules.py (rewritten). R2.5: the old tool exited 0 in a tree without
.git after "moved 6 module(s)" (git failures were check=False), and its
dry-run headline (354 files / 3,487 lines) counted 12 files / 212 lines
inside the moved set that a real run never rewrites.
- --dest-crate/--src-crate replace every hard-coded runtime literal; --json;
  --require-clean (dirty set from `git status --porcelain -z` plus files
  younger than --quiet-minutes, default 15); --apply-cfg (flips only
  `#[cfg(test)]` items that code outside the moved set uses; never touches
  cfg(not(test)) forks, which are an FD-04 decision); --restore <journal>.
- No git mv (the shared index is not touched): plain file operations. Every
  write is compare-and-swap (stat+sha against the bytes the plan was computed
  from, temp file, os.replace) with a pre-image journal under CW/backups/;
  a change between plan and write refuses before writing (exit 3); a change or
  SIGTERM/SIGHUP/SIGINT during the write phase restores every pre-image and
  removes created files and directories (exit 4). git/git grep failures and a
  missing or foreign .git are fatal (exit 2). --require-clean refusals exit 5.
- Rewritten Rust goes through `rustfmt --edition 2024 --emit stdout` on stdin
  per file; a file whose base text is not rustfmt-stable (the 17 include!d
  test_cases_*.rs fragments) is left exactly as rewritten, so no foreign line
  is reflowed.
- Coverage: plain and grouped `use crate::{..}` (3 sites), alias, unqualified
  `<m>::` in the source lib (5 sites; cfg(test)-only use gets a cfg(test)
  import), `<dest>::` -> `crate::` inside moved files (code only), `codewhale_tui::<m>`
  in every other Rust file (cli config_keys 15 lines), sweep of path strings
  (plain, `tui/src/..`, segmented `"tui" / "src" / "<m>"`: this adds
  scripts/check_orcarouter_contract.py, which the old sweep missed) with
  vendor/, crates/config/, scripts/split/, CHANGELOG* and web/data/*.generated.json
  skipped; --derive-facts regenerates web/data (journalled pre-images).
- Reports (and plans, never applies): residue edges from moved files to
  staying modules, super-root residue, manifest needs (deps, features,
  consumer crates), cfg sites by class.
reexports.py (--resolve-reexports): resolves `pub use` facade names (globs
through the upstream sources and the build.rs-generated constants via
catalog_corrections.json / provider_descriptors.json) and rewrites
`crate::config::NAME` and grouped lists to the upstream path.
preflight.py (G11): whole-repo include_str!/include_bytes!/include!/#[path]
resolution against the old-to-new map, features.toml owners, path strings the
sweep cannot fix, `../src/<m>` references, CI test identities; exit 1 on residue.
widen.py: --dest-crate/--src-crate/--packages, signal-safe lints restore
(SIGTERM/SIGHUP/SIGINT and atexit; a manifest left capped by a killed run is
restored first), atomic writes, --prepass static widening, unused_imports
removed with the compiler's machine-applicable suggestions.
count_lines.py promoted from the plan's scratch (identical [M]) with the [F]
counter beside it. testsplit.py: function-level move/stay classifier with call
closure. partition_report/closure_report --non-test; module_graph --tui-src/
--runtime-src and a regex-driven lexer (11.0 s -> 2.5 s over 1,585 files,
0 mismatches against the old lex_mask on every tracked .rs).

Acceptance (scratch export of 00be3d2e4f, config settings config_persistence
config_keys oauth codex_model_cache):
- dry run: staying callers 342 files / 3,275 lines + inside the moved set 12 /
  212 = the old tool's 354 / 3,487; grouped 3; self paths 105 + 5 in comments
  = 110; other crates 2 files / 16 lines (15 cli + 1 runtime doc comment);
  sweep 31 files (+15 skipped); 81 cfg sites (53 test items, 11 test
  mods, 17 not(test) forks = the brief's 17); 9 s.
- real rewrite: 403 paths, 18.6 s, counts identical to the dry run line for
  line; 334 of 351 changed .rs files rustfmt-stable (the 17 others are the
  base-unstable fragments); no `crate::<moved>` left in tui, no
  `codewhale_tui::<moved>` in cli; module_graph.py --check refuses this
  premature move with 43 residue edges (it is the closedness gate working).
- closed module (remote_setup) real move: module_graph --check PASS with the
  unchanged baseline, check-blocking-calls-budget PASS (543 sites, 165 files;
  the sweep re-keyed the path-keyed JSON), dead-code budget PASS 209,
  check-provider-registry PASS, G11 unresolved 0.
- guards on the scratch repo: change between plan and write -> exit 3 nothing
  written; change at write 201 of 403 -> exit 4 and `git status` back to only
  the concurrent edit; SIGTERM at 201 of 403 -> exit 4, status empty; dirty
  file and a file touched < 15 min -> exit 5; no .git -> exit 2.
- widen.py: SIGTERM and SIGHUP while a compile round runs -> exit 130 and
  crates/runtime/Cargo.toml back to `[lints] workspace = true`; a capped
  manifest from an earlier run is restored at start; with a fake cargo JSON
  stream the E0603 widening, --prepass and the unused-import removal applied.
- reexports: 226 config names (213 via models) -> 1,109 direct lines in 142
  files and 68 grouped lists in 60 files outside the cluster, 166 files; the
  ProviderKind figure matches the plan exactly (339 lines / 95 files); the
  plan's 1,189 / 190 and 89 / 75 are not reproduced and likely include the
  cluster's own files; module_graph --check unchanged afterwards.
- preflight on the cluster: residue 8 = lib.rs:20230 include_str, tests/
  integration/main.rs:16 (path + relative), 5 skip-listed mentions in
  crates/config (catalog_corrections.json:4584 and models_dev.bundled.json:11769
  key a source receipt `crates/tui/src/config/models.rs`), vendor picker.rs.
- testsplit: config/tests.rs 407 tests (378 move, 29 stay), scope_tests.rs 8
  (0 move: needs provider_readiness/fleet), whole cluster 687 tests (649 move,
  38 stay); count_lines [M]/[F]: tui 634,761/943,686, cli 20,204/33,761,
  runtime 9,526/15,121, config 29,415/35,830, app-server 7,572/13,095.

Not proven: no cargo build of any rewritten tree; widen.py's compile loop ran
only against a fake cargo; --apply-cfg flipping is planned, not applied or
compiled; Windows/FreeBSD cfg code; whether any test reads the source-receipt
keys in crates/config assets as paths; the re-export resolver does not
rewrite relative `config::NAME` uses (none found at this base); SCC/edge
counts were not re-run after a rewrite (the cargo-free G2 stays Lane A's).

Evidence: /Volumes/VIXinSSD/CW/artifacts/crate-split-w0-20261025/proof-w0.log
and guard-demos.log. Dirty work of other writers in the checkout was left
untouched (about 20 tui/core/models files, window W1).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-25 14:31:42 -07:00
CodeWhale BotandClaude Sonnet 5.5 ee60ebeb56 chore(split): add partition_report.py, the extraction-order measurement for the crate split
`docs/design/TUI_DECONSTRUCTION.md` step 0 asks for measurement before more
crates are cut, and none existed: scripts/split held only the runtime->UI
ratchet, the mover and the visibility widener. `partition_report.py` reuses the
ratchet's lexer and module graph and condenses the production edges into
strongly connected components:

- per top-level module: production and test lines, distinct modules it
  references and is referenced by, cycle size, and an extraction wave (the
  longest chain of components it still depends on; wave 0 depends on nothing
  left to move) with the modules that block it
- `--scc-edges N`: the N heaviest production edges inside the largest cycle,
  with the items they name
- `--json` and `--module NAME`

`module_graph.py` gains `load_graph()` (the graph loading `build_report` already
did, now callable); `build_report` uses it and its behavior is unchanged.

First run on this tree: the runtime is one 100-module cycle (the design doc
counted 85); 30 modules (~17k production lines) are wave 0 and claimable now;
the cycle contains `tui`, `commands` and `lib.rs` only through the 11
production back-edges the ratchet still counts (runtime_api->commands 9,
tools->commands 2).

Evidence (local): `module_graph.py --check` passes with identical totals
(prod 11, test 35, 116 closure modules); `test_check_command_crate_boundaries.py`
30 tests OK; the new report runs end to end.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-09 19:54:57 -07:00
CodeWhale Bot 5cf09da776 feat: consolidate 0.10.1 Engine, reviewed TS mods and Ratatui integration
Use the existing Engine for ACP, children and recursive RLM; retain captured
approval, deadline, history/checkpoint, delivery and one-bill usage authority.
Move legacy thread controls to the authenticated canonical owner with complete
history and durable operation receipts. Keep Rust wire/policy authority and
opt-in extension/Native execution; adopt the pinned Ratatui kit and source/docs.

Exact V22 local validation: Rust 1351 passed/0 failed/0 ignored; additional
localization 54 passed/0 failed/0 ignored; workspace/all-target/all-feature
Clippy passed under existing CI policy.
CLI: 3 actual processes / 6 checks / 6 local fixture requests / 0 paid calls; approval, resume, cancellation and EOF effects passed.
npm test + check:web: 1180 passed / 0 failed / 7 explicit skips (wrapper101, SDK19, host378, website682); strict TypeScript and production web passed.

No hosted/platform/install/live ChatGPT/DO adapter/publication/release claim.

Signed-off-by: CodeWhale Bot <[email protected]>
2026-10-03 05:11:01 -07:00
24a7b99a73 refactor(split): codewhale-runtime crate and boundary ratchet (RS-0..RS-7) (#6586)
* ci(split): RS-0 runtime -> UI boundary ratchet and a hermetic step that cannot pass empty

First rail of the runtime/TUI crate split.

- scripts/split/module_graph.py: module graph of crates/tui/src and
  crates/runtime/src with a comment/string-masking lexer and grouped
  `use crate::{...}` expansion. Computes the runtime closure from the eight
  seed modules (plus everything already in crates/runtime, plus test-only
  support modules its tests use) and counts, per module pair:
  prod / test references into UI code (tui, commands, remote_control,
  context_report, composer_*, private lib.rs items), "late" references into
  modules that move after the core (exec_agent, route_preferences), UI-library
  uses (ratatui, crossterm, codewhale_tui, codewhale_palette), and intra-doc
  links into UI code.
- scripts/runtime-boundary-baseline.json: prod 84, test 95, late 2, uilib 13,
  doc 1 across 101 closure modules. Any rise or new pair fails with file:line;
  a drop the baseline did not record also fails (`--update` lowers it and
  refuses to raise).
- scripts/check-command-crate-boundaries.py is now table-driven
  (BoundaryRule: package, metadata|tree dependency mode, forbidden packages,
  source scan) and runs the ratchet. `tree` mode reads `cargo tree -p`, which
  resolves features per package; `cargo metadata` unifies them and would see
  palette's ratatui feature the TUI enables. The runtime entry lands with the
  crate (RS-2).
- ci.yml hermetic safety step: `cargo test ... -- auto_review authority
  sandbox` becomes `cargo nextest run --no-tests=fail -E 'test(auto_review) |
  test(authority) | test(sandbox)'`, and the job installs nextest. After the
  modules move, the old step would have matched 0 tests and passed.

Tests: python3 scripts/test_check_command_crate_boundaries.py -> Ran 19
tests, OK (11 existing + 8 new: tree mode, lexer masking, grouped imports,
late/uilib edges, runtime-crate closure, rise/drop, checked-in baseline).
python3 scripts/check-command-crate-boundaries.py -> PASS.
No Rust changed; no cargo build run for this commit.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* docs(split): RS-1 record the runtime split sequence and its three departures

docs/design/TUI_DECONSTRUCTION.md gains the runtime split sequence (rails,
crate with live code, cut upward edges one blocker per slice, rename-only
core move, then the client contract) and records where it departs from the
older order in the same file, with why each is safe:

1. config moves inside the runtime's strongly connected component instead
   of leaving first (#6034/#6143 become internal runtime work);
2. loop convergence is not a precondition for moving the engine, because
   run_subagent and Engine::run_turn move into the same crate;
3. the moves use one root path alias in the TUI lib.rs, deleted at the end,
   instead of rewriting every caller in the moving change.

It also states that the engine, turn loop and tools go into
codewhale-runtime (no separate codewhale-engine), why the runtime cannot
live in crates/core (command-contract depends on core), and that the TUI
stays the only terminal-output owner behind the host_terminal port.

docs/ARCHITECTURE.md points at that sequence in its boundary note and
corrects crates/secrets's ownership (it also owns the shared sanitizer and
redaction).

Docs only; no build or test run.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* refactor(split): RS-2 create codewhale-runtime with 19 live leaf modules

`git mv` of the 19 leaf modules that reference nothing outside the batch in
production or test code and touch no UI library (7,149 lines):
session_tree native_memory goal_loop context_budget continual_harness
skill_state tool_history_repair retry_status llm_response_cache sleep_guard
media_originals regex_cache elapsed workspace_discovery hashing fast_hash
prompt_zones safe_label model_context.

- crates/runtime (codewhale-runtime): publishable (version.workspace, no
  `publish = false`), `[lints] workspace = true`, the TUI's crate-level
  `#![allow(clippy::uninlined_format_args)]` copied, no build.rs (nothing in
  the batch needs one). Listed in scripts/release/crates.sh after
  config/core/memory/models and before tui. The first crates.io
  publication of the new name happens at the next release run; that is a
  release-owner decision before this merges.
- TUI: 19 `mod` lines replaced by one private alias block
  `use codewhale_runtime::{...};`, so no TUI file's content changes.
  examples/zz_perf_probe.rs uses `codewhale_runtime::session_tree` instead of
  a `#[path]` include.
- Visibility widened only where the compiler asked (scripts/split/widen.py,
  lints capped to warnings during the loop, final pass with lints on):
  25 items `pub(crate)` -> `pub` (fast_hash aliases, llm_response_cache
  API, regex_cache::compile_user_regex, safe_label API and
  safe_error_text, tool_history_repair receipt, workspace_discovery
  helpers, hashing), and 12 `expect(dead_code)` / `cfg_attr(not(test),
  expect(dead_code))` attributes removed because exported items are no
  longer dead. No security-module items in this batch.
- `test-support` feature: retry_status's per-thread test state and readers,
  SafeLabel::is_redacted and the WorldState diff renders move from
  `cfg(test)` to `cfg(any(test, feature = "test-support"))`, all `pub`; the
  TUI enables it from [dev-dependencies] only.
- Exported-only fixes: five rustdoc links from public `safe_error_text` to
  private items became code spans; `WorldState::len` gets a local
  `allow(clippy::len_without_is_empty)` instead of new API.
- Path sweep (scripts/split/move-modules.py): docs, AGENTS.md,
  check-blocking-calls-budget.json key, check-runtime-contract-budget.py
  fragment path, dev-test.sh examples; dev-test.sh gains a `runtime` area.
- Boundary checker: codewhale-runtime rule in `tree` mode (cargo tree -p,
  per-package features) forbidding codewhale-tui/cli, ratatui*, crossterm,
  ansi-to-tui and kit crates, plus a source scan of crates/runtime/src.
  Ratchet unchanged: prod 84, test 95, late 2, uilib 13, doc 1.

Verification (local):
- widen.py final pass: cargo check -p codewhale-runtime -p codewhale-tui
  --lib --tests --examples with lints on: 0 errors.
- cargo check -p codewhale-runtime --all-targets (standalone features): ok.
- cargo test -p codewhale-runtime --lib: 127 passed; 0 failed.
- cargo clippy -p codewhale-runtime --all-targets --all-features -D warnings: clean.
- RUSTDOCFLAGS=-Dwarnings cargo doc -p codewhale-runtime --no-deps: clean.
- cargo tree -p codewhale-runtime -e normal,build: 0 ratatui/crossterm/tui.
- python3 scripts/test_check_command_crate_boundaries.py: 21 tests OK;
  check-command-crate-boundaries.py: PASS.
- validate-crate-publish-order.test.sh: passed.
- check-blocking-calls-budget.py: 717 sites, within budget.
Not run locally: TUI test execution (the TUI test binary build hit ENOSPC
on the shared volume; TUI tests are type-checked only), TUI clippy,
check-runtime-contract-budget.py measurement (it builds the TUI tests).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* refactor(palette): RS-3 make ratatui an optional default feature

The headless runtime needs palette's theme-setting and hex normalizers
(settings.rs) and RGB tokens (logging.rs), but palette pulled in ratatui
unconditionally. `ratatui` is now a default feature; without it the crate
is the renderer-free core:

- ids.rs (new): ThemeId with from_name/name/display_name/tagline,
  SELECTABLE_THEMES, normalize_theme_name, USER_THEME_PREFIX,
  normalize_user_theme_selector, normalize_theme_setting, and a
  `(u8, u8, u8)` hex parser `parse_hex_rgb` behind normalize_hex_rgb_color
  (same `#rrggbb` output as before).
- rgb.rs (new): the 161 `*_RGB` tuple tokens, split out of tokens.rs with
  their comments; tokens.rs keeps the 103 `Color` roles.
- Behind `#[cfg(feature = "ratatui")]`: adapt, contrast, detect, grammar,
  osc11, themes (UiTheme, ThemeId::ui_theme, parse_hex_rgb_color,
  hex_rgb_string), tokens, user_theme, and the crate tests.
- The public API with default features is unchanged (lib.rs re-exports the
  same names), so codewhale-tui needs no edit. The runtime will depend on
  `codewhale-palette = { default-features = false }` when settings/logging
  move (RS-14).
- Ratchet: codewhale_palette is no longer a UI library for the runtime
  closure; uilib 13 -> 10 (settings.rs's 3 palette uses drop out).

Verification (local):
- cargo check -p codewhale-palette --no-default-features --all-targets: ok.
- cargo test -p codewhale-palette: 81 passed; 0 failed.
- cargo clippy -p codewhale-palette --all-targets -D warnings (default and
  --no-default-features): clean.
- RUSTDOCFLAGS=-Dwarnings cargo doc -p codewhale-palette --no-deps
  --no-default-features: clean.
- cargo check -p codewhale-tui --lib --tests: ok.
- python3 scripts/test_check_command_crate_boundaries.py: 21 tests OK.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* refactor(split): RS-4 the host_terminal port, with raw mode as its first use

Runtime code toggled crossterm raw mode directly around interactive
children in three places (tools/shell.rs x2, shell_dispatcher.rs), each with
its own copy of a restore guard. That is the only crossterm use left in the
runtime closure's production code.

- codewhale_runtime::host_terminal: the one port for terminal side effects.
  `HostTerminal` trait (suspend_raw_mode / resume_raw_mode), a first-wins
  `OnceLock` install, a no-op host when nothing is installed, and
  `suspend_raw_mode()` returning a `RawModeSuspension` guard that resumes
  only if raw mode was on (issue #1690 semantics). RS-7 adds the
  notification-delivery methods to the same trait.
- The three call sites use `crate::host_terminal::suspend_raw_mode()`; the
  three hand-written guard structs are deleted.
- tui/ui/terminal.rs: `TuiHostTerminal` implements the port with crossterm;
  `install_host_terminal()` is called at the top of `run_with_args`, so
  every mode the binary runs (interactive, exec, serve) keeps today's
  behavior. Stdio hosts can later be launched without it.
- integration harness: `use codewhale_runtime::host_terminal;` at the
  harness root, like `tool_parser`, for its `#[path]` shell_dispatcher.
- Ratchet: uilib 10 -> 1 (tools|crossterm 7 -> 1, the remaining one is a
  roster_routes test; shell_dispatcher|crossterm 3 -> 0).

Verification (local):
- cargo test -p codewhale-runtime --lib: 128 passed; 0 failed (adds
  host_terminal::tests::no_host_suspension_is_a_no_op).
- cargo clippy -p codewhale-runtime --all-targets --all-features -D warnings: clean.
- RUSTDOCFLAGS=-Dwarnings cargo doc -p codewhale-runtime --no-deps: clean.
- cargo check -p codewhale-tui --lib --tests --examples (incl. the
  integration harness): ok.
- python3 scripts/split/module_graph.py: PASS after --update.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* refactor(split): RS-5 split the voice capture core out of commands::voice

runtime_api/voice.rs (the `/v1/voice` routes) imported
`crate::commands::voice`, a UI module, for recorder detection, ASR choice
and the headless dictation cycle.

- New runtime-side module `crate::voice` (774 lines) holds everything that
  is not a slash command: recorder detection and `is_available`, WAV
  encoding, `record_audio`, the send-suffix contract (`SEND_PHRASES`,
  `split_send_suffix`), chat-completions transcription (provider, Groq,
  local whisper), voice-control, ASR selection, and the headless
  `dictate_once` / `DictateMode` / `DictateError` / `DictationOutcome`,
  with their tests (vendor pin, WAV header, send suffix, recorder probe).
  It uses `config` and `client`, so it moves into codewhale-runtime with
  the core (M1).
- `commands::voice` keeps the three slash commands, the recording status
  line, `VoiceCaptureOutcome` and `capture_and_transcribe` (it drives the
  composer while the user speaks), importing the core from `crate::voice`.
  Seven core functions become `pub(crate)` for that loop; nothing else
  changes.
- runtime_api/voice.rs imports `crate::voice`.
- Ratchet: prod runtime_api|commands 10 -> 9.

Verification (local): cargo check -p codewhale-tui --lib --tests: ok.
The moved tests live in the TUI test binary, which was not linked locally
(shared volume at ~8 GB free); they are type-checked only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* refactor(split): RS-6 move the context-window formatter down into utils

fleet/capability_badges.rs (runtime) imported
`tui::model_picker::format_picker_context_window`, a pure formatter.

- It moves to `crate::utils::format_context_window` (same body: `1M`,
  `1.05M`, `262K`, `500`); the picker's 10 call sites and the badges' 6
  (including its `1M/1.05M/262K/500` assertions) call it there.
- Not merged with `agent_roster::format_tokens` (`1.2k`): that labels usage,
  not window size, and merging would change output.
- Ratchet: prod fleet|tui 1 -> 0.

Verification (local): cargo check -p codewhale-tui --lib --tests: ok.
The badge and picker tests are in the TUI test binary, which was not
linked locally (shared volume at ~8 GB free); type-checked only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* refactor(split): RS-7 notification policy down, delivery behind host_terminal

Runtime code imported `tui::notifications` and friends: the `notify` tool
(tools/notify.rs), native notification preparation
(runtime_api/notification_delivery.rs) and headless exec (exec_agent.rs).
Per the plan's second review, delivery (OSC 9/99/777 escapes, taskbar and
title sequences, stdout writes) stays in the TUI, the one terminal-output
owner; only policy moves down.

- New runtime-side module `crate::notify`: `git mv` of
  tui/notification_payload.rs -> notify/payload.rs,
  tui/notification_audio.rs -> notify/audio.rs,
  tui/sound_policy.rs -> notify/sound_policy.rs, plus the pure policy that
  was in tui/notifications.rs: `Method`, `DeliveryOutcome` (+ receipts),
  `NotificationGate`, `AttentionCondition` and its grace rule,
  `native_attention_allowed`, and `settings_projection` (now takes
  `&NotificationsConfig`). It uses `config`, so it moves into
  codewhale-runtime with the core.
- `sanitize_stream_chunk` moves to `codewhale_secrets::sanitize` beside
  `strip_ansi_into`; the payload and notifications call it there and
  tui/ui/frame.rs re-exports it for the event loop, which does not change.
- host_terminal port gains `notify_model(title, body) -> receipt`,
  `set_terminal_focused`, and `apply_notification_settings(&NotificationsConfig)`
  (no-op / "no terminal host" receipt when nothing is installed). The TUI
  host delegates to `tui::notifications::{notify_model,
  set_terminal_focused, apply_settings}`; `settings(&Config)` is now a thin
  wrapper over `apply_settings`, so its ~40 UI call sites are unchanged.
- tools/notify.rs calls the port; its emission-chain test (method=off
  silences the sink) moves to tui::notifications tests, where the chain
  lives. exec_agent.rs calls the port.
- notification_delivery.rs uses `crate::notify`; it still calls
  `tui::notifications::notify_with_sinks` with null sinks, because that
  function mixes the policy with transport selection. Known limitation,
  written in notify/mod.rs: splitting transport out of notify_with_sinks is
  the remaining step (1 prod runtime_api|tui reference left).
- Ratchet: prod 82 -> 75 (runtime_api|tui 5 -> 1, tools|tui 11 -> 8),
  test 95 -> 89 (tools|tui 19 -> 14, config|tui 10 -> 9).

Plan note: RS-7 was scoped to wait for R3/R7 (exec_agent.rs) and #6569
(tui/ui/frame.rs); this branch touches those files and will need a rebase
after they land.

Verification (local):
- cargo check -p codewhale-tui --lib --tests --examples: ok.
- cargo test -p codewhale-runtime --lib: 128 passed; 0 failed.
- cargo test -p codewhale-secrets --lib: 76 passed; 0 failed; 1 ignored.
- cargo clippy -p codewhale-runtime -p codewhale-secrets --all-targets
  --all-features -D warnings: clean.
- RUSTDOCFLAGS=-Dwarnings cargo doc -p codewhale-runtime --no-deps: clean.
The notification tests are in the TUI test binary, which was not linked
locally (shared volume at ~8 GB free); type-checked only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* wip: unfinished review fix-up (interrupted by the session usage limit)

Uncommitted edits from the fix-up stage, saved so nothing is lost. Not
compiled or tested after these edits; the next pass reviews and finishes them.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* ci(split): run the ratchet's baseline-ref step under bash; wip fix-up reviewed

The boundary step uses `[[ =~ ]]` to skip an all-zero push `before` SHA, so
it names `shell: bash` instead of relying on the runner default.

This also closes the review of 7c8319eb1 (the interrupted fix-up): its edits
were compiled and tested here unchanged, and are kept as they are:
- the ratchet compares the committed baseline with the PR base
  (`--baseline-ref`), so a hand-raised JSON fails CI;
- `super::` chains that reach the crate root count as references;
- `cargo tree --target all` so cfg(windows) deps are checked too;
- the thread-scoped retry state moves from `test-support` to its own
  `test-thread-scoped-state` feature, debug-assertions only;
- RawModeSuspension resumes on the host that suspended it;
- palette `ids` tests run without the ratatui gate.

Ratchet proven to fail (then reverted):
- new `use crate::tui::...` in tools/notify.rs: FAIL, prod tools|tui 8 -> 9
- same edge via `super::super::tui::...`: FAIL, 8 -> 9
- baseline hand-raised to 9 with `--baseline-ref HEAD`: FAIL
  ("the baseline was raised relative to HEAD")
- `use ratatui::...` in crates/runtime/src: FAIL (source-scan)
Ratchet on a snapshot of this branch merged with origin/main: PASS.

Evidence (local):
- cargo check -p codewhale-runtime --locked: ok
- cargo check -p codewhale-palette --no-default-features --locked: ok
- cargo check -p codewhale-tui --locked: ok, 0 warnings
- cargo test -p codewhale-runtime -p codewhale-palette --lib: 130/0 and 84/0
- cargo test -p codewhale-tui --lib -- host_terminal notif retry_status
  tools:🐚 296 passed, 0 failed, 2 ignored
- scripts/test_check_command_crate_boundaries.py: 23/0
- scripts/check-command-crate-boundaries.py --baseline-ref origin/main: PASS
- validate-crate-publish-order (crates.sh, includes codewhale-runtime
  before codewhale-tui): ok; its test script: passed

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* test(split): pin the notify tool's no-host receipt; document retry-state feature reach

Review follow-up on the runtime split (both reviews: ship, no blocker or
major findings).

- tools::notify: the_model_sees_success_whatever_the_host_did now asserts
  the exact receipt "notification not sent: no terminal host: done". The
  old assertion only checked for "done", so a tool that ignored host()
  and always said "sent" would still have passed. Also fixes the stale
  capabilities comment (delivery goes through the terminal host now).
- runtime::retry_status: documents that Cargo feature unification lets
  test-thread-scoped-state reach the debug `codewhale` binary under
  `cargo test --workspace`; release and optimized builds never get it.

Behavior note for the series (RS-7): hosts that never install the
terminal host (e.g. in-process `cli dispatch`) now get "notification not
sent: no terminal host" from the notify tool instead of OSC bytes on
stdout.

Evidence:
- cargo test -p codewhale-tui --lib -- tools::notify: 7 passed; 0 failed.
- cargo fmt --all; git diff --check: clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* ci(split): move voice blocking-call budget with the code; list runtime crate in web facts

Lint (blocking-calls ratchet): RS-5 moved the synchronous voice
transcription core from commands/groups/core/voice.rs to src/voice.rs.
The 6 std::fs sites (temp WAV write / sidecar read / cleanup) moved
unchanged, so the budget key follows them: voice.rs gets std_fs 6 and
the now-clean commands/groups/core/voice.rs entry is dropped. Total
site count stays 718 across 202 files; no new debt. --update also
tightened fleet/exact.rs (clean on main, std_fs 2 -> 0) and re-sorted
the runtime/media_originals.rs entry.

Lint & Type Check (web): check:facts failed because the committed
facts.generated.ts predates the new `runtime` crate. Regenerated with
derive-facts.mjs.

Local checks: check-blocking-calls-budget.py within budget (718 sites,
202 files); test_check_blocking_calls_budget.py 11/11 OK;
check-facts OK; check-cloud-facts OK; check:latest-release OK;
check:tokens OK; derive-changelog current (6 releases);
check-versions.sh --range-audit-advisory OK; cargo fmt --check clean;
git diff --check clean. Not run locally: web lint/tsc/build/check:docs
(no web node_modules in this checkout; PR touches no other web files).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* fix(web): read palette RGB tokens from rgb.rs after RS-3

RS-3 moved the *_RGB consts from crates/palette/src/tokens.rs to rgb.rs, so
check:tokens failed with "no palette RGB consts found". The exporter and the
three docs that name the token source now point at rgb.rs. tokens.css
changes only its generated-from comment; all 142 token values are identical.

Checks: export-design-tokens.py --check: up to date (1 file, 142 tokens).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

* test(core): keep engine tests off exec_agent/runtime_chat_relay for the runtime ratchet

Merging origin/main brought two core engine test edges into modules outside
the runtime closure: #6517 asserted the isolated-chat prompt via
crate::runtime_chat_relay::dedicated_chat_system_prompt(None), and the
Auto-Review question fix built the headless catalog via
crate::exec_agent::exec_disallowed_tools(None). The RS-0..RS-7 ratchet counts
test edges as `late` (they block the crate move like prod edges), so
test_checked_in_baseline_holds failed: late core|exec_agent 1 -> 2 and
late core|runtime_chat_relay 0 -> 1 (new pair).

Both intents are kept without the edges: the engine test asserts the shared
ISOLATED_CHAT_SYSTEM_PROMPT (the relay test already pins
dedicated_chat_system_prompt(None) to that constant), and the headless half
passes the explicit deny list (exec_agent tests already pin
exec_disallowed_tools(None) to it). Baseline unchanged.

Local: scripts/test_check_command_crate_boundaries.py 23/23 OK;
check-command-crate-boundaries.py --baseline-ref origin/main PASS
(prod 75, test 89, late 2, uilib 1, doc 1); focused cargo tests 4/4 passed
(question_tool_survives..., isolated_runtime_chat_provider_request...,
isolated_chat_prompt_drops..., headless_exec_withholds...); other Lint-job
python gates green; cargo fmt --all --check and git diff --check clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h

---------

Co-authored-by: CodeWhale Bot <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 19:44:58 -07:00