@thanh01.pmt/curriculum-kit 1.4.21 → 1.4.23
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 +265 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.mjs +265 -11
- package/dist/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +15 -0
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.mjs +15 -0
- 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
|
|
@@ -655,6 +669,15 @@ interface GenerateExpositionOptions {
|
|
|
655
669
|
slcMarkdown?: Record<string, Record<string, string>> | string | null;
|
|
656
670
|
techStack?: string;
|
|
657
671
|
hardwarePlatform?: string;
|
|
672
|
+
/** REFERENCE_PACK.md raw content — technical ground truth (versions, APIs).
|
|
673
|
+
* 5-Whys RC-vacuum: without it the KX model regresses to pretrained facts. */
|
|
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
|
+
};
|
|
658
681
|
}
|
|
659
682
|
interface ExpositionResult {
|
|
660
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
|
|
@@ -655,6 +669,15 @@ interface GenerateExpositionOptions {
|
|
|
655
669
|
slcMarkdown?: Record<string, Record<string, string>> | string | null;
|
|
656
670
|
techStack?: string;
|
|
657
671
|
hardwarePlatform?: string;
|
|
672
|
+
/** REFERENCE_PACK.md raw content — technical ground truth (versions, APIs).
|
|
673
|
+
* 5-Whys RC-vacuum: without it the KX model regresses to pretrained facts. */
|
|
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
|
+
};
|
|
658
681
|
}
|
|
659
682
|
interface ExpositionResult {
|
|
660
683
|
relPath: string;
|
package/dist/index.mjs
CHANGED
|
@@ -11507,6 +11507,21 @@ function normalizeHeading(str) {
|
|
|
11507
11507
|
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
11508
11508
|
}
|
|
11509
11509
|
function findCanonicalFuzzy(norm) {
|
|
11510
|
+
if (norm.includes("toolchain") || norm.includes("moi truong phat trien") || norm.includes("cong cu") || norm.includes("version") || norm.includes("phan mem") || norm.includes("development environment") || norm.includes("technical overview") || norm.includes("kien truc")) {
|
|
11511
|
+
return "Technical Overview & Architecture Blueprint";
|
|
11512
|
+
}
|
|
11513
|
+
if (norm.includes("pinout") || norm.includes("phan cung") || norm.includes("hardware") || norm.includes("wiring") || norm.includes("ket noi")) {
|
|
11514
|
+
return "Hardware Pinout & Wiring Configuration Matrix";
|
|
11515
|
+
}
|
|
11516
|
+
if (norm.includes("pedagog") || norm.includes("phuong phap") || norm.includes("day hoc") || norm.includes("su pham")) {
|
|
11517
|
+
return "Core Pedagogical Concept Anchor & Real-World Domain Bridge";
|
|
11518
|
+
}
|
|
11519
|
+
if (norm.includes("standard") || norm.includes("tieu chuan") || norm.includes("csta") || norm.includes("cs2023") || norm.includes("chuan academic")) {
|
|
11520
|
+
return "Standards Alignment";
|
|
11521
|
+
}
|
|
11522
|
+
if (norm.includes("roadmap") || norm.includes("lo trinh") || norm.includes("milestone") || norm.includes("giai doan")) {
|
|
11523
|
+
return void 0;
|
|
11524
|
+
}
|
|
11510
11525
|
if (norm.includes("symbol") || norm.includes("identifier ledger") || norm.includes("dinh danh")) {
|
|
11511
11526
|
return "Symbol & Identifier Ledger";
|
|
11512
11527
|
}
|
|
@@ -11597,6 +11612,137 @@ function extractSymbolLedger(lessonMarkdown) {
|
|
|
11597
11612
|
return result;
|
|
11598
11613
|
}
|
|
11599
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
|
+
|
|
11600
11746
|
// src/services/knowledgeExpositionService.ts
|
|
11601
11747
|
var EXPOSITION_REL = (lessonCode) => "_sot/expositions/" + lessonCode + ".md";
|
|
11602
11748
|
var ExpositionApprovalError = class extends Error {
|
|
@@ -11643,7 +11789,7 @@ function buildSystemPrompt(targetLanguage = "vi", techStack, hardwarePlatform) {
|
|
|
11643
11789
|
langDirective
|
|
11644
11790
|
].join("\n");
|
|
11645
11791
|
}
|
|
11646
|
-
function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown) {
|
|
11792
|
+
function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown, refPack) {
|
|
11647
11793
|
const depthLines = session.depth_assignments.map((d) => "- " + d.node_id + " [" + d.depth.toUpperCase() + "]: " + DEPTH_RULES[d.depth]).join("\n");
|
|
11648
11794
|
const termLines = glossary.map((g) => "- " + g.term + (g.definition ? " \u2014 " + g.definition : "") + (g.example ? " (example: " + g.example + ")" : "")).join("\n");
|
|
11649
11795
|
const langCode = resolveTargetLanguageCode(targetLanguage);
|
|
@@ -11656,6 +11802,28 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
|
|
|
11656
11802
|
const hQuestions = headings["Self-Check Questions"] || (langCode === "vi" ? "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra" : "Self-Check Questions");
|
|
11657
11803
|
const skeleton = `## ${hScope} / ## ${hTerms} / ## ${hNarratives} / ## ${hExamples} / ## ${hMistakes} / ## ${hQuestions}`;
|
|
11658
11804
|
const headingDirective = buildHeadingDirective("KNOWLEDGE_EXPOSITION", slcMarkdown, targetLanguage);
|
|
11805
|
+
let refPackBlock = "";
|
|
11806
|
+
if (refPack && refPack.trim()) {
|
|
11807
|
+
const refExcerpt = buildSectionAwareExcerpt(refPack, {
|
|
11808
|
+
priorities: [
|
|
11809
|
+
"Technical Overview & Architecture Blueprint",
|
|
11810
|
+
"Hardware Pinout & Wiring Configuration Matrix",
|
|
11811
|
+
"Standards Alignment"
|
|
11812
|
+
],
|
|
11813
|
+
budget: 2500
|
|
11814
|
+
}).excerpt;
|
|
11815
|
+
if (refExcerpt) {
|
|
11816
|
+
refPackBlock = [
|
|
11817
|
+
"",
|
|
11818
|
+
"[GROUND TRUTH \u2014 BINDING, from REFERENCE_PACK.md]:",
|
|
11819
|
+
"Tool versions, APIs, and platform facts below are CANONICAL. Do NOT",
|
|
11820
|
+
"contradict them; do NOT substitute versions from memory. If a fact you",
|
|
11821
|
+
"need is not stated here, stay generic rather than inventing specifics.",
|
|
11822
|
+
"",
|
|
11823
|
+
refExcerpt
|
|
11824
|
+
].join("\n");
|
|
11825
|
+
}
|
|
11826
|
+
}
|
|
11659
11827
|
return [
|
|
11660
11828
|
"Session: " + session.id + " \u2014 " + session.title,
|
|
11661
11829
|
"Objective: " + session.prose_objective,
|
|
@@ -11668,6 +11836,7 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
|
|
|
11668
11836
|
"",
|
|
11669
11837
|
"Glossary terms (definitions are canonical):",
|
|
11670
11838
|
termLines || "(none provided \u2014 write definitions and mark them for glossary sync)",
|
|
11839
|
+
refPackBlock,
|
|
11671
11840
|
"",
|
|
11672
11841
|
"Section skeleton to fill (replace ONLY the {{placeholders}}):",
|
|
11673
11842
|
skeleton,
|
|
@@ -11676,7 +11845,7 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
|
|
|
11676
11845
|
].filter(Boolean).join("\n");
|
|
11677
11846
|
}
|
|
11678
11847
|
async function ensureKnowledgeExposition(options) {
|
|
11679
|
-
const { projectId, lessonCode, plan, glossary, llmFn, storage, techStack, hardwarePlatform } = options;
|
|
11848
|
+
const { projectId, lessonCode, plan, glossary, llmFn, storage, techStack, hardwarePlatform, refPack } = options;
|
|
11680
11849
|
const session = plan.sessions.find((s) => s.id === lessonCode);
|
|
11681
11850
|
if (!session) throw new Error("Session " + lessonCode + " not found in plan " + plan.plan_id);
|
|
11682
11851
|
const existing = await storage.readArtifact(projectId, EXPOSITION_REL(lessonCode));
|
|
@@ -11690,9 +11859,16 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11690
11859
|
throw new ExpositionApprovalError("Approval hash " + plan.approval.plan_hash + " does not match current plan hash " + plan.plan_hash + " \u2014 re-approve after plan changes");
|
|
11691
11860
|
}
|
|
11692
11861
|
const targetLanguage = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
11862
|
+
const originalSystemPrompt = buildSystemPrompt(targetLanguage, techStack, hardwarePlatform);
|
|
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 : "");
|
|
11693
11869
|
const content = (await llmFn(
|
|
11694
|
-
|
|
11695
|
-
|
|
11870
|
+
originalSystemPrompt,
|
|
11871
|
+
originalUserPrompt
|
|
11696
11872
|
)).trim();
|
|
11697
11873
|
if (content.length < 200) {
|
|
11698
11874
|
throw new Error("EXPOSITION too short for " + lessonCode + " (" + content.length + " chars) \u2014 refusing to save");
|
|
@@ -11723,11 +11899,30 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11723
11899
|
const parsed = JSON.parse(match[0]);
|
|
11724
11900
|
return { verdict: parsed.verdict ?? "NEEDS_REVISION", score: parsed.score ?? 0, critique: parsed.critique ?? "" };
|
|
11725
11901
|
};
|
|
11726
|
-
|
|
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) };
|
|
11727
11913
|
if (verdict.verdict !== "APPROVED") {
|
|
11728
|
-
const repairPrompt =
|
|
11729
|
-
|
|
11730
|
-
|
|
11914
|
+
const repairPrompt = [
|
|
11915
|
+
originalUserPrompt,
|
|
11916
|
+
"",
|
|
11917
|
+
"--- YOUR PREVIOUS DRAFT (REJECTED, fix ALL issues below) ---",
|
|
11918
|
+
finalContent,
|
|
11919
|
+
"",
|
|
11920
|
+
"--- CRITIQUE (fix ALL issues, output the COMPLETE corrected document) ---",
|
|
11921
|
+
verdict.critique
|
|
11922
|
+
].join("\n");
|
|
11923
|
+
finalContent = (await llmFn(originalSystemPrompt, repairPrompt)).trim();
|
|
11924
|
+
det = runDeterministic(finalContent);
|
|
11925
|
+
verdict = det.ok ? await judgeOnce(finalContent) : { verdict: "NEEDS_REVISION", score: 0, critique: validatorRepairPrompt(det.issues) };
|
|
11731
11926
|
}
|
|
11732
11927
|
if (verdict.verdict !== "APPROVED") {
|
|
11733
11928
|
throw new Error("EXPOSITION failed judge for " + lessonCode + " (verdict " + verdict.verdict + ", score " + verdict.score + "): " + verdict.critique);
|
|
@@ -11767,12 +11962,27 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
11767
11962
|
try {
|
|
11768
11963
|
const lpRaw = await storage.readSotDocument(projectId, "LEARNER_PROFILE.md");
|
|
11769
11964
|
if (lpRaw) {
|
|
11770
|
-
const
|
|
11771
|
-
|
|
11965
|
+
const hwPatterns = [
|
|
11966
|
+
/(?:Student Equipment|Hardware|Platform|Thiết bị|Thiết bị học tập)\s*(?:\(|:|\*)*\s*([^\n\r]+?)(?:\s*\*\*|[.).]?\s*$)/i,
|
|
11967
|
+
/(?:máy|device|machine)\s+([^,.;\n]{4,60}(?:M\d|Intel|PC|computer|mini)[^,.;\n]{0,30})/i
|
|
11968
|
+
];
|
|
11969
|
+
for (const p of hwPatterns) {
|
|
11970
|
+
const m = lpRaw.match(p);
|
|
11971
|
+
if (m && !hw) {
|
|
11972
|
+
hw = m[1].trim();
|
|
11973
|
+
break;
|
|
11974
|
+
}
|
|
11975
|
+
}
|
|
11772
11976
|
}
|
|
11773
11977
|
} catch {
|
|
11774
11978
|
}
|
|
11775
11979
|
}
|
|
11980
|
+
let refPackContent;
|
|
11981
|
+
try {
|
|
11982
|
+
refPackContent = await storage.readSotDocument(projectId, "REFERENCE_PACK.md") || void 0;
|
|
11983
|
+
} catch {
|
|
11984
|
+
refPackContent = void 0;
|
|
11985
|
+
}
|
|
11776
11986
|
const { runCurriculumAIInference: runCurriculumAIInference2 } = await Promise.resolve().then(() => (init_streamRunner(), streamRunner_exports));
|
|
11777
11987
|
const targetLang = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
11778
11988
|
const result = await ensureKnowledgeExposition({
|
|
@@ -11784,6 +11994,7 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
11784
11994
|
slcMarkdown: options.slcMarkdown,
|
|
11785
11995
|
techStack: tech,
|
|
11786
11996
|
hardwarePlatform: hw,
|
|
11997
|
+
refPack: refPackContent,
|
|
11787
11998
|
llmFn: async (systemPrompt, userPrompt) => {
|
|
11788
11999
|
const runnerOpts = options.customInference ? { customInference: options.customInference } : {};
|
|
11789
12000
|
const out = await runCurriculumAIInference2(
|
|
@@ -26082,11 +26293,16 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
26082
26293
|
}).excerpt : "";
|
|
26083
26294
|
let effectiveRefPack = refPack ? buildSectionAwareExcerpt(refPack, {
|
|
26084
26295
|
priorities: [
|
|
26296
|
+
// Toolchain/version matrix FIRST — the most-frequently-hallucinated
|
|
26297
|
+
// facts are tool versions ("Xcode 15" vs ground truth "Xcode 16").
|
|
26298
|
+
// The bilingual fuzzy matcher in contextBuilder resolves these keys
|
|
26299
|
+
// even when the generated RefPack headings are Vietnamese.
|
|
26085
26300
|
"Technical Overview & Architecture Blueprint",
|
|
26086
26301
|
"Hardware Pinout & Wiring Configuration Matrix",
|
|
26302
|
+
"Standards Alignment",
|
|
26087
26303
|
"Core Pedagogical Concept Anchor & Real-World Domain Bridge"
|
|
26088
26304
|
],
|
|
26089
|
-
budget:
|
|
26305
|
+
budget: 3500
|
|
26090
26306
|
}).excerpt : "";
|
|
26091
26307
|
const baseContextPrefix = `PROJECT & ACADEMIC CONTEXT:
|
|
26092
26308
|
- Project ID: ${projectId}
|
|
@@ -26135,6 +26351,10 @@ ${expositionContext}`);
|
|
|
26135
26351
|
parts.push(`### REFERENCE PACK GROUND TRUTH
|
|
26136
26352
|
${effectiveRefPack}`);
|
|
26137
26353
|
}
|
|
26354
|
+
const productSpec = buildProductSpecBlock({ refPack, briefing: options.briefing });
|
|
26355
|
+
if (productSpec) {
|
|
26356
|
+
parts.push(productSpec);
|
|
26357
|
+
}
|
|
26138
26358
|
return parts.length > 0 ? `
|
|
26139
26359
|
|
|
26140
26360
|
[GROUND TRUTH]:
|
|
@@ -26293,6 +26513,23 @@ ${yamlBlock.trim()}
|
|
|
26293
26513
|
`);
|
|
26294
26514
|
};
|
|
26295
26515
|
var injectYamlReviewMetadata = injectYamlReviewMetadata2;
|
|
26516
|
+
const lessonDet = validateArtifactDeterministic({ content: lessonContent, refPack });
|
|
26517
|
+
if (!lessonDet.ok) {
|
|
26518
|
+
const detCritique = validatorRepairPrompt(lessonDet.issues);
|
|
26519
|
+
onProgress?.("@reviewer", `\u26D4 LESSON deterministic validator FAIL: ${detCritique.slice(0, 200)}`);
|
|
26520
|
+
await storage.updateArtifactState(projectId, lessonCode, "LESSON", {
|
|
26521
|
+
state: "rejected",
|
|
26522
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26523
|
+
contentHash: computeContentHash(lessonContent),
|
|
26524
|
+
review: {
|
|
26525
|
+
decision: "NEEDS_REVISION",
|
|
26526
|
+
reviewedBy: "@heuristic-linter",
|
|
26527
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26528
|
+
score: 0,
|
|
26529
|
+
critique: detCritique
|
|
26530
|
+
}
|
|
26531
|
+
});
|
|
26532
|
+
}
|
|
26296
26533
|
const judgeObjectives = parseLessonObjectives(lessonContent, concept, bloomLevel);
|
|
26297
26534
|
const judgeExpositionExcerpt = expositionContext ? buildExpositionExcerpt(expositionContext, { budget: 3e3, sectionLanguageContract: slcMarkdown, artifactType: "KNOWLEDGE_EXPOSITION" }).excerpt : void 0;
|
|
26298
26535
|
const judgeResult = await LLMJudgeEngine.evaluateArtifactWithLLM({
|
|
@@ -26548,6 +26785,23 @@ ${currentContent}` }],
|
|
|
26548
26785
|
${lessonExcerpt}${symbolLedgerBlock}`;
|
|
26549
26786
|
const judgeSat = (sat, content) => {
|
|
26550
26787
|
if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
|
|
26788
|
+
const det = validateArtifactDeterministic({ content, refPack });
|
|
26789
|
+
if (!det.ok) {
|
|
26790
|
+
const critique = validatorRepairPrompt(det.issues);
|
|
26791
|
+
onProgress?.("@reviewer", `\u26D4 ${sat} deterministic validator FAIL: ${critique.slice(0, 200)}`);
|
|
26792
|
+
return storage.updateArtifactState(projectId, lessonCode, sat, {
|
|
26793
|
+
state: "rejected",
|
|
26794
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26795
|
+
contentHash: computeContentHash(content),
|
|
26796
|
+
review: {
|
|
26797
|
+
decision: "NEEDS_REVISION",
|
|
26798
|
+
reviewedBy: "@heuristic-linter",
|
|
26799
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26800
|
+
score: 0,
|
|
26801
|
+
critique
|
|
26802
|
+
}
|
|
26803
|
+
});
|
|
26804
|
+
}
|
|
26551
26805
|
return judgeSatelliteArtifact({
|
|
26552
26806
|
storage,
|
|
26553
26807
|
projectId,
|