@morningljn/mnemo 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dream.d.ts +2 -0
- package/dist/dream.js +20 -0
- package/dist/dream.js.map +1 -0
- package/dist/init.js +4 -24
- package/dist/init.js.map +1 -1
- package/dist/resources.d.ts +22 -8
- package/dist/resources.js +66 -20
- package/dist/resources.js.map +1 -1
- package/dist/retriever.js +42 -47
- package/dist/retriever.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +21 -10
- package/dist/schema.js.map +1 -1
- package/dist/server.js +73 -6
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +59 -1
- package/dist/store.js +308 -10
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +30 -1
- package/docs/superpowers/plans/2026-05-16-memory-dreaming.md +626 -0
- package/docs/superpowers/plans/2026-05-16-memory-self-learning.md +932 -0
- package/openspec/changes/archive/2026-05-16-memory-dreaming/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-05-16-memory-dreaming/design.md +71 -0
- package/openspec/changes/archive/2026-05-16-memory-dreaming/proposal.md +32 -0
- package/openspec/changes/archive/2026-05-16-memory-dreaming/specs/compact-search/spec.md +16 -0
- package/openspec/changes/archive/2026-05-16-memory-dreaming/specs/dream-cycle/spec.md +38 -0
- package/openspec/changes/archive/2026-05-16-memory-dreaming/tasks.md +27 -0
- package/openspec/changes/memory-self-learning/.openspec.yaml +2 -0
- package/openspec/changes/memory-self-learning/design.md +174 -0
- package/openspec/changes/memory-self-learning/proposal.md +35 -0
- package/openspec/changes/memory-self-learning/specs/fact-retrieval/spec.md +35 -0
- package/openspec/changes/memory-self-learning/specs/fact-summary/spec.md +45 -0
- package/openspec/changes/memory-self-learning/specs/length-penalty/spec.md +27 -0
- package/openspec/changes/memory-self-learning/specs/retrieval-log/spec.md +41 -0
- package/openspec/changes/memory-self-learning/specs/self-learning/spec.md +68 -0
- package/openspec/changes/memory-self-learning/tasks.md +56 -0
- package/openspec/specs/compact-search/spec.md +16 -0
- package/openspec/specs/dream-cycle/spec.md +38 -0
- package/package.json +3 -2
- package/src/dream.ts +20 -0
- package/src/init.ts +4 -24
- package/src/resources.ts +77 -21
- package/src/retriever.ts +41 -49
- package/src/schema.ts +21 -10
- package/src/server.ts +81 -7
- package/src/store.ts +378 -11
- package/src/types.ts +28 -1
- package/tests/resource.test.ts +25 -23
- package/tests/retriever.test.ts +53 -0
- package/tests/store.test.ts +239 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
mnemo-mcp 是一个基于 SQLite + FTS5 的结构化事实记忆系统。长期使用后,fact 库会出现以下问题:
|
|
4
|
+
- 多条 fact 内容重叠(如 5 条 Python 编码规范偏好,内容有交叉)
|
|
5
|
+
- 单条 fact 过长(超 500 字的"万能条",占 token、降低检索精度)
|
|
6
|
+
- 分类错误(identity 类里混了 workflow 内容)
|
|
7
|
+
- trust 评分不准确(`runLearning` 只看 feedback rate,忽略高频检索信号)
|
|
8
|
+
|
|
9
|
+
当前 `runLearning()` 仅调 trust 分,`runAudit()` 只读不改。没有内容层面的整理能力。
|
|
10
|
+
|
|
11
|
+
## Goals / Non-Goals
|
|
12
|
+
|
|
13
|
+
**Goals:**
|
|
14
|
+
- 定期自动整理 fact 库:合并重叠、压缩长文、修正分类
|
|
15
|
+
- 整理后搜索结果更精准、token 消耗更少
|
|
16
|
+
- 支持 CLI 命令 `mnemo dream` 手动触发,也支持 cron 定时
|
|
17
|
+
- 整理操作安全:合并前确认、可回滚
|
|
18
|
+
|
|
19
|
+
**Non-Goals:**
|
|
20
|
+
- 不做 AI 生成 summary(用规则提取关键句,不调 LLM)
|
|
21
|
+
- 不做跨项目记忆迁移
|
|
22
|
+
- 不做可视化 dashboard
|
|
23
|
+
- 不改变 MCP Resource 注入方式
|
|
24
|
+
|
|
25
|
+
## Decisions
|
|
26
|
+
|
|
27
|
+
### 1. 合并策略:Jaccard 相似度 > 0.6 且同 category
|
|
28
|
+
|
|
29
|
+
用现有的 `tokenizeForDedup()` + `jaccardSimilarity()` 计算两两相似度。同 category 内 Jaccard > 0.6 的 fact 对,合并为一条(保留 content 更长或 trust 更高的,删除另一条)。
|
|
30
|
+
|
|
31
|
+
**不跨 category 合并**:不同 category 的 fact 即使内容相似也保留(可能是不同语境)。
|
|
32
|
+
|
|
33
|
+
### 2. 长文压缩:规则提取关键句
|
|
34
|
+
|
|
35
|
+
content > 200 字且无 summary 的 fact,自动提取前 2 个完整句子作为 summary。不调 LLM,纯规则:
|
|
36
|
+
- 按中文句号(。)、英文句号(.)、换行符分割
|
|
37
|
+
- 取前 2 个句子(总长 ≤ 150 字)
|
|
38
|
+
|
|
39
|
+
### 3. 分类修正:关键词规则表
|
|
40
|
+
|
|
41
|
+
硬编码规则表,dream 时扫描并修正:
|
|
42
|
+
- 包含"角色设定/暖暖/身份" → identity
|
|
43
|
+
- 包含"编码规范/代码风格/pytest" → coding_style
|
|
44
|
+
- 包含"工作流/OpenSpec/工作流" → workflow
|
|
45
|
+
- 包含"偏好/VS Code/编辑器" → tool_pref
|
|
46
|
+
|
|
47
|
+
### 4. Dream report 输出格式
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
{
|
|
51
|
+
merged: 3,
|
|
52
|
+
compressed: 5,
|
|
53
|
+
reclassified: 2,
|
|
54
|
+
deleted: 4,
|
|
55
|
+
health: { total: 65, avg_trust: 0.62, avg_length: 180, coverage: { identity: 8, coding_style: 12, ... } }
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 5. 搜索结果精简
|
|
60
|
+
|
|
61
|
+
`search` 返回时:
|
|
62
|
+
- 有 summary → 返回 summary
|
|
63
|
+
- 无 summary → 返回 content 前 100 字 + "..."
|
|
64
|
+
- 始终返回 factId、category、trustScore、score
|
|
65
|
+
- 不返回完整 content(减少 token)
|
|
66
|
+
|
|
67
|
+
## Risks / Trade-offs
|
|
68
|
+
|
|
69
|
+
- **[误合并]** → Jaccard > 0.6 可能误判相似内容。缓解:合并前 log 记录,dream report 列出所有合并对
|
|
70
|
+
- **[summary 质量低]** → 规则提取的前 2 句可能不是最关键的。缓解:用户可手动 update summary
|
|
71
|
+
- **[不可逆删除]** → 合并后删除冗余 fact 是不可逆的。缓解:dream 前自动备份数据库
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
mnemo 的记忆库随着长期使用会积累大量冗余、重叠、过长的 fact,导致检索精度下降、token 浪费。当前 `runLearning()` 只调 trust 分,不整理内容。需要一套后台自动整理机制(类似 OpenClaw Dreaming),定期合并去重、压缩摘要、分类修正,保持数据库精炼。
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- 新增 `dream` action(`fact_store(action="dream")`),执行三阶段整理:Collect → Consolidate → Evaluate
|
|
8
|
+
- 新增 `mnemo dream` CLI 命令,支持 cron 定时触发
|
|
9
|
+
- 合并 Jaccard > 0.6 的重叠 fact(保留最完整的,其余删除)
|
|
10
|
+
- 长 fact(content > 200 字且无 summary)自动提取关键句生成 summary
|
|
11
|
+
- 分类自动修正:按关键词规则将误分类的 fact 挪到正确 category
|
|
12
|
+
- 输出 dream report:合并/删除/压缩了什么,健康评分多少
|
|
13
|
+
- 搜索结果精简格式:返回 summary(优先)或 content 前 100 字,减少 token 消耗
|
|
14
|
+
|
|
15
|
+
## Capabilities
|
|
16
|
+
|
|
17
|
+
### New Capabilities
|
|
18
|
+
- `dream-cycle`: 后台定期整理记忆库(合并去重、摘要压缩、分类修正、健康评分)
|
|
19
|
+
- `compact-search`: 搜索结果精简格式(summary 优先、content 截断、限制条数)
|
|
20
|
+
|
|
21
|
+
### Modified Capabilities
|
|
22
|
+
|
|
23
|
+
(无已有 spec 需要修改)
|
|
24
|
+
|
|
25
|
+
## Impact
|
|
26
|
+
|
|
27
|
+
- `src/store.ts` — 新增 `runDream()` 方法(合并、压缩、分类修正、报告)
|
|
28
|
+
- `src/retriever.ts` — 搜索结果格式精简(返回 summary 而非完整 content)
|
|
29
|
+
- `src/server.ts` — `fact_store` 新增 `dream` action
|
|
30
|
+
- `src/init.ts` — `mnemo init` 可选配置 cron 定时 dream
|
|
31
|
+
- `tests/store.test.ts` — dream 相关测试
|
|
32
|
+
- 数据库 — dream 可能删除/合并 fact,不可逆操作需谨慎
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: 搜索结果精简格式
|
|
4
|
+
搜索返回结果 SHALL 优先返回 summary 而非完整 content,减少 token 消耗。
|
|
5
|
+
|
|
6
|
+
#### Scenario: 有 summary 的 fact
|
|
7
|
+
- **WHEN** 搜索结果中的 fact 有 summary 字段且非空
|
|
8
|
+
- **THEN** 返回 summary 作为 display 字段,不返回完整 content
|
|
9
|
+
|
|
10
|
+
#### Scenario: 无 summary 的 fact
|
|
11
|
+
- **WHEN** 搜索结果中的 fact 的 summary 为 NULL
|
|
12
|
+
- **THEN** 返回 content 前 100 字 + "..." 作为 display 字段
|
|
13
|
+
|
|
14
|
+
#### Scenario: 返回字段精简
|
|
15
|
+
- **WHEN** 搜索结果返回给调用方
|
|
16
|
+
- **THEN** 每条结果包含 factId、display(精简内容)、category、trustScore、score,不包含完整 content、keywords、tags 等冗余字段
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Dream action 整理记忆库
|
|
4
|
+
系统 SHALL 提供 `fact_store(action="dream")` 操作,执行三阶段整理:Collect → Consolidate → Evaluate。
|
|
5
|
+
|
|
6
|
+
#### Scenario: 合并重叠 fact
|
|
7
|
+
- **WHEN** 同 category 内两条 fact 的 Jaccard 相似度 > 0.6
|
|
8
|
+
- **THEN** 系统保留 content 更长的 fact,将另一条标记删除,并在 dream report 中记录合并对
|
|
9
|
+
|
|
10
|
+
#### Scenario: 压缩长 fact
|
|
11
|
+
- **WHEN** fact 的 content 长度 > 200 字且 summary 为 NULL
|
|
12
|
+
- **THEN** 系统从 content 提取前 2 个完整句子(总长 ≤ 150 字)写入 summary 字段
|
|
13
|
+
|
|
14
|
+
#### Scenario: 分类修正
|
|
15
|
+
- **WHEN** fact 的 category 与内容不匹配(如 identity 类 fact 内容包含"编码规范")
|
|
16
|
+
- **THEN** 系统根据关键词规则表将 fact 挪到正确 category
|
|
17
|
+
|
|
18
|
+
#### Scenario: Dream 前备份
|
|
19
|
+
- **WHEN** dream action 被触发
|
|
20
|
+
- **THEN** 系统在执行任何修改前,自动将数据库备份到 `~/.mnemo/backup/dream-<timestamp>.db`
|
|
21
|
+
|
|
22
|
+
#### Scenario: 输出 dream report
|
|
23
|
+
- **WHEN** dream 整理完成
|
|
24
|
+
- **THEN** 系统返回 JSON 报告,包含 merged、compressed、reclassified、deleted 计数和 health 统计
|
|
25
|
+
|
|
26
|
+
### Requirement: CLI dream 命令
|
|
27
|
+
系统 SHALL 提供 `mnemo dream` CLI 命令,手动触发 dream 整理。
|
|
28
|
+
|
|
29
|
+
#### Scenario: 手动执行 dream
|
|
30
|
+
- **WHEN** 用户运行 `npx mnemo dream` 或 `mnemo dream`
|
|
31
|
+
- **THEN** 系统执行完整 dream cycle 并输出 report 到 stdout
|
|
32
|
+
|
|
33
|
+
### Requirement: 高频 fact 保护
|
|
34
|
+
Dream 整理 SHALL 保护检索次数 > 100 的 fact 不被删除。
|
|
35
|
+
|
|
36
|
+
#### Scenario: 高频 fact 不被合并删除
|
|
37
|
+
- **WHEN** 两条 fact 满足合并条件,但其中一条 retrieval_count > 100
|
|
38
|
+
- **THEN** 系统保留高频 fact,仅删除另一条低频 fact
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## 1. 搜索结果精简
|
|
2
|
+
|
|
3
|
+
- [ ] 1.1 修改 `ScoredFact` 类型定义,新增 `display` 字段(精简内容)
|
|
4
|
+
- [ ] 1.2 修改 `retriever.ts` search 方法:有 summary 用 summary,无 summary 截取 content 前 100 字
|
|
5
|
+
- [ ] 1.3 修改 `server.ts` search/probe/related/reason 响应格式:返回 display 而非完整 content
|
|
6
|
+
- [ ] 1.4 更新 `tests/retriever.test.ts` 验证精简格式
|
|
7
|
+
|
|
8
|
+
## 2. Dream Cycle - Store 层
|
|
9
|
+
|
|
10
|
+
- [ ] 2.1 实现 `mergeOverlappingFacts()` — 同 category 内 Jaccard > 0.6 的 fact 合并,高频(retrieval > 100)保护
|
|
11
|
+
- [ ] 2.2 实现 `compressLongFacts()` — content > 200 字且无 summary 的 fact 自动提取前 2 句
|
|
12
|
+
- [ ] 2.3 实现 `reclassifyFacts()` — 按关键词规则表修正 category
|
|
13
|
+
- [ ] 2.4 实现 `backupDatabase()` — dream 前备份到 `~/.mnemo/backup/dream-<timestamp>.db`
|
|
14
|
+
- [ ] 2.5 实现 `runDream()` — 编排以上步骤,生成 dream report(merged/compressed/reclassified/deleted + health)
|
|
15
|
+
- [ ] 2.6 新增 `dream` action 到 `server.ts` 的 fact_store handler
|
|
16
|
+
|
|
17
|
+
## 3. CLI 命令
|
|
18
|
+
|
|
19
|
+
- [ ] 3.1 新增 `src/dream.ts` CLI 入口,执行 `store.runDream()` 并输出 report
|
|
20
|
+
- [ ] 3.2 在 `package.json` 添加 `mnemo dream` bin 入口
|
|
21
|
+
|
|
22
|
+
## 4. 测试
|
|
23
|
+
|
|
24
|
+
- [ ] 4.1 `tests/store.test.ts` — mergeOverlappingFacts 合并 + 高频保护测试
|
|
25
|
+
- [ ] 4.2 `tests/store.test.ts` — compressLongFacts 提取 summary 测试
|
|
26
|
+
- [ ] 4.3 `tests/store.test.ts` — reclassifyFacts 分类修正测试
|
|
27
|
+
- [ ] 4.4 `tests/store.test.ts` — runDream 端到端测试(备份数据库 + 整理 + report)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
mnemo-mcp 是一个纯全局记忆 MCP Server(不含项目记忆),70 条事实存储在 `~/.mnemo/facts.db`(SQLite + FTS5)。当前检索管线:FTS5 BM25 候选 → Jaccard 重排序 → trust_score 加权 → 时间衰减。
|
|
4
|
+
|
|
5
|
+
v0.1.4 引入了动态权重(短查询 FTS 0.7/长查询 0.3)、relevance gate (0.15)、content dedup (Jaccard>0.7) 和 query refinement,但实测检索准确率反而下降。根本原因:
|
|
6
|
+
|
|
7
|
+
1. **动态权重适得其反**:长查询给 FTS5 只留 30%,BM25 正确排序被 Jaccard 覆盖
|
|
8
|
+
2. **"万能条"问题**:29% fact 超 500 字(最长 3921 字),字多容易匹配但 helpful 率极低(0.6%)
|
|
9
|
+
3. **无反馈闭环**:系统不知道哪些检索是好的,无法自我优化
|
|
10
|
+
|
|
11
|
+
约束:
|
|
12
|
+
- 纯 stdio MCP Server,无 HTTP 端点
|
|
13
|
+
- better-sqlite3 同步 API
|
|
14
|
+
- 不引入 ML 依赖(embedding 方案留给后续迭代)
|
|
15
|
+
- 向后兼容:已有 facts.db 不需要迁移
|
|
16
|
+
|
|
17
|
+
## Goals / Non-Goals
|
|
18
|
+
|
|
19
|
+
**Goals:**
|
|
20
|
+
- 回退 v3 检索改动,恢复 v2 准确率水平
|
|
21
|
+
- 通过 length penalty 解决"万能条"霸占检索
|
|
22
|
+
- 通过 summary 字段治理超长 fact 的数据质量
|
|
23
|
+
- 通过 retrieval_log + learn action 建立自学习数据基础
|
|
24
|
+
- 写入端质量控制:限制 content 长度,引导拆分
|
|
25
|
+
|
|
26
|
+
**Non-Goals:**
|
|
27
|
+
- 不引入 embedding/向量检索(后续迭代)
|
|
28
|
+
- 不自动拆分已有超长 fact(需要 LLM,留给手动触发或后续工具)
|
|
29
|
+
- 不修改 fact_feedback 机制(保持现有 helpful/unhelpful)
|
|
30
|
+
- 不改 MCP Resource 预热方案(已实现,保持)
|
|
31
|
+
|
|
32
|
+
## Decisions
|
|
33
|
+
|
|
34
|
+
### D1: 回退动态权重为静态 0.5/0.5
|
|
35
|
+
|
|
36
|
+
**选择**:恢复 v2 静态 FTS/Jaccard 权重各 0.5
|
|
37
|
+
**替代方案**:
|
|
38
|
+
- (a) 只调低动态权重范围(如 0.6/0.4)——治标不治本
|
|
39
|
+
- (b) 完全移除 Jaccard 只用 FTS5——会丢失部分长查询的 token 匹配能力
|
|
40
|
+
|
|
41
|
+
**理由**:v2 的静态 0.5/0.5 经用户确认比 v3 更准。FTS5 BM25 本身是成熟的排序算法,Jaccard 作为辅助校验即可,不应主导排序。
|
|
42
|
+
|
|
43
|
+
### D2: Length Penalty 公式
|
|
44
|
+
|
|
45
|
+
**选择**:`score *= min(1.0, 300 / matchText.length)`
|
|
46
|
+
|
|
47
|
+
其中 `matchText` 为 summary(非空时)或 content(summary 为空时)。即 summary 存在时用 summary 长度计算 penalty,避免有 summary 的 fact 被 content 长度过度惩罚。300 字以内不受影响,超过的线性衰减。
|
|
48
|
+
|
|
49
|
+
**替代方案**:
|
|
50
|
+
- (a) `300 / content.length`(不考虑 summary)——有 summary 时过度惩罚
|
|
51
|
+
- (b) 指数衰减 `score *= 0.99^length`——过度惩罚
|
|
52
|
+
- (c) 硬阈值(超 500 字直接排除)——太粗暴
|
|
53
|
+
|
|
54
|
+
**理由**:既然检索用的是 summary,penalty 也应该基于实际匹配文本的长度。一条有 summary(50字)的 2000 字 fact,penalty = 300/50 = 6 → cap 到 1.0,不受惩罚,符合预期。
|
|
55
|
+
|
|
56
|
+
### D3: retrieval_log 设计
|
|
57
|
+
|
|
58
|
+
**选择**:新建 `retrieval_log` 表,search 时自动插入,保留最近 5000 条
|
|
59
|
+
|
|
60
|
+
```sql
|
|
61
|
+
CREATE TABLE retrieval_log (
|
|
62
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
query TEXT NOT NULL,
|
|
64
|
+
results TEXT NOT NULL, -- JSON array of [{id, score}]
|
|
65
|
+
timestamp TEXT DEFAULT (datetime('now'))
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
每个返回的 fact 记录 `{id, score}`,便于后续分析"为什么某个 fact 总排第一"。
|
|
70
|
+
|
|
71
|
+
**替代方案**:
|
|
72
|
+
- (a) 只记 fact_ids 数组——粒度太粗,无法分析排序原因
|
|
73
|
+
- (b) 单独 retrieval_log_details 表——过度设计,JSON 数组够用
|
|
74
|
+
|
|
75
|
+
**理由**:`[{id, score}]` 在调试时能直接看到每次检索的评分分布,成本只是多存几个数字。5000 条上限覆盖约 1 个月的高频使用。
|
|
76
|
+
|
|
77
|
+
### D4: learn action 策略
|
|
78
|
+
|
|
79
|
+
**选择**:统计规则 + trust_score 调整 + 数据质量报告
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
对每条 fact:
|
|
83
|
+
rate = helpful_count / max(retrieval_count, 1)
|
|
84
|
+
|
|
85
|
+
if retrieval_count > 30:
|
|
86
|
+
if rate < 0.05: trust_score *= 0.9
|
|
87
|
+
if rate > 0.3: trust_score = min(1.0, trust_score + 0.05)
|
|
88
|
+
|
|
89
|
+
老化: 超过 60 天 last_retrieved_at → trust_score *= 0.95
|
|
90
|
+
|
|
91
|
+
返回:
|
|
92
|
+
{promoted, demoted, aged, unchanged,
|
|
93
|
+
long_facts: [{id, content_length, penalty, has_summary}]}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
aging 基于 `facts.last_retrieved_at` 字段(由 `logRetrieval` 自动更新),不依赖 retrieval_log 查询。
|
|
97
|
+
|
|
98
|
+
**替代方案**:
|
|
99
|
+
- (a) aging 从 retrieval_log 推导——retrieval_log 有 5000 条上限,旧记录可能被删导致判断不准
|
|
100
|
+
- (b) 完全不自动调整,只提供数据让人手动调——不够自动化
|
|
101
|
+
|
|
102
|
+
**理由**:`last_retrieved_at` 字段比从 retrieval_log 查询更可靠(不受日志上限影响)。`long_facts` 报告让用户知道哪些 fact 被 penalty 影响,引导补充 summary。
|
|
103
|
+
|
|
104
|
+
### D5: summary 字段 + 双阈值设计
|
|
105
|
+
|
|
106
|
+
**选择**:facts 表新增 `summary TEXT DEFAULT NULL` 列
|
|
107
|
+
|
|
108
|
+
- summary 非空时,检索用 summary 匹配(FTS5 也索引 summary)
|
|
109
|
+
- summary 为空时,退化为原有 content 匹配
|
|
110
|
+
|
|
111
|
+
两个阈值分工明确:
|
|
112
|
+
- **300 字**:length penalty 的计算阈值(matchText ≤ 300 不惩罚)
|
|
113
|
+
- **500 字**:写入端数据质量警告阈值(content > 500 且无 summary → 返回 warning)
|
|
114
|
+
|
|
115
|
+
**替代方案**:
|
|
116
|
+
- (a) 自动用 LLM 生成 summary——需要额外依赖,不适合 MCP Server
|
|
117
|
+
- (b) 不加 summary,只靠 length penalty——治标不治本
|
|
118
|
+
|
|
119
|
+
**理由**:300 管检索评分(匹配文本质量),500 管写入质量(数据治理提示),职责不重叠。
|
|
120
|
+
|
|
121
|
+
### D7: 保留 refineQuery
|
|
122
|
+
|
|
123
|
+
**选择**:保留 v3 的 `refineQuery()` 查询提炼
|
|
124
|
+
|
|
125
|
+
**理由**:refineQuery 的核心功能是过滤纯操作指令("运行测试"→ return null),避免无效检索。这个功能与权重策略无关,在静态权重下同样有用。移除它会导致"git commit"等操作也触发检索,浪费资源。
|
|
126
|
+
|
|
127
|
+
### D8: 启动时 learn 延迟执行
|
|
128
|
+
|
|
129
|
+
**选择**:server 启动时用 `process.nextTick()` 延迟执行 learn,不阻塞 stdio 初始化
|
|
130
|
+
|
|
131
|
+
**替代方案**:
|
|
132
|
+
- (a) 完全同步执行——可能阻塞 Claude 端初始化(未来上千条 fact 时)
|
|
133
|
+
- (b) 不自动执行,只手动调用——用户容易忘记
|
|
134
|
+
|
|
135
|
+
**理由**:better-sqlite3 是同步 API,70 条 fact 的遍历虽然只需毫秒,但未来数据增长后可能阻塞。`nextTick` 延迟确保 MCP 握手先完成,learn 在下一个事件循环执行。
|
|
136
|
+
|
|
137
|
+
### D9: 现有超长 fact 治理路径
|
|
138
|
+
|
|
139
|
+
**选择**:learn action 返回 `long_facts` 数据质量报告 + 新增 `audit` action
|
|
140
|
+
|
|
141
|
+
- learn 返回 `long_facts: [{id, content_length, penalty, has_summary}]`,列出被 length penalty 严重影响的 fact
|
|
142
|
+
- 新增 `fact_store(action="audit")` 返回完整数据质量报告:超长 fact 列表、无 summary 的长 fact、低 helpful 率 fact、老化候选
|
|
143
|
+
|
|
144
|
+
**理由**:用户需要知道为什么某些记忆突然排不上来(length penalty 影响),也需要工具引导治理。audit action 比 learn 更全面,专门做数据质量分析不改数据。
|
|
145
|
+
|
|
146
|
+
### D6: 移除 relevance gate
|
|
147
|
+
|
|
148
|
+
**选择**:完全移除 v3 的 `RELEVANCE_THRESHOLD = 0.15` 过滤
|
|
149
|
+
|
|
150
|
+
**理由**:0.15 阈值在短查询时经常误杀相关结果。length penalty 已经能更优雅地解决低质量问题。
|
|
151
|
+
|
|
152
|
+
## Risks / Trade-offs
|
|
153
|
+
|
|
154
|
+
- [Risk] summary 字段增加写入复杂度 → 回退策略:summary 为空时完全退化,零影响
|
|
155
|
+
- [Risk] learn action 误降核心记忆的 trust → 缓解:rate < 0.05 阈值很保守,需 30+ 次检索才能触发;trust 下降是渐进的(×0.9)
|
|
156
|
+
- [Risk] retrieval_log 表增长 → 缓解:保留最近 5000 条,超出自动删除最旧记录
|
|
157
|
+
- [Risk] length penalty 对无 summary 的超长 fact 影响过大 → 缓解:learn 返回 long_facts 报告 + audit action 引导用户补充 summary
|
|
158
|
+
- [Trade-off] 回退动态权重意味着放弃了"长查询偏 Jaccard"的策略 → 可接受:静态 0.5/0.5 经验证更准
|
|
159
|
+
- [Risk] 启动时 learn 在大数据量下阻塞 → 缓解:process.nextTick 延迟执行,MCP 握手先完成
|
|
160
|
+
|
|
161
|
+
## Migration Plan
|
|
162
|
+
|
|
163
|
+
1. ALTER TABLE facts ADD COLUMN summary TEXT DEFAULT NULL —— 向后兼容,无数据丢失
|
|
164
|
+
2. ALTER TABLE facts ADD COLUMN last_retrieved_at TEXT DEFAULT NULL —— 用于 aging 判断
|
|
165
|
+
3. CREATE TABLE retrieval_log —— 新表,无影响
|
|
166
|
+
4. 回退 retriever.ts 中的动态权重代码 —— 纯代码变更
|
|
167
|
+
5. 更新 FTS5 索引以包含 summary 列 —— 需要重建虚拟表(数据量 70 条,瞬间完成)
|
|
168
|
+
6. 发布为 minor version(v0.2.0),向后兼容 v0.1.4
|
|
169
|
+
|
|
170
|
+
## Open Questions
|
|
171
|
+
|
|
172
|
+
- ~~summary 为空时,是否应该自动用 content 前 100 字作为 fallback summary?~~ → 已决策:不自动截取,依赖用户/AI 提供或用 audit action 引导
|
|
173
|
+
- ~~learn action 的执行时机~~ → 已决策:启动时 process.nextTick 延迟执行
|
|
174
|
+
- ~~已有超长 fact 是否需要一次性治理~~ → 已决策:learn 返回 long_facts 报告 + audit action 引导
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
mnemo-mcp 当前记忆检索准确率不足,根因有三:(1) **数据质量差**——29% 的 fact 超过 500 字,最长 3921 字的"万能条"像黑洞一样吸走所有检索请求(#144 被检索 628 次仅 4 次 helpful,率 0.6%);(2) **v3 动态权重适得其反**——长查询给 FTS5 只留 30% 权重,把 BM25 的正确排序拉下来了(#60 "暖暖角色设定"FTS5 rank -10.17 排第一,但经 Jaccard 重排后被 #208 挤掉);(3) **没有反馈闭环**——70 条事实累计检索上万次,fact_feedback 不足 100 次,系统无法自我优化。需要从全生命周期角度治理:数据质量 → 检索策略 → 自学习闭环。
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- 回退 v3 动态权重,恢复 v2 静态 FTS/Jaccard 权重(0.5/0.5)
|
|
8
|
+
- 新增 **length penalty**:基于实际匹配文本(summary 或 content)长度的惩罚,超长无 summary 的 fact 自动降权
|
|
9
|
+
- 新增 **retrieval_log 表**:每次 search 自动记录 query + 每条结果的 `{id, score}`,为自学习和调试提供数据
|
|
10
|
+
- 新增 **`learn` action**:基于 rate 规则自动调整 trust_score + 数据质量报告(long_facts 列表)
|
|
11
|
+
- 新增 **`audit` action**:数据质量报告(超长无 summary、低 helpful 率、老化候选),不修改数据
|
|
12
|
+
- 新增 **summary 字段**:facts 表增加 summary + last_retrieved_at 列,超长 fact 存储提炼后的摘要
|
|
13
|
+
- 修改 **content 长度限制**:add/update 时 content 超 500 字且无 summary 返回警告(300-500 字只受 penalty 不警告)
|
|
14
|
+
- 移除 v3 的 **relevance gate**(0.15 阈值误杀有用结果)和 **动态权重逻辑**
|
|
15
|
+
- 保留 v3 的 **refineQuery**(过滤纯操作指令与权重策略无关)
|
|
16
|
+
|
|
17
|
+
## Capabilities
|
|
18
|
+
|
|
19
|
+
### New Capabilities
|
|
20
|
+
- `retrieval-log`: 检索日志自动记录,每次 search 写入 query + [{id, score}] + timestamp,同步更新 last_retrieved_at
|
|
21
|
+
- `self-learning`: learn action(trust 调整 + 数据质量报告)+ audit action(纯报告不改数据)+ 启动时 nextTick 延迟执行
|
|
22
|
+
- `length-penalty`: 基于 matchText(summary 优先于 content)的长度惩罚,有 summary 的 fact 不受 content 长度惩罚
|
|
23
|
+
- `fact-summary`: facts 表增加 summary + last_retrieved_at 字段 + 双阈值(300 penalty / 500 warning)
|
|
24
|
+
|
|
25
|
+
### Modified Capabilities
|
|
26
|
+
- `fact-retrieval`: 回退动态权重为静态 0.5/0.5,移除 relevance gate,集成 length penalty 和 summary 匹配,保留 refineQuery
|
|
27
|
+
|
|
28
|
+
## Impact
|
|
29
|
+
|
|
30
|
+
- `src/retriever.ts`:回退动态权重,移除 relevance gate,新增 length penalty(基于 matchText),summary 匹配
|
|
31
|
+
- `src/store.ts`:新增 retrieval_log 表、facts 表 summary + last_retrieved_at 字段、learn/audit 分析逻辑
|
|
32
|
+
- `src/server.ts`:新增 learn/audit action handler、search 时自动写入 retrieval_log、add/update 长度校验、启动 nextTick learn
|
|
33
|
+
- `src/types.ts`:新增 RetrievalLog 类型、summary/last_retrieved_at 字段、learn/audit 返回类型
|
|
34
|
+
- 数据库:新增 retrieval_log 表、facts 表加 summary + last_retrieved_at 列(ALTER TABLE,向后兼容)
|
|
35
|
+
- 向后兼容:所有变更对现有数据透明,summary 为空时退化为原始 content 匹配
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## MODIFIED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Static FTS/Jaccard weighting
|
|
4
|
+
检索评分 MUST 使用静态权重 FTS 0.5 / Jaccard 0.5,不再根据查询长度动态调整。
|
|
5
|
+
|
|
6
|
+
#### Scenario: Short query uses static weights
|
|
7
|
+
- **WHEN** 搜索 "深色主题"(2个token)
|
|
8
|
+
- **THEN** 评分使用 ftsWeight=0.5, jaccardWeight=0.5(不再是 0.7/0.3)
|
|
9
|
+
|
|
10
|
+
#### Scenario: Long query uses same static weights
|
|
11
|
+
- **WHEN** 搜索 "为什么 TypeScript 编译报错找不到模块"(8个token)
|
|
12
|
+
- **THEN** 评分使用 ftsWeight=0.5, jaccardWeight=0.5(不再是 0.3/0.7)
|
|
13
|
+
|
|
14
|
+
## REMOVED Requirements
|
|
15
|
+
|
|
16
|
+
### Requirement: Relevance gate threshold
|
|
17
|
+
**Reason**: 0.15 阈值在短查询时误杀相关结果,length penalty 能更优雅地解决低质量问题
|
|
18
|
+
**Migration**: 评分结果不再被 relevance gate 过滤,length penalty 自动惩罚低质量匹配
|
|
19
|
+
|
|
20
|
+
### Requirement: Dynamic scoring weights based on query length
|
|
21
|
+
**Reason**: 动态权重(短查询 FTS 0.7/长查询 0.3)实测降低检索准确率,BM25 正确排序被 Jaccard 覆盖
|
|
22
|
+
**Migration**: 所有查询统一使用静态 FTS 0.5 / Jaccard 0.5
|
|
23
|
+
|
|
24
|
+
## ADDED Requirements
|
|
25
|
+
|
|
26
|
+
### Requirement: Retrieval uses summary for matching when available
|
|
27
|
+
当 fact 的 summary 非空时,检索管线 MUST 使用 summary 进行 FTS5 和 Jaccard 匹配,而非 content。
|
|
28
|
+
|
|
29
|
+
#### Scenario: Search matches on summary
|
|
30
|
+
- **WHEN** 搜索 "VS Code" 且一条 fact 的 summary="用户偏好 VS Code"
|
|
31
|
+
- **THEN** FTS5 和 Jaccard 使用 summary 文本计算匹配度
|
|
32
|
+
|
|
33
|
+
#### Scenario: Returned results include full content
|
|
34
|
+
- **WHEN** 搜索返回一条 fact(summary 非空)
|
|
35
|
+
- **THEN** 返回的 JSON 中 content 字段为完整原始内容,summary 字段为摘要
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: facts table has summary and last_retrieved_at columns
|
|
4
|
+
`facts` 表 MUST 包含 `summary TEXT DEFAULT NULL` 和 `last_retrieved_at TEXT DEFAULT NULL` 列。summary 非空时用于检索匹配,为空时退化为 content 匹配。last_retrieved_at 由检索日志写入时自动更新。
|
|
5
|
+
|
|
6
|
+
#### Scenario: Add fact without summary
|
|
7
|
+
- **WHEN** 调用 `fact_store(action="add", content="用户偏好深色主题")`
|
|
8
|
+
- **THEN** summary 为 NULL,last_retrieved_at 为 NULL,检索时使用 content
|
|
9
|
+
|
|
10
|
+
#### Scenario: Add fact with summary
|
|
11
|
+
- **WHEN** 调用 `fact_store(action="add", content="...", summary="用户偏好深色主题")`
|
|
12
|
+
- **THEN** summary 被存储,检索时优先使用 summary 匹配
|
|
13
|
+
|
|
14
|
+
### Requirement: Write operation warns on long content without summary(500 字阈值)
|
|
15
|
+
当 add/update 的 content 长度超过 500 字且未提供 summary 时,系统 MUST 返回警告提示。
|
|
16
|
+
|
|
17
|
+
#### Scenario: Long fact without summary triggers warning
|
|
18
|
+
- **WHEN** 调用 `fact_store(action="add", content="<600字内容>")` 且未提供 summary
|
|
19
|
+
- **THEN** 操作成功,但返回 JSON 包含 `warnings: ["content 超过 500 字,建议提供 summary 或拆分为多条 fact"]`
|
|
20
|
+
|
|
21
|
+
#### Scenario: Long fact with summary has no warning
|
|
22
|
+
- **WHEN** 调用 `fact_store(action="add", content="<600字内容>", summary="核心摘要")`
|
|
23
|
+
- **THEN** 操作成功,无长度警告
|
|
24
|
+
|
|
25
|
+
#### Scenario: 300-500 字 content without summary has no warning
|
|
26
|
+
- **WHEN** 调用 `fact_store(action="add", content="<400字内容>")` 且未提供 summary
|
|
27
|
+
- **THEN** 操作成功,无警告(300-500 字处于灰色地带,只受 length penalty 影响不受写入警告)
|
|
28
|
+
|
|
29
|
+
### Requirement: FTS5 indexes summary when present
|
|
30
|
+
FTS5 虚拟表 MUST 索引 summary 列。当 summary 非空时,FTS5 使用 summary 进行匹配;summary 为空时使用 content。
|
|
31
|
+
|
|
32
|
+
#### Scenario: FTS5 matches on summary
|
|
33
|
+
- **WHEN** 一条 fact 的 content="很长很长的内容..."(2000字),summary="用户偏好 VS Code"
|
|
34
|
+
- **THEN** 搜索 "VS Code" 时能通过 summary 匹配到该 fact
|
|
35
|
+
|
|
36
|
+
#### Scenario: FTS5 falls back to content when no summary
|
|
37
|
+
- **WHEN** 一条 fact 的 summary=NULL,content="用户偏好 VS Code"
|
|
38
|
+
- **THEN** 搜索 "VS Code" 时通过 content 匹配到该 fact
|
|
39
|
+
|
|
40
|
+
### Requirement: Backward compatible migration
|
|
41
|
+
ALTER TABLE 为已有数据库添加新列时 MUST 不丢失任何现有数据。
|
|
42
|
+
|
|
43
|
+
#### Scenario: Existing database gets new columns
|
|
44
|
+
- **WHEN** mnemo-mcp 启动且 facts 表没有 summary 或 last_retrieved_at 列
|
|
45
|
+
- **THEN** 自动执行 ALTER TABLE 添加缺失列,所有现有 fact 不受影响
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Scoring formula includes length penalty based on match text
|
|
4
|
+
检索评分公式 MUST 对实际匹配文本(summary 非空时用 summary,否则用 content)长度超过 300 字的 fact 施加线性惩罚:`score *= min(1.0, 300 / matchText.length)`。
|
|
5
|
+
|
|
6
|
+
#### Scenario: Short fact gets no penalty
|
|
7
|
+
- **WHEN** 检索到一个 content 长度为 150 字、summary 为 NULL 的 fact,原始 score=0.5
|
|
8
|
+
- **THEN** matchText = content (150字),最终 score = 0.5 × min(1.0, 300/150) = 0.5
|
|
9
|
+
|
|
10
|
+
#### Scenario: Long fact without summary gets penalized
|
|
11
|
+
- **WHEN** 检索到一个 content 长度为 1500 字、summary 为 NULL 的 fact,原始 score=0.5
|
|
12
|
+
- **THEN** matchText = content (1500字),最终 score = 0.5 × min(1.0, 300/1500) = 0.1
|
|
13
|
+
|
|
14
|
+
#### Scenario: Long fact with short summary gets no penalty
|
|
15
|
+
- **WHEN** 检索到一个 content 长度为 2000 字、summary 长度为 50 字的 fact,原始 score=0.5
|
|
16
|
+
- **THEN** matchText = summary (50字),最终 score = 0.5 × min(1.0, 300/50) = 0.5 × 1.0 = 0.5
|
|
17
|
+
|
|
18
|
+
#### Scenario: Boundary at 300 chars of match text
|
|
19
|
+
- **WHEN** 检索到一个 matchText 长度恰好 300 字的 fact
|
|
20
|
+
- **THEN** penalty = min(1.0, 300/300) = 1.0,无惩罚
|
|
21
|
+
|
|
22
|
+
### Requirement: Length penalty applies after all other scoring
|
|
23
|
+
length penalty MUST 在 FTS/Jaccard/trust 评分计算完成后、排序前应用。
|
|
24
|
+
|
|
25
|
+
#### Scenario: Penalty stacks with trust score
|
|
26
|
+
- **WHEN** 一个 fact 的 fts+jaccard relevance=0.6, trust=0.8, matchText 长度=900 字, summary=NULL
|
|
27
|
+
- **THEN** score = 0.6 × 0.8 × min(1.0, 300/900) = 0.48 × 0.333 = 0.16
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Search operations automatically log retrieval results
|
|
4
|
+
每次 `search` 操作 MUST 自动将 query 和返回的 fact 结果写入 `retrieval_log` 表,包含每个 fact 的 id 和 score。
|
|
5
|
+
|
|
6
|
+
#### Scenario: Search logs query and scored results
|
|
7
|
+
- **WHEN** 调用 `fact_store(action="search", query="用户偏好")` 返回 fact_ids [27, 51, 60] 对应 scores [0.45, 0.32, 0.28]
|
|
8
|
+
- **THEN** `retrieval_log` 表新增一条记录,query="用户偏好",results='[{"id":27,"score":0.45},{"id":51,"score":0.32},{"id":60,"score":0.28}]'
|
|
9
|
+
|
|
10
|
+
#### Scenario: Empty search results are still logged
|
|
11
|
+
- **WHEN** 调用 `fact_store(action="search", query="完全不存在的查询")` 返回空结果
|
|
12
|
+
- **THEN** `retrieval_log` 表新增一条记录,query="完全不存在的查询",results="[]"
|
|
13
|
+
|
|
14
|
+
### Requirement: Retrieval log has a size cap
|
|
15
|
+
`retrieval_log` 表 MUST 保留最近 5000 条记录,超出时自动删除最旧记录。
|
|
16
|
+
|
|
17
|
+
#### Scenario: Auto-prune when exceeding 5000 entries
|
|
18
|
+
- **WHEN** `retrieval_log` 已有 5000 条记录,新 search 写入第 5001 条
|
|
19
|
+
- **THEN** 最旧的一条记录被删除,总数保持 5000
|
|
20
|
+
|
|
21
|
+
### Requirement: retrieval_log table schema
|
|
22
|
+
系统 MUST 创建以下表结构:
|
|
23
|
+
```sql
|
|
24
|
+
CREATE TABLE retrieval_log (
|
|
25
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
26
|
+
query TEXT NOT NULL,
|
|
27
|
+
results TEXT NOT NULL,
|
|
28
|
+
timestamp TEXT DEFAULT (datetime('now'))
|
|
29
|
+
);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Scenario: Table exists after database initialization
|
|
33
|
+
- **WHEN** mnemo-mcp 启动并打开 `~/.mnemo/facts.db`
|
|
34
|
+
- **THEN** `retrieval_log` 表存在且符合上述 schema
|
|
35
|
+
|
|
36
|
+
### Requirement: logRetrieval updates last_retrieved_at
|
|
37
|
+
每次写入 retrieval_log 时 MUST 同步更新返回的每条 fact 的 `last_retrieved_at` 字段。
|
|
38
|
+
|
|
39
|
+
#### Scenario: last_retrieved_at is updated on retrieval
|
|
40
|
+
- **WHEN** 搜索返回 fact_ids [27, 51]
|
|
41
|
+
- **THEN** fact 27 和 51 的 `last_retrieved_at` 被更新为当前时间
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: learn action adjusts trust scores automatically
|
|
4
|
+
系统 SHALL 提供 `fact_store(action="learn")` 操作,基于检索日志统计自动调整各 fact 的 trust_score。
|
|
5
|
+
|
|
6
|
+
#### Scenario: High retrieval low helpful fact gets demoted
|
|
7
|
+
- **WHEN** 调用 `fact_store(action="learn")` 且某 fact 的 retrieval_count=100, helpful_count=2 (rate=2%)
|
|
8
|
+
- **THEN** 该 fact 的 trust_score 被乘以 0.9
|
|
9
|
+
|
|
10
|
+
#### Scenario: High helpful rate fact gets promoted
|
|
11
|
+
- **WHEN** 调用 `fact_store(action="learn")` 且某 fact 的 retrieval_count=50, helpful_count=20 (rate=40%)
|
|
12
|
+
- **THEN** 该 fact 的 trust_score 增加 0.05(上限 1.0)
|
|
13
|
+
|
|
14
|
+
#### Scenario: Low retrieval count facts are not adjusted
|
|
15
|
+
- **WHEN** 调用 `fact_store(action="learn")` 且某 fact 的 retrieval_count=10
|
|
16
|
+
- **THEN** 该 fact 的 trust_score 不受 rate 规则影响(低于 30 次阈值)
|
|
17
|
+
|
|
18
|
+
### Requirement: learn action applies aging decay based on last_retrieved_at
|
|
19
|
+
系统 MUST 对 `last_retrieved_at` 超过 60 天的 fact 施加老化衰减,trust_score 乘以 0.95。
|
|
20
|
+
|
|
21
|
+
#### Scenario: Stale fact gets aged
|
|
22
|
+
- **WHEN** 调用 `fact_store(action="learn")` 且某 fact 的 last_retrieved_at 在 61 天前
|
|
23
|
+
- **THEN** 该 fact 的 trust_score 被乘以 0.95
|
|
24
|
+
|
|
25
|
+
#### Scenario: Recently retrieved fact is not aged
|
|
26
|
+
- **WHEN** 调用 `fact_store(action="learn")` 且某 fact 的 last_retrieved_at 在 10 天前
|
|
27
|
+
- **THEN** 该 fact 不受老化规则影响
|
|
28
|
+
|
|
29
|
+
#### Scenario: Never retrieved fact gets aged
|
|
30
|
+
- **WHEN** 调用 `fact_store(action="learn")` 且某 fact 的 last_retrieved_at 为 NULL
|
|
31
|
+
- **THEN** 该 fact 被视为从未检索,不受老化规则影响(新 fact 保护期)
|
|
32
|
+
|
|
33
|
+
### Requirement: learn returns adjustment summary with quality report
|
|
34
|
+
`learn` action MUST 返回调整摘要,包含被调整的 fact 数量、方向以及数据质量报告。
|
|
35
|
+
|
|
36
|
+
#### Scenario: Learn returns summary with long_facts report
|
|
37
|
+
- **WHEN** 调用 `fact_store(action="learn")`
|
|
38
|
+
- **THEN** 返回 JSON 包含:
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"promoted": 2,
|
|
42
|
+
"demoted": 5,
|
|
43
|
+
"aged": 3,
|
|
44
|
+
"unchanged": 60,
|
|
45
|
+
"long_facts": [
|
|
46
|
+
{"id": 144, "content_length": 3921, "penalty": 0.077, "has_summary": false},
|
|
47
|
+
{"id": 169, "content_length": 3095, "penalty": 0.097, "has_summary": false}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Requirement: Server startup triggers learn with non-blocking delay
|
|
53
|
+
mnemo-mcp 启动时 MUST 通过 `process.nextTick()` 延迟执行 learn,不阻塞 MCP stdio 握手。
|
|
54
|
+
|
|
55
|
+
#### Scenario: Learn runs after MCP handshake
|
|
56
|
+
- **WHEN** mnemo-mcp server 启动完成 MCP 初始化
|
|
57
|
+
- **THEN** 在下一个事件循环中自动执行 learn 并输出调整摘要到 stderr
|
|
58
|
+
|
|
59
|
+
### Requirement: audit action returns data quality report
|
|
60
|
+
系统 SHALL 提供 `fact_store(action="audit")` 操作,返回完整数据质量报告,不修改任何数据。
|
|
61
|
+
|
|
62
|
+
#### Scenario: Audit returns quality report
|
|
63
|
+
- **WHEN** 调用 `fact_store(action="audit")`
|
|
64
|
+
- **THEN** 返回 JSON 包含:超长 fact 列表(>500字无 summary)、低 helpful 率 fact(rate<5%且retrieval>30)、老化候选(>60天未检索)、总统计
|
|
65
|
+
|
|
66
|
+
#### Scenario: Audit does not modify data
|
|
67
|
+
- **WHEN** 调用 `fact_store(action="audit")`
|
|
68
|
+
- **THEN** 不修改任何 fact 的 trust_score、retrieval_count 或其他字段
|