geneseed 3.0.0 → 3.1.1
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 +90 -0
- package/DESIGN.md +16 -5
- package/README.md +1 -1
- package/SETUP.md +29 -2
- package/SHIPPED.md +1 -1
- package/bin/geneseed.mjs +51 -2
- package/docs/extending.md +2 -2
- package/docs/web/rules.md +2 -0
- package/docs/web/setup-choices.md +13 -1
- package/harness.config.json +1 -1
- package/js/checkout.mjs +43 -2
- package/js/doctor.mjs +39 -1
- package/js/emit.mjs +6 -5
- package/js/generate.mjs +18 -3
- package/js/installs.mjs +50 -0
- package/js/inventory.mjs +14 -3
- package/js/render.mjs +59 -2
- package/js/settings.mjs +31 -14
- package/js/setup.mjs +15 -5
- package/js/uninstall.mjs +7 -2
- package/js/web/actions.mjs +41 -5
- package/js/web/api.mjs +23 -8
- package/js/web/jobs.mjs +8 -2
- package/package.json +1 -1
- package/src/AGENT.md.tmpl +1 -1
- package/web/dist/assets/{About-C0Mk0igT.js → About-DyK7yRy7.js} +1 -1
- package/web/dist/assets/{Activity-C4aJJVXz.js → Activity-D_sYBlW-.js} +1 -1
- package/web/dist/assets/{ActivityDetail-DaQ85za0.js → ActivityDetail-BomKcMhy.js} +1 -1
- package/web/dist/assets/{Diff-B3tlO2Yj.js → Diff-BSvi1WUg.js} +1 -1
- package/web/dist/assets/{Doctor-BNC-5Dmo.js → Doctor-Cz1s-y1n.js} +1 -1
- package/web/dist/assets/{Graph-us0T6ZcV.js → Graph-CuntzFJ0.js} +1 -1
- package/web/dist/assets/{Harnesses-DAMgyfM_.js → Harnesses-DgRyOviS.js} +1 -1
- package/web/dist/assets/Laws-CWk3Mpri.js +3 -0
- package/web/dist/assets/{Library-KmwrHA1J.js → Library-DXhTN9ju.js} +1 -1
- package/web/dist/assets/{Markdown-0eQOVGSF.js → Markdown-BoiZcdUl.js} +1 -1
- package/web/dist/assets/{Profile-ZU7WV6Xl.js → Profile-DfCHlZ03.js} +1 -1
- package/web/dist/assets/{Rules-CtDoPhhf.js → Rules-eND7hJv9.js} +1 -1
- package/web/dist/assets/{Skills-BhoOxdkD.js → Skills-CDl9opZO.js} +1 -1
- package/web/dist/assets/{StatusBadge-CGvYVCwa.js → StatusBadge-DSDtcArX.js} +1 -1
- package/web/dist/assets/{Themes-Dr7OOkD7.js → Themes-CoAWuIeM.js} +1 -1
- package/web/dist/assets/{activity-B8aq3qtl.js → activity-Dmb0FTAx.js} +1 -1
- package/web/dist/assets/index-B-mXquBZ.css +1 -0
- package/web/dist/assets/index-CH_Z725C.js +12 -0
- package/web/dist/assets/{index-D0lPN_qW.js → index-Dg58ScBe.js} +1 -1
- package/web/dist/assets/{index-AhMzkdbI.js → index-jrY4m9jP.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/pages/Laws.jsx +214 -31
- package/web/dist/assets/Laws-rOxegJ75.js +0 -1
- package/web/dist/assets/index-2wqPDxuN.css +0 -1
- package/web/dist/assets/index-CRftEtBX.js +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,96 @@ labels in `harness.config.json`. The canonical identity of an *installed* harnes
|
|
|
6
6
|
is the source fingerprint in `.geneseed-version` (see `geneseed version`), not this
|
|
7
7
|
label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
|
|
8
8
|
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [3.1.1] — 2026-08-21
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Doctrines toggle per RULE, not per pack.** A second build-time axis,
|
|
16
|
+
`geneseed-build --exclude-rules "process 7"`, drops individual rules from the packs that
|
|
17
|
+
survive `--doctrines`. Either spelling of an address is accepted — `process 7` as the
|
|
18
|
+
constitution writes it, `process.7` as the console addresses it — and `none` is the explicit
|
|
19
|
+
empty selection.
|
|
20
|
+
|
|
21
|
+
**This supersedes the pack-level switch 3.1.0 shipped one day earlier.** That control could
|
|
22
|
+
only say *all of Observance or none of it*; the case it could not express — *keep the pack,
|
|
23
|
+
drop one rule* — is the one people actually have. The console's switches are now one per rule,
|
|
24
|
+
still staged locally and still applied in a single rebuild, and there is no pack switch at all:
|
|
25
|
+
the pack axis is DERIVED at Apply time from whichever rules survive. A pack whose every rule is
|
|
26
|
+
off leaves `--doctrines` entirely, so `AGENT.md` never renders a pack header with nothing under
|
|
27
|
+
it.
|
|
28
|
+
|
|
29
|
+
An install records its exclusions in a second marker line, `Excluded rules: process 7`,
|
|
30
|
+
**written only when something is excluded**. That asymmetry is load-bearing rather than tidy: a
|
|
31
|
+
default build is byte-identical to one from before this axis existed — 261 golden cells, nine
|
|
32
|
+
emit modes, both footprints, all unmoved, nothing re-blessed — and the line's absence is a
|
|
33
|
+
complete answer, so `excludedRulesOfDir` reads a missing line as `[]` where `doctrinesOfDir`
|
|
34
|
+
must read one as `null`. Every rebuild path (`upgrade`, `rebuild-all`, the console's Build and
|
|
35
|
+
Deploy) reads the line back and preserves it, or an upgrade would silently hand back every rule
|
|
36
|
+
its owner switched off.
|
|
37
|
+
|
|
38
|
+
⚠ **The consent gate is re-keyed from the pack to the rule.** `process 5` carries commit/push
|
|
39
|
+
consent, and the `git-gate` hook plus the `git commit*` / `git push*` permission are now wired
|
|
40
|
+
on that rule rather than on its pack — so excluding it alone unwires them, and excluding
|
|
41
|
+
`process 7` beside it does not. Prompt and boundary are never allowed to disagree; `doctor`
|
|
42
|
+
gained a gate that fails if `js/settings.mjs`'s hardcoded `process.5` ever stops naming the
|
|
43
|
+
consent rule. `rm -rf` and force-push refusals remain Rule IV's territory and ride no axis.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **Independent doctrine controls.** Each Constitution doctrine row now keeps its detail
|
|
48
|
+
disclosure separate from the right-aligned switch. Toggling a staged rule no longer opens or
|
|
49
|
+
closes its detail, and the existing bottom **Apply** action remains the sole point at which a
|
|
50
|
+
selection changes the deployed harness.
|
|
51
|
+
|
|
52
|
+
## [3.1.0] — 2026-08-20
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **Doctrine packs are toggleable from the console.** The Constitution page gains a switch on
|
|
57
|
+
each pack header — beside its name, blurb and rule count — staged locally and committed by a
|
|
58
|
+
single **Apply** below the last pack, with **Revert** beside it. Until now the console could
|
|
59
|
+
*show* pack state and not change it: dropping a pack meant `geneseed-build --doctrines`, the
|
|
60
|
+
setup wizard, or editing `harness.config.json` by hand.
|
|
61
|
+
|
|
62
|
+
**On the Constitution page and not in Settings**, because that is where a reader is already
|
|
63
|
+
looking at what each pack contains — dropping `ops` is a decision about the six rules listed
|
|
64
|
+
directly under its header, and a switch three screens away makes it a decision taken blind. A
|
|
65
|
+
staged pack reads `staged on` / `staged off` and greys immediately, so the page never claims a
|
|
66
|
+
change has been applied when it has not.
|
|
67
|
+
|
|
68
|
+
**Staged rather than per-click, and that is the design.** A pack selection is a SET: acting per
|
|
69
|
+
toggle would re-emit the install once per switch — four rebuilds to get from all four packs to
|
|
70
|
+
one, three of them describing a state nobody asked for, each writing a different
|
|
71
|
+
`Active packs:` marker for the next reader to parse.
|
|
72
|
+
|
|
73
|
+
It acts on whichever install the Topbar harness selector points at, so it is already
|
|
74
|
+
per-harness. Pushing one selection to several installs at once is not here.
|
|
75
|
+
|
|
76
|
+
⚠ **`process` toggles like any other pack, and the consequence is named rather than refused.**
|
|
77
|
+
That pack carries commit/push consent, so dropping it also drops the `git-gate` hook and the
|
|
78
|
+
`git commit*` / `git push*` permission — prompt and boundary are never allowed to disagree. An
|
|
79
|
+
inline warning says so while the change is staged, and the confirm repeats it. `rm -rf` and
|
|
80
|
+
force-push refusals are Rule IV's territory, ride no pack, and stay in every build.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- **The Constitution page printed a command that errors.** An inactive pack's enable hint read
|
|
85
|
+
`geneseed build --doctrines …`; the CLI's `build` verb forwards `--theme` and nothing else, so
|
|
86
|
+
that spelling answers `unrecognized arguments`. It is `geneseed-build` — the same trap
|
|
87
|
+
`DESIGN.md` already documents for `--sync-themes`. Shipped in 3.0.0. With an install to
|
|
88
|
+
rebuild the switches replace the hint entirely; it survives only where there is nothing to act
|
|
89
|
+
on.
|
|
90
|
+
|
|
91
|
+
- **A page reachable from three routes remounted on every row expand.** `Laws`, `Skills` and
|
|
92
|
+
`Library` are each reachable from the flat view, `#/section/<name>` and
|
|
93
|
+
`#/item/<type>/<name>`, and each was rendered from three different positions in `App`'s tree.
|
|
94
|
+
Crossing between them is not a prop change to React — it is an unmount and a remount, so the
|
|
95
|
+
page threw away its state, refetched its catalogue and re-entered Suspense. Expanding a row
|
|
96
|
+
does exactly that, because the row IS the route (`#/laws` → `#/item/law/craft.1`). Measured in
|
|
97
|
+
the browser: one refetch of `/api/catalog/laws` per expand *and* per collapse, now zero.
|
|
98
|
+
|
|
9
99
|
## [3.0.0] — 2026-08-20
|
|
10
100
|
|
|
11
101
|
### Changed
|
package/DESIGN.md
CHANGED
|
@@ -106,11 +106,22 @@ vault or a specific tool's hooks.
|
|
|
106
106
|
is narrative and deliberately not alphabetical.
|
|
107
107
|
|
|
108
108
|
**The Ontology and the Invariants are never toggleable** — every build carries both,
|
|
109
|
-
whole. The Doctrine
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
whole. The Doctrine tier is the only one a repository may narrow, and it narrows on
|
|
110
|
+
**two axes**, both chosen once at build time and both defaulting to everything on:
|
|
111
|
+
`--doctrines craft,rigor` (or `none`, or the wizard) selects PACKS, and
|
|
112
|
+
`--exclude-rules "process 7"` drops individual RULES from the packs that survive. They
|
|
113
|
+
compose in one direction only — a pack whose every rule is excluded leaves the active
|
|
114
|
+
set, so no pack header is ever rendered empty. There is no runtime toggle for either.
|
|
115
|
+
Every pack file ships on disk at both footprints whether or not it was built in, which
|
|
116
|
+
is what lets a citation into an inactive pack still resolve.
|
|
117
|
+
|
|
118
|
+
Each axis leaves its own marker line in the carrier for later readers (`upgrade`,
|
|
119
|
+
`rebuild-all`, the console) to parse back: `Active packs:` always, and
|
|
120
|
+
`Excluded rules:` **only when something is excluded**. That asymmetry is deliberate and
|
|
121
|
+
load-bearing — the second line's absence is a complete answer ("nothing"), so a default
|
|
122
|
+
build is byte-identical to one from before the axis existed and no recording had to be
|
|
123
|
+
re-blessed; `excludedRulesOfDir` accordingly reads a missing line as `[]` where
|
|
124
|
+
`doctrinesOfDir` must read one as `null`.
|
|
114
125
|
|
|
115
126
|
**Conflict order, exactly:** Ontology + Invariants → the user's own `user-rules.md` →
|
|
116
127
|
the active Doctrines → `PROFILE.md`. A doctrine rule may *tighten* an invariant, never
|
package/README.md
CHANGED
|
@@ -154,7 +154,7 @@ The harness ships as a small set of layers, and the web console's rail is the sa
|
|
|
154
154
|
| --- | --- |
|
|
155
155
|
| **🧭 Ontology** (`ontology/`) | the mind the rules govern, in four prose sections — **Telos** (the Pact: protect the user, the truth, the agent), **Evidence** (every claim graded by how it was obtained), **Decisions** (classify and tier by reversibility, show the real forks), **Conduct** (answer what was asked, once). Always in force, never toggleable |
|
|
156
156
|
| **🛡️ Rules** (`laws/`) | 9 universal laws the agent obeys — always in force, never toggleable: sealed-secrets, one-intent-one-act, verify-before-assert, deletion-is-deliberate, surface-failures, data-not-orders, least-privilege, root-cause, external-gate |
|
|
157
|
-
| **📐 Doctrines** (`doctrines/`) | practice packs, chosen per install at build time: **craft** (how code is written), **rigor** (how work is proven), **ops** (how the machine is operated), **process** (how a task is run). A doctrine rule may tighten a Rule, never repeal one, and the user's own `user-rules.md` outranks it. Pick with `geneseed-build --doctrines craft,rigor` (or `none`),
|
|
157
|
+
| **📐 Doctrines** (`doctrines/`) | practice packs, chosen per install at build time: **craft** (how code is written), **rigor** (how work is proven), **ops** (how the machine is operated), **process** (how a task is run). A doctrine rule may tighten a Rule, never repeal one, and the user's own `user-rules.md` outranks it. Pick with `geneseed-build --doctrines craft,rigor` (or `none`), a single rule with `--exclude-rules "process 7"`, in the setup wizard, or through the switches on the console's Constitution page; all four pack files ship on disk either way, so a citation into a pack you left out still resolves |
|
|
158
158
|
| **🤖 Agents** (17) | capability specialists: `reviewer`, `tester`, `architect`, `docs`, `security`, `explorer`, `developer` — plus a debate **council** the `council` skill convenes: `advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian` |
|
|
159
159
|
| **🛠 Skills** (47) | repeatable workflows: brainstorm · **clarify** · plan · **codebase-design** · **domain-modeling** · **wayfinder** · **tickets** · tdd · **develop** · debug · **prototype** · refactor · **ponytail** · **forge-mcp** · geneseed-code-review · **fresh-eyes** · **review-response** · commit · **ship** · **release** · **migrate** · **git-archaeology** · **git-rescue** · repo-map · document-project · **frontend-design** · **prose** · **ingest** · **research** · **learning-path** · **gap-detector** · **feynman** · **crash-course** · **drill** · **decode** · handoff · roast-me · **council** · parallel-agents · **workflow** · **wiki** · **geneseed** · **rule** · **profile** · **opencode-theme** · **herdr** · **pipeline** |
|
|
160
160
|
| **🔌 Plugins** (OpenCode) | `geneseed-context` injects project docs *and your machine wiki* every session (and across compaction); `geneseed-learn` distils memory at session end; `geneseed-guard` enforces the safety Laws and protected wiki folders at the tool boundary; `geneseed-workflow` registers the `workflow` tool that runs saved orchestration scripts; `geneseed-notify` sends a native OS notification when a long run finishes; `geneseed-ponytail` holds a minimal-code mode (`/ponytail lite\|full\|ultra\|off`), opt-in, injecting the laziest-that-works ruleset every turn so it doesn't drift; `geneseed-activity` streams what each session is doing to the web console's Activity view |
|
package/SETUP.md
CHANGED
|
@@ -168,8 +168,10 @@ It wires:
|
|
|
168
168
|
for Doctrine `process 5`. The hook inspects the command and forces an `ask` on every
|
|
169
169
|
`git commit` or `git push` (even chained or `-C`-flagged), un-suppressible by a
|
|
170
170
|
one-time "don't ask again". Every other Bash command is deferred to the normal
|
|
171
|
-
permission flow. It is wired only when
|
|
172
|
-
`--doctrines` leaving `process` out
|
|
171
|
+
permission flow. It is wired only when **Doctrine process 5 itself** is active — build
|
|
172
|
+
with `--doctrines` leaving `process` out, or with `--exclude-rules "process 5"`, and this
|
|
173
|
+
hook is not installed at all. The prompt and the boundary move together by construction:
|
|
174
|
+
the gate is keyed on the rule, not on its pack.
|
|
173
175
|
- **SessionStart** (`startup`/`clear`) — prints `AGENT.md` and injects the project
|
|
174
176
|
context (`harness context`, which auto-discovers the repo's docs);
|
|
175
177
|
- **SessionStart** (`resume`) — refreshes the project context only, without re-printing
|
|
@@ -313,6 +315,31 @@ the default is all four:
|
|
|
313
315
|
refuses one that names a pack the checkout does not ship. Order is fixed by the harness
|
|
314
316
|
(craft → rigor → ops → process) regardless of the order you type.
|
|
315
317
|
|
|
318
|
+
### One rule at a time
|
|
319
|
+
|
|
320
|
+
A pack is coarse: `--doctrines` can only keep all of **process** or none of it.
|
|
321
|
+
`--exclude-rules` is the finer axis, and it takes rule addresses:
|
|
322
|
+
|
|
323
|
+
geneseed-build --exclude-rules "process 7" # keep the pack, drop one rule
|
|
324
|
+
geneseed-build --exclude-rules "process 5,craft 2" # several
|
|
325
|
+
geneseed-build --exclude-rules none # exclude nothing (the default)
|
|
326
|
+
|
|
327
|
+
Both spellings of an address work — `process 7` as the constitution writes it, or `process.7`
|
|
328
|
+
as the console addresses it. The two axes compose: a pack whose every rule is excluded drops
|
|
329
|
+
out of `Active packs:` entirely, so you never get a pack header with nothing under it.
|
|
330
|
+
|
|
331
|
+
An install records what it excluded in a second marker line, `Excluded rules: process 7`,
|
|
332
|
+
written **only when something is excluded** — so a build with the default keeps every byte it
|
|
333
|
+
had before this axis existed. `upgrade`, `rebuild-all` and the web console all read that line
|
|
334
|
+
back and preserve it, exactly as they preserve the pack list.
|
|
335
|
+
|
|
336
|
+
⚠ **`process 5` is special**: excluding it also unwires the commit/push consent hook, because
|
|
337
|
+
the tool boundary is keyed on that rule. That is by design — the prompt and the boundary must
|
|
338
|
+
never disagree — and both the console and the wizard say so before applying it.
|
|
339
|
+
|
|
340
|
+
The web console has the switches: **Constitution → Doctrines**, one per rule, staged locally
|
|
341
|
+
with a single **Apply** that rebuilds the install once.
|
|
342
|
+
|
|
316
343
|
The setup wizard asks too — one *all* / *choose* gate, then a yes/no per pack with its
|
|
317
344
|
one-line description — and it **pre-selects what the install already carries**, read off the
|
|
318
345
|
`Active packs:` marker line in the deployed `AGENT.md`. So re-running the wizard and holding
|
package/SHIPPED.md
CHANGED
|
@@ -20,7 +20,7 @@ historical pointers, 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: **3.
|
|
23
|
+
> string. Current label: **3.1.0**.
|
|
24
24
|
|
|
25
25
|
> **Spec status audit (2026-06-15).** Every dated spec authored before today was
|
|
26
26
|
> verified against the source tree and confirmed implemented (code + tests + docs);
|
package/bin/geneseed.mjs
CHANGED
|
@@ -52,7 +52,9 @@ import {
|
|
|
52
52
|
// P5d moved these out of this file for the reason P5c moved the host resolvers: `harness
|
|
53
53
|
// status` renders to count, so `bin/geneseed-cli.mjs` needs the same checkout paths and the
|
|
54
54
|
// same cfg. golden.py's 259 cells all build one, which is what made the move safe.
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
ROOT, CONFIG, THEMES, discoverNames, makeCfg, PACK_ORDER, knownRuleIds,
|
|
57
|
+
} from '../js/checkout.mjs';
|
|
56
58
|
|
|
57
59
|
// P5f moved these, for the same arithmetic a third time: `harness rebuild-all` reads the
|
|
58
60
|
// registry to find every install it must re-emit, and a CLI verb may not reach into a driver
|
|
@@ -124,6 +126,7 @@ export function resolveOut(raw) {
|
|
|
124
126
|
function configDefaults() {
|
|
125
127
|
const d = {
|
|
126
128
|
theme: 'neutral', posture: 'peer', mode: 'direct', doctrines: [...PACK_ORDER],
|
|
129
|
+
excludeRules: [],
|
|
127
130
|
};
|
|
128
131
|
if (!existsSync(CONFIG)) return d;
|
|
129
132
|
try {
|
|
@@ -146,6 +149,19 @@ function configDefaults() {
|
|
|
146
149
|
+ 'list of known pack names — using all packs.\n');
|
|
147
150
|
}
|
|
148
151
|
}
|
|
152
|
+
// The second axis, read the same forgiving way and defaulting the OTHER direction: a
|
|
153
|
+
// bad value excludes NOTHING, because the safe answer for a list of things to take
|
|
154
|
+
// away is to take nothing away. Same reasoning as `doctrines` defaulting to all.
|
|
155
|
+
if (data.excludeRules !== undefined) {
|
|
156
|
+
if (Array.isArray(data.excludeRules)
|
|
157
|
+
&& data.excludeRules.every((x) => typeof x === 'string')) {
|
|
158
|
+
d.excludeRules = [...new Set(data.excludeRules.map((x) => x.replace(/[ \t]+/, '.')))]
|
|
159
|
+
.sort();
|
|
160
|
+
} else {
|
|
161
|
+
process.stderr.write(`[geneseed] WARN: ${path.basename(CONFIG)} "excludeRules" is `
|
|
162
|
+
+ 'not a list of rule addresses — excluding nothing.\n');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
149
165
|
}
|
|
150
166
|
} catch {
|
|
151
167
|
process.stderr.write(`[geneseed] WARN: ${path.basename(CONFIG)} is unreadable — `
|
|
@@ -162,7 +178,7 @@ function configDefaults() {
|
|
|
162
178
|
*/
|
|
163
179
|
const VALUED = {
|
|
164
180
|
'--theme': 'theme', '--posture': 'posture', '--mode': 'mode',
|
|
165
|
-
'--doctrines': 'doctrines',
|
|
181
|
+
'--doctrines': 'doctrines', '--exclude-rules': 'excludeRules',
|
|
166
182
|
'--out': 'out', '--target': 'out', '--emit': 'emit',
|
|
167
183
|
'--footprint': 'footprint', '--root': 'root',
|
|
168
184
|
};
|
|
@@ -204,6 +220,37 @@ function choicesFor() {
|
|
|
204
220
|
* packs the checkout actually has; a discovered pack missing from `PACK_ORDER` is a
|
|
205
221
|
* different fault and `js/render.mjs` refuses the whole build for it.
|
|
206
222
|
*/
|
|
223
|
+
/**
|
|
224
|
+
* `--exclude-rules "process 7,craft 3"` (or `process.7`) -> the cfg's exclusion array.
|
|
225
|
+
*
|
|
226
|
+
* BOTH SPELLINGS ACCEPTED ON THE WAY IN, ONE ON THE WAY OUT. The carrier's marker line and
|
|
227
|
+
* every citation in the corpus spell a rule `process 7`; the console's deep link and the
|
|
228
|
+
* catalogue spell it `process.7`. A flag that took only one of them would be wrong for
|
|
229
|
+
* whichever half of the documentation the reader had open, so it takes either and normalises
|
|
230
|
+
* to the dotted form the code compares.
|
|
231
|
+
*
|
|
232
|
+
* ⚠ VALIDATED AGAINST THE PACK FILES, NOT A RANGE. `process 9` and `craft 0` are both
|
|
233
|
+
* refusals, and so is a pack this checkout does not ship — an exclusion that names nothing is
|
|
234
|
+
* a typo that would otherwise take away nothing in silence, which is the failure this flag
|
|
235
|
+
* can least afford: the user asked for a rule to STOP binding and it would keep binding.
|
|
236
|
+
* Sorted, deduped, for the same marker-stability reason `--doctrines` is.
|
|
237
|
+
*/
|
|
238
|
+
function parseExcludeRules(value) {
|
|
239
|
+
const s = value.trim();
|
|
240
|
+
if (s === 'none' || s === '') return [];
|
|
241
|
+
const known = knownRuleIds();
|
|
242
|
+
const out = [];
|
|
243
|
+
for (const raw of s.split(',').map((x) => x.trim()).filter(Boolean)) {
|
|
244
|
+
const id = raw.replace(/[ \t]+/, '.');
|
|
245
|
+
if (!known.includes(id)) {
|
|
246
|
+
die(2, `argument --exclude-rules: invalid choice: '${raw}' (choose from `
|
|
247
|
+
+ `${known.map((c) => `'${c.replace('.', ' ')}'`).join(', ')}, or 'none')`);
|
|
248
|
+
}
|
|
249
|
+
out.push(id);
|
|
250
|
+
}
|
|
251
|
+
return [...new Set(out)].sort();
|
|
252
|
+
}
|
|
253
|
+
|
|
207
254
|
function parseDoctrines(value) {
|
|
208
255
|
const s = value.trim();
|
|
209
256
|
const known = discoverNames('doctrines', PACK_ORDER[0]);
|
|
@@ -264,6 +311,7 @@ function parseArgs(argv, defaults) {
|
|
|
264
311
|
const args = {
|
|
265
312
|
theme: defaults.theme, posture: defaults.posture, mode: defaults.mode,
|
|
266
313
|
doctrines: defaults.doctrines,
|
|
314
|
+
excludeRules: defaults.excludeRules,
|
|
267
315
|
out: 'Harness', emit: 'files', footprint: 'lean', root: null,
|
|
268
316
|
syncThemes: false, validateOnly: false, verbose: false,
|
|
269
317
|
};
|
|
@@ -296,6 +344,7 @@ function parseArgs(argv, defaults) {
|
|
|
296
344
|
// Only when the flag was actually PASSED is this a string; left at its default it is
|
|
297
345
|
// already the array `configDefaults` built, and re-parsing an array would stringify it.
|
|
298
346
|
if (typeof args.doctrines === 'string') args.doctrines = parseDoctrines(args.doctrines);
|
|
347
|
+
if (typeof args.excludeRules === 'string') args.excludeRules = parseExcludeRules(args.excludeRules);
|
|
299
348
|
return args;
|
|
300
349
|
}
|
|
301
350
|
|
package/docs/extending.md
CHANGED
|
@@ -116,9 +116,9 @@ Notes that matter:
|
|
|
116
116
|
|
|
117
117
|
### The gates CI does not run
|
|
118
118
|
|
|
119
|
-
- **The mutation matrix itself.** `tests/mutate.mjs` —
|
|
119
|
+
- **The mutation matrix itself.** `tests/mutate.mjs` — 33 rows, each a one-string edit to a
|
|
120
120
|
product file with a written argument for which gate should catch it. The full run is **in no
|
|
121
|
-
workflow** and should not be: it mutates the product tree
|
|
121
|
+
workflow** and should not be: it mutates the product tree 33 times and runs a gate per edit.
|
|
122
122
|
Run `node tests/mutate.mjs --all` (or `--only M7`) by hand when you change something a gate is
|
|
123
123
|
supposed to be watching. ⚠ An interrupted run leaves the **product tree mutated** — check
|
|
124
124
|
`git status` before doing anything else.
|
package/docs/web/rules.md
CHANGED
|
@@ -21,6 +21,8 @@ Practices rather than principles: **craft** (how code is written), **rigor** (ho
|
|
|
21
21
|
|
|
22
22
|
A pack you leave out **still ships in the bundle**: every pack file lands under `doctrines/` beside `AGENT.md` whether or not it was built in. So a rule that cites one in an inactive pack still resolves on disk, and you can read the alternatives before turning one on. What an inactive pack loses is bindingness, not availability.
|
|
23
23
|
|
|
24
|
+
Rules toggle **individually** too — `geneseed-build --exclude-rules "process 7"`, or the switches on this page — so *keep all of process except one rule* is a state you can build. A pack whose every rule is excluded drops out of the active set on its own. What an install excluded is recorded beside the pack list, as `Excluded rules: process 7`, and every rebuild preserves it.
|
|
25
|
+
|
|
24
26
|
### Which outranks which
|
|
25
27
|
|
|
26
28
|
Ontology and Invariants first, then your own `user-rules.md`, then the active Doctrines, then `PROFILE.md`. A rule you write in `user-rules.md` **outranks a doctrine rule** — a practice pack chosen at build time never overrides an instruction you wrote for your own repo — and nothing outranks an invariant: a user rule or a doctrine rule may tighten one, never repeal it.
|
|
@@ -36,10 +36,22 @@ Which practice rules this install adopts, fixed at build time. Unlike the dials
|
|
|
36
36
|
- **ops** — how the machine is driven: tool discovery, commands that return, complete teardowns.
|
|
37
37
|
- **process** — how a session runs: planning, context economy, docs first, and the **consent gate on every commit and push**.
|
|
38
38
|
|
|
39
|
-
Turning **process** off takes that consent gate with it, at the tool boundary as well as in the prose: the OpenCode permission block and the Claude/Bob `PreToolUse` hook that ask before a commit or a push are wired only when
|
|
39
|
+
Turning **process 5** off takes that consent gate with it, at the tool boundary as well as in the prose: the OpenCode permission block and the Claude/Bob `PreToolUse` hook that ask before a commit or a push are wired only when that rule is built in, because a boundary that keeps asking for a rule the install never adopted is a gate arguing with its own harness. The invariant-territory refusals — `rm -rf`, a force-push — stay in every build; they are the Rules' territory, not the pack's.
|
|
40
40
|
|
|
41
41
|
A pack you leave out still ships in the bundle under `doctrines/`, so you can read it before turning it on, and a rule that cites it still resolves. Change the set with `geneseed-build --doctrines craft,rigor` (or `--doctrines none`), or in `harness.config.json`.
|
|
42
42
|
|
|
43
|
+
### One rule at a time
|
|
44
|
+
|
|
45
|
+
The pack is the coarse axis. `--exclude-rules` is the fine one, and it takes rule addresses — so *keep all of process, drop just `process 7`* is expressible:
|
|
46
|
+
|
|
47
|
+
geneseed-build --exclude-rules "process 7"
|
|
48
|
+
geneseed-build --exclude-rules "process 5,craft 2"
|
|
49
|
+
geneseed-build --exclude-rules none
|
|
50
|
+
|
|
51
|
+
Either spelling of an address works, `process 7` or `process.7`. The two axes compose: a pack whose every rule is excluded leaves `Active packs:` entirely, so no pack header is ever rendered with nothing under it. An install records its exclusions in a second marker line — `Excluded rules: process 7` — written only when something *is* excluded, and every rebuild path reads it back and preserves it.
|
|
52
|
+
|
|
53
|
+
The switches are on the **Constitution** page, one per rule, staged locally and applied in a single rebuild.
|
|
54
|
+
|
|
43
55
|
### Footprint — how much of the constitution loads inline
|
|
44
56
|
|
|
45
57
|
How much of the constitution `AGENT.md` carries inline every turn. A token-cost dial, not a rules cut — whatever this install adopted always applies:
|
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": "3.
|
|
5
|
+
"version": "3.1.1"
|
|
6
6
|
}
|
package/js/checkout.mjs
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* over 259 cells and compares the tree byte-for-byte, and every one of them builds a cfg
|
|
23
23
|
* from these paths. A depth error or a renamed key fails 259 cells, not zero.
|
|
24
24
|
*/
|
|
25
|
-
import { readdirSync } from 'node:fs';
|
|
25
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { fileURLToPath } from 'node:url';
|
|
28
28
|
|
|
@@ -83,6 +83,34 @@ export function discoverNames(dir, first) {
|
|
|
83
83
|
*/
|
|
84
84
|
export const PACK_ORDER = ['craft', 'rigor', 'ops', 'process'];
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Every doctrine rule address this checkout ships — `pack.n`, in render order.
|
|
88
|
+
*
|
|
89
|
+
* The ONE enumerator, for the same arithmetic reason `PACK_ORDER` is one array: the CLI
|
|
90
|
+
* flag, the console's trust boundary and the doctor all have to close `--exclude-rules`
|
|
91
|
+
* against the SAME set, and a second copy is a set that drifts by one rule the day a pack
|
|
92
|
+
* grows. Read from the UNRENDERED source (`### {{DOCTRINE}} <pack> <n>`) rather than from a
|
|
93
|
+
* built tree, because an address is a property of the source and the rendered heading is
|
|
94
|
+
* themed — fourteen spellings of a thing that must compare equal to itself.
|
|
95
|
+
*
|
|
96
|
+
* Ordered by `PACK_ORDER`, then by the order the headings stand in the pack file — NOT sorted.
|
|
97
|
+
* The file is what renders, so mirroring it is what keeps this list and the rendered
|
|
98
|
+
* constitution in the same sequence; a sort would silently disagree with a file whose headings
|
|
99
|
+
* were ever out of order, and disagree in the direction that reads as correct.
|
|
100
|
+
*/
|
|
101
|
+
export function knownRuleIds() {
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const pack of PACK_ORDER) {
|
|
104
|
+
const file = path.join(SRC, 'doctrines', `${pack}.md`);
|
|
105
|
+
let text = '';
|
|
106
|
+
try { text = readFileSync(file, 'utf8'); } catch { continue; }
|
|
107
|
+
for (const m of text.matchAll(/^### \{\{DOCTRINE\}\} ([a-z]+) (\d+)\b/gm)) {
|
|
108
|
+
if (m[1] === pack) out.push(`${pack}.${Number(m[2])}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
86
114
|
/**
|
|
87
115
|
* `_build_core.js_cfg()`, originated rather than received.
|
|
88
116
|
*
|
|
@@ -95,7 +123,9 @@ export const PACK_ORDER = ['craft', 'rigor', 'ops', 'process'];
|
|
|
95
123
|
* out to renderers and a caller that sorted or spliced it in place would rewrite
|
|
96
124
|
* `PACK_ORDER` for the whole process.
|
|
97
125
|
*/
|
|
98
|
-
export function makeCfg({
|
|
126
|
+
export function makeCfg({
|
|
127
|
+
posture = 'peer', mode = 'direct', doctrines = PACK_ORDER, excludeRules = [],
|
|
128
|
+
} = {}) {
|
|
99
129
|
return {
|
|
100
130
|
root: ROOT,
|
|
101
131
|
src: SRC,
|
|
@@ -107,5 +137,16 @@ export function makeCfg({ posture = 'peer', mode = 'direct', doctrines = PACK_OR
|
|
|
107
137
|
posture,
|
|
108
138
|
mode,
|
|
109
139
|
doctrines: Array.isArray(doctrines) ? [...doctrines] : [...PACK_ORDER],
|
|
140
|
+
// ⚠ THE SECOND DOCTRINE AXIS, AND ITS DEFAULT IS THE OPPOSITE WAY ROUND. `doctrines`
|
|
141
|
+
// defaults to EVERYTHING because an unknown selection must bind the most; `excludeRules`
|
|
142
|
+
// defaults to NOTHING for exactly the same reason — an exclusion is a rule taken AWAY, so
|
|
143
|
+
// "unspecified" has to mean "nothing taken away". Both defaults fail closed; they only
|
|
144
|
+
// look inconsistent until you ask which direction is the safe one for each.
|
|
145
|
+
//
|
|
146
|
+
// Addresses are `<pack>.<n>`, the same spelling the console deep-links and the catalogue
|
|
147
|
+
// publishes. An exclusion whose pack is not in `doctrines` is harmless and stays: the pack
|
|
148
|
+
// is already absent, and dropping the exclusion would silently re-admit the rule the day
|
|
149
|
+
// the pack came back.
|
|
150
|
+
excludeRules: Array.isArray(excludeRules) ? [...excludeRules] : [],
|
|
110
151
|
};
|
|
111
152
|
}
|
package/js/doctor.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import os from 'node:os';
|
|
|
34
34
|
import path from 'node:path';
|
|
35
35
|
|
|
36
36
|
import {
|
|
37
|
-
CONFIG, PACK_ORDER, PLUGIN_SRC, ROOT, SRC, THEMES, WORKFLOW_SRC, makeCfg,
|
|
37
|
+
CONFIG, PACK_ORDER, PLUGIN_SRC, ROOT, SRC, THEMES, WORKFLOW_SRC, knownRuleIds, makeCfg,
|
|
38
38
|
} from './checkout.mjs';
|
|
39
39
|
import {
|
|
40
40
|
buildInto, main as driverMain, emitGlobalInto, emitProjectInto,
|
|
@@ -900,6 +900,44 @@ export function constitutionProblems() {
|
|
|
900
900
|
// notices a blank description in the browser.
|
|
901
901
|
problems.push(...doctrineMetaProblems([...rules.keys()]));
|
|
902
902
|
|
|
903
|
+
// ---- `--exclude-rules` closes against the SAME rules this walk just found.
|
|
904
|
+
//
|
|
905
|
+
// `knownRuleIds` is what the CLI flag, the console's trust boundary and every wizard prompt
|
|
906
|
+
// validate an address against; the walk above is what the constitution actually renders.
|
|
907
|
+
// Two readers of one directory is the shape that lets a rule be authored, rendered, cited —
|
|
908
|
+
// and rejected by the only flag that can switch it off, with `invalid choice` naming a set
|
|
909
|
+
// that visibly contains it. An equality, not containment: a stale id in the enumerator is a
|
|
910
|
+
// rule the console offers a switch for and the build cannot drop.
|
|
911
|
+
const enumerated = knownRuleIds();
|
|
912
|
+
const walked = [...rules.keys()];
|
|
913
|
+
for (const id of enumerated.filter((i) => !walked.includes(i))) {
|
|
914
|
+
problems.push(`[authoring] knownRuleIds() offers '${id.replace('.', ' ')}', which no pack `
|
|
915
|
+
+ 'file defines — --exclude-rules accepts an address the build cannot drop');
|
|
916
|
+
}
|
|
917
|
+
for (const id of walked.filter((i) => !enumerated.includes(i))) {
|
|
918
|
+
problems.push(`[authoring] doctrine ${id.replace('.', ' ')} is defined but knownRuleIds() `
|
|
919
|
+
+ 'does not offer it — --exclude-rules refuses a rule that exists, and the console has '
|
|
920
|
+
+ 'no switch for it');
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// ---- the consent gate's address still names the consent rule.
|
|
924
|
+
//
|
|
925
|
+
// ⚠ THE ONE HARDCODED ADDRESS IN THE CODEBASE, and the one whose drift is silent AND unsafe.
|
|
926
|
+
// `js/settings.mjs` keys the git-gate hooks on `process.5` by literal; a renumber of the
|
|
927
|
+
// process pack moves that rule's neighbours under it, and the boundary would then follow
|
|
928
|
+
// whatever rule inherited the number while the prompt still said `process 5`. Checked
|
|
929
|
+
// against the pack file's own title token rather than against prose, so a reworded rule is
|
|
930
|
+
// not a false alarm and a MOVED one is not a silent pass.
|
|
931
|
+
const consentTitle = 'DOC_PROCESS_5';
|
|
932
|
+
let processText = '';
|
|
933
|
+
try { processText = readText(path.join(dir, 'process.md')); } catch { /* reported above */ }
|
|
934
|
+
if (processText && !new RegExp(`^### \\{\\{DOCTRINE\\}\\} process 5 — \\{\\{${consentTitle}\\}\\}`,
|
|
935
|
+
'm').test(processText)) {
|
|
936
|
+
problems.push('[authoring] js/settings.mjs gates git commit/push on doctrine \'process 5\', '
|
|
937
|
+
+ `but doctrines/process.md does not number {${consentTitle}} 5 — the tool boundary and `
|
|
938
|
+
+ 'the prompt now name different rules');
|
|
939
|
+
}
|
|
940
|
+
|
|
903
941
|
// ---- every `{{DOCTRINE}} <pack> <n>` anywhere in src/ resolves to a rule that exists.
|
|
904
942
|
//
|
|
905
943
|
// The whole tree, not the template: a skill footer citing `process 9` is as broken as a
|
package/js/emit.mjs
CHANGED
|
@@ -846,7 +846,7 @@ export function emitOpencodeRender(cfg, job) {
|
|
|
846
846
|
// downstream: `opencode.json`, or the `.jsonc` sibling when that is what is on disk.
|
|
847
847
|
phaseLog('WIRE');
|
|
848
848
|
const cfgName = path.basename(mergeOpencodeJson(path.join(root, 'opencode.json'), agentPath,
|
|
849
|
-
cfg.doctrines));
|
|
849
|
+
cfg.doctrines, cfg.excludeRules));
|
|
850
850
|
|
|
851
851
|
return {
|
|
852
852
|
owned,
|
|
@@ -1080,7 +1080,7 @@ export function emitOpencodeGlobalRender(cfg, job) {
|
|
|
1080
1080
|
// that split is what `tests/test_emit_phase_order.py` walks, and it walks the Python.
|
|
1081
1081
|
phaseLog('WIRE');
|
|
1082
1082
|
const cfgName = path.basename(mergeOpencodeJson(path.join(cfgDir, 'opencode.json'),
|
|
1083
|
-
agentPath, cfg.doctrines));
|
|
1083
|
+
agentPath, cfg.doctrines, cfg.excludeRules));
|
|
1084
1084
|
|
|
1085
1085
|
return {
|
|
1086
1086
|
owned,
|
|
@@ -1216,7 +1216,8 @@ export function emitClaudeRender(cfg, job) {
|
|
|
1216
1216
|
|
|
1217
1217
|
// WIRE — see `claudeWire` below. One child per emit, so the two halves are two
|
|
1218
1218
|
// functions rather than two spawns; the seam between them is a call, not a process.
|
|
1219
|
-
const managed = claudeWire(job, claudeMdText, agentText !== null, cfg.doctrines
|
|
1219
|
+
const managed = claudeWire(job, claudeMdText, agentText !== null, cfg.doctrines,
|
|
1220
|
+
cfg.excludeRules);
|
|
1220
1221
|
|
|
1221
1222
|
return {
|
|
1222
1223
|
owned,
|
|
@@ -1251,7 +1252,7 @@ export function emitClaudeRender(cfg, job) {
|
|
|
1251
1252
|
* not needed for it: Python tests `os.environ.get(...)` for truthiness and an env var is
|
|
1252
1253
|
* always a string, so `''` is the only falsy value either language sees.
|
|
1253
1254
|
*/
|
|
1254
|
-
function claudeWire(job, claudeMdText, hasAgentText, doctrines = null) {
|
|
1255
|
+
function claudeWire(job, claudeMdText, hasAgentText, doctrines = null, excludeRules = []) {
|
|
1255
1256
|
phaseLog('WIRE');
|
|
1256
1257
|
const { cfgDir, claudeMd, scope, host, oldManaged, preambleExclude, hookOpts } = job;
|
|
1257
1258
|
const old = oldManaged && typeof oldManaged === 'object' && !Array.isArray(oldManaged)
|
|
@@ -1312,7 +1313,7 @@ function claudeWire(job, claudeMdText, hasAgentText, doctrines = null) {
|
|
|
1312
1313
|
// DECIDES the install's packs, so the marker on disk is still the previous build's.
|
|
1313
1314
|
const [, managedHooks] = mergeClaudeSettings(
|
|
1314
1315
|
settingsPath, scope, oldSf === settingsName ? get(old, 'settings_hooks') : null, hookOpts,
|
|
1315
|
-
doctrines,
|
|
1316
|
+
doctrines, excludeRules,
|
|
1316
1317
|
);
|
|
1317
1318
|
managed.settings_hooks = managedHooks;
|
|
1318
1319
|
|
package/js/generate.mjs
CHANGED
|
@@ -37,7 +37,8 @@ import { makeCfg, PACK_ORDER } from './checkout.mjs';
|
|
|
37
37
|
import { opencodeConfigDir, pyResolve } from './hosts.mjs';
|
|
38
38
|
import {
|
|
39
39
|
EMIT_HOST_SCOPE, defaultMode, defaultPosture, defaultTheme, doctrinesForBuild,
|
|
40
|
-
footprintOfDir, installState, installTargets, modeOfDir, postureOfDir,
|
|
40
|
+
excludedRulesOfDir, footprintOfDir, installState, installTargets, modeOfDir, postureOfDir,
|
|
41
|
+
readMaybe, themeOfDir,
|
|
41
42
|
} from './installs.mjs';
|
|
42
43
|
import { colorThemeFiles, colorThemeJson, PALETTE_ROLES } from './opencode.mjs';
|
|
43
44
|
import { renderAll } from './render.mjs';
|
|
@@ -122,7 +123,8 @@ export function cmdBuild(args) {
|
|
|
122
123
|
* accepts (an empty `--doctrines ` is a usage error there, so it can never be produced here).
|
|
123
124
|
*/
|
|
124
125
|
export function setupBuildArgs(theme, emit, out = null, root = null, footprint = 'lean',
|
|
125
|
-
posture = 'peer', mode = 'direct', doctrines = null, allPacks = PACK_ORDER
|
|
126
|
+
posture = 'peer', mode = 'direct', doctrines = null, allPacks = PACK_ORDER,
|
|
127
|
+
excludeRules = null) {
|
|
126
128
|
const argv = ['--theme', theme, '--emit', emit];
|
|
127
129
|
if (!['opencode-global', 'claude-global', 'bob-global', 'copilot-global'].includes(emit)) {
|
|
128
130
|
if (out) argv.push('--out', out);
|
|
@@ -138,6 +140,13 @@ export function setupBuildArgs(theme, emit, out = null, root = null, footprint =
|
|
|
138
140
|
const picked = allPacks.filter((p) => doctrines.includes(p));
|
|
139
141
|
argv.push('--doctrines', picked.length ? picked.join(',') : 'none');
|
|
140
142
|
}
|
|
143
|
+
// The second axis, emitted the same way and for the same reason — but ALWAYS when the
|
|
144
|
+
// caller passed a list, empty included. An omitted flag reads as "no opinion" and the
|
|
145
|
+
// driver's config fallback fills it in; `none` is the caller saying "exclude nothing",
|
|
146
|
+
// which is exactly what a rebuild that just re-enabled the last rule has to say.
|
|
147
|
+
if (Array.isArray(excludeRules)) {
|
|
148
|
+
argv.push('--exclude-rules', excludeRules.length ? excludeRules.join(',') : 'none');
|
|
149
|
+
}
|
|
141
150
|
return argv;
|
|
142
151
|
}
|
|
143
152
|
|
|
@@ -201,7 +210,13 @@ export function cmdRebuildAll() {
|
|
|
201
210
|
// resolving that silence out of a machine-wide config narrows every such install on its
|
|
202
211
|
// first upgrade. `doctrinesForBuild` resolves unknown to ALL packs — see its docblock.
|
|
203
212
|
const doctrines = doctrinesForBuild(root);
|
|
204
|
-
|
|
213
|
+
// SEVEN. The per-rule axis is preserved for the same reason and with sharper teeth: an
|
|
214
|
+
// upgrade that dropped it would quietly hand back every rule its owner switched off, and
|
|
215
|
+
// `process 5` is one of them — the consent gate would return to a harness that had been
|
|
216
|
+
// built without it. No `doctrinesForBuild` rescue is needed here; see `apiInstallCmd`.
|
|
217
|
+
const excludeRules = excludedRulesOfDir(root);
|
|
218
|
+
const argv = setupBuildArgs(theme, emit, out, out, footprint, posture, mode, doctrines,
|
|
219
|
+
PACK_ORDER, excludeRules);
|
|
205
220
|
const label = `${host}:${scope} (${root})`;
|
|
206
221
|
pyPrint(`[rebuild-all] ${label}: theme=${theme} emit=${emit} footprint=${footprint} `
|
|
207
222
|
+ `posture=${posture} mode=${mode}\n`);
|