@thanh01.pmt/curriculum-kit 1.4.23 → 1.4.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +116 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +116 -44
- package/dist/index.mjs.map +1 -1
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1336,6 +1336,10 @@ function parseLessonFlow(lessonMarkdown) {
|
|
|
1336
1336
|
}
|
|
1337
1337
|
const durMatch = lessonMarkdown.match(/Estimated Duration:\s*([^\n]+)/i);
|
|
1338
1338
|
if (durMatch) result.estimatedDuration = durMatch[1].trim();
|
|
1339
|
+
if (!result.lessonTitle) {
|
|
1340
|
+
const h1 = lessonMarkdown.match(/^#\s+(?:LESSON[^:]*:\s*)?(.+)$/m);
|
|
1341
|
+
if (h1 && h1[1]) result.lessonTitle = h1[1].replace(/\*+/g, "").trim();
|
|
1342
|
+
}
|
|
1339
1343
|
const contractMatch = lessonMarkdown.match(/SLIDE must visualize:?\s*([^\n]+)/i);
|
|
1340
1344
|
if (contractMatch) result.slideContract = contractMatch[1].trim();
|
|
1341
1345
|
const actSeqSectionMatch = lessonMarkdown.match(/###\s*(?:\d+\.\s*)?Activity Sequence[\s\S]*?(?=(?:###|\n##\s+|$))/i);
|
|
@@ -1417,7 +1421,7 @@ var init_lessonFlowParser = __esm({
|
|
|
1417
1421
|
|
|
1418
1422
|
// src/ai/prompts/slideBlueprintPrompt.ts
|
|
1419
1423
|
function buildSlideBlueprintPrompt(params) {
|
|
1420
|
-
const { lessonFlow, targetSlideCount, stylePresetName = "Blue Professional" } = params;
|
|
1424
|
+
const { lessonFlow, targetSlideCount, stylePresetName = "Blue Professional", productSpecBlock } = params;
|
|
1421
1425
|
const activitiesSummary = lessonFlow.activities.map(
|
|
1422
1426
|
(a) => `Seq ${a.seq} [${a.phase}]: ${a.purpose} (Teacher: ${a.teacherMove} | Student: ${a.studentAction} | Time: ${a.time})`
|
|
1423
1427
|
).join("\n");
|
|
@@ -1439,7 +1443,9 @@ ${countGuidance}
|
|
|
1439
1443
|
- Target Duration: ${lessonFlow.estimatedDuration}
|
|
1440
1444
|
- Active Visual Style: "${stylePresetName}"
|
|
1441
1445
|
${lessonFlow.slideContract ? `- Slide Artifact Contract: "${lessonFlow.slideContract}"` : ""}
|
|
1442
|
-
|
|
1446
|
+
${productSpecBlock ? `
|
|
1447
|
+
${productSpecBlock}
|
|
1448
|
+
` : ""}
|
|
1443
1449
|
### \u{1F5FA}\uFE0F CANONICAL ACTIVITY SEQUENCE:
|
|
1444
1450
|
${activitiesSummary || "Standard phased sequence"}
|
|
1445
1451
|
|
|
@@ -1513,7 +1519,7 @@ function buildSlideBatchPrompt(params) {
|
|
|
1513
1519
|
skillPrompt,
|
|
1514
1520
|
language = "Vietnamese",
|
|
1515
1521
|
languageDirective = "",
|
|
1516
|
-
|
|
1522
|
+
// deprecated — see interface doc
|
|
1517
1523
|
groundContext = ""
|
|
1518
1524
|
} = params;
|
|
1519
1525
|
const slidesSpec = clusterSlides.map((s) => `
|
|
@@ -1534,7 +1540,6 @@ ${groundContext}
|
|
|
1534
1540
|
---` : ""}
|
|
1535
1541
|
### OPERATIONAL GROUND RULES FOR CHUNK GENERATION:
|
|
1536
1542
|
${languageDirective}
|
|
1537
|
-
${headingDirective}
|
|
1538
1543
|
1. **FOCUS ON CLUSTER ${clusterId}: "${clusterTitle}"**:
|
|
1539
1544
|
Generate EXACTLY the ${clusterSlides.length} slides requested in the user prompt.
|
|
1540
1545
|
2. **ZERO ABBREVIATION / NO "// TODO"**:
|
|
@@ -1725,7 +1730,7 @@ async function inferStructured(schema, systemPrompt, userPrompt, options, label)
|
|
|
1725
1730
|
const messages = [
|
|
1726
1731
|
{ role: "user", content: [systemPrompt, userPrompt].filter(Boolean).join("\n\n") }
|
|
1727
1732
|
];
|
|
1728
|
-
const raw = await runCurriculumAIInference(messages,
|
|
1733
|
+
const raw = await runCurriculumAIInference(messages, "", options.runnerOptions, (token, type) => {
|
|
1729
1734
|
if (type === "usage") options.onProgress?.("@illustrator", token, { type: "usage" });
|
|
1730
1735
|
});
|
|
1731
1736
|
const fenceMatch = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
@@ -1911,7 +1916,8 @@ async function executeSlideProductionWorkflow(options) {
|
|
|
1911
1916
|
const blueprintPrompt = buildSlideBlueprintPrompt({
|
|
1912
1917
|
lessonFlow,
|
|
1913
1918
|
targetSlideCount,
|
|
1914
|
-
stylePresetName: stylePreset?.name || "Blue Professional"
|
|
1919
|
+
stylePresetName: stylePreset?.name || "Blue Professional",
|
|
1920
|
+
productSpecBlock: options.productSpecBlock
|
|
1915
1921
|
});
|
|
1916
1922
|
let blueprintItems;
|
|
1917
1923
|
let allGeneratedSlides;
|
|
@@ -1994,7 +2000,6 @@ ${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
|
|
|
1994
2000
|
skillPrompt,
|
|
1995
2001
|
language,
|
|
1996
2002
|
languageDirective,
|
|
1997
|
-
headingDirective,
|
|
1998
2003
|
groundContext: options.groundContext
|
|
1999
2004
|
});
|
|
2000
2005
|
try {
|
|
@@ -11719,19 +11724,30 @@ function extractToolchainFacts(refPack) {
|
|
|
11719
11724
|
const factLines = [];
|
|
11720
11725
|
for (const rawLine of excerpt.split("\n")) {
|
|
11721
11726
|
const line = rawLine.trim();
|
|
11722
|
-
if (!line || line.startsWith("#")) continue;
|
|
11723
|
-
if (
|
|
11724
|
-
|
|
11725
|
-
}
|
|
11727
|
+
if (!line || line.startsWith("#") || line.startsWith("<!--")) continue;
|
|
11728
|
+
if (!/\d{1,2}(?:\.\d{1,2})?/.test(line) && !/Xcode|Swift|macOS|iOS|SDK|Node|Python|JDK|\.NET/i.test(line)) continue;
|
|
11729
|
+
factLines.push(line.replace(/^[*\-•]\s*/, "- "));
|
|
11726
11730
|
if (factLines.length >= 8) break;
|
|
11727
11731
|
}
|
|
11728
11732
|
return factLines;
|
|
11729
11733
|
}
|
|
11734
|
+
function derivePrimaryTechnology(facts, briefing) {
|
|
11735
|
+
const explicit = (briefing?.coreTechnology || briefing?.techStack || "").trim();
|
|
11736
|
+
if (explicit && explicit.length <= 80) return explicit;
|
|
11737
|
+
const found = [];
|
|
11738
|
+
const CANON = ["Swift", "SwiftUI", "macOS", "iOS", "Xcode", "SwiftData", "Node.js", "TypeScript", "Python", "React"];
|
|
11739
|
+
for (const name of CANON) {
|
|
11740
|
+
if (facts.some((f) => f.includes(name)) && !found.includes(name)) found.push(name);
|
|
11741
|
+
}
|
|
11742
|
+
if (found.length > 0) return found.slice(0, 4).join(" + ");
|
|
11743
|
+
const topic = (briefing?.topic || "").trim();
|
|
11744
|
+
return topic && topic.length <= 60 ? topic : "";
|
|
11745
|
+
}
|
|
11730
11746
|
function buildProductSpecBlock(input) {
|
|
11731
11747
|
const b = input.briefing || {};
|
|
11732
|
-
const platform = (input.overrides?.primaryLanguage || b.coreTechnology || b.techStack || b.topic || "").trim();
|
|
11733
|
-
const hardware = (b.hardwarePlatform || b.studentEquipment || b.classDynamic || "").trim();
|
|
11734
11748
|
const toolchainFacts = input.refPack ? extractToolchainFacts(input.refPack) : [];
|
|
11749
|
+
const platform = (input.overrides?.primaryLanguage || derivePrimaryTechnology(toolchainFacts, b) || "").trim();
|
|
11750
|
+
const hardware = (b.hardwarePlatform || b.studentEquipment || b.classDynamic || "").trim();
|
|
11735
11751
|
const lines = [];
|
|
11736
11752
|
lines.push("[PRODUCT SPEC \u2014 CANONICAL, BINDING FOR EVERY ARTIFACT OF THIS PROJECT]:");
|
|
11737
11753
|
lines.push("All decisions below are PROJECT-WIDE. Every artifact (LESSON, satellites, KX)");
|
|
@@ -11747,7 +11763,7 @@ function buildProductSpecBlock(input) {
|
|
|
11747
11763
|
if (input.overrides?.deliverableTemplate) {
|
|
11748
11764
|
lines.push(`- Deliverable template: ${input.overrides.deliverableTemplate}`);
|
|
11749
11765
|
}
|
|
11750
|
-
if (toolchainFacts.length > 0) {
|
|
11766
|
+
if (toolchainFacts.length > 0 && !input.skipToolchainList) {
|
|
11751
11767
|
lines.push("- Toolchain & versions (ground truth):");
|
|
11752
11768
|
for (const f of toolchainFacts) lines.push(` ${f}`);
|
|
11753
11769
|
}
|
|
@@ -11875,7 +11891,9 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11875
11891
|
const productSpecBlock = buildProductSpecBlock({
|
|
11876
11892
|
refPack,
|
|
11877
11893
|
briefing: techStack || hardwarePlatform ? { techStack, hardwarePlatform } : void 0,
|
|
11878
|
-
overrides: options.productSpec
|
|
11894
|
+
overrides: options.productSpec,
|
|
11895
|
+
skipToolchainList: true
|
|
11896
|
+
// same RefPack excerpt already in [GROUND TRUTH]
|
|
11879
11897
|
});
|
|
11880
11898
|
const originalUserPrompt = buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown, refPack) + (productSpecBlock ? "\n\n" + productSpecBlock : "");
|
|
11881
11899
|
const content = (await llmFn(
|
|
@@ -11975,17 +11993,28 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
11975
11993
|
const lpRaw = await storage.readSotDocument(projectId, "LEARNER_PROFILE.md");
|
|
11976
11994
|
if (lpRaw) {
|
|
11977
11995
|
const hwPatterns = [
|
|
11996
|
+
// "Cơ sở phần cứng (Hardware Ecosystem): Học sinh thực hành trên máy..."
|
|
11997
|
+
// — the audit: the old pattern captured the parenthetical label
|
|
11998
|
+
// "(Hardware Ecosystem):" itself. Capture AFTER the closing paren + colon.
|
|
11999
|
+
/\([^)]*Ecosystem[^)]*\)\s*:\s*([^\n\r]+)/i,
|
|
11978
12000
|
/(?:Student Equipment|Hardware|Platform|Thiết bị|Thiết bị học tập)\s*(?:\(|:|\*)*\s*([^\n\r]+?)(?:\s*\*\*|[.).]?\s*$)/i,
|
|
11979
12001
|
/(?:máy|device|machine)\s+([^,.;\n]{4,60}(?:M\d|Intel|PC|computer|mini)[^,.;\n]{0,30})/i
|
|
11980
12002
|
];
|
|
11981
12003
|
for (const p of hwPatterns) {
|
|
11982
12004
|
const m = lpRaw.match(p);
|
|
11983
|
-
if (m && !hw) {
|
|
11984
|
-
hw = m[1].trim();
|
|
12005
|
+
if (m && m[1] && !hw) {
|
|
12006
|
+
hw = m[1].trim().replace(/\*+/g, "").slice(0, 300);
|
|
11985
12007
|
break;
|
|
11986
12008
|
}
|
|
11987
12009
|
}
|
|
11988
12010
|
}
|
|
12011
|
+
if (!hw) {
|
|
12012
|
+
const rpRaw = await storage.readSotDocument(projectId, "REFERENCE_PACK.md");
|
|
12013
|
+
if (rpRaw) {
|
|
12014
|
+
const rp = rpRaw.match(/(?:School Lab|Thiết bị lớp học)[^\n]*\*\*([^*]+(?:M\d|Mac)[^*]*)\*\*/i);
|
|
12015
|
+
if (rp && rp[1]) hw = rp[1].trim();
|
|
12016
|
+
}
|
|
12017
|
+
}
|
|
11989
12018
|
} catch {
|
|
11990
12019
|
}
|
|
11991
12020
|
}
|
|
@@ -12722,11 +12751,11 @@ function buildSessionSliceContext(plan, lessonCode) {
|
|
|
12722
12751
|
lines.push("- Time split: knowledge " + s.knowledge_minutes + "m / practice " + s.practice_minutes + "m / overhead " + s.overhead_minutes + "m");
|
|
12723
12752
|
lines.push("- Depth assignments: " + s.depth_assignments.map((d) => d.node_id + "=" + d.depth.toUpperCase() + "(" + d.source + ")").join("; "));
|
|
12724
12753
|
if (s.prerequisite_decisions.length > 0) {
|
|
12725
|
-
lines.push("- Prerequisite decisions [
|
|
12754
|
+
lines.push("- Prerequisite decisions [" + s.prerequisite_decisions.length + "]:");
|
|
12726
12755
|
for (const d of s.prerequisite_decisions) lines.push(" * " + d.node_id + ": " + d.decision + " \u2014 " + d.reason);
|
|
12727
12756
|
}
|
|
12728
12757
|
if (s.scaffold_decisions.length > 0) {
|
|
12729
|
-
lines.push("- Scaffold decisions [
|
|
12758
|
+
lines.push("- Scaffold decisions [" + s.scaffold_decisions.length + "]:");
|
|
12730
12759
|
for (const d of s.scaffold_decisions) lines.push(" * " + d.item + ": " + d.decision + (d.minutes_saved > 0 ? " (saves " + d.minutes_saved + "m)" : "") + " \u2014 " + d.reason);
|
|
12731
12760
|
}
|
|
12732
12761
|
lines.push("- Exit evidence: " + s.exit_evidence.join("; "));
|
|
@@ -25961,8 +25990,11 @@ function buildFrameworkExcerptForLesson(framework, lessonId) {
|
|
|
25961
25990
|
});
|
|
25962
25991
|
const rowIdx = scopeRows[0] ?? -1;
|
|
25963
25992
|
const currentRow = rowIdx >= 0 ? lines[rowIdx] : "";
|
|
25964
|
-
const
|
|
25965
|
-
const
|
|
25993
|
+
const isSeparatorRow = (l) => !!l && /^\s*\|?[\s:|-]+\|?\s*$/.test(l) && l.includes("-");
|
|
25994
|
+
const prevRaw = rowIdx > 0 ? lines[rowIdx - 1] : "";
|
|
25995
|
+
const prevRow = prevRaw.trim().startsWith("|") && !isSeparatorRow(prevRaw) ? prevRaw : "";
|
|
25996
|
+
const nextRaw = lines[rowIdx + 1] || "";
|
|
25997
|
+
const nextRow = nextRaw.trim().startsWith("|") && !isSeparatorRow(nextRaw) ? nextRaw : "";
|
|
25966
25998
|
const hierStart = lines.findIndex((l) => /Structural Hierarchy/i.test(l));
|
|
25967
25999
|
let hierarchyBlock = "";
|
|
25968
26000
|
if (hierStart >= 0) {
|
|
@@ -26203,7 +26235,22 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
26203
26235
|
const framework = await storage.readSotDocument(projectId, "CURRICULUM_FRAMEWORK.md") || "";
|
|
26204
26236
|
const styleGuide = await storage.readSotDocument(projectId, "CONTENT_STYLE_GUIDE.md") || "";
|
|
26205
26237
|
const refPack = await storage.readSotDocument(projectId, "REFERENCE_PACK.md") || "";
|
|
26238
|
+
let currentPlanHash;
|
|
26239
|
+
try {
|
|
26240
|
+
const planRaw0 = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
|
|
26241
|
+
if (planRaw0) currentPlanHash = JSON.parse(planRaw0).plan_hash;
|
|
26242
|
+
} catch {
|
|
26243
|
+
}
|
|
26206
26244
|
await storage.readSotDocument(projectId, "LEARNER_PROFILE.md") || "";
|
|
26245
|
+
const isStaleVsPlan = (content) => {
|
|
26246
|
+
if (!content || !currentPlanHash) return false;
|
|
26247
|
+
const m = content.match(/plan_hash:\s*"([0-9a-f]+)"/);
|
|
26248
|
+
return !!m && m[1] !== currentPlanHash;
|
|
26249
|
+
};
|
|
26250
|
+
const stampPlanHash = (content) => {
|
|
26251
|
+
if (!currentPlanHash || content.includes('plan_hash: "' + currentPlanHash + '"')) return content;
|
|
26252
|
+
return content + '\n---\nplan_hash: "' + currentPlanHash + '"\n- **Plan reference:** ' + currentPlanHash + "\n";
|
|
26253
|
+
};
|
|
26207
26254
|
const slcMarkdown = options.sectionLanguageContract || await storage.readSotDocument(projectId, "SECTION_LANGUAGE_CONTRACT.md") || "";
|
|
26208
26255
|
let targetLang = options.targetLanguage;
|
|
26209
26256
|
if (!targetLang) {
|
|
@@ -26353,6 +26400,7 @@ ${renderHorizonPromptBlock(horizon)}`;
|
|
|
26353
26400
|
} catch (hErr) {
|
|
26354
26401
|
console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
|
|
26355
26402
|
}
|
|
26403
|
+
let productSpecBlock = "";
|
|
26356
26404
|
const buildGroundTruthBlock = () => {
|
|
26357
26405
|
const parts = [];
|
|
26358
26406
|
if (expositionContext) {
|
|
@@ -26363,9 +26411,9 @@ ${expositionContext}`);
|
|
|
26363
26411
|
parts.push(`### REFERENCE PACK GROUND TRUTH
|
|
26364
26412
|
${effectiveRefPack}`);
|
|
26365
26413
|
}
|
|
26366
|
-
|
|
26367
|
-
if (
|
|
26368
|
-
parts.push(
|
|
26414
|
+
productSpecBlock = buildProductSpecBlock({ refPack, briefing: options.briefing, skipToolchainList: true });
|
|
26415
|
+
if (productSpecBlock) {
|
|
26416
|
+
parts.push(productSpecBlock);
|
|
26369
26417
|
}
|
|
26370
26418
|
return parts.length > 0 ? `
|
|
26371
26419
|
|
|
@@ -26382,10 +26430,17 @@ ${standardsContext}` : "";
|
|
|
26382
26430
|
const sessionSliceBlock = sessionSliceContext ? `
|
|
26383
26431
|
|
|
26384
26432
|
${sessionSliceContext}` : "";
|
|
26433
|
+
const platformGuardrail = buildDomainLexiconGuardrail(
|
|
26434
|
+
options.briefing?.techStack || options.briefing?.coreTechnology,
|
|
26435
|
+
options.briefing?.hardwarePlatform
|
|
26436
|
+
);
|
|
26437
|
+
const guardrailBlock = `
|
|
26438
|
+
|
|
26439
|
+
${platformGuardrail}`;
|
|
26385
26440
|
const assembleCommonContext = (sg) => `${baseContextPrefix}
|
|
26386
26441
|
|
|
26387
26442
|
[CONTENT STYLE GUIDE EXCERPT]:
|
|
26388
|
-
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}`;
|
|
26443
|
+
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
|
|
26389
26444
|
let commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
26390
26445
|
const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
|
|
26391
26446
|
const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
|
|
@@ -26414,7 +26469,10 @@ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBl
|
|
|
26414
26469
|
const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
|
|
26415
26470
|
const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
|
|
26416
26471
|
let lessonContent = existingLessonContent || "";
|
|
26417
|
-
if (artifactScope.includes("LESSON") && (!existingLessonContent || force)) {
|
|
26472
|
+
if (artifactScope.includes("LESSON") && (!existingLessonContent || force || isStaleVsPlan(existingLessonContent))) {
|
|
26473
|
+
if (existingLessonContent && !force && isStaleVsPlan(existingLessonContent)) {
|
|
26474
|
+
onProgress?.("@content", `[STALENESS] LESSON_${lessonCode} was generated under a different plan_hash \u2014 regenerating against the current plan.`);
|
|
26475
|
+
}
|
|
26418
26476
|
onProgress?.("@content", `[1/4] Authoring Master Lesson Plan (${pedagogyLabel}): \`LESSON_${lessonCode}.md\` (${lessonTitle})...`);
|
|
26419
26477
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26420
26478
|
const mediaDirective = options.mediaPolicy && options.mediaPolicy.maxGenImagesPerArtifact > 0 ? `
|
|
@@ -26485,7 +26543,7 @@ Fix ALL issues above and output the complete corrected document.` }],
|
|
|
26485
26543
|
} else {
|
|
26486
26544
|
lessonContent = lintReport.autoFixedContent || rawLesson;
|
|
26487
26545
|
}
|
|
26488
|
-
await storage.saveArtifact(projectId, lessonRelPath, lessonContent);
|
|
26546
|
+
await storage.saveArtifact(projectId, lessonRelPath, stampPlanHash(lessonContent));
|
|
26489
26547
|
producedArtifacts.push(`LESSON_${lessonCode}.md`);
|
|
26490
26548
|
}
|
|
26491
26549
|
const contentHash = lessonContent ? computeContentHash(lessonContent) : void 0;
|
|
@@ -26830,7 +26888,8 @@ ${lessonExcerpt}${symbolLedgerBlock}`;
|
|
|
26830
26888
|
});
|
|
26831
26889
|
};
|
|
26832
26890
|
const actRelPath = `_content/${unitCode}/ACT_${lessonCode}.md`;
|
|
26833
|
-
|
|
26891
|
+
const existingAct = await storage.readArtifact(projectId, actRelPath);
|
|
26892
|
+
if (artifactScope.includes("ACT") && (!existingAct || force || isStaleVsPlan(existingAct))) {
|
|
26834
26893
|
onProgress?.("@activity", `[2/4] Authoring Hands-on Lab: \`ACT_${lessonCode}.md\`...`);
|
|
26835
26894
|
const actDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26836
26895
|
const actPrompt = `You are @activity (Lead Hands-on STEM & Laboratory Exercise Designer).
|
|
@@ -26890,7 +26949,7 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
|
26890
26949
|
rawError: rawAct || "Empty AI response"
|
|
26891
26950
|
});
|
|
26892
26951
|
}
|
|
26893
|
-
await storage.saveArtifact(projectId, actRelPath, rawAct);
|
|
26952
|
+
await storage.saveArtifact(projectId, actRelPath, stampPlanHash(rawAct));
|
|
26894
26953
|
await storage.updateArtifactState(projectId, lessonCode, "ACT", {
|
|
26895
26954
|
state: "completed",
|
|
26896
26955
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26900,12 +26959,18 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
|
26900
26959
|
await judgeSat("ACT", rawAct);
|
|
26901
26960
|
}
|
|
26902
26961
|
const quizRelPath = `_content/${unitCode}/QUIZ_${lessonCode}.md`;
|
|
26903
|
-
|
|
26962
|
+
const existingQuiz = await storage.readArtifact(projectId, quizRelPath);
|
|
26963
|
+
if (artifactScope.includes("QUIZ") && (!existingQuiz || force || isStaleVsPlan(existingQuiz))) {
|
|
26904
26964
|
onProgress?.("@assessor", `[3/4] Authoring Assessment Bank: \`QUIZ_${lessonCode}.md\`...`);
|
|
26905
26965
|
const quizDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26906
26966
|
const quizPrompt = `You are @assessor (Lead Psychometrician & Competency Assessment Specialist).
|
|
26907
26967
|
Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, author the scenario-based diagnostic question bank: \`QUIZ_${lessonCode}.md\`.
|
|
26908
26968
|
|
|
26969
|
+
SCOPE & PACING (prompt audit S-6):
|
|
26970
|
+
- This is a FORMATIVE CHECKPOINT for ONE 90-minute session, NOT an exam bank.
|
|
26971
|
+
- Size the bank to fit the session's Wrap-up (~10 minutes of answering): the MCQ bank, code-tracing question, and rubric together must be completable by a student in that time.
|
|
26972
|
+
- Question ONLY concepts inside this session's scope (see SESSION SLICE + ALLOWED DESIGN SPACE in the context).
|
|
26973
|
+
|
|
26909
26974
|
Mandatory Format Requirements:
|
|
26910
26975
|
1. EXACT YAML Frontmatter \u2014 copy verbatim, then fill in the blanks:
|
|
26911
26976
|
---
|
|
@@ -26954,7 +27019,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26954
27019
|
rawError: rawQuiz || "Empty AI response"
|
|
26955
27020
|
});
|
|
26956
27021
|
}
|
|
26957
|
-
await storage.saveArtifact(projectId, quizRelPath, rawQuiz);
|
|
27022
|
+
await storage.saveArtifact(projectId, quizRelPath, stampPlanHash(rawQuiz));
|
|
26958
27023
|
await storage.updateArtifactState(projectId, lessonCode, "QUIZ", {
|
|
26959
27024
|
state: "completed",
|
|
26960
27025
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26964,7 +27029,8 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26964
27029
|
await judgeSat("QUIZ", rawQuiz);
|
|
26965
27030
|
}
|
|
26966
27031
|
const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
|
|
26967
|
-
|
|
27032
|
+
const existingSlide = await storage.readArtifact(projectId, slideRelPath);
|
|
27033
|
+
if (artifactScope.includes("SLIDE") && (!existingSlide || force || isStaleVsPlan(existingSlide))) {
|
|
26968
27034
|
onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
|
|
26969
27035
|
const isHtmlEngine = options.slidesEngine !== "marp";
|
|
26970
27036
|
if (isHtmlEngine) {
|
|
@@ -26989,6 +27055,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26989
27055
|
languageDirective,
|
|
26990
27056
|
headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
|
|
26991
27057
|
groundContext: slideGroundContext,
|
|
27058
|
+
productSpecBlock: productSpecBlock || void 0,
|
|
26992
27059
|
satelliteContext,
|
|
26993
27060
|
runnerOptions,
|
|
26994
27061
|
onProgress: (agent, msg, meta) => {
|
|
@@ -26998,7 +27065,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26998
27065
|
let deckJson = workflowResult.deckJson;
|
|
26999
27066
|
const markdownWrapper = workflowResult.markdownWrapper;
|
|
27000
27067
|
let validation = presentationKitAi?.validateHtmlSlideDeck ? presentationKitAi.validateHtmlSlideDeck(deckJson, true) : deckJson && Array.isArray(deckJson.slides) ? { valid: true, score: 95, issues: [], slideCount: deckJson.slides.length} : { valid: false, score: 0, issues: [{ message: "Malformed JSON slide deck" }]};
|
|
27001
|
-
await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
|
|
27068
|
+
await storage.saveArtifact(projectId, slideRelPath, stampPlanHash(markdownWrapper));
|
|
27002
27069
|
producedArtifacts.push(`SLIDE_${lessonCode}.md`);
|
|
27003
27070
|
if (deckJson) {
|
|
27004
27071
|
const deckJsonStr = JSON.stringify(deckJson, null, 2);
|
|
@@ -27089,7 +27156,7 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
27089
27156
|
rawError: rawSlide || "Empty AI response"
|
|
27090
27157
|
});
|
|
27091
27158
|
}
|
|
27092
|
-
await storage.saveArtifact(projectId, slideRelPath, rawSlide);
|
|
27159
|
+
await storage.saveArtifact(projectId, slideRelPath, stampPlanHash(rawSlide));
|
|
27093
27160
|
await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
|
|
27094
27161
|
state: "completed",
|
|
27095
27162
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27100,7 +27167,8 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
27100
27167
|
}
|
|
27101
27168
|
}
|
|
27102
27169
|
const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
|
|
27103
|
-
|
|
27170
|
+
const existingGuide = await storage.readArtifact(projectId, guideRelPath);
|
|
27171
|
+
if (artifactScope.includes("GUIDE") && (!existingGuide || force || isStaleVsPlan(existingGuide))) {
|
|
27104
27172
|
onProgress?.("@content", `Authoring Teacher Guide: \`GUIDE_${lessonCode}.md\`...`, { artifactType: "GUIDE" });
|
|
27105
27173
|
const guideDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27106
27174
|
const guidePrompt = `You are @content (Senior Curriculum Developer & Teacher Facilitation Specialist).
|
|
@@ -27140,7 +27208,7 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
|
27140
27208
|
}
|
|
27141
27209
|
);
|
|
27142
27210
|
if (rawGuide && !rawGuide.startsWith("\u26A0\uFE0F") && rawGuide.length >= 200) {
|
|
27143
|
-
await storage.saveArtifact(projectId, guideRelPath, rawGuide);
|
|
27211
|
+
await storage.saveArtifact(projectId, guideRelPath, stampPlanHash(rawGuide));
|
|
27144
27212
|
await storage.updateArtifactState(projectId, lessonCode, "GUIDE", {
|
|
27145
27213
|
state: "completed",
|
|
27146
27214
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27151,7 +27219,8 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
|
27151
27219
|
}
|
|
27152
27220
|
}
|
|
27153
27221
|
const handoutRelPath = `_content/${unitCode}/HANDOUT_${lessonCode}.md`;
|
|
27154
|
-
|
|
27222
|
+
const existingHandout = await storage.readArtifact(projectId, handoutRelPath);
|
|
27223
|
+
if (artifactScope.includes("HANDOUT") && (!existingHandout || force || isStaleVsPlan(existingHandout))) {
|
|
27155
27224
|
onProgress?.("@content", `Authoring Student Handout: \`HANDOUT_${lessonCode}.md\`...`, { artifactType: "HANDOUT" });
|
|
27156
27225
|
const handoutDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27157
27226
|
const handoutPrompt = `You are @content (Educational Content Specialist).
|
|
@@ -27191,7 +27260,7 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
|
27191
27260
|
}
|
|
27192
27261
|
);
|
|
27193
27262
|
if (rawHandout && !rawHandout.startsWith("\u26A0\uFE0F") && rawHandout.length >= 200) {
|
|
27194
|
-
await storage.saveArtifact(projectId, handoutRelPath, rawHandout);
|
|
27263
|
+
await storage.saveArtifact(projectId, handoutRelPath, stampPlanHash(rawHandout));
|
|
27195
27264
|
await storage.updateArtifactState(projectId, lessonCode, "HANDOUT", {
|
|
27196
27265
|
state: "completed",
|
|
27197
27266
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27202,7 +27271,8 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
|
27202
27271
|
}
|
|
27203
27272
|
}
|
|
27204
27273
|
const wksRelPath = `_content/${unitCode}/WKS_${lessonCode}.md`;
|
|
27205
|
-
|
|
27274
|
+
const existingWks = await storage.readArtifact(projectId, wksRelPath);
|
|
27275
|
+
if (artifactScope.includes("WKS") && (!existingWks || force || isStaleVsPlan(existingWks))) {
|
|
27206
27276
|
onProgress?.("@content", `Authoring Student Worksheet: \`WKS_${lessonCode}.md\`...`, { artifactType: "WKS" });
|
|
27207
27277
|
const wksDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27208
27278
|
const wksPrompt = `You are @content (Instructional Activity & Worksheet Designer).
|
|
@@ -27246,7 +27316,7 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
|
27246
27316
|
}
|
|
27247
27317
|
);
|
|
27248
27318
|
if (rawWks && !rawWks.startsWith("\u26A0\uFE0F") && rawWks.length >= 200) {
|
|
27249
|
-
await storage.saveArtifact(projectId, wksRelPath, rawWks);
|
|
27319
|
+
await storage.saveArtifact(projectId, wksRelPath, stampPlanHash(rawWks));
|
|
27250
27320
|
await storage.updateArtifactState(projectId, lessonCode, "WKS", {
|
|
27251
27321
|
state: "completed",
|
|
27252
27322
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27257,7 +27327,8 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
|
27257
27327
|
}
|
|
27258
27328
|
}
|
|
27259
27329
|
const codeRelPath = `_content/${unitCode}/CODE_${lessonCode}.md`;
|
|
27260
|
-
|
|
27330
|
+
const existingCode = await storage.readArtifact(projectId, codeRelPath);
|
|
27331
|
+
if ((artifactScope.includes("CODE") || artifactScope.includes("CODE_LAB")) && (!existingCode || force || isStaleVsPlan(existingCode))) {
|
|
27261
27332
|
onProgress?.("@activity", `Authoring Executable Code Lab: \`CODE_${lessonCode}.md\`...`, { artifactType: "CODE" });
|
|
27262
27333
|
const codeDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27263
27334
|
const codePrompt = `You are @activity & @tech_sme (Lead Software Engineer & Technical SME).
|
|
@@ -27305,7 +27376,7 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
27305
27376
|
}
|
|
27306
27377
|
);
|
|
27307
27378
|
if (rawCode && !rawCode.startsWith("\u26A0\uFE0F") && rawCode.length >= 200) {
|
|
27308
|
-
await storage.saveArtifact(projectId, codeRelPath, rawCode);
|
|
27379
|
+
await storage.saveArtifact(projectId, codeRelPath, stampPlanHash(rawCode));
|
|
27309
27380
|
await storage.updateArtifactState(projectId, lessonCode, "CODE", {
|
|
27310
27381
|
state: "completed",
|
|
27311
27382
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27321,7 +27392,8 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
27321
27392
|
}
|
|
27322
27393
|
}
|
|
27323
27394
|
const extRelPath = `_content/${unitCode}/EXT_${lessonCode}.md`;
|
|
27324
|
-
|
|
27395
|
+
const existingExt = await storage.readArtifact(projectId, extRelPath);
|
|
27396
|
+
if (artifactScope.includes("EXT") && (!existingExt || force || isStaleVsPlan(existingExt))) {
|
|
27325
27397
|
onProgress?.("@activity", `Authoring Advanced Extension Challenge: \`EXT_${lessonCode}.md\`...`, { artifactType: "EXT" });
|
|
27326
27398
|
const extDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27327
27399
|
const isVi = targetLang === "vi";
|
|
@@ -27391,7 +27463,7 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
|
|
|
27391
27463
|
}
|
|
27392
27464
|
);
|
|
27393
27465
|
if (rawExt && !rawExt.startsWith("\u26A0\uFE0F") && rawExt.length >= 200) {
|
|
27394
|
-
await storage.saveArtifact(projectId, extRelPath, rawExt);
|
|
27466
|
+
await storage.saveArtifact(projectId, extRelPath, stampPlanHash(rawExt));
|
|
27395
27467
|
await storage.updateArtifactState(projectId, lessonCode, "EXT", {
|
|
27396
27468
|
state: "completed",
|
|
27397
27469
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|