geneseed 3.1.1 → 3.1.3

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +123 -1
  2. package/DESIGN.md +15 -15
  3. package/README.md +1 -1
  4. package/SETUP.md +1 -1
  5. package/SHIPPED.md +1 -1
  6. package/bin/{geneseed.mjs → build-driver.mjs} +39 -42
  7. package/bin/geneseed-cli.mjs +33 -32
  8. package/bin/geneseed-hook.mjs +7 -7
  9. package/docs/design-history.md +40 -40
  10. package/docs/extending.md +111 -83
  11. package/docs/web/exclusions.md +1 -1
  12. package/docs/web/footprint.md +1 -1
  13. package/docs/web/foreman-mode.md +2 -2
  14. package/docs/web/themes-author.md +1 -1
  15. package/docs/web-ui.md +41 -29
  16. package/harness.config.json +1 -1
  17. package/js/README.md +203 -0
  18. package/js/build/bundle.mjs +231 -0
  19. package/js/{catalog.mjs → build/catalog.mjs} +7 -7
  20. package/js/build/emit-claude.mjs +287 -0
  21. package/js/build/emit-common.mjs +255 -0
  22. package/js/build/emit-opencode.mjs +214 -0
  23. package/js/{generate.mjs → build/generate.mjs} +27 -28
  24. package/js/{render.mjs → build/render.mjs} +13 -9
  25. package/js/{checkout.mjs → build/source.mjs} +9 -9
  26. package/js/build/stubs.mjs +256 -0
  27. package/js/{themes.mjs → build/themes.mjs} +24 -24
  28. package/js/build/version.mjs +155 -0
  29. package/js/{hooks.mjs → hosts/hooks.mjs} +47 -44
  30. package/js/{hosts.mjs → hosts/hosts.mjs} +30 -28
  31. package/js/{installs.mjs → hosts/installs.mjs} +18 -16
  32. package/js/{link.mjs → hosts/link.mjs} +29 -28
  33. package/js/{mcp.mjs → hosts/mcp.mjs} +10 -9
  34. package/js/{native.mjs → hosts/native.mjs} +24 -22
  35. package/js/{opencode.mjs → hosts/opencode.mjs} +12 -7
  36. package/js/{settings.mjs → hosts/settings.mjs} +63 -55
  37. package/js/inspect/checks-authoring.mjs +761 -0
  38. package/js/inspect/checks-build.mjs +231 -0
  39. package/js/inspect/checks-repo.mjs +308 -0
  40. package/js/{diff.mjs → inspect/diff.mjs} +34 -31
  41. package/js/inspect/doctor.mjs +286 -0
  42. package/js/{excludes.mjs → inspect/excludes.mjs} +30 -33
  43. package/js/{inventory.mjs → inspect/inventory.mjs} +20 -17
  44. package/js/{registry.mjs → inspect/registry.mjs} +12 -10
  45. package/js/inspect/scan.mjs +125 -0
  46. package/js/{status.mjs → inspect/status.mjs} +27 -25
  47. package/js/inspect/validate.mjs +199 -0
  48. package/js/lib/fs.mjs +37 -759
  49. package/js/lib/json.mjs +344 -0
  50. package/js/lib/paths.mjs +242 -0
  51. package/js/lib/proc.mjs +3 -3
  52. package/js/lib/text.mjs +181 -0
  53. package/js/lib/udiff.mjs +1 -1
  54. package/js/{memory.mjs → maintain/memory.mjs} +10 -10
  55. package/js/{migrate.mjs → maintain/migrate.mjs} +28 -26
  56. package/js/{setup.mjs → maintain/setup.mjs} +34 -32
  57. package/js/{uninstall.mjs → maintain/uninstall.mjs} +55 -56
  58. package/js/{update.mjs → maintain/update.mjs} +51 -51
  59. package/js/{anim.mjs → ui/anim.mjs} +30 -28
  60. package/js/{cli.mjs → ui/cli.mjs} +10 -10
  61. package/js/{menu.mjs → ui/menu.mjs} +10 -9
  62. package/js/{tui.mjs → ui/tui.mjs} +36 -34
  63. package/js/web/actions.mjs +84 -85
  64. package/js/web/activity.mjs +22 -21
  65. package/js/web/api.mjs +154 -51
  66. package/js/web/daemon.mjs +319 -0
  67. package/js/web/docs.mjs +24 -21
  68. package/js/web/graph.mjs +0 -0
  69. package/js/web/handler.mjs +442 -0
  70. package/js/web/jobs.mjs +21 -19
  71. package/js/web/routes.mjs +245 -0
  72. package/js/web/server.mjs +56 -987
  73. package/package.json +11 -3
  74. package/src/doctrines/README.md +1 -1
  75. package/themes/opencode/README.md +1 -1
  76. package/web/dist/assets/{Activity-D_sYBlW-.js → Activity-B49fE_ug.js} +1 -1
  77. package/web/dist/assets/{ActivityDetail-BomKcMhy.js → ActivityDetail-abt7hzD2.js} +1 -1
  78. package/web/dist/assets/{Diff-BSvi1WUg.js → Diff-D3_bVkih.js} +1 -1
  79. package/web/dist/assets/{Doctor-Cz1s-y1n.js → Doctor-hXp7srsx.js} +1 -1
  80. package/web/dist/assets/{Graph-CuntzFJ0.js → Graph-DvYMtlvl.js} +1 -1
  81. package/web/dist/assets/Harness-BIWei7N5.js +1 -0
  82. package/web/dist/assets/Laws-CxmnRdwt.js +3 -0
  83. package/web/dist/assets/{Library-DXhTN9ju.js → Library-MjbOkHIW.js} +1 -1
  84. package/web/dist/assets/{Markdown-BoiZcdUl.js → Markdown-Dx_w0faR.js} +1 -1
  85. package/web/dist/assets/{Profile-DfCHlZ03.js → Profile-BRFvv2gr.js} +1 -1
  86. package/web/dist/assets/{Rules-eND7hJv9.js → Rules-CfsV1EVy.js} +1 -1
  87. package/web/dist/assets/{Skills-CDl9opZO.js → Skills-DArA6IHS.js} +1 -1
  88. package/web/dist/assets/{StatusBadge-DSDtcArX.js → StatusBadge-C9bZOEZk.js} +1 -1
  89. package/web/dist/assets/{activity-Dmb0FTAx.js → activity-Cc0n2jwx.js} +1 -1
  90. package/web/dist/assets/index-Bx3oxZ1o.css +1 -0
  91. package/web/dist/assets/index-DC3-GlCY.js +1 -0
  92. package/web/dist/assets/index-DP035wPa.js +12 -0
  93. package/web/dist/assets/{index-Dg58ScBe.js → index-dHi-7M-F.js} +1 -1
  94. package/web/dist/index.html +2 -2
  95. package/web/src/pages/Laws.jsx +5 -19
  96. package/docs/declined.md +0 -200
  97. package/docs/limits.md +0 -329
  98. package/js/doctor.mjs +0 -1750
  99. package/js/emit.mjs +0 -1411
  100. package/web/dist/assets/About-DyK7yRy7.js +0 -1
  101. package/web/dist/assets/Harnesses-DgRyOviS.js +0 -1
  102. package/web/dist/assets/Laws-CWk3Mpri.js +0 -3
  103. package/web/dist/assets/Themes-CoAWuIeM.js +0 -2
  104. package/web/dist/assets/index-B-mXquBZ.css +0 -1
  105. package/web/dist/assets/index-CH_Z725C.js +0 -12
  106. package/web/dist/assets/index-jrY4m9jP.js +0 -1
package/CHANGELOG.md CHANGED
@@ -6,7 +6,129 @@ labels in `harness.config.json`. The canonical identity of an *installed* harnes
6
6
  is the source fingerprint in `.geneseed-version` (see `geneseed version`), not this
7
7
  label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
8
8
 
9
- ## [Unreleased]
9
+ ## [3.1.3] — 2026-08-24
10
+
11
+ ### Added
12
+
13
+ - **The Journal Atlas console.** A fourth dashboard lens, `journal`: growth rings per section,
14
+ the newest hand-authored entries, and the constitution drawn as a map — a hub of rules in
15
+ force, its three tiers, and dashed edges to the skills, agents, memory and wiki they govern.
16
+ Every node navigates. The map is data-driven where the design was hand-placed: circles size
17
+ to the text they hold and the pack nodes ride a fitted arc, so a harness with seven
18
+ invariants or six packs draws the same picture rather than overflowing it.
19
+ - **`atlas`, a twelfth console skin** — loam dark, sage ink, Space Grotesk, pills, in both
20
+ light and dark. Atlas and Greenhouse default to the journal lens; every other skin is
21
+ unchanged, and the lens is pickable from any of them in Settings.
22
+ - **`GET /api/recent`** — the newest file-backed entries across the harness. Nothing on the API
23
+ carried a per-entry date: every catalog row is `{name, title, desc, source}` and a browser
24
+ cannot stat a path. It answers only the four sections a person writes by hand, and drops any
25
+ section too large to stat exhaustively rather than reporting the newest of a truncated
26
+ sample. Declared in `GET_BEYOND_REF` — the GET twin of `POST_BEYOND_REF` — so the route
27
+ partition keeps its set equality against a reference surface that must not be rewritten.
28
+ - **`overview.install` now reports `build_epoch`** beside `build_time`, so a relative label is
29
+ computed rather than parsed back out of a formatted stamp.
30
+ - **`Ctrl`/`⌘`+`K` opens Spotlight**, beside `/`. The chord is the one that still works from
31
+ inside a text field, which is where `/` deliberately does not fire.
32
+
33
+ ### Changed
34
+
35
+ - **The rail regroups into Codex · Care · Setup**, with the Dashboard on its own and a vitals
36
+ card above the index — germination, drift, the doctor's verdict, and how long ago the harness
37
+ was built, on every page. Doctor badges `clean` in words; it used to badge nothing, which
38
+ reads the same as never having run.
39
+ - **Harnesses and Themes merge into one Harness page**, and About folds into Settings. No route
40
+ dies: `#/harnesses`, `#/themes` and `#/about` all still resolve. An uninstalled host now
41
+ offers one `Install…` that discloses its four choices instead of rendering them inline.
42
+ - **The voice gallery is a reference, not a second control.** Every install picks its own voice
43
+ beside its own footprint, posture and mode; a gallery at the top of the page could only ever
44
+ act on one of them. It keeps the taglines — which a `<select>` of bare names cannot show —
45
+ and gives up the buttons.
46
+ - **The rail width is responsive**, `clamp(232px, 18vw, 256px)`, between the two widths the
47
+ console has been designed at. The breakpoint overrides still win, so it still collapses.
48
+
49
+ ### Fixed
50
+
51
+ - **The Spotlight input was never wired to its own listbox.** No `role="combobox"`,
52
+ `aria-expanded`, `aria-controls`, or `aria-activedescendant`, and the rows had no ids —
53
+ so the arrow keys moved a highlight that was visual and nothing else, and a screen reader
54
+ was never told results had appeared.
55
+ - **The rail overflowed the document** on short windows once the vitals card was added: a fixed
56
+ `100vh` box with no scroller of its own produced a second scrollbar beside the page's.
57
+ - **`web/dist` is now gated on the publish path.** It ships in `files[]`, and until now nothing
58
+ on that path built it or looked at it — the freshness check ran only on pull requests, while
59
+ `workflow_dispatch` can publish any ref.
60
+ - **Two tests in `laws.test.jsx` failed roughly one run in three.** They gated on rows existing,
61
+ but the pack selection is seeded in an effect, so there is a rendered frame in which every
62
+ pack reads as off. The same race, twice, and neither was a product defect.
63
+
64
+ ## [3.1.2] — 2026-08-24
65
+
66
+ ### Changed
67
+
68
+
69
+ - **`js/` is now domain folders, not a flat drawer.** The tree splits into `build/`, `hosts/`,
70
+ `inspect/`, and `main/`, and the four largest modules split with it: `server.mjs` into
71
+ routes / handler / daemon / server, `emit.mjs` into common / stubs / version / bundle,
72
+ `doctor.mjs` into scan plus three check groups, and `fs.mjs` into fs / json / paths / text.
73
+ The Node generator driver is renamed to `bin/build-driver.mjs`; the `geneseed-build` bin name
74
+ is unchanged, so nothing on an installed harness has to move.
75
+
76
+ **No emitted byte moves.** The 261-cell corpus replays identical across all 14 themes, which
77
+ is the whole warrant for a reorganization this size — 151 files, 8502 insertions, 6481
78
+ deletions, and zero observable difference for anyone who installed 3.1.1.
79
+
80
+ - **Docs point at the code that exists.** A module map and front door in `js/README.md`, with a
81
+ doctor arm that keeps them honest; 201 stale paths repointed in prose, the 35 split-module
82
+ pointers resolved, and 69 docblock claims corrected where the code had stopped supporting
83
+ what the comment promised. The vestigial `emit.mjs` is deleted.
84
+
85
+ - **The Python-era `py` identifier prefix is retired**, and `js/ bin/ tests/ adapters/` are now
86
+ linted — the half of the tree that never was.
87
+
88
+
89
+ - **Two of "the four things that are genuinely hard" are resolved rather than rewritten.**
90
+ `docs/extending.md` §5.2 — seven theme keys pinned byte-for-byte, so restyling a tagline was a
91
+ red gate — and §5.4 — *a snapshot red is a finding, not a step*. Theme text is free to edit,
92
+ and a red test is a step again. Editing a CLI verb's help text or flags is an ordinary edit.
93
+
94
+ - **The standing rule is now "expected values are written out, not recorded"**: change the
95
+ expectation and the sentence that explains it in the same commit, and say why in the message.
96
+
97
+ ### Added
98
+
99
+
100
+ - **A gate on how a runtime dependency may enter**: vendored under `js/vendor/<name>/` as tracked
101
+ source behind a relative import, never installed. Fixtures run from the OS temp root and cannot
102
+ resolve a bare specifier, and neither can a fresh `git clone`; devDependencies are unaffected.
103
+
104
+ ### Removed
105
+
106
+
107
+ - **The recorded corpora, and the notion of a corpus with them.** `tests/__snapshots__/` held
108
+ 2.6 MB of transcripts taken from the CPython reference before it was deleted, replayed by
109
+ `tests/snapshot/`. With no recorder left they could never be re-blessed, so a red run could
110
+ only ever mean *you changed something*, never *you broke something* — and every deliberate
111
+ change to emitted text arrived as a red gate. The 1381-cell retirement of 2026-08-17 had
112
+ already made the argument; this finishes it.
113
+
114
+ **What they owned is now written out.** `wrapText`, `dwidth` and `formatHelp` had zero unit
115
+ coverage — the recordings were their only gate — so `tests/unit/text_layout.test.mjs` states
116
+ their rules explicitly and renders all 29 verbs. The JSONC, Windows-PATH and token-report
117
+ tables moved inline; `sync_themes` became an owned fixture with plain-text expectations. The
118
+ "corpus contains what it is named for" gates went with them: when the apostrophe is visible in
119
+ the file, nothing has to assert it is there.
120
+
121
+ Recoverable at tag `corpus-reference-v3.1.2`. Nothing can regenerate them.
122
+
123
+ - **Three migration statistics.** "N of the M subcommands run from Node", "0 commands have no
124
+ Node twin", and a native-verb list duplicated across two files purely so each could police the
125
+ other. A migration percentage is not a fact about a finished tool. The durable property under
126
+ the first two — every declared verb is dispatched by an entry point — is now asserted directly.
127
+
128
+ - **`golden.mjs --against`**, which could not have worked since the emit corpus was retired, and
129
+ `orphanCheck`, `narrowingReason` and `tests/helpers/snapshot_io.mjs` with it. The three
130
+ surviving modes are all self-comparisons.
131
+
10
132
 
11
133
  ## [3.1.1] — 2026-08-21
12
134
 
package/DESIGN.md CHANGED
@@ -35,7 +35,7 @@ vault or a specific tool's hooks.
35
35
  names (`ONT_TELOS`, `ONT_EVIDENCE`, `ONT_DECISIONS`, `ONT_CONDUCT`), a few rare
36
36
  technical nouns (`Context`, `Scripts`, `Charter`), and the folder names (`laws/`,
37
37
  `ontology/`, `doctrines/`, `agents/`, `skills/`, `memory/`, `notebook/` via `DIR_*`).
38
- These live in the `STRUCTURE` map in `js/render.mjs` and are laid on top of every
38
+ These live in the `STRUCTURE` map in `js/build/render.mjs` and are laid on top of every
39
39
  render, so a theme can never move a path, a link, or a heading number. No theme file
40
40
  defines a `DIR_*` or an `ONT_*` key, and none may: an ontology citation spells the
41
41
  section on both sides (`{{ONTOLOGY}}: {{ONT_TELOS}}`), so heading and reference move
@@ -102,7 +102,7 @@ vault or a specific tool's hooks.
102
102
  Rules, `I`..`IX`, headed `### {{LAW}} <roman> — {{LEX_<roman>}}`.
103
103
  - **Doctrines** (`src/doctrines/{craft,rigor,ops,process}.md`) — *how work is done
104
104
  here*. Practice rules addressed by pack and number, cited as `Doctrine process 5`.
105
- `PACK_ORDER` (`js/checkout.mjs`) fixes the order craft → rigor → ops → process, which
105
+ `PACK_ORDER` (`js/build/source.mjs`) fixes the order craft → rigor → ops → process, which
106
106
  is narrative and deliberately not alphabetical.
107
107
 
108
108
  **The Ontology and the Invariants are never toggleable** — every build carries both,
@@ -128,7 +128,7 @@ vault or a specific tool's hooks.
128
128
  repeal one; a user rule outranks a doctrine rule outright; nothing outranks an
129
129
  invariant. And an always-on tier may never cite a toggleable one — a `--doctrines craft`
130
130
  build would otherwise ship an invariant pointing at text its `AGENT.md` does not
131
- contain — so `constitutionProblems` (`js/doctor.mjs`) refuses a `{{DOCTRINE}}` token
131
+ contain — so `constitutionProblems` (`js/inspect/checks-authoring.mjs`) refuses a `{{DOCTRINE}}` token
132
132
  anywhere under `src/ontology/` or `src/laws/`.
133
133
 
134
134
  The bar for a **new invariant** is the highest in the tree: universal (it binds *every*
@@ -146,9 +146,9 @@ vault or a specific tool's hooks.
146
146
  |---|---|---|---|
147
147
  | body | append to `src/laws/universal.md` — never insert; nothing resolves a cross-reference against the canon | append to `src/doctrines/<pack>.md`, `### {{DOCTRINE}} <pack> <n> — {{DOC_<PACK>_<n>}}`; ids must run contiguously from 1 | a new `src/doctrines/<pack>.md` with its `**Name** — lead line` |
148
148
  | themed title | `LEX_<roman>` in all 15 theme files | `DOC_<PACK>_<n>` in all 15 theme files | `PACK_<NAME>` in all 15, plus each rule's `DOC_*` |
149
- | class | `LAW_CLASS` in `js/inventory.mjs`, one of the six in `LAW_CLASSES` | **none** — a doctrine rule's class *is* its pack, and there is no second taxonomy over it | — |
149
+ | class | `LAW_CLASS` in `js/inspect/inventory.mjs`, one of the six in `LAW_CLASSES` | **none** — a doctrine rule's class *is* its pack, and there is no second taxonomy over it | — |
150
150
  | console copy | a `LAW_META` row in `web/src/pages/Laws.jsx` (keyed by arabic number) | a `DOCTRINE_META` row in the same file (keyed `<pack>.<n>`) | one `DOCTRINE_META` row per rule |
151
- | registration | the numeral is the registration | the number is the registration | the pack name in `PACK_ORDER`, `js/checkout.mjs` |
151
+ | registration | the numeral is the registration | the number is the registration | the pack name in `PACK_ORDER`, `js/build/source.mjs` |
152
152
  | renumber risk | high — appending is the only safe move | none — packs are numbered independently | none |
153
153
  | counts | README badge + the `N universal laws` prose, `SHIPPED.md`'s triple, the web onboarding copy | **none** — the console spends the `N_PACKS` / `N_PACKS_ACTIVE` / `N_DOCTRINE_RULES` count tokens, all computed at request time | none, same reason |
154
154
  | changelog | `CHANGELOG.md` | `CHANGELOG.md` | `CHANGELOG.md` |
@@ -160,7 +160,7 @@ vault or a specific tool's hooks.
160
160
  synonym. `--sync-themes` writes the template's placeholder and only *prints* which keys
161
161
  to restyle; a shipped placeholder passes every gate.
162
162
 
163
- Almost all of it is gated, across six named checks in `js/doctor.mjs` —
163
+ Almost all of it is gated, across six named checks in `js/inspect/checks-build.mjs` —
164
164
  `themeParityProblems` (key parity across the voices), `lawMetaProblems` and
165
165
  `doctrineMetaProblems` (the console's Principle column, in both directions),
166
166
  `constitutionProblems` (pack numbering and filing, `LEX_I..LEX_IX` as an *equality*, the
@@ -195,11 +195,11 @@ theme file defines a `DIR_*` key, so `laws/` is `laws/` in all fourteen voices.
195
195
  | Notebook | `src/notebook/` | `notebook/` | the agent's sovereign space — any medium, seed-once charter the agent may rewrite; only `.gitignore` re-asserted |
196
196
  | Posture | `src/postures/` | inlined into `AGENT.md` | how much ceremony the agent brings to a task |
197
197
  | Mode | `src/modes/` | inlined into `AGENT.md` | the working axis (solo, foreman, …) — orthogonal to posture |
198
- | Context | `js/emit.mjs` | `context.json` | empty per-repo manifest, written once and never overwritten; git-ignore it |
198
+ | Context | `js/build/stubs.mjs` | `context.json` | empty per-repo manifest, written once and never overwritten; git-ignore it |
199
199
  | Themes | `themes/*.json` | — | token → label maps; `themes/opencode/` is a separate colour system |
200
200
  | Release label | `harness.config.json` | `.geneseed-version` | the theme and the human-readable version the driver reads; the canonical identity is the source fingerprint, not this string |
201
201
  | Lifecycle | `registry.json` | — | maintainer-side status/version/owner per agent and skill; never rendered into a bundle |
202
- | Generator | `bin/geneseed.mjs` (`geneseed-build`) | — | substitution + `<!-- INCLUDE: -->` inlining, and the nine `--emit` targets |
202
+ | Generator | `bin/build-driver.mjs` (`geneseed-build`) | — | substitution + `<!-- INCLUDE: -->` inlining, and the nine `--emit` targets |
203
203
  | CLI, as data | `js/cli-table.json` | — | the owned document the entry point parses its own argv from |
204
204
  | Automation | `bin/geneseed-cli.mjs` (`geneseed`) | — | 25 verbs — `build` / `doctor` / `status` / `prompt` / `diff` / `setup` / `web` / `upgrade` / … |
205
205
  | Hooks | `bin/geneseed-hook.mjs` (`geneseed-hook`) | — | the four verbs an emitted `settings.json` invokes: `context` / `git-gate` / `rule-gate` / `learn` |
@@ -212,28 +212,28 @@ theme file defines a `DIR_*` key, so `laws/` is `laws/` in all fourteen voices.
212
212
  - Unknown tokens are left visible (debugging aid); `doctor` flags them.
213
213
  - Node built-ins only; no third-party dependencies, ever. (`package.json` carries neither a
214
214
  `dependencies` nor a `devDependencies` key, and a test asserts it.)
215
- - **`js/settings.mjs` is the half of the emit that edits files you co-own** — the
215
+ - **`js/hosts/settings.mjs` is the half of the emit that edits files you co-own** — the
216
216
  `settings.json` / `opencode.json` merges, JSONC parsing, the hook shim and the managed
217
217
  CLAUDE.md block. It is a separate module because every function in it reconciles
218
218
  Geneseed's claim with content Geneseed did not write, and because eleven of its names are
219
- driven by the *runtime* as well as by an emit (`js/mcp.mjs` uses ten of them
219
+ driven by the *runtime* as well as by an emit (`js/hosts/mcp.mjs` uses ten of them
220
220
  for deactivate, remerge, reactivate and uninstall; `exclude` and `doctor` use the rest).
221
- Its dependency closure points one way only: nothing in it calls into `js/render.mjs` or
222
- `js/emit.mjs`. Keep it that way — that closure is what makes it a unit.
221
+ Its dependency closure points one way only: nothing in it calls into `js/build/render.mjs` or
222
+ `js/build/bundle.mjs`. Keep it that way — that closure is what makes it a unit.
223
223
  - **Every emit runs five stages in one order: `RENDER* → WIRE* → PRUNE → MANIFEST →
224
- VERIFY`.** RENDER writes files Geneseed owns wholesale; WIRE is `js/settings.mjs`
224
+ VERIFY`.** RENDER writes files Geneseed owns wholesale; WIRE is `js/hosts/settings.mjs`
225
225
  reconciling files you co-own; PRUNE removes what the previous manifest owned and this
226
226
  emit no longer produces; MANIFEST records both; VERIFY re-reads the merge to confirm it
227
227
  stuck. WIRE must precede MANIFEST because wiring is what fills the `managed` record the
228
228
  manifest stores, and no RENDER may follow a WIRE because a render writes wholesale a file
229
229
  a wire has just reconciled. `tests/unit/emit_phase_order.test.mjs` fails the build if any of
230
230
  the nine emits drifts out of that order, or if a new file-mutating routine in
231
- `js/settings.mjs` is called from an emit without being classified. RENDER and WIRE now run
231
+ `js/hosts/settings.mjs` is called from an emit without being classified. RENDER and WIRE now run
232
232
  in the *same process* — the seam that once spawned a second runtime per emit is gone — so
233
233
  the render and wire dispatchers stay two separate functions and two separate statements on
234
234
  purpose: that shape is the only thing left for the walker to check, and a gate refuses an
235
235
  emit whose sequence has lost its WIRE.
236
- `bin/geneseed.mjs`'s `main()` is classified too, as the one stage no per-emit gate can see: it
236
+ `bin/build-driver.mjs`'s `main()` is classified too, as the one stage no per-emit gate can see: it
237
237
  writes the `.geneseed-emit` / `-footprint` / `-theme` markers and the install-registry
238
238
  record *after* every emit returns, and nothing that reconciles a file you co-own may run
239
239
  there, because there is no manifest to record the claim and no teardown able to undo it.
package/README.md CHANGED
@@ -93,7 +93,7 @@ Both launchers are thin shims over the Node CLI: they need `node` (22.3+) on `PA
93
93
 
94
94
  Node ≥ 22.3 is the entire dependency list. Every subcommand, all four hooks, every web-console endpoint and both generators run from it, and every commit runs the whole unit suite plus `doctor --all` across every theme × host × footprint, on Linux and Windows both. So an install needs no second interpreter — not for the harness, and not for anything it ships. (Until 2026-08-17 the generator's output was also replayed byte for byte against recordings taken from the Python implementation. Those were retired — see [`docs/limits.md`](docs/limits.md) for the measurement that licensed it and the coverage it cost.)
95
95
 
96
- - **0 commands have no Node twin**: 25 of the 25 subcommands, and all four hook verbs, answer from the Node entry points. `catalog`, `mcp` and `memory` are Node-native faces the CLI table declares on top of that roster. There is no full-screen browse panel — `geneseed tui` and `geneseed menu` are verbs that refuse it by name and print the command list instead, off a terminal as well as on one.
96
+ - **One runtime, no second one to install**: every command and all four hook verbs answer from the Node entry points. `catalog`, `mcp` and `memory` are console-facing verbs the CLI table declares alongside them. There is no full-screen browse panel — `geneseed tui` and `geneseed menu` are verbs that refuse it by name and print the command list instead, off a terminal as well as on one.
97
97
  - **Nothing you install needs an interpreter.** The `token-report` skill is a script rather than prose, and it ships as `scripts/token_report.mjs`, run with `node`; `daydream` and `herdr` hand their inline code to `node -e`. So **every bundle carries** nothing that needs one: no Python file, and no skill that shells out to an interpreter, on any host, theme or footprint. Three tests freeze that — one scans every tracked file for inline code handed to an interpreter, one globs the bundle source, and one replays the report script over seeded transcripts for all four hosts and compares the bytes.
98
98
  - **`upgrade` / `update` / `sync-self` / `bootstrap` are for a git checkout.** They `git pull` the install's own origin. From an npm install they stop before touching anything and name `npm install -g geneseed@latest` as the update instead.
99
99
 
package/SETUP.md CHANGED
@@ -100,7 +100,7 @@ hand? Pick a path below.
100
100
  > **Every `geneseed-build …` below is the generator.** `npm install -g geneseed` puts it
101
101
  > on your `PATH`; without installing anything, `npx -p geneseed geneseed-build --emit
102
102
  > opencode-global` is the same command. From a checkout it is
103
- > `node bin/geneseed.mjs` with the same flags. Every commit re-emits with it across every
103
+ > `node bin/build-driver.mjs` with the same flags. Every commit re-emits with it across every
104
104
  > theme × host × footprint combination and checks the result with `doctor --all`, on Linux
105
105
  > and Windows both. Until 2026-08-17 that output was also compared byte for byte against
106
106
  > recordings taken from the Python implementation; those were retired, and `docs/limits.md`
package/SHIPPED.md CHANGED
@@ -20,7 +20,7 @@ historical pointers, not files distributed with the repo.
20
20
  > **On version numbers.** [`harness.config.json`](harness.config.json)'s `version` is a
21
21
  > human-readable label only. The canonical identity of an installed harness is the
22
22
  > **source fingerprint** stamped in `.geneseed-version` (see `geneseed version`), not the
23
- > string. Current label: **3.1.0**.
23
+ > string. Current label: **3.1.3**.
24
24
 
25
25
  > **Spec status audit (2026-06-15).** Every dated spec authored before today was
26
26
  > verified against the source tree and confirmed implemented (code + tests + docs);
@@ -20,7 +20,7 @@
20
20
  * `js_cfg()` (_build_core.py:199) always sends `structure` and `capabilityLinkRe`, because
21
21
  * the Python originals are module-level names that TESTS MUTATE — `_OWNED` membership
22
22
  * asked one level out. This driver has no Python module to mutate, so it sends neither and
23
- * `js/render.mjs:215`'s `cfg.structure ?? STRUCTURE` and `js/emit.mjs:680`'s
23
+ * `js/build/render.mjs:215`'s `cfg.structure ?? STRUCTURE` and `js/build/bundle.mjs`'s
24
24
  * `cfg.capabilityLinkRe ? ... : <default>` take their right-hand branches for the first
25
25
  * time. Those two fallbacks have been dead code since they were written — always
26
26
  * overridden by the driver that always supplies them. P4 is what makes them live.
@@ -36,35 +36,36 @@ import {
36
36
  } from 'node:fs';
37
37
  import path from 'node:path';
38
38
  import { fileURLToPath } from 'node:url';
39
- import {
40
- build, emitOpencodeRender, emitOpencodeGlobalRender, emitClaudeRender, phaseLog,
41
- } from '../js/emit.mjs';
42
- import { settingsIntegrityCheck } from '../js/settings.mjs';
43
- import { writeText, parseJson, jsonDumpsIndent, withPyNewlines } from '../js/lib/fs.mjs';
39
+ import { build, phaseLog } from '../js/build/bundle.mjs';
40
+ import { emitClaudeRender } from '../js/build/emit-claude.mjs';
41
+ import { emitOpencodeRender, emitOpencodeGlobalRender } from '../js/build/emit-opencode.mjs';
42
+ import { settingsIntegrityCheck } from '../js/hosts/settings.mjs';
43
+ import { writeText, withPlatformNewlines } from '../js/lib/fs.mjs';
44
+ import { parseJson, jsonDumpsIndent } from '../js/lib/json.mjs';
44
45
  // P5c moved these out of this file: `bin/geneseed-cli.mjs` needs the same four resolvers to
45
46
  // find a global install, and a resolver that decides WHERE a driver writes is the last thing
46
47
  // that should exist twice. golden.py's 259 cells are what made the move safe to attempt.
47
48
  import {
48
- GLOBAL_MANIFEST, pyResolve, opencodeConfigDir, claudeConfigDir, bobConfigDir,
49
+ GLOBAL_MANIFEST, resolvePath, opencodeConfigDir, claudeConfigDir, bobConfigDir,
49
50
  copilotConfigDir,
50
- } from '../js/hosts.mjs';
51
+ } from '../js/hosts/hosts.mjs';
51
52
 
52
53
  // P5d moved these out of this file for the reason P5c moved the host resolvers: `harness
53
54
  // status` renders to count, so `bin/geneseed-cli.mjs` needs the same checkout paths and the
54
55
  // same cfg. golden.py's 259 cells all build one, which is what made the move safe.
55
56
  import {
56
- ROOT, CONFIG, THEMES, discoverNames, makeCfg, PACK_ORDER, knownRuleIds,
57
- } from '../js/checkout.mjs';
57
+ ROOT, CONFIG, discoverNames, makeCfg, PACK_ORDER, knownRuleIds,
58
+ } from '../js/build/source.mjs';
58
59
 
59
60
  // P5f moved these, for the same arithmetic a third time: `harness rebuild-all` reads the
60
61
  // registry to find every install it must re-emit, and a CLI verb may not reach into a driver
61
- // for a reader. See js/registry.mjs.
62
- import { registryRecord, registryRoots } from '../js/registry.mjs';
62
+ // for a reader. See js/inspect/registry.mjs.
63
+ import { registryRecord, registryRoots } from '../js/inspect/registry.mjs';
63
64
 
64
65
  // P2. `--sync-themes` crossed into its own module rather than into this file: it is 90 lines
65
66
  // of textual surgery over committed files with a corpus of its own, and it is the half of the
66
67
  // maintainer pair that needs nothing this driver is banned from having.
67
- import { syncThemes } from '../js/themes.mjs';
68
+ import { syncThemes } from '../js/build/themes.mjs';
68
69
 
69
70
  /** The nine `--emit` choices, in build.py:337-338's order. */
70
71
  const EMITS = ['files', 'opencode', 'opencode-global', 'claude', 'claude-global',
@@ -95,24 +96,24 @@ const NATIVE_CATALOG = { opencode: true, claude: true, bob: false, copilot: fals
95
96
  /** `_build_render.resolve_out` — absolute, or relative to the CURRENT WORKING DIRECTORY
96
97
  * (not to ROOT), so the harness renders straight into any repository.
97
98
  *
98
- * EXPORTED IN P5i because `js/setup.mjs` became its second caller: `_setup_summary_lines`
99
+ * EXPORTED IN P5i because `js/maintain/setup.mjs` became its second caller: `_setup_summary_lines`
99
100
  * names the AGENT.md a bundle emit just wrote, and it names it by the same resolution the
100
101
  * emit used. Copying the one line would be a second owner of the rule that a bare `Harness`
101
102
  * follows the invocation and not the checkout — which is what
102
103
  * `build/the-bundle-follows-cwd-not-the-checkout` exists to gate.
103
104
  *
104
- * `pyResolve` AND NOT `path.resolve`, and the difference is the whole reason `pyResolve`
105
+ * `resolvePath` AND NOT `path.resolve`, and the difference is the whole reason `resolvePath`
105
106
  * exists: the reference ends in `.resolve()`, which canonicalises — 8.3 short names expanded
106
107
  * on Windows, symlinks followed on POSIX, the filesystem's own casing — where `path.resolve`
107
- * only normalises `.`/`..`. Nine `pyResolve` call sites had the rule and this one did not.
108
+ * only normalises `.`/`..`. Nine `resolvePath` call sites had the rule and this one did not.
108
109
  * It is invisible on a machine whose paths are already canonical, which is every machine
109
110
  * this ran on until GitHub's Windows runner handed it a `C:\\Users\\RUNNER~1\\...` and the
110
111
  * two CLIs printed the same directory under two different names — a 70-byte difference in a
111
112
  * line compared BYTE for byte by `test_the_two_entry_points_agree_on_stdout_BYTES`. The
112
- * argument to `pyResolve` is made absolute first so its `expanduser` cannot fire: the
113
+ * argument to `resolvePath` is made absolute first so its `expanduser` cannot fire: the
113
114
  * reference does not expand `~` here, and a bare `~` is a legal directory name. */
114
115
  export function resolveOut(raw) {
115
- return pyResolve(path.resolve(process.cwd(), raw));
116
+ return resolvePath(path.resolve(process.cwd(), raw));
116
117
  }
117
118
 
118
119
  /**
@@ -218,7 +219,7 @@ function choicesFor() {
218
219
  * carrier, and a marker whose contents depend on argument order is one that compares unequal
219
220
  * to itself. Validation is against DISCOVERY, not `PACK_ORDER`, so the refusal names the
220
221
  * packs the checkout actually has; a discovered pack missing from `PACK_ORDER` is a
221
- * different fault and `js/render.mjs` refuses the whole build for it.
222
+ * different fault and `js/build/render.mjs` refuses the whole build for it.
222
223
  */
223
224
  /**
224
225
  * `--exclude-rules "process 7,craft 3"` (or `process.7`) -> the cfg's exclusion array.
@@ -357,11 +358,11 @@ function parseArgs(argv, defaults) {
357
358
  * hand-rolled copy would have its own `--target` alias, its own `choices` lists and its own
358
359
  * `-h`, and `test_the_help_text_names_every_flag_the_reference_takes` gates only this one.
359
360
  *
360
- * `withPyNewlines` because the refusal and `-h` paths WRITE: called from `bin/geneseed.mjs`
361
+ * `withPlatformNewlines` because the refusal and `-h` paths WRITE: called from `bin/build-driver.mjs`
361
362
  * they sit inside `main`'s funnel, and called from the CLI binary they would not.
362
363
  */
363
364
  export function parseDriverArgs(argv) {
364
- return withPyNewlines(() => parseArgs(argv, configDefaults()));
365
+ return withPlatformNewlines(() => parseArgs(argv, configDefaults()));
365
366
  }
366
367
 
367
368
  function choice(flag, value, allowed) {
@@ -441,7 +442,7 @@ function die(code, msg) {
441
442
  * re-emit, which is P10's migration arriving five phases early.
442
443
  *
443
444
  * EXPORTED SINCE P6i, because the emitter is no longer its only caller.
444
- * `js/uninstall.mjs`'s `remergeClaudeHooks` re-merges the canonical hooks when a disabled
445
+ * `js/maintain/uninstall.mjs`'s `remergeClaudeHooks` re-merges the canonical hooks when a disabled
445
446
  * Claude install is turned back on, and `mergeClaudeSettings` refuses to guess the pair —
446
447
  * correctly. There is exactly one right answer on this side and it is this function, so the
447
448
  * second caller imports it rather than restating two lines that must never drift: a
@@ -464,14 +465,14 @@ export function hookRunnerEntry() {
464
465
  * through `_build_core._claude_config_dir`, an `_OWNED` name precisely because the suite
465
466
  * redirects it at a sandbox, and a redirect that stops at a subprocess half-works in
466
467
  * silence. The child must therefore never derive it — but this file is the PARENT, so
467
- * deriving it is exactly this file's job, and `js/emit.mjs` receives the answer.
468
+ * deriving it is exactly this file's job, and `the host emit` receives the answer.
468
469
  *
469
470
  * POSIX spelling, per the original: `claudeMdExcludes` entries are glob patterns, where a
470
471
  * backslash is an escape, so the Windows-native form risks never matching.
471
472
  */
472
473
  function preambleExclude(claudeMd) {
473
474
  if (path.basename(claudeMd) !== 'CLAUDE.md') return null;
474
- return pyResolve(path.join(claudeConfigDir(), 'CLAUDE.md')).split(path.sep).join('/');
475
+ return resolvePath(path.join(claudeConfigDir(), 'CLAUDE.md')).split(path.sep).join('/');
475
476
  }
476
477
 
477
478
  /**
@@ -566,10 +567,6 @@ function pruneOwned(oc, oldOwned, owned) {
566
567
  }
567
568
  }
568
569
 
569
- function isDir(p) {
570
- try { return statSync(p).isDirectory(); } catch { return false; }
571
- }
572
-
573
570
  /**
574
571
  * `_build_global._project_survivors` — registered per-repo PROJECT installs of `emitName`.
575
572
  *
@@ -736,7 +733,7 @@ function emitOpencodeGlobal(cfg, args, out) {
736
733
  * `_build_global._emit_claude_core` — the shared engine behind six emits.
737
734
  *
738
735
  * Only the two Copilot ones run through it so far, and that is a deliberate split rather
739
- * than an arbitrary stopping point: `js/emit.mjs:1166` gates the entire settings/hook path
736
+ * than an arbitrary stopping point: `js/build/emit-claude.mjs's wire` gates the entire settings/hook path
740
737
  * behind `if (!isCopilot)`, so a Copilot emit never reaches `hookPrefix` and therefore never
741
738
  * needs `hookOpts` — the pair of machine-absolute values (`sys.executable` and the harness
742
739
  * entry point) that a Node driver has no way to produce. Copilot crossing first isolates
@@ -1007,7 +1004,7 @@ export function emitProjectInto(host, { theme, out, root, footprint = 'full', do
1007
1004
  // same reason: a drift reader that renders `expected` at the DEFAULT pack set reports the
1008
1005
  // whole doctrines section as edited on every narrowed install. `null` means "no opinion" and
1009
1006
  // `makeCfg` renders all four, which is what every validation caller wants.
1010
- return withPyNewlines(() => emit(makeCfg(doctrines ? { doctrines } : {}),
1007
+ return withPlatformNewlines(() => emit(makeCfg(doctrines ? { doctrines } : {}),
1011
1008
  { theme, footprint, root }, out));
1012
1009
  }
1013
1010
 
@@ -1020,7 +1017,7 @@ export function emitProjectInto(host, { theme, out, root, footprint = 'full', do
1020
1017
  * exactly as `run` inherits it below.
1021
1018
  */
1022
1019
  export function buildInto({ theme, out, footprint = 'lean' }) {
1023
- return withPyNewlines(
1020
+ return withPlatformNewlines(
1024
1021
  () => build(makeCfg(), theme, out, { footprint, nativeCatalog: false }),
1025
1022
  );
1026
1023
  }
@@ -1042,7 +1039,7 @@ export function emitGlobalInto(host, {
1042
1039
  // rendering at `peer`/`direct`; the drift readers pass what the deployment actually says,
1043
1040
  // because rendering `expected` at the defaults reports AGENT.md as edited on every install
1044
1041
  // that chose a register — the same scar the footprint left in `diffCollect`, one axis over.
1045
- return withPyNewlines(
1042
+ return withPlatformNewlines(
1046
1043
  () => emit(
1047
1044
  // THE PACK SELECTION RIDES THE SAME RAIL, with one difference: `doctrinesOfDir` answers
1048
1045
  // `null` for "no marker / undetectable", and that must render at ALL packs rather than
@@ -1062,16 +1059,16 @@ export function emitGlobalInto(host, {
1062
1059
  * `_harness_build.cmd_build` is `run([sys.executable, BUILD, *extra]).returncode` — Python
1063
1060
  * needs a second process because `build.py` is a different PROGRAM. Here it is a module in
1064
1061
  * the same one, and `bin/geneseed-cli.mjs` is under a transitive `child_process` ban, so
1065
- * `js/generate.mjs` calls this directly. The export is what makes the auto-run below need a
1062
+ * `js/build/generate.mjs` calls this directly. The export is what makes the auto-run below need a
1066
1063
  * guard: without one, importing this file to reach `main` would RUN the generator with the
1067
1064
  * CLI's argv.
1068
1065
  */
1069
1066
  export function main(argv) {
1070
- // `withPyNewlines` is what makes this driver's bytes Python's bytes on Windows, for every
1067
+ // `withPlatformNewlines` is what makes this driver's bytes Python's bytes on Windows, for every
1071
1068
  // print site in the whole render tree at once — see its docblock for why a funnel and not
1072
- // 25 calls to `pyPrint`. It wraps the `catch` as well as `run`, because `die` writes its
1069
+ // 25 calls to `printOut`. It wraps the `catch` as well as `run`, because `die` writes its
1073
1070
  // line on the way out.
1074
- return withPyNewlines(() => {
1071
+ return withPlatformNewlines(() => {
1075
1072
  try {
1076
1073
  return run(argv);
1077
1074
  } catch (e) {
@@ -1092,12 +1089,12 @@ function run(argv) {
1092
1089
  // different places because the transitive `child_process` ban splits them.
1093
1090
  //
1094
1091
  // `--sync-themes` is HERE, because it needs nothing this driver may not have: it reads
1095
- // `themes/`, rewrites the files textually, and prints. `js/themes.mjs` carries it.
1092
+ // `themes/`, rewrites the files textually, and prints. `js/build/themes.mjs` carries it.
1096
1093
  // Non-zero when files were CHANGED (0 == already in sync), so CI can run it as a drift
1097
1094
  // check — build.py:393-397's mapping, and a refusal (exit 2) if the template is unreadable.
1098
1095
  if (args.syncThemes) return syncThemes() ? 1 : 0;
1099
1096
  // `--validate-only` is NOT, and cannot be: its source-tree half is the doctor, and
1100
- // `js/doctor.mjs` starts a process (`node --check` over the OpenCode plugins). This driver
1097
+ // `js/inspect/checks-authoring.mjs` starts a process (`node --check` over the OpenCode plugins). This driver
1101
1098
  // is under a transitive ban on reaching any such module, gated twice — by a source grep in
1102
1099
  // `tests/test_node_cli_parity.py` and by an import walk in `tests/test_hook_cli_parity.py`.
1103
1100
  // So the tool crossed onto the CLI binary, which already carries the doctor, and this flag
@@ -1172,10 +1169,10 @@ function run(argv) {
1172
1169
  }
1173
1170
 
1174
1171
  /**
1175
- * Run only when this file IS the entry point, not when `js/generate.mjs` imports it.
1172
+ * Run only when this file IS the entry point, not when `js/build/generate.mjs` imports it.
1176
1173
  *
1177
1174
  * `import.meta.main` would say this in one word and is Node 24; the spec's `engines` floor
1178
- * is still open and this machine runs v22, the same reason `js/emit.mjs` avoids it. So the
1175
+ * is still open and this machine runs v22, which is why the comparison is spelled out rather than using it. So the
1179
1176
  * check is the manual one, through `realpathSync` because `process.argv[1]` is whatever the
1180
1177
  * caller typed and `import.meta.url` is always the resolved file — an `npx`-generated shim
1181
1178
  * or a `geneseed link` symlink makes those differ as strings while naming the same file.
@@ -1183,9 +1180,9 @@ function run(argv) {
1183
1180
  * BOTH ways of getting this wrong are gated, which is why it is a guard and not a comment,
1184
1181
  * and both were measured rather than argued:
1185
1182
  *
1186
- * * stuck FALSE — `node bin/geneseed.mjs` becomes a silent no-op: 259 golden cells.
1183
+ * * stuck FALSE — `node bin/build-driver.mjs` becomes a silent no-op: 259 golden cells.
1187
1184
  * * stuck TRUE — importing this file runs the generator on the IMPORTER's argv, so it is
1188
- * not only `build` that breaks. `bin/geneseed-cli.mjs` reaches `js/generate.mjs` at
1185
+ * not only `build` that breaks. `bin/geneseed-cli.mjs` reaches `js/build/generate.mjs` at
1189
1186
  * module load, so every CLI verb dies in the driver's flag parser: **62 of the 166
1190
1187
  * acceptance cells**, `exclude` and `status` and `version` among them.
1191
1188
  */