Add effort column via migration 1

Amolith created

Change summary

src/cli.rs                              |  4 ++++
src/cmd/create.rs                       |  7 +++++--
src/cmd/export.rs                       |  3 ++-
src/cmd/import.rs                       | 10 ++++++++--
src/cmd/list.rs                         |  2 +-
src/cmd/mod.rs                          |  2 ++
src/cmd/ready.rs                        |  2 +-
src/cmd/search.rs                       |  2 +-
src/cmd/show.rs                         |  1 +
src/db.rs                               |  4 +++-
src/migrate.rs                          |  7 +++++++
src/migrations/0002_add_effort.down.sql |  1 +
src/migrations/0002_add_effort.up.sql   |  1 +
13 files changed, 37 insertions(+), 9 deletions(-)

Detailed changes

src/cli.rs 🔗

@@ -30,6 +30,10 @@ pub enum Command {
         #[arg(short, long, default_value_t = 2)]
         priority: i32,
 
+        /// Effort level (1=low, 2=medium, 3=high)
+        #[arg(short, long, default_value_t = 2)]
+        effort: i32,
+
         /// Task type
         #[arg(short = 't', long = "type", default_value = "task")]
         task_type: String,

src/cmd/create.rs 🔗

@@ -6,6 +6,7 @@ use crate::db;
 pub struct Opts<'a> {
     pub title: Option<&'a str>,
     pub priority: i32,
+    pub effort: i32,
     pub task_type: &'a str,
     pub desc: Option<&'a str>,
     pub parent: Option<&'a str>,
@@ -34,14 +35,15 @@ pub fn run(root: &Path, opts: Opts) -> Result<()> {
     };
 
     conn.execute(
-        "INSERT INTO tasks (id, title, description, type, priority, status, parent, created, updated)
-         VALUES (?1, ?2, ?3, ?4, ?5, 'open', ?6, ?7, ?8)",
+        "INSERT INTO tasks (id, title, description, type, priority, status, effort, parent, created, updated)
+         VALUES (?1, ?2, ?3, ?4, ?5, 'open', ?6, ?7, ?8, ?9)",
         rusqlite::params![
             id,
             title,
             desc,
             opts.task_type,
             opts.priority,
+            opts.effort,
             opts.parent.unwrap_or(""),
             ts,
             ts
@@ -68,6 +70,7 @@ pub fn run(root: &Path, opts: Opts) -> Result<()> {
             task_type: opts.task_type.to_string(),
             priority: opts.priority,
             status: "open".to_string(),
+            effort: opts.effort,
             parent: opts.parent.unwrap_or("").to_string(),
             created: ts.clone(),
             updated: ts,

src/cmd/export.rs 🔗

@@ -7,7 +7,7 @@ pub fn run(root: &Path) -> Result<()> {
     let conn = db::open(root)?;
 
     let mut stmt = conn.prepare(
-        "SELECT id, title, description, type, priority, status, parent, created, updated
+        "SELECT id, title, description, type, priority, status, effort, parent, created, updated
          FROM tasks ORDER BY id",
     )?;
 
@@ -26,6 +26,7 @@ pub fn run(root: &Path) -> Result<()> {
                 task_type: t.task_type.clone(),
                 priority: t.priority,
                 status: t.status.clone(),
+                effort: t.effort,
                 parent: t.parent.clone(),
                 created: t.created.clone(),
                 updated: t.updated.clone(),

src/cmd/import.rs 🔗

@@ -17,6 +17,8 @@ struct ImportTask {
     priority: i32,
     #[serde(default = "default_status")]
     status: String,
+    #[serde(default = "default_effort")]
+    effort: i32,
     #[serde(default)]
     parent: String,
     created: String,
@@ -36,6 +38,9 @@ fn default_priority() -> i32 {
 fn default_status() -> String {
     "open".into()
 }
+fn default_effort() -> i32 {
+    2
+}
 
 pub fn run(root: &Path, file: &str) -> Result<()> {
     let conn = db::open(root)?;
@@ -58,8 +63,8 @@ pub fn run(root: &Path, file: &str) -> Result<()> {
 
         conn.execute(
             "INSERT OR REPLACE INTO tasks
-             (id, title, description, type, priority, status, parent, created, updated)
-             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
+             (id, title, description, type, priority, status, effort, parent, created, updated)
+             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
             rusqlite::params![
                 t.id,
                 t.title,
@@ -67,6 +72,7 @@ pub fn run(root: &Path, file: &str) -> Result<()> {
                 t.task_type,
                 t.priority,
                 t.status,
+                t.effort,
                 t.parent,
                 t.created,
                 t.updated,

src/cmd/list.rs 🔗

@@ -13,7 +13,7 @@ pub fn run(
     let conn = db::open(root)?;
 
     let mut sql = String::from(
-        "SELECT id, title, description, type, priority, status, parent, created, updated
+        "SELECT id, title, description, type, priority, status, effort, parent, created, updated
          FROM tasks WHERE 1=1",
     );
     let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();

src/cmd/mod.rs 🔗

@@ -32,6 +32,7 @@ pub fn dispatch(cli: &Cli) -> Result<()> {
         Command::Create {
             title,
             priority,
+            effort,
             task_type,
             desc,
             parent,
@@ -43,6 +44,7 @@ pub fn dispatch(cli: &Cli) -> Result<()> {
                 create::Opts {
                     title: title.as_deref(),
                     priority: *priority,
+                    effort: *effort,
                     task_type,
                     desc: desc.as_deref(),
                     parent: parent.as_deref(),

src/cmd/ready.rs 🔗

@@ -7,7 +7,7 @@ pub fn run(root: &Path, json: bool) -> Result<()> {
     let conn = db::open(root)?;
 
     let mut stmt = conn.prepare(
-        "SELECT id, title, description, type, priority, status, parent, created, updated
+        "SELECT id, title, description, type, priority, status, effort, parent, created, updated
          FROM tasks
          WHERE status = 'open'
            AND id NOT IN (

src/cmd/search.rs 🔗

@@ -8,7 +8,7 @@ pub fn run(root: &Path, query: &str, json: bool) -> Result<()> {
     let pattern = format!("%{query}%");
 
     let mut stmt = conn.prepare(
-        "SELECT id, title, description, type, priority, status, parent, created, updated
+        "SELECT id, title, description, type, priority, status, effort, parent, created, updated
          FROM tasks
          WHERE title LIKE ?1 OR description LIKE ?1",
     )?;

src/cmd/show.rs 🔗

@@ -25,6 +25,7 @@ pub fn run(root: &Path, id: &str, json: bool) -> Result<()> {
         println!("{}       title{} = {}", c.bold, c.reset, t.title);
         println!("{}      status{} = {}", c.bold, c.reset, t.status);
         println!("{}    priority{} = {}", c.bold, c.reset, t.priority);
+        println!("{}      effort{} = {}", c.bold, c.reset, t.effort);
         println!("{}        type{} = {}", c.bold, c.reset, t.task_type);
         if !t.description.is_empty() {
             println!("{} description{} = {}", c.bold, c.reset, t.description);

src/db.rs 🔗

@@ -19,6 +19,7 @@ pub struct Task {
     pub task_type: String,
     pub priority: i32,
     pub status: String,
+    pub effort: i32,
     pub parent: String,
     pub created: String,
     pub updated: String,
@@ -62,6 +63,7 @@ pub fn row_to_task(row: &rusqlite::Row) -> rusqlite::Result<Task> {
         task_type: row.get("type")?,
         priority: row.get("priority")?,
         status: row.get("status")?,
+        effort: row.get("effort")?,
         parent: row.get("parent")?,
         created: row.get("created")?,
         updated: row.get("updated")?,
@@ -89,7 +91,7 @@ pub fn load_blockers(conn: &Connection, task_id: &str) -> Result<Vec<String>> {
 /// Load a full task with labels and blockers.
 pub fn load_task_detail(conn: &Connection, id: &str) -> Result<TaskDetail> {
     let task = conn.query_row(
-        "SELECT id, title, description, type, priority, status, parent, created, updated
+        "SELECT id, title, description, type, priority, status, effort, parent, created, updated
          FROM tasks WHERE id = ?1",
         [id],
         row_to_task,

src/migrate.rs 🔗

@@ -26,6 +26,13 @@ static MIGRATIONS: &[Migration] = &[
         post_hook_up: None,
         post_hook_down: None,
     },
+    // 1 → 2: add effort column (integer-backed, default medium)
+    Migration {
+        up_sql: include_str!("migrations/0002_add_effort.up.sql"),
+        down_sql: include_str!("migrations/0002_add_effort.down.sql"),
+        post_hook_up: None,
+        post_hook_down: None,
+    },
 ];
 
 /// Read the current schema version from the database.