geneseed 3.1.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +180 -0
- package/README.md +24 -14
- package/SETUP.md +38 -7
- package/SHIPPED.md +6 -5
- package/adapters/bob/README.md +40 -10
- package/adapters/claude-code/README.md +5 -0
- package/adapters/claude-code/settings.json +8 -1
- package/adapters/copilot/README.md +50 -24
- package/adapters/opencode/README.md +5 -1
- package/adapters/opencode/plugins/geneseed-guard.js +40 -6
- package/bin/build-driver.mjs +143 -89
- package/bin/geneseed-cli.mjs +63 -106
- package/bin/geneseed-hook.mjs +26 -16
- package/docs/candidate-hosts.md +74 -0
- package/docs/extending.md +6 -5
- package/docs/token-footprint.md +32 -21
- package/docs/web-ui.md +5 -13
- package/harness.config.json +1 -1
- package/js/README.md +5 -15
- package/js/build/emit-claude.mjs +26 -12
- package/js/build/emit-common.mjs +63 -114
- package/js/build/emit-opencode.mjs +74 -66
- package/js/build/generate.mjs +4 -6
- package/js/build/render.mjs +50 -32
- package/js/build/source.mjs +29 -8
- package/js/build/stubs.mjs +25 -33
- package/js/build/themes.mjs +1 -1
- package/js/build/version.mjs +1 -3
- package/js/cli-table.json +84 -0
- package/js/hosts/hooks.mjs +243 -81
- package/js/hosts/hosts.mjs +57 -5
- package/js/hosts/installs.mjs +35 -28
- package/js/hosts/link.mjs +2 -2
- package/js/hosts/mcp.mjs +3 -1
- package/js/hosts/native.mjs +54 -19
- package/js/hosts/opencode.mjs +39 -28
- package/js/hosts/settings.mjs +206 -115
- package/js/inspect/checks-authoring.mjs +5 -4
- package/js/inspect/checks-build.mjs +1 -1
- package/js/inspect/diff.mjs +1 -3
- package/js/inspect/doctor.mjs +59 -44
- package/js/inspect/excludes.mjs +5 -11
- package/js/inspect/inventory.mjs +38 -56
- package/js/inspect/registry.mjs +4 -7
- package/js/inspect/scan.mjs +8 -15
- package/js/inspect/status.mjs +51 -3
- package/js/lib/fs.mjs +34 -0
- package/js/lib/json.mjs +28 -1
- package/js/lib/paths.mjs +0 -10
- package/js/lib/udiff.mjs +11 -16
- package/js/maintain/memory.mjs +2 -4
- package/js/maintain/migrate.mjs +3 -3
- package/js/maintain/uninstall.mjs +127 -88
- package/js/maintain/update.mjs +4 -4
- package/js/ui/anim.mjs +1 -1
- package/js/ui/menu.mjs +1 -1
- package/js/ui/tui.mjs +3 -20
- package/js/web/actions.mjs +230 -217
- package/js/web/activity.mjs +71 -89
- package/js/web/api.mjs +130 -226
- package/js/web/daemon.mjs +92 -110
- package/js/web/docs.mjs +59 -126
- package/js/web/handler.mjs +94 -123
- package/js/web/jobs.mjs +116 -178
- package/js/web/routes.mjs +47 -192
- package/js/web/server.mjs +47 -82
- package/package.json +1 -1
- package/registry.json +14 -0
- package/src/AGENT.md.tmpl +55 -16
- package/src/agents/explorer.md +2 -1
- package/src/doctrines/README.md +3 -3
- package/src/doctrines/process.md +4 -2
- package/src/doctrines/rigor.md +13 -0
- package/src/laws/universal.md +133 -43
- package/src/ontology/universal.md +38 -13
- package/src/skills/_template.md +5 -1
- package/src/skills/bruno-collection-generator.md +63 -0
- package/src/skills/bruno-test-writer.md +62 -0
- package/src/skills/clarify.md +1 -1
- package/src/skills/crash-course.md +1 -0
- package/src/skills/decode.md +1 -0
- package/src/skills/drill.md +1 -0
- package/src/skills/feynman.md +1 -0
- package/src/skills/forge-mcp.md +2 -1
- package/src/skills/fresh-eyes.md +1 -1
- package/src/skills/herdr.md +1 -0
- package/src/skills/ingest.md +2 -1
- package/src/skills/learning-path.md +1 -0
- package/src/skills/opencode-theme.md +1 -0
- package/src/skills/parallel-agents.md +1 -1
- package/src/skills/ponytail.md +2 -2
- package/src/skills/research.md +4 -2
- package/themes/_TEMPLATE.json +5 -0
- package/themes/biker.json +5 -0
- package/themes/commentator.json +5 -0
- package/themes/cyberpunk.json +5 -0
- package/themes/gamer.json +5 -0
- package/themes/imperial.json +5 -0
- package/themes/joker.json +5 -0
- package/themes/marvin.json +5 -0
- package/themes/mean.json +5 -0
- package/themes/military.json +5 -0
- package/themes/neutral.json +5 -0
- package/themes/pirate.json +5 -0
- package/themes/sports.json +5 -0
- package/themes/verstappen.json +5 -0
- package/themes/wizard.json +5 -0
- package/web/dist/assets/Activity-BfVcLrda.js +1 -0
- package/web/dist/assets/ActivityDetail-CQXccGPl.js +1 -0
- package/web/dist/assets/CatalogRow-DALEQImZ.js +1 -0
- package/web/dist/assets/Diff-BIjxXx46.js +5 -0
- package/web/dist/assets/Doctor-CJLbgsrw.js +1 -0
- package/web/dist/assets/FilterInput-CkUCk_lY.js +1 -0
- package/web/dist/assets/GreenhouseView-DfC5IILJ.js +2 -0
- package/web/dist/assets/Harness-Dm5CCU-v.js +1 -0
- package/web/dist/assets/JournalView-Cpu2eI4s.js +1 -0
- package/web/dist/assets/Laws-DDDVFuQz.js +3 -0
- package/web/dist/assets/Library-Dao3T9na.js +1 -0
- package/web/dist/assets/{marked.esm-JKvyS0Pp.js → Markdown-C__nuWX5.js} +15 -15
- package/web/dist/assets/OperatorHudView-DEpCtAjP.js +2 -0
- package/web/dist/assets/Profile-BlXtUlh2.js +1 -0
- package/web/dist/assets/{Rules-CfsV1EVy.js → Rules-DIre6gxR.js} +1 -1
- package/web/dist/assets/Skills-rY13ZPhM.js +1 -0
- package/web/dist/assets/{StatusBadge-C9bZOEZk.js → StatusBadge-wW_RS_my.js} +1 -1
- package/web/dist/assets/{activity-Cc0n2jwx.js → activity-DpYPKNrA.js} +1 -1
- package/web/dist/assets/index-BEZvPvpF.js +1 -0
- package/web/dist/assets/index-DNQE5ydy.js +10 -0
- package/web/dist/assets/index-DzdQjvM1.css +1 -0
- package/web/dist/assets/index-PmQGRS1X.js +2 -0
- package/web/dist/assets/jobBuckets-DfTUJUxR.js +1 -0
- package/web/dist/assets/lawCats-DUS9UTH4.js +1 -0
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +119 -130
- package/js/web/graph.mjs +0 -0
- package/web/dist/assets/Activity-B49fE_ug.js +0 -1
- package/web/dist/assets/ActivityDetail-abt7hzD2.js +0 -1
- package/web/dist/assets/Diff-D3_bVkih.js +0 -5
- package/web/dist/assets/Doctor-hXp7srsx.js +0 -1
- package/web/dist/assets/Graph-DvYMtlvl.js +0 -1
- package/web/dist/assets/Harness-BIWei7N5.js +0 -1
- package/web/dist/assets/Laws-CxmnRdwt.js +0 -3
- package/web/dist/assets/Library-MjbOkHIW.js +0 -1
- package/web/dist/assets/Markdown-Dx_w0faR.js +0 -1
- package/web/dist/assets/Profile-BRFvv2gr.js +0 -1
- package/web/dist/assets/Skills-DArA6IHS.js +0 -1
- package/web/dist/assets/index-Bx3oxZ1o.css +0 -1
- package/web/dist/assets/index-DC3-GlCY.js +0 -1
- package/web/dist/assets/index-DP035wPa.js +0 -12
- package/web/dist/assets/index-dHi-7M-F.js +0 -2
- package/web/dist/assets/roman-DkYN0LX9.js +0 -1
package/bin/geneseed-cli.mjs
CHANGED
|
@@ -41,31 +41,22 @@
|
|
|
41
41
|
* keeps the two tables from ever answering the same verb twice, since the shim bakes only one
|
|
42
42
|
* of them.
|
|
43
43
|
*
|
|
44
|
-
* P6h MADE THE DISPATCH ASYNCHRONOUS and put `js/web/` on this entry's import graph
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* P6h MADE THE DISPATCH ASYNCHRONOUS and put `js/web/` on this entry's import graph — LAZILY,
|
|
45
|
+
* since the slim pass: every command module loads through its verb's thunk, so the graph is
|
|
46
|
+
* reached per verb rather than per invocation. The modules that spawn are still declared argv
|
|
47
|
+
* by argv in `ALLOWED_SPAWNS` (`tests/unit/hook_cli.test.mjs`), whose closure walk follows
|
|
48
|
+
* the dynamic spelling too.
|
|
48
49
|
*/
|
|
49
|
-
|
|
50
|
+
// THE FOUR EAGER IMPORTS ARE THE PARSER'S OWN, AND NOTHING ELSE LOADS BEFORE DISPATCH.
|
|
51
|
+
// The twenty command modules used to be static imports, which made every invocation pay for
|
|
52
|
+
// all of them: `geneseed status` loaded the web daemon, the TUI, the updater and the whole
|
|
53
|
+
// build driver — 57 modules, ~1 MB, ~40-55 ms — before a byte of its own work. The verb
|
|
54
|
+
// table below now holds one LOADER per row (`() => import(…).then(m => m.cmdX)`), so a verb
|
|
55
|
+
// loads its slice of the tree and no other verb's. The spawn allow-list gate still sees the
|
|
56
|
+
// whole graph: `tests/unit/hook_cli.test.mjs`'s `importClosure` follows both import spellings.
|
|
50
57
|
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
58
|
import { parseIntStrict } from '../js/lib/text.mjs';
|
|
59
|
-
import {
|
|
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';
|
|
68
|
-
import { cmdWeb } from '../js/web/server.mjs';
|
|
59
|
+
import { printErr } from '../js/lib/fs.mjs';
|
|
69
60
|
|
|
70
61
|
/**
|
|
71
62
|
* The verbs this entry answers — and, since P10c, NOTHING ELSE ABOUT THEM.
|
|
@@ -83,56 +74,31 @@ import { cmdWeb } from '../js/web/server.mjs';
|
|
|
83
74
|
* `tests/test_hook_cli_parity.py` scrapes `const VERBS = {` for its three matrix gates, and
|
|
84
75
|
* `main`'s refusal lists `Object.keys(VERBS)` IN THIS ORDER, which `harness_golden` cells
|
|
85
76
|
* assert verbatim — the order is the old table's and is not alphabetical.
|
|
77
|
+
*
|
|
78
|
+
* FLATTENED TO `verb: fn` since Task 5, AND TO `verb: loader` since the lazy pass: every row
|
|
79
|
+
* is now a thunk that imports the command's module and resolves to its function, so the one
|
|
80
|
+
* field a row holds is still the one thing a data file cannot — where the FUNCTION lives.
|
|
81
|
+
* `main` awaits `VERBS[verb]()` after the parse succeeds; the scraping regexes in
|
|
82
|
+
* `tests/unit/{cli_table,hook_cli,docs}.test.mjs` match on the KEY and the colon only; the
|
|
83
|
+
* literal string `const VERBS = {` and the key order neither changed.
|
|
86
84
|
*/
|
|
87
85
|
const VERBS = {
|
|
88
|
-
exclude:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
fn: cmdTheme,
|
|
105
|
-
},
|
|
106
|
-
diff: {
|
|
107
|
-
fn: cmdDiff,
|
|
108
|
-
},
|
|
109
|
-
'rebuild-all': {
|
|
110
|
-
fn: cmdRebuildAll,
|
|
111
|
-
},
|
|
112
|
-
migrate: {
|
|
113
|
-
fn: cmdMigrate,
|
|
114
|
-
},
|
|
115
|
-
doctor: {
|
|
116
|
-
fn: cmdDoctor,
|
|
117
|
-
},
|
|
118
|
-
uninstall: {
|
|
119
|
-
fn: cmdUninstall,
|
|
120
|
-
},
|
|
121
|
-
link: {
|
|
122
|
-
fn: cmdLink,
|
|
123
|
-
},
|
|
124
|
-
unlink: {
|
|
125
|
-
fn: cmdUnlink,
|
|
126
|
-
},
|
|
127
|
-
setup: {
|
|
128
|
-
fn: cmdSetup,
|
|
129
|
-
},
|
|
130
|
-
web: {
|
|
131
|
-
fn: cmdWeb,
|
|
132
|
-
},
|
|
133
|
-
upgrade: {
|
|
134
|
-
fn: cmdUpgrade,
|
|
135
|
-
},
|
|
86
|
+
exclude: () => import('../js/inspect/excludes.mjs').then((m) => m.cmdExclude),
|
|
87
|
+
status: () => import('../js/inspect/status.mjs').then((m) => m.cmdStatus),
|
|
88
|
+
version: () => import('../js/inspect/status.mjs').then((m) => m.cmdVersion),
|
|
89
|
+
build: () => import('../js/build/generate.mjs').then((m) => m.cmdBuild),
|
|
90
|
+
prompt: () => import('../js/build/generate.mjs').then((m) => m.cmdPrompt),
|
|
91
|
+
theme: () => import('../js/build/generate.mjs').then((m) => m.cmdTheme),
|
|
92
|
+
diff: () => import('../js/inspect/diff.mjs').then((m) => m.cmdDiff),
|
|
93
|
+
'rebuild-all': () => import('../js/build/generate.mjs').then((m) => m.cmdRebuildAll),
|
|
94
|
+
migrate: () => import('../js/maintain/migrate.mjs').then((m) => m.cmdMigrate),
|
|
95
|
+
doctor: () => import('../js/inspect/doctor.mjs').then((m) => m.cmdDoctor),
|
|
96
|
+
uninstall: () => import('../js/maintain/uninstall.mjs').then((m) => m.cmdUninstall),
|
|
97
|
+
link: () => import('../js/hosts/link.mjs').then((m) => m.cmdLink),
|
|
98
|
+
unlink: () => import('../js/hosts/link.mjs').then((m) => m.cmdUnlink),
|
|
99
|
+
setup: () => import('../js/maintain/setup.mjs').then((m) => m.cmdSetup),
|
|
100
|
+
web: () => import('../js/web/server.mjs').then((m) => m.cmdWeb),
|
|
101
|
+
upgrade: () => import('../js/maintain/update.mjs').then((m) => m.cmdUpgrade),
|
|
136
102
|
// `up.add_parser(..., aliases=["update"])`, reproduced in P8c as a ROW OF ITS OWN rather than
|
|
137
103
|
// as an `aliases` field on the row above — because a field would be a DECLARATION and the
|
|
138
104
|
// three matrix gates read this table as the DISPATCH (rule 7, and M23 is where it was
|
|
@@ -144,28 +110,18 @@ const VERBS = {
|
|
|
144
110
|
//
|
|
145
111
|
// It is NOT what the web console's `update` ACTION needs: that row's argv names `upgrade`,
|
|
146
112
|
// the subparser, and always did. The two were coupled only in a handoff's note.
|
|
147
|
-
update:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// and `sync-self v1.2.3` differently. See `js/maintain/update.mjs`'s `syncSelf` for both halves.
|
|
155
|
-
fn: cmdSyncSelf,
|
|
156
|
-
},
|
|
157
|
-
bootstrap: {
|
|
158
|
-
fn: cmdBootstrap,
|
|
159
|
-
},
|
|
113
|
+
update: () => import('../js/maintain/update.mjs').then((m) => m.cmdUpgrade),
|
|
114
|
+
// Points at `cmdSyncSelf` rather than at `cmdUpgrade`, and that is not tidiness:
|
|
115
|
+
// `sync-self` DROPS its `ref` before `upgrade` ever sees it, where `upgrade` both WARNS
|
|
116
|
+
// about one and re-reads it as a theme — so the two verbs answer `sync-self cyberpunk`
|
|
117
|
+
// and `sync-self v1.2.3` differently. See `js/maintain/update.mjs`'s `syncSelf` for both halves.
|
|
118
|
+
'sync-self': () => import('../js/maintain/update.mjs').then((m) => m.cmdSyncSelf),
|
|
119
|
+
bootstrap: () => import('../js/maintain/update.mjs').then((m) => m.cmdBootstrap),
|
|
160
120
|
// P7a. Both are DISPATCHERS whose off-TTY arm is the whole of what a cell can reach, and
|
|
161
121
|
// `menu`'s on-TTY arm falls back rather than opening a panel — `js/ui/menu.mjs`'s header
|
|
162
122
|
// argues both.
|
|
163
|
-
menu:
|
|
164
|
-
|
|
165
|
-
},
|
|
166
|
-
home: {
|
|
167
|
-
fn: cmdHome,
|
|
168
|
-
},
|
|
123
|
+
menu: () => import('../js/ui/menu.mjs').then((m) => m.cmdMenu),
|
|
124
|
+
home: () => import('../js/ui/menu.mjs').then((m) => m.cmdHome),
|
|
169
125
|
// P7b, and it is the twenty-fifth and last. `cmd_tui`'s FIRST arm is `if not sys.stdin.
|
|
170
126
|
// isatty()`, so off a TTY — which is every cell there is — the verb is one line and an
|
|
171
127
|
// exit code, and that arm crosses byte for byte. The panel behind it is P7c's;
|
|
@@ -173,9 +129,7 @@ const VERBS = {
|
|
|
173
129
|
// panel-unavailable line rather than inventing a second full-screen UI, and
|
|
174
130
|
// `tests/test_tui_boundary.py` asserts that the arm it declares is genuinely unreachable
|
|
175
131
|
// here rather than merely untested.
|
|
176
|
-
tui:
|
|
177
|
-
fn: cmdTui,
|
|
178
|
-
},
|
|
132
|
+
tui: () => import('../js/ui/tui.mjs').then((m) => m.cmdTui),
|
|
179
133
|
// THE THREE THAT NEVER HAD A PYTHON ORIGINAL, and they are last for that reason rather
|
|
180
134
|
// than by alphabet: every row above is the twin of a subparser, and these three are not.
|
|
181
135
|
// The information behind them existed only behind the web console — the catalog endpoint,
|
|
@@ -190,21 +144,15 @@ const VERBS = {
|
|
|
190
144
|
// ABSOLUTE — `tests/snapshot/cli_help.test.mjs` splits the two populations by name and refuses a
|
|
191
145
|
// verb that falls into neither, and each has its own unit gate stating what it does rather
|
|
192
146
|
// than that it agrees.
|
|
193
|
-
catalog:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
mcp: {
|
|
197
|
-
fn: cmdMcp,
|
|
198
|
-
},
|
|
199
|
-
memory: {
|
|
200
|
-
fn: cmdMemory,
|
|
201
|
-
},
|
|
147
|
+
catalog: () => import('../js/build/catalog.mjs').then((m) => m.cmdCatalog),
|
|
148
|
+
mcp: () => import('../js/hosts/mcp.mjs').then((m) => m.cmdMcp),
|
|
149
|
+
memory: () => import('../js/maintain/memory.mjs').then((m) => m.cmdMemory),
|
|
202
150
|
};
|
|
203
151
|
|
|
204
152
|
function die(code, msg) {
|
|
205
153
|
// CRLF on Windows, for the same reason `js/hosts/hooks.mjs`'s funnels translate: argparse writes
|
|
206
|
-
// this line through `sys.stderr`, which does.
|
|
207
|
-
|
|
154
|
+
// this line through `sys.stderr`, which does. `printErr` owns the `\n`-to-`os.linesep` rule.
|
|
155
|
+
printErr(`geneseed: error: ${msg}\n`);
|
|
208
156
|
return code;
|
|
209
157
|
}
|
|
210
158
|
|
|
@@ -351,6 +299,11 @@ async function main(argv) {
|
|
|
351
299
|
// and it is on THIS binary — not on `bin/build-driver.mjs` — because it runs the doctor, which
|
|
352
300
|
// starts a process the driver is banned from reaching.
|
|
353
301
|
if (verb === 'validate') {
|
|
302
|
+
// Lazy for the same reason the table rows are — `parseDriverArgs` alone drags the whole
|
|
303
|
+
// generator driver in, and every OTHER verb was paying for it.
|
|
304
|
+
const [{ cmdValidate }, { parseDriverArgs }] = await Promise.all([
|
|
305
|
+
import('../js/inspect/validate.mjs'), import('./build-driver.mjs'),
|
|
306
|
+
]);
|
|
354
307
|
try {
|
|
355
308
|
return cmdValidate(parseDriverArgs(argv.slice(1)));
|
|
356
309
|
} catch (e) {
|
|
@@ -358,8 +311,8 @@ async function main(argv) {
|
|
|
358
311
|
throw e;
|
|
359
312
|
}
|
|
360
313
|
}
|
|
361
|
-
const
|
|
362
|
-
if (!
|
|
314
|
+
const load = VERBS[verb];
|
|
315
|
+
if (!load) {
|
|
363
316
|
// "and every other harness subcommand is still Python — run the interpreter against the
|
|
364
317
|
// old harness script" is GONE, and its removal is not cosmetic: with the four hook verbs
|
|
365
318
|
// named, the two entry points now cover every subcommand there is, so the clause was not
|
|
@@ -395,7 +348,11 @@ async function main(argv) {
|
|
|
395
348
|
// it `process.exitCode` would be assigned a PENDING PROMISE, which Node coerces to 0
|
|
396
349
|
// and prints nothing about. `web status` would then report "not running" and exit 0.
|
|
397
350
|
// Awaiting a number is a no-op for the ten synchronous verbs.
|
|
398
|
-
|
|
351
|
+
//
|
|
352
|
+
// The load happens HERE, after the parse succeeded: a bad argument line refuses in a
|
|
353
|
+
// few milliseconds without touching the command's module tree, which is also why the
|
|
354
|
+
// parse errors above cannot depend on anything a loader would have brought in.
|
|
355
|
+
return await (await load())(parsed.args);
|
|
399
356
|
} catch (e) {
|
|
400
357
|
// `e.exitCode` is the generator's existing marker for a DELIBERATE refusal that has
|
|
401
358
|
// already explained itself on stderr — `js/build/emit-claude.mjs` reads the same flag, and it is
|
package/bin/geneseed-hook.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* The hook entry point — a Node twin of `rituals/harness.py` for the
|
|
4
|
-
*
|
|
3
|
+
* The hook entry point — a Node twin of `rituals/harness.py` for the verbs the emitted
|
|
4
|
+
* `settings.json` actually invokes (four from the reference, plus `tool-gate` for Copilot).
|
|
5
5
|
*
|
|
6
6
|
* WHY IT IS A SECOND BINARY AND NOT A SUBCOMMAND OF `bin/build-driver.mjs`. Three reasons,
|
|
7
7
|
* and each alone would be enough:
|
|
@@ -37,13 +37,20 @@
|
|
|
37
37
|
* every Geneseed hook returns 0 and signals through stdout — so "did nothing" and "worked"
|
|
38
38
|
* are the same observation.
|
|
39
39
|
*/
|
|
40
|
-
import { cmdContext, cmdGitGate, cmdRuleGate, cmdLearn } from '../js/hosts/hooks.mjs';
|
|
41
|
-
|
|
40
|
+
import { cmdContext, cmdGitGate, cmdRuleGate, cmdToolGate, cmdLearn } from '../js/hosts/hooks.mjs';
|
|
41
|
+
// Not a new module on the hot path: `js/hosts/hooks.mjs` above already imports
|
|
42
|
+
// `js/lib/fs.mjs`, so this edge names a file the process has already loaded.
|
|
43
|
+
import { printErr } from '../js/lib/fs.mjs';
|
|
42
44
|
|
|
45
|
+
// `--host` selects the verdict dialect (see js/hosts/hooks.mjs's header); the emitter bakes
|
|
46
|
+
// it into the command for every host that is not Claude Code, so the hook never has to guess
|
|
47
|
+
// its reader from the payload's shape.
|
|
48
|
+
const HOSTED = { '--root': 'root', '--host': 'host' };
|
|
43
49
|
const VERBS = {
|
|
44
|
-
context: { fn: cmdContext, flags:
|
|
45
|
-
'git-gate': { fn: cmdGitGate, flags:
|
|
46
|
-
'rule-gate': { fn: cmdRuleGate, flags:
|
|
50
|
+
context: { fn: cmdContext, flags: HOSTED },
|
|
51
|
+
'git-gate': { fn: cmdGitGate, flags: HOSTED },
|
|
52
|
+
'rule-gate': { fn: cmdRuleGate, flags: HOSTED },
|
|
53
|
+
'tool-gate': { fn: cmdToolGate, flags: HOSTED },
|
|
47
54
|
learn: {
|
|
48
55
|
fn: cmdLearn,
|
|
49
56
|
flags: { '--memory': 'memory' },
|
|
@@ -54,18 +61,18 @@ const VERBS = {
|
|
|
54
61
|
|
|
55
62
|
function die(code, msg) {
|
|
56
63
|
// CRLF on Windows, for the same reason `js/hosts/hooks.mjs`'s funnels translate: argparse
|
|
57
|
-
// writes this line through `sys.stderr`, which does.
|
|
58
|
-
|
|
64
|
+
// writes this line through `sys.stderr`, which does. `printErr` owns the `\n`-to-`os.linesep` rule.
|
|
65
|
+
printErr(`geneseed-hook: error: ${msg}\n`);
|
|
59
66
|
return code;
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
|
-
* `argparse`'s surface for these
|
|
70
|
+
* `argparse`'s surface for these verbs, and only theirs: `--flag VALUE`, the
|
|
64
71
|
* `--flag=VALUE` spelling it also accepts, store_true switches, and one optional
|
|
65
72
|
* positional.
|
|
66
73
|
*/
|
|
67
74
|
function parse(spec, argv) {
|
|
68
|
-
const args = { root: null, memory: null, consolidate: false, file: null };
|
|
75
|
+
const args = { root: null, host: null, memory: null, consolidate: false, file: null };
|
|
69
76
|
for (let i = 0; i < argv.length; i += 1) {
|
|
70
77
|
const tok = argv[i];
|
|
71
78
|
const eq = tok.indexOf('=');
|
|
@@ -89,7 +96,7 @@ function parse(spec, argv) {
|
|
|
89
96
|
return { args };
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
function main(argv) {
|
|
99
|
+
async function main(argv) {
|
|
93
100
|
// `geneseed status | head` closes stdout early. Python exits quietly through
|
|
94
101
|
// BrokenPipeError; the Node equivalent is an EPIPE that would otherwise be an unhandled
|
|
95
102
|
// 'error' event and a stack trace on a hook path.
|
|
@@ -108,16 +115,19 @@ function main(argv) {
|
|
|
108
115
|
// The COMMAND is not a table: `geneseed` is this package's `bin` entry for the CLI
|
|
109
116
|
// (package.json), it answers every non-hook verb, and it survives the deletion of the
|
|
110
117
|
// interpreter-plus-script invocation this line used to print.
|
|
111
|
-
return die(2, `invalid choice: '${verb}'. This entry point carries only the
|
|
118
|
+
return die(2, `invalid choice: '${verb}'. This entry point carries only the HOOK `
|
|
112
119
|
+ `verbs (${Object.keys(VERBS).join(', ')}); every other harness subcommand lives `
|
|
113
120
|
+ 'elsewhere — run `geneseed ' + verb + '`.');
|
|
114
121
|
}
|
|
115
122
|
// `<verb> --help`, for the same reason and by the same owner as `bin/geneseed-cli.mjs`:
|
|
116
123
|
// argparse holds `-h` at the parser, so this entry's `parse` calls it an unrecognized
|
|
117
124
|
// argument. The four hook verbs are four of the reference's 26 and had the same gap.
|
|
118
|
-
// `js/ui/cli.mjs`
|
|
119
|
-
//
|
|
125
|
+
// `js/ui/cli.mjs` is imported HERE, not at the top: a static import cost every tool call
|
|
126
|
+
// of every session a module parse (~3 ms of an ~12 ms controllable budget) for a branch
|
|
127
|
+
// only a human at a terminal ever takes. The hook path never awaits it, so `main` staying
|
|
128
|
+
// async-shaped costs the hot verbs nothing.
|
|
120
129
|
if (argv.slice(1).some((t) => t === '-h' || t === '--help')) {
|
|
130
|
+
const { printHelp } = await import('../js/ui/cli.mjs');
|
|
121
131
|
const rc = printHelp('geneseed-hook', verb);
|
|
122
132
|
if (rc !== null) return rc;
|
|
123
133
|
}
|
|
@@ -126,4 +136,4 @@ function main(argv) {
|
|
|
126
136
|
return spec.fn(parsed.args);
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
main(process.argv.slice(2)).then((code) => { process.exitCode = code; });
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Candidate hosts — what Codex, Cursor and Gemini CLI actually offer
|
|
2
|
+
|
|
3
|
+
*2026-09-05. Item 6 of the host-gap review. A docs check, not a design: each tool's
|
|
4
|
+
own reference was read for the three surfaces Geneseed emits — an instructions
|
|
5
|
+
carrier, skills, and hooks — so that "add host X" can be costed without re-reading
|
|
6
|
+
the docs. Nothing here is implemented. Sources: the Codex CLI repo
|
|
7
|
+
(`codex-rs/hooks`, `codex-rs/core/src/agents_md.rs`), `cursor.com/docs/{rules,skills,hooks}`,
|
|
8
|
+
`geminicli.com/docs/{hooks,cli/gemini-md,cli/tutorials/skills-getting-started}`.*
|
|
9
|
+
|
|
10
|
+
## The finding that changes the cost
|
|
11
|
+
|
|
12
|
+
**`.agents/skills/` is a cross-tool convention.** Cursor loads skills from
|
|
13
|
+
`.agents/skills/` and `~/.agents/skills/` (and, for compatibility, from `.claude/skills/`
|
|
14
|
+
and `.codex/skills/`); Codex from `~/.codex/skills` (`$CODEX_HOME/skills`) and
|
|
15
|
+
`.agents/skills`; Gemini CLI from `.gemini/skills` **or the `.agents/skills` alias**. One
|
|
16
|
+
`skills/` emit into `.agents/` therefore reaches all three, byte-identical to what every
|
|
17
|
+
current host gets. Skills are the largest surface by file count and they cost nothing new.
|
|
18
|
+
|
|
19
|
+
## Per-host contracts
|
|
20
|
+
|
|
21
|
+
| Surface | Codex CLI | Cursor | Gemini CLI |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| **Instructions** | `AGENTS.md` at the repo root (and nested), plus a global `~/.codex/AGENTS.md` that no size cap touches | `AGENTS.md` at the root and in subdirectories, combined; also `.cursor/rules/*.mdc` | `GEMINI.md` by default; `context.fileName` in `settings.json` accepts a list, e.g. `["AGENTS.md", "GEMINI.md"]` |
|
|
24
|
+
| **Skills** | `~/.codex/skills`, `.agents/skills` — `SKILL.md` + frontmatter | `.cursor/skills`, `.agents/skills`, `~/.cursor/skills`, `~/.agents/skills`, plus `.claude/skills` and `.codex/skills` for compatibility | `.gemini/skills` or `.agents/skills`; `/skills list`, `/skills reload` |
|
|
25
|
+
| **Hook config** | `hooks.json` in a config layer's folder, or a `[hooks]` table in `config.toml` (same schema, capitalised event names) | `.cursor/hooks.json` (project) and `~/.cursor/hooks.json` (user); plugins may ship `hooks/hooks.json` | `settings.json` `hooks` object (project `.gemini/`, user `~/.gemini/`) |
|
|
26
|
+
| **Hook shape** | **Claude-shaped**: event → `[{matcher, hooks: [{type: "command", command, timeout}]}]`; 11 events incl. `PreToolUse`, `PostToolUse`, `PermissionRequest`, `PreCompact`, `PostCompact`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `SubagentStart`, `SubagentStop`, `Stop` | event → `[{command, matcher?}]`; events incl. `sessionStart`, `preToolUse`, `beforeShellExecution`, `beforeReadFile`, `afterFileEdit`, `stop`, `sessionEnd` | **Claude-shaped groups, Gemini names**: `SessionStart`, `BeforeTool`, `AfterTool`, `BeforeAgent`, `AfterAgent`, `BeforeModel`, `AfterModel`, `BeforeToolSelection`, `PreCompact`, `SessionEnd`; matchers are regexes over tool names |
|
|
27
|
+
| **Gate payload** | `tool_name` / `tool_input` (Claude's field names) | `tool_name` / `tool_input`, `cwd`, `tool_use_id` | `tool_name` / `tool_input`, `session_id`, `transcript_path`, `cwd`, `hook_event_name` |
|
|
28
|
+
| **Gate verdict** | `hookSpecificOutput.permissionDecision`: **`allow` / `deny` / `ask`** — the ask tier exists | `permission: "allow" \| "deny"` + `user_message` / `agent_message`; **no ask** | `decision: "deny"` + `reason`, or exit code 2 with stderr as the reason; `systemMessage` shows the user a line; **no ask** |
|
|
29
|
+
| **Context injection** | `SessionStart` → `additionalContext` (Claude form) | `sessionStart` → `additional_context` (snake_case) | `SessionStart` → `hookSpecificOutput.additionalContext`; plain stdout is not read |
|
|
30
|
+
| **Learn point** | `Stop` / `SubagentStop` / `PreCompact`, transcript on stdin as on Claude | `stop` (payload not checked for a transcript path) | `AfterAgent` (turn end) and `PreCompact`; `transcript_path` is in the base schema |
|
|
31
|
+
| **Tool names for matchers** | Claude's (`Bash`, `Write`, `Edit`) | `Shell`, `Read`, `Write`, MCP, `Task` (generic `preToolUse`) | `run_shell_command` (`command`), `write_file` (`file_path`, `content`), `replace` (`file_path`, `old_string`, `new_string`) |
|
|
32
|
+
| **Subagents** | `SubagentStart`/`SubagentStop` exist; custom agents not checked | Plugins carry `agents/` | Local and remote subagents (`/agents list`, `enable`, `disable`) |
|
|
33
|
+
|
|
34
|
+
## What each would cost, against what exists
|
|
35
|
+
|
|
36
|
+
- **Codex CLI — cheapest, and the only one with an ask tier.** Its hook schema is
|
|
37
|
+
Claude's with `hooks.json`/`config.toml` as the carrier, its payload field names are
|
|
38
|
+
Claude's, and `permissionDecision: "ask"` exists. The Claude engine with `host: "codex"`
|
|
39
|
+
covers instructions (`AGENTS.md`), skills (`.agents/skills`) and the four hook verbs
|
|
40
|
+
unchanged; the only new code is the carrier writer (a `hooks.json` merge or a TOML
|
|
41
|
+
table) and the config-dir resolver (`~/.codex`, `$CODEX_HOME`). Roughly the Copilot
|
|
42
|
+
port minus the dialect work.
|
|
43
|
+
|
|
44
|
+
- **Gemini CLI — Claude's groups, Gemini's names.** The hook groups are Claude's
|
|
45
|
+
`event → [{matcher, hooks}]` and the payload fields are Claude's, but the events are
|
|
46
|
+
`BeforeTool`/`AfterAgent` and the verdict is `decision: "deny"` or a `systemMessage`.
|
|
47
|
+
Bob Shell shares Gemini's *config* shape (`context.fileName`, `tools.allowed:
|
|
48
|
+
["run_shell_command(git)"]`), but Bob's documented *hook* protocol is its own —
|
|
49
|
+
Claude's event names, refusal by exit code 2; see `adapters/bob/README.md` — so the two
|
|
50
|
+
are not one dialect. Instructions need one settings key (`context.fileName` to include
|
|
51
|
+
`AGENTS.md`) or a `GEMINI.md` carrier; skills go to `.agents/skills`. No ask tier: Laws
|
|
52
|
+
I/IV deny, the consent rules become a `systemMessage`.
|
|
53
|
+
|
|
54
|
+
- **Cursor — a third verdict dialect.** `AGENTS.md` and `.agents/skills` are free; hooks
|
|
55
|
+
are `hooks.json` with a `{command}` list per event and a `permission: allow|deny`
|
|
56
|
+
verdict, `additional_context` in snake_case for `sessionStart`. Same block-or-warn stance
|
|
57
|
+
as Copilot, one more output spelling in `js/hosts/hooks.mjs`, one more carrier writer.
|
|
58
|
+
Cursor also reads `.claude/skills/`, so a Claude project install already gives a Cursor
|
|
59
|
+
user the skills.
|
|
60
|
+
|
|
61
|
+
## Recommendation
|
|
62
|
+
|
|
63
|
+
If a fifth host is wanted, Codex first: it reuses the most and it is the only candidate
|
|
64
|
+
where the gates can *ask* rather than block. Before any of them, a `--emit agents` for
|
|
65
|
+
the shared `.agents/skills/` folder would give Codex, Cursor and Gemini users the whole
|
|
66
|
+
skill catalogue for the price of one directory name, with no host-specific code at all.
|
|
67
|
+
A Gemini host would be the fourth verdict spelling in `js/hosts/hooks.mjs` (after Claude's
|
|
68
|
+
ask, Copilot's block, Bob's exit code 2), plus a resolver and a carrier.
|
|
69
|
+
|
|
70
|
+
## Not verified
|
|
71
|
+
|
|
72
|
+
None of the three CLIs is installed on the reviewing machine. Every row above is from the
|
|
73
|
+
tool's own documentation as indexed on 2026-09-05; the "learn point" column for Cursor and
|
|
74
|
+
the Codex custom-agent surface were not checked further than noted.
|
package/docs/extending.md
CHANGED
|
@@ -215,11 +215,12 @@ a defect.
|
|
|
215
215
|
|
|
216
216
|
Steps 1 and 9 of §2c, plus `LAW_META` if the Principle line moved. Two cautions:
|
|
217
217
|
|
|
218
|
-
- `--footprint` defaults to **lean**, and lean
|
|
219
|
-
(`
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
218
|
+
- `--footprint` defaults to **lean**, and a law's lean text is the authored `LEAN:else` half of
|
|
219
|
+
its block in `src/laws/universal.md` (`resolveLean` in `js/build/render.mjs`). Amend **both**
|
|
220
|
+
halves, or the amendment does not exist for most installs. A doctrine rule is different: it is
|
|
221
|
+
still machine-cut to its heading plus first sentence (`terseBlocks`), so a clause in its second
|
|
222
|
+
paragraph is invisible in the default build. The on-disk `laws/` and `ontology/` copies always
|
|
223
|
+
render at full, whatever the footprint.
|
|
223
224
|
|
|
224
225
|
### 2c — Adding an INVARIANT
|
|
225
226
|
|
package/docs/token-footprint.md
CHANGED
|
@@ -4,10 +4,11 @@ What a deployed harness costs in context-window tokens, per host. Token counts a
|
|
|
4
4
|
chars/4 estimates and shift a little with theme and version.
|
|
5
5
|
|
|
6
6
|
**Treat every number below as a floor, not a reading.** They were measured on a
|
|
7
|
-
`--footprint
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
`--footprint lean` build (the default) of the neutral theme when the tree carried
|
|
8
|
+
47 skills and 17 agents, and nothing re-measures this page automatically. For a
|
|
9
|
+
live figure on your own install, ask the agent for the `token-report` skill —
|
|
10
|
+
that is what it is for. A `--footprint full` build runs roughly 5k tokens
|
|
11
|
+
heavier on the root file.
|
|
11
12
|
|
|
12
13
|
## Always-on cost per session
|
|
13
14
|
|
|
@@ -15,40 +16,50 @@ The context every host injects at session start, before you type anything:
|
|
|
15
16
|
|
|
16
17
|
| Component | Claude Code | OpenCode | Bob | Copilot |
|
|
17
18
|
|---|---|---|---|---|
|
|
18
|
-
| Root instruction file | ~
|
|
19
|
-
| Skill metadata (name + description) | ~1.
|
|
20
|
-
| Agent metadata | ~0.
|
|
19
|
+
| Root instruction file | ~6.4k (`CLAUDE.md`) | ~6.4k (`AGENT.md`) | ~8.3k (`AGENTS.md`) | ~8.3k (`AGENTS.md`) |
|
|
20
|
+
| Skill metadata (name + description) | ~1.7k | ~1.7k | ~1.7k | ~1.7k |
|
|
21
|
+
| Agent metadata | ~0.5k | ~0.55k | ~0.5k | ~0.5k |
|
|
21
22
|
| Eager memory/notebook injection | ~1.2k (SessionStart hook) | ~1.2k (context plugin) | ~1.2k (SessionStart hook) | — (no hooks; read on demand) |
|
|
22
|
-
| **Total** | **~
|
|
23
|
+
| **Total** | **~9.8k** | **~9.9k** | **~11.7k** | **~10.5k** |
|
|
23
24
|
|
|
24
|
-
The emits are at parity by design: ~
|
|
25
|
-
window
|
|
26
|
-
|
|
25
|
+
The emits are at parity by design: ~10–12k tokens, about 5–6% of a 200k
|
|
26
|
+
window. Bob and Copilot carry the §3/§4 catalogue tables inline because their
|
|
27
|
+
hosts expose no native skill/agent inventory; Claude Code and OpenCode ship a
|
|
28
|
+
pointer to the host's own inventory instead, ~1.9k tokens lighter. Copilot also
|
|
29
|
+
runs without eager injection — it has no hook mechanism, so the
|
|
30
|
+
memory/notebook indexes load when the agent reads them, not eagerly. The
|
|
27
31
|
eager-injection path is budget-capped identically everywhere
|
|
28
32
|
(16 KB per file, 48 KB total ≈ 12k tokens ceiling), so growing Memory degrades
|
|
29
33
|
every host the same way instead of one silently falling behind.
|
|
30
34
|
|
|
31
35
|
## Where the tokens go
|
|
32
36
|
|
|
33
|
-
- **The root file is ~
|
|
37
|
+
- **The root file is ~65% of the bill**, and the constitution inside it is the
|
|
34
38
|
bulk of that — no longer one section but three: the Ontology, the nine Rules,
|
|
35
39
|
and the doctrine packs this install built in. **Two** levers move it, not one.
|
|
36
|
-
The [footprint dial](#/docs/footprint) (`lean`, the default,
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
The [footprint dial](#/docs/footprint) (`lean`, the default, swaps each Rule,
|
|
41
|
+
the Ontology and the longest prose sections — the preamble, §6 Notebook, §8
|
|
42
|
+
Wiki — for their hand-written condensations, and cuts each doctrine rule to its
|
|
43
|
+
heading plus first line; `full` inlines every rationale). And the
|
|
39
44
|
[doctrine packs](#/docs/setup-choices) — `geneseed-build --doctrines craft,rigor`
|
|
40
45
|
drops whole blocks of §2 at build time, which removes far more than the
|
|
41
46
|
footprint dial trims. Anything else is noise.
|
|
42
47
|
|
|
43
|
-
The
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
The lean condensations are authored, not generated. An earlier design shipped
|
|
49
|
+
the Ontology *whole* under `lean` on the grounds that truncating four flowing
|
|
50
|
+
sections to four orphan sentences would ship a worldview nobody could read —
|
|
51
|
+
the `LEAN:` blocks answer that by hand-writing the short form instead
|
|
52
|
+
(measured on `neutral`, the root file at `lean` went from 29,966 bytes to
|
|
53
|
+
25,614 with them).
|
|
48
54
|
- **Skill bodies (~53k) and agent bodies (~10k) are lazy** on every host —
|
|
49
55
|
loaded only when invoked. A typical skill costs ≤3k per invocation; the
|
|
50
56
|
heaviest (`react-view-transitions`, ~17.5k with its reference files) loads
|
|
51
57
|
its references progressively.
|
|
58
|
+
- **User-only skills leave the catalogue.** A skill whose source carries
|
|
59
|
+
`<!-- invocation: user -->` renders `disable-model-invocation: true`; Claude
|
|
60
|
+
Code and Bob then drop its description from the always-on skill metadata and
|
|
61
|
+
only `/name` opens it. Seven skills carry it today (the five teaching drills,
|
|
62
|
+
`herdr`, `opencode-theme`), roughly a seventh of the ~1.7k skill-metadata line.
|
|
52
63
|
- Plugin JavaScript (OpenCode, ~26k on disk) runs in the host runtime and
|
|
53
64
|
never enters the context window.
|
|
54
65
|
|
|
@@ -73,7 +84,7 @@ token counter also includes, none of which Geneseed controls:
|
|
|
73
84
|
per-repo install, a personal `~/.claude/CLAUDE.md`, or MCP servers each add
|
|
74
85
|
their own share.
|
|
75
86
|
|
|
76
|
-
So a ~
|
|
87
|
+
So a ~30k session on a work repo is normal arithmetic: ~10k harness +
|
|
77
88
|
~10k repo docs + ~5–10k host overhead. Set `GENESEED_DEBUG=1` (OpenCode) to
|
|
78
89
|
log exactly which files the context plugin injected, or run the SessionStart
|
|
79
90
|
command by hand to see the hook's payload.
|
package/docs/web-ui.md
CHANGED
|
@@ -51,7 +51,7 @@ The left rail mirrors the harness's own shape:
|
|
|
51
51
|
| Group | Views |
|
|
52
52
|
| --- | --- |
|
|
53
53
|
| *(ungrouped)* | Dashboard |
|
|
54
|
-
| **📖 Codex** | Constitution · Rules · Profile · Skills · Agents · Library · Docs
|
|
54
|
+
| **📖 Codex** | Constitution · Rules · Profile · Skills · Agents · Library · Docs |
|
|
55
55
|
| **🩺 Care** | Activity · Changes · Doctor |
|
|
56
56
|
| **🎨 Setup** | Harness · Settings |
|
|
57
57
|
|
|
@@ -72,9 +72,10 @@ streams the output of background jobs and keeps their history across reloads.
|
|
|
72
72
|
### 🧬 Harness
|
|
73
73
|
|
|
74
74
|
- **Dashboard** (`#/`) — a live readout of what's deployed. Three lenses: **Status**
|
|
75
|
-
(readiness ring, a KPI band, a genome grid, recent activity), **Lineage** (
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
(readiness ring, a KPI band, a genome grid, recent activity), **Lineage** (the
|
|
76
|
+
source → render → deployed timeline and the genome-by-volume strand chart), and
|
|
77
|
+
**Operator** (a searchable table of every deployed capability). Headlines reflect the
|
|
78
|
+
active theme's voice.
|
|
78
79
|
|
|
79
80
|
The KPI band reads **Agents · Skills · Invariants · Local edits**. That third tile is
|
|
80
81
|
labelled *Invariants* and not *Constitution* on purpose: its value is the invariant count
|
|
@@ -107,15 +108,6 @@ streams the output of background jobs and keeps their history across reloads.
|
|
|
107
108
|
**Promote to rule** control turns a recurring lesson into a trial rule in
|
|
108
109
|
`user-rules.md` — provenance recorded, the source fact deleted so it isn't loaded
|
|
109
110
|
twice — landing you on the Rules page.
|
|
110
|
-
- **Graph** (`#/graph`) — the cross-link constellation across agents, skills and the
|
|
111
|
-
**invariants only**: every `[[wikilink]]` between agents/skills, plus every `Rule N`
|
|
112
|
-
mention that lands on a real invariant. ⚠ The reference pattern matches the tier noun
|
|
113
|
-
followed by a **Roman numeral** — `Rule`, `Law`, or whatever the active theme calls one,
|
|
114
|
-
which is how `Dictate III` still resolves under `imperial`. A doctrine address is
|
|
115
|
-
`<pack> <n>` and matches nothing, so **doctrine cross-links are not drawn**; the graph is
|
|
116
|
-
a view of the invariant backbone, not of the whole constitution. Hover to isolate a
|
|
117
|
-
neighbourhood, scroll to zoom, drag to pan, search to highlight; orphans dim out. Click a
|
|
118
|
-
node to open its spec.
|
|
119
111
|
|
|
120
112
|
### 📚 Learn
|
|
121
113
|
|
package/harness.config.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
"_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
|
|
3
3
|
"theme": "neutral",
|
|
4
4
|
"product": "Geneseed",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.0"
|
|
6
6
|
}
|
package/js/README.md
CHANGED
|
@@ -13,7 +13,7 @@ a module that does not exist, is a test failure. `geneseed doctor` reports the s
|
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
bin/geneseed-cli.mjs the `geneseed` CLI — 20+ verbs, may NOT spawn
|
|
16
|
-
bin/geneseed-hook.mjs the hook entry —
|
|
16
|
+
bin/geneseed-hook.mjs the hook entry — 5 verbs, loads on EVERY tool call (~14 ms)
|
|
17
17
|
bin/build-driver.mjs `geneseed-build` — the generator's flags and emit targets
|
|
18
18
|
|
|
19
19
|
js/build/ src/ + themes/ → rendered text → a bundle → a per-host install
|
|
@@ -66,7 +66,7 @@ user co-owns, and the hook verbs those emitted configs then run.
|
|
|
66
66
|
| module | owns |
|
|
67
67
|
|---|---|
|
|
68
68
|
| `hosts.mjs` | The four host config dirs (opencode/claude/bob/copilot), plus `resolvePath`/`expanduser` |
|
|
69
|
-
| `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `learn` |
|
|
69
|
+
| `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `tool-gate` (the two gates fused, for Copilot), `learn`. `--host` picks the verdict dialect |
|
|
70
70
|
| `settings.mjs` | Merges into user-owned `settings.json`/`opencode.json`; owns the hook shim and the managed blocks |
|
|
71
71
|
| `native.mjs` | Capability specs → host-native subagents and skills. The impure half of the emit |
|
|
72
72
|
| `opencode.mjs` | OpenCode-only extras: colour themes, primary agent, `/commands`, plugins, overrides stub |
|
|
@@ -168,13 +168,12 @@ the deployed harness as JSON.
|
|
|
168
168
|
| `daemon.mjs` | Detached web start/stop/status/restart, the `.geneseed-web.json` record, and `openUrl` |
|
|
169
169
|
| `jobs.mjs` | `JobManager` and the action→argv table. The only module here that spawns job children |
|
|
170
170
|
| `docs.mjs` | The Docs tab: the `docs/web/` page registry, `?harness=` filtering, the five page kinds |
|
|
171
|
-
| `graph.mjs` | `/api/graph` alone — the agent/skill/law cross-link matrix built from rendered bodies |
|
|
172
171
|
| `activity.mjs` | The `activity/` session files: pid-liveness prune, list, detail, and the on/off flag |
|
|
173
172
|
|
|
174
173
|
**Before editing:** `makeHandler(state, jm, token, dist, holder)`'s arity is load-bearing — three
|
|
175
|
-
test call sites construct one directly and drive it without a socket.
|
|
176
|
-
|
|
177
|
-
`
|
|
174
|
+
test call sites construct one directly and drive it without a socket. `routes.mjs`'s tables
|
|
175
|
+
and inline lists are held against a surface list written out in
|
|
176
|
+
`tests/unit/web_server.test.mjs` — growing or shrinking the API edits both, in one commit.
|
|
178
177
|
|
|
179
178
|
---
|
|
180
179
|
|
|
@@ -192,12 +191,3 @@ exported sets are deliberately EMPTY and must stay declared: each is half of a p
|
|
|
192
191
|
| a console doc page | `docs/web/<id>.md` — the stem is the id | `docs/extending.md` §4c. Only `kind: "concept"` gets `{N_*}` substitution |
|
|
193
192
|
| an OpenCode plugin | `adapters/opencode/plugins/geneseed-<name>.js` | `docs/extending.md` §4a — the `geneseed-` prefix is mechanically load-bearing |
|
|
194
193
|
| to change a `js/lib/` primitive | `tests/fixtures/pure_probe.mjs` **first** | the docblock says which Node default it deliberately departs from — change that sentence too, or the next reader restores the default |
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Known debris
|
|
199
|
-
|
|
200
|
-
* **`js/hosts/settings.mjs` carries a private second copy of Python's whitespace class**, duplicating
|
|
201
|
-
`WHITESPACE` in `js/lib/text.mjs`. `text.mjs` is the owner.
|
|
202
|
-
* **`web/src/pages/Skills.jsx`'s `SKILL_CATS` is a fourth, ungated copy of the skill taxonomy** —
|
|
203
|
-
nothing compares it to `SKILL_CLASS`, and an unknown category renders as Build with no warning.
|