@thanh01.pmt/curriculum-kit 1.4.47 → 1.4.49

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
@@ -4019,7 +4019,25 @@ var PlanningNodeSchema = zod.z.object({
4019
4019
  file: zod.z.string(),
4020
4020
  evidence: zod.z.string().default("")
4021
4021
  })).default([]),
4022
- phase_id: zod.z.string().default("")
4022
+ phase_id: zod.z.string().default(""),
4023
+ // P52/T2.1: tangible user-visible outcome projected from the graph step
4024
+ // (checkpoint candidate for session cutting). Empty = not a checkpoint.
4025
+ user_visible_deliverable: zod.z.string().default(""),
4026
+ // P52/T3.1 — depth-reconcile inputs (mirrored from the feed; optional so
4027
+ // raw graphs without variants keep planning unchanged):
4028
+ // • depth_variants: minutes to teach this node at each depth level (ULO ≤
4029
+ // CIO ≤ SIO) — the reconciler's price list for downgrades.
4030
+ // • depth_scaffold_candidates: parallel to scaffold_candidates but acting
4031
+ // on DEPTH (SIO→CIO, CIO→ULO) instead of lesson time.
4032
+ // • is_core: Master-Tree core concept — never downgraded; escalate instead.
4033
+ depth_variants: zod.z.object({ ulo: zod.z.number().int().nonnegative(), cio: zod.z.number().int().nonnegative(), sio: zod.z.number().int().nonnegative() }).optional(),
4034
+ depth_scaffold_candidates: zod.z.array(zod.z.object({
4035
+ from_depth: DepthLevelSchema,
4036
+ to_depth: DepthLevelSchema,
4037
+ minutes_saved: zod.z.number().int().nonnegative(),
4038
+ reason: zod.z.string().default("")
4039
+ })).optional(),
4040
+ is_core: zod.z.boolean().default(false)
4023
4041
  });
4024
4042
  var DependencyEdgeSchema = zod.z.object({
4025
4043
  from: zod.z.string(),
@@ -4857,9 +4875,9 @@ Evaluate the candidate artifact across these 6 dimensions:
4857
4875
  - 0 pts: Missing critical sections.
4858
4876
 
4859
4877
  5. **Technical & Conceptual Accuracy (15 pts):**
4860
- - 15 pts: Code snippets, logic flows, architectural descriptions, and Mermaid diagrams are conceptually sound and valid. If \`canonicalExposition\` or \`[KNOWLEDGE_EXPOSITION]\` is provided, content strictly adheres to canonical concepts without contradicting definitions or examples.
4878
+ - 15 pts: Code snippets, logic flows, architectural descriptions, and Mermaid diagrams are conceptually sound and valid. Every code identifier uses the REAL standard-library/API spelling of the declared stack (grounded in REFERENCE_PACK / canonical LESSON) \u2014 internal consistency with a glossary term name is NOT evidence of correctness. If \`canonicalExposition\` or \`[KNOWLEDGE_EXPOSITION]\` is provided, content strictly adheres to canonical concepts without contradicting definitions or examples.
4861
4879
  - 8 pts: Minor syntax inaccuracies or unidiomatic code that does not break core concepts.
4862
- - 0 pts: Severe technical hallucinations, broken logic, invalid diagrams, or direct contradiction of canonical exposition knowledge.
4880
+ - 0 pts: Severe technical hallucinations, broken logic, invalid diagrams, invented casing/spelling rules for standard-library identifiers, or direct contradiction of canonical exposition knowledge.
4863
4881
 
4864
4882
  6. **Contract Consistency & Zero-Drift (10 pts):**
4865
4883
  - 10 pts: Satellite artifact strictly adheres to the scope, tech stack, and objectives of Lesson ${lessonId} without introducing unrelated topics. If \`canonicalLessonExcerpt\` is present in candidate JSON, all satellite phases/activities strictly operationalize the master Lesson Flow and Activity Sequence without inventing divergent timelines.
@@ -5436,6 +5454,7 @@ var MAX_NEW_CONCEPTS_PER_SESSION_K12 = 2;
5436
5454
  var MAX_NEW_CONCEPTS_PER_SESSION_ADULT = 3;
5437
5455
  var DEFAULT_OVERHEAD_RATIO = 0.15;
5438
5456
  var MAX_IN_SESSION_SETUP_MINUTES = 15;
5457
+ var SESSION_CUT_TOLERANCE = 0.15;
5439
5458
  var ENVIRONMENT_PROVISIONING_MODELS = [
5440
5459
  "PRE_INSTALLED_LAB",
5441
5460
  "CLOUD_MANAGED",
@@ -10189,6 +10208,38 @@ function checkScopeDrift(content, scopedKeywords, tolerance = 0) {
10189
10208
  evidence: drift
10190
10209
  };
10191
10210
  }
10211
+ function checkIdentifierCasing(content, refPack) {
10212
+ if (!refPack || !refPack.trim()) return null;
10213
+ const canonical = /* @__PURE__ */ new Map();
10214
+ const remember = (id) => {
10215
+ if (id.length < 2) return;
10216
+ const key = id.toLowerCase().replace(/_/g, "");
10217
+ if (!canonical.has(key)) canonical.set(key, id);
10218
+ };
10219
+ for (const m of refPack.matchAll(/`([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) remember(m[1]);
10220
+ for (const m of refPack.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) remember(m[1]);
10221
+ for (const m of refPack.matchAll(/`([A-Za-z_][A-Za-z0-9_]*)`/g)) remember(m[1]);
10222
+ if (canonical.size === 0) return null;
10223
+ const RESERVED = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "func", "return", "guard", "catch", "else", "in"]);
10224
+ const violations = [];
10225
+ const fenceRe = /```[a-zA-Z]*\n([\s\S]*?)```/g;
10226
+ for (const fence of content.matchAll(fenceRe)) {
10227
+ for (const m of fence[1].matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) {
10228
+ const tok = m[1];
10229
+ if (RESERVED.has(tok.toLowerCase())) continue;
10230
+ const real = canonical.get(tok.toLowerCase().replace(/_/g, ""));
10231
+ if (real && real !== tok && !violations.includes(`${tok} \u2192 ${real}`)) {
10232
+ violations.push(`${tok} \u2192 ${real}`);
10233
+ }
10234
+ }
10235
+ }
10236
+ if (violations.length === 0) return null;
10237
+ return {
10238
+ code: "identifier-casing",
10239
+ detail: `Code d\xF9ng identifier l\u1EC7ch casing so v\u1EDBi REFERENCE_PACK (ngu\u1ED3n s\u1EF1 th\u1EADt k\u1EF9 thu\u1EADt): ${violations.join(", ")}. Identifier trong code PH\u1EA2I copy nguy\xEAn d\u1EA1ng t\u1EEB REFERENCE_PACK \u2014 KH\xD4NG suy ra casing t\u1EEB t\xEAn keyword/concept (ch\xFAng l\xE0 nh\xE3n s\u01B0 ph\u1EA1m, kh\xF4ng ph\u1EA3i API).`,
10240
+ evidence: violations.slice(0, 5)
10241
+ };
10242
+ }
10192
10243
  function validateArtifactDeterministic(input) {
10193
10244
  const issues = [];
10194
10245
  const cjk = scanCjkLeaks(input.content);
@@ -10196,6 +10247,8 @@ function validateArtifactDeterministic(input) {
10196
10247
  if (input.refPack) {
10197
10248
  const ver = checkVersionGroundTruth(input.content, input.refPack);
10198
10249
  if (ver) issues.push(ver);
10250
+ const casing = checkIdentifierCasing(input.content, input.refPack);
10251
+ if (casing) issues.push(casing);
10199
10252
  }
10200
10253
  if (input.scopedKeywords && input.scopedKeywords.length > 0) {
10201
10254
  const scope = checkScopeDrift(input.content, input.scopedKeywords);
@@ -10307,9 +10360,10 @@ function buildSystemPrompt(targetLanguage = "vi", techStack, hardwarePlatform) {
10307
10360
  "Rules:",
10308
10361
  "1. Cover EXACTLY the scoped keywords and concepts \u2014 nothing beyond the session scope.",
10309
10362
  "2. Every Key Term definition must be consistent with the glossary entry provided.",
10310
- "3. Respect each concept depth target (ULO/CIO/SIO) \u2014 do not overshoot.",
10311
- "4. Student-facing only: no teacher instructions, no classroom management text.",
10312
- "5. Output the full markdown document following the provided section skeleton exactly (headers and placeholders preserved).",
10363
+ '3. IDENTIFIER GROUND TRUTH: glossary/keyword names are pedagogical concept labels \u2014 NEVER derive code identifier casing or spelling from them. In code samples, copy identifiers EXACTLY from the REFERENCE_PACK ground truth (e.g. the standard library function is `print` lowercase; "Print" as a topic label is not an API symbol).',
10364
+ "4. Respect each concept depth target (ULO/CIO/SIO) \u2014 do not overshoot.",
10365
+ "5. Student-facing only: no teacher instructions, no classroom management text.",
10366
+ "6. Output the full markdown document following the provided section skeleton exactly (headers and placeholders preserved).",
10313
10367
  domainGuardrails,
10314
10368
  langDirective
10315
10369
  ].join("\n");
@@ -10547,6 +10601,98 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
10547
10601
  return { relPath: result.relPath, context: buildExpositionContext(result.content), reused: result.reused, rawContent: result.content };
10548
10602
  }
10549
10603
  init_errors();
10604
+
10605
+ // src/services/depthReconciler.ts
10606
+ function tierOf(node, isLeaf) {
10607
+ if (node.phase_id !== "" && /__ADV\d{2}$/.test(node.id)) return 1;
10608
+ const depth = node.depth_hint ?? "cio";
10609
+ if (depth === "sio") return isLeaf ? 2 : 3;
10610
+ if (depth === "cio") return isLeaf ? 4 : 5;
10611
+ return -1;
10612
+ }
10613
+ function classifyDepthCandidates(nodes, edges) {
10614
+ const baseConcept = (id) => id.replace(/__(ADV|PREV)\d{2}$/, "");
10615
+ const conceptIds = new Set(nodes.filter((n) => n.kind === "concept").map((n) => baseConcept(n.id)));
10616
+ const hasDependent = /* @__PURE__ */ new Set();
10617
+ for (const e of edges) {
10618
+ if (e.kind !== "knowledge") continue;
10619
+ const from = baseConcept(e.from);
10620
+ const to = baseConcept(e.to);
10621
+ if (conceptIds.has(from) && conceptIds.has(to)) hasDependent.add(from);
10622
+ }
10623
+ const isLeaf = /* @__PURE__ */ new Map();
10624
+ for (const n of nodes) {
10625
+ if (n.kind !== "concept") continue;
10626
+ const base = baseConcept(n.id);
10627
+ if (!isLeaf.has(base)) isLeaf.set(base, !hasDependent.has(base));
10628
+ }
10629
+ return isLeaf;
10630
+ }
10631
+ function reconcilePlanDepth(nodes, edges, spec, budgetMinutes) {
10632
+ const MAX_ROUNDS = 5;
10633
+ const total = () => nodes.reduce((acc, n) => acc + n.estimated_minutes, 0);
10634
+ const isLeaf = classifyDepthCandidates(nodes, edges);
10635
+ const baseConcept = (id) => id.replace(/__(ADV|PREV)\d{2}$/, "");
10636
+ const applied = [];
10637
+ const mutated = /* @__PURE__ */ new Set();
10638
+ let rounds = 0;
10639
+ while (total() > budgetMinutes && rounds < MAX_ROUNDS) {
10640
+ rounds++;
10641
+ let overBy = total() - budgetMinutes;
10642
+ const candidates = [];
10643
+ for (let i = 0; i < nodes.length; i++) {
10644
+ const n = nodes[i];
10645
+ if (n.kind !== "concept") continue;
10646
+ if (n.is_core) continue;
10647
+ if (n.depth_hint == null) continue;
10648
+ const base = baseConcept(n.id);
10649
+ const leaf = isLeaf.get(base) ?? true;
10650
+ const tier = tierOf(n, leaf);
10651
+ if (tier < 0) continue;
10652
+ const depth = n.depth_hint ?? "cio";
10653
+ const toDepth = depth === "sio" ? "cio" : "ulo";
10654
+ const variants = n.depth_variants;
10655
+ const target = variants ? variants[toDepth] : Math.max(5, Math.round(n.estimated_minutes * 0.6));
10656
+ const saved = n.estimated_minutes - target;
10657
+ if (saved <= 0) continue;
10658
+ candidates.push({
10659
+ nodeId: n.id,
10660
+ fromDepth: depth,
10661
+ toDepth,
10662
+ minutesSaved: saved,
10663
+ reason: n.depth_scaffold_candidates?.find((c) => c.to_depth === toDepth)?.reason ?? `depth downgrade ${depth}\u2192${toDepth} (tier ${tier}${leaf ? ", leaf" : ""})`,
10664
+ node: n,
10665
+ tier,
10666
+ index: i
10667
+ });
10668
+ }
10669
+ if (candidates.length === 0) break;
10670
+ candidates.sort((a, b) => a.tier - b.tier || b.minutesSaved - a.minutesSaved);
10671
+ for (const c of candidates) {
10672
+ if (overBy <= 0) break;
10673
+ const n = c.node;
10674
+ const target = n.depth_variants ? n.depth_variants[c.toDepth] : Math.max(5, Math.round(n.estimated_minutes * 0.6));
10675
+ const saved = n.estimated_minutes - target;
10676
+ if (saved <= 0) continue;
10677
+ n.estimated_minutes = target;
10678
+ n.depth_hint = c.toDepth;
10679
+ applied.push({ nodeId: n.id, fromDepth: c.fromDepth, toDepth: c.toDepth, minutesSaved: saved, reason: c.reason });
10680
+ mutated.add(n.id);
10681
+ overBy -= saved;
10682
+ }
10683
+ }
10684
+ const finalTotal = total();
10685
+ const deficitMinutes = Math.max(0, finalTotal - budgetMinutes);
10686
+ return {
10687
+ unchanged: applied.length === 0,
10688
+ applied,
10689
+ mutatedNodeIds: [...mutated],
10690
+ escalate: deficitMinutes > 0,
10691
+ deficitMinutes
10692
+ };
10693
+ }
10694
+
10695
+ // src/services/curriculumPlannerService.ts
10550
10696
  var asArr = (v) => Array.isArray(v) ? v : [];
10551
10697
  var asStr = (v, dflt = "") => typeof v === "string" ? v : dflt;
10552
10698
  var asNum = (v, dflt) => typeof v === "number" && Number.isFinite(v) ? v : dflt;
@@ -10586,7 +10732,20 @@ function normalizePlanningGraph(raw) {
10586
10732
  suggest: asStr(c.suggest, "provide_full")
10587
10733
  })),
10588
10734
  references: asArr(n.references).map((ref) => ({ file: asStr(ref.file), evidence: asStr(ref.evidence) })).filter((ref) => ref.file),
10589
- phase_id: asStr(n.phase_id)
10735
+ phase_id: asStr(n.phase_id),
10736
+ user_visible_deliverable: asStr(n.user_visible_deliverable),
10737
+ depth_variants: (() => {
10738
+ const dv = n.depth_variants;
10739
+ if (!dv) return void 0;
10740
+ return { ulo: Math.max(0, Math.round(asNum(dv.ulo, 0))), cio: Math.max(0, Math.round(asNum(dv.cio, 0))), sio: Math.max(0, Math.round(asNum(dv.sio, 0))) };
10741
+ })(),
10742
+ depth_scaffold_candidates: asArr(n.depth_scaffold_candidates).map((c) => ({
10743
+ from_depth: asStr(c.from_depth, "sio"),
10744
+ to_depth: asStr(c.to_depth, "cio"),
10745
+ minutes_saved: Math.max(0, Math.round(asNum(c.minutes_saved, 0))),
10746
+ reason: asStr(c.reason)
10747
+ })),
10748
+ is_core: n.is_core === true
10590
10749
  });
10591
10750
  }
10592
10751
  for (const e of asArr(r.dependency_edges)) {
@@ -10630,7 +10789,9 @@ function normalizePlanningGraph(raw) {
10630
10789
  completion_level: "standard",
10631
10790
  scaffold_candidates: [],
10632
10791
  references: [],
10633
- phase_id: ""
10792
+ phase_id: "",
10793
+ user_visible_deliverable: "",
10794
+ is_core: false
10634
10795
  });
10635
10796
  for (const p of asStrArr(c.prerequisites)) {
10636
10797
  edges.push({ from: p, to: asStr(c.id), kind: "knowledge", reason: "concept prerequisite" });
@@ -10667,7 +10828,9 @@ function normalizePlanningGraph(raw) {
10667
10828
  completion_level: "standard",
10668
10829
  scaffold_candidates: [],
10669
10830
  references: [],
10670
- phase_id: ""
10831
+ phase_id: "",
10832
+ user_visible_deliverable: "",
10833
+ is_core: false
10671
10834
  });
10672
10835
  if (prev) edges.push({ from: prev, to: id, kind: "task", reason: "roadmap sequence" });
10673
10836
  prev = id;
@@ -10703,13 +10866,15 @@ function normalizePlanningGraph(raw) {
10703
10866
  completion_level: "standard",
10704
10867
  scaffold_candidates: [],
10705
10868
  references: [],
10706
- phase_id: ""
10869
+ phase_id: "",
10870
+ user_visible_deliverable: "",
10871
+ is_core: false
10707
10872
  });
10708
10873
  if (prev) edges.push({ from: prev, to: id, kind: "task", reason: "feature build order" });
10709
10874
  prev = id;
10710
10875
  }
10711
10876
  if (stepCount === 0 && fid) {
10712
- nodes.push({ id: fid, kind: "skill", name: asStr(f.name, fid), concept_codes: [], keywords: [], new_keywords: [], prerequisite_keywords: [], estimated_minutes: 45, bloom_hint: "Apply", depth_hint: null, completion_level: "standard", scaffold_candidates: [], references: [], phase_id: "" });
10877
+ nodes.push({ id: fid, kind: "skill", name: asStr(f.name, fid), concept_codes: [], keywords: [], new_keywords: [], prerequisite_keywords: [], estimated_minutes: 45, bloom_hint: "Apply", depth_hint: null, completion_level: "standard", scaffold_candidates: [], references: [], phase_id: "", user_visible_deliverable: "", is_core: false });
10713
10878
  }
10714
10879
  }
10715
10880
  }
@@ -10871,6 +11036,39 @@ function computePackingSpec(constraints) {
10871
11036
  sessionDurationMinutes: constraints.session_duration_minutes
10872
11037
  };
10873
11038
  }
11039
+ var filledOf = (s) => s.knowledgeMinutes + s.practiceMinutes;
11040
+ function cutBackToCheckpoint(current, nodeById, spec, warnings) {
11041
+ const budget = spec.contentBudget;
11042
+ const minFill = budget * (1 - SESSION_CUT_TOLERANCE);
11043
+ const hasPartAfter = new Array(current.entries.length).fill(false);
11044
+ let seenPart = false;
11045
+ for (let i = current.entries.length - 1; i >= 0; i--) {
11046
+ hasPartAfter[i] = seenPart;
11047
+ if (current.entries[i].totalParts > 1) seenPart = true;
11048
+ }
11049
+ let bestIdx = -1;
11050
+ let cumulative = 0;
11051
+ for (let i = 0; i < current.entries.length; i++) {
11052
+ const e = current.entries[i];
11053
+ cumulative += e.minutes;
11054
+ if (hasPartAfter[i] || e.totalParts > 1) continue;
11055
+ const n = nodeById.get(e.id);
11056
+ if (!n || n.user_visible_deliverable.trim().length === 0) continue;
11057
+ if (cumulative > budget || cumulative < minFill) continue;
11058
+ bestIdx = i;
11059
+ }
11060
+ if (bestIdx < 0) {
11061
+ warnings.push("no-checkpoint-within-tolerance: session cut at " + filledOf(current) + "m without a user-visible deliverable");
11062
+ return [];
11063
+ }
11064
+ const displacedEntries = current.entries.splice(bestIdx + 1);
11065
+ for (const e of displacedEntries) {
11066
+ if (nodeById.get(e.id)?.kind === "concept") current.knowledgeMinutes -= e.minutes;
11067
+ else current.practiceMinutes -= e.minutes;
11068
+ }
11069
+ current.nodeIds = current.entries.map((e) => e.id);
11070
+ return displacedEntries.map((e) => nodeById.get(e.id)).filter((n) => n != null);
11071
+ }
10874
11072
  function packUnitSessions(group, nodeById, spec, warnings) {
10875
11073
  const sessions = [];
10876
11074
  const fresh = () => ({ groupId: group.key, nodeIds: [], entries: [], knowledgeMinutes: 0, practiceMinutes: 0, oversized: false });
@@ -10880,7 +11078,9 @@ function packUnitSessions(group, nodeById, spec, warnings) {
10880
11078
  current = fresh();
10881
11079
  };
10882
11080
  const filled = () => current.knowledgeMinutes + current.practiceMinutes;
10883
- for (const id of group.orderedIds) {
11081
+ const ids = [...group.orderedIds];
11082
+ while (ids.length > 0) {
11083
+ const id = ids.shift();
10884
11084
  const node = nodeById.get(id);
10885
11085
  const isKnowledge = node.kind === "concept";
10886
11086
  if (node.estimated_minutes > spec.contentBudget) {
@@ -10916,7 +11116,16 @@ function packUnitSessions(group, nodeById, spec, warnings) {
10916
11116
  continue;
10917
11117
  }
10918
11118
  const minutes = node.estimated_minutes;
10919
- if (current.entries.length > 0 && filled() + minutes > spec.contentBudget) flush();
11119
+ if (current.entries.length > 0 && filled() + minutes > spec.contentBudget) {
11120
+ const displaced = cutBackToCheckpoint(current, nodeById, spec, warnings);
11121
+ if (displaced.length > 0) {
11122
+ ids.unshift(id);
11123
+ for (let di = displaced.length - 1; di >= 0; di--) ids.unshift(displaced[di].id);
11124
+ flush();
11125
+ continue;
11126
+ }
11127
+ flush();
11128
+ }
10920
11129
  if (isKnowledge) current.knowledgeMinutes += minutes;
10921
11130
  else current.practiceMinutes += minutes;
10922
11131
  current.entries.push({ id, minutes, part: 1, totalParts: 1 });
@@ -11226,6 +11435,19 @@ async function buildCurriculumPlan(rawGraph, options) {
11226
11435
  const graph = normalizePlanningGraph(rawGraph);
11227
11436
  warnings.push(...graph.warnings);
11228
11437
  assertAcyclic(graph.nodes, graph.edges);
11438
+ let reconcileSummary = null;
11439
+ if (typeof constraints.total_sessions === "number" && constraints.total_sessions > 0) {
11440
+ const spec0 = computePackingSpec(constraints);
11441
+ const budgetMinutes = constraints.total_sessions * spec0.contentBudget;
11442
+ reconcileSummary = reconcilePlanDepth(graph.nodes, graph.edges, spec0, budgetMinutes);
11443
+ if (!reconcileSummary.unchanged) {
11444
+ const saved = reconcileSummary.applied.reduce((a, x) => a + x.minutesSaved, 0);
11445
+ warnings.push(`depth reconcile: ${reconcileSummary.applied.length} downgrade(s), saved ${saved}m across ${reconcileSummary.mutatedNodeIds.length} node(s)`);
11446
+ }
11447
+ if (reconcileSummary.escalate) {
11448
+ warnings.push(`budget_escalation: deficit ${reconcileSummary.deficitMinutes}m after exhausting non-core depth candidates (budget ${constraints.total_sessions * computePackingSpec(constraints).contentBudget}m) \u2014 human review required`);
11449
+ }
11450
+ }
11229
11451
  const orderedIds = topoSort(graph.nodes, graph.edges);
11230
11452
  const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
11231
11453
  const spec = computePackingSpec(constraints);
@@ -27169,6 +27391,7 @@ Your task is to author a canonical, publication-grade learning artifact of type
27169
27391
  ${langDirective}
27170
27392
  ${headingDirective}
27171
27393
  2. NON-MOCK CODE INVARIANT: Any code snippet provided MUST be 100% syntactically valid, idiomatic, and executable without placeholders or fake functions.
27394
+ 2b. IDENTIFIER GROUND TRUTH: Never derive identifier casing/spelling from glossary term or concept names (they are pedagogical labels). Copy identifiers EXACTLY from the canonical LESSON and REFERENCE_PACK ground truth present in the context.
27172
27395
  3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
27173
27396
  4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
27174
27397
  5. TARGET AUDIENCE: ${gradeLevel ? `Grade ${gradeLevel}, ` : ""}${targetAge} on ${hwStr}.${deviceRule}${classDynamicsRule}${extraContextRule}
@@ -31673,6 +31896,7 @@ exports.RotationStationSchema = RotationStationSchema;
31673
31896
  exports.RubricCriteriaSchema = RubricCriteriaSchema;
31674
31897
  exports.RubricSchema = RubricSchema;
31675
31898
  exports.SATELLITE_LESSON_PRIORITIES = SATELLITE_LESSON_PRIORITIES;
31899
+ exports.SESSION_CUT_TOLERANCE = SESSION_CUT_TOLERANCE;
31676
31900
  exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
31677
31901
  exports.STANDARD_REF_REGEX = STANDARD_REF_REGEX;
31678
31902
  exports.STANDARD_SOT_FILES = STANDARD_SOT_FILES;
@@ -31762,6 +31986,7 @@ exports.buildTeacherGuidePrompt = buildTeacherGuidePrompt;
31762
31986
  exports.buildWalkingSkeleton = buildWalkingSkeleton;
31763
31987
  exports.buildWorksheetPrompt = buildWorksheetPrompt;
31764
31988
  exports.checkSessionZpd = checkSessionZpd;
31989
+ exports.classifyDepthCandidates = classifyDepthCandidates;
31765
31990
  exports.closeTruncatedJson = closeTruncatedJson;
31766
31991
  exports.computeConceptSpiralProgression = computeConceptSpiralProgression;
31767
31992
  exports.computeContentHash = computeContentHash;
@@ -31855,6 +32080,7 @@ exports.loadAuthoringTemplate = loadAuthoringTemplate;
31855
32080
  exports.loadCurriculumTemplate = loadCurriculumTemplate;
31856
32081
  exports.loadSotTemplate = loadSotTemplate;
31857
32082
  exports.normalizePlanningGraph = normalizePlanningGraph;
32083
+ exports.packUnitSessions = packUnitSessions;
31858
32084
  exports.packagerTools = packagerTools;
31859
32085
  exports.parseAllSessions = parseAllSessions;
31860
32086
  exports.parseGateSettings = parseGateSettings;
@@ -31865,6 +32091,7 @@ exports.produceSingleLesson = produceSingleLesson;
31865
32091
  exports.publishToGitHub = publishToGitHub;
31866
32092
  exports.publishToSupabase = publishToSupabase;
31867
32093
  exports.rankGenCandidates = rankGenCandidates;
32094
+ exports.reconcilePlanDepth = reconcilePlanDepth;
31868
32095
  exports.renderAssignedRowsTable = renderAssignedRowsTable;
31869
32096
  exports.renderEntitySlotsForType = renderEntitySlotsForType;
31870
32097
  exports.renderFrameworkFromPlan = renderFrameworkFromPlan;