geneseed 2.0.0 → 3.0.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 +217 -5
- package/DESIGN.md +149 -1326
- package/QUICKSTART.md +1 -1
- package/README.md +30 -22
- package/SETUP.md +98 -24
- package/SHIPPED.md +6 -5
- package/adapters/bob/README.md +3 -3
- package/adapters/claude-code/README.md +6 -4
- package/adapters/claude-code/skills/geneseed/SKILL.md +10 -6
- package/adapters/copilot/README.md +5 -4
- package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +11 -6
- package/adapters/opencode/HOW-OPENCODE-LOADS.md +1 -1
- package/adapters/opencode/README.md +15 -9
- package/adapters/opencode/agents/orchestrator.md +1 -1
- package/adapters/opencode/opencode.json +0 -9
- package/adapters/opencode/plugins/geneseed-context.js +7 -7
- package/adapters/opencode/plugins/geneseed-guard.js +6 -6
- package/adapters/opencode/workflows/dispatch.js +1 -1
- package/bin/geneseed.mjs +74 -8
- package/docs/declined.md +53 -2
- package/docs/design-history.md +1290 -0
- package/docs/extending.md +672 -0
- package/docs/limits.md +141 -82
- package/docs/token-footprint.md +15 -4
- package/docs/web/agents.md +1 -1
- package/docs/web/authoring.md +1 -1
- package/docs/web/claude-hooks.md +2 -2
- package/docs/web/collaboration.md +3 -3
- package/docs/web/first-session.md +2 -2
- package/docs/web/footprint.md +14 -7
- package/docs/web/foreman-mode.md +2 -2
- package/docs/web/harnesses.md +5 -3
- package/docs/web/install-quick.md +1 -1
- package/docs/web/model.md +3 -3
- package/docs/web/plugin-context.md +2 -2
- package/docs/web/plugin-guard.md +3 -3
- package/docs/web/plugin-ponytail.md +1 -1
- package/docs/web/rules.md +23 -3
- package/docs/web/setup-choices.md +20 -7
- package/docs/web/themes-author.md +13 -1
- package/docs/web-ui.md +36 -12
- package/geneseed +0 -0
- package/harness.config.json +1 -1
- package/js/catalog.mjs +29 -4
- package/js/checkout.mjs +24 -1
- package/js/cli-table.json +9 -7
- package/js/diff.mjs +8 -3
- package/js/doctor.mjs +374 -26
- package/js/emit.mjs +48 -11
- package/js/generate.mjs +39 -5
- package/js/hooks.mjs +8 -7
- package/js/installs.mjs +101 -4
- package/js/inventory.mjs +150 -20
- package/js/lib/fs.mjs +6 -2
- package/js/mcp.mjs +3 -15
- package/js/migrate.mjs +10 -4
- package/js/render.mjs +116 -11
- package/js/settings.mjs +297 -28
- package/js/setup.mjs +95 -5
- package/js/status.mjs +45 -4
- package/js/themes.mjs +1 -1
- package/js/tui.mjs +46 -3
- package/js/uninstall.mjs +30 -11
- package/js/web/actions.mjs +76 -9
- package/js/web/api.mjs +95 -16
- package/js/web/docs.mjs +33 -5
- package/js/web/jobs.mjs +12 -3
- package/js/web/server.mjs +59 -2
- package/package.json +1 -1
- package/src/AGENT.md.tmpl +75 -65
- package/src/agents/_template.md +4 -4
- package/src/agents/advocate.md +4 -4
- package/src/agents/architect.md +2 -2
- package/src/agents/developer.md +5 -5
- package/src/agents/docs.md +2 -2
- package/src/agents/empiricist.md +3 -3
- package/src/agents/explorer.md +3 -3
- 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/reviewer.md +2 -2
- package/src/agents/security.md +2 -2
- package/src/agents/skeptic.md +4 -4
- package/src/agents/steward.md +3 -3
- package/src/agents/tester.md +2 -2
- package/src/agents/user-advocate.md +3 -3
- package/src/agents/visionary.md +3 -3
- package/src/doctrines/README.md +41 -0
- package/src/doctrines/craft.md +51 -0
- package/src/doctrines/ops.md +61 -0
- package/src/doctrines/process.md +87 -0
- package/src/doctrines/rigor.md +52 -0
- package/src/laws/universal.md +17 -301
- package/src/memory/README.md +2 -2
- package/src/modes/README.md +2 -2
- package/src/modes/foreman.md +1 -1
- package/src/notebook/README.md +2 -2
- package/src/ontology/universal.md +89 -0
- package/src/postures/README.md +3 -3
- package/src/postures/artisan.md +3 -3
- package/src/postures/assistant.md +1 -1
- package/src/postures/expert.md +1 -1
- package/src/postures/mentor.md +1 -1
- package/src/postures/peer.md +2 -2
- package/src/skills/_template.md +2 -2
- package/src/skills/brainstorm.md +2 -2
- package/src/skills/clarify.md +2 -2
- package/src/skills/codebase-design.md +1 -1
- package/src/skills/commit.md +5 -5
- package/src/skills/council.md +4 -4
- package/src/skills/crash-course.md +1 -1
- package/src/skills/daydream/SKILL.md +1 -1
- package/src/skills/debug.md +2 -2
- package/src/skills/decode.md +1 -1
- package/src/skills/develop.md +6 -6
- package/src/skills/document-project.md +4 -4
- package/src/skills/domain-modeling.md +1 -1
- package/src/skills/drill.md +1 -1
- package/src/skills/feynman.md +1 -1
- package/src/skills/forge-mcp.md +3 -3
- package/src/skills/fresh-eyes.md +4 -4
- package/src/skills/frontend-design.md +1 -1
- package/src/skills/gap-detector.md +1 -1
- package/src/skills/geneseed-code-review.md +1 -1
- package/src/skills/geneseed.md +4 -4
- package/src/skills/git-archaeology.md +1 -1
- package/src/skills/git-rescue.md +3 -3
- package/src/skills/handoff.md +4 -4
- package/src/skills/herdr.md +1 -1
- package/src/skills/ingest.md +3 -3
- package/src/skills/learning-path.md +1 -1
- package/src/skills/migrate.md +5 -5
- package/src/skills/opencode-theme.md +1 -1
- package/src/skills/parallel-agents.md +2 -2
- package/src/skills/pipeline.md +2 -2
- package/src/skills/plan.md +2 -2
- package/src/skills/ponytail.md +1 -1
- package/src/skills/profile.md +1 -1
- package/src/skills/prose.md +2 -2
- package/src/skills/prototype.md +2 -2
- package/src/skills/refactor.md +2 -2
- package/src/skills/release.md +4 -4
- package/src/skills/repo-map.md +5 -5
- package/src/skills/research.md +6 -6
- package/src/skills/review-response.md +3 -3
- package/src/skills/roast-me.md +1 -1
- package/src/skills/rule.md +5 -5
- package/src/skills/ship.md +3 -3
- package/src/skills/tdd.md +2 -2
- package/src/skills/tickets.md +1 -1
- package/src/skills/wayfinder.md +1 -1
- package/src/skills/wiki.md +4 -4
- package/src/skills/workflow.md +2 -2
- package/themes/README.md +17 -7
- package/themes/_TEMPLATE.json +37 -33
- package/themes/biker.json +37 -33
- package/themes/commentator.json +37 -33
- package/themes/cyberpunk.json +37 -33
- package/themes/gamer.json +37 -33
- package/themes/imperial.json +37 -33
- package/themes/joker.json +37 -33
- package/themes/marvin.json +37 -33
- package/themes/mean.json +37 -33
- package/themes/military.json +37 -33
- package/themes/neutral.json +37 -33
- package/themes/pirate.json +37 -33
- package/themes/sports.json +37 -33
- package/themes/verstappen.json +37 -33
- package/themes/wizard.json +37 -33
- package/web/dist/assets/{About-DX1-VTBP.js → About-C0Mk0igT.js} +1 -1
- package/web/dist/assets/{Activity-Dp9NaXOn.js → Activity-C4aJJVXz.js} +1 -1
- package/web/dist/assets/{ActivityDetail-HpOBtrXS.js → ActivityDetail-DaQ85za0.js} +1 -1
- package/web/dist/assets/{Diff-Ct4G3Se8.js → Diff-B3tlO2Yj.js} +1 -1
- package/web/dist/assets/{Doctor-DGjkZFuA.js → Doctor-BNC-5Dmo.js} +1 -1
- package/web/dist/assets/{Graph-b8o-S6kv.js → Graph-us0T6ZcV.js} +1 -1
- package/web/dist/assets/Harnesses-DAMgyfM_.js +1 -0
- package/web/dist/assets/Laws-rOxegJ75.js +1 -0
- package/web/dist/assets/{Library-BEZscyaQ.js → Library-KmwrHA1J.js} +1 -1
- package/web/dist/assets/{Markdown-k_LSLBGq.js → Markdown-0eQOVGSF.js} +1 -1
- package/web/dist/assets/{Profile-DydukTxP.js → Profile-ZU7WV6Xl.js} +1 -1
- package/web/dist/assets/{Rules-CIRG4Ldt.js → Rules-CtDoPhhf.js} +1 -1
- package/web/dist/assets/{Skills-Buoi1IBr.js → Skills-BhoOxdkD.js} +1 -1
- package/web/dist/assets/{StatusBadge-CE_AJttl.js → StatusBadge-CGvYVCwa.js} +1 -1
- package/web/dist/assets/{Themes-CI4o3gJC.js → Themes-Dr7OOkD7.js} +1 -1
- package/web/dist/assets/{activity-BCW7e6Cy.js → activity-B8aq3qtl.js} +1 -1
- package/web/dist/assets/index-2wqPDxuN.css +1 -0
- package/web/dist/assets/{index-B1PIx7RM.js → index-AhMzkdbI.js} +1 -1
- package/web/dist/assets/index-CRftEtBX.js +12 -0
- package/web/dist/assets/{index-Ck2iCdaC.js → index-D0lPN_qW.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +282 -88
- package/web/dist/assets/Harnesses-gr3-aHBF.js +0 -1
- package/web/dist/assets/Laws-DTwF5tTC.js +0 -1
- package/web/dist/assets/index-BnTQUSUz.js +0 -12
- package/web/dist/assets/index-DUCsDv7z.css +0 -1
package/js/generate.mjs
CHANGED
|
@@ -33,11 +33,11 @@ import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
|
33
33
|
import path from 'node:path';
|
|
34
34
|
|
|
35
35
|
import { main as driverMain } from '../bin/geneseed.mjs';
|
|
36
|
-
import { makeCfg } from './checkout.mjs';
|
|
36
|
+
import { makeCfg, PACK_ORDER } from './checkout.mjs';
|
|
37
37
|
import { opencodeConfigDir, pyResolve } from './hosts.mjs';
|
|
38
38
|
import {
|
|
39
|
-
EMIT_HOST_SCOPE, defaultMode, defaultPosture, defaultTheme,
|
|
40
|
-
installTargets, modeOfDir, postureOfDir, readMaybe, themeOfDir,
|
|
39
|
+
EMIT_HOST_SCOPE, defaultMode, defaultPosture, defaultTheme, doctrinesForBuild,
|
|
40
|
+
footprintOfDir, installState, installTargets, modeOfDir, postureOfDir, readMaybe, themeOfDir,
|
|
41
41
|
} from './installs.mjs';
|
|
42
42
|
import { colorThemeFiles, colorThemeJson, PALETTE_ROLES } from './opencode.mjs';
|
|
43
43
|
import { renderAll } from './render.mjs';
|
|
@@ -101,9 +101,28 @@ export function cmdBuild(args) {
|
|
|
101
101
|
* because omitting it when it matched the generator's default silently coupled the answer to
|
|
102
102
|
* whatever that default happened to be — the moment the default moved, picking the old one
|
|
103
103
|
* produced the new one. `posture` and `mode` still elide at theirs, and neither has moved.
|
|
104
|
+
*
|
|
105
|
+
* ⚠ `doctrines` DOES NOT ELIDE, AND THAT IS THE SAFETY DECISION, NOT A STYLE ONE. It used to
|
|
106
|
+
* elide on the full set, by analogy with posture and mode — but those two compare against a
|
|
107
|
+
* FROZEN LITERAL (`'peer'`, `'direct'`), while the default a missing `--doctrines` actually
|
|
108
|
+
* lands on is `configDefaults()`, which reads `harness.config.json`. With `{"doctrines":
|
|
109
|
+
* ["craft"]}` in that file, an install carrying all four packs re-emitted by `upgrade` was
|
|
110
|
+
* silently narrowed to one — and the narrowing takes the commit/push consent gate off an
|
|
111
|
+
* install whose owner never asked for that. Same trap as `footprint`, one axis over, with a
|
|
112
|
+
* boundary behind it. A caller that knows the selection now always says so.
|
|
113
|
+
*
|
|
114
|
+
* `allPacks` survives the elision's removal because it is still what CANONICALISES the value:
|
|
115
|
+
* the flag is emitted in the checkout's pack order, not the order handed in, and this function
|
|
116
|
+
* is pure and has no discovery of its own.
|
|
117
|
+
*
|
|
118
|
+
* Two states: `null` means "the caller expressed no opinion" and emits no flag at all, so the
|
|
119
|
+
* generator falls back to `harness.config.json` — the only route left to that fallback, and it
|
|
120
|
+
* is reached only where nothing was read back off an install; `[]` is a deliberate empty
|
|
121
|
+
* selection and emits `--doctrines none`, which is the spelling the driver's `parseDoctrines`
|
|
122
|
+
* accepts (an empty `--doctrines ` is a usage error there, so it can never be produced here).
|
|
104
123
|
*/
|
|
105
124
|
export function setupBuildArgs(theme, emit, out = null, root = null, footprint = 'lean',
|
|
106
|
-
posture = 'peer', mode = 'direct') {
|
|
125
|
+
posture = 'peer', mode = 'direct', doctrines = null, allPacks = PACK_ORDER) {
|
|
107
126
|
const argv = ['--theme', theme, '--emit', emit];
|
|
108
127
|
if (!['opencode-global', 'claude-global', 'bob-global', 'copilot-global'].includes(emit)) {
|
|
109
128
|
if (out) argv.push('--out', out);
|
|
@@ -112,6 +131,13 @@ export function setupBuildArgs(theme, emit, out = null, root = null, footprint =
|
|
|
112
131
|
if (footprint) argv.push('--footprint', footprint);
|
|
113
132
|
if (posture && posture !== 'peer') argv.push('--posture', posture);
|
|
114
133
|
if (mode && mode !== 'direct') argv.push('--mode', mode);
|
|
134
|
+
if (Array.isArray(doctrines)) {
|
|
135
|
+
// Emitted in `allPacks` order, not the order handed in: a caller may legitimately pass an
|
|
136
|
+
// unsorted list, and the flag has to be canonical for the same reason the driver
|
|
137
|
+
// re-normalises on parse — the `Active packs:` marker is compared against itself.
|
|
138
|
+
const picked = allPacks.filter((p) => doctrines.includes(p));
|
|
139
|
+
argv.push('--doctrines', picked.length ? picked.join(',') : 'none');
|
|
140
|
+
}
|
|
115
141
|
return argv;
|
|
116
142
|
}
|
|
117
143
|
|
|
@@ -167,7 +193,15 @@ export function cmdRebuildAll() {
|
|
|
167
193
|
const posture = postureOfDir(root) || defaultPosture();
|
|
168
194
|
const mode = modeOfDir(root) || defaultMode();
|
|
169
195
|
const out = scope === 'global' ? null : root;
|
|
170
|
-
|
|
196
|
+
// SIX values now, not five. A rebuild that defaulted the pack selection would re-emit an
|
|
197
|
+
// install into a constitution its owner did not choose — and in one direction that is not
|
|
198
|
+
// merely surprising: dropping the process pack takes the commit/push consent RULES out of
|
|
199
|
+
// AGENT.md while `claudeHookGroups` keeps the gate wired, or the reverse. ⚠ AND THE
|
|
200
|
+
// FALLBACK IS NOT `harness.config.json`: a pre-migration carrier has no marker, and
|
|
201
|
+
// resolving that silence out of a machine-wide config narrows every such install on its
|
|
202
|
+
// first upgrade. `doctrinesForBuild` resolves unknown to ALL packs — see its docblock.
|
|
203
|
+
const doctrines = doctrinesForBuild(root);
|
|
204
|
+
const argv = setupBuildArgs(theme, emit, out, out, footprint, posture, mode, doctrines);
|
|
171
205
|
const label = `${host}:${scope} (${root})`;
|
|
172
206
|
pyPrint(`[rebuild-all] ${label}: theme=${theme} emit=${emit} footprint=${footprint} `
|
|
173
207
|
+ `posture=${posture} mode=${mode}\n`);
|
package/js/hooks.mjs
CHANGED
|
@@ -214,7 +214,7 @@ const out = pyPrint;
|
|
|
214
214
|
const err = pyPrintErr;
|
|
215
215
|
|
|
216
216
|
// ======================================================================================
|
|
217
|
-
// context — project-context discovery
|
|
217
|
+
// context — project-context discovery
|
|
218
218
|
// ======================================================================================
|
|
219
219
|
|
|
220
220
|
// Kept in step with adapters/opencode/plugins/geneseed-context.js, same as the Python.
|
|
@@ -494,7 +494,7 @@ export function cmdContext(args) {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
const lines = [
|
|
497
|
-
`=== PROJECT CONTEXT \u2014 binding for this repo
|
|
497
|
+
`=== PROJECT CONTEXT \u2014 binding for this repo (via ${source}) ===`,
|
|
498
498
|
'',
|
|
499
499
|
];
|
|
500
500
|
for (const entry of eager) {
|
|
@@ -525,7 +525,7 @@ export function cmdContext(args) {
|
|
|
525
525
|
}
|
|
526
526
|
|
|
527
527
|
// ======================================================================================
|
|
528
|
-
// git-gate —
|
|
528
|
+
// git-gate — Doctrine process 5's tool-boundary backstop
|
|
529
529
|
// ======================================================================================
|
|
530
530
|
|
|
531
531
|
// A `git` verb ANYWHERE in the command trips the gate, including the chained
|
|
@@ -555,12 +555,13 @@ export function cmdGitGate(args) {
|
|
|
555
555
|
return 0;
|
|
556
556
|
}
|
|
557
557
|
if (typeof command !== 'string' || !GIT_GATE_RE.test(command)) return 0;
|
|
558
|
-
out(askDecision('Geneseed
|
|
558
|
+
out(askDecision('Geneseed Doctrine process 5 \u2014 every git commit/push needs explicit '
|
|
559
|
+
+ 'approval'));
|
|
559
560
|
return 0;
|
|
560
561
|
}
|
|
561
562
|
|
|
562
563
|
// ======================================================================================
|
|
563
|
-
// rule-gate —
|
|
564
|
+
// rule-gate — Doctrine process 1's tool-boundary backstop
|
|
564
565
|
// ======================================================================================
|
|
565
566
|
|
|
566
567
|
/**
|
|
@@ -604,8 +605,8 @@ export function cmdRuleGate(args) {
|
|
|
604
605
|
if (typeof p !== 'string' || !p) return 0;
|
|
605
606
|
const target = ruleGateTarget(p, args.root);
|
|
606
607
|
if (!target) return 0;
|
|
607
|
-
out(askDecision(`Geneseed
|
|
608
|
-
+ "remember? That choice is the user's. Run the rule skill first."));
|
|
608
|
+
out(askDecision(`Geneseed Doctrine process 1 \u2014 writing to ${target}: a standing rule, or a `
|
|
609
|
+
+ "fact to remember? That choice is the user's. Run the rule skill first."));
|
|
609
610
|
return 0;
|
|
610
611
|
}
|
|
611
612
|
|
package/js/installs.mjs
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
36
36
|
import path from 'node:path';
|
|
37
37
|
|
|
38
|
-
import { CONFIG, THEMES, discoverNames } from './checkout.mjs';
|
|
38
|
+
import { CONFIG, PACK_ORDER, THEMES, discoverNames } from './checkout.mjs';
|
|
39
39
|
import { GLOBAL_MANIFEST, HOSTS, pyResolve } from './hosts.mjs';
|
|
40
40
|
import { registryRoots } from './registry.mjs';
|
|
41
41
|
import { comparePaths, pyPrintErr, pyRepr, readText } from './lib/fs.mjs';
|
|
@@ -91,6 +91,17 @@ export const defaultTheme = () => configuredDefault('theme', 'neutral');
|
|
|
91
91
|
export const defaultPosture = () => configuredDefault('posture', 'peer');
|
|
92
92
|
export const defaultMode = () => configuredDefault('mode', 'direct');
|
|
93
93
|
|
|
94
|
+
// There is deliberately no `defaultDoctrines()` beside the three above, and its absence is
|
|
95
|
+
// the invariant. `theme`, `posture` and `mode` may fall back to `harness.config.json` when an
|
|
96
|
+
// install does not say, because the worst a wrong answer does there is cosmetic. The pack
|
|
97
|
+
// selection is not like them: it decides whether the commit/push consent gate is wired, so
|
|
98
|
+
// resolving "this install does not say" out of a MACHINE-WIDE config file narrows every
|
|
99
|
+
// pre-2.3 install on its first upgrade — `{"doctrines":["craft"]}` and the gate is gone from
|
|
100
|
+
// carriers that never asked. Unknown resolves to ALL packs; see `doctrinesForBuild` below.
|
|
101
|
+
// The config value keeps exactly one legitimate reader: `configDefaults()` in
|
|
102
|
+
// `bin/geneseed.mjs`, which answers `geneseed build` — the case where there is no install to
|
|
103
|
+
// ask in the first place.
|
|
104
|
+
|
|
94
105
|
// ---- theme, footprint, posture, mode, read back off a deployed tree -----------------------
|
|
95
106
|
|
|
96
107
|
/**
|
|
@@ -159,7 +170,8 @@ const FOOTPRINTS = ['lean', 'full'];
|
|
|
159
170
|
*
|
|
160
171
|
* `status` reads no footprint row at all; the WARN is why this function is ported there, and
|
|
161
172
|
* `diff` and `rebuild-all` read the RETURN value as well. An unrecognised marker is reported
|
|
162
|
-
* rather than swallowed (Rule
|
|
173
|
+
* rather than swallowed (Rule V, *Surface Failures* — VII before the three-tier renumber, and
|
|
174
|
+
* VII now names *Least Privilege*), and a compared stderr is what makes that observable —
|
|
163
175
|
* `status/a-bogus-footprint-marker-warns-on-stderr` is the cell.
|
|
164
176
|
*/
|
|
165
177
|
export function footprintOfDir(d) {
|
|
@@ -208,6 +220,76 @@ function leadOfDir(d, names) {
|
|
|
208
220
|
export const postureOfDir = (d) => leadOfDir(d, discoverNames('postures', 'peer'));
|
|
209
221
|
export const modeOfDir = (d) => leadOfDir(d, discoverNames('modes', 'direct'));
|
|
210
222
|
|
|
223
|
+
/** The `Active packs:` marker line the template emits, in one of the five carriers. */
|
|
224
|
+
const ACTIVE_PACKS_RE = /^Active packs:[ \t]*(.+?)[ \t]*$/m;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The doctrine packs a DEPLOYED install carries — the third register read back off disk,
|
|
228
|
+
* beside `postureOfDir` and `modeOfDir`, and the first that is a SET.
|
|
229
|
+
*
|
|
230
|
+
* MARKERED IN PROSE, like posture and mode and unlike theme/footprint: `src/AGENT.md.tmpl`
|
|
231
|
+
* writes a literal `Active packs: <list>` line under the doctrines section, so no extra dotfile
|
|
232
|
+
* has to survive a copy. The label is NOT token-substituted, so a themed bundle still answers.
|
|
233
|
+
*
|
|
234
|
+
* ⚠ THREE RETURN STATES, AND THE NULL IS THE SAFETY ONE.
|
|
235
|
+
* - `null` — no marker in any carrier. That is a pre-migration install, or a carrier that
|
|
236
|
+
* never had one, and it means "unknown", NOT "no packs". Every consumer must read it as
|
|
237
|
+
* the process pack being ACTIVE, so the commit/push consent gate stays installed. Write
|
|
238
|
+
* `doctrinesOfDir(root)` into an explicit `=== null` / `??` test — never
|
|
239
|
+
* `doctrinesOfDir(root)?.includes('process')`, which is falsy on `null` and would strip
|
|
240
|
+
* the gate from every existing install on its next upgrade.
|
|
241
|
+
* - `[]` — the marker is present and reads `none`. A deliberate empty selection (invariants
|
|
242
|
+
* and ontology only) and a real configuration: the gate comes OFF.
|
|
243
|
+
* - a list — the packs named, re-ordered through `PACK_ORDER`, because this value is
|
|
244
|
+
* compared against a marker the build writes in that order.
|
|
245
|
+
*
|
|
246
|
+
* A marker line this checkout cannot read WHOLE falls back to `null` rather than to whatever
|
|
247
|
+
* part of it did parse, for the same reason: a corrupt line is unknown, and unknown fails
|
|
248
|
+
* closed. ⚠ "Whole" is the strict word and it is what the first cut of this got wrong. The
|
|
249
|
+
* regex is single-line, so a marker a wrapper had folded — `Active packs: craft, rigor,\nops,
|
|
250
|
+
* process` — matched its first half, every name in that half was known, and the function
|
|
251
|
+
* answered the NARROWED set `['craft','rigor']` with no complaint. That is fail-OPEN against
|
|
252
|
+
* this docblock's own promise: the process pack disappears from an install that has it, and
|
|
253
|
+
* the gate goes with it. So every comma-separated field must be non-empty AND a pack this
|
|
254
|
+
* checkout ships; one that is not condemns the whole line to `null`. A trailing comma is
|
|
255
|
+
* exactly the empty field a fold leaves behind, which is what makes this catch it.
|
|
256
|
+
*/
|
|
257
|
+
export function doctrinesOfDir(d) {
|
|
258
|
+
for (const carrier of CARRIERS) {
|
|
259
|
+
const text = readMaybe(path.join(d, carrier));
|
|
260
|
+
if (text === null) continue;
|
|
261
|
+
const m = ACTIVE_PACKS_RE.exec(text);
|
|
262
|
+
if (!m) continue;
|
|
263
|
+
if (m[1] === 'none') return [];
|
|
264
|
+
const named = m[1].split(',').map((s) => s.trim());
|
|
265
|
+
if (!named.every((n) => PACK_ORDER.includes(n))) return null;
|
|
266
|
+
return PACK_ORDER.filter((p) => named.includes(p));
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* `doctrinesOfDir` with the `null` RESOLVED — the one spelling every build-side consumer uses.
|
|
273
|
+
*
|
|
274
|
+
* ⚠ UNKNOWN RESOLVES TO ALL PACKS, NEVER TO A CONFIG VALUE. `doctrinesOfDir`'s docblock puts
|
|
275
|
+
* the rule on the reader; this function is what makes it true of the readers, because the
|
|
276
|
+
* previous shape (`doctrinesOfDir(root) ?? defaultDoctrines()`) left every consumer free to
|
|
277
|
+
* pick its own fallback and all five of them picked `harness.config.json`. With
|
|
278
|
+
* `{"doctrines":["craft"]}` in that file, an `upgrade`/`rebuild-all`/`migrate` re-emitted every
|
|
279
|
+
* pre-2.3 install at ONE pack and took the commit/push consent gate off installs whose owners
|
|
280
|
+
* had never been asked — the exact outcome the fail-closed reading exists to prevent, and it
|
|
281
|
+
* got WIDER when the reader was hardened, because more inputs now answer `null`.
|
|
282
|
+
*
|
|
283
|
+
* A config value is a legitimate default only where there is NO INSTALL TO ASK: a fresh
|
|
284
|
+
* `geneseed build`, which `configDefaults()` in `bin/geneseed.mjs` answers. Anything holding a
|
|
285
|
+
* directory has an install to ask, so it calls this and never the config.
|
|
286
|
+
*
|
|
287
|
+
* `[]` still passes through untouched — a marker that reads `none` is an answer, not a silence.
|
|
288
|
+
*/
|
|
289
|
+
export function doctrinesForBuild(d) {
|
|
290
|
+
return doctrinesOfDir(d) ?? [...PACK_ORDER];
|
|
291
|
+
}
|
|
292
|
+
|
|
211
293
|
// ---- what host a deployed dir belongs to (`_harness_mcp`) ---------------------------------
|
|
212
294
|
|
|
213
295
|
/** `_harness_mcp._claude_read_manifest`. */
|
|
@@ -333,7 +415,7 @@ export function installTargets() {
|
|
|
333
415
|
// ---- what this machine installed (`_harness_setup._installed_defaults`) --------------------
|
|
334
416
|
|
|
335
417
|
/**
|
|
336
|
-
* `_harness_setup._installed_defaults` — all
|
|
418
|
+
* `_harness_setup._installed_defaults` — all six keys.
|
|
337
419
|
*
|
|
338
420
|
* It carried only `theme` and `emit` for four phases because those are the two `status`
|
|
339
421
|
* prints and the two `doctor` reads, and the port's keep-vs-delete rule is "part of an
|
|
@@ -342,9 +424,20 @@ export function installTargets() {
|
|
|
342
424
|
* `inst.posture` / `inst.mode` / `inst.footprint`, so a three-key answer would have silently
|
|
343
425
|
* pre-selected the CONFIGURED default over the DEPLOYED one on three of its five questions.
|
|
344
426
|
* Nothing in `status` or `doctor` moves: both read the keys they already read.
|
|
427
|
+
*
|
|
428
|
+
* `doctrines` joined last, for the same reason and one phase later: the wizard's pack question
|
|
429
|
+
* shipped before the marker reader existed, so it defaulted to `all` for everyone and re-asked
|
|
430
|
+
* an installer who had already narrowed their selection. ⚠ THIS KEY IS THE ONE WITH THREE
|
|
431
|
+
* STATES, and the `=== null` tests below preserve all three: `null` is "no carrier on this
|
|
432
|
+
* machine said", `[]` is a carrier that said `none`, a list is a carrier that named packs.
|
|
433
|
+
* Only a WIZARD may read it — it is a pre-selection shown to a human who then answers. No
|
|
434
|
+
* build-side consumer resolves a pack set from here; that is `doctrinesForBuild(dir)`, which
|
|
435
|
+
* asks one named install rather than whichever candidate this walk happens to reach first.
|
|
345
436
|
*/
|
|
346
437
|
export function installedDefaults() {
|
|
347
|
-
const found = {
|
|
438
|
+
const found = {
|
|
439
|
+
theme: null, posture: null, mode: null, emit: null, footprint: null, doctrines: null,
|
|
440
|
+
};
|
|
348
441
|
const candidates = [];
|
|
349
442
|
for (const { host, configDir } of HOSTS) {
|
|
350
443
|
try { candidates.push([configDir(), `${host}-global`]); } catch { /* as the Python */ }
|
|
@@ -373,6 +466,10 @@ export function installedDefaults() {
|
|
|
373
466
|
if (found.footprint === null && isFile(path.join(base, '.geneseed-footprint'))) {
|
|
374
467
|
found.footprint = footprintOfDir(base);
|
|
375
468
|
}
|
|
469
|
+
// `doctrinesOfDir` and not `doctrinesForBuild`: the resolved form answers `[...PACK_ORDER]`
|
|
470
|
+
// for a carrier with no marker, which would end this walk at the FIRST candidate and hide
|
|
471
|
+
// a later carrier that does say. The null has to survive to the caller here.
|
|
472
|
+
if (found.doctrines === null) found.doctrines = doctrinesOfDir(base);
|
|
376
473
|
}
|
|
377
474
|
return found;
|
|
378
475
|
}
|
package/js/inventory.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import path from 'node:path';
|
|
23
23
|
|
|
24
|
-
import { ROOT, SRC, makeCfg } from './checkout.mjs';
|
|
24
|
+
import { ROOT, SRC, makeCfg, PACK_ORDER } from './checkout.mjs';
|
|
25
25
|
import { firstBlockquote } from './native.mjs';
|
|
26
26
|
import { renderAll } from './render.mjs';
|
|
27
27
|
import { pyResolve } from './hosts.mjs';
|
|
@@ -29,29 +29,59 @@ import { parseJson, readText } from './lib/fs.mjs';
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* `_harness_tui.LAW_HEADING_RE` — a heading in the RENDERED laws file, e.g.
|
|
32
|
-
* `### Rule
|
|
32
|
+
* `### Rule IV — Deletion Is Deliberate`.
|
|
33
33
|
*
|
|
34
34
|
* Not `js/doctor.mjs`'s `LAW_HEADING_RE`, which matches the `{{LAW}}` token in the
|
|
35
35
|
* unrendered source. Two patterns, two inputs, two names in Python as well.
|
|
36
36
|
*/
|
|
37
37
|
export const LAW_HEADING_RE = /^###\s+\S+\s+([IVXLCDM]+)\s+[—-]\s+(.+?)\s*$/;
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A heading in a RENDERED doctrine pack, e.g. `### Doctrine craft 1 — Automate Repetition`.
|
|
41
|
+
*
|
|
42
|
+
* THE TIER NOUN IS THEMED AND THE ADDRESS IS NOT, exactly as the invariants work: `{{LAW}}`
|
|
43
|
+
* plus a Roman numeral there, `{{DOCTRINE}}` plus `<pack> <n>` here. So `\S+` for the noun —
|
|
44
|
+
* ⚠ which is why a theme's `DOCTRINE` value must be ONE WORD, and why `doctor` asserts it in
|
|
45
|
+
* all fourteen. A two-word noun does not error; it silently stops matching, and a pack quietly
|
|
46
|
+
* parses to zero rules.
|
|
47
|
+
*
|
|
48
|
+
* It cannot collide with `LAW_HEADING_RE` in either direction and the reason is case:
|
|
49
|
+
* `([a-z]+)` refuses `Rule I`'s uppercase numeral, and `([IVXLCDM]+)` refuses a lowercase pack
|
|
50
|
+
* id. Measured across all fourteen themes — zero cross-matches either way.
|
|
51
|
+
*/
|
|
52
|
+
export const DOCTRINE_HEADING_RE = /^###\s+\S+\s+([a-z]+)\s+(\d+)\s+[—-]\s+(.+?)\s*$/;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A section heading in the RENDERED ontology, e.g. `#### Telos`.
|
|
56
|
+
*
|
|
57
|
+
* FOUR HASHES, and that is the whole reason the ontology can share `universal.md`'s basename
|
|
58
|
+
* with the laws file without either parser seeing the other's headings. The section names come
|
|
59
|
+
* from `STRUCTURE` rather than from a theme, so the id below is stable across all fourteen
|
|
60
|
+
* voices and is safe to put in a deep link.
|
|
61
|
+
*/
|
|
62
|
+
export const ONTOLOGY_HEADING_RE = /^####\s+(.+?)\s*$/;
|
|
63
|
+
|
|
64
|
+
/** The lead line of a pack file — `**Craft** — how code is written.` */
|
|
65
|
+
const PACK_LEAD_RE = /^\*\*(.+?)\*\*\s+[—-]\s+(.+?)\s*$/;
|
|
66
|
+
|
|
39
67
|
/** `_harness_tui.LAW_CLASS` — each rule's class, keyed by Roman numeral. */
|
|
40
68
|
export const LAW_CLASS = {
|
|
41
|
-
I: 'security', II: 'process', III: 'verify', IV: 'security', V: '
|
|
42
|
-
VI: '
|
|
43
|
-
XI: 'craft', XII: 'craft', XIII: 'craft', XIV: 'process', XV: 'process',
|
|
44
|
-
XVI: 'context', XVII: 'context', XVIII: 'context', XIX: 'context', XX: 'security',
|
|
45
|
-
XXI: 'process', XXII: 'security', XXIII: 'security', XXIV: 'craft', XXV: 'craft',
|
|
46
|
-
XXVI: 'craft', XXVII: 'verify', XXVIII: 'process', XXIX: 'comms', XXX: 'comms',
|
|
47
|
-
XXXI: 'comms', XXXII: 'craft', XXXIII: 'craft', XXXIV: 'verify', XXXV: 'verify',
|
|
48
|
-
XXXVI: 'security', XXXVII: 'process',
|
|
69
|
+
I: 'security', II: 'process', III: 'verify', IV: 'security', V: 'verify',
|
|
70
|
+
VI: 'security', VII: 'security', VIII: 'craft', IX: 'security',
|
|
49
71
|
};
|
|
50
72
|
|
|
51
73
|
/** `_harness_tui.ENTITY_STATUSES` — the lifecycle statuses `registry.json` may carry. */
|
|
52
74
|
export const ENTITY_STATUSES = ['experimental', 'approved', 'deprecated'];
|
|
53
75
|
|
|
54
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* `_harness_tui.LAW_CLASSES` — the six governance classes a law may carry.
|
|
78
|
+
*
|
|
79
|
+
* ⚠ SIX, THOUGH ONLY FOUR ARE IN USE. When the corpus became nine invariants, `context` and
|
|
80
|
+
* `comms` stopped having a member — that material moved to the ontology and the doctrine packs,
|
|
81
|
+
* neither of which is classed here. The list stays six because it is the VOCABULARY, not a
|
|
82
|
+
* census: `doctor` quotes it verbatim in the message a bad class earns, and two recorded cells
|
|
83
|
+
* in `tests/helpers/matrix/cli.*.json` hold that message byte for byte.
|
|
84
|
+
*/
|
|
55
85
|
export const LAW_CLASSES = ['security', 'process', 'verify', 'craft', 'context', 'comms'];
|
|
56
86
|
|
|
57
87
|
/** `_harness_tui.SKILL_CLASS` — each skill's category, keyed by file stem. */
|
|
@@ -105,6 +135,68 @@ export function parseLaws(text) {
|
|
|
105
135
|
return laws;
|
|
106
136
|
}
|
|
107
137
|
|
|
138
|
+
/**
|
|
139
|
+
* One rendered doctrine pack file split into `{pack, n, title, klass, body}` rows.
|
|
140
|
+
*
|
|
141
|
+
* Same accumulator as `parseLaws`, and deliberately so: text before the first heading — the
|
|
142
|
+
* pack's own lead line — is dropped on the floor because `cur` is still null, and each body is
|
|
143
|
+
* trimmed at its ends only.
|
|
144
|
+
*
|
|
145
|
+
* `klass` IS THE PACK ID, not one of `LAW_CLASSES`. A doctrine rule is grouped by the pack it
|
|
146
|
+
* belongs to and there is no second taxonomy over it; the six-class chip is the invariants'.
|
|
147
|
+
* The collision with the class names `craft` and `process` is real and harmless — the two
|
|
148
|
+
* lists are separate end to end and nothing ever unions them.
|
|
149
|
+
*
|
|
150
|
+
* `pack` is read from the HEADING and not from the filename, so a rule filed under the wrong
|
|
151
|
+
* pack is visible rather than silently renamed by its container. `doctor` is what compares the
|
|
152
|
+
* two; this function just reports what the text says.
|
|
153
|
+
*/
|
|
154
|
+
export function parseDoctrines(text) {
|
|
155
|
+
const rules = [];
|
|
156
|
+
let cur = null;
|
|
157
|
+
for (const line of text.split('\n')) {
|
|
158
|
+
const m = DOCTRINE_HEADING_RE.exec(line);
|
|
159
|
+
if (m) {
|
|
160
|
+
if (cur) rules.push(cur);
|
|
161
|
+
cur = { pack: m[1], n: Number(m[2]), title: m[3], klass: m[1], body: '' };
|
|
162
|
+
} else if (cur !== null) {
|
|
163
|
+
cur.body += `${line}\n`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (cur) rules.push(cur);
|
|
167
|
+
for (const rule of rules) rule.body = rule.body.trim();
|
|
168
|
+
return rules;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The rendered ontology split into `{id, title, body}` sections.
|
|
173
|
+
*
|
|
174
|
+
* `id` is the slugified title, and it is an ADDRESS: `#/item/law/ont:telos` is how the console
|
|
175
|
+
* deep-links a section, so it has to survive a theme change. It does, because the four titles
|
|
176
|
+
* come from `STRUCTURE` in `js/render.mjs` and not from any theme file — the same reason a
|
|
177
|
+
* citation can spell itself `({{ONTOLOGY}}: {{ONT_TELOS}})` with a token on both sides.
|
|
178
|
+
*
|
|
179
|
+
* The preamble above the first `####` is dropped, as in the two parsers above. It is the
|
|
180
|
+
* paragraph that states the tier order, and it renders into AGENT.md whole — the catalogue
|
|
181
|
+
* lists sections, and the preamble is not one.
|
|
182
|
+
*/
|
|
183
|
+
export function parseOntology(text) {
|
|
184
|
+
const sections = [];
|
|
185
|
+
let cur = null;
|
|
186
|
+
for (const line of text.split('\n')) {
|
|
187
|
+
const m = ONTOLOGY_HEADING_RE.exec(line);
|
|
188
|
+
if (m) {
|
|
189
|
+
if (cur) sections.push(cur);
|
|
190
|
+
cur = { id: m[1].toLowerCase().replace(/\s+/g, '-'), title: m[1], body: '' };
|
|
191
|
+
} else if (cur !== null) {
|
|
192
|
+
cur.body += `${line}\n`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (cur) sections.push(cur);
|
|
196
|
+
for (const s of sections) s.body = s.body.trim();
|
|
197
|
+
return sections;
|
|
198
|
+
}
|
|
199
|
+
|
|
108
200
|
/**
|
|
109
201
|
* `_harness_tui.load_registry` — the `entities` map from `registry.json`, or `{}`.
|
|
110
202
|
*
|
|
@@ -143,33 +235,71 @@ export function entityStatus(registry, key) {
|
|
|
143
235
|
}
|
|
144
236
|
|
|
145
237
|
/**
|
|
146
|
-
* `_harness_tui._tui_inventory` — render-accurate inventory
|
|
147
|
-
* its one-line purpose,
|
|
148
|
-
*
|
|
238
|
+
* `_harness_tui._tui_inventory` — render-accurate inventory of all three constitutional tiers,
|
|
239
|
+
* plus every agent and skill with its one-line purpose, rendered spec, source and badge.
|
|
240
|
+
*
|
|
241
|
+
* THE WHOLE CATALOGUE, NOT THE BUILT SUBSET. All four pack files ship in every bundle
|
|
242
|
+
* (`OWNED_SRC_DIRS`), which is what lets a cross-pack citation resolve when its pack is not
|
|
243
|
+
* built into AGENT.md — and it is also what lets the console show an inactive pack greyed out
|
|
244
|
+
* with the command that would enable it. So the packs are always all parsed; `doctrines` only
|
|
245
|
+
* decides which of them carry `active: true`. Passing `null` — the default, and what `catalog`,
|
|
246
|
+
* `status` and the graph do — means "no install to ask", and every pack reads active, matching
|
|
247
|
+
* `makeCfg()`'s own default and `doctrinesForBuild`'s resolution of unknown.
|
|
248
|
+
*
|
|
249
|
+
* `laws` KEEPS ITS NAME and means the invariants. Renaming it to `invariants` would churn
|
|
250
|
+
* `status`, `catalog`, `tui`, the web API, its `SECTIONS` id, three test fixtures and two
|
|
251
|
+
* recorded corpora to say the same thing in a different word.
|
|
149
252
|
*/
|
|
150
|
-
export function tuiInventory(themeName) {
|
|
253
|
+
export function tuiInventory(themeName, doctrines = null) {
|
|
151
254
|
const { items } = renderAll(makeCfg(), themeName);
|
|
152
255
|
const registry = loadRegistry();
|
|
153
256
|
const agents = [];
|
|
154
257
|
const skills = [];
|
|
155
258
|
let laws = [];
|
|
259
|
+
let ontology = [];
|
|
260
|
+
const packs = new Map();
|
|
156
261
|
for (const { text, src } of items) {
|
|
157
262
|
if (text === null) continue;
|
|
158
263
|
const parts = path.relative(SRC, src).split(path.sep);
|
|
159
|
-
|
|
160
|
-
|
|
264
|
+
const two = parts.length === 2 && parts[1].endsWith('.md') && !parts[1].startsWith('_');
|
|
265
|
+
const name = two ? parts[1].slice(0, -3) : null;
|
|
266
|
+
if (two && (parts[0] === 'agents' || parts[0] === 'skills')) {
|
|
161
267
|
const entry = { name, desc: firstBlockquote(text), body: text,
|
|
162
268
|
source: pyResolve(src), status: entityStatus(registry, `${parts[0]}/${name}`) };
|
|
163
269
|
if (parts[0] === 'agents') agents.push(entry);
|
|
164
|
-
else
|
|
270
|
+
else {
|
|
165
271
|
entry.klass = has(SKILL_CLASS, name) ? SKILL_CLASS[name] : 'build';
|
|
166
272
|
skills.push(entry);
|
|
167
273
|
}
|
|
168
274
|
}
|
|
169
|
-
|
|
275
|
+
// Every `.md` in the dir is read; `PACK_ORDER` below decides which of them is a pack. The
|
|
276
|
+
// dir also holds a `README.md`, which passes the `_`-scaffold filter, and a second
|
|
277
|
+
// membership test here would be a second owner of the same rule — one that has to agree
|
|
278
|
+
// with the one that actually publishes the list.
|
|
279
|
+
if (two && parts[0] === 'doctrines') {
|
|
280
|
+
const lead = text.split('\n').map((l) => PACK_LEAD_RE.exec(l)).find(Boolean);
|
|
281
|
+
packs.set(name, { pack: name, title: lead ? lead[1] : name, desc: lead ? lead[2] : '',
|
|
282
|
+
source: pyResolve(src), rules: parseDoctrines(text) });
|
|
283
|
+
}
|
|
284
|
+
// Scoped to the laws dir, not matched on the basename: `src/ontology/universal.md`
|
|
285
|
+
// shares the name, sorts after `laws/`, and would clobber the parse with `[]` —
|
|
286
|
+
// `LAW_HEADING_RE` wants `###` and the ontology uses `####`. Same spelling as the
|
|
287
|
+
// already-scoped sites at `js/render.mjs`'s `rel === 'laws/universal.md'`.
|
|
288
|
+
if (parts.join('/') === 'laws/universal.md') laws = parseLaws(text);
|
|
289
|
+
if (parts.join('/') === 'ontology/universal.md') ontology = parseOntology(text);
|
|
170
290
|
}
|
|
171
291
|
const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
172
292
|
agents.sort(byName);
|
|
173
293
|
skills.sort(byName);
|
|
174
|
-
|
|
294
|
+
// ⚠ `PACK_ORDER` IS THE SINGLE OWNER OF THE SET, and of its order. Filtering the order by
|
|
295
|
+
// what was found — rather than publishing what was found — is what keeps `README.md` and any
|
|
296
|
+
// other stray `.md` in that directory out of the catalogue, and it is the same membership
|
|
297
|
+
// rule `js/render.mjs` refuses the build over, so a fifth pack cannot be silently dropped
|
|
298
|
+
// here either. Order because `sortedSourceFiles` walks alphabetically, which puts `ops`
|
|
299
|
+
// second: the narrative order is the reading order, and it is what the marker line, the
|
|
300
|
+
// wizard and the rendered section all use.
|
|
301
|
+
const on = Array.isArray(doctrines) ? doctrines : PACK_ORDER;
|
|
302
|
+
const built = PACK_ORDER.filter((p) => packs.has(p))
|
|
303
|
+
.map((p) => ({ ...packs.get(p), active: on.includes(p) }));
|
|
304
|
+
return { agents, skills, laws, ontology, doctrines: built, theme: themeName };
|
|
175
305
|
}
|
package/js/lib/fs.mjs
CHANGED
|
@@ -281,10 +281,14 @@ export const normcase = process.platform === 'win32'
|
|
|
281
281
|
* flat cmp : the reverse again
|
|
282
282
|
*
|
|
283
283
|
* Nothing in the emitted tree could see it: `js/render.mjs` sorts a list whose members share
|
|
284
|
-
* a directory depth, and `golden.py`
|
|
284
|
+
* a directory depth, and `golden.py` compared CONTENT rather than order. It became visible
|
|
285
285
|
* the first time a full path list was PRINTED \u2014 `geneseed validate -v` \u2014 against a real
|
|
286
286
|
* skills tree that happens to contain both `skills/geneseed/` and `skills/geneseed-code-review/`.
|
|
287
|
-
* `tests/test_maintainer_tools_parity.py`
|
|
287
|
+
* `tests/test_maintainer_tools_parity.py` found it and died with the reference it compared
|
|
288
|
+
* against. What HOLDS it now is `tests/unit/maintainer_tools.test.mjs`, and specifically its
|
|
289
|
+
* `theme files are visited in THIS platform's Path collation order` test: it re-derives Python's
|
|
290
|
+
* `sorted(Path)` order for the running platform instead of replaying a recording, so it cannot
|
|
291
|
+
* agree with a drift \u2014 and it is the measured killer of this function's mutation (M3).
|
|
288
292
|
*/
|
|
289
293
|
export function comparePaths(a, b) {
|
|
290
294
|
const A = normcase(a).split(/[\\/]/);
|
package/js/mcp.mjs
CHANGED
|
@@ -75,8 +75,9 @@ export const MCP_PRESETS = {
|
|
|
75
75
|
label: 'GitLab',
|
|
76
76
|
desc: 'GitLab repo / MR / issue / CI tools via @zereight/mcp-gitlab (npx, no '
|
|
77
77
|
+ 'install). Edit GITLAB_PERSONAL_ACCESS_TOKEN (scopes: api, read_repository) '
|
|
78
|
-
+ 'and GITLAB_API_URL before use.
|
|
79
|
-
+ '
|
|
78
|
+
+ 'and GITLAB_API_URL before use. For a second instance (a self-hosted server), '
|
|
79
|
+
+ 'copy this block by hand under a new name and point it at the other URL — '
|
|
80
|
+
+ 'SETUP.md has the pattern.',
|
|
80
81
|
block: {
|
|
81
82
|
type: 'local',
|
|
82
83
|
command: ['npx', '-y', '@zereight/mcp-gitlab'],
|
|
@@ -85,19 +86,6 @@ export const MCP_PRESETS = {
|
|
|
85
86
|
enabled: true,
|
|
86
87
|
},
|
|
87
88
|
},
|
|
88
|
-
'gitlab-2': {
|
|
89
|
-
label: 'GitLab (2nd instance)',
|
|
90
|
-
desc: 'A second GitLab instance (e.g. a self-hosted server) via the same '
|
|
91
|
-
+ '@zereight/mcp-gitlab command. Point GITLAB_API_URL at the other instance '
|
|
92
|
-
+ "and give it that instance's own token.",
|
|
93
|
-
block: {
|
|
94
|
-
type: 'local',
|
|
95
|
-
command: ['npx', '-y', '@zereight/mcp-gitlab'],
|
|
96
|
-
environment: { GITLAB_PERSONAL_ACCESS_TOKEN: '',
|
|
97
|
-
GITLAB_API_URL: 'https://gitlab.example.com/api/v4' },
|
|
98
|
-
enabled: true,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
89
|
filesystem: {
|
|
102
90
|
label: 'Filesystem',
|
|
103
91
|
desc: 'Read/write files under explicitly allowed directories via '
|
package/js/migrate.mjs
CHANGED
|
@@ -35,8 +35,8 @@ import path from 'node:path';
|
|
|
35
35
|
import { main as driverMain } from '../bin/geneseed.mjs';
|
|
36
36
|
import { ROOT } from './checkout.mjs';
|
|
37
37
|
import {
|
|
38
|
-
EMIT_HOST_SCOPE, defaultMode, defaultPosture, defaultTheme,
|
|
39
|
-
installTargets, modeOfDir, postureOfDir, readMaybe, themeOfDir,
|
|
38
|
+
EMIT_HOST_SCOPE, defaultMode, defaultPosture, defaultTheme, doctrinesForBuild,
|
|
39
|
+
footprintOfDir, installState, installTargets, modeOfDir, postureOfDir, readMaybe, themeOfDir,
|
|
40
40
|
} from './installs.mjs';
|
|
41
41
|
import { DEFAULT_EMIT, setupBuildArgs } from './generate.mjs';
|
|
42
42
|
import {
|
|
@@ -206,7 +206,12 @@ export function cmdMigrate(args = {}) {
|
|
|
206
206
|
try { rmSync(stash, { recursive: true, force: true }); } catch { /* nothing to undo */ }
|
|
207
207
|
};
|
|
208
208
|
|
|
209
|
-
// Step 3 — re-emit, in each install's OWN
|
|
209
|
+
// Step 3 — re-emit, in each install's OWN six values. Same reads as `cmdRebuildAll`, and
|
|
210
|
+
// the pack selection is the sixth: leaving it out re-emitted a narrowed install at whatever
|
|
211
|
+
// `harness.config.json` said, which WIDENS a constitution its owner had cut down (and, the
|
|
212
|
+
// other way, can strip the consent gate). `null` — a pre-marker carrier, which on a MIGRATION
|
|
213
|
+
// is the common case and not the rare one — resolves to ALL packs through
|
|
214
|
+
// `doctrinesForBuild`, never to `harness.config.json`, which is what makes it fail-closed.
|
|
210
215
|
for (const r of rows) {
|
|
211
216
|
const marker = r.marker && (EMIT_HOST_SCOPE.get(r.marker) ?? ['', ''])[0] === r.host
|
|
212
217
|
? r.marker : '';
|
|
@@ -214,7 +219,8 @@ export function cmdMigrate(args = {}) {
|
|
|
214
219
|
const theme = themeOfDir(r.root) || defaultTheme();
|
|
215
220
|
const out = r.scope === 'global' ? null : r.root;
|
|
216
221
|
const argv = setupBuildArgs(theme, emit, out, out, footprintOfDir(r.root),
|
|
217
|
-
postureOfDir(r.root) || defaultPosture(), modeOfDir(r.root) || defaultMode()
|
|
222
|
+
postureOfDir(r.root) || defaultPosture(), modeOfDir(r.root) || defaultMode(),
|
|
223
|
+
doctrinesForBuild(r.root));
|
|
218
224
|
const label = `${r.host}:${r.scope} (${r.root})`;
|
|
219
225
|
pyPrint(`[migrate] re-emitting ${label}: theme=${theme} emit=${emit}\n`);
|
|
220
226
|
let rc = 1;
|