@thanh01.pmt/curriculum-kit 1.0.13 → 1.0.14

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 CHANGED
@@ -14,7 +14,6 @@ var supabaseJs = require('@supabase/supabase-js');
14
14
  var url = require('url');
15
15
  var jsonrepair = require('jsonrepair');
16
16
  var rest = require('@octokit/rest');
17
- var workflow = require('workflow');
18
17
 
19
18
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
20
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -14672,1159 +14671,233 @@ template_contract: "artifact-template-v1"
14672
14671
  timestamp: dateStr
14673
14672
  };
14674
14673
  }
14675
- async function withRateLimitBackoff(options) {
14676
- let attempt = 1;
14677
- try {
14678
- const meta = workflow.getStepMetadata();
14679
- if (meta && typeof meta.attempt === "number") {
14680
- attempt = meta.attempt;
14681
- }
14682
- } catch {
14683
- }
14684
- try {
14685
- return await options.fn();
14686
- } catch (error) {
14687
- const errorMessage = error?.message || String(error);
14688
- const status = error?.status || error?.statusCode || error?.response?.status;
14689
- const isRateLimited = status === 429 || errorMessage.includes("429") || errorMessage.toLowerCase().includes("rate limit") || errorMessage.toLowerCase().includes("resource exhausted") || errorMessage.toLowerCase().includes("quota exceeded");
14690
- const isTransientError = status === 500 || status === 502 || status === 503 || status === 504 || errorMessage.includes("fetch failed") || errorMessage.includes("ECONNRESET") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("socket hang up");
14691
- if (isRateLimited || isTransientError) {
14692
- const retryAfterSeconds = Math.min(120, Math.pow(2, attempt) * 2);
14693
- const reason = isRateLimited ? "Rate limit exceeded (429)" : `Transient server error (${status || "network"})`;
14694
- throw new workflow.RetryableError(`[${options.stepName}] ${reason}. Retrying attempt ${attempt + 1}...`, {
14695
- retryAfter: `${retryAfterSeconds}s`
14696
- });
14697
- }
14698
- throw error;
14699
- }
14700
- }
14701
14674
 
14702
- // src/workflow/steps/lessonSteps.ts
14703
- async function generateMasterLessonStep(input) {
14704
- "use step";
14705
- console.log(` \u23F3 [Step: Master Lesson] Generating 5E Master Lesson Plan for "${input.milestone.name}"...`);
14706
- const t0 = Date.now();
14707
- const res = await withRateLimitBackoff({
14708
- stepName: `generate-master-lesson-${input.milestone.id || "L01"}`,
14709
- fn: async () => {
14710
- return generateLessonMasterFlow({
14711
- milestone: input.milestone,
14712
- language: input.language,
14713
- topic: input.topic,
14714
- targetAudience: input.targetAudience,
14715
- contextContinuity: input.contextContinuity,
14716
- standardsContext: input.standardsContext,
14717
- modelOptions: input.modelOptions
14675
+ // src/index.ts
14676
+ init_errors();
14677
+
14678
+ // src/evaluators/deterministicStructuralLinter.ts
14679
+ var DeterministicStructuralLinter = class {
14680
+ /**
14681
+ * Validates structural invariants across a complete lesson bundle.
14682
+ */
14683
+ static lintBundle(input) {
14684
+ const { lesson, quiz, activity, slides, codeLab } = input;
14685
+ const findings = [];
14686
+ const strengths = [];
14687
+ let score = 100;
14688
+ const baseLessonId = lesson.lessonId;
14689
+ const baseLanguage = lesson.language;
14690
+ if (!lesson.title || lesson.title.trim().length === 0) {
14691
+ score -= 20;
14692
+ findings.push({
14693
+ id: "struct_missing_lesson_title",
14694
+ dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
14695
+ severity: "CRITICAL",
14696
+ title: "Missing Lesson Title",
14697
+ description: "Lesson plan has an empty or whitespace title.",
14698
+ remediationAdvice: "Provide a non-empty lesson title."
14718
14699
  });
14719
14700
  }
14720
- });
14721
- console.log(` \u2705 [Step: Master Lesson] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
14722
- return res;
14723
- }
14724
- async function judgeMasterLessonStep(input) {
14725
- "use step";
14726
- console.log(` \u2696\uFE0F [Step: Quality Judge] Auditing Master Lesson pedagogical quality & language adherence...`);
14727
- const t0 = Date.now();
14728
- const res = await withRateLimitBackoff({
14729
- stepName: `judge-master-lesson-${input.lessonId}`,
14730
- fn: async () => {
14731
- return auditCurriculumQualityFlow({
14732
- targetArtifactType: "LESSON",
14733
- lessonId: input.lessonId,
14734
- expectedLanguage: input.language,
14735
- targetObjectives: input.targetObjectives,
14736
- generatedContentJson: JSON.stringify(input.lesson, null, 2),
14737
- standardStatements: input.standardStatements,
14738
- modelOptions: input.modelOptions
14701
+ if (!lesson.learningObjectives || lesson.learningObjectives.length === 0) {
14702
+ score -= 25;
14703
+ findings.push({
14704
+ id: "struct_empty_learning_objectives",
14705
+ dimension: "CONSTRUCTIVE_ALIGNMENT",
14706
+ severity: "CRITICAL",
14707
+ title: "Empty Learning Objectives Array",
14708
+ description: "Lesson plan contains 0 learning objectives.",
14709
+ remediationAdvice: "Declare at least 2 structured Learning Objectives in the schema."
14739
14710
  });
14740
14711
  }
14741
- });
14742
- console.log(` \u2705 [Step: Quality Judge] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Verdict: ${res.overallVerdict}, Score: ${res.totalScore}/100)`);
14743
- return res;
14744
- }
14745
- async function repairMasterLessonStep(input) {
14746
- "use step";
14747
- console.log(` \u{1F6E0}\uFE0F [Step: Auto-Repair] Repairing Master Lesson based on Judge feedback...`);
14748
- const repairFeedback = `
14749
- PREVIOUS AUDIT VERDICT: ${input.auditReport.overallVerdict} (Score: ${input.auditReport.totalScore}/100)
14750
- Detected Issues:
14751
- ${input.auditReport.criteria.filter((c) => !c.passed).map((c) => `- [${c.name}] ${c.feedback}`).join("\n")}
14752
- Actionable Repairs:
14753
- ${input.auditReport.actionableRepairPrompts.map((p) => `* ${p}`).join("\n")}
14754
- Language Adherence Required: "${input.language}"
14755
- `.trim();
14756
- return withRateLimitBackoff({
14757
- stepName: `repair-master-lesson-${input.milestone.id || "L01"}`,
14758
- fn: async () => {
14759
- return generateLessonMasterFlow({
14760
- milestone: input.milestone,
14761
- language: input.language,
14762
- topic: input.topic,
14763
- targetAudience: input.targetAudience,
14764
- contextContinuity: repairFeedback,
14765
- standardsContext: input.standardsContext,
14766
- modelOptions: input.modelOptions
14712
+ if (!lesson.sections || lesson.sections.length === 0) {
14713
+ score -= 25;
14714
+ findings.push({
14715
+ id: "struct_empty_lesson_sections",
14716
+ dimension: "5E_INSTRUCTIONAL_FIDELITY",
14717
+ severity: "CRITICAL",
14718
+ title: "Empty Lesson Sections Array",
14719
+ description: "Lesson plan contains no instructional sections.",
14720
+ remediationAdvice: "Provide structured lesson flow sections."
14767
14721
  });
14768
14722
  }
14769
- });
14770
- }
14771
-
14772
- // src/workflow/steps/satelliteSteps.ts
14773
- async function generateActivityStep(options) {
14774
- "use step";
14775
- console.log(` \u23F3 [Step: Activity Lab] Generating hands-on ACT.md...`);
14776
- const t0 = Date.now();
14777
- const res = await withRateLimitBackoff({
14778
- stepName: `generate-act-${options.lesson.lessonId}`,
14779
- fn: async () => generateActivityFlow(options)
14780
- });
14781
- console.log(` \u2705 [Step: Activity Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
14782
- return res;
14783
- }
14784
- async function generateCodeLabStep(options) {
14785
- "use step";
14786
- console.log(` \u23F3 [Step: Code Lab] Generating starter & solution code LAB.md...`);
14787
- const t0 = Date.now();
14788
- const res = await withRateLimitBackoff({
14789
- stepName: `generate-lab-${options.lesson.lessonId}`,
14790
- fn: async () => generateCodeLabFlow(options)
14791
- });
14792
- console.log(` \u2705 [Step: Code Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
14793
- return res;
14794
- }
14795
- async function generateSelfLabStep(options) {
14796
- "use step";
14797
- console.log(` \u23F3 [Step: Self-Lab] Generating SELF_LAB.md with Progressive Hints...`);
14798
- const t0 = Date.now();
14799
- const res = await withRateLimitBackoff({
14800
- stepName: `generate-self-lab-${options.milestone.id || "L01"}`,
14801
- fn: async () => generateSelfLabFlow(options)
14802
- });
14803
- console.log(` \u2705 [Step: Self-Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Challenges: 3 tiers Bronze/Silver/Gold)`);
14804
- return res;
14805
- }
14806
- async function generateDiagnosticQuizStep(options) {
14807
- "use step";
14808
- console.log(` \u23F3 [Step: Diagnostic Quiz] Generating QUIZ.json Bloom Assessment...`);
14809
- const t0 = Date.now();
14810
- const res = await withRateLimitBackoff({
14811
- stepName: `generate-quiz-${options.milestone.id || "L01"}`,
14812
- fn: async () => generateDiagnosticQuizFlow(options)
14813
- });
14814
- console.log(` \u2705 [Step: Diagnostic Quiz] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Questions: ${res.questions.length})`);
14815
- return res;
14816
- }
14817
- async function generateSlidesStep(options) {
14818
- "use step";
14819
- console.log(` \u23F3 [Step: Slides] Generating Marp presentation SLIDE.md...`);
14820
- const t0 = Date.now();
14821
- const res = await withRateLimitBackoff({
14822
- stepName: `generate-slides-${options.lesson.lessonId}`,
14823
- fn: async () => generateSlidesFlow(options)
14824
- });
14825
- console.log(` \u2705 [Step: Slides] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Slides: ${res.slides.length})`);
14826
- return res;
14827
- }
14828
- async function generateHandoutStep(options) {
14829
- "use step";
14830
- console.log(` \u23F3 [Step: Handout] Generating 5-Second Rule HANDOUT.md...`);
14831
- const t0 = Date.now();
14832
- const res = await withRateLimitBackoff({
14833
- stepName: `generate-handout-${options.lesson.lessonId}`,
14834
- fn: async () => generateHandoutFlow(options)
14835
- });
14836
- console.log(` \u2705 [Step: Handout] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
14837
- return res;
14838
- }
14839
- async function generateWorksheetStep(options) {
14840
- "use step";
14841
- return withRateLimitBackoff({
14842
- stepName: `generate-worksheet-${options.lesson.lessonId}`,
14843
- fn: async () => generateWorksheetFlow(options)
14844
- });
14845
- }
14846
- async function generateTeacherGuideStep(options) {
14847
- "use step";
14848
- return withRateLimitBackoff({
14849
- stepName: `generate-teacher-guide-${options.lesson.lessonId}`,
14850
- fn: async () => generateTeacherGuideFlow(options)
14851
- });
14852
- }
14853
- async function generateExtensionStep(options) {
14854
- "use step";
14855
- return withRateLimitBackoff({
14856
- stepName: `generate-extension-${options.lesson.lessonId}`,
14857
- fn: async () => generateExtensionFlow(options)
14858
- });
14859
- }
14860
-
14861
- // src/workflow/steps/satelliteJudgeStep.ts
14862
- async function judgeSatelliteStep(input) {
14863
- "use step";
14864
- const t0 = Date.now();
14865
- console.log(" \u{1F9D1}\u200D\u2696\uFE0F [Step: Satellite Judge] " + input.artifactType + " (" + input.artifactId + ")...");
14866
- return withRateLimitBackoff({
14867
- stepName: "judge-satellite-" + input.artifactType + "-" + input.artifactId,
14868
- fn: async () => {
14869
- return auditCurriculumQualityFlow({
14870
- targetArtifactType: input.artifactType,
14871
- lessonId: input.artifactId,
14872
- expectedLanguage: input.language,
14873
- targetObjectives: input.targetObjectives,
14874
- generatedContentJson: JSON.stringify(input.artifact),
14875
- standardStatements: input.standardStatements,
14876
- modelOptions: input.modelOptions
14723
+ if (quiz && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
14724
+ score -= 15;
14725
+ findings.push({
14726
+ id: "struct_quiz_id_mismatch",
14727
+ dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
14728
+ severity: "MAJOR",
14729
+ title: "Quiz ID Contract Mismatch",
14730
+ description: `Quiz ID "${quiz.quizId}" does not match Lesson ID "${baseLessonId}".`,
14731
+ remediationAdvice: `Sync Quiz quizId to "${baseLessonId}".`,
14732
+ affectedElement: quiz.quizId
14877
14733
  });
14878
14734
  }
14879
- }).then((res) => {
14880
- console.log(" \u2705 [Satellite Judge] " + input.artifactType + " \u2192 " + res.overallVerdict + " (" + res.totalScore + "/100) in " + ((Date.now() - t0) / 1e3).toFixed(1) + "s");
14881
- return res;
14882
- });
14883
- }
14884
-
14885
- // src/workflow/steps/publishSteps.ts
14886
- async function saveMilestoneToWorkspaceStep(input) {
14887
- "use step";
14888
- const manager = new LocalWorkspaceManager(input.baseWorkspaceDir);
14889
- const savedFiles = [];
14890
- const lessonMd = serializeLessonToMarkdown(input.lesson);
14891
- const p1 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "LESSON_5E.md", lessonMd, "LESSON");
14892
- savedFiles.push(p1);
14893
- if (input.activity) {
14894
- const actMd = serializeActivityToMarkdown(input.activity);
14895
- const p2 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "ACT.md", actMd, "ACT");
14896
- savedFiles.push(p2);
14897
- }
14898
- if (input.codeLab) {
14899
- const labContent = serializeCodeLabToMarkdown(input.codeLab);
14900
- const p3 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "LAB.md", labContent, "LAB");
14901
- savedFiles.push(p3);
14902
- }
14903
- if (input.selfLab) {
14904
- const selfLabMd = serializeSelfLabToMarkdown(input.selfLab);
14905
- const p4 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "SELF_LAB.md", selfLabMd, "SELF_LAB");
14906
- savedFiles.push(p4);
14907
- }
14908
- if (input.quiz) {
14909
- const quizMd = serializeDiagnosticQuizToMarkdown(input.quiz);
14910
- const p5a = await manager.saveArtifact(input.jobId, input.milestoneSlug, "QUIZ.md", quizMd, "QUIZ_MD");
14911
- const p5b = await manager.saveArtifact(
14912
- input.jobId,
14913
- input.milestoneSlug,
14914
- "QUIZ.json",
14915
- JSON.stringify(input.quiz, null, 2),
14916
- "QUIZ_JSON"
14917
- );
14918
- savedFiles.push(p5a, p5b);
14919
- }
14920
- if (input.slides) {
14921
- const slidesContent = `---
14922
- marp: true
14923
- theme: default
14924
- paginate: true
14925
- ---
14926
-
14927
- # ${input.slides.title}
14928
-
14929
- ---
14930
-
14931
- ${input.slides.slides.map((s) => `## Slide ${s.slideNumber}: ${s.title}
14932
-
14933
- ${s.bulletPoints.map((b) => `- ${b}`).join("\n")}${s.codeSnippet ? `
14934
-
14935
- \`\`\`
14936
- ${s.codeSnippet}
14937
- \`\`\`` : ""}
14938
-
14939
- <!-- Presenter Notes: ${s.presenterNotes || ""} -->`).join("\n\n---\n\n")}
14940
- `.trim();
14941
- const p6 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "SLIDE.md", slidesContent, "SLIDES");
14942
- savedFiles.push(p6);
14943
- }
14944
- if (input.handout) {
14945
- const handoutMd = serializeHandoutToMarkdown(input.handout);
14946
- const p7 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "HANDOUT.md", handoutMd, "HANDOUT");
14947
- savedFiles.push(p7);
14948
- }
14949
- if (input.cheatSheetMarkdown) {
14950
- const p8 = await manager.saveArtifact(
14951
- input.jobId,
14952
- input.milestoneSlug,
14953
- "CHEAT_SHEET.md",
14954
- input.cheatSheetMarkdown,
14955
- "CHEAT_SHEET"
14956
- );
14957
- savedFiles.push(p8);
14958
- }
14959
- await manager.markMilestoneCompleted(input.jobId);
14960
- return {
14961
- milestoneSlug: input.milestoneSlug,
14962
- savedFiles
14963
- };
14964
- }
14965
- async function publishToGitStep(options) {
14966
- "use step";
14967
- return publishToGitHub(options);
14968
- }
14969
- async function publishToSupabaseStep(options) {
14970
- "use step";
14971
- return publishToSupabase(options);
14972
- }
14973
- var approvalPayloadSchema = zod.z.object({
14974
- approved: zod.z.boolean().describe("Whether the human reviewer approves the generated curriculum/lesson"),
14975
- reviewerName: zod.z.string().optional().describe("Name or ID of the reviewer"),
14976
- feedback: zod.z.string().optional().describe("Actionable feedback or required changes if rejected"),
14977
- timestamp: zod.z.string().optional().describe("ISO timestamp of the approval action")
14978
- });
14979
- var lessonApprovalHook = workflow.defineHook();
14980
- function approvalHookToken(projectId, lessonId, artifactType) {
14981
- return `approval:${projectId}:${lessonId}:${artifactType}`;
14982
- }
14983
-
14984
- // src/standards/standardsCoverageGate.ts
14985
- function resolveStatementRef(ref, packs) {
14986
- const [head, ...rest] = ref.split(":");
14987
- const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
14988
- const statementId = rest.length > 0 ? rest.join(":") : ref;
14989
- for (const p of candidatePacks) {
14990
- if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
14991
- }
14992
- return null;
14993
- }
14994
- function evaluateStandardsCoverage(input) {
14995
- const rows = [];
14996
- const aoToLo = /* @__PURE__ */ new Map();
14997
- for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
14998
- const loToRefs = /* @__PURE__ */ new Map();
14999
- for (const lo of input.objectives) {
15000
- loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
15001
- }
15002
- const taughtLOs = /* @__PURE__ */ new Set();
15003
- for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
15004
- const assessedLOs = /* @__PURE__ */ new Set();
15005
- for (const q of input.quizQuestions) {
15006
- if (q.alignedLO) assessedLOs.add(q.alignedLO);
15007
- if (q.alignedAO) {
15008
- const lo = aoToLo.get(q.alignedAO);
15009
- if (lo) assessedLOs.add(lo);
15010
- }
15011
- }
15012
- for (const pack of input.packs) {
15013
- const mappingByStatement = /* @__PURE__ */ new Map();
15014
- for (const m of pack.mappings ?? []) {
15015
- const prev = mappingByStatement.get(m.statementId);
15016
- if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
15017
- mappingByStatement.set(m.statementId, m.kind);
15018
- }
14735
+ if (activity && activity.lessonId !== baseLessonId) {
14736
+ score -= 15;
14737
+ findings.push({
14738
+ id: "struct_act_id_mismatch",
14739
+ dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
14740
+ severity: "MAJOR",
14741
+ title: "Activity Lesson ID Contract Mismatch",
14742
+ description: `Activity lessonId "${activity.lessonId}" does not match Lesson ID "${baseLessonId}".`,
14743
+ remediationAdvice: `Sync Activity lessonId to "${baseLessonId}".`,
14744
+ affectedElement: activity.lessonId
14745
+ });
15019
14746
  }
15020
- for (const statement of pack.statements) {
15021
- const refFull = `${pack.manifest.id}:${statement.id}`;
15022
- const issues = [];
15023
- const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
15024
- const kind = mappingByStatement.get(statement.id);
15025
- const hasMapping = kind !== void 0;
15026
- const isComplianceRelevant = kind === "covers";
15027
- const hasActivity = los.some((lo) => taughtLOs.has(lo));
15028
- const hasAssessment = los.some((lo) => assessedLOs.has(lo));
15029
- let status;
15030
- if (!hasMapping) status = "UNMAPPED";
15031
- else if (!isComplianceRelevant) status = "PARTIAL";
15032
- else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
15033
- else status = "UNCOVERED";
15034
- if (status === "UNCOVERED") {
15035
- if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
15036
- else {
15037
- if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
15038
- if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
15039
- }
15040
- }
15041
- rows.push({
15042
- packId: pack.manifest.id,
15043
- statementId: statement.id,
15044
- statementText: Object.values(statement.texts)[0] ?? "",
15045
- status,
15046
- objectives: los,
15047
- hasActivity,
15048
- hasAssessment,
15049
- issues
14747
+ if (slides && slides.lessonId !== baseLessonId) {
14748
+ score -= 15;
14749
+ findings.push({
14750
+ id: "struct_slides_id_mismatch",
14751
+ dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
14752
+ severity: "MAJOR",
14753
+ title: "Slide Deck Lesson ID Contract Mismatch",
14754
+ description: `Slide deck lessonId "${slides.lessonId}" does not match Lesson ID "${baseLessonId}".`,
14755
+ remediationAdvice: `Sync Slide deck lessonId to "${baseLessonId}".`,
14756
+ affectedElement: slides.lessonId
15050
14757
  });
15051
14758
  }
15052
- }
15053
- for (const lo of input.objectives) {
15054
- for (const r of lo.standardRefs ?? []) {
15055
- if (!resolveStatementRef(r, input.packs)) {
15056
- rows.push({
15057
- packId: "(unresolved)",
15058
- statementId: r,
15059
- statementText: "",
15060
- status: "UNCOVERED",
15061
- objectives: [lo.code],
15062
- hasActivity: false,
15063
- hasAssessment: false,
15064
- issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
14759
+ const satellites = [
14760
+ { type: "QUIZ", lang: quiz?.language },
14761
+ { type: "ACT", lang: activity?.language },
14762
+ { type: "SLIDE", lang: slides?.language }
14763
+ ];
14764
+ for (const sat of satellites) {
14765
+ if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
14766
+ score -= 25;
14767
+ findings.push({
14768
+ id: `struct_language_mismatch_${sat.type}`,
14769
+ dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
14770
+ severity: "CRITICAL",
14771
+ title: `Language Policy Inconsistency in ${sat.type}`,
14772
+ description: `Master Lesson is in "${baseLanguage}" but ${sat.type} is declared in "${sat.lang}".`,
14773
+ remediationAdvice: `Set ${sat.type} language to "${baseLanguage}".`
15065
14774
  });
15066
14775
  }
15067
14776
  }
15068
- }
15069
- const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
15070
- const covered = complianceRows.filter((r) => r.status === "COVERED").length;
15071
- const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
15072
- const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
15073
- const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
15074
- const lines = [
15075
- "# Standards Coverage Report",
15076
- "",
15077
- `- Verdict: **${verdict}**`,
15078
- `- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
15079
- `- Unresolved standardRefs: ${unresolvedCount}`,
15080
- "",
15081
- "| Pack | Statement | Status | LOs | Activity | Assessment |",
15082
- "|---|---|---|---|---|---|",
15083
- ...rows.map(
15084
- (r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
15085
- )
15086
- ];
15087
- const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
15088
- if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
15089
- return {
15090
- verdict,
15091
- coveragePct,
15092
- rows,
15093
- summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
15094
- rawMarkdownReport: lines.join("\n")
15095
- };
15096
- }
15097
- var StandardsRegistryAdapter = class {
15098
- client;
15099
- constructor(config = {}) {
15100
- if (config.client) {
15101
- this.client = config.client;
15102
- return;
15103
- }
15104
- const url = config.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || "";
15105
- const key = config.supabaseServiceRoleKey || process.env.SUPABASE_SERVICE_ROLE_KEY || config.supabaseAnonKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
15106
- if (!url || !key) {
15107
- throw new Error("StandardsRegistryAdapter: Supabase URL/key missing (pass client or env).");
14777
+ if (quiz && quiz.questions) {
14778
+ for (let i = 0; i < quiz.questions.length; i++) {
14779
+ const q = quiz.questions[i];
14780
+ const qId = q.id || `Q${i + 1}`;
14781
+ const options = q.options || [];
14782
+ if (options.length < 4) {
14783
+ score -= 10;
14784
+ findings.push({
14785
+ id: `struct_quiz_option_count_${qId}`,
14786
+ dimension: "MISCONCEPTION_RIGOR",
14787
+ severity: "MAJOR",
14788
+ title: `Structural Option Count Error in ${qId}`,
14789
+ description: `Question ${qId} has ${options.length} options (standard schema requires 4).`,
14790
+ remediationAdvice: "Ensure each question has 4 options (A, B, C, D).",
14791
+ affectedElement: qId
14792
+ });
14793
+ }
14794
+ const correctCount = options.filter((o) => o.isCorrect).length;
14795
+ if (correctCount !== 1) {
14796
+ score -= 20;
14797
+ findings.push({
14798
+ id: `struct_quiz_key_count_${qId}`,
14799
+ dimension: "MISCONCEPTION_RIGOR",
14800
+ severity: "CRITICAL",
14801
+ title: `Key Assignment Error in ${qId}`,
14802
+ description: `Question ${qId} has ${correctCount} correct options (must be exactly 1).`,
14803
+ remediationAdvice: "Set `isCorrect: true` on exactly 1 option.",
14804
+ affectedElement: qId
14805
+ });
14806
+ }
14807
+ }
15108
14808
  }
15109
- this.client = supabaseJs.createClient(url, key);
15110
- }
15111
- // ─── Intake (write path — service role) ───────────────────────────────────
15112
- /** Persist a schema-validated pack as a new framework (status=draft). */
15113
- async importPack(pack, opts) {
15114
- const parsed = FrameworkPackSchema.safeParse(pack);
15115
- if (!parsed.success) {
15116
- throw new Error("importPack: pack failed schema validation: " + parsed.error.issues.map((i) => i.path.join(".") + ": " + i.message).join("; "));
14809
+ if (activity?.materialsBOM?.hardware && activity.materialsBOM.hardware.length > 0) {
14810
+ const hwText = activity.materialsBOM.hardware.join(" ").toLowerCase();
14811
+ const hasLED = hwText.includes("led");
14812
+ const hasResistor = hwText.includes("\u0111i\u1EC7n tr\u1EDF") || hwText.includes("resistor") || hwText.includes("220") || hwText.includes("330") || hwText.includes("1k");
14813
+ if (hasLED && !hasResistor) {
14814
+ score -= 20;
14815
+ findings.push({
14816
+ id: "struct_hardware_unsafe_led_no_resistor",
14817
+ dimension: "TECHNICAL_AUTHENTICITY",
14818
+ severity: "CRITICAL",
14819
+ title: "Unsafe Circuit BOM: LED without Current-Limiting Resistor",
14820
+ description: "Hardware BOM includes LED without a 220\u03A9-1k\u03A9 resistor, causing circuit overload.",
14821
+ remediationAdvice: "Add a 220\u03A9 current-limiting resistor to the hardware materials list."
14822
+ });
14823
+ }
15117
14824
  }
15118
- const p = parsed.data;
15119
- const { data: fw, error: fwErr } = await this.client.from("standards_frameworks").insert({
15120
- pack_id: p.manifest.id,
15121
- content_version: p.manifest.contentVersion,
15122
- name: p.manifest.name,
15123
- spec_version: p.manifest.specVersion,
15124
- subject: p.manifest.subject,
15125
- languages: p.manifest.languages,
15126
- grade_model: p.manifest.gradeModel,
15127
- provenance: p.manifest.provenance,
15128
- trust: p.manifest.trust,
15129
- status: "draft",
15130
- organization_code: opts?.organizationCode ?? (p.manifest.trust === "verified" ? null : p.manifest.provenance.importedBy?.replace(/^org:/, "") || null),
15131
- original_file_path: opts?.originalFilePath ?? null,
15132
- created_by: opts?.createdBy ?? null
15133
- }).select("id").single();
15134
- if (fwErr) throw new Error("importPack: framework insert failed: " + fwErr.message);
15135
- const frameworkId = fw.id;
15136
- const statementRows = p.statements.map((s) => ({
15137
- framework_id: frameworkId,
15138
- statement_id: s.id,
15139
- parent_statement_id: s.parentId ?? null,
15140
- grade_min: s.gradeBand[0],
15141
- grade_max: s.gradeBand[1],
15142
- texts: s.texts,
15143
- classifications: s.classifications ?? [],
15144
- bloom_hint: s.bloomHint ?? null,
15145
- keywords: s.keywords ?? [],
15146
- source_ref: s.sourceRef ?? null,
15147
- provenance: s.provenance
15148
- }));
15149
- const { error: stErr, count: stCount } = await this.client.from("standards_statements").insert(statementRows, { count: "exact" });
15150
- if (stErr) throw new Error("importPack: statement insert failed: " + stErr.message);
15151
- let mappingCount = 0;
15152
- if (p.mappings && p.mappings.length > 0) {
15153
- const mappingRows = p.mappings.map((m) => ({
15154
- framework_id: frameworkId,
15155
- statement_id: m.statementId,
15156
- target_ref: m.targetRef,
15157
- kind: m.kind,
15158
- confidence: m.confidence,
15159
- provenance: m.provenance
15160
- }));
15161
- const { error: mpErr, count: mpCount } = await this.client.from("standards_mappings").insert(mappingRows, { count: "exact" });
15162
- if (mpErr) throw new Error("importPack: mapping insert failed: " + mpErr.message);
15163
- mappingCount = mpCount ?? mappingRows.length;
14825
+ score = Math.max(0, Math.min(100, score));
14826
+ const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
14827
+ if (passed) {
14828
+ strengths.push("Deterministic structure, ID contracts, and schema invariants strictly verified.");
15164
14829
  }
15165
- return { frameworkDbId: frameworkId, statementsInserted: stCount ?? statementRows.length, mappingsInserted: mappingCount };
15166
- }
15167
- /** Activate a draft framework (immutable version is now live). */
15168
- async activatePack(frameworkDbId) {
15169
- const { error } = await this.client.from("standards_frameworks").update({ status: "active" }).eq("id", frameworkDbId).eq("status", "draft");
15170
- if (error) throw new Error("activatePack failed: " + error.message);
15171
- }
15172
- async deprecatePack(frameworkDbId) {
15173
- const { error } = await this.client.from("standards_frameworks").update({ status: "deprecated" }).eq("id", frameworkDbId);
15174
- if (error) throw new Error("deprecatePack failed: " + error.message);
15175
- }
15176
- async adoptPack(frameworkDbId, organizationCode, adoptedBy) {
15177
- const { error } = await this.client.from("standards_org_adoptions").upsert(
15178
- { framework_id: frameworkDbId, organization_code: organizationCode, adopted_by: adoptedBy ?? null },
15179
- { onConflict: "organization_code,framework_id" }
15180
- );
15181
- if (error) throw new Error("adoptPack failed: " + error.message);
14830
+ return {
14831
+ passed,
14832
+ structuralScore: score,
14833
+ findings,
14834
+ strengths
14835
+ };
15182
14836
  }
15183
- // ─── Runtime (read path — generation) ─────────────────────────────────────
14837
+ };
14838
+
14839
+ // src/evaluators/academicAuditor.ts
14840
+ var AcademicAuditor = class {
15184
14841
  /**
15185
- * Load all packs usable by an org: platform packs (verified, org IS NULL) +
15186
- * org packs + org adoptions. Hydrated into the same FrameworkPack shape the
15187
- * in-memory Phase-1 components consume (injector / coverage gate / judge).
14842
+ * Evaluates a complete lesson bundle.
14843
+ * Step 1 (Deterministic): Fast structural, contract, schema, and safety linting.
14844
+ * Step 2 (Semantic LLM-as-a-Judge): Deep pedagogical, cognitive (Bloom), and misconception analysis with live frontier LLM.
15188
14845
  */
15189
- async loadPacksForOrg(organizationCode) {
15190
- let query = this.client.from("standards_frameworks").select("*, standards_org_adoptions(organization_code)").eq("status", "active");
15191
- const { data: frameworks, error } = await query;
15192
- if (error) throw new Error("loadPacksForOrg: " + error.message);
15193
- const visible = (frameworks ?? []).filter((f) => {
15194
- const own = f.organization_code && organizationCode && f.organization_code === organizationCode;
15195
- const global = !f.organization_code;
15196
- const adopted = organizationCode && (f.standards_org_adoptions ?? []).some((a) => a.organization_code === organizationCode);
15197
- return global || own || adopted;
14846
+ static async auditLessonBundle(input) {
14847
+ const { lesson, quiz, activity, slides, codeLab, modelOptions, executeLLMJudge = true } = input;
14848
+ const structuralResult = DeterministicStructuralLinter.lintBundle({
14849
+ lesson,
14850
+ quiz,
14851
+ activity,
14852
+ slides,
14853
+ codeLab
15198
14854
  });
15199
- if (visible.length === 0) return [];
15200
- const ids = visible.map((f) => f.id);
15201
- const [{ data: statements, error: stErr }, { data: mappings, error: mpErr }, { data: overrides, error: ovErr }] = await Promise.all([
15202
- this.client.from("standards_statements").select("*").in("framework_id", ids),
15203
- this.client.from("standards_mappings").select("*").in("framework_id", ids),
15204
- this.client.from("standards_mapping_overrides").select("*").in("framework_id", ids)
15205
- ]);
15206
- if (stErr) throw new Error("loadPacksForOrg statements: " + stErr.message);
15207
- const overridesByFw = /* @__PURE__ */ new Map();
15208
- for (const o of overrides ?? []) {
15209
- const list = overridesByFw.get(o.framework_id) ?? [];
15210
- list.push(o);
15211
- overridesByFw.set(o.framework_id, list);
15212
- }
15213
- return visible.map((f) => {
15214
- const stmts = (statements ?? []).filter((s) => s.framework_id === f.id).map((s) => ({
15215
- id: s.statement_id,
15216
- parentId: s.parent_statement_id ?? void 0,
15217
- gradeBand: [s.grade_min, s.grade_max],
15218
- texts: s.texts,
15219
- classifications: s.classifications ?? [],
15220
- bloomHint: s.bloom_hint ?? void 0,
15221
- keywords: s.keywords ?? [],
15222
- sourceRef: s.source_ref ?? void 0,
15223
- provenance: s.provenance ?? { method: "imported" }
15224
- }));
15225
- const ov = overridesByFw.get(f.id) ?? [];
15226
- const retired = new Set(ov.filter((o) => o.kind === "retired").map((o) => o.statement_id + "|" + o.target_ref));
15227
- const embedded = (mappings ?? []).filter((m) => m.framework_id === f.id).map((m) => ({
15228
- statementId: m.statement_id,
15229
- targetRef: m.target_ref,
15230
- kind: m.kind,
15231
- confidence: Number(m.confidence),
15232
- provenance: m.provenance ?? { method: "imported" }
15233
- }));
15234
- const overlay = ov.filter((o) => o.kind !== "retired").map((o) => ({
15235
- statementId: o.statement_id,
15236
- targetRef: o.target_ref,
15237
- kind: o.kind,
15238
- confidence: Number(o.confidence),
15239
- provenance: { method: "human", reviewedBy: o.created_by ?? "overlay", note: o.note ?? void 0 }
15240
- }));
15241
- const bridge = /* @__PURE__ */ new Map();
15242
- for (const m of embedded) bridge.set(m.statementId + "|" + m.targetRef, m);
15243
- for (const m of overlay) bridge.set(m.statementId + "|" + m.targetRef, m);
15244
- const finalMappings = [...bridge.values()].filter((m) => !retired.has(m.statementId + "|" + m.targetRef));
15245
- const hydrated = FrameworkPackSchema.safeParse({
15246
- manifest: {
15247
- id: f.pack_id,
15248
- name: f.name,
15249
- specVersion: f.spec_version || "1.0",
15250
- contentVersion: f.content_version,
15251
- subject: f.subject,
15252
- languages: f.languages,
15253
- gradeModel: f.grade_model,
15254
- provenance: f.provenance,
15255
- trust: f.trust
15256
- },
15257
- statements: stmts,
15258
- mappings: finalMappings
15259
- });
15260
- if (!hydrated.success) {
15261
- throw new Error('loadPacksForOrg: hydrated pack "' + f.pack_id + '" failed schema: ' + hydrated.error.issues[0]?.message);
15262
- }
15263
- return { ...hydrated.data, dbId: f.id, status: f.status, organizationCode: f.organization_code ?? null };
15264
- });
15265
- }
15266
- };
15267
-
15268
- // src/workflow/workflows/milestoneWorkflow.ts
15269
- async function generateMilestoneWorkflow(input) {
15270
- "use workflow";
15271
- const language = input.language || "vi";
15272
- const milestoneId = input.milestone.id || input.milestone.concept_code || "L01";
15273
- const slugPrefix = /^\d+$/.test(milestoneId) ? `M${milestoneId.padStart(2, "0")}` : milestoneId;
15274
- const milestoneSlug = `${slugPrefix}_${(input.milestone.name || "milestone").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
15275
- const bundleTier = input.bundleTier || "minimum";
15276
- const gates = resolveGateSettings(input.gateSettings);
15277
- const techStack = input.techStack || input.milestone.tech_keywords || ["General Technology"];
15278
- console.log(" \u{1F6A6} [Gate] LESSON: " + gates.LESSON + " | ACT: " + gates.ACT + " | QUIZ: " + gates.QUIZ);
15279
- const targetObjectives = (input.milestone.learning_objectives || []).map((lo) => ({
15280
- code: lo.code,
15281
- description: lo.description || lo.name || "",
15282
- bloomLevel: lo.bloom_level || "understand"
15283
- }));
15284
- const packs = input.standardsPacks ?? [];
15285
- const { block: standardsContext, selected: selectedStatements } = buildStandardsContext({
15286
- packs,
15287
- gradeBand: input.milestone.grade_band ?? [6, 12],
15288
- topicText: [input.topic, input.milestone.name, input.milestone.description, ...input.milestone.tech_keywords || []].filter(Boolean).join(" "),
15289
- conceptCodes: (input.milestone.concept_code || "").split(",").map((c) => c.trim()).filter(Boolean),
15290
- language
15291
- });
15292
- const standardStatements = {};
15293
- for (const s of selectedStatements) {
15294
- standardStatements[s.packId + ":" + s.statement.id] = Object.values(s.statement.texts)[0] || "";
15295
- }
15296
- if (selectedStatements.length > 0) {
15297
- console.log(" \u{1F4D0} [Standards] Grounded with " + selectedStatements.length + " verbatim statement(s) from " + new Set(selectedStatements.map((s) => s.packId)).size + " pack(s)");
15298
- }
15299
- let lesson = await generateMasterLessonStep({
15300
- milestone: input.milestone,
15301
- language,
15302
- topic: input.topic,
15303
- targetAudience: input.targetAudience,
15304
- standardsContext: standardsContext || void 0,
15305
- modelOptions: input.modelOptions
15306
- });
15307
- let auditReport;
15308
- const lessonGate = gateModeFor(gates, "LESSON");
15309
- if (lessonGate === "LLM_JUDGE") {
15310
- auditReport = await judgeMasterLessonStep({
15311
- lessonId: milestoneId,
15312
- lesson,
15313
- language,
15314
- targetObjectives,
15315
- standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0,
15316
- modelOptions: input.modelOptions
15317
- });
15318
- if (auditReport.overallVerdict === "FAIL" || auditReport.overallVerdict === "NEEDS_REVISION") {
15319
- lesson = await repairMasterLessonStep({
15320
- milestone: input.milestone,
15321
- language,
15322
- topic: input.topic,
15323
- targetAudience: input.targetAudience,
15324
- auditReport,
15325
- standardsContext: standardsContext || void 0,
15326
- modelOptions: input.modelOptions
15327
- });
15328
- }
15329
- } else if (lessonGate === "HITL") {
15330
- const hookToken = "gate:lesson:" + milestoneId;
15331
- console.log(" \u23F8 [Gate] LESSON awaiting HUMAN review (HITL) \u2014 hook " + hookToken);
15332
- const workflowPkg = await import('workflow');
15333
- const createHook = workflowPkg.createHook;
15334
- const hook = createHook({ token: hookToken });
15335
- const review = await hook;
15336
- console.log(" \u2705 [Gate] Human verdict: " + (review.approved ? "APPROVED" : "REJECTED") + (review.feedback ? " \u2014 " + review.feedback : ""));
15337
- if (!review.approved) {
15338
- const repairReport = {
15339
- overallVerdict: "NEEDS_REVISION",
15340
- totalScore: 0,
15341
- languageAdherencePassed: true,
15342
- criteria: [],
15343
- actionableRepairPrompts: review.feedback ? [review.feedback] : ["S\u1EEDa theo ph\u1EA3n h\u1ED3i c\u1EE7a ng\u01B0\u1EDDi duy\u1EC7t"]
15344
- };
15345
- lesson = await repairMasterLessonStep({
15346
- milestone: input.milestone,
15347
- language,
15348
- topic: input.topic,
15349
- targetAudience: input.targetAudience,
15350
- auditReport: repairReport,
15351
- standardsContext: standardsContext || void 0,
15352
- modelOptions: input.modelOptions
15353
- });
15354
- const hook2 = createHook({ token: hookToken + ":v2" });
15355
- const review2 = await hook2;
15356
- console.log(" \u2705 [Gate] Human verdict (v2): " + (review2.approved ? "APPROVED" : "REJECTED"));
15357
- if (!review2.approved) {
15358
- console.log(" \u{1F6D1} [Gate] LESSON rejected twice \u2014 satellites & save BLOCKED");
15359
- return {
15360
- milestoneId,
15361
- milestoneSlug,
15362
- status: "FAILED",
15363
- gateBlocked: { artifactType: "LESSON", reason: "HITL_REJECTED_V2", feedback: review2.feedback },
15364
- artifacts: void 0
15365
- };
15366
- }
15367
- }
15368
- }
15369
- const [activity, selfLab, quiz, slides, handout] = await Promise.all([
15370
- generateActivityStep({ lesson, language, modelOptions: input.modelOptions }),
15371
- generateSelfLabStep({ milestone: input.milestone, language, targetTechStack: techStack, modelOptions: input.modelOptions }),
15372
- generateDiagnosticQuizStep({ milestone: input.milestone, language, modelOptions: input.modelOptions }),
15373
- generateSlidesStep({ lesson, language, modelOptions: input.modelOptions }),
15374
- generateHandoutStep({ lesson, language, modelOptions: input.modelOptions })
15375
- ]);
15376
- const codeLab = await generateCodeLabStep({
15377
- lesson,
15378
- activity,
15379
- language,
15380
- targetTechStack: techStack,
15381
- modelOptions: input.modelOptions
15382
- });
15383
- const satelliteReports = {};
15384
- const satelliteCandidates = [
15385
- { type: "ACT", id: "ACT_" + milestoneId, artifact: activity },
15386
- { type: "QUIZ", id: "QUIZ_" + milestoneId, artifact: quiz },
15387
- { type: "SLIDE", id: "SLIDE_" + milestoneId, artifact: slides },
15388
- { type: "HANDOUT", id: "HANDOUT_" + milestoneId, artifact: handout },
15389
- { type: "CODE", id: "CODE_" + milestoneId, artifact: codeLab }
15390
- ];
15391
- const toJudge = satelliteCandidates.filter((c) => c.artifact && gateModeFor(gates, c.type) === "LLM_JUDGE");
15392
- if (toJudge.length > 0) {
15393
- const reports = await Promise.all(
15394
- toJudge.map(
15395
- (c) => judgeSatelliteStep({
15396
- artifactType: c.type,
15397
- artifactId: c.id,
15398
- artifact: c.artifact,
15399
- language,
15400
- targetObjectives,
15401
- standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0,
15402
- modelOptions: input.modelOptions
15403
- }).catch((err) => {
15404
- console.warn(" \u26A0 [Gate] satellite judge failed for " + c.type + ": " + err.message);
15405
- return void 0;
15406
- })
15407
- )
15408
- );
15409
- for (let i = 0; i < toJudge.length; i++) {
15410
- const r = reports[i];
15411
- if (r) satelliteReports[toJudge[i].type] = r;
15412
- }
15413
- }
15414
- let worksheet;
15415
- let teacherGuide;
15416
- let extension;
15417
- if (bundleTier === "full") {
15418
- [worksheet, teacherGuide, extension] = await Promise.all([
15419
- generateWorksheetStep({ lesson, language, modelOptions: input.modelOptions }),
15420
- generateTeacherGuideStep({ lesson, activity, language, modelOptions: input.modelOptions }),
15421
- generateExtensionStep({ lesson, activity, language, modelOptions: input.modelOptions })
15422
- ]);
15423
- }
15424
- let standardsCoverage;
15425
- if (packs.length > 0) {
15426
- standardsCoverage = evaluateStandardsCoverage({
15427
- packs,
15428
- objectives: (lesson.learningObjectives || []).map((lo) => ({
15429
- code: lo.code,
15430
- standardRefs: lo.standardRefs,
15431
- conceptRefs: lo.conceptRefs
15432
- })),
15433
- // NOTE: ActivityLab has no structured LO linkage yet — activity coverage rows are derived
15434
- // from LO→quiz paths inside the gate. Wire ACT.loRefs in a future schema revision.
15435
- activities: [],
15436
- quizQuestions: (quiz?.questions || []).map((q) => ({ alignedLO: q.alignedLO, alignedAO: q.alignedAO })),
15437
- assessmentObjectives: quiz?.assessmentObjectives?.map((ao) => ({ aoCode: ao.aoCode, alignedLO: ao.alignedLO }))
15438
- });
15439
- console.log(" \u{1F4D0} [Standards Coverage] " + standardsCoverage.summary);
15440
- const enforce = input.enforceStandardsCoverage ?? true;
15441
- if (enforce && standardsCoverage.verdict === "FAIL") {
15442
- console.error(" \u26D4 [Standards Coverage] HARD BLOCK \u2014 " + standardsCoverage.summary);
15443
- console.error(standardsCoverage.rawMarkdownReport.split("\n").filter((l) => l.startsWith("- [")).slice(0, 10).join("\n"));
15444
- return {
15445
- milestoneId,
15446
- milestoneSlug,
15447
- status: "FAILED",
15448
- savedResult: { workspaceDir: "", files: [] },
15449
- judgeReport: auditReport,
15450
- standardsCoverage,
15451
- artifacts: { lesson, activity, codeLab, selfLab, quiz, slides, handout, worksheet, teacherGuide, extension }
15452
- };
15453
- }
15454
- }
15455
- const savedResult = await saveMilestoneToWorkspaceStep({
15456
- jobId: input.jobId,
15457
- milestoneSlug,
15458
- lesson,
15459
- activity,
15460
- codeLab,
15461
- selfLab,
15462
- quiz,
15463
- slides,
15464
- handout,
15465
- baseWorkspaceDir: input.baseWorkspaceDir
15466
- });
15467
- return {
15468
- milestoneId,
15469
- milestoneSlug,
15470
- status: "SUCCESS",
15471
- savedResult,
15472
- judgeReport: auditReport,
15473
- satelliteJudgeReports: Object.keys(satelliteReports).length > 0 ? satelliteReports : void 0,
15474
- gatesResolved: gates,
15475
- standardsCoverage,
15476
- artifacts: {
15477
- lesson,
15478
- activity,
15479
- codeLab,
15480
- selfLab,
15481
- quiz,
15482
- slides,
15483
- handout,
15484
- worksheet,
15485
- teacherGuide,
15486
- extension
15487
- }
15488
- };
15489
- }
15490
- async function generateRoadmapWorkflow(input) {
15491
- "use workflow";
15492
- const jobId = input.jobId || `job_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
15493
- const language = input.language || input.roadmap.target_language || "vi";
15494
- const topic = input.roadmap.topic || input.roadmap.goal || "General Curriculum";
15495
- const courseTitle = input.roadmap.title || `Curriculum: ${topic}`;
15496
- const batchSize = input.batchSize || 2;
15497
- const milestones = input.roadmap.milestones || [];
15498
- const workspaceManager = new LocalWorkspaceManager(input.baseWorkspaceDir);
15499
- const workspaceDir = await workspaceManager.initJobWorkspace(jobId, {
15500
- courseTitle,
15501
- topic,
15502
- language,
15503
- totalMilestones: milestones.length
15504
- });
15505
- const milestoneOutcomes = [];
15506
- for (let i = 0; i < milestones.length; i += batchSize) {
15507
- const batch = milestones.slice(i, i + batchSize);
15508
- const batchSettled = await Promise.allSettled(
15509
- batch.map(
15510
- (milestone) => generateMilestoneWorkflow({
15511
- jobId,
15512
- milestone,
15513
- language,
15514
- topic,
15515
- targetAudience: input.roadmap.target_audience,
15516
- techStack: input.roadmap.tech_stack || milestone.tech_keywords,
15517
- bundleTier: input.bundleTier,
15518
- gateSettings: input.gateSettings,
15519
- baseWorkspaceDir: input.baseWorkspaceDir,
15520
- modelOptions: input.modelOptions
15521
- })
15522
- )
15523
- );
15524
- for (const [idx, outcome] of batchSettled.entries()) {
15525
- const targetMilestone = batch[idx];
15526
- const mId = targetMilestone.id || targetMilestone.concept_code || `M${i + idx + 1}`;
15527
- if (outcome.status === "fulfilled") {
15528
- milestoneOutcomes.push({
15529
- milestoneId: mId,
15530
- status: "FULFILLED",
15531
- result: outcome.value
15532
- });
15533
- } else {
15534
- milestoneOutcomes.push({
15535
- milestoneId: mId,
15536
- status: "REJECTED",
15537
- error: outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)
15538
- });
15539
- }
15540
- }
15541
- if (i + batchSize < milestones.length) {
15542
- await workflow.sleep("2s");
15543
- }
15544
- }
15545
- const successfulMilestones = milestoneOutcomes.filter((m) => m.status === "FULFILLED").length;
15546
- const failedMilestones = milestoneOutcomes.filter((m) => m.status === "REJECTED").length;
15547
- let gitPublishResult = void 0;
15548
- let supabasePublishResult = void 0;
15549
- if (input.gitPublishOptions) {
15550
- gitPublishResult = await publishToGitStep({
15551
- jobId,
15552
- localJobDir: workspaceDir,
15553
- ...input.gitPublishOptions
15554
- });
15555
- }
15556
- if (input.supabasePublishOptions) {
15557
- supabasePublishResult = await publishToSupabaseStep({
15558
- jobId,
15559
- courseTitle,
15560
- topic,
15561
- language,
15562
- milestones: milestoneOutcomes.filter((m) => m.status === "FULFILLED" && m.result).map((m) => ({
15563
- milestoneId: m.milestoneId,
15564
- milestoneName: m.result.savedResult.milestoneSlug,
15565
- lessonSlug: m.result.savedResult.milestoneSlug
15566
- })),
15567
- ...input.supabasePublishOptions
15568
- });
15569
- }
15570
- return {
15571
- jobId,
15572
- courseTitle,
15573
- topic,
15574
- language,
15575
- totalMilestones: milestones.length,
15576
- successfulMilestones,
15577
- failedMilestones,
15578
- results: milestoneOutcomes,
15579
- gitPublishResult,
15580
- supabasePublishResult,
15581
- workspaceDir
15582
- };
15583
- }
15584
-
15585
- // src/workflow/workflows/singleArtifactWorkflow.ts
15586
- async function executeSingleArtifactStep(request) {
15587
- "use step";
15588
- const t0 = Date.now();
15589
- console.log(` \u23F3 [Step: Single Artifact] Generating ${request.artifactType.toUpperCase()} for "${request.topic.slice(0, 35)}"...`);
15590
- const res = await withRateLimitBackoff({
15591
- stepName: `generate-single-artifact-${request.artifactType}-${(request.topic || "topic").slice(0, 20)}`,
15592
- fn: async () => generateSingleArtifact(request)
15593
- });
15594
- console.log(` \u2705 [Step: Single Artifact] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (File: "${res.filename}")`);
15595
- return res;
15596
- }
15597
- async function generateSingleArtifactWorkflow(request) {
15598
- "use workflow";
15599
- return executeSingleArtifactStep(request);
15600
- }
15601
-
15602
- // src/index.ts
15603
- init_errors();
15604
-
15605
- // src/evaluators/deterministicStructuralLinter.ts
15606
- var DeterministicStructuralLinter = class {
15607
- /**
15608
- * Validates structural invariants across a complete lesson bundle.
15609
- */
15610
- static lintBundle(input) {
15611
- const { lesson, quiz, activity, slides, codeLab } = input;
15612
- const findings = [];
15613
- const strengths = [];
15614
- let score = 100;
15615
- const baseLessonId = lesson.lessonId;
15616
- const baseLanguage = lesson.language;
15617
- if (!lesson.title || lesson.title.trim().length === 0) {
15618
- score -= 20;
15619
- findings.push({
15620
- id: "struct_missing_lesson_title",
15621
- dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
15622
- severity: "CRITICAL",
15623
- title: "Missing Lesson Title",
15624
- description: "Lesson plan has an empty or whitespace title.",
15625
- remediationAdvice: "Provide a non-empty lesson title."
15626
- });
15627
- }
15628
- if (!lesson.learningObjectives || lesson.learningObjectives.length === 0) {
15629
- score -= 25;
15630
- findings.push({
15631
- id: "struct_empty_learning_objectives",
15632
- dimension: "CONSTRUCTIVE_ALIGNMENT",
15633
- severity: "CRITICAL",
15634
- title: "Empty Learning Objectives Array",
15635
- description: "Lesson plan contains 0 learning objectives.",
15636
- remediationAdvice: "Declare at least 2 structured Learning Objectives in the schema."
15637
- });
15638
- }
15639
- if (!lesson.sections || lesson.sections.length === 0) {
15640
- score -= 25;
15641
- findings.push({
15642
- id: "struct_empty_lesson_sections",
15643
- dimension: "5E_INSTRUCTIONAL_FIDELITY",
15644
- severity: "CRITICAL",
15645
- title: "Empty Lesson Sections Array",
15646
- description: "Lesson plan contains no instructional sections.",
15647
- remediationAdvice: "Provide structured lesson flow sections."
15648
- });
15649
- }
15650
- if (quiz && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
15651
- score -= 15;
15652
- findings.push({
15653
- id: "struct_quiz_id_mismatch",
15654
- dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
15655
- severity: "MAJOR",
15656
- title: "Quiz ID Contract Mismatch",
15657
- description: `Quiz ID "${quiz.quizId}" does not match Lesson ID "${baseLessonId}".`,
15658
- remediationAdvice: `Sync Quiz quizId to "${baseLessonId}".`,
15659
- affectedElement: quiz.quizId
15660
- });
15661
- }
15662
- if (activity && activity.lessonId !== baseLessonId) {
15663
- score -= 15;
15664
- findings.push({
15665
- id: "struct_act_id_mismatch",
15666
- dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
15667
- severity: "MAJOR",
15668
- title: "Activity Lesson ID Contract Mismatch",
15669
- description: `Activity lessonId "${activity.lessonId}" does not match Lesson ID "${baseLessonId}".`,
15670
- remediationAdvice: `Sync Activity lessonId to "${baseLessonId}".`,
15671
- affectedElement: activity.lessonId
15672
- });
15673
- }
15674
- if (slides && slides.lessonId !== baseLessonId) {
15675
- score -= 15;
15676
- findings.push({
15677
- id: "struct_slides_id_mismatch",
15678
- dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
15679
- severity: "MAJOR",
15680
- title: "Slide Deck Lesson ID Contract Mismatch",
15681
- description: `Slide deck lessonId "${slides.lessonId}" does not match Lesson ID "${baseLessonId}".`,
15682
- remediationAdvice: `Sync Slide deck lessonId to "${baseLessonId}".`,
15683
- affectedElement: slides.lessonId
15684
- });
15685
- }
15686
- const satellites = [
15687
- { type: "QUIZ", lang: quiz?.language },
15688
- { type: "ACT", lang: activity?.language },
15689
- { type: "SLIDE", lang: slides?.language }
15690
- ];
15691
- for (const sat of satellites) {
15692
- if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
15693
- score -= 25;
15694
- findings.push({
15695
- id: `struct_language_mismatch_${sat.type}`,
15696
- dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
15697
- severity: "CRITICAL",
15698
- title: `Language Policy Inconsistency in ${sat.type}`,
15699
- description: `Master Lesson is in "${baseLanguage}" but ${sat.type} is declared in "${sat.lang}".`,
15700
- remediationAdvice: `Set ${sat.type} language to "${baseLanguage}".`
15701
- });
15702
- }
15703
- }
15704
- if (quiz && quiz.questions) {
15705
- for (let i = 0; i < quiz.questions.length; i++) {
15706
- const q = quiz.questions[i];
15707
- const qId = q.id || `Q${i + 1}`;
15708
- const options = q.options || [];
15709
- if (options.length < 4) {
15710
- score -= 10;
15711
- findings.push({
15712
- id: `struct_quiz_option_count_${qId}`,
15713
- dimension: "MISCONCEPTION_RIGOR",
15714
- severity: "MAJOR",
15715
- title: `Structural Option Count Error in ${qId}`,
15716
- description: `Question ${qId} has ${options.length} options (standard schema requires 4).`,
15717
- remediationAdvice: "Ensure each question has 4 options (A, B, C, D).",
15718
- affectedElement: qId
15719
- });
15720
- }
15721
- const correctCount = options.filter((o) => o.isCorrect).length;
15722
- if (correctCount !== 1) {
15723
- score -= 20;
15724
- findings.push({
15725
- id: `struct_quiz_key_count_${qId}`,
15726
- dimension: "MISCONCEPTION_RIGOR",
15727
- severity: "CRITICAL",
15728
- title: `Key Assignment Error in ${qId}`,
15729
- description: `Question ${qId} has ${correctCount} correct options (must be exactly 1).`,
15730
- remediationAdvice: "Set `isCorrect: true` on exactly 1 option.",
15731
- affectedElement: qId
15732
- });
15733
- }
15734
- }
15735
- }
15736
- if (activity?.materialsBOM?.hardware && activity.materialsBOM.hardware.length > 0) {
15737
- const hwText = activity.materialsBOM.hardware.join(" ").toLowerCase();
15738
- const hasLED = hwText.includes("led");
15739
- const hasResistor = hwText.includes("\u0111i\u1EC7n tr\u1EDF") || hwText.includes("resistor") || hwText.includes("220") || hwText.includes("330") || hwText.includes("1k");
15740
- if (hasLED && !hasResistor) {
15741
- score -= 20;
15742
- findings.push({
15743
- id: "struct_hardware_unsafe_led_no_resistor",
15744
- dimension: "TECHNICAL_AUTHENTICITY",
15745
- severity: "CRITICAL",
15746
- title: "Unsafe Circuit BOM: LED without Current-Limiting Resistor",
15747
- description: "Hardware BOM includes LED without a 220\u03A9-1k\u03A9 resistor, causing circuit overload.",
15748
- remediationAdvice: "Add a 220\u03A9 current-limiting resistor to the hardware materials list."
15749
- });
15750
- }
15751
- }
15752
- score = Math.max(0, Math.min(100, score));
15753
- const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
15754
- if (passed) {
15755
- strengths.push("Deterministic structure, ID contracts, and schema invariants strictly verified.");
15756
- }
15757
- return {
15758
- passed,
15759
- structuralScore: score,
15760
- findings,
15761
- strengths
15762
- };
15763
- }
15764
- };
15765
-
15766
- // src/evaluators/academicAuditor.ts
15767
- var AcademicAuditor = class {
15768
- /**
15769
- * Evaluates a complete lesson bundle.
15770
- * Step 1 (Deterministic): Fast structural, contract, schema, and safety linting.
15771
- * Step 2 (Semantic LLM-as-a-Judge): Deep pedagogical, cognitive (Bloom), and misconception analysis with live frontier LLM.
15772
- */
15773
- static async auditLessonBundle(input) {
15774
- const { lesson, quiz, activity, slides, codeLab, modelOptions, executeLLMJudge = true } = input;
15775
- const structuralResult = DeterministicStructuralLinter.lintBundle({
15776
- lesson,
15777
- quiz,
15778
- activity,
15779
- slides,
15780
- codeLab
15781
- });
15782
- const allFindings = [...structuralResult.findings];
15783
- const strengths = [...structuralResult.strengths];
15784
- let semanticScore = null;
15785
- let semanticVerdict = "PASS";
15786
- if (executeLLMJudge) {
15787
- try {
15788
- const judgeReport = await auditCurriculumQualityFlow({
15789
- targetArtifactType: "LESSON_BUNDLE",
15790
- lessonId: lesson.lessonId,
15791
- expectedLanguage: lesson.language,
15792
- targetObjectives: (lesson.learningObjectives || []).map((lo) => ({
15793
- code: lo.code,
15794
- description: lo.description,
15795
- bloomLevel: lo.bloomLevel
15796
- })),
15797
- generatedContentJson: JSON.stringify({ lesson, quiz, activity, slides, codeLab }),
15798
- modelOptions
15799
- });
15800
- semanticScore = judgeReport.totalScore;
15801
- semanticVerdict = judgeReport.overallVerdict;
15802
- for (const criterion of judgeReport.criteria) {
15803
- if (!criterion.passed) {
15804
- allFindings.push({
15805
- id: `llm_judge_${criterion.name.toLowerCase().replace(/\s+/g, "_")}`,
15806
- dimension: "CONSTRUCTIVE_ALIGNMENT",
15807
- severity: criterion.score < 50 ? "CRITICAL" : "MAJOR",
15808
- title: `LLM-as-Judge Finding: ${criterion.name}`,
15809
- description: criterion.feedback,
15810
- remediationAdvice: judgeReport.actionableRepairPrompts.join("; ") || "Refine prompt context."
15811
- });
15812
- } else {
15813
- strengths.push(`[LLM-Judge] ${criterion.name}: ${criterion.feedback}`);
15814
- }
15815
- }
15816
- } catch (err) {
15817
- semanticScore = null;
15818
- semanticVerdict = "FAIL";
15819
- allFindings.push({
15820
- id: "llm_judge_connection_error",
15821
- dimension: "CONSTRUCTIVE_ALIGNMENT",
15822
- severity: "MINOR",
15823
- title: "LLM-as-Judge Skipped / Fallback",
15824
- description: `Semantic inference error: ${err.message || String(err)}`,
15825
- remediationAdvice: "Check API credentials for LLM-as-Judge."
15826
- });
15827
- }
14855
+ const allFindings = [...structuralResult.findings];
14856
+ const strengths = [...structuralResult.strengths];
14857
+ let semanticScore = null;
14858
+ let semanticVerdict = "PASS";
14859
+ if (executeLLMJudge) {
14860
+ try {
14861
+ const judgeReport = await auditCurriculumQualityFlow({
14862
+ targetArtifactType: "LESSON_BUNDLE",
14863
+ lessonId: lesson.lessonId,
14864
+ expectedLanguage: lesson.language,
14865
+ targetObjectives: (lesson.learningObjectives || []).map((lo) => ({
14866
+ code: lo.code,
14867
+ description: lo.description,
14868
+ bloomLevel: lo.bloomLevel
14869
+ })),
14870
+ generatedContentJson: JSON.stringify({ lesson, quiz, activity, slides, codeLab }),
14871
+ modelOptions
14872
+ });
14873
+ semanticScore = judgeReport.totalScore;
14874
+ semanticVerdict = judgeReport.overallVerdict;
14875
+ for (const criterion of judgeReport.criteria) {
14876
+ if (!criterion.passed) {
14877
+ allFindings.push({
14878
+ id: `llm_judge_${criterion.name.toLowerCase().replace(/\s+/g, "_")}`,
14879
+ dimension: "CONSTRUCTIVE_ALIGNMENT",
14880
+ severity: criterion.score < 50 ? "CRITICAL" : "MAJOR",
14881
+ title: `LLM-as-Judge Finding: ${criterion.name}`,
14882
+ description: criterion.feedback,
14883
+ remediationAdvice: judgeReport.actionableRepairPrompts.join("; ") || "Refine prompt context."
14884
+ });
14885
+ } else {
14886
+ strengths.push(`[LLM-Judge] ${criterion.name}: ${criterion.feedback}`);
14887
+ }
14888
+ }
14889
+ } catch (err) {
14890
+ semanticScore = null;
14891
+ semanticVerdict = "FAIL";
14892
+ allFindings.push({
14893
+ id: "llm_judge_connection_error",
14894
+ dimension: "CONSTRUCTIVE_ALIGNMENT",
14895
+ severity: "MINOR",
14896
+ title: "LLM-as-Judge Skipped / Fallback",
14897
+ description: `Semantic inference error: ${err.message || String(err)}`,
14898
+ remediationAdvice: "Check API credentials for LLM-as-Judge."
14899
+ });
14900
+ }
15828
14901
  }
15829
14902
  const criticalCount = allFindings.filter((f) => f.severity === "CRITICAL").length;
15830
14903
  const overallScore = executeLLMJudge && semanticScore !== null ? Math.round(structuralResult.structuralScore * 0.4 + semanticScore * 0.6) : structuralResult.structuralScore;
@@ -16705,32 +15778,316 @@ var MisconceptionEvaluator = class {
16705
15778
  missingExplanation = true;
16706
15779
  }
16707
15780
  }
16708
- if (missingExplanation) {
16709
- score -= 10;
16710
- findings.push({
16711
- id: `misconception_shallow_explanation_${qId}`,
16712
- dimension: "MISCONCEPTION_RIGOR",
16713
- severity: "MAJOR",
16714
- title: `Shallow Distractor Explanations in ${qId}`,
16715
- description: `Question ${qId} lacks detailed pedagogical explanations for all options. Students and teachers cannot diagnose root causes without option-level rationale.`,
16716
- remediationAdvice: "For EVERY option (A, B, C, D), explicitly write why the choice is correct or what mental misconception it represents.",
16717
- affectedElement: qId
15781
+ if (missingExplanation) {
15782
+ score -= 10;
15783
+ findings.push({
15784
+ id: `misconception_shallow_explanation_${qId}`,
15785
+ dimension: "MISCONCEPTION_RIGOR",
15786
+ severity: "MAJOR",
15787
+ title: `Shallow Distractor Explanations in ${qId}`,
15788
+ description: `Question ${qId} lacks detailed pedagogical explanations for all options. Students and teachers cannot diagnose root causes without option-level rationale.`,
15789
+ remediationAdvice: "For EVERY option (A, B, C, D), explicitly write why the choice is correct or what mental misconception it represents.",
15790
+ affectedElement: qId
15791
+ });
15792
+ } else {
15793
+ questionsWithFullExplanations++;
15794
+ }
15795
+ }
15796
+ if (questionsWithFullExplanations === questions.length && questions.length >= 3) {
15797
+ strengths.push("All quiz questions feature rigorous 4-option breakdown with comprehensive misconception analysis.");
15798
+ }
15799
+ score = Math.max(0, Math.min(100, score));
15800
+ return {
15801
+ score,
15802
+ weight: 0.2,
15803
+ passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
15804
+ strengths,
15805
+ findings
15806
+ };
15807
+ }
15808
+ };
15809
+
15810
+ // src/standards/standardsCoverageGate.ts
15811
+ function resolveStatementRef(ref, packs) {
15812
+ const [head, ...rest] = ref.split(":");
15813
+ const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
15814
+ const statementId = rest.length > 0 ? rest.join(":") : ref;
15815
+ for (const p of candidatePacks) {
15816
+ if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
15817
+ }
15818
+ return null;
15819
+ }
15820
+ function evaluateStandardsCoverage(input) {
15821
+ const rows = [];
15822
+ const aoToLo = /* @__PURE__ */ new Map();
15823
+ for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
15824
+ const loToRefs = /* @__PURE__ */ new Map();
15825
+ for (const lo of input.objectives) {
15826
+ loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
15827
+ }
15828
+ const taughtLOs = /* @__PURE__ */ new Set();
15829
+ for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
15830
+ const assessedLOs = /* @__PURE__ */ new Set();
15831
+ for (const q of input.quizQuestions) {
15832
+ if (q.alignedLO) assessedLOs.add(q.alignedLO);
15833
+ if (q.alignedAO) {
15834
+ const lo = aoToLo.get(q.alignedAO);
15835
+ if (lo) assessedLOs.add(lo);
15836
+ }
15837
+ }
15838
+ for (const pack of input.packs) {
15839
+ const mappingByStatement = /* @__PURE__ */ new Map();
15840
+ for (const m of pack.mappings ?? []) {
15841
+ const prev = mappingByStatement.get(m.statementId);
15842
+ if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
15843
+ mappingByStatement.set(m.statementId, m.kind);
15844
+ }
15845
+ }
15846
+ for (const statement of pack.statements) {
15847
+ const refFull = `${pack.manifest.id}:${statement.id}`;
15848
+ const issues = [];
15849
+ const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
15850
+ const kind = mappingByStatement.get(statement.id);
15851
+ const hasMapping = kind !== void 0;
15852
+ const isComplianceRelevant = kind === "covers";
15853
+ const hasActivity = los.some((lo) => taughtLOs.has(lo));
15854
+ const hasAssessment = los.some((lo) => assessedLOs.has(lo));
15855
+ let status;
15856
+ if (!hasMapping) status = "UNMAPPED";
15857
+ else if (!isComplianceRelevant) status = "PARTIAL";
15858
+ else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
15859
+ else status = "UNCOVERED";
15860
+ if (status === "UNCOVERED") {
15861
+ if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
15862
+ else {
15863
+ if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
15864
+ if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
15865
+ }
15866
+ }
15867
+ rows.push({
15868
+ packId: pack.manifest.id,
15869
+ statementId: statement.id,
15870
+ statementText: Object.values(statement.texts)[0] ?? "",
15871
+ status,
15872
+ objectives: los,
15873
+ hasActivity,
15874
+ hasAssessment,
15875
+ issues
15876
+ });
15877
+ }
15878
+ }
15879
+ for (const lo of input.objectives) {
15880
+ for (const r of lo.standardRefs ?? []) {
15881
+ if (!resolveStatementRef(r, input.packs)) {
15882
+ rows.push({
15883
+ packId: "(unresolved)",
15884
+ statementId: r,
15885
+ statementText: "",
15886
+ status: "UNCOVERED",
15887
+ objectives: [lo.code],
15888
+ hasActivity: false,
15889
+ hasAssessment: false,
15890
+ issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
16718
15891
  });
16719
- } else {
16720
- questionsWithFullExplanations++;
16721
15892
  }
16722
15893
  }
16723
- if (questionsWithFullExplanations === questions.length && questions.length >= 3) {
16724
- strengths.push("All quiz questions feature rigorous 4-option breakdown with comprehensive misconception analysis.");
15894
+ }
15895
+ const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
15896
+ const covered = complianceRows.filter((r) => r.status === "COVERED").length;
15897
+ const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
15898
+ const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
15899
+ const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
15900
+ const lines = [
15901
+ "# Standards Coverage Report",
15902
+ "",
15903
+ `- Verdict: **${verdict}**`,
15904
+ `- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
15905
+ `- Unresolved standardRefs: ${unresolvedCount}`,
15906
+ "",
15907
+ "| Pack | Statement | Status | LOs | Activity | Assessment |",
15908
+ "|---|---|---|---|---|---|",
15909
+ ...rows.map(
15910
+ (r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
15911
+ )
15912
+ ];
15913
+ const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
15914
+ if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
15915
+ return {
15916
+ verdict,
15917
+ coveragePct,
15918
+ rows,
15919
+ summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
15920
+ rawMarkdownReport: lines.join("\n")
15921
+ };
15922
+ }
15923
+ var StandardsRegistryAdapter = class {
15924
+ client;
15925
+ constructor(config = {}) {
15926
+ if (config.client) {
15927
+ this.client = config.client;
15928
+ return;
16725
15929
  }
16726
- score = Math.max(0, Math.min(100, score));
16727
- return {
16728
- score,
16729
- weight: 0.2,
16730
- passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
16731
- strengths,
16732
- findings
16733
- };
15930
+ const url = config.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || "";
15931
+ const key = config.supabaseServiceRoleKey || process.env.SUPABASE_SERVICE_ROLE_KEY || config.supabaseAnonKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
15932
+ if (!url || !key) {
15933
+ throw new Error("StandardsRegistryAdapter: Supabase URL/key missing (pass client or env).");
15934
+ }
15935
+ this.client = supabaseJs.createClient(url, key);
15936
+ }
15937
+ // ─── Intake (write path — service role) ───────────────────────────────────
15938
+ /** Persist a schema-validated pack as a new framework (status=draft). */
15939
+ async importPack(pack, opts) {
15940
+ const parsed = FrameworkPackSchema.safeParse(pack);
15941
+ if (!parsed.success) {
15942
+ throw new Error("importPack: pack failed schema validation: " + parsed.error.issues.map((i) => i.path.join(".") + ": " + i.message).join("; "));
15943
+ }
15944
+ const p = parsed.data;
15945
+ const { data: fw, error: fwErr } = await this.client.from("standards_frameworks").insert({
15946
+ pack_id: p.manifest.id,
15947
+ content_version: p.manifest.contentVersion,
15948
+ name: p.manifest.name,
15949
+ spec_version: p.manifest.specVersion,
15950
+ subject: p.manifest.subject,
15951
+ languages: p.manifest.languages,
15952
+ grade_model: p.manifest.gradeModel,
15953
+ provenance: p.manifest.provenance,
15954
+ trust: p.manifest.trust,
15955
+ status: "draft",
15956
+ organization_code: opts?.organizationCode ?? (p.manifest.trust === "verified" ? null : p.manifest.provenance.importedBy?.replace(/^org:/, "") || null),
15957
+ original_file_path: opts?.originalFilePath ?? null,
15958
+ created_by: opts?.createdBy ?? null
15959
+ }).select("id").single();
15960
+ if (fwErr) throw new Error("importPack: framework insert failed: " + fwErr.message);
15961
+ const frameworkId = fw.id;
15962
+ const statementRows = p.statements.map((s) => ({
15963
+ framework_id: frameworkId,
15964
+ statement_id: s.id,
15965
+ parent_statement_id: s.parentId ?? null,
15966
+ grade_min: s.gradeBand[0],
15967
+ grade_max: s.gradeBand[1],
15968
+ texts: s.texts,
15969
+ classifications: s.classifications ?? [],
15970
+ bloom_hint: s.bloomHint ?? null,
15971
+ keywords: s.keywords ?? [],
15972
+ source_ref: s.sourceRef ?? null,
15973
+ provenance: s.provenance
15974
+ }));
15975
+ const { error: stErr, count: stCount } = await this.client.from("standards_statements").insert(statementRows, { count: "exact" });
15976
+ if (stErr) throw new Error("importPack: statement insert failed: " + stErr.message);
15977
+ let mappingCount = 0;
15978
+ if (p.mappings && p.mappings.length > 0) {
15979
+ const mappingRows = p.mappings.map((m) => ({
15980
+ framework_id: frameworkId,
15981
+ statement_id: m.statementId,
15982
+ target_ref: m.targetRef,
15983
+ kind: m.kind,
15984
+ confidence: m.confidence,
15985
+ provenance: m.provenance
15986
+ }));
15987
+ const { error: mpErr, count: mpCount } = await this.client.from("standards_mappings").insert(mappingRows, { count: "exact" });
15988
+ if (mpErr) throw new Error("importPack: mapping insert failed: " + mpErr.message);
15989
+ mappingCount = mpCount ?? mappingRows.length;
15990
+ }
15991
+ return { frameworkDbId: frameworkId, statementsInserted: stCount ?? statementRows.length, mappingsInserted: mappingCount };
15992
+ }
15993
+ /** Activate a draft framework (immutable version is now live). */
15994
+ async activatePack(frameworkDbId) {
15995
+ const { error } = await this.client.from("standards_frameworks").update({ status: "active" }).eq("id", frameworkDbId).eq("status", "draft");
15996
+ if (error) throw new Error("activatePack failed: " + error.message);
15997
+ }
15998
+ async deprecatePack(frameworkDbId) {
15999
+ const { error } = await this.client.from("standards_frameworks").update({ status: "deprecated" }).eq("id", frameworkDbId);
16000
+ if (error) throw new Error("deprecatePack failed: " + error.message);
16001
+ }
16002
+ async adoptPack(frameworkDbId, organizationCode, adoptedBy) {
16003
+ const { error } = await this.client.from("standards_org_adoptions").upsert(
16004
+ { framework_id: frameworkDbId, organization_code: organizationCode, adopted_by: adoptedBy ?? null },
16005
+ { onConflict: "organization_code,framework_id" }
16006
+ );
16007
+ if (error) throw new Error("adoptPack failed: " + error.message);
16008
+ }
16009
+ // ─── Runtime (read path — generation) ─────────────────────────────────────
16010
+ /**
16011
+ * Load all packs usable by an org: platform packs (verified, org IS NULL) +
16012
+ * org packs + org adoptions. Hydrated into the same FrameworkPack shape the
16013
+ * in-memory Phase-1 components consume (injector / coverage gate / judge).
16014
+ */
16015
+ async loadPacksForOrg(organizationCode) {
16016
+ let query = this.client.from("standards_frameworks").select("*, standards_org_adoptions(organization_code)").eq("status", "active");
16017
+ const { data: frameworks, error } = await query;
16018
+ if (error) throw new Error("loadPacksForOrg: " + error.message);
16019
+ const visible = (frameworks ?? []).filter((f) => {
16020
+ const own = f.organization_code && organizationCode && f.organization_code === organizationCode;
16021
+ const global = !f.organization_code;
16022
+ const adopted = organizationCode && (f.standards_org_adoptions ?? []).some((a) => a.organization_code === organizationCode);
16023
+ return global || own || adopted;
16024
+ });
16025
+ if (visible.length === 0) return [];
16026
+ const ids = visible.map((f) => f.id);
16027
+ const [{ data: statements, error: stErr }, { data: mappings, error: mpErr }, { data: overrides, error: ovErr }] = await Promise.all([
16028
+ this.client.from("standards_statements").select("*").in("framework_id", ids),
16029
+ this.client.from("standards_mappings").select("*").in("framework_id", ids),
16030
+ this.client.from("standards_mapping_overrides").select("*").in("framework_id", ids)
16031
+ ]);
16032
+ if (stErr) throw new Error("loadPacksForOrg statements: " + stErr.message);
16033
+ const overridesByFw = /* @__PURE__ */ new Map();
16034
+ for (const o of overrides ?? []) {
16035
+ const list = overridesByFw.get(o.framework_id) ?? [];
16036
+ list.push(o);
16037
+ overridesByFw.set(o.framework_id, list);
16038
+ }
16039
+ return visible.map((f) => {
16040
+ const stmts = (statements ?? []).filter((s) => s.framework_id === f.id).map((s) => ({
16041
+ id: s.statement_id,
16042
+ parentId: s.parent_statement_id ?? void 0,
16043
+ gradeBand: [s.grade_min, s.grade_max],
16044
+ texts: s.texts,
16045
+ classifications: s.classifications ?? [],
16046
+ bloomHint: s.bloom_hint ?? void 0,
16047
+ keywords: s.keywords ?? [],
16048
+ sourceRef: s.source_ref ?? void 0,
16049
+ provenance: s.provenance ?? { method: "imported" }
16050
+ }));
16051
+ const ov = overridesByFw.get(f.id) ?? [];
16052
+ const retired = new Set(ov.filter((o) => o.kind === "retired").map((o) => o.statement_id + "|" + o.target_ref));
16053
+ const embedded = (mappings ?? []).filter((m) => m.framework_id === f.id).map((m) => ({
16054
+ statementId: m.statement_id,
16055
+ targetRef: m.target_ref,
16056
+ kind: m.kind,
16057
+ confidence: Number(m.confidence),
16058
+ provenance: m.provenance ?? { method: "imported" }
16059
+ }));
16060
+ const overlay = ov.filter((o) => o.kind !== "retired").map((o) => ({
16061
+ statementId: o.statement_id,
16062
+ targetRef: o.target_ref,
16063
+ kind: o.kind,
16064
+ confidence: Number(o.confidence),
16065
+ provenance: { method: "human", reviewedBy: o.created_by ?? "overlay", note: o.note ?? void 0 }
16066
+ }));
16067
+ const bridge = /* @__PURE__ */ new Map();
16068
+ for (const m of embedded) bridge.set(m.statementId + "|" + m.targetRef, m);
16069
+ for (const m of overlay) bridge.set(m.statementId + "|" + m.targetRef, m);
16070
+ const finalMappings = [...bridge.values()].filter((m) => !retired.has(m.statementId + "|" + m.targetRef));
16071
+ const hydrated = FrameworkPackSchema.safeParse({
16072
+ manifest: {
16073
+ id: f.pack_id,
16074
+ name: f.name,
16075
+ specVersion: f.spec_version || "1.0",
16076
+ contentVersion: f.content_version,
16077
+ subject: f.subject,
16078
+ languages: f.languages,
16079
+ gradeModel: f.grade_model,
16080
+ provenance: f.provenance,
16081
+ trust: f.trust
16082
+ },
16083
+ statements: stmts,
16084
+ mappings: finalMappings
16085
+ });
16086
+ if (!hydrated.success) {
16087
+ throw new Error('loadPacksForOrg: hydrated pack "' + f.pack_id + '" failed schema: ' + hydrated.error.issues[0]?.message);
16088
+ }
16089
+ return { ...hydrated.data, dbId: f.id, status: f.status, organizationCode: f.organization_code ?? null };
16090
+ });
16734
16091
  }
16735
16092
  };
16736
16093
 
@@ -17408,8 +16765,6 @@ exports.WorksheetSchema = WorksheetSchema;
17408
16765
  exports.activityTools = activityTools;
17409
16766
  exports.analystTools = analystTools;
17410
16767
  exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
17411
- exports.approvalHookToken = approvalHookToken;
17412
- exports.approvalPayloadSchema = approvalPayloadSchema;
17413
16768
  exports.assertAcyclic = assertAcyclic;
17414
16769
  exports.assessorTools = assessorTools;
17415
16770
  exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
@@ -17450,7 +16805,6 @@ exports.ensureExpositionForLesson = ensureExpositionForLesson;
17450
16805
  exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
17451
16806
  exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
17452
16807
  exports.executeCurriculumCommand = executeCurriculumCommand;
17453
- exports.executeSingleArtifactStep = executeSingleArtifactStep;
17454
16808
  exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
17455
16809
  exports.expositionCacheKey = expositionCacheKey;
17456
16810
  exports.extractScopeSequenceRows = extractScopeSequenceRows;
@@ -17461,37 +16815,24 @@ exports.formatQuizzesToCsv = formatQuizzesToCsv;
17461
16815
  exports.fulfillMediaLedger = fulfillMediaLedger;
17462
16816
  exports.gateModeFor = gateModeFor;
17463
16817
  exports.generateActivityFlow = generateActivityFlow;
17464
- exports.generateActivityStep = generateActivityStep;
17465
16818
  exports.generateCodeLabFlow = generateCodeLabFlow;
17466
- exports.generateCodeLabStep = generateCodeLabStep;
17467
16819
  exports.generateDiagnosticQuizFlow = generateDiagnosticQuizFlow;
17468
- exports.generateDiagnosticQuizStep = generateDiagnosticQuizStep;
17469
16820
  exports.generateEducationalImage = generateEducationalImage;
17470
16821
  exports.generateExtensionFlow = generateExtensionFlow;
17471
- exports.generateExtensionStep = generateExtensionStep;
17472
16822
  exports.generateHandoutFlow = generateHandoutFlow;
17473
- exports.generateHandoutStep = generateHandoutStep;
17474
16823
  exports.generateLessonMasterFlow = generateLessonMasterFlow;
17475
- exports.generateMasterLessonStep = generateMasterLessonStep;
17476
16824
  exports.generateMilestoneCurriculumBundle = generateMilestoneCurriculumBundle;
17477
- exports.generateMilestoneWorkflow = generateMilestoneWorkflow;
17478
16825
  exports.generatePhase1SotArtifacts = generatePhase1SotArtifacts;
17479
16826
  exports.generatePhase2SotArtifacts = generatePhase2SotArtifacts;
17480
16827
  exports.generateProjectInstruction = generateProjectInstruction;
17481
16828
  exports.generateProjectInstructionFlow = generateProjectInstructionFlow;
17482
16829
  exports.generateRoadmapCurriculum = generateRoadmapCurriculum;
17483
- exports.generateRoadmapWorkflow = generateRoadmapWorkflow;
17484
16830
  exports.generateSelfLabFlow = generateSelfLabFlow;
17485
- exports.generateSelfLabStep = generateSelfLabStep;
17486
16831
  exports.generateSelfPacedBundle = generateSelfPacedBundle;
17487
16832
  exports.generateSingleArtifact = generateSingleArtifact;
17488
- exports.generateSingleArtifactWorkflow = generateSingleArtifactWorkflow;
17489
16833
  exports.generateSlidesFlow = generateSlidesFlow;
17490
- exports.generateSlidesStep = generateSlidesStep;
17491
16834
  exports.generateTeacherGuideFlow = generateTeacherGuideFlow;
17492
- exports.generateTeacherGuideStep = generateTeacherGuideStep;
17493
16835
  exports.generateWorksheetFlow = generateWorksheetFlow;
17494
- exports.generateWorksheetStep = generateWorksheetStep;
17495
16836
  exports.getAIModel = getAIModel;
17496
16837
  exports.getArtifactMetadata = getArtifactMetadata;
17497
16838
  exports.getDesignatedFallbackChain = getDesignatedFallbackChain;
@@ -17512,9 +16853,6 @@ exports.isExpositionFresh = isExpositionFresh;
17512
16853
  exports.isModelAllowed = isModelAllowed;
17513
16854
  exports.isProviderEnabled = isProviderEnabled;
17514
16855
  exports.isTranslationDue = isTranslationDue;
17515
- exports.judgeMasterLessonStep = judgeMasterLessonStep;
17516
- exports.judgeSatelliteStep = judgeSatelliteStep;
17517
- exports.lessonApprovalHook = lessonApprovalHook;
17518
16856
  exports.lintAndSanitizeArtifact = lintAndSanitizeArtifact;
17519
16857
  exports.lintCurriculumFramework = lintCurriculumFramework;
17520
16858
  exports.lintFrameworkPack = lintFrameworkPack;
@@ -17528,13 +16866,10 @@ exports.parseRoadmapJsonToProjectPayload = parseRoadmapJsonToProjectPayload;
17528
16866
  exports.produceBatchLessons = produceBatchLessons;
17529
16867
  exports.produceSingleLesson = produceSingleLesson;
17530
16868
  exports.publishToGitHub = publishToGitHub;
17531
- exports.publishToGitStep = publishToGitStep;
17532
16869
  exports.publishToSupabase = publishToSupabase;
17533
- exports.publishToSupabaseStep = publishToSupabaseStep;
17534
16870
  exports.rankGenCandidates = rankGenCandidates;
17535
16871
  exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
17536
16872
  exports.renderMediaPlaceholder = renderMediaPlaceholder;
17537
- exports.repairMasterLessonStep = repairMasterLessonStep;
17538
16873
  exports.researcherTools = researcherTools;
17539
16874
  exports.resolveGateSettings = resolveGateSettings;
17540
16875
  exports.resolveStandardsPacks = resolveStandardsPacks;
@@ -17543,7 +16878,6 @@ exports.resolveTranslationTargets = resolveTranslationTargets;
17543
16878
  exports.reviewerTools = reviewerTools;
17544
16879
  exports.runCurriculumAIInference = runCurriculumAIInference;
17545
16880
  exports.safeParseJson = safeParseJson;
17546
- exports.saveMilestoneToWorkspaceStep = saveMilestoneToWorkspaceStep;
17547
16881
  exports.searchEducationalImages = searchEducationalImages;
17548
16882
  exports.searchEducationalVideos = searchEducationalVideos;
17549
16883
  exports.selectStatementsForLesson = selectStatementsForLesson;
@@ -17570,6 +16904,5 @@ exports.validateFrameworkPack = validateFrameworkPack;
17570
16904
  exports.validateMarkdownTables = validateMarkdownTables;
17571
16905
  exports.validateMermaidSyntax = validateMermaidSyntax;
17572
16906
  exports.withAutoRepair = withAutoRepair;
17573
- exports.withRateLimitBackoff = withRateLimitBackoff;
17574
16907
  //# sourceMappingURL=index.cjs.map
17575
16908
  //# sourceMappingURL=index.cjs.map