@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.
- package/bin/zuzuu.mjs +12 -3
- 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} +46 -116
- 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-Dx3ub8Pk.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-DM6oHc7c.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-DflaUwqW.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-wbBeb0oN.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-DRwkDcoK.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/trail.mjs +1 -1
- package/zuzuu/commands/act.mjs +1 -1
- 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 +39 -4
- package/zuzuu/commands/enable.mjs +1 -1
- package/zuzuu/commands/eval.mjs +3 -36
- package/zuzuu/commands/faculty.mjs +102 -19
- package/zuzuu/commands/generation.mjs +3 -4
- package/zuzuu/commands/hook.mjs +7 -7
- package/zuzuu/commands/inbox.mjs +1 -6
- package/zuzuu/commands/init.mjs +5 -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/{migrate.mjs → migrations/items.mjs} +34 -246
- 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/{memory/adapter.mjs → faculties/memory/index.mjs} +37 -9
- package/zuzuu/faculty/generation/read.mjs +206 -0
- package/zuzuu/faculty/generation/write.mjs +207 -0
- package/zuzuu/faculty/items.mjs +11 -5
- 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.mjs → guardrails/engine.mjs} +1 -1
- package/zuzuu/{scaffold.mjs → home/scaffold.mjs} +12 -2
- 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-BpGp1akx.js +0 -1
- package/web-app/web-dist/assets/MonacoFile-CqbVacUZ.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-C6R6xoyX.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-DHpC851f.js +0 -268
- package/web-app/web-dist/assets/index-O-t1gyMG.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 -122
- package/zuzuu/digest.mjs +0 -154
- package/zuzuu/faculty/generation.mjs +0 -398
- package/zuzuu/guardrails/adapter.mjs +0 -103
- package/zuzuu/instructions/adapter.mjs +0 -93
- package/zuzuu/knowledge/adapter.mjs +0 -99
- package/zuzuu/miners/actions.mjs +0 -112
- package/zuzuu/miners/guardrails.mjs +0 -176
- 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
package/zuzuu/faculty/items.mjs
CHANGED
|
@@ -22,10 +22,15 @@ const ITEM_DIRS = {
|
|
|
22
22
|
guardrails: ['guardrails', 'items'],
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
/** The flat items dir for a faculty, or null for dir-shaped faculties (actions).
|
|
26
|
-
|
|
25
|
+
/** The flat items dir for a faculty, or null for dir-shaped faculties (actions).
|
|
26
|
+
* Unknown (declarative) faculties default to `<faculty>/<itemsDir||'items'>`
|
|
27
|
+
* under the home — manifest-only faculties get items listing for free. */
|
|
28
|
+
export function itemsDirFor(agentDir, faculty, itemsDir) {
|
|
29
|
+
if (faculty === 'actions') return null;
|
|
27
30
|
const rel = ITEM_DIRS[faculty];
|
|
28
|
-
|
|
31
|
+
if (rel) return join(agentDir, ...rel);
|
|
32
|
+
const sub = itemsDir && itemsDir !== '.' ? itemsDir : 'items';
|
|
33
|
+
return join(agentDir, faculty, sub);
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/** Canonical envelope file path for one item. */
|
|
@@ -36,9 +41,10 @@ export function itemPathFor(agentDir, faculty, id) {
|
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* All envelope items of a faculty. Parse errors collected, never thrown.
|
|
44
|
+
* @param {{itemsDir?: string}} [opts] declarative faculties pass manifest.itemsDir
|
|
39
45
|
* @returns {{items: object[], errors: Array<{file: string, error: string}>}}
|
|
40
46
|
*/
|
|
41
|
-
export function listFacultyItems(agentDir, faculty) {
|
|
47
|
+
export function listFacultyItems(agentDir, faculty, opts = {}) {
|
|
42
48
|
const items = [];
|
|
43
49
|
const errors = [];
|
|
44
50
|
if (faculty === 'actions') {
|
|
@@ -56,7 +62,7 @@ export function listFacultyItems(agentDir, faculty) {
|
|
|
56
62
|
}
|
|
57
63
|
return { items, errors };
|
|
58
64
|
}
|
|
59
|
-
const dir = itemsDirFor(agentDir, faculty);
|
|
65
|
+
const dir = itemsDirFor(agentDir, faculty, opts.itemsDir);
|
|
60
66
|
if (!dir || !existsSync(dir)) return { items, errors };
|
|
61
67
|
for (const f of readdirSync(dir).filter((f) => f.endsWith('.md')).sort()) {
|
|
62
68
|
const { ok, item, errors: errs } = parseEnvelope(readFileSync(join(dir, f), 'utf8'));
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// zuzuu/faculty/module.mjs — the Faculty Module contract (2026-06-13 spec).
|
|
2
|
+
//
|
|
3
|
+
// A faculty = a MANIFEST (<faculty>/faculty.json in the home) + an ITEMS
|
|
4
|
+
// collection (Faculty Standard envelopes under manifest.itemsDir) + a set of
|
|
5
|
+
// named HOOK exports (built-ins: zuzuu/faculties/<id>/index.mjs):
|
|
6
|
+
//
|
|
7
|
+
// manifest — the machine contract (this file's shape)
|
|
8
|
+
// miner = { faculty, aggregate, propose } — REQUIRED for code modules
|
|
9
|
+
// digestSection(agentDir, ctx) — optional; default = "N item(s)" line
|
|
10
|
+
// evalSignals(proposal) — optional; default mechanical scorer
|
|
11
|
+
// gate(toolCall) — optional; ONLY guardrails today
|
|
12
|
+
// applyProposal(agentDir, proposal) — the adapter's apply
|
|
13
|
+
// validate(agentDir, payload) — the adapter's validate
|
|
14
|
+
//
|
|
15
|
+
// Host law (fail-soft everywhere): every hook call is try-wrapped (+ time-boxed
|
|
16
|
+
// for miner-class hooks) by the registry — a broken faculty module degrades to
|
|
17
|
+
// items-only, never crashes the CLI, the gate, or a host hook.
|
|
18
|
+
//
|
|
19
|
+
// Manifest-only folders (faculty.json with NO code) are DECLARATIVE faculties:
|
|
20
|
+
// they get items listing, card UI, schema validation and the default digest
|
|
21
|
+
// line today; third-party CODE loading is deferred (W4).
|
|
22
|
+
|
|
23
|
+
/** The module-API version this host speaks. Majors above this are skipped. */
|
|
24
|
+
export const CONTRACT_VERSION = 1;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a raw manifest object into the full contract shape (defaults
|
|
28
|
+
* filled, id forced to the faculty's directory name when absent). Pure.
|
|
29
|
+
* @param {object} raw parsed faculty.json (or {})
|
|
30
|
+
* @param {string} dirName the faculty folder name (fallback id)
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeManifest(raw = {}, dirName = 'faculty') {
|
|
33
|
+
const id = typeof raw.id === 'string' && raw.id ? raw.id : dirName;
|
|
34
|
+
const title = typeof raw.title === 'string' && raw.title ? raw.title : id.charAt(0).toUpperCase() + id.slice(1);
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
title,
|
|
38
|
+
tagline: typeof raw.tagline === 'string' ? raw.tagline : '',
|
|
39
|
+
version: typeof raw.version === 'string' ? raw.version : '1.0.0',
|
|
40
|
+
contract: Number.isFinite(raw.contract) ? raw.contract : CONTRACT_VERSION,
|
|
41
|
+
kinds: Array.isArray(raw.kinds) ? raw.kinds.map(String) : [],
|
|
42
|
+
itemsDir: typeof raw.itemsDir === 'string' && raw.itemsDir ? raw.itemsDir : 'items',
|
|
43
|
+
schema: typeof raw.schema === 'string' && raw.schema ? raw.schema : 'schema.json',
|
|
44
|
+
hooks: {
|
|
45
|
+
miner: !!raw.hooks?.miner,
|
|
46
|
+
digest: !!raw.hooks?.digest,
|
|
47
|
+
eval: !!raw.hooks?.eval,
|
|
48
|
+
gate: !!raw.hooks?.gate,
|
|
49
|
+
},
|
|
50
|
+
ui: {
|
|
51
|
+
icon: typeof raw.ui?.icon === 'string' ? raw.ui.icon : 'folder',
|
|
52
|
+
accent: typeof raw.ui?.accent === 'string' ? raw.ui.accent : 'neutral',
|
|
53
|
+
teaching: typeof raw.ui?.teaching === 'string' ? raw.ui.teaching : '',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Is a manifest's contract version one this host can serve? (major gate) */
|
|
59
|
+
export function compatibleContract(manifest) {
|
|
60
|
+
return Math.floor(manifest?.contract ?? CONTRACT_VERSION) <= CONTRACT_VERSION;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Validate a manifest's required fields. Returns {ok, errors} — never throws.
|
|
65
|
+
* (Normalization already fills defaults; this flags the truly broken.)
|
|
66
|
+
*/
|
|
67
|
+
export function validateManifest(manifest) {
|
|
68
|
+
const errors = [];
|
|
69
|
+
if (!manifest || typeof manifest !== 'object') return { ok: false, errors: ['not an object'] };
|
|
70
|
+
if (!manifest.id || !/^[a-z0-9][a-z0-9_-]*$/.test(manifest.id)) errors.push(`id must be a slug (got '${manifest.id}')`);
|
|
71
|
+
if (!manifest.title) errors.push('title is required');
|
|
72
|
+
if (!compatibleContract(manifest)) errors.push(`contract ${manifest.contract} > host contract ${CONTRACT_VERSION} — skipped`);
|
|
73
|
+
return { ok: errors.length === 0, errors };
|
|
74
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// zuzuu/faculty/pending.mjs — pending-proposal collection shared by the gate
|
|
2
|
+
// surfaces (`zuzuu review`, `zuzuu proposals`, `zuzuu eval`). One walk order,
|
|
3
|
+
// one ranked grouping; adapters with their own listProposals (dir-shaped
|
|
4
|
+
// faculties) override the spine read.
|
|
5
|
+
|
|
6
|
+
import * as registry from './registry.mjs';
|
|
7
|
+
import { listProposals as spineListProposals } from './proposal.mjs';
|
|
8
|
+
import { readIndex } from '../core/store.mjs';
|
|
9
|
+
import { rank } from '../eval/rank.mjs';
|
|
10
|
+
import { getScorer } from '../eval/score.mjs';
|
|
11
|
+
|
|
12
|
+
// Review walks faculties in a fixed order so piped sessions are deterministic
|
|
13
|
+
// (the combo smoke test feeds one stdin across the actions pass then knowledge).
|
|
14
|
+
export const REVIEW_ORDER = ['actions', 'knowledge', 'guardrails', 'instructions', 'memory'];
|
|
15
|
+
|
|
16
|
+
/** Build sessionMtimes map from the sessions index — best-effort, fail-open. */
|
|
17
|
+
export function buildSessionMtimes(cwd) {
|
|
18
|
+
try {
|
|
19
|
+
const idx = readIndex(cwd);
|
|
20
|
+
const map = {};
|
|
21
|
+
for (const s of idx.sessions ?? []) {
|
|
22
|
+
if (!s.id) continue;
|
|
23
|
+
const ms = s.startedAt ? Date.parse(s.startedAt) : 0;
|
|
24
|
+
if (!isNaN(ms) && ms > 0) map[s.id] = ms;
|
|
25
|
+
}
|
|
26
|
+
return map;
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Pending proposals for one adapter (dir-shaped adapters override listProposals). */
|
|
33
|
+
export function facultyPending(agentDir, a) {
|
|
34
|
+
if (typeof a.listProposals === 'function') return a.listProposals(agentDir);
|
|
35
|
+
// JSON-record faculties: read via the spine (records carry both the spine shape
|
|
36
|
+
// and the legacy candidate/er keys the knowledge card renders from).
|
|
37
|
+
return spineListProposals(agentDir, a.name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Ordered list of adapters that have pending proposals to review. */
|
|
41
|
+
export function pendingByFaculty(agentDir) {
|
|
42
|
+
const adapters = registry.all();
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const ordered = [];
|
|
45
|
+
for (const name of REVIEW_ORDER) {
|
|
46
|
+
const a = adapters.find((x) => x.name === name);
|
|
47
|
+
if (a) { ordered.push(a); seen.add(name); }
|
|
48
|
+
}
|
|
49
|
+
for (const a of adapters) if (!seen.has(a.name)) ordered.push(a);
|
|
50
|
+
const sessionMtimes = buildSessionMtimes();
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const scorer = getScorer();
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const a of ordered) {
|
|
55
|
+
let proposals = facultyPending(agentDir, a);
|
|
56
|
+
if (!proposals.length) continue;
|
|
57
|
+
// Rank proposals highest-score-first (display only — never changes approval/mint).
|
|
58
|
+
const ranked = rank(proposals, scorer, { now, sessionMtimes });
|
|
59
|
+
proposals = ranked.map((r) => r.proposal);
|
|
60
|
+
out.push({ adapter: a, proposals });
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
@@ -1,33 +1,219 @@
|
|
|
1
|
-
// zuzuu/faculty/registry.mjs
|
|
2
|
-
// Faculty adapter registry — a module-level Map keyed by adapter.name.
|
|
3
|
-
// Adapters register themselves on import; consumers query by name or list all.
|
|
1
|
+
// zuzuu/faculty/registry.mjs — the Faculty Module registry (2026-06-13 spec).
|
|
4
2
|
//
|
|
5
|
-
//
|
|
3
|
+
// Replaces the old scattered wiring (miners/registry self-registration,
|
|
4
|
+
// per-faculty adapter imports at every spine call site, digest's hardcoded
|
|
5
|
+
// sections): the five BUILT-IN modules are imported statically here; the spine
|
|
6
|
+
// (gate/review/proposals/eval/distill/digest/doctor) iterates THIS registry —
|
|
7
|
+
// no faculty names hardcoded outside built-in module files (ordering
|
|
8
|
+
// preferences excepted).
|
|
9
|
+
//
|
|
10
|
+
// Discovery beyond built-ins: any `<home>/<dir>/faculty.json` is parsed and
|
|
11
|
+
// listed as a DECLARATIVE faculty (manifest-only — items listing, card UI,
|
|
12
|
+
// schema validation, default digest line work today). Third-party CODE loading
|
|
13
|
+
// is explicitly deferred to W4.
|
|
14
|
+
//
|
|
15
|
+
// Host law: every hook invocation goes through invoke()/invokeTimeboxed() —
|
|
16
|
+
// try-wrapped (+ 5s time-box on miner-class hooks). A broken module degrades
|
|
17
|
+
// to items-only; failures are recorded and surfaced by `zuzuu doctor`.
|
|
18
|
+
|
|
19
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { FACULTIES } from './contract.mjs';
|
|
22
|
+
import { normalizeManifest, compatibleContract } from './module.mjs';
|
|
23
|
+
import * as knowledge from '../faculties/knowledge/index.mjs';
|
|
24
|
+
import * as memory from '../faculties/memory/index.mjs';
|
|
25
|
+
import * as actions from '../faculties/actions/index.mjs';
|
|
26
|
+
import * as instructions from '../faculties/instructions/index.mjs';
|
|
27
|
+
import * as guardrails from '../faculties/guardrails/index.mjs';
|
|
28
|
+
|
|
29
|
+
export const BUILTIN_MODULES = { knowledge, memory, actions, instructions, guardrails };
|
|
30
|
+
|
|
31
|
+
// Legacy adapter/miner walk order (the pre-module import order at every call
|
|
32
|
+
// site) — preserved so list/eval/distill outputs stay byte-identical.
|
|
33
|
+
const LEGACY_ORDER = ['knowledge', 'actions', 'guardrails', 'instructions', 'memory'];
|
|
6
34
|
|
|
7
|
-
|
|
8
|
-
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// adapter surface (back-compat: gate/review/proposals/eval consume these)
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** Test/extension overrides layered over the built-ins. */
|
|
40
|
+
const overrides = new Map();
|
|
9
41
|
|
|
10
42
|
/**
|
|
11
|
-
* Register an adapter (keyed by adapter.name).
|
|
12
|
-
*
|
|
43
|
+
* Register an adapter override (keyed by adapter.name). Built-ins are always
|
|
44
|
+
* present; this layers replacements/additions on top (tests, future plugins).
|
|
13
45
|
*/
|
|
14
46
|
export function register(adapter) {
|
|
15
|
-
|
|
47
|
+
overrides.set(adapter.name, adapter);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Retrieve an adapter by faculty name (override > built-in). */
|
|
51
|
+
export function get(name) {
|
|
52
|
+
return overrides.get(name) ?? BUILTIN_MODULES[name]?.adapter;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** All adapters, built-ins first in the legacy order, then extra overrides. */
|
|
56
|
+
export function all() {
|
|
57
|
+
const names = [...LEGACY_ORDER];
|
|
58
|
+
for (const n of overrides.keys()) if (!names.includes(n)) names.push(n);
|
|
59
|
+
return names.map(get).filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// miner surface (replaces miners/registry.mjs)
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/** All built-in miners in the legacy distill order. */
|
|
67
|
+
export function miners() {
|
|
68
|
+
return LEGACY_ORDER.map((f) => BUILTIN_MODULES[f]?.miner).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The miner for a faculty, or undefined. */
|
|
72
|
+
export function minerOf(faculty) {
|
|
73
|
+
return BUILTIN_MODULES[faculty]?.miner;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// faculty discovery — built-ins + declarative faculty.json folders
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/** Read + normalize `<home>/<id>/faculty.json` → {manifest|null, error|null}. */
|
|
81
|
+
function readHomeManifest(agentDir, id) {
|
|
82
|
+
const p = join(agentDir, id, 'faculty.json');
|
|
83
|
+
if (!existsSync(p)) return { manifest: null, error: null };
|
|
84
|
+
try {
|
|
85
|
+
const manifest = normalizeManifest(JSON.parse(readFileSync(p, 'utf8')), id);
|
|
86
|
+
if (!compatibleContract(manifest)) {
|
|
87
|
+
return { manifest: null, error: `contract ${manifest.contract} unsupported by this host` };
|
|
88
|
+
}
|
|
89
|
+
return { manifest, error: null };
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return { manifest: null, error: e.message ?? String(e) };
|
|
92
|
+
}
|
|
16
93
|
}
|
|
17
94
|
|
|
95
|
+
// Home dirs that are never faculties.
|
|
96
|
+
const NON_FACULTY_DIRS = new Set(['generations']);
|
|
97
|
+
|
|
18
98
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
99
|
+
* Every faculty this home serves: the five built-ins (always — module code +
|
|
100
|
+
* manifest, home faculty.json overriding the built-in manifest when present)
|
|
101
|
+
* plus declarative manifest-only folders. Fail-soft: a broken faculty.json
|
|
102
|
+
* lists the faculty with `manifestError` (degraded — doctor reports it).
|
|
103
|
+
*
|
|
104
|
+
* @param {string} agentDir
|
|
105
|
+
* @returns {Array<{id, manifest, module, builtin, declarative, manifestSource, manifestError?}>}
|
|
22
106
|
*/
|
|
23
|
-
export function
|
|
24
|
-
|
|
107
|
+
export function facultiesOf(agentDir) {
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const id of FACULTIES) {
|
|
110
|
+
const mod = BUILTIN_MODULES[id];
|
|
111
|
+
const home = readHomeManifest(agentDir, id);
|
|
112
|
+
out.push({
|
|
113
|
+
id,
|
|
114
|
+
builtin: true,
|
|
115
|
+
declarative: false,
|
|
116
|
+
module: mod,
|
|
117
|
+
manifest: home.manifest ?? mod.manifest,
|
|
118
|
+
manifestSource: home.manifest ? 'home' : 'builtin',
|
|
119
|
+
...(home.error ? { manifestError: home.error } : {}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
for (const e of readdirSync(agentDir, { withFileTypes: true })) {
|
|
124
|
+
if (!e.isDirectory() || e.name.startsWith('.') || e.name.startsWith('_')) continue;
|
|
125
|
+
if (FACULTIES.includes(e.name) || NON_FACULTY_DIRS.has(e.name)) continue;
|
|
126
|
+
const home = readHomeManifest(agentDir, e.name);
|
|
127
|
+
if (!home.manifest && !home.error) continue; // no faculty.json → not a faculty
|
|
128
|
+
out.push({
|
|
129
|
+
id: e.name,
|
|
130
|
+
builtin: false,
|
|
131
|
+
declarative: true,
|
|
132
|
+
module: null, // third-party CODE loading deferred to W4 — manifest-only today
|
|
133
|
+
manifest: home.manifest ?? normalizeManifest({}, e.name),
|
|
134
|
+
manifestSource: 'home',
|
|
135
|
+
...(home.error ? { manifestError: home.error } : {}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} catch { /* no home dir yet → built-ins only */ }
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** One faculty entry by id (built-in or declarative), or null. */
|
|
143
|
+
export function facultyOf(agentDir, id) {
|
|
144
|
+
return facultiesOf(agentDir).find((f) => f.id === id) ?? null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// fail-soft hook invocation (+ degradation record for doctor)
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/** Miner-class hooks get a wall-clock budget (spec: 5s). */
|
|
152
|
+
export const MINER_HOOK_TIMEOUT_MS = 5000;
|
|
153
|
+
|
|
154
|
+
const failures = new Map(); // `${faculty}.${hook}` → { faculty, hook, error, at }
|
|
155
|
+
|
|
156
|
+
function recordFailure(faculty, hook, error) {
|
|
157
|
+
failures.set(`${faculty}.${hook}`, { faculty, hook, error: String(error?.message ?? error), at: new Date().toISOString() });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Hook failures recorded this process — `zuzuu doctor` surfaces these. */
|
|
161
|
+
export function hookFailures() {
|
|
162
|
+
return [...failures.values()];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Tests only: forget recorded failures. */
|
|
166
|
+
export function clearHookFailures() {
|
|
167
|
+
failures.clear();
|
|
25
168
|
}
|
|
26
169
|
|
|
27
170
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
171
|
+
* Invoke a module hook fail-soft (synchronous spine paths: digest, gate,
|
|
172
|
+
* signals). NEVER throws.
|
|
173
|
+
* @param {{id:string, module:object|null}} entry a facultiesOf() entry (or {id, module})
|
|
174
|
+
* @returns {{ok:true, value:any} | {ok:false, missing?:true, error?:string}}
|
|
30
175
|
*/
|
|
31
|
-
export function
|
|
32
|
-
|
|
176
|
+
export function invoke(entry, hook, ...args) {
|
|
177
|
+
const fn = entry?.module?.[hook];
|
|
178
|
+
if (typeof fn !== 'function') return { ok: false, missing: true };
|
|
179
|
+
try {
|
|
180
|
+
return { ok: true, value: fn(...args) };
|
|
181
|
+
} catch (e) {
|
|
182
|
+
recordFailure(entry.id ?? entry.module?.manifest?.id ?? '?', hook, e);
|
|
183
|
+
return { ok: false, error: String(e?.message ?? e) };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Invoke a miner-class hook fail-soft WITH a time-box: synchronous throws are
|
|
189
|
+
* caught; an async (Promise-returning) hook is raced against `timeoutMs`
|
|
190
|
+
* (default 5s). NEVER rejects.
|
|
191
|
+
* @returns {Promise<{ok:true, value:any} | {ok:false, missing?:true, timedOut?:true, error?:string}>}
|
|
192
|
+
*/
|
|
193
|
+
export async function invokeTimeboxed(entry, hook, args = [], { timeoutMs = MINER_HOOK_TIMEOUT_MS } = {}) {
|
|
194
|
+
const fn = entry?.module?.[hook];
|
|
195
|
+
if (typeof fn !== 'function') return { ok: false, missing: true };
|
|
196
|
+
const faculty = entry.id ?? entry.module?.manifest?.id ?? '?';
|
|
197
|
+
try {
|
|
198
|
+
const r = fn(...args);
|
|
199
|
+
if (!r || typeof r.then !== 'function') return { ok: true, value: r };
|
|
200
|
+
let timer;
|
|
201
|
+
const timeout = new Promise((resolve) => {
|
|
202
|
+
timer = setTimeout(() => resolve({ __zuzuuTimeout: true }), timeoutMs);
|
|
203
|
+
});
|
|
204
|
+
const settled = await Promise.race([Promise.resolve(r).catch((e) => ({ __zuzuuError: e })), timeout]);
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
if (settled && settled.__zuzuuTimeout) {
|
|
207
|
+
recordFailure(faculty, hook, `timed out after ${timeoutMs}ms`);
|
|
208
|
+
return { ok: false, timedOut: true, error: `timed out after ${timeoutMs}ms` };
|
|
209
|
+
}
|
|
210
|
+
if (settled && settled.__zuzuuError !== undefined) {
|
|
211
|
+
recordFailure(faculty, hook, settled.__zuzuuError);
|
|
212
|
+
return { ok: false, error: String(settled.__zuzuuError?.message ?? settled.__zuzuuError) };
|
|
213
|
+
}
|
|
214
|
+
return { ok: true, value: settled };
|
|
215
|
+
} catch (e) {
|
|
216
|
+
recordFailure(faculty, hook, e);
|
|
217
|
+
return { ok: false, error: String(e?.message ?? e) };
|
|
218
|
+
}
|
|
33
219
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// zuzuu/faculty/render.mjs — shared proposal card/text rendering for the human
|
|
2
|
+
// gate surfaces (`zuzuu review` cards + `zuzuu proposals` lines). Pure string
|
|
3
|
+
// builders; no I/O beyond the existing-item lookup the knowledge card shows.
|
|
4
|
+
|
|
5
|
+
import { readItem } from '../knowledge/items.mjs';
|
|
6
|
+
import { evalLine } from '../commands/eval.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The rich knowledge proposal card (id, type, attrs/relations, evidence, ER
|
|
10
|
+
* verdict + the matched item when enrich/duplicate, eval line).
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function knowledgeCard(agentDir, p, i, total, scoreResult) {
|
|
14
|
+
const lines = [];
|
|
15
|
+
lines.push(`\n━━ proposal ${i + 1}/${total} ── ${p.id} ── ${p.kind} ── source: ${p.source ?? '-'} ━━`);
|
|
16
|
+
if (p.kind === 'registry') {
|
|
17
|
+
lines.push(` register ${p.registry.slice(0, -1)}: '${p.key}' (seen ${p.evidence?.occurrences}× in candidates)`);
|
|
18
|
+
} else {
|
|
19
|
+
// dual-read: legacy records carry `candidate`/`er`; spine records (e.g.
|
|
20
|
+
// inbox-promoted) carry `payload`/`analysis.er` — both must render.
|
|
21
|
+
const c = p.candidate ?? p.payload ?? {};
|
|
22
|
+
lines.push(` ${c.type}: ${c.body?.slice(0, 100).replace(/\n/g, ' ')}`);
|
|
23
|
+
for (const [k, v] of Object.entries(c.attributes ?? {})) lines.push(` · ${k} = ${v}`);
|
|
24
|
+
for (const r of c.relations ?? []) lines.push(` → ${r.type} ${r.target}`);
|
|
25
|
+
const ev = p.evidence ?? {};
|
|
26
|
+
if (Object.keys(ev).length) lines.push(` evidence: ${Object.entries(ev).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(' ')}`);
|
|
27
|
+
const er = p.er ?? p.analysis?.er ?? {};
|
|
28
|
+
lines.push(` er: ${er.verdict}${er.match ? ` → ${er.match}` : ''} (${(er.confidence ?? 0).toFixed(2)} · ${er.reason ?? ''})`);
|
|
29
|
+
if (er.match) {
|
|
30
|
+
const m = readItem(agentDir, er.match);
|
|
31
|
+
if (m) lines.push(` existing: ${m.body.slice(0, 80).replace(/\n/g, ' ')}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Eval line — always shown; scoreResult computed by caller from ranked array.
|
|
35
|
+
if (scoreResult) lines.push(` ${evalLine(scoreResult)}`);
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The historical knowledge one-liner for `zuzuu proposals list`. */
|
|
40
|
+
export function knowledgeLine(p) {
|
|
41
|
+
const c = p.candidate ?? p.payload ?? {}; // dual-read, same as the card
|
|
42
|
+
const what = p.kind === 'registry'
|
|
43
|
+
? `register ${p.registry.slice(0, -1)} '${p.key}'`
|
|
44
|
+
: `${c.type}: ${c.body?.slice(0, 60).replace(/\n/g, ' ')}`;
|
|
45
|
+
return ` ${p.id} [${p.er?.verdict ?? p.analysis?.er?.verdict ?? p.kind}] ${what}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Derive the human title for a proposal (the JSON list/table form). */
|
|
49
|
+
export function proposalTitle(adapter, p) {
|
|
50
|
+
let title;
|
|
51
|
+
if (adapter.name === 'knowledge') {
|
|
52
|
+
title = p.kind === 'registry'
|
|
53
|
+
? `register ${p.registry?.slice(0, -1) ?? ''} '${p.key ?? ''}'`
|
|
54
|
+
: (p.candidate?.body ?? p.payload?.body ?? p.id)?.slice(0, 80);
|
|
55
|
+
} else {
|
|
56
|
+
title = p.title ?? adapter.render(p).line;
|
|
57
|
+
}
|
|
58
|
+
return title ?? p.id;
|
|
59
|
+
}
|
package/zuzuu/faculty/trail.mjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
import { join } from 'node:path';
|
|
33
33
|
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
34
|
-
import { parseEnvelope } from '
|
|
34
|
+
import { parseEnvelope } from '../faculty/envelope.mjs';
|
|
35
35
|
|
|
36
36
|
const SEVERITY = { deny: 3, ask: 2, allow: 1 };
|
|
37
37
|
const ACTIONS = new Set(Object.keys(SEVERITY));
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
18
|
-
import { SEED_TYPES, SEED_ATTRIBUTES, SEED_RELATIONS } from '
|
|
19
|
-
import { serializeEnvelope, PAYLOAD_SCHEMAS, FACULTY_KINDS } from '
|
|
18
|
+
import { SEED_TYPES, SEED_ATTRIBUTES, SEED_RELATIONS } from '../knowledge/registry.mjs';
|
|
19
|
+
import { serializeEnvelope, PAYLOAD_SCHEMAS, FACULTY_KINDS } from '../faculty/envelope.mjs';
|
|
20
|
+
import { BUILTIN_MODULES } from '../faculty/registry.mjs';
|
|
20
21
|
|
|
21
22
|
export const MANIFEST_VERSION = 4;
|
|
22
23
|
|
|
@@ -194,6 +195,10 @@ const ENVELOPE_SPEC = JSON.stringify(
|
|
|
194
195
|
|
|
195
196
|
const payloadSchemaSeed = (f) => JSON.stringify(PAYLOAD_SCHEMAS[f], null, 2) + '\n';
|
|
196
197
|
|
|
198
|
+
/** Faculty Module manifest seed (faculty.json) — the built-in module's canonical
|
|
199
|
+
* manifest, serialized. Pinned definitions: byte-identical on re-init. */
|
|
200
|
+
export const manifestSeed = (f) => JSON.stringify(BUILTIN_MODULES[f].manifest, null, 2) + '\n';
|
|
201
|
+
|
|
197
202
|
/** The layout contract: dirs + seed files (relative to the project root). */
|
|
198
203
|
export const LAYOUT = {
|
|
199
204
|
dirs: ['.zuzuu', '.zuzuu/knowledge', '.zuzuu/knowledge/registry', '.zuzuu/knowledge/items', '.zuzuu/knowledge/inbox', '.zuzuu/knowledge/proposals', '.zuzuu/memory', '.zuzuu/memory/entries', '.zuzuu/memory/inbox', '.zuzuu/memory/proposals', '.zuzuu/actions', '.zuzuu/actions/inbox', '.zuzuu/instructions', '.zuzuu/instructions/items', '.zuzuu/instructions/inbox', '.zuzuu/instructions/proposals', '.zuzuu/guardrails', '.zuzuu/guardrails/items', '.zuzuu/guardrails/inbox', '.zuzuu/guardrails/proposals', '.zuzuu/generations', '.zuzuu/generations/snapshots'],
|
|
@@ -202,15 +207,20 @@ export const LAYOUT = {
|
|
|
202
207
|
'.zuzuu/schema.json': ENVELOPE_SPEC,
|
|
203
208
|
'.zuzuu/knowledge/README.md': KNOWLEDGE_README,
|
|
204
209
|
'.zuzuu/knowledge/schema.json': payloadSchemaSeed('knowledge'),
|
|
210
|
+
'.zuzuu/knowledge/faculty.json': manifestSeed('knowledge'),
|
|
205
211
|
'.zuzuu/memory/README.md': MEMORY_README,
|
|
206
212
|
'.zuzuu/memory/schema.json': payloadSchemaSeed('memory'),
|
|
213
|
+
'.zuzuu/memory/faculty.json': manifestSeed('memory'),
|
|
207
214
|
'.zuzuu/actions/README.md': ACTIONS_README,
|
|
208
215
|
'.zuzuu/actions/schema.json': payloadSchemaSeed('actions'),
|
|
216
|
+
'.zuzuu/actions/faculty.json': manifestSeed('actions'),
|
|
209
217
|
'.zuzuu/instructions/README.md': INSTRUCTIONS_README,
|
|
210
218
|
'.zuzuu/instructions/schema.json': payloadSchemaSeed('instructions'),
|
|
219
|
+
'.zuzuu/instructions/faculty.json': manifestSeed('instructions'),
|
|
211
220
|
'.zuzuu/instructions/items/steering.md': STEERING_SEED,
|
|
212
221
|
'.zuzuu/guardrails/README.md': GUARDRAILS_README,
|
|
213
222
|
'.zuzuu/guardrails/schema.json': payloadSchemaSeed('guardrails'),
|
|
223
|
+
'.zuzuu/guardrails/faculty.json': manifestSeed('guardrails'),
|
|
214
224
|
'.zuzuu/guardrails/items/no-root-wipe.md': RULE_SEEDS['no-root-wipe'],
|
|
215
225
|
'.zuzuu/guardrails/items/no-secret-reads.md': RULE_SEEDS['no-secret-reads'],
|
|
216
226
|
'.zuzuu/guardrails/items/confirm-force-push.md': RULE_SEEDS['confirm-force-push'],
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
10
|
-
import { paths, liveDir as liveDirOf } from '../store.mjs';
|
|
11
|
-
import { SessionState } from '../session.mjs';
|
|
10
|
+
import { paths, liveDir as liveDirOf } from '../core/store.mjs';
|
|
11
|
+
import { SessionState } from '../core/session.mjs';
|
|
12
12
|
|
|
13
13
|
const liveDir = (cwd) => liveDirOf(paths(cwd).dir);
|
|
14
14
|
// Some hosts pass a file PATH as the session id (pi → the session-file path).
|
package/zuzuu/live/reconcile.mjs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { listLive, isStale, closeLive } from './live-store.mjs';
|
|
7
7
|
import { byName } from '../capture/adapters/registry.mjs';
|
|
8
|
-
import { captureTrace } from '../capture-core.mjs';
|
|
9
|
-
import { SessionState } from '../session.mjs';
|
|
8
|
+
import { captureTrace } from '../core/capture-core.mjs';
|
|
9
|
+
import { SessionState } from '../core/session.mjs';
|
|
10
10
|
|
|
11
11
|
export const DEFAULT_STALE_MS = 15 * 60 * 1000; // 15 min without a heartbeat → abandoned
|
|
12
12
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// zuzuu/sessions/git.mjs — git PLUMBING for session-git (no session policy here).
|
|
2
|
+
//
|
|
3
|
+
// Every helper is safe-by-construction: argv arrays only (no shell strings),
|
|
4
|
+
// never throws, returns plain data. Session POLICY (open/checkpoint/close/
|
|
5
|
+
// status and the safety gates) lives in session-git.mjs.
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { rmSync } from 'node:fs';
|
|
9
|
+
import { join, isAbsolute, resolve } from 'node:path';
|
|
10
|
+
|
|
11
|
+
/** One git call — argv array only (no shell), never throws. */
|
|
12
|
+
export function git(args, cwd, input) {
|
|
13
|
+
try {
|
|
14
|
+
const r = spawnSync('git', args, { cwd, encoding: 'utf8', input });
|
|
15
|
+
return { ok: r.status === 0 && !r.error, out: (r.stdout ?? '').trim(), err: (r.stderr ?? '').trim() };
|
|
16
|
+
} catch (e) {
|
|
17
|
+
return { ok: false, out: '', err: String(e) };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function gitDir(cwd) {
|
|
22
|
+
const r = git(['rev-parse', '--git-dir'], cwd);
|
|
23
|
+
if (!r.ok || !r.out) return null;
|
|
24
|
+
return isAbsolute(r.out) ? r.out : resolve(cwd, r.out);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Current branch name, or null when detached / not a repo. */
|
|
28
|
+
export function currentBranch(cwd) {
|
|
29
|
+
const r = git(['symbolic-ref', '--short', '-q', 'HEAD'], cwd);
|
|
30
|
+
return r.ok && r.out ? r.out : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const branchExists = (cwd, name) => git(['rev-parse', '-q', '--verify', `refs/heads/${name}`], cwd).ok;
|
|
34
|
+
export const isDirty = (cwd) => !!git(['status', '--porcelain'], cwd).out;
|
|
35
|
+
|
|
36
|
+
/** Best-effort: drop squash leftovers so they can't leak into the user's next commit. */
|
|
37
|
+
export function cleanupSquashState(cwd) {
|
|
38
|
+
const gd = gitDir(cwd);
|
|
39
|
+
if (!gd) return;
|
|
40
|
+
for (const f of ['SQUASH_MSG', 'MERGE_MSG']) {
|
|
41
|
+
try {
|
|
42
|
+
rmSync(join(gd, f), { force: true });
|
|
43
|
+
} catch {
|
|
44
|
+
/* best-effort */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|