geneseed 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +938 -0
  2. package/DESIGN.md +1423 -0
  3. package/LICENSE +21 -0
  4. package/QUICKSTART.md +48 -0
  5. package/README.md +336 -0
  6. package/SETUP.md +1027 -0
  7. package/SHIPPED.md +70 -0
  8. package/adapters/bob/README.md +100 -0
  9. package/adapters/claude-code/README.md +101 -0
  10. package/adapters/claude-code/settings.json +47 -0
  11. package/adapters/claude-code/skills/geneseed/SKILL.md +72 -0
  12. package/adapters/copilot/README.md +76 -0
  13. package/adapters/opencode/GLOBAL-HARNESS-SPEC.md +434 -0
  14. package/adapters/opencode/HOW-OPENCODE-LOADS.md +158 -0
  15. package/adapters/opencode/README.md +582 -0
  16. package/adapters/opencode/agents/orchestrator.md +25 -0
  17. package/adapters/opencode/opencode.json +44 -0
  18. package/adapters/opencode/plugins/geneseed-activity.js +430 -0
  19. package/adapters/opencode/plugins/geneseed-context.js +876 -0
  20. package/adapters/opencode/plugins/geneseed-guard.js +290 -0
  21. package/adapters/opencode/plugins/geneseed-learn.js +396 -0
  22. package/adapters/opencode/plugins/geneseed-notify.js +142 -0
  23. package/adapters/opencode/plugins/geneseed-ponytail.js +148 -0
  24. package/adapters/opencode/plugins/geneseed-workflow.js +183 -0
  25. package/adapters/opencode/workflows/_runtime.js +277 -0
  26. package/adapters/opencode/workflows/council.js +64 -0
  27. package/adapters/opencode/workflows/dispatch.js +89 -0
  28. package/adapters/opencode/workflows/research-plan-implement.js +43 -0
  29. package/adapters/opencode/workflows/review.js +57 -0
  30. package/bin/geneseed-cli.mjs +412 -0
  31. package/bin/geneseed-hook.mjs +129 -0
  32. package/bin/geneseed.mjs +1086 -0
  33. package/docs/declined.md +149 -0
  34. package/docs/limits.md +270 -0
  35. package/docs/opencode-plugin-setup.md +64 -0
  36. package/docs/token-footprint.md +68 -0
  37. package/docs/web/_groups.json +45 -0
  38. package/docs/web/about.md +6 -0
  39. package/docs/web/adapters-claude-code.md +8 -0
  40. package/docs/web/adapters-opencode-loads.md +8 -0
  41. package/docs/web/adapters-opencode-spec.md +8 -0
  42. package/docs/web/adapters-opencode.md +8 -0
  43. package/docs/web/agents.md +8 -0
  44. package/docs/web/authoring.md +11 -0
  45. package/docs/web/autostart.md +9 -0
  46. package/docs/web/claude-hooks.md +36 -0
  47. package/docs/web/cli.md +6 -0
  48. package/docs/web/collaboration.md +27 -0
  49. package/docs/web/configure-wiki.md +9 -0
  50. package/docs/web/design.md +7 -0
  51. package/docs/web/env-knobs.md +9 -0
  52. package/docs/web/exclusions.md +26 -0
  53. package/docs/web/first-session.md +28 -0
  54. package/docs/web/footprint.md +31 -0
  55. package/docs/web/foreman-mode.md +33 -0
  56. package/docs/web/glossary.md +6 -0
  57. package/docs/web/harnesses.md +28 -0
  58. package/docs/web/headless.md +9 -0
  59. package/docs/web/ingest-docs.md +9 -0
  60. package/docs/web/install-paths.md +9 -0
  61. package/docs/web/install-quick.md +35 -0
  62. package/docs/web/lsp-overview.md +37 -0
  63. package/docs/web/mcp-claude-code.md +10 -0
  64. package/docs/web/mcp-filesystem.md +9 -0
  65. package/docs/web/mcp-gitlab.md +9 -0
  66. package/docs/web/mcp-markitdown.md +9 -0
  67. package/docs/web/mcp-overview.md +34 -0
  68. package/docs/web/mcp-trouble.md +9 -0
  69. package/docs/web/mcp-verify.md +9 -0
  70. package/docs/web/memory.md +7 -0
  71. package/docs/web/migrate.md +39 -0
  72. package/docs/web/model.md +20 -0
  73. package/docs/web/notebook.md +7 -0
  74. package/docs/web/pipelines.md +24 -0
  75. package/docs/web/plugin-activity.md +23 -0
  76. package/docs/web/plugin-context.md +26 -0
  77. package/docs/web/plugin-guard.md +24 -0
  78. package/docs/web/plugin-learn.md +27 -0
  79. package/docs/web/plugin-notify.md +24 -0
  80. package/docs/web/plugin-ponytail.md +23 -0
  81. package/docs/web/plugin-setup.md +7 -0
  82. package/docs/web/plugin-workflow.md +24 -0
  83. package/docs/web/plugins.md +17 -0
  84. package/docs/web/project-context.md +9 -0
  85. package/docs/web/rules.md +8 -0
  86. package/docs/web/run-anywhere.md +9 -0
  87. package/docs/web/self-improve.md +8 -0
  88. package/docs/web/setup-choices.md +39 -0
  89. package/docs/web/skills.md +8 -0
  90. package/docs/web/themes-author.md +8 -0
  91. package/docs/web/themes.md +9 -0
  92. package/docs/web/token-footprint.md +7 -0
  93. package/docs/web/trouble.md +38 -0
  94. package/docs/web/upgrade.md +9 -0
  95. package/docs/web/verify.md +30 -0
  96. package/docs/web/wiki.md +7 -0
  97. package/docs/web/worktree.md +10 -0
  98. package/docs/web-ui.md +144 -0
  99. package/docs/wiki.md +43 -0
  100. package/geneseed +39 -0
  101. package/geneseed.cmd +34 -0
  102. package/harness.config.json +6 -0
  103. package/js/anim.mjs +326 -0
  104. package/js/catalog.mjs +112 -0
  105. package/js/checkout.mjs +88 -0
  106. package/js/cli-table.json +1034 -0
  107. package/js/cli.mjs +498 -0
  108. package/js/diff.mjs +384 -0
  109. package/js/doctor.mjs +1364 -0
  110. package/js/emit.mjs +1373 -0
  111. package/js/excludes.mjs +271 -0
  112. package/js/generate.mjs +379 -0
  113. package/js/hooks.mjs +1026 -0
  114. package/js/hosts.mjs +253 -0
  115. package/js/installs.mjs +378 -0
  116. package/js/inventory.mjs +175 -0
  117. package/js/lib/fs.mjs +913 -0
  118. package/js/lib/proc.mjs +28 -0
  119. package/js/lib/udiff.mjs +275 -0
  120. package/js/link.mjs +286 -0
  121. package/js/mcp.mjs +335 -0
  122. package/js/memory.mjs +108 -0
  123. package/js/menu.mjs +118 -0
  124. package/js/migrate.mjs +251 -0
  125. package/js/native.mjs +426 -0
  126. package/js/opencode.mjs +325 -0
  127. package/js/registry.mjs +110 -0
  128. package/js/render.mjs +282 -0
  129. package/js/settings.mjs +924 -0
  130. package/js/setup.mjs +492 -0
  131. package/js/status.mjs +281 -0
  132. package/js/themes.mjs +195 -0
  133. package/js/tui.mjs +444 -0
  134. package/js/uninstall.mjs +1137 -0
  135. package/js/update.mjs +910 -0
  136. package/js/web/actions.mjs +868 -0
  137. package/js/web/activity.mjs +284 -0
  138. package/js/web/api.mjs +873 -0
  139. package/js/web/docs.mjs +531 -0
  140. package/js/web/graph.mjs +0 -0
  141. package/js/web/jobs.mjs +480 -0
  142. package/js/web/server.mjs +1135 -0
  143. package/package.json +54 -0
  144. package/registry.json +474 -0
  145. package/src/AGENT.md.tmpl +390 -0
  146. package/src/agents/_template.md +76 -0
  147. package/src/agents/advocate.md +36 -0
  148. package/src/agents/architect.md +55 -0
  149. package/src/agents/developer.md +66 -0
  150. package/src/agents/docs.md +54 -0
  151. package/src/agents/empiricist.md +39 -0
  152. package/src/agents/explorer.md +54 -0
  153. package/src/agents/framer.md +36 -0
  154. package/src/agents/historian.md +51 -0
  155. package/src/agents/operator.md +37 -0
  156. package/src/agents/pragmatist.md +37 -0
  157. package/src/agents/reviewer.md +52 -0
  158. package/src/agents/security.md +57 -0
  159. package/src/agents/skeptic.md +48 -0
  160. package/src/agents/steward.md +36 -0
  161. package/src/agents/tester.md +54 -0
  162. package/src/agents/user-advocate.md +36 -0
  163. package/src/agents/visionary.md +36 -0
  164. package/src/laws/universal.md +387 -0
  165. package/src/memory/README.md +109 -0
  166. package/src/memory/gitignore +6 -0
  167. package/src/modes/README.md +26 -0
  168. package/src/modes/direct.md +2 -0
  169. package/src/modes/foreman.md +19 -0
  170. package/src/notebook/README.md +39 -0
  171. package/src/notebook/gitignore +7 -0
  172. package/src/postures/README.md +31 -0
  173. package/src/postures/artisan.md +14 -0
  174. package/src/postures/assistant.md +12 -0
  175. package/src/postures/expert.md +11 -0
  176. package/src/postures/mentor.md +11 -0
  177. package/src/postures/peer.md +12 -0
  178. package/src/skills/_template.md +47 -0
  179. package/src/skills/brainstorm.md +28 -0
  180. package/src/skills/clarify.md +28 -0
  181. package/src/skills/codebase-design.md +44 -0
  182. package/src/skills/commit.md +48 -0
  183. package/src/skills/council.md +34 -0
  184. package/src/skills/crash-course.md +28 -0
  185. package/src/skills/daydream/.claude-plugin/plugin.json +9 -0
  186. package/src/skills/daydream/SKILL.md +74 -0
  187. package/src/skills/daydream/VENDOR.md +25 -0
  188. package/src/skills/daydream/critic-prompt.md +38 -0
  189. package/src/skills/daydream/instructions.md +422 -0
  190. package/src/skills/daydream/synthesizer-prompt.md +41 -0
  191. package/src/skills/debug.md +37 -0
  192. package/src/skills/decode.md +28 -0
  193. package/src/skills/develop.md +29 -0
  194. package/src/skills/document-project.md +79 -0
  195. package/src/skills/domain-modeling.md +38 -0
  196. package/src/skills/drill.md +28 -0
  197. package/src/skills/feynman.md +26 -0
  198. package/src/skills/forge-mcp.md +48 -0
  199. package/src/skills/fresh-eyes.md +29 -0
  200. package/src/skills/frontend-design.md +58 -0
  201. package/src/skills/gap-detector.md +27 -0
  202. package/src/skills/geneseed-code-review.md +33 -0
  203. package/src/skills/geneseed.md +29 -0
  204. package/src/skills/git-archaeology.md +47 -0
  205. package/src/skills/git-rescue.md +68 -0
  206. package/src/skills/handoff.md +29 -0
  207. package/src/skills/herdr.md +164 -0
  208. package/src/skills/ingest.md +49 -0
  209. package/src/skills/learning-path.md +26 -0
  210. package/src/skills/migrate.md +45 -0
  211. package/src/skills/opencode-theme.md +52 -0
  212. package/src/skills/parallel-agents.md +28 -0
  213. package/src/skills/pipeline.md +52 -0
  214. package/src/skills/plan.md +45 -0
  215. package/src/skills/ponytail.md +29 -0
  216. package/src/skills/profile.md +58 -0
  217. package/src/skills/prose.md +42 -0
  218. package/src/skills/prototype.md +44 -0
  219. package/src/skills/react-view-transitions/AGENTS.md +955 -0
  220. package/src/skills/react-view-transitions/README.md +42 -0
  221. package/src/skills/react-view-transitions/SKILL.md +320 -0
  222. package/src/skills/react-view-transitions/VENDOR.md +16 -0
  223. package/src/skills/react-view-transitions/metadata.json +12 -0
  224. package/src/skills/react-view-transitions/references/css-recipes.md +242 -0
  225. package/src/skills/react-view-transitions/references/implementation.md +182 -0
  226. package/src/skills/react-view-transitions/references/nextjs.md +176 -0
  227. package/src/skills/react-view-transitions/references/patterns.md +262 -0
  228. package/src/skills/refactor.md +32 -0
  229. package/src/skills/release.md +43 -0
  230. package/src/skills/repo-map.md +36 -0
  231. package/src/skills/research.md +42 -0
  232. package/src/skills/review-response.md +36 -0
  233. package/src/skills/roast-me.md +28 -0
  234. package/src/skills/rule.md +100 -0
  235. package/src/skills/ship.md +49 -0
  236. package/src/skills/tdd.md +31 -0
  237. package/src/skills/tickets.md +44 -0
  238. package/src/skills/token-report/SKILL.md +76 -0
  239. package/src/skills/token-report/VENDOR.md +23 -0
  240. package/src/skills/token-report/scripts/token_report.mjs +1301 -0
  241. package/src/skills/wayfinder.md +49 -0
  242. package/src/skills/wiki.md +68 -0
  243. package/src/skills/workflow.md +38 -0
  244. package/themes/README.md +69 -0
  245. package/themes/_TEMPLATE.json +147 -0
  246. package/themes/biker.json +147 -0
  247. package/themes/commentator.json +147 -0
  248. package/themes/cyberpunk.json +147 -0
  249. package/themes/gamer.json +147 -0
  250. package/themes/imperial.json +147 -0
  251. package/themes/joker.json +147 -0
  252. package/themes/marvin.json +147 -0
  253. package/themes/mean.json +147 -0
  254. package/themes/military.json +147 -0
  255. package/themes/neutral.json +147 -0
  256. package/themes/opencode/README.md +58 -0
  257. package/themes/opencode/catppuccin.json +27 -0
  258. package/themes/opencode/dracula.json +27 -0
  259. package/themes/opencode/everforest.json +27 -0
  260. package/themes/opencode/gruvbox.json +27 -0
  261. package/themes/opencode/kanagawa.json +27 -0
  262. package/themes/opencode/neon.json +27 -0
  263. package/themes/opencode/nord.json +27 -0
  264. package/themes/opencode/rosepine.json +27 -0
  265. package/themes/opencode/tokyonight.json +27 -0
  266. package/themes/pirate.json +147 -0
  267. package/themes/sports.json +147 -0
  268. package/themes/verstappen.json +147 -0
  269. package/themes/wizard.json +147 -0
  270. package/web/dist/assets/About-DX1-VTBP.js +1 -0
  271. package/web/dist/assets/Activity-Dp9NaXOn.js +1 -0
  272. package/web/dist/assets/ActivityDetail-HpOBtrXS.js +1 -0
  273. package/web/dist/assets/Diff-Ct4G3Se8.js +5 -0
  274. package/web/dist/assets/Doctor-DGjkZFuA.js +1 -0
  275. package/web/dist/assets/Graph-b8o-S6kv.js +1 -0
  276. package/web/dist/assets/Harnesses-gr3-aHBF.js +1 -0
  277. package/web/dist/assets/Laws-DTwF5tTC.js +1 -0
  278. package/web/dist/assets/Library-BEZscyaQ.js +1 -0
  279. package/web/dist/assets/Markdown-k_LSLBGq.js +1 -0
  280. package/web/dist/assets/Profile-DydukTxP.js +1 -0
  281. package/web/dist/assets/Rules-CIRG4Ldt.js +1 -0
  282. package/web/dist/assets/Skills-Buoi1IBr.js +1 -0
  283. package/web/dist/assets/StatusBadge-CE_AJttl.js +1 -0
  284. package/web/dist/assets/Themes-CI4o3gJC.js +2 -0
  285. package/web/dist/assets/activity-BCW7e6Cy.js +1 -0
  286. package/web/dist/assets/audiowide-latin-400-normal-6GFCX7ni.woff2 +0 -0
  287. package/web/dist/assets/geist-mono-latin-400-normal-DKaoCDn5.woff2 +0 -0
  288. package/web/dist/assets/geist-mono-latin-500-normal-C3sF8Y1B.woff2 +0 -0
  289. package/web/dist/assets/geist-mono-latin-600-normal-BHzjB6_C.woff2 +0 -0
  290. package/web/dist/assets/hanken-grotesk-latin-400-italic-Bserb8X5.woff2 +0 -0
  291. package/web/dist/assets/hanken-grotesk-latin-400-normal-BG6hkEXj.woff2 +0 -0
  292. package/web/dist/assets/hanken-grotesk-latin-500-italic-BGfwwhek.woff2 +0 -0
  293. package/web/dist/assets/hanken-grotesk-latin-500-normal-DrDcrrxK.woff2 +0 -0
  294. package/web/dist/assets/hanken-grotesk-latin-600-normal-CIXX6EOa.woff2 +0 -0
  295. package/web/dist/assets/hanken-grotesk-latin-700-normal-CeQ8H3UY.woff2 +0 -0
  296. package/web/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  297. package/web/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  298. package/web/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  299. package/web/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
  300. package/web/dist/assets/ibm-plex-serif-latin-500-normal-REKAfBVG.woff2 +0 -0
  301. package/web/dist/assets/ibm-plex-serif-latin-600-normal-DAxitmSu.woff2 +0 -0
  302. package/web/dist/assets/index-B1PIx7RM.js +1 -0
  303. package/web/dist/assets/index-BnTQUSUz.js +12 -0
  304. package/web/dist/assets/index-Ck2iCdaC.js +2 -0
  305. package/web/dist/assets/index-DUCsDv7z.css +1 -0
  306. package/web/dist/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  307. package/web/dist/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  308. package/web/dist/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  309. package/web/dist/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  310. package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  311. package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  312. package/web/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  313. package/web/dist/assets/marked.esm-JKvyS0Pp.js +46 -0
  314. package/web/dist/assets/oswald-latin-400-normal-BAFo8sP8.woff2 +0 -0
  315. package/web/dist/assets/oswald-latin-500-normal-B-eyT0Dw.woff2 +0 -0
  316. package/web/dist/assets/oswald-latin-600-normal-plFmSr5g.woff2 +0 -0
  317. package/web/dist/assets/oswald-latin-700-normal-vy6hmKSr.woff2 +0 -0
  318. package/web/dist/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  319. package/web/dist/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  320. package/web/dist/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  321. package/web/dist/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  322. package/web/dist/assets/react-DOpiTn5v.js +32 -0
  323. package/web/dist/assets/roman-DkYN0LX9.js +1 -0
  324. package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  325. package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
  326. package/web/dist/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
  327. package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  328. package/web/dist/assets/space-mono-latin-400-italic-YylcN9Ay.woff2 +0 -0
  329. package/web/dist/assets/space-mono-latin-400-normal-Rg4St2Dn.woff2 +0 -0
  330. package/web/dist/assets/space-mono-latin-700-italic-vNvENeTh.woff2 +0 -0
  331. package/web/dist/assets/space-mono-latin-700-normal-mWgeinG7.woff2 +0 -0
  332. package/web/dist/icon-maskable.svg +10 -0
  333. package/web/dist/icon.svg +8 -0
  334. package/web/dist/index.html +21 -0
  335. package/web/dist/manifest.webmanifest +14 -0
  336. package/web/dist/sw.js +40 -0
  337. package/web/src/pages/Laws.jsx +230 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * The OpenCode-native extras, in Node — everything `emit_opencode` writes beyond the
3
+ * agents and skills that `js/native.mjs` already owns.
4
+ *
5
+ * A translation of the second half of `_build_emit.py`: the branded and curated colour
6
+ * themes, the opt-in primary agent, the opt-in slash-command layer plus the always-on
7
+ * `/ponytail` switch, the verbatim plugin and workflow copies, and the git-ignored
8
+ * `agent-overrides.json` stub with its staleness notice.
9
+ *
10
+ * WHY THIS IS THE UNIT THAT SETTLES JSON. `jsonDumps` shipped string-only on purpose,
11
+ * because nothing on the Node side wrote a JSON *container* yet and its signature would
12
+ * have been a guess. This is that code. Measured across all 27 `json.dumps` call sites in
13
+ * the generator, the render half needs exactly two container shapes — `indent=2`, and
14
+ * `indent=2, ensure_ascii=False` — and both are `jsonDumpsIndent`, because passing an
15
+ * indent is what switches Python's separators to the ones `JSON.stringify` already uses.
16
+ *
17
+ * TWO STREAMS, AND THE SPLIT IS NOT TIDY. `_warn_if_overrides_stale` prints to STDOUT
18
+ * while every warning in `js/native.mjs` goes to stderr. That is the Python's own
19
+ * inconsistency and it is reproduced, not corrected: the parity gate compares both
20
+ * streams, so "fixing" it here would be a divergence. It is safe because this is the
21
+ * generator's CLI, which prints progress to stdout by design — unlike a hook path, where
22
+ * a stray stdout byte silently disarms a blocking gate.
23
+ */
24
+ import { existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
25
+ import path from 'node:path';
26
+ import {
27
+ writeText, readText, copyFile, jsonDumps, jsonDumpsIndent, parseJson, comparePaths,
28
+ } from './lib/fs.mjs';
29
+ import { descOf, stripSkillBodyLinks, pushOverrideLines } from './native.mjs';
30
+
31
+ /** ANSI colour-name -> integer (0-7). Mirrors `_build_emit._ANSI`. */
32
+ const ANSI = {
33
+ black: 0, red: 1, green: 2, yellow: 3, blue: 4, magenta: 5, cyan: 6, white: 7,
34
+ };
35
+
36
+ /**
37
+ * `_build_emit._SLOT_ROLE` — OpenCode theme slot -> palette role.
38
+ *
39
+ * KEY ORDER IS OUTPUT. `_color_theme_json` builds its dict by iterating this map, and the
40
+ * emitted JSON preserves insertion order in both languages, so a reordering here is a
41
+ * byte-level diff in ten shipped theme files.
42
+ */
43
+ const SLOT_ROLE = {
44
+ primary: 'accent', secondary: 'secondary', accent: 'accent',
45
+ error: 'err', warning: 'warn', success: 'ok', info: 'accent',
46
+ text: 'fg', textMuted: 'fgMuted',
47
+ background: 'bg', backgroundPanel: 'bgPanel', backgroundElement: 'bgElement',
48
+ border: 'border', borderActive: 'accent', borderSubtle: 'border',
49
+ diffAdded: 'ok', diffRemoved: 'err', diffContext: 'fgMuted',
50
+ diffHunkHeader: 'accent', diffHighlightAdded: 'ok', diffHighlightRemoved: 'err',
51
+ diffAddedBg: 'addBg', diffRemovedBg: 'delBg', diffContextBg: 'bgPanel',
52
+ diffLineNumber: 'fgMuted', diffAddedLineNumberBg: 'addBg', diffRemovedLineNumberBg: 'delBg',
53
+ markdownText: 'fg', markdownHeading: 'accent', markdownLink: 'secondary',
54
+ markdownLinkText: 'accent', markdownCode: 'ok', markdownBlockQuote: 'fgMuted',
55
+ markdownEmph: 'warn', markdownStrong: 'warn', markdownHorizontalRule: 'border',
56
+ markdownListItem: 'accent', markdownListEnumeration: 'accent', markdownImage: 'secondary',
57
+ markdownImageText: 'accent', markdownCodeBlock: 'bgElement',
58
+ syntaxComment: 'comment', syntaxKeyword: 'kw', syntaxFunction: 'fn',
59
+ syntaxVariable: 'fg', syntaxString: 'str', syntaxNumber: 'num',
60
+ syntaxType: 'type', syntaxOperator: 'kw', syntaxPunctuation: 'fgMuted',
61
+ };
62
+
63
+ /** Slots that become the terminal default in the transparent flavour. */
64
+ const TRANSPARENT_NONE = new Set([
65
+ 'background', 'backgroundPanel', 'backgroundElement', 'diffContextBg', 'markdownCodeBlock',
66
+ ]);
67
+
68
+ /** Mirrors `_build_emit.AGENT_OVERRIDES_STUB`. Key order is emitted order. */
69
+ const AGENT_OVERRIDES_STUB = {
70
+ _comment: (
71
+ 'Per-agent OpenCode overrides. EMPTY = every agent inherits OpenCode\'s current '
72
+ + 'model as-is (the default — nothing changes). Add entries keyed by agent name; '
73
+ + 'supported keys: model, temperature, variant (reasoning effort, e.g. "high"), '
74
+ + 'steps (max tool-iterations — a runaway-loop cap). e.g. '
75
+ + '"reviewer": {"model": "anthropic/claude-haiku-4-5", "temperature": 0.1, '
76
+ + '"variant": "high", "steps": 20}. '
77
+ + 'Host-specific; git-ignored. A future TUI screen edits this — rebuild to apply.'
78
+ ),
79
+ agents: {},
80
+ };
81
+
82
+ /** Mirrors `_build_emit.COMMAND_SET` — the hot skills worth a one-keystroke trigger. */
83
+ const COMMAND_SET = ['commit', 'plan', 'code-review', 'review-response',
84
+ 'ship', 'debug', 'research'];
85
+
86
+ /** Mirrors `_build_emit.PONYTAIL_COMMAND_BODY`. */
87
+ const PONYTAIL_COMMAND_BODY =
88
+ 'Ponytail level requested: $ARGUMENTS\n\n'
89
+ + 'The geneseed-ponytail plugin records this level and, from your next turn, appends '
90
+ + 'the matching "laziest solution that works" ruleset to your system prompt — honour '
91
+ + 'it going forward, not just this message. An empty argument means `full`; `off` '
92
+ + 'disables ponytail. Acknowledge the new level in one line, then continue.\n';
93
+
94
+ /** `_build_emit._truthy_env`. */
95
+ function truthyEnv(name) {
96
+ // `str.lower()` and `toLowerCase()` disagree on a few code points (dotted/dotless I
97
+ // among them); the accepted values are ASCII, so the sets coincide here.
98
+ return ['1', 'on', 'true', 'yes'].includes((process.env[name] || '').toLowerCase());
99
+ }
100
+
101
+ /** `_build_core.source_release_version`. Belongs to core; travels in `cfg` until core lands. */
102
+ export function sourceReleaseVersion(cfg) {
103
+ try {
104
+ const data = parseJson(readText(cfg.config));
105
+ const v = data && typeof data === 'object' && !Array.isArray(data) ? data.version : null;
106
+ if (typeof v === 'string' && v) return v;
107
+ } catch {
108
+ // OSError / JSONDecodeError — degrade to "not newer" rather than raising.
109
+ }
110
+ return '0.0.0';
111
+ }
112
+
113
+ /** `_build_emit._theme_json` — a COMPLETE terminal-native theme tinted by ACCENT. */
114
+ export function themeJson(theme) {
115
+ const raw = theme && theme.ACCENT !== undefined ? theme.ACCENT : 'cyan';
116
+ const key = String(raw).toLowerCase();
117
+ const acc = Object.prototype.hasOwnProperty.call(ANSI, key) ? ANSI[key] : 6;
118
+ const GRAY = 8;
119
+ const GREEN = 2;
120
+ const RED = 1;
121
+ const YEL = 3;
122
+ const MAG = 5;
123
+ const NONE = 'none';
124
+ const t = {
125
+ primary: acc, secondary: MAG, accent: acc,
126
+ error: RED, warning: YEL, success: GREEN, info: acc,
127
+ text: NONE, textMuted: GRAY,
128
+ background: NONE, backgroundPanel: NONE, backgroundElement: NONE,
129
+ border: GRAY, borderActive: acc, borderSubtle: GRAY,
130
+ diffAdded: GREEN, diffRemoved: RED, diffContext: GRAY,
131
+ diffHunkHeader: acc, diffHighlightAdded: GREEN, diffHighlightRemoved: RED,
132
+ diffAddedBg: NONE, diffRemovedBg: NONE, diffContextBg: NONE,
133
+ diffLineNumber: GRAY, diffAddedLineNumberBg: NONE, diffRemovedLineNumberBg: NONE,
134
+ markdownText: NONE, markdownHeading: acc, markdownLink: MAG,
135
+ markdownLinkText: acc, markdownCode: GREEN, markdownBlockQuote: GRAY,
136
+ markdownEmph: YEL, markdownStrong: YEL, markdownHorizontalRule: GRAY,
137
+ markdownListItem: acc, markdownListEnumeration: acc, markdownImage: MAG,
138
+ markdownImageText: acc, markdownCodeBlock: NONE,
139
+ syntaxComment: GRAY, syntaxKeyword: MAG, syntaxFunction: acc,
140
+ syntaxVariable: NONE, syntaxString: GREEN, syntaxNumber: MAG,
141
+ syntaxType: acc, syntaxOperator: MAG, syntaxPunctuation: NONE,
142
+ };
143
+ return { $schema: 'https://opencode.ai/theme.json', theme: t };
144
+ }
145
+
146
+ /** `_build_emit._write_theme` — `<themes_dir>/geneseed-<theme>.json`. */
147
+ export function writeTheme(themesDir, themeName, theme) {
148
+ mkdirSync(themesDir, { recursive: true });
149
+ const dest = path.join(themesDir, `geneseed-${themeName}.json`);
150
+ writeText(dest, `${jsonDumpsIndent(themeJson(theme))}\n`);
151
+ return dest;
152
+ }
153
+
154
+ /** `_build_emit._color_theme_json`. */
155
+ /**
156
+ * `_build_emit._PALETTE_ROLES` — every role some slot reads, as a set.
157
+ *
158
+ * Derived from `SLOT_ROLE` rather than listed, exactly as the Python derives it from
159
+ * `_SLOT_ROLE.values()`: a second literal list would be a second thing to keep in agreement,
160
+ * and `harness theme` prints the roles a user's palette is MISSING, so a drift between the
161
+ * two would refuse a complete palette or accept an incomplete one.
162
+ */
163
+ export const PALETTE_ROLES = new Set(Object.values(SLOT_ROLE));
164
+
165
+ export function colorThemeJson(palette, transparent) {
166
+ const t = {};
167
+ for (const [slot, role] of Object.entries(SLOT_ROLE)) {
168
+ if (transparent && TRANSPARENT_NONE.has(slot)) {
169
+ t[slot] = 'none';
170
+ continue;
171
+ }
172
+ // `palette[role]` is a plain subscript in Python: a palette missing a role raises
173
+ // KeyError and aborts the emit. A bare JS lookup would yield undefined, which
174
+ // JSON.stringify DROPS — silently emitting a theme with missing slots.
175
+ if (!Object.prototype.hasOwnProperty.call(palette, role)) {
176
+ throw new Error(`palette has no role '${role}' (slot '${slot}')`);
177
+ }
178
+ t[slot] = palette[role];
179
+ }
180
+ return { $schema: 'https://opencode.ai/theme.json', theme: t };
181
+ }
182
+
183
+ /** `_build_emit.color_theme_files` — shipped sources, `_`-prefixed scaffolds excluded. */
184
+ export function colorThemeFiles(cfg) {
185
+ if (!existsSync(cfg.colorThemes) || !statSync(cfg.colorThemes).isDirectory()) return [];
186
+ return readdirSync(cfg.colorThemes)
187
+ .filter((n) => n.endsWith('.json') && !n.startsWith('_'))
188
+ .map((n) => path.join(cfg.colorThemes, n))
189
+ .sort(comparePaths);
190
+ }
191
+
192
+ /** `_build_emit._write_color_themes` — each curated palette in both flavours. */
193
+ export function writeColorThemes(cfg, themesDir) {
194
+ mkdirSync(themesDir, { recursive: true });
195
+ const written = [];
196
+ for (const src of colorThemeFiles(cfg)) {
197
+ const spec = parseJson(readText(src));
198
+ const palette = spec.palette;
199
+ for (const [flavour, transparent] of [['solid', false], ['transparent', true]]) {
200
+ const dest = path.join(themesDir, `geneseed-${spec.name}-${flavour}.json`);
201
+ writeText(dest, `${jsonDumpsIndent(colorThemeJson(palette, transparent))}\n`);
202
+ written.push(dest);
203
+ }
204
+ }
205
+ return written;
206
+ }
207
+
208
+ /** `_build_emit._load_agent_overrides`'s sibling: has this install's stub gone stale? */
209
+ function warnIfOverridesStale(cfg, dest) {
210
+ let data;
211
+ try {
212
+ data = parseJson(readText(dest));
213
+ } catch {
214
+ return;
215
+ }
216
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return;
217
+ // Python's `not data.get("agents")` — an EMPTY override map is never worth flagging,
218
+ // drift or not.
219
+ const agents = data.agents;
220
+ if (!agents || (typeof agents === 'object' && Object.keys(agents).length === 0)) return;
221
+ const stamped = data._version;
222
+ const current = sourceReleaseVersion(cfg);
223
+ if (stamped !== current) {
224
+ // STDOUT, matching the Python. See the module header: not a hook path.
225
+ process.stdout.write('[geneseed] agent-overrides.json was written for Geneseed '
226
+ + `${typeof stamped === 'string' && stamped ? stamped : 'unknown version'}, `
227
+ + `current is ${current} — review your overrides against the updated `
228
+ + 'agent specs\n');
229
+ }
230
+ }
231
+
232
+ /** `_build_emit.ensure_agent_overrides_stub` — drop once, never overwrite. */
233
+ export function ensureAgentOverridesStub(cfg, base) {
234
+ const dest = path.join(base, 'agent-overrides.json');
235
+ if (existsSync(dest)) {
236
+ warnIfOverridesStale(cfg, dest);
237
+ return;
238
+ }
239
+ // `dict(AGENT_OVERRIDES_STUB)` then `stub["_version"] = ...` — a shallow copy with the
240
+ // new key appended LAST, which is the order it is written in.
241
+ const stub = { ...AGENT_OVERRIDES_STUB, _version: sourceReleaseVersion(cfg) };
242
+ writeText(dest, `${jsonDumpsIndent(stub, { ensureAscii: false })}\n`);
243
+ }
244
+
245
+ /** `_build_emit._write_primary_agent` — the opt-in `mode: primary` orchestrator. */
246
+ export function writePrimaryAgent(cfg, agentsDir, overrides) {
247
+ if (!truthyEnv('GENESEED_PRIMARY') || !existsSync(cfg.primaryAgentSrc)
248
+ || !statSync(cfg.primaryAgentSrc).isFile()) {
249
+ return null;
250
+ }
251
+ const body = readText(cfg.primaryAgentSrc).replace(/^\n+/, '');
252
+ const desc = 'Primary orchestrator — works by the harness Rules and delegates to the '
253
+ + 'capability subagents.';
254
+ const fm = [`description: ${jsonDumps(desc)}`, 'mode: primary', 'color: primary'];
255
+ const ov = (Object.prototype.hasOwnProperty.call(overrides, 'orchestrator')
256
+ && overrides.orchestrator) || {};
257
+ pushOverrideLines(fm, ov);
258
+ const dest = path.join(agentsDir, 'orchestrator.md');
259
+ mkdirSync(path.dirname(dest), { recursive: true });
260
+ writeText(dest, `---\n${fm.join('\n')}\n---\n\n${body}`);
261
+ return dest;
262
+ }
263
+
264
+ /** `_build_emit._write_command_layer` — the opt-in /slash commands. */
265
+ export function writeCommandLayer(cfg, items, commandDir) {
266
+ if (!truthyEnv('GENESEED_COMMANDS')) return [];
267
+ const byName = new Map();
268
+ for (const { text, src } of items) {
269
+ if (text === null) continue;
270
+ const sp = path.relative(cfg.src, src).split(path.sep);
271
+ if (sp.length === 2 && sp[0] === 'skills' && sp[1].endsWith('.md')
272
+ && !sp[1].startsWith('_')) {
273
+ byName.set(sp[1].slice(0, -3), text);
274
+ }
275
+ }
276
+ const written = [];
277
+ for (const name of COMMAND_SET) {
278
+ const text = byName.get(name);
279
+ if (text === undefined) continue;
280
+ const desc = descOf(text);
281
+ const body = stripSkillBodyLinks(text.replace(/^\n+/, ''));
282
+ const dest = path.join(commandDir, `${name}.md`);
283
+ mkdirSync(path.dirname(dest), { recursive: true });
284
+ writeText(dest, `---\ndescription: ${jsonDumps(desc)}\n---\n\n${body}`);
285
+ written.push(dest);
286
+ }
287
+ return written;
288
+ }
289
+
290
+ /** `_build_emit._write_ponytail_command` — registered UNCONDITIONALLY. */
291
+ export function writePonytailCommand(commandDir) {
292
+ const dest = path.join(commandDir, 'ponytail.md');
293
+ mkdirSync(path.dirname(dest), { recursive: true });
294
+ const desc = 'Set the ponytail minimal-code level for the session: lite | full | ultra | off';
295
+ writeText(dest, `---\ndescription: ${jsonDumps(desc)}\n---\n\n${PONYTAIL_COMMAND_BODY}`);
296
+ return dest;
297
+ }
298
+
299
+ /** `_copy_plugins` / `_copy_workflows` — maintained files, copied verbatim (copy2, so LF). */
300
+ function copyJsDir(srcDir, dst, owned, prefix) {
301
+ let n = 0;
302
+ if (existsSync(srcDir) && statSync(srcDir).isDirectory()) {
303
+ mkdirSync(dst, { recursive: true });
304
+ const files = readdirSync(srcDir)
305
+ .filter((name) => name.endsWith('.js'))
306
+ .map((name) => path.join(srcDir, name))
307
+ .sort(comparePaths);
308
+ for (const js of files) {
309
+ copyFile(js, path.join(dst, path.basename(js)));
310
+ if (owned !== null && owned !== undefined) owned.push(`${prefix}/${path.basename(js)}`);
311
+ n += 1;
312
+ }
313
+ }
314
+ return n;
315
+ }
316
+
317
+ /** `_build_emit._copy_plugins`. */
318
+ export function copyPlugins(cfg, dst, owned = null) {
319
+ return copyJsDir(cfg.pluginSrc, dst, owned, 'plugins');
320
+ }
321
+
322
+ /** `_build_emit._copy_workflows`. */
323
+ export function copyWorkflows(cfg, dst, owned = null) {
324
+ return copyJsDir(cfg.workflowSrc, dst, owned, 'workflows');
325
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `_install_registry.py` — the persistent list of folders a harness was deployed into.
3
+ *
4
+ * MOVED OUT OF `bin/geneseed.mjs` IN P5f, for the third time the same arithmetic has decided
5
+ * a module boundary in this port (P5c took the host resolvers, P5d the checkout paths). The
6
+ * driver WRITES the registry, one row per per-repo emit; `harness rebuild-all` READS it to
7
+ * find every install it must re-emit, and `bin/geneseed-cli.mjs` may not reach into a driver
8
+ * for a reader. `js/generate.mjs` importing `main` from `bin/geneseed.mjs` is not a
9
+ * precedent for it either: `harness build` IS the driver, and a registry reader is not.
10
+ *
11
+ * The move is licensed the same way the two before it were: `tests/golden.py` drives
12
+ * `bin/geneseed.mjs` over 259 cells with `$XDG_CONFIG_HOME` redirected into the sandbox and
13
+ * compares `installs.json` byte-for-byte, so a changed path or a dropped prune fails cells
14
+ * rather than passing quietly.
15
+ */
16
+ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync } from 'node:fs';
17
+ import path from 'node:path';
18
+ import os from 'node:os';
19
+
20
+ import { expanduser } from './hosts.mjs';
21
+ import { writeText, parseJson, jsonDumpsIndent, pyPathStr } from './lib/fs.mjs';
22
+
23
+ const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
24
+ const isFile = (p) => { try { return statSync(p).isFile(); } catch { return false; } };
25
+
26
+ /** `_install_registry._path()` — `$XDG_CONFIG_HOME/geneseed/installs.json`. */
27
+ export function registryPath() {
28
+ const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
29
+ return path.join(base, 'geneseed', 'installs.json');
30
+ }
31
+
32
+ export function registryLoad() {
33
+ try {
34
+ const data = parseJson(readFileSync(registryPath(), 'utf8'));
35
+ return Array.isArray(data) ? data.map(String) : [];
36
+ } catch { return []; }
37
+ }
38
+
39
+ export function registrySave(items) {
40
+ try {
41
+ const file = registryPath();
42
+ mkdirSync(path.dirname(file), { recursive: true });
43
+ const tmp = `${file}.tmp`;
44
+ // `jsonDumpsIndent` for the same reason as the manifest: these are absolute paths, and
45
+ // a machine whose username carries an accent writes one here.
46
+ writeText(tmp, `${jsonDumpsIndent(items)}\n`);
47
+ renameSync(tmp, file);
48
+ } catch { /* best-effort: a registry hiccup must never break a deploy */ }
49
+ }
50
+
51
+ /**
52
+ * `_install_registry.record` — idempotent, best-effort, and it must NEVER raise into a build.
53
+ *
54
+ * `realpathSync` rather than `path.resolve`, because Python's `Path.resolve()` returns the
55
+ * filesystem's own casing and follows links; `path.resolve` only makes a path absolute. The
56
+ * registry is compared byte-for-byte by `tests/golden.py` (XDG is redirected into the
57
+ * sandbox), so a differently-cased entry is a failing cell.
58
+ */
59
+ export function registryRecord(dir) {
60
+ try {
61
+ let root;
62
+ try { root = realpathSync.native(dir); } catch { root = path.resolve(dir); }
63
+ const cur = registryLoad();
64
+ if (!cur.includes(root)) registrySave([...cur, root]);
65
+ } catch { /* a registry hiccup must never fail a build */ }
66
+ }
67
+
68
+ /**
69
+ * `_install_registry.roots()` — live registered deploy roots, pruning the file as it reads.
70
+ *
71
+ * The prune is a WRITE, and reproducing it matters: `installs.json` is compared byte-for-byte,
72
+ * so a read that failed to drop a dead row would leave the two implementations holding
73
+ * different registries. The snapshot is taken once on purpose (its own comment says why):
74
+ * comparing `kept` against a SECOND read could clobber a root a concurrent `record()`
75
+ * appended between the two.
76
+ */
77
+ export function registryRoots() {
78
+ const original = registryLoad();
79
+ const out = [];
80
+ const kept = [];
81
+ const seen = new Set();
82
+ for (const s of original) {
83
+ // `Path(s).expanduser()`, and `pyPathStr` is the `str()` around it — which is where the
84
+ // NORMALISATION happens. Python turns `C:/x/other` into `C:\x\other` before it is
85
+ // compared, kept, returned and printed, so a registry entry spelled with forward slashes
86
+ // (routine: every tool that writes one from Git Bash) makes Python rewrite the file and
87
+ // print a backslash path while a raw `expanduser` prints the entry verbatim and leaves
88
+ // installs.json alone. Invisible to every cell before P5h, because `rebuild-all`'s
89
+ // registry cells seed native spellings; `uninstall`'s inventory prints the root.
90
+ // `expanduser` now REFUSES a `~user` entry (js/hosts.mjs's docblock) instead of passing
91
+ // it through. This loop scans STORED registry rows, not a fresh CLI argument — a single
92
+ // bad row (hand-edited, or written before this refusal existed) must not abort the scan
93
+ // for every other install, so it is dropped exactly like any other dead row rather than
94
+ // propagated. `rebuild-all`'s contract is "one broken install never blocks the rest".
95
+ let root;
96
+ try { root = pyPathStr(expanduser(s)); } catch { continue; }
97
+ let key;
98
+ try { key = existsSync(root) ? realpathSync.native(root) : root; } catch { key = root; }
99
+ if (seen.has(key)) continue;
100
+ if (isDir(root) && isFile(path.join(root, '.geneseed-emit'))) {
101
+ seen.add(key);
102
+ kept.push(root);
103
+ out.push(root);
104
+ }
105
+ }
106
+ if (kept.length !== original.length || kept.some((v, i) => v !== original[i])) {
107
+ registrySave(kept);
108
+ }
109
+ return out;
110
+ }