@thanh01.pmt/curriculum-kit 1.2.1 → 1.4.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) {
@@ -9794,6 +9842,7 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
9794
9842
  }
9795
9843
  const slcMap = normalizeSlcContract(opts.sectionLanguageContract);
9796
9844
  const sections = parseMarkdownSections(source, slcMap, opts.artifactType);
9845
+ const metaBlock = buildMetaBlock(source);
9797
9846
  if (sections.length === 0) {
9798
9847
  issues.push("parse:no-sections");
9799
9848
  const excerpt2 = truncateByBudget(source, budget);
@@ -9827,12 +9876,13 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
9827
9876
  const block = `## ${heading}
9828
9877
 
9829
9878
  ${trimmedBody}`;
9830
- if (used + block.length > budget && blocks.length > 0) return false;
9879
+ if (used + block.length > budget && used > 0) return false;
9831
9880
  blocks.push(block);
9832
9881
  used += block.length;
9833
9882
  includedSections.push(key);
9834
9883
  return true;
9835
9884
  };
9885
+ blocks.push(metaBlock);
9836
9886
  let sectionAware = false;
9837
9887
  for (const key of priorities) {
9838
9888
  const sec = byCanonical.get(key);
@@ -9849,6 +9899,9 @@ ${trimmedBody}`;
9849
9899
  if (used >= budget) break;
9850
9900
  if (!tryAdd(sec.cleanTitle, sec.body, sec.cleanTitle)) break;
9851
9901
  }
9902
+ if (includedSections.length === 0 && unmatched.length > 0) {
9903
+ issues.push("parse:no-canonical-resolution");
9904
+ }
9852
9905
  if (priorities.length > 0 && !priorities.some((k) => includedSections.includes(k))) {
9853
9906
  issues.push("parse:no-priority-resolution");
9854
9907
  }
@@ -9857,14 +9910,14 @@ ${trimmedBody}`;
9857
9910
  excerpt = truncateByBudget(excerpt, budget);
9858
9911
  }
9859
9912
  const effectiveMinChars = Math.min(minContentChars, source.length);
9860
- if (excerpt.length < effectiveMinChars) {
9913
+ if (excerpt.length - metaBlock.length < effectiveMinChars) {
9861
9914
  issues.push("content:insufficient");
9862
9915
  }
9863
- const verified = !issues.includes("parse:no-priority-resolution") && excerpt.length >= effectiveMinChars;
9916
+ const verified = !issues.includes("parse:no-priority-resolution") && !issues.includes("parse:no-canonical-resolution") && excerpt.length - metaBlock.length >= effectiveMinChars;
9864
9917
  return {
9865
9918
  excerpt,
9866
9919
  verified,
9867
- sectionAware: sectionAware || priorities.length === 0 && sections.length > 0,
9920
+ sectionAware: sectionAware || unmatched.length === 0,
9868
9921
  includedSections,
9869
9922
  issues,
9870
9923
  tokenEstimate: Math.round(excerpt.length / 4)
@@ -9980,6 +10033,18 @@ function truncateByBudget(source, budget) {
9980
10033
  if (fenceCount % 2 === 1) cut += "\n```";
9981
10034
  return cut;
9982
10035
  }
10036
+ function buildMetaBlock(source) {
10037
+ const fm = source.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
10038
+ const pick = (key) => {
10039
+ if (!fm) return "";
10040
+ const m = fm[1].match(new RegExp(`^${key}:\\s*"?([^"\\n]+)"?`, "m"));
10041
+ return (m?.[1] || "").trim();
10042
+ };
10043
+ const id = pick("id");
10044
+ const title = pick("title");
10045
+ const type = pick("type");
10046
+ return `<!-- SOURCE: ${type || "ARTIFACT"} | ${id || "unknown"}${title ? ` \u2014 ${title}` : ""} (section-aware excerpt; canonical knowledge, do not contradict) -->`;
10047
+ }
9983
10048
 
9984
10049
  // src/services/knowledgeExpositionService.ts
9985
10050
  var EXPOSITION_REL = (lessonCode) => "_sot/expositions/" + lessonCode + ".md";
@@ -10128,7 +10193,7 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
10128
10193
  storage,
10129
10194
  gates: options.gates
10130
10195
  });
10131
- return { relPath: result.relPath, context: buildExpositionContext(result.content), reused: result.reused };
10196
+ return { relPath: result.relPath, context: buildExpositionContext(result.content), reused: result.reused, rawContent: result.content };
10132
10197
  }
10133
10198
  init_errors();
10134
10199
  var asArr = (v) => Array.isArray(v) ? v : [];
@@ -11839,6 +11904,8 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11839
11904
  targetLang = targetLang || "vi";
11840
11905
  const languageDirective = buildLanguageDirective(targetLang);
11841
11906
  let expositionContext = "";
11907
+ let expositionInjection;
11908
+ let lessonInjection;
11842
11909
  let sessionSliceContext = "";
11843
11910
  try {
11844
11911
  const exposition = await ensureExpositionForLesson(storage, projectId, lessonCode, {
@@ -11846,7 +11913,18 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11846
11913
  force: options.force
11847
11914
  });
11848
11915
  if (exposition) {
11849
- expositionContext = exposition.context;
11916
+ const expoExcerpt = buildExpositionExcerpt(exposition.rawContent || exposition.context, {
11917
+ budget: 6e3,
11918
+ sectionLanguageContract: slcMarkdown,
11919
+ artifactType: "KNOWLEDGE_EXPOSITION"
11920
+ });
11921
+ expositionContext = expoExcerpt.excerpt || exposition.context;
11922
+ expositionInjection = {
11923
+ source: "KNOWLEDGE_EXPOSITION",
11924
+ verified: expoExcerpt.verified,
11925
+ sectionAware: expoExcerpt.sectionAware,
11926
+ issues: expoExcerpt.issues
11927
+ };
11850
11928
  onProgress?.("@content", `[EXPOSITION] ${exposition.reused ? "Reused" : "Generated"} \`\${exposition.relPath}\` (plan-driven knowledge source)...`);
11851
11929
  } else {
11852
11930
  onProgress?.("@content", "[EXPOSITION] No CURRICULUM_PLAN.json \u2014 running legacy knowledge flow (no plan-driven exposition).");
@@ -12086,6 +12164,7 @@ ${yamlBlock.trim()}
12086
12164
  };
12087
12165
  var injectYamlReviewMetadata = injectYamlReviewMetadata2;
12088
12166
  const judgeObjectives = parseLessonObjectives(lessonContent, concept, bloomLevel);
12167
+ const judgeExpositionExcerpt = expositionContext ? buildExpositionExcerpt(expositionContext, { budget: 3e3, sectionLanguageContract: slcMarkdown, artifactType: "KNOWLEDGE_EXPOSITION" }).excerpt : void 0;
12089
12168
  const judgeResult = await LLMJudgeEngine.evaluateArtifactWithLLM({
12090
12169
  targetArtifactType: "LESSON",
12091
12170
  lessonId: lessonCode,
@@ -12097,7 +12176,7 @@ ${yamlBlock.trim()}
12097
12176
  title: lessonTitle,
12098
12177
  frameworkExcerpt: framework ? buildFrameworkExcerptForLesson(framework, lessonId) : void 0,
12099
12178
  styleGuideExcerpt: effectiveStyleGuide || void 0,
12100
- canonicalExposition: expositionContext ? expositionContext.slice(0, 3e3) : void 0
12179
+ canonicalExposition: judgeExpositionExcerpt
12101
12180
  }),
12102
12181
  standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0
12103
12182
  });
@@ -12152,6 +12231,16 @@ ${currentContent}` }],
12152
12231
  options.onProgress?.("@repair", chunk, { type: type || "content", artifactType: "LESSON" });
12153
12232
  }
12154
12233
  );
12234
+ const repairedExcerptCheck = buildSectionAwareExcerpt(repairedRaw, {
12235
+ priorities: DEFAULT_LESSON_PRIORITIES,
12236
+ budget: 12e3,
12237
+ sectionLanguageContract: slcMarkdown,
12238
+ artifactType: "LESSON"
12239
+ });
12240
+ if (repairedExcerptCheck.issues.includes("parse:no-priority-resolution")) {
12241
+ onProgress?.("@repair", `\u26A0\uFE0F Repair round ${round} lost mandatory section structure (no priority section resolved) \u2014 rejecting repaired draft, retaining previous`);
12242
+ break;
12243
+ }
12155
12244
  if (!repairedRaw || repairedRaw.startsWith("\u26A0\uFE0F") || repairedRaw.length < 200) {
12156
12245
  onProgress?.("@repair", `\u26A0\uFE0F Repair round ${round} produced invalid output \u2014 stopping repair loop`);
12157
12246
  break;
@@ -12176,7 +12265,7 @@ ${currentContent}` }],
12176
12265
  title: lessonTitle,
12177
12266
  frameworkExcerpt: framework ? buildFrameworkExcerptForLesson(framework, lessonId) : void 0,
12178
12267
  styleGuideExcerpt: effectiveStyleGuide || void 0,
12179
- canonicalExposition: expositionContext ? expositionContext.slice(0, 3e3) : void 0
12268
+ canonicalExposition: expositionContext ? buildExpositionExcerpt(expositionContext, { budget: 3e3, sectionLanguageContract: slcMarkdown, artifactType: "KNOWLEDGE_EXPOSITION" }).excerpt : void 0
12180
12269
  }),
12181
12270
  standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0
12182
12271
  });
@@ -12234,7 +12323,9 @@ ${currentContent}` }],
12234
12323
  pedagogy,
12235
12324
  producedArtifacts,
12236
12325
  pausedForReview: true,
12237
- gateStatus: "judge_needs_revision"
12326
+ gateStatus: "judge_needs_revision",
12327
+ contextInjection: expositionInjection || lessonInjection,
12328
+ promptChars: commonContext.length
12238
12329
  };
12239
12330
  }
12240
12331
  }
@@ -12257,7 +12348,9 @@ ${currentContent}` }],
12257
12348
  pedagogy,
12258
12349
  producedArtifacts,
12259
12350
  pausedForReview: true,
12260
- gateStatus: "judge_error"
12351
+ gateStatus: "judge_error",
12352
+ contextInjection: expositionInjection,
12353
+ promptChars: commonContext.length
12261
12354
  };
12262
12355
  }
12263
12356
  }
@@ -12287,7 +12380,9 @@ ${currentContent}` }],
12287
12380
  pedagogy,
12288
12381
  producedArtifacts,
12289
12382
  pausedForReview: true,
12290
- gateStatus: "awaiting_approval"
12383
+ gateStatus: "awaiting_approval",
12384
+ contextInjection: expositionInjection,
12385
+ promptChars: commonContext.length
12291
12386
  };
12292
12387
  }
12293
12388
  if (isLessonApproved && currentLessonInfo?.state !== "approved") {
@@ -12304,6 +12399,13 @@ ${currentContent}` }],
12304
12399
  artifactType: "LESSON"
12305
12400
  });
12306
12401
  const lessonExcerpt = lessonExcerptResult.excerpt;
12402
+ const lessonInjectionMeta = {
12403
+ source: "LESSON",
12404
+ verified: lessonExcerptResult.verified,
12405
+ sectionAware: lessonExcerptResult.sectionAware,
12406
+ issues: lessonExcerptResult.issues
12407
+ };
12408
+ lessonInjection = lessonInjectionMeta;
12307
12409
  const satelliteContext = `${commonContext}
12308
12410
 
12309
12411
  [CANONICAL LESSON PLAN (${pedagogyLabel})]:
@@ -12462,46 +12564,175 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
12462
12564
  const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
12463
12565
  if (artifactScope.includes("SLIDE") && (!await storage.readArtifact(projectId, slideRelPath) || force)) {
12464
12566
  onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
12465
- const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12466
- const templateScaffold = canonicalSlideTemplate || `---
12567
+ const isHtmlEngine = options.slidesEngine === "html";
12568
+ if (isHtmlEngine) {
12569
+ let presentationKitAi = null;
12570
+ try {
12571
+ presentationKitAi = await import('@thanh01.pmt/presentation-kit/ai');
12572
+ } catch {
12573
+ }
12574
+ const slidePrompt = buildHtmlDeckSlidePrompt({
12575
+ lessonCode,
12576
+ lessonTitle,
12577
+ pedagogyLabel,
12578
+ languageDirective,
12579
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
12580
+ glossaryBlock: glossaryContext ? `[GLOSSARY TERMS (use these exact definitions)]:
12581
+ ${glossaryContext}` : void 0,
12582
+ customContract: presentationKitAi?.SLIDE_HTML_PROMPT_CONTRACT
12583
+ });
12584
+ const rawSlide = await runCurriculumAIInference(
12585
+ [{ role: "user", content: slidePrompt }],
12586
+ satelliteContext,
12587
+ {},
12588
+ (chunk, type) => {
12589
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12590
+ }
12591
+ );
12592
+ if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12593
+ throw createAiInferenceError({
12594
+ errorCode: "ERR_AI_RESPONSE_MALFORMED",
12595
+ agent: "@illustrator",
12596
+ artifactType: "SLIDE",
12597
+ lessonId: lessonCode,
12598
+ message: `Agent @illustrator failed to author valid HTML Slides for ${lessonCode}.`,
12599
+ rawError: rawSlide || "Empty AI response"
12600
+ });
12601
+ }
12602
+ let deckJson = null;
12603
+ const extractCleanJson = (str) => {
12604
+ const fenceMatch = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
12605
+ const candidate = fenceMatch ? fenceMatch[1] : str;
12606
+ return candidate.trim();
12607
+ };
12608
+ try {
12609
+ deckJson = JSON.parse(extractCleanJson(rawSlide));
12610
+ } catch {
12611
+ try {
12612
+ const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
12613
+ deckJson = JSON.parse(jsonrepair2(extractCleanJson(rawSlide)));
12614
+ } catch {
12615
+ }
12616
+ }
12617
+ 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 };
12618
+ if (!validation.valid && presentationKitAi?.validateHtmlSlideDeck) {
12619
+ const repairIssues = (validation.issues || []).map((i) => `- Slide ${i.slideIndex ?? "?"}: ${i.message}`).join("\n");
12620
+ onProgress?.("@illustrator", `\u26A0\uFE0F HTML slide deck failed schema validation \u2014 retrying once with targeted repair feedback...`);
12621
+ const repairPrompt = `Your previous slide deck failed strict schema validation:
12622
+ ${repairIssues}
12623
+
12624
+ Fix ALL issues and output the complete corrected JSON object strictly matching the schema:`;
12625
+ const repairedRaw = await runCurriculumAIInference(
12626
+ [{ role: "user", content: repairPrompt }],
12627
+ satelliteContext,
12628
+ {},
12629
+ (chunk, type) => {
12630
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12631
+ }
12632
+ );
12633
+ try {
12634
+ deckJson = JSON.parse(extractCleanJson(repairedRaw));
12635
+ validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
12636
+ } catch {
12637
+ try {
12638
+ const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
12639
+ deckJson = JSON.parse(jsonrepair2(extractCleanJson(repairedRaw)));
12640
+ validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
12641
+ } catch {
12642
+ }
12643
+ }
12644
+ }
12645
+ const markdownWrapper = `---
12646
+ id: "SLIDE_${lessonCode}"
12647
+ title: "${titleHeader(lessonTitle)}"
12648
+ type: "SLIDE"
12649
+ format: "html-deck"
12650
+ engine: "html"
12651
+ phase: "P2"
12652
+ deliverable: "P2-T09"
12653
+ version: "v1.0"
12654
+ date: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
12655
+ ---
12656
+
12657
+ \`\`\`json
12658
+ ${JSON.stringify(deckJson || {}, null, 2)}
12659
+ \`\`\`
12660
+ `;
12661
+ await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
12662
+ producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12663
+ if (deckJson) {
12664
+ const deckJsonStr = JSON.stringify(deckJson, null, 2);
12665
+ await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.deck.json`, deckJsonStr);
12666
+ producedArtifacts.push(`SLIDE_${lessonCode}.deck.json`);
12667
+ if (presentationKitAi?.compileHtmlDeck) {
12668
+ try {
12669
+ const compiled = presentationKitAi.compileHtmlDeck(deckJson);
12670
+ if (compiled?.html) {
12671
+ await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.html`, compiled.html);
12672
+ producedArtifacts.push(`SLIDE_${lessonCode}.html`);
12673
+ }
12674
+ } catch (compErr) {
12675
+ console.warn(`[produceSingleLesson] Failed compiling HTML deck for ${lessonCode}:`, compErr);
12676
+ }
12677
+ }
12678
+ }
12679
+ const passed = Boolean(validation.valid && (validation.score ?? 100) >= 80);
12680
+ const score = validation.score ?? (passed ? 95 : 50);
12681
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12682
+ state: passed ? "approved" : "rejected",
12683
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12684
+ contentHash: computeContentHash(markdownWrapper),
12685
+ review: {
12686
+ decision: passed ? "APPROVED" : "NEEDS_REVISION",
12687
+ reviewedBy: "@agent-as-judge",
12688
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
12689
+ score,
12690
+ 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("; ")}`
12691
+ }
12692
+ });
12693
+ 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]`);
12694
+ } else {
12695
+ const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12696
+ const templateScaffold = canonicalSlideTemplate || `---
12467
12697
  marp: true
12468
12698
  theme: default
12469
12699
  paginate: true
12470
12700
  header: "${titleHeader(lessonTitle)}"
12471
12701
  footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
12472
12702
  ---`;
12473
- const slidePrompt = buildMarpMarkdownSlidePrompt({
12474
- lessonCode,
12475
- templateScaffold,
12476
- languageDirective,
12477
- headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12478
- });
12479
- const rawSlide = await runCurriculumAIInference(
12480
- [{ role: "user", content: slidePrompt }],
12481
- satelliteContext,
12482
- {},
12483
- (chunk, type) => {
12484
- options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12703
+ const slidePrompt = buildMarpMarkdownSlidePrompt({
12704
+ lessonCode,
12705
+ templateScaffold,
12706
+ languageDirective,
12707
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12708
+ });
12709
+ const rawSlide = await runCurriculumAIInference(
12710
+ [{ role: "user", content: slidePrompt }],
12711
+ satelliteContext,
12712
+ {},
12713
+ (chunk, type) => {
12714
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12715
+ }
12716
+ );
12717
+ if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12718
+ throw createAiInferenceError({
12719
+ errorCode: "ERR_AI_RESPONSE_MALFORMED",
12720
+ agent: "@illustrator",
12721
+ artifactType: "SLIDE",
12722
+ lessonId: lessonCode,
12723
+ message: `Agent @illustrator failed to author valid Slides for ${lessonCode}.`,
12724
+ rawError: rawSlide || "Empty AI response"
12725
+ });
12485
12726
  }
12486
- );
12487
- if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12488
- throw createAiInferenceError({
12489
- errorCode: "ERR_AI_RESPONSE_MALFORMED",
12490
- agent: "@illustrator",
12491
- artifactType: "SLIDE",
12492
- lessonId: lessonCode,
12493
- message: `Agent @illustrator failed to author valid Slides for ${lessonCode}.`,
12494
- rawError: rawSlide || "Empty AI response"
12727
+ await storage.saveArtifact(projectId, slideRelPath, rawSlide);
12728
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12729
+ state: "completed",
12730
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12731
+ contentHash: computeContentHash(rawSlide)
12495
12732
  });
12733
+ producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12734
+ await judgeSat("SLIDE", rawSlide);
12496
12735
  }
12497
- await storage.saveArtifact(projectId, slideRelPath, rawSlide);
12498
- await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12499
- state: "completed",
12500
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12501
- contentHash: computeContentHash(rawSlide)
12502
- });
12503
- producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12504
- await judgeSat("SLIDE", rawSlide);
12505
12736
  }
12506
12737
  const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
12507
12738
  if (artifactScope.includes("GUIDE") && (!await storage.readArtifact(projectId, guideRelPath) || force)) {
@@ -12814,7 +13045,9 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
12814
13045
  lessonId: lessonCode,
12815
13046
  pedagogy,
12816
13047
  producedArtifacts,
12817
- pausedForReview: false
13048
+ pausedForReview: false,
13049
+ contextInjection: lessonInjection && lessonInjection.verified ? expositionInjection || lessonInjection : lessonInjection || expositionInjection,
13050
+ promptChars: commonContext.length
12818
13051
  };
12819
13052
  }
12820
13053
  function parseLessonObjectives(lessonContent, concept, bloomLevel) {
@@ -13444,6 +13677,24 @@ function getArtifactSpecializedInvariants(type, req) {
13444
13677
  const studentCount = req.studentCount || "25";
13445
13678
  switch (type) {
13446
13679
  case "slide": {
13680
+ if (req.slidesEngine === "html") {
13681
+ return `
13682
+ ### \u{1F3AC} INTERACTIVE HTML SLIDE ENGINE CONTRACT:
13683
+ You generate structured slide decks for the LearnWell HTML Slide Presentation Engine.
13684
+ Output a single valid JSON object matching: { "title": "...", "theme": "ocean", "slides": [{ "layoutId": "hero-cover", "slots": { ... }, "notes": "..." }] }
13685
+
13686
+ Layout Presets available:
13687
+ - hero-cover (title, subtitle, presenter, date, tag)
13688
+ - two-column-split (col1Title, col1Items[], col2Title, col2Items[])
13689
+ - code-explainer (filename, language, code, highlightLines, annotations[])
13690
+ - metrics-3-card (card1Title, card1Val, card1Desc, ...)
13691
+ - process-flow (steps: [{ label, desc, status }])
13692
+ - quiz-checkpoint (question, options[], answerIdx, explanation)
13693
+ - tiered-practice-3cards (foundation, standard, challenge cards)
13694
+ - takeaways-summary (takeaways: bullet points)
13695
+
13696
+ Mandatory Presenter Notes on every slide with Talk Track, Cold-Call, and Scaffolding Tip.`;
13697
+ }
13447
13698
  const slideCount = cfg.slideCount || 10;
13448
13699
  const presentationStyle = cfg.presentationStyle || "modern";
13449
13700
  const presentationDuration = cfg.presentationDuration || 45;
@@ -17524,6 +17775,7 @@ exports.buildExpositionExcerpt = buildExpositionExcerpt;
17524
17775
  exports.buildExtensionPrompt = buildExtensionPrompt;
17525
17776
  exports.buildHandoutPrompt = buildHandoutPrompt;
17526
17777
  exports.buildHeadingDirective = buildHeadingDirective;
17778
+ exports.buildHtmlDeckSlidePrompt = buildHtmlDeckSlidePrompt;
17527
17779
  exports.buildImagePrompt = buildImagePrompt;
17528
17780
  exports.buildJudgePrompt = buildJudgePrompt;
17529
17781
  exports.buildLanguageDirective = buildLanguageDirective;