@thanh01.pmt/curriculum-kit 1.2.0 → 1.3.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.
@@ -13,6 +13,7 @@ interface SingleArtifactRequest {
13
13
  studentCount?: string;
14
14
  language?: string;
15
15
  sectionLanguageContract?: Record<string, Record<string, string>> | string;
16
+ slidesEngine?: 'html' | 'marp';
16
17
  failClosedContext?: boolean;
17
18
  extraContext?: string;
18
19
  config?: Record<string, any>;
@@ -13,6 +13,7 @@ interface SingleArtifactRequest {
13
13
  studentCount?: string;
14
14
  language?: string;
15
15
  sectionLanguageContract?: Record<string, Record<string, string>> | string;
16
+ slidesEngine?: 'html' | 'marp';
16
17
  failClosedContext?: boolean;
17
18
  extraContext?: string;
18
19
  config?: Record<string, any>;
package/dist/index.cjs CHANGED
@@ -5432,6 +5432,54 @@ ${input.languageDirective}
5432
5432
 
5433
5433
  ${input.headingDirective}`.trim();
5434
5434
  }
5435
+ function buildHtmlDeckSlidePrompt(input) {
5436
+ const contract = input.customContract || `## HTML SLIDE ENGINE \u2014 AGENT GENERATION CONTRACT
5437
+ You generate structured slide decks for the LearnWell HTML Slide Presentation Engine.
5438
+ Your output MUST be a single, valid JSON object matching the Slide Deck JSON schema below.
5439
+
5440
+ ### \u{1F3A8} Available Layout Presets (Use layoutId):
5441
+ 1. **hero-cover**: Title/Cover slide with title, subtitle, presenter, date, tag.
5442
+ 2. **two-column-split**: Side-by-side comparison (col1Title, col1Items[], col2Title, col2Items[]).
5443
+ 3. **code-explainer**: Code walkthrough with filename, language, code, highlightLines, annotations[].
5444
+ 4. **metrics-3-card**: 3 key stat cards (card1Title, card1Val, card1Desc, card2..., card3...).
5445
+ 5. **process-flow**: Step-by-step pipeline (steps: [{ label, desc, status }]).
5446
+ 6. **quiz-checkpoint**: Quick multiple choice question (question, options[], answerIdx, explanation).
5447
+ 7. **tiered-practice-3cards**: Differentiated tiers (foundation, standard, challenge cards).
5448
+ 8. **takeaways-summary**: Summary review with bullet takeaways.
5449
+
5450
+ ### \u{1F399}\uFE0F Mandatory 3-Part Presenter Notes on EVERY slide:
5451
+ Every slide MUST include notes with:
5452
+ - Talk Track: 2-3 sentences speaking script
5453
+ - Cold-Call: 1 check question
5454
+ - Scaffolding Tip: 1 analogy or hint
5455
+
5456
+ ### Output JSON Format:
5457
+ \`\`\`json
5458
+ {
5459
+ "title": "${input.lessonTitle}",
5460
+ "theme": "ocean",
5461
+ "slides": [
5462
+ {
5463
+ "layoutId": "hero-cover",
5464
+ "slots": { "title": "${input.lessonTitle}", "subtitle": "..." },
5465
+ "notes": "Talk Track: ...\\nCold-Call: ...\\nScaffolding Tip: ..."
5466
+ }
5467
+ ]
5468
+ }
5469
+ \`\`\``;
5470
+ return `You are @illustrator & @content (Interactive HTML Slide Deck Presentation Architects).
5471
+ Based on Master Lesson Plan \`LESSON_${input.lessonCode}.md\`, pedagogical model (${input.pedagogyLabel}), and the HTML SLIDE ENGINE CONTRACT below, author the complete, highly engaging HTML presentation deck in JSON format: \`SLIDE_${input.lessonCode}\`.
5472
+
5473
+ ${contract}
5474
+
5475
+ ${input.languageDirective}
5476
+
5477
+ ${input.headingDirective}${input.glossaryBlock ? `
5478
+
5479
+ ${input.glossaryBlock}` : ""}
5480
+
5481
+ Output ONLY the raw JSON object. Do not include extra conversational text or markdown code fences.`.trim();
5482
+ }
5435
5483
 
5436
5484
  // src/ai/prompts/teacherGuidePrompt.ts
5437
5485
  function buildTeacherGuidePrompt(input) {
@@ -5571,13 +5619,13 @@ Evaluate the candidate artifact across these 6 dimensions:
5571
5619
  - 0 pts: Missing critical sections.
5572
5620
 
5573
5621
  5. **Technical & Conceptual Accuracy (15 pts):**
5574
- - 15 pts: Code snippets, logic flows, architectural descriptions, and Mermaid diagrams are conceptually sound, valid, and free of syntax hallucinations.
5622
+ - 15 pts: Code snippets, logic flows, architectural descriptions, and Mermaid diagrams are conceptually sound and valid. If \`canonicalExposition\` or \`[KNOWLEDGE_EXPOSITION]\` is provided, content strictly adheres to canonical concepts without contradicting definitions or examples.
5575
5623
  - 8 pts: Minor syntax inaccuracies or unidiomatic code that does not break core concepts.
5576
- - 0 pts: Severe technical hallucinations, broken logic, or invalid diagrams.
5624
+ - 0 pts: Severe technical hallucinations, broken logic, invalid diagrams, or direct contradiction of canonical exposition knowledge.
5577
5625
 
5578
5626
  6. **Contract Consistency & Zero-Drift (10 pts):**
5579
- - 10 pts: Satellite artifact strictly adheres to the scope, tech stack, and objectives of Lesson ${lessonId} without introducing unrelated topics.
5580
- - 0-5 pts: Scope drift or topic divergence detected.
5627
+ - 10 pts: Satellite artifact strictly adheres to the scope, tech stack, and objectives of Lesson ${lessonId} without introducing unrelated topics. If \`canonicalLessonExcerpt\` is present in candidate JSON, all satellite phases/activities strictly operationalize the master Lesson Flow and Activity Sequence without inventing divergent timelines.
5628
+ - 0-5 pts: Scope drift, topic divergence, or disjoint activity sequence detected compared to master lesson.
5581
5629
 
5582
5630
  ## VERDICT RULES
5583
5631
  - **PASS:** Score >= 80 AND Language Adherence = true AND Standards Alignment >= 12 (when STANDARDS GROUND TRUTH is present).
@@ -11872,11 +11920,42 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11872
11920
  }
11873
11921
  const pedagogy = detectProjectPedagogy(framework, styleGuide, options.pedagogy);
11874
11922
  const pedagogyLabel = pedagogy.toUpperCase();
11875
- const styleGuideExcerpt = styleGuide ? buildSectionAwareExcerpt(styleGuide, {
11923
+ let glossaryContext = "";
11924
+ try {
11925
+ const glossaryRaw = await storage.readArtifact(projectId, "_sot/GLOSSARY.json");
11926
+ if (glossaryRaw) {
11927
+ let allTerms = [];
11928
+ try {
11929
+ allTerms = JSON.parse(glossaryRaw).terms ?? [];
11930
+ } catch {
11931
+ }
11932
+ let scopedTermsSet = null;
11933
+ const planRaw = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
11934
+ if (planRaw) {
11935
+ try {
11936
+ const plan = JSON.parse(planRaw);
11937
+ const sessionScope = plan.glossary_scope?.find((g) => g.session_id === lessonCode);
11938
+ if (sessionScope?.terms?.length) {
11939
+ scopedTermsSet = new Set(sessionScope.terms.map((t) => t.toLowerCase()));
11940
+ }
11941
+ } catch {
11942
+ }
11943
+ }
11944
+ const relevantTerms = scopedTermsSet ? allTerms.filter((g) => scopedTermsSet.has(g.term.toLowerCase())) : allTerms.slice(0, 10);
11945
+ if (relevantTerms.length > 0) {
11946
+ const lines = relevantTerms.map(
11947
+ (t) => `- **${t.term}**: ${t.definition || ""}${t.example ? ` (e.g. \`${t.example}\`)` : ""}`
11948
+ );
11949
+ glossaryContext = buildSectionAwareExcerpt(lines.join("\n"), { budget: 800 }).excerpt;
11950
+ }
11951
+ }
11952
+ } catch {
11953
+ }
11954
+ let effectiveStyleGuide = styleGuide ? buildSectionAwareExcerpt(styleGuide, {
11876
11955
  priorities: ["Voice & Tone", "Standard Terminology (Glossary)", "Lesson Content Standards"],
11877
11956
  budget: 2e3
11878
11957
  }).excerpt : "";
11879
- const refPackExcerpt = refPack ? buildSectionAwareExcerpt(refPack, {
11958
+ let effectiveRefPack = refPack ? buildSectionAwareExcerpt(refPack, {
11880
11959
  priorities: [
11881
11960
  "Technical Overview & Architecture Blueprint",
11882
11961
  "Hardware Pinout & Wiring Configuration Matrix",
@@ -11884,7 +11963,7 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11884
11963
  ],
11885
11964
  budget: 2500
11886
11965
  }).excerpt : "";
11887
- const commonContext = `PROJECT & ACADEMIC CONTEXT:
11966
+ const baseContextPrefix = `PROJECT & ACADEMIC CONTEXT:
11888
11967
  - Project ID: ${projectId}
11889
11968
  - Pedagogical Model: ${pedagogyLabel}
11890
11969
  - Lesson Code: ${lessonCode}
@@ -11894,13 +11973,51 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11894
11973
  - Target Bloom Level: ${bloomLevel}
11895
11974
 
11896
11975
  [CURRICULUM FRAMEWORK EXCERPT]:
11897
- ${buildFrameworkExcerptForLesson(framework, lessonId)}
11976
+ ${buildFrameworkExcerptForLesson(framework, lessonId)}`;
11977
+ const glossaryBlock = glossaryContext ? `
11978
+
11979
+ [GLOSSARY TERMS (use these exact definitions)]:
11980
+ ${glossaryContext}` : "";
11981
+ const standardsBlock = standardsContext ? `
11982
+
11983
+ ${standardsContext}` : "";
11984
+ const expositionBlock = expositionContext ? `
11985
+
11986
+ [KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)]:
11987
+ ${expositionContext}` : "";
11988
+ const sessionSliceBlock = sessionSliceContext ? `
11989
+
11990
+ ${sessionSliceContext}` : "";
11991
+ const assembleCommonContext = (ref, sg) => `${baseContextPrefix}
11898
11992
 
11899
11993
  [CONTENT STYLE GUIDE EXCERPT]:
11900
- ${styleGuideExcerpt}
11994
+ ${sg}
11901
11995
 
11902
11996
  [REFERENCE PACK GROUND TRUTH]:
11903
- ${refPackExcerpt}${standardsContext ? "\n\n" + standardsContext : ""}${expositionContext ? "\n\n[KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)]:\n" + expositionContext : ""}${sessionSliceContext ? "\n\n" + sessionSliceContext : ""}`;
11997
+ ${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}`;
11998
+ let commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
11999
+ const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
12000
+ if (commonContext.length > MAX_COMPOSITE_PROMPT_CHARS && effectiveRefPack.length > 1e3) {
12001
+ effectiveRefPack = buildSectionAwareExcerpt(refPack, {
12002
+ priorities: [
12003
+ "Technical Overview & Architecture Blueprint",
12004
+ "Hardware Pinout & Wiring Configuration Matrix"
12005
+ ],
12006
+ budget: 1e3
12007
+ }).excerpt;
12008
+ commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
12009
+ }
12010
+ if (commonContext.length > MAX_COMPOSITE_PROMPT_CHARS && effectiveStyleGuide.length > 1e3) {
12011
+ effectiveStyleGuide = buildSectionAwareExcerpt(styleGuide, {
12012
+ priorities: ["Voice & Tone", "Standard Terminology (Glossary)"],
12013
+ budget: 1e3
12014
+ }).excerpt;
12015
+ commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
12016
+ }
12017
+ onProgress?.("@content", `[CONTEXT] Composite prompt ready (${commonContext.length} chars, adaptive budget <= ${MAX_COMPOSITE_PROMPT_CHARS})`, {
12018
+ type: "progress",
12019
+ promptChars: commonContext.length
12020
+ });
11904
12021
  const producedArtifacts = [];
11905
12022
  const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
11906
12023
  const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
@@ -12027,7 +12144,7 @@ ${yamlBlock.trim()}
12027
12144
  lessonId: lessonCode,
12028
12145
  title: lessonTitle,
12029
12146
  frameworkExcerpt: framework ? buildFrameworkExcerptForLesson(framework, lessonId) : void 0,
12030
- styleGuideExcerpt: styleGuideExcerpt || void 0,
12147
+ styleGuideExcerpt: effectiveStyleGuide || void 0,
12031
12148
  canonicalExposition: expositionContext ? expositionContext.slice(0, 3e3) : void 0
12032
12149
  }),
12033
12150
  standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0
@@ -12070,7 +12187,7 @@ ${yamlBlock.trim()}
12070
12187
  onProgress?.("@repair", `\u{1F527} Repair round ${round}/${MAX_REPAIR_ROUNDS} for LESSON_${lessonCode} (judge score ${currentResult.score}/100)...`);
12071
12188
  const repairedRaw = await runCurriculumAIInference(
12072
12189
  [{ role: "user", content: `You previously authored \`LESSON_${lessonCode}.md\` and the academic Judge REJECTED it with the critique below.
12073
- Rewrite the COMPLETE corrected document: fix every listed issue, keep the mandatory structure, frontmatter and all approved sections. Output 100% clean Markdown only.
12190
+ Rewrite the COMPLETE corrected document in language "${targetLang}": fix every listed issue, preserve exact frontmatter and mandatory sections (Objectives, Activity Sequence, Lesson Flow). Output 100% clean Markdown only.
12074
12191
 
12075
12192
  JUDGE CRITIQUE:
12076
12193
  ${repairFeedback}
@@ -12088,7 +12205,13 @@ ${currentContent}` }],
12088
12205
  break;
12089
12206
  }
12090
12207
  const repairLint = lintAndSanitizeArtifact(repairedRaw, `LESSON_${lessonCode}.md`);
12091
- currentContent = repairLint.autoFixedContent || repairedRaw;
12208
+ const candidateContent = repairLint.autoFixedContent || repairedRaw;
12209
+ const hasFrontmatter = /^---\s*\r?\n[\s\S]*?\r?\n---/m.test(candidateContent);
12210
+ if (!hasFrontmatter || candidateContent.length < Math.min(800, currentContent.length * 0.4)) {
12211
+ onProgress?.("@repair", `\u26A0\uFE0F Repair round ${round} dropped critical structure (length or frontmatter missing) \u2014 rejecting repaired draft, retaining previous`);
12212
+ break;
12213
+ }
12214
+ currentContent = candidateContent;
12092
12215
  onProgress?.("@reviewer", `\u{1F916} Re-judging repaired LESSON_${lessonCode} (round ${round})...`);
12093
12216
  const reJudge = await LLMJudgeEngine.evaluateArtifactWithLLM({
12094
12217
  targetArtifactType: "LESSON",
@@ -12100,7 +12223,7 @@ ${currentContent}` }],
12100
12223
  lessonId: lessonCode,
12101
12224
  title: lessonTitle,
12102
12225
  frameworkExcerpt: framework ? buildFrameworkExcerptForLesson(framework, lessonId) : void 0,
12103
- styleGuideExcerpt: styleGuideExcerpt || void 0,
12226
+ styleGuideExcerpt: effectiveStyleGuide || void 0,
12104
12227
  canonicalExposition: expositionContext ? expositionContext.slice(0, 3e3) : void 0
12105
12228
  }),
12106
12229
  standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0
@@ -12387,46 +12510,175 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
12387
12510
  const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
12388
12511
  if (artifactScope.includes("SLIDE") && (!await storage.readArtifact(projectId, slideRelPath) || force)) {
12389
12512
  onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
12390
- const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12391
- const templateScaffold = canonicalSlideTemplate || `---
12513
+ const isHtmlEngine = options.slidesEngine === "html";
12514
+ if (isHtmlEngine) {
12515
+ let presentationKitAi = null;
12516
+ try {
12517
+ presentationKitAi = await import('@thanh01.pmt/presentation-kit/ai');
12518
+ } catch {
12519
+ }
12520
+ const slidePrompt = buildHtmlDeckSlidePrompt({
12521
+ lessonCode,
12522
+ lessonTitle,
12523
+ pedagogyLabel,
12524
+ languageDirective,
12525
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
12526
+ glossaryBlock: glossaryContext ? `[GLOSSARY TERMS (use these exact definitions)]:
12527
+ ${glossaryContext}` : void 0,
12528
+ customContract: presentationKitAi?.SLIDE_HTML_PROMPT_CONTRACT
12529
+ });
12530
+ const rawSlide = await runCurriculumAIInference(
12531
+ [{ role: "user", content: slidePrompt }],
12532
+ satelliteContext,
12533
+ {},
12534
+ (chunk, type) => {
12535
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12536
+ }
12537
+ );
12538
+ if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12539
+ throw createAiInferenceError({
12540
+ errorCode: "ERR_AI_RESPONSE_MALFORMED",
12541
+ agent: "@illustrator",
12542
+ artifactType: "SLIDE",
12543
+ lessonId: lessonCode,
12544
+ message: `Agent @illustrator failed to author valid HTML Slides for ${lessonCode}.`,
12545
+ rawError: rawSlide || "Empty AI response"
12546
+ });
12547
+ }
12548
+ let deckJson = null;
12549
+ const extractCleanJson = (str) => {
12550
+ const fenceMatch = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
12551
+ const candidate = fenceMatch ? fenceMatch[1] : str;
12552
+ return candidate.trim();
12553
+ };
12554
+ try {
12555
+ deckJson = JSON.parse(extractCleanJson(rawSlide));
12556
+ } catch {
12557
+ try {
12558
+ const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
12559
+ deckJson = JSON.parse(jsonrepair2(extractCleanJson(rawSlide)));
12560
+ } catch {
12561
+ }
12562
+ }
12563
+ let validation = presentationKitAi?.validateHtmlSlideDeck ? presentationKitAi.validateHtmlSlideDeck(deckJson, true) : deckJson && Array.isArray(deckJson.slides) ? { valid: true, score: 95, issues: [], slideCount: deckJson.slides.length, notesCoveragePercent: 100 } : { valid: false, score: 0, issues: [{ message: "Malformed JSON slide deck" }], slideCount: 0, notesCoveragePercent: 0 };
12564
+ if (!validation.valid && presentationKitAi?.validateHtmlSlideDeck) {
12565
+ const repairIssues = (validation.issues || []).map((i) => `- Slide ${i.slideIndex ?? "?"}: ${i.message}`).join("\n");
12566
+ onProgress?.("@illustrator", `\u26A0\uFE0F HTML slide deck failed schema validation \u2014 retrying once with targeted repair feedback...`);
12567
+ const repairPrompt = `Your previous slide deck failed strict schema validation:
12568
+ ${repairIssues}
12569
+
12570
+ Fix ALL issues and output the complete corrected JSON object strictly matching the schema:`;
12571
+ const repairedRaw = await runCurriculumAIInference(
12572
+ [{ role: "user", content: repairPrompt }],
12573
+ satelliteContext,
12574
+ {},
12575
+ (chunk, type) => {
12576
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12577
+ }
12578
+ );
12579
+ try {
12580
+ deckJson = JSON.parse(extractCleanJson(repairedRaw));
12581
+ validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
12582
+ } catch {
12583
+ try {
12584
+ const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
12585
+ deckJson = JSON.parse(jsonrepair2(extractCleanJson(repairedRaw)));
12586
+ validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
12587
+ } catch {
12588
+ }
12589
+ }
12590
+ }
12591
+ const markdownWrapper = `---
12592
+ id: "SLIDE_${lessonCode}"
12593
+ title: "${titleHeader(lessonTitle)}"
12594
+ type: "SLIDE"
12595
+ format: "html-deck"
12596
+ engine: "html"
12597
+ phase: "P2"
12598
+ deliverable: "P2-T09"
12599
+ version: "v1.0"
12600
+ date: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
12601
+ ---
12602
+
12603
+ \`\`\`json
12604
+ ${JSON.stringify(deckJson || {}, null, 2)}
12605
+ \`\`\`
12606
+ `;
12607
+ await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
12608
+ producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12609
+ if (deckJson) {
12610
+ const deckJsonStr = JSON.stringify(deckJson, null, 2);
12611
+ await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.deck.json`, deckJsonStr);
12612
+ producedArtifacts.push(`SLIDE_${lessonCode}.deck.json`);
12613
+ if (presentationKitAi?.compileHtmlDeck) {
12614
+ try {
12615
+ const compiled = presentationKitAi.compileHtmlDeck(deckJson);
12616
+ if (compiled?.html) {
12617
+ await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.html`, compiled.html);
12618
+ producedArtifacts.push(`SLIDE_${lessonCode}.html`);
12619
+ }
12620
+ } catch (compErr) {
12621
+ console.warn(`[produceSingleLesson] Failed compiling HTML deck for ${lessonCode}:`, compErr);
12622
+ }
12623
+ }
12624
+ }
12625
+ const passed = Boolean(validation.valid && (validation.score ?? 100) >= 80);
12626
+ const score = validation.score ?? (passed ? 95 : 50);
12627
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12628
+ state: passed ? "approved" : "rejected",
12629
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12630
+ contentHash: computeContentHash(markdownWrapper),
12631
+ review: {
12632
+ decision: passed ? "APPROVED" : "NEEDS_REVISION",
12633
+ reviewedBy: "@agent-as-judge",
12634
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
12635
+ score,
12636
+ critique: passed ? `HTML Slide Deck passed strict schema validation (${score}/100, ${validation.slideCount ?? deckJson?.slides?.length ?? 0} slides). Auto-approved.` : `HTML Slide Deck failed schema validation: ${(validation.issues || []).map((i) => i.message).join("; ")}`
12637
+ }
12638
+ });
12639
+ onProgress?.("@reviewer", passed ? `\u2705 SLIDE judge PASS (${score}/100) [html-deck schema validated]` : `\u26A0\uFE0F SLIDE judge NEEDS_REVISION (${score}/100) [html-deck schema issues]`);
12640
+ } else {
12641
+ const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12642
+ const templateScaffold = canonicalSlideTemplate || `---
12392
12643
  marp: true
12393
12644
  theme: default
12394
12645
  paginate: true
12395
12646
  header: "${titleHeader(lessonTitle)}"
12396
12647
  footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
12397
12648
  ---`;
12398
- const slidePrompt = buildMarpMarkdownSlidePrompt({
12399
- lessonCode,
12400
- templateScaffold,
12401
- languageDirective,
12402
- headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12403
- });
12404
- const rawSlide = await runCurriculumAIInference(
12405
- [{ role: "user", content: slidePrompt }],
12406
- satelliteContext,
12407
- {},
12408
- (chunk, type) => {
12409
- options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12649
+ const slidePrompt = buildMarpMarkdownSlidePrompt({
12650
+ lessonCode,
12651
+ templateScaffold,
12652
+ languageDirective,
12653
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12654
+ });
12655
+ const rawSlide = await runCurriculumAIInference(
12656
+ [{ role: "user", content: slidePrompt }],
12657
+ satelliteContext,
12658
+ {},
12659
+ (chunk, type) => {
12660
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12661
+ }
12662
+ );
12663
+ if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12664
+ throw createAiInferenceError({
12665
+ errorCode: "ERR_AI_RESPONSE_MALFORMED",
12666
+ agent: "@illustrator",
12667
+ artifactType: "SLIDE",
12668
+ lessonId: lessonCode,
12669
+ message: `Agent @illustrator failed to author valid Slides for ${lessonCode}.`,
12670
+ rawError: rawSlide || "Empty AI response"
12671
+ });
12410
12672
  }
12411
- );
12412
- if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12413
- throw createAiInferenceError({
12414
- errorCode: "ERR_AI_RESPONSE_MALFORMED",
12415
- agent: "@illustrator",
12416
- artifactType: "SLIDE",
12417
- lessonId: lessonCode,
12418
- message: `Agent @illustrator failed to author valid Slides for ${lessonCode}.`,
12419
- rawError: rawSlide || "Empty AI response"
12673
+ await storage.saveArtifact(projectId, slideRelPath, rawSlide);
12674
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12675
+ state: "completed",
12676
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12677
+ contentHash: computeContentHash(rawSlide)
12420
12678
  });
12679
+ producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12680
+ await judgeSat("SLIDE", rawSlide);
12421
12681
  }
12422
- await storage.saveArtifact(projectId, slideRelPath, rawSlide);
12423
- await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12424
- state: "completed",
12425
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12426
- contentHash: computeContentHash(rawSlide)
12427
- });
12428
- producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12429
- await judgeSat("SLIDE", rawSlide);
12430
12682
  }
12431
12683
  const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
12432
12684
  if (artifactScope.includes("GUIDE") && (!await storage.readArtifact(projectId, guideRelPath) || force)) {
@@ -13369,6 +13621,24 @@ function getArtifactSpecializedInvariants(type, req) {
13369
13621
  const studentCount = req.studentCount || "25";
13370
13622
  switch (type) {
13371
13623
  case "slide": {
13624
+ if (req.slidesEngine === "html") {
13625
+ return `
13626
+ ### \u{1F3AC} INTERACTIVE HTML SLIDE ENGINE CONTRACT:
13627
+ You generate structured slide decks for the LearnWell HTML Slide Presentation Engine.
13628
+ Output a single valid JSON object matching: { "title": "...", "theme": "ocean", "slides": [{ "layoutId": "hero-cover", "slots": { ... }, "notes": "..." }] }
13629
+
13630
+ Layout Presets available:
13631
+ - hero-cover (title, subtitle, presenter, date, tag)
13632
+ - two-column-split (col1Title, col1Items[], col2Title, col2Items[])
13633
+ - code-explainer (filename, language, code, highlightLines, annotations[])
13634
+ - metrics-3-card (card1Title, card1Val, card1Desc, ...)
13635
+ - process-flow (steps: [{ label, desc, status }])
13636
+ - quiz-checkpoint (question, options[], answerIdx, explanation)
13637
+ - tiered-practice-3cards (foundation, standard, challenge cards)
13638
+ - takeaways-summary (takeaways: bullet points)
13639
+
13640
+ Mandatory Presenter Notes on every slide with Talk Track, Cold-Call, and Scaffolding Tip.`;
13641
+ }
13372
13642
  const slideCount = cfg.slideCount || 10;
13373
13643
  const presentationStyle = cfg.presentationStyle || "modern";
13374
13644
  const presentationDuration = cfg.presentationDuration || 45;
@@ -17449,6 +17719,7 @@ exports.buildExpositionExcerpt = buildExpositionExcerpt;
17449
17719
  exports.buildExtensionPrompt = buildExtensionPrompt;
17450
17720
  exports.buildHandoutPrompt = buildHandoutPrompt;
17451
17721
  exports.buildHeadingDirective = buildHeadingDirective;
17722
+ exports.buildHtmlDeckSlidePrompt = buildHtmlDeckSlidePrompt;
17452
17723
  exports.buildImagePrompt = buildImagePrompt;
17453
17724
  exports.buildJudgePrompt = buildJudgePrompt;
17454
17725
  exports.buildLanguageDirective = buildLanguageDirective;