diff --git a/claude-plugin/skills/academy-terms/data/terms.ja.json b/claude-plugin/skills/academy-terms/data/terms.ja.json index aaab603..fe3cd91 100644 --- a/claude-plugin/skills/academy-terms/data/terms.ja.json +++ b/claude-plugin/skills/academy-terms/data/terms.ja.json @@ -15,11 +15,7 @@ "クロード" ], "Anthropic": [ - "アンスロピック", - "人間", - "人間的", - "人為的", - "人類の" + "アンスロピック" ], "Anthropic Academy": [ "Anthropic Academy" @@ -27,9 +23,6 @@ "Enterprise": [ "エンタープライズ" ], - "Personal": [ - "個人" - ], "Plugin": [ "プラグイン" ], @@ -70,15 +63,12 @@ "フック" ], "Cowork": [ - "コワーク", - "共同作業" + "コワーク" ], "Dispatch": [ - "ディスパッチ", - "派遣" + "ディスパッチ" ], "Computer Use": [ - "コンピュータ使用", "コンピュータユース" ] }, diff --git a/claude-plugin/skills/academy-terms/data/terms.ko.json b/claude-plugin/skills/academy-terms/data/terms.ko.json index fe023f9..918645c 100644 --- a/claude-plugin/skills/academy-terms/data/terms.ko.json +++ b/claude-plugin/skills/academy-terms/data/terms.ko.json @@ -11,28 +11,21 @@ "Claude Code": [ "클로드 코드", "클로드 Code", - "클로우드 코드", - "클라우드 코드" + "클로우드 코드" ], "Claude": [ "클로드", - "클로우드", - "클라우드" + "클로우드" ], "Anthropic Academy": [ "앤스로픽 아카데미", "앤트로픽 아카데미", - "안트로픽 아카데미", - "인류학적 아카데미" + "안트로픽 아카데미" ], "Anthropic": [ "앤스로픽", "앤트로픽", - "안트로픽", - "인류학적", - "인간적인", - "인류", - "인간적" + "안트로픽" ], "Enterprise > Personal > Project > Plugins": [ "기업 > 개인 > 프로젝트 > 플러그인", @@ -42,12 +35,6 @@ "기업 → 개인 → 프로젝트 → 플러그인", "엔터프라이즈 → 개인 → 프로젝트 → 플러그인" ], - "Enterprise": [ - "기업" - ], - "Personal": [ - "개인적인" - ], "Plugin": [ "플러그인" ], @@ -55,26 +42,20 @@ "플러그인들" ], "skill": [ - "기술", "스킬" ], "skills": [ - "기술들", - "스킬들", - "기술" + "스킬들" ], "Skills": [ - "스킬들", - "기술들" + "스킬들" ], "SKILL.md": [ "스킬.md", "기술.md" ], "frontmatter": [ - "프론트매터", - "앞부분", - "서문" + "프론트매터" ], "CLAUDE.md": [ "클로드.md" @@ -97,16 +78,12 @@ "후크" ], "Cowork": [ - "코워크", - "공동작업", - "협업" + "코워크" ], "Dispatch": [ - "디스패치", - "발송" + "디스패치" ], "Computer Use": [ - "컴퓨터 사용", "컴퓨터 유즈" ] }, diff --git a/claude-plugin/skills/academy-terms/data/terms.zh-CN.json b/claude-plugin/skills/academy-terms/data/terms.zh-CN.json index 98b392f..102e448 100644 --- a/claude-plugin/skills/academy-terms/data/terms.zh-CN.json +++ b/claude-plugin/skills/academy-terms/data/terms.zh-CN.json @@ -16,22 +16,11 @@ "克劳德" ], "Anthropic": [ - "人类学", - "安特罗皮克", - "人为的", - "人类的", - "人为", - "拟人" + "安特罗皮克" ], "Anthropic Academy": [ "Anthropic Academy" ], - "Enterprise": [ - "企业" - ], - "Personal": [ - "个人" - ], "Plugin": [ "插件" ], @@ -50,9 +39,6 @@ "SKILL.md": [ "SKILL.md" ], - "frontmatter": [ - "前言" - ], "CLAUDE.md": [ "CLAUDE.md" ], @@ -72,18 +58,6 @@ "hooks": [ "钩子", "挂钩" - ], - "Cowork": [ - "协作", - "共同工作" - ], - "Dispatch": [ - "调度", - "分派" - ], - "Computer Use": [ - "电脑使用", - "计算机使用" ] }, "terms": { diff --git a/claude-plugin/skills/academy-terms/data/terms.zh-TW.json b/claude-plugin/skills/academy-terms/data/terms.zh-TW.json index cadf7f5..ef9fce1 100644 --- a/claude-plugin/skills/academy-terms/data/terms.zh-TW.json +++ b/claude-plugin/skills/academy-terms/data/terms.zh-TW.json @@ -16,22 +16,11 @@ "克洛德" ], "Anthropic": [ - "人類學", - "安特羅皮克", - "人為的", - "人類的", - "人為", - "擬人" + "安特羅皮克" ], "Anthropic Academy": [ "Anthropic Academy" ], - "Enterprise": [ - "企業" - ], - "Personal": [ - "個人" - ], "Plugin": [ "外掛程式" ], @@ -50,9 +39,6 @@ "SKILL.md": [ "SKILL.md" ], - "frontmatter": [ - "前言" - ], "CLAUDE.md": [ "CLAUDE.md" ], @@ -72,18 +58,6 @@ "hooks": [ "鉤子", "掛鉤" - ], - "Cowork": [ - "協作", - "共同工作" - ], - "Dispatch": [ - "調度", - "分派" - ], - "Computer Use": [ - "電腦使用", - "計算機使用" ] }, "terms": { diff --git a/src/data/ja.json b/src/data/ja.json index 591b5ac..c97631f 100644 --- a/src/data/ja.json +++ b/src/data/ja.json @@ -1131,66 +1131,27 @@ "Peer learning strategies": "ピアラーニング戦略" }, "_protected": { - "Claude Code": [ - "クロードコード" - ], - "Claude": [ - "クロード" - ], - "Anthropic": [ - "アンスロピック", "人間", "人間的", "人為的", "人類の" - ], - "Anthropic Academy": [ - "Anthropic Academy" - ], - "Enterprise": [ - "エンタープライズ" - ], - "Personal": [ - "個人" - ], - "Plugin": [ - "プラグイン" - ], - "Plugins": [ - "プラグイン" - ], - "skill": [ - "スキル" - ], - "skills": [ - "スキル" - ], - "Skills": [ - "スキル" - ], - "SKILL.md": [ - "SKILL.md" - ], - "frontmatter": [ - "フロントマター" - ], - "CLAUDE.md": [ - "CLAUDE.md" - ], - "YAML": [ - "YAML" - ], - "slash command": [ - "スラッシュコマンド" - ], - "subagent": [ - "サブエージェント" - ], - "hook": [ - "フック" - ], - "hooks": [ - "フック" - ], - "Cowork": ["コワーク", "共同作業"], - "Dispatch": ["ディスパッチ", "派遣"], - "Computer Use": ["コンピュータ使用", "コンピュータユース"] + "Claude Code": ["クロードコード"], + "Claude": ["クロード"], + "Anthropic": ["アンスロピック"], + "Anthropic Academy": ["Anthropic Academy"], + "Enterprise": ["エンタープライズ"], + "Plugin": ["プラグイン"], + "Plugins": ["プラグイン"], + "skill": ["スキル"], + "skills": ["スキル"], + "Skills": ["スキル"], + "SKILL.md": ["SKILL.md"], + "frontmatter": ["フロントマター"], + "CLAUDE.md": ["CLAUDE.md"], + "YAML": ["YAML"], + "slash command": ["スラッシュコマンド"], + "subagent": ["サブエージェント"], + "hook": ["フック"], + "hooks": ["フック"], + "Cowork": ["コワーク"], + "Dispatch": ["ディスパッチ"], + "Computer Use": ["コンピュータユース"] }, "exam_ui": { "Pass": "合格", diff --git a/src/data/ko.json b/src/data/ko.json index 4319cde..fde0031 100644 --- a/src/data/ko.json +++ b/src/data/ko.json @@ -1131,30 +1131,28 @@ "Peer learning strategies": "동료 학습 전략" }, "_protected": { - "Claude Code": ["클로드 코드", "클로드 Code", "클로우드 코드", "클라우드 코드"], - "Claude": ["클로드", "클로우드", "클라우드"], - "Anthropic Academy": ["앤스로픽 아카데미", "앤트로픽 아카데미", "안트로픽 아카데미", "인류학적 아카데미"], - "Anthropic": ["앤스로픽", "앤트로픽", "안트로픽", "인류학적", "인간적인", "인류", "인간적"], + "Claude Code": ["클로드 코드", "클로드 Code", "클로우드 코드"], + "Claude": ["클로드", "클로우드"], + "Anthropic Academy": ["앤스로픽 아카데미", "앤트로픽 아카데미", "안트로픽 아카데미"], + "Anthropic": ["앤스로픽", "앤트로픽", "안트로픽"], "Enterprise > Personal > Project > Plugins": ["기업 > 개인 > 프로젝트 > 플러그인", "엔터프라이즈 > 개인 > 프로젝트 > 플러그인"], "Enterprise → Personal → Project → Plugins": ["기업 → 개인 → 프로젝트 → 플러그인", "엔터프라이즈 → 개인 → 프로젝트 → 플러그인"], - "Enterprise": ["기업"], - "Personal": ["개인적인"], "Plugin": ["플러그인"], "Plugins": ["플러그인들"], - "skill": ["기술", "스킬"], - "skills": ["기술들", "스킬들", "기술"], - "Skills": ["스킬들", "기술들"], + "skill": ["스킬"], + "skills": ["스킬들"], + "Skills": ["스킬들"], "SKILL.md": ["스킬.md", "기술.md"], - "frontmatter": ["프론트매터", "앞부분", "서문"], + "frontmatter": ["프론트매터"], "CLAUDE.md": ["클로드.md"], "YAML": ["야멜"], "slash command": ["슬래시 명령"], "subagent": ["하위 에이전트", "서브에이전트"], "hook": ["후크"], "hooks": ["후크들", "후크"], - "Cowork": ["코워크", "공동작업", "협업"], - "Dispatch": ["디스패치", "발송"], - "Computer Use": ["컴퓨터 사용", "컴퓨터 유즈"] + "Cowork": ["코워크"], + "Dispatch": ["디스패치"], + "Computer Use": ["컴퓨터 유즈"] }, "exam_ui": { "Pass": "합격", diff --git a/src/data/zh-CN.json b/src/data/zh-CN.json index a9183da..9495309 100644 --- a/src/data/zh-CN.json +++ b/src/data/zh-CN.json @@ -1131,69 +1131,22 @@ "Peer learning strategies": "同伴学习策略" }, "_protected": { - "Claude Code": [ - "克洛德代码" - ], - "Claude": [ - "克洛德", - "克劳德" - ], - "Anthropic": [ - "人类学", "安特罗皮克", "人为的", "人类的", "人为", "拟人" - ], - "Anthropic Academy": [ - "Anthropic Academy" - ], - "Enterprise": [ - "企业" - ], - "Personal": [ - "个人" - ], - "Plugin": [ - "插件" - ], - "Plugins": [ - "插件" - ], - "skill": [ - "技能" - ], - "skills": [ - "技能" - ], - "Skills": [ - "技能" - ], - "SKILL.md": [ - "SKILL.md" - ], - "frontmatter": [ - "前言" - ], - "CLAUDE.md": [ - "CLAUDE.md" - ], - "YAML": [ - "YAML" - ], - "slash command": [ - "斜杠命令" - ], - "subagent": [ - "子代理" - ], - "hook": [ - "钩子", - "挂钩" - ], - "hooks": [ - "钩子", - "挂钩" - ], - "Cowork": ["协作", "共同工作"], - "Dispatch": ["调度", "分派"], - "Computer Use": ["电脑使用", "计算机使用"] + "Claude Code": ["克洛德代码"], + "Claude": ["克洛德", "克劳德"], + "Anthropic": ["安特罗皮克"], + "Anthropic Academy": ["Anthropic Academy"], + "Plugin": ["插件"], + "Plugins": ["插件"], + "skill": ["技能"], + "skills": ["技能"], + "Skills": ["技能"], + "SKILL.md": ["SKILL.md"], + "CLAUDE.md": ["CLAUDE.md"], + "YAML": ["YAML"], + "slash command": ["斜杠命令"], + "subagent": ["子代理"], + "hook": ["钩子", "挂钩"], + "hooks": ["钩子", "挂钩"] }, "exam_ui": { "Pass": "通过", diff --git a/src/data/zh-TW.json b/src/data/zh-TW.json index a876f10..768bfa8 100644 --- a/src/data/zh-TW.json +++ b/src/data/zh-TW.json @@ -1131,69 +1131,22 @@ "Peer learning strategies": "同儕學習策略" }, "_protected": { - "Claude Code": [ - "克勞德代碼" - ], - "Claude": [ - "克勞德", - "克洛德" - ], - "Anthropic": [ - "人類學", "安特羅皮克", "人為的", "人類的", "人為", "擬人" - ], - "Anthropic Academy": [ - "Anthropic Academy" - ], - "Enterprise": [ - "企業" - ], - "Personal": [ - "個人" - ], - "Plugin": [ - "外掛程式" - ], - "Plugins": [ - "外掛程式" - ], - "skill": [ - "技能" - ], - "skills": [ - "技能" - ], - "Skills": [ - "技能" - ], - "SKILL.md": [ - "SKILL.md" - ], - "frontmatter": [ - "前言" - ], - "CLAUDE.md": [ - "CLAUDE.md" - ], - "YAML": [ - "YAML" - ], - "slash command": [ - "斜線命令" - ], - "subagent": [ - "子代理" - ], - "hook": [ - "鉤子", - "掛鉤" - ], - "hooks": [ - "鉤子", - "掛鉤" - ], - "Cowork": ["協作", "共同工作"], - "Dispatch": ["調度", "分派"], - "Computer Use": ["電腦使用", "計算機使用"] + "Claude Code": ["克勞德代碼"], + "Claude": ["克勞德", "克洛德"], + "Anthropic": ["安特羅皮克"], + "Anthropic Academy": ["Anthropic Academy"], + "Plugin": ["外掛程式"], + "Plugins": ["外掛程式"], + "skill": ["技能"], + "skills": ["技能"], + "Skills": ["技能"], + "SKILL.md": ["SKILL.md"], + "CLAUDE.md": ["CLAUDE.md"], + "YAML": ["YAML"], + "slash command": ["斜線命令"], + "subagent": ["子代理"], + "hook": ["鉤子", "掛鉤"], + "hooks": ["鉤子", "掛鉤"] }, "exam_ui": { "Pass": "通過", diff --git a/src/lib/translator.js b/src/lib/translator.js index 684c2df..5ae67b6 100644 --- a/src/lib/translator.js +++ b/src/lib/translator.js @@ -735,11 +735,15 @@ User: ${userMessage}`; ); }); } catch (err) { + // Synchronous setup failures (most importantly `!this.isReady` — the Puter + // bridge hasn't finished its handshake) must PROPAGATE, not resolve to a + // string. The sole caller (sidebar-chat) discards chatStream's return value + // and relies on a thrown error to render the error bubble + retry button; + // returning a string here left the "thinking…" spinner stranded forever + // with no error and no retry. (Promise-path failures — timeout, abort, + // success:false — already reject and are unaffected.) console.error('[SkillBridge] Chat stream error:', err); - return ( - (typeof CHAT_ERROR_LABELS !== 'undefined' && CHAT_ERROR_LABELS[targetLang]) || - 'Sorry, I could not generate a response. Please try again.' - ); + throw err; } } diff --git a/tests/protected-terms.test.js b/tests/protected-terms.test.js index e950ddd..d26e91b 100644 --- a/tests/protected-terms.test.js +++ b/tests/protected-terms.test.js @@ -191,3 +191,56 @@ describe('Protected Terms System (real production code)', () => { }); }); }); + +// Regression guard for the corruption class documented in protected-terms.js: +// restoreProtectedTerms does an unanchored replaceAll with no CJK word boundary, +// so any _protected wrong-form that is itself a common standalone word silently +// corrupts correct prose (e.g. 클라우드 "Cloud" -> Claude, 인류 "humanity" -> +// Anthropic, 大型企业 "large enterprise" -> 大型Enterprise). This runs against the +// REAL shipped src/data/*.json (not a fixture), so it fails if a dangerous +// wrong-form is ever (re-)introduced into a CJK dictionary's _protected section. +describe('no corruption of correct CJK prose (real shipped dictionaries)', () => { + // Each sentence is ordinary target-language prose containing a word that used + // to be a dangerous wrong-form. After the fix it must pass through untouched. + const cases = { + ko: [ + '클라우드 컴퓨팅은 확장 가능합니다', // Cloud (was -> Claude) + '인류의 미래를 생각합니다', // humanity (was -> Anthropic) + '대기업 환경에서 일합니다', // large enterprise (was -> 대Enterprise) + '기술자가 필요합니다', // technician (was -> skill자) + '우리는 협업합니다', // collaborate (was -> Cowork) + '문서의 앞부분을 읽으세요', // front part (was -> frontmatter) + '개인적인 의견입니다', // personal opinion (was -> Personal) + ], + ja: [ + '人類の未来を考える', // humanity (was -> Anthropic) + '個人的な意見です', // personal (was -> Personal) + '共同作業のスペース', // joint work (was -> Cowork) + ], + 'zh-CN': [ + '人类的未来', // humanity (was -> Anthropic) + '大型企业环境', // large enterprise (was -> Enterprise) + '任务调度系统', // task scheduling (was -> Dispatch) + '个人观点', // personal view (was -> Personal) + '团队协作', // team collaboration (was -> Cowork) + ], + 'zh-TW': [ + '人類的未來', // humanity (was -> Anthropic) + '大型企業環境', // large enterprise (was -> Enterprise) + '任務調度系統', // task scheduling (was -> Dispatch) + '個人觀點', // personal view (was -> Personal) + '團隊協作', // team collaboration (was -> Cowork) + ], + }; + + for (const [lang, sentences] of Object.entries(cases)) { + test(`${lang}: correct prose is not corrupted by protected-term restoration`, () => { + const dict = require(`../src/data/${lang}.json`); + resetProtectedTerms(); + buildProtectedTermsMap(lang, fakeTranslator(dict._protected)); + for (const sentence of sentences) { + expect(restoreProtectedTerms(sentence)).toBe(sentence); + } + }); + } +}); diff --git a/tests/translator.test.js b/tests/translator.test.js index 2a92c55..68283cf 100644 --- a/tests/translator.test.js +++ b/tests/translator.test.js @@ -287,3 +287,15 @@ describe('Language JSON files', () => { }); }); }); + +describe('chatStream — bridge-not-ready propagates as a rejection', () => { + test('rejects (does not silently resolve to a string) when the bridge is not ready', async () => { + const t = new SkilljarTranslator(); + t.isReady = false; + // The sole caller (sidebar-chat) discards chatStream's return value and + // relies on a thrown error to render the error bubble + retry button. If + // this resolves to a string instead, the "thinking…" spinner is stranded + // forever with no error and no retry. + await expect(t.chatStream('hello', 'ko', '', () => {}, {})).rejects.toThrow('Bridge not ready'); + }); +});