Compare commits

..
3 Commits
8 changed files with 231 additions and 44 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc", "name": "jsc",
"version": "0.0.8", "version": "0.0.9",
"description": "JSC 跨 AI 助理共用規範 pluginClaude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc: 前綴呼叫。", "description": "JSC 跨 AI 助理共用規範 pluginClaude Code / Codex / Antigravity / OpenCode)。所有 skills 以 SKILL.md 為共通標準,於 Claude Code 以 /jsc: 前綴呼叫。",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc", "name": "jsc",
"version": "0.0.8", "version": "0.0.9",
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。", "description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準。",
"skills": "./skills" "skills": "./skills"
} }
+2 -2
View File
@@ -214,9 +214,9 @@ copilot plugin marketplace remove generic
| Skill | 用途 | 使用方法 | | Skill | 用途 | 使用方法 |
| --- | --- | --- | | --- | --- | --- |
| `role` | 讓 CLI 以固定角色(namenaturevibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:啟動時依字元預算載入高價值記憶,Stop hook 先本地過濾低價值回合以節省額度,睡眠時段(預設 22:00–06:00)由 NREM 鞏固與 REM 整合兩階段整理、去重、標籤化、建立關聯、壓縮歸檔並依優先度適當遺忘;新建角色時可詢問是否網路搜尋背景資料作為初始記憶,角色檔名與記憶目錄使用英文大寫 ID | `/jsc:role --new` 建立或更新角色、`--use <角色 ID>` 切換、`--list` 查角色與 ID、`--sleep` 立即整理、`--status` 診斷、`--install-cron` 安裝排程 | | `role` | 讓 CLI 以固定角色(namenaturevibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:啟動時依字元預算載入高價值記憶,Stop hook 先本地過濾低價值回合以節省額度,睡眠時段(預設 22:00–06:00)由 NREM 鞏固與 REM 整合兩階段整理、去重、標籤化、建立關聯,並標記 semanticepisodicproceduralemotionalpreferencerule 與 explicitimplicit 後壓縮歸檔;新建角色時可詢問是否網路搜尋背景資料作為初始記憶,角色檔名與記憶目錄使用英文大寫 ID | `/jsc:role --new` 建立或更新角色、`--use <角色 ID>` 切換、`--list` 查角色與 ID、`--sleep` 立即整理、`--status` 診斷、`--install-cron` 安裝排程 |
`role` 的自動路徑由 hook 與 cron 完成,**建立角色後重開工作階段即生效**;非睡眠時段載入角色後,角色會在本工作階段第一則回覆開頭主動簡短問候一次,並載入上次睡眠摘要作為提取線索。角色檔、記憶目錄、`.active``ROLE_NAME` 一律使用角色 ID(例如 `ENGINEER01`),`--list` 可查每個顯示名稱對應的 ID。沒有建立過角色的人完全不受影響(`~/.roles/.active` 不存在時 hook 立即結束)。細節見 `skills/role/SKILL.md` `role` 的自動路徑由 hook 與 cron 完成,**建立角色後重開工作階段即生效**;非睡眠時段載入角色後,角色會在本工作階段第一則回覆開頭主動簡短問候一次,並載入上次睡眠摘要作為提取線索。感覺記憶不落檔,`inbox/` 作為工作記憶,睡眠整理後才進長期記憶。角色檔、記憶目錄、`.active``ROLE_NAME` 一律使用角色 ID(例如 `ENGINEER01`),`--list` 可查每個顯示名稱對應的 ID。沒有建立過角色的人完全不受影響(`~/.roles/.active` 不存在時 hook 立即結束)。細節見 `skills/role/SKILL.md`
<!-- JSC-SKILLS:END --> <!-- JSC-SKILLS:END -->
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jsc", "name": "jsc",
"version": "0.0.8", "version": "0.0.9",
"description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc: 前綴呼叫。", "description": "JSC 跨 AI 助理共用規範 plugin。所有 skills 以 SKILL.md 為共通標準;於 Antigravity 以 /jsc: 前綴呼叫。",
"skills": "./skills/" "skills": "./skills/"
} }
+167 -9
View File
@@ -4,7 +4,7 @@
// inbox(2) 產生 SessionStart 要注入的記憶區塊,(3) 睡眠整理時輸出待整理 // inbox(2) 產生 SessionStart 要注入的記憶區塊,(3) 睡眠整理時輸出待整理
// 素材並套用整理結果(NREM 鞏固/REM 整合、分類、去重、標籤、總結、 // 素材並套用整理結果(NREM 鞏固/REM 整合、分類、去重、標籤、總結、
// 優先度、關聯、壓縮歸檔),(4) 依使用頻率與優先度遺忘日常與其他類記憶。 // 優先度、關聯、壓縮歸檔),(4) 依使用頻率與優先度遺忘日常與其他類記憶。
// 更新時間:2026/07/28 12:21:11 // 更新時間:2026/07/28 12:40:24
// 相依:Node.js 標準庫。 // 相依:Node.js 標準庫。
// 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。 // 退出碼:0 成功;1 無內容可處理;2 參數錯誤。呼叫端(hook)一律不得因此中斷。
// ============================================================================== // ==============================================================================
@@ -29,6 +29,33 @@ const FULL_CATEGORIES = ["important", "interest"];
const DIGEST_CATEGORIES = ["skill", "news", "daily", "other"]; const DIGEST_CATEGORIES = ["skill", "news", "daily", "other"];
const FORGET_RULES = { daily: [14, 1], other: [7, 1] }; const FORGET_RULES = { daily: [14, 1], other: [7, 1] };
const DEFAULT_PRIORITY = { important: 5, interest: 4, skill: 4, news: 3, daily: 2, other: 1 }; const DEFAULT_PRIORITY = { important: 5, interest: 4, skill: 4, news: 3, daily: 2, other: 1 };
const MEMORY_TYPES = ["semantic", "episodic", "procedural", "emotional", "preference", "rule"];
const MEMORY_TYPE_LABELS = {
semantic: "語意",
episodic: "情節",
procedural: "程序",
emotional: "情緒",
preference: "偏好",
rule: "規範",
};
const DEFAULT_MEMORY_TYPE = {
important: "preference",
interest: "emotional",
news: "semantic",
skill: "procedural",
daily: "episodic",
other: "semantic",
};
const MEMORY_TYPE_WEIGHT = {
rule: 50,
preference: 45,
procedural: 35,
semantic: 30,
emotional: 25,
episodic: 10,
};
const DECLARATIVE_VALUES = ["explicit", "implicit"];
const RETENTION_STAGES = ["working", "long_term"];
const SLEEP_BATCH = 60; const SLEEP_BATCH = 60;
const COLLECT_LIMIT = 12000; const COLLECT_LIMIT = 12000;
@@ -141,6 +168,75 @@ function normalizePriority(value, category = "other") {
return Math.max(1, Math.min(5, priority)); return Math.max(1, Math.min(5, priority));
} }
function normalizeMemoryType(value, category = "other") {
const raw = String(value || "").trim().toLowerCase().replace(/-/g, "_");
const alias = {
explicit: "semantic",
declarative: "semantic",
implicit: "procedural",
non_declarative: "procedural",
nondeclarative: "procedural",
semantic_memory: "semantic",
episodic_memory: "episodic",
procedural_memory: "procedural",
emotion: "emotional",
affective: "emotional",
pref: "preference",
preference_memory: "preference",
policy: "rule",
guideline: "rule",
規範: "rule",
偏好: "preference",
程序: "procedural",
技能: "procedural",
語意: "semantic",
知識: "semantic",
情節: "episodic",
事件: "episodic",
情緒: "emotional",
};
const normalized = alias[raw] || alias[String(value || "").trim()] || raw;
if (MEMORY_TYPES.includes(normalized)) return normalized;
return DEFAULT_MEMORY_TYPE[normalizeCategory(category)] || "semantic";
}
function normalizeDeclarative(value, memoryType = "semantic") {
const raw = String(value || "").trim().toLowerCase().replace(/-/g, "_");
const alias = {
declarative: "explicit",
explicit_memory: "explicit",
non_declarative: "implicit",
nondeclarative: "implicit",
implicit_memory: "implicit",
外顯: "explicit",
陳述性: "explicit",
內隱: "implicit",
非陳述性: "implicit",
};
const normalized = alias[raw] || alias[String(value || "").trim()] || raw;
if (DECLARATIVE_VALUES.includes(normalized)) return normalized;
return ["procedural", "emotional"].includes(memoryType) ? "implicit" : "explicit";
}
function normalizeRetentionStage(value, fallback = "long_term") {
const raw = String(value || "").trim().toLowerCase().replace(/-/g, "_");
const alias = {
short_term: "working",
working_memory: "working",
inbox: "working",
encoding: "working",
long: "long_term",
longterm: "long_term",
long_term_memory: "long_term",
短期: "working",
工作記憶: "working",
長期: "long_term",
};
const normalized = alias[raw] || alias[String(value || "").trim()] || raw;
if (RETENTION_STAGES.includes(normalized)) return normalized;
return fallback;
}
function oneLine(value, limit = 120) { function oneLine(value, limit = 120) {
return String(value || "").replace(/\s+/g, " ").trim().slice(0, limit); return String(value || "").replace(/\s+/g, " ").trim().slice(0, limit);
} }
@@ -155,6 +251,9 @@ function dumpMemory(meta, content) {
"priority", "priority",
"relevance", "relevance",
"links", "links",
"memory_type",
"declarative",
"retention_stage",
"sleep_stage", "sleep_stage",
"created", "created",
"updated", "updated",
@@ -204,6 +303,10 @@ function loadMemory(filePath) {
meta.relevance ||= []; meta.relevance ||= [];
meta.links ||= []; meta.links ||= [];
meta.priority = normalizePriority(meta.priority, meta.category || "other"); meta.priority = normalizePriority(meta.priority, meta.category || "other");
meta.memory_type = normalizeMemoryType(meta.memory_type, meta.category || "other");
meta.declarative = normalizeDeclarative(meta.declarative, meta.memory_type);
const retentionFallback = filePath.includes(`${path.sep}inbox${path.sep}`) || meta.sleep_stage === "encoding" ? "working" : "long_term";
meta.retention_stage = normalizeRetentionStage(meta.retention_stage, retentionFallback);
return [meta, body.trim()]; return [meta, body.trim()];
} }
@@ -228,8 +331,18 @@ function listMemories(role, category) {
items.sort((a, b) => { items.sort((a, b) => {
const am = a[0]; const am = a[0];
const bm = b[0]; const bm = b[0];
const av = [normalizePriority(am.priority, category), am.links?.length ? 1 : 0, am.updated || ""]; const av = [
const bv = [normalizePriority(bm.priority, category), bm.links?.length ? 1 : 0, bm.updated || ""]; normalizePriority(am.priority, category),
MEMORY_TYPE_WEIGHT[am.memory_type] || 0,
am.links?.length ? 1 : 0,
am.updated || "",
];
const bv = [
normalizePriority(bm.priority, category),
MEMORY_TYPE_WEIGHT[bm.memory_type] || 0,
bm.links?.length ? 1 : 0,
bm.updated || "",
];
for (let i = 0; i < av.length; i += 1) { for (let i = 0; i < av.length; i += 1) {
if (av[i] < bv[i]) return 1; if (av[i] < bv[i]) return 1;
if (av[i] > bv[i]) return -1; if (av[i] > bv[i]) return -1;
@@ -267,7 +380,8 @@ function findMemory(role, memoryId) {
function memoryHint(meta) { function memoryHint(meta) {
const relevance = (meta.relevance || []).join("、") || "-"; const relevance = (meta.relevance || []).join("、") || "-";
const links = (meta.links || []).join("、") || "-"; const links = (meta.links || []).join("、") || "-";
return `優先度:${normalizePriority(meta.priority, meta.category)};關聯:${relevance};連結:${links}`; const type = MEMORY_TYPE_LABELS[meta.memory_type] || meta.memory_type || "語意";
return `優先度:${normalizePriority(meta.priority, meta.category)};型態:${type}/${meta.declarative || "explicit"};關聯:${relevance};連結:${links}`;
} }
function archiveFile(filePath, destinationDir) { function archiveFile(filePath, destinationDir) {
@@ -283,7 +397,7 @@ function archiveFile(filePath, destinationDir) {
} }
} }
const FIELD_PATTERN = /^\s*(CATEGORY|SUMMARY|TAGS|PRIORITY|RELEVANCE|CONTENT)\s*[:]\s*(.*)$/i; const FIELD_PATTERN = /^\s*(CATEGORY|SUMMARY|TAGS|PRIORITY|RELEVANCE|MEMORY_TYPE|DECLARATIVE|RETENTION_STAGE|CONTENT)\s*[:]\s*(.*)$/i;
function parseCapture(text) { function parseCapture(text) {
let category = ""; let category = "";
@@ -291,6 +405,9 @@ function parseCapture(text) {
let priority = null; let priority = null;
let tags = []; let tags = [];
let relevance = []; let relevance = [];
let memoryType = "";
let declarative = "";
let retentionStage = "";
const contentLines = []; const contentLines = [];
let inContent = false; let inContent = false;
for (const line of String(text || "").split(/\r?\n/)) { for (const line of String(text || "").split(/\r?\n/)) {
@@ -303,6 +420,9 @@ function parseCapture(text) {
else if (field === "TAGS") tags = normalizeTags(value); else if (field === "TAGS") tags = normalizeTags(value);
else if (field === "PRIORITY") priority = value; else if (field === "PRIORITY") priority = value;
else if (field === "RELEVANCE") relevance = normalizeList(value); else if (field === "RELEVANCE") relevance = normalizeList(value);
else if (field === "MEMORY_TYPE") memoryType = value;
else if (field === "DECLARATIVE") declarative = value;
else if (field === "RETENTION_STAGE") retentionStage = value;
else if (field === "CONTENT") { else if (field === "CONTENT") {
inContent = true; inContent = true;
if (value.trim()) contentLines.push(value); if (value.trim()) contentLines.push(value);
@@ -311,7 +431,17 @@ function parseCapture(text) {
} }
if (inContent) contentLines.push(line); if (inContent) contentLines.push(line);
} }
return { category, summary, tags, priority, relevance, content: contentLines.join("\n").trim() }; return {
category,
summary,
tags,
priority,
relevance,
memoryType,
declarative,
retentionStage,
content: contentLines.join("\n").trim(),
};
} }
function cmdWrite(args) { function cmdWrite(args) {
@@ -319,6 +449,7 @@ function cmdWrite(args) {
if (!parsed.content && !parsed.summary) return 1; if (!parsed.content && !parsed.summary) return 1;
const content = (parsed.content || parsed.summary).slice(0, CONTENT_LIMIT); const content = (parsed.content || parsed.summary).slice(0, CONTENT_LIMIT);
const stamp = nowStamp(); const stamp = nowStamp();
const memoryType = normalizeMemoryType(parsed.memoryType, parsed.category);
const meta = { const meta = {
id: newId(content + stamp), id: newId(content + stamp),
category: normalizeCategory(parsed.category), category: normalizeCategory(parsed.category),
@@ -327,6 +458,9 @@ function cmdWrite(args) {
priority: normalizePriority(parsed.priority, parsed.category), priority: normalizePriority(parsed.priority, parsed.category),
relevance: parsed.relevance.length ? parsed.relevance : ["inbox"], relevance: parsed.relevance.length ? parsed.relevance : ["inbox"],
links: [], links: [],
memory_type: memoryType,
declarative: normalizeDeclarative(parsed.declarative, memoryType),
retention_stage: "working",
sleep_stage: "encoding", sleep_stage: "encoding",
created: stamp, created: stamp,
updated: stamp, updated: stamp,
@@ -345,6 +479,7 @@ function cmdSeed(args) {
const content = (raw || args.summary).slice(0, CONTENT_LIMIT); const content = (raw || args.summary).slice(0, CONTENT_LIMIT);
const stamp = nowStamp(); const stamp = nowStamp();
const category = normalizeCategory(args.category); const category = normalizeCategory(args.category);
const memoryType = normalizeMemoryType(args.memoryType, category);
const meta = { const meta = {
id: newId(content + args.summary + stamp), id: newId(content + args.summary + stamp),
category, category,
@@ -353,6 +488,9 @@ function cmdSeed(args) {
priority: normalizePriority(args.priority, category), priority: normalizePriority(args.priority, category),
relevance: normalizeList(args.relevance).length ? normalizeList(args.relevance) : ["explicit", "background"], relevance: normalizeList(args.relevance).length ? normalizeList(args.relevance) : ["explicit", "background"],
links: [], links: [],
memory_type: memoryType,
declarative: normalizeDeclarative(args.declarative, memoryType),
retention_stage: "long_term",
sleep_stage: "seed", sleep_stage: "seed",
created: stamp, created: stamp,
updated: stamp, updated: stamp,
@@ -387,7 +525,8 @@ function cmdLoad(args) {
digestLines.push(`### ${CATEGORY_LABELS[category]}記憶(總結)`); digestLines.push(`### ${CATEGORY_LABELS[category]}記憶(總結)`);
for (const [meta] of items) { for (const [meta] of items) {
const priority = normalizePriority(meta.priority, category); const priority = normalizePriority(meta.priority, category);
if (priority < args.digestMinPriority && !(meta.links || []).length) continue; const durableType = ["rule", "preference", "procedural"].includes(meta.memory_type);
if (priority < args.digestMinPriority && !(meta.links || []).length && !durableType) continue;
const tags = (meta.tags || []).join("、") || "無標籤"; const tags = (meta.tags || []).join("、") || "無標籤";
digestLines.push(`- ${meta.summary || "(無總結)"}(標籤:${tags}${memoryHint(meta)}`); digestLines.push(`- ${meta.summary || "(無總結)"}(標籤:${tags}${memoryHint(meta)}`);
} }
@@ -424,6 +563,7 @@ function cmdCollect(args) {
lines.push(`初判標籤: ${(meta.tags || []).join("、") || "無"}`); lines.push(`初判標籤: ${(meta.tags || []).join("、") || "無"}`);
lines.push(`初判優先度: ${normalizePriority(meta.priority, meta.category || "other")}`); lines.push(`初判優先度: ${normalizePriority(meta.priority, meta.category || "other")}`);
lines.push(`初判關聯: ${(meta.relevance || []).join("、") || "-"}`); lines.push(`初判關聯: ${(meta.relevance || []).join("、") || "-"}`);
lines.push(`初判記憶型態: ${meta.memory_type} / ${meta.declarative} / ${meta.retention_stage}`);
lines.push("內容:", content, ""); lines.push("內容:", content, "");
} }
@@ -432,7 +572,7 @@ function cmdCollect(args) {
for (const category of CATEGORIES) { for (const category of CATEGORIES) {
for (const [meta] of listMemories(args.role, category)) { for (const [meta] of listMemories(args.role, category)) {
const tags = (meta.tags || []).join("、") || "無"; const tags = (meta.tags || []).join("、") || "無";
rows.push(`- id: ${meta.id} | 分類: ${CATEGORY_LABELS[category]} | 優先度: ${normalizePriority(meta.priority, category)} | 標籤: ${tags} | 關聯: ${(meta.relevance || []).join("、") || "-"} | links: ${(meta.links || []).join("、") || "-"} | 總結: ${meta.summary || ""}`); rows.push(`- id: ${meta.id} | 分類: ${CATEGORY_LABELS[category]} | 優先度: ${normalizePriority(meta.priority, category)} | 型態: ${meta.memory_type}/${meta.declarative}/${meta.retention_stage} | 標籤: ${tags} | 關聯: ${(meta.relevance || []).join("、") || "-"} | links: ${(meta.links || []).join("、") || "-"} | 總結: ${meta.summary || ""}`);
} }
} }
if (rows.length) { if (rows.length) {
@@ -499,6 +639,9 @@ function cmdApply(args) {
const priority = normalizePriority(entry.priority, entryCategory); const priority = normalizePriority(entry.priority, entryCategory);
const relevance = normalizeList(entry.relevance); const relevance = normalizeList(entry.relevance);
const links = normalizeList(entry.links); const links = normalizeList(entry.links);
const memoryType = normalizeMemoryType(entry.memory_type || entry.memoryType, entryCategory);
const declarative = normalizeDeclarative(entry.declarative, memoryType);
const retentionStage = normalizeRetentionStage(entry.retention_stage || entry.retentionStage, "long_term");
const sleepStage = oneLine(entry.sleep_stage || entry.sleepStage || "nrem-rem", 40); const sleepStage = oneLine(entry.sleep_stage || entry.sleepStage || "nrem-rem", 40);
if (!content && !summary) continue; if (!content && !summary) continue;
@@ -509,6 +652,7 @@ function cmdApply(args) {
action = "new"; action = "new";
} else { } else {
const category = normalizeCategory(entry.category || meta.category); const category = normalizeCategory(entry.category || meta.category);
const mergedMemoryType = normalizeMemoryType(entry.memory_type || entry.memoryType || meta.memory_type, category);
const newMeta = { const newMeta = {
id: meta.id, id: meta.id,
category, category,
@@ -517,6 +661,9 @@ function cmdApply(args) {
priority: Math.max(normalizePriority(meta.priority, category), priority), priority: Math.max(normalizePriority(meta.priority, category), priority),
relevance: normalizeList([...(meta.relevance || []), ...relevance]), relevance: normalizeList([...(meta.relevance || []), ...relevance]),
links: normalizeList([...(meta.links || []), ...links]), links: normalizeList([...(meta.links || []), ...links]),
memory_type: mergedMemoryType,
declarative: normalizeDeclarative(entry.declarative || meta.declarative, mergedMemoryType),
retention_stage: normalizeRetentionStage(entry.retention_stage || entry.retentionStage || meta.retention_stage, "long_term"),
sleep_stage: sleepStage, sleep_stage: sleepStage,
created: meta.created || stamp, created: meta.created || stamp,
updated: stamp, updated: stamp,
@@ -546,6 +693,9 @@ function cmdApply(args) {
priority, priority,
relevance, relevance,
links, links,
memory_type: memoryType,
declarative,
retention_stage: retentionStage,
sleep_stage: sleepStage, sleep_stage: sleepStage,
created: stamp, created: stamp,
updated: stamp, updated: stamp,
@@ -581,10 +731,13 @@ function cmdForget(args) {
for (const [meta] of listMemories(args.role, category)) { for (const [meta] of listMemories(args.role, category)) {
const updated = parseStamp(meta.updated) || parseStamp(meta.created); const updated = parseStamp(meta.updated) || parseStamp(meta.created);
if (!updated) continue; if (!updated) continue;
if ((now - updated) / 86400000 < days) continue; const ageDays = (now - updated) / 86400000;
const effectiveDays = meta.memory_type === "episodic" ? Math.max(3, Math.ceil(days / 2)) : days;
if (ageDays < effectiveDays) continue;
if (Number.parseInt(meta.hits || 0, 10) > maxHits) continue; if (Number.parseInt(meta.hits || 0, 10) > maxHits) continue;
if (normalizePriority(meta.priority, category) > 2) continue; if (normalizePriority(meta.priority, category) > 2) continue;
if ((meta.links || []).length) continue; if ((meta.links || []).length) continue;
if (["rule", "preference", "procedural"].includes(meta.memory_type)) continue;
if (args.dryRun) { if (args.dryRun) {
forgotten.push(`${CATEGORY_LABELS[category]}${meta.summary || ""}`); forgotten.push(`${CATEGORY_LABELS[category]}${meta.summary || ""}`);
continue; continue;
@@ -608,8 +761,10 @@ function cmdStats(args) {
ensureLayout(args.role); ensureLayout(args.role);
const state = readState(args.role); const state = readState(args.role);
const rows = ["| 分類 | 筆數 | 平均優先度 |", "| --- | --- | --- |"]; const rows = ["| 分類 | 筆數 | 平均優先度 |", "| --- | --- | --- |"];
const typeCounts = Object.fromEntries(MEMORY_TYPES.map((type) => [type, 0]));
for (const category of CATEGORIES) { for (const category of CATEGORIES) {
const items = listMemories(args.role, category); const items = listMemories(args.role, category);
for (const [meta] of items) typeCounts[meta.memory_type] = (typeCounts[meta.memory_type] || 0) + 1;
let avg = "-"; let avg = "-";
if (items.length) { if (items.length) {
const value = items.reduce((sum, [meta]) => sum + normalizePriority(meta.priority, category), 0) / items.length; const value = items.reduce((sum, [meta]) => sum + normalizePriority(meta.priority, category), 0) / items.length;
@@ -623,6 +778,7 @@ function cmdStats(args) {
rows.push(`- 上次睡眠整理:${state.last_sleep || "尚未整理"}`); rows.push(`- 上次睡眠整理:${state.last_sleep || "尚未整理"}`);
rows.push(`- 上次睡眠摘要:${state.last_sleep_digest || "尚無"}`); rows.push(`- 上次睡眠摘要:${state.last_sleep_digest || "尚無"}`);
rows.push(`- 上次遺忘:${state.last_forget || "尚未執行"}`); rows.push(`- 上次遺忘:${state.last_forget || "尚未執行"}`);
rows.push(`- 記憶型態:${MEMORY_TYPES.map((type) => `${MEMORY_TYPE_LABELS[type]} ${typeCounts[type] || 0}`).join("、")}`);
process.stdout.write(rows.join("\n")); process.stdout.write(rows.join("\n"));
return 0; return 0;
} }
@@ -703,6 +859,8 @@ function main(argv) {
args.source ||= ""; args.source ||= "";
args.priority ||= "4"; args.priority ||= "4";
args.relevance ||= "explicit,background"; args.relevance ||= "explicit,background";
args.memoryType ||= "";
args.declarative ||= "";
args.summary ||= ""; args.summary ||= "";
args.project ||= ""; args.project ||= "";
+15 -6
View File
@@ -4,7 +4,7 @@
# 值得記錄時才呼叫 headless CLI 輕量濃縮成一則 inbox 記憶(粗分類/總結/ # 值得記錄時才呼叫 headless CLI 輕量濃縮成一則 inbox 記憶(粗分類/總結/
# 標籤/優先度/關聯/要點)→ 機密遮蔽 → 寫入 .memory/<角色>/inbox/ # 標籤/優先度/關聯/要點)→ 機密遮蔽 → 寫入 .memory/<角色>/inbox/
# 等待睡眠時段做完整 NREM/REM 整理。睡眠時段雖不載入角色,對話仍照常記錄。 # 等待睡眠時段做完整 NREM/REM 整理。睡眠時段雖不載入角色,對話仍照常記錄。
# 更新時間:2026/07/28 12:21:11 # 更新時間:2026/07/28 12:40:24
# 相依:bash、node、任一 headless CLI、同目錄的 role_lib.shmemory.jstranscript.js。 # 相依:bash、node、任一 headless CLI、同目錄的 role_lib.shmemory.jstranscript.js。
# 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.js redact 遮蔽一次。 # 機密:濃縮提示詞明令不得輸出憑證與個資,寫檔前再以 transcript.js redact 遮蔽一次。
# 退出碼:一律 0 —— hook 絕不可阻斷使用者流程。 # 退出碼:一律 0 —— hook 絕不可阻斷使用者流程。
@@ -89,6 +89,7 @@ SUMMARY: <一句話總結,40 字內>
TAGS: <2 至 4 個標籤,以逗號分隔> TAGS: <2 至 4 個標籤,以逗號分隔>
PRIORITY: <1 到 5> PRIORITY: <1 到 5>
RELEVANCE: <1 至 4 個,以逗號分隔;explicit/future/repeated/novelty/emotional/temporary/inbox/project> RELEVANCE: <1 至 4 個,以逗號分隔;explicit/future/repeated/novelty/emotional/temporary/inbox/project>
MEMORY_TYPE: <semanticepisodicproceduralemotionalpreferencerule 六選一>
CONTENT: <3 至 6 行要點,每行以「- 」開頭> CONTENT: <3 至 6 行要點,每行以「- 」開頭>
2. 分類判準: 2. 分類判準:
- important(重要):使用者的長期偏好、規範、決策、身分背景、明確要求記住的事。 - important(重要):使用者的長期偏好、規範、決策、身分背景、明確要求記住的事。
@@ -97,11 +98,19 @@ CONTENT: <3 至 6 行要點,每行以「- 」開頭>
- skill(技能):可重複套用的做法、指令、流程、除錯手法。 - skill(技能):可重複套用的做法、指令、流程、除錯手法。
- daily(日常):一次性的例行工作與雜項處理。 - daily(日常):一次性的例行工作與雜項處理。
- other(其他):不屬於上述任何一類。 - other(其他):不屬於上述任何一類。
3. 優先度判準:5=使用者明確要求記住、長期規範、穩定偏好;4=可重複套用的流程/技能/決策;3=專案相關且未來可能有用;2=短期進度;1=低價值暫存。 3. 記憶型態判準:
4. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文 - rule:使用者明確規範、固定工作原則、日後應持續遵守的規則
5. 使用繁體中文(台灣用語),保留關鍵事實:檔案/專案/指令/數量/分支/議題編號 - preference:使用者偏好、語氣喜好、穩定選擇傾向
6. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號 - procedural:可重複套用的流程、技能、操作步驟或除錯手法
7. 若這段對話沒有任何值得記住的內容(純寒暄、純確認、無結論、只有簡短狀態回報),只輸出一行:SKIP - semantic:事實、觀念、工具知識、版本與外部資訊。
- episodic:一次性事件、特定時間/專案脈絡下的經歷或進度。
- emotional:語氣、情緒反應、正負向連結或制約式偏好。
4. 優先度判準:5=使用者明確要求記住、長期規範、穩定偏好;4=可重複套用的流程/技能/決策;3=專案相關且未來可能有用;2=短期進度;1=低價值暫存。
5. 這一步只做工作記憶編碼,系統會自動標為 retention_stage=working;感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)不要保存。
6. 記憶主體是「使用者與這段互動」,不是流水帳:寫值得下次記起來的事,不要抄程式碼、不要貼指令全文。
7. 使用繁體中文(台灣用語),保留關鍵事實:檔案/專案/指令/數量/分支/議題編號。
8. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
9. 若這段對話沒有任何值得記住的內容(純寒暄、純確認、無結論、只有簡短狀態回報),只輸出一行:SKIP
對話片段: 對話片段:
${TURN} ${TURN}
+20 -10
View File
@@ -6,7 +6,7 @@
# 連結/抽象化/提取線索)→ 壓縮歸檔 → 日常與其他依使用頻率與優先度遺忘。 # 連結/抽象化/提取線索)→ 壓縮歸檔 → 日常與其他依使用頻率與優先度遺忘。
# 另提供 --catchupcron 未執行時的補跑)、--force(手動立即整理)、 # 另提供 --catchupcron 未執行時的補跑)、--force(手動立即整理)、
# --install-cron--remove-cron(排程安裝與移除)、--status(狀態)。 # --install-cron--remove-cron(排程安裝與移除)、--status(狀態)。
# 更新時間:2026/07/28 12:21:11 # 更新時間:2026/07/28 12:40:24
# 相依:bash、node、任一 headless CLI、crontab(僅排程安裝需要)、 # 相依:bash、node、任一 headless CLI、crontab(僅排程安裝需要)、
# 同目錄的 role_lib.sh 與 memory.js。 # 同目錄的 role_lib.sh 與 memory.js。
# 退出碼:0 成功或無事可做;1 參數錯誤或整理失敗(cron 觸發時不影響使用者)。 # 退出碼:0 成功或無事可做;1 參數錯誤或整理失敗(cron 觸發時不影響使用者)。
@@ -75,7 +75,7 @@ sleep_cycle() {
請模擬睡眠中的兩階段記憶整理,但最後只輸出一個 JSON 物件。 請模擬睡眠中的兩階段記憶整理,但最後只輸出一個 JSON 物件。
1. 只輸出一個 JSON 物件,不要前言、不要結語、不要 code fence,格式為: 1. 只輸出一個 JSON 物件,不要前言、不要結語、不要 code fence,格式為:
{"memories":[{"action":"new","category":"skill","summary":"一句話總結","tags":["標籤1","標籤2"],"priority":4,"relevance":["explicit","future"],"links":["既有記憶 id"],"sleep_stage":"nrem-rem","content":"- 要點\n- 要點","from":["inbox 的 id"]}],"sleepDigest":"本次睡眠整理摘要,80 字內"} {"memories":[{"action":"new","category":"skill","summary":"一句話總結","tags":["標籤1","標籤2"],"priority":4,"relevance":["explicit","future"],"links":["既有記憶 id"],"memory_type":"procedural","declarative":"implicit","retention_stage":"long_term","sleep_stage":"nrem-rem","content":"- 要點\n- 要點","from":["inbox 的 id"]}],"sleepDigest":"本次睡眠整理摘要,80 字內"}
2. NREM 鞏固階段先做:去除雜訊與流水帳、遮蔽憑證與個資、分類、去重、合併、壓縮成可長期保存的穩定記憶。 2. NREM 鞏固階段先做:去除雜訊與流水帳、遮蔽憑證與個資、分類、去重、合併、壓縮成可長期保存的穩定記憶。
3. REM 整合階段再做:找出新記憶與 EXISTING 的關聯,抽出可重複套用的規則、偏好、決策模式、角色語氣調整或未來提取線索。 3. REM 整合階段再做:找出新記憶與 EXISTING 的關聯,抽出可重複套用的規則、偏好、決策模式、角色語氣調整或未來提取線索。
4. action 三選一: 4. action 三選一:
@@ -86,14 +86,24 @@ sleep_cycle() {
important 放長期偏好、規範、決策與身分背景;interest 放反覆關注的主題;news 放新事實與外部資訊; important 放長期偏好、規範、決策與身分背景;interest 放反覆關注的主題;news 放新事實與外部資訊;
skill 放可重複套用的做法;daily 放一次性例行工作;其餘歸 other。 skill 放可重複套用的做法;daily 放一次性例行工作;其餘歸 other。
6. priority 必填,1 到 5:5=使用者明確要求、長期規範、穩定偏好或核心身分;4=可重複套用的技能/決策;3=有用新知;2=短期日常;1=低價值但暫存。 6. priority 必填,1 到 5:5=使用者明確要求、長期規範、穩定偏好或核心身分;4=可重複套用的技能/決策;3=有用新知;2=短期日常;1=低價值但暫存。
7. relevance 必填 1 至 4 個,從下列語意挑選或用等價繁中詞:explicit(使用者明確要求)、future(未來會用)、repeated(反覆出現)、novelty(新知)、emotional(語氣/情緒/偏好)、temporary(短期)。 7. memory_type 必填,六選一:
8. links 可填 EXISTING 中相關記憶 id;沒有就填空陣列。merge 時若有舊 links,應保留並加上新關聯 - rule:長期規範、固定工作原則
9. sleep_stage 填 "nrem"、"rem" 或 "nrem-rem"。只有純分類去噪用 nrem;有建立跨記憶連結或抽象規則用 rem 或 nrem-rem - preference:穩定偏好、語氣與互動喜好
10. **每一則 INBOX 的 id 都必須出現在某一筆的 from 中**,沒被提及的會留到下個睡眠週期重做 - procedural:技能、流程、可重複操作
11. content 壓縮成 5 行以內要點(每行以「- 」開頭),總長不超過 400 字,去除重複敘述與流水帳 - semantic:事實、觀念、工具知識、外部資訊
12. summary 一句話 40 字內;tags 2 至 4 個。全部使用繁體中文(台灣用語) - episodic:個別事件、一次性進度、特定時間地點脈絡
13. sleepDigest 總結本次新增、合併、丟棄、抽象化或建立關聯的重點,80 字內 - emotional:情緒反應、語氣連結、制約式喜惡
14. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號 8. declarative 必填:semanticepisodicpreferencerule 通常為 explicitproceduralemotional 通常為 implicit
9. retention_stage 必填:整理後可長期保存者填 long_term;仍只是短期暫存且不值得長期保存者請用 action=drop,不要輸出 working。
10. relevance 必填 1 至 4 個,從下列語意挑選或用等價繁中詞:explicit(使用者明確要求)、future(未來會用)、repeated(反覆出現)、novelty(新知)、emotional(語氣/情緒/偏好)、temporary(短期)。
11. links 可填 EXISTING 中相關記憶 id;沒有就填空陣列。merge 時若有舊 links,應保留並加上新關聯。
12. sleep_stage 填 "nrem"、"rem" 或 "nrem-rem"。只有純分類去噪用 nrem;有建立跨記憶連結或抽象規則用 rem 或 nrem-rem。
13. 感覺記憶(短暫光影、聲音餘響、無結論的工具雜訊)一律 drop;不要保存到長期記憶。
14. **每一則 INBOX 的 id 都必須出現在某一筆的 from 中**,沒被提及的會留到下個睡眠週期重做。
15. content 壓縮成 5 行以內要點(每行以「- 」開頭),總長不超過 400 字,去除重複敘述與流水帳。
16. summary 一句話 40 字內;tags 2 至 4 個。全部使用繁體中文(台灣用語)。
17. sleepDigest 總結本次新增、合併、丟棄、抽象化或建立關聯的重點,80 字內。
18. 嚴禁輸出任何憑證與個資:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
素材: 素材:
${material} ${material}
+24 -14
View File
@@ -1,6 +1,6 @@
--- ---
name: role name: role
description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(namenature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再壓縮歸檔,日常與其他依使用頻率與優先度遺忘)。提供 --new(新建或更新角色;需產生英文大寫角色 ID,並詢問是否網路搜尋資料作初始記憶)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--sleep(立即整理)、--status--diagnose、--install-cron--remove-cron、--forget-preview 等模式。當使用者說建立角色、新增人格、切換角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色,或提到 .roles.memoryROLE_NAMEROLE_ENABLEDROLE_SLEEP_STARTROLE_MEMORY_HOMEROLE_LOAD_LIMITROLE_CAPTURE_ENABLED 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 doc plugin 的 worklog)、專案文件化(用 doc-funcs)。 description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI 工具以固定角色(namenature/vibe/簽名 emoji)回覆,並把每輪對話累積成長期記憶:搭配相容的 SessionStart hook 於啟動時依字元預算載入高價值記憶、Stop hook 先本地過濾再輕量記錄對話,睡眠時段(預設 22:00 至隔天 06:00)由排程整理記憶(NREM 鞏固:分類/去噪/去重/合併/優先度;REM 整合:跨記憶連結/抽象化/提取線索;再依 semanticepisodicproceduralemotionalpreferencerule 與 explicitimplicit 標記長期記憶型態,壓縮歸檔並適當遺忘)。提供 --new(新建或更新角色;需產生英文大寫角色 ID,並詢問是否網路搜尋資料作初始記憶)、--use(以角色 ID 切換啟用角色)、--list(列出角色與 ID)、--sleep(立即整理)、--status--diagnose、--install-cron--remove-cron、--forget-preview 等模式。當使用者說建立角色、新增人格、切換角色、讓回覆更有特色、角色記憶、記憶整理、睡覺整理記憶、忘記舊記憶、角色沒有載入、hook 沒載入角色,或提到 .roles.memoryROLE_NAMEROLE_ENABLEDROLE_SLEEP_STARTROLE_MEMORY_HOMEROLE_LOAD_LIMITROLE_CAPTURE_ENABLED 時觸發。不適用於:工作紀錄寫入 Gitea wiki(用 doc plugin 的 worklog)、專案文件化(用 doc-funcs)。
--- ---
# role — 角色人格與長期記憶 # role — 角色人格與長期記憶
@@ -15,9 +15,9 @@ description: 角色人格與長期記憶系統的建立與維護 skill。讓 CLI
| cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**,沒有才進入 NREM/REM 兩階段記憶整理 | | cron 排程(本 skill 安裝) | 系統排程 | 睡眠時段每小時檢查一次:**有 AI 在運行就不睡**,沒有才進入 NREM/REM 兩階段記憶整理 |
| 本 skill `/jsc:role` | 使用者/助理手動 | `--new``--use``--list``--sleep``--status``--install-cron``--forget-preview` | | 本 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 | 角色與記憶載入、第一則回覆問候提示(單一實作,避免漂移) |
| `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(四欄固定格式) | | `scripts/role/role_capture.sh` | Stop hook | 對話 → 記憶(固定欄位格式) |
| `scripts/role/role_sleep.sh` | cron/補跑/手動 | 睡眠判斷、記憶整理、排程安裝、狀態輸出 | | `scripts/role/role_sleep.sh` | cron/補跑/手動 | 睡眠判斷、記憶整理、排程安裝、狀態輸出 |
| `scripts/role/memory.js` | 上述共用 | 記憶檔讀寫、分類、去重合併、優先度與關聯 metadata、壓縮歸檔、遺忘、載入組裝 | | `scripts/role/memory.js` | 上述共用 | 記憶檔讀寫、分類、去重合併、優先度、心理學記憶型態與關聯 metadata、壓縮歸檔、遺忘、載入組裝 |
| `scripts/role/transcript.js` | 上述共用 | 抽本輪對話片段、機密與個資遮蔽 | | `scripts/role/transcript.js` | 上述共用 | 抽本輪對話片段、機密與個資遮蔽 |
| `scripts/role/role_lib.sh` | 上述共用 | log、角色解析、睡眠時段、AI 行程偵測、CLI 選擇、記憶鎖 | | `scripts/role/role_lib.sh` | 上述共用 | log、角色解析、睡眠時段、AI 行程偵測、CLI 選擇、記憶鎖 |
@@ -175,10 +175,10 @@ ROLE_DIR="<skill base directory>/../../scripts/role" # 其他助理
| --- | --- | | --- | --- |
| SessionStart | 預設 `ROLE_LOAD_LIMIT=4000`,只載入高優先度全文與中高優先度摘要;低 priority、無 links、久未更新的記憶不進 context | | SessionStart | 預設 `ROLE_LOAD_LIMIT=4000`,只載入高優先度全文與中高優先度摘要;低 priority、無 links、久未更新的記憶不進 context |
| SessionStop | 先用本地規則略過短回合與無記憶線索的對話,只有值得保存才呼叫模型做輕量編碼 | | SessionStop | 先用本地規則略過短回合與無記憶線索的對話,只有值得保存才呼叫模型做輕量編碼 |
| Sleep | 高成本的去重、合併、抽象化links 建立留到睡眠週期,但仍受 `ROLE_SLEEP_COLLECT_LIMIT`、`ROLE_SLEEP_BATCH`、`ROLE_SLEEP_EXISTING_LIMIT` 與 `ROLE_SLEEP_OUTPUT_LIMIT` 控制;沒有 inbox 時只做本地遺忘檢查 | | Sleep | 高成本的去重、合併、抽象化links 建立與長期記憶型態標記留到睡眠週期,但仍受 `ROLE_SLEEP_COLLECT_LIMIT`、`ROLE_SLEEP_BATCH`、`ROLE_SLEEP_EXISTING_LIMIT` 與 `ROLE_SLEEP_OUTPUT_LIMIT` 控制;沒有 inbox 時只做本地遺忘檢查 |
| 手動節流 | 可設 `ROLE_CAPTURE_ENABLED=0` 關閉 Stop 記錄,或調低 `ROLE_LOAD_LIMIT`/調高 `ROLE_LOAD_FULL_MIN_PRIORITY` | | 手動節流 | 可設 `ROLE_CAPTURE_ENABLED=0` 關閉 Stop 記錄,或調低 `ROLE_LOAD_LIMIT`/調高 `ROLE_LOAD_FULL_MIN_PRIORITY` |
Stop hook 只做「編碼前處理」,輸出粗分類、summary、tags、priority、relevance 與要點;完整 NREM/REM 整理只在睡眠週期進行。 Stop hook 只做「編碼前處理」,輸出粗分類、summary、tags、priority、relevance、memory_type 與要點;系統會把 inbox 標為 `retention_stage: working`。完整 NREM/REM 整理與 `declarative``retention_stage: long_term` 判定只在睡眠週期進行。
### `--forget-preview` ### `--forget-preview`
@@ -265,10 +265,10 @@ updated: <yyyy/MM/dd HH:mm:ss>
### 記憶 ### 記憶
- 記憶存放於 `~/.memory/<角色 ID>/`,來源是與使用者的對話與新建角色時使用者同意建立的初始背景資料:每輪結束由 hook 自動記錄到 `inbox/`,睡眠時段整理歸檔。 - 記憶存放於 `~/.memory/<角色 ID>/`,來源是與使用者的對話與新建角色時使用者同意建立的初始背景資料:每輪結束由 hook 自動記錄到 `inbox/` 作為工作記憶,睡眠時段整理成長期記憶;感覺記憶與無結論工具雜訊不落檔。
- 整理規則採睡眠分期模型:**NREM 鞏固**先分類成重要/興趣/新知/技能/日常/其他六類,去除雜訊、去重、合併、設定標籤、摘要與優先度;**REM 整合**再建立跨記憶關聯、抽出可重複使用的規則與提取線索;原始記錄壓縮保存在 `archive/raw/`。 - 整理規則採睡眠分期模型:**NREM 鞏固**先分類成重要/興趣/新知/技能/日常/其他六類,去除雜訊、去重、合併、設定標籤、摘要與優先度;**REM 整合**再建立跨記憶關聯、抽出可重複使用的規則與提取線索,並標記 `memory_type`semanticepisodicproceduralemotionalpreferencerule)、`declarative`explicitimplicit)與 `retention_stage`;原始記錄壓縮保存在 `archive/raw/`。
- **日常與其他**兩類會依使用頻率、優先度與關聯適當遺忘:久未再次出現、命中次數低、優先度低且沒有關聯者,壓縮到 `archive/forgotten/` 後移出常用記憶。 - **日常與其他**兩類會依使用頻率、優先度、型態與關聯適當遺忘:久未再次出現、命中次數低、優先度低且沒有關聯者,壓縮到 `archive/forgotten/` 後移出常用記憶`episodic` 短期事件更容易遺忘,`rule``preference``procedural` 會提高保留權重
- 載入順序:**重要與興趣載入全文**;其餘只載入總結與標籤,依**技能 → 新知 → 日常 → 其他**排序。需要細節時自行讀取對應分類的記憶檔。 - 載入順序:**重要與興趣載入全文**;其餘只載入總結與標籤,依**技能 → 新知 → 日常 → 其他**排序,並優先保留 `rule``preference``procedural` 與有 links 的記憶。需要細節時自行讀取對應分類的記憶檔。
- 使用者明確要求記住某件事時,主動補寫一則記憶(載入時會提供補寫指令)。 - 使用者明確要求記住某件事時,主動補寫一則記憶(載入時會提供補寫指令)。
- **絕不把憑證與個資寫進記憶**:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。 - **絕不把憑證與個資寫進記憶**:token、密碼、API key、連線字串、Email、電話、姓名、身分證號。
<!-- JSC-ROLE-COMMON:END --> <!-- JSC-ROLE-COMMON:END -->
@@ -294,14 +294,24 @@ updated: <yyyy/MM/dd HH:mm:ss>
└── state.json 上次整理/遺忘時間 └── state.json 上次整理/遺忘時間
``` ```
每則記憶是一個 `.md`frontmatter 帶 `id``category``summary`(一句話總結)/`tags``priority`15)/`relevance`explicitfuturerepeatednoveltyemotionaltemporary 等)/`links`(相關記憶 id)/`sleep_stage`encodingseednremremnrem-rem)/`created``updated``last_replayed``hits`(命中次數,去重合併時 +1)。舊記憶沒有新欄位時,讀取時會依分類補預設值。 每則記憶是一個 `.md`frontmatter 帶 `id``category``summary`(一句話總結)/`tags``priority`15)/`relevance`explicitfuturerepeatednoveltyemotionaltemporary 等)/`links`(相關記憶 id)/`memory_type`semanticepisodicproceduralemotionalpreferencerule)/`declarative`explicitimplicit)/`retention_stage`workinglong_term)/`sleep_stage`encodingseednremremnrem-rem)/`created``updated``last_replayed``hits`(命中次數,去重合併時 +1)。舊記憶沒有新欄位時,讀取時會依分類與路徑補預設值。
心理學分類與系統欄位對應:
| 心理學分類 | 系統處理 |
| --- | --- |
| 感覺記憶 | 不落檔;短暫感官殘留與工具雜訊直接丟棄 |
| 短期/工作記憶 | `inbox/``retention_stage: working`,只做輕量編碼 |
| 長期記憶 | 睡眠整理後進入六分類目錄,`retention_stage: long_term` |
| 外顯/陳述性 | `declarative: explicit`,多見於 `semantic`、`episodic`、`preference`、`rule` |
| 內隱/非陳述性 | `declarative: implicit`,多見於 `procedural`、`emotional` |
遺忘規則(只套用於日常與其他): 遺忘規則(只套用於日常與其他):
| 分類 | 未更新天數 | 命中次數 | 優先度 | 關聯 | 動作 | | 分類 | 未更新天數 | 命中次數 | 優先度 | 關聯 | 動作 |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 日常 daily | ≥ 14 天 | ≤ 1 | ≤ 2 | 無 links | 壓縮到 `archive/forgotten/` 後移除 | | 日常 daily | ≥ 14 天`episodic` 約 7 天) | ≤ 1 | ≤ 2 | 無 links,且非 `rule``preference``procedural` | 壓縮到 `archive/forgotten/` 後移除 |
| 其他 other | ≥ 7 天 | ≤ 1 | ≤ 2 | 無 links | 壓縮到 `archive/forgotten/` 後移除 | | 其他 other | ≥ 7 天`episodic` 約 4 天) | ≤ 1 | ≤ 2 | 無 links,且非 `rule``preference``procedural` | 壓縮到 `archive/forgotten/` 後移除 |
--- ---
@@ -312,11 +322,11 @@ flowchart TD
A[cron 每小時觸發<br/>睡眠時段內] --> B{有 AI 正在運行?} A[cron 每小時觸發<br/>睡眠時段內] --> B{有 AI 正在運行?}
B -- 有 --> C[不睡,下個整點再檢查] B -- 有 --> C[不睡,下個整點再檢查]
B -- 沒有 --> D[進入睡眠,取得記憶鎖] B -- 沒有 --> D[進入睡眠,取得記憶鎖]
D --> E[collectinbox 待整理 + 既有記憶索引/優先度/關聯] D --> E[collectinbox 待整理 + 既有記憶索引/優先度/型態/關聯]
E --> F{有待整理記憶?} E --> F{有待整理記憶?}
F -- 沒有 --> G[更新整理時間 → 執行遺忘] F -- 沒有 --> G[更新整理時間 → 執行遺忘]
F -- 有 --> H[NREM:分類/去噪/去重/合併/優先度] F -- 有 --> H[NREM:分類/去噪/去重/合併/優先度]
H --> I[REM:跨記憶連結/抽象規則/提取線索] H --> I[REM:跨記憶連結/抽象規則/記憶型態/提取線索]
I --> J[apply:寫入分類、原始記錄歸檔、保存睡眠摘要] I --> J[apply:寫入分類、原始記錄歸檔、保存睡眠摘要]
J --> K[forget:低優先度且無關聯的日常/其他遺忘] J --> K[forget:低優先度且無關聯的日常/其他遺忘]
K --> Z[釋放鎖] K --> Z[釋放鎖]