mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-03 03:31:52 +08:00
Enrich status with git and project context
Extend /status with project root and git branch details derived from the local repository so the report feels closer to a real Claude Code session dashboard. This adds high-value workspace context without inventing any persisted metadata the runtime does not actually have. Constraint: Status metadata must be computed from the current working tree at runtime and tolerate non-git directories Rejected: Persist branch/root into session files first | a local runtime derivation is smaller and immediately useful without changing session format Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep status context opportunistic and degrade cleanly to unknown when git metadata is unavailable 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 non-git-directory /status run
This commit is contained in:
@@ -258,6 +258,8 @@ struct StatusContext {
|
||||
loaded_config_files: usize,
|
||||
discovered_config_files: usize,
|
||||
memory_file_count: usize,
|
||||
project_root: Option<PathBuf>,
|
||||
git_branch: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -336,6 +338,39 @@ fn format_resume_report(session_path: &str, message_count: usize, turns: u32) ->
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_git_status_metadata(status: Option<&str>) -> (Option<PathBuf>, Option<String>) {
|
||||
let Some(status) = status else {
|
||||
return (None, None);
|
||||
};
|
||||
let branch = status.lines().next().and_then(|line| {
|
||||
line.strip_prefix("## ")
|
||||
.map(|line| {
|
||||
line.split(['.', ' '])
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
})
|
||||
.filter(|value| !value.is_empty())
|
||||
});
|
||||
let project_root = find_git_root().ok();
|
||||
(project_root, branch)
|
||||
}
|
||||
|
||||
fn find_git_root() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let output = std::process::Command::new("git")
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.current_dir(env::current_dir()?)
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err("not a git repository".into());
|
||||
}
|
||||
let path = String::from_utf8(output.stdout)?.trim().to_string();
|
||||
if path.is_empty() {
|
||||
return Err("empty git root".into());
|
||||
}
|
||||
Ok(PathBuf::from(path))
|
||||
}
|
||||
|
||||
fn run_resume_command(
|
||||
session_path: &Path,
|
||||
session: &Session,
|
||||
@@ -724,13 +759,17 @@ fn status_context(
|
||||
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)?;
|
||||
let project_context = ProjectContext::discover_with_git(&cwd, DEFAULT_DATE)?;
|
||||
let (project_root, git_branch) =
|
||||
parse_git_status_metadata(project_context.git_status.as_deref());
|
||||
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(),
|
||||
project_root,
|
||||
git_branch,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -764,10 +803,17 @@ fn format_status_report(
|
||||
format!(
|
||||
"Workspace
|
||||
Cwd {}
|
||||
Project root {}
|
||||
Git branch {}
|
||||
Session {}
|
||||
Config files loaded {}/{}
|
||||
Memory files {}",
|
||||
context.cwd.display(),
|
||||
context
|
||||
.project_root
|
||||
.as_ref()
|
||||
.map_or_else(|| "unknown".to_string(), |path| path.display().to_string()),
|
||||
context.git_branch.as_deref().unwrap_or("unknown"),
|
||||
context.session_path.as_ref().map_or_else(
|
||||
|| "live-repl".to_string(),
|
||||
|path| path.display().to_string()
|
||||
@@ -1287,9 +1333,9 @@ mod tests {
|
||||
use super::{
|
||||
format_cost_report, format_model_report, format_model_switch_report,
|
||||
format_permissions_report, format_permissions_switch_report, format_resume_report,
|
||||
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,
|
||||
format_status_report, normalize_permission_mode, parse_args, parse_git_status_metadata,
|
||||
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};
|
||||
@@ -1500,6 +1546,8 @@ mod tests {
|
||||
loaded_config_files: 2,
|
||||
discovered_config_files: 3,
|
||||
memory_file_count: 4,
|
||||
project_root: Some(PathBuf::from("/tmp")),
|
||||
git_branch: Some("main".to_string()),
|
||||
},
|
||||
);
|
||||
assert!(status.contains("Status"));
|
||||
@@ -1509,6 +1557,8 @@ mod tests {
|
||||
assert!(status.contains("Latest total 10"));
|
||||
assert!(status.contains("Cumulative total 31"));
|
||||
assert!(status.contains("Cwd /tmp/project"));
|
||||
assert!(status.contains("Project root /tmp"));
|
||||
assert!(status.contains("Git branch main"));
|
||||
assert!(status.contains("Session session.json"));
|
||||
assert!(status.contains("Config files loaded 2/3"));
|
||||
assert!(status.contains("Memory files 4"));
|
||||
@@ -1522,6 +1572,16 @@ mod tests {
|
||||
assert!(report.contains("Merged JSON"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_git_status_metadata() {
|
||||
let (root, branch) = parse_git_status_metadata(Some(
|
||||
"## rcc/cli...origin/rcc/cli
|
||||
M src/main.rs",
|
||||
));
|
||||
assert_eq!(branch.as_deref(), Some("rcc/cli"));
|
||||
let _ = root;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_context_reads_real_workspace_metadata() {
|
||||
let context = status_context(None).expect("status context should load");
|
||||
|
||||
Reference in New Issue
Block a user