mirror of
https://github.com/instructkr/claw-code.git
synced 2026-05-19 05:51:26 +08:00
omx(team): auto-checkpoint worker-4 [unknown]
This commit is contained in:
@@ -1972,8 +1972,16 @@ fn render_doctor_report() -> Result<DoctorReport, Box<dyn std::error::Error>> {
|
|||||||
let (project_root, git_branch) =
|
let (project_root, git_branch) =
|
||||||
parse_git_status_metadata(project_context.git_status.as_deref());
|
parse_git_status_metadata(project_context.git_status.as_deref());
|
||||||
let git_summary = parse_git_workspace_summary(project_context.git_status.as_deref());
|
let git_summary = parse_git_workspace_summary(project_context.git_status.as_deref());
|
||||||
|
let branch_freshness = BranchFreshness::from_git_status(project_context.git_status.as_deref());
|
||||||
let empty_config = runtime::RuntimeConfig::empty();
|
let empty_config = runtime::RuntimeConfig::empty();
|
||||||
let sandbox_config = config.as_ref().ok().unwrap_or(&empty_config);
|
let sandbox_config = config.as_ref().ok().unwrap_or(&empty_config);
|
||||||
|
let boot_preflight = build_boot_preflight_snapshot(
|
||||||
|
&cwd,
|
||||||
|
project_root.as_deref(),
|
||||||
|
project_context.git_status.as_deref(),
|
||||||
|
config.as_ref().ok(),
|
||||||
|
config.as_ref().err().map(ToString::to_string).as_deref(),
|
||||||
|
);
|
||||||
let context = StatusContext {
|
let context = StatusContext {
|
||||||
cwd: cwd.clone(),
|
cwd: cwd.clone(),
|
||||||
session_path: None,
|
session_path: None,
|
||||||
@@ -1986,7 +1994,9 @@ fn render_doctor_report() -> Result<DoctorReport, Box<dyn std::error::Error>> {
|
|||||||
project_root,
|
project_root,
|
||||||
git_branch,
|
git_branch,
|
||||||
git_summary,
|
git_summary,
|
||||||
|
branch_freshness,
|
||||||
session_lifecycle: classify_session_lifecycle_for(&cwd),
|
session_lifecycle: classify_session_lifecycle_for(&cwd),
|
||||||
|
boot_preflight,
|
||||||
sandbox_status: resolve_sandbox_status(sandbox_config.sandbox(), &cwd),
|
sandbox_status: resolve_sandbox_status(sandbox_config.sandbox(), &cwd),
|
||||||
// Doctor path has its own config check; StatusContext here is only
|
// Doctor path has its own config check; StatusContext here is only
|
||||||
// fed into health renderers that don't read config_load_error.
|
// fed into health renderers that don't read config_load_error.
|
||||||
@@ -1998,6 +2008,7 @@ fn render_doctor_report() -> Result<DoctorReport, Box<dyn std::error::Error>> {
|
|||||||
check_config_health(&config_loader, config.as_ref()),
|
check_config_health(&config_loader, config.as_ref()),
|
||||||
check_install_source_health(),
|
check_install_source_health(),
|
||||||
check_workspace_health(&context),
|
check_workspace_health(&context),
|
||||||
|
check_boot_preflight_health(&context),
|
||||||
check_sandbox_health(&context.sandbox_status),
|
check_sandbox_health(&context.sandbox_status),
|
||||||
check_system_health(&cwd, config.as_ref().ok()),
|
check_system_health(&cwd, config.as_ref().ok()),
|
||||||
],
|
],
|
||||||
@@ -2393,6 +2404,73 @@ fn check_workspace_health(context: &StatusContext) -> DiagnosticCheck {
|
|||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_boot_preflight_health(context: &StatusContext) -> DiagnosticCheck {
|
||||||
|
let preflight = &context.boot_preflight;
|
||||||
|
let missing_binaries = preflight
|
||||||
|
.required_binaries
|
||||||
|
.iter()
|
||||||
|
.filter(|binary| !binary.available)
|
||||||
|
.map(|binary| binary.name)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let socket_details = preflight
|
||||||
|
.control_sockets
|
||||||
|
.iter()
|
||||||
|
.map(|socket| {
|
||||||
|
format!(
|
||||||
|
"Control socket {} configured={} exists={} path={}",
|
||||||
|
socket.name,
|
||||||
|
socket.configured,
|
||||||
|
socket.exists,
|
||||||
|
socket.path.as_deref().unwrap_or("<none>")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut details = vec![
|
||||||
|
format!("Repo exists {}", preflight.repo_exists),
|
||||||
|
format!("Worktree exists {}", preflight.worktree_exists),
|
||||||
|
format!("Git dir exists {}", preflight.git_dir_exists),
|
||||||
|
format!("Branch behind {}", preflight.branch_freshness.behind),
|
||||||
|
format!("Trust allowlist {:?}", preflight.trust_gate_allowed),
|
||||||
|
format!("Trusted roots {}", preflight.trusted_roots_count),
|
||||||
|
format!(
|
||||||
|
"MCP eligible {} · servers {}",
|
||||||
|
preflight.mcp_startup_eligible, preflight.mcp_servers_configured
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"Plugin eligible {} · configured {}",
|
||||||
|
preflight.plugin_startup_eligible, preflight.plugins_configured
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"Last failed boot {}",
|
||||||
|
preflight
|
||||||
|
.last_failed_boot_reason
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("<none>")
|
||||||
|
),
|
||||||
|
];
|
||||||
|
details.extend(preflight.required_binaries.iter().map(|binary| {
|
||||||
|
format!(
|
||||||
|
"Required binary {} available={}",
|
||||||
|
binary.name, binary.available
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
details.extend(socket_details);
|
||||||
|
DiagnosticCheck::new(
|
||||||
|
"Boot preflight",
|
||||||
|
if preflight.repo_exists && preflight.worktree_exists && missing_binaries.is_empty() {
|
||||||
|
DiagnosticLevel::Ok
|
||||||
|
} else {
|
||||||
|
DiagnosticLevel::Warn
|
||||||
|
},
|
||||||
|
preflight.summary(),
|
||||||
|
)
|
||||||
|
.with_details(details)
|
||||||
|
.with_data(Map::from_iter([(
|
||||||
|
"boot_preflight".to_string(),
|
||||||
|
preflight.json_value(),
|
||||||
|
)]))
|
||||||
|
}
|
||||||
|
|
||||||
fn check_sandbox_health(status: &runtime::SandboxStatus) -> DiagnosticCheck {
|
fn check_sandbox_health(status: &runtime::SandboxStatus) -> DiagnosticCheck {
|
||||||
let degraded = status.enabled && !status.active;
|
let degraded = status.enabled && !status.active;
|
||||||
let mut details = vec![
|
let mut details = vec![
|
||||||
@@ -2841,7 +2919,9 @@ struct StatusContext {
|
|||||||
project_root: Option<PathBuf>,
|
project_root: Option<PathBuf>,
|
||||||
git_branch: Option<String>,
|
git_branch: Option<String>,
|
||||||
git_summary: GitWorkspaceSummary,
|
git_summary: GitWorkspaceSummary,
|
||||||
|
branch_freshness: BranchFreshness,
|
||||||
session_lifecycle: SessionLifecycleSummary,
|
session_lifecycle: SessionLifecycleSummary,
|
||||||
|
boot_preflight: BootPreflightSnapshot,
|
||||||
sandbox_status: runtime::SandboxStatus,
|
sandbox_status: runtime::SandboxStatus,
|
||||||
/// #143: when `.claw.json` (or another loaded config file) fails to parse,
|
/// #143: when `.claw.json` (or another loaded config file) fails to parse,
|
||||||
/// we capture the parse error here and still populate every field that
|
/// we capture the parse error here and still populate every field that
|
||||||
@@ -2852,6 +2932,162 @@ struct StatusContext {
|
|||||||
config_load_error: Option<String>,
|
config_load_error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct BranchFreshness {
|
||||||
|
upstream: Option<String>,
|
||||||
|
ahead: u32,
|
||||||
|
behind: u32,
|
||||||
|
fresh: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BranchFreshness {
|
||||||
|
fn from_git_status(status: Option<&str>) -> Self {
|
||||||
|
let first_line = status
|
||||||
|
.and_then(|status| status.lines().next())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let upstream = first_line
|
||||||
|
.split_once("...")
|
||||||
|
.and_then(|(_, rest)| rest.split([' ', '[']).next())
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.map(ToOwned::to_owned);
|
||||||
|
let mut ahead = 0;
|
||||||
|
let mut behind = 0;
|
||||||
|
if let Some((_, bracketed)) = first_line.split_once('[') {
|
||||||
|
let bracketed = bracketed.trim_end_matches(']');
|
||||||
|
for part in bracketed.split(',').map(str::trim) {
|
||||||
|
if let Some(value) = part.strip_prefix("ahead ") {
|
||||||
|
ahead = value.parse().unwrap_or(0);
|
||||||
|
} else if let Some(value) = part.strip_prefix("behind ") {
|
||||||
|
behind = value.parse().unwrap_or(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let fresh = upstream.as_ref().map(|_| behind == 0);
|
||||||
|
Self {
|
||||||
|
upstream,
|
||||||
|
ahead,
|
||||||
|
behind,
|
||||||
|
fresh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_value(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"upstream": self.upstream,
|
||||||
|
"ahead": self.ahead,
|
||||||
|
"behind": self.behind,
|
||||||
|
"fresh": self.fresh,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct BinaryPreflight {
|
||||||
|
name: &'static str,
|
||||||
|
available: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryPreflight {
|
||||||
|
fn json_value(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"name": self.name,
|
||||||
|
"available": self.available,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct ControlSocketPreflight {
|
||||||
|
name: &'static str,
|
||||||
|
configured: bool,
|
||||||
|
exists: bool,
|
||||||
|
path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlSocketPreflight {
|
||||||
|
fn json_value(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"name": self.name,
|
||||||
|
"configured": self.configured,
|
||||||
|
"exists": self.exists,
|
||||||
|
"path": self.path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct BootPreflightSnapshot {
|
||||||
|
repo_exists: bool,
|
||||||
|
worktree_exists: bool,
|
||||||
|
git_dir_exists: bool,
|
||||||
|
branch_freshness: BranchFreshness,
|
||||||
|
trust_gate_allowed: Option<bool>,
|
||||||
|
trusted_roots_count: usize,
|
||||||
|
required_binaries: Vec<BinaryPreflight>,
|
||||||
|
control_sockets: Vec<ControlSocketPreflight>,
|
||||||
|
mcp_startup_eligible: bool,
|
||||||
|
mcp_servers_configured: usize,
|
||||||
|
plugin_startup_eligible: bool,
|
||||||
|
plugins_configured: usize,
|
||||||
|
last_failed_boot_reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BootPreflightSnapshot {
|
||||||
|
fn json_value(&self) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"repo": {
|
||||||
|
"exists": self.repo_exists,
|
||||||
|
"worktree_exists": self.worktree_exists,
|
||||||
|
"git_dir_exists": self.git_dir_exists,
|
||||||
|
},
|
||||||
|
"branch_freshness": self.branch_freshness.json_value(),
|
||||||
|
"trust_gate": {
|
||||||
|
"allowlisted": self.trust_gate_allowed,
|
||||||
|
"trusted_roots_count": self.trusted_roots_count,
|
||||||
|
},
|
||||||
|
"required_binaries": self.required_binaries.iter().map(BinaryPreflight::json_value).collect::<Vec<_>>(),
|
||||||
|
"control_sockets": self.control_sockets.iter().map(ControlSocketPreflight::json_value).collect::<Vec<_>>(),
|
||||||
|
"mcp_startup": {
|
||||||
|
"eligible": self.mcp_startup_eligible,
|
||||||
|
"servers_configured": self.mcp_servers_configured,
|
||||||
|
},
|
||||||
|
"plugin_startup": {
|
||||||
|
"eligible": self.plugin_startup_eligible,
|
||||||
|
"plugins_configured": self.plugins_configured,
|
||||||
|
},
|
||||||
|
"last_failed_boot_reason": self.last_failed_boot_reason,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary(&self) -> String {
|
||||||
|
let trust = self
|
||||||
|
.trust_gate_allowed
|
||||||
|
.map(|value| {
|
||||||
|
if value {
|
||||||
|
"allowlisted"
|
||||||
|
} else {
|
||||||
|
"not allowlisted"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or("unknown");
|
||||||
|
let freshness = self
|
||||||
|
.branch_freshness
|
||||||
|
.fresh
|
||||||
|
.map(|fresh| if fresh { "fresh" } else { "behind" })
|
||||||
|
.unwrap_or("no upstream");
|
||||||
|
format!(
|
||||||
|
"repo={} worktree={} branch={} trust={} mcp={} plugins={} last_failed={}",
|
||||||
|
self.repo_exists,
|
||||||
|
self.worktree_exists,
|
||||||
|
freshness,
|
||||||
|
trust,
|
||||||
|
self.mcp_startup_eligible,
|
||||||
|
self.plugin_startup_eligible,
|
||||||
|
self.last_failed_boot_reason.as_deref().unwrap_or("none")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct StatusUsage {
|
struct StatusUsage {
|
||||||
message_count: usize,
|
message_count: usize,
|
||||||
@@ -3294,6 +3530,118 @@ fn parse_git_workspace_summary(status: Option<&str>) -> GitWorkspaceSummary {
|
|||||||
summary
|
summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_boot_preflight_snapshot(
|
||||||
|
cwd: &Path,
|
||||||
|
project_root: Option<&Path>,
|
||||||
|
git_status: Option<&str>,
|
||||||
|
runtime_config: Option<&runtime::RuntimeConfig>,
|
||||||
|
config_load_error: Option<&str>,
|
||||||
|
) -> BootPreflightSnapshot {
|
||||||
|
let branch_freshness = BranchFreshness::from_git_status(git_status);
|
||||||
|
let worktree_exists = run_git_bool(cwd, &["rev-parse", "--is-inside-work-tree"]);
|
||||||
|
let git_dir_exists = run_git_capture_in(cwd, &["rev-parse", "--git-dir"])
|
||||||
|
.map(|path| {
|
||||||
|
let path = PathBuf::from(path.trim());
|
||||||
|
if path.is_absolute() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
cwd.join(path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.is_some_and(|path| path.exists());
|
||||||
|
let trusted_roots = runtime_config
|
||||||
|
.map(runtime::RuntimeConfig::trusted_roots)
|
||||||
|
.unwrap_or(&[]);
|
||||||
|
let trust_gate_allowed = runtime_config.map(|_| {
|
||||||
|
trusted_roots
|
||||||
|
.iter()
|
||||||
|
.any(|root| path_matches_trusted_root_local(cwd, Path::new(root)))
|
||||||
|
});
|
||||||
|
let plugin_configured = runtime_config
|
||||||
|
.map(|config| config.plugins().enabled_plugins().len())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mcp_configured = runtime_config
|
||||||
|
.map(|config| config.mcp().servers().len())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let config_ok = config_load_error.is_none();
|
||||||
|
BootPreflightSnapshot {
|
||||||
|
repo_exists: project_root.is_some_and(Path::exists),
|
||||||
|
worktree_exists,
|
||||||
|
git_dir_exists,
|
||||||
|
branch_freshness,
|
||||||
|
trust_gate_allowed,
|
||||||
|
trusted_roots_count: trusted_roots.len(),
|
||||||
|
required_binaries: vec![
|
||||||
|
BinaryPreflight {
|
||||||
|
name: "claw",
|
||||||
|
available: env::current_exe().is_ok_and(|path| path.exists()),
|
||||||
|
},
|
||||||
|
BinaryPreflight {
|
||||||
|
name: "git",
|
||||||
|
available: command_available("git"),
|
||||||
|
},
|
||||||
|
BinaryPreflight {
|
||||||
|
name: "tmux",
|
||||||
|
available: command_available("tmux"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
control_sockets: vec![tmux_control_socket_preflight()],
|
||||||
|
mcp_startup_eligible: config_ok,
|
||||||
|
mcp_servers_configured: mcp_configured,
|
||||||
|
plugin_startup_eligible: config_ok,
|
||||||
|
plugins_configured: plugin_configured,
|
||||||
|
last_failed_boot_reason: last_failed_boot_reason(cwd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_git_bool(cwd: &Path, args: &[&str]) -> bool {
|
||||||
|
Command::new("git")
|
||||||
|
.args(args)
|
||||||
|
.current_dir(cwd)
|
||||||
|
.output()
|
||||||
|
.is_ok_and(|output| output.status.success())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_available(command: &str) -> bool {
|
||||||
|
Command::new(command)
|
||||||
|
.arg("--version")
|
||||||
|
.output()
|
||||||
|
.is_ok_and(|output| output.status.success())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tmux_control_socket_preflight() -> ControlSocketPreflight {
|
||||||
|
let path = env::var("TMUX")
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.split(',').next().map(str::to_string))
|
||||||
|
.filter(|value| !value.is_empty());
|
||||||
|
let exists = path.as_ref().is_some_and(|path| Path::new(path).exists());
|
||||||
|
ControlSocketPreflight {
|
||||||
|
name: "tmux",
|
||||||
|
configured: path.is_some(),
|
||||||
|
exists,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_failed_boot_reason(cwd: &Path) -> Option<String> {
|
||||||
|
env::var("CLAW_LAST_FAILED_BOOT_REASON")
|
||||||
|
.ok()
|
||||||
|
.filter(|value| !value.trim().is_empty())
|
||||||
|
.or_else(|| {
|
||||||
|
fs::read_to_string(cwd.join(".claw").join("last-failed-boot.txt"))
|
||||||
|
.ok()
|
||||||
|
.map(|value| value.trim().to_string())
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_matches_trusted_root_local(cwd: &Path, trusted_root: &Path) -> bool {
|
||||||
|
let cwd = fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf());
|
||||||
|
let trusted_root =
|
||||||
|
fs::canonicalize(trusted_root).unwrap_or_else(|_| trusted_root.to_path_buf());
|
||||||
|
cwd == trusted_root || cwd.starts_with(trusted_root)
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_git_branch_for(cwd: &Path) -> Option<String> {
|
fn resolve_git_branch_for(cwd: &Path) -> Option<String> {
|
||||||
let branch = run_git_capture_in(cwd, &["branch", "--show-current"])?;
|
let branch = run_git_capture_in(cwd, &["branch", "--show-current"])?;
|
||||||
let branch = branch.trim();
|
let branch = branch.trim();
|
||||||
@@ -5810,6 +6158,8 @@ fn status_json_value(
|
|||||||
path.file_stem().map(|n| n.to_string_lossy().into_owned())
|
path.file_stem().map(|n| n.to_string_lossy().into_owned())
|
||||||
}),
|
}),
|
||||||
"session_lifecycle": context.session_lifecycle.json_value(),
|
"session_lifecycle": context.session_lifecycle.json_value(),
|
||||||
|
"branch_freshness": context.branch_freshness.json_value(),
|
||||||
|
"boot_preflight": context.boot_preflight.json_value(),
|
||||||
"loaded_config_files": context.loaded_config_files,
|
"loaded_config_files": context.loaded_config_files,
|
||||||
"discovered_config_files": context.discovered_config_files,
|
"discovered_config_files": context.discovered_config_files,
|
||||||
"memory_file_count": context.memory_file_count,
|
"memory_file_count": context.memory_file_count,
|
||||||
@@ -5843,7 +6193,8 @@ fn status_context(
|
|||||||
// so that one malformed `mcpServers.*` entry doesn't take down the whole
|
// so that one malformed `mcpServers.*` entry doesn't take down the whole
|
||||||
// health surface (workspace, git, model, permission, sandbox can still be
|
// health surface (workspace, git, model, permission, sandbox can still be
|
||||||
// reported independently).
|
// reported independently).
|
||||||
let (loaded_config_files, sandbox_status, config_load_error) = match loader.load() {
|
let runtime_config = loader.load();
|
||||||
|
let (loaded_config_files, sandbox_status, config_load_error) = match runtime_config.as_ref() {
|
||||||
Ok(runtime_config) => (
|
Ok(runtime_config) => (
|
||||||
runtime_config.loaded_entries().len(),
|
runtime_config.loaded_entries().len(),
|
||||||
resolve_sandbox_status(runtime_config.sandbox(), &cwd),
|
resolve_sandbox_status(runtime_config.sandbox(), &cwd),
|
||||||
@@ -5864,6 +6215,14 @@ fn status_context(
|
|||||||
let (project_root, git_branch) =
|
let (project_root, git_branch) =
|
||||||
parse_git_status_metadata(project_context.git_status.as_deref());
|
parse_git_status_metadata(project_context.git_status.as_deref());
|
||||||
let git_summary = parse_git_workspace_summary(project_context.git_status.as_deref());
|
let git_summary = parse_git_workspace_summary(project_context.git_status.as_deref());
|
||||||
|
let branch_freshness = BranchFreshness::from_git_status(project_context.git_status.as_deref());
|
||||||
|
let boot_preflight = build_boot_preflight_snapshot(
|
||||||
|
&cwd,
|
||||||
|
project_root.as_deref(),
|
||||||
|
project_context.git_status.as_deref(),
|
||||||
|
runtime_config.as_ref().ok(),
|
||||||
|
config_load_error.as_deref(),
|
||||||
|
);
|
||||||
Ok(StatusContext {
|
Ok(StatusContext {
|
||||||
cwd: cwd.clone(),
|
cwd: cwd.clone(),
|
||||||
session_path: session_path.map(Path::to_path_buf),
|
session_path: session_path.map(Path::to_path_buf),
|
||||||
@@ -5873,7 +6232,9 @@ fn status_context(
|
|||||||
project_root,
|
project_root,
|
||||||
git_branch,
|
git_branch,
|
||||||
git_summary,
|
git_summary,
|
||||||
|
branch_freshness,
|
||||||
session_lifecycle: classify_session_lifecycle_for(&cwd),
|
session_lifecycle: classify_session_lifecycle_for(&cwd),
|
||||||
|
boot_preflight,
|
||||||
sandbox_status,
|
sandbox_status,
|
||||||
config_load_error,
|
config_load_error,
|
||||||
})
|
})
|
||||||
@@ -5948,6 +6309,8 @@ fn format_status_report(
|
|||||||
Untracked {}
|
Untracked {}
|
||||||
Session {}
|
Session {}
|
||||||
Lifecycle {}
|
Lifecycle {}
|
||||||
|
Branch fresh {}
|
||||||
|
Boot preflight {}
|
||||||
Config files loaded {}/{}
|
Config files loaded {}/{}
|
||||||
Memory files {}
|
Memory files {}
|
||||||
Suggested flow /status → /diff → /commit",
|
Suggested flow /status → /diff → /commit",
|
||||||
@@ -5967,6 +6330,12 @@ fn format_status_report(
|
|||||||
|path| path.display().to_string()
|
|path| path.display().to_string()
|
||||||
),
|
),
|
||||||
context.session_lifecycle.signal(),
|
context.session_lifecycle.signal(),
|
||||||
|
context
|
||||||
|
.branch_freshness
|
||||||
|
.fresh
|
||||||
|
.map(|fresh| if fresh { "yes" } else { "behind" })
|
||||||
|
.unwrap_or("no upstream"),
|
||||||
|
context.boot_preflight.summary(),
|
||||||
context.loaded_config_files,
|
context.loaded_config_files,
|
||||||
context.discovered_config_files,
|
context.discovered_config_files,
|
||||||
context.memory_file_count,
|
context.memory_file_count,
|
||||||
@@ -12119,6 +12488,33 @@ mod tests {
|
|||||||
assert!(report.contains("Switch models with /model <name>"));
|
assert!(report.contains("Switch models with /model <name>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_branch_freshness() -> super::BranchFreshness {
|
||||||
|
super::BranchFreshness {
|
||||||
|
upstream: Some("origin/main".to_string()),
|
||||||
|
ahead: 0,
|
||||||
|
behind: 0,
|
||||||
|
fresh: Some(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_boot_preflight() -> super::BootPreflightSnapshot {
|
||||||
|
super::BootPreflightSnapshot {
|
||||||
|
repo_exists: true,
|
||||||
|
worktree_exists: true,
|
||||||
|
git_dir_exists: true,
|
||||||
|
branch_freshness: test_branch_freshness(),
|
||||||
|
trust_gate_allowed: Some(false),
|
||||||
|
trusted_roots_count: 0,
|
||||||
|
required_binaries: Vec::new(),
|
||||||
|
control_sockets: Vec::new(),
|
||||||
|
mcp_startup_eligible: true,
|
||||||
|
mcp_servers_configured: 0,
|
||||||
|
plugin_startup_eligible: true,
|
||||||
|
plugins_configured: 0,
|
||||||
|
last_failed_boot_reason: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn model_switch_report_preserves_context_summary() {
|
fn model_switch_report_preserves_context_summary() {
|
||||||
let report = format_model_switch_report("claude-sonnet", "claude-opus", 9);
|
let report = format_model_switch_report("claude-sonnet", "claude-opus", 9);
|
||||||
@@ -12165,6 +12561,7 @@ mod tests {
|
|||||||
untracked_files: 1,
|
untracked_files: 1,
|
||||||
conflicted_files: 0,
|
conflicted_files: 0,
|
||||||
},
|
},
|
||||||
|
branch_freshness: test_branch_freshness(),
|
||||||
session_lifecycle: SessionLifecycleSummary {
|
session_lifecycle: SessionLifecycleSummary {
|
||||||
kind: SessionLifecycleKind::IdleShell,
|
kind: SessionLifecycleKind::IdleShell,
|
||||||
pane_id: Some("%7".to_string()),
|
pane_id: Some("%7".to_string()),
|
||||||
@@ -12173,6 +12570,7 @@ mod tests {
|
|||||||
workspace_dirty: true,
|
workspace_dirty: true,
|
||||||
abandoned: true,
|
abandoned: true,
|
||||||
},
|
},
|
||||||
|
boot_preflight: test_boot_preflight(),
|
||||||
sandbox_status: runtime::SandboxStatus::default(),
|
sandbox_status: runtime::SandboxStatus::default(),
|
||||||
config_load_error: None,
|
config_load_error: None,
|
||||||
},
|
},
|
||||||
@@ -12300,6 +12698,7 @@ mod tests {
|
|||||||
project_root: Some(PathBuf::from("/tmp/project")),
|
project_root: Some(PathBuf::from("/tmp/project")),
|
||||||
git_branch: Some("feature/session-lifecycle".to_string()),
|
git_branch: Some("feature/session-lifecycle".to_string()),
|
||||||
git_summary: GitWorkspaceSummary::default(),
|
git_summary: GitWorkspaceSummary::default(),
|
||||||
|
branch_freshness: test_branch_freshness(),
|
||||||
session_lifecycle: SessionLifecycleSummary {
|
session_lifecycle: SessionLifecycleSummary {
|
||||||
kind: SessionLifecycleKind::RunningProcess,
|
kind: SessionLifecycleKind::RunningProcess,
|
||||||
pane_id: Some("%9".to_string()),
|
pane_id: Some("%9".to_string()),
|
||||||
@@ -12308,6 +12707,7 @@ mod tests {
|
|||||||
workspace_dirty: false,
|
workspace_dirty: false,
|
||||||
abandoned: false,
|
abandoned: false,
|
||||||
},
|
},
|
||||||
|
boot_preflight: test_boot_preflight(),
|
||||||
sandbox_status: runtime::SandboxStatus::default(),
|
sandbox_status: runtime::SandboxStatus::default(),
|
||||||
config_load_error: None,
|
config_load_error: None,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user