claude-mem-lite 2.33.5 → 2.34.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +26 -11
- package/README.zh-CN.md +24 -11
- package/adopt-content.mjs +24 -5
- package/hook.mjs +42 -28
- package/mem-cli.mjs +86 -19
- package/package.json +1 -1
- package/scripts/pre-skill-bridge.js +14 -3
- package/server.mjs +168 -30
- package/tool-schemas.mjs +29 -3
package/README.md
CHANGED
|
@@ -209,6 +209,15 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
|
|
|
209
209
|
|
|
210
210
|
### MCP Tools (used automatically by Claude)
|
|
211
211
|
|
|
212
|
+
As of v2.34.0, the server registers 17 tools in total but only the 6 **core**
|
|
213
|
+
tools appear in `tools/list`. The 11 **hidden** tools remain callable at the
|
|
214
|
+
protocol layer (`tools/call` by exact name still routes normally); they're
|
|
215
|
+
omitted from the list response so Claude Code sessions don't load 11 extra
|
|
216
|
+
tool schemas at startup. Hidden tools are the maintenance / admin / browser
|
|
217
|
+
surface — reach them through the CLI column in the second table.
|
|
218
|
+
|
|
219
|
+
**Core (6, exposed to Claude Code)**
|
|
220
|
+
|
|
212
221
|
| Tool | Description |
|
|
213
222
|
|------|-------------|
|
|
214
223
|
| `mem_search` | FTS5 full-text search with BM25 ranking. Filters by type, project, date range, importance level. |
|
|
@@ -217,16 +226,22 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
|
|
|
217
226
|
| `mem_timeline` | Browse observations chronologically around an anchor point. |
|
|
218
227
|
| `mem_get` | Retrieve full details for specific observation IDs (includes importance and related_ids). |
|
|
219
228
|
| `mem_save` | Manually save a memory/observation. |
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
|
224
|
-
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
229
|
+
|
|
230
|
+
**Hidden-but-callable (11, CLI-routed)**
|
|
231
|
+
|
|
232
|
+
| Tool | CLI equivalent | Notes |
|
|
233
|
+
|------|----------------|-------|
|
|
234
|
+
| `mem_update` | `claude-mem-lite update <id>` | Edit an observation in place. |
|
|
235
|
+
| `mem_stats` | `claude-mem-lite stats` | Counts, type distribution, daily activity. |
|
|
236
|
+
| `mem_delete` | `claude-mem-lite delete <id>` | Preview / confirm workflow, FTS5 cleanup. |
|
|
237
|
+
| `mem_compress` | `claude-mem-lite compress --preview` | Roll up old low-value observations. |
|
|
238
|
+
| `mem_maintain` | `claude-mem-lite maintain --action scan` | dedup / decay / cleanup / rebuild_vectors. |
|
|
239
|
+
| `mem_optimize` | `claude-mem-lite optimize --action preview` | LLM-powered re-enrich / normalize / cluster-merge. |
|
|
240
|
+
| `mem_export` | `claude-mem-lite export` | JSON / JSONL dump, filters by project, type, date. |
|
|
241
|
+
| `mem_fts_check` | `claude-mem-lite fts-check [--rebuild]` | FTS5 integrity + rebuild. |
|
|
242
|
+
| `mem_browse` | `claude-mem-lite browse` | Tier-grouped dashboard (working / active / archive). |
|
|
243
|
+
| `mem_registry` | `claude-mem-lite registry <action>` | List / search / import / remove skills + agents. |
|
|
244
|
+
| `mem_use` | _MCP only_ | Load a skill / agent from the registry by name. |
|
|
230
245
|
|
|
231
246
|
### Skill Commands (in Claude Code chat)
|
|
232
247
|
|
|
@@ -399,7 +414,7 @@ Stop
|
|
|
399
414
|
|
|
400
415
|
### Resource Registry
|
|
401
416
|
|
|
402
|
-
The resource registry (`registry.mjs`, `registry-retriever.mjs`) indexes installed skills and agents into a searchable FTS5 database. Unlike the previous proactive dispatch system, the registry is now on-demand — Claude
|
|
417
|
+
The resource registry (`registry.mjs`, `registry-retriever.mjs`) indexes installed skills and agents into a searchable FTS5 database. Unlike the previous proactive dispatch system, the registry is now on-demand — it's reachable via the `claude-mem-lite registry` CLI (primary path for Claude Code since v2.34.0 hides the `mem_registry` MCP tool from `tools/list`) or by direct `tools/call mem_registry` for MCP clients that know the name.
|
|
403
418
|
|
|
404
419
|
```
|
|
405
420
|
Registry pipeline:
|
package/README.zh-CN.md
CHANGED
|
@@ -194,7 +194,14 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
|
|
|
194
194
|
|
|
195
195
|
## 使用方法
|
|
196
196
|
|
|
197
|
-
### MCP
|
|
197
|
+
### MCP 工具
|
|
198
|
+
|
|
199
|
+
v2.34.0 起服务端注册 17 个工具,但 `tools/list` 只暴露 6 个 **核心** 工具;其余
|
|
200
|
+
11 个 **隐藏** 工具仍然注册在 MCP 层(按名 `tools/call` 仍命中),只是不会出现
|
|
201
|
+
在列表响应里,以避免 Claude Code 会话启动时加载 11 份额外的工具 schema。隐藏
|
|
202
|
+
工具走下面表格的 CLI 入口。
|
|
203
|
+
|
|
204
|
+
**核心(6 个,暴露给 Claude Code)**
|
|
198
205
|
|
|
199
206
|
| 工具 | 描述 |
|
|
200
207
|
|------|------|
|
|
@@ -204,16 +211,22 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
|
|
|
204
211
|
| `mem_timeline` | 围绕锚点按时间顺序浏览观察。 |
|
|
205
212
|
| `mem_get` | 获取指定观察 ID 的完整详情(包含重要度和关联 ID)。 |
|
|
206
213
|
| `mem_save` | 手动保存记忆/观察。 |
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
|
211
|
-
|
|
212
|
-
| `
|
|
213
|
-
| `
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
214
|
+
|
|
215
|
+
**隐藏但可按名调用(11 个,走 CLI)**
|
|
216
|
+
|
|
217
|
+
| 工具 | 对应 CLI | 说明 |
|
|
218
|
+
|------|----------|------|
|
|
219
|
+
| `mem_update` | `claude-mem-lite update <id>` | 原地更新某条观察。 |
|
|
220
|
+
| `mem_stats` | `claude-mem-lite stats` | 计数、类型分布、每日活动。 |
|
|
221
|
+
| `mem_delete` | `claude-mem-lite delete <id>` | 预览 / 确认流程,FTS5 自动清理。 |
|
|
222
|
+
| `mem_compress` | `claude-mem-lite compress --preview` | 压缩旧的低价值观察。 |
|
|
223
|
+
| `mem_maintain` | `claude-mem-lite maintain --action scan` | 去重 / decay / 清理 / 向量重建。 |
|
|
224
|
+
| `mem_optimize` | `claude-mem-lite optimize --action preview` | LLM 深度优化:re-enrich / normalize / cluster-merge。 |
|
|
225
|
+
| `mem_export` | `claude-mem-lite export` | JSON / JSONL 导出,支持项目/类型/日期过滤。 |
|
|
226
|
+
| `mem_fts_check` | `claude-mem-lite fts-check [--rebuild]` | FTS5 完整性检查与重建。 |
|
|
227
|
+
| `mem_browse` | `claude-mem-lite browse` | 分层仪表盘(working / active / archive)。 |
|
|
228
|
+
| `mem_registry` | `claude-mem-lite registry <action>` | 列 / 搜索 / 导入 / 移除 skill / agent。 |
|
|
229
|
+
| `mem_use` | _MCP only_ | 从 registry 按名载入 skill / agent。 |
|
|
217
230
|
|
|
218
231
|
### 技能命令(在 Claude Code 聊天中使用)
|
|
219
232
|
|
package/adopt-content.mjs
CHANGED
|
@@ -31,6 +31,9 @@ export function getDetailDoc() {
|
|
|
31
31
|
|
|
32
32
|
## 何时调用 MCP 工具
|
|
33
33
|
|
|
34
|
+
以下 6 个核心 MCP 工具在 \`tools/list\` 中默认暴露,覆盖了契约的热路径:
|
|
35
|
+
\`mem_search\` / \`mem_recent\` / \`mem_recall\` / \`mem_get\` / \`mem_save\` / \`mem_timeline\`。
|
|
36
|
+
|
|
34
37
|
| 时机 | 工具 | 关键参数 |
|
|
35
38
|
|------|------|----------|
|
|
36
39
|
| Edit / Write 前 | \`mem_recall\` | \`file="<路径>"\` —— 过往 bugfix 与教训 |
|
|
@@ -46,12 +49,28 @@ export function getDetailDoc() {
|
|
|
46
49
|
- "最近做了啥" → \`mem_recent\`
|
|
47
50
|
- "<文件> 有哪些记忆" → \`mem_recall\`
|
|
48
51
|
- "#NN 前后发生了啥" → \`mem_timeline\`
|
|
49
|
-
- "清理过期记忆" → \`mem_maintain\`
|
|
50
|
-
- "FTS 索引健康吗" → \`mem_fts_check\`
|
|
51
|
-
- "按 tier 浏览" → \`mem_browse\`
|
|
52
|
-
- "备份导出" → \`mem_export\`
|
|
53
52
|
|
|
54
|
-
## CLI
|
|
53
|
+
## 维护 / 管理类工具(走 CLI)
|
|
54
|
+
|
|
55
|
+
v2.34.0 起,以下 11 个工具从 \`tools/list\` 中隐藏以缩小启动上下文;它们仍注册在
|
|
56
|
+
MCP 层,按名 \`tools/call\` 仍可命中,但对 Claude Code 这类只读 tools/list 的
|
|
57
|
+
调用方只走下面的 CLI 入口:
|
|
58
|
+
|
|
59
|
+
| 场景 | CLI |
|
|
60
|
+
|------|-----|
|
|
61
|
+
| 清理过期记忆 | \`claude-mem-lite maintain --action scan\` → \`--action execute\` |
|
|
62
|
+
| 深度优化(Haiku) | \`claude-mem-lite optimize --action preview\` |
|
|
63
|
+
| 压缩旧条目 | \`claude-mem-lite compress --preview\` |
|
|
64
|
+
| FTS5 索引检查 / 重建 | \`claude-mem-lite fts-check [--rebuild]\` |
|
|
65
|
+
| tier 分组浏览 | \`claude-mem-lite browse [--tier active]\` |
|
|
66
|
+
| 导出 JSON/JSONL | \`claude-mem-lite export [--format jsonl]\` |
|
|
67
|
+
| 统计总量 / 健康 | \`claude-mem-lite stats [--days 30]\` |
|
|
68
|
+
| 删除某条 | \`claude-mem-lite delete <id>[,<id>]\` |
|
|
69
|
+
| 更新某条 | \`claude-mem-lite update <id> [--title ...]\` |
|
|
70
|
+
| 列 / 搜索 / 导入 skill-agent registry | \`claude-mem-lite registry <list\\|search\\|import>\` |
|
|
71
|
+
| 按 registry 名载入 skill/agent | (MCP only:\`mem_use\`;由用户主动请求时才使用) |
|
|
72
|
+
|
|
73
|
+
## CLI 速查(常用检索)
|
|
55
74
|
|
|
56
75
|
| 命令 | 用途 |
|
|
57
76
|
|------|------|
|
package/hook.mjs
CHANGED
|
@@ -417,27 +417,35 @@ async function handleStop() {
|
|
|
417
417
|
try { buildAndSaveHandoff(db, sessionId, project, 'exit', episodeSnapshot, ccSessionId || sessionId); }
|
|
418
418
|
catch (e) { debugCatch(e, 'handleStop-handoff'); }
|
|
419
419
|
|
|
420
|
-
// Fast summary baseline — ensures summary exists even if background LLM fails
|
|
420
|
+
// Fast summary baseline — ensures summary exists even if background LLM fails.
|
|
421
|
+
// T4-P2-B: guard against Stop firing twice for the same session (rare but possible;
|
|
422
|
+
// mirrors handleSessionStart line 795 hasSummary guard). Uses mem-internal sessionId
|
|
423
|
+
// as the WHERE key per the top-of-file dual-id invariant (#7789).
|
|
421
424
|
try {
|
|
422
|
-
const
|
|
423
|
-
SELECT
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
425
|
+
const existingSummary = db.prepare(
|
|
426
|
+
'SELECT 1 FROM session_summaries WHERE memory_session_id = ? LIMIT 1'
|
|
427
|
+
).get(sessionId);
|
|
428
|
+
if (!existingSummary) {
|
|
429
|
+
const firstPrompt = db.prepare(`
|
|
430
|
+
SELECT prompt_text FROM user_prompts
|
|
431
|
+
WHERE content_session_id = ?
|
|
432
|
+
ORDER BY prompt_number ASC LIMIT 1
|
|
433
|
+
`).get(sessionId);
|
|
434
|
+
const recentObs = db.prepare(`
|
|
435
|
+
SELECT title FROM observations
|
|
436
|
+
WHERE memory_session_id = ? AND COALESCE(compressed_into, 0) = 0
|
|
437
|
+
ORDER BY created_at_epoch DESC LIMIT 5
|
|
438
|
+
`).all(sessionId);
|
|
439
|
+
const fastRequest = truncate(firstPrompt?.prompt_text || '', 200);
|
|
440
|
+
const fastCompleted = recentObs.map(o => o.title).filter(Boolean).join('; ');
|
|
441
|
+
if (fastRequest || fastCompleted) {
|
|
442
|
+
const now = new Date();
|
|
443
|
+
db.prepare(`
|
|
444
|
+
INSERT INTO session_summaries
|
|
445
|
+
(memory_session_id, project, request, investigated, learned, completed, next_steps, remaining_items, files_read, files_edited, notes, created_at, created_at_epoch)
|
|
446
|
+
VALUES (?, ?, ?, '', '', ?, '', '', '[]', '[]', 'fast', ?, ?)
|
|
447
|
+
`).run(sessionId, project, fastRequest, truncate(fastCompleted, 300), now.toISOString(), now.getTime());
|
|
448
|
+
}
|
|
441
449
|
}
|
|
442
450
|
} catch (e) { debugCatch(e, 'handleStop-fast-summary'); }
|
|
443
451
|
} finally {
|
|
@@ -603,12 +611,14 @@ async function handleSessionStart() {
|
|
|
603
611
|
const STALE_AGE = Date.now() - 30 * 86400000;
|
|
604
612
|
const OP_CAP = 500;
|
|
605
613
|
|
|
606
|
-
// Purge FIRST: delete
|
|
607
|
-
//
|
|
614
|
+
// Purge FIRST: delete pending-purge entries. Schema has no marked_at_epoch, so we
|
|
615
|
+
// anchor retention on created_at_epoch instead: 30d marking gate + 7d grace = 37d.
|
|
616
|
+
// Older cutoffs (e.g. 7d) were always redundant with the 30d marking filter and
|
|
617
|
+
// made purge effectively immediate on the next maintenance cycle — fix for T4-P1-A.
|
|
608
618
|
const purged = db.prepare(`
|
|
609
619
|
DELETE FROM observations WHERE compressed_into = ${COMPRESSED_PENDING_PURGE}
|
|
610
620
|
AND created_at_epoch < ?
|
|
611
|
-
`).run(Date.now() -
|
|
621
|
+
`).run(Date.now() - 37 * 86400000);
|
|
612
622
|
if (purged.changes > 0) debugLog('DEBUG', 'auto-maintain', `purged ${purged.changes} stale observations`);
|
|
613
623
|
|
|
614
624
|
// Cleanup: remove broken observations (no title AND no narrative)
|
|
@@ -906,9 +916,13 @@ async function handleUserPrompt() {
|
|
|
906
916
|
VALUES (?, ?, ?, ?, ?, 'active')
|
|
907
917
|
`).run(sessionId, sessionId, project, now.toISOString(), now.getTime());
|
|
908
918
|
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
919
|
+
// T4-P2-D: atomic increment+read via UPDATE ... RETURNING (SQLite 3.35+).
|
|
920
|
+
// Previously UPDATE + SELECT as two statements; parallel prompts could read a stale
|
|
921
|
+
// counter and emit duplicate prompt_number values. better-sqlite3 ships a modern SQLite.
|
|
922
|
+
const bumped = db.prepare(
|
|
923
|
+
'UPDATE sdk_sessions SET prompt_counter = COALESCE(prompt_counter, 0) + 1 WHERE content_session_id = ? RETURNING prompt_counter'
|
|
924
|
+
).get(sessionId);
|
|
925
|
+
const promptNumber = bumped?.prompt_counter || 1;
|
|
912
926
|
|
|
913
927
|
db.prepare(`
|
|
914
928
|
INSERT INTO user_prompts (content_session_id, prompt_text, prompt_number, created_at, created_at_epoch)
|
|
@@ -916,7 +930,7 @@ async function handleUserPrompt() {
|
|
|
916
930
|
`).run(
|
|
917
931
|
sessionId,
|
|
918
932
|
scrubSecrets(promptText.slice(0, 10000)),
|
|
919
|
-
|
|
933
|
+
promptNumber,
|
|
920
934
|
now.toISOString(), now.getTime()
|
|
921
935
|
);
|
|
922
936
|
|
|
@@ -928,7 +942,7 @@ async function handleUserPrompt() {
|
|
|
928
942
|
const ccSessionId = typeof hookData.session_id === 'string' && hookData.session_id.length > 0
|
|
929
943
|
? hookData.session_id
|
|
930
944
|
: null;
|
|
931
|
-
if (
|
|
945
|
+
if (promptNumber <= 3) {
|
|
932
946
|
try {
|
|
933
947
|
if (detectContinuationIntent(db, promptText, project, ccSessionId)) {
|
|
934
948
|
const injection = renderHandoffInjection(db, project, ccSessionId);
|
package/mem-cli.mjs
CHANGED
|
@@ -783,7 +783,7 @@ function cmdSave(db, args) {
|
|
|
783
783
|
const { positional, flags } = parseArgs(args);
|
|
784
784
|
const text = positional.join(' ');
|
|
785
785
|
if (!text) {
|
|
786
|
-
fail('[mem] Usage: mem save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2]');
|
|
786
|
+
fail('[mem] Usage: mem save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2] [--lesson T]');
|
|
787
787
|
return;
|
|
788
788
|
}
|
|
789
789
|
|
|
@@ -805,9 +805,21 @@ function cmdSave(db, args) {
|
|
|
805
805
|
const project = flags.project ? resolveProject(db, flags.project) : inferProject();
|
|
806
806
|
const saveFiles = flags.files ? flags.files.split(',').map(f => f.trim()).filter(Boolean) : [];
|
|
807
807
|
|
|
808
|
+
// Optional lesson_learned — accepts --lesson or --lesson-learned (alias)
|
|
809
|
+
// Mirrors MCP memSaveSchema.lesson_learned (≤500 chars) and cmdUpdate's flag handling.
|
|
810
|
+
const rawLesson = flags.lesson !== undefined ? flags.lesson
|
|
811
|
+
: flags['lesson-learned'] !== undefined ? flags['lesson-learned']
|
|
812
|
+
: null;
|
|
813
|
+
if (rawLesson !== null && typeof rawLesson === 'string' && rawLesson.length > 500) {
|
|
814
|
+
fail(`[mem] --lesson too long (${rawLesson.length} chars, max 500).`);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
808
818
|
// Secret scrubbing (aligned with MCP mem_save)
|
|
809
819
|
const safeContent = scrubSecrets(text);
|
|
810
820
|
const safeTitle = scrubSecrets(rawTitle);
|
|
821
|
+
const safeLesson = (rawLesson !== null && typeof rawLesson === 'string' && rawLesson.length > 0)
|
|
822
|
+
? scrubSecrets(rawLesson) : null;
|
|
811
823
|
|
|
812
824
|
// Dedup: skip if similar title/content saved in last 5 minutes (aligned with MCP mem_save)
|
|
813
825
|
const fiveMinAgo = Date.now() - 5 * 60 * 1000;
|
|
@@ -827,8 +839,11 @@ function cmdSave(db, args) {
|
|
|
827
839
|
}
|
|
828
840
|
|
|
829
841
|
// MinHash + CJK bigrams (aligned with MCP mem_save)
|
|
842
|
+
// Include lesson in the FTS-indexed text so the +0.3 lesson-boost actually surfaces
|
|
843
|
+
// lesson-bearing rows (mirrors MCP mem_save which builds the same indexText).
|
|
830
844
|
const minhashSig = computeMinHash(safeTitle + ' ' + safeContent);
|
|
831
|
-
const
|
|
845
|
+
const indexText = [safeTitle, safeContent, safeLesson].filter(Boolean).join(' ');
|
|
846
|
+
const bigramText = cjkBigrams(indexText);
|
|
832
847
|
const textField = bigramText ? safeContent + ' ' + bigramText : safeContent;
|
|
833
848
|
|
|
834
849
|
const now = new Date();
|
|
@@ -843,9 +858,9 @@ function cmdSave(db, args) {
|
|
|
843
858
|
// Atomic: insert observation + observation_files + TF-IDF vector (aligned with MCP mem_save)
|
|
844
859
|
const saveTx = db.transaction(() => {
|
|
845
860
|
const result = db.prepare(`
|
|
846
|
-
INSERT INTO observations (memory_session_id, project, text, type, title, narrative, concepts, facts, files_read, files_modified, importance, minhash_sig, branch, created_at, created_at_epoch)
|
|
847
|
-
VALUES (?, ?, ?, ?, ?, ?, '', '', '[]', ?, ?, ?, ?, ?, ?)
|
|
848
|
-
`).run(sessionId, project, textField, type, safeTitle, safeContent, JSON.stringify(saveFiles), importance, minhashSig, getCurrentBranch(), now.toISOString(), now.getTime());
|
|
861
|
+
INSERT INTO observations (memory_session_id, project, text, type, title, narrative, concepts, facts, files_read, files_modified, importance, minhash_sig, lesson_learned, branch, created_at, created_at_epoch)
|
|
862
|
+
VALUES (?, ?, ?, ?, ?, ?, '', '', '[]', ?, ?, ?, ?, ?, ?, ?)
|
|
863
|
+
`).run(sessionId, project, textField, type, safeTitle, safeContent, JSON.stringify(saveFiles), importance, minhashSig, safeLesson, getCurrentBranch(), now.toISOString(), now.getTime());
|
|
849
864
|
const savedId = Number(result.lastInsertRowid);
|
|
850
865
|
|
|
851
866
|
// Populate observation_files junction table (aligned with MCP mem_save)
|
|
@@ -870,7 +885,8 @@ function cmdSave(db, args) {
|
|
|
870
885
|
});
|
|
871
886
|
const result = saveTx();
|
|
872
887
|
|
|
873
|
-
|
|
888
|
+
const lessonNote = safeLesson ? ' 💡lesson captured' : '';
|
|
889
|
+
out(`[mem] Saved #${result.lastInsertRowid} [${type}] "${truncate(safeTitle, 80)}" (project: ${project})${lessonNote}`);
|
|
874
890
|
}
|
|
875
891
|
|
|
876
892
|
// N-1: Quality-focused stats for R-2 A/B baseline.
|
|
@@ -1645,6 +1661,9 @@ function cmdMaintain(db, args) {
|
|
|
1645
1661
|
const OP_CAP = 1000;
|
|
1646
1662
|
const results = [];
|
|
1647
1663
|
|
|
1664
|
+
// T2-P1-B: surface the OP_CAP hit so users know to re-run, matching MCP mem_maintain.
|
|
1665
|
+
const capHint = (changes) => (changes >= OP_CAP ? ' (cap reached, re-run for more)' : '');
|
|
1666
|
+
|
|
1648
1667
|
db.transaction(() => {
|
|
1649
1668
|
if (ops.includes('cleanup')) {
|
|
1650
1669
|
const deleted = db.prepare(`
|
|
@@ -1655,7 +1674,7 @@ function cmdMaintain(db, args) {
|
|
|
1655
1674
|
${projectFilter} LIMIT ${OP_CAP}
|
|
1656
1675
|
)
|
|
1657
1676
|
`).run(...baseParams);
|
|
1658
|
-
results.push(`Cleaned up ${deleted.changes} broken observations`);
|
|
1677
|
+
results.push(`Cleaned up ${deleted.changes} broken observations${capHint(deleted.changes)}`);
|
|
1659
1678
|
}
|
|
1660
1679
|
|
|
1661
1680
|
if (ops.includes('decay')) {
|
|
@@ -1683,7 +1702,8 @@ function cmdMaintain(db, args) {
|
|
|
1683
1702
|
${projectFilter} LIMIT ${OP_CAP}
|
|
1684
1703
|
)
|
|
1685
1704
|
`).run(staleAge, ...baseParams);
|
|
1686
|
-
|
|
1705
|
+
const decayCap = (decayed.changes >= OP_CAP || idleMarked.changes >= OP_CAP) ? ' (cap reached, re-run for more)' : '';
|
|
1706
|
+
results.push(`Decayed ${decayed.changes} stale observations, marked ${idleMarked.changes} idle as pending-purge${decayCap}`);
|
|
1687
1707
|
}
|
|
1688
1708
|
|
|
1689
1709
|
if (ops.includes('boost')) {
|
|
@@ -1697,7 +1717,7 @@ function cmdMaintain(db, args) {
|
|
|
1697
1717
|
${projectFilter} LIMIT ${OP_CAP}
|
|
1698
1718
|
)
|
|
1699
1719
|
`).run(...baseParams);
|
|
1700
|
-
results.push(`Boosted ${boosted.changes} frequently-accessed observations`);
|
|
1720
|
+
results.push(`Boosted ${boosted.changes} frequently-accessed observations${capHint(boosted.changes)}`);
|
|
1701
1721
|
}
|
|
1702
1722
|
|
|
1703
1723
|
if (ops.includes('dedup') && flags['merge-ids']) {
|
|
@@ -1715,17 +1735,41 @@ function cmdMaintain(db, args) {
|
|
|
1715
1735
|
results.push(`Merged ${totalMerged} duplicate observations`);
|
|
1716
1736
|
}
|
|
1717
1737
|
|
|
1738
|
+
// T2-P1-B parity with MCP: warn when merge-ids is provided but dedup wasn't requested.
|
|
1739
|
+
if (!ops.includes('dedup') && flags['merge-ids']) {
|
|
1740
|
+
results.push('Warning: --merge-ids provided but "dedup" not in operations — merge-ids ignored');
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1718
1743
|
if (ops.includes('purge_stale')) {
|
|
1719
1744
|
const retainDays = parseInt(flags['retain-days'], 10) || 30;
|
|
1720
1745
|
const retainCutoff = Date.now() - retainDays * 86400000;
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1746
|
+
// T2-P0-A (CLI parity): purge_stale is the only DELETE in this code path — require
|
|
1747
|
+
// --confirm so a mis-typed `maintain execute --ops purge_stale` can't wipe rows silently.
|
|
1748
|
+
const confirmed = flags.confirm === true || flags.confirm === 'true';
|
|
1749
|
+
if (!confirmed) {
|
|
1750
|
+
const previewRow = db.prepare(`
|
|
1751
|
+
SELECT COUNT(*) AS candidates, MIN(created_at_epoch) AS oldest, MAX(created_at_epoch) AS newest
|
|
1752
|
+
FROM observations
|
|
1753
|
+
WHERE compressed_into = ${COMPRESSED_PENDING_PURGE} AND created_at_epoch < ? ${projectFilter}
|
|
1754
|
+
`).get(retainCutoff, ...baseParams);
|
|
1755
|
+
const pushLines = [`purge_stale preview (no --confirm):`,
|
|
1756
|
+
` Candidates (pending-purge, older than ${retainDays}d): ${previewRow.candidates}`];
|
|
1757
|
+
if (previewRow.candidates > 0) {
|
|
1758
|
+
pushLines.push(` Oldest: ${new Date(previewRow.oldest).toISOString().slice(0, 10)}`);
|
|
1759
|
+
pushLines.push(` Newest: ${new Date(previewRow.newest).toISOString().slice(0, 10)}`);
|
|
1760
|
+
}
|
|
1761
|
+
pushLines.push(` To delete, re-run with --confirm.`);
|
|
1762
|
+
results.push(pushLines.join('\n'));
|
|
1763
|
+
} else {
|
|
1764
|
+
const purged = db.prepare(`
|
|
1765
|
+
DELETE FROM observations WHERE id IN (
|
|
1766
|
+
SELECT id FROM observations
|
|
1767
|
+
WHERE compressed_into = ${COMPRESSED_PENDING_PURGE} AND created_at_epoch < ?
|
|
1768
|
+
${projectFilter} LIMIT ${OP_CAP}
|
|
1769
|
+
)
|
|
1770
|
+
`).run(retainCutoff, ...baseParams);
|
|
1771
|
+
results.push(`Purged ${purged.changes} stale observations (retained last ${retainDays} days)${capHint(purged.changes)}`);
|
|
1772
|
+
}
|
|
1729
1773
|
}
|
|
1730
1774
|
})();
|
|
1731
1775
|
|
|
@@ -1993,6 +2037,7 @@ Commands:
|
|
|
1993
2037
|
--importance N 1-3 (default: 2)
|
|
1994
2038
|
--project P Project name
|
|
1995
2039
|
--files f1,f2 Comma-separated file paths
|
|
2040
|
+
--lesson T Lesson learned (≤500 chars; alias: --lesson-learned)
|
|
1996
2041
|
|
|
1997
2042
|
delete <id1,id2,...> Delete observations by ID
|
|
1998
2043
|
--confirm Execute deletion (preview by default)
|
|
@@ -2180,10 +2225,32 @@ async function cmdEnrich(argv) {
|
|
|
2180
2225
|
async function cmdOptimize(db, args) {
|
|
2181
2226
|
const run = args.includes('--run');
|
|
2182
2227
|
const runAll = args.includes('--run-all');
|
|
2228
|
+
// T2-P1-D: --task accepts a single task or a comma-separated list, parity with MCP memOptimizeSchema.tasks.
|
|
2229
|
+
const VALID_TASKS = ['re-enrich', 'normalize', 'cluster-merge', 'smart-compress'];
|
|
2183
2230
|
const taskIdx = args.indexOf('--task');
|
|
2184
|
-
|
|
2231
|
+
let tasks;
|
|
2232
|
+
if (taskIdx >= 0 && args[taskIdx + 1]) {
|
|
2233
|
+
const parsed = args[taskIdx + 1].split(',').map(s => s.trim()).filter(Boolean);
|
|
2234
|
+
const invalid = parsed.filter(t => !VALID_TASKS.includes(t));
|
|
2235
|
+
if (invalid.length > 0) {
|
|
2236
|
+
fail(`[mem] Unknown task(s): ${invalid.join(', ')}. Valid: ${VALID_TASKS.join(', ')}`);
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
tasks = parsed;
|
|
2240
|
+
}
|
|
2241
|
+
// T2-P1-C: reject --max 0 / --max <non-positive> / --max <non-number> explicitly — the old
|
|
2242
|
+
// `|| 15` fallback silently turned these into the default (15), burning LLM tokens.
|
|
2185
2243
|
const maxIdx = args.indexOf('--max');
|
|
2186
|
-
|
|
2244
|
+
let maxItems = 15;
|
|
2245
|
+
if (maxIdx >= 0) {
|
|
2246
|
+
const raw = args[maxIdx + 1];
|
|
2247
|
+
const parsed = parseInt(raw, 10);
|
|
2248
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 100) {
|
|
2249
|
+
fail(`[mem] Invalid --max "${raw}". Must be an integer between 1 and 100.`);
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
maxItems = parsed;
|
|
2253
|
+
}
|
|
2187
2254
|
// R-7 micro: --scope wide targets bugfix/refactor/feature/decision with narrative but no
|
|
2188
2255
|
// lesson_learned (the "Haiku judged 'none'" cases). Default 'narrow' preserves old behavior.
|
|
2189
2256
|
const scopeIdx = args.indexOf('--scope');
|
package/package.json
CHANGED
|
@@ -66,13 +66,24 @@ try {
|
|
|
66
66
|
|
|
67
67
|
// Read and output
|
|
68
68
|
const content = readFileSync(skillPath, 'utf8');
|
|
69
|
-
//
|
|
69
|
+
// T4-P1-B: JSON hookSpecificOutput parity with pre-tool-recall.js. Some CC variants
|
|
70
|
+
// (notably sdscc) silently drop plain-text stdout from PreToolUse — the previous
|
|
71
|
+
// console.log() form would render on stock CC but no-op on those variants.
|
|
72
|
+
// Token budget: ~4 chars per token, 4000 token limit = 16000 chars.
|
|
73
|
+
let additionalContext;
|
|
70
74
|
if (content.length > 16000) {
|
|
71
75
|
const summary = content.slice(0, 800);
|
|
72
|
-
|
|
76
|
+
additionalContext = `<skill-bridge name="${row.name}" source="managed" truncated="true">\n${summary}\n...\n</skill-bridge>\n\nSkill content truncated. Use mem_use(name="${row.name}") to load full content.`;
|
|
73
77
|
} else {
|
|
74
|
-
|
|
78
|
+
additionalContext = `<skill-bridge name="${row.name}" source="managed">\n${content}\n</skill-bridge>\n\nThis skill was loaded from the managed registry. Follow the instructions above.`;
|
|
75
79
|
}
|
|
80
|
+
process.stdout.write(JSON.stringify({
|
|
81
|
+
suppressOutput: true,
|
|
82
|
+
hookSpecificOutput: {
|
|
83
|
+
hookEventName: 'PreToolUse',
|
|
84
|
+
additionalContext,
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
76
87
|
} catch {
|
|
77
88
|
// Silent failure — never block Skill tool
|
|
78
89
|
} finally {
|
package/server.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
7
8
|
import { jaccardSimilarity, truncate, typeIcon, sanitizeFtsQuery, relaxFtsQueryToOr, inferProject, computeMinHash, estimateJaccardFromMinHash, scrubSecrets, cjkBigrams, fmtDate, isoWeekKey, debugLog, debugCatch, COMPRESSED_PENDING_PURGE, OBS_BM25, SESS_BM25, TYPE_DECAY_CASE, TYPE_QUALITY_CASE, getCurrentBranch, DEFAULT_DECAY_HALF_LIFE_MS, isPathConfined, notLowSignalTitleClause, LOW_SIGNAL_TITLE } from './utils.mjs';
|
|
8
9
|
import { extractCjkLikePatterns } from './nlp.mjs';
|
|
9
10
|
import { resolveProject as _resolveProjectShared } from './project-utils.mjs';
|
|
@@ -158,7 +159,7 @@ function buildObsFtsQuery(scoring, { multiplier, withSnippet, withOffset, includ
|
|
|
158
159
|
const mult = multiplier ? ` * ${multiplier}` : '';
|
|
159
160
|
const lowSignalClause = includeNoise ? '' : `AND ${notLowSignalTitleClause('o')}`;
|
|
160
161
|
return `
|
|
161
|
-
SELECT o.id, o.type, o.title, o.subtitle, o.project, o.created_at, o.importance,
|
|
162
|
+
SELECT o.id, o.type, o.title, o.subtitle, o.project, o.created_at, o.created_at_epoch, o.importance,
|
|
162
163
|
o.files_modified,
|
|
163
164
|
${withSnippet ? "snippet(observations_fts, 2, '»', '«', '…', 10) as match_snippet," : ''}
|
|
164
165
|
${scoreExpr}${mult} as score
|
|
@@ -200,7 +201,8 @@ function buildObsFtsParams({ now, projectBoost, ftsQuery, args, epochFrom, epoch
|
|
|
200
201
|
function ftsRowToResult(r, { scoreMultiplier, snippet } = {}) {
|
|
201
202
|
return {
|
|
202
203
|
source: 'obs', id: r.id, type: r.type, title: r.title, subtitle: r.subtitle,
|
|
203
|
-
project: r.project, date: r.created_at,
|
|
204
|
+
project: r.project, date: r.created_at, created_at_epoch: r.created_at_epoch,
|
|
205
|
+
score: scoreMultiplier ? r.score * scoreMultiplier : r.score,
|
|
204
206
|
files_modified: r.files_modified, importance: r.importance, snippet: snippet ? (r.match_snippet || '') : '',
|
|
205
207
|
};
|
|
206
208
|
}
|
|
@@ -311,7 +313,7 @@ function searchObservations(ctx) {
|
|
|
311
313
|
LIMIT ? OFFSET ?
|
|
312
314
|
`).all(...params);
|
|
313
315
|
for (const r of rows) {
|
|
314
|
-
results.push({ source: 'obs', id: r.id, type: r.type, title: r.title, subtitle: r.subtitle, project: r.project, date: r.created_at,
|
|
316
|
+
results.push({ source: 'obs', id: r.id, type: r.type, title: r.title, subtitle: r.subtitle, project: r.project, date: r.created_at, created_at_epoch: r.created_at_epoch, files_modified: r.files_modified, importance: r.importance });
|
|
315
317
|
}
|
|
316
318
|
}
|
|
317
319
|
|
|
@@ -370,7 +372,7 @@ function searchSessions(ctx) {
|
|
|
370
372
|
const now = Date.now();
|
|
371
373
|
const sessionProjectBoost = args.project ? null : currentProject;
|
|
372
374
|
const rows = db.prepare(`
|
|
373
|
-
SELECT s.id, s.request, s.completed, s.project, s.created_at,
|
|
375
|
+
SELECT s.id, s.request, s.completed, s.project, s.created_at, s.created_at_epoch,
|
|
374
376
|
${SESS_BM25}
|
|
375
377
|
* (1.0 + EXP(-0.693 * (? - s.created_at_epoch) / ${RECENCY_HALF_LIFE_MS}.0))
|
|
376
378
|
* (CASE WHEN ? IS NOT NULL AND s.project = ? THEN 2.0 ELSE 1.0 END) as score
|
|
@@ -392,7 +394,7 @@ function searchSessions(ctx) {
|
|
|
392
394
|
perSourceLimit, perSourceOffset
|
|
393
395
|
);
|
|
394
396
|
for (const r of rows) {
|
|
395
|
-
results.push({ source: 'session', id: r.id, request: r.request, completed: r.completed, project: r.project, date: r.created_at, score: r.score });
|
|
397
|
+
results.push({ source: 'session', id: r.id, request: r.request, completed: r.completed, project: r.project, date: r.created_at, created_at_epoch: r.created_at_epoch, score: r.score });
|
|
396
398
|
}
|
|
397
399
|
} else if (!searchType) {
|
|
398
400
|
// Skip sessions in unfiltered no-query mode (too noisy)
|
|
@@ -411,7 +413,7 @@ function searchSessions(ctx) {
|
|
|
411
413
|
LIMIT ? OFFSET ?
|
|
412
414
|
`).all(...params);
|
|
413
415
|
for (const r of rows) {
|
|
414
|
-
results.push({ source: 'session', id: r.id, request: r.request, completed: r.completed, project: r.project, date: r.created_at,
|
|
416
|
+
results.push({ source: 'session', id: r.id, request: r.request, completed: r.completed, project: r.project, date: r.created_at, created_at_epoch: r.created_at_epoch });
|
|
415
417
|
}
|
|
416
418
|
}
|
|
417
419
|
|
|
@@ -424,7 +426,7 @@ function searchPrompts(ctx) {
|
|
|
424
426
|
|
|
425
427
|
if (ftsQuery) {
|
|
426
428
|
const rows = db.prepare(`
|
|
427
|
-
SELECT p.id, p.prompt_text, p.content_session_id, p.created_at,
|
|
429
|
+
SELECT p.id, p.prompt_text, p.content_session_id, p.created_at, p.created_at_epoch,
|
|
428
430
|
bm25(user_prompts_fts, 1) as score
|
|
429
431
|
FROM user_prompts_fts
|
|
430
432
|
JOIN user_prompts p ON user_prompts_fts.rowid = p.id
|
|
@@ -444,7 +446,7 @@ function searchPrompts(ctx) {
|
|
|
444
446
|
perSourceLimit, perSourceOffset
|
|
445
447
|
);
|
|
446
448
|
for (const r of rows) {
|
|
447
|
-
results.push({ source: 'prompt', id: r.id, text: r.prompt_text, session: r.content_session_id, date: r.created_at, score: r.score });
|
|
449
|
+
results.push({ source: 'prompt', id: r.id, text: r.prompt_text, session: r.content_session_id, date: r.created_at, created_at_epoch: r.created_at_epoch, score: r.score });
|
|
448
450
|
}
|
|
449
451
|
// CJK LIKE fallback: FTS5 unicode61 can't tokenize CJK substrings in prompts
|
|
450
452
|
if (rows.length === 0 && args.query) {
|
|
@@ -453,7 +455,7 @@ function searchPrompts(ctx) {
|
|
|
453
455
|
const likeConds = cjkPatterns.map(() => 'p.prompt_text LIKE ?');
|
|
454
456
|
const likeParams = cjkPatterns.map(p => `%${p}%`);
|
|
455
457
|
const fallbackRows = db.prepare(`
|
|
456
|
-
SELECT p.id, p.prompt_text, p.content_session_id, p.created_at
|
|
458
|
+
SELECT p.id, p.prompt_text, p.content_session_id, p.created_at, p.created_at_epoch
|
|
457
459
|
FROM user_prompts p
|
|
458
460
|
JOIN sdk_sessions s ON p.content_session_id = s.content_session_id
|
|
459
461
|
WHERE (${likeConds.join(' OR ')})
|
|
@@ -471,7 +473,7 @@ function searchPrompts(ctx) {
|
|
|
471
473
|
perSourceLimit, perSourceOffset
|
|
472
474
|
);
|
|
473
475
|
for (const r of fallbackRows) {
|
|
474
|
-
results.push({ source: 'prompt', id: r.id, text: r.prompt_text, session: r.content_session_id, date: r.created_at, score: 0 });
|
|
476
|
+
results.push({ source: 'prompt', id: r.id, text: r.prompt_text, session: r.content_session_id, date: r.created_at, created_at_epoch: r.created_at_epoch, score: 0 });
|
|
475
477
|
}
|
|
476
478
|
}
|
|
477
479
|
}
|
|
@@ -492,7 +494,7 @@ function searchPrompts(ctx) {
|
|
|
492
494
|
LIMIT ? OFFSET ?
|
|
493
495
|
`).all(...params);
|
|
494
496
|
for (const r of rows) {
|
|
495
|
-
results.push({ source: 'prompt', id: r.id, text: r.prompt_text, session: r.content_session_id, date: r.created_at,
|
|
497
|
+
results.push({ source: 'prompt', id: r.id, text: r.prompt_text, session: r.content_session_id, date: r.created_at, created_at_epoch: r.created_at_epoch });
|
|
496
498
|
}
|
|
497
499
|
}
|
|
498
500
|
|
|
@@ -521,7 +523,10 @@ function formatSearchOutput(paginatedResults, args, ftsQuery, totalCount, isCros
|
|
|
521
523
|
? `${paginatedResults.length} of ${totalCount}`
|
|
522
524
|
: `${paginatedResults.length}`;
|
|
523
525
|
const hasMixed = paginatedResults.some(r => r.source === 'session' || r.source === 'prompt');
|
|
524
|
-
|
|
526
|
+
// P2-6: empty/omitted query falls through to a "listing recent" path — label it explicitly
|
|
527
|
+
// so callers don't mistake BM25-less results for relevance-ranked ones.
|
|
528
|
+
const qLabel = args.query ? ` for "${args.query}"` : ' (no query — listing recent)';
|
|
529
|
+
lines.push(`Found ${countLabel} result(s)${qLabel}:${hasMixed ? ' (# observation, S# session, P# prompt)' : ''}\n`);
|
|
525
530
|
|
|
526
531
|
for (const r of paginatedResults) {
|
|
527
532
|
if (r.source === 'obs') {
|
|
@@ -626,7 +631,7 @@ server.registerTool(
|
|
|
626
631
|
if (ftsQuery) {
|
|
627
632
|
results.sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
|
|
628
633
|
} else {
|
|
629
|
-
results.sort((a, b) => (b.
|
|
634
|
+
results.sort((a, b) => (b.created_at_epoch ?? 0) - (a.created_at_epoch ?? 0));
|
|
630
635
|
}
|
|
631
636
|
}
|
|
632
637
|
|
|
@@ -831,15 +836,17 @@ server.registerTool(
|
|
|
831
836
|
const source = args.source || 'obs';
|
|
832
837
|
const placeholders = args.ids.map(() => '?').join(',');
|
|
833
838
|
|
|
834
|
-
let rows, allFields, prefix;
|
|
839
|
+
let rows, allFields, prefix, sourceLabel;
|
|
835
840
|
if (source === 'session') {
|
|
836
841
|
rows = db.prepare(`SELECT * FROM session_summaries WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC`).all(...args.ids);
|
|
837
842
|
allFields = ['id', 'request', 'investigated', 'learned', 'completed', 'next_steps', 'files_read', 'files_edited', 'notes', 'project', 'created_at', 'memory_session_id', 'prompt_number'];
|
|
838
843
|
prefix = 'S#';
|
|
844
|
+
sourceLabel = 'sessions';
|
|
839
845
|
} else if (source === 'prompt') {
|
|
840
846
|
rows = db.prepare(`SELECT * FROM user_prompts WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC`).all(...args.ids);
|
|
841
847
|
allFields = ['id', 'prompt_text', 'content_session_id', 'prompt_number', 'created_at'];
|
|
842
848
|
prefix = 'P#';
|
|
849
|
+
sourceLabel = 'prompts';
|
|
843
850
|
} else {
|
|
844
851
|
// Increment access_count for retrieved observations (batch UPDATE)
|
|
845
852
|
try {
|
|
@@ -851,15 +858,43 @@ server.registerTool(
|
|
|
851
858
|
rows = db.prepare(`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC`).all(...args.ids);
|
|
852
859
|
allFields = ['id', 'type', 'title', 'subtitle', 'narrative', 'text', 'facts', 'concepts', 'lesson_learned', 'search_aliases', 'files_read', 'files_modified', 'project', 'created_at', 'memory_session_id', 'prompt_number', 'importance', 'related_ids', 'access_count', 'branch', 'superseded_at', 'superseded_by', 'last_accessed_at'];
|
|
853
860
|
prefix = '#';
|
|
861
|
+
sourceLabel = 'observations';
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// P1-3: validate requested fields — throw on all-invalid so callers don't silently get an
|
|
865
|
+
// empty record (header only). Partial-invalid is tolerated but surfaced as a note.
|
|
866
|
+
let fieldsNote = '';
|
|
867
|
+
if (args.fields?.length) {
|
|
868
|
+
const invalid = args.fields.filter(f => !allFields.includes(f));
|
|
869
|
+
const valid = args.fields.filter(f => allFields.includes(f));
|
|
870
|
+
if (valid.length === 0) {
|
|
871
|
+
throw new Error(`No valid fields. Unknown field(s): ${invalid.join(', ')}. Valid: ${allFields.join(', ')}`);
|
|
872
|
+
}
|
|
873
|
+
if (invalid.length > 0) {
|
|
874
|
+
fieldsNote = `Note: unknown field(s) dropped: ${invalid.join(', ')}. Valid: ${allFields.join(', ')}`;
|
|
875
|
+
}
|
|
854
876
|
}
|
|
855
877
|
|
|
856
878
|
if (rows.length === 0) {
|
|
857
|
-
|
|
879
|
+
// P2-7: for source=session/prompt, check whether the IDs exist as observations so the
|
|
880
|
+
// caller can switch source instead of chasing a phantom miss.
|
|
881
|
+
let hint = '';
|
|
882
|
+
if (source === 'session' || source === 'prompt') {
|
|
883
|
+
try {
|
|
884
|
+
const obsHits = db.prepare(`SELECT id FROM observations WHERE id IN (${placeholders})`).all(...args.ids);
|
|
885
|
+
if (obsHits.length > 0) {
|
|
886
|
+
hint = ` These ID(s) exist as observations: ${obsHits.map(r => r.id).join(', ')}. Try source='obs'.`;
|
|
887
|
+
}
|
|
888
|
+
} catch { /* best-effort hint */ }
|
|
889
|
+
}
|
|
890
|
+
const msg = `No ${sourceLabel} found for given IDs.${hint}`;
|
|
891
|
+
return { content: [{ type: 'text', text: fieldsNote ? `${msg}\n\n${fieldsNote}` : msg }] };
|
|
858
892
|
}
|
|
859
893
|
|
|
860
894
|
const fields = args.fields?.length ? args.fields.filter(f => allFields.includes(f)) : allFields;
|
|
861
895
|
|
|
862
896
|
const parts = [];
|
|
897
|
+
if (fieldsNote) parts.push(fieldsNote);
|
|
863
898
|
for (const row of rows) {
|
|
864
899
|
const lines = [`── ${prefix}${row.id} ──`];
|
|
865
900
|
for (const f of fields) {
|
|
@@ -874,6 +909,13 @@ server.registerTool(
|
|
|
874
909
|
parts.push(lines.join('\n'));
|
|
875
910
|
}
|
|
876
911
|
|
|
912
|
+
// P1-4: surface IDs that weren't found (mirrors mem_delete's missing-ID note).
|
|
913
|
+
const foundIds = new Set(rows.map(r => r.id));
|
|
914
|
+
const missing = args.ids.filter(id => !foundIds.has(id));
|
|
915
|
+
if (missing.length > 0) {
|
|
916
|
+
parts.push(`Note: ID(s) ${missing.join(', ')} not found.`);
|
|
917
|
+
}
|
|
918
|
+
|
|
877
919
|
return { content: [{ type: 'text', text: parts.join('\n\n') }] };
|
|
878
920
|
})
|
|
879
921
|
);
|
|
@@ -1365,11 +1407,43 @@ server.registerTool(
|
|
|
1365
1407
|
}
|
|
1366
1408
|
|
|
1367
1409
|
if (action === 'execute') {
|
|
1368
|
-
const ops = args.operations
|
|
1410
|
+
const ops = args.operations && args.operations.length > 0
|
|
1411
|
+
? args.operations
|
|
1412
|
+
: ['cleanup', 'decay', 'boost'];
|
|
1413
|
+
// T2-P1-A: reject explicit empty array (vs. omitted → defaults above). Empty-array
|
|
1414
|
+
// callers are almost always mistakes; silently running only FTS5 optimize hides the error.
|
|
1415
|
+
if (args.operations && args.operations.length === 0) {
|
|
1416
|
+
return { content: [{ type: 'text', text: 'operations array is empty. Pass a non-empty list (e.g. ["cleanup","decay","boost"]) or omit operations to use the default set.' }], isError: true };
|
|
1417
|
+
}
|
|
1369
1418
|
const results = [];
|
|
1370
1419
|
const staleAge = Date.now() - STALE_AGE_MS;
|
|
1371
1420
|
const OP_ROW_CAP = 1000; // safety cap per operation
|
|
1372
1421
|
|
|
1422
|
+
// T2-P0-A: purge_stale is the only DELETE in this handler. Require confirm=true;
|
|
1423
|
+
// a first call without confirm returns a dry-run preview so callers know the blast radius.
|
|
1424
|
+
const purgeRequested = ops.includes('purge_stale');
|
|
1425
|
+
if (purgeRequested && args.confirm !== true) {
|
|
1426
|
+
const retainDays = args.retain_days ?? 30;
|
|
1427
|
+
const retainCutoff = Date.now() - retainDays * 86400000;
|
|
1428
|
+
const previewRow = db.prepare(`
|
|
1429
|
+
SELECT COUNT(*) AS candidates, MIN(created_at_epoch) AS oldest, MAX(created_at_epoch) AS newest
|
|
1430
|
+
FROM observations
|
|
1431
|
+
WHERE compressed_into = ${COMPRESSED_PENDING_PURGE} AND created_at_epoch < ? ${projectFilter}
|
|
1432
|
+
`).get(retainCutoff, ...baseParams);
|
|
1433
|
+
const lines = [
|
|
1434
|
+
'purge_stale preview (confirm=false):',
|
|
1435
|
+
` Candidates (pending-purge, older than ${retainDays}d): ${previewRow.candidates}`,
|
|
1436
|
+
];
|
|
1437
|
+
if (previewRow.candidates > 0) {
|
|
1438
|
+
lines.push(` Oldest: ${new Date(previewRow.oldest).toISOString().slice(0, 10)}`);
|
|
1439
|
+
lines.push(` Newest: ${new Date(previewRow.newest).toISOString().slice(0, 10)}`);
|
|
1440
|
+
}
|
|
1441
|
+
lines.push('');
|
|
1442
|
+
lines.push('Nothing was deleted. To execute, re-run with confirm=true:');
|
|
1443
|
+
lines.push(` mem_maintain(action="execute", operations=${JSON.stringify(ops)}, confirm=true${args.retain_days ? `, retain_days=${args.retain_days}` : ''}${args.project ? `, project="${args.project}"` : ''})`);
|
|
1444
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1373
1447
|
db.transaction(() => {
|
|
1374
1448
|
if (ops.includes('cleanup')) {
|
|
1375
1449
|
const deleted = db.prepare(`
|
|
@@ -1540,6 +1614,9 @@ server.registerTool(
|
|
|
1540
1614
|
tasks: args.tasks,
|
|
1541
1615
|
maxItems: args.max_items || 15,
|
|
1542
1616
|
force,
|
|
1617
|
+
// T2-P0-B: scope parity with CLI (--scope wide). When omitted, optimizeRun defaults
|
|
1618
|
+
// to narrow via its own code; passing through keeps that fallback intact.
|
|
1619
|
+
reenrichScope: args.scope,
|
|
1543
1620
|
});
|
|
1544
1621
|
|
|
1545
1622
|
const lines = ['🔧 LLM Optimization Results:'];
|
|
@@ -1625,15 +1702,18 @@ server.registerTool(
|
|
|
1625
1702
|
const typeFilter = args.type;
|
|
1626
1703
|
const where = typeFilter ? 'WHERE type = ? AND status = ?' : 'WHERE status = ?';
|
|
1627
1704
|
const params = typeFilter ? [typeFilter, 'active'] : ['active'];
|
|
1705
|
+
// T3-P2-A: order by adoption then recommendation (CLI parity), and coalesce NULL counts
|
|
1706
|
+
// so the output shows "adopt:0" rather than the jarring "adopt:null".
|
|
1628
1707
|
const resources = rdb.prepare(`
|
|
1629
1708
|
SELECT name, type, invocation_name, recommend_count, adopt_count, capability_summary
|
|
1630
|
-
FROM resources ${where}
|
|
1709
|
+
FROM resources ${where}
|
|
1710
|
+
ORDER BY COALESCE(adopt_count, 0) DESC, COALESCE(recommend_count, 0) DESC, type, name
|
|
1631
1711
|
`).all(...params);
|
|
1632
1712
|
|
|
1633
1713
|
if (resources.length === 0) return { content: [{ type: 'text', text: 'No resources found.' }] };
|
|
1634
1714
|
|
|
1635
1715
|
const lines = resources.map(r =>
|
|
1636
|
-
`${r.type === 'skill' ? 'S' : 'A'} ${r.name}${r.invocation_name ? ` (${r.invocation_name})` : ''} — rec:${r.recommend_count} adopt:${r.adopt_count} — ${truncate(r.capability_summary || '', 80)}`
|
|
1716
|
+
`${r.type === 'skill' ? 'S' : 'A'} ${r.name}${r.invocation_name ? ` (${r.invocation_name})` : ''} — rec:${r.recommend_count ?? 0} adopt:${r.adopt_count ?? 0} — ${truncate(r.capability_summary || '', 80)}`
|
|
1637
1717
|
);
|
|
1638
1718
|
return { content: [{ type: 'text', text: `Resources (${resources.length}):\n${lines.join('\n')}` }] };
|
|
1639
1719
|
}
|
|
@@ -1908,19 +1988,29 @@ server.registerTool(
|
|
|
1908
1988
|
wheres.push('superseded_at IS NULL');
|
|
1909
1989
|
if (args.project) { wheres.push('project = ?'); params.push(resolveProject(args.project)); }
|
|
1910
1990
|
if (args.type) { wheres.push('type = ?'); params.push(args.type); }
|
|
1991
|
+
// T3-P1-A: surface invalid dates instead of silently dropping the filter — mirrors
|
|
1992
|
+
// mem_search, which threw. A dropped filter can quietly expand the export blast radius.
|
|
1911
1993
|
if (args.date_from) {
|
|
1912
1994
|
const epoch = new Date(args.date_from).getTime();
|
|
1913
|
-
if (
|
|
1995
|
+
if (isNaN(epoch)) throw new Error(`Invalid date_from: "${args.date_from}" (use ISO 8601 or YYYY-MM-DD)`);
|
|
1996
|
+
wheres.push('created_at_epoch >= ?');
|
|
1997
|
+
params.push(epoch);
|
|
1914
1998
|
}
|
|
1915
1999
|
if (args.date_to) {
|
|
1916
2000
|
const d = args.date_to.length === 10 ? args.date_to + 'T23:59:59.999Z' : args.date_to;
|
|
1917
2001
|
const epoch = new Date(d).getTime();
|
|
1918
|
-
if (
|
|
2002
|
+
if (isNaN(epoch)) throw new Error(`Invalid date_to: "${args.date_to}" (use ISO 8601 or YYYY-MM-DD)`);
|
|
2003
|
+
wheres.push('created_at_epoch <= ?');
|
|
2004
|
+
params.push(epoch);
|
|
1919
2005
|
}
|
|
1920
2006
|
|
|
1921
2007
|
const where = wheres.length > 0 ? 'WHERE ' + wheres.join(' AND ') : '';
|
|
1922
2008
|
const exportLimit = Math.min(args.limit ?? 200, 1000);
|
|
1923
|
-
|
|
2009
|
+
// T3-P2-B: probe limit+1 so we can tell "user hit their own limit with more waiting" from
|
|
2010
|
+
// "user got exactly what existed". Trim to exportLimit before rendering.
|
|
2011
|
+
const probed = db.prepare(`SELECT id, project, type, title, subtitle, narrative, concepts, facts, lesson_learned, importance, files_modified, branch, access_count, memory_session_id, created_at, created_at_epoch FROM observations ${where} ORDER BY created_at_epoch DESC LIMIT ?`).all(...params, exportLimit + 1);
|
|
2012
|
+
const rows = probed.slice(0, exportLimit);
|
|
2013
|
+
const moreAvailable = probed.length > exportLimit;
|
|
1924
2014
|
|
|
1925
2015
|
if (rows.length === 0) return { content: [{ type: 'text', text: 'No observations found matching the criteria.' }] };
|
|
1926
2016
|
|
|
@@ -1928,7 +2018,7 @@ server.registerTool(
|
|
|
1928
2018
|
? rows.map(r => JSON.stringify(r)).join('\n')
|
|
1929
2019
|
: JSON.stringify(rows, null, 2);
|
|
1930
2020
|
|
|
1931
|
-
const cap =
|
|
2021
|
+
const cap = moreAvailable ? `\nNote: Results capped at ${exportLimit}. Use date_from/date_to or increase limit (max 1000) to export more.` : '';
|
|
1932
2022
|
return { content: [{ type: 'text', text: `Exported ${rows.length} observations:${cap}\n${output}` }] };
|
|
1933
2023
|
})
|
|
1934
2024
|
);
|
|
@@ -1987,20 +2077,20 @@ server.registerTool(
|
|
|
1987
2077
|
inputSchema: memFtsCheckSchema,
|
|
1988
2078
|
},
|
|
1989
2079
|
safeHandler(async (args) => {
|
|
2080
|
+
// T3-P2-C: Zod `action: z.enum(['check','rebuild'])` filters any other value before we
|
|
2081
|
+
// reach this handler, so there's no "Unknown action" fallback to write.
|
|
1990
2082
|
if (args.action === 'check') {
|
|
1991
2083
|
const result = checkFTSIntegrity(db);
|
|
1992
2084
|
return { content: [{ type: 'text', text: result.healthy
|
|
1993
2085
|
? 'FTS5 indexes are healthy — all integrity checks passed.'
|
|
1994
2086
|
: `FTS5 issues found:\n${result.details.join('\n')}` }] };
|
|
1995
2087
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
}
|
|
2003
|
-
return { content: [{ type: 'text', text: `Unknown action: ${args.action}` }], isError: true };
|
|
2088
|
+
// args.action === 'rebuild'
|
|
2089
|
+
const result = rebuildFTS(db);
|
|
2090
|
+
const summary = result.errors.length > 0
|
|
2091
|
+
? `Rebuilt: ${result.rebuilt.join(', ')}. Errors: ${result.errors.join(', ')}`
|
|
2092
|
+
: `Successfully rebuilt: ${result.rebuilt.join(', ')}`;
|
|
2093
|
+
return { content: [{ type: 'text', text: summary }] };
|
|
2004
2094
|
})
|
|
2005
2095
|
);
|
|
2006
2096
|
|
|
@@ -2085,6 +2175,54 @@ server.registerTool(
|
|
|
2085
2175
|
})
|
|
2086
2176
|
);
|
|
2087
2177
|
|
|
2178
|
+
// ─── Hidden tool filter ─────────────────────────────────────────────────────
|
|
2179
|
+
// All 17 tools are registered (so `tools/call <name>` still resolves for
|
|
2180
|
+
// scripts and direct MCP clients), but only the 6 core tools appear in the
|
|
2181
|
+
// `tools/list` response. Hiding the 11 maintenance/admin tools keeps Claude
|
|
2182
|
+
// Code's startup context small while preserving the contract that the plugin
|
|
2183
|
+
// dogfoods (see CLAUDE.md §Mem usage contract and adopt-content.mjs).
|
|
2184
|
+
//
|
|
2185
|
+
// Safe because:
|
|
2186
|
+
// - Protocol-layer override: we replace the mcp.js default ListTools
|
|
2187
|
+
// handler on the underlying Server (setRequestHandler is a Map.set).
|
|
2188
|
+
// - `enabled` stays true, so `tools/call` keeps routing normally — per
|
|
2189
|
+
// mcp.js line 106, a `disabled` tool would reject calls too.
|
|
2190
|
+
|
|
2191
|
+
const HIDDEN_TOOL_NAMES = new Set(
|
|
2192
|
+
TOOL_DEFS.filter((t) => t.hidden === true).map((t) => t.name),
|
|
2193
|
+
);
|
|
2194
|
+
|
|
2195
|
+
// Opt-out: setting CLAUDE_MEM_ALL_TOOLS=1 restores pre-v2.34.0 behavior where
|
|
2196
|
+
// all 17 tools are visible in `tools/list`. Users who relied on Claude Code
|
|
2197
|
+
// autonomously invoking the now-hidden maintenance tools can use this as an
|
|
2198
|
+
// immediate escape hatch while adopting the CLI entry points documented in
|
|
2199
|
+
// adopt-content.mjs / README.
|
|
2200
|
+
const EXPOSE_ALL_TOOLS = process.env.CLAUDE_MEM_ALL_TOOLS === '1';
|
|
2201
|
+
|
|
2202
|
+
if (!EXPOSE_ALL_TOOLS) {
|
|
2203
|
+
// Force mcp.js to install its default ListTools/CallTools handlers before
|
|
2204
|
+
// we override; registerTool already did this, but keep the call explicit so
|
|
2205
|
+
// a future reorder of tool registration doesn't break the override.
|
|
2206
|
+
const originalHandler = server.server._requestHandlers.get('tools/list');
|
|
2207
|
+
if (typeof originalHandler !== 'function') {
|
|
2208
|
+
throw new Error('tools/list handler missing — server initialization order changed');
|
|
2209
|
+
}
|
|
2210
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async (req, extra) => {
|
|
2211
|
+
const full = await originalHandler(req, extra);
|
|
2212
|
+
return { ...full, tools: full.tools.filter((t) => !HIDDEN_TOOL_NAMES.has(t.name)) };
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// One-time discoverability banner (stderr only — Claude Code surfaces it on
|
|
2217
|
+
// session start). Skipped under MEM_QUIET_HOOKS=1 so CI / tests / hermeticity
|
|
2218
|
+
// harnesses stay silent.
|
|
2219
|
+
if (!effectiveQuiet()) {
|
|
2220
|
+
const status = EXPOSE_ALL_TOOLS
|
|
2221
|
+
? 'all 17 tools exposed via CLAUDE_MEM_ALL_TOOLS=1'
|
|
2222
|
+
: `tools/list narrowed to ${TOOL_DEFS.length - HIDDEN_TOOL_NAMES.size} core tools (${HIDDEN_TOOL_NAMES.size} hidden but callable by exact name; unset CLAUDE_MEM_ALL_TOOLS to keep, set =1 to restore all)`;
|
|
2223
|
+
process.stderr.write(`[claude-mem-lite v${PKG_VERSION}] ${status}\n`);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2088
2226
|
// ─── WAL Checkpoint (periodic) ───────────────────────────────────────────────
|
|
2089
2227
|
|
|
2090
2228
|
// Checkpoint WAL every 5 minutes to prevent unbounded growth
|
package/tool-schemas.mjs
CHANGED
|
@@ -50,8 +50,8 @@ export const memRecentSchema = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const memTimelineSchema = {
|
|
53
|
-
anchor: coerceInt.pipe(z.number().int()).optional().describe('Observation ID as center point'),
|
|
54
|
-
query: z.string().optional().describe('FTS5 query to auto-find anchor'),
|
|
53
|
+
anchor: coerceInt.pipe(z.number().int()).optional().describe('Observation ID as center point. Takes precedence over query when both are provided.'),
|
|
54
|
+
query: z.string().optional().describe('FTS5 query to auto-find anchor. Ignored when anchor is also given; use one or the other.'),
|
|
55
55
|
before: coerceInt.pipe(z.number().int().min(0).max(50)).optional().describe('Items before anchor (default 5)'),
|
|
56
56
|
after: coerceInt.pipe(z.number().int().min(0).max(50)).optional().describe('Items after anchor (default 5)'),
|
|
57
57
|
project: z.string().optional().describe('Filter by project'),
|
|
@@ -96,18 +96,22 @@ export const memOptimizeSchema = {
|
|
|
96
96
|
.describe('Which optimization tasks to run (default: all)'),
|
|
97
97
|
max_items: coerceInt.pipe(z.number().int().min(1).max(100)).optional().default(15)
|
|
98
98
|
.describe('Maximum LLM calls across all tasks (default: 15)'),
|
|
99
|
+
scope: z.enum(['narrow', 'wide']).optional().default('narrow')
|
|
100
|
+
.describe("Re-enrich scope: narrow=narrative-only candidates (default); wide=R-7 backfill (bugfix/refactor/feature/decision with narrative but lesson_learned='none'). CLI parity: --scope wide."),
|
|
99
101
|
};
|
|
100
102
|
|
|
101
103
|
export const memMaintainSchema = {
|
|
102
104
|
action: z.enum(['scan', 'execute']).describe('scan=analyze candidates, execute=apply changes'),
|
|
103
105
|
operations: z.array(z.enum(['dedup', 'decay', 'cleanup', 'boost', 'purge_stale', 'rebuild_vectors'])).optional()
|
|
104
|
-
.describe('Operations: dedup=find/merge duplicate observations, decay=reduce importance of old low-value obs, cleanup=remove orphaned records, boost=promote frequently-accessed obs, purge_stale=
|
|
106
|
+
.describe('Operations: dedup=find/merge duplicate observations, decay=reduce importance of old low-value obs, cleanup=remove orphaned records, boost=promote frequently-accessed obs, purge_stale=DELETE pending-purge obs older than retain_days (requires confirm=true; first call previews), rebuild_vectors=rebuild TF-IDF vocabulary and all observation vectors'),
|
|
105
107
|
merge_ids: z.preprocess(
|
|
106
108
|
(v) => Array.isArray(v) ? v.map(g => Array.isArray(g) ? g.map(x => typeof x === 'string' ? parseInt(x, 10) : x) : g) : v,
|
|
107
109
|
z.array(z.array(z.number().int()).min(2))
|
|
108
110
|
).optional().describe('For dedup: [[keepId, removeId1, removeId2], ...] — first ID in each group is kept'),
|
|
109
111
|
retain_days: coerceInt.pipe(z.number().int().min(7).max(365)).optional()
|
|
110
112
|
.describe('For purge_stale: keep observations newer than N days (default 30)'),
|
|
113
|
+
confirm: coerceBool.optional()
|
|
114
|
+
.describe('Required for destructive ops in `execute` mode (currently: purge_stale). Omit/false → dry-run preview; true → actually delete.'),
|
|
111
115
|
project: z.string().optional().describe('Filter by project'),
|
|
112
116
|
};
|
|
113
117
|
|
|
@@ -186,6 +190,17 @@ export const memBrowseSchema = {
|
|
|
186
190
|
// Research note: discouragement-style descriptions reduce over-invocation by
|
|
187
191
|
// 40-60% vs. encouragement-style ("use this to..."). See tests/tool-schemas.test.mjs
|
|
188
192
|
// for the invariants this list must satisfy.
|
|
193
|
+
//
|
|
194
|
+
// Core vs hidden (v2.34.0): only 6 tools are exposed via MCP `tools/list`. The
|
|
195
|
+
// remaining 11 stay registered — and are still callable by name at the MCP
|
|
196
|
+
// protocol level (`tools/call` by exact name) — but are omitted from the list
|
|
197
|
+
// response so they don't bloat every agent's startup context. The core set
|
|
198
|
+
// covers the hot paths the invited-memory contract promises (recall before
|
|
199
|
+
// Edit, save after bugfix, search/recent/timeline/get for retrieval). Hidden
|
|
200
|
+
// tools are either maintenance (compress/maintain/optimize/fts_check),
|
|
201
|
+
// admin/infra (stats/export/update/delete), or specialized browsers
|
|
202
|
+
// (browse/registry/use) — all of which have CLI equivalents documented in
|
|
203
|
+
// `adopt-content.mjs`.
|
|
189
204
|
// ────────────────────────────────────────────────────────────────────────────
|
|
190
205
|
|
|
191
206
|
export const tools = [
|
|
@@ -278,6 +293,7 @@ export const tools = [
|
|
|
278
293
|
'\n' +
|
|
279
294
|
'Equivalent CLI: claude-mem-lite delete <id>[,<id>,...] [--confirm]',
|
|
280
295
|
inputSchema: memDeleteSchema,
|
|
296
|
+
hidden: true,
|
|
281
297
|
},
|
|
282
298
|
{
|
|
283
299
|
name: 'mem_save',
|
|
@@ -314,6 +330,7 @@ export const tools = [
|
|
|
314
330
|
'\n' +
|
|
315
331
|
'Equivalent CLI: claude-mem-lite stats [--project X] [--days 30]',
|
|
316
332
|
inputSchema: memStatsSchema,
|
|
333
|
+
hidden: true,
|
|
317
334
|
},
|
|
318
335
|
{
|
|
319
336
|
name: 'mem_compress',
|
|
@@ -332,6 +349,7 @@ export const tools = [
|
|
|
332
349
|
'\n' +
|
|
333
350
|
'Equivalent CLI: claude-mem-lite compress [--preview] [--age-days 90]',
|
|
334
351
|
inputSchema: memCompressSchema,
|
|
352
|
+
hidden: true,
|
|
335
353
|
},
|
|
336
354
|
{
|
|
337
355
|
name: 'mem_maintain',
|
|
@@ -350,6 +368,7 @@ export const tools = [
|
|
|
350
368
|
'\n' +
|
|
351
369
|
'Equivalent CLI: claude-mem-lite maintain --action scan --operations dedup,decay',
|
|
352
370
|
inputSchema: memMaintainSchema,
|
|
371
|
+
hidden: true,
|
|
353
372
|
},
|
|
354
373
|
{
|
|
355
374
|
name: 'mem_optimize',
|
|
@@ -368,6 +387,7 @@ export const tools = [
|
|
|
368
387
|
'\n' +
|
|
369
388
|
'Equivalent CLI: claude-mem-lite optimize [--action preview|run|run_all] [--max-items N]',
|
|
370
389
|
inputSchema: memOptimizeSchema,
|
|
390
|
+
hidden: true,
|
|
371
391
|
},
|
|
372
392
|
{
|
|
373
393
|
name: 'mem_registry',
|
|
@@ -386,6 +406,7 @@ export const tools = [
|
|
|
386
406
|
'\n' +
|
|
387
407
|
'Equivalent CLI: claude-mem-lite registry <list|search|import|...> [args]',
|
|
388
408
|
inputSchema: memRegistrySchema,
|
|
409
|
+
hidden: true,
|
|
389
410
|
},
|
|
390
411
|
{
|
|
391
412
|
name: 'mem_use',
|
|
@@ -404,6 +425,7 @@ export const tools = [
|
|
|
404
425
|
'\n' +
|
|
405
426
|
'Equivalent CLI: MCP only (no CLI handler — use mem_registry to inspect)',
|
|
406
427
|
inputSchema: memUseSchema,
|
|
428
|
+
hidden: true,
|
|
407
429
|
},
|
|
408
430
|
{
|
|
409
431
|
name: 'mem_update',
|
|
@@ -422,6 +444,7 @@ export const tools = [
|
|
|
422
444
|
'\n' +
|
|
423
445
|
'Equivalent CLI: claude-mem-lite update <id> [--title ...] [--lesson ...]',
|
|
424
446
|
inputSchema: memUpdateSchema,
|
|
447
|
+
hidden: true,
|
|
425
448
|
},
|
|
426
449
|
{
|
|
427
450
|
name: 'mem_export',
|
|
@@ -440,6 +463,7 @@ export const tools = [
|
|
|
440
463
|
'\n' +
|
|
441
464
|
'Equivalent CLI: claude-mem-lite export [--format jsonl] [--project X] [--limit 500]',
|
|
442
465
|
inputSchema: memExportSchema,
|
|
466
|
+
hidden: true,
|
|
443
467
|
},
|
|
444
468
|
{
|
|
445
469
|
name: 'mem_recall',
|
|
@@ -476,6 +500,7 @@ export const tools = [
|
|
|
476
500
|
'\n' +
|
|
477
501
|
'Equivalent CLI: claude-mem-lite fts-check [--rebuild]',
|
|
478
502
|
inputSchema: memFtsCheckSchema,
|
|
503
|
+
hidden: true,
|
|
479
504
|
},
|
|
480
505
|
{
|
|
481
506
|
name: 'mem_browse',
|
|
@@ -494,5 +519,6 @@ export const tools = [
|
|
|
494
519
|
'\n' +
|
|
495
520
|
'Equivalent CLI: claude-mem-lite browse [--tier active] [--project X]',
|
|
496
521
|
inputSchema: memBrowseSchema,
|
|
522
|
+
hidden: true,
|
|
497
523
|
},
|
|
498
524
|
];
|