docs: plan a definition of done for tasks
Assisted-by: Claude Opus 5.5 via Claude Code
diff --git a/docs/plans/2026-09-10-tag-cloud.md b/docs/plans/2026-09-10-tag-cloud.md
index 66a9d97..b7e4341 100644
--- a/docs/plans/2026-09-10-tag-cloud.md
+++ b/docs/plans/2026-09-10-tag-cloud.md
@@ -2,12 +2,13 @@
**Status:** Proposal. Not scheduled; written down to think through the shape of the change before deciding whether to do it.
-This document proposes two changes to Ranger's data model:
+This document proposes three changes to Ranger's data model:
- Replace backlogs with a single pool of tasks organized by `key:value` tags. A backlog becomes a tag such as `project:ranger`, and the default backlog becomes a default filter.
- Replace stored positions with a graph of task relationships. Blockers and priority are both edges, task order is derived from them, and the graph must be acyclic. Subtasks are a separate tree.
+- Give each task a definition of done as its own field rather than prose buried in the description.
-The two changes are independent. Neither needs the other, and shipping them together would multiply the risk in `src/ops/task.rs`, which holds both the backlog scoping and the ordering logic. Treat them as separate projects.
+The changes are independent. None needs the others, and shipping the first two together would multiply the risk in `src/ops/task.rs`, which holds both the backlog scoping and the ordering logic. Treat them as separate projects.
Out of scope: multi-user features, saved views, and deriving task state from relationships (see [Relationships don't change state](#relationships-dont-change-state)).
@@ -150,6 +151,37 @@ Relationships are displayed, not enforced on state. Ranger won't mark a parent d
Ordering is the one exception, and it is derived rather than stored, so nothing has to stay consistent on disk.
+## Part 3: a definition of done
+
+### Why it gets its own field
+
+A task today has a title and a free-form description, so whatever says when the task is finished is mixed into the description, if it's written down at all. The `ranger` skill tells agents to pick up the top task and keep working, which makes "how do I know I'm done" the question an agent most needs answered and the one Ranger gives it least help with. A separate field makes the criteria something Ranger can display, prompt for, and filter on.
+
+### Schema
+
+Add one nullable column:
+
+```sql
+ALTER TABLE tasks ADD COLUMN done_when TEXT;
+```
+
+It's free-form text, like `description`. Structured checklists with per-item completion are out of scope; they would bring back the derived-state problem described in [Relationships don't change state](#relationships-dont-change-state). Because this is a plain `ADD COLUMN`, the migration doesn't recreate `tasks` and needs no `PRAGMA foreign_keys` dance. Existing tasks get `NULL`.
+
+### CLI surface
+
+- `task create` and `task edit` take `--done-when <text>`.
+- `task show` prints the field after the description, labeled `Done when:`.
+- The editor flow in `edit_in_editor` currently treats the first line as the title and everything else as the description. It needs a marker line, such as `Done when:`, that splits the body into description and definition of done. A missing marker leaves the field unchanged, and a marker with nothing under it clears the field.
+- The filter language from Part 1 gains a term for tasks without a definition, such as `-has:done_when`, so the gaps are easy to find.
+
+### Ranger records the definition but doesn't check it
+
+Marking a task done doesn't verify the criteria; Ranger can't, and pretending to would be worse than not trying. The `ranger` skill changes instead: agents read `done_when` before starting a task and confirm each criterion before marking it done.
+
+### Affected code
+
+`migrations/`, `src/models.rs` (`Task`), `src/ops/task.rs` (`create` and `edit`), `src/bin/ranger/commands/task.rs` (flags, `print_task_detail`, and the editor parser), the task detail view in `serve.rs`, and `skills/ranger/SKILL.md`. The editor parser's marker handling is the main new branching under the coverage gate.
+
## Affected code
The following files reference backlogs, with approximate counts of the word `backlog`:
@@ -183,9 +215,12 @@ Each step leaves the build green and can ship on its own:
Steps 1 through 3 are reversible. Step 6 is the one that can't be undone without restoring positions from a backup.
+Part 3 doesn't depend on either sequence and can ship before, between, or after them. Only the `has:` filter term waits for step 2.
+
## Open questions
- Why were `blockers`, `parent_id`, and `task_edges` dropped in migrations 005, 008, and 011? This proposal rebuilds the `009` schema almost exactly, so whatever made it not worth keeping applies here too.
- Should the migration chain only `ready` tasks, as proposed above, or every task in position order? Chaining everything preserves today's list exactly, at the cost of turning incidental positions into stated constraints.
- Is `created_at` the right linearization tiebreak, or should unranked tasks sort newest-first?
- May a blocker edge connect a task to its own ancestor or descendant in the subtask tree?
+- Should moving a task to `ready` require a definition of done? That would make the field a gate rather than a convention, at the cost of breaking `task create --state ready` for quick captures.