From 8b36d2df84b97cd23eb52f92dda772e0524a751c Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Sun, 16 Aug 2026 16:56:09 -0700 Subject: [PATCH 1/4] refactor(protocol,supervisor): self-enforcing registries for Command extension --- src/protocol_tests.rs | 40 ++++++++++++++++++++++++++++++++- src/supervisor.rs | 52 +++++++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/protocol_tests.rs b/src/protocol_tests.rs index 728f1f7..0cd6890 100644 --- a/src/protocol_tests.rs +++ b/src/protocol_tests.rs @@ -2,7 +2,12 @@ use super::*; /// Every command survives encode→frame-payload→decode unchanged, including /// the `Watch{None}` null, raw `Input` bytes (0 and 255), and the no-field -/// `Shutdown`. +/// `Shutdown`. Also the exhaustiveness gate for `Command`: encode is a +/// compiler-exhaustive match, but decode's string match falls through to +/// `None`, so a variant missing its decode arm would ship encoding fine and +/// silently drop on decode. `variant_index` makes a new variant a compile +/// error here until it gains an arm, and the coverage assert fails until a +/// case round-trips it — which is what catches the forgotten decode arm. #[test] fn command_round_trips() { let cases = [ @@ -142,10 +147,43 @@ fn command_round_trips() { Command::ListSessions, Command::Shutdown, ]; + // No `_` arm: adding a `Command` variant breaks compilation right here. + fn variant_index(c: &Command) -> usize { + match c { + Command::Spawn { .. } => 0, + Command::Kill { .. } => 1, + Command::Remove { .. } => 2, + Command::Restart { .. } => 3, + Command::Tag { .. } => 4, + Command::SetGroup { .. } => 5, + Command::SetName { .. } => 6, + Command::Resize { .. } => 7, + Command::Watch { .. } => 8, + Command::Input { .. } => 9, + Command::Paste { .. } => 10, + Command::Mouse { .. } => 11, + Command::Key { .. } => 12, + Command::Scrollback { .. } => 13, + Command::SaveSession { .. } => 14, + Command::LoadSession { .. } => 15, + Command::LoadRecovery { .. } => 16, + Command::ListSessions => 17, + Command::Shutdown => 18, + } + } + let mut seen = [false; 19]; for c in cases { + seen[variant_index(&c)] = true; let (k, p) = encode_command(&c); assert_eq!(decode_command(k, &p).as_ref(), Some(&c), "round-trip {c:?}"); } + for (i, covered) in seen.iter().enumerate() { + assert!( + covered, + "Command variant #{i} (see variant_index) never round-tripped: \ + add a `cases` entry above and its decode arm in decode_command" + ); + } } #[test] diff --git a/src/supervisor.rs b/src/supervisor.rs index f92ed3a..6bbf12d 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -173,6 +173,36 @@ fn harness_home(env: &[(OsString, OsString)], h: &dyn harness::Harness) -> Optio h.resolve_home(&|key| env_get(env, key).map(PathBuf::from)) } +/// Whether applying `cmd` can change the session recipe: `session_config` +/// serializes each task's command, group, name, and cwd, nothing else. The +/// match is exhaustive so a new recipe-affecting variant fails compilation +/// here instead of silently skipping recovery arming. +fn affects_recipe(cmd: &Command) -> bool { + match cmd { + Command::Spawn { .. } + | Command::Remove { .. } + | Command::Restart { .. } + | Command::SetGroup { .. } + | Command::SetName { .. } + | Command::LoadSession { .. } + | Command::LoadRecovery { .. } => true, + // Kill changes lifecycle and Tag flips the in-use flag — neither is + // serialized; the rest never touch a task's recipe fields. + Command::Kill { .. } + | Command::Tag { .. } + | Command::Resize { .. } + | Command::Watch { .. } + | Command::Input { .. } + | Command::Paste { .. } + | Command::Mouse { .. } + | Command::Key { .. } + | Command::Scrollback { .. } + | Command::SaveSession { .. } + | Command::ListSessions + | Command::Shutdown => false, + } +} + /// State for automatic recovery snapshots. Write failures do not interrupt /// task supervision, and teardown does not write or delete snapshots. struct Recovery { @@ -334,16 +364,7 @@ impl Supervisor { pub fn apply(&mut self, cmd: Command) { // Recipe-affecting command variants arm recovery before validation; // fingerprinting filters rejected commands and other no-ops. - if matches!( - &cmd, - Command::Spawn { .. } - | Command::Remove { .. } - | Command::Restart { .. } - | Command::SetGroup { .. } - | Command::SetName { .. } - | Command::LoadSession { .. } - | Command::LoadRecovery { .. } - ) { + if affects_recipe(&cmd) { self.recovery.dirty = true; self.recovery.last_mutation = Some(Instant::now()); } @@ -897,11 +918,14 @@ impl Supervisor { /// Build `{dir: [entries]}` in spawn order. Groups and names remain intact; /// agent entries use the command returned by `recipe_command`. fn session_config(&self) -> SessionConfig { - let mut order: Vec = (0..self.tasks.len()).collect(); - order.sort_by_key(|&i| self.tasks[i].id); + // Ascending by construction: `admit` pushes under a monotonic + // `next_id`, and `rerun` replaces in place under the same id. + debug_assert!( + self.tasks.is_sorted_by_key(|t| t.id), + "task set left id order" + ); let mut cfg = SessionConfig::new(); - for &i in &order { - let t = &self.tasks[i]; + for t in &self.tasks { cfg.entry(path::abbreviate(&t.cwd)) .or_default() .push(SessionEntry { From c28bdafea6082e1a0be7c04944fd3f85e93a0270 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Sun, 16 Aug 2026 16:58:00 -0700 Subject: [PATCH 2/4] test(terminal): one named wrapper-oracle test per corpus fixture --- src/terminal/ansi.rs | 4 +- src/terminal/emulator.rs | 5 ++ src/terminal/golden.rs | 115 +++++++++++++++------------------------ 3 files changed, 51 insertions(+), 73 deletions(-) diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs index 2836542..024ab54 100644 --- a/src/terminal/ansi.rs +++ b/src/terminal/ansi.rs @@ -217,7 +217,9 @@ pub fn formatted(term: &Term) -> (Vec, (u16, u16), bool) { /// the display offset. Paired wide-char spacers are skipped so wide glyphs /// appear once; zero-width marks ride their base character; `'\t'` cells, /// concealed (SGR 8) cells, and orphaned wide halves read as the blank the -/// replayed screen shows; trailing spaces are trimmed per row. +/// replayed screen shows; trailing spaces are trimmed per row. The blanking +/// is display policy: the emulator's scan-side reader (`push_row_glyphs`) +/// deliberately keeps those glyphs for harness matchers. pub fn contents(term: &Term) -> String { let grid = term.grid(); let cols = grid.columns(); diff --git a/src/terminal/emulator.rs b/src/terminal/emulator.rs index d2e2cd8..eaffddc 100644 --- a/src/terminal/emulator.rs +++ b/src/terminal/emulator.rs @@ -579,6 +579,11 @@ fn live_floor_of(term: &Term) -> String { /// Append a grid row's glyphs, omitting wide-character spacers, mapping tabs /// to spaces, and preserving combining marks. Callers handle trailing spaces. +/// +/// Deliberately diverges from [`crate::ansi::contents`]: concealed (SGR 8) +/// cells and orphaned wide halves keep their glyphs here because every caller +/// feeds scan input to harness matchers, while `contents` blanks them for +/// display parity with the replayed screen. fn push_row_glyphs(out: &mut String, row: &Row) { for cell in row { if cell diff --git a/src/terminal/golden.rs b/src/terminal/golden.rs index e1a2a30..c96550b 100644 --- a/src/terminal/golden.rs +++ b/src/terminal/golden.rs @@ -577,76 +577,47 @@ fn semantic_dec_scrollregion_charset_translation() { assert_eq!(al.grid().cursor.point, Point::new(Line(39), Column(0))); } -/// Compare `ObservedTerm` and the raw backend across the corpus: screen, -/// cursor, and alternate-screen mode must match. -#[test] -fn emulator_wrapper_matches_the_raw_backend_on_every_fixture() { - let fixtures: [(&str, &[u8]); 12] = [ - ( - "tmux_split", - include_bytes!("../../tests/corpus/tmux_split.bin"), - ), - ( - "vim_session", - include_bytes!("../../tests/corpus/vim_session.bin"), - ), - ( - "less_altscreen", - include_bytes!("../../tests/corpus/less_altscreen.bin"), - ), - ( - "top_live", - include_bytes!("../../tests/corpus/top_live.bin"), - ), - ( - "shell_colors", - include_bytes!("../../tests/corpus/shell_colors.bin"), - ), - ( - "build_log", - include_bytes!("../../tests/corpus/build_log.bin"), - ), - ( - "claude_resume", - include_bytes!("../../tests/corpus/claude_resume.bin"), - ), - ( - "codex_resume", - include_bytes!("../../tests/corpus/codex_resume.bin"), - ), - ( - "grok_resume", - include_bytes!("../../tests/corpus/grok_resume.bin"), - ), - ( - "wide_emoji", - include_bytes!("../../tests/corpus/wide_emoji.bin"), - ), - ( - "dec_scrollregion", - include_bytes!("../../tests/corpus/dec_scrollregion.bin"), - ), - ( - "topregion_scroll", - include_bytes!("../../tests/corpus/topregion_scroll.bin"), - ), - ]; - for (name, bytes) in fixtures { - let al = alacritty(bytes); - let mut emu = crate::testutil::corpus_emulator(); - emu.process(bytes); - let (_, al_cursor, al_hidden) = ansi::formatted(&al); - let (_, emu_cursor, emu_hidden) = emu.formatted(); - assert_eq!(emu.contents(), ansi::contents(&al), "{name}: screen"); - assert_eq!( - (emu_cursor, emu_hidden), - (al_cursor, al_hidden), - "{name}: cursor" - ); - assert_eq!( - emu.alternate_screen(), - al.mode().contains(TermMode::ALT_SCREEN), - "{name}: alt bit" - ); - } +/// Compare the [`emulator::Emulator`] wrapper and the raw backend on one +/// fixture: screen, cursor, and alternate-screen mode must match. +/// +/// [`emulator::Emulator`]: crate::emulator::Emulator +fn assert_wrapper_matches(file: &str, bytes: &[u8]) { + let al = alacritty(bytes); + let mut emu = crate::testutil::corpus_emulator(); + emu.process(bytes); + let (_, al_cursor, al_hidden) = ansi::formatted(&al); + let (_, emu_cursor, emu_hidden) = emu.formatted(); + assert_eq!(emu.contents(), ansi::contents(&al), "{file}: screen"); + assert_eq!( + (emu_cursor, emu_hidden), + (al_cursor, al_hidden), + "{file}: cursor" + ); + assert_eq!( + emu.alternate_screen(), + al.mode().contains(TermMode::ALT_SCREEN), + "{file}: alt bit" + ); +} + +macro_rules! wrapper_oracle { + ($name:ident, $file:literal) => { + #[test] + fn $name() { + assert_wrapper_matches($file, include_bytes!(concat!("../../tests/corpus/", $file))); + } + }; } + +wrapper_oracle!(wrapper_tmux_split, "tmux_split.bin"); +wrapper_oracle!(wrapper_vim_session, "vim_session.bin"); +wrapper_oracle!(wrapper_less_altscreen, "less_altscreen.bin"); +wrapper_oracle!(wrapper_top_live, "top_live.bin"); +wrapper_oracle!(wrapper_shell_colors, "shell_colors.bin"); +wrapper_oracle!(wrapper_build_log, "build_log.bin"); +wrapper_oracle!(wrapper_claude_resume, "claude_resume.bin"); +wrapper_oracle!(wrapper_codex_resume, "codex_resume.bin"); +wrapper_oracle!(wrapper_grok_resume, "grok_resume.bin"); +wrapper_oracle!(wrapper_wide_emoji, "wide_emoji.bin"); +wrapper_oracle!(wrapper_dec_scrollregion, "dec_scrollregion.bin"); +wrapper_oracle!(wrapper_topregion_scroll, "topregion_scroll.bin"); From 90a26ffa6c2dbc2fc54f4e7ff1df62e263325c26 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Sun, 16 Aug 2026 16:58:55 -0700 Subject: [PATCH 3/4] refactor(harness): registry canaries, unique_in_window de-parameterized, grok_screen builder --- src/harness/claude.rs | 30 +++++++---------- src/harness/mod.rs | 28 +++++++++++----- src/harness/summary.rs | 8 +++++ src/harness/summary_tests.rs | 65 +++++++++++++++++++----------------- 4 files changed, 74 insertions(+), 57 deletions(-) diff --git a/src/harness/claude.rs b/src/harness/claude.rs index 319f282..8c98c0e 100644 --- a/src/harness/claude.rs +++ b/src/harness/claude.rs @@ -85,14 +85,7 @@ impl Harness for Claude { fn correlate_fs(&self, cwd: &Path, spawned: SystemTime, home: Option<&Path>) -> Option { let dir = self.home_root(home)?.join("projects").join(slug(cwd)?); - unique_in_window(dir, spawned, |entry| { - // A transcript's stem is its session ID. - let path = entry.path(); - if path.extension().and_then(|e| e.to_str()) != Some("jsonl") { - return None; - } - Some(path.file_stem()?.to_str()?.to_string()) - }) + unique_in_window(dir, spawned) } } @@ -162,17 +155,18 @@ fn record_for_pid( (rec.pid == pid && same_cwd && within_window_ms(rec.started_at, spawned_ms)).then_some(rec) } -/// Return the sole candidate created within [`super::CORRELATE_WINDOW`] of -/// `spawned`. Missing creation times, multiple candidates, and a sole invalid -/// UUID return `None`. -fn unique_in_window( - dir: PathBuf, - spawned: SystemTime, - candidate: impl Fn(&fs::DirEntry) -> Option, -) -> Option { +/// Return the stem of the sole `.jsonl` transcript in `dir` created within +/// [`super::CORRELATE_WINDOW`] of `spawned`. Missing creation times, multiple +/// candidates, and a sole invalid UUID return `None`. +fn unique_in_window(dir: PathBuf, spawned: SystemTime) -> Option { let mut candidates: Vec = Vec::new(); for entry in fs::read_dir(dir).ok()?.flatten() { - let Some(name) = candidate(&entry) else { + // A transcript's stem is its session ID. + let path = entry.path(); + if path.extension().and_then(|e| e.to_str()) != Some("jsonl") { + continue; + } + let Some(name) = path.file_stem().and_then(|s| s.to_str()) else { continue; }; let Ok(created) = entry.metadata().and_then(|m| m.created()) else { @@ -181,7 +175,7 @@ fn unique_in_window( if !within_window(created, spawned) { continue; } - candidates.push(name); + candidates.push(name.to_string()); } match candidates.as_slice() { [only] if is_uuid(only) => Some(only.clone()), diff --git a/src/harness/mod.rs b/src/harness/mod.rs index 95d109e..503298f 100644 --- a/src/harness/mod.rs +++ b/src/harness/mod.rs @@ -638,18 +638,30 @@ mod tests { #[test] fn home_env_vars_name_each_tools_override() { - assert_eq!(Claude.home_env_var(), "CLAUDE_CONFIG_DIR"); - assert_eq!(Codex.home_env_var(), "CODEX_HOME"); - assert_eq!(Grok.home_env_var(), "GROK_HOME"); - assert_eq!(Omp.home_env_var(), "PI_CODING_AGENT_SESSION_DIR"); - assert_eq!(Claude.home_dot_dir(), ".claude"); - assert_eq!(Codex.home_dot_dir(), ".codex"); - assert_eq!(Grok.home_dot_dir(), ".grok"); - assert_eq!(Omp.home_dot_dir(), ".omp/agent/sessions"); + // (env var, dot dir) per harness, in AGENTS order. + const OVERRIDES: [(&str, &str); 4] = [ + ("CLAUDE_CONFIG_DIR", ".claude"), + ("CODEX_HOME", ".codex"), + ("GROK_HOME", ".grok"), + ("PI_CODING_AGENT_SESSION_DIR", ".omp/agent/sessions"), + ]; + assert_eq!( + AGENTS.len(), + OVERRIDES.len(), + "a new harness needs its (env var, dot dir) row added here" + ); + for (a, (env_var, dot_dir)) in AGENTS.iter().zip(OVERRIDES) { + let program = a.harness.shape().0; + assert_eq!(a.harness.home_env_var(), env_var, "{program}"); + assert_eq!(a.harness.home_dot_dir(), dot_dir, "{program}"); + } } #[test] fn registry_detect_routes_to_the_matching_harness() { + // The test enumerates each harness by hand: this canary turns a + // silently-passing fifth harness into a failure naming this test. + assert_eq!(AGENTS.len(), 4, "route the new harness's command here"); let (h, inv) = detect("claude").unwrap(); assert_eq!(h.home_dot_dir(), ".claude"); assert_eq!(inv, Invocation::Bare); diff --git a/src/harness/summary.rs b/src/harness/summary.rs index 2231b52..63f9650 100644 --- a/src/harness/summary.rs +++ b/src/harness/summary.rs @@ -7,6 +7,14 @@ //! command. It is therefore outside the session-ID validation boundary in //! [`is_uuid`](super::is_uuid). //! +//! # Title tiers +//! +//! Adapters also normalize announced terminal titles for the preview +//! cascade's Title tiers. Recognition is asymmetric: an alt-screen title may +//! fall back to the raw title when normalization refuses, but a +//! primary-screen title renders only when the adapter affirmatively +//! recognizes the shape — any inline program may have once set a title. +//! //! # Anchor discipline //! //! Status-shaped text can also appear in scrollback or conversation content. diff --git a/src/harness/summary_tests.rs b/src/harness/summary_tests.rs index 8669040..6c126c8 100644 --- a/src/harness/summary_tests.rs +++ b/src/harness/summary_tests.rs @@ -20,6 +20,16 @@ fn claude_screen>(above: &[S]) -> Vec { rows } +/// Place the provided rows above grok's three-row bordered input box; the +/// footer carries the `Grok 4.5 (xhigh)` model label. +fn grok_screen>(above: &[S]) -> Vec { + let mut rows: Vec = above.iter().map(|s| s.as_ref().to_string()).collect(); + rows.push(" ╭──────────────────────╮".to_string()); + rows.push(" │ ❯ │".to_string()); + rows.push(" ╰── Grok 4.5 (xhigh) · always-approve ─╯".to_string()); + rows +} + /// Resolve a corpus fixture at 40 rows and return its text, source, and rule. fn corpus( bytes: &[u8], @@ -84,6 +94,13 @@ fn select_covers_every_registered_shape() { /// adapter fires that CLI's rule on that CLI's screen shape. #[test] fn select_routes_to_the_matching_adapter() { + // The test enumerates each CLI's screen by hand: this canary turns a + // silently-passing fifth adapter into a failure naming this test. + assert_eq!( + crate::harness::AGENTS.len(), + 4, + "route the new adapter's screen here" + ); let sep = "─".repeat(80); let claude = rs(&["✻ Hashing… (6s · ↓ 87 tokens)", &sep, "❯", &sep]); assert_eq!( @@ -101,12 +118,9 @@ fn select_routes_to_the_matching_adapter() { select("codex").unwrap().live_preview(&codex).unwrap().1, "codex:working" ); - let grok = rs(&[ + let grok = grok_screen(&[ " ⠼ Sleep 5 seconds then echo ok… 1.5s 2.8s ⇣14.2k [↓][stop]", "", - " ╭──────────────────────╮", - " │ ❯ │", - " ╰── Grok 4.5 (xhigh) · always-approve ─╯", ]); assert_eq!( select("grok").unwrap().live_preview(&grok).unwrap().1, @@ -1045,16 +1059,7 @@ fn codex_requires_the_composer_pin() { /// longer durations included; free text above the box refuses. #[test] fn grok_status_shapes() { - let boxed = [ - " ╭──────────────────────╮", - " │ ❯ │", - " ╰── Grok 4.5 (xhigh) · always-approve ─╯", - ]; - let probe = |status: &str| { - let mut rows = vec![status, ""]; - rows.extend(boxed); - GrokSummary.live_preview(&rs(&rows)) - }; + let probe = |status: &str| GrokSummary.live_preview(&grok_screen(&[status, ""])); assert_eq!( probe(" ⠼ Sleep 5 seconds then echo ok… 1.5s 2.8s ⇣14.2k [↓][stop]"), Some(("Sleep 5 seconds then echo ok…".to_string(), "grok:spinner")) @@ -1077,10 +1082,8 @@ fn grok_status_shapes() { None ); - let mut rows = vec![" ⠋ Thinking… 0.2s", ""]; - rows.extend(boxed); assert_eq!( - GrokSummary.model_label(&rs(&rows)), + GrokSummary.model_label(&grok_screen(&[" ⠋ Thinking… 0.2s", ""])), Some("Grok 4.5 (xhigh)".to_string()) ); // A plain border carries no label. @@ -1093,16 +1096,7 @@ fn grok_status_shapes() { /// do not match. A closer Worked-for row wins: no upward scan. #[test] fn grok_still_running_shapes() { - let boxed = [ - " ╭──────────────────────╮", - " │ ❯ │", - " ╰── Grok 4.5 (xhigh) · always-approve ─╯", - ]; - let probe = |status: &str| { - let mut rows = vec![status, ""]; - rows.extend(boxed); - GrokSummary.live_preview(&rs(&rows)) - }; + let probe = |status: &str| GrokSummary.live_preview(&grok_screen(&[status, ""])); assert_eq!( probe(" ◎ 1 subagent still running"), Some(("1 subagent still running".to_string(), "grok:still-running")) @@ -1142,15 +1136,14 @@ fn grok_still_running_shapes() { } // The probe is a single row: Worked-for closer to the box wins. - let mut rows = vec![ + let rows = grok_screen(&[ " ◎ 1 subagent still running", "", " Worked for 8.7s", "", - ]; - rows.extend(boxed); + ]); assert_eq!( - GrokSummary.live_preview(&rs(&rows)), + GrokSummary.live_preview(&rows), Some(("Worked for 8.7s".to_string(), "grok:worked")) ); } @@ -1513,6 +1506,16 @@ fn corpus_positive_states_anchor_exactly() { "omp:approval-menu", ), ]; + // Fixture names embed the program word, so this canary forces every + // registered harness to pin at least one positive screen here. + for a in crate::harness::AGENTS { + let program = a.harness.shape().0; + let prefix = format!("preview_{program}_"); + assert!( + cases.iter().any(|Case(name, ..)| name.starts_with(&prefix)), + "a new adapter needs a positive corpus fixture named {prefix}*" + ); + } for Case(name, bytes, adapter, text, rule) in cases { let got = corpus(bytes, adapter, 120); assert_eq!(got, anchor(text, rule), "{name}"); From c56a9e21c1a7fdd7fa809c8c0f5d31f1ffff60fa Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Sun, 16 Aug 2026 18:31:11 -0700 Subject: [PATCH 4/4] refactor(harness, terminal): improve documentation and comments for clarity and accuracy --- src/harness/claude.rs | 8 ++++---- src/harness/mod.rs | 7 ++++--- src/harness/summary.rs | 10 +++++----- src/harness/summary_tests.rs | 11 +++++------ src/protocol_tests.rs | 14 +++++++------- src/supervisor.rs | 16 ++++++++-------- src/terminal/ansi.rs | 6 +++--- src/terminal/emulator.rs | 7 +++---- src/terminal/golden.rs | 6 ++---- 9 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/harness/claude.rs b/src/harness/claude.rs index 8c98c0e..03f5b89 100644 --- a/src/harness/claude.rs +++ b/src/harness/claude.rs @@ -155,13 +155,13 @@ fn record_for_pid( (rec.pid == pid && same_cwd && within_window_ms(rec.started_at, spawned_ms)).then_some(rec) } -/// Return the stem of the sole `.jsonl` transcript in `dir` created within -/// [`super::CORRELATE_WINDOW`] of `spawned`. Missing creation times, multiple -/// candidates, and a sole invalid UUID return `None`. +/// Return the UUID stem of the sole `.jsonl` transcript created within +/// [`super::CORRELATE_WINDOW`] of `spawned`. Unreadable entries and creation +/// times are ignored; directory errors, zero or multiple candidates, and an +/// invalid sole stem return `None`. fn unique_in_window(dir: PathBuf, spawned: SystemTime) -> Option { let mut candidates: Vec = Vec::new(); for entry in fs::read_dir(dir).ok()?.flatten() { - // A transcript's stem is its session ID. let path = entry.path(); if path.extension().and_then(|e| e.to_str()) != Some("jsonl") { continue; diff --git a/src/harness/mod.rs b/src/harness/mod.rs index 503298f..7141aea 100644 --- a/src/harness/mod.rs +++ b/src/harness/mod.rs @@ -638,7 +638,8 @@ mod tests { #[test] fn home_env_vars_name_each_tools_override() { - // (env var, dot dir) per harness, in AGENTS order. + // Expected environment override and default directory for each + // `AGENTS` entry, in the same order. const OVERRIDES: [(&str, &str); 4] = [ ("CLAUDE_CONFIG_DIR", ".claude"), ("CODEX_HOME", ".codex"), @@ -659,8 +660,8 @@ mod tests { #[test] fn registry_detect_routes_to_the_matching_harness() { - // The test enumerates each harness by hand: this canary turns a - // silently-passing fifth harness into a failure naming this test. + // The literal count keeps this hand-written routing coverage aligned + // with `AGENTS`. assert_eq!(AGENTS.len(), 4, "route the new harness's command here"); let (h, inv) = detect("claude").unwrap(); assert_eq!(h.home_dot_dir(), ".claude"); diff --git a/src/harness/summary.rs b/src/harness/summary.rs index 63f9650..8443786 100644 --- a/src/harness/summary.rs +++ b/src/harness/summary.rs @@ -9,11 +9,11 @@ //! //! # Title tiers //! -//! Adapters also normalize announced terminal titles for the preview -//! cascade's Title tiers. Recognition is asymmetric: an alt-screen title may -//! fall back to the raw title when normalization refuses, but a -//! primary-screen title renders only when the adapter affirmatively -//! recognizes the shape — any inline program may have once set a title. +//! Adapters also normalize terminal titles for the preview cascade's Title +//! tiers. An alternate-screen title falls back to the sanitized captured title +//! when normalization rejects it. A retained primary-screen title renders only +//! when the adapter recognizes its shape because any inline program can replace +//! the terminal title. //! //! # Anchor discipline //! diff --git a/src/harness/summary_tests.rs b/src/harness/summary_tests.rs index 6c126c8..921dea4 100644 --- a/src/harness/summary_tests.rs +++ b/src/harness/summary_tests.rs @@ -20,8 +20,7 @@ fn claude_screen>(above: &[S]) -> Vec { rows } -/// Place the provided rows above grok's three-row bordered input box; the -/// footer carries the `Grok 4.5 (xhigh)` model label. +/// Append Grok's three-row input box, including its model label, to `above`. fn grok_screen>(above: &[S]) -> Vec { let mut rows: Vec = above.iter().map(|s| s.as_ref().to_string()).collect(); rows.push(" ╭──────────────────────╮".to_string()); @@ -94,8 +93,8 @@ fn select_covers_every_registered_shape() { /// adapter fires that CLI's rule on that CLI's screen shape. #[test] fn select_routes_to_the_matching_adapter() { - // The test enumerates each CLI's screen by hand: this canary turns a - // silently-passing fifth adapter into a failure naming this test. + // The literal count keeps this hand-written routing coverage aligned with + // the registered adapters. assert_eq!( crate::harness::AGENTS.len(), 4, @@ -1506,8 +1505,8 @@ fn corpus_positive_states_anchor_exactly() { "omp:approval-menu", ), ]; - // Fixture names embed the program word, so this canary forces every - // registered harness to pin at least one positive screen here. + // Fixture names start with the program word: require a positive case for + // every registered harness. for a in crate::harness::AGENTS { let program = a.harness.shape().0; let prefix = format!("preview_{program}_"); diff --git a/src/protocol_tests.rs b/src/protocol_tests.rs index 0cd6890..4cdf385 100644 --- a/src/protocol_tests.rs +++ b/src/protocol_tests.rs @@ -2,12 +2,11 @@ use super::*; /// Every command survives encode→frame-payload→decode unchanged, including /// the `Watch{None}` null, raw `Input` bytes (0 and 255), and the no-field -/// `Shutdown`. Also the exhaustiveness gate for `Command`: encode is a -/// compiler-exhaustive match, but decode's string match falls through to -/// `None`, so a variant missing its decode arm would ship encoding fine and -/// silently drop on decode. `variant_index` makes a new variant a compile -/// error here until it gains an arm, and the coverage assert fails until a -/// case round-trips it — which is what catches the forgotten decode arm. +/// `Shutdown`. +/// +/// `variant_index` exhaustively matches `Command`, and `seen` verifies that +/// `cases` covers every arm. This guards `decode_command`, whose unknown-tag +/// fallback prevents the compiler from detecting an omitted decode arm. #[test] fn command_round_trips() { let cases = [ @@ -147,7 +146,8 @@ fn command_round_trips() { Command::ListSessions, Command::Shutdown, ]; - // No `_` arm: adding a `Command` variant breaks compilation right here. + // Keep this match exhaustive: `seen` then proves that `cases` covers every + // arm. fn variant_index(c: &Command) -> usize { match c { Command::Spawn { .. } => 0, diff --git a/src/supervisor.rs b/src/supervisor.rs index 6bbf12d..fd9791c 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -173,10 +173,9 @@ fn harness_home(env: &[(OsString, OsString)], h: &dyn harness::Harness) -> Optio h.resolve_home(&|key| env_get(env, key).map(PathBuf::from)) } -/// Whether applying `cmd` can change the session recipe: `session_config` -/// serializes each task's command, group, name, and cwd, nothing else. The -/// match is exhaustive so a new recipe-affecting variant fails compilation -/// here instead of silently skipping recovery arming. +/// Whether `cmd` may change the task set or fields serialized by +/// `session_config`. Exhaustive matching requires every command variant to +/// declare its recovery effect. fn affects_recipe(cmd: &Command) -> bool { match cmd { Command::Spawn { .. } @@ -186,8 +185,9 @@ fn affects_recipe(cmd: &Command) -> bool { | Command::SetName { .. } | Command::LoadSession { .. } | Command::LoadRecovery { .. } => true, - // Kill changes lifecycle and Tag flips the in-use flag — neither is - // serialized; the rest never touch a task's recipe fields. + // `Kill` changes lifecycle and `Tag` changes dashboard state; neither + // changes the task set or serialized fields. The remaining variants + // also leave the recipe unchanged. Command::Kill { .. } | Command::Tag { .. } | Command::Resize { .. } @@ -918,8 +918,8 @@ impl Supervisor { /// Build `{dir: [entries]}` in spawn order. Groups and names remain intact; /// agent entries use the command returned by `recipe_command`. fn session_config(&self) -> SessionConfig { - // Ascending by construction: `admit` pushes under a monotonic - // `next_id`, and `rerun` replaces in place under the same id. + // `admit` appends monotonic IDs; `rerun` preserves both index and ID; + // removal preserves relative order. debug_assert!( self.tasks.is_sorted_by_key(|t| t.id), "task set left id order" diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs index 024ab54..f605d62 100644 --- a/src/terminal/ansi.rs +++ b/src/terminal/ansi.rs @@ -217,9 +217,9 @@ pub fn formatted(term: &Term) -> (Vec, (u16, u16), bool) { /// the display offset. Paired wide-char spacers are skipped so wide glyphs /// appear once; zero-width marks ride their base character; `'\t'` cells, /// concealed (SGR 8) cells, and orphaned wide halves read as the blank the -/// replayed screen shows; trailing spaces are trimmed per row. The blanking -/// is display policy: the emulator's scan-side reader (`push_row_glyphs`) -/// deliberately keeps those glyphs for harness matchers. +/// replayed screen shows; trailing spaces are trimmed per row. This display +/// policy differs from [`crate::emulator::Emulator::live_rows`], which +/// preserves the stored glyphs for structural matching. pub fn contents(term: &Term) -> String { let grid = term.grid(); let cols = grid.columns(); diff --git a/src/terminal/emulator.rs b/src/terminal/emulator.rs index eaffddc..316b783 100644 --- a/src/terminal/emulator.rs +++ b/src/terminal/emulator.rs @@ -580,10 +580,9 @@ fn live_floor_of(term: &Term) -> String { /// Append a grid row's glyphs, omitting wide-character spacers, mapping tabs /// to spaces, and preserving combining marks. Callers handle trailing spaces. /// -/// Deliberately diverges from [`crate::ansi::contents`]: concealed (SGR 8) -/// cells and orphaned wide halves keep their glyphs here because every caller -/// feeds scan input to harness matchers, while `contents` blanks them for -/// display parity with the replayed screen. +/// Unlike [`crate::ansi::contents`], this scan view preserves glyphs in +/// concealed (SGR 8) cells and orphaned wide halves. Harness matchers inspect +/// stored grid text, not replay-equivalent display text. fn push_row_glyphs(out: &mut String, row: &Row) { for cell in row { if cell diff --git a/src/terminal/golden.rs b/src/terminal/golden.rs index c96550b..08838c2 100644 --- a/src/terminal/golden.rs +++ b/src/terminal/golden.rs @@ -577,10 +577,8 @@ fn semantic_dec_scrollregion_charset_translation() { assert_eq!(al.grid().cursor.point, Point::new(Line(39), Column(0))); } -/// Compare the [`emulator::Emulator`] wrapper and the raw backend on one -/// fixture: screen, cursor, and alternate-screen mode must match. -/// -/// [`emulator::Emulator`]: crate::emulator::Emulator +/// Assert identical screen text, cursor state, and alternate-screen mode for +/// one fixture replayed through [`crate::emulator::Emulator`] and a raw `Term`. fn assert_wrapper_matches(file: &str, bytes: &[u8]) { let al = alacritty(bytes); let mut emu = crate::testutil::corpus_emulator();