Skip to main content

gba_core/
task.rs

1//! Task types for the GBA core engine.
2//!
3//! This module defines the task structure and related types
4//! for representing work units executed by the engine.
5
6use std::path::PathBuf;
7
8use serde::{Deserialize, Serialize};
9
10/// The kind of task to execute.
11///
12/// Each task kind maps to a directory under `tasks/` containing
13/// the task configuration and prompt templates.
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16#[non_exhaustive]
17pub enum TaskKind {
18    /// Initialize a repository for GBA.
19    Init,
20    /// Plan a new feature through interactive conversation.
21    Plan,
22    /// Execute a planned feature phase.
23    Execute,
24    /// Review code changes (read-only).
25    Review,
26    /// Verify that implementations meet specifications.
27    Verification,
28    /// Fix issues identified in review or verification.
29    Fix,
30    /// Generate PR description.
31    Pr,
32    /// Custom task with a user-defined name.
33    Custom(String),
34}
35
36impl TaskKind {
37    /// Get the directory name for this task kind.
38    ///
39    /// This is used to locate the task configuration and templates
40    /// under the `tasks/` directory.
41    #[must_use]
42    pub fn dir_name(&self) -> &str {
43        match self {
44            Self::Init => "init",
45            Self::Plan => "plan",
46            Self::Execute => "execute",
47            Self::Review => "review",
48            Self::Verification => "verification",
49            Self::Fix => "fix",
50            Self::Pr => "pr",
51            Self::Custom(name) => name.as_str(),
52        }
53    }
54}
55
56impl std::fmt::Display for TaskKind {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(f, "{}", self.dir_name())
59    }
60}
61
62/// A task to be executed by the engine.
63///
64/// A task combines a task kind with context data for template rendering
65/// and an optional system prompt override.
66#[derive(Debug, Clone)]
67pub struct Task {
68    /// The kind of task to execute.
69    pub kind: TaskKind,
70
71    /// Context data for template rendering.
72    ///
73    /// This is passed to the Jinja templates when rendering
74    /// the system and user prompts.
75    pub context: serde_json::Value,
76
77    /// Optional system prompt override.
78    ///
79    /// If provided, this completely overrides the rendered system prompt
80    /// from the template.
81    pub system_prompt: Option<String>,
82}
83
84impl Task {
85    /// Create a new task with the given kind and context.
86    ///
87    /// # Arguments
88    ///
89    /// * `kind` - The type of task to execute
90    /// * `context` - Context data for template rendering
91    ///
92    /// # Example
93    ///
94    /// ```
95    /// use gba_core::{Task, TaskKind};
96    /// use serde_json::json;
97    ///
98    /// let task = Task::new(TaskKind::Init, json!({"repo_path": "/path/to/repo"}));
99    /// ```
100    #[must_use]
101    pub fn new(kind: TaskKind, context: serde_json::Value) -> Self {
102        Self {
103            kind,
104            context,
105            system_prompt: None,
106        }
107    }
108
109    /// Set an optional system prompt override.
110    ///
111    /// This method consumes and returns `self` for method chaining.
112    #[must_use]
113    pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
114        self.system_prompt = Some(prompt.into());
115        self
116    }
117}
118
119/// Result of a task execution.
120#[derive(Debug, Clone)]
121#[non_exhaustive]
122pub struct TaskResult {
123    /// Whether the task completed successfully.
124    pub success: bool,
125
126    /// The output text from the task execution.
127    pub output: String,
128
129    /// Artifacts produced by the task (e.g., files created or modified).
130    pub artifacts: Vec<Artifact>,
131
132    /// Execution statistics.
133    pub stats: TaskStats,
134}
135
136/// An artifact produced by task execution.
137#[derive(Debug, Clone)]
138#[non_exhaustive]
139pub struct Artifact {
140    /// Path to the artifact file.
141    pub path: PathBuf,
142
143    /// Content of the artifact.
144    pub content: String,
145}
146
147/// Statistics from task execution.
148#[derive(Debug, Clone, Default)]
149#[non_exhaustive]
150pub struct TaskStats {
151    /// Number of conversation turns.
152    pub turns: u32,
153
154    /// Total input tokens used.
155    pub input_tokens: u64,
156
157    /// Total output tokens generated.
158    pub output_tokens: u64,
159
160    /// Estimated cost in USD.
161    pub cost_usd: f64,
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use serde_json::json;
168
169    #[test]
170    fn test_task_kind_dir_names() {
171        assert_eq!(TaskKind::Init.dir_name(), "init");
172        assert_eq!(TaskKind::Plan.dir_name(), "plan");
173        assert_eq!(TaskKind::Execute.dir_name(), "execute");
174        assert_eq!(TaskKind::Review.dir_name(), "review");
175        assert_eq!(TaskKind::Verification.dir_name(), "verification");
176        assert_eq!(TaskKind::Fix.dir_name(), "fix");
177        assert_eq!(
178            TaskKind::Custom("my_task".to_string()).dir_name(),
179            "my_task"
180        );
181    }
182
183    #[test]
184    fn test_task_kind_display() {
185        assert_eq!(format!("{}", TaskKind::Init), "init");
186        assert_eq!(
187            format!("{}", TaskKind::Custom("custom".to_string())),
188            "custom"
189        );
190    }
191
192    #[test]
193    fn test_should_create_task() {
194        let task = Task::new(TaskKind::Init, json!({"repo_path": "/tmp/repo"}));
195
196        assert_eq!(task.kind, TaskKind::Init);
197        assert_eq!(task.context["repo_path"], "/tmp/repo");
198        assert!(task.system_prompt.is_none());
199    }
200
201    #[test]
202    fn test_should_create_task_with_system_prompt() {
203        let task = Task::new(TaskKind::Plan, json!({})).with_system_prompt("Custom system prompt");
204
205        assert_eq!(task.system_prompt, Some("Custom system prompt".to_string()));
206    }
207
208    #[test]
209    fn test_task_kind_serialization() {
210        assert_eq!(serde_json::to_string(&TaskKind::Init).unwrap(), "\"init\"");
211        assert_eq!(
212            serde_json::to_string(&TaskKind::Custom("test".to_string())).unwrap(),
213            "{\"custom\":\"test\"}"
214        );
215    }
216
217    #[test]
218    fn test_task_kind_deserialization() {
219        let kind: TaskKind = serde_json::from_str("\"init\"").unwrap();
220        assert_eq!(kind, TaskKind::Init);
221
222        let kind: TaskKind = serde_json::from_str("{\"custom\":\"my_task\"}").unwrap();
223        assert_eq!(kind, TaskKind::Custom("my_task".to_string()));
224    }
225}