Skip to content

Commit 15b140e

Browse files
CodeWhale Botclaude
andcommitted
c03: delete the four re-export shims and migrate their last consumers
`docs/design/TUI_DECONSTRUCTION.md:176,185-186` forbids landing temporary re-export shims whose last consumers are deferred: "do not land temporary re-export shims with their last consumers left for an unspecified future migration. Keep shims only for genuine external compatibility contracts and identify that contract." Four survived the earlier C03 slices — `tui/app/types.rs`, `tui/file_mention.rs`, `tui/agent_roster.rs`, `tui/approval.rs` — and none named such a contract. There was nothing to protect: no crate outside `crates/tui` referenced the moved symbols. So this migrates every remaining internal consumer to the owning crate and removes all four in one commit, because landing three of four is precisely the deferred-consumer state the rule exists to prevent. `crates/tui/src/turn_route_plan.rs` now imports `codewhale_config::AppMode` directly. `AppModeUi` stays in `tui::app` — it is genuinely UI-specific and not a shim. `docs/design/TUI_DECONSTRUCTION.md` is updated in the same commit so the dependency list describes the tree as it now is rather than as it was. Also closes the coverage gap this packet's own done-evidence depended on. `cargo test -p codewhale-config --lib app_mode` previously reported `0 passed; 0 failed` with 693 filtered out — a filter matching nothing, which the repo's rules count as a gap and not a pass — while C03 claimed "unchanged serialization behavior". `AppMode`'s new owner now carries focused tests for the setting round-trip, parse/label/cycle order, and the fallback for unknown values. cargo test -p codewhale-config --lib --locked app_mode test result: ok. 5 passed; 0 failed; 693 filtered out (was 0 passed) cargo test -p codewhale-tui --lib --locked -- approval agent_roster context_reference app_mode test result: ok. 298 passed; 0 failed; 12317 filtered out cargo test -p codewhale-execpolicy -p codewhale-config --locked test result: ok. 697 passed; 0 failed; 1 ignored test result: ok. 112 passed; 0 failed (plus 10, 5, 1, 1 passed across the remaining targets) cargo check --workspace --all-targets --locked: 0 errors, 0 unused warnings The fallback test deserves a note, since its first draft asserted the opposite: `from_setting` returns `Agent` for an empty or unrecognized value, which is the long-standing shipped behavior and is unchanged here. Mode is not the permission surface — as `AppMode::parse` documents, the bypass posture the legacy YOLO spellings imply travels on the permission surface, not on a mode. Refs: Linear C03 / SHA-6179, GitHub #5316. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X57KRgwqYkmmtVQScczqiL
1 parent 7ad4e66 commit 15b140e

36 files changed

Lines changed: 264 additions & 174 deletions

crates/config/src/app_mode.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,102 @@ impl AppMode {
122122
Self::CYCLE[(index + Self::CYCLE.len() - 1) % Self::CYCLE.len()]
123123
}
124124
}
125+
126+
#[cfg(test)]
127+
mod tests {
128+
use super::*;
129+
130+
#[test]
131+
fn app_mode_helpers_centralize_parse_labels_and_cycle_order() {
132+
assert_eq!(AppMode::parse("agent"), Some(AppMode::Agent));
133+
assert_eq!(AppMode::parse("act"), Some(AppMode::Agent));
134+
assert_eq!(AppMode::parse("work"), Some(AppMode::Agent));
135+
assert_eq!(AppMode::parse("2"), Some(AppMode::Plan));
136+
assert_eq!(AppMode::parse("auto"), Some(AppMode::Agent));
137+
assert_eq!(AppMode::parse("3"), Some(AppMode::Operate));
138+
assert_eq!(AppMode::parse("operate"), Some(AppMode::Operate));
139+
// Legacy YOLO spellings resolve to Act; the bypass posture they imply
140+
// travels on the permission surface, not on a mode.
141+
assert_eq!(AppMode::parse("YOLO"), Some(AppMode::Agent));
142+
assert_eq!(AppMode::parse("4"), Some(AppMode::Agent));
143+
assert_eq!(AppMode::parse("bypass"), Some(AppMode::Agent));
144+
assert_eq!(AppMode::parse("bypass-permissions"), Some(AppMode::Agent));
145+
assert_eq!(AppMode::parse("multitask"), None);
146+
assert_eq!(AppMode::parse("5"), None);
147+
assert_eq!(AppMode::parse("fast"), None);
148+
assert_eq!(AppMode::from_setting("multitask"), AppMode::Operate);
149+
assert_eq!(AppMode::from_setting("5"), AppMode::Operate);
150+
151+
assert_eq!(AppMode::Agent.as_setting(), "agent");
152+
assert_eq!(AppMode::Plan.display_name(), "Plan");
153+
assert_eq!(AppMode::Agent.number(), '1');
154+
assert_eq!(AppMode::Operate.number(), '3');
155+
assert_eq!(
156+
AppMode::CYCLE,
157+
[AppMode::Plan, AppMode::Agent, AppMode::Operate]
158+
);
159+
160+
assert_eq!(AppMode::Plan.next(), AppMode::Agent);
161+
assert_eq!(AppMode::Agent.next(), AppMode::Operate);
162+
assert_eq!(AppMode::Operate.next(), AppMode::Plan);
163+
assert_eq!(AppMode::Plan.previous(), AppMode::Operate);
164+
assert_eq!(AppMode::Agent.previous(), AppMode::Plan);
165+
assert_eq!(AppMode::Operate.previous(), AppMode::Agent);
166+
}
167+
168+
/// The durable form of a mode is the `as_setting()` string persisted into
169+
/// settings and session records — `AppMode` derives no `Serialize`, so the
170+
/// round-trip that has to hold is string -> mode -> string.
171+
#[test]
172+
fn setting_strings_round_trip_for_every_mode() {
173+
for mode in AppMode::CYCLE {
174+
let setting = mode.as_setting();
175+
assert_eq!(
176+
AppMode::from_setting(setting),
177+
mode,
178+
"from_setting({setting})"
179+
);
180+
assert_eq!(AppMode::parse(setting), Some(mode), "parse({setting})");
181+
}
182+
183+
assert_eq!(AppMode::Agent.as_setting(), "agent");
184+
assert_eq!(AppMode::Plan.as_setting(), "plan");
185+
assert_eq!(AppMode::Operate.as_setting(), "operate");
186+
}
187+
188+
/// `from_setting` is the de-facto default: an absent, empty, or unreadable
189+
/// stored value must land on Act rather than panicking or picking Operate.
190+
#[test]
191+
fn from_setting_falls_back_to_act_for_unknown_values() {
192+
assert_eq!(AppMode::from_setting(""), AppMode::Agent);
193+
assert_eq!(AppMode::from_setting(" "), AppMode::Agent);
194+
assert_eq!(AppMode::from_setting("nonsense"), AppMode::Agent);
195+
assert_eq!(AppMode::from_setting("OPERATE"), AppMode::Operate);
196+
}
197+
198+
#[test]
199+
fn labels_and_descriptions_cover_every_mode() {
200+
assert_eq!(AppMode::Agent.label(), "ACT");
201+
assert_eq!(AppMode::Plan.label(), "PLAN");
202+
assert_eq!(AppMode::Operate.label(), "OPERATE");
203+
204+
assert_eq!(AppMode::Agent.display_name(), "Act");
205+
assert_eq!(AppMode::Plan.display_name(), "Plan");
206+
assert_eq!(AppMode::Operate.display_name(), "Operate");
207+
208+
for mode in AppMode::CYCLE {
209+
assert!(!mode.description().is_empty());
210+
}
211+
}
212+
213+
#[test]
214+
fn operate_shares_the_agent_baseline_and_raises_the_launch_floor() {
215+
assert!(AppMode::Agent.uses_agent_baseline());
216+
assert!(AppMode::Operate.uses_agent_baseline());
217+
assert!(!AppMode::Plan.uses_agent_baseline());
218+
219+
assert_eq!(AppMode::Operate.mode_delegation_launch_floor(), 4);
220+
assert_eq!(AppMode::Agent.mode_delegation_launch_floor(), 1);
221+
assert_eq!(AppMode::Plan.mode_delegation_launch_floor(), 1);
222+
}
223+
}

crates/execpolicy/src/approval_mode.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,71 @@ impl ApprovalMode {
6262
}
6363
}
6464
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use super::*;
69+
70+
#[test]
71+
fn test_approval_mode_labels() {
72+
assert_eq!(ApprovalMode::Auto.label(), "AUTO");
73+
assert_eq!(ApprovalMode::Suggest.label(), "SUGGEST");
74+
assert_eq!(ApprovalMode::Never.label(), "NEVER");
75+
}
76+
77+
#[test]
78+
fn test_approval_mode_from_config_value_accepts_aliases() {
79+
assert_eq!(
80+
ApprovalMode::from_config_value("auto"),
81+
Some(ApprovalMode::Auto)
82+
);
83+
assert_eq!(
84+
ApprovalMode::from_config_value("on-request"),
85+
Some(ApprovalMode::Suggest)
86+
);
87+
assert_eq!(
88+
ApprovalMode::from_config_value("full_access"),
89+
Some(ApprovalMode::Bypass)
90+
);
91+
assert_eq!(
92+
ApprovalMode::from_config_value("deny"),
93+
Some(ApprovalMode::Never)
94+
);
95+
assert_eq!(ApprovalMode::from_config_value("unknown"), None);
96+
}
97+
98+
#[test]
99+
fn permission_cycle_is_a_closed_loop_and_never_is_off_cycle() {
100+
assert_eq!(
101+
ApprovalMode::PERMISSION_CYCLE,
102+
[
103+
ApprovalMode::Suggest,
104+
ApprovalMode::Auto,
105+
ApprovalMode::Bypass
106+
]
107+
);
108+
assert_eq!(
109+
ApprovalMode::Suggest.cycle_permission_next(),
110+
ApprovalMode::Auto
111+
);
112+
assert_eq!(
113+
ApprovalMode::Auto.cycle_permission_next(),
114+
ApprovalMode::Bypass
115+
);
116+
assert_eq!(
117+
ApprovalMode::Bypass.cycle_permission_next(),
118+
ApprovalMode::Suggest
119+
);
120+
// Never is deliberately outside the Shift+Tab cycle; stepping from it
121+
// must land on the safest posture rather than panicking or wrapping.
122+
assert_eq!(
123+
ApprovalMode::Never.cycle_permission_next(),
124+
ApprovalMode::Suggest
125+
);
126+
}
127+
128+
#[test]
129+
fn default_posture_is_suggest() {
130+
assert_eq!(ApprovalMode::default(), ApprovalMode::Suggest);
131+
}
132+
}

crates/tui/src/commands/groups/config/config.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ use crate::config_persistence::{
1919
use crate::localization::{MessageId, resolve_locale, tr};
2020
use crate::reasoning_preference::ReasoningEffort;
2121
use crate::settings::Settings;
22-
use crate::tui::app::{App, AppAction, AppMode, OnboardingState, ScreenMode, SettingSelection, VimMode};
22+
use crate::tui::app::{App, AppAction, OnboardingState, ScreenMode, SettingSelection, VimMode};
2323
use anyhow::Result;
24+
use codewhale_config::AppMode;
2425
use codewhale_execpolicy::ApprovalMode;
2526
use std::path::{Path, PathBuf};
2627

crates/tui/src/commands/groups/config/status.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,9 @@ mod tests {
526526
use super::*;
527527
use crate::config::{ApiProvider, Config};
528528
use crate::models::{ContentBlock, Message};
529-
use crate::tui::app::{AppMode, TuiOptions};
529+
use crate::tui::app::TuiOptions;
530530
use crate::tui::history::HistoryCell;
531+
use codewhale_config::AppMode;
531532

532533
#[test]
533534
fn status_keeps_current_session_snapshot_remedy_after_notice_delivery() {

crates/tui/src/commands/groups/core/core.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use crate::config::{
1010
use crate::localization::{Locale, MessageId, tr};
1111
#[cfg(test)]
1212
use crate::reasoning_preference::ReasoningEffort;
13-
use crate::tui::app::{App, AppAction, AppMode};
13+
use crate::tui::app::{App, AppAction};
1414
use crate::tui::views::{HelpView, ModalKind, SubAgentsView, subagent_view_agents};
15+
use codewhale_config::AppMode;
1516

1617
use super::CommandResult;
1718

@@ -754,8 +755,9 @@ mod tests {
754755
use crate::config::Config;
755756
use crate::models::Message;
756757
use crate::models::Role;
757-
use crate::tui::app::{App, AppMode, TuiOptions, TurnCacheRecord};
758+
use crate::tui::app::{App, TuiOptions, TurnCacheRecord};
758759
use crate::tui::history::HistoryCell;
760+
use codewhale_config::AppMode;
759761
use std::ffi::OsString;
760762
use std::path::PathBuf;
761763
use std::time::Instant;

crates/tui/src/commands/groups/core/workflow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use crate::commands::traits::{CommandInfo, RegisterCommand};
1313
use crate::localization::MessageId;
1414
use crate::models::ContentBlock;
1515
use crate::tui::app::WORKFLOW_DRAFT_INSTRUCTION_PREFIX;
16-
use crate::tui::app::{App, AppAction, AppMode};
16+
use crate::tui::app::{App, AppAction};
17+
use codewhale_config::AppMode;
1718
#[cfg(test)]
1819
use codewhale_execpolicy::ApprovalMode;
1920

crates/tui/src/commands/session_control_regression_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ use crate::localization::{Locale, MessageId, tr};
1717
use crate::models::{ContentBlock, Message, Role, SystemPrompt};
1818
use crate::session_manager::{SessionManager, create_saved_session_with_mode};
1919
use crate::test_support::{EnvVarGuard, TestEnvLock};
20-
use crate::tui::app::{App, AppAction, AppMode, TuiOptions};
20+
use crate::tui::app::{App, AppAction, TuiOptions};
21+
use codewhale_config::AppMode;
2122

2223
/// Owns the global environment lock for as long as its CODEWHALE_HOME guard.
2324
/// Fields are ordered so the guard restores the environment before the lock

crates/tui/src/commands/session_lifecycle_regression_tests.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ use crate::models::Role;
1414
use crate::reasoning_preference::ReasoningEffort;
1515
use crate::session_manager::create_saved_session_with_id_and_mode;
1616
use crate::test_support::EnvVarGuard;
17-
use crate::tui::app::{App, AppAction, AppMode, TuiOptions, TurnCacheRecord};
17+
use crate::tui::app::{App, AppAction, TuiOptions, TurnCacheRecord};
1818
use crate::tui::history::HistoryCell;
19+
use codewhale_config::AppMode;
1920

2021
fn dispatch_lifecycle(app: &mut App, name: &str, arg: Option<&str>) -> CommandResult {
2122
let command = match arg {
@@ -548,9 +549,9 @@ fn test_load_valid_session_defers_state_restore_to_event_loop() {
548549
app2.session_context_references
549550
.push(crate::session_manager::SessionContextReference {
550551
message_index: 0,
551-
reference: crate::tui::file_mention::ContextReference {
552-
kind: crate::tui::file_mention::ContextReferenceKind::File,
553-
source: crate::tui::file_mention::ContextReferenceSource::AtMention,
552+
reference: codewhale_core::ContextReference {
553+
kind: codewhale_core::ContextReferenceKind::File,
554+
source: codewhale_core::ContextReferenceSource::AtMention,
554555
badge: "file".to_string(),
555556
label: "stale.rs".to_string(),
556557
target: tmpdir.path().join("stale.rs").display().to_string(),

crates/tui/src/tui/agent_roster.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
66
use std::collections::BTreeMap;
77

8-
#[allow(unused_imports)]
9-
pub use crate::agent_roster::{
10-
AgentRosterRow, RosterState, all_rows_have_usage, build_agent_roster, display_name,
11-
format_duration, format_tokens, one_line, roster_totals, wall_millis,
8+
use crate::agent_roster::{
9+
AgentRosterRow, all_rows_have_usage, format_duration, format_tokens, roster_totals,
1210
};
1311

1412
/// Absent receipts render as `—`. See the truth rule in the module docs.

crates/tui/src/tui/agent_roster/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::*;
22

3+
use crate::agent_roster::{RosterState, build_agent_roster};
34
use crate::tools::subagent::{
45
AgentRunUsage, AgentWorkerEvent, AgentWorkerRecord, AgentWorkerSpec, AgentWorkerStatus,
56
AgentWorkerToolProfile, FleetRole,

0 commit comments

Comments
 (0)