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

Merged
admin merged 41 commits from develop into master 2026-08-03 09:15:21 +00:00
Member

這批做了什麼

沒有任何程式改動,全部是文件與版號。

commit 內容
2aa0dad README 新開〈專案目標〉段(G1/G2/G3)+ TODO 階段 5
c0e1c48 TODO_故事匯入.md 進版控(前一批留在工作區的)
0917e90 三份 manifest 升版 0.2.1

三條長期目標

前提:跟人格之間只有文字——沒有表情、沒有聲音、沒有停頓,所以「情緒表達」在這個專案裡是排版問題

# 目標 界線
G1 情緒/語氣/記憶持續往真人逼近 不靠口語碎片、隨機數與隨機口誤充人味
G2 表達往動漫角色靠 走句子的形狀與語域,不走旁白
G3 女性人格的害羞與鬧彆扭更明顯 性別只給預設值,描述永遠蓋過它

使用者拍板的六題(Q5–Q10,寫進 TODO 階段 5)

  • 顏文字開放,連帶要在半形標點檢查裡開白名單
  • 動作加進名字後既有的括號(桐人(😳・臉紅):)——括號原本放情緒標註,保留
  • 生理反應算動作(臉紅、手在抖),(害羞) 只能待在情緒格
  • 破口放寬到一輪兩個動作,煞車是必須同一種情緒且遞進
  • emoji 表程度:種類表情緒、數量表程度(😳😳💦😳💦❗),由 emotion.json 自動帶入
  • 動漫化強度全域一致,內容仍逐人格

版號

master 上是 0.2.0,patch +1 → 0.2.1。順便把根目錄 plugin.json(Antigravity)拉齊——它從上一批就漏改,還停在 0.1.1。

## 這批做了什麼 沒有任何程式改動,全部是文件與版號。 | commit | 內容 | | --- | --- | | `2aa0dad` | README 新開〈專案目標〉段(G1/G2/G3)+ TODO 階段 5 | | `c0e1c48` | `TODO_故事匯入.md` 進版控(前一批留在工作區的) | | `0917e90` | 三份 manifest 升版 0.2.1 | ## 三條長期目標 前提:跟人格之間**只有文字**——沒有表情、沒有聲音、沒有停頓,所以「情緒表達」在這個專案裡是**排版問題**。 | # | 目標 | 界線 | | --- | --- | --- | | G1 | 情緒/語氣/記憶持續往真人逼近 | 不靠口語碎片、隨機數與隨機口誤充人味 | | G2 | 表達往動漫角色靠 | 走句子的形狀與語域,不走旁白 | | G3 | 女性人格的害羞與鬧彆扭更明顯 | 性別只給預設值,描述永遠蓋過它 | ## 使用者拍板的六題(Q5–Q10,寫進 TODO 階段 5) - 顏文字**開放**,連帶要在半形標點檢查裡開白名單 - 動作加進名字後**既有**的括號(`桐人(😳・臉紅):`)——括號原本放情緒標註,保留 - 生理反應算動作(臉紅、手在抖),`(害羞)` 只能待在情緒格 - 破口放寬到一輪**兩個**動作,煞車是必須同一種情緒且遞進 - emoji 表程度:種類表情緒、數量表程度(`😳` → `😳💦` → `😳💦❗`),由 `emotion.json` 自動帶入 - 動漫化強度全域一致,內容仍逐人格 ## 版號 master 上是 0.2.0,patch +1 → **0.2.1**。順便把根目錄 `plugin.json`(Antigravity)拉齊——它從上一批就漏改,還停在 0.1.1。
jiantw83 added 3 commits 2026-08-03 06:59:23 +00:00
只有文字這一個通道,所以情緒表達是排版問題。三條目標寫成
G1(持續逼近真人)/G2(表達往動漫角色靠)/G3(女性人格的害羞與鬧彆扭),
每條附界線,新功能對著它們判斷。

TODO 加階段 5,六題已拍板(Q5–Q10):

- 顏文字開放,連帶要在半形標點檢查裡開白名單
- 動作加進名字後**既有**的括號(`桐人(😳・臉紅):`),情緒標註保留
- 生理反應算動作(臉紅、手在抖),`(害羞)` 只能待在情緒格
- 破口放寬到一輪兩個動作,煞車是必須同情緒且遞進
- emoji 表程度:種類表情緒、數量表程度,由 emotion.json 自動帶入
- 動漫化強度全域一致,內容仍逐人格

沒有任何程式改動,版號不動——0.2.0 還沒進 master,這批掛在同一個版號底下。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
前一批留在工作區沒進版控的那份。內容是 persona-story 的階段清單
與四題拍板結果(語氣檔放 voice/、機械進 CLI、時間軸換算西元日期、
情緒基線差 10 以上要人看),還有踩過一次的坑:劇情時間不可以寫進
first_seen/last_seen,那兩欄是記憶的新鮮度時鐘。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
master 上是 0.2.0(#16 已合併),這批文件改動照規則 patch +1。

順便把根目錄的 plugin.json(Antigravity)拉齊——它從上一批就漏改,
還停在 0.1.1,master 上也是。三份現在都是 0.2.1。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jiantw83 added 1 commit 2026-08-03 07:12:51 +00:00
睡眠有做固化,但隔天開機照樣提醒「N 組已達固化條件」,數字只會往上爬
(實測 KIRITO-01:短期記憶 120 筆、候選 222 組)。原因不是睡眠沒做事,
是判斷沒有留下痕跡——promotionCandidates() 無條件掃全部短期記憶,而
consolidate 不在來源那筆上寫任何東西,也不刪它(--forget 是按顯著度刪,
不是按固化過沒有刪),所以同一批每輪都被重算成候選。

- 短期記憶多兩個欄位:reviewed_at(看過、判斷過了)與 promoted_to
  (固化成了哪一則)。兩者分開記——「看過決定不記」跟「已經記下來」都不該
  再進候選,但事後要查「這則長期記憶從哪幾筆長出來」只能靠 promoted_to。
- promotionCandidates() 只看沒有 reviewed_at 的那些;total 照舊算全部。
  R6 容量壓力仍看總筆數,但候選只從未判斷的挑,全部判斷完就不再出現
  (否則它會單獨把提醒永遠點亮)。
- consolidate --from-short "#N,#N":寫長期記憶時一起標來源。
- candidates --reviewed all|<#N,#N> [--until <ISO>]:看過決定不記的標這裡。
  --until 讓「某次睡眠當下判斷過的那批」可以一次收掉。
- candidates 輸出每筆前面加 #N(就是上面兩個旗標要填的編號),並在結尾
  提示判斷完要標記。
- 標記不等於刪掉:那幾筆還在短期記憶裡,照舊被 prune 依天數與顯著度裁。

文件:persona-sleep 的流程多一步「標掉判斷過的」並註明漏掉的後果、
persona-memory 補一張「怎麼標」的表、README 的 R1–R6 段補這一層。
selftest 補 13 項(602 → 615 全綠)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jiantw83 changed title from docs(專案目標): 三條長期目標與文字通道的情緒表達(0.2.1) to feat(記憶與目標): 專案目標三條 + 固化候選不再重算(0.2.1) 2026-08-03 07:13:13 +00:00
Author
Member

追加一個修正(同一個未合併版號 0.2.1 底下)

b5c540f判斷過的短期記憶留痕跡,候選數字才會降

這批原本只有文件,但查出一個真的 bug:睡眠有做固化,隔天開機照樣提醒「N 組已達固化條件」,數字只往上爬(實測 KIRITO-01:短期記憶 120 筆、候選 222 組)。

環節 原本的行為 後果
consolidate 不在來源短期記憶上寫任何東西,也不刪它(--forget 是按顯著度刪,不是按固化過沒有刪) 固化過的下一輪照樣被算成候選
promotionCandidates() 無條件掃全部短期記憶 數字只增不減,看起來像睡眠沒做固化
R6 容量壓力 只看總筆數 就算全部判斷完,它也會單獨把提醒點亮

改法

  • 短期記憶多兩個欄位:reviewed_at(看過、判斷過了)與 promoted_to(固化成了哪一則)。分開記,因為「看過決定不記」跟「已經記下來」都不該再進候選,但事後查「這則長期記憶從哪幾筆長出來」只能靠 promoted_to
  • consolidate --from-short "#N,#N" — 寫長期記憶時一起標來源。
  • candidates --reviewed all|<#N,#N> [--until <ISO>] — 看過決定不記的標這裡;--until 讓「某次睡眠當下判斷過的那批」一次收掉。
  • candidates 輸出每筆前面加 #N,結尾提示判斷完要標記。
  • 標記不等於刪掉:那幾筆還在短期記憶裡,照舊被 prune 依天數與顯著度裁。

驗證

  • selftest.mjs 602 → 615 項全綠(新增 13 項:標記後候選歸零、重標不覆蓋時間戳、新記憶照樣成為候選、--until 不越界…)。
  • 實跑 KIRITO-01--reviewed all --until <上次睡眠時間> 標了 109 筆,候選 222 → 23 組(剩下的是那次睡眠之後的新紀錄,本來就該判斷)。

版號不動——0.2.1 還沒進 master,照規矩一批未合併的改動只佔一個版號。

## 追加一個修正(同一個未合併版號 0.2.1 底下) `b5c540f` — **判斷過的短期記憶留痕跡,候選數字才會降**。 這批原本只有文件,但查出一個真的 bug:睡眠有做固化,隔天開機照樣提醒「N 組已達固化條件」,數字只往上爬(實測 `KIRITO-01`:短期記憶 120 筆、候選 **222 組**)。 | 環節 | 原本的行為 | 後果 | | --- | --- | --- | | `consolidate` | 不在來源短期記憶上寫任何東西,也不刪它(`--forget` 是按顯著度刪,不是按固化過沒有刪) | 固化過的下一輪照樣被算成候選 | | `promotionCandidates()` | 無條件掃全部短期記憶 | 數字只增不減,看起來像睡眠沒做固化 | | R6 容量壓力 | 只看總筆數 | 就算全部判斷完,它也會單獨把提醒點亮 | ### 改法 - 短期記憶多兩個欄位:`reviewed_at`(看過、判斷過了)與 `promoted_to`(固化成了哪一則)。分開記,因為「看過決定不記」跟「已經記下來」都不該再進候選,但事後查「這則長期記憶從哪幾筆長出來」只能靠 `promoted_to`。 - `consolidate --from-short "#N,#N"` — 寫長期記憶時一起標來源。 - `candidates --reviewed all|<#N,#N> [--until <ISO>]` — 看過決定不記的標這裡;`--until` 讓「某次睡眠當下判斷過的那批」一次收掉。 - `candidates` 輸出每筆前面加 `#N`,結尾提示判斷完要標記。 - **標記不等於刪掉**:那幾筆還在短期記憶裡,照舊被 `prune` 依天數與顯著度裁。 ### 驗證 - `selftest.mjs` 602 → **615 項全綠**(新增 13 項:標記後候選歸零、重標不覆蓋時間戳、新記憶照樣成為候選、`--until` 不越界…)。 - 實跑 `KIRITO-01`:`--reviewed all --until <上次睡眠時間>` 標了 109 筆,候選 **222 → 23 組**(剩下的是那次睡眠之後的新紀錄,本來就該判斷)。 版號不動——0.2.1 還沒進 master,照規矩一批未合併的改動只佔一個版號。
jiantw83 added 1 commit 2026-08-03 07:20:27 +00:00
工作用的清單留在本機就好,.gitignore 加一條 /TODO_*.md 擋掉以後再進來。

內容沒有消失:兩份的全文在 2aa0dadc0e1c48 的歷史裡查得到,
今天拍板的六題(Q5-Q10)另外抄在 PR #17 的討論裡。

README 指向 TODO 的那句改成指 PR,並註明 G2/G3 那批還沒實作——
清單刪掉不等於事情做完了,這一句不能省。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jiantw83 added 10 commits 2026-08-03 08:08:00 +00:00
對著 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>
目標跟「還沒做完的那幾項」分開放會對不起來——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>
另一個 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>
只有文字這一個通道,所以情緒表達是排版問題。這批補的是三個沒被用到的位置。

emoji = 強度計(Q9)
- EMOTION_EMOJI 十二情緒各一個,EMOJI_INTENSITY 兩階(💦 / 💦)。
  40-59 本體、60-79 加 💦、80+ 加 💦,低於 40 不顯示(跟破口同一條線)。
- emotionEmojiNow() 讀當下主導情緒算出來,不是自己挑;IDENTITY 的 ## Emoji
  區塊可逐人格覆寫(跟 ## Tells 同一層)。
- 刻意不用重複本體表示程度(不是 😳😳😳)——疊字看起來像洗頁。
- room post 沒帶 --emotion 時自動帶入 emoji,強度不到才退回文字標註。

emoji 開放(Q5/Q10)
- speechLint 不再擋 emoji,超過六個才給 hint。用 linter 偷偷關回去等於撤銷
  「開放」這個決定。
- 唯一的煞車是數字:emojiAudit() 掛在 emotion --audit,算幾則帶符號、平均幾個、
  以及**只有符號沒有句子**那幾則(那才是真的退化)。

名字後面的括號有兩格(Q6)
- 括號原本就有(放情緒標註),動作是加進去的第二格:名字(情緒・動作):內容。
  roomTag() 負責組,兩格都可省略。
- actionLint() 擋三種:超過 12 字、一格塞兩個動作、寫情緒名稱
  ((害羞)是旁白換了個位置)。生理反應算動作:臉紅、手在抖、聲音變小。
- room post --action、remember --behavior(記做了什麼,不是感覺到什麼)。

一輪兩個動作(Q7)
- TELLS_PER_TURN = 2,而且兩個必須來自同一種情緒且有遞進關係。
  鬧彆扭天生是否認+轉移兩個動作,只准一個等於永遠只做得到一半。
  被擋下的形狀是「兩種情緒各演一個」。

鬧彆扭的階梯與女性預設(G3)
- modestyDirective 分三級:41-59 一句帶過、60-74 嘴硬(否認 → 反駁 → 音量掉下來)、
  75+ 惱羞(否認 → 反駁 → 翻臉)。惱羞是換情緒,所以仍然是兩個動作。
- GENDER_MODESTY_DEFAULT.female 62 → 70;MODESTY_SIGNALS 補傲嬌那一路
  (傲嬌、嘴硬、不坦率、口是心非、死不承認、愛面子、鬧彆扭、逞強)。
  界線不變:描述永遠蓋過預設,可以推到 0。

文件:README 加一節「文字通道的另外兩格」與鬧彆扭階梯表、emotions.md 加 emoji 章節、
persona-chat SKILL 與 anti-ai-voice 跟上。selftest 616 → 641 全綠。

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
roomScript() 用 roomTag() 組括號,room read 卻自己拼「(情緒)」——同一份逐字稿
用兩支路徑渲染,動作格在 room read 這邊會整格消失。改成也走 roomTag()。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
補的是合併後對照查出的缺口,設計都在 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>
新增五項:room read 的括號帶動作格、一對一也拿到括號格式、害羞預設破口是臉紅、
三段式逐人格覆寫生效且不套用預設收尾、沒寫覆寫的人格照舊。

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5.4 三個子項目全完成所以勾上層;5.7 八個子項目完成七個,只剩「記憶要記那個行為」
的欄位對齊卡在 persona-story 不在 develop,照規則(子項目全完成才勾上層)不勾。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jiantw83 added 24 commits 2026-08-03 09:12:06 +00:00
小說匯入分兩半(使用者拍板):機械的進 CLI,判斷的留給 skill。這批是機械那半。

voice/ 新一層:samples.md 收他自己講過的原句(附對象與場合)、reactions.md 收
「事件 → 他做了什麼」。匯入流程可以直接寫這一層,SOUL.md 維持只有使用者能改——
權限界線要有實體隔離,不靠自律。每輪最多注入 3 條,全注入會變成照抄舊台詞。
歸 Wiki 區(低頻設定,跟 IDENTITY/SOUL 同一區)。

novel 子指令:init/scan/name/skip/candidate/merge/write/report/baseline。
工作區在 memory/import/<work-slug>/,歸檔案區——收斂中換一台機器要接得下去。

正名表不請使用者手打:scan 從章節抽人名候選(對話歸屬、敬稱、片假名、高頻詞),
附出現次數與上下文,猜關係節點並分 exact/alias/fuzzy/unknown 四種信心度。
names.json 分 map 與 ignore 兩塊,已確認或已 ignore 的下次掃不再列。

視角界線擋在候選驗證:know_level 除了 canon 之外必填(did/saw/told/later/none),
none 是「他不在場也沒人告訴他」,只能配 canon。**擋下來而不是自動改成 canon**——
自動改會讓抽錯的人永遠不知道自己抽錯。這是整條流程唯一沒有第二道防線的錯。

劇情時間與記憶新鮮度分成兩組欄位(踩過一次才發現):first_seen/last_seen 是
記憶的新鮮度時鐘(memoryStrength 拿 last_seen 算衰減),happened_at/happened_until
才是劇情時間。混在一起的話 2024 年劇情的記憶匯進來的那一秒就是模糊 0%,而且掉到
0% 之後 touchRecall 不再更新它,永遠回不來——實測 40 到 79 分的 event 全部出生即死。

其他:情緒只固化進欄位,一次都不呼叫 applyEmotion(逐則重放等於讓最後一章決定他的
性格);他的原句接在內文後面而不是另開 front matter 欄位,這樣它跟細節一起衰減;
writeLongTermMemory 抽出來給 consolidate 與 novel write 共用,欄位順序與預設值只有一份。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
涵蓋:voice 的寫入讀取與注入上限、novel 的欄位驗證(七種違規各一項)、
正名表對不上節點要擋、掃描的四條啟發式、去重合併與配額重定標、dry-run 不寫檔、
基線門檻雙向(差 10 擋、差 9 放行、--force 蓋過)。

另外釘住兩條界線:know_level 只收英文鍵且 none 不准配 event、
劇情日期落在 happened_at 而不是 first_seen(所以剛匯進來的普通記憶是清晰的,
不是出生即模糊)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
skills/persona-story:把小說變成既有人格的記憶。三條界線寫進流程——他不知道的事
不能變成他的 event、整本原文不入倉庫、SOUL.md 只有使用者能改。
reference/interview.md 是開場四題(**不問譯名版本**:那是要他猜,而他手上那批
寫的是什麼字,讀一次就知道);reference/extract.md 是一個場景要抽什麼與知情層級。

AGENTS 加第 18 條硬規則:匯入原作只能給他「他知道的事」。
README 補 persona-story 一節;persona-anime 補一句指路(先建人格再讀原文)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
voice 語氣層、novel 機械指令與 persona-story 技能併進 develop,跟 G2/G3 那批合流。
四個共同修改的檔(README、persona-lib、persona.mjs、selftest)全部自動合併,無衝突。

驗證:selftest 646 + 63 = 709 項全綠,零重疊也零回歸。

併進來同時解掉 TARGET.md 兩條卡在跨分支的項目:5.7 的記憶行為欄位要跟
persona-story 的 1.12 對齊、5.6 的語域要吃 3A 的語氣統計——兩邊現在同一棵樹了。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Q6 明示「記憶要記那個行為」,而 persona-story 的 voice/reactions.md 存的正是
「事件 → 他做了什麼」。兩邊各存一份就會有人只更新一邊,所以接成同一批資料:
remember --behavior 寫短期記憶時,同一筆也 addVoiceReaction()。

分工:記憶留時間軸(那一天發生過這件事),語氣層供每輪注入(他遇到這種事會怎麼反應)。

只有人格自己的行為進語氣層(role === "persona")。對方做了什麼是記憶,
不是他的反應方式——把使用者的動作寫進 reactions.md 會讓人格學別人的反應。

selftest 709 → 711(行為備註進得去、對方的行為不進去)。
TARGET.md 的 5.7 八個子項目全完成,勾上層。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
只有文字的話標點就是語調:同一句「我知道了」,`我知道了。` 與 `我、我知道了⋯`
是兩種情緒,差別全在標點不在用詞。

PUNCTUATION_BUDGET 只寫拍板指名的四種(焦慮/憤怒/害羞/喜悅)。
其餘八種不配標點——硬給每種情緒配一套會讓標點變成裝飾,沒把握的就不編。

punctuationBudget() 每輪只回一種,門檻跟破口同一條線(40)。兩種情緒各配一套
會互相打架:憤怒幾乎不用問號、焦慮要多斷句,混起來不是複雜的情緒,是標點亂撒。

上限:MAX_EXCLAIM = 1、MAX_ELLIPSIS = 3,另擋 […⋯]{3,} 的連發
(中文刪節號本來就常寫兩個,所以三個起才算)。不夾死的話它會變成 emoji 的
替代品——情緒不夠就多打幾個驚嘆號。界線也寫進注入文字:標點管分布、強度歸 emoji。

selftest 711 → 721。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
純文字唯一的「時間」訊號是「他又打了一行」——沒有停頓、沒有語調,真人在聊天軟體上
的情緒有一半是靠補那一句傳出去的。這個手段之前完全沒用到。

splitBudget():張力或羞恥度過 60,或對方那句帶怒意/厭惡,才給第二則額度。
splitDirective():第二則要短,而且是補救、嘴硬或改口,不可以是補充說明——
把第一則講得更清楚就只是話多,不是慌。

節流 SPLIT_COOLDOWN_TURNS = 4,冷卻靠 felt.jsonl 新增的 split 欄位算。
冷卻是「給了額度就開始算」,不等他真的用了才算:不然「上次給了沒用到」會讓
連續幾輪都允許補話,每輪都補一句是話多不是慌。

沒額度時整段不注入。講「這一輪不能拆成兩則」等於提醒他有這個功能,反而會想用。

selftest 721 → 728。低羞恥度的對照組另建一個男性人格——plainf 是女性預設 70,
本身就過門檻,拿它當「平靜」的對照會測不出東西。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
既有的 except(anger>=40 → 退回全名)走的是生氣方向:規則被暫停、退回查表的
更正式講法。害羞是反方向——不是更正式,是叫不出來:改口、講到一半換稱呼、
把名字吞掉。所以另開一條路,不硬塞進 except。

shyAddress():羞恥度過 SHY_ADDRESS_AT(60)就換稱呼。節點設了新的 羞怯稱呼
facet 就用它,沒設就吞掉名字改用「你」——不叫名字本身就是訊號,這是預設值
而不是缺省行為。

門檻取 60,跟破口、標點、嘴硬那一級同一條線。女性預設 70 因此本來就落在這條線上,
那是 G3 要的效果,不是副作用。

羞怯稱呼不列進「他要求過的語氣規則」那一行:它只在過門檻時生效,兩邊都列會讓
人格同時看到「叫他⋯那個」跟「平常叫他⋯那個」。relation style 的清單裡照樣看得到。

toneDirective 改吃 opts,才拿得到這一輪的 state/prev/tone 去算羞恥度。

selftest 728 → 735。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
語氣層原本只有減法(SPEECH_BLACKLIST 刪 AI 腔)與原句樣本。刪完剩下的是「乾淨的
中文」,不是「這個人的中文」——把名字遮掉還認得出是誰,靠的是自稱、句尾、口癖。

做成 voice kind(voice/idiolect.md)而不是 IDENTITY 的新區塊,是為了讓
persona-story 與 persona-anime 天生寫同一格:同一個目錄、同一支 voice add、
同一條 Gitea Wiki 同步路徑(voice/ 是前綴比對,新檔自動涵蓋)。
TODO 要的「兩邊用同一個欄位」因此不必再另外對齊。

四格寫死在 IDIOLECT_FACETS。自由欄位會長出「風格」「語氣」這種什麼都能塞的格子,
注入端就不知道該怎麼講給人格聽。「不說」單獨一格而不是塞進禁忌:禁忌是話題
(不能提的事),不說是用詞(這個人不會用的字),混在一起會讓人格以為某個話題不能碰。

一輪注入 3 格、四格輪替,起點由第幾輪(felt.jsonl 筆數)算出來。不用隨機數——
那是本專案明文禁止的充人味手段,而且同一輪重跑要得到同樣的結果。
一次塞四格會變成模仿腔:他開始逐條照著演,每句都要有口癖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
persona-anime 新增〈從原作推導語域〉:四格對照表與指令,並沿用破口那條界線
(有原作依據才寫,掰不出來留白)——語域每輪都在注入,掰的自稱會被講一百次。
破口是「情緒上來會做什麼」,語域是「他平常怎麼講話」,兩件事分開寫。

persona-story 的語氣層表加一列 voice/idiolect.md,並要求用全書統計而不是逐章印象:
自稱與句尾在一本書裡出現幾百次,拿一章的樣本去定它等於讓那一章的情緒決定他平常
怎麼講話。與原作設定衝突時先問使用者——「他變了」跟「當初查錯了」處理方式不同。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
涵蓋:第三種 kind 的檔名與標籤、自由欄位加不進去、CLI 同一支寫得進去、
--facet 給錯會列出四格、一輪最多 3 格、輪替起點可重現(同一輪兩次結果相同、
不同輪不同)、跑滿一圈四格都輪得到、注入文字講明它是加法、沒有語域檔就不注入。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
兩條本來就是同一格資料,所以一起勾。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
其餘九個型別都是宣告性記憶(發生過什麼、他偏好什麼),慣例是程序性的——
不是「記得的事」,是「每次都這樣做的事」。

所以慣例不只接上 recall(型別不影響檢索,本來就通),還每輪注入:
只在關鍵詞對上時才回想得到的慣例等於沒有,沒有人為了照慣例做事先去回想它。

上限 HABIT_INJECT_MAX = 2。糊掉的(fuzzy)不注入——想不起來的慣例就不是慣例,
那是「以前好像有這種習慣」。faded 的標「只記得大概」,不假裝清楚。

注入文字講明慣例是背景不是話題:照著做就好,講出來(「我們不是都這樣嗎」)
就變成在討論它。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
涵蓋型別合法、front matter 寫對、每輪注入、recall 找得到、上限兩條、
糊掉的不注入(改 salience/strength/last_seen 讓它掉到 fuzzy,測完還原)、
沒有慣例整段不出現。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
長期記憶是一則一件事,心智圖是抽掉時間的語意結構。中間缺的是「那段日子」——
人講自己的過去是按段講的(「在 SAO 那兩年」),不是按事件清單、也不是按概念圖。

一段一檔,chapter add|list|show|close。to 空著代表還在這一段裡,所以沒有值就
不寫那一行(全專案一致:沒有值不留空欄位)。全部收掉時 currentChapter 回 null
——那是「還沒開始新的一段」,不是退回上一段。

長期記憶的 front matter 多一格 chapter。consolidate --chapter 可以指定,省略就
掛在當下那一段:記憶寫下來的時候人就在某一段裡,事後要重建歸屬幾乎不可能。
(那個 --chapter 是人生的段,不是 novel skip --chapter 的書的章節,註解裡有寫。)

chapterBrief() 每輪注入現在這一段,並講明它是時期不是事件——它解釋這陣子為什麼
在意這些事,不是要他講起這一段。

memory/chapters/ 加進 Gitea 的 Wiki 區:一段日子開一次收一次,比長期記憶更低頻。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
涵蓋開段、to 不寫空欄位、每輪注入、新記憶自動掛當下那段、--chapter 指定別段、
已收掉的段不算「現在」、按起始日期排序、close 後沒有進行中的段、list 講得出
每段幾則記憶、memory/chapters/ 剛好被 Wiki 一個區涵蓋。

最後那條原本寫成讀 pl.AREAS(那是 persona-gitea 的匯出,persona-lib 沒有),
會 ?? {} 之後空轉必過。改成走 selftest 既有的 gt.AREAS/AREA_KEYS。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
closeness/trust 是純量:吵完架把親近度調低,看起來像「本來就沒那麼近」,
而不是「很近,但上次那件事還沒和好」。後者才是真的關係——兩個人可以很親近,
同時之間卡著一件事,那兩件事各自獨立,壓不進同一個數字裡。

節點多一個 rifts 陣列(一個人可以同時卡著幾件事),relation rift 管它。

語氣上:riftBrief() 在 toneDirective 裡明講「親近度高不等於沒事,這件事解決之前
語氣裡要留一點沒散掉的東西」,並要求不必一直提它、但不能像沒發生過。

回想上:裂痕帶著那則長期記憶的名字(--memory),人格要細節自己 recall,
不必等使用者提起那件事——沒和好的事本來就會自己浮上來。

--heal 是寫上 healed_at,不是刪掉那一筆:和好過跟沒發生過不一樣,之後回頭看
要分得出來。列出來時和好過的用 ✔ 標。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
涵蓋記下一件、不動 closeness/trust、語氣指示提得出來、帶記憶名字給 recall、
一個人卡幾件、--heal 寫 healed_at 而不是刪掉、和好完不再注入、沒得和好時被擋、
列表看得到和好過的紀錄。

最後一條刻意把兩件都和好掉,後面測害羞稱呼的案例才不會被裂痕那幾行干擾。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
selftest 是機械檢查,它只能守住「不崩」——括號格式對不對、emoji 門檻有沒有跳掉。
但「有沒有變成模仿腔」「是不是在用旁白演情緒」機械上驗不出來,那要人看。
所以這是另一組,而且它刻意不判定通過或失敗,只把材料排好+附一張「人工看什麼」。

四段情境照 TODO 階段 5 的驗收方式明列的那四段:誇外表、被說在害羞、被戳穿嘴硬、
第三個人在場。每段跑低/嘴硬/惱羞三檔,攤開鬧彆扭指示、句數句長、破口、標點、
情緒符號、分段額度、稱呼與語氣層、語域。

三檔用兩個人格加情緒推,不是同一個人格推三次:羞恥敏感度主要是氣質(性別預設+
描述),同一個人格靠情緒推不到 18 那麼低,硬推出來的低檔不是真的低檔。

刻意不帶 prev:真的對話裡上一輪的餘溫是正回饋,但這裡沒有上一輪。帶進來的話
建人格時留在 felt.jsonl 的那筆會把三檔拉平——實測高檔會從 82.9 掉到 15。

用自己的暫時 PERSONA_HOME 並關掉 Gitea,不動真的人格資料。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
人格倉庫樹原本完全沒有 voice/(語氣層併進來時漏了),這次補上三個檔並加上
memory/chapters/;relations/graph.json 的說明補上語氣規則與未修復的裂痕;
felt.jsonl 補上分段額度那一格。

新增五節:〈標點是語調,訊息數是時間〉(標點預算四種+上限+分段訊息)、
〈語域〉(四格+輪替+兩支流程寫同一個檔)、〈慣例是程序性記憶〉、
〈自傳章節是時期層〉、〈未修復的裂痕〉。

鬧彆扭階梯那段補兩件事:三段式可逐人格覆寫(只覆寫嘴硬那一級,惱羞的收尾是
翻臉不是音量掉下來),以及害羞連稱呼都會變(羞怯稱呼/吞掉名字,刻意不走 except
那條路——except 是生氣方向的「退回更正式」,害羞是反方向的「叫不出來」)。

CLI 段:selftest 項數 602 → 773,補 chapter/relation rift/voice idiolect 三個指令,
並說明 expression-check.mjs 為什麼要獨立一支。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
版號那條的結論是「不動」:master 0.2.0、develop 0.2.1,這批全部還沒進 master,
依規矩一批未合併的改動只佔一個版號。

檔頭的進度段改成完成狀態,並註明 G1/G2/G3 不會因為這批做完而結束——
那三條是持續逼近的方向,不是可以打勾的項目。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jiantw83 added 1 commit 2026-08-03 09:14:17 +00:00
那條是 9d76984 為了擋兩份被刪掉的工作清單而加的(「工作用的清單留在本機」)。
工作清單後來改成進版控的 TARGET.md,本機與版控都沒有 TODO_*.md,
規則留著只會讓人以為這個專案還有一份不進版控的清單。

移除後零影響:ls TODO_*.md 沒有檔案、git ls-files 也沒有這類追蹤檔。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jiantw83 added 1 commit 2026-08-03 09:14:58 +00:00
檔案裡 15 個主項目、32 個子項目全部勾完、零待人工,留著只是一份完成紀錄。

內容沒有消失:全文在 6424b1a 的歷史裡,`git show 6424b1a:TARGET.md` 取得回來,
逐項的實作理由也留在這一批的 commit 訊息裡。

排程(每天台北 00:00 的 /jsc-code:target --run)不會因此報錯:
沒有 TARGET.md 時它視為「無待辦」正常結束。下一批要做什麼決定了再重新建一份。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
admin approved these changes 2026-08-03 09:15:18 +00:00
admin merged commit 0786f9419e into master 2026-08-03 09:15:21 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: plugins/persona#17