CLI
Check and generate: check, env, explain, model draft
These commands read only your definitions, your files and your Product Model. They need no database, so they run in CI, in a pre-commit hook and in an agent's sandbox.
check
npx tunables check
Checks, without a database, everything that must hold:
- The definitions are sound: no id defined twice, every unit and effect
declared, every
picknaming a catalog, every preset set with a slot, and every group and preset default passing its own check. .env.examplelists every deployment value as declared, when the config has anenvironment.- The explanations module is up to date with the Product Model, when the
config names
explanations. - Every field agrees with the Product Model, when there is one: the Entity a group names exists, the fact each field names is one that Entity keeps, and a default the model states is the field's own.
✓ 2 groups, 1 preset sets and 1 catalogs are defined soundly
✓ env.example lists all 3 deployment values
✓ explanations.ts matches the Product Model
✗ model: limits.maxProjects defaults to 5, but Limits says "Projects" defaults to 3.
1 problem.
It exits 1 when anything fails. --json writes
{ config, passed, findings } instead, for tools and agents.
env
npx tunables env
npx tunables env --check
Writes .env.example from your deployment values:
each under its section and description, commented out at its default, or left
empty when it must be set. It rewrites the whole file, so a variable you have
not declared is dropped. --check only says whether the file is up to date,
and exits 1 when it is not.
explain
npx tunables explain
npx tunables explain --check
Writes the explanations module from your Product Model, so each field explains itself in the model's own words. Name where it goes in the config:
export default defineConfig({
groups,
productModel: 'product-model',
explanations: 'server/tunables-explanations.ts'
})
Then pass it to createTunables, and give fields a fact instead of help:
import { explanations } from './tunables-explanations'
const limits = defineGroup({
id: 'limits',
title: 'Limits',
entity: 'limits',
fields: {
maxProjects: number({ fact: 'Projects', unit: 'count', default: 3 })
}
})
createTunables({ store, groups: [limits], explanations })
--check only says whether the module is current. Never edit the module by
hand: change the model, then run explain again.
How the model is read
A pdd-kit Product Model keeps each Entity in
product-model/entities/<id>.md. A group's entity is that id; each line under
the Entity's ## Information kept is a fact:
# Limits
## Information kept
- **Projects** — the most projects a workspace may have open at once; defaults to 3
- **Attachment size** — the largest file a task may carry; defaults to 10 megabytes
The bold name is the fact a field names. The words after the dash are its
explanation; what follows defaults to is the default the model states, and
check compares it with the field's own default as it reads, by its numbers:
10 megabytes agrees with 10 MB, and 400 by 225 pixels with
400 × 225 pixels. Without numbers, the words must match, such as
List.
model draft
npx tunables model draft
npx tunables model draft --out /tmp/settings-facts.md
Drafts the facts the Product Model lacks: for each group, preset set and
catalog, one line in the model's own form for every field that names no fact
the model keeps, with its explanation and, for a group, its default. It prints
the draft, or writes it to --out.
It never writes the model. A Product Model decides what the product keeps, so
bring the draft to /pdd-ideate, which decides each fact with you and writes
it. Then run tunables explain.