Draft a unit

import subprocess
import sys
import tempfile
from pathlib import Path

from spotregen import make_sample_repo

tmp = tempfile.TemporaryDirectory()
root = make_sample_repo(Path(tmp.name) / "demo")


def spotregen(*args, drop=None):
    """Run `spotregen` in the sample repository; show the call and its output."""
    done = subprocess.run(
        [sys.executable, "-m", "spotregen", *args],
        cwd=root,
        capture_output=True,
        text=True,
        check=False,
    )
    print("$ spotregen", *args)
    lines = (done.stdout + done.stderr).splitlines()
    print("\n".join(x for x in lines if not (drop and drop in x)))
    print(f"[exit {done.returncode}]")

Definition 1 (The scaffold) spotregen new-unit MODULE --pace P --risk R drafts regen/units/<unit>.toml for a module that has no record yet. Everything the tree can tell is filled in: the path, the first docstring paragraph as summary, the public names (__all__, else public definitions and constants), the package imports as depends_on, and every file that references the module as a consumer. What only a person can supply is left open: the intent items, the evaluations, the decisions and the external, operational and human consumers.

First remove the record of the sample unit, so that the module is unrecorded:

(root / "regen" / "units" / "core.calc.toml").unlink()
spotregen("check")
$ spotregen check
WARNING PROV05  regen/decisions/why-add.toml: no intent item references this decision
regen check: 0 units, 0 errors, 1 warnings
[exit 0]

Example 1 (Look at the draft) --dry-run prints the draft instead of writing it. The header line carries today’s date, so it is left out here.

spotregen("new-unit", "core.calc", "--pace", "slow", "--risk", "high", "--dry-run", drop="Drafted by")
$ spotregen new-unit core.calc --pace slow --risk high --dry-run

# `spotregen check` fails with UNIT06 until the first [[intent]] item is written.

unit = "samplepkg.core.calc"
path = "src/samplepkg/core/calc.py"
summary = """
Calculator.
"""
pace = "slow"
risk = "high"
public = ["add"]
depends_on = []
evaluations = []  # e.g. tests/test_regen_eval_calc.py, marked pytest.mark.regen
unit_tests = []

# Intent: what must stay true for any implementation, and why. Ask "what would a
# competent stranger get wrong?" and turn every incident that touched the module into
# an item with an evaluation and a decision record.
#
# [[intent]]
# id = "CAL-1"
# scope = ""  # where the requirement applies
# invariant = ""  # what must remain true
# reason = ""  # why the invariant matters
# evaluations = []  # test functions in the evaluation files above
# decisions = []  # ids of regen/decisions/<id>.toml

[[consumers]]
ref = "src/samplepkg/app/main.py"
kind = "code"
note = ""

[[consumers]]
ref = "tests/test_regen_eval_calc.py"
kind = "test"
note = ""

[[consumers]]
ref = "docs/guide.qmd"
kind = "doc"
note = ""

# Add what the tree cannot show: downstream files as kind = "external"
# (ref = "<repo>:<path>"), scheduled runs as "operational", people as "human".
[exit 0]

Example 2 (Write it and see the check fail)  

spotregen("new-unit", "core.calc", "--pace", "slow", "--risk", "high")
spotregen("check")
$ spotregen new-unit core.calc --pace slow --risk high
wrote regen/units/core.calc.toml
ERROR   UNIT06  samplepkg.core.calc: declares no intent item (a unit without intent cannot be regenerated)
next: write the intent items and evaluations, then run spotregen check
[exit 0]
$ spotregen check
ERROR   UNIT06  samplepkg.core.calc: declares no intent item (a unit without intent cannot be regenerated)
WARNING PROV05  regen/decisions/why-add.toml: no intent item references this decision
regen check: 1 units, 1 errors, 1 warnings
[exit 1]

The draft deliberately fails the check (UNIT06) until the first intent item is written, so a scaffold never passes unreviewed.

Example 3 (Write the first intent item) Ask what a competent stranger would get wrong, state it with its reason, and name the evaluation that checks it and the decision record behind it. Here the sample already has an evaluation, test_add, and a decision, why-add.

unit = root / "regen" / "units" / "core.calc.toml"
text = unit.read_text()
text = text.replace("evaluations = []", 'evaluations = ["tests/test_regen_eval_calc.py"]', 1)
text += '''
[[intent]]
id = "CAL-1"
scope = "add"
invariant = "add(a, b) == a + b"
reason = "Callers rely on plain arithmetic."
evaluations = ["test_add"]
decisions = ["why-add"]
'''
unit.write_text(text)
spotregen("check")
$ spotregen check
regen check: 1 units, 0 errors, 0 warnings
[exit 0]
TipWhat the tool cannot see

Downstream repositories, scheduled runs and people depend on a unit too. Declare them as [[consumers]] of kind external (ref = "<repo>:<path>"), operational or human.

The unit is now regenerable; see the bundle and the drivers.