@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
@@ -0,0 +1,360 @@
1
+ // zuzuu/commands/migrations/items.mjs — the Faculty Standard migrator (W24):
2
+ // legacy per-faculty shapes → one envelope. knowledge/memory frontmatter keys
3
+ // standardised, rules.json exploded into guardrails/items/, action.json+SKILL.md
4
+ // → ACTION.md, instructions/project.md → items/steering.md. Idempotent,
5
+ // fail-soft per item. Auto-runs from `zuzuu init` when old shapes are detected.
6
+
7
+ import { existsSync, readdirSync, readFileSync, writeFileSync, rmSync, statSync, mkdirSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { serializeEnvelope, deriveTitle } from '../../faculty/envelope.mjs';
10
+ import { serializeItem } from '../../knowledge/items.mjs';
11
+ import { FACULTIES } from '../../faculty/contract.mjs';
12
+ import { BUILTIN_MODULES } from '../../faculty/registry.mjs';
13
+
14
+ /** Does this file's frontmatter already carry the envelope (a `faculty:` key)? */
15
+ function isEnvelopeText(text) {
16
+ const m = String(text).match(/^---\r?\n([\s\S]*?)\r?\n---/);
17
+ return !!m && /^faculty:/m.test(m[1]);
18
+ }
19
+
20
+ const unquoteLegacy = (s) => {
21
+ const t = String(s).trim();
22
+ return (t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'")) ? t.slice(1, -1) : t;
23
+ };
24
+
25
+ /**
26
+ * Parse the PRE-standard knowledge/memory frontmatter grammar (top-level
27
+ * scalars; ONE nested map `attributes`/`provenance`; arrays of flat maps).
28
+ * Kept here (and only here) — the live parsers are envelope-only (clean break).
29
+ * Throws on violations; the caller fail-softs per item.
30
+ */
31
+ function parseLegacyFrontmatter(text) {
32
+ const m = String(text).match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
33
+ if (!m) throw new Error('no frontmatter block');
34
+ const [, fm, body] = m;
35
+ const item = { scalars: {}, maps: {}, lists: {}, body: body.trim() };
36
+ let section = null; // current nested key
37
+ let mode = null; // 'map' | 'list'
38
+ let current = null;
39
+ for (const raw of fm.split('\n')) {
40
+ if (!raw.trim()) continue;
41
+ const indent = raw.match(/^ */)[0].length;
42
+ const line = raw.trim();
43
+ const kv = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
44
+ if (indent === 0) {
45
+ current = null;
46
+ if (!kv) throw new Error(`bad line: ${line}`);
47
+ const [, key, val] = kv;
48
+ if (val === '') { section = key; mode = null; }
49
+ else { section = null; item.scalars[key] = unquoteLegacy(val); }
50
+ } else if (section) {
51
+ if (line.startsWith('- ')) {
52
+ mode = mode ?? 'list';
53
+ if (!item.lists[section]) item.lists[section] = [];
54
+ const ekv = line.slice(2).match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
55
+ if (ekv) { current = { [ekv[1]]: unquoteLegacy(ekv[2]) }; item.lists[section].push(current); }
56
+ else { current = null; item.lists[section].push(unquoteLegacy(line.slice(2))); }
57
+ } else if (current && kv) {
58
+ current[kv[1]] = unquoteLegacy(kv[2]);
59
+ } else if (kv) {
60
+ mode = mode ?? 'map';
61
+ if (!item.maps[section]) item.maps[section] = {};
62
+ item.maps[section][kv[1]] = unquoteLegacy(kv[2]);
63
+ } else {
64
+ throw new Error(`bad nested line: ${line}`);
65
+ }
66
+ } else {
67
+ throw new Error(`unexpected indented line: ${line}`);
68
+ }
69
+ }
70
+ return item;
71
+ }
72
+
73
+ /** Parse a legacy inline list: `[a, b]` or an already-array value. */
74
+ function legacyList(v) {
75
+ if (Array.isArray(v)) return v.map(String);
76
+ const t = String(v ?? '').trim();
77
+ if (t.startsWith('[') && t.endsWith(']')) {
78
+ return t.slice(1, -1).split(',').map((s) => unquoteLegacy(s)).filter(Boolean);
79
+ }
80
+ return t ? [t] : [];
81
+ }
82
+
83
+ /** knowledge/items/*.md: legacy keys → envelope (ids unchanged). */
84
+ function migrateKnowledgeItems(agentDir, out) {
85
+ const dir = join(agentDir, 'knowledge', 'items');
86
+ if (!existsSync(dir)) return;
87
+ for (const f of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
88
+ const path = join(dir, f);
89
+ try {
90
+ const text = readFileSync(path, 'utf8');
91
+ if (isEnvelopeText(text)) { out.skipped++; continue; }
92
+ const legacy = parseLegacyFrontmatter(text);
93
+ const item = {
94
+ id: legacy.scalars.id || f.replace(/\.md$/, ''),
95
+ type: legacy.scalars.type,
96
+ created_at: legacy.scalars.created_at,
97
+ updated_at: legacy.scalars.updated_at,
98
+ status: legacy.scalars.status ?? 'active',
99
+ attributes: legacy.maps.attributes ?? {},
100
+ relations: (legacy.lists.relations ?? []).filter((r) => typeof r === 'object'),
101
+ provenance: (legacy.lists.provenance ?? []).filter((r) => typeof r === 'object'),
102
+ body: legacy.body,
103
+ };
104
+ if (!item.type) throw new Error('item missing type');
105
+ writeFileSync(path, serializeItem(item)); // envelope via the knowledge wrapper
106
+ out.knowledge++;
107
+ } catch (e) {
108
+ out.errors.push({ file: `knowledge/items/${f}`, error: e.message });
109
+ }
110
+ }
111
+ }
112
+
113
+ /** memory/entries/*.md: legacy episode keys → envelope (kind: episode). */
114
+ function migrateMemoryEntries(agentDir, out) {
115
+ const dir = join(agentDir, 'memory', 'entries');
116
+ if (!existsSync(dir)) return;
117
+ for (const f of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
118
+ const path = join(dir, f);
119
+ try {
120
+ const text = readFileSync(path, 'utf8');
121
+ if (isEnvelopeText(text)) { out.skipped++; continue; }
122
+ const legacy = parseLegacyFrontmatter(text);
123
+ const id = legacy.scalars.id || f.replace(/\.md$/, '');
124
+ const payload = {};
125
+ const prov = legacy.maps.provenance ?? {};
126
+ const sessions = legacyList(prov.sessions ?? legacy.scalars.sessions ?? '');
127
+ const hosts = legacyList(prov.hosts ?? legacy.scalars.hosts ?? '');
128
+ const tags = legacyList(legacy.scalars.tags ?? '');
129
+ if (sessions.length) payload.sessions = sessions;
130
+ if (hosts.length) payload.hosts = hosts;
131
+ if (tags.length) payload.tags = tags;
132
+ writeFileSync(path, serializeEnvelope({
133
+ id,
134
+ faculty: 'memory',
135
+ kind: 'episode',
136
+ title: legacy.scalars.title ?? deriveTitle(legacy.body, id),
137
+ status: 'active', // curated/proposed lifecycles fold into active
138
+ created_at: legacy.scalars.date ?? legacy.scalars.created_at,
139
+ payload,
140
+ body: legacy.body,
141
+ }));
142
+ out.memory++;
143
+ } catch (e) {
144
+ out.errors.push({ file: `memory/entries/${f}`, error: e.message });
145
+ }
146
+ }
147
+ }
148
+
149
+ /** guardrails/rules.json: EXPLODE into items/<id>.md, then delete rules.json. */
150
+ function migrateGuardrails(agentDir, out) {
151
+ const rulesPath = join(agentDir, 'guardrails', 'rules.json');
152
+ if (!existsSync(rulesPath)) return;
153
+ let data;
154
+ try {
155
+ data = JSON.parse(readFileSync(rulesPath, 'utf8'));
156
+ } catch (e) {
157
+ out.errors.push({ file: 'guardrails/rules.json', error: e.message });
158
+ return; // unreadable → leave the file for the human, never destroy it
159
+ }
160
+ const rules = Array.isArray(data?.rules) ? data.rules : [];
161
+ const itemsDir = join(agentDir, 'guardrails', 'items');
162
+ let failed = 0;
163
+ for (let i = 0; i < rules.length; i++) {
164
+ const r = rules[i] ?? {};
165
+ try {
166
+ const id = String(r.id ?? `rule-${i}`);
167
+ mkdirSync(itemsDir, { recursive: true });
168
+ writeFileSync(join(itemsDir, `${id}.md`), serializeEnvelope({
169
+ id,
170
+ faculty: 'guardrails',
171
+ kind: 'rule',
172
+ title: deriveTitle(r.reason, id),
173
+ status: 'active',
174
+ created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
175
+ payload: { action: r.action, tool: r.tool || '*', pattern: String(r.pattern ?? ''), reason: String(r.reason ?? '') },
176
+ body: '',
177
+ }));
178
+ out.guardrails++;
179
+ } catch (e) {
180
+ failed++;
181
+ out.errors.push({ file: `guardrails/rules.json#${r.id ?? i}`, error: e.message });
182
+ }
183
+ }
184
+ // rules.json goes away only when every rule landed as an item (fail-soft)
185
+ if (failed === 0) {
186
+ try { rmSync(rulesPath, { force: true }); } catch { /* fail-soft */ }
187
+ }
188
+ }
189
+
190
+ /** One action dir: action.json (+SKILL.md) → ACTION.md; legacy files removed on success. */
191
+ function migrateActionDir(dir, slug, out) {
192
+ const actionMd = join(dir, 'ACTION.md');
193
+ if (existsSync(actionMd)) { out.skipped++; return; }
194
+ const manPath = join(dir, 'action.json');
195
+ const skillPath = join(dir, 'SKILL.md');
196
+ if (!existsSync(manPath) && !existsSync(skillPath)) return; // not an action dir
197
+ try {
198
+ let man = {};
199
+ if (existsSync(manPath)) man = JSON.parse(readFileSync(manPath, 'utf8'));
200
+ let skillFm = {};
201
+ let skillBody = '';
202
+ if (existsSync(skillPath)) {
203
+ const text = readFileSync(skillPath, 'utf8');
204
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
205
+ if (m) {
206
+ for (const line of m[1].split('\n')) {
207
+ const kv = line.match(/^(\w+):\s*(.*)$/);
208
+ if (kv) skillFm[kv[1]] = kv[2].trim();
209
+ }
210
+ skillBody = m[2].trim();
211
+ } else {
212
+ skillBody = text.trim();
213
+ }
214
+ }
215
+ const isScript = existsSync(join(dir, 'run.mjs'));
216
+ const payload = {};
217
+ if (isScript) payload.exec = 'run.mjs';
218
+ // default_args survive as payload.args (flat scalars only — the envelope grammar)
219
+ const args = {};
220
+ for (const [k, v] of Object.entries(man.default_args ?? {})) {
221
+ if (v == null || typeof v === 'object') continue;
222
+ args[k] = String(v);
223
+ }
224
+ if (Object.keys(args).length) payload.args = args;
225
+ const snippet = man.promptSnippet ?? man.description ?? skillFm.description ?? slug;
226
+ const bodyParts = [snippet];
227
+ if (man.description && man.description !== snippet) bodyParts.push('', man.description);
228
+ if (skillBody) bodyParts.push('', skillBody);
229
+ writeFileSync(actionMd, serializeEnvelope({
230
+ id: slug,
231
+ faculty: 'actions',
232
+ kind: isScript ? 'script' : 'runbook',
233
+ title: man.title ?? skillFm.name ?? slug,
234
+ status: 'active',
235
+ created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
236
+ payload,
237
+ body: bodyParts.join('\n'),
238
+ }));
239
+ // legacy files leave only after ACTION.md landed
240
+ rmSync(manPath, { force: true });
241
+ rmSync(skillPath, { force: true });
242
+ out.actions++;
243
+ } catch (e) {
244
+ out.errors.push({ file: `actions/${slug}`, error: e.message });
245
+ }
246
+ }
247
+
248
+ /** All action dirs: active + inbox (proposed) — same conversion. */
249
+ function migrateActions(agentDir, out) {
250
+ for (const base of [join(agentDir, 'actions'), join(agentDir, 'actions', 'inbox')]) {
251
+ if (!existsSync(base)) continue;
252
+ for (const name of readdirSync(base)) {
253
+ if (name === 'inbox' || name === 'proposals' || name === '_rolledback') continue;
254
+ const dir = join(base, name);
255
+ let isDir = false;
256
+ try { isDir = statSync(dir).isDirectory(); } catch { continue; }
257
+ if (isDir) migrateActionDir(dir, name, out);
258
+ }
259
+ }
260
+ }
261
+
262
+ /** instructions/project.md → items/steering.md. A customized steering item is
263
+ * never clobbered — project.md then stays put for the human to reconcile. */
264
+ function migrateInstructions(agentDir, out) {
265
+ const projPath = join(agentDir, 'instructions', 'project.md');
266
+ if (!existsSync(projPath)) return;
267
+ const steeringPath = join(agentDir, 'instructions', 'items', 'steering.md');
268
+ try {
269
+ const existing = existsSync(steeringPath) ? readFileSync(steeringPath, 'utf8') : null;
270
+ const placeholder = existing != null && existing.includes('<!-- Fill in:');
271
+ if (existing != null && !placeholder) {
272
+ out.errors.push({ file: 'instructions/project.md', error: 'a customized steering item already exists — merge by hand, then delete project.md' });
273
+ return;
274
+ }
275
+ const body = readFileSync(projPath, 'utf8').trim();
276
+ mkdirSync(join(agentDir, 'instructions', 'items'), { recursive: true });
277
+ writeFileSync(steeringPath, serializeEnvelope({
278
+ id: 'steering',
279
+ faculty: 'instructions',
280
+ kind: 'steering',
281
+ title: 'Project steering',
282
+ status: 'active',
283
+ created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
284
+ payload: { scope: 'project' },
285
+ body,
286
+ }));
287
+ out.instructions++;
288
+ rmSync(projPath, { force: true });
289
+ } catch (e) {
290
+ out.errors.push({ file: 'instructions/project.md', error: e.message });
291
+ }
292
+ }
293
+
294
+ /** Seed missing <faculty>/faculty.json manifests into an existing home (the
295
+ * Faculty Module contract, 2026-06-13). Only faculties whose dir exists get
296
+ * one — a migrator repairs, it never scaffolds. Idempotent, fail-soft. */
297
+ function seedFacultyManifests(agentDir, out) {
298
+ for (const f of FACULTIES) {
299
+ try {
300
+ const dir = join(agentDir, f);
301
+ if (!existsSync(dir)) continue;
302
+ const p = join(dir, 'faculty.json');
303
+ if (existsSync(p)) continue;
304
+ writeFileSync(p, JSON.stringify(BUILTIN_MODULES[f].manifest, null, 2) + '\n');
305
+ out.manifests++;
306
+ } catch (e) {
307
+ out.errors.push({ file: `${f}/faculty.json`, error: e.message });
308
+ }
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Are pre-standard shapes present? Cheap checks gate the init auto-run.
314
+ */
315
+ export function needsItemsMigration(agentDir) {
316
+ for (const f of FACULTIES) {
317
+ if (existsSync(join(agentDir, f)) && !existsSync(join(agentDir, f, 'faculty.json'))) return true;
318
+ }
319
+ if (existsSync(join(agentDir, 'guardrails', 'rules.json'))) return true;
320
+ if (existsSync(join(agentDir, 'instructions', 'project.md'))) return true;
321
+ for (const base of [join(agentDir, 'actions'), join(agentDir, 'actions', 'inbox')]) {
322
+ if (!existsSync(base)) continue;
323
+ for (const name of readdirSync(base)) {
324
+ if (name === 'inbox' || name === 'proposals' || name === '_rolledback') continue;
325
+ const dir = join(base, name);
326
+ try { if (!statSync(dir).isDirectory()) continue; } catch { continue; }
327
+ if (existsSync(join(dir, 'ACTION.md'))) continue;
328
+ if (existsSync(join(dir, 'action.json')) || existsSync(join(dir, 'SKILL.md'))) return true;
329
+ }
330
+ }
331
+ for (const seg of [['knowledge', 'items'], ['memory', 'entries']]) {
332
+ const dir = join(agentDir, ...seg);
333
+ if (!existsSync(dir)) continue;
334
+ for (const f of readdirSync(dir).filter((f) => f.endsWith('.md'))) {
335
+ try {
336
+ if (!isEnvelopeText(readFileSync(join(dir, f), 'utf8'))) return true;
337
+ } catch { /* unreadable file never forces a migration */ }
338
+ }
339
+ }
340
+ return false;
341
+ }
342
+
343
+ /**
344
+ * One-shot Faculty Standard migration for a home. Idempotent (already-envelope
345
+ * files are skipped) and fail-soft per item (an unconvertible item is reported,
346
+ * never fatal; its legacy source is left in place).
347
+ * @returns {{knowledge:number, memory:number, guardrails:number, actions:number,
348
+ * instructions:number, manifests:number, skipped:number, errors:Array<{file,error}>}}
349
+ */
350
+ export function migrateItems(agentDir) {
351
+ const out = { knowledge: 0, memory: 0, guardrails: 0, actions: 0, instructions: 0, manifests: 0, skipped: 0, errors: [] };
352
+ migrateKnowledgeItems(agentDir, out);
353
+ migrateMemoryEntries(agentDir, out);
354
+ migrateGuardrails(agentDir, out);
355
+ migrateActions(agentDir, out);
356
+ migrateInstructions(agentDir, out);
357
+ seedFacultyManifests(agentDir, out);
358
+ return out;
359
+ }
360
+
@@ -0,0 +1,100 @@
1
+ // zuzuu/commands/migrations/proposals.mjs — legacy proposal-schema migrator.
2
+ // {candidate, er} → spine {payload, analysis, faculty} (WS2-T5). Pure core:
3
+ // migrateProposals(agentDir) → { scanned, migrated, skipped }. Fail-soft per file.
4
+
5
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { proposalsDir, archiveDir } from '../../faculty/contract.mjs';
8
+
9
+ /**
10
+ * Determine whether a parsed JSON record is already in the new shape.
11
+ * A record is "new" when it has `payload` AND `faculty` set.
12
+ * If it only has `candidate` and/or lacks `faculty` it is legacy.
13
+ */
14
+ function isLegacy(rec) {
15
+ if (!rec || typeof rec !== 'object') return false;
16
+ // already migrated: has payload and faculty
17
+ if (rec.payload !== undefined && rec.faculty !== undefined) return false;
18
+ // legacy if it has candidate or er keys, or is simply missing faculty/payload
19
+ return rec.candidate !== undefined || rec.er !== undefined || rec.faculty === undefined;
20
+ }
21
+
22
+ /**
23
+ * Convert a legacy record to the new unified shape.
24
+ * Returns the migrated record (caller writes it back).
25
+ */
26
+ function migrateRecord(rec) {
27
+ const out = { ...rec };
28
+
29
+ // payload = candidate (drop candidate)
30
+ if (out.candidate !== undefined) {
31
+ if (out.payload === undefined) out.payload = out.candidate;
32
+ delete out.candidate;
33
+ }
34
+
35
+ // analysis = { er } (drop er)
36
+ if (out.er !== undefined) {
37
+ if (out.analysis === undefined) out.analysis = { er: out.er };
38
+ delete out.er;
39
+ }
40
+
41
+ // faculty defaults to 'knowledge' (only knowledge proposals exist pre-spine)
42
+ if (!out.faculty) out.faculty = 'knowledge';
43
+
44
+ return out;
45
+ }
46
+
47
+ /**
48
+ * Scan one directory of *.json files and migrate legacy records in-place.
49
+ * Fail-soft: bad JSON files are counted as skipped and never throw.
50
+ * Returns { migrated, scanned, skipped } for this directory.
51
+ */
52
+ function migrateDir(dir) {
53
+ if (!existsSync(dir)) return { migrated: 0, scanned: 0, skipped: 0 };
54
+
55
+ const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
56
+ let migrated = 0;
57
+ let skipped = 0;
58
+
59
+ for (const file of files) {
60
+ const fpath = join(dir, file);
61
+ let rec;
62
+ try {
63
+ rec = JSON.parse(readFileSync(fpath, 'utf8'));
64
+ } catch {
65
+ skipped++;
66
+ continue;
67
+ }
68
+
69
+ if (!isLegacy(rec)) {
70
+ skipped++;
71
+ continue;
72
+ }
73
+
74
+ try {
75
+ const migrated_rec = migrateRecord(rec);
76
+ writeFileSync(fpath, JSON.stringify(migrated_rec, null, 2) + '\n');
77
+ migrated++;
78
+ } catch {
79
+ skipped++;
80
+ }
81
+ }
82
+
83
+ return { migrated, scanned: files.length, skipped };
84
+ }
85
+
86
+ /**
87
+ * Scan both pending and archived Knowledge proposals.
88
+ * Returns { scanned, migrated, skipped }.
89
+ */
90
+ export function migrateProposals(agentDir) {
91
+ const pending = migrateDir(proposalsDir(agentDir, 'knowledge'));
92
+ const archived = migrateDir(archiveDir(agentDir, 'knowledge'));
93
+
94
+ return {
95
+ scanned: pending.scanned + archived.scanned,
96
+ migrated: pending.migrated + archived.migrated,
97
+ skipped: pending.skipped + archived.skipped,
98
+ };
99
+ }
100
+
@@ -0,0 +1,131 @@
1
+ // `zuzuu proposals` — the human gate, non-interactive: list|show|approve|reject.
2
+ // The same adapter-driven path `zuzuu review` walks, minus the ceremony. Pure
3
+ // data fns (proposalsListData/approveData/rejectData) feed the --json surface
4
+ // and the web workbench.
5
+
6
+ import { paths } from '../core/store.mjs';
7
+ import { processInbox } from '../knowledge/inbox.mjs';
8
+ import { getProposal } from '../knowledge/proposals.mjs';
9
+ import * as registry from '../faculty/registry.mjs';
10
+ import * as gate from '../faculty/gate.mjs';
11
+ import { pendingByFaculty } from '../faculty/pending.mjs';
12
+ import { knowledgeLine, proposalTitle } from '../faculty/render.mjs';
13
+
14
+ /**
15
+ * Resolve which faculty owns a given proposal id (used when --faculty is omitted).
16
+ * Defaults to 'knowledge' (the historical path) when no other faculty claims it.
17
+ */
18
+ function facultyOf(agentDir, id, only) {
19
+ if (only) return only;
20
+ for (const { adapter, proposals } of pendingByFaculty(agentDir)) {
21
+ if (proposals.some((p) => p.id === id)) return adapter.name;
22
+ }
23
+ return 'knowledge';
24
+ }
25
+
26
+ /**
27
+ * Pure: the structured object for `proposals approve --json`.
28
+ * Calls gate.approve and returns the result object the branch prints.
29
+ * @param {string} agentDir
30
+ * @param {string} id
31
+ * @param {string} faculty
32
+ * @returns {object} the gate result (contains ok, action, etc.)
33
+ */
34
+ export function approveData(agentDir, id, faculty) {
35
+ return gate.approve(agentDir, faculty, id);
36
+ }
37
+
38
+ /**
39
+ * Pure: the structured object for `proposals reject --json`.
40
+ * Calls gate.reject and returns the result object the branch prints.
41
+ * @param {string} agentDir
42
+ * @param {string} id
43
+ * @param {string} faculty
44
+ * @param {string} [reason]
45
+ * @returns {object} { ok, id, ... }
46
+ */
47
+ export function rejectData(agentDir, id, faculty, reason = '') {
48
+ const r = gate.reject(agentDir, faculty, id, reason);
49
+ return { ...r, id };
50
+ }
51
+
52
+ /**
53
+ * Pure: list pending proposals as structured data — the zuzuu-web /proposals source.
54
+ * @param {string} agentDir
55
+ * @param {string} [only] optional faculty filter
56
+ * @returns {{ pending: Array<{id, faculty, title}> }}
57
+ */
58
+ export function proposalsListData(agentDir, only) {
59
+ const groups = pendingByFaculty(agentDir).filter((g) => !only || g.adapter.name === only);
60
+ const pending = [];
61
+ for (const { adapter, proposals } of groups) {
62
+ for (const p of proposals) {
63
+ pending.push({ id: p.id, faculty: adapter.name, title: proposalTitle(adapter, p) });
64
+ }
65
+ }
66
+ return { pending };
67
+ }
68
+
69
+ /** Non-interactive: zuzuu proposals list|show <id>|approve <id>|reject <id> [--reason r] [--faculty f] */
70
+ export function proposals(args) {
71
+ const agentDir = paths().dir;
72
+ const sub = args._[0] || 'list';
73
+ const only = args.faculty; // optional filter; default = all
74
+ if (sub === 'list') {
75
+ if (args.json) {
76
+ processInbox(agentDir); // promote plain-text inbox candidates, same as text path
77
+ const d = proposalsListData(agentDir, only);
78
+ console.log(JSON.stringify(d));
79
+ return;
80
+ }
81
+ const inbox = processInbox(agentDir);
82
+ if (inbox.processed) console.log(`(processed ${inbox.processed} inbox candidate(s))`);
83
+ const groups = pendingByFaculty(agentDir).filter((g) => !only || g.adapter.name === only);
84
+ const any = groups.some((g) => g.proposals.length);
85
+ if (!any) return console.log('no pending proposals');
86
+ for (const { adapter, proposals } of groups) {
87
+ for (const p of proposals) {
88
+ // knowledge keeps its historical one-liner; other faculties use adapter.render
89
+ if (adapter.name === 'knowledge') {
90
+ console.log(knowledgeLine(p));
91
+ } else {
92
+ console.log(` ${adapter.render(p).line}`);
93
+ }
94
+ }
95
+ }
96
+ return;
97
+ }
98
+ const id = args._[1];
99
+ if (sub === 'show') {
100
+ const faculty = facultyOf(agentDir, id, only);
101
+ const a = registry.get(faculty);
102
+ const p = (a && typeof a.getProposal === 'function') ? a.getProposal(agentDir, id) : getProposal(agentDir, id);
103
+ if (!p) return console.error('not found');
104
+ // show always prints JSON (both with and without --json flag)
105
+ console.log(JSON.stringify(p, null, 2));
106
+ return;
107
+ }
108
+ if (sub === 'approve') {
109
+ const faculty = facultyOf(agentDir, id, only);
110
+ const r = approveData(agentDir, id, faculty);
111
+ if (args.json) {
112
+ console.log(JSON.stringify(r));
113
+ } else {
114
+ console.log(r.ok ? `✓ ${r.action}` : `✗ ${(r.errors ?? [r.action]).join('; ')}`);
115
+ for (const w of r.warnings ?? []) console.log(`⚠ ${w}`);
116
+ }
117
+ process.exit(r.ok ? 0 : 1);
118
+ }
119
+ if (sub === 'reject') {
120
+ const faculty = facultyOf(agentDir, id, only);
121
+ const r = rejectData(agentDir, id, faculty, args.reason || '');
122
+ if (args.json) {
123
+ console.log(JSON.stringify(r));
124
+ } else {
125
+ console.log(r.ok ? '✓ rejected' : '✗ not found');
126
+ }
127
+ process.exit(r.ok ? 0 : 1);
128
+ }
129
+ console.error('usage: zuzuu proposals list|show <id>|approve <id>|reject <id> [--reason r] [--faculty f]');
130
+ process.exit(1);
131
+ }