ADR 0001: Cookiecutter templates, updated with cruft, sharing one base¶
Context¶
Every research project needs several repositories (code, workspace, paper, and sometimes an application), and they should start from the same standards. A standard also changes over time, so a project created last year should be able to take this year's change.
Three tools were compared on 2026-09-30, from their installed versions and documentation:
- copier 9.18.2 updates projects itself, but its documentation recommends "1 template = 1 Git repository", because "Git tags are shared across the whole Git repository".
- cookiecutter 2.7.1 supports several templates in one repository: its
--directoryoption is "for advanced repositories with multi templates in it", and a top-levelcookiecutter.jsonmay list them undertemplates. It cannot update a project after creating it. - cruft 2.16.0 adds
cruft updateandcruft checkto cookiecutter. It records the template's subdirectory in.cruft.jsonand uses it on update.
Decision¶
- cookiecutter for the templates, cruft to create and update projects. All templates live in this one repository.
- One shared base, reached by an include, never copied. Cookiecutter
loads Jinja includes from
['.', '../templates']relative to the template folder (cookiecutter/generate.py). Each template'stemplates/is a link to_shared/, and a shared file is one line in the template:{% include "base/<file>" -%}.
A spike on 2026-10-01 showed both halves:
- the include resolves when cookiecutter clones the repository from git;
cruft updatecarries a change in_shared/into a project generated before the change.
tests/functional/test_cruft_update.py now repeats that round trip on every
CI run.
Consequences¶
- cruft is a risk. It has had no release or commit since 2024-12-25. If it breaks, projects can still be generated with plain cookiecutter. A changed standard then reaches existing projects by hand.
- One version line for every template. A release tag versions the whole set, which is also the version of the standards they encode.
- Releases are reproducible references. Releases follow Semantic Versioning and Keep a Changelog 1.1.0. A change that generated projects must act on is major, a new template answer or component is minor, and a compatible fix is patch. A released tag is never moved: projects update to that tag, so changing it would give the same requested version different template contents.
- Generation hooks run without asking. Cookiecutter runs hooks by default. Hooks here therefore only delete what a project did not choose, touch no network, and touch nothing outside the generated project.
- Includes are whole files. A template that needs extra lines writes them
after the include, as
paper/.gitignoredoes.
Alternatives not taken¶
- copier with one template asking which kind to build. This was workable, but it was set aside once the user preferred cookiecutter's documented support for several templates in one repository.
- A script that assembles each template from
_shared/, with a test that the committed result is current. This is the fallback if links ever stop working. - A generation hook that copies
_shared/. Rejected because it is the least transparent of the three.