docs: fix inconsistencies found by a tree-wide docs and skills audit #395

Merged
natsukium merged 24 commits from docs/consistency-audit into main 2026-09-25 00:54:54 +09:00
Owner

Tree-wide consistency pass over docs/, .agents/skills/, skills/felis, AGENTS.md, CONTRIBUTING.md, CHANGELOG.md and the comments that cite them. Seven read-only auditors checked every page against the Rust source, manifests, justfile, workflows and each other; all 177 REQ rows were re-checked against their cited sections. Four fixers applied the confirmed findings with exclusive file ownership, and one integration commit carried the cross-page copies.

The code is taken as the truth throughout; no product behavior changes. Non-Markdown edits are comments, doc comments, one clap help string (completions now lists all five shells, snapshot updated), and the regenerated artifacts those feed (felis-config.schema.json, the generated proto file's header comment).

What changed, by kind:

  • Wrong facts corrected:
    • IPC: the GridMsg field numbering, the Ops::Info query, SpawnArgs carriers and fields, and the phase table.
    • CLI: exit-code exceptions for search and subscribe --once, and the info wire mapping.
    • Kitty graphics: only t=t goes through openat from a pinned parent, a=d ID rules, and a=p advancing the cursor.
    • VT behavior: the scope of DECNCSM, the XTGETTCAP subset, and OSC control-byte rejection.
    • Rendering: grayscale-only AA, one pull per painted frame, the 7× scale cap, and dirty-image uploads.
    • Scrollback: the ≈150 MiB figure at 100k rows.
    • Workspace: the crate dependency graph, redrawn from the manifests.
    • Glossary: the Damage, Run, Marginalia, IPC and Transport entries.
  • Dead pointers repointed: nonexistent sections and functions, § N citations, leftovers from #384 in the bench sources, and docs citing a skill as the source of a fact.
  • Agent-facing instructions that would mislead:
    • The extend-ipc skill pointed to caps bits that do not exist.
    • The add-config-key skill's "unknown keys fall back to all defaults" gotcha was wrong, and it omitted the closed-enum wiring.
    • SHELL belongs on the client, not the daemon.
    • Daemon logs are in daemon.log, not stderr.
    • just kani also covers felis-client-core.
    • The skills disagreed on what belongs in tests/.
    • drive.sh now starts its own daemon (passes bash -n; not yet run end to end, see #394).
  • Role fixes: removed history narration, restated values (MSRV, thresholds, latency numbers) and a trailing "See also".

Checks: prose_check --range and --all report 0 hits, skill_check passes, lychee --offline --include-fragments over all Markdown reports 0 errors, cargo fmt --check passes, and cargo nextest on felis-client-core, felis-cli, felis-protocol and the workspace tests passes (1059/1059).

Findings that need a decision or a code change were filed instead of edited:

  • Bugs: #385 (OSC 8 keeps the previous link; the doc stays the contract), #386 (shift+A chord never fires), #387 (device limits defeat the atlas clamp), #394 (samply and drive.sh vs the systemd-run hand-off).
  • Design: #388 (what XTGETTCAP TN reports), #389 (duplicate REQ rows), #390 (vt-compliance record rule vs doc-cascade), #391 (support-matrix legend edges), #392 (the eleven-crate rule), #393 (CHANGELOG link host).
Tree-wide consistency pass over `docs/`, `.agents/skills/`, `skills/felis`, AGENTS.md, CONTRIBUTING.md, CHANGELOG.md and the comments that cite them. Seven read-only auditors checked every page against the Rust source, manifests, justfile, workflows and each other; all 177 REQ rows were re-checked against their cited sections. Four fixers applied the confirmed findings with exclusive file ownership, and one integration commit carried the cross-page copies. The code is taken as the truth throughout; no product behavior changes. Non-Markdown edits are comments, doc comments, one clap help string (`completions` now lists all five shells, snapshot updated), and the regenerated artifacts those feed (`felis-config.schema.json`, the generated proto file's header comment). What changed, by kind: - **Wrong facts corrected**: - IPC: the `GridMsg` field numbering, the `Ops::Info` query, `SpawnArgs` carriers and fields, and the phase table. - CLI: exit-code exceptions for `search` and `subscribe --once`, and the `info` wire mapping. - Kitty graphics: only `t=t` goes through `openat` from a pinned parent, `a=d` ID rules, and `a=p` advancing the cursor. - VT behavior: the scope of DECNCSM, the XTGETTCAP subset, and OSC control-byte rejection. - Rendering: grayscale-only AA, one pull per painted frame, the 7× scale cap, and dirty-image uploads. - Scrollback: the ≈150 MiB figure at 100k rows. - Workspace: the crate dependency graph, redrawn from the manifests. - Glossary: the Damage, Run, Marginalia, IPC and Transport entries. - **Dead pointers repointed**: nonexistent sections and functions, `§ N` citations, leftovers from #384 in the bench sources, and docs citing a skill as the source of a fact. - **Agent-facing instructions that would mislead**: - The extend-ipc skill pointed to caps bits that do not exist. - The add-config-key skill's "unknown keys fall back to all defaults" gotcha was wrong, and it omitted the closed-enum wiring. - SHELL belongs on the client, not the daemon. - Daemon logs are in `daemon.log`, not stderr. - `just kani` also covers felis-client-core. - The skills disagreed on what belongs in `tests/`. - `drive.sh` now starts its own daemon (passes `bash -n`; not yet run end to end, see #394). - **Role fixes**: removed history narration, restated values (MSRV, thresholds, latency numbers) and a trailing "See also". Checks: `prose_check --range` and `--all` report 0 hits, `skill_check` passes, `lychee --offline --include-fragments` over all Markdown reports 0 errors, `cargo fmt --check` passes, and `cargo nextest` on felis-client-core, felis-cli, felis-protocol and the workspace tests passes (1059/1059). Findings that need a decision or a code change were filed instead of edited: - Bugs: #385 (OSC 8 keeps the previous link; the doc stays the contract), #386 (`shift+A` chord never fires), #387 (device limits defeat the atlas clamp), #394 (samply and drive.sh vs the systemd-run hand-off). - Design: #388 (what XTGETTCAP `TN` reports), #389 (duplicate REQ rows), #390 (vt-compliance record rule vs doc-cascade), #391 (support-matrix legend edges), #392 (the eleven-crate rule), #393 (CHANGELOG link host).
The help said bash, zsh and fish, but the argument is clap_complete's
Shell, which also takes elvish and powershell; the reference page
already lists all five, so the help was the one surface understating it.

Assisted-by: Claude Code
Several IPC statements contradicted felis.proto:

- Oneof field numbers were said to follow the Rust variant's source
  order. GridMsg already breaks that (ThemeColor is 13, PaletteColor 19),
  and the numbers are frozen as declared, so the rule is now "frozen as
  declared; a new arm takes the next free number".
- Ops::Info / InfoReply is admitted in Window and Ops modes like List
  and Status, but the mode table, the pure-query lists, the verb carrier
  list and the ConnectionMode doc comments named only two queries.
- The Attached phase row claimed every Ops verb (Stop is Setup-only)
  and omitted Session::InputFence / InputAccepted.
- The Input list claimed wire-tag order but put Key (10) before
  Viewport (7); a cross-reference said "Input (kind = 3)".
- SpawnArgs was said to ride only SessionMsg::Create with five fields;
  `sessions spawn` sends OpsMsg::Spawn, and tags / env_base exist.
- `max_connections` is not a knob; the cap is the MAX_CONNECTIONS const.
- SwitchTarget's session arm carries a prefix, not an id.

The explanation's enumeration of four specific non-additive changes to
the major pointed at a ledger that lists none; the rule stays, the
change list (history) goes.

Assisted-by: Claude Code
- Exit codes were "a function of error.kind alone" with only config
  check and doctor excepted, but `sessions search` with no match and a
  `notifications subscribe --once` stream the daemon ends both exit 1
  with no error object (cli_sessions.rs, cli_notifications.rs).
- An incompatible --format is a clap refusal: exit 2 with no `usage`
  error object.
- `info` rides OpsMsg::Info, not List; `in_flight_decode_bytes` is the
  m=1 reassembly buffer, REQ-908, not the parser caps of REQ-903.
- A relay-forked daemon inherits the relay's stderr (the SSH channel),
  so "a detached child's console is /dev/null" held only for local forks.
- The SSH re-dial verb is `felis ssh`; `window retarget` takes a local
  socket. An id switch moves one window by default, not every
  subscriber. The keymap token is `new_session`, not `spawn_session`.
- The remote daemon keeps a symlink to the newest forwarded agent, not
  a proxy; session-lifecycle.md rejects a proxy explicitly.
- Two citations pointed at a scrollback.md heading that does not exist;
  the transient-session lifecycle is in input.md "Action mapping".
- The config section list lacked [shader] and the [client.<id>] overlay,
  a "pre-freeze window" remark described a contract already frozen for
  v:1, and the reap recipe's jq note had the null ordering backwards
  (`> 3600` already excludes null).

Assisted-by: Claude Code
extend-ipc pointed optimizations at "caps bits" in caps.rs, but no
bitset exists: caps.rs holds ConnectionMode, the only negotiated
optimization is Hello.pull_paced, and the IPC explanation rejects a
bitmap. It also said FrameKind values are absent from the schema
(felis.proto declares them), asked for round-trip proptests where
message families use the `*_cases()` corpus, restated exit codes that
cli.md owns, and claimed field numbers follow Rust source order.

The shipped felis skill called exit codes a function of error.kind
alone, while `sessions search` with no match and a `subscribe --once`
stream the daemon ends exit 1 with no error object.

Assisted-by: Claude Code
keybindings.md and input.md sent readers to a config.md "[keymap]"
section that does not exist, while config.md points back to
keybindings.md; the grammar now lives in keybindings.md, including the
layering rule Keymap::apply_overrides implements (an entry replaces or
unbinds the default; entries apply in the section's key order).

The page also claimed character keys are case-sensitive with
`shift+a` vs `shift+A` as the example, but the winit adapter lowers
ASCII letters and adds Shift, so a `shift+A` binding never fires.
The IME commit is sent as raw UTF-8 KeyBytes, not as key events, and
the menu idiom in input.md must read FELIS_ORIGIN_SESSION_ID: in the
transient session FELIS_SESSION_ID names the menu itself. No default
fullscreen chord ships, so input.md no longer promises one.

Assisted-by: Claude Code
The keymap doc comments (and the schema descriptions generated from
them) cited a config.md "[keymap]" section that does not exist; the
chord and binding grammar is in keybindings.md. The `run` binding's
comment said the command runs with FELIS_SESSION_ID, but pipe.rs
exports FELIS_ORIGIN_SESSION_ID, FELIS_HOST and FELIS_CWD, and uses the
OSC 7 directory only when it is local.

Assisted-by: Claude Code
add-config-key said older builds reject unknown keys and fall back to
all defaults; the loader warns on unknown keys and degrades unknown
closed-enum tokens per field, and only a wrong-shape value fails the
document. It also omitted the three pieces REQ-1103 needs for a new
closed-enum key (the lenient deserializer, the closed_enums entry, the
config.md list).

add-escape-sequence placed the Kitty keyboard encoder in
felis-protocol; it is the daemon's serve/key_encode.rs. Its drive-helper
rule named shapes that exist only in felis-grid's integration tests.

producer-traffic-debug claimed the auto-spawned daemon's stderr lands in
the log, but spawn.rs nulls it (or systemd sends it to the journal), so
drive.sh now starts `felis-daemon serve` itself with stderr in the log.
The event list named a nonexistent `Header` event and cited the release
tracker as if it were a placeholder issue.

Assisted-by: Claude Code
The comment showed `shader.post = "trail"`, a bare string the config
loader rejects (docs/reference/shaders.md); the selector is
`{ builtin = "trail" }`.

Assisted-by: Claude Code
key-encoding.md said win32-input-mode is mirrored to the client, which
honors it, and the ModeFlags doc comments said the client encoder acts
on modifyOtherKeys and win32-input-mode; keys are encoded in the
daemon's serve/key_encode.rs against its own mode state.
kitty_keyboard.rs pointed at a nonexistent felis-client-core::key_encode.

The Kitty key-event rules also understated the encoder: any key
producing a single ASCII character takes its lowercased code, not only
letters; Shift+Space is escaped under bit 1 like Shift+Enter and
Shift+Backspace; and Escape is escaped under bit 1 even unmodified.

Assisted-by: Claude Code
The reference said both t=f and t=t open through openat under a pinned
parent directory and that the temp-file unlink happens on `X=1`; in
image_decode.rs only t=t pins the parent and uses openat/unlinkat, t=f
is a plain open with O_NOFOLLOW, and t=t always unlinks. The same
overstatement sat in the explanation twin and a Windows-shim comment.

"a=p and a=d require explicit IDs" overstated the delete side: only
d=i/I and d=f/F resolve an ID, the positional and bulk deletes take
none. The explanation also said only a=T moves the cursor, but a=p runs
the same upsert_placement path and advances it too.

Assisted-by: Claude Code
- XTGETTCAP does not answer an xterm-256color cap table: xtgettcap.rs
  and editing.rs answer Co/colors, TN/name, co/cols and li/lines, and
  everything else as unknown.
- The DECCOLM omission read as if DECNCSM suppressed all side effects;
  sink.rs suppresses only the ED 2 clear, and only at level 5 or above.
- Three DECRQM test names in the consumed-without-effect table did not
  exist; they now name the proptests in proptest_decrqm.rs.
- esctest-compatibility listed test_XtermWinops_DECSLPP as deferred to
  daemon scope while vt-compliance calls DECSLPP a conscious omission
  consumed without effect; it moves to the rejected group.
- Resize-then-restore returns the original sized-text layout only while
  every run still fits; discard_unfit_sized_runs drops the others, as
  the same explanation page already says a paragraph earlier.
- image-store narrated history ("was a real divergence") and said the
  store holds RGBA only; f=24 is stored as Rgb24.

Assisted-by: Claude Code
The timg workaround offered TERM_PROGRAM=kitty, which timg never reads:
it matches kitty in TERM or the XTVERSION reply, so the workable fixes
are `-p kitty` or FELIS_TERM. The how-to also ended in a "See also"
list, which doc-cascade excludes.

The explanation said the hatch is read from the daemon's own
environment; resolve_env_base reads the create's base first and falls
back to the daemon's environment. It also claimed the reference lists
both identity costs, but the reference names only the tic install, so
the pointer goes.

Assisted-by: Claude Code
Several REQ rows and their security-model twins stated behavior the
source does not have:

- REQ-407/1204 promised an unconditional resize-and-restore round trip,
  but retrack_geometry discards any sized run that no longer fits.
- REQ-310/907 said t=f resolves through openat; it opens the absolute
  path with O_NOFOLLOW on the final component, and only t=t pins the
  parent for openat/unlinkat.
- REQ-209 had DECNCSM suppress every DECCOLM side effect; sink.rs skips
  only the clear, and only at DECSCL level 5 or above.
- REQ-200 counted 11 primary states; felis-vt's State has 14.
- REQ-900 and the constant reply class listed XTGETTCAP and CSI ? u,
  but co/li answer the live grid and CSI ? u reports the pushed flags.
- REQ-901 said \n/\r truncate an OSC; sanitize_osc_str drops the value.
- REQ-916 called the 1024-connection cap configurable; it is a const.
- REQ-305, REQ-504, REQ-603, REQ-111 and REQ-105a understated or
  misnamed what format_response, single_ascii_lower, the continuation
  count, felis.proto and the limits table actually hold.
- The backpressure paragraph had the renderer pausing PTY reads, which
  pipeline.md and session-lifecycle.md reject in favor of coalescing
  and eviction.

Citations that pointed at sections that do not carry the claim (OSC
1337 under "Compatibility theater", RAM-only scrollback under "Capacity
and eviction", the nonexistent scrollback "What felis does not do", the
disconnect section for REQ-006a, text-shaping "Atlas integration" for
the atlas sizes) now point where the text lives. The non-goals tab-stop
bullet named CSI 23 t, which is the implemented title-stack pop, and
comparison.md credited iTerm2 with inventing OSC 133, which FinalTerm
defined.

Assisted-by: Claude Code
- pipeline.md claimed platform-gated subpixel anti-aliasing, but the
  rasterizer always renders Format::Alpha; every image re-uploaded each
  frame, but upload_dirty_images sends only dirty images and the live
  set only after an atlas reset; an 8x text-sizing scale, but REQ-401
  caps it at 7; and one pull per about_to_wait, which the same page
  rejects in favor of one pull per painted frame.
- scrollback.md pointed at the deleted ViewportRing model and
  region_rows_indexed (now region_rows_window), put 100 000 rows at
  256 MiB where 16-byte cells give about 150 MiB, and hung a revisit
  trigger on configuring a capacity that is fixed.
- grid-and-cells.md and scrollback.md sourced their measurements from
  perf-trace skill sections that do not exist; a doc must not cite a
  skill, and the numbers the argument uses are already inline.
- The glossary described damage as client-side redraw state (it is the
  daemon's row set; the client consults none), a shaping run as a
  font/script/direction split (it is the ASCII ligature span), tags and
  annotations as both opaque (annotations are daemon-derived), and the
  IPC transports without Windows named pipes; overview.md had the same
  Unix-socket-only wording.
- explanation/benchmarks.md repeated the Criterion driver's rationale,
  including a pinned-font bullet that is false for the cross-terminal
  harness (it uses system fonts), and counted five terminals where the
  field has up to seven.

Assisted-by: Claude Code
- The session shell comes from the client's env_base first
  (default_shell_command reads SHELL from the base before the daemon's
  env), so setting SHELL on a hand-started daemon had no effect on a
  local session; the recipes now set it on the client, as field.py does.
- On a systemd Linux desktop connect_or_spawn_daemon hands the daemon
  to systemd-run, so samply's launch mode does not follow it; the
  profile script also named tokio-rt-worker as the parse thread, which
  is felis-pty-parser.
- The older-commit comparison built only the felis front door, which
  execs a sibling felis-client and autospawns a sibling felis-daemon
  before PATH, so the comparison ran the current tree's binaries.
- felis-client logs to stdout, so 2>/tmp/felis.log captured nothing.
- harnesses.md listed a Linux vtebench suite that does not exist, said
  cursor_motion/light_cells are dropped while both are published, and
  hard-coded latency figures the reference page owns.
- principle-check cited kitty-graphics.md for the Sixel reject, which
  lives in non-goals.md and the support matrix.

Assisted-by: Claude Code
The latency rationale ("Input latency is borrowed, not invented") moved
from docs/explanation/testing.md to docs/explanation/benchmarks.md, so
the wl-latency, loaders.py and suites.py pointers named a page that no
longer carries it. The rotation docstring counted five terminals where
FIELD_ORDER now holds six besides felis.

Assisted-by: Claude Code
The seven-line doc comment on CellMetrics::from_scaled failed
prose-check --all's five-line cap. The worked 7.35 px example and the
kitty comparison already live in text-shaping.md "Atlas integration",
which the comment cites; the site keeps only the rejected alternative
(rounding) and what it breaks.

Assisted-by: Claude Code
The daemon's per-cycle diff builder is compose_diffs
(serve/streaming.rs); send_diffs no longer exists, so the benches and
tests that describe themselves as mirroring it pointed at nothing. The
test function large_burst_collapses_to_few_send_diffs_cycles keeps its
name: renaming a test is outside a comment-only change.

Assisted-by: Claude Code
The workspace map and AGENTS.md drew edges that the Cargo.toml files do
not have and missed ones they do: felis-pty and felis-shaping depend on
no felis-* crate, yet both hung off felis-protocol; felis-daemon's
felis-pty edge and felis-render-wgpu's felis-grid edge were absent; the
client extraction set left out felis-vt and felis-grid, which
felis-client-core pulls at runtime. The dev-dependency note named
felis-client, which has none, and omitted felis-cli, felis-daemon and
felis-pty, which have some.

The MUST NOT column claimed review alone enforces it, but
crate_purity.rs and no_render_deps.rs pin the felis-protocol and
felis-grid rows in CI, and tests/README.md named only cargo-deny for
dependency bans. The headless-verb lists left out config, doctor,
version, daemon and bridge, which felis-cli runs in-process.

Assisted-by: Claude Code
Several testing-page claims no longer matched the source:
combining_mark_cluster_shapes_with_gpos_offset was renamed to
combining_mark_cluster_shapes_without_panicking and asserts no GPOS
offset; the damage walk is compose_diffs, not send_diffs;
FELIS_TEST_FONT_DIR is also read by felis-render-wgpu's glyphs.rs;
bench.yml sets THRESHOLD and ALLOWLIST but not CRITERION_ROOT, and it
benchmarks pull_request.base.sha, not the merge-base; the RefTest
fixtures live under crates/felis-grid/tests/ref/, not a top-level
tests/ref/.

The detach/attach bullet said a new client re-hydrates scrollback,
while session-lifecycle.md "Attach" and the reattach test cover only
the visible screen; scrollback reaches the client through viewport
requests. The explanation page also restated the 15% threshold the
reference owns; it now points there.

Assisted-by: Claude Code
implementation.md cited "Language / runtime" (the heading is "Language
and runtime") and a "Startup memory footprint" pass that does not
exist; the glyph-atlas sizing lives in pipeline.md "Atlases". It sent
readers to felis-pty's [lints] table for the unsafe relaxations, but
that table only inherits the workspace; the record is the
#[allow(unsafe_code)] sites and their SAFETY comments. Its PTY revisit
trigger waited on the windows runner going green, which workspace.md
already reports as passed.

CHANGELOG.md attributed the dated-section rule to workspace.md
"Versioning"; the rule is item 5 of "Release gate". CONTRIBUTING.md's
summary of `just check` left out the docs-links recipe.

Assisted-by: Claude Code
Comments in felis-vt, felis-transport, felis-grid and the justfile
pointed at "testing.md § 5/8/10/12", but the page has no numbered
sections, so the numbers resolve to nothing. They now name the headings
("Snapshot tests", "Performance benchmarks", "Running benchmarks",
"Security tests", "Kani proof inventory").

The `just check` recipe comment also omitted skill-check and
proto-compat, two of the recipes it runs.

Assisted-by: Claude Code
implement-feature hard-coded MSRV 1.88 where Cargo.toml's rust-version
is the record; said the daemon logs to stderr, while an auto-spawned
daemon's stderr is /dev/null or the journal and its lines land in
daemon.log; and implied `cargo run -p felis-client` is enough, but that
builds only the client, which spawns a sibling felis-daemon binary. Its
regeneration list covered only the config schema, while `just schema`
also rewrites the CLI and felis-json schemas and `just golden` and
`just proto` own other generated files.

test-strategy listed three `just kani` crates where the recipe runs
four, pointed at the reference inventory for the Kani selection
decision that explanation/testing.md records, and sent docs-tree and
dependency-ban guards to the tests/ member, which AGENTS.md and
tests/README.md reserve for the source-reference guard.

Both skills cited docs for "crates/ is fixed at eleven crates" that do
not state it; they now cite the extraction-point rule AGENTS.md does
state. doc-cascade said AGENTS.md carries the commit and
decision-record rules (CONTRIBUTING.md and its own section 3 do) and
that config.md's schema section is generated (only the .schema.json
files are).

Assisted-by: Claude Code
docs: carry the audit's cross-page corrections into the remaining pages
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 54s
darwin / publish felis (aarch64-darwin) (pull_request) Has been skipped
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m33s
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m11s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m3s
pr / nix flake check (pull_request) Successful in 34s
pr / wire schema is compatible with the base (pull_request) Successful in 15s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m19s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 4m11s
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / frontend smoke (Windows) (pull_request) Successful in 1m30s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
1f7c7f0074
The per-area fixes left a few copies of the same false claims in pages
outside each area: `t=f` does not open through `openat` (only `t=t`
pins a parent), no `max_connections` knob exists (the cap is the
`MAX_CONNECTIONS` const), the headless-verb list missed config,
doctor, version, daemon and bridge, and "only `pipe` reaches the wire"
contradicted `run` sending `SessionMsg::Create` in the same sentence.

Assisted-by: Claude Code
natsukium deleted branch docs/consistency-audit 2026-09-25 00:54:55 +09:00
Commenting is not possible because the repository is archived.
No description provided.