@thanh01.pmt/curriculum-kit 1.4.44 → 1.4.45

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.
Files changed (119) hide show
  1. package/dist/ai/index.cjs.map +1 -1
  2. package/dist/ai/index.d.cts +77 -77
  3. package/dist/ai/index.d.ts +77 -77
  4. package/dist/ai/index.mjs.map +1 -1
  5. package/dist/index.cjs +362 -265
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +117 -28
  8. package/dist/index.d.ts +117 -28
  9. package/dist/index.mjs +357 -264
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/pipeline/index.cjs +33 -14
  12. package/dist/pipeline/index.cjs.map +1 -1
  13. package/dist/pipeline/index.mjs +33 -14
  14. package/dist/pipeline/index.mjs.map +1 -1
  15. package/dist/schemas/index.d.cts +1064 -1064
  16. package/dist/schemas/index.d.ts +1064 -1064
  17. package/dist/standards/index.d.cts +2 -2
  18. package/dist/standards/index.d.ts +2 -2
  19. package/dist/{standardsCoverageGate-49pJq5t8.d.cts → standardsCoverageGate-CLnq0d0s.d.cts} +12 -12
  20. package/dist/{standardsCoverageGate-49pJq5t8.d.ts → standardsCoverageGate-CLnq0d0s.d.ts} +12 -12
  21. package/dist/templates/ACT_template.md +18 -9
  22. package/dist/templates/ALIGNMENT_MATRIX_template.md +5 -0
  23. package/dist/templates/ART_DIRECTION_template.md +7 -0
  24. package/dist/templates/ASSET_MANIFEST_template.md +5 -0
  25. package/dist/templates/BASELINE_STANDARD.md +5 -0
  26. package/dist/templates/BELL_RINGER_template.md +13 -13
  27. package/dist/templates/CHANGELOG_template.md +3 -0
  28. package/dist/templates/CHOICE_BOARD_template.md +8 -5
  29. package/dist/templates/CODE_template.md +5 -6
  30. package/dist/templates/CONTENT_CONTINUITY_template.md +6 -0
  31. package/dist/templates/CONTENT_STYLE_GUIDE_template.md +4 -5
  32. package/dist/templates/CURRICULUM_FRAMEWORK_template.md +5 -6
  33. package/dist/templates/DEPENDENCY_MAP_template.md +3 -4
  34. package/dist/templates/EXIT_TICKET_template.md +8 -8
  35. package/dist/templates/EXT_template.md +3 -0
  36. package/dist/templates/GLOSSARY_template.md +1 -2
  37. package/dist/templates/GRAPHIC_ORGANIZER_template.md +8 -8
  38. package/dist/templates/GUIDE_template.md +5 -6
  39. package/dist/templates/HANDOUT_template.md +5 -6
  40. package/dist/templates/HANDOVER_DOCUMENT_template.md +8 -0
  41. package/dist/templates/KNOWLEDGE_EXPOSITION_template.md +16 -6
  42. package/dist/templates/LAB_TEMPLATE.md +20 -3
  43. package/dist/templates/LEARNER_PROFILE_template.md +4 -5
  44. package/dist/templates/LESSONS_LEARNED_template.md +8 -0
  45. package/dist/templates/LESSON_5E_template.md +24 -9
  46. package/dist/templates/LESSON_EDP_template.md +26 -10
  47. package/dist/templates/LESSON_GENERAL_template.md +22 -8
  48. package/dist/templates/OUTPUT_CONSTRAINTS_template.md +7 -0
  49. package/dist/templates/PACKAGING_SPEC_template.md +8 -0
  50. package/dist/templates/PILOT_FEEDBACK_template.md +6 -0
  51. package/dist/templates/PROJECT_BRIEF_template.md +8 -0
  52. package/dist/templates/PROJECT_STATUS_template.md +9 -0
  53. package/dist/templates/QUIZ_template.md +7 -0
  54. package/dist/templates/QUIZ_v4.6_template.md +4 -5
  55. package/dist/templates/REFERENCE_PACK_template.md +4 -0
  56. package/dist/templates/REVIEW_GAME_template.md +11 -8
  57. package/dist/templates/RUBRIC_template.md +2 -3
  58. package/dist/templates/SCIENCE_LAB_template.md +11 -5
  59. package/dist/templates/SECTION_LANGUAGE_CONTRACT_template.md +44 -0
  60. package/dist/templates/SELF_LAB_template.md +7 -0
  61. package/dist/templates/SLIDE_template.md +23 -0
  62. package/dist/templates/STATION_ROTATION_template.md +11 -11
  63. package/dist/templates/TASK_CARDS_template.md +8 -8
  64. package/dist/templates/TIERED_PRACTICE_template.md +14 -10
  65. package/dist/templates/UPDATE_ROADMAP_template.md +7 -0
  66. package/dist/templates/WKS_template.md +5 -6
  67. package/dist/workflow/index.cjs +231 -209
  68. package/dist/workflow/index.cjs.map +1 -1
  69. package/dist/workflow/index.d.cts +1 -1
  70. package/dist/workflow/index.d.ts +1 -1
  71. package/dist/workflow/index.mjs +231 -209
  72. package/dist/workflow/index.mjs.map +1 -1
  73. package/docs/refs/templates/ACT_template.md +18 -9
  74. package/docs/refs/templates/ALIGNMENT_MATRIX_template.md +5 -0
  75. package/docs/refs/templates/ART_DIRECTION_template.md +7 -0
  76. package/docs/refs/templates/ASSET_MANIFEST_template.md +5 -0
  77. package/docs/refs/templates/BASELINE_STANDARD.md +5 -0
  78. package/docs/refs/templates/BELL_RINGER_template.md +13 -13
  79. package/docs/refs/templates/CHANGELOG_template.md +3 -0
  80. package/docs/refs/templates/CHOICE_BOARD_template.md +8 -5
  81. package/docs/refs/templates/CODE_template.md +5 -6
  82. package/docs/refs/templates/CONTENT_CONTINUITY_template.md +6 -0
  83. package/docs/refs/templates/CONTENT_STYLE_GUIDE_template.md +4 -5
  84. package/docs/refs/templates/CURRICULUM_FRAMEWORK_template.md +5 -6
  85. package/docs/refs/templates/DEPENDENCY_MAP_template.md +3 -4
  86. package/docs/refs/templates/EXIT_TICKET_template.md +8 -8
  87. package/docs/refs/templates/EXT_template.md +3 -0
  88. package/docs/refs/templates/GLOSSARY_template.md +1 -2
  89. package/docs/refs/templates/GRAPHIC_ORGANIZER_template.md +8 -8
  90. package/docs/refs/templates/GUIDE_template.md +5 -6
  91. package/docs/refs/templates/HANDOUT_template.md +5 -6
  92. package/docs/refs/templates/HANDOVER_DOCUMENT_template.md +8 -0
  93. package/docs/refs/templates/KNOWLEDGE_EXPOSITION_template.md +16 -6
  94. package/docs/refs/templates/LAB_TEMPLATE.md +20 -3
  95. package/docs/refs/templates/LEARNER_PROFILE_template.md +4 -5
  96. package/docs/refs/templates/LESSONS_LEARNED_template.md +8 -0
  97. package/docs/refs/templates/LESSON_5E_template.md +24 -9
  98. package/docs/refs/templates/LESSON_EDP_template.md +26 -10
  99. package/docs/refs/templates/LESSON_GENERAL_template.md +22 -8
  100. package/docs/refs/templates/OUTPUT_CONSTRAINTS_template.md +7 -0
  101. package/docs/refs/templates/PACKAGING_SPEC_template.md +8 -0
  102. package/docs/refs/templates/PILOT_FEEDBACK_template.md +6 -0
  103. package/docs/refs/templates/PROJECT_BRIEF_template.md +8 -0
  104. package/docs/refs/templates/PROJECT_STATUS_template.md +9 -0
  105. package/docs/refs/templates/QUIZ_template.md +7 -0
  106. package/docs/refs/templates/QUIZ_v4.6_template.md +4 -5
  107. package/docs/refs/templates/REFERENCE_PACK_template.md +4 -0
  108. package/docs/refs/templates/REVIEW_GAME_template.md +11 -8
  109. package/docs/refs/templates/RUBRIC_template.md +2 -3
  110. package/docs/refs/templates/SCIENCE_LAB_template.md +11 -5
  111. package/docs/refs/templates/SECTION_LANGUAGE_CONTRACT_template.md +44 -0
  112. package/docs/refs/templates/SELF_LAB_template.md +7 -0
  113. package/docs/refs/templates/SLIDE_template.md +23 -0
  114. package/docs/refs/templates/STATION_ROTATION_template.md +11 -11
  115. package/docs/refs/templates/TASK_CARDS_template.md +8 -8
  116. package/docs/refs/templates/TIERED_PRACTICE_template.md +14 -10
  117. package/docs/refs/templates/UPDATE_ROADMAP_template.md +7 -0
  118. package/docs/refs/templates/WKS_template.md +5 -6
  119. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -5,11 +5,11 @@ var path4 = require('path');
5
5
  var google = require('@ai-sdk/google');
6
6
  var openai = require('@ai-sdk/openai');
7
7
  var deepseek = require('@ai-sdk/deepseek');
8
+ var url = require('url');
8
9
  var zod = require('zod');
9
10
  var ai = require('ai');
10
11
  var pRetry = require('p-retry');
11
12
  var jsonrepair = require('jsonrepair');
12
- var url = require('url');
13
13
  var pLimit = require('p-limit');
14
14
  var fsPromises = require('fs/promises');
15
15
  var crypto = require('crypto');
@@ -1178,6 +1178,229 @@ var init_provider_factory = __esm({
1178
1178
  };
1179
1179
  }
1180
1180
  });
1181
+ function stripTemplateFrontmatter(raw) {
1182
+ let body = raw;
1183
+ if (body.startsWith("---")) {
1184
+ const end = body.indexOf("\n---", 3);
1185
+ if (end !== -1) body = body.slice(end + 4);
1186
+ }
1187
+ return body.split("\n").filter((l) => l.trim() !== "<!-- INJECT_BODY -->").join("\n").replace(/^\s*\n/, "").trimEnd();
1188
+ }
1189
+ function extractRequiredSections(raw) {
1190
+ const lines = raw.split("\n");
1191
+ const start = lines.findIndex((l) => /^template_required_sections:\s*$/.test(l.trim()));
1192
+ if (start === -1) return null;
1193
+ const items = [];
1194
+ for (let i = start + 1; i < lines.length; i++) {
1195
+ const line = lines[i] ?? "";
1196
+ if (line.trim() === "") continue;
1197
+ if (!/^\s+-\s/.test(line)) break;
1198
+ items.push(line.trim().replace(/^-\s+/, "").replace(/^["']|["']$/g, ""));
1199
+ }
1200
+ return items.length > 0 ? items : null;
1201
+ }
1202
+ function stripHeadingKey(h) {
1203
+ return h.replace(/\[(REQUIRED|OPTIONAL|CONDITIONAL)\]\s*/gi, "").replace(/^\s*\d+[.)]\s+/, "").replace(/\s+/g, " ").trim();
1204
+ }
1205
+ function isVariableHeading(text) {
1206
+ return /\{\{[^}]*\}\}/.test(text) || /\[[^\]]+\]/.test(text);
1207
+ }
1208
+ function collectHeadings(body) {
1209
+ const out = [];
1210
+ let inFence = false;
1211
+ for (const line of body.split("\n")) {
1212
+ if (/^(```|~~~)/.test(line.trim())) {
1213
+ inFence = !inFence;
1214
+ continue;
1215
+ }
1216
+ if (inFence) continue;
1217
+ const m = line.match(/^(#{2,3})\s+(.*)$/);
1218
+ if (!m) continue;
1219
+ const text = m[2].trim();
1220
+ const optional = /^\[OPTIONAL\]/i.test(text);
1221
+ const name = stripHeadingKey(text);
1222
+ if (!name || isVariableHeading(name)) continue;
1223
+ out.push({ level: m[1].length, name, optional });
1224
+ }
1225
+ return out;
1226
+ }
1227
+ function extractEchoSections(raw) {
1228
+ const hs = collectHeadings(stripTemplateFrontmatter(raw));
1229
+ return {
1230
+ required: hs.filter((h) => !h.optional).map((h) => h.name),
1231
+ optional: hs.filter((h) => h.optional).map((h) => h.name)
1232
+ };
1233
+ }
1234
+ function extractConditionalSections(raw) {
1235
+ const body = stripTemplateFrontmatter(raw);
1236
+ return body.split("\n").filter((l) => /^#{2,3} .*\[CONDITIONAL\]/i.test(l)).map((l) => stripHeadingKey(l.replace(/^#{2,3}\s*/, "").trim())).filter((s) => s.length > 0 && !isVariableHeading(s));
1237
+ }
1238
+ function extractDeclaredLanguage(raw) {
1239
+ const m = raw.match(/^template_language:\s*["']?([a-zA-Z-]+)["']?\s*$/m);
1240
+ return m?.[1]?.toLowerCase() || "en";
1241
+ }
1242
+ function candidateDirs(explicitDir) {
1243
+ const dirs = [];
1244
+ if (explicitDir) dirs.push(explicitDir);
1245
+ if (process.env.CURRICULUM_TEMPLATES_DIR) dirs.push(process.env.CURRICULUM_TEMPLATES_DIR);
1246
+ dirs.push(path4__default.default.resolve(process.cwd(), "_templates"));
1247
+ for (const rel of KIT_TEMPLATE_FALLBACK_DIRS) {
1248
+ dirs.push(path4__default.default.resolve(_moduleDir, rel));
1249
+ dirs.push(path4__default.default.resolve(process.cwd(), rel));
1250
+ dirs.push(path4__default.default.resolve(process.cwd(), "packages/curriculum-kit", rel));
1251
+ }
1252
+ return dirs;
1253
+ }
1254
+ function findTemplatePath(templateName, explicitDir) {
1255
+ for (const dir of candidateDirs(explicitDir)) {
1256
+ const p = path4__default.default.join(dir, templateName);
1257
+ if (!fs3__default.default.existsSync(p)) continue;
1258
+ try {
1259
+ fs3__default.default.accessSync(p, fs3__default.default.constants.R_OK);
1260
+ return p;
1261
+ } catch {
1262
+ }
1263
+ }
1264
+ return null;
1265
+ }
1266
+ function resolveArtifactTemplate(templateName, opts) {
1267
+ const p = findTemplatePath(templateName, opts?.templatesDir);
1268
+ if (!p) return null;
1269
+ try {
1270
+ const raw = fs3__default.default.readFileSync(p, "utf-8");
1271
+ const echo = extractEchoSections(raw);
1272
+ return {
1273
+ content: stripTemplateFrontmatter(raw),
1274
+ sections: echo.required,
1275
+ optionalSections: echo.optional,
1276
+ sourcePath: p,
1277
+ conditionals: extractConditionalSections(raw),
1278
+ declaredLang: extractDeclaredLanguage(raw)
1279
+ };
1280
+ } catch {
1281
+ return null;
1282
+ }
1283
+ }
1284
+ function assembleTranslatedTemplate(templateName, translatedBody, targetLanguage, opts) {
1285
+ const raw = loadCurriculumTemplate(templateName, opts?.templatesDir);
1286
+ if (!raw || !translatedBody?.trim()) return null;
1287
+ const base = collectHeadings(stripTemplateFrontmatter(raw));
1288
+ const out = collectHeadings(translatedBody);
1289
+ if (base.length !== out.length) return null;
1290
+ for (let i = 0; i < base.length; i++) {
1291
+ if (base[i].level !== out[i].level || base[i].optional !== out[i].optional) return null;
1292
+ }
1293
+ const srcBody = stripTemplateFrontmatter(raw);
1294
+ for (const ph of new Set(srcBody.match(/\{\{[^}]+\}\}/g) ?? [])) {
1295
+ if (!translatedBody.includes(ph)) return null;
1296
+ }
1297
+ for (const mk of new Set(srcBody.match(/<!--\s*\[\/?[^\]]+\]\s*-->/g) ?? [])) {
1298
+ if (!translatedBody.includes(mk)) return null;
1299
+ }
1300
+ const lang = targetLanguage.trim().toLowerCase() || "en";
1301
+ const req = out.filter((h) => !h.optional).map((h) => h.name);
1302
+ let fmRaw = "";
1303
+ if (raw.startsWith("---")) {
1304
+ const end = raw.indexOf("\n---", 3);
1305
+ if (end !== -1) fmRaw = raw.slice(0, end + 4);
1306
+ }
1307
+ const fm = stampEchoOnFrontmatter(fmRaw || "---\n---", lang, req);
1308
+ return { raw: `${fm}
1309
+ ${translatedBody.trimStart()}`, sections: req, templateLanguage: lang };
1310
+ }
1311
+ function stampEchoOnFrontmatter(rawDoc, lang, sections) {
1312
+ const echoLines = ['template_language: "' + lang + '"', "template_required_sections:", ...sections.map((n) => ` - "${n.replace(/"/g, '\\"')}"`)];
1313
+ if (rawDoc.startsWith("---")) {
1314
+ const lines = rawDoc.split("\n");
1315
+ const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
1316
+ if (closeIdx === -1) {
1317
+ return ["---", ...echoLines, "---", "", stripTemplateFrontmatter(rawDoc)].join("\n");
1318
+ }
1319
+ const kept = [];
1320
+ let skippingList = false;
1321
+ for (const l of lines.slice(0, closeIdx)) {
1322
+ const t = l.trim();
1323
+ if (/^template_required_sections:$/.test(t)) {
1324
+ skippingList = true;
1325
+ continue;
1326
+ }
1327
+ if (/^template_language:/.test(t)) continue;
1328
+ if (skippingList) {
1329
+ if (/^\s+-\s/.test(l)) continue;
1330
+ skippingList = false;
1331
+ }
1332
+ kept.push(l);
1333
+ }
1334
+ return [...kept, ...echoLines, "---", ...lines.slice(closeIdx + 1)].join("\n");
1335
+ }
1336
+ return ["---", ...echoLines, "---", "", rawDoc].join("\n");
1337
+ }
1338
+ async function translateTemplate(templateName, targetLanguage, runner, opts) {
1339
+ const raw = loadCurriculumTemplate(templateName, opts?.templatesDir);
1340
+ if (!raw) return null;
1341
+ const body = stripTemplateFrontmatter(raw);
1342
+ const placeholders = Array.from(new Set(body.match(/\{\{[^}]+\}\}/g) ?? []));
1343
+ const markers = Array.from(new Set(body.match(/<!--\s*\[\/?[^\]]+\]\s*-->/g) ?? []));
1344
+ const prompt = `Translate the following Markdown artifact TEMPLATE body into ${targetLanguage}.
1345
+ STRICT PRESERVATION RULES (violating any of these invalidates the output):
1346
+ 1. Keep the document structure EXACTLY: same number of headings, same order, same heading levels (## / ###).
1347
+ 2. Translate heading TEXT naturally, but a heading must remain a single static line: move nothing into or out of it. Headings never contain durations or placeholders \u2014 if a source heading carries "[X mins]"-style timing, the timing belongs on the line below the heading, not inside it.
1348
+ 3. Keep every placeholder token VERBATIM (do not translate or alter), including {{ XX }}-style duration tokens: ${placeholders.join(" ") || "(none)"}
1349
+ 4. Keep every HTML comment marker VERBATIM: ${markers.join(" ") || "(none)"}
1350
+ 5. Keep code blocks, file paths, technical identifiers (API names, protocols, component models) untranslated.
1351
+ 6. Translate ONLY natural-language prose, headings, and table headers into natural ${targetLanguage}.
1352
+ 7. Output ONLY the translated markdown \u2014 no explanations, no code fences around the whole document.
1353
+
1354
+ TEMPLATE BODY:
1355
+ ${body}`;
1356
+ let translatedBody;
1357
+ try {
1358
+ translatedBody = await runner(prompt);
1359
+ } catch {
1360
+ return null;
1361
+ }
1362
+ if (!translatedBody || translatedBody.length < body.length * 0.4) return null;
1363
+ translatedBody = translatedBody.trim().replace(/^```(?:markdown)?\n/, "").replace(/\n```$/, "");
1364
+ return assembleTranslatedTemplate(templateName, translatedBody, targetLanguage, opts);
1365
+ }
1366
+ function loadCurriculumTemplate(templateName, templatesDir) {
1367
+ const p = findTemplatePath(templateName, templatesDir);
1368
+ if (!p) return "";
1369
+ try {
1370
+ return fs3__default.default.readFileSync(p, "utf-8");
1371
+ } catch {
1372
+ return "";
1373
+ }
1374
+ }
1375
+ function loadAuthoringTemplate(templateName, templatesDir) {
1376
+ const p = findTemplatePath(templateName, templatesDir);
1377
+ if (!p) return "";
1378
+ try {
1379
+ return stripTemplateFrontmatter(fs3__default.default.readFileSync(p, "utf-8"));
1380
+ } catch {
1381
+ return "";
1382
+ }
1383
+ }
1384
+ var KIT_TEMPLATE_FALLBACK_DIRS, _moduleDir;
1385
+ var init_templateLoader = __esm({
1386
+ "src/services/templateLoader.ts"() {
1387
+ KIT_TEMPLATE_FALLBACK_DIRS = [
1388
+ // Packaged with the npm bundle (dist/templates) — resolves from node_modules.
1389
+ "templates",
1390
+ // Repo checkout layouts.
1391
+ "docs/refs/templates"
1392
+ ];
1393
+ _moduleDir = (() => {
1394
+ try {
1395
+ if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && 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") {
1396
+ return path4__default.default.dirname(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))));
1397
+ }
1398
+ } catch {
1399
+ }
1400
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
1401
+ })();
1402
+ }
1403
+ });
1181
1404
 
1182
1405
  // src/pipeline/artifactLinter.ts
1183
1406
  var artifactLinter_exports = {};
@@ -1544,14 +1767,26 @@ function parseLessonFlow(lessonMarkdown) {
1544
1767
  }
1545
1768
  }
1546
1769
  }
1547
- const flowMatch = lessonMarkdown.match(/##\s*\[?REQUIRED\]?\s*B\.\s*Lesson Flow([\s\S]*?)$/i);
1770
+ const flowMatch = lessonMarkdown.match(/##\s*(?:\[?REQUIRED\]?\s*)?B\.\s*Lesson Flow([\s\S]*?)$/i);
1548
1771
  const flowSection = flowMatch ? flowMatch[1] : lessonMarkdown;
1549
- const phaseRegex = /###\s*(?:(\d+)\.\s*)?([^\n—\-]+)(?:[—\-]\s*\[?([^\]\n]+)\]?)?\s*\n([\s\S]*?)(?=(?:###\s*(?:\d+\.)?|$))/gi;
1772
+ const phaseRegex = /###\s*(?:(\d+)\.\s*)?([^\n]+)\n([\s\S]*?)(?=(?:###\s*(?:\d+\.)?|$))/gi;
1550
1773
  let pMatch;
1551
1774
  while ((pMatch = phaseRegex.exec(flowSection)) !== null) {
1552
- const rawPhaseName = pMatch[2].trim();
1553
- const timing = pMatch[3]?.trim();
1554
- const content = pMatch[4].trim();
1775
+ const headingText = pMatch[2].trim();
1776
+ const tailMatch = headingText.match(/[—–-]\s*\[?([^\]—–]+)\]?\s*$/);
1777
+ const tail = tailMatch?.[1]?.trim() ?? "";
1778
+ const rawPhaseName = stripHeadingKey(
1779
+ tailMatch && /mins?/i.test(tail) ? headingText.slice(0, tailMatch.index).trim() : headingText
1780
+ );
1781
+ let content = pMatch[3].trim();
1782
+ let timing;
1783
+ const durMatch2 = content.match(/^>?\s*\**Duration:?\**\s*([~0-9][^\n]*)/i);
1784
+ if (durMatch2) {
1785
+ timing = durMatch2[1].trim();
1786
+ content = content.replace(/^>?\s*\**Duration:?\**\s*[^\n]*\n?/i, "").trim();
1787
+ } else if (tail && /mins?/i.test(tail)) {
1788
+ timing = tail;
1789
+ }
1555
1790
  const codeSnippets = [];
1556
1791
  const codeRegex = /```(?:[a-zA-Z0-9_\-]+)?\s*\n([\s\S]*?)```/g;
1557
1792
  let cMatch;
@@ -1579,6 +1814,7 @@ function parseLessonFlow(lessonMarkdown) {
1579
1814
  }
1580
1815
  var init_lessonFlowParser = __esm({
1581
1816
  "src/parsers/lessonFlowParser.ts"() {
1817
+ init_templateLoader();
1582
1818
  }
1583
1819
  });
1584
1820
 
@@ -3944,107 +4180,9 @@ var RoadmapInputSchema = zod.z.object({
3944
4180
 
3945
4181
  // src/ai/index.ts
3946
4182
  init_provider_factory();
3947
- function stripTemplateFrontmatter(raw) {
3948
- let body = raw;
3949
- if (body.startsWith("---")) {
3950
- const end = body.indexOf("\n---", 3);
3951
- if (end !== -1) body = body.slice(end + 4);
3952
- }
3953
- return body.split("\n").filter((l) => l.trim() !== "<!-- INJECT_BODY -->").join("\n").replace(/^\s*\n/, "").trimEnd();
3954
- }
3955
- function extractRequiredSections(raw) {
3956
- const lines = raw.split("\n");
3957
- const start = lines.findIndex((l) => /^template_required_sections:\s*$/.test(l.trim()));
3958
- if (start === -1) return null;
3959
- const items = [];
3960
- for (let i = start + 1; i < lines.length; i++) {
3961
- const line = lines[i] ?? "";
3962
- if (line.trim() === "") continue;
3963
- if (!/^\s+-\s/.test(line)) break;
3964
- items.push(line.trim().replace(/^-\s+/, "").replace(/^["']|["']$/g, ""));
3965
- }
3966
- return items.length > 0 ? items : null;
3967
- }
3968
- function normalizeHeading(h) {
3969
- return h.replace(/\[(REQUIRED|OPTIONAL|CONDITIONAL)\]\s*/gi, "").replace(/\s+/g, " ").trim();
3970
- }
3971
- function extractBodySections(raw) {
3972
- const body = stripTemplateFrontmatter(raw);
3973
- return body.split("\n").filter((l) => l.startsWith("## ")).map((l) => normalizeHeading(l.slice(3).trim())).filter((s) => s.length > 0);
3974
- }
3975
- var KIT_TEMPLATE_FALLBACK_DIRS = [
3976
- // Packaged with the npm bundle (dist/templates) — resolves from node_modules.
3977
- "templates",
3978
- // Repo checkout layouts.
3979
- "docs/refs/templates"
3980
- ];
3981
- var _moduleDir = (() => {
3982
- try {
3983
- if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) }) !== "undefined" && 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") {
3984
- return path4__default.default.dirname(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))));
3985
- }
3986
- } catch {
3987
- }
3988
- return typeof __dirname !== "undefined" ? __dirname : process.cwd();
3989
- })();
3990
- function candidateDirs(explicitDir) {
3991
- const dirs = [];
3992
- if (explicitDir) dirs.push(explicitDir);
3993
- if (process.env.CURRICULUM_TEMPLATES_DIR) dirs.push(process.env.CURRICULUM_TEMPLATES_DIR);
3994
- dirs.push(path4__default.default.resolve(process.cwd(), "_templates"));
3995
- for (const rel of KIT_TEMPLATE_FALLBACK_DIRS) {
3996
- dirs.push(path4__default.default.resolve(_moduleDir, rel));
3997
- dirs.push(path4__default.default.resolve(process.cwd(), rel));
3998
- dirs.push(path4__default.default.resolve(process.cwd(), "packages/curriculum-kit", rel));
3999
- }
4000
- return dirs;
4001
- }
4002
- function findTemplatePath(templateName, explicitDir) {
4003
- for (const dir of candidateDirs(explicitDir)) {
4004
- const p = path4__default.default.join(dir, templateName);
4005
- if (!fs3__default.default.existsSync(p)) continue;
4006
- try {
4007
- fs3__default.default.accessSync(p, fs3__default.default.constants.R_OK);
4008
- return p;
4009
- } catch {
4010
- }
4011
- }
4012
- return null;
4013
- }
4014
- function resolveArtifactTemplate(templateName, opts) {
4015
- const p = findTemplatePath(templateName, opts?.templatesDir);
4016
- if (!p) return null;
4017
- try {
4018
- const raw = fs3__default.default.readFileSync(p, "utf-8");
4019
- return {
4020
- content: stripTemplateFrontmatter(raw),
4021
- sections: extractRequiredSections(raw) ?? extractBodySections(raw),
4022
- sourcePath: p
4023
- };
4024
- } catch {
4025
- return null;
4026
- }
4027
- }
4028
- function loadCurriculumTemplate(templateName, templatesDir) {
4029
- const p = findTemplatePath(templateName, templatesDir);
4030
- if (!p) return "";
4031
- try {
4032
- return fs3__default.default.readFileSync(p, "utf-8");
4033
- } catch {
4034
- return "";
4035
- }
4036
- }
4037
- function loadAuthoringTemplate(templateName, templatesDir) {
4038
- const p = findTemplatePath(templateName, templatesDir);
4039
- if (!p) return "";
4040
- try {
4041
- return stripTemplateFrontmatter(fs3__default.default.readFileSync(p, "utf-8"));
4042
- } catch {
4043
- return "";
4044
- }
4045
- }
4046
4183
 
4047
4184
  // src/ai/prompts/lessonMasterPrompt.ts
4185
+ init_templateLoader();
4048
4186
  function buildLessonMasterPrompt(input) {
4049
4187
  const { milestone, language, topic, targetAudience, contextContinuity, standardsContext } = input;
4050
4188
  const learningObjectives = milestone.learning_objectives || [];
@@ -4146,6 +4284,7 @@ Return a single valid JSON object matching the LessonPlanSchema with these exact
4146
4284
  }
4147
4285
 
4148
4286
  // src/ai/prompts/activityPrompt.ts
4287
+ init_templateLoader();
4149
4288
  function buildActivityPrompt(input) {
4150
4289
  const { lesson, language, hardwareBOM, softwareTools } = input;
4151
4290
  const loList = lesson.learningObjectives.map((lo) => `- [${lo.bloomLevel}] ${lo.code}: ${lo.name || ""} \u2014 ${lo.description}${lo.successCriteria ? ` (Evidence: ${lo.successCriteria})` : ""}`).join("\n");
@@ -4245,6 +4384,7 @@ Return a single JSON object matching ActivityLabSchema with these exact keys:
4245
4384
  }
4246
4385
 
4247
4386
  // src/ai/prompts/codeLabPrompt.ts
4387
+ init_templateLoader();
4248
4388
  function buildCodeLabPrompt(input) {
4249
4389
  const { lesson, activity, language, targetTechStack } = input;
4250
4390
  const techStackList = targetTechStack && targetTechStack.length > 0 ? targetTechStack : ["General Programming & Engineering"];
@@ -4934,6 +5074,7 @@ Please return a single JSON object matching these exact keys:
4934
5074
  }
4935
5075
 
4936
5076
  // src/ai/prompts/selfLabPrompt.ts
5077
+ init_templateLoader();
4937
5078
  function buildSelfLabPrompt(input) {
4938
5079
  const { milestone, projectGraph, language, targetTechStack, contextContinuity } = input;
4939
5080
  const techStackList = targetTechStack && targetTechStack.length > 0 ? targetTechStack : milestone.tech_keywords && milestone.tech_keywords.length > 0 ? milestone.tech_keywords : ["General Engineering"];
@@ -5042,6 +5183,7 @@ Return a single valid JSON object matching SelfLabSchema with these exact keys:
5042
5183
  }
5043
5184
 
5044
5185
  // src/ai/prompts/diagnosticQuizPrompt.ts
5186
+ init_templateLoader();
5045
5187
  function buildDiagnosticQuizPrompt(input) {
5046
5188
  const { milestone, language, questionCount = 4, passingScorePct = 80 } = input;
5047
5189
  const learningObjectives = milestone.learning_objectives || [];
@@ -6506,7 +6648,7 @@ ${cc.technicalDepth}`;
6506
6648
  const dataRows = cc.comparisonTable.rows.map((r) => `| ${r.join(" | ")} |`).join("\n");
6507
6649
  str += `
6508
6650
 
6509
- #### Comparison & Trade-off Analysis
6651
+ **Comparison & Trade-off Analysis**
6510
6652
  ${headerRow}
6511
6653
  ${dividerRow}
6512
6654
  ${dataRows}`;
@@ -6550,7 +6692,8 @@ ${learningObjectivesStr}
6550
6692
 
6551
6693
  ---
6552
6694
 
6553
- ## 1. ENGAGE (Hook & High-Stakes Scenario) \u2014 10-15 mins
6695
+ ## 1. ENGAGE (Hook & High-Stakes Scenario)
6696
+ > **Duration:** 10-15 mins
6554
6697
 
6555
6698
  ### Real-World Context: "${lesson.hookTitle || "The Incident"}"
6556
6699
  ${lesson.hookScenario}
@@ -6559,13 +6702,15 @@ ${lesson.hookQuestions && lesson.hookQuestions.length > 0 ? lesson.hookQuestions
6559
6702
 
6560
6703
  ---
6561
6704
 
6562
- ## 2. EXPLORE & EXPLAIN (Core Technical Concepts & Principles) \u2014 30-40 mins
6705
+ ## 2. EXPLORE & EXPLAIN (Core Technical Concepts & Principles)
6706
+ > **Duration:** 30-40 mins
6563
6707
 
6564
6708
  ${coreConceptsStr}
6565
6709
 
6566
6710
  ---
6567
6711
 
6568
- ## 3. GUIDED PRACTICE (Visual Architecture & Annotated Code) \u2014 20-25 mins
6712
+ ## 3. GUIDED PRACTICE (Visual Architecture & Annotated Code)
6713
+ > **Duration:** 20-25 mins
6569
6714
 
6570
6715
  ### System Architecture & Data Flow (Mermaid Diagram)
6571
6716
  ${mermaidBlock}
@@ -6579,7 +6724,8 @@ ${guidedPractice.codeSnippet}
6579
6724
 
6580
6725
  ---
6581
6726
 
6582
- ## 4. ELABORATE & INDEPENDENT PRACTICE (Technical Challenge & Scaffolding) \u2014 15-20 mins
6727
+ ## 4. ELABORATE & INDEPENDENT PRACTICE (Technical Challenge & Scaffolding)
6728
+ > **Duration:** 15-20 mins
6583
6729
 
6584
6730
  ### Engineering Challenge: "${lesson.independentPractice?.title || "System Optimization Challenge"}"
6585
6731
  ${lesson.independentPractice?.description || "Implement the core logic and test against simulated edge cases."}
@@ -6590,7 +6736,8 @@ ${lesson.independentPractice?.description || "Implement the core logic and test
6590
6736
 
6591
6737
  ---
6592
6738
 
6593
- ## 5. EVALUATE (Summary, Assessment & Metacognition) \u2014 5-10 mins
6739
+ ## 5. EVALUATE (Summary, Assessment & Metacognition)
6740
+ > **Duration:** 5-10 mins
6594
6741
 
6595
6742
  ### Exit Ticket (Bloom: Apply / Analyze)
6596
6743
  > **Scenario Question:** ${lesson.exitTicket.questionStem}
@@ -6683,12 +6830,16 @@ ${hardwareItems}
6683
6830
 
6684
6831
  ## [REQUIRED] Active Learning Workflow
6685
6832
 
6686
- ### Phase 1: Planning & Design \u2014 ${phase1.durationMinutes} mins
6833
+ ### Phase 1: Planning & Design
6834
+ > **Duration:** ${phase1.durationMinutes} mins
6835
+
6687
6836
  - **Student Task:** ${phase1.studentTask}
6688
6837
  - **Teacher Action:** ${phase1.teacherAction}
6689
6838
  ${phase1.inquiryQuestions.map((q) => ` - *Inquiry Question:* ${q}`).join("\n")}
6690
6839
 
6691
- ### Phase 2: Implementation & Discovery \u2014 ${phase2.durationMinutes} mins
6840
+ ### Phase 2: Implementation & Discovery
6841
+ > **Duration:** ${phase2.durationMinutes} mins
6842
+
6692
6843
  - **Student Task:** ${phase2.studentTask}
6693
6844
  - **Teacher Action:** ${phase2.teacherAction}
6694
6845
 
@@ -6696,7 +6847,9 @@ ${phase2.codeOrCommands.map((c) => `\`\`\`text
6696
6847
  ${c}
6697
6848
  \`\`\``).join("\n\n")}
6698
6849
 
6699
- ### Phase 3: Debugging & Testing \u2014 ${phase3.durationMinutes} mins
6850
+ ### Phase 3: Debugging & Testing
6851
+ > **Duration:** ${phase3.durationMinutes} mins
6852
+
6700
6853
  - **Testing:** ${phase3.testingSteps}
6701
6854
  - **Error Analysis:** ${phase3.errorAnalysis}
6702
6855
 
@@ -6751,22 +6904,28 @@ This lab focuses on the practical implementation of core technical concepts, bri
6751
6904
 
6752
6905
  ## \u{1F4DD} Lab Tasks (3-Tier Differentiation)
6753
6906
 
6754
- ### \u{1F949} Bronze (Foundational) \u2014 ${tasks.bronze.mins} mins
6907
+ ### \u{1F949} Bronze (Foundational)
6908
+ > **Duration:** ${tasks.bronze.mins} mins
6909
+
6755
6910
  **Objective:** ${tasks.bronze.objective}
6756
6911
  - **Task:** ${tasks.bronze.task}
6757
6912
  - **Requirement:** ${tasks.bronze.requirement}
6758
6913
 
6759
- ### \u{1F948} Silver (Application) \u2014 ${tasks.silver.mins} mins
6914
+ ### \u{1F948} Silver (Application)
6915
+ > **Duration:** ${tasks.silver.mins} mins
6916
+
6760
6917
  **Objective:** ${tasks.silver.objective}
6761
6918
  - **Task:** ${tasks.silver.task}
6762
6919
  - **Requirement:** ${tasks.silver.requirement}
6763
6920
 
6764
- ### \u{1F947} Gold (Enrichment/Creative) \u2014 ${tasks.gold.mins} mins
6921
+ ### \u{1F947} Gold (Enrichment/Creative)
6922
+ > **Duration:** ${tasks.gold.mins} mins
6923
+
6765
6924
  **Objective:** ${tasks.gold.objective}
6766
6925
  - **Task:** ${tasks.gold.task}
6767
6926
  - **Requirement:** ${tasks.gold.requirement}
6768
6927
 
6769
- ## \u{1F4BB} Technical Reference ([SME_MANDATE])
6928
+ ## \u{1F4BB} Technical Reference
6770
6929
  <!-- [SME_MANDATE] -->
6771
6930
  > Any code provided in this section is verified for syntax accuracy and runtime execution.
6772
6931
 
@@ -6904,7 +7063,9 @@ ${instruction.verificationChecklist.map((c) => `- [ ] ${c}`).join("\n")}
6904
7063
  function serializeSelfLabToMarkdown(lab) {
6905
7064
  const renderChallenge = (c, label) => {
6906
7065
  const tasksList = c.tasks.map((t) => `- ${t}`).join("\n");
6907
- return `### ${label}: ${c.title} \u2014 ~${c.estimatedMinutes} mins
7066
+ return `### ${label}: ${c.title}
7067
+ > **Duration:** ~${c.estimatedMinutes} mins
7068
+
6908
7069
  **Objective:** ${c.objective}
6909
7070
 
6910
7071
  #### Tasks & Implementation Details:
@@ -9267,6 +9428,7 @@ function gateModeFor(resolved, type) {
9267
9428
  }
9268
9429
 
9269
9430
  // src/services/languageDirective.ts
9431
+ init_templateLoader();
9270
9432
  function resolveTargetLanguageCode(language) {
9271
9433
  const raw = String(language || "vi").trim().toLowerCase();
9272
9434
  if (raw.startsWith("vi")) return "vi";
@@ -9307,137 +9469,58 @@ function matchesArtifactType(candidate, target) {
9307
9469
  if ((c === "EXT" || c === "EXTENSION") && (t === "EXT" || t === "EXTENSION")) return true;
9308
9470
  return false;
9309
9471
  }
9310
- var DEFAULT_VIETNAMESE_SECTION_HEADINGS = {
9311
- LEARNER_PROFILE: {
9312
- "Foundational Info": "Th\xF4ng tin N\u1EC1n t\u1EA3ng",
9313
- "Entry Level": "Tr\xECnh \u0111\u1ED9 \u0110\u1EA7u v\xE0o",
9314
- "Learning Context": "B\u1ED1i c\u1EA3nh H\u1ECDc t\u1EADp",
9315
- "Client Objectives": "M\u1EE5c ti\xEAu Kh\xE1ch h\xE0ng & Chu\u1EA9n \u0111\u1EA7u ra"
9316
- },
9317
- PROJECT_BRIEF: {
9318
- "Project Overview": "T\u1ED5ng quan D\u1EF1 \xE1n",
9319
- "Audience & Duration": "\u0110\u1ED1i t\u01B0\u1EE3ng & Th\u1EDDi l\u01B0\u1EE3ng",
9320
- "Learning Roadmap": "L\u1ED9 tr\xECnh H\u1ECDc t\u1EADp & C\u1ED9t m\u1ED1c",
9321
- "Artifact Scope": "Ph\u1EA1m vi S\u1EA3n ph\u1EA9m H\u1ECDc t\u1EADp"
9322
- },
9323
- CURRICULUM_FRAMEWORK: {
9324
- "Course Overview": "T\u1ED5ng quan Kh\xF3a h\u1ECDc",
9325
- "Global Learning Objectives": "M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n",
9326
- "Structural Hierarchy (Unit & Module)": "C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)",
9327
- "Scope & Sequence (Detailed Roadmap)": "Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt"
9328
- },
9329
- CONTENT_STYLE_GUIDE: {
9330
- "Voice & Tone": "Gi\u1ECDng v\u0103n & Phong c\xE1ch",
9331
- "Standard Terminology (Glossary)": "Thu\u1EADt ng\u1EEF Chu\u1EA9n (B\u1EA3ng thu\u1EADt ng\u1EEF)",
9332
- "Examples & Context Rules": "Quy t\u1EAFc V\xED d\u1EE5 & Ng\u1EEF c\u1EA3nh",
9333
- "Lesson Content Standards": "Ti\xEAu chu\u1EA9n N\u1ED9i dung B\xE0i h\u1ECDc"
9334
- },
9335
- SECTION_LANGUAGE_CONTRACT: {
9336
- "Metadata & Scope": "Th\xF4ng tin & Ph\u1EA1m vi H\u1EE3p \u0111\u1ED3ng",
9337
- "Section Translation Matrix": "Ma tr\u1EADn Ti\xEAu \u0111\u1EC1 \u0110a ng\xF4n ng\u1EEF",
9338
- "Validation Rules": "Quy t\u1EAFc X\xE1c th\u1EF1c & Kh\xF3a Chu\u1EA9n"
9339
- },
9340
- KNOWLEDGE_EXPOSITION: {
9341
- "Session Scope": "Ph\u1EA1m vi Bu\u1ED5i h\u1ECDc",
9342
- "Key Terms": "Thu\u1EADt ng\u1EEF Then ch\u1ED1t",
9343
- "Concept Narratives": "Di\u1EC5n gi\u1EA3i Kh\xE1i ni\u1EC7m",
9344
- "Worked Micro-Examples": "V\xED d\u1EE5 M\u1EABu Chi ti\u1EBFt",
9345
- "Common Mistakes": "L\u1ED7i Th\u01B0\u1EDDng g\u1EB7p & Kh\u1EAFc ph\u1EE5c",
9346
- "Self-Check Questions": "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra"
9347
- },
9348
- LESSON: {
9349
- "A. Lesson Design Plan": "A. K\u1EBF ho\u1EA1ch Thi\u1EBFt k\u1EBF B\xE0i h\u1ECDc",
9350
- "Learning Objectives & Evidence": "M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp & B\u1EB1ng ch\u1EE9ng N\u0103ng l\u1EF1c",
9351
- "Activity Sequence": "Chu\u1ED7i Ho\u1EA1t \u0111\u1ED9ng D\u1EA1y & H\u1ECDc",
9352
- "Resource Map": "B\u1EA3n \u0111\u1ED3 T\xE0i nguy\xEAn & H\u1ECDc li\u1EC7u",
9353
- "Assessment Map": "B\u1EA3n \u0111\u1ED3 \u0110\xE1nh gi\xE1 N\u0103ng l\u1EF1c",
9354
- "Artifact Contract": "H\u1EE3p \u0111\u1ED3ng S\u1EA3n ph\u1EA9m \u0110\u1EA7u ra",
9355
- "B. Lesson Flow": "B. Ti\u1EBFn tr\xECnh Gi\u1EA3ng d\u1EA1y Chi ti\u1EBFt"
9356
- },
9357
- ACT: {
9358
- "Computational Thinking Focus": "Tr\u1ECDng t\xE2m T\u01B0 duy M\xE1y t\xEDnh",
9359
- "Materials & Setup": "V\u1EADt li\u1EC7u & Chu\u1EA9n b\u1ECB Thi\u1EBFt b\u1ECB",
9360
- "Constraints & Safety": "R\xE0ng bu\u1ED9c & An to\xE0n Ph\xF2ng th\u1EF1c h\xE0nh",
9361
- "Active Learning Workflow": "Quy tr\xECnh Ho\u1EA1t \u0111\u1ED9ng Tr\u1EA3i nghi\u1EC7m",
9362
- "3-Tier Differentiation": "Ph\xE2n h\xF3a H\u1ECDc t\u1EADp 3 T\u1EA7ng",
9363
- "Reflection": "T\u1ED5ng k\u1EBFt & \u0110\xFAc k\u1EBFt Tr\u1EA3i nghi\u1EC7m"
9364
- },
9365
- CODE: {
9366
- "Technical Overview & Architecture Blueprint": "T\u1ED5ng quan K\u1EF9 thu\u1EADt & B\u1EA3n thi\u1EBFt k\u1EBF Ki\u1EBFn tr\xFAc",
9367
- "Hardware Pinout & Wiring Configuration Matrix": "S\u01A1 \u0111\u1ED3 Ch\xE2n & Ma tr\u1EADn N\u1ED1i d\xE2y Ph\u1EA7n c\u1EE9ng",
9368
- "Starter Code Sandbox": "M\xE3 ngu\u1ED3n Kh\u1EDFi \u0111\u1EA7u (Starter Code)",
9369
- "Verified Reference Solution Code": "M\xE3 ngu\u1ED3n L\u1EDDi gi\u1EA3i Chu\u1EA9n (Reference Solution)",
9370
- "Automated Test / Verification Script": "K\u1ECBch b\u1EA3n Ki\u1EC3m th\u1EED T\u1EF1 \u0111\u1ED9ng",
9371
- "Common Syntax & Runtime Pitfalls Matrix": "Ma tr\u1EADn L\u1ED7i C\xFA ph\xE1p & Th\u1EDDi gian ch\u1EA1y Th\u01B0\u1EDDng g\u1EB7p"
9372
- },
9373
- CODE_LAB: {
9374
- "Technical Overview & Architecture Blueprint": "T\u1ED5ng quan K\u1EF9 thu\u1EADt & B\u1EA3n thi\u1EBFt k\u1EBF Ki\u1EBFn tr\xFAc",
9375
- "Hardware Pinout & Wiring Configuration Matrix": "S\u01A1 \u0111\u1ED3 Ch\xE2n & Ma tr\u1EADn N\u1ED1i d\xE2y Ph\u1EA7n c\u1EE9ng",
9376
- "Starter Code Sandbox": "M\xE3 ngu\u1ED3n Kh\u1EDFi \u0111\u1EA7u (Starter Code)",
9377
- "Verified Reference Solution Code": "M\xE3 ngu\u1ED3n L\u1EDDi gi\u1EA3i Chu\u1EA9n (Reference Solution)",
9378
- "Automated Test / Verification Script": "K\u1ECBch b\u1EA3n Ki\u1EC3m th\u1EED T\u1EF1 \u0111\u1ED9ng",
9379
- "Common Syntax & Runtime Pitfalls Matrix": "Ma tr\u1EADn L\u1ED7i C\xFA ph\xE1p & Th\u1EDDi gian ch\u1EA1y Th\u01B0\u1EDDng g\u1EB7p"
9380
- },
9381
- QUIZ: {
9382
- "Assessment Objectives": "M\u1EE5c ti\xEAu \u0110\xE1nh gi\xE1",
9383
- "Multiple-Choice Question Bank": "Ng\xE2n h\xE0ng C\xE2u h\u1ECFi Tr\u1EAFc nghi\u1EC7m",
9384
- "Code Analysis / Debugging Question": "C\xE2u h\u1ECFi Ph\xE2n t\xEDch M\xE3 ngu\u1ED3n & S\u1EEDa l\u1ED7i",
9385
- "Competency Rubric": "Thang \u0111o \u0110\xE1nh gi\xE1 N\u0103ng l\u1EF1c (Rubric)"
9386
- },
9387
- GUIDE: {
9388
- "Objectives & Preparation": "M\u1EE5c ti\xEAu & Chu\u1EA9n b\u1ECB Gi\u1EA3ng d\u1EA1y",
9389
- "Preparation Checklist & Workstation Setup": "Danh m\u1EE5c Ki\u1EC3m tra Chu\u1EA9n b\u1ECB & Tr\u1EA1m Th\u1EF1c h\xE0nh",
9390
- "Facilitation Script & Timeline": "K\u1ECBch b\u1EA3n \u0110i\u1EC1u ph\u1ED1i & Khung Th\u1EDDi gian",
9391
- "Common Misconceptions & Diagnostic Remediation Matrix": "Quan ni\u1EC7m Sai l\u1EA7m Th\u01B0\u1EDDng g\u1EB7p & Ma tr\u1EADn Kh\u1EAFc ph\u1EE5c",
9392
- "Differentiated Support Strategies": "Chi\u1EBFn l\u01B0\u1EE3c H\u1ED7 tr\u1EE3 Ph\xE2n h\xF3a"
9393
- },
9394
- HANDOUT: {
9395
- "Core Concepts": "Kh\xE1i ni\u1EC7m C\u1ED1t l\xF5i",
9396
- "Core Syntax & Mechanism Cheat Sheet": "B\u1EA3ng Tra c\u1EE9u C\xFA ph\xE1p & C\u01A1 ch\u1EBF Tr\u1ECDng t\xE2m",
9397
- "Visual Mental Model / Architecture Diagram": "M\xF4 h\xECnh T\u01B0 duy Tr\u1EF1c quan / S\u01A1 \u0111\u1ED3 Ki\u1EBFn tr\xFAc",
9398
- "Step-by-Step Practical Quick-Start Guide": "H\u01B0\u1EDBng d\u1EABn Th\u1EF1c h\xE0nh T\u1EEBng b\u01B0\u1EDBc",
9399
- "Self-Check Diagnostic Checklist": "B\u1EA3ng Ki\u1EC3m tra T\u1EF1 ch\u1EA9n \u0111o\xE1n"
9400
- },
9401
- WKS: {
9402
- "Part 1: Knowledge Check": "Ph\u1EA7n 1: Ki\u1EC3m tra Ki\u1EBFn th\u1EE9c",
9403
- "Part 2: Concept Tracing & Diagram Fill-in": "Ph\u1EA7n 2: L\u1EA7n v\u1EBFt Kh\xE1i ni\u1EC7m & \u0110i\u1EC1n S\u01A1 \u0111\u1ED3",
9404
- "Part 3: Code Analysis & Bug Hunting Challenge": "Ph\u1EA7n 3: Ph\xE2n t\xEDch M\xE3 & Th\u1EED th\xE1ch S\u0103n l\u1ED7i",
9405
- "Part 4: Synthesis & Problem-Solving Application": "Ph\u1EA7n 4: T\u1ED5ng h\u1EE3p & \u1EE8ng d\u1EE5ng Gi\u1EA3i quy\u1EBFt V\u1EA5n \u0111\u1EC1",
9406
- "Part 5: Self-Reflection & Learning Log": "Ph\u1EA7n 5: T\u1EF1 suy ng\u1EABm & Nh\u1EADt k\xFD H\u1ECDc t\u1EADp"
9407
- },
9408
- EXT: {
9409
- "For: Advanced Students / Extra Time": "D\xE0nh cho: H\u1ECDc sinh N\xE2ng cao / Ho\xE0n th\xE0nh S\u1EDBm",
9410
- "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",
9411
- "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",
9412
- "Challenge 3: Extension Milestones": "Th\u1EED th\xE1ch 3: C\xE1c M\u1ED1c Nhi\u1EC7m v\u1EE5 N\xE2ng cao",
9413
- "Metacognitive Deep Dive & Engineering Trade-offs": "Suy ng\u1EABm Si\xEAu nh\u1EADn th\u1EE9c & \u0110\xE1nh \u0111\u1ED5i K\u1EF9 thu\u1EADt"
9414
- },
9415
- SLIDE: {
9416
- "Title & Agenda": "Ti\xEAu \u0111\u1EC1 & M\u1EE5c l\u1EE5c",
9417
- "Core Concepts": "Kh\xE1i ni\u1EC7m C\u1ED1t l\xF5i",
9418
- "Guided Demonstration": "H\u01B0\u1EDBng d\u1EABn Th\u1EF1c h\xE0nh & Th\u1ECB ph\u1EA1m",
9419
- "Hands-on Mission": "Nhi\u1EC7m v\u1EE5 Th\u1EF1c h\xE0nh Tr\u1EA3i nghi\u1EC7m",
9420
- "Summary & Next Steps": "T\u1ED5ng k\u1EBFt & \u0110\u1ECBnh h\u01B0\u1EDBng Ti\u1EBFp theo"
9421
- }
9472
+ var TEMPLATE_BY_TYPE = {
9473
+ LESSON: "LESSON_5E_template.md",
9474
+ LESSON_5E: "LESSON_5E_template.md",
9475
+ LESSON_EDP: "LESSON_EDP_template.md",
9476
+ LESSON_GENERAL: "LESSON_GENERAL_template.md",
9477
+ ACT: "ACT_template.md",
9478
+ CODE: "CODE_template.md",
9479
+ // JSON-first code lab shares the CODE label set (the serializer renders its
9480
+ // own headings; this lookup only feeds the SLC/directive fallback path).
9481
+ CODE_LAB: "CODE_template.md",
9482
+ QUIZ: "QUIZ_v4.6_template.md",
9483
+ GUIDE: "GUIDE_template.md",
9484
+ HANDOUT: "HANDOUT_template.md",
9485
+ WKS: "WKS_template.md",
9486
+ EXT: "EXT_template.md",
9487
+ SLIDE: "SLIDE_template.md",
9488
+ KNOWLEDGE_EXPOSITION: "KNOWLEDGE_EXPOSITION_template.md",
9489
+ LEARNER_PROFILE: "LEARNER_PROFILE_template.md",
9490
+ PROJECT_BRIEF: "PROJECT_BRIEF_template.md",
9491
+ CURRICULUM_FRAMEWORK: "CURRICULUM_FRAMEWORK_template.md",
9492
+ CONTENT_STYLE_GUIDE: "CONTENT_STYLE_GUIDE_template.md",
9493
+ SECTION_LANGUAGE_CONTRACT: "SECTION_LANGUAGE_CONTRACT_template.md"
9422
9494
  };
9495
+ function keepContractEntries(artifactType, mapping) {
9496
+ const templateFile = TEMPLATE_BY_TYPE[artifactType.trim().toUpperCase()];
9497
+ if (!templateFile) return mapping;
9498
+ const resolved = resolveArtifactTemplate(templateFile);
9499
+ if (!resolved) return mapping;
9500
+ const contract = new Set(resolved.sections.map(stripHeadingKey));
9501
+ const kept = {};
9502
+ for (const [k, v] of Object.entries(mapping)) {
9503
+ if (contract.has(stripHeadingKey(k))) kept[k] = v;
9504
+ }
9505
+ return kept;
9506
+ }
9423
9507
  function extractSectionHeadingsFromSLC(slcInput, artifactType, targetLanguage = "vi") {
9424
9508
  const upperType = artifactType.trim().toUpperCase();
9425
9509
  const getFallback = () => {
9426
- if (resolveTargetLanguageCode(targetLanguage) === "vi") {
9427
- for (const [key, mapping] of Object.entries(DEFAULT_VIETNAMESE_SECTION_HEADINGS)) {
9428
- if (matchesArtifactType(key, upperType)) {
9429
- return mapping;
9430
- }
9431
- }
9432
- }
9433
- return {};
9510
+ const templateFile = TEMPLATE_BY_TYPE[upperType];
9511
+ if (!templateFile) return {};
9512
+ const resolved = resolveArtifactTemplate(templateFile);
9513
+ if (!resolved) return {};
9514
+ return Object.fromEntries(resolved.sections.map((s) => [s, s]));
9434
9515
  };
9435
9516
  if (!slcInput) return getFallback();
9436
9517
  if (typeof slcInput === "object") {
9437
- if (slcInput[upperType] && Object.keys(slcInput[upperType]).length > 0) return slcInput[upperType];
9518
+ if (slcInput[upperType] && Object.keys(slcInput[upperType]).length > 0) {
9519
+ return keepContractEntries(upperType, slcInput[upperType]);
9520
+ }
9438
9521
  for (const [key, mapping] of Object.entries(slcInput)) {
9439
9522
  if (matchesArtifactType(key, upperType) && Object.keys(mapping).length > 0) {
9440
- return mapping;
9523
+ return keepContractEntries(upperType, mapping);
9441
9524
  }
9442
9525
  }
9443
9526
  return getFallback();
@@ -9465,24 +9548,33 @@ function extractSectionHeadingsFromSLC(slcInput, artifactType, targetLanguage =
9465
9548
  }
9466
9549
  }
9467
9550
  }
9468
- if (Object.keys(mapping).length > 0) return mapping;
9551
+ if (Object.keys(mapping).length > 0) return keepContractEntries(upperType, mapping);
9469
9552
  }
9470
9553
  }
9471
9554
  return getFallback();
9472
9555
  }
9473
- function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
9474
- const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType, targetLanguage);
9556
+ function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi", opts) {
9557
+ const upperType = artifactType.trim().toUpperCase();
9558
+ const templateFile = TEMPLATE_BY_TYPE[upperType];
9559
+ const resolved = templateFile ? resolveArtifactTemplate(templateFile, opts) : null;
9560
+ const baked = !!resolved && resolved.declaredLang !== "en";
9561
+ const headings = resolved ? Object.fromEntries(
9562
+ resolved.sections.map((s) => [
9563
+ s,
9564
+ baked ? s : extractSectionHeadingsFromSLC(slcMarkdown, upperType, targetLanguage)[s] || s
9565
+ ])
9566
+ ) : extractSectionHeadingsFromSLC(baked ? null : slcMarkdown, upperType, targetLanguage);
9475
9567
  if (Object.keys(headings).length === 0) {
9476
9568
  return "";
9477
9569
  }
9478
9570
  const lines = Object.entries(headings).map(
9479
- ([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
9571
+ ([heading, display]) => heading === display ? ` - "${display}"` : ` - "${heading}" -> Use Display Heading: "${display}"`
9480
9572
  );
9481
- return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
9482
- - The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
9483
- - In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
9573
+ return `MANDATORY SECTION HEADINGS (SOURCE: RESOLVED ARTIFACT TEMPLATE):
9574
+ - Copy the template's YAML frontmatter \`template_required_sections\` list VERBATIM into the artifact frontmatter \u2014 never translate, reorder, or invent entries.
9575
+ - In the markdown body, section headers MUST be exactly the Display Headings below (do NOT translate, paraphrase, or re-marker them):
9484
9576
  ${lines.join("\n")}
9485
- - For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
9577
+ - Example: if the resolved template's heading is "${Object.values(headings)[0]}", your body heading must be exactly "## ${Object.values(headings)[0]}".`;
9486
9578
  }
9487
9579
 
9488
9580
  // src/services/curriculumHorizon.ts
@@ -9866,7 +9958,7 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
9866
9958
  if (s.canonicalKey && !byCanonical.has(s.canonicalKey)) {
9867
9959
  byCanonical.set(s.canonicalKey, s);
9868
9960
  }
9869
- const norm2 = normalizeHeading2(s.cleanTitle);
9961
+ const norm2 = normalizeHeading(s.cleanTitle);
9870
9962
  if (!byCleanTitle.has(norm2)) {
9871
9963
  byCleanTitle.set(norm2, s);
9872
9964
  }
@@ -9916,7 +10008,7 @@ ${truncated}`;
9916
10008
  blocks.push(metaBlock);
9917
10009
  let sectionAware = false;
9918
10010
  for (const key of priorities) {
9919
- const sec = byCanonical.get(key) || byCleanTitle.get(normalizeHeading2(key));
10011
+ const sec = byCanonical.get(key) || byCleanTitle.get(normalizeHeading(key));
9920
10012
  if (!sec) continue;
9921
10013
  sectionAware = true;
9922
10014
  if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
@@ -9998,15 +10090,15 @@ function parseMarkdownSections(markdown, slcMap, artifactType) {
9998
10090
  const maps = artifactType && slcMap[artifactType] ? [slcMap[artifactType]] : Object.values(slcMap);
9999
10091
  for (const m of maps) {
10000
10092
  for (const [canonical, localized] of Object.entries(m)) {
10001
- reverseMap.set(normalizeHeading2(localized), canonical);
10002
- reverseMap.set(normalizeHeading2(canonical), canonical);
10093
+ reverseMap.set(normalizeHeading(localized), canonical);
10094
+ reverseMap.set(normalizeHeading(canonical), canonical);
10003
10095
  }
10004
10096
  }
10005
10097
  }
10006
10098
  const flush = () => {
10007
10099
  if (!currentHeading) return;
10008
10100
  const cleanTitle = currentHeading.replace(/^#{1,4}\s+/, "").trim();
10009
- const norm2 = normalizeHeading2(cleanTitle);
10101
+ const norm2 = normalizeHeading(cleanTitle);
10010
10102
  const canonicalKey = reverseMap.get(norm2) || findCanonicalFuzzy(norm2);
10011
10103
  sections.push({
10012
10104
  rawHeading: currentHeading,
@@ -10030,7 +10122,7 @@ function parseMarkdownSections(markdown, slcMap, artifactType) {
10030
10122
  flush();
10031
10123
  return sections;
10032
10124
  }
10033
- function normalizeHeading2(str) {
10125
+ function normalizeHeading(str) {
10034
10126
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
10035
10127
  }
10036
10128
  function findCanonicalFuzzy(norm2) {
@@ -10336,14 +10428,13 @@ function buildSystemPrompt(targetLanguage = "vi", techStack, hardwarePlatform) {
10336
10428
  function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown, refPack) {
10337
10429
  const depthLines = session.depth_assignments.map((d) => "- " + d.node_id + " [" + d.depth.toUpperCase() + "]: " + DEPTH_RULES[d.depth]).join("\n");
10338
10430
  const termLines = glossary.map((g) => "- " + g.term + (g.definition ? " \u2014 " + g.definition : "") + (g.example ? " (example: " + g.example + ")" : "")).join("\n");
10339
- const langCode = resolveTargetLanguageCode(targetLanguage);
10340
- const headings = extractSectionHeadingsFromSLC(slcMarkdown, "KNOWLEDGE_EXPOSITION");
10341
- const hScope = headings["Session Scope"] || (langCode === "vi" ? "Ph\u1EA1m vi Bu\u1ED5i h\u1ECDc" : "Session Scope");
10342
- const hTerms = headings["Key Terms"] || (langCode === "vi" ? "Thu\u1EADt ng\u1EEF Then ch\u1ED1t" : "Key Terms");
10343
- const hNarratives = headings["Concept Narratives"] || (langCode === "vi" ? "Di\u1EC5n gi\u1EA3i Kh\xE1i ni\u1EC7m" : "Concept Narratives");
10344
- const hExamples = headings["Worked Micro-Examples"] || (langCode === "vi" ? "V\xED d\u1EE5 M\u1EABu Chi ti\u1EBFt" : "Worked Micro-Examples");
10345
- const hMistakes = headings["Common Mistakes"] || (langCode === "vi" ? "L\u1ED7i Th\u01B0\u1EDDng g\u1EB7p & Kh\u1EAFc ph\u1EE5c" : "Common Mistakes");
10346
- const hQuestions = headings["Self-Check Questions"] || (langCode === "vi" ? "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra" : "Self-Check Questions");
10431
+ const headings = extractSectionHeadingsFromSLC(slcMarkdown, "KNOWLEDGE_EXPOSITION", targetLanguage);
10432
+ const hScope = headings["Session Scope"] || "Session Scope";
10433
+ const hTerms = headings["Key Terms"] || "Key Terms";
10434
+ const hNarratives = headings["Concept Narratives"] || "Concept Narratives";
10435
+ const hExamples = headings["Worked Micro-Examples"] || "Worked Micro-Examples";
10436
+ const hMistakes = headings["Common Mistakes"] || "Common Mistakes";
10437
+ const hQuestions = headings["Self-Check Questions"] || "Self-Check Questions";
10347
10438
  const skeleton = `## ${hScope} / ## ${hTerms} / ## ${hNarratives} / ## ${hExamples} / ## ${hMistakes} / ## ${hQuestions}`;
10348
10439
  const headingDirective = buildHeadingDirective("KNOWLEDGE_EXPOSITION", slcMarkdown, targetLanguage);
10349
10440
  let refPackBlock = "";
@@ -25101,6 +25192,7 @@ function evaluateActivityAlignment(canonicalExcerpt, generatedContent, now = /*
25101
25192
  }
25102
25193
 
25103
25194
  // src/services/lessonProductionService.ts
25195
+ init_templateLoader();
25104
25196
  var LESSON_TEMPLATE_BY_PEDAGOGY = {
25105
25197
  edp: "LESSON_EDP_template.md",
25106
25198
  general: "LESSON_GENERAL_template.md",
@@ -29036,6 +29128,7 @@ function isTranslationDue(target, context) {
29036
29128
  }
29037
29129
 
29038
29130
  // src/services/index.ts
29131
+ init_templateLoader();
29039
29132
  init_slideProductionWorkflow();
29040
29133
  var LocalWorkspaceManager = class {
29041
29134
  baseDir;
@@ -31612,7 +31705,6 @@ exports.DEFAULT_LESSON_PRIORITIES = DEFAULT_LESSON_PRIORITIES;
31612
31705
  exports.DEFAULT_OVERHEAD_RATIO = DEFAULT_OVERHEAD_RATIO;
31613
31706
  exports.DEFAULT_STREAM_IDLE_MS = DEFAULT_STREAM_IDLE_MS;
31614
31707
  exports.DEFAULT_STREAM_TOTAL_MS = DEFAULT_STREAM_TOTAL_MS;
31615
- exports.DEFAULT_VIETNAMESE_SECTION_HEADINGS = DEFAULT_VIETNAMESE_SECTION_HEADINGS;
31616
31708
  exports.DependencyEdgeSchema = DependencyEdgeSchema;
31617
31709
  exports.DepthAssignmentSchema = DepthAssignmentSchema;
31618
31710
  exports.DepthLevelSchema = DepthLevelSchema;
@@ -31752,6 +31844,7 @@ exports.ZpdVerdictSchema = ZpdVerdictSchema;
31752
31844
  exports.activityTools = activityTools;
31753
31845
  exports.analystTools = analystTools;
31754
31846
  exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
31847
+ exports.assembleTranslatedTemplate = assembleTranslatedTemplate;
31755
31848
  exports.assertAcyclic = assertAcyclic;
31756
31849
  exports.assessorTools = assessorTools;
31757
31850
  exports.assignDepths = assignDepths;
@@ -31828,8 +31921,10 @@ exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
31828
31921
  exports.expositionCacheKey = expositionCacheKey;
31829
31922
  exports.extractActivityContractRows = extractActivityContractRows;
31830
31923
  exports.extractAssessmentMap = extractAssessmentMap;
31831
- exports.extractBodySections = extractBodySections;
31924
+ exports.extractConditionalSections = extractConditionalSections;
31832
31925
  exports.extractCurriculumHorizon = extractCurriculumHorizon;
31926
+ exports.extractDeclaredLanguage = extractDeclaredLanguage;
31927
+ exports.extractEchoSections = extractEchoSections;
31833
31928
  exports.extractJsonArray = extractJsonArray;
31834
31929
  exports.extractRequiredSections = extractRequiredSections;
31835
31930
  exports.extractScopeSequenceRows = extractScopeSequenceRows;
@@ -31934,11 +32029,13 @@ exports.streamLLMWithFallback = streamLLMWithFallback;
31934
32029
  exports.streamLayerPrefillWithFallback = streamLayerPrefillWithFallback;
31935
32030
  exports.streamLessonMasterFlow = streamLessonMasterFlow;
31936
32031
  exports.streamSelfLabFlow = streamSelfLabFlow;
32032
+ exports.stripHeadingKey = stripHeadingKey;
31937
32033
  exports.stripLlmJsonWrappers = stripLlmJsonWrappers;
31938
32034
  exports.stripTemplateFrontmatter = stripTemplateFrontmatter;
31939
32035
  exports.targetLanguageDisplayName = targetLanguageDisplayName;
31940
32036
  exports.techSmeTools = techSmeTools;
31941
32037
  exports.topoSort = topoSort;
32038
+ exports.translateTemplate = translateTemplate;
31942
32039
  exports.uploadAssetToBucket = uploadAssetToBucket;
31943
32040
  exports.validateArtifactDependencies = validateArtifactDependencies;
31944
32041
  exports.validateCurriculumPlan = validateCurriculumPlan;