diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index b6eb6993..d391b639 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -1848,10 +1848,18 @@ fn launch_reorient( let state = service .state()? .ok_or_else(|| anyhow!("cannot launch reorientation without native coordinator state"))?; - let checkpoint = state - .investigation_checkpoint - .as_ref() - .ok_or_else(|| anyhow!("cannot launch reorientation without a durable checkpoint"))?; + let checkpoint = match state.investigation_checkpoint.clone() { + Some(checkpoint) => checkpoint, + None => { + let projection = epiphany_core::runtime_modeling_semantic_projection_input(runtime_store) + .context("cannot launch reorientation without a valid admitted RepoModel checkpoint")?; + epiphany_state_model::reorient_checkpoint_from_admitted_repo_model( + projection.snapshot(), + &projection.obligation().obligation_id, + &projection.obligation().source_commit_id, + ) + } + }; let status = collect_coordinator_status(runtime_store, thread_id)?; let decision: epiphany_core::EpiphanyReorientDecision = serde_json::from_value(status["reorient"]["decision"].clone()) @@ -1869,7 +1877,7 @@ fn launch_reorient( expected_revision, Some(max_runtime_seconds), &state, - checkpoint, + &checkpoint, &decision, Some(context), ); diff --git a/epiphany-core/src/bin/epiphany-mvp-status.rs b/epiphany-core/src/bin/epiphany-mvp-status.rs index 5ebdf398..3a619f55 100644 --- a/epiphany-core/src/bin/epiphany-mvp-status.rs +++ b/epiphany-core/src/bin/epiphany-mvp-status.rs @@ -308,52 +308,25 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result EpiphanyReorientPressureLevel::High, epiphany_core::EpiphanyPressureLevel::Critical => EpiphanyReorientPressureLevel::Critical, }; - let admitted_model_projection = - match epiphany_core::runtime_current_repo_model(&runtime_store_path)? { - Some(_) => Some( - epiphany_core::runtime_modeling_semantic_projection_input(&runtime_store_path) - .context("failed to validate current RepoModel continuity projection")?, - ), + let reorient_checkpoint = match state_ref.and_then(|state| state.investigation_checkpoint.clone()) { + Some(checkpoint) => Some(checkpoint), + None => match epiphany_core::runtime_current_repo_model(&runtime_store_path)? { None => None, - }; - let investigation_checkpoint = - state_ref.and_then(|state| state.investigation_checkpoint.as_ref()); - let repo_model_code_refs = admitted_model_projection - .as_ref() - .map(|projection| { - projection - .snapshot() - .frontier - .iter() - .filter(|item| item.status == epiphany_core::RepoFrontierStatus::Active) - .flat_map(|item| item.source_scope.iter()) - .map(|path| epiphany_state_model::EpiphanyCodeRef { - path: path.into(), - start_line: None, - end_line: None, - symbol: None, - note: Some("Current admitted RepoModel frontier scope".to_string()), - }) - .collect::>() - }) - .unwrap_or_default(); - let reorient_checkpoint = investigation_checkpoint - .map(epiphany_state_model::EpiphanyReorientCheckpoint::from) - .or_else(|| { - admitted_model_projection.as_ref().map(|projection| { - epiphany_state_model::EpiphanyReorientCheckpoint { - checkpoint_id: &projection.obligation().obligation_id, - disposition: - epiphany_state_model::EpiphanyInvestigationDisposition::ResumeReady, - next_action: Some("Resume from the current Mind-admitted RepoModel frontier."), - code_refs: &repo_model_code_refs, - } - }) - }); + Some(_) => { + let projection = epiphany_core::runtime_modeling_semantic_projection_input(&runtime_store_path) + .context("failed to validate current RepoModel continuity projection")?; + Some(epiphany_state_model::reorient_checkpoint_from_admitted_repo_model( + projection.snapshot(), + &projection.obligation().obligation_id, + &projection.obligation().source_commit_id, + )) + } + }, + }; let durable_checkpoint_present = reorient_checkpoint.is_some(); let (reorient_state_status, reorient_decision) = recommend_reorientation(EpiphanyReorientInput { - checkpoint: reorient_checkpoint, + checkpoint: reorient_checkpoint.as_ref(), state_present: state_ref.is_some(), pressure_level: reorient_pressure_level, retrieval_status: reorient_retrieval_status(freshness.retrieval.status), diff --git a/epiphany-core/src/surfaces/reorient.rs b/epiphany-core/src/surfaces/reorient.rs index 2f269e9a..7505989f 100644 --- a/epiphany-core/src/surfaces/reorient.rs +++ b/epiphany-core/src/surfaces/reorient.rs @@ -1,5 +1,5 @@ use epiphany_state_model::EpiphanyInvestigationDisposition; -use epiphany_state_model::EpiphanyReorientCheckpoint; +use epiphany_state_model::EpiphanyInvestigationCheckpoint; use serde::Deserialize; use serde::Serialize; use std::collections::HashSet; @@ -66,7 +66,7 @@ pub enum EpiphanyReorientFreshnessStatus { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EpiphanyReorientInput<'a> { - pub checkpoint: Option>, + pub checkpoint: Option<&'a EpiphanyInvestigationCheckpoint>, pub state_present: bool, pub pressure_level: EpiphanyReorientPressureLevel, pub retrieval_status: EpiphanyReorientFreshnessStatus, @@ -196,7 +196,7 @@ pub fn recommend_reorientation( }; let workspace_root = input.watched_root.as_deref(); let checkpoint_dirty_paths = overlapping_checkpoint_paths( - &checkpoint, + checkpoint, input .retrieval_dirty_paths .iter() @@ -204,7 +204,7 @@ pub fn recommend_reorientation( workspace_root, ); let checkpoint_changed_paths = overlapping_checkpoint_paths( - &checkpoint, + checkpoint, input.watcher_changed_paths.iter(), workspace_root, ); @@ -266,12 +266,12 @@ pub fn recommend_reorientation( build_decision( EpiphanyReorientAction::Resume, checkpoint_status, - Some(checkpoint.checkpoint_id.to_string()), + Some(checkpoint.checkpoint_id.clone()), reasons, Vec::new(), Vec::new(), Vec::new(), - checkpoint.next_action.map(str::to_string).unwrap_or_else(|| { + checkpoint.next_action.clone().unwrap_or_else(|| { "Resume from the durable checkpoint focus and verify the seam before broad edits." .to_string() }), @@ -333,14 +333,14 @@ pub fn recommend_reorientation( build_decision( EpiphanyReorientAction::Regather, checkpoint_status, - Some(checkpoint.checkpoint_id.to_string()), + Some(checkpoint.checkpoint_id.clone()), reasons, checkpoint_dirty_paths, checkpoint_changed_paths, input.active_frontier_node_ids, checkpoint .next_action - .map(str::to_string) + .clone() .unwrap_or_else(|| "Re-gather source context before editing.".to_string()), format!("Re-gather before editing: {}.", note_fragments.join("; ")), ), @@ -348,7 +348,7 @@ pub fn recommend_reorientation( } fn overlapping_checkpoint_paths<'a>( - checkpoint: &EpiphanyReorientCheckpoint<'_>, + checkpoint: &EpiphanyInvestigationCheckpoint, candidate_paths: impl IntoIterator, workspace_root: Option<&Path>, ) -> Vec { @@ -391,7 +391,7 @@ fn epiphany_path_key(path: &Path) -> String { #[cfg(test)] mod tests { use super::*; - use epiphany_state_model::{EpiphanyCodeRef, EpiphanyInvestigationCheckpoint}; + use epiphany_state_model::EpiphanyCodeRef; fn checkpoint() -> EpiphanyInvestigationCheckpoint { EpiphanyInvestigationCheckpoint { @@ -416,7 +416,7 @@ mod tests { fn input(checkpoint: Option<&EpiphanyInvestigationCheckpoint>) -> EpiphanyReorientInput<'_> { EpiphanyReorientInput { - checkpoint: checkpoint.map(EpiphanyReorientCheckpoint::from), + checkpoint, state_present: true, pressure_level: EpiphanyReorientPressureLevel::Low, retrieval_status: EpiphanyReorientFreshnessStatus::Clean, diff --git a/epiphany-state-model/src/lib.rs b/epiphany-state-model/src/lib.rs index 3a9283ed..74023438 100644 --- a/epiphany-state-model/src/lib.rs +++ b/epiphany-state-model/src/lib.rs @@ -873,22 +873,38 @@ pub enum RepoModelPatchPurpose { }, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyReorientCheckpoint<'a> { - pub checkpoint_id: &'a str, - pub disposition: EpiphanyInvestigationDisposition, - pub next_action: Option<&'a str>, - pub code_refs: &'a [EpiphanyCodeRef], -} - -impl<'a> From<&'a EpiphanyInvestigationCheckpoint> for EpiphanyReorientCheckpoint<'a> { - fn from(checkpoint: &'a EpiphanyInvestigationCheckpoint) -> Self { - Self { - checkpoint_id: &checkpoint.checkpoint_id, - disposition: checkpoint.disposition, - next_action: checkpoint.next_action.as_deref(), - code_refs: &checkpoint.code_refs, - } +pub fn reorient_checkpoint_from_admitted_repo_model( + snapshot: &EpiphanyMemoryGraphSnapshot, + obligation_id: &str, + source_commit_id: &str, +) -> EpiphanyInvestigationCheckpoint { + EpiphanyInvestigationCheckpoint { + checkpoint_id: obligation_id.to_string(), + kind: "admitted_repo_model".to_string(), + disposition: EpiphanyInvestigationDisposition::ResumeReady, + focus: "Current Mind-admitted RepoModel frontier".to_string(), + summary: Some( + "Derived from the authenticated current RepoModel semantic projection.".to_string(), + ), + next_action: Some( + "Resume from the current Mind-admitted RepoModel frontier.".to_string(), + ), + captured_at_turn_id: None, + open_questions: Vec::new(), + code_refs: snapshot + .frontier + .iter() + .filter(|item| item.status == RepoFrontierStatus::Active) + .flat_map(|item| item.source_scope.iter()) + .map(|path| EpiphanyCodeRef { + path: path.into(), + start_line: None, + end_line: None, + symbol: None, + note: Some("Current admitted RepoModel frontier scope".to_string()), + }) + .collect(), + evidence_ids: vec![source_commit_id.to_string()], } }