From 7289751e89af022527b98d8a2fcae1c7b824964d Mon Sep 17 00:00:00 2001
From: modusensus
Date: Fri, 13 Nov 2026 02:06:54 +0800
Subject: [PATCH 1/4] =?UTF-8?q?docs(readme):=20=E7=89=88=E6=9C=AC=E5=8E=86?=
=?UTF-8?q?=E5=8F=B2=E7=A7=BB=E5=87=BA=20README=EF=BC=8C=E5=85=A5=E5=8F=A3?=
=?UTF-8?q?=E9=A1=B5=E5=8F=AA=E8=AE=B2=E5=AE=83=E6=98=AF=E4=BB=80=E4=B9=88?=
=?UTF-8?q?=E3=80=81=E6=80=8E=E4=B9=88=E8=A3=85=E3=80=81=E5=87=AD=E4=BB=80?=
=?UTF-8?q?=E4=B9=88=E4=BF=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
根 README 重写:去掉路线图与 News 表(每版改了什么归 CHANGELOG 与 Releases),
恢复 Mneme / Mnemosyne 的寓意段(希腊人把记忆与梦都交给她,正好是 autoDream 做的事),
并按大仓首发页的读法重排:先一句解决什么,再安装与产物位置,再能力、可信度、面板、接法。
子 README 删掉「📜 版本历史」整节。
代码跟随:syncReadmeTables 随表格一起删除——留着它只会对一份没有表格的 README
报告「跳过」,正是它自己注释里记的那类静默失效;release-prep 第 4 段改成一行如实说明。
release-docs 测试改为反向锁住「表别加回来」,SECURITY.md 支持版本表三条不变。
全量 3440 条,3439 过 / 0 失败 / 1 跳过。
---
CONTRIBUTING.md | 5 +-
README.md | 227 ++++++++++++----------------
dsh-mneme/README.md | 32 +---
dsh-mneme/scripts/release-docs.mjs | 169 ++-------------------
dsh-mneme/test/release-docs.test.js | 122 +++------------
scripts/release-prep.mjs | 62 ++------
6 files changed, 145 insertions(+), 472 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2980b5ec..ddc5fc3e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -354,11 +354,10 @@ DSH 上游仍处于 developer preview 阶段,API 与服务接口变动频繁
版本号遵循语义化版本(`MAJOR.MINOR.PATCH`)。机械的部分交给工作流,编辑判断的部分留给人——**版本号自己能确定的东西机器写,需要判断的东西人写**。完整流程:
-1. **跑 Release Prep 工作流**(`.github/workflows/release-prep.yml`,手动触发,只填目标版本号)。它做这些:改 `dsh-mneme/package.json` 与 `package-lock.json` 的 `version`;刷新两个 README 的测试数(徽章 + 四种 `# N …测试` 注释形状);在 `[Unreleased]` 下面开新版本节,并把里面攒的条目整段搬进去(只有 `[Unreleased]` 本来是空的才留一条 `- (待填)`);给两个 README 的版本表各插一行占位;把路线图上遗留的「🚧 准备中」回填成已发布;最后推 `release-prep/X.Y.Z` 分支并开 PR。**最后那一步吃仓库级开关**:Settings → Actions → General →「Allow GitHub Actions to create and approve pull requests」要打开,否则分支已经推上去了、PR 却开不出来(v0.9.4 于 2026-10-21 就是这样)。这时**别重跑工作流**——机器产出已经在那条分支上了,人直接 `gh pr create --base main --head release-prep/X.Y.Z` 补开就行。
+1. **跑 Release Prep 工作流**(`.github/workflows/release-prep.yml`,手动触发,只填目标版本号)。它做这些:改 `dsh-mneme/package.json` 与 `package-lock.json` 的 `version`;刷新两个 README 的测试数(徽章 + 四种 `# N …测试` 注释形状);在 `[Unreleased]` 下面开新版本节,并把里面攒的条目整段搬进去(只有 `[Unreleased]` 本来是空的才留一条 `- (待填)`);给两份 README 的版本表插占位行、回填路线图状态格的代码**已随版本表一起删除**(两份 README 不再承载版本历史:入口页只回答「它是什么、怎么装、凭什么信」,每版改了什么由 CHANGELOG 与 GitHub Releases 承担);最后推 `release-prep/X.Y.Z` 分支并开 PR。**最后那一步吃仓库级开关**:Settings → Actions → General →「Allow GitHub Actions to create and approve pull requests」要打开,否则分支已经推上去了、PR 却开不出来(v0.9.4 于 2026-10-21 就是这样)。这时**别重跑工作流**——机器产出已经在那条分支上了,人直接 `gh pr create --base main --head release-prep/X.Y.Z` 补开就行。
2. **在这个 PR 里补上只有人能写的内容**:
- CHANGELOG 条目的措辞与取舍(`[Unreleased]` 攒的条目工作流已整段搬进新版本节,**不用手搬**;把它们分节并改写成外部读者能看懂的话)。小节末尾加 `### 贡献者 / Thanks` 汇总本版外部贡献者(`git log v上一版..HEAD --format='%an <%ae>' | sort -u`,非维护者逐条列「GitHub 用户名 + 一句话做了什么」,条目内也保留「感谢 @用户名」);无外部贡献者时省掉该小节。**条目一律用中性措辞**:不引用非公开的 issue 编号、不出现内部实验代号——它们在公开仓库里解析不出来,读者只会困惑。
- - 两个 README 里新占位行的主题描述;
- - 根 README 的「📰 最近更新」与「📰 Recent updates」那一段的头条(现在是标题式散文、不是表格;纯编辑内容,工作流从不改它)。
+ - ~~两个 README 里新占位行的主题描述~~、~~根 README 的「📰 最近更新」头条~~:两项都已不存在——版本表与 News 表在 2026-11-13 从两份 README 里删掉了,发版时不再需要在 README 里补位。
3. **只有跨 minor 发版时**:`SECURITY.md` 的支持版本表还写着上一个 minor 时,工作流只警告不失败。在同一份 PR 里把中英两张表向下挪一格(新 minor 进活跃行、上一版进尽力维护行,并同步改下面的停止维护通知);在那之前 `test/security-doc.test.js` 是红的——那正是它该红的时候。
4. **全量测试**:`npm test` 确认通过(动过 `lib/` 的话 `node scripts/check-sync.js` 也要过)。
5. **合并 PR**,然后打 tag:`git tag vX.Y.Z` → `git push origin vX.Y.Z`。到此为止,发布是自动的。
diff --git a/README.md b/README.md
index b5e8ca15..6b0333e0 100644
--- a/README.md
+++ b/README.md
@@ -2,27 +2,18 @@
-```
-$ whoami
-mneme — the memory that dreams
-
-$ cat /etc/mneme
-cross-session memory for DeepSeek Harness
-every memory keeps its provenance
-conflicts freeze until you decide
-the only copy stays on your machine
-
-$ ls ~/.dsh/memory
-memory.db constraints.md decisions.md documents.md history.md
-patterns.md pitfalls.md preferences.md projects.md
-rejected-solutions.md summary.md
-```
+
@@ -156,39 +141,22 @@ dsh plugin --profile web remove @modusensus/dsh-mneme
-```
-$ whoami
-mneme — the memory that dreams
-
-$ cat /etc/mneme
-cross-session memory for DeepSeek Harness
-every memory keeps its provenance
-conflicts freeze until you decide
-the only copy stays on your machine
-```
-
-### 01 · What it is
-
-*Every new chat, the AI meets you for the first time.*
+
dsh-mneme
-**dsh-mneme** gives [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) cross-session memory. Projects you discussed, preferences you mentioned, decisions you made — close the window, open it later, and it still remembers.
+
+ Long-term, cross-session memory for DeepSeek Harness
+ projects · preferences · decisions — still there after you close the window
+
-It is not a vector store. Memories live in a local SQLite file, with a Markdown mirror beside it: a file you can open, read, and edit, and your edits are not overwritten by the machine.
+> **Mneme** (Μνήμη) is the name of **Mnemosyne**, the goddess of memory — and of dreams. The name means the same thing here: remember by day, tidy up at night. `autoDream` does that while you are idle: hot memories stay hot, stale ones compress into summaries, old ones archive.
-### 02 · Why you can trust it
+## What it is for
-*The parts you never see decide whether it holds up over months.*
+Every new chat, the AI meets you for the first time. You explain the project again, restate your preferences, and re-argue a decision that was already settled.
-- **Every memory carries its provenance** — which conversation it came from, when it changed, who touched it. Consolidation leaves a receipt too: input snapshot, decision detail, result hash, so the same run can be replayed.
-- **Conflicts are not decided for you** — when two memories disagree, both are frozen and shown side by side, and you pick which one stays.
-- **Your edits win** — memories are local Markdown; hand-edited text is kept when it is merged back into the store.
-- **It works when you are idle** — frequently used memories stay hot, stale ones compress into summaries, old ones archive. The store gets leaner, not fatter.
-- **Isolation and offline** — memories can be scoped per agent and workspace; point the embedder at a local model and semantic search runs fully offline, with no API key.
-- **Delete the session ≠ delete the memory** — memories are cross-session knowledge; clearing a chat window leaves what was saved alone.
-
-> How does it compare with Zep / Mem0 / Letta / Hindsight? There is an 11-row table — storage, retrieval, write-time extraction, offline consolidation, conflicts, forgetting, scoping, auditability, multi-host, offline, license. **"—" does not mean "worse"**: [capability comparison](docs/COMPARISON.md) (Chinese).
+**dsh-mneme** gives [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) memory that outlives a conversation: when a chat ends, what is worth keeping is distilled into your machine, and the relevant pieces surface on their own next time. The memories are not a pile of floats locked in a vector store — they live in a local SQLite file with a Markdown mirror beside it: open it, read it, edit it, and your edits are not overwritten.
-### 03 · Install
+## Install
```bash
dsh plugin --profile web add @modusensus/dsh-mneme
@@ -218,7 +186,25 @@ dsh plugin --profile web remove @modusensus/dsh-mneme
The plugin directory goes away, the memories in `~/.dsh/memory` stay — delete that directory yourself if you want them gone. The three names are easy to mix up: [directories](dsh-mneme/docs/DIRECTORIES.md).
-### 04 · The panel
+## What you get
+
+- **Remembered automatically** — when a chat ends, the facts, preferences and decisions worth keeping are distilled; there is no "save to memory" step.
+- **Injected on demand** — the relevant handful enters context next time, ranked by heat, recency and importance rather than simply by date.
+- **Consolidated while idle** — `autoDream` merges duplicates, compresses what went stale, archives what expired. The store gets leaner, not fatter.
+- **Conflicts are not decided for you** — when two memories disagree, both are frozen and shown side by side, and you pick which one stays.
+- **Memories are files** — ten Markdown mirrors you can read, edit and export; hand-edited text wins when it is merged back.
+- **A panel** — record, browse, filter, search, edit, delete, all in the settings page.
+
+## Why you can trust it
+
+- **Every memory carries its provenance** — which conversation it came from, when it changed, who touched it. Consolidation leaves a receipt too: input snapshot, decision detail, result hash, so the same run can be replayed.
+- **Isolation and offline** — memories can be scoped per agent and workspace; point the embedder at a local model and semantic search runs fully offline, with no API key.
+- **Delete the session ≠ delete the memory** — memories are cross-session knowledge; clearing a chat window leaves what was saved alone.
+- **You can always take it over** — the store is a local SQLite file plus Markdown; no proprietary format holds your data.
+
+How does it compare with Zep / Mem0 / Letta / Hindsight? An 11-row table — storage, retrieval, write-time extraction, offline consolidation, conflicts, forgetting, scoping, auditability, multi-host, offline, license: [capability comparison](docs/COMPARISON.md) (Chinese). **"—" does not mean "worse"**.
+
+## The panel
@@ -235,55 +221,30 @@ The plugin directory goes away, the memories in `~/.dsh/memory` stay — delete
Vector index, LLM spend and consolidation activity at a glance.
-### 05 · Elsewhere
+## Elsewhere
| Way in | What you get |
|--------|--------------|
| **MCP** | A zero-dependency stdio server (standalone package `mneme-memory`); Claude Code / Cursor / Codex / OpenCode and any other MCP client can mount the six memory tools |
| **Standalone daemon** | `dsh-mneme-serve` keeps the store running: with DSH closed, scripts, web bridges and your own panels read and write the same memories |
+| **External API** | An HTTP surface, so another plugin can write memories in through the API |
| **Community adapters** | Such as [Mneme-Bridge](https://github.com/SternChiri/Mneme-Bridge), which brings the DeepSeek web chat into the same store |
-### 06 · Docs
-
-| To learn | Where |
-|----------|-------|
-| Features, install, config, architecture (full) | [dsh-mneme/README.md](dsh-mneme/README.md) |
-| Every configuration key | [Configuration](dsh-mneme/docs/CONFIGURATION.md) |
-| Which directory is which: `~/.dsh/memory` ≠ `~/.dsh/mneme` | [Directories](dsh-mneme/docs/DIRECTORIES.md) |
-| Head-to-head with Zep / Mem0 / Letta / Hindsight | [Comparison](docs/COMPARISON.md) |
-| Version history | [CHANGELOG](dsh-mneme/CHANGELOG.md) |
-
-### 07 · The road here
-
-*Rows are maintained by the release-prep script; the current one is updated with the tag.*
-
-| Version | Theme | Status |
-|---------|-------|--------|
-| **v0.14** | Images and moods became memories; two silent recall truncations removed (500-row lexical corpus, 200-row injection pool) | ✅ Released (v0.14.0) |
-| **v0.13** | Moods finally had somewhere to live: one-off occasions kept apart from stable tendencies | ✅ Released (v0.13.0) |
-| **v0.12** | Explicit pins (`memory_pin`) and an adaptive context budget | ✅ Released (v0.12.0) |
-| **v0.9 / v0.10** | Attribution and gates: distillation runs carry an identity, session-level no-trace gate, anti-sycophancy on the write path | ✅ Released |
-
----
-
## Privacy
-- **Local only.** A SQLite store plus an editable Markdown mirror, with no mneme server in the path.
-- **Memory is a file you can open.** Markdown, human-readable and hand-editable; your edits win when it merges back.
-- **No mneme API key of its own.** Consolidation and summarization use the host model you already configured (`autoDream` / `autoSummarize` are on by default).
-- **Fully offline if you want it** — point the host at a local model *and* clear `dreamProvider` / `dreamModel` plus `summarizeProvider` / `summarizeModel` (setting those routes talks straight to that provider, so switching the host alone will not help), or just turn both switches off.
-- **No telemetry, no analytics, no remote logging.**
-
-
-
-
-
-> *"Memory is not what you save from a conversation. It is what lets the next one continue."*
-
-
-
-© 2026 MODUSENSUS · MIT
-
-
-
-
\ No newline at end of file
+- **Local only.** A SQLite store plus a hand-editable Markdown mirror; no mneme server sits in between.
+- **Memories are files you can open.** Markdown, human readable and hand editable; your edits are kept when merged back.
+- **No mneme API key required.** Consolidation and summaries go through the host model you already configured (`autoDream` / `autoSummarize`, on by default).
+- **Fully offline consolidation** is possible: point the host at a local model, and clear `dreamProvider` / `dreamModel` and `summarizeProvider` / `summarizeModel` (if set, they bypass the host), or turn the two switches off.
+- **No telemetry, no analytics, no remote logs.**
+
+## Docs
+
+| Looking for | Where |
+|-------------|-------|
+| Full reference: every setting, CLI, external API, architecture | [dsh-mneme/README.md](dsh-mneme/README.md) |
+| Defaults and meaning of each config key | [docs/CONFIGURATION.md](dsh-mneme/docs/CONFIGURATION.md) |
+| How it compares with other memory systems | [docs/COMPARISON.md](docs/COMPARISON.md) |
+| What changed in each version | [CHANGELOG](dsh-mneme/CHANGELOG.md) · [Releases](https://github.com/slow-stack/mneme/releases) |
+| Which of the three directories is which | [docs/DIRECTORIES.md](dsh-mneme/docs/DIRECTORIES.md) |
+| Credits and related work | [full docs · credits](dsh-mneme/README.md#-致谢) (Chinese) |
diff --git a/dsh-mneme/README.md b/dsh-mneme/README.md
index 4109ed93..d7a81535 100644
--- a/dsh-mneme/README.md
+++ b/dsh-mneme/README.md
@@ -11,7 +11,7 @@ $ cat ~/.dsh/memory/preferences.md
-
+
@@ -292,30 +292,6 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
- **选择性注入**:query 向量可用时注入候选按主题相似度重排(`selectiveInjectEnabled` 可关);**搜索时语义去重**为激进选项(`searchSemanticDedup=true` 显式开启,近重复行 Rerank 前丢弃)
- **召回基准**(`scripts/benchmark-recall.js`):标准查询集驱动,计算 Recall@5 与 MRR,`legacy`(三特性全关)vs `fused`(默认配置)双跑对比
-## 📜 版本历史
-
-🧬 基因 → 🛡️ 审计加固 → 💤 睡眠巩固 → 🕸️ 召回融合与图谱 → 🌡️ 自进化 → 🔐 作用域隔离 → 🌐 MCP 生态 → 🧭 M.O. → 💭 情绪归因 → 📔 日记本
-
-当前版本的能力以本 README 正文的配置章节为准——**这张表只留一句话头条**,每一版具体怎么实现见 [CHANGELOG](CHANGELOG.md) 与 [Releases](https://github.com/slow-stack/mneme/releases)。
-
-| 版本 | 头条 |
-|------|------|
-| **v0.13.0**(2026-11-10) | 情绪终于有地方可放:`affective_context` 带情绪词与归因,三个独立会话才升成稳定倾向,不足就降级不丢弃 |
-| **v0.14.0**(2026-11-12) | 图像描述有了溯源表(摘要能回指哪张图),单次场合的情绪补上注入出口;召回侧两处静默截断(词法语料 500 行、注入池 200 行)改成全量 + 增量;后台 LLM 调用统一补 `sessionId` |
-| **v0.12.0**(2026-11-07) | pin 从「按类型猜」变成你亲手钉的事实;向量缓存从恒 0% 命中改为跟工作集自适应(1.8–2.4s → 370–424ms) |
-| **v0.11.0**(2026-11-05) | 记忆读得懂仓库:文件树与 import 图编译成模块划分与地志页 |
-| **v0.10.1**(2026-11-03) | 打开记忆详情不再崩掉整个宿主 |
-| **v0.10.0**(2026-11-02) | 记忆开始按「哪一轮」算账:蒸馏有了运行身份,溯源补上反方向 |
-| **v0.9.5**(2026-10-30) | 记忆能安全地递出去,也能整包收回来 |
-| **v0.9.4**(2026-10-21) | 三条链各自补上「最后一环」:主动整理、画像提取草稿、人格寻址收口 |
-| **v0.9.3**(2026-10-20) | M.O. 从「挖得出来」跑到「会被叫去挖」 |
-| **v0.9.2**(2026-10-19) | 让「看不见的失败」显形 |
-| **v0.9.1**(2026-10-18) | M.O. 从「看得见」跑到「跑得出账」 |
-| **v0.9.0**(2026-10-17) | M.O.(工作流)进入 beta:工作流住进项目仓库、可提交可 diff |
-| **v0.8.x** | 作用域隔离(agent / workspace 双维)、冲突队列人工裁决、stdio MCP server、图召回轴、冷启动 |
-
-> 早期版本中的实验性功能(Wiki-Link、tag 系统等)已在 v0.7.11 移除,详见 CHANGELOG 对应条目。
-
## 📦 安装
### 前置条件
@@ -775,7 +751,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
-test/ # 3446 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
+test/ # 3440 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
```
@@ -784,7 +760,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
-npm test # 运行 3446 个测试
+npm test # 运行 3440 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
@@ -839,4 +815,4 @@ MIT
## 🤝 参与之前
-行为守则:[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)。它管三件事——**署名归人**(模型不进作者行)、**责任落在签下作者行的人身上**、**发言权按证据分配,不按身份**。参与 issue / PR / 评审即受其约束;使用本插件不受约束,MIT 已经授权。
+行为守则:[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)。它管三件事——**署名归人**(模型不进作者行)、**责任落在签下作者行的人身上**、**发言权按证据分配,不按身份**。参与 issue / PR / 评审即受其约束;使用本插件不受约束,MIT 已经授权。
\ No newline at end of file
diff --git a/dsh-mneme/scripts/release-docs.mjs b/dsh-mneme/scripts/release-docs.mjs
index 82bbadbd..6cd827f7 100644
--- a/dsh-mneme/scripts/release-docs.mjs
+++ b/dsh-mneme/scripts/release-docs.mjs
@@ -1,166 +1,15 @@
-// 发布准备里「往两份 README 的版本表插占位行 + 回填路线图状态 + 核对 SECURITY.md 支持版本表」
-// 的单一实现。
+// 发布准备里「核对 SECURITY.md 支持版本表」的单一实现。
//
-// 为什么抽成模块而不是留在入口里:入口 scripts/release-prep.mjs 在仓库根、import 即执行,
-// 测不了。而这一步恰好静默失效过——根 README 的路线图表版本列写的是两段式 `**v0.9**`,
-// 而匹配规则只认三段式 `**v0.9.0**`,于是脚本对根 README 一个字节也没改,打印的却是
-// 「无版本表格行,跳过」:读起来像「这里不需要做」,实际是「这里没做」。v0.9.0 那次发布
-// 准备(a9f8e35)只有 5 个文件变动、根 README 的路线图与 News 一字未动,佐证这不是新问题。
+// 这里原本还有一件事:往两份 README 的版本表插占位行。2026-11-13 起两份 README
+// 都不再承载版本历史——根 README 是入口页,只回答「它是什么、怎么装、凭什么信」;
+// 每版改了什么由 CHANGELOG 与 GitHub Releases 承担。表格既然不在了,插行逻辑也一起
+// 删掉:留着它只会变成「拿到一份没有版本表的 README,打印跳过」,正是这个模块当初
+// 注释里记的那类静默失效(脚本对一个字节没改的 README 报告「无需改动」)。
//
-// 与 scripts/changelog-prep.mjs、scripts/test-count-sync.mjs 同一个理由:规则收在共用模块里
-// 才测得到,也才不会两处各写一份。
-import { detectEol } from "./changelog-prep.mjs";
+// 为什么这个模块要独立存在:入口 scripts/release-prep.mjs 在仓库根、import 即执行,
+// 测不了。核对逻辑收在共用模块里,测试才能拿真实 SECURITY.md 断言。
-// 版本号写在表格第一格才算「版本行」。三段式与两段式分开认:根 README 的路线图按 minor 排
-// (v0.9),子 README 的版本历史按 patch 排(v0.9.0),两者不能互相匹配——否则给根 README
-// 插一行 `**v0.9.1**` 会凭空造出一个不存在的 minor 条目。
-const THREE = /^\|\s*\*\*v(\d+\.\d+\.\d+)\*\*/;
-const TWO = /^\|\s*\*\*v(\d+\.\d+)\*\*/;
-
-const cellCount = (line) => line.split("|").filter((c) => c.trim()).length;
-const hasCJK = (s) => /[\u4e00-\u9fff]/.test(s);
-
-/** semver 比较,长度不同时缺位按 0 处理(两段式路由图对上三段式目标版本时要用)。 */
-function cmpVersion(a, b) {
- const pa = a.split(".").map(Number);
- const pb = b.split(".").map(Number);
- for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
- const x = pa[i] || 0;
- const y = pb[i] || 0;
- if (x !== y) return x < y ? -1 : 1;
- }
- return 0;
-}
-
-/**
- * 扫描文本里所有版本行,按写法分三段式 / 两段式,并按**表格块**分组。
- *
- * 为什么要分组:根 README 的中英路线图是两张独立的表,版本行一模一样。旧实现把所有版本行
- * 合并成一个数组,「表里是否已有目标版本」于是变成全文件判断——给中文表插了行之后,英文表
- * 就被判成「已存在」,永远补不上。分组之后每张表各自判断、各自插入。
- *
- * 分组规则:连续的版本行算一块,中间隔着表头/分隔行这类表格行也仍算同一块;遇到空行或正文
- * 才收块。导出是为了让测试能拿真实 README 断言「这些表仍被认得出」——正则一旦被改窄,
- * 用户看到的会是「无版本表格行,跳过」,而不是测试变红。
- */
-export function scanReleaseRows(text) {
- const three = [];
- const two = [];
- const blocks = [];
- let current = null;
- text.split(/\r?\n/).forEach((line, index) => {
- const m3 = THREE.exec(line);
- const m2 = m3 ? null : TWO.exec(line);
- if (m3 || m2) {
- const kind = m3 ? "three" : "two";
- const row = { index, ver: (m3 || m2)[1], kind };
- (kind === "three" ? three : two).push(row);
- if (current && current.next === index) current.rows.push(row);
- else {
- current = { rows: [row], next: index + 1 };
- blocks.push(current);
- }
- current.next = index + 1;
- return;
- }
- if (current && /^\s*\|/.test(line)) current.next = index + 1;
- else current = null;
- });
- return { three, two, blocks };
-}
-
-/** 按锚点行的列数与语言生成占位行。列数是「这张表有几列」的唯一判据:2 列=版本历史,3 列=路线图。 */
-function rowFor(anchorLine, version, date) {
- const n = cellCount(anchorLine);
- if (n <= 2) return date ? `| **v${version}**(${date}) | (待填) |` : `| **v${version}** | (待填) |`;
- if (n === 3) {
- return hasCJK(anchorLine)
- ? `| **v${version}** | (待填) | 🚧 准备中 |`
- : `| **v${version}** | TBD | 🚧 In prep |`;
- }
- return `| **v${version}** | 🚧 准备中 | (待填) | (待填) |`;
-}
-
-/**
- * 给一份 README 插版本占位行,并把当前 minor 那行路线图的状态回填成已发布。
- *
- * 按**表块**而不是整份文件处理:根 README 的中英路线图是两张独立的表,版本行一模一样。合并成
- * 一个数组判断「表里是否已有目标版本」,会让先插的中文表把英文表判成「已存在」。
- *
- * 返回 { text, actions },而不是布尔:「没插」有三种原因(已存在 / 没有版本表 / 表里已有更高
- * 版本),入口需要能如实说明是哪一种——旧实现在这里统一打印「跳过」,把「没做」说成了「不需要做」。
- *
- * @param {string} text README 全文
- * @param {string} version 目标版本,如 "0.9.1"
- * @param {string} date 如 "2026-10-18"
- */
-export function syncReadmeTables(text, version, date) {
- const [major, minor] = version.split(".");
- const minorKey = `${major}.${minor}`;
- const eol = detectEol(text);
- const lines = text.split(/\r?\n/);
- const { blocks, two } = scanReleaseRows(text);
-
- const insertAfter = new Map();
- const replaceAt = new Map();
- const actions = [];
-
- // 1) 逐张表块插占位行。**必须按块**:根 README 的中英路线图是两张独立的表、版本行一模一样,
- // 旧实现把全文件所有版本行当一个数组,「表里已有目标版本」于是退化成全文件判断——先插的
- // 中文表会让英文表被判成「已存在」,那张就永远补不上。
- // 锚点取块内「低于目标的最新版本行」,不插表尾:这类表常在末尾写一条 v0.8.x 这样的概括行。
- for (const block of blocks) {
- const isHistory = block.rows[0].kind === "three";
- const target = isHistory ? version : minorKey;
- if (block.rows.some((r) => r.ver === target)) continue;
- const lower = block.rows.filter((r) => cmpVersion(r.ver, target) < 0);
- if (!lower.length) continue;
- const anchor = lower.reduce((a, b) => (cmpVersion(b.ver, a.ver) > 0 ? b : a));
- const anchorLine = lines[anchor.index];
- let row = rowFor(anchorLine, target, date);
- // 路线图的状态格直接写已发布,而不是「🚧 准备中」:这一行随 PR 一起进 main,合并之后紧接着
- // 就是打 tag,所以它就是这次要发出去的那一版。若写成「🚧」,它不会自己变——下次跑发版准备
- // 时回填的是那一版的补丁号(0.10.0 已发布,路线图却写 v0.10.1),把首个发布版本记错。
- // 与下面第 2 步「不改写已发布行」不冲突:那条规则防的是抹掉「这个 minor 首个发布的是谁」。
- if (!isHistory && cellCount(anchorLine) === 3) {
- const released = hasCJK(anchorLine) ? `✅ 已发布(v${version})` : `✅ Released (v${version})`;
- const cells = row.split("|");
- cells[cells.length - 2] = ` ${released} `;
- row = cells.join("|");
- }
- insertAfter.set(anchor.index, row);
- actions.push({ kind: "insert", table: isHistory ? "版本历史" : "路线图", after: `v${anchor.ver}` });
- }
-
- // 2) 回填当前 minor 那行仍是「🚧 准备中」的状态格——上一次发版留下的占位,发版准备这一刻
- // 它就该变成已发布(版本号已定,是事实)。主题文字(第二格)始终不碰,那是人写的。
- //
- // 刻意不把「✅ 已发布(v0.9.0)」改写成「(v0.9.1)」:本脚本跑在打 tag 之前,改了就等于
- // 在 PR 里提前宣布一个还没发出去的版本;PR 若被放弃,那行立刻变成谎话。补丁号只由人在
- // 真正发出去之后按需要更新(与 v0.8 那行「至 v0.8.16」同一口径)。
- for (const row of two) {
- if (row.ver !== minorKey) continue;
- const line = lines[row.index];
- if (cellCount(line) < 3) continue;
- const cells = line.split("|");
- const old = cells[3].trim();
- if (!/🚧/.test(old)) continue;
- const next = hasCJK(line) ? `✅ 已发布(v${version})` : `✅ Released (v${version})`;
- cells[3] = ` ${next} `;
- replaceAt.set(row.index, cells.join("|"));
- actions.push({ kind: "rollup", table: "路线图", from: old, to: next });
- }
-
- if (!actions.length) return { text, actions };
- const out = [];
- lines.forEach((line, index) => {
- out.push(replaceAt.has(index) ? replaceAt.get(index) : line);
- if (insertAfter.has(index)) out.push(insertAfter.get(index));
- });
- return { text: out.join(eol), actions };
-}
-
-/** 取某一节(到下一个同级 ## 标题为止)。用节而不是全文 grep:历史审计节里也有版本号。 */
+/** 取某一节(到下一个同级 ## 标题为止)。用节而不是全文 grep:别处也有版本号。 */
export function sectionOf(text, heading) {
const start = text.indexOf(heading);
if (start < 0) return null;
diff --git a/dsh-mneme/test/release-docs.test.js b/dsh-mneme/test/release-docs.test.js
index b4db7ae1..4c7fd30d 100644
--- a/dsh-mneme/test/release-docs.test.js
+++ b/dsh-mneme/test/release-docs.test.js
@@ -1,18 +1,21 @@
-// 锁发布准备里「改文档表格」这一段的三件事,它们各自都静默失效过或随时会:
+// 锁发布准备里「改文档」这一段的两件事,它们各自都静默失效过或随时会:
//
-// 1. 根 README 的路线图版本列写的是两段式 **v0.9**,旧实现只认三段式 **v0.9.0**,于是
-// 对根 README 一个字节没改,打印的却是「无版本表格行,跳过」——把「没做」说成「不需要做」。
-// 2. 往 CRLF 文件里插一行而不显式保行尾,会把文件混成一半 CRLF 一半 LF(changelog-prep 踩过)。
-// 3. SECURITY.md 的支持版本表跨 minor 时必须由人挪格。脚本只核对;核对一旦退化成永真,
-// 0.10.0 就能带着「0.9.x 活跃开发中」的表格发出去——那是安全策略文档里的谎话。
+// 1. 版本历史不再写进 README(2026-11-13 决定)。根 README 是入口页,回答「它是什么、
+// 怎么装、凭什么信」;每版改了什么由 CHANGELOG 与 GitHub Releases 承担。原先根 README
+// 有两张路线图、子 README 有一张版本历史,发版脚本靠往表里插占位行维持——而那条路
+// 恰好静默失效过:根 README 的版本列是两段式 **v0.9**,旧实现只认三段式 **v0.9.0**,
+// 于是对一个字节没改的 README 报告「无版本表格行,跳过」。表格删掉,插行逻辑一起删;
+// 这里反向锁住「别把表加回来」,否则它又会变成发版脚本的依赖。
//
-// 前两类直接读真实 README 断言(正则被改窄时,用户会静默看到「跳过」,而测试必须变红)。
+// 2. SECURITY.md 的支持版本表跨 minor 时必须由人挪格。脚本只核对;核对一旦退化成永真,
+// 0.10.0 就能带着「0.9.x 活跃开发中」的表格发出去——那是安全策略文档里的谎话。
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
-import { checkSupportTable, scanReleaseRows, syncReadmeTables } from "../scripts/release-docs.mjs";
+import * as releaseDocs from "../scripts/release-docs.mjs";
+import { checkSupportTable } from "../scripts/release-docs.mjs";
const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
const repoRoot = path.dirname(pkgRoot);
@@ -21,99 +24,16 @@ const pkgReadme = readFileSync(path.join(pkgRoot, "README.md"), "utf8");
const securityDoc = readFileSync(path.join(repoRoot, "SECURITY.md"), "utf8");
const { version } = JSON.parse(readFileSync(path.join(pkgRoot, "package.json"), "utf8"));
-const CRLF = "\r\n";
-const crlf = (...lines) => lines.join(CRLF) + CRLF;
-
-test("两份真实 README 的版本表都还认得出(根按 minor、子按 patch)", () => {
- const root = scanReleaseRows(rootReadme);
- assert.ok(root.two.length >= 3, `根 README 的路线图应解析出至少 3 行两段式版本行,实际 ${root.two.length}`);
- assert.equal(root.three.length, 0,
- "根 README 的路线图按 minor 排,不该出现三段式版本行——出现了说明表格形状变了,插入位置需要重算");
- const sub = scanReleaseRows(pkgReadme);
- assert.ok(sub.three.length >= 1, `子 README 的版本历史应解析出至少 1 行三段式版本行,实际 ${sub.three.length}`);
-});
-
-test("根 README 的两段式路线图能插进新 minor 占位行(旧实现零命中)", () => {
- const sample = crlf(
- "| 版本 | 主题 | 状态 |",
- "|------|------|------|",
- "| **v0.8** | 生态化 | ✅ 已发布(至 v0.8.16) |",
- "| **v0.9** | 手法 beta | ✅ 已发布(v0.9.0) |",
- );
- const { text, actions } = syncReadmeTables(sample, "0.10.0", "2026-11-01");
- assert.equal(actions.filter((a) => a.kind === "insert").length, 1, "应插入且只插入一行");
- assert.match(text, /\| \*\*v0\.10\*\* \| (待填) \| ✅ 已发布(v0\.10\.0) \|/,
- "占位行按 minor 写 v0.10,状态写这次要发出去的完整版本");
- // 插在 v0.9 之后,而不是表尾——表尾将来会有更高的计划行
- assert.ok(text.indexOf("**v0.10**") > text.indexOf("**v0.9**"), "占位行应排在 v0.9 之后");
-});
-
-test("真实根 README 的中英两张路线图各插一行(同一版本号,不能只插一张)", () => {
- // 两张表版本行完全一样,旧实现把全文件版本行合成一个数组判断「已存在」,
- // 于是先插的中文表会让英文表被跳过——英文读者永远等不到那一行。
- //
- // 目标版本**不能写死**:写死当次要发的版本,这条用例在 release-prep 分支上必红——那一行
- // 恰好已经由发版准备脚本插进去了,「各插一行」于是退化成零插入。取比现存的最高 major 再高一档,
- // 断言就与「这次发的是哪一版」彻底无关,也不会在下一个 minor 上重新长成定时炸弹。
- const maxMajor = Math.max(...scanReleaseRows(rootReadme).two.map((r) => Number(r.ver.split(".")[0])));
- const target = `${maxMajor + 1}.0`;
- const { actions } = syncReadmeTables(rootReadme, target, "2026-11-01");
- const inserts = actions.filter((a) => a.kind === "insert");
- assert.equal(inserts.length, 2, "中英路线图各一行,实际:" + JSON.stringify(actions));
-});
-
-test("子 README 的版本历史插占位行时写完整 patch 号并带日期", () => {
- const sample = crlf(
- "| 版本 | 主题 |",
- "|------|------|",
- "| **v0.9.0**(2026-10-17) | 手法 beta |",
- "| **v0.8.x** | 作用域隔离 |",
- );
- const { text } = syncReadmeTables(sample, "0.9.1", "2026-10-18");
- assert.match(text, /\| \*\*v0\.9\.1\*\*(2026-10-18) \| (待填) \|/);
- assert.ok(text.indexOf("**v0.9.1**") < text.indexOf("**v0.8.x**"),
- "占位行要排在低于它的最新版本之后、概括行之前");
-});
-
-test("路线图里上一版留下的「🚧 准备中」在发版准备时回填成已发布", () => {
- const sample = crlf(
- "| 版本 | 主题 | 状态 |",
- "|------|------|------|",
- "| **v0.9** | 手法 beta | 🚧 准备中 |",
- );
- const { text } = syncReadmeTables(sample, "0.9.0", "2026-10-17");
- assert.match(text, /\| \*\*v0\.9\*\* \| 手法 beta \| ✅ 已发布(v0\.9\.0) \|/);
- // 已经写着已发布的行不动:脚本跑在打 tag 之前,改写补丁号等于提前宣布一个还没发出去的版本。
- const already = crlf(
- "| 版本 | 主题 | 状态 |",
- "|------|------|------|",
- "| **v0.9** | 手法 beta | ✅ 已发布(v0.9.0) |",
- );
- assert.equal(syncReadmeTables(already, "0.9.1", "2026-10-18").text, already,
- "已发布的补丁号由人在真正发出去之后更新,脚本不擅自提前改");
-});
-
-test("插入占位行不改变行尾风格(CRLF 文件不会混进裸 LF)", () => {
- const sample = crlf(
- "| 版本 | 主题 |",
- "|------|------|",
- "| **v0.9.0**(2026-10-17) | 手法 beta |",
- );
- const { text } = syncReadmeTables(sample, "0.9.1", "2026-10-18");
- const bareLf = (text.match(/(? {
- const sample = crlf(
- "| 版本 | 主题 |",
- "|------|------|",
- "| **v0.9.0**(2026-10-17) | 手法 beta |",
- );
- const once = syncReadmeTables(sample, "0.9.1", "2026-10-18").text;
- const twice = syncReadmeTables(once, "0.9.1", "2026-10-18");
- assert.equal(twice.text, once);
- assert.deepEqual(twice.actions, [], "第二次应报告零变更,而不是再插一行");
+test("两份 README 都不再承载版本表(版本历史在 CHANGELOG 与 Releases)", () => {
+ const versionRow = /^\|\s*\*\*v\d+\.\d+/m;
+ for (const [label, text] of [["根 README", rootReadme], ["子 README", pkgReadme]]) {
+ assert.equal(versionRow.test(text), false,
+ `${label} 里又出现了版本行(| **v0.x**):版本历史归 CHANGELOG 与 Releases,` +
+ "加回 README 会让发版脚本重新依赖一个没人读、又常年漂的表格");
+ }
+ assert.equal(pkgReadme.includes("## 📜 版本历史"), false, "子 README 的版本历史一节已删除,不要再加回来");
+ assert.equal("syncReadmeTables" in releaseDocs, false,
+ "syncReadmeTables 已随版本表一起删除:留着它只会对没有表格的 README 报告「跳过」");
});
test("SECURITY.md 支持版本表:同 minor 通过,跨 minor 如实报出中英两处", () => {
diff --git a/scripts/release-prep.mjs b/scripts/release-prep.mjs
index f86d2fe4..3e07cf1e 100644
--- a/scripts/release-prep.mjs
+++ b/scripts/release-prep.mjs
@@ -1,11 +1,13 @@
#!/usr/bin/env node
// 发布准备自动化:bump 版本号(dsh-mneme/package.json + package-lock 两处)+ 同步双 README 测试徽章
-// + CHANGELOG 顶部空节 + 双 README 版本表占位行与路线图状态回填。
+// + CHANGELOG 顶部空节。
//
// 分工(本脚本最重要的约定):**机械的归机器,判断的归人**。
-// - 机器:版本号、测试数、CHANGELOG 空节、占位行、路线图状态格("已发布 vX.Y.Z" 是事实)
-// - 人:CHANGELOG 与版本表里的**主题描述**、根 README 的 News 头条、SECURITY.md 支持版本表的
-// 跨 minor 挪格(那是维护策略的判断,写错就是「0.9.x 已停止支持」这种真事故)
+// - 机器:版本号、测试数、CHANGELOG 空节(把 [Unreleased] 攒的条目整段搬进去)
+// - 人:CHANGELOG 条目的**措辞与取舍**、SECURITY.md 支持版本表的跨 minor 挪格
+// (那是维护策略的判断,写错就是「0.9.x 已停止支持」这种真事故)
+// 版本历史不进 README(2026-11-13):两份 README 都不再放版本表,每版改了什么由 CHANGELOG
+// 与 GitHub Releases 承担。原先给表插占位行的 syncReadmeTables 已随表格删除。
// 所以 SECURITY.md 只核对不改写:表没跟上就硬失败、PR 不生成,而不是替人写一句可能很危险的
// 版本声明。核对逻辑在 dsh-mneme/scripts/release-docs.mjs,与测试共用同一份。
//
@@ -15,7 +17,7 @@
import fs from "node:fs";
import { applyTestCount } from "../dsh-mneme/scripts/test-count-sync.mjs";
import { insertReleaseSection } from "../dsh-mneme/scripts/changelog-prep.mjs";
-import { checkSupportTable, scanReleaseRows, syncReadmeTables } from "../dsh-mneme/scripts/release-docs.mjs";
+import { checkSupportTable } from "../dsh-mneme/scripts/release-docs.mjs";
const version = (process.argv[2] || "").replace(/^v/, "");
const testCount = process.argv[3];
@@ -117,52 +119,18 @@ if (testCount && /^\d+$/.test(testCount)) {
}
}
-// ── 4. 双 README 的版本表占位行 + 路线图状态回填(幂等)──
-// 旧实现只认三段式 **v0.9.0**,而根 README 的路线图写的是两段式 **v0.9**:于是根 README
-// 一个字节没改,打印的却是「无版本表格行,跳过」——把「没做」说成了「不需要做」。
-// 现在两种写法分开认,且每张表单独报告做了什么、或为什么没做。
-let touched = 0;
-for (const f of READMES) {
- const t = fs.readFileSync(f, "utf8");
- if (isPrerelease) {
- // 版本表按 minor/patch 排条目,预发布版不是一次版本发布:给它插行会凭空造出
- // 「**v0.14.0-beta.1**」这样一个版本历史条目,而正式的 0.14.0 还没发。
- console.log("- " + f + " 版本表不随预发布版 " + version + " 插行(等正式版发布时再补)");
- continue;
- }
- const r = syncReadmeTables(t, version, date);
- if (!r.actions.length) {
- const scanned = scanReleaseRows(t);
- if (!scanned.three.length && !scanned.two.length) {
- console.log("- " + f + " 没有版本表格(既无三段式也无两段式版本行),跳过");
- } else {
- console.log("- " + f + " 版本表已是最新,跳过");
- }
- continue;
- }
- if (r.text === t) {
- // 只在真有变更时才返回新文本;相等说明是逻辑 bug,宁可硬失败也不静默发布。
- console.error("✗ " + f + " 报告了 " + r.actions.length + " 项变更但文本未变——这是脚本 bug,请勿合并");
- process.exit(1);
- }
- fs.writeFileSync(f, r.text);
- touched += 1;
- for (const a of r.actions) {
- if (a.kind === "insert") console.log("✓ " + f + " " + a.table + ":在 v" + a.after.replace(/^v/, "") + " 之后插入占位行");
- else console.log("✓ " + f + " " + a.table + ":状态 " + a.from + " → " + a.to);
- }
-}
-
-// 机械部分一句话收口:让人一眼看出还剩下什么要自己写。
-// 机器能做的到此为止,剩下三件都是判断/编辑,写进同一个清单比散在三处提示里可靠。
+// ── 4. 版本历史不进 README ──
+// 两份 README 都不再放版本表(2026-11-13):入口页只回答「它是什么、怎么装、凭什么信」,
+// 每版改了什么由 CHANGELOG 与 GitHub Releases 承担。原先往表里插占位行的
+// syncReadmeTables 已随表格一起删除——留着它只会对一份没有表格的 README 报告「跳过」,
+// 正是它自己注释里记的那类静默失效。
+console.log("- 两份 README 均不含版本表(版本历史在 CHANGELOG 与 Releases)");
console.log("");
-console.log(touched ? "完成(" + touched + " 份 README 有改动)。还需要人写的:" : "完成(README 无需改动)。还需要人写的:");
+console.log("完成。还需要人写的:");
console.log(" 1. dsh-mneme/CHANGELOG.md 新节条目的**措辞与取舍**([Unreleased] 攒的已整段搬过去,不用手搬)");
-console.log(" 2. 双 README 版本表占位行里的主题描述" + (isPrerelease ? "(本版是预发布,表不插行,这条留给正式版)" : ""));
-console.log(" 3. 根 README 的「📰 最近更新」与「📰 Recent updates」段落的头条(现在是标题式散文,已不是表格;纯编辑内容,脚本不碰)");
if (supportTableProblems.length) {
console.log("");
console.log("⚠ 还有一件必须先做,否则这个 PR 的 CI 会是红的:");
console.log(" SECURITY.md 的支持版本表要挪到 " + version.split(".").slice(0, 2).join(".") + ".x");
console.log(" 中英两张表 + 下面那段停止维护通知一起改(旧版本降一格、新版本进活跃行)。");
-}
+}
\ No newline at end of file
From 9464577647969a691b71de02bca9b02bb774b37d Mon Sep 17 00:00:00 2001
From: modusensus
Date: Fri, 13 Nov 2026 02:12:43 +0800
Subject: [PATCH 2/4] =?UTF-8?q?docs(comparison):=20=E8=A1=A5=200.14.0=20?=
=?UTF-8?q?=E7=9A=84=E4=B8=89=E8=A1=8C=EF=BC=8C=E7=AB=9E=E5=93=81=E6=A0=BC?=
=?UTF-8?q?=E5=85=88=E7=95=99=E7=99=BD=E4=B8=8D=E5=86=92=E5=85=85=E7=BB=93?=
=?UTF-8?q?=E8=AE=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
dsh-mneme 一侧补上 0.14.0 才有的三项:情感记忆(occasion 档情绪可进注入、
达阈值的升 pattern)、图像描述与溯源(image_sources:记忆 ← 窗口 ← 图)、
第三方写入(HTTP API + 常驻 daemon)。检索行同步到全量语料增量索引。
竞品侧新增三行的格一律写「未核对」,不用「—」冒充「没有」;四家许可证
于今日重核 LICENSE 实文,与 10-16 一致。其余各格沿用 10-16 结果,复核进行中。
---
docs/COMPARISON.md | 28 ++++++++++++++++++++--------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md
index 7a0122a5..aeec4f34 100644
--- a/docs/COMPARISON.md
+++ b/docs/COMPARISON.md
@@ -1,17 +1,22 @@
# dsh-mneme 与其他记忆工具的能力对比 / Capability Comparison
-> 更新于 2026-10-16。对比基于各项目公开文档与源码(出处见文末),只比较**能力结构**,不回答「谁更准」——效果类问题请以各系统在公开基准上的实测为准。dsh-mneme 是为 DSH 设计的记忆插件(SQLite + Markdown 镜像、完全离线),同时自带独立服务:stdio MCP server(npm 包 `mneme-memory`,任意 MCP 客户端可挂载)与不开 DSH 的常驻 daemon(`dsh-mneme-serve`),第三方集成读写同一份记忆库;其余为通用记忆服务/库,定位不同,读表时请带着这个前提。
+> 更新于 2026-11-13(上一版 2026-10-16)。对比基于各项目公开文档与源码(出处见文末),只比较**能力结构**,不回答「谁更准」——效果类问题请以各系统在公开基准上的实测为准。dsh-mneme 是为 DSH 设计的记忆插件(SQLite + Markdown 镜像、完全离线),同时自带独立服务:stdio MCP server(npm 包 `mneme-memory`,任意 MCP 客户端可挂载)与不开 DSH 的常驻 daemon(`dsh-mneme-serve`),第三方集成读写同一份记忆库;其余为通用记忆服务/库,定位不同,读表时请带着这个前提。
+>
+> **本版做了什么**:补上 dsh-mneme 0.14.0 才有的三项(情感记忆、图像描述与溯源、第三方写入),检索行同步到 0.14.0 的全量语料增量索引;四家的许可证于 2026-11-13 重核 LICENSE 实文。本版新增的三行(情感记忆 / 图像 / 第三方写入)里竞品侧还没逐列查过,一律标「未核对」;其余各格沿用 2026-10-16 的核对结果,正在复核中。
| 能力 | dsh-mneme | Zep / Graphiti | Mem0 | Letta (MemGPT) | Hindsight |
|---|---|---|---|---|---|
| 存储底座 | SQLite 主库 + 人类可编辑 Markdown 镜像 | 时序知识图谱(Neo4j / FalkorDB / Neptune) | 向量 + 图混合的抽取式记忆 | 自编辑记忆块(核心内存) | 四层仿生结构(事实/经历/观察/心智模型) |
-| 检索 | BM25 + 图谱 + 会话热记忆,v0.8.4 起实体图第四路(RRF 融合) | 时序图遍历 + 语义 | 语义 + 关键词 + 图 | 递归检索 + 自编辑 | 四路并行(语义/BM25/图/时间)+ RRF + 重排 |
+| 检索 | BM25(0.14.0 起全量语料 + 增量索引)+ 图谱 + 会话热记忆,实体图第四路(RRF 融合) | 时序图遍历 + 语义 | 语义 + 关键词 + 图 | 递归检索 + 自编辑 | 四路并行(语义/BM25/图/时间)+ RRF + 重排 |
| 写入时 LLM 抽取 | 否(原文直存,巩固期才用 LLM) | 是(抽实体/关系) | 是(抽事实) | 部分 | 是(retain 抽实体/时间) |
+| 情感记忆 | 情绪归因写成关系边(`affective_context`);跨会话达阈值的升为 pattern 进注入,单次场合的情绪经 `affectiveInjectEnabled` 附在注入行上(默认关) | 未核对 | 未核对 | 未核对 | 未核对 |
+| 图像 / 多模态 | 描述档先让能看图的模型给图写一句、并进蒸馏(`imageDescribeEnabled`);`image_sources` 让记忆回指来源窗口与图(`imageMemoryEnabled`)。不存图字节、不做 OCR | 未核对 | 未核对 | 未核对 | 未核对 |
+| 第三方写入 | HTTP API + 常驻 daemon(`dsh-mneme-serve`),别的插件可直接写进同一份库;stdio MCP 另开六个工具 | 未核对 | 未核对 | 未核对 | 未核对 |
| 离线巩固 | autoDream(快写慢巩固) | — | — | sleep-time compute(学术同源) | 观察沉淀 |
| 冲突处理 | 矛盾冻结 → 人工裁决队列 | 时序失效(边失效标注) | ADD/UPDATE 等操作 | 无专门机制 | 证据回传更新观察 |
| 遗忘 | heat 热度衰减降权 + 显式 archive/delete,无静默淘汰 | 边失效 | ADD/UPDATE/DELETE 操作 | 无 | — |
| 作用域隔离 | agent / workspace 双维 + opt-in 硬过滤 | 用户/会话分 graph | user/agent/run 多层 | 多 agent | memory bank 按 bank |
-| 可审计性 | sha256 审计链 + 幂等重放 | — | — | — | — |
+| 可审计性 | sha256 审计链 + 幂等重放;图像链另存「记忆 ← 窗口 ← 图」的溯源行 | — | — | — | — |
| 多宿主接入 | DSH 内直用 + stdio MCP(`mneme-memory`,任意 MCP 客户端)+ HTTP daemon(`dsh-mneme-serve`)+ 网页端桥接([Mneme-Bridge](https://github.com/SternChiri/Mneme-Bridge)) | 托管 API + SDK | SDK / API | API / server | SDK / API / MCP |
| 完全离线 | ✅(可配本地嵌入模型) | 需 LLM/嵌入服务 | 需 LLM | 需 LLM | 需 LLM |
| 许可证 | MIT | Apache-2.0 | Apache-2.0 | Apache-2.0 | MIT |
@@ -19,23 +24,29 @@
**如何读这张表**
- 「—」不等于「差」:通用服务面向多租户在线场景,把裁决权交给调用方也是一种合理设计;dsh-mneme 的差异点在于**把记忆管理做成有账本、可人工复核、可回放的流水线**。
+- **「未核对」不等于「没有」**:那几格是还没逐条查过,不是核实过为空。这张表宁可留白,也不拿没查过的结论去垫自己。
- 效果类问题(检索准不准、巩固有没有用)看各系统的公开基准与本项目自己的评测,本表不回答跑分。
-- 许可证为 2026-10-16 各仓库 LICENSE 文件实文核对结果。
+- 许可证为 2026-11-13 各仓库 LICENSE 实文核对结果。
## Capability Comparison (English)
-> Updated 2026-10-16. Based on public docs and source code (sources below). This compares **capability structure**, not accuracy — effect questions belong to public benchmarks and our own evals. dsh-mneme is a memory plugin designed for DSH (SQLite + human-editable Markdown mirror, fully offline) that also ships standalone: a zero-dependency stdio MCP server (npm package `mneme-memory`, mountable by any MCP client) and a DSH-free daemon (`dsh-mneme-serve`), so third-party integrations read and write the same library; the others are general-purpose memory services/libraries.
+> Updated 2026-11-13 (previous version 2026-10-16). Based on public docs and source code (sources below). This compares **capability structure**, not accuracy — effect questions belong to public benchmarks and our own evals. dsh-mneme is a memory plugin designed for DSH (SQLite + human-editable Markdown mirror, fully offline) that also ships standalone: a zero-dependency stdio MCP server (npm package `mneme-memory`, mountable by any MCP client) and a DSH-free daemon (`dsh-mneme-serve`), so third-party integrations read and write the same library; the others are general-purpose memory services/libraries.
+>
+> **What changed here**: it now covers three things that only exist since 0.14.0 (affective memory, image description with provenance, third-party writes) and updates the retrieval row to the full-corpus incremental index. The four licenses were re-verified against their LICENSE files on 2026-11-13. The competitor cells of the three new rows (affective memory / images / third-party writes) have not been checked column by column yet and say "not verified"; the remaining cells carry the 2026-10-16 results and are being re-checked.
| Capability | dsh-mneme | Zep / Graphiti | Mem0 | Letta (MemGPT) | Hindsight |
|---|---|---|---|---|---|
| Storage | SQLite + human-editable Markdown mirror | Temporal knowledge graph (Neo4j / FalkorDB / Neptune) | Vector + graph, extraction-based | Self-editing core-memory blocks | 4-layer biomimetic (facts/experiences/observations/mental models) |
-| Retrieval | BM25 + graph + hot session memory; entity-graph 4th path since v0.8.4 (RRF) | Temporal graph traversal + semantic | Semantic + keyword + graph | Recursive search + edits | 4 parallel strategies + RRF + rerank |
+| Retrieval | BM25 (full corpus + incremental index since 0.14.0) + graph + hot session memory; entity-graph 4th path (RRF) | Temporal graph traversal + semantic | Semantic + keyword + graph | Recursive search + edits | 4 parallel strategies + RRF + rerank |
| LLM extraction at write | No (raw text stored; LLM only during consolidation) | Yes (entities/relations) | Yes (facts) | Partial | Yes (retain extracts entities/time) |
+| Affective memory | Emotion attributions stored as relation edges (`affective_context`); recurring ones are promoted to injected patterns, one-off occasions attach via `affectiveInjectEnabled` (off by default) | not verified | not verified | not verified | not verified |
+| Images / multimodal | A description pass has a vision model write one line per image into distillation (`imageDescribeEnabled`); `image_sources` lets each memory point back to the window and image it came from (`imageMemoryEnabled`). No image bytes stored, no OCR | not verified | not verified | not verified | not verified |
+| Third-party writes | HTTP API + standing daemon (`dsh-mneme-serve`) so another plugin can write into the same store; stdio MCP adds six tools | not verified | not verified | not verified | not verified |
| Offline consolidation | autoDream (fast write, slow consolidate) | — | — | sleep-time compute (same academic thread) | Observation layer |
| Conflict handling | Freeze → human review queue | Temporal edge invalidation | ADD/UPDATE ops | None specific | Evidence-fed observation updates |
| Forgetting | Heat decay (down-weight) + explicit archive/delete; no silent eviction | Edge invalidation | ADD/UPDATE/DELETE ops | None | — |
| Scoping | agent + workspace dual-dim, opt-in hard filter | per-user/session graphs | user/agent/run layers | multi-agent | memory banks |
-| Auditability | sha256 audit chain + idempotent replay | — | — | — | — |
+| Auditability | sha256 audit chain + idempotent replay; the image path also records "memory ← window ← image" provenance rows | — | — | — | — |
| Multi-host access | in-DSH + stdio MCP (`mneme-memory`, any MCP client) + HTTP daemon (`dsh-mneme-serve`) + web-client bridge ([Mneme-Bridge](https://github.com/SternChiri/Mneme-Bridge)) | hosted API + SDKs | SDK / API | API / server | SDK / API / MCP |
| Fully offline | ✅ (local embedding model supported) | needs LLM/embedding service | needs LLM | needs LLM | needs LLM |
| License | MIT | Apache-2.0 | Apache-2.0 | Apache-2.0 | MIT |
@@ -43,8 +54,9 @@
**How to read this table**
- "—" does not mean "worse": general services target online multi-tenant settings where the caller owns adjudication; dsh-mneme's differentiator is turning memory management into an **accounted, human-reviewable, replayable pipeline**.
+- **"not verified" does not mean "absent"**: those cells have not been checked line by line yet. The table would rather leave a blank than use an unchecked claim to prop itself up.
- Accuracy questions belong to public benchmarks and this project's own evals; this table does not answer them.
-- Licenses verified against each repository's LICENSE file on 2026-10-16.
+- Licenses verified against each repository's LICENSE file on 2026-11-13.
## Sources
From 568ddfd37178e3741cc1ec02411b42ad3e475fad Mon Sep 17 00:00:00 2001
From: modusensus
Date: Fri, 13 Nov 2026 02:16:38 +0800
Subject: [PATCH 3/4] docs(comparison): fold in the 2026-11-13 verification
pass
Nine cells corrected after re-reading each project's current source and
docs; four of them had recorded "we did not find it" as "they do not have
it". Mem0 keeps a history table and a history endpoint, Graphiti exposes
episode provenance and searches BM25 as well, Letta tracks all context in
MemFS and calls its background pass dreaming, Hindsight stores on
PostgreSQL with pgvector and ships observations with supporting evidence.
The Zep / Graphiti column now says which half it follows: the open-source
framework, without crediting it with Zep's hosted layer. The three rows
added for 0.14.0 stay marked as unverified on the competitor side rather
than being filled with guesses.
---
docs/COMPARISON.md | 57 ++++++++++++++++++++++++++++------------------
1 file changed, 35 insertions(+), 22 deletions(-)
diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md
index aeec4f34..7c2b4ef0 100644
--- a/docs/COMPARISON.md
+++ b/docs/COMPARISON.md
@@ -2,21 +2,23 @@
> 更新于 2026-11-13(上一版 2026-10-16)。对比基于各项目公开文档与源码(出处见文末),只比较**能力结构**,不回答「谁更准」——效果类问题请以各系统在公开基准上的实测为准。dsh-mneme 是为 DSH 设计的记忆插件(SQLite + Markdown 镜像、完全离线),同时自带独立服务:stdio MCP server(npm 包 `mneme-memory`,任意 MCP 客户端可挂载)与不开 DSH 的常驻 daemon(`dsh-mneme-serve`),第三方集成读写同一份记忆库;其余为通用记忆服务/库,定位不同,读表时请带着这个前提。
>
-> **本版做了什么**:补上 dsh-mneme 0.14.0 才有的三项(情感记忆、图像描述与溯源、第三方写入),检索行同步到 0.14.0 的全量语料增量索引;四家的许可证于 2026-11-13 重核 LICENSE 实文。本版新增的三行(情感记忆 / 图像 / 第三方写入)里竞品侧还没逐列查过,一律标「未核对」;其余各格沿用 2026-10-16 的核对结果,正在复核中。
+> **本版做了什么**:补上 dsh-mneme 0.14.0 才有的三项(情感记忆、图像描述与溯源、第三方写入),检索行同步到全量语料增量索引;竞品侧逐列复核,改掉 9 处(其中 4 处原先把「我们没找到」写成了「对方没有」)。许可证于 2026-11-13 重核四家 LICENSE 实文,与上一版一致。
+>
+> **一列的前提**:Zep 是托管的企业上下文层(自研 Konig 图库),Graphiti 是它的开源框架(自带 Neo4j / FalkorDB / Neptune)。下面这列以**开源 Graphiti 的实现**为准,不拿 Zep 的托管能力给 Graphiti 加分。
-| 能力 | dsh-mneme | Zep / Graphiti | Mem0 | Letta (MemGPT) | Hindsight |
+| 能力 | dsh-mneme | Graphiti(Zep 开源内核) | Mem0 | Letta (MemGPT) | Hindsight |
|---|---|---|---|---|---|
-| 存储底座 | SQLite 主库 + 人类可编辑 Markdown 镜像 | 时序知识图谱(Neo4j / FalkorDB / Neptune) | 向量 + 图混合的抽取式记忆 | 自编辑记忆块(核心内存) | 四层仿生结构(事实/经历/观察/心智模型) |
-| 检索 | BM25(0.14.0 起全量语料 + 增量索引)+ 图谱 + 会话热记忆,实体图第四路(RRF 融合) | 时序图遍历 + 语义 | 语义 + 关键词 + 图 | 递归检索 + 自编辑 | 四路并行(语义/BM25/图/时间)+ RRF + 重排 |
-| 写入时 LLM 抽取 | 否(原文直存,巩固期才用 LLM) | 是(抽实体/关系) | 是(抽事实) | 部分 | 是(retain 抽实体/时间) |
+| 存储底座 | SQLite 主库 + 人类可编辑 Markdown 镜像 | 时序知识图谱(Neo4j / FalkorDB / Neptune) | 向量 + 图混合的抽取式记忆 | 自编辑记忆块(core memory)+ MemFS 文件层 | PostgreSQL + pgvector(或 Oracle 23ai);四层(事实/经历/观察/心智模型)是记忆类型,不是底座 |
+| 检索 | BM25(0.14.0 起全量语料 + 增量索引)+ 图谱 + 会话热记忆,实体图第四路(RRF 融合) | 语义 + 关键词 BM25 + 时序图遍历 | 语义 + 关键词 + 图 | 递归检索 + 自编辑 | 四路并行(语义/BM25/图/时间)+ RRF + 重排 |
+| 写入时 LLM 抽取 | 否(原文直存,巩固期才用 LLM) | 是(抽实体/关系) | 是(抽事实,Add-only:只累积、不覆盖) | 部分 | 是(retain 抽实体/时间) |
| 情感记忆 | 情绪归因写成关系边(`affective_context`);跨会话达阈值的升为 pattern 进注入,单次场合的情绪经 `affectiveInjectEnabled` 附在注入行上(默认关) | 未核对 | 未核对 | 未核对 | 未核对 |
-| 图像 / 多模态 | 描述档先让能看图的模型给图写一句、并进蒸馏(`imageDescribeEnabled`);`image_sources` 让记忆回指来源窗口与图(`imageMemoryEnabled`)。不存图字节、不做 OCR | 未核对 | 未核对 | 未核对 | 未核对 |
+| 图像 / 多模态 | 描述档让能看图的模型给每张图写一句、并进蒸馏(`imageDescribeEnabled`);`image_sources` 让记忆回指来源窗口与图(`imageMemoryEnabled`)。不存图字节、不做 OCR | 未核对 | 未核对 | 未核对 | 未核对 |
| 第三方写入 | HTTP API + 常驻 daemon(`dsh-mneme-serve`),别的插件可直接写进同一份库;stdio MCP 另开六个工具 | 未核对 | 未核对 | 未核对 | 未核对 |
-| 离线巩固 | autoDream(快写慢巩固) | — | — | sleep-time compute(学术同源) | 观察沉淀 |
-| 冲突处理 | 矛盾冻结 → 人工裁决队列 | 时序失效(边失效标注) | ADD/UPDATE 等操作 | 无专门机制 | 证据回传更新观察 |
-| 遗忘 | heat 热度衰减降权 + 显式 archive/delete,无静默淘汰 | 边失效 | ADD/UPDATE/DELETE 操作 | 无 | — |
+| 离线巩固 | autoDream(快写慢巩固) | 无批处理式巩固(明说只增量、不重算) | 无后台巩固 | sleep-time compute,现称 dreaming(`/sleeptime`、`/dream`、`/reflect`) | 后台自动巩固 事实 → 观察 → 心智模型;近重复按 0.97 余弦合并;可按 scope 配 mission |
+| 冲突处理 | 矛盾冻结 → 人工裁决队列 | 时序失效(边失效标注) | 单次 Add-only:不覆盖、只累积,冲突留给读者 | 无专门机制 | 证据回传更新观察 |
+| 遗忘 | heat 热度衰减降权 + 显式 archive/delete,无静默淘汰 | 边失效 | 显式 DELETE(Add-only 后不再 UPDATE 覆盖) | 无 | 无自动衰减遗忘 |
| 作用域隔离 | agent / workspace 双维 + opt-in 硬过滤 | 用户/会话分 graph | user/agent/run 多层 | 多 agent | memory bank 按 bank |
-| 可审计性 | sha256 审计链 + 幂等重放;图像链另存「记忆 ← 窗口 ← 图」的溯源行 | — | — | — | — |
+| 可审计性 | sha256 审计链 + 幂等重放;图像链另存「记忆 ← 窗口 ← 图」的溯源行 | Episodes & Provenance:每个实体/关系可回溯到原始 episode | history 表(old / new / event / actor / role)+ `GET /memories/{id}/history` | MemFS:全部上下文(含记忆块)走 git,可同步到自建仓库 | 观察带 supporting evidence 原句 + proof count;raw facts 永久保留、可回溯 |
| 多宿主接入 | DSH 内直用 + stdio MCP(`mneme-memory`,任意 MCP 客户端)+ HTTP daemon(`dsh-mneme-serve`)+ 网页端桥接([Mneme-Bridge](https://github.com/SternChiri/Mneme-Bridge)) | 托管 API + SDK | SDK / API | API / server | SDK / API / MCP |
| 完全离线 | ✅(可配本地嵌入模型) | 需 LLM/嵌入服务 | 需 LLM | 需 LLM | 需 LLM |
| 许可证 | MIT | Apache-2.0 | Apache-2.0 | Apache-2.0 | MIT |
@@ -24,7 +26,7 @@
**如何读这张表**
- 「—」不等于「差」:通用服务面向多租户在线场景,把裁决权交给调用方也是一种合理设计;dsh-mneme 的差异点在于**把记忆管理做成有账本、可人工复核、可回放的流水线**。
-- **「未核对」不等于「没有」**:那几格是还没逐条查过,不是核实过为空。这张表宁可留白,也不拿没查过的结论去垫自己。
+- **「未核对」不等于「没有」**:这几格(情感 / 图像 / 第三方写入)还没逐条查过。上一版有 4 格是把「我们没找到」写成「对方没有」,这次复核已改——所以留白比补一个好看的「—」更可信。
- 效果类问题(检索准不准、巩固有没有用)看各系统的公开基准与本项目自己的评测,本表不回答跑分。
- 许可证为 2026-11-13 各仓库 LICENSE 实文核对结果。
@@ -32,21 +34,23 @@
> Updated 2026-11-13 (previous version 2026-10-16). Based on public docs and source code (sources below). This compares **capability structure**, not accuracy — effect questions belong to public benchmarks and our own evals. dsh-mneme is a memory plugin designed for DSH (SQLite + human-editable Markdown mirror, fully offline) that also ships standalone: a zero-dependency stdio MCP server (npm package `mneme-memory`, mountable by any MCP client) and a DSH-free daemon (`dsh-mneme-serve`), so third-party integrations read and write the same library; the others are general-purpose memory services/libraries.
>
-> **What changed here**: it now covers three things that only exist since 0.14.0 (affective memory, image description with provenance, third-party writes) and updates the retrieval row to the full-corpus incremental index. The four licenses were re-verified against their LICENSE files on 2026-11-13. The competitor cells of the three new rows (affective memory / images / third-party writes) have not been checked column by column yet and say "not verified"; the remaining cells carry the 2026-10-16 results and are being re-checked.
+> **What changed here**: it now covers three things that only exist since 0.14.0 (affective memory, image description with provenance, third-party writes) and updates retrieval to the full-corpus incremental index. The competitor side was re-checked column by column: nine cells changed, four of which had turned "we did not find it" into "they do not have it". Licenses re-verified against all four LICENSE files on 2026-11-13, unchanged from the previous version.
+>
+> **One column carries a caveat**: Zep is the hosted enterprise context layer (its own Konig graph store); Graphiti is its open-source framework (Neo4j / FalkorDB / Neptune). The column below follows the **open-source Graphiti implementation** and does not credit it with Zep's hosted capabilities.
-| Capability | dsh-mneme | Zep / Graphiti | Mem0 | Letta (MemGPT) | Hindsight |
+| Capability | dsh-mneme | Graphiti (Zep's OSS core) | Mem0 | Letta (MemGPT) | Hindsight |
|---|---|---|---|---|---|
-| Storage | SQLite + human-editable Markdown mirror | Temporal knowledge graph (Neo4j / FalkorDB / Neptune) | Vector + graph, extraction-based | Self-editing core-memory blocks | 4-layer biomimetic (facts/experiences/observations/mental models) |
-| Retrieval | BM25 (full corpus + incremental index since 0.14.0) + graph + hot session memory; entity-graph 4th path (RRF) | Temporal graph traversal + semantic | Semantic + keyword + graph | Recursive search + edits | 4 parallel strategies + RRF + rerank |
-| LLM extraction at write | No (raw text stored; LLM only during consolidation) | Yes (entities/relations) | Yes (facts) | Partial | Yes (retain extracts entities/time) |
+| Storage | SQLite + human-editable Markdown mirror | Temporal knowledge graph (Neo4j / FalkorDB / Neptune) | Vector + graph, extraction-based | Self-editing core-memory blocks + MemFS file layer | PostgreSQL + pgvector (or Oracle 23ai); the four layers (facts / experiences / observations / mental models) are memory types, not the store |
+| Retrieval | BM25 (full corpus + incremental index since 0.14.0) + graph + hot session memory; entity-graph 4th path (RRF) | Semantic + keyword BM25 + temporal graph traversal | Semantic + keyword + graph | Recursive search + edits | 4 parallel strategies + RRF + rerank |
+| LLM extraction at write | No (raw text stored; LLM only during consolidation) | Yes (entities/relations) | Yes (facts; add-only, accumulates rather than overwrites) | Partial | Yes (retain extracts entities/time) |
| Affective memory | Emotion attributions stored as relation edges (`affective_context`); recurring ones are promoted to injected patterns, one-off occasions attach via `affectiveInjectEnabled` (off by default) | not verified | not verified | not verified | not verified |
| Images / multimodal | A description pass has a vision model write one line per image into distillation (`imageDescribeEnabled`); `image_sources` lets each memory point back to the window and image it came from (`imageMemoryEnabled`). No image bytes stored, no OCR | not verified | not verified | not verified | not verified |
| Third-party writes | HTTP API + standing daemon (`dsh-mneme-serve`) so another plugin can write into the same store; stdio MCP adds six tools | not verified | not verified | not verified | not verified |
-| Offline consolidation | autoDream (fast write, slow consolidate) | — | — | sleep-time compute (same academic thread) | Observation layer |
-| Conflict handling | Freeze → human review queue | Temporal edge invalidation | ADD/UPDATE ops | None specific | Evidence-fed observation updates |
-| Forgetting | Heat decay (down-weight) + explicit archive/delete; no silent eviction | Edge invalidation | ADD/UPDATE/DELETE ops | None | — |
+| Offline consolidation | autoDream (fast write, slow consolidate) | No batch consolidation (incremental only, by design) | No background consolidation | sleep-time compute, now called dreaming (`/sleeptime`, `/dream`, `/reflect`) | Background consolidation facts → observations → mental models; near-duplicates merged at 0.97 cosine; per-scope mission |
+| Conflict handling | Freeze → human review queue | Temporal edge invalidation | Add-only, no overwrite: conflicts are left to the reader | None specific | Evidence-fed observation updates |
+| Forgetting | Heat decay (down-weight) + explicit archive/delete; no silent eviction | Edge invalidation | Explicit DELETE (add-only no longer overwrites) | None | No automatic decay/eviction |
| Scoping | agent + workspace dual-dim, opt-in hard filter | per-user/session graphs | user/agent/run layers | multi-agent | memory banks |
-| Auditability | sha256 audit chain + idempotent replay; the image path also records "memory ← window ← image" provenance rows | — | — | — | — |
+| Auditability | sha256 audit chain + idempotent replay; the image path also records "memory ← window ← image" provenance rows | Episodes & Provenance: every entity/relation traces back to its source episode | history table (old / new / event / actor / role) + `GET /memories/{id}/history` | MemFS: all context (memory blocks included) is git-tracked and syncable to your own repo | Observations carry supporting evidence quotes + proof count; raw facts retained, traceable |
| Multi-host access | in-DSH + stdio MCP (`mneme-memory`, any MCP client) + HTTP daemon (`dsh-mneme-serve`) + web-client bridge ([Mneme-Bridge](https://github.com/SternChiri/Mneme-Bridge)) | hosted API + SDKs | SDK / API | API / server | SDK / API / MCP |
| Fully offline | ✅ (local embedding model supported) | needs LLM/embedding service | needs LLM | needs LLM | needs LLM |
| License | MIT | Apache-2.0 | Apache-2.0 | Apache-2.0 | MIT |
@@ -54,14 +58,23 @@
**How to read this table**
- "—" does not mean "worse": general services target online multi-tenant settings where the caller owns adjudication; dsh-mneme's differentiator is turning memory management into an **accounted, human-reviewable, replayable pipeline**.
-- **"not verified" does not mean "absent"**: those cells have not been checked line by line yet. The table would rather leave a blank than use an unchecked claim to prop itself up.
+- **"not verified" does not mean "absent"**: those cells (affective / images / third-party writes) have not been checked yet. The previous version turned "we did not find it" into "they do not have it" in four places; this pass fixed them — a blank is more trustworthy than a flattering dash.
- Accuracy questions belong to public benchmarks and this project's own evals; this table does not answer them.
- Licenses verified against each repository's LICENSE file on 2026-11-13.
+## 核对记录 / Verification notes(2026-11-13)
+
+- 许可证:四家 LICENSE 实文 —— Graphiti / Mem0 / Letta 为 Apache-2.0,Hindsight 为 MIT,与 2026-10-16 一致。
+- Mem0:`mem0/memory/storage.py:150` 的 `add_history(...)` 与 `GET /memories/{id}/history` 是 history 表的出处;「Add-only」取自其 2026-04「New Memory Algorithm」说明(不再 UPDATE / DELETE,只累积)。
+- Graphiti:「Episodes & Provenance」一节写明 derived fact 可回溯到 source episode;检索为语义 + BM25 + 图遍历三道。
+- Letta:MemFS 说明全部上下文 git 跟踪;`/sleeptime`、`/dream`、`/reflect` 是现名 dreaming 的入口。**代码已从 `letta-ai/letta` 迁至 `letta-ai/letta-code`**,主仓现在是文档壳。
+- Hindsight:v1.2.1 与 Hindsight Models 两次发布;存储为 PostgreSQL + pgvector 或 Oracle 23ai;观察层带 supporting evidence 与 proof count。
+- 仍待核:情感记忆 / 图像 / 第三方写入三行的竞品格(标「未核对」)。
+
## Sources
- Zep: arXiv:2501.13956 · repo [getzep/graphiti](https://github.com/getzep/graphiti)
- Mem0: arXiv:2504.19413 · repo [mem0ai/mem0](https://github.com/mem0ai/mem0)
- Hindsight: arXiv:2512.12818 · repo [vectorize-io/hindsight](https://github.com/vectorize-io/hindsight)
-- Letta: [letta-ai/letta](https://github.com/letta-ai/letta)
+- Letta: [letta-ai/letta-code](https://github.com/letta-ai/letta-code)(代码仓;`letta-ai/letta` 现为文档壳)
- dsh-mneme: 本仓 README 与 [dsh-mneme/CHANGELOG.md](dsh-mneme/CHANGELOG.md)
From b90a9825a1ebe6e6f73f2870be5a4a4dda28dc53 Mon Sep 17 00:00:00 2001
From: modusensus
Date: Fri, 13 Nov 2026 02:29:27 +0800
Subject: [PATCH 4/4] docs(comparison): fill the twelve new cells instead of
leaving them blank
Affective memory: none of the four has a dedicated mechanism. Emotion is
extraction content for Graphiti and Mem0, fits only as markdown inside a
block for Letta, and shows up in Hindsight as per-bank disposition traits,
which are reasoning style rather than memory.
Images: four different positions, not one yes/no. Graphiti has no image
episode type at all; Mem0 stores a vision description and drops the file;
Letta keeps originals in MemFS under git; Hindsight keeps originals with a
content-addressed store and a provenance key.
Third-party writes: all four have one, at different levels of readiness --
Graphiti ships reference REST/MCP servers you deploy, Mem0 and Hindsight
expose REST directly, Letta's official REST defaults to Cloud.
The table now records positions rather than checkmarks, and each cell
carries a source in the verification notes.
---
docs/COMPARISON.md | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/docs/COMPARISON.md b/docs/COMPARISON.md
index 7c2b4ef0..395b59b3 100644
--- a/docs/COMPARISON.md
+++ b/docs/COMPARISON.md
@@ -2,7 +2,7 @@
> 更新于 2026-11-13(上一版 2026-10-16)。对比基于各项目公开文档与源码(出处见文末),只比较**能力结构**,不回答「谁更准」——效果类问题请以各系统在公开基准上的实测为准。dsh-mneme 是为 DSH 设计的记忆插件(SQLite + Markdown 镜像、完全离线),同时自带独立服务:stdio MCP server(npm 包 `mneme-memory`,任意 MCP 客户端可挂载)与不开 DSH 的常驻 daemon(`dsh-mneme-serve`),第三方集成读写同一份记忆库;其余为通用记忆服务/库,定位不同,读表时请带着这个前提。
>
-> **本版做了什么**:补上 dsh-mneme 0.14.0 才有的三项(情感记忆、图像描述与溯源、第三方写入),检索行同步到全量语料增量索引;竞品侧逐列复核,改掉 9 处(其中 4 处原先把「我们没找到」写成了「对方没有」)。许可证于 2026-11-13 重核四家 LICENSE 实文,与上一版一致。
+> **本版做了什么**:补上 dsh-mneme 0.14.0 才有的三项(情感记忆、图像描述与溯源、第三方写入),检索行同步到全量语料增量索引;竞品侧逐列复核,改掉 9 处(其中 4 处原先把「我们没找到」写成了「对方没有」),并把新增的三行 × 四家共 12 格查实。许可证于 2026-11-13 重核四家 LICENSE 实文,与上一版一致。
>
> **一列的前提**:Zep 是托管的企业上下文层(自研 Konig 图库),Graphiti 是它的开源框架(自带 Neo4j / FalkorDB / Neptune)。下面这列以**开源 Graphiti 的实现**为准,不拿 Zep 的托管能力给 Graphiti 加分。
@@ -11,9 +11,9 @@
| 存储底座 | SQLite 主库 + 人类可编辑 Markdown 镜像 | 时序知识图谱(Neo4j / FalkorDB / Neptune) | 向量 + 图混合的抽取式记忆 | 自编辑记忆块(core memory)+ MemFS 文件层 | PostgreSQL + pgvector(或 Oracle 23ai);四层(事实/经历/观察/心智模型)是记忆类型,不是底座 |
| 检索 | BM25(0.14.0 起全量语料 + 增量索引)+ 图谱 + 会话热记忆,实体图第四路(RRF 融合) | 语义 + 关键词 BM25 + 时序图遍历 | 语义 + 关键词 + 图 | 递归检索 + 自编辑 | 四路并行(语义/BM25/图/时间)+ RRF + 重排 |
| 写入时 LLM 抽取 | 否(原文直存,巩固期才用 LLM) | 是(抽实体/关系) | 是(抽事实,Add-only:只累积、不覆盖) | 部分 | 是(retain 抽实体/时间) |
-| 情感记忆 | 情绪归因写成关系边(`affective_context`);跨会话达阈值的升为 pattern 进注入,单次场合的情绪经 `affectiveInjectEnabled` 附在注入行上(默认关) | 未核对 | 未核对 | 未核对 | 未核对 |
-| 图像 / 多模态 | 描述档让能看图的模型给每张图写一句、并进蒸馏(`imageDescribeEnabled`);`image_sources` 让记忆回指来源窗口与图(`imageMemoryEnabled`)。不存图字节、不做 OCR | 未核对 | 未核对 | 未核对 | 未核对 |
-| 第三方写入 | HTTP API + 常驻 daemon(`dsh-mneme-serve`),别的插件可直接写进同一份库;stdio MCP 另开六个工具 | 未核对 | 未核对 | 未核对 | 未核对 |
+| 情感记忆 | 情绪归因写成关系边(`affective_context`);跨会话达阈值的升为 pattern 进注入,单次场合的情绪经 `affectiveInjectEnabled` 附在注入行上(默认关) | 无专门机制:情绪只作为抽取内容,落成普通实体/关系 | 无专门类型:prompt 要求保留情绪状态,但仍是普通 memory | 无:MemFS 记忆文件只认 markdown,情绪只能塞进某个块 | 无:只有 bank 的 disposition traits(怀疑/字面/共情),那是推理风格不是情绪记忆 |
+| 图像 / 多模态 | 描述档让能看图的模型给每张图写一句、并进蒸馏(`imageDescribeEnabled`);`image_sources` 让记忆回指来源窗口与图(`imageMemoryEnabled`)。不存图字节、不做 OCR | 不支持:EpisodeType 只有 message / json / text / fact_triple,全库无 image_url | 只存描述:vision 模型生成文字后入库,原图不留,因而没有图像溯源 | 存原图:MemFS 收 png/jpg/webp/gif,返回 `kind:"image"` 与 mime,git 跟踪、可同步;另有 `view_image` 工具 | 存原图 + 溯源:retain 收图文交错附件,按内容寻址落盘,`(bank_id, document_id, attachment_hash)` 记行 |
+| 第三方写入 | HTTP API + 常驻 daemon(`dsh-mneme-serve`),别的插件可直接写进同一份库;stdio MCP 另开六个工具 | 有,REST 与 MCP 均为参考实现,需自行部署 | 有,自托管 REST `POST /memories`(另有 PUT / DELETE / search / history) | 有,官方 REST `/v1/agents…`,默认走 Cloud | 有,REST + 每个 bank 一个 MCP 端点(retain / recall / reflect) |
| 离线巩固 | autoDream(快写慢巩固) | 无批处理式巩固(明说只增量、不重算) | 无后台巩固 | sleep-time compute,现称 dreaming(`/sleeptime`、`/dream`、`/reflect`) | 后台自动巩固 事实 → 观察 → 心智模型;近重复按 0.97 余弦合并;可按 scope 配 mission |
| 冲突处理 | 矛盾冻结 → 人工裁决队列 | 时序失效(边失效标注) | 单次 Add-only:不覆盖、只累积,冲突留给读者 | 无专门机制 | 证据回传更新观察 |
| 遗忘 | heat 热度衰减降权 + 显式 archive/delete,无静默淘汰 | 边失效 | 显式 DELETE(Add-only 后不再 UPDATE 覆盖) | 无 | 无自动衰减遗忘 |
@@ -26,7 +26,8 @@
**如何读这张表**
- 「—」不等于「差」:通用服务面向多租户在线场景,把裁决权交给调用方也是一种合理设计;dsh-mneme 的差异点在于**把记忆管理做成有账本、可人工复核、可回放的流水线**。
-- **「未核对」不等于「没有」**:这几格(情感 / 图像 / 第三方写入)还没逐条查过。上一版有 4 格是把「我们没找到」写成「对方没有」,这次复核已改——所以留白比补一个好看的「—」更可信。
+- 图像这一行没有「有/没有」的二分:四家分别是**不支持、只存描述、存原图、存原图带溯源**四档,所以写档位而不是打勾。dsh-mneme 选的是「描述进记忆 + 记忆回指来源图」,不存图字节。
+- 情感记忆四家都没有专门机制,这条是我们的空位;但「没有专门机制」不等于他们记不住情绪——情绪能当普通内容存进去,只是没有结构、不进注入、不可按情绪检索。
- 效果类问题(检索准不准、巩固有没有用)看各系统的公开基准与本项目自己的评测,本表不回答跑分。
- 许可证为 2026-11-13 各仓库 LICENSE 实文核对结果。
@@ -34,7 +35,7 @@
> Updated 2026-11-13 (previous version 2026-10-16). Based on public docs and source code (sources below). This compares **capability structure**, not accuracy — effect questions belong to public benchmarks and our own evals. dsh-mneme is a memory plugin designed for DSH (SQLite + human-editable Markdown mirror, fully offline) that also ships standalone: a zero-dependency stdio MCP server (npm package `mneme-memory`, mountable by any MCP client) and a DSH-free daemon (`dsh-mneme-serve`), so third-party integrations read and write the same library; the others are general-purpose memory services/libraries.
>
-> **What changed here**: it now covers three things that only exist since 0.14.0 (affective memory, image description with provenance, third-party writes) and updates retrieval to the full-corpus incremental index. The competitor side was re-checked column by column: nine cells changed, four of which had turned "we did not find it" into "they do not have it". Licenses re-verified against all four LICENSE files on 2026-11-13, unchanged from the previous version.
+> **What changed here**: it now covers three things that only exist since 0.14.0 (affective memory, image description with provenance, third-party writes) and updates retrieval to the full-corpus incremental index. The competitor side was re-checked column by column: nine cells changed, four of which had turned "we did not find it" into "they do not have it", and the twelve new cells across the three new rows were verified. Licenses re-verified against all four LICENSE files on 2026-11-13, unchanged from the previous version.
>
> **One column carries a caveat**: Zep is the hosted enterprise context layer (its own Konig graph store); Graphiti is its open-source framework (Neo4j / FalkorDB / Neptune). The column below follows the **open-source Graphiti implementation** and does not credit it with Zep's hosted capabilities.
@@ -43,9 +44,9 @@
| Storage | SQLite + human-editable Markdown mirror | Temporal knowledge graph (Neo4j / FalkorDB / Neptune) | Vector + graph, extraction-based | Self-editing core-memory blocks + MemFS file layer | PostgreSQL + pgvector (or Oracle 23ai); the four layers (facts / experiences / observations / mental models) are memory types, not the store |
| Retrieval | BM25 (full corpus + incremental index since 0.14.0) + graph + hot session memory; entity-graph 4th path (RRF) | Semantic + keyword BM25 + temporal graph traversal | Semantic + keyword + graph | Recursive search + edits | 4 parallel strategies + RRF + rerank |
| LLM extraction at write | No (raw text stored; LLM only during consolidation) | Yes (entities/relations) | Yes (facts; add-only, accumulates rather than overwrites) | Partial | Yes (retain extracts entities/time) |
-| Affective memory | Emotion attributions stored as relation edges (`affective_context`); recurring ones are promoted to injected patterns, one-off occasions attach via `affectiveInjectEnabled` (off by default) | not verified | not verified | not verified | not verified |
-| Images / multimodal | A description pass has a vision model write one line per image into distillation (`imageDescribeEnabled`); `image_sources` lets each memory point back to the window and image it came from (`imageMemoryEnabled`). No image bytes stored, no OCR | not verified | not verified | not verified | not verified |
-| Third-party writes | HTTP API + standing daemon (`dsh-mneme-serve`) so another plugin can write into the same store; stdio MCP adds six tools | not verified | not verified | not verified | not verified |
+| Affective memory | Emotion attributions stored as relation edges (`affective_context`); recurring ones are promoted to injected patterns, one-off occasions attach via `affectiveInjectEnabled` (off by default) | No dedicated mechanism: emotion is just extraction content and lands as an ordinary entity/relation | No dedicated type: prompts ask to keep emotional state, but it stays an ordinary memory | None: MemFS memory files are markdown only, emotion fits only as text inside a block | None: only per-bank disposition traits (skepticism / literalism / empathy), which are reasoning style, not emotion memory |
+| Images / multimodal | A description pass has a vision model write one line per image into distillation (`imageDescribeEnabled`); `image_sources` lets each memory point back to the window and image it came from (`imageMemoryEnabled`). No image bytes stored, no OCR | Unsupported: EpisodeType is message / json / text / fact_triple only, no image_url anywhere | Description only: a vision model writes text into the store, the image itself is not kept, so there is no image provenance | Stores originals: MemFS accepts png/jpg/webp/gif, returns `kind:"image"` and mime, git-tracked and syncable; a `view_image` tool exists | Stores originals with provenance: retain accepts inline text+image attachments, content-addressed on disk, with `(bank_id, document_id, attachment_hash)` rows |
+| Third-party writes | HTTP API + standing daemon (`dsh-mneme-serve`) so another plugin can write into the same store; stdio MCP adds six tools | Yes; REST and MCP are reference implementations you deploy yourself | Yes; self-hosted REST `POST /memories` (plus PUT / DELETE / search / history) | Yes; official REST `/v1/agents…`, defaults to Cloud | Yes; REST plus one MCP endpoint per bank (retain / recall / reflect) |
| Offline consolidation | autoDream (fast write, slow consolidate) | No batch consolidation (incremental only, by design) | No background consolidation | sleep-time compute, now called dreaming (`/sleeptime`, `/dream`, `/reflect`) | Background consolidation facts → observations → mental models; near-duplicates merged at 0.97 cosine; per-scope mission |
| Conflict handling | Freeze → human review queue | Temporal edge invalidation | Add-only, no overwrite: conflicts are left to the reader | None specific | Evidence-fed observation updates |
| Forgetting | Heat decay (down-weight) + explicit archive/delete; no silent eviction | Edge invalidation | Explicit DELETE (add-only no longer overwrites) | None | No automatic decay/eviction |
@@ -58,18 +59,18 @@
**How to read this table**
- "—" does not mean "worse": general services target online multi-tenant settings where the caller owns adjudication; dsh-mneme's differentiator is turning memory management into an **accounted, human-reviewable, replayable pipeline**.
-- **"not verified" does not mean "absent"**: those cells (affective / images / third-party writes) have not been checked yet. The previous version turned "we did not find it" into "they do not have it" in four places; this pass fixed them — a blank is more trustworthy than a flattering dash.
+- The image row is not a yes/no: the four systems sit at **unsupported, description only, originals stored, originals with provenance**. dsh-mneme keeps a description inside memory and points back to the source image, without storing image bytes.
+- None of the four has a dedicated affective mechanism; that is the gap we fill. "No dedicated mechanism" does not mean they cannot remember emotion — it can be stored as ordinary content, just with no structure, no injection path, no way to query by emotion.
- Accuracy questions belong to public benchmarks and this project's own evals; this table does not answer them.
- Licenses verified against each repository's LICENSE file on 2026-11-13.
## 核对记录 / Verification notes(2026-11-13)
- 许可证:四家 LICENSE 实文 —— Graphiti / Mem0 / Letta 为 Apache-2.0,Hindsight 为 MIT,与 2026-10-16 一致。
-- Mem0:`mem0/memory/storage.py:150` 的 `add_history(...)` 与 `GET /memories/{id}/history` 是 history 表的出处;「Add-only」取自其 2026-04「New Memory Algorithm」说明(不再 UPDATE / DELETE,只累积)。
-- Graphiti:「Episodes & Provenance」一节写明 derived fact 可回溯到 source episode;检索为语义 + BM25 + 图遍历三道。
-- Letta:MemFS 说明全部上下文 git 跟踪;`/sleeptime`、`/dream`、`/reflect` 是现名 dreaming 的入口。**代码已从 `letta-ai/letta` 迁至 `letta-ai/letta-code`**,主仓现在是文档壳。
-- Hindsight:v1.2.1 与 Hindsight Models 两次发布;存储为 PostgreSQL + pgvector 或 Oracle 23ai;观察层带 supporting evidence 与 proof count。
-- 仍待核:情感记忆 / 图像 / 第三方写入三行的竞品格(标「未核对」)。
+- Mem0:`mem0/memory/storage.py:150` 的 `add_history(...)` 与 `GET /memories/{id}/history` 是 history 表的出处;「Add-only」取自其 2026-04「New Memory Algorithm」说明(不再 UPDATE / DELETE,只累积)。情感:`mem0/configs/prompts.py:624`。图像:`mem0/memory/utils.py:161,186`、`mem0/memory/main.py:867`(描述入库、原图不留)。写入:`server/main.py:367 POST /memories`。
+- Graphiti:「Episodes & Provenance」一节写明 derived fact 可回溯到 source episode;检索为语义 + BM25 + 图遍历三道。情感:`graphiti_core/prompts/extract_nodes_and_edges.py:130`。图像:`graphiti_core/nodes.py:54,74-77`(EpisodeType 无图像类型)。写入:`server/graph_service/routers/ingest.py:51,73`、`mcp_server/src/graphiti_mcp_server.py:402`。
+- Letta:MemFS 说明全部上下文 git 跟踪;`/sleeptime`、`/dream`、`/reflect` 是现名 dreaming 的入口。情感/图像:`letta-code:src/websocket/listener/commands/memory.ts:24-31,40-42,323-338`(只认 markdown;图像收 png/jpg/webp/gif,`kind:"image"`)。写入:`letta-code:src/backend/api/agents.ts`(默认 Cloud)。**代码已从 `letta-ai/letta` 迁至 `letta-ai/letta-code`**,主仓现在是文档壳。
+- Hindsight:v1.2.1 与 Hindsight Models 两次发布;存储为 PostgreSQL + pgvector 或 Oracle 23ai;观察层带 supporting evidence 与 proof count。情感:README:385(disposition traits)。图像:`hindsight-docs/docs/developer/configuration.mdx:2019,319-324`、`hindsight-api-slim/hindsight_api/engine/retain/attachment_store.py:24`(内容寻址 + 溯源键)。写入:README:166,263-271(REST + 每 bank 一个 MCP)。
## Sources