Schema and finding codes

Everything spotregen knows about a repository comes from three kinds of TOML files below regen/. This page describes them and every finding the static check can report.

The three file kinds

Definition 1 (Architecture) regen/architecture.toml is the compilation target: the shape every regenerated module must fit into. There is exactly one.

Definition 2 (Unit) regen/units/<unit>.toml describes one regenerable module: where it lives, what it promises (intent), which tests judge it (evaluations) and who depends on it (consumers). The file stem is conventionally the module name without the package.

Definition 3 (Decision) regen/decisions/<id>.toml is a provenance record: the driver, the decision, the rejected alternatives, the evidence, the assumptions and a review date. The file stem is the decision id.

architecture.toml

Table 1: Keys of regen/architecture.toml.
key required meaning
package yes top-level package name
source_dir yes directory (relative to the root) that holds the package, for example src
[allowed] yes per subpackage (or top-level module), the subpackages it may import; "__init__" is the package’s __init__.py
[pace] no per pace, the verification a change at that pace needs
known_cycles no accepted import cycles, each a pair of subpackages
scan_dirs no directories whose Python files count as consumers; default ["src", "tests", "scripts"]
doc_globs no glob patterns of documents whose text counts as a consumer
conventions, open_choices no rules every implementation follows; choices left to the generator
decisions no ids of decisions behind the architecture itself
kb_dir no directory of knowledge-base entries for KB: evidence; REGEN_KB_DIR overrides it

units/<unit>.toml

Table 2: Keys of a unit file. *A unit without an intent item fails the check (UNIT06).
key required meaning
unit yes dotted module name, for example samplepkg.core.calc
path yes repository-relative path of the implementation
summary yes one paragraph of purpose
pace, risk yes vocabularies below
public no public names the unit must define (its contract)
depends_on no modules of the package the unit may import
evaluations no repository-relative paths of the durable evaluation files
unit_tests no implementation-coupled tests, regenerated with the code
[[intent]] no* id, scope, invariant, reason (all required), evaluations, decisions
[[consumers]] no ref, kind (required), note

An intent item has the three parts of Fowler, Regenerative Software, ch. 3: the scope (where the requirement applies), the invariant (what must remain true) and the reason (why the invariant matters). The fields statement and kind of spotregen < 0.2.0 are rejected with exit code 2 and a hint.

decisions/<id>.toml

Required: title, date, driver, decision, approved_by. Optional: status (default active), origin (default human), invariant, evidence, assumes, review_after, and [[alternatives]] with option and rejected_because. Evidence entries start with file: (must exist in the repository), commit: (must be a commit of it), KB: (an entry of the knowledge-base directory, checked only if that directory exists) or url: (never fetched).

Vocabularies

The values below are read from spotregen.spec, so this page cannot drift from the loader. A value outside its vocabulary stops the tool with exit code 2.

from spotregen import spec

for name, values in [
    ("pace", spec.PACES),
    ("risk", spec.RISKS),
    ("consumer kind", spec.CONSUMER_KINDS),
    ("decision status", spec.DECISION_STATUSES),
    ("decision origin", spec.ORIGINS),
]:
    print(f"{name:16s}", ", ".join(values))
pace             fast, medium, slow
risk             low, medium, high
consumer kind    code, test, script, book, doc, external, operational, human
decision status  active, superseded, expired
decision origin  human, machine

Only the consumer kinds code, test, script, book and doc (files in this repository) are checked against the tree. external is a file in a downstream repository, written <repo>:<path>; operational and human are free text. They cannot be checked, so keep them current when a contract changes.

What counts as a consumer

A Python file under scan_dirs is a consumer of a unit when an import statement names the module, or imports one of its public names through a package that re-exports it. A document matched by doc_globs is a consumer when its text names the module, imports its leaf from the parent package or imports a re-exported public name. Attribute access through an imported package is not detected.

NoteFixed conventions

These are part of the schema and shared between repositories that use the tool:

  • An evaluation file must contain pytest.mark.regen and import only names listed in the unit’s public (EVAL02, EVAL03).
  • Files under tests/ are shown as kind test in the deletion report, everything else in Python as code.
  • The directories .venv, __pycache__, _site, _freeze and reference (the quartodoc output) are never scanned for consumers.
  • When kb_dir is unset and REGEN_KB_DIR is not exported, KB: evidence is not checked.

Finding codes

A finding is rendered as one line, LEVEL CODE where: message, the level padded to 7 and the code to 7 characters. Errors fail the check; warnings ask for attention and fail it only with --strict. The line format, the codes and their levels are a stability contract.

import ast
import re
from pathlib import Path

from IPython.display import Markdown

from spotregen import checks

MEANING = {
    "ARCH00": "a source file of the package does not parse",
    "ARCH01": "a subpackage imports a subpackage that `[allowed]` does not permit",
    "ARCH02": "a subpackage is missing from `[allowed]`",
    "ARCH03": "an allowed edge is unused, or a listed subpackage does not exist",
    "ARCH04": "a new import cycle, not listed in `known_cycles`",
    "ARCH05": "a cycle listed in `known_cycles` no longer exists",
    "UNIT01": "the unit's module does not exist or does not parse",
    "UNIT02": "`path` is not the file of the module the record names",
    "UNIT03": "a name in `public` is not defined in the module",
    "UNIT04": "the module imports a package module that is not in `depends_on`",
    "UNIT05": "an entry of `depends_on` is not imported",
    "UNIT06": "the record declares no intent item",
    "EVAL01": "an evaluation file or a named evaluation does not exist, or the file does not parse",
    "EVAL02": "an evaluation file lacks the pytest marker `regen`",
    "EVAL03": "an evaluation imports a name of the unit that is not public",
    "EVAL04": "an intent item names no evaluation",
    "EVAL05": "an evaluation is not linked to any intent item",
    "PROV01": "an intent item or the architecture cites an unknown decision",
    "PROV02": "evidence of a decision does not resolve (file, commit, KB entry or prefix)",
    "PROV03": "an active decision is past its `review_after` date",
    "PROV04": "an intent item rests on a decision that is not active",
    "PROV05": "no intent item or architecture entry cites the decision",
    "DEL01": "a file in the repository depends on the unit but is not a declared consumer",
    "DEL02": "a declared consumer in the repository does not exist",
    "DEL03": "a declared consumer no longer references the unit",
}
# Findings are built by Finding(...) and by local helpers (declared_at, at_item, at),
# all of which take the level and the code as their first two positional arguments.
found = {}
for node in ast.walk(ast.parse(Path(checks.__file__).read_text(encoding="utf-8"))):
    if (
        isinstance(node, ast.Call)
        and len(node.args) >= 2
        and all(isinstance(a, ast.Constant) and isinstance(a.value, str) for a in node.args[:2])
        and node.args[0].value in ("error", "warning")
        and re.fullmatch(r"[A-Z]{3,4}\d\d", node.args[1].value)
    ):
        found.setdefault(node.args[1].value, set()).add(node.args[0].value)
assert set(found) == set(MEANING), set(found) ^ set(MEANING)
assert all(len(levels) == 1 for levels in found.values())
rows = ["| code | level | meaning |", "|:--|:--|:--|"]
for code in sorted(MEANING):
    rows.append(f"| {code} | {next(iter(found[code]))} | {MEANING[code]} |")
Markdown("\n".join(rows))
Table 3: Every finding code, its level and meaning. The codes and levels are read from the source of spotregen.checks: a code that is added, removed or changes its level stops the render until this table is updated.
code level meaning
ARCH00 error a source file of the package does not parse
ARCH01 error a subpackage imports a subpackage that [allowed] does not permit
ARCH02 error a subpackage is missing from [allowed]
ARCH03 warning an allowed edge is unused, or a listed subpackage does not exist
ARCH04 error a new import cycle, not listed in known_cycles
ARCH05 warning a cycle listed in known_cycles no longer exists
DEL01 error a file in the repository depends on the unit but is not a declared consumer
DEL02 error a declared consumer in the repository does not exist
DEL03 warning a declared consumer no longer references the unit
EVAL01 error an evaluation file or a named evaluation does not exist, or the file does not parse
EVAL02 error an evaluation file lacks the pytest marker regen
EVAL03 error an evaluation imports a name of the unit that is not public
EVAL04 error an intent item names no evaluation
EVAL05 warning an evaluation is not linked to any intent item
PROV01 error an intent item or the architecture cites an unknown decision
PROV02 error evidence of a decision does not resolve (file, commit, KB entry or prefix)
PROV03 warning an active decision is past its review_after date
PROV04 warning an intent item rests on a decision that is not active
PROV05 warning no intent item or architecture entry cites the decision
UNIT01 error the unit’s module does not exist or does not parse
UNIT02 error path is not the file of the module the record names
UNIT03 error a name in public is not defined in the module
UNIT04 error the module imports a package module that is not in depends_on
UNIT05 warning an entry of depends_on is not imported
UNIT06 error the record declares no intent item

The prefixes group the checks: ARCH (compilation: allowed imports between subpackages, import cycles), UNIT (a unit record against its module), EVAL (the evaluation files and their links to intent items), PROV (decision records and the citations of them) and DEL (every dependent inside the repository is a declared consumer, and every declared consumer exists).

Exit codes and summary lines

Table 4: Exit codes. Exit 2 covers a malformed record (including invalid TOML), an unknown unit, a missing regen/architecture.toml and argparse errors.
command exit 0 exit 1 exit 2
check no error (with --strict: no finding) an error, or a warning with --strict spec or argument error
deletion-test the report says Verdict: regenerable any other verdict spec, unit or argument error
status, context, compact, measures, new-unit success spec, unit or argument error; with measures --mutation-score also evaluations that fail on the unchanged module
mutate every mutant killed a mutant survived spec, unit or argument error, no evaluation file, or evaluations that fail on the unchanged module
claude, local the last attempt’s evaluations pass they do not spec, unit or missing-tool error

The summary lines regen check: N units, E errors, W warnings and regen compact: N candidates keep their regen prefix. Messages on standard error start with spotregen:.

Run records

The drivers write a run.toml per run. Its field protocol names the procedure that produced the run: regeneration for spotregen claude, regeneration-local-sessions for spotregen local and canary for spotregen local --canary. Archived runs written before 0.7.0 carry the values blind-regeneration and blind-regeneration-local-sessions.

Stability contract

Pinned by the test suite and treated as MAJOR-relevant even in 0.x: the finding codes and levels, the line format and order, the summary lines, the exit codes, the Markdown of status, deletion-test and context, the TOML schema, the regen/ directory and REGEN_KB_DIR. Context bundles are byte-identical across releases, because archived runs record the SHA-256 of the bundle they were given (bundle_sha256). Additions to the schema are optional only.