Builds the task, creating all necessary files and directories
(self, ctx: &AppContext)
| 234 | |
| 235 | /// Builds the task, creating all necessary files and directories |
| 236 | pub async fn build(self, ctx: &AppContext) -> Result<Task, Box<dyn Error>> { |
| 237 | let repo_root = self |
| 238 | .repo_root |
| 239 | .clone() |
| 240 | .ok_or("Repository root is required")?; |
| 241 | let name = self.name.clone().ok_or("Task name is required")?; |
| 242 | let task_type = self |
| 243 | .task_type |
| 244 | .clone() |
| 245 | .unwrap_or_else(|| "generic".to_string()); |
| 246 | |
| 247 | // Check if template requires a prompt |
| 248 | let template_needs_prompt = if task_type != "generic" { |
| 249 | match find_template(&task_type, Some(&repo_root), &ctx.tsk_env()) { |
| 250 | Ok(template_content) => { |
| 251 | let body = strip_frontmatter(&template_content); |
| 252 | body.contains("{{PROMPT}}") || body.contains("{{DESCRIPTION}}") |
| 253 | } |
| 254 | Err(_) => true, // If we can't read the template, assume it needs a prompt |
| 255 | } |
| 256 | } else { |
| 257 | true // Generic tasks always need a prompt |
| 258 | }; |
| 259 | |
| 260 | // Validate input |
| 261 | if template_needs_prompt |
| 262 | && self.prompt.is_none() |
| 263 | && self.prompt_file_path.is_none() |
| 264 | && self.existing_instructions_file.is_none() |
| 265 | && !self.edit |
| 266 | { |
| 267 | return Err("Either prompt or prompt file must be provided, or use edit mode".into()); |
| 268 | } |
| 269 | |
| 270 | if self.edit && !ctx.interactive() { |
| 271 | return Err("--edit requires an interactive terminal (stdin is not a TTY)".into()); |
| 272 | } |
| 273 | |
| 274 | // Auto-detect project name first (needed for project config lookup) |
| 275 | let project = match self.project { |
| 276 | Some(ref p) => p.clone(), |
| 277 | None => match crate::repository::detect_project_name(&repo_root).await { |
| 278 | Ok(detected) => detected, |
| 279 | Err(e) => { |
| 280 | eprintln!("Warning: Failed to detect project name: {e}. Using default."); |
| 281 | "default".to_string() |
| 282 | } |
| 283 | }, |
| 284 | }; |
| 285 | |
| 286 | // Resolve configuration for this project (layers defaults + project file + project config) |
| 287 | let tsk_config = ctx.tsk_config(); |
| 288 | let project_config = tsk_config::load_project_config(&repo_root); |
| 289 | let mut resolved = |
| 290 | tsk_config.resolve_config(&project, project_config.as_ref(), Some(&repo_root)); |
| 291 | |
| 292 | // Get agent: CLI flag > resolved config (project > defaults > built-in) |
| 293 | let agent = tsk_config::resolve_agent(self.agent.clone(), &resolved); |