geneseed 3.1.1 → 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 +68 -1
- package/DESIGN.md +15 -15
- package/README.md +1 -1
- package/SETUP.md +1 -1
- package/SHIPPED.md +1 -1
- package/bin/{geneseed.mjs → build-driver.mjs} +39 -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 +111 -83
- 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} +27 -28
- package/js/{render.mjs → build/render.mjs} +13 -9
- package/js/{checkout.mjs → build/source.mjs} +9 -9
- 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} +18 -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} +63 -55
- 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} +20 -17
- 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} +34 -32
- package/js/{uninstall.mjs → maintain/uninstall.mjs} +55 -56
- 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 +84 -85
- package/js/web/activity.mjs +22 -21
- package/js/web/api.mjs +72 -50
- 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 +21 -19
- package/js/web/routes.mjs +223 -0
- package/js/web/server.mjs +56 -987
- package/package.json +11 -3
- package/src/doctrines/README.md +1 -1
- package/themes/opencode/README.md +1 -1
- package/web/dist/assets/{About-DyK7yRy7.js → About-CPvIFZGh.js} +1 -1
- package/web/dist/assets/{Activity-D_sYBlW-.js → Activity-z0uXdS7J.js} +1 -1
- package/web/dist/assets/{ActivityDetail-BomKcMhy.js → ActivityDetail-CEj5IJpF.js} +1 -1
- package/web/dist/assets/{Diff-BSvi1WUg.js → Diff-BMR8fPuG.js} +1 -1
- package/web/dist/assets/{Doctor-Cz1s-y1n.js → Doctor-Bz5qE4r9.js} +1 -1
- package/web/dist/assets/{Graph-CuntzFJ0.js → Graph-BV6a6t2l.js} +1 -1
- package/web/dist/assets/{Harnesses-DgRyOviS.js → Harnesses-C5Ag5PaQ.js} +1 -1
- package/web/dist/assets/{Laws-CWk3Mpri.js → Laws-BK07NKw9.js} +1 -1
- package/web/dist/assets/{Library-DXhTN9ju.js → Library-CiXN9sC3.js} +1 -1
- package/web/dist/assets/{Markdown-BoiZcdUl.js → Markdown-DG6FjUz-.js} +1 -1
- package/web/dist/assets/{Profile-DfCHlZ03.js → Profile-B2oSvdxH.js} +1 -1
- package/web/dist/assets/{Rules-eND7hJv9.js → Rules-DobVPd80.js} +1 -1
- package/web/dist/assets/{Skills-CDl9opZO.js → Skills-DQPsvsn9.js} +1 -1
- package/web/dist/assets/{StatusBadge-DSDtcArX.js → StatusBadge-BhJ3HhdI.js} +1 -1
- package/web/dist/assets/{Themes-CoAWuIeM.js → Themes-BP38FBU9.js} +1 -1
- package/web/dist/assets/{activity-Dmb0FTAx.js → activity-D9mfv7pY.js} +1 -1
- package/web/dist/assets/index-BE226Gv6.css +1 -0
- package/web/dist/assets/{index-jrY4m9jP.js → index-BZ7cPLMI.js} +1 -1
- package/web/dist/assets/index-BgIs5eIa.js +12 -0
- package/web/dist/assets/{index-Dg58ScBe.js → index-DGM5j2hB.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +1 -1
- package/docs/declined.md +0 -200
- package/docs/limits.md +0 -329
- package/js/doctor.mjs +0 -1750
- package/js/emit.mjs +0 -1411
- package/web/dist/assets/index-B-mXquBZ.css +0 -1
- package/web/dist/assets/index-CH_Z725C.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.
|
|
@@ -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/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
|
}
|
package/js/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# `js/` — the module map
|
|
2
|
+
|
|
3
|
+
Fifty-six modules in seven folders. This page is the address book: **what each one owns**, and
|
|
4
|
+
**which file to open first** for a given task. It is not documentation of behaviour — every module
|
|
5
|
+
has a docblock for that, and the docblock is the thing to read before editing.
|
|
6
|
+
|
|
7
|
+
`tests/unit/module_map.test.mjs` keeps this page honest: a module with no row here, or a row naming
|
|
8
|
+
a module that does not exist, is a test failure. `geneseed doctor` reports the same thing.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## The shape
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
bin/geneseed-cli.mjs the `geneseed` CLI — 20+ verbs, may NOT spawn
|
|
16
|
+
bin/geneseed-hook.mjs the hook entry — 4 verbs, loads on EVERY tool call (~14 ms)
|
|
17
|
+
bin/build-driver.mjs `geneseed-build` — the generator's flags and emit targets
|
|
18
|
+
|
|
19
|
+
js/build/ src/ + themes/ → rendered text → a bundle → a per-host install
|
|
20
|
+
js/hosts/ where it stops being a renderer and touches the machine
|
|
21
|
+
js/inspect/ the read-only half: doctor, status, diff, validate, catalog
|
|
22
|
+
js/lib/ primitives, every one a deliberate divergence from the Node default
|
|
23
|
+
js/maintain/ lifecycle verbs that change an install that already exists
|
|
24
|
+
js/ui/ what it prints at a terminal
|
|
25
|
+
js/web/ the `geneseed web` console
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Dependency direction is one-way: `lib/` ← everything; `build/` ← `hosts/`; `inspect/` and `web/`
|
|
29
|
+
read what `build/` produced. Nothing in `lib/` imports anything above it.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## `js/build/` — the generator
|
|
34
|
+
|
|
35
|
+
Turns the checkout's `src/` + `themes/` into rendered text, writes that into a bundle, and emits it
|
|
36
|
+
as a per-host install at project or global scope.
|
|
37
|
+
|
|
38
|
+
| module | owns |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `source.mjs` | Where the SOURCE is: `ROOT`/`SRC`/`THEMES`/`CONFIG`, `PACK_ORDER`, rule ids, the cfg object |
|
|
41
|
+
| `render.mjs` | The pure text pipeline: theme load, `INCLUDE` inlining, CATALOG blocks, `{{TOKENS}}`, dest paths |
|
|
42
|
+
| `bundle.mjs` | `build` — render a theme into a bundle dir, plus the source-completeness refusal. Sits UNDER both emits |
|
|
43
|
+
| `emit-claude.mjs` | The Claude/Bob/Copilot emit — the render half plus the settings + CLAUDE.md managed-block wire |
|
|
44
|
+
| `emit-opencode.mjs` | The OpenCode emits — a project's `.opencode/` and the global config dir. Deliberate twin of the above |
|
|
45
|
+
| `emit-common.mjs` | Constants, tree walkers, and the three writers BOTH global emits share. Decides no host |
|
|
46
|
+
| `stubs.mjs` | Write-once seed files: context, wiki, rules, excludes, profile, `.gitignore`, memory/notebook indexes |
|
|
47
|
+
| `version.mjs` | The release marker: fingerprint the sources, read/compare/write an install's version, warn on downgrade |
|
|
48
|
+
| `generate.mjs` | CLI verbs `build`, `prompt`, `theme`, `rebuild-all` — the thin face over `bin/build-driver.mjs` |
|
|
49
|
+
| `catalog.mjs` | `geneseed catalog` — prints the shipped roster. Classifies nothing itself |
|
|
50
|
+
| `themes.mjs` | Maintainer-only `--sync-themes`: inserts `_TEMPLATE.json`'s missing keys into the committed themes |
|
|
51
|
+
|
|
52
|
+
**Before editing:** `render.mjs` writes nothing, and its output is what every emit target
|
|
53
|
+
serialises — a change there is visible in all 14 themes at once. `emit-claude.mjs`'s
|
|
54
|
+
wire half edits files the **user** co-owns — get its prior-claim pruning wrong and you delete
|
|
55
|
+
someone's own settings keys. `emit-opencode.mjs` is shaped as a twin on purpose: a change that
|
|
56
|
+
lands on one host and not the other is the historical defect here, so edit both or neither.
|
|
57
|
+
`stubs.mjs` bodies are compared byte-for-byte by a recording — editing prose in one is a product
|
|
58
|
+
change. `themes.mjs` rewrites hand-written files in the repo, and `git diff` is where you would
|
|
59
|
+
find out about a slip.
|
|
60
|
+
|
|
61
|
+
## `js/hosts/` — touching the machine
|
|
62
|
+
|
|
63
|
+
Resolving each host tool's config directories, writing host-native agents and skills into files the
|
|
64
|
+
user co-owns, and the hook verbs those emitted configs then run.
|
|
65
|
+
|
|
66
|
+
| module | owns |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `hosts.mjs` | The four host config dirs (opencode/claude/bob/copilot), plus `resolvePath`/`expanduser` |
|
|
69
|
+
| `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `learn` |
|
|
70
|
+
| `settings.mjs` | Merges into user-owned `settings.json`/`opencode.json`; owns the hook shim and the managed blocks |
|
|
71
|
+
| `native.mjs` | Capability specs → host-native subagents and skills. The impure half of the emit |
|
|
72
|
+
| `opencode.mjs` | OpenCode-only extras: colour themes, primary agent, `/commands`, plugins, overrides stub |
|
|
73
|
+
| `installs.mjs` | Detects deployed installs and reads back their theme, footprint, posture and mode |
|
|
74
|
+
| `mcp.mjs` | MCP server presets, the per-host `mcpServers` config read/write, and toggle state |
|
|
75
|
+
| `link.mjs` | `geneseed link`/`unlink` — the PATH shim, and the only Windows USER-Path registry edit |
|
|
76
|
+
|
|
77
|
+
**Before editing:** `hooks.mjs` **is the hook path** — the verdict travels as JSON on stdout and
|
|
78
|
+
every arm exits 0, so one stray printed byte turns a blocking gate silently permissive, and anything
|
|
79
|
+
imported here is paid on every tool call. `hosts.mjs` is shared by the generator, the CLI and the
|
|
80
|
+
hook path, so it must stay free of `child_process`. `installs.mjs` reads through `readMaybe`, which
|
|
81
|
+
folds CRLF — a bare `readFileSync` here is a bug.
|
|
82
|
+
|
|
83
|
+
## `js/inspect/` — the read-only half
|
|
84
|
+
|
|
85
|
+
Everything that inspects an already-built or already-deployed harness and reports on it.
|
|
86
|
+
|
|
87
|
+
| module | owns |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `doctor.mjs` | The `geneseed doctor` **driver** only: picks themes, runs the checks, prints the verdict |
|
|
90
|
+
| `checks-build.mjs` | Gates over what the build PRODUCED: links, tokens, theme parity, OpenCode colours, bundle drift |
|
|
91
|
+
| `checks-repo.mjs` | Gates over the repo's own records: `registry.json`, committed secrets, the hook shim, vendored pins |
|
|
92
|
+
| `checks-authoring.mjs` | Self-consistency: law/doctrine metadata, constitution numbering, the counts README and the web quote |
|
|
93
|
+
| `scan.mjs` | The shared walk/sort/strip/test primitives all three `checks-*` use. Owns no judgement itself |
|
|
94
|
+
| `validate.mjs` | The `--validate-only` verb: renders a sandbox, scans it for tokens and dead links, own exit contract |
|
|
95
|
+
| `inventory.mjs` | The one catalog walk and its taxonomy tables, read by status, the TUI and the web console |
|
|
96
|
+
| `status.mjs` | `geneseed status` and `version` — the panel data, counts, accent colour, version verdict |
|
|
97
|
+
| `diff.mjs` | `geneseed diff` — what a deployed install has that a fresh render of `src/` does not |
|
|
98
|
+
| `excludes.mjs` | `geneseed exclude add|remove|list` — the WRITER for `excludes.json`; `hooks.mjs` only reads it |
|
|
99
|
+
| `registry.mjs` | `installs.json` under XDG — the persistent deploy-root list `rebuild-all` re-emits into |
|
|
100
|
+
|
|
101
|
+
**Before editing:** every check returns an array of problem strings, empty when clean — and since
|
|
102
|
+
deleting any check leaves a clean run **byte-identical**, each one is gated by a PLANTED FAULT, not
|
|
103
|
+
by a comparison. `scan.mjs`'s `TOKEN_RE` must stay in step with `render.mjs`'s. `registry.mjs`
|
|
104
|
+
swallows its own errors on purpose: a registry hiccup must never fail a build.
|
|
105
|
+
|
|
106
|
+
## `js/lib/` — the primitives
|
|
107
|
+
|
|
108
|
+
Zero dependencies. **Every function here exists because the Node default is wrong for this tool** —
|
|
109
|
+
each one's docblock says which default, and why. Change a rule and you change what the tool writes
|
|
110
|
+
into files users have committed, so change the test that states it in the same commit.
|
|
111
|
+
|
|
112
|
+
| module | owns |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `fs.mjs` | All disk and stdout/stderr writes. The single owner of the `\n` → `os.linesep` translation |
|
|
115
|
+
| `json.mjs` | The int/float-preserving parser (`JsonNumber`) plus every dumps/repr renderer |
|
|
116
|
+
| `paths.mjs` | Case folding, ordering, containment, spelling, PATH lookup — the platform-shaped rules |
|
|
117
|
+
| `text.mjs` | String primitives: `codePointLength`, `WHITESPACE`, `parseIntStrict`, `percentDecode` |
|
|
118
|
+
| `udiff.mjs` | `geneseed diff`'s own difflib clone — the alignment IS the printed hunks — plus `splitLines` |
|
|
119
|
+
| `proc.mjs` | One constant: the `windowsHide` flag every spawn in `js/` must reuse |
|
|
120
|
+
|
|
121
|
+
**Before editing:** parse with `parseJson`, never `JSON.parse` — the latter collapses int `1` and
|
|
122
|
+
float `1.0` into one double, and that value gets written into a user's file. Do not swap in a diff
|
|
123
|
+
library for `udiff.mjs`: it is not Myers, ties resolve to the earliest match. Every function in
|
|
124
|
+
`paths.mjs` branches on `process.platform`, so green on one OS proves nothing about the other.
|
|
125
|
+
|
|
126
|
+
## `js/maintain/` — lifecycle
|
|
127
|
+
|
|
128
|
+
Verbs that change an install that already exists, as opposed to rendering one.
|
|
129
|
+
|
|
130
|
+
| module | owns |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `update.mjs` | `upgrade`/`sync-self`/`bootstrap` — git pull, doctor gate, rebuild, each via a respawn |
|
|
133
|
+
| `setup.mjs` | The install wizard, and the CLI's only synchronous stdin readers (`ask`/`confirm`/`askChoice`) |
|
|
134
|
+
| `uninstall.mjs` | Removes or disables an install: manifest-driven reversal, plus stash deactivate/reactivate |
|
|
135
|
+
| `migrate.mjs` | `geneseed migrate` — all-or-nothing re-emit moving installs onto the npm install shape |
|
|
136
|
+
| `memory.mjs` | `geneseed memory list|rm` — CLI-side fact deletion, twin of the web DELETE endpoint |
|
|
137
|
+
|
|
138
|
+
**Before editing:** `update.mjs` is the one module that MUST spawn rather than import — the code on
|
|
139
|
+
disk is replaced mid-run, so an in-process call would validate the *old* source and then render it.
|
|
140
|
+
`setup.mjs` is the whole program's stdin layer, not just setup's. `memory.mjs` hard-unlinks user
|
|
141
|
+
facts with no archive path.
|
|
142
|
+
|
|
143
|
+
## `js/ui/` — the terminal
|
|
144
|
+
|
|
145
|
+
| module | owns |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `cli.mjs` | The whole CLI surface as data (`js/cli-table.json`) plus `--help` rendering and text wrapping |
|
|
148
|
+
| `menu.mjs` | Entry points for a bare `geneseed` and for `menu` — the web-vs-terminal fork |
|
|
149
|
+
| `tui.mjs` | The `tui` verb plus the panel's pure layout half: display width, glyphs, fit/truncate, rows |
|
|
150
|
+
| `anim.mjs` | Per-theme install ASCII art and its line animation — the only ANSI-emitting file here |
|
|
151
|
+
|
|
152
|
+
**Before editing:** there is no argument-parser *definition* in the code — every verb, flag, default
|
|
153
|
+
and help string lives in `js/cli-table.json`, and the table IS the parser. Most of `tui.mjs`'s
|
|
154
|
+
exports have no caller inside it: they are the curses-free half of a panel that does not exist.
|
|
155
|
+
|
|
156
|
+
## `js/web/` — the console
|
|
157
|
+
|
|
158
|
+
A dependency-free localhost HTTP daemon that serves the built React UI from `web/dist` and exposes
|
|
159
|
+
the deployed harness as JSON.
|
|
160
|
+
|
|
161
|
+
| module | owns |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `server.mjs` | `serve()`/`cmdWeb`: UI-build precheck, socket bind, token mint, daemon record wiring |
|
|
164
|
+
| `routes.mjs` | Route DECLARATION only: the `POST_ROUTES` table and the ported/unported/declined sets |
|
|
165
|
+
| `handler.mjs` | One function per request: dispatch, gzip, Host/CSRF guards, static SPA fallback |
|
|
166
|
+
| `api.mjs` | `WebState` plus every GET read endpoint; owns `STATE_ROUTES`, `PREFIX_ROUTES`, `NotFound` |
|
|
167
|
+
| `actions.mjs` | The mutating endpoints — rules, profile, memory, MCP, excludes — plus install/deploy argv |
|
|
168
|
+
| `daemon.mjs` | Detached web start/stop/status/restart, the `.geneseed-web.json` record, and `openUrl` |
|
|
169
|
+
| `jobs.mjs` | `JobManager` and the action→argv table. The only module here that spawns job children |
|
|
170
|
+
| `docs.mjs` | The Docs tab: the `docs/web/` page registry, `?harness=` filtering, the five page kinds |
|
|
171
|
+
| `graph.mjs` | `/api/graph` alone — the agent/skill/law cross-link matrix built from rendered bodies |
|
|
172
|
+
| `activity.mjs` | The `activity/` session files: pid-liveness prune, list, detail, and the on/off flag |
|
|
173
|
+
|
|
174
|
+
**Before editing:** `makeHandler(state, jm, token, dist, holder)`'s arity is load-bearing — three
|
|
175
|
+
test call sites construct one directly and drive it without a socket. Four of `routes.mjs`'s
|
|
176
|
+
exported sets are deliberately EMPTY and must stay declared: each is half of a partition test.
|
|
177
|
+
`graph.mjs` contains a literal NUL byte, so `grep`/`ripgrep` treat it as binary and skip it.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Where do I add…
|
|
182
|
+
|
|
183
|
+
| I want to | open first | then |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| a doctrine rule | `src/doctrines/<pack>.md` — APPEND, ids run contiguously | `docs/extending.md` §2a — five files, `--sync-themes`, restyle 14 voices by hand |
|
|
186
|
+
| a law (invariant) | `src/laws/universal.md` — **APPEND, never insert** | `docs/extending.md` §2c. Amend the FIRST sentence: lean footprint keeps nothing else |
|
|
187
|
+
| a skill or an agent | `src/skills/<name>.md` or `src/agents/<name>.md` | `docs/extending.md` §3 — eight steps. Write the FILE before the table row or the build aborts |
|
|
188
|
+
| a doctor check | `js/inspect/checks-{build,repo,authoring}.mjs` | register with `ran(...)` in `doctor.mjs`, then a PLANTED FAULT in `tests/unit/harness.test.mjs` |
|
|
189
|
+
| a web endpoint | GET → `js/web/api.mjs`; POST → `js/web/actions.mjs` | declare it in `js/web/routes.mjs`; the partition test probes the real handler |
|
|
190
|
+
| a CLI verb | `js/cli-table.json` — the table IS the parser | the `VERBS` row in `bin/geneseed-cli.mjs`, the row count in `cli_table.test.mjs`, `NATIVE` in **two** test files |
|
|
191
|
+
| a hook verb | `js/hosts/hooks.mjs` | `claudeHookGroups` in `settings.mjs`, `VERBS` in `bin/geneseed-hook.mjs`. Keep the import graph tiny |
|
|
192
|
+
| a console doc page | `docs/web/<id>.md` — the stem is the id | `docs/extending.md` §4c. Only `kind: "concept"` gets `{N_*}` substitution |
|
|
193
|
+
| an OpenCode plugin | `adapters/opencode/plugins/geneseed-<name>.js` | `docs/extending.md` §4a — the `geneseed-` prefix is mechanically load-bearing |
|
|
194
|
+
| to change a `js/lib/` primitive | `tests/fixtures/pure_probe.mjs` **first** | the docblock says which Node default it deliberately departs from — change that sentence too, or the next reader restores the default |
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Known debris
|
|
199
|
+
|
|
200
|
+
* **`js/hosts/settings.mjs` carries a private second copy of Python's whitespace class**, duplicating
|
|
201
|
+
`WHITESPACE` in `js/lib/text.mjs`. `text.mjs` is the owner.
|
|
202
|
+
* **`web/src/pages/Skills.jsx`'s `SKILL_CATS` is a fourth, ungated copy of the skill taxonomy** —
|
|
203
|
+
nothing compares it to `SKILL_CLASS`, and an unknown category renders as Build with no warning.
|