@thanh01.pmt/curriculum-kit 1.4.7 → 1.4.8

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
@@ -9819,6 +9819,224 @@ function gateModeFor(resolved, type) {
9819
9819
  return resolved[type] ?? "OFF";
9820
9820
  }
9821
9821
 
9822
+ // src/services/languageDirective.ts
9823
+ function resolveTargetLanguageCode(language) {
9824
+ const raw = String(language || "vi").trim().toLowerCase();
9825
+ if (raw.startsWith("vi")) return "vi";
9826
+ if (raw.startsWith("en")) return "en";
9827
+ return raw || "vi";
9828
+ }
9829
+ function targetLanguageDisplayName(code) {
9830
+ const map = {
9831
+ vi: "Vietnamese (Ti\u1EBFng Vi\u1EC7t)",
9832
+ en: "English (US)"
9833
+ };
9834
+ return map[code] || code;
9835
+ }
9836
+ function buildLanguageDirective(targetLanguage) {
9837
+ const code = resolveTargetLanguageCode(targetLanguage);
9838
+ const name = targetLanguageDisplayName(code);
9839
+ const perLanguageExamples = {
9840
+ vi: `- V\xED d\u1EE5 ti\xEAu \u0111\u1EC1 b\xE0i h\u1ECDc \u0111\xFAng chu\u1EA9n: "B\xE0i 1: Kh\xE1m ph\xE1 m\u1EA1ch \u0111i\u1EC7n c\u01A1 b\u1EA3n", "B\xE0i 2: \u0110i\u1EC1u khi\u1EC3n \u0111\xE8n LED nh\u1EA5p nh\xE1y" \u2014 KH\xD4NG d\xF9ng ti\xEAu \u0111\u1EC1 ti\u1EBFng Anh.`,
9841
+ en: `- Example compliant lesson title: "Lesson 1: Exploring Basic Circuits" \u2014 natural, idiomatic English throughout.`
9842
+ };
9843
+ const exampleLine = perLanguageExamples[code] || "";
9844
+ return `MANDATORY LANGUAGE DIRECTIVE (TARGET OUTPUT LANGUAGE: ${name}):
9845
+ - The TARGET OUTPUT LANGUAGE for this course is ${name}. You MUST author the ENTIRE document in ${name}.
9846
+ - All section titles, table headers, table cells, lesson names, learning objectives, pedagogical narratives, and cognitive analyses MUST be written in natural, fluent, academic ${name}.
9847
+ - Technical identifiers, code keywords (e.g. setup(), loop(), pinMode()), standard protocols (I2C, SPI, UART, GPIO), and component model numbers (ESP32, Arduino Uno) remain in standard technical notation, but all surrounding explanations MUST be in ${name}.
9848
+ - DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
9849
+ ${exampleLine}` : ""}`;
9850
+ }
9851
+ function matchesArtifactType(candidate, target) {
9852
+ const c = candidate.trim().toUpperCase();
9853
+ const t = target.trim().toUpperCase();
9854
+ if (c === t) return true;
9855
+ if (c.startsWith("LESSON") && t.startsWith("LESSON")) return true;
9856
+ if ((c === "CODE" || c === "CODE_LAB") && (t === "CODE" || t === "CODE_LAB")) return true;
9857
+ if ((c === "EXPOSITION" || c === "KNOWLEDGE_EXPOSITION") && (t === "EXPOSITION" || t === "KNOWLEDGE_EXPOSITION")) return true;
9858
+ if ((c === "WKS" || c === "WORKSHEET") && (t === "WKS" || t === "WORKSHEET")) return true;
9859
+ if ((c === "EXT" || c === "EXTENSION") && (t === "EXT" || t === "EXTENSION")) return true;
9860
+ return false;
9861
+ }
9862
+ var DEFAULT_VIETNAMESE_SECTION_HEADINGS = {
9863
+ LEARNER_PROFILE: {
9864
+ "Foundational Info": "Th\xF4ng tin N\u1EC1n t\u1EA3ng",
9865
+ "Entry Level": "Tr\xECnh \u0111\u1ED9 \u0110\u1EA7u v\xE0o",
9866
+ "Learning Context": "B\u1ED1i c\u1EA3nh H\u1ECDc t\u1EADp",
9867
+ "Client Objectives": "M\u1EE5c ti\xEAu Kh\xE1ch h\xE0ng & Chu\u1EA9n \u0111\u1EA7u ra"
9868
+ },
9869
+ PROJECT_BRIEF: {
9870
+ "Project Overview": "T\u1ED5ng quan D\u1EF1 \xE1n",
9871
+ "Audience & Duration": "\u0110\u1ED1i t\u01B0\u1EE3ng & Th\u1EDDi l\u01B0\u1EE3ng",
9872
+ "Learning Roadmap": "L\u1ED9 tr\xECnh H\u1ECDc t\u1EADp & C\u1ED9t m\u1ED1c",
9873
+ "Artifact Scope": "Ph\u1EA1m vi S\u1EA3n ph\u1EA9m H\u1ECDc t\u1EADp"
9874
+ },
9875
+ CURRICULUM_FRAMEWORK: {
9876
+ "Course Overview": "T\u1ED5ng quan Kh\xF3a h\u1ECDc",
9877
+ "Global Learning Objectives": "M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n",
9878
+ "Structural Hierarchy (Unit & Module)": "C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)",
9879
+ "Scope & Sequence (Detailed Roadmap)": "Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt"
9880
+ },
9881
+ CONTENT_STYLE_GUIDE: {
9882
+ "Voice & Tone": "Gi\u1ECDng v\u0103n & Phong c\xE1ch",
9883
+ "Standard Terminology (Glossary)": "Thu\u1EADt ng\u1EEF Chu\u1EA9n (B\u1EA3ng thu\u1EADt ng\u1EEF)",
9884
+ "Examples & Context Rules": "Quy t\u1EAFc V\xED d\u1EE5 & Ng\u1EEF c\u1EA3nh",
9885
+ "Lesson Content Standards": "Ti\xEAu chu\u1EA9n N\u1ED9i dung B\xE0i h\u1ECDc"
9886
+ },
9887
+ SECTION_LANGUAGE_CONTRACT: {
9888
+ "Metadata & Scope": "Th\xF4ng tin & Ph\u1EA1m vi H\u1EE3p \u0111\u1ED3ng",
9889
+ "Section Translation Matrix": "Ma tr\u1EADn Ti\xEAu \u0111\u1EC1 \u0110a ng\xF4n ng\u1EEF",
9890
+ "Validation Rules": "Quy t\u1EAFc X\xE1c th\u1EF1c & Kh\xF3a Chu\u1EA9n"
9891
+ },
9892
+ KNOWLEDGE_EXPOSITION: {
9893
+ "Session Scope": "Ph\u1EA1m vi Bu\u1ED5i h\u1ECDc",
9894
+ "Key Terms": "Thu\u1EADt ng\u1EEF Then ch\u1ED1t",
9895
+ "Concept Narratives": "Di\u1EC5n gi\u1EA3i Kh\xE1i ni\u1EC7m",
9896
+ "Worked Micro-Examples": "V\xED d\u1EE5 M\u1EABu Chi ti\u1EBFt",
9897
+ "Common Mistakes": "L\u1ED7i Th\u01B0\u1EDDng g\u1EB7p & Kh\u1EAFc ph\u1EE5c",
9898
+ "Self-Check Questions": "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra"
9899
+ },
9900
+ LESSON: {
9901
+ "A. Lesson Design Plan": "A. K\u1EBF ho\u1EA1ch Thi\u1EBFt k\u1EBF B\xE0i h\u1ECDc",
9902
+ "Learning Objectives & Evidence": "M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp & B\u1EB1ng ch\u1EE9ng N\u0103ng l\u1EF1c",
9903
+ "Activity Sequence": "Chu\u1ED7i Ho\u1EA1t \u0111\u1ED9ng D\u1EA1y & H\u1ECDc",
9904
+ "Resource Map": "B\u1EA3n \u0111\u1ED3 T\xE0i nguy\xEAn & H\u1ECDc li\u1EC7u",
9905
+ "Assessment Map": "B\u1EA3n \u0111\u1ED3 \u0110\xE1nh gi\xE1 N\u0103ng l\u1EF1c",
9906
+ "Artifact Contract": "H\u1EE3p \u0111\u1ED3ng S\u1EA3n ph\u1EA9m \u0110\u1EA7u ra",
9907
+ "B. Lesson Flow": "B. Ti\u1EBFn tr\xECnh Gi\u1EA3ng d\u1EA1y Chi ti\u1EBFt"
9908
+ },
9909
+ ACT: {
9910
+ "Computational Thinking Focus": "Tr\u1ECDng t\xE2m T\u01B0 duy M\xE1y t\xEDnh",
9911
+ "Materials & Setup": "V\u1EADt li\u1EC7u & Chu\u1EA9n b\u1ECB Thi\u1EBFt b\u1ECB",
9912
+ "Constraints & Safety": "R\xE0ng bu\u1ED9c & An to\xE0n Ph\xF2ng th\u1EF1c h\xE0nh",
9913
+ "Active Learning Workflow": "Quy tr\xECnh Ho\u1EA1t \u0111\u1ED9ng Tr\u1EA3i nghi\u1EC7m",
9914
+ "3-Tier Differentiation": "Ph\xE2n h\xF3a H\u1ECDc t\u1EADp 3 T\u1EA7ng",
9915
+ "Reflection": "T\u1ED5ng k\u1EBFt & \u0110\xFAc k\u1EBFt Tr\u1EA3i nghi\u1EC7m"
9916
+ },
9917
+ CODE: {
9918
+ "Technical Overview & Architecture Blueprint": "T\u1ED5ng quan K\u1EF9 thu\u1EADt & B\u1EA3n thi\u1EBFt k\u1EBF Ki\u1EBFn tr\xFAc",
9919
+ "Hardware Pinout & Wiring Configuration Matrix": "S\u01A1 \u0111\u1ED3 Ch\xE2n & Ma tr\u1EADn N\u1ED1i d\xE2y Ph\u1EA7n c\u1EE9ng",
9920
+ "Starter Code Sandbox": "M\xE3 ngu\u1ED3n Kh\u1EDFi \u0111\u1EA7u (Starter Code)",
9921
+ "Verified Reference Solution Code": "M\xE3 ngu\u1ED3n L\u1EDDi gi\u1EA3i Chu\u1EA9n (Reference Solution)",
9922
+ "Automated Test / Verification Script": "K\u1ECBch b\u1EA3n Ki\u1EC3m th\u1EED T\u1EF1 \u0111\u1ED9ng",
9923
+ "Common Syntax & Runtime Pitfalls Matrix": "Ma tr\u1EADn L\u1ED7i C\xFA ph\xE1p & Th\u1EDDi gian ch\u1EA1y Th\u01B0\u1EDDng g\u1EB7p"
9924
+ },
9925
+ CODE_LAB: {
9926
+ "Technical Overview & Architecture Blueprint": "T\u1ED5ng quan K\u1EF9 thu\u1EADt & B\u1EA3n thi\u1EBFt k\u1EBF Ki\u1EBFn tr\xFAc",
9927
+ "Hardware Pinout & Wiring Configuration Matrix": "S\u01A1 \u0111\u1ED3 Ch\xE2n & Ma tr\u1EADn N\u1ED1i d\xE2y Ph\u1EA7n c\u1EE9ng",
9928
+ "Starter Code Sandbox": "M\xE3 ngu\u1ED3n Kh\u1EDFi \u0111\u1EA7u (Starter Code)",
9929
+ "Verified Reference Solution Code": "M\xE3 ngu\u1ED3n L\u1EDDi gi\u1EA3i Chu\u1EA9n (Reference Solution)",
9930
+ "Automated Test / Verification Script": "K\u1ECBch b\u1EA3n Ki\u1EC3m th\u1EED T\u1EF1 \u0111\u1ED9ng",
9931
+ "Common Syntax & Runtime Pitfalls Matrix": "Ma tr\u1EADn L\u1ED7i C\xFA ph\xE1p & Th\u1EDDi gian ch\u1EA1y Th\u01B0\u1EDDng g\u1EB7p"
9932
+ },
9933
+ QUIZ: {
9934
+ "Assessment Objectives": "M\u1EE5c ti\xEAu \u0110\xE1nh gi\xE1",
9935
+ "Multiple-Choice Question Bank": "Ng\xE2n h\xE0ng C\xE2u h\u1ECFi Tr\u1EAFc nghi\u1EC7m",
9936
+ "Code Analysis / Debugging Question": "C\xE2u h\u1ECFi Ph\xE2n t\xEDch M\xE3 ngu\u1ED3n & S\u1EEDa l\u1ED7i",
9937
+ "Competency Rubric": "Thang \u0111o \u0110\xE1nh gi\xE1 N\u0103ng l\u1EF1c (Rubric)"
9938
+ },
9939
+ GUIDE: {
9940
+ "Objectives & Preparation": "M\u1EE5c ti\xEAu & Chu\u1EA9n b\u1ECB Gi\u1EA3ng d\u1EA1y",
9941
+ "Preparation Checklist & Workstation Setup": "Danh m\u1EE5c Ki\u1EC3m tra Chu\u1EA9n b\u1ECB & Tr\u1EA1m Th\u1EF1c h\xE0nh",
9942
+ "Facilitation Script & Timeline": "K\u1ECBch b\u1EA3n \u0110i\u1EC1u ph\u1ED1i & Khung Th\u1EDDi gian",
9943
+ "Common Misconceptions & Diagnostic Remediation Matrix": "Quan ni\u1EC7m Sai l\u1EA7m Th\u01B0\u1EDDng g\u1EB7p & Ma tr\u1EADn Kh\u1EAFc ph\u1EE5c",
9944
+ "Differentiated Support Strategies": "Chi\u1EBFn l\u01B0\u1EE3c H\u1ED7 tr\u1EE3 Ph\xE2n h\xF3a"
9945
+ },
9946
+ HANDOUT: {
9947
+ "Core Concepts": "Kh\xE1i ni\u1EC7m C\u1ED1t l\xF5i",
9948
+ "Core Syntax & Mechanism Cheat Sheet": "B\u1EA3ng Tra c\u1EE9u C\xFA ph\xE1p & C\u01A1 ch\u1EBF Tr\u1ECDng t\xE2m",
9949
+ "Visual Mental Model / Architecture Diagram": "M\xF4 h\xECnh T\u01B0 duy Tr\u1EF1c quan / S\u01A1 \u0111\u1ED3 Ki\u1EBFn tr\xFAc",
9950
+ "Step-by-Step Practical Quick-Start Guide": "H\u01B0\u1EDBng d\u1EABn Th\u1EF1c h\xE0nh T\u1EEBng b\u01B0\u1EDBc",
9951
+ "Self-Check Diagnostic Checklist": "B\u1EA3ng Ki\u1EC3m tra T\u1EF1 ch\u1EA9n \u0111o\xE1n"
9952
+ },
9953
+ WKS: {
9954
+ "Part 1: Knowledge Check": "Ph\u1EA7n 1: Ki\u1EC3m tra Ki\u1EBFn th\u1EE9c",
9955
+ "Part 2: Concept Tracing & Diagram Fill-in": "Ph\u1EA7n 2: L\u1EA7n v\u1EBFt Kh\xE1i ni\u1EC7m & \u0110i\u1EC1n S\u01A1 \u0111\u1ED3",
9956
+ "Part 3: Code Analysis & Bug Hunting Challenge": "Ph\u1EA7n 3: Ph\xE2n t\xEDch M\xE3 & Th\u1EED th\xE1ch S\u0103n l\u1ED7i",
9957
+ "Part 4: Synthesis & Problem-Solving Application": "Ph\u1EA7n 4: T\u1ED5ng h\u1EE3p & \u1EE8ng d\u1EE5ng Gi\u1EA3i quy\u1EBFt V\u1EA5n \u0111\u1EC1",
9958
+ "Part 5: Self-Reflection & Learning Log": "Ph\u1EA7n 5: T\u1EF1 suy ng\u1EABm & Nh\u1EADt k\xFD H\u1ECDc t\u1EADp"
9959
+ },
9960
+ EXT: {
9961
+ "For: Advanced Students / Extra Time": "D\xE0nh cho: H\u1ECDc sinh N\xE2ng cao / Ho\xE0n th\xE0nh S\u1EDBm",
9962
+ "Challenge 1: Mission Briefing & Real-World High-Stakes Scenario": "Th\u1EED th\xE1ch 1: B\u1ED1i c\u1EA3nh Nhi\u1EC7m v\u1EE5 Th\u1EF1c t\u1EBF & K\u1ECBch b\u1EA3n \u1EE8ng d\u1EE5ng",
9963
+ "Challenge 2: Advanced Architectural Constraints & Edge Cases": "Th\u1EED th\xE1ch 2: R\xE0ng bu\u1ED9c Ki\u1EBFn tr\xFAc N\xE2ng cao & Tr\u01B0\u1EDDng h\u1EE3p Bi\xEAn",
9964
+ "Challenge 3: Extension Milestones": "Th\u1EED th\xE1ch 3: C\xE1c M\u1ED1c Nhi\u1EC7m v\u1EE5 N\xE2ng cao",
9965
+ "Metacognitive Deep Dive & Engineering Trade-offs": "Suy ng\u1EABm Si\xEAu nh\u1EADn th\u1EE9c & \u0110\xE1nh \u0111\u1ED5i K\u1EF9 thu\u1EADt"
9966
+ },
9967
+ SLIDE: {
9968
+ "Title & Agenda": "Ti\xEAu \u0111\u1EC1 & M\u1EE5c l\u1EE5c",
9969
+ "Core Concepts": "Kh\xE1i ni\u1EC7m C\u1ED1t l\xF5i",
9970
+ "Guided Demonstration": "H\u01B0\u1EDBng d\u1EABn Th\u1EF1c h\xE0nh & Th\u1ECB ph\u1EA1m",
9971
+ "Hands-on Mission": "Nhi\u1EC7m v\u1EE5 Th\u1EF1c h\xE0nh Tr\u1EA3i nghi\u1EC7m",
9972
+ "Summary & Next Steps": "T\u1ED5ng k\u1EBFt & \u0110\u1ECBnh h\u01B0\u1EDBng Ti\u1EBFp theo"
9973
+ }
9974
+ };
9975
+ function extractSectionHeadingsFromSLC(slcInput, artifactType, targetLanguage = "vi") {
9976
+ const upperType = artifactType.trim().toUpperCase();
9977
+ const getFallback = () => {
9978
+ if (resolveTargetLanguageCode(targetLanguage) === "vi") {
9979
+ for (const [key, mapping] of Object.entries(DEFAULT_VIETNAMESE_SECTION_HEADINGS)) {
9980
+ if (matchesArtifactType(key, upperType)) {
9981
+ return mapping;
9982
+ }
9983
+ }
9984
+ }
9985
+ return {};
9986
+ };
9987
+ if (!slcInput) return getFallback();
9988
+ if (typeof slcInput === "object") {
9989
+ if (slcInput[upperType] && Object.keys(slcInput[upperType]).length > 0) return slcInput[upperType];
9990
+ for (const [key, mapping] of Object.entries(slcInput)) {
9991
+ if (matchesArtifactType(key, upperType) && Object.keys(mapping).length > 0) {
9992
+ return mapping;
9993
+ }
9994
+ }
9995
+ return getFallback();
9996
+ }
9997
+ const slcMarkdown = slcInput;
9998
+ const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
9999
+ for (const block of blocks) {
10000
+ const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
10001
+ if (!headerMatch) continue;
10002
+ const blockType = headerMatch[1].trim().toUpperCase();
10003
+ if (matchesArtifactType(blockType, upperType)) {
10004
+ const mapping = {};
10005
+ const lines = block.split("\n");
10006
+ for (const line of lines) {
10007
+ const trimmed = line.trim();
10008
+ if (trimmed.startsWith("|") && !trimmed.includes("---")) {
10009
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
10010
+ if (cells.length >= 2 && !cells[0].toLowerCase().includes("canonical") && !cells[1].toLowerCase().includes("localized")) {
10011
+ mapping[cells[0]] = cells[1];
10012
+ }
10013
+ } else {
10014
+ const bullet = line.match(/^[-*]\s+([^:]+):\s+(.+)$/);
10015
+ if (bullet) {
10016
+ mapping[bullet[1].trim()] = bullet[2].trim();
10017
+ }
10018
+ }
10019
+ }
10020
+ if (Object.keys(mapping).length > 0) return mapping;
10021
+ }
10022
+ }
10023
+ return getFallback();
10024
+ }
10025
+ function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
10026
+ const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType, targetLanguage);
10027
+ if (Object.keys(headings).length === 0) {
10028
+ return "";
10029
+ }
10030
+ const lines = Object.entries(headings).map(
10031
+ ([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
10032
+ );
10033
+ return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
10034
+ - The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
10035
+ - In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
10036
+ ${lines.join("\n")}
10037
+ - For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
10038
+ }
10039
+
9822
10040
  // src/services/contextBuilder.ts
9823
10041
  var DEFAULT_LESSON_PRIORITIES = [
9824
10042
  "A. Lesson Design Plan",
@@ -10108,7 +10326,8 @@ var DEPTH_RULES = {
10108
10326
  cio: "Write WHAT + WHY + HOW: include the mechanism, step by step, still technology-independent.",
10109
10327
  sio: "Write WHAT + WHY + HOW + SPECIFIC: include concrete implementations and how the real project keywords are used."
10110
10328
  };
10111
- function buildSystemPrompt() {
10329
+ function buildSystemPrompt(targetLanguage = "vi") {
10330
+ const langDirective = buildLanguageDirective(targetLanguage);
10112
10331
  return [
10113
10332
  "You write KNOWLEDGE_EXPOSITION: self-study reading material for a student in a teacherless program.",
10114
10333
  "Rules:",
@@ -10117,12 +10336,22 @@ function buildSystemPrompt() {
10117
10336
  "3. Respect each concept depth target (ULO/CIO/SIO) \u2014 do not overshoot.",
10118
10337
  "4. Student-facing only: no teacher instructions, no classroom management text.",
10119
10338
  "5. Output the full markdown document following the provided section skeleton exactly (headers and placeholders preserved).",
10120
- "6. English."
10339
+ langDirective
10121
10340
  ].join("\n");
10122
10341
  }
10123
- function buildUserPrompt(session, plan, glossary) {
10342
+ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown) {
10124
10343
  const depthLines = session.depth_assignments.map((d) => "- " + d.node_id + " [" + d.depth.toUpperCase() + "]: " + DEPTH_RULES[d.depth]).join("\n");
10125
10344
  const termLines = glossary.map((g) => "- " + g.term + (g.definition ? " \u2014 " + g.definition : "") + (g.example ? " (example: " + g.example + ")" : "")).join("\n");
10345
+ const langCode = resolveTargetLanguageCode(targetLanguage);
10346
+ const headings = extractSectionHeadingsFromSLC(slcMarkdown, "KNOWLEDGE_EXPOSITION");
10347
+ const hScope = headings["Session Scope"] || (langCode === "vi" ? "Ph\u1EA1m vi Bu\u1ED5i h\u1ECDc" : "Session Scope");
10348
+ const hTerms = headings["Key Terms"] || (langCode === "vi" ? "Thu\u1EADt ng\u1EEF Then ch\u1ED1t" : "Key Terms");
10349
+ const hNarratives = headings["Concept Narratives"] || (langCode === "vi" ? "Di\u1EC5n gi\u1EA3i Kh\xE1i ni\u1EC7m" : "Concept Narratives");
10350
+ const hExamples = headings["Worked Micro-Examples"] || (langCode === "vi" ? "V\xED d\u1EE5 M\u1EABu Chi ti\u1EBFt" : "Worked Micro-Examples");
10351
+ const hMistakes = headings["Common Mistakes"] || (langCode === "vi" ? "L\u1ED7i Th\u01B0\u1EDDng g\u1EB7p & Kh\u1EAFc ph\u1EE5c" : "Common Mistakes");
10352
+ const hQuestions = headings["Self-Check Questions"] || (langCode === "vi" ? "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra" : "Self-Check Questions");
10353
+ const skeleton = `## ${hScope} / ## ${hTerms} / ## ${hNarratives} / ## ${hExamples} / ## ${hMistakes} / ## ${hQuestions}`;
10354
+ const headingDirective = buildHeadingDirective("KNOWLEDGE_EXPOSITION", slcMarkdown, targetLanguage);
10126
10355
  return [
10127
10356
  "Session: " + session.id + " \u2014 " + session.title,
10128
10357
  "Objective: " + session.prose_objective,
@@ -10137,9 +10366,10 @@ function buildUserPrompt(session, plan, glossary) {
10137
10366
  termLines || "(none provided \u2014 write definitions and mark them for glossary sync)",
10138
10367
  "",
10139
10368
  "Section skeleton to fill (replace ONLY the {{placeholders}}):",
10140
- "## Session Scope / ## Key Terms / ## Concept Narratives / ## Worked Micro-Examples / ## Common Mistakes / ## Self-Check Questions",
10369
+ skeleton,
10370
+ headingDirective ? "\n" + headingDirective : "",
10141
10371
  "Plan hash: " + plan.plan_hash
10142
- ].join("\n");
10372
+ ].filter(Boolean).join("\n");
10143
10373
  }
10144
10374
  async function ensureKnowledgeExposition(options) {
10145
10375
  const { projectId, lessonCode, plan, glossary, llmFn, storage } = options;
@@ -10155,11 +10385,18 @@ async function ensureKnowledgeExposition(options) {
10155
10385
  if (plan.approval.plan_hash !== plan.plan_hash) {
10156
10386
  throw new ExpositionApprovalError("Approval hash " + plan.approval.plan_hash + " does not match current plan hash " + plan.plan_hash + " \u2014 re-approve after plan changes");
10157
10387
  }
10158
- const content = (await llmFn(buildSystemPrompt(), buildUserPrompt(session, plan, glossary))).trim();
10388
+ const targetLanguage = options.targetLanguage || plan.translation?.target_language || "vi";
10389
+ const content = (await llmFn(
10390
+ buildSystemPrompt(targetLanguage),
10391
+ buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown)
10392
+ )).trim();
10159
10393
  if (content.length < 200) {
10160
10394
  throw new Error("EXPOSITION too short for " + lessonCode + " (" + content.length + " chars) \u2014 refusing to save");
10161
10395
  }
10162
- if (!content.includes("## Key Terms") || !content.includes("## Concept Narratives") || !content.includes("## Self-Check")) {
10396
+ const hasKeyTerms = content.includes("Key Terms") || content.includes("Thu\u1EADt ng\u1EEF");
10397
+ const hasNarratives = content.includes("Concept Narratives") || content.includes("Kh\xE1i ni\u1EC7m") || content.includes("Di\u1EC5n gi\u1EA3i");
10398
+ const hasSelfCheck = content.includes("Self-Check") || content.includes("T\u1EF1 ki\u1EC3m tra") || content.includes("C\xE2u h\u1ECFi");
10399
+ if (!hasKeyTerms || !hasNarratives || !hasSelfCheck) {
10163
10400
  throw new Error("EXPOSITION missing required sections for " + lessonCode + " \u2014 refusing to save");
10164
10401
  }
10165
10402
  const gateMode = gateModeFor(options.gates ?? {}, "KNOWLEDGE_EXPOSITION");
@@ -10221,16 +10458,20 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
10221
10458
  const scopedTerms = new Set(plan.glossary_scope.find((g) => g.session_id === lessonCode)?.terms ?? []);
10222
10459
  glossary = glossary.filter((g) => scopedTerms.has(g.term));
10223
10460
  const { runCurriculumAIInference: runCurriculumAIInference2 } = await Promise.resolve().then(() => (init_streamRunner(), streamRunner_exports));
10461
+ const targetLang = options.targetLanguage || plan.translation?.target_language || "vi";
10224
10462
  const result = await ensureKnowledgeExposition({
10225
10463
  projectId,
10226
10464
  lessonCode,
10227
10465
  plan,
10228
10466
  glossary,
10467
+ targetLanguage: targetLang,
10468
+ slcMarkdown: options.slcMarkdown,
10229
10469
  llmFn: async (systemPrompt, userPrompt) => {
10470
+ const runnerOpts = options.customInference ? { customInference: options.customInference } : {};
10230
10471
  const out = await runCurriculumAIInference2(
10231
10472
  [{ role: "user", content: userPrompt + "\n\n[SYSTEM RULES]:\n" + systemPrompt }],
10232
- "You are generating KNOWLEDGE_EXPOSITION - canonical self-study knowledge for one session. Output the complete markdown document only.",
10233
- {}
10473
+ `You are generating KNOWLEDGE_EXPOSITION - canonical self-study knowledge for one session in ${targetLang === "vi" ? "VIETNAMESE" : targetLang}. Output the complete markdown document only.`,
10474
+ runnerOpts
10234
10475
  );
10235
10476
  return out;
10236
10477
  },
@@ -23978,90 +24219,6 @@ function extractStandardRefs(text) {
23978
24219
  return Array.from(new Set(matches));
23979
24220
  }
23980
24221
 
23981
- // src/services/languageDirective.ts
23982
- function resolveTargetLanguageCode(language) {
23983
- const raw = String(language || "vi").trim().toLowerCase();
23984
- if (raw.startsWith("vi")) return "vi";
23985
- if (raw.startsWith("en")) return "en";
23986
- return raw || "vi";
23987
- }
23988
- function targetLanguageDisplayName(code) {
23989
- const map = {
23990
- vi: "Vietnamese (Ti\u1EBFng Vi\u1EC7t)",
23991
- en: "English (US)"
23992
- };
23993
- return map[code] || code;
23994
- }
23995
- function buildLanguageDirective(targetLanguage) {
23996
- const code = resolveTargetLanguageCode(targetLanguage);
23997
- const name = targetLanguageDisplayName(code);
23998
- const perLanguageExamples = {
23999
- vi: `- V\xED d\u1EE5 ti\xEAu \u0111\u1EC1 b\xE0i h\u1ECDc \u0111\xFAng chu\u1EA9n: "B\xE0i 1: Kh\xE1m ph\xE1 m\u1EA1ch \u0111i\u1EC7n c\u01A1 b\u1EA3n", "B\xE0i 2: \u0110i\u1EC1u khi\u1EC3n \u0111\xE8n LED nh\u1EA5p nh\xE1y" \u2014 KH\xD4NG d\xF9ng ti\xEAu \u0111\u1EC1 ti\u1EBFng Anh.`,
24000
- en: `- Example compliant lesson title: "Lesson 1: Exploring Basic Circuits" \u2014 natural, idiomatic English throughout.`
24001
- };
24002
- const exampleLine = perLanguageExamples[code] || "";
24003
- return `MANDATORY LANGUAGE DIRECTIVE (TARGET OUTPUT LANGUAGE: ${name}):
24004
- - The TARGET OUTPUT LANGUAGE for this course is ${name}. You MUST author the ENTIRE document in ${name}.
24005
- - All section titles, table headers, table cells, lesson names, learning objectives, pedagogical narratives, and cognitive analyses MUST be written in natural, fluent, academic ${name}.
24006
- - Technical identifiers, code keywords (e.g. setup(), loop(), pinMode()), standard protocols (I2C, SPI, UART, GPIO), and component model numbers (ESP32, Arduino Uno) remain in standard technical notation, but all surrounding explanations MUST be in ${name}.
24007
- - DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
24008
- ${exampleLine}` : ""}`;
24009
- }
24010
- function extractSectionHeadingsFromSLC(slcInput, artifactType) {
24011
- if (!slcInput) return {};
24012
- const upperType = artifactType.trim().toUpperCase();
24013
- if (typeof slcInput === "object") {
24014
- if (slcInput[upperType]) return slcInput[upperType];
24015
- for (const [key, mapping] of Object.entries(slcInput)) {
24016
- if (key.toUpperCase() === upperType || upperType.startsWith("LESSON") && key.toUpperCase().startsWith("LESSON")) {
24017
- return mapping;
24018
- }
24019
- }
24020
- return {};
24021
- }
24022
- const slcMarkdown = slcInput;
24023
- const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
24024
- for (const block of blocks) {
24025
- const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
24026
- if (!headerMatch) continue;
24027
- const blockType = headerMatch[1].trim().toUpperCase();
24028
- if (blockType === upperType || upperType.startsWith("LESSON") && blockType.startsWith("LESSON")) {
24029
- const mapping = {};
24030
- const lines = block.split("\n");
24031
- for (const line of lines) {
24032
- const trimmed = line.trim();
24033
- if (trimmed.startsWith("|") && !trimmed.includes("---")) {
24034
- const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
24035
- if (cells.length >= 2 && !cells[0].toLowerCase().includes("canonical") && !cells[1].toLowerCase().includes("localized")) {
24036
- mapping[cells[0]] = cells[1];
24037
- }
24038
- } else {
24039
- const bullet = line.match(/^[-*]\s+([^:]+):\s+(.+)$/);
24040
- if (bullet) {
24041
- mapping[bullet[1].trim()] = bullet[2].trim();
24042
- }
24043
- }
24044
- }
24045
- if (Object.keys(mapping).length > 0) return mapping;
24046
- }
24047
- }
24048
- return {};
24049
- }
24050
- function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
24051
- const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType);
24052
- if (Object.keys(headings).length === 0) {
24053
- return "";
24054
- }
24055
- const lines = Object.entries(headings).map(
24056
- ([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
24057
- );
24058
- return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
24059
- - The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
24060
- - In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
24061
- ${lines.join("\n")}
24062
- - For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
24063
- }
24064
-
24065
24222
  // src/services/lessonProductionService.ts
24066
24223
  var __filename2 = typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" && typeof url.fileURLToPath === "function" ? url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) : "";
24067
24224
  var _resolvedDir = __filename2 ? path3__default.default.dirname(__filename2) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
@@ -24475,7 +24632,10 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
24475
24632
  try {
24476
24633
  const exposition = await ensureExpositionForLesson(storage, projectId, lessonCode, {
24477
24634
  gates: resolveGateSettings(options.gateSettings),
24478
- force: options.force
24635
+ force: options.force,
24636
+ targetLanguage: targetLang,
24637
+ slcMarkdown,
24638
+ customInference: options.customInference
24479
24639
  });
24480
24640
  if (exposition) {
24481
24641
  const expoExcerpt = buildExpositionExcerpt(exposition.rawContent || exposition.context, {
@@ -25525,6 +25685,22 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
25525
25685
  if (artifactScope.includes("EXT") && (!await storage.readArtifact(projectId, extRelPath) || force)) {
25526
25686
  onProgress?.("@activity", `Authoring Advanced Extension Challenge: \`EXT_${lessonCode}.md\`...`, { artifactType: "EXT" });
25527
25687
  const extDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
25688
+ const isVi = targetLang === "vi";
25689
+ const extHeadings = isVi ? {
25690
+ main: "# NHI\u1EC6M V\u1EE4 N\xC2NG CAO: [TI\xCAU \u0110\u1EC0 B\xC0I H\u1ECCC]",
25691
+ sub: "## D\xE0nh cho: H\u1ECDc sinh N\xE2ng cao / Ho\xE0n th\xE0nh S\u1EDBm",
25692
+ c1: "### Th\u1EED th\xE1ch 1: [T\xEAn th\u1EED th\xE1ch] (\u0110\u1ED9 kh\xF3: \u2B50\u2B50\u2B50) \u2014 B\u1ED1i c\u1EA3nh Nhi\u1EC7m v\u1EE5 Th\u1EF1c t\u1EBF & K\u1ECBch b\u1EA3n \u1EE8ng d\u1EE5ng",
25693
+ c2: "### Th\u1EED th\xE1ch 2: [T\xEAn th\u1EED th\xE1ch] (\u0110\u1ED9 kh\xF3: \u2B50\u2B50\u2B50\u2B50) \u2014 R\xE0ng bu\u1ED9c Ki\u1EBFn tr\xFAc N\xE2ng cao & Tr\u01B0\u1EDDng h\u1EE3p Bi\xEAn",
25694
+ c3: "### Th\u1EED th\xE1ch 3: [T\xEAn th\u1EED th\xE1ch] (\u0110\u1ED9 kh\xF3: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 C\xE1c M\u1ED1c Nhi\u1EC7m v\u1EE5 N\xE2ng cao (C\u1EA5p 1: Kh\u1EDFi \u0111\u1ED9ng \u2192 C\u1EA5p 2: N\xE2ng cao \u2192 C\u1EA5p 3: Chuy\xEAn gia)",
25695
+ meta: "## [REQUIRED] Suy ng\u1EABm Si\xEAu nh\u1EADn th\u1EE9c & \u0110\xE1nh \u0111\u1ED5i K\u1EF9 thu\u1EADt"
25696
+ } : {
25697
+ main: "# EXTENSION TASKS: [LESSON TITLE]",
25698
+ sub: "## For: Advanced Students / Extra Time",
25699
+ c1: "### Challenge 1: [Name] (Difficulty: \u2B50\u2B50\u2B50) \u2014 Mission Briefing & Real-World High-Stakes Scenario",
25700
+ c2: "### Challenge 2: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50) \u2014 Advanced Architectural Constraints & Edge Cases",
25701
+ c3: "### Challenge 3: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 Extension Milestones (Level 1: Ninja \u2192 Level 2: Guru \u2192 Level 3: Master)",
25702
+ meta: "## [REQUIRED] Metacognitive Deep Dive & Engineering Trade-offs"
25703
+ };
25528
25704
  const extPrompt = `You are @activity (Lead STEM Specialist & Differentiated Extension Designer).
25529
25705
  Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, author the advanced extension challenge: \`EXT_${lessonCode}.md\`.
25530
25706
 
@@ -25540,16 +25716,21 @@ deliverable: "P3-T8"
25540
25716
  version: "v1.0"
25541
25717
  date: "${extDateStr}"
25542
25718
  template_contract: "artifact-template-v1"
25719
+ template_required_sections:
25720
+ - "Challenge 1: Mission Briefing & Real-World High-Stakes Scenario"
25721
+ - "Challenge 2: Advanced Architectural Constraints & Edge Cases"
25722
+ - "Challenge 3: Extension Milestones"
25723
+ - "Metacognitive Deep Dive & Engineering Trade-offs"
25543
25724
  ---
25544
25725
 
25545
- # EXTENSION TASKS: [LESSON TITLE]
25546
- ## For: Advanced Students / Extra Time
25726
+ ${extHeadings.main}
25727
+ ${extHeadings.sub}
25547
25728
 
25548
25729
  2. Mandatory sections (matching EXT_template.md):
25549
- - ### Challenge 1: [Name] (Difficulty: \u2B50\u2B50\u2B50) \u2014 Mission Briefing & Real-World High-Stakes Scenario
25550
- - ### Challenge 2: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50) \u2014 Advanced Architectural Constraints & Edge Cases
25551
- - ### Challenge 3: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 Extension Milestones (Level 1: Ninja \u2192 Level 2: Guru \u2192 Level 3: Master)
25552
- - ## [REQUIRED] Metacognitive Deep Dive & Engineering Trade-offs
25730
+ - ${extHeadings.c1}
25731
+ - ${extHeadings.c2}
25732
+ - ${extHeadings.c3}
25733
+ - ${extHeadings.meta}
25553
25734
  3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
25554
25735
 
25555
25736
  ${languageDirective}
@@ -30205,6 +30386,7 @@ exports.DEFAULT_KX_PRIORITIES = DEFAULT_KX_PRIORITIES;
30205
30386
  exports.DEFAULT_LESSON_PRIORITIES = DEFAULT_LESSON_PRIORITIES;
30206
30387
  exports.DEFAULT_STREAM_IDLE_MS = DEFAULT_STREAM_IDLE_MS;
30207
30388
  exports.DEFAULT_STREAM_TOTAL_MS = DEFAULT_STREAM_TOTAL_MS;
30389
+ exports.DEFAULT_VIETNAMESE_SECTION_HEADINGS = DEFAULT_VIETNAMESE_SECTION_HEADINGS;
30208
30390
  exports.DependencyEdgeSchema = DependencyEdgeSchema;
30209
30391
  exports.DepthAssignmentSchema = DepthAssignmentSchema;
30210
30392
  exports.DepthLevelSchema = DepthLevelSchema;