Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dsh-mneme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [Unreleased]

- **知识库未启用时的空态文案指错了分组名(#668 尾巴)**。空态一直写「设置 → 记忆(mneme) → 功能 → 知识库」,但 v0.13 起 `group.ext` 是独立分组、标签是「扩展功能」(英文 `Extensions`),「知识库」只是组里的一项——照这句话去设置页找「知识库」分组会找不到。中英两版都改成「功能 → 扩展功能」并点名组内那一项,`test/client.test.js` 的文案锁同步跟上。

- **跨类型同义双录:蒸馏提示词里「宁可拆成多条」把同一件事记了两遍,而三处去重各守一道类型闸(#687);实体表 75% 的实体只被提过一次、名字只按大小写敏感精确匹配(#688)**。两条 issue 报告的是同一个根病:**记忆的「类型」被当成了「对象身份」**。#687 上报者(xueyue33)给的分布很刺眼——他自己库里 `sim>=0.85` 的 81 对近重复中 **72 对跨类型**、`>=0.90` 的 7 对中 6 对跨类型;`sim>=0.88` 的 24 对里 21 对跨类型。四处夹缝逐个核过都成立:`src/lang.js` 的 `codingSummary` 明确让模型「宁可拆成多条」而 `pitfall`(现象+根因+规避)与 `constraint`(约束+来源)本就是同一结论的两种写法;`src/service.js:1480` 的 `findSessionDuplicate` 用 `store.list({ type: memory.type, source, limit: 200 })` 锁死同类型同会话;`src/dream/clustering.js:117` 的 `findPotentialConflicts` 首行 `if (memories[i].type !== memories[j].type) continue;`;dream 巩固提示词同样写着「仅合并同类型」。**我们不开常开类型闸**:本机活跃库 9,275 条带向量的记忆里跨类型 `cosine>=0.90` 共 45 对,逐对读正文后 **36 对(80%)确为同义,9 对合并会丢信息**(5 对时间序列/状态快照、2 对症状 vs 修复、1 对结论反转、1 对版本号不同)——也就是说「凡是跨类型高相似就合并」每 5 对要吞掉 1 对真互补的信息,而这类对只占活跃总量 0.97%,收益与风险不对称。落地的三层是**源头互斥 + 批内去重 + 存量一次性处置**:① `codingSummary` 提示词(中英双语)补一条互斥约束——同一结论只记一条,`pitfall` 与 `constraint` 不双写,时间序列/状态快照写进同一条;② 蒸馏落盘前做**批内**跨类型去重,只对 `pitfall↔constraint`、`history↔project` 两组生效(正是实测最集中的应用对:9 对 + 8 对),`planBatchCrossTypeDedupe(entries, { minSim })` 是纯函数——按相似度降序、每条最多被退休一次、不许链式藏行(A 取代 B 的同时 B 又取代过 C 会把 C 永久埋在链底);门槛 `BATCH_CROSS_TYPE_MIN_SIM = 0.85`(textSim,不是余弦,词面重合是这两组对的主特征)、`BATCH_CROSS_TYPE_MIN_CHARS = 24`、`BATCH_CROSS_TYPE_MIN_LEN_RATIO = 0.5`;③ 处置走 `store.supersedeChain` 只写 `superseded_by` / `superseded_at`,**保留正文更全的一条、被取代者正文零改写、不归档、不 merge**——判错时旧行仍在库里、显式搜索照旧读得到,且 `supersededIds()` 的 `JOIN ... AND w.archived=0` 让继任者一旦被归档旧行自动回到可见,等于自带回退。刻意**没有**复用 `src/dream/decisions.js` 的 `supersedeInfoFirstPair`:它走 `service.update` 会在正文尾部追加「已被取代」前缀,与「正文零改写」直接冲突。**默认开**(2026-11-13 拍板):这个开关存在的意义就是让新写入不再双录,默认关等于修了不生效,所以 `src/config.js` 给 `true`、设 `false` 是 kill switch,且关时审计行的 `batchDeduped` / `batchPairs` 两个键根本不出现——加开关之前的行为逐字节不变。两个键同时登记进 `settings.js` 的 `FEATURE_FLAG_BOOLEANS` 与 `FEATURE_FLAG_NUMBER_RANGES`,面板上能改。存量 17 对**不由代码处置**:只读脚本 `scripts/cross-type-dedupe-dryrun.mjs` 出清单(输出**禁止落在仓库内**,清单里含记忆原文),复核后由人决定、按纯 SQL 打链。

**#688 的修法只落在写入侧**。上报者库里 12,700+ 实体、38,500+ 关系、孤儿 41%、`mention_count=1` 占 72%;本机 live 库(只读打开)实测实体 **25,474**、关系 **59,715**、`mention_count=1` 占 **75%**、大小写/空白重复 262 组;而按 `entity_relations.memory_id` 真实连接口径,孤儿是 **17.9%(4,560 行)** 而不是 41%——口径不同,这里写清楚。存量**不做合并**:实体 id 是 `randomUUID()`,合并要重指 59,715 行关系且 `entity_relations` 没有唯一约束,重指过程一旦中断会造出重复边,收益(0.68% 的实体)与风险同样不对称。改的是查找键——`findEntityByName` 从 `WHERE name = ?` 改成 `lower(trim(name))` + `type` 的等价比较并为此建表达式索引(`DeepSeek-V3` 与 `DeepSeek-V4` 是两个不同对象,不剥版本号后缀;`createEntity` 落库只去首尾空白、**保留原大小写**用于展示)。类型维度不能省:实测去掉 type 会误并 95 组(`constraint` 与 `pitfall` 常同名)。另外 `entities.canonical_memory_id` 是 v0.7.1(`5b5a2aa4`)引入的预留列,**从没接通线**——`src/store.js` 里只有 DDL、行映射、`INSERT`、`UPDATE` 四处 plumbing,没有任何调用方写它或读它,25,474 行全 NULL;这次按幂等迁移删列(`PRAGMA table_info` 判断 + `ALTER TABLE ... DROP COLUMN`,宿主 SQLite 3.51.3 支持)。迁移成本实测:25,474 行旧 schema 表首次 `createStore` **396ms**(含建表达式索引),二次 24ms。
Expand Down
4 changes: 2 additions & 2 deletions dsh-mneme/lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ window.__ModuleLoader__.load({
"kb.panel.open": "知识库",
"kb.search": "检索",
"kb.col.intro": "知识库是 mneme 插件的功能:把外部资料(本地文件 / 网页 / 仓库地志页)切块索引,检索与注入时一起用。",
"kb.empty.disabled": "「知识库」未启用:打开 设置 → 记忆(mneme) → 功能 → 知识库,打开开关后重启 DSH 生效。",
"kb.empty.disabled": "「知识库」未启用:打开 设置 → 记忆(mneme) → 功能 → 扩展功能,勾上「知识库」后重启 DSH 生效。",
"kb.empty.unavailable": "知识库后端不可用:宿主里还没装配 kb 模块,重启 DSH 或升级插件后再看。",
"kb.empty.collections": "还没有集合。左边先建一个集合(比如「项目文档」),再把本地文件、网页或地志页收进去;入库的正文会被切块索引。",
"kb.empty.docs": "这个集合还没有文档:右边选来源类型,填路径或 URL 后点「入库」。",
Expand Down Expand Up @@ -1583,7 +1583,7 @@ window.__ModuleLoader__.load({
"kb.panel.open": "Knowledge Base",
"kb.search": "Search",
"kb.col.intro": "The knowledge base is an mneme plugin feature: it chunks and indexes external material (local files / web pages / Repo Atlas pages) so recall and injection can use it.",
"kb.empty.disabled": "The knowledge base is disabled: open Settings → Memory (mneme) → Features → Knowledge base, turn it on, then restart DSH to apply.",
"kb.empty.disabled": "The knowledge base is disabled: open Settings → Memory (mneme) → Features → Extensions, turn on the knowledge base switch, then restart DSH to apply.",
"kb.empty.unavailable": "The knowledge base backend is unavailable: the host has not wired the kb module yet — restart DSH or update the plugin and try again.",
"kb.empty.collections": "No collections yet. Create one on the left (say \"Project docs\"), then add local files, web pages or atlas pages; ingested text gets chunked and indexed.",
"kb.empty.docs": "This collection has no documents yet: pick a source type on the right, fill in a path or URL, then press \"Ingest\".",
Expand Down
6 changes: 3 additions & 3 deletions dsh-mneme/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,12 @@ test("knowledge base page exposes the §3.6 routes and explains every empty stat

// 文案本身锁一次:说清这是 mneme 的功能、开关在哪儿。
assert.ok(
clientSource.includes("设置 → 记忆(mneme) → 功能 → 知识库"),
clientSource.includes("设置 → 记忆(mneme) → 功能 → 扩展功能"),
"the Chinese disabled copy must name the exact toggle path"
);
assert.ok(
clientSource.includes("Settings → Memory (mneme) → Features → Knowledge base"),
"the English disabled copy must name the same path"
clientSource.includes("Settings → Memory (mneme) → Features → Extensions, turn on the knowledge base switch"),
"the English disabled copy must name the same path (group label is Extensions since v0.13)"
);
});

Expand Down
Loading