feat(role): 新增心理學記憶型態 metadata
This commit is contained in:
+167
-9
@@ -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 ||= "";
|
||||||
|
|
||||||
|
|||||||
@@ -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.sh/memory.js/transcript.js。
|
# 相依:bash、node、任一 headless CLI、同目錄的 role_lib.sh/memory.js/transcript.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: <semantic/episodic/procedural/emotional/preference/rule 六選一>
|
||||||
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
@@ -6,7 +6,7 @@
|
|||||||
# 連結/抽象化/提取線索)→ 壓縮歸檔 → 日常與其他依使用頻率與優先度遺忘。
|
# 連結/抽象化/提取線索)→ 壓縮歸檔 → 日常與其他依使用頻率與優先度遺忘。
|
||||||
# 另提供 --catchup(cron 未執行時的補跑)、--force(手動立即整理)、
|
# 另提供 --catchup(cron 未執行時的補跑)、--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 必填:semantic/episodic/preference/rule 通常為 explicit;procedural/emotional 通常為 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}
|
||||||
|
|||||||
Reference in New Issue
Block a user