|
| 1 | +"""Every command that scans checkbox markers must say it skips code fences. |
| 2 | +
|
| 3 | +A checklist is free to *document* the checkbox format inside a fenced block. Counting |
| 4 | +those example markers reports items nobody can tick, and `/speckit-implement` treats a |
| 5 | +non-zero unchecked count as a reason to stop — so an example fence blocks implementation |
| 6 | +(#4272). `/speckit-clarify` already scoped its scan to markers outside code fences; this |
| 7 | +keeps the two commands from drifting apart again, and holds any future command that |
| 8 | +starts counting markers to the same rule. |
| 9 | +""" |
| 10 | + |
| 11 | +from __future__ import annotations |
| 12 | + |
| 13 | +import re |
| 14 | +from pathlib import Path |
| 15 | + |
| 16 | +import pytest |
| 17 | + |
| 18 | +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent |
| 19 | +COMMAND_DIRS = [ |
| 20 | + PROJECT_ROOT / "templates" / "commands", |
| 21 | + *sorted((PROJECT_ROOT / "presets").glob("*/commands")), |
| 22 | +] |
| 23 | + |
| 24 | +# The instruction that tells the agent which lines are checkbox markers. Written to catch |
| 25 | +# the phrasing both commands use rather than one exact sentence. |
| 26 | +SCAN_INSTRUCTION = re.compile(r"lines matching\s+`- \[ \]`", re.IGNORECASE) |
| 27 | +FENCE_EXCLUSION = re.compile(r"outside\s+(?:of\s+)?code\s+fences", re.IGNORECASE) |
| 28 | + |
| 29 | + |
| 30 | +def scan_instructions() -> list[tuple[Path, int, str]]: |
| 31 | + """Every line in a command template that defines what counts as a checkbox marker.""" |
| 32 | + found: list[tuple[Path, int, str]] = [] |
| 33 | + for directory in COMMAND_DIRS: |
| 34 | + if not directory.is_dir(): |
| 35 | + continue |
| 36 | + for path in sorted(directory.glob("*.md")): |
| 37 | + for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): |
| 38 | + if SCAN_INSTRUCTION.search(line): |
| 39 | + found.append((path, number, line)) |
| 40 | + return found |
| 41 | + |
| 42 | + |
| 43 | +def test_the_contract_is_actually_stated_somewhere() -> None: |
| 44 | + """Guard against the regex silently matching nothing and the test passing vacuously.""" |
| 45 | + assert scan_instructions(), "no command template defines a checkbox-marker scan any more" |
| 46 | + |
| 47 | + |
| 48 | +@pytest.mark.parametrize( |
| 49 | + ("path", "number", "line"), |
| 50 | + scan_instructions(), |
| 51 | + ids=lambda value: value.name if isinstance(value, Path) else str(value), |
| 52 | +) |
| 53 | +def test_marker_scans_exclude_code_fences(path: Path, number: int, line: str) -> None: |
| 54 | + assert FENCE_EXCLUSION.search(line), ( |
| 55 | + f"{path.relative_to(PROJECT_ROOT)}:{number} tells the agent to match checkbox " |
| 56 | + f"markers without excluding fenced code blocks, so an example fence is counted " |
| 57 | + f"as real work:\n {line.strip()}" |
| 58 | + ) |
0 commit comments