Template Overlay Model

Templates are organized as layered overlays that compose into the final project tree. Each layer can replace files from earlier layers, which is how shared baselines, language conventions, project-type opinions, and capability toggles compose without conditional logic in YAML.

Overlay order

1. shared                  templates/shared/
2. <language>              templates/<language>/
3. <project-type>          templates/<id>/
4. capability templates    templates/capabilities/<set>/  (per enabled capability, in resolution order)

The first three come from the manifest’s templates array. The last comes from capability_templates keyed by capability id.

Files written in later layers overwrite files written in earlier layers, which is intentional: a project-type template can replace a shared file with a more specific version, and a capability template can do the same.

File handling

  • Files ending in .erb are rendered as ERB and the suffix is stripped.
  • Path segments matching __<var>__ are interpolated from the context’s variables. For example, lib/__name__.rb.erb becomes lib/<name>.rb.
  • Executable bits on source templates are preserved on the destination.

Template scope

ERB templates have access to a small, intentional API:

<%= name %>                # the project name
<%= var("ruby_version") %> # a manifest variable, with optional default
<%= var("author", "TODO") %>
<% if cap?("cli") %>...    # capability predicate
<%= manifest.id %>         # the manifest object
<%= capabilities.join(",") %>

Templates should not reach for arbitrary state. If a template needs a value, declare it as a manifest variable.

Mise

mise.toml is generated by the MiseWriter from manifest.mise, not from a template file. The output is project-local. The mise capability acts as an enable/disable toggle: when supported and disabled, no mise.toml is written.

Capability template directory naming

When the same capability needs different overlays per language (e.g. coverage uses SimpleCov for Ruby and Vitest config for TypeScript), each manifest maps the capability to a language-specific set:

# manifests/ruby-gem.yml
capability_templates:
  coverage:
    - coverage-ruby

# manifests/ts-cli.yml
capability_templates:
  coverage:
    - coverage-ts

This keeps the template directory tree flat and explicit instead of encoding language detection in YAML.


project-kit · MIT License · GitHub

This site uses Just the Docs, a documentation theme for Jekyll.