geneseed 4.10.0 → 4.14.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 (139) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +4 -3
  3. package/SHIPPED.md +1 -1
  4. package/docs/concepts/kits.md +14 -2
  5. package/docs/concepts/skills.md +1 -1
  6. package/docs/extending.md +3 -4
  7. package/docs/guides/run-anywhere.md +2 -2
  8. package/docs/guides/upgrade.md +2 -2
  9. package/docs/reference/troubleshoot-install.md +1 -1
  10. package/docs/web-ui.md +6 -1
  11. package/harness.config.json +1 -1
  12. package/js/hosts/link.mjs +88 -1
  13. package/js/inspect/checks-build.mjs +8 -2
  14. package/js/inspect/cost.mjs +1 -1
  15. package/js/inspect/diff.mjs +53 -7
  16. package/js/inspect/inventory.mjs +1 -0
  17. package/js/lib/prompt.mjs +19 -2
  18. package/js/maintain/setup.mjs +92 -28
  19. package/js/maintain/update.mjs +2 -2
  20. package/js/web/actions.mjs +8 -0
  21. package/js/web/api.mjs +39 -17
  22. package/js/web/catalog.mjs +17 -2
  23. package/js/web/handler.mjs +1 -0
  24. package/js/web/server.mjs +11 -5
  25. package/js/web/state.mjs +38 -3
  26. package/package.json +1 -1
  27. package/registry.json +28 -0
  28. package/src/AGENT.md.tmpl +5 -5
  29. package/src/agents/_self-improvement.md +11 -0
  30. package/src/agents/_template.md +1 -8
  31. package/src/agents/advocate.md +1 -8
  32. package/src/agents/architect.md +1 -8
  33. package/src/agents/developer.md +1 -8
  34. package/src/agents/devops-engineer.md +1 -9
  35. package/src/agents/docker-expert.md +1 -9
  36. package/src/agents/docs.md +1 -8
  37. package/src/agents/empiricist.md +1 -8
  38. package/src/agents/explorer.md +1 -8
  39. package/src/agents/framer.md +1 -8
  40. package/src/agents/historian.md +1 -8
  41. package/src/agents/kubernetes-specialist.md +1 -9
  42. package/src/agents/memory-curator.md +1 -8
  43. package/src/agents/operator.md +1 -8
  44. package/src/agents/performance.md +1 -8
  45. package/src/agents/pragmatist.md +1 -8
  46. package/src/agents/react-engineer.md +38 -0
  47. package/src/agents/react-reviewer.md +42 -0
  48. package/src/agents/researcher.md +5 -10
  49. package/src/agents/reviewer.md +1 -8
  50. package/src/agents/security.md +1 -8
  51. package/src/agents/skeptic.md +1 -8
  52. package/src/agents/spring-boot-engineer.md +1 -9
  53. package/src/agents/spring-reviewer.md +1 -9
  54. package/src/agents/spring-security.md +1 -9
  55. package/src/agents/steward.md +1 -8
  56. package/src/agents/tester.md +1 -8
  57. package/src/agents/user-advocate.md +1 -8
  58. package/src/agents/visionary.md +1 -8
  59. package/src/kits/react/rules.md +13 -0
  60. package/src/kits/react.json +18 -0
  61. package/src/licenses/everything-claude-code.txt +21 -0
  62. package/src/skills/_self-improvement.md +22 -5
  63. package/src/skills/brainstorm.md +7 -2
  64. package/src/skills/codebase-design.md +6 -2
  65. package/src/skills/debug.md +1 -1
  66. package/src/skills/develop.md +4 -2
  67. package/src/skills/handoff.md +1 -1
  68. package/src/skills/parallel-agents.md +3 -1
  69. package/src/skills/plan.md +5 -2
  70. package/src/skills/prototype.md +4 -2
  71. package/src/skills/react/a11y.md +33 -0
  72. package/src/skills/react/vite.md +35 -0
  73. package/src/skills/react/webpack.md +40 -0
  74. package/src/skills/react-testing.md +68 -0
  75. package/src/skills/react.md +63 -0
  76. package/src/skills/research.md +4 -2
  77. package/src/skills/rule.md +4 -2
  78. package/src/skills/ship.md +6 -4
  79. package/src/skills/skill-forge.md +10 -5
  80. package/src/skills/wayfinder.md +6 -2
  81. package/src/skills/wiki.md +18 -5
  82. package/src/upstreams.json +7 -0
  83. package/themes/_TEMPLATE.json +4 -0
  84. package/themes/biker.json +4 -0
  85. package/themes/commentator.json +4 -0
  86. package/themes/cyberpunk.json +4 -0
  87. package/themes/gamer.json +4 -0
  88. package/themes/imperial.json +4 -0
  89. package/themes/joker.json +4 -0
  90. package/themes/marvin.json +4 -0
  91. package/themes/mean.json +4 -0
  92. package/themes/military.json +4 -0
  93. package/themes/neutral.json +4 -0
  94. package/themes/pirate.json +4 -0
  95. package/themes/sports.json +4 -0
  96. package/themes/verstappen.json +4 -0
  97. package/themes/wizard.json +4 -0
  98. package/web/dist/assets/Activity-44rbWUQI.js +1 -0
  99. package/web/dist/assets/ActivityDetail-Dk9k893a.js +1 -0
  100. package/web/dist/assets/Dashboard-DCoUN60W.js +2 -0
  101. package/web/dist/assets/{Docs-DLVckkew.js → Docs-C_je2lRG.js} +3 -3
  102. package/web/dist/assets/FilterInput-CZREvrjX.js +1 -0
  103. package/web/dist/assets/GreenhouseView-BXAOIznW.js +2 -0
  104. package/web/dist/assets/Installs-DAXF403D.js +3 -0
  105. package/web/dist/assets/JournalView-DZ184wKB.js +1 -0
  106. package/web/dist/assets/KitApply-pjhvGha0.js +1 -0
  107. package/web/dist/assets/Kits-BCCKjSIY.js +1 -0
  108. package/web/dist/assets/{Laws-BkKnZOBI.js → Laws-BgBUpRkE.js} +1 -1
  109. package/web/dist/assets/Library-CW0Zl6RF.js +1 -0
  110. package/web/dist/assets/Loops-BvFOzjEw.js +2 -0
  111. package/web/dist/assets/{Markdown-iHvBrpRN.js → Markdown-zaXXXHz9.js} +13 -13
  112. package/web/dist/assets/OperatorHudView-Bs4unPNi.js +2 -0
  113. package/web/dist/assets/Personal-C23XaT-a.js +1 -0
  114. package/web/dist/assets/Tabs-b2XFu4OS.js +1 -0
  115. package/web/dist/assets/activity-Jw8XcPiP.js +1 -0
  116. package/web/dist/assets/{drawable-CgYi0Dtg.js → drawable-BERkKhx_.js} +1 -1
  117. package/web/dist/assets/index-C9R_bRw_.js +2 -0
  118. package/web/dist/assets/{index-BCJz4_8C.css → index-jYlWihN-.css} +1 -1
  119. package/web/dist/assets/{react-BOF6yL40.js → react-DlYQp10C.js} +1 -1
  120. package/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  121. package/web/dist/assets/sections-ChwaobfW.js +1 -0
  122. package/web/dist/assets/usePoll-bDFN-Hr6.js +1 -0
  123. package/web/dist/index.html +5 -4
  124. package/web/dist/assets/Activity-BjyJaf8e.js +0 -1
  125. package/web/dist/assets/ActivityDetail-Bo9nRDNV.js +0 -1
  126. package/web/dist/assets/Dashboard-BQJGU7vE.js +0 -2
  127. package/web/dist/assets/FilterInput-zLFXoSt6.js +0 -1
  128. package/web/dist/assets/GreenhouseView--DML7CW3.js +0 -2
  129. package/web/dist/assets/Installs-CxCiln1F.js +0 -3
  130. package/web/dist/assets/JournalView-ymgZleHi.js +0 -1
  131. package/web/dist/assets/Library-C8ercIZv.js +0 -1
  132. package/web/dist/assets/Loops-C95zhbFl.js +0 -2
  133. package/web/dist/assets/OperatorHudView-B9qwc3w3.js +0 -2
  134. package/web/dist/assets/Personal-DyR72AF-.js +0 -1
  135. package/web/dist/assets/Tabs-ChCr_HqV.js +0 -1
  136. package/web/dist/assets/activity-DvPxoS4Y.js +0 -1
  137. package/web/dist/assets/index-B32EvfDT.js +0 -2
  138. package/web/dist/assets/sections-BET3c1WT.js +0 -1
  139. package/web/dist/assets/usePoll-NSTzLOF4.js +0 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,91 @@ label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [4.14.0] — 2026-10-22
12
+
13
+ Minor: the React kit supports webpack projects as well as Vite.
14
+
15
+ ### Upgrade notes
16
+ - Run `geneseed rebuild-all` to get the new kit text; nothing changes on a machine until something re-emits.
17
+
18
+ ### Changed
19
+ - **The `react` kit supports webpack as well as Vite** (#368). The `react` skill reads the bundler from `package.json` and loads its side file: the new `react/webpack.md` (config, client-side routing with `historyApiFallback` and `publicPath`, `DefinePlugin` env values, a webpack build-failure table) or `react/vite.md`. The kit rules, both agents, the Jest configuration table in `react-testing` and every theme's descriptions now name both bundlers.
20
+
21
+ ## [4.13.0] — 2026-10-21
22
+
23
+ Minor: sharper ported skills, a wiki that keeps its history, and self-improvement notes that carry their evidence.
24
+
25
+ ### Upgrade notes
26
+ - Run `geneseed rebuild-all` to get the new skill and agent text; nothing changes on a machine until something re-emits. Notes already in `notebook/skill-notes/` and `notebook/agent-notes/` keep working as written; only new lines take the new format.
27
+
28
+ ### Changed
29
+ - **Self-improvement notes carry their evidence and their scope** (#366). A note is now one line, `- [skill|general] <correction> — seen: <what happened, against what was expected> (<date>)`, and an agent's `spec-feedback:` line takes the same shape. `seen:` is what a correction is judged by when `geneseed diff` hands it back for folding into `src/`, and pruning drops the lines without one first. `general` marks a correction that would fix other skills or agents too, so it belongs in a law, doctrine or memory rather than one file. The `geneseed diff` report explains both markers. Agents now share one self-improvement footer (`src/agents/_self-improvement.md`) instead of 29 copies; rendered agents differ only by the new wording.
30
+ - **The wiki writes history, not the present** (#365). Wiki claims are dated, code claims cite `owner/repo@commit`, and a correction supersedes a claim rather than rewriting it. A new report-only lint pass lists contradictions, orphans and overtaken claims without restructuring the wiki. Adapted from mattpocock/skills' chief-of-staff.
31
+ - **The mattpocock ports are sharpened against upstream `5beb35b`** (#365):
32
+ - `parallel-agents`: each slice branches off the integration tip when it is dispatched and merges the tip back before returning, so a later slice no longer misses its blocker's merged work. A whole-branch review follows.
33
+ - `codebase-design`: an interface is designed twice, and an ADR is not silently reopened.
34
+ - `research` and the `researcher` agent: one read in the source that owns a claim stands on its own; any other claim still needs two sources.
35
+ - `ship`: the docs check moves before the push it gates.
36
+ - `plan`, `wayfinder`, `prototype`, `develop`, `debug`, `rule`, `brainstorm`, `handoff` and `skill-forge` get smaller fixes: slice sizing, ticket graduation, mocking only at boundaries, checking for an existing guardrail before proposing one, and trigger pruning.
37
+ - New credits on `parallel-agents`, `skill-forge`, `brainstorm` and `wiki`.
38
+
39
+ ## [4.12.0] — 2026-10-21
40
+
41
+ Minor: a second opt-in kit, `react`, and the setup wizard asks about each kit on its own.
42
+
43
+ ### Upgrade notes
44
+ - Nothing to rebuild unless you want the new kit. Default installs are unchanged: no kit is on. To turn it on, run `geneseed build --emit <target> --kits react` (or `--kits react,spring-boot` for both), or use **Installs → Kits** in the web console.
45
+
46
+ ### Added
47
+ - **The `react` kit** (#362), for React 18 or 19 apps built with Vite, in TypeScript or JavaScript, tested with Jest and React Testing Library. Skills `react` (with side files for Vite and accessibility) and `react-testing`; agents `react-engineer` and `react-reviewer` (read-only); kit rules, and a banned-API gate that keeps `ReactDOM.render(`, `findDOMNode`, Enzyme, `react-test-renderer` and `@testing-library/react-hooks` out of kit files. Adapted from affaan-m/everything-claude-code (MIT), rewritten and condensed for Vite and Jest, with no Server Components material.
48
+
49
+ ### Changed
50
+ - **The setup wizard asks one yes/no per kit** (#362). A single pick could not turn on two kits at once.
51
+
52
+ ### Repository
53
+ Not in the package; how Geneseed itself is built and checked.
54
+ - CI's golden cells run as two parallel jobs per platform, emit and CLI (#361). On Windows that leg drops from about 7 minutes in sequence to about 4. The unit suite runs at `--test-concurrency=8` on Linux and in the publish job; Windows keeps the default, where the flag bought nothing. If branch protection names the old `node-cells (<os>)` checks, they now report as `node-cells (<os>, emit)` and `node-cells (<os>, cli)`.
55
+ - The unit suite drops three repeated full-cost runs (#363): two update-flow arms use a doctor stand-in (the real rejection and exact rollback stay in `update_git`), the packaging test counts themes in process instead of a second `doctor --all`, and `--no-deletion-gate` replays only the 67 real-loss rows. 151 s to 139 s locally, same 2012 tests.
56
+
57
+ ## [4.11.2] — 2026-10-21
58
+
59
+ Patch: a clone install on macOS or Linux puts the `geneseed` command on your PATH.
60
+
61
+ ### Upgrade notes
62
+ - Nothing to rebuild. If `geneseed` is not found in a new terminal, run `./geneseed link` from your clone and accept the offer to add `~/.local/bin` to your shell's startup file.
63
+
64
+ ### Fixed
65
+ - **A clone install puts `geneseed` on your PATH on macOS and Linux** (#359). `./install` never linked the command, and `geneseed link` wrote it to `~/.local/bin`, which macOS zsh does not search, so `geneseed` was not found (reported 2026-10). `./install` now runs `link` when `geneseed` is not found yet, and `link` offers, in a terminal, to add the folder to your shell's startup file (`~/.zshrc`, or `~/.bashrc` / `~/.bash_profile` for bash). It appends one marked block, never twice; `geneseed unlink` removes it. Outside a terminal it prints the line to add, as before.
66
+
67
+ ## [4.11.1] — 2026-10-21
68
+
69
+ Patch: the web console on a machine with only IBM Bob (or any host but OpenCode): it opens on the install you have, the first-deploy form asks a per-repo mode for its folder, and the doctor stops reading a misplaced project install as a broken bundle.
70
+
71
+ ### Upgrade notes
72
+ - Restart the console after updating (`geneseed web restart`) so it serves the rebuilt UI. If a per-repo install landed in `Harness/` inside your Geneseed clone, remove it with `./geneseed uninstall --target Harness --yes`, then delete `Harness/`.
73
+
74
+ ### Fixed
75
+ - **The web console works on a machine without OpenCode** (#357). It always opened on the OpenCode global dir, so with only IBM Bob installed the Overview showed the first-deploy form over a working `~/.bob`, and nothing could re-point it (the harness selector hides itself below two installs). When nothing is deployed where it looks, the console now shows the first active install it finds, a global before a project, and follows a first deploy to where it landed.
76
+ - **The first-deploy form asks a project mode for its folder** (#357). Picking a per-repo mode (`bob`, `claude`, `opencode`, `openclaude`) sent a Build with no folder, so the files landed in `Harness/` inside the Geneseed clone, where the host never looks. The form now asks for the folder (typed or Browse…) and deploys there; Build refuses a per-repo mode it has no folder for; the portable `files` bundle is left to the CLI. To remove an install misplaced this way, run `./geneseed uninstall --target Harness --yes` in the clone, then delete `Harness/`.
77
+ - **The doctor's committed-bundle check no longer reads a Claude, Bob or OpenClaude project install as a broken bundle** (#357). Such a tree has a `.bob/`-style data dir, not the bundle layout, so every file read "missing" (178 lines on the reported machine) and no rebuild could clear them.
78
+
79
+ ## [4.11.0] — 2026-10-21
80
+
81
+ Minor: the self-improvement loops write to notebook notes instead of the installed skill or agent file, the web console gains a Kits section, and `./install` waits for your answers and finds tools installed for one user.
82
+
83
+ ### Upgrade notes
84
+ - Run `geneseed rebuild-all` once after updating so installed skills, agents and AGENT.md carry the new self-improvement footer. Lessons the loops wrote into installed files before this release are still exported by `upgrade` into `improvements/`, as before.
85
+
86
+ ### Added
87
+ - **Web console: Kits section** (#353). A top-level Kits page between Library and Loops: a list and a detail page per kit (rules, banned APIs, members linked to the Library, upstream pin and compare link, a switch per install that rebuilds through the install job). Installs → Kits shares its staging and Apply control. `GET /api/kits` now carries rules, word budget, banned APIs and credits, and the Library shows a `kit:` badge on kit members.
88
+
89
+ ### Changed
90
+ - **Self-improvement writes to a sidecar, not the skill** (#354). The skill and agent loops used to edit the rendered file in place, which the doctor reported as stale and the next rebuild erased. They now write corrections to `notebook/skill-notes/<name>.md` and `notebook/agent-notes/<name>.md` (seed-once, agent-owned, never overwritten), and read them back before the skill runs or the agent is dispatched. `geneseed diff` lists the notes and `--out` exports them for back-porting into `src/`. AGENT.md names the notes paths, so the carrier ceilings rise by exactly that text (full 62_263, lean 39_156).
91
+
92
+ ### Fixed
93
+ - **Setup's prompts no longer answer themselves in a real terminal** (#356). Reading `process.stdin` puts a terminal's input in non-blocking mode, and the line reader took the resulting `EAGAIN` for end of input: "Continue without one? (y/N)" printed and answered N at once, so `./install` stopped before anyone could type, and every other wizard question silently took its default. The reader now waits for the keypress.
94
+ - **A tool installed for one user is found** (#356). Setup looked only on PATH, so an IBM Bob installed under the user's profile read as missing. It now also checks `~/.local/bin`, `~/bin`, each tool's own installer dir (`~/.opencode/bin`, `~/.claude/local`), the per-user npm prefix on Windows, and Bob's desktop app folder (`%LOCALAPPDATA%\Programs` on Windows, `/Applications` and `~/Applications` on macOS); a tool found there is listed with its path. When nothing is found, setup asks for the tool's path before offering to stop. The double-clickable `install.command` now waits for Enter before its window can close, as `install.cmd` already did.
95
+
11
96
  ## [4.10.0] — 2026-10-20
12
97
 
13
98
  Minor: opt-in kits (and a first one, `spring-boot`), credits for adapted content, a weekly upstream-drift check, and a web Kits tab.
package/README.md CHANGED
@@ -157,7 +157,7 @@ geneseed web stop # stop the daemon
157
157
  geneseed web status # is it running, and where
158
158
  ```
159
159
 
160
- The sidebar has six pages: **Overview** (what's deployed, health, recent jobs), **Constitution** (the rules and doctrine switches), **Library** (skills, agents, the wiki), **Personal** (your rules, profile, memory, notebook), **Installs** (hosts, local edits, doctor, server) and **Docs** — this documentation, opening on the Understand track, with a map of what Geneseed put on your machine.
160
+ The sidebar has eight pages: **Overview** (what's deployed, health, recent jobs), **Constitution** (the rules and doctrine switches), **Library** (skills, agents, the wiki), **Kits** (opt-in stack bundles), **Loops** (the loop catalogue), **Personal** (your rules, profile, memory, notebook), **Installs** (hosts, local edits, doctor, server) and **Docs** — this documentation, opening on the Understand track, with a map of what Geneseed put on your machine.
161
161
 
162
162
  It binds to `127.0.0.1` only and runs entirely offline — no npm needed at runtime; the UI build ships in `web/dist/`. Mutating actions run in the background and report back as toasts (fire-and-notify), guarded by a per-session token so other sites can't trigger them. A global **Spotlight** search in the topbar jumps to any agent, skill, rule, doc, or MCP server. Rebuild the UI after changing anything under `web/src/` with `cd web && npm install && npm run build`. If `web/dist/` is missing (fresh clone, never built), `geneseed web` offers to run that build for you — answer `Y` and it installs, builds, and starts the server; in non-interactive shells it prints the manual recipe instead.
163
163
 
@@ -334,7 +334,7 @@ geneseed rebuild-all # re-render every registered install in its own theme + m
334
334
  ./geneseed upgrade # just the content refresh (remembers theme + emit mode)
335
335
  ```
336
336
 
337
- **Local edits survive.** If the agent refined its deployed agent/skill files in place, an upgrade exports that drift to an `improvements/` file inside the install before overwriting. Details: [Upgrade](docs/guides/upgrade.md).
337
+ **Local edits survive.** The self-improvement loops write to notes under `notebook/`, which no rebuild touches. If you or the agent edited deployed agent/skill files in place anyway, an upgrade exports that drift to an `improvements/` file inside the install before overwriting. Details: [Upgrade](docs/guides/upgrade.md).
338
338
 
339
339
  ## 📚 Documentation
340
340
 
@@ -373,12 +373,13 @@ Some skills and agents are adapted from other open-source skill collections, und
373
373
 
374
374
  - `daydream` — [glebis/claude-skills](https://github.com/glebis/claude-skills) by Gleb Kalinin (MIT): the vendored `daydream` skill.
375
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.
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 and questionnaire of brainstorm, the slicing of plan, the reproduction step of debug, the integration-branch rules of parallel-agents, the trigger-pruning of skill-forge, and the dated-history rule of wiki.
377
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
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
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
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
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
+ - `everything-claude-code` — [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) by Affaan Mustafa (MIT): the React kit's skills and agents, rewritten and condensed for Vite and Jest.
382
383
 
383
384
  ## 📄 License
384
385
 
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.10.0**.
23
+ > string. Current label: **4.14.0**.
24
24
 
25
25
  ## Capabilities
26
26
 
@@ -26,10 +26,22 @@ The first kit is `spring-boot`, for Spring Boot 4 (Spring Framework 7, Java 21+)
26
26
 
27
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
28
 
29
+ The second kit is `react`, for React 18 or 19 apps built with Vite or webpack, in TypeScript or JavaScript, tested with Jest and React Testing Library. It adds four members:
30
+
31
+ | Member | Kind | What it is for |
32
+ |---|---|---|
33
+ | `react` | skill | Components, hooks, state placement, data fetching, TypeScript or JavaScript, React security; side files for Vite and for webpack (config, client-side routing, env variables, build failures), read by whichever the project uses, and accessibility |
34
+ | `react-testing` | skill | Jest and Testing Library: role queries, `userEvent`, MSW network mocks, `jest-axe`, and the Jest configuration a Vite or webpack project needs |
35
+ | `react-engineer` | agent | Implements a React feature, or makes a broken Vite or webpack build or Jest run green |
36
+ | `react-reviewer` | agent | Framework-aware review: hooks, state, data fetching, accessibility, React security, tests |
37
+
38
+ It is adapted from [affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) by Affaan Mustafa (MIT), rewritten and condensed for Vite and Jest; the webpack side file is first-party. It has no Server Components material: a client-rendered app has no server render.
39
+
29
40
  ### Switching kits on and off
30
41
 
31
42
  ```bash
32
43
  geneseed build --emit claude-global --kits spring-boot
44
+ geneseed build --emit claude-global --kits react,spring-boot
33
45
  geneseed build --emit claude-global --kits none
34
46
  ```
35
47
 
@@ -37,7 +49,7 @@ geneseed build --emit claude-global --kits none
37
49
 
38
50
  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
51
 
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.
52
+ The setup wizard asks one yes/no question per kit, each defaulting to whether the install has that kit on. It asks them only when at least one kit exists.
41
53
 
42
54
  ### What an install records
43
55
 
@@ -47,7 +59,7 @@ An install with a kit on records it in its instructions file, under the skill se
47
59
 
48
60
  `geneseed status` shows `kits: spring-boot` on the install's row, and `status --json` carries the same list.
49
61
 
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.
62
+ 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. The top-level **Kits** page (`#/kits`) lists the kits; a kit's page (`#/kits/<kit>`) shows its rules, banned APIs and upstream credits with the same per-install switch, and the Library tags each member skill or agent with a `kit:` badge.
51
63
 
52
64
  ### What a kit adds to an install
53
65
 
@@ -41,7 +41,7 @@ The full list, with each description, is in the catalog.
41
41
 
42
42
  ### Skills improve themselves
43
43
 
44
- Every skill ends with one beat of reflection: if a step misled or a step was missing, the agent proposes the exact edit to that skill and applies it only with your assent. Most runs end with nothing to change.
44
+ Every skill ends with one beat of reflection: if a step misled or a step was missing, the agent writes the exact correction, with your assent, into that skill's notes file, `notebook/skill-notes/<name>.md`, and reads those notes the next time the skill runs. It never edits the installed skill itself: that file is rendered from `src/`, a rebuild would overwrite the edit, and `doctor` would report it as drift. Agents work the same way through `notebook/agent-notes/`. Each note is one line that carries its evidence, `- [skill|general] <correction> — seen: <what happened, against what was expected> (<date>)`: `seen:` is what you judge the correction by before folding it in, and `general` marks one that belongs in a law, doctrine or memory rather than in that one skill. `geneseed diff` lists the notes so you can fold them back into `src/`. Most runs end with nothing to change.
45
45
 
46
46
  ### Old names keep working
47
47
 
package/docs/extending.md CHANGED
@@ -513,10 +513,9 @@ enumerates skills would miss it. `src/kits/<kit>/` holds only `rules.md`.
513
513
  to a kit member (it would dangle in an install without the kit), and a kit file that links to
514
514
  another kit's member. Links run kit → core only. A kit's links to its own members do not
515
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.
516
+ 5. **The setup wizard.** It asks one yes/no per kit, and only when at least one kit exists. Each
517
+ shipped kit therefore adds one answer line to the stdin strings in `tests/unit/wizard.test.mjs`,
518
+ and the `wizard-no-kit` job holds that the questions are skipped on a tree with no kit.
520
519
  6. **Golden cells**: the matrix (`tests/helpers/matrix/emit.*.json`) carries kit on for `claude` and
521
520
  `opencode`, the kit-off baseline (`kits: []`, spelled `--kits none`), and three deletion cells
522
521
  (kit on to off). One deletion cell sets `edit` (a map of sandbox path to appended text) so a member
@@ -8,7 +8,7 @@ description: "Put the geneseed command on your PATH from a clone."
8
8
  ---
9
9
  **Installed from npm? You have nothing to do.** `npm install -g geneseed` already puts `geneseed`, `geneseed-build` and `geneseed-hook` on your `PATH`.
10
10
 
11
- From a clone, you call the launcher inside the clone: `./geneseed` (Windows: `.\geneseed.cmd`). To type plain `geneseed` from any folder, link it.
11
+ From a clone, you call the launcher inside the clone: `./geneseed` (Windows: `.\geneseed.cmd`). To type plain `geneseed` from any folder, link it. On macOS and Linux, `./install` does this for you when `geneseed` is not found yet.
12
12
 
13
13
  ## macOS and Linux
14
14
 
@@ -17,7 +17,7 @@ From a clone, you call the launcher inside the clone: `./geneseed` (Windows: `.\
17
17
  ./geneseed link /usr/local/bin # or another bin folder (may ask for sudo)
18
18
  ```
19
19
 
20
- `link` writes a small shell launcher that runs this clone by its absolute path. It also tells you whether the target folder is on your `PATH`, and if it is not, the one line to add to your shell profile. Then:
20
+ `link` writes a small shell launcher that runs this clone by its absolute path. If the target folder is not on your `PATH` (macOS zsh does not search `~/.local/bin`), it offers to add it to your shell's startup file: `~/.zshrc` for zsh, `~/.bashrc` for bash on Linux, `~/.bash_profile` for bash on macOS. It appends one marked block, never twice, and `geneseed unlink` removes it. Outside a terminal, or for another shell, it prints the line to add instead. Open a new terminal, then:
21
21
 
22
22
  ```
23
23
  geneseed # the web console (or the command list), from any folder
@@ -46,7 +46,7 @@ A rebuild only replaces files the install's manifest says Geneseed owns. It leav
46
46
 
47
47
  ## Local edits, and the improvements file
48
48
 
49
- The agent can refine its own deployed agent and skill files as it works, and so can you. A rebuild would overwrite those edits, so **setup, re-theme and upgrade export them first**. When the files they are about to overwrite differ from a fresh render, the differences are written to a markdown **improvements file** in `improvements/`, inside the install's own folder (for the OpenCode global install, `~/.config/opencode/improvements/`). The manifest does not list that folder, so rebuilds never overwrite it and uninstall never removes it.
49
+ The agent records what it learns about a skill or agent in notes under `notebook/skill-notes/` and `notebook/agent-notes/`, which rebuilds never touch. Edits made to the deployed agent and skill files themselves, by you or by an older harness, are another matter: a rebuild would overwrite those, so **setup, re-theme and upgrade export them first**. When the files they are about to overwrite differ from a fresh render, the differences are written to a markdown **improvements file** in `improvements/`, inside the install's own folder (for the OpenCode global install, `~/.config/opencode/improvements/`). The manifest does not list that folder, so rebuilds never overwrite it and uninstall never removes it.
50
50
 
51
51
  A rebuild also refuses to overwrite a file you edited. The manifest records a fingerprint of every file Geneseed wrote; when a file no longer matches it, the rebuild leaves it alone and prints `kept your edited <file> — delete it to get Geneseed's copy back`. From then on the file is yours and later rebuilds skip it. A file the new version no longer ships is kept the same way, with `(no longer shipped)`. Line-ending changes from a git checkout do not count as edits. Installs made before fingerprints existed are overwritten as before.
52
52
 
@@ -58,7 +58,7 @@ geneseed diff --full # with line-level diffs
58
58
  geneseed diff --out improvements.md # write the improvements file
59
59
  ```
60
60
 
61
- `diff` compares the global OpenCode install by default. Pass `--target <config dir>` to compare another install. A file you replaced with a link to your own copy (a skill folder junctioned or symlinked elsewhere, say) is listed as `linked`: Geneseed never writes through a link, so it is yours and not drift, and it stays out of the improvements file. The improvements file is self-contained. To make the changes permanent, hand it to an agent in a Geneseed clone and ask it to fold them back into `src/`. Then they survive every future build. The web console's **Changes** page exports the same file.
61
+ `diff` compares the global OpenCode install by default. Pass `--target <config dir>` to compare another install. A file you replaced with a link to your own copy (a skill folder junctioned or symlinked elsewhere, say) is listed as `linked`: Geneseed never writes through a link, so it is yours and not drift, and it stays out of the improvements file. `diff` also lists every non-empty self-improvement note, and `--out` copies them into the improvements file; the upgrade export does not fire on notes alone, since a rebuild cannot lose them. The improvements file is self-contained. To make the changes permanent, hand it to an agent in a Geneseed clone and ask it to fold them back into `src/`. Then they survive every future build. The web console's **Changes** page exports the same file.
62
62
 
63
63
  ## Moving from a clone to npm
64
64
 
@@ -97,7 +97,7 @@ reports `version "21` or higher.
97
97
  .\geneseed.cmd link
98
98
  ```
99
99
 
100
- On Windows, `link` writes to `%LOCALAPPDATA%\Geneseed\bin` and adds it to your user PATH — only **new** terminals see it, so close and reopen yours.
100
+ On macOS and Linux, `link` offers to add `~/.local/bin` to your shell's startup file when it is not on your PATH; accept, then open a **new** terminal. On Windows, `link` writes to `%LOCALAPPDATA%\Geneseed\bin` and adds it to your user PATH — only **new** terminals see it, so close and reopen yours.
101
101
 
102
102
  **Confirm.**
103
103
 
package/docs/web-ui.md CHANGED
@@ -114,6 +114,11 @@ streams the output of background jobs and keeps their history across reloads.
114
114
  **Promote to rule** control turns a recurring lesson into a trial rule in
115
115
  `user-rules.md` — provenance recorded, the source fact deleted so it isn't loaded
116
116
  twice — landing you on the Rules page.
117
+ - **Kits** (`#/kits`, `#/kits/<kit>`) — the list of shipped kits and, per kit, a detail page
118
+ with its rules, word budget, banned APIs, upstream credits and one switch per install.
119
+ `GET /api/kits` carries `rules`, `rulesWordBudget`, `banned` and `credits` per kit; the
120
+ catalog and item endpoints for skills and agents carry `kit` (the owning kit or `null`),
121
+ which the Library shows as a `kit:` badge.
117
122
  - **Loops** (`#/loops`) — the loop catalogue `geneseed loop` runs from, read-only, laid out
118
123
  like the Library: a rail of sections (**Templates**, **Bricks**, **Active**, each with its
119
124
  count; `#/loops/<section>/<item>` opens an entry), with the selected section's groups
@@ -194,7 +199,7 @@ streams the output of background jobs and keeps their history across reloads.
194
199
  card (shown once a global install exists) manages sovereign repos — folders where every
195
200
  global harness goes dormant (hooks stay silent, the global preamble never loads);
196
201
  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.
202
+ - **Kits** (`#/installs/kits`) — every install (disabled ones read-only) as a panel with one switch per kit.
198
203
  Toggling only stages the set; **Apply** asks first, then rebuilds that install with
199
204
  exactly that set as a console job. Files you edited in a kit that is now off are kept,
200
205
  and the row lists them.
@@ -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.10.0"
5
+ "version": "4.14.0"
6
6
  }
package/js/hosts/link.mjs CHANGED
@@ -74,6 +74,7 @@
74
74
  import {
75
75
  chmodSync, existsSync, lstatSync, mkdirSync, readlinkSync, rmSync,
76
76
  } from 'node:fs';
77
+ import { isatty } from 'node:tty';
77
78
  import { spawnSync } from 'node:child_process';
78
79
  import path from 'node:path';
79
80
  import os from 'node:os';
@@ -81,12 +82,84 @@ import os from 'node:os';
81
82
  import { ROOT } from '../build/source.mjs';
82
83
  import { readMaybe } from './installs.mjs';
83
84
  import { printOut, printErr, writeText } from '../lib/fs.mjs';
85
+ import { confirm } from '../lib/prompt.mjs';
84
86
  import { toPlatformPath, which } from '../lib/paths.mjs';
85
87
 
86
88
  const IS_WIN = process.platform === 'win32';
87
89
  /** What `cmdUnlink` recognises a written Unix launcher by — see `isOurLauncher`. */
88
90
  const SHIM_MARKER = 'GENESEED_LINK_SHIM';
89
91
 
92
+ /** The lines that open and close the PATH block `link` writes into a shell startup file. */
93
+ const RC_BEGIN = '# >>> geneseed >>> (added by `geneseed link`; `geneseed unlink` removes it)';
94
+ const RC_END = '# <<< geneseed <<<';
95
+
96
+ /**
97
+ * The startup file to put the bin dir on PATH in, for this login shell — or null when it is
98
+ * not one we know how to edit (fish, csh, none), where `link` prints the line instead.
99
+ *
100
+ * zsh reads `$ZDOTDIR/.zshrc` (default `~`). bash reads `~/.bashrc` on Linux, but macOS
101
+ * Terminal opens LOGIN shells, which read `~/.bash_profile` and not `~/.bashrc`. Pure, for the
102
+ * test: nothing here may touch the real user's files.
103
+ */
104
+ export function shellRcFile({
105
+ shell = process.env.SHELL || '', home = os.homedir(), zdotdir = process.env.ZDOTDIR,
106
+ platform = process.platform,
107
+ } = {}) {
108
+ const name = path.basename(shell);
109
+ if (name === 'zsh') return path.join(zdotdir || home, '.zshrc');
110
+ if (name === 'bash') return path.join(home, platform === 'darwin' ? '.bash_profile' : '.bashrc');
111
+ return null;
112
+ }
113
+
114
+ /**
115
+ * `text` with the PATH block for `dir` appended, or unchanged when a block is already there.
116
+ *
117
+ * The dir goes inside double quotes with `\`, `"`, `$` and backtick escaped, so a home path
118
+ * holding any of them stays one literal word to the shell rather than becoming code.
119
+ */
120
+ export function withPathBlock(text, dir) {
121
+ if (text.includes(RC_BEGIN)) return text;
122
+ const quoted = dir.replace(/[\\"$`]/g, (c) => `\\${c}`);
123
+ const sep = text === '' || text.endsWith('\n') ? '' : '\n';
124
+ return `${text}${sep}${RC_BEGIN}\nexport PATH="${quoted}:$PATH"\n${RC_END}\n`;
125
+ }
126
+
127
+ /** `text` without the PATH block `withPathBlock` wrote; unchanged when there is none. */
128
+ export function withoutPathBlock(text) {
129
+ const start = text.indexOf(RC_BEGIN);
130
+ if (start < 0) return text;
131
+ const end = text.indexOf(RC_END, start);
132
+ if (end < 0) return text;
133
+ let stop = end + RC_END.length;
134
+ if (text[stop] === '\n') stop += 1;
135
+ return text.slice(0, start) + text.slice(stop);
136
+ }
137
+
138
+ /**
139
+ * Put `dir` on PATH for future shells, ASKING first — macOS zsh does not search
140
+ * `~/.local/bin`, so a linked `geneseed` was not found (reported 2026-10). Only in a terminal:
141
+ * the web console's button and every test run without one, and get the printed line instead,
142
+ * so no gate can edit the developer's own startup file. Returns whether it wrote.
143
+ */
144
+ function offerShellPath(dir) {
145
+ const rc = shellRcFile();
146
+ if (!rc || !isatty(0)) return false;
147
+ const text = readMaybe(rc) ?? '';
148
+ if (text.includes(RC_BEGIN)) {
149
+ printOut(`geneseed: ${rc} already adds it — open a NEW terminal, then run 'geneseed'.\n`);
150
+ return true;
151
+ }
152
+ if (!confirm(`geneseed: add '${dir}' to your PATH in ${rc}?`, true)) return false;
153
+ try {
154
+ writeText(rc, withPathBlock(text, dir));
155
+ } catch (e) {
156
+ printErr(`geneseed: could not write ${rc} (${asOsError(e)})\n`);
157
+ return false;
158
+ }
159
+ printOut(`geneseed: added it to ${rc} — open a NEW terminal, then run 'geneseed'.\n`);
160
+ return true;
161
+ }
162
+
90
163
  /** `_harness_lifecycle._win_bin_dir`. */
91
164
  function winBinDir() {
92
165
  // `os.environ.get(...) or str(Path.home())` — an EMPTY LOCALAPPDATA falls back too,
@@ -267,7 +340,7 @@ export function cmdLink(args) {
267
340
  printOut(`geneseed: linked ${dest} -> ${entry}\n`);
268
341
  if ((process.env.PATH || '').split(path.delimiter).includes(targetDir)) {
269
342
  printOut(`geneseed: '${targetDir}' is on PATH — run 'geneseed' from anywhere.\n`);
270
- } else {
343
+ } else if (!offerShellPath(targetDir)) {
271
344
  printOut(`geneseed: NOTE '${targetDir}' is not on your PATH. Add it, e.g.:\n`);
272
345
  printOut(` echo 'export PATH="${targetDir}:$PATH"' >> ~/.zshrc # or ~/.bashrc\n`);
273
346
  }
@@ -311,6 +384,20 @@ export function cmdUnlink() {
311
384
  } catch { /* `except OSError: pass` */ }
312
385
  }
313
386
  }
387
+ // The PATH block `link` may have written, from each startup file it could have chosen.
388
+ const home = os.homedir();
389
+ const rcs = new Set([shellRcFile({ shell: 'zsh' }), path.join(home, '.bashrc'),
390
+ path.join(home, '.bash_profile')]);
391
+ for (const rc of rcs) {
392
+ const text = readMaybe(rc);
393
+ if (text === null || withoutPathBlock(text) === text) continue;
394
+ try {
395
+ writeText(rc, withoutPathBlock(text));
396
+ printOut(`geneseed: removed the PATH line from ${rc}\n`);
397
+ } catch (e) {
398
+ printErr(`geneseed: could not edit ${rc} (${asOsError(e)})\n`);
399
+ }
400
+ }
314
401
  if (!removed) printOut('geneseed: no linked launcher found on PATH\n');
315
402
  return 0;
316
403
  }
@@ -10,7 +10,7 @@ import path from 'node:path';
10
10
  import { stripCapabilityLinks } from '../build/emit-common.mjs';
11
11
  import { STRUCTURE, renderAll } from '../build/render.mjs';
12
12
  import { CONFIG, CREDIT_RE, makeCfg } from '../build/source.mjs';
13
- import { hostCatalogsNatively, resolvePath } from '../hosts/hosts.mjs';
13
+ import { CLAUDE_STYLE, hostCatalogsNatively, resolvePath } from '../hosts/hosts.mjs';
14
14
  import { EMIT_HOST_SCOPE, footprintOfDir, themeFiles } from '../hosts/installs.mjs';
15
15
  import { isVendoredPath } from '../hosts/native.mjs';
16
16
  import { PALETTE_ROLES, colorThemeFiles } from '../hosts/opencode.mjs';
@@ -217,7 +217,13 @@ export function renderedProblems(bundle) {
217
217
  else themeName = 'neutral';
218
218
  let emit;
219
219
  try { emit = readText(path.join(bundle, '.geneseed-emit')).trim(); } catch { emit = ''; }
220
- const host = (EMIT_HOST_SCOPE.get(emit) ?? ['', ''])[0];
220
+ const [host, scope] = EMIT_HOST_SCOPE.get(emit) ?? ['', ''];
221
+ // A Claude-shaped PROJECT install is not a bundle: its tree is `AGENTS.md`/`CLAUDE.md` plus a
222
+ // `.bob/`/`.claude/`/`.openclaude/` data dir, so every rendered file reads "missing" and no
223
+ // rebuild can clear it. The console's onboarding once put a Bob project install exactly here
224
+ // (`./Harness` under the checkout, reported 2026-10: 178 such lines). Its own drift is
225
+ // `geneseed diff`'s and `status`'s to report, not this committed-bundle check's.
226
+ if (scope === 'project' && CLAUDE_STYLE.includes(host)) return [];
221
227
  let items;
222
228
  try {
223
229
  // THE FIRST CALLER IN THE PORT THAT CATCHES A REFUSAL, and that is why the stderr is
@@ -35,7 +35,7 @@ import { isFile, readText } from '../lib/fs.mjs';
35
35
  * console draws it on the cost chart, so it lives here, once, where both read it. Raise it by
36
36
  * exactly the measured overrun and say why in that test's docblock, which keeps its history.
37
37
  */
38
- export const CARRIER_CEILING = { full: 62_219, lean: 39_111 };
38
+ export const CARRIER_CEILING = { full: 62_263, lean: 39_156 };
39
39
 
40
40
  /**
41
41
  * A frontmatter value as the host shows it. The emit writes descriptions JSON-escaped
@@ -321,8 +321,38 @@ export function diffCollect({
321
321
  /** The records that are drift: everything but a `linked` path, which is the user's own. */
322
322
  export const drifted = (files) => files.filter((f) => f.status !== 'linked');
323
323
 
324
+ /** Where the self-improvement loops write, relative to an install's `notebook/`. */
325
+ export const NOTES_DIRS = ['skill-notes', 'agent-notes'];
326
+
327
+ /**
328
+ * The self-improvement notes an install has accumulated, as `{rel, body}` sorted by `rel`.
329
+ *
330
+ * WHY THESE ARE NOT DRIFT. The loops used to edit the rendered skill and agent files in place,
331
+ * which the doctor reported as stale and the next rebuild erased. They now write a sidecar
332
+ * under the notebook — seed-once and agent-owned, never in the manifest, so no rebuild touches
333
+ * it. That makes the notes the back-port QUEUE rather than an endangered edit: `diff` lists
334
+ * them and `--out` carries them so they can reach `src/`, but the upgrade export does not
335
+ * fire on them alone, because there is nothing for the rebuild to destroy. Empty files are
336
+ * skipped — an agent that cleared its notes has nothing to back-port.
337
+ */
338
+ export function collectNotes(dir) {
339
+ const notes = [];
340
+ for (const sub of NOTES_DIRS) {
341
+ const d = path.join(dir, 'notebook', sub);
342
+ let names;
343
+ try { names = readdirSync(d); } catch { continue; }
344
+ for (const n of names.filter((x) => x.endsWith('.md')).sort()) {
345
+ const a = path.join(d, n);
346
+ if (!isFile(a)) continue;
347
+ const body = readText(a);
348
+ if (body.trim()) notes.push({ rel: `notebook/${sub}/${n}`, body });
349
+ }
350
+ }
351
+ return notes;
352
+ }
353
+
324
354
  /** `_harness_diff._improvements_md` — pure; `when` is the caller's timestamp. */
325
- export function improvementsMd(target, theme, allFiles, when) {
355
+ export function improvementsMd(target, theme, allFiles, when, notes = []) {
326
356
  // A linked path is never a back-port item: the file is the user's, not a local edit of ours.
327
357
  const files = drifted(allFiles);
328
358
  const edited = files.filter((f) => f.status === 'edited');
@@ -337,8 +367,8 @@ export function improvementsMd(target, theme, allFiles, when) {
337
367
  `- ${edited.length} edited · ${added.length} added in deployed · `
338
368
  + `${missing.length} missing from deployed`,
339
369
  '',
340
- 'The deployed harness drifted from a fresh render of `src/` — typically the',
341
- 'self-improvement loops editing agent/skill files in place. Hand this file to',
370
+ 'The deployed harness drifted from a fresh render of `src/` — typically',
371
+ 'agent/skill files edited in place, by hand or by an older harness. Hand this file to',
342
372
  'an agent in the Geneseed source repo and ask it to fold the changes below',
343
373
  'back into `src/`. Diffs read source -> deployed; the expected copy was',
344
374
  'rendered in the deployed theme, so only genuine local edits appear.',
@@ -352,6 +382,17 @@ export function improvementsMd(target, theme, allFiles, when) {
352
382
  for (const f of files) {
353
383
  lines.push(`## \`${f.rel}\` (${label[f.status]})`, '', '```diff', ...f.diff, '```', '');
354
384
  }
385
+ // Only when there are notes, so a report without them stays byte-identical.
386
+ if (notes.length) {
387
+ lines.push('# Self-improvement notes', '',
388
+ 'Corrections the agent recorded against a skill or agent instead of editing it.',
389
+ 'Fold each one into the matching file under `src/`, then clear the note. Judge it by',
390
+ 'its `seen:` evidence — a line without one is a claim, not a lesson — and weigh a',
391
+ '`[general]` line as a law, doctrine or memory rather than an edit to that one file.', '');
392
+ for (const n of notes) {
393
+ lines.push(`## \`${n.rel}\``, '', '```markdown', ...splitLines(n.body), '```', '');
394
+ }
395
+ }
355
396
  return `${lines.join('\n')}\n`;
356
397
  }
357
398
 
@@ -385,7 +426,9 @@ function stamp(d) {
385
426
  * the same instant, so a run that crosses a second boundary cannot name a file one second
386
427
  * away from what it says inside.
387
428
  */
388
- export function writeImprovements(target, theme, files, outPath = null) {
429
+ export function writeImprovements(
430
+ target, theme, files, outPath = null, notes = collectNotes(target),
431
+ ) {
389
432
  const now = stamp(new Date());
390
433
  // `Path(out_path).expanduser()` — again NOT resolved, and again observable: the path is
391
434
  // printed, so `--out p.md` reports `p.md`.
@@ -393,7 +436,7 @@ export function writeImprovements(target, theme, files, outPath = null) {
393
436
  ? expanduser(outPath)
394
437
  : path.join(target, 'improvements', now.file);
395
438
  mkdirSync(path.dirname(p), { recursive: true });
396
- writeText(p, improvementsMd(target, theme, files, now.when));
439
+ writeText(p, improvementsMd(target, theme, files, now.when, notes));
397
440
  return p;
398
441
  }
399
442
 
@@ -477,18 +520,21 @@ export function cmdDiff(args) {
477
520
  + `${missing.length} missing-from-deployed\n`);
478
521
  // Its own line, printed only when there is one, so the summary above is unchanged otherwise.
479
522
  if (linked.length) printOut(`[diff] ${linked.length} linked (yours, left alone)\n`);
523
+ const notes = collectNotes(target);
524
+ if (notes.length) printOut(`[diff] ${notes.length} self-improvement note(s) to back-port\n`);
480
525
  for (const f of edited) printOut(` ~ ${f.rel} (edited in deployed — review to back-port)\n`);
481
526
  for (const f of added) printOut(` + ${f.rel} (only in deployed — your addition)\n`);
482
527
  for (const f of missing) printOut(` - ${f.rel} (in source, not deployed — re-emit to add)\n`);
483
528
  for (const f of linked) printOut(` = ${f.rel} (a link — yours, left alone)\n`);
529
+ for (const n of notes) printOut(` * ${n.rel} (self-improvement note — review to back-port)\n`);
484
530
  if (args.out) {
485
- if (drifted(files).length) {
531
+ if (drifted(files).length || notes.length) {
486
532
  // Bare `--out` is argparse's `nargs="?"` const: True, meaning "the default timestamped
487
533
  // path". `parse()` gives a flag `true` and an option a string, so the two arrive
488
534
  // distinguishable here without a third kind of entry in the VERBS table.
489
535
  const outPath = args.out === true ? null : args.out;
490
536
  printOut(`[diff] improvements file written: ${
491
- writeImprovements(target, theme, files, outPath)}\n`);
537
+ writeImprovements(target, theme, files, outPath, notes)}\n`);
492
538
  } else {
493
539
  printOut('[diff] no differences — nothing written.\n');
494
540
  }
@@ -97,6 +97,7 @@ export const SKILL_CLASS = {
97
97
  'frontend-design': 'build', prototype: 'build', deck: 'build',
98
98
  'forge-mcp': 'build', bruno: 'build', worktree: 'build',
99
99
  'spring-boot': 'build', 'spring-data-jpa': 'build', 'spring-testing': 'build',
100
+ react: 'build', 'react-testing': 'build',
100
101
  'geneseed-code-review': 'review', 'fresh-eyes': 'review', 'security-audit': 'review',
101
102
  'roast-me': 'review', 'review-response': 'review', ponytail: 'review',
102
103
  commit: 'ship', ship: 'ship', release: 'ship', handoff: 'ship',
package/js/lib/prompt.mjs CHANGED
@@ -12,6 +12,9 @@ import { readSync } from 'node:fs';
12
12
  import { printOut } from './fs.mjs';
13
13
  import { parseIntStrict } from './text.mjs';
14
14
 
15
+ /** A cell to `Atomics.wait` on: the synchronous pause between `readLine`'s EAGAIN retries. */
16
+ const PAUSE = new Int32Array(new SharedArrayBuffer(4));
17
+
15
18
  /**
16
19
  * `_harness_setup._ask` — `input(f"{prompt}{suffix}: ").strip()`, or the default at EOF.
17
20
  *
@@ -71,14 +74,28 @@ export function promptLine(prompt) {
71
74
  * the wizard's first non-ASCII answer is what exposed it, and the corpus caught it on the
72
75
  * first run. Scanning for the terminators a byte at a time is still correct: `\n` and `\r`
73
76
  * are ASCII, and no continuation byte of a UTF-8 sequence is below 0x80.
77
+ *
78
+ * EAGAIN IS "NOTHING TYPED YET", NOT EOF. Touching `process.stdin` — even `.isTTY`, which
79
+ * `cmdSetup` reads first — makes libuv put a terminal fd 0 in non-blocking mode, and from then
80
+ * on `readSync` throws EAGAIN until a key is pressed. Caught as EOF, every prompt took its
81
+ * default the instant it printed: setup's "Continue without one? (y/N)" answered itself N and
82
+ * the install stopped before anyone could type (reported 2026-10, reproduced under a pty). So
83
+ * it waits 20 ms and reads again; any other error is still the end of input. `read` is the
84
+ * test seam.
74
85
  */
75
- function readLine() {
86
+ export function readLine(read = readSync) {
76
87
  const buf = Buffer.alloc(1);
77
88
  const bytes = [];
78
89
  let eof = false;
79
90
  for (;;) {
80
91
  let n = 0;
81
- try { n = readSync(0, buf, 0, 1, null); } catch { eof = true; break; }
92
+ try {
93
+ n = read(0, buf, 0, 1, null);
94
+ } catch (e) {
95
+ if (e && e.code === 'EAGAIN') { Atomics.wait(PAUSE, 0, 0, 20); continue; }
96
+ eof = true;
97
+ break;
98
+ }
82
99
  if (n === 0) { eof = true; break; }
83
100
  if (buf[0] === 0x0a) break;
84
101
  if (buf[0] !== 0x0d) bytes.push(buf[0]);