@thanh01.pmt/curriculum-kit 1.4.34 → 1.4.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- 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 +764 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +234 -74
- package/dist/index.d.ts +234 -74
- package/dist/index.mjs +748 -71
- 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 +22 -23
- 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,229 @@ function extractStandardRefs(text) {
|
|
|
26099
26448
|
return Array.from(new Set(matches));
|
|
26100
26449
|
}
|
|
26101
26450
|
|
|
26451
|
+
// src/services/contextSlots.ts
|
|
26452
|
+
var LESSON_SLOT_BUDGETS = {
|
|
26453
|
+
ACT: { budget: 8e3 },
|
|
26454
|
+
GUIDE: { budget: 6e3 },
|
|
26455
|
+
QUIZ: { budget: 3500, priorities: ["A. Lesson Design Plan"] },
|
|
26456
|
+
SLIDE: { budget: 4500, priorities: ["B. Lesson Flow", "A. Lesson Design Plan"] },
|
|
26457
|
+
WKS: { budget: 4500, priorities: ["A. Lesson Design Plan", "B. Lesson Flow"] },
|
|
26458
|
+
EXIT_TICKET: { budget: 3e3, priorities: ["A. Lesson Design Plan", "B. Lesson Flow"] }
|
|
26459
|
+
};
|
|
26460
|
+
var KX_ONLY_TYPES = /* @__PURE__ */ new Set(["CODE", "HANDOUT", "EXT"]);
|
|
26461
|
+
var KX_SLOT_PRIORITIES = {
|
|
26462
|
+
QUIZ: ["Common Mistakes", "Self-Check Questions", "Key Terms", "Concept Narratives"],
|
|
26463
|
+
EXIT_TICKET: ["Self-Check Questions", "Key Terms"],
|
|
26464
|
+
WKS: ["Self-Check Questions", "Worked Micro-Examples", "Key Terms"],
|
|
26465
|
+
CODE: ["Worked Micro-Examples", "Key Terms", "Common Mistakes"],
|
|
26466
|
+
GUIDE: ["Common Mistakes", "Key Terms", "Concept Narratives"],
|
|
26467
|
+
HANDOUT: ["Key Terms", "Concept Narratives", "Common Mistakes"],
|
|
26468
|
+
SLIDE: ["Key Terms", "Worked Micro-Examples"],
|
|
26469
|
+
ACT: ["Worked Micro-Examples", "Common Mistakes"],
|
|
26470
|
+
EXT: ["Worked Micro-Examples", "Common Mistakes"]
|
|
26471
|
+
};
|
|
26472
|
+
var norm = (s) => s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "");
|
|
26473
|
+
function extractActivityContractRows(lessonContent, artifactType, maxChars = 1500) {
|
|
26474
|
+
if (!lessonContent) return "";
|
|
26475
|
+
const wanted = norm(artifactType).replace(/_/g, "");
|
|
26476
|
+
const lines = lessonContent.split("\n");
|
|
26477
|
+
let inSeq = false;
|
|
26478
|
+
const headerRows = [];
|
|
26479
|
+
const matchedRows = [];
|
|
26480
|
+
const allRows = [];
|
|
26481
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26482
|
+
const line = lines[i];
|
|
26483
|
+
const h = line.match(/^#{2,4}\s+(.*)$/);
|
|
26484
|
+
if (h) {
|
|
26485
|
+
const t2 = h[1];
|
|
26486
|
+
if (/activity\s*sequence/i.test(t2)) inSeq = true;
|
|
26487
|
+
else if (inSeq) break;
|
|
26488
|
+
continue;
|
|
26489
|
+
}
|
|
26490
|
+
if (!inSeq) continue;
|
|
26491
|
+
const t = line.trim();
|
|
26492
|
+
if (!t.startsWith("|")) continue;
|
|
26493
|
+
if (/^\|\s*[-: |]+\|\s*$/.test(t)) continue;
|
|
26494
|
+
const cells = t.split("|").slice(1, -1).map((c) => c.trim());
|
|
26495
|
+
if (cells.length >= 4) {
|
|
26496
|
+
if (headerRows.length === 0) {
|
|
26497
|
+
headerRows.push(t);
|
|
26498
|
+
continue;
|
|
26499
|
+
}
|
|
26500
|
+
allRows.push(t);
|
|
26501
|
+
const contract = cells[cells.length - 1] ?? "";
|
|
26502
|
+
const candidates = contract.split(/[,;/]/).map((c) => norm(c));
|
|
26503
|
+
if (candidates.some((c) => c === wanted || c.length > 3 && (c.includes(wanted) || wanted.includes(c)))) {
|
|
26504
|
+
matchedRows.push(t);
|
|
26505
|
+
}
|
|
26506
|
+
}
|
|
26507
|
+
}
|
|
26508
|
+
const rows = matchedRows.length > 0 ? matchedRows : allRows.slice(0, 3);
|
|
26509
|
+
if (rows.length === 0) return "";
|
|
26510
|
+
let out = headerRows[0] ?? "";
|
|
26511
|
+
for (const r of rows) {
|
|
26512
|
+
if (out.length + r.length + 1 > maxChars) break;
|
|
26513
|
+
out += "\n" + r;
|
|
26514
|
+
}
|
|
26515
|
+
return out;
|
|
26516
|
+
}
|
|
26517
|
+
function extractTieredScaffoldingBlock(lessonContent, maxChars = 900) {
|
|
26518
|
+
if (!lessonContent) return "";
|
|
26519
|
+
const lines = lessonContent.split("\n");
|
|
26520
|
+
let capture = null;
|
|
26521
|
+
for (const line of lines) {
|
|
26522
|
+
const h = line.match(/^#{2,4}\s+(.*)$/);
|
|
26523
|
+
if (h) {
|
|
26524
|
+
const isTierHeading = /elaborate|tiered|scaffold/i.test(h[1] ?? "");
|
|
26525
|
+
if (capture && !isTierHeading) break;
|
|
26526
|
+
if (isTierHeading) capture = [];
|
|
26527
|
+
continue;
|
|
26528
|
+
}
|
|
26529
|
+
if (capture) {
|
|
26530
|
+
capture.push(line);
|
|
26531
|
+
if (capture.join("").length >= maxChars) break;
|
|
26532
|
+
}
|
|
26533
|
+
}
|
|
26534
|
+
const body = (capture ?? []).join("\n").trim();
|
|
26535
|
+
return body ? body.slice(0, maxChars) : "";
|
|
26536
|
+
}
|
|
26537
|
+
function extractAssessmentMap(lessonContent, maxChars = 1200) {
|
|
26538
|
+
if (!lessonContent) return "";
|
|
26539
|
+
const lines = lessonContent.split("\n");
|
|
26540
|
+
let capture = null;
|
|
26541
|
+
for (const line of lines) {
|
|
26542
|
+
const h = line.match(/^#{2,4}\s+(.*)$/);
|
|
26543
|
+
if (h) {
|
|
26544
|
+
const isTarget = /assessment\s*map/i.test(h[1] ?? "");
|
|
26545
|
+
if (capture && !isTarget) break;
|
|
26546
|
+
if (isTarget) capture = [];
|
|
26547
|
+
continue;
|
|
26548
|
+
}
|
|
26549
|
+
if (capture) {
|
|
26550
|
+
capture.push(line);
|
|
26551
|
+
if (capture.join("").length >= maxChars) break;
|
|
26552
|
+
}
|
|
26553
|
+
}
|
|
26554
|
+
const body = (capture ?? []).join("\n").trim();
|
|
26555
|
+
return body ? body.slice(0, maxChars) : "";
|
|
26556
|
+
}
|
|
26557
|
+
function blockMeta(slot, source, text, verified, issues = []) {
|
|
26558
|
+
return { slot, source, chars: text.length, verified, issues };
|
|
26559
|
+
}
|
|
26560
|
+
function buildSatelliteContext(input) {
|
|
26561
|
+
const {
|
|
26562
|
+
artifactType,
|
|
26563
|
+
commonContext,
|
|
26564
|
+
lessonContent,
|
|
26565
|
+
lessonExcerpt,
|
|
26566
|
+
expositionContent,
|
|
26567
|
+
slcMarkdown,
|
|
26568
|
+
symbolLedgerBlock,
|
|
26569
|
+
pedagogyLabel,
|
|
26570
|
+
mode = "legacy"
|
|
26571
|
+
} = input;
|
|
26572
|
+
const type = (artifactType || "").toUpperCase().trim();
|
|
26573
|
+
if (mode === "legacy" || type === "LESSON") {
|
|
26574
|
+
const context2 = `${commonContext}
|
|
26575
|
+
|
|
26576
|
+
[CANONICAL LESSON PLAN (${pedagogyLabel ?? "LESSON"})]:
|
|
26577
|
+
${lessonExcerpt.excerpt}${symbolLedgerBlock}`;
|
|
26578
|
+
return {
|
|
26579
|
+
context: context2,
|
|
26580
|
+
blocks: [
|
|
26581
|
+
blockMeta("commonContext", "COMPOSITE", commonContext, true),
|
|
26582
|
+
blockMeta("lessonExcerpt", "LESSON", lessonExcerpt.excerpt, lessonExcerpt.verified, lessonExcerpt.issues)
|
|
26583
|
+
],
|
|
26584
|
+
verified: lessonExcerpt.verified,
|
|
26585
|
+
sectionAware: lessonExcerpt.sectionAware,
|
|
26586
|
+
issues: lessonExcerpt.issues,
|
|
26587
|
+
tokenEstimate: Math.round(context2.length / 4)
|
|
26588
|
+
};
|
|
26589
|
+
}
|
|
26590
|
+
const blocks = [];
|
|
26591
|
+
const issues = [];
|
|
26592
|
+
const parts = [commonContext];
|
|
26593
|
+
blocks.push(blockMeta("commonContext", "COMPOSITE", commonContext, true));
|
|
26594
|
+
const compositeHasKx = /###\s+KNOWLEDGE_EXPOSITION/.test(commonContext);
|
|
26595
|
+
if (expositionContent.trim() && !compositeHasKx) {
|
|
26596
|
+
const kxExcerpt = buildSectionAwareExcerpt(expositionContent, {
|
|
26597
|
+
priorities: KX_SLOT_PRIORITIES[type] ?? ["Key Terms", "Concept Narratives", "Worked Micro-Examples"],
|
|
26598
|
+
budget: 4500,
|
|
26599
|
+
sectionLanguageContract: slcMarkdown,
|
|
26600
|
+
artifactType: "KNOWLEDGE_EXPOSITION"
|
|
26601
|
+
});
|
|
26602
|
+
parts.push(`
|
|
26603
|
+
|
|
26604
|
+
[KNOWLEDGE_EXPOSITION (canonical knowledge \u2014 teach from this, do not contradict; scoped for ${type})]:
|
|
26605
|
+
${kxExcerpt.excerpt}`);
|
|
26606
|
+
blocks.push(blockMeta("kxExcerpt:" + type, "KNOWLEDGE_EXPOSITION", kxExcerpt.excerpt, kxExcerpt.verified, kxExcerpt.issues));
|
|
26607
|
+
if (!kxExcerpt.verified) issues.push(...kxExcerpt.issues.map((i) => `kx:${i}`));
|
|
26608
|
+
}
|
|
26609
|
+
if (!KX_ONLY_TYPES.has(type)) {
|
|
26610
|
+
const spec = LESSON_SLOT_BUDGETS[type] ?? { budget: 12e3 };
|
|
26611
|
+
const excerpt = buildSectionAwareExcerpt(lessonContent, {
|
|
26612
|
+
priorities: spec.priorities ?? ["Artifact Contract", "A. Lesson Design Plan", "B. Lesson Flow", "Learning Objectives & Evidence", "Activity Sequence"],
|
|
26613
|
+
budget: spec.budget,
|
|
26614
|
+
sectionLanguageContract: slcMarkdown,
|
|
26615
|
+
artifactType: "LESSON"
|
|
26616
|
+
});
|
|
26617
|
+
parts.push(`
|
|
26618
|
+
|
|
26619
|
+
[CANONICAL LESSON PLAN (scoped for ${type})]:
|
|
26620
|
+
${excerpt.excerpt}`);
|
|
26621
|
+
blocks.push(blockMeta("lessonExcerpt:" + type, "LESSON", excerpt.excerpt, excerpt.verified, excerpt.issues));
|
|
26622
|
+
if (!excerpt.verified) issues.push(...excerpt.issues.map((i) => `lesson:${i}`));
|
|
26623
|
+
if (type === "QUIZ" || type === "WKS") {
|
|
26624
|
+
const am = extractAssessmentMap(lessonContent, 1200);
|
|
26625
|
+
if (am) {
|
|
26626
|
+
parts.push(`
|
|
26627
|
+
|
|
26628
|
+
[ASSESSMENT MAP (criteria anchor \u2014 assess exactly these)]:
|
|
26629
|
+
${am}`);
|
|
26630
|
+
blocks.push(blockMeta("assessmentMap:" + type, "LESSON", am, true));
|
|
26631
|
+
} else {
|
|
26632
|
+
issues.push(`assessment-map:unresolved:${type}`);
|
|
26633
|
+
}
|
|
26634
|
+
}
|
|
26635
|
+
if (symbolLedgerBlock) {
|
|
26636
|
+
parts.push(symbolLedgerBlock);
|
|
26637
|
+
blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
|
|
26638
|
+
}
|
|
26639
|
+
} else {
|
|
26640
|
+
const contractRows = extractActivityContractRows(lessonContent, type === "CODE" ? "CODE_LAB" : type, 1500);
|
|
26641
|
+
const tierBlock = type === "CODE" || type === "EXT" ? extractTieredScaffoldingBlock(lessonContent, 900) : "";
|
|
26642
|
+
const mini = [
|
|
26643
|
+
contractRows ? `### Activity Sequence rows assigned to ${type} (canonical \u2014 do not invent a different task):
|
|
26644
|
+
${contractRows}` : "",
|
|
26645
|
+
tierBlock ? `### Tiered task ladder (from LESSON Elaborate phase):
|
|
26646
|
+
${tierBlock}` : ""
|
|
26647
|
+
].filter(Boolean).join("\n\n");
|
|
26648
|
+
if (mini) {
|
|
26649
|
+
parts.push(`
|
|
26650
|
+
|
|
26651
|
+
[LESSON ACTIVITY CONTRACT (mini-slot)]:
|
|
26652
|
+
${mini}`);
|
|
26653
|
+
blocks.push(blockMeta("activityContract:" + type, "LESSON", mini, true));
|
|
26654
|
+
} else {
|
|
26655
|
+
issues.push(`activity-contract:unresolved:${type}`);
|
|
26656
|
+
blocks.push(blockMeta("activityContract:" + type, "LESSON", "", false, ["no-matching-rows"]));
|
|
26657
|
+
}
|
|
26658
|
+
if (type === "CODE" && symbolLedgerBlock) {
|
|
26659
|
+
parts.push(symbolLedgerBlock);
|
|
26660
|
+
blocks.push(blockMeta("symbolLedger", "LESSON", symbolLedgerBlock, true));
|
|
26661
|
+
}
|
|
26662
|
+
}
|
|
26663
|
+
const context = parts.join("");
|
|
26664
|
+
return {
|
|
26665
|
+
context,
|
|
26666
|
+
blocks,
|
|
26667
|
+
verified: blocks.filter((b) => b.slot !== "commonContext").every((b) => b.verified),
|
|
26668
|
+
sectionAware: blocks.some((b) => b.slot.startsWith("lessonExcerpt") || b.slot.startsWith("kxExcerpt")),
|
|
26669
|
+
issues,
|
|
26670
|
+
tokenEstimate: Math.round(context.length / 4)
|
|
26671
|
+
};
|
|
26672
|
+
}
|
|
26673
|
+
|
|
26102
26674
|
// src/services/lessonProductionService.ts
|
|
26103
26675
|
var __filename2 = typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" && typeof url.fileURLToPath === "function" ? url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) : "";
|
|
26104
26676
|
var _resolvedDir = __filename2 ? path3__default.default.dirname(__filename2) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
@@ -26671,9 +27243,9 @@ ${renderHorizonPromptBlock(horizon)}`;
|
|
|
26671
27243
|
console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
|
|
26672
27244
|
}
|
|
26673
27245
|
let productSpecBlock = "";
|
|
26674
|
-
const buildGroundTruthBlock = () => {
|
|
27246
|
+
const buildGroundTruthBlock = (includeKx = true) => {
|
|
26675
27247
|
const parts = [];
|
|
26676
|
-
if (expositionContext) {
|
|
27248
|
+
if (includeKx && expositionContext) {
|
|
26677
27249
|
parts.push(`### KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)
|
|
26678
27250
|
${expositionContext}`);
|
|
26679
27251
|
}
|
|
@@ -26707,10 +27279,10 @@ ${sessionSliceContext}` : "";
|
|
|
26707
27279
|
const guardrailBlock = `
|
|
26708
27280
|
|
|
26709
27281
|
${platformGuardrail}`;
|
|
26710
|
-
const assembleCommonContext = (sg) => `${baseContextPrefix}
|
|
27282
|
+
const assembleCommonContext = (sg, includeKx = true) => `${baseContextPrefix}
|
|
26711
27283
|
|
|
26712
27284
|
[CONTENT STYLE GUIDE EXCERPT]:
|
|
26713
|
-
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
|
|
27285
|
+
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock(includeKx)}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
|
|
26714
27286
|
let commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
26715
27287
|
const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
|
|
26716
27288
|
const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
|
|
@@ -27119,10 +27691,32 @@ ${currentContent}` }],
|
|
|
27119
27691
|
- Entry Source File: \`${symbolLedger.entryFileName || symbolLedger.primarySymbol}\`
|
|
27120
27692
|
- Key Identifiers to inherit verbatim: ${symbolLedger.keySymbols.map((s) => `\`${s}\``).join(", ")}
|
|
27121
27693
|
- INVARIANT: You MUST use these exact identifier names in all code examples, exercises, and test assertions. Do NOT invent new struct/class names!` : "";
|
|
27122
|
-
const
|
|
27123
|
-
|
|
27124
|
-
|
|
27125
|
-
|
|
27694
|
+
const routingMode = options.contextRoutingMode ?? "legacy";
|
|
27695
|
+
const kxFreeCommonContext = routingMode === "hybrid" ? assembleCommonContext(effectiveStyleGuide, false) : "";
|
|
27696
|
+
const satelliteContexts = {};
|
|
27697
|
+
const satelliteContextFor = (artifactType) => {
|
|
27698
|
+
const key = artifactType.toUpperCase();
|
|
27699
|
+
if (!satelliteContexts[key]) {
|
|
27700
|
+
const built = buildSatelliteContext({
|
|
27701
|
+
artifactType: key,
|
|
27702
|
+
commonContext: routingMode === "hybrid" ? kxFreeCommonContext : commonContext,
|
|
27703
|
+
lessonContent,
|
|
27704
|
+
lessonExcerpt: lessonExcerptResult,
|
|
27705
|
+
expositionContent: expositionContext,
|
|
27706
|
+
slcMarkdown,
|
|
27707
|
+
symbolLedgerBlock,
|
|
27708
|
+
pedagogyLabel,
|
|
27709
|
+
mode: routingMode
|
|
27710
|
+
});
|
|
27711
|
+
satelliteContexts[key] = built.context;
|
|
27712
|
+
onProgress?.(
|
|
27713
|
+
"@content",
|
|
27714
|
+
`[CONTEXT] ${key} satellite context: ${built.context.length} chars (${built.blocks.map((b) => `${b.slot}:${b.chars}`).join(", ")})`,
|
|
27715
|
+
{ type: "progress", promptChars: built.context.length, artifactType: key }
|
|
27716
|
+
);
|
|
27717
|
+
}
|
|
27718
|
+
return satelliteContexts[key];
|
|
27719
|
+
};
|
|
27126
27720
|
const judgeSat = (sat, content) => {
|
|
27127
27721
|
if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
|
|
27128
27722
|
const det = validateArtifactDeterministic({ content, refPack });
|
|
@@ -27203,7 +27797,7 @@ ${languageDirective}
|
|
|
27203
27797
|
${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
27204
27798
|
const rawAct = await runCurriculumAIInference(
|
|
27205
27799
|
[{ role: "user", content: actPrompt }],
|
|
27206
|
-
|
|
27800
|
+
satelliteContextFor("ACT"),
|
|
27207
27801
|
runnerOptions,
|
|
27208
27802
|
(chunk, type) => {
|
|
27209
27803
|
options.onProgress?.("@activity", chunk, { type: type || "content", artifactType: "ACT" });
|
|
@@ -27273,7 +27867,7 @@ ${languageDirective}
|
|
|
27273
27867
|
${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
27274
27868
|
const rawQuiz = await runCurriculumAIInference(
|
|
27275
27869
|
[{ role: "user", content: quizPrompt }],
|
|
27276
|
-
|
|
27870
|
+
satelliteContextFor("QUIZ"),
|
|
27277
27871
|
runnerOptions,
|
|
27278
27872
|
(chunk, type) => {
|
|
27279
27873
|
options.onProgress?.("@assessor", chunk, { type: type || "content", artifactType: "QUIZ" });
|
|
@@ -27326,7 +27920,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
27326
27920
|
headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
|
|
27327
27921
|
groundContext: slideGroundContext,
|
|
27328
27922
|
productSpecBlock: productSpecBlock || void 0,
|
|
27329
|
-
satelliteContext,
|
|
27923
|
+
satelliteContext: satelliteContextFor("SLIDE"),
|
|
27330
27924
|
runnerOptions,
|
|
27331
27925
|
onProgress: (agent, msg, meta) => {
|
|
27332
27926
|
options.onProgress?.(agent, msg, meta || { type: "content", artifactType: "SLIDE" });
|
|
@@ -27410,7 +28004,7 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
27410
28004
|
});
|
|
27411
28005
|
const rawSlide = await runCurriculumAIInference(
|
|
27412
28006
|
[{ role: "user", content: slidePrompt }],
|
|
27413
|
-
|
|
28007
|
+
satelliteContextFor("SLIDE"),
|
|
27414
28008
|
runnerOptions,
|
|
27415
28009
|
(chunk, type) => {
|
|
27416
28010
|
options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
|
|
@@ -27471,7 +28065,7 @@ ${languageDirective}
|
|
|
27471
28065
|
${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
27472
28066
|
const rawGuide = await runCurriculumAIInference(
|
|
27473
28067
|
[{ role: "user", content: guidePrompt }],
|
|
27474
|
-
|
|
28068
|
+
satelliteContextFor("GUIDE"),
|
|
27475
28069
|
runnerOptions,
|
|
27476
28070
|
(chunk, type) => {
|
|
27477
28071
|
options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "GUIDE" });
|
|
@@ -27523,7 +28117,7 @@ ${languageDirective}
|
|
|
27523
28117
|
${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
27524
28118
|
const rawHandout = await runCurriculumAIInference(
|
|
27525
28119
|
[{ role: "user", content: handoutPrompt }],
|
|
27526
|
-
|
|
28120
|
+
satelliteContextFor("HANDOUT"),
|
|
27527
28121
|
runnerOptions,
|
|
27528
28122
|
(chunk, type) => {
|
|
27529
28123
|
options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "HANDOUT" });
|
|
@@ -27579,7 +28173,7 @@ ${languageDirective}
|
|
|
27579
28173
|
${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
27580
28174
|
const rawWks = await runCurriculumAIInference(
|
|
27581
28175
|
[{ role: "user", content: wksPrompt }],
|
|
27582
|
-
|
|
28176
|
+
satelliteContextFor("WKS"),
|
|
27583
28177
|
runnerOptions,
|
|
27584
28178
|
(chunk, type) => {
|
|
27585
28179
|
options.onProgress?.("@content", chunk, { type: type || "chunk", artifactType: "WKS" });
|
|
@@ -27639,7 +28233,7 @@ ${languageDirective}
|
|
|
27639
28233
|
${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
27640
28234
|
const rawCode = await runCurriculumAIInference(
|
|
27641
28235
|
[{ role: "user", content: codePrompt }],
|
|
27642
|
-
|
|
28236
|
+
satelliteContextFor("CODE"),
|
|
27643
28237
|
runnerOptions,
|
|
27644
28238
|
(chunk, type) => {
|
|
27645
28239
|
options.onProgress?.("@activity", chunk, { type: type || "chunk", artifactType: "CODE" });
|
|
@@ -27726,7 +28320,7 @@ ${languageDirective}
|
|
|
27726
28320
|
${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
|
|
27727
28321
|
const rawExt = await runCurriculumAIInference(
|
|
27728
28322
|
[{ role: "user", content: extPrompt }],
|
|
27729
|
-
|
|
28323
|
+
satelliteContextFor("EXT"),
|
|
27730
28324
|
runnerOptions,
|
|
27731
28325
|
(chunk, type) => {
|
|
27732
28326
|
options.onProgress?.("@activity", chunk, { type: type || "chunk", artifactType: "EXT" });
|
|
@@ -29703,26 +30297,40 @@ Return concise JSON matching:
|
|
|
29703
30297
|
"technicalGotchas": ["Important safety or compatibility note 1", "Note 2"]
|
|
29704
30298
|
}`;
|
|
29705
30299
|
let rawContent = "";
|
|
29706
|
-
|
|
29707
|
-
|
|
29708
|
-
|
|
29709
|
-
|
|
29710
|
-
|
|
29711
|
-
|
|
29712
|
-
|
|
29713
|
-
|
|
29714
|
-
|
|
29715
|
-
|
|
29716
|
-
|
|
29717
|
-
|
|
29718
|
-
|
|
29719
|
-
|
|
29720
|
-
|
|
29721
|
-
|
|
29722
|
-
|
|
29723
|
-
|
|
29724
|
-
|
|
29725
|
-
|
|
30300
|
+
if (options.customInference) {
|
|
30301
|
+
rawContent = await options.customInference({
|
|
30302
|
+
systemInstruction: "You are an authoritative STEM & CS curriculum researcher. Output valid JSON only.",
|
|
30303
|
+
userPrompt: researchPrompt,
|
|
30304
|
+
messages: [{ role: "user", content: researchPrompt }],
|
|
30305
|
+
temperature: 0.2,
|
|
30306
|
+
maxTokens: 32768,
|
|
30307
|
+
onChunk: (token, type) => {
|
|
30308
|
+
if (type === "content") rawContent += token;
|
|
30309
|
+
if (type === "content" || type === "thought") onChunk?.(token, type);
|
|
30310
|
+
}
|
|
30311
|
+
}) || "";
|
|
30312
|
+
} else {
|
|
30313
|
+
await streamLLMWithFallback(
|
|
30314
|
+
"You are an authoritative STEM & CS curriculum researcher. Output valid JSON only.",
|
|
30315
|
+
researchPrompt,
|
|
30316
|
+
apiKeys,
|
|
30317
|
+
(chunk, type) => {
|
|
30318
|
+
if (type === "content") rawContent += chunk;
|
|
30319
|
+
onChunk?.(chunk, type);
|
|
30320
|
+
},
|
|
30321
|
+
options.model,
|
|
30322
|
+
options.provider,
|
|
30323
|
+
// Research may run live web grounding — generous budget, still idle-guarded.
|
|
30324
|
+
resolveStreamBudget(void 0, {
|
|
30325
|
+
idleMs: options.idleTimeoutMs,
|
|
30326
|
+
totalMs: options.timeoutMs,
|
|
30327
|
+
model: options.model,
|
|
30328
|
+
provider: options.provider
|
|
30329
|
+
}),
|
|
30330
|
+
options.signal,
|
|
30331
|
+
options.onProviderEvent
|
|
30332
|
+
);
|
|
30333
|
+
}
|
|
29726
30334
|
let parsedResearch = {
|
|
29727
30335
|
groundTruthSummary: `Verified ecosystem standards for ${projectName}`,
|
|
29728
30336
|
hardwareVersion: "Standard Environment",
|
|
@@ -30812,6 +31420,75 @@ var DeterministicStructuralLinter = class {
|
|
|
30812
31420
|
strengths
|
|
30813
31421
|
};
|
|
30814
31422
|
}
|
|
31423
|
+
/**
|
|
31424
|
+
* P50: Validates macro-pedagogical invariants across a CurriculumPlan.
|
|
31425
|
+
* Checks:
|
|
31426
|
+
* 1. ZPD violations without self-healing recap (NO_ZPD_BRIDGE)
|
|
31427
|
+
* 2. Walking Skeleton existence (Unit 1 delivers working software)
|
|
31428
|
+
* 3. Inter-unit Mastery Gate presence
|
|
31429
|
+
*/
|
|
31430
|
+
static lintPlan(plan) {
|
|
31431
|
+
const findings = [];
|
|
31432
|
+
const strengths = [];
|
|
31433
|
+
let score = 100;
|
|
31434
|
+
for (const s of plan.sessions) {
|
|
31435
|
+
if (s.zpd_status?.verdict === "TOO_MANY_NEW") {
|
|
31436
|
+
score -= 10;
|
|
31437
|
+
findings.push({
|
|
31438
|
+
id: `PLAN_TOO_MANY_NEW_${s.id}`,
|
|
31439
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
31440
|
+
severity: "MINOR",
|
|
31441
|
+
title: `Cognitive Overload in Session ${s.id}`,
|
|
31442
|
+
description: s.zpd_status.issues.join("; ") || `Session introduces too many new concepts/keywords, exceeding ZPD capacity.`,
|
|
31443
|
+
remediationAdvice: `Distribute new concepts across multiple sessions or introduce via scaffolding.`,
|
|
31444
|
+
affectedElement: s.id
|
|
31445
|
+
});
|
|
31446
|
+
} else if (s.zpd_status?.verdict === "NO_ZPD_BRIDGE") {
|
|
31447
|
+
const hasRecap = s.prerequisite_decisions.some((d) => d.decision === "recap_in_lesson");
|
|
31448
|
+
if (!hasRecap) {
|
|
31449
|
+
score -= 15;
|
|
31450
|
+
findings.push({
|
|
31451
|
+
id: `PLAN_NO_ZPD_BRIDGE_${s.id}`,
|
|
31452
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
31453
|
+
severity: "MAJOR",
|
|
31454
|
+
title: `Unhealed ZPD Bridge in Session ${s.id}`,
|
|
31455
|
+
description: `Session introduces new concepts with 0 known concept bridges, and lacks recap scaffolding.`,
|
|
31456
|
+
remediationAdvice: `Add a recap_in_lesson prerequisite decision to anchor new learning in prior concepts.`,
|
|
31457
|
+
affectedElement: s.id
|
|
31458
|
+
});
|
|
31459
|
+
}
|
|
31460
|
+
}
|
|
31461
|
+
}
|
|
31462
|
+
if (!plan.walking_skeleton || plan.walking_skeleton.epitome_deliverables.length === 0) {
|
|
31463
|
+
score -= 10;
|
|
31464
|
+
findings.push({
|
|
31465
|
+
id: "PLAN_MISSING_WALKING_SKELETON",
|
|
31466
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
31467
|
+
severity: "MINOR",
|
|
31468
|
+
title: "Missing Walking Skeleton Definition",
|
|
31469
|
+
description: "Plan does not declare an Epitome (walking skeleton) in Unit 1.",
|
|
31470
|
+
remediationAdvice: "Ensure Unit 1 defines minimal end-to-end deliverables."
|
|
31471
|
+
});
|
|
31472
|
+
} else {
|
|
31473
|
+
strengths.push(`Unit ${plan.walking_skeleton.epitome_unit_id} establishes an end-to-end Walking Skeleton (Epitome).`);
|
|
31474
|
+
}
|
|
31475
|
+
if (plan.units.length > 1 && (!plan.mastery_gates || plan.mastery_gates.length === 0)) {
|
|
31476
|
+
score -= 10;
|
|
31477
|
+
findings.push({
|
|
31478
|
+
id: "PLAN_MISSING_MASTERY_GATES",
|
|
31479
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
31480
|
+
severity: "MINOR",
|
|
31481
|
+
title: "Missing Inter-Unit Mastery Gates",
|
|
31482
|
+
description: "Multi-unit course lacks formal transition gates between units.",
|
|
31483
|
+
remediationAdvice: "Define exit criteria and remediation sprints for each inter-unit boundary."
|
|
31484
|
+
});
|
|
31485
|
+
} else if (plan.mastery_gates && plan.mastery_gates.length > 0) {
|
|
31486
|
+
strengths.push(`${plan.mastery_gates.length} inter-unit Mastery Gates defined with remediation protocols.`);
|
|
31487
|
+
}
|
|
31488
|
+
score = Math.max(0, Math.min(100, score));
|
|
31489
|
+
const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
|
|
31490
|
+
return { passed, score, findings, strengths };
|
|
31491
|
+
}
|
|
30815
31492
|
};
|
|
30816
31493
|
|
|
30817
31494
|
// src/evaluators/academicAuditor.ts
|
|
@@ -32482,6 +33159,8 @@ exports.CodeLabSchema = CodeLabSchema;
|
|
|
32482
33159
|
exports.CodeSnippetSchema = CodeSnippetSchema;
|
|
32483
33160
|
exports.CompetencyRubricRowSchema = CompetencyRubricRowSchema;
|
|
32484
33161
|
exports.ComputationalThinkingSchema = ComputationalThinkingSchema;
|
|
33162
|
+
exports.ConceptPrerequisiteEdgeSchema = ConceptPrerequisiteEdgeSchema;
|
|
33163
|
+
exports.ConceptSpiralEncounterSchema = ConceptSpiralEncounterSchema;
|
|
32485
33164
|
exports.ConstructiveAlignmentEvaluator = ConstructiveAlignmentEvaluator;
|
|
32486
33165
|
exports.CoreConceptSchema = CoreConceptSchema;
|
|
32487
33166
|
exports.CourseObjectiveSchema = CourseObjectiveSchema;
|
|
@@ -32556,6 +33235,7 @@ exports.LessonSectionSchema = LessonSectionSchema;
|
|
|
32556
33235
|
exports.LocalWorkspaceManager = LocalWorkspaceManager;
|
|
32557
33236
|
exports.MappingKindSchema = MappingKindSchema;
|
|
32558
33237
|
exports.MarpSlideSchema = MarpSlideSchema;
|
|
33238
|
+
exports.MasteryGateSchema = MasteryGateSchema;
|
|
32559
33239
|
exports.MediaError = MediaError;
|
|
32560
33240
|
exports.MediaLedger = MediaLedger;
|
|
32561
33241
|
exports.MentorCheatsheetSchema = MentorCheatsheetSchema;
|
|
@@ -32609,6 +33289,7 @@ exports.SelfLabSchema = SelfLabSchema;
|
|
|
32609
33289
|
exports.SelfPacedBundleSchema = SelfPacedBundleSchema;
|
|
32610
33290
|
exports.SelfPacedChallengeSchema = SelfPacedChallengeSchema;
|
|
32611
33291
|
exports.SessionPlanSchema = SessionPlanSchema;
|
|
33292
|
+
exports.SessionZpdStatusSchema = SessionZpdStatusSchema;
|
|
32612
33293
|
exports.SilverTierSchema = SilverTierSchema;
|
|
32613
33294
|
exports.SlideDeckSchema = SlideDeckSchema;
|
|
32614
33295
|
exports.StandardizedTermRowSchema = StandardizedTermRowSchema;
|
|
@@ -32633,20 +33314,24 @@ exports.TroubleshootingRowSchema = TroubleshootingRowSchema;
|
|
|
32633
33314
|
exports.UnitPlanSchema = UnitPlanSchema;
|
|
32634
33315
|
exports.UserJourneySchema = UserJourneySchema;
|
|
32635
33316
|
exports.WORKSHEET_TEMPLATE = WORKSHEET_TEMPLATE;
|
|
33317
|
+
exports.WalkingSkeletonSchema = WalkingSkeletonSchema;
|
|
32636
33318
|
exports.WorksheetItemSchema = WorksheetItemSchema;
|
|
32637
33319
|
exports.WorksheetItemTypeEnum = WorksheetItemTypeEnum;
|
|
32638
33320
|
exports.WorksheetPartSchema = WorksheetPartSchema;
|
|
32639
33321
|
exports.WorksheetSchema = WorksheetSchema;
|
|
33322
|
+
exports.ZpdVerdictSchema = ZpdVerdictSchema;
|
|
32640
33323
|
exports.activityTools = activityTools;
|
|
32641
33324
|
exports.analystTools = analystTools;
|
|
32642
33325
|
exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
|
|
32643
33326
|
exports.assertAcyclic = assertAcyclic;
|
|
32644
33327
|
exports.assessorTools = assessorTools;
|
|
33328
|
+
exports.assignDepths = assignDepths;
|
|
32645
33329
|
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
32646
33330
|
exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
|
|
32647
33331
|
exports.auditQualityReport = auditQualityReport;
|
|
32648
33332
|
exports.buildActivityPrompt = buildActivityPrompt;
|
|
32649
33333
|
exports.buildCodeLabPrompt = buildCodeLabPrompt;
|
|
33334
|
+
exports.buildConceptPrerequisites = buildConceptPrerequisites;
|
|
32650
33335
|
exports.buildCurriculumContext = buildCurriculumContext;
|
|
32651
33336
|
exports.buildCurriculumPlan = buildCurriculumPlan;
|
|
32652
33337
|
exports.buildDeliveryPackages = buildDeliveryPackages;
|
|
@@ -32664,7 +33349,9 @@ exports.buildLanguageDirective = buildLanguageDirective;
|
|
|
32664
33349
|
exports.buildLessonExcerpt = buildLessonExcerpt;
|
|
32665
33350
|
exports.buildLessonMasterPrompt = buildLessonMasterPrompt;
|
|
32666
33351
|
exports.buildMarpMarkdownSlidePrompt = buildMarpMarkdownSlidePrompt;
|
|
33352
|
+
exports.buildMasteryGates = buildMasteryGates;
|
|
32667
33353
|
exports.buildProjectInstructionPrompt = buildProjectInstructionPrompt;
|
|
33354
|
+
exports.buildSatelliteContext = buildSatelliteContext;
|
|
32668
33355
|
exports.buildSectionAwareExcerpt = buildSectionAwareExcerpt;
|
|
32669
33356
|
exports.buildSelfLabPrompt = buildSelfLabPrompt;
|
|
32670
33357
|
exports.buildSessionSliceContext = buildSessionSliceContext;
|
|
@@ -32673,8 +33360,11 @@ exports.buildStandardStackMarkdown = buildStandardStackMarkdown;
|
|
|
32673
33360
|
exports.buildStandardsContext = buildStandardsContext;
|
|
32674
33361
|
exports.buildStandardsContextBlock = buildStandardsContextBlock;
|
|
32675
33362
|
exports.buildTeacherGuidePrompt = buildTeacherGuidePrompt;
|
|
33363
|
+
exports.buildWalkingSkeleton = buildWalkingSkeleton;
|
|
32676
33364
|
exports.buildWorksheetPrompt = buildWorksheetPrompt;
|
|
33365
|
+
exports.checkSessionZpd = checkSessionZpd;
|
|
32677
33366
|
exports.closeTruncatedJson = closeTruncatedJson;
|
|
33367
|
+
exports.computeConceptSpiralProgression = computeConceptSpiralProgression;
|
|
32678
33368
|
exports.computeContentHash = computeContentHash;
|
|
32679
33369
|
exports.computePackingSpec = computePackingSpec;
|
|
32680
33370
|
exports.conductPreliminaryResearch = conductPreliminaryResearch;
|
|
@@ -32690,6 +33380,7 @@ exports.createStreamChunkExtractor = createStreamChunkExtractor;
|
|
|
32690
33380
|
exports.curateMediaLedger = curateMediaLedger;
|
|
32691
33381
|
exports.designerTools = designerTools;
|
|
32692
33382
|
exports.detectProjectPedagogy = detectProjectPedagogy;
|
|
33383
|
+
exports.dropCyclicConceptEdges = dropCyclicConceptEdges;
|
|
32693
33384
|
exports.emitUsage = emitUsage;
|
|
32694
33385
|
exports.ensureExpositionForLesson = ensureExpositionForLesson;
|
|
32695
33386
|
exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
|
|
@@ -32698,6 +33389,8 @@ exports.executeCurriculumCommand = executeCurriculumCommand;
|
|
|
32698
33389
|
exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
|
|
32699
33390
|
exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
|
|
32700
33391
|
exports.expositionCacheKey = expositionCacheKey;
|
|
33392
|
+
exports.extractActivityContractRows = extractActivityContractRows;
|
|
33393
|
+
exports.extractAssessmentMap = extractAssessmentMap;
|
|
32701
33394
|
exports.extractCurriculumHorizon = extractCurriculumHorizon;
|
|
32702
33395
|
exports.extractJsonArray = extractJsonArray;
|
|
32703
33396
|
exports.extractScopeSequenceRows = extractScopeSequenceRows;
|
|
@@ -32707,6 +33400,7 @@ exports.extractStandardRefs = extractStandardRefs;
|
|
|
32707
33400
|
exports.extractStreamChunk = extractStreamChunk;
|
|
32708
33401
|
exports.extractSymbolLedger = extractSymbolLedger;
|
|
32709
33402
|
exports.extractThoughtAndContent = extractThoughtAndContent;
|
|
33403
|
+
exports.extractTieredScaffoldingBlock = extractTieredScaffoldingBlock;
|
|
32710
33404
|
exports.findStandardStatement = findStandardStatement;
|
|
32711
33405
|
exports.formatQuizzesToCsv = formatQuizzesToCsv;
|
|
32712
33406
|
exports.fulfillMediaLedger = fulfillMediaLedger;
|