@morningljn/mnemo 0.1.3 → 0.1.4
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/README.md +43 -14
- package/dist/init.js +16 -8
- package/dist/init.js.map +1 -1
- package/dist/refine.d.ts +14 -0
- package/dist/refine.js +115 -0
- package/dist/refine.js.map +1 -0
- package/dist/resources.d.ts +27 -0
- package/dist/resources.js +56 -0
- package/dist/resources.js.map +1 -0
- package/dist/retriever.d.ts +3 -1
- package/dist/retriever.js +38 -26
- package/dist/retriever.js.map +1 -1
- package/dist/server.js +7 -0
- package/dist/server.js.map +1 -1
- package/docs/superpowers/plans/2026-05-15-mnemo-mcp.md +1154 -0
- package/docs/superpowers/plans/2026-05-16-mnemo-query-cache.md +613 -0
- package/docs/superpowers/plans/2026-05-16-retrieval-and-injection-optimization.md +770 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/design.md +83 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/proposal.md +32 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/specs/fact-retrieval/spec.md +75 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/specs/fact-store/spec.md +83 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/specs/mcp-server/spec.md +34 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/specs/security/spec.md +37 -0
- package/openspec/changes/archive/2026-05-15-mnemo-mcp/tasks.md +44 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/design.md +96 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/proposal.md +29 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/specs/batch-operations/spec.md +42 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/specs/perf-metrics/spec.md +55 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/specs/query-cache/spec.md +65 -0
- package/openspec/changes/archive/2026-05-16-mnemo-query-cache/tasks.md +45 -0
- package/openspec/changes/retrieval-and-injection-optimization/.openspec.yaml +2 -0
- package/openspec/changes/retrieval-and-injection-optimization/design.md +117 -0
- package/openspec/changes/retrieval-and-injection-optimization/proposal.md +30 -0
- package/openspec/changes/retrieval-and-injection-optimization/specs/adaptive-scoring/spec.md +43 -0
- package/openspec/changes/retrieval-and-injection-optimization/specs/injection-protocol/spec.md +48 -0
- package/openspec/changes/retrieval-and-injection-optimization/specs/mcp-resources/spec.md +39 -0
- package/openspec/changes/retrieval-and-injection-optimization/specs/query-refinement/spec.md +39 -0
- package/openspec/changes/retrieval-and-injection-optimization/tasks.md +33 -0
- package/openspec/config.yaml +20 -0
- package/package.json +1 -1
- package/src/init.ts +17 -9
- package/src/refine.ts +127 -0
- package/src/resources.ts +78 -0
- package/src/retriever.ts +40 -26
- package/src/server.ts +8 -0
- package/tests/refine.test.ts +52 -0
- package/tests/resource.test.ts +62 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: Query results are cached with TTL
|
|
4
|
+
The system SHALL cache query results and return cached data for identical queries within the TTL period.
|
|
5
|
+
|
|
6
|
+
Cache key:
|
|
7
|
+
- SHALL be derived from query string, category, limit, and minTrust parameters
|
|
8
|
+
- SHALL NOT include trust_score or updated_at values (these change frequently)
|
|
9
|
+
|
|
10
|
+
Cache behavior:
|
|
11
|
+
- Cache TTL SHALL be 60 seconds by default
|
|
12
|
+
- Cache SHALL be stored in process memory (Map)
|
|
13
|
+
- Cache SHALL be cleared on any write operation (add/update/remove)
|
|
14
|
+
|
|
15
|
+
#### Scenario: Identical query returns cached result
|
|
16
|
+
- **WHEN** a search query with parameters {query: "用户偏好", category: "identity", limit: 10} is executed
|
|
17
|
+
- **AND** the same query is executed again within 60 seconds
|
|
18
|
+
- **THEN** the second call SHALL return the cached results
|
|
19
|
+
- **AND** no database query SHALL be executed
|
|
20
|
+
|
|
21
|
+
#### Scenario: Cache cleared on fact addition
|
|
22
|
+
- **WHEN** a new fact is added via fact_store
|
|
23
|
+
- **THEN** all cached query results SHALL be invalidated
|
|
24
|
+
- **AND** the next query SHALL hit the database
|
|
25
|
+
|
|
26
|
+
#### Scenario: Cache entry expires after TTL
|
|
27
|
+
- **WHEN** a cached query result ages beyond 60 seconds
|
|
28
|
+
- **THEN** the next identical query SHALL execute against the database
|
|
29
|
+
- **AND** generate a new cache entry
|
|
30
|
+
|
|
31
|
+
### Requirement: Cache supports all search actions
|
|
32
|
+
The system SHALL apply caching to all read-only fact_store actions.
|
|
33
|
+
|
|
34
|
+
Cached actions:
|
|
35
|
+
- search
|
|
36
|
+
- probe
|
|
37
|
+
- related
|
|
38
|
+
- reason
|
|
39
|
+
- contradict
|
|
40
|
+
- list
|
|
41
|
+
|
|
42
|
+
Non-cached actions:
|
|
43
|
+
- add
|
|
44
|
+
- update
|
|
45
|
+
- remove
|
|
46
|
+
|
|
47
|
+
#### Scenario: Probe query is cached
|
|
48
|
+
- **WHEN** a probe action is executed with entity "TypeScript"
|
|
49
|
+
- **AND** the same probe is executed again within TTL
|
|
50
|
+
- **THEN** the second call SHALL return cached results
|
|
51
|
+
|
|
52
|
+
### Requirement: Cache hit/miss is trackable
|
|
53
|
+
The system SHALL record cache statistics when debug mode is enabled.
|
|
54
|
+
|
|
55
|
+
Debug metrics:
|
|
56
|
+
- Total queries processed
|
|
57
|
+
- Cache hits and misses
|
|
58
|
+
- Cache hit ratio (hits / total)
|
|
59
|
+
- Average query execution time (cache miss only)
|
|
60
|
+
|
|
61
|
+
#### Scenario: Debug mode logs cache metrics
|
|
62
|
+
- **WHEN** MNEMO_DEBUG=1 is set
|
|
63
|
+
- **AND** a query is executed
|
|
64
|
+
- **THEN** the system SHALL log whether the query was a cache hit or miss
|
|
65
|
+
- **AND** SHALL include execution time for cache misses
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## 1. Query Cache Layer
|
|
2
|
+
|
|
3
|
+
- [ ] 1.1 Create `src/cache.ts` with `QueryCache` class
|
|
4
|
+
- [ ] 1.2 Implement cache key generation: hash of query + category + limit + minTrust
|
|
5
|
+
- [ ] 1.3 Implement `get(key)` and `set(key, results)` with TTL (60s)
|
|
6
|
+
- [ ] 1.4 Implement `clear()` for cache invalidation on writes
|
|
7
|
+
- [ ] 1.5 Integrate cache into `FactRetriever.search()` — check cache before DB query
|
|
8
|
+
- [ ] 1.6 Integrate cache into `FactRetriever.probe()`, `related()`, `reason()`, `contradict()`
|
|
9
|
+
- [ ] 1.7 Call `cache.clear()` on add/update/remove operations in server.ts
|
|
10
|
+
|
|
11
|
+
## 2. Batch Operations
|
|
12
|
+
|
|
13
|
+
- [ ] 2.1 Update `FactStoreArgs` type in `src/types.ts`: `content` accepts `string | string[]`
|
|
14
|
+
- [ ] 2.2 Update `fact_id` type: accepts `number | number[]` for batch remove
|
|
15
|
+
- [ ] 2.3 Implement batch add handler in `server.ts`: iterate array, call `store.addFact()` per item
|
|
16
|
+
- [ ] 2.4 Implement batch remove handler in `server.ts`: iterate array, call `store.removeFact()` per item
|
|
17
|
+
- [ ] 2.5 Update response format: return array of individual results for batch operations
|
|
18
|
+
- [ ] 2.6 Ensure backward compatibility: single string/number still works
|
|
19
|
+
|
|
20
|
+
## 3. Performance Metrics
|
|
21
|
+
|
|
22
|
+
- [ ] 3.1 Create `src/metrics.ts` with `PerfMetrics` class
|
|
23
|
+
- [ ] 3.2 Implement query timing: record start/end timestamps around retriever calls
|
|
24
|
+
- [ ] 3.3 Implement retrieval path tracking: log which fallback stages were executed
|
|
25
|
+
- [ ] 3.4 Implement cache hit/miss tracking
|
|
26
|
+
- [ ] 3.5 Implement aggregate statistics: totalQueries, cacheHits, cacheMisses, avgQueryTime
|
|
27
|
+
- [ ] 3.6 Add `MNEMO_DEBUG` env var check: only record metrics when set to "1"
|
|
28
|
+
- [ ] 3.7 Add debug log output: print metrics after each query when debug enabled
|
|
29
|
+
|
|
30
|
+
## 4. Startup Optimization
|
|
31
|
+
|
|
32
|
+
- [ ] 4.1 Remove `auditContradictions()` from startup path in `server.ts`
|
|
33
|
+
- [ ] 4.2 Remove `decayTrustScores()` from startup path in `server.ts`
|
|
34
|
+
- [ ] 4.3 Add lazy initialization: first tool call triggers audit + decay if not yet run
|
|
35
|
+
- [ ] 4.4 Add flag to prevent duplicate lazy initialization
|
|
36
|
+
|
|
37
|
+
## 5. Testing & Verification
|
|
38
|
+
|
|
39
|
+
- [ ] 5.1 Verify identical query returns cached result within TTL
|
|
40
|
+
- [ ] 5.2 Verify cache is cleared after fact addition
|
|
41
|
+
- [ ] 5.3 Verify batch add works with string array
|
|
42
|
+
- [ ] 5.4 Verify batch remove works with number array
|
|
43
|
+
- [ ] 5.5 Verify single fact add remains backward compatible
|
|
44
|
+
- [ ] 5.6 Verify debug mode logs query timing and cache stats
|
|
45
|
+
- [ ] 5.7 Verify startup is faster (no immediate audit/decay)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
mnemo-mcp 是纯全局记忆 MCP server,服务于 Claude Code / Codex 等客户端。当前架构:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
用户消息 → CLAUDE.md 规则触发 fact_store(search) → MCP tool call → FTS5 检索 → 注入 context
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
当前问题:
|
|
10
|
+
- **每条消息**都触发一次 search MCP 调用,90%+ 的消息与记忆无关(如"git status""运行测试")
|
|
11
|
+
- 查询直接使用原始用户消息,"帮我重构 auth 模块"中的"帮我""重构"是噪音 token
|
|
12
|
+
- FTS/Jaccard 权重固定 0.5/0.5,短查询和长查询用同一套评分
|
|
13
|
+
- Category 多样性策略硬性限制每个 category 只取 top1,general 类事实被误伤
|
|
14
|
+
|
|
15
|
+
事实库现状:70+ 条全局事实,5 个 category(identity/coding_style/tool_pref/workflow/general)。
|
|
16
|
+
|
|
17
|
+
## Goals / Non-Goals
|
|
18
|
+
|
|
19
|
+
**Goals:**
|
|
20
|
+
- 检索准确率:减少噪音召回,提高 top-K 结果与查询的语义相关性
|
|
21
|
+
- 注入效率:将 MCP 调用频率从"每条消息"降至"会话预热 + 按需补充"
|
|
22
|
+
- 向后兼容:所有变更对现有 tool 调用接口透明
|
|
23
|
+
|
|
24
|
+
**Non-Goals:**
|
|
25
|
+
- 不引入向量数据库或 embedding 模型(保持零依赖、纯本地运行)
|
|
26
|
+
- 不做项目记忆(scope 明确只做全局记忆)
|
|
27
|
+
- 不改 MCP 协议本身,只利用现有 Resource + Tool 机制
|
|
28
|
+
|
|
29
|
+
## Decisions
|
|
30
|
+
|
|
31
|
+
### D1: MCP Resource 暴露全局记忆
|
|
32
|
+
|
|
33
|
+
**选择**:为每个 category 注册 MCP Resource URI(`mnemo://global/{category}`),返回该 category 下 trust 排名 top-N 的事实摘要。
|
|
34
|
+
|
|
35
|
+
**替代方案**:
|
|
36
|
+
- A) SessionStart hook 触发 bulk search → 需要客户端支持,且仍有 MCP 调用开销
|
|
37
|
+
- B) 静态文件注入 → 无法动态更新
|
|
38
|
+
|
|
39
|
+
**理由**:MCP Resource 是协议原生的"客户端主动拉取"机制。Claude Code 在 session 启动时自动拉取所有 MCP Resource 并注入 system context。这实现了**零 MCP tool call 的预热注入**——LLM 直接在 system prompt 中看到记忆,不需要主动调 search。
|
|
40
|
+
|
|
41
|
+
Resource URI 设计:
|
|
42
|
+
```
|
|
43
|
+
mnemo://global/identity → identity 类 top-10
|
|
44
|
+
mnemo://global/coding_style → coding_style 类 top-10
|
|
45
|
+
mnemo://global/tool_pref → tool_pref 类 top-10
|
|
46
|
+
mnemo://global/workflow → workflow 类 top-10
|
|
47
|
+
mnemo://global/general → general 类 top-10
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
每个 Resource 返回 JSON 格式,客户端(Claude Code)会自动序列化为文本注入。
|
|
51
|
+
|
|
52
|
+
### D2: 查询提炼层
|
|
53
|
+
|
|
54
|
+
**选择**:在 `search()` 入口前加 `refineQuery()` 纯函数,做 token 级过滤。
|
|
55
|
+
|
|
56
|
+
**提炼策略**:
|
|
57
|
+
1. 移除中文虚词(已有 `CN_STOP_WORDS`,复用)
|
|
58
|
+
2. 移除动作词("帮我""看看""做一下""帮我看看"等)
|
|
59
|
+
3. 提取实体词(引号内容、大写开头词、书名号内容)
|
|
60
|
+
4. 如果提炼后为空(纯操作指令如"运行测试"),返回空查询 → 跳过检索
|
|
61
|
+
|
|
62
|
+
**不选 LLM-based 查询重写**:引入模型调用成本,违反零依赖原则。
|
|
63
|
+
|
|
64
|
+
### D3: 动态评分权重
|
|
65
|
+
|
|
66
|
+
**选择**:根据查询 token 数动态调整 FTS/Jaccard 权重。
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// 短查询(≤3 token)→ 更依赖 FTS 精确匹配
|
|
70
|
+
// 长查询(>3 token)→ 更依赖 Jaccard 语义覆盖
|
|
71
|
+
const ftsWeight = tokenCount <= 3 ? 0.7 : 0.3
|
|
72
|
+
const jaccardWeight = tokenCount <= 3 ? 0.3 : 0.7
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**不选机器学习权重**:过重,事实库规模不够训练。
|
|
76
|
+
|
|
77
|
+
### D4: 多样性策略修复
|
|
78
|
+
|
|
79
|
+
**选择**:去掉硬性 category-per-top1 限制,改为 relevance score 去重——如果两条事实的 Jaccard 相似度 > 0.7(内容高度重叠),只保留评分高的那条。
|
|
80
|
+
|
|
81
|
+
**理由**:全局记忆 5 个 category,general 占比通常 > 50%。硬性去重导致 general 只取 1 条,丢掉大量有效记忆。
|
|
82
|
+
|
|
83
|
+
### D5: 相关性门控
|
|
84
|
+
|
|
85
|
+
**选择**:在 trust 阈值(0.3)之上增加 relevance score 阈值(0.15)。score < 0.15 的结果不返回。
|
|
86
|
+
|
|
87
|
+
**理由**:trust 是事实质量的静态指标,relevance 是与当前查询的匹配度。两者必须同时满足。
|
|
88
|
+
|
|
89
|
+
### D6: 注入协议
|
|
90
|
+
|
|
91
|
+
**选择**:将 CLAUDE.md 规则 1 从"每条消息都搜"改为:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
会话启动 → MCP Resource 自动预热(系统层,LLM 不参与)
|
|
95
|
+
用户消息 → LLM 判断是否需要补充查询记忆
|
|
96
|
+
├─ 涉及个人偏好/习惯/工具选择 → search
|
|
97
|
+
├─ 纯操作/技术问题 → 不查
|
|
98
|
+
└─ 模糊 → 可查可不查(偏向不查)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**风险**:LLM 可能判断失误,遗漏该查的场景。但 Resource 预热已经覆盖了高频记忆,遗漏的主要是边缘场景。
|
|
102
|
+
|
|
103
|
+
## Risks / Trade-offs
|
|
104
|
+
|
|
105
|
+
| Risk | Mitigation |
|
|
106
|
+
|------|-----------|
|
|
107
|
+
| Resource 预热数据量过大占用 context window | 每个 category 限 top-10,总共 ≤50 条,约 2000 token |
|
|
108
|
+
| LLM 判断"不需要查"导致遗漏 | Resource 预热覆盖 80%+ 高频场景;边缘 case 通过 fact_feedback 自然淘汰 |
|
|
109
|
+
| 查询提炼过度过滤导致召回不足 | 保留原始查询作为 fallback(提炼为空时用原始查询) |
|
|
110
|
+
| 动态权重在中等长度查询时表现不稳定 | token 阈值 3 是经验值,可通过 fact_feedback 数据调优 |
|
|
111
|
+
| Content-based 去重(Jaccard > 0.7)可能误判 | 去重只影响 top-K 选择,不影响存储;误判只是多返回一条 |
|
|
112
|
+
|
|
113
|
+
## Open Questions
|
|
114
|
+
|
|
115
|
+
- Resource 返回格式:JSON 数组 vs Markdown 文本?Claude Code 如何渲染 MCP Resource 内容?
|
|
116
|
+
- 是否需要 `mnemo://global/all` 聚合 Resource,还是 5 个独立 Resource 分别拉取?
|
|
117
|
+
- CLAUDE.md 规则变更需要用户手动更新,是否提供自动迁移脚本?
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
mnemo-mcp 作为纯全局记忆系统(不含项目记忆),当前检索和注入机制存在两个核心问题:**检索准确率不足**(查询直接用原始用户消息、评分权重静态固定、category 多样性策略误伤高密度类),以及**注入成本过高**(CLAUDE.md 规则要求每条用户消息都触发一次 `fact_store(search)` MCP 调用,即使消息与记忆完全无关)。随着事实库增长到 70+ 条,这两个问题将导致噪音召回增加和 token 浪费加剧。
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
- 新增 **MCP Resource** 端点,暴露 5 个全局 category 的 top-N 高信任记忆,实现会话启动时零成本预热注入
|
|
8
|
+
- 新增**查询提炼层**(query refinement),从用户消息中提取记忆相关关键词,过滤动作词和无关 token
|
|
9
|
+
- **评分公式动态化**:根据查询长度和类型自适应调整 FTS/Jaccard 权重比例
|
|
10
|
+
- **注入时机重构**:从"每条消息都搜"改为"MCP Resource 预热 + 按需补充",需配合 CLAUDE.md 规则变更
|
|
11
|
+
- 修复 **category 多样性策略**:对高密度 category(如 general)允许多条入选,改为基于相关性去重而非硬性 category 去重
|
|
12
|
+
- 新增**相关性门控**:检索结果在 trust 阈值之上增加 relevance score 阈值过滤
|
|
13
|
+
|
|
14
|
+
## Capabilities
|
|
15
|
+
|
|
16
|
+
### New Capabilities
|
|
17
|
+
- `mcp-resources`: 通过 MCP Resource 协议暴露全局记忆,支持会话预热零调用注入
|
|
18
|
+
- `query-refinement`: 用户消息 → 记忆关键词提炼,过滤动作词/虚词/无关 token
|
|
19
|
+
- `adaptive-scoring`: 查询长度自适应的 FTS/Jaccard 权重分配 + relevance 门控
|
|
20
|
+
- `injection-protocol`: 优化后的注入协议定义(何时查、何时注入、何时跳过),需同步更新 CLAUDE.md 规则
|
|
21
|
+
|
|
22
|
+
### Modified Capabilities
|
|
23
|
+
<!-- 无既有 spec 需要修改 -->
|
|
24
|
+
|
|
25
|
+
## Impact
|
|
26
|
+
|
|
27
|
+
- `src/server.ts`:新增 Resource handler,修改 search 入口加查询提炼
|
|
28
|
+
- `src/retriever.ts`:评分公式动态化、多样性策略修复、relevance 门控
|
|
29
|
+
- `CLAUDE.md`(用户侧配置):规则 1 从"每条消息都搜"改为"会话预热 + 按需触发"
|
|
30
|
+
- 向后兼容:所有变更对现有 fact_store/fact_feedback tool 调用透明,不影响已有客户端
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: 查询长度自适应权重
|
|
4
|
+
系统 SHALL 根据查询 token 数动态调整 FTS 和 Jaccard 的权重比例。
|
|
5
|
+
|
|
6
|
+
- 短查询(token 数 ≤ 3):FTS 权重 0.7,Jaccard 权重 0.3(偏精确匹配)
|
|
7
|
+
- 长查询(token 数 > 3):FTS 权重 0.3,Jaccard 权重 0.7(偏语义覆盖)
|
|
8
|
+
|
|
9
|
+
#### Scenario: 短查询使用高 FTS 权重
|
|
10
|
+
- **WHEN** 查询为 "深色主题"(2 个 token)
|
|
11
|
+
- **THEN** 评分公式中 FTS 权重为 0.7,Jaccard 权重为 0.3
|
|
12
|
+
|
|
13
|
+
#### Scenario: 长查询使用高 Jaccard 权重
|
|
14
|
+
- **WHEN** 查询为 "为什么 TypeScript 编译报错找不到模块"(7 个 token)
|
|
15
|
+
- **THEN** 评分公式中 FTS 权重为 0.3,Jaccard 权重为 0.7
|
|
16
|
+
|
|
17
|
+
### Requirement: 相关性评分门控
|
|
18
|
+
系统 SHALL 在 trust 阈值之上增加 relevance score 阈值。综合评分(relevance × trustScore × temporalDecay)低于 0.15 的结果 SHALL 被过滤。
|
|
19
|
+
|
|
20
|
+
#### Scenario: 低相关性结果被过滤
|
|
21
|
+
- **WHEN** 一条事实 trust_score=0.9 但与查询的 relevance score=0.1
|
|
22
|
+
- **THEN** 综合评分 = 0.1 × 0.9 = 0.09 < 0.15,该结果不返回
|
|
23
|
+
|
|
24
|
+
#### Scenario: 高相关性结果通过
|
|
25
|
+
- **WHEN** 一条事实 trust_score=0.4 且与查询的 relevance score=0.8
|
|
26
|
+
- **THEN** 综合评分 = 0.8 × 0.4 = 0.32 > 0.15,该结果正常返回
|
|
27
|
+
|
|
28
|
+
### Requirement: 内容相似度去重
|
|
29
|
+
系统 SHALL 对检索结果中内容高度重叠的事实进行去重——两条事实的 Jaccard 相似度 > 0.7 时,只保留评分高的那条。
|
|
30
|
+
|
|
31
|
+
替换现有的 category-per-top1 硬性去重策略。
|
|
32
|
+
|
|
33
|
+
#### Scenario: 相似事实去重
|
|
34
|
+
- **WHEN** 检索结果中有两条事实内容 Jaccard 相似度 > 0.7
|
|
35
|
+
- **THEN** 只保留评分高的那条,另一条被移除
|
|
36
|
+
|
|
37
|
+
#### Scenario: 不同 category 的不同事实不被误去重
|
|
38
|
+
- **WHEN** 一条 identity 事实和一条 coding_style 事实内容不相似(Jaccard < 0.7)
|
|
39
|
+
- **THEN** 两者都保留在结果中
|
|
40
|
+
|
|
41
|
+
#### Scenario: general 类允许返回多条
|
|
42
|
+
- **WHEN** general category 有 5 条高相关性事实且内容不重复
|
|
43
|
+
- **THEN** 5 条都保留在结果中(不再受 category-per-top1 限制)
|
package/openspec/changes/retrieval-and-injection-optimization/specs/injection-protocol/spec.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: 会话预热注入协议
|
|
4
|
+
系统 SHALL 通过 MCP Resource 机制实现会话启动时的自动预热注入,无需 LLM 主动调用 tool。
|
|
5
|
+
|
|
6
|
+
- 客户端在 session 启动时自动拉取所有 `mnemo://global/*` Resource
|
|
7
|
+
- Resource 内容作为 system context 注入,LLM 可直接访问
|
|
8
|
+
- 预热注入覆盖 identity/coding_style/tool_pref/workflow/general 全部 5 个分类
|
|
9
|
+
|
|
10
|
+
#### Scenario: 新会话自动获得全局记忆
|
|
11
|
+
- **WHEN** Claude Code 启动新 session
|
|
12
|
+
- **THEN** 系统自动拉取 5 个 category Resource,记忆内容出现在 system context 中
|
|
13
|
+
|
|
14
|
+
#### Scenario: 预热后 LLM 不需要主动 search 即可回答偏好问题
|
|
15
|
+
- **WHEN** 用户问 "我喜欢什么编辑器" 且预热 Resource 中包含 "用户偏好 VS Code 编辑器"
|
|
16
|
+
- **THEN** LLM 直接从预热 context 回答,无需调用 fact_store(search)
|
|
17
|
+
|
|
18
|
+
### Requirement: 按需补充查询触发规则
|
|
19
|
+
系统 SHALL 定义何时触发补充 fact_store(search) 调用的规则,替代当前"每条消息都搜"的模式。
|
|
20
|
+
|
|
21
|
+
触发条件(满足任一即触发):
|
|
22
|
+
- 用户消息包含明确的记忆查询意图("我记得说过""我之前说过""按我的习惯")
|
|
23
|
+
- 用户消息涉及技术选型/工具选择且预热中未覆盖
|
|
24
|
+
- 用户显式要求记住新信息(触发 add 而非 search)
|
|
25
|
+
|
|
26
|
+
不触发条件:
|
|
27
|
+
- 纯操作指令("创建文件""运行测试""git commit")
|
|
28
|
+
- 通用技术问题("怎么用 Promise""React hooks 语法")
|
|
29
|
+
- 代码审查/解释请求
|
|
30
|
+
|
|
31
|
+
#### Scenario: 技术选型触发补充查询
|
|
32
|
+
- **WHEN** 用户说 "用 React 还是 Vue 开发这个项目" 且预热中无相关偏好
|
|
33
|
+
- **THEN** 触发 fact_store(search, query="React Vue 前端框架 偏好")
|
|
34
|
+
|
|
35
|
+
#### Scenario: 纯操作指令不触发
|
|
36
|
+
- **WHEN** 用户说 "运行测试"
|
|
37
|
+
- **THEN** 不触发 fact_store(search),节省 MCP 调用
|
|
38
|
+
|
|
39
|
+
#### Scenario: 明确记忆查询触发
|
|
40
|
+
- **WHEN** 用户说 "我之前说不喜欢什么颜色来着"
|
|
41
|
+
- **THEN** 触发 fact_store(search, query="不喜欢 颜色")
|
|
42
|
+
|
|
43
|
+
### Requirement: 注入协议配置说明
|
|
44
|
+
系统 SHALL 在 README 或文档中提供更新后的 CLAUDE.md 规则模板,用户可直接复制使用。
|
|
45
|
+
|
|
46
|
+
#### Scenario: 用户更新 CLAUDE.md 规则
|
|
47
|
+
- **WHEN** 用户按文档更新 CLAUDE.md 中的记忆系统使用规则
|
|
48
|
+
- **THEN** 规则 1 从"每条消息都搜"变为"会话预热 + 按需触发"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: MCP Resource 暴露全局分类记忆
|
|
4
|
+
系统 SHALL 为每个全局 category 注册 MCP Resource URI,返回该 category 下按 trust 排序的 top-N 事实摘要。
|
|
5
|
+
|
|
6
|
+
- URI 格式:`mnemo://global/{category}`,其中 category 为 identity / coding_style / tool_pref / workflow / general
|
|
7
|
+
- 每个 Resource 返回该 category 下 trust_score 最高的前 10 条事实
|
|
8
|
+
- 返回格式为 JSON 数组,每条包含 fact_id、content、trust_score
|
|
9
|
+
- Resource 内容在服务启动时计算并缓存,写操作(add/update/remove)时失效重算
|
|
10
|
+
|
|
11
|
+
#### Scenario: 客户端拉取 identity Resource
|
|
12
|
+
- **WHEN** MCP 客户端请求 `mnemo://global/identity`
|
|
13
|
+
- **THEN** 返回 identity category 下 trust_score DESC 排序的前 10 条事实 JSON 数组
|
|
14
|
+
|
|
15
|
+
#### Scenario: 写操作后 Resource 刷新
|
|
16
|
+
- **WHEN** 通过 fact_store 执行 add/update/remove 操作
|
|
17
|
+
- **THEN** 所有 Resource 缓存失效,下次拉取时重新从 DB 计算
|
|
18
|
+
|
|
19
|
+
#### Scenario: 空 category 返回空数组
|
|
20
|
+
- **WHEN** 某个 category 下没有任何事实
|
|
21
|
+
- **THEN** 对应 Resource 返回空 JSON 数组 `[]`
|
|
22
|
+
|
|
23
|
+
### Requirement: MCP Resource 列表发现
|
|
24
|
+
系统 SHALL 在 MCP `resources/list` 响应中暴露所有 5 个全局 category Resource,包含 name 和 description。
|
|
25
|
+
|
|
26
|
+
#### Scenario: 客户端发现可用 Resource
|
|
27
|
+
- **WHEN** MCP 客户端调用 `resources/list`
|
|
28
|
+
- **THEN** 响应包含 5 个 Resource 条目,每个的 URI 为 `mnemo://global/{category}`,name 为分类名,description 说明内容
|
|
29
|
+
|
|
30
|
+
### Requirement: Resource 缓存生命周期
|
|
31
|
+
系统 SHALL 维护 Resource 缓存,避免每次拉取都查询数据库。
|
|
32
|
+
|
|
33
|
+
- 缓存类型:进程内 Map,key 为 category
|
|
34
|
+
- 缓存失效:任何写操作(add/update/remove)触发全部缓存清空
|
|
35
|
+
- 首次访问时惰性计算
|
|
36
|
+
|
|
37
|
+
#### Scenario: 连续拉取命中缓存
|
|
38
|
+
- **WHEN** 短时间内连续两次拉取同一 category Resource,中间无写操作
|
|
39
|
+
- **THEN** 第二次直接返回缓存结果,不触发 DB 查询
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
## ADDED Requirements
|
|
2
|
+
|
|
3
|
+
### Requirement: 查询提炼函数
|
|
4
|
+
系统 SHALL 提供 `refineQuery(rawQuery: string): string | null` 纯函数,从用户原始消息中提取记忆相关关键词。
|
|
5
|
+
|
|
6
|
+
提炼流程:
|
|
7
|
+
1. 分词(按空格 + 中文字符边界)
|
|
8
|
+
2. 过滤中文虚词(复用 CN_STOP_WORDS)
|
|
9
|
+
3. 过滤动作词("帮我""看看""做一下""帮我看看""能不能""为什么""怎么""是什么")
|
|
10
|
+
4. 提取高信号 token(引号内容、大写开头连续词、书名号内容)
|
|
11
|
+
5. 剩余 token 作为提炼结果
|
|
12
|
+
6. 如果提炼后为空(纯操作指令),返回 null
|
|
13
|
+
|
|
14
|
+
#### Scenario: 包含实体的查询成功提炼
|
|
15
|
+
- **WHEN** 输入 "帮我用 TypeScript 重构 auth 模块"
|
|
16
|
+
- **THEN** 提炼结果包含 "TypeScript" "auth" 等关键词(过滤掉"帮我""重构""模块")
|
|
17
|
+
|
|
18
|
+
#### Scenario: 纯操作指令返回 null
|
|
19
|
+
- **WHEN** 输入 "运行测试" 或 "git status"
|
|
20
|
+
- **THEN** 返回 null,表示不需要检索记忆
|
|
21
|
+
|
|
22
|
+
#### Scenario: 引号内容优先保留
|
|
23
|
+
- **WHEN** 输入 "我喜欢「深色主题」"
|
|
24
|
+
- **THEN** 提炼结果包含 "深色主题"
|
|
25
|
+
|
|
26
|
+
#### Scenario: 提炼结果集成到 search
|
|
27
|
+
- **WHEN** search() 接收到原始查询
|
|
28
|
+
- **THEN** 先调用 refineQuery();若返回 null 则直接返回空结果;若返回非空则用提炼结果作为 FTS5 查询词
|
|
29
|
+
|
|
30
|
+
### Requirement: 提炼为空时的 fallback
|
|
31
|
+
系统 SHALL 在 refineQuery 返回 null 时保留原始查询作为 fallback,由调用方决定是否跳过检索。
|
|
32
|
+
|
|
33
|
+
#### Scenario: 调用方选择跳过
|
|
34
|
+
- **WHEN** refineQuery 返回 null 且调用方为自动触发(CLAUDE.md 规则 1)
|
|
35
|
+
- **THEN** 跳过检索,返回空结果
|
|
36
|
+
|
|
37
|
+
#### Scenario: 调用方显式搜索
|
|
38
|
+
- **WHEN** 用户显式调用 `fact_store(action="search", query="运行测试")`
|
|
39
|
+
- **THEN** 即使 refineQuery 返回 null,仍用原始查询执行检索
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
## 1. MCP Resource 注册与缓存
|
|
2
|
+
|
|
3
|
+
- [ ] 1.1 在 `src/server.ts` 中注册 5 个 MCP Resource URI(`mnemo://global/{identity,coding_style,tool_pref,workflow,general}`),每个 Resource 返回对应 category 的 top-10 事实 JSON 数组
|
|
4
|
+
- [ ] 1.2 实现 Resource 缓存层:进程内 Map,写操作时清空,首次访问惰性计算
|
|
5
|
+
- [ ] 1.3 在 `resources/list` 响应中暴露所有 5 个 Resource 条目(含 name + description)
|
|
6
|
+
- [ ] 1.4 验证:启动 server 后用 MCP Inspector 拉取 Resource 确认返回格式正确
|
|
7
|
+
|
|
8
|
+
## 2. 查询提炼层
|
|
9
|
+
|
|
10
|
+
- [ ] 2.1 在 `src/retriever.ts` 中实现 `refineQuery(rawQuery: string): string | null` 纯函数,过滤虚词 + 动作词 + 提取高信号 token
|
|
11
|
+
- [ ] 2.2 修改 `search()` 入口:先调 refineQuery(),返回 null 时直接返回空结果;返回非空时用提炼结果作为查询词
|
|
12
|
+
- [ ] 2.3 保留原始查询 fallback:当用户显式调用 search action(非自动触发)时,即使 refineQuery 返回 null 也用原始查询
|
|
13
|
+
- [ ] 2.4 验证:测试 "帮我用 TypeScript 重构 auth 模块" → 提炼出 "TypeScript auth";"运行测试" → 返回 null
|
|
14
|
+
|
|
15
|
+
## 3. 动态评分权重
|
|
16
|
+
|
|
17
|
+
- [ ] 3.1 修改 `search()` 中的评分公式:根据 query token 数动态设置 ftsWeight/jaccardWeight(≤3 token → 0.7/0.3,>3 → 0.3/0.7)
|
|
18
|
+
- [ ] 3.2 新增 relevance score 阈值门控:综合评分 < 0.15 的结果不返回
|
|
19
|
+
- [ ] 3.3 替换 category-per-top1 硬性去重为 Jaccard 相似度去重(> 0.7 的只保留高分)
|
|
20
|
+
- [ ] 3.4 验证:general 类多条不同事实可以同时出现在结果中
|
|
21
|
+
|
|
22
|
+
## 4. 注入协议文档
|
|
23
|
+
|
|
24
|
+
- [ ] 4.1 编写更新后的 CLAUDE.md 记忆系统使用规则模板(会话预热 + 按需触发 + 写入/反馈规则)
|
|
25
|
+
- [ ] 4.2 更新 README.md 添加注入协议说明和 CLAUDE.md 规则配置示例
|
|
26
|
+
- [ ] 4.3 验证:按新规则配置 CLAUDE.md 后启动 session,确认 Resource 自动拉取且无需每条消息都调 search
|
|
27
|
+
|
|
28
|
+
## 5. 集成测试
|
|
29
|
+
|
|
30
|
+
- [ ] 5.1 编写 `tests/resource.test.ts`:验证 Resource 返回格式、缓存命中、写操作后缓存失效
|
|
31
|
+
- [ ] 5.2 编写 `tests/refine.test.ts`:验证查询提炼的虚词过滤、动作词剥离、null 返回场景
|
|
32
|
+
- [ ] 5.3 更新 `tests/retriever.test.ts`:验证动态权重、relevance 门控、Jaccard 去重
|
|
33
|
+
- [ ] 5.4 运行全量测试 `npm run test` 确认无回归
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
schema: spec-driven
|
|
2
|
+
|
|
3
|
+
# Project context (optional)
|
|
4
|
+
# This is shown to AI when creating artifacts.
|
|
5
|
+
# Add your tech stack, conventions, style guides, domain knowledge, etc.
|
|
6
|
+
# Example:
|
|
7
|
+
# context: |
|
|
8
|
+
# Tech stack: TypeScript, React, Node.js
|
|
9
|
+
# We use conventional commits
|
|
10
|
+
# Domain: e-commerce platform
|
|
11
|
+
|
|
12
|
+
# Per-artifact rules (optional)
|
|
13
|
+
# Add custom rules for specific artifacts.
|
|
14
|
+
# Example:
|
|
15
|
+
# rules:
|
|
16
|
+
# proposal:
|
|
17
|
+
# - Keep proposals under 500 words
|
|
18
|
+
# - Always include a "Non-goals" section
|
|
19
|
+
# tasks:
|
|
20
|
+
# - Break tasks into chunks of max 2 hours
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morningljn/mnemo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Structured fact memory MCP server — SQLite + FTS5, trust scoring, entity graph, bilingual retrieval for Claude Code & Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
package/src/init.ts
CHANGED
|
@@ -23,19 +23,27 @@ const MEMORY_RULES = `
|
|
|
23
23
|
|
|
24
24
|
你有 mnemo 记忆工具(fact_store / fact_feedback),必须按以下规则使用:
|
|
25
25
|
|
|
26
|
-
## 规则 1
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
## 规则 1:会话预热(自动)
|
|
27
|
+
会话启动时,mnemo-mcp 的 MCP Resource 会自动注入全局记忆到 system context。
|
|
28
|
+
你不需要主动调用 fact_store(search) 来获取高频记忆。
|
|
29
|
+
|
|
30
|
+
## 规则 2:按需补充查询
|
|
31
|
+
仅在以下情况调用 \`fact_store(action="search")\`:
|
|
32
|
+
- 用户消息涉及个人偏好/习惯/工具选择且预热中未覆盖
|
|
33
|
+
- 用户明确查询记忆("我之前说过什么""按我的习惯")
|
|
34
|
+
- 技术选型时需要确认用户偏好
|
|
35
|
+
|
|
36
|
+
不触发查询的情况:
|
|
37
|
+
- 纯操作指令("运行测试""git commit")
|
|
38
|
+
- 通用技术问题("Promise 怎么用")
|
|
39
|
+
- 代码审查/解释请求
|
|
40
|
+
|
|
41
|
+
## 规则 3:写入记忆
|
|
34
42
|
用户说"记住"、"记下来"时,调用 \`fact_store(action="add", content="...", category="...")\`。
|
|
35
43
|
- 先 search 检查是否已有相似事实,有则 update
|
|
36
44
|
- category:identity / coding_style / tool_pref / workflow / general
|
|
37
45
|
|
|
38
|
-
## 规则
|
|
46
|
+
## 规则 4:反馈强化
|
|
39
47
|
成功使用某条记忆时,调用 \`fact_feedback(action="helpful", fact_id=...)\`。
|
|
40
48
|
`
|
|
41
49
|
|