fix(role): 寫檔前加繁簡防線,歧義字刻意不自動轉換
病因(由角色稽核記憶時發現):實測有整則記憶以簡體寫成,連 summary 與 tags 都是, 而該則的 sources 指向另一個專案 —— 不同環境下 CLI 的行為並不一致, 光靠 prompt 的「使用繁體中文」條款擋不住。0.1.2 只補了 prompt(症狀由人工修檔), 寫入器本身沒防線,同樣環境下還會再產出簡體。 - memory.js 新增 toTraditional/ambiguousSimplified/warnIfSimplified - cmdWrite(inbox)、cmdApply(整理落檔)、appendBond(關係史)三處寫檔前都經過 - 一簡對一繁、無歧義的約 700 字自動轉繁 - 一簡對多繁刻意不轉(发→發/髮、干→乾/幹、后→後/后、里→裡/里、复→復/複/覆、 系→系/係/繫、脏→臟/髒…),改為 stderr 警告,留待整理階段依上下文處理 —— 機械替換會把「头发」變成「頭發」,那比留著簡體更難發現, 因為它看起來已經是繁體了。寧可留下可偵測的瑕疵,也不要製造隱形錯誤 - 警告只警告不阻斷:記憶寧可帶著瑕疵留下,也不能因為用字問題而遺失 - 轉換只在字形層;用語差異(反饋/回饋)仍由 prompt 的台灣用語條款負責 實測:整則簡體素材寫入後 summary/tags/content 均正確轉繁, 「发」「系」保留並發出警告,未產生「頭發」這類隱形錯誤。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jsc-generic",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "JSC 跨 AI 助理共用規範 plugin(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc-generic: 前綴呼叫。",
|
||||
"skills": "./skills",
|
||||
"author": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jsc-generic",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。",
|
||||
"skills": "./skills"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jsc-generic",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc-generic: 前綴呼叫。",
|
||||
"skills": "./skills/"
|
||||
}
|
||||
|
||||
+48
-5
@@ -5,7 +5,7 @@
|
||||
// 記憶交接,使用獨立字元預算),(3) 睡眠整理時輸出待整理
|
||||
// 素材並套用整理結果(NREM 鞏固/REM 整合、分類、去重、標籤、總結、
|
||||
// 優先度、關聯、壓縮歸檔),(4) 依使用頻率與優先度遺忘日常與其他類記憶。
|
||||
// 更新時間:2026/07/29 17:16:12
|
||||
// 更新時間:2026/07/29 18:56:33
|
||||
// 相依:Node.js 標準庫。
|
||||
// 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。
|
||||
// ==============================================================================
|
||||
@@ -62,6 +62,43 @@ const MEMORY_TYPE_WEIGHT = {
|
||||
semantic: 30,
|
||||
episodic: 30,
|
||||
};
|
||||
// ── 繁簡防線(第二道)────────────────────────────────────────────────────────
|
||||
// prompt 已明令輸出繁體(capture 第 8 條、sleep 第 16 條),但實測仍出現整則簡體記憶,
|
||||
// 而且該則的 sources 指向另一個專案 —— 不同環境下 CLI 的行為並不一致,光靠 prompt 擋不住。
|
||||
// 這裡是寫檔前的第二道防線。
|
||||
//
|
||||
// 只轉換「一簡對一繁、無歧義」的字。歧義字刻意**不自動轉換**:
|
||||
// 发→發/髮、干→乾/幹、后→後/后、里→裡/里、复→復/複/覆、系→系/係/繫、余→余/餘…
|
||||
// 機械替換會產生「頭發」這種比留著簡體更難發現的錯誤,改為警告並留待整理階段依上下文處理。
|
||||
const ZH_SAFE_PAIRS = "这這个個们們说說时時间間过過还還没沒对對认認为為经經开開单單区區华華严嚴来來长長问問题題无無现現实實电電话話车車门門马馬东東书書学學习習应應该該让讓给給从從众眾会會体體万萬与與专專业業两兩广廣庆慶亚亞产產亲親亿億仅僅仓倉仪儀优優伟偉传傳伤傷伦倫伪偽侠俠侦偵侧側侨僑俭儉债債倾傾偿償储儲兰蘭关關兴興养養兽獸内內决決况況冻凍净淨减減凑湊几幾凭憑击擊刘劉则則刚剛创創删刪别別剧劇劳勞势勢动動励勵汇匯医醫协協卖賣卢盧卫衛却卻厂廠压壓厌厭厅廳县縣参參双雙变變叙敘叹嘆叶葉号號吗嗎员員响響唤喚团團园園围圍图圖圆圓圣聖场場坏壞块塊坚堅坛壇垒壘执執扩擴扫掃扬揚拟擬择擇挂掛挥揮损損换換据據搅攪摆擺摄攝撑撐敌敵数數斗鬥断斷旧舊显顯晓曉暂暫术術机機杂雜权權条條极極构構标標树樹样樣桥橋检檢楼樓横橫欢歡欧歐汉漢汤湯沟溝泪淚泼潑洁潔济濟测測浏瀏涛濤涨漲渐漸渔漁渗滲温溫满滿滚滾滤濾滨濱潜潛澜瀾灭滅灯燈灵靈灾災炉爐烂爛烦煩热熱爱愛爷爺牵牽牺犧犹猶独獨狮獅猪豬猫貓献獻环環玛瑪琼瓊画畫畅暢疗療痒癢瘫癱盘盤监監盖蓋盗盜瞒瞞矫矯码碼碍礙确確礼禮祸禍禅禪离離种種积積称稱稳穩穷窮窃竊窜竄竞競笔筆笼籠筑築简簡篮籃类類粮糧紧緊纠糾红紅约約级級纪紀纬緯纯純纱紗纲綱纳納纵縱纷紛纸紙纹紋纺紡纽紐线線练練组組绅紳细細织織终終绊絆绍紹绎繹绑綁绒絨结結绕繞绘繪络絡绝絕绞絞统統绢絹绣繡继繼绩績绪緒续續绳繩维維绵綿综綜绿綠缀綴缓緩缔締编編缘緣缚縛缝縫缠纏缩縮缴繳网網罚罰罢罷罗羅义義耻恥聋聾职職联聯聪聰肃肅肠腸肤膚胀脹胆膽脉脈脑腦脱脫腊臘腻膩脸臉舆輿舰艦舱艙艰艱艺藝节節芦蘆苏蘇苹蘋茎莖荐薦荣榮药藥莱萊获獲萧蕭蓝藍虏虜虑慮虫蟲蚀蝕蚁蟻蛮蠻蜡蠟蝇蠅补補衬襯装裝见見观觀觉覺览覽触觸誉譽计計订訂议議讯訊记記讲講许許论論讼訟讽諷设設访訪证證评評识識诉訴诊診词詞译譯试試诗詩诚誠详詳语語误誤诱誘诵誦请請诸諸诺諾读讀课課调調谅諒谈談谋謀谎謊谐諧谓謂谜謎谢謝谣謠谤謗谦謙谨謹谱譜贝貝贞貞负負贡貢财財责責贤賢败敗货貨质質贩販贪貪贫貧贬貶购購贯貫贱賤贴貼贵貴贷貸贸貿费費贺賀贼賊贾賈贿賄资資赁賃赃贓赋賦赌賭赎贖赏賞赐賜赔賠赖賴赘贅赚賺赛賽赞贊赠贈赡贍赢贏赶趕趋趨轧軋轨軌转轉轮輪软軟轰轟轴軸轻輕载載辅輔辆輛辈輩辉輝输輸边邊达達迁遷运運进進远遠违違连連迟遲适適选選逊遜递遞逻邏遗遺邓鄧邮郵邻鄰郑鄭释釋针針钉釘钢鋼钥鑰钦欽钩鉤钱錢钳鉗钻鑽铁鐵铃鈴铅鉛铜銅铝鋁银銀铸鑄铺鋪链鏈销銷锁鎖锅鍋锋鋒错錯锦錦键鍵锯鋸镇鎮镜鏡闪閃闭閉闯闖闲閒闷悶闹鬧闻聞阀閥阁閣阅閱阔闊队隊阳陽阴陰阵陣际際陆陸陈陳陕陝险險随隨隐隱难難雾霧韦韋韧韌韩韓页頁顶頂顷頃项項顺順须須顽頑顾顧顿頓颁頒预預领領颇頗颈頸频頻额額颤顫风風飘飄飞飛饥飢饭飯饮飲饰飾饱飽饲飼饺餃饼餅饿餓馆館馈饋驭馭驯馴驰馳驱驅驳駁驴驢驻駐驼駝驾駕骂罵骄驕验驗骑騎骗騙骚騷骤驟鱼魚鲁魯昵暱桩樁谘諮侣侶侥僥仑侖头頭点點总總当當张張报報买買乐樂气氣户戶态態忆憶军軍办辦听聽声聲处處虚虛丰豐宾賓龙龍鸟鳥丽麗齐齊庄莊农農乡鄉阶階颜顏尽盡毕畢状狀奖獎纤纖盐鹽础礎硕碩竖豎筹籌肿腫胁脅荡蕩苍蒼梦夢杀殺杨楊枪槍柜櫃栏欄洒灑泽澤浓濃润潤湾灣湿濕烛燭烧燒营營猎獵疯瘋护護挡擋拥擁挤擠捡撿举舉录錄归歸彻徹恶惡悬懸惊驚惧懼惯慣愿願戏戲战戰扰擾抚撫拨撥拦攔摊攤旷曠毁毀狱獄阐闡陨隕隶隸韵韻颂頌颖穎馊餿骏駿鲜鮮鸡雞鸣鳴鸿鴻鹅鵝鹏鵬鹰鷹麦麥黄黃齿齒龄齡遥遙静靜颅顱";
|
||||
const ZH_SAFE = (() => {
|
||||
const m = Object.create(null);
|
||||
for (let i = 0; i < ZH_SAFE_PAIRS.length; i += 2) m[ZH_SAFE_PAIRS[i]] = ZH_SAFE_PAIRS[i + 1];
|
||||
return m;
|
||||
})();
|
||||
const ZH_AMBIGUOUS = new Set(["丑", "么", "云", "伙", "余", "党", "准", "划", "卷", "历", "发", "只", "台", "后", "咨", "回", "复", "宁", "干", "扑", "朴", "签", "系", "脏", "著", "里", "钟", "雕"]);
|
||||
|
||||
function toTraditional(value) {
|
||||
if (typeof value !== "string" || !value) return value;
|
||||
let out = "";
|
||||
for (const ch of value) out += ZH_SAFE[ch] || ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
function ambiguousSimplified(value) {
|
||||
const hit = new Set();
|
||||
for (const ch of String(value || "")) if (ZH_AMBIGUOUS.has(ch)) hit.add(ch);
|
||||
return [...hit];
|
||||
}
|
||||
|
||||
function warnIfSimplified(role, where, ...texts) {
|
||||
// 只警告不阻斷:記憶寧可帶著瑕疵留下,也不能因為用字問題而遺失
|
||||
const hit = ambiguousSimplified(texts.join(" "));
|
||||
if (hit.length) {
|
||||
process.stderr.write(`[memory][WRN]: ${role} 的${where}含歧義簡體字(${hit.join("")}),未自動轉換,請於整理階段依上下文修正\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const DECLARATIVE_VALUES = ["explicit", "implicit"];
|
||||
const RETENTION_STAGES = ["working", "long_term"];
|
||||
|
||||
@@ -544,6 +581,11 @@ function parseCapture(text) {
|
||||
function cmdWrite(args) {
|
||||
const parsed = parseCapture(readStdin());
|
||||
if (!parsed.content && !parsed.summary) return 1;
|
||||
// 繁簡防線:prompt 已要求繁體,這裡在寫檔前再擋一次(實測有整則簡體記憶落檔)
|
||||
warnIfSimplified(args.role, "待寫入記憶", parsed.summary || "", parsed.content || "", (parsed.tags || []).join(""));
|
||||
parsed.summary = toTraditional(parsed.summary);
|
||||
parsed.content = toTraditional(parsed.content);
|
||||
parsed.tags = (parsed.tags || []).map(toTraditional);
|
||||
const content = (parsed.content || parsed.summary).slice(0, CONTENT_LIMIT);
|
||||
const stamp = nowStamp();
|
||||
const memoryType = normalizeMemoryType(parsed.memoryType, parsed.category);
|
||||
@@ -800,7 +842,7 @@ function readBonds(role) {
|
||||
}
|
||||
|
||||
function appendBond(role, entry, direction) {
|
||||
const text = oneLine(entry, 160);
|
||||
const text = toTraditional(oneLine(entry, 160));
|
||||
if (!text) return false;
|
||||
const line = `- ${nowStamp().slice(0, 16)}|${BOND_DIRECTIONS[normalizeBondDirection(direction)]}|${text}`;
|
||||
const previous = readBonds(role);
|
||||
@@ -889,9 +931,10 @@ function cmdApply(args) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = String(entry.content || "").trim().slice(0, CONTENT_LIMIT);
|
||||
const summary = oneLine(entry.summary);
|
||||
const tags = normalizeTags(entry.tags);
|
||||
warnIfSimplified(args.role, "整理結果", String(entry.summary || ""), String(entry.content || ""));
|
||||
const content = toTraditional(String(entry.content || "").trim().slice(0, CONTENT_LIMIT));
|
||||
const summary = toTraditional(oneLine(entry.summary));
|
||||
const tags = normalizeTags(entry.tags).map(toTraditional);
|
||||
const entryCategory = normalizeCategory(entry.category);
|
||||
const priority = normalizePriority(entry.priority, entryCategory);
|
||||
const relevance = normalizeList(entry.relevance);
|
||||
|
||||
@@ -786,6 +786,19 @@ updated: <yyyy/MM/dd HH:mm:ss>
|
||||
|
||||
判準刻意放在 `relevance` 而非型態:並非所有事件與知識都該優待 —— **沒有情感脈絡的一次性工作進度仍照原規則淡去**,被留下的是帶著溫度的那些。因此兩份 prompt 都明訂:只要內容承載情感、關係溫度或當時的心情,`relevance` **必須**含 `emotional`;`episodic` 與 `semantic` 最容易漏標,漏了就會被當成一般進度處理。
|
||||
|
||||
### 繁簡防線(寫檔前的第二道)
|
||||
|
||||
兩份 prompt 已明令輸出繁體,但**實測仍出現整則簡體記憶**(連 `summary` 與 `tags` 都是),而且該則的 `sources` 指向另一個專案 —— 不同環境下 CLI 的行為並不一致,光靠 prompt 擋不住。因此 `memory.js` 在寫檔前再擋一次:`cmdWrite`(inbox)、`cmdApply`(整理落檔)、`appendBond`(關係史)都會經過。
|
||||
|
||||
| 字類 | 處理 |
|
||||
| --- | --- |
|
||||
| 一簡對一繁、無歧義(約 700 字) | **自動轉為繁體** |
|
||||
| 一簡對多繁(`发`→發/髮、`干`→乾/幹、`后`→後/后、`里`→裡/里、`复`→復/複/覆、`系`→系/係/繫、`脏`→臟/髒…) | **刻意不自動轉換**,改為 stderr 警告,留待整理階段依上下文處理 |
|
||||
|
||||
**為什麼歧義字不轉**:機械替換會把「头发」變成「頭發」—— 那比留著簡體更難發現,因為它看起來已經是繁體了。**寧可留下可偵測的瑕疵,也不要製造隱形的錯誤。**
|
||||
|
||||
轉換只在字形層,用語差異(例如「反饋」與「回饋」)仍由 prompt 的「台灣用語」條款負責。警告只警告、不阻斷 —— 記憶寧可帶著瑕疵留下,也不能因為用字問題而遺失。
|
||||
|
||||
### 排程涵蓋所有角色
|
||||
|
||||
睡眠、小睡與晨間檢查排程**預設服務角色目錄下的每一個角色**,而不只是 `.active` 指定的啟用角色。
|
||||
|
||||
Reference in New Issue
Block a user