dsh-plugin-lookatstudy 0.25.1 → 0.25.3

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/lib/index.mjs CHANGED
@@ -1130,6 +1130,21 @@ function normalizeSpeechText(md) {
1130
1130
  s = s.replace(/`[^`\n]*`/g, "");
1131
1131
  s = s.replace(/!\[[^\]]*\]\([^)\n]*\)/g, "");
1132
1132
  s = s.replace(/\[([^\]]+)\]\([^)\n]*\)/g, "$1");
1133
+ s = s.replace(/\[\]\([^)\n]*\)/g, "");
1134
+ s = s.replace(/<br\s*\/?>/gi, "\n");
1135
+ s = s.replace(/<https?:\/\/[^>\s]+>/g, "");
1136
+ s = s.replace(/https?:\/\/[^\s)\]]+/g, "");
1137
+ s = s.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, "\n");
1138
+ s = s.replace(/<[^>\n]+>/g, "");
1139
+ s = s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#0?39;/g, "'").replace(/&nbsp;/g, " ").replace(/&#(\d+);/g, (_m, d) => {
1140
+ try {
1141
+ return String.fromCodePoint(Number(d));
1142
+ } catch {
1143
+ return "";
1144
+ }
1145
+ }).replace(/&[a-z]+;/gi, "");
1146
+ s = s.replace(/\[([^\]]+)\]\[[^\]]*\]/g, "$1");
1147
+ s = s.replace(/\[\^[^\]]*\]/g, "");
1133
1148
  s = s.replace(/^[ \t]{0,3}#{1,6}[ \t]+/gm, "");
1134
1149
  s = s.replace(/^[ \t]*(?:[-*+]|\d+\.)[ \t]+/gm, "");
1135
1150
  s = s.replace(/^[ \t]*>[ \t]?/gm, "");
@@ -3122,6 +3137,7 @@ function workbenchState(state, now) {
3122
3137
  speechText: normalizeSpeechText(normalizeMathNotation(ref.lesson.body)),
3123
3138
  ...ref.lesson.translation !== void 0 ? {
3124
3139
  translationHtml: renderMarkdown(normalizeMathNotation(ref.lesson.translation)),
3140
+ translationSpeechText: normalizeSpeechText(normalizeMathNotation(ref.lesson.translation)),
3125
3141
  translationLang: ref.lesson.translationLang ?? ""
3126
3142
  } : {}
3127
3143
  };
@@ -6582,12 +6598,19 @@ function validateDesign(design, validFiles) {
6582
6598
  };
6583
6599
  }
6584
6600
  /**
6585
- * Heading similarity (word-level overlap) — the translation misalignment
6586
- * guard. Ported verbatim from upstream import-pipeline.ts (their audit F16,
6587
- * 2026-09-13): ordinal alignment assumes both heading lists share structure;
6588
- * a machine-translated file with a dropped header shifts every ordinal, and
6589
- * A's translation would silently land on B's node — overlap too low skips the
6590
- * segment (宁缺毋错). Empty titles cannot be checked and pass conservatively.
6601
+ * Heading similarity — the translation misalignment guard. Ported from
6602
+ * upstream import-pipeline.ts (their audit F16, 2026-09-13): ordinal
6603
+ * alignment assumes both heading lists share structure; a machine-translated
6604
+ * file with a dropped header shifts every ordinal, and A's translation would
6605
+ * silently land on B's node — overlap too low skips the segment (宁缺毋错).
6606
+ * Empty titles cannot be checked and pass conservatively.
6607
+ *
6608
+ * 0.25.2 (owner real-import catch): upstream's word overlap normalizes on
6609
+ * whitespace, and a CJK title is ONE token — paraphrased Chinese headings
6610
+ * (课程总览与环境搭建指南 vs 课程总览) could never overlap and the guard
6611
+ * skipped every non-verbatim segment (38/110 paired on the owner's import;
6612
+ * the 38 were exact matches). CJK strings now compare by character
6613
+ * containment first, then character-bigram overlap ≥ 0.3.
6591
6614
  */
6592
6615
  function headingsSimilar(a, b) {
6593
6616
  const norm = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim().split(/\s+/).filter(Boolean);
@@ -6595,6 +6618,24 @@ function headingsSimilar(a, b) {
6595
6618
  const B = norm(b);
6596
6619
  if (A.length === 0 || B.length === 0) return true;
6597
6620
  if (A.join(" ") === B.join(" ")) return true;
6621
+ const han = (s) => s.toLowerCase().replace(/[^\p{Script=Han}\p{N}]/gu, "");
6622
+ const ca = han(a);
6623
+ const cb = han(b);
6624
+ if (ca.length >= 2 || cb.length >= 2) {
6625
+ if (ca === "" || cb === "") return true;
6626
+ if (ca.includes(cb) || cb.includes(ca)) return true;
6627
+ const bigramsOf = (s) => {
6628
+ const out = /* @__PURE__ */ new Set();
6629
+ const chars = Array.from(s);
6630
+ for (let i = 0; i + 1 < chars.length; i++) out.add(chars[i] + chars[i + 1]);
6631
+ return out;
6632
+ };
6633
+ const ba = bigramsOf(ca);
6634
+ const bb = bigramsOf(cb);
6635
+ let hit = 0;
6636
+ for (const g of ba) if (bb.has(g)) hit++;
6637
+ return hit / Math.max(1, ba.size) >= .3;
6638
+ }
6598
6639
  const setB = new Set(B);
6599
6640
  return A.filter((w) => setB.has(w)).length / A.length >= .3;
6600
6641
  }
@@ -6675,7 +6716,8 @@ function buildCourseFromDesign(courseTitle, validated, contents, translations) {
6675
6716
  let tIndex = titleIndex;
6676
6717
  let keep = true;
6677
6718
  if (lesson.anchor === null || titleIndex === -1) tIndex = -1;
6678
- else if (titleIndex >= tHeadings.length || !headingsSimilar(lesson.title, tHeadings[titleIndex].title)) keep = false;
6719
+ else if (titleIndex >= tHeadings.length) keep = false;
6720
+ else if (!(tHeadings.length === headings.length && tHeadings.every((h, i) => h.level === headings[i].level)) && !headingsSimilar(lesson.title, tHeadings[titleIndex].title)) keep = false;
6679
6721
  if (keep) translation = replaceImagesByPosition(sliceLessonBody(pair.content, tHeadings, tIndex, isFirst), originalImagesOf(body));
6680
6722
  }
6681
6723
  return {
@@ -7739,7 +7781,7 @@ ${p.text}`));
7739
7781
  translations: {
7740
7782
  type: "integer",
7741
7783
  required: true,
7742
- description: "Lessons that landed with a paired translation (the interface-language mirror existed and its anchor matched) — their teach tab offers 原文/对照/译文."
7784
+ description: "Lessons that landed with a paired translation (the interface-language mirror existed and its anchor matched) — their teach tab offers 原文/译文."
7743
7785
  },
7744
7786
  created: {
7745
7787
  type: "boolean",
@@ -7754,7 +7796,7 @@ ${p.text}`));
7754
7796
  },
7755
7797
  render: (_args, value) => [{
7756
7798
  type: "text",
7757
- text: value.created === false ? `“${value.title}” (id ${value.courseId}) was ALREADY imported — the design did not create a new course. Continue with the existing course, or delete it first (study_delete_course) to rebuild from this design.` : `Imported designed course “${value.title}” (${value.sections} sections, ${value.lessons} lessons${value.droppedLessons > 0 ? `, ${value.droppedLessons} hallucinated lesson(s) dropped` : ""})${typeof value.translations === "number" && value.translations > 0 ? ` — ${value.translations} lesson(s) carry the learner's-language translation (原文/对照/译文 in the teach tab)` : ""}${typeof value.languageTarget === "string" ? ` — language course (${value.languageTarget}): keep reading passages, example sentences, and quiz language material in the target language` : ""}. First lesson: “${value.firstLessonTitle}” (id ${value.firstLessonId}). Present the course map to the learner.`
7799
+ text: value.created === false ? `“${value.title}” (id ${value.courseId}) was ALREADY imported — the design did not create a new course. Continue with the existing course, or delete it first (study_delete_course) to rebuild from this design.` : `Imported designed course “${value.title}” (${value.sections} sections, ${value.lessons} lessons${value.droppedLessons > 0 ? `, ${value.droppedLessons} hallucinated lesson(s) dropped` : ""})${typeof value.translations === "number" && value.translations > 0 ? ` — ${value.translations} lesson(s) carry the learner's-language translation (原文/译文 in the teach tab)` : ""}${typeof value.languageTarget === "string" ? ` — language course (${value.languageTarget}): keep reading passages, example sentences, and quiz language material in the target language` : ""}. First lesson: “${value.firstLessonTitle}” (id ${value.firstLessonId}). Present the course map to the learner.`
7758
7800
  }]
7759
7801
  },
7760
7802
  async execute(args, exec) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-lookatstudy",
3
- "version": "0.25.1",
3
+ "version": "0.25.3",
4
4
  "packageManager": "pnpm@11.7.0",
5
5
  "description": "Turn any markdown, local folder, or GitHub learning repo into a guided course inside DeepSeek Harness: gated skill-tree progression, BKT mastery tracking, SM-2 spaced repetition.",
6
6
  "type": "module",