geneseed 4.7.0 → 4.9.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 (84) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/SHIPPED.md +1 -1
  4. package/adapters/claude-code/README.md +5 -1
  5. package/adapters/opencode/HOW-OPENCODE-LOADS.md +1 -1
  6. package/adapters/opencode/README.md +5 -4
  7. package/adapters/opencode/plugins/geneseed-context.js +17 -1
  8. package/adapters/opencode/plugins/geneseed-guard.js +569 -38
  9. package/bin/geneseed-hook.mjs +16 -5
  10. package/docs/concepts/enforcement-table.md +3 -3
  11. package/docs/concepts/exclusions.md +37 -1
  12. package/docs/concepts/hooks.md +13 -5
  13. package/docs/concepts/host-matrix.md +3 -3
  14. package/docs/reference/plugin-context.md +1 -1
  15. package/docs/reference/plugin-guard.md +3 -3
  16. package/docs/reference/troubleshoot-doctor.md +5 -2
  17. package/docs/understand/a-day.md +6 -6
  18. package/docs/understand/design-choices-gates.md +2 -2
  19. package/docs/understand/enforced-vs-asked.md +4 -4
  20. package/docs/understand/harness.md +1 -1
  21. package/docs/understand/machine-claude.md +1 -1
  22. package/docs/understand/machine-opencode.md +2 -2
  23. package/docs/web-ui.md +7 -4
  24. package/harness.config.json +1 -1
  25. package/js/README.md +2 -1
  26. package/js/build/args.mjs +7 -1
  27. package/js/build/driver.mjs +19 -7
  28. package/js/build/emit-claude.mjs +4 -3
  29. package/js/build/emit-opencode.mjs +2 -1
  30. package/js/build/generate.mjs +18 -5
  31. package/js/build/render.mjs +7 -1
  32. package/js/build/source.mjs +22 -1
  33. package/js/cli-table.json +120 -0
  34. package/js/hosts/gitref.mjs +47 -1
  35. package/js/hosts/hooks-done.mjs +4 -0
  36. package/js/hosts/hooks-learn.mjs +3 -1
  37. package/js/hosts/hooks.mjs +22 -43
  38. package/js/hosts/installs.mjs +30 -1
  39. package/js/hosts/loss.mjs +522 -0
  40. package/js/hosts/settings.mjs +90 -80
  41. package/js/inspect/checks-probe.mjs +18 -6
  42. package/js/inspect/diff.mjs +4 -1
  43. package/js/inspect/status.mjs +5 -2
  44. package/js/inspect/validate.mjs +11 -4
  45. package/js/maintain/uninstall.mjs +7 -2
  46. package/js/maintain/update.mjs +1 -1
  47. package/js/web/actions.mjs +24 -3
  48. package/js/web/api.mjs +5 -2
  49. package/package.json +1 -1
  50. package/src/AGENT.md.tmpl +1 -1
  51. package/src/laws/universal.md +17 -17
  52. package/src/skills/worktree.md +1 -1
  53. package/web/dist/assets/{Activity-Dj0izJwj.js → Activity-DZ3O_DsJ.js} +1 -1
  54. package/web/dist/assets/{ActivityDetail-wjq5W02K.js → ActivityDetail-BQSWwoJ-.js} +1 -1
  55. package/web/dist/assets/Dashboard-COLskkOJ.js +2 -0
  56. package/web/dist/assets/Docs-DUxizutI.js +5 -0
  57. package/web/dist/assets/FilterInput-zLFXoSt6.js +1 -0
  58. package/web/dist/assets/GreenhouseView-BSzVX_l7.js +2 -0
  59. package/web/dist/assets/{Installs-XSm0a_zQ.js → Installs-Ck4YyEzs.js} +2 -2
  60. package/web/dist/assets/JournalView-BbbACsYK.js +1 -0
  61. package/web/dist/assets/Laws-BZ_M86r3.js +3 -0
  62. package/web/dist/assets/{Library-CkaPVUYU.js → Library-D_4LbUg2.js} +1 -1
  63. package/web/dist/assets/{Loops-CexwOARr.js → Loops-Bzb2Y96W.js} +2 -2
  64. package/web/dist/assets/{Markdown-DrXLM6TA.js → Markdown-iHvBrpRN.js} +1 -1
  65. package/web/dist/assets/{OperatorHudView-D5qTQat0.js → OperatorHudView-D-nzB6_r.js} +1 -1
  66. package/web/dist/assets/{Personal-B_Vx9a7a.js → Personal-C66284wd.js} +1 -1
  67. package/web/dist/assets/Tabs-DkPfjG3H.js +1 -0
  68. package/web/dist/assets/{activity-dcg5vF9j.js → activity-DvPxoS4Y.js} +1 -1
  69. package/web/dist/assets/drawable-DlrXDOJ7.js +1 -0
  70. package/web/dist/assets/{index-BoG1M8Xy.css → index-BCJz4_8C.css} +1 -1
  71. package/web/dist/assets/{index-BmbWeoTM.js → index-DJorAjeH.js} +2 -2
  72. package/web/dist/assets/{react-DRhoX0kS.js → react-BOF6yL40.js} +2 -2
  73. package/web/dist/assets/{sections-GalMS0Zv.js → sections-BET3c1WT.js} +1 -1
  74. package/web/dist/assets/{usePoll-BmR631BB.js → usePoll-NSTzLOF4.js} +1 -1
  75. package/web/dist/index.html +4 -4
  76. package/web/src/pages/Laws.jsx +84 -9
  77. package/web/dist/assets/Dashboard-UMbgHbzF.js +0 -2
  78. package/web/dist/assets/Docs-D3ABE_ds.js +0 -5
  79. package/web/dist/assets/FilterInput-BSfpStOZ.js +0 -1
  80. package/web/dist/assets/GreenhouseView-C238LWBD.js +0 -2
  81. package/web/dist/assets/JournalView-BUj4Ff-6.js +0 -1
  82. package/web/dist/assets/Laws-yGBJFKgY.js +0 -3
  83. package/web/dist/assets/Tabs-YZbGqWPW.js +0 -1
  84. package/web/dist/assets/drawable-BoQnwVpr.js +0 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,44 @@ label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [4.9.0] — 2026-10-18
12
+
13
+ Minor: on/off switches for the Sealed Secrets and done gates, and the console calls the laws Dictates.
14
+
15
+ ### Added
16
+ - **On/off switches for the Sealed Secrets and Verify Before Asserting gates**, beside Law IV's. `geneseed build --no-secrets-gate` stands down the credential scan (`rule-gate` on Claude Code and OpenClaude, `tool-gate` on Bob, the OpenCode guard's key-file block and content scan); `--no-done-gate` stands down the done check in `learn` on Stop (and OpenCode's reminder), while learning still runs. Both armed by default; the Rule's text still binds. The install records `Secrets gate: off` / `Done gate: off` beside its pack list; rebuilds, upgrades, `status`, `diff`, `validate` and the console keep them. The console's Constitution page now has a toggle column on every Rule: a switch on Rules I, III and IV, *no gate* on the others.
17
+
18
+ ### Upgrade notes
19
+ - The secrets and done gate switches: run `geneseed rebuild-all` before switching either off. While the machine's hook shim points at an older checkout, a disarmed install's `rule-gate`/`tool-gate` refuses the new flag and lets every write through (External Gate and Persist Insight too, and on Bob every check), and its `learn` refuses `--no-done-gate`, so that install stops learning as well. OpenCode reads both switches when it starts: restart it after flipping one.
20
+
21
+ ### Changed
22
+ - **The console calls the laws Dictates**, the name AGENT.md gives them, instead of Invariants: the Laws tab and band, the dashboard KPI tile, the constitution map, Greenhouse and the Operator HUD. Routes and internal keys are unchanged (#321).
23
+
24
+ ## [4.8.0] — 2026-10-18
25
+
26
+ Minor: Deletion Is Deliberate asks only on real data loss, and can be switched off per install.
27
+
28
+ ### Added
29
+ - **An on/off switch for the Deletion Is Deliberate gate.** `geneseed build --no-deletion-gate` (or the switch on Rule IV's row of the console's Constitution page, which then reads *Gate off*) stops the real-loss check from asking on that install: `git-gate`/`tool-gate` are wired with `--no-deletion-gate` on Claude Code, OpenClaude and Bob, the OpenCode guard plugin stops blocking real loss, and OpenCode's `permission.bash` drops its Law IV keys. Armed by default. The Rule's text still binds, and Sealed Secrets, the root-scan check, Consent Before Push, External Gate and the OpenCode guard's catastrophic-command block stay. The install records `Deletion gate: off` beside its pack list; rebuilds and upgrades keep it, `geneseed status` shows it, and `doctor --probe` checks a disarmed gate on a root-wide scan instead. On Claude Code, OpenClaude and Bob a removal outside a repository, `rm -rf /` included, is caught only by the real-loss check, so it no longer asks there while the gate is off.
30
+
31
+ ### Upgrade notes
32
+ - Run `geneseed rebuild-all` once after updating, before switching the deletion gate off: an older hook shim given the new flag silently drops the whole gate call.
33
+ - The deletion gate switch: an existing `opencode.json` keeps its Law IV `permission.bash` keys when the gate is switched off (the build warns and names them); delete them by hand. OpenCode reads the switch when it starts, so restart it after flipping. While the machine's hook shim points at an older checkout, a disarmed install's gate verb refuses the new flag and lets every call through — the root-scan and consent checks too, and on Bob Sealed Secrets and External Gate — until `geneseed rebuild-all` points the shim at this version.
34
+ - OpenCode: as with the 4.7.0 worktree keys, an existing `opencode.json` keeps the state-dependent Law IV keys the calibration below removed from `permission.bash` — the force-push forms (`git push --force*`, `git push -f*`, `git push *--force*`, `git push * -f*`, `git push *+*`), `git reset --hard*`, `git reset * --hard*`, the `git clean` force forms, `git branch -D*` and both `--delete`/`--force` orders, `git checkout -- *`, the `git checkout`/`git switch` force and `--discard-changes` forms, `git stash drop*`, `git stash clear*`, `git push *--delete*` and `git push * :*`. An existing `opencode.json` keeps them and they keep asking; delete them by hand to get the calibrated behaviour. `git reflog expire*` and `git gc --prune*` stay Geneseed's, and new keys ask for a force push to, or a delete of, `main`/`master` by name, so those still ask with the guard plugin off.
35
+ - OpenCode: `rm -rf *` leaves `permission.bash` too. The guard plugin now decides every `rm`: it lets through a removal of tracked, unmodified files and blocks one git cannot restore, and it still blocks `rm -rf /`, `~` and drive roots outright. An existing `opencode.json` keeps its `rm -rf *` key, which keeps asking; Geneseed no longer claims it, so it neither removes nor reports it. Delete it by hand to get the calibrated behaviour. With the guard plugin off (`GENESEED_GUARD=off`) or in `warn` mode, an `rm` no longer meets any prompt.
36
+ - `geneseed doctor --probe` now sends `git reflog expire --expire=now --all` as the call a gate must refuse, instead of `git reset --hard HEAD`, which passes on a clean tree.
37
+
38
+ ### Changed
39
+ - **Deletion Is Deliberate asks only on real data loss.** The gate used to ask on every destructive-looking git form. It now reads the repository's state and asks only when a command would destroy something nothing else holds:
40
+ - deleting or force-pushing `main`, `master` or a remote's default branch, locally or remotely;
41
+ - `branch -D` of a branch whose commits are on no remote and not merged into HEAD;
42
+ - `reset --hard`, `checkout -f`/`-- <paths>`, `switch --discard-changes`, `restore` and `clean -f` over uncommitted or untracked work that actually exists;
43
+ - `stash drop`/`clear` while a stash exists, and `reflog expire` and `gc --prune` always;
44
+ - **new:** removing files (`rm`, `rmdir`, `del`, `erase`, `Remove-Item`/`ri`/`rd`) that git cannot restore — untracked, ignored or modified files, a `.git`, or anything outside a repository.
45
+ - **new, on a protected branch only:** `checkout <paths>` without `--` and `git rm -f` over changed files while `main`, `master` or the default branch is checked out, and `branch -f`/`checkout -B`/`switch -C` moving a protected branch off commits no remote and not the new target holds (asks when it cannot verify). On a feature branch, and for a new branch (`checkout -b`, `switch -c`, `branch x`), these pass.
46
+ The same commands on a clean tree, a pushed or merged branch, a feature-branch force push and an `rm` of tracked, unmodified files pass. When the gate cannot tell — a glob or variable it would have to expand, a `cd` it cannot follow, `sh -c`/`xargs`, git failing or missing — it asks and says it could not verify. To read the state the git gate runs `git` itself (ADR 0002 amended), only once a command already looked destructive; ordinary tool calls are unaffected. On every host: an ask on Claude Code and OpenClaude, exit 2 on Bob, and on OpenCode a block by the guard plugin, which has no ask tier (`GENESEED_GUARD=warn` downgrades it), with `reflog expire`/`gc --prune` and the `main`/`master` force/delete forms also asked by static `permission.bash` globs.
47
+ - **The Deletion Is Deliberate law text says the same.** Both halves of the rule now name real data loss as what needs confirmation — deleting what version control cannot restore (untracked, modified, unpushed or unmerged work), discarding uncommitted changes, deleting or force-moving `main`, `master` or the default branch, pruning the reflog — beside irreversible, financial, privilege-changing and outward-facing acts. They say that what git can restore runs freely: removing tracked clean files, a worktree, a force-push to a feature branch. Force-push is no longer listed as an outward-facing act that always asks.
48
+
11
49
  ## [4.7.0] — 2026-10-17
12
50
 
13
51
  Minor: the Deletion Is Deliberate gate stops asking on worktree removal.
package/README.md CHANGED
@@ -37,7 +37,7 @@ Want to read what it leaves behind before installing anything? [**geneseed-demo*
37
37
 
38
38
  A hand-written instructions file is prose the model may or may not honour, copied into every repo and drifting in each one. Geneseed is a build, and that changes four things:
39
39
 
40
- - **Laws are enforced, not suggested.** A force-push, a `reset --hard`, or a credential written into a tracked file is caught by a hook *before* the tool runs, in the host's own dialect — a prompt on Claude Code and OpenClaude, a hard block on OpenCode, exit 2 on Bob. The gates fail closed, and every catch is one line in a ledger that `geneseed status` counts.
40
+ - **Laws are enforced, not suggested.** A force-push to `main`, a `reset --hard` over uncommitted work, or a credential written into a tracked file is caught by a hook *before* the tool runs, in the host's own dialect — a prompt on Claude Code and OpenClaude, a hard block on OpenCode, exit 2 on Bob. The gates fail closed, and every catch is one line in a ledger that `geneseed status` counts.
41
41
  - **One source, five targets.** Skills are byte-identical on every host; only the wiring differs. Every commit renders all 263 emit configurations, and a second emit into the same tree must change nothing.
42
42
  - **Costs are measured, not guessed.** Zero runtime dependencies. The hook path loads in about 14 ms per tool call. The default *lean* footprint keeps the always-on context small and puts the full rationale one read away — the numbers are in [docs/token-footprint.md](docs/token-footprint.md).
43
43
  - **It follows you.** Install once, globally; every repo inherits it. One `git pull` or `npm install -g geneseed@latest` rebuilds every active install.
package/SHIPPED.md CHANGED
@@ -20,7 +20,7 @@ historical references, not files distributed with the repo.
20
20
  > **On version numbers.** [`harness.config.json`](harness.config.json)'s `version` is a
21
21
  > human-readable label only. The canonical identity of an installed harness is the
22
22
  > **source fingerprint** stamped in `.geneseed-version` (see `geneseed version`), not the
23
- > string. Current label: **4.7.0**.
23
+ > string. Current label: **4.9.0**.
24
24
 
25
25
  ## Capabilities
26
26
 
@@ -18,7 +18,11 @@ absolute-path hooks instead — `geneseed setup` does that for you.) It:
18
18
  half of Doctrine process 5 (*consent before every commit and push*) and of the
19
19
  destructive-git Rule. Drop the `process` pack (or exclude process 5) and the hook stays,
20
20
  wired with `--no-consent`: commits and pushes pass without asking, while the
21
- invariant-territory refusals (force-push, `reset --hard`) stay in every build. The hook inspects
21
+ Law IV refusals (asked only on real data loss: a force-push to `main`, a `reset --hard`
22
+ over uncommitted work, an `rm` git cannot undo) stay in every build unless the install is
23
+ built `--no-deletion-gate`, which appends that flag and stands the real-loss check down
24
+ (`--no-secrets-gate` does the same for `rule-gate`'s credential scan, `--no-done-gate` for
25
+ `learn`'s done check). The hook inspects
22
26
  command and, when it runs a `git commit` or `git push` (bare, flagged, `-C <path>`,
23
27
  or chained like `git add . && git commit … && git push`), returns
24
28
  `permissionDecision: "ask"` so Claude Code **prompts on every such call**. Crucially
@@ -125,7 +125,7 @@ to native **skills**, not slash commands (same `SKILL.md` shape as Claude Code).
125
125
  | plugin on `experimental.session.compacting` | **context plugin** — re-pushes the `eager` docs so they survive compaction |
126
126
  | plugin on `session.idle` | **learn plugin** — distils memory into `memory/` (debounced to fire once, at session end) |
127
127
  | plugin on `experimental.chat.messages.transform` (V1) | **context plugin** — also adds the done gate's reminder when a check in `.geneseed/done-checks.txt` has not passed since the last edit (Verify Before Asserting); OpenCode 2 turns are not judged |
128
- | plugin on `tool.execute.before` | **guard plugin** — blocks secret-file writes and credential-shaped write content (Sealed Secrets), writes to a protected check or the done list (External Gate), catastrophic shell (Deletion Is Deliberate), a recursive whole-filesystem scan (Commands Must Return), the first rule-store write (Persist Insight) and protected wiki folders; logs each block to the gate ledger and counts skill/subagent use (`GENESEED_GUARD=off\|warn`) |
128
+ | plugin on `tool.execute.before` | **guard plugin** — blocks secret-file writes and credential-shaped write content (Sealed Secrets), writes to a protected check or the done list (External Gate), catastrophic shell and every other real data loss, judged against the repository's state (Deletion Is Deliberate), a recursive whole-filesystem scan (Commands Must Return), the first rule-store write (Persist Insight) and protected wiki folders; logs each block to the gate ledger and counts skill/subagent use (`GENESEED_GUARD=off\|warn`) |
129
129
  | plugin registering a custom tool | **workflow plugin** — the `workflow` tool runs saved orchestration scripts from `workflows/` |
130
130
  | `context.json` / `.harness/` (manifest) | *optional* override when discovery doesn't fit |
131
131
 
@@ -384,9 +384,10 @@ behaviour** — nothing changes the machine's current agent/model unless you opt
384
384
  `2026-06-17-opencode-color-themes` spec).
385
385
  - **Runtime guard plugin** (`geneseed-guard.js`, installed with the others). Enforces
386
386
  the safety Laws at the tool boundary: **blocks** writes to private-key/credential
387
- files (Sealed Secrets), catastrophic shell like `rm -rf /` (Deletion Is Deliberate), and any mutation under
387
+ files (Sealed Secrets), catastrophic shell like `rm -rf /` and every command that would really lose data, judged
388
+ against the repository's state (Deletion Is Deliberate), and any mutation under
388
389
  a declared wiki's `protected` folders (AGENT.md §8, from `geneseed-wiki.jsonc`); **warns** on
389
- `.env` writes and force-push. `GENESEED_GUARD=off` disables it, `=warn` downgrades
390
+ `.env` writes. `GENESEED_GUARD=off` disables it, `=warn` downgrades
390
391
  blocks to warnings. Every **block** appends one line to the install's
391
392
  `notebook/gates.jsonl` (rule `law-1` / `law-4` / `process-1` / `wiki`, a timestamp,
392
393
  never the path or command) — the same ledger the Claude-family hooks write, so
@@ -398,8 +399,8 @@ behaviour** — nothing changes the machine's current agent/model unless you opt
398
399
  auto-falls back to the visible `session.created` message. `GENESEED_CONTEXT_VISIBLE=1`
399
400
  forces the visible block (legacy `GENESEED_CONTEXT_TRANSFORM=0/off` does the same).
400
401
  - **Default permissions.** A fresh `opencode.json` gets a minimal policy that **asks**
401
- before `rm -rf *` and a force-push — those are **Rule IV**'s territory and are in every
402
- build — and, **on an install that built the `process` pack in**, before every
402
+ before `git reflog expire`, `git gc --prune` and a force-push or delete of `main`/`master` —
403
+ **Rule IV**'s territory, in every build; the guard plugin decides every other data loss — and, **on an install that built the `process` pack in**, before every
403
404
  `git commit` and `git push` (the host-level backstop for **Consent Before Push**: the
404
405
  agent never records or shares code unprompted, on any branch). Build with
405
406
  `--doctrines` leaving `process` out and those two entries are simply not written.
@@ -895,6 +895,21 @@ const DONE_LIST = ".geneseed/done-checks.txt"
895
895
  const DONE_EDIT_TOOLS = new Set(["edit", "write", "patch", "apply_patch", "multiedit"])
896
896
  const DONE_SHELL_TOOLS = new Set(["bash", "shell"])
897
897
 
898
+ // Law III's gate switch (`--no-done-gate`, user decision 2026-10-18): the build writes
899
+ // `Done gate: off` into the install's AGENT.md only when the gate is off. Read ONCE, at the first
900
+ // use, from the harness dir (a switch flip takes an OpenCode restart); anything unreadable reads
901
+ // as ARMED. Off silences the reminder and the V2 notice, nothing else.
902
+ let doneGateOffOnce = null
903
+ function doneGateOff() {
904
+ doneGateOffOnce ??= (async () => {
905
+ const dir = await harnessDir()
906
+ if (!dir) return false
907
+ const text = await fs.readFile(path.join(dir, "AGENT.md"), "utf8")
908
+ return /^Done gate:[ \t]*off[ \t]*\r?$/m.test(text)
909
+ })().catch(() => false)
910
+ return doneGateOffOnce
911
+ }
912
+
898
913
  async function readDoneChecks(root) {
899
914
  try {
900
915
  const text = await fs.readFile(path.join(root, DONE_LIST), "utf8")
@@ -1140,6 +1155,7 @@ export const GeneseedContext = async (ctx) => {
1140
1155
  // INJECT_OFF and the visible delivery leave it on, and no early return below can skip it.
1141
1156
  // Its own function, so its early returns end the reminder, never the context injection.
1142
1157
  await (async () => {
1158
+ if (await doneGateOff()) return
1143
1159
  const checks = await readDoneChecks(root)
1144
1160
  if (!checks.length) return
1145
1161
  const owed = doneOwed(output.messages, checks, root)
@@ -1211,7 +1227,7 @@ async function setupV2(ctx) {
1211
1227
  })
1212
1228
 
1213
1229
  // The done gate is not judged on OpenCode 2 (see `doneOwed`): said once, where its log goes.
1214
- if ((await readDoneChecks(root).catch(() => [])).length) {
1230
+ if (!(await doneGateOff()) && (await readDoneChecks(root).catch(() => [])).length) {
1215
1231
  console.error(`[geneseed-context] done gate: ${DONE_LIST} found, but OpenCode 2 turns are not judged.`)
1216
1232
  }
1217
1233