From 9d2dbb017dd8a093dd9740db94e13d20e1916263 Mon Sep 17 00:00:00 2001 From: istos Date: Wed, 29 Jul 2026 22:37:36 +0200 Subject: [PATCH] Enrich the task template for new cards (task 07) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge the highest-value slots from a fuller development-task template into bench's lean one: an optional Depends on header line, an Affected areas line in Context, Out of scope bullets in What to build, edge-case and Given/When/Then guidance in Acceptance, and a Risks slot in Notes. Each slot says who consumes it and is marked deletable — empty boilerplate is worse than absence. The load-bearing contracts are unchanged: exact Status values, the Open questions heading the NOT READY gate keys off, Acceptance as the review target, and the template staying off the board. Organisation process (staging, sign-offs, approvals, contacts) is deliberately absent; a new contract test pins both the gained slots and what must not grow back. CLAUDE.md's Task file format section documents the Depends on line. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 10 +++++ tasks/task-template.md | 42 ++++++++++++++++-- tests/test_task_template.py | 85 +++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 tests/test_task_template.py diff --git a/CLAUDE.md b/CLAUDE.md index 459c588..f8aeaf8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -413,5 +413,15 @@ Type is orthogonal to status. A discovery task — research, scoping, spiking an approach — moves through the same five stages as everything else; "discovery" describes the work, not where it sits on the board. +An optional **Depends on** line can name what must land first — task numbers +or external preconditions — so sequencing lives in the header instead of +prose asides: + +```markdown +**Depends on:** 03, 05 +``` + +The board does not enforce it; it informs whoever picks the next card. + The rest of the file is freeform — description, research findings, approach, open questions, whatever is relevant to the current stage. diff --git a/tasks/task-template.md b/tasks/task-template.md index 4fb41c7..fe3f865 100644 --- a/tasks/task-template.md +++ b/tasks/task-template.md @@ -3,6 +3,15 @@ Copy this file into backlog/ as NN-short-kebab-title.md — numbers are allocated in creation order and stay with the task for life. The board never lists this template (it only reads the stage directories). The **PR:** line is added by the board itself when the task reaches review/ with a branch. + +Anything below marked *optional* is deletable, and deleting beats leaving +it hollow — an empty boilerplate section reads as thinking that never +happened. Board process (review, PR, CI, merge) and the project-wide +definition of done stay off the card: the board does the former +mechanically and the repo CLAUDE.md owns the latter. + +This template lives in tasks/, which updates never touch — improvements to +it ship only with fresh installs, so local edits are yours to keep. --> # NN — Imperative title: what changes when this is done @@ -10,6 +19,8 @@ is added by the board itself when the task reaches review/ with a branch. **Status:** Backlog **Priority:** Medium — one clause on why it sits at this level **Type:** Feature +**Depends on:** 03, 05 — task numbers or external preconditions that must +land first, for whoever sequences the board; delete when nothing blocks this One paragraph for someone — human or agent — who has the codebase but not the conversation: what this task changes, and why it is worth doing. @@ -18,7 +29,12 @@ the conversation: what this task changes, and why it is worth doing. What exists today and why it falls short. Point at real places rather than describing from memory: packages and modules (`packages/domain/...`), prior -tasks (`../done/...`), reference documents (`../../reference/...`). +tasks (`../done/...`), plan files (`../../plans/...`) and reference +documents (`../../reference/...`) — a link outlives a summary. + +**Affected areas:** the modules or layers this touches, one line in the +repo CLAUDE.md's module-map vocabulary — telling reviewers where to look +and agents where to stop. Optional: delete when the title already says it. ## What to build @@ -29,13 +45,24 @@ convenience. - First piece - Second piece +**Out of scope** — the adjacent changes this task deliberately does not +make. This is what bounds the work agent's brief; scope creep is the +classic headless failure. Optional, but cheap insurance on any task with +tempting neighbours — delete rather than leave empty. + +- Not this, even though it is nearby + ## Acceptance -Observable outcomes, not implementation steps. The repo's definition of done -(tests pass, `lint-imports` clean, new behaviour covered) applies on top. +Observable outcomes, not implementation steps — review agents judge the +diff against exactly this list. The repo's definition of done (tests pass, +`lint-imports` clean, new behaviour covered) applies on top; don't restate +it. Given/When/Then phrasing is welcome where it sharpens a criterion, and +edge cases belong here too — boundaries, empty inputs, failure paths. - [ ] Something a reviewer can check without reading the diff -- [ ] Another one +- [ ] Given , when , then +- [ ] Edge case: the boundary that would embarrass this feature if missed ## Open questions @@ -51,3 +78,10 @@ it) when the task is ready to action. Freeform: research findings, links, decisions taken along the way. The board's relevance checks and PR reviews append their reports below this line as the task moves. + +**Risks** — known hazards and blockers: what could sink the approach, what +the change might break, what must hold for it to work. For the author +weighing the task and the reviewer double-checking it. Optional: delete +when there is nothing real to name. + +- None worth naming yet. diff --git a/tests/test_task_template.py b/tests/test_task_template.py new file mode 100644 index 0000000..0220625 --- /dev/null +++ b/tests/test_task_template.py @@ -0,0 +1,85 @@ +"""The task template's load-bearing contract. Its headings are machinery, +not prose convention: the body becomes the work agent's brief verbatim, +Acceptance is what review agents judge against, and the Open questions +heading is what the NOT READY gate keys off — so the template must keep +them, exactly, in order. Task 07 enriched the template; these tests pin +both what it gained and what it must never grow. + + python3 -m unittest discover -s tests -v +""" + +from __future__ import annotations + +import re +import unittest +from pathlib import Path + +TEMPLATE = (Path(__file__).resolve().parents[1] + / "tasks" / "task-template.md") + +LOAD_BEARING_HEADINGS = [ + "Context", "What to build", "Acceptance", "Open questions", "Notes", +] + +# Organisation process the board either does mechanically or doesn't own — +# deliberately absent from the template, and it must not grow back. +ORG_PROCESS_TERMS = ( + "staging", "sign-off", "signoff", "qa ", "product owner", + "browser", "device matrix", "contact", +) + +# The slots task 07 added, each of which earns its keep on the card. +ENRICHED_SLOTS = ( + "**Depends on:**", + "**Affected areas:**", + "**Out of scope**", + "**Risks**", + "Given <", + "Edge case", +) + + +class TemplateContract(unittest.TestCase): + def setUp(self): + self.text = TEMPLATE.read_text() + + def test_load_bearing_headings_survive_in_order(self): + headings = re.findall(r"^## (.+)$", self.text, re.MULTILINE) + self.assertEqual(headings, LOAD_BEARING_HEADINGS) + + def test_status_is_an_exact_board_value(self): + self.assertIn("**Status:** Backlog\n", self.text) + + def test_open_questions_section_names_the_gate(self): + section = self.text.split("## Open questions", 1)[1] + section = section.split("## ", 1)[0] + self.assertIn("`NOT READY`", section) + + def test_enriched_slots_are_present(self): + for slot in ENRICHED_SLOTS: + with self.subTest(slot=slot): + self.assertIn(slot, self.text) + + def test_new_slots_say_they_are_deletable(self): + # "delete" appears with each optional slot, so authors trim rather + # than leave hollow sections behind. + for slot in ("**Depends on:**", "**Affected areas:**", + "**Out of scope**", "**Risks**"): + with self.subTest(slot=slot): + paragraph = self.text.split(slot, 1)[1].split("\n\n", 1)[0] + self.assertIn("delete", paragraph.lower()) + + def test_no_organisation_process_creeps_in(self): + lowered = self.text.lower() + for term in ORG_PROCESS_TERMS: + with self.subTest(term=term): + self.assertNotIn(term, lowered) + + def test_template_sits_outside_every_stage_directory(self): + # The board lists stage directories only; the template must stay a + # sibling of them, not a card. + self.assertEqual(TEMPLATE.parent.name, "tasks") + + +if __name__ == "__main__": + unittest.main()