@thanh01.pmt/curriculum-kit 1.4.18 → 1.4.21
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/{gateSettings-L3FR2-MO.d.cts → gateSettings-DabOqP6_.d.cts} +94 -1
- package/dist/{gateSettings-L3FR2-MO.d.ts → gateSettings-DabOqP6_.d.ts} +94 -1
- package/dist/index.cjs +872 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +283 -125
- package/dist/index.d.ts +283 -125
- package/dist/index.mjs +865 -188
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.cjs +21 -9
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.d.cts +11 -1
- package/dist/storage/index.d.ts +11 -1
- package/dist/storage/index.mjs +21 -10
- package/dist/storage/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +280 -8
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.d.cts +2 -2
- package/dist/workflow/index.d.ts +2 -2
- package/dist/workflow/index.mjs +280 -8
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +23 -22
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -1513,7 +1513,8 @@ function buildSlideBatchPrompt(params) {
|
|
|
1513
1513
|
skillPrompt,
|
|
1514
1514
|
language = "Vietnamese",
|
|
1515
1515
|
languageDirective = "",
|
|
1516
|
-
headingDirective = ""
|
|
1516
|
+
headingDirective = "",
|
|
1517
|
+
groundContext = ""
|
|
1517
1518
|
} = params;
|
|
1518
1519
|
const slidesSpec = clusterSlides.map((s) => `
|
|
1519
1520
|
- Slide ${s.slideIndex} [Phase: ${s.lessonPhase}] -> Layout: "${s.layoutId}"
|
|
@@ -1526,7 +1527,11 @@ function buildSlideBatchPrompt(params) {
|
|
|
1526
1527
|
const systemPrompt = `
|
|
1527
1528
|
${skillPrompt}
|
|
1528
1529
|
|
|
1529
|
-
|
|
1530
|
+
${groundContext ? `---
|
|
1531
|
+
### PROJECT GROUND CONTEXT (framework, style guide, glossary, canonical knowledge \u2014 author from these):
|
|
1532
|
+
${groundContext}
|
|
1533
|
+
|
|
1534
|
+
---` : ""}
|
|
1530
1535
|
### OPERATIONAL GROUND RULES FOR CHUNK GENERATION:
|
|
1531
1536
|
${languageDirective}
|
|
1532
1537
|
${headingDirective}
|
|
@@ -1597,19 +1602,115 @@ var slideProductionWorkflow_exports = {};
|
|
|
1597
1602
|
__export(slideProductionWorkflow_exports, {
|
|
1598
1603
|
GeneratedSlideArraySchema: () => exports.GeneratedSlideArraySchema,
|
|
1599
1604
|
GeneratedSlideSchema: () => exports.GeneratedSlideSchema,
|
|
1605
|
+
HybridBlueprintArraySchema: () => exports.HybridBlueprintArraySchema,
|
|
1606
|
+
HybridBlueprintItemSchema: () => exports.HybridBlueprintItemSchema,
|
|
1607
|
+
HybridDeckSlideArraySchema: () => exports.HybridDeckSlideArraySchema,
|
|
1608
|
+
HybridDeckSlideSchema: () => exports.HybridDeckSlideSchema,
|
|
1609
|
+
HybridPipelineError: () => exports.HybridPipelineError,
|
|
1600
1610
|
SlideBlueprintArraySchema: () => exports.SlideBlueprintArraySchema,
|
|
1601
1611
|
SlideBlueprintItemSchema: () => exports.SlideBlueprintItemSchema,
|
|
1602
|
-
|
|
1612
|
+
emitUsage: () => emitUsage,
|
|
1613
|
+
executeSlideProductionWorkflow: () => executeSlideProductionWorkflow,
|
|
1614
|
+
extractJsonArray: () => extractJsonArray,
|
|
1615
|
+
validateHybridDeckSlides: () => validateHybridDeckSlides
|
|
1603
1616
|
});
|
|
1617
|
+
function extractJsonArray(raw) {
|
|
1618
|
+
if (!raw) return { error: "empty response" };
|
|
1619
|
+
let text = raw.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
|
|
1620
|
+
text = text.replace(/^```(?:json)?\s*/m, "").replace(/```\s*$/m, "").trim();
|
|
1621
|
+
const start = text.indexOf("[");
|
|
1622
|
+
const end = text.lastIndexOf("]");
|
|
1623
|
+
if (start < 0 || end <= start) {
|
|
1624
|
+
return {
|
|
1625
|
+
error: "no JSON array span found",
|
|
1626
|
+
head: text.slice(0, 300),
|
|
1627
|
+
tail: text.slice(-300)
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
const span = text.slice(start, end + 1);
|
|
1631
|
+
try {
|
|
1632
|
+
return { value: JSON.parse(span) };
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
try {
|
|
1636
|
+
return { value: JSON.parse(jsonrepair.jsonrepair(span)) };
|
|
1637
|
+
} catch (e) {
|
|
1638
|
+
return {
|
|
1639
|
+
error: "JSON.parse/jsonrepair failed: " + String(e?.message || e).slice(0, 120),
|
|
1640
|
+
head: text.slice(0, 300),
|
|
1641
|
+
tail: text.slice(-300)
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
function validateHybridDeckSlides(slides, blueprint) {
|
|
1646
|
+
const v = [];
|
|
1647
|
+
if (blueprint.length > 0 && slides.length !== blueprint.length) {
|
|
1648
|
+
v.push(`slide count mismatch: got ${slides.length}, blueprint requires ${blueprint.length}`);
|
|
1649
|
+
}
|
|
1650
|
+
slides.forEach((s, i) => {
|
|
1651
|
+
const n = i + 1;
|
|
1652
|
+
const title = typeof s?.title === "string" ? s.title : "";
|
|
1653
|
+
if (!title.trim()) v.push(`slide ${n}: missing/empty title`);
|
|
1654
|
+
else if (title.length > 150) v.push(`slide ${n}: title too long (${title.length} chars, max 150) \u2014 repetition-loop guard`);
|
|
1655
|
+
const notes = typeof s?.notes === "string" ? s.notes.trim() : "";
|
|
1656
|
+
if (notes.length < 80) v.push(`slide ${n}: presenter notes missing or too short (${notes.length} chars, min 80)`);
|
|
1657
|
+
const code = s?.slots?.code;
|
|
1658
|
+
if (typeof code === "string") {
|
|
1659
|
+
if (code.length > 6e3) v.push(`slide ${n}: code block too long (${code.length} chars, max 6000)`);
|
|
1660
|
+
if (/\/\/\s*TODO|<CODE>|your code here/i.test(code)) v.push(`slide ${n}: placeholder code detected (TODO/<CODE>)`);
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
return v.slice(0, 10);
|
|
1664
|
+
}
|
|
1665
|
+
function emitUsage(usage, onProgress) {
|
|
1666
|
+
if (!onProgress || !usage) return;
|
|
1667
|
+
const promptTokens = usage.promptTokens ?? usage.inputTokens ?? 0;
|
|
1668
|
+
const completionTokens = usage.completionTokens ?? usage.outputTokens ?? 0;
|
|
1669
|
+
if (promptTokens === 0 && completionTokens === 0) return;
|
|
1670
|
+
onProgress("@illustrator", JSON.stringify({
|
|
1671
|
+
promptTokens,
|
|
1672
|
+
completionTokens,
|
|
1673
|
+
totalTokens: usage.totalTokens ?? promptTokens + completionTokens,
|
|
1674
|
+
reasoningTokens: usage.reasoningTokens ?? 0
|
|
1675
|
+
}), { type: "usage" });
|
|
1676
|
+
}
|
|
1677
|
+
async function inferTextJson(schema, systemPrompt, userPrompt, options, label) {
|
|
1678
|
+
const model = getAIModel(options.modelOptions);
|
|
1679
|
+
try {
|
|
1680
|
+
const { text, finishReason, usage } = await ai.generateText({
|
|
1681
|
+
model,
|
|
1682
|
+
system: systemPrompt || void 0,
|
|
1683
|
+
prompt: userPrompt,
|
|
1684
|
+
maxOutputTokens: options.maxOutputTokens ?? 65536
|
|
1685
|
+
});
|
|
1686
|
+
emitUsage(usage, options.onProgress);
|
|
1687
|
+
const extracted = extractJsonArray(text);
|
|
1688
|
+
if (!("value" in extracted) || extracted.value === void 0) {
|
|
1689
|
+
console.warn(
|
|
1690
|
+
`[SlideProductionWorkflow] ${label}: JSON extraction failed (${extracted.error}); finish=${finishReason}`,
|
|
1691
|
+
extracted.head ? `head=${String(extracted.head).slice(0, 150)}` : ""
|
|
1692
|
+
);
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
const validated = schema.safeParse(extracted.value);
|
|
1696
|
+
if (validated.success) return validated.data;
|
|
1697
|
+
console.warn(`[SlideProductionWorkflow] ${label}: output failed schema validation:`, validated.error?.message);
|
|
1698
|
+
return null;
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
console.warn(`[SlideProductionWorkflow] ${label}: generateText failed:`, err?.message || err);
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1604
1704
|
async function inferStructured(schema, systemPrompt, userPrompt, options, label) {
|
|
1605
1705
|
try {
|
|
1606
1706
|
const model = getAIModel(options.modelOptions);
|
|
1607
|
-
const { object } = await ai.generateObject({
|
|
1707
|
+
const { object, usage } = await ai.generateObject({
|
|
1608
1708
|
model,
|
|
1609
1709
|
schema,
|
|
1610
1710
|
system: systemPrompt || void 0,
|
|
1611
1711
|
prompt: userPrompt
|
|
1612
1712
|
});
|
|
1713
|
+
emitUsage(usage, options.onProgress);
|
|
1613
1714
|
const validated = schema.safeParse(object);
|
|
1614
1715
|
if (validated.success) return validated.data;
|
|
1615
1716
|
console.warn(
|
|
@@ -1624,7 +1725,9 @@ async function inferStructured(schema, systemPrompt, userPrompt, options, label)
|
|
|
1624
1725
|
const messages = [
|
|
1625
1726
|
{ role: "user", content: [systemPrompt, userPrompt].filter(Boolean).join("\n\n") }
|
|
1626
1727
|
];
|
|
1627
|
-
const raw = await runCurriculumAIInference(messages, options.satelliteContext || "", options.runnerOptions)
|
|
1728
|
+
const raw = await runCurriculumAIInference(messages, options.satelliteContext || "", options.runnerOptions, (token, type) => {
|
|
1729
|
+
if (type === "usage") options.onProgress?.("@illustrator", token, { type: "usage" });
|
|
1730
|
+
});
|
|
1628
1731
|
const fenceMatch = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
1629
1732
|
const candidate = (fenceMatch ? fenceMatch[1] : raw).trim();
|
|
1630
1733
|
let parsed;
|
|
@@ -1642,9 +1745,144 @@ async function inferStructured(schema, systemPrompt, userPrompt, options, label)
|
|
|
1642
1745
|
return null;
|
|
1643
1746
|
}
|
|
1644
1747
|
}
|
|
1748
|
+
function normalizeBlueprintItems(items) {
|
|
1749
|
+
return items.sort((a, b) => (a.slideIndex ?? 0) - (b.slideIndex ?? 0)).map((item, idx) => ({
|
|
1750
|
+
slideIndex: idx + 1,
|
|
1751
|
+
clusterId: item.clusterId ?? Math.floor(idx / 5) + 1,
|
|
1752
|
+
clusterTitle: item.clusterTitle || "Cluster",
|
|
1753
|
+
lessonPhase: item.lessonPhase || "Content",
|
|
1754
|
+
layoutId: item.layoutId,
|
|
1755
|
+
title: item.title,
|
|
1756
|
+
pedagogicalGoal: item.pedagogicalGoal || "",
|
|
1757
|
+
contentFocus: item.contentFocus || [],
|
|
1758
|
+
codeSnippetIntent: item.codeSnippetIntent ?? void 0,
|
|
1759
|
+
visualIntent: item.visualIntent ?? void 0
|
|
1760
|
+
}));
|
|
1761
|
+
}
|
|
1762
|
+
function buildHybridDeckPrompts(params) {
|
|
1763
|
+
const { lessonFlow, blueprint, skillPrompt, language, languageDirective, headingDirective, groundContext } = params;
|
|
1764
|
+
const blueprintText = JSON.stringify(
|
|
1765
|
+
blueprint.map((s) => ({
|
|
1766
|
+
slideIndex: s.slideIndex,
|
|
1767
|
+
lessonPhase: s.lessonPhase,
|
|
1768
|
+
layoutId: s.layoutId,
|
|
1769
|
+
title: s.title,
|
|
1770
|
+
pedagogicalGoal: s.pedagogicalGoal,
|
|
1771
|
+
contentFocus: s.contentFocus,
|
|
1772
|
+
codeSnippetIntent: s.codeSnippetIntent ?? void 0,
|
|
1773
|
+
visualIntent: s.visualIntent ?? void 0
|
|
1774
|
+
})),
|
|
1775
|
+
null,
|
|
1776
|
+
1
|
|
1777
|
+
);
|
|
1778
|
+
const phasesContent = lessonFlow.phases.length > 0 ? lessonFlow.phases.map((p) => `### ${p.phaseName}
|
|
1779
|
+
${p.content}`).join("\n\n") : lessonFlow.rawContent;
|
|
1780
|
+
const systemPrompt = [
|
|
1781
|
+
skillPrompt,
|
|
1782
|
+
groundContext || "",
|
|
1783
|
+
languageDirective,
|
|
1784
|
+
headingDirective,
|
|
1785
|
+
`
|
|
1786
|
+
### OPERATIONAL GROUND RULES (FULL-DECK AUTHORING):
|
|
1787
|
+
1. **EXACT COUNT: author EXACTLY ${blueprint.length} slide objects \u2014 ONE per blueprint item, SAME ORDER, SAME layoutId.** Do NOT merge, skip, reorder, or add slides.
|
|
1788
|
+
2. **ZERO ABBREVIATION / NO "// TODO"**: All code snippets MUST be real, fully authored, compilable code relevant to "${lessonFlow.lessonTitle}". Never write placeholders, stubs, or "<CODE>".
|
|
1789
|
+
3. **MANDATORY 3-PART PRESENTER NOTES ON EVERY SLIDE (120+ chars)**: SCRIPT (60-90s spoken talk track with an intuitive analogy), COLD CALL / CHECK (one targeted understanding question), SCAFFOLDING / GOTCHA (one common misconception or bug).
|
|
1790
|
+
4. Titles must be < 100 characters \u2014 never repeat or loop text.
|
|
1791
|
+
5. Output ONLY the JSON array. No prose, no markdown fences.
|
|
1792
|
+
`.trim()
|
|
1793
|
+
].filter(Boolean).join("\n\n");
|
|
1794
|
+
const userPrompt = `
|
|
1795
|
+
### BLUEPRINT (${blueprint.length} slides \u2014 AUTHOR ALL OF THEM, in this exact order):
|
|
1796
|
+
${blueprintText}
|
|
1797
|
+
|
|
1798
|
+
### LESSON GROUND TRUTH:
|
|
1799
|
+
- Lesson Title: "${lessonFlow.lessonTitle}"
|
|
1800
|
+
- Target Duration: ${lessonFlow.estimatedDuration}
|
|
1801
|
+
- Pedagogy: "${lessonFlow.pedagogicalModel.toUpperCase()}"
|
|
1802
|
+
- Language: "${language}"
|
|
1803
|
+
|
|
1804
|
+
### LESSON PHASE CONTENT (SOURCE OF TRUTH FOR REAL CONTENT):
|
|
1805
|
+
${phasesContent.slice(0, 16e3)}
|
|
1806
|
+
|
|
1807
|
+
---
|
|
1808
|
+
|
|
1809
|
+
### OUTPUT:
|
|
1810
|
+
A single JSON array of exactly ${blueprint.length} slide objects:
|
|
1811
|
+
\`\`\`json
|
|
1812
|
+
[
|
|
1813
|
+
{
|
|
1814
|
+
"id": "slide-1",
|
|
1815
|
+
"layoutId": "${blueprint[0]?.layoutId || "split-concept-code"}",
|
|
1816
|
+
"title": "${blueprint[0]?.title || "Slide Title"}",
|
|
1817
|
+
"slots": { "...layout-specific slots with REAL content..." },
|
|
1818
|
+
"notes": "SCRIPT: ...\\nCOLD CALL: ...\\nSCAFFOLDING: ..."
|
|
1819
|
+
}
|
|
1820
|
+
]
|
|
1821
|
+
\`\`\`
|
|
1822
|
+
`.trim();
|
|
1823
|
+
return { systemPrompt, userPrompt };
|
|
1824
|
+
}
|
|
1825
|
+
async function runHybridPipeline(ctx) {
|
|
1826
|
+
const { lessonFlow, blueprintPrompt, skillPrompt, language, languageDirective, headingDirective, maxRetries, options, onProgress } = ctx;
|
|
1827
|
+
let blueprint = null;
|
|
1828
|
+
for (let attempt = 1; attempt <= maxRetries && !blueprint; attempt++) {
|
|
1829
|
+
onProgress?.("@illustrator", `[1/4] L\u1EADp D\xE0n \xFD Slides \u2014 hybrid blueprint (l\u1EA7n ${attempt}/${maxRetries})...`);
|
|
1830
|
+
const parsed = await inferTextJson(exports.HybridBlueprintArraySchema, "", blueprintPrompt, options, `hybrid-blueprint#${attempt}`);
|
|
1831
|
+
if (parsed && parsed.length > 0) {
|
|
1832
|
+
blueprint = normalizeBlueprintItems(parsed);
|
|
1833
|
+
} else if (attempt < maxRetries) {
|
|
1834
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Hybrid blueprint l\u1EA7n ${attempt}/${maxRetries} l\u1ED7i \u2014 th\u1EED l\u1EA1i...`, { type: "warning" });
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (!blueprint || blueprint.length === 0) {
|
|
1838
|
+
throw new exports.HybridPipelineError(`blueprint failed after ${maxRetries} attempts`);
|
|
1839
|
+
}
|
|
1840
|
+
onProgress?.("@illustrator", `[1/4] D\xE0n \xFD ${blueprint.length} slides ho\xE0n t\u1EA5t \u2014 chuy\u1EC3n sang authoring to\xE0n deck...`);
|
|
1841
|
+
const { systemPrompt, userPrompt } = buildHybridDeckPrompts({
|
|
1842
|
+
lessonFlow,
|
|
1843
|
+
blueprint,
|
|
1844
|
+
skillPrompt,
|
|
1845
|
+
language,
|
|
1846
|
+
languageDirective,
|
|
1847
|
+
headingDirective,
|
|
1848
|
+
groundContext: options.groundContext
|
|
1849
|
+
});
|
|
1850
|
+
let slides = null;
|
|
1851
|
+
let lastViolations = [];
|
|
1852
|
+
for (let attempt = 1; attempt <= maxRetries && !slides; attempt++) {
|
|
1853
|
+
onProgress?.("@illustrator", `[2/4] Sinh chi ti\u1EBFt to\xE0n b\u1ED9 ${blueprint.length} slides trong 1 l\u1EA7n g\u1ECDi (l\u1EA7n ${attempt}/${maxRetries})...`);
|
|
1854
|
+
const feedback = attempt > 1 && lastViolations.length > 0 ? `
|
|
1855
|
+
|
|
1856
|
+
### \u26A0\uFE0F PREVIOUS ATTEMPT REJECTED \u2014 fix these violations:
|
|
1857
|
+
${lastViolations.map((x) => "- " + x).join("\n")}
|
|
1858
|
+
Return exactly ${blueprint.length} slides, same order as the blueprint.` : "";
|
|
1859
|
+
const parsed = await inferTextJson(exports.HybridDeckSlideArraySchema, systemPrompt, userPrompt + feedback, options, `hybrid-author#${attempt}`);
|
|
1860
|
+
if (!parsed || parsed.length === 0) {
|
|
1861
|
+
lastViolations = ["Output missing, empty, or not a valid JSON array of slide objects"];
|
|
1862
|
+
} else {
|
|
1863
|
+
const violations = validateHybridDeckSlides(parsed, blueprint);
|
|
1864
|
+
if (violations.length === 0) {
|
|
1865
|
+
slides = parsed;
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
lastViolations = violations;
|
|
1869
|
+
}
|
|
1870
|
+
if (attempt < maxRetries) {
|
|
1871
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Deck authoring l\u1EA7n ${attempt}/${maxRetries} vi ph\u1EA1m guardrails \u2014 retry v\u1EDBi corrective feedback...`, {
|
|
1872
|
+
type: "warning",
|
|
1873
|
+
violations: lastViolations
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (!slides) {
|
|
1878
|
+
throw new exports.HybridPipelineError(
|
|
1879
|
+
`deck authoring failed after ${maxRetries} attempts. Last violations: ${lastViolations.slice(0, 3).join("; ")}`
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
return { slides, blueprint };
|
|
1883
|
+
}
|
|
1645
1884
|
async function executeSlideProductionWorkflow(options) {
|
|
1646
1885
|
const {
|
|
1647
|
-
lessonMarkdown,
|
|
1648
1886
|
lessonCode,
|
|
1649
1887
|
lessonTitle,
|
|
1650
1888
|
targetSlideCount,
|
|
@@ -1655,6 +1893,7 @@ async function executeSlideProductionWorkflow(options) {
|
|
|
1655
1893
|
maxRetries = 3,
|
|
1656
1894
|
onProgress
|
|
1657
1895
|
} = options;
|
|
1896
|
+
const engineRequested = options.engine ?? "hybrid";
|
|
1658
1897
|
let presentationKitSkills = null;
|
|
1659
1898
|
let presentationKitCore = null;
|
|
1660
1899
|
try {
|
|
@@ -1665,117 +1904,141 @@ async function executeSlideProductionWorkflow(options) {
|
|
|
1665
1904
|
presentationKitCore = await import('@thanh01.pmt/presentation-kit');
|
|
1666
1905
|
} catch {
|
|
1667
1906
|
}
|
|
1668
|
-
const
|
|
1669
|
-
onProgress?.("@illustrator", `[1/4] Ph\xE2n t\xEDch m\u1EA1ch b\xE0i gi\u1EA3ng LESSON & L\u1EADp D\xE0n \xFD ${countLabel}Slides...`);
|
|
1670
|
-
const lessonFlow = parseLessonFlow(lessonMarkdown);
|
|
1907
|
+
const lessonFlow = parseLessonFlow(options.lessonMarkdown);
|
|
1671
1908
|
const getStylePreset = presentationKitSkills?.getStylePreset;
|
|
1672
1909
|
const stylePreset = getStylePreset ? getStylePreset(stylePresetId) : { name: "Blue Professional" };
|
|
1910
|
+
const skillPrompt = presentationKitSkills?.getFrontendSlidesSkillPrompt ? presentationKitSkills.getFrontendSlidesSkillPrompt({ stylePresetId }) : "";
|
|
1673
1911
|
const blueprintPrompt = buildSlideBlueprintPrompt({
|
|
1674
1912
|
lessonFlow,
|
|
1675
1913
|
targetSlideCount,
|
|
1676
1914
|
stylePresetName: stylePreset?.name || "Blue Professional"
|
|
1677
1915
|
});
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1916
|
+
let blueprintItems;
|
|
1917
|
+
let allGeneratedSlides;
|
|
1918
|
+
let engineUsed = engineRequested;
|
|
1919
|
+
const hybridResult = engineRequested === "hybrid" ? await runHybridPipeline({
|
|
1920
|
+
lessonFlow,
|
|
1921
|
+
blueprintPrompt,
|
|
1922
|
+
skillPrompt,
|
|
1923
|
+
language,
|
|
1924
|
+
languageDirective,
|
|
1925
|
+
headingDirective,
|
|
1926
|
+
maxRetries,
|
|
1927
|
+
options,
|
|
1928
|
+
onProgress
|
|
1929
|
+
}).catch((hybridErr) => {
|
|
1930
|
+
console.warn(`[SlideProductionWorkflow] Hybrid engine failed: ${hybridErr?.message || hybridErr} \u2014 falling back to chunked pipeline.`);
|
|
1931
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Hybrid engine l\u1ED7i \u2014 chuy\u1EC3n sang chunked pipeline (per-cluster)...`, { type: "warning" });
|
|
1932
|
+
return null;
|
|
1933
|
+
}) : null;
|
|
1934
|
+
if (hybridResult) {
|
|
1935
|
+
blueprintItems = hybridResult.blueprint;
|
|
1936
|
+
allGeneratedSlides = hybridResult.slides.map((s, idx) => ({
|
|
1937
|
+
id: s.id || `slide-${idx + 1}`,
|
|
1938
|
+
layoutId: s.layoutId,
|
|
1939
|
+
title: s.title,
|
|
1940
|
+
slots: s.slots || {},
|
|
1941
|
+
notes: s.notes || ""
|
|
1942
|
+
}));
|
|
1943
|
+
} else {
|
|
1944
|
+
engineUsed = "chunked";
|
|
1945
|
+
const countLabel = targetSlideCount ? `${targetSlideCount} ` : "h\u1EEFu c\u01A1 ";
|
|
1946
|
+
onProgress?.("@illustrator", `[1/4] Ph\xE2n t\xEDch m\u1EA1ch b\xE0i gi\u1EA3ng LESSON & L\u1EADp D\xE0n \xFD ${countLabel}Slides...`);
|
|
1947
|
+
blueprintItems = await pRetry__default.default(
|
|
1948
|
+
async () => {
|
|
1949
|
+
const items = await inferStructured(
|
|
1950
|
+
exports.SlideBlueprintArraySchema,
|
|
1951
|
+
"",
|
|
1952
|
+
blueprintPrompt,
|
|
1953
|
+
options,
|
|
1954
|
+
"blueprint"
|
|
1955
|
+
);
|
|
1956
|
+
if (!items || items.length === 0) {
|
|
1957
|
+
throw new Error("Blueprint generation returned empty or schema-invalid output");
|
|
1958
|
+
}
|
|
1959
|
+
return normalizeBlueprintItems(items);
|
|
1960
|
+
},
|
|
1961
|
+
{
|
|
1962
|
+
retries: maxRetries - 1,
|
|
1963
|
+
onFailedAttempt: (err) => {
|
|
1964
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Blueprint attempt ${err.attemptNumber}/${maxRetries} failed \u2014 retrying...`, {
|
|
1965
|
+
type: "warning"
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1706
1968
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
const matchingPhases = lessonFlow.phases.filter((p) => clusterPhaseNames.has(p.phaseName.toLowerCase()));
|
|
1726
|
-
const lessonExcerpt = matchingPhases.length > 0 ? matchingPhases.map((p) => `### ${p.phaseName}
|
|
1969
|
+
);
|
|
1970
|
+
const clustersMap = /* @__PURE__ */ new Map();
|
|
1971
|
+
for (const item of blueprintItems) {
|
|
1972
|
+
const cId = item.clusterId || Math.floor((item.slideIndex - 1) / 5) + 1;
|
|
1973
|
+
if (!clustersMap.has(cId)) clustersMap.set(cId, []);
|
|
1974
|
+
clustersMap.get(cId).push(item);
|
|
1975
|
+
}
|
|
1976
|
+
const clusters = Array.from(clustersMap.entries()).sort(([a], [b]) => a - b);
|
|
1977
|
+
allGeneratedSlides = [];
|
|
1978
|
+
const failedClusters = [];
|
|
1979
|
+
let clusterIdx = 0;
|
|
1980
|
+
for (const [cId, clusterSlides] of clusters) {
|
|
1981
|
+
clusterIdx++;
|
|
1982
|
+
const clusterTitle = clusterSlides[0]?.clusterTitle || `Giai \u0111o\u1EA1n ${clusterIdx}`;
|
|
1983
|
+
onProgress?.("@illustrator", `[2/4] Sinh chi ti\u1EBFt C\u1EE5m ${clusterIdx}/${clusters.length}: "${clusterTitle}" (${clusterSlides.length} slides)...`);
|
|
1984
|
+
const clusterPhaseNames = new Set(clusterSlides.map((s) => s.lessonPhase.toLowerCase()));
|
|
1985
|
+
const matchingPhases = lessonFlow.phases.filter((p) => clusterPhaseNames.has(p.phaseName.toLowerCase()));
|
|
1986
|
+
const lessonExcerpt = matchingPhases.length > 0 ? matchingPhases.map((p) => `### ${p.phaseName}
|
|
1727
1987
|
${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1988
|
+
const { systemPrompt, userPrompt } = buildSlideBatchPrompt({
|
|
1989
|
+
clusterId: cId,
|
|
1990
|
+
clusterTitle,
|
|
1991
|
+
clusterSlides,
|
|
1992
|
+
lessonFlow,
|
|
1993
|
+
lessonExcerpt,
|
|
1994
|
+
skillPrompt,
|
|
1995
|
+
language,
|
|
1996
|
+
languageDirective,
|
|
1997
|
+
headingDirective,
|
|
1998
|
+
groundContext: options.groundContext
|
|
1999
|
+
});
|
|
2000
|
+
try {
|
|
2001
|
+
const batchSlides = await pRetry__default.default(
|
|
2002
|
+
async () => {
|
|
2003
|
+
const slides = await inferStructured(
|
|
2004
|
+
exports.GeneratedSlideArraySchema,
|
|
2005
|
+
systemPrompt,
|
|
2006
|
+
userPrompt,
|
|
2007
|
+
options,
|
|
2008
|
+
`cluster-${cId}`
|
|
2009
|
+
);
|
|
2010
|
+
if (!slides || slides.length === 0) {
|
|
2011
|
+
throw new Error(`Cluster ${cId} returned empty or schema-invalid output`);
|
|
2012
|
+
}
|
|
2013
|
+
return slides;
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
retries: maxRetries - 1,
|
|
2017
|
+
onFailedAttempt: (err) => {
|
|
2018
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F C\u1EE5m ${clusterIdx} l\u1EA7n ${err.attemptNumber}/${maxRetries} l\u1ED7i \u2014 retry ri\xEAng c\u1EE5m n\xE0y...`, {
|
|
2019
|
+
type: "warning"
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
1760
2022
|
}
|
|
1761
|
-
|
|
2023
|
+
);
|
|
2024
|
+
allGeneratedSlides.push(...batchSlides);
|
|
2025
|
+
} catch (clusterErr) {
|
|
2026
|
+
failedClusters.push(cId);
|
|
2027
|
+
console.error(`[SlideProductionWorkflow] Cluster ${cId} permanently failed after ${maxRetries} attempts:`, clusterErr?.message || clusterErr);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (failedClusters.length > 0) {
|
|
2031
|
+
throw new Error(
|
|
2032
|
+
`[SlideProductionWorkflow] Failed to generate ${failedClusters.length}/${clusters.length} cluster(s) (clusterId: ${failedClusters.join(", ")}) after ${maxRetries} attempts each. Aborting to prevent placeholder/degraded slide output. Review provider keys/model availability and retry.`
|
|
1762
2033
|
);
|
|
1763
|
-
allGeneratedSlides.push(...batchSlides);
|
|
1764
|
-
} catch (clusterErr) {
|
|
1765
|
-
failedClusters.push(cId);
|
|
1766
|
-
console.error(`[SlideProductionWorkflow] Cluster ${cId} permanently failed after ${maxRetries} attempts:`, clusterErr?.message || clusterErr);
|
|
1767
2034
|
}
|
|
1768
2035
|
}
|
|
1769
|
-
if (failedClusters.length > 0) {
|
|
1770
|
-
throw new Error(
|
|
1771
|
-
`[SlideProductionWorkflow] Failed to generate ${failedClusters.length}/${clusters.length} cluster(s) (clusterId: ${failedClusters.join(", ")}) after ${maxRetries} attempts each. Aborting to prevent placeholder/degraded slide output. Review provider keys/model availability and retry.`
|
|
1772
|
-
);
|
|
1773
|
-
}
|
|
1774
2036
|
onProgress?.("@illustrator", `[3/4] Chu\u1EA9n h\xF3a b\u1ED1 c\u1EE5c v\xE0 bi\xEAn d\u1ECBch 1920\xD71080 Stage Deck (${allGeneratedSlides.length} slides)...`);
|
|
1775
2037
|
const normalizer = presentationKitCore?.normalizeSlideSlots;
|
|
1776
2038
|
const normalizedSlides = allGeneratedSlides.map((s, idx) => {
|
|
1777
2039
|
const base = normalizer ? normalizer(s) : s;
|
|
1778
2040
|
if (!base.id) base.id = `slide-${idx + 1}`;
|
|
2041
|
+
if (!base.slots || typeof base.slots !== "object") base.slots = {};
|
|
1779
2042
|
return base;
|
|
1780
2043
|
});
|
|
1781
2044
|
const deckJson = {
|
|
@@ -1785,16 +2048,19 @@ ${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
|
|
|
1785
2048
|
slides: normalizedSlides
|
|
1786
2049
|
};
|
|
1787
2050
|
let compiledHtml;
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
} catch (compErr) {
|
|
1796
|
-
console.warn(`[SlideProductionWorkflow] Failed compiling HTML deck:`, compErr?.message || compErr);
|
|
2051
|
+
try {
|
|
2052
|
+
let compiled = null;
|
|
2053
|
+
if (typeof presentationKitCore?.compileHtmlDeckAsync === "function") {
|
|
2054
|
+
compiled = await presentationKitCore.compileHtmlDeckAsync(deckJson);
|
|
2055
|
+
}
|
|
2056
|
+
if (!compiled?.html && typeof presentationKitCore?.compileHtmlDeck === "function") {
|
|
2057
|
+
compiled = presentationKitCore.compileHtmlDeck(deckJson);
|
|
1797
2058
|
}
|
|
2059
|
+
if (compiled?.html) {
|
|
2060
|
+
compiledHtml = compiled.html;
|
|
2061
|
+
}
|
|
2062
|
+
} catch (compErr) {
|
|
2063
|
+
console.warn(`[SlideProductionWorkflow] Failed compiling HTML deck:`, compErr?.message || compErr);
|
|
1798
2064
|
}
|
|
1799
2065
|
const markdownWrapper = `---
|
|
1800
2066
|
id: "SLIDE_${lessonCode}"
|
|
@@ -1818,10 +2084,11 @@ ${JSON.stringify(deckJson, null, 2)}
|
|
|
1818
2084
|
compiledHtml,
|
|
1819
2085
|
markdownWrapper,
|
|
1820
2086
|
blueprint: blueprintItems,
|
|
1821
|
-
slideCount: normalizedSlides.length
|
|
2087
|
+
slideCount: normalizedSlides.length,
|
|
2088
|
+
engine: engineUsed
|
|
1822
2089
|
};
|
|
1823
2090
|
}
|
|
1824
|
-
exports.SlideBlueprintItemSchema = void 0; exports.SlideBlueprintArraySchema = void 0; exports.GeneratedSlideSchema = void 0; exports.GeneratedSlideArraySchema = void 0;
|
|
2091
|
+
var LAYOUT_ID_ENUM; exports.SlideBlueprintItemSchema = void 0; exports.SlideBlueprintArraySchema = void 0; exports.GeneratedSlideSchema = void 0; exports.GeneratedSlideArraySchema = void 0; exports.HybridBlueprintItemSchema = void 0; exports.HybridBlueprintArraySchema = void 0; exports.HybridDeckSlideSchema = void 0; exports.HybridDeckSlideArraySchema = void 0; exports.HybridPipelineError = void 0;
|
|
1825
2092
|
var init_slideProductionWorkflow = __esm({
|
|
1826
2093
|
"src/services/slideProductionWorkflow.ts"() {
|
|
1827
2094
|
init_lessonFlowParser();
|
|
@@ -1829,37 +2096,61 @@ var init_slideProductionWorkflow = __esm({
|
|
|
1829
2096
|
init_slideBatchPrompt();
|
|
1830
2097
|
init_provider_factory();
|
|
1831
2098
|
init_streamRunner();
|
|
2099
|
+
LAYOUT_ID_ENUM = zod.z.enum([
|
|
2100
|
+
"hero-cover",
|
|
2101
|
+
"split-concept-code",
|
|
2102
|
+
"two-columns-compare",
|
|
2103
|
+
"three-cards-grid",
|
|
2104
|
+
"timeline-steps",
|
|
2105
|
+
"metric-callout",
|
|
2106
|
+
"checkpoint-quiz",
|
|
2107
|
+
"tiered-practice-3cards",
|
|
2108
|
+
"summary-takeaways"
|
|
2109
|
+
]);
|
|
1832
2110
|
exports.SlideBlueprintItemSchema = zod.z.object({
|
|
1833
2111
|
slideIndex: zod.z.number().int().min(1),
|
|
1834
|
-
clusterId: zod.z.number().int().min(1).default(1),
|
|
1835
|
-
clusterTitle: zod.z.string().default("Cluster"),
|
|
1836
|
-
lessonPhase: zod.z.string().default("Content"),
|
|
1837
|
-
layoutId:
|
|
1838
|
-
"hero-cover",
|
|
1839
|
-
"split-concept-code",
|
|
1840
|
-
"two-columns-compare",
|
|
1841
|
-
"three-cards-grid",
|
|
1842
|
-
"timeline-steps",
|
|
1843
|
-
"metric-callout",
|
|
1844
|
-
"checkpoint-quiz",
|
|
1845
|
-
"tiered-practice-3cards",
|
|
1846
|
-
"summary-takeaways"
|
|
1847
|
-
]),
|
|
2112
|
+
clusterId: zod.z.number().int().min(1).nullish().default(1),
|
|
2113
|
+
clusterTitle: zod.z.string().nullish().default("Cluster"),
|
|
2114
|
+
lessonPhase: zod.z.string().nullish().default("Content"),
|
|
2115
|
+
layoutId: LAYOUT_ID_ENUM,
|
|
1848
2116
|
title: zod.z.string().min(1),
|
|
1849
|
-
pedagogicalGoal: zod.z.string().default(""),
|
|
1850
|
-
contentFocus: zod.z.array(zod.z.string()).default([]),
|
|
1851
|
-
codeSnippetIntent: zod.z.string().
|
|
1852
|
-
visualIntent: zod.z.string().
|
|
2117
|
+
pedagogicalGoal: zod.z.string().nullish().default(""),
|
|
2118
|
+
contentFocus: zod.z.array(zod.z.string()).nullish().default([]),
|
|
2119
|
+
codeSnippetIntent: zod.z.string().nullish(),
|
|
2120
|
+
visualIntent: zod.z.string().nullish()
|
|
1853
2121
|
});
|
|
1854
2122
|
exports.SlideBlueprintArraySchema = zod.z.array(exports.SlideBlueprintItemSchema);
|
|
1855
2123
|
exports.GeneratedSlideSchema = zod.z.object({
|
|
1856
|
-
id: zod.z.string().
|
|
1857
|
-
layoutId: zod.z.string(),
|
|
2124
|
+
id: zod.z.string().nullish(),
|
|
2125
|
+
layoutId: zod.z.string().min(1),
|
|
1858
2126
|
title: zod.z.string().min(1),
|
|
1859
|
-
slots: zod.z.record(zod.z.any()).default({}),
|
|
1860
|
-
notes: zod.z.string().default("")
|
|
2127
|
+
slots: zod.z.record(zod.z.any()).nullish().default({}),
|
|
2128
|
+
notes: zod.z.string().nullish().default("")
|
|
1861
2129
|
});
|
|
1862
2130
|
exports.GeneratedSlideArraySchema = zod.z.array(exports.GeneratedSlideSchema);
|
|
2131
|
+
exports.HybridBlueprintItemSchema = zod.z.object({
|
|
2132
|
+
slideIndex: zod.z.number().int().min(1),
|
|
2133
|
+
clusterId: zod.z.number().int().min(1).nullish(),
|
|
2134
|
+
clusterTitle: zod.z.string().nullish(),
|
|
2135
|
+
lessonPhase: zod.z.string().nullish(),
|
|
2136
|
+
layoutId: LAYOUT_ID_ENUM,
|
|
2137
|
+
title: zod.z.string().min(1).max(300),
|
|
2138
|
+
pedagogicalGoal: zod.z.string().nullish().default(""),
|
|
2139
|
+
contentFocus: zod.z.array(zod.z.string()).nullish().default([]),
|
|
2140
|
+
codeSnippetIntent: zod.z.string().nullish(),
|
|
2141
|
+
visualIntent: zod.z.string().nullish()
|
|
2142
|
+
});
|
|
2143
|
+
exports.HybridBlueprintArraySchema = zod.z.array(exports.HybridBlueprintItemSchema);
|
|
2144
|
+
exports.HybridDeckSlideSchema = zod.z.object({
|
|
2145
|
+
id: zod.z.string().nullish(),
|
|
2146
|
+
layoutId: zod.z.string().min(1),
|
|
2147
|
+
title: zod.z.string().min(1).max(300),
|
|
2148
|
+
slots: zod.z.record(zod.z.any()).nullish().default({}),
|
|
2149
|
+
notes: zod.z.string().nullish().default("")
|
|
2150
|
+
});
|
|
2151
|
+
exports.HybridDeckSlideArraySchema = zod.z.array(exports.HybridDeckSlideSchema);
|
|
2152
|
+
exports.HybridPipelineError = class extends Error {
|
|
2153
|
+
};
|
|
1863
2154
|
}
|
|
1864
2155
|
});
|
|
1865
2156
|
var LearningObjectiveRowSchema = zod.z.object({
|
|
@@ -9046,6 +9337,21 @@ var STANDARD_SOT_FILES = [
|
|
|
9046
9337
|
"ART_DIRECTION.md",
|
|
9047
9338
|
"ALIGNMENT_MATRIX.md"
|
|
9048
9339
|
];
|
|
9340
|
+
function atomicWriteFileSync(targetPath, content) {
|
|
9341
|
+
const dir = path3__default.default.dirname(targetPath);
|
|
9342
|
+
if (!fs2__default.default.existsSync(dir)) fs2__default.default.mkdirSync(dir, { recursive: true });
|
|
9343
|
+
const tmpPath = path3__default.default.join(dir, `.${path3__default.default.basename(targetPath)}.${process.pid}.${Date.now()}.${crypto__default.default.randomBytes(4).toString("hex")}.tmp`);
|
|
9344
|
+
try {
|
|
9345
|
+
fs2__default.default.writeFileSync(tmpPath, content, "utf-8");
|
|
9346
|
+
fs2__default.default.renameSync(tmpPath, targetPath);
|
|
9347
|
+
} catch (err) {
|
|
9348
|
+
try {
|
|
9349
|
+
if (fs2__default.default.existsSync(tmpPath)) fs2__default.default.unlinkSync(tmpPath);
|
|
9350
|
+
} catch {
|
|
9351
|
+
}
|
|
9352
|
+
throw err;
|
|
9353
|
+
}
|
|
9354
|
+
}
|
|
9049
9355
|
var FileSystemCurriculumAdapter = class {
|
|
9050
9356
|
baseDir;
|
|
9051
9357
|
constructor(options = {}) {
|
|
@@ -9184,7 +9490,7 @@ var FileSystemCurriculumAdapter = class {
|
|
|
9184
9490
|
if (oldContent !== content) {
|
|
9185
9491
|
const historyDir = path3__default.default.join(projectDir, ".history", relPath);
|
|
9186
9492
|
fs2__default.default.mkdirSync(historyDir, { recursive: true });
|
|
9187
|
-
|
|
9493
|
+
atomicWriteFileSync(path3__default.default.join(historyDir, `${Date.now()}.md`), oldContent);
|
|
9188
9494
|
const versions = fs2__default.default.readdirSync(historyDir).filter((f) => f.endsWith(".md")).sort();
|
|
9189
9495
|
while (versions.length > 10) {
|
|
9190
9496
|
fs2__default.default.unlinkSync(path3__default.default.join(historyDir, versions.shift()));
|
|
@@ -9194,19 +9500,15 @@ var FileSystemCurriculumAdapter = class {
|
|
|
9194
9500
|
console.warn("[FileSystemCurriculumAdapter] version history snapshot failed:", histErr?.message || histErr);
|
|
9195
9501
|
}
|
|
9196
9502
|
}
|
|
9197
|
-
|
|
9503
|
+
atomicWriteFileSync(targetPath, content);
|
|
9198
9504
|
const filename = path3__default.default.basename(relPath);
|
|
9199
9505
|
const match = filename.match(/(LESSON|ACT|QUIZ|SLIDE|GUIDE|HANDOUT|WKS|EXT)_(U\d+_M\d+_L\d+)\.md/i);
|
|
9200
9506
|
if (match) {
|
|
9201
9507
|
const lessonId = match[2].toUpperCase();
|
|
9202
|
-
|
|
9203
|
-
if (!fs2__default.default.existsSync(lessonsDir)) {
|
|
9204
|
-
fs2__default.default.mkdirSync(lessonsDir, { recursive: true });
|
|
9205
|
-
}
|
|
9206
|
-
fs2__default.default.writeFileSync(path3__default.default.join(lessonsDir, filename), content, "utf-8");
|
|
9508
|
+
atomicWriteFileSync(path3__default.default.join(projectDir, "lessons", lessonId, filename), content);
|
|
9207
9509
|
const legacyContentDir = path3__default.default.join(projectDir, "_content", lessonId);
|
|
9208
9510
|
if (fs2__default.default.existsSync(legacyContentDir)) {
|
|
9209
|
-
|
|
9511
|
+
atomicWriteFileSync(path3__default.default.join(legacyContentDir, filename), content);
|
|
9210
9512
|
}
|
|
9211
9513
|
}
|
|
9212
9514
|
}
|
|
@@ -9297,7 +9599,7 @@ ${lessonTable}
|
|
|
9297
9599
|
}
|
|
9298
9600
|
const stateFile = path3__default.default.join(pipelineDir, "state.json");
|
|
9299
9601
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9300
|
-
|
|
9602
|
+
atomicWriteFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
9301
9603
|
}
|
|
9302
9604
|
async updateArtifactState(projectId, taskId, artifactType, update) {
|
|
9303
9605
|
const current = await this.getPipelineState(projectId);
|
|
@@ -10688,6 +10990,306 @@ ${lines.join("\n")}
|
|
|
10688
10990
|
- For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
|
|
10689
10991
|
}
|
|
10690
10992
|
|
|
10993
|
+
// src/services/curriculumHorizon.ts
|
|
10994
|
+
init_errors();
|
|
10995
|
+
var DETAILED_BRIDGE_WINDOW = 2;
|
|
10996
|
+
var BOUNDARY_PEEK_WINDOW = 2;
|
|
10997
|
+
function parseAllSessions(plan, frameworkMarkdown) {
|
|
10998
|
+
if (plan) {
|
|
10999
|
+
const rawSessions = Array.isArray(plan.sessions) ? plan.sessions : Array.isArray(plan) ? plan : [];
|
|
11000
|
+
if (rawSessions.length > 0) {
|
|
11001
|
+
return rawSessions.map((s, idx) => ({
|
|
11002
|
+
id: s.id || `L${String(idx + 1).padStart(2, "0")}`,
|
|
11003
|
+
order: typeof s.order === "number" ? s.order : idx + 1,
|
|
11004
|
+
title: s.title || "",
|
|
11005
|
+
prose_objective: s.prose_objective || s.objective || "",
|
|
11006
|
+
new_keywords: Array.isArray(s.new_keywords) ? s.new_keywords : Array.isArray(s.keywords) ? s.keywords : [],
|
|
11007
|
+
prerequisite_keywords: Array.isArray(s.prerequisite_keywords) ? s.prerequisite_keywords : [],
|
|
11008
|
+
depth_assignments: Array.isArray(s.depth_assignments) ? s.depth_assignments : []
|
|
11009
|
+
}));
|
|
11010
|
+
}
|
|
11011
|
+
}
|
|
11012
|
+
if (frameworkMarkdown && typeof frameworkMarkdown === "string") {
|
|
11013
|
+
const lines = frameworkMarkdown.split("\n");
|
|
11014
|
+
let headerCols = [];
|
|
11015
|
+
const sessions = [];
|
|
11016
|
+
for (const line of lines) {
|
|
11017
|
+
const trimmed = line.trim();
|
|
11018
|
+
if (!trimmed.startsWith("|")) continue;
|
|
11019
|
+
const cols = trimmed.split("|").slice(1, -1).map((c) => c.trim());
|
|
11020
|
+
const lower = cols.map((c) => c.toLowerCase());
|
|
11021
|
+
if (lower.some((c) => c.includes("lesson code") || c === "m\xE3 b\xE0i" || c.includes("m\xE3 b\xE0i h\u1ECDc"))) {
|
|
11022
|
+
headerCols = cols;
|
|
11023
|
+
continue;
|
|
11024
|
+
}
|
|
11025
|
+
if (/^[-: |]+$/.test(trimmed.slice(1, -1))) continue;
|
|
11026
|
+
if (cols.length < 3) continue;
|
|
11027
|
+
let lessonCode = "";
|
|
11028
|
+
let title = "";
|
|
11029
|
+
let objective = "";
|
|
11030
|
+
let concept = "";
|
|
11031
|
+
let keywordsStr = "";
|
|
11032
|
+
if (headerCols.length > 0) {
|
|
11033
|
+
const col = (name) => {
|
|
11034
|
+
const idx = headerCols.findIndex((h) => h.toLowerCase().includes(name));
|
|
11035
|
+
return idx >= 0 ? cols[idx] || "" : "";
|
|
11036
|
+
};
|
|
11037
|
+
lessonCode = (col("lesson code") || col("m\xE3 b\xE0i") || cols[1] || "").replace(/\*\*/g, "").trim();
|
|
11038
|
+
title = (col("title") || col("t\xEAn") || cols[2] || "").replace(/\*\*/g, "").trim();
|
|
11039
|
+
objective = col("learning objective") || col("objective") || col("m\u1EE5c ti\xEAu") || "";
|
|
11040
|
+
concept = col("key concept") || col("concept") || col("kh\xE1i ni\u1EC7m") || "";
|
|
11041
|
+
keywordsStr = col("keywords") || col("t\u1EEB kh\xF3a") || "";
|
|
11042
|
+
} else {
|
|
11043
|
+
lessonCode = (cols[1] || "").replace(/\*\*/g, "").trim();
|
|
11044
|
+
title = (cols[2] || "").replace(/\*\*/g, "").trim();
|
|
11045
|
+
objective = cols[5] || "";
|
|
11046
|
+
concept = cols[4] || "";
|
|
11047
|
+
}
|
|
11048
|
+
if (lessonCode && /^[A-Za-z0-9_\-]+$/.test(lessonCode)) {
|
|
11049
|
+
const keywords = keywordsStr ? keywordsStr.split(/[,;\n]/).map((k) => k.trim().replace(/^`|`$/g, "")).filter(Boolean) : concept ? concept.split(/[,;\n]/).map((k) => k.trim().replace(/^`|`$/g, "")).filter(Boolean) : [];
|
|
11050
|
+
sessions.push({
|
|
11051
|
+
id: lessonCode,
|
|
11052
|
+
order: sessions.length + 1,
|
|
11053
|
+
title: title || lessonCode,
|
|
11054
|
+
prose_objective: objective,
|
|
11055
|
+
new_keywords: keywords
|
|
11056
|
+
});
|
|
11057
|
+
}
|
|
11058
|
+
}
|
|
11059
|
+
if (sessions.length > 0) {
|
|
11060
|
+
return sessions;
|
|
11061
|
+
}
|
|
11062
|
+
}
|
|
11063
|
+
return [];
|
|
11064
|
+
}
|
|
11065
|
+
async function extractCurriculumHorizon(opts) {
|
|
11066
|
+
const { plan, frameworkMarkdown, targetLessonId, storage, projectId } = opts;
|
|
11067
|
+
const sessions = parseAllSessions(plan, frameworkMarkdown);
|
|
11068
|
+
if (sessions.length === 0) {
|
|
11069
|
+
throw new exports.CurriculumError({
|
|
11070
|
+
errorCode: "ERR_LESSON_NOT_IN_SOT",
|
|
11071
|
+
lessonId: targetLessonId,
|
|
11072
|
+
message: `[CurriculumHorizon] No sessions could be parsed from CURRICULUM_PLAN.json or CURRICULUM_FRAMEWORK.md. Fail fast \u2014 cannot construct curriculum horizon without SOT.`,
|
|
11073
|
+
suggestedAction: "Ensure CURRICULUM_PLAN.json or CURRICULUM_FRAMEWORK.md is generated and contains valid sessions.",
|
|
11074
|
+
retryable: false
|
|
11075
|
+
});
|
|
11076
|
+
}
|
|
11077
|
+
const targetCodePattern = new RegExp("^" + targetLessonId.replace(/_/g, "[_-]") + "$", "i");
|
|
11078
|
+
const targetIndex = sessions.findIndex(
|
|
11079
|
+
(s) => s.id === targetLessonId || targetCodePattern.test(s.id)
|
|
11080
|
+
);
|
|
11081
|
+
if (targetIndex < 0) {
|
|
11082
|
+
throw new exports.CurriculumError({
|
|
11083
|
+
errorCode: "ERR_LESSON_NOT_IN_SOT",
|
|
11084
|
+
lessonId: targetLessonId,
|
|
11085
|
+
message: `[CurriculumHorizon] Lesson "${targetLessonId}" was not found among the ${sessions.length} planned sessions. Fail fast \u2014 refusing to generate unanchored lesson.`,
|
|
11086
|
+
suggestedAction: `Check lesson id against planned sessions: [${sessions.map((s) => s.id).join(", ")}]`,
|
|
11087
|
+
retryable: false
|
|
11088
|
+
});
|
|
11089
|
+
}
|
|
11090
|
+
const currentSession = sessions[targetIndex];
|
|
11091
|
+
const compactEnd = Math.max(0, targetIndex - DETAILED_BRIDGE_WINDOW);
|
|
11092
|
+
const compactSessions = sessions.slice(0, compactEnd);
|
|
11093
|
+
const masteredKeywords = Array.from(
|
|
11094
|
+
new Set(
|
|
11095
|
+
compactSessions.flatMap((s) => s.new_keywords || []).map((k) => k.trim()).filter(Boolean)
|
|
11096
|
+
)
|
|
11097
|
+
);
|
|
11098
|
+
const masteredConcepts = Array.from(
|
|
11099
|
+
new Set(
|
|
11100
|
+
compactSessions.map((s) => s.title || s.prose_objective || "").map((t) => t.trim()).filter(Boolean)
|
|
11101
|
+
)
|
|
11102
|
+
);
|
|
11103
|
+
const bridgeStart = Math.max(0, targetIndex - DETAILED_BRIDGE_WINDOW);
|
|
11104
|
+
const bridgeSessions = sessions.slice(bridgeStart, targetIndex);
|
|
11105
|
+
const detailedBridge = [];
|
|
11106
|
+
for (const session of bridgeSessions) {
|
|
11107
|
+
const bridgeIndex = sessions.indexOf(session) + 1;
|
|
11108
|
+
const bridge = {
|
|
11109
|
+
lessonId: session.id,
|
|
11110
|
+
lessonIndex: bridgeIndex,
|
|
11111
|
+
title: session.title,
|
|
11112
|
+
proseObjective: session.prose_objective,
|
|
11113
|
+
keywords: session.new_keywords || []
|
|
11114
|
+
};
|
|
11115
|
+
if (storage && projectId) {
|
|
11116
|
+
try {
|
|
11117
|
+
const candidates = [
|
|
11118
|
+
`_content/${session.id.replace(/_L\d+$/, "")}/LESSON_${session.id}.md`,
|
|
11119
|
+
`_content/LESSON_${session.id}.md`,
|
|
11120
|
+
`LESSON_${session.id}.md`
|
|
11121
|
+
];
|
|
11122
|
+
let lessonMd = "";
|
|
11123
|
+
for (const c of candidates) {
|
|
11124
|
+
try {
|
|
11125
|
+
const raw = await storage.readArtifact(projectId, c);
|
|
11126
|
+
if (raw) {
|
|
11127
|
+
lessonMd = raw;
|
|
11128
|
+
break;
|
|
11129
|
+
}
|
|
11130
|
+
} catch {
|
|
11131
|
+
}
|
|
11132
|
+
}
|
|
11133
|
+
if (lessonMd) {
|
|
11134
|
+
const ledger = extractSymbolLedger(lessonMd);
|
|
11135
|
+
if (ledger.primarySymbol || ledger.entryFileName || ledger.keySymbols.length > 0) {
|
|
11136
|
+
bridge.symbolLedger = {
|
|
11137
|
+
primaryStructOrClass: ledger.primarySymbol,
|
|
11138
|
+
mainEntryFile: ledger.entryFileName,
|
|
11139
|
+
keyVariables: ledger.keySymbols
|
|
11140
|
+
};
|
|
11141
|
+
}
|
|
11142
|
+
}
|
|
11143
|
+
} catch {
|
|
11144
|
+
}
|
|
11145
|
+
}
|
|
11146
|
+
detailedBridge.push(bridge);
|
|
11147
|
+
}
|
|
11148
|
+
const peekStart = targetIndex + 1;
|
|
11149
|
+
const peekEnd = Math.min(sessions.length, peekStart + BOUNDARY_PEEK_WINDOW);
|
|
11150
|
+
const boundaryPeek = sessions.slice(peekStart, peekEnd).map((s, idx) => ({
|
|
11151
|
+
lessonId: s.id,
|
|
11152
|
+
lessonIndex: peekStart + idx + 1,
|
|
11153
|
+
title: s.title,
|
|
11154
|
+
keywords: s.new_keywords || []
|
|
11155
|
+
}));
|
|
11156
|
+
return {
|
|
11157
|
+
targetLessonId,
|
|
11158
|
+
targetLessonIndex: targetIndex + 1,
|
|
11159
|
+
totalLessons: sessions.length,
|
|
11160
|
+
targetTitle: currentSession.title,
|
|
11161
|
+
targetKeywords: currentSession.new_keywords || [],
|
|
11162
|
+
compactMasterySet: {
|
|
11163
|
+
masteredKeywords,
|
|
11164
|
+
masteredConcepts
|
|
11165
|
+
},
|
|
11166
|
+
detailedBridge,
|
|
11167
|
+
boundaryPeek
|
|
11168
|
+
};
|
|
11169
|
+
}
|
|
11170
|
+
function renderHorizonPromptBlock(horizon) {
|
|
11171
|
+
const {
|
|
11172
|
+
targetLessonId,
|
|
11173
|
+
targetLessonIndex,
|
|
11174
|
+
totalLessons,
|
|
11175
|
+
targetTitle,
|
|
11176
|
+
targetKeywords,
|
|
11177
|
+
compactMasterySet,
|
|
11178
|
+
detailedBridge,
|
|
11179
|
+
boundaryPeek
|
|
11180
|
+
} = horizon;
|
|
11181
|
+
const lines = [
|
|
11182
|
+
`# \u{1F9ED} CURRICULUM HORIZON & SCAFFOLDING MATRIX (Lesson ${targetLessonIndex}/${totalLessons}: "${targetTitle}" [${targetLessonId}])`
|
|
11183
|
+
];
|
|
11184
|
+
if (compactMasterySet.masteredKeywords.length > 0 || compactMasterySet.masteredConcepts.length > 0) {
|
|
11185
|
+
const rawKeywords = compactMasterySet.masteredKeywords;
|
|
11186
|
+
const displayedKeywords = rawKeywords.length > 20 ? rawKeywords.slice(-20) : rawKeywords;
|
|
11187
|
+
const vocab = displayedKeywords.map((k) => `\`${k}\``).join(", ");
|
|
11188
|
+
const moreSuffix = rawKeywords.length > 20 ? ` *(+${rawKeywords.length - 20} earlier terms)*` : "";
|
|
11189
|
+
const concepts = compactMasterySet.masteredConcepts.length > 0 ? `
|
|
11190
|
+
- **Prior Concept Foundations:** ${compactMasterySet.masteredConcepts.slice(-3).join("; ")}` : "";
|
|
11191
|
+
lines.push(
|
|
11192
|
+
`
|
|
11193
|
+
## 1. \u{1F393} MASTERED VOCABULARY (Prior Lessons 1..${Math.max(1, targetLessonIndex - 3)} \u2014 Compact Set):`,
|
|
11194
|
+
`- **Mastered Terms & Syntax:** ${vocab || "(Core fundamentals)"}${moreSuffix}${concepts}`
|
|
11195
|
+
);
|
|
11196
|
+
} else if (targetLessonIndex === 1) {
|
|
11197
|
+
lines.push(
|
|
11198
|
+
`
|
|
11199
|
+
## 1. \u{1F393} PRIOR KNOWLEDGE FRONTIER:`,
|
|
11200
|
+
`- **Entry Point:** Inaugural lesson (Lesson 1/${totalLessons}). Students have no prior course vocabulary. All concepts must be introduced from baseline.`
|
|
11201
|
+
);
|
|
11202
|
+
}
|
|
11203
|
+
if (detailedBridge.length > 0) {
|
|
11204
|
+
lines.push(`
|
|
11205
|
+
## 2. \u{1F309} IMMEDIATE PREDECESSOR CONTEXT (Bridge Lessons):`);
|
|
11206
|
+
for (const bridge of detailedBridge) {
|
|
11207
|
+
lines.push(`### Lesson ${bridge.lessonIndex} (${bridge.lessonId}): ${bridge.title}`);
|
|
11208
|
+
if (bridge.proseObjective) {
|
|
11209
|
+
const obj = bridge.proseObjective.length > 120 ? bridge.proseObjective.slice(0, 117) + "..." : bridge.proseObjective;
|
|
11210
|
+
lines.push(`- **Objective:** ${obj}`);
|
|
11211
|
+
}
|
|
11212
|
+
if (bridge.keywords.length > 0) {
|
|
11213
|
+
lines.push(`- **Keywords:** ${bridge.keywords.map((k) => `\`${k}\``).join(", ")}`);
|
|
11214
|
+
}
|
|
11215
|
+
if (bridge.symbolLedger) {
|
|
11216
|
+
const parts = [];
|
|
11217
|
+
if (bridge.symbolLedger.primaryStructOrClass) {
|
|
11218
|
+
parts.push(`Primary Struct: \`${bridge.symbolLedger.primaryStructOrClass}\``);
|
|
11219
|
+
}
|
|
11220
|
+
if (bridge.symbolLedger.mainEntryFile) {
|
|
11221
|
+
parts.push(`Entry File: \`${bridge.symbolLedger.mainEntryFile}\``);
|
|
11222
|
+
}
|
|
11223
|
+
if (bridge.symbolLedger.keyVariables?.length) {
|
|
11224
|
+
parts.push(`Symbols: ${bridge.symbolLedger.keyVariables.map((v) => `\`${v}\``).join(", ")}`);
|
|
11225
|
+
}
|
|
11226
|
+
if (parts.length > 0) {
|
|
11227
|
+
lines.push(`- **Code Symbol Ledger:** ${parts.join(" | ")}`);
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
}
|
|
11231
|
+
lines.push(
|
|
11232
|
+
`\u{1F449} INSTRUCTION: Seamlessly connect the opening Hook and Guided Practice by referencing the student's recent work from Lesson ${detailedBridge[detailedBridge.length - 1]?.lessonIndex}.`
|
|
11233
|
+
);
|
|
11234
|
+
}
|
|
11235
|
+
const currentKeywordsStr = targetKeywords.length > 0 ? targetKeywords.map((k) => `\`${k}\``).join(", ") : "`Current Lesson Concepts`";
|
|
11236
|
+
lines.push(
|
|
11237
|
+
`
|
|
11238
|
+
## 3. \u{1F3AF} ALLOWED DESIGN SPACE (Positive-Only Allow List):`,
|
|
11239
|
+
`- **Student Toolkit:** Mastered Vocabulary + Immediate Bridge Keywords + Current Lesson Scope (${currentKeywordsStr}).`,
|
|
11240
|
+
`- **MANDATE:** ALL extension challenges (EXT), hands-on activities (ACT), and assessment questions (QUIZ) MUST be 100% solvable using ONLY items within this toolkit. Do NOT require unintroduced APIs!`
|
|
11241
|
+
);
|
|
11242
|
+
if (boundaryPeek.length > 0) {
|
|
11243
|
+
const nextLesson = boundaryPeek[0];
|
|
11244
|
+
const nextKeywords = nextLesson.keywords.length > 0 ? nextLesson.keywords.map((k) => `\`${k}\``).join(", ") : "upcoming features";
|
|
11245
|
+
lines.push(
|
|
11246
|
+
`
|
|
11247
|
+
## 4. \u{1F6A7} BOUNDARY (Next Lesson Peek):`,
|
|
11248
|
+
`- **Upcoming Lesson ${nextLesson.lessonIndex} ("${nextLesson.title}"):** Introduces ${nextKeywords}.`,
|
|
11249
|
+
`- **BOUNDARY MANDATE:** These concepts are NOT yet available to the student. Do not introduce or require these future concepts.`
|
|
11250
|
+
);
|
|
11251
|
+
}
|
|
11252
|
+
return lines.join("\n");
|
|
11253
|
+
}
|
|
11254
|
+
function validateHorizonCompliance(artifactContent, horizon) {
|
|
11255
|
+
const issues = [];
|
|
11256
|
+
const forbiddenMatches = [];
|
|
11257
|
+
if (!artifactContent || horizon.boundaryPeek.length === 0) {
|
|
11258
|
+
return { compliant: true, issues: [], forbiddenMatches: [] };
|
|
11259
|
+
}
|
|
11260
|
+
const contentLower = artifactContent.toLowerCase();
|
|
11261
|
+
const immediateNext = horizon.boundaryPeek[0];
|
|
11262
|
+
if (immediateNext && immediateNext.keywords.length > 0) {
|
|
11263
|
+
for (const kw of immediateNext.keywords) {
|
|
11264
|
+
const trimmed = kw.trim().toLowerCase();
|
|
11265
|
+
if (trimmed.length <= 2) continue;
|
|
11266
|
+
const pattern = new RegExp(`\\b${trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
11267
|
+
if (pattern.test(contentLower)) {
|
|
11268
|
+
const inMastered = horizon.compactMasterySet.masteredKeywords.some(
|
|
11269
|
+
(m) => m.toLowerCase() === trimmed
|
|
11270
|
+
);
|
|
11271
|
+
const inBridge = horizon.detailedBridge.some(
|
|
11272
|
+
(b) => b.keywords.some((bk) => bk.toLowerCase() === trimmed)
|
|
11273
|
+
);
|
|
11274
|
+
const inCurrent = horizon.targetKeywords.some(
|
|
11275
|
+
(ck) => ck.toLowerCase() === trimmed
|
|
11276
|
+
);
|
|
11277
|
+
if (!inMastered && !inBridge && !inCurrent) {
|
|
11278
|
+
forbiddenMatches.push(kw);
|
|
11279
|
+
issues.push(
|
|
11280
|
+
`Artifact contains boundary concept "${kw}" scheduled for future Lesson ${immediateNext.lessonIndex} ("${immediateNext.title}").`
|
|
11281
|
+
);
|
|
11282
|
+
}
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
11285
|
+
}
|
|
11286
|
+
return {
|
|
11287
|
+
compliant: issues.length === 0,
|
|
11288
|
+
issues,
|
|
11289
|
+
forbiddenMatches
|
|
11290
|
+
};
|
|
11291
|
+
}
|
|
11292
|
+
|
|
10691
11293
|
// src/services/contextBuilder.ts
|
|
10692
11294
|
var DEFAULT_LESSON_PRIORITIES = [
|
|
10693
11295
|
"Symbol & Identifier Ledger",
|
|
@@ -10697,6 +11299,13 @@ var DEFAULT_LESSON_PRIORITIES = [
|
|
|
10697
11299
|
"Learning Objectives & Evidence",
|
|
10698
11300
|
"Activity Sequence"
|
|
10699
11301
|
];
|
|
11302
|
+
var SATELLITE_LESSON_PRIORITIES = [
|
|
11303
|
+
"Artifact Contract",
|
|
11304
|
+
"A. Lesson Design Plan",
|
|
11305
|
+
"B. Lesson Flow",
|
|
11306
|
+
"Learning Objectives & Evidence",
|
|
11307
|
+
"Activity Sequence"
|
|
11308
|
+
];
|
|
10700
11309
|
var DEFAULT_KX_PRIORITIES = [
|
|
10701
11310
|
"Key Terms",
|
|
10702
11311
|
"Concept Narratives",
|
|
@@ -25502,6 +26111,47 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
25502
26111
|
|
|
25503
26112
|
[CURRICULUM FRAMEWORK EXCERPT]:
|
|
25504
26113
|
${buildFrameworkExcerptForLesson(framework, lessonId)}`;
|
|
26114
|
+
let horizon = null;
|
|
26115
|
+
let horizonBlock = "";
|
|
26116
|
+
try {
|
|
26117
|
+
let planObj = null;
|
|
26118
|
+
const planRaw = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
|
|
26119
|
+
if (planRaw) {
|
|
26120
|
+
try {
|
|
26121
|
+
planObj = JSON.parse(planRaw);
|
|
26122
|
+
} catch {
|
|
26123
|
+
}
|
|
26124
|
+
}
|
|
26125
|
+
horizon = await extractCurriculumHorizon({
|
|
26126
|
+
plan: planObj,
|
|
26127
|
+
frameworkMarkdown: framework,
|
|
26128
|
+
targetLessonId: lessonCode,
|
|
26129
|
+
storage,
|
|
26130
|
+
projectId
|
|
26131
|
+
});
|
|
26132
|
+
if (horizon) {
|
|
26133
|
+
horizonBlock = `
|
|
26134
|
+
|
|
26135
|
+
${renderHorizonPromptBlock(horizon)}`;
|
|
26136
|
+
}
|
|
26137
|
+
} catch (hErr) {
|
|
26138
|
+
console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
|
|
26139
|
+
}
|
|
26140
|
+
const buildGroundTruthBlock = () => {
|
|
26141
|
+
const parts = [];
|
|
26142
|
+
if (expositionContext) {
|
|
26143
|
+
parts.push(`### KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)
|
|
26144
|
+
${expositionContext}`);
|
|
26145
|
+
}
|
|
26146
|
+
if (effectiveRefPack) {
|
|
26147
|
+
parts.push(`### REFERENCE PACK GROUND TRUTH
|
|
26148
|
+
${effectiveRefPack}`);
|
|
26149
|
+
}
|
|
26150
|
+
return parts.length > 0 ? `
|
|
26151
|
+
|
|
26152
|
+
[GROUND TRUTH]:
|
|
26153
|
+
${parts.join("\n\n")}` : "";
|
|
26154
|
+
};
|
|
25505
26155
|
const glossaryBlock = glossaryContext ? `
|
|
25506
26156
|
|
|
25507
26157
|
[GLOSSARY TERMS (use these exact definitions)]:
|
|
@@ -25509,21 +26159,14 @@ ${glossaryContext}` : "";
|
|
|
25509
26159
|
const standardsBlock = standardsContext ? `
|
|
25510
26160
|
|
|
25511
26161
|
${standardsContext}` : "";
|
|
25512
|
-
const expositionBlock = expositionContext ? `
|
|
25513
|
-
|
|
25514
|
-
[KNOWLEDGE_EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict)]:
|
|
25515
|
-
${expositionContext}` : "";
|
|
25516
26162
|
const sessionSliceBlock = sessionSliceContext ? `
|
|
25517
26163
|
|
|
25518
26164
|
${sessionSliceContext}` : "";
|
|
25519
|
-
const assembleCommonContext = (
|
|
26165
|
+
const assembleCommonContext = (sg) => `${baseContextPrefix}
|
|
25520
26166
|
|
|
25521
26167
|
[CONTENT STYLE GUIDE EXCERPT]:
|
|
25522
|
-
${sg}
|
|
25523
|
-
|
|
25524
|
-
[REFERENCE PACK GROUND TRUTH]:
|
|
25525
|
-
${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}`;
|
|
25526
|
-
let commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
|
|
26168
|
+
${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBlock}${horizonBlock}`;
|
|
26169
|
+
let commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
25527
26170
|
const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
|
|
25528
26171
|
const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
|
|
25529
26172
|
if (commonContext.length > MAX_COMPOSITE_PROMPT_CHARS && effectiveRefPack.length > 1e3) {
|
|
@@ -25534,14 +26177,14 @@ ${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}`;
|
|
|
25534
26177
|
],
|
|
25535
26178
|
budget: 1e3
|
|
25536
26179
|
}).excerpt;
|
|
25537
|
-
commonContext = assembleCommonContext(
|
|
26180
|
+
commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
25538
26181
|
}
|
|
25539
26182
|
if (commonContext.length > MAX_COMPOSITE_PROMPT_CHARS && effectiveStyleGuide.length > 1e3) {
|
|
25540
26183
|
effectiveStyleGuide = buildSectionAwareExcerpt(styleGuide, {
|
|
25541
26184
|
priorities: ["Voice & Tone", "Standard Terminology (Glossary)"],
|
|
25542
26185
|
budget: 1e3
|
|
25543
26186
|
}).excerpt;
|
|
25544
|
-
commonContext = assembleCommonContext(
|
|
26187
|
+
commonContext = assembleCommonContext(effectiveStyleGuide);
|
|
25545
26188
|
}
|
|
25546
26189
|
onProgress?.("@content", `[CONTEXT] Composite prompt ready (${commonContext.length} chars, adaptive budget <= ${MAX_COMPOSITE_PROMPT_CHARS})`, {
|
|
25547
26190
|
type: "progress",
|
|
@@ -25892,7 +26535,7 @@ ${currentContent}` }],
|
|
|
25892
26535
|
});
|
|
25893
26536
|
}
|
|
25894
26537
|
const lessonExcerptResult = buildSectionAwareExcerpt(lessonContent, {
|
|
25895
|
-
priorities:
|
|
26538
|
+
priorities: SATELLITE_LESSON_PRIORITIES,
|
|
25896
26539
|
budget: 12e3,
|
|
25897
26540
|
sectionLanguageContract: slcMarkdown,
|
|
25898
26541
|
artifactType: "LESSON"
|
|
@@ -26082,6 +26725,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26082
26725
|
} catch {
|
|
26083
26726
|
}
|
|
26084
26727
|
const { executeSlideProductionWorkflow: executeSlideProductionWorkflow2 } = await Promise.resolve().then(() => (init_slideProductionWorkflow(), slideProductionWorkflow_exports));
|
|
26728
|
+
const slideGroundContext = `${commonContext}${symbolLedgerBlock}`;
|
|
26085
26729
|
const workflowResult = await executeSlideProductionWorkflow2({
|
|
26086
26730
|
lessonMarkdown: lessonContent || "",
|
|
26087
26731
|
lessonCode,
|
|
@@ -26090,6 +26734,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26090
26734
|
language: targetLang || "Vietnamese",
|
|
26091
26735
|
languageDirective,
|
|
26092
26736
|
headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
|
|
26737
|
+
groundContext: slideGroundContext,
|
|
26093
26738
|
satelliteContext,
|
|
26094
26739
|
runnerOptions,
|
|
26095
26740
|
onProgress: (agent, msg, meta) => {
|
|
@@ -26098,7 +26743,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26098
26743
|
});
|
|
26099
26744
|
let deckJson = workflowResult.deckJson;
|
|
26100
26745
|
const markdownWrapper = workflowResult.markdownWrapper;
|
|
26101
|
-
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" }]
|
|
26746
|
+
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" }]};
|
|
26102
26747
|
await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
|
|
26103
26748
|
producedArtifacts.push(`SLIDE_${lessonCode}.md`);
|
|
26104
26749
|
if (deckJson) {
|
|
@@ -26118,21 +26763,45 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26118
26763
|
}
|
|
26119
26764
|
}
|
|
26120
26765
|
}
|
|
26121
|
-
const
|
|
26122
|
-
const
|
|
26123
|
-
|
|
26124
|
-
|
|
26125
|
-
|
|
26126
|
-
|
|
26127
|
-
|
|
26128
|
-
|
|
26129
|
-
|
|
26130
|
-
|
|
26131
|
-
|
|
26132
|
-
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
|
|
26766
|
+
const structuralOk = Boolean(validation.valid && (validation.score ?? 100) >= 80);
|
|
26767
|
+
const structuralScore = validation.score ?? (structuralOk ? 95 : 50);
|
|
26768
|
+
const gateMode = gateModeFor(gates, "SLIDE");
|
|
26769
|
+
if (!structuralOk) {
|
|
26770
|
+
await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
|
|
26771
|
+
state: "rejected",
|
|
26772
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26773
|
+
contentHash: computeContentHash(markdownWrapper),
|
|
26774
|
+
review: {
|
|
26775
|
+
decision: "NEEDS_REVISION",
|
|
26776
|
+
reviewedBy: "@heuristic-linter",
|
|
26777
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26778
|
+
score: structuralScore,
|
|
26779
|
+
critique: `HTML Slide Deck failed schema validation: ${(validation.issues || []).map((i) => i.message).join("; ")}`
|
|
26780
|
+
}
|
|
26781
|
+
});
|
|
26782
|
+
onProgress?.("@reviewer", `\u26A0\uFE0F SLIDE structural pre-check FAILED (${structuralScore}/100) [html-deck schema issues]`);
|
|
26783
|
+
} else {
|
|
26784
|
+
await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
|
|
26785
|
+
state: gateMode === "LLM_JUDGE" ? "pending" : "completed",
|
|
26786
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26787
|
+
contentHash: computeContentHash(markdownWrapper)
|
|
26788
|
+
});
|
|
26789
|
+
onProgress?.("@reviewer", gateMode === "LLM_JUDGE" ? `\u2705 SLIDE structural pre-check PASS (${structuralScore}/100) \u2014 handing content review to LLM-as-Judge gate` : `\u2705 SLIDE structural pre-check PASS (${structuralScore}/100) [gate=${gateMode}, no LLM judge]`);
|
|
26790
|
+
const deckTextForJudge = Array.isArray(deckJson?.slides) ? deckJson.slides.map((s, i) => {
|
|
26791
|
+
const slotLines = Object.entries(s.slots || {}).map(([k, v]) => {
|
|
26792
|
+
if (Array.isArray(v)) return `- ${k}:
|
|
26793
|
+
${v.map((item) => typeof item === "object" ? ` - ${JSON.stringify(item)}` : ` - ${item}`).join("\n")}`;
|
|
26794
|
+
if (v && typeof v === "object") return `- ${k}: ${JSON.stringify(v, null, 1)}`;
|
|
26795
|
+
return `- ${k}: ${v}`;
|
|
26796
|
+
}).join("\n");
|
|
26797
|
+
return `## Slide ${i + 1} [${s.layoutId}]
|
|
26798
|
+
${slotLines}
|
|
26799
|
+
|
|
26800
|
+
Presenter Notes:
|
|
26801
|
+
${s.notes || "(none)"}`;
|
|
26802
|
+
}).join("\n\n") : String(markdownWrapper);
|
|
26803
|
+
await judgeSat("SLIDE", deckTextForJudge);
|
|
26804
|
+
}
|
|
26136
26805
|
} else {
|
|
26137
26806
|
const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
|
|
26138
26807
|
const templateScaffold = canonicalSlideTemplate || `---
|
|
@@ -26417,14 +27086,12 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
26417
27086
|
c3: "### Challenge 3: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 Extension Milestones (Level 1: Ninja \u2192 Level 2: Guru \u2192 Level 3: Master)",
|
|
26418
27087
|
meta: "## [REQUIRED] Metacognitive Deep Dive & Engineering Trade-offs"
|
|
26419
27088
|
};
|
|
26420
|
-
const
|
|
26421
|
-
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
|
|
26425
|
-
|
|
26426
|
-
- STRICTLY FORBIDDEN: NEVER introduce advanced mechanisms from future lessons (No complex event buses, No remote network/API calls, No persistent databases, No async concurrency, No out-of-scope hardware/sensors).` : `
|
|
26427
|
-
4. ZPD SCOPE: Focus on architectural edge cases, algorithmic optimization, and synthesis of learned concepts up to Lesson #${lessonIdx}.`;
|
|
27089
|
+
const zpdCeilingPrompt = horizon ? `
|
|
27090
|
+
4. \u{1F3AF} CURRICULUM HORIZON ALLOWED SPACE & BOUNDARY MANDATE:
|
|
27091
|
+
- Student's COMPLETE allowed toolkit = Mastered Vocabulary + Immediate Bridge + Current Lesson Scope (${horizon.targetKeywords.join(", ") || "Current Lesson Concepts"}).
|
|
27092
|
+
- All extension challenges MUST be 100% solvable using ONLY items within this toolkit.
|
|
27093
|
+
${horizon.boundaryPeek.length > 0 ? `- STRICT BOUNDARY: Upcoming Lesson ${horizon.boundaryPeek[0].lessonIndex} introduces "${horizon.boundaryPeek[0].title}" (${horizon.boundaryPeek[0].keywords.join(", ")}). These are STRICTLY OUT OF SCOPE. Do NOT introduce or require these future APIs.` : ""}` : `
|
|
27094
|
+
4. ZPD SCOPE: Focus on architectural edge cases, algorithmic optimization, and creative variations within current and past lesson concepts.`;
|
|
26428
27095
|
const extPrompt = `You are @activity (Lead STEM Specialist & Differentiated Extension Designer).
|
|
26429
27096
|
Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, author the advanced extension challenge: \`EXT_${lessonCode}.md\`.
|
|
26430
27097
|
|
|
@@ -26996,6 +27663,20 @@ async function generateSingleArtifact(req) {
|
|
|
26996
27663
|
- Entry Source File: \`${symbolLedger.entryFileName || symbolLedger.primarySymbol + ".swift"}\`
|
|
26997
27664
|
- Key Identifiers to inherit: ${symbolLedger.keySymbols.map((s) => `\`${s}\``).join(", ")}
|
|
26998
27665
|
- INVARIANT: You MUST use these exact identifier names in all code examples, exercises, and starter templates. Do NOT invent conflicting struct or class names.` : "";
|
|
27666
|
+
let horizon = contextSot.horizon || null;
|
|
27667
|
+
if (!horizon && (contextSot.plan || contextSot.framework) && lessonId) {
|
|
27668
|
+
try {
|
|
27669
|
+
horizon = await extractCurriculumHorizon({
|
|
27670
|
+
plan: contextSot.plan,
|
|
27671
|
+
frameworkMarkdown: contextSot.framework,
|
|
27672
|
+
targetLessonId: lessonId
|
|
27673
|
+
});
|
|
27674
|
+
} catch {
|
|
27675
|
+
}
|
|
27676
|
+
}
|
|
27677
|
+
const horizonPrompt = horizon ? `
|
|
27678
|
+
|
|
27679
|
+
${renderHorizonPromptBlock(horizon)}` : "";
|
|
26999
27680
|
const systemPrompt = `You are ${meta.persona}, an expert Curriculum & Pedagogical Specialist in the Curriculum OS Multi-Agent Team.
|
|
27000
27681
|
Your task is to author a canonical, publication-grade learning artifact of type "${artifactType.toUpperCase()}".
|
|
27001
27682
|
|
|
@@ -27009,7 +27690,7 @@ ${headingDirective}
|
|
|
27009
27690
|
6. DOMAIN & TECH STACK GUARDRAILS:
|
|
27010
27691
|
${domainGuardrail}
|
|
27011
27692
|
${symbolLedgerPrompt}
|
|
27012
|
-
8. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}${mediaPolicy ? `
|
|
27693
|
+
8. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}${horizonPrompt}${mediaPolicy ? `
|
|
27013
27694
|
|
|
27014
27695
|
IMAGE PLANNING (media ledger) \u2014 QUOTA GEN: t\u1ED1i \u0111a ${mediaPolicy.maxGenImagesPerArtifact} \u1EA3nh AI cho artifact n\xE0y (\u1EA3nh search kho kh\xF4ng gi\u1EDBi h\u1EA1n).
|
|
27015
27696
|
Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u1ED3 kh\xE1i ni\u1EC7m, step-by-step visual), ch\xE8n placeholder \u0111\xFAng format [IMAGE: slug] (slug ch\u1EEF th\u01B0\u1EDDng-g\u1EA1ch ngang, unique trong b\xE0i, vd img-for-loop-diagram) T\u1EA0I \u0110\xDANG CH\u1ED6 c\u1EA7n \u1EA3nh, tr\xEAn d\xF2ng ri\xEAng. KH\xD4NG t\u1EF1 sinh \u1EA3nh, KH\xD4NG d\xF9ng markdown image \u2014 placeholder s\u1EBD \u0111\u01B0\u1EE3c thay b\u1EB1ng \u1EA3nh th\u1EADt sau khi Media Curator duy\u1EC7t. Ch\u1EC9 \u0111\u1EB7t cho \u1EA3nh TH\u1EF0C S\u1EF0 c\u1EA7n thi\u1EBFt.` : ""}`;
|
|
@@ -27490,13 +28171,9 @@ ${includeUnitTests ? "- Unit Testing: Provide automated assertions or test runne
|
|
|
27490
28171
|
- Exact quantities calculated for ${studentCount} students.
|
|
27491
28172
|
- Component specifications, estimated unit cost, and affordable alternatives.`;
|
|
27492
28173
|
case "ext": {
|
|
27493
|
-
const
|
|
27494
|
-
|
|
27495
|
-
|
|
27496
|
-
const zpdCeilingRule = isEarlyLesson ? `- \u{1F6D1} ZPD COMPLEXITY CEILING (FOUNDATIONAL LESSON #${lessonIdx}):
|
|
27497
|
-
\u2022 SCOPE LOCK: This is an early foundational lesson. The extension challenge MUST focus on creative design variations, parameterization, or edge cases of the current lesson concepts ONLY.
|
|
27498
|
-
\u2022 STRICTLY FORBIDDEN: NEVER introduce advanced mechanisms from future lessons (No complex event buses, No remote network/API calls, No persistent databases, No async concurrency, No out-of-scope hardware/sensors).` : `- \u{1F680} ZPD ADVANCED CHALLENGE (LESSON #${lessonIdx}):
|
|
27499
|
-
\u2022 Focus on architectural edge cases, algorithmic optimization, and synthesis of learned concepts up to Lesson #${lessonIdx}.`;
|
|
28174
|
+
const zpdCeilingRule = `- \u{1F3AF} CURRICULUM HORIZON ALLOWED SPACE & BOUNDARY MANDATE:
|
|
28175
|
+
\u2022 SCOPE LOCK: All extension challenges MUST be 100% solvable using ONLY items from the student's Mastered Vocabulary and Current Lesson Scope.
|
|
28176
|
+
\u2022 STRICTLY FORBIDDEN: NEVER introduce unintroduced APIs or mechanisms from upcoming lessons (No complex event buses, No remote network/API calls, No persistent databases, No async concurrency, No out-of-scope hardware/sensors unless explicitly part of the allowed toolkit).`;
|
|
27500
28177
|
return `
|
|
27501
28178
|
### \u{1F680} EXTENSION CHALLENGE ARCHITECTURE INVARIANTS:
|
|
27502
28179
|
${zpdCeilingRule}
|
|
@@ -31273,6 +31950,7 @@ exports.RoadmapInputSchema = RoadmapInputSchema;
|
|
|
31273
31950
|
exports.RotationStationSchema = RotationStationSchema;
|
|
31274
31951
|
exports.RubricCriteriaSchema = RubricCriteriaSchema;
|
|
31275
31952
|
exports.RubricSchema = RubricSchema;
|
|
31953
|
+
exports.SATELLITE_LESSON_PRIORITIES = SATELLITE_LESSON_PRIORITIES;
|
|
31276
31954
|
exports.SCIENCE_LAB_TEMPLATE = SCIENCE_LAB_TEMPLATE;
|
|
31277
31955
|
exports.SELF_LAB_TEMPLATE = SELF_LAB_TEMPLATE;
|
|
31278
31956
|
exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
|
|
@@ -31322,6 +32000,7 @@ exports.analystTools = analystTools;
|
|
|
31322
32000
|
exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
|
|
31323
32001
|
exports.assertAcyclic = assertAcyclic;
|
|
31324
32002
|
exports.assessorTools = assessorTools;
|
|
32003
|
+
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
31325
32004
|
exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
|
|
31326
32005
|
exports.auditQualityReport = auditQualityReport;
|
|
31327
32006
|
exports.buildActivityPrompt = buildActivityPrompt;
|
|
@@ -31369,6 +32048,7 @@ exports.createStreamChunkExtractor = createStreamChunkExtractor;
|
|
|
31369
32048
|
exports.curateMediaLedger = curateMediaLedger;
|
|
31370
32049
|
exports.designerTools = designerTools;
|
|
31371
32050
|
exports.detectProjectPedagogy = detectProjectPedagogy;
|
|
32051
|
+
exports.emitUsage = emitUsage;
|
|
31372
32052
|
exports.ensureExpositionForLesson = ensureExpositionForLesson;
|
|
31373
32053
|
exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
|
|
31374
32054
|
exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
|
|
@@ -31376,6 +32056,8 @@ exports.executeCurriculumCommand = executeCurriculumCommand;
|
|
|
31376
32056
|
exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
|
|
31377
32057
|
exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
|
|
31378
32058
|
exports.expositionCacheKey = expositionCacheKey;
|
|
32059
|
+
exports.extractCurriculumHorizon = extractCurriculumHorizon;
|
|
32060
|
+
exports.extractJsonArray = extractJsonArray;
|
|
31379
32061
|
exports.extractScopeSequenceRows = extractScopeSequenceRows;
|
|
31380
32062
|
exports.extractSectionHeadingsFromSLC = extractSectionHeadingsFromSLC;
|
|
31381
32063
|
exports.extractSessionSlice = extractSessionSlice;
|
|
@@ -31434,6 +32116,7 @@ exports.loadSotTemplate = loadSotTemplate;
|
|
|
31434
32116
|
exports.normalizePlanningGraph = normalizePlanningGraph;
|
|
31435
32117
|
exports.normalizeSlcContract = normalizeSlcContract;
|
|
31436
32118
|
exports.packagerTools = packagerTools;
|
|
32119
|
+
exports.parseAllSessions = parseAllSessions;
|
|
31437
32120
|
exports.parseGateSettings = parseGateSettings;
|
|
31438
32121
|
exports.parseQuizMarkdown = parseQuizMarkdown;
|
|
31439
32122
|
exports.parseRoadmapJsonToProjectPayload = parseRoadmapJsonToProjectPayload;
|
|
@@ -31443,6 +32126,7 @@ exports.publishToGitHub = publishToGitHub;
|
|
|
31443
32126
|
exports.publishToSupabase = publishToSupabase;
|
|
31444
32127
|
exports.rankGenCandidates = rankGenCandidates;
|
|
31445
32128
|
exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
|
|
32129
|
+
exports.renderHorizonPromptBlock = renderHorizonPromptBlock;
|
|
31446
32130
|
exports.renderMediaPlaceholder = renderMediaPlaceholder;
|
|
31447
32131
|
exports.researcherTools = researcherTools;
|
|
31448
32132
|
exports.resolveGateSettings = resolveGateSettings;
|
|
@@ -31479,6 +32163,8 @@ exports.uploadAssetToBucket = uploadAssetToBucket;
|
|
|
31479
32163
|
exports.validateArtifactDependencies = validateArtifactDependencies;
|
|
31480
32164
|
exports.validateCurriculumPlan = validateCurriculumPlan;
|
|
31481
32165
|
exports.validateFrameworkPack = validateFrameworkPack;
|
|
32166
|
+
exports.validateHorizonCompliance = validateHorizonCompliance;
|
|
32167
|
+
exports.validateHybridDeckSlides = validateHybridDeckSlides;
|
|
31482
32168
|
exports.validateMarkdownTables = validateMarkdownTables;
|
|
31483
32169
|
exports.validateMermaidSyntax = validateMermaidSyntax;
|
|
31484
32170
|
exports.withAutoRepair = withAutoRepair;
|