geneseed 3.6.0 → 3.8.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 (88) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/DESIGN.md +6 -6
  3. package/QUICKSTART.md +1 -1
  4. package/README.md +33 -31
  5. package/SETUP.md +36 -10
  6. package/SHIPPED.md +3 -3
  7. package/adapters/claude-code/skills/geneseed/SKILL.md +1 -1
  8. package/adapters/openclaude/README.md +64 -0
  9. package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
  10. package/adapters/opencode/workflows/_runtime.js +1 -1
  11. package/docs/extending.md +18 -6
  12. package/docs/web/exclusions.md +1 -0
  13. package/docs/web/footprint.md +1 -1
  14. package/docs/web/harnesses.md +14 -14
  15. package/docs/web/install-quick.md +1 -1
  16. package/docs/web/rules.md +1 -1
  17. package/docs/web/themes-author.md +1 -1
  18. package/docs/web-ui.md +1 -1
  19. package/harness.config.json +1 -1
  20. package/js/README.md +3 -3
  21. package/js/build/driver.mjs +64 -14
  22. package/js/build/emit-claude.mjs +1 -1
  23. package/js/build/generate.mjs +92 -42
  24. package/js/build/source.mjs +5 -4
  25. package/js/cli-table.json +151 -2
  26. package/js/hosts/hooks.mjs +11 -4
  27. package/js/hosts/hosts.mjs +17 -2
  28. package/js/hosts/installs.mjs +32 -2
  29. package/js/hosts/mcp.mjs +8 -1
  30. package/js/hosts/settings.mjs +4 -1
  31. package/js/inspect/diff.mjs +1 -1
  32. package/js/inspect/doctor.mjs +3 -3
  33. package/js/inspect/excludes.mjs +7 -5
  34. package/js/inspect/status.mjs +44 -8
  35. package/js/maintain/migrate.mjs +1 -1
  36. package/js/maintain/setup.mjs +7 -2
  37. package/js/maintain/uninstall.mjs +3 -3
  38. package/js/web/actions.mjs +7 -5
  39. package/js/web/api.mjs +2 -2
  40. package/js/web/docs.mjs +1 -1
  41. package/package.json +2 -1
  42. package/src/doctrines/README.md +3 -2
  43. package/src/doctrines/comms.md +53 -0
  44. package/src/doctrines/process.md +0 -26
  45. package/src/skills/geneseed.md +9 -8
  46. package/src/skills/parallel-agents.md +1 -1
  47. package/src/skills/pipeline.md +1 -1
  48. package/src/skills/token-report/SKILL.md +2 -1
  49. package/src/skills/token-report/VENDOR.md +3 -3
  50. package/src/skills/token-report/scripts/token_report.mjs +14 -7
  51. package/src/skills/workflow.md +2 -2
  52. package/themes/_TEMPLATE.json +5 -3
  53. package/themes/biker.json +5 -3
  54. package/themes/commentator.json +5 -3
  55. package/themes/cyberpunk.json +5 -3
  56. package/themes/gamer.json +5 -3
  57. package/themes/imperial.json +5 -3
  58. package/themes/joker.json +5 -3
  59. package/themes/marvin.json +5 -3
  60. package/themes/mean.json +5 -3
  61. package/themes/military.json +5 -3
  62. package/themes/neutral.json +5 -3
  63. package/themes/pirate.json +5 -3
  64. package/themes/sports.json +5 -3
  65. package/themes/verstappen.json +5 -3
  66. package/themes/wizard.json +5 -3
  67. package/web/dist/assets/{Activity-Flgf4kHy.js → Activity-CH-7cPIM.js} +1 -1
  68. package/web/dist/assets/{ActivityDetail-DIpr85nh.js → ActivityDetail-DZvz0xS5.js} +1 -1
  69. package/web/dist/assets/{CatalogRow-CjyLjaLb.js → CatalogRow-DftXpZHY.js} +1 -1
  70. package/web/dist/assets/{Diff-Deisdha8.js → Diff-DvwdG0L9.js} +1 -1
  71. package/web/dist/assets/{Docs-DGUdY4l4.js → Docs-DrJNDPwI.js} +1 -1
  72. package/web/dist/assets/{Doctor-F74lJ69P.js → Doctor-OTmwmYZg.js} +1 -1
  73. package/web/dist/assets/{GreenhouseView-D072lBiD.js → GreenhouseView-XxrAdriX.js} +1 -1
  74. package/web/dist/assets/Harness-C_LYNk5c.js +1 -0
  75. package/web/dist/assets/{JournalView-DWFqZ0Iw.js → JournalView-BcuyeEaR.js} +1 -1
  76. package/web/dist/assets/{Laws-BN6g3HU7.js → Laws-CWHrTKY4.js} +1 -1
  77. package/web/dist/assets/{Library-CDOv4emM.js → Library-CA9IHghn.js} +1 -1
  78. package/web/dist/assets/{OperatorHudView-CwNIUVX8.js → OperatorHudView-BMRY1Tt4.js} +1 -1
  79. package/web/dist/assets/{Profile-CpP9BTne.js → Profile-wceET1JY.js} +1 -1
  80. package/web/dist/assets/{Rules-DFXsUCB7.js → Rules-BEXvY4Tx.js} +1 -1
  81. package/web/dist/assets/{Settings-BqZVUtJ5.js → Settings-DS2zF0Ez.js} +1 -1
  82. package/web/dist/assets/{Skills-Cu6kBgfF.js → Skills-DahlOJuI.js} +1 -1
  83. package/web/dist/assets/{drawable-BgfrEBer.js → drawable-BTHjTzF7.js} +1 -1
  84. package/web/dist/assets/{index-CAqrWhKl.js → index-AN5Emnrg.js} +2 -2
  85. package/web/dist/assets/{lawCats-BLgxl151.js → lawCats-Btjp3Hk_.js} +1 -1
  86. package/web/dist/index.html +1 -1
  87. package/web/src/pages/Laws.jsx +6 -2
  88. package/web/dist/assets/Harness-DNRP2Va7.js +0 -1
@@ -14,6 +14,7 @@ Exclusion is all-or-nothing per folder — it doesn't trim which Rules apply or
14
14
  ### How it's wired, per host
15
15
 
16
16
  - **Claude Code** — native suppression via `claudeMdExcludes` in the repo's `.claude/settings.local.json`.
17
+ - **OpenClaude** — the same `claudeMdExcludes`, in the repo's `.openclaude/settings.local.json`.
17
18
  - **Bob** — a shadow `rules/geneseed.md` stub that shadows the global one (never overwrites a hand-written stub already there).
18
19
  - **OpenCode** — the hook entry's sovereign-bypass guard and its twin in the plugins short-circuit at session start.
19
20
  - **Copilot — documented limitation.** GitHub Copilot has no native per-repo suppression hook: the global `copilot-instructions.md` still loads even inside an excluded folder.
@@ -26,7 +26,7 @@ Neither setting hides text from the agent; they hide it from *every turn*.
26
26
 
27
27
  ### How to set it
28
28
 
29
- It's set-and-forget — stored in the `.geneseed-footprint` marker, preserved across every rebuild, identical on every host (OpenCode, Claude Code, Bob, Copilot). Changing it re-emits the install.
29
+ It's set-and-forget — stored in the `.geneseed-footprint` marker, preserved across every rebuild, identical on every host (OpenCode, Claude Code, Bob, Copilot, OpenClaude). Changing it re-emits the install.
30
30
 
31
31
  - **Settings** — the **Footprint** toggle flips the current install (full ⇄ lean) and rebuilds it in place.
32
32
  - **Harness tab** — on an active install, a per-harness dropdown sets it for that install independently, then **Apply**. On a host that is not installed yet, it is step 2 of **Install…**.
@@ -4,26 +4,26 @@ order: 4
4
4
  title: "Supported harnesses"
5
5
  kind: "concept"
6
6
  ---
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.
7
+ One source, **six 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**, **Copilot**, and **OpenClaude** share one Claude-shaped engine that diverges only by host dialect.
8
8
 
9
- The harness *content* — the constitution (Ethos, 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.
9
+ The harness *content* — the constitution (Ethos, Rules, Doctrines), Agents, Skills, Memory, and the preamble voice — is **identical on every host**: one build renders the same text for all six targets. What differs is how much of it the host can *automate* (via plugins or hooks) versus carry as preamble discipline.
10
10
 
11
- | Capability | OpenCode | Claude | Bob | Copilot |
12
- | --- | :---: | :---: | :---: | :---: |
13
- | Agents · Skills · Memory | ✅ | ✅ | ✅ | ✅ |
14
- | Context injection | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
15
- | Memory write-back (learn) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
16
- | Git-gate consent (Doctrine process 5) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble |
17
- | Sovereign-repo excludes | ⚙️ plugin | ✅ | ✅ | ➖ |
18
- | MCP server wiring | ✅ | ✅ | ✅ | ✅ |
19
- | Colour themes | ✅ | ➖ | ➖ | ➖ |
20
- | LSP · workflows · primary-agent · `/`-commands | ✅ | ➖ | ➖ | ➖ |
11
+ | Capability | OpenCode | Claude | Bob | Copilot | OpenClaude |
12
+ | --- | :---: | :---: | :---: | :---: | :---: |
13
+ | Agents · Skills · Memory | ✅ | ✅ | ✅ | ✅ | ✅ |
14
+ | Context injection | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble | 🪝 hook² |
15
+ | Memory write-back (learn) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble | 🪝 hook² |
16
+ | Git-gate consent (Doctrine process 5) | ⚙️ plugin | 🪝 hook | 🪝 hook¹ | 📄 preamble | 🪝 hook² |
17
+ | Sovereign-repo excludes | ⚙️ plugin | ✅ | ✅ | ➖ | ✅ |
18
+ | MCP server wiring | ✅ | ✅ | ✅ | ✅ | ✅ |
19
+ | Colour themes | ✅ | ➖ | ➖ | ➖ | ➖ |
20
+ | LSP · workflows · primary-agent · `/`-commands | ✅ | ➖ | ➖ | ➖ | ➖ |
21
21
 
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.
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. ² OpenClaude is a Claude Code fork: Claude's hooks verbatim, under `~/.openclaude` or a repo's `.openclaude/`.
23
23
 
24
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
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.
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/OpenClaude'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.
27
27
 
28
28
  ---
29
29
 
@@ -18,7 +18,7 @@ To keep the command around: `npm install -g geneseed`, then plain `geneseed …`
18
18
 
19
19
  ### 2. Open your agent
20
20
 
21
- Open OpenCode (or Claude Code, Bob, Copilot, or any `AGENT.md`-aware tool) in any repo. The first reply opens with the readiness sigil (`✅` neutral / `🧬` imperial / your theme's equivalent) and your project's docs are already in context.
21
+ Open OpenCode (or Claude Code, Bob, Copilot, OpenClaude, or any `AGENT.md`-aware tool) in any repo. The first reply opens with the readiness sigil (`✅` neutral / `🧬` imperial / your theme's equivalent) and your project's docs are already in context.
22
22
 
23
23
  ### Prefer a clone?
24
24
 
package/docs/web/rules.md CHANGED
@@ -17,7 +17,7 @@ The agent's standing rules come in **three tiers**, and the tier decides how a r
17
17
 
18
18
  ### Doctrines — chosen at build time
19
19
 
20
- Practices rather than principles: **craft** (how code is written), **rigor** (how work is proven), **ops** (how the machine is operated), **process** (how a task is run), one file each under `src/doctrines/`. A repository picks its set with `geneseed-build --doctrines <list>` or through the setup wizard — all {N_PACKS} are on by default. This install built in {N_PACKS_ACTIVE}, carrying {N_DOCTRINE_RULES} rules between them. Cited by pack and number — `Doctrine process 5`.
20
+ Practices rather than principles: **craft** (how code is written), **rigor** (how work is proven), **ops** (how the machine is operated), **process** (how a task is run), **comms** (how answers are presented), one file each under `src/doctrines/`. A repository picks its set with `geneseed-build --doctrines <list>` or through the setup wizard — all {N_PACKS} are on by default. This install built in {N_PACKS_ACTIVE}, carrying {N_DOCTRINE_RULES} rules between them. Cited by pack and number — `Doctrine process 5`.
21
21
 
22
22
  A pack you leave out **still ships in the bundle**: every pack file lands under `doctrines/` beside `AGENT.md` whether or not it was built in. So a rule that cites one in an inactive pack still resolves on disk, and you can read the alternatives before turning one on. What an inactive pack loses is bindingness, not availability.
23
23
 
@@ -13,7 +13,7 @@ Every rule in the constitution gets its title from a theme key, and a theme that
13
13
 
14
14
  - `LEX_I`…`LEX_IX` — one title per invariant. `doctor` holds this to an **equality**, not mere presence: a `LEX_*` outside `I`–`IX` is a title for a rule that does not exist and fails the check too.
15
15
  - `DOC_*` — one title per doctrine rule, named after its address (`DOC_CRAFT_1`, `DOC_PROCESS_5`, …). `doctor` reads the pack files under `src/doctrines/` and requires exactly the set they name — a missing one is a blank heading, an extra one is a dead key.
16
- - `PACK_*` — one name per pack (`PACK_CRAFT`, `PACK_RIGOR`, `PACK_OPS`, `PACK_PROCESS`), used for the pack's own sub-heading.
16
+ - `PACK_*` — one name per pack (`PACK_CRAFT`, `PACK_RIGOR`, `PACK_OPS`, `PACK_PROCESS`, `PACK_COMMS`), used for the pack's own sub-heading.
17
17
 
18
18
  The Ethos section names are **not** theme keys: a citation reads `({{ONTOLOGY}}: {{ONT_TELOS}})`, token on both sides, so heading and reference always move together. They live in the build's fixed structure table and you neither declare nor rename them.
19
19
 
package/docs/web-ui.md CHANGED
@@ -120,7 +120,7 @@ streams the output of background jobs and keeps their history across reloads.
120
120
  - **Docs** (`#/docs`) — rendered documentation: markdown pages, concept pages, a CLI
121
121
  reference (generated from the harness argument parser), and a glossary, grouped into
122
122
  Get started / Core concepts / How-to / MCP servers / Plugins / Reference / Deeper. Filtered
123
- for one host family (OpenCode, or Claude Code — which Bob and Copilot share); with no
123
+ for one host family (OpenCode, or Claude Code — which Bob, Copilot and OpenClaude share); with no
124
124
  choice stored it follows the deployed install.
125
125
 
126
126
  ### 🩺 Care
@@ -2,5 +2,5 @@
2
2
  "_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
3
3
  "theme": "neutral",
4
4
  "product": "Geneseed",
5
- "version": "3.6.0"
5
+ "version": "3.8.0"
6
6
  }
package/js/README.md CHANGED
@@ -40,12 +40,12 @@ as a per-host install at project or global scope.
40
40
  | `source.mjs` | Where the SOURCE is: `ROOT`/`SRC`/`THEMES`/`CONFIG`, `PACK_ORDER`, rule ids, the cfg object |
41
41
  | `render.mjs` | The pure text pipeline: theme load, `INCLUDE` inlining, CATALOG blocks, `{{TOKENS}}`, dest paths |
42
42
  | `bundle.mjs` | `build` — render a theme into a bundle dir, plus the source-completeness refusal. Sits UNDER both emits |
43
- | `emit-claude.mjs` | The Claude/Bob/Copilot emit — the render half plus the settings + CLAUDE.md managed-block wire |
43
+ | `emit-claude.mjs` | The Claude/Bob/Copilot/OpenClaude emit — the render half plus the settings + CLAUDE.md managed-block wire |
44
44
  | `emit-opencode.mjs` | The OpenCode emits — a project's `.opencode/` and the global config dir. Deliberate twin of the above |
45
45
  | `emit-common.mjs` | Constants, tree walkers, and the three writers BOTH global emits share. Decides no host |
46
46
  | `stubs.mjs` | Write-once seed files: context, wiki, rules, excludes, profile, `.gitignore`, memory/notebook indexes |
47
47
  | `version.mjs` | The release marker: fingerprint the sources, read/compare/write an install's version, warn on downgrade |
48
- | `driver.mjs` | The generator driver: `--emit`/`--theme`/`--footprint` flag parsing, the nine emit targets, per-host emit orchestration, the prune and the manifest. `bin/build-driver.mjs` is only its entry |
48
+ | `driver.mjs` | The generator driver: `--emit`/`--theme`/`--footprint` flag parsing, the eleven emit targets, per-host emit orchestration, the prune and the manifest. `bin/build-driver.mjs` is only its entry |
49
49
  | `generate.mjs` | CLI verbs `build`, `prompt`, `theme`, `rebuild-all` — the thin face over `driver.mjs` |
50
50
  | `catalog.mjs` | `geneseed catalog` — prints the shipped roster. Classifies nothing itself |
51
51
  | `themes.mjs` | Maintainer-only `--sync-themes`: inserts `_TEMPLATE.json`'s missing keys into the committed themes |
@@ -66,7 +66,7 @@ user co-owns, and the hook verbs those emitted configs then run.
66
66
 
67
67
  | module | owns |
68
68
  |---|---|
69
- | `hosts.mjs` | The four host config dirs (opencode/claude/bob/copilot), plus `resolvePath`/`expanduser` |
69
+ | `hosts.mjs` | The five host config dirs (opencode/claude/bob/copilot/openclaude), plus `resolvePath`/`expanduser` |
70
70
  | `hooks.mjs` | The verbs the emitted hooks run every session: `context`, `git-gate`, `rule-gate`, `tool-gate` (the two gates fused, for Copilot), `learn`. `--host` picks the verdict dialect |
71
71
  | `settings.mjs` | Merges into user-owned `settings.json`/`opencode.json`; owns the hook shim and the managed blocks |
72
72
  | `native.mjs` | Capability specs → host-native subagents and skills. The impure half of the emit |
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The generator driver — flag parsing, the nine emit targets, and the per-host emit orchestration.
2
+ * The generator driver — flag parsing, the eleven emit targets, and the per-host emit orchestration.
3
3
  * `bin/build-driver.mjs` (`geneseed-build`) is only its entry; this module is what the CLI verbs
4
4
  * (`build`, `setup`, `migrate`, `doctor`, `diff`, `validate`, the web console) import. It lived in
5
5
  * `bin/` until 2026-09, which made nine `js/` modules import upward from a binary.
@@ -51,7 +51,7 @@ import { parseJson, jsonDumpsIndent } from '../lib/json.mjs';
51
51
  // that should exist twice. golden.py's 259 cells are what made the move safe to attempt.
52
52
  import {
53
53
  GLOBAL_MANIFEST, resolvePath, opencodeConfigDir, claudeConfigDir, bobConfigDir,
54
- copilotConfigDir, hostCatalogsNatively,
54
+ copilotConfigDir, openclaudeConfigDir, hostCatalogsNatively,
55
55
  } from '../hosts/hosts.mjs';
56
56
 
57
57
  // P5d moved these out of this file for the reason P5c moved the host resolvers: `harness
@@ -71,9 +71,9 @@ import { registryRecord, registryRoots } from '../inspect/registry.mjs';
71
71
  // maintainer pair that needs nothing this driver is banned from having.
72
72
  import { syncThemes } from './themes.mjs';
73
73
 
74
- /** The nine `--emit` choices, in build.py:337-338's order. */
74
+ /** The eleven `--emit` choices: build.py:337-338's nine, then OpenClaude's pair. */
75
75
  const EMITS = ['files', 'opencode', 'opencode-global', 'claude', 'claude-global',
76
- 'bob', 'bob-global', 'copilot', 'copilot-global'];
76
+ 'bob', 'bob-global', 'copilot', 'copilot-global', 'openclaude', 'openclaude-global'];
77
77
 
78
78
  /** `_build_emit.PRIMARY_AGENT_SRC`. */
79
79
  const PRIMARY_AGENT_SRC = path.join(ROOT, 'adapters', 'opencode', 'agents', 'orchestrator.md');
@@ -166,7 +166,7 @@ const VALUED = {
166
166
  '--theme': 'theme', '--posture': 'posture', '--mode': 'mode',
167
167
  '--doctrines': 'doctrines', '--exclude-rules': 'excludeRules',
168
168
  '--out': 'out', '--target': 'out', '--emit': 'emit',
169
- '--footprint': 'footprint', '--root': 'root',
169
+ '--footprint': 'footprint', '--root': 'root', '--config-dir': 'cfgDir',
170
170
  };
171
171
  const FLAGS = {
172
172
  '--sync-themes': 'syncThemes', '--validate-only': 'validateOnly',
@@ -509,9 +509,11 @@ export function hookRunnerEntry() {
509
509
  * POSIX spelling, per the original: `claudeMdExcludes` entries are glob patterns, where a
510
510
  * backslash is an escape, so the Windows-native form risks never matching.
511
511
  */
512
- function preambleExclude(claudeMd) {
512
+ function preambleExclude(claudeMd, host) {
513
513
  if (path.basename(claudeMd) !== 'CLAUDE.md') return null;
514
- return resolvePath(path.join(claudeConfigDir(), 'CLAUDE.md')).split(path.sep).join('/');
514
+ // OpenClaude inherited `claudeMdExcludes` from Claude Code; its global preamble is its own.
515
+ const globalDir = host === 'openclaude' ? openclaudeConfigDir() : claudeConfigDir();
516
+ return resolvePath(path.join(globalDir, 'CLAUDE.md')).split(path.sep).join('/');
515
517
  }
516
518
 
517
519
  /**
@@ -808,7 +810,7 @@ function emitClaudeCore(cfg, args, { cfgDir, claudeMd, scope, host, out, hookOpt
808
810
  theme: args.theme, cfgDir, claudeMd, scope, host,
809
811
  out: out === null ? null : out,
810
812
  footprint: args.footprint, nativeCatalog: hostCatalogsNatively(host),
811
- oldOwned, oldManaged, preambleExclude: preambleExclude(claudeMd),
813
+ oldOwned, oldManaged, preambleExclude: preambleExclude(claudeMd, host),
812
814
  ...(hookOpts ? { hookOpts } : {}),
813
815
  });
814
816
  const { owned, stats, memStatus, nbStatus, managed } = rendered;
@@ -930,6 +932,42 @@ function emitClaude(cfg, args, out) {
930
932
  + `settings.json), ${r.memStatus}, ${r.nbStatus}.\n`);
931
933
  }
932
934
 
935
+ /**
936
+ * OpenClaude global — `~/.openclaude` (or `$OPENCLAUDE_CONFIG_DIR`). The Claude emit with
937
+ * another config dir: OpenClaude is a Claude Code fork that reads neither `~/.claude` nor
938
+ * `CLAUDE_CONFIG_DIR`, so a Claude install is invisible to it and it needs its own.
939
+ */
940
+ function emitOpenclaudeGlobal(cfg, args, out) {
941
+ const cfgDir = args.cfgDir ?? openclaudeConfigDir();
942
+ const hookOpts = hookRunnerEntry();
943
+ const r = emitClaudeCore(cfg, args, {
944
+ cfgDir, claudeMd: path.join(cfgDir, 'CLAUDE.md'), scope: 'global', host: 'openclaude', out,
945
+ hookOpts,
946
+ });
947
+ process.stdout.write(`[geneseed] openclaude-global -> ${cfgDir}: ${r.nAgents} subagents, `
948
+ + `${r.nSkills} skills, CLAUDE.md, ${r.nHooks} hook group(s), settings.json, `
949
+ + `${r.memStatus}, ${r.nbStatus}. MCP servers go in .openclaude.json.\n`);
950
+ return cfgDir;
951
+ }
952
+
953
+ /**
954
+ * OpenClaude per-repo — everything under `.openclaude/`, the preamble included. OpenClaude
955
+ * reads the root CLAUDE.md only when the repo has no AGENTS.md, but `.openclaude/CLAUDE.md`
956
+ * always; keeping the root untouched also lets a Claude Code install share the repo.
957
+ */
958
+ function emitOpenclaude(cfg, args, out) {
959
+ const root = args.root ? resolveOut(args.root) : out;
960
+ const cfgDir = path.join(root, '.openclaude');
961
+ const hookOpts = hookRunnerEntry();
962
+ const r = emitClaudeCore(cfg, args, {
963
+ cfgDir, claudeMd: path.join(cfgDir, 'CLAUDE.md'), scope: 'project', host: 'openclaude', out,
964
+ hookOpts,
965
+ });
966
+ process.stdout.write(`[geneseed] openclaude (folder) -> ${root}: .openclaude/ `
967
+ + `(CLAUDE.md, ${r.nAgents} subagents, ${r.nSkills} skills, ${r.nHooks} hook group(s), `
968
+ + `settings.local.json), ${r.memStatus}, ${r.nbStatus}.\n`);
969
+ }
970
+
933
971
  /**
934
972
  * `_build_global.emit_bob_global` — into Bob's global config dir (~/.bob).
935
973
  *
@@ -994,10 +1032,10 @@ function writeMarkers(markerDir, emit, footprint) {
994
1032
  * compares it file by file. The Python spells that `HOSTS[host]["emit_global"](theme,
995
1033
  * out=..., cfg=<tmp>, footprint=...)`, where `cfg` overrides the target the emit would
996
1034
  * otherwise resolve. This is that call, and the override is threaded as `args.cfgDir` through
997
- * the four `emit*Global` bodies rather than added to `parseArgs`: `build.py` has no
998
- * `--cfg-dir` flag, and a flag on one driver and not the other is a divergence
999
- * `tests/golden.py` compares 259 times and could never see, because it drives both from the
1000
- * same argv.
1035
+ * the `emit*Global` bodies. The same key is also `--config-dir` on the command line: a global
1036
+ * emit never writes to `--out` (there `out` is the legacy bundle a memory store migrates FROM,
1037
+ * and `update` passes it for exactly that), so without the flag there was no way to preview a
1038
+ * global install anywhere but over the live one.
1001
1039
  *
1002
1040
  * WHICH SIDE OF THE BOUNDARY THIS SITS ON. P3c's rule was that a render CHILD must never
1003
1041
  * resolve a config dir, because a child that did would write 135 files into the developer's
@@ -1012,6 +1050,7 @@ const GLOBAL_EMITS = {
1012
1050
  claude: emitClaudeGlobal,
1013
1051
  bob: emitBobGlobal,
1014
1052
  copilot: emitCopilotGlobal,
1053
+ openclaude: emitOpenclaudeGlobal,
1015
1054
  };
1016
1055
 
1017
1056
  /**
@@ -1033,7 +1072,7 @@ const GLOBAL_EMITS = {
1033
1072
  * three-positional call leaves in place, inherited here rather than re-decided.
1034
1073
  */
1035
1074
  const PROJECT_EMITS = {
1036
- claude: emitClaude, bob: emitBob, copilot: emitCopilot,
1075
+ claude: emitClaude, bob: emitBob, copilot: emitCopilot, openclaude: emitOpenclaude,
1037
1076
  // P2. `opencode` joins the three for `cmdValidate`, which has to be able to render EVERY
1038
1077
  // `--emit` choice into its sandbox and not only the three doctor already scans. Its call
1039
1078
  // shape is `emitClaude`'s exactly — `(cfg, args, out)` — so the row is the whole change.
@@ -1148,6 +1187,15 @@ function run(argv) {
1148
1187
  }
1149
1188
 
1150
1189
  const out = resolveOut(args.out);
1190
+ // Refused rather than ignored on a per-repo emit: an ignored target flag is how a preview
1191
+ // lands on a live install, which is the bug this flag exists to close.
1192
+ if (args.cfgDir !== undefined) {
1193
+ if (!args.emit.endsWith('-global')) {
1194
+ die(2, `argument --config-dir: only applies to a -global emit (got --emit ${args.emit}); `
1195
+ + 'a per-repo emit writes under --out/--root');
1196
+ }
1197
+ args.cfgDir = resolveOut(args.cfgDir);
1198
+ }
1151
1199
  const cfg = makeCfg(args);
1152
1200
 
1153
1201
  // The marker directory is the emit's TARGET, which for a global emit is the config dir it
@@ -1186,6 +1234,8 @@ function run(argv) {
1186
1234
  // An ALLOW-LIST, not "everything that is not global": a plain `--emit files` dev build —
1187
1235
  // the default — must never pollute the registry, and only the four per-repo host emits
1188
1236
  // are ones `_EMIT_HOST_SCOPE` can map back to a row. Records `out`, where the marker is.
1189
- if (['opencode', 'claude', 'bob', 'copilot'].includes(args.emit)) registryRecord(markerDir);
1237
+ if (['opencode', 'claude', 'bob', 'copilot', 'openclaude'].includes(args.emit)) {
1238
+ registryRecord(markerDir);
1239
+ }
1190
1240
  return 0;
1191
1241
  }
@@ -126,7 +126,7 @@ export function emitClaudeRender(cfg, job) {
126
126
  // settings.local.json; Bob documents no local variant, so its settings.json is ignored.
127
127
  if (scope === 'project') {
128
128
  const gi = path.join(cfgDir, '.gitignore');
129
- const giLines = (host === 'claude' ? ['settings.local.json']
129
+ const giLines = (host === 'claude' || host === 'openclaude' ? ['settings.local.json']
130
130
  : host === 'bob' ? ['settings.json'] : [])
131
131
  .concat(['wiki.jsonc', 'agent-overrides.json']);
132
132
  if (!existsSync(gi)) {
@@ -80,12 +80,28 @@ function sysExit(msg) {
80
80
  * Calling the driver in-process keeps that: `resolveOut` resolves against `process.cwd()`
81
81
  * the same way. `build/the-bundle-follows-cwd-not-the-checkout` is the cell.
82
82
  *
83
- * Every other flag is the generator's own default, read from `harness.config.json` by the
84
- * driver — `harness build` forwards `--theme` and nothing else, and its own `--theme`
85
- * carries no `choices`, so an unknown one is refused by the generator rather than here.
83
+ * `geneseed build` used to forward `--theme` alone, which made `setup`'s own off-TTY hint
84
+ * (`geneseed build --emit … --theme …`) a usage error and left every other axis reachable
85
+ * only through the undocumented `geneseed-build`. Values are forwarded unchecked: the driver
86
+ * owns the choices, so the table does not repeat them.
87
+ *
88
+ * `--validate-only` is NOT one of these: it lives on `geneseed validate` only — that verb's
89
+ * source-tree half is the doctor, which starts a process, and this command's whole closure
90
+ * (`bin/geneseed-cli.mjs` → here → `driverMain`) is under the same transitive `child_process`
91
+ * ban as `bin/build-driver.mjs` itself. `driver.mjs`'s `run()` refuses the flag unconditionally
92
+ * for exactly that reason.
86
93
  */
94
+ const BUILD_FORWARD = [
95
+ ['theme', '--theme'], ['emit', '--emit'], ['footprint', '--footprint'],
96
+ ['posture', '--posture'], ['mode', '--mode'], ['doctrines', '--doctrines'],
97
+ ['excludeRules', '--exclude-rules'], ['out', '--out'], ['root', '--root'],
98
+ ['configDir', '--config-dir'],
99
+ ];
100
+
87
101
  export function cmdBuild(args) {
88
- return driverMain(args.theme ? ['--theme', args.theme] : []);
102
+ const argv = [];
103
+ for (const [key, flag] of BUILD_FORWARD) if (args[key] != null) argv.push(flag, args[key]);
104
+ return driverMain(argv);
89
105
  }
90
106
 
91
107
  // --------------------------------------------------------------------------------------
@@ -123,7 +139,7 @@ export function setupBuildArgs(theme, emit, out = null, root = null, footprint =
123
139
  posture = 'peer', mode = 'direct', doctrines = null, allPacks = PACK_ORDER,
124
140
  excludeRules = null) {
125
141
  const argv = ['--theme', theme, '--emit', emit];
126
- if (!['opencode-global', 'claude-global', 'bob-global', 'copilot-global'].includes(emit)) {
142
+ if (!emit.endsWith('-global')) {
127
143
  if (out) argv.push('--out', out);
128
144
  if (root) argv.push('--root', root);
129
145
  }
@@ -158,8 +174,69 @@ export const DEFAULT_EMIT = new Map([
158
174
  ['claude global', 'claude-global'], ['claude project', 'claude'],
159
175
  ['bob global', 'bob-global'], ['bob project', 'bob'],
160
176
  ['copilot global', 'copilot-global'], ['copilot project', 'copilot'],
177
+ ['openclaude global', 'openclaude-global'], ['openclaude project', 'openclaude'],
161
178
  ]);
162
179
 
180
+ /**
181
+ * Everything `rebuild-all` reads back off one install, and the argv that re-emits it unchanged.
182
+ *
183
+ * EXTRACTED SO `status` SHOWS WHAT A REBUILD WOULD USE, not a second reading of the same
184
+ * markers that could drift from this one. An agent changing one setting takes `argv`, edits one
185
+ * flag and runs it; every flag it did not touch has to be the install's own, or the "one
186
+ * change" silently resets the rest — which is exactly the failure the seven read-backs below
187
+ * exist to prevent.
188
+ *
189
+ * THE MARKER IS TRUSTED ONLY FOR ITS OWN HOST. There is one `.geneseed-emit` per root and the
190
+ * last deploy wins, so in a dual-host repo (`.opencode/` and `.claude/` in one cwd) the other
191
+ * host's row would otherwise rebuild with the WRONG emit — turning a Claude install into an
192
+ * OpenCode one. `rebuild-all/a-dual-host-repo-rebuilds-each-row-in-its-own-emit` is the cell.
193
+ */
194
+ export function installProfile(host, scope, root) {
195
+ const raw = readMaybe(path.join(root, '.geneseed-emit'));
196
+ let marker = raw === null ? '' : raw.trim();
197
+ if (marker && (EMIT_HOST_SCOPE.get(marker) ?? ['', ''])[0] !== host) marker = '';
198
+ const emit = marker || DEFAULT_EMIT.get(`${host} ${scope}`) || 'opencode-global';
199
+ const theme = themeOfDir(root) || defaultTheme();
200
+ const footprint = footprintOfDir(root);
201
+ const posture = postureOfDir(root) || defaultPosture();
202
+ const mode = modeOfDir(root) || defaultMode();
203
+ // A rebuild that defaulted the pack selection would re-emit an install into a constitution
204
+ // its owner did not choose — and in one direction that is not merely surprising: dropping
205
+ // the process pack takes the commit/push consent RULES out of AGENT.md while
206
+ // `claudeHookGroups` keeps the gate wired, or the reverse. ⚠ AND THE FALLBACK IS NOT
207
+ // `harness.config.json`: a pre-migration carrier has no marker, and resolving that silence
208
+ // out of a machine-wide config narrows every such install on its first upgrade.
209
+ // `doctrinesForBuild` resolves unknown to ALL packs — see its docblock.
210
+ const doctrines = doctrinesForBuild(root);
211
+ // The per-rule axis is preserved for the same reason and with sharper teeth: an upgrade that
212
+ // dropped it would quietly hand back every rule its owner switched off, and `process 5` is
213
+ // one of them. `null` (no marker) stays `null` so the argv omits the flag, as it always did.
214
+ const excludeRules = excludedRulesOfDir(root);
215
+ const out = scope === 'global' ? null : root;
216
+ const argv = setupBuildArgs(theme, emit, out, out, footprint, posture, mode, doctrines,
217
+ PACK_ORDER, excludeRules);
218
+ // A GLOBAL ROW NAMES ITS OWN DIR. `root` came from the host's config-dir resolver, which
219
+ // honours env overrides (`OPENCODE_CONFIG_DIR`, `CLAUDE_CONFIG_DIR`…); without the flag the
220
+ // same argv run from a shell lacking that variable re-emits into the DEFAULT dir — a second
221
+ // install beside the one it was meant to rebuild. Appended, so the argv's head is unchanged.
222
+ if (scope === 'global') argv.push('--config-dir', root);
223
+ return {
224
+ host, scope, root, state: installState(root, host, scope),
225
+ emit, theme, footprint, posture, mode, doctrines, excludeRules, argv,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * `argv` as one pasteable command, using the `geneseed build` launcher rather than the raw
231
+ * generator: `geneseed-build` is only on PATH for an npm global install, while a git-clone
232
+ * install has only the `geneseed` launcher, and `geneseed build` now forwards every flag
233
+ * `setupBuildArgs` produces. Only whitespace is quoted — a path holding shell metacharacters
234
+ * (`$`, `&`) would need quoting by hand.
235
+ */
236
+ export function rebuildCommand(argv) {
237
+ return ['geneseed', 'build', ...argv.map((a) => (/\s/.test(a) ? `"${a}"` : a))].join(' ');
238
+ }
239
+
163
240
  /**
164
241
  * `_harness_build.cmd_rebuild_all` — re-emit every ACTIVE install in place, best-effort.
165
242
  *
@@ -171,16 +248,12 @@ export const DEFAULT_EMIT = new Map([
171
248
  * `main` converts, and `rebuild-all/one-broken-install-does-not-stop-the-rest` is the cell
172
249
  * that fails if either half is undone.
173
250
  *
174
- * FIVE VALUES ARE READ BACK OFF EACH INSTALL, not defaulted. Theme and emit and footprint
175
- * come from markers; posture and mode are detected from the `**<Name>**` lead in the
176
- * deployed carrier's prose, because they were never markered. A rebuild that defaulted any
177
- * of them would silently re-emit an install into something its owner did not choose — which
178
- * is the same failure mode `_footprint_of_dir`'s WARN exists to prevent, at a larger scale.
179
- *
180
- * THE MARKER IS TRUSTED ONLY FOR ITS OWN HOST. There is one `.geneseed-emit` per root and the
181
- * last deploy wins, so in a dual-host repo (`.opencode/` and `.claude/` in one cwd) the other
182
- * host's row would otherwise rebuild with the WRONG emit — turning a Claude install into an
183
- * OpenCode one. `rebuild-all/a-dual-host-repo-rebuilds-each-row-in-its-own-emit` is the cell.
251
+ * SEVEN VALUES ARE READ BACK OFF EACH INSTALL, not defaulted — see `installProfile`. Theme and
252
+ * emit and footprint come from markers; posture and mode are detected from the `**<Name>**`
253
+ * lead in the deployed carrier's prose, because they were never markered. A rebuild that
254
+ * defaulted any of them would silently re-emit an install into something its owner did not
255
+ * choose — which is the same failure mode `_footprint_of_dir`'s WARN exists to prevent, at a
256
+ * larger scale.
184
257
  */
185
258
  export function cmdRebuildAll() {
186
259
  const targets = installTargets().filter(([h, s, r]) => installState(r, h, s) === 'active');
@@ -190,34 +263,11 @@ export function cmdRebuildAll() {
190
263
  }
191
264
  const failures = [];
192
265
  for (const [host, scope, root] of targets) {
193
- const raw = readMaybe(path.join(root, '.geneseed-emit'));
194
- let marker = raw === null ? '' : raw.trim();
195
- if (marker && (EMIT_HOST_SCOPE.get(marker) ?? ['', ''])[0] !== host) marker = '';
196
- const emit = marker || DEFAULT_EMIT.get(`${host} ${scope}`) || 'opencode-global';
197
- const theme = themeOfDir(root) || defaultTheme();
198
- const footprint = footprintOfDir(root);
199
- const posture = postureOfDir(root) || defaultPosture();
200
- const mode = modeOfDir(root) || defaultMode();
201
- const out = scope === 'global' ? null : root;
202
- // SIX values now, not five. A rebuild that defaulted the pack selection would re-emit an
203
- // install into a constitution its owner did not choose — and in one direction that is not
204
- // merely surprising: dropping the process pack takes the commit/push consent RULES out of
205
- // AGENT.md while `claudeHookGroups` keeps the gate wired, or the reverse. ⚠ AND THE
206
- // FALLBACK IS NOT `harness.config.json`: a pre-migration carrier has no marker, and
207
- // resolving that silence out of a machine-wide config narrows every such install on its
208
- // first upgrade. `doctrinesForBuild` resolves unknown to ALL packs — see its docblock.
209
- const doctrines = doctrinesForBuild(root);
210
- // SEVEN. The per-rule axis is preserved for the same reason and with sharper teeth: an
211
- // upgrade that dropped it would quietly hand back every rule its owner switched off, and
212
- // `process 5` is one of them — the consent gate would return to a harness that had been
213
- // built without it. No `doctrinesForBuild` rescue is needed here; see `apiInstallCmd`.
214
- const excludeRules = excludedRulesOfDir(root);
215
- const argv = setupBuildArgs(theme, emit, out, out, footprint, posture, mode, doctrines,
216
- PACK_ORDER, excludeRules);
266
+ const p = installProfile(host, scope, root);
217
267
  const label = `${host}:${scope} (${root})`;
218
- printOut(`[rebuild-all] ${label}: theme=${theme} emit=${emit} footprint=${footprint} `
219
- + `posture=${posture} mode=${mode}\n`);
220
- const rc = driverMain(argv);
268
+ printOut(`[rebuild-all] ${label}: theme=${p.theme} emit=${p.emit} footprint=${p.footprint} `
269
+ + `posture=${p.posture} mode=${p.mode}\n`);
270
+ const rc = driverMain(p.argv);
221
271
  if (rc !== 0) {
222
272
  failures.push(label);
223
273
  printErr(`[rebuild-all] FAILED ${label} (exit ${rc})\n`);
@@ -92,17 +92,18 @@ export function discoverNames(dir, first) {
92
92
  *
93
93
  * DELIBERATELY NOT `discoverNames('doctrines', 'craft')`. That helper `.sort()`s, so it
94
94
  * answers `craft, ops, process, rigor`; the constitution's order is a reading order —
95
- * write it (craft), prove it (rigor), run it (ops), ship it (process) — and no sort
96
- * produces it. Discovery still runs, but as a GATE rather than as the order: `js/build/render.mjs`
95
+ * write it (craft), prove it (rigor), run it (ops), ship it (process), then say it
96
+ * (comms) — and no sort produces it. comms is LAST because presenting the answer is the last
97
+ * act of any task, and because appending left every earlier pack's position untouched. Discovery still runs, but as a GATE rather than as the order: `js/build/render.mjs`
97
98
  * refuses to build when a pack file exists under `src/doctrines/` that is missing from this
98
- * array, so a fifth pack cannot be silently dropped from every install.
99
+ * array, so a sixth pack cannot be silently dropped from every install.
99
100
  *
100
101
  * It lives here, beside `makeCfg` and `discoverNames`, because every consumer (the cfg
101
102
  * default, the render loop, the `Active packs:` marker, the CLI flag, the wizard, the
102
103
  * doctor) already imports this module and this module imports nothing of theirs — the one
103
104
  * placement that cannot introduce a cycle.
104
105
  */
105
- export const PACK_ORDER = ['craft', 'rigor', 'ops', 'process'];
106
+ export const PACK_ORDER = ['craft', 'rigor', 'ops', 'process', 'comms'];
106
107
 
107
108
  /**
108
109
  * Every doctrine rule address this checkout ships — `pack.n`, in render order.