geneseed 3.3.0 → 3.5.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 +323 -0
- package/DESIGN.md +11 -14
- package/QUICKSTART.md +3 -1
- package/README.md +53 -44
- package/SETUP.md +25 -27
- package/SHIPPED.md +26 -32
- package/adapters/bob/README.md +12 -9
- package/adapters/copilot/README.md +28 -26
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +1 -1
- package/adapters/opencode/README.md +8 -4
- package/adapters/opencode/plugins/geneseed-context.js +5 -3
- package/adapters/opencode/plugins/geneseed-workflow.js +30 -3
- package/adapters/opencode/workflows/_runtime.js +97 -7
- package/bin/build-driver.mjs +5 -1248
- package/bin/geneseed-cli.mjs +11 -6
- package/bin/geneseed-hook.mjs +4 -2
- package/docs/candidate-hosts.md +1 -1
- package/docs/design-history.md +37 -37
- package/docs/extending.md +49 -27
- package/docs/token-footprint.md +52 -26
- package/docs/web/agents.md +1 -1
- package/docs/web/claude-hooks.md +3 -2
- package/docs/web/collaboration.md +2 -2
- package/docs/web/first-session.md +2 -2
- package/docs/web/footprint.md +2 -2
- package/docs/web/harnesses.md +1 -1
- package/docs/web/model.md +1 -1
- package/docs/web/plugin-workflow.md +1 -0
- package/docs/web/rules.md +3 -3
- package/docs/web/setup-choices.md +2 -2
- package/docs/web/skills.md +1 -1
- package/docs/web/themes-author.md +1 -1
- package/docs/web-ui.md +18 -10
- package/harness.config.json +1 -1
- package/js/README.md +6 -5
- package/js/build/driver.mjs +1191 -0
- package/js/build/emit-claude.mjs +17 -10
- package/js/build/emit-common.mjs +4 -4
- package/js/build/generate.mjs +1 -1
- package/js/build/render.mjs +48 -44
- package/js/build/stubs.mjs +1 -1
- package/js/build/themes.mjs +4 -4
- package/js/build/version.mjs +12 -0
- package/js/cli-table.json +21 -6
- package/js/hosts/hooks.mjs +89 -74
- package/js/hosts/hosts.mjs +19 -2
- package/js/hosts/mcp.mjs +14 -20
- package/js/hosts/native.mjs +85 -25
- package/js/hosts/opencode.mjs +14 -0
- package/js/hosts/settings.mjs +52 -104
- package/js/inspect/checks-authoring.mjs +117 -3
- package/js/inspect/diff.mjs +1 -1
- package/js/inspect/doctor.mjs +1 -1
- package/js/inspect/excludes.mjs +1 -1
- package/js/inspect/inventory.mjs +12 -7
- package/js/inspect/scan.mjs +3 -14
- package/js/inspect/status.mjs +2 -2
- package/js/inspect/validate.mjs +1 -1
- package/js/lib/fs.mjs +23 -8
- package/js/lib/json.mjs +7 -1
- package/js/lib/proc.mjs +6 -4
- package/js/maintain/migrate.mjs +1 -1
- package/js/maintain/setup.mjs +1 -1
- package/js/maintain/uninstall.mjs +17 -15
- package/js/ui/cli.mjs +19 -0
- package/js/ui/menu.mjs +1 -1
- package/js/web/actions.mjs +4 -4
- package/js/web/activity.mjs +4 -4
- package/js/web/api.mjs +14 -11
- package/js/web/daemon.mjs +1 -1
- package/js/web/docs.mjs +12 -12
- package/js/web/handler.mjs +12 -5
- package/js/web/jobs.mjs +1 -1
- package/package.json +1 -1
- package/registry.json +86 -79
- package/src/AGENT.md.tmpl +83 -48
- package/src/agents/architect.md +4 -1
- package/src/agents/developer.md +2 -2
- package/src/agents/explorer.md +2 -0
- package/src/agents/operator.md +13 -0
- package/src/agents/pragmatist.md +12 -0
- package/src/agents/researcher.md +72 -0
- package/src/agents/reviewer.md +5 -2
- package/src/agents/security.md +7 -1
- package/src/agents/skeptic.md +2 -1
- package/src/agents/tester.md +5 -1
- package/src/agents/user-advocate.md +13 -0
- package/src/doctrines/README.md +26 -3
- package/src/doctrines/craft.md +78 -5
- package/src/doctrines/ops.md +62 -0
- package/src/doctrines/process.md +91 -4
- package/src/doctrines/rigor.md +41 -1
- package/src/laws/universal.md +27 -21
- package/src/memory/README.md +4 -2
- package/src/modes/foreman.md +5 -5
- package/src/notebook/README.md +2 -1
- package/src/ontology/universal.md +71 -67
- package/src/postures/peer.md +3 -0
- package/src/skills/_self-improvement.md +19 -0
- package/src/skills/_template.md +1 -12
- package/src/skills/brainstorm.md +37 -19
- package/src/skills/bruno-collection-generator.md +2 -13
- package/src/skills/bruno-test-writer.md +3 -14
- package/src/skills/ci-fix.md +49 -0
- package/src/skills/codebase-design.md +1 -12
- package/src/skills/commit.md +1 -12
- package/src/skills/consolidate-memory.md +50 -0
- package/src/skills/council.md +1 -12
- package/src/skills/daydream/LICENSE +21 -0
- package/src/skills/daydream/VENDOR.md +3 -1
- package/src/skills/debug.md +1 -12
- package/src/skills/deps-audit.md +46 -0
- package/src/skills/develop.md +38 -20
- package/src/skills/document-project.md +1 -12
- package/src/skills/domain-modeling.md +1 -12
- package/src/skills/forge-mcp.md +1 -12
- package/src/skills/fresh-eyes.md +1 -12
- package/src/skills/frontend-design.md +1 -12
- package/src/skills/geneseed-code-review.md +7 -14
- package/src/skills/geneseed.md +1 -12
- package/src/skills/git-archaeology.md +1 -12
- package/src/skills/git-rescue.md +1 -12
- package/src/skills/handoff.md +1 -12
- package/src/skills/herdr.md +1 -12
- package/src/skills/ingest.md +1 -12
- package/src/skills/migrate.md +1 -12
- package/src/skills/opencode-theme.md +1 -12
- package/src/skills/parallel-agents.md +4 -15
- package/src/skills/pipeline.md +6 -17
- package/src/skills/plan.md +1 -12
- package/src/skills/ponytail.md +1 -12
- package/src/skills/profile.md +1 -12
- package/src/skills/prose.md +1 -12
- package/src/skills/prototype.md +1 -12
- package/src/skills/quiz.md +43 -0
- package/src/skills/react-view-transitions/VENDOR.md +7 -1
- package/src/skills/refactor.md +1 -12
- package/src/skills/release.md +1 -12
- package/src/skills/repo-map.md +1 -12
- package/src/skills/research.md +5 -13
- package/src/skills/review-response.md +1 -12
- package/src/skills/roast-me.md +1 -12
- package/src/skills/rule.md +1 -12
- package/src/skills/security-audit.md +51 -0
- package/src/skills/ship.md +1 -12
- package/src/skills/skill-forge.md +59 -0
- package/src/skills/teach.md +50 -0
- package/src/skills/tickets.md +1 -12
- package/src/skills/wayfinder.md +1 -12
- package/src/skills/wiki.md +1 -12
- package/src/skills/workflow.md +82 -26
- package/src/skills/worktree.md +52 -0
- package/themes/_TEMPLATE.json +15 -12
- package/themes/biker.json +14 -11
- package/themes/commentator.json +14 -11
- package/themes/cyberpunk.json +14 -11
- package/themes/gamer.json +14 -11
- package/themes/imperial.json +14 -11
- package/themes/joker.json +14 -11
- package/themes/marvin.json +14 -11
- package/themes/mean.json +14 -11
- package/themes/military.json +14 -11
- package/themes/neutral.json +15 -12
- package/themes/opencode/README.md +2 -2
- package/themes/pirate.json +14 -11
- package/themes/sports.json +14 -11
- package/themes/verstappen.json +14 -11
- package/themes/wizard.json +14 -11
- package/web/dist/assets/Activity-Flgf4kHy.js +1 -0
- package/web/dist/assets/ActivityDetail-DIpr85nh.js +1 -0
- package/web/dist/assets/CatalogRow-CjyLjaLb.js +1 -0
- package/web/dist/assets/Diff-Deisdha8.js +2 -0
- package/web/dist/assets/Docs-DGUdY4l4.js +2 -0
- package/web/dist/assets/Doctor-F74lJ69P.js +1 -0
- package/web/dist/assets/FilterInput-BakJLvaV.js +1 -0
- package/web/dist/assets/GreenhouseView-D072lBiD.js +2 -0
- package/web/dist/assets/Harness-DNRP2Va7.js +1 -0
- package/web/dist/assets/JournalView-DWFqZ0Iw.js +1 -0
- package/web/dist/assets/Laws-BN6g3HU7.js +3 -0
- package/web/dist/assets/Library-CDOv4emM.js +1 -0
- package/web/dist/assets/Markdown-BQAvN8xx.js +38 -0
- package/web/dist/assets/OperatorHudView-CwNIUVX8.js +2 -0
- package/web/dist/assets/Profile-CpP9BTne.js +1 -0
- package/web/dist/assets/Rules-DFXsUCB7.js +1 -0
- package/web/dist/assets/Settings-BqZVUtJ5.js +1 -0
- package/web/dist/assets/Skills-Cu6kBgfF.js +1 -0
- package/web/dist/assets/StatusBadge-Cg6j-NJL.js +1 -0
- package/web/dist/assets/activity-iWaWVtsR.js +1 -0
- package/web/dist/assets/drawable-BgfrEBer.js +1 -0
- package/web/dist/assets/index-CAqrWhKl.js +2 -0
- package/web/dist/assets/index-Ruh93A3A.css +1 -0
- package/web/dist/assets/jobBuckets-Dbq8ZlWW.js +1 -0
- package/web/dist/assets/lawCats-BLgxl151.js +1 -0
- package/web/dist/assets/react-DbkvxWG5.js +8 -0
- package/web/dist/assets/rolldown-runtime-CbXtAM7H.js +1 -0
- package/web/dist/index.html +4 -3
- package/web/src/pages/Laws.jsx +22 -8
- package/src/skills/clarify.md +0 -28
- package/src/skills/crash-course.md +0 -29
- package/src/skills/decode.md +0 -29
- package/src/skills/drill.md +0 -29
- package/src/skills/feynman.md +0 -27
- package/src/skills/gap-detector.md +0 -27
- package/src/skills/learning-path.md +0 -27
- package/src/skills/react-view-transitions/README.md +0 -42
- package/src/skills/tdd.md +0 -31
- package/web/dist/assets/Activity-BfVcLrda.js +0 -1
- package/web/dist/assets/ActivityDetail-CQXccGPl.js +0 -1
- package/web/dist/assets/CatalogRow-DALEQImZ.js +0 -1
- package/web/dist/assets/Diff-BIjxXx46.js +0 -5
- package/web/dist/assets/Doctor-CJLbgsrw.js +0 -1
- package/web/dist/assets/FilterInput-CkUCk_lY.js +0 -1
- package/web/dist/assets/GreenhouseView-DfC5IILJ.js +0 -2
- package/web/dist/assets/Harness-Dm5CCU-v.js +0 -1
- package/web/dist/assets/JournalView-Cpu2eI4s.js +0 -1
- package/web/dist/assets/Laws-DDDVFuQz.js +0 -3
- package/web/dist/assets/Library-Dao3T9na.js +0 -1
- package/web/dist/assets/Markdown-C__nuWX5.js +0 -46
- package/web/dist/assets/OperatorHudView-DEpCtAjP.js +0 -2
- package/web/dist/assets/Profile-BlXtUlh2.js +0 -1
- package/web/dist/assets/Rules-DIre6gxR.js +0 -1
- package/web/dist/assets/Skills-rY13ZPhM.js +0 -1
- package/web/dist/assets/StatusBadge-wW_RS_my.js +0 -1
- package/web/dist/assets/activity-DpYPKNrA.js +0 -1
- package/web/dist/assets/index-BEZvPvpF.js +0 -1
- package/web/dist/assets/index-DNQE5ydy.js +0 -10
- package/web/dist/assets/index-DzdQjvM1.css +0 -1
- package/web/dist/assets/index-PmQGRS1X.js +0 -2
- package/web/dist/assets/jobBuckets-DfTUJUxR.js +0 -1
- package/web/dist/assets/lawCats-DUS9UTH4.js +0 -1
- package/web/dist/assets/react-DOpiTn5v.js +0 -32
package/js/build/emit-claude.mjs
CHANGED
|
@@ -10,8 +10,8 @@ import { VERSION_MARKER } from '../hosts/hosts.mjs';
|
|
|
10
10
|
import { loadAgentOverrides, writeNativeLayer } from '../hosts/native.mjs';
|
|
11
11
|
import { ensureAgentOverridesStub } from '../hosts/opencode.mjs';
|
|
12
12
|
import {
|
|
13
|
-
managedBlockRemove, managedBlockWrite, mergeClaudeSettings,
|
|
14
|
-
unwireClaudeExcludes, unwireClaudeSettings, wireClaudeExcludes,
|
|
13
|
+
managedBlockRemove, managedBlockWrite, mergeClaudeSettings,
|
|
14
|
+
unwireClaudeExcludes, unwireClaudeSettings, unwireCopilotSettings, wireClaudeExcludes,
|
|
15
15
|
} from '../hosts/settings.mjs';
|
|
16
16
|
import { writeText } from '../lib/fs.mjs';
|
|
17
17
|
import { isTruthy } from '../lib/json.mjs';
|
|
@@ -121,10 +121,13 @@ export function emitClaudeRender(cfg, job) {
|
|
|
121
121
|
ensureExcludesStub(cfgDir);
|
|
122
122
|
|
|
123
123
|
// Project hygiene, claim-on-create: an existing (possibly user-authored) .gitignore is
|
|
124
|
-
// never rewritten, but one we created stays owned across re-emits.
|
|
124
|
+
// never rewritten, but one we created stays owned across re-emits. The hook file is
|
|
125
|
+
// machine-specific (it names this user's hook shim): Claude's is the personal
|
|
126
|
+
// settings.local.json; Bob documents no local variant, so its settings.json is ignored.
|
|
125
127
|
if (scope === 'project') {
|
|
126
128
|
const gi = path.join(cfgDir, '.gitignore');
|
|
127
|
-
const giLines = (host === 'claude' ? ['settings.local.json']
|
|
129
|
+
const giLines = (host === 'claude' ? ['settings.local.json']
|
|
130
|
+
: host === 'bob' ? ['settings.json'] : [])
|
|
128
131
|
.concat(['wiki.jsonc', 'agent-overrides.json']);
|
|
129
132
|
if (!existsSync(gi)) {
|
|
130
133
|
writeText(gi, `${giLines.join('\n')}\n`);
|
|
@@ -254,7 +257,7 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
254
257
|
// DECIDES the install's packs, so the marker on disk is still the previous build's.
|
|
255
258
|
const [, managedHooks] = mergeClaudeSettings(
|
|
256
259
|
settingsPath, scope, oldSf === settingsName ? get(old, 'settings_hooks') : null, hookOpts,
|
|
257
|
-
doctrines, excludeRules, host,
|
|
260
|
+
doctrines, excludeRules, host, cfgDir,
|
|
258
261
|
);
|
|
259
262
|
managed.settings_hooks = managedHooks;
|
|
260
263
|
|
|
@@ -288,13 +291,17 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
288
291
|
managed.settings_excludes = priorExcl;
|
|
289
292
|
}
|
|
290
293
|
} else if (scope === 'global') {
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
+
// Copilot's hooks are event → array now, so the Claude merge (append/prune by deep
|
|
295
|
+
// equality) is the Copilot merge too, recorded under `settings_hooks`. An install from
|
|
296
|
+
// before that recorded single-slot `copilot_hooks` (the dead `toolCall` event): unwire
|
|
297
|
+
// those claims first, or they linger in the file forever.
|
|
294
298
|
const settingsPath = path.join(cfgDir, 'settings.json');
|
|
295
299
|
managed.settings_file = 'settings.json';
|
|
296
|
-
const
|
|
297
|
-
|
|
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;
|
|
298
305
|
}
|
|
299
306
|
return managed;
|
|
300
307
|
}
|
package/js/build/emit-common.mjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* What every emit shares — the constants that name what this tool owns, the small readers it
|
|
3
3
|
* walks a tree with, and the three writers both GLOBAL emits call.
|
|
4
4
|
*
|
|
5
|
-
* Split out of `emit.mjs` because each of `globalMemory`, `globalNotebook`, `shipLeanLaws`
|
|
5
|
+
* Split out of the old `emit.mjs` because each of `globalMemory`, `globalNotebook`, `shipLeanLaws`
|
|
6
6
|
* and `stripCapabilityLinks` has two callers in two different host emits. A helper with two
|
|
7
7
|
* hosts belongs to neither, and putting it in one of them is how a file grows to 1400 lines.
|
|
8
8
|
*
|
|
9
9
|
* Nothing here decides WHICH host is being emitted. `emit-opencode.mjs` and
|
|
10
|
-
* `emit-claude.mjs` do that; `
|
|
10
|
+
* `emit-claude.mjs` do that; `bundle.mjs` renders the bundle they read from.
|
|
11
11
|
*/
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { copyFile, writeText, isFile, isDir } from '../lib/fs.mjs';
|
|
@@ -176,8 +176,8 @@ export function globalNotebook(cfgDir, items, legacy, srcRoot) {
|
|
|
176
176
|
*
|
|
177
177
|
* NO LONGER LEAN-ONLY, and the name is kept because the lean case is still the whole reason
|
|
178
178
|
* it exists. `doctrines` ships at BOTH footprints, because the two footprints lose different
|
|
179
|
-
* things: lean loses text (
|
|
180
|
-
*
|
|
179
|
+
* things: lean loses text (each rule inlines its authored brief, so the full text has to be on
|
|
180
|
+
* disk), while FULL loses whole packs (only the active ones are inlined, so the
|
|
181
181
|
* inactive ones have to be on disk). AGENT.md tells the agent it may read a rule in a pack
|
|
182
182
|
* that is not active, and `src/doctrines/README.md` rests cross-pack citations on exactly
|
|
183
183
|
* that — a claim the emit has to make true, not merely the `files` bundle.
|
package/js/build/generate.mjs
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
33
33
|
import path from 'node:path';
|
|
34
34
|
|
|
35
|
-
import { main as driverMain } from '
|
|
35
|
+
import { main as driverMain } from './driver.mjs';
|
|
36
36
|
import { makeCfg, PACK_ORDER } from './source.mjs';
|
|
37
37
|
import { opencodeConfigDir, resolvePath } from '../hosts/hosts.mjs';
|
|
38
38
|
import {
|
package/js/build/render.mjs
CHANGED
|
@@ -67,11 +67,15 @@ const TEXT_SUFFIXES = new Set(['.md', '.tmpl', '.json', '.txt', '.yml', '.yaml']
|
|
|
67
67
|
// silently never substitutes. A leading digit stays illegal, so `{{1}}` is still not a token.
|
|
68
68
|
const TOKEN_RE = /\{\{([A-Z_][A-Z0-9_]*)\}\}/g;
|
|
69
69
|
const INCLUDE_RE = /^[ \t]*<!--[ \t]*INCLUDE:[ \t]*(?<path>[^ \t]+)[ \t]*-->[ \t]*$/gm;
|
|
70
|
+
// Each block names its kind (`agents` | `skills`) so a host that catalogues one natively and
|
|
71
|
+
// not the other — Bob — keeps exactly the table it still needs.
|
|
70
72
|
const CATALOG_BLOCK_RE =
|
|
71
|
-
/[ \t]*<!-- CATALOG:begin -->\n(?<table>[\s\S]*?)[ \t]*<!-- CATALOG:else -->\n(?<pointer>[\s\S]*?)[ \t]*<!-- CATALOG:end -->\n/g;
|
|
73
|
+
/[ \t]*<!-- CATALOG:begin (?<kind>agents|skills) -->\n(?<table>[\s\S]*?)[ \t]*<!-- CATALOG:else -->\n(?<pointer>[\s\S]*?)[ \t]*<!-- CATALOG:end -->\n/g;
|
|
72
74
|
// The footprint twin of CATALOG. Before it existed, `lean` could only reach the two
|
|
73
75
|
// include-driven corpora (`laws/universal.md` and the doctrine packs, both truncated
|
|
74
|
-
// mechanically to heading + first sentence
|
|
76
|
+
// mechanically to heading + first sentence — neither is, now: the laws, the ontology and every
|
|
77
|
+
// doctrine rule reach lean through an authored half of this block); every other section of the
|
|
78
|
+
// root template was
|
|
75
79
|
// byte-identical across footprints, which is how ~1.5k tokens of §-level prose rode into
|
|
76
80
|
// every lean install. A LEAN block ships a HAND-WRITTEN condensation instead of a
|
|
77
81
|
// truncation — the objection `docs/token-footprint.md` raised against terse-ing the
|
|
@@ -154,10 +158,16 @@ export function substitute(text, theme) {
|
|
|
154
158
|
|
|
155
159
|
/** `_build_render._resolve_catalogs`. */
|
|
156
160
|
function resolveCatalogs(text, nativeCatalog) {
|
|
157
|
-
|
|
161
|
+
// `true`/`false` still mean "both kinds", for the plain-bundle callers that pass a boolean.
|
|
162
|
+
const nat = nativeCatalog === true ? { skills: true, agents: true } : (nativeCatalog || {});
|
|
163
|
+
return text.replace(CATALOG_BLOCK_RE, (_m, kind, table, pointer) => (nat[kind] ? pointer : table));
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* LEAN blocks — full text or the hand-written condensation, by footprint. Laws, ontology,
|
|
168
|
+
* doctrine rules: every tier of the constitution authors its own lean form, and this is the
|
|
169
|
+
* one function that chooses between the halves.
|
|
170
|
+
*/
|
|
161
171
|
function resolveLean(text, footprint) {
|
|
162
172
|
return text.replace(LEAN_BLOCK_RE, (_m, full, lean) => (footprint === 'lean' ? lean : full));
|
|
163
173
|
}
|
|
@@ -182,36 +192,6 @@ function splitAtLawHeadings(text) {
|
|
|
182
192
|
return out;
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
/**
|
|
186
|
-
* Heading + first sentence, per `### ` block. DOCTRINES ONLY, now.
|
|
187
|
-
*
|
|
188
|
-
* The laws used to go through this too, and it made a law's lean footprint a function of its
|
|
189
|
-
* punctuation: Law II shipped as one 36-character sentence and lost its stop-and-ask
|
|
190
|
-
* mechanism, while a law written as one colon-chained run-on shipped nearly whole. The laws
|
|
191
|
-
* now carry authored LEAN blocks in `src/laws/universal.md`, the way the ontology already did,
|
|
192
|
-
* and `resolveLean` picks the half. Doctrine rules stay machine-cut: there are ~23 of them
|
|
193
|
-
* across four packs, and a pack is a practice catalogue whose first sentence is written to
|
|
194
|
-
* stand alone.
|
|
195
|
-
*/
|
|
196
|
-
function terseBlocks(text) {
|
|
197
|
-
const blocks = splitAtLawHeadings(text);
|
|
198
|
-
const out = [blocks[0].trimEnd()];
|
|
199
|
-
for (const b of blocks.slice(1)) {
|
|
200
|
-
// Python's `splitlines()` also breaks on VT, FF, FS-RS, NEL, LINE SEPARATOR and
|
|
201
|
-
// PARAGRAPH SEPARATOR, where `split('\n')` does not. The law corpus is plain
|
|
202
|
-
// markdown and readText has already folded CRLF and CR to LF, so the sets coincide.
|
|
203
|
-
// (Spelling those out by name rather than by literal is not fussiness: the first
|
|
204
|
-
// draft of this comment contained the actual LINE/PARAGRAPH SEPARATOR characters,
|
|
205
|
-
// which are JS line terminators, and they ended the comment mid-sentence.)
|
|
206
|
-
const lines = b.split('\n');
|
|
207
|
-
const heading = lines[0].trimEnd();
|
|
208
|
-
const body = lines.slice(1).map((l) => l.trim()).join(' ').trim();
|
|
209
|
-
const m = /^([\s\S]+?[.!?])(?:\s|$)/.exec(body);
|
|
210
|
-
out.push(`${heading}\n${m ? m[1].trim() : body}`);
|
|
211
|
-
}
|
|
212
|
-
return out.join('\n\n');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
195
|
/** The pointer under §1 at lean. The laws' lean text itself is authored in LEAN blocks. */
|
|
216
196
|
export function lawsPointer(theme, lawsPrefix = '') {
|
|
217
197
|
const law = theme.LAW ?? 'Law';
|
|
@@ -222,11 +202,29 @@ export function lawsPointer(theme, lawsPrefix = '') {
|
|
|
222
202
|
+ 'secrets, deletion, git history, scope, or untrusted content.';
|
|
223
203
|
}
|
|
224
204
|
|
|
205
|
+
/**
|
|
206
|
+
* SOURCE TEXT, READ ONCE PER `cfg`. One emit renders the same partials over and over — the
|
|
207
|
+
* self-improvement footer is included by ~50 specs — and `doctor --all` repeats that per theme:
|
|
208
|
+
* 91% of its file reads were repeats. Keyed on the `cfg` OBJECT, not the process: the web
|
|
209
|
+
* daemon renders in-process for days, and `makeCfg()` is called per operation, so each request
|
|
210
|
+
* sees today's source. Only files under `cfg.src` — the one tree nothing in a render writes to.
|
|
211
|
+
*/
|
|
212
|
+
const SRC_TEXT = new WeakMap();
|
|
213
|
+
function readSource(cfg, p) {
|
|
214
|
+
const src = cfg && cfg.src ? normcase(path.resolve(cfg.src)) + path.sep : null;
|
|
215
|
+
if (!src || !normcase(p).startsWith(src)) return readText(p);
|
|
216
|
+
let seen = SRC_TEXT.get(cfg);
|
|
217
|
+
if (!seen) SRC_TEXT.set(cfg, (seen = new Map()));
|
|
218
|
+
let text = seen.get(p);
|
|
219
|
+
if (text === undefined) seen.set(p, (text = readText(p)));
|
|
220
|
+
return text;
|
|
221
|
+
}
|
|
222
|
+
|
|
225
223
|
/** `_build_render.render_file`. */
|
|
226
224
|
export function renderFile(cfg, filePath, theme, footprint = 'full', lawsPrefix = '',
|
|
227
225
|
visiting = new Set(), nativeCatalog = false) {
|
|
228
226
|
const here = path.resolve(filePath);
|
|
229
|
-
let text =
|
|
227
|
+
let text = readSource(cfg, here);
|
|
230
228
|
|
|
231
229
|
text = text.replace(INCLUDE_RE, (_whole, rel) => {
|
|
232
230
|
const target = path.resolve(cfg.src, rel);
|
|
@@ -318,10 +316,10 @@ function activeDoctrines(cfg) {
|
|
|
318
316
|
* `registerBody`'s single-select one.
|
|
319
317
|
*
|
|
320
318
|
* `registerBody` reads ONE file because a posture and a mode are scalars; a doctrine set is
|
|
321
|
-
* 0-4 files, so this loops instead of wrapping it. Under `lean` each
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* or not a pack is active.
|
|
319
|
+
* 0-4 files, so this loops instead of wrapping it. Under `lean` each rule ships the authored
|
|
320
|
+
* `LEAN:else` half of its block — `resolveLean`, the same picker the invariants go through,
|
|
321
|
+
* the first-sentence cut gone — and the whole section, not each pack, closes with one pointer
|
|
322
|
+
* at the full catalogue, which ships in every bundle whether or not a pack is active.
|
|
325
323
|
*/
|
|
326
324
|
/** `### {{DOCTRINE}} <pack> <n> —` in the UNRENDERED source, which is where the address is. */
|
|
327
325
|
const SRC_RULE_HEADING_RE = /^### \{\{DOCTRINE\}\} ([a-z]+) (\d+)\b/;
|
|
@@ -356,8 +354,11 @@ function doctrinesBody(cfg, theme, active, footprint, lawsPrefix) {
|
|
|
356
354
|
const parts = active.map((name) => {
|
|
357
355
|
const raw = dropExcludedRules(readPackText(path.join(cfg.src, 'doctrines', `${name}.md`)),
|
|
358
356
|
name, excluded);
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
// The same picker `renderFile` runs over every other file: the authored half first, the
|
|
358
|
+
// voice second. THE ORDER IS LOAD-BEARING — `resolveLean` reads HTML markers, and
|
|
359
|
+
// substituting first would hand it prose in fourteen voices. The first-sentence cut is gone;
|
|
360
|
+
// a rule's lean text is written in `src/doctrines/<pack>.md`, not measured out of its prose.
|
|
361
|
+
return substitute(resolveLean(raw, footprint), theme).trim();
|
|
361
362
|
});
|
|
362
363
|
if (footprint === 'lean') {
|
|
363
364
|
const doctrine = theme.DOCTRINE ?? 'Doctrine';
|
|
@@ -454,10 +455,13 @@ export function renderAll(cfg, themeName, {
|
|
|
454
455
|
for (const file of sortedSourceFiles(cfg.src)) {
|
|
455
456
|
const rel = path.relative(cfg.src, file);
|
|
456
457
|
const outRel = destRel(themedRel(rel, theme)).split(path.sep).join('/'); // as_posix()
|
|
457
|
-
// The on-disk `laws/` and `
|
|
458
|
-
//
|
|
458
|
+
// The on-disk `laws/`, `ontology/` and `doctrines/` are the "complete text" the two lean
|
|
459
|
+
// pointers promise — §1's and the Doctrines section's. They render at full whatever the
|
|
460
|
+
// footprint; only what AGENT.md INLINES leans. `doctrines/` joined them when its rules got
|
|
461
|
+
// authored LEAN blocks: before that a pack file carried no markers and shipped whole by
|
|
462
|
+
// accident, which is a different thing from shipping whole by contract.
|
|
459
463
|
// Keyed on the SOURCE rel, not the themed one, so a DIR_* rename cannot un-exempt them.
|
|
460
|
-
const fp = /^(?:laws|ontology)[\\/]/.test(rel) ? 'full' : footprint;
|
|
464
|
+
const fp = /^(?:laws|ontology|doctrines)[\\/]/.test(rel) ? 'full' : footprint;
|
|
461
465
|
items.push(TEXT_SUFFIXES.has(suffixOf(path.basename(file)))
|
|
462
466
|
? { rel: outRel, text: renderFile(cfg, file, theme, fp, lawsPrefix, new Set(), nativeCatalog), src: file }
|
|
463
467
|
: { rel: outRel, text: null, src: file });
|
package/js/build/stubs.mjs
CHANGED
|
@@ -114,7 +114,7 @@ export const EXCLUDES_FILE = 'excludes.json';
|
|
|
114
114
|
*/
|
|
115
115
|
export const EXCLUDES_STUB = `\
|
|
116
116
|
{
|
|
117
|
-
"_comment": "Folders where this global Geneseed install goes dormant (hooks silent, preamble suppressed). Managed by \`
|
|
117
|
+
"_comment": "Folders where this global Geneseed install goes dormant (hooks silent, preamble suppressed). Managed by \`geneseed exclude add|remove|list\`; safe to edit by hand. Paths are absolute.",
|
|
118
118
|
"excludes": []
|
|
119
119
|
}
|
|
120
120
|
`;
|
package/js/build/themes.mjs
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* `_argv` emits neither flag (argued in situ at `bin/build-driver.mjs`'s dispatch), so the
|
|
15
15
|
* acceptance matrix that gates all nine emits is structurally blind to both, and the driver
|
|
16
16
|
* refused them rather than crossing for exactly that reason. The gate is
|
|
17
|
-
* `tests/unit/maintainer_tools.test.mjs`,
|
|
18
|
-
* `tests/
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* `tests/unit/maintainer_tools.test.mjs`, over the written-out cases in
|
|
18
|
+
* `tests/fixtures/sync_themes_cases.json`: hand-made theme directories, each with the output,
|
|
19
|
+
* exit status and FILE BYTES it must produce. A change that moves a byte changes a case and
|
|
20
|
+
* the sentence explaining it, in the same commit.
|
|
21
21
|
*
|
|
22
22
|
* WHY THIS MODULE IS NOT `js/inspect/doctor.mjs`. `--sync-themes` has no doctor dependency, so it
|
|
23
23
|
* stays on the generator driver where its flag lives — and the driver is under a transitive
|
package/js/build/version.mjs
CHANGED
|
@@ -28,6 +28,18 @@ import { readFileSync } from 'node:fs';
|
|
|
28
28
|
* in the output; reproducing the wrong one changes the fingerprint of every install.
|
|
29
29
|
*/
|
|
30
30
|
export function sourceFingerprint(cfg) {
|
|
31
|
+
// Once per `cfg` — the source cannot change under one operation, and every emit (84 of them
|
|
32
|
+
// in `doctor --all`) hashed the whole tree again. Per cfg OBJECT, not per process, for the
|
|
33
|
+
// same reason as render.mjs's source cache: the web daemon is long-lived.
|
|
34
|
+
if (FINGERPRINT.has(cfg)) return FINGERPRINT.get(cfg);
|
|
35
|
+
const fp = hashSource(cfg);
|
|
36
|
+
FINGERPRINT.set(cfg, fp);
|
|
37
|
+
return fp;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const FINGERPRINT = new WeakMap();
|
|
41
|
+
|
|
42
|
+
function hashSource(cfg) {
|
|
31
43
|
const h = createHash('sha256');
|
|
32
44
|
const files = [];
|
|
33
45
|
for (const r of [cfg.src, cfg.themes, cfg.pluginSrc, cfg.workflowSrc]) {
|
package/js/cli-table.json
CHANGED
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
],
|
|
163
163
|
"dest": "host",
|
|
164
164
|
"metavar": null,
|
|
165
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
165
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) and 'copilot' ask, each in its own envelope; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
166
166
|
"choices": null,
|
|
167
167
|
"default": null,
|
|
168
168
|
"required": false,
|
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
},
|
|
354
354
|
{
|
|
355
355
|
"name": "git-gate",
|
|
356
|
-
"help": "PreToolUse hook:
|
|
356
|
+
"help": "PreToolUse hook: ASK before a history-discarding git act (Law IV) and before every git commit/push (process 5 backstop)",
|
|
357
357
|
"description": "",
|
|
358
358
|
"positionals": [],
|
|
359
359
|
"options": [
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
],
|
|
379
379
|
"dest": "host",
|
|
380
380
|
"metavar": null,
|
|
381
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
381
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) and 'copilot' ask, each in its own envelope; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
382
382
|
"choices": null,
|
|
383
383
|
"default": null,
|
|
384
384
|
"required": false,
|
|
@@ -386,6 +386,21 @@
|
|
|
386
386
|
"is_flag": false,
|
|
387
387
|
"type": null,
|
|
388
388
|
"hidden": false
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"names": [
|
|
392
|
+
"--no-consent"
|
|
393
|
+
],
|
|
394
|
+
"dest": "no_consent",
|
|
395
|
+
"metavar": null,
|
|
396
|
+
"help": "skip the process-5 commit/push ask (set by the emit when the process pack or process 5 is off); Law IV still asks",
|
|
397
|
+
"choices": null,
|
|
398
|
+
"default": false,
|
|
399
|
+
"required": false,
|
|
400
|
+
"nargs": "0",
|
|
401
|
+
"is_flag": true,
|
|
402
|
+
"type": null,
|
|
403
|
+
"hidden": false
|
|
389
404
|
}
|
|
390
405
|
],
|
|
391
406
|
"mutex": []
|
|
@@ -641,7 +656,7 @@
|
|
|
641
656
|
],
|
|
642
657
|
"dest": "host",
|
|
643
658
|
"metavar": null,
|
|
644
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
659
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) and 'copilot' ask, each in its own envelope; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
645
660
|
"choices": null,
|
|
646
661
|
"default": null,
|
|
647
662
|
"required": false,
|
|
@@ -811,7 +826,7 @@
|
|
|
811
826
|
},
|
|
812
827
|
{
|
|
813
828
|
"name": "tool-gate",
|
|
814
|
-
"help": "
|
|
829
|
+
"help": "PreToolUse hook for hosts with no tool matcher (Copilot, Bob): git-gate and rule-gate fused behind one command, dispatched on the payload",
|
|
815
830
|
"description": "",
|
|
816
831
|
"positionals": [],
|
|
817
832
|
"options": [
|
|
@@ -836,7 +851,7 @@
|
|
|
836
851
|
],
|
|
837
852
|
"dest": "host",
|
|
838
853
|
"metavar": null,
|
|
839
|
-
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default)
|
|
854
|
+
"help": "the host that reads the verdict (set by the emitted hook): 'claude' (default) and 'copilot' ask, each in its own envelope; 'bob' has no ask tier, so Laws I/IV exit 2 and everything else is a stderr warning",
|
|
840
855
|
"choices": null,
|
|
841
856
|
"default": null,
|
|
842
857
|
"required": false,
|