diff --git a/scripts/role/memory.py b/scripts/role/memory.py index ea0c442..cc25ae5 100755 --- a/scripts/role/memory.py +++ b/scripts/role/memory.py @@ -4,7 +4,7 @@ # inbox,(2) 產生 SessionStart 要注入的記憶區塊,(3) 睡眠整理時輸出待整理 # 素材並套用整理結果(分類/去重/標籤/總結/壓縮歸檔),(4) 依使用頻率 # 遺忘日常與其他類記憶。 -# 更新時間:2026/07/28 00:00:00 +# 更新時間:2026/07/28 11:50:00 # 相依:Python 3 標準庫。 # 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。 # ============================================================================== @@ -330,6 +330,40 @@ def cmd_write(args): return 0 +# ------------------------------------------------------------------------------ +# 子命令:seed —— 新建角色時寫入已整理的初始記憶 +# ------------------------------------------------------------------------------ + + +def cmd_seed(args): + """把 stdin 寫成一則已整理記憶,用於新建角色時灌入背景資料。""" + content = sys.stdin.read().strip() + if not content and not args.summary: + return 1 + if not content: + content = args.summary + content = content[:CONTENT_LIMIT] + stamp = now_stamp() + category = normalize_category(args.category) + meta = { + "id": new_id(content + args.summary + stamp), + "category": category, + "summary": one_line(args.summary) or one_line(content), + "tags": normalize_tags(args.tags), + "created": stamp, + "updated": stamp, + "hits": 1, + } + if args.source: + meta["sources"] = [args.source] + ensure_layout(args.role) + path = os.path.join(memory_root(args.role), category, f"{meta['id']}.md") + with open(path, "w", encoding="utf-8") as fh: + fh.write(dump_memory(meta, content)) + sys.stdout.write(meta["id"]) + return 0 + + # ------------------------------------------------------------------------------ # 子命令:load —— 產生 SessionStart 要注入的記憶區塊 # ------------------------------------------------------------------------------ @@ -587,6 +621,7 @@ def cmd_forget(args): def cmd_stats(args): """輸出記憶統計(各分類筆數、待整理筆數、上次整理時間)。""" + ensure_layout(args.role) state = read_state(args.role) rows = [f"| 分類 | 筆數 |", "| --- | --- |"] for category in CATEGORIES: @@ -646,6 +681,14 @@ def build_parser(): write.add_argument("--project", default="") write.set_defaults(func=cmd_write) + seed = sub.add_parser("seed", help="自 stdin 寫入已整理的初始記憶") + seed.add_argument("--role", required=True) + seed.add_argument("--category", default="important") + seed.add_argument("--summary", required=True) + seed.add_argument("--tags", default="") + seed.add_argument("--source", default="") + seed.set_defaults(func=cmd_seed) + load = sub.add_parser("load", help="輸出 SessionStart 要注入的記憶區塊") load.add_argument("--role", required=True) load.add_argument("--limit", type=int, default=int(os.environ.get("ROLE_LOAD_LIMIT", "8000"))) diff --git a/scripts/role/role_load.sh b/scripts/role/role_load.sh index d0b1689..ce1a325 100755 --- a/scripts/role/role_load.sh +++ b/scripts/role/role_load.sh @@ -4,7 +4,7 @@ # 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤; # 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。 # 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。 -# 更新時間:2026/07/28 00:00:00 +# 更新時間:2026/07/28 11:28:29 # 相依:bash、python3、同目錄的 role_lib.sh 與 memory.py。 # 退出碼:一律 0 —— hook 絕不可阻斷使用者啟動 CLI。 # ============================================================================== @@ -93,6 +93,12 @@ CONTEXT="$(cat < 角色切換用 `/jsc:role --use <名稱>`(寫 `.active`)即可,一般不需要設 `ROLE_NAME`;`ROLE_NAME` 適合「單一專案固定用某角色」時寫進該環境。 +> 角色切換用 `/jsc:role --use <角色 ID>`(寫 `.active`)即可,一般不需要設 `ROLE_NAME`;`ROLE_NAME` 適合「單一專案固定用某角色」時寫進該環境。角色 ID 是英文大寫語意前綴加數字索引,例如 `ENGINEER01`、`MUSE02`。 --- @@ -94,11 +94,13 @@ ROLE_DIR="/../../scripts/role" # 其他助理 ### `--new`(預設模式) -建立或更新角色。缺少的資訊**一次問齊**,不得代填: +建立或更新角色。缺少的資訊**一次問齊**,不得代填。使用者輸入的角色資訊視為「描述」, +不得直接拿描述或姓名當檔名;必須先產生角色 ID,再用 ID 作為角色檔名、記憶目錄名稱、`.active` 與 `ROLE_NAME` 的值。 | 欄位 | 說明 | 範例 | | --- | --- | --- | -| `name` | 角色名稱,同時是檔名 `~/.roles/.md` 與記憶目錄名。不得含 `/`、`\`、空白與前後點 | `小豹` | +| `name` | 顯示名稱,只寫入角色檔 frontmatter 與標題,不作為檔名或目錄名 | `小豹` | +| `id` | 角色 ID,由助理依角色描述產生:英文大寫、有意義、加兩位數索引;同前綴已存在時遞增 | `ENGINEER01` | | `nature` | 本質:這個角色是什麼、專長與行事準則 | 冷靜可靠的資深工程師,重證據、不打包票 | | `vibe` | 氛圍:語氣、句長、稱呼、幽默感、禁忌 | 簡潔直白、偶爾吐槽,不用客套開場白 | | `emoji` | 簽名 emoji,一到二個 | 🐆 | @@ -106,31 +108,45 @@ ROLE_DIR="/../../scripts/role" # 其他助理 流程: 1. 解析 `${ROLE_DIR}`;不存在則中止(見「腳本路徑解析」)。 -2. 以 `AskUserQuestion` 或提問取得四個欄位(使用者已在指令中給的欄位不得重複問)。 -3. 依「角色檔標準格式」產生新內容,`updated` 用當下時間(Asia/Taipei)。 -4. **若 `~/.roles/.md` 已存在**:讀舊檔,以表格逐欄列出差異後**停下來等使用者確認**: +2. 以 `AskUserQuestion` 或提問取得 `name`/`nature`/`vibe`/`emoji` 四個描述欄位(使用者已在指令中給的欄位不得重複問)。 +3. 詢問使用者是否要到網路搜尋角色資料來建立初始記憶;這是新建角色時的固定問題,不得跳過。若使用者同意,依角色描述搜尋可靠來源,摘要成繁體中文要點,並保留來源 URL;若使用者不同意或無網路,仍可建立角色,只是不建立背景種子記憶。 +4. 產生角色 ID: + - 從 `name`/`nature`/`vibe` 推出 1 個有意義的英文大寫前綴,使用 4 到 16 個英文字母與數字,必須以英文字母開頭,例如 `ENGINEER`、`WRITER`、`MUSE`、`RESEARCHER`。 + - 掃描 `~/.roles/*.md` 的檔名與 frontmatter `id`,找出同前綴既有 ID 的最大兩位數索引;新角色使用下一個索引,從 `01` 起,例如 `ENGINEER01`、`ENGINEER02`。 + - 不得使用空白、底線、連字號、斜線、非 ASCII 或小寫字母。 +5. 依「角色檔標準格式」產生新內容,`id` 寫入 frontmatter,`updated` 用當下時間(Asia/Taipei)。 +6. **若 `~/.roles/.md` 已存在**:讀舊檔,以表格逐欄列出差異後**停下來等使用者確認**: | 欄位 | 舊值 | 新值 | 變更 | | --- | --- | --- | --- | + | id | … | … | 是/否 | + | name | … | … | 是/否 | | nature | … | … | 是/否 | | vibe | … | … | 是/否 | | emoji | … | … | 是/否 | | 共用行為區塊 | 版本 A | 版本 B | 是/否 | 個性欄位若使用者只想改其中一項,其餘一律沿用舊值;**共用行為區塊一律以本 skill 的最新版本覆寫**(該區塊由系統維護)。使用者不確認就不寫入。 -5. 寫入 `~/.roles/.md`(UTF-8 無 BOM)。 -6. 建立記憶目錄:`python3 "${ROLE_DIR}/memory.py" stats --role ""`(會順帶建好 `inbox/`、六個分類與 `archive/`)。 -7. 若尚未有啟用角色,或使用者要求,寫入 `~/.roles/.active`(單行角色名)。 -8. 執行 `ROLE_NAME="" "${ROLE_DIR}/role_sleep.sh" --install-cron` 安裝睡眠排程(已安裝則更新)。 -9. 回報結果並提醒:**重開 CLI 工作階段**角色才會載入;`SessionStart` hook 只在啟動時觸發。 +7. 寫入 `~/.roles/.md`(UTF-8 無 BOM)。 +8. 建立記憶目錄:`python3 "${ROLE_DIR}/memory.py" stats --role ""`(會順帶建好 `inbox/`、六個分類與 `archive/`)。 +9. 若使用者同意網路搜尋且已取得可保存內容,將搜尋摘要寫成已整理記憶,不進 inbox: -### `--use <名稱>` + ```bash + printf '<繁體中文要點>' | python3 "${ROLE_DIR}/memory.py" seed --role "" --category important --summary "<一句話總結>" --tags "角色背景,初始資料" --source "<來源 URL>" + ``` -切換啟用角色:確認 `~/.roles/<名稱>.md` 存在後,把名稱寫入 `~/.roles/.active`(覆蓋單行),回報舊角色與新角色,並提醒重開工作階段。 + 多個來源可各寫一則,或合併同主題後以最主要來源作 `--source`。不可寫入憑證或個資。 +10. 若尚未有啟用角色,或使用者要求,寫入 `~/.roles/.active`(單行角色 ID)。 +11. 執行 `ROLE_NAME="" "${ROLE_DIR}/role_sleep.sh" --install-cron` 安裝睡眠排程(已安裝則更新)。 +12. 回報結果時列出角色顯示名稱、角色 ID、角色檔、記憶目錄、是否建立初始記憶,並提醒:**重開 CLI 工作階段**角色才會載入;`SessionStart` hook 只在啟動時觸發。 + +### `--use <角色 ID>` + +切換啟用角色:確認 `~/.roles/<角色 ID>.md` 存在後,把 ID 寫入 `~/.roles/.active`(覆蓋單行),回報舊角色與新角色,並提醒重開工作階段。使用者若輸入顯示名稱而非 ID,先用 `--list` 的邏輯查出唯一對應 ID;找不到或不唯一時詢問使用者。 ### `--list` -列出 `~/.roles/*.md`,以表格輸出:角色、emoji、nature 摘要、更新時間、是否為 `.active`、記憶總數(可用 `memory.py stats` 取得)。 +列出 `~/.roles/*.md`,以表格輸出:角色 ID、顯示名稱、emoji、nature 摘要、更新時間、是否為 `.active`、記憶目錄、記憶總數(可用 `memory.py stats` 取得)。這個指令必須能查出每個角色對應的 ID。 ### `--sleep` @@ -147,7 +163,7 @@ ROLE_DIR="/../../scripts/role" # 其他助理 只預覽會被遺忘的記憶、不實際刪除: ```bash -python3 "${ROLE_DIR}/memory.py" forget --role "" --dry-run +python3 "${ROLE_DIR}/memory.py" forget --role "<角色 ID>" --dry-run ``` ### `--status`/`--diagnose` @@ -183,11 +199,12 @@ python3 "${ROLE_DIR}/memory.py" forget --role "" --dry-run ## 角色檔標準格式 -`~/.roles/.md`,UTF-8 無 BOM。個性區塊由使用者決定,**共用行為區塊由本 skill 維護、逐字寫入每個角色檔**: +`~/.roles/<角色 ID>.md`,UTF-8 無 BOM。個性區塊由使用者決定,**共用行為區塊由本 skill 維護、逐字寫入每個角色檔**: ````markdown --- -name: <角色名> +id: <角色 ID> +name: <角色顯示名稱> nature: <本質,一句話> vibe: <氛圍,一句話> emoji: <簽名 emoji> @@ -195,7 +212,7 @@ created: updated: --- -# <角色名> +# <角色顯示名稱> ## 本質(nature) @@ -226,7 +243,7 @@ updated: ### 記憶 -- 記憶存放於 `~/.memory/<角色名>/`,來源是與使用者的對話:每輪結束由 hook 自動記錄到 `inbox/`,睡眠時段整理歸檔。 +- 記憶存放於 `~/.memory/<角色 ID>/`,來源是與使用者的對話與新建角色時使用者同意建立的初始背景資料:每輪結束由 hook 自動記錄到 `inbox/`,睡眠時段整理歸檔。 - 整理規則:分類成**重要/興趣/新知/技能/日常/其他**六類 → 去除重複(重複者併入既有記憶)→ 設定標籤與一句話總結 → 壓縮內容後歸檔;原始記錄壓縮保存在 `archive/raw/`。 - **日常與其他**兩類會依使用頻率適當遺忘:久未再次出現且命中次數低者,壓縮到 `archive/forgotten/` 後移出常用記憶。 - 載入順序:**重要與興趣載入全文**;其餘只載入總結與標籤,依**技能 → 新知 → 日常 → 其他**排序。需要細節時自行讀取對應分類的記憶檔。 @@ -235,14 +252,14 @@ updated: ```` -`~/.roles/.active` 只放一行角色名,代表目前啟用的角色。 +`~/.roles/.active` 只放一行角色 ID,代表目前啟用的角色。 --- ## 記憶模型 ``` -~/.memory/<角色名>/ +~/.memory/<角色 ID>/ ├── inbox/ 每輪對話產生、尚未整理的記憶 ├── important/ 重要:長期偏好、規範、決策、身分背景 ├── interest/ 興趣:反覆關注、主動深入的主題 @@ -305,6 +322,6 @@ flowchart TD | 助理 | 呼叫 | | --- | --- | -| Claude Code / Antigravity | `/jsc:role --new`、`/jsc:role --use 小豹`、`/jsc:role --list`、`/jsc:role --sleep`、`/jsc:role --status` | +| Claude Code / Antigravity | `/jsc:role --new`、`/jsc:role --use ENGINEER01`、`/jsc:role --list`、`/jsc:role --sleep`、`/jsc:role --status` | | Codex | `$role --status`,或用 `/skills` 選單 | | OpenCode / GitHub Copilot | 需完整 plugin 目錄保留 `scripts/`;OpenCode 以複製 `skills/` 安裝時不可用 |