merge 33d3ee8d-cb1b-4933-ac48-d8b2e01a353a

558aab6gl0w

all commits

Message

merge 33d3ee8d-cb1b-4933-ac48-d8b2e01a353a

Diff

crates/cli/src/cmd/issue.rs+2 −1
diff --git a/crates/cli/src/cmd/issue.rs b/crates/cli/src/cmd/issue.rsindex 13d8975..74cdd23 100644--- a/crates/cli/src/cmd/issue.rs+++ b/crates/cli/src/cmd/issue.rs@@ -73,9 +73,10 @@ fn numbers(fg: &Forge) -> Result<BTreeMap<String, u64>, CliError> {7373     }7474 }7575 76-/// Accept derived number or full uuid; garbage -> usage (exit 2).76+/// Accept derived number ("N" or "#N") or full uuid; garbage -> usage (exit 2).7777 fn resolve_id(fg: &Forge, raw: &str) -> Result<String, CliError> {7878     let id = clean(raw);79+    let id = id.strip_prefix('#').unwrap_or(&id).to_string();7980     if !id.is_empty() && id.bytes().all(|b| b.is_ascii_digit()) {8081         let n: u64 = id.parse().map_err(|_| usage("f0rge issue <verb> <id>"))?;8182         for (uuid, num) in numbers(fg)? {
crates/cli/src/cmd/task.rs+30 −7
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(())
spec/schema.md+2 −1
diff --git a/spec/schema.md b/spec/schema.mdindex b5d08f6..1099da6 100644--- a/spec/schema.md+++ b/spec/schema.md@@ -66,7 +66,8 @@ Body = review text. Golden: `spec/fixtures/pr-review.md`.6666 ### task — `.forge/tasks/<uuid>.md`6767 Required: `id`, `title`, `status`, `created`.6868 Optional: `assignee`, `updated`, `closed`.69-Column derived from `status`. Golden: `spec/fixtures/task.md`.69+Column derived from `status` (`todo|doing|blocked|done`).70+Create: `--title T [--body B] [--column C]` (default `todo`); body = markdown after frontmatter. Golden: `spec/fixtures/task.md`.7071 7172 ### label — entries in `.forge/labels.md` (YAML list)7273 Required: `name`. Optional: `color`, `description`.