geneseed 3.8.0 → 3.10.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 +71 -1
- package/DESIGN.md +13 -10
- package/QUICKSTART.md +1 -1
- package/README.md +31 -32
- package/SETUP.md +7 -42
- package/SHIPPED.md +4 -4
- package/adapters/bob/README.md +17 -1
- package/adapters/openclaude/README.md +1 -1
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +1 -1
- package/adapters/opencode/README.md +3 -3
- package/adapters/opencode/plugins/geneseed-context.js +78 -8
- package/adapters/opencode/plugins/geneseed-guard.js +9 -9
- package/adapters/opencode/plugins/geneseed-workflow.js +1 -1
- package/adapters/opencode/workflows/_runtime.js +2 -2
- package/adapters/opencode/workflows/dispatch.js +1 -1
- package/bin/geneseed-hook.mjs +1 -1
- package/docs/candidate-hosts.md +12 -5
- package/docs/extending.md +72 -40
- package/docs/token-footprint.md +26 -21
- package/docs/web/claude-hooks.md +1 -1
- package/docs/web/collaboration.md +1 -1
- package/docs/web/exclusions.md +0 -1
- 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/plugin-guard.md +2 -2
- package/docs/web/rules.md +1 -1
- package/docs/web/themes-author.md +2 -2
- package/docs/web-ui.md +2 -2
- package/harness.config.json +1 -1
- package/js/README.md +8 -7
- package/js/build/driver.mjs +26 -95
- package/js/build/emit-claude.mjs +76 -81
- package/js/build/generate.mjs +0 -1
- package/js/build/render.mjs +10 -3
- package/js/build/source.mjs +105 -1
- package/js/build/stubs.mjs +25 -4
- package/js/cli-table.json +8 -8
- package/js/hosts/hooks.mjs +91 -59
- package/js/hosts/hosts.mjs +5 -21
- package/js/hosts/installs.mjs +4 -6
- package/js/hosts/mcp.mjs +3 -13
- package/js/hosts/native.mjs +28 -51
- package/js/hosts/opencode.mjs +1 -1
- package/js/hosts/settings.mjs +4 -78
- package/js/inspect/checks-authoring.mjs +153 -117
- package/js/inspect/checks-repo.mjs +45 -1
- package/js/inspect/diff.mjs +11 -3
- package/js/inspect/doctor.mjs +3 -3
- package/js/inspect/excludes.mjs +0 -4
- package/js/inspect/inventory.mjs +1 -2
- package/js/inspect/status.mjs +4 -4
- package/js/maintain/migrate.mjs +5 -5
- package/js/maintain/setup.mjs +3 -5
- package/js/maintain/uninstall.mjs +8 -42
- package/js/ui/tui.mjs +1 -1
- package/js/web/actions.mjs +4 -5
- package/js/web/api.mjs +10 -5
- package/package.json +1 -2
- package/registry.json +7 -0
- package/src/AGENT.md.tmpl +86 -65
- package/src/agents/_template.md +3 -3
- package/src/agents/advocate.md +3 -3
- package/src/agents/architect.md +3 -3
- package/src/agents/developer.md +4 -4
- package/src/agents/docs.md +3 -3
- package/src/agents/empiricist.md +4 -4
- package/src/agents/explorer.md +4 -4
- package/src/agents/framer.md +3 -3
- package/src/agents/historian.md +3 -3
- package/src/agents/operator.md +3 -3
- package/src/agents/pragmatist.md +3 -3
- package/src/agents/researcher.md +7 -7
- package/src/agents/reviewer.md +3 -3
- package/src/agents/security.md +3 -3
- package/src/agents/skeptic.md +3 -3
- package/src/agents/steward.md +3 -3
- package/src/agents/tester.md +3 -3
- package/src/agents/user-advocate.md +3 -3
- package/src/agents/visionary.md +3 -3
- package/src/doctrines/README.md +4 -3
- package/src/doctrines/comms.md +4 -4
- package/src/doctrines/craft.md +20 -20
- package/src/doctrines/ops.md +19 -19
- package/src/doctrines/process.md +20 -20
- package/src/doctrines/rigor.md +22 -22
- package/src/laws/universal.md +26 -44
- package/src/memory/README.md +3 -3
- package/src/modes/foreman.md +1 -1
- package/src/notebook/README.md +1 -1
- package/src/ontology/universal.md +8 -10
- package/src/postures/artisan.md +2 -2
- package/src/skills/_self-improvement.md +4 -4
- package/src/skills/_template.md +1 -1
- package/src/skills/brainstorm.md +3 -3
- package/src/skills/bruno-collection-generator.md +6 -6
- package/src/skills/bruno-test-writer.md +5 -5
- package/src/skills/ci-fix.md +4 -4
- package/src/skills/commit.md +11 -10
- package/src/skills/consolidate-memory.md +4 -4
- package/src/skills/council.md +2 -2
- package/src/skills/daydream/SKILL.md +10 -3
- package/src/skills/daydream/VENDOR.md +10 -2
- package/src/skills/daydream/instructions.md +2 -2
- package/src/skills/debug.md +4 -4
- package/src/skills/deps-audit.md +3 -3
- package/src/skills/develop.md +7 -7
- package/src/skills/document-project.md +6 -6
- package/src/skills/explain-changes/SKILL.md +69 -0
- package/src/skills/explain-changes/VENDOR.md +20 -0
- package/src/skills/explain-changes/scripts/render_changes.mjs +558 -0
- package/src/skills/forge-mcp.md +4 -4
- package/src/skills/fresh-eyes.md +4 -4
- package/src/skills/frontend-design.md +1 -1
- package/src/skills/geneseed.md +4 -4
- package/src/skills/git-archaeology.md +5 -5
- package/src/skills/git-rescue.md +7 -7
- package/src/skills/handoff.md +3 -3
- package/src/skills/ingest.md +5 -5
- package/src/skills/migrate.md +8 -8
- package/src/skills/parallel-agents.md +2 -2
- package/src/skills/pipeline.md +2 -2
- package/src/skills/plan.md +3 -3
- package/src/skills/ponytail.md +2 -2
- package/src/skills/profile.md +2 -2
- package/src/skills/prose.md +1 -1
- package/src/skills/prototype.md +1 -1
- package/src/skills/react-view-transitions/SKILL.md +1 -1
- package/src/skills/react-view-transitions/VENDOR.md +7 -2
- package/src/skills/refactor.md +3 -3
- package/src/skills/release.md +5 -5
- package/src/skills/repo-map.md +4 -4
- package/src/skills/research.md +9 -9
- package/src/skills/review-response.md +4 -4
- package/src/skills/rule.md +3 -3
- package/src/skills/security-audit.md +5 -5
- package/src/skills/ship.md +5 -5
- package/src/skills/skill-forge.md +4 -4
- package/src/skills/token-report/SKILL.md +2 -3
- package/src/skills/token-report/VENDOR.md +2 -3
- package/src/skills/token-report/scripts/token_report.mjs +7 -121
- package/src/skills/wiki.md +5 -5
- package/src/skills/workflow.md +3 -4
- package/src/skills/worktree.md +4 -4
- package/themes/_TEMPLATE.json +37 -39
- package/themes/biker.json +37 -39
- package/themes/commentator.json +37 -39
- package/themes/cyberpunk.json +37 -39
- package/themes/gamer.json +37 -39
- package/themes/imperial.json +37 -39
- package/themes/joker.json +37 -39
- package/themes/marvin.json +37 -39
- package/themes/mean.json +37 -39
- package/themes/military.json +37 -39
- package/themes/neutral.json +37 -39
- package/themes/pirate.json +37 -39
- package/themes/sports.json +37 -39
- package/themes/verstappen.json +37 -39
- package/themes/wizard.json +37 -39
- package/web/dist/assets/Activity-BjVp522q.js +1 -0
- package/web/dist/assets/ActivityDetail-8ep1bspW.js +1 -0
- package/web/dist/assets/Dashboard-OkC_PCH6.js +2 -0
- package/web/dist/assets/{Docs-DrJNDPwI.js → Docs-CKPWz00o.js} +2 -2
- package/web/dist/assets/FilterInput-CVyqmYWM.js +1 -0
- package/web/dist/assets/GreenhouseView-D0cbCJ5m.js +2 -0
- package/web/dist/assets/Installs-BtGGdoTt.js +2 -0
- package/web/dist/assets/JournalView-Ckl4dmzR.js +1 -0
- package/web/dist/assets/Laws-CLpdRRk8.js +3 -0
- package/web/dist/assets/Library-wPPuKQrI.js +1 -0
- package/web/dist/assets/{Markdown-BQAvN8xx.js → Markdown-CkFyfgNf.js} +14 -14
- package/web/dist/assets/OperatorHudView-CiAkAkQL.js +2 -0
- package/web/dist/assets/Personal-D8CnDo9v.js +1 -0
- package/web/dist/assets/Tabs-B4HNrrCs.js +1 -0
- package/web/dist/assets/drawable-BiwaOo6x.js +1 -0
- package/web/dist/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-YINYabwD.woff2 +0 -0
- package/web/dist/assets/index-C1yil5j1.js +2 -0
- package/web/dist/assets/index-CDPDk2vj.css +1 -0
- package/web/dist/assets/{react-DbkvxWG5.js → react-B9Z_tXRV.js} +1 -1
- package/web/dist/assets/sections-GalMS0Zv.js +1 -0
- package/web/dist/assets/usePoll-O0sJEw1Y.js +1 -0
- package/web/dist/index.html +4 -4
- package/web/src/pages/Laws.jsx +616 -361
- package/adapters/copilot/README.md +0 -105
- package/web/dist/assets/Activity-CH-7cPIM.js +0 -1
- package/web/dist/assets/ActivityDetail-DZvz0xS5.js +0 -1
- package/web/dist/assets/CatalogRow-DftXpZHY.js +0 -1
- package/web/dist/assets/Diff-DvwdG0L9.js +0 -2
- package/web/dist/assets/Doctor-OTmwmYZg.js +0 -1
- package/web/dist/assets/FilterInput-BakJLvaV.js +0 -1
- package/web/dist/assets/GreenhouseView-XxrAdriX.js +0 -2
- package/web/dist/assets/Harness-C_LYNk5c.js +0 -1
- package/web/dist/assets/JournalView-BcuyeEaR.js +0 -1
- package/web/dist/assets/Laws-CWHrTKY4.js +0 -3
- package/web/dist/assets/Library-CA9IHghn.js +0 -1
- package/web/dist/assets/OperatorHudView-BMRY1Tt4.js +0 -2
- package/web/dist/assets/Profile-wceET1JY.js +0 -1
- package/web/dist/assets/Rules-BEXvY4Tx.js +0 -1
- package/web/dist/assets/Settings-DS2zF0Ez.js +0 -1
- package/web/dist/assets/Skills-DahlOJuI.js +0 -1
- package/web/dist/assets/StatusBadge-Cg6j-NJL.js +0 -1
- package/web/dist/assets/activity-iWaWVtsR.js +0 -1
- package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
- package/web/dist/assets/drawable-BTHjTzF7.js +0 -1
- package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
- package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
- package/web/dist/assets/index-AN5Emnrg.js +0 -2
- package/web/dist/assets/index-Ruh93A3A.css +0 -1
- package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/web/dist/assets/lawCats-Btjp3Hk_.js +0 -1
- package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
- package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
- package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
- package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/web/dist/assets/rolldown-runtime-CbXtAM7H.js +0 -1
package/js/build/emit-claude.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { loadAgentOverrides, writeNativeLayer } from '../hosts/native.mjs';
|
|
|
11
11
|
import { ensureAgentOverridesStub } from '../hosts/opencode.mjs';
|
|
12
12
|
import {
|
|
13
13
|
managedBlockRemove, managedBlockWrite, mergeClaudeSettings,
|
|
14
|
-
unwireClaudeExcludes, unwireClaudeSettings,
|
|
14
|
+
unwireClaudeExcludes, unwireClaudeSettings, wireClaudeExcludes,
|
|
15
15
|
} from '../hosts/settings.mjs';
|
|
16
16
|
import { writeText } from '../lib/fs.mjs';
|
|
17
17
|
import { isTruthy } from '../lib/json.mjs';
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
} from './emit-common.mjs';
|
|
22
22
|
import { renderAll, renderFile } from './render.mjs';
|
|
23
23
|
import {
|
|
24
|
-
BOB_RULES_STUB, ensureExcludesStub, ensureMemoryIndex, ensureNotebookIndex,
|
|
24
|
+
BOB_RULES_STUB, ensureContextStub, ensureExcludesStub, ensureMemoryIndex, ensureNotebookIndex,
|
|
25
|
+
ensureProfileStub,
|
|
25
26
|
ensureRulesStub, ensureWikiStub,
|
|
26
27
|
} from './stubs.mjs';
|
|
27
28
|
import { writeVersion } from './version.mjs';
|
|
@@ -36,7 +37,7 @@ import { existsSync, mkdirSync } from 'node:fs';
|
|
|
36
37
|
* `claudeWire` below for the CLAUDE.md managed-block merge and the settings file. PRUNE,
|
|
37
38
|
* MANIFEST and VERIFY are the driver's, in `bin/build-driver.mjs`.
|
|
38
39
|
*
|
|
39
|
-
* Six emits route through here — claude, bob and
|
|
40
|
+
* Six emits route through here — claude, bob and openclaude at both scopes — so this one
|
|
40
41
|
* job is what puts two thirds of the matrix on the seam.
|
|
41
42
|
*
|
|
42
43
|
* `claudeMdText` is the payload's one item that is not a file this process wrote. WIRE
|
|
@@ -76,9 +77,11 @@ export function emitClaudeRender(cfg, job) {
|
|
|
76
77
|
// `themed_rel` and `lstripNewlines` are, so the two verdicts stay distinguishable.
|
|
77
78
|
if (!prefix) return tmplItem.text;
|
|
78
79
|
const ptheme = { ...theme };
|
|
79
|
-
for (const tok of ['DIR_LAWS', 'DIR_AGENTS', 'DIR_SKILLS', 'DIR_MEMORY', 'DIR_NOTEBOOK'
|
|
80
|
+
for (const tok of ['DIR_LAWS', 'DIR_AGENTS', 'DIR_SKILLS', 'DIR_MEMORY', 'DIR_NOTEBOOK',
|
|
81
|
+
'DIR_DOCTRINES']) {
|
|
80
82
|
ptheme[tok] = prefix + (ptheme[tok] ?? tok.split('_').slice(1).join('_').toLowerCase());
|
|
81
83
|
}
|
|
84
|
+
ptheme.AT_HARNESS = prefix;
|
|
82
85
|
// Same catalogue decision as the renderAll above — a re-render that forgot it would
|
|
83
86
|
// quietly put the stripped tables back.
|
|
84
87
|
return renderFile(cfg, tmplItem.src, ptheme, footprint, '', new Set(), nativeCatalog);
|
|
@@ -87,7 +90,6 @@ export function emitClaudeRender(cfg, job) {
|
|
|
87
90
|
const owned = [];
|
|
88
91
|
const agentText = items.find((i) => i.rel === 'AGENT.md' && i.text !== null)?.text ?? null;
|
|
89
92
|
const isBob = host === 'bob';
|
|
90
|
-
const isCopilot = host === 'copilot';
|
|
91
93
|
|
|
92
94
|
// Bob's always-injected channel is the rules folder. At GLOBAL scope rules/geneseed.md
|
|
93
95
|
// IS the preamble (a global ~/.bob/AGENTS.md is not auto-loaded), and its pointers need
|
|
@@ -102,19 +104,30 @@ export function emitClaudeRender(cfg, job) {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
ensureAgentOverridesStub(cfg, cfgDir);
|
|
105
|
-
// Bob
|
|
107
|
+
// Bob gives the model a skill's text but not its directory, so a folder skill's
|
|
108
|
+
// `<this-skill-directory>` is resolved here. Per repo it is relative to the workspace root,
|
|
109
|
+
// where Bob runs commands, so a committed `.bob/` stays valid on a teammate's checkout;
|
|
110
|
+
// globally there is no workspace to be relative to, so it is absolute.
|
|
111
|
+
const skillDirOf = !isBob ? null
|
|
112
|
+
: scope === 'project' ? (name) => relPosix(path.dirname(cfgDir), path.join(cfgDir, 'skills', name))
|
|
113
|
+
: (name) => path.join(cfgDir, 'skills', name);
|
|
114
|
+
// Bob's and OpenClaude's agents/skills use the Claude dialect verbatim.
|
|
106
115
|
// `manifestExisted` is deliberately not passed: the Python does not pass it either, so
|
|
107
116
|
// the pre-manifest header line is unreachable from this emit on both sides.
|
|
108
117
|
const { nAgents, nSkills, written } = writeNativeLayer(
|
|
109
118
|
items, path.join(cfgDir, 'agents'), path.join(cfgDir, 'skills'),
|
|
110
119
|
loadAgentOverrides(cfgDir),
|
|
111
|
-
{ host:
|
|
120
|
+
{ host: 'claude', oldOwned, cfg: cfgDir, src: cfg.src, skillDirOf });
|
|
112
121
|
for (const p of written) owned.push(relPosix(cfgDir, p));
|
|
113
122
|
|
|
114
123
|
const memStatus = globalMemory(cfgDir, items, out, cfg.src);
|
|
115
124
|
ensureMemoryIndex(path.join(cfgDir, 'memory'));
|
|
116
125
|
const nbStatus = globalNotebook(cfgDir, items, out, cfg.src);
|
|
117
126
|
ensureNotebookIndex(path.join(cfgDir, 'notebook'));
|
|
127
|
+
// context.json too: the root names it as the project's own manifest, and until 2026-10 this
|
|
128
|
+
// emit never seeded one — the bundle and OpenCode did — so §9 pointed at a file that was not
|
|
129
|
+
// there. The context hook reads it from this dir (`resolveContextSets`' last candidate).
|
|
130
|
+
ensureContextStub(cfgDir);
|
|
118
131
|
ensureWikiStub(cfgDir);
|
|
119
132
|
ensureRulesStub(cfgDir);
|
|
120
133
|
ensureProfileStub(cfgDir);
|
|
@@ -128,7 +141,7 @@ export function emitClaudeRender(cfg, job) {
|
|
|
128
141
|
const gi = path.join(cfgDir, '.gitignore');
|
|
129
142
|
const giLines = (host === 'claude' || host === 'openclaude' ? ['settings.local.json']
|
|
130
143
|
: host === 'bob' ? ['settings.json'] : [])
|
|
131
|
-
.concat(['wiki.jsonc', 'agent-overrides.json']);
|
|
144
|
+
.concat(['context.json', 'wiki.jsonc', 'agent-overrides.json']);
|
|
132
145
|
if (!existsSync(gi)) {
|
|
133
146
|
writeText(gi, `${giLines.join('\n')}\n`);
|
|
134
147
|
owned.push('.gitignore');
|
|
@@ -194,7 +207,6 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
194
207
|
const old = oldManaged && typeof oldManaged === 'object' && !Array.isArray(oldManaged)
|
|
195
208
|
? oldManaged : {};
|
|
196
209
|
const isBob = host === 'bob';
|
|
197
|
-
const isCopilot = host === 'copilot';
|
|
198
210
|
const managed = {};
|
|
199
211
|
|
|
200
212
|
// CLAUDE.md — Claude auto-loads it by location; merge as a delimited block so any user
|
|
@@ -228,80 +240,63 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
228
240
|
// settings.local.json — the personal, untracked file — never the team-shared
|
|
229
241
|
// settings.json, which would hand every teammate failing hooks pointing at this machine's
|
|
230
242
|
// node and this machine's checkout. (Bob documents no local variant, so it keeps
|
|
231
|
-
// settings.json.)
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
settingsPath, scope, oldSf === settingsName ? get(old, 'settings_hooks') : null, hookOpts,
|
|
260
|
-
doctrines, excludeRules, host, cfgDir,
|
|
261
|
-
);
|
|
262
|
-
managed.settings_hooks = managedHooks;
|
|
243
|
+
// settings.json.)
|
|
244
|
+
// Bob GLOBAL: its hooks doc puts the global file at `~/.bob/settings/settings.json`
|
|
245
|
+
// (nested), where an older emit wrote `~/.bob/settings.json`; the `oldSf !==
|
|
246
|
+
// settingsName` branch below unwires the old file, so a re-emit migrates. Bob's project
|
|
247
|
+
// file stays `.bob/settings.json`.
|
|
248
|
+
const settingsName = scope === 'project' && !isBob ? 'settings.local.json'
|
|
249
|
+
: isBob && scope === 'global' ? path.join('settings', 'settings.json') : 'settings.json';
|
|
250
|
+
const settingsPath = path.join(cfgDir, settingsName);
|
|
251
|
+
mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
252
|
+
managed.settings_file = settingsName;
|
|
253
|
+
// Migration: an older install wired hooks/excludes into a different file — unwire the
|
|
254
|
+
// recorded claims there, or they linger (and run) forever.
|
|
255
|
+
const oldSf = get(old, 'settings_file') || 'settings.json';
|
|
256
|
+
if (oldSf !== settingsName) {
|
|
257
|
+
unwireClaudeSettings(path.join(cfgDir, oldSf), get(old, 'settings_hooks') || []);
|
|
258
|
+
unwireClaudeExcludes(path.join(cfgDir, oldSf), get(old, 'settings_excludes') || []);
|
|
259
|
+
}
|
|
260
|
+
// The merge prunes recorded groups that are no longer canonical and returns the
|
|
261
|
+
// COMPLETE current claim set — store it as-is; unioning with prior would resurrect the
|
|
262
|
+
// stale claims.
|
|
263
|
+
// `doctrines` decides whether the git-gate group is part of the canonical claim set. It
|
|
264
|
+
// travels from `cfg` rather than being re-read off the deployment: this is the emit that
|
|
265
|
+
// DECIDES the install's packs, so the marker on disk is still the previous build's.
|
|
266
|
+
const [, managedHooks] = mergeClaudeSettings(
|
|
267
|
+
settingsPath, scope, oldSf === settingsName ? get(old, 'settings_hooks') : null, hookOpts,
|
|
268
|
+
doctrines, excludeRules, host, cfgDir,
|
|
269
|
+
);
|
|
270
|
+
managed.settings_hooks = managedHooks;
|
|
263
271
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
} else if (priorExcl.length && isBob) {
|
|
286
|
-
// Self-heal older Bob installs: earlier versions wrote the global AGENTS.md into
|
|
287
|
-
// claudeMdExcludes here. The key is Claude-only and its Bob semantics are unknown, so
|
|
288
|
-
// a re-emit removes it instead of carrying it forward.
|
|
289
|
-
unwireClaudeExcludes(settingsPath, priorExcl);
|
|
290
|
-
} else if (priorExcl.length) {
|
|
291
|
-
managed.settings_excludes = priorExcl;
|
|
272
|
+
// Project-bypasses-global (Claude only): a PROJECT install suppresses the GLOBAL
|
|
273
|
+
// ~/.claude/CLAUDE.md while cwd is this repo, via Claude's native claudeMdExcludes.
|
|
274
|
+
// Written only when this run actually emitted the project's own preamble (never
|
|
275
|
+
// suppress with no replacement); GENESEED_STACK_GLOBAL=1 opts out, and a re-emit with
|
|
276
|
+
// it set strips a prior exclude. Bob never gets one: its bypass is the same-named
|
|
277
|
+
// workspace rules file.
|
|
278
|
+
const priorRaw = get(old, 'settings_excludes');
|
|
279
|
+
const priorExcl = Array.isArray(priorRaw) ? priorRaw : [];
|
|
280
|
+
if (scope === 'project' && hasAgentText && preambleExclude) {
|
|
281
|
+
const wantExcl = [preambleExclude];
|
|
282
|
+
if (process.env.GENESEED_STACK_GLOBAL) {
|
|
283
|
+
unwireClaudeExcludes(settingsPath, wantExcl);
|
|
284
|
+
managed.settings_excludes = [];
|
|
285
|
+
} else {
|
|
286
|
+
const addedExcl = wireClaudeExcludes(settingsPath, wantExcl);
|
|
287
|
+
// Claim only what Geneseed itself wired (prior + newly added) — folding `wantExcl`
|
|
288
|
+
// in unconditionally would claim a user's own pre-existing exclude, and uninstall
|
|
289
|
+
// would then strip it. `sorted(set(a) | set(b))` — Python sorts strings by code
|
|
290
|
+
// point and so does the default `Array.sort`, which is why no comparator is passed.
|
|
291
|
+
managed.settings_excludes = [...new Set([...priorExcl, ...addedExcl])].sort();
|
|
292
292
|
}
|
|
293
|
-
} else if (
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
managed.
|
|
300
|
-
const legacy = get(old, 'copilot_hooks');
|
|
301
|
-
if (Array.isArray(legacy) && legacy.length) unwireCopilotSettings(settingsPath, legacy);
|
|
302
|
-
const [, managedHooks] = mergeClaudeSettings(settingsPath, scope,
|
|
303
|
-
Array.isArray(legacy) ? null : get(old, 'settings_hooks'), hookOpts, null, [], host, cfgDir);
|
|
304
|
-
managed.settings_hooks = managedHooks;
|
|
293
|
+
} else if (priorExcl.length && isBob) {
|
|
294
|
+
// Self-heal older Bob installs: earlier versions wrote the global AGENTS.md into
|
|
295
|
+
// claudeMdExcludes here. The key is Claude-only and its Bob semantics are unknown, so
|
|
296
|
+
// a re-emit removes it instead of carrying it forward.
|
|
297
|
+
unwireClaudeExcludes(settingsPath, priorExcl);
|
|
298
|
+
} else if (priorExcl.length) {
|
|
299
|
+
managed.settings_excludes = priorExcl;
|
|
305
300
|
}
|
|
306
301
|
return managed;
|
|
307
302
|
}
|
package/js/build/generate.mjs
CHANGED
|
@@ -173,7 +173,6 @@ export const DEFAULT_EMIT = new Map([
|
|
|
173
173
|
['opencode global', 'opencode-global'], ['opencode project', 'opencode'],
|
|
174
174
|
['claude global', 'claude-global'], ['claude project', 'claude'],
|
|
175
175
|
['bob global', 'bob-global'], ['bob project', 'bob'],
|
|
176
|
-
['copilot global', 'copilot-global'], ['copilot project', 'copilot'],
|
|
177
176
|
['openclaude global', 'openclaude-global'], ['openclaude project', 'openclaude'],
|
|
178
177
|
]);
|
|
179
178
|
|
package/js/build/render.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import { readText } from '../lib/fs.mjs';
|
|
|
34
34
|
import { parseJson } from '../lib/json.mjs';
|
|
35
35
|
import { normcase, comparePaths } from '../lib/paths.mjs';
|
|
36
36
|
// `js/build/source.mjs` imports nothing but node builtins, so this direction cannot cycle.
|
|
37
|
-
import { PACK_ORDER, readPackText } from './source.mjs';
|
|
37
|
+
import { PACK_ORDER, readPackText, resolveRuleIds } from './source.mjs';
|
|
38
38
|
|
|
39
39
|
/** Document STRUCTURE is theme-INDEPENDENT — mirrors `_build_render.STRUCTURE`. */
|
|
40
40
|
export const STRUCTURE = {
|
|
@@ -42,6 +42,10 @@ export const STRUCTURE = {
|
|
|
42
42
|
SCRIPT: 'Script', SCRIPTS: 'Scripts',
|
|
43
43
|
DIR_LAWS: 'laws', DIR_AGENTS: 'agents', DIR_SKILLS: 'skills', DIR_MEMORY: 'memory',
|
|
44
44
|
DIR_NOTEBOOK: 'notebook', DIR_ONTOLOGY: 'ontology', DIR_DOCTRINES: 'doctrines',
|
|
45
|
+
// Where the harness's own files sit, as seen from the root instruction file: empty when they
|
|
46
|
+
// are its siblings (`files`, OpenCode), `.claude/`-style when the root sits a level above them —
|
|
47
|
+
// set per emit by `prefixedAgentText` in js/build/emit-claude.mjs, like the DIR_* prefixes.
|
|
48
|
+
AT_HARNESS: '',
|
|
45
49
|
// Ontology section names. Theme-INDEPENDENT on purpose: a citation reads
|
|
46
50
|
// `({{ONTOLOGY}}: {{ONT_TELOS}})` — token on both sides, so the heading and every
|
|
47
51
|
// reference to it move together and a rename can never desync them.
|
|
@@ -216,7 +220,8 @@ function readSource(cfg, p) {
|
|
|
216
220
|
let seen = SRC_TEXT.get(cfg);
|
|
217
221
|
if (!seen) SRC_TEXT.set(cfg, (seen = new Map()));
|
|
218
222
|
let text = seen.get(p);
|
|
219
|
-
|
|
223
|
+
// Rule ids resolve here, once per file, so every stage after sees the addressed form.
|
|
224
|
+
if (text === undefined) seen.set(p, (text = resolveRuleIds(readText(p), cfg.src)));
|
|
220
225
|
return text;
|
|
221
226
|
}
|
|
222
227
|
|
|
@@ -250,7 +255,9 @@ export function renderFile(cfg, filePath, theme, footprint = 'full', lawsPrefix
|
|
|
250
255
|
function registerBody(cfg, theme, dir, selected, fallback) {
|
|
251
256
|
for (const name of [selected, fallback]) {
|
|
252
257
|
const p = path.join(cfg.src, dir, `${name}.md`);
|
|
253
|
-
if (existsSync(p) && statSync(p).isFile())
|
|
258
|
+
if (existsSync(p) && statSync(p).isFile()) {
|
|
259
|
+
return substitute(resolveRuleIds(readText(p), cfg.src), theme).trim();
|
|
260
|
+
}
|
|
254
261
|
}
|
|
255
262
|
return '';
|
|
256
263
|
}
|
package/js/build/source.mjs
CHANGED
|
@@ -37,7 +37,7 @@ import path from 'node:path';
|
|
|
37
37
|
* on raw or folded text, so one behaviour serves both.
|
|
38
38
|
*/
|
|
39
39
|
const _packTexts = new Map();
|
|
40
|
-
export function
|
|
40
|
+
export function readRawText(file) {
|
|
41
41
|
const key = path.resolve(file);
|
|
42
42
|
const mtime = statSync(file).mtimeMs;
|
|
43
43
|
const hit = _packTexts.get(key);
|
|
@@ -47,6 +47,110 @@ export function readPackText(file) {
|
|
|
47
47
|
return text;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A doctrine pack as the render sees it: ids resolved, so the heading reads
|
|
52
|
+
* `### {{DOCTRINE}} process 5 — {{DOC_CONSENT_BEFORE_PUSH}}` again. Every reader that keys on the
|
|
53
|
+
* `<pack> <n>` address (`knownRuleIds`, the exclusion filter, the survives gate) goes through
|
|
54
|
+
* here and keeps working unchanged — the address is computed, only the source stopped typing it.
|
|
55
|
+
*/
|
|
56
|
+
export function readPackText(file) {
|
|
57
|
+
return resolveRuleIds(readRawText(file), path.dirname(path.dirname(file)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* STABLE RULE IDS. Every law and doctrine rule is DECLARED by its heading —
|
|
62
|
+
* `### {{LAW:verify-before-asserting}} Verify Before Asserting` — and CITED by its id —
|
|
63
|
+
* `{{LAW:verify-before-asserting}}`. The id is permanent; the number is not stored anywhere. It
|
|
64
|
+
* is the rule's position (Roman in `laws/universal.md`, Arabic within its pack), computed here
|
|
65
|
+
* at render time, so removing or reordering a rule renumbers every heading after it and rewires
|
|
66
|
+
* nothing: no citation ever named the number.
|
|
67
|
+
*
|
|
68
|
+
* WHY CITATIONS RENDER AS THE NAME, NOT THE NUMBER. A citation is the one place a number would
|
|
69
|
+
* leak into text that outlives the build — an agent reads "Law III", writes "Law III" into its
|
|
70
|
+
* memory, and the memory is wrong the day the canon moves. The rendered harness models what it
|
|
71
|
+
* asks of the agent: a rule is cited by its principle, in English, identical in every voice. The
|
|
72
|
+
* number survives only where it is structural — the rule's own heading.
|
|
73
|
+
*
|
|
74
|
+
* Retired ids live in `RETIRED_RULE_IDS` so the doctor can refuse their reuse: an id that meant
|
|
75
|
+
* one thing in someone's notes must never come back meaning another.
|
|
76
|
+
*/
|
|
77
|
+
const DECL_RE = /^### \{\{(LAW|DOCTRINE):([a-z0-9]+(?:-[a-z0-9]+)*)\}\}[ \t]+(\S[^\n]*?)[ \t]*$/gm;
|
|
78
|
+
const CITE_RE = /\{\{(LAW|DOCTRINE):([a-z0-9-]+)\}\}/g;
|
|
79
|
+
export const RULE_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Ids that no longer name a live rule, and must never be declared again. `liveAs` names the one
|
|
83
|
+
* tier that may still declare it — `external-gate` was Law IX before it became a rigor rule, so
|
|
84
|
+
* the doctrine keeps it and `{{LAW:external-gate}}` is refused; `null` means gone for good.
|
|
85
|
+
*/
|
|
86
|
+
export const RETIRED_RULE_IDS = {
|
|
87
|
+
'external-gate': { was: 'Law IX', liveAs: 'DOCTRINE', note: 'moved to the rigor pack' },
|
|
88
|
+
'absence-is-a-claim': { was: 'Law XI', liveAs: null, note: 'folded into verify-before-asserting' },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const ROMAN = [[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'], [90, 'XC'],
|
|
92
|
+
[50, 'L'], [40, 'XL'], [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']];
|
|
93
|
+
export function toRoman(n) {
|
|
94
|
+
let out = '';
|
|
95
|
+
for (const [v, r] of ROMAN) while (n >= v) { out += r; n -= v; }
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** `sealed-secrets` -> `LEX_SEALED_SECRETS`; a doctrine rule's title key is `DOC_<ID>`. */
|
|
100
|
+
export const titleKey = (kind, id) =>
|
|
101
|
+
`${kind === 'LAW' ? 'LEX' : 'DOC'}_${id.toUpperCase().replaceAll('-', '_')}`;
|
|
102
|
+
|
|
103
|
+
/** The files that may declare rules, in canon order: `[kind, pack|null, file]`. */
|
|
104
|
+
export function canonFiles(src = SRC) {
|
|
105
|
+
return [['LAW', null, path.join(src, 'laws', 'universal.md')],
|
|
106
|
+
...PACK_ORDER.map((p) => ['DOCTRINE', p, path.join(src, 'doctrines', `${p}.md`)])];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const _canons = new Map();
|
|
110
|
+
/**
|
|
111
|
+
* Every declared rule, in render order: `{ kind, pack, id, name, n, label }`, where `label` is
|
|
112
|
+
* the address the heading renders (`III`, `process 5`). Memoised on the raw texts' identity —
|
|
113
|
+
* `readRawText` hands back the same string until a file's mtime moves.
|
|
114
|
+
*/
|
|
115
|
+
export function ruleCanon(src = SRC) {
|
|
116
|
+
const files = canonFiles(src);
|
|
117
|
+
const texts = files.map(([, , f]) => { try { return readRawText(f); } catch { return ''; } });
|
|
118
|
+
const key = path.resolve(src);
|
|
119
|
+
const hit = _canons.get(key);
|
|
120
|
+
if (hit && hit.texts.every((t, i) => t === texts[i])) return hit.canon;
|
|
121
|
+
const rules = [];
|
|
122
|
+
files.forEach(([kind, pack], i) => {
|
|
123
|
+
let n = 0;
|
|
124
|
+
for (const m of texts[i].matchAll(DECL_RE)) {
|
|
125
|
+
if (m[1] !== kind) continue; // a wrong-tier declaration is the doctor's to name
|
|
126
|
+
n += 1;
|
|
127
|
+
rules.push({ kind, pack, id: m[2], name: m[3], n, label: kind === 'LAW' ? toRoman(n) : `${pack} ${n}` });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
const byId = new Map();
|
|
131
|
+
for (const r of rules) if (!byId.has(r.id)) byId.set(r.id, r);
|
|
132
|
+
const canon = { rules, byId };
|
|
133
|
+
_canons.set(key, { texts, canon });
|
|
134
|
+
return canon;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolve every declaration and citation in `text` against the canon under `src`. An unknown id
|
|
139
|
+
* is left exactly as written — visible in the output, and named by the doctor — rather than
|
|
140
|
+
* guessed at.
|
|
141
|
+
*/
|
|
142
|
+
export function resolveRuleIds(text, src = SRC) {
|
|
143
|
+
if (!text.includes('{{LAW:') && !text.includes('{{DOCTRINE:')) return text;
|
|
144
|
+
const { byId } = ruleCanon(src);
|
|
145
|
+
const known = (kind, id) => { const r = byId.get(id); return r && r.kind === kind ? r : null; };
|
|
146
|
+
return text
|
|
147
|
+
.replace(DECL_RE, (whole, kind, id) => {
|
|
148
|
+
const r = known(kind, id);
|
|
149
|
+
return r ? `### {{${kind}}} ${r.label} — {{${titleKey(kind, id)}}}` : whole;
|
|
150
|
+
})
|
|
151
|
+
.replace(CITE_RE, (whole, kind, id) => { const r = known(kind, id); return r ? `*${r.name}*` : whole; });
|
|
152
|
+
}
|
|
153
|
+
|
|
50
154
|
/**
|
|
51
155
|
* `_build_core.ROOT` — the checkout, from this file's own location (`js/..`).
|
|
52
156
|
*
|
package/js/build/stubs.mjs
CHANGED
|
@@ -137,8 +137,13 @@ the project's own. Follow the root \`AGENTS.md\`.
|
|
|
137
137
|
/** `_build_render.PROFILE_FILE`. */
|
|
138
138
|
const PROFILE_FILE = 'PROFILE.md';
|
|
139
139
|
|
|
140
|
-
/**
|
|
141
|
-
|
|
140
|
+
/**
|
|
141
|
+
* `_build_render.PROFILE_STUB`.
|
|
142
|
+
*
|
|
143
|
+
* Exported for the web console: `/api/profile` answers `seeded: true` while PROFILE.md is
|
|
144
|
+
* still byte-for-byte this text, which is how the Overview knows to ask for it to be filled in.
|
|
145
|
+
*/
|
|
146
|
+
export const PROFILE_STUB = `\
|
|
142
147
|
# Your profile
|
|
143
148
|
|
|
144
149
|
Who you are and how you like to work — so the agent can meet you where you are
|
|
@@ -240,9 +245,25 @@ export const ensureBundleGitignore = stubWriter('.gitignore', BUNDLE_GITIGNORE);
|
|
|
240
245
|
* right after the store dir is created, but never invent the dir themselves). */
|
|
241
246
|
const storeIndexWriter = (file, header) => (dir) => { if (isDir(dir)) seed(path.join(dir, file), header); };
|
|
242
247
|
|
|
248
|
+
const MEMORY_INDEX = '# Memory Index\n';
|
|
249
|
+
const NOTEBOOK_INDEX = '# Notebook Index\n';
|
|
250
|
+
|
|
243
251
|
/** `_build_render.ensure_memory_index` — only inside an EXISTING store dir. */
|
|
244
|
-
export const ensureMemoryIndex = storeIndexWriter('MEMORY.md',
|
|
252
|
+
export const ensureMemoryIndex = storeIndexWriter('MEMORY.md', MEMORY_INDEX);
|
|
245
253
|
|
|
246
254
|
/** `_build_render.ensure_notebook_index`. */
|
|
247
|
-
export const ensureNotebookIndex = storeIndexWriter('NOTEBOOK.md',
|
|
255
|
+
export const ensureNotebookIndex = storeIndexWriter('NOTEBOOK.md', NOTEBOOK_INDEX);
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The seeds of the files the session-start injection reads (`SESSION_FILES` in
|
|
259
|
+
* `js/hosts/hooks.mjs`, and its twin in the OpenCode context plugin). Both carry the SHA-256
|
|
260
|
+
* of these bodies rather than importing them — the hook path pays for every import, and the
|
|
261
|
+
* plugin is deployed on its own — so an untouched seed is skipped instead of injected as
|
|
262
|
+
* noise. `tests/unit/claude.test.mjs` gates both hash lists against this map: edit a stub and
|
|
263
|
+
* the two copies must move with it.
|
|
264
|
+
*/
|
|
265
|
+
export const SESSION_SEEDS = {
|
|
266
|
+
'user-rules.md': RULES_STUB, 'PROFILE.md': PROFILE_STUB, 'wiki.jsonc': WIKI_STUB,
|
|
267
|
+
'MEMORY.md': MEMORY_INDEX, 'NOTEBOOK.md': NOTEBOOK_INDEX,
|
|
268
|
+
};
|
|
248
269
|
|
package/js/cli-table.json
CHANGED
|
@@ -297,7 +297,7 @@
|
|
|
297
297
|
],
|
|
298
298
|
"dest": "host",
|
|
299
299
|
"metavar": null,
|
|
300
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
300
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
301
301
|
"choices": null,
|
|
302
302
|
"default": null,
|
|
303
303
|
"required": false,
|
|
@@ -488,7 +488,7 @@
|
|
|
488
488
|
},
|
|
489
489
|
{
|
|
490
490
|
"name": "git-gate",
|
|
491
|
-
"help": "PreToolUse hook: ASK before a history-discarding git act (
|
|
491
|
+
"help": "PreToolUse hook: ASK before a history-discarding git act (Deletion Is Deliberate) and before every git commit/push (Consent Before Push backstop)",
|
|
492
492
|
"description": "",
|
|
493
493
|
"positionals": [],
|
|
494
494
|
"options": [
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
],
|
|
514
514
|
"dest": "host",
|
|
515
515
|
"metavar": null,
|
|
516
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
516
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
517
517
|
"choices": null,
|
|
518
518
|
"default": null,
|
|
519
519
|
"required": false,
|
|
@@ -528,7 +528,7 @@
|
|
|
528
528
|
],
|
|
529
529
|
"dest": "no_consent",
|
|
530
530
|
"metavar": null,
|
|
531
|
-
"help": "skip the
|
|
531
|
+
"help": "skip the Consent Before Push commit/push ask (set by the emit when the process pack or that rule is off); Deletion Is Deliberate still asks",
|
|
532
532
|
"choices": null,
|
|
533
533
|
"default": false,
|
|
534
534
|
"required": false,
|
|
@@ -766,7 +766,7 @@
|
|
|
766
766
|
},
|
|
767
767
|
{
|
|
768
768
|
"name": "rule-gate",
|
|
769
|
-
"help": "PreToolUse hook: force an ASK before any write to user-rules.md or memory (
|
|
769
|
+
"help": "PreToolUse hook: force an ASK before any write to user-rules.md or memory (Persist Insight backstop)",
|
|
770
770
|
"description": "",
|
|
771
771
|
"positionals": [],
|
|
772
772
|
"options": [
|
|
@@ -791,7 +791,7 @@
|
|
|
791
791
|
],
|
|
792
792
|
"dest": "host",
|
|
793
793
|
"metavar": null,
|
|
794
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
794
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
795
795
|
"choices": null,
|
|
796
796
|
"default": null,
|
|
797
797
|
"required": false,
|
|
@@ -975,7 +975,7 @@
|
|
|
975
975
|
},
|
|
976
976
|
{
|
|
977
977
|
"name": "tool-gate",
|
|
978
|
-
"help": "PreToolUse hook for hosts with no tool matcher (
|
|
978
|
+
"help": "PreToolUse hook for hosts with no tool matcher (Bob): git-gate and rule-gate fused behind one command, dispatched on the payload",
|
|
979
979
|
"description": "",
|
|
980
980
|
"positionals": [],
|
|
981
981
|
"options": [
|
|
@@ -1000,7 +1000,7 @@
|
|
|
1000
1000
|
],
|
|
1001
1001
|
"dest": "host",
|
|
1002
1002
|
"metavar": null,
|
|
1003
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
1003
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) asks; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
1004
1004
|
"choices": null,
|
|
1005
1005
|
"default": null,
|
|
1006
1006
|
"required": false,
|