@thanh01.pmt/curriculum-kit 1.4.34 → 1.4.36

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ai/index.cjs +57 -1
  3. package/dist/ai/index.cjs.map +1 -1
  4. package/dist/ai/index.d.cts +2 -2
  5. package/dist/ai/index.d.ts +2 -2
  6. package/dist/ai/index.mjs +57 -1
  7. package/dist/ai/index.mjs.map +1 -1
  8. package/dist/{gateSettings-DabOqP6_.d.cts → gateSettings-oo5tCUS_.d.cts} +18 -0
  9. package/dist/{gateSettings-DabOqP6_.d.ts → gateSettings-oo5tCUS_.d.ts} +18 -0
  10. package/dist/index.cjs +764 -70
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +234 -74
  13. package/dist/index.d.ts +234 -74
  14. package/dist/index.mjs +748 -71
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/pipeline/index.cjs +57 -1
  17. package/dist/pipeline/index.cjs.map +1 -1
  18. package/dist/pipeline/index.mjs +57 -1
  19. package/dist/pipeline/index.mjs.map +1 -1
  20. package/dist/schemas/index.cjs +63 -1
  21. package/dist/schemas/index.cjs.map +1 -1
  22. package/dist/schemas/index.d.cts +436 -33
  23. package/dist/schemas/index.d.ts +436 -33
  24. package/dist/schemas/index.mjs +58 -2
  25. package/dist/schemas/index.mjs.map +1 -1
  26. package/dist/standards/index.d.cts +2 -2
  27. package/dist/standards/index.d.ts +2 -2
  28. package/dist/workflow/index.cjs +84 -2
  29. package/dist/workflow/index.cjs.map +1 -1
  30. package/dist/workflow/index.d.cts +3 -3
  31. package/dist/workflow/index.d.ts +3 -3
  32. package/dist/workflow/index.mjs +84 -2
  33. package/dist/workflow/index.mjs.map +1 -1
  34. package/package.json +22 -23
  35. package/dist/{standardsCoverageGate-DR5YTtlt.d.cts → standardsCoverageGate-49pJq5t8.d.cts} +6 -6
  36. package/dist/{standardsCoverageGate-DR5YTtlt.d.ts → standardsCoverageGate-49pJq5t8.d.ts} +6 -6
package/dist/index.cjs CHANGED
@@ -3574,6 +3574,50 @@ var ScaffoldDecisionEntrySchema = zod.z.object({
3574
3574
  minutes_saved: zod.z.number().int().nonnegative().default(0),
3575
3575
  reason: zod.z.string().min(3)
3576
3576
  });
3577
+ var ZpdVerdictSchema = zod.z.enum(["OK", "TOO_MANY_NEW", "NO_ZPD_BRIDGE"]);
3578
+ var SessionZpdStatusSchema = zod.z.object({
3579
+ verdict: ZpdVerdictSchema,
3580
+ new_concept_count: zod.z.number().int().nonnegative().default(0),
3581
+ known_concept_count: zod.z.number().int().nonnegative().default(0),
3582
+ issues: zod.z.array(zod.z.string()).default([])
3583
+ });
3584
+ var ConceptSpiralEncounterSchema = zod.z.object({
3585
+ concept_code: zod.z.string(),
3586
+ session_id: LessonCodeSchema,
3587
+ encounter_index: zod.z.number().int().positive(),
3588
+ bloom_cap: BloomLevelSchema,
3589
+ depth: DepthLevelSchema
3590
+ });
3591
+ var WalkingSkeletonSchema = zod.z.object({
3592
+ epitome_unit_id: zod.z.string().default("U01"),
3593
+ epitome_deliverables: zod.z.array(zod.z.string()).default([]),
3594
+ elaborations: zod.z.array(zod.z.object({
3595
+ unit_id: zod.z.string(),
3596
+ focus_area: zod.z.string(),
3597
+ elaborates_on: zod.z.array(zod.z.string()).default([])
3598
+ })).default([])
3599
+ });
3600
+ var MasteryGateSchema = zod.z.object({
3601
+ phase_or_unit_id: zod.z.string(),
3602
+ gate_name: zod.z.string(),
3603
+ exit_criteria: zod.z.array(zod.z.string()).min(1),
3604
+ next_unit_id: zod.z.string(),
3605
+ remediation: zod.z.object({
3606
+ trigger: zod.z.literal("FAIL_GATE_CRITERIA"),
3607
+ focus_minutes: zod.z.number().int().default(15),
3608
+ action: zod.z.string()
3609
+ })
3610
+ });
3611
+ var ConceptPrerequisiteEdgeSchema = zod.z.object({
3612
+ concept_code: zod.z.string(),
3613
+ requires: zod.z.string(),
3614
+ source: zod.z.enum(["MASTER_TREE", "INFERRED", "CONCEPT_LEVEL_PREREQ"]).default("CONCEPT_LEVEL_PREREQ"),
3615
+ confidence: zod.z.number().min(0).max(1).default(0.8),
3616
+ rationale: zod.z.string().default(""),
3617
+ structure_supported: zod.z.boolean().default(true),
3618
+ needs_review: zod.z.boolean().default(false),
3619
+ hub: zod.z.boolean().default(false)
3620
+ });
3577
3621
  var SessionPlanSchema = zod.z.object({
3578
3622
  id: LessonCodeSchema,
3579
3623
  unit_id: zod.z.string().regex(/^U\d{2}$/),
@@ -3594,7 +3638,14 @@ var SessionPlanSchema = zod.z.object({
3594
3638
  scaffold_decisions: zod.z.array(ScaffoldDecisionEntrySchema).default([]),
3595
3639
  // What the student can demonstrate after this session — feeds exit tickets.
3596
3640
  exit_evidence: zod.z.array(zod.z.string()).min(1),
3597
- differentiation: zod.z.object({ bronze: zod.z.string(), silver: zod.z.string(), gold: zod.z.string() })
3641
+ differentiation: zod.z.object({ bronze: zod.z.string(), silver: zod.z.string(), gold: zod.z.string() }),
3642
+ // P50: ZPD cognitive load status for this session
3643
+ zpd_status: SessionZpdStatusSchema.default({
3644
+ verdict: "OK",
3645
+ new_concept_count: 0,
3646
+ known_concept_count: 0,
3647
+ issues: []
3648
+ })
3598
3649
  });
3599
3650
  var TranslationPolicySchema = zod.z.object({
3600
3651
  policy: zod.z.enum(["after_artifact", "end_of_unit", "at_publish", "native"]).default("native"),
@@ -3649,6 +3700,11 @@ var CurriculumPlanSchema = zod.z.object({
3649
3700
  // [2]
3650
3701
  sessions: zod.z.array(SessionPlanSchema).min(1),
3651
3702
  // [3][4][5][6]
3703
+ // P50: Macro-pedagogical course structures
3704
+ walking_skeleton: WalkingSkeletonSchema.optional(),
3705
+ mastery_gates: zod.z.array(MasteryGateSchema).default([]),
3706
+ concept_spiral_progression: zod.z.array(ConceptSpiralEncounterSchema).default([]),
3707
+ concept_prerequisites: zod.z.array(ConceptPrerequisiteEdgeSchema).default([]),
3652
3708
  glossary_scope: zod.z.array(zod.z.object({
3653
3709
  session_id: LessonCodeSchema,
3654
3710
  terms: zod.z.array(zod.z.string())
@@ -11279,7 +11335,8 @@ function parseAllSessions(plan, frameworkMarkdown) {
11279
11335
  prose_objective: s.prose_objective || s.objective || "",
11280
11336
  new_keywords: Array.isArray(s.new_keywords) ? s.new_keywords : Array.isArray(s.keywords) ? s.keywords : [],
11281
11337
  prerequisite_keywords: Array.isArray(s.prerequisite_keywords) ? s.prerequisite_keywords : [],
11282
- depth_assignments: Array.isArray(s.depth_assignments) ? s.depth_assignments : []
11338
+ depth_assignments: Array.isArray(s.depth_assignments) ? s.depth_assignments : [],
11339
+ zpd_status: s.zpd_status
11283
11340
  }));
11284
11341
  }
11285
11342
  }
@@ -11427,12 +11484,16 @@ async function extractCurriculumHorizon(opts) {
11427
11484
  title: s.title,
11428
11485
  keywords: s.new_keywords || []
11429
11486
  }));
11487
+ const rawSpirals = Array.isArray(plan?.concept_spiral_progression) ? plan.concept_spiral_progression : [];
11488
+ const sessionSpirals = rawSpirals.filter((e) => e.session_id === targetLessonId);
11430
11489
  return {
11431
11490
  targetLessonId,
11432
11491
  targetLessonIndex: targetIndex + 1,
11433
11492
  totalLessons: sessions.length,
11434
11493
  targetTitle: currentSession.title,
11435
11494
  targetKeywords: currentSession.new_keywords || [],
11495
+ zpdStatus: currentSession.zpd_status,
11496
+ spiralProgression: sessionSpirals,
11436
11497
  compactMasterySet: {
11437
11498
  masteredKeywords,
11438
11499
  masteredConcepts
@@ -11448,6 +11509,8 @@ function renderHorizonPromptBlock(horizon) {
11448
11509
  totalLessons,
11449
11510
  targetTitle,
11450
11511
  targetKeywords,
11512
+ zpdStatus,
11513
+ spiralProgression,
11451
11514
  compactMasterySet,
11452
11515
  detailedBridge,
11453
11516
  boundaryPeek
@@ -11455,6 +11518,19 @@ function renderHorizonPromptBlock(horizon) {
11455
11518
  const lines = [
11456
11519
  `# \u{1F9ED} CURRICULUM HORIZON & SCAFFOLDING MATRIX (Lesson ${targetLessonIndex}/${totalLessons}: "${targetTitle}" [${targetLessonId}])`
11457
11520
  ];
11521
+ if (zpdStatus && zpdStatus.verdict !== "OK") {
11522
+ lines.push(
11523
+ `
11524
+ > \u26A0\uFE0F **ZPD COGNITIVE LOAD ALERT (${zpdStatus.verdict}):** ${zpdStatus.issues?.join("; ") || "Scaffolding bridge mandated"}. Introduce minimal new syntax and anchor into known concepts.`
11525
+ );
11526
+ }
11527
+ if (spiralProgression && spiralProgression.length > 0) {
11528
+ const spiralNotes = spiralProgression.map(
11529
+ (sp) => `\`${sp.concept_code}\` (Encounter #${sp.encounter_index}): Bloom Cap [${sp.bloom_cap}], Depth [${sp.depth.toUpperCase()}]`
11530
+ );
11531
+ lines.push(`
11532
+ - **\u{1F300} Bruner Spiral Curricula Guidance:** ${spiralNotes.join(" | ")}`);
11533
+ }
11458
11534
  if (compactMasterySet.masteredKeywords.length > 0 || compactMasterySet.masteredConcepts.length > 0) {
11459
11535
  const rawKeywords = compactMasterySet.masteredKeywords;
11460
11536
  const displayedKeywords = rawKeywords.length > 20 ? rawKeywords.slice(-20) : rawKeywords;
@@ -11625,9 +11701,9 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
11625
11701
  if (s.canonicalKey && !byCanonical.has(s.canonicalKey)) {
11626
11702
  byCanonical.set(s.canonicalKey, s);
11627
11703
  }
11628
- const norm = normalizeHeading(s.cleanTitle);
11629
- if (!byCleanTitle.has(norm)) {
11630
- byCleanTitle.set(norm, s);
11704
+ const norm2 = normalizeHeading(s.cleanTitle);
11705
+ if (!byCleanTitle.has(norm2)) {
11706
+ byCleanTitle.set(norm2, s);
11631
11707
  }
11632
11708
  if (!s.canonicalKey) {
11633
11709
  unmatched.push(s);
@@ -11765,8 +11841,8 @@ function parseMarkdownSections(markdown, slcMap, artifactType) {
11765
11841
  const flush = () => {
11766
11842
  if (!currentHeading) return;
11767
11843
  const cleanTitle = currentHeading.replace(/^#{1,4}\s+/, "").trim();
11768
- const norm = normalizeHeading(cleanTitle);
11769
- const canonicalKey = reverseMap.get(norm) || findCanonicalFuzzy(norm);
11844
+ const norm2 = normalizeHeading(cleanTitle);
11845
+ const canonicalKey = reverseMap.get(norm2) || findCanonicalFuzzy(norm2);
11770
11846
  sections.push({
11771
11847
  rawHeading: currentHeading,
11772
11848
  cleanTitle,
@@ -11792,49 +11868,55 @@ function parseMarkdownSections(markdown, slcMap, artifactType) {
11792
11868
  function normalizeHeading(str) {
11793
11869
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
11794
11870
  }
11795
- function findCanonicalFuzzy(norm) {
11796
- if (norm.includes("toolchain") || norm.includes("moi truong phat trien") || norm.includes("cong cu") || norm.includes("version") || norm.includes("phan mem") || norm.includes("development environment") || norm.includes("technical overview") || norm.includes("kien truc")) {
11871
+ function findCanonicalFuzzy(norm2) {
11872
+ if (norm2.includes("toolchain") || norm2.includes("moi truong phat trien") || norm2.includes("cong cu") || norm2.includes("version") || norm2.includes("phan mem") || norm2.includes("development environment") || norm2.includes("technical overview") || norm2.includes("kien truc")) {
11797
11873
  return "Technical Overview & Architecture Blueprint";
11798
11874
  }
11799
- if (norm.includes("pinout") || norm.includes("phan cung") || norm.includes("hardware") || norm.includes("wiring") || norm.includes("ket noi")) {
11875
+ if (norm2.includes("pinout") || norm2.includes("phan cung") || norm2.includes("hardware") || norm2.includes("wiring") || norm2.includes("ket noi")) {
11800
11876
  return "Hardware Pinout & Wiring Configuration Matrix";
11801
11877
  }
11802
- if (norm.includes("pedagog") || norm.includes("phuong phap") || norm.includes("day hoc") || norm.includes("su pham")) {
11878
+ if (norm2.includes("pedagog") || norm2.includes("phuong phap") || norm2.includes("day hoc") || norm2.includes("su pham")) {
11803
11879
  return "Core Pedagogical Concept Anchor & Real-World Domain Bridge";
11804
11880
  }
11805
- if (norm.includes("standard") || norm.includes("tieu chuan") || norm.includes("csta") || norm.includes("cs2023") || norm.includes("chuan academic")) {
11881
+ if (norm2.includes("standard") || norm2.includes("tieu chuan") || norm2.includes("csta") || norm2.includes("cs2023") || norm2.includes("chuan academic")) {
11806
11882
  return "Standards Alignment";
11807
11883
  }
11808
- if (norm.includes("roadmap") || norm.includes("lo trinh") || norm.includes("milestone") || norm.includes("giai doan")) {
11884
+ if (norm2.includes("roadmap") || norm2.includes("lo trinh") || norm2.includes("milestone") || norm2.includes("giai doan")) {
11809
11885
  return void 0;
11810
11886
  }
11811
- if (norm.includes("symbol") || norm.includes("identifier ledger") || norm.includes("dinh danh")) {
11887
+ if (norm2.includes("symbol") || norm2.includes("identifier ledger") || norm2.includes("dinh danh")) {
11812
11888
  return "Symbol & Identifier Ledger";
11813
11889
  }
11814
- if (norm.includes("artifact contract") || norm.includes("hop dong hoc lieu")) {
11890
+ if (norm2.includes("artifact contract") || norm2.includes("hop dong hoc lieu")) {
11815
11891
  return "Artifact Contract";
11816
11892
  }
11817
- if (norm.includes("lesson design plan") || norm.includes("ke hoach thiet ke")) {
11893
+ if (norm2.includes("lesson design plan") || norm2.includes("ke hoach thiet ke")) {
11818
11894
  return "A. Lesson Design Plan";
11819
11895
  }
11820
- if (norm.includes("lesson flow") || norm.includes("tien trinh giang day")) {
11896
+ if (norm2.includes("lesson flow") || norm2.includes("tien trinh giang day")) {
11821
11897
  return "B. Lesson Flow";
11822
11898
  }
11823
- if (norm.includes("learning objective") || norm.includes("muc tieu bai hoc")) {
11899
+ if (norm2.includes("learning objective") || norm2.includes("muc tieu bai hoc")) {
11824
11900
  return "Learning Objectives & Evidence";
11825
11901
  }
11826
- if (norm.includes("activity sequence") || norm.includes("chuoi hoat dong")) {
11902
+ if (norm2.includes("activity sequence") || norm2.includes("chuoi hoat dong")) {
11827
11903
  return "Activity Sequence";
11828
11904
  }
11829
- if (norm.includes("key term") || norm.includes("thuat ngu")) {
11905
+ if (norm2.includes("key term") || norm2.includes("thuat ngu")) {
11830
11906
  return "Key Terms";
11831
11907
  }
11832
- if (norm.includes("concept narrative") || norm.includes("dien giai khai niem")) {
11908
+ if (norm2.includes("concept narrative") || norm2.includes("dien giai khai niem")) {
11833
11909
  return "Concept Narratives";
11834
11910
  }
11835
- if (norm.includes("worked example") || norm.includes("vi du")) {
11911
+ if (norm2.includes("worked example") || norm2.includes("vi du")) {
11836
11912
  return "Worked Micro-Examples";
11837
11913
  }
11914
+ if (norm2.includes("common mistake") || norm2.includes("loi thuong gap") || norm2.includes("sai lam") || norm2.includes("pitfall")) {
11915
+ return "Common Mistakes";
11916
+ }
11917
+ if (norm2.includes("self check") || norm2.includes("self-check") || norm2.includes("tu kiem tra")) {
11918
+ return "Self-Check Questions";
11919
+ }
11838
11920
  return void 0;
11839
11921
  }
11840
11922
  function truncateByBudget(source, budget) {
@@ -12769,7 +12851,7 @@ function decideScaffolds(session, nodeById, warnings) {
12769
12851
  return decisions;
12770
12852
  }
12771
12853
  var DEPTH_NEXT = { ulo: "cio", cio: "sio", sio: "sio" };
12772
- function assignDepths(nodeIds, nodeById) {
12854
+ function assignDepths(nodeIds, nodeById, conceptEncounterMap) {
12773
12855
  const assignments = [];
12774
12856
  let lastDepth = null;
12775
12857
  for (const id of nodeIds) {
@@ -12777,8 +12859,14 @@ function assignDepths(nodeIds, nodeById) {
12777
12859
  let depth;
12778
12860
  let source;
12779
12861
  if (node.kind === "concept") {
12862
+ const primaryCode = node.concept_codes[0];
12863
+ const encounters = primaryCode && conceptEncounterMap ? conceptEncounterMap.get(primaryCode) ?? 1 : 1;
12780
12864
  const advanced = node.phase_id !== "" && /__ADV\d{2}$/.test(node.id);
12781
- depth = advanced ? "sio" : "cio";
12865
+ if (encounters >= 2 || advanced) {
12866
+ depth = "sio";
12867
+ } else {
12868
+ depth = "cio";
12869
+ }
12782
12870
  source = "planner";
12783
12871
  } else {
12784
12872
  depth = node.depth_hint ?? "cio";
@@ -12793,6 +12881,201 @@ function assignDepths(nodeIds, nodeById) {
12793
12881
  }
12794
12882
  return assignments;
12795
12883
  }
12884
+ function computeConceptSpiralProgression(sessions, nodeById) {
12885
+ const encounters = [];
12886
+ const conceptCounts = /* @__PURE__ */ new Map();
12887
+ for (const s of sessions) {
12888
+ const sessionConcepts = [...new Set(s.node_ids.flatMap((id) => nodeById.get(id)?.concept_codes || []))];
12889
+ for (const cCode of sessionConcepts) {
12890
+ const count = (conceptCounts.get(cCode) || 0) + 1;
12891
+ conceptCounts.set(cCode, count);
12892
+ let bloomCap;
12893
+ let depth;
12894
+ if (count === 1) {
12895
+ bloomCap = "Understand";
12896
+ depth = "cio";
12897
+ } else if (count === 2) {
12898
+ bloomCap = "Apply";
12899
+ depth = "sio";
12900
+ } else {
12901
+ bloomCap = "Analyze";
12902
+ depth = "sio";
12903
+ }
12904
+ encounters.push({
12905
+ concept_code: cCode,
12906
+ session_id: s.id,
12907
+ encounter_index: count,
12908
+ bloom_cap: bloomCap,
12909
+ depth
12910
+ });
12911
+ }
12912
+ }
12913
+ return encounters;
12914
+ }
12915
+ function checkSessionZpd(sessionIndex, nodeIds, nodeById, allSeenConcepts, allSeenKeywords, entryKw, warnings) {
12916
+ const concepts = [...new Set(nodeIds.flatMap((id) => nodeById.get(id)?.concept_codes || []))];
12917
+ const newConcepts = concepts.filter((c) => !allSeenConcepts.has(c));
12918
+ const knownConcepts = concepts.filter((c) => allSeenConcepts.has(c));
12919
+ const allKeywords = [...new Set(nodeIds.flatMap((id) => nodeById.get(id)?.keywords || []))];
12920
+ const newKeywords = allKeywords.filter((k) => !allSeenKeywords.has(k.toLowerCase()) && !entryKw.has(k.toLowerCase()));
12921
+ const knownKeywords = allKeywords.filter((k) => allSeenKeywords.has(k.toLowerCase()) || entryKw.has(k.toLowerCase()));
12922
+ const issues = [];
12923
+ let verdict = "OK";
12924
+ if (concepts.length > 0 && newConcepts.length > 2) {
12925
+ verdict = "TOO_MANY_NEW";
12926
+ issues.push(`${newConcepts.length} new concepts exceed ZPD limit (max 2)`);
12927
+ } else if (concepts.length === 0 && newKeywords.length > 4) {
12928
+ verdict = "TOO_MANY_NEW";
12929
+ issues.push(`${newKeywords.length} new keywords exceed ZPD limit (max 4)`);
12930
+ }
12931
+ if (sessionIndex > 0) {
12932
+ if (concepts.length > 1 && knownConcepts.length === 0) {
12933
+ verdict = "NO_ZPD_BRIDGE";
12934
+ issues.push("No known concepts from previous sessions to bridge new learning");
12935
+ } else if (concepts.length === 0 && allKeywords.length > 2 && knownKeywords.length === 0) {
12936
+ verdict = "NO_ZPD_BRIDGE";
12937
+ issues.push("No known keywords from previous sessions to bridge new learning");
12938
+ }
12939
+ }
12940
+ let bridgeRepair;
12941
+ if (verdict === "NO_ZPD_BRIDGE" && (allSeenConcepts.size > 0 || allSeenKeywords.size > 0)) {
12942
+ const bridgeAnchor = [...allSeenConcepts].pop() || [...allSeenKeywords].pop() || "prior knowledge";
12943
+ bridgeRepair = {
12944
+ node_id: nodeIds[0],
12945
+ decision: "recap_in_lesson",
12946
+ reason: `ZPD self-healing: recap anchor (${bridgeAnchor}) to bridge into new material`
12947
+ };
12948
+ warnings.push(`ZPD bridge self-healing: session ${sessionIndex + 1} injected recap anchor (${bridgeAnchor})`);
12949
+ }
12950
+ for (const c of concepts) allSeenConcepts.add(c);
12951
+ for (const k of allKeywords) allSeenKeywords.add(k.toLowerCase());
12952
+ return {
12953
+ status: {
12954
+ verdict,
12955
+ new_concept_count: concepts.length > 0 ? newConcepts.length : newKeywords.length,
12956
+ known_concept_count: concepts.length > 0 ? knownConcepts.length : knownKeywords.length,
12957
+ issues
12958
+ },
12959
+ bridgeRepair
12960
+ };
12961
+ }
12962
+ function buildWalkingSkeleton(units, sessions) {
12963
+ const epitomeUnit = units[0];
12964
+ const epitomeId = epitomeUnit?.id || "U01";
12965
+ const epitomeDeliverables = sessions.filter((s) => s.unit_id === epitomeId).flatMap((s) => s.exit_evidence || []).slice(0, 8);
12966
+ const elaborations = units.slice(1).map((u) => ({
12967
+ unit_id: u.id,
12968
+ focus_area: u.name,
12969
+ elaborates_on: epitomeDeliverables.slice(0, 3)
12970
+ }));
12971
+ return {
12972
+ epitome_unit_id: epitomeId,
12973
+ epitome_deliverables: epitomeDeliverables,
12974
+ elaborations
12975
+ };
12976
+ }
12977
+ function buildMasteryGates(units, sessions) {
12978
+ const gates = [];
12979
+ for (let i = 0; i < units.length - 1; i++) {
12980
+ const cur = units[i];
12981
+ const next = units[i + 1];
12982
+ const unitEvidence = sessions.filter((s) => s.unit_id === cur.id).flatMap((s) => s.exit_evidence || []).slice(0, 6);
12983
+ gates.push({
12984
+ phase_or_unit_id: cur.id,
12985
+ gate_name: `${cur.name} Mastery Gate`,
12986
+ exit_criteria: unitEvidence.length > 0 ? unitEvidence : [`Master all deliverables for ${cur.name}`],
12987
+ next_unit_id: next.id,
12988
+ remediation: {
12989
+ trigger: "FAIL_GATE_CRITERIA",
12990
+ focus_minutes: 15,
12991
+ action: `Review and complete failing practical criteria for ${cur.name} before starting ${next.name}`
12992
+ }
12993
+ });
12994
+ }
12995
+ return gates;
12996
+ }
12997
+ function dropCyclicConceptEdges(edges) {
12998
+ const pairSet = new Set(edges.map((e) => `${e.concept_code}|${e.requires}`));
12999
+ const nonSymmetric = edges.filter((e) => !pairSet.has(`${e.requires}|${e.concept_code}`));
13000
+ const deps = /* @__PURE__ */ new Map();
13001
+ const createsCycle = (a, b) => {
13002
+ const stack = [b];
13003
+ const seen = /* @__PURE__ */ new Set();
13004
+ while (stack.length > 0) {
13005
+ const n = stack.pop();
13006
+ if (n === a) return true;
13007
+ if (seen.has(n)) continue;
13008
+ seen.add(n);
13009
+ const targets = deps.get(n);
13010
+ if (targets) {
13011
+ for (const t of targets) stack.push(t);
13012
+ }
13013
+ }
13014
+ return false;
13015
+ };
13016
+ const out = [];
13017
+ for (const e of nonSymmetric) {
13018
+ if (createsCycle(e.concept_code, e.requires)) continue;
13019
+ if (!deps.has(e.concept_code)) deps.set(e.concept_code, /* @__PURE__ */ new Set());
13020
+ deps.get(e.concept_code).add(e.requires);
13021
+ out.push(e);
13022
+ }
13023
+ return out;
13024
+ }
13025
+ function buildConceptPrerequisites(sessions, nodeById, edges) {
13026
+ const firstSeen = /* @__PURE__ */ new Map();
13027
+ for (let i = 0; i < sessions.length; i++) {
13028
+ const s = sessions[i];
13029
+ for (const nid of s.node_ids) {
13030
+ const codes = nodeById.get(nid)?.concept_codes || [];
13031
+ for (const c of codes) {
13032
+ if (!firstSeen.has(c)) firstSeen.set(c, i);
13033
+ }
13034
+ }
13035
+ }
13036
+ const rawEdges = [];
13037
+ const seenPair = /* @__PURE__ */ new Set();
13038
+ for (const e of edges) {
13039
+ const fromNode = nodeById.get(e.from);
13040
+ const toNode = nodeById.get(e.to);
13041
+ if (!fromNode || !toNode) continue;
13042
+ const fromCodes = fromNode.concept_codes || [];
13043
+ const toCodes = toNode.concept_codes || [];
13044
+ for (const tc of toCodes) {
13045
+ for (const fc of fromCodes) {
13046
+ if (tc === fc) continue;
13047
+ const key = `${tc}|${fc}`;
13048
+ if (seenPair.has(key)) continue;
13049
+ seenPair.add(key);
13050
+ const firstSeenTc = firstSeen.get(tc) ?? 999;
13051
+ const firstSeenFc = firstSeen.get(fc) ?? 999;
13052
+ const supported = firstSeenFc <= firstSeenTc;
13053
+ rawEdges.push({
13054
+ concept_code: tc,
13055
+ requires: fc,
13056
+ source: "CONCEPT_LEVEL_PREREQ",
13057
+ confidence: supported ? 0.8 : 0.6,
13058
+ rationale: e.reason || `Derived from planning node dependency: ${fromNode.name} -> ${toNode.name}`,
13059
+ structure_supported: supported,
13060
+ needs_review: !supported,
13061
+ hub: false
13062
+ });
13063
+ }
13064
+ }
13065
+ }
13066
+ const fanOut = /* @__PURE__ */ new Map();
13067
+ for (const re of rawEdges) {
13068
+ fanOut.set(re.concept_code, (fanOut.get(re.concept_code) || 0) + 1);
13069
+ }
13070
+ for (const re of rawEdges) {
13071
+ if ((fanOut.get(re.concept_code) || 0) >= 6) {
13072
+ re.hub = true;
13073
+ re.confidence = Math.min(re.confidence, 0.5);
13074
+ re.needs_review = true;
13075
+ }
13076
+ }
13077
+ return dropCyclicConceptEdges(rawEdges);
13078
+ }
12796
13079
  async function buildCurriculumPlan(rawGraph, options) {
12797
13080
  const { constraints } = options;
12798
13081
  const warnings = [];
@@ -12888,6 +13171,9 @@ async function buildCurriculumPlan(rawGraph, options) {
12888
13171
  const seenNodes = /* @__PURE__ */ new Set();
12889
13172
  const multiPartNodes = new Set(packed.flatMap((s) => s.entries.filter((e) => e.totalParts > 1).map((e) => e.id)));
12890
13173
  const sessions = [];
13174
+ const allSeenConcepts = /* @__PURE__ */ new Set();
13175
+ const allSeenKeywords = /* @__PURE__ */ new Set();
13176
+ const conceptEncounterMap = /* @__PURE__ */ new Map();
12891
13177
  for (let i = 0; i < packed.length; i++) {
12892
13178
  const s = packed[i];
12893
13179
  for (const id of s.nodeIds) {
@@ -12932,6 +13218,16 @@ async function buildCurriculumPlan(rawGraph, options) {
12932
13218
  knowledge: s.knowledgeMinutes || 0,
12933
13219
  practice: s.practiceMinutes + (s.practiceMinutes === 0 && s.knowledgeMinutes > 0 ? Math.min(spareMinutes, 10) : 0)
12934
13220
  };
13221
+ const sessionConcepts = [...new Set(s.nodeIds.flatMap((id) => nodeById.get(id)?.concept_codes || []))];
13222
+ for (const cCode of sessionConcepts) {
13223
+ const curCount = (conceptEncounterMap.get(cCode) || 0) + 1;
13224
+ conceptEncounterMap.set(cCode, curCount);
13225
+ }
13226
+ const zpdResult = checkSessionZpd(i, s.nodeIds, nodeById, allSeenConcepts, allSeenKeywords, entryKw, warnings);
13227
+ const prereqDecisions = decidePrerequisites(ctx, nodeById, graph.edges, sessionIndexByNode, sessionOrderByIndex, constraints);
13228
+ if (zpdResult.bridgeRepair) {
13229
+ prereqDecisions.unshift(zpdResult.bridgeRepair);
13230
+ }
12935
13231
  sessions.push({
12936
13232
  id: lessonCode,
12937
13233
  unit_id: unit.id,
@@ -12952,8 +13248,8 @@ async function buildCurriculumPlan(rawGraph, options) {
12952
13248
  // Spiral pedagogy = apply at greater depth, not re-lecture.
12953
13249
  practice_minutes: sessionMinutes.practice,
12954
13250
  overhead_minutes: spec.overheadMinutes,
12955
- depth_assignments: assignDepths(s.nodeIds, nodeById),
12956
- prerequisite_decisions: decidePrerequisites(ctx, nodeById, graph.edges, sessionIndexByNode, sessionOrderByIndex, constraints),
13251
+ depth_assignments: assignDepths(s.nodeIds, nodeById, conceptEncounterMap),
13252
+ prerequisite_decisions: prereqDecisions,
12957
13253
  scaffold_decisions: decideScaffolds(ctx, nodeById, warnings),
12958
13254
  // Deliverable phrasing follows the node role: concept → evidence is the
12959
13255
  // explanation, product_step/skill → the artifact or activity itself.
@@ -12968,16 +13264,58 @@ async function buildCurriculumPlan(rawGraph, options) {
12968
13264
  bronze: "Complete the core deliverable for " + nodeNames[0] + " with provided scaffolding.",
12969
13265
  silver: "Complete all session deliverables independently.",
12970
13266
  gold: "Extend the deliverable: combine " + nodeNames.join(", ") + " in a self-chosen variant."
12971
- }
13267
+ },
13268
+ // P50: ZPD cognitive load status for this session
13269
+ zpd_status: zpdResult.status
12972
13270
  });
12973
13271
  }
12974
13272
  const unassigned = graph.nodes.map((n) => n.id).filter((id) => !seenNodes.has(id));
12975
13273
  if (unassigned.length > 0) throw new Error("Coverage violation: unassigned nodes " + unassigned.join(", "));
13274
+ if (options.llmFn && sessions.length > 0) {
13275
+ try {
13276
+ const sessionSummaries = sessions.map((s) => `${s.id}|${s.title}|objective: ${s.prose_objective.slice(0, 120)}|keywords: ${s.new_keywords.join("/")}`).join("\n");
13277
+ const dSys = 'You differentiate learning tasks. Output ONLY a JSON object mapping session-id to {"bronze":string,"silver":string,"gold":string}. Each tier ONE sentence: bronze = foundational task with provided scaffolding; silver = independent application; gold = creative extension combining session concepts. Same language as the objectives.';
13278
+ const dUsr = `Age band: ${constraints.age_band[0]}-${constraints.age_band[1]}. Entry level: ${constraints.entry_level}.
13279
+ Sessions:
13280
+ ${sessionSummaries}`;
13281
+ const raw = (await options.llmFn(dSys, dUsr)).trim();
13282
+ const match = raw.match(/\{[\s\S]*\}/);
13283
+ if (match) {
13284
+ const parsed = JSON.parse(match[0]);
13285
+ let upgraded = 0;
13286
+ for (const s of sessions) {
13287
+ const d = parsed[s.id];
13288
+ if (d && typeof d.bronze === "string" && d.bronze.length >= 10 && typeof d.silver === "string" && d.silver.length >= 10 && typeof d.gold === "string" && d.gold.length >= 10) {
13289
+ s.differentiation = { bronze: d.bronze, silver: d.silver, gold: d.gold };
13290
+ upgraded++;
13291
+ }
13292
+ }
13293
+ if (upgraded < sessions.length) warnings.push(`differentiation_llm_partial:${upgraded}/${sessions.length}`);
13294
+ } else {
13295
+ warnings.push("differentiation_llm_unparseable");
13296
+ }
13297
+ } catch (e) {
13298
+ warnings.push("differentiation_llm_failed:" + (e instanceof Error ? e.message.slice(0, 80) : String(e).slice(0, 80)));
13299
+ }
13300
+ }
12976
13301
  const glossaryScope = sessions.map((s) => ({
12977
13302
  session_id: s.id,
12978
13303
  terms: [...new Set(s.node_ids.flatMap((id) => nodeById.get(id).keywords))]
12979
13304
  }));
12980
- const planPayload = { units, sessions, course: { objectives: courseObjectives }, constraints };
13305
+ const conceptSpiralProgression = computeConceptSpiralProgression(sessions, nodeById);
13306
+ const walkingSkeleton = buildWalkingSkeleton(units, sessions);
13307
+ const masteryGates = buildMasteryGates(units, sessions);
13308
+ const conceptPrerequisites = buildConceptPrerequisites(sessions, nodeById, graph.edges);
13309
+ const planPayload = {
13310
+ units,
13311
+ sessions,
13312
+ course: { objectives: courseObjectives },
13313
+ constraints,
13314
+ walking_skeleton: walkingSkeleton,
13315
+ mastery_gates: masteryGates,
13316
+ concept_spiral_progression: conceptSpiralProgression,
13317
+ concept_prerequisites: conceptPrerequisites
13318
+ };
12981
13319
  const planHash = crypto.createHash("sha256").update(JSON.stringify(planPayload)).digest("hex").slice(0, 16);
12982
13320
  const plan = CurriculumPlanSchema.parse({
12983
13321
  schema_version: 1,
@@ -12989,6 +13327,10 @@ async function buildCurriculumPlan(rawGraph, options) {
12989
13327
  course: { objectives: courseObjectives, capstone_ref: constraints.capstone },
12990
13328
  units,
12991
13329
  sessions,
13330
+ walking_skeleton: walkingSkeleton,
13331
+ mastery_gates: masteryGates,
13332
+ concept_spiral_progression: conceptSpiralProgression,
13333
+ concept_prerequisites: conceptPrerequisites,
12992
13334
  glossary_scope: glossaryScope,
12993
13335
  translation: options.translation ?? { policy: "at_publish", target_language: null },
12994
13336
  coverage_report: { assigned_node_ids: [...seenNodes], unassigned_node_ids: [], warnings },
@@ -13020,6 +13362,13 @@ function buildSessionSliceContext(plan, lessonCode) {
13020
13362
  lines.push("- Prerequisite keywords: " + (s.prerequisite_keywords.join(", ") || "(none)"));
13021
13363
  lines.push("- Time split: knowledge " + s.knowledge_minutes + "m / practice " + s.practice_minutes + "m / overhead " + s.overhead_minutes + "m");
13022
13364
  lines.push("- Depth assignments: " + s.depth_assignments.map((d) => d.node_id + "=" + d.depth.toUpperCase() + "(" + d.source + ")").join("; "));
13365
+ if (s.zpd_status && s.zpd_status.verdict !== "OK") {
13366
+ lines.push("- ZPD alert: " + s.zpd_status.verdict + " (" + s.zpd_status.issues.join("; ") + ")");
13367
+ }
13368
+ const sessionSpirals = (plan.concept_spiral_progression || []).filter((e) => e.session_id === lessonCode);
13369
+ if (sessionSpirals.length > 0) {
13370
+ lines.push("- Spiral encounters: " + sessionSpirals.map((e) => `${e.concept_code}#${e.encounter_index}=${e.bloom_cap}(${e.depth.toUpperCase()})`).join("; "));
13371
+ }
13023
13372
  if (s.prerequisite_decisions.length > 0) {
13024
13373
  lines.push("- Prerequisite decisions [" + s.prerequisite_decisions.length + "]:");
13025
13374
  for (const d of s.prerequisite_decisions) lines.push(" * " + d.node_id + ": " + d.decision + " \u2014 " + d.reason);
@@ -13341,14 +13690,14 @@ function lintFrameworkPack(pack) {
13341
13690
  const primaryLang = pack.manifest.languages[0];
13342
13691
  const text = s.texts[primaryLang];
13343
13692
  if (!text) continue;
13344
- const norm = text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter((w) => w.length > 2).sort().join(" ");
13345
- if (!norm) continue;
13346
- const prev = seen.get(norm);
13693
+ const norm2 = text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter((w) => w.length > 2).sort().join(" ");
13694
+ if (!norm2) continue;
13695
+ const prev = seen.get(norm2);
13347
13696
  if (prev !== void 0) {
13348
13697
  issues.push({ severity: "WARNING", code: "DUP_TEXT_EXACT", message: `Text is token-identical to statement "${prev}"`, ref: s.id });
13349
13698
  } else {
13350
13699
  for (const [otherNorm, otherId] of seen.entries()) {
13351
- const a = new Set(norm.split(" "));
13700
+ const a = new Set(norm2.split(" "));
13352
13701
  const b = new Set(otherNorm.split(" "));
13353
13702
  let inter = 0;
13354
13703
  for (const w of a) if (b.has(w)) inter++;
@@ -13358,7 +13707,7 @@ function lintFrameworkPack(pack) {
13358
13707
  break;
13359
13708
  }
13360
13709
  }
13361
- seen.set(norm, s.id);
13710
+ seen.set(norm2, s.id);
13362
13711
  }
13363
13712
  }
13364
13713
  for (const m of pack.mappings ?? []) {
@@ -26099,6 +26448,229 @@ function extractStandardRefs(text) {
26099
26448
  return Array.from(new Set(matches));
26100
26449
  }
26101
26450
 
26451
+ // src/services/contextSlots.ts
26452
+ var LESSON_SLOT_BUDGETS = {
26453
+ ACT: { budget: 8e3 },
26454
+ GUIDE: { budget: 6e3 },
26455
+ QUIZ: { budget: 3500, priorities: ["A. Lesson Design Plan"] },
26456
+ SLIDE: { budget: 4500, priorities: ["B. Lesson Flow", "A. Lesson Design Plan"] },
26457
+ WKS: { budget: 4500, priorities: ["A. Lesson Design Plan", "B. Lesson Flow"] },
26458
+ EXIT_TICKET: { budget: 3e3, priorities: ["A. Lesson Design Plan", "B. Lesson Flow"] }
26459
+ };
26460
+ var KX_ONLY_TYPES = /* @__PURE__ */ new Set(["CODE", "HANDOUT", "EXT"]);
26461
+ var KX_SLOT_PRIORITIES = {
26462
+ QUIZ: ["Common Mistakes", "Self-Check Questions", "Key Terms", "Concept Narratives"],
26463
+ EXIT_TICKET: ["Self-Check Questions", "Key Terms"],
26464
+ WKS: ["Self-Check Questions", "Worked Micro-Examples", "Key Terms"],
26465
+ CODE: ["Worked Micro-Examples", "Key Terms", "Common Mistakes"],
26466
+ GUIDE: ["Common Mistakes", "Key Terms", "Concept Narratives"],
26467
+ HANDOUT: ["Key Terms", "Concept Narratives", "Common Mistakes"],
26468
+ SLIDE: ["Key Terms", "Worked Micro-Examples"],
26469
+ ACT: ["Worked Micro-Examples", "Common Mistakes"],
26470
+ EXT: ["Worked Micro-Examples", "Common Mistakes"]
26471
+ };
26472
+ var norm = (s) => s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "");
26473
+ function extractActivityContractRows(lessonContent, artifactType, maxChars = 1500) {
26474
+ if (!lessonContent) return "";
26475
+ const wanted = norm(artifactType).replace(/_/g, "");
26476
+ const lines = lessonContent.split("\n");
26477
+ let inSeq = false;
26478
+ const headerRows = [];
26479
+ const matchedRows = [];
26480
+ const allRows = [];
26481
+ for (let i = 0; i < lines.length; i++) {
26482
+ const line = lines[i];
26483
+ const h = line.match(/^#{2,4}\s+(.*)$/);
26484
+ if (h) {
26485
+ const t2 = h[1];
26486
+ if (/activity\s*sequence/i.test(t2)) inSeq = true;
26487
+ else if (inSeq) break;
26488
+ continue;
26489
+ }
26490
+ if (!inSeq) continue;
26491
+ const t = line.trim();
26492
+ if (!t.startsWith("|")) continue;
26493
+ if (/^\|\s*[-: |]+\|\s*$/.test(t)) continue;
26494
+ const cells = t.split("|").slice(1, -1).map((c) => c.trim());
26495
+ if (cells.length >= 4) {
26496
+ if (headerRows.length === 0) {
26497
+ headerRows.push(t);
26498
+ continue;
26499
+ }
26500
+ allRows.push(t);
26501
+ const contract = cells[cells.length - 1] ?? "";
26502
+ const candidates = contract.split(/[,;/]/).map((c) => norm(c));
26503
+ if (candidates.some((c) => c === wanted || c.length > 3 && (c.includes(wanted) || wanted.includes(c)))) {
26504
+ matchedRows.push(t);
26505
+ }
26506
+ }
26507
+ }
26508
+ const rows = matchedRows.length > 0 ? matchedRows : allRows.slice(0, 3);
26509
+ if (rows.length === 0) return "";
26510
+ let out = headerRows[0] ?? "";
26511
+ for (const r of rows) {
26512
+ if (out.length + r.length + 1 > maxChars) break;
26513
+ out += "\n" + r;
26514
+ }
26515
+ return out;
26516
+ }
26517
+ function extractTieredScaffoldingBlock(lessonContent, maxChars = 900) {
26518
+ if (!lessonContent) return "";
26519
+ const lines = lessonContent.split("\n");
26520
+ let capture = null;
26521
+ for (const line of lines) {
26522
+ const h = line.match(/^#{2,4}\s+(.*)$/);
26523
+ if (h) {
26524
+ const isTierHeading = /elaborate|tiered|scaffold/i.test(h[1] ?? "");
26525
+ if (capture && !isTierHeading) break;
26526
+ if (isTierHeading) capture = [];
26527
+ continue;
26528
+ }
26529
+ if (capture) {
26530
+ capture.push(line);
26531
+ if (capture.join("").length >= maxChars) break;
26532
+ }
26533
+ }
26534
+ const body = (capture ?? []).join("\n").trim();
26535
+ return body ? body.slice(0, maxChars) : "";
26536
+ }
26537
+ function extractAssessmentMap(lessonContent, maxChars = 1200) {
26538
+ if (!lessonContent) return "";
26539
+ const lines = lessonContent.split("\n");
26540
+ let capture = null;
26541
+ for (const line of lines) {
26542
+ const h = line.match(/^#{2,4}\s+(.*)$/);
26543
+ if (h) {
26544
+ const isTarget = /assessment\s*map/i.test(h[1] ?? "");
26545
+ if (capture && !isTarget) break;
26546
+ if (isTarget) capture = [];
26547
+ continue;
26548
+ }
26549
+ if (capture) {
26550
+ capture.push(line);
26551
+ if (capture.join("").length >= maxChars) break;
26552
+ }
26553
+ }
26554
+ const body = (capture ?? []).join("\n").trim();
26555
+ return body ? body.slice(0, maxChars) : "";
26556
+ }
26557
+ function blockMeta(slot, source, text, verified, issues = []) {
26558
+ return { slot, source, chars: text.length, verified, issues };
26559
+ }
26560
+ function buildSatelliteContext(input) {
26561
+ const {
26562
+ artifactType,
26563
+ commonContext,
26564
+ lessonContent,
26565
+ lessonExcerpt,
26566
+ expositionContent,
26567
+ slcMarkdown,
26568
+ symbolLedgerBlock,
26569
+ pedagogyLabel,
26570
+ mode = "legacy"
26571
+ } = input;
26572
+ const type = (artifactType || "").toUpperCase().trim();
26573
+ if (mode === "legacy" || type === "LESSON") {
26574
+ const context2 = `${commonContext}
26575
+
26576
+ [CANONICAL LESSON PLAN (${pedagogyLabel ?? "LESSON"})]:
26577
+ ${lessonExcerpt.excerpt}${symbolLedgerBlock}`;
26578
+ return {
26579
+ context: context2,
26580
+ blocks: [
26581
+ blockMeta("commonContext", "COMPOSITE", commonContext, true),
26582
+ blockMeta("lessonExcerpt", "LESSON", lessonExcerpt.excerpt, lessonExcerpt.verified, lessonExcerpt.issues)
26583
+ ],
26584
+ verified: lessonExcerpt.verified,
26585
+ sectionAware: lessonExcerpt.sectionAware,
26586
+ issues: lessonExcerpt.issues,
26587
+ tokenEstimate: Math.round(context2.length / 4)
26588
+ };
26589
+ }
26590
+ const blocks = [];
26591
+ const issues = [];
26592
+ const parts = [commonContext];
26593
+ blocks.push(blockMeta("commonContext", "COMPOSITE", commonContext, true));
26594
+ const compositeHasKx = /###\s+KNOWLEDGE_EXPOSITION/.test(commonContext);
26595
+ if (expositionContent.trim() && !compositeHasKx) {
26596
+ const kxExcerpt = buildSectionAwareExcerpt(expositionContent, {
26597
+ priorities: KX_SLOT_PRIORITIES[type] ?? ["Key Terms", "Concept Narratives", "Worked Micro-Examples"],
26598
+ budget: 4500,
26599
+ sectionLanguageContract: slcMarkdown,
26600
+ artifactType: "KNOWLEDGE_EXPOSITION"
26601
+ });
26602
+ parts.push(`
26603
+
26604
+ [KNOWLEDGE_EXPOSITION (canonical knowledge \u2014 teach from this, do not contradict; scoped for ${type})]:
26605
+ ${kxExcerpt.excerpt}`);
26606
+ blocks.push(blockMeta("kxExcerpt:" + type, "KNOWLEDGE_EXPOSITION", kxExcerpt.excerpt, kxExcerpt.verified, kxExcerpt.issues));
26607
+ if (!kxExcerpt.verified) issues.push(...kxExcerpt.issues.map((i) => `kx:${i}`));
26608
+ }
26609
+ if (!KX_ONLY_TYPES.has(type)) {
26610
+ 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(`
26618
+
26619
+ [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(`
26627
+
26628
+ [ASSESSMENT MAP (criteria anchor \u2014 assess exactly these)]:
26629
+ ${am}`);
26630
+ blocks.push(blockMeta("assessmentMap:" + type, "LESSON", am, true));
26631
+ } else {
26632
+ issues.push(`assessment-map:unresolved:${type}`);
26633
+ }
26634
+ }
26635
+ if (symbolLedgerBlock) {
26636
+ parts.push(symbolLedgerBlock);
26637
+ blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
26638
+ }
26639
+ } 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");
26648
+ if (mini) {
26649
+ parts.push(`
26650
+
26651
+ [LESSON ACTIVITY CONTRACT (mini-slot)]:
26652
+ ${mini}`);
26653
+ blocks.push(blockMeta("activityContract:" + type, "LESSON", mini, true));
26654
+ } else {
26655
+ issues.push(`activity-contract:unresolved:${type}`);
26656
+ blocks.push(blockMeta("activityContract:" + type, "LESSON", "", false, ["no-matching-rows"]));
26657
+ }
26658
+ if (type === "CODE" && symbolLedgerBlock) {
26659
+ parts.push(symbolLedgerBlock);
26660
+ blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
26661
+ }
26662
+ }
26663
+ const context = parts.join("");
26664
+ return {
26665
+ context,
26666
+ blocks,
26667
+ verified: blocks.filter((b) => b.slot !== "commonContext").every((b) => b.verified),
26668
+ sectionAware: blocks.some((b) => b.slot.startsWith("lessonExcerpt") || b.slot.startsWith("kxExcerpt")),
26669
+ issues,
26670
+ tokenEstimate: Math.round(context.length / 4)
26671
+ };
26672
+ }
26673
+
26102
26674
  // src/services/lessonProductionService.ts
26103
26675
  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))) : "";
26104
26676
  var _resolvedDir = __filename2 ? path3__default.default.dirname(__filename2) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
@@ -26671,9 +27243,9 @@ ${renderHorizonPromptBlock(horizon)}`;
26671
27243
  console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
26672
27244
  }
26673
27245
  let productSpecBlock = "";
26674
- const buildGroundTruthBlock = () => {
27246
+ const buildGroundTruthBlock = (includeKx = true) => {
26675
27247
  const parts = [];
26676
- if (expositionContext) {
27248
+ if (includeKx && expositionContext) {
26677
27249
  parts.push(`### KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)
26678
27250
  ${expositionContext}`);
26679
27251
  }
@@ -26707,10 +27279,10 @@ ${sessionSliceContext}` : "";
26707
27279
  const guardrailBlock = `
26708
27280
 
26709
27281
  ${platformGuardrail}`;
26710
- const assembleCommonContext = (sg) => `${baseContextPrefix}
27282
+ const assembleCommonContext = (sg, includeKx = true) => `${baseContextPrefix}
26711
27283
 
26712
27284
  [CONTENT STYLE GUIDE EXCERPT]:
26713
- ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
27285
+ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock(includeKx)}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
26714
27286
  let commonContext = assembleCommonContext(effectiveStyleGuide);
26715
27287
  const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
26716
27288
  const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
@@ -27119,10 +27691,32 @@ ${currentContent}` }],
27119
27691
  - Entry Source File: \`${symbolLedger.entryFileName || symbolLedger.primarySymbol}\`
27120
27692
  - Key Identifiers to inherit verbatim: ${symbolLedger.keySymbols.map((s) => `\`${s}\``).join(", ")}
27121
27693
  - INVARIANT: You MUST use these exact identifier names in all code examples, exercises, and test assertions. Do NOT invent new struct/class names!` : "";
27122
- const satelliteContext = `${commonContext}
27123
-
27124
- [CANONICAL LESSON PLAN (${pedagogyLabel})]:
27125
- ${lessonExcerpt}${symbolLedgerBlock}`;
27694
+ const routingMode = options.contextRoutingMode ?? "legacy";
27695
+ const kxFreeCommonContext = routingMode === "hybrid" ? assembleCommonContext(effectiveStyleGuide, false) : "";
27696
+ const satelliteContexts = {};
27697
+ const satelliteContextFor = (artifactType) => {
27698
+ const key = artifactType.toUpperCase();
27699
+ if (!satelliteContexts[key]) {
27700
+ const built = buildSatelliteContext({
27701
+ artifactType: key,
27702
+ commonContext: routingMode === "hybrid" ? kxFreeCommonContext : commonContext,
27703
+ lessonContent,
27704
+ lessonExcerpt: lessonExcerptResult,
27705
+ expositionContent: expositionContext,
27706
+ slcMarkdown,
27707
+ symbolLedgerBlock,
27708
+ pedagogyLabel,
27709
+ mode: routingMode
27710
+ });
27711
+ satelliteContexts[key] = built.context;
27712
+ onProgress?.(
27713
+ "@content",
27714
+ `[CONTEXT] ${key} satellite context: ${built.context.length} chars (${built.blocks.map((b) => `${b.slot}:${b.chars}`).join(", ")})`,
27715
+ { type: "progress", promptChars: built.context.length, artifactType: key }
27716
+ );
27717
+ }
27718
+ return satelliteContexts[key];
27719
+ };
27126
27720
  const judgeSat = (sat, content) => {
27127
27721
  if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
27128
27722
  const det = validateArtifactDeterministic({ content, refPack });
@@ -27203,7 +27797,7 @@ ${languageDirective}
27203
27797
  ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
27204
27798
  const rawAct = await runCurriculumAIInference(
27205
27799
  [{ role: "user", content: actPrompt }],
27206
- satelliteContext,
27800
+ satelliteContextFor("ACT"),
27207
27801
  runnerOptions,
27208
27802
  (chunk, type) => {
27209
27803
  options.onProgress?.("@activity", chunk, { type: type || "content", artifactType: "ACT" });
@@ -27273,7 +27867,7 @@ ${languageDirective}
27273
27867
  ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
27274
27868
  const rawQuiz = await runCurriculumAIInference(
27275
27869
  [{ role: "user", content: quizPrompt }],
27276
- satelliteContext,
27870
+ satelliteContextFor("QUIZ"),
27277
27871
  runnerOptions,
27278
27872
  (chunk, type) => {
27279
27873
  options.onProgress?.("@assessor", chunk, { type: type || "content", artifactType: "QUIZ" });
@@ -27326,7 +27920,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
27326
27920
  headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
27327
27921
  groundContext: slideGroundContext,
27328
27922
  productSpecBlock: productSpecBlock || void 0,
27329
- satelliteContext,
27923
+ satelliteContext: satelliteContextFor("SLIDE"),
27330
27924
  runnerOptions,
27331
27925
  onProgress: (agent, msg, meta) => {
27332
27926
  options.onProgress?.(agent, msg, meta || { type: "content", artifactType: "SLIDE" });
@@ -27410,7 +28004,7 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
27410
28004
  });
27411
28005
  const rawSlide = await runCurriculumAIInference(
27412
28006
  [{ role: "user", content: slidePrompt }],
27413
- satelliteContext,
28007
+ satelliteContextFor("SLIDE"),
27414
28008
  runnerOptions,
27415
28009
  (chunk, type) => {
27416
28010
  options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
@@ -27471,7 +28065,7 @@ ${languageDirective}
27471
28065
  ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
27472
28066
  const rawGuide = await runCurriculumAIInference(
27473
28067
  [{ role: "user", content: guidePrompt }],
27474
- satelliteContext,
28068
+ satelliteContextFor("GUIDE"),
27475
28069
  runnerOptions,
27476
28070
  (chunk, type) => {
27477
28071
  options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "GUIDE" });
@@ -27523,7 +28117,7 @@ ${languageDirective}
27523
28117
  ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
27524
28118
  const rawHandout = await runCurriculumAIInference(
27525
28119
  [{ role: "user", content: handoutPrompt }],
27526
- satelliteContext,
28120
+ satelliteContextFor("HANDOUT"),
27527
28121
  runnerOptions,
27528
28122
  (chunk, type) => {
27529
28123
  options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "HANDOUT" });
@@ -27579,7 +28173,7 @@ ${languageDirective}
27579
28173
  ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
27580
28174
  const rawWks = await runCurriculumAIInference(
27581
28175
  [{ role: "user", content: wksPrompt }],
27582
- satelliteContext,
28176
+ satelliteContextFor("WKS"),
27583
28177
  runnerOptions,
27584
28178
  (chunk, type) => {
27585
28179
  options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "WKS" });
@@ -27639,7 +28233,7 @@ ${languageDirective}
27639
28233
  ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
27640
28234
  const rawCode = await runCurriculumAIInference(
27641
28235
  [{ role: "user", content: codePrompt }],
27642
- satelliteContext,
28236
+ satelliteContextFor("CODE"),
27643
28237
  runnerOptions,
27644
28238
  (chunk, type) => {
27645
28239
  options.onProgress?.("@activity", chunk, { type: type || "chunk", artifactType: "CODE" });
@@ -27726,7 +28320,7 @@ ${languageDirective}
27726
28320
  ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
27727
28321
  const rawExt = await runCurriculumAIInference(
27728
28322
  [{ role: "user", content: extPrompt }],
27729
- satelliteContext,
28323
+ satelliteContextFor("EXT"),
27730
28324
  runnerOptions,
27731
28325
  (chunk, type) => {
27732
28326
  options.onProgress?.("@activity", chunk, { type: type || "chunk", artifactType: "EXT" });
@@ -29703,26 +30297,40 @@ Return concise JSON matching:
29703
30297
  "technicalGotchas": ["Important safety or compatibility note 1", "Note 2"]
29704
30298
  }`;
29705
30299
  let rawContent = "";
29706
- await streamLLMWithFallback(
29707
- "You are an authoritative STEM & CS curriculum researcher. Output valid JSON only.",
29708
- researchPrompt,
29709
- apiKeys,
29710
- (chunk, type) => {
29711
- if (type === "content") rawContent += chunk;
29712
- onChunk?.(chunk, type);
29713
- },
29714
- options.model,
29715
- options.provider,
29716
- // Research may run live web grounding — generous budget, still idle-guarded.
29717
- resolveStreamBudget(void 0, {
29718
- idleMs: options.idleTimeoutMs,
29719
- totalMs: options.timeoutMs,
29720
- model: options.model,
29721
- provider: options.provider
29722
- }),
29723
- options.signal,
29724
- options.onProviderEvent
29725
- );
30300
+ if (options.customInference) {
30301
+ rawContent = await options.customInference({
30302
+ systemInstruction: "You are an authoritative STEM & CS curriculum researcher. Output valid JSON only.",
30303
+ userPrompt: researchPrompt,
30304
+ messages: [{ role: "user", content: researchPrompt }],
30305
+ temperature: 0.2,
30306
+ maxTokens: 32768,
30307
+ onChunk: (token, type) => {
30308
+ if (type === "content") rawContent += token;
30309
+ if (type === "content" || type === "thought") onChunk?.(token, type);
30310
+ }
30311
+ }) || "";
30312
+ } else {
30313
+ await streamLLMWithFallback(
30314
+ "You are an authoritative STEM & CS curriculum researcher. Output valid JSON only.",
30315
+ researchPrompt,
30316
+ apiKeys,
30317
+ (chunk, type) => {
30318
+ if (type === "content") rawContent += chunk;
30319
+ onChunk?.(chunk, type);
30320
+ },
30321
+ options.model,
30322
+ options.provider,
30323
+ // Research may run live web grounding — generous budget, still idle-guarded.
30324
+ resolveStreamBudget(void 0, {
30325
+ idleMs: options.idleTimeoutMs,
30326
+ totalMs: options.timeoutMs,
30327
+ model: options.model,
30328
+ provider: options.provider
30329
+ }),
30330
+ options.signal,
30331
+ options.onProviderEvent
30332
+ );
30333
+ }
29726
30334
  let parsedResearch = {
29727
30335
  groundTruthSummary: `Verified ecosystem standards for ${projectName}`,
29728
30336
  hardwareVersion: "Standard Environment",
@@ -30812,6 +31420,75 @@ var DeterministicStructuralLinter = class {
30812
31420
  strengths
30813
31421
  };
30814
31422
  }
31423
+ /**
31424
+ * P50: Validates macro-pedagogical invariants across a CurriculumPlan.
31425
+ * Checks:
31426
+ * 1. ZPD violations without self-healing recap (NO_ZPD_BRIDGE)
31427
+ * 2. Walking Skeleton existence (Unit 1 delivers working software)
31428
+ * 3. Inter-unit Mastery Gate presence
31429
+ */
31430
+ static lintPlan(plan) {
31431
+ const findings = [];
31432
+ const strengths = [];
31433
+ let score = 100;
31434
+ for (const s of plan.sessions) {
31435
+ if (s.zpd_status?.verdict === "TOO_MANY_NEW") {
31436
+ score -= 10;
31437
+ findings.push({
31438
+ id: `PLAN_TOO_MANY_NEW_${s.id}`,
31439
+ dimension: "CONSTRUCTIVE_ALIGNMENT",
31440
+ severity: "MINOR",
31441
+ title: `Cognitive Overload in Session ${s.id}`,
31442
+ description: s.zpd_status.issues.join("; ") || `Session introduces too many new concepts/keywords, exceeding ZPD capacity.`,
31443
+ remediationAdvice: `Distribute new concepts across multiple sessions or introduce via scaffolding.`,
31444
+ affectedElement: s.id
31445
+ });
31446
+ } else if (s.zpd_status?.verdict === "NO_ZPD_BRIDGE") {
31447
+ const hasRecap = s.prerequisite_decisions.some((d) => d.decision === "recap_in_lesson");
31448
+ if (!hasRecap) {
31449
+ score -= 15;
31450
+ findings.push({
31451
+ id: `PLAN_NO_ZPD_BRIDGE_${s.id}`,
31452
+ dimension: "CONSTRUCTIVE_ALIGNMENT",
31453
+ severity: "MAJOR",
31454
+ title: `Unhealed ZPD Bridge in Session ${s.id}`,
31455
+ description: `Session introduces new concepts with 0 known concept bridges, and lacks recap scaffolding.`,
31456
+ remediationAdvice: `Add a recap_in_lesson prerequisite decision to anchor new learning in prior concepts.`,
31457
+ affectedElement: s.id
31458
+ });
31459
+ }
31460
+ }
31461
+ }
31462
+ if (!plan.walking_skeleton || plan.walking_skeleton.epitome_deliverables.length === 0) {
31463
+ score -= 10;
31464
+ findings.push({
31465
+ id: "PLAN_MISSING_WALKING_SKELETON",
31466
+ dimension: "CONSTRUCTIVE_ALIGNMENT",
31467
+ severity: "MINOR",
31468
+ title: "Missing Walking Skeleton Definition",
31469
+ description: "Plan does not declare an Epitome (walking skeleton) in Unit 1.",
31470
+ remediationAdvice: "Ensure Unit 1 defines minimal end-to-end deliverables."
31471
+ });
31472
+ } else {
31473
+ strengths.push(`Unit ${plan.walking_skeleton.epitome_unit_id} establishes an end-to-end Walking Skeleton (Epitome).`);
31474
+ }
31475
+ if (plan.units.length > 1 && (!plan.mastery_gates || plan.mastery_gates.length === 0)) {
31476
+ score -= 10;
31477
+ findings.push({
31478
+ id: "PLAN_MISSING_MASTERY_GATES",
31479
+ dimension: "5E_INSTRUCTIONAL_FIDELITY",
31480
+ severity: "MINOR",
31481
+ title: "Missing Inter-Unit Mastery Gates",
31482
+ description: "Multi-unit course lacks formal transition gates between units.",
31483
+ remediationAdvice: "Define exit criteria and remediation sprints for each inter-unit boundary."
31484
+ });
31485
+ } else if (plan.mastery_gates && plan.mastery_gates.length > 0) {
31486
+ strengths.push(`${plan.mastery_gates.length} inter-unit Mastery Gates defined with remediation protocols.`);
31487
+ }
31488
+ score = Math.max(0, Math.min(100, score));
31489
+ const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
31490
+ return { passed, score, findings, strengths };
31491
+ }
30815
31492
  };
30816
31493
 
30817
31494
  // src/evaluators/academicAuditor.ts
@@ -32482,6 +33159,8 @@ exports.CodeLabSchema = CodeLabSchema;
32482
33159
  exports.CodeSnippetSchema = CodeSnippetSchema;
32483
33160
  exports.CompetencyRubricRowSchema = CompetencyRubricRowSchema;
32484
33161
  exports.ComputationalThinkingSchema = ComputationalThinkingSchema;
33162
+ exports.ConceptPrerequisiteEdgeSchema = ConceptPrerequisiteEdgeSchema;
33163
+ exports.ConceptSpiralEncounterSchema = ConceptSpiralEncounterSchema;
32485
33164
  exports.ConstructiveAlignmentEvaluator = ConstructiveAlignmentEvaluator;
32486
33165
  exports.CoreConceptSchema = CoreConceptSchema;
32487
33166
  exports.CourseObjectiveSchema = CourseObjectiveSchema;
@@ -32556,6 +33235,7 @@ exports.LessonSectionSchema = LessonSectionSchema;
32556
33235
  exports.LocalWorkspaceManager = LocalWorkspaceManager;
32557
33236
  exports.MappingKindSchema = MappingKindSchema;
32558
33237
  exports.MarpSlideSchema = MarpSlideSchema;
33238
+ exports.MasteryGateSchema = MasteryGateSchema;
32559
33239
  exports.MediaError = MediaError;
32560
33240
  exports.MediaLedger = MediaLedger;
32561
33241
  exports.MentorCheatsheetSchema = MentorCheatsheetSchema;
@@ -32609,6 +33289,7 @@ exports.SelfLabSchema = SelfLabSchema;
32609
33289
  exports.SelfPacedBundleSchema = SelfPacedBundleSchema;
32610
33290
  exports.SelfPacedChallengeSchema = SelfPacedChallengeSchema;
32611
33291
  exports.SessionPlanSchema = SessionPlanSchema;
33292
+ exports.SessionZpdStatusSchema = SessionZpdStatusSchema;
32612
33293
  exports.SilverTierSchema = SilverTierSchema;
32613
33294
  exports.SlideDeckSchema = SlideDeckSchema;
32614
33295
  exports.StandardizedTermRowSchema = StandardizedTermRowSchema;
@@ -32633,20 +33314,24 @@ exports.TroubleshootingRowSchema = TroubleshootingRowSchema;
32633
33314
  exports.UnitPlanSchema = UnitPlanSchema;
32634
33315
  exports.UserJourneySchema = UserJourneySchema;
32635
33316
  exports.WORKSHEET_TEMPLATE = WORKSHEET_TEMPLATE;
33317
+ exports.WalkingSkeletonSchema = WalkingSkeletonSchema;
32636
33318
  exports.WorksheetItemSchema = WorksheetItemSchema;
32637
33319
  exports.WorksheetItemTypeEnum = WorksheetItemTypeEnum;
32638
33320
  exports.WorksheetPartSchema = WorksheetPartSchema;
32639
33321
  exports.WorksheetSchema = WorksheetSchema;
33322
+ exports.ZpdVerdictSchema = ZpdVerdictSchema;
32640
33323
  exports.activityTools = activityTools;
32641
33324
  exports.analystTools = analystTools;
32642
33325
  exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
32643
33326
  exports.assertAcyclic = assertAcyclic;
32644
33327
  exports.assessorTools = assessorTools;
33328
+ exports.assignDepths = assignDepths;
32645
33329
  exports.atomicWriteFileSync = atomicWriteFileSync;
32646
33330
  exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
32647
33331
  exports.auditQualityReport = auditQualityReport;
32648
33332
  exports.buildActivityPrompt = buildActivityPrompt;
32649
33333
  exports.buildCodeLabPrompt = buildCodeLabPrompt;
33334
+ exports.buildConceptPrerequisites = buildConceptPrerequisites;
32650
33335
  exports.buildCurriculumContext = buildCurriculumContext;
32651
33336
  exports.buildCurriculumPlan = buildCurriculumPlan;
32652
33337
  exports.buildDeliveryPackages = buildDeliveryPackages;
@@ -32664,7 +33349,9 @@ exports.buildLanguageDirective = buildLanguageDirective;
32664
33349
  exports.buildLessonExcerpt = buildLessonExcerpt;
32665
33350
  exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
32666
33351
  exports.buildMarpMarkdownSlidePrompt = buildMarpMarkdownSlidePrompt;
33352
+ exports.buildMasteryGates = buildMasteryGates;
32667
33353
  exports.buildProjectInstructionPrompt = buildProjectInstructionPrompt;
33354
+ exports.buildSatelliteContext = buildSatelliteContext;
32668
33355
  exports.buildSectionAwareExcerpt = buildSectionAwareExcerpt;
32669
33356
  exports.buildSelfLabPrompt = buildSelfLabPrompt;
32670
33357
  exports.buildSessionSliceContext = buildSessionSliceContext;
@@ -32673,8 +33360,11 @@ exports.buildStandardStackMarkdown = buildStandardStackMarkdown;
32673
33360
  exports.buildStandardsContext = buildStandardsContext;
32674
33361
  exports.buildStandardsContextBlock = buildStandardsContextBlock;
32675
33362
  exports.buildTeacherGuidePrompt = buildTeacherGuidePrompt;
33363
+ exports.buildWalkingSkeleton = buildWalkingSkeleton;
32676
33364
  exports.buildWorksheetPrompt = buildWorksheetPrompt;
33365
+ exports.checkSessionZpd = checkSessionZpd;
32677
33366
  exports.closeTruncatedJson = closeTruncatedJson;
33367
+ exports.computeConceptSpiralProgression = computeConceptSpiralProgression;
32678
33368
  exports.computeContentHash = computeContentHash;
32679
33369
  exports.computePackingSpec = computePackingSpec;
32680
33370
  exports.conductPreliminaryResearch = conductPreliminaryResearch;
@@ -32690,6 +33380,7 @@ exports.createStreamChunkExtractor = createStreamChunkExtractor;
32690
33380
  exports.curateMediaLedger = curateMediaLedger;
32691
33381
  exports.designerTools = designerTools;
32692
33382
  exports.detectProjectPedagogy = detectProjectPedagogy;
33383
+ exports.dropCyclicConceptEdges = dropCyclicConceptEdges;
32693
33384
  exports.emitUsage = emitUsage;
32694
33385
  exports.ensureExpositionForLesson = ensureExpositionForLesson;
32695
33386
  exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
@@ -32698,6 +33389,8 @@ exports.executeCurriculumCommand = executeCurriculumCommand;
32698
33389
  exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
32699
33390
  exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
32700
33391
  exports.expositionCacheKey = expositionCacheKey;
33392
+ exports.extractActivityContractRows = extractActivityContractRows;
33393
+ exports.extractAssessmentMap = extractAssessmentMap;
32701
33394
  exports.extractCurriculumHorizon = extractCurriculumHorizon;
32702
33395
  exports.extractJsonArray = extractJsonArray;
32703
33396
  exports.extractScopeSequenceRows = extractScopeSequenceRows;
@@ -32707,6 +33400,7 @@ exports.extractStandardRefs = extractStandardRefs;
32707
33400
  exports.extractStreamChunk = extractStreamChunk;
32708
33401
  exports.extractSymbolLedger = extractSymbolLedger;
32709
33402
  exports.extractThoughtAndContent = extractThoughtAndContent;
33403
+ exports.extractTieredScaffoldingBlock = extractTieredScaffoldingBlock;
32710
33404
  exports.findStandardStatement = findStandardStatement;
32711
33405
  exports.formatQuizzesToCsv = formatQuizzesToCsv;
32712
33406
  exports.fulfillMediaLedger = fulfillMediaLedger;