@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// zuzuu/digest/compose.mjs
|
|
2
|
+
// The grounding digest — a pure, deterministic, zero-network, no-model brief of
|
|
3
|
+
// the faculty home, injected at session start. Returns { text, sections }.
|
|
4
|
+
// I/O-free: callers (the CLI + the SessionStart hook) handle output.
|
|
5
|
+
//
|
|
6
|
+
// Composition (the Faculty Module contract): each built-in module exports its
|
|
7
|
+
// own digestSection(agentDir, ctx); this file iterates the registry and stacks
|
|
8
|
+
// the sections in the canonical order — instructions → knowledge → actions →
|
|
9
|
+
// proposals (spine-level) → guardrails — then a default "N item(s)" section
|
|
10
|
+
// for every DECLARATIVE faculty. Every hook call rides registry.invoke
|
|
11
|
+
// (fail-soft): a single broken faculty never sinks the whole digest.
|
|
12
|
+
|
|
13
|
+
import { listProposals } from '../knowledge/proposals.mjs';
|
|
14
|
+
import { listFacultyItems } from '../faculty/items.mjs';
|
|
15
|
+
import { facultiesOf, invoke } from '../faculty/registry.mjs';
|
|
16
|
+
|
|
17
|
+
// The canonical section order (instructions/knowledge/actions render above the
|
|
18
|
+
// proposals block; guardrails closes the brief — preserved pre-module layout).
|
|
19
|
+
const HEAD_SECTIONS = ['instructions', 'knowledge', 'actions'];
|
|
20
|
+
const TAIL_SECTIONS = ['guardrails'];
|
|
21
|
+
|
|
22
|
+
function proposalsSection(agentDir) {
|
|
23
|
+
try {
|
|
24
|
+
// count only pending — defensive if listProposals ever returns archived too
|
|
25
|
+
const pending = listProposals(agentDir).filter((p) => p.status === 'pending');
|
|
26
|
+
return { pending: pending.length };
|
|
27
|
+
} catch {
|
|
28
|
+
return { pending: 0 };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Run one faculty's digestSection hook fail-soft; null = no section. */
|
|
33
|
+
function sectionOf(entry, agentDir, ctx) {
|
|
34
|
+
const r = invoke(entry, 'digestSection', agentDir, ctx);
|
|
35
|
+
if (!r.ok || !r.value || !Array.isArray(r.value.lines)) return null;
|
|
36
|
+
return r.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The default section a faculty WITHOUT a digest hook gets: "N item(s)". */
|
|
40
|
+
function defaultSection(agentDir, entry) {
|
|
41
|
+
let count = 0;
|
|
42
|
+
try {
|
|
43
|
+
count = listFacultyItems(agentDir, entry.id, { itemsDir: entry.manifest?.itemsDir }).items.length;
|
|
44
|
+
} catch { /* unreadable → 0 */ }
|
|
45
|
+
return { lines: [`## ${entry.manifest?.title ?? entry.id}`, `${count} item(s)`], data: { count } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compute the digest for a faculty home.
|
|
50
|
+
* @param {string} agentDir path to the .zuzuu/ directory
|
|
51
|
+
* @param {{ knowledgeLimit?: number, budget?: number }} options
|
|
52
|
+
* @returns {{ text: string, sections: object }}
|
|
53
|
+
*/
|
|
54
|
+
export function computeDigest(agentDir, { knowledgeLimit = 5, budget = 1500 } = {}) {
|
|
55
|
+
const charBudget = budget * 4;
|
|
56
|
+
const sections = {};
|
|
57
|
+
const lines = ['# zuzuu faculty digest', ''];
|
|
58
|
+
|
|
59
|
+
const faculties = facultiesOf(agentDir);
|
|
60
|
+
const byId = new Map(faculties.map((f) => [f.id, f]));
|
|
61
|
+
const ctx = () => ({ limit: knowledgeLimit, charBudget, priorLines: lines });
|
|
62
|
+
|
|
63
|
+
for (const id of HEAD_SECTIONS) {
|
|
64
|
+
const s = sectionOf(byId.get(id), agentDir, ctx());
|
|
65
|
+
if (!s) continue;
|
|
66
|
+
sections[id] = s.data;
|
|
67
|
+
if (s.lines.length) lines.push(...s.lines, '');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Proposals — spine-level (cross-faculty pending count lives with the gate).
|
|
71
|
+
const proposals = proposalsSection(agentDir);
|
|
72
|
+
sections.proposals = proposals;
|
|
73
|
+
if (proposals.pending > 0) {
|
|
74
|
+
lines.push('## Proposals');
|
|
75
|
+
lines.push(`${proposals.pending} proposal(s) await your approval — run \`zuzuu review\`; approving mints a generation (your checkpoint).`);
|
|
76
|
+
lines.push('');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const id of TAIL_SECTIONS) {
|
|
80
|
+
const s = sectionOf(byId.get(id), agentDir, ctx());
|
|
81
|
+
if (!s) continue;
|
|
82
|
+
sections[id] = s.data;
|
|
83
|
+
if (s.lines.length) lines.push(...s.lines, '');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Declarative faculties (manifest-only): the default "N item(s)" line each —
|
|
87
|
+
// a faculty you drop into the home is mentioned in the very next brief.
|
|
88
|
+
for (const entry of faculties) {
|
|
89
|
+
if (!entry.declarative || entry.manifestError) continue;
|
|
90
|
+
const s = sectionOf(entry, agentDir, ctx()) ?? defaultSection(agentDir, entry);
|
|
91
|
+
sections[entry.id] = s.data;
|
|
92
|
+
if (s.lines.length) lines.push(...s.lines, '');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { text: lines.join('\n').trimEnd() + '\n', sections };
|
|
96
|
+
}
|
package/zuzuu/eval/score.mjs
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
// zuzuu/eval/score.mjs
|
|
2
2
|
// Mechanical scorer — weighted sum of normalized signals → { score, confidence, rationale, signals }.
|
|
3
3
|
// Pure; deterministic; no FS, no Date.now(), no Math.random().
|
|
4
|
+
//
|
|
5
|
+
// Faculty Module hook: a module may export evalSignals(proposal) → PARTIAL
|
|
6
|
+
// signals; those overlay the mechanical extraction (fail-soft via the
|
|
7
|
+
// registry's invoke — a broken hook leaves the default scorer untouched).
|
|
8
|
+
// No built-in implements it today, so behavior is unchanged.
|
|
4
9
|
|
|
5
10
|
import { extractSignals } from './signals.mjs';
|
|
11
|
+
import { BUILTIN_MODULES, invoke } from '../faculty/registry.mjs';
|
|
6
12
|
|
|
7
13
|
// Weight vector (must sum to 1.0).
|
|
8
14
|
const W = {
|
|
@@ -60,7 +66,14 @@ function buildRationale(s) {
|
|
|
60
66
|
* @returns {{ score: number, confidence: string, rationale: string, signals: object }}
|
|
61
67
|
*/
|
|
62
68
|
export function mechanicalScore(proposal, opts = {}) {
|
|
63
|
-
|
|
69
|
+
let s = extractSignals(proposal, opts);
|
|
70
|
+
|
|
71
|
+
// Optional per-faculty evalSignals hook — partial overlay, fail-soft.
|
|
72
|
+
const mod = BUILTIN_MODULES[proposal?.faculty];
|
|
73
|
+
if (mod && typeof mod.evalSignals === 'function') {
|
|
74
|
+
const r = invoke({ id: proposal.faculty, module: mod }, 'evalSignals', proposal);
|
|
75
|
+
if (r.ok && r.value && typeof r.value === 'object') s = { ...s, ...r.value };
|
|
76
|
+
}
|
|
64
77
|
|
|
65
78
|
const raw =
|
|
66
79
|
W.occurrence * s.occurrence +
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// zuzuu/faculties/actions/index.mjs — the Actions faculty module.
|
|
2
|
+
//
|
|
3
|
+
// Consolidates the adapter (the inbox gate over dir-shaped proposals, WS2-T3),
|
|
4
|
+
// the miner (recurring Bash 2-gram sequences → runbook proposals, WS5-T2) and
|
|
5
|
+
// the digest section behind the Faculty Module contract. Substrate code stays
|
|
6
|
+
// in zuzuu/actions/ — this module is the contract face.
|
|
7
|
+
//
|
|
8
|
+
// Actions payloads are DIRECTORIES (ACTION.md + sibling scripts), not JSON.
|
|
9
|
+
// Strategy (lowest-risk): the inbox stays a dir; this adapter emits/reads a
|
|
10
|
+
// spine-shaped proposal RECORD that REFERENCES the dir
|
|
11
|
+
// (payload = { slug, kind, dir:'inbox/<slug>' }). The gate resolves a single
|
|
12
|
+
// record via `getProposal`, lists pending via `listProposals`, and — because
|
|
13
|
+
// the payload is dir-shaped — archives rejections via `rejectDir` (a dir move
|
|
14
|
+
// into actions/proposals/archive/, not a JSON archive).
|
|
15
|
+
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { listActions, allActions, inboxDir, actionsDir, isSafeSlug } from '../../actions/manifest.mjs';
|
|
19
|
+
import { activateAction, rejectAction } from '../../actions/inbox.mjs';
|
|
20
|
+
import { parseEnvelope, validateEnvelope, serializeEnvelope, PAYLOAD_SCHEMAS } from '../../faculty/envelope.mjs';
|
|
21
|
+
import { slugify } from '../../knowledge/items.mjs';
|
|
22
|
+
|
|
23
|
+
const name = 'actions';
|
|
24
|
+
|
|
25
|
+
export const manifest = {
|
|
26
|
+
id: 'actions',
|
|
27
|
+
title: 'Actions',
|
|
28
|
+
tagline: 'how to DO things — runbooks + runnable scripts',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
contract: 1,
|
|
31
|
+
kinds: ['runbook', 'script'],
|
|
32
|
+
itemsDir: '.',
|
|
33
|
+
schema: 'schema.json',
|
|
34
|
+
hooks: { miner: true, digest: true, eval: false, gate: false },
|
|
35
|
+
ui: { icon: 'play', accent: 'success', teaching: 'Reusable runbooks and scripts, mined from how you actually work and approved by you.' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// adapter contract
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/** Build a spine-shaped proposal record for one proposed action. */
|
|
43
|
+
function recordFor(a) {
|
|
44
|
+
return {
|
|
45
|
+
id: a.slug,
|
|
46
|
+
faculty: name,
|
|
47
|
+
kind: 'action',
|
|
48
|
+
status: 'pending',
|
|
49
|
+
source: 'agent',
|
|
50
|
+
payload: { slug: a.slug, kind: a.kind, dir: `inbox/${a.slug}` },
|
|
51
|
+
// carry render hints alongside the payload (cheap, dir read already done)
|
|
52
|
+
title: a.title,
|
|
53
|
+
promptSnippet: a.promptSnippet,
|
|
54
|
+
analysis: {},
|
|
55
|
+
evidence: {},
|
|
56
|
+
provenance: [],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Pending action proposals (dirs in .zuzuu/actions/inbox/), surfaced as
|
|
62
|
+
* spine-shaped records so the gate can render/approve/reject them uniformly.
|
|
63
|
+
*/
|
|
64
|
+
function listProposals(agentDir) {
|
|
65
|
+
return listActions(inboxDir(agentDir)).map(recordFor);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Resolve a single proposed action by slug → spine-shaped record, or null. */
|
|
69
|
+
function getProposal(agentDir, slug) {
|
|
70
|
+
if (!isSafeSlug(slug)) return null;
|
|
71
|
+
return listProposals(agentDir).find((p) => p.id === slug) ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Ingest is a pass-through for Actions: proposing scaffolds a dir
|
|
76
|
+
* (zuzuu act propose / act-author). Kept for adapter-contract symmetry.
|
|
77
|
+
*/
|
|
78
|
+
function ingest(_agentDir, raw) {
|
|
79
|
+
return { payload: raw?.payload ?? raw ?? {}, analysis: {} };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate a proposed action's ACTION.md envelope (id matches the dir; the
|
|
84
|
+
* payload validates against the actions schema). Missing ACTION.md → accept
|
|
85
|
+
* (slug fallback, mirrors the historical missing-manifest tolerance).
|
|
86
|
+
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
87
|
+
*/
|
|
88
|
+
export function validate(agentDir, payload) {
|
|
89
|
+
const slug = payload?.slug;
|
|
90
|
+
if (!isSafeSlug(slug)) return { ok: false, errors: [`invalid slug '${slug}'`], warnings: [] };
|
|
91
|
+
const manPath = join(inboxDir(agentDir), slug, 'ACTION.md');
|
|
92
|
+
if (!existsSync(manPath)) return { ok: true, errors: [], warnings: [] };
|
|
93
|
+
const { ok, item, errors: parseErrors } = parseEnvelope(readFileSync(manPath, 'utf8'));
|
|
94
|
+
if (!ok) return { ok: false, errors: [`ACTION.md is not a valid envelope: ${parseErrors[0]}`], warnings: [] };
|
|
95
|
+
if (item.id && item.id !== slug) return { ok: false, errors: [`ACTION.md id '${item.id}' ≠ dir '${slug}'`], warnings: [] };
|
|
96
|
+
if (item.faculty !== 'actions') return { ok: false, errors: [`ACTION.md faculty must be 'actions' (got '${item.faculty}')`], warnings: [] };
|
|
97
|
+
const v = validateEnvelope(item, PAYLOAD_SCHEMAS.actions);
|
|
98
|
+
return { ok: v.ok, errors: v.errors, warnings: [] };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Apply an approved action proposal: activate it (move inbox/<slug> → <slug>).
|
|
103
|
+
* Preserves the "already exists" guard from activateAction.
|
|
104
|
+
* @returns {{ok:boolean, action:string, itemIds:string[], warnings:string[]}}
|
|
105
|
+
*/
|
|
106
|
+
function apply(agentDir, proposal) {
|
|
107
|
+
const slug = proposal?.payload?.slug ?? proposal?.id;
|
|
108
|
+
const r = activateAction(agentDir, slug);
|
|
109
|
+
if (!r.ok) return { ok: false, action: r.error, itemIds: [], warnings: [] };
|
|
110
|
+
return { ok: true, action: `activated ${slug}`, itemIds: [slug], warnings: [] };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const applyProposal = apply;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Reject path: dir-shaped, so the gate calls this instead of the JSON archive.
|
|
117
|
+
* Moves inbox/<slug> → actions/proposals/archive/<slug> (archive, not delete).
|
|
118
|
+
*/
|
|
119
|
+
function rejectDir(agentDir, slug, _reason = '') {
|
|
120
|
+
return rejectAction(agentDir, slug);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Render a proposed action for the human gate. `card` mirrors the current review
|
|
125
|
+
* card (slug ── kind, then the prompt snippet); `line` is the one-line list form.
|
|
126
|
+
* @returns {{line:string, card:string}}
|
|
127
|
+
*/
|
|
128
|
+
function render(proposal) {
|
|
129
|
+
const slug = proposal?.id ?? proposal?.payload?.slug ?? '';
|
|
130
|
+
const kind = proposal?.payload?.kind ?? proposal?.kind ?? 'action';
|
|
131
|
+
const snippet = proposal?.promptSnippet ?? '';
|
|
132
|
+
return {
|
|
133
|
+
line: `${slug} [${kind}] ${snippet}`,
|
|
134
|
+
card: `${slug} ── ${kind}\n ${snippet}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const adapter = { name, ingest, validate, apply, render, listProposals, getProposal, rejectDir };
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// miner (WS5-T2 — recurring Bash 2-grams → runbook proposals, unchanged)
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
// Must match the constant in knowledge/distill.mjs (adjacent Bash separator).
|
|
145
|
+
const SEQ_SEP = ' && ';
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Derive a safe slug from a raw sequence string (bounded, safe chars only).
|
|
149
|
+
* e.g. "npm ci && npm test" → "npm-ci-npm-test" (max 50 chars).
|
|
150
|
+
*/
|
|
151
|
+
function slugFromSequence(seq) {
|
|
152
|
+
const raw = slugify(seq.replace(/ && /g, ' '), 50);
|
|
153
|
+
// slugify already returns safe chars [a-z0-9-]; isSafeSlug allows upper too,
|
|
154
|
+
// but we keep lower for readability. Force-safe just in case.
|
|
155
|
+
return raw || 'action-sequence';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Aggregate recurring Bash 2-gram sequences from mined sessions.
|
|
160
|
+
*
|
|
161
|
+
* @param {Array<{sessionId:string, sequences:string[]}>} sessions
|
|
162
|
+
* The per-session mineTranscript output array.
|
|
163
|
+
* @param {object} opts
|
|
164
|
+
* @param {number} [opts.minSeqCount=3] min total occurrences across all sessions
|
|
165
|
+
* @param {number} [opts.minSeqSessions=2] min distinct sessions the sequence appears in
|
|
166
|
+
* @returns {Array<{payload:{slug,title,steps,promptSnippet,sequence}, evidence:{occurrences,sessions,sequence}}>}
|
|
167
|
+
*/
|
|
168
|
+
export function aggregate(sessions, { minSeqCount = 3, minSeqSessions = 2 } = {}) {
|
|
169
|
+
// Count occurrences per sequence string, tracking distinct session ids.
|
|
170
|
+
const stats = new Map(); // rawSeq → { count, sessions: Set<sessionId> }
|
|
171
|
+
for (const s of sessions) {
|
|
172
|
+
if (!Array.isArray(s.sequences)) continue;
|
|
173
|
+
for (const seq of s.sequences) {
|
|
174
|
+
const st = stats.get(seq) ?? { count: 0, sessions: new Set() };
|
|
175
|
+
st.count++;
|
|
176
|
+
st.sessions.add(s.sessionId);
|
|
177
|
+
stats.set(seq, st);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const candidates = [];
|
|
182
|
+
for (const [seq, st] of stats) {
|
|
183
|
+
if (st.count < minSeqCount || st.sessions.size < minSeqSessions) continue;
|
|
184
|
+
const steps = seq.split(SEQ_SEP);
|
|
185
|
+
const slug = slugFromSequence(seq);
|
|
186
|
+
// Make sure the slug is safe; if not, skip rather than emit a bad slug.
|
|
187
|
+
if (!isSafeSlug(slug)) continue;
|
|
188
|
+
const title = `Run sequence: ${steps.join(' → ')}`;
|
|
189
|
+
const promptSnippet = `Runs: ${steps.join(' then ')}`;
|
|
190
|
+
candidates.push({
|
|
191
|
+
payload: { slug, title, steps, promptSnippet, sequence: seq },
|
|
192
|
+
evidence: { occurrences: st.count, sessions: st.sessions.size, sequence: seq },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return candidates;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Write a runbook action proposal into actions/inbox/<slug>/ for each candidate.
|
|
200
|
+
* Idempotent: skips if inbox/<slug>/ OR active actions/<slug>/ already exists.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} agentDir
|
|
203
|
+
* @param {ReturnType<typeof aggregate>} aggregated
|
|
204
|
+
* @returns {number} count of new proposals written
|
|
205
|
+
*/
|
|
206
|
+
export function propose(agentDir, aggregated) {
|
|
207
|
+
const actDir = actionsDir(agentDir);
|
|
208
|
+
const ibDir = inboxDir(agentDir);
|
|
209
|
+
let count = 0;
|
|
210
|
+
for (const c of aggregated) {
|
|
211
|
+
const { slug, title, steps, promptSnippet } = c.payload;
|
|
212
|
+
const inboxSlug = join(ibDir, slug);
|
|
213
|
+
const activeSlug = join(actDir, slug);
|
|
214
|
+
// Idempotent: skip if already proposed or already active.
|
|
215
|
+
if (existsSync(inboxSlug) || existsSync(activeSlug)) continue;
|
|
216
|
+
|
|
217
|
+
mkdirSync(inboxSlug, { recursive: true });
|
|
218
|
+
|
|
219
|
+
// ACTION.md — a runbook envelope (no run.mjs; the body IS the procedure).
|
|
220
|
+
// The body's first line is the digest one-liner (promptSnippet).
|
|
221
|
+
const stepsBlock = steps.map((cmd, i) => `${i + 1}. \`${cmd}\``).join('\n');
|
|
222
|
+
writeFileSync(join(inboxSlug, 'ACTION.md'), serializeEnvelope({
|
|
223
|
+
id: slug,
|
|
224
|
+
faculty: 'actions',
|
|
225
|
+
kind: 'runbook',
|
|
226
|
+
title,
|
|
227
|
+
status: 'active',
|
|
228
|
+
created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
|
|
229
|
+
provenance: [],
|
|
230
|
+
payload: {},
|
|
231
|
+
body: `${promptSnippet}\n\nRecurring command sequence detected from session traces.\n\n## Steps\n\n${stepsBlock}`,
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
count++;
|
|
235
|
+
}
|
|
236
|
+
return count;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export const miner = { faculty: name, aggregate, propose };
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// digest section (moved from the pre-module digest, byte-identical output)
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The Actions digest section: available actions under the shared char budget.
|
|
247
|
+
* Renders NOTHING when no actions exist (preserved pre-module behaviour).
|
|
248
|
+
* @param {string} agentDir
|
|
249
|
+
* @param {{limit:number, charBudget:number, priorLines:string[]}} ctx
|
|
250
|
+
* @returns {{lines: string[], data: object}}
|
|
251
|
+
*/
|
|
252
|
+
export function digestSection(agentDir, { limit, charBudget, priorLines }) {
|
|
253
|
+
let actions;
|
|
254
|
+
try {
|
|
255
|
+
const list = allActions(agentDir);
|
|
256
|
+
actions = { count: list.length, shown: list.slice(0, limit).map((a) => ({ slug: a.slug, kind: a.kind, promptSnippet: a.promptSnippet })) };
|
|
257
|
+
} catch {
|
|
258
|
+
actions = { count: 0, shown: [] };
|
|
259
|
+
}
|
|
260
|
+
if (!actions.count) return { lines: [], data: { ...actions, renderedCount: 0 } };
|
|
261
|
+
const lines = ['## Actions'];
|
|
262
|
+
lines.push(`${actions.count} available; run with \`zuzuu act <slug>\`:`);
|
|
263
|
+
let shownA = 0;
|
|
264
|
+
for (const a of actions.shown) {
|
|
265
|
+
const line = `- ${a.slug} · ${a.promptSnippet}`;
|
|
266
|
+
if ([...priorLines, ...lines].join('\n').length + line.length > charBudget && shownA > 0) break;
|
|
267
|
+
lines.push(line);
|
|
268
|
+
shownA++;
|
|
269
|
+
}
|
|
270
|
+
const droppedA = actions.count - shownA;
|
|
271
|
+
if (droppedA > 0) lines.push(`- … (${droppedA} more — \`zuzuu act list\`)`);
|
|
272
|
+
// mirror the Knowledge contract: shown reflects what actually rendered
|
|
273
|
+
return { lines, data: { ...actions, shown: actions.shown.slice(0, shownA), renderedCount: shownA } };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// session signals (the observability surface — `zuzuu session inspect`)
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
/** Counts of the mined-signal superset slices this faculty grows from. */
|
|
281
|
+
export function sessionSignals(signals = {}) {
|
|
282
|
+
return { sequences: signals.sequences?.length ?? 0 };
|
|
283
|
+
}
|