@zuzuucodes/cli 1.5.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 (198) hide show
  1. package/bin/zuzuu.mjs +12 -3
  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} +46 -116
  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-Dx3ub8Pk.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-DM6oHc7c.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-DflaUwqW.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-wbBeb0oN.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-DRwkDcoK.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/trail.mjs +1 -1
  110. package/zuzuu/commands/act.mjs +1 -1
  111. package/zuzuu/commands/capture.mjs +2 -2
  112. package/zuzuu/commands/code.mjs +2 -2
  113. package/zuzuu/commands/digest.mjs +2 -2
  114. package/zuzuu/commands/distill.mjs +15 -16
  115. package/zuzuu/commands/doctor.mjs +39 -4
  116. package/zuzuu/commands/enable.mjs +1 -1
  117. package/zuzuu/commands/eval.mjs +3 -36
  118. package/zuzuu/commands/faculty.mjs +102 -19
  119. package/zuzuu/commands/generation.mjs +3 -4
  120. package/zuzuu/commands/hook.mjs +7 -7
  121. package/zuzuu/commands/inbox.mjs +1 -6
  122. package/zuzuu/commands/init.mjs +5 -4
  123. package/zuzuu/commands/knowledge.mjs +1 -1
  124. package/zuzuu/commands/migrations/home.mjs +96 -0
  125. package/zuzuu/commands/migrations/index.mjs +48 -0
  126. package/zuzuu/commands/{migrate.mjs → migrations/items.mjs} +34 -246
  127. package/zuzuu/commands/migrations/proposals.mjs +100 -0
  128. package/zuzuu/commands/proposals.mjs +131 -0
  129. package/zuzuu/commands/review.mjs +13 -227
  130. package/zuzuu/commands/session.mjs +8 -2
  131. package/zuzuu/commands/sessions.mjs +159 -0
  132. package/zuzuu/commands/status.mjs +3 -3
  133. package/zuzuu/commands/trace.mjs +1 -1
  134. package/zuzuu/{capture-core.mjs → core/capture-core.mjs} +3 -3
  135. package/zuzuu/{store.mjs → core/store.mjs} +1 -1
  136. package/zuzuu/digest/compose.mjs +96 -0
  137. package/zuzuu/eval/score.mjs +14 -1
  138. package/zuzuu/faculties/actions/index.mjs +283 -0
  139. package/zuzuu/faculties/guardrails/index.mjs +320 -0
  140. package/zuzuu/faculties/instructions/index.mjs +288 -0
  141. package/zuzuu/faculties/knowledge/index.mjs +185 -0
  142. package/zuzuu/{memory/adapter.mjs → faculties/memory/index.mjs} +37 -9
  143. package/zuzuu/faculty/generation/read.mjs +206 -0
  144. package/zuzuu/faculty/generation/write.mjs +207 -0
  145. package/zuzuu/faculty/items.mjs +11 -5
  146. package/zuzuu/faculty/module.mjs +74 -0
  147. package/zuzuu/faculty/pending.mjs +63 -0
  148. package/zuzuu/faculty/registry.mjs +204 -18
  149. package/zuzuu/faculty/render.mjs +59 -0
  150. package/zuzuu/faculty/trail.mjs +1 -1
  151. package/zuzuu/{guardrails.mjs → guardrails/engine.mjs} +1 -1
  152. package/zuzuu/{scaffold.mjs → home/scaffold.mjs} +12 -2
  153. package/zuzuu/live/live-store.mjs +2 -2
  154. package/zuzuu/live/reconcile.mjs +2 -2
  155. package/zuzuu/sessions/git.mjs +47 -0
  156. package/zuzuu/{session-git.mjs → sessions/session-git.mjs} +5 -43
  157. package/web-app/web-dist/assets/DiffTab-BpGp1akx.js +0 -1
  158. package/web-app/web-dist/assets/MonacoFile-CqbVacUZ.js +0 -1
  159. package/web-app/web-dist/assets/angular-html-CmT26mqM.js +0 -1
  160. package/web-app/web-dist/assets/c-BvoqrSVH.js +0 -1
  161. package/web-app/web-dist/assets/cpp-BXsk94m0.js +0 -1
  162. package/web-app/web-dist/assets/css-Z8oOGxII.js +0 -1
  163. package/web-app/web-dist/assets/dist-C6R6xoyX.js +0 -153
  164. package/web-app/web-dist/assets/glsl-KwyfU2aa.js +0 -1
  165. package/web-app/web-dist/assets/graphql-DSeOUAa2.js +0 -1
  166. package/web-app/web-dist/assets/haml-azVoxQRV.js +0 -1
  167. package/web-app/web-dist/assets/html-D_7P5S4m.js +0 -1
  168. package/web-app/web-dist/assets/index-DHpC851f.js +0 -268
  169. package/web-app/web-dist/assets/index-O-t1gyMG.css +0 -2
  170. package/web-app/web-dist/assets/java-D4RbCvBe.js +0 -1
  171. package/web-app/web-dist/assets/javascript-Cb010CKM.js +0 -1
  172. package/web-app/web-dist/assets/json-DWgqV4D1.js +0 -1
  173. package/web-app/web-dist/assets/jsx-CZjSJa1f.js +0 -1
  174. package/web-app/web-dist/assets/lua-TGj_6NzO.js +0 -1
  175. package/web-app/web-dist/assets/r-fCpuAR7u.js +0 -1
  176. package/web-app/web-dist/assets/regexp-B4yxx-Ty.js +0 -1
  177. package/web-app/web-dist/assets/scss-QdjMO_xV.js +0 -1
  178. package/web-app/web-dist/assets/shellscript-BnlgeVVx.js +0 -1
  179. package/web-app/web-dist/assets/sql-DGnQv6iD.js +0 -1
  180. package/web-app/web-dist/assets/tsx-MJ0-9sYG.js +0 -1
  181. package/web-app/web-dist/assets/typescript-C17ZkDe8.js +0 -1
  182. package/web-app/web-dist/assets/xml-CA9lHFQV.js +0 -1
  183. package/web-app/web-dist/assets/yaml-CwRYMJka.js +0 -1
  184. package/zuzuu/actions/adapter.mjs +0 -122
  185. package/zuzuu/digest.mjs +0 -154
  186. package/zuzuu/faculty/generation.mjs +0 -398
  187. package/zuzuu/guardrails/adapter.mjs +0 -103
  188. package/zuzuu/instructions/adapter.mjs +0 -93
  189. package/zuzuu/knowledge/adapter.mjs +0 -99
  190. package/zuzuu/miners/actions.mjs +0 -112
  191. package/zuzuu/miners/guardrails.mjs +0 -176
  192. package/zuzuu/miners/instructions.mjs +0 -157
  193. package/zuzuu/miners/knowledge.mjs +0 -25
  194. package/zuzuu/miners/memory.mjs +0 -27
  195. package/zuzuu/miners/registry.mjs +0 -31
  196. /package/web-app/web-dist/assets/{chunk-QTnfLwEv.js → rolldown-runtime-QTnfLwEv.js} +0 -0
  197. /package/zuzuu/{session.mjs → core/session.mjs} +0 -0
  198. /package/zuzuu/{inject.mjs → home/inject.mjs} +0 -0
@@ -0,0 +1,96 @@
1
+ // zuzuu/commands/migrations/home.mjs — the home migrator (W1, 2026-06-12):
2
+ // visible agent/ → hidden .zuzuu/ (byte-identical inner layout), plus the
3
+ // follow-ups: traceRef rewrite, gitignore + deny-rule swap, host-block re-inject.
4
+ // Pure core: migrateHome(root) → { migrated }. Idempotent + fail-soft.
5
+
6
+ import { existsSync, readFileSync, writeFileSync, renameSync, rmSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { repoRoot } from '../../core/store.mjs';
9
+ import { ensureGitignore } from '../../home/scaffold.mjs';
10
+ import { injectBlock, BLOCK_VERSION } from '../../home/inject.mjs';
11
+
12
+ // The denies the old visible-agent/ home installed; scrubbed here (NOT kept in
13
+ // install.mjs — clean break) and replaced by the current narrow .zuzuu/ pair.
14
+ const LEGACY_DENY_RULES = ['Read(./agent/.traces/**)', 'Read(./agent/.live/**)'];
15
+ const NEW_DENY_RULES = ['Read(./.zuzuu/.traces/**)', 'Read(./.zuzuu/.live/**)'];
16
+
17
+ /**
18
+ * One-shot HOME migration: visible `agent/` → hidden `.zuzuu/` (byte-identical
19
+ * inner layout). Gated on `agent/agent.json` — `agent/` is a common dir name,
20
+ * so an unrelated agent/ dir in a brownfield repo must NEVER be touched (the
21
+ * one place this differs from the old `.mns→agent` precedent). Idempotent +
22
+ * fail-soft; NEVER clobbers an existing .zuzuu/. Pure FS move (renameSync).
23
+ * @returns {{migrated: boolean}}
24
+ */
25
+ export function migrateHome(root = repoRoot()) {
26
+ const legacy = join(root, 'agent');
27
+ const home = join(root, '.zuzuu');
28
+ if (existsSync(home) || !existsSync(join(legacy, 'agent.json'))) return { migrated: false };
29
+
30
+ renameSync(legacy, home); // move the whole home (atomic on same filesystem)
31
+
32
+ rewriteTraceRefs(home);
33
+ rewriteGitignore(root);
34
+ scrubLegacyDenies(root);
35
+ // derived index: drop, it rebuilds on the next recall/reindex
36
+ try { rmSync(join(home, 'knowledge', '.index.db'), { force: true }); } catch { /* fail-soft */ }
37
+ return { migrated: true };
38
+ }
39
+
40
+ /** sessions.json stores repo-relative traceRefs (`agent/.traces/…`) — re-point them. */
41
+ function rewriteTraceRefs(home) {
42
+ const index = join(home, 'sessions.json');
43
+ if (!existsSync(index)) return;
44
+ try {
45
+ const idx = JSON.parse(readFileSync(index, 'utf8'));
46
+ for (const s of idx.sessions || []) {
47
+ if (typeof s.traceRef === 'string' && s.traceRef.startsWith('agent/')) {
48
+ s.traceRef = '.zuzuu/' + s.traceRef.slice('agent/'.length);
49
+ }
50
+ }
51
+ writeFileSync(index, JSON.stringify(idx, null, 2) + '\n');
52
+ } catch { /* fail-soft: a bad index never blocks the move */ }
53
+ }
54
+
55
+ /** Drop legacy `agent/` ignore lines, then append the canonical .zuzuu/ ones. */
56
+ function rewriteGitignore(root) {
57
+ const path = join(root, '.gitignore');
58
+ if (existsSync(path)) {
59
+ const kept = readFileSync(path, 'utf8')
60
+ .split('\n')
61
+ .filter((l) => !l.trim().startsWith('agent/'))
62
+ .join('\n');
63
+ writeFileSync(path, kept.endsWith('\n') || kept === '' ? kept : kept + '\n');
64
+ }
65
+ ensureGitignore(root); // appends .zuzuu/.traces/, .zuzuu/.live/, .zuzuu/knowledge/.index.db
66
+ }
67
+
68
+ /** Swap the old agent/ deny rules for the .zuzuu/ pair in any .claude settings file. */
69
+ function scrubLegacyDenies(root) {
70
+ for (const f of ['settings.json', 'settings.local.json']) {
71
+ const path = join(root, '.claude', f);
72
+ if (!existsSync(path)) continue;
73
+ try {
74
+ const s = JSON.parse(readFileSync(path, 'utf8'));
75
+ const deny = s?.permissions?.deny;
76
+ if (!Array.isArray(deny)) continue;
77
+ const hadOurs = deny.some((r) => LEGACY_DENY_RULES.includes(r));
78
+ if (!hadOurs) continue;
79
+ s.permissions.deny = deny.filter((r) => !LEGACY_DENY_RULES.includes(r));
80
+ for (const rule of NEW_DENY_RULES) if (!s.permissions.deny.includes(rule)) s.permissions.deny.push(rule);
81
+ writeFileSync(path, JSON.stringify(s, null, 2) + '\n');
82
+ } catch { /* fail-soft: never break settings we can't parse */ }
83
+ }
84
+ }
85
+
86
+ /** Re-inject the current faculties block into any existing host instruction files. */
87
+ export function reinjectHostBlocks(root) {
88
+ for (const f of ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']) {
89
+ const p = join(root, f);
90
+ if (existsSync(p)) {
91
+ const text = readFileSync(p, 'utf8');
92
+ if (!text.includes(`zuzuu:faculties:v${BLOCK_VERSION}`)) writeFileSync(p, injectBlock(text));
93
+ }
94
+ }
95
+ }
96
+
@@ -0,0 +1,48 @@
1
+ // zuzuu/commands/migrations/index.mjs — `zuzuu migrate` dispatch.
2
+ //
3
+ // (default) proposal schema: legacy {candidate, er} → spine {payload, analysis, faculty}
4
+ // --home faculty home: visible agent/ → hidden .zuzuu/
5
+ // --items Faculty Standard: legacy faculty shapes → the envelope standard
6
+ //
7
+ // Pure cores live beside this file (proposals/home/items.mjs); this is the CLI
8
+ // surface — resolves paths, runs a core, prints the summary.
9
+
10
+ import { paths, repoRoot } from '../../core/store.mjs';
11
+ import { BLOCK_VERSION } from '../../home/inject.mjs';
12
+ import { migrateProposals } from './proposals.mjs';
13
+ import { migrateHome, reinjectHostBlocks } from './home.mjs';
14
+ import { migrateItems, needsItemsMigration } from './items.mjs';
15
+
16
+ export { migrateProposals } from './proposals.mjs';
17
+ export { migrateHome } from './home.mjs';
18
+ export { migrateItems, needsItemsMigration } from './items.mjs';
19
+
20
+ export function migrate(args = {}) {
21
+ if (args.items) {
22
+ const agentDir = paths().dir;
23
+ const r = migrateItems(agentDir);
24
+ const total = r.knowledge + r.memory + r.guardrails + r.actions + r.instructions;
25
+ console.log(`migrate --items: ${total} item(s) → the Faculty Standard envelope — knowledge ${r.knowledge} · memory ${r.memory} · guardrails ${r.guardrails} · actions ${r.actions} · instructions ${r.instructions} (${r.skipped} already standard)`);
26
+ if (r.manifests) console.log(` seeded ${r.manifests} faculty manifest(s) (faculty.json — the Faculty Module contract)`);
27
+ for (const e of r.errors) console.log(` ✗ ${e.file}: ${e.error}`);
28
+ if (!total && !r.manifests && !r.errors.length) console.log(' nothing to migrate (the home already speaks the envelope)');
29
+ return;
30
+ }
31
+ if (args.home) {
32
+ const root = repoRoot(process.cwd());
33
+ const { migrated } = migrateHome(root);
34
+ if (!migrated) { console.log('migrate --home: nothing to do (already .zuzuu/, or no zuzuu home at agent/)'); return; }
35
+ try { reinjectHostBlocks(root); } catch { /* fail-open */ }
36
+ console.log(`migrate --home: agent/ → .zuzuu/ (hidden, like .git; block v${BLOCK_VERSION}, gitignore + deny rules rewritten)`);
37
+ console.log(' transparency lives in porcelain now: zuzuu status · explain · digest');
38
+ return;
39
+ }
40
+ const agentDir = paths().dir;
41
+ const { scanned, migrated, skipped } = migrateProposals(agentDir);
42
+ console.log(`migrate: scanned ${scanned} proposal(s) — migrated ${migrated}, skipped ${skipped}`);
43
+ if (migrated > 0) {
44
+ console.log(' legacy candidate/er keys rewritten to payload/analysis.er + faculty:knowledge');
45
+ } else {
46
+ console.log(' nothing to migrate (all records already in new shape)');
47
+ }
48
+ }
@@ -1,217 +1,15 @@
1
- // zuzuu/commands/migrate.mjs
2
- // `zuzuu migrate` one-time migrators.
3
- //
4
- // (default) proposal schema: legacy {candidate, er}spine {payload, analysis, faculty} (WS2-T5)
5
- // --home faculty home: visible agent/ hidden .zuzuu/ (W1, 2026-06-12)
6
- // --items Faculty Standard (W24): legacy per-faculty shapes → one envelope —
7
- // knowledge/memory frontmatter keys standardised, rules.json
8
- // exploded into guardrails/items/, action.json+SKILL.md → ACTION.md,
9
- // instructions/project.md → items/steering.md. Idempotent,
10
- // fail-soft per item. Auto-runs from `zuzuu init` when old shapes
11
- // are detected (like migrateHome).
12
- //
13
- // Pure cores: migrateProposals(agentDir) → { scanned, migrated, skipped }
14
- // migrateHome(root) → { migrated }
15
- // migrateItems(agentDir) → { knowledge, memory, guardrails, actions, instructions, skipped, errors }
16
- // CLI surface: migrate(args) — resolves paths, runs the core, prints summary.
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.mditems/steering.md. Idempotent,
5
+ // fail-soft per item. Auto-runs from `zuzuu init` when old shapes are detected.
17
6
 
18
- import { existsSync, readdirSync, readFileSync, writeFileSync, renameSync, rmSync, statSync, mkdirSync } from 'node:fs';
7
+ import { existsSync, readdirSync, readFileSync, writeFileSync, rmSync, statSync, mkdirSync } from 'node:fs';
19
8
  import { join } from 'node:path';
20
- import { paths, repoRoot } from '../store.mjs';
21
- import { proposalsDir, archiveDir } from '../faculty/contract.mjs';
22
- import { ensureGitignore } from '../scaffold.mjs';
23
- import { injectBlock, BLOCK_VERSION } from '../inject.mjs';
24
- import { serializeEnvelope, deriveTitle } from '../faculty/envelope.mjs';
25
- import { serializeItem } from '../knowledge/items.mjs';
26
-
27
- // ---------------------------------------------------------------------------
28
- // pure core — testable without process.*
29
- // ---------------------------------------------------------------------------
30
-
31
- /**
32
- * Determine whether a parsed JSON record is already in the new shape.
33
- * A record is "new" when it has `payload` AND `faculty` set.
34
- * If it only has `candidate` and/or lacks `faculty` it is legacy.
35
- */
36
- function isLegacy(rec) {
37
- if (!rec || typeof rec !== 'object') return false;
38
- // already migrated: has payload and faculty
39
- if (rec.payload !== undefined && rec.faculty !== undefined) return false;
40
- // legacy if it has candidate or er keys, or is simply missing faculty/payload
41
- return rec.candidate !== undefined || rec.er !== undefined || rec.faculty === undefined;
42
- }
43
-
44
- /**
45
- * Convert a legacy record to the new unified shape.
46
- * Returns the migrated record (caller writes it back).
47
- */
48
- function migrateRecord(rec) {
49
- const out = { ...rec };
50
-
51
- // payload = candidate (drop candidate)
52
- if (out.candidate !== undefined) {
53
- if (out.payload === undefined) out.payload = out.candidate;
54
- delete out.candidate;
55
- }
56
-
57
- // analysis = { er } (drop er)
58
- if (out.er !== undefined) {
59
- if (out.analysis === undefined) out.analysis = { er: out.er };
60
- delete out.er;
61
- }
62
-
63
- // faculty defaults to 'knowledge' (only knowledge proposals exist pre-spine)
64
- if (!out.faculty) out.faculty = 'knowledge';
65
-
66
- return out;
67
- }
68
-
69
- /**
70
- * Scan one directory of *.json files and migrate legacy records in-place.
71
- * Fail-soft: bad JSON files are counted as skipped and never throw.
72
- * Returns { migrated, scanned, skipped } for this directory.
73
- */
74
- function migrateDir(dir) {
75
- if (!existsSync(dir)) return { migrated: 0, scanned: 0, skipped: 0 };
76
-
77
- const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
78
- let migrated = 0;
79
- let skipped = 0;
80
-
81
- for (const file of files) {
82
- const fpath = join(dir, file);
83
- let rec;
84
- try {
85
- rec = JSON.parse(readFileSync(fpath, 'utf8'));
86
- } catch {
87
- skipped++;
88
- continue;
89
- }
90
-
91
- if (!isLegacy(rec)) {
92
- skipped++;
93
- continue;
94
- }
95
-
96
- try {
97
- const migrated_rec = migrateRecord(rec);
98
- writeFileSync(fpath, JSON.stringify(migrated_rec, null, 2) + '\n');
99
- migrated++;
100
- } catch {
101
- skipped++;
102
- }
103
- }
104
-
105
- return { migrated, scanned: files.length, skipped };
106
- }
107
-
108
- /**
109
- * Scan both pending and archived Knowledge proposals.
110
- * Returns { scanned, migrated, skipped }.
111
- */
112
- export function migrateProposals(agentDir) {
113
- const pending = migrateDir(proposalsDir(agentDir, 'knowledge'));
114
- const archived = migrateDir(archiveDir(agentDir, 'knowledge'));
115
-
116
- return {
117
- scanned: pending.scanned + archived.scanned,
118
- migrated: pending.migrated + archived.migrated,
119
- skipped: pending.skipped + archived.skipped,
120
- };
121
- }
122
-
123
- // ---------------------------------------------------------------------------
124
- // home migration — agent/ → .zuzuu/ (W1, 2026-06-12)
125
- // ---------------------------------------------------------------------------
126
-
127
- // The denies the old visible-agent/ home installed; scrubbed here (NOT kept in
128
- // install.mjs — clean break) and replaced by the current narrow .zuzuu/ pair.
129
- const LEGACY_DENY_RULES = ['Read(./agent/.traces/**)', 'Read(./agent/.live/**)'];
130
- const NEW_DENY_RULES = ['Read(./.zuzuu/.traces/**)', 'Read(./.zuzuu/.live/**)'];
131
-
132
- /**
133
- * One-shot HOME migration: visible `agent/` → hidden `.zuzuu/` (byte-identical
134
- * inner layout). Gated on `agent/agent.json` — `agent/` is a common dir name,
135
- * so an unrelated agent/ dir in a brownfield repo must NEVER be touched (the
136
- * one place this differs from the old `.mns→agent` precedent). Idempotent +
137
- * fail-soft; NEVER clobbers an existing .zuzuu/. Pure FS move (renameSync).
138
- * @returns {{migrated: boolean}}
139
- */
140
- export function migrateHome(root = repoRoot()) {
141
- const legacy = join(root, 'agent');
142
- const home = join(root, '.zuzuu');
143
- if (existsSync(home) || !existsSync(join(legacy, 'agent.json'))) return { migrated: false };
144
-
145
- renameSync(legacy, home); // move the whole home (atomic on same filesystem)
146
-
147
- rewriteTraceRefs(home);
148
- rewriteGitignore(root);
149
- scrubLegacyDenies(root);
150
- // derived index: drop, it rebuilds on the next recall/reindex
151
- try { rmSync(join(home, 'knowledge', '.index.db'), { force: true }); } catch { /* fail-soft */ }
152
- return { migrated: true };
153
- }
154
-
155
- /** sessions.json stores repo-relative traceRefs (`agent/.traces/…`) — re-point them. */
156
- function rewriteTraceRefs(home) {
157
- const index = join(home, 'sessions.json');
158
- if (!existsSync(index)) return;
159
- try {
160
- const idx = JSON.parse(readFileSync(index, 'utf8'));
161
- for (const s of idx.sessions || []) {
162
- if (typeof s.traceRef === 'string' && s.traceRef.startsWith('agent/')) {
163
- s.traceRef = '.zuzuu/' + s.traceRef.slice('agent/'.length);
164
- }
165
- }
166
- writeFileSync(index, JSON.stringify(idx, null, 2) + '\n');
167
- } catch { /* fail-soft: a bad index never blocks the move */ }
168
- }
169
-
170
- /** Drop legacy `agent/` ignore lines, then append the canonical .zuzuu/ ones. */
171
- function rewriteGitignore(root) {
172
- const path = join(root, '.gitignore');
173
- if (existsSync(path)) {
174
- const kept = readFileSync(path, 'utf8')
175
- .split('\n')
176
- .filter((l) => !l.trim().startsWith('agent/'))
177
- .join('\n');
178
- writeFileSync(path, kept.endsWith('\n') || kept === '' ? kept : kept + '\n');
179
- }
180
- ensureGitignore(root); // appends .zuzuu/.traces/, .zuzuu/.live/, .zuzuu/knowledge/.index.db
181
- }
182
-
183
- /** Swap the old agent/ deny rules for the .zuzuu/ pair in any .claude settings file. */
184
- function scrubLegacyDenies(root) {
185
- for (const f of ['settings.json', 'settings.local.json']) {
186
- const path = join(root, '.claude', f);
187
- if (!existsSync(path)) continue;
188
- try {
189
- const s = JSON.parse(readFileSync(path, 'utf8'));
190
- const deny = s?.permissions?.deny;
191
- if (!Array.isArray(deny)) continue;
192
- const hadOurs = deny.some((r) => LEGACY_DENY_RULES.includes(r));
193
- if (!hadOurs) continue;
194
- s.permissions.deny = deny.filter((r) => !LEGACY_DENY_RULES.includes(r));
195
- for (const rule of NEW_DENY_RULES) if (!s.permissions.deny.includes(rule)) s.permissions.deny.push(rule);
196
- writeFileSync(path, JSON.stringify(s, null, 2) + '\n');
197
- } catch { /* fail-soft: never break settings we can't parse */ }
198
- }
199
- }
200
-
201
- /** Re-inject the current faculties block into any existing host instruction files. */
202
- function reinjectHostBlocks(root) {
203
- for (const f of ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']) {
204
- const p = join(root, f);
205
- if (existsSync(p)) {
206
- const text = readFileSync(p, 'utf8');
207
- if (!text.includes(`zuzuu:faculties:v${BLOCK_VERSION}`)) writeFileSync(p, injectBlock(text));
208
- }
209
- }
210
- }
211
-
212
- // ---------------------------------------------------------------------------
213
- // items migration — the Faculty Standard (W24)
214
- // ---------------------------------------------------------------------------
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';
215
13
 
216
14
  /** Does this file's frontmatter already carry the envelope (a `faculty:` key)? */
217
15
  function isEnvelopeText(text) {
@@ -493,10 +291,31 @@ function migrateInstructions(agentDir, out) {
493
291
  }
494
292
  }
495
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
+
496
312
  /**
497
313
  * Are pre-standard shapes present? Cheap checks gate the init auto-run.
498
314
  */
499
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
+ }
500
319
  if (existsSync(join(agentDir, 'guardrails', 'rules.json'))) return true;
501
320
  if (existsSync(join(agentDir, 'instructions', 'project.md'))) return true;
502
321
  for (const base of [join(agentDir, 'actions'), join(agentDir, 'actions', 'inbox')]) {
@@ -526,47 +345,16 @@ export function needsItemsMigration(agentDir) {
526
345
  * files are skipped) and fail-soft per item (an unconvertible item is reported,
527
346
  * never fatal; its legacy source is left in place).
528
347
  * @returns {{knowledge:number, memory:number, guardrails:number, actions:number,
529
- * instructions:number, skipped:number, errors:Array<{file,error}>}}
348
+ * instructions:number, manifests:number, skipped:number, errors:Array<{file,error}>}}
530
349
  */
531
350
  export function migrateItems(agentDir) {
532
- const out = { knowledge: 0, memory: 0, guardrails: 0, actions: 0, instructions: 0, skipped: 0, errors: [] };
351
+ const out = { knowledge: 0, memory: 0, guardrails: 0, actions: 0, instructions: 0, manifests: 0, skipped: 0, errors: [] };
533
352
  migrateKnowledgeItems(agentDir, out);
534
353
  migrateMemoryEntries(agentDir, out);
535
354
  migrateGuardrails(agentDir, out);
536
355
  migrateActions(agentDir, out);
537
356
  migrateInstructions(agentDir, out);
357
+ seedFacultyManifests(agentDir, out);
538
358
  return out;
539
359
  }
540
360
 
541
- // ---------------------------------------------------------------------------
542
- // CLI surface
543
- // ---------------------------------------------------------------------------
544
-
545
- export function migrate(args = {}) {
546
- if (args.items) {
547
- const agentDir = paths().dir;
548
- const r = migrateItems(agentDir);
549
- const total = r.knowledge + r.memory + r.guardrails + r.actions + r.instructions;
550
- console.log(`migrate --items: ${total} item(s) → the Faculty Standard envelope — knowledge ${r.knowledge} · memory ${r.memory} · guardrails ${r.guardrails} · actions ${r.actions} · instructions ${r.instructions} (${r.skipped} already standard)`);
551
- for (const e of r.errors) console.log(` ✗ ${e.file}: ${e.error}`);
552
- if (!total && !r.errors.length) console.log(' nothing to migrate (the home already speaks the envelope)');
553
- return;
554
- }
555
- if (args.home) {
556
- const root = repoRoot(process.cwd());
557
- const { migrated } = migrateHome(root);
558
- if (!migrated) { console.log('migrate --home: nothing to do (already .zuzuu/, or no zuzuu home at agent/)'); return; }
559
- try { reinjectHostBlocks(root); } catch { /* fail-open */ }
560
- console.log(`migrate --home: agent/ → .zuzuu/ (hidden, like .git; block v${BLOCK_VERSION}, gitignore + deny rules rewritten)`);
561
- console.log(' transparency lives in porcelain now: zuzuu status · explain · digest');
562
- return;
563
- }
564
- const agentDir = paths().dir;
565
- const { scanned, migrated, skipped } = migrateProposals(agentDir);
566
- console.log(`migrate: scanned ${scanned} proposal(s) — migrated ${migrated}, skipped ${skipped}`);
567
- if (migrated > 0) {
568
- console.log(' legacy candidate/er keys rewritten to payload/analysis.er + faculty:knowledge');
569
- } else {
570
- console.log(' nothing to migrate (all records already in new shape)');
571
- }
572
- }
@@ -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
+