Skip to main content

sc/model/
plan.rs

1//! Plan model for SaveContext.
2//!
3//! Plans represent PRDs, specs, or feature documentation that can be linked
4//! to epics and issues for tracking implementation.
5
6use serde::{Deserialize, Serialize};
7
8/// Plan status values.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum PlanStatus {
12    Draft,
13    Active,
14    Completed,
15}
16
17impl PlanStatus {
18    /// Get the string representation for storage.
19    #[must_use]
20    pub const fn as_str(&self) -> &'static str {
21        match self {
22            Self::Draft => "draft",
23            Self::Active => "active",
24            Self::Completed => "completed",
25        }
26    }
27
28    /// Parse from string.
29    #[must_use]
30    pub fn from_str(s: &str) -> Self {
31        match s.to_lowercase().as_str() {
32            "active" => Self::Active,
33            "completed" => Self::Completed,
34            _ => Self::Draft,
35        }
36    }
37}
38
39impl Default for PlanStatus {
40    fn default() -> Self {
41        Self::Draft
42    }
43}
44
45/// A plan in SaveContext.
46///
47/// Plans provide:
48/// - PRD/specification storage
49/// - Linkage to epics and issues
50/// - Success criteria tracking
51/// - Status progression (draft -> active -> completed)
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Plan {
54    /// Unique identifier (UUID format)
55    pub id: String,
56
57    /// Short ID for easy reference (e.g., "PLAN-1")
58    pub short_id: Option<String>,
59
60    /// Project ID this plan belongs to
61    pub project_id: String,
62
63    /// Project path for queries
64    pub project_path: String,
65
66    /// Plan title
67    pub title: String,
68
69    /// Plan content (markdown PRD/spec)
70    pub content: Option<String>,
71
72    /// Current status
73    pub status: PlanStatus,
74
75    /// Success criteria for completion
76    pub success_criteria: Option<String>,
77
78    /// Session where this plan was created
79    pub created_in_session: Option<String>,
80
81    /// Session where this plan was completed
82    pub completed_in_session: Option<String>,
83
84    /// Creation timestamp (Unix milliseconds)
85    pub created_at: i64,
86
87    /// Last update timestamp (Unix milliseconds)
88    pub updated_at: i64,
89
90    /// Completion timestamp (Unix milliseconds)
91    pub completed_at: Option<i64>,
92}
93
94impl Plan {
95    /// Create a new plan with default values.
96    pub fn new(project_id: String, project_path: String, title: String) -> Self {
97        let now = chrono::Utc::now().timestamp_millis();
98        let id = format!("plan_{}", &uuid::Uuid::new_v4().to_string()[..12]);
99
100        Self {
101            id,
102            short_id: None,
103            project_id,
104            project_path,
105            title,
106            content: None,
107            status: PlanStatus::Draft,
108            success_criteria: None,
109            created_in_session: None,
110            completed_in_session: None,
111            created_at: now,
112            updated_at: now,
113            completed_at: None,
114        }
115    }
116
117    /// Set the plan content.
118    #[must_use]
119    pub fn with_content(mut self, content: &str) -> Self {
120        self.content = Some(content.to_string());
121        self
122    }
123
124    /// Set the plan status.
125    #[must_use]
126    pub fn with_status(mut self, status: PlanStatus) -> Self {
127        self.status = status;
128        self
129    }
130
131    /// Set the success criteria.
132    #[must_use]
133    pub fn with_success_criteria(mut self, criteria: &str) -> Self {
134        self.success_criteria = Some(criteria.to_string());
135        self
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_new_plan() {
145        let plan = Plan::new(
146            "proj_123".to_string(),
147            "/home/user/myproject".to_string(),
148            "Authentication System".to_string(),
149        );
150
151        assert!(plan.id.starts_with("plan_"));
152        assert_eq!(plan.project_id, "proj_123");
153        assert_eq!(plan.title, "Authentication System");
154        assert_eq!(plan.status, PlanStatus::Draft);
155    }
156
157    #[test]
158    fn test_plan_status_parsing() {
159        assert_eq!(PlanStatus::from_str("draft"), PlanStatus::Draft);
160        assert_eq!(PlanStatus::from_str("active"), PlanStatus::Active);
161        assert_eq!(PlanStatus::from_str("completed"), PlanStatus::Completed);
162        assert_eq!(PlanStatus::from_str("unknown"), PlanStatus::Draft);
163    }
164}