mirror of
https://github.com/lWolvesl/claw-code.git
synced 2026-04-02 21:51:51 +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,
|
loaded_config_files: usize,
|
||||||
discovered_config_files: usize,
|
discovered_config_files: usize,
|
||||||
memory_file_count: usize,
|
memory_file_count: usize,
|
||||||
|
project_root: Option<PathBuf>,
|
||||||
|
git_branch: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[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(
|
fn run_resume_command(
|
||||||
session_path: &Path,
|
session_path: &Path,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
@@ -724,13 +759,17 @@ fn status_context(
|
|||||||
let loader = ConfigLoader::default_for(&cwd);
|
let loader = ConfigLoader::default_for(&cwd);
|
||||||
let discovered_config_files = loader.discover().len();
|
let discovered_config_files = loader.discover().len();
|
||||||
let runtime_config = loader.load()?;
|
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 {
|
Ok(StatusContext {
|
||||||
cwd,
|
cwd,
|
||||||
session_path: session_path.map(Path::to_path_buf),
|
session_path: session_path.map(Path::to_path_buf),
|
||||||
loaded_config_files: runtime_config.loaded_entries().len(),
|
loaded_config_files: runtime_config.loaded_entries().len(),
|
||||||
discovered_config_files,
|
discovered_config_files,
|
||||||
memory_file_count: project_context.instruction_files.len(),
|
memory_file_count: project_context.instruction_files.len(),
|
||||||
|
project_root,
|
||||||
|
git_branch,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,10 +803,17 @@ fn format_status_report(
|
|||||||
format!(
|
format!(
|
||||||
"Workspace
|
"Workspace
|
||||||
Cwd {}
|
Cwd {}
|
||||||
|
Project root {}
|
||||||
|
Git branch {}
|
||||||
Session {}
|
Session {}
|
||||||
Config files loaded {}/{}
|
Config files loaded {}/{}
|
||||||
Memory files {}",
|
Memory files {}",
|
||||||
context.cwd.display(),
|
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(
|
context.session_path.as_ref().map_or_else(
|
||||||
|| "live-repl".to_string(),
|
|| "live-repl".to_string(),
|
||||||
|path| path.display().to_string()
|
|path| path.display().to_string()
|
||||||
@@ -1287,9 +1333,9 @@ mod tests {
|
|||||||
use super::{
|
use super::{
|
||||||
format_cost_report, format_model_report, format_model_switch_report,
|
format_cost_report, format_model_report, format_model_switch_report,
|
||||||
format_permissions_report, format_permissions_switch_report, format_resume_report,
|
format_permissions_report, format_permissions_switch_report, format_resume_report,
|
||||||
format_status_report, normalize_permission_mode, parse_args, render_init_claude_md,
|
format_status_report, normalize_permission_mode, parse_args, parse_git_status_metadata,
|
||||||
render_repl_help, resume_supported_slash_commands, status_context, CliAction, SlashCommand,
|
render_init_claude_md, render_repl_help, resume_supported_slash_commands, status_context,
|
||||||
StatusUsage, DEFAULT_MODEL,
|
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};
|
||||||
@@ -1500,6 +1546,8 @@ mod tests {
|
|||||||
loaded_config_files: 2,
|
loaded_config_files: 2,
|
||||||
discovered_config_files: 3,
|
discovered_config_files: 3,
|
||||||
memory_file_count: 4,
|
memory_file_count: 4,
|
||||||
|
project_root: Some(PathBuf::from("/tmp")),
|
||||||
|
git_branch: Some("main".to_string()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert!(status.contains("Status"));
|
assert!(status.contains("Status"));
|
||||||
@@ -1509,6 +1557,8 @@ mod tests {
|
|||||||
assert!(status.contains("Latest total 10"));
|
assert!(status.contains("Latest total 10"));
|
||||||
assert!(status.contains("Cumulative total 31"));
|
assert!(status.contains("Cumulative total 31"));
|
||||||
assert!(status.contains("Cwd /tmp/project"));
|
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("Session session.json"));
|
||||||
assert!(status.contains("Config files loaded 2/3"));
|
assert!(status.contains("Config files loaded 2/3"));
|
||||||
assert!(status.contains("Memory files 4"));
|
assert!(status.contains("Memory files 4"));
|
||||||
@@ -1522,6 +1572,16 @@ mod tests {
|
|||||||
assert!(report.contains("Merged JSON"));
|
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]
|
#[test]
|
||||||
fn status_context_reads_real_workspace_metadata() {
|
fn status_context_reads_real_workspace_metadata() {
|
||||||
let context = status_context(None).expect("status context should load");
|
let context = status_context(None).expect("status context should load");
|
||||||
|
|||||||
Reference in New Issue
Block a user