From 40c2d2ae463ba0d8406b05063ddee70c005dfebe Mon Sep 17 00:00:00 2001 From: Jeffery Date: Wed, 29 Jul 2026 11:30:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(role):=20=E4=BA=BA=E6=A0=BC=E6=AA=94?= =?UTF-8?q?=E7=9A=84=E8=87=AA=E7=94=B1=E7=AB=A0=E7=AF=80=E8=88=87=E8=BA=AB?= =?UTF-8?q?=E5=88=86=E6=AA=94=E5=89=8D=E8=A8=80=E6=AE=B5=E8=90=BD=E4=B9=9F?= =?UTF-8?q?=E8=A6=81=E6=B3=A8=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用者依新格式在人格檔補寫了核心信念、語氣與風格、邊界與規範,並在身分檔標題下 以條目寫存在本質、角色原型、主要稱呼 —— 但兩者都不會被載入,等於白寫: - role_load.sh 原本只抽 ## 本質 與 ## 氛圍 兩節,人格檔其他章節被靜默丟棄 - 也只抽 frontmatter 與具名章節,身分檔「標題後、第一個 ## 之前」的前言段落被丟棄 修正: - 新增 extraSections():注入人格檔除本質與氛圍之外的所有 ## 章節,不限章節名 - 新增 preamble():注入身分檔的前言段落 - SKILL.md 補完整人格檔結構範例(核心信念/語氣與風格/邊界與規範為選填章節)、 新增「注入規則」對照表,並註明角色專屬邊界只能加嚴不可放寬共用行為 - --new 流程第 8 步說明兩個檔案各自該寫什麼 驗證方法的修正也一併記錄:第一次驗證時字串比對命中的其實是「近期對話」區塊裡 使用者剛貼上的原文,造成 16/17 的假陽性。改為只比對 SOUL 區塊後,才驗出前言段落 實際未被注入。往後驗證注入結果一律限定在目標區塊內比對。 回歸:新格式(含前言與自由章節)、舊格式單一檔案、新格式但缺人格檔三種情境 均正常載入,缺人格檔時輸出警告且不會崩。 版號沿用 0.0.5(master 為 0.0.4,同一 PR 不再累加) Co-Authored-By: Claude Opus 5 (1M context) --- scripts/role/role_load.sh | 35 +++++++++++++++++++++++++++++++++++ skills/role/SKILL.md | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/scripts/role/role_load.sh b/scripts/role/role_load.sh index 0d00a9d..a2bd7d0 100755 --- a/scripts/role/role_load.sh +++ b/scripts/role/role_load.sh @@ -157,6 +157,37 @@ const natureFm = soulRaw ? soulFm : fm; const nature = section(natureSrc, "本質(nature)") || natureFm.nature || ""; const vibe = section(natureSrc, "氛圍(vibe)") || natureFm.vibe || ""; +// soul 檔的其餘章節(例如核心信念、語氣與風格、邊界與規範)也要注入。 +// 只抽固定的兩節會讓使用者在人格檔裡寫的其他章節被靜默丟棄。 +function extraSections(text, skip) { + if (!text) return ""; + const body = text.replace(/^---\n[\s\S]*?\n---\n?/, ""); + const out = []; + const re = /^##\s+(.+)$/gm; + const marks = []; + let m; + while ((m = re.exec(body)) !== null) marks.push([m.index, m[0].length, m[1].trim()]); + for (let i = 0; i < marks.length; i += 1) { + const [idx, len, title] = marks[i]; + if (skip.some((s) => title.startsWith(s))) continue; + const end = i + 1 < marks.length ? marks[i + 1][0] : body.length; + const content = body.slice(idx + len, end).trim(); + if (content) out.push(`## ${title}`, "", content); + } + return out.join("\n"); +} +const extra = extraSections(soulRaw, ["本質", "氛圍"]); + +// 標題後、第一個 ## 之前的前言段落(使用者常在此寫存在本質、角色原型等摘要條目)。 +// 只抽 frontmatter 與具名章節會讓這段被靜默丟棄。 +function preamble(text) { + if (!text) return ""; + const body = text.replace(/^---\n[\s\S]*?\n---\n?/, "").replace(/^#\s+[^\n]*\n/, ""); + const idx = body.search(/^##\s+/m); + return (idx < 0 ? body : body.slice(0, idx)).trim(); +} +const intro = preamble(raw); + // 身分只可能在 identity/舊檔裡 const emoji = section(raw, "簽名 emoji") || fm.emoji || ""; const source = section(raw, "來源(source)") || fm.source || ""; @@ -167,6 +198,7 @@ const lines = [ `- 顯示名稱:${fm.name || title || ""}`, `- 簽名 emoji:${fm.emoji || ""}`, ]; +if (intro) lines.push("", intro); if (source) lines.push("", "## 來源(source)", "", source); if (relationship) lines.push("", "## 關係定位(relationship)", "", relationship); lines.push( @@ -178,6 +210,9 @@ lines.push( "## 氛圍(vibe)", "", vibe || "(未設定)", +); +if (extra) lines.push("", extra); +lines.push( "", "## 簽名 emoji", "", diff --git a/skills/role/SKILL.md b/skills/role/SKILL.md index 5c05e40..1b6de8b 100644 --- a/skills/role/SKILL.md +++ b/skills/role/SKILL.md @@ -170,7 +170,10 @@ ROLE_DIR="/../../scripts/role" # 其他助理 | 共用行為區塊 | 版本 A | 版本 B | 是/否 | 個性欄位若使用者只想改其中一項,其餘一律沿用舊值;**共用行為區塊一律以本 skill 的最新版本覆寫**(該區塊由系統維護)。使用者不確認就不寫入。 -8. 寫入 `~/.roles/.identity.md` 與 `~/.roles/.soul.md`(UTF-8 無 BOM,格式見「角色檔標準格式」)。身分檔需填來源與關係定位;共用行為不寫入角色檔。 +8. 寫入 `~/.roles/.identity.md` 與 `~/.roles/.soul.md`(UTF-8 無 BOM,格式見「角色檔標準格式」)。 + 身分檔需填**來源**與**關係定位**,並可在標題下以條目寫存在本質、角色原型、主要稱呼等摘要; + 人格檔除必要的本質與氛圍外,可依角色特性增加核心信念、語氣與風格、邊界與規範等章節。 + 共用行為**不寫入角色檔**(由 `role_load.sh` 注入)。 9. 建立記憶目錄:`node "${ROLE_DIR}/memory.js" stats --role ""`(會順帶建好 `inbox/`、六個分類與 `archive/`)。 10. 若使用者同意網路搜尋且已取得可保存內容,將搜尋摘要寫成已整理記憶,不進 inbox: @@ -431,6 +434,8 @@ updated: ### `<角色 ID>.soul.md` +`## 本質` 與 `## 氛圍`是必要章節;**其餘章節可自由增加,會一併注入**(例如核心信念、語氣與風格、邊界與規範)。 + ````markdown --- id: <角色 ID> @@ -444,8 +449,34 @@ updated: ## 氛圍(vibe) <3 至 5 行:語氣、句子長度、對使用者的稱呼、幽默感尺度、明確禁忌> + +## 核心信念 + +<選填:這個角色在意什麼、用什麼視角看世界、主動性到哪裡> + +## 語氣與風格 + +<選填:語調、表情符號與顏文字習慣、口頭禪;並註明僅適用於自然語言回覆> + +## 邊界與規範 + +<選填:角色專屬的邊界。與共用行為衝突時以共用行為為準> ```` +### 注入規則 + +| 來源 | 是否注入 | +| --- | --- | +| `identity` 的 frontmatter(`id`/`name`/`emoji`) | ✅ | +| `identity` 標題後、第一個 `##` 之前的**前言段落** | ✅ 常用來寫存在本質、角色原型等摘要條目 | +| `identity` 的 `## 來源`/`## 關係定位`/`## 簽名 emoji` | ✅ | +| `soul` 的 `## 本質`/`## 氛圍` | ✅ | +| `soul` 的**其他任何 `##` 章節** | ✅ 不限章節名 | + +寫進角色檔的內容若未被注入就等於白寫,因此上述兩處(前言段落與自由章節)都會完整帶入 —— 曾發生使用者在人格檔補寫章節卻被靜默丟棄的情況。 + +**角色專屬邊界不得放寬共用行為的限制**:共用行為(由 `role_load.sh` 注入)永遠優先,角色檔只能加嚴不能放寬。 + `~/.roles/.active` 只放一行角色 ID,代表目前啟用的角色。 ---