@thanh01.pmt/curriculum-kit 1.0.21 → 1.1.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
@@ -2432,9 +2432,20 @@ var SessionPlanSchema = zod.z.object({
2432
2432
  differentiation: zod.z.object({ bronze: zod.z.string(), silver: zod.z.string(), gold: zod.z.string() })
2433
2433
  });
2434
2434
  var TranslationPolicySchema = zod.z.object({
2435
- policy: zod.z.enum(["after_artifact", "end_of_unit", "at_publish"]).default("at_publish"),
2435
+ policy: zod.z.enum(["after_artifact", "end_of_unit", "at_publish", "native"]).default("native"),
2436
2436
  target_language: zod.z.string().nullable().default(null)
2437
- });
2437
+ }).refine(
2438
+ (data) => {
2439
+ if (data.policy === "native") {
2440
+ return typeof data.target_language === "string" && data.target_language.trim().length > 0;
2441
+ }
2442
+ return true;
2443
+ },
2444
+ {
2445
+ message: "When translation policy is 'native', target_language must be a non-empty string",
2446
+ path: ["target_language"]
2447
+ }
2448
+ );
2438
2449
  var CoverageReportSchema = zod.z.object({
2439
2450
  assigned_node_ids: zod.z.array(zod.z.string()),
2440
2451
  unassigned_node_ids: zod.z.array(zod.z.string()),
@@ -8637,12 +8648,15 @@ var SupabaseCurriculumAdapter = class {
8637
8648
  } catch {
8638
8649
  }
8639
8650
  }
8651
+ if (relPathOrIdentifier.includes(".md") || relPathOrIdentifier.includes(".json") || relPathOrIdentifier.includes("/")) {
8652
+ return null;
8653
+ }
8640
8654
  const lessonMatch = relPathOrIdentifier.match(/U\d+_M\d+_L\d+/i);
8641
8655
  if (lessonMatch) {
8642
8656
  const lessonId = lessonMatch[0].toUpperCase();
8643
8657
  const unitCode = lessonId.split("_")[0] || "U01";
8644
8658
  const lower = relPathOrIdentifier.toLowerCase();
8645
- let artifactPrefix = "LESSON";
8659
+ let artifactPrefix = null;
8646
8660
  if (lower.includes("act") || lower.includes("lab") || lower.includes("th\u1EF1c h\xE0nh")) {
8647
8661
  artifactPrefix = "ACT";
8648
8662
  } else if (lower.includes("quiz") || lower.includes("tr\u1EAFc nghi\u1EC7m") || lower.includes("\u0111\xE1nh gi\xE1")) {
@@ -8651,8 +8665,31 @@ var SupabaseCurriculumAdapter = class {
8651
8665
  artifactPrefix = "SLIDE";
8652
8666
  } else if (lower.includes("guide") || lower.includes("h\u01B0\u1EDBng d\u1EABn")) {
8653
8667
  artifactPrefix = "GUIDE";
8668
+ } else if (lower.includes("wks") || lower.includes("worksheet") || lower.includes("phi\u1EBFu b\xE0i t\u1EADp")) {
8669
+ artifactPrefix = "WKS";
8670
+ } else if (lower.includes("handout") || lower.includes("t\xE0i li\u1EC7u")) {
8671
+ artifactPrefix = "HANDOUT";
8672
+ } else if (lower.includes("code")) {
8673
+ artifactPrefix = "CODE";
8674
+ } else if (lower.includes("ext") || lower.includes("extension") || lower.includes("m\u1EDF r\u1ED9ng")) {
8675
+ artifactPrefix = "EXT";
8676
+ } else if (lower.includes("exp") || lower.includes("exposition") || lower.includes("t\u1EF1 h\u1ECDc") || lower.includes("ki\u1EBFn th\u1EE9c n\u1EC1n")) {
8677
+ artifactPrefix = "EXPOSITION";
8678
+ } else if (lower.includes("rubric")) {
8679
+ artifactPrefix = "RUBRIC";
8680
+ } else if (lower.includes("bom")) {
8681
+ artifactPrefix = "BOM";
8682
+ } else if (lower.includes("lesson") || lower.includes("gi\xE1o \xE1n") || lower.includes("5e")) {
8683
+ artifactPrefix = "LESSON";
8684
+ }
8685
+ if (!artifactPrefix) {
8686
+ return null;
8654
8687
  }
8655
- const candidatePaths = [
8688
+ const candidatePaths = artifactPrefix === "EXPOSITION" ? [
8689
+ `${projectId}/_sot/expositions/${lessonId}.md`,
8690
+ `${projectId}/_content/${unitCode}/EXPOSITION_${lessonId}.md`,
8691
+ `${projectId}/lessons/${lessonId}/EXPOSITION_${lessonId}.md`
8692
+ ] : [
8656
8693
  `${projectId}/_content/${unitCode}/${artifactPrefix}_${lessonId}.md`,
8657
8694
  `${projectId}/lessons/${lessonId}/${artifactPrefix}_${lessonId}.md`,
8658
8695
  `${projectId}/${artifactPrefix}_${lessonId}.md`
@@ -9829,6 +9866,7 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
9829
9866
  });
9830
9867
  return { relPath: result.relPath, context: buildExpositionContext(result.content), reused: result.reused };
9831
9868
  }
9869
+ init_errors();
9832
9870
  var asArr = (v) => Array.isArray(v) ? v : [];
9833
9871
  var asStr = (v, dflt = "") => typeof v === "string" ? v : dflt;
9834
9872
  var asNum = (v, dflt) => typeof v === "number" && Number.isFinite(v) ? v : dflt;
@@ -10471,7 +10509,15 @@ function extractSessionSlice(plan, lessonCode) {
10471
10509
  }
10472
10510
  function buildSessionSliceContext(plan, lessonCode) {
10473
10511
  const s = plan.sessions.find((x) => x.id === lessonCode);
10474
- if (!s) return "";
10512
+ if (!s) {
10513
+ throw new exports.CurriculumError({
10514
+ errorCode: "ERR_LESSON_NOT_IN_SOT",
10515
+ message: `Session "${lessonCode}" not found in CURRICULUM_PLAN.json (${plan.plan_hash})`,
10516
+ suggestedAction: "\u0110\u1ED3ng b\u1ED9 CURRICULUM_PLAN r\u1ED3i ch\u1EA1y l\u1EA1i",
10517
+ retryable: false,
10518
+ lessonId: lessonCode
10519
+ });
10520
+ }
10475
10521
  const lines = [];
10476
10522
  lines.push("[SESSION SLICE (from CURRICULUM_PLAN " + plan.plan_hash + ")]");
10477
10523
  lines.push("- Objective: " + s.prose_objective);
@@ -11038,6 +11084,80 @@ function resolveStandardsPacks(packIds) {
11038
11084
  return packs;
11039
11085
  }
11040
11086
 
11087
+ // src/services/languageDirective.ts
11088
+ function resolveTargetLanguageCode(language) {
11089
+ const raw = String(language || "vi").trim().toLowerCase();
11090
+ if (raw.startsWith("vi")) return "vi";
11091
+ if (raw.startsWith("en")) return "en";
11092
+ return raw || "vi";
11093
+ }
11094
+ function targetLanguageDisplayName(code) {
11095
+ const map = {
11096
+ vi: "Vietnamese (Ti\u1EBFng Vi\u1EC7t)",
11097
+ en: "English (US)"
11098
+ };
11099
+ return map[code] || code;
11100
+ }
11101
+ function buildLanguageDirective(targetLanguage) {
11102
+ const code = resolveTargetLanguageCode(targetLanguage);
11103
+ const name = targetLanguageDisplayName(code);
11104
+ const perLanguageExamples = {
11105
+ 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.`,
11106
+ en: `- Example compliant lesson title: "Lesson 1: Exploring Basic Circuits" \u2014 natural, idiomatic English throughout.`
11107
+ };
11108
+ const exampleLine = perLanguageExamples[code] || "";
11109
+ return `MANDATORY LANGUAGE DIRECTIVE (TARGET OUTPUT LANGUAGE: ${name}):
11110
+ - The TARGET OUTPUT LANGUAGE for this course is ${name}. You MUST author the ENTIRE document in ${name}.
11111
+ - All section titles, table headers, table cells, lesson names, learning objectives, pedagogical narratives, and cognitive analyses MUST be written in natural, fluent, academic ${name}.
11112
+ - 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}.
11113
+ - DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
11114
+ ${exampleLine}` : ""}`;
11115
+ }
11116
+ function extractSectionHeadingsFromSLC(slcMarkdown, artifactType) {
11117
+ if (!slcMarkdown) return {};
11118
+ const upperType = artifactType.trim().toUpperCase();
11119
+ const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
11120
+ for (const block of blocks) {
11121
+ const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
11122
+ if (!headerMatch) continue;
11123
+ const blockType = headerMatch[1].trim().toUpperCase();
11124
+ if (blockType === upperType || upperType.startsWith("LESSON") && blockType.startsWith("LESSON")) {
11125
+ const mapping = {};
11126
+ const lines = block.split("\n");
11127
+ for (const line of lines) {
11128
+ const trimmed = line.trim();
11129
+ if (trimmed.startsWith("|") && !trimmed.includes("---")) {
11130
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
11131
+ if (cells.length >= 2 && !cells[0].toLowerCase().includes("canonical") && !cells[1].toLowerCase().includes("localized")) {
11132
+ mapping[cells[0]] = cells[1];
11133
+ }
11134
+ } else {
11135
+ const bullet = line.match(/^[-*]\s+([^:]+):\s+(.+)$/);
11136
+ if (bullet) {
11137
+ mapping[bullet[1].trim()] = bullet[2].trim();
11138
+ }
11139
+ }
11140
+ }
11141
+ if (Object.keys(mapping).length > 0) return mapping;
11142
+ }
11143
+ }
11144
+ return {};
11145
+ }
11146
+ function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
11147
+ const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType);
11148
+ if (Object.keys(headings).length === 0) {
11149
+ return "";
11150
+ }
11151
+ const lines = Object.entries(headings).map(
11152
+ ([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
11153
+ );
11154
+ return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
11155
+ - The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
11156
+ - In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
11157
+ ${lines.join("\n")}
11158
+ - For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
11159
+ }
11160
+
11041
11161
  // src/services/lessonProductionService.ts
11042
11162
  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" ? 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))) : "";
11043
11163
  var _resolvedDir = __filename2 ? path3__default.default.dirname(__filename2) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
@@ -11079,11 +11199,44 @@ function detectProjectPedagogy(frameworkText, styleGuideText, explicitPedagogy)
11079
11199
  return "5e";
11080
11200
  }
11081
11201
  async function getLessonMetadata(storage, projectId, lessonId) {
11082
- const framework = await storage.readSotDocument(projectId, "CURRICULUM_FRAMEWORK.md") || "";
11083
11202
  const parts = lessonId.split("_");
11084
11203
  const unitCode = parts[0] || "U01";
11085
11204
  const moduleCode = parts.length >= 2 ? `${parts[0]}_${parts[1]}` : `${unitCode}_M01`;
11086
11205
  const lessonCode = lessonId;
11206
+ let planStr = null;
11207
+ try {
11208
+ planStr = await storage.readSotDocument(projectId, "CURRICULUM_PLAN.json") || await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json") || await storage.readArtifact(projectId, "CURRICULUM_PLAN.json");
11209
+ } catch {
11210
+ }
11211
+ if (planStr) {
11212
+ try {
11213
+ const plan = JSON.parse(planStr);
11214
+ if (Array.isArray(plan.sessions)) {
11215
+ const session = plan.sessions.find((s) => s.id === lessonId);
11216
+ if (session) {
11217
+ const lessonTitle2 = session.title || `Lesson ${lessonId}`;
11218
+ const keywords = Array.isArray(session.new_keywords) && session.new_keywords.length > 0 ? ` T\u1EEB kh\xF3a: ${session.new_keywords.join(", ")}` : "";
11219
+ const objective2 = session.prose_objective || session.title || "";
11220
+ const concept2 = objective2 ? `${objective2}.${keywords}` : session.title || "Core Technical Competency";
11221
+ const prerequisites2 = session.anchor_type ? `Anchor: ${session.anchor_type}` : "None";
11222
+ const depth = Array.isArray(session.depth_assignments) ? session.depth_assignments[0]?.depth : session.depth_assignments?.knowledge_depth;
11223
+ const bloomLevel2 = depth === "sio" ? "Create" : depth === "cio" ? "Apply" : "Understand";
11224
+ return {
11225
+ unitCode: session.unit_id || unitCode,
11226
+ moduleCode,
11227
+ lessonCode,
11228
+ lessonTitle: lessonTitle2,
11229
+ concept: `M\u1EE5c ti\xEAu bu\u1ED5i h\u1ECDc: ${concept2}`,
11230
+ prerequisites: prerequisites2,
11231
+ bloomLevel: bloomLevel2
11232
+ };
11233
+ }
11234
+ }
11235
+ } catch (planErr) {
11236
+ console.warn(`[getLessonMetadata] Error parsing CURRICULUM_PLAN.json for ${projectId}:`, planErr);
11237
+ }
11238
+ }
11239
+ const framework = await storage.readSotDocument(projectId, "CURRICULUM_FRAMEWORK.md") || "";
11087
11240
  let lessonTitle = `Lesson ${lessonId}`;
11088
11241
  let concept = "Core Technical Competency";
11089
11242
  let prerequisites = "None";
@@ -11128,15 +11281,24 @@ async function getLessonMetadata(storage, projectId, lessonId) {
11128
11281
  }
11129
11282
  if (found) break;
11130
11283
  }
11131
- return {
11132
- unitCode,
11133
- moduleCode,
11134
- lessonCode,
11135
- lessonTitle,
11136
- concept: objective ? `${concept}. M\u1EE5c ti\xEAu bu\u1ED5i h\u1ECDc: ${objective}` : concept,
11137
- prerequisites: deliverable ? `${prerequisites}. S\u1EA3n ph\u1EA9m hands-on c\u1EE7a bu\u1ED5i: ${deliverable}` : prerequisites,
11138
- bloomLevel
11139
- };
11284
+ if (found) {
11285
+ return {
11286
+ unitCode,
11287
+ moduleCode,
11288
+ lessonCode,
11289
+ lessonTitle,
11290
+ concept: objective ? `${concept}. M\u1EE5c ti\xEAu bu\u1ED5i h\u1ECDc: ${objective}` : concept,
11291
+ prerequisites: deliverable ? `${prerequisites}. S\u1EA3n ph\u1EA9m hands-on c\u1EE7a bu\u1ED5i: ${deliverable}` : prerequisites,
11292
+ bloomLevel
11293
+ };
11294
+ }
11295
+ throw new exports.CurriculumError({
11296
+ errorCode: "ERR_LESSON_NOT_IN_SOT",
11297
+ lessonId,
11298
+ message: `ERR_LESSON_NOT_IN_SOT: B\xE0i h\u1ECDc "${lessonId}" kh\xF4ng t\u1ED3n t\u1EA1i trong CURRICULUM_PLAN.json ho\u1EB7c CURRICULUM_FRAMEWORK.md c\u1EE7a d\u1EF1 \xE1n "${projectId}".`,
11299
+ suggestedAction: "\u0110\u1ED3ng b\u1ED9 CURRICULUM_PLAN r\u1ED3i ch\u1EA1y l\u1EA1i",
11300
+ retryable: false
11301
+ });
11140
11302
  }
11141
11303
  function buildFrameworkExcerptForLesson(framework, lessonId) {
11142
11304
  if (!framework) return "";
@@ -11178,7 +11340,7 @@ ${nextRow}`
11178
11340
  ].filter(Boolean);
11179
11341
  return parts.join("\n\n") || framework.slice(0, 1500);
11180
11342
  }
11181
- function buildLessonPrompt(pedagogy, lessonCode, unitCode, lessonTitle, concept, bloomLevel, dateStr) {
11343
+ function buildLessonPrompt(pedagogy, lessonCode, unitCode, lessonTitle, concept, bloomLevel, dateStr, targetLanguage = "vi", sectionLanguageContract) {
11182
11344
  const commonFrontmatter = `id: "LESSON_${lessonCode}"
11183
11345
  title: "${lessonTitle}"
11184
11346
  created_by: "Curriculum OS Builder"
@@ -11198,8 +11360,9 @@ template_required_sections:
11198
11360
  - "Artifact Contract"
11199
11361
  - "B. Lesson Flow"
11200
11362
  - "Artifact Linkage"`;
11363
+ let prompt = "";
11201
11364
  if (pedagogy === "edp") {
11202
- return `You are @content (Educational Content Editor & EDP Engineering Design Process Specialist).
11365
+ prompt = `You are @content (Educational Content Editor & EDP Engineering Design Process Specialist).
11203
11366
  Author the comprehensive Master Lesson Plan adhering strictly to \`LESSON_EDP_template.md\` standards for:
11204
11367
  - Lesson Code: ${lessonCode}
11205
11368
  - Title: ${lessonTitle}
@@ -11248,9 +11411,8 @@ Linkage ledger mapping all satellite artifacts (\`ACT_...\`, \`QUIZ_...\`, \`SLI
11248
11411
 
11249
11412
  3. Ensure all sample code and schemas are 100% syntax-valid and executable.
11250
11413
  4. Output 100% clean Markdown directly.`;
11251
- }
11252
- if (pedagogy === "pbl" || pedagogy === "general") {
11253
- return `You are @content (Educational Content Editor & PBL/General Pedagogy Specialist).
11414
+ } else if (pedagogy === "pbl" || pedagogy === "general") {
11415
+ prompt = `You are @content (Educational Content Editor & PBL/General Pedagogy Specialist).
11254
11416
  Author the comprehensive Master Lesson Plan adhering strictly to \`LESSON_GENERAL_template.md\` standards for:
11255
11417
  - Lesson Code: ${lessonCode}
11256
11418
  - Title: ${lessonTitle}
@@ -11297,8 +11459,8 @@ type: "LESSON_GENERAL"
11297
11459
  Linkage ledger mapping all satellite artifacts (\`ACT_...\`, \`QUIZ_...\`, \`SLIDE_...\`).
11298
11460
 
11299
11461
  3. Output 100% clean Markdown directly.`;
11300
- }
11301
- return `You are @content (Educational Content Editor & 5E Instructional Model Specialist).
11462
+ } else {
11463
+ prompt = `You are @content (Educational Content Editor & 5E Instructional Model Specialist).
11302
11464
  Author the comprehensive Master Lesson Plan adhering strictly to \`LESSON_5E_template.md\` standards for:
11303
11465
  - Lesson Code: ${lessonCode}
11304
11466
  - Title: ${lessonTitle}
@@ -11346,6 +11508,18 @@ Linkage ledger mapping all satellite artifacts (\`GUIDE_...\`, \`SLIDE_...\`, \`
11346
11508
 
11347
11509
  3. Ensure all sample code and syntax are 100% verified and runnable.
11348
11510
  4. Output 100% clean Markdown directly.`;
11511
+ }
11512
+ const langDirective = buildLanguageDirective(targetLanguage);
11513
+ const headingDirective = buildHeadingDirective(
11514
+ pedagogy === "edp" ? "LESSON_EDP" : pedagogy === "general" || pedagogy === "pbl" ? "LESSON_GENERAL" : "LESSON_5E",
11515
+ sectionLanguageContract,
11516
+ targetLanguage
11517
+ );
11518
+ return `${prompt}
11519
+
11520
+ ${langDirective}${headingDirective ? `
11521
+
11522
+ ${headingDirective}` : ""}`;
11349
11523
  }
11350
11524
  async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11351
11525
  const {
@@ -11376,6 +11550,20 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
11376
11550
  const styleGuide = await storage.readSotDocument(projectId, "CONTENT_STYLE_GUIDE.md") || "";
11377
11551
  const refPack = await storage.readSotDocument(projectId, "REFERENCE_PACK.md") || "";
11378
11552
  await storage.readSotDocument(projectId, "LEARNER_PROFILE.md") || "";
11553
+ const slcMarkdown = options.sectionLanguageContract || await storage.readSotDocument(projectId, "SECTION_LANGUAGE_CONTRACT.md") || "";
11554
+ let targetLang = options.targetLanguage;
11555
+ if (!targetLang) {
11556
+ try {
11557
+ const planRaw = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
11558
+ if (planRaw) {
11559
+ const p = JSON.parse(planRaw);
11560
+ targetLang = p?.translation?.target_language;
11561
+ }
11562
+ } catch {
11563
+ }
11564
+ }
11565
+ targetLang = targetLang || "vi";
11566
+ const languageDirective = buildLanguageDirective(targetLang);
11379
11567
  let expositionContext = "";
11380
11568
  let sessionSliceContext = "";
11381
11569
  try {
@@ -11445,7 +11633,9 @@ Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u
11445
11633
  lessonTitle,
11446
11634
  concept,
11447
11635
  bloomLevel,
11448
- dateStr
11636
+ dateStr,
11637
+ targetLang,
11638
+ slcMarkdown
11449
11639
  ) + mediaDirective;
11450
11640
  const rawLesson = await runCurriculumAIInference(
11451
11641
  [{ role: "user", content: lessonPrompt }],
@@ -11786,7 +11976,11 @@ template_required_sections:
11786
11976
  - ### Phase 3: Debugging & Testing
11787
11977
  - ## [REQUIRED] 3-Tier Differentiation (Bronze/Silver/Gold tasks)
11788
11978
  - ## [REQUIRED] Reflection (Discussion question)
11789
- 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.`;
11979
+ 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
11980
+
11981
+ ${languageDirective}
11982
+
11983
+ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
11790
11984
  const rawAct = await runCurriculumAIInference(
11791
11985
  [{ role: "user", content: actPrompt }],
11792
11986
  satelliteContext,
@@ -11858,7 +12052,11 @@ template_required_sections:
11858
12052
  - ## [REQUIRED] Multiple-Choice Question Bank (\u22655 scenario-based 4-option questions with: Question text, A/B/C/D options, Correct answer, Bloom level, Detailed distractor rationale)
11859
12053
  - ## [REQUIRED] Code Analysis / Debugging Question (1 deep code tracing question requiring students to predict output or identify bugs)
11860
12054
  - ## [REQUIRED] Competency Rubric (4-tier matrix: Developing \u2192 Proficient \u2192 Advanced \u2192 Exemplary)
11861
- 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.`;
12055
+ 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
12056
+
12057
+ ${languageDirective}
12058
+
12059
+ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
11862
12060
  const rawQuiz = await runCurriculumAIInference(
11863
12061
  [{ role: "user", content: quizPrompt }],
11864
12062
  satelliteContext,
@@ -11937,7 +12135,11 @@ Mandatory Canvas & Presentation Invariants:
11937
12135
  * COLD CALL / CHECK: Specific diagnostic question to ask students.
11938
12136
  * SCAFFOLDING / GOTCHA: Guidance for students who get stuck.
11939
12137
  * PACING: Recommended minutes for the slide.
11940
- - Output 100% valid Marp Markdown.`;
12138
+ - Output 100% valid Marp Markdown.
12139
+
12140
+ ${languageDirective}
12141
+
12142
+ ${buildHeadingDirective("SLIDE", slcMarkdown, targetLang)}`;
11941
12143
  const rawSlide = await runCurriculumAIInference(
11942
12144
  [{ role: "user", content: slidePrompt }],
11943
12145
  satelliteContext,
@@ -12004,7 +12206,11 @@ template_contract: "artifact-template-v1"
12004
12206
  - ## [REQUIRED] Facilitation Script & Timeline (Phase-by-phase teacher moves, inquiry questions, transitions)
12005
12207
  - ## [REQUIRED] Common Misconceptions & Diagnostic Remediation Matrix
12006
12208
  - ## [REQUIRED] Differentiated Support Strategies (Support scaffolds vs Extension challenges)
12007
- 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.`;
12209
+ 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
12210
+
12211
+ ${languageDirective}
12212
+
12213
+ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
12008
12214
  const rawGuide = await runCurriculumAIInference(
12009
12215
  [{ role: "user", content: guidePrompt }],
12010
12216
  satelliteContext,
@@ -12063,7 +12269,11 @@ template_contract: "artifact-template-v1"
12063
12269
  - ## [REQUIRED] Visual Mental Model / Architecture Diagram (Mermaid or ASCII diagram)
12064
12270
  - ## [REQUIRED] Step-by-Step Practical Quick-Start Guide
12065
12271
  - ## [REQUIRED] Self-Check Diagnostic Checklist (Can-do statements for student self-assessment)
12066
- 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.`;
12272
+ 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
12273
+
12274
+ ${languageDirective}
12275
+
12276
+ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
12067
12277
  const rawHandout = await runCurriculumAIInference(
12068
12278
  [{ role: "user", content: handoutPrompt }],
12069
12279
  satelliteContext,
@@ -12126,7 +12336,11 @@ template_contract: "artifact-template-v1"
12126
12336
  - ## [REQUIRED] Part 3: Code Analysis & Bug Hunting Challenge
12127
12337
  - ## [REQUIRED] Part 4: Synthesis & Problem-Solving Application
12128
12338
  - ## [REQUIRED] Part 5: Self-Reflection & Learning Log
12129
- 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.`;
12339
+ 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
12340
+
12341
+ ${languageDirective}
12342
+
12343
+ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
12130
12344
  const rawWks = await runCurriculumAIInference(
12131
12345
  [{ role: "user", content: wksPrompt }],
12132
12346
  satelliteContext,
@@ -12193,7 +12407,11 @@ template_required_sections:
12193
12407
  - ## [REQUIRED] Verified Reference Solution Code (100% syntactically valid, production-ready, fully commented)
12194
12408
  - ## [REQUIRED] Automated Test / Verification Script (How to compile, flash, or unit test)
12195
12409
  - ## [REQUIRED] Common Syntax & Runtime Pitfalls Matrix
12196
- 3. Output 100% clean Markdown directly with runnable code snippets \u2014 NO outer code fences around the document.`;
12410
+ 3. Output 100% clean Markdown directly with runnable code snippets \u2014 NO outer code fences around the document.
12411
+
12412
+ ${languageDirective}
12413
+
12414
+ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
12197
12415
  const rawCode = await runCurriculumAIInference(
12198
12416
  [{ role: "user", content: codePrompt }],
12199
12417
  satelliteContext,
@@ -12259,7 +12477,11 @@ template_contract: "artifact-template-v1"
12259
12477
  - ### Challenge 2: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50) \u2014 Advanced Architectural Constraints & Edge Cases
12260
12478
  - ### Challenge 3: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 Extension Milestones (Level 1: Ninja \u2192 Level 2: Guru \u2192 Level 3: Master)
12261
12479
  - ## [REQUIRED] Metacognitive Deep Dive & Engineering Trade-offs
12262
- 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.`;
12480
+ 3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
12481
+
12482
+ ${languageDirective}
12483
+
12484
+ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
12263
12485
  const rawExt = await runCurriculumAIInference(
12264
12486
  [{ role: "user", content: extPrompt }],
12265
12487
  satelliteContext,
@@ -16903,8 +17125,10 @@ exports.buildDiagnosticQuizPrompt = buildDiagnosticQuizPrompt;
16903
17125
  exports.buildExpositionContext = buildExpositionContext;
16904
17126
  exports.buildExtensionPrompt = buildExtensionPrompt;
16905
17127
  exports.buildHandoutPrompt = buildHandoutPrompt;
17128
+ exports.buildHeadingDirective = buildHeadingDirective;
16906
17129
  exports.buildImagePrompt = buildImagePrompt;
16907
17130
  exports.buildJudgePrompt = buildJudgePrompt;
17131
+ exports.buildLanguageDirective = buildLanguageDirective;
16908
17132
  exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
16909
17133
  exports.buildProjectInstructionPrompt = buildProjectInstructionPrompt;
16910
17134
  exports.buildSelfLabPrompt = buildSelfLabPrompt;
@@ -16937,6 +17161,7 @@ exports.executeCurriculumCommand = executeCurriculumCommand;
16937
17161
  exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
16938
17162
  exports.expositionCacheKey = expositionCacheKey;
16939
17163
  exports.extractScopeSequenceRows = extractScopeSequenceRows;
17164
+ exports.extractSectionHeadingsFromSLC = extractSectionHeadingsFromSLC;
16940
17165
  exports.extractSessionSlice = extractSessionSlice;
16941
17166
  exports.extractStreamChunk = extractStreamChunk;
16942
17167
  exports.extractThoughtAndContent = extractThoughtAndContent;
@@ -17003,6 +17228,7 @@ exports.researcherTools = researcherTools;
17003
17228
  exports.resolveGateSettings = resolveGateSettings;
17004
17229
  exports.resolveStandardsPacks = resolveStandardsPacks;
17005
17230
  exports.resolveStreamBudget = resolveStreamBudget;
17231
+ exports.resolveTargetLanguageCode = resolveTargetLanguageCode;
17006
17232
  exports.resolveTranslationTargets = resolveTranslationTargets;
17007
17233
  exports.reviewerTools = reviewerTools;
17008
17234
  exports.runCurriculumAIInference = runCurriculumAIInference;
@@ -17026,6 +17252,7 @@ exports.streamLayerPrefillWithFallback = streamLayerPrefillWithFallback;
17026
17252
  exports.streamLessonMasterFlow = streamLessonMasterFlow;
17027
17253
  exports.streamSelfLabFlow = streamSelfLabFlow;
17028
17254
  exports.stripLlmJsonWrappers = stripLlmJsonWrappers;
17255
+ exports.targetLanguageDisplayName = targetLanguageDisplayName;
17029
17256
  exports.techSmeTools = techSmeTools;
17030
17257
  exports.topoSort = topoSort;
17031
17258
  exports.uploadAssetToBucket = uploadAssetToBucket;