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.
Files changed (229) hide show
  1. package/CHANGELOG.md +71 -1
  2. package/DESIGN.md +13 -10
  3. package/QUICKSTART.md +1 -1
  4. package/README.md +31 -32
  5. package/SETUP.md +7 -42
  6. package/SHIPPED.md +4 -4
  7. package/adapters/bob/README.md +17 -1
  8. package/adapters/openclaude/README.md +1 -1
  9. package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +1 -1
  10. package/adapters/opencode/README.md +3 -3
  11. package/adapters/opencode/plugins/geneseed-context.js +78 -8
  12. package/adapters/opencode/plugins/geneseed-guard.js +9 -9
  13. package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
  14. package/adapters/opencode/workflows/_runtime.js +2 -2
  15. package/adapters/opencode/workflows/dispatch.js +1 -1
  16. package/bin/geneseed-hook.mjs +1 -1
  17. package/docs/candidate-hosts.md +12 -5
  18. package/docs/extending.md +72 -40
  19. package/docs/token-footprint.md +26 -21
  20. package/docs/web/claude-hooks.md +1 -1
  21. package/docs/web/collaboration.md +1 -1
  22. package/docs/web/exclusions.md +0 -1
  23. package/docs/web/footprint.md +1 -1
  24. package/docs/web/harnesses.md +14 -14
  25. package/docs/web/install-quick.md +1 -1
  26. package/docs/web/plugin-guard.md +2 -2
  27. package/docs/web/rules.md +1 -1
  28. package/docs/web/themes-author.md +2 -2
  29. package/docs/web-ui.md +2 -2
  30. package/harness.config.json +1 -1
  31. package/js/README.md +8 -7
  32. package/js/build/driver.mjs +26 -95
  33. package/js/build/emit-claude.mjs +76 -81
  34. package/js/build/generate.mjs +0 -1
  35. package/js/build/render.mjs +10 -3
  36. package/js/build/source.mjs +105 -1
  37. package/js/build/stubs.mjs +25 -4
  38. package/js/cli-table.json +8 -8
  39. package/js/hosts/hooks.mjs +91 -59
  40. package/js/hosts/hosts.mjs +5 -21
  41. package/js/hosts/installs.mjs +4 -6
  42. package/js/hosts/mcp.mjs +3 -13
  43. package/js/hosts/native.mjs +28 -51
  44. package/js/hosts/opencode.mjs +1 -1
  45. package/js/hosts/settings.mjs +4 -78
  46. package/js/inspect/checks-authoring.mjs +153 -117
  47. package/js/inspect/checks-repo.mjs +45 -1
  48. package/js/inspect/diff.mjs +11 -3
  49. package/js/inspect/doctor.mjs +3 -3
  50. package/js/inspect/excludes.mjs +0 -4
  51. package/js/inspect/inventory.mjs +1 -2
  52. package/js/inspect/status.mjs +4 -4
  53. package/js/maintain/migrate.mjs +5 -5
  54. package/js/maintain/setup.mjs +3 -5
  55. package/js/maintain/uninstall.mjs +8 -42
  56. package/js/ui/tui.mjs +1 -1
  57. package/js/web/actions.mjs +4 -5
  58. package/js/web/api.mjs +10 -5
  59. package/package.json +1 -2
  60. package/registry.json +7 -0
  61. package/src/AGENT.md.tmpl +86 -65
  62. package/src/agents/_template.md +3 -3
  63. package/src/agents/advocate.md +3 -3
  64. package/src/agents/architect.md +3 -3
  65. package/src/agents/developer.md +4 -4
  66. package/src/agents/docs.md +3 -3
  67. package/src/agents/empiricist.md +4 -4
  68. package/src/agents/explorer.md +4 -4
  69. package/src/agents/framer.md +3 -3
  70. package/src/agents/historian.md +3 -3
  71. package/src/agents/operator.md +3 -3
  72. package/src/agents/pragmatist.md +3 -3
  73. package/src/agents/researcher.md +7 -7
  74. package/src/agents/reviewer.md +3 -3
  75. package/src/agents/security.md +3 -3
  76. package/src/agents/skeptic.md +3 -3
  77. package/src/agents/steward.md +3 -3
  78. package/src/agents/tester.md +3 -3
  79. package/src/agents/user-advocate.md +3 -3
  80. package/src/agents/visionary.md +3 -3
  81. package/src/doctrines/README.md +4 -3
  82. package/src/doctrines/comms.md +4 -4
  83. package/src/doctrines/craft.md +20 -20
  84. package/src/doctrines/ops.md +19 -19
  85. package/src/doctrines/process.md +20 -20
  86. package/src/doctrines/rigor.md +22 -22
  87. package/src/laws/universal.md +26 -44
  88. package/src/memory/README.md +3 -3
  89. package/src/modes/foreman.md +1 -1
  90. package/src/notebook/README.md +1 -1
  91. package/src/ontology/universal.md +8 -10
  92. package/src/postures/artisan.md +2 -2
  93. package/src/skills/_self-improvement.md +4 -4
  94. package/src/skills/_template.md +1 -1
  95. package/src/skills/brainstorm.md +3 -3
  96. package/src/skills/bruno-collection-generator.md +6 -6
  97. package/src/skills/bruno-test-writer.md +5 -5
  98. package/src/skills/ci-fix.md +4 -4
  99. package/src/skills/commit.md +11 -10
  100. package/src/skills/consolidate-memory.md +4 -4
  101. package/src/skills/council.md +2 -2
  102. package/src/skills/daydream/SKILL.md +10 -3
  103. package/src/skills/daydream/VENDOR.md +10 -2
  104. package/src/skills/daydream/instructions.md +2 -2
  105. package/src/skills/debug.md +4 -4
  106. package/src/skills/deps-audit.md +3 -3
  107. package/src/skills/develop.md +7 -7
  108. package/src/skills/document-project.md +6 -6
  109. package/src/skills/explain-changes/SKILL.md +69 -0
  110. package/src/skills/explain-changes/VENDOR.md +20 -0
  111. package/src/skills/explain-changes/scripts/render_changes.mjs +558 -0
  112. package/src/skills/forge-mcp.md +4 -4
  113. package/src/skills/fresh-eyes.md +4 -4
  114. package/src/skills/frontend-design.md +1 -1
  115. package/src/skills/geneseed.md +4 -4
  116. package/src/skills/git-archaeology.md +5 -5
  117. package/src/skills/git-rescue.md +7 -7
  118. package/src/skills/handoff.md +3 -3
  119. package/src/skills/ingest.md +5 -5
  120. package/src/skills/migrate.md +8 -8
  121. package/src/skills/parallel-agents.md +2 -2
  122. package/src/skills/pipeline.md +2 -2
  123. package/src/skills/plan.md +3 -3
  124. package/src/skills/ponytail.md +2 -2
  125. package/src/skills/profile.md +2 -2
  126. package/src/skills/prose.md +1 -1
  127. package/src/skills/prototype.md +1 -1
  128. package/src/skills/react-view-transitions/SKILL.md +1 -1
  129. package/src/skills/react-view-transitions/VENDOR.md +7 -2
  130. package/src/skills/refactor.md +3 -3
  131. package/src/skills/release.md +5 -5
  132. package/src/skills/repo-map.md +4 -4
  133. package/src/skills/research.md +9 -9
  134. package/src/skills/review-response.md +4 -4
  135. package/src/skills/rule.md +3 -3
  136. package/src/skills/security-audit.md +5 -5
  137. package/src/skills/ship.md +5 -5
  138. package/src/skills/skill-forge.md +4 -4
  139. package/src/skills/token-report/SKILL.md +2 -3
  140. package/src/skills/token-report/VENDOR.md +2 -3
  141. package/src/skills/token-report/scripts/token_report.mjs +7 -121
  142. package/src/skills/wiki.md +5 -5
  143. package/src/skills/workflow.md +3 -4
  144. package/src/skills/worktree.md +4 -4
  145. package/themes/_TEMPLATE.json +37 -39
  146. package/themes/biker.json +37 -39
  147. package/themes/commentator.json +37 -39
  148. package/themes/cyberpunk.json +37 -39
  149. package/themes/gamer.json +37 -39
  150. package/themes/imperial.json +37 -39
  151. package/themes/joker.json +37 -39
  152. package/themes/marvin.json +37 -39
  153. package/themes/mean.json +37 -39
  154. package/themes/military.json +37 -39
  155. package/themes/neutral.json +37 -39
  156. package/themes/pirate.json +37 -39
  157. package/themes/sports.json +37 -39
  158. package/themes/verstappen.json +37 -39
  159. package/themes/wizard.json +37 -39
  160. package/web/dist/assets/Activity-BjVp522q.js +1 -0
  161. package/web/dist/assets/ActivityDetail-8ep1bspW.js +1 -0
  162. package/web/dist/assets/Dashboard-OkC_PCH6.js +2 -0
  163. package/web/dist/assets/{Docs-DrJNDPwI.js → Docs-CKPWz00o.js} +2 -2
  164. package/web/dist/assets/FilterInput-CVyqmYWM.js +1 -0
  165. package/web/dist/assets/GreenhouseView-D0cbCJ5m.js +2 -0
  166. package/web/dist/assets/Installs-BtGGdoTt.js +2 -0
  167. package/web/dist/assets/JournalView-Ckl4dmzR.js +1 -0
  168. package/web/dist/assets/Laws-CLpdRRk8.js +3 -0
  169. package/web/dist/assets/Library-wPPuKQrI.js +1 -0
  170. package/web/dist/assets/{Markdown-BQAvN8xx.js → Markdown-CkFyfgNf.js} +14 -14
  171. package/web/dist/assets/OperatorHudView-CiAkAkQL.js +2 -0
  172. package/web/dist/assets/Personal-D8CnDo9v.js +1 -0
  173. package/web/dist/assets/Tabs-B4HNrrCs.js +1 -0
  174. package/web/dist/assets/drawable-BiwaOo6x.js +1 -0
  175. package/web/dist/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
  176. package/web/dist/assets/geist-mono-latin-500-normal-YINYabwD.woff2 +0 -0
  177. package/web/dist/assets/index-C1yil5j1.js +2 -0
  178. package/web/dist/assets/index-CDPDk2vj.css +1 -0
  179. package/web/dist/assets/{react-DbkvxWG5.js → react-B9Z_tXRV.js} +1 -1
  180. package/web/dist/assets/sections-GalMS0Zv.js +1 -0
  181. package/web/dist/assets/usePoll-O0sJEw1Y.js +1 -0
  182. package/web/dist/index.html +4 -4
  183. package/web/src/pages/Laws.jsx +616 -361
  184. package/adapters/copilot/README.md +0 -105
  185. package/web/dist/assets/Activity-CH-7cPIM.js +0 -1
  186. package/web/dist/assets/ActivityDetail-DZvz0xS5.js +0 -1
  187. package/web/dist/assets/CatalogRow-DftXpZHY.js +0 -1
  188. package/web/dist/assets/Diff-DvwdG0L9.js +0 -2
  189. package/web/dist/assets/Doctor-OTmwmYZg.js +0 -1
  190. package/web/dist/assets/FilterInput-BakJLvaV.js +0 -1
  191. package/web/dist/assets/GreenhouseView-XxrAdriX.js +0 -2
  192. package/web/dist/assets/Harness-C_LYNk5c.js +0 -1
  193. package/web/dist/assets/JournalView-BcuyeEaR.js +0 -1
  194. package/web/dist/assets/Laws-CWHrTKY4.js +0 -3
  195. package/web/dist/assets/Library-CA9IHghn.js +0 -1
  196. package/web/dist/assets/OperatorHudView-BMRY1Tt4.js +0 -2
  197. package/web/dist/assets/Profile-wceET1JY.js +0 -1
  198. package/web/dist/assets/Rules-BEXvY4Tx.js +0 -1
  199. package/web/dist/assets/Settings-DS2zF0Ez.js +0 -1
  200. package/web/dist/assets/Skills-DahlOJuI.js +0 -1
  201. package/web/dist/assets/StatusBadge-Cg6j-NJL.js +0 -1
  202. package/web/dist/assets/activity-iWaWVtsR.js +0 -1
  203. package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
  204. package/web/dist/assets/drawable-BTHjTzF7.js +0 -1
  205. package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
  206. package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
  207. package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
  208. package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  209. package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  210. package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  211. package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
  212. package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
  213. package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
  214. package/web/dist/assets/index-AN5Emnrg.js +0 -2
  215. package/web/dist/assets/index-Ruh93A3A.css +0 -1
  216. package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  217. package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  218. package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  219. package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  220. package/web/dist/assets/lawCats-Btjp3Hk_.js +0 -1
  221. package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
  222. package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
  223. package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
  224. package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
  225. package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  226. package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  227. package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  228. package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  229. 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
- for (const rel of [path.join(".harness", "context.json"), "context.json"]) {
481
- const p = path.join(root, rel)
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
- if (process.env.GENESEED_HARNESS) bases.push(process.env.GENESEED_HARNESS)
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 src = await resolveSource(root)
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
- // - Law I (Sealed Secrets): block writes to private-key / credential files.
6
- // - Law IV (Deletion Is Deliberate): block catastrophic shell commands.
7
- // - Doctrine process 1 (rule vs memory): speed-bump the first write to user-rules.md
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 (Doctrine process 1) -----------------------------
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} (Law I)`, "law-1"); return }
289
- if (p && SECRET_WARN_RE.some((re) => re.test(p))) log(`WARN: writing ${p} — keep secrets out of tracked files (Law I)`)
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 (Doctrine process 1). Settle it through the rule skill, ` +
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 (Law IV): ${c.slice(0, 80)}`, "law-4"); return }
313
- if (c && SHELL_WARN_RE.some((re) => re.test(c))) log(`WARN: irreversible op — confirm intent (Law IV): ${c.slice(0, 80)}`)
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 (process 7) — both are said out loud, never left for the trace to hold.
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 (process 7, one writer per
186
- // file), so each isolated agent gets its own branch + worktree off the root's HEAD, and
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 — Doctrine process 5 stays with
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[] }
@@ -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 Copilot).
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:
@@ -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`). Roughly the Copilot
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 Copilot, one more output spelling in `js/hosts/hooks.mjs`, one more carrier writer.
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 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.
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. The addresses are `{{LAW}} <roman>` for an invariant and
159
- `{{DOCTRINE}} <pack> <n>` for a doctrine rule; the ontology is cited by section name and adding a
160
- section to it is not a checklist, it is a design change.
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\}\} [IVXL]+" src/ | wc -l
166
- grep -rhoE "\{\{DOCTRINE\}\} [a-z]+ [0-9]+" src/ | wc -l
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
- On this checkout that reports 153 and 209. Both numbers move on every wave of editing, which is
170
- why the commands are here and the answers are not load-bearing.
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` — **append** `### {{DOCTRINE}} <pack> <n> — {{DOC_<PACK>_<n>}}` plus
178
- the body, wrapped in a `LEAN` block with **both halves authored** — the heading stays outside the
179
- block, the full text goes in `LEAN:begin`, and the lean half is the rule *and its mechanism*, no
180
- maxims (the style rule is in `src/doctrines/README.md`, and `leanBlockProblems` refuses a rule
181
- with no block or an empty half). Two gates in `constitutionProblems`
182
- (`js/inspect/checks-authoring.mjs`) sit on this too: the pack named in
183
- the *heading* is what addresses the rule (not the filename, so a rule filed in the wrong file is
184
- reported rather than silently renamed), and ids must run **contiguously from 1**, so appending
185
- at the end is the only place `n` is free. Packs are numbered independently — nothing outside
186
- this file renumbers.
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>'`. Its first field is the
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 renumber risk.** But there *are* written-out rule totals to bump — expected values are
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, and `harness.test.mjs`'s contiguity fixture plants a rule at
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` — **append** `### {{LAW}} <roman> — {{LEX_<roman>}}` plus the body.
252
- Never insert: the `{{LAW}}` cross-references counted above live all over `src/`, plus hardcoded
253
- citations in `js/hosts/hooks.mjs` and two OpenCode plugins, and **nothing resolves a cross-reference
254
- against the canon**. Renumbering silently rewires every one of them.
255
- 2. `themes/_TEMPLATE.json` — add `LEX_<roman>` in template order. Gated, as in §2a step 2:
256
- `constitutionProblems` holds `LEX_I..LEX_XI` as an **equality** across all fifteen files (the
257
- fourteen voices and the template), so both a missing key *and* a leftover one are reported. That
258
- arm exists because the renumber left `LEX_XXII`, `LEX_XXIII`, `LEX_XXIV` and `LEX_XXXVI` behind
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, plus the one-line Principle. Note the map
263
- is keyed by **arabic** number where `universal.md` heads with a Roman numeral. **This copy
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 plants a rule at
269
- the *first free numeral*, which is `XII` now that the invariants are I..XI. Bump it, or it stops
270
- being a fixture and starts being a duplicate.
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 carrying no `### {{DOCTRINE}} <pack> <n>`
292
- heading at all, is a refusal.
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) — `LEX_I..LEX_XI` (11), `DOC_<PACK>_<n>` (27), `PACK_<NAME>` (4) | `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 |
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 |
@@ -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 | Copilot |
19
- |---|---|---|---|---|
20
- | Root instruction file | ~7.3k (`CLAUDE.md`) | ~7.3k (`AGENT.md`) | ~7.7k (`AGENTS.md`, §3 table inline) | ~9.0k (`AGENTS.md`, §3+§4 tables inline) |
21
- | Skill metadata (name + description) | ~4.1k | ~4.1k | ~4.1k (native) | — (§4 table; native catalogue unverified) |
22
- | Agent metadata | ~0.5k | ~0.5k | — (no agents directory) | — (§3 table) |
23
- | Eager memory/notebook injection | ~1.2k (SessionStart hook) | ~1.2k (context plugin) | ~1.2k (SessionStart hook) | ~1.2k (sessionStart hook) |
24
- | **Total** | **~13k** | **~13k** | **~13k** | **~10k** |
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: ~10–13k tokens, about 6% of a 200k
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 and Copilot carry the §3/§4 catalogue tables inline because their
33
- hosts expose no native skill/agent inventory; Claude Code and OpenCode ship a
34
- pointer to the host's own inventory instead, ~1.9k tokens lighter. Copilot's
35
- eager injection rides its `sessionStart` hook, which the **global** emit wires;
36
- a project-scope Copilot install has no hooks, so there the memory/notebook
37
- indexes load when the agent reads them, not eagerly. The
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 and Copilot, `AGENT.md`/`AGENTS.md`/`CLAUDE.md` on OpenCode):
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–§10 carry authored lean halves too** (Memory, Notebook, Workspace, Wiki,
76
- Context, Scripts): each keeps its obligations — read `MEMORY.md`, read
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, `sessionStart`
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
@@ -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 (Copilot, Bob). Claude Code matches per tool, so its `settings.json` wires the two gates above instead.
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, Copilot) unchanged.
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
 
@@ -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
 
@@ -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, Copilot, OpenClaude). Changing it re-emits the install.
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…**.