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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3E63DD?style=flat-square" alt="license"></a>
<a href="https://github.com/slow-stack/mneme/actions"><img src="https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml?style=flat-square&label=CI" alt="CI"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-22%2B-3E63DD?style=flat-square&logo=nodedotjs&logoColor=white" alt="node"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1609%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1613%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://codecov.io/gh/slow-stack/mneme"><img src="https://img.shields.io/codecov/c/github/slow-stack/mneme/main?style=flat-square" alt="coverage"></a>
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome"></a>
</p>
Expand Down Expand Up @@ -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 # 1613 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -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 # 1613 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
Expand Down
6 changes: 3 additions & 3 deletions dsh-mneme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![tests](https://img.shields.io/badge/tests-1609%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-1613%20passed-success)](https://github.com/slow-stack/mneme)
[![CI](https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml)](https://github.com/slow-stack/mneme/actions)
[![node](https://img.shields.io/badge/node-22%2B-blue)](https://nodejs.org)
[![npm downloads](https://img.shields.io/npm/d18m/@modusensus/dsh-mneme.svg?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
Expand Down Expand Up @@ -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/ # 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 运行时清单
```

Expand All @@ -622,7 +622,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 1609 个测试
npm test # 运行 1613 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
17 changes: 15 additions & 2 deletions dsh-mneme/lib/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1634,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 词法召回领位。
Expand Down
17 changes: 15 additions & 2 deletions dsh-mneme/src/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
// document 摘要行(#230):仅 documentMemoryEnabled 开启时进注入候选。独立
// 集合而非常改共享 Set——INJECT_TYPES 本身是「类型可注入」的静态事实,flag
Expand Down Expand Up @@ -1634,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 词法召回领位。
Expand Down
39 changes: 39 additions & 0 deletions dsh-mneme/test/inject-recall.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,42 @@ 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 应进注入候选");
});

// 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, "低于闸门的行不得借语义缓存绕开门槛");
});
9 changes: 7 additions & 2 deletions dsh-mneme/test/service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});

Expand Down Expand Up @@ -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", () => {
Expand Down
Loading