| 1 | //! Pure compatibility projections for the pre-Work-Graph Plan and To-do wire |
| 2 | //! formats. |
| 3 | //! |
| 4 | //! The graph owns the ordering, metadata, titles, and states. These functions |
| 5 | //! only derive owned snapshots; they never receive mutable graph access (V10). |
| 6 | |
| 7 | use crate::tools::plan::{PlanItemArg, PlanSnapshot, StepStatus}; |
| 8 | use crate::tools::todo::{TodoItem, TodoListSnapshot, TodoStatus}; |
| 9 | |
| 10 | use super::model::{CompatPlanMetadata, NodeState, WorkGraphSnapshot, WorkNode}; |
| 11 | |
| 12 | pub type PlanProjection = PlanSnapshot; |
| 13 | pub type TodoProjection = TodoListSnapshot; |
| 14 | |
| 15 | impl CompatPlanMetadata { |
| 16 | #[must_use] |
| 17 | pub fn from_plan_snapshot(snapshot: &PlanSnapshot) -> Self { |
| 18 | Self { |
| 19 | title: snapshot.title.clone(), |
| 20 | objective: snapshot.objective.clone(), |
| 21 | context_summary: snapshot.context_summary.clone(), |
| 22 | explanation: snapshot.explanation.clone(), |
| 23 | sources_used: snapshot.sources_used.clone(), |
| 24 | critical_files: snapshot.critical_files.clone(), |
| 25 | constraints: snapshot.constraints.clone(), |
| 26 | recommended_approach: snapshot.recommended_approach.clone(), |
| 27 | verification_plan: snapshot.verification_plan.clone(), |
| 28 | risks_and_unknowns: snapshot.risks_and_unknowns.clone(), |
| 29 | handoff_packet: snapshot.handoff_packet.clone(), |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | #[must_use] |
| 34 | fn to_plan_snapshot(&self) -> PlanSnapshot { |
| 35 | PlanSnapshot { |
| 36 | title: self.title.clone(), |
| 37 | objective: self.objective.clone(), |
| 38 | context_summary: self.context_summary.clone(), |
| 39 | explanation: self.explanation.clone(), |
| 40 | sources_used: self.sources_used.clone(), |
| 41 | critical_files: self.critical_files.clone(), |
| 42 | constraints: self.constraints.clone(), |
| 43 | recommended_approach: self.recommended_approach.clone(), |
| 44 | verification_plan: self.verification_plan.clone(), |
| 45 | risks_and_unknowns: self.risks_and_unknowns.clone(), |
| 46 | handoff_packet: self.handoff_packet.clone(), |
| 47 | items: Vec::new(), |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | /// Derive the complete legacy Strategy/Plan snapshot. |
| 53 | #[must_use] |
| 54 | pub fn project_plan(snapshot: &WorkGraphSnapshot) -> PlanProjection { |
| 55 | let mut plan = snapshot.compat.plan.to_plan_snapshot(); |
| 56 | plan.items = snapshot |
| 57 | .compat |
| 58 | .plan_order |
| 59 | .iter() |
| 60 | .filter_map(|id| snapshot.node(id)) |
| 61 | .map(|node| PlanItemArg { |
| 62 | step: node.title.clone(), |
| 63 | status: plan_status(node), |
| 64 | }) |
| 65 | .collect(); |
| 66 | plan |
| 67 | } |
| 68 | |
| 69 | /// Derive the complete legacy To-do snapshot. Migration provenance stays in |
| 70 | /// the graph; old readers receive clean, user-visible content. |
| 71 | #[must_use] |
| 72 | pub fn project_todos(snapshot: &WorkGraphSnapshot) -> TodoProjection { |
| 73 | let items = snapshot |
| 74 | .compat |
| 75 | .todos |
| 76 | .iter() |
| 77 | .filter_map(|binding| { |
| 78 | let node = snapshot.node(&binding.node)?; |
| 79 | Some(TodoItem { |
| 80 | id: binding.legacy_id, |
| 81 | content: node.title.clone(), |
| 82 | status: todo_status(node), |
| 83 | }) |
| 84 | }) |
| 85 | .collect::<Vec<_>>(); |
| 86 | let settled = items.iter().filter(|item| item.status.is_settled()).count(); |
| 87 | let completion_pct = if items.is_empty() { |
| 88 | 0 |
| 89 | } else { |
| 90 | let rounded = (settled.saturating_mul(100) + items.len() / 2) / items.len(); |
| 91 | u8::try_from(rounded).unwrap_or(u8::MAX) |
| 92 | }; |
| 93 | let in_progress_id = items |
| 94 | .iter() |
| 95 | .find(|item| item.status == TodoStatus::InProgress) |
| 96 | .map(|item| item.id); |
| 97 | TodoListSnapshot { |
| 98 | items, |
| 99 | completion_pct, |
| 100 | in_progress_id, |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | fn plan_status(node: &WorkNode) -> StepStatus { |
| 105 | match node.state { |
| 106 | NodeState::Initializing | NodeState::Active => StepStatus::InProgress, |
| 107 | NodeState::Completed | NodeState::Verified => StepStatus::Completed, |
| 108 | _ => StepStatus::Pending, |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | fn todo_status(node: &WorkNode) -> TodoStatus { |
| 113 | match node.state { |
| 114 | NodeState::Initializing | NodeState::Active => TodoStatus::InProgress, |
| 115 | NodeState::Completed | NodeState::Verified => TodoStatus::Completed, |
| 116 | NodeState::Cancelled | NodeState::Superseded => TodoStatus::Cancelled, |
| 117 | _ => TodoStatus::Pending, |
| 118 | } |
| 119 | } |
| 120 |