@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
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// zuzuu/instructions/adapter.mjs
|
|
2
|
-
// The Instructions faculty adapter (WS2-T4). Wraps steering-amendment proposals
|
|
3
|
-
// behind the faculty-spine adapter contract — { name, ingest, validate, apply,
|
|
4
|
-
// render } — so `zuzuu review` can surface and approve them uniformly.
|
|
5
|
-
//
|
|
6
|
-
// An instructions proposal payload is a steering amendment:
|
|
7
|
-
// { text } — a line or paragraph to append to project.md
|
|
8
|
-
//
|
|
9
|
-
// apply: appends the text as a line to .zuzuu/instructions/project.md (creates
|
|
10
|
-
// the file if absent; never duplicates an already-present line).
|
|
11
|
-
//
|
|
12
|
-
// Registers itself on import.
|
|
13
|
-
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
16
|
-
import * as registry from '../faculty/registry.mjs';
|
|
17
|
-
|
|
18
|
-
const name = 'instructions';
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// helpers
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
function projectMdPath(agentDir) {
|
|
25
|
-
return join(agentDir, 'instructions', 'project.md');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// adapter contract
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Ingest a raw amendment object. Pass-through: the payload IS the amendment.
|
|
34
|
-
*/
|
|
35
|
-
function ingest(_agentDir, raw) {
|
|
36
|
-
const payload = raw?.payload ?? raw ?? {};
|
|
37
|
-
return { payload, analysis: {} };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Validate an amendment payload.
|
|
42
|
-
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
43
|
-
*/
|
|
44
|
-
function validate(_agentDir, payload) {
|
|
45
|
-
const errors = [];
|
|
46
|
-
if (!payload?.text || !String(payload.text).trim()) {
|
|
47
|
-
errors.push('text is required (non-empty steering amendment)');
|
|
48
|
-
}
|
|
49
|
-
return { ok: errors.length === 0, errors, warnings: [] };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Apply an approved amendment: append text to project.md (idempotent on
|
|
54
|
-
* identical lines — won't duplicate a line already present).
|
|
55
|
-
* @returns {{ok:boolean, action:string, itemIds:string[]}}
|
|
56
|
-
*/
|
|
57
|
-
function apply(agentDir, proposal) {
|
|
58
|
-
const text = proposal?.payload?.text ?? '';
|
|
59
|
-
|
|
60
|
-
// Ensure the instructions dir exists
|
|
61
|
-
mkdirSync(join(agentDir, 'instructions'), { recursive: true });
|
|
62
|
-
|
|
63
|
-
const path = projectMdPath(agentDir);
|
|
64
|
-
const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
65
|
-
|
|
66
|
-
// Idempotence: skip if the exact text is already present
|
|
67
|
-
if (existing.includes(text)) {
|
|
68
|
-
return { ok: true, action: 'amended instructions (already present)', itemIds: [] };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Append (with trailing newline)
|
|
72
|
-
const separator = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
73
|
-
writeFileSync(path, existing + separator + text + '\n');
|
|
74
|
-
|
|
75
|
-
return { ok: true, action: 'amended instructions', itemIds: [] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Render an amendment proposal for the human gate.
|
|
80
|
-
* @returns {{line:string, card:string}}
|
|
81
|
-
*/
|
|
82
|
-
function render(proposal) {
|
|
83
|
-
const text = proposal?.payload?.text ?? '';
|
|
84
|
-
const preview = text.slice(0, 80).replace(/\n/g, ' ');
|
|
85
|
-
return {
|
|
86
|
-
line: `[amendment] ${preview}`,
|
|
87
|
-
card: text,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const adapter = { name, ingest, validate, apply, render };
|
|
92
|
-
|
|
93
|
-
registry.register(adapter);
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// zuzuu/knowledge/adapter.mjs
|
|
2
|
-
// The Knowledge faculty adapter (WS2-T2). Wraps the EXISTING Knowledge pipeline
|
|
3
|
-
// (proposals/ER/registry/items/index) behind the faculty-spine adapter contract
|
|
4
|
-
// — { name, ingest, validate, apply, render } — without changing any behaviour.
|
|
5
|
-
//
|
|
6
|
-
// ingest — run ER on a candidate, mirroring createProposal's analysis step
|
|
7
|
-
// validate — registry-based validation of an item
|
|
8
|
-
// apply — IS the extracted approve apply body (applyKnowledgeProposal)
|
|
9
|
-
// render — the human card the `zuzuu review` gate shows for a knowledge proposal
|
|
10
|
-
//
|
|
11
|
-
// Registers itself on import.
|
|
12
|
-
|
|
13
|
-
import { resolve as erResolve } from './er.mjs';
|
|
14
|
-
import { loadRegistry, validateItem } from './registry.mjs';
|
|
15
|
-
import { allItems, slugify } from './items.mjs';
|
|
16
|
-
import { applyKnowledgeProposal } from './proposals.mjs';
|
|
17
|
-
import * as registry from '../faculty/registry.mjs';
|
|
18
|
-
|
|
19
|
-
const name = 'knowledge';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Ingest a raw candidate: run ER against existing items and return the
|
|
23
|
-
* normalised payload + analysis. Mirrors what createProposal computes today.
|
|
24
|
-
* @param {string} agentDir
|
|
25
|
-
* @param {{candidate:object, source?:string, evidence?:object}} raw
|
|
26
|
-
*/
|
|
27
|
-
function ingest(agentDir, raw) {
|
|
28
|
-
const { items } = allItems(agentDir);
|
|
29
|
-
const candidate = { ...raw.candidate };
|
|
30
|
-
candidate.id = candidate.id || slugify(candidate.body);
|
|
31
|
-
const er = erResolve(candidate, items);
|
|
32
|
-
return { payload: candidate, analysis: { er }, dedupeKey: candidate.id };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Validate an item against the Knowledge registry.
|
|
37
|
-
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
38
|
-
*/
|
|
39
|
-
function validate(agentDir, payload) {
|
|
40
|
-
const reg = loadRegistry(agentDir);
|
|
41
|
-
const v = validateItem(reg, payload);
|
|
42
|
-
const warnings = [
|
|
43
|
-
...v.unknownKeys.attributes.map((k) => `unregistered attribute '${k}'`),
|
|
44
|
-
...v.unknownKeys.relations.map((t) => `unregistered relation type '${t}'`),
|
|
45
|
-
];
|
|
46
|
-
return { ok: v.ok, errors: v.errors, warnings };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Apply an approved proposal — delegates to the extracted approve apply body.
|
|
51
|
-
* @returns {{ok:boolean, action:string, itemIds:string[], warnings:string[]}}
|
|
52
|
-
*/
|
|
53
|
-
function apply(agentDir, proposal) {
|
|
54
|
-
// Bridge spine-shaped records (payload/analysis.er) onto applyKnowledgeProposal's
|
|
55
|
-
// legacy shape (candidate/er). Records that still carry candidate/er pass through.
|
|
56
|
-
const legacy = {
|
|
57
|
-
...proposal,
|
|
58
|
-
candidate: proposal.candidate ?? proposal.payload,
|
|
59
|
-
er: proposal.er ?? proposal.analysis?.er,
|
|
60
|
-
};
|
|
61
|
-
const r = applyKnowledgeProposal(agentDir, legacy);
|
|
62
|
-
return {
|
|
63
|
-
ok: r.ok,
|
|
64
|
-
action: r.action,
|
|
65
|
-
itemIds: r.item ? [r.item] : [],
|
|
66
|
-
warnings: r.warnings ?? [],
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Render a proposal for the human gate. `card` mirrors the multi-line summary
|
|
72
|
-
* `zuzuu review` shows for knowledge proposals (id, type, attrs/relations, ER
|
|
73
|
-
* verdict); `line` is the one-line list form (`zuzuu proposals list`).
|
|
74
|
-
* @returns {{line:string, card:string}}
|
|
75
|
-
*/
|
|
76
|
-
function render(proposal) {
|
|
77
|
-
if (proposal.kind === 'registry') {
|
|
78
|
-
const what = `register ${String(proposal.registry).slice(0, -1)} '${proposal.key}'`;
|
|
79
|
-
return {
|
|
80
|
-
line: `${proposal.id} [${proposal.kind}] ${what}`,
|
|
81
|
-
card: `${what} (seen ${proposal.evidence?.occurrences}× in candidates)`,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const c = proposal.candidate ?? {};
|
|
85
|
-
const er = proposal.er ?? {};
|
|
86
|
-
const lines = [];
|
|
87
|
-
lines.push(`${c.id ?? ''} ── ${c.type}: ${c.body?.slice(0, 100).replace(/\n/g, ' ')}`);
|
|
88
|
-
for (const [k, v] of Object.entries(c.attributes ?? {})) lines.push(` · ${k} = ${v}`);
|
|
89
|
-
for (const r of c.relations ?? []) lines.push(` → ${r.type} ${r.target}`);
|
|
90
|
-
lines.push(` er: ${er.verdict}${er.match ? ` → ${er.match}` : ''} (${(er.confidence ?? 0).toFixed(2)} · ${er.reason ?? ''})`);
|
|
91
|
-
return {
|
|
92
|
-
line: `${proposal.id} [${er.verdict ?? proposal.kind}] ${c.type}: ${c.body?.slice(0, 60).replace(/\n/g, ' ')}`,
|
|
93
|
-
card: lines.join('\n'),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export const adapter = { name, ingest, validate, apply, render };
|
|
98
|
-
|
|
99
|
-
registry.register(adapter);
|
package/zuzuu/memory/adapter.mjs
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
// zuzuu/memory/adapter.mjs
|
|
2
|
-
// The Memory faculty adapter (WS2-T4). Wraps episode proposals behind the
|
|
3
|
-
// faculty-spine adapter contract — { name, ingest, validate, apply, render } —
|
|
4
|
-
// so `zuzuu review` can surface and approve memory entries uniformly.
|
|
5
|
-
//
|
|
6
|
-
// A memory proposal payload is an episode record matching the WS1 Memory schema:
|
|
7
|
-
// { id, date, title, provenance, body }
|
|
8
|
-
// id format: mem-<YYYY-MM-DD>-<slug>
|
|
9
|
-
//
|
|
10
|
-
// apply: writes .zuzuu/memory/entries/<id>.md with YAML frontmatter (status: curated)
|
|
11
|
-
// and the body sections (Attempted / Resulted / Remember next time).
|
|
12
|
-
//
|
|
13
|
-
// Registers itself on import.
|
|
14
|
-
|
|
15
|
-
import { join } from 'node:path';
|
|
16
|
-
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
17
|
-
import * as registry from '../faculty/registry.mjs';
|
|
18
|
-
|
|
19
|
-
const name = 'memory';
|
|
20
|
-
|
|
21
|
-
// mem-<YYYY-MM-DD>-<slug>: the id must START with "mem-"
|
|
22
|
-
const MEM_ID_RE = /^mem-/;
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// helpers
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
function entriesDir(agentDir) {
|
|
29
|
-
return join(agentDir, 'memory', 'entries');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function entryPath(agentDir, id) {
|
|
33
|
-
return join(entriesDir(agentDir), `${id}.md`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Render YAML frontmatter block from the payload fields. */
|
|
37
|
-
function renderFrontmatter(payload) {
|
|
38
|
-
const lines = ['---'];
|
|
39
|
-
lines.push(`id: ${payload.id}`);
|
|
40
|
-
if (payload.date) lines.push(`date: ${payload.date}`);
|
|
41
|
-
if (payload.title) lines.push(`title: ${payload.title}`);
|
|
42
|
-
if (payload.provenance) {
|
|
43
|
-
lines.push('provenance:');
|
|
44
|
-
const p = payload.provenance;
|
|
45
|
-
if (Array.isArray(p.sessions)) lines.push(` sessions: [${p.sessions.join(', ')}]`);
|
|
46
|
-
if (Array.isArray(p.hosts)) lines.push(` hosts: [${p.hosts.join(', ')}]`);
|
|
47
|
-
}
|
|
48
|
-
if (Array.isArray(payload.tags) && payload.tags.length) {
|
|
49
|
-
lines.push(`tags: [${payload.tags.join(', ')}]`);
|
|
50
|
-
}
|
|
51
|
-
lines.push('status: curated');
|
|
52
|
-
lines.push('---');
|
|
53
|
-
return lines.join('\n');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// adapter contract
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Ingest a raw episode. Pass-through: the payload IS the episode.
|
|
62
|
-
*/
|
|
63
|
-
function ingest(_agentDir, raw) {
|
|
64
|
-
const payload = raw?.payload ?? raw ?? {};
|
|
65
|
-
return { payload, analysis: {}, dedupeKey: payload.id };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Validate an episode payload.
|
|
70
|
-
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
71
|
-
*/
|
|
72
|
-
function validate(_agentDir, payload) {
|
|
73
|
-
const errors = [];
|
|
74
|
-
if (!payload?.id || typeof payload.id !== 'string') {
|
|
75
|
-
errors.push('id is required');
|
|
76
|
-
} else if (!MEM_ID_RE.test(payload.id)) {
|
|
77
|
-
errors.push(`id must match mem-<YYYY-MM-DD>-<slug> format (got '${payload.id}')`);
|
|
78
|
-
}
|
|
79
|
-
if (!payload?.title || !String(payload.title).trim()) {
|
|
80
|
-
errors.push('title is required');
|
|
81
|
-
}
|
|
82
|
-
return { ok: errors.length === 0, errors, warnings: [] };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Apply an approved episode proposal: write the entry Markdown file.
|
|
87
|
-
* @returns {{ok:boolean, action:string, itemIds:string[]}}
|
|
88
|
-
*/
|
|
89
|
-
function apply(agentDir, proposal) {
|
|
90
|
-
const payload = proposal?.payload ?? {};
|
|
91
|
-
const id = payload.id;
|
|
92
|
-
|
|
93
|
-
mkdirSync(entriesDir(agentDir), { recursive: true });
|
|
94
|
-
|
|
95
|
-
const frontmatter = renderFrontmatter(payload);
|
|
96
|
-
const body = payload.body ?? '';
|
|
97
|
-
const content = frontmatter + '\n' + body + (body.endsWith('\n') ? '' : '\n');
|
|
98
|
-
|
|
99
|
-
writeFileSync(entryPath(agentDir, id), content);
|
|
100
|
-
|
|
101
|
-
return { ok: true, action: `wrote memory ${id}`, itemIds: [id] };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Render an episode proposal for the human gate.
|
|
106
|
-
* @returns {{line:string, card:string}}
|
|
107
|
-
*/
|
|
108
|
-
function render(proposal) {
|
|
109
|
-
const p = proposal?.payload ?? {};
|
|
110
|
-
const title = p.title ?? '';
|
|
111
|
-
const date = p.date ?? '';
|
|
112
|
-
const id = p.id ?? '';
|
|
113
|
-
return {
|
|
114
|
-
line: `${id} [episode] ${title} (${date})`,
|
|
115
|
-
card: `${title}\n id: ${id} date: ${date}`,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export const adapter = { name, ingest, validate, apply, render };
|
|
120
|
-
|
|
121
|
-
registry.register(adapter);
|
package/zuzuu/miners/actions.mjs
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
// zuzuu/miners/actions.mjs
|
|
2
|
-
// Actions miner (WS5-T2) — detect recurring Bash 2-gram sequences across
|
|
3
|
-
// sessions and scaffold runbook proposals into actions/inbox/<slug>/.
|
|
4
|
-
//
|
|
5
|
-
// Shape: { faculty:'actions', aggregate(sessions, opts), propose(agentDir, aggregated) }
|
|
6
|
-
// Self-registers on import.
|
|
7
|
-
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
10
|
-
import { slugify } from '../knowledge/items.mjs';
|
|
11
|
-
import { isSafeSlug, actionsDir, inboxDir } from '../actions/manifest.mjs';
|
|
12
|
-
import { register } from './registry.mjs';
|
|
13
|
-
|
|
14
|
-
// Must match the constant in knowledge/distill.mjs (adjacent Bash separator).
|
|
15
|
-
const SEQ_SEP = ' && ';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Derive a safe slug from a raw sequence string (bounded, safe chars only).
|
|
19
|
-
* e.g. "npm ci && npm test" → "npm-ci-npm-test" (max 50 chars).
|
|
20
|
-
*/
|
|
21
|
-
function slugFromSequence(seq) {
|
|
22
|
-
const raw = slugify(seq.replace(/ && /g, ' '), 50);
|
|
23
|
-
// slugify already returns safe chars [a-z0-9-]; isSafeSlug allows upper too,
|
|
24
|
-
// but we keep lower for readability. Force-safe just in case.
|
|
25
|
-
return raw || 'action-sequence';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Aggregate recurring Bash 2-gram sequences from mined sessions.
|
|
30
|
-
*
|
|
31
|
-
* @param {Array<{sessionId:string, sequences:string[]}>} sessions
|
|
32
|
-
* The per-session mineTranscript output array.
|
|
33
|
-
* @param {object} opts
|
|
34
|
-
* @param {number} [opts.minSeqCount=3] min total occurrences across all sessions
|
|
35
|
-
* @param {number} [opts.minSeqSessions=2] min distinct sessions the sequence appears in
|
|
36
|
-
* @returns {Array<{payload:{slug,title,steps,promptSnippet,sequence}, evidence:{occurrences,sessions,sequence}}>}
|
|
37
|
-
*/
|
|
38
|
-
export function aggregate(sessions, { minSeqCount = 3, minSeqSessions = 2 } = {}) {
|
|
39
|
-
// Count occurrences per sequence string, tracking distinct session ids.
|
|
40
|
-
const stats = new Map(); // rawSeq → { count, sessions: Set<sessionId> }
|
|
41
|
-
for (const s of sessions) {
|
|
42
|
-
if (!Array.isArray(s.sequences)) continue;
|
|
43
|
-
for (const seq of s.sequences) {
|
|
44
|
-
const st = stats.get(seq) ?? { count: 0, sessions: new Set() };
|
|
45
|
-
st.count++;
|
|
46
|
-
st.sessions.add(s.sessionId);
|
|
47
|
-
stats.set(seq, st);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const candidates = [];
|
|
52
|
-
for (const [seq, st] of stats) {
|
|
53
|
-
if (st.count < minSeqCount || st.sessions.size < minSeqSessions) continue;
|
|
54
|
-
const steps = seq.split(SEQ_SEP);
|
|
55
|
-
const slug = slugFromSequence(seq);
|
|
56
|
-
// Make sure the slug is safe; if not, skip rather than emit a bad slug.
|
|
57
|
-
if (!isSafeSlug(slug)) continue;
|
|
58
|
-
const title = `Run sequence: ${steps.join(' → ')}`;
|
|
59
|
-
const promptSnippet = `Runs: ${steps.join(' then ')}`;
|
|
60
|
-
candidates.push({
|
|
61
|
-
payload: { slug, title, steps, promptSnippet, sequence: seq },
|
|
62
|
-
evidence: { occurrences: st.count, sessions: st.sessions.size, sequence: seq },
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return candidates;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Write a runbook action proposal into actions/inbox/<slug>/ for each candidate.
|
|
70
|
-
* Idempotent: skips if inbox/<slug>/ OR active actions/<slug>/ already exists.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} agentDir
|
|
73
|
-
* @param {ReturnType<typeof aggregate>} aggregated
|
|
74
|
-
* @returns {number} count of new proposals written
|
|
75
|
-
*/
|
|
76
|
-
export function propose(agentDir, aggregated) {
|
|
77
|
-
const actDir = actionsDir(agentDir);
|
|
78
|
-
const ibDir = inboxDir(agentDir);
|
|
79
|
-
let count = 0;
|
|
80
|
-
for (const c of aggregated) {
|
|
81
|
-
const { slug, title, steps, promptSnippet } = c.payload;
|
|
82
|
-
const inboxSlug = join(ibDir, slug);
|
|
83
|
-
const activeSlug = join(actDir, slug);
|
|
84
|
-
// Idempotent: skip if already proposed or already active.
|
|
85
|
-
if (existsSync(inboxSlug) || existsSync(activeSlug)) continue;
|
|
86
|
-
|
|
87
|
-
mkdirSync(inboxSlug, { recursive: true });
|
|
88
|
-
|
|
89
|
-
// action.json — minimal manifest (no run.mjs; this is a runbook action).
|
|
90
|
-
const manifest = {
|
|
91
|
-
slug,
|
|
92
|
-
title,
|
|
93
|
-
description: `Recurring command sequence detected from session traces: ${steps.join(' → ')}.`,
|
|
94
|
-
promptSnippet,
|
|
95
|
-
};
|
|
96
|
-
writeFileSync(join(inboxSlug, 'action.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
97
|
-
|
|
98
|
-
// SKILL.md — numbered runbook steps.
|
|
99
|
-
const stepsBlock = steps.map((cmd, i) => `${i + 1}. \`${cmd}\``).join('\n');
|
|
100
|
-
const skillMd = `---
|
|
101
|
-
name: ${title}
|
|
102
|
-
description: ${manifest.description}
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Steps
|
|
106
|
-
|
|
107
|
-
${stepsBlock}
|
|
108
|
-
`;
|
|
109
|
-
writeFileSync(join(inboxSlug, 'SKILL.md'), skillMd);
|
|
110
|
-
|
|
111
|
-
count++;
|
|
112
|
-
}
|
|
113
|
-
return count;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export const miner = { faculty: 'actions', aggregate, propose };
|
|
117
|
-
|
|
118
|
-
register(miner);
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
// zuzuu/miners/guardrails.mjs
|
|
2
|
-
// Guardrails miner (WS5-T3) — detect repeated destructive-command failures
|
|
3
|
-
// across sessions and propose ask-only guardrail rules.
|
|
4
|
-
//
|
|
5
|
-
// MANDATORY SAFETY PROPERTIES (enforced in aggregate):
|
|
6
|
-
// 1. action is ALWAYS 'ask' — never 'deny'. Auto-proposed rules only escalate
|
|
7
|
-
// to the human prompt, they never hard-block.
|
|
8
|
-
// 2. Patterns are LITERAL-ESCAPED from the observed command — never a broad/
|
|
9
|
-
// free regex. escapeRegex() handles this.
|
|
10
|
-
// 3. Cross-session corroboration required — a destructive command must fail
|
|
11
|
-
// ≥minFailures (default 3) times across ≥minSessions (default 2) DISTINCT
|
|
12
|
-
// sessions. A single session — no matter how many failures — produces
|
|
13
|
-
// NOTHING.
|
|
14
|
-
//
|
|
15
|
-
// Shape: { faculty:'guardrails', aggregate(sessions, opts), propose(agentDir, aggregated) }
|
|
16
|
-
// Self-registers on import.
|
|
17
|
-
|
|
18
|
-
import { join } from 'node:path';
|
|
19
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
20
|
-
import { slugify } from '../knowledge/items.mjs';
|
|
21
|
-
import { makeProposal, writeProposal, listProposals, isArchivedResolved } from '../faculty/proposal.mjs';
|
|
22
|
-
import { register } from './registry.mjs';
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// escapeRegex — the ONLY safe way to build a pattern from a literal command.
|
|
26
|
-
// Escapes all RegExp metacharacters so the pattern matches the exact string.
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Escape all regex metacharacters in `s` so that `new RegExp(escapeRegex(s))`
|
|
30
|
-
* matches exactly the string `s` and nothing broader.
|
|
31
|
-
*
|
|
32
|
-
* @param {string} s
|
|
33
|
-
* @returns {string}
|
|
34
|
-
*/
|
|
35
|
-
export function escapeRegex(s) {
|
|
36
|
-
// Standard set of regex metacharacters that need escaping.
|
|
37
|
-
return String(s).replace(/[.*+?^${}()|[\]\\\/\-]/g, '\\$&');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// helpers
|
|
42
|
-
|
|
43
|
-
/** Normalise a command string (trim + collapse whitespace). */
|
|
44
|
-
const norm = (cmd) => String(cmd).trim().replace(/\s+/g, ' ').slice(0, 200);
|
|
45
|
-
|
|
46
|
-
/** Derive a guardrails-miner id for a command. */
|
|
47
|
-
function guardId(cmd) {
|
|
48
|
-
return 'guard-' + slugify(cmd, 50);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Load rules.json; returns { version, rules:[] } if absent/unreadable. */
|
|
52
|
-
function loadRules(agentDir) {
|
|
53
|
-
const path = join(agentDir, 'guardrails', 'rules.json');
|
|
54
|
-
if (!existsSync(path)) return { version: 1, rules: [] };
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
57
|
-
} catch {
|
|
58
|
-
return { version: 1, rules: [] };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// aggregate
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Group destructiveFailures by normalised command; emit a candidate ONLY when
|
|
67
|
-
* both the occurrence count and distinct-session count meet their thresholds.
|
|
68
|
-
*
|
|
69
|
-
* SAFETY: a single-session cluster, no matter how large, produces NOTHING.
|
|
70
|
-
*
|
|
71
|
-
* @param {Array<{sessionId:string, destructiveFailures:{cmd:string,tool:string}[]}>} sessions
|
|
72
|
-
* @param {object} opts
|
|
73
|
-
* @param {number} [opts.minFailures=3] min total failures across all sessions
|
|
74
|
-
* @param {number} [opts.minSessions=2] min distinct sessions with ≥1 failure each
|
|
75
|
-
* @returns {Array<{payload:{id,action,tool,pattern,reason}, evidence:{occurrences,sessions}}>}
|
|
76
|
-
*/
|
|
77
|
-
export function aggregate(sessions, { minFailures = 3, minSessions = 2 } = {}) {
|
|
78
|
-
// cmd (normalized) → { count: number, sessions: Set<sessionId>, tool: string }
|
|
79
|
-
const stats = new Map();
|
|
80
|
-
|
|
81
|
-
for (const s of sessions) {
|
|
82
|
-
if (!Array.isArray(s.destructiveFailures)) continue;
|
|
83
|
-
for (const { cmd, tool } of s.destructiveFailures) {
|
|
84
|
-
const key = norm(cmd);
|
|
85
|
-
const st = stats.get(key) ?? { count: 0, sessions: new Set(), tool: tool ?? 'Bash' };
|
|
86
|
-
st.count++;
|
|
87
|
-
st.sessions.add(s.sessionId);
|
|
88
|
-
// Keep first observed tool name (they should all be 'Bash' for destructive cmds).
|
|
89
|
-
stats.set(key, st);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const candidates = [];
|
|
94
|
-
for (const [cmd, st] of stats) {
|
|
95
|
-
// SAFETY: enforce BOTH thresholds — cross-session gate is the key one.
|
|
96
|
-
if (st.count < minFailures) continue;
|
|
97
|
-
if (st.sessions.size < minSessions) continue; // ← single-session always rejected here
|
|
98
|
-
|
|
99
|
-
const id = guardId(cmd);
|
|
100
|
-
const pattern = escapeRegex(cmd);
|
|
101
|
-
const tool = st.tool ?? 'Bash';
|
|
102
|
-
|
|
103
|
-
candidates.push({
|
|
104
|
-
payload: {
|
|
105
|
-
id,
|
|
106
|
-
// SAFETY: ALWAYS 'ask', never 'deny'.
|
|
107
|
-
action: 'ask',
|
|
108
|
-
tool,
|
|
109
|
-
pattern,
|
|
110
|
-
reason: `auto-proposed: '${cmd}' failed repeatedly across sessions — confirm before running`,
|
|
111
|
-
},
|
|
112
|
-
evidence: {
|
|
113
|
-
occurrences: st.count,
|
|
114
|
-
sessions: st.sessions.size,
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return candidates;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// propose
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Write a guardrails proposal into .zuzuu/guardrails/proposals/ for each candidate.
|
|
127
|
-
* Idempotent:
|
|
128
|
-
* - skips if a guardrails proposal with the same payload.id already exists
|
|
129
|
-
* - skips if rules.json already has a rule with that id
|
|
130
|
-
* - skips if the id is already resolved in proposals/archive/ — a rejection
|
|
131
|
-
* is remembered; re-distilling never resurrects it
|
|
132
|
-
*
|
|
133
|
-
* The proposals flow through `zuzuu review` → guardrails adapter on approval.
|
|
134
|
-
*
|
|
135
|
-
* @param {string} agentDir
|
|
136
|
-
* @param {ReturnType<typeof aggregate>} aggregated
|
|
137
|
-
* @returns {number} count of new proposals written
|
|
138
|
-
*/
|
|
139
|
-
export function propose(agentDir, aggregated) {
|
|
140
|
-
// Load existing proposals (ids already pending).
|
|
141
|
-
const existing = listProposals(agentDir, 'guardrails');
|
|
142
|
-
const existingIds = new Set(existing.map((p) => p.payload?.id).filter(Boolean));
|
|
143
|
-
|
|
144
|
-
// Load existing rules (ids already applied).
|
|
145
|
-
const rulesData = loadRules(agentDir);
|
|
146
|
-
const rulesIds = new Set((rulesData.rules ?? []).map((r) => r.id).filter(Boolean));
|
|
147
|
-
|
|
148
|
-
let count = 0;
|
|
149
|
-
for (const c of aggregated) {
|
|
150
|
-
const { payload, evidence } = c;
|
|
151
|
-
|
|
152
|
-
// Idempotent: skip if already proposed or already a live rule.
|
|
153
|
-
if (existingIds.has(payload.id)) continue;
|
|
154
|
-
if (rulesIds.has(payload.id)) continue;
|
|
155
|
-
|
|
156
|
-
const proposal = makeProposal({
|
|
157
|
-
faculty: 'guardrails',
|
|
158
|
-
kind: 'rule',
|
|
159
|
-
source: 'distill',
|
|
160
|
-
payload,
|
|
161
|
-
evidence,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// A rejection is remembered: never resurrect an archive-resolved id.
|
|
165
|
-
if (isArchivedResolved(agentDir, 'guardrails', proposal.id)) continue;
|
|
166
|
-
|
|
167
|
-
writeProposal(agentDir, proposal);
|
|
168
|
-
count++;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return count;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// self-register
|
|
176
|
-
|
|
177
|
-
export const miner = { faculty: 'guardrails', aggregate, propose };
|
|
178
|
-
|
|
179
|
-
register(miner);
|