ADR-048: The scaffold is planned from configuration, not copied from a tree
Status Active · Filed 2026-08-11 · Influenced by ADR-036 · ADR-047
Context
ADR-036 made luria new’s kinds derive from the config, and
ADR-047 made the config able to say “this record has no
decision scheme”. luria init still copied a fixed template tree: the four
default layers, whatever the config said. The wart showed at both ends. A
project that already had a luria.toml declaring a custom shape got the
template’s directories scaffolded beside it — record/decisions.d/ for a
record whose config declared no decisions. And there was no way to hand init
the config you actually wanted; adoption meant scaffolding the default shape
and then renaming, moving, and deleting your way to the record you meant.
The question that prompted this — can init not take a config file as
input? — is the feature named plainly.
Decision
init resolves a config, in priority order: --config PATH, then a
luria.toml already at the project root, then the shipped template’s. The
plan is a pure function of that config:
- per scheme, its directory, a
_template.md, and aREADME.stub; - per journal and per fragment directory, its directory and template;
- a docs
README.mdwhose view list is generated from the config, so the index and the record cannot disagree about what exists on day one; - the agent file and CI workflows from the template, unchanged.
Richness follows doctrine, not mechanism. The ADR scheme and a
document-rendered DP scheme get the shipped templates — the decision
doctrine and the seed principles are the content this package genuinely has
to offer a record. Every other scheme gets a neutral template and a stub
titled after itself, because Luria has no opinion about what an RFC should
say. The neutral template uses the {PREFIX}-NNN placeholder that
luria new already substitutes (ADR-036) — one contract, both
directions.
Never-overwrite stands: existing files are skipped and reported, re-running
fills only holes. The one hard refusal: --config against a project that
already has a luria.toml is an error, not a skip. A skip would scaffold
the new config’s directories while the old config governs every other
command — a record whose own machinery doesn’t know about half its tree.
config.load gained a text= parameter so the plan (and --dry-run) can
be computed from a config that is not on disk and, under dry-run, never
will be.
Alternatives considered
- Keep the fixed-tree copy. The status quo, and the wart: init ignored the one file that says what this record is. Every custom adoption started with an apology tour of renames and deletions.
- Flags instead of a file —
luria init --scheme RFC --journal incidents. Re-states in a second vocabulary whatluria.tomlalready says, and the flags drift from the file the moment the config grows a key the flags don’t cover. The config is the interface (ADR-006). - Rich per-scheme templates. Generating decision-grade doctrine for an
arbitrary prefix means inventing opinions Luria doesn’t hold. The neutral
template is honest: frontmatter the lint demands, a placeholder
luria newfills, and nothing pretending to be doctrine. - Generate the docs README prose too. The view list is derived — it
must match the config — but the surrounding prose stays authored in the
template file, where it is browsable and editable, with a
{views}placeholder marking the one derived region.
Consequences
Config-first adoption works end to end, and the loop is pinned by tests:
declare RFCs and an incident log, luria init --config, luria new rfc,
luria index, luria lint — clean, with no decision directory anywhere.
Automating “the three documented commands, run as documented” as a test
immediately caught two template defects — a bare LU-ADR-048 and a bare
DP-1 that scheme-driven reference detection (ADR-046) could
now see — meaning a fresh scaffold would have started red. The guard that
found them (test_a_fresh_default_init_lints_clean) stays as the pin for
that whole class: the adoption guide is a script, and CI now runs it.
What this obliges: the template’s docs/README.md carries a {views}
placeholder, so the template is no longer a literally-copyable tree — it is
input to a planner. luria init remains the only consumer, and the
never-overwrite loop is unchanged.