@isonimus/stele 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,8 +10,9 @@ rather than a durable decision, use `/slice` instead.
10
10
 
11
11
  Steps:
12
12
 
13
- 1. Compute the next free id: the highest ordinal in `adr/` plus one, zero-padded to four
14
- digits. Do not reuse or renumber.
13
+ 1. Compute the next free id: the highest ordinal across **both** `adr/` and `slices/` plus
14
+ one, zero-padded to four digits. ADRs and slices share one id space — rule 2 fails on a
15
+ duplicate id whichever directory it sits in. Do not reuse or renumber.
15
16
  2. Choose the `type`: `architecture` for a durable decision, `slice` for a feature unit,
16
17
  `batch` only if it genuinely bundles several unrelated decisions that cannot map 1:1.
17
18
  3. Write `adr/NNNN-<kebab-title>.md` opening with the stele:ADR-0002 frontmatter block:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Install the method kit (CLAUDE.md, LEDGER.md, linter, index, pre-commit hook) into a git repo
3
- argument-hint: [repo-root] [--check | --update]
3
+ argument-hint: [repo-root] [--check | --update [--force]]
4
4
  ---
5
5
 
6
6
  Install this kit into a repo, or verify an existing install (stele:ADR-0006). The script does
@@ -33,13 +33,19 @@ node scripts/init-method.mjs <target> --apply
33
33
 
34
34
  This scaffolds `adr/`, `CLAUDE.md` and `LEDGER.md` (never overwriting), vendors
35
35
  `lint-docs.mjs` / `build-index.mjs` / the hook and the slash commands, generates
36
- `adr/INDEX.md`, links `~/.claude/CLAUDE.md`, and installs the pre-commit hook **only if
37
- the corpus lints clean**.
36
+ `adr/INDEX.md`, and installs the pre-commit hook **only if the corpus lints clean**.
38
37
 
39
- The commands are vendored under softer rules than the machinery (stele:ADR-0007): a repo may
40
- edit its own copy of `/slice` or `/wrap-up` to say something repo-specific, and an
41
- install keeps that edit rather than overwriting it. If you edit one, say so — an edit
42
- made in an installed repo does not travel back to the toolkit.
38
+ Nothing outside the target repo is written never `$HOME`, never `~/.claude/CLAUDE.md`
39
+ (stele:ADR-0016). Wiring the operator's machine-level conventions is a personal choice, not
40
+ an install step; do not add it back as a convenience.
41
+
42
+ The commands and `docs/quality-bar.md` are vendored under softer rules than the machinery
43
+ (stele:ADR-0023): a repo may edit its own copy of `/slice`, `/wrap-up` or the bar to say
44
+ something repo-specific, and an install keeps that edit rather than overwriting it. If you
45
+ edit one, say so — an edit made in an installed repo does not travel back to the toolkit.
46
+
47
+ The quality bar is the standard the slice `## Definition of Done` is measured against
48
+ (stele:ADR-0024). Cite it from the scaffolded `CLAUDE.md`; do not restate its rules there.
43
49
 
44
50
  ## 3. If the repo already has a hook framework
45
51
 
@@ -92,14 +98,33 @@ node scripts/init-method.mjs <target> --check
92
98
  ```
93
99
 
94
100
  Writes nothing; fails on a missing or broken hook, a drifted vendored **script**, a stale
95
- index, a red corpus, or a broken `~/.claude/CLAUDE.md` link. Report its output verbatim
96
- rather than summarising it as "installed".
101
+ index, or a red corpus. Report its output verbatim rather than summarising it as
102
+ "installed".
97
103
 
98
104
  `LOCAL` and `MISSING` lines are about commands only and are **not** failures — they say
99
105
  this repo adapted or declined one. Read them, mention them, do not "fix" them without
100
106
  asking; that is somebody's deliberate edit.
101
107
 
102
- `--update` re-copies the vendored scripts, the hook and the commands for a command it
103
- discards a local edit, which is exactly what it is for. It is the only way a toolkit fix
104
- reaches an installed repo: the copies are deliberate (stele:ADR-0006, stele:ADR-0007), and drift is
105
- the price.
108
+ A `PROBLEM` on a command is different: it means that file is **behind the toolkit and
109
+ unmodified here**, so the repo is simply missing a fix. `--update` takes it.
110
+
111
+ ## 7. Updating an installed repo
112
+
113
+ ```
114
+ node scripts/init-method.mjs <target> --update --apply
115
+ ```
116
+
117
+ `--update` re-copies the vendored scripts and the hook unconditionally — a locally edited
118
+ linter is a defect, not an adaptation (stele:ADR-0006). Commands are gentler
119
+ (stele:ADR-0023): anything this repo has **not** touched takes the toolkit's version,
120
+ anything it **has** touched is kept and reported.
121
+
122
+ `.claude/.stele-vendored.json` is what makes that distinction possible — it records what
123
+ the toolkit last wrote, so a stale copy and a deliberate edit stop being the same
124
+ observation. **Commit it.** A repo without one (installed before the record existed) has
125
+ every differing command reported as unreconciled and overwrites none of them; reconcile
126
+ those by hand, once.
127
+
128
+ `--update --force` discards local adaptations. Never reach for it to make output tidy —
129
+ it is for when the operator has decided an adaptation should go. Say what will be lost
130
+ before running it.
@@ -12,7 +12,22 @@ get recorded until they have gone stale (stele:ADR-0003).
12
12
  generated index is drift by another name.
13
13
  - `npm test` — the test suite must pass; docs changes must not disturb code.
14
14
 
15
- 2. **Adversarial pass** (stele:ADR-0017) run it if this change touched a `CLAUDE.md` §4
15
+ 2. **Mutation check** — only if this repo has `scripts/check-mutants.mjs` *and* this change
16
+ touched a module its list covers. Otherwise skip it and say so; on most changes it has
17
+ nothing to look at.
18
+
19
+ - `npm run mutants`. Every non-exempt mutant must die.
20
+ - A **survivor is not a bug** — it is correct behaviour that no test is watching, so a
21
+ later refactor could reverse it in silence. Fix it by writing the missing regression
22
+ test, never by deleting the mutant.
23
+ - Mark a mutant `equivalent` only when the mutated code genuinely means the same thing,
24
+ and say why in the entry. That field is the one way a survivor passes, so it is also
25
+ the obvious place to bury an inconvenient gap.
26
+ - It measures whether tests *bite*, not whether code is right: it cannot see a missing
27
+ input or a rule that was wrong from the start. A green run is not a correctness claim,
28
+ and step 3 is what covers what it misses.
29
+
30
+ 3. **Adversarial pass** (stele:ADR-0017) — run it if this change touched a `CLAUDE.md` §4
16
31
  standing invariant, an exported/public API, a data format or anything persisted, or a
17
32
  Definition of Done scenario the slice flagged as risky. Otherwise skip it and say so.
18
33
 
@@ -20,6 +35,13 @@ get recorded until they have gone stale (stele:ADR-0003).
20
35
  edge cases, error paths, boundaries, ordering and partial-failure hazards; **cost** —
21
36
  complexity class, allocation, IO in loops). Sonnet 5 for correctness; announce the
22
37
  delegation before it starts (`~/.claude/CLAUDE.md` §6).
38
+ - **Tell the reviewer to read the files itself and not to spawn subagents of its own.** A
39
+ brief that fans out and does not forbid recursion has unbounded cost: a 12-reviewer sweep
40
+ on 2026-07-30 became an estimated 40–50 agents, exhausted a month's API budget, and lost
41
+ eight batches mid-run — while the one reviewer that read its own files returned the best
42
+ report of the twelve. One reviewer deciding to parallelise is enough to reproduce that at
43
+ smaller scale, so the sentence belongs in every brief, not only the wide ones
44
+ (stele:ADR-0017).
23
45
  - Brief it **blind to intent, aware of law**: give it the diff, this repo's `CLAUDE.md`,
24
46
  and `adr/INDEX.md`. Do *not* give it the conversation, your rationale, or the slice's
25
47
  claims about itself. A reviewer handed the reasoning returns the reasoning; one handed
@@ -34,7 +56,7 @@ get recorded until they have gone stale (stele:ADR-0003).
34
56
  raised again by every future fresh reader until the reason is written at the site
35
57
  (stele:ADR-0012) or in the §4 table. That is what makes the next pass cheaper than this one.
36
58
 
37
- 3. Then answer these four out loud, and act on each:
59
+ 4. Then answer these four out loud, and act on each:
38
60
  - **Did this change a user- or dev-facing API or feature?** If so, update `README.md`
39
61
  (and any docs) in the same change — it is a live document.
40
62
  - **Did this make a decision later work must obey?** If so, write it with `/adr` now,
@@ -48,5 +70,5 @@ get recorded until they have gone stale (stele:ADR-0003).
48
70
  ADR at that site in a comment (stele:ADR-0012), so the choice announces it is on purpose
49
71
  where the edit happens, not only in the §4 table nobody thinks to open.
50
72
 
51
- 4. Report what you found and did for each of the four, so the operator can confirm
73
+ 5. Report what you found and did for each of the four, so the operator can confirm
52
74
  nothing was silently skipped.
package/CHANGELOG.md ADDED
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ Generated by `scripts/build-changelog.mjs` from annotated git tags — never hand-edited
4
+ (ADR-0026). Run `npm run changelog` after tagging a release.
5
+
6
+ Every entry is a commit subject verbatim. Work committed after the newest tag is absent by
7
+ design and appears when the next release is tagged.
8
+
9
+ ## v0.4.1 — 2026-08-01
10
+
11
+ - docs(releasing): a change after the tag costs a version, not a moved tag (`5b3c621`)
12
+ - docs(readme): show the formats the linter enforces, and what the kit will not install (`f307d2e`)
13
+ - chore: 0.4.1 — README documents 0.4.0's behaviour, so it ships with it (`23ab57a`)
14
+ - chore: changelog for v0.4.0 (`c9f535e`)
15
+
16
+ ## v0.4.0 — 2026-08-01
17
+
18
+ - feat: generate the changelog from tags, and stop there (ADR-0026) (`b242d33`)
19
+ - feat: reinvention must name a cost and measure the build (ADR-0025) (`d54711f`)
20
+ - feat: ship the quality bar with the method (ADR-0024) (`7ae3ca8`)
21
+ - fix: ship the changelog to the people it was written for (`6847262`)
22
+ - fix: refuse a managed path that is not a readable file, by name (0.4.0) (`9eed537`)
23
+ - fix: the vendor report says "copied", not "copyd" (`e7af0d5`)
24
+ - docs(adr-0024): the import-the-constant check is worth running after all (`f358cbf`)
25
+ - docs(ledger): a fan-out brief that permits recursion has unbounded cost (`5383f97`)
26
+ - docs(ledger): log the rule that would have caught a ledger losing a section (`5b19a6f`)
27
+ - docs(init-method): stop describing the global link ADR-0016 withdrew (`82d51bd`)
28
+ - chore: stop tracking npm pack output (`7915705`)
29
+
30
+ ## v0.3.0 — 2026-07-28
31
+
32
+ - feat: an update overwrites only what the repo has not touched (ADR-0023) (`8bcba5d`)
33
+ - feat: make the mutation check standing, wired into /wrap-up (ADR-0022) (`f269c6a`)
34
+ - fix: two defects the adversarial pass found in the published machinery (`97bedd6`)
35
+ - fix: /adr computes the next id across both document directories (`75d36ae`)
36
+ - test: pin six behaviours a mutation probe found unpinned (`19a45f9`)
37
+ - chore: 0.3.0 — an update no longer discards local command adaptations (`bdbaf86`)
38
+ - chore: 0.2.1 — patch the two defects found after publishing 0.2.0 (`7d2086e`)
39
+
40
+ ## v0.2.0 — 2026-07-27
41
+
42
+ - feat: check citations wherever they are read (ADR-0020) (`68e56ac`)
43
+ - feat: enforce document immutability with a git-aware check (ADR-0019) (`a165fa1`)
44
+ - feat: give the coverage layer an instrument — the adversarial pass (ADR-0017) (`6e2fa6d`)
45
+ - fix: the hook checks every path a rule reads (ADR-0021) (`dec6967`)
46
+ - fix: close the three remaining false greens found by the audit (`b3efdcb`)
47
+ - fix: check the commit rather than the working tree in the pre-commit hook (ADR-0018) (`5144718`)
48
+ - docs: log six enforcement gaps found by auditing the machinery (`562f838`)
49
+ - chore: 0.2.0 — new error-severity rules change installed behaviour (`4fc52f6`)
50
+ - chore: close npm-cleanup ledger item — 0.1.2 published (ADR-0016) (`e8f8812`)
51
+
52
+ ## v0.1.2 — 2026-07-23
53
+
54
+ - feat: surface the reuse decision (ADR-0013); slice records existing code considered (`c0a9034`)
55
+ - feat: cite the governing ADR at the code site (ADR-0012); /slice folds in ledger (`47a22eb`)
56
+ - feat(lint): rules 12/13 — slices carry Verification and a Gherkin Definition of Done (`8a8a4c0`)
57
+ - feat(init-method): compose with an existing pre-commit framework instead of taking the file (`719178b`)
58
+ - feat(init-method): vendor the slash commands, and let a repo adapt its copy (`c3ae30d`)
59
+ - feat(init): /init-method installs the kit, and refuses to brick the repo (ADR-0006) (`206c629`)
60
+ - feat(lint): rule 11 — verify scripts must be wired (ADR-0004) (`7d81dab`)
61
+ - feat(lint): rule 10 — an empty corpus is not a pass (`3a4c18e`)
62
+ - feat(method): route writes by what they govern, bounding memory (ADR-0005) (`2abf31c`)
63
+ - feat(method): add global CLAUDE.md for cross-repo working conventions (`6575f00`)
64
+ - feat(method): make the architectural type set per-repo (--arch) (`e98a039`)
65
+ - feat(method): enforcement layer — hook, index, commands, CI (Phase 4) (`6a21f42`)
66
+ - feat(method): migrate legacy ADRs to frontmatter (Phase 2) (`b8476de`)
67
+ - feat(method): document invariants, linter, and legacy scanner (`e26d709`)
68
+ - fix: stop the installer touching ~/.claude; drop the global-conventions link (ADR-0016) (`f6199b5`)
69
+ - fix: run the CLI when invoked via a bin symlink (npx no-op) (`53c61c8`)
70
+ - fix(lint): qualify cross-repo citations, unbreaking install into a fresh repo (`04b7afd`)
71
+ - fix(test): read rule codes by pattern, not by fixed width (`f6d709d`)
72
+ - fix(templates): stop the scaffold from duplicating global conventions (`a01c22c`)
73
+ - fix(migrate): don't read a supersession date as the decision date (`35e8792`)
74
+ - refactor: rename to Stele and prepare npm distribution as @isonimus/stele (`ef294ae`)
75
+ - docs: add contributing, security, code of conduct, and PR template (`3ea62f9`)
76
+ - docs: add README — the front door, mental model, and usage scenarios (`108502c`)
77
+ - docs(ledger): close the taxonomy question and the l33t migration (`7c0969d`)
78
+ - docs(adr): name the fourth kind — the live doc — superseding ADR-0001 (`42d7d61`)
79
+ - docs(ledger): record the commands gap and the decision to vendor them (`127dcd8`)
80
+ - docs(ledger): reconcile with boxel, which has now run the whole flow by hand (`78d9baf`)
81
+ - docs(global): expand §3 quality bar with structure, error-handling and test-quality rules (`684954c`)
82
+ - docs(adr): sanction dated corrections on immutable bodies (ADR-0001) (`efc4d29`)
83
+ - docs(ledger): record that an empty corpus lints green (`2902686`)
84
+ - docs(method): record that boxel has no CLAUDE.md (`6fcaa2b`)
85
+ - docs(method): correction covers ADR violations and subpar proposals (`64d0d54`)
86
+ - Add verification harness and standing-invariants conventions (ADR-0004) (`4a995a9`)
package/README.md CHANGED
@@ -39,7 +39,7 @@ Every document is exactly one kind. There is no fifth. ([ADR-0010](adr/0010-live
39
39
  | Kind | Files | Rule |
40
40
  |---|---|---|
41
41
  | **Immutable** | `adr/*.md`, `slices/*.md` | Written once. Body prose is never edited — only status/supersession fields change. |
42
- | **Generated** | `adr/INDEX.md` | Built from frontmatter by script. Never hand-edited. |
42
+ | **Generated** | `adr/INDEX.md`, and this repo's `CHANGELOG.md` | Built by script — from frontmatter, or from git tags. Never hand-edited. |
43
43
  | **Mutable** | `LEDGER.md` | Exactly one per repo. The only file maintained by hand. |
44
44
  | **Live** | `README.md`, `docs/*` | Describes how something behaves *now*; updated in the same change as the code it describes. |
45
45
 
@@ -63,6 +63,60 @@ free to change, because status and supersession are how a record announces it wa
63
63
 
64
64
  ---
65
65
 
66
+ ## The three formats
67
+
68
+ Everything the linter enforces is one of these. They are small on purpose: a schema you can
69
+ parse by hand is one you can also fix by hand at 6pm.
70
+
71
+ **An ADR's frontmatter.** Eight fields, all required, closed vocabularies for `type` and
72
+ `status` (R1–R3):
73
+
74
+ ```markdown
75
+ ---
76
+ id: '0007'
77
+ title: "Sessions are signed, not stored"
78
+ type: architecture # architecture | slice | batch
79
+ status: accepted # accepted | proposed | superseded | amended
80
+ date: 2026-03-14 # a real calendar date; 2026-02-30 is rejected
81
+ supersedes: [] # [0003] — and 0003 must point back (R4–R7)
82
+ superseded_by: []
83
+ ---
84
+
85
+ # ADR-0007 — Sessions are signed, not stored
86
+
87
+ ## Context
88
+ ## Decision
89
+ ## Consequences
90
+ ```
91
+
92
+ **A ledger line.** One per item, and closing it means deleting the line (R8 checks the
93
+ citation resolves):
94
+
95
+ ```markdown
96
+ - [bug] Token refresh races when two tabs renew at once; the loser gets a 401 and no retry.
97
+ Reproduced on staging 2026-03-02 (ADR-0007).
98
+ ```
99
+
100
+ `type` is `bug` | `feature` | `deferred` | `audit`.
101
+
102
+ **A Definition of Done.** Acceptance criteria as Given/When/Then, written before the code
103
+ (R13 requires a full triad, R12 requires `## Verification` alongside it):
104
+
105
+ ```markdown
106
+ ## Definition of Done
107
+
108
+ - **Given** a session signed 25 hours ago
109
+ **When** the client calls any authenticated endpoint
110
+ **Then** the response is 401 and the body names `token_expired`
111
+
112
+ ## Verification
113
+
114
+ `test/session-expiry.test.mjs`, plus `scripts/session-verify.mjs` for the clock-skew case a
115
+ unit test cannot assert — wired into `package.json`, or R11 fails the commit.
116
+ ```
117
+
118
+ ---
119
+
66
120
  ## Quickstart
67
121
 
68
122
  Install into a git repo — dry-run first, always ([ADR-0006](adr/0006-init-method-bootstrap.md)).
@@ -81,20 +135,55 @@ updated linter calls red. A release that adds a rule can turn a green repo red b
81
135
  hook that is already installed, and the failure otherwise shows up as an unexplained
82
136
  blocked commit some time later (ADR-0021).
83
137
 
138
+ It also **keeps any slash command this repo adapted**. `.claude/.stele-vendored.json`
139
+ records what the toolkit last handed over, which is what lets an update tell a stale copy
140
+ from a deliberate edit — before that record existed the two were the same observation, so
141
+ taking a fix meant discarding every adaptation
142
+ ([ADR-0023](adr/0023-vendored-commands-and-what-an-update-may-overwrite.md)).
143
+ Commit that file. `--update --force` discards adaptations, and has to be typed.
144
+
84
145
  From a clone of this repo, the same entry point runs directly:
85
146
 
86
147
  ```
87
148
  node scripts/init-method.mjs <repo-root> --apply
88
149
  ```
89
150
 
90
- It installs `CLAUDE.md`, `LEDGER.md`, the linter, the index builder, and a pre-commit hook —
151
+ It installs `CLAUDE.md`, `LEDGER.md`, `docs/quality-bar.md`, the linter, the index builder,
152
+ and a pre-commit hook —
91
153
  and **refuses to install the hook on a linter-red corpus**, because a hook that blocks every
92
154
  commit is the tool bricking the repo it was meant to protect. If a hook framework already
93
155
  owns the pre-commit slot, the doc checks join it rather than fight for the file
94
156
  ([ADR-0008](adr/0008-compose-with-an-existing-hook-framework.md)).
95
157
 
158
+ It also refuses, by name, when a path it manages is not a file it can read and write — a
159
+ directory where `CLAUDE.md` should be, or a symlink that does not resolve. Nothing is written
160
+ in that state, in any mode: the alternative was an `EISDIR` stack trace naming `readFileSync`,
161
+ or, for an unresolvable link, silently writing through it.
162
+
96
163
  The linter and slash commands are **vendored per repo** and a repo's local edits to the
97
- commands survive re-runs ([ADR-0007](adr/0007-commands-are-vendored-and-adaptable.md)).
164
+ commands survive re-runs — including across an `--update`
165
+ ([ADR-0023](adr/0023-vendored-commands-and-what-an-update-may-overwrite.md), superseding
166
+ [ADR-0007](adr/0007-commands-are-vendored-and-adaptable.md)).
167
+
168
+ `docs/quality-bar.md` rides the same rules. It is the standard a slice's
169
+ `## Definition of Done` is measured against — no `any`, fail loud, no magic values, the
170
+ testing rule that a test derives from the spec and never from the code
171
+ ([ADR-0024](adr/0024-the-quality-bar-ships-with-the-method.md)), and the rule that where a
172
+ well-tested library or standard already covers the need it is proposed **by name** before any
173
+ bespoke design, with a departure argued from a property it would cost *and* a measured build
174
+ cost ([ADR-0025](adr/0025-the-established-solution-is-the-default.md)). Shipping the demand for a
175
+ Definition of Done without the standard it is judged by left every consumer to supply the
176
+ standard themselves. Adapt it freely: a repo whose stack has no `any` should cut that rule,
177
+ and an `--update` keeps the cut.
178
+
179
+ Almost all of it is **`review-only`** and says so in the file. No linter here can tell
180
+ whether a test was derived from a specification or from the implementation it tests; that is
181
+ what the adversarial pass in `/wrap-up` is for, and claiming otherwise would be the failure
182
+ this kit exists to remove. One subset *is* mechanical and the bar says so: a test asserting a
183
+ constant it imported from the module under test is the assertion `K === K`, it passes whatever
184
+ `K` is, and an AST linter can find it. Catching some instances of a defect beats catching
185
+ none — the original claim that this was not worth checking was withdrawn by amendment after a
186
+ wider count found six sites in one suite.
98
187
 
99
188
  ---
100
189
 
@@ -107,7 +196,7 @@ Run in Claude Code as `/<name>`.
107
196
  | `/adr <title>` | Scaffold a new Architecture Decision Record, frontmatter pre-filled. |
108
197
  | `/slice <title>` | Scaffold a new slice (one feature work-unit). |
109
198
  | `/audit` | Full-corpus health check — run every invariant, surface warnings and drift. |
110
- | `/wrap-up` | End-of-task gate — run the checks, run the adversarial pass when it triggers, and ask the four questions that get forgotten. |
199
+ | `/wrap-up` | End-of-task gate — run the checks, run the mutation check and adversarial pass when they trigger, and ask the four questions that get forgotten. |
111
200
  | `/remember <fact>` | Route a fact to the destination that governs it (see [Where things live](#where-things-live)). |
112
201
  | `/init-method` | Install the kit into a git repo. |
113
202
 
@@ -117,6 +206,8 @@ Under the hood, the npm scripts are the enforcement surface:
117
206
  npm run lint # node scripts/lint-docs.mjs . — the invariant checker
118
207
  npm run index # regenerate adr/INDEX.md
119
208
  npm run immutable # immutable bodies only gained lines since HEAD
209
+ npm run mutants # do the tests actually bite? (mutation check)
210
+ npm run changelog # regenerate CHANGELOG.md from git tags (this repo only)
120
211
  npm test # the regression suite (every rule has a fixture)
121
212
  ```
122
213
 
@@ -202,7 +293,21 @@ are graded by what can actually be mechanised
202
293
  a human read-through, never claimed as guaranteed. A linter that pretended to check them
203
294
  would be a false green, the exact failure this project exists to prevent.
204
295
 
205
- This layer has one instrument, and it is not a linter: the **adversarial pass**
296
+ This layer has two instruments, and neither is a linter. The first is the **mutation
297
+ check** (`npm run mutants`), which answers one narrow question mechanically: *do the
298
+ tests bite?* It applies a curated list of small behaviour changes to the linter's pure
299
+ predicates and requires each to break a test. A survivor is not a bug — it is correct
300
+ behaviour nothing is watching, which a later refactor can reverse in silence. Measured on
301
+ adoption: 3 of 10 mutants died, and the seven survivors were all correct-but-unpinned
302
+ behaviour, now covered. It is deliberately not a framework — that would cost the
303
+ zero-dependency property, and curating the list is the work anyway.
304
+
305
+ Its limit is stated rather than discovered: it perturbs code and asks whether tests
306
+ notice, so it is blind to an input nobody wrote and to a rule that was wrong from the
307
+ start. **None of the three defects this project has actually suffered would have been
308
+ caught by it.** It buys regression durability, not correctness.
309
+
310
+ The second instrument is the one that covers exactly that gap: the **adversarial pass**
206
311
  ([ADR-0017](adr/0017-adversarial-review-is-the-coverage-mechanism.md)). Every coverage
207
312
  question has the form "is what you wrote sufficient?" and is addressed to the person who
208
313
  just wrote it — who already believes it is, which is why the code looks that way. So
@@ -231,6 +336,31 @@ happened ([ADR-0005](adr/0005-write-routing-and-the-bounds-of-memory.md)):
231
336
  A rule that governs a codebase never belongs in assistant memory: memory is invisible to
232
337
  every other reader of the repo, unversioned, and lost on a change of machine.
233
338
 
339
+ One artifact moved off row two. General *working practice* — the quality bar — now ships
340
+ with the method as `docs/quality-bar.md` rather than living only in a personal global file
341
+ ([ADR-0024](adr/0024-the-quality-bar-ships-with-the-method.md)). ADR-0005 rejected
342
+ per-repo copies for having no reconciliation point; the vendoring record built in ADR-0023
343
+ is that point, so the rejection is answered rather than overruled. The table itself still
344
+ governs everything else.
345
+
346
+ ---
347
+
348
+ ## What this kit does *not* install
349
+
350
+ Stele publishes itself to npm, so it carries release machinery: annotated tags as release
351
+ boundaries, a `CHANGELOG.md` generated from them, and [`docs/releasing.md`](docs/releasing.md)
352
+ for the order of the steps. **None of it is installed into your repo, and no rule requires any
353
+ of it.** Nor are `CONTRIBUTING.md`, `SECURITY.md`, a code of conduct or a PR template
354
+ scaffolded ([ADR-0026](adr/0026-the-changelog-is-generated-and-release-engineering-stops-at-this-repo.md)).
355
+
356
+ Three reasons, and the third is the one that decides it. Most repos using this kit publish
357
+ nothing, so a release convention there is ceremony against nobody. `CLAUDE.md` and `LEDGER.md`
358
+ *are* scaffolded because the method reads and writes them — community-health files are
359
+ load-bearing for nothing it does, and adding them would make this a repo scaffolder, which
360
+ GitHub already ships and tests better than we would. And `SECURITY.md` is a **promise** — a
361
+ disclosure address, a response window. A missing one says "no stated policy", which is true;
362
+ a templated one says something false to whoever reads it in the moment they rely on it.
363
+
234
364
  ---
235
365
 
236
366
  ## Design principles
@@ -242,7 +372,10 @@ every other reader of the repo, unversioned, and lost on a change of machine.
242
372
  - **Grammar over toolchain.** Given/When/Then is adopted as *writing discipline*, not a test
243
373
  framework — the verify scripts are the executable layer.
244
374
  - **The diff is the audit trail.** Immutable records, generated indexes, and single-writer
245
- ledgers mean the git log *is* the history no hand-maintained changelog to drift.
375
+ ledgers mean the git log *is* the history. This repo's own `CHANGELOG.md` follows from that
376
+ rather than sitting beside it: it is generated from annotated tags, never hand-written, so
377
+ there is no second copy of the facts to drift
378
+ ([ADR-0026](adr/0026-the-changelog-is-generated-and-release-engineering-stops-at-this-repo.md)).
246
379
 
247
380
  For the reasoning behind any of these, read the ADR it links to. That is what the ADRs are
248
381
  for.
@@ -0,0 +1,169 @@
1
+ # Quality bar
2
+
3
+ The standard a slice's `## Definition of Done` is measured against (stele:ADR-0024). This
4
+ file ships with the method and is vendored into this repo; it is a **live doc**
5
+ (stele:ADR-0010) — update it in the same change as the practice it describes.
6
+
7
+ It is yours to adapt. An `--update` keeps an edit you made here and reports it, and
8
+ overwrites this file only if the repo never touched it (stele:ADR-0023). A rule that does
9
+ not apply to this stack should be cut rather than ignored.
10
+
11
+ Each section says how it is **enforced**, in the vocabulary of `CLAUDE.md` §4 —
12
+ `verified_by: <script>`, `pending (LEDGER)`, `review-only`. Most of this is `review-only`:
13
+ it is enforced by `/wrap-up` and by review, not by a script. That is stated rather than
14
+ glossed, because a standard that overstates its own enforcement is the failure
15
+ stele:ADR-0003 exists to prevent, committed one level up.
16
+
17
+ ## 1. Correctness — `review-only`
18
+
19
+ - **Never use `any`.** No exceptions. `unknown` plus narrowing, generics, or proper types.
20
+ In a language without `any`, the rule is the same one: no escape hatch that turns off the
21
+ checker for a value you did not want to describe.
22
+ - **Fail loud.** No swallowed errors — no empty `catch`, no catch-log-and-continue past a
23
+ broken invariant, no default or fallback that masks a real failure. Validate at boundaries
24
+ and stop at the first sign something is wrong. A silenced error is a bug debugged twice:
25
+ once now, blind, and once later for real.
26
+ - **No hacks, no quick fixes, no workarounds.** Find the root cause and fix it for good. A
27
+ symptom silenced is a bug rescheduled.
28
+ - **No magic values.** A bare `86400` or `0.15` in a branch is a latent bug — name it as a
29
+ constant whose name explains what it is and why.
30
+
31
+ ## 2. Design — `review-only`
32
+
33
+ - **Lean, purposeful code — KISS.** The simplest thing that works. Complexity must earn its
34
+ place, and unexplained complexity is a defect. No speculative generality, no abstraction
35
+ without a present need. Fight cyclomatic complexity by extracting composable, well-named
36
+ functions **for readability and reuse — never to hit a number**; decomposition that adds
37
+ indirection without adding clarity is its own smell.
38
+ - **Cohesion and DRY, by the rule of three.** Modules stay cohesive; a piece of logic lives
39
+ in one place. Reach for reuse or composition on the **third** instance of the same logic —
40
+ not the first, which is the speculative generality forbidden above. Apply SOLID only where
41
+ it earns its keep; misapplied, it produces exactly the over-abstraction this bar exists to
42
+ prevent.
43
+ - **Names are accurate descriptors.** No `x`, `tmp`, `data2`, `handle2`. A name states the
44
+ thing's intent and its meaning in the domain. When a good name is hard to find, the design
45
+ is usually the problem, not the vocabulary.
46
+ - **Comments explain WHY, not WHAT** — the code already shows what. No commented-out code and
47
+ no unused exports left behind; dead code is deleted, not parked. Version control remembers
48
+ it.
49
+ - **Never hardcode absolute paths** in config files, scripts, or commands. Always relative.
50
+ - **Prefer the established solution; argue any departure in writing** (stele:ADR-0025). Where a
51
+ well-tested library, pattern, protocol, or industry standard already covers the need, propose
52
+ it first and **by name**, before drawing a bespoke design — including when the operator asked
53
+ for the bespoke build, since asking for one is not evidence that none exists. Hand-rolling is
54
+ the exception and it is a legitimate one, under two conditions together: it names the property
55
+ the standard would cost (a hard constraint, a dependency budget, a platform or licence limit)
56
+ **and** it states the build cost as a measurement, not an estimate. What is not admissible is
57
+ reinventing by default, or meeting the standard after the code is written — at that point the
58
+ sunk build argues for itself and the comparison is a design against a rewrite. Both cases are
59
+ on the record in the repo that ships this file: `stele:ADR-0022` declines a mutation-testing
60
+ framework, naming the zero-dependency property it would cost and the twenty lines its
61
+ replacement takes; `stele:ADR-0008` is the bill for the omission — a design that assumed the
62
+ pre-commit slot was free met the widely-deployed tool that owns it three ADRs later, and pays
63
+ for a second install shape permanently. Inside a single codebase the same rule is
64
+ `stele:ADR-0013`.
65
+
66
+ ## 3. Design first — measure twice, cut once — `verified_by: lint-docs.mjs` (partly)
67
+
68
+ Design before implementation. The decision is written down *before* the code, and the doc
69
+ and the code ship in the same commit. R12 and R13 check that a slice carries its
70
+ `## Verification` and its `## Definition of Done`; they cannot check that a scenario is
71
+ *right*, which is what `/wrap-up` is for.
72
+
73
+ **Decisions cite measured data, not estimates.** If a design question has a measurable
74
+ answer, measure it — that is what `scripts/<topic>-probe.mjs` is for, and the number goes in
75
+ the ADR.
76
+
77
+ For anything non-trivial, **present the options and their tradeoffs before building.**
78
+
79
+ ## 4. Tests — `review-only`
80
+
81
+ - **Every relevant piece of logic gets a regression test — a good one.** No excuses, and no
82
+ irrelevant, duplicate, or fragile tests either. Test observable behaviour, not
83
+ implementation internals, and cover the error and edge paths, not just the happy one. Tests
84
+ are codebase: same standards, same strict typing.
85
+ - **A regression test must fail before the fix and pass after.** One that passes before
86
+ proves nothing.
87
+ - **Tests assert intended behaviour, not observed behaviour** (stele:ADR-0024). A test
88
+ derives from the spec — the slice's `## Definition of Done`, the API contract, the
89
+ reference implementation, the issue — never from reading the code and recording what it
90
+ currently returns. A test written from the code cannot fail when the code is wrong: it
91
+ detects change, and it will pin a bug in place and defend it against the fix.
92
+ - Where there is no prior behaviour to fail against, **write the expected value down before
93
+ running it**, and treat a first-run pass as unverified rather than as proof.
94
+ - Where no specification exists, **say so at the test site** and name what the expectation
95
+ is derived from instead. An invented citation is worse than an admitted gap.
96
+ - When the spec is ambiguous, **settle the spec.** Never let the implementation cast the
97
+ deciding vote.
98
+ - **Citing the source is not the same as opening it.** The finding behind this rule was a
99
+ test whose comment cited the reference implementation by class name and then stated the
100
+ wrong arithmetic. Read the source, not the writeup — including your own. An aggregated
101
+ report of somebody else's findings is a writeup too.
102
+ - **Never assert a value the test imported from the module under test.** `expect(f(x)).toBe(K)`
103
+ where `K` comes from the module that produced it is the assertion `K === K`; it passes
104
+ whatever `K` is, including wrong. Write the number, and put the derivation in a comment.
105
+ This is the one part of this rule a machine can find, and in the suite that motivated it
106
+ the form appeared six times — more often than any other test-basis defect
107
+ (stele:ADR-0024). If this repo has an AST linter, it should carry the rule; scope it to
108
+ **scalar constants**, since asserting an imported *enum member* (`toBe(ItemId.Apple)`) is
109
+ correct and was 594 of the hits when the scope was widened.
110
+
111
+ Neither instrument in the coverage layer detects a test derived from the implementation.
112
+ `npm run mutants` cannot: such a test kills its mutant perfectly well, because mutating the
113
+ constant breaks the test that asserts the constant (stele:ADR-0022). The adversarial pass in
114
+ `/wrap-up` is the enforcement (stele:ADR-0017) — except for the imported-constant form above,
115
+ which is the rule's one mechanically checkable subset, and catching some instances of a defect
116
+ beats catching none.
117
+
118
+ ## 5. Finishing — `review-only`
119
+
120
+ - **Boyscout rule:** leave every touched file better than found. A noticed bug is ours even
121
+ if we did not introduce it — fix it, or log it in `LEDGER.md` if deferred.
122
+ - **Verify before declaring done.** Never report a task complete without running this repo's
123
+ own checks — typecheck, linter, tests. "Done" means the checks ran and passed in this
124
+ conversation, and that what ran was said out loud — not that the change looks right.
125
+
126
+ ## 6. Commits — `review-only`
127
+
128
+ - **Explain WHY, not WHAT.** The diff already shows what changed.
129
+ - **Atomic and coherent** — one logical change per commit.
130
+ - **Ship the doc and the code in the same commit.**
131
+ - **Commit only when asked.** Never commit unprompted.
132
+
133
+ ## 7. The operator may be wrong — `review-only`
134
+
135
+ The operator is not infallible. If a proposal, observation, or assumption is incorrect, say
136
+ so directly, with data or a clear explanation. Deferring to a wrong idea to be agreeable
137
+ builds on a bad premise; being corrected early is cheaper for everyone.
138
+
139
+ Correction is not only for factual errors. When the operator asks for something that violates
140
+ a recorded decision without justifiable reason, or proposes a subpar fix, feature, or plan,
141
+ push back the same way — with evidence and a concrete better option — and where a standard
142
+ already solves the problem, the better option is that standard, named (§2). An operator can lack
143
+ context a decision record already settled, so citing it *is* the correction. If a violation
144
+ turns out to be justified, that justification is written down as a new or superseding ADR —
145
+ never a silent exception.
146
+
147
+ This cuts both ways: when the evidence contradicts a convention stated here, report that too.
148
+
149
+ **Explain the reasoning behind operating choices**, do not just execute. An unexplained
150
+ choice teaches nothing.
151
+
152
+ ## 8. Token economy — `review-only`
153
+
154
+ - The main model plans, reviews, corrects, and writes tests. Delegate rock-mining
155
+ (mechanical refactors, boilerplate, broad surveys) to cheaper subagents — a small fast
156
+ model for mechanical work, a mid-tier one for work needing judgement — instructed to return
157
+ minimal, structured output so the main context stays clean.
158
+ - **Say what was delegated, to which agent and model, and why** — one line, before the work
159
+ starts. Delegation is otherwise invisible: a subagent's reasoning never reaches the main
160
+ conversation, so an unannounced handoff means a result arrives with no way to judge how much
161
+ to trust it, and no chance to object that the task needed judgement rather than a cheaper
162
+ model.
163
+ - When context is deep and the task is clearly switching, **say it is a good moment to
164
+ `/compact`** — it gets forgotten, and it causes context rot.
165
+
166
+ ## 9. Language — `review-only`
167
+
168
+ **Everything written is in English** — code, comments, commits, docs, and user-facing or
169
+ creative copy alike — regardless of the language of the conversation.