From 0949c439123852668776f6b028f38059c6531446 Mon Sep 17 00:00:00 2001
From: modusensus
Date: Sun, 11 Oct 2026 19:54:51 +0800
Subject: [PATCH 1/2] =?UTF-8?q?fix(inject):=20=E6=B3=A8=E5=85=A5=E7=99=BD?=
=?UTF-8?q?=E5=90=8D=E5=8D=95=E6=94=B6=E7=BC=96=20history=20=E4=B8=8E=20pa?=
=?UTF-8?q?ttern=E2=80=94=E2=80=94=E5=AD=98=E6=B4=BB=E5=8D=B4=E4=B8=8D?=
=?UTF-8?q?=E5=8F=AF=E6=B3=A8=E5=85=A5=E7=9A=84=2023%=EF=BC=88#369?=
=?UTF-8?q?=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- INJECT_TYPES 原先不收 history:蒸馏白名单(src/summarize.js 的 VALID)产得出,
注入面却一次不给看。本机活跃库 378/1820(20.8%)受此影响,而它们在 recall_runs
的 auto 模式里被按需检索过 328 次——是被真实使用的数据,只是进不了注入池。
- pattern 一并收编:40 条全为手工写入(source=manual),且已被 dreamMergeGuard 列为
长保留受保护类型。巩固层当资产护住、注入面无视,是同一条口径的两头不一致。
- 候选池 1362 → 1753;重要度闸门(>=3,summary/preference 例外)对新收编类型一视同仁,
低于闸门照旧出局。top_k=5 下粗算多占约 1 席。
- test/service.test.js 两处「history 排除」的旧行为锁按新口径翻成「收编生效」——白名单
日后若被收窄会当场红,而不是静默把两成条目关回不可注入;另加三条候选池用例
(history 进池 / 闸门不豁免 / pattern 进池)。
- src 变更已 sync;测试 1609 → 1612。
---
README.md | 6 +++---
dsh-mneme/README.md | 6 +++---
dsh-mneme/lib/service.js | 10 +++++++++-
dsh-mneme/src/service.js | 10 +++++++++-
dsh-mneme/test/inject-recall.test.js | 26 ++++++++++++++++++++++++++
dsh-mneme/test/service.test.js | 9 +++++++--
6 files changed, 57 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index f9624880..0cc4306b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
@@ -207,7 +207,7 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790
```bash
cd dsh-mneme && npm install
-npm test # 1609 个测试
+npm test # 1612 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
@@ -414,7 +414,7 @@ Community projects that wire mneme into more platforms — not part of this repo
```bash
cd dsh-mneme && npm install
-npm test # 1609 tests
+npm test # 1612 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
diff --git a/dsh-mneme/README.md b/dsh-mneme/README.md
index 5edf195a..048cfe2f 100644
--- a/dsh-mneme/README.md
+++ b/dsh-mneme/README.md
@@ -5,7 +5,7 @@
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[](LICENSE)
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
-[](https://github.com/slow-stack/mneme)
+[](https://github.com/slow-stack/mneme)
[](https://github.com/slow-stack/mneme/actions)
[](https://nodejs.org)
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
@@ -613,7 +613,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
-test/ # 1609 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
+test/ # 1612 个 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 运行时清单
```
@@ -622,7 +622,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
-npm test # 运行 1609 个测试
+npm test # 运行 1612 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
diff --git a/dsh-mneme/lib/service.js b/dsh-mneme/lib/service.js
index c2ba694c..cca03dff 100644
--- a/dsh-mneme/lib/service.js
+++ b/dsh-mneme/lib/service.js
@@ -13,7 +13,15 @@ import { createBM25Index } from "./search/bm25.js";
import { adaptiveThreshold } from "./search/adaptive.js";
import { anchorSeeds, normalizePath, cascadeDepths, HOP_QUOTA } from "./graph/anchoring.js";
-const INJECT_TYPES = new Set(["preference", "project", "decision", "summary", "rejected_solution", "pitfall", "constraint"]);
+// Issue #369:蒸馏白名单(src/summarize.js 的 VALID)产得出 history,注入白名单
+// 原先不收——本机活跃库实测 378/1820(20.8%)因此永不可自动注入,而它们在同一库
+// 的 recall_runs `auto` 模式里被按需检索过 328 次:不是垃圾数据,只是进不了注入池。
+// 收编后候选池 1362 → 1713(+25.8%),按 top_k=5 粗算约多占 1 席;重要度闸门对
+// history 一视同仁(见 injectCandidates 的 importance >= threshold),低于闸门照旧出局。
+// pattern 一并收编:它只由手工写入(实测 40 条全 source=manual),却已被 dreamMergeGuard
+// 列为长保留受保护类型(docs/CONFIGURATION.md)——巩固层当资产护住、注入面一次不给看,
+// 是同一条口径的两头不一致。
+const INJECT_TYPES = new Set(["preference", "project", "decision", "summary", "rejected_solution", "pitfall", "constraint", "history", "pattern"]);
// document 摘要行(#230):仅 documentMemoryEnabled 开启时进注入候选。独立
// 集合而非常改共享 Set——INJECT_TYPES 本身是「类型可注入」的静态事实,flag
diff --git a/dsh-mneme/src/service.js b/dsh-mneme/src/service.js
index c2ba694c..cca03dff 100644
--- a/dsh-mneme/src/service.js
+++ b/dsh-mneme/src/service.js
@@ -13,7 +13,15 @@ import { createBM25Index } from "./search/bm25.js";
import { adaptiveThreshold } from "./search/adaptive.js";
import { anchorSeeds, normalizePath, cascadeDepths, HOP_QUOTA } from "./graph/anchoring.js";
-const INJECT_TYPES = new Set(["preference", "project", "decision", "summary", "rejected_solution", "pitfall", "constraint"]);
+// Issue #369:蒸馏白名单(src/summarize.js 的 VALID)产得出 history,注入白名单
+// 原先不收——本机活跃库实测 378/1820(20.8%)因此永不可自动注入,而它们在同一库
+// 的 recall_runs `auto` 模式里被按需检索过 328 次:不是垃圾数据,只是进不了注入池。
+// 收编后候选池 1362 → 1713(+25.8%),按 top_k=5 粗算约多占 1 席;重要度闸门对
+// history 一视同仁(见 injectCandidates 的 importance >= threshold),低于闸门照旧出局。
+// pattern 一并收编:它只由手工写入(实测 40 条全 source=manual),却已被 dreamMergeGuard
+// 列为长保留受保护类型(docs/CONFIGURATION.md)——巩固层当资产护住、注入面一次不给看,
+// 是同一条口径的两头不一致。
+const INJECT_TYPES = new Set(["preference", "project", "decision", "summary", "rejected_solution", "pitfall", "constraint", "history", "pattern"]);
// document 摘要行(#230):仅 documentMemoryEnabled 开启时进注入候选。独立
// 集合而非常改共享 Set——INJECT_TYPES 本身是「类型可注入」的静态事实,flag
diff --git a/dsh-mneme/test/inject-recall.test.js b/dsh-mneme/test/inject-recall.test.js
index 7d3ca45d..423cabc1 100644
--- a/dsh-mneme/test/inject-recall.test.js
+++ b/dsh-mneme/test/inject-recall.test.js
@@ -103,3 +103,29 @@ test("slotFillRate = 注入条数 / (轮数 × 槽位数)", () => {
assert.equal(stats.injection.slotFillRate, 3 / 4);
store.close();
});
+
+// #369:history 曾被注入白名单挡在门外(蒸馏产得出、注入收不进)。这两条断言分
+// 别锁「收编生效」与「重要度闸门对 history 不豁免」——后者一旦失效,短流水账会
+// 直接挤掉决策类条目,那才是这个改动真正的风险面。
+test("#369: history 进注入候选池", () => {
+ const { service } = setup();
+ service.saveWithDedupe({ type: "history", title: "切到暗色主题", content: "用户本次会话把界面切成暗色主题", importance: 4 });
+ const titles = service.injectCandidates({ maxItems: 5 }).map((m) => m.title);
+ assert.ok(titles.includes("切到暗色主题"), "importance>=3 的 history 应进注入候选");
+});
+
+test("#369: 低于重要度闸门的 history 不得例外放行", () => {
+ const { service } = setup();
+ service.saveWithDedupe({ type: "history", title: "打开过一次终端", content: "用户中途打开过一次终端", importance: 2 });
+ const titles = service.injectCandidates({ maxItems: 5 }).map((m) => m.title);
+ assert.ok(!titles.includes("打开过一次终端"), "history 不是 summary/preference,闸门照样生效");
+});
+
+// pattern 只由手工写入,却已被 dreamMergeGuard 列为长保留受保护类型——巩固层当资产
+// 护住、注入面一次不给看是同一条口径的两头不一致。这条锁它进池。
+test("#369: 手写的 pattern 进注入候选池", () => {
+ const { service } = setup();
+ service.saveWithDedupe({ type: "pattern", title: "重复字符串先抽共用模块", content: "多处重复的截断逻辑先收拢成共用模块,再改调用方", importance: 5 });
+ const titles = service.injectCandidates({ maxItems: 5 }).map((m) => m.title);
+ assert.ok(titles.includes("重复字符串先抽共用模块"), "importance>=3 的 pattern 应进注入候选");
+});
diff --git a/dsh-mneme/test/service.test.js b/dsh-mneme/test/service.test.js
index 440ffb3d..6809e177 100644
--- a/dsh-mneme/test/service.test.js
+++ b/dsh-mneme/test/service.test.js
@@ -53,7 +53,10 @@ test("injectCandidates returns preferences + high-importance items within limit"
const types = candidates.map((c) => c.type);
assert.ok(types.includes("preference"), "preferences included");
assert.ok(types.includes("project"), "high-importance project included");
- assert.ok(!types.includes("history"), "history excluded by default");
+ // #369:history 曾被注入白名单挡在门外,现按「注入侧收编」进池(理由与实测数字
+ // 见 src/service.js 的 INJECT_TYPES 注释)。这里从「锁排除」改成「锁收编」——
+ // 白名单日后若被收窄,这条会红,而不是静默把两成条目关回不可注入。
+ assert.ok(types.includes("history"), "history included since #369");
assert.ok(candidates.length <= 5, "respects maxItems");
});
@@ -220,7 +223,9 @@ test("Bug4: injectCandidates with an empty query keeps the legacy rule-based sel
assert.deepEqual(noQuery.map((c) => c.id), legacy.map((c) => c.id), "empty query behaves identically to the old logic");
assert.deepEqual(blankQuery.map((c) => c.id), legacy.map((c) => c.id), "whitespace-only query degrades too");
assert.equal(legacy[0].type, "summary", "summary still leads");
- assert.ok(!legacy.some((c) => c.type === "history"), "history still excluded");
+ // #369 收编后 history 可进池;本用例的重心是「空查询与旧规则路逐字节同序」,
+ // 这条只确认收编没有撼动优先级分层——summary 仍打头。
+ assert.ok(legacy.some((c) => c.type === "history"), "history included since #369");
});
test("Bug4: injectCandidates ignores the query when hybridInject is disabled", () => {
From 61698c47182b4902739d1f908405936a0def5127 Mon Sep 17 00:00:00 2001
From: modusensus
Date: Sun, 11 Oct 2026 22:07:43 +0800
Subject: [PATCH 2/2] =?UTF-8?q?fix(inject):=20=E8=AF=AD=E4=B9=89=E7=BC=93?=
=?UTF-8?q?=E5=AD=98=E5=9B=9E=E5=A1=AB=E5=88=86=E6=94=AF=E8=A1=A5=E9=BD=90?=
=?UTF-8?q?=E9=87=8D=E8=A6=81=E5=BA=A6=E9=97=A8=E6=A7=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CodeRabbit on #404:injectCandidates 的 lastSemanticRecall 回填分支独漏
importance 门槛,而向量路(同函数上方)与 BM25 路(:1655 注释「门控与向量路径
一致(含 importance 阈值)」)都带着它。缺口在 #369 收编 history 后被放大——
库里低于闸门的行(本机 history 378 条中 27 条 i<=2)只要被一次检索灌进缓存,
就能绕开门槛占注入位。
- 回填谓词与另两路对齐(summary/preference 仍豁免);
- 新用例一正一反:正向锁「i4 的 history 经缓存进池」,反向锁「i2 的不得借缓存
绕行」,避免断言退化成常量真。撤掉门槛后正是反向那条红(已本机验证)。
---
README.md | 6 +++---
dsh-mneme/README.md | 6 +++---
dsh-mneme/lib/service.js | 7 ++++++-
dsh-mneme/src/service.js | 7 ++++++-
dsh-mneme/test/inject-recall.test.js | 13 +++++++++++++
5 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 0cc4306b..5f1c9d85 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
@@ -207,7 +207,7 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790
```bash
cd dsh-mneme && npm install
-npm test # 1612 个测试
+npm test # 1613 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
@@ -414,7 +414,7 @@ Community projects that wire mneme into more platforms — not part of this repo
```bash
cd dsh-mneme && npm install
-npm test # 1612 tests
+npm test # 1613 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
diff --git a/dsh-mneme/README.md b/dsh-mneme/README.md
index 048cfe2f..c32a783b 100644
--- a/dsh-mneme/README.md
+++ b/dsh-mneme/README.md
@@ -5,7 +5,7 @@
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[](LICENSE)
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
-[](https://github.com/slow-stack/mneme)
+[](https://github.com/slow-stack/mneme)
[](https://github.com/slow-stack/mneme/actions)
[](https://nodejs.org)
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
@@ -613,7 +613,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
-test/ # 1612 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
+test/ # 1613 个 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 运行时清单
```
@@ -622,7 +622,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
-npm test # 运行 1612 个测试
+npm test # 运行 1613 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
diff --git a/dsh-mneme/lib/service.js b/dsh-mneme/lib/service.js
index cca03dff..17526a95 100644
--- a/dsh-mneme/lib/service.js
+++ b/dsh-mneme/lib/service.js
@@ -1642,9 +1642,14 @@ export function createService({ store, mirror, config, onWrite, logger, document
} catch { /* vector unavailable: fall through to the recall cache */ }
}
if (!semanticItems.length && lastSemanticRecall?.query === q && lastSemanticRecall.items?.length) {
+ // CodeRabbit on #404:这条回填分支此前独漏重要度门槛——向量路(上方)与
+ // BM25 路(下方 :1655 注释「门控与向量路径一致(含 importance 阈值)」)
+ // 都带着它。#369 收编 history 后缺口会被放大:库里低于闸门的行(实测
+ // history 378 条中有 27 条 i<=2)走一次检索进缓存就能绕开门槛占注入位。
for (const m of lastSemanticRecall.items) {
if (m && !m.archived && injectTypes.has(m.type) && !m.forgotten &&
- (m.source !== "narrative" || allowNarrative) && codingGate(m)) semanticItems.push(m);
+ (m.source !== "narrative" || allowNarrative) && codingGate(m) &&
+ (m.type === "summary" || m.type === "preference" || m.importance >= threshold)) semanticItems.push(m);
}
}
// Issue #198:首轮(无向量、无缓存召回)的同步兜底——BM25 词法召回领位。
diff --git a/dsh-mneme/src/service.js b/dsh-mneme/src/service.js
index cca03dff..17526a95 100644
--- a/dsh-mneme/src/service.js
+++ b/dsh-mneme/src/service.js
@@ -1642,9 +1642,14 @@ export function createService({ store, mirror, config, onWrite, logger, document
} catch { /* vector unavailable: fall through to the recall cache */ }
}
if (!semanticItems.length && lastSemanticRecall?.query === q && lastSemanticRecall.items?.length) {
+ // CodeRabbit on #404:这条回填分支此前独漏重要度门槛——向量路(上方)与
+ // BM25 路(下方 :1655 注释「门控与向量路径一致(含 importance 阈值)」)
+ // 都带着它。#369 收编 history 后缺口会被放大:库里低于闸门的行(实测
+ // history 378 条中有 27 条 i<=2)走一次检索进缓存就能绕开门槛占注入位。
for (const m of lastSemanticRecall.items) {
if (m && !m.archived && injectTypes.has(m.type) && !m.forgotten &&
- (m.source !== "narrative" || allowNarrative) && codingGate(m)) semanticItems.push(m);
+ (m.source !== "narrative" || allowNarrative) && codingGate(m) &&
+ (m.type === "summary" || m.type === "preference" || m.importance >= threshold)) semanticItems.push(m);
}
}
// Issue #198:首轮(无向量、无缓存召回)的同步兜底——BM25 词法召回领位。
diff --git a/dsh-mneme/test/inject-recall.test.js b/dsh-mneme/test/inject-recall.test.js
index 423cabc1..cb36d319 100644
--- a/dsh-mneme/test/inject-recall.test.js
+++ b/dsh-mneme/test/inject-recall.test.js
@@ -129,3 +129,16 @@ test("#369: 手写的 pattern 进注入候选池", () => {
const titles = service.injectCandidates({ maxItems: 5 }).map((m) => m.title);
assert.ok(titles.includes("重复字符串先抽共用模块"), "importance>=3 的 pattern 应进注入候选");
});
+
+// CodeRabbit on PR #404:语义缓存回填分支曾独漏重要度门槛(向量路与 BM25 路都带)。
+// 一正一反:反向锁「低于闸门的行走缓存也不许进池」,正向锁「达标的照常进池」,
+// 免得断言退化成常量真;缓存没被灌进来时正向那条会先红,用例不会空压。
+test("#369: 语义缓存回填同样受重要度闸门约束", async () => {
+ const { service } = setup();
+ const low = service.saveWithDedupe({ type: "history", title: "终端开了一次", content: "用户中途打开过一次终端做检查", importance: 2 });
+ const high = service.saveWithDedupe({ type: "history", title: "终端方案定案", content: "用户终端接入方案已定案并验证", importance: 4 });
+ await service.searchMemories("终端", { mode: "keyword", useRerank: false, recordRecall: false });
+ const ids = service.injectCandidates({ query: "终端", maxItems: 5 }).map((m) => m.id);
+ assert.ok(ids.includes(high.memory.id), "i4 的 history 应经缓存回填进池(正向对照)");
+ assert.equal(ids.includes(low.memory.id), false, "低于闸门的行不得借语义缓存绕开门槛");
+});