@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,288 @@
|
|
|
1
|
+
// zuzuu/faculties/instructions/index.mjs — the Instructions faculty module.
|
|
2
|
+
//
|
|
3
|
+
// Consolidates the adapter (steering-amendment proposals → envelope items),
|
|
4
|
+
// the miner (recurring corrective user turns → amendment proposals, WS5-T4)
|
|
5
|
+
// and the digest section (the steering text the agent grounds on) behind the
|
|
6
|
+
// Faculty Module contract.
|
|
7
|
+
//
|
|
8
|
+
// An instructions proposal payload is a steering amendment:
|
|
9
|
+
// { id?, text } — a line or paragraph of steering
|
|
10
|
+
//
|
|
11
|
+
// apply: writes the amendment as a Faculty Standard envelope item under
|
|
12
|
+
// .zuzuu/instructions/items/<id>.md (kind: amendment; body = the text).
|
|
13
|
+
// The pinned steering itself lives at items/steering.md; future
|
|
14
|
+
// amendments are MORE items, never edits to steering. Idempotent: a
|
|
15
|
+
// text already present in any instructions item is not duplicated.
|
|
16
|
+
|
|
17
|
+
import { listFacultyItems, writeFacultyItem } from '../../faculty/items.mjs';
|
|
18
|
+
import { deriveTitle } from '../../faculty/envelope.mjs';
|
|
19
|
+
import { makeProposal, writeProposal, listProposals, isArchivedResolved } from '../../faculty/proposal.mjs';
|
|
20
|
+
import { slugify } from '../../knowledge/items.mjs';
|
|
21
|
+
|
|
22
|
+
const name = 'instructions';
|
|
23
|
+
|
|
24
|
+
export const manifest = {
|
|
25
|
+
id: 'instructions',
|
|
26
|
+
title: 'Instructions',
|
|
27
|
+
tagline: 'who to BE — steering and project conventions',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
contract: 1,
|
|
30
|
+
kinds: ['steering', 'amendment'],
|
|
31
|
+
itemsDir: 'items',
|
|
32
|
+
schema: 'schema.json',
|
|
33
|
+
hooks: { miner: true, digest: true, eval: false, gate: false },
|
|
34
|
+
ui: { icon: 'compass', accent: 'warning', teaching: 'The pinned steering the agent is told at session start; corrections graduate into amendments here.' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// adapter contract
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ingest a raw amendment object. Pass-through: the payload IS the amendment.
|
|
43
|
+
*/
|
|
44
|
+
function ingest(_agentDir, raw) {
|
|
45
|
+
const payload = raw?.payload ?? raw ?? {};
|
|
46
|
+
return { payload, analysis: {} };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate an amendment payload.
|
|
51
|
+
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
52
|
+
*/
|
|
53
|
+
export function validate(_agentDir, payload) {
|
|
54
|
+
const errors = [];
|
|
55
|
+
if (!payload?.text || !String(payload.text).trim()) {
|
|
56
|
+
errors.push('text is required (non-empty steering amendment)');
|
|
57
|
+
}
|
|
58
|
+
return { ok: errors.length === 0, errors, warnings: [] };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Apply an approved amendment: write an amendment item (idempotent on
|
|
63
|
+
* identical text — won't duplicate steering already present in any item).
|
|
64
|
+
* @returns {{ok:boolean, action:string, itemIds:string[]}}
|
|
65
|
+
*/
|
|
66
|
+
function apply(agentDir, proposal) {
|
|
67
|
+
const text = String(proposal?.payload?.text ?? '').trim();
|
|
68
|
+
|
|
69
|
+
// Idempotence: skip if the exact text already lives in an instructions item
|
|
70
|
+
const { items } = listFacultyItems(agentDir, 'instructions');
|
|
71
|
+
if (items.some((i) => String(i.body ?? '').includes(text))) {
|
|
72
|
+
return { ok: true, action: 'amended instructions (already present)', itemIds: [] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const id = proposal?.payload?.id || slugify(text, 50);
|
|
76
|
+
writeFacultyItem(agentDir, {
|
|
77
|
+
id,
|
|
78
|
+
faculty: name,
|
|
79
|
+
kind: 'amendment',
|
|
80
|
+
title: deriveTitle(text, id),
|
|
81
|
+
status: 'active',
|
|
82
|
+
created_at: new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
|
|
83
|
+
provenance: Array.isArray(proposal?.provenance) ? proposal.provenance : [],
|
|
84
|
+
payload: { scope: 'project' },
|
|
85
|
+
body: text,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return { ok: true, action: 'amended instructions', itemIds: [id] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const applyProposal = apply;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Render an amendment proposal for the human gate.
|
|
95
|
+
* @returns {{line:string, card:string}}
|
|
96
|
+
*/
|
|
97
|
+
function render(proposal) {
|
|
98
|
+
const text = proposal?.payload?.text ?? '';
|
|
99
|
+
const preview = text.slice(0, 80).replace(/\n/g, ' ');
|
|
100
|
+
return {
|
|
101
|
+
line: `[amendment] ${preview}`,
|
|
102
|
+
card: text,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const adapter = { name, ingest, validate, apply, render };
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// miner (WS5-T4 — recurring corrective turns → amendment proposals, unchanged)
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Normalise a correction text for grouping:
|
|
114
|
+
* lowercase, collapse whitespace, truncate to 200 chars.
|
|
115
|
+
*
|
|
116
|
+
* v1 grouping: near-identical normalised text (exact key match). Simple and
|
|
117
|
+
* deterministic; a fuzzy grouper can be earned later.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} text
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
function normText(text) {
|
|
123
|
+
return String(text).toLowerCase().replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Derive a proposal id fragment from a normalised text key.
|
|
128
|
+
* Keep it stable, short, and filesystem-safe.
|
|
129
|
+
*/
|
|
130
|
+
function instrId(normKey) {
|
|
131
|
+
// Use a slugified version of the first 60 chars of the normalised text,
|
|
132
|
+
// prefixed to make collisions with other faculties impossible.
|
|
133
|
+
const slug = normKey.slice(0, 60).replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 50) || 'instr';
|
|
134
|
+
return 'instr-' + slug;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Group correctionTurns from mined sessions; propose when a similar correction
|
|
139
|
+
* recurs across ≥minSessions (default 2) distinct sessions.
|
|
140
|
+
*
|
|
141
|
+
* @param {Array<{sessionId:string, correctionTurns:{text:string}[]}>} sessions
|
|
142
|
+
* @param {object} opts
|
|
143
|
+
* @param {number} [opts.minSessions=2] min distinct sessions with the same normalised correction
|
|
144
|
+
* @returns {Array<{payload:{text:string}, evidence:{occurrences:number, sessions:number}}>}
|
|
145
|
+
*/
|
|
146
|
+
export function aggregate(sessions, { minSessions = 2 } = {}) {
|
|
147
|
+
// normalised text → { count, sessions: Set<sessionId>, rawText: string }
|
|
148
|
+
const stats = new Map();
|
|
149
|
+
|
|
150
|
+
for (const s of sessions) {
|
|
151
|
+
if (!Array.isArray(s.correctionTurns)) continue;
|
|
152
|
+
// Track distinct normalised texts per session to avoid double-counting
|
|
153
|
+
// the same session for the same correction text.
|
|
154
|
+
const seenInSession = new Set();
|
|
155
|
+
for (const { text } of s.correctionTurns) {
|
|
156
|
+
const key = normText(text);
|
|
157
|
+
if (!key) continue;
|
|
158
|
+
const st = stats.get(key) ?? { count: 0, sessions: new Set(), rawText: text };
|
|
159
|
+
st.count++;
|
|
160
|
+
st.sessions.add(s.sessionId);
|
|
161
|
+
seenInSession.add(key);
|
|
162
|
+
stats.set(key, st);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const candidates = [];
|
|
167
|
+
for (const [, st] of stats) {
|
|
168
|
+
if (st.sessions.size < minSessions) continue;
|
|
169
|
+
|
|
170
|
+
// Phrase the raw correction as an instruction for the steering amendment.
|
|
171
|
+
// The corrective turn text already reads like user guidance; use it directly
|
|
172
|
+
// (trimmed to 500 chars to match mineTranscript's cap).
|
|
173
|
+
const amendmentText = st.rawText.slice(0, 500).trim();
|
|
174
|
+
const id = instrId(normText(amendmentText));
|
|
175
|
+
|
|
176
|
+
candidates.push({
|
|
177
|
+
payload: { id, text: amendmentText },
|
|
178
|
+
evidence: { occurrences: st.count, sessions: st.sessions.size },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return candidates;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Write an instructions proposal into .zuzuu/instructions/proposals/ for each
|
|
187
|
+
* candidate.
|
|
188
|
+
*
|
|
189
|
+
* Idempotent:
|
|
190
|
+
* - skips if an instructions proposal with the same derived id already exists
|
|
191
|
+
* - skips if the text is already present in an instructions item (steering
|
|
192
|
+
* or a prior amendment)
|
|
193
|
+
* - skips if the id is already resolved in proposals/archive/ — a rejection
|
|
194
|
+
* is remembered; re-distilling never resurrects it
|
|
195
|
+
*
|
|
196
|
+
* @param {string} agentDir
|
|
197
|
+
* @param {ReturnType<typeof aggregate>} aggregated
|
|
198
|
+
* @returns {number} count of new proposals written
|
|
199
|
+
*/
|
|
200
|
+
export function propose(agentDir, aggregated) {
|
|
201
|
+
// Collect ids of existing pending proposals for this faculty.
|
|
202
|
+
const existing = listProposals(agentDir, 'instructions');
|
|
203
|
+
const existingIds = new Set(existing.map((p) => p.payload?.id).filter(Boolean));
|
|
204
|
+
|
|
205
|
+
// Read the instructions items (steering + amendments) to skip applied text.
|
|
206
|
+
let appliedText = '';
|
|
207
|
+
try {
|
|
208
|
+
appliedText = listFacultyItems(agentDir, 'instructions').items.map((i) => i.body ?? '').join('\n');
|
|
209
|
+
} catch { appliedText = ''; }
|
|
210
|
+
|
|
211
|
+
let count = 0;
|
|
212
|
+
for (const c of aggregated) {
|
|
213
|
+
const { payload, evidence } = c;
|
|
214
|
+
|
|
215
|
+
// Idempotent: skip if already proposed.
|
|
216
|
+
if (existingIds.has(payload.id)) continue;
|
|
217
|
+
|
|
218
|
+
// Idempotent: skip if text already present in an instructions item.
|
|
219
|
+
if (appliedText.includes(payload.text)) continue;
|
|
220
|
+
|
|
221
|
+
const proposal = makeProposal({
|
|
222
|
+
faculty: 'instructions',
|
|
223
|
+
kind: 'block',
|
|
224
|
+
source: 'distill',
|
|
225
|
+
payload,
|
|
226
|
+
evidence,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// A rejection is remembered: never resurrect an archive-resolved id.
|
|
230
|
+
if (isArchivedResolved(agentDir, 'instructions', proposal.id)) continue;
|
|
231
|
+
|
|
232
|
+
writeProposal(agentDir, proposal);
|
|
233
|
+
count++;
|
|
234
|
+
}
|
|
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
|
+
const PLACEHOLDER_MARK = '<!-- Fill in:';
|
|
246
|
+
|
|
247
|
+
const INTERVIEW = [
|
|
248
|
+
'Project steering is empty. Before substantive work, interview your human',
|
|
249
|
+
'(what is this project, its conventions, its priorities), draft the steering item',
|
|
250
|
+
'.zuzuu/instructions/items/steering.md from their answers, and get their approval.',
|
|
251
|
+
].join(' ');
|
|
252
|
+
|
|
253
|
+
/** Read the instructions items (steering first, then amendments); classify
|
|
254
|
+
* empty vs steering text. Items are Faculty Standard envelopes (W24). */
|
|
255
|
+
function readInstructions(agentDir) {
|
|
256
|
+
let items = [];
|
|
257
|
+
try {
|
|
258
|
+
items = listFacultyItems(agentDir, 'instructions').items;
|
|
259
|
+
} catch { /* missing or unreadable → treat as empty */ }
|
|
260
|
+
// steering pins the top; amendments follow in id order (already sorted)
|
|
261
|
+
items.sort((a, b) => (a.kind === 'steering' ? -1 : 1) - (b.kind === 'steering' ? -1 : 1));
|
|
262
|
+
const bodies = items
|
|
263
|
+
.map((i) => String(i.body ?? ''))
|
|
264
|
+
.map((raw) => (raw.includes(PLACEHOLDER_MARK) ? '' : raw.replace(/^#.*$/gm, '').trim() && raw.trim()))
|
|
265
|
+
.filter(Boolean);
|
|
266
|
+
const text = bodies.join('\n\n');
|
|
267
|
+
return { empty: !text, text };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The Instructions digest section: the pinned steering (or the interview
|
|
272
|
+
* prompt when empty). Always renders — grounding starts here.
|
|
273
|
+
* @param {string} agentDir
|
|
274
|
+
* @returns {{lines: string[], data: object}}
|
|
275
|
+
*/
|
|
276
|
+
export function digestSection(agentDir) {
|
|
277
|
+
const instr = readInstructions(agentDir);
|
|
278
|
+
return { lines: ['## Instructions', instr.empty ? INTERVIEW : instr.text], data: instr };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// session signals (the observability surface — `zuzuu session inspect`)
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
/** Counts of the mined-signal superset slices this faculty grows from. */
|
|
286
|
+
export function sessionSignals(signals = {}) {
|
|
287
|
+
return { correctionTurns: signals.correctionTurns?.length ?? 0 };
|
|
288
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// zuzuu/faculties/knowledge/index.mjs — the Knowledge faculty module.
|
|
2
|
+
//
|
|
3
|
+
// Consolidates the faculty's spine surface behind the Faculty Module contract
|
|
4
|
+
// (faculty.json manifest + hook exports): the adapter (ingest/validate/apply/
|
|
5
|
+
// render — wraps the EXISTING Knowledge pipeline: proposals/ER/registry/items/
|
|
6
|
+
// index), the miner (the source-A distill path), and the digest section.
|
|
7
|
+
// Substrate code stays in zuzuu/knowledge/ — this module is the contract face.
|
|
8
|
+
|
|
9
|
+
import { resolve as erResolve } from '../../knowledge/er.mjs';
|
|
10
|
+
import { loadRegistry, validateItem } from '../../knowledge/registry.mjs';
|
|
11
|
+
import { allItems, slugify } from '../../knowledge/items.mjs';
|
|
12
|
+
import { applyKnowledgeProposal, createProposal } from '../../knowledge/proposals.mjs';
|
|
13
|
+
import { aggregate } from '../../knowledge/distill.mjs';
|
|
14
|
+
|
|
15
|
+
const name = 'knowledge';
|
|
16
|
+
|
|
17
|
+
export const manifest = {
|
|
18
|
+
id: 'knowledge',
|
|
19
|
+
title: 'Knowledge',
|
|
20
|
+
tagline: "what's TRUE — facts about this project",
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
contract: 1,
|
|
23
|
+
kinds: ['fact', 'entity', 'command', 'decision'],
|
|
24
|
+
itemsDir: 'items',
|
|
25
|
+
schema: 'schema.json',
|
|
26
|
+
hooks: { miner: true, digest: true, eval: false, gate: false },
|
|
27
|
+
ui: { icon: 'book', accent: 'info', teaching: 'Facts zuzuu learns from your sessions land here after your approval.' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// adapter (WS2-T2 — unchanged behaviour)
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ingest a raw candidate: run ER against existing items and return the
|
|
36
|
+
* normalised payload + analysis. Mirrors what createProposal computes today.
|
|
37
|
+
* @param {string} agentDir
|
|
38
|
+
* @param {{candidate:object, source?:string, evidence?:object}} raw
|
|
39
|
+
*/
|
|
40
|
+
function ingest(agentDir, raw) {
|
|
41
|
+
const { items } = allItems(agentDir);
|
|
42
|
+
const candidate = { ...raw.candidate };
|
|
43
|
+
candidate.id = candidate.id || slugify(candidate.body);
|
|
44
|
+
const er = erResolve(candidate, items);
|
|
45
|
+
return { payload: candidate, analysis: { er }, dedupeKey: candidate.id };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate an item against the Knowledge registry.
|
|
50
|
+
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
51
|
+
*/
|
|
52
|
+
export function validate(agentDir, payload) {
|
|
53
|
+
const reg = loadRegistry(agentDir);
|
|
54
|
+
const v = validateItem(reg, payload);
|
|
55
|
+
const warnings = [
|
|
56
|
+
...v.unknownKeys.attributes.map((k) => `unregistered attribute '${k}'`),
|
|
57
|
+
...v.unknownKeys.relations.map((t) => `unregistered relation type '${t}'`),
|
|
58
|
+
];
|
|
59
|
+
return { ok: v.ok, errors: v.errors, warnings };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Apply an approved proposal — delegates to the extracted approve apply body.
|
|
64
|
+
* @returns {{ok:boolean, action:string, itemIds:string[], warnings:string[]}}
|
|
65
|
+
*/
|
|
66
|
+
function apply(agentDir, proposal) {
|
|
67
|
+
// Bridge spine-shaped records (payload/analysis.er) onto applyKnowledgeProposal's
|
|
68
|
+
// legacy shape (candidate/er). Records that still carry candidate/er pass through.
|
|
69
|
+
const legacy = {
|
|
70
|
+
...proposal,
|
|
71
|
+
candidate: proposal.candidate ?? proposal.payload,
|
|
72
|
+
er: proposal.er ?? proposal.analysis?.er,
|
|
73
|
+
};
|
|
74
|
+
const r = applyKnowledgeProposal(agentDir, legacy);
|
|
75
|
+
return {
|
|
76
|
+
ok: r.ok,
|
|
77
|
+
action: r.action,
|
|
78
|
+
itemIds: r.item ? [r.item] : [],
|
|
79
|
+
warnings: r.warnings ?? [],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const applyProposal = apply;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Render a proposal for the human gate. `card` mirrors the multi-line summary
|
|
87
|
+
* `zuzuu review` shows for knowledge proposals (id, type, attrs/relations, ER
|
|
88
|
+
* verdict); `line` is the one-line list form (`zuzuu proposals list`).
|
|
89
|
+
* @returns {{line:string, card:string}}
|
|
90
|
+
*/
|
|
91
|
+
function render(proposal) {
|
|
92
|
+
if (proposal.kind === 'registry') {
|
|
93
|
+
const what = `register ${String(proposal.registry).slice(0, -1)} '${proposal.key}'`;
|
|
94
|
+
return {
|
|
95
|
+
line: `${proposal.id} [${proposal.kind}] ${what}`,
|
|
96
|
+
card: `${what} (seen ${proposal.evidence?.occurrences}× in candidates)`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const c = proposal.candidate ?? {};
|
|
100
|
+
const er = proposal.er ?? {};
|
|
101
|
+
const lines = [];
|
|
102
|
+
lines.push(`${c.id ?? ''} ── ${c.type}: ${c.body?.slice(0, 100).replace(/\n/g, ' ')}`);
|
|
103
|
+
for (const [k, v] of Object.entries(c.attributes ?? {})) lines.push(` · ${k} = ${v}`);
|
|
104
|
+
for (const r of c.relations ?? []) lines.push(` → ${r.type} ${r.target}`);
|
|
105
|
+
lines.push(` er: ${er.verdict}${er.match ? ` → ${er.match}` : ''} (${(er.confidence ?? 0).toFixed(2)} · ${er.reason ?? ''})`);
|
|
106
|
+
return {
|
|
107
|
+
line: `${proposal.id} [${er.verdict ?? proposal.kind}] ${c.type}: ${c.body?.slice(0, 60).replace(/\n/g, ' ')}`,
|
|
108
|
+
card: lines.join('\n'),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const adapter = { name, ingest, validate, apply, render };
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// miner (WS5-T1 — the golden source-A distill path, unchanged)
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/** File one knowledge proposal per aggregated candidate; return the count of
|
|
119
|
+
* actually-filed proposals (archive-resolved ids are skipped, not re-filed). */
|
|
120
|
+
export function propose(agentDir, aggregated) {
|
|
121
|
+
let count = 0;
|
|
122
|
+
for (const c of aggregated) {
|
|
123
|
+
const p = createProposal(agentDir, { candidate: c.candidate, source: 'distill', evidence: c.evidence });
|
|
124
|
+
if (p && p.status !== 'archived-skip') count++;
|
|
125
|
+
}
|
|
126
|
+
return count;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const miner = { faculty: name, aggregate, propose };
|
|
130
|
+
|
|
131
|
+
export { aggregate };
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// digest section (moved from the pre-module digest, byte-identical output)
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The Knowledge digest section: most-recent items under the shared char budget.
|
|
139
|
+
* @param {string} agentDir
|
|
140
|
+
* @param {{limit:number, charBudget:number, priorLines:string[]}} ctx
|
|
141
|
+
* @returns {{lines: string[], data: object}}
|
|
142
|
+
*/
|
|
143
|
+
export function digestSection(agentDir, { limit, charBudget, priorLines }) {
|
|
144
|
+
let knowledge;
|
|
145
|
+
try {
|
|
146
|
+
const { items } = allItems(agentDir);
|
|
147
|
+
const ranked = [...items]
|
|
148
|
+
.sort((a, b) => String(b.created_at).localeCompare(String(a.created_at)))
|
|
149
|
+
.slice(0, limit);
|
|
150
|
+
knowledge = { count: items.length, shown: ranked.map((i) => ({ id: i.id, type: i.type, body: i.body })) };
|
|
151
|
+
} catch {
|
|
152
|
+
knowledge = { count: 0, shown: [] };
|
|
153
|
+
}
|
|
154
|
+
const lines = ['## Knowledge'];
|
|
155
|
+
if (!knowledge.count) {
|
|
156
|
+
lines.push('(no items yet — propose facts to knowledge/inbox/)');
|
|
157
|
+
return { lines, data: { ...knowledge, renderedCount: 0 } };
|
|
158
|
+
}
|
|
159
|
+
lines.push(`${knowledge.count} item(s); most recent:`);
|
|
160
|
+
let shown = 0;
|
|
161
|
+
for (const it of knowledge.shown) {
|
|
162
|
+
const line = `- ${it.id} · ${it.type} · ${it.body.split('\n')[0].slice(0, 80)}`;
|
|
163
|
+
// join is O(items²) but trivial: once-per-session, limit default 5
|
|
164
|
+
if ([...priorLines, ...lines].join('\n').length + line.length > charBudget && shown > 0) break;
|
|
165
|
+
lines.push(line);
|
|
166
|
+
shown++;
|
|
167
|
+
}
|
|
168
|
+
const dropped = knowledge.count - shown;
|
|
169
|
+
if (dropped > 0) lines.push(`- … (${dropped} more — \`zuzuu recall\`)`);
|
|
170
|
+
// `shown` = items actually rendered (after budget); `count` = total available
|
|
171
|
+
return { lines, data: { ...knowledge, shown: knowledge.shown.slice(0, shown), renderedCount: shown } };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// session signals (the observability surface — `zuzuu session inspect`)
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
/** Counts of the mined-signal superset slices this faculty grows from. */
|
|
179
|
+
export function sessionSignals(signals = {}) {
|
|
180
|
+
return {
|
|
181
|
+
commands: signals.commands?.length ?? 0,
|
|
182
|
+
files: signals.files?.length ?? 0,
|
|
183
|
+
failures: signals.failures?.length ?? 0,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// zuzuu/faculties/memory/index.mjs — the Memory faculty module.
|
|
2
|
+
//
|
|
3
|
+
// Consolidates the adapter (episode proposals → envelope entries) and the
|
|
4
|
+
// miner STUB (WS5-T4, deferred — see the WHAT IT WOULD MINE note below) behind
|
|
5
|
+
// the Faculty Module contract. No digest section today (the pre-module digest
|
|
6
|
+
// never rendered one — preserved).
|
|
7
|
+
//
|
|
8
|
+
// A memory proposal payload is an episode record:
|
|
9
|
+
// { id, date, title, provenance: {sessions, hosts}, tags, body }
|
|
10
|
+
// id format: mem-<YYYY-MM-DD>-<slug>
|
|
11
|
+
//
|
|
12
|
+
// apply: writes .zuzuu/memory/entries/<id>.md as a Faculty Standard envelope
|
|
13
|
+
// (kind: episode; payload = {sessions, hosts, tags}; body = the
|
|
14
|
+
// Attempted / Resulted / Remember-next-time sections).
|
|
15
|
+
//
|
|
16
|
+
// MINER (when implemented): completed-session episodes — a Run that reached
|
|
17
|
+
// `completed` in .zuzuu/sessions.json distilled into curated entries, emitted
|
|
18
|
+
// as kind-'episode' proposals for `zuzuu review`. Deferred until the Memory
|
|
19
|
+
// substrate decision lands and completed runs carry rich enough trace data.
|
|
20
|
+
|
|
21
|
+
import { writeFacultyItem } from '../../faculty/items.mjs';
|
|
22
|
+
|
|
23
|
+
const name = 'memory';
|
|
24
|
+
|
|
25
|
+
export const manifest = {
|
|
26
|
+
id: 'memory',
|
|
27
|
+
title: 'Memory',
|
|
28
|
+
tagline: 'what HAPPENED — curated episodes from past sessions',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
contract: 1,
|
|
31
|
+
kinds: ['episode'],
|
|
32
|
+
itemsDir: 'entries',
|
|
33
|
+
schema: 'schema.json',
|
|
34
|
+
hooks: { miner: true, digest: false, eval: false, gate: false },
|
|
35
|
+
ui: { icon: 'clock', accent: 'neutral', teaching: 'Curated episodes from past sessions — what was attempted, what resulted, what to remember.' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// mem-<YYYY-MM-DD>-<slug>: the id must START with "mem-"
|
|
39
|
+
const MEM_ID_RE = /^mem-/;
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// adapter contract
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Ingest a raw episode. Pass-through: the payload IS the episode.
|
|
47
|
+
*/
|
|
48
|
+
function ingest(_agentDir, raw) {
|
|
49
|
+
const payload = raw?.payload ?? raw ?? {};
|
|
50
|
+
return { payload, analysis: {}, dedupeKey: payload.id };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate an episode payload.
|
|
55
|
+
* @returns {{ok:boolean, errors:string[], warnings:string[]}}
|
|
56
|
+
*/
|
|
57
|
+
export function validate(_agentDir, payload) {
|
|
58
|
+
const errors = [];
|
|
59
|
+
if (!payload?.id || typeof payload.id !== 'string') {
|
|
60
|
+
errors.push('id is required');
|
|
61
|
+
} else if (!MEM_ID_RE.test(payload.id)) {
|
|
62
|
+
errors.push(`id must match mem-<YYYY-MM-DD>-<slug> format (got '${payload.id}')`);
|
|
63
|
+
}
|
|
64
|
+
if (!payload?.title || !String(payload.title).trim()) {
|
|
65
|
+
errors.push('title is required');
|
|
66
|
+
}
|
|
67
|
+
return { ok: errors.length === 0, errors, warnings: [] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Apply an approved episode proposal: write the envelope entry file.
|
|
72
|
+
* @returns {{ok:boolean, action:string, itemIds:string[]}}
|
|
73
|
+
*/
|
|
74
|
+
function apply(agentDir, proposal) {
|
|
75
|
+
const p = proposal?.payload ?? {};
|
|
76
|
+
const envPayload = {};
|
|
77
|
+
if (Array.isArray(p.provenance?.sessions) && p.provenance.sessions.length) envPayload.sessions = p.provenance.sessions.map(String);
|
|
78
|
+
if (Array.isArray(p.provenance?.hosts) && p.provenance.hosts.length) envPayload.hosts = p.provenance.hosts.map(String);
|
|
79
|
+
if (Array.isArray(p.tags) && p.tags.length) envPayload.tags = p.tags.map(String);
|
|
80
|
+
|
|
81
|
+
writeFacultyItem(agentDir, {
|
|
82
|
+
id: p.id,
|
|
83
|
+
faculty: name,
|
|
84
|
+
kind: 'episode',
|
|
85
|
+
title: p.title,
|
|
86
|
+
status: 'active',
|
|
87
|
+
created_at: p.date || new Date().toISOString().replace(/\.\d+Z$/, 'Z'),
|
|
88
|
+
provenance: Array.isArray(proposal?.provenance) ? proposal.provenance : [],
|
|
89
|
+
payload: envPayload,
|
|
90
|
+
body: p.body ?? '',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return { ok: true, action: `wrote memory ${p.id}`, itemIds: [p.id] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const applyProposal = apply;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Render an episode proposal for the human gate.
|
|
100
|
+
* @returns {{line:string, card:string}}
|
|
101
|
+
*/
|
|
102
|
+
function render(proposal) {
|
|
103
|
+
const p = proposal?.payload ?? {};
|
|
104
|
+
const title = p.title ?? '';
|
|
105
|
+
const date = p.date ?? '';
|
|
106
|
+
const id = p.id ?? '';
|
|
107
|
+
return {
|
|
108
|
+
line: `${id} [episode] ${title} (${date})`,
|
|
109
|
+
card: `${title}\n id: ${id} date: ${date}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const adapter = { name, ingest, validate, apply, render };
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// miner (registered no-op stub — deferred)
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
export const miner = {
|
|
120
|
+
faculty: name,
|
|
121
|
+
stub: true,
|
|
122
|
+
aggregate: () => [],
|
|
123
|
+
propose: () => 0,
|
|
124
|
+
};
|