From 2ad2ec087ff611f46ae8d7bdbded96ef74b877df Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Tue, 31 Mar 2026 20:22:59 +0000 Subject: [PATCH] Expose real workspace context in status output Expand /status so it reports the current working directory, whether the CLI is operating on a live REPL or resumed session file, how many Claude config files were loaded, and how many instruction memory files were discovered. This makes status feel more like an operator dashboard instead of a bare token counter while still only surfacing metadata we can inspect locally. Constraint: Status must only report context available from the current filesystem and session state Rejected: Include guessed project metadata or upstream-only fields | would make the status output look richer than the implementation actually is Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep status additive and local-truthful; avoid inventing context that is not directly discoverable Tested: cargo fmt --manifest-path ./rust/Cargo.toml --all; cargo clippy --manifest-path ./rust/Cargo.toml --workspace --all-targets -- -D warnings; cargo test --manifest-path ./rust/Cargo.toml --workspace Not-tested: Manual interactive comparison of REPL /status versus resumed-session /status --- rust/crates/rusty-claude-cli/src/main.rs | 165 +++++++++++++++++------ 1 file changed, 124 insertions(+), 41 deletions(-) diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 95e1b14..e17ea19 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -251,6 +251,24 @@ struct ResumeCommandOutcome { message: Option, } +#[derive(Debug, Clone)] +struct StatusContext { + cwd: PathBuf, + session_path: Option, + loaded_config_files: usize, + discovered_config_files: usize, + memory_file_count: usize, +} + +#[derive(Debug, Clone, Copy)] +struct StatusUsage { + message_count: usize, + turns: u32, + latest: TokenUsage, + cumulative: TokenUsage, + estimated_tokens: usize, +} + fn run_resume_command( session_path: &Path, session: &Session, @@ -297,14 +315,17 @@ fn run_resume_command( let usage = tracker.cumulative_usage(); Ok(ResumeCommandOutcome { session: session.clone(), - message: Some(format_status_line( + message: Some(format_status_report( "restored-session", - session.messages.len(), - tracker.turns(), - tracker.current_turn_usage(), - usage, - 0, + StatusUsage { + message_count: session.messages.len(), + turns: tracker.turns(), + latest: tracker.current_turn_usage(), + cumulative: usage, + estimated_tokens: 0, + }, permission_mode_label(), + &status_context(Some(session_path))?, )), }) } @@ -443,14 +464,17 @@ impl LiveCli { let latest = self.runtime.usage().current_turn_usage(); println!( "{}", - format_status_line( + format_status_report( &self.model, - self.runtime.session().messages.len(), - self.runtime.usage().turns(), - latest, - cumulative, - self.runtime.estimated_tokens(), + StatusUsage { + message_count: self.runtime.session().messages.len(), + turns: self.runtime.usage().turns(), + latest, + cumulative, + estimated_tokens: self.runtime.estimated_tokens(), + }, permission_mode_label(), + &status_context(None).expect("status context should load"), ) ); } @@ -586,21 +610,58 @@ fn render_repl_help() -> String { ) } -fn format_status_line( +fn status_context( + session_path: Option<&Path>, +) -> Result> { + let cwd = env::current_dir()?; + let loader = ConfigLoader::default_for(&cwd); + let discovered_config_files = loader.discover().len(); + let runtime_config = loader.load()?; + let project_context = ProjectContext::discover(&cwd, DEFAULT_DATE)?; + Ok(StatusContext { + cwd, + session_path: session_path.map(Path::to_path_buf), + loaded_config_files: runtime_config.loaded_entries().len(), + discovered_config_files, + memory_file_count: project_context.instruction_files.len(), + }) +} + +fn format_status_report( model: &str, - message_count: usize, - turns: u32, - latest: TokenUsage, - cumulative: TokenUsage, - estimated_tokens: usize, + usage: StatusUsage, permission_mode: &str, + context: &StatusContext, ) -> String { - format!( - "status: model={model} permission_mode={permission_mode} messages={message_count} turns={turns} estimated_tokens={estimated_tokens} latest_tokens={} cumulative_input_tokens={} cumulative_output_tokens={} cumulative_total_tokens={}", - latest.total_tokens(), - cumulative.input_tokens, - cumulative.output_tokens, - cumulative.total_tokens(), + let mut lines = vec![format!( + "status: model={model} permission_mode={permission_mode} messages={} turns={} estimated_tokens={} latest_tokens={} cumulative_input_tokens={} cumulative_output_tokens={} cumulative_total_tokens={}", + usage.message_count, + usage.turns, + usage.estimated_tokens, + usage.latest.total_tokens(), + usage.cumulative.input_tokens, + usage.cumulative.output_tokens, + usage.cumulative.total_tokens(), + )]; + lines.push(format!(" cwd {}", context.cwd.display())); + lines.push(format!( + " session {}", + context.session_path.as_ref().map_or_else( + || "live-repl".to_string(), + |path| path.display().to_string() + ) + )); + lines.push(format!( + " config loaded {}/{} files", + context.loaded_config_files, context.discovered_config_files + )); + lines.push(format!( + " memory {} instruction files", + context.memory_file_count + )); + lines.join( + " +", ) } @@ -1097,8 +1158,9 @@ fn print_help() { #[cfg(test)] mod tests { use super::{ - format_status_line, normalize_permission_mode, parse_args, render_init_claude_md, - render_repl_help, resume_supported_slash_commands, CliAction, SlashCommand, DEFAULT_MODEL, + format_status_report, normalize_permission_mode, parse_args, render_init_claude_md, + render_repl_help, resume_supported_slash_commands, status_context, CliAction, SlashCommand, + StatusUsage, DEFAULT_MODEL, }; use runtime::{ContentBlock, ConversationMessage, MessageRole}; use std::path::{Path, PathBuf}; @@ -1215,30 +1277,51 @@ mod tests { #[test] fn status_line_reports_model_and_token_totals() { - let status = format_status_line( + let status = format_status_report( "claude-sonnet", - 7, - 3, - runtime::TokenUsage { - input_tokens: 5, - output_tokens: 4, - cache_creation_input_tokens: 1, - cache_read_input_tokens: 0, + StatusUsage { + message_count: 7, + turns: 3, + latest: runtime::TokenUsage { + input_tokens: 5, + output_tokens: 4, + cache_creation_input_tokens: 1, + cache_read_input_tokens: 0, + }, + cumulative: runtime::TokenUsage { + input_tokens: 20, + output_tokens: 8, + cache_creation_input_tokens: 2, + cache_read_input_tokens: 1, + }, + estimated_tokens: 128, }, - runtime::TokenUsage { - input_tokens: 20, - output_tokens: 8, - cache_creation_input_tokens: 2, - cache_read_input_tokens: 1, - }, - 128, "workspace-write", + &super::StatusContext { + cwd: PathBuf::from("/tmp/project"), + session_path: Some(PathBuf::from("session.json")), + loaded_config_files: 2, + discovered_config_files: 3, + memory_file_count: 4, + }, ); assert!(status.contains("model=claude-sonnet")); assert!(status.contains("permission_mode=workspace-write")); assert!(status.contains("messages=7")); assert!(status.contains("latest_tokens=10")); assert!(status.contains("cumulative_total_tokens=31")); + assert!(status.contains("cwd /tmp/project")); + assert!(status.contains("session session.json")); + assert!(status.contains("config loaded 2/3 files")); + assert!(status.contains("memory 4 instruction files")); + } + + #[test] + fn status_context_reads_real_workspace_metadata() { + let context = status_context(None).expect("status context should load"); + assert!(context.cwd.is_absolute()); + assert_eq!(context.discovered_config_files, 3); + assert!(context.loaded_config_files <= context.discovered_config_files); } #[test]