diff --git a/crates/cli/src/cmd/task.rs b/crates/cli/src/cmd/task.rsindex f94f516..b43b585 100644--- a/crates/cli/src/cmd/task.rs+++ b/crates/cli/src/cmd/task.rs@@ -5,14 +5,14 @@ use f0rge_core::{Forge, Task};55 use serde::Serialize;66 77 pub fn help() -> &'static str {8- "f0rge task: kanban cards\nusage: f0rge task [--json] <create|list|move|done>\ncols derived: todo doing blocked done\n"8+ "f0rge task: kanban cards\nusage: f0rge task [--json] <create|list|move|done>\ncreate: --title T [--body B] [--column C]\ncols: todo doing blocked done\n"99 }1010 1111 const COLS: &[&str] = &["todo", "doing", "blocked", "done"];1212 const VERBS: &[&str] = &["create", "list", "move", "done"];1313 1414 fn spec() -> Vec<args::FlagSpec> {15- args::spec_from_lists(&["title", "issue", "jq"], &["json"])15+ args::spec_from_lists(&["title", "body", "column", "issue", "jq"], &["json"])1616 }1717 1818 fn usage(m: &str) -> CliError {@@ -23,6 +23,17 @@ fn clean(s: &str) -> String {2323 s.chars().filter(|c| *c != '\n' && *c != '\r').take(80).collect::<String>().trim().to_string()2424 }2525 26+fn norm_body(b: &str) -> String {27+ if b.is_empty() {28+ return String::new();29+ }30+ if b.ends_with('\n') {31+ b.to_string()32+ } else {33+ format!("{b}\n")34+ }35+}36+2637 /// True when a plain scalar would re-parse as bool/null/number instead2738 /// of a string (e.g. a task titled `123`). Quoting is always safe.2839 fn risky(s: &str) -> bool {@@ -219,16 +230,28 @@ fn create(fs: &args::FlagSet, fg: &Forge) -> Result<(), CliError> {219230 None => None,220231 };221232 let id = uuid::Uuid::new_v4().to_string();233+ let body = norm_body(fs.get("body").unwrap_or(""));234+ let col = match fs.get("column") {235+ Some(c) => {236+ let cc = clean(c);237+ if !COLS.contains(&cc.as_str()) {238+ return Err(usage("f0rge task create --column todo|doing|blocked|done"));239+ }240+ cc241+ }242+ None => "todo".to_string(),243+ };244+ let now = f0rge_core::now_rfc3339();222245 let t = Task {223246 id: id.clone(),224247 title: title.to_string(),225- status: "todo".to_string(),248+ status: col.clone(),226249 assignee: None,227- created: f0rge_core::now_rfc3339(),228- updated: None,229- closed: None,250+ created: now.clone(),251+ updated: if col == "done" { Some(now.clone()) } else { None },252+ closed: if col == "done" { Some(now) } else { None },230253 };231- fg.atomic_write(&fg.task_file(&id), render(&t, issue.as_deref(), "").as_bytes())254+ fg.atomic_write(&fg.task_file(&id), render(&t, issue.as_deref(), &body).as_bytes())232255 .map_err(CliError::from)?;233256 output::emit_json(&view_of(&t, issue), fs.get("jq"));234257 Ok(())