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/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,74 @@ 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
|
-
## [
|
|
9
|
+
## [3.1.2] — 2026-08-24
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
- **`js/` is now domain folders, not a flat drawer.** The tree splits into `build/`, `hosts/`,
|
|
15
|
+
`inspect/`, and `main/`, and the four largest modules split with it: `server.mjs` into
|
|
16
|
+
routes / handler / daemon / server, `emit.mjs` into common / stubs / version / bundle,
|
|
17
|
+
`doctor.mjs` into scan plus three check groups, and `fs.mjs` into fs / json / paths / text.
|
|
18
|
+
The Node generator driver is renamed to `bin/build-driver.mjs`; the `geneseed-build` bin name
|
|
19
|
+
is unchanged, so nothing on an installed harness has to move.
|
|
20
|
+
|
|
21
|
+
**No emitted byte moves.** The 261-cell corpus replays identical across all 14 themes, which
|
|
22
|
+
is the whole warrant for a reorganization this size — 151 files, 8502 insertions, 6481
|
|
23
|
+
deletions, and zero observable difference for anyone who installed 3.1.1.
|
|
24
|
+
|
|
25
|
+
- **Docs point at the code that exists.** A module map and front door in `js/README.md`, with a
|
|
26
|
+
doctor arm that keeps them honest; 201 stale paths repointed in prose, the 35 split-module
|
|
27
|
+
pointers resolved, and 69 docblock claims corrected where the code had stopped supporting
|
|
28
|
+
what the comment promised. The vestigial `emit.mjs` is deleted.
|
|
29
|
+
|
|
30
|
+
- **The Python-era `py` identifier prefix is retired**, and `js/ bin/ tests/ adapters/` are now
|
|
31
|
+
linted — the half of the tree that never was.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
- **Two of "the four things that are genuinely hard" are resolved rather than rewritten.**
|
|
35
|
+
`docs/extending.md` §5.2 — seven theme keys pinned byte-for-byte, so restyling a tagline was a
|
|
36
|
+
red gate — and §5.4 — *a snapshot red is a finding, not a step*. Theme text is free to edit,
|
|
37
|
+
and a red test is a step again. Editing a CLI verb's help text or flags is an ordinary edit.
|
|
38
|
+
|
|
39
|
+
- **The standing rule is now "expected values are written out, not recorded"**: change the
|
|
40
|
+
expectation and the sentence that explains it in the same commit, and say why in the message.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
- **A gate on how a runtime dependency may enter**: vendored under `js/vendor/<name>/` as tracked
|
|
46
|
+
source behind a relative import, never installed. Fixtures run from the OS temp root and cannot
|
|
47
|
+
resolve a bare specifier, and neither can a fresh `git clone`; devDependencies are unaffected.
|
|
48
|
+
|
|
49
|
+
### Removed
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
- **The recorded corpora, and the notion of a corpus with them.** `tests/__snapshots__/` held
|
|
53
|
+
2.6 MB of transcripts taken from the CPython reference before it was deleted, replayed by
|
|
54
|
+
`tests/snapshot/`. With no recorder left they could never be re-blessed, so a red run could
|
|
55
|
+
only ever mean *you changed something*, never *you broke something* — and every deliberate
|
|
56
|
+
change to emitted text arrived as a red gate. The 1381-cell retirement of 2026-08-17 had
|
|
57
|
+
already made the argument; this finishes it.
|
|
58
|
+
|
|
59
|
+
**What they owned is now written out.** `wrapText`, `dwidth` and `formatHelp` had zero unit
|
|
60
|
+
coverage — the recordings were their only gate — so `tests/unit/text_layout.test.mjs` states
|
|
61
|
+
their rules explicitly and renders all 29 verbs. The JSONC, Windows-PATH and token-report
|
|
62
|
+
tables moved inline; `sync_themes` became an owned fixture with plain-text expectations. The
|
|
63
|
+
"corpus contains what it is named for" gates went with them: when the apostrophe is visible in
|
|
64
|
+
the file, nothing has to assert it is there.
|
|
65
|
+
|
|
66
|
+
Recoverable at tag `corpus-reference-v3.1.2`. Nothing can regenerate them.
|
|
67
|
+
|
|
68
|
+
- **Three migration statistics.** "N of the M subcommands run from Node", "0 commands have no
|
|
69
|
+
Node twin", and a native-verb list duplicated across two files purely so each could police the
|
|
70
|
+
other. A migration percentage is not a fact about a finished tool. The durable property under
|
|
71
|
+
the first two — every declared verb is dispatched by an entry point — is now asserted directly.
|
|
72
|
+
|
|
73
|
+
- **`golden.mjs --against`**, which could not have worked since the emit corpus was retired, and
|
|
74
|
+
`orphanCheck`, `narrowingReason` and `tests/helpers/snapshot_io.mjs` with it. The three
|
|
75
|
+
surviving modes are all self-comparisons.
|
|
76
|
+
|
|
10
77
|
|
|
11
78
|
## [3.1.1] — 2026-08-21
|
|
12
79
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
- **
|
|
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/
|
|
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.
|
|
23
|
+
> string. Current label: **3.1.2**.
|
|
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/
|
|
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
|
-
|
|
41
|
-
} from '../js/emit.mjs';
|
|
42
|
-
import { settingsIntegrityCheck } from '../js/settings.mjs';
|
|
43
|
-
import { writeText,
|
|
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,
|
|
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,
|
|
57
|
-
} from '../js/
|
|
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
|
-
* `
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
-
* `
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
// `
|
|
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 `
|
|
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
|
|
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/
|
|
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
|
|
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/
|
|
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
|
*/
|
package/bin/geneseed-cli.mjs
CHANGED
|
@@ -4,24 +4,24 @@
|
|
|
4
4
|
* invokes. `bin/geneseed-hook.mjs` is the twin of the four it does.
|
|
5
5
|
*
|
|
6
6
|
* WHY A THIRD BINARY. P5c's whole question, and the three candidates were: grow the hook
|
|
7
|
-
* entry, put verbs in `bin/
|
|
7
|
+
* entry, put verbs in `bin/build-driver.mjs`, or this. Each of the other two costs something
|
|
8
8
|
* specific.
|
|
9
9
|
*
|
|
10
10
|
* * GROWING `bin/geneseed-hook.mjs` weakens the gate the design now rests on.
|
|
11
11
|
* `test_the_entry_carries_exactly_the_verbs_the_emitter_wires` asserts EQUALITY between
|
|
12
|
-
* that file's table and the hooks `js/settings.mjs` wires, and P5b made it load-bearing:
|
|
12
|
+
* that file's table and the hooks `js/hosts/settings.mjs` wires, and P5b made it load-bearing:
|
|
13
13
|
* the shim is machine-wide (`~/.geneseed/bin/geneseed-hook[.cmd]`, no per-install
|
|
14
14
|
* component) and last-writer-wins, so a Node emit owns the hooks of installs Python
|
|
15
15
|
* wrote, and the verb sets matching is what makes that safe. Relaxing it to a subset
|
|
16
16
|
* relation to admit `exclude` would be the single most damaging edit available here.
|
|
17
17
|
* Two more reasons point the same way, and the second is not in that file's own
|
|
18
|
-
* argument: `exclude`'s writer imports `js/settings.mjs` and `
|
|
18
|
+
* argument: `exclude`'s writer imports `js/hosts/settings.mjs` and `the emit modules`, ~2,100 lines
|
|
19
19
|
* that would then load on EVERY PreToolUse call — the spec's hook-latency budget says
|
|
20
20
|
* the shim must exec a minimal entry, not the CLI. And the entry's refusal list, which
|
|
21
21
|
* names every verb it does not carry, is what makes "silently accepted `doctor` and did
|
|
22
22
|
* nothing" impossible; every Geneseed hook exits 0 and signals through stdout, so a
|
|
23
23
|
* no-op and a success are one observation.
|
|
24
|
-
* * `bin/
|
|
24
|
+
* * `bin/build-driver.mjs` IS `build.py`'s `main()` — a different Python program from
|
|
25
25
|
* `rituals/harness.py`, not a different half of the same one. It parses generator FLAGS
|
|
26
26
|
* with a partition asserted over them (`test_the_node_driver_classifies_every_emit`),
|
|
27
27
|
* `tests/golden.py` drives it 259 times per run, and it is under a hard `child_process`
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
*
|
|
32
32
|
* What this costs is what the P5b handoff predicted: a second acceptance matrix. It is paid
|
|
33
33
|
* in `tests/harness_golden.py`, which now carries a `bin` per cell and takes `--new-cli`
|
|
34
|
-
* beside `--new` — and it would have been paid identically by the `bin/
|
|
34
|
+
* beside `--new` — and it would have been paid identically by the `bin/build-driver.mjs` option,
|
|
35
35
|
* so it is not a discriminator between the two.
|
|
36
36
|
*
|
|
37
37
|
* THE VERB SET REFUSES THE REST BY NAME, exactly as the hook entry does. It carries 22 of the
|
|
@@ -46,24 +46,25 @@
|
|
|
46
46
|
* for the file as a whole is that its transitive imports now reach two modules that spawn,
|
|
47
47
|
* which `_ALLOWED_SPAWNS` in `tests/test_hook_cli_parity.py` declares argv by argv.
|
|
48
48
|
*/
|
|
49
|
-
import { cmdCatalog } from '../js/catalog.mjs';
|
|
50
|
-
import { cliSpec, printHelp } from '../js/cli.mjs';
|
|
51
|
-
import { cmdDiff } from '../js/diff.mjs';
|
|
52
|
-
import { cmdDoctor
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
49
|
+
import { cmdCatalog } from '../js/build/catalog.mjs';
|
|
50
|
+
import { cliSpec, printHelp } from '../js/ui/cli.mjs';
|
|
51
|
+
import { cmdDiff } from '../js/inspect/diff.mjs';
|
|
52
|
+
import { cmdDoctor } from '../js/inspect/doctor.mjs';
|
|
53
|
+
import { cmdValidate } from '../js/inspect/validate.mjs';
|
|
54
|
+
import { parseDriverArgs } from './build-driver.mjs';
|
|
55
|
+
import { cmdExclude } from '../js/inspect/excludes.mjs';
|
|
56
|
+
import { cmdBuild, cmdPrompt, cmdRebuildAll, cmdTheme } from '../js/build/generate.mjs';
|
|
57
|
+
import { cmdMigrate } from '../js/maintain/migrate.mjs';
|
|
58
|
+
import { parseIntStrict } from '../js/lib/text.mjs';
|
|
59
|
+
import { cmdLink, cmdUnlink } from '../js/hosts/link.mjs';
|
|
60
|
+
import { cmdMcp } from '../js/hosts/mcp.mjs';
|
|
61
|
+
import { cmdMemory } from '../js/maintain/memory.mjs';
|
|
62
|
+
import { cmdHome, cmdMenu } from '../js/ui/menu.mjs';
|
|
63
|
+
import { cmdTui } from '../js/ui/tui.mjs';
|
|
64
|
+
import { cmdSetup } from '../js/maintain/setup.mjs';
|
|
65
|
+
import { cmdStatus, cmdVersion } from '../js/inspect/status.mjs';
|
|
66
|
+
import { cmdUninstall } from '../js/maintain/uninstall.mjs';
|
|
67
|
+
import { cmdBootstrap, cmdSyncSelf, cmdUpgrade } from '../js/maintain/update.mjs';
|
|
67
68
|
import { cmdWeb } from '../js/web/server.mjs';
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -74,7 +75,7 @@ import { cmdWeb } from '../js/web/server.mjs';
|
|
|
74
75
|
* `add_argument` calls, gated against the subparsers by NAME only. When
|
|
75
76
|
* `_web_docs._cli_reference` stopped walking the parser and started reading the CLI table,
|
|
76
77
|
* keeping that would have made THREE transcriptions of one parser — the parser, the file, and
|
|
77
|
-
* here. So the argument surface comes from `js/cli.mjs`'s `cliSpec()`, derived from the same
|
|
78
|
+
* here. So the argument surface comes from `js/ui/cli.mjs`'s `cliSpec()`, derived from the same
|
|
78
79
|
* file the docs page serves, and what is left in a row is the one thing a data file cannot
|
|
79
80
|
* hold: the FUNCTION.
|
|
80
81
|
*
|
|
@@ -150,14 +151,14 @@ const VERBS = {
|
|
|
150
151
|
// Points at `cmdSyncSelf` rather than at `cmdUpgrade`, and that is not tidiness:
|
|
151
152
|
// `sync-self` DROPS its `ref` before `upgrade` ever sees it, where `upgrade` both WARNS
|
|
152
153
|
// about one and re-reads it as a theme — so the two verbs answer `sync-self cyberpunk`
|
|
153
|
-
// and `sync-self v1.2.3` differently. See `js/update.mjs`'s `syncSelf` for both halves.
|
|
154
|
+
// and `sync-self v1.2.3` differently. See `js/maintain/update.mjs`'s `syncSelf` for both halves.
|
|
154
155
|
fn: cmdSyncSelf,
|
|
155
156
|
},
|
|
156
157
|
bootstrap: {
|
|
157
158
|
fn: cmdBootstrap,
|
|
158
159
|
},
|
|
159
160
|
// P7a. Both are DISPATCHERS whose off-TTY arm is the whole of what a cell can reach, and
|
|
160
|
-
// `menu`'s on-TTY arm falls back rather than opening a panel — `js/menu.mjs`'s header
|
|
161
|
+
// `menu`'s on-TTY arm falls back rather than opening a panel — `js/ui/menu.mjs`'s header
|
|
161
162
|
// argues both.
|
|
162
163
|
menu: {
|
|
163
164
|
fn: cmdMenu,
|
|
@@ -168,7 +169,7 @@ const VERBS = {
|
|
|
168
169
|
// P7b, and it is the twenty-fifth and last. `cmd_tui`'s FIRST arm is `if not sys.stdin.
|
|
169
170
|
// isatty()`, so off a TTY — which is every cell there is — the verb is one line and an
|
|
170
171
|
// exit code, and that arm crosses byte for byte. The panel behind it is P7c's;
|
|
171
|
-
// `js/tui.mjs`'s header argues why this entry falls back to `cmd_tui`'s own
|
|
172
|
+
// `js/ui/tui.mjs`'s header argues why this entry falls back to `cmd_tui`'s own
|
|
172
173
|
// panel-unavailable line rather than inventing a second full-screen UI, and
|
|
173
174
|
// `tests/test_tui_boundary.py` asserts that the arm it declares is genuinely unreachable
|
|
174
175
|
// here rather than merely untested.
|
|
@@ -201,7 +202,7 @@ const VERBS = {
|
|
|
201
202
|
};
|
|
202
203
|
|
|
203
204
|
function die(code, msg) {
|
|
204
|
-
// CRLF on Windows, for the same reason `js/hooks.mjs`'s funnels translate: argparse writes
|
|
205
|
+
// CRLF on Windows, for the same reason `js/hosts/hooks.mjs`'s funnels translate: argparse writes
|
|
205
206
|
// this line through `sys.stderr`, which does.
|
|
206
207
|
process.stderr.write(`geneseed: error: ${msg}${process.platform === 'win32' ? '\r\n' : '\n'}`);
|
|
207
208
|
return code;
|
|
@@ -211,7 +212,7 @@ function die(code, msg) {
|
|
|
211
212
|
* `argparse`'s surface for the verbs here, and only theirs: ordered positionals, some
|
|
212
213
|
* optional, some with `choices`.
|
|
213
214
|
*
|
|
214
|
-
* `spec` comes from `js/cli.mjs`'s `cliSpec()` since P10c — derived from `js/cli-table.json`,
|
|
215
|
+
* `spec` comes from `js/ui/cli.mjs`'s `cliSpec()` since P10c — derived from `js/cli-table.json`,
|
|
215
216
|
* which P2 made the owned document. This function's rules are unchanged; what
|
|
216
217
|
* changed is that the table they run over is no longer written out by hand beside them. Every
|
|
217
218
|
* argparse feature named below is a row of that derivation.
|
|
@@ -274,7 +275,7 @@ function parse(spec, argv) {
|
|
|
274
275
|
// `type=int`. argparse's own wording, minus the usage block it prints around it — the
|
|
275
276
|
// same rule the rest of this parser follows. The VALUE stays a string in `args`; the
|
|
276
277
|
// command converts, and this is what guarantees the conversion cannot fail there.
|
|
277
|
-
if ((spec.ints ?? []).includes(flag) &&
|
|
278
|
+
if ((spec.ints ?? []).includes(flag) && parseIntStrict(value) === null) {
|
|
278
279
|
return { error: `argument ${flag}: invalid int value: '${value}'` };
|
|
279
280
|
}
|
|
280
281
|
args[dest] = value;
|
|
@@ -347,7 +348,7 @@ async function main(argv) {
|
|
|
347
348
|
//
|
|
348
349
|
// So a row would have meant amending two gates and recording a corpus half blind. It takes
|
|
349
350
|
// the generator's own parser (see `parseDriverArgs`) because its flags are the generator's,
|
|
350
|
-
// and it is on THIS binary — not on `bin/
|
|
351
|
+
// and it is on THIS binary — not on `bin/build-driver.mjs` — because it runs the doctor, which
|
|
351
352
|
// starts a process the driver is banned from reaching.
|
|
352
353
|
if (verb === 'validate') {
|
|
353
354
|
try {
|
|
@@ -397,7 +398,7 @@ async function main(argv) {
|
|
|
397
398
|
return await spec.fn(parsed.args);
|
|
398
399
|
} catch (e) {
|
|
399
400
|
// `e.exitCode` is the generator's existing marker for a DELIBERATE refusal that has
|
|
400
|
-
// already explained itself on stderr — `js/emit.mjs
|
|
401
|
+
// already explained itself on stderr — `js/build/emit-claude.mjs` reads the same flag, and it is
|
|
401
402
|
// `sys.exit(<message>)` on the Python side. Anything without it is a crash and keeps its
|
|
402
403
|
// stack: Python prints a traceback for an unhandled exception, and turning one into a
|
|
403
404
|
// tidy one-liner here would hide a bug behind a refusal's clothing.
|