@thanh01.pmt/curriculum-kit 1.4.36 → 1.4.38

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
@@ -1532,6 +1532,8 @@ function parseLessonFlow(lessonMarkdown) {
1532
1532
  purpose: cells[4] || "",
1533
1533
  studentAction: cells[5] || "",
1534
1534
  teacherMove: cells[6] || "",
1535
+ outputEvidence: cells[7] || "",
1536
+ lo: (cells[8] || "").replace(/\*/g, "").split(/[,;/\s]+/).map((s) => s.trim()).filter(Boolean),
1535
1537
  time: cells[9] || cells[cells.length - 2] || "",
1536
1538
  artifactContract: cells[cells.length - 1] || ""
1537
1539
  });
@@ -26448,6 +26450,195 @@ function extractStandardRefs(text) {
26448
26450
  return Array.from(new Set(matches));
26449
26451
  }
26450
26452
 
26453
+ // src/parsers/lessonEntity.ts
26454
+ init_lessonFlowParser();
26455
+ var LessonEntitySchema = zod.z.object({
26456
+ lessonId: zod.z.string(),
26457
+ title: zod.z.string(),
26458
+ pedagogyModel: zod.z.string().default("5e"),
26459
+ estimatedDuration: zod.z.string().default(""),
26460
+ learningObjectives: zod.z.array(LearningObjectiveRowSchema).default([]),
26461
+ activitySequence: zod.z.array(ActivitySeqRowSchema).default([]),
26462
+ /** Header→cell rows straight from the A4 Assessment Map table (headers vary by template). */
26463
+ assessmentMap: zod.z.array(zod.z.record(zod.z.string())).default([]),
26464
+ tieredScaffolding: zod.z.object({ bronze: zod.z.string().default(""), silver: zod.z.string().default(""), gold: zod.z.string().default("") }).default({ bronze: "", silver: "", gold: "" })
26465
+ });
26466
+ var stripMd = (s) => s.replace(/\*\*/g, "").replace(/`/g, "").trim();
26467
+ function splitRow(line) {
26468
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
26469
+ }
26470
+ function toActivitySeqRows(md) {
26471
+ const flow = parseLessonFlow(md);
26472
+ const rows = [];
26473
+ for (const a of flow.activities) {
26474
+ const parsed = ActivitySeqRowSchema.safeParse({
26475
+ seq: a.seq,
26476
+ phase: a.phase,
26477
+ activityType: a.activityType,
26478
+ actor: a.actor,
26479
+ purpose: a.purpose,
26480
+ studentAction: a.studentAction,
26481
+ teacherMove: a.teacherMove,
26482
+ outputEvidence: a.outputEvidence,
26483
+ lo: a.lo,
26484
+ durationMinutes: parseInt((a.time || "").replace(/[^0-9]/g, ""), 10) || 10,
26485
+ artifactContract: a.artifactContract
26486
+ });
26487
+ if (parsed.success) rows.push(parsed.data);
26488
+ }
26489
+ return rows;
26490
+ }
26491
+ function parseLearningObjectives(md) {
26492
+ const rows = [];
26493
+ let inLO = false;
26494
+ let headerSeen = false;
26495
+ for (const line of md.split("\n")) {
26496
+ const h = line.match(/^#{2,4}\s+(.*)$/);
26497
+ if (h) {
26498
+ if (/learning\s+objectives/i.test(h[1] ?? "")) inLO = true;
26499
+ else if (inLO) break;
26500
+ continue;
26501
+ }
26502
+ if (!inLO) continue;
26503
+ const t = line.trim();
26504
+ if (!t.startsWith("|")) continue;
26505
+ if (/^[\s|:-]+$/.test(t)) continue;
26506
+ const cells = splitRow(t);
26507
+ if (cells.length < 4) continue;
26508
+ if (!headerSeen) {
26509
+ headerSeen = true;
26510
+ continue;
26511
+ }
26512
+ const parsed = LearningObjectiveRowSchema.safeParse({
26513
+ code: stripMd(cells[0] ?? ""),
26514
+ objective: stripMd(cells[1] ?? ""),
26515
+ evidence: stripMd(cells[2] ?? ""),
26516
+ successCriteria: stripMd(cells[3] ?? ""),
26517
+ standardRefs: cells[4] ?? "" ? cells[4].split(/[\s,;]+/).filter(Boolean) : [],
26518
+ conceptRefs: []
26519
+ });
26520
+ if (parsed.success) rows.push(parsed.data);
26521
+ }
26522
+ return rows;
26523
+ }
26524
+ function parseAssessmentMap(md) {
26525
+ const rows = [];
26526
+ let inMap = false;
26527
+ let headers = [];
26528
+ for (const line of md.split("\n")) {
26529
+ const h = line.match(/^#{2,4}\s+(.*)$/);
26530
+ if (h) {
26531
+ if (/assessment\s*map/i.test(h[1] ?? "")) inMap = true;
26532
+ else if (inMap) break;
26533
+ continue;
26534
+ }
26535
+ if (!inMap) continue;
26536
+ const t = line.trim();
26537
+ if (!t.startsWith("|")) continue;
26538
+ if (/^[\s|:-]+$/.test(t)) continue;
26539
+ const cells = splitRow(t);
26540
+ if (headers.length === 0) {
26541
+ headers = cells;
26542
+ continue;
26543
+ }
26544
+ const row = {};
26545
+ headers.forEach((hdr, i) => {
26546
+ row[hdr] = stripMd(cells[i] ?? "");
26547
+ });
26548
+ rows.push(row);
26549
+ }
26550
+ return rows;
26551
+ }
26552
+ function parseTieredScaffolding(md) {
26553
+ const lines = md.split("\n");
26554
+ let capture = null;
26555
+ for (const line of lines) {
26556
+ const h = line.match(/^#{2,4}\s+(.*)$/);
26557
+ if (h) {
26558
+ const isTier = /elaborate|tiered|scaffold/i.test(h[1] ?? "");
26559
+ if (capture && !isTier) break;
26560
+ if (isTier) capture = [];
26561
+ continue;
26562
+ }
26563
+ if (capture) capture.push(line);
26564
+ }
26565
+ const body = (capture ?? []).join("\n");
26566
+ const grab = (label) => {
26567
+ const m = body.match(new RegExp(`(?:\\ud83e\\udd49|\\ud83e\\udd48|\\ud83e\\udd47)?\\s*\\*\\*${label}:\\*\\*\\s*([^\\n]+)`, "i"));
26568
+ return m ? m[1].trim() : "";
26569
+ };
26570
+ return { bronze: grab("Bronze"), silver: grab("Silver"), gold: grab("Gold") };
26571
+ }
26572
+ function buildLessonEntityJson(lessonMarkdown) {
26573
+ const md = lessonMarkdown || "";
26574
+ if (!md.trim()) return null;
26575
+ const flow = parseLessonFlow(md);
26576
+ const fm = md.match(/^---\s*\n([\s\S]*?)\n---/)?.[1] ?? "";
26577
+ const activitySequence = toActivitySeqRows(md);
26578
+ const learningObjectives = parseLearningObjectives(md);
26579
+ if (activitySequence.length === 0 && learningObjectives.length === 0) return null;
26580
+ const parsed = LessonEntitySchema.safeParse({
26581
+ lessonId: flow.lessonId || (fm.match(/id:\s*["']?([^"'\n]+)["']?/i)?.[1]?.trim() ?? ""),
26582
+ title: flow.lessonTitle,
26583
+ pedagogyModel: flow.pedagogicalModel,
26584
+ estimatedDuration: flow.estimatedDuration,
26585
+ learningObjectives,
26586
+ activitySequence,
26587
+ assessmentMap: parseAssessmentMap(md),
26588
+ tieredScaffolding: parseTieredScaffolding(md)
26589
+ });
26590
+ return parsed.success ? parsed.data : null;
26591
+ }
26592
+ function renderEntitySlotsForType(entity, artifactType, maxChars = 8e3) {
26593
+ const type = (artifactType || "").toUpperCase().trim();
26594
+ const parts = [];
26595
+ const wantActivities = ["ACT", "GUIDE", "SLIDE", "WKS", "QUIZ"].includes(type);
26596
+ const wantObjectives = type !== "CODE";
26597
+ const wantAssessment = ["QUIZ", "WKS"].includes(type);
26598
+ const wantTiers = ["ACT", "CODE", "EXT", "WKS"].includes(type);
26599
+ if (wantObjectives && entity.learningObjectives.length > 0) {
26600
+ parts.push(
26601
+ "### Learning Objectives & Evidence\n" + entity.learningObjectives.map((o) => `- ${o.code}: ${o.objective} \u2192 Evidence: ${o.evidence} | Pass: ${o.successCriteria}`).join("\n")
26602
+ );
26603
+ }
26604
+ if (wantActivities && entity.activitySequence.length > 0) {
26605
+ const header = "| Seq | Phase | Activity | Actor | Student Action | Teacher Move | Output/Evidence | LO | Mins | Artifact Contract |";
26606
+ const rows = [];
26607
+ for (const a of entity.activitySequence) {
26608
+ const r = `| ${a.seq} | ${a.phase} | ${a.activityType} | ${a.actor} | ${a.studentAction} | ${a.teacherMove} | ${a.outputEvidence} | ${a.lo.join(",")} | ${a.durationMinutes} | ${a.artifactContract} |`;
26609
+ if (parts.join("\n\n").length + r.length > maxChars) break;
26610
+ rows.push(r);
26611
+ }
26612
+ if (rows.length > 0) {
26613
+ parts.push("### Activity Sequence\n" + [header, "|---|---|---|---|---|---|---|---|---|---|", ...rows].join("\n"));
26614
+ }
26615
+ }
26616
+ if (wantAssessment && entity.assessmentMap.length > 0) {
26617
+ const lines = entity.assessmentMap.map((r) => Object.entries(r).map(([k, v]) => `${k}: ${v}`).join(" | "));
26618
+ parts.push("### Assessment Map (assess exactly these)\n" + lines.join("\n"));
26619
+ }
26620
+ const t = entity.tieredScaffolding;
26621
+ if (wantTiers && (t.bronze || t.silver || t.gold)) {
26622
+ parts.push(
26623
+ "### Tiered task ladder\n" + [t.bronze && `- Bronze: ${t.bronze}`, t.silver && `- Silver: ${t.silver}`, t.gold && `- Gold: ${t.gold}`].filter(Boolean).join("\n")
26624
+ );
26625
+ }
26626
+ return parts.join("\n\n").slice(0, maxChars);
26627
+ }
26628
+ function entityRowAssignedTo(row, artifactType) {
26629
+ const norm2 = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
26630
+ const wanted = norm2(artifactType);
26631
+ return row.artifactContract.split(/[,;/]/).map((c) => norm2(c)).some((c) => c === wanted || c.length > 3 && (c.includes(wanted) || wanted.includes(c)));
26632
+ }
26633
+ function renderAssignedRowsTable(entity, artifactType, maxChars = 1500) {
26634
+ const rows = entity.activitySequence.filter((r) => entityRowAssignedTo(r, artifactType));
26635
+ if (rows.length === 0) return "";
26636
+ const header = "| Seq | Phase | Activity | Actor | Student Action | Teacher Move | Output/Evidence | LO | Mins | Artifact Contract |";
26637
+ const sep = "|---|---|---|---|---|---|---|---|---|---|";
26638
+ const body = rows.map((a) => `| ${a.seq} | ${a.phase} | ${a.activityType} | ${a.actor} | ${a.studentAction} | ${a.teacherMove} | ${a.outputEvidence} | ${a.lo.join(",")} | ${a.durationMinutes} | ${a.artifactContract} |`);
26639
+ return [header, sep, ...body].join("\n").slice(0, maxChars);
26640
+ }
26641
+
26451
26642
  // src/services/contextSlots.ts
26452
26643
  var LESSON_SLOT_BUDGETS = {
26453
26644
  ACT: { budget: 8e3 },
@@ -26567,6 +26758,7 @@ function buildSatelliteContext(input) {
26567
26758
  slcMarkdown,
26568
26759
  symbolLedgerBlock,
26569
26760
  pedagogyLabel,
26761
+ lessonEntity,
26570
26762
  mode = "legacy"
26571
26763
  } = input;
26572
26764
  const type = (artifactType || "").toUpperCase().trim();
@@ -26584,13 +26776,16 @@ ${lessonExcerpt.excerpt}${symbolLedgerBlock}`;
26584
26776
  verified: lessonExcerpt.verified,
26585
26777
  sectionAware: lessonExcerpt.sectionAware,
26586
26778
  issues: lessonExcerpt.issues,
26587
- tokenEstimate: Math.round(context2.length / 4)
26779
+ tokenEstimate: Math.round(context2.length / 4),
26780
+ canonicalExcerpt: lessonExcerpt.excerpt
26588
26781
  };
26589
26782
  }
26590
26783
  const blocks = [];
26591
26784
  const issues = [];
26592
26785
  const parts = [commonContext];
26593
26786
  blocks.push(blockMeta("commonContext", "COMPOSITE", commonContext, true));
26787
+ let excerptSlot;
26788
+ let miniSlot;
26594
26789
  const compositeHasKx = /###\s+KNOWLEDGE_EXPOSITION/.test(commonContext);
26595
26790
  if (expositionContent.trim() && !compositeHasKx) {
26596
26791
  const kxExcerpt = buildSectionAwareExcerpt(expositionContent, {
@@ -26608,28 +26803,57 @@ ${kxExcerpt.excerpt}`);
26608
26803
  }
26609
26804
  if (!KX_ONLY_TYPES.has(type)) {
26610
26805
  const spec = LESSON_SLOT_BUDGETS[type] ?? { budget: 12e3 };
26611
- const excerpt = buildSectionAwareExcerpt(lessonContent, {
26612
- priorities: spec.priorities ?? ["Artifact Contract", "A. Lesson Design Plan", "B. Lesson Flow", "Learning Objectives & Evidence", "Activity Sequence"],
26613
- budget: spec.budget,
26614
- sectionLanguageContract: slcMarkdown,
26615
- artifactType: "LESSON"
26616
- });
26617
- parts.push(`
26806
+ let usedEntity = false;
26807
+ if (lessonEntity) {
26808
+ const slots = renderEntitySlotsForType(lessonEntity, type, spec.budget);
26809
+ if (slots) {
26810
+ const slotBlock = `
26811
+
26812
+ [LESSON ENTITY SLOTS (structured JSON source \u2014 canonical)]:
26813
+ ${slots}`;
26814
+ parts.push(slotBlock);
26815
+ excerptSlot = slotBlock;
26816
+ blocks.push(blockMeta("lessonEntity:" + type, "LESSON_JSON", slots, true));
26817
+ usedEntity = true;
26818
+ } else {
26819
+ issues.push(`lesson-entity:empty-slots:${type}`);
26820
+ }
26821
+ }
26822
+ if (!usedEntity) {
26823
+ const excerpt = buildSectionAwareExcerpt(lessonContent, {
26824
+ priorities: spec.priorities ?? ["Artifact Contract", "A. Lesson Design Plan", "B. Lesson Flow", "Learning Objectives & Evidence", "Activity Sequence"],
26825
+ budget: spec.budget,
26826
+ sectionLanguageContract: slcMarkdown,
26827
+ artifactType: "LESSON"
26828
+ });
26829
+ const excerptBlock = `
26618
26830
 
26619
26831
  [CANONICAL LESSON PLAN (scoped for ${type})]:
26620
- ${excerpt.excerpt}`);
26621
- blocks.push(blockMeta("lessonExcerpt:" + type, "LESSON", excerpt.excerpt, excerpt.verified, excerpt.issues));
26622
- if (!excerpt.verified) issues.push(...excerpt.issues.map((i) => `lesson:${i}`));
26623
- if (type === "QUIZ" || type === "WKS") {
26624
- const am = extractAssessmentMap(lessonContent, 1200);
26625
- if (am) {
26626
- parts.push(`
26832
+ ${excerpt.excerpt}`;
26833
+ parts.push(excerptBlock);
26834
+ excerptSlot = excerptBlock;
26835
+ blocks.push(blockMeta("lessonExcerpt:" + type, "LESSON", excerpt.excerpt, excerpt.verified, excerpt.issues));
26836
+ if (!excerpt.verified) issues.push(...excerpt.issues.map((i) => `lesson:${i}`));
26837
+ if (type === "QUIZ" || type === "WKS") {
26838
+ if (lessonEntity && lessonEntity.assessmentMap.length > 0) {
26839
+ const amJson = lessonEntity.assessmentMap.map((r) => Object.entries(r).map(([k, v]) => `${k}: ${v}`).join(" | ")).join("\n");
26840
+ parts.push(`
26841
+
26842
+ [ASSESSMENT MAP (criteria anchor \u2014 assess exactly these)]:
26843
+ ${amJson}`);
26844
+ blocks.push(blockMeta("assessmentMap:" + type, "LESSON_JSON", amJson, true));
26845
+ } else {
26846
+ const am = extractAssessmentMap(lessonContent, 1200);
26847
+ if (am) {
26848
+ parts.push(`
26627
26849
 
26628
26850
  [ASSESSMENT MAP (criteria anchor \u2014 assess exactly these)]:
26629
26851
  ${am}`);
26630
- blocks.push(blockMeta("assessmentMap:" + type, "LESSON", am, true));
26631
- } else {
26632
- issues.push(`assessment-map:unresolved:${type}`);
26852
+ blocks.push(blockMeta("assessmentMap:" + type, "LESSON", am, true));
26853
+ } else {
26854
+ issues.push(`assessment-map:unresolved:${type}`);
26855
+ }
26856
+ }
26633
26857
  }
26634
26858
  }
26635
26859
  if (symbolLedgerBlock) {
@@ -26637,19 +26861,34 @@ ${am}`);
26637
26861
  blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
26638
26862
  }
26639
26863
  } else {
26640
- const contractRows = extractActivityContractRows(lessonContent, type === "CODE" ? "CODE_LAB" : type, 1500);
26641
- const tierBlock = type === "CODE" || type === "EXT" ? extractTieredScaffoldingBlock(lessonContent, 900) : "";
26642
- const mini = [
26643
- contractRows ? `### Activity Sequence rows assigned to ${type} (canonical \u2014 do not invent a different task):
26644
- ${contractRows}` : "",
26645
- tierBlock ? `### Tiered task ladder (from LESSON Elaborate phase):
26646
- ${tierBlock}` : ""
26647
- ].filter(Boolean).join("\n\n");
26864
+ const miniParts = [];
26865
+ if (lessonEntity) {
26866
+ const rowsTable = renderAssignedRowsTable(lessonEntity, type === "CODE" ? "CODE_LAB" : type, 1500);
26867
+ if (rowsTable) miniParts.push(`### Activity Sequence rows assigned to ${type} (canonical \u2014 do not invent a different task):
26868
+ ${rowsTable}`);
26869
+ const t = lessonEntity.tieredScaffolding;
26870
+ if ((type === "CODE" || type === "EXT") && (t.bronze || t.silver || t.gold)) {
26871
+ miniParts.push(
26872
+ "### Tiered task ladder (from LESSON Elaborate phase):\n" + [t.bronze && `- Bronze: ${t.bronze}`, t.silver && `- Silver: ${t.silver}`, t.gold && `- Gold: ${t.gold}`].filter(Boolean).join("\n")
26873
+ );
26874
+ }
26875
+ }
26876
+ if (miniParts.length === 0) {
26877
+ const contractRows = extractActivityContractRows(lessonContent, type === "CODE" ? "CODE_LAB" : type, 1500);
26878
+ const tierBlock = type === "CODE" || type === "EXT" ? extractTieredScaffoldingBlock(lessonContent, 900) : "";
26879
+ if (contractRows) miniParts.push(`### Activity Sequence rows assigned to ${type} (canonical \u2014 do not invent a different task):
26880
+ ${contractRows}`);
26881
+ if (tierBlock) miniParts.push(`### Tiered task ladder (from LESSON Elaborate phase):
26882
+ ${tierBlock}`);
26883
+ }
26884
+ const mini = miniParts.join("\n\n");
26648
26885
  if (mini) {
26649
- parts.push(`
26886
+ const miniBlock = `
26650
26887
 
26651
26888
  [LESSON ACTIVITY CONTRACT (mini-slot)]:
26652
- ${mini}`);
26889
+ ${mini}`;
26890
+ parts.push(miniBlock);
26891
+ miniSlot = miniBlock;
26653
26892
  blocks.push(blockMeta("activityContract:" + type, "LESSON", mini, true));
26654
26893
  } else {
26655
26894
  issues.push(`activity-contract:unresolved:${type}`);
@@ -26667,9 +26906,36 @@ ${mini}`);
26667
26906
  verified: blocks.filter((b) => b.slot !== "commonContext").every((b) => b.verified),
26668
26907
  sectionAware: blocks.some((b) => b.slot.startsWith("lessonExcerpt") || b.slot.startsWith("kxExcerpt")),
26669
26908
  issues,
26670
- tokenEstimate: Math.round(context.length / 4)
26909
+ tokenEstimate: Math.round(context.length / 4),
26910
+ // Judge ground truth = exactly what THIS type consumed (scoped or mini slot).
26911
+ canonicalExcerpt: (miniSlot ?? excerptSlot ?? lessonExcerpt.excerpt).trim()
26671
26912
  };
26672
26913
  }
26914
+ var ACTIVITY_ALIGNMENT_LOG_ONLY_UNTIL = /* @__PURE__ */ new Date("2026-09-23T00:00:00Z");
26915
+ function isActivityAlignmentLogOnly(now = /* @__PURE__ */ new Date()) {
26916
+ const env = (process.env.DRIFT_ALIGNMENT_LOG_ONLY ?? "").toLowerCase().trim();
26917
+ if (env === "false" || env === "0") return false;
26918
+ if (env === "true" || env === "1") return true;
26919
+ return now < ACTIVITY_ALIGNMENT_LOG_ONLY_UNTIL;
26920
+ }
26921
+ function evaluateActivityAlignment(canonicalExcerpt, generatedContent, now = /* @__PURE__ */ new Date()) {
26922
+ const mode = isActivityAlignmentLogOnly(now) ? "log_only" : "enforcing";
26923
+ if (!canonicalExcerpt || !canonicalExcerpt.trim()) {
26924
+ return { checked: false, passed: null, matchedTokens: [], missingTokens: [], mode };
26925
+ }
26926
+ const content = (generatedContent || "").toLowerCase();
26927
+ const stripped = canonicalExcerpt.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
26928
+ const latin = Array.from(new Set(stripped.match(/[a-z][a-z0-9_]{3,}/g) ?? []));
26929
+ const cjk = Array.from(new Set(canonicalExcerpt.match(/[\u4e00-\u9fff]{2,}/g) ?? []));
26930
+ const tokens = [...latin, ...cjk];
26931
+ if (tokens.length === 0) {
26932
+ return { checked: false, passed: null, matchedTokens: [], missingTokens: [], mode };
26933
+ }
26934
+ const matchedTokens = tokens.filter((t) => content.includes(t));
26935
+ const missingTokens = tokens.filter((t) => !content.includes(t));
26936
+ const passed = matchedTokens.length > 0;
26937
+ return { checked: true, passed, matchedTokens: matchedTokens.slice(0, 10), missingTokens: missingTokens.slice(0, 10), mode };
26938
+ }
26673
26939
 
26674
26940
  // src/services/lessonProductionService.ts
26675
26941
  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))) : "";
@@ -27309,7 +27575,9 @@ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock(includeKx)}${sessi
27309
27575
  });
27310
27576
  const producedArtifacts = [];
27311
27577
  const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
27578
+ const lessonEntityRelPath = `_content/${unitCode}/LESSON_${lessonCode}.json`;
27312
27579
  const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
27580
+ let lessonEntity = buildLessonEntityJson(existingLessonContent || "");
27313
27581
  let lessonContent = existingLessonContent || "";
27314
27582
  if (artifactScope.includes("LESSON") && (!existingLessonContent || force || isStaleVsPlan(existingLessonContent))) {
27315
27583
  if (existingLessonContent && !force && isStaleVsPlan(existingLessonContent)) {
@@ -27385,7 +27653,12 @@ Fix ALL issues above and output the complete corrected document.` }],
27385
27653
  } else {
27386
27654
  lessonContent = lintReport.autoFixedContent || rawLesson;
27387
27655
  }
27388
- await storage.saveArtifact(projectId, lessonRelPath, stampPlanHash(lessonContent));
27656
+ const stampedLesson = stampPlanHash(lessonContent);
27657
+ await storage.saveArtifact(projectId, lessonRelPath, stampedLesson);
27658
+ lessonEntity = buildLessonEntityJson(stampedLesson);
27659
+ if (lessonEntity) {
27660
+ await storage.saveArtifact(projectId, lessonEntityRelPath, JSON.stringify({ planHash: currentPlanHash ?? null, entity: lessonEntity }, null, 2));
27661
+ }
27389
27662
  producedArtifacts.push(`LESSON_${lessonCode}.md`);
27390
27663
  }
27391
27664
  const contentHash = lessonContent ? computeContentHash(lessonContent) : void 0;
@@ -27694,6 +27967,7 @@ ${currentContent}` }],
27694
27967
  const routingMode = options.contextRoutingMode ?? "legacy";
27695
27968
  const kxFreeCommonContext = routingMode === "hybrid" ? assembleCommonContext(effectiveStyleGuide, false) : "";
27696
27969
  const satelliteContexts = {};
27970
+ const satelliteContextMeta = {};
27697
27971
  const satelliteContextFor = (artifactType) => {
27698
27972
  const key = artifactType.toUpperCase();
27699
27973
  if (!satelliteContexts[key]) {
@@ -27706,9 +27980,11 @@ ${currentContent}` }],
27706
27980
  slcMarkdown,
27707
27981
  symbolLedgerBlock,
27708
27982
  pedagogyLabel,
27983
+ lessonEntity,
27709
27984
  mode: routingMode
27710
27985
  });
27711
27986
  satelliteContexts[key] = built.context;
27987
+ satelliteContextMeta[key] = built;
27712
27988
  onProgress?.(
27713
27989
  "@content",
27714
27990
  `[CONTEXT] ${key} satellite context: ${built.context.length} chars (${built.blocks.map((b) => `${b.slot}:${b.chars}`).join(", ")})`,
@@ -27736,6 +28012,30 @@ ${currentContent}` }],
27736
28012
  }
27737
28013
  });
27738
28014
  }
28015
+ const slotMeta = satelliteContextMeta[sat.toUpperCase()];
28016
+ if (slotMeta) {
28017
+ const alignment = evaluateActivityAlignment(slotMeta.canonicalExcerpt, content);
28018
+ if (alignment.checked && alignment.passed === false) {
28019
+ const note = `activity-alignment drift vs ${sat} contract slot (matched: ${alignment.matchedTokens.join(", ") || "none"}); canonical tokens absent from output`;
28020
+ if (alignment.mode === "enforcing") {
28021
+ const critique = validatorRepairPrompt([{ code: "scope-drift", detail: note, evidence: alignment.missingTokens }]);
28022
+ onProgress?.("@reviewer", `\u26D4 ${sat} activity-alignment drift FAIL: ${note}`);
28023
+ return storage.updateArtifactState(projectId, lessonCode, sat, {
28024
+ state: "rejected",
28025
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
28026
+ contentHash: computeContentHash(content),
28027
+ review: {
28028
+ decision: "NEEDS_REVISION",
28029
+ reviewedBy: "@heuristic-linter",
28030
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
28031
+ score: 0,
28032
+ critique
28033
+ }
28034
+ });
28035
+ }
28036
+ onProgress?.("@reviewer", `\u{1F9ED} [log-only] ${sat} activity-alignment drift: ${note}`);
28037
+ }
28038
+ }
27739
28039
  return judgeSatelliteArtifact({
27740
28040
  storage,
27741
28041
  projectId,
@@ -27748,7 +28048,7 @@ ${currentContent}` }],
27748
28048
  onProgress,
27749
28049
  targetLang,
27750
28050
  canonicalLessonContent: lessonContent,
27751
- canonicalLessonExcerpt: lessonExcerpt
28051
+ canonicalLessonExcerpt: slotMeta?.canonicalExcerpt ?? lessonExcerpt
27752
28052
  });
27753
28053
  };
27754
28054
  const actRelPath = `_content/${unitCode}/ACT_${lessonCode}.md`;
@@ -33227,6 +33527,7 @@ exports.LLMJudgeEngine = LLMJudgeEngine;
33227
33527
  exports.LabTierTaskSchema = LabTierTaskSchema;
33228
33528
  exports.LearningObjectiveInputSchema = LearningObjectiveInputSchema;
33229
33529
  exports.LearningObjectiveRowSchema = LearningObjectiveRowSchema;
33530
+ exports.LessonEntitySchema = LessonEntitySchema;
33230
33531
  exports.LessonFlowPhaseSchema = LessonFlowPhaseSchema;
33231
33532
  exports.LessonPlan5ESchema = LessonPlan5ESchema;
33232
33533
  exports.LessonPlanEDPSchema = LessonPlanEDPSchema;
@@ -33346,6 +33647,7 @@ exports.buildHtmlDeckSlidePrompt = buildHtmlDeckSlidePrompt;
33346
33647
  exports.buildImagePrompt = buildImagePrompt;
33347
33648
  exports.buildJudgePrompt = buildJudgePrompt;
33348
33649
  exports.buildLanguageDirective = buildLanguageDirective;
33650
+ exports.buildLessonEntityJson = buildLessonEntityJson;
33349
33651
  exports.buildLessonExcerpt = buildLessonExcerpt;
33350
33652
  exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
33351
33653
  exports.buildMarpMarkdownSlidePrompt = buildMarpMarkdownSlidePrompt;
@@ -33384,6 +33686,8 @@ exports.dropCyclicConceptEdges = dropCyclicConceptEdges;
33384
33686
  exports.emitUsage = emitUsage;
33385
33687
  exports.ensureExpositionForLesson = ensureExpositionForLesson;
33386
33688
  exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
33689
+ exports.entityRowAssignedTo = entityRowAssignedTo;
33690
+ exports.evaluateActivityAlignment = evaluateActivityAlignment;
33387
33691
  exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
33388
33692
  exports.executeCurriculumCommand = executeCurriculumCommand;
33389
33693
  exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
@@ -33440,6 +33744,7 @@ exports.importRoadmapToProject = importRoadmapToProject;
33440
33744
  exports.ingestProjectGraphIntoProject = ingestProjectGraphIntoProject;
33441
33745
  exports.initializeProjectInStorage = initializeProjectInStorage;
33442
33746
  exports.injectMediaIntoMarkdown = injectMediaIntoMarkdown;
33747
+ exports.isActivityAlignmentLogOnly = isActivityAlignmentLogOnly;
33443
33748
  exports.isExpositionFresh = isExpositionFresh;
33444
33749
  exports.isModelAllowed = isModelAllowed;
33445
33750
  exports.isProviderEnabled = isProviderEnabled;
@@ -33461,6 +33766,8 @@ exports.produceSingleLesson = produceSingleLesson;
33461
33766
  exports.publishToGitHub = publishToGitHub;
33462
33767
  exports.publishToSupabase = publishToSupabase;
33463
33768
  exports.rankGenCandidates = rankGenCandidates;
33769
+ exports.renderAssignedRowsTable = renderAssignedRowsTable;
33770
+ exports.renderEntitySlotsForType = renderEntitySlotsForType;
33464
33771
  exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
33465
33772
  exports.renderHorizonPromptBlock = renderHorizonPromptBlock;
33466
33773
  exports.renderMediaPlaceholder = renderMediaPlaceholder;