geneseed 2.0.0 → 3.0.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 (196) hide show
  1. package/CHANGELOG.md +217 -5
  2. package/DESIGN.md +149 -1326
  3. package/QUICKSTART.md +1 -1
  4. package/README.md +30 -22
  5. package/SETUP.md +98 -24
  6. package/SHIPPED.md +6 -5
  7. package/adapters/bob/README.md +3 -3
  8. package/adapters/claude-code/README.md +6 -4
  9. package/adapters/claude-code/skills/geneseed/SKILL.md +10 -6
  10. package/adapters/copilot/README.md +5 -4
  11. package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +11 -6
  12. package/adapters/opencode/HOW-OPENCODE-LOADS.md +1 -1
  13. package/adapters/opencode/README.md +15 -9
  14. package/adapters/opencode/agents/orchestrator.md +1 -1
  15. package/adapters/opencode/opencode.json +0 -9
  16. package/adapters/opencode/plugins/geneseed-context.js +7 -7
  17. package/adapters/opencode/plugins/geneseed-guard.js +6 -6
  18. package/adapters/opencode/workflows/dispatch.js +1 -1
  19. package/bin/geneseed.mjs +74 -8
  20. package/docs/declined.md +53 -2
  21. package/docs/design-history.md +1290 -0
  22. package/docs/extending.md +672 -0
  23. package/docs/limits.md +141 -82
  24. package/docs/token-footprint.md +15 -4
  25. package/docs/web/agents.md +1 -1
  26. package/docs/web/authoring.md +1 -1
  27. package/docs/web/claude-hooks.md +2 -2
  28. package/docs/web/collaboration.md +3 -3
  29. package/docs/web/first-session.md +2 -2
  30. package/docs/web/footprint.md +14 -7
  31. package/docs/web/foreman-mode.md +2 -2
  32. package/docs/web/harnesses.md +5 -3
  33. package/docs/web/install-quick.md +1 -1
  34. package/docs/web/model.md +3 -3
  35. package/docs/web/plugin-context.md +2 -2
  36. package/docs/web/plugin-guard.md +3 -3
  37. package/docs/web/plugin-ponytail.md +1 -1
  38. package/docs/web/rules.md +23 -3
  39. package/docs/web/setup-choices.md +20 -7
  40. package/docs/web/themes-author.md +13 -1
  41. package/docs/web-ui.md +36 -12
  42. package/geneseed +0 -0
  43. package/harness.config.json +1 -1
  44. package/js/catalog.mjs +29 -4
  45. package/js/checkout.mjs +24 -1
  46. package/js/cli-table.json +9 -7
  47. package/js/diff.mjs +8 -3
  48. package/js/doctor.mjs +374 -26
  49. package/js/emit.mjs +48 -11
  50. package/js/generate.mjs +39 -5
  51. package/js/hooks.mjs +8 -7
  52. package/js/installs.mjs +101 -4
  53. package/js/inventory.mjs +150 -20
  54. package/js/lib/fs.mjs +6 -2
  55. package/js/mcp.mjs +3 -15
  56. package/js/migrate.mjs +10 -4
  57. package/js/render.mjs +116 -11
  58. package/js/settings.mjs +297 -28
  59. package/js/setup.mjs +95 -5
  60. package/js/status.mjs +45 -4
  61. package/js/themes.mjs +1 -1
  62. package/js/tui.mjs +46 -3
  63. package/js/uninstall.mjs +30 -11
  64. package/js/web/actions.mjs +76 -9
  65. package/js/web/api.mjs +95 -16
  66. package/js/web/docs.mjs +33 -5
  67. package/js/web/jobs.mjs +12 -3
  68. package/js/web/server.mjs +59 -2
  69. package/package.json +1 -1
  70. package/src/AGENT.md.tmpl +75 -65
  71. package/src/agents/_template.md +4 -4
  72. package/src/agents/advocate.md +4 -4
  73. package/src/agents/architect.md +2 -2
  74. package/src/agents/developer.md +5 -5
  75. package/src/agents/docs.md +2 -2
  76. package/src/agents/empiricist.md +3 -3
  77. package/src/agents/explorer.md +3 -3
  78. package/src/agents/framer.md +3 -3
  79. package/src/agents/historian.md +3 -3
  80. package/src/agents/operator.md +3 -3
  81. package/src/agents/pragmatist.md +3 -3
  82. package/src/agents/reviewer.md +2 -2
  83. package/src/agents/security.md +2 -2
  84. package/src/agents/skeptic.md +4 -4
  85. package/src/agents/steward.md +3 -3
  86. package/src/agents/tester.md +2 -2
  87. package/src/agents/user-advocate.md +3 -3
  88. package/src/agents/visionary.md +3 -3
  89. package/src/doctrines/README.md +41 -0
  90. package/src/doctrines/craft.md +51 -0
  91. package/src/doctrines/ops.md +61 -0
  92. package/src/doctrines/process.md +87 -0
  93. package/src/doctrines/rigor.md +52 -0
  94. package/src/laws/universal.md +17 -301
  95. package/src/memory/README.md +2 -2
  96. package/src/modes/README.md +2 -2
  97. package/src/modes/foreman.md +1 -1
  98. package/src/notebook/README.md +2 -2
  99. package/src/ontology/universal.md +89 -0
  100. package/src/postures/README.md +3 -3
  101. package/src/postures/artisan.md +3 -3
  102. package/src/postures/assistant.md +1 -1
  103. package/src/postures/expert.md +1 -1
  104. package/src/postures/mentor.md +1 -1
  105. package/src/postures/peer.md +2 -2
  106. package/src/skills/_template.md +2 -2
  107. package/src/skills/brainstorm.md +2 -2
  108. package/src/skills/clarify.md +2 -2
  109. package/src/skills/codebase-design.md +1 -1
  110. package/src/skills/commit.md +5 -5
  111. package/src/skills/council.md +4 -4
  112. package/src/skills/crash-course.md +1 -1
  113. package/src/skills/daydream/SKILL.md +1 -1
  114. package/src/skills/debug.md +2 -2
  115. package/src/skills/decode.md +1 -1
  116. package/src/skills/develop.md +6 -6
  117. package/src/skills/document-project.md +4 -4
  118. package/src/skills/domain-modeling.md +1 -1
  119. package/src/skills/drill.md +1 -1
  120. package/src/skills/feynman.md +1 -1
  121. package/src/skills/forge-mcp.md +3 -3
  122. package/src/skills/fresh-eyes.md +4 -4
  123. package/src/skills/frontend-design.md +1 -1
  124. package/src/skills/gap-detector.md +1 -1
  125. package/src/skills/geneseed-code-review.md +1 -1
  126. package/src/skills/geneseed.md +4 -4
  127. package/src/skills/git-archaeology.md +1 -1
  128. package/src/skills/git-rescue.md +3 -3
  129. package/src/skills/handoff.md +4 -4
  130. package/src/skills/herdr.md +1 -1
  131. package/src/skills/ingest.md +3 -3
  132. package/src/skills/learning-path.md +1 -1
  133. package/src/skills/migrate.md +5 -5
  134. package/src/skills/opencode-theme.md +1 -1
  135. package/src/skills/parallel-agents.md +2 -2
  136. package/src/skills/pipeline.md +2 -2
  137. package/src/skills/plan.md +2 -2
  138. package/src/skills/ponytail.md +1 -1
  139. package/src/skills/profile.md +1 -1
  140. package/src/skills/prose.md +2 -2
  141. package/src/skills/prototype.md +2 -2
  142. package/src/skills/refactor.md +2 -2
  143. package/src/skills/release.md +4 -4
  144. package/src/skills/repo-map.md +5 -5
  145. package/src/skills/research.md +6 -6
  146. package/src/skills/review-response.md +3 -3
  147. package/src/skills/roast-me.md +1 -1
  148. package/src/skills/rule.md +5 -5
  149. package/src/skills/ship.md +3 -3
  150. package/src/skills/tdd.md +2 -2
  151. package/src/skills/tickets.md +1 -1
  152. package/src/skills/wayfinder.md +1 -1
  153. package/src/skills/wiki.md +4 -4
  154. package/src/skills/workflow.md +2 -2
  155. package/themes/README.md +17 -7
  156. package/themes/_TEMPLATE.json +37 -33
  157. package/themes/biker.json +37 -33
  158. package/themes/commentator.json +37 -33
  159. package/themes/cyberpunk.json +37 -33
  160. package/themes/gamer.json +37 -33
  161. package/themes/imperial.json +37 -33
  162. package/themes/joker.json +37 -33
  163. package/themes/marvin.json +37 -33
  164. package/themes/mean.json +37 -33
  165. package/themes/military.json +37 -33
  166. package/themes/neutral.json +37 -33
  167. package/themes/pirate.json +37 -33
  168. package/themes/sports.json +37 -33
  169. package/themes/verstappen.json +37 -33
  170. package/themes/wizard.json +37 -33
  171. package/web/dist/assets/{About-DX1-VTBP.js → About-C0Mk0igT.js} +1 -1
  172. package/web/dist/assets/{Activity-Dp9NaXOn.js → Activity-C4aJJVXz.js} +1 -1
  173. package/web/dist/assets/{ActivityDetail-HpOBtrXS.js → ActivityDetail-DaQ85za0.js} +1 -1
  174. package/web/dist/assets/{Diff-Ct4G3Se8.js → Diff-B3tlO2Yj.js} +1 -1
  175. package/web/dist/assets/{Doctor-DGjkZFuA.js → Doctor-BNC-5Dmo.js} +1 -1
  176. package/web/dist/assets/{Graph-b8o-S6kv.js → Graph-us0T6ZcV.js} +1 -1
  177. package/web/dist/assets/Harnesses-DAMgyfM_.js +1 -0
  178. package/web/dist/assets/Laws-rOxegJ75.js +1 -0
  179. package/web/dist/assets/{Library-BEZscyaQ.js → Library-KmwrHA1J.js} +1 -1
  180. package/web/dist/assets/{Markdown-k_LSLBGq.js → Markdown-0eQOVGSF.js} +1 -1
  181. package/web/dist/assets/{Profile-DydukTxP.js → Profile-ZU7WV6Xl.js} +1 -1
  182. package/web/dist/assets/{Rules-CIRG4Ldt.js → Rules-CtDoPhhf.js} +1 -1
  183. package/web/dist/assets/{Skills-Buoi1IBr.js → Skills-BhoOxdkD.js} +1 -1
  184. package/web/dist/assets/{StatusBadge-CE_AJttl.js → StatusBadge-CGvYVCwa.js} +1 -1
  185. package/web/dist/assets/{Themes-CI4o3gJC.js → Themes-Dr7OOkD7.js} +1 -1
  186. package/web/dist/assets/{activity-BCW7e6Cy.js → activity-B8aq3qtl.js} +1 -1
  187. package/web/dist/assets/index-2wqPDxuN.css +1 -0
  188. package/web/dist/assets/{index-B1PIx7RM.js → index-AhMzkdbI.js} +1 -1
  189. package/web/dist/assets/index-CRftEtBX.js +12 -0
  190. package/web/dist/assets/{index-Ck2iCdaC.js → index-D0lPN_qW.js} +1 -1
  191. package/web/dist/index.html +2 -2
  192. package/web/src/pages/Laws.jsx +282 -88
  193. package/web/dist/assets/Harnesses-gr3-aHBF.js +0 -1
  194. package/web/dist/assets/Laws-DTwF5tTC.js +0 -1
  195. package/web/dist/assets/index-BnTQUSUz.js +0 -12
  196. package/web/dist/assets/index-DUCsDv7z.css +0 -1
package/docs/limits.md CHANGED
@@ -3,9 +3,11 @@
3
3
  Geneseed is one implementation: `bin/`, `js/`, `adapters/`. It used to be two. Until the 2.0
4
4
  re-layout there was a second, older implementation beside it, and three cell harnesses ran both
5
5
  and compared every byte either one wrote plus both streams and the exit code. That second party
6
- is deleted. What survives it is a **recording**: the answers it gave, frozen under
7
- `tests/__snapshots__/`, replayed by `tests/golden.mjs`, `tests/cli_golden.mjs` and
8
- `tests/web_golden.mjs` against the code that ships.
6
+ is deleted, and **on 2026-08-17 the recording it left behind was retired with it** — the emit, cli
7
+ and web corpora, 1381 cells, plus the two replayers that existed only to drive them. What survives
8
+ under `tests/__snapshots__/` is the narrower set of pure-function and help recordings that
9
+ `tests/snapshot/` replays. *The emit, cli and web corpora were RETIRED* below carries the
10
+ measurement that licensed that, what it cost, and what is left holding the line.
9
11
 
10
12
  This file is the standing answer to *"what does this tool not prove about itself?"* It is kept
11
13
  beside the code rather than in a phase note, because phase notes are per-machine and this
@@ -19,9 +21,10 @@ admits the limit, or deleting the paragraph is a way to pass it.
19
21
  **New in the 2.0 re-layout**, and each is checked against the tree rather than inherited from the
20
22
  previous version of this document:
21
23
 
22
- * **Limit zero** below — the corpus proves *unchanged*, never *correct* — promoted from a bullet
24
+ * **Limit zero** below — a corpus proves *unchanged*, never *correct* — promoted from a bullet
23
25
  to the frame every other row sits inside, because with the second party gone it now governs all
24
- of them.
26
+ of them. It governs a smaller estate since the retirement: the pure-function and help
27
+ recordings, and nothing else.
25
28
  * **Row 1** — there is no full-screen panel at all any more, on any path. Not "unported": absent.
26
29
  * **Row 3** — `/api/pick-folder` still exists as a declared decline and a live 501, but there is
27
30
  no longer a handler anywhere for it to be declined *against*.
@@ -30,9 +33,10 @@ previous version of this document:
30
33
 
31
34
  ---
32
35
 
33
- ## ⚠ Limit zero — the corpus proves UNCHANGED, never CORRECT
36
+ ## ⚠ Limit zero — a corpus proves UNCHANGED, never CORRECT
34
37
 
35
- Every replay in this repository compares today's answer against a recorded one. That is a
38
+ Every replay left in this repository — the pure-function recordings under `tests/__snapshots__/`
39
+ and the 26 help fixtures beside them — compares today's answer against a recorded one. That is a
36
40
  regression gate and it is a good one. It is **not** a correctness gate, and the difference is the
37
41
  whole subject of this file.
38
42
 
@@ -42,37 +46,69 @@ two agreed and were both wrong, the corpus freezes the agreement — and there i
42
46
  second party who could disagree. A cross-implementation comparison was already structurally blind
43
47
  to a defect both sides shared; the recording inherited that blindness and made it permanent.
44
48
 
45
- So: a green replay says *"this code still answers what it answered on 2026-08-17."* It says
46
- nothing about whether that answer was ever the right one. Everything below is the standing list of
47
- where that matters and of the branches nothing reaches at all.
48
-
49
- ## ⚠ The corpus is frozen — recorded 2026-08-17, replay-only from here
50
-
51
- **The recordings under `tests/__snapshots__/` can never be taken again.** They were produced by the
52
- reference implementation and by nothing else; every program that has ever written a cell is
53
- deleted, and the three Node replayers reject `--record` by design. The dispatch-only CI job that
54
- produced the `lf` halves is gone from `.github/workflows/ci.yml` in the same history, so the
55
- workflow did not outlive the reference it drove.
56
-
57
- The last recording was taken deliberately, immediately after the edits that had to be made while
58
- an oracle still existed — the emitted prose that named files the deletion removes, the removal of
59
- the web API's interpreter field, the three verbs the reference never had, and the release-label
60
- destamp that stops a version bump from reddening a corpus nobody can re-bless. Everything after
61
- that point may only be checked against those bytes.
62
-
63
- **The consequence, ASSERTED by the replayers themselves:** a change that moves a recorded byte is
64
- a **finding, not a step**. There is no re-bless. If a replay goes red the choice is to revert the
65
- change or to argue, in writing, that the recording was wrong — and nothing can settle that
66
- argument any more.
67
-
68
- **What the last recording fixed, and what a reader should know it cost.** The two platform halves
69
- had drifted apart for a reason that had nothing to do with either implementation: the `crlf` half
70
- had captured a stale `Harness/` — this repository's own dogfooded install, which is **gitignored
71
- build output** — so `doctor` reported seven stale renders on the machine that recorded it and none
72
- on CI, and the two platforms disagreed permanently. The final recording was taken with that
73
- directory absent, which is the state a fresh clone has. **A stale local build tree moves recorded
74
- bytes; an absent or freshly built one does not.** Anyone comparing the halves should know they now
75
- agree because that input was removed, not because it was ever normalised.
49
+ So: a green replay says *"this code still answers what it answered on the day the recording was
50
+ taken."* It says nothing about whether that answer was ever the right one. Everything below is the
51
+ standing list of where that matters and of the branches nothing reaches at all.
52
+
53
+ **The recordings that remain can never be taken again either.** They were produced by the
54
+ reference implementation and by nothing else; every program that could write one is deleted, and
55
+ the two suites in `tests/snapshot/` replay and do not record. So a change that moves a byte in one
56
+ of them is a **finding, not a step**. There is no re-bless. If a replay goes red the choice is to
57
+ revert the change or to argue, in writing, that the recording was wrong — and nothing can settle
58
+ that argument any more.
59
+
60
+ ## ⚠ The emit, cli and web corpora were RETIRED — 2026-08-17
61
+
62
+ **1381 recorded cells under `tests/__snapshots__/{emit,cli,web}` are deleted**, with the two
63
+ replayers that existed only to drive them (`tests/cli_golden.mjs`, `tests/web_golden.mjs`), the
64
+ scan that swept their bytes for Python, and the three `node-cells` steps in
65
+ `.github/workflows/ci.yml` that ran them. What is left of that job is the two self-comparisons,
66
+ which have no corpus and need none.
67
+
68
+ **The reason is limit zero with the brake off.** A recording nobody can re-bless makes every
69
+ deliberate content change a red gate — an emitted sentence, a reworded refusal, a version bump —
70
+ while saying nothing at all about a defect that happened to be stable on the day it was taken. Run
71
+ long enough, that is a gate reporting the author's intent rather than the code's behaviour, and the
72
+ only way to stay green is to stop changing the product. The 2.0 bump had already shown the shape:
73
+ one label moving reddened 224 of 259 emit cells and none of them was a defect.
74
+
75
+ **The retirement was licensed by MEASUREMENT, not by the redness.** `tests/mutate.mjs` declares 31
76
+ mutations and names, per row, the gate that must kill each one. Five declared a corpus replay and
77
+ nothing else — **M1, M2, M3, M4 and M8** (`EMIT`, `EMIT_IMPERIAL`, `EMIT_CLAUDE`, `WEB_CATALOG`,
78
+ `CLI_CONTEXT`). All five are killed by `tests/unit/` alone, the control included, and each row now
79
+ **declares** the unit file that kills it rather than a replay that can no longer run — M1 and M3
80
+ `maintainer_tools`, M2 `node_driver`, M4 `hook_form`, M8 `hook_cli`. A gate a matrix names and
81
+ cannot run guards nothing, and is worse than none, because it is trusted (Doctrine rigor 2,
82
+ Honest Tests). For every defect the mutation matrix knows how to plant, the corpus was
83
+ redundant. That is the whole argument, and it is the only one that licenses a deletion:
84
+ a test goes because a living gate already proves the property, never because it is red.
85
+
86
+ **What was knowingly LOST: byte-level regression detection on emit, cli and web output.** Nothing
87
+ now notices that a rendered bundle, a verb's stdout / stderr / exit code, or an HTTP response body
88
+ moved by one byte for a reason nobody intended. A defect the mutation matrix does not carry, in a
89
+ region `tests/unit/` does not reach, is one this repository will no longer see. That is a real
90
+ reduction in coverage, stated here rather than discovered later.
91
+
92
+ **What REMAINS:**
93
+
94
+ * `node tests/golden.mjs --idempotent` — the re-emit path every user is on from their second build
95
+ onwards, a run compared against another run of the same generator.
96
+ * `node tests/golden.mjs --deletion` — configuration A, then B onto A's tree, against a **fresh**
97
+ emit of B; the only thing that makes the write-before-delete prune actually delete.
98
+ * `node bin/geneseed-cli.mjs doctor --all` — every theme, plus the parity, authoring and drift
99
+ checks.
100
+ * `tests/unit/` — the suite that kills every mutation the matrix declares but one. The exception is
101
+ **M7**, whose seam is the `updated` branch of `managedBlockWrite`: it runs only on a *re-emit*, so
102
+ no fresh-sandbox fixture reaches it and `--idempotent` is its gate by construction.
103
+
104
+ Both `golden.mjs` modes compare a run against another run of the *same* implementation, so they are
105
+ determinism checks rather than regression gates — the same degradation standing decision 2 below
106
+ has been calling the live one. What they buy for it is that they keep working however the content
107
+ changes, which is exactly what the retired corpora could not do.
108
+
109
+ **The tag `python-reference-v1.0.0` is kept as the historical pointer.** The second implementation
110
+ and every cell it recorded still exist there, in full, and that is the only place they will. Nothing
111
+ in the working tree reads it; it is a place to go and look, not a gate.
76
112
 
77
113
  ### The one hand-edit ever made to a recorded file
78
114
 
@@ -89,9 +125,14 @@ edit was verified to move only `note` lines.
89
125
  **This is an exception, not a precedent.** The next person who wants to hand-edit a recording
90
126
  should assume the answer is no and re-derive the whole argument above for their own field.
91
127
 
92
- ### The second thing the comparison was taught to ignore, and why that is not the same as a hand-edit
128
+ ### The second thing the comparison was taught to ignore — HISTORY, and the reason the retirement above happened
93
129
 
94
- **2026-08-17 — `destamp()` in `tests/helpers/golden.mjs` now blanks `"_version"` inside
130
+ *This describes the emit corpus, which is gone. The destamp survives it and is now inert: a run
131
+ compared against another run of the same generator carries the same version on both sides. Kept
132
+ because it is the clearest measurement of what a frozen corpus costs, and it is what made the case
133
+ for retiring one.*
134
+
135
+ **2026-08-17 — `destamp()` in `tests/helpers/golden.mjs` learned to blank `"_version"` inside
95
136
  `agent-overrides.json`, as it already blanked `[release <REL>]` inside `.geneseed-version`.**
96
137
 
97
138
  The configured release version is stamped into emitted bundles in **two** places, and only the first
@@ -105,38 +146,33 @@ the size never moved and only the hash could see it.
105
146
  not merely 2.0.0 but every possible bump, in 448 recorded files, with no recorder left to re-bless
106
147
  one. That is not a limit anyone chose, and it is the reason this is a fix rather than a concession.
107
148
 
108
- **The distinction that matters:** this narrows what the replay COMPARES; it does not touch a recorded
109
- byte. The other ~553 bytes of that stub, and every other file in all 259 cells, stay asserted exactly
110
- as before. **But it is still a real loss of coverage** — nothing now checks that the emitted
111
- `_version` matches the configured one, and nothing can, because verifying it would need the oracle
112
- that recorded these bytes. If that mapping ever breaks, this corpus will be silent about it.
149
+ **The distinction that mattered at the time:** it narrowed what the replay COMPARED; it did not touch
150
+ a recorded byte. The other ~553 bytes of that stub, and every other file in all 259 cells, stayed
151
+ asserted exactly as before. **It was still a real loss of coverage** — nothing checked that the
152
+ emitted `_version` matched the configured one, and nothing could, because verifying it needed the
153
+ oracle that recorded those bytes. **And that is the pattern that ran out.** Every bump, every reworded
154
+ sentence, every renamed file would have arrived as the same choice: narrow the comparison again, or
155
+ freeze the product. Two exemptions in one release is a trend, not an incident, and the third would
156
+ have been the version bump after this one.
113
157
 
114
158
  ## Frozen residue — true statements that are now permanently unfixable
115
159
 
116
160
  Four things in this repository are wrong, known to be wrong, and **cannot be corrected**, because
117
161
  correcting them would move a byte in a corpus with no recorder left. They are listed so a future
118
- reader finds a decision here rather than concluding nobody looked.
162
+ reader finds a decision here rather than concluding nobody looked. This list was six until
163
+ 2026-08-17; the two the retirement freed are recorded under it, because *which* residue a corpus
164
+ creates is the clearest measure of what freezing one costs.
119
165
 
120
- - **`js/tui.mjs` and `js/menu.mjs` end a refusal with ``Use `harness setup`, `doctor`, or `build`.``**
121
- `harness` was the reference's argparse program name and now names nothing. Both strings are
122
- recorded in the CLI corpus. Changing either reddens cells that can never be re-recorded.
123
166
  - **`tests/__snapshots__/primitives/{win32,posix}.json` freeze `[authoring] _web_core prose says …`**
124
167
  across five cases, naming a module the cut deleted. Frozen evidence about a program that no
125
168
  longer exists — accurate as a record of what the reference answered, misleading as a description
126
169
  of today.
127
- - **`js/cli-table.json`'s `tui` help still promises a "full-screen curses control panel"** for a
128
- verb that returns 1 on every path. The help corpus is frozen at the 26 texts argparse rendered,
129
- so this string has no oracle to be corrected against. The verb's actual output is honest; only
130
- its help is not.
131
- - **`tests/helpers/matrix/*.json` — 6 files — are permanently un-regenerable.** Their only producer
132
- was the reference's matrix exporter. The committed copies are the last export, and
133
- `tests/unit/hook_cli.test.mjs` reads both halves of them to derive the platform union.
134
- - **`tests/workflow_runtime.test.mjs` sits at `tests/` top level, alone among the plugin suites.**
135
- `adapters/opencode/workflows/_runtime.js` names that path in a comment, and that file is EMITTED,
136
- so its bytes are hashed into the emit corpus. Moving the suite to `tests/plugins/` and updating the
137
- pointer grew the emitted file by 8 bytes and turned the corpus red. **The layout bent around the
138
- frozen recording, rather than the shipped comment being left to rot.** This is the one place where
139
- the freeze is visible in the directory structure, and it is deliberate.
170
+ - **`tests/helpers/matrix/*.json` — 4 files — are permanently un-regenerable.** Their only producer
171
+ was the reference's matrix exporter. The committed copies are the last export. `tests/golden.mjs`
172
+ drives the `emit` pair; `tests/unit/hook_cli.test.mjs` reads both halves of the `cli` pair to
173
+ derive the platform union. **The `web` pair was deleted on 2026-08-19** — 418 kB whose only
174
+ reader went with the web corpus, so nothing had exercised them since; `tests/ported.json` carries
175
+ the decision and the criterion it was taken on.
140
176
  - **`tests/__snapshots__/token_report_primitives.json` names `js/lib/pyfs.mjs`** in one recorded
141
177
  `why` field, and this release renamed that file to `js/lib/fs.mjs`. **This one was left alone on
142
178
  purpose, and the reasoning is the point:** `why` is never compared — it is interpolated into a
@@ -146,8 +182,22 @@ reader finds a decision here rather than concluding nobody looked.
146
182
  demands it, not for when it is merely harmless**, or the exception becomes the rule and the corpus
147
183
  stops being evidence.
148
184
 
149
- The first three share one shape: **a string is cheap to fix right up until it is recorded, and then
150
- it is not fixable at all.** That is the cost of freezing a corpus, paid knowingly.
185
+ Three of the four share one shape: **a string is cheap to fix right up until it is recorded, and
186
+ then it is not fixable at all.** That is the cost of freezing a corpus, paid knowingly.
187
+
188
+ **Freed on 2026-08-17, and neither is fixed yet.** Both are now ordinary edits somebody has to make;
189
+ they are kept here so the next reader finds the reason the code looks the way it does.
190
+
191
+ - **`js/tui.mjs` and `js/menu.mjs` end a refusal with ``Use `harness setup`, `doctor`, or `build`.``**
192
+ `harness` was the reference's argparse program name and now names nothing. Both strings were
193
+ recorded in the CLI corpus, so changing either used to redden cells nobody could re-record. That
194
+ corpus is gone; the wrong word is now just a wrong word.
195
+ - **`tests/workflow_runtime.test.mjs` sits at `tests/` top level, alone among the plugin suites.**
196
+ `adapters/opencode/workflows/_runtime.js` names that path in a comment, and that file is EMITTED,
197
+ so its bytes were hashed into the emit corpus: moving the suite to `tests/plugins/` and updating
198
+ the pointer grew the emitted file by 8 bytes and turned the corpus red. **The layout bent around
199
+ the frozen recording rather than leaving the shipped comment to rot** — the one place the freeze
200
+ was visible in the directory structure. Nothing holds it there now.
151
201
 
152
202
  ## What IS covered
153
203
 
@@ -162,8 +212,8 @@ missing thing visible instead of silently absent.
162
212
  | 1 | **There is no full-screen panel.** Not unported — absent. `js/tui.mjs`'s `cmdTui` has two arms and **both print and return 1**: off a TTY it says so, on one it says the entry carries the TUI's layout half and not its screens. The reference's panel — its loop, its eighteen screens, its `(stdscr, curses, pal)` drawing helpers, its Windows console window, its menus, the curses arms of `setup` and `bootstrap`, and `doctor`'s progress callback — is deleted with the implementation that had it. **The comparison half of this row is history**: there is no panel to declare a gap against. What is live is the claim about the shipping code, and it is a claim about ABSENCE, which is the kind a structural gate can hold. (This row used to say the refusal *names* the interpreter-and-script invocation of the panel. It has not since P2, which removed the pointer rather than re-aiming it: a refusal naming a file this migration deletes is worse than one that simply says the screen does not exist. The row was factually stale for two phases, which is the argument for reading the gate out of the code rather than out of the prose.) | **`tests/unit/no_panel.test.mjs`**, which needs nothing but the port: behavioural (faked `isTTY`, exit 1, no panel marks on stdout, and the refusal is the only line it writes) and structural — it fails if `js/tui.mjs` grows any of `?1049h`, `?1049l`, `?25l`, a literal ESC, `\x1b[`, `[`. **What is NOT asserted and cannot be**: that a panel, if one were written, would work. The positive control that used to prove the reference panel really painted — the thing that stopped the mark list being a set of strings nothing produces — retired with the reference. The Node file replaces it with a control of its own (see row 6), which proves the marks still match how *this* codebase spells an escape, not that any panel exists. Landing a real panel means deleting the structural test **first** and updating this row second. |
163
213
  | 2 | **`_wrap_lines`, the panel's line wrapper.** Its only caller was the panel loop, so it travels with row 1 and is now history along with it. **`textwrap.wrap` itself CROSSED** — this row said "a stdlib payload with no Node twin … port it *with* the panel or not at all" until P1 made `--help` the caller that required it: `pyTextWrap` (`js/cli.mjs`) is a real port of it, driven by `formatHelp`. | `tests/__snapshots__/textwrap.json` freezes the CPython answers (38 cases × widths 11..198, sha256 over the matrix) and `tests/snapshot/pure_snapshot.test.mjs` replays them with no interpreter anywhere on the machine. The live comparison against a running `textwrap` retired with the reference, so **limit zero applies here in its sharpest form**: the matrix is 38 cases wide and one CPython version deep, and a wrap CPython itself later changes is a divergence this repository cannot see. The corpus declares which side of gh-139065 it was recorded on, precisely so it cannot be mistaken for one frozen against the behaviour CPython already fixed. |
164
214
  | 3 | **`/api/pick-folder` will never exist here.** It opens an OS-native folder dialog on the daemon host, which is a GUI dependency and a sixth allowed-spawn row for a modal window. `js/web/server.mjs` carries it in `DECLINED_POST`, a set kept distinct from `NOT_PORTED_POST` because "will never cross" is a different claim from "not yet". **What changed in this phase**: there is no longer anything on the other side to decline. The reference's own handler is deleted, so this stopped being a comparison between an endpoint that exists over there and one that does not exist here, and became simply a documented, permanent 501 — and it is the ONLY member of either set, which makes it the last live target the dispatcher's 501 arm has (see row 9). | Probed live, not merely declared: `tests/unit/web_server.test.mjs` drives the real dispatcher and requires POST → 501 and GET → *not* 501 (it must fall through to the SPA). That pair is what refuses the one-line collapse of the POST declarations back into the GET set, which would leave every exported set exactly as written and every set-reading test green. The collapse is planted as **M23** in `tests/mutate.mjs` and killed, so the row's own argument is gated rather than asserted. |
165
- | 4 | **`_win_user_path`'s registry-write success arm.** `link`/`unlink` edit the persistent user Path through PowerShell. No test in this repo may run that — the edit would outlive the suite, the checkout and the machine. | The *failure* arm is reached honestly: the `unlink` cells hand the verb a PATH with no `powershell` on it, so the code takes the "said nothing about PATH" branch with the registry untouched. Everything that can be *wrong* lives in the pure `winUserPathScript` half, and `tests/__snapshots__/win_user_path.json` is a recorded corpus over it, replayed by `tests/snapshot/pure_snapshot.test.mjs` on every platform (the builder reads nothing host-shaped, and a non-Windows run is what measures that). That the spawn half carries no logic used to be **STATED**, in a test docstring; `tests/unit/win_user_path.test.mjs`'s last test now checks it — one `spawnSync`, the builder's output passed through unchanged, no string work on the directory, and a statement ceiling. If logic drifts INTO the spawn the ungated region grows, and this is what refuses that. |
166
- | 5 | ~~**`link`/`unlink`'s Unix arm.**~~ **CLOSED at P11.** It writes a `#!/bin/sh` shim into `~/.local/bin` carrying a `# GENESEED_LINK_SHIM` marker and chmod 0755, and it has six cells of its own. (It *symlinked* `ROOT/geneseed` until P0/P1 Task 2b; the `is_symlink()` branch survives so `unlink` still removes a legacy symlink install, and **no cell covers that branch** — the one piece of this row still open.) The six include the `PATH` split (a substring test passes every other cell and fails `a-path-entry-that-merely-contains-the-dir`), the **marker check** that leaves a foreign `geneseed` alone — the decoy is a regular file, not a symlink, so the marker is what has to reject it — and an explicit dir argument with a trailing slash. That last one found a live divergence: the reference built a `Path` and the port kept the raw string, so `str(Path('/x/bin/'))` and `/x/bin/` disagreed in two messages and in the PATH comparison. Fixed in `js/link.mjs` with `pyPathStr`. | The group is a UNION across the two platforms. `tests/cli_golden.mjs`'s `platformDeclarationProblems` travels with the matrix and asserts, on every replay and on both operating systems in CI, that every id declared for this platform is built, every id declared for the other is absent, and neither half is empty; it also PRINTS the half it did not run, so a run says out loud what it skipped. The two directions that need to read **both** halves at once live in `tests/unit/hook_cli.test.mjs`. |
215
+ | 4 | **`_win_user_path`'s registry-write success arm.** `link`/`unlink` edit the persistent user Path through PowerShell. No test in this repo may run that — the edit would outlive the suite, the checkout and the machine. | The *failure* arm used to be reached honestly, by `unlink` cells that handed the verb a PATH with no `powershell` on it so the code took the "said nothing about PATH" branch with the registry untouched. **Those cells went with the CLI corpus on 2026-08-17 and nothing replaced them**, so the spawn half is now ungated on both arms rather than one — a downgrade this row carries rather than hides. Everything that can be *wrong* still lives in the pure `winUserPathScript` half, and `tests/__snapshots__/win_user_path.json` is a recorded corpus over it, replayed by `tests/snapshot/pure_snapshot.test.mjs` on every platform (the builder reads nothing host-shaped, and a non-Windows run is what measures that). That the spawn half carries no logic used to be **STATED**, in a test docstring; `tests/unit/win_user_path.test.mjs`'s last test now checks it — one `spawnSync`, the builder's output passed through unchanged, no string work on the directory, and a statement ceiling. If logic drifts INTO the spawn the ungated region grows, and this is what refuses that. |
216
+ | 5 | ~~**`link`/`unlink`'s Unix arm.**~~ **CLOSED at P11.** It writes a `#!/bin/sh` shim into `~/.local/bin` carrying a `# GENESEED_LINK_SHIM` marker and chmod 0755, and it has six cells of its own. (It *symlinked* `ROOT/geneseed` until P0/P1 Task 2b; the `is_symlink()` branch survives so `unlink` still removes a legacy symlink install, and **no cell covers that branch** — the one piece of this row still open.) The six include the `PATH` split (a substring test passes every other cell and fails `a-path-entry-that-merely-contains-the-dir`), the **marker check** that leaves a foreign `geneseed` alone — the decoy is a regular file, not a symlink, so the marker is what has to reject it — and an explicit dir argument with a trailing slash. That last one found a live divergence: the reference built a `Path` and the port kept the raw string, so `str(Path('/x/bin/'))` and `/x/bin/` disagreed in two messages and in the PATH comparison. Fixed in `js/link.mjs` with `pyPathStr`. | The group is a UNION across the two platforms, and **the six cells are gone with the CLI corpus.** `platformDeclarationProblems` travelled with the replay — one id per built cell, the other platform's half absent, neither half empty, and the skipped half PRINTED — so it retired with it. What is left is `tests/unit/hook_cli.test.mjs`, which reads **both** halves of `tests/helpers/matrix/*.json` at once and is now the whole of this row's gate rather than the part of it that needed two files. The behaviour itself — the marker check, the `PATH` split, the trailing slash — is no longer probed end to end anywhere. |
167
217
  | 6 | **The boot animation's call site.** `playLine` itself is exercised; what is declared is that `js/setup.mjs` and `js/update.mjs` reach no panel. **Nothing about this row ever needed a second implementation** — its subject is two JavaScript files — and until P3 its only gate was a test that retired with the reference. | **`tests/unit/no_panel.test.mjs`**. Escape sequences, not the word "curses": the first draft looked for the word and failed on a *docblock*, and a structural gate that reads prose is a gate on the documentation — it would have been satisfied by deleting the paragraph that admits the limit. It carries two things its predecessor had not. A **positive control**: the same scan run over `js/anim.mjs`, which paints by design and is excluded by name, must find a hit — otherwise a mark list that has stopped matching how this codebase spells an escape retires all three gates green. And a **literal ESC byte** among the marks, found by accident while falsifying the gate: the falsifying edit was appended with `printf`, which ate the backslash and wrote a real ESC, and every scan stayed green over a module that had just grown a screen-clear. A gate that knows the spellings of a character but not the character is a gate on a coding style. |
168
218
 
169
219
  ## Ungated, and honest about it
@@ -172,12 +222,14 @@ missing thing visible instead of silently absent.
172
222
  |---|---|---|
173
223
  | 7 | **`npmBuild`** (`npm install && npm run build`). | A first-run-from-a-partial-checkout path; no cell can be in that state, because `web/dist/` is tracked. `tests/unit/web_daemon.test.mjs` asserts *that* — it re-derives the unreachability from `git ls-files`, so untracking `web/dist/index.html` fails the test instead of quietly making the arm live. It also asserts the file must EXIST in the working tree, because tracked-but-absent is a real state in which the `ask` arm is reachable here and now while the tracking claim still holds. The two branches only reachable through it — the prompt's wording and EOF handling, and the `npm install` → `npm run build` order — are asserted as source and gated as **M25**. |
174
224
  | 8 | **The stale-factory probe.** *(HISTORY, not a live limit.)* | Its subject was a reference CLI too old to know `upgrade`, dead-ending on argparse's `invalid choice` — a program that no longer exists. `js/update.mjs` argues the absence out at the call site: the update step is an **import, not a spawn**, so there is no second program to be stale, no argparse to refuse, and no `invalid choice` to recognise. The half that DID have a live subject is the durable trace a failed step leaves, and it is asserted absolutely by `tests/unit/update_steps.test.mjs` — persisted before it is printed, appended rather than overwritten, still returned when the log itself cannot be written. |
175
- | 9 | **The web dispatcher's 501 arm, for anything but one path.** | `NOT_PORTED_ACTIONS`, `NOT_PORTED_KINDS` and `NOT_PORTED_POST` are all empty, so `/api/pick-folder` (row 3) is the only target the arm has left, and no second one can be borrowed: every remaining action either starts a job in the developer's checkout or writes a shim and edits the real user PATH. The action-side claim therefore rests on `tests/unit/web_jobs.test.mjs` reading the set out of the module — **a gate on a declaration, which is weaker than a probe**, and is declared as such at both sites. A new row in that set makes the arm reachable again and obliges this row to become a probe. What is stronger is the sibling declaration beside it: each of the three `INLINE_ACTIONS` names a recorded cell that really drives the dispatcher, and the cell's existence is asserted on both corpus halves. |
176
- | 10 | **`pyStrPath` / `pyPathStr`.** Two spellings of one translation in `js/generate.mjs`. | Ungated; the comment at the call site says so. The recorded CLI cells read stderr through a universal-newline decode and cannot see the difference the split is about. |
225
+ | 9 | **The web dispatcher's 501 arm, for anything but one path.** | `NOT_PORTED_ACTIONS`, `NOT_PORTED_KINDS` and `NOT_PORTED_POST` are all empty, so `/api/pick-folder` (row 3) is the only target the arm has left, and no second one can be borrowed: every remaining action either starts a job in the developer's checkout or writes a shim and edits the real user PATH. The action-side claim therefore rests on `tests/unit/web_jobs.test.mjs` reading the set out of the module — **a gate on a declaration, which is weaker than a probe**, and is declared as such at both sites. A new row in that set makes the arm reachable again and obliges this row to become a probe. The sibling declaration beside it used to be stronger — each of the three `INLINE_ACTIONS` named a recorded cell that really drove the dispatcher, and the cell's existence was asserted on both corpus halves. **Those cells retired on 2026-08-17**, so both declarations now rest on the module being read rather than driven, and the row is weaker by exactly that much. |
226
+ | 10 | **`pyStrPath` / `pyPathStr`.** Two spellings of one translation in `js/generate.mjs`. | Ungated; the comment at the call site says so. The recorded CLI cells could not see the difference the split is about either — they read stderr through a universal-newline decode — and they are gone as of 2026-08-17, so the row went from ungated-with-a-witness-nearby to simply ungated. |
177
227
  | 11 | **The Linux browser lookup answering FALSE.** The reference walked `webbrowser`'s registry (`$BROWSER`, `xdg-open`, `gio`, `gvfs-open`, a dozen browser binaries); `js/menu.mjs`'s `browserAvailable` asks only whether `xdg-open` — the binary `openUrl` will actually run — is on PATH. On a box with `gio` and no `xdg-open` they disagreed, which is what a WSL run measured. | The corpus in `tests/unit/web_first.test.mjs` SUPPLIES the input instead of inheriting it: an `xdg-open` stub on PATH plus `DISPLAY`, so the True arm is reached identically on every host, and a `no-display` row gates the Linux-only display refusal absolutely. Reaching the FALSE arm needs a PATH with no `xdg-open` at all, which is not something a probe on Windows survives — so it stays ungated, here, rather than in a comment. **The divergence it names is now permanent** and belongs in `docs/declined.md`'s territory rather than this file's: with the reference gone, `browserAvailable`'s answer is simply the answer, and nothing can say it is the wrong one. |
178
228
  | 12 | **The width tables are pinned by a HASH, not by a live oracle.** `js/tui.mjs`'s `WIDE`/`COMBINING` tables are a snapshot of unidata **15.1.0** (declared as `DWIDTH_UNIDATA`). Nothing in this repository consults a live Unicode database any more — there is none to consult — so what the sweep proves is that the tables have not moved since they were recorded, and **not** that they encode any Unicode version correctly. This was already true of the CPython comparison in one direction (3.14 carries 16.0.0 and assigned U+0897 a combining class, so the old live sweep reported a divergence that was neither implementation's); it is now true in both. | `tests/__snapshots__/dwidth.json` carries all 527 runs, a sha256 over them, and the unidata version they were measured at; `tests/snapshot/pure_snapshot.test.mjs` checks `DWIDTH_UNIDATA` against that declaration, asserts the constant NAMES a Unicode version at all — read out of `js/tui.mjs` by regex **as well as** imported, so the gate cannot be satisfied by deleting the declaration and supplying the value some other way — and replays the sweep. The document self-checks against its own hash first, so a hand-edited `rle` is not accepted as an oracle. The interpreter pin that used to hold this together retired with the live sweep. Regenerating the tables means moving the constant and re-recording the corpus **together**, and re-recording is exactly what is no longer possible: **new tables would have to arrive with a new corpus recorded from the port itself, which is a determinism check and not a regression gate.** |
179
229
  | 13 | **`minuteStamp`'s recorded answers carry the recording machine's clock.** `%Y-%m-%d %H:%M` off a naive local-time conversion, so the frozen answers hold one UTC offset. There is no token to substitute — the offset is the answer's arithmetic, not a substring of it. | Each of the five cases carries a `guard` naming the UTC offset **at that instant** (so DST is part of the match, not an approximation), and `tests/snapshot/pure_snapshot.test.mjs` recomputes it from `Date` before asserting. A replay in another zone SKIPS those cases and PRINTS how many it skipped — the width sweep's rule that a skip is not a pass. The live comparison that used to gate them everywhere is retired, so they are gated only where the offsets agree, which is not many machines. That is the cost of the cut, taken deliberately: a case asserted in one zone and declared in the others beats one silently re-blessed to whatever zone last ran it. |
180
230
 
231
+ | 14 | **A RENAMED test file is no longer detected.** `tests/ported.json` named the Node successor of every deleted Python test, and `tests/unit/ported.test.mjs` asserted each of those 52 paths still existed on disk — so renaming one reddened the suite. That test was **retired on 2026-08-19**: four of its six assertions only ever checked that the ledger agreed with itself, and the two that reached outside it charged a hand edit inside a 231 kB, 92%-prose JSON for every rename. | A *deletion* is still caught, and over a wider set: `.github/workflows/ci.yml`'s discovery floor fails below 500 reported tests across all 71 files, where the ledger pinned 52. What is genuinely gone is the *rename* signal — a file renamed and still discovered by the glob passes. The one assertion that outranked the ledger, the recursive walk for a stray untracked `tests/**/*.py`, was salvaged verbatim into `tests/unit/package_manifest.test.mjs` beside the tracked-file count it strengthens: it needs no row to be true and no repository to run, which is exactly why it was worth keeping and the other five were not. The ledger itself is kept, unread, as the record of the migration; its own `doc` now opens by saying it does not execute. |
232
+
181
233
  **On row 8, and the shape it is an example of.** A row whose only gate was a retiring test silently
182
234
  demotes from ASSERTED to prose at a deletion — which is the exact failure this file exists to
183
235
  prevent. Row 8 was the first to be settled the other way instead: its *stale-factory* half
@@ -188,13 +240,13 @@ now settled the same way, and row 3's history half with them.
188
240
 
189
241
  ## Not proven anywhere, and worth knowing before a release
190
242
 
191
- * ~~**The three cell harnesses have only ever run on Windows.**~~ **FIXED at P11**, and widened in
192
- the 2.0 re-layout: the `node-cells` job in `.github/workflows/ci.yml` replays all three corpora
193
- on **`ubuntu-latest` AND `windows-latest`** on every push, and `validate` (doctor plus the whole
194
- Node suite) runs on both as well. The budget argument that kept the old comparison job
195
- Linux-only does not carry over — a replay is a single spawn per cell — and the `crlf` half of
196
- the corpus has no other replayer at all. What the first Linux run found, recorded because it is
197
- the honest measure of what one-platform testing hides:
243
+ * ~~**The three cell harnesses have only ever run on Windows.**~~ **FIXED at P11**, and the
244
+ two-platform rule outlived the corpora that motivated it: `node-cells` still runs on
245
+ **`ubuntu-latest` AND `windows-latest`** on every push — `--idempotent` and `--deletion` now,
246
+ where it replayed all three corpora until 2026-08-17 — and `validate` (doctor plus the whole
247
+ Node suite) runs on both as well. What the first Linux run found, recorded because it is the
248
+ honest measure of what one-platform testing hides, and because every one of these was found by
249
+ a cell harness that no longer exists:
198
250
  * The POSIX shim's `"$@"` was read as a path, so every healthy install's hooks were reported
199
251
  dead — **a user-visible `doctor` bug on every Linux and macOS install**, in both
200
252
  implementations, plus 112 of 259 golden cells and four `--validate-only` tests. Fixed at the
@@ -234,9 +286,10 @@ now settled the same way, and row 3's history half with them.
234
286
  * **A temp-directory cleanup race killed the Linux job** after it had compared 270 of 318 cells;
235
287
  the same commit passed on re-run. A sandbox teardown cannot raise.
236
288
  * ~~**The reference is load-bearing.**~~ **HISTORY.** The replayers used to default to the reference
237
- as their `--ref`; there is no `--ref` left to default. What replaced it is limit zero: the
238
- recording is now the only second party, and it cannot be asked a new question.
239
- * **Nothing measures the corpus's own coverage from the outside.** The claims that stop a corpus
289
+ as their `--ref`; there is no `--ref` left to default, and since 2026-08-17 there is no recording
290
+ of it left on the emit, cli or web surfaces either. On those three, this project's only second
291
+ party is another run of itself.
292
+ * **Nothing measures the surviving corpora's own coverage from the outside.** The claims that stop a corpus
240
293
  quietly ceasing to exercise a function it still names — the fence really varies, the diff reaches
241
294
  past 200 elements, a splitlines case breaks where `split('\n')` does not, a capitalize case where
242
295
  the rest matters, a `len` case where code points and UTF-16 units differ, both arms of every
@@ -262,9 +315,15 @@ per-machine.
262
315
  the port — which makes them a DETERMINISM check rather than a regression gate, the same
263
316
  degradation `--idempotent`/`--deletion` already carry. Neither is wrong; picking neither is.
264
317
  **This is limit zero with a deadline**, and it is the live one.
265
- 3. ~~**`record-corpus` stops working entirely.**~~ **DONE, and it is what makes item 2 sharp.** The
318
+ 3. ~~**`record-corpus` stops working entirely.**~~ **DONE, and it is what made item 2 sharp.** The
266
319
  job is deleted from `.github/workflows/ci.yml`. With it went the platform-independence gate and
267
- the record-twice rot detector: the two recorded platform halves ARE the platform table now, and
268
- nothing re-derives them. A replacement had to be written while both implementations still
320
+ the record-twice rot detector: the two recorded platform halves WERE the platform table, and
321
+ nothing re-derived them. A replacement had to be written while both implementations still
269
322
  existed — a port-recorded corpus proves nothing the port did not already believe — and what was
270
- written instead is the two-OS `node-cells` job above, which replays rather than records.
323
+ written instead was the two-OS `node-cells` job above, which replayed rather than recorded.
324
+ 4. ~~**Every content change is a corpus argument.**~~ **SETTLED ON 2026-08-17 by retiring the emit,
325
+ cli and web corpora** — see the section of that name above. This is what item 2 looks like once
326
+ its deadline arrives: the choice was never between a green corpus and a red one, it was between
327
+ freezing the product and admitting the recording had stopped being evidence. Item 2 is still
328
+ open, on the 26 help fixtures, and it is still the live one — but it is now the *only* one, and
329
+ the CLI's help text is a much smaller thing to hold still than every emitted byte.
@@ -30,10 +30,21 @@ every host the same way instead of one silently falling behind.
30
30
 
31
31
  ## Where the tokens go
32
32
 
33
- - **The root file is ~76% of the bill**, and its Rules section alone is ~6.3k
34
- of the ~10.7k. The [footprint dial](#/docs/footprint) (`full` vs `lean`)
35
- exists precisely because this is the only lever that matters — trimming
36
- anything else is noise.
33
+ - **The root file is ~76% of the bill**, and the constitution inside it is the
34
+ bulk of that — no longer one section but three: the Ontology, the nine Rules,
35
+ and the doctrine packs this install built in. **Two** levers move it, not one.
36
+ The [footprint dial](#/docs/footprint) (`lean`, the default, keeps each Rule
37
+ and each doctrine rule as its heading plus first line, and ships the Ontology
38
+ *whole*; `full` inlines every rationale). And the
39
+ [doctrine packs](#/docs/setup-choices) — `geneseed-build --doctrines craft,rigor`
40
+ drops whole blocks of §2 at build time, which removes far more than the
41
+ footprint dial trims. Anything else is noise.
42
+
43
+ The three-tier split grew the root file: measured on `neutral`, `AGENT.md`
44
+ went from 31,457 bytes to 36,882 at `lean` (+17.2%) and 53,378 to 54,810 at
45
+ `full` (+2.7%). The lean growth is the larger one *by design* — the Ontology
46
+ is prose, and truncating four flowing sections to four orphan sentences would
47
+ have shipped a worldview nobody could read.
37
48
  - **Skill bodies (~53k) and agent bodies (~10k) are lazy** on every host —
38
49
  loaded only when invoked. A typical skill costs ≤3k per invocation; the
39
50
  heaviest (`react-view-transitions`, ~17.5k with its reference files) loads
@@ -5,4 +5,4 @@ title: "Agents"
5
5
  kind: "concept"
6
6
  link: {"hash": "#/section/agents", "label": "Browse the catalog →"}
7
7
  ---
8
- {N_AGENTS} capability specialists — `reviewer`, `tester`, `architect`, `docs`, `security`, `explorer`, plus a debate **council** the [[council]] skill convenes (`advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian`). You delegate by capability, not by folder: the harness picks the right specialist from the request.
8
+ {N_AGENTS} capability specialists — `reviewer`, `tester`, `architect`, `developer`, `docs`, `security`, `explorer`, plus a debate **council** the [[council]] skill convenes (`advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian`). You delegate by capability, not by folder: the harness picks the right specialist from the request.
@@ -4,7 +4,7 @@ order: 0
4
4
  title: "Edit the source"
5
5
  kind: "concept"
6
6
  ---
7
- Everything theme-independent lives under `src/` — laws, agents, skills, memory and notebook conventions, and the `AGENT.md.tmpl` entrypoint. Voice tokens live under `themes/` as one JSON file per theme. After editing, `geneseed build --emit opencode-global` (or `geneseed update`) re-renders the deployed bundle. The `doctor` action verifies the result: unresolved theme tokens, dead links, hermetic escapes, theme-key parity, and that the committed bundle matches a fresh render.
7
+ Everything theme-independent lives under `src/` — the constitution (`ontology/`, `laws/`, `doctrines/`), the `postures/` and `modes/` registers, agents, skills, memory and notebook conventions, and the `AGENT.md.tmpl` entrypoint. Voice tokens live under `themes/` as one JSON file per theme. After editing, `geneseed build --emit opencode-global` (or `geneseed update`) re-renders the deployed bundle. The `doctor` action verifies the result: unresolved theme tokens, dead links, hermetic escapes, theme-key parity, and that the committed bundle matches a fresh render.
8
8
 
9
9
  Each agent and skill also carries a lifecycle status — `experimental`, `approved` or `deprecated` — recorded in `registry.json` at the repo root alongside its version, owner and add-date. The file is maintainer-side: it is never rendered into a bundle, so a status costs no session context. Adding a spec means adding its row; `doctor` fails on a spec with no row and on a row with no spec, and the catalog badges anything that is not `approved`. The same pass refuses to let a credential ship, and holds every vendored skill folder to an immutable upstream commit rather than a moving branch.
10
10
 
@@ -9,8 +9,8 @@ Claude Code has no `instructions` array and no JS plugin dir — the harness rea
9
9
  ### The four hooks
10
10
 
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
- - **Git gate** — `PreToolUse` on Bash runs `geneseed-hook git-gate`: enforces the safety Laws at the tool boundary, before a shell command executes.
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) (**Law VI**). Ordinary edits are untouched.
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
+ - **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
14
  - **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`).
15
15
 
16
16
  ### Where they live
@@ -4,7 +4,7 @@ order: 11
4
4
  title: "The collaboration layer"
5
5
  kind: "concept"
6
6
  ---
7
- Beyond the Laws, four mechanisms shape *how* the agent works with you — the register, the mutual contract, how memory binds, and who you are. All four 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, Copilot) unchanged.
8
8
 
9
9
  ### Postures — the register
10
10
 
@@ -16,7 +16,7 @@ A **mode** is the session's operating register — *how* work gets executed, as
16
16
 
17
17
  ### The Pact — a two-way contract
18
18
 
19
- Where the Laws bind the agent, the **Pact** binds the collaboration. It holds three co-equal protections (you, the truth, the agent) and — unusually — names what *you* owe back: don't punish candour that honours the pact, give context up front, decide when shown a fork. It is framing, not an enforced rule.
19
+ Where the Rules bind the agent, the **Pact** binds the collaboration — and it is not a layer beside them: it is stated inside the Ontology's **Telos**, the tier that is always in force, so it holds in every task and every repository. It carries three co-equal protections (you, the truth, the agent), never traded one for another, and — unusually — names what *you* owe back: don't punish candour that honours the pact, give context up front, decide when shown a fork. Your half is not enforceable against you; the agent's half is binding on it.
20
20
 
21
21
  ### Typed memory — binding force
22
22
 
@@ -24,4 +24,4 @@ A memory may carry an optional `force` — **constraint** (imposed, not the agen
24
24
 
25
25
  ### Your profile — identity
26
26
 
27
- `PROFILE.md`, seeded once beside `AGENT.md` and never overwritten, holds *who you are*: role, habits, register preferences. It is identity, not rules — it colours how the agent works but never binds (precedence is Laws, then `user-rules.md`, then the profile). Edit it here under the **Profile** tab, or in the file directly — or let the agent draft it: the [[profile]] skill interviews you (who you are, how you work, how you like answers pitched) and writes the file only with your consent, routing anything that is really a standing rule to `user-rules.md` instead.
27
+ `PROFILE.md`, seeded once beside `AGENT.md` and never overwritten, holds *who you are*: role, habits, register preferences. It is identity, not rules — it colours how the agent works but never binds (precedence is the Ontology and the Rules, then `user-rules.md`, then the active Doctrines, then the profile). Edit it here under the **Profile** tab, or in the file directly — or let the agent draft it: the [[profile]] skill interviews you (who you are, how you work, how you like answers pitched) and writes the file only with your consent, routing anything that is really a standing rule to `user-rules.md` instead.
@@ -8,7 +8,7 @@ Once installed, the agent doesn't change *how* you talk to your tool — it chan
8
8
 
9
9
  ### What loaded automatically
10
10
 
11
- - **`AGENT.md`** — {N_LAWS} universal Rules the agent obeys.
11
+ - **`AGENT.md`** — the [constitution](#/docs/rules): the Ontology's {N_ONTOLOGY} sections, {N_LAWS} always-on Rules, and the {N_DOCTRINE_RULES} doctrine rules from the packs this install built in.
12
12
  - **Your repo's docs** — `README.md`, `CONTRIBUTING.md`, anything under `docs/` the harness discovers.
13
13
  - **Your machine's wiki** (if you [set one up](#/docs/configure-wiki)) — eager entries and a lazy listing of the rest.
14
14
  - **The skill and agent catalogue** — invokable by name.
@@ -25,4 +25,4 @@ Skills are repeatable workflows; agents are capability specialists. You invoke t
25
25
 
26
26
  ---
27
27
 
28
- **Catalog:** [Skills](#/section/skills) · [Agents](#/section/agents) · [Rules](#/laws)
28
+ **Catalog:** [Skills](#/section/skills) · [Agents](#/section/agents) · [Constitution](#/laws)
@@ -5,17 +5,24 @@ title: "Footprint (lean vs full)"
5
5
  kind: "concept"
6
6
  link: {"hash": "#/settings", "label": "Toggle it in Settings →"}
7
7
  ---
8
- **Footprint** controls how much of the Rules your agent carries *inline* in `AGENT.md` every turn. Two states — **lean** (the default) and **full** — set per install. It's a token-cost dial, not a change to which Rules apply: every Rule is always in force, and across lean and full the emitted files are otherwise identical — same Agents, Skills, plugins, commands, Memory, and Notebook.
8
+ **Footprint** controls how much of the [constitution](#/docs/rules) your agent carries *inline* in `AGENT.md` every turn. Two states — **lean** (the default) and **full** — set per install. It's a token-cost dial, not a change to which rules apply: whatever this install adopted is in force at either setting, and across lean and full the emitted files are otherwise identical — same Agents, Skills, plugins, commands, Memory, and Notebook. (The dial that *does* change which rules apply is the [doctrine packs](#/docs/setup-choices), a separate choice.)
9
9
 
10
10
  ### The difference
11
11
 
12
- - **Full** — Section 1 of `AGENT.md` inlines every Rule's complete text *and* its reasoning. The agent sees the full law set, rationale included, on every single turn.
13
- - **Lean** — Section 1 carries each Rule as its **heading + the rule itself** (one line), followed by a pointer to the complete law file (`laws/universal.md`, shipped beside `AGENT.md`) — the agent reads the rationale on demand when a rule's application is unclear. Lean trims Section 1 by roughly 40%.
12
+ - **Full** — `AGENT.md` inlines the Ontology, every Rule and every rule of the **active** doctrine packs, complete text and reasoning included, on every single turn.
13
+ - **Lean** — each Rule and each doctrine rule is carried as its **heading + the rule itself** (one line), each tier closing with a pointer to its complete text shipped beside `AGENT.md` — the agent reads the reasoning on demand when a rule's application is unclear. The **Ontology ships whole even at lean**: it is flowing prose, not numbered blocks, and truncating it to first sentences would leave orphan fragments rather than a shorter argument.
14
+
15
+ ### What ships on disk either way
16
+
17
+ Neither setting hides text from the agent; they hide it from *every turn*.
18
+
19
+ - **Lean** writes `laws/`, `ontology/` and `doctrines/` in full text into the install, beside `AGENT.md`, because the inline copy has lost text the agent may still need.
20
+ - **Full** writes `doctrines/` for the mirror-image reason: full inlines only the packs this install built in, so the ones it left out have to be on disk for a citation into them to resolve, and for you to read before turning one on.
14
21
 
15
22
  ### Which to choose
16
23
 
17
- - Keep **full** if token cost is a non-issue, you want the rationale always present, or you run a smaller/cheaper model — with the *why* eagerly in context, a model applies a Rule's nuance more reliably. This is why full is the default.
18
- - Switch to **lean** to reclaim context and cost on long sessions, large repos, or cost-sensitive runs, trusting the agent to pull the full law when it needs the *why*. Lean is **safe**: the complete law text still ships, and the harness explicitly points the agent there before acting on secrets, deletion, git history, scope, or untrusted content. It's an optimization, not a rules cut.
24
+ - Keep **lean** — the default — on long sessions, large repos, or cost-sensitive runs, trusting the agent to pull the complete text when it needs the *why*. Lean is **safe**: that text still ships, and the harness explicitly points the agent there before acting on secrets, deletion, git history, scope, or untrusted content. It's an optimization, not a rules cut.
25
+ - Switch to **full** if token cost is a non-issue, you want the reasoning always present, or you run a smaller/cheaper model — with the *why* eagerly in context, a model applies a rule's nuance more reliably.
19
26
 
20
27
  ### How to set it
21
28
 
@@ -23,9 +30,9 @@ It's set-and-forget — stored in the `.geneseed-footprint` marker, preserved ac
23
30
 
24
31
  - **Settings** — the **Footprint** toggle flips the current install (full ⇄ lean) and rebuilds it in place.
25
32
  - **Harnesses tab** — a per-harness dropdown sets it for any one install independently, then **Apply**.
26
- - **Setup / re-theme wizard** — asks for footprint alongside voice and mode.
33
+ - **Setup / re-theme wizard** — asks for footprint alongside voice, posture, mode, and doctrine packs.
27
34
  - **CLI** — `geneseed-build --footprint lean` (with any `--emit`).
28
35
 
29
36
  ---
30
37
 
31
- **Related:** [Rules (Laws)](#/laws) · [Voice vs structure](#/docs/themes) · [Token footprint — the numbers](#/docs/token-footprint)
38
+ **Related:** [The constitution](#/docs/rules) · [Voice vs structure](#/docs/themes) · [Token footprint — the numbers](#/docs/token-footprint)
@@ -5,7 +5,7 @@ title: "Foreman mode"
5
5
  kind: "concept"
6
6
  link: {"hash": "#/harnesses", "label": "Set the mode in Harnesses →"}
7
7
  ---
8
- **Mode** is the session's *operating* register — how work gets executed — a separate dial from posture (the *relationship* register) and footprint (how much of the Rules loads inline). Two modes ship, set at build time so the choice holds steady instead of drifting mid-session, and preserved across every rebuild and re-theme.
8
+ **Mode** is the session's *operating* register — how work gets executed — one of four build-time dials, beside posture (the *relationship* register), [doctrine packs](#/docs/setup-choices) (which practice rules bind) and footprint (how much of the constitution loads inline). Two modes ship, set at build time so the choice holds steady instead of drifting mid-session, and preserved across every rebuild and re-theme.
9
9
 
10
10
  ### direct — the default
11
11
 
@@ -25,7 +25,7 @@ Because the crew runs apart from the conversation, the session stays responsive
25
25
  Set-and-forget, stored beside the install and preserved across rebuilds and re-themes:
26
26
 
27
27
  - **Harnesses tab** — the per-install **Mode** dropdown (beside Voice, Footprint, and Posture), then **Apply**.
28
- - **Setup / re-theme wizard** — asks for mode alongside voice, posture, and footprint.
28
+ - **Setup / re-theme wizard** — asks for mode alongside voice, posture, doctrine packs, and footprint.
29
29
  - **CLI** — `geneseed-build --mode foreman`; switch back with `--mode direct`.
30
30
 
31
31
  ---
@@ -6,14 +6,14 @@ kind: "concept"
6
6
  ---
7
7
  One source, **five emit targets** — each with a per-repo and a global (`-global`) variant, plus a portable `files` bundle any `AGENT.md`-aware tool can read. **OpenCode** runs its own engine (JS plugins, colour themes, LSP); **Claude Code**, **Bob**, and **Copilot** share one Claude-shaped engine that diverges only by host dialect.
8
8
 
9
- The harness itself — Rules, Agents, Skills, Memory, and the preamble voice — is **identical on every host**. What differs is how much of it the host can *automate* (via plugins or hooks) versus carry as preamble discipline.
9
+ The harness *content* — the constitution (Ontology, Rules, Doctrines), Agents, Skills, Memory, and the preamble voice — is **identical on every host**: one build renders the same text for all five targets. What differs is how much of it the host can *automate* (via plugins or hooks) versus carry as preamble discipline.
10
10
 
11
11
  | Capability | OpenCode | Claude | Bob | Copilot |
12
12
  | --- | :---: | :---: | :---: | :---: |
13
13
  | Agents · Skills · Memory | ✅ | ✅ | ✅ | ✅ |
14
14
  | Context injection | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
15
15
  | Memory write-back (learn) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
16
- | Git-gate consent (Rule XX) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
16
+ | Git-gate consent (Doctrine process 5) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
17
17
  | Sovereign-repo excludes | ⚙️ plugin | ✅ | ✅ | ➖ |
18
18
  | MCP server wiring | ✅ | ✅ | ✅ | ✅ |
19
19
  | Colour themes | ✅ | ➖ | ➖ | ➖ |
@@ -21,7 +21,9 @@ The harness itself — Rules, Agents, Skills, Memory, and the preamble voice —
21
21
 
22
22
  ✅ native · ⚙️ OpenCode plugin · 🪝 `settings.json` hook · 📄 preamble prose only · ➖ no host mechanism (harness discipline still applies). ¹ Bob honours Claude-dialect hooks best-effort — inert if unsupported, harness still holds via the preamble.
23
23
 
24
- **No host drops an Agent, Skill, or the memory convention.** The asymmetry is entirely in *automation mechanism*: OpenCode's plugins and Claude/Bob's hooks enforce a few Rules for you, where **Copilot** (no hook mechanism) enforces them through preamble discipline. The OpenCode-only extras (themes, LSP, workflow runner, primary-agent) have no analogue on a Claude-shaped host.
24
+ **The git-gate row is the one capability that is not always present**, on any host. It implements **Doctrine process 5**, so it is wired only on an install that built the **process** [doctrine pack](#/docs/setup-choices) in — drop that pack and the boundary drops with it, because a gate that keeps asking for a rule the install never adopted is a gate arguing with its own harness. One asymmetry: on OpenCode that holds for a *fresh* install, but a pack-off rebuild of an install whose `opencode.json` already carries a `git commit*` entry leaves it in place and reports it — Geneseed cannot tell its own entry from one you typed, so it removes neither ([doctrine packs](#/docs/setup-choices)). The invariant-territory refusals in the same neighbourhood (`rm -rf`, a force-push) are the Rules' territory, not the pack's, and stay in every build.
25
+
26
+ **No host drops an Agent, Skill, or the memory convention.** The asymmetry is otherwise entirely in *automation mechanism*: OpenCode's plugins and Claude/Bob's hooks enforce a few of these rules for you at the tool boundary, where **Copilot** (no hook mechanism) enforces them through preamble discipline. The OpenCode-only extras (themes, LSP, workflow runner, primary-agent) have no analogue on a Claude-shaped host.
25
27
 
26
28
  ---
27
29
 
@@ -32,4 +32,4 @@ A checkout needs **git** and the same **Node ≥ 22.3** as everything else — t
32
32
 
33
33
  ---
34
34
 
35
- **Next:** [Posture & footprint](#/docs/setup-choices) · [Verify it works](#/docs/verify) · [What you just installed](#/docs/model) · [Install by hand instead](#/docs/install-paths)
35
+ **Next:** [Posture, doctrines & footprint](#/docs/setup-choices) · [Verify it works](#/docs/verify) · [What you just installed](#/docs/model) · [Install by hand instead](#/docs/install-paths)