@thanh01.pmt/curriculum-kit 1.4.20 → 1.4.22

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/dist/index.cjs CHANGED
@@ -1513,7 +1513,8 @@ function buildSlideBatchPrompt(params) {
1513
1513
  skillPrompt,
1514
1514
  language = "Vietnamese",
1515
1515
  languageDirective = "",
1516
- headingDirective = ""
1516
+ headingDirective = "",
1517
+ groundContext = ""
1517
1518
  } = params;
1518
1519
  const slidesSpec = clusterSlides.map((s) => `
1519
1520
  - Slide ${s.slideIndex} [Phase: ${s.lessonPhase}] -> Layout: "${s.layoutId}"
@@ -1526,7 +1527,11 @@ function buildSlideBatchPrompt(params) {
1526
1527
  const systemPrompt = `
1527
1528
  ${skillPrompt}
1528
1529
 
1529
- ---
1530
+ ${groundContext ? `---
1531
+ ### PROJECT GROUND CONTEXT (framework, style guide, glossary, canonical knowledge \u2014 author from these):
1532
+ ${groundContext}
1533
+
1534
+ ---` : ""}
1530
1535
  ### OPERATIONAL GROUND RULES FOR CHUNK GENERATION:
1531
1536
  ${languageDirective}
1532
1537
  ${headingDirective}
@@ -1604,6 +1609,7 @@ __export(slideProductionWorkflow_exports, {
1604
1609
  HybridPipelineError: () => exports.HybridPipelineError,
1605
1610
  SlideBlueprintArraySchema: () => exports.SlideBlueprintArraySchema,
1606
1611
  SlideBlueprintItemSchema: () => exports.SlideBlueprintItemSchema,
1612
+ emitUsage: () => emitUsage,
1607
1613
  executeSlideProductionWorkflow: () => executeSlideProductionWorkflow,
1608
1614
  extractJsonArray: () => extractJsonArray,
1609
1615
  validateHybridDeckSlides: () => validateHybridDeckSlides
@@ -1656,15 +1662,28 @@ function validateHybridDeckSlides(slides, blueprint) {
1656
1662
  });
1657
1663
  return v.slice(0, 10);
1658
1664
  }
1665
+ function emitUsage(usage, onProgress) {
1666
+ if (!onProgress || !usage) return;
1667
+ const promptTokens = usage.promptTokens ?? usage.inputTokens ?? 0;
1668
+ const completionTokens = usage.completionTokens ?? usage.outputTokens ?? 0;
1669
+ if (promptTokens === 0 && completionTokens === 0) return;
1670
+ onProgress("@illustrator", JSON.stringify({
1671
+ promptTokens,
1672
+ completionTokens,
1673
+ totalTokens: usage.totalTokens ?? promptTokens + completionTokens,
1674
+ reasoningTokens: usage.reasoningTokens ?? 0
1675
+ }), { type: "usage" });
1676
+ }
1659
1677
  async function inferTextJson(schema, systemPrompt, userPrompt, options, label) {
1660
1678
  const model = getAIModel(options.modelOptions);
1661
1679
  try {
1662
- const { text, finishReason } = await ai.generateText({
1680
+ const { text, finishReason, usage } = await ai.generateText({
1663
1681
  model,
1664
1682
  system: systemPrompt || void 0,
1665
1683
  prompt: userPrompt,
1666
1684
  maxOutputTokens: options.maxOutputTokens ?? 65536
1667
1685
  });
1686
+ emitUsage(usage, options.onProgress);
1668
1687
  const extracted = extractJsonArray(text);
1669
1688
  if (!("value" in extracted) || extracted.value === void 0) {
1670
1689
  console.warn(
@@ -1685,12 +1704,13 @@ async function inferTextJson(schema, systemPrompt, userPrompt, options, label) {
1685
1704
  async function inferStructured(schema, systemPrompt, userPrompt, options, label) {
1686
1705
  try {
1687
1706
  const model = getAIModel(options.modelOptions);
1688
- const { object } = await ai.generateObject({
1707
+ const { object, usage } = await ai.generateObject({
1689
1708
  model,
1690
1709
  schema,
1691
1710
  system: systemPrompt || void 0,
1692
1711
  prompt: userPrompt
1693
1712
  });
1713
+ emitUsage(usage, options.onProgress);
1694
1714
  const validated = schema.safeParse(object);
1695
1715
  if (validated.success) return validated.data;
1696
1716
  console.warn(
@@ -1705,7 +1725,9 @@ async function inferStructured(schema, systemPrompt, userPrompt, options, label)
1705
1725
  const messages = [
1706
1726
  { role: "user", content: [systemPrompt, userPrompt].filter(Boolean).join("\n\n") }
1707
1727
  ];
1708
- const raw = await runCurriculumAIInference(messages, options.satelliteContext || "", options.runnerOptions);
1728
+ const raw = await runCurriculumAIInference(messages, options.satelliteContext || "", options.runnerOptions, (token, type) => {
1729
+ if (type === "usage") options.onProgress?.("@illustrator", token, { type: "usage" });
1730
+ });
1709
1731
  const fenceMatch = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
1710
1732
  const candidate = (fenceMatch ? fenceMatch[1] : raw).trim();
1711
1733
  let parsed;
@@ -1738,7 +1760,7 @@ function normalizeBlueprintItems(items) {
1738
1760
  }));
1739
1761
  }
1740
1762
  function buildHybridDeckPrompts(params) {
1741
- const { lessonFlow, blueprint, skillPrompt, language, languageDirective, headingDirective } = params;
1763
+ const { lessonFlow, blueprint, skillPrompt, language, languageDirective, headingDirective, groundContext } = params;
1742
1764
  const blueprintText = JSON.stringify(
1743
1765
  blueprint.map((s) => ({
1744
1766
  slideIndex: s.slideIndex,
@@ -1757,6 +1779,7 @@ function buildHybridDeckPrompts(params) {
1757
1779
  ${p.content}`).join("\n\n") : lessonFlow.rawContent;
1758
1780
  const systemPrompt = [
1759
1781
  skillPrompt,
1782
+ groundContext || "",
1760
1783
  languageDirective,
1761
1784
  headingDirective,
1762
1785
  `
@@ -1821,7 +1844,8 @@ async function runHybridPipeline(ctx) {
1821
1844
  skillPrompt,
1822
1845
  language,
1823
1846
  languageDirective,
1824
- headingDirective
1847
+ headingDirective,
1848
+ groundContext: options.groundContext
1825
1849
  });
1826
1850
  let slides = null;
1827
1851
  let lastViolations = [];
@@ -1970,7 +1994,8 @@ ${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
1970
1994
  skillPrompt,
1971
1995
  language,
1972
1996
  languageDirective,
1973
- headingDirective
1997
+ headingDirective,
1998
+ groundContext: options.groundContext
1974
1999
  });
1975
2000
  try {
1976
2001
  const batchSlides = await pRetry__default.default(
@@ -9312,6 +9337,21 @@ var STANDARD_SOT_FILES = [
9312
9337
  "ART_DIRECTION.md",
9313
9338
  "ALIGNMENT_MATRIX.md"
9314
9339
  ];
9340
+ function atomicWriteFileSync(targetPath, content) {
9341
+ const dir = path3__default.default.dirname(targetPath);
9342
+ if (!fs2__default.default.existsSync(dir)) fs2__default.default.mkdirSync(dir, { recursive: true });
9343
+ const tmpPath = path3__default.default.join(dir, `.${path3__default.default.basename(targetPath)}.${process.pid}.${Date.now()}.${crypto__default.default.randomBytes(4).toString("hex")}.tmp`);
9344
+ try {
9345
+ fs2__default.default.writeFileSync(tmpPath, content, "utf-8");
9346
+ fs2__default.default.renameSync(tmpPath, targetPath);
9347
+ } catch (err) {
9348
+ try {
9349
+ if (fs2__default.default.existsSync(tmpPath)) fs2__default.default.unlinkSync(tmpPath);
9350
+ } catch {
9351
+ }
9352
+ throw err;
9353
+ }
9354
+ }
9315
9355
  var FileSystemCurriculumAdapter = class {
9316
9356
  baseDir;
9317
9357
  constructor(options = {}) {
@@ -9450,7 +9490,7 @@ var FileSystemCurriculumAdapter = class {
9450
9490
  if (oldContent !== content) {
9451
9491
  const historyDir = path3__default.default.join(projectDir, ".history", relPath);
9452
9492
  fs2__default.default.mkdirSync(historyDir, { recursive: true });
9453
- fs2__default.default.writeFileSync(path3__default.default.join(historyDir, `${Date.now()}.md`), oldContent, "utf-8");
9493
+ atomicWriteFileSync(path3__default.default.join(historyDir, `${Date.now()}.md`), oldContent);
9454
9494
  const versions = fs2__default.default.readdirSync(historyDir).filter((f) => f.endsWith(".md")).sort();
9455
9495
  while (versions.length > 10) {
9456
9496
  fs2__default.default.unlinkSync(path3__default.default.join(historyDir, versions.shift()));
@@ -9460,19 +9500,15 @@ var FileSystemCurriculumAdapter = class {
9460
9500
  console.warn("[FileSystemCurriculumAdapter] version history snapshot failed:", histErr?.message || histErr);
9461
9501
  }
9462
9502
  }
9463
- fs2__default.default.writeFileSync(targetPath, content, "utf-8");
9503
+ atomicWriteFileSync(targetPath, content);
9464
9504
  const filename = path3__default.default.basename(relPath);
9465
9505
  const match = filename.match(/(LESSON|ACT|QUIZ|SLIDE|GUIDE|HANDOUT|WKS|EXT)_(U\d+_M\d+_L\d+)\.md/i);
9466
9506
  if (match) {
9467
9507
  const lessonId = match[2].toUpperCase();
9468
- const lessonsDir = path3__default.default.join(projectDir, "lessons", lessonId);
9469
- if (!fs2__default.default.existsSync(lessonsDir)) {
9470
- fs2__default.default.mkdirSync(lessonsDir, { recursive: true });
9471
- }
9472
- fs2__default.default.writeFileSync(path3__default.default.join(lessonsDir, filename), content, "utf-8");
9508
+ atomicWriteFileSync(path3__default.default.join(projectDir, "lessons", lessonId, filename), content);
9473
9509
  const legacyContentDir = path3__default.default.join(projectDir, "_content", lessonId);
9474
9510
  if (fs2__default.default.existsSync(legacyContentDir)) {
9475
- fs2__default.default.writeFileSync(path3__default.default.join(legacyContentDir, filename), content, "utf-8");
9511
+ atomicWriteFileSync(path3__default.default.join(legacyContentDir, filename), content);
9476
9512
  }
9477
9513
  }
9478
9514
  }
@@ -9563,7 +9599,7 @@ ${lessonTable}
9563
9599
  }
9564
9600
  const stateFile = path3__default.default.join(pipelineDir, "state.json");
9565
9601
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9566
- fs2__default.default.writeFileSync(stateFile, JSON.stringify(state, null, 2), "utf-8");
9602
+ atomicWriteFileSync(stateFile, JSON.stringify(state, null, 2));
9567
9603
  }
9568
9604
  async updateArtifactState(projectId, taskId, artifactType, update) {
9569
9605
  const current = await this.getPipelineState(projectId);
@@ -11263,6 +11299,13 @@ var DEFAULT_LESSON_PRIORITIES = [
11263
11299
  "Learning Objectives & Evidence",
11264
11300
  "Activity Sequence"
11265
11301
  ];
11302
+ var SATELLITE_LESSON_PRIORITIES = [
11303
+ "Artifact Contract",
11304
+ "A. Lesson Design Plan",
11305
+ "B. Lesson Flow",
11306
+ "Learning Objectives & Evidence",
11307
+ "Activity Sequence"
11308
+ ];
11266
11309
  var DEFAULT_KX_PRIORITIES = [
11267
11310
  "Key Terms",
11268
11311
  "Concept Narratives",
@@ -11476,6 +11519,21 @@ function normalizeHeading(str) {
11476
11519
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
11477
11520
  }
11478
11521
  function findCanonicalFuzzy(norm) {
11522
+ if (norm.includes("toolchain") || norm.includes("moi truong phat trien") || norm.includes("cong cu") || norm.includes("version") || norm.includes("phan mem") || norm.includes("development environment") || norm.includes("technical overview") || norm.includes("kien truc")) {
11523
+ return "Technical Overview & Architecture Blueprint";
11524
+ }
11525
+ if (norm.includes("pinout") || norm.includes("phan cung") || norm.includes("hardware") || norm.includes("wiring") || norm.includes("ket noi")) {
11526
+ return "Hardware Pinout & Wiring Configuration Matrix";
11527
+ }
11528
+ if (norm.includes("pedagog") || norm.includes("phuong phap") || norm.includes("day hoc") || norm.includes("su pham")) {
11529
+ return "Core Pedagogical Concept Anchor & Real-World Domain Bridge";
11530
+ }
11531
+ if (norm.includes("standard") || norm.includes("tieu chuan") || norm.includes("csta") || norm.includes("cs2023") || norm.includes("chuan academic")) {
11532
+ return "Standards Alignment";
11533
+ }
11534
+ if (norm.includes("roadmap") || norm.includes("lo trinh") || norm.includes("milestone") || norm.includes("giai doan")) {
11535
+ return void 0;
11536
+ }
11479
11537
  if (norm.includes("symbol") || norm.includes("identifier ledger") || norm.includes("dinh danh")) {
11480
11538
  return "Symbol & Identifier Ledger";
11481
11539
  }
@@ -11612,7 +11670,7 @@ function buildSystemPrompt(targetLanguage = "vi", techStack, hardwarePlatform) {
11612
11670
  langDirective
11613
11671
  ].join("\n");
11614
11672
  }
11615
- function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown) {
11673
+ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown, refPack) {
11616
11674
  const depthLines = session.depth_assignments.map((d) => "- " + d.node_id + " [" + d.depth.toUpperCase() + "]: " + DEPTH_RULES[d.depth]).join("\n");
11617
11675
  const termLines = glossary.map((g) => "- " + g.term + (g.definition ? " \u2014 " + g.definition : "") + (g.example ? " (example: " + g.example + ")" : "")).join("\n");
11618
11676
  const langCode = resolveTargetLanguageCode(targetLanguage);
@@ -11625,6 +11683,28 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
11625
11683
  const hQuestions = headings["Self-Check Questions"] || (langCode === "vi" ? "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra" : "Self-Check Questions");
11626
11684
  const skeleton = `## ${hScope} / ## ${hTerms} / ## ${hNarratives} / ## ${hExamples} / ## ${hMistakes} / ## ${hQuestions}`;
11627
11685
  const headingDirective = buildHeadingDirective("KNOWLEDGE_EXPOSITION", slcMarkdown, targetLanguage);
11686
+ let refPackBlock = "";
11687
+ if (refPack && refPack.trim()) {
11688
+ const refExcerpt = buildSectionAwareExcerpt(refPack, {
11689
+ priorities: [
11690
+ "Technical Overview & Architecture Blueprint",
11691
+ "Hardware Pinout & Wiring Configuration Matrix",
11692
+ "Standards Alignment"
11693
+ ],
11694
+ budget: 2500
11695
+ }).excerpt;
11696
+ if (refExcerpt) {
11697
+ refPackBlock = [
11698
+ "",
11699
+ "[GROUND TRUTH \u2014 BINDING, from REFERENCE_PACK.md]:",
11700
+ "Tool versions, APIs, and platform facts below are CANONICAL. Do NOT",
11701
+ "contradict them; do NOT substitute versions from memory. If a fact you",
11702
+ "need is not stated here, stay generic rather than inventing specifics.",
11703
+ "",
11704
+ refExcerpt
11705
+ ].join("\n");
11706
+ }
11707
+ }
11628
11708
  return [
11629
11709
  "Session: " + session.id + " \u2014 " + session.title,
11630
11710
  "Objective: " + session.prose_objective,
@@ -11637,6 +11717,7 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
11637
11717
  "",
11638
11718
  "Glossary terms (definitions are canonical):",
11639
11719
  termLines || "(none provided \u2014 write definitions and mark them for glossary sync)",
11720
+ refPackBlock,
11640
11721
  "",
11641
11722
  "Section skeleton to fill (replace ONLY the {{placeholders}}):",
11642
11723
  skeleton,
@@ -11645,7 +11726,7 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
11645
11726
  ].filter(Boolean).join("\n");
11646
11727
  }
11647
11728
  async function ensureKnowledgeExposition(options) {
11648
- const { projectId, lessonCode, plan, glossary, llmFn, storage, techStack, hardwarePlatform } = options;
11729
+ const { projectId, lessonCode, plan, glossary, llmFn, storage, techStack, hardwarePlatform, refPack } = options;
11649
11730
  const session = plan.sessions.find((s) => s.id === lessonCode);
11650
11731
  if (!session) throw new Error("Session " + lessonCode + " not found in plan " + plan.plan_id);
11651
11732
  const existing = await storage.readArtifact(projectId, EXPOSITION_REL(lessonCode));
@@ -11659,9 +11740,11 @@ async function ensureKnowledgeExposition(options) {
11659
11740
  throw new ExpositionApprovalError("Approval hash " + plan.approval.plan_hash + " does not match current plan hash " + plan.plan_hash + " \u2014 re-approve after plan changes");
11660
11741
  }
11661
11742
  const targetLanguage = options.targetLanguage || plan.translation?.target_language || "vi";
11743
+ const originalSystemPrompt = buildSystemPrompt(targetLanguage, techStack, hardwarePlatform);
11744
+ const originalUserPrompt = buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown, refPack);
11662
11745
  const content = (await llmFn(
11663
- buildSystemPrompt(targetLanguage, techStack, hardwarePlatform),
11664
- buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown)
11746
+ originalSystemPrompt,
11747
+ originalUserPrompt
11665
11748
  )).trim();
11666
11749
  if (content.length < 200) {
11667
11750
  throw new Error("EXPOSITION too short for " + lessonCode + " (" + content.length + " chars) \u2014 refusing to save");
@@ -11694,8 +11777,16 @@ async function ensureKnowledgeExposition(options) {
11694
11777
  };
11695
11778
  let verdict = await judgeOnce(finalContent);
11696
11779
  if (verdict.verdict !== "APPROVED") {
11697
- const repairPrompt = finalContent + "\n\nJUDGE CRITIQUE (fix ALL issues, output the COMPLETE corrected document):\n" + verdict.critique;
11698
- finalContent = (await llmFn(buildSystemPrompt(), repairPrompt)).trim();
11780
+ const repairPrompt = [
11781
+ originalUserPrompt,
11782
+ "",
11783
+ "--- YOUR PREVIOUS DRAFT (REJECTED, fix ALL issues below) ---",
11784
+ finalContent,
11785
+ "",
11786
+ "--- JUDGE CRITIQUE (fix ALL issues, output the COMPLETE corrected document) ---",
11787
+ verdict.critique
11788
+ ].join("\n");
11789
+ finalContent = (await llmFn(originalSystemPrompt, repairPrompt)).trim();
11699
11790
  verdict = await judgeOnce(finalContent);
11700
11791
  }
11701
11792
  if (verdict.verdict !== "APPROVED") {
@@ -11736,12 +11827,27 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
11736
11827
  try {
11737
11828
  const lpRaw = await storage.readSotDocument(projectId, "LEARNER_PROFILE.md");
11738
11829
  if (lpRaw) {
11739
- const hwMatch = lpRaw.match(/(?:Student Equipment|Hardware|Platform|Thiết bị)[:\s*`]+([^\n\r]+)/i);
11740
- if (hwMatch && !hw) hw = hwMatch[1].trim();
11830
+ const hwPatterns = [
11831
+ /(?:Student Equipment|Hardware|Platform|Thiết bị|Thiết bị học tập)\s*(?:\(|:|\*)*\s*([^\n\r]+?)(?:\s*\*\*|[.).]?\s*$)/i,
11832
+ /(?:máy|device|machine)\s+([^,.;\n]{4,60}(?:M\d|Intel|PC|computer|mini)[^,.;\n]{0,30})/i
11833
+ ];
11834
+ for (const p of hwPatterns) {
11835
+ const m = lpRaw.match(p);
11836
+ if (m && !hw) {
11837
+ hw = m[1].trim();
11838
+ break;
11839
+ }
11840
+ }
11741
11841
  }
11742
11842
  } catch {
11743
11843
  }
11744
11844
  }
11845
+ let refPackContent;
11846
+ try {
11847
+ refPackContent = await storage.readSotDocument(projectId, "REFERENCE_PACK.md") || void 0;
11848
+ } catch {
11849
+ refPackContent = void 0;
11850
+ }
11745
11851
  const { runCurriculumAIInference: runCurriculumAIInference2 } = await Promise.resolve().then(() => (init_streamRunner(), streamRunner_exports));
11746
11852
  const targetLang = options.targetLanguage || plan.translation?.target_language || "vi";
11747
11853
  const result = await ensureKnowledgeExposition({
@@ -11753,6 +11859,7 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
11753
11859
  slcMarkdown: options.slcMarkdown,
11754
11860
  techStack: tech,
11755
11861
  hardwarePlatform: hw,
11862
+ refPack: refPackContent,
11756
11863
  llmFn: async (systemPrompt, userPrompt) => {
11757
11864
  const runnerOpts = options.customInference ? { customInference: options.customInference } : {};
11758
11865
  const out = await runCurriculumAIInference2(
@@ -26051,11 +26158,16 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
26051
26158
  }).excerpt : "";
26052
26159
  let effectiveRefPack = refPack ? buildSectionAwareExcerpt(refPack, {
26053
26160
  priorities: [
26161
+ // Toolchain/version matrix FIRST — the most-frequently-hallucinated
26162
+ // facts are tool versions ("Xcode 15" vs ground truth "Xcode 16").
26163
+ // The bilingual fuzzy matcher in contextBuilder resolves these keys
26164
+ // even when the generated RefPack headings are Vietnamese.
26054
26165
  "Technical Overview & Architecture Blueprint",
26055
26166
  "Hardware Pinout & Wiring Configuration Matrix",
26167
+ "Standards Alignment",
26056
26168
  "Core Pedagogical Concept Anchor & Real-World Domain Bridge"
26057
26169
  ],
26058
- budget: 2500
26170
+ budget: 3500
26059
26171
  }).excerpt : "";
26060
26172
  const baseContextPrefix = `PROJECT & ACADEMIC CONTEXT:
26061
26173
  - Project ID: ${projectId}
@@ -26094,6 +26206,21 @@ ${renderHorizonPromptBlock(horizon)}`;
26094
26206
  } catch (hErr) {
26095
26207
  console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
26096
26208
  }
26209
+ const buildGroundTruthBlock = () => {
26210
+ const parts = [];
26211
+ if (expositionContext) {
26212
+ parts.push(`### KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)
26213
+ ${expositionContext}`);
26214
+ }
26215
+ if (effectiveRefPack) {
26216
+ parts.push(`### REFERENCE PACK GROUND TRUTH
26217
+ ${effectiveRefPack}`);
26218
+ }
26219
+ return parts.length > 0 ? `
26220
+
26221
+ [GROUND TRUTH]:
26222
+ ${parts.join("\n\n")}` : "";
26223
+ };
26097
26224
  const glossaryBlock = glossaryContext ? `
26098
26225
 
26099
26226
  [GLOSSARY TERMS (use these exact definitions)]:
@@ -26101,21 +26228,14 @@ ${glossaryContext}` : "";
26101
26228
  const standardsBlock = standardsContext ? `
26102
26229
 
26103
26230
  ${standardsContext}` : "";
26104
- const expositionBlock = expositionContext ? `
26105
-
26106
- [KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)]:
26107
- ${expositionContext}` : "";
26108
26231
  const sessionSliceBlock = sessionSliceContext ? `
26109
26232
 
26110
26233
  ${sessionSliceContext}` : "";
26111
- const assembleCommonContext = (ref, sg) => `${baseContextPrefix}
26234
+ const assembleCommonContext = (sg) => `${baseContextPrefix}
26112
26235
 
26113
26236
  [CONTENT STYLE GUIDE EXCERPT]:
26114
- ${sg}
26115
-
26116
- [REFERENCE PACK GROUND TRUTH]:
26117
- ${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}${horizonBlock}`;
26118
- let commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
26237
+ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}`;
26238
+ let commonContext = assembleCommonContext(effectiveStyleGuide);
26119
26239
  const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
26120
26240
  const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
26121
26241
  if (commonContext.length > MAX_COMPOSITE_PROMPT_CHARS && effectiveRefPack.length > 1e3) {
@@ -26126,14 +26246,14 @@ ${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}${h
26126
26246
  ],
26127
26247
  budget: 1e3
26128
26248
  }).excerpt;
26129
- commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
26249
+ commonContext = assembleCommonContext(effectiveStyleGuide);
26130
26250
  }
26131
26251
  if (commonContext.length > MAX_COMPOSITE_PROMPT_CHARS && effectiveStyleGuide.length > 1e3) {
26132
26252
  effectiveStyleGuide = buildSectionAwareExcerpt(styleGuide, {
26133
26253
  priorities: ["Voice & Tone", "Standard Terminology (Glossary)"],
26134
26254
  budget: 1e3
26135
26255
  }).excerpt;
26136
- commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
26256
+ commonContext = assembleCommonContext(effectiveStyleGuide);
26137
26257
  }
26138
26258
  onProgress?.("@content", `[CONTEXT] Composite prompt ready (${commonContext.length} chars, adaptive budget <= ${MAX_COMPOSITE_PROMPT_CHARS})`, {
26139
26259
  type: "progress",
@@ -26484,7 +26604,7 @@ ${currentContent}` }],
26484
26604
  });
26485
26605
  }
26486
26606
  const lessonExcerptResult = buildSectionAwareExcerpt(lessonContent, {
26487
- priorities: DEFAULT_LESSON_PRIORITIES,
26607
+ priorities: SATELLITE_LESSON_PRIORITIES,
26488
26608
  budget: 12e3,
26489
26609
  sectionLanguageContract: slcMarkdown,
26490
26610
  artifactType: "LESSON"
@@ -26674,6 +26794,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
26674
26794
  } catch {
26675
26795
  }
26676
26796
  const { executeSlideProductionWorkflow: executeSlideProductionWorkflow2 } = await Promise.resolve().then(() => (init_slideProductionWorkflow(), slideProductionWorkflow_exports));
26797
+ const slideGroundContext = `${commonContext}${symbolLedgerBlock}`;
26677
26798
  const workflowResult = await executeSlideProductionWorkflow2({
26678
26799
  lessonMarkdown: lessonContent || "",
26679
26800
  lessonCode,
@@ -26682,6 +26803,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
26682
26803
  language: targetLang || "Vietnamese",
26683
26804
  languageDirective,
26684
26805
  headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
26806
+ groundContext: slideGroundContext,
26685
26807
  satelliteContext,
26686
26808
  runnerOptions,
26687
26809
  onProgress: (agent, msg, meta) => {
@@ -26690,7 +26812,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
26690
26812
  });
26691
26813
  let deckJson = workflowResult.deckJson;
26692
26814
  const markdownWrapper = workflowResult.markdownWrapper;
26693
- let validation = presentationKitAi?.validateHtmlSlideDeck ? presentationKitAi.validateHtmlSlideDeck(deckJson, true) : deckJson && Array.isArray(deckJson.slides) ? { valid: true, score: 95, issues: [], slideCount: deckJson.slides.length} : { valid: false, score: 0, issues: [{ message: "Malformed JSON slide deck" }], slideCount: 0};
26815
+ let validation = presentationKitAi?.validateHtmlSlideDeck ? presentationKitAi.validateHtmlSlideDeck(deckJson, true) : deckJson && Array.isArray(deckJson.slides) ? { valid: true, score: 95, issues: [], slideCount: deckJson.slides.length} : { valid: false, score: 0, issues: [{ message: "Malformed JSON slide deck" }]};
26694
26816
  await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
26695
26817
  producedArtifacts.push(`SLIDE_${lessonCode}.md`);
26696
26818
  if (deckJson) {
@@ -26710,21 +26832,45 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
26710
26832
  }
26711
26833
  }
26712
26834
  }
26713
- const passed = Boolean(validation.valid && (validation.score ?? 100) >= 80);
26714
- const score = validation.score ?? (passed ? 95 : 50);
26715
- await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
26716
- state: passed ? "approved" : "rejected",
26717
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
26718
- contentHash: computeContentHash(markdownWrapper),
26719
- review: {
26720
- decision: passed ? "APPROVED" : "NEEDS_REVISION",
26721
- reviewedBy: "@agent-as-judge",
26722
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
26723
- score,
26724
- 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("; ")}`
26725
- }
26726
- });
26727
- 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]`);
26835
+ const structuralOk = Boolean(validation.valid && (validation.score ?? 100) >= 80);
26836
+ const structuralScore = validation.score ?? (structuralOk ? 95 : 50);
26837
+ const gateMode = gateModeFor(gates, "SLIDE");
26838
+ if (!structuralOk) {
26839
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
26840
+ state: "rejected",
26841
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
26842
+ contentHash: computeContentHash(markdownWrapper),
26843
+ review: {
26844
+ decision: "NEEDS_REVISION",
26845
+ reviewedBy: "@heuristic-linter",
26846
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
26847
+ score: structuralScore,
26848
+ critique: `HTML Slide Deck failed schema validation: ${(validation.issues || []).map((i) => i.message).join("; ")}`
26849
+ }
26850
+ });
26851
+ onProgress?.("@reviewer", `\u26A0\uFE0F SLIDE structural pre-check FAILED (${structuralScore}/100) [html-deck schema issues]`);
26852
+ } else {
26853
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
26854
+ state: gateMode === "LLM_JUDGE" ? "pending" : "completed",
26855
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
26856
+ contentHash: computeContentHash(markdownWrapper)
26857
+ });
26858
+ onProgress?.("@reviewer", gateMode === "LLM_JUDGE" ? `\u2705 SLIDE structural pre-check PASS (${structuralScore}/100) \u2014 handing content review to LLM-as-Judge gate` : `\u2705 SLIDE structural pre-check PASS (${structuralScore}/100) [gate=${gateMode}, no LLM judge]`);
26859
+ const deckTextForJudge = Array.isArray(deckJson?.slides) ? deckJson.slides.map((s, i) => {
26860
+ const slotLines = Object.entries(s.slots || {}).map(([k, v]) => {
26861
+ if (Array.isArray(v)) return `- ${k}:
26862
+ ${v.map((item) => typeof item === "object" ? ` - ${JSON.stringify(item)}` : ` - ${item}`).join("\n")}`;
26863
+ if (v && typeof v === "object") return `- ${k}: ${JSON.stringify(v, null, 1)}`;
26864
+ return `- ${k}: ${v}`;
26865
+ }).join("\n");
26866
+ return `## Slide ${i + 1} [${s.layoutId}]
26867
+ ${slotLines}
26868
+
26869
+ Presenter Notes:
26870
+ ${s.notes || "(none)"}`;
26871
+ }).join("\n\n") : String(markdownWrapper);
26872
+ await judgeSat("SLIDE", deckTextForJudge);
26873
+ }
26728
26874
  } else {
26729
26875
  const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
26730
26876
  const templateScaffold = canonicalSlideTemplate || `---
@@ -31873,6 +32019,7 @@ exports.RoadmapInputSchema = RoadmapInputSchema;
31873
32019
  exports.RotationStationSchema = RotationStationSchema;
31874
32020
  exports.RubricCriteriaSchema = RubricCriteriaSchema;
31875
32021
  exports.RubricSchema = RubricSchema;
32022
+ exports.SATELLITE_LESSON_PRIORITIES = SATELLITE_LESSON_PRIORITIES;
31876
32023
  exports.SCIENCE_LAB_TEMPLATE = SCIENCE_LAB_TEMPLATE;
31877
32024
  exports.SELF_LAB_TEMPLATE = SELF_LAB_TEMPLATE;
31878
32025
  exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
@@ -31922,6 +32069,7 @@ exports.analystTools = analystTools;
31922
32069
  exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
31923
32070
  exports.assertAcyclic = assertAcyclic;
31924
32071
  exports.assessorTools = assessorTools;
32072
+ exports.atomicWriteFileSync = atomicWriteFileSync;
31925
32073
  exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
31926
32074
  exports.auditQualityReport = auditQualityReport;
31927
32075
  exports.buildActivityPrompt = buildActivityPrompt;
@@ -31969,6 +32117,7 @@ exports.createStreamChunkExtractor = createStreamChunkExtractor;
31969
32117
  exports.curateMediaLedger = curateMediaLedger;
31970
32118
  exports.designerTools = designerTools;
31971
32119
  exports.detectProjectPedagogy = detectProjectPedagogy;
32120
+ exports.emitUsage = emitUsage;
31972
32121
  exports.ensureExpositionForLesson = ensureExpositionForLesson;
31973
32122
  exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
31974
32123
  exports.evaluateStandardsCoverage = evaluateStandardsCoverage;