@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,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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// The Guardrails faculty — v1 rule engine (pure; I/O lives in the hook command).
|
|
2
|
+
//
|
|
3
|
+
// Rules are DATA, not code: one envelope item per rule under
|
|
4
|
+
// .zuzuu/guardrails/items/<id>.md (the Faculty Standard, W24) — *definitions*
|
|
5
|
+
// in the pin-definitions sense (versioned in git, graduate via proposals like
|
|
6
|
+
// every faculty's contents).
|
|
7
|
+
//
|
|
8
|
+
// ---
|
|
9
|
+
// id: no-root-wipe
|
|
10
|
+
// faculty: guardrails
|
|
11
|
+
// kind: rule
|
|
12
|
+
// title: …
|
|
13
|
+
// payload:
|
|
14
|
+
// action: deny # deny | ask | allow
|
|
15
|
+
// tool: Bash # exact tool name, or "*"
|
|
16
|
+
// pattern: "rm\\s+-rf\\s+/" # regex over the tool INPUT (stringified)
|
|
17
|
+
// reason: destructive root delete
|
|
18
|
+
// ---
|
|
19
|
+
// (optional rationale prose)
|
|
20
|
+
//
|
|
21
|
+
// Evaluation: collect every matching rule, then severity wins — deny > ask >
|
|
22
|
+
// allow (an explicit allow can whitelist past a later ask/deny only if it is
|
|
23
|
+
// NOT outweighed; severity beats file order so a sloppy rule ordering can never
|
|
24
|
+
// silently disarm a deny).
|
|
25
|
+
//
|
|
26
|
+
// FAIL-OPEN, per item: a malformed rule file is SKIPPED (and counted in
|
|
27
|
+
// `skipped`), never a crash and never a block — the other rules still apply.
|
|
28
|
+
// The gate runs per tool call, so loads are cached on the items dir's stat
|
|
29
|
+
// signature (names+mtimes+sizes): re-parse only when something changed. No
|
|
30
|
+
// derived file — the item files stay the single source of truth.
|
|
31
|
+
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
34
|
+
import { parseEnvelope } from '../faculty/envelope.mjs';
|
|
35
|
+
|
|
36
|
+
const SEVERITY = { deny: 3, ask: 2, allow: 1 };
|
|
37
|
+
const ACTIONS = new Set(Object.keys(SEVERITY));
|
|
38
|
+
|
|
39
|
+
// dir → { sig, result } — tiny in-memory cache (per process; the spawned gate
|
|
40
|
+
// pays one cold load, long-lived processes like `zuzuu web` skip re-parses).
|
|
41
|
+
const cache = new Map();
|
|
42
|
+
|
|
43
|
+
/** Compile one parsed envelope item into a rule, or null if malformed. */
|
|
44
|
+
function compileRule(item) {
|
|
45
|
+
const p = item?.payload ?? {};
|
|
46
|
+
if (!item?.id || !ACTIONS.has(p.action) || typeof p.pattern !== 'string' || !p.pattern) return null;
|
|
47
|
+
try {
|
|
48
|
+
return { id: String(item.id), action: p.action, tool: p.tool || '*', re: new RegExp(p.pattern, 'i'), reason: String(p.reason ?? '') };
|
|
49
|
+
} catch {
|
|
50
|
+
return null; // uncompilable pattern → skip this rule only
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load the rules from a guardrails faculty dir (…/guardrails) by composing the
|
|
56
|
+
* envelope items under its items/ subdir. FAIL-OPEN: a missing dir is zero
|
|
57
|
+
* rules; a malformed item is skipped + counted, never a crash.
|
|
58
|
+
* @param {string} guardrailsDir the faculty dir (e.g. <home>/guardrails)
|
|
59
|
+
* @returns {{ok: boolean, rules: Array, skipped: Array<{file: string, error: string}>}}
|
|
60
|
+
*/
|
|
61
|
+
export function loadRules(guardrailsDir) {
|
|
62
|
+
try {
|
|
63
|
+
const dir = join(guardrailsDir, 'items');
|
|
64
|
+
let names;
|
|
65
|
+
try {
|
|
66
|
+
names = readdirSync(dir).filter((f) => f.endsWith('.md')).sort();
|
|
67
|
+
} catch {
|
|
68
|
+
return { ok: true, rules: [], skipped: [] }; // no items dir → no rules (normal flow)
|
|
69
|
+
}
|
|
70
|
+
let sig = null;
|
|
71
|
+
try {
|
|
72
|
+
sig = names.map((n) => { const s = statSync(join(dir, n)); return `${n}:${s.mtimeMs}:${s.size}`; }).join('|');
|
|
73
|
+
const hit = cache.get(dir);
|
|
74
|
+
if (hit && hit.sig === sig) return hit.result;
|
|
75
|
+
} catch { sig = null; /* stat race → just load uncached */ }
|
|
76
|
+
|
|
77
|
+
const rules = [];
|
|
78
|
+
const skipped = [];
|
|
79
|
+
for (const f of names) {
|
|
80
|
+
try {
|
|
81
|
+
const { ok, item, errors } = parseEnvelope(readFileSync(join(dir, f), 'utf8'));
|
|
82
|
+
const rule = ok ? compileRule(item) : null;
|
|
83
|
+
if (rule) rules.push(rule);
|
|
84
|
+
else skipped.push({ file: f, error: ok ? 'malformed rule payload' : (errors[0] ?? 'parse error') });
|
|
85
|
+
} catch (e) {
|
|
86
|
+
skipped.push({ file: f, error: e.message });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const result = { ok: true, rules, skipped };
|
|
90
|
+
if (sig != null) cache.set(dir, { sig, result });
|
|
91
|
+
return result;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return { ok: false, rules: [], skipped: [], error: e.message }; // engine trouble → fail open
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Evaluate a tool call against loaded rules.
|
|
99
|
+
* @param {Array} rules from loadRules().rules
|
|
100
|
+
* @param {{tool:string, input:any}} call
|
|
101
|
+
* @returns {null | {action:'deny'|'ask'|'allow', rule:string, reason:string}}
|
|
102
|
+
* null = no rule matched → defer to the host's normal permission flow
|
|
103
|
+
*/
|
|
104
|
+
export function evaluate(rules, { tool, input }) {
|
|
105
|
+
const haystack = typeof input === 'string' ? input : JSON.stringify(input ?? {});
|
|
106
|
+
let winner = null;
|
|
107
|
+
for (const r of rules) {
|
|
108
|
+
if (r.tool !== '*' && r.tool !== tool) continue;
|
|
109
|
+
if (!r.re.test(haystack)) continue;
|
|
110
|
+
if (!winner || SEVERITY[r.action] > SEVERITY[winner.action]) {
|
|
111
|
+
winner = { action: r.action, rule: r.id, reason: r.reason || `matched guardrail ${r.id}` };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return winner;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Gemini CLI block shape: stdout JSON { decision: "deny", reason } (exit 0).
|
|
119
|
+
* Gemini has no "ask" decision → defer (null) so its own approval flow runs.
|
|
120
|
+
* Only an explicit deny blocks.
|
|
121
|
+
*/
|
|
122
|
+
export function toGeminiDecision(verdict) {
|
|
123
|
+
if (!verdict || verdict.action !== 'deny') return null;
|
|
124
|
+
return { decision: 'deny', reason: `guardrail ${verdict.rule}: ${verdict.reason}` };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Map a verdict to Claude Code's PreToolUse hookSpecificOutput (verified schema). */
|
|
128
|
+
export function toPreToolUseDecision(verdict) {
|
|
129
|
+
if (!verdict || verdict.action === 'allow') return null; // no output → normal flow (fail-open / explicit allow)
|
|
130
|
+
return {
|
|
131
|
+
hookSpecificOutput: {
|
|
132
|
+
hookEventName: 'PreToolUse',
|
|
133
|
+
permissionDecision: verdict.action, // 'deny' | 'ask'
|
|
134
|
+
permissionDecisionReason: `guardrail ${verdict.rule}: ${verdict.reason}`,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -15,9 +15,15 @@
|
|
|
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 '
|
|
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';
|
|
19
21
|
|
|
20
|
-
export const MANIFEST_VERSION =
|
|
22
|
+
export const MANIFEST_VERSION = 4;
|
|
23
|
+
|
|
24
|
+
// Deterministic seed timestamp (the Faculty Standard date) — seeds are pinned
|
|
25
|
+
// definitions, so idempotent re-inits must produce byte-identical files.
|
|
26
|
+
const SEED_AT = '2026-06-12T00:00:00Z';
|
|
21
27
|
|
|
22
28
|
const AGENT_README = `# .zuzuu/ — your agent's home (hidden, like .git — yours to read & version)
|
|
23
29
|
|
|
@@ -70,17 +76,23 @@ Curated recollections of past sessions, distilled from the observability traces
|
|
|
70
76
|
- **Who writes:** zuzuu (distillation — *not built yet*), human (curation). Raw traces stay in traces/ — this is the *curated* layer.
|
|
71
77
|
- **Where:** one Markdown file per entry under \`entries/\`, named \`<id>.md\`.
|
|
72
78
|
|
|
73
|
-
## Record schema (
|
|
79
|
+
## Record schema (the Faculty Standard envelope)
|
|
74
80
|
\`\`\`markdown
|
|
75
81
|
---
|
|
76
82
|
id: mem-2026-06-11-flaky-ci-retry # mem-<YYYY-MM-DD>-<slug>, stable
|
|
77
|
-
|
|
83
|
+
faculty: memory
|
|
84
|
+
kind: episode
|
|
78
85
|
title: Flaky CI fixed by pinning node 22
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
status: active
|
|
87
|
+
created_at: 2026-06-11 # ISO date the episode occurred
|
|
88
|
+
payload:
|
|
89
|
+
sessions: # ids that exist in .zuzuu/sessions.json
|
|
90
|
+
- ses_abc123
|
|
91
|
+
hosts:
|
|
92
|
+
- claude-code
|
|
93
|
+
tags: # optional
|
|
94
|
+
- ci
|
|
95
|
+
- flaky-test
|
|
84
96
|
---
|
|
85
97
|
## Attempted
|
|
86
98
|
What was tried.
|
|
@@ -89,7 +101,6 @@ What happened (outcome / error / fix).
|
|
|
89
101
|
## Remember next time
|
|
90
102
|
The durable lesson.
|
|
91
103
|
\`\`\`
|
|
92
|
-
\`status: proposed\` and the distiller→review pipeline are **reserved** (not built this pass).
|
|
93
104
|
`;
|
|
94
105
|
|
|
95
106
|
const ACTIONS_README = `# actions/ — procedural faculty (how to DO things)
|
|
@@ -104,55 +115,115 @@ const INSTRUCTIONS_README = `# instructions/ — the Instructions faculty (direc
|
|
|
104
115
|
|
|
105
116
|
Cognition steering: identity, conventions, priorities — the project-level seed of
|
|
106
117
|
the pinned system prompt. The host agent reads and follows this.
|
|
107
|
-
- \`
|
|
118
|
+
- \`items/steering.md\` — the pinned steering item (what this is, conventions, priorities).
|
|
119
|
+
- Approved amendments land as further items in \`items/\` (kind: amendment).
|
|
108
120
|
- Hard *enforced* rules live in \`../guardrails/\` (a separate faculty), not here.
|
|
109
121
|
`;
|
|
110
122
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
const STEERING_SEED = serializeEnvelope({
|
|
124
|
+
id: 'steering',
|
|
125
|
+
faculty: 'instructions',
|
|
126
|
+
kind: 'steering',
|
|
127
|
+
title: 'Project steering',
|
|
128
|
+
status: 'active',
|
|
129
|
+
created_at: SEED_AT,
|
|
130
|
+
payload: { scope: 'project' },
|
|
131
|
+
body: '<!-- Fill in: what this project is, conventions, priorities. The host agent reads this. -->',
|
|
132
|
+
});
|
|
115
133
|
|
|
116
134
|
const GUARDRAILS_README = `# guardrails/ — the Guardrails faculty (enforced, not advisory)
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
136
|
+
One rule per envelope item in \`items/\` (markdown + frontmatter; payload =
|
|
137
|
+
\`{ action: deny|ask|allow, tool: "Bash"|"*", pattern: <regex over the tool
|
|
138
|
+
input>, reason }\`; the body is optional rationale prose). Every tool call is
|
|
139
|
+
evaluated by the zuzuu PreToolUse gate (installed by \`zuzuu enable\`). Severity
|
|
140
|
+
wins: deny > ask > allow; no match → the host's normal permission flow. The
|
|
141
|
+
engine FAILS OPEN — a malformed item is skipped, never a block — and matched
|
|
142
|
+
decisions are logged for the trace. Edit, commit, done — rules are definitions,
|
|
125
143
|
versioned in git like everything else.
|
|
126
144
|
`;
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
/** Seeded rules, one envelope item each (the Faculty Standard, W24). */
|
|
147
|
+
const ruleSeed = ({ id, title, action, tool, pattern, reason, body }) =>
|
|
148
|
+
serializeEnvelope({
|
|
149
|
+
id, faculty: 'guardrails', kind: 'rule', title, status: 'active', created_at: SEED_AT,
|
|
150
|
+
payload: { action, tool, pattern, reason }, body,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const RULE_SEEDS = {
|
|
154
|
+
'no-root-wipe': ruleSeed({
|
|
155
|
+
id: 'no-root-wipe', title: 'No destructive delete at filesystem root', action: 'deny', tool: 'Bash',
|
|
156
|
+
pattern: 'rm\\s+-[a-z]*r[a-z]*\\s+/(\\s|$)', reason: 'destructive delete at filesystem root',
|
|
157
|
+
body: 'Blocks `rm -rf /` and flag-order variants targeting the filesystem root.',
|
|
158
|
+
}),
|
|
159
|
+
'no-secret-reads': ruleSeed({
|
|
160
|
+
id: 'no-secret-reads', title: 'No reading secret material', action: 'deny', tool: '*',
|
|
161
|
+
pattern: '\\.env(\\.|\\b)|id_rsa|\\.pem\\b', reason: 'secret material should not enter the context',
|
|
162
|
+
body: 'Keys and env files must not enter the model context — across every tool, not just Bash.',
|
|
163
|
+
}),
|
|
164
|
+
'confirm-force-push': ruleSeed({
|
|
165
|
+
id: 'confirm-force-push', title: 'Confirm before force-push', action: 'ask', tool: 'Bash',
|
|
166
|
+
pattern: 'git\\b.*\\bpush\\b.*--force', reason: 'force-push rewrites shared history',
|
|
167
|
+
// \b.*\bpush, not push adjacent to git: a real session bypassed the
|
|
168
|
+
// adjacent form with `git -C /path push --force-with-lease` (exp-8).
|
|
169
|
+
body: 'Asks (never blocks) on any force-push. The loose `git\\b.*\\bpush` form catches the real exp-8 bypass: `git -C /path push --force-with-lease`.',
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** Envelope spec seed (.zuzuu/schema.json) — descriptive, for humans + tools. */
|
|
174
|
+
const ENVELOPE_SPEC = JSON.stringify(
|
|
175
|
+
{
|
|
176
|
+
standard: 'zuzuu-faculty-envelope',
|
|
177
|
+
version: 1,
|
|
178
|
+
description: 'One file per item: markdown body + strict frontmatter. One rigid envelope across all five faculties; payload is faculty-typed and validated by <faculty>/schema.json.',
|
|
179
|
+
envelope: {
|
|
180
|
+
id: 'required — slug [a-z0-9-]',
|
|
181
|
+
faculty: 'required — knowledge|memory|actions|instructions|guardrails',
|
|
182
|
+
kind: 'required — per-faculty kinds (see kinds)',
|
|
183
|
+
title: 'required — single line',
|
|
184
|
+
status: 'active|archived (default active)',
|
|
185
|
+
created_at: 'required — ISO date/datetime',
|
|
186
|
+
updated_at: 'optional — ISO date/datetime',
|
|
187
|
+
provenance: 'optional list of {session, ref}',
|
|
188
|
+
payload: 'faculty-typed machine fields (see <faculty>/schema.json)',
|
|
139
189
|
},
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
kinds: { ...FACULTY_KINDS, knowledge: 'registry-governed (knowledge/registry/types.json)' },
|
|
191
|
+
},
|
|
192
|
+
null,
|
|
193
|
+
2,
|
|
194
|
+
) + '\n';
|
|
195
|
+
|
|
196
|
+
const payloadSchemaSeed = (f) => JSON.stringify(PAYLOAD_SCHEMAS[f], null, 2) + '\n';
|
|
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';
|
|
143
201
|
|
|
144
202
|
/** The layout contract: dirs + seed files (relative to the project root). */
|
|
145
203
|
export const LAYOUT = {
|
|
146
|
-
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/inbox', '.zuzuu/instructions/proposals', '.zuzuu/guardrails', '.zuzuu/guardrails/inbox', '.zuzuu/guardrails/proposals', '.zuzuu/generations', '.zuzuu/generations/snapshots'],
|
|
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'],
|
|
147
205
|
files: {
|
|
148
206
|
'.zuzuu/README.md': AGENT_README,
|
|
207
|
+
'.zuzuu/schema.json': ENVELOPE_SPEC,
|
|
149
208
|
'.zuzuu/knowledge/README.md': KNOWLEDGE_README,
|
|
209
|
+
'.zuzuu/knowledge/schema.json': payloadSchemaSeed('knowledge'),
|
|
210
|
+
'.zuzuu/knowledge/faculty.json': manifestSeed('knowledge'),
|
|
150
211
|
'.zuzuu/memory/README.md': MEMORY_README,
|
|
212
|
+
'.zuzuu/memory/schema.json': payloadSchemaSeed('memory'),
|
|
213
|
+
'.zuzuu/memory/faculty.json': manifestSeed('memory'),
|
|
151
214
|
'.zuzuu/actions/README.md': ACTIONS_README,
|
|
215
|
+
'.zuzuu/actions/schema.json': payloadSchemaSeed('actions'),
|
|
216
|
+
'.zuzuu/actions/faculty.json': manifestSeed('actions'),
|
|
152
217
|
'.zuzuu/instructions/README.md': INSTRUCTIONS_README,
|
|
153
|
-
'.zuzuu/instructions/
|
|
218
|
+
'.zuzuu/instructions/schema.json': payloadSchemaSeed('instructions'),
|
|
219
|
+
'.zuzuu/instructions/faculty.json': manifestSeed('instructions'),
|
|
220
|
+
'.zuzuu/instructions/items/steering.md': STEERING_SEED,
|
|
154
221
|
'.zuzuu/guardrails/README.md': GUARDRAILS_README,
|
|
155
|
-
'.zuzuu/guardrails/
|
|
222
|
+
'.zuzuu/guardrails/schema.json': payloadSchemaSeed('guardrails'),
|
|
223
|
+
'.zuzuu/guardrails/faculty.json': manifestSeed('guardrails'),
|
|
224
|
+
'.zuzuu/guardrails/items/no-root-wipe.md': RULE_SEEDS['no-root-wipe'],
|
|
225
|
+
'.zuzuu/guardrails/items/no-secret-reads.md': RULE_SEEDS['no-secret-reads'],
|
|
226
|
+
'.zuzuu/guardrails/items/confirm-force-push.md': RULE_SEEDS['confirm-force-push'],
|
|
156
227
|
'.zuzuu/knowledge/registry/types.json': JSON.stringify(SEED_TYPES, null, 2) + '\n',
|
|
157
228
|
'.zuzuu/knowledge/registry/attributes.json': JSON.stringify(SEED_ATTRIBUTES, null, 2) + '\n',
|
|
158
229
|
'.zuzuu/knowledge/registry/relations.json': JSON.stringify(SEED_RELATIONS, null, 2) + '\n',
|
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
// Knowledge items — files as truth
|
|
2
|
-
// .zuzuu/knowledge/items/<id>.md:
|
|
3
|
-
// writer and reader; grammar below) + a prose body (the fact in your voice).
|
|
1
|
+
// Knowledge items — files as truth, in the Faculty Standard envelope (W24).
|
|
2
|
+
// One item per markdown file under .zuzuu/knowledge/items/<id>.md:
|
|
4
3
|
//
|
|
5
4
|
// ---
|
|
6
5
|
// id: test-command
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// faculty: knowledge
|
|
7
|
+
// kind: command
|
|
8
|
+
// title: The test command
|
|
9
9
|
// status: active
|
|
10
|
-
//
|
|
11
|
-
// command: npm test
|
|
12
|
-
// relations:
|
|
13
|
-
// - type: relates-to
|
|
14
|
-
// target: ci-pipeline
|
|
15
|
-
// commentary: optional
|
|
10
|
+
// created_at: 2026-06-10T12:00:00Z
|
|
16
11
|
// provenance:
|
|
17
12
|
// - session: ses_abc
|
|
18
13
|
// ref: occurrences=12
|
|
14
|
+
// payload:
|
|
15
|
+
// type: command
|
|
16
|
+
// attributes:
|
|
17
|
+
// command: npm test
|
|
18
|
+
// relations:
|
|
19
|
+
// - type: relates-to
|
|
20
|
+
// target: ci-pipeline
|
|
19
21
|
// ---
|
|
20
22
|
// Body prose.
|
|
21
23
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
24
|
+
// This module is a thin wrapper over faculty/envelope.mjs: the ON-DISK format
|
|
25
|
+
// is the envelope; the IN-MEMORY item shape stays the historical knowledge one
|
|
26
|
+
// ({id, type, created_at, status, attributes, relations, provenance, body}) so
|
|
27
|
+
// the registry/ER/index/digest pipeline is untouched. Ids are unchanged by the
|
|
28
|
+
// standard — only frontmatter keys moved (type/attributes/relations → payload).
|
|
26
29
|
|
|
27
30
|
import { join } from 'node:path';
|
|
28
31
|
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs';
|
|
32
|
+
import { parseEnvelope, serializeEnvelope, deriveTitle } from '../faculty/envelope.mjs';
|
|
29
33
|
|
|
30
34
|
export const itemsDir = (agentDir) => join(agentDir, 'knowledge', 'items');
|
|
31
35
|
|
|
@@ -38,88 +42,49 @@ export function slugify(text, max = 60) {
|
|
|
38
42
|
.replace(/-+$/, '') || 'item';
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const quoteIfNeeded = (s) => {
|
|
46
|
-
const t = String(s);
|
|
47
|
-
if (t.includes('\n')) throw new Error('item values must be single-line');
|
|
48
|
-
return /[:#'"\[\]{}]|^\s|\s$/.test(t) ? JSON.stringify(t) : t;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** Parse an item file's text → item object. Throws on grammar violations. */
|
|
45
|
+
/**
|
|
46
|
+
* Parse an item file's text → the in-memory knowledge item. Throws on grammar
|
|
47
|
+
* violations (callers catch — allItems collects, inbox falls back to prose).
|
|
48
|
+
*/
|
|
52
49
|
export function parseItem(text) {
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (['attributes', 'relations', 'provenance'].includes(key)) {
|
|
69
|
-
section = key;
|
|
70
|
-
if (val) throw new Error(`${key} must be a block`);
|
|
71
|
-
} else {
|
|
72
|
-
section = null;
|
|
73
|
-
item[key] = unquote(val);
|
|
74
|
-
}
|
|
75
|
-
} else if (section === 'attributes') {
|
|
76
|
-
const kv = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
77
|
-
if (!kv) throw new Error(`bad attribute line: ${line}`);
|
|
78
|
-
item.attributes[kv[1]] = unquote(kv[2]);
|
|
79
|
-
} else if (section === 'relations' || section === 'provenance') {
|
|
80
|
-
if (line.startsWith('- ')) {
|
|
81
|
-
current = {};
|
|
82
|
-
item[section].push(current);
|
|
83
|
-
const kv = line.slice(2).match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
84
|
-
if (!kv) throw new Error(`bad ${section} entry: ${line}`);
|
|
85
|
-
current[kv[1]] = unquote(kv[2]);
|
|
86
|
-
} else {
|
|
87
|
-
if (!current) throw new Error(`${section} entry continuation without "-": ${line}`);
|
|
88
|
-
const kv = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
|
|
89
|
-
if (!kv) throw new Error(`bad ${section} line: ${line}`);
|
|
90
|
-
current[kv[1]] = unquote(kv[2]);
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
throw new Error(`unexpected indented line: ${line}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (!item.id) throw new Error('item missing id');
|
|
50
|
+
const { ok, item: env, errors } = parseEnvelope(text);
|
|
51
|
+
if (!ok) throw new Error(errors[0] ?? 'invalid envelope');
|
|
52
|
+
if (env.faculty !== 'knowledge') throw new Error(`not a knowledge item (faculty: ${env.faculty})`);
|
|
53
|
+
const p = env.payload ?? {};
|
|
54
|
+
const item = {
|
|
55
|
+
attributes: p.attributes ?? {},
|
|
56
|
+
relations: p.relations ?? [],
|
|
57
|
+
provenance: env.provenance ?? [],
|
|
58
|
+
body: env.body,
|
|
59
|
+
};
|
|
60
|
+
item.id = env.id;
|
|
61
|
+
item.type = p.type ?? env.kind;
|
|
62
|
+
if (env.created_at != null) item.created_at = env.created_at;
|
|
63
|
+
if (env.updated_at != null) item.updated_at = env.updated_at;
|
|
64
|
+
if (env.status != null) item.status = env.status;
|
|
97
65
|
if (!item.type) throw new Error('item missing type');
|
|
98
66
|
return item;
|
|
99
67
|
}
|
|
100
68
|
|
|
101
|
-
/** Serialize an item
|
|
69
|
+
/** Serialize an in-memory knowledge item → envelope file text. */
|
|
102
70
|
export function serializeItem(item) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
lines.push('---', '');
|
|
122
|
-
return lines.join('\n') + (item.body ? item.body.trim() + '\n' : '');
|
|
71
|
+
if (!item.id) throw new Error('item missing id');
|
|
72
|
+
if (!item.type) throw new Error('item missing type');
|
|
73
|
+
const payload = { type: item.type };
|
|
74
|
+
if (Object.keys(item.attributes ?? {}).length) payload.attributes = item.attributes;
|
|
75
|
+
if ((item.relations ?? []).length) payload.relations = item.relations;
|
|
76
|
+
return serializeEnvelope({
|
|
77
|
+
id: item.id,
|
|
78
|
+
faculty: 'knowledge',
|
|
79
|
+
kind: item.type,
|
|
80
|
+
title: item.title ?? deriveTitle(item.body, item.id),
|
|
81
|
+
status: item.status,
|
|
82
|
+
created_at: item.created_at,
|
|
83
|
+
updated_at: item.updated_at,
|
|
84
|
+
provenance: item.provenance ?? [],
|
|
85
|
+
payload,
|
|
86
|
+
body: item.body,
|
|
87
|
+
});
|
|
123
88
|
}
|
|
124
89
|
|
|
125
90
|
/** Write an item to its canonical file. Returns the path. */
|
package/zuzuu/live/install.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const DENY_RULES = ['Read(./.zuzuu/.traces/**)', 'Read(./.zuzuu/.live/**)'];
|
|
|
13
13
|
|
|
14
14
|
// Minimal hook set: lifecycle (Design B re-captures the transcript — no
|
|
15
15
|
// PostToolUse needed) + the PreToolUse Guardrails GATE (the one place we *do*
|
|
16
|
-
// sit on the hot path: it evaluates .zuzuu/guardrails/rules
|
|
16
|
+
// sit on the hot path: it evaluates the .zuzuu/guardrails/items/ rules per tool call,
|
|
17
17
|
// fails open, and stays silent unless a rule matches).
|
|
18
18
|
export const LIFECYCLE_EVENTS = ['SessionStart', 'Stop', 'SessionEnd'];
|
|
19
19
|
export const GATE_EVENTS = ['PreToolUse'];
|
|
@@ -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
|
|