geneseed 2.0.0 → 2.1.0
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/CHANGELOG.md +109 -5
- package/DESIGN.md +54 -1282
- package/README.md +8 -10
- package/SETUP.md +15 -7
- package/SHIPPED.md +3 -3
- package/adapters/opencode/opencode.json +0 -9
- package/bin/geneseed.mjs +2 -2
- package/docs/declined.md +53 -2
- package/docs/design-history.md +1290 -0
- package/docs/extending.md +563 -0
- package/docs/limits.md +141 -82
- package/docs/web/agents.md +1 -1
- package/docs/web/rules.md +1 -1
- package/geneseed +0 -0
- package/harness.config.json +1 -1
- package/js/cli-table.json +1 -1
- package/js/doctor.mjs +67 -22
- package/js/inventory.mjs +2 -1
- package/js/lib/fs.mjs +6 -2
- package/js/mcp.mjs +3 -15
- package/js/render.mjs +6 -4
- package/js/settings.mjs +79 -1
- package/js/themes.mjs +1 -1
- package/js/tui.mjs +2 -2
- package/js/uninstall.mjs +5 -5
- package/js/web/actions.mjs +20 -4
- package/js/web/server.mjs +53 -2
- package/package.json +1 -1
- package/src/laws/universal.md +35 -0
- package/themes/_TEMPLATE.json +3 -0
- package/themes/biker.json +3 -0
- package/themes/commentator.json +3 -0
- package/themes/cyberpunk.json +3 -0
- package/themes/gamer.json +3 -0
- package/themes/imperial.json +3 -0
- package/themes/joker.json +3 -0
- package/themes/marvin.json +3 -0
- package/themes/mean.json +3 -0
- package/themes/military.json +3 -0
- package/themes/neutral.json +3 -0
- package/themes/pirate.json +3 -0
- package/themes/sports.json +3 -0
- package/themes/verstappen.json +3 -0
- package/themes/wizard.json +3 -0
- package/web/dist/assets/{About-DX1-VTBP.js → About-BYXlmbyo.js} +1 -1
- package/web/dist/assets/{Activity-Dp9NaXOn.js → Activity-us5MPf4u.js} +1 -1
- package/web/dist/assets/{ActivityDetail-HpOBtrXS.js → ActivityDetail-CiaQXUk2.js} +1 -1
- package/web/dist/assets/{Diff-Ct4G3Se8.js → Diff-Bh0qbZ2s.js} +1 -1
- package/web/dist/assets/{Doctor-DGjkZFuA.js → Doctor-BIlQME45.js} +1 -1
- package/web/dist/assets/{Graph-b8o-S6kv.js → Graph-4dYD1dPG.js} +1 -1
- package/web/dist/assets/Harnesses-PzYZ3LOC.js +1 -0
- package/web/dist/assets/Laws-DtZweXal.js +1 -0
- package/web/dist/assets/{Library-BEZscyaQ.js → Library-CjMbksJW.js} +1 -1
- package/web/dist/assets/{Markdown-k_LSLBGq.js → Markdown-Bk81c3aZ.js} +1 -1
- package/web/dist/assets/{Profile-DydukTxP.js → Profile-Curui_-j.js} +1 -1
- package/web/dist/assets/{Rules-CIRG4Ldt.js → Rules-Bm283wdv.js} +1 -1
- package/web/dist/assets/{Skills-Buoi1IBr.js → Skills-gruqYuhl.js} +1 -1
- package/web/dist/assets/{StatusBadge-CE_AJttl.js → StatusBadge-EiM_FPqv.js} +1 -1
- package/web/dist/assets/{Themes-CI4o3gJC.js → Themes-DU99j4Hr.js} +1 -1
- package/web/dist/assets/{activity-BCW7e6Cy.js → activity-0rvQ6Vra.js} +1 -1
- package/web/dist/assets/{index-B1PIx7RM.js → index-0b2hobOo.js} +1 -1
- package/web/dist/assets/index-D5VY3TY_.js +12 -0
- package/web/dist/assets/{index-Ck2iCdaC.js → index-IiGFmCW_.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/src/pages/Laws.jsx +3 -0
- package/web/dist/assets/Harnesses-gr3-aHBF.js +0 -1
- package/web/dist/assets/Laws-DTwF5tTC.js +0 -1
- package/web/dist/assets/index-BnTQUSUz.js +0 -12
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Extending — what it costs to change this tool
|
|
2
|
+
|
|
3
|
+
`docs/limits.md` is the standing answer to *"what does this tool not prove about itself?"*.
|
|
4
|
+
This file is its companion: **"what does it cost to add one thing?"** — one law, one skill, one
|
|
5
|
+
plugin, one hook verb, one doc page, one theme, one screen.
|
|
6
|
+
|
|
7
|
+
It exists because the answer is never one file. Every user-visible noun in Geneseed has
|
|
8
|
+
*satellites* — a themed title in fourteen voices, a class in a hand-written map, a count in a
|
|
9
|
+
badge, a row in a ledger, a page in the console — and the gates that keep them in step are spread
|
|
10
|
+
across `doctor`, the Node suite and four CI jobs. A contributor who knows the satellites ships in
|
|
11
|
+
one pass; one who does not discovers them one red gate at a time.
|
|
12
|
+
|
|
13
|
+
Written after the Python migration completed (2026-08-18), against `main`. Verified: every
|
|
14
|
+
file:line below was opened, and the commands were run.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 0 — The frame
|
|
19
|
+
|
|
20
|
+
Three seams, and almost every question about "where do I edit?" resolves to one of them.
|
|
21
|
+
|
|
22
|
+
| Seam | Source of truth | How it reaches a user | Rebuild needed |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| **Harness content** | `src/` (laws, agents, skills, memory/notebook conventions, `AGENT.md.tmpl`) + `themes/*.json` for voice | rendered into a bundle by `geneseed build` / `update` / `rebuild-all` | yes — every active install must be re-emitted |
|
|
25
|
+
| **Console docs** | `docs/web/*.md` + `docs/web/_groups.json` | read off disk at request time by `js/web/docs.mjs:101` | no — not even a daemon restart for content |
|
|
26
|
+
| **Console app** | `web/src/**` | built by vite into the **tracked** `web/dist/` | yes — rebuild *and commit* dist, then `geneseed web restart` |
|
|
27
|
+
|
|
28
|
+
Two rules follow from the table and they cause most of the surprises:
|
|
29
|
+
|
|
30
|
+
- **Nothing propagates by itself.** A bundle is a *render*. Editing `src/` changes nothing on any
|
|
31
|
+
machine until something re-emits (`js/emit.mjs:362` re-fingerprints, `js/status.mjs:80`
|
|
32
|
+
reports the drift).
|
|
33
|
+
- **`docs/web/` is data, `web/src/` is code.** A new documentation page is one file and no
|
|
34
|
+
rebuild. A new screen is a new hashed chunk and a mandatory `web/dist` commit.
|
|
35
|
+
|
|
36
|
+
### Counts are computed — never type one
|
|
37
|
+
|
|
38
|
+
`{N_LAWS}` `{N_AGENTS}` `{N_SKILLS}` `{N_PLUGINS}` are substituted at request time from the live
|
|
39
|
+
inventory (`js/web/docs.mjs:169-172`). Typing a literal number into a `docs/web` page is caught by
|
|
40
|
+
`proseMirrorProblems` (`js/doctor.mjs:715-737`). The README badges and prose, and the
|
|
41
|
+
`N laws, N agents, N skills` line in `SHIPPED.md`, are the hand-written exceptions — and each has
|
|
42
|
+
its own doctor arm (`js/doctor.mjs:676`, `:739`, `:822-829`).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 1 — The baseline, and the five commands that reproduce CI
|
|
47
|
+
|
|
48
|
+
Verified on this checkout: `doctor --all` green on 14 themes; the Node suite reports
|
|
49
|
+
**1033 tests, 1029 pass, 4 skipped, 0 fail**.
|
|
50
|
+
|
|
51
|
+
CI is **four jobs expanding to six runs** (`.github/workflows/ci.yml`): `validate` and
|
|
52
|
+
`node-cells` on Linux *and* Windows, plus `package-no-python` and `web` on Linux. From the repo
|
|
53
|
+
root — never from a worktree, see §5:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node bin/geneseed-cli.mjs doctor --all
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node --test --test-reporter=tap "tests/**/*.test.mjs"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node tests/golden.mjs --idempotent
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
node tests/golden.mjs --deletion
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
node tests/mutate.mjs --verify
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd web && npm ci && npm run lint && npm run format:check && npm test && npm run build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Notes that matter:
|
|
80
|
+
|
|
81
|
+
- The root `package.json` has **no `scripts` key**, and no pre-commit hook, Makefile or justfile
|
|
82
|
+
exists either. `npm test` at the root exits 1 with *Missing script: "test"*; the suite is run by
|
|
83
|
+
hand with the glob above.
|
|
84
|
+
- `--test-reporter=tap` is not decoration. Both workflows gate on the *count* (`# tests N ≥ 500`)
|
|
85
|
+
because `node --test` over a glob that matches nothing prints `# tests 0` and **exits 0**. Node's
|
|
86
|
+
default reporter changed between majors, which is how a green 1043-test suite once reported zero.
|
|
87
|
+
- `--deletion` is the only thing that exercises the write-before-delete prune. Anyone renaming or
|
|
88
|
+
removing a plugin, a skill or an emitted file needs it.
|
|
89
|
+
- **A local pass is weaker than CI.** ~18 tests in `tests/unit/package_manifest.test.mjs` carry
|
|
90
|
+
`{ skip: NO_NPM }` (`:83`) — every tarball, install and rename gate. On a machine without `npm`
|
|
91
|
+
on PATH they report *skipped* and the run is green.
|
|
92
|
+
- CI additionally re-runs the packaging suite under `npm@latest` (Linux, last in the job) and
|
|
93
|
+
packs the tarball into a `node:22-slim` container with no interpreter. Neither is reproducible
|
|
94
|
+
from the five commands; the container one is reachable locally through
|
|
95
|
+
`tests/helpers/no-python-container.sh` with a docker daemon.
|
|
96
|
+
- **Two assertions in `tests/unit/claude.test.mjs` silently stand down** on any machine where an
|
|
97
|
+
ancestor of the temp sandbox is a `.claude`/`.bob` install — which is every developer machine
|
|
98
|
+
with Claude Code installed under `~`. They are replaced by a `t.diagnostic`, so the TAP
|
|
99
|
+
`# skipped` count does **not** move. Locally you read `1035/1031/0/4` and see no sign of it.
|
|
100
|
+
|
|
101
|
+
### The gates CI does not run
|
|
102
|
+
|
|
103
|
+
- **The mutation matrix itself.** `tests/mutate.mjs` — 31 rows, each a one-string edit to a
|
|
104
|
+
product file with a written argument for which gate should catch it. The full run is **in no
|
|
105
|
+
workflow** and should not be: it mutates the product tree 31 times and runs a gate per edit.
|
|
106
|
+
Run `node tests/mutate.mjs --all` (or `--only M7`) by hand when you change something a gate is
|
|
107
|
+
supposed to be watching. ⚠ An interrupted run leaves the **product tree mutated** — check
|
|
108
|
+
`git status` before doing anything else.
|
|
109
|
+
Its *anchors* are now in CI (`--verify`, above): a row whose anchor a refactor has moved counts
|
|
110
|
+
as a **survivor**, not a skip, and until 2026-08-19 that only showed up for whoever ran the
|
|
111
|
+
matrix by hand — which nothing did.
|
|
112
|
+
- **Node 24.** No CI job runs it; `publish.yml` does. See §8.
|
|
113
|
+
- **`tests/golden.mjs`** is absent from the publish path entirely.
|
|
114
|
+
|
|
115
|
+
### Gates that fire on changes you would not expect to be gated
|
|
116
|
+
|
|
117
|
+
- **A new subprocess anywhere in `js/` or `bin/`** — `tests/unit/spawn_hygiene.test.mjs` requires
|
|
118
|
+
capturing spawns to set `windowsHide` and inheriting spawns not to. (`adapters/` is *not*
|
|
119
|
+
scanned, so a plugin that spawns is on its own.)
|
|
120
|
+
- **Editing `.github/workflows/*.yml`** — seven structural assertions in
|
|
121
|
+
`tests/unit/docs.test.mjs:336-399` gate `publish.yml`'s shape, and `:422-444` discovers every
|
|
122
|
+
workflow with a test-count gate and pins `--test-reporter=tap` on it. A unit test gates the CI
|
|
123
|
+
files.
|
|
124
|
+
- **Adding any tracked file** — `tests/unit/package_manifest.test.mjs:401` demands every tracked
|
|
125
|
+
path fall on exactly one side of a declared partition, *with a written reason*. Adding this very
|
|
126
|
+
document required a `SHIPS` row; without one the tarball test fails, and it fails **before** the
|
|
127
|
+
file is committed too, because npm packs by glob while the partition reads `git ls-files`.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 2 — Adding a LAW
|
|
132
|
+
|
|
133
|
+
Ten files, of which five are pure satellites. Ground truth: `git show --stat 1b53701` (law XXXVIII)
|
|
134
|
+
touched 21 files, 14 of them themes.
|
|
135
|
+
|
|
136
|
+
1. `src/laws/universal.md` — **append** `### {{LAW}} <roman> — {{LEX_<roman>}}` plus the body.
|
|
137
|
+
Never insert: 397 `{{LAW}} <numeral>` cross-references live in `src/`, plus hardcoded citations
|
|
138
|
+
in `js/hooks.mjs` and two OpenCode plugins, and **nothing resolves a cross-reference against the
|
|
139
|
+
canon**. Renumbering silently rewires every one of them.
|
|
140
|
+
2. `themes/_TEMPLATE.json` — add `LEX_<roman>` in template order. **Nothing gates this file**
|
|
141
|
+
(`themeFiles()` filters `_`-prefixed names, `js/installs.mjs:113`), so forgetting it is green
|
|
142
|
+
today and starves the *next* law's sync.
|
|
143
|
+
3. Seed the fourteen themes:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
node bin/geneseed.mjs --sync-themes
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
⚠ Four spellings of this command are in circulation and two of them fail — `doctor`'s own tip
|
|
150
|
+
is one of the two. See §7.2 before you copy a command from anywhere else.
|
|
151
|
+
4. Restyle all fourteen by hand. `--sync-themes` writes the template's `<themed title for …>`
|
|
152
|
+
placeholder and only *prints* `RESTYLE these`; a shipped placeholder passes every gate.
|
|
153
|
+
5. `js/inventory.mjs:40` — `LAW_CLASS`, one of the six in `LAW_CLASSES` (`:56`).
|
|
154
|
+
6. `web/src/pages/Laws.jsx:28` — `LAW_META`: same class, plus the one-line Principle. **This copy
|
|
155
|
+
exists nowhere else**; a law absent here renders with a blank description. It is why that one
|
|
156
|
+
React file ships alone out of `web/src` in `package.json` — `doctor` regex-parses its literal.
|
|
157
|
+
7. `README.md` — badge `badge/laws-N` and the `N universal laws` sentence.
|
|
158
|
+
8. `SHIPPED.md` — the `N laws, N agents, N skills` triple.
|
|
159
|
+
9. `tests/unit/harness.test.mjs:293` — the negative fixture hardcodes the *next* free numeral;
|
|
160
|
+
bump it or it stops being a fixture and starts being a duplicate.
|
|
161
|
+
10. `CHANGELOG.md`, then rebuild and commit `web/dist` (step 6 touched `web/src`).
|
|
162
|
+
|
|
163
|
+
**Amending** a law is steps 1, 10 and — if the principle moved — 6. Two extra cautions:
|
|
164
|
+
|
|
165
|
+
- `--footprint` defaults to **lean**, and lean keeps only the heading plus the first sentence
|
|
166
|
+
(`js/render.mjs:167`). A clause added to the second paragraph is invisible in the default build.
|
|
167
|
+
Amend the first sentence, or the amendment does not exist for most installs.
|
|
168
|
+
- ⚠ **Law I is frozen.** `LEX_I` is one of seven theme keys recorded byte-for-byte in the
|
|
169
|
+
primitives corpus (§5). Retitling Law I in any theme reddens a snapshot that cannot be re-blessed.
|
|
170
|
+
|
|
171
|
+
The carrier itself is gated: `tests/unit/emit_smoke.test.mjs:211-265` checks every law reaches
|
|
172
|
+
every one of nine emit modes in order and themed, under both footprints, with a ceiling **and** a
|
|
173
|
+
floor on the rendered size. Headroom measured today: ~2 100 chars full, ~1 200 lean — about three
|
|
174
|
+
average laws at full footprint, which is the binding one.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 3 — Adding a SKILL or an AGENT
|
|
179
|
+
|
|
180
|
+
**Order matters at the first two steps.** `missingReferencedSpecs` (`js/emit.mjs:493`) *aborts the
|
|
181
|
+
build* when `AGENT.md.tmpl` names a spec with no file — so the file comes before the table row, or
|
|
182
|
+
nothing builds at all. The refusal is clean: `tests/unit/emit_gates.test.mjs:265` proves a refused
|
|
183
|
+
emit leaves the prior install intact.
|
|
184
|
+
|
|
185
|
+
1. `src/skills/<name>.md` or `src/agents/<name>.md`. The shape is load-bearing: `# {{SKILL}}: <name>`,
|
|
186
|
+
a blank line, then a `>` blockquote holding `{{DESC_<NAME>}}` — title, blockquote, nothing
|
|
187
|
+
between. `doctor` checks both the presence of the purpose line and that the blockquote is the
|
|
188
|
+
*first* block (`js/doctor.mjs:899-908`).
|
|
189
|
+
2. One row in the hand-authored table in `src/AGENT.md.tmpl` (agents `125-141`, skills `183-229`).
|
|
190
|
+
Gated both ways: a row with no file, and a file with no row.
|
|
191
|
+
3. `DESC_<NAME>` in **all fourteen** themes. Theme parity catches a missing one; nothing catches
|
|
192
|
+
an unstyled placeholder.
|
|
193
|
+
4. **Skills only** — `SKILL_CLASS` in `js/inventory.mjs:59`. Gated both ways
|
|
194
|
+
(`js/doctor.mjs:785-791`), which matters because the runtime fallback is silent: an unclassified
|
|
195
|
+
skill renders as `build` (`js/inventory.mjs:166`).
|
|
196
|
+
5. `registry.json` — a row keyed `skills/<name>` or `agents/<name>` with `status` ∈
|
|
197
|
+
{`experimental`, `approved`, `deprecated`}, a semver `version`, a non-blank `owner`, an ISO
|
|
198
|
+
`added`, and `last_verified: ""`. **This is the most forgettable step**: four hand-typed fields,
|
|
199
|
+
and the gate fires in both directions (`js/doctor.mjs:388-436`).
|
|
200
|
+
6. `README.md` — the badge, the `**🤖 Agents** (N)` / `**🛠 Skills** (N)` count, and for a skill the
|
|
201
|
+
`·`-separated name in the 🛠 row (that enumeration is gated two ways, unlike the laws' one).
|
|
202
|
+
7. `SHIPPED.md` — the `N laws, N agents, N skills` triple.
|
|
203
|
+
8. `doctor --all`, then the suite.
|
|
204
|
+
|
|
205
|
+
**A vendored skill folder** (`src/skills/<name>/SKILL.md`) is a different shape: add it to
|
|
206
|
+
`VENDORED_SKILL_DIRS` (`js/native.mjs:37` — the literal is frozen in
|
|
207
|
+
`tests/unit/authoring_gates.test.mjs:312`), write a `VENDOR.md` carrying `**Upstream:**`,
|
|
208
|
+
`**Commit:**` (a 40-hex commit, never a moving branch) and `**License:**`, add the registry row —
|
|
209
|
+
and **skip steps 2, 4, 6, 7**: the flat-spec globs only see `*.md` at the top level.
|
|
210
|
+
|
|
211
|
+
Two traps worth knowing before you write the spec:
|
|
212
|
+
|
|
213
|
+
- **The word "Read-only" anywhere in an agent spec locks its emit.** It is a bare substring test
|
|
214
|
+
(`js/native.mjs:148`). The escape hatch is an explicit `<!-- bash: allow -->` marker, which
|
|
215
|
+
re-opens bash on all three hosts.
|
|
216
|
+
- **The console has a fourth, ungated copy of the skill taxonomy.** `SKILL_CATS` in
|
|
217
|
+
`web/src/pages/Skills.jsx:19` mirrors `SKILL_CLASS` with nothing comparing them (contrast
|
|
218
|
+
`LAW_META`, which *is* cross-checked). Inventing a new *category* means editing that file —
|
|
219
|
+
which drags in the `web/dist` rebuild — and an unknown category renders as Build with no warning.
|
|
220
|
+
|
|
221
|
+
Adding a spec reddens no recording: none of the surviving snapshots contains a skill or agent name.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 4 — Adding a PLUGIN, a HOOK verb, or a doc page
|
|
226
|
+
|
|
227
|
+
### 4a — An OpenCode plugin
|
|
228
|
+
|
|
229
|
+
The `geneseed-` prefix and the `.js` suffix are mechanically load-bearing: the badge count, the
|
|
230
|
+
`{N_PLUGINS}` substitution and the four-way gate all key off that glob.
|
|
231
|
+
|
|
232
|
+
1. `adapters/opencode/plugins/geneseed-<name>.js` — ESM, zero dependencies. The factory-export
|
|
233
|
+
convention is real (OpenCode requires it) but **ungated**; the only generic gate on plugin
|
|
234
|
+
source is `node --check` (`js/doctor.mjs:924-941`).
|
|
235
|
+
2. `docs/web/plugin-<name>.md` — `group: plugins`, `title: "geneseed-<name>"`, `kind: "concept"`.
|
|
236
|
+
3. `docs/web/plugins.md` — a bullet. The count above it renders from `{N_PLUGINS}`.
|
|
237
|
+
4. `docs/web/model.md` — a second `{N_PLUGINS}` sentence whose **enumeration of the capabilities
|
|
238
|
+
is hand-written and ungated**.
|
|
239
|
+
5. `README.md` — **three** sites: the badge, the `N plugins` prose, and the 🔌 Plugins row.
|
|
240
|
+
6. `SHIPPED.md` — the bare name inside `plugins (…)`; that list must equal the directory exactly,
|
|
241
|
+
in both directions (`tests/unit/web_api.test.mjs:1655`).
|
|
242
|
+
7. `adapters/opencode/README.md` and `docs/opencode-plugin-setup.md` — spelled-out counts, in
|
|
243
|
+
**word form** ("seven" → "eight"), the second file twice.
|
|
244
|
+
8. `tests/plugins/<name>.test.mjs`. Auto-discovered; no workflow edit.
|
|
245
|
+
|
|
246
|
+
No `registry.json` row, no `package.json` edit — `files[]` ships `adapters/` whole.
|
|
247
|
+
|
|
248
|
+
### 4b — A hook verb (claude + bob)
|
|
249
|
+
|
|
250
|
+
⚠ **Read §5 first. A fifth hook verb has no green path today.**
|
|
251
|
+
|
|
252
|
+
The mechanical part: implement `cmd<Verb>` in `js/hooks.mjs`; wire the command string in
|
|
253
|
+
`claudeHookGroups` (`js/settings.mjs:434`, spelled `` `${run} <verb> …` `` — the gate scrapes that
|
|
254
|
+
shape); add the entry to `bin/geneseed-hook.mjs:43` `VERBS` with **exactly two-space indent**; add
|
|
255
|
+
a row to `js/cli-table.json` with a help string over 20 characters.
|
|
256
|
+
|
|
257
|
+
### 4c — A documentation page
|
|
258
|
+
|
|
259
|
+
**One file, no rebuild, no server change.** `docs/web/<id>.md`, where the stem is the id.
|
|
260
|
+
Frontmatter is JSON-per-value, not YAML (`js/web/docs.mjs:63-82`):
|
|
261
|
+
|
|
262
|
+
- `group` must match an id in `_groups.json` — an unknown group is a **silent skip**.
|
|
263
|
+
- `kind` ∈ `markdown | concept | glossary | about | cli`; anything else 404s.
|
|
264
|
+
- `harness: "claude"|"opencode"` hides the page from the other host. Note the `plugins` group
|
|
265
|
+
carries this at the **group** level, and `hooks` symmetrically — a Claude-relevant page dropped
|
|
266
|
+
into `plugins` vanishes with no error.
|
|
267
|
+
- `kind: "markdown"` pulls from a repo file via `source:`, traversal-guarded, optionally sliced to
|
|
268
|
+
one section with `anchor:` + `slice: true`.
|
|
269
|
+
|
|
270
|
+
Inline `<!-- harness:claude -->` blocks strip per host and **fail open** on an unbalanced marker —
|
|
271
|
+
the page renders, wrong, with nothing red.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 5 — The four things that are genuinely hard
|
|
276
|
+
|
|
277
|
+
These are not checklist items. They are structural, and each one should be settled *before* the
|
|
278
|
+
change that needs it, not during.
|
|
279
|
+
|
|
280
|
+
### 5.1 ✅ A fifth hook verb — resolved 2026-08-19
|
|
281
|
+
|
|
282
|
+
**This was a hard blocker and it is now open.** Recorded here because the shape of the problem
|
|
283
|
+
recurs: any gate whose expected value lives in an artifact nothing can re-make will eventually
|
|
284
|
+
stop a legitimate change, and the fix is never to delete the artifact.
|
|
285
|
+
|
|
286
|
+
`tests/unit/hook_cli.test.mjs` asserted a strict equality between the hook verbs the frozen
|
|
287
|
+
matrix covers and the verbs `bin/geneseed-hook.mjs` carries. The CLI half of the same test
|
|
288
|
+
already exempted a named `NATIVE` list; the hook half exempted nothing — so a fifth hook verb
|
|
289
|
+
failed with **no green path**, since the recorder died on 2026-08-17 and the failure message
|
|
290
|
+
correctly forbids deleting cells to pass.
|
|
291
|
+
|
|
292
|
+
Both halves now filter through the same `NATIVE` list, and the reverse check refuses an
|
|
293
|
+
exemption for a verb neither entry point carries. The argument is in `docs/declined.md` — the
|
|
294
|
+
short version is that the reason those verbs have no cell (*the reference had nothing to compare
|
|
295
|
+
against*) is the same reason a verb postdating the reference has none, so it is one exemption,
|
|
296
|
+
not two. Nothing measurable was lost: the hook verb set is pinned twice more, from editable
|
|
297
|
+
sources, twelve lines above.
|
|
298
|
+
|
|
299
|
+
**To add a hook verb today:** implement `cmd<Verb>` in `js/hooks.mjs`, wire the string in
|
|
300
|
+
`claudeHookGroups`, add the `VERBS` entry, add the `js/cli-table.json` row, then name the verb in
|
|
301
|
+
`NATIVE` in **both** `tests/unit/hook_cli.test.mjs` and `tests/snapshot/cli_help.test.mjs` (they
|
|
302
|
+
read each other's source, so one alone fails) and give it an absolute unit gate.
|
|
303
|
+
|
|
304
|
+
### 5.2 ⚠ Seven theme keys are frozen byte-for-byte
|
|
305
|
+
|
|
306
|
+
`tests/__snapshots__/primitives/{posix,win32}.json` record `theme_preview` and `theme_flair`
|
|
307
|
+
answers for all fourteen themes **and the template**. Those probes read the live `themes/*.json`
|
|
308
|
+
at replay time, which pins:
|
|
309
|
+
|
|
310
|
+
| Probe | Frozen keys |
|
|
311
|
+
|---|---|
|
|
312
|
+
| `theme_preview` | `TAGLINE`, `LOADED_SIGIL`, `VOICE`, **`LEX_I`**, `BENEDICTION` |
|
|
313
|
+
| `theme_flair` | `ACCENT`, `BANNER` |
|
|
314
|
+
|
|
315
|
+
Editing any of those, in any theme, reddens a snapshot with **no re-bless path**. Adding a *new*
|
|
316
|
+
law is safe (only `LEX_I` is recorded). Restyling an existing theme's tagline or banner is not.
|
|
317
|
+
|
|
318
|
+
This was proven, not inferred: prefixing `marvin.json`'s `TAGLINE` turned
|
|
319
|
+
`tests/snapshot/pure_snapshot.test.mjs` red at `case 1275`.
|
|
320
|
+
|
|
321
|
+
### 5.3 ⚠ The hook shim is machine-wide and last-writer-wins
|
|
322
|
+
|
|
323
|
+
`~/.geneseed/bin/geneseed-hook[.cmd]` has no per-install component. The last checkout to emit
|
|
324
|
+
anything owns **every** install's hooks. Emitting from a git worktree or a temp copy used to
|
|
325
|
+
repoint the shim at that disposable path; removing the worktree then killed hooks for every
|
|
326
|
+
install on the machine.
|
|
327
|
+
|
|
328
|
+
**`hookPrefix` now refuses the claim** (`js/settings.mjs`, `ephemeralCheckout`): an emit whose
|
|
329
|
+
checkout is under the OS temp root, or is a linked git worktree (`.git` is a *file*), leaves a
|
|
330
|
+
shim that still resolves exactly as it is and wires the emitted hooks to it anyway. The bundle it
|
|
331
|
+
writes is byte-identical to a normal emit — the install simply keeps running the durable
|
|
332
|
+
checkout's entry, which is what last-writer-wins already handed every other install. A shim that
|
|
333
|
+
is **absent or already dead** is still written from anywhere, so a first emit on a fresh machine
|
|
334
|
+
works and the suite's own sandboxed emits are unchanged.
|
|
335
|
+
|
|
336
|
+
Two gates hold it: `tests/unit/hook_form.test.mjs` pins the rule (including the worktree arm,
|
|
337
|
+
reachable because `ephemeralCheckout` takes the temp root as a parameter), and
|
|
338
|
+
`tests/shim_intact.mjs` — a step in `ci.yml` and `publish.yml`, *after* `node --test`, because
|
|
339
|
+
each test file runs in its own process and the file that causes this is not the file that would
|
|
340
|
+
notice — fails if the suite left the shim naming anything that is not there.
|
|
341
|
+
|
|
342
|
+
This is not hypothetical — it happened while this file was being written. Several agents were
|
|
343
|
+
verifying gates out of copied checkouts (the `copyCheckout` fixture pattern
|
|
344
|
+
`tests/unit/harness.test.mjs:173` uses), one of them emitted, and the shim ended up pointing at
|
|
345
|
+
`…/Temp/gs-fix-8DYuJP/bin/geneseed-hook.mjs` — a directory that no longer existed. Every hook in
|
|
346
|
+
every install on the machine was dead and nothing said so.
|
|
347
|
+
|
|
348
|
+
The recovery is free, which is the part worth remembering: **`doctor` detects it and the run's own
|
|
349
|
+
emit repairs it**, reporting *"the checkout most likely moved … no further action needed"*. That
|
|
350
|
+
is still the answer for the case the guard cannot cover — a real checkout that genuinely moved —
|
|
351
|
+
so after any session that emitted from somewhere unusual, `doctor --all` from the real checkout
|
|
352
|
+
remains the cheapest thing to run.
|
|
353
|
+
|
|
354
|
+
Related: a stray `console.log` on a hook path silently disables a gate. Hooks signal through
|
|
355
|
+
stdout JSON and return 0 on every path — a printed byte is not a warning, it is a disabled gate.
|
|
356
|
+
|
|
357
|
+
### 5.4 ⚠ A snapshot red is a finding, not a step
|
|
358
|
+
|
|
359
|
+
Everything left under `tests/__snapshots__/` — the primitives, the 26 help texts, and the small
|
|
360
|
+
pure-function recordings — was produced by an implementation that no longer exists. There is no
|
|
361
|
+
`--record`. A change that moves a recorded byte leaves two honest options: revert it, or argue in
|
|
362
|
+
writing (in `docs/declined.md`) that the recording was wrong. `docs/limits.md` carries the full
|
|
363
|
+
argument and the measurement that licensed retiring the three larger corpora.
|
|
364
|
+
|
|
365
|
+
**A worked example, from the session that wrote this file.** Closing a real defect — three count
|
|
366
|
+
gates that go green when you *delete* the sentence they check — turned the primitives corpus red
|
|
367
|
+
at `case 371`: `proseMirrorProblems` is a recorded pure function, the recording holds a case that
|
|
368
|
+
passes an empty `shipped` string and expects `[]`, and the new absence arm answered otherwise.
|
|
369
|
+
Both obvious moves are wrong. Re-blessing is impossible. Abandoning the fix leaves a gate failing
|
|
370
|
+
open. The third move is the one to reach for: **the presence check moved up one level, into
|
|
371
|
+
`countTableProblems`, which is not recorded** — where it reads better anyway, beside the code that
|
|
372
|
+
already knows whether the file could be opened. The rule that falls out: *when a frozen recording
|
|
373
|
+
blocks a change, look for the nearest caller that is not frozen.* Check with
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
node -e "const j=require('./tests/__snapshots__/primitives/win32.json');console.log([...new Set(j.cases.map(c=>c.fn))].join('\n'))"
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The concrete consequence for evolution: **editing the help text or the flags of any of the 26
|
|
380
|
+
recorded CLI verbs is a frozen surface.** New verbs have a path (`NATIVE`); changes to old ones
|
|
381
|
+
do not. `docs/limits.md:311-316` names this cluster as the one open standing decision.
|
|
382
|
+
|
|
383
|
+
The CLI surface is the most expensive thing in the repo to change, because four hand-written
|
|
384
|
+
equalities converge on it: `js/cli-table.json`'s 29-command count (`tests/unit/cli_table.test.mjs:67`),
|
|
385
|
+
the 5-entry hidden-argument list (`tests/snapshot/cli_help.test.mjs:219`), the 25-subparser count
|
|
386
|
+
(`tests/unit/docs.test.mjs:193`), and the 26 unblessable help fixtures. Plus
|
|
387
|
+
`tests/unit/docs.test.mjs:554`, which validates every fenced `geneseed <verb> <flags>` line in
|
|
388
|
+
`README.md`, `SETUP.md`, `QUICKSTART.md` and `SHIPPED.md` against the table.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 6 — Hand-maintained, therefore rotting
|
|
393
|
+
|
|
394
|
+
Each of these is a copy of a decision that lives somewhere else, with no gate on its *accuracy* —
|
|
395
|
+
only, at best, on its presence.
|
|
396
|
+
|
|
397
|
+
| What | Where | Gated on |
|
|
398
|
+
|---|---|---|
|
|
399
|
+
| Per-law Principle lines | `web/src/pages/Laws.jsx` | presence and class only, never accuracy |
|
|
400
|
+
| 560 themed law titles (40 × 14) | `themes/*.json` | key presence only — a shipped placeholder is green |
|
|
401
|
+
| The README keyword enumerations | `README.md`, `docs/web/rules.md` | nothing |
|
|
402
|
+
| The plugin capability enumeration | `docs/web/model.md` | nothing (the number substitutes; the list does not) |
|
|
403
|
+
| Section labels and page subtitles | `web/src/lib/sections.js`, `web/src/pages/Docs/index.jsx` | nothing |
|
|
404
|
+
| `THEME_BLURBS` (8 of 14), `ART` (8 of 14) | `js/setup.mjs`, `js/anim.mjs` | nothing — missing entries fall back silently |
|
|
405
|
+
| `LOADED_SIGIL` uniqueness | `themes/*.json` | nothing, and it is load-bearing for theme detection |
|
|
406
|
+
| `adapters/claude-code/settings.json` | — | nothing compares it to what the emitter writes; **it is already divergent** |
|
|
407
|
+
| `NATIVE`, twice | two test files | they check each other, which is the gate that makes the duplication safe |
|
|
408
|
+
| The emit-size `CEILING` | `tests/unit/emit_smoke.test.mjs:54` | a hand-transcribed measurement |
|
|
409
|
+
| Stale docblock counts | `js/themes.mjs:62` ("137 values" → 140), `js/render.mjs:116` ("145 tokens" → 148) | nothing |
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 7 — Stale on 2026-08-18, fixed on 2026-08-19
|
|
414
|
+
|
|
415
|
+
Nine items were found while writing this file, and fixed the next day. They are recorded as a
|
|
416
|
+
list rather than left as a diff, because the PATTERN is the useful part: every one was a document
|
|
417
|
+
or a comment pointing a reader at something that is not there, and in each case the gate that
|
|
418
|
+
should have caught it either did not cover that file — or was itself the frozen copy of a wrong
|
|
419
|
+
answer.
|
|
420
|
+
|
|
421
|
+
**Fixed.**
|
|
422
|
+
|
|
423
|
+
1. **`DESIGN.md` taught the deleted Python build.** Its header promises *"the spec behind the
|
|
424
|
+
harness — read this before changing structure"*, and under it sat the six-edit checklist for
|
|
425
|
+
adding a Law, naming a deleted generator, `rituals/_harness_tui.py` for `LAW_CLASS`, and an
|
|
426
|
+
invocation of a program that no longer exists. Worse, `tests/unit/no_python.test.mjs` exempted
|
|
427
|
+
the file as a *historical record* — so the gate that hunts pointers-to-nothing was looking away
|
|
428
|
+
from the one document most likely to hold one. **Split**: the 190-line standing contract keeps
|
|
429
|
+
the name and is now scanned; the 1,290-line port narrative moved to
|
|
430
|
+
[design-history.md](design-history.md), which is exempt on the merits, and its dead links were
|
|
431
|
+
repaired because a link is a pointer whatever the prose around it says.
|
|
432
|
+
2. **`doctor` printed a command that errors.** The theme-parity tip named
|
|
433
|
+
`./geneseed build --sync-themes`; `cmdBuild` forwards `--theme` and nothing else. The comment
|
|
434
|
+
above the tip argued that spelling on a premise the port had quietly stopped satisfying. It now
|
|
435
|
+
names `geneseed-build --sync-themes`, which is what README and SETUP said all along.
|
|
436
|
+
3. **User-facing output named a program that does not exist.** `tui` and `uninstall` told users to
|
|
437
|
+
run `harness setup` / `harness uninstall` — the reference's name, dropped in P0. Five sites.
|
|
438
|
+
One of them was **pinned verbatim by a test**, so a gate was holding the wrong string in place;
|
|
439
|
+
that assertion is now a property — every backticked command a refusal offers must be a verb
|
|
440
|
+
`js/cli-table.json` declares — which is strictly stronger and fires on the next rename too.
|
|
441
|
+
4. **The `tui` help promised a "full-screen curses control panel"** for a verb that returns 1 on
|
|
442
|
+
every path. `docs/limits.md` listed this as permanently unfixable, on the grounds that the help
|
|
443
|
+
corpus is frozen — but `formatHelp` never reads `cmd.help`, so it was an ordinary edit all
|
|
444
|
+
along. The limits row went with the fix.
|
|
445
|
+
5. **Numbers in docblocks that had drifted** — `_TEMPLATE.json`'s "137 values" (140), "the 145
|
|
446
|
+
tokens" `src/` uses (148), and "sixteen `_*_problems` checks" (fifteen — the sixteenth was
|
|
447
|
+
named as `cli`, which was removed).
|
|
448
|
+
6. **A claim that was simply false.** `js/render.mjs` said `--theme` is validated against
|
|
449
|
+
`choices` before anything renders, which is why its own refusal read as an unreachable nicety.
|
|
450
|
+
It is not in `choicesFor`; that refusal is the only one there is, on every path.
|
|
451
|
+
7. **Five dead cross-references** to `tests/snapshot/no_python_in_corpus.test.mjs`, retired with
|
|
452
|
+
the corpora — three test files justified their own design by pointing at a gate that is gone.
|
|
453
|
+
Plus a skip message naming a `record-corpus` CI job that no longer exists.
|
|
454
|
+
8. **`CHANGELOG.md` said 2.0.0 was "not yet published"**, with "no tag, no release and no install
|
|
455
|
+
URL"; `geneseed@2.0.0` has been on npm since 2026-08-17. And **`SETUP.md`** documented
|
|
456
|
+
`geneseed upgrade v1.0.0 # pin to a tag`, which `js/update.mjs` prints is IGNORED.
|
|
457
|
+
9. **`docs/web/agents.md`** enumerated 16 agents and omitted `developer`, where README says 17 —
|
|
458
|
+
the doctor arm cannot fire, because the page spells `{N_AGENTS}` and that arm only ever catches
|
|
459
|
+
a literal. **`docs/web/rules.md`** called each law "a short markdown file under `src/laws/`";
|
|
460
|
+
there is one file.
|
|
461
|
+
|
|
462
|
+
**Still open.**
|
|
463
|
+
|
|
464
|
+
- **`adapters/claude-code/settings.json` is divergent documentation.** It writes
|
|
465
|
+
`node bin/geneseed-hook.mjs <verb>` on seven lines, including one the emitter does not produce
|
|
466
|
+
at all, and carries none of the flags `claudeHookGroups` emits. Nothing compares the two; the
|
|
467
|
+
only reference to it anywhere is the error message that sends users to it. Either gate it
|
|
468
|
+
against the emitter, or delete it.
|
|
469
|
+
- **`AGENT_LESSON_PROMPT` has no drift gate.** Its sibling `LEARN_PROMPT_HEAD` is checked for
|
|
470
|
+
extractability; this one's parity test was retired. Reformat the declaration in
|
|
471
|
+
`geneseed-learn.js` and the per-agent lesson prompt silently degrades to a two-line fallback.
|
|
472
|
+
- **The fenced-command gate still covers only four documents.** `tests/unit/docs.test.mjs`
|
|
473
|
+
validates every `geneseed <verb> <flags>` line against `js/cli-table.json` — but only in
|
|
474
|
+
`README.md`, `SETUP.md`, `QUICKSTART.md` and `SHIPPED.md`. Adding `DESIGN.md` and the `docs/`
|
|
475
|
+
tree to that array is one line, and it would have caught item 2 on the day it was written.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## 8 — Release
|
|
480
|
+
|
|
481
|
+
Version lives in **`harness.config.json`** (the owner) and is mirrored into `package.json` — both
|
|
482
|
+
gated. Every other appearance is derived at runtime (`.geneseed-version` markers,
|
|
483
|
+
`agent-overrides.json` `_version`), and gated as a *relationship* rather than a literal, which is
|
|
484
|
+
why no test pins a version string and none should.
|
|
485
|
+
|
|
486
|
+
Do not run `npm version`. Bump the two files by hand, add the `CHANGELOG` section, update
|
|
487
|
+
`SHIPPED.md`, rebuild `web/dist` if `web/src` moved, run the five commands from §1, push, tag,
|
|
488
|
+
then rehearse and fire the workflow:
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
gh workflow run publish.yml -f dry_run=true
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Two structural facts worth carrying:
|
|
495
|
+
|
|
496
|
+
- **`publish.yml` deliberately runs a newer toolchain than `ci.yml`** — Node 24 and `npm@latest`,
|
|
497
|
+
because trusted publishing requires them, against `ci.yml`'s Node 22. So toolchain-sensitive
|
|
498
|
+
defaults break *first and only* at the release. That has now happened twice: `npm pack --json`
|
|
499
|
+
changed its container between majors, and Node 24's default test reporter made the count gate
|
|
500
|
+
read zero on a green suite.
|
|
501
|
+
- **Renaming `publish.yml` breaks publishing silently** — npm keys the trusted publisher on the
|
|
502
|
+
filename.
|
|
503
|
+
|
|
504
|
+
Both known breaks have been patched at the symptom (`ci.yml` re-runs the packaging suite under
|
|
505
|
+
`npm@latest`; both workflows pin the TAP reporter). The *cause* is untouched: **no CI job runs
|
|
506
|
+
Node 24 at all**, so the release still executes a runtime nothing exercises — and `publish.yml`
|
|
507
|
+
never runs `tests/golden.mjs`, so the two self-comparisons are absent from the release path.
|
|
508
|
+
|
|
509
|
+
A sweep-and-replace version bump must not touch `registry.json`'s per-entity `"version": "1.0.0"`
|
|
510
|
+
rows, nor `web/package.json`.
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 9 — How an existing install picks up a change
|
|
515
|
+
|
|
516
|
+
| Install shape | Command | What it refreshes |
|
|
517
|
+
|---|---|---|
|
|
518
|
+
| git checkout | `geneseed upgrade` | export improvements → preflight → ff-only pull → `doctor` on the pulled tree → re-emit this bundle → bounce the web daemon → `rebuild-all` |
|
|
519
|
+
| npm | `npm install -g geneseed@latest`, then `geneseed rebuild-all` | there is no `postinstall`; the two steps are manual |
|
|
520
|
+
| any | `geneseed rebuild-all` | every **active** install, each re-read in its own five values (theme, emit, footprint, posture, mode) |
|
|
521
|
+
|
|
522
|
+
**Which installs get missed:** project installs that were never registered; installs disabled via
|
|
523
|
+
the `.geneseed-disabled` stash; registry rows whose directory is gone; and everything after the
|
|
524
|
+
point where a `rebuild-all` fails mid-loop — it is best-effort by contract and leaves a mixed
|
|
525
|
+
machine. Autostart entries are surveyed and reported, never rewritten.
|
|
526
|
+
|
|
527
|
+
**The web daemon serves the code it launched with**, plus an in-memory cache of every dist file.
|
|
528
|
+
After a `web/dist` rebuild the correct move is `geneseed web restart`, not a hard refresh —
|
|
529
|
+
otherwise the daemon serves an `index.html` naming assets that `emptyOutDir` deleted, the static
|
|
530
|
+
handler falls back to `index.html` at 200 with `text/html`, and the browser reports a MIME error.
|
|
531
|
+
|
|
532
|
+
⚠ A bare `geneseed web` runs in the **foreground and writes no record**, so `web stop` reports
|
|
533
|
+
"no running server recorded" and exits 0, and `web restart` starts a *second* daemon. Recovery is
|
|
534
|
+
to kill every pid on the port, then `geneseed web start`.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## 10 — What to settle before the next wave
|
|
539
|
+
|
|
540
|
+
Ordered by what unblocks the most work per unit of effort. Each is small; none is urgent enough to
|
|
541
|
+
stop a change today, and all of them cost a contributor a wrong turn until they are done.
|
|
542
|
+
|
|
543
|
+
1. **Rewrite the authoring section of `DESIGN.md` (§7.1) and fix `doctor`'s tip (§7.2).** These are
|
|
544
|
+
the two documents a contributor reads *first* when adding a law, and both name programs that no
|
|
545
|
+
longer exist. Half an hour, and it removes the most likely first wrong turn.
|
|
546
|
+
2. **Extend the fenced-command gate to `DESIGN.md` and `docs/**/*.md` (§7.3).** One array literal.
|
|
547
|
+
It would have caught item 1 the day it was written, and it keeps catching it.
|
|
548
|
+
3. **Decide the hook-verb question (§5.1) before it is asked under pressure.** Either mirror the
|
|
549
|
+
CLI side's exemption list onto the hook half, or write down that the hook verb set is closed.
|
|
550
|
+
Either answer is fine; discovering the question mid-feature is not.
|
|
551
|
+
4. **Write down the frozen-key list (§5.2) where a theme author will see it** — `themes/README.md`
|
|
552
|
+
is the natural home. Seven keys × fifteen files is a small enough surface to name explicitly,
|
|
553
|
+
and today nothing warns you before the snapshot goes red.
|
|
554
|
+
5. **Gate `AGENT_LESSON_PROMPT` like its sibling (§7.7), and either gate or delete
|
|
555
|
+
`adapters/claude-code/settings.json` (§7.10).** Two silent failure modes, both cheap to close.
|
|
556
|
+
6. **Consider a `scripts` block in the root `package.json`.** There is currently no single command
|
|
557
|
+
that runs what CI runs; every contributor reassembles the five by hand from this file. A
|
|
558
|
+
`verify` script is three lines and makes §1 executable rather than descriptive.
|
|
559
|
+
|
|
560
|
+
The list of hand-maintained satellites in §6 is the *design*, not a defect — this project
|
|
561
|
+
deliberately keeps tables and prose hand-written and gates them from the source tree. The
|
|
562
|
+
recommendations above are about the places where the gate is missing, wrong, or pointed at a file
|
|
563
|
+
that no longer exists.
|