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,290 @@
1
+ // Geneseed — OpenCode runtime guard plugin.
2
+ //
3
+ // Enforces the safety Laws at the tool boundary (`tool.execute.before`), the same
4
+ // "enforce by injection, don't just instruct" stance as the context plugin:
5
+ // - Law I (Sealed Secrets): block writes to private-key / credential files.
6
+ // - Law IV (Deletion Is Deliberate): block catastrophic shell commands.
7
+ // - Law VI (rule vs memory): speed-bump the first write to user-rules.md or a
8
+ // memory file — that choice belongs to the user, via the rule skill.
9
+ // - Wiki (AGENT.md §7): block mutations under a declared wiki's `protected`
10
+ // folders — the user's knowledge base sets its own no-go zones in wiki.jsonc.
11
+ // High-confidence patterns only, so legitimate work is never caught. Borderline cases
12
+ // (.env edits, force-push) are WARNED, not blocked.
13
+ //
14
+ // GENESEED_GUARD=off disable entirely.
15
+ // GENESEED_GUARD=warn downgrade every block to a warning (log, but allow).
16
+ //
17
+ // Install: dropped into the plugins dir by `build --emit opencode[-global]` (the *.js
18
+ // glob), exactly like the context and learn plugins. Errors never break a tool call.
19
+
20
+ import { promises as fs } from "node:fs"
21
+ import * as path from "node:path"
22
+ import { fileURLToPath } from "node:url"
23
+ import { homedir } from "node:os"
24
+
25
+ const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url))
26
+
27
+ // Sovereign-repo bypass — twin of the harness's own `sovereignBypass()`.
28
+ // <cfg>/excludes.json (user-owned, managed by `harness exclude`) lists folders where
29
+ // this GLOBAL install goes dormant. Any error degrades to "not excluded".
30
+ const norm = (p) => {
31
+ // Expand a leading "~" / "~/…" to the home dir — a hand-edited excludes.json entry
32
+ // like "~/vault" must resolve the same way here as it does for the Claude/Bob
33
+ // hook guard.
34
+ let raw = String(p)
35
+ if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
36
+ raw = raw === "~" ? homedir() : path.join(homedir(), raw.slice(2))
37
+ }
38
+ let s = path.resolve(raw)
39
+ return process.platform === "win32" ? s.toLowerCase() : s
40
+ }
41
+ async function sovereignBypass(cwd) {
42
+ try {
43
+ const cfg = path.dirname(PLUGIN_DIR) // plugins/ sits directly in <cfg>
44
+ const raw = await fs.readFile(path.join(cfg, "excludes.json"), "utf8")
45
+ const entries = (JSON.parse(raw).excludes) || []
46
+ const here = norm(cwd || process.cwd())
47
+ for (const e of entries) {
48
+ const p = typeof e === "string" ? e : e && e.path
49
+ if (typeof p !== "string" || !p.trim()) continue
50
+ const base = norm(p.trim()).replace(/[\\/]+$/, "")
51
+ if (here === base || here.startsWith(base + path.sep)) return true
52
+ }
53
+ } catch { /* degrade to active */ }
54
+ return false
55
+ }
56
+
57
+ const MODE = (process.env.GENESEED_GUARD || "on").toLowerCase()
58
+ const OFF = ["off", "0", "false", "no"].includes(MODE)
59
+ const WARN_ONLY = MODE === "warn"
60
+
61
+ function log(msg) { console.error(`[geneseed-guard] ${msg}`) }
62
+
63
+ // Files the agent must not write (secrets / private keys) → BLOCK.
64
+ const SECRET_RE = [
65
+ /(^|\/)id_(rsa|ed25519|ecdsa|dsa)(\.pub)?$/i,
66
+ /\.(pem|key|p12|pfx|kdbx|keystore|jks)$/i,
67
+ /(^|\/)\.aws\/credentials$/i,
68
+ /(^|\/)\.ssh\//i,
69
+ /(^|\/)\.npmrc$/i,
70
+ /(^|\/)\.pypirc$/i,
71
+ ]
72
+ // .env files are often edited legitimately → WARN, don't hard-block.
73
+ const SECRET_WARN_RE = [/(^|\/)\.env(\.[\w.-]+)?$/i]
74
+
75
+ // Catastrophic, effectively irreversible shell → BLOCK. Both Unix and Windows shells,
76
+ // since OpenCode runs natively on Windows and the agent may emit cmd / PowerShell.
77
+ const SHELL_BLOCK_RE = [
78
+ /\brm\s+-[a-z]*r[a-z]*f?[a-z]*\s+(--no-preserve-root\s+)?\/(\s|$)/, // rm -rf /
79
+ /\brm\s+-[a-z]*r[a-z]*f?[a-z]*\s+~(\/)?(\s|$)/, // rm -rf ~
80
+ /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, // fork bomb
81
+ /\bmkfs\.\w+\s+\/dev\//, // format a device
82
+ /\bdd\b[^\n]*\bof=\/dev\/(sd|nvme|hd|disk)/, // dd over a raw disk
83
+ /\bformat\s+[a-z]:/i, // Windows: format a drive
84
+ /\b(rmdir|rd)\b[^\n]*\/s\b[^\n]*\b[a-z]:[\\/]?(\s|"|$)/i, // Windows: rd /s /q C:\
85
+ /\bdel\b[^\n]*\/s\b[^\n]*\b[a-z]:[\\/]?(\s|"|$)/i, // Windows: del /s /q C:\
86
+ /\bremove-item\b[^\n]*-recurse\b[^\n]*-force\b/i, // PowerShell rm -rf
87
+ /\bremove-item\b[^\n]*-force\b[^\n]*-recurse\b/i, // (either flag order)
88
+ ]
89
+ // History-rewriting / irreversible git ops → WARN.
90
+ const SHELL_WARN_RE = [/\bgit\s+push\b[^\n]*(--force\b|-f\b)/, /\bgit\s+reset\s+--hard\b/]
91
+
92
+ function pickPath(args) {
93
+ for (const k of ["filePath", "path", "file", "target", "filename"]) {
94
+ // Normalize Windows backslashes to `/` so the secret-path patterns (which use `/`
95
+ // as the segment separator) match `C:\Users\me\.ssh\id_rsa` the same as a POSIX path.
96
+ if (args && typeof args[k] === "string") return args[k].replace(/\\/g, "/")
97
+ }
98
+ return ""
99
+ }
100
+ function pickCommand(args) {
101
+ for (const k of ["command", "cmd", "script"]) {
102
+ if (args && typeof args[k] === "string") return args[k]
103
+ }
104
+ return ""
105
+ }
106
+
107
+ // Tool-name classes, matched as SUBSTRINGS so a host's variant names
108
+ // (write_file, str_replace_editor, apply_patch, run_command, execute_bash, …) are
109
+ // still covered as the tool surface evolves. Over-matching the class is harmless:
110
+ // the inner guards act only when a real path/command argument is present AND matches
111
+ // a high-confidence secret/catastrophe pattern, so a misclassified tool with no such
112
+ // argument simply does nothing.
113
+ const WRITE_TOOLS = ["write", "edit", "patch", "create", "save", "insert", "replace"]
114
+ const SHELL_TOOLS = ["bash", "shell", "exec", "command", "terminal", "run"]
115
+ const hasAny = (name, parts) => parts.some((s) => name.includes(s))
116
+
117
+ // ---- protected wiki folders (AGENT.md §7) --------------------------------------
118
+ // wiki.jsonc (the machine-level knowledge-base manifest) may list `protected` folders
119
+ // per wiki. Mutating anything under one is denied. Same resolution chain as the
120
+ // context plugin: $GENESEED_WIKI -> $GENESEED_HARNESS/wiki.jsonc -> beside the install.
121
+ async function isFile(p) { try { return (await fs.stat(p)).isFile() } catch { return false } }
122
+
123
+ // wiki.jsonc is JSONC (the seeded stub carries a commented example): strip // and
124
+ // /* */ comments plus trailing commas before parsing — string-aware, so quoted
125
+ // "https://…" or "C:/…" values are untouched. Kept in sync with the context plugin's
126
+ // copy (plugins stay self-contained, like the other shared helpers).
127
+ function stripJsonc(text) {
128
+ let out = "", inStr = false, esc = false
129
+ for (let i = 0; i < text.length; i++) {
130
+ const c = text[i]
131
+ if (inStr) {
132
+ out += c
133
+ if (esc) esc = false
134
+ else if (c === "\\") esc = true
135
+ else if (c === '"') inStr = false
136
+ continue
137
+ }
138
+ if (c === '"') { inStr = true; out += c; continue }
139
+ if (c === "/" && text[i + 1] === "/") { while (i < text.length && text[i] !== "\n") i++; out += "\n"; continue }
140
+ if (c === "/" && text[i + 1] === "*") { i += 2; while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++; i++; continue }
141
+ out += c
142
+ }
143
+ let res = ""
144
+ inStr = false; esc = false
145
+ for (let i = 0; i < out.length; i++) {
146
+ const c = out[i]
147
+ if (inStr) {
148
+ res += c
149
+ if (esc) esc = false
150
+ else if (c === "\\") esc = true
151
+ else if (c === '"') inStr = false
152
+ continue
153
+ }
154
+ if (c === '"') { inStr = true; res += c; continue }
155
+ if (c === ",") {
156
+ let j = i + 1
157
+ while (j < out.length && /\s/.test(out[j])) j++
158
+ if (out[j] === "]" || out[j] === "}") continue
159
+ }
160
+ res += c
161
+ }
162
+ return res
163
+ }
164
+
165
+ async function wikiFile() {
166
+ const explicit = process.env.GENESEED_WIKI
167
+ if (explicit && (await isFile(explicit))) return explicit
168
+ const bases = []
169
+ if (process.env.GENESEED_HARNESS) bases.push(process.env.GENESEED_HARNESS)
170
+ bases.push(path.resolve(PLUGIN_DIR, ".."))
171
+ for (const base of bases) {
172
+ // wiki.json is the legacy name from earlier seeds — still honoured.
173
+ for (const name of ["wiki.jsonc", "wiki.json"]) {
174
+ const p = path.join(base, name)
175
+ if (await isFile(p)) return p
176
+ }
177
+ }
178
+ return null
179
+ }
180
+
181
+ // Cached absolute prefixes, refreshed on a short TTL so a wiki.jsonc edit lands
182
+ // without a restart. Compared slash-normalized and case-insensitive — vault paths on
183
+ // Windows and macOS are case-insensitive in practice, and for a guard the rare
184
+ // case-only over-match is the safe direction.
185
+ let _prot = { at: 0, prefixes: [] }
186
+ const PROT_TTL_MS = 30000
187
+ async function protectedPrefixes() {
188
+ const now = Date.now()
189
+ if (now - _prot.at < PROT_TTL_MS) return _prot.prefixes
190
+ const prefixes = []
191
+ try {
192
+ const file = await wikiFile()
193
+ if (file) {
194
+ const data = JSON.parse(stripJsonc(await fs.readFile(file, "utf8")))
195
+ for (const w of Array.isArray(data?.wikis) ? data.wikis : []) {
196
+ if (!w?.path || !Array.isArray(w.protected)) continue
197
+ for (const d of w.protected) {
198
+ if (typeof d !== "string" || !d) continue
199
+ const abs = path.resolve(w.path, d).replace(/\\/g, "/").replace(/\/+$/, "")
200
+ prefixes.push({ prefix: abs.toLowerCase() + "/",
201
+ label: `${w.name || path.basename(w.path)}: ${d}` })
202
+ }
203
+ }
204
+ }
205
+ } catch { /* unreadable manifest = no extra protection; never break a tool call */ }
206
+ _prot = { at: now, prefixes }
207
+ return prefixes
208
+ }
209
+
210
+ // ---- the rule / memory stores (Law VI) -----------------------------------------
211
+ // Whether something the user wants kept is a standing rule or a durable fact is THEIR
212
+ // call, settled through the rule skill. `tool.execute.before` can only allow or throw —
213
+ // it has no "ask the user" tier like the Claude/Bob rule-gate hook —
214
+ // so this is a SPEED BUMP, not a wall: the first write to a given path is refused with
215
+ // the Law and the skill named, and a re-issued write goes through. One beat of
216
+ // reconsideration, and a legitimate write is never trapped.
217
+ //
218
+ // Install folder and file names are theme-independent by design, so the match is on
219
+ // literal names. `user-rules.md` / `MEMORY.md` are Geneseed's own coinage and match
220
+ // anywhere; other markdown matches only inside this install's own memory/ dir, so a
221
+ // project's unrelated memory/ folder is never caught.
222
+ const RULE_STORE_BUMPED = new Set()
223
+ const nocase = (s) => (process.platform === "win32" ? s.toLowerCase() : s)
224
+ function ruleStoreTarget(p) {
225
+ const abs = (path.isAbsolute(p) ? p : path.resolve(p)).replace(/\\/g, "/")
226
+ const base = path.basename(abs).toLowerCase()
227
+ if (base === "user-rules.md") return "user-rules.md"
228
+ if (base === "memory.md") return "the memory index"
229
+ if (!base.endsWith(".md")) return null
230
+ const mem = path.join(path.dirname(PLUGIN_DIR), "memory").replace(/\\/g, "/")
231
+ return nocase(abs).startsWith(nocase(mem) + "/") ? "memory" : null
232
+ }
233
+
234
+ // Mutation-class tools for the wiki check — wider than WRITE_TOOLS because moving,
235
+ // renaming, or deleting a protected note is as destructive as overwriting it. Same
236
+ // substring stance: over-matching the class is harmless (see WRITE_TOOLS note).
237
+ const WIKI_MUTATE_TOOLS = [...WRITE_TOOLS, "delete", "remove", "rename", "move", "trash"]
238
+
239
+ export const GeneseedGuard = async () => {
240
+ return {
241
+ "tool.execute.before": async (input, output) => {
242
+ if (OFF) return
243
+ if (await sovereignBypass(process.cwd())) return
244
+ const tool = (input?.tool || input?.name || "").toLowerCase()
245
+ const args = output?.args || input?.args || {}
246
+ const deny = (why) => {
247
+ if (WARN_ONLY) { log(`WARN (would block): ${why}`); return false }
248
+ log(`BLOCKED: ${why}`)
249
+ throw new Error(`[geneseed-guard] blocked: ${why} — set GENESEED_GUARD=off to allow`)
250
+ }
251
+ try {
252
+ if (hasAny(tool, WRITE_TOOLS)) {
253
+ const p = pickPath(args)
254
+ if (p && SECRET_RE.some((re) => re.test(p))) { deny(`write to secret/key file ${p} (Law I)`); return }
255
+ if (p && SECRET_WARN_RE.some((re) => re.test(p))) log(`WARN: writing ${p} — keep secrets out of tracked files (Law I)`)
256
+ const store = p && ruleStoreTarget(p)
257
+ if (store && !RULE_STORE_BUMPED.has(p)) {
258
+ RULE_STORE_BUMPED.add(p)
259
+ deny(`writing to ${store} — a standing rule, or a fact to remember? That ` +
260
+ `choice is the user's (Law VI). Settle it through the rule skill, ` +
261
+ `then re-issue this write`)
262
+ return
263
+ }
264
+ }
265
+ if (hasAny(tool, WIKI_MUTATE_TOOLS)) {
266
+ const p = pickPath(args)
267
+ if (p) {
268
+ const abs = (path.isAbsolute(p) ? p : path.resolve(p)).replace(/\\/g, "/").toLowerCase()
269
+ const hit = (await protectedPrefixes()).find(
270
+ (x) => abs.startsWith(x.prefix) || abs === x.prefix.slice(0, -1))
271
+ if (hit) { deny(`mutation in protected wiki folder — ${hit.label} (AGENT.md §7)`); return }
272
+ }
273
+ }
274
+ // NOT else-if: a compound tool name (e.g. exec_and_save) can match both
275
+ // classes, and the shell check must still run after the write check.
276
+ if (hasAny(tool, SHELL_TOOLS)) {
277
+ const c = pickCommand(args)
278
+ if (c && SHELL_BLOCK_RE.some((re) => re.test(c))) { deny(`catastrophic command (Law IV): ${c.slice(0, 80)}`); return }
279
+ if (c && SHELL_WARN_RE.some((re) => re.test(c))) log(`WARN: irreversible op — confirm intent (Law IV): ${c.slice(0, 80)}`)
280
+ }
281
+ } catch (err) {
282
+ // Our own deny must propagate; any inspection error must never break a tool call.
283
+ if (err && String(err.message || "").startsWith("[geneseed-guard]")) throw err
284
+ log(`inspect error (ignored): ${err?.message ?? err}`)
285
+ }
286
+ },
287
+ }
288
+ }
289
+
290
+ export default GeneseedGuard