keating 0.3.6
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/README.md +334 -0
- package/SYSTEM.md +33 -0
- package/bin/keating.js +31 -0
- package/dist/src/cli/main.js +357 -0
- package/dist/src/cli/setup.js +197 -0
- package/dist/src/cli/web.js +84 -0
- package/dist/src/core/animation.js +304 -0
- package/dist/src/core/ax-optimizer.js +81 -0
- package/dist/src/core/ax-prompt-learner.js +59 -0
- package/dist/src/core/ax-trial.js +181 -0
- package/dist/src/core/benchmark.js +253 -0
- package/dist/src/core/commands.js +57 -0
- package/dist/src/core/config.js +120 -0
- package/dist/src/core/engagement.js +235 -0
- package/dist/src/core/env.js +9 -0
- package/dist/src/core/evolution.js +242 -0
- package/dist/src/core/flashcards.js +133 -0
- package/dist/src/core/learner-state.js +108 -0
- package/dist/src/core/lesson-plan.js +155 -0
- package/dist/src/core/map-elites.js +228 -0
- package/dist/src/core/map.js +89 -0
- package/dist/src/core/mastery.js +207 -0
- package/dist/src/core/paths.js +100 -0
- package/dist/src/core/pi-agent.js +82 -0
- package/dist/src/core/policy.js +79 -0
- package/dist/src/core/project.js +337 -0
- package/dist/src/core/projects.js +281 -0
- package/dist/src/core/prompt-evolution.js +344 -0
- package/dist/src/core/quiz.js +194 -0
- package/dist/src/core/random.js +19 -0
- package/dist/src/core/self-improve.js +425 -0
- package/dist/src/core/speech.js +54 -0
- package/dist/src/core/terminal.js +117 -0
- package/dist/src/core/theme.js +101 -0
- package/dist/src/core/topics.js +620 -0
- package/dist/src/core/types.js +1 -0
- package/dist/src/core/util.js +30 -0
- package/dist/src/core/verification.js +162 -0
- package/dist/src/pi/hyperteacher-extension.js +573 -0
- package/dist/src/runtime/pi.js +343 -0
- package/package.json +78 -0
- package/pi/prompts/bridge.md +14 -0
- package/pi/prompts/diagnose.md +15 -0
- package/pi/prompts/improve.md +39 -0
- package/pi/prompts/learn.md +21 -0
- package/pi/prompts/quiz.md +14 -0
- package/pi/skills/adaptive-teaching/SKILL.md +33 -0
- package/scripts/install/install.sh +308 -0
- package/web/dist/.well-known/llms.txt +44 -0
- package/web/dist/apple-touch-icon.svg +10 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/_baseFor-B_cjfMB6.js +1 -0
- package/web/dist/assets/anthropic-BT6Vfzb1.js +36 -0
- package/web/dist/assets/arc-x2nTilpc.js +1 -0
- package/web/dist/assets/architecture-YZFGNWBL-B1hlUWjX.js +1 -0
- package/web/dist/assets/architectureDiagram-Q4EWVU46-CMApWFyw.js +36 -0
- package/web/dist/assets/array-B9UHiPd-.js +1 -0
- package/web/dist/assets/azure-openai-responses-CommX3YJ.js +1 -0
- package/web/dist/assets/blockDiagram-DXYQGD6D-DOQbsNRY.js +132 -0
- package/web/dist/assets/c4Diagram-AHTNJAMY-VFfRZWWA.js +10 -0
- package/web/dist/assets/channel-KY2Tg8Ba.js +1 -0
- package/web/dist/assets/chunk-2KRD3SAO-B-AqvS0u.js +1 -0
- package/web/dist/assets/chunk-336JU56O-DlYgPyl6.js +2 -0
- package/web/dist/assets/chunk-426QAEUC-CsVoBkfR.js +1 -0
- package/web/dist/assets/chunk-4BX2VUAB-0Z13aFAn.js +1 -0
- package/web/dist/assets/chunk-4TB4RGXK-DqC0Zwm7.js +206 -0
- package/web/dist/assets/chunk-55IACEB6-CWE_u-IY.js +1 -0
- package/web/dist/assets/chunk-5FUZZQ4R-CApli0xX.js +62 -0
- package/web/dist/assets/chunk-5PVQY5BW-Cbzhfhln.js +2 -0
- package/web/dist/assets/chunk-67CJDMHE-Cx7uJS4d.js +1 -0
- package/web/dist/assets/chunk-7N4EOEYR-CYPNsFus.js +1 -0
- package/web/dist/assets/chunk-AA7GKIK3-rU0uhR_u.js +1 -0
- package/web/dist/assets/chunk-BSJP7CBP-5VmcfR4-.js +1 -0
- package/web/dist/assets/chunk-Bj-mKKzh.js +1 -0
- package/web/dist/assets/chunk-CIAEETIT-CHJ-L8H1.js +1 -0
- package/web/dist/assets/chunk-EDXVE4YY-DZHAJjMI.js +1 -0
- package/web/dist/assets/chunk-ENJZ2VHE-DbUDFa7w.js +10 -0
- package/web/dist/assets/chunk-FMBD7UC4-BsYE5e_h.js +15 -0
- package/web/dist/assets/chunk-FOC6F5B3-Cm6aoTv7.js +1 -0
- package/web/dist/assets/chunk-ICPOFSXX-C5eNZ4L6.js +123 -0
- package/web/dist/assets/chunk-K5T4RW27-R7dAJ4rq.js +94 -0
- package/web/dist/assets/chunk-KGLVRYIC-MO99YZXL.js +1 -0
- package/web/dist/assets/chunk-LIHQZDEY-DUJ656sT.js +1 -0
- package/web/dist/assets/chunk-ORNJ4GCN-DXuuEC1n.js +1 -0
- package/web/dist/assets/chunk-OYMX7WX6-pJlEprWq.js +231 -0
- package/web/dist/assets/chunk-QZHKN3VN-_pQxbbiW.js +1 -0
- package/web/dist/assets/chunk-U2HBQHQK-Mh_l9PLe.js +70 -0
- package/web/dist/assets/chunk-X2U36JSP-BOeiJW0w.js +1 -0
- package/web/dist/assets/chunk-XPW4576I-fQ9SDvr_.js +32 -0
- package/web/dist/assets/chunk-YZCP3GAM-eboO4P5S.js +1 -0
- package/web/dist/assets/chunk-ZZ45TVLE-Cky0eqlr.js +1 -0
- package/web/dist/assets/classDiagram-6PBFFD2Q-DEPsZSU3.js +1 -0
- package/web/dist/assets/classDiagram-v2-HSJHXN6E-DhmIOEpX.js +1 -0
- package/web/dist/assets/clone-DeTzYqo8.js +1 -0
- package/web/dist/assets/cose-bilkent-S5V4N54A-N4zWUJ7C.js +1 -0
- package/web/dist/assets/cytoscape.esm-BBMd0vGm.js +321 -0
- package/web/dist/assets/dagre-IpK1aoMm.js +1 -0
- package/web/dist/assets/dagre-KV5264BT-DCytJuju.js +4 -0
- package/web/dist/assets/defaultLocale-5eAKkKJC.js +1 -0
- package/web/dist/assets/diagram-5BDNPKRD-Cv4miBae.js +10 -0
- package/web/dist/assets/diagram-G4DWMVQ6-CtICKUFi.js +24 -0
- package/web/dist/assets/diagram-MMDJMWI5-Cn7aGorh.js +43 -0
- package/web/dist/assets/diagram-TYMM5635-CCUWDPsC.js +24 -0
- package/web/dist/assets/dist-Dm98VvTW.js +1 -0
- package/web/dist/assets/env-api-keys-BNlMKqxw.js +1 -0
- package/web/dist/assets/erDiagram-SMLLAGMA-uT88sBlT.js +85 -0
- package/web/dist/assets/event-stream-D33K9rpL.js +1 -0
- package/web/dist/assets/flatten-C-u5nd5-.js +1 -0
- package/web/dist/assets/flowDiagram-DWJPFMVM-Bl3O7S1m.js +162 -0
- package/web/dist/assets/ganttDiagram-T4ZO3ILL-B1FhwV45.js +292 -0
- package/web/dist/assets/gitGraph-7Q5UKJZL-Bc_7vzer.js +1 -0
- package/web/dist/assets/gitGraphDiagram-UUTBAWPF-DfW6svMS.js +106 -0
- package/web/dist/assets/github-copilot-headers-L39QqneT.js +1 -0
- package/web/dist/assets/google-BdYNeCP_.js +1 -0
- package/web/dist/assets/google-gemini-cli-DpxAL3K4.js +2 -0
- package/web/dist/assets/google-shared-DyQdgtsI.js +2 -0
- package/web/dist/assets/google-vertex-CKRybaXj.js +1 -0
- package/web/dist/assets/graphlib-CMTVFyOZ.js +1 -0
- package/web/dist/assets/hash-kZ2KD_no.js +1 -0
- package/web/dist/assets/index-Bdb7P7gx.css +2 -0
- package/web/dist/assets/index-DNxepp8B.js +2891 -0
- package/web/dist/assets/info-OMHHGYJF-BGcxeaZt.js +1 -0
- package/web/dist/assets/infoDiagram-42DDH7IO-BbES7X_c.js +2 -0
- package/web/dist/assets/init-DlZdxViB.js +1 -0
- package/web/dist/assets/isEmpty-DssUW35f.js +1 -0
- package/web/dist/assets/ishikawaDiagram-UXIWVN3A-DxQ28rho.js +70 -0
- package/web/dist/assets/journeyDiagram-VCZTEJTY-D0X8qQ0P.js +139 -0
- package/web/dist/assets/json-parse-C6tSeIxX.js +2 -0
- package/web/dist/assets/kanban-definition-6JOO6SKY-DWYfSlpl.js +89 -0
- package/web/dist/assets/katex-CyM-5LlM.js +265 -0
- package/web/dist/assets/line-CuHce5JG.js +1 -0
- package/web/dist/assets/linear-Ca0Vkwuj.js +1 -0
- package/web/dist/assets/mermaid-parser.core-Cy4iY_Dy.js +4 -0
- package/web/dist/assets/mermaid.core-6PGkQdYc.js +11 -0
- package/web/dist/assets/mindmap-definition-QFDTVHPH-BBnKdtQh.js +96 -0
- package/web/dist/assets/mistral-BWaUMIgd.js +7 -0
- package/web/dist/assets/openai-D4NSaQIs.js +16 -0
- package/web/dist/assets/openai-codex-responses-CHBgKhmb.js +7 -0
- package/web/dist/assets/openai-completions-kcXmmaHI.js +5 -0
- package/web/dist/assets/openai-responses-Cqq3H3p3.js +1 -0
- package/web/dist/assets/openai-responses-shared-CTNuo9ci.js +10 -0
- package/web/dist/assets/ordinal-_K3x1fkz.js +1 -0
- package/web/dist/assets/ort-wasm-simd-threaded.jsep-B0T3yYHD.wasm +0 -0
- package/web/dist/assets/packet-4T2RLAQJ-D35ZLSBH.js +1 -0
- package/web/dist/assets/path-6uRLdFF7.js +1 -0
- package/web/dist/assets/pdf.worker.min-Cpi8b8z3.mjs +28 -0
- package/web/dist/assets/pie-ZZUOXDRM-DRoETpJX.js +1 -0
- package/web/dist/assets/pieDiagram-DEJITSTG-DfMjfTQz.js +30 -0
- package/web/dist/assets/preload-helper-DSXbuxSR.js +1 -0
- package/web/dist/assets/quadrantDiagram-34T5L4WZ-DfBSEept.js +7 -0
- package/web/dist/assets/radar-PYXPWWZC-DLKxRJ0V.js +1 -0
- package/web/dist/assets/reduce-836A2NiQ.js +1 -0
- package/web/dist/assets/requirementDiagram-MS252O5E-BPkxJQkz.js +84 -0
- package/web/dist/assets/rough.esm-Djo4Abte.js +1 -0
- package/web/dist/assets/sankeyDiagram-XADWPNL6-He3x9tNT.js +10 -0
- package/web/dist/assets/sequenceDiagram-FGHM5R23-DfCDpvrT.js +157 -0
- package/web/dist/assets/src-DdOdIreR.js +1 -0
- package/web/dist/assets/stateDiagram-FHFEXIEX-fuww6347.js +1 -0
- package/web/dist/assets/stateDiagram-v2-QKLJ7IA2-U6voafO3.js +1 -0
- package/web/dist/assets/timeline-definition-GMOUNBTQ-BWunHgBC.js +120 -0
- package/web/dist/assets/transform-messages-CqKEdRVp.js +1 -0
- package/web/dist/assets/transformers.web-DKUtmSAi.js +2818 -0
- package/web/dist/assets/treeView-SZITEDCU-BCx0xSAm.js +1 -0
- package/web/dist/assets/treemap-W4RFUUIX-2CvghWJK.js +1 -0
- package/web/dist/assets/vennDiagram-DHZGUBPP-CBXRutSP.js +34 -0
- package/web/dist/assets/wardley-RL74JXVD-BkPL_mhd.js +1 -0
- package/web/dist/assets/wardleyDiagram-NUSXRM2D-DTcVscPH.js +20 -0
- package/web/dist/assets/web-CMKYLKbT.js +10 -0
- package/web/dist/assets/xychartDiagram-5P7HB3ND-CZLgX9Fe.js +7 -0
- package/web/dist/favicon.svg +10 -0
- package/web/dist/index.html +104 -0
- package/web/dist/keating-metaharness.pdf +10557 -3
- package/web/dist/llms.txt +44 -0
- package/web/dist/logo.png +0 -0
- package/web/dist/manifest.webmanifest +1 -0
- package/web/dist/og-image.png +0 -0
- package/web/dist/pwa-192x192.svg +10 -0
- package/web/dist/pwa-512x512.svg +10 -0
- package/web/dist/registerSW.js +1 -0
- package/web/dist/robots.txt +8 -0
- package/web/dist/sitemap.xml +39 -0
- package/web/dist/sw.js +1 -0
- package/web/dist/tapes/doctor.mp4 +0 -0
- package/web/dist/tapes/feedback-flow.mp4 +0 -0
- package/web/dist/tapes/improve-flow.mp4 +0 -0
- package/web/dist/tapes/intro.mp4 +0 -0
- package/web/dist/tapes/learning-flow.mp4 +0 -0
- package/web/dist/tapes/session-flow.mp4 +0 -0
- package/web/dist/tapes/teacher-flow.mp4 +0 -0
- package/web/dist/tapes/tests.mp4 +0 -0
- package/web/dist/workbox-66610c77.js +1 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { buildLessonPlan } from "./lesson-plan.js";
|
|
4
|
+
import { animationsDir } from "./paths.js";
|
|
5
|
+
import { resolveTopic } from "./topics.js";
|
|
6
|
+
import { slugify } from "./util.js";
|
|
7
|
+
function pickSceneKind(topicName) {
|
|
8
|
+
const topic = resolveTopic(topicName);
|
|
9
|
+
// Canonical slug overrides for backward compatibility
|
|
10
|
+
if (topic.slug === "derivative")
|
|
11
|
+
return "function-graph";
|
|
12
|
+
if (topic.slug === "entropy")
|
|
13
|
+
return "distribution-bars";
|
|
14
|
+
if (topic.slug === "bayes-rule")
|
|
15
|
+
return "belief-update";
|
|
16
|
+
// Route by domain
|
|
17
|
+
switch (topic.domain) {
|
|
18
|
+
case "math": return "function-graph";
|
|
19
|
+
case "science": return "distribution-bars";
|
|
20
|
+
case "code": return "code-trace";
|
|
21
|
+
case "history": return "timeline";
|
|
22
|
+
case "law":
|
|
23
|
+
case "politics": return "case-diagram";
|
|
24
|
+
case "psychology":
|
|
25
|
+
case "arts": return "mind-map";
|
|
26
|
+
case "medicine": return "distribution-bars";
|
|
27
|
+
default: return "concept-card";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function importSpecifierFrom(dirPath, targetPath) {
|
|
31
|
+
const specifier = relative(dirPath, targetPath).replaceAll("\\", "/");
|
|
32
|
+
return specifier.startsWith(".") ? specifier : `./${specifier}`;
|
|
33
|
+
}
|
|
34
|
+
function installedManimWebDistDir() {
|
|
35
|
+
const moduleUrl = import.meta.url;
|
|
36
|
+
const modulePath = decodeURIComponent(moduleUrl.replace(/^file:\/\//, ""));
|
|
37
|
+
const packageRoot = modulePath.includes("/src/")
|
|
38
|
+
? modulePath.split("/src/")[0]
|
|
39
|
+
: modulePath.includes("/dist/src/")
|
|
40
|
+
? modulePath.split("/dist/src/")[0]
|
|
41
|
+
: join(dirname(modulePath), "../../../");
|
|
42
|
+
return join(packageRoot, "node_modules/manim-web/dist");
|
|
43
|
+
}
|
|
44
|
+
async function copyDir(sourceDir, targetDir) {
|
|
45
|
+
await mkdir(targetDir, { recursive: true });
|
|
46
|
+
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
const sourcePath = join(sourceDir, entry.name);
|
|
49
|
+
const targetPath = join(targetDir, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
await copyDir(sourcePath, targetPath);
|
|
52
|
+
}
|
|
53
|
+
else if (entry.isFile()) {
|
|
54
|
+
await writeFile(targetPath, await readFile(sourcePath));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function sceneRationale(topicName, policy, sceneKind) {
|
|
59
|
+
const topic = resolveTopic(topicName);
|
|
60
|
+
return [
|
|
61
|
+
`${topic.title} is marked visualizable=${String(topic.visualizable)} and belongs to ${topic.domain}.`,
|
|
62
|
+
`The current policy prefers diagrams at ${policy.diagramBias.toFixed(2)} and formalism at ${policy.formalism.toFixed(2)}.`,
|
|
63
|
+
sceneKind === "function-graph"
|
|
64
|
+
? "A function graph highlights local change, secant-to-tangent motion, and equation refinement."
|
|
65
|
+
: sceneKind === "distribution-bars"
|
|
66
|
+
? "A bar chart makes multiplicity, relative weight, and statistical relationships legible before symbol manipulation."
|
|
67
|
+
: sceneKind === "belief-update"
|
|
68
|
+
? "A belief-update chart makes prior, evidence, and posterior shifts visible instead of purely verbal."
|
|
69
|
+
: sceneKind === "code-trace"
|
|
70
|
+
? "A code-trace scene shows execution flow, variable state, and call-stack evolution step by step."
|
|
71
|
+
: sceneKind === "timeline"
|
|
72
|
+
? "A timeline scene places events in chronological order so causal relationships and periodization become visible."
|
|
73
|
+
: sceneKind === "case-diagram"
|
|
74
|
+
? "A case-diagram scene structures arguments as premises leading to conclusions, making reasoning transparent."
|
|
75
|
+
: sceneKind === "mind-map"
|
|
76
|
+
? "A mind-map scene radiates concepts from a central idea, revealing connections and clustering."
|
|
77
|
+
: "A concept-card scene is safer when the concept is philosophical or the visual grammar is still exploratory.",
|
|
78
|
+
`Interdisciplinary hooks carried into the scene: ${topic.interdisciplinaryHooks.join(", ")}.`
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
export function buildAnimationManifest(topicName, policy) {
|
|
82
|
+
const topic = resolveTopic(topicName);
|
|
83
|
+
const plan = buildLessonPlan(topicName, policy);
|
|
84
|
+
const sceneKind = pickSceneKind(topicName);
|
|
85
|
+
return {
|
|
86
|
+
topic: topic.title,
|
|
87
|
+
slug: slugify(topicName),
|
|
88
|
+
domain: topic.domain,
|
|
89
|
+
sceneKind,
|
|
90
|
+
rationale: sceneRationale(topicName, policy, sceneKind),
|
|
91
|
+
focusMoments: plan.phases.slice(0, 4).map((phase) => `${phase.title}: ${phase.purpose}`)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
import { piComplete } from "./pi-agent.js";
|
|
95
|
+
export async function animationSceneSource(cwd, topicName, policy, importSpecifier) {
|
|
96
|
+
const topic = resolveTopic(topicName);
|
|
97
|
+
const manifest = buildAnimationManifest(topicName, policy);
|
|
98
|
+
const thesis = topic.formalCore[0] ?? topic.summary;
|
|
99
|
+
const misconception = topic.misconceptions[0] ?? `Avoid flattening ${topic.title} into a slogan.`;
|
|
100
|
+
const bridge = topic.interdisciplinaryHooks[0] ?? "application";
|
|
101
|
+
const palette = {
|
|
102
|
+
background: "#08111f",
|
|
103
|
+
ink: "#f8f5ec",
|
|
104
|
+
accent: "#ff7a59",
|
|
105
|
+
support: "#7bb0ff",
|
|
106
|
+
soft: "#9dd7c8",
|
|
107
|
+
warning: "#ff8e72"
|
|
108
|
+
};
|
|
109
|
+
const commonHelpers = `
|
|
110
|
+
const palette = ${JSON.stringify(palette, null, 2)};
|
|
111
|
+
|
|
112
|
+
function textLine(text, y, fontSize = 28, color = palette.ink, fontFamily = "Iowan Old Style, Georgia, serif") {
|
|
113
|
+
const node = new Text({
|
|
114
|
+
text,
|
|
115
|
+
fontSize,
|
|
116
|
+
color,
|
|
117
|
+
fontFamily,
|
|
118
|
+
lineHeight: 1.15
|
|
119
|
+
});
|
|
120
|
+
node.moveTo([0, y, 0]);
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
const prompt = `You are an expert ManimJS animation developer. Output the body of the 'construct(scene)' function for an animation explaining the academic topic: "${topic.title}".
|
|
125
|
+
|
|
126
|
+
Domain: ${topic.domain}
|
|
127
|
+
Focus: ${manifest.sceneKind}
|
|
128
|
+
Topic Details:
|
|
129
|
+
- Thesis: ${thesis}
|
|
130
|
+
- Misconception to avoid: ${misconception}
|
|
131
|
+
- Application: ${bridge}
|
|
132
|
+
- Diagram Nodes available: ${JSON.stringify(topic.diagramNodes)}
|
|
133
|
+
|
|
134
|
+
Environment Context:
|
|
135
|
+
We are using manim-web. You have imported Scene, Text, MathTex, Axes, BarChart, Create, Write, FadeIn, FadeOut, Transform.
|
|
136
|
+
You can use the helper \`textLine(text, y, fontSize, color)\` which returns a Text node centered at (0, y).
|
|
137
|
+
Colors available: palette.accent, palette.support, palette.soft, palette.warning, palette.ink.
|
|
138
|
+
|
|
139
|
+
Respond ONLY with the JavaScript code that belongs inside \`export async function construct(scene) { ... }\`. Do NOT wrap it in markdown block quotes. Do not provide a pre-amble or explanation. Generate sequential scene animations using \`await scene.play(...)\` and \`await scene.wait(...)\` in a way that matches the requested topic's depth. Ensure it looks impressive and dynamic.`;
|
|
140
|
+
let sceneLogic = "";
|
|
141
|
+
try {
|
|
142
|
+
sceneLogic = await piComplete(cwd, prompt, { thinking: "medium" });
|
|
143
|
+
// Remove markdown code blocks if the LLM leaked them
|
|
144
|
+
sceneLogic = sceneLogic.replace(/^```[a-z]*\n?/gm, "").replace(/```$/g, "").trim();
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error("Failed to dynamically generate scene logic, falling back to basic stub:", error);
|
|
148
|
+
sceneLogic = `
|
|
149
|
+
const thesisNode = textLine(${JSON.stringify(thesis)}, 1.25, 26);
|
|
150
|
+
const warningNode = textLine(${JSON.stringify(`Misconception: ${misconception}`)}, -0.15, 24, palette.warning);
|
|
151
|
+
await scene.play(new FadeIn(thesisNode), new FadeIn(warningNode));
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
return `import {
|
|
155
|
+
Scene, Text, MathTex, Axes, BarChart,
|
|
156
|
+
Create, Write, FadeIn, FadeOut, Transform
|
|
157
|
+
} from ${JSON.stringify(importSpecifier)};
|
|
158
|
+
|
|
159
|
+
${commonHelpers}
|
|
160
|
+
|
|
161
|
+
export async function construct(scene) {
|
|
162
|
+
const title = textLine(${JSON.stringify(`${topic.title}: ${manifest.sceneKind}`)}, 3.2, 34, palette.accent);
|
|
163
|
+
await scene.play(new Write(title));
|
|
164
|
+
|
|
165
|
+
${sceneLogic}
|
|
166
|
+
|
|
167
|
+
await scene.wait(2.0);
|
|
168
|
+
}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
export function animationStoryboardMarkdown(topicName, policy) {
|
|
172
|
+
const manifest = buildAnimationManifest(topicName, policy);
|
|
173
|
+
const plan = buildLessonPlan(topicName, policy);
|
|
174
|
+
const lines = [
|
|
175
|
+
`# Animation Storyboard: ${manifest.topic}`,
|
|
176
|
+
"",
|
|
177
|
+
`- Scene kind: ${manifest.sceneKind}`,
|
|
178
|
+
`- Domain: ${manifest.domain}`,
|
|
179
|
+
`- Policy: ${policy.name}`,
|
|
180
|
+
"",
|
|
181
|
+
"## Why This Visual",
|
|
182
|
+
...manifest.rationale.map((item) => `- ${item}`),
|
|
183
|
+
"",
|
|
184
|
+
"## Focus Moments",
|
|
185
|
+
...manifest.focusMoments.map((item) => `- ${item}`),
|
|
186
|
+
"",
|
|
187
|
+
"## Teaching Beats",
|
|
188
|
+
...plan.phases.map((phase) => `- ${phase.title}: ${phase.purpose}`),
|
|
189
|
+
""
|
|
190
|
+
];
|
|
191
|
+
return `${lines.join("\n").trim()}\n`;
|
|
192
|
+
}
|
|
193
|
+
export async function writeLessonAnimation(cwd, topicName, policy) {
|
|
194
|
+
const slug = slugify(topicName);
|
|
195
|
+
const topicDir = join(animationsDir(cwd), slug);
|
|
196
|
+
await mkdir(topicDir, { recursive: true });
|
|
197
|
+
const vendorDir = join(topicDir, "_vendor", "manim-web");
|
|
198
|
+
await copyDir(installedManimWebDistDir(), vendorDir);
|
|
199
|
+
const importSpecifier = importSpecifierFrom(topicDir, join(vendorDir, "index.js"));
|
|
200
|
+
const playerPath = join(topicDir, "player.html");
|
|
201
|
+
const scenePath = join(topicDir, "scene.mjs");
|
|
202
|
+
const storyboardPath = join(topicDir, "storyboard.md");
|
|
203
|
+
const manifestPath = join(topicDir, "manifest.json");
|
|
204
|
+
const readmePath = join(topicDir, "README.md");
|
|
205
|
+
const manifest = buildAnimationManifest(topicName, policy);
|
|
206
|
+
const sceneSource = await animationSceneSource(cwd, topicName, policy, importSpecifier);
|
|
207
|
+
const playerHtml = `<!doctype html>
|
|
208
|
+
<html lang="en">
|
|
209
|
+
<head>
|
|
210
|
+
<meta charset="utf-8" />
|
|
211
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
212
|
+
<title>Keating Animation: ${manifest.topic}</title>
|
|
213
|
+
<style>
|
|
214
|
+
:root {
|
|
215
|
+
color-scheme: dark;
|
|
216
|
+
--paper: #f8f5ec;
|
|
217
|
+
--ink: #07111d;
|
|
218
|
+
--frame: #10223a;
|
|
219
|
+
--muted: #90a3b8;
|
|
220
|
+
}
|
|
221
|
+
body {
|
|
222
|
+
margin: 0;
|
|
223
|
+
min-height: 100vh;
|
|
224
|
+
display: grid;
|
|
225
|
+
place-items: center;
|
|
226
|
+
background:
|
|
227
|
+
radial-gradient(circle at top, rgba(121, 168, 255, 0.25), transparent 28rem),
|
|
228
|
+
linear-gradient(180deg, #08111f 0%, #050a12 100%);
|
|
229
|
+
color: var(--paper);
|
|
230
|
+
font-family: "Iowan Old Style", Georgia, serif;
|
|
231
|
+
}
|
|
232
|
+
main {
|
|
233
|
+
width: min(92vw, 1280px);
|
|
234
|
+
display: grid;
|
|
235
|
+
gap: 1rem;
|
|
236
|
+
}
|
|
237
|
+
header {
|
|
238
|
+
display: flex;
|
|
239
|
+
justify-content: space-between;
|
|
240
|
+
gap: 1rem;
|
|
241
|
+
align-items: end;
|
|
242
|
+
}
|
|
243
|
+
.meta {
|
|
244
|
+
color: var(--muted);
|
|
245
|
+
font-size: 0.95rem;
|
|
246
|
+
}
|
|
247
|
+
#scene {
|
|
248
|
+
height: min(72vh, 760px);
|
|
249
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
250
|
+
border-radius: 20px;
|
|
251
|
+
overflow: hidden;
|
|
252
|
+
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.35);
|
|
253
|
+
background: #08111f;
|
|
254
|
+
}
|
|
255
|
+
a { color: #b5d2ff; }
|
|
256
|
+
</style>
|
|
257
|
+
</head>
|
|
258
|
+
<body>
|
|
259
|
+
<main>
|
|
260
|
+
<header>
|
|
261
|
+
<div>
|
|
262
|
+
<div class="meta">Keating visual artifact</div>
|
|
263
|
+
<h1>${manifest.topic}</h1>
|
|
264
|
+
</div>
|
|
265
|
+
<div class="meta">
|
|
266
|
+
<div><a href="./storyboard.md">storyboard.md</a></div>
|
|
267
|
+
<div><a href="./manifest.json">manifest.json</a></div>
|
|
268
|
+
</div>
|
|
269
|
+
</header>
|
|
270
|
+
<div id="scene"></div>
|
|
271
|
+
</main>
|
|
272
|
+
<script type="module">
|
|
273
|
+
import { Scene } from ${JSON.stringify(importSpecifier)};
|
|
274
|
+
import { construct } from "./scene.mjs";
|
|
275
|
+
|
|
276
|
+
const container = document.getElementById("scene");
|
|
277
|
+
const scene = new Scene(container, {
|
|
278
|
+
width: 1280,
|
|
279
|
+
height: 720,
|
|
280
|
+
backgroundColor: "#08111f"
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await construct(scene);
|
|
284
|
+
</script>
|
|
285
|
+
</body>
|
|
286
|
+
</html>
|
|
287
|
+
`;
|
|
288
|
+
const readme = `# ${manifest.topic} Animation Bundle
|
|
289
|
+
|
|
290
|
+
- Serve the repository root with a static file server, for example: \`python3 -m http.server 4173\`
|
|
291
|
+
- Open: \`http://localhost:4173/${relative(cwd, playerPath).replaceAll("\\", "/")}\`
|
|
292
|
+
- Inspect the bundle in \`scene.mjs\`, \`storyboard.md\`, and \`manifest.json\`
|
|
293
|
+
|
|
294
|
+
This bundle is deterministic source output. Keating does not yet export a video in Node; it generates a browser-runnable \`manim-web\` scene so the visual teaching layer can evolve under versioned prompts and tests.
|
|
295
|
+
`;
|
|
296
|
+
await Promise.all([
|
|
297
|
+
writeFile(scenePath, sceneSource, "utf8"),
|
|
298
|
+
writeFile(playerPath, playerHtml, "utf8"),
|
|
299
|
+
writeFile(storyboardPath, animationStoryboardMarkdown(topicName, policy), "utf8"),
|
|
300
|
+
writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
|
|
301
|
+
writeFile(readmePath, readme, "utf8")
|
|
302
|
+
]);
|
|
303
|
+
return { topicDir, playerPath, scenePath, storyboardPath, manifestPath };
|
|
304
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { writeFile, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { runBenchmarkSuite } from "./benchmark.js";
|
|
4
|
+
import { PolicyTrial, trialToPolicy, trialToWeights, optimize } from "./ax-trial.js";
|
|
5
|
+
import { mean } from "./util.js";
|
|
6
|
+
import { stateDir } from "./paths.js";
|
|
7
|
+
import { mapElitesEvolve, mapElitesToEvolutionRun } from "./map-elites.js";
|
|
8
|
+
function gepaResultPath(cwd) {
|
|
9
|
+
return join(stateDir(cwd), "gepa-optimized.json");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Optimizes the TeacherPolicy using GEPA multi-objective tuning.
|
|
13
|
+
*/
|
|
14
|
+
export async function optimizePolicy(cwd, basePolicy, options = {}) {
|
|
15
|
+
const { nTrials = 24, objectives = ["score", "transfer", "engagement"], focusTopic, seed = 20260401 } = options;
|
|
16
|
+
console.log(`Starting GEPA Policy Optimization (${nTrials} trials, objectives: ${objectives.join(", ")})`);
|
|
17
|
+
const objective = async (trial) => {
|
|
18
|
+
const policy = trialToPolicy(trial, `gepa-candidate-${trial.params.iteration ?? 0}`);
|
|
19
|
+
const weights = trialToWeights(trial);
|
|
20
|
+
const benchmark = await runBenchmarkSuite(cwd, policy, focusTopic, seed, 3, weights);
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const obj of objectives) {
|
|
23
|
+
if (obj === "score")
|
|
24
|
+
result[obj] = benchmark.overallScore / 100;
|
|
25
|
+
else if (obj === "transfer")
|
|
26
|
+
result[obj] = mean(benchmark.topicBenchmarks.map(b => b.meanTransfer));
|
|
27
|
+
else if (obj === "engagement")
|
|
28
|
+
result[obj] = mean(benchmark.topicBenchmarks.map(b => b.meanEngagement));
|
|
29
|
+
else if (obj === "retention")
|
|
30
|
+
result[obj] = mean(benchmark.topicBenchmarks.map(b => b.meanRetention));
|
|
31
|
+
else if (obj === "confusion")
|
|
32
|
+
result[obj] = 1 - mean(benchmark.topicBenchmarks.map(b => b.meanConfusion));
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
const study = await optimize(objective, {
|
|
38
|
+
nTrials,
|
|
39
|
+
direction: "maximize",
|
|
40
|
+
seed
|
|
41
|
+
});
|
|
42
|
+
const bestPolicy = trialToPolicy(new PolicyTrial(study.bestParams), `gepa-best-${Date.now().toString(36)}`);
|
|
43
|
+
const archiveContent = await readFile(join(stateDir(cwd), "policy-archive.json"), "utf8").catch(() => "[]");
|
|
44
|
+
const archive = JSON.parse(archiveContent);
|
|
45
|
+
const baseline = await runBenchmarkSuite(cwd, basePolicy, focusTopic, seed);
|
|
46
|
+
const best = await runBenchmarkSuite(cwd, bestPolicy, focusTopic, seed);
|
|
47
|
+
const run = {
|
|
48
|
+
baseline,
|
|
49
|
+
best,
|
|
50
|
+
acceptedCandidates: [], // Not using candidate-by-candidate acceptance here
|
|
51
|
+
exploredCandidates: study.paretoFront.map((p, i) => ({
|
|
52
|
+
policy: trialToPolicy(new PolicyTrial(p.params), `gepa-pareto-${i}`),
|
|
53
|
+
benchmark: baseline, // Placeholder
|
|
54
|
+
parentName: basePolicy.name,
|
|
55
|
+
iteration: i,
|
|
56
|
+
novelty: 1.0,
|
|
57
|
+
accepted: true,
|
|
58
|
+
parameterDelta: [],
|
|
59
|
+
decision: { improves: true, safe: true, novelEnough: true, scoreDelta: 0, weakestTopicDelta: 0, reasons: [] }
|
|
60
|
+
})),
|
|
61
|
+
archive: {
|
|
62
|
+
currentPolicy: bestPolicy,
|
|
63
|
+
bestScore: best.overallScore,
|
|
64
|
+
candidates: study.paretoFront.map(p => ({
|
|
65
|
+
policy: trialToPolicy(new PolicyTrial(p.params), "gepa"),
|
|
66
|
+
score: mean(Object.values(p.scores)) * 100,
|
|
67
|
+
novelty: 1.0,
|
|
68
|
+
accepted: true,
|
|
69
|
+
iteration: 0
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
await writeFile(gepaResultPath(cwd), JSON.stringify(study, null, 2), "utf8");
|
|
74
|
+
return run;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.warn("GEPA Optimization failed, falling back to MAP-Elites.", error);
|
|
78
|
+
const meRun = await mapElitesEvolve(cwd, basePolicy, { iterations: nTrials, focusTopic, seed });
|
|
79
|
+
return mapElitesToEvolutionRun(meRun);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { writeFile, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ai, ax, AxACE } from "@ax-llm/ax";
|
|
4
|
+
import { evaluatePromptContent } from "./prompt-evolution.js";
|
|
5
|
+
import { stateDir } from "./paths.js";
|
|
6
|
+
import { loadKeatingConfig } from "./config.js";
|
|
7
|
+
// Helper to convert typical string models to Ax model enums or strings
|
|
8
|
+
function mapToAxModel(keatingModel) {
|
|
9
|
+
if (keatingModel.includes("gpt-4o-mini"))
|
|
10
|
+
return "gpt-4o-mini";
|
|
11
|
+
if (keatingModel.includes("gpt-4o"))
|
|
12
|
+
return "gpt-4o";
|
|
13
|
+
if (keatingModel.includes("claude-3-5-sonnet"))
|
|
14
|
+
return "claude-3-5-sonnet-20240620";
|
|
15
|
+
// fallback
|
|
16
|
+
return keatingModel;
|
|
17
|
+
}
|
|
18
|
+
export async function learnPrompt(cwd, promptName = "learn", options = {}) {
|
|
19
|
+
const { maxEpochs = 3 } = options;
|
|
20
|
+
const promptPath = join(cwd, "pi", "prompts", `${promptName}.md`);
|
|
21
|
+
const basePrompt = await readFile(promptPath, "utf8");
|
|
22
|
+
const config = await loadKeatingConfig(cwd);
|
|
23
|
+
const studentAI = ai({
|
|
24
|
+
name: config.pi.defaultProvider === "openai" ? "openai" : "google-gemini",
|
|
25
|
+
apiKey: process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "",
|
|
26
|
+
config: { model: mapToAxModel(config.pi.defaultModel || "google/gemini-3.1-pro-preview") }
|
|
27
|
+
});
|
|
28
|
+
const teacherAI = ai({
|
|
29
|
+
name: config.pi.defaultProvider === "openai" ? "openai" : "google-gemini",
|
|
30
|
+
apiKey: process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "",
|
|
31
|
+
config: { model: mapToAxModel("google/gemini-3.1-pro-preview") }
|
|
32
|
+
});
|
|
33
|
+
const analyzer = ax('topic:string -> teachingResponse:string');
|
|
34
|
+
analyzer.setDescription("You are Keating, a hyperteacher.");
|
|
35
|
+
// Dummy baseline examples for ACE
|
|
36
|
+
const examples = [
|
|
37
|
+
{ topic: "Functions", teachingResponse: "Functions are machines that take inputs and produce outputs." }
|
|
38
|
+
];
|
|
39
|
+
const metric = async ({ prediction, example }) => {
|
|
40
|
+
// Call the original heuristic / LLM evaluator
|
|
41
|
+
const evalResult = await evaluatePromptContent(cwd, promptPath, prediction.teachingResponse);
|
|
42
|
+
return evalResult.score / 100.0;
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
const optimizer = new AxACE({ studentAI, teacherAI, verbose: true }, { maxEpochs });
|
|
46
|
+
console.log(`Running ACE Prompt Learning for ${promptName}...`);
|
|
47
|
+
const result = await optimizer.compile(analyzer, examples, metric);
|
|
48
|
+
if (result.artifact?.playbook) {
|
|
49
|
+
const playbookPath = join(stateDir(cwd), "ace-playbook.json");
|
|
50
|
+
await writeFile(playbookPath, JSON.stringify(result.artifact.playbook, null, 2), "utf8");
|
|
51
|
+
return { playbook: result.artifact.playbook };
|
|
52
|
+
}
|
|
53
|
+
return { playbook: {} };
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn("Ax ACE prompt optimization failed or API key missing, falling back to heuristics.", error);
|
|
57
|
+
return { playbook: {} };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ax-trial.ts — Hyperparameter trial abstraction for Keating
|
|
3
|
+
*
|
|
4
|
+
* Maps TeacherPolicy fields into an Optuna-style suggest API that
|
|
5
|
+
* GEPA's multi-objective optimizer can explore. Provides the
|
|
6
|
+
* `KeatingTrial` / `optimize()` surface that mirrors the
|
|
7
|
+
* `hyperparameter` package API from the design spec.
|
|
8
|
+
*/
|
|
9
|
+
import { mean } from "./util.js";
|
|
10
|
+
import { clampPolicy, clampWeights, DEFAULT_POLICY, DEFAULT_WEIGHTS } from "./policy.js";
|
|
11
|
+
/** The full search space for Keating's TeacherPolicy hyperparameters. */
|
|
12
|
+
export const POLICY_PARAM_SPACE = [
|
|
13
|
+
{ name: "analogyDensity", type: "float", low: 0.0, high: 1.0 },
|
|
14
|
+
{ name: "socraticRatio", type: "float", low: 0.0, high: 1.0 },
|
|
15
|
+
{ name: "formalism", type: "float", low: 0.0, high: 1.0 },
|
|
16
|
+
{ name: "retrievalPractice", type: "float", low: 0.0, high: 1.0 },
|
|
17
|
+
{ name: "exerciseCount", type: "int", low: 1, high: 5 },
|
|
18
|
+
{ name: "diagramBias", type: "float", low: 0.0, high: 1.0 },
|
|
19
|
+
{ name: "reflectionBias", type: "float", low: 0.0, high: 1.0 },
|
|
20
|
+
{ name: "interdisciplinaryBias", type: "float", low: 0.0, high: 1.0 },
|
|
21
|
+
{ name: "challengeRate", type: "float", low: 0.0, high: 1.0 }
|
|
22
|
+
];
|
|
23
|
+
/** The search space for SimulationWeights (learned scoring weights). */
|
|
24
|
+
export const WEIGHT_PARAM_SPACE = [
|
|
25
|
+
{ name: "masteryGain", type: "float", low: 0.05, high: 1.0 },
|
|
26
|
+
{ name: "retention", type: "float", low: 0.05, high: 1.0 },
|
|
27
|
+
{ name: "engagement", type: "float", low: 0.05, high: 1.0 },
|
|
28
|
+
{ name: "transfer", type: "float", low: 0.05, high: 1.0 },
|
|
29
|
+
{ name: "confusion", type: "float", low: 0.05, high: 1.0 }
|
|
30
|
+
];
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Trial implementation — used internally by the optimizer
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
export class PolicyTrial {
|
|
35
|
+
values;
|
|
36
|
+
_params = {};
|
|
37
|
+
constructor(values) {
|
|
38
|
+
this.values = values;
|
|
39
|
+
}
|
|
40
|
+
get params() {
|
|
41
|
+
return { ...this._params };
|
|
42
|
+
}
|
|
43
|
+
suggestFloat(name, low, high) {
|
|
44
|
+
const value = this.values[name] ?? (low + high) / 2;
|
|
45
|
+
const clamped = Math.max(low, Math.min(high, value));
|
|
46
|
+
this._params[name] = clamped;
|
|
47
|
+
return clamped;
|
|
48
|
+
}
|
|
49
|
+
suggestInt(name, low, high) {
|
|
50
|
+
const value = this.values[name] ?? Math.round((low + high) / 2);
|
|
51
|
+
const clamped = Math.max(low, Math.min(high, Math.round(value)));
|
|
52
|
+
this._params[name] = clamped;
|
|
53
|
+
return clamped;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Conversion helpers
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
/**
|
|
60
|
+
* Build a TeacherPolicy from a trial's suggested parameters.
|
|
61
|
+
* Missing fields fall back to DEFAULT_POLICY.
|
|
62
|
+
*/
|
|
63
|
+
export function trialToPolicy(trial, label) {
|
|
64
|
+
const params = trial.params;
|
|
65
|
+
return clampPolicy({
|
|
66
|
+
name: label,
|
|
67
|
+
analogyDensity: params.analogyDensity ?? DEFAULT_POLICY.analogyDensity,
|
|
68
|
+
socraticRatio: params.socraticRatio ?? DEFAULT_POLICY.socraticRatio,
|
|
69
|
+
formalism: params.formalism ?? DEFAULT_POLICY.formalism,
|
|
70
|
+
retrievalPractice: params.retrievalPractice ?? DEFAULT_POLICY.retrievalPractice,
|
|
71
|
+
exerciseCount: params.exerciseCount ?? DEFAULT_POLICY.exerciseCount,
|
|
72
|
+
diagramBias: params.diagramBias ?? DEFAULT_POLICY.diagramBias,
|
|
73
|
+
reflectionBias: params.reflectionBias ?? DEFAULT_POLICY.reflectionBias,
|
|
74
|
+
interdisciplinaryBias: params.interdisciplinaryBias ?? DEFAULT_POLICY.interdisciplinaryBias,
|
|
75
|
+
challengeRate: params.challengeRate ?? DEFAULT_POLICY.challengeRate
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build SimulationWeights from a trial's suggested parameters.
|
|
80
|
+
* Missing fields fall back to DEFAULT_WEIGHTS.
|
|
81
|
+
*/
|
|
82
|
+
export function trialToWeights(trial) {
|
|
83
|
+
const params = trial.params;
|
|
84
|
+
return clampWeights({
|
|
85
|
+
masteryGain: params.masteryGain ?? DEFAULT_WEIGHTS.masteryGain,
|
|
86
|
+
retention: params.retention ?? DEFAULT_WEIGHTS.retention,
|
|
87
|
+
engagement: params.engagement ?? DEFAULT_WEIGHTS.engagement,
|
|
88
|
+
transfer: params.transfer ?? DEFAULT_WEIGHTS.transfer,
|
|
89
|
+
confusion: params.confusion ?? DEFAULT_WEIGHTS.confusion
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Convert an existing TeacherPolicy into a parameter dict suitable for PolicyTrial.
|
|
94
|
+
*/
|
|
95
|
+
export function policyToParams(policy) {
|
|
96
|
+
return {
|
|
97
|
+
analogyDensity: policy.analogyDensity,
|
|
98
|
+
socraticRatio: policy.socraticRatio,
|
|
99
|
+
formalism: policy.formalism,
|
|
100
|
+
retrievalPractice: policy.retrievalPractice,
|
|
101
|
+
exerciseCount: policy.exerciseCount,
|
|
102
|
+
diagramBias: policy.diagramBias,
|
|
103
|
+
reflectionBias: policy.reflectionBias,
|
|
104
|
+
interdisciplinaryBias: policy.interdisciplinaryBias,
|
|
105
|
+
challengeRate: policy.challengeRate
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function weightsToParams(weights) {
|
|
109
|
+
return {
|
|
110
|
+
masteryGain: weights.masteryGain,
|
|
111
|
+
retention: weights.retention,
|
|
112
|
+
engagement: weights.engagement,
|
|
113
|
+
transfer: weights.transfer,
|
|
114
|
+
confusion: weights.confusion
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A basic multi-objective optimizer that returns the Pareto frontier.
|
|
119
|
+
*/
|
|
120
|
+
export async function optimize(objective, options) {
|
|
121
|
+
const { nTrials, seed = Date.now() } = options;
|
|
122
|
+
const prng = new (await import("./random.js")).Prng(seed);
|
|
123
|
+
const trials = [];
|
|
124
|
+
for (let i = 0; i < nTrials; i++) {
|
|
125
|
+
const values = {};
|
|
126
|
+
for (const range of POLICY_PARAM_SPACE) {
|
|
127
|
+
values[range.name] = range.type === "float"
|
|
128
|
+
? range.low + prng.next() * (range.high - range.low)
|
|
129
|
+
: Math.round(range.low + prng.next() * (range.high - range.low));
|
|
130
|
+
}
|
|
131
|
+
for (const range of WEIGHT_PARAM_SPACE) {
|
|
132
|
+
values[range.name] = range.type === "float"
|
|
133
|
+
? range.low + prng.next() * (range.high - range.low)
|
|
134
|
+
: Math.round(range.low + prng.next() * (range.high - range.low));
|
|
135
|
+
}
|
|
136
|
+
const trial = new PolicyTrial(values);
|
|
137
|
+
const scores = await objective(trial);
|
|
138
|
+
trials.push({ params: trial.params, scores });
|
|
139
|
+
}
|
|
140
|
+
// Compute Pareto frontier
|
|
141
|
+
const paretoFront = trials.filter((t1) => {
|
|
142
|
+
// A trial is Pareto-optimal if no other trial dominates it
|
|
143
|
+
return !trials.some((t2) => {
|
|
144
|
+
if (t1 === t2)
|
|
145
|
+
return false;
|
|
146
|
+
const objectives = Object.keys(t1.scores);
|
|
147
|
+
// t2 dominates t1 if:
|
|
148
|
+
// 1. t2 is better than or equal to t1 in all objectives
|
|
149
|
+
// 2. t2 is strictly better than t1 in at least one objective
|
|
150
|
+
let betterOrEqual = true;
|
|
151
|
+
let strictlyBetter = false;
|
|
152
|
+
for (const obj of objectives) {
|
|
153
|
+
const v1 = t1.scores[obj];
|
|
154
|
+
const v2 = t2.scores[obj];
|
|
155
|
+
if (options.direction === "maximize") {
|
|
156
|
+
if (v2 < v1)
|
|
157
|
+
betterOrEqual = false;
|
|
158
|
+
if (v2 > v1)
|
|
159
|
+
strictlyBetter = true;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
if (v2 > v1)
|
|
163
|
+
betterOrEqual = false;
|
|
164
|
+
if (v2 < v1)
|
|
165
|
+
strictlyBetter = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return betterOrEqual && strictlyBetter;
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
// Pick "best" by simple mean of scores
|
|
172
|
+
const best = paretoFront.sort((a, b) => {
|
|
173
|
+
const meanA = mean(Object.values(a.scores));
|
|
174
|
+
const meanB = mean(Object.values(b.scores));
|
|
175
|
+
return meanB - meanA;
|
|
176
|
+
})[0] || trials[0];
|
|
177
|
+
return {
|
|
178
|
+
bestParams: best.params,
|
|
179
|
+
paretoFront
|
|
180
|
+
};
|
|
181
|
+
}
|