use std::path::Path;
use std::process::ExitCode;
use anyhow::{bail, Result};
use serde::Serialize;
use crate::handoff::{self, BranchMode};
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::state::session as session_state;
#[derive(Serialize)]
pub struct HandoffRefreshReport {
command: &'static str,
ok: bool,
path: String,
profile: String,
mode: &'static str,
source: &'static str,
handoff: HandoffSurfaceView,
refresh: RefreshView,
#[serde(skip_serializing_if = "Option::is_none")]
write_result: Option<WriteResultView>,
warnings: Vec<String>,
}
#[derive(Serialize)]
struct HandoffSurfaceView {
path: String,
status: &'static str,
section: &'static str,
section_present: bool,
}
#[derive(Serialize)]
struct RefreshView {
changed: bool,
current_lines: Vec<String>,
replacement_lines: Vec<String>,
note: &'static str,
}
#[derive(Serialize)]
struct WriteResultView {
action: &'static str,
path: String,
section: &'static str,
}
impl CommandReport for HandoffRefreshReport {
fn exit_code(&self) -> ExitCode {
ExitCode::SUCCESS
}
fn render_text(&self) {
match &self.write_result {
Some(write_result) => {
println!(
"Current System State is derived live and was not written to the handoff."
);
println!("Write: {} ({})", write_result.action, write_result.path);
}
None => {
println!("Prepared live Current System State preview.");
}
}
println!("Mode: {}", self.mode);
println!("Handoff: {} ({})", self.handoff.path, self.handoff.status);
for warning in &self.warnings {
println!("Warning: {warning}");
}
println!();
println!("Replacement preview:");
for line in &self.refresh.replacement_lines {
println!("- {line}");
}
println!();
println!("{}", self.refresh.note);
}
}
pub fn run(
repo_root: &Path,
explicit_profile: Option<&str>,
write: bool,
) -> Result<HandoffRefreshReport> {
run_internal(repo_root, explicit_profile, write)
}
fn run_internal(
repo_root: &Path,
explicit_profile: Option<&str>,
write: bool,
) -> Result<HandoffRefreshReport> {
let profile = profile::resolve(explicit_profile)?;
let layout = StateLayout::resolve(repo_root, profile.clone())?;
ensure_profile_exists(&layout, repo_root)?;
let locality_id = ensure_repo_linked(repo_root)?;
let native_state_path = layout.state_db_path();
let git = handoff::read_git_state(repo_root, BranchMode::AllowDetachedHead)?;
let checkout_advisory = handoff::checkout_continuity_advisory(repo_root, &git);
let runtime = crate::state::runtime::load_runtime_state(repo_root, &layout, &locality_id)?;
let handoff_surface = runtime.sources.handoff.clone();
if handoff_surface.is_missing() {
bail!(
"canonical native workspace-local handoff state is missing at {}; \
create it with `ccd handoff write --path {}` or bootstrap with `ccd start --path {}`",
native_state_path.display(),
repo_root.display(),
repo_root.display()
);
}
let session_id = session_state::load_session_id(&layout)?;
let replacement_lines = handoff::current_system_state_lines(Some(&git), session_id.as_deref());
let mut warnings = Vec::new();
if let Some(advisory) = checkout_advisory {
warnings.push(advisory.handoff_refresh_warning(&git));
}
let write_result = if write {
Some(WriteResultView {
action: "derived_live",
path: native_state_path.display().to_string(),
section: "Current System State",
})
} else {
None
};
let note = if write {
"Write mode is retained for compatibility only. Current System State is derived live at read time and no canonical handoff mutation was performed."
} else {
"Preview only. Current System State is derived live at read time and is no longer stored in the handoff."
};
Ok(HandoffRefreshReport {
command: "handoff-refresh",
ok: true,
path: repo_root.display().to_string(),
profile: profile.to_string(),
mode: if write { "write" } else { "preview" },
source: "git_metadata",
handoff: HandoffSurfaceView {
path: native_state_path.display().to_string(),
status: "loaded",
section: "Current System State",
section_present: false,
},
refresh: RefreshView {
changed: false,
current_lines: replacement_lines.clone(),
replacement_lines,
note,
},
write_result,
warnings,
})
}
fn ensure_profile_exists(layout: &StateLayout, repo_root: &Path) -> Result<()> {
let profile_root = layout.profile_root();
if profile_root.is_dir() {
return Ok(());
}
bail!(
"profile `{}` does not exist at {}; bootstrap it with `ccd attach --path {}` before refreshing the handoff",
layout.profile(),
profile_root.display(),
repo_root.display()
)
}
fn ensure_repo_linked(repo_root: &Path) -> Result<String> {
let Some(marker) = repo_marker::load(repo_root)? else {
bail!(
"repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
repo_root.join(repo_marker::MARKER_FILE).display(),
repo_root.display(),
repo_root.display()
)
};
Ok(marker.locality_id)
}