geneseed 3.13.0 → 3.15.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 +93 -0
- package/SHIPPED.md +1 -1
- package/adapters/opencode/plugins/geneseed-context.js +23 -9
- package/adapters/opencode/plugins/geneseed-guard.js +16 -4
- package/adapters/opencode/plugins/geneseed-learn.js +45 -10
- package/adapters/opencode/plugins/geneseed-notify.js +37 -19
- package/adapters/opencode/plugins/geneseed-workflow.js +3 -1
- package/adapters/opencode/workflows/_runtime.js +16 -3
- package/bin/geneseed-cli.mjs +32 -34
- package/bin/geneseed-hook.mjs +28 -18
- package/docs/extending.md +5 -5
- package/harness.config.json +1 -1
- package/js/README.md +28 -15
- package/js/build/args.mjs +375 -0
- package/js/build/bundle.mjs +10 -10
- package/js/build/driver.mjs +145 -560
- package/js/build/emit-claude.mjs +5 -6
- package/js/build/emit-common.mjs +2 -14
- package/js/build/emit-opencode.mjs +12 -8
- package/js/build/generate.mjs +21 -14
- package/js/build/render.mjs +6 -10
- package/js/build/source.mjs +9 -17
- package/js/build/stubs.mjs +6 -5
- package/js/build/version.mjs +21 -2
- package/js/hosts/hooks-context.mjs +432 -0
- package/js/hosts/hooks-learn.mjs +275 -0
- package/js/hosts/hooks-prims.mjs +108 -0
- package/js/hosts/hooks.mjs +50 -977
- package/js/hosts/hosts.mjs +31 -25
- package/js/hosts/installs.mjs +38 -20
- package/js/hosts/link.mjs +75 -26
- package/js/hosts/mcp.mjs +12 -48
- package/js/hosts/memory-files.mjs +190 -0
- package/js/hosts/native.mjs +106 -48
- package/js/hosts/opencode.mjs +44 -36
- package/js/hosts/settings.mjs +135 -414
- package/js/hosts/shim.mjs +375 -0
- package/js/inspect/checks-authoring.mjs +21 -828
- package/js/inspect/checks-build.mjs +4 -4
- package/js/inspect/checks-constitution.mjs +493 -0
- package/js/inspect/checks-counts.mjs +242 -0
- package/js/inspect/checks-repo.mjs +17 -8
- package/js/inspect/diff.mjs +9 -7
- package/js/inspect/doctor.mjs +2 -2
- package/js/inspect/excludes.mjs +10 -5
- package/js/inspect/inventory.mjs +4 -4
- package/js/inspect/scan.mjs +1 -1
- package/js/inspect/status.mjs +19 -22
- package/js/inspect/validate.mjs +50 -37
- package/js/lib/fs.mjs +12 -11
- package/js/lib/json.mjs +4 -4
- package/js/lib/paths.mjs +27 -33
- package/js/lib/proc.mjs +3 -3
- package/js/lib/prompt.mjs +132 -0
- package/js/lib/text.mjs +18 -4
- package/js/lib/udiff.mjs +3 -2
- package/js/loop/catalog.mjs +5 -3
- package/js/loop/cli.mjs +26 -12
- package/js/loop/graph.mjs +1 -1
- package/js/loop/registry.mjs +10 -15
- package/js/loop/score.mjs +1 -1
- package/js/loop/state.mjs +35 -11
- package/js/maintain/memory.mjs +6 -7
- package/js/maintain/migrate.mjs +22 -33
- package/js/maintain/setup.mjs +9 -131
- package/js/maintain/uninstall.mjs +57 -97
- package/js/maintain/update.mjs +115 -34
- package/js/ui/anim.mjs +8 -12
- package/js/ui/cli.mjs +37 -43
- package/js/ui/menu.mjs +7 -13
- package/js/ui/tui.mjs +14 -123
- package/js/web/actions.mjs +109 -403
- package/js/web/activity.mjs +1 -1
- package/js/web/api.mjs +41 -725
- package/js/web/catalog.mjs +527 -0
- package/js/web/daemon.mjs +21 -3
- package/js/web/docs.mjs +29 -72
- package/js/web/handler.mjs +51 -27
- package/js/web/jobs.mjs +24 -2
- package/js/web/routes.mjs +61 -6
- package/js/web/server.mjs +4 -4
- package/js/web/state.mjs +138 -0
- package/js/web/user-files.mjs +339 -0
- package/package.json +1 -1
- package/web/dist/assets/Activity-kpWxd70F.js +1 -0
- package/web/dist/assets/ActivityDetail-Ccr942VL.js +1 -0
- package/web/dist/assets/Dashboard-D1lE7jcg.js +2 -0
- package/web/dist/assets/Docs-BHyaggFy.js +5 -0
- package/web/dist/assets/FilterInput-CVyqmYWM.js +1 -0
- package/web/dist/assets/GreenhouseView-Sc_UDMOE.js +2 -0
- package/web/dist/assets/Installs-Gw-pJ3Zi.js +2 -0
- package/web/dist/assets/JournalView-_gxpsxXi.js +1 -0
- package/web/dist/assets/Laws-S9fq1r9T.js +3 -0
- package/web/dist/assets/Library-MBxadRzn.js +1 -0
- package/web/dist/assets/Loops-Da_JhlzM.js +2 -0
- package/web/dist/assets/{Markdown-CdTX0kLe.js → Markdown-X2a3qvOh.js} +11 -11
- package/web/dist/assets/OperatorHudView--TkvDybH.js +2 -0
- package/web/dist/assets/Personal-CTXKHVbV.js +1 -0
- package/web/dist/assets/Tabs-B0cdf8k6.js +1 -0
- package/web/dist/assets/activity-Dr7ZMBI3.js +1 -0
- package/web/dist/assets/api-CxgfYu18.js +1 -0
- package/web/dist/assets/drawable-BbWQxe82.js +1 -0
- package/web/dist/assets/index-Ctp-4poP.css +1 -0
- package/web/dist/assets/index-ujKG1ZNf.js +2 -0
- package/web/dist/assets/{react-Bbzhmq4v.js → react-B9Z_tXRV.js} +1 -1
- package/web/dist/assets/sections-GalMS0Zv.js +1 -0
- package/web/dist/assets/usePoll-DD8VjtSg.js +1 -0
- package/web/dist/index.html +5 -6
- package/web/src/pages/Laws.jsx +0 -1
- package/web/dist/assets/Activity-Ca2Aruu7.js +0 -1
- package/web/dist/assets/ActivityDetail-Bo7v4Z_r.js +0 -1
- package/web/dist/assets/Dashboard-BuMq46aT.js +0 -2
- package/web/dist/assets/Docs-BzccCllG.js +0 -5
- package/web/dist/assets/FilterInput-CBcJBQPI.js +0 -1
- package/web/dist/assets/GreenhouseView-BCIKcnGG.js +0 -2
- package/web/dist/assets/Installs-DMFJvq7s.js +0 -2
- package/web/dist/assets/JournalView-o1rmd1_1.js +0 -1
- package/web/dist/assets/Laws-BANxq_I_.js +0 -3
- package/web/dist/assets/Library-DW4x0fbh.js +0 -1
- package/web/dist/assets/Loops-DEBibbBP.js +0 -2
- package/web/dist/assets/OperatorHudView-CLD0fYaA.js +0 -2
- package/web/dist/assets/Personal-6uMJvyBG.js +0 -1
- package/web/dist/assets/Tabs-BubnyZhF.js +0 -1
- package/web/dist/assets/activity-CObqyxkL.js +0 -1
- package/web/dist/assets/api-D_ixdyEY.js +0 -1
- package/web/dist/assets/drawable-C_x5xncq.js +0 -1
- package/web/dist/assets/index-CMPgezFm.js +0 -2
- package/web/dist/assets/index-DMsT4Aa6.css +0 -1
- package/web/dist/assets/rolldown-runtime-hePW80VL.js +0 -1
- package/web/dist/assets/sections-Dg23Hel5.js +0 -1
- package/web/dist/assets/usePoll-BOqeunzF.js +0 -1
package/js/build/emit-claude.mjs
CHANGED
|
@@ -14,10 +14,11 @@ import {
|
|
|
14
14
|
unwireClaudeExcludes, unwireClaudeSettings, wireClaudeExcludes,
|
|
15
15
|
} from '../hosts/settings.mjs';
|
|
16
16
|
import { writeText } from '../lib/fs.mjs';
|
|
17
|
-
import { isTruthy } from '../lib/json.mjs';
|
|
17
|
+
import { get, isDict, isTruthy } from '../lib/json.mjs';
|
|
18
|
+
import { relPosix } from '../lib/text.mjs';
|
|
18
19
|
import { assertSourceComplete, phaseLog } from './bundle.mjs';
|
|
19
20
|
import {
|
|
20
|
-
|
|
21
|
+
globalMemory, globalNotebook, shipLeanLaws, stripCapabilityLinks,
|
|
21
22
|
} from './emit-common.mjs';
|
|
22
23
|
import { renderAll, renderFile } from './render.mjs';
|
|
23
24
|
import {
|
|
@@ -40,7 +41,7 @@ import { existsSync, mkdirSync } from 'node:fs';
|
|
|
40
41
|
* Six emits route through here — claude, bob and openclaude at both scopes — so this one
|
|
41
42
|
* job is what puts two thirds of the matrix on the seam.
|
|
42
43
|
*
|
|
43
|
-
* `claudeMdText` is the
|
|
44
|
+
* `claudeMdText` is the one product of this render that is not a file it writes. WIRE
|
|
44
45
|
* needs the managed block's text, and that text is a RENDER (`prefixedAgentText`
|
|
45
46
|
* re-renders AGENT.md with every store dir prefixed), so it is computed here and merged
|
|
46
47
|
* there. `emitOpencodeRender` needed nothing of the kind — its wired file is derived from
|
|
@@ -174,8 +175,6 @@ export function emitClaudeRender(cfg, job) {
|
|
|
174
175
|
stats: { nAgents, nSkills },
|
|
175
176
|
memStatus,
|
|
176
177
|
nbStatus,
|
|
177
|
-
hasAgentText: agentText !== null,
|
|
178
|
-
claudeMdText,
|
|
179
178
|
managed,
|
|
180
179
|
};
|
|
181
180
|
}
|
|
@@ -264,7 +263,7 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRu
|
|
|
264
263
|
// travels from `cfg` rather than being re-read off the deployment: this is the emit that
|
|
265
264
|
// DECIDES the install's packs, so the marker on disk is still the previous build's.
|
|
266
265
|
const [, managedHooks] = mergeClaudeSettings(
|
|
267
|
-
settingsPath,
|
|
266
|
+
settingsPath, oldSf === settingsName ? get(old, 'settings_hooks') : null, hookOpts,
|
|
268
267
|
doctrines, excludeRules, host, cfgDir,
|
|
269
268
|
);
|
|
270
269
|
managed.settings_hooks = managedHooks;
|
package/js/build/emit-common.mjs
CHANGED
|
@@ -10,18 +10,11 @@
|
|
|
10
10
|
* `emit-claude.mjs` do that; `bundle.mjs` renders the bundle they read from.
|
|
11
11
|
*/
|
|
12
12
|
import path from 'node:path';
|
|
13
|
-
import { copyFile, writeText,
|
|
14
|
-
import {
|
|
13
|
+
import { copyFile, writeText, isDir } from '../lib/fs.mjs';
|
|
14
|
+
import { relPosix } from '../lib/text.mjs';
|
|
15
15
|
import { SRC_DIR_TOKENS, destRel } from './render.mjs';
|
|
16
16
|
import { mkdirSync, readdirSync, cpSync } from 'node:fs';
|
|
17
17
|
|
|
18
|
-
// `isFile`/`isDir`/`get`/`isDict` are owned by `js/lib` now (single owner across `hosts/`,
|
|
19
|
-
// `build/`, `inspect/` and `web/` — layering forbids `build/` reaching into `inspect/scan.mjs`
|
|
20
|
-
// for them, so `js/lib` is the one home both sides can reach). Re-exported here so this
|
|
21
|
-
// module's own existing callers (`emit-claude.mjs`, `emit-opencode.mjs`, `version.mjs`,
|
|
22
|
-
// `bundle.mjs`) keep importing them from `./emit-common.mjs` unchanged.
|
|
23
|
-
export { isFile, isDir, get, isDict };
|
|
24
|
-
|
|
25
18
|
/** `_build_render.SRC_DIRS_MARKER`. */
|
|
26
19
|
export const SRC_DIRS_MARKER = '.geneseed-srcdirs.json';
|
|
27
20
|
/**
|
|
@@ -79,11 +72,6 @@ export function rglobFiles(root) {
|
|
|
79
72
|
.map((d) => path.join(d.parentPath, d.name));
|
|
80
73
|
}
|
|
81
74
|
|
|
82
|
-
/** `p.relative_to(base).as_posix()`. */
|
|
83
|
-
export function relPosix(base, p) {
|
|
84
|
-
return path.relative(base, p).split(path.sep).join('/');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
75
|
/**
|
|
88
76
|
* `_build_emit._strip_capability_links` — `re.sub(r"\1")`, so the pattern needs `g`.
|
|
89
77
|
*
|
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { VERSION_MARKER } from '../hosts/hosts.mjs';
|
|
11
|
-
import { loadAgentOverrides, writeNativeLayer } from '../hosts/native.mjs';
|
|
11
|
+
import { claimer, loadAgentOverrides, writeNativeLayer } from '../hosts/native.mjs';
|
|
12
12
|
import {
|
|
13
13
|
copyPlugins, copyWorkflows, ensureAgentOverridesStub, writeColorThemes, writeCommandLayer,
|
|
14
14
|
writePonytailCommand, writePrimaryAgent, writeTheme,
|
|
15
15
|
} from '../hosts/opencode.mjs';
|
|
16
16
|
import { mergeOpencodeJson } from '../hosts/settings.mjs';
|
|
17
|
-
import { readText, writeText } from '../lib/fs.mjs';
|
|
17
|
+
import { isFile, readText, writeText } from '../lib/fs.mjs';
|
|
18
|
+
import { relPosix } from '../lib/text.mjs';
|
|
18
19
|
import { assertSourceComplete, build, phaseLog } from './bundle.mjs';
|
|
19
20
|
import {
|
|
20
|
-
globalMemory, globalNotebook,
|
|
21
|
+
globalMemory, globalNotebook, shipLeanLaws, stripCapabilityLinks,
|
|
21
22
|
} from './emit-common.mjs';
|
|
22
23
|
import { renderAll } from './render.mjs';
|
|
23
24
|
import {
|
|
@@ -56,17 +57,20 @@ function opencodeLayer(cfg, items, themeName, theme, dir, owned, opts) {
|
|
|
56
57
|
} = opts;
|
|
57
58
|
ensureAgentOverridesStub(cfg, overridesDir);
|
|
58
59
|
const overrides = loadAgentOverrides(overridesDir);
|
|
60
|
+
// One claim for every writer below whose path lands in `owned` — see `claimer`.
|
|
61
|
+
const claim = claimer(oldOwned, dir, manifestExisted);
|
|
59
62
|
|
|
60
63
|
const { nAgents, nSkills, written } = writeNativeLayer(
|
|
61
64
|
items, path.join(dir, 'agents'), path.join(dir, 'skills'), overrides,
|
|
62
|
-
{ host: 'opencode',
|
|
65
|
+
{ host: 'opencode', theme, src: cfg.src, claim });
|
|
63
66
|
for (const p of written) owned.push(relPosix(dir, p));
|
|
64
67
|
|
|
65
|
-
const primary = writePrimaryAgent(cfg, path.join(dir, 'agents'), overrides);
|
|
68
|
+
const primary = writePrimaryAgent(cfg, path.join(dir, 'agents'), overrides, claim);
|
|
66
69
|
if (primary) owned.push(relPosix(dir, primary));
|
|
67
70
|
|
|
68
|
-
const commands = writeCommandLayer(cfg, items, path.join(dir, 'command'));
|
|
69
|
-
|
|
71
|
+
const commands = writeCommandLayer(cfg, items, path.join(dir, 'command'), claim);
|
|
72
|
+
const ponytail = writePonytailCommand(path.join(dir, 'command'), claim); // always-on /ponytail
|
|
73
|
+
if (ponytail) commands.push(ponytail);
|
|
70
74
|
for (const p of commands) owned.push(relPosix(dir, p));
|
|
71
75
|
|
|
72
76
|
owned.push(relPosix(dir, writeTheme(path.join(dir, 'themes'), themeName, theme)));
|
|
@@ -97,7 +101,7 @@ function opencodeLayer(cfg, items, themeName, theme, dir, owned, opts) {
|
|
|
97
101
|
* manifest is the DRIVER's stage: it reads the file, hands the prior claim in, and prunes
|
|
98
102
|
* and rewrites it after this returns. One owner of the file, and this function is not it.
|
|
99
103
|
*
|
|
100
|
-
* `nativeCatalog` likewise arrives decided. It is `
|
|
104
|
+
* `nativeCatalog` likewise arrives decided. It is `hostCatalogsNatively('opencode')` from
|
|
101
105
|
* `js/hosts/hosts.mjs`, resolved by the driver before the call; passing the decision rather
|
|
102
106
|
* than re-reading the registry keeps one owner of it.
|
|
103
107
|
*/
|
package/js/build/generate.mjs
CHANGED
|
@@ -9,25 +9,27 @@
|
|
|
9
9
|
* WHY `build` IMPORTS THE DRIVER RATHER THAN SPAWNING IT. The Python is
|
|
10
10
|
* `run([sys.executable, BUILD, *extra]).returncode` — a passthrough to a second process.
|
|
11
11
|
* Reproducing that shape here would mean spawning `node bin/build-driver.mjs`, and
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* `tests/unit/hook_cli.test.mjs`'s 'the CLI entry reaches child_process only where it is
|
|
13
|
+
* declared' forbids it: the check is transitive over this entry's imports and allow-lists only
|
|
14
|
+
* the modules that genuinely start processes — `web`, `upgrade`, `setup` — and P5c's argument
|
|
15
|
+
* for it is that the ban should outlive them rather than be dismantled by a verb that does not.
|
|
16
|
+
* It is also the wrong shape on its own terms. Python needs a second
|
|
16
17
|
* process because `build.py` is a different PROGRAM; `bin/build-driver.mjs` is a module in this
|
|
17
18
|
* one, and a Node CLI spawning a Node driver to do work it could do in-process is exactly
|
|
18
19
|
* the passthrough the whole port has been removing. So the driver's `main` is exported and
|
|
19
20
|
* called directly, and the observable contract — the tree, the streams, the exit code — is
|
|
20
|
-
* what `build/*`
|
|
21
|
+
* what the `build/*` cells of `tests/golden.mjs --cli` assert.
|
|
21
22
|
*
|
|
22
23
|
* WHAT NO CELL CAN REACH HERE, stated rather than left implicit. `fenceFor`'s real arm is
|
|
23
24
|
* unreachable from the matrix: measured over the whole rendered tree, the longest backtick
|
|
24
25
|
* run in any source text is 3, so `max(4, longest + 1)` picks 4 for all 96 files, and a port
|
|
25
26
|
* that hardcoded four backticks is byte-identical in every prompt cell. Reaching the other
|
|
26
27
|
* arm needs a file in `src/`, and `src/` is the tree no fixture can redirect (P5d). It is
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* the former Python suite gated it as a pure function over a corpus, with the unreachability
|
|
29
|
+
* measured in BOTH directions rather than asserted: one test proved the corpus left the floor,
|
|
30
|
+
* and one re-rendered every theme × posture × mode of the live tree and proved `src/` still did
|
|
31
|
+
* not. The 96 above is one render — the sweep was 13 440 files. That suite is gone and no Node
|
|
32
|
+
* test calls `fenceFor`, so the real arm is pinned by nothing today.
|
|
31
33
|
*/
|
|
32
34
|
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
33
35
|
import path from 'node:path';
|
|
@@ -171,7 +173,7 @@ export function setupBuildArgs(theme, emit, out = null, root = null, footprint =
|
|
|
171
173
|
* A pre-marker install still has a (host, scope), which is what the walk found it by, so it
|
|
172
174
|
* is rebuilt in its own mode rather than defaulted to OpenCode.
|
|
173
175
|
*/
|
|
174
|
-
|
|
176
|
+
const DEFAULT_EMIT = new Map([
|
|
175
177
|
['opencode global', 'opencode-global'], ['opencode project', 'opencode'],
|
|
176
178
|
['claude global', 'claude-global'], ['claude project', 'claude'],
|
|
177
179
|
['bob global', 'bob-global'], ['bob project', 'bob'],
|
|
@@ -202,7 +204,7 @@ export function installProfile(host, scope, root) {
|
|
|
202
204
|
const posture = postureOfDir(root) || defaultPosture();
|
|
203
205
|
const mode = modeOfDir(root) || defaultMode();
|
|
204
206
|
// Read off the deployed loop skill; no config fallback — `DEFAULT_PRESET` is the engine's.
|
|
205
|
-
const trust = trustOfDir(root) || DEFAULT_PRESET;
|
|
207
|
+
const trust = trustOfDir(root, host) || DEFAULT_PRESET;
|
|
206
208
|
// A rebuild that defaulted the pack selection would re-emit an install into a constitution
|
|
207
209
|
// its owner did not choose — and in one direction that is not merely surprising: dropping
|
|
208
210
|
// the process pack takes the commit/push consent RULES out of AGENT.md while
|
|
@@ -360,7 +362,9 @@ export function buildPrompt(cfg, themeName) {
|
|
|
360
362
|
* Windows. At ~10,000 newlines in a 400 KB document that is the largest byte difference this
|
|
361
363
|
* port could ship, and `harness_golden` can only see half of it — the FILE half is compared
|
|
362
364
|
* directly, the stdout half is folded by `subprocess`'s universal-newline decode, which is
|
|
363
|
-
* why
|
|
365
|
+
* why the former Python suite's stdout-bytes comparison grew a `prompt` row — kept today as
|
|
366
|
+
* a row of `tests/unit/hook_cli.test.mjs`'s 'every newline the entry points write is the
|
|
367
|
+
* platform's'.
|
|
364
368
|
*/
|
|
365
369
|
export function cmdPrompt(args) {
|
|
366
370
|
// `args.theme or "neutral"` — a literal in the Python, NOT the config's theme, so this
|
|
@@ -483,7 +487,10 @@ export function cmdTheme(args) {
|
|
|
483
487
|
written.push(dest);
|
|
484
488
|
}
|
|
485
489
|
printOut(`[theme] wrote ${written.map((p) => path.basename(p)).join(', ')} to ${destDir}\n`);
|
|
486
|
-
|
|
487
|
-
|
|
490
|
+
// The hint names what was WRITTEN: `--transparent-only` writes no `${full}.json`, and a
|
|
491
|
+
// hint selecting it would point OpenCode at a theme that does not exist.
|
|
492
|
+
const [first, ...rest] = written.map((p) => path.basename(p, '.json'));
|
|
493
|
+
printOut(`[theme] select in OpenCode with: /theme ${first}`
|
|
494
|
+
+ rest.map((n) => ` (or /theme ${n})`).join('') + '\n');
|
|
488
495
|
return 0;
|
|
489
496
|
}
|
package/js/build/render.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* WHY THIS HALF AND NOT MORE. The emits split into five stages —
|
|
10
10
|
* RENDER* -> WIRE* -> PRUNE -> MANIFEST -> VERIFY (see `_build_global._emit_claude_core`
|
|
11
|
-
* and tests/
|
|
11
|
+
* and tests/unit/emit_phase_order.test.mjs). RENDER produces files Geneseed owns wholesale;
|
|
12
12
|
* WIRE reconciles files the user co-owns (settings.json, opencode.json, the CLAUDE.md
|
|
13
13
|
* managed block) and lives in `js/hosts/settings.mjs`, driven from the two emit modules.
|
|
14
14
|
* THIS module is only RENDER
|
|
@@ -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, resolveRuleIds } from './source.mjs';
|
|
37
|
+
import { PACK_ORDER, readPackText, resolveRuleIds, ruleCanon } from './source.mjs';
|
|
38
38
|
import { DEFAULT_PRESET } from '../loop/score.mjs';
|
|
39
39
|
|
|
40
40
|
/** Document STRUCTURE is theme-INDEPENDENT — mirrors `_build_render.STRUCTURE`. */
|
|
@@ -86,7 +86,7 @@ const CATALOG_BLOCK_RE =
|
|
|
86
86
|
// truncation — the objection `docs/token-footprint.md` raised against terse-ing the
|
|
87
87
|
// Ontology ("four orphan sentences") is answered by authoring the short form, not
|
|
88
88
|
// generating it. Same marker grammar as CATALOG so the two stay learnable as one idea.
|
|
89
|
-
const LEAN_BLOCK_RE =
|
|
89
|
+
export const LEAN_BLOCK_RE =
|
|
90
90
|
/[ \t]*<!-- LEAN:begin -->\n(?<full>[\s\S]*?)[ \t]*<!-- LEAN:else -->\n(?<lean>[\s\S]*?)[ \t]*<!-- LEAN:end -->\n/g;
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -184,7 +184,7 @@ function resolveLean(text, footprint) {
|
|
|
184
184
|
* match is a corner both engines implement, and the leading '' Python produces when the
|
|
185
185
|
* text starts with a heading is load-bearing (it becomes `blocks[0]`, the preamble).
|
|
186
186
|
*/
|
|
187
|
-
function splitAtLawHeadings(text) {
|
|
187
|
+
export function splitAtLawHeadings(text) {
|
|
188
188
|
const out = [];
|
|
189
189
|
let start = 0;
|
|
190
190
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -310,12 +310,8 @@ function activeDoctrines(cfg) {
|
|
|
310
310
|
// interpret. Dropped here rather than in `doctrinesBody` so `DOCTRINES_LIST` — the
|
|
311
311
|
// `Active packs:` marker a later reader parses back — agrees with what was rendered.
|
|
312
312
|
const excluded = new Set(cfg.excludeRules ?? []);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
.matchAll(/^### \{\{DOCTRINE\}\} ([a-z]+) (\d+)\b/gm)]
|
|
316
|
-
.map((m) => `${m[1]}.${Number(m[2])}`);
|
|
317
|
-
return ids.some((id) => !excluded.has(id));
|
|
318
|
-
};
|
|
313
|
+
const { rules } = ruleCanon(cfg.src);
|
|
314
|
+
const survives = (pack) => rules.some((r) => r.pack === pack && !excluded.has(`${pack}.${r.n}`));
|
|
319
315
|
return PACK_ORDER.filter((p) => wanted.includes(p)).filter(survives);
|
|
320
316
|
}
|
|
321
317
|
|
package/js/build/source.mjs
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* `capabilityLinkRe`'s equivalent override in `stripCapabilityLinks` had no producer either
|
|
21
21
|
* and was deleted outright in the over-engineering cleanup, `cfg` param and all.
|
|
22
22
|
*
|
|
23
|
-
* The move is safe for the reason P5c's was: `tests/golden.
|
|
24
|
-
* over
|
|
25
|
-
* from these paths. A depth error or a renamed key fails
|
|
23
|
+
* The move is safe for the reason P5c's was: `tests/golden.mjs` drives `bin/build-driver.mjs`
|
|
24
|
+
* over every emit configuration and requires each to render, and every one of them builds a
|
|
25
|
+
* cfg from these paths. A depth error or a renamed key fails every cell, not zero.
|
|
26
26
|
*/
|
|
27
27
|
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
28
28
|
import path from 'node:path';
|
|
@@ -38,7 +38,7 @@ import { DEFAULT_PRESET } from '../loop/score.mjs';
|
|
|
38
38
|
* on raw or folded text, so one behaviour serves both.
|
|
39
39
|
*/
|
|
40
40
|
const _packTexts = new Map();
|
|
41
|
-
|
|
41
|
+
function readRawText(file) {
|
|
42
42
|
const key = path.resolve(file);
|
|
43
43
|
const mtime = statSync(file).mtimeMs;
|
|
44
44
|
const hit = _packTexts.get(key);
|
|
@@ -160,8 +160,8 @@ export function resolveRuleIds(text, src = SRC) {
|
|
|
160
160
|
* `_OWNED` redirect the suite uses is an in-process write, and no env var moves it
|
|
161
161
|
* (`$GENESEED_ROOT` is `harness context`'s doc-discovery root — a different name for a
|
|
162
162
|
* different job). Both implementations therefore answer from their own file's location,
|
|
163
|
-
* which is what
|
|
164
|
-
* status
|
|
163
|
+
* which is what made them comparable and what makes them unfenceable. See
|
|
164
|
+
* `js/inspect/status.mjs`'s header for what that leaves unreachable from any cell.
|
|
165
165
|
*/
|
|
166
166
|
export const ROOT = path.resolve(import.meta.dirname, '../..');
|
|
167
167
|
export const SRC = path.join(ROOT, 'src');
|
|
@@ -222,7 +222,8 @@ export const PACK_ORDER = ['craft', 'rigor', 'ops', 'process', 'comms'];
|
|
|
222
222
|
* The ONE enumerator, for the same arithmetic reason `PACK_ORDER` is one array: the CLI
|
|
223
223
|
* flag, the console's trust boundary and the doctor all have to close `--exclude-rules`
|
|
224
224
|
* against the SAME set, and a second copy is a set that drifts by one rule the day a pack
|
|
225
|
-
* grows. Read
|
|
225
|
+
* grows. Read off `ruleCanon` — the same declarations, in the same order, that the render
|
|
226
|
+
* numbers its headings from and the doctor validates citations against — rather than from a
|
|
226
227
|
* built tree, because an address is a property of the source and the rendered heading is
|
|
227
228
|
* themed — fourteen spellings of a thing that must compare equal to itself.
|
|
228
229
|
*
|
|
@@ -232,16 +233,7 @@ export const PACK_ORDER = ['craft', 'rigor', 'ops', 'process', 'comms'];
|
|
|
232
233
|
* were ever out of order, and disagree in the direction that reads as correct.
|
|
233
234
|
*/
|
|
234
235
|
export function knownRuleIds() {
|
|
235
|
-
|
|
236
|
-
for (const pack of PACK_ORDER) {
|
|
237
|
-
const file = path.join(SRC, 'doctrines', `${pack}.md`);
|
|
238
|
-
let text = '';
|
|
239
|
-
try { text = readPackText(file); } catch { continue; }
|
|
240
|
-
for (const m of text.matchAll(/^### \{\{DOCTRINE\}\} ([a-z]+) (\d+)\b/gm)) {
|
|
241
|
-
if (m[1] === pack) out.push(`${pack}.${Number(m[2])}`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return out;
|
|
236
|
+
return ruleCanon(SRC).rules.filter((r) => r.kind === 'DOCTRINE').map((r) => `${r.pack}.${r.n}`);
|
|
245
237
|
}
|
|
246
238
|
|
|
247
239
|
/**
|
package/js/build/stubs.mjs
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { writeText } from '../lib/fs.mjs';
|
|
12
|
+
import { isDir } from '../lib/fs.mjs';
|
|
12
13
|
import { jsonDumpsIndent } from '../lib/json.mjs';
|
|
13
|
-
import {
|
|
14
|
+
import { EXCLUDES_FILE } from '../hosts/hosts.mjs';
|
|
14
15
|
import { existsSync } from 'node:fs';
|
|
15
16
|
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -102,8 +103,8 @@ usually one promoted from a recurring memory. Review it by that date, then
|
|
|
102
103
|
graduate it (remove the marker) or demote it back to memory.
|
|
103
104
|
`;
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
export
|
|
106
|
+
// Owned by `js/hosts/hosts.mjs` (the hook reads it there); re-exported for `js/inspect/excludes.mjs`.
|
|
107
|
+
export { EXCLUDES_FILE };
|
|
107
108
|
|
|
108
109
|
/**
|
|
109
110
|
* `_build_render.EXCLUDES_STUB` — one long line, exactly as Python spells it.
|
|
@@ -232,8 +233,8 @@ export const ensureProfileStub = stubWriter(PROFILE_FILE, PROFILE_STUB);
|
|
|
232
233
|
|
|
233
234
|
/**
|
|
234
235
|
* `_build_render.ensure_excludes_stub` — the sovereign-repo list, seeded once and NEVER
|
|
235
|
-
* overwritten.
|
|
236
|
-
*
|
|
236
|
+
* overwritten. Called by every GLOBAL emit — the Claude-shaped ones and opencode-global —
|
|
237
|
+
* since that is where the hook looks for it.
|
|
237
238
|
*/
|
|
238
239
|
export const ensureExcludesStub = stubWriter(EXCLUDES_FILE, EXCLUDES_STUB);
|
|
239
240
|
|
package/js/build/version.mjs
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { VERSION_MARKER } from '../hosts/hosts.mjs';
|
|
11
|
-
import { sourceReleaseVersion } from '../hosts/opencode.mjs';
|
|
12
11
|
import { readText, writeText } from '../lib/fs.mjs';
|
|
13
|
-
import {
|
|
12
|
+
import { parseJson } from '../lib/json.mjs';
|
|
13
|
+
import { isDir } from '../lib/fs.mjs';
|
|
14
|
+
import { relPosix } from '../lib/text.mjs';
|
|
15
|
+
import { rglobFiles } from './emit-common.mjs';
|
|
14
16
|
import { createHash } from 'node:crypto';
|
|
15
17
|
import { readFileSync } from 'node:fs';
|
|
16
18
|
|
|
@@ -132,6 +134,23 @@ export function versionIsNewer(a, b) {
|
|
|
132
134
|
return false;
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
/**
|
|
138
|
+
* `_build_core.source_release_version` — the release this checkout would install. It reads
|
|
139
|
+
* `cfg.config`, so it takes `cfg`. It lived in `js/hosts/opencode.mjs` for no better reason
|
|
140
|
+
* than that the first caller was there; every reader since asks a version question, so it
|
|
141
|
+
* lives with them. `js/hosts/opencode.mjs` re-exports it for its older importers.
|
|
142
|
+
*/
|
|
143
|
+
export function sourceReleaseVersion(cfg) {
|
|
144
|
+
try {
|
|
145
|
+
const data = parseJson(readText(cfg.config));
|
|
146
|
+
const v = data && typeof data === 'object' && !Array.isArray(data) ? data.version : null;
|
|
147
|
+
if (typeof v === 'string' && v) return v;
|
|
148
|
+
} catch {
|
|
149
|
+
// OSError / JSONDecodeError — degrade to "not newer" rather than raising.
|
|
150
|
+
}
|
|
151
|
+
return '0.0.0';
|
|
152
|
+
}
|
|
153
|
+
|
|
135
154
|
/** `_build_render._warn_if_downgrade` — STDOUT, matching the Python. */
|
|
136
155
|
function warnIfDowngrade(cfg, out) {
|
|
137
156
|
const deployed = readReleaseVersion(out);
|