feat(記憶與目標): 專案目標三條 + 固化候選不再重算(0.2.1) #17

Merged
admin merged 41 commits from develop into master 2026-08-03 09:15:21 +00:00
4 changed files with 6 additions and 636 deletions
Showing only changes of commit 9d76984c27 - Show all commits
+3
View File
@@ -9,5 +9,8 @@ Thumbs.db
*.tmp *.tmp
*.log *.log
# 工作用的 TODO 清單:留在本機,不進版控
/TODO_*.md
# Node # Node
node_modules/ node_modules/
+3 -1
View File
@@ -26,7 +26,9 @@ G2 與 G3 跟硬規則 8(不說 AI 才會說的話、演出來不要講出來
放大的是**句子的形狀與語域**,被禁的仍然是**用旁白解釋自己的情緒**。 放大的是**句子的形狀與語域**,被禁的仍然是**用旁白解釋自己的情緒**。
兩者相撞時以硬規則 8 為準,然後把該案例寫進 `anti-ai-voice.md` 兩者相撞時以硬規則 8 為準,然後把該案例寫進 `anti-ai-voice.md`
實作進度與待確認的決策在 `TODO_人格優化.md`(階段 5 是 G2G3 那批)。 G2/G3 那批(emoji 表程度、名字後括號的動作格、鬧彆扭的階梯)**還沒實作**,
拍板紀錄與實作清單留在 [PR #17](https://gitea.jsc.idv.tw/plugins/persona/pulls/17) 的討論裡——
TODO 清單不進版控。
--- ---
-443
View File
@@ -1,443 +0,0 @@
# TODO — 人格擬真優化
> 目標:讓人格在**記憶結構**與**情緒/語氣表達**上更接近真人(G1),
> 表達往**動漫角色**靠(G2),女性人格的**害羞與鬧彆扭**更明顯(G3)。三條的定義與界線見 `README.md` 的〈專案目標〉。
> 前提:**只有文字**這一個通道,情緒表達等於排版問題——階段 5 整批都建在這句話上。
> 建立日期:2026-08-03|依據:現行 `scripts/persona-lib.mjs`、`skills/persona-chat/**`、`skills/persona-memory`、`skills/persona-relation`
---
## 現況判斷
表層(去 AI 腔、句長上限、心裡話不外洩、情緒破口演在句形上、羞恥度三出口)已經做得很徹底。
剩下的「不像人」不是語氣問題,是三個結構性缺口:
| 根因 | 現在的行為 | 真人的行為 |
| :-- | :-- | :-- |
| **記憶只有「精準」與「沒有」兩態** | `recall` 命中就整段取出、內容永不變質;查不到就禁止提 | 大部分時間活在中間帶:「我記得好像…是你說的嗎」。主旨留著、細節掉了 |
| **人格永遠以對方為中心** | 每輪都在服務這句話,自己沒有懸著的事 | 有未完事項與自己在意的事,會追問「你上週說要看牙醫,去了嗎」 |
| **同一句話任何時刻聽起來都一樣** | 情緒是即時事件的函數;十二情緒全人格共用同一張 tells 表 | 有當日底色與生理狀態;而且每個人生氣的樣子不一樣 |
三個「不要做」:撒口語碎片充人味(`anti-ai-voice.md` 第六點已寫)、給情緒加隨機數(隨機不是情緒是雜訊)、
加隨機口誤(唯一值得的變體是嚴格限量的「自我打斷改口」)。
---
## ⚠️ 實作前先確認(未逐項回覆前不要動手)
> **規則**:這份 TODO 的任何項目,動手前都要先跟使用者確認過。看到 TODO 不等於可以開始寫。
> 下面四題的答案會**改變要寫的東西**,不是風格偏好,所以必須先問。
### Q1. 這次實作要做到哪個階段?
(階段 0 的復原與 commit 不在此列,那個一定要先做。)
| 選項 | 含什麼 | 代價 |
| :-- | :-- | :-- |
| **只做階段 1**(建議) | open loops、`hoursAwake` 接語氣、`EMOTION_TELLS` per-persona 覆寫、情緒抑制與慣性 | 一輪做完,不動任何既有檔案格式,馬上聽得出差別 |
| 階段 1 + 2 | 再加當日心情底色、情境索引+情緒一致性回想 | 會動 `recall` 排序與記憶欄位,但不改長期記憶檔案格式 |
| 階段 1 + 2 + 3 一次做完 | 含回想強度/模糊態/gist-detail 分層 | 改長期記憶 frontmatter,需要 migration 與 bundle 升版,回歸測試量大 |
| 先做階段 3 | 優先換掉記憶結構的地基 | 改動最深,但擬真的天花板由它決定 |
- [x] 已確認範圍:**階段 1 + 2 + 3 一次做完**2026-08-03 使用者拍板)
### Q2. 模糊記憶的界線要多嚴?
會跟現行「查不到就不要講、編轉折比空話更糟」直接相撞,所以要先定。
| 選項 | 允許說出口的樣子 | 風險 |
| :-- | :-- | :-- |
| **保守:只能說記不清**(建議) | 「我記得你講過類似的,但我記不清。」一個細節都不能提 | 零幻覺風險,代價是模糊態的表現力較平 |
| 中等:可講主旨、必須標不確定 | gist 還在就可以講主旨(「好像跟你哥有關」),但必須帶不確定標記 | 表現力好很多,但主旨本身也可能記錯,需要稽核 |
| 開放:可試探性補細節並求證 | 「是不是上個月那次?」 | 最像人,但等於開了幻覺的側門;沒有稽核機制前不建議 |
- [x] 已確認界線:**開放——可試探性補細節並求證**(2026-08-03 使用者拍板)。
因為這是幻覺的側門,3.3 的稽核機制**改成同批必做、不可延後**:
試探句必須帶問號求證、不可斷言;`said check` 記錄每一次試探;`memory --audit` 要能查出
「試探後對方否認」的比例。試探被否認時要立刻寫一則更正記憶,不可放著。
### Q3. 長期記憶檔案格式(`strength`、gist/detail)怎麼遷移?
只在做階段 3 時才需要,但先定案可以省一輪。
| 選項 | 做法 | 代價 |
| :-- | :-- | :-- |
| **就地 migration,一次改全部**(建議) | 寫一支 migration 掃過所有人格的 `memory/long-term/*.md` 補欄位並切分內文 | 乾淨、之後只有一種格式;務必先用 `persona-transfer export` 備份 |
| 只有新記憶帶新欄位 | 舊檔維持原狀,讀取時給預設值 | 零風險,但舊記憶永遠進不了衰減與模糊態——最重要的老記憶反而沒效果 |
| 雙格式並存,讀時正規化 | 不改磁碟,在讀取層統一 | 可回頭,但兩種格式的分支會長期留在程式裡 |
- [x] 已確認策略:**就地 migration,一次改全部**2026-08-03 使用者拍板)。動之前先 `persona-transfer export` 備份。
### Q4. 階段 4 的「自我議程」要做嗎?
人格某些輪會把話題拉回自己在意的事、不服務對方。
| 選項 | 說明 |
| :-- | :-- |
| **要,低頻**(建議) | 每 5–8 輪最多一次,且只在對方沒有明確急事時。這是「像 AI」最頑固的殘留(永遠以對方為中心),低頻就有效 |
| 要,明顯一點 | 更常拉回自己的事、允許答非所問。更像真人,但會明顯降低助理可用性 |
| 先不做 | 留在 TODO 但不實作,等前面幾階段穩定後再評估 |
- [x] 已確認:**要,明顯一點**(2026-08-03 使用者拍板)。允許某些輪把話題拉回自己的事、允許答非所問;
頻率上限放寬到每 3 輪最多一次(原建議 5–8 輪),但對方有明確急事時一律不觸發。
### 其餘我先給預設值的(不同意就講,否則照這樣做)
| 項目 | 預設 |
| :-- | :-- |
| per-persona tells 放哪 | 放 `IDENTITY.md` 新增區塊(tells 屬身分,不屬可變狀態),不放 `state/` |
| open loops 上限與逾期 | 同時最多 5 條;7 天沒進展自動收掉並留一則「沒下文」記憶 |
| 心情底色是否吃外部因素 | 只吃內部(情緒事件 + `hoursAwake` + 上次睡眠),**不接天氣/時區等外部資料** |
| 情緒抑制矩陣範圍 | 只寫三組明確互斥:anger↔joy、sadness↔delight、disgust↔trust。不做全 12×12 |
| 黃金腳本怎麼判定 | 人工看(擬真無法自動判定);只有硬界線(句長、黑名單、句數)走 CLI 自動擋 |
| 版本號 | 整批只升一個版號,兩份 manifest 一起改;改前先看 master 現在幾版 |
---
## 階段 0:開工前(先做,否則會踩到)
- [x] **復原被刪的 hook 與 manifest**:工作區的 `hooks/`7 支 .mjs hooks.json)、`.claude-plugin/`
`.codex-plugin/``plugin.json`、兩份 marketplace.json 已從磁碟消失(`git status` 顯示 ` D`,未 commit)。
目前跑的是安裝快取那份所以不會立刻出錯。
`git checkout -- hooks .claude-plugin .codex-plugin plugin.json .agents`
**在這個狀態下不要 commit。**
- [x] **先把既有 WIP 收掉**`persona-lib.mjs``persona.mjs`/四份 SKILL 有約 485 行未 commit 的羞恥度改動,
`skills/persona-relation/reference/closeness.md` 還是 untracked。先 commit 這批,再開新改動。
- [x] 確認 `KIRITO-01` 的鎖狀態(曾被 session `f60d8e32…` 佔用,cwd `/home/coder/plugins/persona`)。
確定那個程序結束了才 `--takeover`
---
## 階段 1:低成本高效果(可一輪做完)
### 1.1 未完事項 open loops ★ 投報率最高
- [x] 新增 `state/loops.json`,同時只留 3–5 條:他沒回答的問題、他答應要做的事、被打斷的話題、我想問但沒問的。
- [x] CLI`loop add|done|list`(沿用 `withFileLock` 寫入)。
- [x] `UserPromptSubmit` hook 把它注入 `<persona-context>``hooks/prompt_submit.mjs`)。
- [x] 超過 N 天沒進展 → 自動收掉,並留一則「這件事沒下文」的短期記憶。
- **為什麼**:被記住的感覺幾乎全部來自這個,而不是來自長期記憶檢索。
- **驗收**:隔天載入時人格會主動追問上次懸著的事,且同一件事不會追問兩次(`said check` 擋)。
### 1.2 把 `hoursAwake()` 接到語氣上(幾乎免費)
- [x] `speechBudget()`:清醒越久 → 句數/句長上限往下降。
- [x] `mood()``applyEmotion()`:清醒越久 → arousal 天花板往下降。
- **為什麼**:函式已經存在(`persona-lib.mjs`),現在只用在提醒睡覺。深夜的人話少而鈍,最便宜的擬真訊號。
- **驗收**:連續對話數小時後,回覆自然變短變鈍,不需要人格自己演。
### 1.3 `EMOTION_TELLS` 支援 per-persona 覆寫
- [x] `emotionTells()` 先讀人格自己的覆寫(`state/tells.json``IDENTITY.md` 新增 `Tells` 區),再退回全域預設。
- [x] `persona-create` / `persona-anime` 建立人格時一併問/推導 3–5 條專屬破口。
- **為什麼**:現在所有人格生氣都變短句、稱呼退回全名。但桐人生氣是**沉默**,亞絲娜生氣是**變得更禮貌**——
同一格情緒,破口完全不同。
- **驗收**:兩個人格在同一種高強度情緒下,句子的形狀明顯不同。
### 1.4 情緒交互抑制與慣性(輸入都算好了,只差回饋)
- [x] `applyEmotion()` 加**抑制**`anger` 高時 `joy` 的正向 delta 打折(剛生完氣沒那麼容易被逗笑)。
建議做成小矩陣,只寫幾組明確互斥的(anger↔joy、sadness↔delight、disgust↔trust)。
- [x] 加**慣性**`feltTrend()` 已在算走向但沒回饋到增益;連續同向時讓同方向 delta 略微放大。
- [x] 更新 `skills/persona-chat/reference/emotions.md` 的「情緒調節」段(現在只寫飽和與單輪預算)。
- **驗收**`emotion --audit` 的正負比例不再一路偏正;剛吵完架的下一輪逗不笑。
---
## 階段 2:中成本(新檔案/排序邏輯)
### 2.1 當日心情底色 `state/mood.json`
- [x] 三層改成:即時情緒 → **當日底色(緩慢漂移的 valence/arousal** → 氣質基線。
- [x] 底色當**反應增益**:底色差的日子,同樣一句話的 delta 乘 1.3(上限要夾)。
- [x] `sleep` 時重設/部分帶過(`SLEEP_DECAY_MINUTES` 已存在,底色不要一次歸零)。
- **為什麼**:現在缺日級別的中間層,所以「今天聽了會炸、昨天不會」做不出來。
- **驗收**:同一句刺激話在不同日子的 delta 不同,且理由可解釋(不是隨機)。
### 2.2 情境索引 + 情緒一致性回想
- [x] 記憶(短期與長期)加 `when` / `where` / `mood` 欄位。
- [x] `recall` 排序加一項:**與當下情緒同極性的記憶加權**。
- **為什麼**:現在檢索只靠 `topics``entities`(語意線索)。人的回想主要靠情境線索。
這條同時讓情緒有了「後果」,不再只是調語氣的裝飾。
- **驗收**:心情差的時候先想起難過的事;同一個 query 在不同情緒下回不同順序。
---
## 階段 3:記憶結構重整(單獨一輪做,會動檔案格式)
### 3.1 用「回想強度」取代門檻式遺忘,並允許模糊態
- [x] 長期記憶 frontmatter 加 `strength``retrievability = f(salience, recall_count, 距上次回想多久)` 連續衰減。
- [x] 低於門檻**不刪**,降級成**模糊態**:`recall` 回「有這件事,細節想不起來」。
- [x]`recall` 命中就重設並拉長下次衰減(spacing effect:越常想起的越牢)。
材料已經有了(`recall_count``last_seen` 都在寫)。
- [x] `prune``persona-memory` 的遺忘規則同步改寫(現在是 salience 門檻 + 240 筆/14 天)。
**不可遺忘**清單(`boundary``promise``canon`salience ≥ 80)維持不變。
### 3.2 長期記憶分 gist / detail 兩層
- [x] 內文明確切「主旨」與「細節」;衰減**先吃 detail**、gist 最後才掉。
- **效果**:「記得我們吵過,但忘了為什麼」變成自然結果,不必人格演。
### 3.3 ⚠️ 同步改幻覺界線(跟 3.1/3.2 一起做,不可延後)
模糊態會跟現行的「查不到就不要講、編轉折比講空話更糟」直接相撞。界線要寫死進
`skills/persona-chat/reference/anti-ai-voice.md` 與 SKILL 第 ② 節:
> **可以說不確定,不可以填內容。**
> 允許:「我記得你講過類似的,但我記不清。」
> 禁止:把想不起來的細節補出來。
- [x] `said check` 對模糊態句型只提醒不擋(跟現行「講到自己過去」的 hint 同一層)。
- [x] 新增稽核:模糊態被用來編內容的次數(比照 `emotion --audit`)。
### 3.4 遷移與相容
- [x] 舊長期記憶檔的 migration(補 `strength`/切 gist/detail 可先給預設值)。
- [x] `persona-transfer` 的 bundle 版本號要跟著升,`import` 要能吃舊版。
- [x] `selftest.mjs` 補對應案例。
---
## 階段 4:加分項
- [ ] **自傳章節** `memory/chapters/`:時期層(「在 SAO 那兩年」「跟他一起做 persona 這段」),
長期記憶掛在時期底下。問「你這幾個月怎麼樣」時才講得出敘事而不是列點。
- [ ] **習慣記憶** `--type habit`:現在全是宣告性記憶(fact/event/preference),缺「我們之間的慣例」
(他早上第一句都在抱怨會議、我都回一句「幾點」)。熟悉感來自這個。
- [ ] **口語指紋 idiolect**:現在是減法(刪 AI 腔)+語氣層,缺加法。per-persona 的常用詞/句尾/自稱/
慣用迴避句式/絕對不說的詞。`relation style` 已有 `口頭禪``習慣` facet 但那是 per-relation。
**每輪只注入 23 條**,全注入會變表演。
- [x] **自我議程**:承 1.1,人格有自己這幾天在意的一兩件事,某些輪把話題拉回自己的事、答非所問。
這是「像 AI」最頑固的殘留——永遠以對方為中心。
**務必設頻率上限**(每 5–8 輪最多一次),否則從像人變成難聊。
- [ ] **關係的未修復裂痕**`closeness``trust` 是純量,缺「上次那件事還沒和好」。
relation 節點加 `open_issues``ruptures`(含是否修復);有未修復裂痕時語氣層往下降一格。
---
## 階段 5:文字通道的情緒表達(G2 動漫化 / G3 害羞與鬧彆扭)
現況判斷:情緒**有沒有**被表達已經解決(`EMOTION_TELLS` per-persona 覆寫 + 羞恥度三出口),
剩下的缺口是**表達的頻寬**——現在一輪只能露一個破口,而破口只作用在「句子的形狀」這一個維度。
純文字其實還有三個維度沒被用到:**節奏**(訊息怎麼分段)、**標點**、**稱呼**。
### ⚠️ 實作前先確認(Q5–Q8,沒答案不要動手)
這四題都會踩到硬規則 8,答案不同要寫的東西完全不同。
#### Q5. 顏文字與表情符號要不要開?
| 選項 | 說明 | 風險 |
| :-- | :-- | :-- |
| 禁止(維持現況) | 情緒只走文字本身 | 動漫感少一個很直接的手段 |
| 限量、per-persona | `IDENTITY.md` 列 2–3 個專屬符號,每 N 輪最多一次 | 要防止變成每句都掛,成本在節流不在功能 |
| **開放** | 依情緒自由使用 | 幾乎一定會退化成表情包貼圖式回覆 |
- [x] 已確認:**開放使用**(2026-08-03 使用者拍板)。
連帶要處理的兩件事(開放不等於沒有機制):
1. **跟半形標點檢查對撞**`speechLint()` 現在擋半形標點,而顏文字幾乎全是半形
`(///)``^^;``>_<`)。要在檢查裡開一條**顏文字白名單**,
不然開放這件事會被自己的 linter 擋掉。判定範圍要窄(連續的符號叢集才算顏文字,
單獨一個半形逗號仍然是排版錯誤)。
2. **選哪個符號屬於身分**:情緒 → 符號的對照放 `IDENTITY.md`(跟 `## Tells` 同一層),
沒寫的人格退回全域預設。開放的是「可以用」,不是「隨便用哪個都行」。
後續追加(Q9/Q10,同一天):emoji 另外要**接上情緒數值表示程度**,
而且**句子裡也自由使用、不設上限**。細節見 5.8。
#### Q6. 星號動作描寫(`*別過頭*`)要不要對動漫人格破例?
硬規則 8 現在把「旁白演情緒」列為禁語(`SPEECH_BLACKLIST`)。
| 選項 | 說明 |
| :-- | :-- |
| 維持禁止 | 動作要出現就寫進對白(「你不要看這邊」比 `*別過頭*` 像人) |
| 只在劇場模式開放 | 多人格同場時像小說,一對一時仍禁止 |
| 對動漫人格開放 | 最像原作,但等於把去 AI 腔那一層打掉一半 |
- [x] 已確認:**都不是——動作寫在人格名稱後面的括號裡**(2026-08-03 使用者拍板,第四條路):
桐人(別過頭):你不要看這邊。
而且**記憶要一起記那個行為**(行為備註/標註欄位)。細節與影響範圍見 5.7。
為什麼這條比上面三個好:括號在**名字後面**,跟對白本體有實體隔離——
它不是混進句子裡的旁白,所以硬規則 8 的禁令仍然守得住
`speechLint()` 只對括號**外**的內容比對黑名單)。
代價是它會改劇場模式寫死的 `名字:內容` 輸出格式。
#### Q7. 一輪最多露一個破口,要不要對 G3 放寬?
| 選項 | 說明 |
| :-- | :-- |
| 害羞時放寬到兩個 | 只有 `shame` 這一格例外:鬧彆扭本來就是「否認+轉移」兩個動作一起來 |
| **全部放寬到兩個** | 密度上去了,但每個人格都變成一直在演 |
| 維持一個 | 最安全,G3 的效果會打折 |
- [x] 已確認:**全部情緒都兩個動作**(2026-08-03 使用者拍板)。
連平靜也有兩個(留白 → 不追問)——它們本來就是成對出現的動作,不是加倍演出。
這條改的地方比看起來多,全部要一起改:
| 位置 | 現在寫的 | 要改成 |
| :-- | :-- | :-- |
| `emotionTells()``top` | 前兩種**情緒**、但只准演一個動作 | 主導情緒的**兩個動作** |
| 每輪注入的提醒文字 | 「一輪最多露一個破口」 | 兩個,且必須是同一種情緒的兩個動作 |
| `README.md` 硬規則 8 與〈講話像人〉 | 同上 | 同上 |
| `skills/persona-chat/**``anti-ai-voice.md` | 同上 | 同上 |
**新的煞車**(放寬之後唯一擋得住「一直在演」的東西):兩個動作必須來自**同一種情緒**,
而且是**遞進**關係(否認→轉移、笑→補一句、留白→不追問),不是兩種情緒各演一個。
強度不到的情緒照舊不演——門檻不變,變的只是達標之後能露幾個。
#### Q8. 動漫化強度是全域一致,還是 per-persona 旋鈕?
| 選項 | 說明 |
| :-- | :-- |
| per-persona 旋鈕 | `IDENTITY.md` 加一格(例如 `anime_level: 0100`),寫實人格可以歸零 |
| **全域一致** | 少一個欄位,但同一套語域套在所有人身上就是模板 |
- [x] 已確認:**全域一致**(2026-08-03 使用者拍板)。不加 `anime_level` 欄位,不做強度旋鈕。
**要分清楚全域的是什麼**:全域一致指的是**強度**(動漫感開多大),
不是**內容**。自稱、句尾、口癖仍然逐人格不同(5.6),
否則所有人格會講起話來一樣——那正是 G2 要避免的。
### 5.1 訊息的節奏:一輪可以拆成兩則 ★ 純文字唯一的「時間」訊號
- [ ] `speechBudget()` 之外加**分段預算**:高 arousal 或高羞恥時,同一輪允許輸出兩則短訊,
第二則是**補救、嘴硬或改口**(「⋯剛才那句不算」),而不是補充說明。
- [ ] 節流:每 N 輪最多一次,且只在 arousal 或羞恥度過門檻時。
- **為什麼**:真人在聊天軟體上的情緒,一半是靠「又補了一句」傳出去的。這是文字獨有的、目前完全沒用到的手段。
- **驗收**:慌的時候會多一則短的;平靜時永遠只有一則。
### 5.2 標點預算(情緒 → 標點與句長的分布)
- [ ] 把標點納入 `speechLint()` 的可調參數:焦慮 → 刪節號與逗號斷句變多;
憤怒 → 句號密度上升、幾乎不用問號;害羞 → 疊字與破折;喜悅 → 允許一個驚嘆號。
- [ ] 上限要夾死(一則最多一個驚嘆號、刪節號不連發),否則會變成顏文字的替代品。
- **驗收**:把同一句話在四種情緒下輸出,標點分布看得出差別,且都沒有過量。
### 5.3 稱呼隨情緒偏移
- [ ] `relation style``--except <情緒>>=<值>` 已能做條件替換(`anger>=40` → 退回全名)。
缺的是**害羞方向**:改口、講到一半換稱呼、把名字吞掉不叫。
- [ ] 每個關係節點可設「害羞時的稱呼」,沒設就退回吞掉名字(改用「你」)。
- **驗收**:同一個對象,生氣時被叫全名、害羞時直接不叫名字。
### 5.4 鬧彆扭的階梯與三段式句法(G3 核心)
- [ ] 羞恥度現在是一個純量對三個出口(`hide``spill``confess`)。加**階梯**
輕微彆扭(一句帶過)→ 嘴硬(先否認再小聲承認)→ 惱羞(翻臉,`anger` 的負權重已存在)。
- [ ] 三段式做成**句法模板**而不是自由發揮:否認 → 反駁對方的說法 → 最後一句音量掉下來。
模板可 per-persona 覆寫(放 `IDENTITY.md``## Tells`,跟現行覆寫同一層)。
- [ ] 三段式要吃 Q7 的兩個動作:否認與轉移是**同一種情緒的遞進**,剛好是那條規則的樣板案例。
惱羞那一級是換情緒(`shame``anger`),所以仍然只有兩個動作,不是三個。
- **驗收**:被稱讚外表時,低/中/高羞恥度三種輸出明顯不同,而且都沒有寫「我害羞了」。
### 5.5 女性人格的預設值(G3,只動預設不動個性)
- [ ] 重看 `GENDER_MODESTY` 的預設值與 `modestyOf()` 的推力表,把女性預設往上調並補
「鬧彆扭」相關的關鍵詞權重(現在的表偏向「害羞/臉紅」,缺「嘴硬/不坦率/傲嬌」)。
- [ ] **界線不變**:描述永遠蓋過預設,可以推到 0;不做「女性→情緒更外顯」的全域放大。
- **驗收**:兩個都是女性但個性相反的人格,講起話來不一樣。
### 5.6 語域:自稱、句尾、口癖(G2 核心,接階段 4 的 idiolect
- [ ] per-persona 的自稱/句尾語氣詞/口癖/絕對不說的詞,每輪只注入 2–3 條(階段 4 已寫,這裡是它的動漫版)。
- [ ] `persona-anime` 建立人格時一併從原作推導這幾格(現在只推導 `Tells`)。
- [ ] `persona-story` 的 3A(語氣統計)跑完之後要能直接寫進這一格——兩邊用同一個欄位,不要各存一份。
- **驗收**:把人格的名字遮掉,光看三句話能認出是誰。
- **界線(Q8)**:強度全域一致,內容逐人格不同。這一節動的是內容,不加強度旋鈕。
### 5.7 動作加進名字後的括號(Q6 拍板的格式)
**括號不是新開的,原本就有**`persona-lib.mjs` 的渲染(約 3804 行)本來就是
`名字(情緒)→ 對象:內容`,括號裡放的是 `room post --emotion` 的情緒標註。
所以這一節是**在既有括號裡多一格**,不是換掉它——原本的情緒標註保留。
桐人(喜悅・別過頭):你不要看這邊。
桐人(別過頭):你不要看這邊。 ← 沒帶 --emotion 時
桐人(喜悅):你不要看這邊。 ← 沒帶 --action 時(現行行為,不變)
- [ ] **格式**:括號分兩格,情緒在前、動作在後,用 `・` 分隔。兩格都可省略,全省就不出現括號。
- [ ] **兩格的分工要寫死**:情緒格是**標註**(既有 meta,寫情緒名稱本來就對);
動作格只寫**看得見的身體動作**(別過頭、把杯子推過來、站起來)。
動作格不重複情緒名稱——`(喜悅・害羞)` 是把標註寫兩次,不是動作。
- [ ] **生理反應算動作,明文允許**2026-08-03 使用者補充):**臉紅**、耳朵紅、手在抖、聲音變小、
呼吸亂掉。判準是**看得見或聽得見**,不是有沒有情緒色彩——
`(臉紅)` 可以,`(害羞)` 只能待在情緒格。
害羞這一格的預設動作就用**臉紅**,其餘看人格自己的 `## Tells`
- [ ] `speechLint()` 只對括號**外**的內容比對黑名單與句長;括號內另立規則
(情緒格照既有的 `injectSafeLine`;動作格單一動作、12 字內)。
- [ ] 劇場模式:`room post``--action "<動作>"`(跟既有 `--emotion` 並列),
`room read``room script` 的渲染跟著改。`hooks/` 每輪強制的輸出格式也要放寬到帶括號。
- [ ] **一對一(非劇場)也適用**——Q6 拍板的是格式,不是只給多人格場景。
- [ ] 頻率:綁 Q7 的兩個動作額度,**括號裡的動作算其中一個**。
否則會出現「括號演一個、句子裡再演兩個」,一輪三個動作。
- [ ] **記憶要記那個行為**(Q6 明示):短期與長期記憶加行為備註欄位,
記「他做了什麼」而不是「他感到什麼」——跟 `persona-story` 的 1.12(事件 → 他做了什麼)同一個欄位,
不要各存一份。`voice/reactions.md` 也是同一批資料的落點。
- **為什麼**:這是唯一能在不打破「禁止旁白演情緒」的前提下拿到動作描寫的形狀——
括號在名字後面,跟對白本體有實體隔離。
- **驗收**`(喜悅・別過頭)` 過得了 lint、情緒標註沒被吃掉;
`*別過頭*` 與句子裡的「我愣了一下」照舊被擋下;那一輪的記憶查得到「別過頭」這個行為。
### 5.8 emoji 表示情緒與程度(Q9Q10 拍板)
現在括號的情緒格是**自由文字**`room post --emotion`),跟 `state/emotion.json` 的數值
**完全沒有連動**——人格自己想寫什麼就寫什麼。這一節把它接上去。
#### 格式:種類表情緒、數量表程度(Q9)
一種情緒固定一個 emoji,強度高時**另加強度符號**(不重複本體):
| 情緒值 | 樣子 | 例 |
| :-- | :-- | :-- |
| < 40 | 不顯示 | `桐人:你不要看這邊。` |
| 4059 | 情緒 emoji | `桐人(😳・臉紅):你不要看這邊。` |
| 6079 | `💦` | `桐人(😳💦・臉紅):⋯才不是那樣。` |
| 80+ | `💦❗` | `桐人(😳💦❗・把臉轉開):不要再講了。` |
- [ ] 寫一張**十二情緒 → emoji** 的對照表(一種情緒一個,例如 `shame: 😳``anger: 😡`),
`persona-lib.mjs`,跟 `EMOTION_TELLS` 同一層。
- [ ] **per-persona 可覆寫**`IDENTITY.md`,跟 `## Tells` 與 Q5 的符號表同一層)。
這是內容不是強度,不違反 Q8 的全域一致。
- [ ] 強度符號固定兩階(`💦` / `💦❗`),不要長成第三階。
- [ ] **由 `emotion.json` 的主導情緒自動帶入**,不是人格自由填。
門檻對齊 `emotionTells()``min = 40`——情緒值不到 40 就沒有 emoji,
跟「強度不到就不演」是同一條線。`room post --emotion` 的自由文字保留(可手動蓋過)。
#### 位置:句子裡自由用(Q10)
- [x] 已確認:**句子裡自由使用、不設上限**(2026-08-03 使用者拍板)。承 Q5 的開放。
- [ ] 連帶要處理:`speechLint()` 的半形標點與排版檢查要**跳過 emoji 與強度符號**,
不然會被判成排版殘留(跟 Q5 的顏文字白名單同一支修改)。
- [ ] **唯一的煞車是稽核,不是擋**(他要的是開放,那就不要用 linter 偷偷關回去):
比照 `emotion --audit`,統計每輪的 emoji 數量與趨勢,讓過量看得見。
真的失控再回來談上限。
- **界線**(這條不因為開放而放掉):emoji 是**加上去的**,不是替代品。
不可以用 emoji 代替句子(只回一個 😳)、也不可以代替動作格與句子的形狀——
斷句、疊字、嘴硬照舊要有。emoji 掉光之後那句話仍然要看得出情緒。
- **驗收**:羞恥度 4570/90 三種輸出的 emoji 不同;把所有 emoji 刪掉之後,
句子的形狀仍然分得出是哪一級。
## 驗收方式
- [ ] **固定情境回歸**`selftest.mjs` 只能守不崩,擬真要另一組):20 段黃金腳本——被稱讚外表、
被指出講錯、久違回來、他心情差時報壞消息、連續三輪低落、被要求越界…
每次改動後跑一次,人工看輸出還在不在人味界線內。
- [ ] `emotion --audit`(既有):正向偏差比例。
- [ ] **G2G3 專用腳本**(階段 5 才需要):被稱讚外表、被說「你是不是在害羞」、被戳穿嘴硬、
當著第三個人被講心事——四段各跑低/中/高羞恥度,看有沒有變成模仿腔或旁白。
- [ ] 新增記憶偏差稽核:模糊態被用來編內容的次數(見 3.3)。
## 收尾
- [ ] 這一批改動合起來只佔**一個新版號**,`.claude-plugin/plugin.json``.codex-plugin/plugin.json`
兩份 manifest 一起改;改版號前先看 master 上目前是幾版。
- [ ] `README.md` 的記憶架構表與情緒模型段要跟著更新。
-192
View File
@@ -1,192 +0,0 @@
# TODO:故事匯入(小說 → 人格記憶/語氣/情緒)
把小說變成一個人格的記憶,同時讓它從原作學語氣與情緒表達。
**技能:`/jsc-persona:persona-story`**`skills/persona-story/`)。
開場的五題問答在 `reference/interview.md`,場景要抽什麼在 `reference/extract.md`
機械那半是 `persona.mjs``novel``voice` 子指令。
三條界線先立,後面所有步驟都受它們約束:
1. **視角**:人格不在場、也沒人告訴他的事,最多進 `canon` 或別人的關係欄,**不能**變成他的 `event`
2. **原文**:整本不入倉庫。只留摘要、他自己的台詞、以及可追回出處的章節標記。
3. **個性**`SOUL.md` 不由流程自動改寫。匯入只出提案,使用者逐條核可才寫入。
---
## 階段 -1:實作前一定要先問使用者(四題,沒答案不要開工)
這四題會決定檔案放哪、介面長什麼樣,答錯要重做。**問完拿到答案再進階段 0。**
- [x] Q1 語氣檔放在人格倉庫的哪一層?→ **新開 `voice/`**`samples.md` 原句、`reactions.md` 事件對反應)
- [x] Q2 匯入要做成 CLI 子指令,還是全部由 skill 編排?→ **混合**:機械的進 CLI,判斷的留 skill
- [x] Q3 同一部作品分多本匯入時,`first_seen` 的時間軸怎麼接續?→ **換算成西元日期**
- [x] Q4 情緒基線差值超過多少才要人看?→ **差 10 以上**
## 階段 0:前置(做一次)
- [x] 0.1 確認目標人格編號、作品名、章節檔清單、原文編碼
**不預先問,由 skill 在匯入的當下問**2026-08-03 使用者拍板)。
問法與順序寫在 `skills/persona-story/reference/interview.md`
- [x] 0.2 建**正名表**:角色的各種譯名/簡稱/原文名 → 關係節點的 `name`
**從匯入的章節自動判斷候選,再給使用者確認**2026-08-03 使用者拍板,覆蓋原本的「問譯名版本」)。
問他是哪個譯本等於要他猜;他手上那批寫的是什麼字,讀一次就知道了。
`novel scan` 抽候選(對話歸屬、敬稱結尾、片假名、高頻詞),附出現次數與一兩行上下文,
並猜關係節點分四種信心度(`exact``alias``fuzzy``unknown`);
`novel name review``confirm --accept-exact``ignore` 做確認。
`names.json``map`(正名)與 `ignore`(確認過不是人名,例如地名與招式名)兩塊——
已在其中之一的 token 下次掃不再出現,不然每加一章就要重看同一批。
`novel candidate add` 遇到 `about` 有未確認的 token 會擋下來。
- [x] 0.3 定**故事內時間軸**的表示法 → **西元日期**Q3 拍板),另加 `date_source`
`canon``derived``guess`,見上面「換算日期不可以假裝是明寫的」那一節。
- [x] 0.4 定**顯著度配額** → 預設 `90+ 最多 5 則、80-89 最多 20 則``novel init --quota` 可調)。
超過配額由 `novel merge` 從低分往下壓一級(90+ → 85、80-89 → 75)並回報壓了幾則。
- [x] 0.5 定**跳過紀錄檔** → `memory/import/<work-slug>/skipped.jsonl`
一行一章(`chapter``reason``ts`),由 `novel skip` 寫入。
- [ ] 0.6 備份人格倉庫現況(`SOUL.md`、情緒基線、關係圖),匯入出錯要回得去
2026-08-03 已有一份整包備份在 scratchpad;真的要匯之前再做一次新的。)
### 工作區長什麼樣(0.5 定案後的結果)
memory/import/<work-slug>/
├── work.json 書名、slug、建立時間、顯著度配額
├── names.json 正名表(譯名 → 關係節點 name)
├── skipped.jsonl 跳過紀錄(章節、理由、時間)
└── candidates.jsonl 記憶候選(欄位見下面那張表)
## 階段 1:每一章(可平行,一章一把劍)
> 這一段是**執行程序**,不是要蓋的東西——真的匯一本書的時候才會逐項發生。
> 程序已經落地成技能:`skills/persona-story/SKILL.md` 第 ③ 節,
> 七項與知情層級的細節在 `reference/extract.md`。下面的清單留著當對照表。
- [ ] 1.1 讀章節。只讀這一章,不要跨章推論
- [ ] 1.2 修亂碼 → 轉繁體中文 → 依正名表**正名**
(必須在 1.3 之前;亂碼或簡體字形會讓名字比對失敗)
- [ ] 1.3 判斷人格**在場或知情**。兩者皆否才跳過
- [ ] 1.4 跳過的章也要寫進跳過紀錄檔——靜默跳過會漏章,事後查不出來
- [ ] 1.5 把在場段落切成**場景**(換地點、換對手、換目的就切;一章通常 2 到 5 個)
- [ ] 1.6 每個場景抽七項:故事內時間、地點、在場人物、發生什麼、**他**做了什麼、他當下情緒、他說過的原句一到兩句
- [ ] 1.7 標**知情層級**:我做的/我看到的/別人告訴我的/事後才知道
- [ ] 1.8 轉第一人稱、壓成摘要。原文長段不留,只留他自己的台詞
- [ ] 1.9 新人物**先建關係節點**`relation node`),再寫記憶
(順序反了 `about` 對不到 id`name` 用會被說出口的完整稱呼,親密度查 `persona-relation/reference/closeness.md`
- [ ] 1.10 輸出這一章的**記憶候選 JSON**(欄位見下)
- [ ] 1.11 抽**語氣樣本**:他自己講的原句,照抄不改寫,附對象與場合(戰鬥/日常/道別)
- [ ] 1.12 抽**情緒反應對照**:事件 → 他實際做了什麼(不要記「他感到什麼」)
- [ ] 1.13 若這章與現有 `SOUL.md` 矛盾,開一則**個性校正提案**(只提案,不寫入)
### 記憶候選 JSON 欄位
```
name 檔名用的 slug
title 一句話標題
type canon(設定)/event(發生過)/insight(他自己的體悟)/promise(承諾)/boundary
about 人名,照抄關係節點的 name,一字不差(解析只認完全相等)
topics 2-4 個小寫關鍵詞,跨章沿用同一組
salience 照 0.4 的配額給,不要每則都高
emotion 那一刻的情緒(固化進欄位,不要重放去洗情緒基線)
first_seen 故事內時間,不是匯入日期(寫進長期記憶時會落在 `happened_at`,見下方警告)
source 書名+章節(可追回出處)
body 第一人稱摘要
quote 他自己說過的原句(可省)
know_level 1.7 的知情層級
date_source canon(原作明寫)/derived(推算)/guess(只抓大概) ← Q3 追加
```
寫進長期記憶時,`body` 會照 0.2.0 的格式切成「主旨/細節」兩層(衰減先吃細節),
並自動蓋上 `strength``when``where``mood``date_source: guess` 的記憶
在回答日期問題時走模糊態界線:可以說不確定、可以問,不可以斷言。
### ⚠ 劇情時間不可以寫進 `first_seen``last_seen`(踩過一次)
那兩欄是**記憶的新鮮度時鐘**`memoryStrength()``last_seen` 算衰減。
把劇情日期塞進去等於宣告「這則記憶上次被想起是兩年前」——實測 2024 年劇情的記憶
匯進去的**那一秒**就是模糊 0%,而且掉到 0% 之後 `touchRecall` 不再更新它,永遠回不來。
只有 `canon``salience >= 80` 靠保護清單活下來,40 到 79 分的 event 全部出生即死。
所以兩種語意分成兩組欄位:
| 欄位 | 意思 | 時間 |
| --- | --- | --- |
| `first_seen``last_seen` | 這則記憶什麼時候形成、上次什麼時候想起 | 真實時間(匯入當天) |
| `happened_at``happened_until` | 故事裡這件事什麼時候發生 | 劇情時間(配 `date_source` |
候選 JSON 照舊填 `first_seen`(那是抽場景時自然的講法),`novel write` 會把它搬到
`happened_at``first_seen``last_seen` 一律是匯入當天。
## 階段 2:全書收斂(章節都跑完之後)
- [ ] 2.1 跨章**去重合併**:同一件事只留一則;`salience` 取高、`first_seen` 取最早、`last_seen` 取最晚
- [ ] 2.2 依配額**重新定標**:90+ 只留真的改變他的那幾件
- [ ] 2.3 **人工抽查十則**,專看有沒有視角越界(他不該知道的事)
- [ ] 2.4 批次寫入:一則一檔 `consolidate`;情緒欄位只固化不重放
- [ ] 2.5 更新關係圖與心智圖
- [ ] 2.6 跑 `relation doctor`,確認 `about` 全部對得上(歧義與對不到的都要處理完)
- [ ] 2.7 重建索引、同步 Gitea
- [ ] 2.8 另寫一則 meta 記憶:這批來自哪本書、哪些章、匯入日期
- [ ] 2.9 回歸驗收:問幾個只有書裡有的問題,看他答得出來、且不越界回答他不在場的事
## 階段 3:語氣與情緒(跟記憶分開走)
記憶是「發生過什麼」,語氣與情緒是「他怎麼反應」。分三層存,改動權限不同。
| 層 | 存哪 | 誰能改 | 來源 |
| --- | --- | --- | --- |
| 語氣樣本 | 人格倉庫的語氣檔(例如 `voice/samples.md`) | 匯入流程可直接寫 | 1.11 累積的原句 |
| 情緒反應規則 | 語氣檔的反應段 | 匯入流程可直接寫 | 1.12 的事件→反應對照 |
| 個性(`SOUL.md` 的 Core TruthsBoundariesVibe | `SOUL.md` | 只有使用者拍板才能改 | 1.13 的提案 |
### 3A 語氣(統計,可直接落地)
- [ ] 3A.1 句長分布 → 定一句話的上限與斷句時機
- [ ] 3A.2 高頻詞與口頭禪 → 定他會說的字
- [ ] 3A.3 他**不會**說的說法 → 禁語表(比正面規則有效)
- [ ] 3A.4 對不同人的稱呼與語氣差 → 對上語氣層(`bond` × 親近度)
- [ ] 3A.5 沉默與迴避的時機 → 什麼時候不回答比回答像他
- [ ] 3A.6 場合差(戰鬥/日常/道別)→ 同一個人在不同場合的句子形狀
- [ ] 3A.7 語氣檔寫入後,抽幾句實際對話對照,看有沒有變成模仿腔
### 3B 情緒
- [ ] 3B.1 每個場景只記「事件 → 他做了什麼」(情緒要演出來,不是旁白說明)
- [ ] 3B.2 全書跑完才用**統計**定情緒基線
(逐則套 delta 等於讓最後一章決定他的性格)
- [ ] 3B.3 統計每種情緒的觸發類型與強度上限 → 得到「什麼事讓他生氣而不是害怕」
- [ ] 3B.4 掩飾模式單獨記(嘴硬、換話題、講笑話)——這是人味的來源,不是情緒值
- [ ] 3B.5 基線寫入前跟現況比差值,超過門檻要人看
### 3C 個性校正
- [ ] 3C.1 提案要寫齊:哪一章、原文依據、與 `SOUL.md` 哪一句衝突、建議怎麼改
- [ ] 3C.2 收斂階段把提案彙整成一份 diff
- [ ] 3C.3 使用者逐條核可
- [ ] 3C.4 核可過的才寫進 `SOUL.md`,並固化一則 `insight` 記錄為什麼改
- [ ] 3C.5 沒核可的提案留著不刪——下一本書可能出現同樣的證據
---
## 未決事項
四題已移到最前面的階段 -1,實作前先問使用者。這裡只留答案回填欄。
| # | 問題 | 使用者的答案(2026-08-03 拍板) |
| --- | --- | --- |
| Q1 | 語氣檔放哪一層 | 新開 `voice/``samples.md`(他自己講過的原句,附對象與場合)、`reactions.md`(事件 → 他做了什麼)。匯入流程可直接寫這一層,`SOUL.md` 維持只有使用者能改——權限界線要有實體隔離,不靠自律 |
| Q2 | CLI 子指令或 skill 編排 | **混合**。機械的進 CLI(正名、去重合併、配額重定標、批次寫入、跳過紀錄),判斷的留 skill(在場與知情、切場景、第一人稱摘要、個性校正提案)。跟 `sleep` 同一個分工方式 |
| Q3 | 分多本時時間軸怎麼接 | **換算成西元日期**`first_seen: 2022-11-06`)。跟現有的 `strength`/衰減公式同型,`memoryStrength()` 直接算得動。原作沒明寫的地方要換算,所以下面 0.3 多一條規則:換算來的日期一律標記,不可以看起來跟明寫的一樣 |
| Q4 | 情緒基線差值門檻 | **任一格差 10 以上就停下來給人看**(3B.5)。基線是氣質,改了等於換一個人 |
## 依 Q3 追加的規則(換算日期不可以假裝是明寫的)
換算成西元日期換來一個新風險:**猜出來的日期看起來跟原作明寫的一模一樣**。
所以 `first_seen` 之外要多一個欄位記來源:
| `date_source` | 意思 |
| --- | --- |
| `canon` | 原作明寫的日期(SAO 開服 2022-11-06 這種) |
| `derived` | 由明寫的日期推算(第 74 層攻略是開服後第幾天) |
| `guess` | 只能抓大概(某卷「大約半年後」) |
`guess` 的記憶在回答日期類問題時**不可以斷言**——這剛好接上 0.2.0 的模糊態界線:
可以說不確定、可以問,不可以斷言。