From c713359fb42d03dfeec998fe5a70d0812d19f8bc Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 28 Jul 2026 14:21:48 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(role):=20=E8=A7=92=E8=89=B2=E5=88=86?= =?UTF-8?q?=E5=B1=A4=E8=BC=89=E5=85=A5=E8=88=87=E5=80=8B=E4=BA=BA=E8=A8=98?= =?UTF-8?q?=E6=86=B6=E5=90=8C=E6=84=8F=E7=8B=80=E6=85=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit role_load.sh 不再原封不動注入整份角色檔,改採 SOUL/AGENTS/USER/MEMORY 分層:只抽出角色 ID、顯示名稱、本質、氛圍與簽名 emoji 作為人格層,由 hook 產生固定操作邊界,記憶與同意狀態獨立成一區。避免人格檔裡的背景故事與模板 文字污染工程與安全規則。 memory.js 新增 consent/consent-status 子命令,同意狀態寫入 ~/.memory/<角色 ID>/state.json 的 personal_memory_consent(accepted/ declined/unknown);role_capture.sh 於本輪出現明確同意或拒絕時更新狀態, 偵測不到明確表述時不自行推論。accepted 後不再每次重問。 Co-Authored-By: Claude Opus 5 (1M context) --- scripts/role/memory.js | 62 ++++++++++++++++++++- scripts/role/role_capture.sh | 13 ++++- scripts/role/role_load.sh | 102 ++++++++++++++++++++++++++++++----- 3 files changed, 162 insertions(+), 15 deletions(-) diff --git a/scripts/role/memory.js b/scripts/role/memory.js index 8a08f19..ee7f790 100755 --- a/scripts/role/memory.js +++ b/scripts/role/memory.js @@ -4,7 +4,7 @@ // inbox,(2) 產生 SessionStart 要注入的記憶區塊,(3) 睡眠整理時輸出待整理 // 素材並套用整理結果(NREM 鞏固/REM 整合、分類、去重、標籤、總結、 // 優先度、關聯、壓縮歸檔),(4) 依使用頻率與優先度遺忘日常與其他類記憶。 -// 更新時間:2026/07/28 12:40:24 +// 更新時間:2026/07/28 13:50:43 // 相依:Node.js 標準庫。 // 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。 // ============================================================================== @@ -775,6 +775,7 @@ function cmdStats(args) { rows.push(`| 待整理(inbox) | ${listInbox(args.role).length} | - |`); rows.push(""); rows.push(`- 記憶目錄:${memoryRoot(args.role)}`); + rows.push(`- 個人記憶同意狀態:${consentStatusValue(args.role)}`); rows.push(`- 上次睡眠整理:${state.last_sleep || "尚未整理"}`); rows.push(`- 上次睡眠摘要:${state.last_sleep_digest || "尚無"}`); rows.push(`- 上次遺忘:${state.last_forget || "尚未執行"}`); @@ -803,6 +804,61 @@ function cmdNeedSleep(args) { return 0; } +function cmdConsent(args) { + const value = String(args.value || "").trim().toLowerCase(); + const normalized = { + accept: "accepted", + accepted: "accepted", + yes: "accepted", + true: "accepted", + "1": "accepted", + decline: "declined", + declined: "declined", + no: "declined", + false: "declined", + "0": "declined", + unknown: "unknown", + }[value]; + if (!normalized) { + process.stderr.write("同意狀態只接受 accepted/declined/unknown\n"); + return 2; + } + writeState(args.role, { + personal_memory_consent: normalized, + personal_memory_consent_updated: nowStamp(), + }); + process.stdout.write(normalized); + return 0; +} + +function cmdConsentStatus(args) { + process.stdout.write(consentStatusValue(args.role)); + return 0; +} + +function consentStatusValue(role) { + ensureLayout(role); + const state = readState(role); + const status = String(state.personal_memory_consent || "unknown").trim(); + if (["accepted", "declined"].includes(status)) return status; + + const haystacks = []; + for (const [meta, content] of listInbox(role)) haystacks.push(`${meta.summary}\n${content}`); + for (const category of CATEGORIES) { + for (const [meta, content] of listMemories(role, category)) haystacks.push(`${meta.summary}\n${content}`); + } + const consentText = haystacks + .filter((text) => /個人資料|個資|記憶|記住|保存|同意|拒絕/.test(text)) + .join("\n"); + if (/不同意|不願意|不要保存|不要記住|拒絕|不可以保存|不可以記住/.test(consentText)) { + return "declined"; + } + if (/已同意|明確同意|同意.*保存|同意.*記住|可以保存|可以記住|願意.*保存|願意.*記住/.test(consentText)) { + return "accepted"; + } + return "unknown"; +} + function parseArgs(argv) { const command = argv[0]; const opts = { command }; @@ -836,7 +892,7 @@ function requireRole(args) { function main(argv) { const args = parseArgs(argv); if (!args.command || args.command === "-h" || args.command === "--help") { - process.stdout.write(`用法:memory.js <子命令> [參數]\n\n子命令:write、seed、load、collect、apply、forget、stats、mark-sleep、need-sleep\n`); + process.stdout.write(`用法:memory.js <子命令> [參數]\n\n子命令:write、seed、load、collect、apply、forget、stats、mark-sleep、need-sleep、consent、consent-status\n`); return 0; } if (!requireRole(args)) return 2; @@ -874,6 +930,8 @@ function main(argv) { stats: cmdStats, "mark-sleep": cmdMarkSleep, "need-sleep": cmdNeedSleep, + consent: cmdConsent, + "consent-status": cmdConsentStatus, }; if (!commands[args.command]) { process.stderr.write("未知子命令\n"); diff --git a/scripts/role/role_capture.sh b/scripts/role/role_capture.sh index 964bde5..aac1549 100755 --- a/scripts/role/role_capture.sh +++ b/scripts/role/role_capture.sh @@ -4,7 +4,7 @@ # 值得記錄時才呼叫 headless CLI 輕量濃縮成一則 inbox 記憶(粗分類/總結/ # 標籤/優先度/關聯/要點)→ 機密遮蔽 → 寫入 .memory/<角色>/inbox/, # 等待睡眠時段做完整 NREM/REM 整理。睡眠時段雖不載入角色,對話仍照常記錄。 -# 更新時間:2026/07/28 12:40:24 +# 更新時間:2026/07/28 13:50:43 # 相依:bash、node、任一 headless CLI、同目錄的 role_lib.sh/memory.js/transcript.js。 # 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.js redact 遮蔽一次。 # 退出碼:一律 0 —— hook 絕不可阻斷使用者流程。 @@ -62,6 +62,17 @@ fi TURN="$(node "${SCRIPT_DIR}/transcript.js" extract "$TRANSCRIPT_PATH" 2>/dev/null)" [ -n "$TURN" ] || role_quit "本輪無可記錄內容" +USER_TURN="$(printf '%s\n' "$TURN" | grep '^\[user\]' || true)" +if printf '%s' "$USER_TURN" | grep -qiE '個人資料|個資|偏好|記憶|記住|保存|save|remember|memory|personal'; then + if printf '%s' "$USER_TURN" | grep -qiE '不同意|不願意|不要保存|不要記住|拒絕|不可以保存|不可以記住|do not save|don'\''t save|do not remember|don'\''t remember|(^|[^[:alpha:]])no([^[:alpha:]]|$)'; then + node "${SCRIPT_DIR}/memory.js" consent --role "$ROLE" --value declined >/dev/null 2>&1 || true + role_log "INF" "已更新個人記憶同意狀態:declined(角色 ${ROLE})" + elif printf '%s' "$USER_TURN" | grep -qiE '同意|願意|可以保存|可以記住|允許|(^|[^[:alpha:]])yes([^[:alpha:]]|$)|(^|[^[:alpha:]])ok([^[:alpha:]]|$)|(^|[^[:alpha:]])okay([^[:alpha:]]|$)|(^|[^[:alpha:]])sure([^[:alpha:]]|$)'; then + node "${SCRIPT_DIR}/memory.js" consent --role "$ROLE" --value accepted >/dev/null 2>&1 || true + role_log "INF" "已更新個人記憶同意狀態:accepted(角色 ${ROLE})" + fi +fi + PROJECT="$(role_project_name "$HOOK_CWD")" CAPTURE_MIN_CHARS="${ROLE_CAPTURE_MIN_CHARS:-240}" CAPTURE_TIMEOUT="${ROLE_CAPTURE_TIMEOUT:-25}" diff --git a/scripts/role/role_load.sh b/scripts/role/role_load.sh index 2e65d14..f7eec83 100755 --- a/scripts/role/role_load.sh +++ b/scripts/role/role_load.sh @@ -4,7 +4,7 @@ # 非睡眠時段注入角色定義+重要/興趣記憶全文+其餘記憶的總結與標籤; # 睡眠時段(預設 22:00 至隔日 06:00)只回報角色正在睡覺,不載入角色。 # 白天發現昨夜未整理記憶時,於背景補跑一次睡眠整理。 -# 更新時間:2026/07/28 12:21:11 +# 更新時間:2026/07/28 13:50:43 # 相依:bash、node、同目錄的 role_lib.sh 與 memory.js。 # 退出碼:一律 0 —— hook 絕不可阻斷使用者啟動 CLI。 # ============================================================================== @@ -76,12 +76,74 @@ EOF_SLEEP fi # ------------------------------------------------------------------------------ -# 非睡眠時段:組出角色定義 + 記憶 +# 非睡眠時段:組出角色人格 + 操作規則 + 記憶 # ------------------------------------------------------------------------------ -DEFINITION="$(cat "$ROLE_DEF" 2>/dev/null)" -[ -n "$DEFINITION" ] || role_quit "角色定義檔為空:${ROLE_DEF}" "WRN" +ROLE_PROFILE="$(node - "$ROLE_DEF" <<'NODE_PROFILE' 2>/dev/null +const fs = require("fs"); + +const file = process.argv[2]; +const raw = fs.readFileSync(file, "utf8"); + +function parseFrontmatter(text) { + const match = text.match(/^---\n([\s\S]*?)\n---\n?/); + const data = {}; + if (!match) return data; + for (const line of match[1].split(/\r?\n/)) { + const idx = line.indexOf(":"); + if (idx < 0) continue; + data[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); + } + return data; +} + +function section(text, title) { + const re = new RegExp(`^##\\s+${title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[^\\n]*\\n([\\s\\S]*?)(?=^##\\s+|$(?![\\s\\S]))`, "m"); + const match = text.match(re); + return match ? match[1].trim() : ""; +} + +const fm = parseFrontmatter(raw); +const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() || [fm.name, fm.emoji].filter(Boolean).join(" "); +const nature = section(raw, "本質(nature)") || fm.nature || ""; +const vibe = section(raw, "氛圍(vibe)") || fm.vibe || ""; +const emoji = section(raw, "簽名 emoji") || fm.emoji || ""; + +const lines = [ + `- 角色 ID:${fm.id || ""}`, + `- 顯示名稱:${fm.name || title || ""}`, + `- 簽名 emoji:${fm.emoji || ""}`, + "", + "## 本質(nature)", + "", + nature || "(未設定)", + "", + "## 氛圍(vibe)", + "", + vibe || "(未設定)", + "", + "## 簽名 emoji", + "", + emoji || fm.emoji || "(未設定)", +]; + +process.stdout.write(lines.join("\n")); +NODE_PROFILE +)" +[ -n "$ROLE_PROFILE" ] || role_quit "角色定義檔為空或無法解析:${ROLE_DEF}" "WRN" MEMORY="$(node "${SCRIPT_DIR}/memory.js" load --role "$ROLE" 2>/dev/null)" +CONSENT_STATUS="$(node "${SCRIPT_DIR}/memory.js" consent-status --role "$ROLE" 2>/dev/null || printf 'unknown')" +case "$CONSENT_STATUS" in + accepted) + CONSENT_NOTE="已告知並取得使用者同意保存非敏感個人資料與長期偏好;仍禁止保存憑證、token、密碼、API key、連線字串、身分證號、住址等機密或高敏感資料。" + ;; + declined) + CONSENT_NOTE="使用者已拒絕保存個人資料;只能保存非個人化的操作規則與技術偏好,不保存可識別個人的背景。" + ;; + *) + CONSENT_NOTE="尚未確認;第一則自然回覆後,請簡短告知記憶保存範圍並詢問是否同意保存非敏感個人資料。未取得同意前,只能保存非個人化的操作規則與技術偏好。" + ;; +esac # 補跑判斷:cron 未執行(例如 WSL 沒開 cron 服務)時,白天啟動 CLI 補做一次整理 CATCHUP_NOTE="" @@ -94,18 +156,34 @@ fi CONTEXT="$(cat < Date: Tue, 28 Jul 2026 14:21:48 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(hooks):=20hooks.json=20=E7=B5=B1?= =?UTF-8?q?=E4=B8=80=E7=82=BA=E5=90=88=E4=BD=B5=E8=B6=85=E9=9B=86=E4=B8=A6?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E8=B7=A8=20cache=20=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同名 plugin(三個 repo 都叫 jsc)只有一份 hooks.json 會生效,無法預期哪一 份會贏,因此三家必須同步為同一份超集。本次補上 doc 的 worklog Stop hook, 避免 generic 贏得註冊時工作紀錄靜默消失。 role 的兩個指令同步改用新解析器:先驗證 CLAUDE_PLUGIN_ROOT 下腳本存在,再 依「擁有者 marketplace 優先 → 全 cache 後援」搜尋 ~/.claude 與 ~/.codex 快取。舊版在 CLAUDE_PLUGIN_ROOT 為空時只搜 ~/.codex,且未檢查腳本是否存在 於該 plugin root。 Co-Authored-By: Claude Opus 5 (1M context) --- hooks/hooks.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hooks/hooks.json b/hooks/hooks.json index aed29cb..6eab4fe 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -5,7 +5,7 @@ "hooks": [ { "type": "command", - "command": "root=\"${CLAUDE_PLUGIN_ROOT:-}\"; if [ -n \"$root\" ]; then exec \"$root/scripts/role/role_load.sh\"; fi; script=$(find \"$HOME/.codex/plugins/cache/generic/jsc\" -path '*/scripts/role/role_load.sh' -type f 2>/dev/null | sort -V | tail -n 1); if [ -n \"$script\" ]; then exec \"$script\"; fi; exit 0", + "command": "rel='scripts/role/role_load.sh'; own='generic'; root=\"${CLAUDE_PLUGIN_ROOT:-}\"; if [ -n \"$root\" ] && [ -f \"$root/$rel\" ]; then exec \"$root/$rel\"; fi; for base in \"$HOME/.claude/plugins/cache\" \"$HOME/.codex/plugins/cache\"; do for dir in \"$base/$own/jsc\" \"$base\"; do s=$(find \"$dir\" -path \"*/jsc/*/$rel\" -type f 2>/dev/null | sort -V | tail -n 1); if [ -n \"$s\" ]; then exec \"$s\"; fi; done; done; exit 0", "timeout": 20 } ] @@ -16,7 +16,12 @@ "hooks": [ { "type": "command", - "command": "root=\"${CLAUDE_PLUGIN_ROOT:-}\"; if [ -n \"$root\" ]; then exec \"$root/scripts/role/role_capture.sh\"; fi; script=$(find \"$HOME/.codex/plugins/cache/generic/jsc\" -path '*/scripts/role/role_capture.sh' -type f 2>/dev/null | sort -V | tail -n 1); if [ -n \"$script\" ]; then exec \"$script\"; fi; exit 0", + "command": "rel='scripts/worklog/worklog.sh'; own='doc'; root=\"${CLAUDE_PLUGIN_ROOT:-}\"; if [ -n \"$root\" ] && [ -f \"$root/$rel\" ]; then exec \"$root/$rel\"; fi; for base in \"$HOME/.claude/plugins/cache\" \"$HOME/.codex/plugins/cache\"; do for dir in \"$base/$own/jsc\" \"$base\"; do s=$(find \"$dir\" -path \"*/jsc/*/$rel\" -type f 2>/dev/null | sort -V | tail -n 1); if [ -n \"$s\" ]; then exec \"$s\"; fi; done; done; exit 0", + "timeout": 60 + }, + { + "type": "command", + "command": "rel='scripts/role/role_capture.sh'; own='generic'; root=\"${CLAUDE_PLUGIN_ROOT:-}\"; if [ -n \"$root\" ] && [ -f \"$root/$rel\" ]; then exec \"$root/$rel\"; fi; for base in \"$HOME/.claude/plugins/cache\" \"$HOME/.codex/plugins/cache\"; do for dir in \"$base/$own/jsc\" \"$base\"; do s=$(find \"$dir\" -path \"*/jsc/*/$rel\" -type f 2>/dev/null | sort -V | tail -n 1); if [ -n \"$s\" ]; then exec \"$s\"; fi; done; done; exit 0", "timeout": 60 } ] -- 2.53.0 From 6d5c4e3f02b07f6f6dd358483b6ef3e83abd36ec Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 28 Jul 2026 14:21:48 +0800 Subject: [PATCH 3/4] =?UTF-8?q?docs(role):=20=E8=A3=9C=E5=90=8C=E5=90=8D?= =?UTF-8?q?=20plugin=20hooks=20=E8=A8=BB=E5=86=8A=E9=99=90=E5=88=B6?= =?UTF-8?q?=E8=88=87=E5=88=86=E5=B1=A4=E8=BC=89=E5=85=A5=E8=AA=AA=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SKILL.md 記錄實測結論:同名 plugin 的 hooks 只會生效一份(hookCount 為 1),三個 repo 的 hooks/hooks.json 必須是同一份合併超集且不得假設 CLAUDE_PLUGIN_ROOT 指向擁有該腳本的 plugin。另補上分層載入原則、 personal_memory_consent 狀態欄位與 Stop hook 更新同意狀態的規則。 Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 2 +- skills/role/SKILL.md | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 39a4e8d..28aa97e 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ copilot plugin marketplace remove generic | --- | --- | --- | | `role` | 讓 CLI 以固定角色(name/nature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:啟動時依字元預算載入高價值記憶,Stop hook 先本地過濾低價值回合以節省額度,睡眠時段(預設 22:00–06:00)由 NREM 鞏固與 REM 整合兩階段整理、去重、標籤化、建立關聯,並標記 semantic/episodic/procedural/emotional/preference/rule 與 explicit/implicit 後壓縮歸檔;新建角色時可詢問是否網路搜尋背景資料作為初始記憶,角色檔名與記憶目錄使用英文大寫 ID | `/jsc:role --new` 建立或更新角色、`--use <角色 ID>` 切換、`--list` 查角色與 ID、`--sleep` 立即整理、`--status` 診斷、`--install-cron` 安裝排程 | -`role` 的自動路徑由 hook 與 cron 完成,**建立角色後重開工作階段即生效**;非睡眠時段載入角色後,角色會在本工作階段第一則回覆開頭主動簡短問候一次,並載入上次睡眠摘要作為提取線索。感覺記憶不落檔,`inbox/` 作為工作記憶,睡眠整理後才進長期記憶。角色檔、記憶目錄、`.active` 與 `ROLE_NAME` 一律使用角色 ID(例如 `ENGINEER01`),`--list` 可查每個顯示名稱對應的 ID。沒有建立過角色的人完全不受影響(`~/.roles/.active` 不存在時 hook 立即結束)。細節見 `skills/role/SKILL.md`。 +`role` 的自動路徑由 hook 與 cron 完成,**建立角色後重開工作階段即生效**;非睡眠時段載入角色後,角色會在本工作階段第一則回覆開頭主動簡短問候一次。載入方式參考 OpenClaw 的分層概念:從角色檔抽出人格作為 `SOUL`,由 hook 產生固定操作邊界作為 `AGENTS`,再把同意狀態與高價值記憶作為 `USER/MEMORY` 注入,避免整份人格檔污染工程規則。感覺記憶不落檔,`inbox/` 作為工作記憶,睡眠整理後才進長期記憶;個人記憶保存同意狀態寫在 `~/.memory/<角色 ID>/state.json`,同意後不會每次重問。角色檔、記憶目錄、`.active` 與 `ROLE_NAME` 一律使用角色 ID(例如 `ENGINEER01`),`--list` 可查每個顯示名稱對應的 ID。沒有建立過角色的人完全不受影響(`~/.roles/.active` 不存在時 hook 立即結束)。細節見 `skills/role/SKILL.md`。 diff --git a/skills/role/SKILL.md b/skills/role/SKILL.md index b99c785..2b55d47 100644 --- a/skills/role/SKILL.md +++ b/skills/role/SKILL.md @@ -14,7 +14,7 @@ description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI | `hooks/hooks.json` 的 `Stop` hook | harness 自動 | 每輪結束先用本地規則過濾低價值短回合 → 值得保存時才濃縮成一則輕量 inbox 記憶 → 遮蔽 → 寫入 `inbox/` | | cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**,沒有才進入 NREM/REM 兩階段記憶整理 | | 本 skill `/jsc:role` | 使用者/助理手動 | `--new`/`--use`/`--list`/`--sleep`/`--status`/`--install-cron`/`--forget-preview` | -| `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入、第一則回覆問候提示(單一實作,避免漂移) | +| `scripts/role/role_load.sh` | SessionStart hook | 角色與記憶載入;參考 OpenClaw 的 SOUL/AGENTS/USER/MEMORY 分層,把人格、操作邊界、使用者記憶分開注入,並提供第一則回覆問候提示(單一實作,避免漂移) | | `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(固定欄位格式) | | `scripts/role/role_sleep.sh` | cron/補跑/手動 | 睡眠判斷、記憶整理、排程安裝、狀態輸出 | | `scripts/role/memory.js` | 上述共用 | 記憶檔讀寫、分類、去重合併、優先度、心理學記憶型態與關聯 metadata、壓縮歸檔、遺忘、載入組裝 | @@ -33,6 +33,7 @@ description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI - **`hooks/hooks.json` 只有 Claude Code 一定會讀**;Codex 會從 `~/.codex/plugins/cache/generic/jsc` 找腳本。其他助理若提供等效 hook,`transcript.js` 需補對應解析器。 - 不支援 hook 的助理仍可用:cron 排程與手動模式照常運作,只是角色不會自動載入。 +- **同名 plugin 的 hooks 只會生效一份**:`code`/`doc`/`generic` 三個 repo 的 `plugin.json` 名稱都是 `jsc`,Claude Code 以 plugin 名稱為鍵註冊 hooks,同名時只保留一份(實測 `stop_hook_summary` 的 `hookCount` 為 1)。因此**三個 repo 的 `hooks/hooks.json` 必須是同一份合併版超集**(worklog 的 `Stop` + role 的 `SessionStart`/`Stop`),任一 repo 修改 hooks 時三份都要同步;且每個 hook 指令不得假設 `CLAUDE_PLUGIN_ROOT` 指向擁有該腳本的 plugin,必須先試 `$CLAUDE_PLUGIN_ROOT`,找不到再依「擁有者 marketplace 優先 → 全 cache 後援」的順序搜尋 `~/.claude/plugins/cache`/`~/.codex/plugins/cache`。 ### 腳本路徑解析(重要) @@ -66,6 +67,8 @@ ROLE_DIR="/../../scripts/role" # 其他助理 - **不臆測角色設定**:`nature`/`vibe`/`emoji` 一律問使用者,不得代填。 - **記憶只增不刪**:手動模式不得直接刪除分類記憶;淘汰一律走遺忘規則(先壓縮歸檔再移除)。 - **絕不阻斷**:hook 路徑任何失敗都以 exit 0 結束,只在 stderr 留訊息。 +- **角色分層載入**:SessionStart 不把整份角色檔原封不動注入;只抽出角色 ID、顯示名稱、本質、氛圍與簽名 emoji 作為 `SOUL`,再由 hook 產生固定 `AGENTS` 操作邊界與 `USER/MEMORY` 記憶區塊。這是為了避免人格檔裡的背景故事、模板文字或舊共用規則污染工程規則。 +- **個人記憶同意狀態**:使用者第一次同意或拒絕保存非敏感個人資料後,狀態寫入 `~/.memory/<角色 ID>/state.json` 的 `personal_memory_consent`。狀態為 `accepted` 時不必每次重問;`declined` 或 `unknown` 時不得保存可識別個人的背景。 --- @@ -307,6 +310,8 @@ updated: 每則記憶是一個 `.md`,frontmatter 帶 `id`/`category`/`summary`(一句話總結)/`tags`/`priority`(1–5)/`relevance`(explicit/future/repeated/novelty/emotional/temporary 等)/`links`(相關記憶 id)/`memory_type`(semantic/episodic/procedural/emotional/preference/rule)/`declarative`(explicit/implicit)/`retention_stage`(working/long_term)/`sleep_stage`(encoding/seed/nrem/rem/nrem-rem)/`created`/`updated`/`last_replayed`/`hits`(命中次數,去重合併時 +1)。舊記憶沒有新欄位時,讀取時會依分類與路徑補預設值。 +`state.json` 保存角色記憶系統狀態,例如 `last_sleep`、`last_sleep_digest`、`last_forget` 與 `personal_memory_consent`。`personal_memory_consent` 只允許 `accepted`/`declined`/`unknown`,供 SessionStart 判斷是否需要再次告知與詢問個人資料保存同意。 + 心理學分類與系統欄位對應: | 心理學分類 | 系統處理 | @@ -360,6 +365,8 @@ flowchart TD 第二道防線不可移除 —— 模型不一定遵守指令,而記憶會被長期保存並在每次啟動時載入。 +Stop hook 會在本輪對話明確包含個人記憶保存同意或拒絕時,呼叫 `memory.js consent --role <角色 ID> --value accepted|declined` 更新同意狀態。偵測不到明確同意時不得自行推論。 + --- ## 呼叫方式 -- 2.53.0 From bb48a77dd4a8c2728dbbe2306d47ad72165cf261 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 28 Jul 2026 14:21:48 +0800 Subject: [PATCH 4/4] =?UTF-8?q?chore(plugin=20=E7=89=88=E6=9C=AC):=20?= =?UTF-8?q?=E4=B8=89=E5=AE=B6=20manifest=20=E5=8D=87=E7=89=88=200.0.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hooks/hooks.json 與 role 腳本有變更,需升版才會重建版本化快取目錄並被載入。 Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- plugin.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 2f3af6d..d904ac9 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc", - "version": "0.0.6", + "version": "0.0.7", "description": "JSC 跨 AI 助理共用規範 plugin(Claude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc: 前綴呼叫。", "skills": "./skills", "author": { diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 2cfd7e8..e64d6c3 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc", - "version": "0.0.6", + "version": "0.0.7", "description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。", "skills": "./skills" } diff --git a/plugin.json b/plugin.json index fbfd54e..0983d51 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "name": "jsc", - "version": "0.0.6", + "version": "0.0.7", "description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc: 前綴呼叫。", "skills": "./skills/" } -- 2.53.0