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
package/js/render.mjs ADDED
@@ -0,0 +1,282 @@
1
+ /**
2
+ * The generator's render core, in Node — the first half of the port.
3
+ *
4
+ * This is a faithful translation of `_build_render.py`'s pure text pipeline: load a
5
+ * theme, lay the fixed neutral STRUCTURE over it, inline INCLUDE directives, resolve
6
+ * AGENT.md's CATALOG blocks, substitute {{TOKENS}}, and hand back every source file with
7
+ * its themed output path. It writes nothing and reads nothing but `src/` and `themes/`.
8
+ *
9
+ * WHY THIS HALF AND NOT MORE. The emits split into five stages —
10
+ * RENDER* -> WIRE* -> PRUNE -> MANIFEST -> VERIFY (see `_build_global._emit_claude_core`
11
+ * and tests/test_emit_phase_order.py). RENDER produces files Geneseed owns wholesale;
12
+ * WIRE reconciles files the user co-owns (settings.json, opencode.json, the CLAUDE.md
13
+ * managed block) and stays in Python for now, in `_build_settings.py`. Node takes RENDER
14
+ * because it is the only stage whose output is a pure function of the source tree.
15
+ *
16
+ * PARITY IS THE CONTRACT, NOT THE INTENT. Every function here has a named Python
17
+ * counterpart and must produce byte-identical output for it; tests/test_render_parity.py
18
+ * drives both over every theme x footprint x posture x mode and byte-compares the
19
+ * written trees. Where Python and JS semantics differ silently — path sort order,
20
+ * `.suffix`, `splitlines`, universal newlines — the difference is spelled out at the
21
+ * call site rather than left to the reader.
22
+ *
23
+ * NO MODULE-LEVEL MUTABLE CONFIG. `_build_core` is the single owner of the source/theme
24
+ * roots and the posture/mode selection precisely because copies of those names made
25
+ * redirects half-work. The JS side keeps that property structurally: the four values
26
+ * travel in one explicit `cfg` object, so there is nothing to copy and nothing to
27
+ * redirect.
28
+ */
29
+ import { readdirSync, statSync, existsSync } from 'node:fs';
30
+ import path from 'node:path';
31
+ import { readText, parseJson, normcase, comparePaths } from './lib/fs.mjs';
32
+
33
+ /** Document STRUCTURE is theme-INDEPENDENT — mirrors `_build_render.STRUCTURE`. */
34
+ export const STRUCTURE = {
35
+ HARNESS: 'Geneseed', CHARTER: 'Charter', CONTEXT: 'Context',
36
+ SCRIPT: 'Script', SCRIPTS: 'Scripts',
37
+ DIR_LAWS: 'laws', DIR_AGENTS: 'agents', DIR_SKILLS: 'skills', DIR_MEMORY: 'memory',
38
+ DIR_NOTEBOOK: 'notebook',
39
+ };
40
+
41
+ /**
42
+ * Source top-level dirs whose OUTPUT name is themed. Mirrors `SRC_DIR_TOKENS`.
43
+ * Exported because `js/emit.mjs`'s `build` resolves the same tokens to decide which
44
+ * dirs it owns and wipes — one owner, so the two cannot disagree about a folder name.
45
+ */
46
+ export const SRC_DIR_TOKENS = {
47
+ laws: 'DIR_LAWS', agents: 'DIR_AGENTS', skills: 'DIR_SKILLS',
48
+ memory: 'DIR_MEMORY', notebook: 'DIR_NOTEBOOK',
49
+ };
50
+
51
+ const TEXT_SUFFIXES = new Set(['.md', '.tmpl', '.json', '.txt', '.yml', '.yaml']);
52
+
53
+ const TOKEN_RE = /\{\{([A-Z_]+)\}\}/g;
54
+ const INCLUDE_RE = /^[ \t]*<!--[ \t]*INCLUDE:[ \t]*(?<path>[^ \t]+)[ \t]*-->[ \t]*$/gm;
55
+ const CATALOG_BLOCK_RE =
56
+ /[ \t]*<!-- CATALOG:begin -->\n(?<table>[\s\S]*?)[ \t]*<!-- CATALOG:else -->\n(?<pointer>[\s\S]*?)[ \t]*<!-- CATALOG:end -->\n/g;
57
+
58
+ /**
59
+ * `PurePath.suffix`. Not `path.extname`: Python returns '' for a name whose only dot is
60
+ * leading (`.gitignore`) or trailing (`foo.`), where `extname` returns '' and '.'
61
+ * respectively. Neither spelling is in TEXT_SUFFIXES, so the divergence is invisible
62
+ * today — which is exactly why it is written out rather than relied upon.
63
+ */
64
+ function suffixOf(name) {
65
+ const i = name.lastIndexOf('.');
66
+ return 0 < i && i < name.length - 1 ? name.slice(i) : '';
67
+ }
68
+
69
+ /** `Path.rglob("*")` restricted to files, then `sorted()`. */
70
+ function sortedSourceFiles(src) {
71
+ const out = [];
72
+ (function walk(dir) {
73
+ for (const name of readdirSync(dir)) {
74
+ const full = path.join(dir, name);
75
+ if (name === '__pycache__') continue; // `"__pycache__" in path.parts`
76
+ if (statSync(full).isDirectory()) walk(full); // rglob yields dirs; render_all skips them
77
+ else out.push(full);
78
+ }
79
+ })(src);
80
+ return out.sort(comparePaths); // `sorted()` — case-folded on Windows only
81
+ }
82
+
83
+ /** `_build_render.load_theme`. Throws where Python calls `sys.exit`. */
84
+ export function loadTheme(cfg, name) {
85
+ const p = path.join(cfg.themes, `${name}.json`);
86
+ if (!existsSync(p)) {
87
+ const available = readdirSync(cfg.themes)
88
+ .filter((f) => f.endsWith('.json') && !f.startsWith('_'))
89
+ .map((f) => f.slice(0, -5)).sort().join(', ');
90
+ // `sys.exit(<str>)` — the message on stderr, exit 1 — spelled the way
91
+ // `assertSourceComplete` spells it: written at the raise site, and the throw carries
92
+ // only the marker. It reads as an unreachable nicety under `bin/geneseed.mjs`, whose
93
+ // `--theme` is validated against `choices` before anything renders. It is NOT
94
+ // unreachable under `harness status`, which takes its theme from a `.geneseed-theme`
95
+ // marker — an unvalidated file in a user's install. A bare `throw` there printed a Node
96
+ // stack trace where Python printed one line, and the acceptance matrix is what said so.
97
+ process.stderr.write(`[geneseed] unknown theme '${name}'. available: ${available}\n`);
98
+ const e = new Error(`unknown theme '${name}'`);
99
+ e.exitCode = 1;
100
+ throw e;
101
+ }
102
+ // parseJson, not JSON.parse: one rule for every JSON the generator reads, so a numeric
103
+ // theme value keeps the int/float distinction Python's `str()` and `repr()` render
104
+ // differently. No shipped theme has one today; `_theme_json`'s ACCENT and the
105
+ // AGENT_COLORS validation warning are both one authoring typo away from needing it.
106
+ return parseJson(readText(p));
107
+ }
108
+
109
+ /** `_build_render.substitute`. */
110
+ export function substitute(text, theme) {
111
+ return text.replace(TOKEN_RE, (whole, key) => {
112
+ if (!Object.prototype.hasOwnProperty.call(theme, key)) return whole; // visible for debugging
113
+ const v = theme[key];
114
+ // Python renders `str(theme[key])`, which agrees with JS only for strings —
115
+ // `str(True)`/`String(true)` and `str({...})`/`String({...})` do not. Every one of
116
+ // the 145 tokens the source tree actually uses resolves to a string today
117
+ // (AGENT_COLORS is a dict but is never a token), so a non-string here means the
118
+ // source or a theme grew a case nobody has decided the semantics for. Fail loudly
119
+ // rather than emit `[object Object]` into somebody's AGENT.md.
120
+ if (typeof v !== 'string') {
121
+ throw new TypeError(`theme token {{${key}}} is ${typeof v}, not a string — Python's `
122
+ + `str() and JS's String() disagree here; decide the rendering explicitly`);
123
+ }
124
+ return v;
125
+ });
126
+ }
127
+
128
+ /** `_build_render._resolve_catalogs`. */
129
+ export function resolveCatalogs(text, nativeCatalog) {
130
+ return text.replace(CATALOG_BLOCK_RE, (_m, table, pointer) => (nativeCatalog ? pointer : table));
131
+ }
132
+
133
+ /**
134
+ * `re.split(r"(?m)^(?=### )", text)`.
135
+ *
136
+ * Hand-rolled rather than `String.split` with a lookahead: splitting on a zero-width
137
+ * match is a corner both engines implement, and the leading '' Python produces when the
138
+ * text starts with a heading is load-bearing (it becomes `blocks[0]`, the preamble).
139
+ */
140
+ function splitAtLawHeadings(text) {
141
+ const out = [];
142
+ let start = 0;
143
+ for (let i = 0; i < text.length; i++) {
144
+ if ((i === 0 || text[i - 1] === '\n') && text.startsWith('### ', i)) {
145
+ out.push(text.slice(start, i));
146
+ start = i;
147
+ }
148
+ }
149
+ out.push(text.slice(start));
150
+ return out;
151
+ }
152
+
153
+ /** `_build_render._terse_laws`. */
154
+ export function terseLaws(text, theme, lawsPrefix = '') {
155
+ const blocks = splitAtLawHeadings(text);
156
+ const out = [blocks[0].trimEnd()];
157
+ for (const b of blocks.slice(1)) {
158
+ // Python's `splitlines()` also breaks on VT, FF, FS-RS, NEL, LINE SEPARATOR and
159
+ // PARAGRAPH SEPARATOR, where `split('\n')` does not. The law corpus is plain
160
+ // markdown and readText has already folded CRLF and CR to LF, so the sets coincide.
161
+ // (Spelling those out by name rather than by literal is not fussiness: the first
162
+ // draft of this comment contained the actual LINE/PARAGRAPH SEPARATOR characters,
163
+ // which are JS line terminators, and they ended the comment mid-sentence.)
164
+ const lines = b.split('\n');
165
+ const heading = lines[0].trimEnd();
166
+ const body = lines.slice(1).map((l) => l.trim()).join(' ').trim();
167
+ const m = /^([\s\S]+?[.!?])(?:\s|$)/.exec(body);
168
+ out.push(`${heading}\n${m ? m[1].trim() : body}`);
169
+ }
170
+ const law = theme.LAW ?? 'Law';
171
+ const lawsDir = theme.DIR_LAWS ?? 'laws';
172
+ const pointer =
173
+ `> Each ${law} above is given in brief — the rule, not its reasoning. The `
174
+ + `complete, binding text of every ${law} is in \`${lawsPrefix}${lawsDir}/universal.md\`; `
175
+ + `read it whenever a ${law}'s application is unclear, and before any act touching `
176
+ + `secrets, deletion, git history, scope, or untrusted content.`;
177
+ return out.join('\n\n') + '\n\n' + pointer;
178
+ }
179
+
180
+ /** `_build_render.render_file`. */
181
+ export function renderFile(cfg, filePath, theme, footprint = 'full', lawsPrefix = '',
182
+ visiting = new Set(), nativeCatalog = false) {
183
+ const here = path.resolve(filePath);
184
+ let text = readText(filePath);
185
+
186
+ text = text.replace(INCLUDE_RE, (_whole, rel) => {
187
+ const target = path.resolve(cfg.src, rel);
188
+ if (!existsSync(target)) return `<!-- MISSING INCLUDE: ${rel} -->`;
189
+ if (normcase(target) === normcase(here) || visiting.has(normcase(target))) {
190
+ return `<!-- CIRCULAR INCLUDE: ${rel} -->`;
191
+ }
192
+ let inner = renderFile(cfg, target, theme, footprint, lawsPrefix,
193
+ new Set(visiting).add(normcase(here)), nativeCatalog)
194
+ .replace(/\n+$/, ''); // `.rstrip("\n")`
195
+ if (footprint === 'lean' && rel === 'laws/universal.md') {
196
+ inner = terseLaws(inner, theme, lawsPrefix);
197
+ }
198
+ return inner;
199
+ });
200
+
201
+ text = resolveCatalogs(text, nativeCatalog);
202
+ return substitute(text, theme);
203
+ }
204
+
205
+ /** `_build_render._posture_body` / `_mode_body` — one helper, both registers. */
206
+ function registerBody(cfg, theme, dir, selected, fallback) {
207
+ for (const name of [selected, fallback]) {
208
+ const p = path.join(cfg.src, dir, `${name}.md`);
209
+ if (existsSync(p) && statSync(p).isFile()) return substitute(readText(p), theme).trim();
210
+ }
211
+ return '';
212
+ }
213
+
214
+ /**
215
+ * `_build_render.effective_theme`.
216
+ *
217
+ * `cfg.structure` overrides the constant below when the driver supplies one, and it
218
+ * always does. STRUCTURE is a module-level dict on the Python side that tests MUTATE to
219
+ * simulate a themed DIR_* rename (`SrcDirRenameOrphanTests`); in-process that reaches
220
+ * every reader because the splice shares one dict object, and across a process boundary
221
+ * it reaches nothing at all unless it travels. The constant stays as the default for the
222
+ * parity harnesses, which drive this module directly.
223
+ */
224
+ export function effectiveTheme(cfg, themeName) {
225
+ const theme = { ...loadTheme(cfg, themeName), ...(cfg.structure ?? STRUCTURE) };
226
+ theme.POSTURE_BODY = registerBody(cfg, theme, 'postures', cfg.posture ?? 'peer', 'peer');
227
+ theme.MODE_BODY = registerBody(cfg, theme, 'modes', cfg.mode ?? 'direct', 'direct');
228
+ return theme;
229
+ }
230
+
231
+ /** `_build_render.themed_rel` — rename the top-level folder of an output path per theme. */
232
+ export function themedRel(rel, theme) {
233
+ const parts = rel.split(path.sep);
234
+ if (parts.length && Object.prototype.hasOwnProperty.call(SRC_DIR_TOKENS, parts[0])) {
235
+ parts[0] = theme[SRC_DIR_TOKENS[parts[0]]] ?? parts[0];
236
+ }
237
+ return parts.join(path.sep);
238
+ }
239
+
240
+ /**
241
+ * `_build_render.dest_rel` — AGENT.md.tmpl -> AGENT.md; gitignore -> .gitignore;
242
+ * everything else keeps its name.
243
+ *
244
+ * WHY THE IGNORE FILES ARE STORED WITHOUT THE DOT — the Python twin's comment carries the
245
+ * full argument. In short: `src/memory/.gitignore` and `src/notebook/.gitignore` are
246
+ * product content, and under their real names `npm install` renamed them to `.npmignore`
247
+ * on extraction (so an npm-installed Geneseed emitted bundles with NO `.gitignore` and
248
+ * leaked the user's agent memory into their repo) while `npm pack` read the notebook's
249
+ * `*` as a live ignore rule and dropped its sibling README. A file named `gitignore` is
250
+ * invisible to all three; the dot goes back on here. Emitted bytes unchanged.
251
+ */
252
+ export function destRel(rel) {
253
+ // path.join normalises the '.' that dirname returns for a bare filename, so a
254
+ // top-level AGENT.md.tmpl comes back as 'AGENT.md', not './AGENT.md'.
255
+ const name = path.basename(rel);
256
+ if (name === 'AGENT.md.tmpl') return path.join(path.dirname(rel), 'AGENT.md');
257
+ if (name === 'gitignore') return path.join(path.dirname(rel), '.gitignore');
258
+ return rel;
259
+ }
260
+
261
+ /**
262
+ * `_build_render.render_all`.
263
+ *
264
+ * Returns `{ theme, items }` where each item is `{ rel, text, src }` — `rel` is the
265
+ * themed POSIX output path, `text` is the rendered text or `null` for a binary that the
266
+ * caller copies from `src`. Python returns a tuple of tuples; the object form is the
267
+ * same data with the field names the Python docstring already uses.
268
+ */
269
+ export function renderAll(cfg, themeName, {
270
+ footprint = 'full', lawsPrefix = '', nativeCatalog = false,
271
+ } = {}) {
272
+ const theme = effectiveTheme(cfg, themeName);
273
+ const items = [];
274
+ for (const file of sortedSourceFiles(cfg.src)) {
275
+ const rel = path.relative(cfg.src, file);
276
+ const outRel = destRel(themedRel(rel, theme)).split(path.sep).join('/'); // as_posix()
277
+ items.push(TEXT_SUFFIXES.has(suffixOf(path.basename(file)))
278
+ ? { rel: outRel, text: renderFile(cfg, file, theme, footprint, lawsPrefix, new Set(), nativeCatalog), src: file }
279
+ : { rel: outRel, text: null, src: file });
280
+ }
281
+ return { theme, items };
282
+ }