"""Every plan doc declares a status, so the queue is answerable. Before this, `plans/` held 58 documents and exactly one said whether it was open. The rest mixed finished work, reviews of shipped work, parked specs and genuinely pending ones, with nothing distinguishing them -- so "how many plans are in the queue" had no answer short of reading all 58. The point of the marker is that `grep -H '^Status:' plans/*.md` is the answer. This test keeps a new plan from arriving without one. """ from __future__ import annotations import re from pathlib import Path import pytest PLANS = sorted((Path(__file__).resolve().parent.parent / "plans").glob("*.md")) # Deliberately small. A larger vocabulary invites "mostly done" and # "blocked-ish", which is how the directory got unreadable in the first place. VALID = {"done", "planned", "in progress", "parked", "reference"} STATUS_RE = re.compile(r"^Status: (?P[a-z ]+?)(?: -- (?P.+))?$", re.M) def test_there_are_plans_to_check(): assert len(PLANS) > 20, "plans/ did not resolve; the glob is probably wrong" @pytest.mark.parametrize("path", PLANS, ids=lambda p: p.name) def test_each_plan_declares_a_valid_status(path): text = path.read_text(encoding="utf-8") match = STATUS_RE.search(text) assert match, ( f"{path.name} has no 'Status:' line. Add one of {sorted(VALID)} " f"below the title, ideally with ' -- '." ) status = match.group("status").strip() assert status in VALID, f"{path.name}: unknown status {status!r}, want one of {sorted(VALID)}" @pytest.mark.parametrize("path", PLANS, ids=lambda p: p.name) def test_the_status_is_near_the_top(path): """A marker buried on line 200 is not a marker anyone reads.""" head = "\n".join(path.read_text(encoding="utf-8").split("\n")[:8]) assert STATUS_RE.search(head), f"{path.name}: Status line is not in the first 8 lines" @pytest.mark.parametrize("path", PLANS, ids=lambda p: p.name) def test_open_plans_say_why_they_are_open(path): """`done` can stand alone; anything still live needs a reason. "planned" with no reason is the state that rots -- nobody can tell later whether it is waiting on a decision, a dependency, or just nobody's turn. """ match = STATUS_RE.search(path.read_text(encoding="utf-8")) status = match.group("status").strip() if status in {"planned", "in progress", "parked"}: assert match.group("reason"), ( f"{path.name}: status {status!r} needs ' -- ' so the next " f"reader knows what it is waiting on" )