Author SHA1 Message Date
jiantw83andClaude Opus 5 febac85c79 chore(TARGET): 5.4 完成、5.7 剩一條待人工,進度表換掉暫停段
5.4 三個子項目全完成所以勾上層;5.7 八個子項目完成七個,只剩「記憶要記那個行為」
的欄位對齊卡在 persona-story 不在 develop,照規則(子項目全完成才勾上層)不勾。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:07:45 +00:00
jiantw83andClaude Opus 5 f0d7d7422e test(selftest): 補動作格渲染與三段式覆寫的案例,641 → 646
新增五項:room read 的括號帶動作格、一對一也拿到括號格式、害羞預設破口是臉紅、
三段式逐人格覆寫生效且不套用預設收尾、沒寫覆寫的人格照舊。

改一項:「劇場模式強制只輸出對話」原本斷言 ctx 含字面「名字:內容」,
Q6 把格式改成「名字(情緒・動作):內容」之後那個斷言必然失敗——
斷言跟著格式走,不是把格式改回去。

覆寫測試的 fixture 刻意用 sulk 這個 key 而不是「鬧彆扭」:後者是 MODESTY_SIGNALS
的推力詞,寫進 IDENTITY 會把羞恥度推過 75 落到惱羞那級,就測不到嘴硬的三段。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:07:45 +00:00
jiantw83andClaude Opus 5 05031da6dc feat(情緒表達): 三段式逐人格覆寫、括號動作算進額度、害羞預設臉紅、一對一也吃括號
補的是合併後對照查出的缺口,設計都在 Q6/Q7 拍板範圍內,沒有新決策。

三段式逐人格覆寫(G2 的核心顧慮)
- personaSulk() 讀 ## Tells 的 sulk/鬧彆扭/三段式 那一行,分隔吃 → 或頓號。
  共用 ## Tells 這一層、不另開區塊——三段式跟破口是同一種東西(不自覺會做的事),
  分兩個地方寫只會有人只改一邊。
- 只覆寫嘴硬那一級(60-74)。惱羞(75+)最後一段是翻臉、不是音量掉下來,
  拿嘴硬的收尾去套會變成另一個形狀。
- 沒有覆寫就照舊用預設模板。原本所有人格嘴硬起來是同一個樣子,那正是 G2 要避免的。
- 順手把 personaTells() 讀區塊那段抽成 tellsSection(),personaSulk 共用;
  不抽就得再寫一份一樣的 reader。

括號裡的動作算在額度內(Q7 的漏洞)
- 每輪指示原本只說「一輪最多露 2 個動作」,沒說括號那個算不算。
  不講明就會出現「括號演一個、句子裡再演兩個」=一輪三個。

害羞的預設動作是臉紅
- EMOTION_TELLS.shame 原本三條全是句法(鬧彆扭/講反話/轉移話題),
  沒有一條是看得見的,動作格拿不到預設值。臉紅擺第一個。

一對一也吃括號格式
- Q6 拍板的是格式,不是只給劇場。hooks 的劇場那段放寬成
  「名字(情緒・動作):內容」,非劇場那段從完全沒提補成講清楚,
  並重申 *別過頭* 與句子裡的「我愣了一下」仍然不行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:07:45 +00:00
jiantw83andClaude Opus 5 f02e01f629 fix(劇場渲染): room read 的括號吃掉動作格
roomScript() 用 roomTag() 組括號,room read 卻自己拼「(情緒)」——同一份逐字稿
用兩支路徑渲染,動作格在 room read 這邊會整格消失。改成也走 roomTag()。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:07:45 +00:00
jiantw83andClaude Opus 5 f1f5ed61eb chore(TARGET): 合併後逐項對照,5.8 與 5.5 完成、5.7 與 5.4 標出還缺哪幾格
feat/persona-anime-expression 併進來之後照解除條件逐項對照,不照 commit 訊息
照信,每條都回程式裡查過。

完成:5.8 emoji 表程度全部六個子項目、5.5 女性預設值兩個子項目。

5.7 與 5.4 大部分做完但不勾選,各自還缺幾格,逐條寫在該行下面:
- room read 還是只印(情緒),persona.mjs:1861 沒改用 roomTag(),動作格會被吃掉
  (roomScript() 有吃,所以是一半)
- hooks/prompt_submit.mjs:34 劇場模式仍寫死「名字:內容」,非劇場那段更是完全沒提括號
- 每輪指示沒講「括號裡的動作算在兩個額度內」——emoji 那段有提括號,動作沒有
- EMOTION_TELLS.shame 沒把臉紅放進預設破口(actionLint 放行了,但預設值沒改)
- 三段式模板沒有 per-persona 覆寫,所有人格的三段式目前是同一個樣子

記憶行為欄位那條標 ⏭️ 待人工處理:persona-story 不在 develop(在 feat/persona-story-import),
「跟它 1.12 用同一個欄位」現在對齊不了,硬做只會變成各存一份,正是那條要避免的事。
voice/reactions.md 全 repo 零引用。

順便修掉收尾裡已經過期的半句(README 第 29 行那句已隨 dccd07a 移除)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:56:07 +00:00
jiantw83andClaude Opus 5 e12c16ca3f merge(情緒表達): feat/persona-anime-expression 併入 develop
把 G2/G3 的第一批實作(emoji 表程度、名字後括號的動作格、一輪兩個動作、
鬧彆扭三級階梯、女性羞恥預設 62 → 70)併進 develop,與 TARGET.md 的清單合流。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:49:40 +00:00
jiantw83andClaude Opus 5 547cd2bd76 chore(TARGET): 標記暫停實作,解除條件寫在清單裡
另一個 session 正在 feat/persona-anime-expression 上改 persona-lib.mjs、
persona.mjs 與 selftest.mjs,做的是同一批 G2/G3。兩邊同時改渲染與
speechLint() 會重複實作 5.7/5.8 並互相覆蓋,所以先暫停十二項的實作,
只准改 TARGET.md 本身。

解除條件寫死在檔案裡(該分支合進 develop、先逐項對照勾掉已完成的再繼續),
因為每天 00:00 的排程是非互動環境,沒有人在旁邊講這件事——寫在 log 或
對話裡都到不了那一跑的眼前,只有 TARGET.md 到得了。

順便講明排程跑到這段該做什麼:停下來寫 log,不要繞過階段 A1 的未提交變更檢查。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:43:41 +00:00
jiantw83andClaude Opus 5 dccd07a9b8 docs(專案目標): 三條長期目標從 README 搬進 TARGET.md
目標跟「還沒做完的那幾項」分開放會對不起來——G2/G3 的界線寫在 README、
實作清單在別的地方,改東西的時候只會看到一邊。搬成同一份。

README 的〈專案目標〉留一段指路:三條目標各一句話 + 指向 TARGET.md。
原本那句「拍板紀錄與實作清單留在 PR #17 的討論裡」一併移除,
清單現在就在版控裡,不必再繞 PR。

TARGET.md 檔頭改成兩段:〈專案目標〉收 README 搬過來的完整表(前提、意思、
界線三欄原樣),〈這份清單〉說明來源與 Q5-Q10 已拍板。原本自己抄的六條界線
表拿掉——那六條除了 Q8 的「強度全域一致」之外,全都已經在搬過來的界線欄裡,
留著等於同一條規則寫兩遍,日後只會改一邊。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:38:30 +00:00
jiantw83andClaude Opus 5 d9013c6121 chore(TARGET): 建立情緒與語氣擬真優化的每日待辦清單
對著 README〈專案目標〉的 G1/G2/G3 做,收 PR #17 所指「TODO 階段 5」
八節(5.1-5.8)+階段 4 剩餘四項,共 12 個項目。

內容全部取自 9d76984^ 的 TODO_人格優化.md 與 PR #17 的 Q5-Q10 拍板紀錄,
沒有新增設計決策。原 TODO 已移出版控,所以把動手前的六條界線
(硬規則 8 優先、動作只能進括號、emoji 是加上去的、不靠隨機充人味、
性別只給預設值、強度全域一致)抄進檔頭,讓非互動排程也讀得到。

排序按相依性:5.7 括號格式是 5.8 emoji 與 5.4 動作額度的基礎,所以排最前面;
口語指紋緊接 5.6 語域,因為兩者共用同一格資料,不能各存一份。

.gitignore 的 /TODO_*.md 不匹配 TARGET.md,本檔進版控無須改 ignore。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:34:50 +00:00
6 changed files with 262 additions and 30 deletions
+4 -16
View File
@@ -13,22 +13,10 @@ skills 是共通標準,**鎖與隔離的強制執行需要 hook,目前只有
## 專案目標(長期,新功能對著它們判斷)
前提:**跟人格之間只有文字**。沒有表情、沒有聲音、沒有停頓,所有情緒都只能靠字面
與句子的形狀傳過去——所以「情緒表達」在這個專案裡是**排版問題**,不是形容詞問題。
| # | 目標 | 意思 | 界線(不是這樣就走錯了) |
| --- | --- | --- | --- |
| **G1** | 情緒/語氣/記憶持續往**真人**逼近 | 記憶會糊、情緒有底色與疲勞、人格有自己懸著的事,行為與反應不是「回答問題」而是「這個人此刻會怎麼回」 | 不靠口語碎片、隨機數與隨機口誤充人味(見 `anti-ai-voice.md` |
| **G2** | 表達往**動漫角色**靠 | 自稱、句尾、口癖、擬聲短語、嘴硬的三段式(先否認 → 反駁 → 小聲承認)——語域與句法照原作 | **動漫感走句子的形狀,不走旁白**。動作只能加在**名字後面既有的括號**裡(`桐人(喜悅・別過頭):你不要看這邊。`)——括號原本放情緒標註,保留,動作接在後面那一格,只寫看得見的身體動作;`*別過頭*` 這種內嵌星號、「她臉紅了」這種第三人稱旁白,以及句子本體裡的「我愣了一下」一律仍然禁止(硬規則 8)。emoji 用來表示情緒與程度(種類表情緒、數量表程度:`😳``😳💦``😳💦❗`),句子裡也可以用,但它是**加上去的**——把所有 emoji 刪掉之後,句子的形狀仍然要看得出情緒 |
| **G3** | 女性人格的**害羞與鬧彆扭**要更明顯 | 羞恥敏感度預設更高、鬧彆扭有階梯(輕微彆扭 → 嘴硬 → 惱羞),害羞的預設動作是**臉紅** | 性別只給**預設值**,`IDENTITY``SOUL` 的描述永遠蓋過它(見〈性別 → 羞恥敏感度〉);不做「女性=情緒更外顯」的全域放大 |
G2 與 G3 跟硬規則 8(不說 AI 才會說的話、演出來不要講出來)**不衝突但貼得很近**:
放大的是**句子的形狀與語域**,被禁的仍然是**用旁白解釋自己的情緒**。
兩者相撞時以硬規則 8 為準,然後把該案例寫進 `anti-ai-voice.md`
G2/G3 那批(emoji 表程度、名字後括號的動作格、鬧彆扭的階梯)**還沒實作**,
拍板紀錄與實作清單留在 [PR #17](https://gitea.jsc.idv.tw/plugins/persona/pulls/17) 的討論裡——
TODO 清單不進版控。
三條長期目標(**G1** 情緒/語氣/記憶往真人逼近、**G2** 表達往動漫角色靠、
**G3** 女性人格的害羞與鬧彆扭更明顯)連同它們的界線與實作進度,都在
[`TARGET.md`](TARGET.md)——目標跟「還沒做完的那幾項」放在一起才對得起來。
新功能一律對著那三條判斷。
---
+177
View File
@@ -0,0 +1,177 @@
# TARGET — 情緒/語氣/小動作/記憶的擬真優化
## 專案目標(長期,新功能對著它們判斷)
前提:**跟人格之間只有文字**。沒有表情、沒有聲音、沒有停頓,所有情緒都只能靠字面
與句子的形狀傳過去——所以「情緒表達」在這個專案裡是**排版問題**,不是形容詞問題。
| # | 目標 | 意思 | 界線(不是這樣就走錯了) |
| --- | --- | --- | --- |
| **G1** | 情緒/語氣/記憶持續往**真人**逼近 | 記憶會糊、情緒有底色與疲勞、人格有自己懸著的事,行為與反應不是「回答問題」而是「這個人此刻會怎麼回」 | 不靠口語碎片、隨機數與隨機口誤充人味(見 `anti-ai-voice.md` |
| **G2** | 表達往**動漫角色**靠 | 自稱、句尾、口癖、擬聲短語、嘴硬的三段式(先否認 → 反駁 → 小聲承認)——語域與句法照原作 | **動漫感走句子的形狀,不走旁白**。動作只能加在**名字後面既有的括號**裡(`桐人(喜悅・別過頭):你不要看這邊。`)——括號原本放情緒標註,保留,動作接在後面那一格,只寫看得見的身體動作;`*別過頭*` 這種內嵌星號、「她臉紅了」這種第三人稱旁白,以及句子本體裡的「我愣了一下」一律仍然禁止(硬規則 8)。emoji 用來表示情緒與程度(種類表情緒、數量表程度:`😳``😳💦``😳💦❗`),句子裡也可以用,但它是**加上去的**——把所有 emoji 刪掉之後,句子的形狀仍然要看得出情緒 |
| **G3** | 女性人格的**害羞與鬧彆扭**要更明顯 | 羞恥敏感度預設更高、鬧彆扭有階梯(輕微彆扭 → 嘴硬 → 惱羞),害羞的預設動作是**臉紅** | 性別只給**預設值**,`IDENTITY``SOUL` 的描述永遠蓋過它(見 README〈性別 → 羞恥敏感度〉);不做「女性=情緒更外顯」的全域放大 |
G2 與 G3 跟硬規則 8(不說 AI 才會說的話、演出來不要講出來)**不衝突但貼得很近**:
放大的是**句子的形狀與語域**,被禁的仍然是**用旁白解釋自己的情緒**。
兩者相撞時以硬規則 8 為準,然後把該案例寫進 `anti-ai-voice.md`
## 這份清單
下面十二項是 G2/G3(+G1 尾巴)的實作清單,來源是 [PR #17](https://gitea.jsc.idv.tw/plugins/persona/pulls/17)
所指「TODO 階段 5」+階段 4 剩餘項(原 `TODO_人格優化.md` 已於 `9d76984` 移出版控,
全文仍可由 `git show 9d76984^:TODO_人格優化.md` 取回)。
Q5Q10 六題已於 2026/08/03 由使用者拍板,設計不再需要確認,直接實作。
上表沒寫進去、但同樣約束每一項的一條界線:
| 界線 | 內容 |
| --- | --- |
| 強度全域一致(Q8) | 動漫化的**強度**全域一致,不加 `anime_level` 欄位、不做強度旋鈕;**內容**(自稱/句尾/口癖)仍然逐人格不同,否則所有人格會講起話來一樣——那正是 G2 要避免的 |
## 進度(2026/08/03 16:06:10
`feat/persona-anime-expression` 已併入 `develop``35c8665`),暫停解除;
對照時查出的五個缺口也補完了(`selftest` 641 → **646 項全綠**)。
| 節 | 狀態 |
| --- | --- |
| 5.4 鬧彆扭階梯與三段式 | ✅ 完成 |
| 5.5 女性人格預設值 | ✅ 完成 |
| 5.8 emoji 表程度 | ✅ 完成 |
| **5.7 括號動作格** | 八個子項目**七個完成**,只剩「記憶要記那個行為」那條的欄位對齊——見該行的 ⏭️ |
| 5.15.25.35.6 | ⬜ 還沒動 |
| 階段 4 四項、收尾三項 | ⬜ 還沒動 |
5.7 不勾選是因為子項目沒全完成(規則是子項目全完成才勾上層),**不是整節重做**。
---
- [ ] **5.7 動作加進名字後的括號(Q6 拍板格式,其他項目的基礎,先做)**
- 括號**原本就有**`persona-lib.mjs` 的渲染(約 3804 行)本來就是 `名字(情緒)→ 對象:內容`
括號裡是 `room post --emotion` 的情緒標註。這一節是**在既有括號裡多一格**,不是換掉它。
- [x] 格式:括號分兩格,**情緒在前、動作在後,用 `・` 分隔**。兩格都可省略,全省就不出現括號
`桐人(喜悅・別過頭):` `桐人(別過頭):` `桐人(喜悅):` 三種都要成立)
—— `roomTag()``persona-lib.mjs:3908`)(2026/08/03 15:52:52 完成)
- [x] 兩格分工寫死:情緒格是**標註**(既有 meta);動作格只寫**看得見的身體動作**。
動作格不重複情緒名稱——`(喜悅・害羞)` 是把標註寫兩次,不是動作
—— `actionLint()` 擋「寫情緒名稱」(2026/08/03 15:52:52 完成)
- [x] 生理反應算動作,明文允許:臉紅、耳朵紅、手在抖、聲音變小、呼吸亂掉。
判準是**看得見或聽得見**,不是有沒有情緒色彩——`(臉紅)` 可以,`(害羞)` 只能待在情緒格。
害羞這格的預設動作就用**臉紅**,其餘看人格自己的 `## Tells`
—— `actionLint()` 放行;`EMOTION_TELLS.shame` 把**臉紅擺第一個**2026/08/03 16:06:10 完成)
- [x] `speechLint()` 只對括號**外**的內容比對黑名單與句長;括號內另立規則
(情緒格照既有 `injectSafeLine`;動作格單一動作、12 字內)
—— `actionLint()` 擋超過 12 字與一格兩個動作(2026/08/03 15:52:52 完成)
- [x] 劇場模式:`room post``--action "<動作>"`(與既有 `--emotion` 並列),
`room read``room script` 的渲染跟著改;`hooks/` 每輪強制的輸出格式放寬到帶括號
—— `room read` 改用 `roomTag()`(不再自己拼 `(情緒)` 把動作吐掉);
`hooks/prompt_submit.mjs` 劇場那段放寬成 `名字(情緒・動作):內容`2026/08/03 16:06:10 完成)
- [x] **一對一(非劇場)也適用** —— Q6 拍板的是格式,不是只給多人格場景
—— 非劇場那段也講了括號格式與「`*別過頭*` 仍然不行」(2026/08/03 16:06:10 完成)
- [x] 頻率綁 Q7 的兩個動作額度,**括號裡的動作算其中一個**(否則會變成一輪三個動作)
—— 每輪指示補上「名字後面括號裡的動作**算在這個額度裡**——括號演一個、
句子裡再演兩個就是三個」(2026/08/03 16:06:10 完成)
- [ ] **記憶要記那個行為**(Q6 明示):短期與長期記憶加行為備註欄位,記「他做了什麼」而不是「他感到什麼」。
`persona-story` 的 1.12 同一個欄位,**不要各存一份**;`voice/reactions.md` 是同一批資料的落點
**已完成一半**`remember --behavior``behavior` 欄位有了,寫入時跑 `actionLint()`
> ⏭️ 待人工處理:`persona-story` 這個 skill 目前不在 `develop`(在 `feat/persona-story-import` 分支),
> 所以「跟它 1.12 用同一個欄位」無法在這裡對齊;`voice/reactions.md` 全 repo 零引用。
> 兩件都要等那條分支併進來才能一次決定欄位,先做只會變成各存一份——正是這條要避免的事。
- 驗收:`(喜悅・別過頭)` 過得了 lint、情緒標註沒被吃掉;`*別過頭*` 與句子裡的「我愣了一下」照舊被擋下;
那一輪的記憶查得到「別過頭」這個行為
- [x] **5.8 emoji 表示情緒與程度(Q9/Q10 拍板,依賴 5.7 的括號)**2026/08/03 15:52:52 完成)
- 現在情緒格是**自由文字**,跟 `state/emotion.json` 的數值完全沒有連動。這一節把它接上去。
- 格式:**種類表情緒、數量表程度**。`< 40` 不顯示/`4059` 情緒 emoji`6079` `💦``80+` `💦❗`
- [x] 寫一張**十二情緒 → emoji** 對照表(一種情緒一個,例如 `shame: 😳``anger: 😡`),
`persona-lib.mjs`,與 `EMOTION_TELLS` 同一層 —— `EMOTION_EMOJI`
- [x] **per-persona 可覆寫**`IDENTITY.md`,與 `## Tells`、Q5 的符號表同一層)。這是內容不是強度,不違反 Q8
—— `IDENTITY.md``## Emoji` 區塊
- [x] 強度符號固定兩階(`💦` / `💦❗`),不要長成第三階 —— `EMOJI_INTENSITY`
刻意不用重複本體表程度(不是 `😳😳😳`,疊字看起來像洗頁)
- [x] **由 `emotion.json` 的主導情緒自動帶入**,不是人格自由填。門檻對齊 `emotionTells()``min = 40`
`room post --emotion` 的自由文字保留(可手動蓋過)—— `emotionEmojiNow()`
- [x] `speechLint()` 的半形標點與排版檢查要**跳過 emoji 與強度符號**(與 Q5 的顏文字白名單同一支修改)
—— emoji 明確不擋(只在超過 `EMOJI_HINT_AT` 時給 hint);半形標點那條只比對
`[一-鿿]` 相鄰的 `,.!?;:`,碰不到 emoji 與強度符號
- [x] 句子裡自由使用、不設上限(Q10 拍板)。**唯一的煞車是稽核,不是擋**:比照 `emotion --audit`
統計每輪 emoji 數量與趨勢,讓過量看得見;真的失控再回來談上限
—— `emojiAudit()` 掛在 `emotion --audit`,另外算「只有符號沒有句子」那幾則(那才是真的退化)
- 驗收:羞恥度 45/70/90 三種輸出的 emoji 不同;把所有 emoji 刪掉之後,句子的形狀仍然分得出是哪一級
- [ ] **5.2 標點預算(情緒 → 標點與句長的分布)**
- [ ] 把標點納入 `speechLint()` 的可調參數:焦慮 → 刪節號與逗號斷句變多;憤怒 → 句號密度上升、幾乎不用問號;
害羞 → 疊字與破折;喜悅 → 允許一個驚嘆號
- [ ] 上限夾死(一則最多一個驚嘆號、刪節號不連發),否則會變成顏文字的替代品
- 驗收:同一句話在四種情緒下輸出,標點分布看得出差別,且都沒有過量
- [ ] **5.1 訊息的節奏:一輪可以拆成兩則(純文字唯一的「時間」訊號)**
- [ ] `speechBudget()` 之外加**分段預算**:高 arousal 或高羞恥時,同一輪允許輸出兩則短訊,
第二則是**補救、嘴硬或改口**(「⋯剛才那句不算」),而不是補充說明
- [ ] 節流:每 N 輪最多一次,且只在 arousal 或羞恥度過門檻時
- 為什麼:真人在聊天軟體上的情緒,一半是靠「又補了一句」傳出去的。這是文字獨有、目前完全沒用到的手段
- 驗收:慌的時候會多一則短的;平靜時永遠只有一則
- [ ] **5.3 稱呼隨情緒偏移**
- `relation style``--except <情緒>>=<值>` 已能做條件替換(`anger>=40` → 退回全名)。缺的是**害羞方向**。
- [ ] 害羞方向:改口、講到一半換稱呼、把名字吞掉不叫
- [ ] 每個關係節點可設「害羞時的稱呼」,沒設就退回吞掉名字(改用「你」)
- 驗收:同一個對象,生氣時被叫全名、害羞時直接不叫名字
- [x] **5.4 鬧彆扭的階梯與三段式句法(G3 核心,依賴 5.7 的動作額度)**2026/08/03 16:06:10 完成)
- [x] 羞恥度現在是一個純量對三個出口(`hide``spill``confess`)。加**階梯**
輕微彆扭(一句帶過)→ 嘴硬(先否認再小聲承認)→ 惱羞(翻臉,`anger` 的負權重已存在)
—— `modestyDirective` 分三級:41–59 一句帶過/60–74 嘴硬/75+ 惱羞(2026/08/03 15:52:52 完成)
- [x] 三段式做成**句法模板**而不是自由發揮:否認 → 反駁對方的說法 → 最後一句音量掉下來。
模板可 per-persona 覆寫(放 `IDENTITY.md``## Tells`,與現行覆寫同一層)
—— `personaSulk()``## Tells``sulk``鬧彆扭``三段式` 那一行(分隔可用 `→` 或頓號),
`modestyDirective` 有覆寫就用人格自己的形狀。**只覆寫嘴硬那一級(60–74)**:
惱羞(75+)最後一段是翻臉而不是音量掉下來,套嘴硬的收尾會變成另一個形狀
2026/08/03 16:06:10 完成)
- [x] 三段式要吃 Q7 的兩個動作:否認與轉移是**同一種情緒的遞進**,剛好是那條規則的樣板案例。
惱羞那一級是換情緒(`shame``anger`),所以仍然只有兩個動作,不是三個
—— 惱羞級是換情緒,所以仍然兩個動作(2026/08/03 15:52:52 完成)
- 驗收:被稱讚外表時,低/中/高羞恥度三種輸出明顯不同,而且都沒有寫「我害羞了」
- [x] **5.5 女性人格的預設值(G3,只動預設不動個性)**2026/08/03 15:52:52 完成)
- [x] 重看 `GENDER_MODESTY` 的預設值與 `modestyOf()` 的推力表,把女性預設往上調,
並補「鬧彆扭」相關的關鍵詞權重(現在的表偏向「害羞/臉紅」,缺「嘴硬/不坦率/傲嬌」)
—— `GENDER_MODESTY_DEFAULT.female` 62 → **70**`MODESTY_SIGNALS` 補了傲嬌那一路
(傲嬌、嘴硬、不坦率、口是心非、死不承認、愛面子、鬧彆扭、逞強)
- [x] **界線不變**:描述永遠蓋過預設,可以推到 0;不做「女性→情緒更外顯」的全域放大
—— 界線維持,README 的〈性別 → 羞恥敏感度〉表已同步成 70/38/50
- 驗收:兩個都是女性但個性相反的人格,講起話來不一樣
- [ ] **5.6 語域:自稱、句尾、口癖(G2 核心)**
- [ ] per-persona 的自稱/句尾語氣詞/口癖/絕對不說的詞,每輪只注入 2–3 條
- [ ] `persona-anime` 建立人格時一併從原作推導這幾格(現在只推導 `Tells`
- [ ] `persona-story` 的 3A(語氣統計)跑完之後要能直接寫進這一格——兩邊用**同一個欄位**,不要各存一份
- 驗收:把人格的名字遮掉,光看三句話能認出是誰
- 界線(Q8):強度全域一致,內容逐人格不同。這一節動的是內容,不加強度旋鈕
- [ ] **階段 4:口語指紋 idiolect(5.6 的加法面,緊接著做、共用同一格資料)**
- 現在是減法(刪 AI 腔)+語氣層,缺加法。
- [ ] per-persona 的常用詞/句尾/自稱/絕不說的詞,落在與 5.6 相同的欄位上(不得另開一份)
- [ ] **階段 4:習慣記憶 `--type habit`**
- 現在全是宣告性記憶(factevent/preference),缺「我們之間的慣例」。
- [ ] 新增 `habit` 型別,讓「我們之間的慣例」有地方放,並接上 `recall`
- [ ] **階段 4:自傳章節 `memory/chapters/`**
- [ ] 加時期層(「在 SAO 那兩年」「跟他一起做 persona 這段」),介於長期記憶與心智圖之間
- [ ] **階段 4:關係的未修復裂痕**
- `closeness``trust` 是純量,缺「上次那件事還沒和好」。
- [ ] 讓關係節點能掛未修復的裂痕,並在回想與語氣上生效
---
## 收尾(全部項目做完後才處理,不要中途升版)
- [ ] 這一批改動合起來只佔**一個新版號**;三份 manifest`plugin.json``.claude-plugin/plugin.json`
`.codex-plugin/plugin.json`)一起改,**改版號前先看 master 上目前是幾版**
- [ ] `README.md` 的記憶架構表與情緒模型段跟著更新
(原本還有一句「〈專案目標〉第 29 行『G2/G3 那批還沒實作』要改掉」,
那句已隨 `dccd07a` 的目標搬家一起移除,不必再處理)
- [ ] G2/G3 專用驗收腳本:被稱讚外表、被說「你是不是在害羞」、被戳穿嘴硬、當著第三個人被講心事
——四段各跑低/中/高羞恥度,看有沒有變成模仿腔或旁白
+5 -1
View File
@@ -31,7 +31,8 @@ pl.appendJsonl(pl.journalPath(host), {
const theater = Boolean(data.theater) && (data.rooms || []).length > 0;
const hint = theater
? "\n🎭 劇場模式:本輪輸出**只能**是人格對話(`名字:內容`,一行一句)。" +
? "\n🎭 劇場模式:本輪輸出**只能**是人格對話(`名字(情緒・動作):內容`,一行一句" +
"括號兩格都可省略,動作格只寫看得見的身體動作、12 字內,且算在一輪兩個動作的額度裡)。" +
"不要輸出指令、指令結果、狀態、分析、旁白或摘要;CLI 一律加 `--quiet` 並把 stdout 丟到 /dev/null。" +
`\n每個人格每輪 ${pl.MAX_SENTENCES} 句以內;推導寫進 \`think\`(心裡話),不要演成旁白;` +
"近似重複的台詞會被 `room post` 擋下,換個說法或推進話題。" +
@@ -45,6 +46,9 @@ const hint = theater
" ④用 `persona.mjs remember` 寫回短期記憶。" +
`\n講話的樣子:短句(一句 ${pl.MAX_SENTENCE_CHARS} 字內,長了就斷開)、日常用詞、` +
"多講看得見的東西(人、動作、場面)而不是概念;講完就算了,不要解釋自己剛講的話。" +
"\n動作寫在**名字後面的括號**裡(`名字(情緒・動作):內容`),一對一也適用——" +
"括號兩格都可省略,動作格只寫看得見的身體動作(別過頭、臉紅、手在抖)、12 字內," +
"且算在一輪兩個動作的額度裡。`*別過頭*` 與句子裡的「我愣了一下」仍然不行。" +
"\n情緒與記憶指令請參考 /jsc-persona:persona-chat。";
respond({
+49 -9
View File
@@ -795,7 +795,9 @@ export const EMOTION_TELLS = {
sadness: ["話少,只回一個詞", "句子沒說完就停", "答非所問,跳開話題"],
fear: ["先確認安全再講別的", "問句連發", "描述身體與動作(手在抖、往後退)"],
disgust: ["用拉開距離的詞(「那個東西」)", "不肯講出對方的名字", "句子往後退、想結束話題"],
shame: ["鬧彆扭:先否認,再小聲承認", "講反話、嘴硬", "轉移話題、把責任講小"],
// 臉紅擺第一個:這是害羞唯一「看得見」的破口,也是括號動作格的預設值。
// 其餘照人格自己的 `## Tells`。
shame: ["臉紅", "鬧彆扭:先否認,再小聲承認", "講反話、嘴硬", "轉移話題、把責任講小"],
anxiety: ["句子斷在一半", "疊字(「就、就是」「我、我知道」)", "追一句「這樣可以嗎」"],
};
@@ -813,18 +815,23 @@ export const EMOTION_TELLS = {
const EMOTION_ZH_TO_KEY = Object.fromEntries(EMOTION_KEYS.map((k) => [EMOTIONS[k].zh, k]));
/** IDENTITY.md 的 `## Tells` 區塊;沒有就回空物件。 */
export function personaTells(slug) {
/** `IDENTITY.md` 的 `## Tells` 區塊原文;讀不到就回空字串。 */
function tellsSection(slug) {
let text;
try {
text = fs.readFileSync(path.join(personaDir(slug), "IDENTITY.md"), "utf8");
} catch {
return {};
return "";
}
const m = text.match(/^##+\s*(?:Tells|破口|不自覺會做的事)\s*$/im);
if (!m) return {};
const rest = text.slice(m.index + m[0].length);
const section = rest.split(/^##\s/m)[0] || "";
if (!m) return "";
return text.slice(m.index + m[0].length).split(/^##\s/m)[0] || "";
}
/** 讀 IDENTITY.md 的 `## Tells` 區塊;沒有就回空物件。 */
export function personaTells(slug) {
const section = tellsSection(slug);
if (!section) return {};
const out = {};
for (const line of section.split("\n")) {
const row = line.match(/^\s*[-*]\s*([A-Za-z_]+|[一-鿿]{2})\s*[:]\s*(.+)$/);
@@ -849,6 +856,32 @@ export function tellsFor(slug, key) {
return own[key] || EMOTION_TELLS[key] || [];
}
/**
* 鬧彆扭三段式的 per-persona 覆寫:`## Tells` 裡一行
* `- 鬧彆扭: 否認 → 反駁 → 收尾`(也吃 `sulk` 與 `三段式`,分隔可用 `→` 或頓號)。
*
* 跟破口共用 `## Tells` 這一層、不另開區塊——三段式跟破口是同一種東西
* (不自覺會做的事),分兩個地方寫只會有人只改一邊。
*
* 只覆寫**嘴硬那一級**(羞恥度 60–74)的三段。惱羞(75+)最後一段是翻臉、
* 不是音量掉下來,拿嘴硬的收尾去套會變成另一個形狀。
*/
export function personaSulk(slug) {
const section = slug ? tellsSection(slug) : "";
if (!section) return [];
for (const line of section.split("\n")) {
const row = line.match(/^\s*[-*]\s*(?:sulk|鬧彆扭|三段式)\s*[:]\s*(.+)$/i);
if (!row) continue;
const beats = stripInjectionMarkers(row[1])
.split(/[;、,]|→/)
.map((s) => s.trim())
.filter(Boolean)
.slice(0, 3);
if (beats.length >= 2) return beats;
}
return [];
}
/**
* 此刻會不自覺出現的行為:取主導情緒(超出基線最多)裡強度夠的前兩種。
* 強度不夠就不演——低強度的情緒在語氣上是看不出來的。
@@ -1186,9 +1219,15 @@ export function modestyDirective(slug, opts = {}) {
"不要直接說「我害羞」(那是解釋),要讓它出現在句子的形狀上。";
}
if (m.value >= 60) {
// 三段式可以逐人格覆寫(`## Tells` 的 `鬧彆扭` 那一行)。共用一份模板等於所有人格
// 嘴硬起來是同一個樣子,那正是 G2 要避免的。
const own = personaSulk(slug);
const shape = own.length
? `三段照**這個人格自己的形狀**走:**${own.join(" → ")}**`
: "三段照這個形狀走:**先否認 → 反駁他的說法 → 最後一句音量掉下來**" +
"(「⋯才不是那樣。」「你不要一直看這邊。」「⋯嗯。」)";
return `羞恥度 ${m.value}${from}):被稱讚外表、被戳穿心事、距離突然變近的時候,` +
"**先鬧彆扭再承認**——三段照這個形狀走:**先否認 → 反駁他的說法 → 最後一句音量掉下來**" +
"(「⋯才不是那樣。」「你不要一直看這邊。」「⋯嗯。」)。" +
`**先鬧彆扭再承認**——${shape}` +
"不要直接說「我害羞」(那是解釋),要讓它出現在句子的形狀上。";
}
if (m.value <= 40) {
@@ -4748,6 +4787,7 @@ export function turnContext(slug, sessionId, prompt = "") {
"不要用旁白說明自己的狀態(「我有點緊張」是解釋,斷句才是緊張)。" +
`一輪最多露 ${TELLS_PER_TURN} 個動作,而且兩個要來自**同一種情緒**、有遞進關係` +
"(否認 → 轉移、笑 → 補一句、留白 → 不追問);兩種情緒各演一個是演戲。" +
"名字後面括號裡的動作**算在這個額度裡**——括號演一個、句子裡再演兩個就是三個。" +
"強度不到就不演。",
);
}
+2 -2
View File
@@ -1861,8 +1861,8 @@ commands.room = ({ flags, positional }) => {
// `roomPost` 已在寫入端處理,這是給舊逐字稿的第二道)。
for (const row of rows) {
const arrow = row.to && row.to !== pl.ROOM_ALL && row.to !== row.speaker ? `${row.to}` : "";
const emotion = pl.injectSafeLine(row.emotion);
lines.push(`[${row.ts}] ${row.speaker}${emotion ? `${emotion}` : ""}${arrow}${pl.injectSafeLine(row.text)}`);
// 括號用 `roomTag()` 組,跟 `roomScript()` 同一支——自己拼 `(情緒)` 會把動作格吐掉。
lines.push(`[${row.ts}] ${row.speaker}${pl.roomTag(row)}${arrow}${pl.injectSafeLine(row.text)}`);
}
emit({ room, meta, messages: rows }, flags.json, lines);
return;
+25 -2
View File
@@ -388,6 +388,12 @@ check("room script 只輸出 `名字:內容`(無時間戳、無 slug、無
script.includes("Beta") && script.includes(":我是 Beta。") && !script.includes("[20") && !script.includes("system"),
JSON.stringify(script));
check("劇場模式的對話有 emoji 前綴", script.includes("🌙") && script.includes("🪼"), script);
const acted = cli(["room", "post", "--session", S_HOST, "--room", room, "--as", "alpha",
"--text", "你不要看這邊。", "--emotion", "羞愧", "--action", "別過頭"]);
const roomReadOut = cli(["room", "read", "--session", S_HOST, "--room", room]).stdout;
check("room read 的括號也帶動作格(不是只印情緒)",
acted.status === 0 && roomReadOut.includes("(羞愧・別過頭)"),
JSON.stringify(roomReadOut.slice(-300)));
const quiet = cli(["room", "post", "--session", S_HOST, "--room", room, "--as", "alpha", "--text", "安靜發言", "--quiet"]);
check("--quiet 成功時不輸出任何字", quiet.status === 0 && quiet.stdout === "", JSON.stringify(quiet.stdout));
cli(["remember", "--persona", "beta", "--session", S_HOST, "--as-guest", "--scope", "inbox", "--room", room,
@@ -477,8 +483,13 @@ out = hook("turn_end.mjs", { session_id: S_HOST, last_assistant_message: "🪼 A
check("劇場模式時 Stop 不發任何提醒", out.systemMessage === undefined, JSON.stringify(out));
out = hook("prompt_submit.mjs", { session_id: S_HOST, prompt: "你們繼續", cwd: HERE });
ctx = out.hookSpecificOutput?.additionalContext || "";
check("劇場模式時 UserPromptSubmit 強制只輸出對話", ctx.includes("只能") && ctx.includes("名字:內容"), ctx.slice(-300));
check("劇場模式時 UserPromptSubmit 強制只輸出對話",
ctx.includes("只能") && ctx.includes("名字(情緒・動作):內容"), ctx.slice(-300));
cli(["room", "theater", "--session", S_HOST, "--off"]);
out = hook("prompt_submit.mjs", { session_id: S_HOST, prompt: "回來一對一", cwd: HERE });
ctx = out.hookSpecificOutput?.additionalContext || "";
check("一對一也拿到括號格式(Q6 拍板的是格式,不是只給劇場)",
ctx.includes("名字(情緒・動作):內容") && ctx.includes("一對一也適用"), ctx.slice(-400));
out = hook("session_end.mjs", { session_id: S_HOST, reason: "exit" });
check("SessionEnd 釋放鎖", !pl.lockStatus("alpha").locked);
check("釋放後其他 session 可載入", cli(["load", "--persona", "alpha", "--session", S_THIRD]).status === 0);
@@ -1062,7 +1073,7 @@ check("情緒的破口有注入,且說明是演出來不是講出來", (() =>
const ctxTell = pl.turnContext("alpha", S_SPEAK, "那件事做完了嗎");
return ctxTell.includes("此刻不自覺會出現的") && ctxTell.includes("疊字") &&
ctxTell.includes("不要用旁白說明") && ctxTell.includes("一輪最多露 2 個動作") &&
ctxTell.includes("同一種情緒");
ctxTell.includes("同一種情緒") && ctxTell.includes("算在這個額度裡");
})(), pl.turnContext("alpha", S_SPEAK, "那件事做完了嗎").slice(0, 900));
check("十二情緒每一種都有破口可演",
pl.EMOTION_KEYS.every((k) => (pl.EMOTION_TELLS[k] || []).length >= 3));
@@ -1133,6 +1144,8 @@ check("括號兩格:情緒在前、動作在後、用・分隔",
check("只有情緒時括號照舊(現行行為不變)", pl.roomTag({ emotion: "😳" }) === "(😳)");
check("只有動作時也成立", pl.roomTag({ action: "別過頭" }) === "(別過頭)");
check("兩格都沒有就不出現括號", pl.roomTag({}) === "");
check("害羞的預設破口第一個就是臉紅(括號動作格的預設值)",
pl.EMOTION_TELLS.shame[0] === "臉紅", JSON.stringify(pl.EMOTION_TELLS.shame));
check("記憶可以記行為備註(做了什麼,不是感覺到什麼)", (() => {
const out = cli(["remember", "--persona", "alpha", "--session", S_SPEAK, "--role", "persona",
"--text", "他說我修得比上次穩", "--behavior", "把手縮回來", "--salience", "45"]);
@@ -1170,6 +1183,16 @@ console.log("⑥-d 鬧彆扭的階梯與三段式");
check("傲嬌那一路的詞會推高羞恥敏感度",
pl.modestyOf("tsun").value > pl.modestyOf("plainf").value,
`${pl.modestyOf("tsun").value} vs ${pl.modestyOf("plainf").value}`);
// 覆寫用 `sulk` 這個 key,不用「鬧彆扭」——後者是 MODESTY_SIGNALS 的推力詞,
// 寫進 IDENTITY 會把羞恥度推過 75,那就落到惱羞那一級、測不到嘴硬的三段。
mkp("ownsulk", "- Name: 丑\n- Creature: 人類女性\n\n## Tells\n- sulk: 把書擋在臉前 → 你很閒嗎 → ⋯隨便你\n",
"講話簡短。");
const ownShape = pl.modestyDirective("ownsulk", {});
check("三段式可以逐人格覆寫(## Tells 的 sulk 那一行)",
ownShape.includes("這個人格自己的形狀") && ownShape.includes("把書擋在臉前") &&
ownShape.includes("⋯隨便你") && !ownShape.includes("音量掉下來"),
`${pl.modestyOf("ownsulk").value}${ownShape.slice(0, 220)}`);
check("沒寫覆寫的人格照舊用預設模板", pl.personaSulk("shyladder").length === 0);
}
check("說話規則有注入「話短、日常字、看得見的東西、不要回頭解釋」", (() => {
const ctxSay = pl.turnContext("alpha", S_SPEAK, "隨便講");