geneseed 3.5.0 → 3.7.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 (89) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/DESIGN.md +6 -6
  3. package/QUICKSTART.md +1 -1
  4. package/README.md +32 -30
  5. package/SETUP.md +34 -8
  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 +22 -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 +50 -9
  22. package/js/build/emit-claude.mjs +1 -1
  23. package/js/build/generate.mjs +2 -1
  24. package/js/build/source.mjs +5 -4
  25. package/js/hosts/hooks.mjs +11 -4
  26. package/js/hosts/hosts.mjs +17 -2
  27. package/js/hosts/installs.mjs +32 -2
  28. package/js/hosts/mcp.mjs +8 -1
  29. package/js/hosts/settings.mjs +4 -1
  30. package/js/inspect/diff.mjs +1 -1
  31. package/js/inspect/doctor.mjs +3 -3
  32. package/js/inspect/excludes.mjs +7 -5
  33. package/js/inspect/status.mjs +4 -4
  34. package/js/maintain/migrate.mjs +1 -1
  35. package/js/maintain/setup.mjs +7 -2
  36. package/js/maintain/uninstall.mjs +3 -3
  37. package/js/web/actions.mjs +7 -5
  38. package/js/web/api.mjs +2 -2
  39. package/js/web/docs.mjs +1 -1
  40. package/package.json +2 -1
  41. package/src/AGENT.md.tmpl +8 -5
  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/debug.md +1 -1
  46. package/src/skills/document-project.md +127 -25
  47. package/src/skills/parallel-agents.md +1 -1
  48. package/src/skills/pipeline.md +1 -1
  49. package/src/skills/token-report/SKILL.md +2 -1
  50. package/src/skills/token-report/VENDOR.md +3 -3
  51. package/src/skills/token-report/scripts/token_report.mjs +14 -7
  52. package/src/skills/workflow.md +2 -2
  53. package/themes/_TEMPLATE.json +5 -3
  54. package/themes/biker.json +5 -3
  55. package/themes/commentator.json +5 -3
  56. package/themes/cyberpunk.json +5 -3
  57. package/themes/gamer.json +5 -3
  58. package/themes/imperial.json +5 -3
  59. package/themes/joker.json +5 -3
  60. package/themes/marvin.json +5 -3
  61. package/themes/mean.json +5 -3
  62. package/themes/military.json +5 -3
  63. package/themes/neutral.json +5 -3
  64. package/themes/pirate.json +5 -3
  65. package/themes/sports.json +5 -3
  66. package/themes/verstappen.json +5 -3
  67. package/themes/wizard.json +5 -3
  68. package/web/dist/assets/{Activity-Flgf4kHy.js → Activity-CH-7cPIM.js} +1 -1
  69. package/web/dist/assets/{ActivityDetail-DIpr85nh.js → ActivityDetail-DZvz0xS5.js} +1 -1
  70. package/web/dist/assets/{CatalogRow-CjyLjaLb.js → CatalogRow-DftXpZHY.js} +1 -1
  71. package/web/dist/assets/{Diff-Deisdha8.js → Diff-DvwdG0L9.js} +1 -1
  72. package/web/dist/assets/{Docs-DGUdY4l4.js → Docs-DrJNDPwI.js} +1 -1
  73. package/web/dist/assets/{Doctor-F74lJ69P.js → Doctor-OTmwmYZg.js} +1 -1
  74. package/web/dist/assets/{GreenhouseView-D072lBiD.js → GreenhouseView-XxrAdriX.js} +1 -1
  75. package/web/dist/assets/Harness-C_LYNk5c.js +1 -0
  76. package/web/dist/assets/{JournalView-DWFqZ0Iw.js → JournalView-BcuyeEaR.js} +1 -1
  77. package/web/dist/assets/{Laws-BN6g3HU7.js → Laws-CWHrTKY4.js} +1 -1
  78. package/web/dist/assets/{Library-CDOv4emM.js → Library-CA9IHghn.js} +1 -1
  79. package/web/dist/assets/{OperatorHudView-CwNIUVX8.js → OperatorHudView-BMRY1Tt4.js} +1 -1
  80. package/web/dist/assets/{Profile-CpP9BTne.js → Profile-wceET1JY.js} +1 -1
  81. package/web/dist/assets/{Rules-DFXsUCB7.js → Rules-BEXvY4Tx.js} +1 -1
  82. package/web/dist/assets/{Settings-BqZVUtJ5.js → Settings-DS2zF0Ez.js} +1 -1
  83. package/web/dist/assets/{Skills-Cu6kBgfF.js → Skills-DahlOJuI.js} +1 -1
  84. package/web/dist/assets/{drawable-BgfrEBer.js → drawable-BTHjTzF7.js} +1 -1
  85. package/web/dist/assets/{index-CAqrWhKl.js → index-AN5Emnrg.js} +2 -2
  86. package/web/dist/assets/{lawCats-BLgxl151.js → lawCats-Btjp3Hk_.js} +1 -1
  87. package/web/dist/index.html +1 -1
  88. package/web/src/pages/Laws.jsx +6 -2
  89. 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.5.0"
5
+ "version": "3.7.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');
@@ -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
  *
@@ -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.
@@ -1186,6 +1225,8 @@ function run(argv) {
1186
1225
  // An ALLOW-LIST, not "everything that is not global": a plain `--emit files` dev build —
1187
1226
  // the default — must never pollute the registry, and only the four per-repo host emits
1188
1227
  // 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);
1228
+ if (['opencode', 'claude', 'bob', 'copilot', 'openclaude'].includes(args.emit)) {
1229
+ registryRecord(markerDir);
1230
+ }
1190
1231
  return 0;
1191
1232
  }
@@ -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)) {
@@ -123,7 +123,7 @@ export function setupBuildArgs(theme, emit, out = null, root = null, footprint =
123
123
  posture = 'peer', mode = 'direct', doctrines = null, allPacks = PACK_ORDER,
124
124
  excludeRules = null) {
125
125
  const argv = ['--theme', theme, '--emit', emit];
126
- if (!['opencode-global', 'claude-global', 'bob-global', 'copilot-global'].includes(emit)) {
126
+ if (!emit.endsWith('-global')) {
127
127
  if (out) argv.push('--out', out);
128
128
  if (root) argv.push('--root', root);
129
129
  }
@@ -158,6 +158,7 @@ export const DEFAULT_EMIT = new Map([
158
158
  ['claude global', 'claude-global'], ['claude project', 'claude'],
159
159
  ['bob global', 'bob-global'], ['bob project', 'bob'],
160
160
  ['copilot global', 'copilot-global'], ['copilot project', 'copilot'],
161
+ ['openclaude global', 'openclaude-global'], ['openclaude project', 'openclaude'],
161
162
  ]);
162
163
 
163
164
  /**
@@ -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.
@@ -13,7 +13,8 @@
13
13
  * exactly where Claude asks, in Copilot's envelope. `--host bob` is Bob's own
14
14
  * protocol: PreToolUse ignores stdout and refuses only on EXIT CODE 2, so the two Laws
15
15
  * exit 2 with the reason on stderr and the rest is a stderr line with exit 0; SessionStart
16
- * context is plain stdout, as on Claude. Since P5b they are also what the emitted hooks name: `bin/build-driver.mjs` bakes
16
+ * context is plain stdout, as on Claude. `--host openclaude` speaks Claude's dialect verbatim
17
+ * (OpenClaude is a Claude Code fork); the flag only picks which root file counts as native. Since P5b they are also what the emitted hooks name: `bin/build-driver.mjs` bakes
17
18
  * `<node> <checkout>/bin/geneseed-hook.mjs` into the machine-wide shim, so an install this
18
19
  * driver emits has no Python in its hook path at all — which is what let the interpreter
19
20
  * discovery and its exit-4 refusal be deleted rather than merely bypassed.
@@ -223,7 +224,12 @@ const EXCLUDE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'vendor',
223
224
  // host had its own root in EAGER_ROOT. Other tools' roots stay eager (a repo carrying a
224
225
  // hand-written AGENTS.md is worth showing Claude Code); only the host's OWN is dropped.
225
226
  // The OpenCode context plugin carries the same rule for AGENT.md/AGENTS.md/CLAUDE.md.
226
- const NATIVE_ROOT = { claude: ['CLAUDE.md'], bob: ['AGENTS.md'], copilot: ['AGENTS.md'] };
227
+ // OpenClaude's root file is AGENTS.md, or CLAUDE.md when AGENTS.md is absent — resolved
228
+ // per repo in `discoverContext`, since which one it loads depends on what is on disk.
229
+ const NATIVE_ROOT = {
230
+ claude: ['CLAUDE.md'], bob: ['AGENTS.md'], copilot: ['AGENTS.md'],
231
+ openclaude: ['AGENTS.md', 'CLAUDE.md'],
232
+ };
227
233
 
228
234
  // Eager injection budget: a root README of 40k chars was going out whole, every session and
229
235
  // every compaction, and cost more than the harness itself. Per-file cut at the last line
@@ -231,7 +237,7 @@ const NATIVE_ROOT = { claude: ['CLAUDE.md'], bob: ['AGENTS.md'], copilot: ['AGEN
231
237
  const EAGER_FILE_BYTES = 16 * 1024;
232
238
  const EAGER_TOTAL_BYTES = 48 * 1024;
233
239
 
234
- const CLAUDE_MARKERS = ['.claude', '.bob'];
240
+ const CLAUDE_MARKERS = ['.claude', '.bob', '.openclaude'];
235
241
  const GENESEED_MANIFEST = '.geneseed-manifest.json';
236
242
 
237
243
  /**
@@ -302,7 +308,8 @@ function rglobMd(dir, acc = []) {
302
308
  export function discoverContext(root, host = HOST) {
303
309
  const eager = new Map();
304
310
  const lazy = new Map();
305
- const native = NATIVE_ROOT[host] || [];
311
+ const native = host === 'openclaude' && isFile(path.join(root, 'AGENTS.md'))
312
+ ? ['AGENTS.md'] : NATIVE_ROOT[host] || [];
306
313
  for (const full of sortPaths(listDir(root).map((n) => path.join(root, n)))) {
307
314
  if (!isFile(full)) continue;
308
315
  const name = path.basename(full);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The four host config dirs, and the two path primitives they are built on.
2
+ * The five host config dirs, and the two path primitives they are built on.
3
3
  *
4
4
  * EXTRACTED IN P5c, and the reason is arithmetic rather than taste. `bin/build-driver.mjs`
5
5
  * owned these; `bin/geneseed-cli.mjs` needs the same four to find a global install, and a
@@ -208,9 +208,22 @@ export function bobConfigDir() {
208
208
  return resolvePath(path.join(os.homedir(), '.bob'));
209
209
  }
210
210
 
211
+ /**
212
+ * OpenClaude (`@gitlawb/openclaude`) — `~/.openclaude`, relocatable via `$OPENCLAUDE_CONFIG_DIR`.
213
+ *
214
+ * Unlike Claude's, this env branch is the HOST'S OWN documented variable, not a Geneseed knob:
215
+ * OpenClaude reads it in `src/utils/envUtils.ts` and never falls back to `~/.claude` or
216
+ * `$CLAUDE_CONFIG_DIR`, so honouring it is what keeps the two CLIs agreeing on the target.
217
+ */
218
+ export function openclaudeConfigDir() {
219
+ const env = process.env.OPENCLAUDE_CONFIG_DIR;
220
+ if (env) return resolvePath(env);
221
+ return resolvePath(path.join(os.homedir(), '.openclaude'));
222
+ }
223
+
211
224
  /**
212
225
  * `_build_global.HOSTS`, reduced to the two columns a non-emitting caller needs, and IN ITS
213
- * ORDER — opencode, claude, bob, copilot.
226
+ * ORDER — opencode, claude, bob, copilot, openclaude.
214
227
  *
215
228
  * An array rather than an object because the order is observable output, not an
216
229
  * implementation detail: `harness exclude add` walks it and prints one message per host, so
@@ -235,6 +248,7 @@ export const HOSTS = [
235
248
  { host: 'claude', configDir: claudeConfigDir, projectMarker: '.claude', agentFile: 'CLAUDE.md' },
236
249
  { host: 'bob', configDir: bobConfigDir, projectMarker: '.bob', agentFile: 'AGENTS.md' },
237
250
  { host: 'copilot', configDir: copilotConfigDir, projectMarker: '.github', agentFile: 'AGENTS.md' },
251
+ { host: 'openclaude', configDir: openclaudeConfigDir, projectMarker: '.openclaude', agentFile: 'CLAUDE.md' },
238
252
  ];
239
253
 
240
254
  /**
@@ -265,6 +279,7 @@ const NATIVE_CATALOG = {
265
279
  claude: { skills: true, agents: true },
266
280
  bob: { skills: true, agents: false },
267
281
  copilot: { skills: false, agents: false },
282
+ openclaude: { skills: true, agents: true },
268
283
  };
269
284
 
270
285
  /** `{ skills, agents }` for a known host, `false` for an unknown one. */
@@ -234,6 +234,24 @@ export const modeOfDir = (d) => leadOfDir(d, discoverNames('modes', 'direct'));
234
234
  /** The `Active packs:` marker line the template emits, in one of the five carriers. */
235
235
  const ACTIVE_PACKS_RE = /^Active packs:[ \t]*(.+?)[ \t]*$/m;
236
236
 
237
+ /**
238
+ * A carrier rendered BEFORE the comms pack existed — the address migration's one detector.
239
+ *
240
+ * The comms pack took `process 7` (reference codes) out of process, and `process 8` (one
241
+ * writer per file) was renumbered to `process 7`. An install built before that names
242
+ * `process` in its marker and not `comms`, and so does a later install whose owner turned
243
+ * comms OFF — the marker alone cannot tell them apart. The carrier text can: a pre-comms
244
+ * process pack had eight rules, so wherever it was active the carrier renders a
245
+ * `process 8` heading, and wherever that rule was excluded the `Excluded rules:` line names
246
+ * it. No current carrier can say `process 8` — the pack has seven rules and nothing cites
247
+ * an eighth.
248
+ *
249
+ * ponytail: text sniff, not a stamp. It holds only while process has seven rules; the day
250
+ * process gets an eighth, this must move to a build stamp (or be retired, once no install
251
+ * built before the migration can remain).
252
+ */
253
+ const legacyProcessCarrier = (text) => /\bprocess 8\b/.test(text);
254
+
237
255
  /**
238
256
  * The doctrine packs a DEPLOYED install carries — the third register read back off disk,
239
257
  * beside `postureOfDir` and `modeOfDir`, and the first that is a SET.
@@ -274,6 +292,12 @@ export function doctrinesOfDir(d) {
274
292
  if (m[1] === 'none') return [];
275
293
  const named = m[1].split(',').map((s) => s.trim());
276
294
  if (!named.every((n) => PACK_ORDER.includes(n))) return null;
295
+ // MIGRATION: a pre-comms install that carried process carried the codes rule inside it,
296
+ // so it keeps that rule by gaining comms. Its old `process 7` exclusion, if any, is
297
+ // re-addressed to `comms 1` by `excludedRulesOfDir` in the same pass.
298
+ if (named.includes('process') && !named.includes('comms') && legacyProcessCarrier(text)) {
299
+ named.push('comms');
300
+ }
277
301
  return PACK_ORDER.filter((pk) => named.includes(pk));
278
302
  });
279
303
  }
@@ -336,7 +360,12 @@ export function excludedRulesOfDir(d) {
336
360
  return parts.length === 2 && PACK_ORDER.includes(parts[0]) && /^\d+$/.test(parts[1])
337
361
  ? `${parts[0]}.${Number(parts[1])}` : null;
338
362
  });
339
- return ids.every(Boolean) ? [...new Set(ids)].sort() : [];
363
+ if (!ids.every(Boolean)) return [];
364
+ // MIGRATION: the same pre-comms carrier spells the old addresses — see
365
+ // `legacyProcessCarrier`. One map, applied once, so `process 8` cannot chain onward.
366
+ const MOVED = { 'process.7': 'comms.1', 'process.8': 'process.7' };
367
+ const out = legacyProcessCarrier(text) ? ids.map((id) => MOVED[id] ?? id) : ids;
368
+ return [...new Set(out)].sort();
340
369
  }, []);
341
370
  }
342
371
 
@@ -364,6 +393,7 @@ export const EMIT_HOST_SCOPE = new Map([
364
393
  ['claude', ['claude', 'project']], ['claude-global', ['claude', 'global']],
365
394
  ['bob', ['bob', 'project']], ['bob-global', ['bob', 'global']],
366
395
  ['copilot', ['copilot', 'project']], ['copilot-global', ['copilot', 'global']],
396
+ ['openclaude', ['openclaude', 'project']], ['openclaude-global', ['openclaude', 'global']],
367
397
  ]);
368
398
 
369
399
  /** `_harness_mcp._emit_host_scope_of` — `root`'s own marker, resolved, or null. */
@@ -405,7 +435,7 @@ export function installKind(root) {
405
435
 
406
436
  /** `_harness_mcp._install_state` — 'active' | 'disabled' | 'absent'. */
407
437
  export function installState(root, host = 'opencode', scope = 'global') {
408
- if (['claude', 'bob', 'copilot'].includes(host)) return claudeState(root, scope, host);
438
+ if (['claude', 'bob', 'copilot', 'openclaude'].includes(host)) return claudeState(root, scope, host);
409
439
  if (isDir(path.join(root, DISABLED_STASH))) return 'disabled';
410
440
  return installKind(root) !== null ? 'active' : 'absent';
411
441
  }
package/js/hosts/mcp.mjs CHANGED
@@ -44,7 +44,7 @@ import { opencodeTarget, readJsonc } from './settings.mjs';
44
44
  const dget = (obj, key, dflt) => (isDict(obj) && Object.hasOwn(obj, key) ? obj[key] : dflt);
45
45
 
46
46
  /** The hosts whose MCP config is strict JSON under `mcpServers`, with no `enabled` flag. */
47
- const FLAGLESS = ['claude', 'bob', 'copilot'];
47
+ const FLAGLESS = ['claude', 'bob', 'copilot', 'openclaude'];
48
48
 
49
49
  /**
50
50
  * `_harness_mcp._MCP_PRESETS` — the ready-to-wire servers the MCP screen can toggle.
@@ -248,6 +248,13 @@ export function mcpConfigFor(host, scope, root) {
248
248
  // file is documented, so a project install carries no MCP wiring.
249
249
  return scope === 'global' ? path.join(copilotConfigDir(), 'mcp-config.json') : null;
250
250
  }
251
+ if (host === 'openclaude') {
252
+ // Claude's `.mcp.json` per repo; globally `.openclaude.json` beside the home dir, or
253
+ // INSIDE `$OPENCLAUDE_CONFIG_DIR` when set — OpenClaude's own `getGlobalClaudeFile` rule.
254
+ if (scope === 'project') return path.join(root, '.mcp.json');
255
+ const env = process.env.OPENCLAUDE_CONFIG_DIR;
256
+ return resolvePath(path.join(env || os.homedir(), '.openclaude.json'));
257
+ }
251
258
  return null;
252
259
  }
253
260
 
@@ -716,7 +716,10 @@ export function claudeHookGroups(cfg, hookOpts, doctrines = null, excluded = [],
716
716
  preCompact: entry(`${run} learn ${mem} || exit 0`),
717
717
  };
718
718
  }
719
- const context = `${run} context --root "${cfg}" || exit 0`;
719
+ // OpenClaude takes Claude's whole group set and verdicts unchanged; only `context` needs
720
+ // the host, to know which root file OpenClaude already loads by itself.
721
+ const h = host === 'openclaude' ? ' --host openclaude' : '';
722
+ const context = `${run} context --root "${cfg}"${h} || exit 0`;
720
723
  const gate = `${run} git-gate --root "${cfg}"${consentRuleOn(doctrines, excluded) ? '' : ' --no-consent'}`;
721
724
  const ruleGate = `${run} rule-gate --root "${cfg}"`;
722
725
  const learn = `${run} learn ${mem} || exit 0`;
@@ -349,7 +349,7 @@ export function cmdDiff(args) {
349
349
  + 'installs against a fresh render (a project layer is diffed against '
350
350
  + 'its own repo, which this command does not do). Pass --target at a '
351
351
  + 'global config dir (opencode-global/claude-global/bob-global/'
352
- + 'copilot-global), or omit '
352
+ + 'copilot-global/openclaude-global), or omit '
353
353
  + '--target to use the default.\n');
354
354
  } else {
355
355
  printErr(`[diff] no global Geneseed install at ${target} (no ${GLOBAL_MANIFEST}). `
@@ -134,7 +134,7 @@ export function globalEmitProblems(themeName) {
134
134
  }
135
135
 
136
136
  /**
137
- * `_harness_build._claude_bob_emit_problems` — validate the claude/bob/copilot PER-REPO
137
+ * `_harness_build._claude_bob_emit_problems` — validate the claude/bob/copilot/openclaude PER-REPO
138
138
  * emits, which were never checked before.
139
139
  *
140
140
  * That is exactly why the CLAUDE.md/AGENTS.md skill-table dead links shipped unnoticed: the
@@ -143,7 +143,7 @@ export function globalEmitProblems(themeName) {
143
143
  * skills one level deeper than a bundle does.
144
144
  */
145
145
  export function claudeBobEmitProblems(themeName) {
146
- return emitProblemsOver(['claude', 'bob', 'copilot'], (tmp, label) => {
146
+ return emitProblemsOver(['claude', 'bob', 'copilot', 'openclaude'], (tmp, label) => {
147
147
  const root = path.join(tmp, 'root');
148
148
  return {
149
149
  label: `${themeName} ${label}`,
@@ -211,7 +211,7 @@ export function doctorCollect({
211
211
  problems = problems.concat(ran('global', `Global install (${themeName})`,
212
212
  globalEmitProblems(themeName)));
213
213
  problems = problems.concat(ran('claude_bob',
214
- `Claude/Bob/Copilot per-repo emit (${themeName})`, claudeBobEmitProblems(themeName)));
214
+ `Claude/Bob/Copilot/OpenClaude per-repo emit (${themeName})`, claudeBobEmitProblems(themeName)));
215
215
  }
216
216
  });
217
217
  problems = problems.concat(ran('parity', 'Theme parity', themeParityProblems()));
@@ -28,6 +28,10 @@ import { readText, writeText, printOut, printErr, isDir, isFile, isOsError } fro
28
28
  import { parseJson, jsonDumpsIndent, get, isDict } from '../lib/json.mjs';
29
29
  import { normcase, toPlatformPath } from '../lib/paths.mjs';
30
30
 
31
+ /** The per-repo settings file a claudeMdExcludes entry goes in (Claude and OpenClaude). */
32
+ const localSettings = (repo, host) =>
33
+ path.join(repo, HOSTS.find((h) => h.host === host).projectMarker, 'settings.local.json');
34
+
31
35
  /** `_global_installs` — every host whose GLOBAL config dir carries a Geneseed manifest. */
32
36
  function globalInstalls() {
33
37
  const out = [];
@@ -102,10 +106,9 @@ export function excludeAdd(target) {
102
106
  const prior = data.excludes.find((e) => isDict(e) && same(get(e, 'path'), repo));
103
107
  const priorWired = (isDict(prior) ? get(prior, 'wired') : null) || {};
104
108
  const wired = {};
105
- if (host === 'claude' && isDir(repo)) {
109
+ if ((host === 'claude' || host === 'openclaude') && isDir(repo)) {
106
110
  const entry = resolvePath(path.join(cfg, 'CLAUDE.md')).split(path.sep).join('/');
107
- const added = wireClaudeExcludes(
108
- path.join(repo, '.claude', 'settings.local.json'), [entry]);
111
+ const added = wireClaudeExcludes(localSettings(repo, host), [entry]);
109
112
  if (added.length) {
110
113
  wired.claude_md_excludes = added;
111
114
  } else if (get(priorWired, 'claude_md_excludes')) {
@@ -172,8 +175,7 @@ export function excludeRemove(target) {
172
175
  found = true;
173
176
  const wired = get(mine, 'wired') || {};
174
177
  if (get(wired, 'claude_md_excludes')) {
175
- unwireClaudeExcludes(path.join(repo, '.claude', 'settings.local.json'),
176
- get(wired, 'claude_md_excludes'));
178
+ unwireClaudeExcludes(localSettings(repo, host), get(wired, 'claude_md_excludes'));
177
179
  }
178
180
  if (get(wired, 'bob_rules_stub')) {
179
181
  const stub = path.join(repo, '.bob', 'rules', 'geneseed.md');
@@ -61,8 +61,8 @@ import { THEMES, ROOT, makeCfg } from '../build/source.mjs';
61
61
  import { tuiInventory } from './inventory.mjs';
62
62
  import { readVersion, sourceFingerprint } from '../build/version.mjs';
63
63
  import {
64
- claudeConfigDir, bobConfigDir, copilotConfigDir, opencodeConfigDir, resolvePath,
65
- resolveMemoryDir, sovereignBypass, GATE_LEDGER,
64
+ claudeConfigDir, bobConfigDir, copilotConfigDir, openclaudeConfigDir, opencodeConfigDir,
65
+ resolvePath, resolveMemoryDir, sovereignBypass, GATE_LEDGER,
66
66
  } from '../hosts/hosts.mjs';
67
67
  // P5f moved the install DETECTORS out of this file — `diff` renders its expected copy in the
68
68
  // deployed theme and footprint, and `rebuild-all` re-emits in the deployed everything, so
@@ -100,7 +100,7 @@ export function cmdVersion(args) {
100
100
  if (installed === null) {
101
101
  // A claude/bob/copilot-only machine must not report "no install detected". The bundle
102
102
  // candidates are cwd-relative only — see the Python for why `ROOT / "Harness"` left.
103
- for (const base of [claudeConfigDir(), bobConfigDir(), copilotConfigDir(),
103
+ for (const base of [claudeConfigDir(), bobConfigDir(), copilotConfigDir(), openclaudeConfigDir(),
104
104
  path.join(process.cwd(), 'Harness'), process.cwd()]) {
105
105
  const v = readVersion(base);
106
106
  if (v) { installed = v; target = base; break; }
@@ -225,7 +225,7 @@ export function statusData() {
225
225
  let installedFp = null;
226
226
  let verTarget = null;
227
227
  const otherCfg = [];
228
- for (const fn of [claudeConfigDir, bobConfigDir, copilotConfigDir]) {
228
+ for (const fn of [claudeConfigDir, bobConfigDir, copilotConfigDir, openclaudeConfigDir]) {
229
229
  try { otherCfg.push(fn()); } catch { /* a missing host dir must not sink status */ }
230
230
  }
231
231
  // Same walk as `cmdVersion`'s fallback, and cwd-relative for the same reason.
@@ -59,7 +59,7 @@ const KNOWN_EMITS = new Set(EMIT_HOST_SCOPE.keys());
59
59
  * hosts, which is what the project's host-parity rule asks for.
60
60
  */
61
61
  function hookSettingsFile(root, host, scope) {
62
- if (host !== 'claude' && host !== 'bob') return null;
62
+ if (!['claude', 'bob', 'openclaude'].includes(host)) return null;
63
63
  const name = scope === 'project' && host !== 'bob' ? 'settings.local.json' : 'settings.json';
64
64
  return path.join(root, name);
65
65
  }