mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-02 07:41:52 +08:00
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
This commit is contained in:
@@ -251,6 +251,24 @@ struct ResumeCommandOutcome {
|
|||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct StatusContext {
|
||||||
|
cwd: PathBuf,
|
||||||
|
session_path: Option<PathBuf>,
|
||||||
|
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(
|
fn run_resume_command(
|
||||||
session_path: &Path,
|
session_path: &Path,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
@@ -297,14 +315,17 @@ fn run_resume_command(
|
|||||||
let usage = tracker.cumulative_usage();
|
let usage = tracker.cumulative_usage();
|
||||||
Ok(ResumeCommandOutcome {
|
Ok(ResumeCommandOutcome {
|
||||||
session: session.clone(),
|
session: session.clone(),
|
||||||
message: Some(format_status_line(
|
message: Some(format_status_report(
|
||||||
"restored-session",
|
"restored-session",
|
||||||
session.messages.len(),
|
StatusUsage {
|
||||||
tracker.turns(),
|
message_count: session.messages.len(),
|
||||||
tracker.current_turn_usage(),
|
turns: tracker.turns(),
|
||||||
usage,
|
latest: tracker.current_turn_usage(),
|
||||||
0,
|
cumulative: usage,
|
||||||
|
estimated_tokens: 0,
|
||||||
|
},
|
||||||
permission_mode_label(),
|
permission_mode_label(),
|
||||||
|
&status_context(Some(session_path))?,
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -443,14 +464,17 @@ impl LiveCli {
|
|||||||
let latest = self.runtime.usage().current_turn_usage();
|
let latest = self.runtime.usage().current_turn_usage();
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
format_status_line(
|
format_status_report(
|
||||||
&self.model,
|
&self.model,
|
||||||
self.runtime.session().messages.len(),
|
StatusUsage {
|
||||||
self.runtime.usage().turns(),
|
message_count: self.runtime.session().messages.len(),
|
||||||
|
turns: self.runtime.usage().turns(),
|
||||||
latest,
|
latest,
|
||||||
cumulative,
|
cumulative,
|
||||||
self.runtime.estimated_tokens(),
|
estimated_tokens: self.runtime.estimated_tokens(),
|
||||||
|
},
|
||||||
permission_mode_label(),
|
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<StatusContext, Box<dyn std::error::Error>> {
|
||||||
|
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,
|
model: &str,
|
||||||
message_count: usize,
|
usage: StatusUsage,
|
||||||
turns: u32,
|
|
||||||
latest: TokenUsage,
|
|
||||||
cumulative: TokenUsage,
|
|
||||||
estimated_tokens: usize,
|
|
||||||
permission_mode: &str,
|
permission_mode: &str,
|
||||||
|
context: &StatusContext,
|
||||||
) -> String {
|
) -> String {
|
||||||
format!(
|
let mut lines = vec![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={}",
|
"status: model={model} permission_mode={permission_mode} messages={} turns={} estimated_tokens={} latest_tokens={} cumulative_input_tokens={} cumulative_output_tokens={} cumulative_total_tokens={}",
|
||||||
latest.total_tokens(),
|
usage.message_count,
|
||||||
cumulative.input_tokens,
|
usage.turns,
|
||||||
cumulative.output_tokens,
|
usage.estimated_tokens,
|
||||||
cumulative.total_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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
format_status_line, normalize_permission_mode, parse_args, render_init_claude_md,
|
format_status_report, normalize_permission_mode, parse_args, render_init_claude_md,
|
||||||
render_repl_help, resume_supported_slash_commands, CliAction, SlashCommand, DEFAULT_MODEL,
|
render_repl_help, resume_supported_slash_commands, status_context, CliAction, SlashCommand,
|
||||||
|
StatusUsage, DEFAULT_MODEL,
|
||||||
};
|
};
|
||||||
use runtime::{ContentBlock, ConversationMessage, MessageRole};
|
use runtime::{ContentBlock, ConversationMessage, MessageRole};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -1215,30 +1277,51 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn status_line_reports_model_and_token_totals() {
|
fn status_line_reports_model_and_token_totals() {
|
||||||
let status = format_status_line(
|
let status = format_status_report(
|
||||||
"claude-sonnet",
|
"claude-sonnet",
|
||||||
7,
|
StatusUsage {
|
||||||
3,
|
message_count: 7,
|
||||||
runtime::TokenUsage {
|
turns: 3,
|
||||||
|
latest: runtime::TokenUsage {
|
||||||
input_tokens: 5,
|
input_tokens: 5,
|
||||||
output_tokens: 4,
|
output_tokens: 4,
|
||||||
cache_creation_input_tokens: 1,
|
cache_creation_input_tokens: 1,
|
||||||
cache_read_input_tokens: 0,
|
cache_read_input_tokens: 0,
|
||||||
},
|
},
|
||||||
runtime::TokenUsage {
|
cumulative: runtime::TokenUsage {
|
||||||
input_tokens: 20,
|
input_tokens: 20,
|
||||||
output_tokens: 8,
|
output_tokens: 8,
|
||||||
cache_creation_input_tokens: 2,
|
cache_creation_input_tokens: 2,
|
||||||
cache_read_input_tokens: 1,
|
cache_read_input_tokens: 1,
|
||||||
},
|
},
|
||||||
128,
|
estimated_tokens: 128,
|
||||||
|
},
|
||||||
"workspace-write",
|
"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("model=claude-sonnet"));
|
||||||
assert!(status.contains("permission_mode=workspace-write"));
|
assert!(status.contains("permission_mode=workspace-write"));
|
||||||
assert!(status.contains("messages=7"));
|
assert!(status.contains("messages=7"));
|
||||||
assert!(status.contains("latest_tokens=10"));
|
assert!(status.contains("latest_tokens=10"));
|
||||||
assert!(status.contains("cumulative_total_tokens=31"));
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user