@zuzuucodes/cli 1.4.0 → 1.6.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 (211) hide show
  1. package/bin/zuzuu.mjs +20 -4
  2. package/package.json +1 -1
  3. package/web-app/dist/auth.js +91 -0
  4. package/web-app/dist/server.js +16 -79
  5. package/web-app/dist/zuzuu-cli.js +124 -0
  6. package/web-app/dist/{zuzuu-api.js → zuzuu-routes.js} +158 -133
  7. package/web-app/web-dist/assets/CommandPalette-DhBdR7X3.js +45 -0
  8. package/web-app/web-dist/assets/DiffTab-CqxwSjI2.js +1 -0
  9. package/web-app/web-dist/assets/EditorPane-94QPFR9R.js +41 -0
  10. package/web-app/web-dist/assets/MonacoFile-D76epTrG.js +1 -0
  11. package/web-app/web-dist/assets/angular-html-BVBpGdXr.js +1 -0
  12. package/web-app/web-dist/assets/{angular-ts-CD_OonCa.js → angular-ts-BfdufMKP.js} +1 -1
  13. package/web-app/web-dist/assets/{apl-uOGC3x4e.js → apl-DWBSSoBH.js} +1 -1
  14. package/web-app/web-dist/assets/{astro-B6ybQmWG.js → astro-3LtMP0Sq.js} +1 -1
  15. package/web-app/web-dist/assets/{blade-B1QGRlVx.js → blade-llJRbbtR.js} +1 -1
  16. package/web-app/web-dist/assets/c-Wt1voDr2.js +1 -0
  17. package/web-app/web-dist/assets/{cobol-BgqgtYWn.js → cobol-x_HIyl2P.js} +1 -1
  18. package/web-app/web-dist/assets/{coffee-0wIRKYlr.js → coffee-CThvmt4R.js} +1 -1
  19. package/web-app/web-dist/assets/cpp-NtAeskI3.js +1 -0
  20. package/web-app/web-dist/assets/{crystal-CyTK3qFN.js → crystal-DNu_sX0G.js} +1 -1
  21. package/web-app/web-dist/assets/css-DJp_X0uY.js +1 -0
  22. package/web-app/web-dist/assets/{cssMode-B9jnrWOz.js → cssMode-ByQBaInt.js} +1 -1
  23. package/web-app/web-dist/assets/dist-DQqjtuhV.js +153 -0
  24. package/web-app/web-dist/assets/{edge-CvML9pwC.js → edge-ozw5tpLl.js} +1 -1
  25. package/web-app/web-dist/assets/{editor.api2-BmGoRSl4.js → editor.api2-C7skgoRB.js} +1 -1
  26. package/web-app/web-dist/assets/{elixir-CrjqTiSc.js → elixir-VhA6FeZt.js} +1 -1
  27. package/web-app/web-dist/assets/{elm-C4JtJ0Au.js → elm-dREJmIFz.js} +1 -1
  28. package/web-app/web-dist/assets/{erb-Cmeb-29V.js → erb-CIg6G69l.js} +1 -1
  29. package/web-app/web-dist/assets/{freemarker2-B5LAi19B.js → freemarker2-CBBwP9JV.js} +1 -1
  30. package/web-app/web-dist/assets/{git-rebase-CXqdToiP.js → git-rebase-B44mJPta.js} +1 -1
  31. package/web-app/web-dist/assets/{glimmer-js-Kq-kdTyV.js → glimmer-js-vH_gHG0-.js} +1 -1
  32. package/web-app/web-dist/assets/{glimmer-ts-D0RKLJNf.js → glimmer-ts--abOzSAQ.js} +1 -1
  33. package/web-app/web-dist/assets/glsl-Dv5r7kPw.js +1 -0
  34. package/web-app/web-dist/assets/graphql-CB4jsw2E.js +1 -0
  35. package/web-app/web-dist/assets/{hack-trjVF3Po.js → hack-DvEYX148.js} +1 -1
  36. package/web-app/web-dist/assets/haml-zE6W3STP.js +1 -0
  37. package/web-app/web-dist/assets/{handlebars-B8_x7Zx7.js → handlebars-CzBR2SDs.js} +1 -1
  38. package/web-app/web-dist/assets/{handlebars-g7ZhGhI_.js → handlebars-tXdfxEd6.js} +1 -1
  39. package/web-app/web-dist/assets/html-C8UlPnhE.js +1 -0
  40. package/web-app/web-dist/assets/{html-CfvRMgoC.js → html-DgPn1QYH.js} +1 -1
  41. package/web-app/web-dist/assets/{html-derivative-BYX_F_XH.js → html-derivative-CY6NRz-J.js} +1 -1
  42. package/web-app/web-dist/assets/{htmlMode-Bi8vSvwb.js → htmlMode-BtdIDgA2.js} +1 -1
  43. package/web-app/web-dist/assets/{http-BIVDpHT-.js → http-Cyd7bS_S.js} +1 -1
  44. package/web-app/web-dist/assets/{hurl-CFsshMju.js → hurl-CWPsiEpf.js} +1 -1
  45. package/web-app/web-dist/assets/index-B27_WOhS.css +2 -0
  46. package/web-app/web-dist/assets/index-De6DWTZM.js +7 -0
  47. package/web-app/web-dist/assets/java-CGc3VwQr.js +1 -0
  48. package/web-app/web-dist/assets/{javascript-Bxx2wV4w.js → javascript-5m05n-Be.js} +1 -1
  49. package/web-app/web-dist/assets/javascript-CUt1pgmJ.js +1 -0
  50. package/web-app/web-dist/assets/{jinja-_ZS5zWwe.js → jinja-CD-Z-FLd.js} +1 -1
  51. package/web-app/web-dist/assets/{jison-D8mMEpcs.js → jison-imPNup1l.js} +1 -1
  52. package/web-app/web-dist/assets/json-Bg9ijW3F.js +1 -0
  53. package/web-app/web-dist/assets/{jsonMode-C6ELX5GM.js → jsonMode-BG32YnTY.js} +1 -1
  54. package/web-app/web-dist/assets/jsx-CY6oMTks.js +1 -0
  55. package/web-app/web-dist/assets/{julia-D4h2DZrs.js → julia-Dc3O-irA.js} +1 -1
  56. package/web-app/web-dist/assets/{just-bMqQi3xg.js → just-BhOq_Kbv.js} +1 -1
  57. package/web-app/web-dist/assets/{latex-DThYi3CX.js → latex-Cu4Y1d5w.js} +1 -1
  58. package/web-app/web-dist/assets/lib-KIOQTlcs.js +1 -0
  59. package/web-app/web-dist/assets/{liquid-CUjzzP4r.js → liquid-3ZnQzTbs.js} +1 -1
  60. package/web-app/web-dist/assets/{liquid-CesB-zzl.js → liquid-CvXMrjlQ.js} +1 -1
  61. package/web-app/web-dist/assets/{lspLanguageFeatures-gTnJsses.js → lspLanguageFeatures-6KXALSrl.js} +1 -1
  62. package/web-app/web-dist/assets/lua-BjLEUjKY.js +1 -0
  63. package/web-app/web-dist/assets/{marko-yoGoLK2m.js → marko-DvhNOisQ.js} +1 -1
  64. package/web-app/web-dist/assets/{mdc-BvtXU6eH.js → mdc-Bm9TpL1X.js} +1 -1
  65. package/web-app/web-dist/assets/{mdx-DrXGQbNB.js → mdx-DffTEkNE.js} +1 -1
  66. package/web-app/web-dist/assets/{monaco-setup-CsR6EfHe.js → monaco-setup-DM3A5_VI.js} +3 -3
  67. package/web-app/web-dist/assets/{nginx-DoUz032F.js → nginx-Bhc82uuv.js} +1 -1
  68. package/web-app/web-dist/assets/{nim-B0Pl8B4R.js → nim-DXTVBFnF.js} +1 -1
  69. package/web-app/web-dist/assets/{perl-D2tfAALb.js → perl-C7veXV9z.js} +1 -1
  70. package/web-app/web-dist/assets/{php-BImCcX5X.js → php-BRiuMnnr.js} +1 -1
  71. package/web-app/web-dist/assets/{pug-BcnpC8P_.js → pug-C5hz5LQ7.js} +1 -1
  72. package/web-app/web-dist/assets/{python-ypRCBnvu.js → python-DyLAD3Wt.js} +1 -1
  73. package/web-app/web-dist/assets/{qml-DFDAunHY.js → qml-BdUV3aTS.js} +1 -1
  74. package/web-app/web-dist/assets/r-8R7vtdQc.js +1 -0
  75. package/web-app/web-dist/assets/{razor-aqrhpwqZ.js → razor-C49xQTPQ.js} +1 -1
  76. package/web-app/web-dist/assets/{razor-1_376SZM.js → razor-DRL52XO2.js} +1 -1
  77. package/web-app/web-dist/assets/react-vendor-CCIEwYL0.js +9 -0
  78. package/web-app/web-dist/assets/regexp-Omp9DhTb.js +1 -0
  79. package/web-app/web-dist/assets/{rst-2vG6f11Y.js → rst-BHX71KW9.js} +1 -1
  80. package/web-app/web-dist/assets/{ruby-Dj6bCFXR.js → ruby-B--HzjGU.js} +1 -1
  81. package/web-app/web-dist/assets/{sas-BhVZ4qL2.js → sas-DrLaYOK_.js} +1 -1
  82. package/web-app/web-dist/assets/scss-DdSxiZKl.js +1 -0
  83. package/web-app/web-dist/assets/shellscript-DwcUjJBL.js +1 -0
  84. package/web-app/web-dist/assets/{shellsession-CyO2fnhB.js → shellsession-CPZkydE6.js} +1 -1
  85. package/web-app/web-dist/assets/{soy-DIkw6E88.js → soy-Br5FhD7c.js} +1 -1
  86. package/web-app/web-dist/assets/sql-DNssxck8.js +1 -0
  87. package/web-app/web-dist/assets/{stata-DvkM932O.js → stata-DXn1tqOr.js} +1 -1
  88. package/web-app/web-dist/assets/{surrealql-B4-Q8tqV.js → surrealql-IeLNQw0f.js} +1 -1
  89. package/web-app/web-dist/assets/{svelte-p6yBy-Ki.js → svelte-DOdLCIlh.js} +1 -1
  90. package/web-app/web-dist/assets/{templ-C7EkuiZr.js → templ-CIwIngms.js} +1 -1
  91. package/web-app/web-dist/assets/{tex-DkmD8uFC.js → tex-D8QMumu5.js} +1 -1
  92. package/web-app/web-dist/assets/{ts-tags-U-hncHg4.js → ts-tags-BMVY4q-l.js} +1 -1
  93. package/web-app/web-dist/assets/{tsMode-a8OvovQd.js → tsMode-BndVBac5.js} +1 -1
  94. package/web-app/web-dist/assets/tsx-5Eka4NBX.js +1 -0
  95. package/web-app/web-dist/assets/{twig-CU0OP-IA.js → twig-C8o_5mgw.js} +1 -1
  96. package/web-app/web-dist/assets/{typescript-DnLjiKtn.js → typescript-B1w9vqKF.js} +1 -1
  97. package/web-app/web-dist/assets/typescript-DOu2WMV5.js +1 -0
  98. package/web-app/web-dist/assets/{vue-Db7nY3ba.js → vue-BU18DNDL.js} +1 -1
  99. package/web-app/web-dist/assets/{vue-html-BvAbiAw1.js → vue-html-BeluIYX0.js} +1 -1
  100. package/web-app/web-dist/assets/{vue-vine-BEaIQIlA.js → vue-vine-DGUAbOCX.js} +1 -1
  101. package/web-app/web-dist/assets/{xml-an4Nuuqq.js → xml-D8uAlVv5.js} +1 -1
  102. package/web-app/web-dist/assets/xml-DIqSwXR3.js +1 -0
  103. package/web-app/web-dist/assets/{xsl-D3NQgH22.js → xsl-Ct_-YIAy.js} +1 -1
  104. package/web-app/web-dist/assets/xterm-B1ffpRuj.js +36 -0
  105. package/web-app/web-dist/assets/xterm-addons-psDEiUMC.js +136 -0
  106. package/web-app/web-dist/assets/{yaml-Diiu6O9P.js → yaml-Bb7jXyQv.js} +1 -1
  107. package/web-app/web-dist/assets/yaml-DTtCYNlS.js +1 -0
  108. package/web-app/web-dist/index.html +6 -3
  109. package/zuzuu/actions/convert.mjs +10 -9
  110. package/zuzuu/actions/dispatch.mjs +12 -7
  111. package/zuzuu/actions/inbox.mjs +5 -5
  112. package/zuzuu/actions/manifest.mjs +48 -30
  113. package/zuzuu/actions/schema.mjs +9 -3
  114. package/zuzuu/actions/trail.mjs +1 -1
  115. package/zuzuu/commands/act-author.mjs +23 -13
  116. package/zuzuu/commands/act.mjs +4 -6
  117. package/zuzuu/commands/capture.mjs +2 -2
  118. package/zuzuu/commands/code.mjs +2 -2
  119. package/zuzuu/commands/digest.mjs +2 -2
  120. package/zuzuu/commands/distill.mjs +15 -16
  121. package/zuzuu/commands/doctor.mjs +41 -19
  122. package/zuzuu/commands/enable.mjs +1 -1
  123. package/zuzuu/commands/eval.mjs +3 -36
  124. package/zuzuu/commands/explain.mjs +4 -4
  125. package/zuzuu/commands/faculty.mjs +158 -0
  126. package/zuzuu/commands/generation.mjs +5 -8
  127. package/zuzuu/commands/hook.mjs +14 -12
  128. package/zuzuu/commands/inbox.mjs +1 -6
  129. package/zuzuu/commands/init.mjs +18 -4
  130. package/zuzuu/commands/knowledge.mjs +1 -1
  131. package/zuzuu/commands/migrations/home.mjs +96 -0
  132. package/zuzuu/commands/migrations/index.mjs +48 -0
  133. package/zuzuu/commands/migrations/items.mjs +360 -0
  134. package/zuzuu/commands/migrations/proposals.mjs +100 -0
  135. package/zuzuu/commands/proposals.mjs +131 -0
  136. package/zuzuu/commands/review.mjs +13 -227
  137. package/zuzuu/commands/session.mjs +8 -2
  138. package/zuzuu/commands/sessions.mjs +159 -0
  139. package/zuzuu/commands/status.mjs +3 -3
  140. package/zuzuu/commands/trace.mjs +1 -1
  141. package/zuzuu/{capture-core.mjs → core/capture-core.mjs} +3 -3
  142. package/zuzuu/{store.mjs → core/store.mjs} +1 -1
  143. package/zuzuu/digest/compose.mjs +96 -0
  144. package/zuzuu/eval/score.mjs +14 -1
  145. package/zuzuu/faculties/actions/index.mjs +283 -0
  146. package/zuzuu/faculties/guardrails/index.mjs +320 -0
  147. package/zuzuu/faculties/instructions/index.mjs +288 -0
  148. package/zuzuu/faculties/knowledge/index.mjs +185 -0
  149. package/zuzuu/faculties/memory/index.mjs +124 -0
  150. package/zuzuu/faculty/envelope.mjs +290 -0
  151. package/zuzuu/faculty/generation/read.mjs +206 -0
  152. package/zuzuu/faculty/generation/write.mjs +207 -0
  153. package/zuzuu/faculty/items.mjs +81 -0
  154. package/zuzuu/faculty/module.mjs +74 -0
  155. package/zuzuu/faculty/pending.mjs +63 -0
  156. package/zuzuu/faculty/registry.mjs +204 -18
  157. package/zuzuu/faculty/render.mjs +59 -0
  158. package/zuzuu/faculty/trail.mjs +1 -1
  159. package/zuzuu/guardrails/engine.mjs +137 -0
  160. package/zuzuu/{scaffold.mjs → home/scaffold.mjs} +110 -39
  161. package/zuzuu/knowledge/items.mjs +56 -91
  162. package/zuzuu/live/install.mjs +1 -1
  163. package/zuzuu/live/live-store.mjs +2 -2
  164. package/zuzuu/live/reconcile.mjs +2 -2
  165. package/zuzuu/sessions/git.mjs +47 -0
  166. package/zuzuu/{session-git.mjs → sessions/session-git.mjs} +5 -43
  167. package/web-app/web-dist/assets/DiffTab-BuWonUNJ.js +0 -1
  168. package/web-app/web-dist/assets/MonacoFile-CL3DhFKG.js +0 -1
  169. package/web-app/web-dist/assets/angular-html-CmT26mqM.js +0 -1
  170. package/web-app/web-dist/assets/c-BvoqrSVH.js +0 -1
  171. package/web-app/web-dist/assets/cpp-BXsk94m0.js +0 -1
  172. package/web-app/web-dist/assets/css-Z8oOGxII.js +0 -1
  173. package/web-app/web-dist/assets/dist-ChcDQ_7s.js +0 -153
  174. package/web-app/web-dist/assets/glsl-KwyfU2aa.js +0 -1
  175. package/web-app/web-dist/assets/graphql-DSeOUAa2.js +0 -1
  176. package/web-app/web-dist/assets/haml-azVoxQRV.js +0 -1
  177. package/web-app/web-dist/assets/html-D_7P5S4m.js +0 -1
  178. package/web-app/web-dist/assets/index--5yy8RbA.js +0 -267
  179. package/web-app/web-dist/assets/index-BVG4hgk7.css +0 -2
  180. package/web-app/web-dist/assets/java-D4RbCvBe.js +0 -1
  181. package/web-app/web-dist/assets/javascript-Cb010CKM.js +0 -1
  182. package/web-app/web-dist/assets/json-DWgqV4D1.js +0 -1
  183. package/web-app/web-dist/assets/jsx-CZjSJa1f.js +0 -1
  184. package/web-app/web-dist/assets/lua-TGj_6NzO.js +0 -1
  185. package/web-app/web-dist/assets/r-fCpuAR7u.js +0 -1
  186. package/web-app/web-dist/assets/regexp-B4yxx-Ty.js +0 -1
  187. package/web-app/web-dist/assets/scss-QdjMO_xV.js +0 -1
  188. package/web-app/web-dist/assets/shellscript-BnlgeVVx.js +0 -1
  189. package/web-app/web-dist/assets/sql-DGnQv6iD.js +0 -1
  190. package/web-app/web-dist/assets/tsx-MJ0-9sYG.js +0 -1
  191. package/web-app/web-dist/assets/typescript-C17ZkDe8.js +0 -1
  192. package/web-app/web-dist/assets/xml-CA9lHFQV.js +0 -1
  193. package/web-app/web-dist/assets/yaml-CwRYMJka.js +0 -1
  194. package/zuzuu/actions/adapter.mjs +0 -130
  195. package/zuzuu/commands/migrate.mjs +0 -225
  196. package/zuzuu/digest.mjs +0 -149
  197. package/zuzuu/faculty/generation.mjs +0 -392
  198. package/zuzuu/guardrails/adapter.mjs +0 -134
  199. package/zuzuu/guardrails.mjs +0 -89
  200. package/zuzuu/instructions/adapter.mjs +0 -93
  201. package/zuzuu/knowledge/adapter.mjs +0 -99
  202. package/zuzuu/memory/adapter.mjs +0 -121
  203. package/zuzuu/miners/actions.mjs +0 -118
  204. package/zuzuu/miners/guardrails.mjs +0 -179
  205. package/zuzuu/miners/instructions.mjs +0 -157
  206. package/zuzuu/miners/knowledge.mjs +0 -25
  207. package/zuzuu/miners/memory.mjs +0 -27
  208. package/zuzuu/miners/registry.mjs +0 -31
  209. /package/web-app/web-dist/assets/{chunk-QTnfLwEv.js → rolldown-runtime-QTnfLwEv.js} +0 -0
  210. /package/zuzuu/{session.mjs → core/session.mjs} +0 -0
  211. /package/zuzuu/{inject.mjs → home/inject.mjs} +0 -0
@@ -1,392 +0,0 @@
1
- // zuzuu/faculty/generation.mjs — the generation core (WS3-T1).
2
- //
3
- // A *generation* is an immutable, content-addressed snapshot of the agent's
4
- // pinned faculties (the lockfile). Minting freezes the current faculty state;
5
- // rollback restores any past generation by *content* (we copy each pinned item's
6
- // bytes into generations/snapshots/<id>/ at mint time, so a rollback works even
7
- // for items that were never committed). Identity: Agent → Generation → Run —
8
- // rollback = flip the active pointer + restore content; never `git revert`.
9
- //
10
- // Layout under .zuzuu/:
11
- // generations/active {active: "gen_NNN"} — the live pointer
12
- // generations/<id>.json the lockfile (content-addressed manifest)
13
- // generations/snapshots/<id>/<faculty>/... pinned item bytes (rollback source)
14
-
15
- import { createHash } from 'node:crypto';
16
- import { join, dirname } from 'node:path';
17
- import {
18
- existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, renameSync,
19
- } from 'node:fs';
20
- import { reindex } from '../knowledge/index.mjs';
21
-
22
- /** Hex sha256 of a string or Buffer. */
23
- export function sha256(buf) {
24
- return createHash('sha256').update(buf).digest('hex');
25
- }
26
-
27
- const read = (p) => readFileSync(p, 'utf8');
28
- const readJson = (p) => JSON.parse(read(p));
29
- const writeJson = (p, obj) => {
30
- mkdirSync(dirname(p), { recursive: true });
31
- writeFileSync(p, JSON.stringify(obj, null, 2) + '\n');
32
- };
33
-
34
- // --- paths ------------------------------------------------------------------
35
-
36
- const generationsDir = (agentDir) => join(agentDir, 'generations');
37
- const snapshotsDir = (agentDir) => join(generationsDir(agentDir), 'snapshots');
38
- const activePath = (agentDir) => join(generationsDir(agentDir), 'active');
39
- const lockfilePath = (agentDir, id) => join(generationsDir(agentDir), `${id}.json`);
40
- const agentJsonPath = (agentDir) => join(agentDir, 'agent.json');
41
-
42
- // --- faculty file enumeration (the pinned set) ------------------------------
43
- // Each entry: { id, faculty, src (absolute live path), rel (path under the
44
- // faculty snapshot dir), hash }. `rel` is what we mirror into snapshots/<id>/.
45
-
46
- function sortDirents(dir) {
47
- if (!existsSync(dir)) return [];
48
- return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
49
- }
50
-
51
- function knowledgeFiles(agentDir) {
52
- const dir = join(agentDir, 'knowledge', 'items');
53
- return sortDirents(dir)
54
- .filter((e) => e.isFile() && e.name.endsWith('.md'))
55
- .map((e) => {
56
- const src = join(dir, e.name);
57
- return { id: e.name.replace(/\.md$/, ''), faculty: 'knowledge', src, rel: e.name, hash: sha256(readFileSync(src)) };
58
- });
59
- }
60
-
61
- function actionFiles(agentDir) {
62
- const dir = join(agentDir, 'actions');
63
- return sortDirents(dir)
64
- .filter((e) => e.isDirectory() && e.name !== 'inbox' && e.name !== 'proposals')
65
- .map((e) => {
66
- const adir = join(dir, e.name);
67
- // Hash the dir's defining files concatenated (action.json + run.mjs/SKILL.md).
68
- const parts = ['action.json', 'run.mjs', 'SKILL.md']
69
- .map((f) => join(adir, f))
70
- .filter((p) => existsSync(p));
71
- const concat = Buffer.concat(parts.map((p) => readFileSync(p)));
72
- return {
73
- id: e.name, faculty: 'actions', files: parts.map((p) => p.slice(adir.length + 1)),
74
- adir, hash: parts.length ? sha256(concat) : null,
75
- };
76
- });
77
- }
78
-
79
- function memoryFiles(agentDir) {
80
- const dir = join(agentDir, 'memory', 'entries');
81
- return sortDirents(dir)
82
- .filter((e) => e.isFile() && e.name.endsWith('.md'))
83
- .map((e) => {
84
- const src = join(dir, e.name);
85
- return { id: e.name.replace(/\.md$/, ''), faculty: 'memory', src, rel: e.name, hash: sha256(readFileSync(src)) };
86
- });
87
- }
88
-
89
- function registryHash(agentDir) {
90
- const dir = join(agentDir, 'knowledge', 'registry');
91
- const files = sortDirents(dir).filter((e) => e.isFile() && e.name.endsWith('.json'));
92
- if (!files.length) return null;
93
- return sha256(Buffer.concat(files.map((e) => readFileSync(join(dir, e.name)))));
94
- }
95
-
96
- function fileHashOrNull(p) {
97
- return existsSync(p) ? sha256(readFileSync(p)) : null;
98
- }
99
-
100
- /**
101
- * Snapshot the current faculty state → the `faculties` manifest object.
102
- * Tolerates missing files (empty arrays / null hashes).
103
- */
104
- export function snapshotFaculties(agentDir) {
105
- return {
106
- knowledge: {
107
- items: knowledgeFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
108
- registryHash: registryHash(agentDir),
109
- },
110
- actions: {
111
- items: actionFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
112
- },
113
- guardrails: {
114
- rulesHash: fileHashOrNull(join(agentDir, 'guardrails', 'rules.json')),
115
- },
116
- instructions: {
117
- projectHash: fileHashOrNull(join(agentDir, 'instructions', 'project.md')),
118
- },
119
- memory: {
120
- items: memoryFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
121
- },
122
- };
123
- }
124
-
125
- // --- agent identity ---------------------------------------------------------
126
-
127
- /** Stable agent id derived from the repo root: agt_<first12 of sha256(root)>. */
128
- export function agentId(agentDir) {
129
- // agentDir is the .zuzuu/ dir; the repo root is its parent.
130
- const root = dirname(agentDir);
131
- return 'agt_' + sha256(root).slice(0, 12);
132
- }
133
-
134
- /** Add/repair the agent block in agent.json (bump to v2), preserving other fields. */
135
- export function ensureAgent(agentDir) {
136
- const path = agentJsonPath(agentDir);
137
- const m = existsSync(path) ? readJson(path) : {};
138
- const id = agentId(agentDir);
139
- if (!m.agent || !m.agent.id) {
140
- m.agent = { id, createdAt: new Date().toISOString() };
141
- }
142
- m.version = 2;
143
- writeJson(path, m);
144
- return m.agent;
145
- }
146
-
147
- // --- generation read/list ---------------------------------------------------
148
-
149
- /** The active generation id, or null. */
150
- export function activeGeneration(agentDir) {
151
- const p = activePath(agentDir);
152
- if (!existsSync(p)) return null;
153
- try { return readJson(p).active ?? null; } catch { return null; }
154
- }
155
-
156
- /** All generation ids in ascending order. */
157
- export function listGenerations(agentDir) {
158
- const dir = generationsDir(agentDir);
159
- if (!existsSync(dir)) return [];
160
- return readdirSync(dir)
161
- .filter((f) => /^gen_\d+\.json$/.test(f))
162
- .map((f) => f.replace(/\.json$/, ''))
163
- .sort();
164
- }
165
-
166
- /** Read one lockfile, or null. */
167
- export function readGeneration(agentDir, id) {
168
- const p = lockfilePath(agentDir, id);
169
- return existsSync(p) ? readJson(p) : null;
170
- }
171
-
172
- /** Item-list faculties carry {id,hash}[]; single-file faculties a *Hash scalar. */
173
- const HASH_KEYS = { knowledge: 'registryHash', instructions: 'projectHash', guardrails: 'rulesHash' };
174
-
175
- /** Diff two item-manifest arrays → {added, changed, removed} (id lists). */
176
- function diffItems(parentItems = [], childItems = []) {
177
- const p = new Map(parentItems.map((i) => [i.id, i.hash]));
178
- const c = new Map(childItems.map((i) => [i.id, i.hash]));
179
- const added = [], changed = [], removed = [];
180
- for (const [id, hash] of c) {
181
- if (!p.has(id)) added.push(id);
182
- else if (p.get(id) !== hash) changed.push(id);
183
- }
184
- for (const id of p.keys()) if (!c.has(id)) removed.push(id);
185
- return { added: added.sort(), changed: changed.sort(), removed: removed.sort() };
186
- }
187
-
188
- /**
189
- * Per-faculty diff of generation `id` against its forkedFrom parent (pure).
190
- * For item-list faculties (knowledge/actions/memory) reports added/changed/removed
191
- * id lists. For hash-only faculties (guardrails/instructions, and knowledge's
192
- * registry) reports a `changed` boolean when the scalar hash differs. When there
193
- * is no parent (forkedFrom null), everything present counts as added.
194
- * Returns null for an unknown id.
195
- */
196
- export function diffGenerations(agentDir, id) {
197
- const child = readGeneration(agentDir, id);
198
- if (!child) return null;
199
- const parent = child.forkedFrom ? readGeneration(agentDir, child.forkedFrom) : null;
200
- const cf = child.faculties || {};
201
- const pf = parent?.faculties || {};
202
- const faculties = {};
203
- for (const f of ['knowledge', 'actions', 'memory']) {
204
- faculties[f] = diffItems(pf[f]?.items, cf[f]?.items);
205
- // knowledge also has a registry hash
206
- if (f === 'knowledge') {
207
- faculties[f].registryChanged = (cf.knowledge?.registryHash ?? null) !== (pf.knowledge?.registryHash ?? null);
208
- }
209
- }
210
- for (const f of ['guardrails', 'instructions']) {
211
- const key = HASH_KEYS[f];
212
- faculties[f] = { changed: (cf[f]?.[key] ?? null) !== (pf[f]?.[key] ?? null) };
213
- }
214
- return {
215
- id,
216
- forkedFrom: child.forkedFrom ?? null,
217
- mintedFrom: Array.isArray(child.mintedFrom) ? child.mintedFrom : [],
218
- mintedAt: child.mintedAt ?? null,
219
- faculties,
220
- };
221
- }
222
-
223
- function nextGenId(agentDir) {
224
- const ids = listGenerations(agentDir);
225
- const max = ids.reduce((m, id) => Math.max(m, parseInt(id.slice(4), 10) || 0), 0);
226
- return 'gen_' + String(max + 1).padStart(3, '0');
227
- }
228
-
229
- // --- mint -------------------------------------------------------------------
230
-
231
- function copySnapshot(agentDir, id) {
232
- const base = join(snapshotsDir(agentDir), id);
233
- for (const it of knowledgeFiles(agentDir)) {
234
- const dest = join(base, 'knowledge', it.rel);
235
- mkdirSync(dirname(dest), { recursive: true });
236
- writeFileSync(dest, readFileSync(it.src));
237
- }
238
- for (const it of memoryFiles(agentDir)) {
239
- const dest = join(base, 'memory', it.rel);
240
- mkdirSync(dirname(dest), { recursive: true });
241
- writeFileSync(dest, readFileSync(it.src));
242
- }
243
- for (const a of actionFiles(agentDir)) {
244
- for (const rel of a.files) {
245
- const dest = join(base, 'actions', a.id, rel);
246
- mkdirSync(dirname(dest), { recursive: true });
247
- writeFileSync(dest, readFileSync(join(a.adir, rel)));
248
- }
249
- }
250
- // single-file faculties
251
- const rules = join(agentDir, 'guardrails', 'rules.json');
252
- if (existsSync(rules)) {
253
- const dest = join(base, 'guardrails', 'rules.json');
254
- mkdirSync(dirname(dest), { recursive: true });
255
- writeFileSync(dest, readFileSync(rules));
256
- }
257
- const proj = join(agentDir, 'instructions', 'project.md');
258
- if (existsSync(proj)) {
259
- const dest = join(base, 'instructions', 'project.md');
260
- mkdirSync(dirname(dest), { recursive: true });
261
- writeFileSync(dest, readFileSync(proj));
262
- }
263
- }
264
-
265
- /**
266
- * Mint a new generation: freeze the current faculty state into a content-addressed
267
- * lockfile + a byte-for-byte snapshot, and make it active.
268
- */
269
- export function mintGeneration(agentDir, { forkedFrom = null, mintedFrom = [] } = {}) {
270
- const agent = ensureAgent(agentDir).id;
271
- const id = nextGenId(agentDir);
272
- const lockfile = {
273
- id,
274
- agent,
275
- mintedAt: new Date().toISOString(),
276
- forkedFrom,
277
- mintedFrom,
278
- faculties: snapshotFaculties(agentDir),
279
- };
280
- copySnapshot(agentDir, id);
281
- writeJson(lockfilePath(agentDir, id), lockfile);
282
- writeJson(activePath(agentDir), { active: id });
283
- return lockfile;
284
- }
285
-
286
- // --- rollback ---------------------------------------------------------------
287
-
288
- function archive(agentDir, faculty, src) {
289
- // Park (never delete) under <faculty>/_rolledback/<basename> — by basename so
290
- // a restore is a simple, flat audit trail of what the rollback displaced.
291
- const dest = join(agentDir, faculty, '_rolledback', src.slice(dirname(src).length + 1));
292
- mkdirSync(dirname(dest), { recursive: true });
293
- renameSync(src, dest);
294
- }
295
-
296
- /**
297
- * Restore a past generation by content: write each snapshotted item back to its
298
- * live faculty path; MOVE (never delete) active items absent from the target into
299
- * <faculty>/_rolledback/; reindex knowledge; flip the active pointer.
300
- */
301
- export function rollback(agentDir, id) {
302
- const target = readGeneration(agentDir, id);
303
- if (!target) throw new Error(`no generation '${id}'`);
304
- const base = join(snapshotsDir(agentDir), id);
305
- let restored = 0;
306
-
307
- // 1) restore snapshotted knowledge items
308
- const targetKnowledge = new Set((target.faculties.knowledge?.items ?? []).map((i) => i.id));
309
- for (const i of target.faculties.knowledge?.items ?? []) {
310
- const snap = join(base, 'knowledge', `${i.id}.md`);
311
- if (existsSync(snap)) {
312
- const dest = join(agentDir, 'knowledge', 'items', `${i.id}.md`);
313
- mkdirSync(dirname(dest), { recursive: true });
314
- writeFileSync(dest, readFileSync(snap));
315
- restored++;
316
- }
317
- }
318
- // archive live knowledge items not in the target
319
- const kdir = join(agentDir, 'knowledge', 'items');
320
- if (existsSync(kdir)) {
321
- for (const e of readdirSync(kdir, { withFileTypes: true })) {
322
- if (e.isFile() && e.name.endsWith('.md') && !targetKnowledge.has(e.name.replace(/\.md$/, ''))) {
323
- archive(agentDir, 'knowledge', join(kdir, e.name));
324
- }
325
- }
326
- }
327
-
328
- // 2) restore snapshotted memory items + archive extras
329
- const targetMemory = new Set((target.faculties.memory?.items ?? []).map((i) => i.id));
330
- for (const i of target.faculties.memory?.items ?? []) {
331
- const snap = join(base, 'memory', `${i.id}.md`);
332
- if (existsSync(snap)) {
333
- const dest = join(agentDir, 'memory', 'entries', `${i.id}.md`);
334
- mkdirSync(dirname(dest), { recursive: true });
335
- writeFileSync(dest, readFileSync(snap));
336
- restored++;
337
- }
338
- }
339
- const mdir = join(agentDir, 'memory', 'entries');
340
- if (existsSync(mdir)) {
341
- for (const e of readdirSync(mdir, { withFileTypes: true })) {
342
- if (e.isFile() && e.name.endsWith('.md') && !targetMemory.has(e.name.replace(/\.md$/, ''))) {
343
- archive(agentDir, 'memory', join(mdir, e.name));
344
- }
345
- }
346
- }
347
-
348
- // 3) restore snapshotted actions + archive extras
349
- const targetActions = new Set((target.faculties.actions?.items ?? []).map((i) => i.id));
350
- const asnap = join(base, 'actions');
351
- if (existsSync(asnap)) {
352
- for (const slugEnt of readdirSync(asnap, { withFileTypes: true })) {
353
- if (!slugEnt.isDirectory()) continue;
354
- const sdir = join(asnap, slugEnt.name);
355
- for (const f of readdirSync(sdir)) {
356
- const dest = join(agentDir, 'actions', slugEnt.name, f);
357
- mkdirSync(dirname(dest), { recursive: true });
358
- writeFileSync(dest, readFileSync(join(sdir, f)));
359
- }
360
- restored++;
361
- }
362
- }
363
- const adir = join(agentDir, 'actions');
364
- if (existsSync(adir)) {
365
- for (const e of readdirSync(adir, { withFileTypes: true })) {
366
- if (e.isDirectory() && e.name !== 'inbox' && e.name !== 'proposals' && e.name !== '_rolledback' && !targetActions.has(e.name)) {
367
- archive(agentDir, 'actions', join(adir, e.name));
368
- }
369
- }
370
- }
371
-
372
- // 4) restore single-file faculties from the snapshot
373
- const grules = join(base, 'guardrails', 'rules.json');
374
- if (existsSync(grules)) {
375
- const dest = join(agentDir, 'guardrails', 'rules.json');
376
- mkdirSync(dirname(dest), { recursive: true });
377
- writeFileSync(dest, readFileSync(grules));
378
- restored++;
379
- }
380
- const proj = join(base, 'instructions', 'project.md');
381
- if (existsSync(proj)) {
382
- const dest = join(agentDir, 'instructions', 'project.md');
383
- mkdirSync(dirname(dest), { recursive: true });
384
- writeFileSync(dest, readFileSync(proj));
385
- restored++;
386
- }
387
-
388
- // 5) regenerate the derived knowledge index + flip the pointer
389
- try { reindex(agentDir); } catch { /* derived index; tolerate absence of node:sqlite features */ }
390
- writeJson(activePath(agentDir), { active: id });
391
- return { ok: true, restored };
392
- }
@@ -1,134 +0,0 @@
1
- // zuzuu/guardrails/adapter.mjs
2
- // The Guardrails faculty adapter (WS2-T4). Wraps the rules engine behind the
3
- // faculty-spine adapter contract — { name, ingest, validate, apply, render } —
4
- // so `zuzuu review` can surface and approve/reject rule proposals the same way it
5
- // does Knowledge proposals.
6
- //
7
- // A guardrails proposal payload is a single rule record:
8
- // { id, action: deny|ask|allow, tool, pattern, reason }
9
- //
10
- // apply: loads .zuzuu/guardrails/rules.json (seeding {version:1,rules:[]} if
11
- // absent), appends the rule or replaces an existing one with the same id,
12
- // then writes the file back.
13
- //
14
- // Registers itself on import.
15
-
16
- import { join } from 'node:path';
17
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
18
- import * as registry from '../faculty/registry.mjs';
19
-
20
- const name = 'guardrails';
21
- const VALID_ACTIONS = new Set(['deny', 'ask', 'allow']);
22
-
23
- // ---------------------------------------------------------------------------
24
- // helpers
25
- // ---------------------------------------------------------------------------
26
-
27
- function rulesPath(agentDir) {
28
- return join(agentDir, 'guardrails', 'rules.json');
29
- }
30
-
31
- function loadRulesFile(agentDir) {
32
- const path = rulesPath(agentDir);
33
- if (!existsSync(path)) return { version: 1, rules: [] };
34
- try {
35
- return JSON.parse(readFileSync(path, 'utf8'));
36
- } catch {
37
- return { version: 1, rules: [] };
38
- }
39
- }
40
-
41
- // ---------------------------------------------------------------------------
42
- // adapter contract
43
- // ---------------------------------------------------------------------------
44
-
45
- /**
46
- * Ingest a raw rule object. Pass-through: rule fields are the payload.
47
- * @param {string} agentDir
48
- * @param {object} raw — expected shape: { id, action, tool, pattern, reason }
49
- * or { payload: { ... } } from the spine
50
- */
51
- function ingest(_agentDir, raw) {
52
- const payload = raw?.payload ?? raw ?? {};
53
- return { payload, analysis: {}, dedupeKey: payload.id };
54
- }
55
-
56
- /**
57
- * Validate a rule payload.
58
- * @returns {{ok:boolean, errors:string[], warnings:string[]}}
59
- */
60
- function validate(_agentDir, payload) {
61
- const errors = [];
62
- if (!payload?.id || typeof payload.id !== 'string' || !payload.id.trim()) {
63
- errors.push('rule id is required (non-empty string slug)');
64
- }
65
- if (!VALID_ACTIONS.has(payload?.action)) {
66
- errors.push(`action must be one of deny|ask|allow (got '${payload?.action}')`);
67
- }
68
- if (!payload?.tool || typeof payload.tool !== 'string') {
69
- errors.push('tool is required (exact tool name or \'*\')');
70
- }
71
- if (typeof payload?.pattern !== 'string' || !payload.pattern) {
72
- errors.push('pattern is required (a non-empty regex string)');
73
- } else {
74
- try {
75
- new RegExp(payload.pattern); // eslint-disable-line no-new
76
- } catch (e) {
77
- errors.push(`pattern does not compile as a RegExp: ${e.message}`);
78
- }
79
- }
80
- if (!payload?.reason || !String(payload.reason).trim()) {
81
- errors.push('reason is required (non-empty)');
82
- }
83
- return { ok: errors.length === 0, errors, warnings: [] };
84
- }
85
-
86
- /**
87
- * Apply an approved rule proposal: upsert into rules.json.
88
- * @returns {{ok:boolean, action:string, itemIds:string[]}}
89
- */
90
- function apply(agentDir, proposal) {
91
- const rule = proposal?.payload ?? {};
92
- const id = rule.id;
93
-
94
- // Ensure the guardrails dir exists
95
- mkdirSync(join(agentDir, 'guardrails'), { recursive: true });
96
-
97
- const data = loadRulesFile(agentDir);
98
- if (!Array.isArray(data.rules)) data.rules = [];
99
-
100
- const idx = data.rules.findIndex((r) => r.id === id);
101
- // Store only the canonical fields (id, action, tool, pattern, reason)
102
- const entry = {
103
- id: rule.id,
104
- action: rule.action,
105
- tool: rule.tool,
106
- pattern: rule.pattern,
107
- reason: rule.reason,
108
- };
109
- if (idx >= 0) {
110
- data.rules[idx] = entry;
111
- } else {
112
- data.rules.push(entry);
113
- }
114
-
115
- writeFileSync(rulesPath(agentDir), JSON.stringify(data, null, 2) + '\n');
116
- return { ok: true, action: `added rule ${id}`, itemIds: [id] };
117
- }
118
-
119
- /**
120
- * Render a rule proposal for the human gate.
121
- * @returns {{line:string, card:string}}
122
- */
123
- function render(proposal) {
124
- const r = proposal?.payload ?? {};
125
- const summary = `${r.action ?? '?'} ${r.tool ?? '*'} /${r.pattern ?? ''}/ — ${r.reason ?? ''}`;
126
- return {
127
- line: `${r.id ?? ''} [rule] ${summary}`,
128
- card: summary,
129
- };
130
- }
131
-
132
- export const adapter = { name, ingest, validate, apply, render };
133
-
134
- registry.register(adapter);
@@ -1,89 +0,0 @@
1
- // The Guardrails faculty — v1 rule engine (pure; I/O lives in the hook command).
2
- //
3
- // Rules are DATA, not code: .zuzuu/guardrails/rules.json, ordered, declarative —
4
- // a *definition* in the pin-definitions sense (versioned in git, graduates via
5
- // proposals like every faculty's contents).
6
- //
7
- // { "version": 1,
8
- // "rules": [ { "id": "no-root-wipe", "action": "deny",
9
- // "tool": "Bash", // exact tool name, or "*"
10
- // "pattern": "rm\\s+-rf\\s+/", // regex over the tool INPUT (stringified)
11
- // "reason": "destructive root delete" } ] }
12
- //
13
- // Evaluation: collect every matching rule, then severity wins — deny > ask >
14
- // allow (an explicit allow can whitelist past a later ask/deny only if it is
15
- // NOT outweighed; severity beats file order so a sloppy rule ordering can never
16
- // silently disarm a deny).
17
- //
18
- // FAIL-OPEN: any malformed rule/file yields { ok:false } and no decision — the
19
- // host proceeds through its normal permission flow. A guardrail bug must never
20
- // brick the agent; misses are logged, not fatal.
21
-
22
- import { readFileSync } from 'node:fs';
23
-
24
- const SEVERITY = { deny: 3, ask: 2, allow: 1 };
25
- const ACTIONS = new Set(Object.keys(SEVERITY));
26
-
27
- /** Parse + validate a rules file. Fail-open: returns ok:false on any problem. */
28
- export function loadRules(path) {
29
- try {
30
- const data = JSON.parse(readFileSync(path, 'utf8'));
31
- if (!Array.isArray(data.rules)) return { ok: false, rules: [], error: 'rules is not an array' };
32
- const rules = [];
33
- for (const r of data.rules) {
34
- if (!r || typeof r !== 'object' || !ACTIONS.has(r.action) || typeof r.pattern !== 'string') {
35
- return { ok: false, rules: [], error: `malformed rule: ${JSON.stringify(r).slice(0, 80)}` };
36
- }
37
- try {
38
- rules.push({ id: String(r.id ?? `rule-${rules.length}`), action: r.action, tool: r.tool || '*', re: new RegExp(r.pattern, 'i'), reason: String(r.reason ?? '') });
39
- } catch (e) {
40
- return { ok: false, rules: [], error: `bad pattern in ${r.id}: ${e.message}` };
41
- }
42
- }
43
- return { ok: true, rules };
44
- } catch (e) {
45
- return { ok: false, rules: [], error: e.message };
46
- }
47
- }
48
-
49
- /**
50
- * Evaluate a tool call against loaded rules.
51
- * @param {Array} rules from loadRules().rules
52
- * @param {{tool:string, input:any}} call
53
- * @returns {null | {action:'deny'|'ask'|'allow', rule:string, reason:string}}
54
- * null = no rule matched → defer to the host's normal permission flow
55
- */
56
- export function evaluate(rules, { tool, input }) {
57
- const haystack = typeof input === 'string' ? input : JSON.stringify(input ?? {});
58
- let winner = null;
59
- for (const r of rules) {
60
- if (r.tool !== '*' && r.tool !== tool) continue;
61
- if (!r.re.test(haystack)) continue;
62
- if (!winner || SEVERITY[r.action] > SEVERITY[winner.action]) {
63
- winner = { action: r.action, rule: r.id, reason: r.reason || `matched guardrail ${r.id}` };
64
- }
65
- }
66
- return winner;
67
- }
68
-
69
- /**
70
- * Gemini CLI block shape: stdout JSON { decision: "deny", reason } (exit 0).
71
- * Gemini has no "ask" decision → defer (null) so its own approval flow runs.
72
- * Only an explicit deny blocks.
73
- */
74
- export function toGeminiDecision(verdict) {
75
- if (!verdict || verdict.action !== 'deny') return null;
76
- return { decision: 'deny', reason: `guardrail ${verdict.rule}: ${verdict.reason}` };
77
- }
78
-
79
- /** Map a verdict to Claude Code's PreToolUse hookSpecificOutput (verified schema). */
80
- export function toPreToolUseDecision(verdict) {
81
- if (!verdict || verdict.action === 'allow') return null; // no output → normal flow (fail-open / explicit allow)
82
- return {
83
- hookSpecificOutput: {
84
- hookEventName: 'PreToolUse',
85
- permissionDecision: verdict.action, // 'deny' | 'ask'
86
- permissionDecisionReason: `guardrail ${verdict.rule}: ${verdict.reason}`,
87
- },
88
- };
89
- }