@amemhq/core 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{chunk-B2NS7WAM.js → chunk-K6WZTDM7.js} +229 -58
- package/dist/chunk-K6WZTDM7.js.map +1 -0
- package/dist/cli-migrate.cjs +206 -52
- package/dist/cli-migrate.cjs.map +1 -1
- package/dist/cli-migrate.d.cts +21 -7
- package/dist/cli-migrate.d.ts +21 -7
- package/dist/cli-migrate.js +103 -42
- package/dist/cli-migrate.js.map +1 -1
- package/dist/index.cjs +228 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +145 -29
- package/dist/index.d.ts +145 -29
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-B2NS7WAM.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/quality.ts","../src/crud-guard.ts"],"sourcesContent":["/**\n * quality.ts — Memory quality scanning and review batch generation (Story 31)\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { listNotes, patchNotePayload, type MemoryNote } from './storage.js'\nimport { canWrite } from './auth.js'\nimport type { PromptLocale } from './prompts.js'\n\nconst LOCALE: PromptLocale = (process.env.AMEM_PROMPT_LOCALE as PromptLocale) === 'zh' ? 'zh' : 'en'\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface LowQualityItem {\n note: MemoryNote\n reasons: LowQualityReason[]\n}\n\nexport type LowQualityReason = 'too_short' | 'expired_ephemeral' | 'pending_conflict'\n\n// ── scanLowQuality ────────────────────────────────────────────────────────────\n\nexport async function scanLowQuality(agentId: string): Promise<LowQualityItem[]> {\n const notes = await listNotes(agentId)\n const now = Date.now()\n const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000\n const results: LowQualityItem[] = []\n\n for (const note of notes) {\n // Story 33: listNotes also returns SHARED notes owned by other agents. Quality\n // enforcement marks notes low_quality, so scan only what this agent may write —\n // flagging a note we cannot act on would be noise, and patching it a violation.\n if (!canWrite(note, agentId)) continue\n\n const reasons: LowQualityReason[] = []\n\n if (note.content.trim().length < 10) {\n reasons.push('too_short')\n }\n\n if (note.ephemeral === true) {\n const createdAt = new Date(note.timestamp).getTime()\n if (now - createdAt > SEVEN_DAYS_MS) {\n reasons.push('expired_ephemeral')\n }\n }\n\n if (note.conflict === true) {\n reasons.push('pending_conflict')\n }\n\n if (reasons.length > 0) {\n if (!note.low_quality) {\n await patchNotePayload(note.id, { low_quality: true })\n }\n results.push({ note, reasons })\n }\n }\n\n return results\n}\n\n// ── generateReviewBatch ───────────────────────────────────────────────────────\n\nconst DEFAULT_OUTPUT_DIR = process.env.AMEM_REVIEW_DIR || process.cwd()\n\nfunction nextBatchNumber(dir: string): number {\n let max = 0\n try {\n const files = fs.readdirSync(dir)\n for (const f of files) {\n const match = f.match(/^amem-review-batch(\\d+)\\.md$/)\n if (match) {\n const n = parseInt(match[1], 10)\n if (n > max) max = n\n }\n }\n } catch {\n // dir doesn't exist yet\n }\n return max + 1\n}\n\nfunction reasonLabel(r: LowQualityReason): string {\n if (LOCALE === 'zh') {\n switch (r) {\n case 'too_short':\n return '内容过短(<10字)'\n case 'expired_ephemeral':\n return '临时记忆已过期(>7天)'\n case 'pending_conflict':\n return '存在冲突标记'\n }\n }\n switch (r) {\n case 'too_short':\n return 'Content too short (<10 chars)'\n case 'expired_ephemeral':\n return 'Ephemeral memory expired (>7 days)'\n case 'pending_conflict':\n return 'Pending conflict flag'\n }\n}\n\nfunction severityBadge(reasons: LowQualityReason[]): string {\n if (reasons.includes('too_short')) return '🔴 LOW'\n if (reasons.includes('expired_ephemeral')) return '🟡 EXPIRED'\n return '🟠 CONFLICT'\n}\n\nexport async function generateReviewBatch(agentId: string, outputPath?: string): Promise<string> {\n // outputPath is a bare filename, not a path. It arrives from the\n // memory_quality_scan tool, so a prompt-injected agent could otherwise hand\n // us an absolute path or a ../ traversal and overwrite any file the process\n // can write (CodeQL js/path-injection). path.basename() strips every\n // directory component, so the write can only ever land in the review root;\n // we reject anything that carried a directory part loudly rather than\n // silently rewriting it. Operators choose the root with AMEM_REVIEW_DIR.\n const root = path.resolve(DEFAULT_OUTPUT_DIR)\n let filePath: string\n let batchN: number\n if (outputPath) {\n const name = path.basename(outputPath)\n if (name !== outputPath || name === '' || name === '.' || name === '..') {\n throw new Error(`[quality] outputPath 必须是纯文件名(不含目录): ${outputPath}`)\n }\n filePath = path.join(root, name)\n batchN = 0\n } else {\n batchN = nextBatchNumber(root)\n filePath = path.join(root, `amem-review-batch${batchN}.md`)\n }\n\n const items = await scanLowQuality(agentId)\n\n const now = new Date().toISOString().slice(0, 10)\n const lines: string[] = []\n\n const title = LOCALE === 'zh' ? 'A-MEM 质量审核' : 'A-MEM Quality Review'\n const genLabel = LOCALE === 'zh' ? '生成时间' : 'Generated'\n const countLabel = LOCALE === 'zh' ? `共 ${items.length} 条低质量条目` : `${items.length} low-quality item(s)`\n const applyHint =\n LOCALE === 'zh' ? '勾选后交给助手处理这些条目' : 'Tick your choices, then ask the assistant to act on them'\n\n lines.push(`# ${title} — Batch ${batchN || 'custom'}`)\n lines.push('')\n lines.push(`> ${genLabel}:${now} | ${countLabel}`)\n lines.push(`> ${applyHint}`)\n lines.push('')\n\n if (items.length === 0) {\n lines.push(LOCALE === 'zh' ? '✅ 没有发现低质量条目。' : '✅ No low-quality items found.')\n }\n\n // ── Story 43: conflicts render as ONE decision, not two entries ────────────\n // A contradiction involves a PAIR. Listing each note separately forces the\n // reviewer to find both, reconstruct that they belong together, then tick two\n // boxes — which is the single biggest source of review friction. Shown side by\n // side with timestamps, the reason, and a recommendation, it is one glance and\n // one tick. Each note still gets its own entry below for the apply tool.\n const byId = new Map(items.map((it) => [it.note.id, it.note]))\n const renderedPairs = new Set<string>()\n const pairLines: string[] = []\n for (const { note } of items) {\n for (const otherId of note.conflicts_with ?? []) {\n const other = byId.get(otherId)\n if (!other) continue\n const key = note.id < otherId ? `${note.id}:${otherId}` : `${otherId}:${note.id}`\n if (renderedPairs.has(key)) continue\n renderedPairs.add(key)\n\n // Newer first — the later statement is usually the current one.\n const [newer, older] = Date.parse(note.timestamp) >= Date.parse(other.timestamp) ? [note, other] : [other, note]\n const zh = LOCALE === 'zh'\n pairLines.push(`### 🟠 ${zh ? '冲突' : 'CONFLICT'} | ${newer.category || 'General'}`)\n if (newer.conflict_reason) {\n pairLines.push(`**${zh ? '判定理由' : 'Why'}:** ${newer.conflict_reason}`)\n pairLines.push('')\n }\n pairLines.push(`| | ${zh ? '时间' : 'When'} | ${zh ? '内容' : 'Content'} |`)\n pairLines.push('| :-- | :-- | :-- |')\n pairLines.push(`| **A** | ${newer.timestamp.slice(0, 10)} | ${newer.content.replace(/\\n/g, ' ')} |`)\n pairLines.push(`| **B** | ${older.timestamp.slice(0, 10)} | ${older.content.replace(/\\n/g, ' ')} |`)\n pairLines.push('')\n pairLines.push(`\\`A: ${newer.id}\\``)\n pairLines.push(`\\`B: ${older.id}\\``)\n pairLines.push('')\n pairLines.push(\n zh\n ? `- [ ] ✅ **A 是当前状态,停用 B**(推荐:A 更新)`\n : `- [ ] ✅ **A is current — retire B** (recommended: A is newer)`\n )\n pairLines.push(zh ? `- [ ] ↩️ B 是当前状态,停用 A` : `- [ ] ↩️ B is current — retire A`)\n pairLines.push(zh ? `- [ ] 🤝 两者都成立(误判)` : `- [ ] 🤝 Both hold — not a contradiction`)\n pairLines.push('')\n pairLines.push('---')\n pairLines.push('')\n }\n }\n if (pairLines.length > 0) {\n lines.push(LOCALE === 'zh' ? '## 冲突(成对,一个冲突一个决定)' : '## Conflicts (paired — one decision each)')\n lines.push('')\n lines.push(...pairLines)\n lines.push(LOCALE === 'zh' ? '## 其余条目' : '## Other items')\n lines.push('')\n }\n\n for (let i = 0; i < items.length; i++) {\n const { note, reasons } = items[i]\n const badge = severityBadge(reasons)\n const reasonStr = reasons.map(reasonLabel).join('、')\n\n const issueLabel = LOCALE === 'zh' ? '问题' : 'Issue'\n const contentLabel = LOCALE === 'zh' ? '内容' : 'Content'\n const kwLabel = LOCALE === 'zh' ? '关键词' : 'Keywords'\n const tagLabel = LOCALE === 'zh' ? '标签' : 'Tags'\n const keepLabel = LOCALE === 'zh' ? '保留' : 'Keep'\n const rewriteLabel = LOCALE === 'zh' ? '改写' : 'Rewrite'\n const deleteLabel = LOCALE === 'zh' ? '删除' : 'Delete'\n\n lines.push(`### [${i + 1}] ${badge} | ${note.category || 'General'}`)\n lines.push(`\\`${note.id}\\``)\n lines.push('')\n lines.push(`**${issueLabel}:** ${reasonStr}`)\n lines.push('')\n lines.push(`**${contentLabel}:**`)\n lines.push('```')\n lines.push(note.content)\n lines.push('```')\n lines.push('')\n lines.push(`**${kwLabel}:** ${note.keywords.join(', ')}`)\n lines.push(`**${tagLabel}:** ${note.tags.join(', ')}`)\n lines.push('')\n lines.push(`- [ ] ✅ ${keepLabel}`)\n lines.push(`- [ ] 🔧 ${rewriteLabel}`)\n lines.push(`- [ ] 🗑️ ${deleteLabel}`)\n lines.push('')\n lines.push('---')\n lines.push('')\n }\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, lines.join('\\n'), 'utf8')\n\n return filePath\n}\n","/**\n * crud-guard.ts — write-safety policy for the agent_end CRUD decision (Story 41).\n *\n * The CRUD step hands the LLM a numbered list of candidate memories and asks it\n * to pick one to UPDATE or DELETE. Picking the WRONG number is the engine's only\n * silent, unrecoverable failure:\n *\n * - DELETE is already safe — `invalidateNote` is a soft delete (is_active=false).\n * - UPDATE is not — `updateNoteContent` overwrites content + embedding in place.\n *\n * An out-of-range index is harmless (`memories[bad]` is undefined and the caller\n * skips it). The dangerous case is an in-range but WRONG index: both are valid\n * array positions, so nothing structural catches it, and the access protocol\n * (Story 33/36) does not either — the caller usually does own the note it is\n * about to clobber.\n *\n * This is a documented failure class, not a hypothetical: mem0 removed its own\n * CRUD step in part because \"overwrites sometimes erased key information from the\n * original fact\", and Memory-R1 exists because vanilla LLMs mis-classify additive\n * facts as contradictions. The risk scales inversely with model capability, and\n * the memories that reach this step have already survived hash and vector dedup —\n * i.e. they are the HARDEST subset, exactly where a cheap model is least reliable.\n *\n * The rule below is the architectural answer to that, rather than paying for a\n * bigger model: before overwriting a memory, check that the replacement text is\n * at least plausibly ABOUT that memory. A mis-targeted UPDATE rewrites a note\n * with content that has nothing to do with it, which is cheap to detect — both\n * embeddings are already in hand, so this costs one dot product and no LLM call.\n */\nimport { cosineSimilarity } from './embedding.js'\n\n/**\n * Similarity floor for accepting an UPDATE target.\n *\n * Heuristic, not empirically tuned: it sits just above the 0.3 bar the engine\n * already uses for \"these two notes are related at all\", because a legitimate\n * CRUD UPDATE is often a correction or contradiction (\"drinks tea\" → \"switched to\n * coffee\") that is related but not near-identical. Set it too high and real\n * corrections get downgraded; too low and the guard does nothing.\n *\n * Failing this check is SAFE by construction — the caller inserts the fact as a\n * new memory instead of overwriting, and scheduled consolidation can merge later.\n * So the cost of a false positive is a duplicate, and the cost of a false\n * negative is a destroyed memory. Bias accordingly: raise it for cheaper models.\n */\nexport const DEFAULT_CRUD_UPDATE_MIN_SIM = 0.35\n\n/** Resolve the threshold: env var wins, then an explicit override, then default. */\nexport function resolveCrudUpdateMinSim(override?: number): number {\n const envVal = Number(process.env.AMEM_CRUD_UPDATE_MIN_SIM)\n if (Number.isFinite(envVal) && envVal >= 0) return envVal\n if (override !== undefined && Number.isFinite(override) && override >= 0) return override\n return DEFAULT_CRUD_UPDATE_MIN_SIM\n}\n\n/**\n * May `newEmbedding`'s fact overwrite the memory `targetEmbedding` belongs to?\n *\n * True when the replacement is plausibly about the same thing. False means the\n * LLM most likely named the wrong index — the caller should insert instead of\n * overwrite, never throw.\n *\n * Both vectors are L2-normalized by `encode`, so this is a dot product.\n */\nexport function isPlausibleUpdateTarget(\n newEmbedding: number[],\n targetEmbedding: number[],\n minSimilarity?: number\n): boolean {\n // A missing or malformed vector is not evidence of a good target. Refuse\n // rather than let a degenerate similarity wave the overwrite through.\n if (!newEmbedding?.length || !targetEmbedding?.length) return false\n if (newEmbedding.length !== targetEmbedding.length) return false\n return cosineSimilarity(newEmbedding, targetEmbedding) >= resolveCrudUpdateMinSim(minSimilarity)\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/quality.ts","../src/crud-guard.ts"],"sourcesContent":["/**\n * quality.ts — Memory quality scanning and review batch generation (Story 31)\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\nimport { listNotes, patchNotePayload, type MemoryNote } from './storage.js'\nimport { canWrite } from './auth.js'\nimport type { PromptLocale } from './prompts.js'\n\nconst LOCALE: PromptLocale = (process.env.AMEM_PROMPT_LOCALE as PromptLocale) === 'zh' ? 'zh' : 'en'\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface LowQualityItem {\n note: MemoryNote\n reasons: LowQualityReason[]\n}\n\nexport type LowQualityReason = 'too_short' | 'expired_ephemeral' | 'pending_conflict'\n\n// ── scanLowQuality ────────────────────────────────────────────────────────────\n\nexport async function scanLowQuality(agentId: string): Promise<LowQualityItem[]> {\n const notes = await listNotes(agentId)\n const now = Date.now()\n const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000\n const results: LowQualityItem[] = []\n\n for (const note of notes) {\n // Story 33: listNotes also returns SHARED notes owned by other agents. Quality\n // enforcement marks notes low_quality, so scan only what this agent may write —\n // flagging a note we cannot act on would be noise, and patching it a violation.\n if (!canWrite(note, agentId)) continue\n\n const reasons: LowQualityReason[] = []\n\n if (note.content.trim().length < 10) {\n reasons.push('too_short')\n }\n\n if (note.ephemeral === true) {\n const createdAt = new Date(note.timestamp).getTime()\n if (now - createdAt > SEVEN_DAYS_MS) {\n reasons.push('expired_ephemeral')\n }\n }\n\n if (note.conflict === true) {\n reasons.push('pending_conflict')\n }\n\n if (reasons.length > 0) {\n if (!note.low_quality) {\n await patchNotePayload(note.id, { low_quality: true })\n }\n results.push({ note, reasons })\n }\n }\n\n return results\n}\n\n// ── generateReviewBatch ───────────────────────────────────────────────────────\n\nconst DEFAULT_OUTPUT_DIR = process.env.AMEM_REVIEW_DIR || process.cwd()\n\nfunction nextBatchNumber(dir: string): number {\n let max = 0\n try {\n const files = fs.readdirSync(dir)\n for (const f of files) {\n const match = f.match(/^amem-review-batch(\\d+)\\.md$/)\n if (match) {\n const n = parseInt(match[1], 10)\n if (n > max) max = n\n }\n }\n } catch {\n // dir doesn't exist yet\n }\n return max + 1\n}\n\nfunction reasonLabel(r: LowQualityReason): string {\n if (LOCALE === 'zh') {\n switch (r) {\n case 'too_short':\n return '内容过短(<10字)'\n case 'expired_ephemeral':\n return '临时记忆已过期(>7天)'\n case 'pending_conflict':\n return '存在冲突标记'\n }\n }\n switch (r) {\n case 'too_short':\n return 'Content too short (<10 chars)'\n case 'expired_ephemeral':\n return 'Ephemeral memory expired (>7 days)'\n case 'pending_conflict':\n return 'Pending conflict flag'\n }\n}\n\nfunction severityBadge(reasons: LowQualityReason[]): string {\n if (reasons.includes('too_short')) return '🔴 LOW'\n if (reasons.includes('expired_ephemeral')) return '🟡 EXPIRED'\n return '🟠 CONFLICT'\n}\n\nexport async function generateReviewBatch(agentId: string, outputPath?: string): Promise<string> {\n // outputPath is a bare filename, not a path. It arrives from the\n // memory_quality_scan tool, so a prompt-injected agent could otherwise hand\n // us an absolute path or a ../ traversal and overwrite any file the process\n // can write (CodeQL js/path-injection). path.basename() strips every\n // directory component, so the write can only ever land in the review root;\n // we reject anything that carried a directory part loudly rather than\n // silently rewriting it. Operators choose the root with AMEM_REVIEW_DIR.\n const root = path.resolve(DEFAULT_OUTPUT_DIR)\n let filePath: string\n let batchN: number\n if (outputPath) {\n const name = path.basename(outputPath)\n if (name !== outputPath || name === '' || name === '.' || name === '..') {\n throw new Error(`[quality] outputPath 必须是纯文件名(不含目录): ${outputPath}`)\n }\n filePath = path.join(root, name)\n batchN = 0\n } else {\n batchN = nextBatchNumber(root)\n filePath = path.join(root, `amem-review-batch${batchN}.md`)\n }\n\n const items = await scanLowQuality(agentId)\n\n const now = new Date().toISOString().slice(0, 10)\n const lines: string[] = []\n\n const title = LOCALE === 'zh' ? 'A-MEM 质量审核' : 'A-MEM Quality Review'\n const genLabel = LOCALE === 'zh' ? '生成时间' : 'Generated'\n const countLabel = LOCALE === 'zh' ? `共 ${items.length} 条低质量条目` : `${items.length} low-quality item(s)`\n const applyHint =\n LOCALE === 'zh' ? '勾选后交给助手处理这些条目' : 'Tick your choices, then ask the assistant to act on them'\n\n lines.push(`# ${title} — Batch ${batchN || 'custom'}`)\n lines.push('')\n lines.push(`> ${genLabel}:${now} | ${countLabel}`)\n lines.push(`> ${applyHint}`)\n lines.push('')\n\n if (items.length === 0) {\n lines.push(LOCALE === 'zh' ? '✅ 没有发现低质量条目。' : '✅ No low-quality items found.')\n }\n\n // ── Story 43: conflicts render as ONE decision, not two entries ────────────\n // A contradiction involves a PAIR. Listing each note separately forces the\n // reviewer to find both, reconstruct that they belong together, then tick two\n // boxes — which is the single biggest source of review friction. Shown side by\n // side with timestamps, the reason, and a recommendation, it is one glance and\n // one tick. Each note still gets its own entry below for the apply tool.\n const byId = new Map(items.map((it) => [it.note.id, it.note]))\n const renderedPairs = new Set<string>()\n const pairLines: string[] = []\n for (const { note } of items) {\n for (const otherId of note.conflicts_with ?? []) {\n const other = byId.get(otherId)\n if (!other) continue\n const key = note.id < otherId ? `${note.id}:${otherId}` : `${otherId}:${note.id}`\n if (renderedPairs.has(key)) continue\n renderedPairs.add(key)\n\n // Newer first — the later statement is usually the current one.\n const [newer, older] = Date.parse(note.timestamp) >= Date.parse(other.timestamp) ? [note, other] : [other, note]\n const zh = LOCALE === 'zh'\n pairLines.push(`### 🟠 ${zh ? '冲突' : 'CONFLICT'} | ${newer.category || 'General'}`)\n if (newer.conflict_reason) {\n pairLines.push(`**${zh ? '判定理由' : 'Why'}:** ${newer.conflict_reason}`)\n pairLines.push('')\n }\n pairLines.push(`| | ${zh ? '时间' : 'When'} | ${zh ? '内容' : 'Content'} |`)\n pairLines.push('| :-- | :-- | :-- |')\n pairLines.push(`| **A** | ${newer.timestamp.slice(0, 10)} | ${newer.content.replace(/\\n/g, ' ')} |`)\n pairLines.push(`| **B** | ${older.timestamp.slice(0, 10)} | ${older.content.replace(/\\n/g, ' ')} |`)\n pairLines.push('')\n pairLines.push(`\\`A: ${newer.id}\\``)\n pairLines.push(`\\`B: ${older.id}\\``)\n pairLines.push('')\n pairLines.push(\n zh\n ? `- [ ] ✅ **A 是当前状态,停用 B**(推荐:A 更新)`\n : `- [ ] ✅ **A is current — retire B** (recommended: A is newer)`\n )\n pairLines.push(zh ? `- [ ] ↩️ B 是当前状态,停用 A` : `- [ ] ↩️ B is current — retire A`)\n pairLines.push(zh ? `- [ ] 🤝 两者都成立(误判)` : `- [ ] 🤝 Both hold — not a contradiction`)\n pairLines.push('')\n pairLines.push('---')\n pairLines.push('')\n }\n }\n if (pairLines.length > 0) {\n lines.push(LOCALE === 'zh' ? '## 冲突(成对,一个冲突一个决定)' : '## Conflicts (paired — one decision each)')\n lines.push('')\n lines.push(...pairLines)\n lines.push(LOCALE === 'zh' ? '## 其余条目' : '## Other items')\n lines.push('')\n }\n\n for (let i = 0; i < items.length; i++) {\n const { note, reasons } = items[i]\n const badge = severityBadge(reasons)\n const reasonStr = reasons.map(reasonLabel).join('、')\n\n const issueLabel = LOCALE === 'zh' ? '问题' : 'Issue'\n const contentLabel = LOCALE === 'zh' ? '内容' : 'Content'\n const kwLabel = LOCALE === 'zh' ? '关键词' : 'Keywords'\n const tagLabel = LOCALE === 'zh' ? '标签' : 'Tags'\n const keepLabel = LOCALE === 'zh' ? '保留' : 'Keep'\n const rewriteLabel = LOCALE === 'zh' ? '改写' : 'Rewrite'\n const deleteLabel = LOCALE === 'zh' ? '删除' : 'Delete'\n\n lines.push(`### [${i + 1}] ${badge} | ${note.category || 'General'}`)\n lines.push(`\\`${note.id}\\``)\n lines.push('')\n lines.push(`**${issueLabel}:** ${reasonStr}`)\n lines.push('')\n lines.push(`**${contentLabel}:**`)\n lines.push('```')\n lines.push(note.content)\n lines.push('```')\n lines.push('')\n lines.push(`**${kwLabel}:** ${note.keywords.join(', ')}`)\n lines.push(`**${tagLabel}:** ${note.tags.join(', ')}`)\n lines.push('')\n lines.push(`- [ ] ✅ ${keepLabel}`)\n lines.push(`- [ ] 🔧 ${rewriteLabel}`)\n lines.push(`- [ ] 🗑️ ${deleteLabel}`)\n lines.push('')\n lines.push('---')\n lines.push('')\n }\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, lines.join('\\n'), 'utf8')\n\n return filePath\n}\n","/**\n * crud-guard.ts — write-safety policy for the agent_end CRUD decision (Story 41).\n *\n * The CRUD step hands the LLM a numbered list of candidate memories and asks it\n * to pick one to UPDATE or DELETE. Picking the WRONG number is the engine's only\n * silent, unrecoverable failure:\n *\n * - DELETE is already safe — `invalidateNote` is a soft delete (is_active=false).\n * - UPDATE is not — `updateNoteContent` overwrites content + embedding in place.\n *\n * An out-of-range index is harmless (`memories[bad]` is undefined and the caller\n * skips it). The dangerous case is an in-range but WRONG index: both are valid\n * array positions, so nothing structural catches it, and the access protocol\n * (Story 33/36) does not either — the caller usually does own the note it is\n * about to clobber.\n *\n * This is a documented failure class, not a hypothetical: mem0 removed its own\n * CRUD step in part because \"overwrites sometimes erased key information from the\n * original fact\", and Memory-R1 exists because vanilla LLMs mis-classify additive\n * facts as contradictions. The risk scales inversely with model capability, and\n * the memories that reach this step have already survived hash and vector dedup —\n * i.e. they are the HARDEST subset, exactly where a cheap model is least reliable.\n *\n * The rule below is the architectural answer to that, rather than paying for a\n * bigger model: before overwriting a memory, check that the replacement text is\n * at least plausibly ABOUT that memory. A mis-targeted UPDATE rewrites a note\n * with content that has nothing to do with it, which is cheap to detect — both\n * embeddings are already in hand, so this costs one dot product and no LLM call.\n */\nimport { cosineSimilarity } from './embedding.js'\n\n/**\n * Similarity floor for accepting an UPDATE target.\n *\n * Heuristic, not empirically tuned: it sits just above the 0.3 bar the engine\n * already uses for \"these two notes are related at all\", because a legitimate\n * CRUD UPDATE is often a correction or contradiction (\"drinks tea\" → \"switched to\n * coffee\") that is related but not near-identical. Set it too high and real\n * corrections get downgraded; too low and the guard does nothing.\n *\n * Failing this check is SAFE by construction — the caller inserts the fact as a\n * new memory instead of overwriting, and scheduled consolidation can merge later.\n * So the cost of a false positive is a duplicate, and the cost of a false\n * negative is a destroyed memory. Bias accordingly: raise it for cheaper models.\n */\nexport const DEFAULT_CRUD_UPDATE_MIN_SIM = 0.35\n\n/** Resolve the threshold: env var wins, then an explicit override, then default. */\nexport function resolveCrudUpdateMinSim(override?: number): number {\n const envVal = Number(process.env.AMEM_CRUD_UPDATE_MIN_SIM)\n if (Number.isFinite(envVal) && envVal >= 0) return envVal\n if (override !== undefined && Number.isFinite(override) && override >= 0) return override\n return DEFAULT_CRUD_UPDATE_MIN_SIM\n}\n\n/**\n * May `newEmbedding`'s fact overwrite the memory `targetEmbedding` belongs to?\n *\n * True when the replacement is plausibly about the same thing. False means the\n * LLM most likely named the wrong index — the caller should insert instead of\n * overwrite, never throw.\n *\n * Both vectors are L2-normalized by `encode`, so this is a dot product.\n */\nexport function isPlausibleUpdateTarget(\n newEmbedding: number[],\n targetEmbedding: number[],\n minSimilarity?: number\n): boolean {\n // A missing or malformed vector is not evidence of a good target. Refuse\n // rather than let a degenerate similarity wave the overwrite through.\n if (!newEmbedding?.length || !targetEmbedding?.length) return false\n if (newEmbedding.length !== targetEmbedding.length) return false\n return cosineSimilarity(newEmbedding, targetEmbedding) >= resolveCrudUpdateMinSim(minSimilarity)\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amemhq/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "The agentic memory engine — notes that construct, link, and evolve like a Zettelkasten, on Qdrant + Transformers.js. No Python. Framework-agnostic.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://amem.owo.lc",
|