Skip to content

ADR 0007: An interactive generator that configures, not templates

Status: accepted

Context

ADR 0004 rejected cookiecutter because "the template itself is dead code: it cannot be browsed comfortably, cannot run, and cannot be CI-tested as-is, and template bugs are only found by generating." That reasoning still holds. But the same ADR left a door open in its own consequences: "A cookiecutter wrapper could be generated mechanically later if a downstream community wants one; nothing in this decision blocks it."

What it left behind was an onboarding path made of manual steps. Click Use this template, clone, copy .env.example, run make setup, then work through seven steps in the bring-your-own-domain tutorial and hand-edit domain/domain.yaml. The only automation was scripts/init_domain.py, which set a name and a description. Everything that actually makes a project yours stayed manual: the ontology, the credential mode, the corpus source, the parser and reranker choices, the environment manager.

Cookiecutter Data Science, the nearest comparable tool, answers this with a short questionnaire and no directory to create first. The gap was never the idea of a wizard. It was that the obvious way to build one drags back in exactly what ADR 0004 refused.

Decision

Ship an interactive generator that is a post-fetch applier rather than a template renderer.

sci-rag-new downloads this repository at a pinned tag, then rewrites its configuration files in place. sci-rag init does the same inside a checkout you already have. Both drive the same run_wizard().

Four properties keep this inside ADR 0004 rather than around it:

  • No placeholders. There is no {{ }} syntax and no {{ }} directory anywhere in the repository. A test asserts that a generated project contains no bare {{ in any Markdown, TOML, YAML, or JSONL file.
  • The template stays the runnable application. What the generator fetches is this repository, byte for byte: browsable, runnable, and CI-tested as itself. Nothing here becomes real code only after generation.
  • The wizard is ordinary tested code. It lives in src/sci_rag/scaffold/, the unit suite covers it like anything else, and .github/workflows/generated-projects.yml exercises the result once per environment manager. The test suite finds template bugs; you do not have to generate a project and look.
  • The package name still does not change. Derived projects keep the sci_rag import path, for the reason ADR 0004 gave: it preserves the ability to diff against, and pull improvements from, upstream.

The appliers round-trip their output through the same models the application reads. domain/domain.yaml comes from serializing a DomainConfig, so a generated profile cannot be something load_domain() rejects, and an LLM-drafted ontology goes through that same validation before anything reaches disk.

One RunnerProfile per environment manager renders uv, pixi, conda, and venv+pip. The kit is manager-wired in five places (task commands, CI, container, dev container, docs), and any disagreement between them breaks a generated project on its first run.

Consequences

  • Onboarding is pipx install sci-rag-kit, then sci-rag-new. No repository to clone first, no file to hand-edit before the first run.
  • The generator ships in the same distribution as the application, so installing it also installs sci-rag. That install is heavy for what is mostly a generator: fastapi, uvicorn, asyncpg, sqlalchemy, alembic, pgvector, google-genai, mcp, and pypdf all come along. Accepted knowingly. The same install is the tool the user wants next, pipx isolates it, and the scaffold package lazy-imports the runtime, so startup stays fast even though installation is not.
  • sci-rag-new fetches the tag matching its own installed version. A given generator release always produces the same project, and upgrading the generator is the only way to change what a new project contains.
  • The generator is now a compatibility surface. CI and the documentation both read its question names and answers-file format, so changing them follows the promises in VERSIONING.md.
  • The homepage session is generated from the question list rather than recorded by hand, so it cannot quietly drift from what the wizard asks.
  • ADR 0004 is not superseded. This implements the escape hatch it described, under the constraints it set.

Reversal conditions

Revisit if any of these stop being true:

  • A generated project stays diffable against upstream. If it stops being so, the generator has started producing structure rather than configuration.
  • A person will sit through the question set. Past that point the answers file is the primary interface and the interactive path is the fallback, which is a different tool.
  • No placeholder syntax appears anywhere in the tree. That is the specific failure ADR 0004 refused, and the reason the no-{{ test exists.