@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,93 +0,0 @@
1
- // zuzuu/instructions/adapter.mjs
2
- // The Instructions faculty adapter (WS2-T4). Wraps steering-amendment proposals
3
- // behind the faculty-spine adapter contract — { name, ingest, validate, apply,
4
- // render } — so `zuzuu review` can surface and approve them uniformly.
5
- //
6
- // An instructions proposal payload is a steering amendment:
7
- // { text } — a line or paragraph to append to project.md
8
- //
9
- // apply: appends the text as a line to .zuzuu/instructions/project.md (creates
10
- // the file if absent; never duplicates an already-present line).
11
- //
12
- // Registers itself on import.
13
-
14
- import { join } from 'node:path';
15
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
16
- import * as registry from '../faculty/registry.mjs';
17
-
18
- const name = 'instructions';
19
-
20
- // ---------------------------------------------------------------------------
21
- // helpers
22
- // ---------------------------------------------------------------------------
23
-
24
- function projectMdPath(agentDir) {
25
- return join(agentDir, 'instructions', 'project.md');
26
- }
27
-
28
- // ---------------------------------------------------------------------------
29
- // adapter contract
30
- // ---------------------------------------------------------------------------
31
-
32
- /**
33
- * Ingest a raw amendment object. Pass-through: the payload IS the amendment.
34
- */
35
- function ingest(_agentDir, raw) {
36
- const payload = raw?.payload ?? raw ?? {};
37
- return { payload, analysis: {} };
38
- }
39
-
40
- /**
41
- * Validate an amendment payload.
42
- * @returns {{ok:boolean, errors:string[], warnings:string[]}}
43
- */
44
- function validate(_agentDir, payload) {
45
- const errors = [];
46
- if (!payload?.text || !String(payload.text).trim()) {
47
- errors.push('text is required (non-empty steering amendment)');
48
- }
49
- return { ok: errors.length === 0, errors, warnings: [] };
50
- }
51
-
52
- /**
53
- * Apply an approved amendment: append text to project.md (idempotent on
54
- * identical lines — won't duplicate a line already present).
55
- * @returns {{ok:boolean, action:string, itemIds:string[]}}
56
- */
57
- function apply(agentDir, proposal) {
58
- const text = proposal?.payload?.text ?? '';
59
-
60
- // Ensure the instructions dir exists
61
- mkdirSync(join(agentDir, 'instructions'), { recursive: true });
62
-
63
- const path = projectMdPath(agentDir);
64
- const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
65
-
66
- // Idempotence: skip if the exact text is already present
67
- if (existing.includes(text)) {
68
- return { ok: true, action: 'amended instructions (already present)', itemIds: [] };
69
- }
70
-
71
- // Append (with trailing newline)
72
- const separator = existing && !existing.endsWith('\n') ? '\n' : '';
73
- writeFileSync(path, existing + separator + text + '\n');
74
-
75
- return { ok: true, action: 'amended instructions', itemIds: [] };
76
- }
77
-
78
- /**
79
- * Render an amendment proposal for the human gate.
80
- * @returns {{line:string, card:string}}
81
- */
82
- function render(proposal) {
83
- const text = proposal?.payload?.text ?? '';
84
- const preview = text.slice(0, 80).replace(/\n/g, ' ');
85
- return {
86
- line: `[amendment] ${preview}`,
87
- card: text,
88
- };
89
- }
90
-
91
- export const adapter = { name, ingest, validate, apply, render };
92
-
93
- registry.register(adapter);
@@ -1,99 +0,0 @@
1
- // zuzuu/knowledge/adapter.mjs
2
- // The Knowledge faculty adapter (WS2-T2). Wraps the EXISTING Knowledge pipeline
3
- // (proposals/ER/registry/items/index) behind the faculty-spine adapter contract
4
- // — { name, ingest, validate, apply, render } — without changing any behaviour.
5
- //
6
- // ingest — run ER on a candidate, mirroring createProposal's analysis step
7
- // validate — registry-based validation of an item
8
- // apply — IS the extracted approve apply body (applyKnowledgeProposal)
9
- // render — the human card the `zuzuu review` gate shows for a knowledge proposal
10
- //
11
- // Registers itself on import.
12
-
13
- import { resolve as erResolve } from './er.mjs';
14
- import { loadRegistry, validateItem } from './registry.mjs';
15
- import { allItems, slugify } from './items.mjs';
16
- import { applyKnowledgeProposal } from './proposals.mjs';
17
- import * as registry from '../faculty/registry.mjs';
18
-
19
- const name = 'knowledge';
20
-
21
- /**
22
- * Ingest a raw candidate: run ER against existing items and return the
23
- * normalised payload + analysis. Mirrors what createProposal computes today.
24
- * @param {string} agentDir
25
- * @param {{candidate:object, source?:string, evidence?:object}} raw
26
- */
27
- function ingest(agentDir, raw) {
28
- const { items } = allItems(agentDir);
29
- const candidate = { ...raw.candidate };
30
- candidate.id = candidate.id || slugify(candidate.body);
31
- const er = erResolve(candidate, items);
32
- return { payload: candidate, analysis: { er }, dedupeKey: candidate.id };
33
- }
34
-
35
- /**
36
- * Validate an item against the Knowledge registry.
37
- * @returns {{ok:boolean, errors:string[], warnings:string[]}}
38
- */
39
- function validate(agentDir, payload) {
40
- const reg = loadRegistry(agentDir);
41
- const v = validateItem(reg, payload);
42
- const warnings = [
43
- ...v.unknownKeys.attributes.map((k) => `unregistered attribute '${k}'`),
44
- ...v.unknownKeys.relations.map((t) => `unregistered relation type '${t}'`),
45
- ];
46
- return { ok: v.ok, errors: v.errors, warnings };
47
- }
48
-
49
- /**
50
- * Apply an approved proposal — delegates to the extracted approve apply body.
51
- * @returns {{ok:boolean, action:string, itemIds:string[], warnings:string[]}}
52
- */
53
- function apply(agentDir, proposal) {
54
- // Bridge spine-shaped records (payload/analysis.er) onto applyKnowledgeProposal's
55
- // legacy shape (candidate/er). Records that still carry candidate/er pass through.
56
- const legacy = {
57
- ...proposal,
58
- candidate: proposal.candidate ?? proposal.payload,
59
- er: proposal.er ?? proposal.analysis?.er,
60
- };
61
- const r = applyKnowledgeProposal(agentDir, legacy);
62
- return {
63
- ok: r.ok,
64
- action: r.action,
65
- itemIds: r.item ? [r.item] : [],
66
- warnings: r.warnings ?? [],
67
- };
68
- }
69
-
70
- /**
71
- * Render a proposal for the human gate. `card` mirrors the multi-line summary
72
- * `zuzuu review` shows for knowledge proposals (id, type, attrs/relations, ER
73
- * verdict); `line` is the one-line list form (`zuzuu proposals list`).
74
- * @returns {{line:string, card:string}}
75
- */
76
- function render(proposal) {
77
- if (proposal.kind === 'registry') {
78
- const what = `register ${String(proposal.registry).slice(0, -1)} '${proposal.key}'`;
79
- return {
80
- line: `${proposal.id} [${proposal.kind}] ${what}`,
81
- card: `${what} (seen ${proposal.evidence?.occurrences}× in candidates)`,
82
- };
83
- }
84
- const c = proposal.candidate ?? {};
85
- const er = proposal.er ?? {};
86
- const lines = [];
87
- lines.push(`${c.id ?? ''} ── ${c.type}: ${c.body?.slice(0, 100).replace(/\n/g, ' ')}`);
88
- for (const [k, v] of Object.entries(c.attributes ?? {})) lines.push(` · ${k} = ${v}`);
89
- for (const r of c.relations ?? []) lines.push(` → ${r.type} ${r.target}`);
90
- lines.push(` er: ${er.verdict}${er.match ? ` → ${er.match}` : ''} (${(er.confidence ?? 0).toFixed(2)} · ${er.reason ?? ''})`);
91
- return {
92
- line: `${proposal.id} [${er.verdict ?? proposal.kind}] ${c.type}: ${c.body?.slice(0, 60).replace(/\n/g, ' ')}`,
93
- card: lines.join('\n'),
94
- };
95
- }
96
-
97
- export const adapter = { name, ingest, validate, apply, render };
98
-
99
- registry.register(adapter);
@@ -1,121 +0,0 @@
1
- // zuzuu/memory/adapter.mjs
2
- // The Memory faculty adapter (WS2-T4). Wraps episode proposals behind the
3
- // faculty-spine adapter contract — { name, ingest, validate, apply, render } —
4
- // so `zuzuu review` can surface and approve memory entries uniformly.
5
- //
6
- // A memory proposal payload is an episode record matching the WS1 Memory schema:
7
- // { id, date, title, provenance, body }
8
- // id format: mem-<YYYY-MM-DD>-<slug>
9
- //
10
- // apply: writes .zuzuu/memory/entries/<id>.md with YAML frontmatter (status: curated)
11
- // and the body sections (Attempted / Resulted / Remember next time).
12
- //
13
- // Registers itself on import.
14
-
15
- import { join } from 'node:path';
16
- import { writeFileSync, mkdirSync } from 'node:fs';
17
- import * as registry from '../faculty/registry.mjs';
18
-
19
- const name = 'memory';
20
-
21
- // mem-<YYYY-MM-DD>-<slug>: the id must START with "mem-"
22
- const MEM_ID_RE = /^mem-/;
23
-
24
- // ---------------------------------------------------------------------------
25
- // helpers
26
- // ---------------------------------------------------------------------------
27
-
28
- function entriesDir(agentDir) {
29
- return join(agentDir, 'memory', 'entries');
30
- }
31
-
32
- function entryPath(agentDir, id) {
33
- return join(entriesDir(agentDir), `${id}.md`);
34
- }
35
-
36
- /** Render YAML frontmatter block from the payload fields. */
37
- function renderFrontmatter(payload) {
38
- const lines = ['---'];
39
- lines.push(`id: ${payload.id}`);
40
- if (payload.date) lines.push(`date: ${payload.date}`);
41
- if (payload.title) lines.push(`title: ${payload.title}`);
42
- if (payload.provenance) {
43
- lines.push('provenance:');
44
- const p = payload.provenance;
45
- if (Array.isArray(p.sessions)) lines.push(` sessions: [${p.sessions.join(', ')}]`);
46
- if (Array.isArray(p.hosts)) lines.push(` hosts: [${p.hosts.join(', ')}]`);
47
- }
48
- if (Array.isArray(payload.tags) && payload.tags.length) {
49
- lines.push(`tags: [${payload.tags.join(', ')}]`);
50
- }
51
- lines.push('status: curated');
52
- lines.push('---');
53
- return lines.join('\n');
54
- }
55
-
56
- // ---------------------------------------------------------------------------
57
- // adapter contract
58
- // ---------------------------------------------------------------------------
59
-
60
- /**
61
- * Ingest a raw episode. Pass-through: the payload IS the episode.
62
- */
63
- function ingest(_agentDir, raw) {
64
- const payload = raw?.payload ?? raw ?? {};
65
- return { payload, analysis: {}, dedupeKey: payload.id };
66
- }
67
-
68
- /**
69
- * Validate an episode payload.
70
- * @returns {{ok:boolean, errors:string[], warnings:string[]}}
71
- */
72
- function validate(_agentDir, payload) {
73
- const errors = [];
74
- if (!payload?.id || typeof payload.id !== 'string') {
75
- errors.push('id is required');
76
- } else if (!MEM_ID_RE.test(payload.id)) {
77
- errors.push(`id must match mem-<YYYY-MM-DD>-<slug> format (got '${payload.id}')`);
78
- }
79
- if (!payload?.title || !String(payload.title).trim()) {
80
- errors.push('title is required');
81
- }
82
- return { ok: errors.length === 0, errors, warnings: [] };
83
- }
84
-
85
- /**
86
- * Apply an approved episode proposal: write the entry Markdown file.
87
- * @returns {{ok:boolean, action:string, itemIds:string[]}}
88
- */
89
- function apply(agentDir, proposal) {
90
- const payload = proposal?.payload ?? {};
91
- const id = payload.id;
92
-
93
- mkdirSync(entriesDir(agentDir), { recursive: true });
94
-
95
- const frontmatter = renderFrontmatter(payload);
96
- const body = payload.body ?? '';
97
- const content = frontmatter + '\n' + body + (body.endsWith('\n') ? '' : '\n');
98
-
99
- writeFileSync(entryPath(agentDir, id), content);
100
-
101
- return { ok: true, action: `wrote memory ${id}`, itemIds: [id] };
102
- }
103
-
104
- /**
105
- * Render an episode proposal for the human gate.
106
- * @returns {{line:string, card:string}}
107
- */
108
- function render(proposal) {
109
- const p = proposal?.payload ?? {};
110
- const title = p.title ?? '';
111
- const date = p.date ?? '';
112
- const id = p.id ?? '';
113
- return {
114
- line: `${id} [episode] ${title} (${date})`,
115
- card: `${title}\n id: ${id} date: ${date}`,
116
- };
117
- }
118
-
119
- export const adapter = { name, ingest, validate, apply, render };
120
-
121
- registry.register(adapter);
@@ -1,118 +0,0 @@
1
- // zuzuu/miners/actions.mjs
2
- // Actions miner (WS5-T2) — detect recurring Bash 2-gram sequences across
3
- // sessions and scaffold runbook proposals into actions/inbox/<slug>/.
4
- //
5
- // Shape: { faculty:'actions', aggregate(sessions, opts), propose(agentDir, aggregated) }
6
- // Self-registers on import.
7
-
8
- import { join } from 'node:path';
9
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
10
- import { slugify } from '../knowledge/items.mjs';
11
- import { isSafeSlug, actionsDir, inboxDir } from '../actions/manifest.mjs';
12
- import { register } from './registry.mjs';
13
-
14
- // Must match the constant in knowledge/distill.mjs (adjacent Bash separator).
15
- const SEQ_SEP = ' && ';
16
-
17
- /**
18
- * Derive a safe slug from a raw sequence string (bounded, safe chars only).
19
- * e.g. "npm ci && npm test" → "npm-ci-npm-test" (max 50 chars).
20
- */
21
- function slugFromSequence(seq) {
22
- const raw = slugify(seq.replace(/ && /g, ' '), 50);
23
- // slugify already returns safe chars [a-z0-9-]; isSafeSlug allows upper too,
24
- // but we keep lower for readability. Force-safe just in case.
25
- return raw || 'action-sequence';
26
- }
27
-
28
- /**
29
- * Aggregate recurring Bash 2-gram sequences from mined sessions.
30
- *
31
- * @param {Array<{sessionId:string, sequences:string[]}>} sessions
32
- * The per-session mineTranscript output array.
33
- * @param {object} opts
34
- * @param {number} [opts.minSeqCount=3] min total occurrences across all sessions
35
- * @param {number} [opts.minSeqSessions=2] min distinct sessions the sequence appears in
36
- * @returns {Array<{payload:{slug,title,steps,promptSnippet,sequence}, evidence:{occurrences,sessions,sequence}}>}
37
- */
38
- export function aggregate(sessions, { minSeqCount = 3, minSeqSessions = 2 } = {}) {
39
- // Count occurrences per sequence string, tracking distinct session ids.
40
- const stats = new Map(); // rawSeq → { count, sessions: Set<sessionId> }
41
- for (const s of sessions) {
42
- if (!Array.isArray(s.sequences)) continue;
43
- for (const seq of s.sequences) {
44
- const st = stats.get(seq) ?? { count: 0, sessions: new Set() };
45
- st.count++;
46
- st.sessions.add(s.sessionId);
47
- stats.set(seq, st);
48
- }
49
- }
50
-
51
- const candidates = [];
52
- for (const [seq, st] of stats) {
53
- if (st.count < minSeqCount || st.sessions.size < minSeqSessions) continue;
54
- const steps = seq.split(SEQ_SEP);
55
- const slug = slugFromSequence(seq);
56
- // Make sure the slug is safe; if not, skip rather than emit a bad slug.
57
- if (!isSafeSlug(slug)) continue;
58
- const title = `Run sequence: ${steps.join(' → ')}`;
59
- const promptSnippet = `Runs: ${steps.join(' then ')}`;
60
- candidates.push({
61
- payload: { slug, title, steps, promptSnippet, sequence: seq },
62
- evidence: { occurrences: st.count, sessions: st.sessions.size, sequence: seq },
63
- });
64
- }
65
- return candidates;
66
- }
67
-
68
- /**
69
- * Write a runbook action proposal into actions/inbox/<slug>/ for each candidate.
70
- * Idempotent: skips if inbox/<slug>/ OR active actions/<slug>/ already exists.
71
- *
72
- * @param {string} agentDir
73
- * @param {ReturnType<typeof aggregate>} aggregated
74
- * @returns {number} count of new proposals written
75
- */
76
- export function propose(agentDir, aggregated) {
77
- const actDir = actionsDir(agentDir);
78
- const ibDir = inboxDir(agentDir);
79
- let count = 0;
80
- for (const c of aggregated) {
81
- const { slug, title, steps, promptSnippet } = c.payload;
82
- const inboxSlug = join(ibDir, slug);
83
- const activeSlug = join(actDir, slug);
84
- // Idempotent: skip if already proposed or already active.
85
- if (existsSync(inboxSlug) || existsSync(activeSlug)) continue;
86
-
87
- mkdirSync(inboxSlug, { recursive: true });
88
-
89
- // action.json — minimal manifest (no run.mjs; this is a runbook action).
90
- const manifest = {
91
- slug,
92
- title,
93
- description: `Recurring command sequence detected from session traces: ${steps.join(' → ')}.`,
94
- promptSnippet,
95
- };
96
- writeFileSync(join(inboxSlug, 'action.json'), JSON.stringify(manifest, null, 2) + '\n');
97
-
98
- // SKILL.md — numbered runbook steps.
99
- const stepsBlock = steps.map((cmd, i) => `${i + 1}. \`${cmd}\``).join('\n');
100
- const skillMd = `---
101
- name: ${title}
102
- description: ${manifest.description}
103
- ---
104
-
105
- ## Steps
106
-
107
- ${stepsBlock}
108
- `;
109
- writeFileSync(join(inboxSlug, 'SKILL.md'), skillMd);
110
-
111
- count++;
112
- }
113
- return count;
114
- }
115
-
116
- export const miner = { faculty: 'actions', aggregate, propose };
117
-
118
- register(miner);
@@ -1,179 +0,0 @@
1
- // zuzuu/miners/guardrails.mjs
2
- // Guardrails miner (WS5-T3) — detect repeated destructive-command failures
3
- // across sessions and propose ask-only guardrail rules.
4
- //
5
- // MANDATORY SAFETY PROPERTIES (enforced in aggregate):
6
- // 1. action is ALWAYS 'ask' — never 'deny'. Auto-proposed rules only escalate
7
- // to the human prompt, they never hard-block.
8
- // 2. Patterns are LITERAL-ESCAPED from the observed command — never a broad/
9
- // free regex. escapeRegex() handles this.
10
- // 3. Cross-session corroboration required — a destructive command must fail
11
- // ≥minFailures (default 3) times across ≥minSessions (default 2) DISTINCT
12
- // sessions. A single session — no matter how many failures — produces
13
- // NOTHING.
14
- //
15
- // Shape: { faculty:'guardrails', aggregate(sessions, opts), propose(agentDir, aggregated) }
16
- // Self-registers on import.
17
-
18
- import { join } from 'node:path';
19
- import { existsSync, readFileSync } from 'node:fs';
20
- import { slugify } from '../knowledge/items.mjs';
21
- import { makeProposal, writeProposal, listProposals, isArchivedResolved } from '../faculty/proposal.mjs';
22
- import { register } from './registry.mjs';
23
-
24
- // ---------------------------------------------------------------------------
25
- // escapeRegex — the ONLY safe way to build a pattern from a literal command.
26
- // Escapes all RegExp metacharacters so the pattern matches the exact string.
27
-
28
- /**
29
- * Escape all regex metacharacters in `s` so that `new RegExp(escapeRegex(s))`
30
- * matches exactly the string `s` and nothing broader.
31
- *
32
- * @param {string} s
33
- * @returns {string}
34
- */
35
- export function escapeRegex(s) {
36
- // Standard set of regex metacharacters that need escaping.
37
- return String(s).replace(/[.*+?^${}()|[\]\\\/\-]/g, '\\$&');
38
- }
39
-
40
- // ---------------------------------------------------------------------------
41
- // helpers
42
-
43
- /** Normalise a command string (trim + collapse whitespace). */
44
- const norm = (cmd) => String(cmd).trim().replace(/\s+/g, ' ').slice(0, 200);
45
-
46
- /** Derive a guardrails-miner id for a command. */
47
- function guardId(cmd) {
48
- return 'guard-' + slugify(cmd, 50);
49
- }
50
-
51
- /** Load rules.json; returns { version, rules:[] } if absent/unreadable. */
52
- function loadRules(agentDir) {
53
- const path = join(agentDir, 'guardrails', 'rules.json');
54
- if (!existsSync(path)) return { version: 1, rules: [] };
55
- try {
56
- return JSON.parse(readFileSync(path, 'utf8'));
57
- } catch {
58
- return { version: 1, rules: [] };
59
- }
60
- }
61
-
62
- // ---------------------------------------------------------------------------
63
- // aggregate
64
-
65
- /**
66
- * Group destructiveFailures by normalised command; emit a candidate ONLY when
67
- * both the occurrence count and distinct-session count meet their thresholds.
68
- *
69
- * SAFETY: a single-session cluster, no matter how large, produces NOTHING.
70
- *
71
- * @param {Array<{sessionId:string, destructiveFailures:{cmd:string,tool:string}[]}>} sessions
72
- * @param {object} opts
73
- * @param {number} [opts.minFailures=3] min total failures across all sessions
74
- * @param {number} [opts.minSessions=2] min distinct sessions with ≥1 failure each
75
- * @returns {Array<{payload:{id,action,tool,pattern,reason}, evidence:{occurrences,sessions}}>}
76
- */
77
- export function aggregate(sessions, { minFailures = 3, minSessions = 2 } = {}) {
78
- // cmd (normalized) → { count: number, sessions: Set<sessionId>, tool: string }
79
- const stats = new Map();
80
-
81
- for (const s of sessions) {
82
- if (!Array.isArray(s.destructiveFailures)) continue;
83
- for (const { cmd, tool } of s.destructiveFailures) {
84
- const key = norm(cmd);
85
- const st = stats.get(key) ?? { count: 0, sessions: new Set(), tool: tool ?? 'Bash' };
86
- st.count++;
87
- st.sessions.add(s.sessionId);
88
- // Keep first observed tool name (they should all be 'Bash' for destructive cmds).
89
- stats.set(key, st);
90
- }
91
- }
92
-
93
- const candidates = [];
94
- for (const [cmd, st] of stats) {
95
- // SAFETY: enforce BOTH thresholds — cross-session gate is the key one.
96
- if (st.count < minFailures) continue;
97
- if (st.sessions.size < minSessions) continue; // ← single-session always rejected here
98
-
99
- const id = guardId(cmd);
100
- const pattern = escapeRegex(cmd);
101
- const tool = st.tool ?? 'Bash';
102
-
103
- candidates.push({
104
- payload: {
105
- id,
106
- // SAFETY: ALWAYS 'ask', never 'deny'.
107
- action: 'ask',
108
- tool,
109
- pattern,
110
- reason: `auto-proposed: '${cmd}' failed repeatedly across sessions — confirm before running`,
111
- },
112
- evidence: {
113
- occurrences: st.count,
114
- sessions: st.sessions.size,
115
- },
116
- });
117
- }
118
-
119
- return candidates;
120
- }
121
-
122
- // ---------------------------------------------------------------------------
123
- // propose
124
-
125
- /**
126
- * Write a guardrails proposal into .zuzuu/guardrails/proposals/ for each candidate.
127
- * Idempotent:
128
- * - skips if a guardrails proposal with the same payload.id already exists
129
- * - skips if rules.json already has a rule with that id
130
- * - skips if the id is already resolved in proposals/archive/ — a rejection
131
- * is remembered; re-distilling never resurrects it
132
- *
133
- * The proposals flow through `zuzuu review` → guardrails adapter on approval.
134
- *
135
- * @param {string} agentDir
136
- * @param {ReturnType<typeof aggregate>} aggregated
137
- * @returns {number} count of new proposals written
138
- */
139
- export function propose(agentDir, aggregated) {
140
- // Load existing proposals (ids already pending).
141
- const existing = listProposals(agentDir, 'guardrails');
142
- const existingIds = new Set(existing.map((p) => p.payload?.id).filter(Boolean));
143
-
144
- // Load existing rules (ids already applied).
145
- const rulesData = loadRules(agentDir);
146
- const rulesIds = new Set((rulesData.rules ?? []).map((r) => r.id).filter(Boolean));
147
-
148
- let count = 0;
149
- for (const c of aggregated) {
150
- const { payload, evidence } = c;
151
-
152
- // Idempotent: skip if already proposed or already a live rule.
153
- if (existingIds.has(payload.id)) continue;
154
- if (rulesIds.has(payload.id)) continue;
155
-
156
- const proposal = makeProposal({
157
- faculty: 'guardrails',
158
- kind: 'rule',
159
- source: 'distill',
160
- payload,
161
- evidence,
162
- });
163
-
164
- // A rejection is remembered: never resurrect an archive-resolved id.
165
- if (isArchivedResolved(agentDir, 'guardrails', proposal.id)) continue;
166
-
167
- writeProposal(agentDir, proposal);
168
- count++;
169
- }
170
-
171
- return count;
172
- }
173
-
174
- // ---------------------------------------------------------------------------
175
- // self-register
176
-
177
- export const miner = { faculty: 'guardrails', aggregate, propose };
178
-
179
- register(miner);