@thanh01.pmt/curriculum-kit 1.4.8 → 1.4.9

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tony Pham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
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
  }
@@ -27870,6 +27903,7 @@ var pad22 = (n) => String(n).padStart(2, "0");
27870
27903
  var esc = (s) => s.replace(/\|/g, "/").replace(/\n/g, " ").trim();
27871
27904
  var BLOOM_OF_DEPTH = { ulo: "Understand", cio: "Apply", sio: "Create" };
27872
27905
  function renderFrameworkFromPlan(plan, meta) {
27906
+ const isVi = meta.targetLanguage === "vi";
27873
27907
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
27874
27908
  const totalSessions = plan.sessions.length;
27875
27909
  const totalMinutes = plan.sessions.reduce(
@@ -27887,10 +27921,16 @@ function renderFrameworkFromPlan(plan, meta) {
27887
27921
  const depthTops = [...new Set(s.depth_assignments.map((d) => d.depth))].map((d) => BLOOM_OF_DEPTH[d] ?? "Apply");
27888
27922
  const bloom = depthTops[depthTops.length - 1] ?? "Apply";
27889
27923
  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";
27924
+ 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
27925
  const duration = s.knowledge_minutes + s.practice_minutes + s.overhead_minutes;
27892
27926
  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
27927
  }).join("\n");
27928
+ const courseOverviewHeader = isVi ? "## [REQUIRED] T\u1ED5ng quan Kh\xF3a h\u1ECDc" : "## [REQUIRED] Course Overview";
27929
+ const globalObjectivesHeader = isVi ? "## [REQUIRED] M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n" : "## [REQUIRED] Global Learning Objectives";
27930
+ const structuralHierarchyHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)" : "## [REQUIRED] Structural Hierarchy (Unit & Module)";
27931
+ const scopeSequenceHeader = isVi ? "## [REQUIRED] Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt (Scope & Sequence)" : "## [REQUIRED] Scope & Sequence (Detailed Roadmap)";
27932
+ const standardFormatHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc B\xE0i h\u1ECDc Chu\u1EA9n" : "## [REQUIRED] Standard Lesson Format";
27933
+ 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
27934
  return [
27895
27935
  "---",
27896
27936
  'id: "CURRICULUM-FRAMEWORK"',
@@ -27912,7 +27952,7 @@ function renderFrameworkFromPlan(plan, meta) {
27912
27952
  "",
27913
27953
  "_Projection of CURRICULUM_PLAN (hash " + plan.plan_hash + "). Approving this framework approves the per-session SCOPE recorded in the plan._",
27914
27954
  "",
27915
- "## [REQUIRED] Course Overview",
27955
+ courseOverviewHeader,
27916
27956
  "- **Official Course Name:** " + esc(meta.courseName),
27917
27957
  meta.shortDescription ? "- **Short Description:** " + esc(meta.shortDescription) : "",
27918
27958
  "- **Total Units:** " + plan.units.length,
@@ -27920,20 +27960,20 @@ function renderFrameworkFromPlan(plan, meta) {
27920
27960
  "- **Total Duration:** ~" + totalHours + " hours (" + totalSessions + " sessions x " + plan.constraints.session_duration_minutes + " min).",
27921
27961
  "- **Entry Level:** " + plan.constraints.entry_level + " (age band " + plan.constraints.age_band[0] + "-" + plan.constraints.age_band[1] + ").",
27922
27962
  "",
27923
- "## [REQUIRED] Global Learning Objectives",
27963
+ globalObjectivesHeader,
27924
27964
  ...plan.course.objectives.map((o, i) => i + 1 + ". **[" + o.bloom + "]:** " + esc(o.statement)),
27925
27965
  "",
27926
- "## [REQUIRED] Structural Hierarchy (Unit & Module)",
27966
+ structuralHierarchyHeader,
27927
27967
  "| Unit | Module | Lessons | Module Objective | Lesson Codes |",
27928
27968
  "|---|---|:---:|---|---|",
27929
27969
  hierarchyRows || "| (none) | | | | |",
27930
27970
  "",
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) |",
27971
+ scopeSequenceHeader,
27972
+ scopeTableHeader,
27933
27973
  "|:---:|:---:|---|---|---|---|---|---|---|:---:|:---:|",
27934
27974
  scopeRows || "| 01 | U01_M01_L01 | (empty plan) | U01 | U01_M01 | - | - | - | None | Understand | " + plan.constraints.session_duration_minutes + " |",
27935
27975
  "",
27936
- "## [REQUIRED] Standard Lesson Format",
27976
+ standardFormatHeader,
27937
27977
  "- **Session duration:** " + plan.constraints.session_duration_minutes + " minutes (overhead " + String(plan.sessions[0]?.overhead_minutes ?? 0) + " min).",
27938
27978
  "- **Knowledge/practice split per session:** see plan JSON (knowledge_minutes / practice_minutes).",
27939
27979
  "- **Pedagogical flow:** 5E (Engage / Explore / Explain / Elaborate / Evaluate) unless policy overrides.",