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