geneseed 4.18.0 → 4.19.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 +26 -0
- package/DESIGN.md +5 -5
- package/SHIPPED.md +1 -1
- package/docs/concepts/footprint.md +1 -1
- package/docs/extending.md +22 -30
- package/docs/web-ui.md +1 -1
- package/harness.config.json +1 -1
- package/js/README.md +1 -1
- package/js/build/source.mjs +27 -0
- package/js/inspect/checks-authoring.mjs +1 -1
- package/js/inspect/checks-constitution.mjs +66 -135
- package/js/inspect/checks-counts.mjs +3 -6
- package/js/web/actions.mjs +14 -5
- package/js/web/catalog.mjs +11 -4
- package/js/web/user-files.mjs +1 -1
- package/package.json +1 -2
- package/src/doctrines/comms.md +5 -0
- package/src/doctrines/craft.md +7 -0
- package/src/doctrines/ops.md +7 -0
- package/src/doctrines/process.md +7 -0
- package/src/doctrines/rigor.md +5 -0
- package/src/laws/universal.md +11 -1
- package/src/skills/rule.md +4 -2
- package/web/dist/assets/{Activity-D3_bQCUu.js → Activity-2fa9G39h.js} +1 -1
- package/web/dist/assets/{ActivityDetail-CF4t4Tfu.js → ActivityDetail-DIT7ITJw.js} +1 -1
- package/web/dist/assets/Dashboard-Ddz0KQF3.js +3 -0
- package/web/dist/assets/{Docs-DxUYipLM.js → Docs-j5cPrsOs.js} +1 -1
- package/web/dist/assets/{GreenhouseView-DYYctWtr.js → GreenhouseView-BhI_tF4c.js} +1 -1
- package/web/dist/assets/Installs-kkE0H4Q2.js +3 -0
- package/web/dist/assets/JournalView-w5adl4Sm.js +1 -0
- package/web/dist/assets/{KitApply-BllCOBIp.js → KitApply-BAF0gtXb.js} +1 -1
- package/web/dist/assets/{Kits-Bkujcmd1.js → Kits-BteVDSKD.js} +1 -1
- package/web/dist/assets/Laws-DNTpf31b.js +3 -0
- package/web/dist/assets/{Library-CsVwkY-2.js → Library-CgX1dJTr.js} +1 -1
- package/web/dist/assets/{Loops-Dj50x8qB.js → Loops-MhJU2d8B.js} +1 -1
- package/web/dist/assets/{OperatorHudView-DcY3jDhO.js → OperatorHudView-BvLd1zig.js} +1 -1
- package/web/dist/assets/Personal-BdN0jlmh.js +1 -0
- package/web/dist/assets/{Tabs-u5wJjzu8.js → Tabs-BPs85vgk.js} +1 -1
- package/web/dist/assets/{drawable-CUtqWN8k.js → drawable-DjpisR6c.js} +1 -1
- package/web/dist/assets/index-B9-bvRjd.js +2 -0
- package/web/dist/assets/index-DwzZK5uG.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/Dashboard-CLs-bR_v.js +0 -3
- package/web/dist/assets/Installs-sz2MgKXB.js +0 -3
- package/web/dist/assets/JournalView-DFvDimtU.js +0 -1
- package/web/dist/assets/Laws-B2cyrICK.js +0 -3
- package/web/dist/assets/Personal-RriJXVUS.js +0 -1
- package/web/dist/assets/index-D7KB52Os.js +0 -2
- package/web/dist/assets/index-ngEyS9_E.css +0 -1
- package/web/src/pages/Laws.jsx +0 -683
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,32 @@ label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [4.19.0] — 2026-10-24
|
|
12
|
+
|
|
13
|
+
Minor: shared console building blocks, rule summaries kept next to their rules, and `global` as a documented rule scope.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Shared console building blocks.** `PageHeader` (one primary action), `EmptyState`, `DataTable`, `Stat`, `Bar`/`StackBar`, `Ring` and `Popover`, on a 7-step type scale, an 8-step spacing scale and named z-index layers. Pages adopt them in the next release.
|
|
17
|
+
- **`global` is a documented rule scope.** The `rule` skill names `project`, `global` and `user`, and the console's scope select offers `global`. A scope it does not list is still kept as written.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Rule summaries live next to their rules.** The one-line summary the Constitution page prints for each law and doctrine rule is now a `<!-- note: summary: … -->` line under the rule's heading in `src/`, served by the catalog; the page's own copy of the 40 summaries is gone. The text moved word for word, and installs are byte-identical. `doctor` checks one summary per rule, none orphaned.
|
|
21
|
+
- **Destructive confirmations look destructive.** Forget, Retire, Restore, Stop server and discarding an unsaved edit use a danger button and put focus on Cancel. An error toast stays until you dismiss it.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- The danger button's text now meets WCAG AA on dark backgrounds in every flavour.
|
|
25
|
+
- The Overview lenses' readiness ring follows the fleet verdict (capped at 65% when the fleet needs a look, 40% when it is in trouble), and the Constitution map and KPI strip use the voice's own tier names.
|
|
26
|
+
|
|
27
|
+
## [4.18.1] — 2026-10-24
|
|
28
|
+
|
|
29
|
+
Patch: the web console's light mode meets WCAG AA, and console installs default to lean.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- **Light mode meets WCAG AA in every flavour, mode and accent.** Accent and status text use darker text companions (`--accent-text`, `--good-text`, `--warn-text`, `--bad-text`), measured against each flavour's real surfaces; button text is chosen by contrast, so a light cyan, amber or green button now carries dark text; every control shows a 2 px focus ring; the light topbar is solid and light cards keep their flavour; the page behind the console and the browser's theme colour follow the active mode. A warning next to an amber accent turns orange, so it no longer looks like the accent. `contrast.test.js` checks all of it against the real stylesheet.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- **New installs from the web console default to the lean footprint**, like the CLI: a deploy, an install on an absent row and a first-run build. An explicit `full` still works, and an existing install keeps its footprint on rebuild.
|
|
36
|
+
|
|
11
37
|
## [4.18.0] — 2026-10-24
|
|
12
38
|
|
|
13
39
|
Minor: the web console's numbers tell the truth (stale vs edited drift, lenses, Constitution, Docs) and it keeps your work (error boundary, draft guard, shared staging).
|
package/DESIGN.md
CHANGED
|
@@ -150,9 +150,9 @@ vault or a specific tool's hooks.
|
|
|
150
150
|
| body | `### {{LAW:<id>}} <Principle>` in `src/laws/universal.md`; citations resolve by id, so no position rewires one | `### {{DOCTRINE:<id>}} <Principle>` in `src/doctrines/<pack>.md`; the number is its position | a new `src/doctrines/<pack>.md` with its `**Name** — lead line` |
|
|
151
151
|
| themed title | `LEX_<ID>` in all 15 theme files | `DOC_<ID>` in all 15 theme files | `PACK_<NAME>` in all 15, plus each rule's `DOC_*` |
|
|
152
152
|
| class | `LAW_CLASS` in `js/inspect/inventory.mjs`, one of the six in `LAW_CLASSES` | **none** — a doctrine rule's class *is* its pack, and there is no second taxonomy over it | — |
|
|
153
|
-
| console copy | a
|
|
153
|
+
| console copy | a `<!-- note: summary: … -->` line under the rule's heading | the same line under each rule's heading | one summary line per rule |
|
|
154
154
|
| registration | the numeral is the registration | the number is the registration | the pack name in `PACK_ORDER`, `js/build/source.mjs` |
|
|
155
|
-
| renumber risk | none for citations (by id); `
|
|
155
|
+
| renumber risk | none for citations (by id); `LAW_CLASS` is number-keyed and doctor-pinned, so append by preference | none for citations; exclusions and hook ledger keys are `<pack>.<n>` — append by preference | none |
|
|
156
156
|
| counts | README badge + the `N universal laws` prose, `SHIPPED.md`'s triple, the web onboarding copy | **none** — the console spends the `N_PACKS` / `N_PACKS_ACTIVE` / `N_DOCTRINE_RULES` count tokens, all computed at request time | none, same reason |
|
|
157
157
|
| changelog | `CHANGELOG.md` | `CHANGELOG.md` | `CHANGELOG.md` |
|
|
158
158
|
|
|
@@ -163,9 +163,9 @@ vault or a specific tool's hooks.
|
|
|
163
163
|
synonym. `--sync-themes` writes the template's placeholder and only *prints* which keys
|
|
164
164
|
to restyle; a shipped placeholder passes every gate.
|
|
165
165
|
|
|
166
|
-
Almost all of it is gated, across
|
|
167
|
-
`themeParityProblems` (key parity across the voices), `
|
|
168
|
-
|
|
166
|
+
Almost all of it is gated, across five named checks in `js/inspect/checks-build.mjs` —
|
|
167
|
+
`themeParityProblems` (key parity across the voices), `ruleSummaryProblems`
|
|
168
|
+
(one console summary per rule, none orphaned),
|
|
169
169
|
`constitutionProblems` (rule ids declared, unique and never retired, `LEX_*` as an *equality*,
|
|
170
170
|
the `DOC_*` vocabulary, the hook-gated addresses, and every `{{LAW:…}}`/`{{DOCTRINE:…}}`
|
|
171
171
|
citation in `src/` — a citation by number is refused), and
|
package/SHIPPED.md
CHANGED
|
@@ -20,7 +20,7 @@ historical references, not files distributed with the repo.
|
|
|
20
20
|
> **On version numbers.** [`harness.config.json`](harness.config.json)'s `version` is a
|
|
21
21
|
> human-readable label only. The canonical identity of an installed harness is the
|
|
22
22
|
> **source fingerprint** stamped in `.geneseed-version` (see `geneseed version`), not the
|
|
23
|
-
> string. Current label: **4.
|
|
23
|
+
> string. Current label: **4.19.0**.
|
|
24
24
|
|
|
25
25
|
## Capabilities
|
|
26
26
|
|
|
@@ -42,7 +42,7 @@ Footprint is stored in a `.geneseed-footprint` marker in the install and kept ac
|
|
|
42
42
|
geneseed setup
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
- **Web console** — the **Footprint** toggle in Settings flips the current install and rebuilds it; on the Harness page, each install has its own dropdown.
|
|
45
|
+
- **Web console** — the **Footprint** toggle in Settings flips the current install and rebuilds it; on the Harness page, each install has its own dropdown. A new install or deploy from the console starts lean, as from the command line.
|
|
46
46
|
- **Command line** — with the emit target of the install you are changing:
|
|
47
47
|
|
|
48
48
|
```bash
|
package/docs/extending.md
CHANGED
|
@@ -179,7 +179,7 @@ not.
|
|
|
179
179
|
|
|
180
180
|
### 2a — Adding a DOCTRINE RULE (the common case)
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
Four files, plus the written-out rule totals in four test files. This is the slot most new
|
|
183
183
|
material belongs in, and it is cheap on purpose so that the invariant slot stays scarce.
|
|
184
184
|
|
|
185
185
|
1. `src/doctrines/<pack>.md` — add `### {{DOCTRINE:<new-id>}} <Principle Name>` plus the body,
|
|
@@ -188,8 +188,11 @@ material belongs in, and it is cheap on purpose so that the invariant slot stays
|
|
|
188
188
|
(the style rule is in `src/doctrines/README.md`, and `leanBlockProblems` refuses a rule with no
|
|
189
189
|
block or an empty half). The id is kebab-case, unique across laws **and** doctrines, and never
|
|
190
190
|
one listed in `RETIRED_RULE_IDS` (`js/build/source.mjs`). Where it goes in the file decides its
|
|
191
|
-
number; appending is still the kind move, because `excludeRules
|
|
192
|
-
|
|
191
|
+
number; appending is still the kind move, because `excludeRules` and the hook-gated addresses
|
|
192
|
+
are keyed `<pack>.<n>` (see §2e). On the line directly under the heading, above `LEAN:begin`,
|
|
193
|
+
add `<!-- note: summary: <one line> -->` — the one-line Principle the console prints under the
|
|
194
|
+
rule's name. **That copy exists nowhere else**, and `ruleSummaryProblems` refuses a rule with
|
|
195
|
+
none, two, an empty one or one off its line. Being an author note, it never reaches an emit.
|
|
193
196
|
2. `themes/_TEMPLATE.json` — add `DOC_<ID>` (`consent-before-push` → `DOC_CONSENT_BEFORE_PUSH`).
|
|
194
197
|
⚠ **This file *is* gated for this key family.**
|
|
195
198
|
`themeFiles()` filters `_`-prefixed names and theme parity never sees the template, but
|
|
@@ -206,15 +209,7 @@ material belongs in, and it is cheap on purpose so that the invariant slot stays
|
|
|
206
209
|
is one of the two. See §7.2 before you copy a command from anywhere else. Then restyle all
|
|
207
210
|
fourteen by hand: `--sync-themes` writes the template's placeholder and only *prints*
|
|
208
211
|
`RESTYLE these`; a shipped placeholder passes every gate.
|
|
209
|
-
4. `
|
|
210
|
-
rule's id — `doctrineMetaProblems` holds that pin to the canon, so a row left behind by a
|
|
211
|
-
renumber is a failure rather than a description of the neighbour. Its first field is the
|
|
212
|
-
**pack**, not one of `LAW_CLASSES`' six; `doctrineMetaProblems` requires an *equality* between
|
|
213
|
-
that field and the key's own pack, which catches the copy error a six-way vocabulary cannot — a
|
|
214
|
-
row pasted from the pack above it, keeping the wrong pack. Its second field is the one-line
|
|
215
|
-
Principle, and **that copy exists nowhere else**; a rule absent here renders with a blank
|
|
216
|
-
description.
|
|
217
|
-
5. `CHANGELOG.md`, then rebuild and commit `web/dist` (step 4 touched `web/src`).
|
|
212
|
+
4. `CHANGELOG.md`.
|
|
218
213
|
|
|
219
214
|
**What a doctrine rule does *not* cost**, and each of these is a deliberate asymmetry:
|
|
220
215
|
|
|
@@ -243,7 +238,7 @@ a defect.
|
|
|
243
238
|
|
|
244
239
|
### 2b — Amending an INVARIANT
|
|
245
240
|
|
|
246
|
-
Steps 1 and
|
|
241
|
+
Steps 1 and 8 of §2c, plus the rule's `<!-- note: summary: … -->` line if the Principle moved. Two cautions:
|
|
247
242
|
|
|
248
243
|
- `--footprint` defaults to **lean**, and a law's lean text is the authored `LEAN:else` half of
|
|
249
244
|
its block in `src/laws/universal.md` (`resolveLean` in `js/build/render.mjs`). Amend **both**
|
|
@@ -253,12 +248,14 @@ Steps 1 and 9 of §2c, plus `LAW_META` if the Principle line moved. Two cautions
|
|
|
253
248
|
|
|
254
249
|
### 2c — Adding an INVARIANT
|
|
255
250
|
|
|
256
|
-
|
|
251
|
+
Eight steps, and only the first is the rule itself. This is the expensive one, and it should be:
|
|
257
252
|
the bar is in `DESIGN.md` Decision 7, and most candidates belong in a doctrine pack instead.
|
|
258
253
|
|
|
259
254
|
1. `src/laws/universal.md` — add `### {{LAW:<new-id>}} <Principle Name>` plus the body. Citations
|
|
260
255
|
are by id and resolve against the canon, so position is safe to choose — but append unless the
|
|
261
|
-
order is the point: `LAW_CLASS`
|
|
256
|
+
order is the point: `LAW_CLASS` is keyed by the rendered number (see §2e). On the line directly
|
|
257
|
+
under the heading, add the rule's `<!-- note: summary: <one line> -->`, as in §2a step 1 — the
|
|
258
|
+
console's one-line Principle, gated by `ruleSummaryProblems`.
|
|
262
259
|
2. `themes/_TEMPLATE.json` — add `LEX_<ID>` in template order. Gated, as in §2a step 2:
|
|
263
260
|
`constitutionProblems` holds the `LEX_*` keys as an **equality** with the declared law ids across
|
|
264
261
|
all fifteen files (the fourteen voices and the template), so both a missing key *and* a leftover
|
|
@@ -266,16 +263,12 @@ the bar is in `DESIGN.md` Decision 7, and most candidates belong in a doctrine p
|
|
|
266
263
|
`LEX_XXIV` and `LEX_XXXVI` behind in every file and parity was silent.
|
|
267
264
|
3. `--sync-themes`, then restyle all fourteen by hand (§2a step 3).
|
|
268
265
|
4. `LAW_CLASS` in `js/inspect/inventory.mjs` — one of the six in `LAW_CLASSES` beside it.
|
|
269
|
-
5. `
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
8. `tests/unit/harness.test.mjs` — the negative fixture for the `LAW_CLASS` gate appends a law and
|
|
276
|
-
expects the next numeral (`X` while there are nine), and `ROMANS` there and in `emit_smoke`
|
|
277
|
-
lists the numerals written out. Bump both.
|
|
278
|
-
9. `CHANGELOG.md`, then rebuild and commit `web/dist` (step 5 touched `web/src`).
|
|
266
|
+
5. `README.md` — badge `badge/laws-N` and the `N universal laws` sentence.
|
|
267
|
+
6. `SHIPPED.md` — the `N laws, N agents, N skills` triple.
|
|
268
|
+
7. `tests/unit/harness.test.mjs` — the negative fixture for the `LAW_CLASS` gate appends a law and
|
|
269
|
+
expects the next numeral (`X` while there are nine), and `ROMANS` in `emit_smoke` lists the
|
|
270
|
+
numerals written out. Bump both.
|
|
271
|
+
8. `CHANGELOG.md`.
|
|
279
272
|
|
|
280
273
|
⚠ While restyling, `{{LAW}}` must stay a **single word** in every theme (and so must
|
|
281
274
|
`{{DOCTRINE}}`). Both heading parsers match the tier noun with `\S+`, so a two-word value does not
|
|
@@ -341,8 +334,7 @@ render — no citation moves, because none names a number. Then:
|
|
|
341
334
|
carries the text.
|
|
342
335
|
3. Its `LEX_<ID>`/`DOC_<ID>` key in all fifteen theme files (the equality arm names each).
|
|
343
336
|
4. The number-keyed tables, which the doctor checks against the canon: `LAW_CLASS`
|
|
344
|
-
(`js/inspect/inventory.mjs`),
|
|
345
|
-
row), and — only if a hook-gated rule moved — `HOOK_PINNED` in `checks-constitution.mjs` names the
|
|
337
|
+
(`js/inspect/inventory.mjs`), and — only if a hook-gated rule moved — `HOOK_PINNED` in `checks-constitution.mjs` names the
|
|
346
338
|
ledger keys, `CONSENT_RULE` and `HOOK_GATED` to move with it. `--exclude-rules` addresses are
|
|
347
339
|
`<pack>.<n>` too, and an install that excluded a shifted doctrine rule excludes its neighbour
|
|
348
340
|
after a re-emit: prefer appending in a pack, and say so in the CHANGELOG when you cannot.
|
|
@@ -422,8 +414,8 @@ Two traps worth knowing before you write the spec:
|
|
|
422
414
|
re-opens bash on all three hosts (OpenCode gates it to `ask`), and `<!-- webfetch: allow -->`
|
|
423
415
|
re-opens web fetch — the researcher is the one spec that carries it.
|
|
424
416
|
- **The console has a fourth, ungated copy of the skill taxonomy.** `SKILL_CATS` in
|
|
425
|
-
`web/src/lib/lawCats.js` mirrors `SKILL_CLASS` with nothing comparing them (contrast
|
|
426
|
-
|
|
417
|
+
`web/src/lib/lawCats.js` mirrors `SKILL_CLASS` with nothing comparing them (contrast the
|
|
418
|
+
rules' classes, which the server sends from `LAW_CLASS`). Inventing a new *category* means editing that file —
|
|
427
419
|
which drags in the `web/dist` rebuild — and an unknown category lands under Personal in the
|
|
428
420
|
Library (`splitSkills` in `web/src/pages/Library.jsx`) with no warning.
|
|
429
421
|
|
|
@@ -769,7 +761,7 @@ only, at best, on its presence.
|
|
|
769
761
|
| What | Where | Gated on |
|
|
770
762
|
|---|---|---|
|
|
771
763
|
| `§N` cross-references into `AGENT.md`'s anatomy | `src/skills/*`, `src/agents/*`, `adapters/**` | **range only.** `constitutionProblems` refuses a `§N` the template declares no section for — which catches a pointer past the end, the shape *removing* a section leaves. It cannot catch one that still resolves and now means something else, which is the shape *inserting* a section leaves, and is what actually happened when `## 2. Doctrines` pushed every later section down one. **Renumbering the anatomy means `grep -rn '§' src/ adapters/` and reading every hit.** Four satellites and a live OpenCode deny message shipped stale because the sweep stopped at `AGENT.md.tmpl` |
|
|
772
|
-
| Per-
|
|
764
|
+
| Per-rule Principle lines | the `<!-- note: summary: … -->` line under each rule heading in `src/laws/universal.md` and `src/doctrines/*.md` | presence and placement only (`ruleSummaryProblems`), never accuracy |
|
|
773
765
|
| 588 themed constitution titles (42 keys × 14 voices) — `LEX_<ID>` (one per law), `DOC_<ID>` (one per doctrine rule), `PACK_<NAME>` | `themes/*.json` | key presence only — a shipped placeholder is green. `LEX_*` and `DOC_*` are held across the template too, and in both directions; `PACK_*` only across the voices |
|
|
774
766
|
| The README keyword enumerations | `README.md`, `docs/concepts/rules.md` | nothing |
|
|
775
767
|
| The per-host map of what lands on a machine | `docs/understand/machine-opencode.md`, `docs/understand/machine-claude.md` | table shape only, never accuracy |
|
package/docs/web-ui.md
CHANGED
|
@@ -101,7 +101,7 @@ streams the output of background jobs and keeps their history across reloads.
|
|
|
101
101
|
|
|
102
102
|
- **Rules** (`#/rules`) — your own standing rules, read from and written to the
|
|
103
103
|
`user-rules.md` beside the deployed AGENT.md (the seed-once file no update ever
|
|
104
|
-
touches). List with scope (user/
|
|
104
|
+
touches). List with scope (project/user/global, or any other scope written by hand, which a
|
|
105
105
|
save keeps as written) and trial chips, add/edit/retire forms (a body line starting
|
|
106
106
|
`## ` is refused: the file would read it as a new rule heading), a
|
|
107
107
|
**Graduate** action that adopts a trial rule for good, and a budget meter that turns
|
package/harness.config.json
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
"_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
|
|
3
3
|
"theme": "neutral",
|
|
4
4
|
"product": "Geneseed",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.19.0"
|
|
6
6
|
}
|
package/js/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Everything that inspects an already-built or already-deployed harness and report
|
|
|
125
125
|
| `checks-repo.mjs` | Gates over the repo's own records: `registry.json`, committed secrets, the hook shim, vendored pins and folder skills, skill aliases and side files, this module map, the host tables, the scorecard ratchet |
|
|
126
126
|
| `checks-authoring.mjs` | `authoringProblems`, the aggregate self-consistency gate: spec purpose lines, the learn-prompt literal, `node --check` over the plugins (the doctor's one default spawn), then the groups below |
|
|
127
127
|
| `checks-probe.mjs` | `doctor --probe` (opt-in): runs each live install's own gate command through the host's shell on a must-refuse and a must-pass payload — the doctor's second spawn; the OpenCode guard plugin is imported and called in-process instead, through both plugin APIs |
|
|
128
|
-
| `checks-constitution.mjs` | The constitution: rule declarations and citations, LEAN blocks, `HOOK_PINNED`, theme vocabulary, the
|
|
128
|
+
| `checks-constitution.mjs` | The constitution: rule declarations and citations, LEAN blocks, `HOOK_PINNED`, theme vocabulary, the per-rule summaries (`ruleSummaryProblems`) |
|
|
129
129
|
| `checks-counts.mjs` | The hand-written counts: AGENT.md tables, README badges and prose, `SHIPPED.md`, held against `src/` |
|
|
130
130
|
| `scan.mjs` | The shared walk/sort/strip/test primitives every `checks-*` uses. Owns no judgement itself |
|
|
131
131
|
| `validate.mjs` | The `validate` verb (the generator's old `--validate-only`): renders a sandbox, scans it for tokens and dead links, own exit contract |
|
package/js/build/source.mjs
CHANGED
|
@@ -145,6 +145,33 @@ export function ruleCanon(src = SRC) {
|
|
|
145
145
|
return canon;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* RULE SUMMARIES — the one-line Principle the web console prints under each rule's name. It lives
|
|
150
|
+
* on the line directly under the rule's heading as `<!-- note: summary: <text> -->`, so the copy
|
|
151
|
+
* sits beside the rule it describes and moves with it; being an author note, `stripAuthorNotes`
|
|
152
|
+
* removes the whole line before any render, and no emit carries it. Read from the RAW text, apart
|
|
153
|
+
* from `ruleCanon`, so the render path pays nothing for it. `ruleSummaryProblems`
|
|
154
|
+
* (`js/inspect/checks-constitution.mjs`) is the gate: one summary per rule, in that place.
|
|
155
|
+
*/
|
|
156
|
+
export const SUMMARY_LINE_RE = /^<!--[ \t]*note:[ \t]*summary:[ \t]*(.*?)[ \t]*-->[ \t]*$/;
|
|
157
|
+
/** Anything that LOOKS like a summary — the gate's net, wider than `SUMMARY_LINE_RE` on purpose. */
|
|
158
|
+
export const SUMMARY_MARK_RE = /<!--[ \t]*note:[ \t]*summary:/;
|
|
159
|
+
export function ruleSummaries(src = SRC) {
|
|
160
|
+
const out = new Map();
|
|
161
|
+
for (const [kind, , file] of canonFiles(src)) {
|
|
162
|
+
let text;
|
|
163
|
+
try { text = readRawText(file); } catch { continue; }
|
|
164
|
+
for (const m of text.matchAll(DECL_RE)) {
|
|
165
|
+
if (m[1] !== kind) continue;
|
|
166
|
+
const start = m.index + m[0].length + 1;
|
|
167
|
+
const end = text.indexOf('\n', start);
|
|
168
|
+
const s = SUMMARY_LINE_RE.exec(text.slice(start, end === -1 ? undefined : end));
|
|
169
|
+
if (s && !out.has(m[2])) out.set(m[2], s[1]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
148
175
|
/**
|
|
149
176
|
* AUTHOR NOTES — `<!-- note: ... -->`, single or multi-line, is maintainer-only prose and must
|
|
150
177
|
* never reach an agent (user decision 2026-10-11). Stripped here because `resolveRuleIds` is the
|
|
@@ -113,7 +113,7 @@ export function authorNoteProblems(src = SRC) {
|
|
|
113
113
|
// Moved to the two sibling modules; re-exported so callers that import the authoring group as
|
|
114
114
|
// one module keep working.
|
|
115
115
|
export {
|
|
116
|
-
HOOK_PINNED, constitutionProblems,
|
|
116
|
+
HOOK_PINNED, constitutionProblems, leanBlockProblems, ruleSummaryProblems,
|
|
117
117
|
} from './checks-constitution.mjs';
|
|
118
118
|
export { countTableProblems, kitProblems, proseMirrorProblems } from './checks-counts.mjs';
|
|
119
119
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The CONSTITUTION gates — whether the laws and doctrine packs agree with themselves and with
|
|
3
3
|
* everything keyed off them: rule declarations and citations, the LEAN blocks, the hook-pinned
|
|
4
|
-
* addresses, the theme vocabulary, and the
|
|
4
|
+
* addresses, the theme vocabulary, and the per-rule summary the web console prints.
|
|
5
5
|
*
|
|
6
6
|
* Split out of `checks-authoring.mjs`, which still aggregates these (and re-exports them for
|
|
7
7
|
* its existing importers). Same contract as every check module here: an array of problem
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import {
|
|
12
|
-
CONFIG, PACK_ORDER, RETIRED_RULE_IDS,
|
|
12
|
+
CONFIG, PACK_ORDER, RETIRED_RULE_IDS, RULE_ID_RE, SRC, SUMMARY_LINE_RE, SUMMARY_MARK_RE, THEMES, canonFiles, ruleCanon,
|
|
13
|
+
titleKey, toRoman,
|
|
13
14
|
} from '../build/source.mjs';
|
|
14
15
|
import { LEAN_BLOCK_RE, splitAtLawHeadings } from '../build/render.mjs';
|
|
15
16
|
import { themeFiles } from '../hosts/installs.mjs';
|
|
@@ -18,138 +19,68 @@ import { parseJson, formatRepr } from '../lib/json.mjs';
|
|
|
18
19
|
import { NOTE, isFile, rglob } from './scan.mjs';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* literal is gone (`missing` is that problem's text).
|
|
24
|
-
*/
|
|
25
|
-
function lawsPageLiteral(name, missing) {
|
|
26
|
-
let text;
|
|
27
|
-
try { text = readText(path.join(ROOT, 'web', 'src', 'pages', 'Laws.jsx')); } catch (e) {
|
|
28
|
-
return [null, `[authoring] web/src/pages/Laws.jsx unreadable: ${e.message}`];
|
|
29
|
-
}
|
|
30
|
-
const block = new RegExp(`^const ${name} = \\{$([\\s\\S]*?)^\\}$`, 'm').exec(text);
|
|
31
|
-
return block ? [block[1], null] : [null, missing];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* `_harness_build._LAW_META_ROW` — `<arabic>: ['<class>', '<principle>', '<id>'],` in the web page.
|
|
22
|
+
* Every rule carries exactly one one-line summary, on the line directly under its heading —
|
|
23
|
+
* `<!-- note: summary: <text> -->` — the Principle the web console prints under the rule's name.
|
|
36
24
|
*
|
|
37
|
-
* The
|
|
38
|
-
*
|
|
39
|
-
*
|
|
25
|
+
* The copy exists nowhere else, and a rule without it renders with a blank description while
|
|
26
|
+
* every count upstream reads correct: two laws shipped exactly that way when the copy lived in a
|
|
27
|
+
* table in `web/src/pages/Laws.jsx`. Beside the heading, a removed or reordered rule takes its
|
|
28
|
+
* summary with it, so no positional key or id pin is left to drift.
|
|
40
29
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
30
|
+
* PLACEMENT IS PART OF THE CONTRACT because `ruleSummaries` (`js/build/source.mjs`) reads only
|
|
31
|
+
* the line under the heading: a summary one line lower is stripped from every render all the
|
|
32
|
+
* same, and the console would show nothing. A summary above the first rule, or under any heading
|
|
33
|
+
* that declares no rule, describes no rule at all.
|
|
45
34
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
35
|
+
* ONE DEFINITION OF A SUMMARY LINE: `SUMMARY_LINE_RE`, the reader's own, applied to the line as
|
|
36
|
+
* written. A line `SUMMARY_MARK_RE` catches but that regex refuses — indented, or wrapped over two
|
|
37
|
+
* lines — is malformed here, because the reader would hand the console nothing for it.
|
|
48
38
|
*/
|
|
49
|
-
|
|
50
|
-
/^[^\S\n]*(\d+):\s*\[\s*(['"])(.*?)\2\s*,\s*(['"])(.*?)\4\s*(?:,\s*(['"])(.*?)\6\s*)?,?\s*\]\s*,?[^\S\n]*$/gm;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* `_harness_build._law_meta_problems` — the web Laws ledger's per-rule copy stays complete.
|
|
54
|
-
*
|
|
55
|
-
* `LAW_META` holds each law's one-line Principle, copy that exists nowhere else in the tree,
|
|
56
|
-
* and a law with no row falls back to `['craft', '']`: the rule renders with a blank
|
|
57
|
-
* description and the wrong class chip, while everything upstream is fine. That is exactly
|
|
58
|
-
* how two laws shipped description-less.
|
|
59
|
-
*/
|
|
60
|
-
export function lawMetaProblems(lawRules, lawClass, lawClasses) {
|
|
61
|
-
const [block, refusal] = lawsPageLiteral('LAW_META', '[authoring] LAW_META literal not found '
|
|
62
|
-
+ "in web/src/pages/Laws.jsx — the web Laws ledger's Principle column would have no gate");
|
|
63
|
-
if (refusal) return [refusal];
|
|
64
|
-
const meta = new Map();
|
|
65
|
-
for (const m of block.matchAll(LAW_META_ROW)) {
|
|
66
|
-
meta.set(Number(m[1]), [m[3], m[5], m[7]]);
|
|
67
|
-
}
|
|
39
|
+
export function ruleSummaryProblems(src = SRC) {
|
|
68
40
|
const problems = [];
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
* `lawMetaProblems` existed, and 23 new rules is a much larger surface to lose one in.
|
|
113
|
-
*
|
|
114
|
-
* ⚠ THE CLASS ARM IS DIFFERENT FROM THE INVARIANTS'. An invariant's class is checked against
|
|
115
|
-
* `LAW_CLASSES`, a vocabulary of six; a doctrine rule has no second taxonomy — its class IS its
|
|
116
|
-
* pack — so the check is an EQUALITY against the pack in its own key. That catches the copy
|
|
117
|
-
* error a six-way vocabulary cannot: a row pasted from the pack above it, keeping the class of
|
|
118
|
-
* the pack it came from, which would colour the row and label its chip with the wrong pack.
|
|
119
|
-
*/
|
|
120
|
-
export function doctrineMetaProblems(addrs, ids = null) {
|
|
121
|
-
const [block, refusal] = lawsPageLiteral('DOCTRINE_META', '[authoring] DOCTRINE_META literal not '
|
|
122
|
-
+ "found in web/src/pages/Laws.jsx — the console's doctrine rows would have no gate on their "
|
|
123
|
-
+ 'Principle column');
|
|
124
|
-
if (refusal) return [refusal];
|
|
125
|
-
const meta = new Map();
|
|
126
|
-
for (const m of block.matchAll(DOCTRINE_META_ROW)) meta.set(m[2], [m[4], m[6], m[8]]);
|
|
127
|
-
const problems = [];
|
|
128
|
-
const seen = new Set();
|
|
129
|
-
for (const [i, addr] of addrs.entries()) {
|
|
130
|
-
seen.add(addr);
|
|
131
|
-
if (!meta.has(addr)) {
|
|
132
|
-
problems.push(`[authoring] doctrine rule ${addr} has no row in DOCTRINE_META `
|
|
133
|
-
+ '(web/src/pages/Laws.jsx) — the console would render it with a blank Principle');
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
const [klass, principle, pin] = meta.get(addr);
|
|
137
|
-
if (ids && pin !== ids[i]) {
|
|
138
|
-
problems.push(`[authoring] DOCTRINE_META['${addr}'] is pinned to '${pin ?? '(no id)'}' but `
|
|
139
|
-
+ `that address is '${ids[i]}' now — the row describes a different rule`);
|
|
140
|
-
}
|
|
141
|
-
if (!principle.trim()) {
|
|
142
|
-
problems.push(`[authoring] DOCTRINE_META['${addr}'] has an empty principle line — that `
|
|
143
|
-
+ 'rule would render with a blank description');
|
|
144
|
-
}
|
|
145
|
-
const pack = addr.split('.')[0];
|
|
146
|
-
if (klass !== pack) {
|
|
147
|
-
problems.push(`[authoring] DOCTRINE_META['${addr}'] carries pack '${klass}' — a doctrine `
|
|
148
|
-
+ `rule's class IS its pack, so this must read '${pack}'`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
for (const addr of [...meta.keys()].filter((k) => !seen.has(k)).sort()) {
|
|
152
|
-
problems.push(`[authoring] DOCTRINE_META lists ${addr} but no pack file defines it`);
|
|
41
|
+
for (const [kind, pack, file] of canonFiles(src)) {
|
|
42
|
+
const rel = path.relative(src, file).split(path.sep).join('/');
|
|
43
|
+
let lines;
|
|
44
|
+
try { lines = readText(file).replace(/\r\n?/g, '\n').split('\n'); } catch { continue; } // constitutionProblems names it
|
|
45
|
+
const rules = [];
|
|
46
|
+
let current = null;
|
|
47
|
+
lines.forEach((line, i) => {
|
|
48
|
+
if (/^#{1,6} /.test(line)) {
|
|
49
|
+
const m = line.startsWith('### ') ? DECL_LINE_RE.exec(line) : null;
|
|
50
|
+
const id = m && m[1] === kind ? m[2] : null;
|
|
51
|
+
current = id ? { id, at: i, found: [] } : null;
|
|
52
|
+
if (current) rules.push(current);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!SUMMARY_MARK_RE.test(line)) return;
|
|
56
|
+
if (current) current.found.push([i, SUMMARY_LINE_RE.exec(line)?.[1] ?? null]);
|
|
57
|
+
else {
|
|
58
|
+
problems.push(`[authoring] ${rel}: a summary line sits outside any rule declaration — it `
|
|
59
|
+
+ 'describes no rule');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
rules.forEach(({ id, at, found }, k) => {
|
|
63
|
+
const where = `[authoring] ${rel}: rule ${kind === 'LAW' ? toRoman(k + 1) : `${pack} ${k + 1}`} '${id}'`;
|
|
64
|
+
if (!found.length) {
|
|
65
|
+
problems.push(`${where} has no summary — the console would render it with a blank `
|
|
66
|
+
+ "Principle; add '<!-- note: summary: <one line> -->' on the line directly under its heading");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (found.length > 1) {
|
|
70
|
+
problems.push(`${where} has ${found.length} summary lines — the console shows one; keep `
|
|
71
|
+
+ 'exactly one');
|
|
72
|
+
}
|
|
73
|
+
if (found[0][0] !== at + 1) {
|
|
74
|
+
problems.push(`${where} summary is not directly under its heading — ruleSummaries reads `
|
|
75
|
+
+ 'only that line, so the console would show none');
|
|
76
|
+
}
|
|
77
|
+
if (found[0][1] === null) {
|
|
78
|
+
problems.push(`${where} has a malformed summary line — it must be one unindented line, `
|
|
79
|
+
+ "'<!-- note: summary: <text> -->', the only shape ruleSummaries reads");
|
|
80
|
+
} else if (!found[0][1].trim()) {
|
|
81
|
+
problems.push(`${where} has an empty summary — the console would render a blank Principle`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
153
84
|
}
|
|
154
85
|
return problems;
|
|
155
86
|
}
|
|
@@ -242,8 +173,8 @@ export function leanBlockProblems() {
|
|
|
242
173
|
}
|
|
243
174
|
|
|
244
175
|
/**
|
|
245
|
-
* The three-tier constitution's own authoring gates — the doctrine twin of the `LAW_CLASS`
|
|
246
|
-
*
|
|
176
|
+
* The three-tier constitution's own authoring gates — the doctrine twin of the `LAW_CLASS`
|
|
177
|
+
* family, the per-rule summaries, plus the two purity rules the tiering rests on.
|
|
247
178
|
*
|
|
248
179
|
* IT LIVES BESIDE `countTableProblems` RATHER THAN INSIDE IT, and is called from
|
|
249
180
|
* `authoringProblems` beside `registry`/`secret`/`vendorPin`. `countTableProblems` opens by
|
|
@@ -328,10 +259,10 @@ export function constitutionProblems() {
|
|
|
328
259
|
}
|
|
329
260
|
}
|
|
330
261
|
|
|
331
|
-
// The console's Principle column,
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
problems.push(...
|
|
262
|
+
// The console's Principle column, read off the same files — so a rule added to the canon
|
|
263
|
+
// without its summary is caught the same day, not when someone notices a blank description
|
|
264
|
+
// in the browser.
|
|
265
|
+
problems.push(...ruleSummaryProblems());
|
|
335
266
|
|
|
336
267
|
// ---- every citation in src/ is by id, and every id resolves.
|
|
337
268
|
//
|
|
@@ -14,7 +14,6 @@ import { effectiveTheme, renderFile } from '../build/render.mjs';
|
|
|
14
14
|
import { themeFiles } from '../hosts/installs.mjs';
|
|
15
15
|
import { readText } from '../lib/fs.mjs';
|
|
16
16
|
import { formatRepr } from '../lib/json.mjs';
|
|
17
|
-
import { lawMetaProblems } from './checks-constitution.mjs';
|
|
18
17
|
import { LAW_CLASS, LAW_CLASSES, SKILL_CLASS } from './inventory.mjs';
|
|
19
18
|
import { globSorted, has, isDir, isFile, srcStems } from './scan.mjs';
|
|
20
19
|
import { existsSync, readdirSync } from 'node:fs';
|
|
@@ -183,11 +182,10 @@ export function countTableProblems() {
|
|
|
183
182
|
problems.push(`[authoring] SKILL_CLASS lists '${stale}' but no skills/${stale}.md exists`);
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
// The numerals the laws RENDER with — positional, off the canon — because LAW_CLASS
|
|
187
|
-
//
|
|
185
|
+
// The numerals the laws RENDER with — positional, off the canon — because LAW_CLASS is keyed
|
|
186
|
+
// the way the rendered catalogue numbers them.
|
|
188
187
|
const canonRules = ruleCanon(SRC).rules;
|
|
189
|
-
const
|
|
190
|
-
const lawNums = lawRules.map((r) => r.label);
|
|
188
|
+
const lawNums = canonRules.filter((r) => r.kind === 'LAW').map((r) => r.label);
|
|
191
189
|
for (const num of lawNums) {
|
|
192
190
|
if (!has(LAW_CLASS, num)) {
|
|
193
191
|
problems.push(`[authoring] laws/universal.md rule ${num} has no class in LAW_CLASS`);
|
|
@@ -200,7 +198,6 @@ export function countTableProblems() {
|
|
|
200
198
|
+ `class ${formatRepr(LAW_CLASSES)}`);
|
|
201
199
|
}
|
|
202
200
|
}
|
|
203
|
-
problems.push(...lawMetaProblems(lawRules, LAW_CLASS, LAW_CLASSES));
|
|
204
201
|
|
|
205
202
|
// WHAT SHIPS, NOT WHAT IS FLAT. A folder skill (`src/skills/<name>/SKILL.md`, vendored with
|
|
206
203
|
// its scripts or references) ships exactly like a flat one, so the badge, the README list and
|
package/js/web/actions.mjs
CHANGED
|
@@ -466,7 +466,9 @@ export function buildOverride(state, body) {
|
|
|
466
466
|
* deliberately carries none of this install's switches or exclusions — and keeps the defaults it
|
|
467
467
|
* always had: footprint, posture
|
|
468
468
|
* and mode off the viewed dir, and ALL packs when unknown — never `harness.config.json`, which
|
|
469
|
-
* would drop the consent gate.
|
|
469
|
+
* would drop the consent gate. The one exception is the footprint with NOTHING deployed: the
|
|
470
|
+
* viewed dir then has no footprint of its own (`footprintOfDir`'s no-marker answer is `full`),
|
|
471
|
+
* so a first install takes the CLI's default, lean — see `apiDeployCmd`.
|
|
470
472
|
*/
|
|
471
473
|
export function buildActionCmds(state, body) {
|
|
472
474
|
const [theme, emit] = buildOverride(state, body);
|
|
@@ -486,7 +488,8 @@ export function buildActionCmds(state, body) {
|
|
|
486
488
|
+ 'not Build' };
|
|
487
489
|
}
|
|
488
490
|
return actionCommands('build', {
|
|
489
|
-
theme, emit, footprint: state
|
|
491
|
+
theme, emit, footprint: deployed(state) ? state.footprint : 'lean',
|
|
492
|
+
posture: state.posture, mode: state.mode,
|
|
490
493
|
trust: state.trust,
|
|
491
494
|
doctrines: doctrinesForBuild(state.target),
|
|
492
495
|
});
|
|
@@ -673,8 +676,11 @@ export function apiInstallCmd(state, body) {
|
|
|
673
676
|
// Every read-back below is narrowed to `host`'s OWN carrier (host-compat B1) — see the
|
|
674
677
|
// comment at `installProfile` in `js/build/generate.mjs`.
|
|
675
678
|
// The theme default is THIS install's own, not the viewed one's: the Kits tab acts on any row.
|
|
679
|
+
// An ABSENT row has no footprint of its own (`footprintOfDir` answers its no-marker `full`),
|
|
680
|
+
// so a new install takes the CLI's default, lean — see `apiDeployCmd` below.
|
|
676
681
|
const { theme, footprint: fp, posture: pos, mode, trust } = bodyAxes(body, {
|
|
677
|
-
theme: themeOfDir(root, host) || state.theme,
|
|
682
|
+
theme: themeOfDir(root, host) || state.theme,
|
|
683
|
+
footprint: installState(root, host, scope) === 'absent' ? 'lean' : footprintOfDir(root),
|
|
678
684
|
posture: postureOfDir(root, host) || 'peer',
|
|
679
685
|
mode: modeOfDir(root, host) || 'direct', trust: trustOfDir(root, host) || DEFAULT_PRESET });
|
|
680
686
|
// Unspecified means "keep what this install already has", exactly as theme, footprint,
|
|
@@ -792,9 +798,12 @@ export function apiDeployCmd(state, body) {
|
|
|
792
798
|
return { error: `${root} predates the comms pack (its carrier names 'process 8') — `
|
|
793
799
|
+ 'run `geneseed setup` for it before deploying over it' };
|
|
794
800
|
}
|
|
795
|
-
// A fresh deploy is
|
|
801
|
+
// A fresh deploy is LEAN, at the default register, mode and trust: the same default as the
|
|
802
|
+
// CLI's `--footprint`, which the user chose (2026-10-24) over the console's old `full` — a
|
|
803
|
+
// console install and a CLI install of the same folder must not differ in always-on text for
|
|
804
|
+
// no reason the user picked. An explicit `full` in the body still wins.
|
|
796
805
|
const { theme, footprint: fp, posture: pos, mode, trust } = bodyAxes(body, {
|
|
797
|
-
theme: state.theme, footprint: '
|
|
806
|
+
theme: state.theme, footprint: 'lean', posture: 'peer', mode: 'direct',
|
|
798
807
|
trust: DEFAULT_PRESET });
|
|
799
808
|
// Same resolution as `apiInstallCmd` above, and for the same reason: the console's Deploy
|
|
800
809
|
// form sends host/path/theme/footprint/posture/mode and NO pack selection, and nothing stops
|