@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.d.cts
CHANGED
|
@@ -1076,6 +1076,10 @@ interface SlideProductionWorkflowOptions {
|
|
|
1076
1076
|
* already injected into user prompts via parseLessonFlow (no duplication).
|
|
1077
1077
|
*/
|
|
1078
1078
|
groundContext?: string;
|
|
1079
|
+
/** Canonical PRODUCT_SPEC block (prompt audit S-1): injected into the
|
|
1080
|
+
* blueprint prompt so deck-wide content decisions see binding platform/
|
|
1081
|
+
* version facts even though the blueprint call carries no groundContext. */
|
|
1082
|
+
productSpecBlock?: string;
|
|
1079
1083
|
satelliteContext?: any;
|
|
1080
1084
|
/** Vercel AI SDK model resolution options (provider/model/apiKey). */
|
|
1081
1085
|
modelOptions?: ModelResolutionOptions;
|
package/dist/index.d.ts
CHANGED
|
@@ -1076,6 +1076,10 @@ interface SlideProductionWorkflowOptions {
|
|
|
1076
1076
|
* already injected into user prompts via parseLessonFlow (no duplication).
|
|
1077
1077
|
*/
|
|
1078
1078
|
groundContext?: string;
|
|
1079
|
+
/** Canonical PRODUCT_SPEC block (prompt audit S-1): injected into the
|
|
1080
|
+
* blueprint prompt so deck-wide content decisions see binding platform/
|
|
1081
|
+
* version facts even though the blueprint call carries no groundContext. */
|
|
1082
|
+
productSpecBlock?: string;
|
|
1079
1083
|
satelliteContext?: any;
|
|
1080
1084
|
/** Vercel AI SDK model resolution options (provider/model/apiKey). */
|
|
1081
1085
|
modelOptions?: ModelResolutionOptions;
|
package/dist/index.mjs
CHANGED
|
@@ -1324,6 +1324,10 @@ function parseLessonFlow(lessonMarkdown) {
|
|
|
1324
1324
|
}
|
|
1325
1325
|
const durMatch = lessonMarkdown.match(/Estimated Duration:\s*([^\n]+)/i);
|
|
1326
1326
|
if (durMatch) result.estimatedDuration = durMatch[1].trim();
|
|
1327
|
+
if (!result.lessonTitle) {
|
|
1328
|
+
const h1 = lessonMarkdown.match(/^#\s+(?:LESSON[^:]*:\s*)?(.+)$/m);
|
|
1329
|
+
if (h1 && h1[1]) result.lessonTitle = h1[1].replace(/\*+/g, "").trim();
|
|
1330
|
+
}
|
|
1327
1331
|
const contractMatch = lessonMarkdown.match(/SLIDE must visualize:?\s*([^\n]+)/i);
|
|
1328
1332
|
if (contractMatch) result.slideContract = contractMatch[1].trim();
|
|
1329
1333
|
const actSeqSectionMatch = lessonMarkdown.match(/###\s*(?:\d+\.\s*)?Activity Sequence[\s\S]*?(?=(?:###|\n##\s+|$))/i);
|
|
@@ -1405,7 +1409,7 @@ var init_lessonFlowParser = __esm({
|
|
|
1405
1409
|
|
|
1406
1410
|
// src/ai/prompts/slideBlueprintPrompt.ts
|
|
1407
1411
|
function buildSlideBlueprintPrompt(params) {
|
|
1408
|
-
const { lessonFlow, targetSlideCount, stylePresetName = "Blue Professional" } = params;
|
|
1412
|
+
const { lessonFlow, targetSlideCount, stylePresetName = "Blue Professional", productSpecBlock } = params;
|
|
1409
1413
|
const activitiesSummary = lessonFlow.activities.map(
|
|
1410
1414
|
(a) => `Seq ${a.seq} [${a.phase}]: ${a.purpose} (Teacher: ${a.teacherMove} | Student: ${a.studentAction} | Time: ${a.time})`
|
|
1411
1415
|
).join("\n");
|
|
@@ -1427,7 +1431,9 @@ ${countGuidance}
|
|
|
1427
1431
|
- Target Duration: ${lessonFlow.estimatedDuration}
|
|
1428
1432
|
- Active Visual Style: "${stylePresetName}"
|
|
1429
1433
|
${lessonFlow.slideContract ? `- Slide Artifact Contract: "${lessonFlow.slideContract}"` : ""}
|
|
1430
|
-
|
|
1434
|
+
${productSpecBlock ? `
|
|
1435
|
+
${productSpecBlock}
|
|
1436
|
+
` : ""}
|
|
1431
1437
|
### \u{1F5FA}\uFE0F CANONICAL ACTIVITY SEQUENCE:
|
|
1432
1438
|
${activitiesSummary || "Standard phased sequence"}
|
|
1433
1439
|
|
|
@@ -1501,7 +1507,7 @@ function buildSlideBatchPrompt(params) {
|
|
|
1501
1507
|
skillPrompt,
|
|
1502
1508
|
language = "Vietnamese",
|
|
1503
1509
|
languageDirective = "",
|
|
1504
|
-
|
|
1510
|
+
// deprecated — see interface doc
|
|
1505
1511
|
groundContext = ""
|
|
1506
1512
|
} = params;
|
|
1507
1513
|
const slidesSpec = clusterSlides.map((s) => `
|
|
@@ -1522,7 +1528,6 @@ ${groundContext}
|
|
|
1522
1528
|
---` : ""}
|
|
1523
1529
|
### OPERATIONAL GROUND RULES FOR CHUNK GENERATION:
|
|
1524
1530
|
${languageDirective}
|
|
1525
|
-
${headingDirective}
|
|
1526
1531
|
1. **FOCUS ON CLUSTER ${clusterId}: "${clusterTitle}"**:
|
|
1527
1532
|
Generate EXACTLY the ${clusterSlides.length} slides requested in the user prompt.
|
|
1528
1533
|
2. **ZERO ABBREVIATION / NO "// TODO"**:
|
|
@@ -1713,7 +1718,7 @@ async function inferStructured(schema, systemPrompt, userPrompt, options, label)
|
|
|
1713
1718
|
const messages = [
|
|
1714
1719
|
{ role: "user", content: [systemPrompt, userPrompt].filter(Boolean).join("\n\n") }
|
|
1715
1720
|
];
|
|
1716
|
-
const raw = await runCurriculumAIInference(messages,
|
|
1721
|
+
const raw = await runCurriculumAIInference(messages, "", options.runnerOptions, (token, type) => {
|
|
1717
1722
|
if (type === "usage") options.onProgress?.("@illustrator", token, { type: "usage" });
|
|
1718
1723
|
});
|
|
1719
1724
|
const fenceMatch = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
@@ -1899,7 +1904,8 @@ async function executeSlideProductionWorkflow(options) {
|
|
|
1899
1904
|
const blueprintPrompt = buildSlideBlueprintPrompt({
|
|
1900
1905
|
lessonFlow,
|
|
1901
1906
|
targetSlideCount,
|
|
1902
|
-
stylePresetName: stylePreset?.name || "Blue Professional"
|
|
1907
|
+
stylePresetName: stylePreset?.name || "Blue Professional",
|
|
1908
|
+
productSpecBlock: options.productSpecBlock
|
|
1903
1909
|
});
|
|
1904
1910
|
let blueprintItems;
|
|
1905
1911
|
let allGeneratedSlides;
|
|
@@ -1982,7 +1988,6 @@ ${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
|
|
|
1982
1988
|
skillPrompt,
|
|
1983
1989
|
language,
|
|
1984
1990
|
languageDirective,
|
|
1985
|
-
headingDirective,
|
|
1986
1991
|
groundContext: options.groundContext
|
|
1987
1992
|
});
|
|
1988
1993
|
try {
|
|
@@ -11707,19 +11712,30 @@ function extractToolchainFacts(refPack) {
|
|
|
11707
11712
|
const factLines = [];
|
|
11708
11713
|
for (const rawLine of excerpt.split("\n")) {
|
|
11709
11714
|
const line = rawLine.trim();
|
|
11710
|
-
if (!line || line.startsWith("#")) continue;
|
|
11711
|
-
if (
|
|
11712
|
-
|
|
11713
|
-
}
|
|
11715
|
+
if (!line || line.startsWith("#") || line.startsWith("<!--")) continue;
|
|
11716
|
+
if (!/\d{1,2}(?:\.\d{1,2})?/.test(line) && !/Xcode|Swift|macOS|iOS|SDK|Node|Python|JDK|\.NET/i.test(line)) continue;
|
|
11717
|
+
factLines.push(line.replace(/^[*\-•]\s*/, "- "));
|
|
11714
11718
|
if (factLines.length >= 8) break;
|
|
11715
11719
|
}
|
|
11716
11720
|
return factLines;
|
|
11717
11721
|
}
|
|
11722
|
+
function derivePrimaryTechnology(facts, briefing) {
|
|
11723
|
+
const explicit = (briefing?.coreTechnology || briefing?.techStack || "").trim();
|
|
11724
|
+
if (explicit && explicit.length <= 80) return explicit;
|
|
11725
|
+
const found = [];
|
|
11726
|
+
const CANON = ["Swift", "SwiftUI", "macOS", "iOS", "Xcode", "SwiftData", "Node.js", "TypeScript", "Python", "React"];
|
|
11727
|
+
for (const name of CANON) {
|
|
11728
|
+
if (facts.some((f) => f.includes(name)) && !found.includes(name)) found.push(name);
|
|
11729
|
+
}
|
|
11730
|
+
if (found.length > 0) return found.slice(0, 4).join(" + ");
|
|
11731
|
+
const topic = (briefing?.topic || "").trim();
|
|
11732
|
+
return topic && topic.length <= 60 ? topic : "";
|
|
11733
|
+
}
|
|
11718
11734
|
function buildProductSpecBlock(input) {
|
|
11719
11735
|
const b = input.briefing || {};
|
|
11720
|
-
const platform = (input.overrides?.primaryLanguage || b.coreTechnology || b.techStack || b.topic || "").trim();
|
|
11721
|
-
const hardware = (b.hardwarePlatform || b.studentEquipment || b.classDynamic || "").trim();
|
|
11722
11736
|
const toolchainFacts = input.refPack ? extractToolchainFacts(input.refPack) : [];
|
|
11737
|
+
const platform = (input.overrides?.primaryLanguage || derivePrimaryTechnology(toolchainFacts, b) || "").trim();
|
|
11738
|
+
const hardware = (b.hardwarePlatform || b.studentEquipment || b.classDynamic || "").trim();
|
|
11723
11739
|
const lines = [];
|
|
11724
11740
|
lines.push("[PRODUCT SPEC \u2014 CANONICAL, BINDING FOR EVERY ARTIFACT OF THIS PROJECT]:");
|
|
11725
11741
|
lines.push("All decisions below are PROJECT-WIDE. Every artifact (LESSON, satellites, KX)");
|
|
@@ -11735,7 +11751,7 @@ function buildProductSpecBlock(input) {
|
|
|
11735
11751
|
if (input.overrides?.deliverableTemplate) {
|
|
11736
11752
|
lines.push(`- Deliverable template: ${input.overrides.deliverableTemplate}`);
|
|
11737
11753
|
}
|
|
11738
|
-
if (toolchainFacts.length > 0) {
|
|
11754
|
+
if (toolchainFacts.length > 0 && !input.skipToolchainList) {
|
|
11739
11755
|
lines.push("- Toolchain & versions (ground truth):");
|
|
11740
11756
|
for (const f of toolchainFacts) lines.push(` ${f}`);
|
|
11741
11757
|
}
|
|
@@ -11863,7 +11879,9 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11863
11879
|
const productSpecBlock = buildProductSpecBlock({
|
|
11864
11880
|
refPack,
|
|
11865
11881
|
briefing: techStack || hardwarePlatform ? { techStack, hardwarePlatform } : void 0,
|
|
11866
|
-
overrides: options.productSpec
|
|
11882
|
+
overrides: options.productSpec,
|
|
11883
|
+
skipToolchainList: true
|
|
11884
|
+
// same RefPack excerpt already in [GROUND TRUTH]
|
|
11867
11885
|
});
|
|
11868
11886
|
const originalUserPrompt = buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown, refPack) + (productSpecBlock ? "\n\n" + productSpecBlock : "");
|
|
11869
11887
|
const content = (await llmFn(
|
|
@@ -11963,17 +11981,28 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
11963
11981
|
const lpRaw = await storage.readSotDocument(projectId, "LEARNER_PROFILE.md");
|
|
11964
11982
|
if (lpRaw) {
|
|
11965
11983
|
const hwPatterns = [
|
|
11984
|
+
// "Cơ sở phần cứng (Hardware Ecosystem): Học sinh thực hành trên máy..."
|
|
11985
|
+
// — the audit: the old pattern captured the parenthetical label
|
|
11986
|
+
// "(Hardware Ecosystem):" itself. Capture AFTER the closing paren + colon.
|
|
11987
|
+
/\([^)]*Ecosystem[^)]*\)\s*:\s*([^\n\r]+)/i,
|
|
11966
11988
|
/(?:Student Equipment|Hardware|Platform|Thiết bị|Thiết bị học tập)\s*(?:\(|:|\*)*\s*([^\n\r]+?)(?:\s*\*\*|[.).]?\s*$)/i,
|
|
11967
11989
|
/(?:máy|device|machine)\s+([^,.;\n]{4,60}(?:M\d|Intel|PC|computer|mini)[^,.;\n]{0,30})/i
|
|
11968
11990
|
];
|
|
11969
11991
|
for (const p of hwPatterns) {
|
|
11970
11992
|
const m = lpRaw.match(p);
|
|
11971
|
-
if (m && !hw) {
|
|
11972
|
-
hw = m[1].trim();
|
|
11993
|
+
if (m && m[1] && !hw) {
|
|
11994
|
+
hw = m[1].trim().replace(/\*+/g, "").slice(0, 300);
|
|
11973
11995
|
break;
|
|
11974
11996
|
}
|
|
11975
11997
|
}
|
|
11976
11998
|
}
|
|
11999
|
+
if (!hw) {
|
|
12000
|
+
const rpRaw = await storage.readSotDocument(projectId, "REFERENCE_PACK.md");
|
|
12001
|
+
if (rpRaw) {
|
|
12002
|
+
const rp = rpRaw.match(/(?:School Lab|Thiết bị lớp học)[^\n]*\*\*([^*]+(?:M\d|Mac)[^*]*)\*\*/i);
|
|
12003
|
+
if (rp && rp[1]) hw = rp[1].trim();
|
|
12004
|
+
}
|
|
12005
|
+
}
|
|
11977
12006
|
} catch {
|
|
11978
12007
|
}
|
|
11979
12008
|
}
|
|
@@ -12710,11 +12739,11 @@ function buildSessionSliceContext(plan, lessonCode) {
|
|
|
12710
12739
|
lines.push("- Time split: knowledge " + s.knowledge_minutes + "m / practice " + s.practice_minutes + "m / overhead " + s.overhead_minutes + "m");
|
|
12711
12740
|
lines.push("- Depth assignments: " + s.depth_assignments.map((d) => d.node_id + "=" + d.depth.toUpperCase() + "(" + d.source + ")").join("; "));
|
|
12712
12741
|
if (s.prerequisite_decisions.length > 0) {
|
|
12713
|
-
lines.push("- Prerequisite decisions [
|
|
12742
|
+
lines.push("- Prerequisite decisions [" + s.prerequisite_decisions.length + "]:");
|
|
12714
12743
|
for (const d of s.prerequisite_decisions) lines.push(" * " + d.node_id + ": " + d.decision + " \u2014 " + d.reason);
|
|
12715
12744
|
}
|
|
12716
12745
|
if (s.scaffold_decisions.length > 0) {
|
|
12717
|
-
lines.push("- Scaffold decisions [
|
|
12746
|
+
lines.push("- Scaffold decisions [" + s.scaffold_decisions.length + "]:");
|
|
12718
12747
|
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);
|
|
12719
12748
|
}
|
|
12720
12749
|
lines.push("- Exit evidence: " + s.exit_evidence.join("; "));
|
|
@@ -25949,8 +25978,11 @@ function buildFrameworkExcerptForLesson(framework, lessonId) {
|
|
|
25949
25978
|
});
|
|
25950
25979
|
const rowIdx = scopeRows[0] ?? -1;
|
|
25951
25980
|
const currentRow = rowIdx >= 0 ? lines[rowIdx] : "";
|
|
25952
|
-
const
|
|
25953
|
-
const
|
|
25981
|
+
const isSeparatorRow = (l) => !!l && /^\s*\|?[\s:|-]+\|?\s*$/.test(l) && l.includes("-");
|
|
25982
|
+
const prevRaw = rowIdx > 0 ? lines[rowIdx - 1] : "";
|
|
25983
|
+
const prevRow = prevRaw.trim().startsWith("|") && !isSeparatorRow(prevRaw) ? prevRaw : "";
|
|
25984
|
+
const nextRaw = lines[rowIdx + 1] || "";
|
|
25985
|
+
const nextRow = nextRaw.trim().startsWith("|") && !isSeparatorRow(nextRaw) ? nextRaw : "";
|
|
25954
25986
|
const hierStart = lines.findIndex((l) => /Structural Hierarchy/i.test(l));
|
|
25955
25987
|
let hierarchyBlock = "";
|
|
25956
25988
|
if (hierStart >= 0) {
|
|
@@ -26191,7 +26223,22 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
26191
26223
|
const framework = await storage.readSotDocument(projectId, "CURRICULUM_FRAMEWORK.md") || "";
|
|
26192
26224
|
const styleGuide = await storage.readSotDocument(projectId, "CONTENT_STYLE_GUIDE.md") || "";
|
|
26193
26225
|
const refPack = await storage.readSotDocument(projectId, "REFERENCE_PACK.md") || "";
|
|
26226
|
+
let currentPlanHash;
|
|
26227
|
+
try {
|
|
26228
|
+
const planRaw0 = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
|
|
26229
|
+
if (planRaw0) currentPlanHash = JSON.parse(planRaw0).plan_hash;
|
|
26230
|
+
} catch {
|
|
26231
|
+
}
|
|
26194
26232
|
await storage.readSotDocument(projectId, "LEARNER_PROFILE.md") || "";
|
|
26233
|
+
const isStaleVsPlan = (content) => {
|
|
26234
|
+
if (!content || !currentPlanHash) return false;
|
|
26235
|
+
const m = content.match(/plan_hash:\s*"([0-9a-f]+)"/);
|
|
26236
|
+
return !!m && m[1] !== currentPlanHash;
|
|
26237
|
+
};
|
|
26238
|
+
const stampPlanHash = (content) => {
|
|
26239
|
+
if (!currentPlanHash || content.includes('plan_hash: "' + currentPlanHash + '"')) return content;
|
|
26240
|
+
return content + '\n---\nplan_hash: "' + currentPlanHash + '"\n- **Plan reference:** ' + currentPlanHash + "\n";
|
|
26241
|
+
};
|
|
26195
26242
|
const slcMarkdown = options.sectionLanguageContract || await storage.readSotDocument(projectId, "SECTION_LANGUAGE_CONTRACT.md") || "";
|
|
26196
26243
|
let targetLang = options.targetLanguage;
|
|
26197
26244
|
if (!targetLang) {
|
|
@@ -26341,6 +26388,7 @@ ${renderHorizonPromptBlock(horizon)}`;
|
|
|
26341
26388
|
} catch (hErr) {
|
|
26342
26389
|
console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
|
|
26343
26390
|
}
|
|
26391
|
+
let productSpecBlock = "";
|
|
26344
26392
|
const buildGroundTruthBlock = () => {
|
|
26345
26393
|
const parts = [];
|
|
26346
26394
|
if (expositionContext) {
|
|
@@ -26351,9 +26399,9 @@ ${expositionContext}`);
|
|
|
26351
26399
|
parts.push(`### REFERENCE PACK GROUND TRUTH
|
|
26352
26400
|
${effectiveRefPack}`);
|
|
26353
26401
|
}
|
|
26354
|
-
|
|
26355
|
-
if (
|
|
26356
|
-
parts.push(
|
|
26402
|
+
productSpecBlock = buildProductSpecBlock({ refPack, briefing: options.briefing, skipToolchainList: true });
|
|
26403
|
+
if (productSpecBlock) {
|
|
26404
|
+
parts.push(productSpecBlock);
|
|
26357
26405
|
}
|
|
26358
26406
|
return parts.length > 0 ? `
|
|
26359
26407
|
|
|
@@ -26370,10 +26418,17 @@ ${standardsContext}` : "";
|
|
|
26370
26418
|
const sessionSliceBlock = sessionSliceContext ? `
|
|
26371
26419
|
|
|
26372
26420
|
${sessionSliceContext}` : "";
|
|
26421
|
+
const platformGuardrail = buildDomainLexiconGuardrail(
|
|
26422
|
+
options.briefing?.techStack || options.briefing?.coreTechnology,
|
|
26423
|
+
options.briefing?.hardwarePlatform
|
|
26424
|
+
);
|
|
26425
|
+
const guardrailBlock = `
|
|
26426
|
+
|
|
26427
|
+
${platformGuardrail}`;
|
|
26373
26428
|
const assembleCommonContext = (sg) => `${baseContextPrefix}
|
|
26374
26429
|
|
|
26375
26430
|
[CONTENT STYLE GUIDE EXCERPT]:
|
|
26376
|
-
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}`;
|
|
26431
|
+
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}${guardrailBlock}`;
|
|
26377
26432
|
let commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
26378
26433
|
const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
|
|
26379
26434
|
const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
|
|
@@ -26402,7 +26457,10 @@ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBl
|
|
|
26402
26457
|
const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
|
|
26403
26458
|
const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
|
|
26404
26459
|
let lessonContent = existingLessonContent || "";
|
|
26405
|
-
if (artifactScope.includes("LESSON") && (!existingLessonContent || force)) {
|
|
26460
|
+
if (artifactScope.includes("LESSON") && (!existingLessonContent || force || isStaleVsPlan(existingLessonContent))) {
|
|
26461
|
+
if (existingLessonContent && !force && isStaleVsPlan(existingLessonContent)) {
|
|
26462
|
+
onProgress?.("@content", `[STALENESS] LESSON_${lessonCode} was generated under a different plan_hash \u2014 regenerating against the current plan.`);
|
|
26463
|
+
}
|
|
26406
26464
|
onProgress?.("@content", `[1/4] Authoring Master Lesson Plan (${pedagogyLabel}): \`LESSON_${lessonCode}.md\` (${lessonTitle})...`);
|
|
26407
26465
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26408
26466
|
const mediaDirective = options.mediaPolicy && options.mediaPolicy.maxGenImagesPerArtifact > 0 ? `
|
|
@@ -26473,7 +26531,7 @@ Fix ALL issues above and output the complete corrected document.` }],
|
|
|
26473
26531
|
} else {
|
|
26474
26532
|
lessonContent = lintReport.autoFixedContent || rawLesson;
|
|
26475
26533
|
}
|
|
26476
|
-
await storage.saveArtifact(projectId, lessonRelPath, lessonContent);
|
|
26534
|
+
await storage.saveArtifact(projectId, lessonRelPath, stampPlanHash(lessonContent));
|
|
26477
26535
|
producedArtifacts.push(`LESSON_${lessonCode}.md`);
|
|
26478
26536
|
}
|
|
26479
26537
|
const contentHash = lessonContent ? computeContentHash(lessonContent) : void 0;
|
|
@@ -26818,7 +26876,8 @@ ${lessonExcerpt}${symbolLedgerBlock}`;
|
|
|
26818
26876
|
});
|
|
26819
26877
|
};
|
|
26820
26878
|
const actRelPath = `_content/${unitCode}/ACT_${lessonCode}.md`;
|
|
26821
|
-
|
|
26879
|
+
const existingAct = await storage.readArtifact(projectId, actRelPath);
|
|
26880
|
+
if (artifactScope.includes("ACT") && (!existingAct || force || isStaleVsPlan(existingAct))) {
|
|
26822
26881
|
onProgress?.("@activity", `[2/4] Authoring Hands-on Lab: \`ACT_${lessonCode}.md\`...`);
|
|
26823
26882
|
const actDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26824
26883
|
const actPrompt = `You are @activity (Lead Hands-on STEM & Laboratory Exercise Designer).
|
|
@@ -26878,7 +26937,7 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
|
26878
26937
|
rawError: rawAct || "Empty AI response"
|
|
26879
26938
|
});
|
|
26880
26939
|
}
|
|
26881
|
-
await storage.saveArtifact(projectId, actRelPath, rawAct);
|
|
26940
|
+
await storage.saveArtifact(projectId, actRelPath, stampPlanHash(rawAct));
|
|
26882
26941
|
await storage.updateArtifactState(projectId, lessonCode, "ACT", {
|
|
26883
26942
|
state: "completed",
|
|
26884
26943
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26888,12 +26947,18 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
|
26888
26947
|
await judgeSat("ACT", rawAct);
|
|
26889
26948
|
}
|
|
26890
26949
|
const quizRelPath = `_content/${unitCode}/QUIZ_${lessonCode}.md`;
|
|
26891
|
-
|
|
26950
|
+
const existingQuiz = await storage.readArtifact(projectId, quizRelPath);
|
|
26951
|
+
if (artifactScope.includes("QUIZ") && (!existingQuiz || force || isStaleVsPlan(existingQuiz))) {
|
|
26892
26952
|
onProgress?.("@assessor", `[3/4] Authoring Assessment Bank: \`QUIZ_${lessonCode}.md\`...`);
|
|
26893
26953
|
const quizDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26894
26954
|
const quizPrompt = `You are @assessor (Lead Psychometrician & Competency Assessment Specialist).
|
|
26895
26955
|
Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, author the scenario-based diagnostic question bank: \`QUIZ_${lessonCode}.md\`.
|
|
26896
26956
|
|
|
26957
|
+
SCOPE & PACING (prompt audit S-6):
|
|
26958
|
+
- This is a FORMATIVE CHECKPOINT for ONE 90-minute session, NOT an exam bank.
|
|
26959
|
+
- 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.
|
|
26960
|
+
- Question ONLY concepts inside this session's scope (see SESSION SLICE + ALLOWED DESIGN SPACE in the context).
|
|
26961
|
+
|
|
26897
26962
|
Mandatory Format Requirements:
|
|
26898
26963
|
1. EXACT YAML Frontmatter \u2014 copy verbatim, then fill in the blanks:
|
|
26899
26964
|
---
|
|
@@ -26942,7 +27007,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26942
27007
|
rawError: rawQuiz || "Empty AI response"
|
|
26943
27008
|
});
|
|
26944
27009
|
}
|
|
26945
|
-
await storage.saveArtifact(projectId, quizRelPath, rawQuiz);
|
|
27010
|
+
await storage.saveArtifact(projectId, quizRelPath, stampPlanHash(rawQuiz));
|
|
26946
27011
|
await storage.updateArtifactState(projectId, lessonCode, "QUIZ", {
|
|
26947
27012
|
state: "completed",
|
|
26948
27013
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26952,7 +27017,8 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26952
27017
|
await judgeSat("QUIZ", rawQuiz);
|
|
26953
27018
|
}
|
|
26954
27019
|
const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
|
|
26955
|
-
|
|
27020
|
+
const existingSlide = await storage.readArtifact(projectId, slideRelPath);
|
|
27021
|
+
if (artifactScope.includes("SLIDE") && (!existingSlide || force || isStaleVsPlan(existingSlide))) {
|
|
26956
27022
|
onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
|
|
26957
27023
|
const isHtmlEngine = options.slidesEngine !== "marp";
|
|
26958
27024
|
if (isHtmlEngine) {
|
|
@@ -26977,6 +27043,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26977
27043
|
languageDirective,
|
|
26978
27044
|
headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
|
|
26979
27045
|
groundContext: slideGroundContext,
|
|
27046
|
+
productSpecBlock: productSpecBlock || void 0,
|
|
26980
27047
|
satelliteContext,
|
|
26981
27048
|
runnerOptions,
|
|
26982
27049
|
onProgress: (agent, msg, meta) => {
|
|
@@ -26986,7 +27053,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26986
27053
|
let deckJson = workflowResult.deckJson;
|
|
26987
27054
|
const markdownWrapper = workflowResult.markdownWrapper;
|
|
26988
27055
|
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" }]};
|
|
26989
|
-
await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
|
|
27056
|
+
await storage.saveArtifact(projectId, slideRelPath, stampPlanHash(markdownWrapper));
|
|
26990
27057
|
producedArtifacts.push(`SLIDE_${lessonCode}.md`);
|
|
26991
27058
|
if (deckJson) {
|
|
26992
27059
|
const deckJsonStr = JSON.stringify(deckJson, null, 2);
|
|
@@ -27077,7 +27144,7 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
27077
27144
|
rawError: rawSlide || "Empty AI response"
|
|
27078
27145
|
});
|
|
27079
27146
|
}
|
|
27080
|
-
await storage.saveArtifact(projectId, slideRelPath, rawSlide);
|
|
27147
|
+
await storage.saveArtifact(projectId, slideRelPath, stampPlanHash(rawSlide));
|
|
27081
27148
|
await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
|
|
27082
27149
|
state: "completed",
|
|
27083
27150
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27088,7 +27155,8 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
27088
27155
|
}
|
|
27089
27156
|
}
|
|
27090
27157
|
const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
|
|
27091
|
-
|
|
27158
|
+
const existingGuide = await storage.readArtifact(projectId, guideRelPath);
|
|
27159
|
+
if (artifactScope.includes("GUIDE") && (!existingGuide || force || isStaleVsPlan(existingGuide))) {
|
|
27092
27160
|
onProgress?.("@content", `Authoring Teacher Guide: \`GUIDE_${lessonCode}.md\`...`, { artifactType: "GUIDE" });
|
|
27093
27161
|
const guideDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27094
27162
|
const guidePrompt = `You are @content (Senior Curriculum Developer & Teacher Facilitation Specialist).
|
|
@@ -27128,7 +27196,7 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
|
27128
27196
|
}
|
|
27129
27197
|
);
|
|
27130
27198
|
if (rawGuide && !rawGuide.startsWith("\u26A0\uFE0F") && rawGuide.length >= 200) {
|
|
27131
|
-
await storage.saveArtifact(projectId, guideRelPath, rawGuide);
|
|
27199
|
+
await storage.saveArtifact(projectId, guideRelPath, stampPlanHash(rawGuide));
|
|
27132
27200
|
await storage.updateArtifactState(projectId, lessonCode, "GUIDE", {
|
|
27133
27201
|
state: "completed",
|
|
27134
27202
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27139,7 +27207,8 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
|
27139
27207
|
}
|
|
27140
27208
|
}
|
|
27141
27209
|
const handoutRelPath = `_content/${unitCode}/HANDOUT_${lessonCode}.md`;
|
|
27142
|
-
|
|
27210
|
+
const existingHandout = await storage.readArtifact(projectId, handoutRelPath);
|
|
27211
|
+
if (artifactScope.includes("HANDOUT") && (!existingHandout || force || isStaleVsPlan(existingHandout))) {
|
|
27143
27212
|
onProgress?.("@content", `Authoring Student Handout: \`HANDOUT_${lessonCode}.md\`...`, { artifactType: "HANDOUT" });
|
|
27144
27213
|
const handoutDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27145
27214
|
const handoutPrompt = `You are @content (Educational Content Specialist).
|
|
@@ -27179,7 +27248,7 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
|
27179
27248
|
}
|
|
27180
27249
|
);
|
|
27181
27250
|
if (rawHandout && !rawHandout.startsWith("\u26A0\uFE0F") && rawHandout.length >= 200) {
|
|
27182
|
-
await storage.saveArtifact(projectId, handoutRelPath, rawHandout);
|
|
27251
|
+
await storage.saveArtifact(projectId, handoutRelPath, stampPlanHash(rawHandout));
|
|
27183
27252
|
await storage.updateArtifactState(projectId, lessonCode, "HANDOUT", {
|
|
27184
27253
|
state: "completed",
|
|
27185
27254
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27190,7 +27259,8 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
|
27190
27259
|
}
|
|
27191
27260
|
}
|
|
27192
27261
|
const wksRelPath = `_content/${unitCode}/WKS_${lessonCode}.md`;
|
|
27193
|
-
|
|
27262
|
+
const existingWks = await storage.readArtifact(projectId, wksRelPath);
|
|
27263
|
+
if (artifactScope.includes("WKS") && (!existingWks || force || isStaleVsPlan(existingWks))) {
|
|
27194
27264
|
onProgress?.("@content", `Authoring Student Worksheet: \`WKS_${lessonCode}.md\`...`, { artifactType: "WKS" });
|
|
27195
27265
|
const wksDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27196
27266
|
const wksPrompt = `You are @content (Instructional Activity & Worksheet Designer).
|
|
@@ -27234,7 +27304,7 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
|
27234
27304
|
}
|
|
27235
27305
|
);
|
|
27236
27306
|
if (rawWks && !rawWks.startsWith("\u26A0\uFE0F") && rawWks.length >= 200) {
|
|
27237
|
-
await storage.saveArtifact(projectId, wksRelPath, rawWks);
|
|
27307
|
+
await storage.saveArtifact(projectId, wksRelPath, stampPlanHash(rawWks));
|
|
27238
27308
|
await storage.updateArtifactState(projectId, lessonCode, "WKS", {
|
|
27239
27309
|
state: "completed",
|
|
27240
27310
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27245,7 +27315,8 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
|
27245
27315
|
}
|
|
27246
27316
|
}
|
|
27247
27317
|
const codeRelPath = `_content/${unitCode}/CODE_${lessonCode}.md`;
|
|
27248
|
-
|
|
27318
|
+
const existingCode = await storage.readArtifact(projectId, codeRelPath);
|
|
27319
|
+
if ((artifactScope.includes("CODE") || artifactScope.includes("CODE_LAB")) && (!existingCode || force || isStaleVsPlan(existingCode))) {
|
|
27249
27320
|
onProgress?.("@activity", `Authoring Executable Code Lab: \`CODE_${lessonCode}.md\`...`, { artifactType: "CODE" });
|
|
27250
27321
|
const codeDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27251
27322
|
const codePrompt = `You are @activity & @tech_sme (Lead Software Engineer & Technical SME).
|
|
@@ -27293,7 +27364,7 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
27293
27364
|
}
|
|
27294
27365
|
);
|
|
27295
27366
|
if (rawCode && !rawCode.startsWith("\u26A0\uFE0F") && rawCode.length >= 200) {
|
|
27296
|
-
await storage.saveArtifact(projectId, codeRelPath, rawCode);
|
|
27367
|
+
await storage.saveArtifact(projectId, codeRelPath, stampPlanHash(rawCode));
|
|
27297
27368
|
await storage.updateArtifactState(projectId, lessonCode, "CODE", {
|
|
27298
27369
|
state: "completed",
|
|
27299
27370
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27309,7 +27380,8 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
27309
27380
|
}
|
|
27310
27381
|
}
|
|
27311
27382
|
const extRelPath = `_content/${unitCode}/EXT_${lessonCode}.md`;
|
|
27312
|
-
|
|
27383
|
+
const existingExt = await storage.readArtifact(projectId, extRelPath);
|
|
27384
|
+
if (artifactScope.includes("EXT") && (!existingExt || force || isStaleVsPlan(existingExt))) {
|
|
27313
27385
|
onProgress?.("@activity", `Authoring Advanced Extension Challenge: \`EXT_${lessonCode}.md\`...`, { artifactType: "EXT" });
|
|
27314
27386
|
const extDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27315
27387
|
const isVi = targetLang === "vi";
|
|
@@ -27379,7 +27451,7 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
|
|
|
27379
27451
|
}
|
|
27380
27452
|
);
|
|
27381
27453
|
if (rawExt && !rawExt.startsWith("\u26A0\uFE0F") && rawExt.length >= 200) {
|
|
27382
|
-
await storage.saveArtifact(projectId, extRelPath, rawExt);
|
|
27454
|
+
await storage.saveArtifact(projectId, extRelPath, stampPlanHash(rawExt));
|
|
27383
27455
|
await storage.updateArtifactState(projectId, lessonCode, "EXT", {
|
|
27384
27456
|
state: "completed",
|
|
27385
27457
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|