Skip to main content

chant/spec/
frontmatter.rs

1//! Frontmatter types and defaults for specs.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
6#[serde(rename_all = "snake_case")]
7pub enum SpecStatus {
8    #[default]
9    Pending,
10    InProgress,
11    Paused,
12    Completed,
13    Failed,
14    NeedsAttention,
15    Ready,
16    Blocked,
17    Cancelled,
18}
19
20/// Approval status for a spec
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
22#[serde(rename_all = "snake_case")]
23pub enum ApprovalStatus {
24    #[default]
25    Pending,
26    Approved,
27    Rejected,
28}
29
30/// Approval information for a spec
31#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32pub struct Approval {
33    /// Whether approval is required for this spec
34    #[serde(default)]
35    pub required: bool,
36    /// Current approval status
37    #[serde(default)]
38    pub status: ApprovalStatus,
39    /// Name of the person who approved/rejected
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub by: Option<String>,
42    /// Timestamp of approval/rejection
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub at: Option<String>,
45}
46
47/// Represents a dependency that is blocking a spec from being ready.
48#[derive(Debug, Clone)]
49pub struct BlockingDependency {
50    /// The spec ID of the blocking dependency.
51    pub spec_id: String,
52    /// The title of the blocking dependency, if available.
53    pub title: Option<String>,
54    /// The current status of the blocking dependency.
55    pub status: SpecStatus,
56    /// When the dependency was completed, if applicable.
57    pub completed_at: Option<String>,
58    /// Whether this is a sibling dependency (from group ordering).
59    pub is_sibling: bool,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct SpecFrontmatter {
64    #[serde(default = "default_type")]
65    pub r#type: String,
66    #[serde(default)]
67    pub status: SpecStatus,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub depends_on: Option<Vec<String>>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub labels: Option<Vec<String>>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub target_files: Option<Vec<String>>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub context: Option<Vec<String>>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub prompt: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub branch: Option<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub commits: Option<Vec<String>>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub completed_at: Option<String>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub model: Option<String>,
86    // Documentation-specific fields
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub tracks: Option<Vec<String>>,
89    // Research-specific fields
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub informed_by: Option<Vec<String>>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub origin: Option<Vec<String>>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub schedule: Option<String>,
96    // Conflict-specific fields
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub source_branch: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub target_branch: Option<String>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub conflicting_files: Option<Vec<String>>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub blocked_specs: Option<Vec<String>>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub original_spec: Option<String>,
107    // Verification-specific fields
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub last_verified: Option<String>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub verification_status: Option<String>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub verification_failures: Option<Vec<String>>,
114    // Replay tracking fields
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub replayed_at: Option<String>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub replay_count: Option<u32>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub original_completed_at: Option<String>,
121    // Derivation tracking - which fields were automatically derived
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub derived_fields: Option<Vec<String>>,
124    // Approval workflow fields
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub approval: Option<Approval>,
127    // Driver/group member tracking
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub members: Option<Vec<String>>,
130    // Output schema validation
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub output_schema: Option<String>,
133    // Site generation control - set to false to exclude from site
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub public: Option<bool>,
136    // Retry state for failed specs (watch mode)
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub retry_state: Option<crate::retry::RetryState>,
139}
140
141pub(crate) fn default_type() -> String {
142    "code".to_string()
143}
144
145impl Default for SpecFrontmatter {
146    fn default() -> Self {
147        Self {
148            r#type: default_type(),
149            status: SpecStatus::Pending,
150            depends_on: None,
151            labels: None,
152            target_files: None,
153            context: None,
154            prompt: None,
155            branch: None,
156            commits: None,
157            completed_at: None,
158            model: None,
159            tracks: None,
160            informed_by: None,
161            origin: None,
162            schedule: None,
163            source_branch: None,
164            target_branch: None,
165            conflicting_files: None,
166            blocked_specs: None,
167            original_spec: None,
168            last_verified: None,
169            verification_status: None,
170            verification_failures: None,
171            replayed_at: None,
172            replay_count: None,
173            original_completed_at: None,
174            derived_fields: None,
175            approval: None,
176            members: None,
177            output_schema: None,
178            public: None,
179            retry_state: None,
180        }
181    }
182}