@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.
- package/bin/zuzuu.mjs +20 -4
- package/package.json +1 -1
- package/web-app/dist/auth.js +91 -0
- package/web-app/dist/server.js +16 -79
- package/web-app/dist/zuzuu-cli.js +124 -0
- package/web-app/dist/{zuzuu-api.js → zuzuu-routes.js} +158 -133
- package/web-app/web-dist/assets/CommandPalette-DhBdR7X3.js +45 -0
- package/web-app/web-dist/assets/DiffTab-CqxwSjI2.js +1 -0
- package/web-app/web-dist/assets/EditorPane-94QPFR9R.js +41 -0
- package/web-app/web-dist/assets/MonacoFile-D76epTrG.js +1 -0
- package/web-app/web-dist/assets/angular-html-BVBpGdXr.js +1 -0
- package/web-app/web-dist/assets/{angular-ts-CD_OonCa.js → angular-ts-BfdufMKP.js} +1 -1
- package/web-app/web-dist/assets/{apl-uOGC3x4e.js → apl-DWBSSoBH.js} +1 -1
- package/web-app/web-dist/assets/{astro-B6ybQmWG.js → astro-3LtMP0Sq.js} +1 -1
- package/web-app/web-dist/assets/{blade-B1QGRlVx.js → blade-llJRbbtR.js} +1 -1
- package/web-app/web-dist/assets/c-Wt1voDr2.js +1 -0
- package/web-app/web-dist/assets/{cobol-BgqgtYWn.js → cobol-x_HIyl2P.js} +1 -1
- package/web-app/web-dist/assets/{coffee-0wIRKYlr.js → coffee-CThvmt4R.js} +1 -1
- package/web-app/web-dist/assets/cpp-NtAeskI3.js +1 -0
- package/web-app/web-dist/assets/{crystal-CyTK3qFN.js → crystal-DNu_sX0G.js} +1 -1
- package/web-app/web-dist/assets/css-DJp_X0uY.js +1 -0
- package/web-app/web-dist/assets/{cssMode-B9jnrWOz.js → cssMode-ByQBaInt.js} +1 -1
- package/web-app/web-dist/assets/dist-DQqjtuhV.js +153 -0
- package/web-app/web-dist/assets/{edge-CvML9pwC.js → edge-ozw5tpLl.js} +1 -1
- package/web-app/web-dist/assets/{editor.api2-BmGoRSl4.js → editor.api2-C7skgoRB.js} +1 -1
- package/web-app/web-dist/assets/{elixir-CrjqTiSc.js → elixir-VhA6FeZt.js} +1 -1
- package/web-app/web-dist/assets/{elm-C4JtJ0Au.js → elm-dREJmIFz.js} +1 -1
- package/web-app/web-dist/assets/{erb-Cmeb-29V.js → erb-CIg6G69l.js} +1 -1
- package/web-app/web-dist/assets/{freemarker2-B5LAi19B.js → freemarker2-CBBwP9JV.js} +1 -1
- package/web-app/web-dist/assets/{git-rebase-CXqdToiP.js → git-rebase-B44mJPta.js} +1 -1
- package/web-app/web-dist/assets/{glimmer-js-Kq-kdTyV.js → glimmer-js-vH_gHG0-.js} +1 -1
- package/web-app/web-dist/assets/{glimmer-ts-D0RKLJNf.js → glimmer-ts--abOzSAQ.js} +1 -1
- package/web-app/web-dist/assets/glsl-Dv5r7kPw.js +1 -0
- package/web-app/web-dist/assets/graphql-CB4jsw2E.js +1 -0
- package/web-app/web-dist/assets/{hack-trjVF3Po.js → hack-DvEYX148.js} +1 -1
- package/web-app/web-dist/assets/haml-zE6W3STP.js +1 -0
- package/web-app/web-dist/assets/{handlebars-B8_x7Zx7.js → handlebars-CzBR2SDs.js} +1 -1
- package/web-app/web-dist/assets/{handlebars-g7ZhGhI_.js → handlebars-tXdfxEd6.js} +1 -1
- package/web-app/web-dist/assets/html-C8UlPnhE.js +1 -0
- package/web-app/web-dist/assets/{html-CfvRMgoC.js → html-DgPn1QYH.js} +1 -1
- package/web-app/web-dist/assets/{html-derivative-BYX_F_XH.js → html-derivative-CY6NRz-J.js} +1 -1
- package/web-app/web-dist/assets/{htmlMode-Bi8vSvwb.js → htmlMode-BtdIDgA2.js} +1 -1
- package/web-app/web-dist/assets/{http-BIVDpHT-.js → http-Cyd7bS_S.js} +1 -1
- package/web-app/web-dist/assets/{hurl-CFsshMju.js → hurl-CWPsiEpf.js} +1 -1
- package/web-app/web-dist/assets/index-B27_WOhS.css +2 -0
- package/web-app/web-dist/assets/index-De6DWTZM.js +7 -0
- package/web-app/web-dist/assets/java-CGc3VwQr.js +1 -0
- package/web-app/web-dist/assets/{javascript-Bxx2wV4w.js → javascript-5m05n-Be.js} +1 -1
- package/web-app/web-dist/assets/javascript-CUt1pgmJ.js +1 -0
- package/web-app/web-dist/assets/{jinja-_ZS5zWwe.js → jinja-CD-Z-FLd.js} +1 -1
- package/web-app/web-dist/assets/{jison-D8mMEpcs.js → jison-imPNup1l.js} +1 -1
- package/web-app/web-dist/assets/json-Bg9ijW3F.js +1 -0
- package/web-app/web-dist/assets/{jsonMode-C6ELX5GM.js → jsonMode-BG32YnTY.js} +1 -1
- package/web-app/web-dist/assets/jsx-CY6oMTks.js +1 -0
- package/web-app/web-dist/assets/{julia-D4h2DZrs.js → julia-Dc3O-irA.js} +1 -1
- package/web-app/web-dist/assets/{just-bMqQi3xg.js → just-BhOq_Kbv.js} +1 -1
- package/web-app/web-dist/assets/{latex-DThYi3CX.js → latex-Cu4Y1d5w.js} +1 -1
- package/web-app/web-dist/assets/lib-KIOQTlcs.js +1 -0
- package/web-app/web-dist/assets/{liquid-CUjzzP4r.js → liquid-3ZnQzTbs.js} +1 -1
- package/web-app/web-dist/assets/{liquid-CesB-zzl.js → liquid-CvXMrjlQ.js} +1 -1
- package/web-app/web-dist/assets/{lspLanguageFeatures-gTnJsses.js → lspLanguageFeatures-6KXALSrl.js} +1 -1
- package/web-app/web-dist/assets/lua-BjLEUjKY.js +1 -0
- package/web-app/web-dist/assets/{marko-yoGoLK2m.js → marko-DvhNOisQ.js} +1 -1
- package/web-app/web-dist/assets/{mdc-BvtXU6eH.js → mdc-Bm9TpL1X.js} +1 -1
- package/web-app/web-dist/assets/{mdx-DrXGQbNB.js → mdx-DffTEkNE.js} +1 -1
- package/web-app/web-dist/assets/{monaco-setup-CsR6EfHe.js → monaco-setup-DM3A5_VI.js} +3 -3
- package/web-app/web-dist/assets/{nginx-DoUz032F.js → nginx-Bhc82uuv.js} +1 -1
- package/web-app/web-dist/assets/{nim-B0Pl8B4R.js → nim-DXTVBFnF.js} +1 -1
- package/web-app/web-dist/assets/{perl-D2tfAALb.js → perl-C7veXV9z.js} +1 -1
- package/web-app/web-dist/assets/{php-BImCcX5X.js → php-BRiuMnnr.js} +1 -1
- package/web-app/web-dist/assets/{pug-BcnpC8P_.js → pug-C5hz5LQ7.js} +1 -1
- package/web-app/web-dist/assets/{python-ypRCBnvu.js → python-DyLAD3Wt.js} +1 -1
- package/web-app/web-dist/assets/{qml-DFDAunHY.js → qml-BdUV3aTS.js} +1 -1
- package/web-app/web-dist/assets/r-8R7vtdQc.js +1 -0
- package/web-app/web-dist/assets/{razor-aqrhpwqZ.js → razor-C49xQTPQ.js} +1 -1
- package/web-app/web-dist/assets/{razor-1_376SZM.js → razor-DRL52XO2.js} +1 -1
- package/web-app/web-dist/assets/react-vendor-CCIEwYL0.js +9 -0
- package/web-app/web-dist/assets/regexp-Omp9DhTb.js +1 -0
- package/web-app/web-dist/assets/{rst-2vG6f11Y.js → rst-BHX71KW9.js} +1 -1
- package/web-app/web-dist/assets/{ruby-Dj6bCFXR.js → ruby-B--HzjGU.js} +1 -1
- package/web-app/web-dist/assets/{sas-BhVZ4qL2.js → sas-DrLaYOK_.js} +1 -1
- package/web-app/web-dist/assets/scss-DdSxiZKl.js +1 -0
- package/web-app/web-dist/assets/shellscript-DwcUjJBL.js +1 -0
- package/web-app/web-dist/assets/{shellsession-CyO2fnhB.js → shellsession-CPZkydE6.js} +1 -1
- package/web-app/web-dist/assets/{soy-DIkw6E88.js → soy-Br5FhD7c.js} +1 -1
- package/web-app/web-dist/assets/sql-DNssxck8.js +1 -0
- package/web-app/web-dist/assets/{stata-DvkM932O.js → stata-DXn1tqOr.js} +1 -1
- package/web-app/web-dist/assets/{surrealql-B4-Q8tqV.js → surrealql-IeLNQw0f.js} +1 -1
- package/web-app/web-dist/assets/{svelte-p6yBy-Ki.js → svelte-DOdLCIlh.js} +1 -1
- package/web-app/web-dist/assets/{templ-C7EkuiZr.js → templ-CIwIngms.js} +1 -1
- package/web-app/web-dist/assets/{tex-DkmD8uFC.js → tex-D8QMumu5.js} +1 -1
- package/web-app/web-dist/assets/{ts-tags-U-hncHg4.js → ts-tags-BMVY4q-l.js} +1 -1
- package/web-app/web-dist/assets/{tsMode-a8OvovQd.js → tsMode-BndVBac5.js} +1 -1
- package/web-app/web-dist/assets/tsx-5Eka4NBX.js +1 -0
- package/web-app/web-dist/assets/{twig-CU0OP-IA.js → twig-C8o_5mgw.js} +1 -1
- package/web-app/web-dist/assets/{typescript-DnLjiKtn.js → typescript-B1w9vqKF.js} +1 -1
- package/web-app/web-dist/assets/typescript-DOu2WMV5.js +1 -0
- package/web-app/web-dist/assets/{vue-Db7nY3ba.js → vue-BU18DNDL.js} +1 -1
- package/web-app/web-dist/assets/{vue-html-BvAbiAw1.js → vue-html-BeluIYX0.js} +1 -1
- package/web-app/web-dist/assets/{vue-vine-BEaIQIlA.js → vue-vine-DGUAbOCX.js} +1 -1
- package/web-app/web-dist/assets/{xml-an4Nuuqq.js → xml-D8uAlVv5.js} +1 -1
- package/web-app/web-dist/assets/xml-DIqSwXR3.js +1 -0
- package/web-app/web-dist/assets/{xsl-D3NQgH22.js → xsl-Ct_-YIAy.js} +1 -1
- package/web-app/web-dist/assets/xterm-B1ffpRuj.js +36 -0
- package/web-app/web-dist/assets/xterm-addons-psDEiUMC.js +136 -0
- package/web-app/web-dist/assets/{yaml-Diiu6O9P.js → yaml-Bb7jXyQv.js} +1 -1
- package/web-app/web-dist/assets/yaml-DTtCYNlS.js +1 -0
- package/web-app/web-dist/index.html +6 -3
- package/zuzuu/actions/convert.mjs +10 -9
- package/zuzuu/actions/dispatch.mjs +12 -7
- package/zuzuu/actions/inbox.mjs +5 -5
- package/zuzuu/actions/manifest.mjs +48 -30
- package/zuzuu/actions/schema.mjs +9 -3
- package/zuzuu/actions/trail.mjs +1 -1
- package/zuzuu/commands/act-author.mjs +23 -13
- package/zuzuu/commands/act.mjs +4 -6
- package/zuzuu/commands/capture.mjs +2 -2
- package/zuzuu/commands/code.mjs +2 -2
- package/zuzuu/commands/digest.mjs +2 -2
- package/zuzuu/commands/distill.mjs +15 -16
- package/zuzuu/commands/doctor.mjs +41 -19
- package/zuzuu/commands/enable.mjs +1 -1
- package/zuzuu/commands/eval.mjs +3 -36
- package/zuzuu/commands/explain.mjs +4 -4
- package/zuzuu/commands/faculty.mjs +158 -0
- package/zuzuu/commands/generation.mjs +5 -8
- package/zuzuu/commands/hook.mjs +14 -12
- package/zuzuu/commands/inbox.mjs +1 -6
- package/zuzuu/commands/init.mjs +18 -4
- package/zuzuu/commands/knowledge.mjs +1 -1
- package/zuzuu/commands/migrations/home.mjs +96 -0
- package/zuzuu/commands/migrations/index.mjs +48 -0
- package/zuzuu/commands/migrations/items.mjs +360 -0
- package/zuzuu/commands/migrations/proposals.mjs +100 -0
- package/zuzuu/commands/proposals.mjs +131 -0
- package/zuzuu/commands/review.mjs +13 -227
- package/zuzuu/commands/session.mjs +8 -2
- package/zuzuu/commands/sessions.mjs +159 -0
- package/zuzuu/commands/status.mjs +3 -3
- package/zuzuu/commands/trace.mjs +1 -1
- package/zuzuu/{capture-core.mjs → core/capture-core.mjs} +3 -3
- package/zuzuu/{store.mjs → core/store.mjs} +1 -1
- package/zuzuu/digest/compose.mjs +96 -0
- package/zuzuu/eval/score.mjs +14 -1
- package/zuzuu/faculties/actions/index.mjs +283 -0
- package/zuzuu/faculties/guardrails/index.mjs +320 -0
- package/zuzuu/faculties/instructions/index.mjs +288 -0
- package/zuzuu/faculties/knowledge/index.mjs +185 -0
- package/zuzuu/faculties/memory/index.mjs +124 -0
- package/zuzuu/faculty/envelope.mjs +290 -0
- package/zuzuu/faculty/generation/read.mjs +206 -0
- package/zuzuu/faculty/generation/write.mjs +207 -0
- package/zuzuu/faculty/items.mjs +81 -0
- package/zuzuu/faculty/module.mjs +74 -0
- package/zuzuu/faculty/pending.mjs +63 -0
- package/zuzuu/faculty/registry.mjs +204 -18
- package/zuzuu/faculty/render.mjs +59 -0
- package/zuzuu/faculty/trail.mjs +1 -1
- package/zuzuu/guardrails/engine.mjs +137 -0
- package/zuzuu/{scaffold.mjs → home/scaffold.mjs} +110 -39
- package/zuzuu/knowledge/items.mjs +56 -91
- package/zuzuu/live/install.mjs +1 -1
- package/zuzuu/live/live-store.mjs +2 -2
- package/zuzuu/live/reconcile.mjs +2 -2
- package/zuzuu/sessions/git.mjs +47 -0
- package/zuzuu/{session-git.mjs → sessions/session-git.mjs} +5 -43
- package/web-app/web-dist/assets/DiffTab-BuWonUNJ.js +0 -1
- package/web-app/web-dist/assets/MonacoFile-CL3DhFKG.js +0 -1
- package/web-app/web-dist/assets/angular-html-CmT26mqM.js +0 -1
- package/web-app/web-dist/assets/c-BvoqrSVH.js +0 -1
- package/web-app/web-dist/assets/cpp-BXsk94m0.js +0 -1
- package/web-app/web-dist/assets/css-Z8oOGxII.js +0 -1
- package/web-app/web-dist/assets/dist-ChcDQ_7s.js +0 -153
- package/web-app/web-dist/assets/glsl-KwyfU2aa.js +0 -1
- package/web-app/web-dist/assets/graphql-DSeOUAa2.js +0 -1
- package/web-app/web-dist/assets/haml-azVoxQRV.js +0 -1
- package/web-app/web-dist/assets/html-D_7P5S4m.js +0 -1
- package/web-app/web-dist/assets/index--5yy8RbA.js +0 -267
- package/web-app/web-dist/assets/index-BVG4hgk7.css +0 -2
- package/web-app/web-dist/assets/java-D4RbCvBe.js +0 -1
- package/web-app/web-dist/assets/javascript-Cb010CKM.js +0 -1
- package/web-app/web-dist/assets/json-DWgqV4D1.js +0 -1
- package/web-app/web-dist/assets/jsx-CZjSJa1f.js +0 -1
- package/web-app/web-dist/assets/lua-TGj_6NzO.js +0 -1
- package/web-app/web-dist/assets/r-fCpuAR7u.js +0 -1
- package/web-app/web-dist/assets/regexp-B4yxx-Ty.js +0 -1
- package/web-app/web-dist/assets/scss-QdjMO_xV.js +0 -1
- package/web-app/web-dist/assets/shellscript-BnlgeVVx.js +0 -1
- package/web-app/web-dist/assets/sql-DGnQv6iD.js +0 -1
- package/web-app/web-dist/assets/tsx-MJ0-9sYG.js +0 -1
- package/web-app/web-dist/assets/typescript-C17ZkDe8.js +0 -1
- package/web-app/web-dist/assets/xml-CA9lHFQV.js +0 -1
- package/web-app/web-dist/assets/yaml-CwRYMJka.js +0 -1
- package/zuzuu/actions/adapter.mjs +0 -130
- package/zuzuu/commands/migrate.mjs +0 -225
- package/zuzuu/digest.mjs +0 -149
- package/zuzuu/faculty/generation.mjs +0 -392
- package/zuzuu/guardrails/adapter.mjs +0 -134
- package/zuzuu/guardrails.mjs +0 -89
- package/zuzuu/instructions/adapter.mjs +0 -93
- package/zuzuu/knowledge/adapter.mjs +0 -99
- package/zuzuu/memory/adapter.mjs +0 -121
- package/zuzuu/miners/actions.mjs +0 -118
- package/zuzuu/miners/guardrails.mjs +0 -179
- package/zuzuu/miners/instructions.mjs +0 -157
- package/zuzuu/miners/knowledge.mjs +0 -25
- package/zuzuu/miners/memory.mjs +0 -27
- package/zuzuu/miners/registry.mjs +0 -31
- /package/web-app/web-dist/assets/{chunk-QTnfLwEv.js → rolldown-runtime-QTnfLwEv.js} +0 -0
- /package/zuzuu/{session.mjs → core/session.mjs} +0 -0
- /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
|
+
}
|