@thanh01.pmt/curriculum-kit 1.4.8 → 1.4.10

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
@@ -8417,13 +8417,16 @@ function computeContentHash(content) {
8417
8417
  return "sha256:" + crypto__default.default.createHash("sha256").update(content.trim(), "utf-8").digest("hex");
8418
8418
  }
8419
8419
  var STANDARD_SOT_FILES = [
8420
- "PROJECT_BRIEF.md",
8421
8420
  "LEARNER_PROFILE.md",
8421
+ "PROJECT_BRIEF.md",
8422
+ "REFERENCE_PACK.md",
8423
+ "PROJECT_GRAPH.json",
8424
+ "HYBRID_GRAPH.json",
8425
+ "SECTION_LANGUAGE_CONTRACT.md",
8422
8426
  "CURRICULUM_FRAMEWORK.md",
8423
- "PROJECT_STATUS.md",
8424
8427
  "CONTENT_STYLE_GUIDE.md",
8425
8428
  "ART_DIRECTION.md",
8426
- "REFERENCE_PACK.md"
8429
+ "ALIGNMENT_MATRIX.md"
8427
8430
  ];
8428
8431
  var FileSystemCurriculumAdapter = class {
8429
8432
  baseDir;
@@ -8465,19 +8468,33 @@ var FileSystemCurriculumAdapter = class {
8465
8468
  async listSotDocuments(projectId) {
8466
8469
  const projectDir = this.getProjectDir(projectId);
8467
8470
  const sotDir = path3__default.default.join(projectDir, "_sot");
8468
- return STANDARD_SOT_FILES.map((filename) => {
8471
+ const discoveredFiles = new Set(STANDARD_SOT_FILES);
8472
+ if (fs2__default.default.existsSync(sotDir)) {
8473
+ try {
8474
+ const diskFiles = fs2__default.default.readdirSync(sotDir);
8475
+ for (const f of diskFiles) {
8476
+ if (f.startsWith(".") || f.endsWith(".review.json")) continue;
8477
+ const fullPath = path3__default.default.join(sotDir, f);
8478
+ if (fs2__default.default.statSync(fullPath).isFile()) {
8479
+ discoveredFiles.add(f);
8480
+ }
8481
+ }
8482
+ } catch {
8483
+ }
8484
+ }
8485
+ return Array.from(discoveredFiles).map((filename) => {
8469
8486
  const p = fs2__default.default.existsSync(path3__default.default.join(sotDir, filename)) ? path3__default.default.join(sotDir, filename) : fs2__default.default.existsSync(path3__default.default.join(projectDir, filename)) ? path3__default.default.join(projectDir, filename) : null;
8470
8487
  if (p && fs2__default.default.existsSync(p)) {
8471
8488
  const stats = fs2__default.default.statSync(p);
8472
8489
  return {
8473
- name: filename.replace(".md", "").replace(/_/g, " "),
8490
+ name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
8474
8491
  filename,
8475
8492
  exists: true,
8476
8493
  sizeBytes: stats.size
8477
8494
  };
8478
8495
  }
8479
8496
  return {
8480
- name: filename.replace(".md", "").replace(/_/g, " "),
8497
+ name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
8481
8498
  filename,
8482
8499
  exists: false,
8483
8500
  sizeBytes: 0
@@ -8723,11 +8740,23 @@ var SupabaseCurriculumAdapter = class {
8723
8740
  return null;
8724
8741
  }
8725
8742
  async listSotDocuments(projectId) {
8743
+ const discoveredFiles = new Set(STANDARD_SOT_FILES);
8744
+ try {
8745
+ const { data, error } = await this.client.storage.from(this.bucketName).list(`${projectId}/_sot`);
8746
+ if (!error && data) {
8747
+ for (const item of data) {
8748
+ if (item.name && !item.name.startsWith(".") && !item.name.endsWith(".review.json")) {
8749
+ discoveredFiles.add(item.name);
8750
+ }
8751
+ }
8752
+ }
8753
+ } catch {
8754
+ }
8726
8755
  const results = [];
8727
- for (const filename of STANDARD_SOT_FILES) {
8756
+ for (const filename of discoveredFiles) {
8728
8757
  const content = await this.readSotDocument(projectId, filename);
8729
8758
  results.push({
8730
- name: filename.replace(".md", "").replace(/_/g, " "),
8759
+ name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
8731
8760
  filename,
8732
8761
  exists: !!content,
8733
8762
  content: content || void 0,
@@ -9104,10 +9133,12 @@ async function auditQualityReport(storage, projectId) {
9104
9133
  computedScore += Math.round(bloomPassCount / total * 20);
9105
9134
  computedScore += Math.round(Math.min(syntaxExecutableCount, total) / total * 20);
9106
9135
  computedScore += Math.round(scopeCompletenessCount / total * 20);
9136
+ const sotCompletedCount = status.sotReadiness.filter((s) => s.exists).length;
9137
+ const sotTotalCount = status.sotReadiness.length;
9107
9138
  if (sotReady) {
9108
- details.push("\u2705 N\u1EC1n t\u1EA3ng SOT (7/7 t\xE0i li\u1EC7u) s\u1EB5n s\xE0ng.");
9139
+ details.push(`\u2705 N\u1EC1n t\u1EA3ng SOT (${sotCompletedCount}/${sotTotalCount} t\xE0i li\u1EC7u) s\u1EB5n s\xE0ng.`);
9109
9140
  } else {
9110
- details.push("\u26A0\uFE0F C\u1EA7n ho\xE0n thi\u1EC7n \u0111\u1EA7y \u0111\u1EE7 t\xE0i li\u1EC7u SOT c\u01A1 s\u1EDF.");
9141
+ details.push(`\u26A0\uFE0F C\u1EA7n ho\xE0n thi\u1EC7n \u0111\u1EA7y \u0111\u1EE7 t\xE0i li\u1EC7u SOT c\u01A1 s\u1EDF (${sotCompletedCount}/${sotTotalCount}).`);
9111
9142
  }
9112
9143
  details.push(`\u{1F3AF} Ph\u1EA1m vi h\u1ECDc li\u1EC7u theo SOT (Artifact Scope): [${artifactScope.join(", ")}].`);
9113
9144
  if (pedagogy5EPass) {
@@ -9123,7 +9154,7 @@ async function auditQualityReport(storage, projectId) {
9123
9154
  details.push(`\u2705 \u0110\u1ED9 ph\u1EE7 \u0111\u1EA7y \u0111\u1EE7 c\xE1c h\u1ECDc li\u1EC7u trong Artifact Scope: ${scopeCompletenessCount}/${total} b\xE0i h\u1ECDc.`);
9124
9155
  const rawMarkdownReport = `## \u{1F6E1}\uFE0F B\xC1O C\xC1O KI\u1EC2M \u0110\u1ECANH CH\u1EA4T L\u01AF\u1EE2NG (QUALITY AUDIT): \`${projectId.toUpperCase()}\`
9125
9156
  - **\u0110i\u1EC3m th\u1EA9m \u0111\u1ECBnh ch\u1EA5t l\u01B0\u1EE3ng:** **${computedScore}/100**
9126
- - **Tr\u1EA1ng th\xE1i SOT:** ${sotReady ? "\u2705 7/7 File SOT ho\xE0n t\u1EA5t" : "\u26A0\uFE0F C\u1EA7n b\u1ED5 sung t\xE0i li\u1EC7u SOT thi\u1EBFu"}.
9157
+ - **Tr\u1EA1ng th\xE1i SOT:** ${sotReady ? `\u2705 ${sotCompletedCount}/${sotTotalCount} File SOT ho\xE0n t\u1EA5t` : `\u26A0\uFE0F C\u1EA7n b\u1ED5 sung t\xE0i li\u1EC7u SOT thi\u1EBFu (${sotCompletedCount}/${sotTotalCount})`}.
9127
9158
  - **Ph\u1EA1m vi h\u1ECDc li\u1EC7u (SOT Artifact Scope):** \`${artifactScope.join(", ")}\`
9128
9159
  - **C\u1EA5u tr\xFAc S\u01B0 ph\u1EA1m (5E / EDP):** ${pedagogy5ECount}/${total} b\xE0i h\u1ECDc \u0111\u1EA1t chu\u1EA9n ph\xE2n pha.
9129
9160
  - **Thang \u0111o nh\u1EADn th\u1EE9c Bloom:** ${bloomPassCount}/${total} b\xE0i h\u1ECDc \u0111\u1EA1t ma tr\u1EADn m\u1EE5c ti\xEAu.
@@ -9225,7 +9256,9 @@ function extractScopeSequenceRows(framework) {
9225
9256
  const trimmed = line.trim();
9226
9257
  if (!trimmed.startsWith("|")) continue;
9227
9258
  const lower = trimmed.toLowerCase();
9228
- if (lower.includes("lesson code") && lower.includes("learning objective")) {
9259
+ const hasCodeCol = lower.includes("lesson code") || lower.includes("m\xE3 b\xE0i") || lower.includes("m\xE3 b\xE0i h\u1ECDc");
9260
+ const hasObjCol = lower.includes("learning objective") || lower.includes("m\u1EE5c ti\xEAu") || lower.includes("m\u1EE5c ti\xEAu h\u1ECDc t\u1EADp");
9261
+ if (hasCodeCol && hasObjCol) {
9229
9262
  headerFound = true;
9230
9263
  continue;
9231
9264
  }
@@ -27269,13 +27302,13 @@ var DeterministicPipelineRunner = class {
27269
27302
 
27270
27303
  // src/services/prefillService.ts
27271
27304
  init_streamRunner();
27272
- var DEFAULT_STREAM_IDLE_MS = 9e4;
27305
+ var DEFAULT_STREAM_IDLE_MS = 18e4;
27273
27306
  var DEFAULT_STREAM_TOTAL_MS = 48e4;
27274
27307
  var LAYER_IDLE_BUDGET_MS = {
27275
- 1: 9e4,
27276
- 2: 9e4,
27308
+ 1: 18e4,
27309
+ 2: 18e4,
27277
27310
  3: 18e4
27278
- // Layer 3 synthesizes all accumulated context + pathways (3 minutes idle protection)
27311
+ // 3 minutes idle protection across all layers for free-tier reasoning models
27279
27312
  };
27280
27313
  var LAYER_TOTAL_BUDGET_MS = {
27281
27314
  1: 48e4,
@@ -27336,10 +27369,20 @@ function extractStreamChunk(part) {
27336
27369
  }
27337
27370
  return {};
27338
27371
  }
27339
- function createStreamAbortSignal(budget) {
27372
+ function createStreamAbortSignal(budget, parentSignal) {
27340
27373
  const controller = new AbortController();
27341
27374
  let idleTimer = null;
27342
27375
  let totalTimer = null;
27376
+ const onParentAbort = () => {
27377
+ controller.abort(parentSignal?.reason || new Error("Aborted by parent signal"));
27378
+ };
27379
+ if (parentSignal) {
27380
+ if (parentSignal.aborted) {
27381
+ controller.abort(parentSignal.reason || new Error("Parent signal already aborted"));
27382
+ } else {
27383
+ parentSignal.addEventListener("abort", onParentAbort, { once: true });
27384
+ }
27385
+ }
27343
27386
  const armIdle = () => {
27344
27387
  if (idleTimer) clearTimeout(idleTimer);
27345
27388
  if (budget.idleMs > 0) {
@@ -27366,6 +27409,9 @@ function createStreamAbortSignal(budget) {
27366
27409
  dispose: () => {
27367
27410
  if (idleTimer) clearTimeout(idleTimer);
27368
27411
  if (totalTimer) clearTimeout(totalTimer);
27412
+ if (parentSignal) {
27413
+ parentSignal.removeEventListener("abort", onParentAbort);
27414
+ }
27369
27415
  idleTimer = null;
27370
27416
  totalTimer = null;
27371
27417
  }
@@ -27477,7 +27523,7 @@ function safeParseJson(rawText) {
27477
27523
  }
27478
27524
  return null;
27479
27525
  }
27480
- async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget) {
27526
+ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget, signal) {
27481
27527
  const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
27482
27528
  const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
27483
27529
  const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
@@ -27509,7 +27555,6 @@ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onC
27509
27555
  const nemotronModels = [
27510
27556
  "nvidia/nemotron-3-ultra-550b-a55b",
27511
27557
  "nvidia/nemotron-3.5-lightning-30b-a3b",
27512
- "nvidia/nemotron-3-super-120b-a12b",
27513
27558
  "nvidia/llama-3.1-nemotron-70b-instruct"
27514
27559
  ].filter((m) => isModelAllowed(m));
27515
27560
  for (const m of nemotronModels) {
@@ -27560,8 +27605,12 @@ THINKING DIRECTIVE: Do ALL your reasoning internally BEFORE emitting output. Do
27560
27605
  totalMs: budget?.totalMs ?? DEFAULT_STREAM_TOTAL_MS
27561
27606
  };
27562
27607
  for (const candidate of candidates) {
27608
+ if (signal?.aborted) {
27609
+ console.log(`[curriculum-kit:VercelAI] AbortSignal already aborted, cancelling candidate loop.`);
27610
+ break;
27611
+ }
27563
27612
  const t0 = Date.now();
27564
- const abort = createStreamAbortSignal(resolvedBudget);
27613
+ const abort = createStreamAbortSignal(resolvedBudget, signal);
27565
27614
  try {
27566
27615
  const modelInstance = getAIModel({
27567
27616
  provider: candidate.provider,
@@ -27593,6 +27642,13 @@ THINKING DIRECTIVE: Do ALL your reasoning internally BEFORE emitting output. Do
27593
27642
  }
27594
27643
  } catch (e) {
27595
27644
  console.warn(`[curriculum-kit:VercelAI] Candidate ${candidate.provider} (${candidate.model}) failed in ${Date.now() - t0}ms:`, e?.message || e);
27645
+ const isClientAborted = Boolean(
27646
+ signal?.aborted || e?.name === "AbortError" && signal?.aborted || e?.message && (e.message.includes("Controller is already closed") || e.message.includes("The operation was aborted") || e.message.includes("Aborted by parent signal"))
27647
+ );
27648
+ if (isClientAborted) {
27649
+ console.log(`[curriculum-kit:VercelAI] Client aborted or stream closed, halting candidate fallback loop.`);
27650
+ break;
27651
+ }
27596
27652
  } finally {
27597
27653
  abort.dispose();
27598
27654
  }
@@ -27644,7 +27700,8 @@ Return concise JSON matching:
27644
27700
  totalMs: options.timeoutMs,
27645
27701
  model: options.model,
27646
27702
  provider: options.provider
27647
- })
27703
+ }),
27704
+ options.signal
27648
27705
  );
27649
27706
  let parsedResearch = {
27650
27707
  groundTruthSummary: `Verified ecosystem standards for ${projectName}`,
@@ -27854,7 +27911,8 @@ REQUIREMENT: Synthesize ALL parameters above \u2014 every single line in FULL AC
27854
27911
  totalMs: options.timeoutMs,
27855
27912
  model: options.model,
27856
27913
  provider: options.provider
27857
- })
27914
+ }),
27915
+ options.signal
27858
27916
  );
27859
27917
  const parsed = safeParseJson(rawContent);
27860
27918
  return {
@@ -27870,6 +27928,7 @@ var pad22 = (n) => String(n).padStart(2, "0");
27870
27928
  var esc = (s) => s.replace(/\|/g, "/").replace(/\n/g, " ").trim();
27871
27929
  var BLOOM_OF_DEPTH = { ulo: "Understand", cio: "Apply", sio: "Create" };
27872
27930
  function renderFrameworkFromPlan(plan, meta) {
27931
+ const isVi = meta.targetLanguage === "vi";
27873
27932
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
27874
27933
  const totalSessions = plan.sessions.length;
27875
27934
  const totalMinutes = plan.sessions.reduce(
@@ -27887,10 +27946,16 @@ function renderFrameworkFromPlan(plan, meta) {
27887
27946
  const depthTops = [...new Set(s.depth_assignments.map((d) => d.depth))].map((d) => BLOOM_OF_DEPTH[d] ?? "Apply");
27888
27947
  const bloom = depthTops[depthTops.length - 1] ?? "Apply";
27889
27948
  const keyConcepts = s.new_keywords.length > 0 ? s.new_keywords.join(", ") : s.depth_assignments.map((d) => d.node_id).join(", ");
27890
- const prereq = s.prerequisite_decisions.length > 0 ? s.prerequisite_decisions.filter((p) => p.decision === "taught_in_prior_lesson").length + " prior-taught, " + s.prerequisite_decisions.filter((p) => p.decision === "recap_in_lesson").length + " recap" : "None";
27949
+ const prereq = s.prerequisite_decisions.length > 0 ? s.prerequisite_decisions.filter((p) => p.decision === "taught_in_prior_lesson").length + " prior-taught, " + s.prerequisite_decisions.filter((p) => p.decision === "recap_in_lesson").length + " recap" : isVi ? "Kh\xF4ng" : "None";
27891
27950
  const duration = s.knowledge_minutes + s.practice_minutes + s.overhead_minutes;
27892
27951
  return "| " + pad22(i + 1) + " | " + s.id + " | " + esc(s.title) + " | " + s.unit_id + " | " + s.unit_id + "_M01 | " + esc(s.prose_objective) + " | " + esc(keyConcepts) + " | " + esc(s.exit_evidence[0] ?? "") + " | " + esc(prereq) + " | " + bloom + " | " + duration + " |";
27893
27952
  }).join("\n");
27953
+ const courseOverviewHeader = isVi ? "## [REQUIRED] T\u1ED5ng quan Kh\xF3a h\u1ECDc" : "## [REQUIRED] Course Overview";
27954
+ const globalObjectivesHeader = isVi ? "## [REQUIRED] M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n" : "## [REQUIRED] Global Learning Objectives";
27955
+ const structuralHierarchyHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)" : "## [REQUIRED] Structural Hierarchy (Unit & Module)";
27956
+ const scopeSequenceHeader = isVi ? "## [REQUIRED] Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt (Scope & Sequence)" : "## [REQUIRED] Scope & Sequence (Detailed Roadmap)";
27957
+ const standardFormatHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc B\xE0i h\u1ECDc Chu\u1EA9n" : "## [REQUIRED] Standard Lesson Format";
27958
+ const scopeTableHeader = isVi ? "| # | Lesson Code | Title | Unit | Module | Learning Objective (H\u1ECDc sinh l\xE0m \u0111\u01B0\u1EE3c g\xEC...) | Key Concepts (H\u1ECDc g\xEC) | Hands-on Deliverable (S\u1EA3n ph\u1EA9m bu\u1ED5i h\u1ECDc) | Prerequisites | Bloom | Duration (mins) |" : "| # | Lesson Code | Title | Unit | Module | Learning Objective (Students will be able to...) | Key Concepts (Hoc gi) | Hands-on Deliverable (Lam duoc gi) | Prerequisites | Bloom | Duration (mins) |";
27894
27959
  return [
27895
27960
  "---",
27896
27961
  'id: "CURRICULUM-FRAMEWORK"',
@@ -27912,7 +27977,7 @@ function renderFrameworkFromPlan(plan, meta) {
27912
27977
  "",
27913
27978
  "_Projection of CURRICULUM_PLAN (hash " + plan.plan_hash + "). Approving this framework approves the per-session SCOPE recorded in the plan._",
27914
27979
  "",
27915
- "## [REQUIRED] Course Overview",
27980
+ courseOverviewHeader,
27916
27981
  "- **Official Course Name:** " + esc(meta.courseName),
27917
27982
  meta.shortDescription ? "- **Short Description:** " + esc(meta.shortDescription) : "",
27918
27983
  "- **Total Units:** " + plan.units.length,
@@ -27920,20 +27985,20 @@ function renderFrameworkFromPlan(plan, meta) {
27920
27985
  "- **Total Duration:** ~" + totalHours + " hours (" + totalSessions + " sessions x " + plan.constraints.session_duration_minutes + " min).",
27921
27986
  "- **Entry Level:** " + plan.constraints.entry_level + " (age band " + plan.constraints.age_band[0] + "-" + plan.constraints.age_band[1] + ").",
27922
27987
  "",
27923
- "## [REQUIRED] Global Learning Objectives",
27988
+ globalObjectivesHeader,
27924
27989
  ...plan.course.objectives.map((o, i) => i + 1 + ". **[" + o.bloom + "]:** " + esc(o.statement)),
27925
27990
  "",
27926
- "## [REQUIRED] Structural Hierarchy (Unit & Module)",
27991
+ structuralHierarchyHeader,
27927
27992
  "| Unit | Module | Lessons | Module Objective | Lesson Codes |",
27928
27993
  "|---|---|:---:|---|---|",
27929
27994
  hierarchyRows || "| (none) | | | | |",
27930
27995
  "",
27931
- "## [REQUIRED] Scope & Sequence (Detailed Roadmap)",
27932
- "| # | Lesson Code | Title | Unit | Module | Learning Objective (Students will be able to...) | Key Concepts (Hoc gi) | Hands-on Deliverable (Lam duoc gi) | Prerequisites | Bloom | Duration (mins) |",
27996
+ scopeSequenceHeader,
27997
+ scopeTableHeader,
27933
27998
  "|:---:|:---:|---|---|---|---|---|---|---|:---:|:---:|",
27934
27999
  scopeRows || "| 01 | U01_M01_L01 | (empty plan) | U01 | U01_M01 | - | - | - | None | Understand | " + plan.constraints.session_duration_minutes + " |",
27935
28000
  "",
27936
- "## [REQUIRED] Standard Lesson Format",
28001
+ standardFormatHeader,
27937
28002
  "- **Session duration:** " + plan.constraints.session_duration_minutes + " minutes (overhead " + String(plan.sessions[0]?.overhead_minutes ?? 0) + " min).",
27938
28003
  "- **Knowledge/practice split per session:** see plan JSON (knowledge_minutes / practice_minutes).",
27939
28004
  "- **Pedagogical flow:** 5E (Engage / Explore / Explain / Elaborate / Evaluate) unless policy overrides.",