@thanh01.pmt/curriculum-kit 1.4.35 → 1.4.37
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/ai/index.cjs +57 -1
- package/dist/ai/index.cjs.map +1 -1
- package/dist/ai/index.d.cts +2 -2
- package/dist/ai/index.d.ts +2 -2
- package/dist/ai/index.mjs +57 -1
- package/dist/ai/index.mjs.map +1 -1
- package/dist/{gateSettings-DabOqP6_.d.cts → gateSettings-oo5tCUS_.d.cts} +18 -0
- package/dist/{gateSettings-DabOqP6_.d.ts → gateSettings-oo5tCUS_.d.ts} +18 -0
- package/dist/index.cjs +793 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +251 -74
- package/dist/index.d.ts +251 -74
- package/dist/index.mjs +775 -52
- package/dist/index.mjs.map +1 -1
- package/dist/pipeline/index.cjs +57 -1
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.mjs +57 -1
- package/dist/pipeline/index.mjs.map +1 -1
- package/dist/schemas/index.cjs +63 -1
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +436 -33
- package/dist/schemas/index.d.ts +436 -33
- package/dist/schemas/index.mjs +58 -2
- package/dist/schemas/index.mjs.map +1 -1
- package/dist/standards/index.d.cts +2 -2
- package/dist/standards/index.d.ts +2 -2
- package/dist/workflow/index.cjs +84 -2
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.d.cts +3 -3
- package/dist/workflow/index.d.ts +3 -3
- package/dist/workflow/index.mjs +84 -2
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/{standardsCoverageGate-DR5YTtlt.d.cts → standardsCoverageGate-49pJq5t8.d.cts} +6 -6
- 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
|
|
11629
|
-
if (!byCleanTitle.has(
|
|
11630
|
-
byCleanTitle.set(
|
|
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
|
|
11769
|
-
const canonicalKey = reverseMap.get(
|
|
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(
|
|
11796
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
11887
|
+
if (norm2.includes("symbol") || norm2.includes("identifier ledger") || norm2.includes("dinh danh")) {
|
|
11812
11888
|
return "Symbol & Identifier Ledger";
|
|
11813
11889
|
}
|
|
11814
|
-
if (
|
|
11890
|
+
if (norm2.includes("artifact contract") || norm2.includes("hop dong hoc lieu")) {
|
|
11815
11891
|
return "Artifact Contract";
|
|
11816
11892
|
}
|
|
11817
|
-
if (
|
|
11893
|
+
if (norm2.includes("lesson design plan") || norm2.includes("ke hoach thiet ke")) {
|
|
11818
11894
|
return "A. Lesson Design Plan";
|
|
11819
11895
|
}
|
|
11820
|
-
if (
|
|
11896
|
+
if (norm2.includes("lesson flow") || norm2.includes("tien trinh giang day")) {
|
|
11821
11897
|
return "B. Lesson Flow";
|
|
11822
11898
|
}
|
|
11823
|
-
if (
|
|
11899
|
+
if (norm2.includes("learning objective") || norm2.includes("muc tieu bai hoc")) {
|
|
11824
11900
|
return "Learning Objectives & Evidence";
|
|
11825
11901
|
}
|
|
11826
|
-
if (
|
|
11902
|
+
if (norm2.includes("activity sequence") || norm2.includes("chuoi hoat dong")) {
|
|
11827
11903
|
return "Activity Sequence";
|
|
11828
11904
|
}
|
|
11829
|
-
if (
|
|
11905
|
+
if (norm2.includes("key term") || norm2.includes("thuat ngu")) {
|
|
11830
11906
|
return "Key Terms";
|
|
11831
11907
|
}
|
|
11832
|
-
if (
|
|
11908
|
+
if (norm2.includes("concept narrative") || norm2.includes("dien giai khai niem")) {
|
|
11833
11909
|
return "Concept Narratives";
|
|
11834
11910
|
}
|
|
11835
|
-
if (
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
13345
|
-
if (!
|
|
13346
|
-
const prev = seen.get(
|
|
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(
|
|
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(
|
|
13710
|
+
seen.set(norm2, s.id);
|
|
13362
13711
|
}
|
|
13363
13712
|
}
|
|
13364
13713
|
for (const m of pack.mappings ?? []) {
|
|
@@ -26099,6 +26448,263 @@ 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
|
+
canonicalExcerpt: lessonExcerpt.excerpt
|
|
26589
|
+
};
|
|
26590
|
+
}
|
|
26591
|
+
const blocks = [];
|
|
26592
|
+
const issues = [];
|
|
26593
|
+
const parts = [commonContext];
|
|
26594
|
+
blocks.push(blockMeta("commonContext", "COMPOSITE", commonContext, true));
|
|
26595
|
+
let excerptSlot;
|
|
26596
|
+
let miniSlot;
|
|
26597
|
+
const compositeHasKx = /###\s+KNOWLEDGE_EXPOSITION/.test(commonContext);
|
|
26598
|
+
if (expositionContent.trim() && !compositeHasKx) {
|
|
26599
|
+
const kxExcerpt = buildSectionAwareExcerpt(expositionContent, {
|
|
26600
|
+
priorities: KX_SLOT_PRIORITIES[type] ?? ["Key Terms", "Concept Narratives", "Worked Micro-Examples"],
|
|
26601
|
+
budget: 4500,
|
|
26602
|
+
sectionLanguageContract: slcMarkdown,
|
|
26603
|
+
artifactType: "KNOWLEDGE_EXPOSITION"
|
|
26604
|
+
});
|
|
26605
|
+
parts.push(`
|
|
26606
|
+
|
|
26607
|
+
[KNOWLEDGE_EXPOSITION (canonical knowledge \u2014 teach from this, do not contradict; scoped for ${type})]:
|
|
26608
|
+
${kxExcerpt.excerpt}`);
|
|
26609
|
+
blocks.push(blockMeta("kxExcerpt:" + type, "KNOWLEDGE_EXPOSITION", kxExcerpt.excerpt, kxExcerpt.verified, kxExcerpt.issues));
|
|
26610
|
+
if (!kxExcerpt.verified) issues.push(...kxExcerpt.issues.map((i) => `kx:${i}`));
|
|
26611
|
+
}
|
|
26612
|
+
if (!KX_ONLY_TYPES.has(type)) {
|
|
26613
|
+
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 = `
|
|
26621
|
+
|
|
26622
|
+
[CANONICAL LESSON PLAN (scoped for ${type})]:
|
|
26623
|
+
${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(`
|
|
26632
|
+
|
|
26633
|
+
[ASSESSMENT MAP (criteria anchor \u2014 assess exactly these)]:
|
|
26634
|
+
${am}`);
|
|
26635
|
+
blocks.push(blockMeta("assessmentMap:" + type, "LESSON", am, true));
|
|
26636
|
+
} else {
|
|
26637
|
+
issues.push(`assessment-map:unresolved:${type}`);
|
|
26638
|
+
}
|
|
26639
|
+
}
|
|
26640
|
+
if (symbolLedgerBlock) {
|
|
26641
|
+
parts.push(symbolLedgerBlock);
|
|
26642
|
+
blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
|
|
26643
|
+
}
|
|
26644
|
+
} 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");
|
|
26653
|
+
if (mini) {
|
|
26654
|
+
const miniBlock = `
|
|
26655
|
+
|
|
26656
|
+
[LESSON ACTIVITY CONTRACT (mini-slot)]:
|
|
26657
|
+
${mini}`;
|
|
26658
|
+
parts.push(miniBlock);
|
|
26659
|
+
miniSlot = miniBlock;
|
|
26660
|
+
blocks.push(blockMeta("activityContract:" + type, "LESSON", mini, true));
|
|
26661
|
+
} else {
|
|
26662
|
+
issues.push(`activity-contract:unresolved:${type}`);
|
|
26663
|
+
blocks.push(blockMeta("activityContract:" + type, "LESSON", "", false, ["no-matching-rows"]));
|
|
26664
|
+
}
|
|
26665
|
+
if (type === "CODE" && symbolLedgerBlock) {
|
|
26666
|
+
parts.push(symbolLedgerBlock);
|
|
26667
|
+
blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
|
|
26668
|
+
}
|
|
26669
|
+
}
|
|
26670
|
+
const context = parts.join("");
|
|
26671
|
+
return {
|
|
26672
|
+
context,
|
|
26673
|
+
blocks,
|
|
26674
|
+
verified: blocks.filter((b) => b.slot !== "commonContext").every((b) => b.verified),
|
|
26675
|
+
sectionAware: blocks.some((b) => b.slot.startsWith("lessonExcerpt") || b.slot.startsWith("kxExcerpt")),
|
|
26676
|
+
issues,
|
|
26677
|
+
tokenEstimate: Math.round(context.length / 4),
|
|
26678
|
+
// Judge ground truth = exactly what THIS type consumed (scoped or mini slot).
|
|
26679
|
+
canonicalExcerpt: (miniSlot ?? excerptSlot ?? lessonExcerpt.excerpt).trim()
|
|
26680
|
+
};
|
|
26681
|
+
}
|
|
26682
|
+
var ACTIVITY_ALIGNMENT_LOG_ONLY_UNTIL = /* @__PURE__ */ new Date("2026-09-23T00:00:00Z");
|
|
26683
|
+
function isActivityAlignmentLogOnly(now = /* @__PURE__ */ new Date()) {
|
|
26684
|
+
const env = (process.env.DRIFT_ALIGNMENT_LOG_ONLY ?? "").toLowerCase().trim();
|
|
26685
|
+
if (env === "false" || env === "0") return false;
|
|
26686
|
+
if (env === "true" || env === "1") return true;
|
|
26687
|
+
return now < ACTIVITY_ALIGNMENT_LOG_ONLY_UNTIL;
|
|
26688
|
+
}
|
|
26689
|
+
function evaluateActivityAlignment(canonicalExcerpt, generatedContent, now = /* @__PURE__ */ new Date()) {
|
|
26690
|
+
const mode = isActivityAlignmentLogOnly(now) ? "log_only" : "enforcing";
|
|
26691
|
+
if (!canonicalExcerpt || !canonicalExcerpt.trim()) {
|
|
26692
|
+
return { checked: false, passed: null, matchedTokens: [], missingTokens: [], mode };
|
|
26693
|
+
}
|
|
26694
|
+
const content = (generatedContent || "").toLowerCase();
|
|
26695
|
+
const stripped = canonicalExcerpt.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
26696
|
+
const latin = Array.from(new Set(stripped.match(/[a-z][a-z0-9_]{3,}/g) ?? []));
|
|
26697
|
+
const cjk = Array.from(new Set(canonicalExcerpt.match(/[\u4e00-\u9fff]{2,}/g) ?? []));
|
|
26698
|
+
const tokens = [...latin, ...cjk];
|
|
26699
|
+
if (tokens.length === 0) {
|
|
26700
|
+
return { checked: false, passed: null, matchedTokens: [], missingTokens: [], mode };
|
|
26701
|
+
}
|
|
26702
|
+
const matchedTokens = tokens.filter((t) => content.includes(t));
|
|
26703
|
+
const missingTokens = tokens.filter((t) => !content.includes(t));
|
|
26704
|
+
const passed = matchedTokens.length > 0;
|
|
26705
|
+
return { checked: true, passed, matchedTokens: matchedTokens.slice(0, 10), missingTokens: missingTokens.slice(0, 10), mode };
|
|
26706
|
+
}
|
|
26707
|
+
|
|
26102
26708
|
// src/services/lessonProductionService.ts
|
|
26103
26709
|
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
26710
|
var _resolvedDir = __filename2 ? path3__default.default.dirname(__filename2) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
@@ -26671,9 +27277,9 @@ ${renderHorizonPromptBlock(horizon)}`;
|
|
|
26671
27277
|
console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
|
|
26672
27278
|
}
|
|
26673
27279
|
let productSpecBlock = "";
|
|
26674
|
-
const buildGroundTruthBlock = () => {
|
|
27280
|
+
const buildGroundTruthBlock = (includeKx = true) => {
|
|
26675
27281
|
const parts = [];
|
|
26676
|
-
if (expositionContext) {
|
|
27282
|
+
if (includeKx && expositionContext) {
|
|
26677
27283
|
parts.push(`### KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)
|
|
26678
27284
|
${expositionContext}`);
|
|
26679
27285
|
}
|
|
@@ -26707,10 +27313,10 @@ ${sessionSliceContext}` : "";
|
|
|
26707
27313
|
const guardrailBlock = `
|
|
26708
27314
|
|
|
26709
27315
|
${platformGuardrail}`;
|
|
26710
|
-
const assembleCommonContext = (sg) => `${baseContextPrefix}
|
|
27316
|
+
const assembleCommonContext = (sg, includeKx = true) => `${baseContextPrefix}
|
|
26711
27317
|
|
|
26712
27318
|
[CONTENT STYLE GUIDE EXCERPT]:
|
|
26713
|
-
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
|
|
27319
|
+
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock(includeKx)}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
|
|
26714
27320
|
let commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
26715
27321
|
const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
|
|
26716
27322
|
const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
|
|
@@ -27119,10 +27725,34 @@ ${currentContent}` }],
|
|
|
27119
27725
|
- Entry Source File: \`${symbolLedger.entryFileName || symbolLedger.primarySymbol}\`
|
|
27120
27726
|
- Key Identifiers to inherit verbatim: ${symbolLedger.keySymbols.map((s) => `\`${s}\``).join(", ")}
|
|
27121
27727
|
- 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
|
|
27123
|
-
|
|
27124
|
-
|
|
27125
|
-
|
|
27728
|
+
const routingMode = options.contextRoutingMode ?? "legacy";
|
|
27729
|
+
const kxFreeCommonContext = routingMode === "hybrid" ? assembleCommonContext(effectiveStyleGuide, false) : "";
|
|
27730
|
+
const satelliteContexts = {};
|
|
27731
|
+
const satelliteContextMeta = {};
|
|
27732
|
+
const satelliteContextFor = (artifactType) => {
|
|
27733
|
+
const key = artifactType.toUpperCase();
|
|
27734
|
+
if (!satelliteContexts[key]) {
|
|
27735
|
+
const built = buildSatelliteContext({
|
|
27736
|
+
artifactType: key,
|
|
27737
|
+
commonContext: routingMode === "hybrid" ? kxFreeCommonContext : commonContext,
|
|
27738
|
+
lessonContent,
|
|
27739
|
+
lessonExcerpt: lessonExcerptResult,
|
|
27740
|
+
expositionContent: expositionContext,
|
|
27741
|
+
slcMarkdown,
|
|
27742
|
+
symbolLedgerBlock,
|
|
27743
|
+
pedagogyLabel,
|
|
27744
|
+
mode: routingMode
|
|
27745
|
+
});
|
|
27746
|
+
satelliteContexts[key] = built.context;
|
|
27747
|
+
satelliteContextMeta[key] = built;
|
|
27748
|
+
onProgress?.(
|
|
27749
|
+
"@content",
|
|
27750
|
+
`[CONTEXT] ${key} satellite context: ${built.context.length} chars (${built.blocks.map((b) => `${b.slot}:${b.chars}`).join(", ")})`,
|
|
27751
|
+
{ type: "progress", promptChars: built.context.length, artifactType: key }
|
|
27752
|
+
);
|
|
27753
|
+
}
|
|
27754
|
+
return satelliteContexts[key];
|
|
27755
|
+
};
|
|
27126
27756
|
const judgeSat = (sat, content) => {
|
|
27127
27757
|
if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
|
|
27128
27758
|
const det = validateArtifactDeterministic({ content, refPack });
|
|
@@ -27142,6 +27772,30 @@ ${lessonExcerpt}${symbolLedgerBlock}`;
|
|
|
27142
27772
|
}
|
|
27143
27773
|
});
|
|
27144
27774
|
}
|
|
27775
|
+
const slotMeta = satelliteContextMeta[sat.toUpperCase()];
|
|
27776
|
+
if (slotMeta) {
|
|
27777
|
+
const alignment = evaluateActivityAlignment(slotMeta.canonicalExcerpt, content);
|
|
27778
|
+
if (alignment.checked && alignment.passed === false) {
|
|
27779
|
+
const note = `activity-alignment drift vs ${sat} contract slot (matched: ${alignment.matchedTokens.join(", ") || "none"}); canonical tokens absent from output`;
|
|
27780
|
+
if (alignment.mode === "enforcing") {
|
|
27781
|
+
const critique = validatorRepairPrompt([{ code: "scope-drift", detail: note, evidence: alignment.missingTokens }]);
|
|
27782
|
+
onProgress?.("@reviewer", `\u26D4 ${sat} activity-alignment drift FAIL: ${note}`);
|
|
27783
|
+
return storage.updateArtifactState(projectId, lessonCode, sat, {
|
|
27784
|
+
state: "rejected",
|
|
27785
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27786
|
+
contentHash: computeContentHash(content),
|
|
27787
|
+
review: {
|
|
27788
|
+
decision: "NEEDS_REVISION",
|
|
27789
|
+
reviewedBy: "@heuristic-linter",
|
|
27790
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27791
|
+
score: 0,
|
|
27792
|
+
critique
|
|
27793
|
+
}
|
|
27794
|
+
});
|
|
27795
|
+
}
|
|
27796
|
+
onProgress?.("@reviewer", `\u{1F9ED} [log-only] ${sat} activity-alignment drift: ${note}`);
|
|
27797
|
+
}
|
|
27798
|
+
}
|
|
27145
27799
|
return judgeSatelliteArtifact({
|
|
27146
27800
|
storage,
|
|
27147
27801
|
projectId,
|
|
@@ -27154,7 +27808,7 @@ ${lessonExcerpt}${symbolLedgerBlock}`;
|
|
|
27154
27808
|
onProgress,
|
|
27155
27809
|
targetLang,
|
|
27156
27810
|
canonicalLessonContent: lessonContent,
|
|
27157
|
-
canonicalLessonExcerpt: lessonExcerpt
|
|
27811
|
+
canonicalLessonExcerpt: slotMeta?.canonicalExcerpt ?? lessonExcerpt
|
|
27158
27812
|
});
|
|
27159
27813
|
};
|
|
27160
27814
|
const actRelPath = `_content/${unitCode}/ACT_${lessonCode}.md`;
|
|
@@ -27203,7 +27857,7 @@ ${languageDirective}
|
|
|
27203
27857
|
${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
27204
27858
|
const rawAct = await runCurriculumAIInference(
|
|
27205
27859
|
[{ role: "user", content: actPrompt }],
|
|
27206
|
-
|
|
27860
|
+
satelliteContextFor("ACT"),
|
|
27207
27861
|
runnerOptions,
|
|
27208
27862
|
(chunk, type) => {
|
|
27209
27863
|
options.onProgress?.("@activity", chunk, { type: type || "content", artifactType: "ACT" });
|
|
@@ -27273,7 +27927,7 @@ ${languageDirective}
|
|
|
27273
27927
|
${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
27274
27928
|
const rawQuiz = await runCurriculumAIInference(
|
|
27275
27929
|
[{ role: "user", content: quizPrompt }],
|
|
27276
|
-
|
|
27930
|
+
satelliteContextFor("QUIZ"),
|
|
27277
27931
|
runnerOptions,
|
|
27278
27932
|
(chunk, type) => {
|
|
27279
27933
|
options.onProgress?.("@assessor", chunk, { type: type || "content", artifactType: "QUIZ" });
|
|
@@ -27326,7 +27980,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
27326
27980
|
headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
|
|
27327
27981
|
groundContext: slideGroundContext,
|
|
27328
27982
|
productSpecBlock: productSpecBlock || void 0,
|
|
27329
|
-
satelliteContext,
|
|
27983
|
+
satelliteContext: satelliteContextFor("SLIDE"),
|
|
27330
27984
|
runnerOptions,
|
|
27331
27985
|
onProgress: (agent, msg, meta) => {
|
|
27332
27986
|
options.onProgress?.(agent, msg, meta || { type: "content", artifactType: "SLIDE" });
|
|
@@ -27410,7 +28064,7 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
27410
28064
|
});
|
|
27411
28065
|
const rawSlide = await runCurriculumAIInference(
|
|
27412
28066
|
[{ role: "user", content: slidePrompt }],
|
|
27413
|
-
|
|
28067
|
+
satelliteContextFor("SLIDE"),
|
|
27414
28068
|
runnerOptions,
|
|
27415
28069
|
(chunk, type) => {
|
|
27416
28070
|
options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
|
|
@@ -27471,7 +28125,7 @@ ${languageDirective}
|
|
|
27471
28125
|
${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
27472
28126
|
const rawGuide = await runCurriculumAIInference(
|
|
27473
28127
|
[{ role: "user", content: guidePrompt }],
|
|
27474
|
-
|
|
28128
|
+
satelliteContextFor("GUIDE"),
|
|
27475
28129
|
runnerOptions,
|
|
27476
28130
|
(chunk, type) => {
|
|
27477
28131
|
options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "GUIDE" });
|
|
@@ -27523,7 +28177,7 @@ ${languageDirective}
|
|
|
27523
28177
|
${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
27524
28178
|
const rawHandout = await runCurriculumAIInference(
|
|
27525
28179
|
[{ role: "user", content: handoutPrompt }],
|
|
27526
|
-
|
|
28180
|
+
satelliteContextFor("HANDOUT"),
|
|
27527
28181
|
runnerOptions,
|
|
27528
28182
|
(chunk, type) => {
|
|
27529
28183
|
options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "HANDOUT" });
|
|
@@ -27579,7 +28233,7 @@ ${languageDirective}
|
|
|
27579
28233
|
${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
27580
28234
|
const rawWks = await runCurriculumAIInference(
|
|
27581
28235
|
[{ role: "user", content: wksPrompt }],
|
|
27582
|
-
|
|
28236
|
+
satelliteContextFor("WKS"),
|
|
27583
28237
|
runnerOptions,
|
|
27584
28238
|
(chunk, type) => {
|
|
27585
28239
|
options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "WKS" });
|
|
@@ -27639,7 +28293,7 @@ ${languageDirective}
|
|
|
27639
28293
|
${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
27640
28294
|
const rawCode = await runCurriculumAIInference(
|
|
27641
28295
|
[{ role: "user", content: codePrompt }],
|
|
27642
|
-
|
|
28296
|
+
satelliteContextFor("CODE"),
|
|
27643
28297
|
runnerOptions,
|
|
27644
28298
|
(chunk, type) => {
|
|
27645
28299
|
options.onProgress?.("@activity", chunk, { type: type || "chunk", artifactType: "CODE" });
|
|
@@ -27726,7 +28380,7 @@ ${languageDirective}
|
|
|
27726
28380
|
${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
|
|
27727
28381
|
const rawExt = await runCurriculumAIInference(
|
|
27728
28382
|
[{ role: "user", content: extPrompt }],
|
|
27729
|
-
|
|
28383
|
+
satelliteContextFor("EXT"),
|
|
27730
28384
|
runnerOptions,
|
|
27731
28385
|
(chunk, type) => {
|
|
27732
28386
|
options.onProgress?.("@activity", chunk, { type: type || "chunk", artifactType: "EXT" });
|
|
@@ -30826,6 +31480,75 @@ var DeterministicStructuralLinter = class {
|
|
|
30826
31480
|
strengths
|
|
30827
31481
|
};
|
|
30828
31482
|
}
|
|
31483
|
+
/**
|
|
31484
|
+
* P50: Validates macro-pedagogical invariants across a CurriculumPlan.
|
|
31485
|
+
* Checks:
|
|
31486
|
+
* 1. ZPD violations without self-healing recap (NO_ZPD_BRIDGE)
|
|
31487
|
+
* 2. Walking Skeleton existence (Unit 1 delivers working software)
|
|
31488
|
+
* 3. Inter-unit Mastery Gate presence
|
|
31489
|
+
*/
|
|
31490
|
+
static lintPlan(plan) {
|
|
31491
|
+
const findings = [];
|
|
31492
|
+
const strengths = [];
|
|
31493
|
+
let score = 100;
|
|
31494
|
+
for (const s of plan.sessions) {
|
|
31495
|
+
if (s.zpd_status?.verdict === "TOO_MANY_NEW") {
|
|
31496
|
+
score -= 10;
|
|
31497
|
+
findings.push({
|
|
31498
|
+
id: `PLAN_TOO_MANY_NEW_${s.id}`,
|
|
31499
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
31500
|
+
severity: "MINOR",
|
|
31501
|
+
title: `Cognitive Overload in Session ${s.id}`,
|
|
31502
|
+
description: s.zpd_status.issues.join("; ") || `Session introduces too many new concepts/keywords, exceeding ZPD capacity.`,
|
|
31503
|
+
remediationAdvice: `Distribute new concepts across multiple sessions or introduce via scaffolding.`,
|
|
31504
|
+
affectedElement: s.id
|
|
31505
|
+
});
|
|
31506
|
+
} else if (s.zpd_status?.verdict === "NO_ZPD_BRIDGE") {
|
|
31507
|
+
const hasRecap = s.prerequisite_decisions.some((d) => d.decision === "recap_in_lesson");
|
|
31508
|
+
if (!hasRecap) {
|
|
31509
|
+
score -= 15;
|
|
31510
|
+
findings.push({
|
|
31511
|
+
id: `PLAN_NO_ZPD_BRIDGE_${s.id}`,
|
|
31512
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
31513
|
+
severity: "MAJOR",
|
|
31514
|
+
title: `Unhealed ZPD Bridge in Session ${s.id}`,
|
|
31515
|
+
description: `Session introduces new concepts with 0 known concept bridges, and lacks recap scaffolding.`,
|
|
31516
|
+
remediationAdvice: `Add a recap_in_lesson prerequisite decision to anchor new learning in prior concepts.`,
|
|
31517
|
+
affectedElement: s.id
|
|
31518
|
+
});
|
|
31519
|
+
}
|
|
31520
|
+
}
|
|
31521
|
+
}
|
|
31522
|
+
if (!plan.walking_skeleton || plan.walking_skeleton.epitome_deliverables.length === 0) {
|
|
31523
|
+
score -= 10;
|
|
31524
|
+
findings.push({
|
|
31525
|
+
id: "PLAN_MISSING_WALKING_SKELETON",
|
|
31526
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
31527
|
+
severity: "MINOR",
|
|
31528
|
+
title: "Missing Walking Skeleton Definition",
|
|
31529
|
+
description: "Plan does not declare an Epitome (walking skeleton) in Unit 1.",
|
|
31530
|
+
remediationAdvice: "Ensure Unit 1 defines minimal end-to-end deliverables."
|
|
31531
|
+
});
|
|
31532
|
+
} else {
|
|
31533
|
+
strengths.push(`Unit ${plan.walking_skeleton.epitome_unit_id} establishes an end-to-end Walking Skeleton (Epitome).`);
|
|
31534
|
+
}
|
|
31535
|
+
if (plan.units.length > 1 && (!plan.mastery_gates || plan.mastery_gates.length === 0)) {
|
|
31536
|
+
score -= 10;
|
|
31537
|
+
findings.push({
|
|
31538
|
+
id: "PLAN_MISSING_MASTERY_GATES",
|
|
31539
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
31540
|
+
severity: "MINOR",
|
|
31541
|
+
title: "Missing Inter-Unit Mastery Gates",
|
|
31542
|
+
description: "Multi-unit course lacks formal transition gates between units.",
|
|
31543
|
+
remediationAdvice: "Define exit criteria and remediation sprints for each inter-unit boundary."
|
|
31544
|
+
});
|
|
31545
|
+
} else if (plan.mastery_gates && plan.mastery_gates.length > 0) {
|
|
31546
|
+
strengths.push(`${plan.mastery_gates.length} inter-unit Mastery Gates defined with remediation protocols.`);
|
|
31547
|
+
}
|
|
31548
|
+
score = Math.max(0, Math.min(100, score));
|
|
31549
|
+
const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
|
|
31550
|
+
return { passed, score, findings, strengths };
|
|
31551
|
+
}
|
|
30829
31552
|
};
|
|
30830
31553
|
|
|
30831
31554
|
// src/evaluators/academicAuditor.ts
|
|
@@ -32496,6 +33219,8 @@ exports.CodeLabSchema = CodeLabSchema;
|
|
|
32496
33219
|
exports.CodeSnippetSchema = CodeSnippetSchema;
|
|
32497
33220
|
exports.CompetencyRubricRowSchema = CompetencyRubricRowSchema;
|
|
32498
33221
|
exports.ComputationalThinkingSchema = ComputationalThinkingSchema;
|
|
33222
|
+
exports.ConceptPrerequisiteEdgeSchema = ConceptPrerequisiteEdgeSchema;
|
|
33223
|
+
exports.ConceptSpiralEncounterSchema = ConceptSpiralEncounterSchema;
|
|
32499
33224
|
exports.ConstructiveAlignmentEvaluator = ConstructiveAlignmentEvaluator;
|
|
32500
33225
|
exports.CoreConceptSchema = CoreConceptSchema;
|
|
32501
33226
|
exports.CourseObjectiveSchema = CourseObjectiveSchema;
|
|
@@ -32570,6 +33295,7 @@ exports.LessonSectionSchema = LessonSectionSchema;
|
|
|
32570
33295
|
exports.LocalWorkspaceManager = LocalWorkspaceManager;
|
|
32571
33296
|
exports.MappingKindSchema = MappingKindSchema;
|
|
32572
33297
|
exports.MarpSlideSchema = MarpSlideSchema;
|
|
33298
|
+
exports.MasteryGateSchema = MasteryGateSchema;
|
|
32573
33299
|
exports.MediaError = MediaError;
|
|
32574
33300
|
exports.MediaLedger = MediaLedger;
|
|
32575
33301
|
exports.MentorCheatsheetSchema = MentorCheatsheetSchema;
|
|
@@ -32623,6 +33349,7 @@ exports.SelfLabSchema = SelfLabSchema;
|
|
|
32623
33349
|
exports.SelfPacedBundleSchema = SelfPacedBundleSchema;
|
|
32624
33350
|
exports.SelfPacedChallengeSchema = SelfPacedChallengeSchema;
|
|
32625
33351
|
exports.SessionPlanSchema = SessionPlanSchema;
|
|
33352
|
+
exports.SessionZpdStatusSchema = SessionZpdStatusSchema;
|
|
32626
33353
|
exports.SilverTierSchema = SilverTierSchema;
|
|
32627
33354
|
exports.SlideDeckSchema = SlideDeckSchema;
|
|
32628
33355
|
exports.StandardizedTermRowSchema = StandardizedTermRowSchema;
|
|
@@ -32647,20 +33374,24 @@ exports.TroubleshootingRowSchema = TroubleshootingRowSchema;
|
|
|
32647
33374
|
exports.UnitPlanSchema = UnitPlanSchema;
|
|
32648
33375
|
exports.UserJourneySchema = UserJourneySchema;
|
|
32649
33376
|
exports.WORKSHEET_TEMPLATE = WORKSHEET_TEMPLATE;
|
|
33377
|
+
exports.WalkingSkeletonSchema = WalkingSkeletonSchema;
|
|
32650
33378
|
exports.WorksheetItemSchema = WorksheetItemSchema;
|
|
32651
33379
|
exports.WorksheetItemTypeEnum = WorksheetItemTypeEnum;
|
|
32652
33380
|
exports.WorksheetPartSchema = WorksheetPartSchema;
|
|
32653
33381
|
exports.WorksheetSchema = WorksheetSchema;
|
|
33382
|
+
exports.ZpdVerdictSchema = ZpdVerdictSchema;
|
|
32654
33383
|
exports.activityTools = activityTools;
|
|
32655
33384
|
exports.analystTools = analystTools;
|
|
32656
33385
|
exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
|
|
32657
33386
|
exports.assertAcyclic = assertAcyclic;
|
|
32658
33387
|
exports.assessorTools = assessorTools;
|
|
33388
|
+
exports.assignDepths = assignDepths;
|
|
32659
33389
|
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
32660
33390
|
exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
|
|
32661
33391
|
exports.auditQualityReport = auditQualityReport;
|
|
32662
33392
|
exports.buildActivityPrompt = buildActivityPrompt;
|
|
32663
33393
|
exports.buildCodeLabPrompt = buildCodeLabPrompt;
|
|
33394
|
+
exports.buildConceptPrerequisites = buildConceptPrerequisites;
|
|
32664
33395
|
exports.buildCurriculumContext = buildCurriculumContext;
|
|
32665
33396
|
exports.buildCurriculumPlan = buildCurriculumPlan;
|
|
32666
33397
|
exports.buildDeliveryPackages = buildDeliveryPackages;
|
|
@@ -32678,7 +33409,9 @@ exports.buildLanguageDirective = buildLanguageDirective;
|
|
|
32678
33409
|
exports.buildLessonExcerpt = buildLessonExcerpt;
|
|
32679
33410
|
exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
|
|
32680
33411
|
exports.buildMarpMarkdownSlidePrompt = buildMarpMarkdownSlidePrompt;
|
|
33412
|
+
exports.buildMasteryGates = buildMasteryGates;
|
|
32681
33413
|
exports.buildProjectInstructionPrompt = buildProjectInstructionPrompt;
|
|
33414
|
+
exports.buildSatelliteContext = buildSatelliteContext;
|
|
32682
33415
|
exports.buildSectionAwareExcerpt = buildSectionAwareExcerpt;
|
|
32683
33416
|
exports.buildSelfLabPrompt = buildSelfLabPrompt;
|
|
32684
33417
|
exports.buildSessionSliceContext = buildSessionSliceContext;
|
|
@@ -32687,8 +33420,11 @@ exports.buildStandardStackMarkdown = buildStandardStackMarkdown;
|
|
|
32687
33420
|
exports.buildStandardsContext = buildStandardsContext;
|
|
32688
33421
|
exports.buildStandardsContextBlock = buildStandardsContextBlock;
|
|
32689
33422
|
exports.buildTeacherGuidePrompt = buildTeacherGuidePrompt;
|
|
33423
|
+
exports.buildWalkingSkeleton = buildWalkingSkeleton;
|
|
32690
33424
|
exports.buildWorksheetPrompt = buildWorksheetPrompt;
|
|
33425
|
+
exports.checkSessionZpd = checkSessionZpd;
|
|
32691
33426
|
exports.closeTruncatedJson = closeTruncatedJson;
|
|
33427
|
+
exports.computeConceptSpiralProgression = computeConceptSpiralProgression;
|
|
32692
33428
|
exports.computeContentHash = computeContentHash;
|
|
32693
33429
|
exports.computePackingSpec = computePackingSpec;
|
|
32694
33430
|
exports.conductPreliminaryResearch = conductPreliminaryResearch;
|
|
@@ -32704,14 +33440,18 @@ exports.createStreamChunkExtractor = createStreamChunkExtractor;
|
|
|
32704
33440
|
exports.curateMediaLedger = curateMediaLedger;
|
|
32705
33441
|
exports.designerTools = designerTools;
|
|
32706
33442
|
exports.detectProjectPedagogy = detectProjectPedagogy;
|
|
33443
|
+
exports.dropCyclicConceptEdges = dropCyclicConceptEdges;
|
|
32707
33444
|
exports.emitUsage = emitUsage;
|
|
32708
33445
|
exports.ensureExpositionForLesson = ensureExpositionForLesson;
|
|
32709
33446
|
exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
|
|
33447
|
+
exports.evaluateActivityAlignment = evaluateActivityAlignment;
|
|
32710
33448
|
exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
|
|
32711
33449
|
exports.executeCurriculumCommand = executeCurriculumCommand;
|
|
32712
33450
|
exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
|
|
32713
33451
|
exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
|
|
32714
33452
|
exports.expositionCacheKey = expositionCacheKey;
|
|
33453
|
+
exports.extractActivityContractRows = extractActivityContractRows;
|
|
33454
|
+
exports.extractAssessmentMap = extractAssessmentMap;
|
|
32715
33455
|
exports.extractCurriculumHorizon = extractCurriculumHorizon;
|
|
32716
33456
|
exports.extractJsonArray = extractJsonArray;
|
|
32717
33457
|
exports.extractScopeSequenceRows = extractScopeSequenceRows;
|
|
@@ -32721,6 +33461,7 @@ exports.extractStandardRefs = extractStandardRefs;
|
|
|
32721
33461
|
exports.extractStreamChunk = extractStreamChunk;
|
|
32722
33462
|
exports.extractSymbolLedger = extractSymbolLedger;
|
|
32723
33463
|
exports.extractThoughtAndContent = extractThoughtAndContent;
|
|
33464
|
+
exports.extractTieredScaffoldingBlock = extractTieredScaffoldingBlock;
|
|
32724
33465
|
exports.findStandardStatement = findStandardStatement;
|
|
32725
33466
|
exports.formatQuizzesToCsv = formatQuizzesToCsv;
|
|
32726
33467
|
exports.fulfillMediaLedger = fulfillMediaLedger;
|
|
@@ -32760,6 +33501,7 @@ exports.importRoadmapToProject = importRoadmapToProject;
|
|
|
32760
33501
|
exports.ingestProjectGraphIntoProject = ingestProjectGraphIntoProject;
|
|
32761
33502
|
exports.initializeProjectInStorage = initializeProjectInStorage;
|
|
32762
33503
|
exports.injectMediaIntoMarkdown = injectMediaIntoMarkdown;
|
|
33504
|
+
exports.isActivityAlignmentLogOnly = isActivityAlignmentLogOnly;
|
|
32763
33505
|
exports.isExpositionFresh = isExpositionFresh;
|
|
32764
33506
|
exports.isModelAllowed = isModelAllowed;
|
|
32765
33507
|
exports.isProviderEnabled = isProviderEnabled;
|