geneseed 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/DESIGN.md +31 -20
- package/README.md +2 -2
- package/SETUP.md +30 -3
- package/SHIPPED.md +1 -1
- package/bin/{geneseed.mjs → build-driver.mjs} +88 -42
- package/bin/geneseed-cli.mjs +33 -32
- package/bin/geneseed-hook.mjs +7 -7
- package/docs/design-history.md +40 -40
- package/docs/extending.md +113 -85
- package/docs/web/rules.md +2 -0
- package/docs/web/setup-choices.md +13 -1
- package/harness.config.json +1 -1
- package/js/README.md +203 -0
- package/js/build/bundle.mjs +231 -0
- package/js/{catalog.mjs → build/catalog.mjs} +7 -7
- package/js/build/emit-claude.mjs +287 -0
- package/js/build/emit-common.mjs +255 -0
- package/js/build/emit-opencode.mjs +214 -0
- package/js/{generate.mjs → build/generate.mjs} +45 -31
- package/js/{render.mjs → build/render.mjs} +72 -11
- package/js/{checkout.mjs → build/source.mjs} +52 -11
- package/js/build/stubs.mjs +256 -0
- package/js/{themes.mjs → build/themes.mjs} +24 -24
- package/js/build/version.mjs +155 -0
- package/js/{hooks.mjs → hosts/hooks.mjs} +47 -44
- package/js/{hosts.mjs → hosts/hosts.mjs} +30 -28
- package/js/{installs.mjs → hosts/installs.mjs} +68 -16
- package/js/{link.mjs → hosts/link.mjs} +29 -28
- package/js/{mcp.mjs → hosts/mcp.mjs} +10 -9
- package/js/{native.mjs → hosts/native.mjs} +24 -22
- package/js/{opencode.mjs → hosts/opencode.mjs} +12 -7
- package/js/{settings.mjs → hosts/settings.mjs} +94 -69
- package/js/inspect/checks-authoring.mjs +761 -0
- package/js/inspect/checks-build.mjs +231 -0
- package/js/inspect/checks-repo.mjs +308 -0
- package/js/{diff.mjs → inspect/diff.mjs} +34 -31
- package/js/inspect/doctor.mjs +286 -0
- package/js/{excludes.mjs → inspect/excludes.mjs} +30 -33
- package/js/{inventory.mjs → inspect/inventory.mjs} +34 -20
- package/js/{registry.mjs → inspect/registry.mjs} +12 -10
- package/js/inspect/scan.mjs +125 -0
- package/js/{status.mjs → inspect/status.mjs} +27 -25
- package/js/inspect/validate.mjs +199 -0
- package/js/lib/fs.mjs +37 -759
- package/js/lib/json.mjs +344 -0
- package/js/lib/paths.mjs +242 -0
- package/js/lib/proc.mjs +3 -3
- package/js/lib/text.mjs +181 -0
- package/js/lib/udiff.mjs +1 -1
- package/js/{memory.mjs → maintain/memory.mjs} +10 -10
- package/js/{migrate.mjs → maintain/migrate.mjs} +28 -26
- package/js/{setup.mjs → maintain/setup.mjs} +49 -37
- package/js/{uninstall.mjs → maintain/uninstall.mjs} +62 -58
- package/js/{update.mjs → maintain/update.mjs} +51 -51
- package/js/{anim.mjs → ui/anim.mjs} +30 -28
- package/js/{cli.mjs → ui/cli.mjs} +10 -10
- package/js/{menu.mjs → ui/menu.mjs} +10 -9
- package/js/{tui.mjs → ui/tui.mjs} +36 -34
- package/js/web/actions.mjs +124 -89
- package/js/web/activity.mjs +22 -21
- package/js/web/api.mjs +88 -58
- package/js/web/daemon.mjs +319 -0
- package/js/web/docs.mjs +24 -21
- package/js/web/graph.mjs +0 -0
- package/js/web/handler.mjs +442 -0
- package/js/web/jobs.mjs +29 -21
- package/js/web/routes.mjs +223 -0
- package/js/web/server.mjs +56 -987
- package/package.json +11 -3
- package/src/AGENT.md.tmpl +1 -1
- package/src/doctrines/README.md +1 -1
- package/themes/opencode/README.md +1 -1
- package/web/dist/assets/{About-_MnJcwb_.js → About-CPvIFZGh.js} +1 -1
- package/web/dist/assets/{Activity-DpjqO-FR.js → Activity-z0uXdS7J.js} +1 -1
- package/web/dist/assets/{ActivityDetail-Bvrjv8oW.js → ActivityDetail-CEj5IJpF.js} +1 -1
- package/web/dist/assets/{Diff-DZ0IehBV.js → Diff-BMR8fPuG.js} +1 -1
- package/web/dist/assets/{Doctor-cu4krooO.js → Doctor-Bz5qE4r9.js} +1 -1
- package/web/dist/assets/{Graph-DUOPY65j.js → Graph-BV6a6t2l.js} +1 -1
- package/web/dist/assets/{Harnesses-B5Osg-BF.js → Harnesses-C5Ag5PaQ.js} +1 -1
- package/web/dist/assets/Laws-BK07NKw9.js +3 -0
- package/web/dist/assets/{Library-8y69I3_z.js → Library-CiXN9sC3.js} +1 -1
- package/web/dist/assets/{Markdown-Ds5GS1OD.js → Markdown-DG6FjUz-.js} +1 -1
- package/web/dist/assets/{Profile-7Jxa7ZUX.js → Profile-B2oSvdxH.js} +1 -1
- package/web/dist/assets/{Rules-UZ0jWkjg.js → Rules-DobVPd80.js} +1 -1
- package/web/dist/assets/{Skills-Bgkl0xLd.js → Skills-DQPsvsn9.js} +1 -1
- package/web/dist/assets/{StatusBadge-Cx4caF_2.js → StatusBadge-BhJ3HhdI.js} +1 -1
- package/web/dist/assets/{Themes-De8az7Sv.js → Themes-BP38FBU9.js} +1 -1
- package/web/dist/assets/{activity-VDAdVQ3q.js → activity-D9mfv7pY.js} +1 -1
- package/web/dist/assets/index-BE226Gv6.css +1 -0
- package/web/dist/assets/{index-BKrjBRV4.js → index-BZ7cPLMI.js} +1 -1
- package/web/dist/assets/index-BgIs5eIa.js +12 -0
- package/web/dist/assets/{index-wNiNzFFQ.js → index-DGM5j2hB.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +135 -69
- package/docs/declined.md +0 -200
- package/docs/limits.md +0 -329
- package/js/doctor.mjs +0 -1712
- package/js/emit.mjs +0 -1410
- package/web/dist/assets/Laws-D8DJgTBp.js +0 -3
- package/web/dist/assets/index-1pPSz7Kl.css +0 -1
- package/web/dist/assets/index-C-gb-q-r.js +0 -12
package/docs/extending.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# Extending — what it costs to change this tool
|
|
2
2
|
|
|
3
|
-
`docs/limits.md` is the standing answer to *"what does this tool not prove about itself?"*.
|
|
4
3
|
This file is its companion: **"what does it cost to add one thing?"** — one invariant, one
|
|
5
4
|
doctrine rule, one skill, one plugin, one hook verb, one doc page, one theme, one screen.
|
|
6
5
|
|
|
@@ -33,7 +32,7 @@ Three seams, and almost every question about "where do I edit?" resolves to one
|
|
|
33
32
|
Two rules follow from the table and they cause most of the surprises:
|
|
34
33
|
|
|
35
34
|
- **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`
|
|
35
|
+
machine until something re-emits (`js/build/emit.mjs:362` re-fingerprints, `js/inspect/status.mjs:80`
|
|
37
36
|
reports the drift).
|
|
38
37
|
- **`docs/web/` is data, `web/src/` is code.** A new documentation page is one file and no
|
|
39
38
|
rebuild. A new screen is a new hashed chunk and a mandatory `web/dist` commit.
|
|
@@ -47,14 +46,14 @@ three-tier split added — `{N_ONTOLOGY}` (the ontology's sections), `{N_PACKS}`
|
|
|
47
46
|
packs). `{N_LAWS}` is the **invariant** count and nothing else; there is deliberately no token
|
|
48
47
|
meaning "the whole constitution", because `proseMirrorProblems` holds README and `SHIPPED.md`
|
|
49
48
|
prose to that same number and a token that quietly meant something wider would put the docs and
|
|
50
|
-
the gate at odds
|
|
49
|
+
the gate at odds.
|
|
51
50
|
|
|
52
51
|
⚠ **Only a `kind: "concept"` page is substituted.** A `markdown`-kind page renders `{N_PACKS}`
|
|
53
52
|
literally, on the screen, as those nine characters.
|
|
54
53
|
|
|
55
54
|
Typing a literal number into a `docs/web` page is caught by `proseMirrorProblems`. The README
|
|
56
55
|
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
|
|
56
|
+
exceptions — and each has its own arm in `js/inspect/doctor.mjs` (`countTableProblems` walks the five
|
|
58
57
|
badge keys `agents`/`skills`/`laws`/`themes`/`plugins`; `proseMirrorProblems` holds the prose).
|
|
59
58
|
|
|
60
59
|
---
|
|
@@ -94,9 +93,10 @@ cd web && npm ci && npm run lint && npm run format:check && npm test && npm run
|
|
|
94
93
|
|
|
95
94
|
Notes that matter:
|
|
96
95
|
|
|
97
|
-
- The root `package.json` has
|
|
98
|
-
exists
|
|
99
|
-
hand with the glob above.
|
|
96
|
+
- The root `package.json` has exactly one script, `lint`, and no pre-commit hook, Makefile or
|
|
97
|
+
justfile exists. `npm test` at the root exits 1 with *Missing script: "test"*; the suite is run
|
|
98
|
+
by hand with the glob above. `npm ci` once per clone installs the devDependencies `lint` needs
|
|
99
|
+
— it pulls no runtime dependency, because there are none.
|
|
100
100
|
- `--test-reporter=tap` is not decoration. Both workflows gate on the *count* (`# tests N ≥ 500`)
|
|
101
101
|
because `node --test` over a glob that matches nothing prints `# tests 0` and **exits 0**. Node's
|
|
102
102
|
default reporter changed between majors, which is how a green 1043-test suite once reported zero.
|
|
@@ -116,9 +116,9 @@ Notes that matter:
|
|
|
116
116
|
|
|
117
117
|
### The gates CI does not run
|
|
118
118
|
|
|
119
|
-
- **The mutation matrix itself.** `tests/mutate.mjs` —
|
|
119
|
+
- **The mutation matrix itself.** `tests/mutate.mjs` — 33 rows, each a one-string edit to a
|
|
120
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
|
|
121
|
+
workflow** and should not be: it mutates the product tree 33 times and runs a gate per edit.
|
|
122
122
|
Run `node tests/mutate.mjs --all` (or `--only M7`) by hand when you change something a gate is
|
|
123
123
|
supposed to be watching. ⚠ An interrupted run leaves the **product tree mutated** — check
|
|
124
124
|
`git status` before doing anything else.
|
|
@@ -166,7 +166,7 @@ Five files, none of them a count. This is the slot most new material belongs in,
|
|
|
166
166
|
on purpose so that the invariant slot stays scarce.
|
|
167
167
|
|
|
168
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
|
|
169
|
+
the body. Two gates in `constitutionProblems` (`js/inspect/doctor.mjs`) sit on this: the pack named in
|
|
170
170
|
the *heading* is what addresses the rule (not the filename, so a rule filed in the wrong file is
|
|
171
171
|
reported rather than silently renamed), and ids must run **contiguously from 1**, so appending
|
|
172
172
|
at the end is the only place `n` is free. Packs are numbered independently — nothing outside
|
|
@@ -179,7 +179,7 @@ on purpose so that the invariant slot stays scarce.
|
|
|
179
179
|
3. Seed the fourteen themes:
|
|
180
180
|
|
|
181
181
|
```bash
|
|
182
|
-
node bin/
|
|
182
|
+
node bin/build-driver.mjs --sync-themes
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
⚠ Four spellings of this command are in circulation and two of them fail — `doctor`'s own tip
|
|
@@ -216,13 +216,10 @@ a defect.
|
|
|
216
216
|
Steps 1 and 9 of §2c, plus `LAW_META` if the Principle line moved. Two cautions:
|
|
217
217
|
|
|
218
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
|
|
219
|
+
(`terseBlocks` in `js/build/render.mjs`). A clause added to the second paragraph is invisible in the
|
|
220
220
|
default build. Amend the first sentence, or the amendment does not exist for most installs. The
|
|
221
221
|
same is true of a doctrine rule; it is **not** true of the ontology, which ships whole at both
|
|
222
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
223
|
|
|
227
224
|
### 2c — Adding an INVARIANT
|
|
228
225
|
|
|
@@ -231,7 +228,7 @@ the bar is in `DESIGN.md` Decision 7, and most candidates belong in a doctrine p
|
|
|
231
228
|
|
|
232
229
|
1. `src/laws/universal.md` — **append** `### {{LAW}} <roman> — {{LEX_<roman>}}` plus the body.
|
|
233
230
|
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
|
|
231
|
+
citations in `js/hosts/hooks.mjs` and two OpenCode plugins, and **nothing resolves a cross-reference
|
|
235
232
|
against the canon**. Renumbering silently rewires every one of them.
|
|
236
233
|
2. `themes/_TEMPLATE.json` — add `LEX_<roman>` in template order. Gated, as in §2a step 2:
|
|
237
234
|
`constitutionProblems` holds `LEX_I..LEX_IX` as an **equality** across all fifteen files (the
|
|
@@ -239,7 +236,7 @@ the bar is in `DESIGN.md` Decision 7, and most candidates belong in a doctrine p
|
|
|
239
236
|
arm exists because the renumber left `LEX_XXII`, `LEX_XXIII`, `LEX_XXIV` and `LEX_XXXVI` behind
|
|
240
237
|
in every file and parity was silent — they were absent from nowhere.
|
|
241
238
|
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.
|
|
239
|
+
4. `LAW_CLASS` in `js/inspect/inventory.mjs` — one of the six in `LAW_CLASSES` beside it.
|
|
243
240
|
5. `LAW_META` in `web/src/pages/Laws.jsx` — same class, plus the one-line Principle. Note the map
|
|
244
241
|
is keyed by **arabic** number where `universal.md` heads with a Roman numeral. **This copy
|
|
245
242
|
exists nowhere else**; a rule absent here renders with a blank description. It is why that one
|
|
@@ -271,13 +268,13 @@ Rare, and mechanical. On top of §2a for each rule the pack carries:
|
|
|
271
268
|
1. `src/doctrines/<pack>.md`, opening with its lead line — `**{{PACK_<NAME>}}** — how … .` A pack
|
|
272
269
|
file `PACK_ORDER` names but cannot read, or one carrying no `### {{DOCTRINE}} <pack> <n>`
|
|
273
270
|
heading at all, is a refusal.
|
|
274
|
-
2. `PACK_ORDER` in `js/
|
|
271
|
+
2. `PACK_ORDER` in `js/build/source.mjs` — the registration, and the render order. It is **narrative,
|
|
275
272
|
not alphabetical** (craft → rigor → ops → process); discovery sorts alphabetically and the
|
|
276
273
|
renderer refuses a `.md` under `src/doctrines/` that `PACK_ORDER` does not name, rather than
|
|
277
274
|
skipping it.
|
|
278
275
|
3. `PACK_<NAME>` in `themes/_TEMPLATE.json` and all fourteen voices. Unlike `DOC_*`, this family is
|
|
279
276
|
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,
|
|
277
|
+
4. `DOCTRINE_BLURBS` in `js/maintain/setup.mjs` — the one-line description an installer sees in the wizard,
|
|
281
278
|
and the only place a pack is ever explained to them. Missing entries fall back to the bare name.
|
|
282
279
|
5. `harness.config.json` if the pack should be on by default; `constitutionProblems` refuses a
|
|
283
280
|
`doctrines` array naming a pack this checkout does not ship.
|
|
@@ -286,7 +283,7 @@ Rare, and mechanical. On top of §2a for each rule the pack carries:
|
|
|
286
283
|
|
|
287
284
|
## 3 — Adding a SKILL or an AGENT
|
|
288
285
|
|
|
289
|
-
**Order matters at the first two steps.** `missingReferencedSpecs` (in `js/emit.mjs`) *aborts the
|
|
286
|
+
**Order matters at the first two steps.** `missingReferencedSpecs` (in `js/build/emit.mjs`) *aborts the
|
|
290
287
|
build* when `AGENT.md.tmpl` names a spec with no file — so the file comes before the table row, or
|
|
291
288
|
nothing builds at all. The refusal is clean: `tests/unit/emit_gates.test.mjs:265` proves a refused
|
|
292
289
|
emit leaves the prior install intact.
|
|
@@ -299,20 +296,20 @@ emit leaves the prior install intact.
|
|
|
299
296
|
Gated both ways: a row with no file, and a file with no row.
|
|
300
297
|
3. `DESC_<NAME>` in **all fourteen** themes. Theme parity catches a missing one; nothing catches
|
|
301
298
|
an unstyled placeholder.
|
|
302
|
-
4. **Skills only** — `SKILL_CLASS` in `js/inventory.mjs`. Gated both ways, inside
|
|
299
|
+
4. **Skills only** — `SKILL_CLASS` in `js/inspect/inventory.mjs`. Gated both ways, inside
|
|
303
300
|
`countTableProblems`, which matters because the runtime fallback is silent: an unclassified
|
|
304
301
|
skill renders as `build`.
|
|
305
302
|
5. `registry.json` — a row keyed `skills/<name>` or `agents/<name>` with `status` ∈
|
|
306
303
|
{`experimental`, `approved`, `deprecated`}, a semver `version`, a non-blank `owner`, an ISO
|
|
307
304
|
`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`).
|
|
305
|
+
and the gate fires in both directions (`js/inspect/doctor.mjs:388-436`).
|
|
309
306
|
6. `README.md` — the badge, the `**🤖 Agents** (N)` / `**🛠 Skills** (N)` count, and for a skill the
|
|
310
307
|
`·`-separated name in the 🛠 row (that enumeration is gated two ways, unlike the laws' one).
|
|
311
308
|
7. `SHIPPED.md` — the `N laws, N agents, N skills` triple.
|
|
312
309
|
8. `doctor --all`, then the suite.
|
|
313
310
|
|
|
314
311
|
**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
|
|
312
|
+
`VENDORED_SKILL_DIRS` (`js/hosts/native.mjs:37` — the literal is frozen in
|
|
316
313
|
`tests/unit/authoring_gates.test.mjs:312`), write a `VENDOR.md` carrying `**Upstream:**`,
|
|
317
314
|
`**Commit:**` (a 40-hex commit, never a moving branch) and `**License:**`, add the registry row —
|
|
318
315
|
and **skip steps 2, 4, 6, 7**: the flat-spec globs only see `*.md` at the top level.
|
|
@@ -320,14 +317,13 @@ and **skip steps 2, 4, 6, 7**: the flat-spec globs only see `*.md` at the top le
|
|
|
320
317
|
Two traps worth knowing before you write the spec:
|
|
321
318
|
|
|
322
319
|
- **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
|
|
320
|
+
(`js/hosts/native.mjs:148`). The escape hatch is an explicit `<!-- bash: allow -->` marker, which
|
|
324
321
|
re-opens bash on all three hosts.
|
|
325
322
|
- **The console has a fourth, ungated copy of the skill taxonomy.** `SKILL_CATS` in
|
|
326
323
|
`web/src/pages/Skills.jsx:19` mirrors `SKILL_CLASS` with nothing comparing them (contrast
|
|
327
324
|
`LAW_META`, which *is* cross-checked). Inventing a new *category* means editing that file —
|
|
328
325
|
which drags in the `web/dist` rebuild — and an unknown category renders as Build with no warning.
|
|
329
326
|
|
|
330
|
-
Adding a spec reddens no recording: none of the surviving snapshots contains a skill or agent name.
|
|
331
327
|
|
|
332
328
|
---
|
|
333
329
|
|
|
@@ -358,8 +354,8 @@ No `registry.json` row, no `package.json` edit — `files[]` ships `adapters/` w
|
|
|
358
354
|
|
|
359
355
|
⚠ **Read §5 first. A fifth hook verb has no green path today.**
|
|
360
356
|
|
|
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
|
|
357
|
+
The mechanical part: implement `cmd<Verb>` in `js/hosts/hooks.mjs`; wire the command string in
|
|
358
|
+
`claudeHookGroups` (`js/hosts/settings.mjs:434`, spelled `` `${run} <verb> …` `` — the gate scrapes that
|
|
363
359
|
shape); add the entry to `bin/geneseed-hook.mjs:43` `VERBS` with **exactly two-space indent**; add
|
|
364
360
|
a row to `js/cli-table.json` with a help string over 20 characters.
|
|
365
361
|
|
|
@@ -381,6 +377,55 @@ the page renders, wrong, with nothing red.
|
|
|
381
377
|
|
|
382
378
|
---
|
|
383
379
|
|
|
380
|
+
## 4bis — Adding a RUNTIME DEPENDENCY
|
|
381
|
+
|
|
382
|
+
**Short answer: you vendor it, or you do not add it.** A dependency arrives as tracked source under
|
|
383
|
+
`js/vendor/<name>/`, imported by relative path, landing in the same `git merge --ff-only` as the
|
|
384
|
+
code that imports it. It never comes from a registry at install time. `tests/unit/dependency_policy.test.mjs`
|
|
385
|
+
gates this, and its header carries the long form.
|
|
386
|
+
|
|
387
|
+
This is not conservatism. The npm route is *unbuildable* here, for three reasons that were measured
|
|
388
|
+
rather than assumed:
|
|
389
|
+
|
|
390
|
+
1. **Every fixture would die at module load.** `copyCheckout` lists the tree with
|
|
391
|
+
`git ls-files --cached --others --exclude-standard`, which honours `.gitignore`, and
|
|
392
|
+
`.gitignore` ignores `/node_modules/`. The copy is written outside the repository, under the OS
|
|
393
|
+
temp root, and the product is run out of it as a real child process — so Node's upward resolver
|
|
394
|
+
walk starts in temp and finds nothing. Eleven test files build such a copy. And this is not a
|
|
395
|
+
fixture defect to work around: a user's fresh `git clone` has no `node_modules` either. **The
|
|
396
|
+
fixture is that clone.** Giving the fixture something the real install lacks makes it stop
|
|
397
|
+
reproducing the failure it exists to catch.
|
|
398
|
+
|
|
399
|
+
2. **The clone channel cannot install, and it is the only channel that would need to.** `geneseed
|
|
400
|
+
update` is `git pull` + rebuild. `npm ci` deletes `node_modules` before refetching, so a blocked
|
|
401
|
+
download leaves an install whose CLI cannot start — the one outcome the update must never
|
|
402
|
+
produce. `npm install` writes the tracked `package-lock.json`, so `preflight()`'s
|
|
403
|
+
`git status --porcelain` then reports a dirty tree and the *next* update refuses with "You have
|
|
404
|
+
local changes": a self-poisoning update. Meanwhile npm/npx installs already resolve
|
|
405
|
+
`dependencies` at install time and need nothing from us. There is no overlap between the channel
|
|
406
|
+
that would need a sync step and the channel where one could work.
|
|
407
|
+
|
|
408
|
+
3. **The hook path pays per tool call.** `bin/geneseed-hook.mjs` loads on every tool call of every
|
|
409
|
+
agent session (~14 ms). A relative import costs no resolver walk; a bare one does.
|
|
410
|
+
|
|
411
|
+
What vendoring costs, by contrast, is almost nothing: `files[]` already carries `js/` and the
|
|
412
|
+
manifest partition already carries the `js/` prefix row, so a vendored subtree costs **zero**
|
|
413
|
+
manifest lines and zero partition lines. The existing doctor gate already covers a bad drop, because
|
|
414
|
+
`runDoctor` validates the pulled tree in a fresh interpreter before the rebuild.
|
|
415
|
+
|
|
416
|
+
**The one exception, and it is not a loophole.** `adapters/` does not run in this tool's process — it
|
|
417
|
+
ships into OpenCode's. A module the *host* provides is resolvable there and nowhere else, so
|
|
418
|
+
reaching for one is legitimate. But it must be a **guarded dynamic import with a working degraded
|
|
419
|
+
path**, never a static one: an older host that lacks it would otherwise fail the whole plugin at
|
|
420
|
+
load instead of losing one capability. `geneseed-workflow.js`'s `@opencode-ai/plugin` is the shape
|
|
421
|
+
to copy. The allow-list in the gate is two-sided — an entry nothing imports fails as loudly as an
|
|
422
|
+
import nothing declares.
|
|
423
|
+
|
|
424
|
+
**Before proposing one at all, price it.** At the time this was written there were 463 import
|
|
425
|
+
specifiers under `js/`, `bin/` and `adapters/` and exactly one was bare — the host-provided
|
|
426
|
+
exception above. The invariant already held; it had simply never been written down. A library that
|
|
427
|
+
would delete fewer lines than its vendored source adds is not a saving.
|
|
428
|
+
|
|
384
429
|
## 5 — The four things that are genuinely hard
|
|
385
430
|
|
|
386
431
|
These are not checklist items. They are structural, and each one should be settled *before* the
|
|
@@ -398,34 +443,24 @@ already exempted a named `NATIVE` list; the hook half exempted nothing — so a
|
|
|
398
443
|
failed with **no green path**, since the recorder died on 2026-08-17 and the failure message
|
|
399
444
|
correctly forbids deleting cells to pass.
|
|
400
445
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
446
|
+
`NATIVE` in `tests/unit/hook_cli.test.mjs` names the verbs held to the structural rules rather
|
|
447
|
+
than to a per-verb gate, and the reverse check refuses an exemption for a verb neither entry point
|
|
448
|
+
carries — so a name that stops meaning anything fails rather than sitting there.
|
|
414
449
|
|
|
415
|
-
`
|
|
416
|
-
|
|
417
|
-
|
|
450
|
+
**To add a hook verb today:** implement `cmd<Verb>` in `js/hosts/hooks.mjs`, wire the string in
|
|
451
|
+
`claudeHookGroups`, add the `VERBS` entry, add the `js/cli-table.json` row, then give it an
|
|
452
|
+
absolute unit gate. `NATIVE` used to be duplicated across two files that read each other's source
|
|
453
|
+
to stay in step; there is one copy now, so there is nothing to keep in step.
|
|
418
454
|
|
|
419
|
-
|
|
420
|
-
|---|---|
|
|
421
|
-
| `theme_preview` | `TAGLINE`, `LOADED_SIGIL`, `VOICE`, **`LEX_I`**, `BENEDICTION` |
|
|
422
|
-
| `theme_flair` | `ACCENT`, `BANNER` |
|
|
455
|
+
### 5.2 ✅ Theme text is free to edit — resolved 2026-08-23
|
|
423
456
|
|
|
424
|
-
|
|
425
|
-
|
|
457
|
+
Seven keys (`TAGLINE`, `LOADED_SIGIL`, `VOICE`, `LEX_I`, `BENEDICTION`, `ACCENT`, `BANNER`) used to
|
|
458
|
+
be pinned byte-for-byte by a recording with no re-bless path, so restyling a theme's tagline was a
|
|
459
|
+
red gate. **That recording is gone.** Theme text is now held only by `doctor --all` — key presence,
|
|
460
|
+
cross-theme parity, and no unresolved tokens — which is a claim about structure, not wording.
|
|
426
461
|
|
|
427
|
-
|
|
428
|
-
|
|
462
|
+
Restyle freely. What still bites is `LOADED_SIGIL` uniqueness (§6): it is load-bearing for theme
|
|
463
|
+
detection and nothing checks it.
|
|
429
464
|
|
|
430
465
|
### 5.3 ⚠ The hook shim is machine-wide and last-writer-wins
|
|
431
466
|
|
|
@@ -434,7 +469,7 @@ anything owns **every** install's hooks. Emitting from a git worktree or a temp
|
|
|
434
469
|
repoint the shim at that disposable path; removing the worktree then killed hooks for every
|
|
435
470
|
install on the machine.
|
|
436
471
|
|
|
437
|
-
**`hookPrefix` now refuses the claim** (`js/settings.mjs`, `ephemeralCheckout`): an emit whose
|
|
472
|
+
**`hookPrefix` now refuses the claim** (`js/hosts/settings.mjs`, `ephemeralCheckout`): an emit whose
|
|
438
473
|
checkout is under the OS temp root, or is a linked git worktree (`.git` is a *file*), leaves a
|
|
439
474
|
shim that still resolves exactly as it is and wires the emitted hooks to it anyway. The bundle it
|
|
440
475
|
writes is byte-identical to a normal emit — the install simply keeps running the durable
|
|
@@ -463,37 +498,30 @@ remains the cheapest thing to run.
|
|
|
463
498
|
Related: a stray `console.log` on a hook path silently disables a gate. Hooks signal through
|
|
464
499
|
stdout JSON and return 0 on every path — a printed byte is not a warning, it is a disabled gate.
|
|
465
500
|
|
|
466
|
-
### 5.4
|
|
501
|
+
### 5.4 ✅ Expected values are written out — resolved 2026-08-23
|
|
467
502
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
writing
|
|
472
|
-
|
|
503
|
+
This section used to say that a snapshot red was a finding rather than a step: the recordings under
|
|
504
|
+
`tests/__snapshots__/` came from an implementation that no longer existed, there was no `--record`,
|
|
505
|
+
and a change that moved a recorded byte left only two honest options — revert it, or argue in
|
|
506
|
+
writing that the recording was wrong. The rule that fell out of it was *when a frozen recording
|
|
507
|
+
blocks a change, move the logic to the nearest caller that is not frozen*.
|
|
473
508
|
|
|
474
|
-
**
|
|
475
|
-
|
|
476
|
-
|
|
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
|
|
509
|
+
**None of that applies any more.** The recordings are retired (tag `corpus-reference-v3.1.2` is the
|
|
510
|
+
only way back to them), and every expected value now lives in the test file that asserts it, above
|
|
511
|
+
a sentence saying which decision it pins. The rule is correspondingly ordinary:
|
|
483
512
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
```
|
|
513
|
+
> **Change the expectation and the sentence that explains it in the same commit, and say why in
|
|
514
|
+
> the message.**
|
|
487
515
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
do not. `docs/limits.md:311-316` names this cluster as the one open standing decision.
|
|
516
|
+
A red test is a step again. What it costs is an argument, in prose, that the new answer is better —
|
|
517
|
+
the cost that keeps the tables honest, and far cheaper than the one it replaced.
|
|
491
518
|
|
|
492
|
-
The CLI surface is the most expensive thing in the repo to change
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
`tests/unit/
|
|
519
|
+
The CLI surface is no longer the most expensive thing in the repo to change. Editing a verb's help
|
|
520
|
+
text or flags is now an ordinary edit: `js/cli-table.json` is the owned document, and
|
|
521
|
+
`tests/unit/text_layout.test.mjs` renders every verb through `formatHelp` to check the layout
|
|
522
|
+
holds. Three hand-written equalities still converge on it and are cheap to keep in step — the
|
|
523
|
+
29-command count and the hidden-argument rule (`tests/unit/cli_table.test.mjs`), and
|
|
524
|
+
`tests/unit/docs.test.mjs`, which validates every fenced `geneseed <verb> <flags>` line in
|
|
497
525
|
`README.md`, `SETUP.md`, `QUICKSTART.md` and `SHIPPED.md` against the table.
|
|
498
526
|
|
|
499
527
|
---
|
|
@@ -511,12 +539,12 @@ only, at best, on its presence.
|
|
|
511
539
|
| The README keyword enumerations | `README.md`, `docs/web/rules.md` | nothing |
|
|
512
540
|
| The plugin capability enumeration | `docs/web/model.md` | nothing (the number substitutes; the list does not) |
|
|
513
541
|
| 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 |
|
|
542
|
+
| `THEME_BLURBS` (8 of 14), `ART` (8 of 14) | `js/maintain/setup.mjs`, `js/ui/anim.mjs` | nothing — missing entries fall back silently |
|
|
515
543
|
| `LOADED_SIGIL` uniqueness | `themes/*.json` | nothing, and it is load-bearing for theme detection |
|
|
516
544
|
| `adapters/claude-code/settings.json` | — | nothing compares it to what the emitter writes; **it is already divergent** |
|
|
517
545
|
| `NATIVE`, twice | two test files | they check each other, which is the gate that makes the duplication safe |
|
|
518
546
|
| 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 |
|
|
547
|
+
| Stale docblock counts | `js/build/themes.mjs:62` ("137 values" → 140), `js/build/render.mjs:116` ("145 tokens" → 148) | nothing |
|
|
520
548
|
|
|
521
549
|
---
|
|
522
550
|
|
|
@@ -555,7 +583,7 @@ answer.
|
|
|
555
583
|
5. **Numbers in docblocks that had drifted** — `_TEMPLATE.json`'s "137 values" (140), "the 145
|
|
556
584
|
tokens" `src/` uses (148), and "sixteen `_*_problems` checks" (fifteen — the sixteenth was
|
|
557
585
|
named as `cli`, which was removed).
|
|
558
|
-
6. **A claim that was simply false.** `js/render.mjs` said `--theme` is validated against
|
|
586
|
+
6. **A claim that was simply false.** `js/build/render.mjs` said `--theme` is validated against
|
|
559
587
|
`choices` before anything renders, which is why its own refusal read as an unreachable nicety.
|
|
560
588
|
It is not in `choicesFor`; that refusal is the only one there is, on every path.
|
|
561
589
|
7. **Five dead cross-references** to `tests/snapshot/no_python_in_corpus.test.mjs`, retired with
|
|
@@ -563,7 +591,7 @@ answer.
|
|
|
563
591
|
Plus a skip message naming a `record-corpus` CI job that no longer exists.
|
|
564
592
|
8. **`CHANGELOG.md` said 2.0.0 was "not yet published"**, with "no tag, no release and no install
|
|
565
593
|
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.
|
|
594
|
+
`geneseed upgrade v1.0.0 # pin to a tag`, which `js/maintain/update.mjs` prints is IGNORED.
|
|
567
595
|
9. **`docs/web/agents.md`** enumerated 16 agents and omitted `developer`, where README says 17 —
|
|
568
596
|
the doctor arm cannot fire, because the page spells `{N_AGENTS}` and that arm only ever catches
|
|
569
597
|
a literal. **`docs/web/rules.md`** called each law "a short markdown file under `src/laws/`";
|
|
@@ -662,9 +690,9 @@ stop a change today, and all of them cost a contributor a wrong turn until they
|
|
|
662
690
|
and today nothing warns you before the snapshot goes red.
|
|
663
691
|
4. **Gate `AGENT_LESSON_PROMPT` like its sibling (§7.7), and either gate or delete
|
|
664
692
|
`adapters/claude-code/settings.json` (§7.10).** Two silent failure modes, both cheap to close.
|
|
665
|
-
5. **
|
|
666
|
-
|
|
667
|
-
|
|
693
|
+
5. **Finish the `scripts` block in the root `package.json`.** `lint` landed with the ESLint
|
|
694
|
+
config; the rest of what CI runs is still reassembled by hand from this file. A `verify` script
|
|
695
|
+
chaining lint + suite + doctor is three lines and makes §1 executable rather than descriptive.
|
|
668
696
|
|
|
669
697
|
The list of hand-maintained satellites in §6 is the *design*, not a defect — this project
|
|
670
698
|
deliberately keeps tables and prose hand-written and gates them from the source tree. The
|
package/docs/web/rules.md
CHANGED
|
@@ -21,6 +21,8 @@ Practices rather than principles: **craft** (how code is written), **rigor** (ho
|
|
|
21
21
|
|
|
22
22
|
A pack you leave out **still ships in the bundle**: every pack file lands under `doctrines/` beside `AGENT.md` whether or not it was built in. So a rule that cites one in an inactive pack still resolves on disk, and you can read the alternatives before turning one on. What an inactive pack loses is bindingness, not availability.
|
|
23
23
|
|
|
24
|
+
Rules toggle **individually** too — `geneseed-build --exclude-rules "process 7"`, or the switches on this page — so *keep all of process except one rule* is a state you can build. A pack whose every rule is excluded drops out of the active set on its own. What an install excluded is recorded beside the pack list, as `Excluded rules: process 7`, and every rebuild preserves it.
|
|
25
|
+
|
|
24
26
|
### Which outranks which
|
|
25
27
|
|
|
26
28
|
Ontology and Invariants first, then your own `user-rules.md`, then the active Doctrines, then `PROFILE.md`. A rule you write in `user-rules.md` **outranks a doctrine rule** — a practice pack chosen at build time never overrides an instruction you wrote for your own repo — and nothing outranks an invariant: a user rule or a doctrine rule may tighten one, never repeal it.
|
|
@@ -36,10 +36,22 @@ Which practice rules this install adopts, fixed at build time. Unlike the dials
|
|
|
36
36
|
- **ops** — how the machine is driven: tool discovery, commands that return, complete teardowns.
|
|
37
37
|
- **process** — how a session runs: planning, context economy, docs first, and the **consent gate on every commit and push**.
|
|
38
38
|
|
|
39
|
-
Turning **process** off takes that consent gate with it, at the tool boundary as well as in the prose: the OpenCode permission block and the Claude/Bob `PreToolUse` hook that ask before a commit or a push are wired only when
|
|
39
|
+
Turning **process 5** off takes that consent gate with it, at the tool boundary as well as in the prose: the OpenCode permission block and the Claude/Bob `PreToolUse` hook that ask before a commit or a push are wired only when that rule is built in, because a boundary that keeps asking for a rule the install never adopted is a gate arguing with its own harness. The invariant-territory refusals — `rm -rf`, a force-push — stay in every build; they are the Rules' territory, not the pack's.
|
|
40
40
|
|
|
41
41
|
A pack you leave out still ships in the bundle under `doctrines/`, so you can read it before turning it on, and a rule that cites it still resolves. Change the set with `geneseed-build --doctrines craft,rigor` (or `--doctrines none`), or in `harness.config.json`.
|
|
42
42
|
|
|
43
|
+
### One rule at a time
|
|
44
|
+
|
|
45
|
+
The pack is the coarse axis. `--exclude-rules` is the fine one, and it takes rule addresses — so *keep all of process, drop just `process 7`* is expressible:
|
|
46
|
+
|
|
47
|
+
geneseed-build --exclude-rules "process 7"
|
|
48
|
+
geneseed-build --exclude-rules "process 5,craft 2"
|
|
49
|
+
geneseed-build --exclude-rules none
|
|
50
|
+
|
|
51
|
+
Either spelling of an address works, `process 7` or `process.7`. The two axes compose: a pack whose every rule is excluded leaves `Active packs:` entirely, so no pack header is ever rendered with nothing under it. An install records its exclusions in a second marker line — `Excluded rules: process 7` — written only when something *is* excluded, and every rebuild path reads it back and preserves it.
|
|
52
|
+
|
|
53
|
+
The switches are on the **Constitution** page, one per rule, staged locally and applied in a single rebuild.
|
|
54
|
+
|
|
43
55
|
### Footprint — how much of the constitution loads inline
|
|
44
56
|
|
|
45
57
|
How much of the constitution `AGENT.md` carries inline every turn. A token-cost dial, not a rules cut — whatever this install adopted always applies:
|
package/harness.config.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
"_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
|
|
3
3
|
"theme": "neutral",
|
|
4
4
|
"product": "Geneseed",
|
|
5
|
-
"version": "3.1.
|
|
5
|
+
"version": "3.1.2"
|
|
6
6
|
}
|