@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.
- package/.claude/commands/adr.md +3 -2
- package/.claude/commands/init-method.md +38 -13
- package/.claude/commands/wrap-up.md +25 -3
- package/CHANGELOG.md +86 -0
- package/README.md +139 -6
- package/docs/quality-bar.md +169 -0
- package/docs/releasing.md +84 -0
- package/package.json +5 -1
- package/scripts/build-changelog.mjs +194 -0
- package/scripts/check-mutants.mjs +278 -0
- package/scripts/init-method.mjs +245 -29
- package/scripts/lint-docs.mjs +21 -2
- package/templates/CLAUDE.md +8 -4
package/.claude/commands/adr.md
CHANGED
|
@@ -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
|
|
14
|
-
digits.
|
|
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`,
|
|
37
|
-
the corpus lints clean**.
|
|
36
|
+
`adr/INDEX.md`, and installs the pre-commit hook **only if the corpus lints clean**.
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
install
|
|
42
|
-
|
|
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
|
|
96
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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. **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|