1use std::collections::HashMap;
20
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23
24use crate::contracts::{
25 Model, ModelEffort, PhaseOverrides, ReasoningEffort, Runner, RunnerCliOptionsPatch,
26};
27
28use super::priority::TaskPriority;
29use super::serde_helpers::{
30 custom_fields_schema, deserialize_custom_fields, model_effort_is_default, model_effort_schema,
31};
32
33#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
34#[serde(deny_unknown_fields)]
35pub struct Task {
36 pub id: String,
37
38 #[serde(default)]
39 pub status: TaskStatus,
40
41 pub title: String,
42
43 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub description: Option<String>,
46
47 #[serde(default)]
48 pub priority: TaskPriority,
49
50 #[serde(default)]
51 pub tags: Vec<String>,
52
53 #[serde(default)]
54 pub scope: Vec<String>,
55
56 #[serde(default)]
57 pub evidence: Vec<String>,
58
59 #[serde(default)]
60 pub plan: Vec<String>,
61
62 #[serde(default)]
63 pub notes: Vec<String>,
64
65 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub request: Option<String>,
68
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub agent: Option<TaskAgent>,
72
73 #[schemars(required)]
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub created_at: Option<String>,
77 #[schemars(required)]
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub updated_at: Option<String>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub completed_at: Option<String>,
82
83 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub started_at: Option<String>,
90
91 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub estimated_minutes: Option<u32>,
95
96 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub actual_minutes: Option<u32>,
100
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub scheduled_start: Option<String>,
104
105 #[serde(default)]
107 pub depends_on: Vec<String>,
108
109 #[serde(default)]
112 pub blocks: Vec<String>,
113
114 #[serde(default)]
117 pub relates_to: Vec<String>,
118
119 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub duplicates: Option<String>,
123
124 #[serde(default, deserialize_with = "deserialize_custom_fields")]
127 #[schemars(schema_with = "custom_fields_schema")]
128 pub custom_fields: HashMap<String, String>,
129
130 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub parent_id: Option<String>,
133}
134
135#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default, JsonSchema)]
136#[serde(rename_all = "snake_case")]
137pub enum TaskStatus {
138 Draft,
139 #[default]
140 Todo,
141 Doing,
142 Done,
143 Rejected,
144}
145
146impl TaskStatus {
147 pub fn as_str(self) -> &'static str {
148 match self {
149 TaskStatus::Draft => "draft",
150 TaskStatus::Todo => "todo",
151 TaskStatus::Doing => "doing",
152 TaskStatus::Done => "done",
153 TaskStatus::Rejected => "rejected",
154 }
155 }
156}
157
158impl std::fmt::Display for TaskStatus {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.write_str(self.as_str())
161 }
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
165#[serde(deny_unknown_fields)]
166pub struct TaskAgent {
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub runner: Option<Runner>,
169
170 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub model: Option<Model>,
172
173 #[serde(default, skip_serializing_if = "model_effort_is_default")]
175 #[schemars(schema_with = "model_effort_schema")]
176 pub model_effort: ModelEffort,
177
178 #[schemars(range(min = 1, max = 3))]
180 #[serde(default, skip_serializing_if = "Option::is_none")]
181 pub phases: Option<u8>,
182
183 #[schemars(range(min = 1))]
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub iterations: Option<u8>,
187
188 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub followup_reasoning_effort: Option<ReasoningEffort>,
191
192 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub runner_cli: Option<RunnerCliOptionsPatch>,
198
199 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub phase_overrides: Option<PhaseOverrides>,
202}