@isonimus/stele 0.1.2
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 +43 -0
- package/.claude/commands/audit.md +25 -0
- package/.claude/commands/init-method.md +105 -0
- package/.claude/commands/remember.md +61 -0
- package/.claude/commands/slice.md +72 -0
- package/.claude/commands/wrap-up.md +30 -0
- package/.claude/hooks/pre-commit +29 -0
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/package.json +48 -0
- package/scripts/build-index.mjs +129 -0
- package/scripts/init-method.mjs +359 -0
- package/scripts/lint-docs.mjs +463 -0
- package/scripts/migrate-adrs.mjs +218 -0
- package/scripts/scan-legacy.mjs +266 -0
- package/templates/CLAUDE.md +114 -0
- package/templates/LEDGER.md +24 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scaffold a new Architecture Decision Record with frontmatter pre-filled
|
|
3
|
+
argument-hint: <short title of the decision>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Create a new ADR recording a decision that later work must obey — a mechanism, data
|
|
7
|
+
format, or boundary. An ADR asserts *"on date X we chose Y because Z"*: a historical
|
|
8
|
+
claim that stays true forever (ADR-0001). If you are recording a **feature work-unit**
|
|
9
|
+
rather than a durable decision, use `/slice` instead.
|
|
10
|
+
|
|
11
|
+
Steps:
|
|
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.
|
|
15
|
+
2. Choose the `type`: `architecture` for a durable decision, `slice` for a feature unit,
|
|
16
|
+
`batch` only if it genuinely bundles several unrelated decisions that cannot map 1:1.
|
|
17
|
+
3. Write `adr/NNNN-<kebab-title>.md` opening with the ADR-0002 frontmatter block:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
---
|
|
21
|
+
id: 'NNNN'
|
|
22
|
+
title: "<title>"
|
|
23
|
+
type: architecture
|
|
24
|
+
status: accepted
|
|
25
|
+
date: <today, YYYY-MM-DD>
|
|
26
|
+
supersedes: []
|
|
27
|
+
superseded_by: []
|
|
28
|
+
---
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
4. Body sections: `## Context` (the forces and the observed problem — cite data, not
|
|
32
|
+
estimates), `## Decision`, `## Consequences`.
|
|
33
|
+
5. If this decision **supersedes** an existing ADR: add its id to this ADR's `supersedes`,
|
|
34
|
+
set the old ADR's `status: superseded` and add this id to its `superseded_by`, and in
|
|
35
|
+
the body state *why the old reasoning was wrong* — that record is the point. Both
|
|
36
|
+
directions must match or rule 4 fails. Never edit the old ADR's prose body; only its
|
|
37
|
+
status/supersession fields may change.
|
|
38
|
+
6. If the decision defers something, record it as one line in `LEDGER.md` citing this ADR.
|
|
39
|
+
Do not track it anywhere else.
|
|
40
|
+
7. Regenerate the index (`npm run index`) and run the linter (`npm run lint`) — both must
|
|
41
|
+
be green before you are done.
|
|
42
|
+
|
|
43
|
+
Title argument: $ARGUMENTS
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Full-corpus health check — run every invariant and surface warnings and drift
|
|
3
|
+
argument-hint: [repo-root]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run a full audit of the ADR corpus and report its health. Unlike `/wrap-up` (a quick gate
|
|
7
|
+
on the current change), this looks at the whole corpus and reports advisory findings the
|
|
8
|
+
build tolerates, so the operator can decide what to promote or clean up.
|
|
9
|
+
|
|
10
|
+
1. `npm run lint` — report every error (must be zero) and every warning. Rule 9 warnings
|
|
11
|
+
(bare `ADR NNNN` prose references that do not resolve) are expected on a legacy corpus;
|
|
12
|
+
list them so a genuine typo can be told from a deliberate cross-repo reference.
|
|
13
|
+
|
|
14
|
+
2. `npm run index -- --check` — confirm `adr/INDEX.md` is current. If stale, regenerate.
|
|
15
|
+
|
|
16
|
+
3. `npm test` — the invariant checks are themselves tested; the suite must pass.
|
|
17
|
+
|
|
18
|
+
4. Cross-check the ledger against reality: for each `LEDGER.md` open item, confirm the ADR
|
|
19
|
+
it cites still exists (rule 8 covers this) and the deferral is genuinely still open. A
|
|
20
|
+
closed item that was never deleted is the dual-write failure this method exists to fix.
|
|
21
|
+
|
|
22
|
+
5. Summarise: error count, warning count, index freshness, and any ledger items that look
|
|
23
|
+
resolved-but-undeleted. Recommend concrete next actions; do not fix silently.
|
|
24
|
+
|
|
25
|
+
Repo root argument (default: current repo): $ARGUMENTS
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
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]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Install this kit into a repo, or verify an existing install (ADR-0006). The script does
|
|
7
|
+
the mechanical half; you do the two halves that need judgement — filling the scaffolded
|
|
8
|
+
`CLAUDE.md`, and wiring verify scripts.
|
|
9
|
+
|
|
10
|
+
Target repo: `$ARGUMENTS` (default: the current repo root).
|
|
11
|
+
|
|
12
|
+
## 1. Look before writing
|
|
13
|
+
|
|
14
|
+
Run the dry run first and read what it plans:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
node scripts/init-method.mjs <target>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
It writes nothing. `WOULD` lines are the plan, `KEEP` means a file already exists and
|
|
21
|
+
will be left alone, `PROBLEM` means something needs a decision. Report the plan to the
|
|
22
|
+
operator before applying it if anything is being scaffolded over a repo that already has
|
|
23
|
+
conventions of its own.
|
|
24
|
+
|
|
25
|
+
If the target is not a git repository the run refuses. Offer `git init`; do not work
|
|
26
|
+
around it — the hook has nowhere to live.
|
|
27
|
+
|
|
28
|
+
## 2. Apply
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
node scripts/init-method.mjs <target> --apply
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This scaffolds `adr/`, `CLAUDE.md` and `LEDGER.md` (never overwriting), vendors
|
|
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**.
|
|
38
|
+
|
|
39
|
+
The commands are vendored under softer rules than the machinery (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.
|
|
43
|
+
|
|
44
|
+
## 3. If the repo already has a hook framework
|
|
45
|
+
|
|
46
|
+
A target with a `.pre-commit-config.yaml` gets the doc checks **composed into it** as a
|
|
47
|
+
`repo: local` block rather than a symlink (ADR-0008) — the framework owns
|
|
48
|
+
`.git/hooks/pre-commit`, and a symlink there is silently erased by the next
|
|
49
|
+
`pre-commit install`. The append is idempotent and additive; the existing config is
|
|
50
|
+
never reordered or rewritten, and `--update` leaves it alone.
|
|
51
|
+
|
|
52
|
+
Two things to watch:
|
|
53
|
+
|
|
54
|
+
- A config the script does not recognise (no top-level `repos:`) is **refused**, not
|
|
55
|
+
guessed at. Add the block by hand and say you did.
|
|
56
|
+
- `--check` reports a problem when the framework is configured but never installed. That
|
|
57
|
+
is not our hook failing — it means *no* hook runs, including the repo's own ruff and
|
|
58
|
+
mypy. Tell the operator to run `pre-commit install`; do not paper over it by
|
|
59
|
+
symlinking ours instead.
|
|
60
|
+
|
|
61
|
+
## 4. If it refused the hook
|
|
62
|
+
|
|
63
|
+
A refusal is normal on a repo that already has work in it, and the reason is almost
|
|
64
|
+
always **rule 11: a `scripts/*-verify.mjs` that no `package.json` command runs**. An
|
|
65
|
+
unwired verify script ran once, the day it was written; the linter treats that as an
|
|
66
|
+
error, so the corpus is red and a hook would block every commit.
|
|
67
|
+
|
|
68
|
+
Fix it, in this order — the order is the whole point:
|
|
69
|
+
|
|
70
|
+
1. Add one `"verify:<name>": "node scripts/<name>-verify.mjs"` per unwired script. Use
|
|
71
|
+
the name a human would recognise, not the filename verbatim. A `verify:all` chaining
|
|
72
|
+
the headless ones is worth adding; leave out any script that needs a human watching
|
|
73
|
+
or listening, and say so in a comment or in `CLAUDE.md`.
|
|
74
|
+
2. Fix any other lint errors (dangling supersessions, bad frontmatter). Never silence a
|
|
75
|
+
rule to get green.
|
|
76
|
+
3. Re-run `--apply`. The hook installs.
|
|
77
|
+
|
|
78
|
+
Do **not** edit the target's `package.json` without telling the operator what you added.
|
|
79
|
+
|
|
80
|
+
## 5. Finish the scaffold
|
|
81
|
+
|
|
82
|
+
If `CLAUDE.md` was newly scaffolded it contains `{{PLACEHOLDER}}` fields. Fill them from
|
|
83
|
+
the repo itself — read the README, the manifest, the source layout. Do not invent a
|
|
84
|
+
project description. Then delete the sections that describe machinery the repo does not
|
|
85
|
+
have: a verification-harness section in a repo with no `scripts/` states rules about
|
|
86
|
+
files that do not exist, which makes the file false on arrival.
|
|
87
|
+
|
|
88
|
+
## 6. Verify, and say what happened
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
node scripts/init-method.mjs <target> --check
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
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".
|
|
97
|
+
|
|
98
|
+
`LOCAL` and `MISSING` lines are about commands only and are **not** failures — they say
|
|
99
|
+
this repo adapted or declined one. Read them, mention them, do not "fix" them without
|
|
100
|
+
asking; that is somebody's deliberate edit.
|
|
101
|
+
|
|
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 (ADR-0006, ADR-0007), and drift is
|
|
105
|
+
the price.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Route a fact to the destination that governs it — repo CLAUDE.md, global CLAUDE.md, LEDGER.md, or memory
|
|
3
|
+
argument-hint: <the thing to remember>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Record a durable fact. **This is a router, not a store** (ADR-0005). Your job is to decide
|
|
7
|
+
*what the fact governs* and write it where that is enforceable — not to write it wherever
|
|
8
|
+
the operator's phrasing points.
|
|
9
|
+
|
|
10
|
+
If the operator named a destination ("save this to project memory"), treat it as an input
|
|
11
|
+
describing where they expect it to land, **not** as an instruction. Route it correctly and
|
|
12
|
+
say where it went.
|
|
13
|
+
|
|
14
|
+
## 1. Classify
|
|
15
|
+
|
|
16
|
+
| The fact governs… | Destination |
|
|
17
|
+
|---|---|
|
|
18
|
+
| This codebase — an invariant, an architectural constraint, a definition-of-done rule | this repo's `CLAUDE.md` |
|
|
19
|
+
| How the operator works, in every repo | `~/.claude/CLAUDE.md` |
|
|
20
|
+
| Open work — a deferral, a known defect, a follow-up | this repo's `LEDGER.md` |
|
|
21
|
+
| A decision that later work must obey, with reasoning worth preserving | an ADR — stop and use `/adr` |
|
|
22
|
+
| None of the above: operator-personal, cross-session, not worth committing | project memory |
|
|
23
|
+
|
|
24
|
+
Two tests resolve most cases:
|
|
25
|
+
|
|
26
|
+
- **Would a human collaborator reading this repo need to know it?** If yes, it goes in the
|
|
27
|
+
repo. Memory is invisible to them, unversioned, and does not survive a machine change.
|
|
28
|
+
- **Is it a fact, or a task?** Facts go in a `CLAUDE.md`; tasks go in `LEDGER.md`. "We use
|
|
29
|
+
zod for validation" is a fact. "Migrate the remaining validators to zod" is a task.
|
|
30
|
+
|
|
31
|
+
Memory is the **residual**, not the default. If you are about to write a rule that
|
|
32
|
+
constrains code into memory, you have misrouted — that is the exact failure ADR-0005
|
|
33
|
+
records, which cost an amend and force-push after the same rule was independently rewritten
|
|
34
|
+
into 12 files across 8 projects.
|
|
35
|
+
|
|
36
|
+
## 2. Write it
|
|
37
|
+
|
|
38
|
+
- **Repo `CLAUDE.md`** — add to the section it belongs to. If it is a standing invariant,
|
|
39
|
+
add a row to the §5 invariants table citing the ADR that created it. If this repo has no
|
|
40
|
+
`CLAUDE.md`, scaffold one from `templates/CLAUDE.md` first; without it there is no
|
|
41
|
+
destination and the fact will silently fall back to memory (ADR-0005).
|
|
42
|
+
- **Global `CLAUDE.md`** — edit your global `~/.claude/CLAUDE.md`, wherever your global
|
|
43
|
+
conventions live. Keep it dense: it loads into every session in every repo.
|
|
44
|
+
- **`LEDGER.md`** — one line, `- [type] description (ADR-NNNN)`, citing the source ADR if
|
|
45
|
+
one exists.
|
|
46
|
+
- **Memory** — one fact per file, with the frontmatter schema, plus a one-line pointer in
|
|
47
|
+
`MEMORY.md`.
|
|
48
|
+
|
|
49
|
+
Before writing anywhere, check whether an existing entry already covers it and update that
|
|
50
|
+
instead of adding a duplicate.
|
|
51
|
+
|
|
52
|
+
## 3. Report
|
|
53
|
+
|
|
54
|
+
State which destination it went to and why in one line — e.g. *"→ repo `CLAUDE.md` §3: it
|
|
55
|
+
constrains code, so it needs to be greppable and reviewable."*
|
|
56
|
+
|
|
57
|
+
This step is not optional. Routing cannot be linted (ADR-0005), so saying the decision out
|
|
58
|
+
loud is the only thing that makes a misroute correctable in the moment rather than
|
|
59
|
+
discoverable in a survey weeks later.
|
|
60
|
+
|
|
61
|
+
Fact to record: $ARGUMENTS
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Scaffold a new slice (one feature work-unit) with frontmatter pre-filled
|
|
3
|
+
argument-hint: <short title of the feature>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Create a new **slice**: one feature work-unit, written *before* implementation and frozen
|
|
7
|
+
at merge (ADR-0001). A slice is a current-state claim while open, so it goes stale by
|
|
8
|
+
nature — freezing it to past tense at merge ("this is what shipped") converts it into a
|
|
9
|
+
historical claim that never goes stale. If you are recording a durable decision rather
|
|
10
|
+
than a unit of feature work, use `/adr` instead.
|
|
11
|
+
|
|
12
|
+
Steps:
|
|
13
|
+
|
|
14
|
+
1. Compute the next free id: highest ordinal in `adr/` (and `slices/` if present) plus
|
|
15
|
+
one, zero-padded to four digits.
|
|
16
|
+
2. Scan `LEDGER.md` for open items this slice touches — a `[feature]` it delivers, a
|
|
17
|
+
`[bug]` it fixes, a `[deferred]` it finally picks up. Fold each relevant one into
|
|
18
|
+
`## Goal` so the slice knows the history it is answering, and **delete the line from the
|
|
19
|
+
ledger for any item this slice closes** — closing an item means deleting its line (see
|
|
20
|
+
the ledger's own header). If nothing relates, note nothing; this is a read, not a
|
|
21
|
+
requirement to invent links.
|
|
22
|
+
3. Write the file opening with the ADR-0002 frontmatter block, `type: slice`:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
---
|
|
26
|
+
id: 'NNNN'
|
|
27
|
+
title: "<title>"
|
|
28
|
+
type: slice
|
|
29
|
+
status: accepted
|
|
30
|
+
date: <today, YYYY-MM-DD>
|
|
31
|
+
supersedes: []
|
|
32
|
+
superseded_by: []
|
|
33
|
+
---
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
4. Body sections, written before you code:
|
|
37
|
+
- `## Goal` — what ships and why, in one paragraph. Fold in any ledger items from step 2.
|
|
38
|
+
- `## Definition of Done` — **required.** The acceptance criteria as one or more
|
|
39
|
+
**Given / When / Then** scenarios, written before you code (ADR-0011). This is the
|
|
40
|
+
grammar, not a toolchain: plain markdown steps, no Cucumber. State the observable
|
|
41
|
+
outcome — a criterion with no observable "Then" is not done-able. Each scenario's
|
|
42
|
+
proof is named below in `## Verification`; a scenario with no proof is an unmet
|
|
43
|
+
criterion, not a finished one. Example:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
## Definition of Done
|
|
47
|
+
|
|
48
|
+
- **Given** a fresh world
|
|
49
|
+
- **When** the player places the block
|
|
50
|
+
- **Then** it renders with its mesher shape, a hotbar icon, and a hand-held model
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `## Design` — the approach; cite measured numbers from a probe where a design
|
|
54
|
+
question has a measurable answer, not estimates. State **what existing code was
|
|
55
|
+
considered**: what you searched for, what you reused, and what you deliberately did
|
|
56
|
+
*not* reuse and why (ADR-0013). This is not a mandate to reuse — the wrong abstraction
|
|
57
|
+
costs more than a duplication (global §3, rule of three); it makes the
|
|
58
|
+
reuse-or-duplicate choice *visible*, so an un-considered duplication is caught at
|
|
59
|
+
review. It is checked by human read-through, not the linter: whether you truly searched
|
|
60
|
+
is the coverage question no machine decides.
|
|
61
|
+
- `## Verification` — **required.** Name the unit tests, and if the behaviour cannot be
|
|
62
|
+
asserted in a unit test (rendering, worldgen, physics, timing), name the
|
|
63
|
+
`scripts/<slice>-verify.mjs` script and confirm it is wired into `package.json`
|
|
64
|
+
(ADR-0004). A slice with no `## Verification` section is incomplete. The linter
|
|
65
|
+
enforces both required sections (R12/R13): a `type: slice` must carry `## Verification`
|
|
66
|
+
and a `## Definition of Done` holding at least one full Given/When/Then triad.
|
|
67
|
+
- `## As built` — filled in at merge: what actually shipped, in past tense, confirming
|
|
68
|
+
each Definition-of-Done scenario was met or recording the deviation. This is the
|
|
69
|
+
freeze step.
|
|
70
|
+
5. Regenerate the index and run the linter; both green before done.
|
|
71
|
+
|
|
72
|
+
Title argument: $ARGUMENTS
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: End-of-task gate — run the checks and ask the four questions that get forgotten
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run before finishing a task. The point is to make mechanical what memory keeps dropping:
|
|
6
|
+
the linter catches structural drift, and four questions catch the follow-ups that never
|
|
7
|
+
get recorded until they have gone stale (ADR-0003).
|
|
8
|
+
|
|
9
|
+
1. Run the linter and the index check:
|
|
10
|
+
- `npm run lint` — must be green (rule 9 warnings are acceptable on a legacy corpus).
|
|
11
|
+
- `npm run index` — regenerate `adr/INDEX.md` and stage it if it changed. A stale
|
|
12
|
+
generated index is drift by another name.
|
|
13
|
+
- `npm test` — the test suite must pass; docs changes must not disturb code.
|
|
14
|
+
|
|
15
|
+
2. Then answer these four out loud, and act on each:
|
|
16
|
+
- **Did this change a user- or dev-facing API or feature?** If so, update `README.md`
|
|
17
|
+
(and any docs) in the same change — it is a live document.
|
|
18
|
+
- **Did this make a decision later work must obey?** If so, write it with `/adr` now,
|
|
19
|
+
while the reasoning is fresh. An unrecorded decision is re-litigated later from
|
|
20
|
+
nobody's memory.
|
|
21
|
+
- **Did this defer something** — a follow-up, a known-but-unfixed bug, a TODO? If so,
|
|
22
|
+
add one line to `LEDGER.md` citing the relevant ADR. The ledger is the only place
|
|
23
|
+
deferrals live; a TODO in code or a note in your head is not tracked.
|
|
24
|
+
- **Did this write code a later operator would plausibly try to "fix"?** — a deliberate
|
|
25
|
+
deviation, a non-obvious constraint, a hard-won exception. If so, cite the governing
|
|
26
|
+
ADR at that site in a comment (ADR-0012), so the choice announces it is on purpose
|
|
27
|
+
where the edit happens, not only in the §4 table nobody thinks to open.
|
|
28
|
+
|
|
29
|
+
3. Report what you found and did for each of the four, so the operator can confirm
|
|
30
|
+
nothing was silently skipped.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Stele pre-commit hook — the load-bearing enforcement decision (ADR-0003).
|
|
3
|
+
#
|
|
4
|
+
# A linter nobody runs is another convention resting on memory, which is the failure this
|
|
5
|
+
# project exists to fix. This hook makes the checks mechanical: no green corpus, no commit.
|
|
6
|
+
# CI (.github/workflows/docs.yml) is the backstop for clones without the hook installed.
|
|
7
|
+
#
|
|
8
|
+
# Install: ln -sf ../../.claude/hooks/pre-commit .git/hooks/pre-commit
|
|
9
|
+
# (/init-method does this; see ADR-0003. Do NOT install on a linter-red corpus — fix the
|
|
10
|
+
# reds first, or every commit is blocked. See LEDGER.)
|
|
11
|
+
#
|
|
12
|
+
# Bypass in a genuine emergency with `git commit --no-verify`; CI still catches it.
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
root=$(git rev-parse --show-toplevel)
|
|
17
|
+
cd "$root"
|
|
18
|
+
|
|
19
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
20
|
+
echo "pre-commit: node not found on PATH — cannot run doc checks." >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# 1. Invariants must hold (rules 1–8 block; rule 9 warns). Zero-dependency, so no install.
|
|
25
|
+
node scripts/lint-docs.mjs .
|
|
26
|
+
|
|
27
|
+
# 2. The generated index must match the corpus. A stale INDEX.md is drift by another name;
|
|
28
|
+
# regenerate and stage it rather than letting it fall behind.
|
|
29
|
+
node scripts/build-index.mjs --check .
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Isonimus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Stele
|
|
2
|
+
|
|
3
|
+
A zero-dependency workflow that keeps a codebase's **decisions, work, and definition of
|
|
4
|
+
done** honest — enforced by a git hook and CI, not by anyone remembering to.
|
|
5
|
+
|
|
6
|
+
It is meant to be **vendored into any git repo**: one command drops in a decision-record
|
|
7
|
+
tree, a worklist, a linter, and a pre-commit hook, and from then on the repo refuses commits
|
|
8
|
+
that let its own records rot.
|
|
9
|
+
|
|
10
|
+
> This README is a **live doc** — the *how-to* and the vision. The *why* behind every rule
|
|
11
|
+
> lives in [`adr/`](adr/), and each section below links to the decision that governs it.
|
|
12
|
+
> Where the two ever disagree, the ADR wins; tell us so we can fix the README.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The problem
|
|
17
|
+
|
|
18
|
+
Conventions kept in a model's memory, a wiki, or someone's head hold until the first busy
|
|
19
|
+
afternoon. They are invisible in review, unversioned, and gone on a change of machine. Three
|
|
20
|
+
failures recur:
|
|
21
|
+
|
|
22
|
+
- **Decisions drift.** A choice recorded as prose gets quietly edited until the record no
|
|
23
|
+
longer says what was actually decided, and the reasoning that made it right is lost.
|
|
24
|
+
- **Work-in-progress lies.** A doc written in the present tense ("the system does X") is
|
|
25
|
+
true the day it is written and slowly false forever after.
|
|
26
|
+
- **"Done" is assumed, not defined.** A feature with three required outputs and no written
|
|
27
|
+
acceptance criteria ships with two of them, because the intent was never in a form that
|
|
28
|
+
made the omission visible.
|
|
29
|
+
|
|
30
|
+
Stele's answer is a single principle: **if a convention matters, it is executable;
|
|
31
|
+
if it genuinely cannot be checked, that limit is stated out loud rather than trusted.**
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## The mental model — four kinds of document
|
|
36
|
+
|
|
37
|
+
Every document is exactly one kind. There is no fifth. ([ADR-0010](adr/0010-live-docs-are-the-fourth-kind.md), superseding [ADR-0001](adr/0001-immutable-or-generated.md))
|
|
38
|
+
|
|
39
|
+
| Kind | Files | Rule |
|
|
40
|
+
|---|---|---|
|
|
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. |
|
|
43
|
+
| **Mutable** | `LEDGER.md` | Exactly one per repo. The only file maintained by hand. |
|
|
44
|
+
| **Live** | `README.md`, `docs/*` | Describes how something behaves *now*; updated in the same change as the code it describes. |
|
|
45
|
+
|
|
46
|
+
- An **ADR** records a decision later work must obey: *"on date X we chose Y because Z"* — a
|
|
47
|
+
historical claim, true forever.
|
|
48
|
+
- A **slice** is one feature work-unit, written *before* implementation and **frozen to past
|
|
49
|
+
tense at merge** ("this is what shipped") — which converts a going-stale claim into a
|
|
50
|
+
never-stale one.
|
|
51
|
+
|
|
52
|
+
**Single writer, one direction.** An ADR records a deferral once, as a fact. `LEDGER.md`
|
|
53
|
+
cites the ADR. You never reach back into an ADR to close a ledger item — **closing an item
|
|
54
|
+
means deleting its line from the ledger.** Changing your mind means a **new** ADR that
|
|
55
|
+
supersedes the old one and says *why the old reasoning was wrong* — that record is the most
|
|
56
|
+
valuable thing this workflow produces, and an in-place edit destroys it.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
Install into a git repo — dry-run first, always ([ADR-0006](adr/0006-init-method-bootstrap.md)).
|
|
63
|
+
Stele is delivered by `npx`, then **vendored** into the repo; it is never a runtime
|
|
64
|
+
dependency ([ADR-0015](adr/0015-distribution-by-npx-delivered-vendoring.md)):
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
npx @isonimus/stele <repo-root> # dry run: shows what it would do
|
|
68
|
+
npx @isonimus/stele <repo-root> --apply # install the kit
|
|
69
|
+
npx @isonimus/stele <repo-root> --check # verify an install is intact
|
|
70
|
+
npx @isonimus/stele <repo-root> --update # re-sync vendored machinery
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
From a clone of this repo, the same entry point runs directly:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
node scripts/init-method.mjs <repo-root> --apply
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
It installs `CLAUDE.md`, `LEDGER.md`, the linter, the index builder, and a pre-commit hook —
|
|
80
|
+
and **refuses to install the hook on a linter-red corpus**, because a hook that blocks every
|
|
81
|
+
commit is the tool bricking the repo it was meant to protect. If a hook framework already
|
|
82
|
+
owns the pre-commit slot, the doc checks join it rather than fight for the file
|
|
83
|
+
([ADR-0008](adr/0008-compose-with-an-existing-hook-framework.md)).
|
|
84
|
+
|
|
85
|
+
The linter and slash commands are **vendored per repo** and a repo's local edits to the
|
|
86
|
+
commands survive re-runs ([ADR-0007](adr/0007-commands-are-vendored-and-adaptable.md)).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Commands
|
|
91
|
+
|
|
92
|
+
Run in Claude Code as `/<name>`.
|
|
93
|
+
|
|
94
|
+
| Command | What it does |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `/adr <title>` | Scaffold a new Architecture Decision Record, frontmatter pre-filled. |
|
|
97
|
+
| `/slice <title>` | Scaffold a new slice (one feature work-unit). |
|
|
98
|
+
| `/audit` | Full-corpus health check — run every invariant, surface warnings and drift. |
|
|
99
|
+
| `/wrap-up` | End-of-task gate — run the checks and ask the four questions that get forgotten. |
|
|
100
|
+
| `/remember <fact>` | Route a fact to the destination that governs it (see [Where things live](#where-things-live)). |
|
|
101
|
+
| `/init-method` | Install the kit into a git repo. |
|
|
102
|
+
|
|
103
|
+
Under the hood, the npm scripts are the enforcement surface:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
npm run lint # node scripts/lint-docs.mjs . — the invariant checker
|
|
107
|
+
npm run index # regenerate adr/INDEX.md
|
|
108
|
+
npm test # the regression suite (every rule has a fixture)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Scenarios
|
|
114
|
+
|
|
115
|
+
**Record a decision.** `/adr "Use a hand-rolled frontmatter parser"`. Fill Context /
|
|
116
|
+
Decision / Consequences, cite measured numbers where a question has a measurable answer, and
|
|
117
|
+
commit. Never edit it afterward.
|
|
118
|
+
|
|
119
|
+
**A decision changed.** Do *not* edit the old ADR. `/adr` a new one, set `supersedes: [NNNN]`
|
|
120
|
+
on it and `superseded_by` on the old, and spend a paragraph on *why the old reasoning was
|
|
121
|
+
wrong*. The linter enforces that the supersession is bidirectional and that a superseded ADR
|
|
122
|
+
no longer reads as "accepted" (rules R4–R7).
|
|
123
|
+
|
|
124
|
+
**Start a feature.** `/slice "CSV export"`. Before you write code, fill:
|
|
125
|
+
- `## Goal` — what ships and why;
|
|
126
|
+
- `## Definition of Done` — the acceptance criteria as **Given / When / Then** scenarios
|
|
127
|
+
([ADR-0011](adr/0011-slices-carry-a-definition-of-done.md));
|
|
128
|
+
- `## Design` — the approach, citing probe numbers not estimates;
|
|
129
|
+
- `## Verification` — the unit tests, or a `scripts/<slice>-verify.mjs` for behaviour a unit
|
|
130
|
+
test can't assert.
|
|
131
|
+
|
|
132
|
+
At merge, freeze `## As built` in past tense, confirming each Definition-of-Done scenario was
|
|
133
|
+
met. The linter requires both `## Verification` (R12) and a `## Definition of Done` holding a
|
|
134
|
+
real Given/When/Then triad (R13).
|
|
135
|
+
|
|
136
|
+
**Behaviour a unit test can't assert** (rendering, worldgen, physics, timing). Write
|
|
137
|
+
`scripts/<slice>-verify.mjs` that drives the real system headlessly, **fails on any console
|
|
138
|
+
error**, and writes artifacts (screenshots, numbers) for human review. Name it in
|
|
139
|
+
`## Verification` and **wire it into `package.json`** — an unwired verify script runs once and
|
|
140
|
+
is dead thereafter, so the linter fails if any is unwired (R11). ([ADR-0004](adr/0004-verification-harness-and-in-repo-invariants.md))
|
|
141
|
+
|
|
142
|
+
**Adopt into an existing repo.** `/init-method <repo> --apply`. Migrate the corpus and hand-
|
|
143
|
+
fix any red supersession pairs *before* the hook goes on. Legacy documents that predate a
|
|
144
|
+
rule warn rather than error, so adoption is never blocked by history.
|
|
145
|
+
|
|
146
|
+
**A rule you must break, with reason.** Don't take a silent exception. Record the
|
|
147
|
+
justification as a new or superseding ADR — in this workflow, a justified violation *is* a
|
|
148
|
+
decision, and decisions are immutable records.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Enforcement, in three honest layers
|
|
153
|
+
|
|
154
|
+
The linter ([`scripts/lint-docs.mjs`](scripts/lint-docs.mjs)) runs from the pre-commit hook
|
|
155
|
+
and in CI. Its rules are graded by what can actually be mechanised
|
|
156
|
+
([ADR-0003](adr/0003-enforcement-by-hook.md)):
|
|
157
|
+
|
|
158
|
+
1. **Machine-checked (error — blocks the commit).** Frontmatter shape and completeness
|
|
159
|
+
(R1), id/filename agreement and uniqueness (R2), closed status/type vocabulary (R3), the
|
|
160
|
+
supersession graph (R4–R7), ledger citations resolve (R8), the linter isn't pointed at an
|
|
161
|
+
empty corpus (R10), verify scripts are wired (R11), and slices carry their required
|
|
162
|
+
sections (R12/R13).
|
|
163
|
+
2. **Legacy-aware (warning, not error).** Bare prose cross-references (R9) and slice-section
|
|
164
|
+
rules on documents that predate them warn instead of failing, so a repo's history never
|
|
165
|
+
blocks its next commit — while *new* work is held to the full bar.
|
|
166
|
+
3. **Coverage — unenforceable, and said so.** Whether a verify script tests something *true*,
|
|
167
|
+
whether a §4 invariant actually holds, whether the acceptance scenarios are *complete* —
|
|
168
|
+
none can be decided by reading one version of a file. These are surfaced by `/wrap-up` for
|
|
169
|
+
a human read-through, never claimed as guaranteed. A linter that pretended to check them
|
|
170
|
+
would be a false green, the exact failure this project exists to prevent.
|
|
171
|
+
|
|
172
|
+
The dividing line is the whole point: **a rule is enforced, legacy-tolerated, or explicitly
|
|
173
|
+
declared uncheckable — never silently trusted.**
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Where things live
|
|
178
|
+
|
|
179
|
+
`/remember` routes a fact to whatever *governs* it, never to wherever the conversation
|
|
180
|
+
happened ([ADR-0005](adr/0005-write-routing-and-the-bounds-of-memory.md)):
|
|
181
|
+
|
|
182
|
+
| The fact governs… | Goes in |
|
|
183
|
+
|---|---|
|
|
184
|
+
| A codebase — invariants, architecture, definition-of-done | that repo's `CLAUDE.md` |
|
|
185
|
+
| How you work, everywhere | global `~/.claude/CLAUDE.md` |
|
|
186
|
+
| Open work — deferrals, defects, follow-ups | that repo's `LEDGER.md` |
|
|
187
|
+
| Operator-personal, cross-session facts (e.g. git identity) | assistant memory |
|
|
188
|
+
|
|
189
|
+
A rule that governs a codebase never belongs in assistant memory: memory is invisible to
|
|
190
|
+
every other reader of the repo, unversioned, and lost on a change of machine.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Design principles
|
|
195
|
+
|
|
196
|
+
- **Zero dependencies.** The whole kit is Node's standard library. It drops into any repo
|
|
197
|
+
regardless of package manager, and the frontmatter schema is small enough to parse by hand.
|
|
198
|
+
- **Enforce with a hook, remember nothing.** A convention that matters becomes a rule; one
|
|
199
|
+
that can't be checked is stated as such, out loud.
|
|
200
|
+
- **Grammar over toolchain.** Given/When/Then is adopted as *writing discipline*, not a test
|
|
201
|
+
framework — the verify scripts are the executable layer.
|
|
202
|
+
- **The diff is the audit trail.** Immutable records, generated indexes, and single-writer
|
|
203
|
+
ledgers mean the git log *is* the history — no hand-maintained changelog to drift.
|
|
204
|
+
|
|
205
|
+
For the reasoning behind any of these, read the ADR it links to. That is what the ADRs are
|
|
206
|
+
for.
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isonimus/stele",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A linted, installable ADR workflow for git projects using Claude as an assistant",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"adr",
|
|
8
|
+
"architecture-decision-record",
|
|
9
|
+
"claude",
|
|
10
|
+
"pre-commit",
|
|
11
|
+
"documentation",
|
|
12
|
+
"workflow",
|
|
13
|
+
"linter"
|
|
14
|
+
],
|
|
15
|
+
"author": "Iker Laforga",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+ssh://git@github.com/Isonimus/stele.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/Isonimus/stele#readme",
|
|
22
|
+
"bugs": "https://github.com/Isonimus/stele/issues",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"stele": "scripts/init-method.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"scripts/",
|
|
31
|
+
"templates/",
|
|
32
|
+
".claude/commands/",
|
|
33
|
+
".claude/hooks/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "node --test test/*.test.mjs",
|
|
42
|
+
"lint": "node scripts/lint-docs.mjs .",
|
|
43
|
+
"index": "node scripts/build-index.mjs .",
|
|
44
|
+
"scan": "node scripts/scan-legacy.mjs",
|
|
45
|
+
"migrate": "node scripts/migrate-adrs.mjs",
|
|
46
|
+
"init": "node scripts/init-method.mjs"
|
|
47
|
+
}
|
|
48
|
+
}
|