From 9ac544462873bb4ebef36d52b263720c359d9ea7 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sat, 25 Jul 2026 17:56:09 +0200 Subject: [PATCH] feat(skills): add no-defaults skill Forbids substantive defaults for parameters, settings and env lookups across languages; only false, null and "" are free. A missing value must fail loudly at the earliest point instead of silently becoming a plausible wrong answer. Co-Authored-By: Claude Opus 5 (1M context) --- skills/no-defaults/SKILL.md | 108 ++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 skills/no-defaults/SKILL.md diff --git a/skills/no-defaults/SKILL.md b/skills/no-defaults/SKILL.md new file mode 100644 index 0000000..aa57df3 --- /dev/null +++ b/skills/no-defaults/SKILL.md @@ -0,0 +1,108 @@ +--- +name: no-defaults +description: > + Forbid default values for parameters, settings, and environment lookups in + every language. Load and apply AUTOMATICALLY on every task that writes, + edits, generates, refactors, or reviews code, config, templates, schemas, + or infrastructure — never wait to be asked. A default silently substitutes + a guess for a value the caller failed to supply, so a misconfiguration + becomes wrong behaviour instead of a loud failure. Demand strict, explicit + values and fail loudly when one is missing. `false`, `null` and `""` are the + only free defaults; every other default needs a written justification. + Portable across projects. +--- + +A default value is a guess, written at the point where the caller's intent is +least known, and it turns a missing value into a plausible wrong answer instead +of an error. Two callers then read the same call differently: one means "use the +default", the other simply forgot. That ambiguity is the defect this rule exists +to prevent. + +Never write a substantive default. Require the value. When it is absent, fail +immediately, loudly, and with a message that names the missing thing. + +## The rule + +1. Every parameter, option, setting, environment lookup, column, and template + variable MUST be supplied explicitly by its caller. +2. A missing value MUST abort at the earliest possible moment — argument + parsing, config load, container start — never at the point of use, and never + by proceeding with a substitute. +3. The error MUST name the missing key and where to set it. +4. Optionality, when a value genuinely may be absent, MUST be modelled as an + explicit named case that the call site handles, never as a silent + substitution. `None`/`null` handled in a visible branch is fine; a fallback + baked into the accessor is not. + +## The empty defaults are free + +`false`, `null` (`None`, `nil`, `undefined`), and the empty string `""` MAY be +used as defaults without justification. They are the one class that carries no +guess: they denote absence itself rather than inventing a value the caller might +have meant. `${VAR:-}`, `d.get(k)`, `a ?? null`, and `def f(a=None)` are fine. + +Every other default value — a number, a non-empty string, a non-empty list or +map, an enum member, a path, a timeout, a retry count — MUST carry an inline +comment stating why that specific value is correct for every caller that omits +it. No comment, no default. Use whatever exception-comment form the project +sanctions; where none exists, a plain comment on or directly above the default. + +The comment is the point: it forces you to name the assumption, so a reviewer +can disagree with it. "Sensible", "standard", and "usual" name nothing — if the +justification does not survive being written down, the default does not survive +either. + +## Banned constructs, by language + +Each `Never` below means the default is a substantive value. The same construct +with `false`, `null`, or `""` is permitted, and with any other value is permitted +only when justified in a comment. + +| Language | Never | Instead | +|---|---|---| +| Bash | `${VAR:-x}`, `${VAR:=x}`, `: "${VAR:=x}"` | `: "${VAR:?set VAR in }"` | +| Python | `def f(a=1)`, `d.get(k, x)`, `getattr(o, n, x)`, `os.environ.get(k, x)`, `argparse(default=)` | required args, `d[k]`, `os.environ[k]`, `add_argument(required=True)` | +| Jinja / Ansible | `\| default(x)`, `lookup('env', k) \| default('')` | `\| mandatory`, `assert` on the var | +| JS / TS | `function f(a = 1)`, `const {a = 1} = o`, `a \|\| x`, `a ?? x` | required params, explicit `if (a === undefined) throw` | +| Go | relying on zero values | constructor that validates every field | +| Rust | `unwrap_or`, `unwrap_or_default`, `#[serde(default)]` | `ok_or(Error::MissingX)?`, `#[serde(deny_unknown_fields)]` | +| Java / C# | optional parameters, `??`, `getOrDefault` | required constructor args, explicit null check that throws | +| SQL | `DEFAULT` on a column | `NOT NULL` and supply it in every insert | +| Make | `VAR ?= x` | `VAR := $(or $(VAR),$(error VAR is required))` | +| Docker / Compose | `${VAR:-x}`, `ENV` placeholders standing in for config | `${VAR:?VAR is required}` | +| Terraform / Helm | `variable { default = }`, `default` in `values.yaml` | no default; fail on the missing input | + +## How to apply + +Before writing any parameter, ask: what happens if the caller omits it? If the +answer is anything other than "it fails and says so", rewrite it. + +While editing, treat every `default`, `?:`, `??`, `||`, `:-`, `:=`, `or`, +`fallback`, and `getOrElse` in a value-resolution position as a finding unless +its substitute is `false`, `null` or `""`, or a comment already justifies it. +Grep your own diff for them before presenting it. + +Pushing a default one layer up is not a fix: a caller that passes a constant it +invented has only moved the guess. Follow the value to its true owner — +the operator, the inventory, the deployment config — and require it there. + +Removing an existing default is a behaviour change. Do not do it silently as a +drive-by: name it, and let the operator decide whether it lands in this change +or its own. + +## What does not count as a justification + +"It is conventional", "every other project does it", "the value is obvious", +and "it keeps the signature short" justify nothing — they assert that someone +else made the choice, which is the ambiguity this rule exists to remove. A valid +justification names why this value is right for a caller who says nothing. + +An operator asking for a specific default, in this conversation, for this value, +settles it without a comment. + +## Penalty + +If this audit finds an unjustified default you wrote yourself: say so in the +report, with file and count and no excuses; lower the confidence numbers for the affected +bundle; and write the incident to persistent agent memory, recording the date +and the construct you reached for, so the calibration survives the session.