geneseed 3.8.0 → 3.10.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 +71 -1
- package/DESIGN.md +13 -10
- package/QUICKSTART.md +1 -1
- package/README.md +31 -32
- package/SETUP.md +7 -42
- package/SHIPPED.md +4 -4
- package/adapters/bob/README.md +17 -1
- package/adapters/openclaude/README.md +1 -1
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +1 -1
- package/adapters/opencode/README.md +3 -3
- package/adapters/opencode/plugins/geneseed-context.js +78 -8
- package/adapters/opencode/plugins/geneseed-guard.js +9 -9
- package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
- package/adapters/opencode/workflows/_runtime.js +2 -2
- package/adapters/opencode/workflows/dispatch.js +1 -1
- package/bin/geneseed-hook.mjs +1 -1
- package/docs/candidate-hosts.md +12 -5
- package/docs/extending.md +72 -40
- package/docs/token-footprint.md +26 -21
- package/docs/web/claude-hooks.md +1 -1
- package/docs/web/collaboration.md +1 -1
- package/docs/web/exclusions.md +0 -1
- package/docs/web/footprint.md +1 -1
- package/docs/web/harnesses.md +14 -14
- package/docs/web/install-quick.md +1 -1
- package/docs/web/plugin-guard.md +2 -2
- package/docs/web/rules.md +1 -1
- package/docs/web/themes-author.md +2 -2
- package/docs/web-ui.md +2 -2
- package/harness.config.json +1 -1
- package/js/README.md +8 -7
- package/js/build/driver.mjs +26 -95
- package/js/build/emit-claude.mjs +76 -81
- package/js/build/generate.mjs +0 -1
- package/js/build/render.mjs +10 -3
- package/js/build/source.mjs +105 -1
- package/js/build/stubs.mjs +25 -4
- package/js/cli-table.json +8 -8
- package/js/hosts/hooks.mjs +91 -59
- package/js/hosts/hosts.mjs +5 -21
- package/js/hosts/installs.mjs +4 -6
- package/js/hosts/mcp.mjs +3 -13
- package/js/hosts/native.mjs +28 -51
- package/js/hosts/opencode.mjs +1 -1
- package/js/hosts/settings.mjs +4 -78
- package/js/inspect/checks-authoring.mjs +153 -117
- package/js/inspect/checks-repo.mjs +45 -1
- package/js/inspect/diff.mjs +11 -3
- package/js/inspect/doctor.mjs +3 -3
- package/js/inspect/excludes.mjs +0 -4
- package/js/inspect/inventory.mjs +1 -2
- package/js/inspect/status.mjs +4 -4
- package/js/maintain/migrate.mjs +5 -5
- package/js/maintain/setup.mjs +3 -5
- package/js/maintain/uninstall.mjs +8 -42
- package/js/ui/tui.mjs +1 -1
- package/js/web/actions.mjs +4 -5
- package/js/web/api.mjs +10 -5
- package/package.json +1 -2
- package/registry.json +7 -0
- package/src/AGENT.md.tmpl +86 -65
- package/src/agents/_template.md +3 -3
- package/src/agents/advocate.md +3 -3
- package/src/agents/architect.md +3 -3
- package/src/agents/developer.md +4 -4
- package/src/agents/docs.md +3 -3
- package/src/agents/empiricist.md +4 -4
- package/src/agents/explorer.md +4 -4
- package/src/agents/framer.md +3 -3
- package/src/agents/historian.md +3 -3
- package/src/agents/operator.md +3 -3
- package/src/agents/pragmatist.md +3 -3
- package/src/agents/researcher.md +7 -7
- package/src/agents/reviewer.md +3 -3
- package/src/agents/security.md +3 -3
- package/src/agents/skeptic.md +3 -3
- package/src/agents/steward.md +3 -3
- package/src/agents/tester.md +3 -3
- package/src/agents/user-advocate.md +3 -3
- package/src/agents/visionary.md +3 -3
- package/src/doctrines/README.md +4 -3
- package/src/doctrines/comms.md +4 -4
- package/src/doctrines/craft.md +20 -20
- package/src/doctrines/ops.md +19 -19
- package/src/doctrines/process.md +20 -20
- package/src/doctrines/rigor.md +22 -22
- package/src/laws/universal.md +26 -44
- package/src/memory/README.md +3 -3
- package/src/modes/foreman.md +1 -1
- package/src/notebook/README.md +1 -1
- package/src/ontology/universal.md +8 -10
- package/src/postures/artisan.md +2 -2
- package/src/skills/_self-improvement.md +4 -4
- package/src/skills/_template.md +1 -1
- package/src/skills/brainstorm.md +3 -3
- package/src/skills/bruno-collection-generator.md +6 -6
- package/src/skills/bruno-test-writer.md +5 -5
- package/src/skills/ci-fix.md +4 -4
- package/src/skills/commit.md +11 -10
- package/src/skills/consolidate-memory.md +4 -4
- package/src/skills/council.md +2 -2
- package/src/skills/daydream/SKILL.md +10 -3
- package/src/skills/daydream/VENDOR.md +10 -2
- package/src/skills/daydream/instructions.md +2 -2
- package/src/skills/debug.md +4 -4
- package/src/skills/deps-audit.md +3 -3
- package/src/skills/develop.md +7 -7
- package/src/skills/document-project.md +6 -6
- package/src/skills/explain-changes/SKILL.md +69 -0
- package/src/skills/explain-changes/VENDOR.md +20 -0
- package/src/skills/explain-changes/scripts/render_changes.mjs +558 -0
- package/src/skills/forge-mcp.md +4 -4
- package/src/skills/fresh-eyes.md +4 -4
- package/src/skills/frontend-design.md +1 -1
- package/src/skills/geneseed.md +4 -4
- package/src/skills/git-archaeology.md +5 -5
- package/src/skills/git-rescue.md +7 -7
- package/src/skills/handoff.md +3 -3
- package/src/skills/ingest.md +5 -5
- package/src/skills/migrate.md +8 -8
- package/src/skills/parallel-agents.md +2 -2
- package/src/skills/pipeline.md +2 -2
- package/src/skills/plan.md +3 -3
- package/src/skills/ponytail.md +2 -2
- package/src/skills/profile.md +2 -2
- package/src/skills/prose.md +1 -1
- package/src/skills/prototype.md +1 -1
- package/src/skills/react-view-transitions/SKILL.md +1 -1
- package/src/skills/react-view-transitions/VENDOR.md +7 -2
- package/src/skills/refactor.md +3 -3
- package/src/skills/release.md +5 -5
- package/src/skills/repo-map.md +4 -4
- package/src/skills/research.md +9 -9
- package/src/skills/review-response.md +4 -4
- package/src/skills/rule.md +3 -3
- package/src/skills/security-audit.md +5 -5
- package/src/skills/ship.md +5 -5
- package/src/skills/skill-forge.md +4 -4
- package/src/skills/token-report/SKILL.md +2 -3
- package/src/skills/token-report/VENDOR.md +2 -3
- package/src/skills/token-report/scripts/token_report.mjs +7 -121
- package/src/skills/wiki.md +5 -5
- package/src/skills/workflow.md +3 -4
- package/src/skills/worktree.md +4 -4
- package/themes/_TEMPLATE.json +37 -39
- package/themes/biker.json +37 -39
- package/themes/commentator.json +37 -39
- package/themes/cyberpunk.json +37 -39
- package/themes/gamer.json +37 -39
- package/themes/imperial.json +37 -39
- package/themes/joker.json +37 -39
- package/themes/marvin.json +37 -39
- package/themes/mean.json +37 -39
- package/themes/military.json +37 -39
- package/themes/neutral.json +37 -39
- package/themes/pirate.json +37 -39
- package/themes/sports.json +37 -39
- package/themes/verstappen.json +37 -39
- package/themes/wizard.json +37 -39
- package/web/dist/assets/Activity-BjVp522q.js +1 -0
- package/web/dist/assets/ActivityDetail-8ep1bspW.js +1 -0
- package/web/dist/assets/Dashboard-OkC_PCH6.js +2 -0
- package/web/dist/assets/{Docs-DrJNDPwI.js → Docs-CKPWz00o.js} +2 -2
- package/web/dist/assets/FilterInput-CVyqmYWM.js +1 -0
- package/web/dist/assets/GreenhouseView-D0cbCJ5m.js +2 -0
- package/web/dist/assets/Installs-BtGGdoTt.js +2 -0
- package/web/dist/assets/JournalView-Ckl4dmzR.js +1 -0
- package/web/dist/assets/Laws-CLpdRRk8.js +3 -0
- package/web/dist/assets/Library-wPPuKQrI.js +1 -0
- package/web/dist/assets/{Markdown-BQAvN8xx.js → Markdown-CkFyfgNf.js} +14 -14
- package/web/dist/assets/OperatorHudView-CiAkAkQL.js +2 -0
- package/web/dist/assets/Personal-D8CnDo9v.js +1 -0
- package/web/dist/assets/Tabs-B4HNrrCs.js +1 -0
- package/web/dist/assets/drawable-BiwaOo6x.js +1 -0
- package/web/dist/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-YINYabwD.woff2 +0 -0
- package/web/dist/assets/index-C1yil5j1.js +2 -0
- package/web/dist/assets/index-CDPDk2vj.css +1 -0
- package/web/dist/assets/{react-DbkvxWG5.js → react-B9Z_tXRV.js} +1 -1
- package/web/dist/assets/sections-GalMS0Zv.js +1 -0
- package/web/dist/assets/usePoll-O0sJEw1Y.js +1 -0
- package/web/dist/index.html +4 -4
- package/web/src/pages/Laws.jsx +616 -361
- package/adapters/copilot/README.md +0 -105
- package/web/dist/assets/Activity-CH-7cPIM.js +0 -1
- package/web/dist/assets/ActivityDetail-DZvz0xS5.js +0 -1
- package/web/dist/assets/CatalogRow-DftXpZHY.js +0 -1
- package/web/dist/assets/Diff-DvwdG0L9.js +0 -2
- package/web/dist/assets/Doctor-OTmwmYZg.js +0 -1
- package/web/dist/assets/FilterInput-BakJLvaV.js +0 -1
- package/web/dist/assets/GreenhouseView-XxrAdriX.js +0 -2
- package/web/dist/assets/Harness-C_LYNk5c.js +0 -1
- package/web/dist/assets/JournalView-BcuyeEaR.js +0 -1
- package/web/dist/assets/Laws-CWHrTKY4.js +0 -3
- package/web/dist/assets/Library-CA9IHghn.js +0 -1
- package/web/dist/assets/OperatorHudView-BMRY1Tt4.js +0 -2
- package/web/dist/assets/Profile-wceET1JY.js +0 -1
- package/web/dist/assets/Rules-BEXvY4Tx.js +0 -1
- package/web/dist/assets/Settings-DS2zF0Ez.js +0 -1
- package/web/dist/assets/Skills-DahlOJuI.js +0 -1
- package/web/dist/assets/StatusBadge-Cg6j-NJL.js +0 -1
- package/web/dist/assets/activity-iWaWVtsR.js +0 -1
- package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
- package/web/dist/assets/drawable-BTHjTzF7.js +0 -1
- package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
- package/web/dist/assets/index-AN5Emnrg.js +0 -2
- package/web/dist/assets/index-Ruh93A3A.css +0 -1
- package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/web/dist/assets/lawCats-Btjp3Hk_.js +0 -1
- package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
- package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
- package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
- package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/web/dist/assets/rolldown-runtime-CbXtAM7H.js +0 -1
|
@@ -56,6 +56,7 @@ import { promises as fs } from "node:fs"
|
|
|
56
56
|
import * as path from "node:path"
|
|
57
57
|
import { fileURLToPath } from "node:url"
|
|
58
58
|
import { homedir } from "node:os"
|
|
59
|
+
import { createHash } from "node:crypto"
|
|
59
60
|
|
|
60
61
|
const MARKER = "<!-- geneseed-context:v2 -->"
|
|
61
62
|
const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -156,6 +157,49 @@ const EXCLUDE_DIRS = new Set([
|
|
|
156
157
|
async function isFile(p) { try { return (await fs.stat(p)).isFile() } catch { return false } }
|
|
157
158
|
async function isDir(p) { try { return (await fs.stat(p)).isDirectory() } catch { return false } }
|
|
158
159
|
|
|
160
|
+
// ---- session files (the harness's own) --------------------------------------
|
|
161
|
+
// The files AGENT.md names for session start, relative to the HARNESS dir — the dir that
|
|
162
|
+
// holds the installed AGENT.md: the repo root for a project install, the config dir for a
|
|
163
|
+
// global one. Same list and seed hashes as SESSION_FILES/SEED_SHA256 in js/hosts/hooks.mjs;
|
|
164
|
+
// tests/unit/claude.test.mjs gates both copies against SESSION_SEEDS in js/build/stubs.mjs.
|
|
165
|
+
// context.json is not here: it is a manifest, honoured by resolveSource. wiki.jsonc is not
|
|
166
|
+
// here either: the MACHINE WIKI block below already renders it in full.
|
|
167
|
+
const SESSION_FILES = ["user-rules.md", "PROFILE.md", "memory/MEMORY.md", "anamnesis/MEMORY.md",
|
|
168
|
+
"notebook/NOTEBOOK.md"]
|
|
169
|
+
const SEED_SHA256 = new Set([
|
|
170
|
+
"5c2e92fb1acde041e02d9ebf158460d8ff7a07cbc2b447859631604610130721", // user-rules.md
|
|
171
|
+
"29e012c3c4349ea62a9082cbd04bb25599a0ec280d62c33f6e10742809471816", // PROFILE.md
|
|
172
|
+
"cccc917c34e6b990e821620bdb4739090151885ab71b6e85046f30a3b71fa5e8", // wiki.jsonc
|
|
173
|
+
"99c786049c260f6baf7aec68a5c0f59907861afe9b867da009440613bdddb907", // MEMORY.md
|
|
174
|
+
"9acb5c9d9cb5dac572104480f05b48cc144e676869bd60e86d8a15b1f6308184", // NOTEBOOK.md
|
|
175
|
+
])
|
|
176
|
+
|
|
177
|
+
// $GENESEED_HARNESS when set, else the first of <plugin dir>/.. (global: the config dir) and
|
|
178
|
+
// <plugin dir>/../.. (project: .opencode/plugins -> the repo root) that holds AGENT.md.
|
|
179
|
+
async function harnessDir() {
|
|
180
|
+
if (process.env.GENESEED_HARNESS && (await isDir(process.env.GENESEED_HARNESS))) {
|
|
181
|
+
return process.env.GENESEED_HARNESS
|
|
182
|
+
}
|
|
183
|
+
for (const d of [path.resolve(PLUGIN_DIR, ".."), path.resolve(PLUGIN_DIR, "..", "..")]) {
|
|
184
|
+
if (await isFile(path.join(d, "AGENT.md"))) return d
|
|
185
|
+
}
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Session files present under the harness dir and changed from their seed: [{ rel, abs, text }].
|
|
190
|
+
async function sessionFiles(dir) {
|
|
191
|
+
const found = []
|
|
192
|
+
if (!dir) return found
|
|
193
|
+
for (const rel of SESSION_FILES) {
|
|
194
|
+
const abs = path.join(dir, rel)
|
|
195
|
+
let text
|
|
196
|
+
try { text = (await fs.readFile(abs, "utf8")).replace(/\r\n/g, "\n") } catch { continue }
|
|
197
|
+
if (SEED_SHA256.has(createHash("sha256").update(text).digest("hex"))) continue
|
|
198
|
+
found.push({ rel, abs, text: text.replace(/\n+$/, "") })
|
|
199
|
+
}
|
|
200
|
+
return found
|
|
201
|
+
}
|
|
202
|
+
|
|
159
203
|
async function readJson(p) {
|
|
160
204
|
try { return JSON.parse(await fs.readFile(p, "utf8")) } catch { return null }
|
|
161
205
|
}
|
|
@@ -474,11 +518,13 @@ async function fromManifest(manifestFile, root) {
|
|
|
474
518
|
return { eager: eager.sort(byRel), lazy: lazy.sort(byRel) }
|
|
475
519
|
}
|
|
476
520
|
|
|
477
|
-
async function resolveSource(root) {
|
|
521
|
+
async function resolveSource(root, harness = null) {
|
|
478
522
|
const explicit = process.env.GENESEED_CONTEXT
|
|
479
523
|
if (explicit && (await isFile(explicit))) return { mode: "manifest", file: explicit }
|
|
480
|
-
|
|
481
|
-
|
|
524
|
+
// The harness dir last: a repo's own manifest outranks the one seeded beside the install.
|
|
525
|
+
const cands = [path.join(root, ".harness", "context.json"), path.join(root, "context.json")]
|
|
526
|
+
if (harness) cands.push(path.join(harness, "context.json"))
|
|
527
|
+
for (const p of cands) {
|
|
482
528
|
if (await isFile(p)) return { mode: "manifest", file: p }
|
|
483
529
|
}
|
|
484
530
|
return { mode: "discover" }
|
|
@@ -494,8 +540,12 @@ async function resolveSource(root) {
|
|
|
494
540
|
async function resolveWikiFile() {
|
|
495
541
|
const explicit = process.env.GENESEED_WIKI
|
|
496
542
|
if (explicit && (await isFile(explicit))) return explicit
|
|
543
|
+
// The harness dir (which honours $GENESEED_HARNESS) first: on a PROJECT install the plugin
|
|
544
|
+
// sits in .opencode/plugins, so <plugin dir>/.. is .opencode, not the dir the seed landed in.
|
|
545
|
+
// <plugin dir>/.. stays as the fallback for an install without an AGENT.md.
|
|
497
546
|
const bases = []
|
|
498
|
-
|
|
547
|
+
const harness = await harnessDir()
|
|
548
|
+
if (harness) bases.push(harness)
|
|
499
549
|
bases.push(path.resolve(PLUGIN_DIR, ".."))
|
|
500
550
|
for (const base of bases) {
|
|
501
551
|
// wiki.json is the legacy name from earlier seeds — still honoured.
|
|
@@ -652,9 +702,27 @@ async function renderSet(out, { eager, lazy }, state) {
|
|
|
652
702
|
state.lazy += lazyLines.length
|
|
653
703
|
}
|
|
654
704
|
|
|
655
|
-
async function buildBlock(sets, wikis = [], commands = []) {
|
|
705
|
+
async function buildBlock(sets, wikis = [], commands = [], session = []) {
|
|
656
706
|
const state = { spent: 0, injected: 0, lazy: 0, headingsRead: 0 }
|
|
657
707
|
|
|
708
|
+
// A session file discovery also found (user-rules.md at the root of a project install,
|
|
709
|
+
// whose harness dir IS the repo root) is injected once, in the session block.
|
|
710
|
+
const seen = new Set(session.map((s) => path.resolve(s.abs)))
|
|
711
|
+
const fresh = (x) => !seen.has(path.resolve(x.abs))
|
|
712
|
+
sets = { eager: sets.eager.filter(fresh), lazy: sets.lazy.filter(fresh) }
|
|
713
|
+
const ses = []
|
|
714
|
+
for (const s of session) {
|
|
715
|
+
const size = Buffer.byteLength(s.text, "utf8")
|
|
716
|
+
ses.push(`----- ${s.rel} -----`)
|
|
717
|
+
if (size > EAGER_FILE_KB * 1024) {
|
|
718
|
+
ses.push(`[demoted: ${s.rel} exceeded ${EAGER_FILE_KB} KB — read ${s.abs} on demand]`, "")
|
|
719
|
+
continue
|
|
720
|
+
}
|
|
721
|
+
ses.push(s.text, "")
|
|
722
|
+
state.spent += size
|
|
723
|
+
state.injected++
|
|
724
|
+
}
|
|
725
|
+
|
|
658
726
|
const proj = []
|
|
659
727
|
await renderSet(proj, sets, state)
|
|
660
728
|
|
|
@@ -675,8 +743,9 @@ async function buildBlock(sets, wikis = [], commands = []) {
|
|
|
675
743
|
if (w.truncated) wik.push(` [+${w.truncated} more notes in this wiki — explore its folders on demand]`, "")
|
|
676
744
|
}
|
|
677
745
|
|
|
678
|
-
if (!proj.length && !wik.length) return null
|
|
746
|
+
if (!ses.length && !proj.length && !wik.length) return null
|
|
679
747
|
const out = [MARKER]
|
|
748
|
+
if (ses.length) out.push("=== SESSION FILES — your harness files, injected at session start (untouched seeds skipped) ===", "", ...ses)
|
|
680
749
|
if (proj.length) out.push("=== PROJECT CONTEXT — binding for this repo ===", "", ...proj)
|
|
681
750
|
if (wik.length) out.push("=== MACHINE WIKI — the user's knowledge base, binding per AGENT.md §8 ===", "", ...wik)
|
|
682
751
|
return { text: out.join("\n"), injected: state.injected, lazy: state.lazy, kb: kb(state.spent) }
|
|
@@ -686,13 +755,14 @@ async function buildBlock(sets, wikis = [], commands = []) {
|
|
|
686
755
|
// and render the injection block — shared by the session.created injection and the
|
|
687
756
|
// compaction hook (the transform path reuses it via cachedBlockText).
|
|
688
757
|
async function resolveBlock(root) {
|
|
689
|
-
const
|
|
758
|
+
const harness = await harnessDir()
|
|
759
|
+
const src = await resolveSource(root, harness)
|
|
690
760
|
const sets = src.mode === "manifest"
|
|
691
761
|
? await fromManifest(src.file, root)
|
|
692
762
|
: await discover(root)
|
|
693
763
|
const wikis = await wikiSets()
|
|
694
764
|
const commands = await discoverCommands(root)
|
|
695
|
-
return { block: await buildBlock(sets, wikis, commands), src }
|
|
765
|
+
return { block: await buildBlock(sets, wikis, commands, await sessionFiles(harness)), src }
|
|
696
766
|
}
|
|
697
767
|
|
|
698
768
|
// Per-process cache of the rendered block text for the transform path (root is fixed
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Enforces the safety Laws at the tool boundary (`tool.execute.before`), the same
|
|
4
4
|
// "enforce by injection, don't just instruct" stance as the context plugin:
|
|
5
|
-
// -
|
|
6
|
-
// -
|
|
7
|
-
// -
|
|
5
|
+
// - Sealed Secrets: block writes to private-key / credential files.
|
|
6
|
+
// - Deletion Is Deliberate: block catastrophic shell commands.
|
|
7
|
+
// - Persist Insight (rule vs memory): speed-bump the first write to user-rules.md
|
|
8
8
|
// memory file — that choice belongs to the user, via the rule skill.
|
|
9
9
|
// - Wiki (AGENT.md §8): block mutations under a declared wiki's `protected`
|
|
10
10
|
// folders — the user's knowledge base sets its own no-go zones in wiki.jsonc.
|
|
@@ -238,7 +238,7 @@ async function protectedPrefixes() {
|
|
|
238
238
|
return prefixes
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
// ---- the rule / memory stores (
|
|
241
|
+
// ---- the rule / memory stores (Persist Insight) --------------------------------
|
|
242
242
|
// Whether something the user wants kept is a standing rule or a durable fact is THEIR
|
|
243
243
|
// call, settled through the rule skill. `tool.execute.before` can only allow or throw —
|
|
244
244
|
// it has no "ask the user" tier like the Claude/Bob rule-gate hook —
|
|
@@ -285,13 +285,13 @@ export const GeneseedGuard = async () => {
|
|
|
285
285
|
try {
|
|
286
286
|
if (hasAny(tool, WRITE_TOOLS)) {
|
|
287
287
|
const p = pickPath(args)
|
|
288
|
-
if (p && SECRET_RE.some((re) => re.test(p))) { await deny(`write to secret/key file ${p} (
|
|
289
|
-
if (p && SECRET_WARN_RE.some((re) => re.test(p))) log(`WARN: writing ${p} — keep secrets out of tracked files (
|
|
288
|
+
if (p && SECRET_RE.some((re) => re.test(p))) { await deny(`write to secret/key file ${p} (Sealed Secrets)`, "law-1"); return }
|
|
289
|
+
if (p && SECRET_WARN_RE.some((re) => re.test(p))) log(`WARN: writing ${p} — keep secrets out of tracked files (Sealed Secrets)`)
|
|
290
290
|
const store = p && ruleStoreTarget(p)
|
|
291
291
|
if (store && !RULE_STORE_BUMPED.has(p)) {
|
|
292
292
|
RULE_STORE_BUMPED.add(p)
|
|
293
293
|
await deny(`writing to ${store} — a standing rule, or a fact to remember? That ` +
|
|
294
|
-
`choice is the user's (
|
|
294
|
+
`choice is the user's (Persist Insight). Settle it through the rule skill, ` +
|
|
295
295
|
`then re-issue this write`, "process-1")
|
|
296
296
|
return
|
|
297
297
|
}
|
|
@@ -309,8 +309,8 @@ export const GeneseedGuard = async () => {
|
|
|
309
309
|
// classes, and the shell check must still run after the write check.
|
|
310
310
|
if (hasAny(tool, SHELL_TOOLS)) {
|
|
311
311
|
const c = pickCommand(args)
|
|
312
|
-
if (c && SHELL_BLOCK_RE.some((re) => re.test(c))) { await deny(`catastrophic command (
|
|
313
|
-
if (c && SHELL_WARN_RE.some((re) => re.test(c))) log(`WARN: irreversible op — confirm intent (
|
|
312
|
+
if (c && SHELL_BLOCK_RE.some((re) => re.test(c))) { await deny(`catastrophic command (Deletion Is Deliberate): ${c.slice(0, 80)}`, "law-4"); return }
|
|
313
|
+
if (c && SHELL_WARN_RE.some((re) => re.test(c))) log(`WARN: irreversible op — confirm intent (Deletion Is Deliberate): ${c.slice(0, 80)}`)
|
|
314
314
|
}
|
|
315
315
|
} catch (err) {
|
|
316
316
|
// Our own deny must propagate; any inspection error must never break a tool call.
|
|
@@ -89,7 +89,7 @@ function summarize(name, result, stats, tracePath) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// Kept worktrees are work awaiting review, and an overlap is a conflict awaiting ONE
|
|
92
|
-
// merger (
|
|
92
|
+
// merger (One Writer Per File) — both are said out loud, never left for the trace to hold.
|
|
93
93
|
function isolationReport(stats) {
|
|
94
94
|
const kept = stats.worktrees || []
|
|
95
95
|
if (!kept.length) return ""
|
|
@@ -182,8 +182,8 @@ async function runChildSession(client, { prompt, agent, model, title, directory
|
|
|
182
182
|
|
|
183
183
|
// ---- worktree isolation ------------------------------------------------------
|
|
184
184
|
// `agent(prompt, { isolation: "worktree" })` — the same option Claude Code's Workflow
|
|
185
|
-
// takes. Parallel editors in one tree lose each other's writes (
|
|
186
|
-
//
|
|
185
|
+
// takes. Parallel editors in one tree lose each other's writes (One Writer Per File),
|
|
186
|
+
// so each isolated agent gets its own branch + worktree off the root's HEAD, and
|
|
187
187
|
// the files it changed are recorded for `overlaps()`. A worktree with no change is
|
|
188
188
|
// removed with its branch; one with changes is KEPT for the parent to review and merge —
|
|
189
189
|
// the runtime never merges, commits, or deletes work. `git(argv, cwd)` is injected by the
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The model-driven fallback is the parallel-agents skill; this runs the same
|
|
5
5
|
// shape on rails: the script, not the model, enforces routing and the handoff
|
|
6
|
-
// envelope (context in, output contract back, no commits —
|
|
6
|
+
// envelope (context in, output contract back, no commits — Consent Before Push stays with
|
|
7
7
|
// the caller).
|
|
8
8
|
//
|
|
9
9
|
// args: { goal: string, context?: string, agents?: string[] }
|
package/bin/geneseed-hook.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
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
|
|
4
|
+
* `settings.json` actually invokes (four from the reference, plus `tool-gate` for Bob).
|
|
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:
|
package/docs/candidate-hosts.md
CHANGED
|
@@ -38,8 +38,7 @@ current host gets. Skills are the largest surface by file count and they cost no
|
|
|
38
38
|
Claude's, and `permissionDecision: "ask"` exists. The Claude engine with `host: "codex"`
|
|
39
39
|
covers instructions (`AGENTS.md`), skills (`.agents/skills`) and the hook verbs
|
|
40
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`).
|
|
42
|
-
port minus the dialect work.
|
|
41
|
+
table) and the config-dir resolver (`~/.codex`, `$CODEX_HOME`).
|
|
43
42
|
|
|
44
43
|
- **Gemini CLI — Claude's groups, Gemini's names.** The hook groups are Claude's
|
|
45
44
|
`event → [{matcher, hooks}]` and the payload fields are Claude's, but the events are
|
|
@@ -54,7 +53,7 @@ current host gets. Skills are the largest surface by file count and they cost no
|
|
|
54
53
|
- **Cursor — a third verdict dialect.** `AGENTS.md` and `.agents/skills` are free; hooks
|
|
55
54
|
are `hooks.json` with a `{command}` list per event and a `permission: allow|deny`
|
|
56
55
|
verdict, `additional_context` in snake_case for `sessionStart`. Same block-or-warn stance
|
|
57
|
-
as
|
|
56
|
+
as Bob, one more output spelling in `js/hosts/hooks.mjs`, one more carrier writer.
|
|
58
57
|
Cursor also reads `.claude/skills/`, so a Claude project install already gives a Cursor
|
|
59
58
|
user the skills.
|
|
60
59
|
|
|
@@ -64,8 +63,16 @@ If a fifth host is wanted, Codex first: it reuses the most and it is the only ca
|
|
|
64
63
|
where the gates can *ask* rather than block. Before any of them, a `--emit agents` for
|
|
65
64
|
the shared `.agents/skills/` folder would give Codex, Cursor and Gemini users the whole
|
|
66
65
|
skill catalogue for the price of one directory name, with no host-specific code at all.
|
|
67
|
-
A Gemini host would be the
|
|
68
|
-
ask
|
|
66
|
+
A Gemini host would be the third verdict spelling in `js/hosts/hooks.mjs` (after Claude's
|
|
67
|
+
ask and Bob's exit code 2), plus a resolver and a carrier.
|
|
68
|
+
|
|
69
|
+
## Evaluated, dropped
|
|
70
|
+
|
|
71
|
+
- **GitHub Copilot** — shipped as a host through 3.9.0, removed in 3.10.0. Dropped for lack
|
|
72
|
+
of support: its hooks were never verified live, its `postToolUse` output does not reach
|
|
73
|
+
the model, and it shared no cross-tool surface with the other hosts (camelCase payloads,
|
|
74
|
+
matcherless hook entries, its own agent dialect and carrier), so every feature needed a
|
|
75
|
+
Copilot-only decision.
|
|
69
76
|
|
|
70
77
|
## Not verified
|
|
71
78
|
|
package/docs/extending.md
CHANGED
|
@@ -155,36 +155,43 @@ Notes that matter:
|
|
|
155
155
|
|
|
156
156
|
## 2 — Changing the constitution
|
|
157
157
|
|
|
158
|
-
Three tiers, four different costs.
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
Three tiers, four different costs. Every law and doctrine rule has a **permanent id** — kebab-case,
|
|
159
|
+
derived from its English principle when it is created (`verify-before-asserting`,
|
|
160
|
+
`consent-before-push`) — declared by its heading, `### {{LAW:<id>}} <Principle Name>` or
|
|
161
|
+
`### {{DOCTRINE:<id>}} <Principle Name>`, and cited by it: `{{LAW:<id>}}`, `{{DOCTRINE:<id>}}`.
|
|
162
|
+
**The number is not stored anywhere.** `ruleCanon` in `js/build/source.mjs` computes it from the
|
|
163
|
+
rule's position (Roman in `laws/universal.md`, Arabic within its pack) at render time; the heading
|
|
164
|
+
renders `Rule III — <themed title>` and a citation renders as the principle's name, in italics,
|
|
165
|
+
identical in every voice — the same string an agent is told to write into its memory. So adding,
|
|
166
|
+
removing or reordering a rule rewires nothing. `constitutionProblems` refuses a citation by number,
|
|
167
|
+
an unknown id, a duplicate id, a heading with no id, and a retired id declared again. The ontology is
|
|
168
|
+
cited by section name, and adding a section to it is not a checklist, it is a design change.
|
|
161
169
|
|
|
162
170
|
To count the cross-references either tier already carries, from the repo root:
|
|
163
171
|
|
|
164
172
|
```bash
|
|
165
|
-
grep -rhoE "\{\{LAW
|
|
166
|
-
grep -rhoE "\{\{DOCTRINE
|
|
173
|
+
grep -rhoE "\{\{LAW:[a-z0-9-]+\}\}" src/ | wc -l
|
|
174
|
+
grep -rhoE "\{\{DOCTRINE:[a-z0-9-]+\}\}" src/ | wc -l
|
|
167
175
|
```
|
|
168
176
|
|
|
169
|
-
|
|
170
|
-
|
|
177
|
+
Both numbers move on every wave of editing, which is why the commands are here and the answers are
|
|
178
|
+
not.
|
|
171
179
|
|
|
172
180
|
### 2a — Adding a DOCTRINE RULE (the common case)
|
|
173
181
|
|
|
174
182
|
Five files, plus the written-out rule totals in four test files. This is the slot most new
|
|
175
183
|
material belongs in, and it is cheap on purpose so that the invariant slot stays scarce.
|
|
176
184
|
|
|
177
|
-
1. `src/doctrines/<pack>.md` —
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
(`js/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
2. `themes/_TEMPLATE.json` — add `DOC_<PACK>_<n>`. ⚠ **This file *is* gated for this key family.**
|
|
185
|
+
1. `src/doctrines/<pack>.md` — add `### {{DOCTRINE:<new-id>}} <Principle Name>` plus the body,
|
|
186
|
+
wrapped in a `LEAN` block with **both halves authored** — the heading stays outside the block,
|
|
187
|
+
the full text goes in `LEAN:begin`, and the lean half is the rule *and its mechanism*, no maxims
|
|
188
|
+
(the style rule is in `src/doctrines/README.md`, and `leanBlockProblems` refuses a rule with no
|
|
189
|
+
block or an empty half). The id is kebab-case, unique across laws **and** doctrines, and never
|
|
190
|
+
one listed in `RETIRED_RULE_IDS` (`js/build/source.mjs`). Where it goes in the file decides its
|
|
191
|
+
number; appending is still the kind move, because `excludeRules`, `DOCTRINE_META` and the
|
|
192
|
+
hook-gated addresses are keyed `<pack>.<n>` (see §2e).
|
|
193
|
+
2. `themes/_TEMPLATE.json` — add `DOC_<ID>` (`consent-before-push` → `DOC_CONSENT_BEFORE_PUSH`).
|
|
194
|
+
⚠ **This file *is* gated for this key family.**
|
|
188
195
|
`themeFiles()` filters `_`-prefixed names and theme parity never sees the template, but
|
|
189
196
|
`constitutionProblems` reads it alongside the fourteen voices for `DOC_*` and `LEX_*`
|
|
190
197
|
specifically — because it is what `--sync-themes` seeds the *next* voice from. Every other key
|
|
@@ -199,7 +206,9 @@ material belongs in, and it is cheap on purpose so that the invariant slot stays
|
|
|
199
206
|
is one of the two. See §7.2 before you copy a command from anywhere else. Then restyle all
|
|
200
207
|
fourteen by hand: `--sync-themes` writes the template's placeholder and only *prints*
|
|
201
208
|
`RESTYLE these`; a shipped placeholder passes every gate.
|
|
202
|
-
4. `web/src/pages/Laws.jsx` — a `DOCTRINE_META` row keyed `'<pack>.<n>'
|
|
209
|
+
4. `web/src/pages/Laws.jsx` — a `DOCTRINE_META` row keyed `'<pack>.<n>'`, whose third field is the
|
|
210
|
+
rule's id — `doctrineMetaProblems` holds that pin to the canon, so a row left behind by a
|
|
211
|
+
renumber is a failure rather than a description of the neighbour. Its first field is the
|
|
203
212
|
**pack**, not one of `LAW_CLASSES`' six; `doctrineMetaProblems` requires an *equality* between
|
|
204
213
|
that field and the key's own pack, which catches the copy error a six-way vocabulary cannot — a
|
|
205
214
|
row pasted from the pack above it, keeping the wrong pack. Its second field is the one-line
|
|
@@ -213,19 +222,18 @@ material belongs in, and it is cheap on purpose so that the invariant slot stays
|
|
|
213
222
|
there is no second taxonomy over it.
|
|
214
223
|
- **No count in the product.** No badge, no `SHIPPED.md` triple, no prose mirror. The console spends
|
|
215
224
|
`{N_PACKS}` / `{N_PACKS_ACTIVE}` / `{N_DOCTRINE_RULES}`, all computed at request time.
|
|
216
|
-
- **No
|
|
225
|
+
- **No citation to rewire.** But there *are* written-out rule totals to bump — expected values are
|
|
217
226
|
written out, never recorded — in `tests/unit/emit_smoke.test.mjs`, `harness.test.mjs`,
|
|
218
227
|
`setup.test.mjs` and `web_api.test.mjs`. Each fails loudly with the new count; change the
|
|
219
228
|
number *and* the comment that explains it. A rule in **craft** moves more: `setup` and
|
|
220
229
|
`web_api` also pin the craft-only narrowed counts, `generate.test.mjs` excludes every craft
|
|
221
|
-
rule by address to empty the pack
|
|
222
|
-
the first free craft id — the doctrine twin of §2c step 8. Grep `tests/` for the previous
|
|
230
|
+
rule by address to empty the pack. Grep `tests/` for the previous
|
|
223
231
|
rule's address before trusting this list. A rule long enough to matter can also breach the
|
|
224
232
|
`full` carrier ceiling in `emit_smoke` — raise it with a dated note there, as every earlier
|
|
225
233
|
move did.
|
|
226
234
|
|
|
227
235
|
⚠ **Cite only downward.** An always-on tier may never reference a toggleable one:
|
|
228
|
-
`constitutionProblems` refuses a `{{DOCTRINE}}` token anywhere under `src/ontology/` or
|
|
236
|
+
`constitutionProblems` refuses a `{{DOCTRINE:…}}` token anywhere under `src/ontology/` or
|
|
229
237
|
`src/laws/`, because a `--doctrines craft` build would then ship an invariant pointing at text its
|
|
230
238
|
`AGENT.md` does not contain. Doctrine→doctrine citations are fine and stay — every pack file ships
|
|
231
239
|
on disk whether or not it was built in. If the build's own default is narrowed,
|
|
@@ -248,26 +256,25 @@ Steps 1 and 9 of §2c, plus `LAW_META` if the Principle line moved. Two cautions
|
|
|
248
256
|
Nine steps, and only the first is the rule itself. This is the expensive one, and it should be:
|
|
249
257
|
the bar is in `DESIGN.md` Decision 7, and most candidates belong in a doctrine pack instead.
|
|
250
258
|
|
|
251
|
-
1. `src/laws/universal.md` —
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
in every file and parity was silent — they were absent from nowhere.
|
|
259
|
+
1. `src/laws/universal.md` — add `### {{LAW:<new-id>}} <Principle Name>` plus the body. Citations
|
|
260
|
+
are by id and resolve against the canon, so position is safe to choose — but append unless the
|
|
261
|
+
order is the point: `LAW_CLASS` and `LAW_META` are keyed by the rendered number (see §2e).
|
|
262
|
+
2. `themes/_TEMPLATE.json` — add `LEX_<ID>` in template order. Gated, as in §2a step 2:
|
|
263
|
+
`constitutionProblems` holds the `LEX_*` keys as an **equality** with the declared law ids across
|
|
264
|
+
all fifteen files (the fourteen voices and the template), so both a missing key *and* a leftover
|
|
265
|
+
one are reported. That arm exists because an old renumber left `LEX_XXII`, `LEX_XXIII`,
|
|
266
|
+
`LEX_XXIV` and `LEX_XXXVI` behind in every file and parity was silent.
|
|
260
267
|
3. `--sync-themes`, then restyle all fourteen by hand (§2a step 3).
|
|
261
268
|
4. `LAW_CLASS` in `js/inspect/inventory.mjs` — one of the six in `LAW_CLASSES` beside it.
|
|
262
|
-
5. `LAW_META` in `web/src/pages/Laws.jsx` — same class,
|
|
263
|
-
is keyed by **arabic** number where
|
|
269
|
+
5. `LAW_META` in `web/src/pages/Laws.jsx` — same class, the one-line Principle, and the rule's id
|
|
270
|
+
as the pin. Note the map is keyed by **arabic** number where the heading renders a Roman numeral. **This copy
|
|
264
271
|
exists nowhere else**; a rule absent here renders with a blank description. It is why that one
|
|
265
272
|
React file ships alone out of `web/src` in `package.json` — `doctor` regex-parses its literal.
|
|
266
273
|
6. `README.md` — badge `badge/laws-N` and the `N universal laws` sentence.
|
|
267
274
|
7. `SHIPPED.md` — the `N laws, N agents, N skills` triple.
|
|
268
|
-
8. `tests/unit/harness.test.mjs` — the negative fixture for the `LAW_CLASS` gate
|
|
269
|
-
the
|
|
270
|
-
|
|
275
|
+
8. `tests/unit/harness.test.mjs` — the negative fixture for the `LAW_CLASS` gate appends a law and
|
|
276
|
+
expects the next numeral (`X` while there are nine), and `ROMANS` there and in `emit_smoke`
|
|
277
|
+
lists the numerals written out. Bump both.
|
|
271
278
|
9. `CHANGELOG.md`, then rebuild and commit `web/dist` (step 5 touched `web/src`).
|
|
272
279
|
|
|
273
280
|
⚠ While restyling, `{{LAW}}` must stay a **single word** in every theme (and so must
|
|
@@ -288,8 +295,8 @@ the only configuration it can speak for.
|
|
|
288
295
|
Rare, and mechanical. On top of §2a for each rule the pack carries:
|
|
289
296
|
|
|
290
297
|
1. `src/doctrines/<pack>.md`, opening with its lead line — `**{{PACK_<NAME>}}** — how … .` A pack
|
|
291
|
-
file `PACK_ORDER` names but cannot read, or one
|
|
292
|
-
|
|
298
|
+
file `PACK_ORDER` names but cannot read, or one declaring no `### {{DOCTRINE:<id>}}` rule at
|
|
299
|
+
all, is a refusal.
|
|
293
300
|
2. `PACK_ORDER` in `js/build/source.mjs` — the registration, and the render order. It is **narrative,
|
|
294
301
|
not alphabetical** (craft → rigor → ops → process → comms); discovery sorts alphabetically and the
|
|
295
302
|
renderer refuses a `.md` under `src/doctrines/` that `PACK_ORDER` does not name, rather than
|
|
@@ -315,6 +322,31 @@ Rare, and mechanical. On top of §2a for each rule the pack carries:
|
|
|
315
322
|
|
|
316
323
|
---
|
|
317
324
|
|
|
325
|
+
### 2e — Removing, retiring or reordering a rule
|
|
326
|
+
|
|
327
|
+
Delete the rule's heading and both LEAN halves, and every rule after it renumbers on the next
|
|
328
|
+
render — no citation moves, because none names a number. Then:
|
|
329
|
+
|
|
330
|
+
1. `RETIRED_RULE_IDS` in `js/build/source.mjs` — add the id with what it was and where its text
|
|
331
|
+
went. `constitutionProblems` refuses a retired id declared again, so an id that meant one rule in
|
|
332
|
+
someone's notes can never come back meaning another. `liveAs` names the one tier that may still
|
|
333
|
+
declare it (Law IX's `external-gate` lives on as a rigor rule).
|
|
334
|
+
2. Every citation of the removed id — the doctor names each one; point it at the rule that now
|
|
335
|
+
carries the text.
|
|
336
|
+
3. Its `LEX_<ID>`/`DOC_<ID>` key in all fifteen theme files (the equality arm names each).
|
|
337
|
+
4. The number-keyed tables, which the doctor checks against the canon: `LAW_CLASS`
|
|
338
|
+
(`js/inspect/inventory.mjs`), `LAW_META`/`DOCTRINE_META` (their id pins fail on every shifted
|
|
339
|
+
row), and — only if a hook-gated rule moved — `HOOK_PINNED` in `checks-authoring.mjs` names the
|
|
340
|
+
ledger keys, `CONSENT_RULE` and `HOOK_GATED` to move with it. `--exclude-rules` addresses are
|
|
341
|
+
`<pack>.<n>` too, and an install that excluded a shifted doctrine rule excludes its neighbour
|
|
342
|
+
after a re-emit: prefer appending in a pack, and say so in the CHANGELOG when you cannot.
|
|
343
|
+
5. The count mirrors: README badge and prose, `SHIPPED.md`, and the written-out totals in tests.
|
|
344
|
+
|
|
345
|
+
Laws IX (External Gate) and XI (Absence Is a Claim) were removed this way on 2026-09-30; Echo the
|
|
346
|
+
Intent became IX.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
318
350
|
## 3 — Adding a SKILL or an AGENT
|
|
319
351
|
|
|
320
352
|
**Order matters at the first two steps.** `missingReferencedSpecs` (in `js/build/bundle.mjs`) *aborts the
|
|
@@ -574,7 +606,7 @@ only, at best, on its presence.
|
|
|
574
606
|
|---|---|---|
|
|
575
607
|
| `§N` cross-references into `AGENT.md`'s anatomy | `src/skills/*`, `src/agents/*`, `adapters/**` | **range only.** `constitutionProblems` refuses a `§N` the template declares no section for — which catches a pointer past the end, the shape *removing* a section leaves. It cannot catch one that still resolves and now means something else, which is the shape *inserting* a section leaves, and is what actually happened when `## 2. Doctrines` pushed every later section down one. **Renumbering the anatomy means `grep -rn '§' src/ adapters/` and reading every hit.** Four satellites and a live OpenCode deny message shipped stale because the sweep stopped at `AGENT.md.tmpl` |
|
|
576
608
|
| Per-law Principle lines | `web/src/pages/Laws.jsx` | presence and class only, never accuracy |
|
|
577
|
-
| 588 themed constitution titles (42 keys × 14 voices) — `
|
|
609
|
+
| 588 themed constitution titles (42 keys × 14 voices) — `LEX_<ID>` (one per law), `DOC_<ID>` (one per doctrine rule), `PACK_<NAME>` | `themes/*.json` | key presence only — a shipped placeholder is green. `LEX_*` and `DOC_*` are held across the template too, and in both directions; `PACK_*` only across the voices |
|
|
578
610
|
| The README keyword enumerations | `README.md`, `docs/web/rules.md` | nothing |
|
|
579
611
|
| The plugin capability enumeration | `docs/web/model.md` | nothing (the number substitutes; the list does not) |
|
|
580
612
|
| Section labels and page subtitles | `web/src/lib/sections.js`, `web/src/pages/Docs/index.jsx` | nothing |
|
package/docs/token-footprint.md
CHANGED
|
@@ -15,33 +15,33 @@ heavier on the root file.
|
|
|
15
15
|
|
|
16
16
|
The context every host injects at session start, before you type anything:
|
|
17
17
|
|
|
18
|
-
| Component | Claude Code | OpenCode | Bob |
|
|
19
|
-
|
|
20
|
-
| Root instruction file | ~7.
|
|
21
|
-
| Skill metadata (name + description) | ~4.1k | ~4.1k | ~4.1k (native) |
|
|
22
|
-
| Agent metadata | ~0.5k | ~0.5k | — (no agents directory) |
|
|
23
|
-
|
|
|
24
|
-
| **Total** | **~
|
|
18
|
+
| Component | Claude Code | OpenCode | Bob |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| Root instruction file | ~7.2k (`CLAUDE.md`) | ~7.2k (`AGENT.md`) | ~7.5k (`AGENTS.md`, §3 table inline) |
|
|
21
|
+
| Skill metadata (name + description) | ~4.1k | ~4.1k | ~4.1k (native) |
|
|
22
|
+
| Agent metadata | ~0.5k | ~0.5k | — (no agents directory) |
|
|
23
|
+
| Session files (rules, profile, Memory/Notebook indexes, wiki) | 0 fresh, grows with use (SessionStart hook) | 0 fresh, grows with use (context plugin) | 0 fresh, grows with use (SessionStart hook) |
|
|
24
|
+
| **Total** | **~12k** | **~12k** | **~12k** |
|
|
25
25
|
|
|
26
|
-
The emits are at parity by design: ~
|
|
26
|
+
The emits are at parity by design: ~12k tokens, about 6% of a 200k
|
|
27
27
|
window. Bob catalogues skills natively but has no agents directory, so its
|
|
28
28
|
root keeps the §3 Agents table and drops the §4 Skills table — the catalogue
|
|
29
29
|
flag is per kind (`hostCatalogsNatively`), not one boolean. Before the 2026-09 footprint pass every hooked host paid roughly double
|
|
30
30
|
this: the context hook re-injected the root file the host had already loaded
|
|
31
31
|
natively, and skill descriptions ran to 900 characters each — see the
|
|
32
|
-
"Where the tokens go" list below for what changed. Bob
|
|
33
|
-
|
|
34
|
-
pointer to the host's own inventory instead, ~1.9k tokens lighter.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
"Where the tokens go" list below for what changed. Bob carries the §3/§4 catalogue tables inline because its host exposes
|
|
33
|
+
no native skill/agent inventory; Claude Code and OpenCode ship a
|
|
34
|
+
pointer to the host's own inventory instead, ~1.9k tokens lighter. A session file still
|
|
35
|
+
byte-identical to its seed is skipped — it says nothing — so a fresh install injects none
|
|
36
|
+
of them; until 2026-10 the hook read them from the repo root, where a Claude-shaped install
|
|
37
|
+
never puts them, and none reached the session at all. The
|
|
38
38
|
eager-injection path is budget-capped identically everywhere — 16 KB per file
|
|
39
39
|
(cut at a line break, with a marker saying so) and 48 KB per session (files past
|
|
40
40
|
the budget are listed lazy with the reason) — so a 40k-character README no
|
|
41
41
|
longer leaves whole every session, and growing Memory degrades every host the
|
|
42
42
|
same way instead of one silently falling behind. The hook also **never injects
|
|
43
43
|
the root file the host loads by itself** (`CLAUDE.md` on Claude Code,
|
|
44
|
-
`AGENTS.md` on Bob
|
|
44
|
+
`AGENTS.md` on Bob, `AGENT.md`/`AGENTS.md`/`CLAUDE.md` on OpenCode):
|
|
45
45
|
until 2026-09 it did, and the whole harness was paid twice per session.
|
|
46
46
|
|
|
47
47
|
## Where the tokens go
|
|
@@ -72,10 +72,17 @@ until 2026-09 it did, and the whole harness was paid twice per session.
|
|
|
72
72
|
capped at 320 characters (`skillDescription` in `js/hosts/native.mjs`); at
|
|
73
73
|
the earlier 900-character cap the catalogue weighed ~5.6k tokens. The later
|
|
74
74
|
trigger sentences still live in the body, read as soon as the skill is chosen.
|
|
75
|
-
- **§5–§
|
|
76
|
-
Context
|
|
75
|
+
- **§5–§9 carry authored lean halves too** (Memory, Notebook, Workspace, Wiki,
|
|
76
|
+
Context): each keeps its obligations — read `MEMORY.md`, read
|
|
77
77
|
`context.json` before the first reply, the git-ignored folders — and points at
|
|
78
|
-
the on-disk README or config file for the rest.
|
|
78
|
+
the on-disk README or config file for the rest. §10 (Scripts) is full-only: it
|
|
79
|
+
describes the CLI to a reader, not an obligation to the agent.
|
|
80
|
+
- **Restated prose is full-only** (2026-10): the laws and Ethos intros, the §2
|
|
81
|
+
pack intro, the posture/mode intros, §3's dispatch envelope and council roster
|
|
82
|
+
(they live in the parallel-agents and council Skills; the lean root keeps
|
|
83
|
+
"dispatched agents never commit or push"), and the folder-Skill list, which
|
|
84
|
+
now sits in the §4 catalogue half and so drops wherever the host catalogues
|
|
85
|
+
Skills natively.
|
|
79
86
|
- **Skill bodies (~53k) and agent bodies (~10k) are lazy** on every host —
|
|
80
87
|
loaded only when invoked. A typical skill costs ≤3k per invocation; the
|
|
81
88
|
heaviest (`react-view-transitions`, ~17.5k with its reference files) loads
|
|
@@ -97,9 +104,7 @@ token counter also includes, none of which Geneseed controls:
|
|
|
97
104
|
definitions. On OpenCode this is typically 5–10k tokens before any
|
|
98
105
|
harness content loads.
|
|
99
106
|
2. **Your repo's docs, injected eagerly** — the context delivery
|
|
100
|
-
(plugin on OpenCode, SessionStart hook on Claude Code / Bob
|
|
101
|
-
hook on a global Copilot install; a project-scope Copilot install has no
|
|
102
|
-
hooks, so it reads docs on demand instead) discovers and
|
|
107
|
+
(plugin on OpenCode, SessionStart hook on Claude Code / Bob / OpenClaude) discovers and
|
|
103
108
|
injects `README.md`, `CONTRIBUTING.md`, and files under `docs/`, up to the
|
|
104
109
|
48 KB budget (≈12k tokens). A doc-heavy repo fills it.
|
|
105
110
|
3. **Wiki eager entries** plus the lazy listing of the rest, if a wiki is
|
package/docs/web/claude-hooks.md
CHANGED
|
@@ -11,7 +11,7 @@ Claude Code has no `instructions` array and no JS plugin dir — the harness rea
|
|
|
11
11
|
- **Context injection** — `SessionStart` (startup, clear, resume) runs `geneseed-hook context`: auto-discovers the repo's docs by convention and injects them before your first turn, plus your machine wiki. It honours the same `GENESEED_CONTEXT` manifest as the OpenCode context plugin.
|
|
12
12
|
- **Git gate** — `PreToolUse` on Bash runs `geneseed-hook git-gate`: a `git commit` or `git push` anywhere in the command line — a compound one-liner and a `-C <path>` form included — asks first, at the tool boundary and before the shell runs it (**Doctrine process 5**). It is the one hook that rides a doctrine pack: an install built without **process** never gets this group, because a boundary that keeps asking for a rule the install did not adopt is a gate arguing with its own harness.
|
|
13
13
|
- **Rule gate** — `PreToolUse` on the write tools runs `geneseed-hook rule-gate`: a write to `user-rules.md` or to a memory file asks first, because whether something you want kept is a standing rule or a durable fact is *your* call, settled through the [rule skill](#/docs/skills) (**Doctrine process 1**). Ordinary edits are untouched.
|
|
14
|
-
- **Tool gate** — `geneseed-hook tool-gate` is the git gate and the rule gate fused behind one command, for hosts whose hook config has no per-tool matcher (
|
|
14
|
+
- **Tool gate** — `geneseed-hook tool-gate` is the git gate and the rule gate fused behind one command, for hosts whose hook config has no per-tool matcher (Bob). Claude Code matches per tool, so its `settings.json` wires the two gates above instead.
|
|
15
15
|
- **Learn** — `Stop` and `SubagentStop` run `geneseed-hook learn`: distils durable memories into the install's `memory/` store at session end; a subagent's stop routes to the per-agent lesson path (`memory/agents/<name>.md`).
|
|
16
16
|
|
|
17
17
|
### Where they live
|
|
@@ -4,7 +4,7 @@ order: 11
|
|
|
4
4
|
title: "The collaboration layer"
|
|
5
5
|
kind: "concept"
|
|
6
6
|
---
|
|
7
|
-
Beside the [constitution](#/docs/rules), a handful of mechanisms shape *how* the agent works with you — the two registers, the mutual contract, how memory binds, and who you are. All of them are plain content in `AGENT.md` and its neighbours, so they ride to every host (OpenCode, Claude, Bob,
|
|
7
|
+
Beside the [constitution](#/docs/rules), a handful of mechanisms shape *how* the agent works with you — the two registers, the mutual contract, how memory binds, and who you are. All of them are plain content in `AGENT.md` and its neighbours, so they ride to every host (OpenCode, Claude, Bob, OpenClaude) unchanged.
|
|
8
8
|
|
|
9
9
|
### Postures — the register
|
|
10
10
|
|
package/docs/web/exclusions.md
CHANGED
|
@@ -17,7 +17,6 @@ Exclusion is all-or-nothing per folder — it doesn't trim which Rules apply or
|
|
|
17
17
|
- **OpenClaude** — the same `claudeMdExcludes`, in the repo's `.openclaude/settings.local.json`.
|
|
18
18
|
- **Bob** — a shadow `rules/geneseed.md` stub that shadows the global one (never overwrites a hand-written stub already there).
|
|
19
19
|
- **OpenCode** — the hook entry's sovereign-bypass guard and its twin in the plugins short-circuit at session start.
|
|
20
|
-
- **Copilot — documented limitation.** GitHub Copilot has no native per-repo suppression hook: the global `copilot-instructions.md` still loads even inside an excluded folder.
|
|
21
20
|
|
|
22
21
|
### Manage it
|
|
23
22
|
|
package/docs/web/footprint.md
CHANGED
|
@@ -26,7 +26,7 @@ Neither setting hides text from the agent; they hide it from *every turn*.
|
|
|
26
26
|
|
|
27
27
|
### How to set it
|
|
28
28
|
|
|
29
|
-
It's set-and-forget — stored in the `.geneseed-footprint` marker, preserved across every rebuild, identical on every host (OpenCode, Claude Code, Bob,
|
|
29
|
+
It's set-and-forget — stored in the `.geneseed-footprint` marker, preserved across every rebuild, identical on every host (OpenCode, Claude Code, Bob, OpenClaude). Changing it re-emits the install.
|
|
30
30
|
|
|
31
31
|
- **Settings** — the **Footprint** toggle flips the current install (full ⇄ lean) and rebuilds it in place.
|
|
32
32
|
- **Harness tab** — on an active install, a per-harness dropdown sets it for that install independently, then **Apply**. On a host that is not installed yet, it is step 2 of **Install…**.
|