@thanh01.pmt/curriculum-kit 1.4.22 → 1.4.24
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 +234 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.mjs +234 -23
- 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
|
@@ -165,6 +165,20 @@ interface ProduceLessonOptions {
|
|
|
165
165
|
mode?: 'search_only' | 'gen_only' | 'auto';
|
|
166
166
|
};
|
|
167
167
|
approvedArtifacts?: string[];
|
|
168
|
+
/**
|
|
169
|
+
* 5-Whys RC-canonicality: project briefing feeding the canonical PRODUCT_SPEC
|
|
170
|
+
* block (platform, hardware, deliverable template). Injected identically into
|
|
171
|
+
* every artifact's ground truth so no artifact re-decides product shape.
|
|
172
|
+
*/
|
|
173
|
+
briefing?: {
|
|
174
|
+
techStack?: string;
|
|
175
|
+
coreTechnology?: string;
|
|
176
|
+
topic?: string;
|
|
177
|
+
hardwarePlatform?: string;
|
|
178
|
+
studentEquipment?: string;
|
|
179
|
+
classDynamic?: string;
|
|
180
|
+
language?: string;
|
|
181
|
+
};
|
|
168
182
|
/**
|
|
169
183
|
* P46 RC2: host-provided inference override (Vercel AI SDK gateway).
|
|
170
184
|
* When supplied, ALL lesson/satellite LLM calls route through it — the kit's
|
|
@@ -658,6 +672,12 @@ interface GenerateExpositionOptions {
|
|
|
658
672
|
/** REFERENCE_PACK.md raw content — technical ground truth (versions, APIs).
|
|
659
673
|
* 5-Whys RC-vacuum: without it the KX model regresses to pretrained facts. */
|
|
660
674
|
refPack?: string;
|
|
675
|
+
/** 5-Whys RC-canonicality: canonical PRODUCT_SPEC fields so KX does not
|
|
676
|
+
* re-decide the deliverable shape that LESSON/satellites already share. */
|
|
677
|
+
productSpec?: {
|
|
678
|
+
deliverableTemplate?: string;
|
|
679
|
+
primaryLanguage?: string;
|
|
680
|
+
};
|
|
661
681
|
}
|
|
662
682
|
interface ExpositionResult {
|
|
663
683
|
relPath: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -165,6 +165,20 @@ interface ProduceLessonOptions {
|
|
|
165
165
|
mode?: 'search_only' | 'gen_only' | 'auto';
|
|
166
166
|
};
|
|
167
167
|
approvedArtifacts?: string[];
|
|
168
|
+
/**
|
|
169
|
+
* 5-Whys RC-canonicality: project briefing feeding the canonical PRODUCT_SPEC
|
|
170
|
+
* block (platform, hardware, deliverable template). Injected identically into
|
|
171
|
+
* every artifact's ground truth so no artifact re-decides product shape.
|
|
172
|
+
*/
|
|
173
|
+
briefing?: {
|
|
174
|
+
techStack?: string;
|
|
175
|
+
coreTechnology?: string;
|
|
176
|
+
topic?: string;
|
|
177
|
+
hardwarePlatform?: string;
|
|
178
|
+
studentEquipment?: string;
|
|
179
|
+
classDynamic?: string;
|
|
180
|
+
language?: string;
|
|
181
|
+
};
|
|
168
182
|
/**
|
|
169
183
|
* P46 RC2: host-provided inference override (Vercel AI SDK gateway).
|
|
170
184
|
* When supplied, ALL lesson/satellite LLM calls route through it — the kit's
|
|
@@ -658,6 +672,12 @@ interface GenerateExpositionOptions {
|
|
|
658
672
|
/** REFERENCE_PACK.md raw content — technical ground truth (versions, APIs).
|
|
659
673
|
* 5-Whys RC-vacuum: without it the KX model regresses to pretrained facts. */
|
|
660
674
|
refPack?: string;
|
|
675
|
+
/** 5-Whys RC-canonicality: canonical PRODUCT_SPEC fields so KX does not
|
|
676
|
+
* re-decide the deliverable shape that LESSON/satellites already share. */
|
|
677
|
+
productSpec?: {
|
|
678
|
+
deliverableTemplate?: string;
|
|
679
|
+
primaryLanguage?: string;
|
|
680
|
+
};
|
|
661
681
|
}
|
|
662
682
|
interface ExpositionResult {
|
|
663
683
|
relPath: string;
|
package/dist/index.mjs
CHANGED
|
@@ -11612,6 +11612,137 @@ function extractSymbolLedger(lessonMarkdown) {
|
|
|
11612
11612
|
return result;
|
|
11613
11613
|
}
|
|
11614
11614
|
|
|
11615
|
+
// src/services/artifactValidators.ts
|
|
11616
|
+
var CJK_RE = /[\u3400-\u4DBF\u4E00-\u9FFF\u3040-\u30FF\uAC00-\uD7AF]/g;
|
|
11617
|
+
function scanCjkLeaks(content) {
|
|
11618
|
+
const matches = content.match(CJK_RE);
|
|
11619
|
+
if (!matches || matches.length === 0) return null;
|
|
11620
|
+
const lines = [];
|
|
11621
|
+
for (const line of content.split("\n")) {
|
|
11622
|
+
if (CJK_RE.test(line)) lines.push(line.trim().slice(0, 160));
|
|
11623
|
+
}
|
|
11624
|
+
return {
|
|
11625
|
+
code: "cjk-leak",
|
|
11626
|
+
detail: `Ph\xE1t hi\u1EC7n ${matches.length} k\xFD t\u1EF1 CJK (Trung/Nh\u1EADt/H\xE0n) trong artifact ng\xF4n ng\u1EEF Vi\u1EC7t/Anh. B\u1ECB c\u1EA5m tuy\u1EC7t \u0111\u1ED1i.`,
|
|
11627
|
+
evidence: lines.slice(0, 5)
|
|
11628
|
+
};
|
|
11629
|
+
}
|
|
11630
|
+
var VERSION_CLAIM_RE = /\b([A-Z][A-Za-z+#.\-]{1,24})\s+(\d{1,2}(?:\.\d{1,2})?)\b/g;
|
|
11631
|
+
function checkVersionGroundTruth(content, refPack) {
|
|
11632
|
+
if (!refPack || !refPack.trim()) return null;
|
|
11633
|
+
const packLower = refPack.toLowerCase();
|
|
11634
|
+
const claims = /* @__PURE__ */ new Map();
|
|
11635
|
+
for (const m of content.matchAll(VERSION_CLAIM_RE)) {
|
|
11636
|
+
const tool = m[1];
|
|
11637
|
+
if (claims.size === 0 || !claims.has(tool)) claims.set(tool, m[2]);
|
|
11638
|
+
}
|
|
11639
|
+
const contradictions = [];
|
|
11640
|
+
for (const [tool, claimed] of claims) {
|
|
11641
|
+
const toolRe = new RegExp(`\\b${tool.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s+(\\d{1,2}(?:\\.\\d{1,2})?)\\b`, "i");
|
|
11642
|
+
const packMatch = packLower.match(toolRe);
|
|
11643
|
+
if (packMatch && packMatch[1] !== claimed) {
|
|
11644
|
+
contradictions.push(`${tool}: artifact n\xF3i ${claimed}, REFERENCE_PACK ch\u1ED1t ${packMatch[1]}`);
|
|
11645
|
+
}
|
|
11646
|
+
}
|
|
11647
|
+
if (contradictions.length === 0) return null;
|
|
11648
|
+
return {
|
|
11649
|
+
code: "version-contradiction",
|
|
11650
|
+
detail: `Version m\xE2u thu\u1EABn v\u1EDBi ground truth REFERENCE_PACK: ${contradictions.join("; ")}. S\u1EEDa theo b\u1EA3n trong REFERENCE_PACK.`,
|
|
11651
|
+
evidence: contradictions
|
|
11652
|
+
};
|
|
11653
|
+
}
|
|
11654
|
+
function checkScopeDrift(content, scopedKeywords, tolerance = 0) {
|
|
11655
|
+
const conceptHeaders = /* @__PURE__ */ new Set();
|
|
11656
|
+
for (const m of content.matchAll(/\*\*([^*\n]{3,60}?)\s*\((?:WHAT|CIO|SIO|ULO)[^)]*\):\*\*/g)) {
|
|
11657
|
+
conceptHeaders.add(m[1].toLowerCase());
|
|
11658
|
+
}
|
|
11659
|
+
if (conceptHeaders.size === 0) return null;
|
|
11660
|
+
const drift = [];
|
|
11661
|
+
for (const header of conceptHeaders) {
|
|
11662
|
+
const hit = scopedKeywords.some((kw) => {
|
|
11663
|
+
const k = kw.toLowerCase().trim();
|
|
11664
|
+
if (!k) return false;
|
|
11665
|
+
const tokens = k.split(/\s+/);
|
|
11666
|
+
const present = tokens.filter((t) => header.includes(t)).length;
|
|
11667
|
+
return present >= Math.max(1, tokens.length - tolerance);
|
|
11668
|
+
});
|
|
11669
|
+
if (!hit) drift.push(header);
|
|
11670
|
+
}
|
|
11671
|
+
if (drift.length === 0) return null;
|
|
11672
|
+
return {
|
|
11673
|
+
code: "scope-drift",
|
|
11674
|
+
detail: `C\xE1c kh\xE1i ni\u1EC7m sau KH\xD4NG thu\u1ED9c scope c\u1EE7a bu\u1ED5i h\u1ECDc (kh\xF4ng c\xF3 trong new_keywords/graph node keywords): ${drift.join(" | ")}. Xo\xE1 ho\xE0n to\xE0n ho\u1EB7c thay b\u1EB1ng kh\xE1i ni\u1EC7m trong scope.`,
|
|
11675
|
+
evidence: drift
|
|
11676
|
+
};
|
|
11677
|
+
}
|
|
11678
|
+
function validateArtifactDeterministic(input) {
|
|
11679
|
+
const issues = [];
|
|
11680
|
+
const cjk = scanCjkLeaks(input.content);
|
|
11681
|
+
if (cjk) issues.push(cjk);
|
|
11682
|
+
if (input.refPack) {
|
|
11683
|
+
const ver = checkVersionGroundTruth(input.content, input.refPack);
|
|
11684
|
+
if (ver) issues.push(ver);
|
|
11685
|
+
}
|
|
11686
|
+
if (input.scopedKeywords && input.scopedKeywords.length > 0) {
|
|
11687
|
+
const scope = checkScopeDrift(input.content, input.scopedKeywords);
|
|
11688
|
+
if (scope) issues.push(scope);
|
|
11689
|
+
}
|
|
11690
|
+
return { ok: issues.length === 0, issues };
|
|
11691
|
+
}
|
|
11692
|
+
function validatorRepairPrompt(issues) {
|
|
11693
|
+
return [
|
|
11694
|
+
"DETERMINISTIC VALIDATOR FAILURES (mechanically detected \u2014 non-negotiable, fix ALL):",
|
|
11695
|
+
...issues.map((i, n) => `${n + 1}. [${i.code}] ${i.detail}${i.evidence.length ? `
|
|
11696
|
+
Evidence: ${i.evidence.join(" | ").slice(0, 400)}` : ""}`)
|
|
11697
|
+
].join("\n");
|
|
11698
|
+
}
|
|
11699
|
+
|
|
11700
|
+
// src/services/productSpec.ts
|
|
11701
|
+
function extractToolchainFacts(refPack) {
|
|
11702
|
+
const { excerpt } = buildSectionAwareExcerpt(refPack, {
|
|
11703
|
+
priorities: ["Technical Overview & Architecture Blueprint", "Hardware Pinout & Wiring Configuration Matrix"],
|
|
11704
|
+
budget: 4e3
|
|
11705
|
+
});
|
|
11706
|
+
if (!excerpt) return [];
|
|
11707
|
+
const factLines = [];
|
|
11708
|
+
for (const rawLine of excerpt.split("\n")) {
|
|
11709
|
+
const line = rawLine.trim();
|
|
11710
|
+
if (!line || line.startsWith("#")) continue;
|
|
11711
|
+
if (/\d{1,2}(?:\.\d{1,2})?/.test(line) || /Xcode|Swift|macOS|iOS|SDK|Node|Python|JDK|\.NET/i.test(line)) {
|
|
11712
|
+
factLines.push(line.replace(/^[-*•]\s*/, "- "));
|
|
11713
|
+
}
|
|
11714
|
+
if (factLines.length >= 8) break;
|
|
11715
|
+
}
|
|
11716
|
+
return factLines;
|
|
11717
|
+
}
|
|
11718
|
+
function buildProductSpecBlock(input) {
|
|
11719
|
+
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
|
+
const toolchainFacts = input.refPack ? extractToolchainFacts(input.refPack) : [];
|
|
11723
|
+
const lines = [];
|
|
11724
|
+
lines.push("[PRODUCT SPEC \u2014 CANONICAL, BINDING FOR EVERY ARTIFACT OF THIS PROJECT]:");
|
|
11725
|
+
lines.push("All decisions below are PROJECT-WIDE. Every artifact (LESSON, satellites, KX)");
|
|
11726
|
+
lines.push("must describe the SAME product. Contradicting or re-deciding any line here is a");
|
|
11727
|
+
lines.push("consistency failure \u2014 if a needed decision is absent, stay GENERIC, never invent.");
|
|
11728
|
+
lines.push("");
|
|
11729
|
+
if (platform) {
|
|
11730
|
+
lines.push(`- Primary technology: ${platform}`);
|
|
11731
|
+
}
|
|
11732
|
+
if (hardware) {
|
|
11733
|
+
lines.push(`- Hardware / classroom setup: ${hardware}`);
|
|
11734
|
+
}
|
|
11735
|
+
if (input.overrides?.deliverableTemplate) {
|
|
11736
|
+
lines.push(`- Deliverable template: ${input.overrides.deliverableTemplate}`);
|
|
11737
|
+
}
|
|
11738
|
+
if (toolchainFacts.length > 0) {
|
|
11739
|
+
lines.push("- Toolchain & versions (ground truth):");
|
|
11740
|
+
for (const f of toolchainFacts) lines.push(` ${f}`);
|
|
11741
|
+
}
|
|
11742
|
+
if (lines.length <= 5) return "";
|
|
11743
|
+
return lines.join("\n");
|
|
11744
|
+
}
|
|
11745
|
+
|
|
11615
11746
|
// src/services/knowledgeExpositionService.ts
|
|
11616
11747
|
var EXPOSITION_REL = (lessonCode) => "_sot/expositions/" + lessonCode + ".md";
|
|
11617
11748
|
var ExpositionApprovalError = class extends Error {
|
|
@@ -11729,7 +11860,12 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11729
11860
|
}
|
|
11730
11861
|
const targetLanguage = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
11731
11862
|
const originalSystemPrompt = buildSystemPrompt(targetLanguage, techStack, hardwarePlatform);
|
|
11732
|
-
const
|
|
11863
|
+
const productSpecBlock = buildProductSpecBlock({
|
|
11864
|
+
refPack,
|
|
11865
|
+
briefing: techStack || hardwarePlatform ? { techStack, hardwarePlatform } : void 0,
|
|
11866
|
+
overrides: options.productSpec
|
|
11867
|
+
});
|
|
11868
|
+
const originalUserPrompt = buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown, refPack) + (productSpecBlock ? "\n\n" + productSpecBlock : "");
|
|
11733
11869
|
const content = (await llmFn(
|
|
11734
11870
|
originalSystemPrompt,
|
|
11735
11871
|
originalUserPrompt
|
|
@@ -11763,7 +11899,17 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11763
11899
|
const parsed = JSON.parse(match[0]);
|
|
11764
11900
|
return { verdict: parsed.verdict ?? "NEEDS_REVISION", score: parsed.score ?? 0, critique: parsed.critique ?? "" };
|
|
11765
11901
|
};
|
|
11766
|
-
|
|
11902
|
+
const scopedKeywords = [
|
|
11903
|
+
...session.new_keywords,
|
|
11904
|
+
...glossary.map((g) => g.term)
|
|
11905
|
+
];
|
|
11906
|
+
const runDeterministic = (candidate) => validateArtifactDeterministic({
|
|
11907
|
+
content: candidate,
|
|
11908
|
+
refPack,
|
|
11909
|
+
scopedKeywords
|
|
11910
|
+
});
|
|
11911
|
+
let det = runDeterministic(finalContent);
|
|
11912
|
+
let verdict = det.ok ? await judgeOnce(finalContent) : { verdict: "NEEDS_REVISION", score: 0, critique: validatorRepairPrompt(det.issues) };
|
|
11767
11913
|
if (verdict.verdict !== "APPROVED") {
|
|
11768
11914
|
const repairPrompt = [
|
|
11769
11915
|
originalUserPrompt,
|
|
@@ -11771,11 +11917,12 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11771
11917
|
"--- YOUR PREVIOUS DRAFT (REJECTED, fix ALL issues below) ---",
|
|
11772
11918
|
finalContent,
|
|
11773
11919
|
"",
|
|
11774
|
-
"---
|
|
11920
|
+
"--- CRITIQUE (fix ALL issues, output the COMPLETE corrected document) ---",
|
|
11775
11921
|
verdict.critique
|
|
11776
11922
|
].join("\n");
|
|
11777
11923
|
finalContent = (await llmFn(originalSystemPrompt, repairPrompt)).trim();
|
|
11778
|
-
|
|
11924
|
+
det = runDeterministic(finalContent);
|
|
11925
|
+
verdict = det.ok ? await judgeOnce(finalContent) : { verdict: "NEEDS_REVISION", score: 0, critique: validatorRepairPrompt(det.issues) };
|
|
11779
11926
|
}
|
|
11780
11927
|
if (verdict.verdict !== "APPROVED") {
|
|
11781
11928
|
throw new Error("EXPOSITION failed judge for " + lessonCode + " (verdict " + verdict.verdict + ", score " + verdict.score + "): " + verdict.critique);
|
|
@@ -26044,7 +26191,22 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
26044
26191
|
const framework = await storage.readSotDocument(projectId, "CURRICULUM_FRAMEWORK.md") || "";
|
|
26045
26192
|
const styleGuide = await storage.readSotDocument(projectId, "CONTENT_STYLE_GUIDE.md") || "";
|
|
26046
26193
|
const refPack = await storage.readSotDocument(projectId, "REFERENCE_PACK.md") || "";
|
|
26194
|
+
let currentPlanHash;
|
|
26195
|
+
try {
|
|
26196
|
+
const planRaw0 = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
|
|
26197
|
+
if (planRaw0) currentPlanHash = JSON.parse(planRaw0).plan_hash;
|
|
26198
|
+
} catch {
|
|
26199
|
+
}
|
|
26047
26200
|
await storage.readSotDocument(projectId, "LEARNER_PROFILE.md") || "";
|
|
26201
|
+
const isStaleVsPlan = (content) => {
|
|
26202
|
+
if (!content || !currentPlanHash) return false;
|
|
26203
|
+
const m = content.match(/plan_hash:\s*"([0-9a-f]+)"/);
|
|
26204
|
+
return !!m && m[1] !== currentPlanHash;
|
|
26205
|
+
};
|
|
26206
|
+
const stampPlanHash = (content) => {
|
|
26207
|
+
if (!currentPlanHash || content.includes('plan_hash: "' + currentPlanHash + '"')) return content;
|
|
26208
|
+
return content + '\n---\nplan_hash: "' + currentPlanHash + '"\n- **Plan reference:** ' + currentPlanHash + "\n";
|
|
26209
|
+
};
|
|
26048
26210
|
const slcMarkdown = options.sectionLanguageContract || await storage.readSotDocument(projectId, "SECTION_LANGUAGE_CONTRACT.md") || "";
|
|
26049
26211
|
let targetLang = options.targetLanguage;
|
|
26050
26212
|
if (!targetLang) {
|
|
@@ -26204,6 +26366,10 @@ ${expositionContext}`);
|
|
|
26204
26366
|
parts.push(`### REFERENCE PACK GROUND TRUTH
|
|
26205
26367
|
${effectiveRefPack}`);
|
|
26206
26368
|
}
|
|
26369
|
+
const productSpec = buildProductSpecBlock({ refPack, briefing: options.briefing });
|
|
26370
|
+
if (productSpec) {
|
|
26371
|
+
parts.push(productSpec);
|
|
26372
|
+
}
|
|
26207
26373
|
return parts.length > 0 ? `
|
|
26208
26374
|
|
|
26209
26375
|
[GROUND TRUTH]:
|
|
@@ -26251,7 +26417,10 @@ ${sg}${standardsBlock}${glossaryBlock}${buildGroundTruthBlock()}${sessionSliceBl
|
|
|
26251
26417
|
const lessonRelPath = `_content/${unitCode}/LESSON_${lessonCode}.md`;
|
|
26252
26418
|
const existingLessonContent = await storage.readArtifact(projectId, lessonRelPath);
|
|
26253
26419
|
let lessonContent = existingLessonContent || "";
|
|
26254
|
-
if (artifactScope.includes("LESSON") && (!existingLessonContent || force)) {
|
|
26420
|
+
if (artifactScope.includes("LESSON") && (!existingLessonContent || force || isStaleVsPlan(existingLessonContent))) {
|
|
26421
|
+
if (existingLessonContent && !force && isStaleVsPlan(existingLessonContent)) {
|
|
26422
|
+
onProgress?.("@content", `[STALENESS] LESSON_${lessonCode} was generated under a different plan_hash \u2014 regenerating against the current plan.`);
|
|
26423
|
+
}
|
|
26255
26424
|
onProgress?.("@content", `[1/4] Authoring Master Lesson Plan (${pedagogyLabel}): \`LESSON_${lessonCode}.md\` (${lessonTitle})...`);
|
|
26256
26425
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26257
26426
|
const mediaDirective = options.mediaPolicy && options.mediaPolicy.maxGenImagesPerArtifact > 0 ? `
|
|
@@ -26322,7 +26491,7 @@ Fix ALL issues above and output the complete corrected document.` }],
|
|
|
26322
26491
|
} else {
|
|
26323
26492
|
lessonContent = lintReport.autoFixedContent || rawLesson;
|
|
26324
26493
|
}
|
|
26325
|
-
await storage.saveArtifact(projectId, lessonRelPath, lessonContent);
|
|
26494
|
+
await storage.saveArtifact(projectId, lessonRelPath, stampPlanHash(lessonContent));
|
|
26326
26495
|
producedArtifacts.push(`LESSON_${lessonCode}.md`);
|
|
26327
26496
|
}
|
|
26328
26497
|
const contentHash = lessonContent ? computeContentHash(lessonContent) : void 0;
|
|
@@ -26362,6 +26531,23 @@ ${yamlBlock.trim()}
|
|
|
26362
26531
|
`);
|
|
26363
26532
|
};
|
|
26364
26533
|
var injectYamlReviewMetadata = injectYamlReviewMetadata2;
|
|
26534
|
+
const lessonDet = validateArtifactDeterministic({ content: lessonContent, refPack });
|
|
26535
|
+
if (!lessonDet.ok) {
|
|
26536
|
+
const detCritique = validatorRepairPrompt(lessonDet.issues);
|
|
26537
|
+
onProgress?.("@reviewer", `\u26D4 LESSON deterministic validator FAIL: ${detCritique.slice(0, 200)}`);
|
|
26538
|
+
await storage.updateArtifactState(projectId, lessonCode, "LESSON", {
|
|
26539
|
+
state: "rejected",
|
|
26540
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26541
|
+
contentHash: computeContentHash(lessonContent),
|
|
26542
|
+
review: {
|
|
26543
|
+
decision: "NEEDS_REVISION",
|
|
26544
|
+
reviewedBy: "@heuristic-linter",
|
|
26545
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26546
|
+
score: 0,
|
|
26547
|
+
critique: detCritique
|
|
26548
|
+
}
|
|
26549
|
+
});
|
|
26550
|
+
}
|
|
26365
26551
|
const judgeObjectives = parseLessonObjectives(lessonContent, concept, bloomLevel);
|
|
26366
26552
|
const judgeExpositionExcerpt = expositionContext ? buildExpositionExcerpt(expositionContext, { budget: 3e3, sectionLanguageContract: slcMarkdown, artifactType: "KNOWLEDGE_EXPOSITION" }).excerpt : void 0;
|
|
26367
26553
|
const judgeResult = await LLMJudgeEngine.evaluateArtifactWithLLM({
|
|
@@ -26617,6 +26803,23 @@ ${currentContent}` }],
|
|
|
26617
26803
|
${lessonExcerpt}${symbolLedgerBlock}`;
|
|
26618
26804
|
const judgeSat = (sat, content) => {
|
|
26619
26805
|
if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
|
|
26806
|
+
const det = validateArtifactDeterministic({ content, refPack });
|
|
26807
|
+
if (!det.ok) {
|
|
26808
|
+
const critique = validatorRepairPrompt(det.issues);
|
|
26809
|
+
onProgress?.("@reviewer", `\u26D4 ${sat} deterministic validator FAIL: ${critique.slice(0, 200)}`);
|
|
26810
|
+
return storage.updateArtifactState(projectId, lessonCode, sat, {
|
|
26811
|
+
state: "rejected",
|
|
26812
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26813
|
+
contentHash: computeContentHash(content),
|
|
26814
|
+
review: {
|
|
26815
|
+
decision: "NEEDS_REVISION",
|
|
26816
|
+
reviewedBy: "@heuristic-linter",
|
|
26817
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26818
|
+
score: 0,
|
|
26819
|
+
critique
|
|
26820
|
+
}
|
|
26821
|
+
});
|
|
26822
|
+
}
|
|
26620
26823
|
return judgeSatelliteArtifact({
|
|
26621
26824
|
storage,
|
|
26622
26825
|
projectId,
|
|
@@ -26633,7 +26836,8 @@ ${lessonExcerpt}${symbolLedgerBlock}`;
|
|
|
26633
26836
|
});
|
|
26634
26837
|
};
|
|
26635
26838
|
const actRelPath = `_content/${unitCode}/ACT_${lessonCode}.md`;
|
|
26636
|
-
|
|
26839
|
+
const existingAct = await storage.readArtifact(projectId, actRelPath);
|
|
26840
|
+
if (artifactScope.includes("ACT") && (!existingAct || force || isStaleVsPlan(existingAct))) {
|
|
26637
26841
|
onProgress?.("@activity", `[2/4] Authoring Hands-on Lab: \`ACT_${lessonCode}.md\`...`);
|
|
26638
26842
|
const actDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26639
26843
|
const actPrompt = `You are @activity (Lead Hands-on STEM & Laboratory Exercise Designer).
|
|
@@ -26693,7 +26897,7 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
|
26693
26897
|
rawError: rawAct || "Empty AI response"
|
|
26694
26898
|
});
|
|
26695
26899
|
}
|
|
26696
|
-
await storage.saveArtifact(projectId, actRelPath, rawAct);
|
|
26900
|
+
await storage.saveArtifact(projectId, actRelPath, stampPlanHash(rawAct));
|
|
26697
26901
|
await storage.updateArtifactState(projectId, lessonCode, "ACT", {
|
|
26698
26902
|
state: "completed",
|
|
26699
26903
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26703,7 +26907,8 @@ ${buildHeadingDirective("ACT", slcMarkdown, targetLang)}`;
|
|
|
26703
26907
|
await judgeSat("ACT", rawAct);
|
|
26704
26908
|
}
|
|
26705
26909
|
const quizRelPath = `_content/${unitCode}/QUIZ_${lessonCode}.md`;
|
|
26706
|
-
|
|
26910
|
+
const existingQuiz = await storage.readArtifact(projectId, quizRelPath);
|
|
26911
|
+
if (artifactScope.includes("QUIZ") && (!existingQuiz || force || isStaleVsPlan(existingQuiz))) {
|
|
26707
26912
|
onProgress?.("@assessor", `[3/4] Authoring Assessment Bank: \`QUIZ_${lessonCode}.md\`...`);
|
|
26708
26913
|
const quizDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26709
26914
|
const quizPrompt = `You are @assessor (Lead Psychometrician & Competency Assessment Specialist).
|
|
@@ -26757,7 +26962,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26757
26962
|
rawError: rawQuiz || "Empty AI response"
|
|
26758
26963
|
});
|
|
26759
26964
|
}
|
|
26760
|
-
await storage.saveArtifact(projectId, quizRelPath, rawQuiz);
|
|
26965
|
+
await storage.saveArtifact(projectId, quizRelPath, stampPlanHash(rawQuiz));
|
|
26761
26966
|
await storage.updateArtifactState(projectId, lessonCode, "QUIZ", {
|
|
26762
26967
|
state: "completed",
|
|
26763
26968
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26767,7 +26972,8 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26767
26972
|
await judgeSat("QUIZ", rawQuiz);
|
|
26768
26973
|
}
|
|
26769
26974
|
const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
|
|
26770
|
-
|
|
26975
|
+
const existingSlide = await storage.readArtifact(projectId, slideRelPath);
|
|
26976
|
+
if (artifactScope.includes("SLIDE") && (!existingSlide || force || isStaleVsPlan(existingSlide))) {
|
|
26771
26977
|
onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
|
|
26772
26978
|
const isHtmlEngine = options.slidesEngine !== "marp";
|
|
26773
26979
|
if (isHtmlEngine) {
|
|
@@ -26801,7 +27007,7 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
|
|
|
26801
27007
|
let deckJson = workflowResult.deckJson;
|
|
26802
27008
|
const markdownWrapper = workflowResult.markdownWrapper;
|
|
26803
27009
|
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" }]};
|
|
26804
|
-
await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
|
|
27010
|
+
await storage.saveArtifact(projectId, slideRelPath, stampPlanHash(markdownWrapper));
|
|
26805
27011
|
producedArtifacts.push(`SLIDE_${lessonCode}.md`);
|
|
26806
27012
|
if (deckJson) {
|
|
26807
27013
|
const deckJsonStr = JSON.stringify(deckJson, null, 2);
|
|
@@ -26892,7 +27098,7 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
26892
27098
|
rawError: rawSlide || "Empty AI response"
|
|
26893
27099
|
});
|
|
26894
27100
|
}
|
|
26895
|
-
await storage.saveArtifact(projectId, slideRelPath, rawSlide);
|
|
27101
|
+
await storage.saveArtifact(projectId, slideRelPath, stampPlanHash(rawSlide));
|
|
26896
27102
|
await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
|
|
26897
27103
|
state: "completed",
|
|
26898
27104
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26903,7 +27109,8 @@ footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
|
|
|
26903
27109
|
}
|
|
26904
27110
|
}
|
|
26905
27111
|
const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
|
|
26906
|
-
|
|
27112
|
+
const existingGuide = await storage.readArtifact(projectId, guideRelPath);
|
|
27113
|
+
if (artifactScope.includes("GUIDE") && (!existingGuide || force || isStaleVsPlan(existingGuide))) {
|
|
26907
27114
|
onProgress?.("@content", `Authoring Teacher Guide: \`GUIDE_${lessonCode}.md\`...`, { artifactType: "GUIDE" });
|
|
26908
27115
|
const guideDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26909
27116
|
const guidePrompt = `You are @content (Senior Curriculum Developer & Teacher Facilitation Specialist).
|
|
@@ -26943,7 +27150,7 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
|
26943
27150
|
}
|
|
26944
27151
|
);
|
|
26945
27152
|
if (rawGuide && !rawGuide.startsWith("\u26A0\uFE0F") && rawGuide.length >= 200) {
|
|
26946
|
-
await storage.saveArtifact(projectId, guideRelPath, rawGuide);
|
|
27153
|
+
await storage.saveArtifact(projectId, guideRelPath, stampPlanHash(rawGuide));
|
|
26947
27154
|
await storage.updateArtifactState(projectId, lessonCode, "GUIDE", {
|
|
26948
27155
|
state: "completed",
|
|
26949
27156
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -26954,7 +27161,8 @@ ${buildHeadingDirective("GUIDE", slcMarkdown, targetLang)}`;
|
|
|
26954
27161
|
}
|
|
26955
27162
|
}
|
|
26956
27163
|
const handoutRelPath = `_content/${unitCode}/HANDOUT_${lessonCode}.md`;
|
|
26957
|
-
|
|
27164
|
+
const existingHandout = await storage.readArtifact(projectId, handoutRelPath);
|
|
27165
|
+
if (artifactScope.includes("HANDOUT") && (!existingHandout || force || isStaleVsPlan(existingHandout))) {
|
|
26958
27166
|
onProgress?.("@content", `Authoring Student Handout: \`HANDOUT_${lessonCode}.md\`...`, { artifactType: "HANDOUT" });
|
|
26959
27167
|
const handoutDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
26960
27168
|
const handoutPrompt = `You are @content (Educational Content Specialist).
|
|
@@ -26994,7 +27202,7 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
|
26994
27202
|
}
|
|
26995
27203
|
);
|
|
26996
27204
|
if (rawHandout && !rawHandout.startsWith("\u26A0\uFE0F") && rawHandout.length >= 200) {
|
|
26997
|
-
await storage.saveArtifact(projectId, handoutRelPath, rawHandout);
|
|
27205
|
+
await storage.saveArtifact(projectId, handoutRelPath, stampPlanHash(rawHandout));
|
|
26998
27206
|
await storage.updateArtifactState(projectId, lessonCode, "HANDOUT", {
|
|
26999
27207
|
state: "completed",
|
|
27000
27208
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27005,7 +27213,8 @@ ${buildHeadingDirective("HANDOUT", slcMarkdown, targetLang)}`;
|
|
|
27005
27213
|
}
|
|
27006
27214
|
}
|
|
27007
27215
|
const wksRelPath = `_content/${unitCode}/WKS_${lessonCode}.md`;
|
|
27008
|
-
|
|
27216
|
+
const existingWks = await storage.readArtifact(projectId, wksRelPath);
|
|
27217
|
+
if (artifactScope.includes("WKS") && (!existingWks || force || isStaleVsPlan(existingWks))) {
|
|
27009
27218
|
onProgress?.("@content", `Authoring Student Worksheet: \`WKS_${lessonCode}.md\`...`, { artifactType: "WKS" });
|
|
27010
27219
|
const wksDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27011
27220
|
const wksPrompt = `You are @content (Instructional Activity & Worksheet Designer).
|
|
@@ -27049,7 +27258,7 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
|
27049
27258
|
}
|
|
27050
27259
|
);
|
|
27051
27260
|
if (rawWks && !rawWks.startsWith("\u26A0\uFE0F") && rawWks.length >= 200) {
|
|
27052
|
-
await storage.saveArtifact(projectId, wksRelPath, rawWks);
|
|
27261
|
+
await storage.saveArtifact(projectId, wksRelPath, stampPlanHash(rawWks));
|
|
27053
27262
|
await storage.updateArtifactState(projectId, lessonCode, "WKS", {
|
|
27054
27263
|
state: "completed",
|
|
27055
27264
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27060,7 +27269,8 @@ ${buildHeadingDirective("WKS", slcMarkdown, targetLang)}`;
|
|
|
27060
27269
|
}
|
|
27061
27270
|
}
|
|
27062
27271
|
const codeRelPath = `_content/${unitCode}/CODE_${lessonCode}.md`;
|
|
27063
|
-
|
|
27272
|
+
const existingCode = await storage.readArtifact(projectId, codeRelPath);
|
|
27273
|
+
if ((artifactScope.includes("CODE") || artifactScope.includes("CODE_LAB")) && (!existingCode || force || isStaleVsPlan(existingCode))) {
|
|
27064
27274
|
onProgress?.("@activity", `Authoring Executable Code Lab: \`CODE_${lessonCode}.md\`...`, { artifactType: "CODE" });
|
|
27065
27275
|
const codeDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27066
27276
|
const codePrompt = `You are @activity & @tech_sme (Lead Software Engineer & Technical SME).
|
|
@@ -27108,7 +27318,7 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
27108
27318
|
}
|
|
27109
27319
|
);
|
|
27110
27320
|
if (rawCode && !rawCode.startsWith("\u26A0\uFE0F") && rawCode.length >= 200) {
|
|
27111
|
-
await storage.saveArtifact(projectId, codeRelPath, rawCode);
|
|
27321
|
+
await storage.saveArtifact(projectId, codeRelPath, stampPlanHash(rawCode));
|
|
27112
27322
|
await storage.updateArtifactState(projectId, lessonCode, "CODE", {
|
|
27113
27323
|
state: "completed",
|
|
27114
27324
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -27124,7 +27334,8 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
27124
27334
|
}
|
|
27125
27335
|
}
|
|
27126
27336
|
const extRelPath = `_content/${unitCode}/EXT_${lessonCode}.md`;
|
|
27127
|
-
|
|
27337
|
+
const existingExt = await storage.readArtifact(projectId, extRelPath);
|
|
27338
|
+
if (artifactScope.includes("EXT") && (!existingExt || force || isStaleVsPlan(existingExt))) {
|
|
27128
27339
|
onProgress?.("@activity", `Authoring Advanced Extension Challenge: \`EXT_${lessonCode}.md\`...`, { artifactType: "EXT" });
|
|
27129
27340
|
const extDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27130
27341
|
const isVi = targetLang === "vi";
|
|
@@ -27194,7 +27405,7 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
|
|
|
27194
27405
|
}
|
|
27195
27406
|
);
|
|
27196
27407
|
if (rawExt && !rawExt.startsWith("\u26A0\uFE0F") && rawExt.length >= 200) {
|
|
27197
|
-
await storage.saveArtifact(projectId, extRelPath, rawExt);
|
|
27408
|
+
await storage.saveArtifact(projectId, extRelPath, stampPlanHash(rawExt));
|
|
27198
27409
|
await storage.updateArtifactState(projectId, lessonCode, "EXT", {
|
|
27199
27410
|
state: "completed",
|
|
27200
27411
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|