@thanh01.pmt/curriculum-kit 1.4.37 → 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();
@@ -26611,30 +26803,57 @@ ${kxExcerpt.excerpt}`);
26611
26803
  }
26612
26804
  if (!KX_ONLY_TYPES.has(type)) {
26613
26805
  const spec = LESSON_SLOT_BUDGETS[type] ?? { budget: 12e3 };
26614
- const excerpt = buildSectionAwareExcerpt(lessonContent, {
26615
- priorities: spec.priorities ?? ["Artifact Contract", "A. Lesson Design Plan", "B. Lesson Flow", "Learning Objectives & Evidence", "Activity Sequence"],
26616
- budget: spec.budget,
26617
- sectionLanguageContract: slcMarkdown,
26618
- artifactType: "LESSON"
26619
- });
26620
- const excerptBlock = `
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 = `
26621
26830
 
26622
26831
  [CANONICAL LESSON PLAN (scoped for ${type})]:
26623
26832
  ${excerpt.excerpt}`;
26624
- parts.push(excerptBlock);
26625
- excerptSlot = excerptBlock;
26626
- blocks.push(blockMeta("lessonExcerpt:" + type, "LESSON", excerpt.excerpt, excerpt.verified, excerpt.issues));
26627
- if (!excerpt.verified) issues.push(...excerpt.issues.map((i) => `lesson:${i}`));
26628
- if (type === "QUIZ" || type === "WKS") {
26629
- const am = extractAssessmentMap(lessonContent, 1200);
26630
- if (am) {
26631
- parts.push(`
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(`
26632
26849
 
26633
26850
  [ASSESSMENT MAP (criteria anchor \u2014 assess exactly these)]:
26634
26851
  ${am}`);
26635
- blocks.push(blockMeta("assessmentMap:" + type, "LESSON", am, true));
26636
- } else {
26637
- 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
+ }
26638
26857
  }
26639
26858
  }
26640
26859
  if (symbolLedgerBlock) {
@@ -26642,14 +26861,27 @@ ${am}`);
26642
26861
  blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
26643
26862
  }
26644
26863
  } else {
26645
- const contractRows = extractActivityContractRows(lessonContent, type === "CODE" ? "CODE_LAB" : type, 1500);
26646
- const tierBlock = type === "CODE" || type === "EXT" ? extractTieredScaffoldingBlock(lessonContent, 900) : "";
26647
- const mini = [
26648
- contractRows ? `### Activity Sequence rows assigned to ${type} (canonical \u2014 do not invent a different task):
26649
- ${contractRows}` : "",
26650
- tierBlock ? `### Tiered task ladder (from LESSON Elaborate phase):
26651
- ${tierBlock}` : ""
26652
- ].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");
26653
26885
  if (mini) {
26654
26886
  const miniBlock = `
26655
26887
 
@@ -27343,7 +27575,9 @@ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock(includeKx)}${sessi
27343
27575
  });
27344
27576
  const producedArtifacts = [];
27345
27577
  const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
27578
+ const lessonEntityRelPath = `_content/${unitCode}/LESSON_${lessonCode}.json`;
27346
27579
  const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
27580
+ let lessonEntity = buildLessonEntityJson(existingLessonContent || "");
27347
27581
  let lessonContent = existingLessonContent || "";
27348
27582
  if (artifactScope.includes("LESSON") && (!existingLessonContent || force || isStaleVsPlan(existingLessonContent))) {
27349
27583
  if (existingLessonContent && !force && isStaleVsPlan(existingLessonContent)) {
@@ -27419,7 +27653,12 @@ Fix ALL issues above and output the complete corrected document.` }],
27419
27653
  } else {
27420
27654
  lessonContent = lintReport.autoFixedContent || rawLesson;
27421
27655
  }
27422
- 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
+ }
27423
27662
  producedArtifacts.push(`LESSON_${lessonCode}.md`);
27424
27663
  }
27425
27664
  const contentHash = lessonContent ? computeContentHash(lessonContent) : void 0;
@@ -27741,6 +27980,7 @@ ${currentContent}` }],
27741
27980
  slcMarkdown,
27742
27981
  symbolLedgerBlock,
27743
27982
  pedagogyLabel,
27983
+ lessonEntity,
27744
27984
  mode: routingMode
27745
27985
  });
27746
27986
  satelliteContexts[key] = built.context;
@@ -33287,6 +33527,7 @@ exports.LLMJudgeEngine = LLMJudgeEngine;
33287
33527
  exports.LabTierTaskSchema = LabTierTaskSchema;
33288
33528
  exports.LearningObjectiveInputSchema = LearningObjectiveInputSchema;
33289
33529
  exports.LearningObjectiveRowSchema = LearningObjectiveRowSchema;
33530
+ exports.LessonEntitySchema = LessonEntitySchema;
33290
33531
  exports.LessonFlowPhaseSchema = LessonFlowPhaseSchema;
33291
33532
  exports.LessonPlan5ESchema = LessonPlan5ESchema;
33292
33533
  exports.LessonPlanEDPSchema = LessonPlanEDPSchema;
@@ -33406,6 +33647,7 @@ exports.buildHtmlDeckSlidePrompt = buildHtmlDeckSlidePrompt;
33406
33647
  exports.buildImagePrompt = buildImagePrompt;
33407
33648
  exports.buildJudgePrompt = buildJudgePrompt;
33408
33649
  exports.buildLanguageDirective = buildLanguageDirective;
33650
+ exports.buildLessonEntityJson = buildLessonEntityJson;
33409
33651
  exports.buildLessonExcerpt = buildLessonExcerpt;
33410
33652
  exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
33411
33653
  exports.buildMarpMarkdownSlidePrompt = buildMarpMarkdownSlidePrompt;
@@ -33444,6 +33686,7 @@ exports.dropCyclicConceptEdges = dropCyclicConceptEdges;
33444
33686
  exports.emitUsage = emitUsage;
33445
33687
  exports.ensureExpositionForLesson = ensureExpositionForLesson;
33446
33688
  exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
33689
+ exports.entityRowAssignedTo = entityRowAssignedTo;
33447
33690
  exports.evaluateActivityAlignment = evaluateActivityAlignment;
33448
33691
  exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
33449
33692
  exports.executeCurriculumCommand = executeCurriculumCommand;
@@ -33523,6 +33766,8 @@ exports.produceSingleLesson = produceSingleLesson;
33523
33766
  exports.publishToGitHub = publishToGitHub;
33524
33767
  exports.publishToSupabase = publishToSupabase;
33525
33768
  exports.rankGenCandidates = rankGenCandidates;
33769
+ exports.renderAssignedRowsTable = renderAssignedRowsTable;
33770
+ exports.renderEntitySlotsForType = renderEntitySlotsForType;
33526
33771
  exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
33527
33772
  exports.renderHorizonPromptBlock = renderHorizonPromptBlock;
33528
33773
  exports.renderMediaPlaceholder = renderMediaPlaceholder;