geneseed 3.6.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.
- package/CHANGELOG.md +49 -0
- package/DESIGN.md +6 -6
- package/QUICKSTART.md +1 -1
- package/README.md +32 -30
- package/SETUP.md +34 -8
- package/SHIPPED.md +3 -3
- package/adapters/claude-code/skills/geneseed/SKILL.md +1 -1
- package/adapters/openclaude/README.md +64 -0
- package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
- package/adapters/opencode/workflows/_runtime.js +1 -1
- package/docs/extending.md +18 -6
- package/docs/web/exclusions.md +1 -0
- package/docs/web/footprint.md +1 -1
- package/docs/web/harnesses.md +14 -14
- package/docs/web/install-quick.md +1 -1
- package/docs/web/rules.md +1 -1
- package/docs/web/themes-author.md +1 -1
- package/docs/web-ui.md +1 -1
- package/harness.config.json +1 -1
- package/js/README.md +3 -3
- package/js/build/driver.mjs +50 -9
- package/js/build/emit-claude.mjs +1 -1
- package/js/build/generate.mjs +2 -1
- package/js/build/source.mjs +5 -4
- package/js/hosts/hooks.mjs +11 -4
- package/js/hosts/hosts.mjs +17 -2
- package/js/hosts/installs.mjs +32 -2
- package/js/hosts/mcp.mjs +8 -1
- package/js/hosts/settings.mjs +4 -1
- package/js/inspect/diff.mjs +1 -1
- package/js/inspect/doctor.mjs +3 -3
- package/js/inspect/excludes.mjs +7 -5
- package/js/inspect/status.mjs +4 -4
- package/js/maintain/migrate.mjs +1 -1
- package/js/maintain/setup.mjs +7 -2
- package/js/maintain/uninstall.mjs +3 -3
- package/js/web/actions.mjs +7 -5
- package/js/web/api.mjs +2 -2
- package/js/web/docs.mjs +1 -1
- package/package.json +2 -1
- package/src/doctrines/README.md +3 -2
- package/src/doctrines/comms.md +53 -0
- package/src/doctrines/process.md +0 -26
- package/src/skills/parallel-agents.md +1 -1
- package/src/skills/pipeline.md +1 -1
- package/src/skills/token-report/SKILL.md +2 -1
- package/src/skills/token-report/VENDOR.md +3 -3
- package/src/skills/token-report/scripts/token_report.mjs +14 -7
- package/src/skills/workflow.md +2 -2
- package/themes/_TEMPLATE.json +4 -2
- package/themes/biker.json +4 -2
- package/themes/commentator.json +4 -2
- package/themes/cyberpunk.json +4 -2
- package/themes/gamer.json +4 -2
- package/themes/imperial.json +4 -2
- package/themes/joker.json +4 -2
- package/themes/marvin.json +4 -2
- package/themes/mean.json +4 -2
- package/themes/military.json +4 -2
- package/themes/neutral.json +4 -2
- package/themes/pirate.json +4 -2
- package/themes/sports.json +4 -2
- package/themes/verstappen.json +4 -2
- package/themes/wizard.json +4 -2
- package/web/dist/assets/{Activity-Flgf4kHy.js → Activity-CH-7cPIM.js} +1 -1
- package/web/dist/assets/{ActivityDetail-DIpr85nh.js → ActivityDetail-DZvz0xS5.js} +1 -1
- package/web/dist/assets/{CatalogRow-CjyLjaLb.js → CatalogRow-DftXpZHY.js} +1 -1
- package/web/dist/assets/{Diff-Deisdha8.js → Diff-DvwdG0L9.js} +1 -1
- package/web/dist/assets/{Docs-DGUdY4l4.js → Docs-DrJNDPwI.js} +1 -1
- package/web/dist/assets/{Doctor-F74lJ69P.js → Doctor-OTmwmYZg.js} +1 -1
- package/web/dist/assets/{GreenhouseView-D072lBiD.js → GreenhouseView-XxrAdriX.js} +1 -1
- package/web/dist/assets/Harness-C_LYNk5c.js +1 -0
- package/web/dist/assets/{JournalView-DWFqZ0Iw.js → JournalView-BcuyeEaR.js} +1 -1
- package/web/dist/assets/{Laws-BN6g3HU7.js → Laws-CWHrTKY4.js} +1 -1
- package/web/dist/assets/{Library-CDOv4emM.js → Library-CA9IHghn.js} +1 -1
- package/web/dist/assets/{OperatorHudView-CwNIUVX8.js → OperatorHudView-BMRY1Tt4.js} +1 -1
- package/web/dist/assets/{Profile-CpP9BTne.js → Profile-wceET1JY.js} +1 -1
- package/web/dist/assets/{Rules-DFXsUCB7.js → Rules-BEXvY4Tx.js} +1 -1
- package/web/dist/assets/{Settings-BqZVUtJ5.js → Settings-DS2zF0Ez.js} +1 -1
- package/web/dist/assets/{Skills-Cu6kBgfF.js → Skills-DahlOJuI.js} +1 -1
- package/web/dist/assets/{drawable-BgfrEBer.js → drawable-BTHjTzF7.js} +1 -1
- package/web/dist/assets/{index-CAqrWhKl.js → index-AN5Emnrg.js} +2 -2
- package/web/dist/assets/{lawCats-BLgxl151.js → lawCats-Btjp3Hk_.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/src/pages/Laws.jsx +6 -2
- package/web/dist/assets/Harness-DNRP2Va7.js +0 -1
package/docs/web/harnesses.md
CHANGED
|
@@ -4,26 +4,26 @@ order: 4
|
|
|
4
4
|
title: "Supported harnesses"
|
|
5
5
|
kind: "concept"
|
|
6
6
|
---
|
|
7
|
-
One source, **
|
|
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
|
|
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
|
|
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
|
package/harness.config.json
CHANGED
|
@@ -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
|
+
"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
|
|
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
|
|
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 |
|
package/js/build/driver.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The generator driver — flag parsing, the
|
|
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
|
|
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
|
-
|
|
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))
|
|
1228
|
+
if (['opencode', 'claude', 'bob', 'copilot', 'openclaude'].includes(args.emit)) {
|
|
1229
|
+
registryRecord(markerDir);
|
|
1230
|
+
}
|
|
1190
1231
|
return 0;
|
|
1191
1232
|
}
|
package/js/build/emit-claude.mjs
CHANGED
|
@@ -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)) {
|
package/js/build/generate.mjs
CHANGED
|
@@ -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 (!
|
|
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
|
/**
|
package/js/build/source.mjs
CHANGED
|
@@ -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)
|
|
96
|
-
* produces it.
|
|
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
|
|
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.
|
package/js/hosts/hooks.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 =
|
|
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);
|
package/js/hosts/hosts.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
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. */
|
package/js/hosts/installs.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/js/hosts/settings.mjs
CHANGED
|
@@ -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
|
-
|
|
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`;
|
package/js/inspect/diff.mjs
CHANGED
|
@@ -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}). `
|
package/js/inspect/doctor.mjs
CHANGED
|
@@ -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()));
|
package/js/inspect/excludes.mjs
CHANGED
|
@@ -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(
|
|
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');
|
package/js/inspect/status.mjs
CHANGED
|
@@ -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,
|
|
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.
|
package/js/maintain/migrate.mjs
CHANGED
|
@@ -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 (
|
|
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
|
}
|