geneseed 4.9.4 → 4.10.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +13 -0
  3. package/SHIPPED.md +1 -1
  4. package/docs/README.md +1 -1
  5. package/docs/concepts/collaboration.md +1 -1
  6. package/docs/concepts/enforcement-table.md +1 -1
  7. package/docs/concepts/foreman-mode.md +1 -1
  8. package/docs/concepts/kits.md +79 -0
  9. package/docs/concepts/loop-branch.md +1 -1
  10. package/docs/concepts/loop-bricks.md +1 -1
  11. package/docs/concepts/loop-contracts.md +1 -1
  12. package/docs/concepts/loop-gates.md +1 -1
  13. package/docs/concepts/loop-notes.md +1 -1
  14. package/docs/concepts/loop-risk.md +1 -1
  15. package/docs/concepts/loop-templates.md +1 -1
  16. package/docs/concepts/loops.md +1 -1
  17. package/docs/concepts/lsp.md +1 -1
  18. package/docs/extending.md +108 -4
  19. package/docs/guides/install-scripted.md +1 -1
  20. package/docs/reference/glossary.md +1 -0
  21. package/docs/web-ui.md +4 -0
  22. package/harness.config.json +1 -1
  23. package/js/build/args.mjs +27 -6
  24. package/js/build/driver.mjs +29 -8
  25. package/js/build/emit-claude.mjs +3 -1
  26. package/js/build/generate.mjs +10 -5
  27. package/js/build/render.mjs +94 -11
  28. package/js/build/source.mjs +117 -4
  29. package/js/cli-table.json +15 -0
  30. package/js/hosts/installs.mjs +35 -2
  31. package/js/hosts/native.mjs +14 -4
  32. package/js/inspect/checks-authoring.mjs +6 -3
  33. package/js/inspect/checks-build.mjs +7 -1
  34. package/js/inspect/checks-counts.mjs +153 -6
  35. package/js/inspect/checks-repo.mjs +95 -20
  36. package/js/inspect/diff.mjs +3 -1
  37. package/js/inspect/inventory.mjs +1 -0
  38. package/js/inspect/status.mjs +44 -3
  39. package/js/inspect/validate.mjs +3 -3
  40. package/js/maintain/setup.mjs +30 -6
  41. package/js/maintain/update.mjs +1 -1
  42. package/js/web/actions.mjs +30 -6
  43. package/js/web/api.mjs +40 -3
  44. package/js/web/routes.mjs +2 -1
  45. package/package.json +1 -1
  46. package/registry.json +63 -0
  47. package/src/AGENT.md.tmpl +2 -2
  48. package/src/agents/devops-engineer.md +45 -0
  49. package/src/agents/docker-expert.md +45 -0
  50. package/src/agents/kubernetes-specialist.md +45 -0
  51. package/src/agents/reviewer.md +2 -0
  52. package/src/agents/spring-boot-engineer.md +48 -0
  53. package/src/agents/spring-reviewer.md +49 -0
  54. package/src/agents/spring-security.md +50 -0
  55. package/src/kits/spring-boot/rules.md +10 -0
  56. package/src/kits/spring-boot.json +23 -0
  57. package/src/licenses/mattpocock-skills.txt +21 -0
  58. package/src/licenses/piomin-spring-boot.txt +190 -0
  59. package/src/licenses/superpowers.txt +21 -0
  60. package/src/licenses/vibe-wise.txt +21 -0
  61. package/src/skills/brainstorm/builder-check.md +2 -0
  62. package/src/skills/brainstorm.md +4 -0
  63. package/src/skills/codebase-design.md +4 -0
  64. package/src/skills/daydream/VENDOR.md +7 -7
  65. package/src/skills/debug.md +5 -1
  66. package/src/skills/develop.md +2 -0
  67. package/src/skills/geneseed-code-review.md +6 -2
  68. package/src/skills/git-history.md +2 -0
  69. package/src/skills/handoff.md +2 -0
  70. package/src/skills/learn-mode.md +3 -2
  71. package/src/skills/parallel-agents.md +2 -0
  72. package/src/skills/pipeline.md +2 -0
  73. package/src/skills/plan.md +4 -0
  74. package/src/skills/prototype.md +2 -0
  75. package/src/skills/react-view-transitions/AGENTS.md +646 -221
  76. package/src/skills/react-view-transitions/SKILL.md +28 -16
  77. package/src/skills/react-view-transitions/VENDOR.md +10 -8
  78. package/src/skills/react-view-transitions/references/css-recipes.md +72 -2
  79. package/src/skills/react-view-transitions/references/implementation.md +14 -26
  80. package/src/skills/react-view-transitions/references/nextjs.md +94 -37
  81. package/src/skills/react-view-transitions/references/patterns.md +60 -48
  82. package/src/skills/react-view-transitions/references/troubleshooting.md +47 -0
  83. package/src/skills/research.md +2 -0
  84. package/src/skills/review-response.md +2 -0
  85. package/src/skills/rule.md +2 -0
  86. package/src/skills/ship.md +2 -0
  87. package/src/skills/skill-forge.md +1 -0
  88. package/src/skills/spring-boot/cloud.md +103 -0
  89. package/src/skills/spring-boot/java-modern.md +71 -0
  90. package/src/skills/spring-boot/logging.md +89 -0
  91. package/src/skills/spring-boot/security.md +112 -0
  92. package/src/skills/spring-boot/web.md +112 -0
  93. package/src/skills/spring-boot.md +57 -0
  94. package/src/skills/spring-data-jpa/patterns.md +68 -0
  95. package/src/skills/spring-data-jpa.md +81 -0
  96. package/src/skills/spring-testing.md +93 -0
  97. package/src/skills/wayfinder.md +2 -0
  98. package/src/upstreams.json +59 -0
  99. package/themes/_TEMPLATE.json +9 -0
  100. package/themes/biker.json +9 -0
  101. package/themes/commentator.json +9 -0
  102. package/themes/cyberpunk.json +9 -0
  103. package/themes/gamer.json +9 -0
  104. package/themes/imperial.json +9 -0
  105. package/themes/joker.json +9 -0
  106. package/themes/marvin.json +9 -0
  107. package/themes/mean.json +9 -0
  108. package/themes/military.json +9 -0
  109. package/themes/neutral.json +9 -0
  110. package/themes/pirate.json +9 -0
  111. package/themes/sports.json +9 -0
  112. package/themes/verstappen.json +9 -0
  113. package/themes/wizard.json +9 -0
  114. package/web/dist/assets/{Activity-C8sZVIUU.js → Activity-BjyJaf8e.js} +1 -1
  115. package/web/dist/assets/{ActivityDetail-Cc6vWUS4.js → ActivityDetail-Bo9nRDNV.js} +1 -1
  116. package/web/dist/assets/{Dashboard-C5ATa3ow.js → Dashboard-BQJGU7vE.js} +2 -2
  117. package/web/dist/assets/{Docs-tDW7i7mo.js → Docs-DLVckkew.js} +1 -1
  118. package/web/dist/assets/{GreenhouseView-zk2Uzc3A.js → GreenhouseView--DML7CW3.js} +1 -1
  119. package/web/dist/assets/{Installs-CB_rvbBP.js → Installs-CxCiln1F.js} +2 -2
  120. package/web/dist/assets/{JournalView-Bfj27XHC.js → JournalView-ymgZleHi.js} +1 -1
  121. package/web/dist/assets/{Laws-BA3CzUf5.js → Laws-BkKnZOBI.js} +1 -1
  122. package/web/dist/assets/Library-C8ercIZv.js +1 -0
  123. package/web/dist/assets/{Loops-DaPk-ckL.js → Loops-C95zhbFl.js} +1 -1
  124. package/web/dist/assets/{OperatorHudView-CyiGhiHk.js → OperatorHudView-B9qwc3w3.js} +1 -1
  125. package/web/dist/assets/{Personal-Bwd27CmT.js → Personal-DyR72AF-.js} +1 -1
  126. package/web/dist/assets/{Tabs-BOD7Kaz1.js → Tabs-ChCr_HqV.js} +1 -1
  127. package/web/dist/assets/api-BfpZmUMI.js +1 -0
  128. package/web/dist/assets/{drawable-CyLe1gtW.js → drawable-CgYi0Dtg.js} +1 -1
  129. package/web/dist/assets/index-B32EvfDT.js +2 -0
  130. package/web/dist/index.html +2 -2
  131. package/web/dist/assets/Library-BWwiy9Vb.js +0 -1
  132. package/web/dist/assets/api-CiXlkbe0.js +0 -1
  133. package/web/dist/assets/index-DQXG_mqo.js +0 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,27 @@ label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [4.10.0] — 2026-10-20
12
+
13
+ Minor: opt-in kits (and a first one, `spring-boot`), credits for adapted content, a weekly upstream-drift check, and a web Kits tab.
14
+
15
+ ### Upgrade notes
16
+ - Run `geneseed rebuild-all` once after updating. Default installs are unchanged (no kit is on); enable one with `geneseed build --kits spring-boot` or the new **Installs → Kits** tab.
17
+
18
+ ### Added
19
+ - **Kits: opt-in bundles of skills, agents and rules** (#343). `src/kits/<kit>.json` declares a kit; `--kits a,b` turns kits on (`none` turns all off), the install records `Active kits:` and `rebuild-all`, `update`, `migrate` and the web console keep it. A plain build warns before it drops a kit an install has; `status` lists kits and files you edited that were kept from a kit turned off. The doctor gates kit membership, links (kit → core only), registry rows and each kit's rules budget.
20
+ - **The `spring-boot` kit** (#349), adapted from piomin/claude-ai-spring-boot (Apache-2.0) and rewritten for Spring Boot 4: skills `spring-boot`, `spring-data-jpa`, `spring-testing`; agents `spring-boot-engineer`, `spring-reviewer` and `spring-security` (both read-only), `kubernetes-specialist`, `docker-expert`, `devops-engineer`; kit rules. A banned-API gate keeps removed Boot APIs (`@MockBean`, `javax.persistence`, …) out of kit files.
21
+ - **Credits for adapted content** (#342). `src/upstreams.json` records each upstream's repo, commit, author and licence; a `{{CREDIT:key:path}}` token renders an "Adapted from … modified." line and the skill ships the licence beside it. Superpowers, Matt Pocock's skills and vibe-wise ports are now credited; the README gains a Credits section, gated by the doctor.
22
+ - **Weekly upstream-drift check** (#344, #351). A CI job compares every pinned upstream with its HEAD on the files Geneseed uses and keeps one tracking issue per upstream; vendored skills are compared on their folder only, and `"frozen": true` entries are skipped.
23
+ - **Web console: Installs → Kits** (#350). One row per global and project install, a switch per kit, apply through the usual confirm and rebuild job.
24
+
25
+ ### Changed
26
+ - `react-view-transitions` re-vendored at upstream HEAD (Next.js no longer needs the `experimental.viewTransition` flag; back/forward navigation and shared-element guidance updated) (#351).
27
+ - `debug` proves a forced red landed by diffing against a pristine copy; `geneseed-code-review` searches the repo for its standards files (#351).
28
+
29
+ ### Fixed
30
+ - The web install action no longer re-themes an install you are not viewing (#350).
31
+
11
32
  ## [4.9.4] — 2026-10-18
12
33
 
13
34
  Patch: `git reset --hard` asks only when it would abandon existing commits, not over uncommitted work.
package/README.md CHANGED
@@ -367,6 +367,19 @@ Three things that bite when you don't know them:
367
367
  - **The version has one owner: `harness.config.json`.** `package.json` mirrors it and a test fails the fork. Never `npm version` — it edits one of the two.
368
368
  - **Publishing is deliberate and manual.** `.github/workflows/publish.yml` uses npm trusted publishing (OIDC); there is no `NPM_TOKEN` in this repository and there must not be one. It runs only from Actions → publish → Run workflow, and the npm-side trusted publisher is keyed on that workflow's **filename** — renaming the file breaks publishing with no local symptom, which is why the file names itself in its own header and a test asserts the two agree.
369
369
 
370
+ ## 🙏 Credits
371
+
372
+ Some skills and agents are adapted from other open-source skill collections, under the MIT or Apache-2.0 licence (named on each entry below). Each adapted skill names its source in its own text, and ships that upstream's licence beside it. `src/upstreams.json` pins every upstream to a commit.
373
+
374
+ - `daydream` — [glebis/claude-skills](https://github.com/glebis/claude-skills) by Gleb Kalinin (MIT): the vendored `daydream` skill.
375
+ - `react-view-transitions` — [vercel-labs/agent-skills](https://github.com/vercel-labs/agent-skills) by Vercel Engineering (MIT): the vendored `react-view-transitions` skill.
376
+ - `mattpocock-skills` — [mattpocock/skills](https://github.com/mattpocock/skills) by Matt Pocock (MIT): the vocabulary and procedures behind codebase-design, wayfinder, prototype, develop, handoff and research, the spec pass of geneseed-code-review, the question rounds of brainstorm, the slicing of plan, and the reproduction step of debug.
377
+ - `mattpocock-skills-frozen` — [mattpocock/skills](https://github.com/mattpocock/skills) by Matt Pocock (MIT), pinned at 391a2701, the last commit carrying `resolving-merge-conflicts` (deleted upstream since): the merge-conflict step of git-history, from a skill upstream has since deleted, so the weekly drift check skips it.
378
+ - `mattpocock-skills-2509` — [mattpocock/skills](https://github.com/mattpocock/skills) by Matt Pocock (MIT), a second key for the same repo, at the same pin as `mattpocock-skills` today (5beb35b0): the PR body of ship and the guardrail clause of rule.
379
+ - `superpowers` — [obra/superpowers](https://github.com/obra/superpowers) by Jesse Vincent (MIT): the core of debug, the size step and builder check of brainstorm, the plan rules of plan, the reviewer brief behind geneseed-code-review and the reviewer agent, the review-handling steps of review-response, and the ruling format of pipeline and parallel-agents.
380
+ - `vibe-wise` — [nykooi1/vibe-wise](https://github.com/nykooi1/vibe-wise) by Noah Kim (MIT): the state file, levels and checkpoints of learn-mode.
381
+ - `piomin-spring-boot` — [piomin/claude-ai-spring-boot](https://github.com/piomin/claude-ai-spring-boot) by Piotr Mińkowski (Apache-2.0): the Spring Boot kit's skills and agents, rewritten and condensed for Boot 4.
382
+
370
383
  ## 📄 License
371
384
 
372
385
  [MIT](LICENSE) — built by [@Arylmera](https://github.com/Arylmera).
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.9.4**.
23
+ > string. Current label: **4.10.0**.
24
24
 
25
25
  ## Capabilities
26
26
 
package/docs/README.md CHANGED
@@ -42,7 +42,7 @@
42
42
 
43
43
  **Memory** — [Memory](concepts/memory.md) · [Notebook](concepts/notebook.md)
44
44
 
45
- **Configuration** — [Footprint](concepts/footprint.md) · [Themes](concepts/themes.md) · [Exclusions](concepts/exclusions.md) · [Code intelligence (LSP)](concepts/lsp.md)
45
+ **Configuration** — [Footprint](concepts/footprint.md) · [Themes](concepts/themes.md) · [Exclusions](concepts/exclusions.md) · [Kits](concepts/kits.md) · [Code intelligence (LSP)](concepts/lsp.md)
46
46
 
47
47
  **Working together** — [Collaboration](concepts/collaboration.md) · [Foreman mode](concepts/foreman-mode.md)
48
48
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  group: concepts
3
- order: 14
3
+ order: 15
4
4
  title: "Collaboration"
5
5
  kind: "concept"
6
6
  section: "Working together"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  group: concepts
3
- order: 24
3
+ order: 25
4
4
  title: "Rule enforcement, by host"
5
5
  kind: "concept"
6
6
  section: "Harness"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  group: concepts
3
- order: 15
3
+ order: 16
4
4
  title: "Foreman mode"
5
5
  kind: "concept"
6
6
  section: "Working together"
@@ -0,0 +1,79 @@
1
+ ---
2
+ group: concepts
3
+ order: 13
4
+ title: "Kits"
5
+ kind: "concept"
6
+ section: "Configuration"
7
+ description: "Opt-in bundles of skills, agents and rules for one stack, off unless you name them."
8
+ ---
9
+ A **kit** is an opt-in bundle of skills, agents and rules for one stack. Kits are off by default and switched on per install, project or global, on every host. An install without a kit pays nothing for it. Inside an install that has it on, a kit's skills and agents are ordinary: the model picks them as it picks any other.
10
+
11
+ > **You already know this:** an optional dependency group, like `pip install pkg[postgres]`. The core install stays small, and you opt into the extra parts for your stack.
12
+
13
+ The first kit is `spring-boot`, for Spring Boot 4 (Spring Framework 7, Java 21+) projects. It adds nine members:
14
+
15
+ | Member | Kind | What it is for |
16
+ |---|---|---|
17
+ | `spring-boot` | skill | Building a Spring Boot service: web layer, configuration, security, HTTP clients, logging, cloud-native packaging, and a table of Boot 4 pitfalls |
18
+ | `spring-data-jpa` | skill | Spring Data JPA and Hibernate: entities, repositories, transactions, N+1 and fetch problems, migrations |
19
+ | `spring-testing` | skill | Spring Boot tests: slices, `MockMvcTester`, `@MockitoBean`, Testcontainers with `@ServiceConnection` |
20
+ | `spring-boot-engineer` | agent | Implements a Spring Boot feature end to end |
21
+ | `spring-reviewer` | agent | Framework-aware review: layering, transactions, JPA, configuration, test slices |
22
+ | `spring-security` | agent | Security pass on a Spring Security change |
23
+ | `kubernetes-specialist` | agent | Kubernetes manifests, probes, resources and rollout for a Spring Boot service |
24
+ | `docker-expert` | agent | Dockerfile, layered or native image for a Spring Boot service |
25
+ | `devops-engineer` | agent | Build and delivery pipeline, on whatever CI the project already uses |
26
+
27
+ The kit is adapted from [piomin/claude-ai-spring-boot](https://github.com/piomin/claude-ai-spring-boot) by Piotr Mińkowski (Apache-2.0), rewritten and condensed for Boot 4. Each of its skill folders ships `LICENSE-piomin-spring-boot.txt`, and each agent carries a credit line.
28
+
29
+ ### Switching kits on and off
30
+
31
+ ```bash
32
+ geneseed build --emit claude-global --kits spring-boot
33
+ geneseed build --emit claude-global --kits none
34
+ ```
35
+
36
+ `--kits` takes a comma-separated list. `none` and an empty value both mean no kits. An unknown kit name is refused with exit code 2, and the error lists the kits this Geneseed ships. `none` is reserved and can never name a kit. The flag's row is in the [CLI reference](../reference/cli.md), under `build`.
37
+
38
+ A build does exactly what its flags say. A plain `build` without `--kits` therefore turns off a kit the install has on. Before it does, it warns: `this install has kit <k>; this build turns it off — pass --kits <k> to keep it`.
39
+
40
+ The setup wizard asks one question listing the kits, with the install's current kits as the default. It asks it only when at least one kit exists.
41
+
42
+ ### What an install records
43
+
44
+ An install with a kit on records it in its instructions file, under the skill section: `Active kits: spring-boot`. The line is written only when at least one kit is on. A missing line means no kits, so an install built before kits existed reads as having none.
45
+
46
+ `geneseed rebuild-all`, `geneseed update` and `geneseed migrate` read the line back and rebuild with the same kits. `geneseed diff` and the web console read it too, so they compare against or rebuild that same set. `geneseed validate` does not read it: pass `--kits` to dry-run a kit build. A plain `build` does not read it; it only warns, as above. If the line names a kit this Geneseed no longer ships, the rebuild drops that kit with a notice and keeps the others. It never fails `rebuild-all`. A retired kit name is never reused for a new kit.
47
+
48
+ `geneseed status` shows `kits: spring-boot` on the install's row, and `status --json` carries the same list.
49
+
50
+ In the web console, **Installs → Kits** (`#/installs/kits`) lists every install (disabled ones read-only) with one switch per kit. Toggling stages the set; **Apply** asks for confirmation, then rebuilds that one install with exactly that set as a console job (replace, not replay). Files you edited in a kit that is now off are kept, and the row names them so you can delete them by hand.
51
+
52
+ ### What a kit adds to an install
53
+
54
+ - **Its skills and agents.** They are written like any core skill or agent. The members of a kit that is off are not written at all.
55
+ - **A Kits block in the instructions file**, after the doctrine layer. It holds each active kit's rules, the same text at either [footprint](footprint.md).
56
+ - **Its catalogue rows**, as the last rows of the skills table and of the agents table, only where the host does not list that kind natively. The plain bundle (`--emit files`) gets both, Bob gets the agent rows (it lists skills natively), and Claude Code, OpenClaude and OpenCode get none: their instructions file keeps its pointer.
57
+
58
+ `--exclude-skills` still applies: it can drop one kit skill while the rest of the kit stays on, and the exclusion wins. A kit's links to its own skills do not stop that, but a kit file's link to a core skill still protects that core skill from `--exclude-skills`, as any link from the harness does. Kit agents cannot be left out one by one. See [Exclusions](exclusions.md).
59
+
60
+ ### Turning a kit off
61
+
62
+ Rebuild with `--kits none`, or with a list that leaves the kit out. The kit's files are removed from the install, its block and catalogue rows go, and the `Active kits:` line goes when no kit is left.
63
+
64
+ A kit file you edited is kept, as with any file Geneseed wrote and you changed. `geneseed status` names it on a row of its own, `kept from inactive kit <kit>: <files>`, and `status --json` lists it under `keptKitFiles`. Delete it by hand if you no longer want it.
65
+
66
+ ### Kits are not doctrine packs
67
+
68
+ A [doctrine pack](rules.md) is part of the constitution. Kits are not:
69
+
70
+ - A kit's rules are plain text. They have no rule ids, cannot be excluded one at a time with `--exclude-rules`, and have no row in the hook ledger. No hook gate enforces them.
71
+ - A kit is not a pack. A missing `Active packs:` line reads as every pack, but a missing `Active kits:` line reads as no kits, so an old install never turns a kit on by accident.
72
+
73
+ ### What a kit costs
74
+
75
+ - **Hooks:** nothing. The hook binary does not read kits, so a kit adds no time to a tool call.
76
+ - **The instructions file:** the Kits block. Each kit has its own word budget for its rules, set in its manifest and checked by `doctor`, so a kit never eats into the core ceiling.
77
+ - **The skill listing:** one line per kit skill, every session, while the kit is on. That is the same price as a core skill.
78
+
79
+ The cost of authoring one, file by file, is in [Extending](../extending.md#3bis--adding-a-kit).
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 23
5
+ order: 24
6
6
  title: "LOOP.md, trailers and the loop branch"
7
7
  description: "The loop's state file, the trailers on every commit, the loop/* branch exemption, and the console's view."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 18
5
+ order: 19
6
6
  title: "Bricks and their origins"
7
7
  description: "What a brick is, how templates wire bricks, and how project and global bricks override shipped ones."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 22
5
+ order: 23
6
6
  title: "Contracts and ignored deletions"
7
7
  description: "The contract globs that always raise risk, the deletions a template ignores, and the glob dialect."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 21
5
+ order: 22
6
6
  title: "Human gates"
7
7
  description: "Bricks that hold for a person whatever the score, and how ok, no and amend answer them."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 19
5
+ order: 20
6
6
  title: "Notes and rules"
7
7
  description: "What survives an iteration in LOOP.md's notes, and the rules a template hands every brick."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 20
5
+ order: 21
6
6
  title: "Risk, trust and validation levels"
7
7
  description: "How a loop scores a change, the trust presets, and the silent, soft and blocking levels."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 17
5
+ order: 18
6
6
  title: "Loop templates"
7
7
  description: "Every shipped loop template, what it is for, and the notes that matter when you pick one."
8
8
  ---
@@ -2,7 +2,7 @@
2
2
  group: concepts
3
3
  kind: "concept"
4
4
  section: "Loops"
5
- order: 16
5
+ order: 17
6
6
  title: "Loops"
7
7
  link: {"hash":"#/docs/loop-actions","label":"The geneseed loop actions →"}
8
8
  description: "What a loop is, the ring of bricks it runs, and how it differs from foreman mode."
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  group: concepts
3
- order: 13
3
+ order: 14
4
4
  title: "Code intelligence (LSP, OpenCode)"
5
5
  kind: "concept"
6
6
  section: "Configuration"
package/docs/extending.md CHANGED
@@ -389,10 +389,31 @@ emit leaves the prior install intact.
389
389
  never fires. Hand-run, spends model calls on your own login, not part of the suite.
390
390
 
391
391
  **A vendored skill folder** (`src/skills/<name>/SKILL.md`) is a different shape: add it to
392
- `VENDORED_SKILL_DIRS` (`js/hosts/native.mjs:37` — the literal is frozen in
393
- `tests/unit/authoring_gates.test.mjs:312`), write a `VENDOR.md` carrying `**Upstream:**`,
394
- `**Commit:**` (a 40-hex commit, never a moving branch) and `**License:**`, add the registry row —
395
- and **skip steps 2, 4, 6, 7**: the flat-spec globs only see `*.md` at the top level.
392
+ `VENDORED_SKILL_DIRS` (`js/hosts/native.mjs:48` — the literal is frozen in
393
+ `tests/unit/authoring_gates.test.mjs:421`), add a `src/upstreams.json` entry (`repo`, `commit` —
394
+ a 40-hex commit, never a moving branch — `author`, `license`, and `licenseFile`, a path under
395
+ `src/` or `null`; an optional `"frozen": true` marks text left at its pin on purpose: the weekly drift job skips it, `creditProblems` still validates it), write a `VENDOR.md` carrying `**Upstream key:** <that key>` and a
396
+ `{{CREDIT:<key>:<upstream path>}}` line, which renders the attribution into every install (the
397
+ folder ships its upstream's own LICENSE, so it gets no `LICENSE-<key>.txt` copy; a first-party
398
+ folder writes `**Upstream:** this repository` and a `**License:**` instead, and has no entry),
399
+ add the registry row — and **skip steps 2, 4, 6, 7**: the flat-spec globs only see `*.md` at the
400
+ top level. The doctor (`vendorPinProblems`, `creditProblems`) refuses a key with no entry, an
401
+ entry nothing credits, an entry missing `repo`/`author`/`license`, a bad commit, and a licence
402
+ file that is missing or outside `src/`.
403
+
404
+ **Porting a flat skill or an agent from another repository** is steps 1–9 plus a
405
+ `{{CREDIT:}}` token. Only an upstream not yet in `src/upstreams.json` needs a new entry, as
406
+ above, with the upstream's licence text at `src/licenses/<key>.txt` as its `licenseFile` (never
407
+ `null` here; two keys from one repo may share a file); a port from a listed upstream reuses its
408
+ key. Put
409
+ `{{CREDIT:<key>:<upstream path>}}` on its own line under the purpose blockquote; the render turns
410
+ it into `` *Adapted from [repo](url) (`path`) by author — license, modified.* ``, and for a skill the
411
+ emit adds `skills/<name>/LICENSE-<key>.txt` (an agent is one file, so it gets the line only). A
412
+ skill drawn from two upstreams carries two tokens, as `debug` does. A new entry also needs a
413
+ `` `<key>` `` bullet under README `## 🙏 Credits` — `proseMirrorProblems` gates that list both
414
+ ways. Do it in
415
+ the commit that brings the text in: no gate can find an unmarked port afterwards, because nothing
416
+ distinguishes it from first-party text.
396
417
 
397
418
  Two traps worth knowing before you write the spec:
398
419
 
@@ -435,6 +456,89 @@ because a code span is not a link. Measured on 2026-10-08, with the neutral them
435
456
  footprint: debug's SKILL.md went from 1.64k to 1.18k tokens and rule's from 2.33k to 1.83k
436
457
  (chars/4).
437
458
 
459
+ ---
460
+
461
+ ## 3bis — Adding a KIT
462
+
463
+ A kit is an opt-in bundle of skills, agents and rules for one stack, off unless an install names
464
+ it with `--kits` ([Kits](concepts/kits.md)). Nothing in the mechanism is specific to one stack.
465
+
466
+ **The manifest is the sole writer of membership.** `src/kits/<kit>.json`:
467
+
468
+ ```json
469
+ {
470
+ "description": "One line: what the kit is for.",
471
+ "skills": ["<skill>"],
472
+ "agents": ["<agent>"],
473
+ "rules": "kits/<kit>/rules.md",
474
+ "rulesWordBudget": 300,
475
+ "catalogue": {
476
+ "skills": ["| [<skill>]({{DIR_SKILLS}}/<skill>.md) | when to reach for it |"],
477
+ "agents": ["| [<agent>]({{DIR_AGENTS}}/<agent>.md) | when to delegate to it |"]
478
+ }
479
+ }
480
+ ```
481
+
482
+ - `banned` (optional): strings no member file may contain, in the skill and agent specs, a skill's side files (flat, `skills/<m>/*.md`) and the kit's `rules.md`; `doctor` refuses each hit, naming file and line. A line carrying `<!-- banned: ok -->` is exempt (a rule that names the construct in order to forbid it).
483
+
484
+ `loadKits` (`js/build/source.mjs`) reads every `src/kits/*.json`; `src/kits/` itself never ships
485
+ (`NOT_BUNDLED` in `js/build/render.mjs`). The members stay **flat** in `src/skills/` and
486
+ `src/agents/`, never in a `src/kits/<kit>/skills/` folder: every host's native layer accepts only
487
+ `skills/<x>.md` and `agents/<x>.md` and would drop a nested file silently, and every check that
488
+ enumerates skills would miss it. `src/kits/<kit>/` holds only `rules.md`.
489
+
490
+ **Per-kit cost:**
491
+
492
+ 1. **Each member is a skill or an agent first.** §3 applies to it, with these differences:
493
+ - **No row in `AGENT.md.tmpl`.** Its catalogue row, a table row of the same shape, goes in
494
+ the manifest's `catalogue`. With the kit on, the render inserts the skill rows as the last
495
+ rows of the §4 skills table (before the folder-skills list) and the agent rows at the end of
496
+ the §3 agents table, on a host that does not catalogue that kind natively.
497
+ `countTableProblems` checks the template's links and the kits' catalogue rows together
498
+ against the flat specs, and `kitProblems` refuses a catalogue row for a non-member.
499
+ - **No README badge and no `SHIPPED.md` count.** Core counts exclude kit members, so
500
+ skip §3 steps 6 and 7.
501
+ - **Still** a `DESC_<NAME>` in every theme, a `SKILL_CLASS` entry for a skill (the members are
502
+ flat, so `srcStems('skills')` sees them), and a `registry.json` row.
503
+ - **An agent's web glyph, required:** an entry in `GLYPHS`
504
+ (`web/src/components/AgentGlyph.jsx`), then rebuild and commit `web/dist`.
505
+ `web/src/__tests__/agentGlyph.test.jsx` gates every `src/agents/*.md`, kit members included.
506
+ 2. **The manifest**, above.
507
+ 3. **`src/kits/<kit>/rules.md`**, the kit's rules. The render puts it in the carrier's Kits block,
508
+ after §2, only while the kit is on. It is plain text, not doctrine machinery: no rule ids, no
509
+ per-rule exclusion, no hook-ledger row, and the same text at either footprint.
510
+ `doctor` holds it to the manifest's `rulesWordBudget` (300 when unset).
511
+ 4. **Membership and links.** `doctor` (`kitProblems`) refuses a member listed in two kits, a
512
+ core skill listed in a kit, a member with no file or no registry row, a core file that links
513
+ to a kit member (it would dangle in an install without the kit), and a kit file that links to
514
+ another kit's member. Links run kit → core only. A kit's links to its own members do not
515
+ protect them from `--exclude-skills`; its link to a core skill still protects that skill.
516
+ 5. **The setup wizard.** It asks its kits question only when at least one kit exists. The shipped
517
+ `spring-boot` kit therefore added one answer line to the stdin strings in
518
+ `tests/unit/wizard.test.mjs`, and the `wizard-no-kit` job holds that the question is skipped on a
519
+ tree with no kit.
520
+ 6. **Golden cells**: the matrix (`tests/helpers/matrix/emit.*.json`) carries kit on for `claude` and
521
+ `opencode`, the kit-off baseline (`kits: []`, spelled `--kits none`), and three deletion cells
522
+ (kit on to off). One deletion cell sets `edit` (a map of sandbox path to appended text) so a member
523
+ is the user's own before the kit goes off, and `sovereign` excuses it: the prune keeps it (R6).
524
+
525
+ **What a kit costs an install** — what to weigh before adding one:
526
+
527
+ - **Hooks: zero.** The hook binary never reads a kit, so a kit adds no time to any tool call.
528
+ - **The carrier:** the Kits block. Each kit is measured against its own
529
+ `rulesWordBudget`, not the core carrier ceiling, which stays measured on core alone.
530
+ - **The skill listing:** one description per kit skill, every session, while the kit is on.
531
+ - **An install without the kit: nothing.** Its members are not written, and a default build
532
+ stays byte-identical, with no `Active kits:` line.
533
+
534
+ **Retiring a kit** puts its id in `RETIRED_KIT_IDS` (`js/build/source.mjs`), with the reason
535
+ beside it. An install whose `Active kits:` line names it drops it wherever that line is read back
536
+ (`rebuild-all`, `update`, `migrate`, `diff`, the web console), without failing; `rebuild-all`, `update` and `migrate` print a notice. `doctor` refuses a retired id on a new kit. `none` is
537
+ already there: `--kits none` means no kits.
538
+
539
+ **Kits are not packs.** Never add a kit to `PACK_ORDER`. A missing `Active packs:` line reads as
540
+ every pack, so a kit there would switch itself on in every install built before it existed.
541
+
438
542
 
439
543
  ---
440
544
 
@@ -12,7 +12,7 @@ description: "The build command to run in a script, a CI job, or a shell with no
12
12
  geneseed build --emit opencode-global --theme neutral
13
13
  ```
14
14
 
15
- `geneseed build` takes the same flags as the generator `geneseed-build`. `--emit` picks the host and its scope: `files`, `opencode`, `opencode-global`, `claude`, `claude-global`, `bob`, `bob-global`, `openclaude` or `openclaude-global`. A `-global` emit writes into the tool's own config directory. A project emit writes into the repo you name with `--out <repo> --root <repo>`. All the other flags (`--theme`, `--footprint`, `--posture`, `--mode`, `--trust`, `--doctrines`, `--exclude-rules`) are covered in [Choose your setup](choose-your-setup.md) and the two pages after it. `--exclude-skills` leaves skills you never use out of the install; see [Exclusions](../concepts/exclusions.md).
15
+ `geneseed build` takes the same flags as the generator `geneseed-build`. `--emit` picks the host and its scope: `files`, `opencode`, `opencode-global`, `claude`, `claude-global`, `bob`, `bob-global`, `openclaude` or `openclaude-global`. A `-global` emit writes into the tool's own config directory. A project emit writes into the repo you name with `--out <repo> --root <repo>`. All the other flags (`--theme`, `--footprint`, `--posture`, `--mode`, `--trust`, `--doctrines`, `--exclude-rules`) are covered in [Choose your setup](choose-your-setup.md) and the two pages after it. `--exclude-skills` leaves skills you never use out of the install; see [Exclusions](../concepts/exclusions.md). `--kits <kit>` switches on an opt-in kit, and `--kits none` switches every kit off; see [Kits](../concepts/kits.md).
16
16
 
17
17
  To test a build before writing anything, use `geneseed validate` with the same flags. See [Verify](verify.md).
18
18
 
@@ -28,6 +28,7 @@ Every word Geneseed uses that you would not find in a general programming glossa
28
28
  | Posture | — | the relationship register the agent works in (peer, mentor, expert, assistant, artisan) | pairing style |
29
29
  | Mode | — | how work gets executed — direct (the agent works every task itself) or foreman (substantial tasks spawn an isolated pipeline) | solo vs. delegating to a sub-team |
30
30
  | Footprint | — | how much of the Rules loads inline each turn (full vs lean) | eager vs. lazy loading |
31
+ | Kit | — | an opt-in bundle of skills, agents and rules for one stack, switched on per install with `--kits`; not a doctrine pack | an optional dependency group (`pkg[extra]`) |
31
32
  | Loop | — | a requirement worked as small, validated, committed iterations on their own branch, driven by `geneseed loop` | a state machine over a ticket's steps |
32
33
  | Iteration | — | one cycle of a loop back to its head; closes into its own commit | one sprint-of-one through a checklist |
33
34
  | Brick | — | one node of a loop graph: a prompt for one agent or skill, reporting one of a declared set of outcomes | a step in a runbook |
package/docs/web-ui.md CHANGED
@@ -194,6 +194,10 @@ streams the output of background jobs and keeps their history across reloads.
194
194
  card (shown once a global install exists) manages sovereign repos — folders where every
195
195
  global harness goes dormant (hooks stay silent, the global preamble never loads);
196
196
  add or remove one here, the web mirror of `harness exclude add|remove|list`.
197
+ - **Kits** (`#/installs/kits`) — every install (disabled ones read-only) as a card with one switch per kit.
198
+ Toggling only stages the set; **Apply** asks first, then rebuilds that install with
199
+ exactly that set as a console job. Files you edited in a kit that is now off are kept,
200
+ and the row lists them.
197
201
  - **Settings** (`#/settings`) — a **Console direction** picker (the visual flavour of the
198
202
  console: Cultivar, Operator, Matrix or Atlas) with a **Dashboard layout** control (Auto follows each theme's designed Status
199
203
  lens; Cultivar / Greenhouse / Operator / Journal force one regardless of skin), the
@@ -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.9.4"
5
+ "version": "4.10.0"
6
6
  }
package/js/build/args.mjs CHANGED
@@ -11,7 +11,8 @@ import { parseArgs as nodeParseArgs } from 'node:util';
11
11
  import { withPlatformNewlines } from '../lib/fs.mjs';
12
12
  import path from 'node:path';
13
13
  import {
14
- CONFIG, discoverNames, GATE_SWITCHES, PACK_ORDER, knownRuleIds, knownSkillIds, linkedSkillIds, resolveSkillNames,
14
+ CONFIG, discoverNames, GATE_SWITCHES, PACK_ORDER, SRC, knownRuleIds, knownSkillIds, linkedSkillIds, loadKits,
15
+ resolveSkillNames, skillAliases,
15
16
  } from './source.mjs';
16
17
  // The loop engine's trust presets — the `--trust` choices. `score.mjs` imports nothing, which is
17
18
  // what lets it sit inside this module's `child_process` ban.
@@ -32,7 +33,7 @@ const EMITS = ['files', 'opencode', 'opencode-global', 'claude', 'claude-global'
32
33
  export function configDefaults() {
33
34
  const d = {
34
35
  theme: 'neutral', posture: 'peer', mode: 'direct', trust: DEFAULT_PRESET,
35
- doctrines: [...PACK_ORDER], excludeRules: [], excludeSkills: [],
36
+ doctrines: [...PACK_ORDER], excludeRules: [], excludeSkills: [], kits: [],
36
37
  };
37
38
  if (!existsSync(CONFIG)) return d;
38
39
  try {
@@ -96,6 +97,7 @@ export function configDefaults() {
96
97
  const VALUED = {
97
98
  '--theme': 'theme', '--posture': 'posture', '--mode': 'mode', '--trust': 'trust',
98
99
  '--doctrines': 'doctrines', '--exclude-rules': 'excludeRules', '--exclude-skills': 'excludeSkills',
100
+ '--kits': 'kits',
99
101
  '--out': 'out', '--target': 'out', '--emit': 'emit',
100
102
  '--footprint': 'footprint', '--root': 'root', '--config-dir': 'cfgDir',
101
103
  };
@@ -196,13 +198,13 @@ function parseExcludeRules(value) {
196
198
  * through `resolveSkillNames` and leave one stderr line each; the linked refusal stays for a
197
199
  * name typed as itself.
198
200
  */
199
- function parseExcludeSkills(value) {
201
+ export function parseExcludeSkills(value, src = SRC) {
200
202
  const s = value.trim();
201
203
  if (s === 'none' || s === '') return [];
202
- const known = knownSkillIds();
203
- const linked = linkedSkillIds();
204
+ const known = knownSkillIds(src);
205
+ const linked = linkedSkillIds(src);
204
206
  const names = s.split(',').map((x) => x.trim()).filter(Boolean);
205
- const { ids, notices, unknown } = resolveSkillNames(names, { known, linked });
207
+ const { ids, notices, unknown } = resolveSkillNames(names, { known, linked, aliases: skillAliases(src) });
206
208
  if (unknown.length) {
207
209
  die(2, `argument --exclude-skills: unknown skill '${unknown[0]}' (choose from `
208
210
  + `${known.filter((k) => !linked.includes(k)).join(', ')}, or 'none')`);
@@ -216,6 +218,23 @@ function parseExcludeSkills(value) {
216
218
  return ids;
217
219
  }
218
220
 
221
+ /**
222
+ * `--kits a,b` / `--kits none` -> the cfg's kit list, sorted and deduped for the same marker
223
+ * stability as `--exclude-skills`. `none` and '' mean no kits; an unknown kit is refused, naming
224
+ * the known ones (spec R1). No `harness.config.json` key feeds it, for `excludeSkills`' reason.
225
+ */
226
+ export function parseKits(value, known = [...loadKits().keys()]) {
227
+ const s = value.trim();
228
+ if (s === 'none' || s === '') return [];
229
+ const names = [...new Set(s.split(',').map((x) => x.trim()).filter(Boolean))].sort();
230
+ const unknown = names.find((n) => !known.includes(n));
231
+ if (unknown) {
232
+ const choices = known.length ? `${known.join(', ')}, or 'none'` : "'none'";
233
+ die(2, `argument --kits: unknown kit '${unknown}' (choose from ${choices})`);
234
+ }
235
+ return names;
236
+ }
237
+
219
238
  /**
220
239
  * `--doctrines craft,rigor` / `--doctrines none` -> the cfg's pack array.
221
240
  *
@@ -313,6 +332,7 @@ export function parseArgs(argv, defaults) {
313
332
  doctrines: defaults.doctrines,
314
333
  excludeRules: defaults.excludeRules,
315
334
  excludeSkills: defaults.excludeSkills ?? [],
335
+ kits: defaults.kits ?? [],
316
336
  out: 'Harness', emit: 'files', footprint: 'lean', root: null,
317
337
  syncThemes: false, validateOnly: false, verbose: false,
318
338
  ...Object.fromEntries(GATE_SWITCHES.map((g) => [g.arg, false])),
@@ -356,6 +376,7 @@ export function parseArgs(argv, defaults) {
356
376
  if (typeof args.doctrines === 'string') args.doctrines = parseDoctrines(args.doctrines);
357
377
  if (typeof args.excludeRules === 'string') args.excludeRules = parseExcludeRules(args.excludeRules);
358
378
  if (typeof args.excludeSkills === 'string') args.excludeSkills = parseExcludeSkills(args.excludeSkills);
379
+ if (typeof args.kits === 'string') args.kits = parseKits(args.kits);
359
380
  return args;
360
381
  }
361
382
 
@@ -65,6 +65,7 @@ import {
65
65
  // registry to find every install it must re-emit, and a CLI verb may not reach into a driver
66
66
  // for a reader. See js/inspect/registry.mjs.
67
67
  import { registryRecord, registryRoots } from '../inspect/registry.mjs';
68
+ import { kitsOfDir } from '../hosts/installs.mjs';
68
69
 
69
70
  // P2. `--sync-themes` crossed into its own module rather than into this file: it is 90 lines
70
71
  // of textual surgery over committed files with a corpus of its own, and it is the half of the
@@ -651,12 +652,12 @@ const PROJECT_EMITS = {
651
652
  * carry straight past the parameter default.
652
653
  */
653
654
  function axesCfg({
654
- posture, mode, trust, doctrines, excludeRules, excludeSkills, ...gates
655
+ posture, mode, trust, doctrines, excludeRules, excludeSkills, kits, ...gates
655
656
  }) {
656
657
  return makeCfg({
657
658
  ...(posture ? { posture } : {}), ...(mode ? { mode } : {}), ...(trust ? { trust } : {}),
658
659
  ...(doctrines ? { doctrines } : {}), ...(excludeRules ? { excludeRules } : {}),
659
- ...(excludeSkills ? { excludeSkills } : {}),
660
+ ...(excludeSkills ? { excludeSkills } : {}), ...(kits ? { kits } : {}),
660
661
  // The gate switches (`GATE_SWITCHES` keys): only an explicit `false` disarms; `null` (no
661
662
  // opinion) keeps the gate.
662
663
  ...Object.fromEntries(GATE_SWITCHES.filter((g) => gates[g.key] === false).map((g) => [g.arg, true])),
@@ -665,7 +666,7 @@ function axesCfg({
665
666
 
666
667
  export function emitProjectInto(host, {
667
668
  theme, out, root, footprint = 'full', posture = null, mode = null, trust = null,
668
- doctrines = null, excludeRules = null, excludeSkills = null, ...gates
669
+ doctrines = null, excludeRules = null, excludeSkills = null, kits = null, ...gates
669
670
  }) {
670
671
  const emit = PROJECT_EMITS[host];
671
672
  // The axes ride the same rail into every `*Into`, and for the same reason: a drift reader
@@ -674,7 +675,7 @@ export function emitProjectInto(host, {
674
675
  // checked an all-packs render nobody asked for.
675
676
  return withPlatformNewlines(() => emit(
676
677
  axesCfg({
677
- posture, mode, trust, doctrines, excludeRules, excludeSkills, ...gates }),
678
+ posture, mode, trust, doctrines, excludeRules, excludeSkills, kits, ...gates }),
678
679
  { theme, footprint, root }, out,
679
680
  ));
680
681
  }
@@ -689,16 +690,16 @@ export function emitProjectInto(host, {
689
690
  */
690
691
  export function buildInto({
691
692
  theme, out, footprint = 'lean', posture = null, mode = null, trust = null, doctrines = null,
692
- excludeRules = null, excludeSkills = null, ...gates
693
+ excludeRules = null, excludeSkills = null, kits = null, ...gates
693
694
  }) {
694
695
  return withPlatformNewlines(() => build(axesCfg({
695
- posture, mode, trust, doctrines, excludeRules, excludeSkills, ...gates }),
696
+ posture, mode, trust, doctrines, excludeRules, excludeSkills, kits, ...gates }),
696
697
  theme, out, { footprint, nativeCatalog: false }));
697
698
  }
698
699
 
699
700
  export function emitGlobalInto(host, {
700
701
  theme, out, cfgDir, footprint, posture = null, mode = null, doctrines = null, trust = null,
701
- excludeRules = null, excludeSkills = null, ...gates
702
+ excludeRules = null, excludeSkills = null, kits = null, ...gates
702
703
  }) {
703
704
  // `build.HOSTS.get(host, build.HOSTS["opencode"])` — an unknown host falls back rather than
704
705
  // raising, because the host comes from a marker file a user can edit.
@@ -715,7 +716,7 @@ export function emitGlobalInto(host, {
715
716
  // the same scar the footprint left in `diffCollect`, one axis over each time.
716
717
  return withPlatformNewlines(() => emit(
717
718
  axesCfg({
718
- posture, mode, trust, doctrines, excludeRules, excludeSkills, ...gates }),
719
+ posture, mode, trust, doctrines, excludeRules, excludeSkills, kits, ...gates }),
719
720
  { theme, footprint, root: null, cfgDir }, out,
720
721
  ));
721
722
  }
@@ -749,6 +750,25 @@ export function main(argv) {
749
750
  });
750
751
  }
751
752
 
753
+ /**
754
+ * Spec B3: a build does what its flags say, so one without `--kits` turns off a kit the target
755
+ * install has on. Said BEFORE the emit rewrites the marker. The target is the emit's own: the
756
+ * config dir for a `-global` emit, `out` otherwise. `kitsOfDir` has already dropped a kit this
757
+ * checkout no longer ships, so the advice never names one `--kits` would refuse. Best-effort.
758
+ */
759
+ function warnDroppedKits(args, out) {
760
+ try {
761
+ const global = args.emit.endsWith('-global');
762
+ const host = global ? args.emit.slice(0, -'-global'.length) : (args.emit === 'files' ? null : args.emit);
763
+ const target = global ? (args.cfgDir ?? HOSTS.find((h) => h.host === host).configDir()) : out;
764
+ for (const k of kitsOfDir(target, host).filter((x) => !args.kits.includes(x))) {
765
+ process.stderr.write(`[geneseed] WARN: this install has kit ${k}; this build turns it off — `
766
+ + `pass --kits ${k} to keep it
767
+ `);
768
+ }
769
+ } catch { /* a warning never fails a build */ }
770
+ }
771
+
752
772
  function run(argv) {
753
773
  const args = parseArgs(argv, configDefaults());
754
774
 
@@ -783,6 +803,7 @@ function run(argv) {
783
803
  args.cfgDir = resolveOut(args.cfgDir);
784
804
  }
785
805
  const cfg = makeCfg(args);
806
+ warnDroppedKits(args, out);
786
807
 
787
808
  // The marker directory is the emit's TARGET, which for a global emit is the config dir it
788
809
  // just rendered into and not `--out` at all — so the emit returns it rather than the