Files
bench/tests/test_task_template.py
T
istosandClaude Fable 5 9d2dbb017d Enrich the task template for new cards (task 07)
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 <noreply@anthropic.com>
2026-07-29 22:37:36 +02:00

86 lines
2.9 KiB
Python

"""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()