@thanh01.pmt/curriculum-kit 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -85,6 +85,8 @@ function getSuggestedActionForCode(code) {
85
85
  return "N\u1ED9i dung sinh ra thi\u1EBFu c\xE1c ph\u1EA7n b\u1EAFt bu\u1ED9c [REQUIRED]. H\xE3y k\xEDch ho\u1EA1t ki\u1EC3m th\u1EED ho\u1EB7c b\u1EA5m Rerun.";
86
86
  case "ERR_STORAGE_IO_FAILED":
87
87
  return "L\u1ED7i \u0111\u1ECDc/ghi t\u1EC7p tin tr\xEAn h\u1EC7 th\u1ED1ng l\u01B0u tr\u1EEF. Ki\u1EC3m tra quy\u1EC1n ghi th\u01B0 m\u1EE5c output/projects.";
88
+ case "ERR_CONTEXT_MISSING":
89
+ return "Thi\u1EBFu ngu\u1ED3n tri th\u1EE9c b\u1EAFt bu\u1ED9c (KNOWLEDGE_EXPOSITION ho\u1EB7c CANONICAL LESSON) theo Dependency Routing Matrix. Vui l\xF2ng t\u1EA1o ho\u1EB7c \u0111\u1ED3ng b\u1ED9 t\xE0i li\u1EC7u ngu\u1ED3n tr\u01B0\u1EDBc.";
88
90
  default:
89
91
  return "B\u1EA5m Rerun \u0111\u1EC3 th\u1EED l\u1EA1i ho\u1EB7c ki\u1EC3m tra nh\u1EADt k\xFD h\u1EC7 th\u1ED1ng.";
90
92
  }
@@ -102,11 +104,23 @@ function isCodeRetryable(code) {
102
104
  case "ERR_TASK_GATE_BLOCKED":
103
105
  case "ERR_METERED_LIMIT_EXCEEDED":
104
106
  case "ERR_SCHEMA_CONTRACT_VIOLATION":
107
+ case "ERR_CONTEXT_MISSING":
105
108
  return false;
106
109
  default:
107
110
  return true;
108
111
  }
109
112
  }
113
+ function createContextMissingError(params) {
114
+ return new exports.CurriculumError({
115
+ errorCode: "ERR_CONTEXT_MISSING",
116
+ artifactType: params.artifactType,
117
+ lessonId: params.lessonId,
118
+ agent: params.agent,
119
+ message: `Required context source "${params.missingSource}" is missing for artifact "${params.artifactType}"`,
120
+ suggestedAction: `Generate or provide the authoritative "${params.missingSource}" source before producing "${params.artifactType}".`,
121
+ retryable: false
122
+ });
123
+ }
110
124
  exports.CurriculumError = void 0;
111
125
  var init_errors = __esm({
112
126
  "src/errors/index.ts"() {
@@ -5379,6 +5393,45 @@ Return a single valid JSON object matching SlideDeckSchema with these exact keys
5379
5393
  }
5380
5394
  `.trim();
5381
5395
  }
5396
+ var SLIDE_CANONICAL_METHODOLOGY = `
5397
+ MANDATORY 12-14 SLIDE MICRO-CYCLE STRUCTURE (Delimited by '---'):
5398
+ 1. **Slide 1 [Title & Mission]:** Real-world inquiry hook and overarching build mission
5399
+ 2. **Slide 2 [Agenda & Roadmap]:** Timing and milestone breakdown table
5400
+ 3. **Slide 3 [Warm-Up / Prior Knowledge]:** 2-column recall + 60-second diagnostic challenge card
5401
+ 4. **Slide 4 [High-Stakes Hook Scenario]:** Authentic engineering/domain crisis card + dilemma constraint
5402
+ 5. **Slide 5 [Core Concept 1: Mental Model]:** Definition, intuitive everyday analogy, and golden rule card
5403
+ 6. **Slide 6 [Architecture & Visual Flow]:** Clean Mermaid diagram (flowchart or sequenceDiagram) + 3 flow steps
5404
+ 7. **Slide 7 [Core Concept 2: Syntax / Mechanism Anatomy]:** Side-by-side runnable code + anatomy breakdown & syntax trap card
5405
+ 8. **Slide 8 [Guided Practice: Try It Now]:** 3-minute active desk task card with starter hint and success criteria
5406
+ 9. **Slide 9 [Integrated Hands-On Lab]:** 3-tier differentiated challenge cards (Bronze / Silver / Gold) + gotcha alert card
5407
+ 10. **Slide 10 [Traffic Light Checkpoint & Pitfalls]:** Formative self-check cards (\u{1F7E2}/\u{1F7E1}/\u{1F534}) & diagnostic test question
5408
+ 11. **Slide 11 [Exit Ticket & Metacognition]:** 2 scenario-based check questions + 1 metacognitive reflection prompt
5409
+ 12. **Slide 12 [Summary & Next Steps]:** 3 core golden rules + preview card of next upcoming milestone
5410
+
5411
+ Mandatory Canvas & Presentation Invariants:
5412
+ - A slide is an INTERACTIVE PROJECTION CANVAS, NOT a textbook! Absolutely NO dense paragraphs or walls of text.
5413
+ - Follow the 6x6 rule: Maximum 4-6 bullet points per slide, maximum 8-12 words per bullet.
5414
+ - Structure slides using \`<div class="columns"><div>...</div><div>...</div></div>\` and \`<div class="card">\`, \`<div class="card highlight">\`, \`<div class="card warning">\`, \`<div class="card success">\`.
5415
+ - MANDATORY PRESENTER NOTES ON EVERY SLIDE: Every slide MUST end with \`<!-- Presenter Notes: ... -->\` containing:
5416
+ * SCRIPT: 60-90s teacher talk track with intuitive analogies.
5417
+ * COLD CALL / CHECK: Specific diagnostic question to ask students.
5418
+ * SCAFFOLDING / GOTCHA: Guidance for students who get stuck.
5419
+ * PACING: Recommended minutes for the slide.
5420
+ - Output 100% valid Marp Markdown.
5421
+ `.trim();
5422
+ function buildMarpMarkdownSlidePrompt(input) {
5423
+ return `You are @illustrator & @content (Pedagogical Slide Deck Presentation Specialists).
5424
+ Based on Master Lesson Plan \`LESSON_${input.lessonCode}.md\`, lesson context, and the MARP SLIDE TEMPLATE below, author the complete, highly engaging Marp presentation deck: \`SLIDE_${input.lessonCode}.md\`.
5425
+
5426
+ [MARP SLIDE TEMPLATE SCAFFOLD]:
5427
+ ${input.templateScaffold}
5428
+
5429
+ ${SLIDE_CANONICAL_METHODOLOGY}
5430
+
5431
+ ${input.languageDirective}
5432
+
5433
+ ${input.headingDirective}`.trim();
5434
+ }
5382
5435
 
5383
5436
  // src/ai/prompts/teacherGuidePrompt.ts
5384
5437
  function buildTeacherGuidePrompt(input) {
@@ -9711,6 +9764,224 @@ function resolveGateSettings(raw) {
9711
9764
  function gateModeFor(resolved, type) {
9712
9765
  return resolved[type] ?? "OFF";
9713
9766
  }
9767
+
9768
+ // src/services/contextBuilder.ts
9769
+ var DEFAULT_LESSON_PRIORITIES = [
9770
+ "A. Lesson Design Plan",
9771
+ "B. Lesson Flow",
9772
+ "Learning Objectives & Evidence",
9773
+ "Activity Sequence"
9774
+ ];
9775
+ var DEFAULT_KX_PRIORITIES = [
9776
+ "Key Terms",
9777
+ "Concept Narratives",
9778
+ "Worked Micro-Examples"
9779
+ ];
9780
+ function buildSectionAwareExcerpt(markdown, opts = {}) {
9781
+ const budget = opts.budget ?? 12e3;
9782
+ const minContentChars = opts.minContentChars ?? 400;
9783
+ const issues = [];
9784
+ const source = (markdown || "").trim();
9785
+ if (!source) {
9786
+ return {
9787
+ excerpt: "",
9788
+ verified: false,
9789
+ sectionAware: false,
9790
+ includedSections: [],
9791
+ issues: ["empty-source"],
9792
+ tokenEstimate: 0
9793
+ };
9794
+ }
9795
+ const slcMap = normalizeSlcContract(opts.sectionLanguageContract);
9796
+ const sections = parseMarkdownSections(source, slcMap, opts.artifactType);
9797
+ if (sections.length === 0) {
9798
+ issues.push("parse:no-sections");
9799
+ const excerpt2 = truncateByBudget(source, budget);
9800
+ const verified2 = excerpt2.length >= minContentChars;
9801
+ if (excerpt2.length < minContentChars) issues.push("content:insufficient");
9802
+ return {
9803
+ excerpt: excerpt2,
9804
+ verified: verified2,
9805
+ sectionAware: false,
9806
+ includedSections: [],
9807
+ issues,
9808
+ tokenEstimate: Math.round(excerpt2.length / 4)
9809
+ };
9810
+ }
9811
+ const byCanonical = /* @__PURE__ */ new Map();
9812
+ const unmatched = [];
9813
+ for (const s of sections) {
9814
+ if (s.canonicalKey && !byCanonical.has(s.canonicalKey)) {
9815
+ byCanonical.set(s.canonicalKey, s);
9816
+ } else {
9817
+ unmatched.push(s);
9818
+ }
9819
+ }
9820
+ const priorities = opts.priorities ?? DEFAULT_LESSON_PRIORITIES;
9821
+ const includedSections = [];
9822
+ const blocks = [];
9823
+ let used = 0;
9824
+ const tryAdd = (heading, body, key) => {
9825
+ const trimmedBody = body.trim();
9826
+ if (!trimmedBody) return false;
9827
+ const block = `## ${heading}
9828
+
9829
+ ${trimmedBody}`;
9830
+ if (used + block.length > budget && blocks.length > 0) return false;
9831
+ blocks.push(block);
9832
+ used += block.length;
9833
+ includedSections.push(key);
9834
+ return true;
9835
+ };
9836
+ let sectionAware = false;
9837
+ for (const key of priorities) {
9838
+ const sec = byCanonical.get(key);
9839
+ if (!sec) continue;
9840
+ sectionAware = true;
9841
+ if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
9842
+ }
9843
+ for (const [key, sec] of byCanonical) {
9844
+ if (includedSections.includes(key)) continue;
9845
+ if (used >= budget) break;
9846
+ if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
9847
+ }
9848
+ for (const sec of unmatched) {
9849
+ if (used >= budget) break;
9850
+ if (!tryAdd(sec.cleanTitle, sec.body, sec.cleanTitle)) break;
9851
+ }
9852
+ if (priorities.length > 0 && !priorities.some((k) => includedSections.includes(k))) {
9853
+ issues.push("parse:no-priority-resolution");
9854
+ }
9855
+ let excerpt = blocks.join("\n\n");
9856
+ if (excerpt.length > budget) {
9857
+ excerpt = truncateByBudget(excerpt, budget);
9858
+ }
9859
+ const effectiveMinChars = Math.min(minContentChars, source.length);
9860
+ if (excerpt.length < effectiveMinChars) {
9861
+ issues.push("content:insufficient");
9862
+ }
9863
+ const verified = !issues.includes("parse:no-priority-resolution") && excerpt.length >= effectiveMinChars;
9864
+ return {
9865
+ excerpt,
9866
+ verified,
9867
+ sectionAware: sectionAware || priorities.length === 0 && sections.length > 0,
9868
+ includedSections,
9869
+ issues,
9870
+ tokenEstimate: Math.round(excerpt.length / 4)
9871
+ };
9872
+ }
9873
+ function buildExpositionExcerpt(markdown, opts = {}) {
9874
+ return buildSectionAwareExcerpt(markdown, {
9875
+ ...opts,
9876
+ priorities: DEFAULT_KX_PRIORITIES
9877
+ });
9878
+ }
9879
+ function buildLessonExcerpt(markdown, opts = {}) {
9880
+ return buildSectionAwareExcerpt(markdown, {
9881
+ ...opts,
9882
+ priorities: DEFAULT_LESSON_PRIORITIES
9883
+ });
9884
+ }
9885
+ function normalizeSlcContract(slc) {
9886
+ if (!slc) return void 0;
9887
+ if (typeof slc !== "string") return slc;
9888
+ const out = {};
9889
+ const blocks = slc.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
9890
+ for (const block of blocks) {
9891
+ const header = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
9892
+ if (!header?.[1]) continue;
9893
+ const mapping = {};
9894
+ for (const line of block.split("\n")) {
9895
+ const t = line.trim();
9896
+ if (!t.startsWith("|") || t.includes("---")) continue;
9897
+ const cells = t.split("|").map((c) => c.trim()).filter((_, i, a) => i > 0 && i < a.length - 1);
9898
+ const [c0, c1] = cells;
9899
+ if (c0 && c1 && !c0.toLowerCase().includes("canonical") && !c1.toLowerCase().includes("localized")) {
9900
+ mapping[c0] = c1;
9901
+ }
9902
+ }
9903
+ if (Object.keys(mapping).length > 0) out[header[1]] = mapping;
9904
+ }
9905
+ return Object.keys(out).length > 0 ? out : void 0;
9906
+ }
9907
+ function parseMarkdownSections(markdown, slcMap, artifactType) {
9908
+ const sections = [];
9909
+ const lines = markdown.split("\n");
9910
+ let currentHeading = "";
9911
+ let currentBody = [];
9912
+ const reverseMap = /* @__PURE__ */ new Map();
9913
+ if (slcMap) {
9914
+ const maps = artifactType && slcMap[artifactType] ? [slcMap[artifactType]] : Object.values(slcMap);
9915
+ for (const m of maps) {
9916
+ for (const [canonical, localized] of Object.entries(m)) {
9917
+ reverseMap.set(normalizeHeading(localized), canonical);
9918
+ reverseMap.set(normalizeHeading(canonical), canonical);
9919
+ }
9920
+ }
9921
+ }
9922
+ const flush = () => {
9923
+ if (!currentHeading && currentBody.length === 0) return;
9924
+ const cleanTitle = currentHeading.replace(/^#{1,4}\s+/, "").trim();
9925
+ const norm = normalizeHeading(cleanTitle);
9926
+ const canonicalKey = reverseMap.get(norm) || findCanonicalFuzzy(norm);
9927
+ sections.push({
9928
+ rawHeading: currentHeading,
9929
+ cleanTitle,
9930
+ canonicalKey,
9931
+ body: currentBody.join("\n")
9932
+ });
9933
+ currentBody = [];
9934
+ };
9935
+ const hasH2 = lines.some((l) => /^##\s+/.test(l));
9936
+ const headingRegex = hasH2 ? /^##\s+(.*)$/ : /^#{2,3}\s+(.*)$/;
9937
+ for (const line of lines) {
9938
+ const headingMatch = line.match(headingRegex);
9939
+ if (headingMatch) {
9940
+ flush();
9941
+ currentHeading = line.trim();
9942
+ } else {
9943
+ currentBody.push(line);
9944
+ }
9945
+ }
9946
+ flush();
9947
+ return sections;
9948
+ }
9949
+ function normalizeHeading(str) {
9950
+ return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
9951
+ }
9952
+ function findCanonicalFuzzy(norm) {
9953
+ if (norm.includes("lesson design plan") || norm.includes("ke hoach thiet ke")) {
9954
+ return "A. Lesson Design Plan";
9955
+ }
9956
+ if (norm.includes("lesson flow") || norm.includes("tien trinh giang day")) {
9957
+ return "B. Lesson Flow";
9958
+ }
9959
+ if (norm.includes("learning objective") || norm.includes("muc tieu bai hoc")) {
9960
+ return "Learning Objectives & Evidence";
9961
+ }
9962
+ if (norm.includes("activity sequence") || norm.includes("chuoi hoat dong")) {
9963
+ return "Activity Sequence";
9964
+ }
9965
+ if (norm.includes("key term") || norm.includes("thuat ngu")) {
9966
+ return "Key Terms";
9967
+ }
9968
+ if (norm.includes("concept narrative") || norm.includes("dien giai khai niem")) {
9969
+ return "Concept Narratives";
9970
+ }
9971
+ if (norm.includes("worked example") || norm.includes("vi du")) {
9972
+ return "Worked Micro-Examples";
9973
+ }
9974
+ return void 0;
9975
+ }
9976
+ function truncateByBudget(source, budget) {
9977
+ if (source.length <= budget) return source;
9978
+ let cut = source.slice(0, budget);
9979
+ const fenceCount = (cut.match(/^```/gm) || []).length;
9980
+ if (fenceCount % 2 === 1) cut += "\n```";
9981
+ return cut;
9982
+ }
9983
+
9984
+ // src/services/knowledgeExpositionService.ts
9714
9985
  var EXPOSITION_REL = (lessonCode) => "_sot/expositions/" + lessonCode + ".md";
9715
9986
  var ExpositionApprovalError = class extends Error {
9716
9987
  constructor(message) {
@@ -9817,15 +10088,8 @@ async function ensureKnowledgeExposition(options) {
9817
10088
  return { relPath: EXPOSITION_REL(lessonCode), content: stamped, reused: false, gateMode: gateModeFor(options.gates ?? {}, "KNOWLEDGE_EXPOSITION") };
9818
10089
  }
9819
10090
  function buildExpositionContext(expositionContent, maxChars = 6e3) {
9820
- const trimmed = expositionContent.trim();
9821
- if (trimmed.length <= maxChars) return trimmed;
9822
- const sections = trimmed.split(/\n(?=## )/);
9823
- let out = sections[0];
9824
- for (let i = 1; i < sections.length && out.length < maxChars; i++) {
9825
- const s = sections[i];
9826
- out += "\n" + s.slice(0, Math.max(400, maxChars - out.length));
9827
- }
9828
- return out.slice(0, maxChars);
10091
+ if (!expositionContent || !expositionContent.trim()) return "";
10092
+ return buildExpositionExcerpt(expositionContent, { budget: maxChars }).excerpt;
9829
10093
  }
9830
10094
  function expositionCacheKey(planHash, lessonCode) {
9831
10095
  return crypto.createHash("sha256").update(planHash + "|" + lessonCode).digest("hex").slice(0, 16);
@@ -11113,9 +11377,19 @@ function buildLanguageDirective(targetLanguage) {
11113
11377
  - DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
11114
11378
  ${exampleLine}` : ""}`;
11115
11379
  }
11116
- function extractSectionHeadingsFromSLC(slcMarkdown, artifactType) {
11117
- if (!slcMarkdown) return {};
11380
+ function extractSectionHeadingsFromSLC(slcInput, artifactType) {
11381
+ if (!slcInput) return {};
11118
11382
  const upperType = artifactType.trim().toUpperCase();
11383
+ if (typeof slcInput === "object") {
11384
+ if (slcInput[upperType]) return slcInput[upperType];
11385
+ for (const [key, mapping] of Object.entries(slcInput)) {
11386
+ if (key.toUpperCase() === upperType || upperType.startsWith("LESSON") && key.toUpperCase().startsWith("LESSON")) {
11387
+ return mapping;
11388
+ }
11389
+ }
11390
+ return {};
11391
+ }
11392
+ const slcMarkdown = slcInput;
11119
11393
  const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
11120
11394
  for (const block of blocks) {
11121
11395
  const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
@@ -11598,6 +11872,18 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11598
11872
  }
11599
11873
  const pedagogy = detectProjectPedagogy(framework, styleGuide, options.pedagogy);
11600
11874
  const pedagogyLabel = pedagogy.toUpperCase();
11875
+ const styleGuideExcerpt = styleGuide ? buildSectionAwareExcerpt(styleGuide, {
11876
+ priorities: ["Voice & Tone", "Standard Terminology (Glossary)", "Lesson Content Standards"],
11877
+ budget: 2e3
11878
+ }).excerpt : "";
11879
+ const refPackExcerpt = refPack ? buildSectionAwareExcerpt(refPack, {
11880
+ priorities: [
11881
+ "Technical Overview & Architecture Blueprint",
11882
+ "Hardware Pinout & Wiring Configuration Matrix",
11883
+ "Core Pedagogical Concept Anchor & Real-World Domain Bridge"
11884
+ ],
11885
+ budget: 2500
11886
+ }).excerpt : "";
11601
11887
  const commonContext = `PROJECT & ACADEMIC CONTEXT:
11602
11888
  - Project ID: ${projectId}
11603
11889
  - Pedagogical Model: ${pedagogyLabel}
@@ -11611,10 +11897,10 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11611
11897
  ${buildFrameworkExcerptForLesson(framework, lessonId)}
11612
11898
 
11613
11899
  [CONTENT STYLE GUIDE EXCERPT]:
11614
- ${styleGuide.slice(0, 1500)}
11900
+ ${styleGuideExcerpt}
11615
11901
 
11616
11902
  [REFERENCE PACK GROUND TRUTH]:
11617
- ${refPack.slice(0, 1e3)}${standardsContext ? "\n\n" + standardsContext : ""}${expositionContext ? "\n\n[KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)]:\n" + expositionContext : ""}${sessionSliceContext ? "\n\n" + sessionSliceContext : ""}`;
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 : ""}`;
11618
11904
  const producedArtifacts = [];
11619
11905
  const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
11620
11906
  const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
@@ -11734,14 +12020,15 @@ ${yamlBlock.trim()}
11734
12020
  const judgeResult = await LLMJudgeEngine.evaluateArtifactWithLLM({
11735
12021
  targetArtifactType: "LESSON",
11736
12022
  lessonId: lessonCode,
11737
- expectedLanguage: "en",
12023
+ expectedLanguage: targetLang || "en",
11738
12024
  targetObjectives: judgeObjectives,
11739
12025
  generatedContentJson: JSON.stringify({
11740
12026
  lessonContent,
11741
12027
  lessonId: lessonCode,
11742
12028
  title: lessonTitle,
11743
- frameworkExcerpt: framework ? framework.slice(0, 2500) : void 0,
11744
- styleGuideExcerpt: styleGuide ? styleGuide.slice(0, 1500) : void 0
12029
+ frameworkExcerpt: framework ? buildFrameworkExcerptForLesson(framework, lessonId) : void 0,
12030
+ styleGuideExcerpt: styleGuideExcerpt || void 0,
12031
+ canonicalExposition: expositionContext ? expositionContext.slice(0, 3e3) : void 0
11745
12032
  }),
11746
12033
  standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0
11747
12034
  });
@@ -11806,14 +12093,15 @@ ${currentContent}` }],
11806
12093
  const reJudge = await LLMJudgeEngine.evaluateArtifactWithLLM({
11807
12094
  targetArtifactType: "LESSON",
11808
12095
  lessonId: lessonCode,
11809
- expectedLanguage: "en",
12096
+ expectedLanguage: targetLang || "en",
11810
12097
  targetObjectives: parseLessonObjectives(currentContent, concept, bloomLevel),
11811
12098
  generatedContentJson: JSON.stringify({
11812
12099
  lessonContent: currentContent,
11813
12100
  lessonId: lessonCode,
11814
12101
  title: lessonTitle,
11815
- frameworkExcerpt: framework ? framework.slice(0, 2500) : void 0,
11816
- styleGuideExcerpt: styleGuide ? styleGuide.slice(0, 1500) : void 0
12102
+ frameworkExcerpt: framework ? buildFrameworkExcerptForLesson(framework, lessonId) : void 0,
12103
+ styleGuideExcerpt: styleGuideExcerpt || void 0,
12104
+ canonicalExposition: expositionContext ? expositionContext.slice(0, 3e3) : void 0
11817
12105
  }),
11818
12106
  standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0
11819
12107
  });
@@ -11934,10 +12222,34 @@ ${currentContent}` }],
11934
12222
  contentHash
11935
12223
  });
11936
12224
  }
12225
+ const lessonExcerptResult = buildSectionAwareExcerpt(lessonContent, {
12226
+ priorities: DEFAULT_LESSON_PRIORITIES,
12227
+ budget: 12e3,
12228
+ sectionLanguageContract: slcMarkdown,
12229
+ artifactType: "LESSON"
12230
+ });
12231
+ const lessonExcerpt = lessonExcerptResult.excerpt;
11937
12232
  const satelliteContext = `${commonContext}
11938
12233
 
11939
12234
  [CANONICAL LESSON PLAN (${pedagogyLabel})]:
11940
- ${lessonContent.slice(0, 4e3)}`;
12235
+ ${lessonExcerpt}`;
12236
+ const judgeSat = (sat, content) => {
12237
+ if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
12238
+ return judgeSatelliteArtifact({
12239
+ storage,
12240
+ projectId,
12241
+ lessonCode,
12242
+ sat,
12243
+ content,
12244
+ concept,
12245
+ bloomLevel,
12246
+ standardStatements,
12247
+ onProgress,
12248
+ targetLang,
12249
+ canonicalLessonContent: lessonContent,
12250
+ canonicalLessonExcerpt: lessonExcerpt
12251
+ });
12252
+ };
11941
12253
  const actRelPath = `_content/${unitCode}/ACT_${lessonCode}.md`;
11942
12254
  if (artifactScope.includes("ACT") && (!await storage.readArtifact(projectId, actRelPath) || force)) {
11943
12255
  onProgress?.("@activity", `[2/4] Authoring Hands-on Lab: \`ACT_${lessonCode}.md\`...`);
@@ -12006,19 +12318,7 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
12006
12318
  contentHash: computeContentHash(rawAct)
12007
12319
  });
12008
12320
  producedArtifacts.push(`ACT_${lessonCode}.md`);
12009
- if (gateModeFor(gates, "ACT") === "LLM_JUDGE") {
12010
- await judgeSatelliteArtifact({
12011
- storage,
12012
- projectId,
12013
- lessonCode,
12014
- sat: "ACT",
12015
- content: rawAct,
12016
- concept,
12017
- bloomLevel,
12018
- standardStatements,
12019
- onProgress
12020
- });
12021
- }
12321
+ await judgeSat("ACT", rawAct);
12022
12322
  }
12023
12323
  const quizRelPath = `_content/${unitCode}/QUIZ_${lessonCode}.md`;
12024
12324
  if (artifactScope.includes("QUIZ") && (!await storage.readArtifact(projectId, quizRelPath) || force)) {
@@ -12082,64 +12382,25 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
12082
12382
  contentHash: computeContentHash(rawQuiz)
12083
12383
  });
12084
12384
  producedArtifacts.push(`QUIZ_${lessonCode}.md`);
12085
- if (gateModeFor(gates, "QUIZ") === "LLM_JUDGE") {
12086
- await judgeSatelliteArtifact({
12087
- storage,
12088
- projectId,
12089
- lessonCode,
12090
- sat: "QUIZ",
12091
- content: rawQuiz,
12092
- concept,
12093
- bloomLevel,
12094
- standardStatements,
12095
- onProgress
12096
- });
12097
- }
12385
+ await judgeSat("QUIZ", rawQuiz);
12098
12386
  }
12099
12387
  const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
12100
12388
  if (artifactScope.includes("SLIDE") && (!await storage.readArtifact(projectId, slideRelPath) || force)) {
12101
12389
  onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
12102
12390
  const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12103
- const slidePrompt = `You are @illustrator & @content (Pedagogical Slide Deck Presentation Specialists).
12104
- Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, lesson context, and the MARP SLIDE TEMPLATE below, author the complete, highly engaging Marp presentation deck: \`SLIDE_${lessonCode}.md\`.
12105
-
12106
- [MARP SLIDE TEMPLATE SCAFFOLD]:
12107
- ${canonicalSlideTemplate || `---
12391
+ const templateScaffold = canonicalSlideTemplate || `---
12108
12392
  marp: true
12109
12393
  theme: default
12110
12394
  paginate: true
12111
12395
  header: "${titleHeader(lessonTitle)}"
12112
12396
  footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
12113
- ---`}
12114
-
12115
- MANDATORY 12-14 SLIDE MICRO-CYCLE STRUCTURE (Delimited by '---'):
12116
- 1. **Slide 1 [Title & Mission]:** ${lessonCode}: ${lessonTitle} + Real-world inquiry hook
12117
- 2. **Slide 2 [Agenda & Roadmap]:** Timing and milestone breakdown table (${pedagogyLabel})
12118
- 3. **Slide 3 [Warm-Up / Prior Knowledge]:** 2-column recall + 60-second diagnostic challenge card
12119
- 4. **Slide 4 [High-Stakes Hook Scenario]:** Authentic engineering/domain crisis card + dilemma constraint
12120
- 5. **Slide 5 [Core Concept 1: Mental Model]:** Definition, intuitive everyday analogy, and golden rule card
12121
- 6. **Slide 6 [Architecture & Visual Flow]:** Clean Mermaid diagram (flowchart or sequenceDiagram) + 3 flow steps
12122
- 7. **Slide 7 [Core Concept 2: Syntax / Mechanism Anatomy]:** Side-by-side 6-10 lines runnable code + anatomy breakdown & syntax trap card
12123
- 8. **Slide 8 [Guided Practice: Try It Now]:** 3-minute active desk task card with starter hint and success criteria
12124
- 9. **Slide 9 [Integrated Hands-On Lab]:** 3-tier differentiated challenge cards (Bronze / Silver / Gold) + gotcha alert card
12125
- 10. **Slide 10 [Traffic Light Checkpoint & Pitfalls]:** Formative self-check cards (\u{1F7E2}/\u{1F7E1}/\u{1F534}) & diagnostic test question
12126
- 11. **Slide 11 [Exit Ticket & Metacognition]:** 2 scenario-based check questions + 1 metacognitive reflection prompt
12127
- 12. **Slide 12 [Summary & Next Steps]:** 3 core golden rules + preview card of next upcoming milestone
12128
-
12129
- Mandatory Canvas & Presentation Invariants:
12130
- - A slide is an INTERACTIVE PROJECTION CANVAS, NOT a textbook! Absolutely NO dense paragraphs or walls of text.
12131
- - Follow the 6x6 rule: Maximum 4-6 bullet points per slide, maximum 8-12 words per bullet.
12132
- - Structure slides using \`<div class="columns"><div>...</div><div>...</div></div>\` and \`<div class="card">\`, \`<div class="card highlight">\`, \`<div class="card warning">\`, \`<div class="card success">\`.
12133
- - MANDATORY PRESENTER NOTES ON EVERY SLIDE: Every slide MUST end with \`<!-- Presenter Notes: ... -->\` containing:
12134
- * SCRIPT: 60-90s teacher talk track with intuitive analogies.
12135
- * COLD CALL / CHECK: Specific diagnostic question to ask students.
12136
- * SCAFFOLDING / GOTCHA: Guidance for students who get stuck.
12137
- * PACING: Recommended minutes for the slide.
12138
- - Output 100% valid Marp Markdown.
12139
-
12140
- ${languageDirective}
12141
-
12142
- ${buildHeadingDirective("SLIDE", slcMarkdown, targetLang)}`;
12397
+ ---`;
12398
+ const slidePrompt = buildMarpMarkdownSlidePrompt({
12399
+ lessonCode,
12400
+ templateScaffold,
12401
+ languageDirective,
12402
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12403
+ });
12143
12404
  const rawSlide = await runCurriculumAIInference(
12144
12405
  [{ role: "user", content: slidePrompt }],
12145
12406
  satelliteContext,
@@ -12165,19 +12426,7 @@ ${buildHeadingDirective("SLIDE", slcMarkdown, targetLang)}`;
12165
12426
  contentHash: computeContentHash(rawSlide)
12166
12427
  });
12167
12428
  producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12168
- if (gateModeFor(gates, "SLIDE") === "LLM_JUDGE") {
12169
- await judgeSatelliteArtifact({
12170
- storage,
12171
- projectId,
12172
- lessonCode,
12173
- sat: "SLIDE",
12174
- content: rawSlide,
12175
- concept,
12176
- bloomLevel,
12177
- standardStatements,
12178
- onProgress
12179
- });
12180
- }
12429
+ await judgeSat("SLIDE", rawSlide);
12181
12430
  }
12182
12431
  const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
12183
12432
  if (artifactScope.includes("GUIDE") && (!await storage.readArtifact(projectId, guideRelPath) || force)) {
@@ -12227,19 +12476,7 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
12227
12476
  contentHash: computeContentHash(rawGuide)
12228
12477
  });
12229
12478
  producedArtifacts.push(`GUIDE_${lessonCode}.md`);
12230
- if (gateModeFor(gates, "GUIDE") === "LLM_JUDGE") {
12231
- await judgeSatelliteArtifact({
12232
- storage,
12233
- projectId,
12234
- lessonCode,
12235
- sat: "GUIDE",
12236
- content: rawGuide,
12237
- concept,
12238
- bloomLevel,
12239
- standardStatements,
12240
- onProgress
12241
- });
12242
- }
12479
+ await judgeSat("GUIDE", rawGuide);
12243
12480
  }
12244
12481
  }
12245
12482
  const handoutRelPath = `_content/${unitCode}/HANDOUT_${lessonCode}.md`;
@@ -12290,19 +12527,7 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
12290
12527
  contentHash: computeContentHash(rawHandout)
12291
12528
  });
12292
12529
  producedArtifacts.push(`HANDOUT_${lessonCode}.md`);
12293
- if (gateModeFor(gates, "HANDOUT") === "LLM_JUDGE") {
12294
- await judgeSatelliteArtifact({
12295
- storage,
12296
- projectId,
12297
- lessonCode,
12298
- sat: "HANDOUT",
12299
- content: rawHandout,
12300
- concept,
12301
- bloomLevel,
12302
- standardStatements,
12303
- onProgress
12304
- });
12305
- }
12530
+ await judgeSat("HANDOUT", rawHandout);
12306
12531
  }
12307
12532
  }
12308
12533
  const wksRelPath = `_content/${unitCode}/WKS_${lessonCode}.md`;
@@ -12357,19 +12582,7 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
12357
12582
  contentHash: computeContentHash(rawWks)
12358
12583
  });
12359
12584
  producedArtifacts.push(`WKS_${lessonCode}.md`);
12360
- if (gateModeFor(gates, "WKS") === "LLM_JUDGE") {
12361
- await judgeSatelliteArtifact({
12362
- storage,
12363
- projectId,
12364
- lessonCode,
12365
- sat: "WKS",
12366
- content: rawWks,
12367
- concept,
12368
- bloomLevel,
12369
- standardStatements,
12370
- onProgress
12371
- });
12372
- }
12585
+ await judgeSat("WKS", rawWks);
12373
12586
  }
12374
12587
  }
12375
12588
  const codeRelPath = `_content/${unitCode}/CODE_${lessonCode}.md`;
@@ -12433,19 +12646,7 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
12433
12646
  contentHash: computeContentHash(rawCode)
12434
12647
  });
12435
12648
  producedArtifacts.push(`CODE_${lessonCode}.md`);
12436
- if (gateModeFor(gates, "CODE") === "LLM_JUDGE") {
12437
- await judgeSatelliteArtifact({
12438
- storage,
12439
- projectId,
12440
- lessonCode,
12441
- sat: "CODE",
12442
- content: rawCode,
12443
- concept,
12444
- bloomLevel,
12445
- standardStatements,
12446
- onProgress
12447
- });
12448
- }
12649
+ await judgeSat("CODE", rawCode);
12449
12650
  }
12450
12651
  }
12451
12652
  const extRelPath = `_content/${unitCode}/EXT_${lessonCode}.md`;
@@ -12498,19 +12699,7 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
12498
12699
  contentHash: computeContentHash(rawExt)
12499
12700
  });
12500
12701
  producedArtifacts.push(`EXT_${lessonCode}.md`);
12501
- if (gateModeFor(gates, "EXT") === "LLM_JUDGE") {
12502
- await judgeSatelliteArtifact({
12503
- storage,
12504
- projectId,
12505
- lessonCode,
12506
- sat: "EXT",
12507
- content: rawExt,
12508
- concept,
12509
- bloomLevel,
12510
- standardStatements,
12511
- onProgress
12512
- });
12513
- }
12702
+ await judgeSat("EXT", rawExt);
12514
12703
  }
12515
12704
  }
12516
12705
  onProgress?.("@reviewer", `\u2705 Successfully authored all requested artifacts for \`${lessonCode}\` (${pedagogyLabel})!`);
@@ -12576,12 +12765,22 @@ async function judgeSatelliteArtifact(ctx) {
12576
12765
  const { storage, projectId, lessonCode, sat, content } = ctx;
12577
12766
  try {
12578
12767
  ctx.onProgress?.("@reviewer", `\u{1F916} Judging ${sat}_${lessonCode}...`);
12768
+ const targetObjectives = parseLessonObjectives(
12769
+ ctx.canonicalLessonContent || content,
12770
+ ctx.concept,
12771
+ ctx.bloomLevel
12772
+ );
12579
12773
  const result = await LLMJudgeEngine.evaluateArtifactWithLLM({
12580
12774
  targetArtifactType: sat,
12581
12775
  lessonId: lessonCode,
12582
- expectedLanguage: "en",
12583
- targetObjectives: parseLessonObjectives(content, ctx.concept, ctx.bloomLevel),
12584
- generatedContentJson: JSON.stringify({ content, artifactType: sat, lessonId: lessonCode }),
12776
+ expectedLanguage: ctx.targetLang || "en",
12777
+ targetObjectives,
12778
+ generatedContentJson: JSON.stringify({
12779
+ content,
12780
+ artifactType: sat,
12781
+ lessonId: lessonCode,
12782
+ canonicalLessonExcerpt: ctx.canonicalLessonExcerpt
12783
+ }),
12585
12784
  standardStatements: Object.keys(ctx.standardStatements).length > 0 ? ctx.standardStatements : void 0
12586
12785
  });
12587
12786
  const passed = result.passed && result.score >= 85;
@@ -12704,6 +12903,87 @@ async function produceBatchLessons(storage, projectId, lessonIds, options = {})
12704
12903
  // src/services/singleArtifactService.ts
12705
12904
  init_streamRunner();
12706
12905
  init_errors();
12906
+
12907
+ // src/services/contextRouting.ts
12908
+ init_errors();
12909
+ var ARTIFACT_DEPENDENCY_ROUTING = {
12910
+ LESSON: {
12911
+ required: ["KNOWLEDGE_EXPOSITION", "SESSION_SLICE", "FRAMEWORK_EXCERPT"],
12912
+ optional: ["STYLE_GUIDE", "REFERENCE_PACK"],
12913
+ rationale: "D\u1EA1y theo ki\u1EBFn th\u1EE9c chu\u1EA9n c\u1EE7a plan, ph\xE2n b\u1ED5 m\u1EE5c ti\xEAu v\xE0 khung ch\u01B0\u01A1ng tr\xECnh"
12914
+ },
12915
+ HANDOUT: {
12916
+ required: ["KNOWLEDGE_EXPOSITION"],
12917
+ optional: ["SESSION_SLICE"],
12918
+ rationale: "T\xE0i li\u1EC7u t\u1EF1 h\u1ECDc \u2014 b\xE1m kh\xE1i ni\u1EC7m chu\u1EA9n, kh\xF4ng c\u1EA7n activity timeline"
12919
+ },
12920
+ EXT: {
12921
+ required: ["KNOWLEDGE_EXPOSITION"],
12922
+ optional: ["SESSION_SLICE"],
12923
+ rationale: "Th\u1EED th\xE1ch m\u1EDF r\u1ED9ng chuy\xEAn s\xE2u b\xE1m kh\xE1i ni\u1EC7m"
12924
+ },
12925
+ CODE: {
12926
+ required: ["KNOWLEDGE_EXPOSITION"],
12927
+ optional: ["LESSON_OBJECTIVES"],
12928
+ rationale: "Code lab th\u1EF1c h\xE0nh b\xE1m kh\xE1i ni\u1EC7m k\u1EF9 thu\u1EADt v\xE0 v\xED d\u1EE5 worked examples"
12929
+ },
12930
+ ACT: {
12931
+ required: ["CANONICAL_LESSON"],
12932
+ optional: ["KNOWLEDGE_EXPOSITION", "SESSION_SLICE"],
12933
+ rationale: "STEM Activity Lab b\u1EAFt bu\u1ED9c ph\u1EA3i kh\u1EDBp 100% Activity Sequence v\xE0 Lesson Flow c\u1EE7a LESSON"
12934
+ },
12935
+ WKS: {
12936
+ required: ["CANONICAL_LESSON"],
12937
+ optional: ["KNOWLEDGE_EXPOSITION"],
12938
+ rationale: "Phi\u1EBFu h\u1ECDc t\u1EADp (Worksheet) luy\u1EC7n t\u1EADp theo c\xE1c ch\u1EB7ng ho\u1EA1t \u0111\u1ED9ng c\u1EE7a gi\xE1o \xE1n"
12939
+ },
12940
+ GUIDE: {
12941
+ required: ["CANONICAL_LESSON"],
12942
+ optional: ["SESSION_SLICE"],
12943
+ rationale: "K\u1ECBch b\u1EA3n s\u01B0 ph\u1EA1m gi\xE1o vi\xEAn (Teacher Guide) b\xE1m s\xE1t timeline t\u1EEBng ph\xFAt c\u1EE7a LESSON"
12944
+ },
12945
+ SLIDE: {
12946
+ required: ["CANONICAL_LESSON"],
12947
+ optional: ["KNOWLEDGE_EXPOSITION"],
12948
+ rationale: "Slide b\xE0i gi\u1EA3ng tr\xECnh chi\u1EBFu theo c\u1EA5u tr\xFAc ph\xE2n ph\u1ED1i n\u1ED9i dung c\u1EE7a gi\xE1o \xE1n"
12949
+ },
12950
+ QUIZ: {
12951
+ required: ["KNOWLEDGE_EXPOSITION", "CANONICAL_LESSON"],
12952
+ optional: ["SESSION_SLICE"],
12953
+ rationale: "Ng\xE2n h\xE0ng c\xE2u h\u1ECFi \u0111\xE1nh gi\xE1 c\u1EA3 ki\u1EBFn th\u1EE9c chu\u1EA9n m\u1EF1c (KX) l\u1EABn m\u1EE5c ti\xEAu b\xE0i d\u1EA1y (LESSON)"
12954
+ }
12955
+ };
12956
+ function validateArtifactDependencies(artifactType, availableSources, opts = {}) {
12957
+ const normType = (artifactType || "").toUpperCase().trim();
12958
+ const rule = ARTIFACT_DEPENDENCY_ROUTING[normType];
12959
+ const failClosed = opts.failClosed ?? true;
12960
+ if (!rule) {
12961
+ return { valid: true, missing: [] };
12962
+ }
12963
+ const missing = [];
12964
+ for (const req of rule.required) {
12965
+ const val = availableSources[req];
12966
+ const isPresent = val !== void 0 && val !== null && (typeof val === "string" ? val.trim().length > 0 : true);
12967
+ if (!isPresent) {
12968
+ missing.push(req);
12969
+ }
12970
+ }
12971
+ if (missing.length > 0) {
12972
+ const err = createContextMissingError({
12973
+ artifactType: normType,
12974
+ missingSource: missing[0],
12975
+ lessonId: opts.lessonId,
12976
+ agent: opts.agent
12977
+ });
12978
+ if (failClosed) {
12979
+ throw err;
12980
+ }
12981
+ return { valid: false, missing, error: err };
12982
+ }
12983
+ return { valid: true, missing: [] };
12984
+ }
12985
+
12986
+ // src/services/singleArtifactService.ts
12707
12987
  function getArtifactMetadata(type, lessonId, pedagogy) {
12708
12988
  const id = lessonId || "ON_DEMAND";
12709
12989
  switch (type) {
@@ -12783,9 +13063,24 @@ async function generateSingleArtifact(req) {
12783
13063
  const templateContent = loadedTemplate || getDefaultFallbackTemplate(artifactType, displayTitle, lessonId || "ON_DEMAND");
12784
13064
  const hwStr = hardwarePlatform.join(", ");
12785
13065
  const selectedDevices = (config?.devices || (hardwarePlatform[0]?.toLowerCase().includes("paper") ? "paper-only" : "laptop")).toLowerCase();
12786
- const isBilingual = language.toLowerCase().includes("bilingual") || language.toLowerCase().includes("song");
12787
- const isVietnamese = !isBilingual && language.toLowerCase().includes("viet");
12788
- const langRule = isBilingual ? `1. AUTHORING LANGUAGE: Bilingual (Vietnamese - English). Headings and core conceptual definitions should be presented in Vietnamese with English technical terms in parentheses. Explanations and instructional text are in Vietnamese. Key code terms, comments, and challenge summaries are presented bilingually.` : isVietnamese ? `1. AUTHORING LANGUAGE: Vietnamese for all instructional text, explanations, questions, rubrics, and presenter notes. Programming code, syntax keywords, and standard API identifiers remain in standard English (with Vietnamese explanation comments/notes).` : `1. AUTHORING LANGUAGE: 100% English (Plan C Canonical Specification).`;
13066
+ language.toLowerCase().includes("bilingual") || language.toLowerCase().includes("song");
13067
+ const targetLang = resolveTargetLanguageCode(language);
13068
+ const langDirective = buildLanguageDirective(language);
13069
+ const headingDirective = buildHeadingDirective(
13070
+ artifactType.toUpperCase(),
13071
+ req.sectionLanguageContract,
13072
+ targetLang
13073
+ );
13074
+ const availableSources = {
13075
+ KNOWLEDGE_EXPOSITION: contextSot.exposition || researchContext,
13076
+ CANONICAL_LESSON: contextSot.lessonPlan,
13077
+ FRAMEWORK_EXCERPT: contextSot.framework
13078
+ };
13079
+ validateArtifactDependencies(artifactType.toUpperCase(), availableSources, {
13080
+ lessonId,
13081
+ agent: meta.persona,
13082
+ failClosed: req.failClosedContext ?? false
13083
+ });
12789
13084
  const isPaperOnly = selectedDevices === "paper-only" || hwStr.toLowerCase().includes("paper") || hwStr.toLowerCase().includes("print-ready");
12790
13085
  const deviceRule = isPaperOnly ? `
12791
13086
  - \u{1F4C4} PAPER-ONLY / NO-DEVICE ENVIRONMENT: The learning space has NO computers or screens. All exercises, worksheets, task cards, exit tickets, and code analyses MUST be 100% printable. Provide write-in blank boxes, printed code snippets with line numbers, tracing tables, and physical manipulative instructions.` : selectedDevices === "tablet" || selectedDevices === "smartphone" ? `
@@ -12806,7 +13101,8 @@ async function generateSingleArtifact(req) {
12806
13101
  Your task is to author a canonical, publication-grade learning artifact of type "${artifactType.toUpperCase()}".
12807
13102
 
12808
13103
  ### CORE OPERATIONAL INVARIANTS:
12809
- ${langRule}
13104
+ ${langDirective}
13105
+ ${headingDirective}
12810
13106
  2. NON-MOCK CODE INVARIANT: Any code snippet provided MUST be 100% syntactically valid, idiomatic, and executable without placeholders or fake functions.
12811
13107
  3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
12812
13108
  4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
@@ -12815,6 +13111,11 @@ ${langRule}
12815
13111
 
12816
13112
  IMAGE PLANNING (media ledger) \u2014 QUOTA GEN: t\u1ED1i \u0111a ${mediaPolicy.maxGenImagesPerArtifact} \u1EA3nh AI cho artifact n\xE0y (\u1EA3nh search kho kh\xF4ng gi\u1EDBi h\u1EA1n).
12817
13113
  Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u1ED3 kh\xE1i ni\u1EC7m, step-by-step visual), ch\xE8n placeholder \u0111\xFAng format [IMAGE: slug] (slug ch\u1EEF th\u01B0\u1EDDng-g\u1EA1ch ngang, unique trong b\xE0i, vd img-for-loop-diagram) T\u1EA0I \u0110\xDANG CH\u1ED6 c\u1EA7n \u1EA3nh, tr\xEAn d\xF2ng ri\xEAng. KH\xD4NG t\u1EF1 sinh \u1EA3nh, KH\xD4NG d\xF9ng markdown image \u2014 placeholder s\u1EBD \u0111\u01B0\u1EE3c thay b\u1EB1ng \u1EA3nh th\u1EADt sau khi Media Curator duy\u1EC7t. Ch\u1EC9 \u0111\u1EB7t cho \u1EA3nh TH\u1EF0C S\u1EF0 c\u1EA7n thi\u1EBFt.` : ""}`;
13114
+ const slcContract = req.sectionLanguageContract;
13115
+ const frameworkExcerpt = contextSot.framework ? buildSectionAwareExcerpt(contextSot.framework, { budget: 2500, sectionLanguageContract: slcContract }).excerpt : "";
13116
+ const lessonPlanExcerpt = contextSot.lessonPlan ? buildLessonExcerpt(contextSot.lessonPlan, { budget: 8e3, sectionLanguageContract: slcContract, artifactType: "LESSON" }).excerpt : "";
13117
+ const handoutExcerpt = contextSot.handout ? buildSectionAwareExcerpt(contextSot.handout, { budget: 2e3, sectionLanguageContract: slcContract }).excerpt : "";
13118
+ const expositionExcerpt = contextSot.exposition ? buildExpositionExcerpt(contextSot.exposition, { budget: 6e3, sectionLanguageContract: slcContract }).excerpt : "";
12818
13119
  const userPrompt = `### CONTEXT GROUND TRUTH:
12819
13120
  - Topic / Concept Domain: "${topic}"
12820
13121
  - Lesson ID: "${lessonId || "ON_DEMAND"}"
@@ -12832,14 +13133,17 @@ ${selectedDevices ? `- Device Mode: "${selectedDevices}"
12832
13133
  ${researchContext ? `### RESEARCH GROUND TRUTH (fact-checked context \u2014 \u01B0u ti\xEAn d\xF9ng, KH\xD4NG b\u1ECBa s\u1ED1 li\u1EC7u):
12833
13134
  ${researchContext.slice(0, 4e3)}
12834
13135
  ` : ""}
12835
- ${contextSot.framework ? `### SOT CURRICULUM FRAMEWORK:
12836
- ${contextSot.framework.slice(0, 1500)}
13136
+ ${expositionExcerpt ? `### SOT KNOWLEDGE EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict):
13137
+ ${expositionExcerpt}
13138
+ ` : ""}
13139
+ ${frameworkExcerpt ? `### SOT CURRICULUM FRAMEWORK:
13140
+ ${frameworkExcerpt}
12837
13141
  ` : ""}
12838
- ${contextSot.lessonPlan ? `### SOT CANONICAL LESSON PLAN:
12839
- ${contextSot.lessonPlan.slice(0, 1500)}
13142
+ ${lessonPlanExcerpt ? `### SOT CANONICAL LESSON PLAN:
13143
+ ${lessonPlanExcerpt}
12840
13144
  ` : ""}
12841
- ${contextSot.handout ? `### SOT STUDENT HANDOUT:
12842
- ${contextSot.handout.slice(0, 1e3)}
13145
+ ${handoutExcerpt ? `### SOT STUDENT HANDOUT:
13146
+ ${handoutExcerpt}
12843
13147
  ` : ""}
12844
13148
 
12845
13149
  ### MANDATORY CANONICAL TEMPLATE CONTRACT:
@@ -12928,9 +13232,16 @@ ${yaml.trim()}
12928
13232
  const verdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
12929
13233
  targetArtifactType: artifactType.toUpperCase(),
12930
13234
  lessonId: lessonId || "ON_DEMAND",
12931
- expectedLanguage: language.toLowerCase().includes("viet") ? "vietnamese" : "english",
13235
+ expectedLanguage: targetLang || "en",
12932
13236
  targetObjectives: judgeObjectives,
12933
- generatedContentJson: JSON.stringify({ content: finalContent, artifactType, lessonId: lessonId || "ON_DEMAND", title: displayTitle })
13237
+ generatedContentJson: JSON.stringify({
13238
+ content: finalContent,
13239
+ artifactType,
13240
+ lessonId: lessonId || "ON_DEMAND",
13241
+ title: displayTitle,
13242
+ canonicalLessonExcerpt: lessonPlanExcerpt || void 0,
13243
+ canonicalExposition: expositionExcerpt || void 0
13244
+ })
12934
13245
  });
12935
13246
  const passed = Boolean(verdict.passed) && verdict.score >= 85;
12936
13247
  if (passed) {
@@ -12972,9 +13283,16 @@ ${finalContent}` }
12972
13283
  const reVerdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
12973
13284
  targetArtifactType: artifactType.toUpperCase(),
12974
13285
  lessonId: lessonId || "ON_DEMAND",
12975
- expectedLanguage: language.toLowerCase().includes("viet") ? "vietnamese" : "english",
13286
+ expectedLanguage: targetLang || "en",
12976
13287
  targetObjectives: judgeObjectives,
12977
- generatedContentJson: JSON.stringify({ content: repaired, artifactType, lessonId: lessonId || "ON_DEMAND", title: displayTitle })
13288
+ generatedContentJson: JSON.stringify({
13289
+ content: repaired,
13290
+ artifactType,
13291
+ lessonId: lessonId || "ON_DEMAND",
13292
+ title: displayTitle,
13293
+ canonicalLessonExcerpt: lessonPlanExcerpt || void 0,
13294
+ canonicalExposition: expositionExcerpt || void 0
13295
+ })
12978
13296
  });
12979
13297
  if (reVerdict.passed && reVerdict.score >= 85) {
12980
13298
  return {
@@ -16926,6 +17244,7 @@ function renderMediaPlaceholder(entry) {
16926
17244
  }
16927
17245
 
16928
17246
  exports.ACT_TEMPLATE = ACT_TEMPLATE;
17247
+ exports.ARTIFACT_DEPENDENCY_ROUTING = ARTIFACT_DEPENDENCY_ROUTING;
16929
17248
  exports.ARTIFACT_EXECUTION_ORDER = ARTIFACT_EXECUTION_ORDER;
16930
17249
  exports.AcademicAuditor = AcademicAuditor;
16931
17250
  exports.ActiveLearningWorkflowSchema = ActiveLearningWorkflowSchema;
@@ -16970,6 +17289,8 @@ exports.CurriculumPlanSchema = CurriculumPlanSchema;
16970
17289
  exports.CurriculumQualityReportSchema = CurriculumQualityReportSchema;
16971
17290
  exports.DEFAULT_ENABLED_PROVIDERS = DEFAULT_ENABLED_PROVIDERS;
16972
17291
  exports.DEFAULT_GATE_SETTINGS = DEFAULT_GATE_SETTINGS;
17292
+ exports.DEFAULT_KX_PRIORITIES = DEFAULT_KX_PRIORITIES;
17293
+ exports.DEFAULT_LESSON_PRIORITIES = DEFAULT_LESSON_PRIORITIES;
16973
17294
  exports.DEFAULT_STREAM_IDLE_MS = DEFAULT_STREAM_IDLE_MS;
16974
17295
  exports.DEFAULT_STREAM_TOTAL_MS = DEFAULT_STREAM_TOTAL_MS;
16975
17296
  exports.DependencyEdgeSchema = DependencyEdgeSchema;
@@ -17069,6 +17390,7 @@ exports.RubricCriteriaSchema = RubricCriteriaSchema;
17069
17390
  exports.RubricSchema = RubricSchema;
17070
17391
  exports.SCIENCE_LAB_TEMPLATE = SCIENCE_LAB_TEMPLATE;
17071
17392
  exports.SELF_LAB_TEMPLATE = SELF_LAB_TEMPLATE;
17393
+ exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
17072
17394
  exports.SLIDE_LAYOUT_PRESETS = SLIDE_LAYOUT_PRESETS;
17073
17395
  exports.SLIDE_TEMPLATE = SLIDE_TEMPLATE;
17074
17396
  exports.STANDARD_SOT_FILES = STANDARD_SOT_FILES;
@@ -17123,14 +17445,18 @@ exports.buildCurriculumPlan = buildCurriculumPlan;
17123
17445
  exports.buildDeliveryPackages = buildDeliveryPackages;
17124
17446
  exports.buildDiagnosticQuizPrompt = buildDiagnosticQuizPrompt;
17125
17447
  exports.buildExpositionContext = buildExpositionContext;
17448
+ exports.buildExpositionExcerpt = buildExpositionExcerpt;
17126
17449
  exports.buildExtensionPrompt = buildExtensionPrompt;
17127
17450
  exports.buildHandoutPrompt = buildHandoutPrompt;
17128
17451
  exports.buildHeadingDirective = buildHeadingDirective;
17129
17452
  exports.buildImagePrompt = buildImagePrompt;
17130
17453
  exports.buildJudgePrompt = buildJudgePrompt;
17131
17454
  exports.buildLanguageDirective = buildLanguageDirective;
17455
+ exports.buildLessonExcerpt = buildLessonExcerpt;
17132
17456
  exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
17457
+ exports.buildMarpMarkdownSlidePrompt = buildMarpMarkdownSlidePrompt;
17133
17458
  exports.buildProjectInstructionPrompt = buildProjectInstructionPrompt;
17459
+ exports.buildSectionAwareExcerpt = buildSectionAwareExcerpt;
17134
17460
  exports.buildSelfLabPrompt = buildSelfLabPrompt;
17135
17461
  exports.buildSessionSliceContext = buildSessionSliceContext;
17136
17462
  exports.buildSlidesPrompt = buildSlidesPrompt;
@@ -17146,6 +17472,7 @@ exports.conductPreliminaryResearch = conductPreliminaryResearch;
17146
17472
  exports.contentTools = contentTools;
17147
17473
  exports.convertRoadmapToFoundationSot = convertRoadmapToFoundationSot;
17148
17474
  exports.createAiInferenceError = createAiInferenceError;
17475
+ exports.createContextMissingError = createContextMissingError;
17149
17476
  exports.createCurriculumStorage = createCurriculumStorage;
17150
17477
  exports.createIdleAbortController = createIdleAbortController;
17151
17478
  exports.createStreamAbortController = createStreamAbortController;
@@ -17213,6 +17540,7 @@ exports.lintFrameworkPack = lintFrameworkPack;
17213
17540
  exports.loadCurriculumTemplate = loadCurriculumTemplate;
17214
17541
  exports.loadSotTemplate = loadSotTemplate;
17215
17542
  exports.normalizePlanningGraph = normalizePlanningGraph;
17543
+ exports.normalizeSlcContract = normalizeSlcContract;
17216
17544
  exports.packagerTools = packagerTools;
17217
17545
  exports.parseGateSettings = parseGateSettings;
17218
17546
  exports.parseQuizMarkdown = parseQuizMarkdown;
@@ -17256,6 +17584,7 @@ exports.targetLanguageDisplayName = targetLanguageDisplayName;
17256
17584
  exports.techSmeTools = techSmeTools;
17257
17585
  exports.topoSort = topoSort;
17258
17586
  exports.uploadAssetToBucket = uploadAssetToBucket;
17587
+ exports.validateArtifactDependencies = validateArtifactDependencies;
17259
17588
  exports.validateCurriculumPlan = validateCurriculumPlan;
17260
17589
  exports.validateFrameworkPack = validateFrameworkPack;
17261
17590
  exports.validateMarkdownTables = validateMarkdownTables;