@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
@@ -1,398 +0,0 @@
1
- // zuzuu/faculty/generation.mjs — the generation core (WS3-T1).
2
- //
3
- // A *generation* is an immutable, content-addressed snapshot of the agent's
4
- // pinned faculties (the lockfile). Minting freezes the current faculty state;
5
- // rollback restores any past generation by *content* (we copy each pinned item's
6
- // bytes into generations/snapshots/<id>/ at mint time, so a rollback works even
7
- // for items that were never committed). Identity: Agent → Generation → Run —
8
- // rollback = flip the active pointer + restore content; never `git revert`.
9
- //
10
- // Layout under .zuzuu/:
11
- // generations/active {active: "gen_NNN"} — the live pointer
12
- // generations/<id>.json the lockfile (content-addressed manifest)
13
- // generations/snapshots/<id>/<faculty>/... pinned item bytes (rollback source)
14
-
15
- import { createHash } from 'node:crypto';
16
- import { join, dirname } from 'node:path';
17
- import {
18
- existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, renameSync,
19
- } from 'node:fs';
20
- import { reindex } from '../knowledge/index.mjs';
21
-
22
- /** Hex sha256 of a string or Buffer. */
23
- export function sha256(buf) {
24
- return createHash('sha256').update(buf).digest('hex');
25
- }
26
-
27
- const read = (p) => readFileSync(p, 'utf8');
28
- const readJson = (p) => JSON.parse(read(p));
29
- const writeJson = (p, obj) => {
30
- mkdirSync(dirname(p), { recursive: true });
31
- writeFileSync(p, JSON.stringify(obj, null, 2) + '\n');
32
- };
33
-
34
- // --- paths ------------------------------------------------------------------
35
-
36
- const generationsDir = (agentDir) => join(agentDir, 'generations');
37
- const snapshotsDir = (agentDir) => join(generationsDir(agentDir), 'snapshots');
38
- const activePath = (agentDir) => join(generationsDir(agentDir), 'active');
39
- const lockfilePath = (agentDir, id) => join(generationsDir(agentDir), `${id}.json`);
40
- const agentJsonPath = (agentDir) => join(agentDir, 'agent.json');
41
-
42
- // --- faculty file enumeration (the pinned set) ------------------------------
43
- // Each entry: { id, faculty, src (absolute live path), rel (path under the
44
- // faculty snapshot dir), hash }. `rel` is what we mirror into snapshots/<id>/.
45
-
46
- function sortDirents(dir) {
47
- if (!existsSync(dir)) return [];
48
- return readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
49
- }
50
-
51
- function knowledgeFiles(agentDir) {
52
- const dir = join(agentDir, 'knowledge', 'items');
53
- return sortDirents(dir)
54
- .filter((e) => e.isFile() && e.name.endsWith('.md'))
55
- .map((e) => {
56
- const src = join(dir, e.name);
57
- return { id: e.name.replace(/\.md$/, ''), faculty: 'knowledge', src, rel: e.name, hash: sha256(readFileSync(src)) };
58
- });
59
- }
60
-
61
- function actionFiles(agentDir) {
62
- const dir = join(agentDir, 'actions');
63
- return sortDirents(dir)
64
- .filter((e) => e.isDirectory() && e.name !== 'inbox' && e.name !== 'proposals' && e.name !== '_rolledback')
65
- .map((e) => {
66
- const adir = join(dir, e.name);
67
- // Hash the dir's defining files concatenated: the ACTION.md envelope
68
- // (W24) + sibling scripts (*.mjs — run.mjs and any payload.exec module).
69
- const parts = sortDirents(adir)
70
- .filter((f) => f.isFile() && (f.name === 'ACTION.md' || f.name.endsWith('.mjs')))
71
- .map((f) => join(adir, f.name));
72
- const concat = Buffer.concat(parts.map((p) => readFileSync(p)));
73
- return {
74
- id: e.name, faculty: 'actions', files: parts.map((p) => p.slice(adir.length + 1)),
75
- adir, hash: parts.length ? sha256(concat) : null,
76
- };
77
- });
78
- }
79
-
80
- /** Flat envelope-item faculties share one enumerator. */
81
- function mdItemFiles(agentDir, faculty, ...segments) {
82
- const dir = join(agentDir, ...segments);
83
- return sortDirents(dir)
84
- .filter((e) => e.isFile() && e.name.endsWith('.md'))
85
- .map((e) => {
86
- const src = join(dir, e.name);
87
- return { id: e.name.replace(/\.md$/, ''), faculty, src, rel: e.name, hash: sha256(readFileSync(src)) };
88
- });
89
- }
90
-
91
- const guardrailFiles = (agentDir) => mdItemFiles(agentDir, 'guardrails', 'guardrails', 'items');
92
- const instructionFiles = (agentDir) => mdItemFiles(agentDir, 'instructions', 'instructions', 'items');
93
-
94
- function memoryFiles(agentDir) {
95
- const dir = join(agentDir, 'memory', 'entries');
96
- return sortDirents(dir)
97
- .filter((e) => e.isFile() && e.name.endsWith('.md'))
98
- .map((e) => {
99
- const src = join(dir, e.name);
100
- return { id: e.name.replace(/\.md$/, ''), faculty: 'memory', src, rel: e.name, hash: sha256(readFileSync(src)) };
101
- });
102
- }
103
-
104
- function registryHash(agentDir) {
105
- const dir = join(agentDir, 'knowledge', 'registry');
106
- const files = sortDirents(dir).filter((e) => e.isFile() && e.name.endsWith('.json'));
107
- if (!files.length) return null;
108
- return sha256(Buffer.concat(files.map((e) => readFileSync(join(dir, e.name)))));
109
- }
110
-
111
- /**
112
- * Snapshot the current faculty state → the `faculties` manifest object.
113
- * Tolerates missing files (empty arrays / null hashes).
114
- */
115
- export function snapshotFaculties(agentDir) {
116
- return {
117
- knowledge: {
118
- items: knowledgeFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
119
- registryHash: registryHash(agentDir),
120
- },
121
- actions: {
122
- items: actionFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
123
- },
124
- guardrails: {
125
- items: guardrailFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
126
- },
127
- instructions: {
128
- items: instructionFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
129
- },
130
- memory: {
131
- items: memoryFiles(agentDir).map(({ id, hash }) => ({ id, hash })),
132
- },
133
- };
134
- }
135
-
136
- // --- agent identity ---------------------------------------------------------
137
-
138
- /** Stable agent id derived from the repo root: agt_<first12 of sha256(root)>. */
139
- export function agentId(agentDir) {
140
- // agentDir is the .zuzuu/ dir; the repo root is its parent.
141
- const root = dirname(agentDir);
142
- return 'agt_' + sha256(root).slice(0, 12);
143
- }
144
-
145
- /** Add/repair the agent block in agent.json (bump to v2), preserving other fields. */
146
- export function ensureAgent(agentDir) {
147
- const path = agentJsonPath(agentDir);
148
- const m = existsSync(path) ? readJson(path) : {};
149
- const id = agentId(agentDir);
150
- if (!m.agent || !m.agent.id) {
151
- m.agent = { id, createdAt: new Date().toISOString() };
152
- }
153
- m.version = 2;
154
- writeJson(path, m);
155
- return m.agent;
156
- }
157
-
158
- // --- generation read/list ---------------------------------------------------
159
-
160
- /** The active generation id, or null. */
161
- export function activeGeneration(agentDir) {
162
- const p = activePath(agentDir);
163
- if (!existsSync(p)) return null;
164
- try { return readJson(p).active ?? null; } catch { return null; }
165
- }
166
-
167
- /** All generation ids in ascending order. */
168
- export function listGenerations(agentDir) {
169
- const dir = generationsDir(agentDir);
170
- if (!existsSync(dir)) return [];
171
- return readdirSync(dir)
172
- .filter((f) => /^gen_\d+\.json$/.test(f))
173
- .map((f) => f.replace(/\.json$/, ''))
174
- .sort();
175
- }
176
-
177
- /** Read one lockfile, or null. */
178
- export function readGeneration(agentDir, id) {
179
- const p = lockfilePath(agentDir, id);
180
- return existsSync(p) ? readJson(p) : null;
181
- }
182
-
183
- /** Diff two item-manifest arrays → {added, changed, removed} (id lists). */
184
- function diffItems(parentItems = [], childItems = []) {
185
- const p = new Map(parentItems.map((i) => [i.id, i.hash]));
186
- const c = new Map(childItems.map((i) => [i.id, i.hash]));
187
- const added = [], changed = [], removed = [];
188
- for (const [id, hash] of c) {
189
- if (!p.has(id)) added.push(id);
190
- else if (p.get(id) !== hash) changed.push(id);
191
- }
192
- for (const id of p.keys()) if (!c.has(id)) removed.push(id);
193
- return { added: added.sort(), changed: changed.sort(), removed: removed.sort() };
194
- }
195
-
196
- /**
197
- * Per-faculty diff of generation `id` against its forkedFrom parent (pure).
198
- * ALL five faculties are item lists under the Faculty Standard (W24) —
199
- * added/changed/removed id lists per faculty; knowledge additionally reports
200
- * registryChanged. When there is no parent (forkedFrom null), everything
201
- * present counts as added. Returns null for an unknown id.
202
- */
203
- export function diffGenerations(agentDir, id) {
204
- const child = readGeneration(agentDir, id);
205
- if (!child) return null;
206
- const parent = child.forkedFrom ? readGeneration(agentDir, child.forkedFrom) : null;
207
- const cf = child.faculties || {};
208
- const pf = parent?.faculties || {};
209
- const faculties = {};
210
- for (const f of ['knowledge', 'actions', 'memory', 'guardrails', 'instructions']) {
211
- faculties[f] = diffItems(pf[f]?.items, cf[f]?.items);
212
- // knowledge also has a registry hash
213
- if (f === 'knowledge') {
214
- faculties[f].registryChanged = (cf.knowledge?.registryHash ?? null) !== (pf.knowledge?.registryHash ?? null);
215
- }
216
- }
217
- return {
218
- id,
219
- forkedFrom: child.forkedFrom ?? null,
220
- mintedFrom: Array.isArray(child.mintedFrom) ? child.mintedFrom : [],
221
- mintedAt: child.mintedAt ?? null,
222
- faculties,
223
- };
224
- }
225
-
226
- function nextGenId(agentDir) {
227
- const ids = listGenerations(agentDir);
228
- const max = ids.reduce((m, id) => Math.max(m, parseInt(id.slice(4), 10) || 0), 0);
229
- return 'gen_' + String(max + 1).padStart(3, '0');
230
- }
231
-
232
- // --- mint -------------------------------------------------------------------
233
-
234
- function copySnapshot(agentDir, id) {
235
- const base = join(snapshotsDir(agentDir), id);
236
- for (const it of knowledgeFiles(agentDir)) {
237
- const dest = join(base, 'knowledge', it.rel);
238
- mkdirSync(dirname(dest), { recursive: true });
239
- writeFileSync(dest, readFileSync(it.src));
240
- }
241
- for (const it of memoryFiles(agentDir)) {
242
- const dest = join(base, 'memory', it.rel);
243
- mkdirSync(dirname(dest), { recursive: true });
244
- writeFileSync(dest, readFileSync(it.src));
245
- }
246
- for (const a of actionFiles(agentDir)) {
247
- for (const rel of a.files) {
248
- const dest = join(base, 'actions', a.id, rel);
249
- mkdirSync(dirname(dest), { recursive: true });
250
- writeFileSync(dest, readFileSync(join(a.adir, rel)));
251
- }
252
- }
253
- for (const it of guardrailFiles(agentDir)) {
254
- const dest = join(base, 'guardrails', it.rel);
255
- mkdirSync(dirname(dest), { recursive: true });
256
- writeFileSync(dest, readFileSync(it.src));
257
- }
258
- for (const it of instructionFiles(agentDir)) {
259
- const dest = join(base, 'instructions', it.rel);
260
- mkdirSync(dirname(dest), { recursive: true });
261
- writeFileSync(dest, readFileSync(it.src));
262
- }
263
- }
264
-
265
- /**
266
- * Mint a new generation: freeze the current faculty state into a content-addressed
267
- * lockfile + a byte-for-byte snapshot, and make it active.
268
- */
269
- export function mintGeneration(agentDir, { forkedFrom = null, mintedFrom = [] } = {}) {
270
- const agent = ensureAgent(agentDir).id;
271
- const id = nextGenId(agentDir);
272
- const lockfile = {
273
- id,
274
- agent,
275
- mintedAt: new Date().toISOString(),
276
- forkedFrom,
277
- mintedFrom,
278
- faculties: snapshotFaculties(agentDir),
279
- };
280
- copySnapshot(agentDir, id);
281
- writeJson(lockfilePath(agentDir, id), lockfile);
282
- writeJson(activePath(agentDir), { active: id });
283
- return lockfile;
284
- }
285
-
286
- // --- rollback ---------------------------------------------------------------
287
-
288
- function archive(agentDir, faculty, src) {
289
- // Park (never delete) under <faculty>/_rolledback/<basename> — by basename so
290
- // a restore is a simple, flat audit trail of what the rollback displaced.
291
- const dest = join(agentDir, faculty, '_rolledback', src.slice(dirname(src).length + 1));
292
- mkdirSync(dirname(dest), { recursive: true });
293
- renameSync(src, dest);
294
- }
295
-
296
- /**
297
- * Restore a past generation by content: write each snapshotted item back to its
298
- * live faculty path; MOVE (never delete) active items absent from the target into
299
- * <faculty>/_rolledback/; reindex knowledge; flip the active pointer.
300
- */
301
- export function rollback(agentDir, id) {
302
- const target = readGeneration(agentDir, id);
303
- if (!target) throw new Error(`no generation '${id}'`);
304
- const base = join(snapshotsDir(agentDir), id);
305
- let restored = 0;
306
-
307
- // 1) restore snapshotted knowledge items
308
- const targetKnowledge = new Set((target.faculties.knowledge?.items ?? []).map((i) => i.id));
309
- for (const i of target.faculties.knowledge?.items ?? []) {
310
- const snap = join(base, 'knowledge', `${i.id}.md`);
311
- if (existsSync(snap)) {
312
- const dest = join(agentDir, 'knowledge', 'items', `${i.id}.md`);
313
- mkdirSync(dirname(dest), { recursive: true });
314
- writeFileSync(dest, readFileSync(snap));
315
- restored++;
316
- }
317
- }
318
- // archive live knowledge items not in the target
319
- const kdir = join(agentDir, 'knowledge', 'items');
320
- if (existsSync(kdir)) {
321
- for (const e of readdirSync(kdir, { withFileTypes: true })) {
322
- if (e.isFile() && e.name.endsWith('.md') && !targetKnowledge.has(e.name.replace(/\.md$/, ''))) {
323
- archive(agentDir, 'knowledge', join(kdir, e.name));
324
- }
325
- }
326
- }
327
-
328
- // 2) restore snapshotted memory items + archive extras
329
- const targetMemory = new Set((target.faculties.memory?.items ?? []).map((i) => i.id));
330
- for (const i of target.faculties.memory?.items ?? []) {
331
- const snap = join(base, 'memory', `${i.id}.md`);
332
- if (existsSync(snap)) {
333
- const dest = join(agentDir, 'memory', 'entries', `${i.id}.md`);
334
- mkdirSync(dirname(dest), { recursive: true });
335
- writeFileSync(dest, readFileSync(snap));
336
- restored++;
337
- }
338
- }
339
- const mdir = join(agentDir, 'memory', 'entries');
340
- if (existsSync(mdir)) {
341
- for (const e of readdirSync(mdir, { withFileTypes: true })) {
342
- if (e.isFile() && e.name.endsWith('.md') && !targetMemory.has(e.name.replace(/\.md$/, ''))) {
343
- archive(agentDir, 'memory', join(mdir, e.name));
344
- }
345
- }
346
- }
347
-
348
- // 3) restore snapshotted actions + archive extras
349
- const targetActions = new Set((target.faculties.actions?.items ?? []).map((i) => i.id));
350
- const asnap = join(base, 'actions');
351
- if (existsSync(asnap)) {
352
- for (const slugEnt of readdirSync(asnap, { withFileTypes: true })) {
353
- if (!slugEnt.isDirectory()) continue;
354
- const sdir = join(asnap, slugEnt.name);
355
- for (const f of readdirSync(sdir)) {
356
- const dest = join(agentDir, 'actions', slugEnt.name, f);
357
- mkdirSync(dirname(dest), { recursive: true });
358
- writeFileSync(dest, readFileSync(join(sdir, f)));
359
- }
360
- restored++;
361
- }
362
- }
363
- const adir = join(agentDir, 'actions');
364
- if (existsSync(adir)) {
365
- for (const e of readdirSync(adir, { withFileTypes: true })) {
366
- if (e.isDirectory() && e.name !== 'inbox' && e.name !== 'proposals' && e.name !== '_rolledback' && !targetActions.has(e.name)) {
367
- archive(agentDir, 'actions', join(adir, e.name));
368
- }
369
- }
370
- }
371
-
372
- // 4) restore guardrails + instructions items (same item-list contract)
373
- for (const [faculty, liveSeg] of [['guardrails', ['guardrails', 'items']], ['instructions', ['instructions', 'items']]]) {
374
- const targetIds = new Set((target.faculties[faculty]?.items ?? []).map((i) => i.id));
375
- for (const i of target.faculties[faculty]?.items ?? []) {
376
- const snap = join(base, faculty, `${i.id}.md`);
377
- if (existsSync(snap)) {
378
- const dest = join(agentDir, ...liveSeg, `${i.id}.md`);
379
- mkdirSync(dirname(dest), { recursive: true });
380
- writeFileSync(dest, readFileSync(snap));
381
- restored++;
382
- }
383
- }
384
- const liveDir = join(agentDir, ...liveSeg);
385
- if (existsSync(liveDir)) {
386
- for (const e of readdirSync(liveDir, { withFileTypes: true })) {
387
- if (e.isFile() && e.name.endsWith('.md') && !targetIds.has(e.name.replace(/\.md$/, ''))) {
388
- archive(agentDir, faculty, join(liveDir, e.name));
389
- }
390
- }
391
- }
392
- }
393
-
394
- // 5) regenerate the derived knowledge index + flip the pointer
395
- try { reindex(agentDir); } catch { /* derived index; tolerate absence of node:sqlite features */ }
396
- writeJson(activePath(agentDir), { active: id });
397
- return { ok: true, restored };
398
- }
@@ -1,103 +0,0 @@
1
- // zuzuu/guardrails/adapter.mjs
2
- // The Guardrails faculty adapter. Wraps the rules engine behind the
3
- // faculty-spine adapter contract — { name, ingest, validate, apply, render } —
4
- // so `zuzuu review` can surface and approve/reject rule proposals the same way it
5
- // does Knowledge proposals.
6
- //
7
- // A guardrails proposal payload is a single rule record:
8
- // { id, action: deny|ask|allow, tool, pattern, reason, body? }
9
- //
10
- // apply: writes the rule as a Faculty Standard envelope item at
11
- // .zuzuu/guardrails/items/<id>.md (upsert — same id replaces the file).
12
- //
13
- // Registers itself on import.
14
-
15
- import * as registry from '../faculty/registry.mjs';
16
- import { writeFacultyItem } from '../faculty/items.mjs';
17
- import { deriveTitle } from '../faculty/envelope.mjs';
18
-
19
- const name = 'guardrails';
20
- const VALID_ACTIONS = new Set(['deny', 'ask', 'allow']);
21
-
22
- // ---------------------------------------------------------------------------
23
- // adapter contract
24
- // ---------------------------------------------------------------------------
25
-
26
- /**
27
- * Ingest a raw rule object. Pass-through: rule fields are the payload.
28
- * @param {string} agentDir
29
- * @param {object} raw — expected shape: { id, action, tool, pattern, reason }
30
- * or { payload: { ... } } from the spine
31
- */
32
- function ingest(_agentDir, raw) {
33
- const payload = raw?.payload ?? raw ?? {};
34
- return { payload, analysis: {}, dedupeKey: payload.id };
35
- }
36
-
37
- /**
38
- * Validate a rule payload.
39
- * @returns {{ok:boolean, errors:string[], warnings:string[]}}
40
- */
41
- function validate(_agentDir, payload) {
42
- const errors = [];
43
- if (!payload?.id || typeof payload.id !== 'string' || !payload.id.trim()) {
44
- errors.push('rule id is required (non-empty string slug)');
45
- }
46
- if (!VALID_ACTIONS.has(payload?.action)) {
47
- errors.push(`action must be one of deny|ask|allow (got '${payload?.action}')`);
48
- }
49
- if (!payload?.tool || typeof payload.tool !== 'string') {
50
- errors.push('tool is required (exact tool name or \'*\')');
51
- }
52
- if (typeof payload?.pattern !== 'string' || !payload.pattern) {
53
- errors.push('pattern is required (a non-empty regex string)');
54
- } else {
55
- try {
56
- new RegExp(payload.pattern); // eslint-disable-line no-new
57
- } catch (e) {
58
- errors.push(`pattern does not compile as a RegExp: ${e.message}`);
59
- }
60
- }
61
- if (!payload?.reason || !String(payload.reason).trim()) {
62
- errors.push('reason is required (non-empty)');
63
- }
64
- return { ok: errors.length === 0, errors, warnings: [] };
65
- }
66
-
67
- /**
68
- * Apply an approved rule proposal: write the envelope item (upsert by id).
69
- * @returns {{ok:boolean, action:string, itemIds:string[]}}
70
- */
71
- function apply(agentDir, proposal) {
72
- const rule = proposal?.payload ?? {};
73
- const id = rule.id;
74
- writeFacultyItem(agentDir, {
75
- id,
76
- faculty: name,
77
- kind: 'rule',
78
- title: deriveTitle(rule.reason, id),
79
- status: 'active',
80
- created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
81
- provenance: Array.isArray(proposal?.provenance) ? proposal.provenance : [],
82
- payload: { action: rule.action, tool: rule.tool || '*', pattern: rule.pattern, reason: rule.reason },
83
- body: rule.body ?? '',
84
- });
85
- return { ok: true, action: `added rule ${id}`, itemIds: [id] };
86
- }
87
-
88
- /**
89
- * Render a rule proposal for the human gate.
90
- * @returns {{line:string, card:string}}
91
- */
92
- function render(proposal) {
93
- const r = proposal?.payload ?? {};
94
- const summary = `${r.action ?? '?'} ${r.tool ?? '*'} /${r.pattern ?? ''}/ — ${r.reason ?? ''}`;
95
- return {
96
- line: `${r.id ?? ''} [rule] ${summary}`,
97
- card: summary,
98
- };
99
- }
100
-
101
- export const adapter = { name, ingest, validate, apply, render };
102
-
103
- registry.register(adapter);
@@ -1,93 +0,0 @@
1
- // zuzuu/instructions/adapter.mjs
2
- // The Instructions faculty adapter. 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
- // { id?, text } — a line or paragraph of steering
8
- //
9
- // apply: writes the amendment as a Faculty Standard envelope item under
10
- // .zuzuu/instructions/items/<id>.md (kind: amendment; body = the text).
11
- // The pinned steering itself lives at items/steering.md; future
12
- // amendments are MORE items, never edits to steering. Idempotent: a
13
- // text already present in any instructions item is not duplicated.
14
- //
15
- // Registers itself on import.
16
-
17
- import * as registry from '../faculty/registry.mjs';
18
- import { listFacultyItems, writeFacultyItem } from '../faculty/items.mjs';
19
- import { deriveTitle } from '../faculty/envelope.mjs';
20
- import { slugify } from '../knowledge/items.mjs';
21
-
22
- const name = 'instructions';
23
-
24
- // ---------------------------------------------------------------------------
25
- // adapter contract
26
- // ---------------------------------------------------------------------------
27
-
28
- /**
29
- * Ingest a raw amendment object. Pass-through: the payload IS the amendment.
30
- */
31
- function ingest(_agentDir, raw) {
32
- const payload = raw?.payload ?? raw ?? {};
33
- return { payload, analysis: {} };
34
- }
35
-
36
- /**
37
- * Validate an amendment payload.
38
- * @returns {{ok:boolean, errors:string[], warnings:string[]}}
39
- */
40
- function validate(_agentDir, payload) {
41
- const errors = [];
42
- if (!payload?.text || !String(payload.text).trim()) {
43
- errors.push('text is required (non-empty steering amendment)');
44
- }
45
- return { ok: errors.length === 0, errors, warnings: [] };
46
- }
47
-
48
- /**
49
- * Apply an approved amendment: write an amendment item (idempotent on
50
- * identical text — won't duplicate steering already present in any item).
51
- * @returns {{ok:boolean, action:string, itemIds:string[]}}
52
- */
53
- function apply(agentDir, proposal) {
54
- const text = String(proposal?.payload?.text ?? '').trim();
55
-
56
- // Idempotence: skip if the exact text already lives in an instructions item
57
- const { items } = listFacultyItems(agentDir, 'instructions');
58
- if (items.some((i) => String(i.body ?? '').includes(text))) {
59
- return { ok: true, action: 'amended instructions (already present)', itemIds: [] };
60
- }
61
-
62
- const id = proposal?.payload?.id || slugify(text, 50);
63
- writeFacultyItem(agentDir, {
64
- id,
65
- faculty: name,
66
- kind: 'amendment',
67
- title: deriveTitle(text, id),
68
- status: 'active',
69
- created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
70
- provenance: Array.isArray(proposal?.provenance) ? proposal.provenance : [],
71
- payload: { scope: 'project' },
72
- body: text,
73
- });
74
-
75
- return { ok: true, action: 'amended instructions', itemIds: [id] };
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);