cmdDone marks a task done. Per spec §5.3 this is a single UPDATE that does NOT touch the iTerm tab, kill the Claude session, or clear session_id — the session can still be resumed via `flow do` after manually reopening the task if the user ever needs to. After the status flip, if the task has a ses
(args []string)
| 26 | // A failed sweep (missing claude binary, non-zero exit) only emits a |
| 27 | // warning — the status flip is the contract; the sweep is best-effort. |
| 28 | func cmdDone(args []string) int { |
| 29 | if len(args) == 0 { |
| 30 | fmt.Fprintln(os.Stderr, "error: done requires a task ref") |
| 31 | return 2 |
| 32 | } |
| 33 | query := args[0] |
| 34 | fs := flagSet("done") |
| 35 | if err := fs.Parse(args[1:]); err != nil { |
| 36 | return 2 |
| 37 | } |
| 38 | |
| 39 | dbPath, err := flowDBPath() |
| 40 | if err != nil { |
| 41 | fmt.Fprintf(os.Stderr, "error: %v\n", err) |
| 42 | return 1 |
| 43 | } |
| 44 | db, err := flowdb.OpenDB(dbPath) |
| 45 | if err != nil { |
| 46 | fmt.Fprintf(os.Stderr, "error: %v\n", err) |
| 47 | return 1 |
| 48 | } |
| 49 | defer db.Close() |
| 50 | |
| 51 | task, rc := findTask(db, query) |
| 52 | if rc != 0 { |
| 53 | return rc |
| 54 | } |
| 55 | |
| 56 | // Done implies a session existed and produced learnings. A backlog |
| 57 | // task that was never started has nothing to sweep. The task-table |
| 58 | // CHECK constraint would reject the UPDATE with a cryptic error; |
| 59 | // we surface the right verbs here instead. |
| 60 | if task.Status == "backlog" && (!task.SessionID.Valid || task.SessionID.String == "") { |
| 61 | fmt.Fprintf(os.Stderr, |
| 62 | "error: task %q has no session_id — flow done requires at least one prior `flow do` (or `flow do --here`) to have attached a session whose transcript the sweep can read.\n"+ |
| 63 | " options:\n"+ |
| 64 | " - if you've been working on this task in the current Claude session, bind it now and retry close-out:\n"+ |
| 65 | " flow do --here %s (then re-run: flow done %s)\n"+ |
| 66 | " - if it was never worked on and isn't relevant, archive it instead:\n"+ |
| 67 | " flow archive %s\n", |
| 68 | task.Slug, task.Slug, task.Slug, task.Slug) |
| 69 | return 1 |
| 70 | } |
| 71 | |
| 72 | now := flowdb.NowISO() |
| 73 | res, err := db.Exec( |
| 74 | `UPDATE tasks SET status='done', status_changed_at=?, updated_at=? WHERE slug=?`, |
| 75 | now, now, task.Slug, |
| 76 | ) |
| 77 | if err != nil { |
| 78 | fmt.Fprintf(os.Stderr, "error: mark done: %v\n", err) |
| 79 | return 1 |
| 80 | } |
| 81 | if affected, _ := res.RowsAffected(); affected == 0 { |
| 82 | fmt.Fprintf(os.Stderr, "error: task %q not updated\n", task.Slug) |
| 83 | return 1 |
| 84 | } |
| 85 | fmt.Printf("Marked %s as done\n", task.Slug) |