claude-mem-lite 2.31.2 → 2.32.2

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.31.2",
13
+ "version": "2.32.2",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.31.2",
3
+ "version": "2.32.2",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/README.md CHANGED
@@ -92,6 +92,7 @@ The original sends **everything to the LLM and hopes it filters well**. claude-m
92
92
  - **Prompt-time memory injection** -- UserPromptSubmit hook automatically searches and injects relevant past observations with recency and importance weighting
93
93
  - **Smart skill invocation** -- Auto-loaded and searched managed skills/agents include portable `~` paths with `Read()` guidance; native plugin skills recommend `Skill("full:name")`; prevents `Skill()` misuse for managed resources that aren't registered with Claude Code's native handler
94
94
  - **Dual injection dedup** -- `user-prompt-search.js` and `handleUserPrompt` coordinate via temp file to prevent duplicate memory injection
95
+ - **Plugin cache hook self-heal** -- Claude Code runtime reads plugin hooks from `~/.claude/plugins/cache/<mp>/<plugin>/<ver>/hooks/hooks.json`, not from the marketplace source. When `install.mjs`-managed `settings.json` hooks coexist with a stale cache `hooks.json` (e.g. from a previous marketplace install or a plugin auto-update), the runtime registers hooks twice → every session start / user prompt fires twice. `install.mjs` and `hook-update.mjs` now clear cache `hooks.json` in every version dir, and `hook.mjs session-start` self-heals on every session (gated by `hasInstallManagedHooks` so plugin-only users are not affected). `install.mjs status` reports cache pollution state (since v2.31.1/2.31.2).
95
96
  - **Result-dedup cooldown** -- User-prompt memory injection uses result-overlap detection (>80% ID overlap → skip) instead of time-based cooldown, allowing topic switches within seconds while preventing redundant injections
96
97
  - **OR query fallback** -- When AND-joined FTS5 queries return zero results, automatically relaxes to OR-joined queries for broader recall (applied in both user-prompt-search and hook-memory paths)
97
98
  - **Configurable LLM model** -- Switch between Haiku (fast/cheap) and Sonnet (deeper analysis) via `CLAUDE_MEM_MODEL` env var
@@ -101,6 +102,9 @@ The original sends **everything to the LLM and hopes it filters well**. claude-m
101
102
  - **LLM concurrency control** -- File-based semaphore limits background workers to 2 concurrent LLM calls, preventing resource contention
102
103
  - **stdin overflow protection** -- Hook input truncated at 256KB with regex-based action salvage for oversized tool outputs
103
104
  - **Cross-session handoff** -- Captures session state (request, completed work, next steps, key files) on `/clear` or `/exit`, then injects context when the next session detects continuation intent via explicit keywords or FTS5 term overlap
105
+ - **Git-SHA continuation anchor** (v2.31.0) -- Handoff rows include `git_sha_at_handoff`; any handoff matching the current `HEAD` counts as continuation regardless of TTL. Code state is a stronger continuation signal than wall-clock time
106
+ - **Startup dashboard** (v2.31.0) -- SessionStart hook aggregates `git status` + `~/.claude/tasks/*.json` + `~/.claude/plans/*.md` + most-recent exit handoff + recent event count into a single structured block injected via `hookSpecificOutput.additionalContext`
107
+ - **Activity namespace** (v2.31.0) -- Dedicated `events` table + FTS5 for non-memdir types (`bugfix`, `lesson`, `bug`, `discovery`, `refactor`, `feature`, `observation`, `decision`) that don't compete with `WHAT_NOT_TO_SAVE` semantics on the observations table. CLI: `claude-mem-lite activity save|search|recent|show`. Slash commands: `/lesson`, `/bug`. `hook-llm` routes non-memdir summary types through `persistHaikuSummary` so upgrades from observations→events are atomic
104
108
  - **In-place observation updates** -- `mem_update` tool modifies existing observations atomically (field update + FTS text rebuild + vector re-computation in one transaction), preserving original IDs and references
105
109
  - **Bulk export** -- `mem_export` tool exports observations as JSON or JSONL, with project/type/date filtering and 1000-row pagination cap with batch guidance
106
110
  - **FTS integrity management** -- `mem_fts_check` tool verifies FTS5 index health or rebuilds indexes on demand, useful after database recovery or when search results seem wrong
@@ -235,6 +239,8 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
235
239
  /mem timeline <query> # Browse timeline around a match
236
240
  /mem browse # Tier-grouped memory dashboard
237
241
  /mem <query> # Shorthand for search
242
+ /lesson <text> # Save a non-obvious lesson to the events table (v2.31.0)
243
+ /bug <text> # Log a known bug + repro steps to the events table (v2.31.0)
238
244
  ```
239
245
 
240
246
  ### Efficient Search Workflow
@@ -245,6 +251,48 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
245
251
  3. mem_get(ids=[12345, 12346]) -> full details
246
252
  ```
247
253
 
254
+ ### Invited Memory (v2.32+)
255
+
256
+ Opt-in mechanism that installs a single sentinel-wrapped line into the project's
257
+ memdir (`~/.claude/projects/<encoded>/memory/MEMORY.md`) so Claude Code loads
258
+ the plugin's MCP-tool triggers as **user-memory** — a higher instruction-following
259
+ authority than MCP server instructions (which are framed as tool metadata).
260
+
261
+ ```bash
262
+ claude-mem-lite adopt # install for current project
263
+ claude-mem-lite adopt --all # install for every project under ~/.claude/projects/
264
+ claude-mem-lite adopt --status # list adopted projects + sentinel versions
265
+ claude-mem-lite adopt --dry-run # preview without writing
266
+ claude-mem-lite unadopt # remove cleanly
267
+ ```
268
+
269
+ Slash commands `/adopt` and `/unadopt` wrap the same CLI.
270
+
271
+ **What adoption changes:**
272
+ - A `<!-- claude-mem-lite:begin v1 -->…<!-- claude-mem-lite:end -->` block is
273
+ added to `MEMORY.md` under a `## 插件契约` header, containing one ≤150-char
274
+ line pointing at `mem_recall` / `mem_save` with their key arguments.
275
+ - A `plugin_claude_mem_lite.md` detail file is written (not auto-loaded; read
276
+ on demand when the MEMORY.md pointer surfaces in context).
277
+ - Post-adopt the conservative hook layer auto-trims: MCP server instructions
278
+ drop the `WHEN TO USE` section, SessionStart injection drops the `File Lessons`
279
+ / `Key Context` sections. `#ID` references and the `Recent` table still fire
280
+ so `mem_get` remains reachable.
281
+
282
+ **Safety:**
283
+ - Hash-guarded: editing the sentinel body yourself blocks automatic rewrites
284
+ unless you pass `--force`.
285
+ - Budget-gated: refuses to insert when MEMORY.md is already >180 lines, so
286
+ Claude Code's 200-line MEMORY.md cap won't truncate the block.
287
+ - `install` only auto-adopts when run from the claude-mem-lite source repo
288
+ itself (detected via git remote match); other users must opt in explicitly.
289
+ - The fallback hook layer is never removed from source — conditional trim is
290
+ runtime-gated on sentinel presence, so projects without adoption get the
291
+ full verbose output.
292
+
293
+ See `docs/plans/2026-04-16-invited-memory-pattern.md` for the full design
294
+ (including the reusable template other plugins can follow).
295
+
248
296
  ## Database Schema
249
297
 
250
298
  Five core tables with FTS5 virtual tables for search:
@@ -523,6 +571,8 @@ npm run benchmark:gate # CI gate: fails if metrics regress beyond 5% toleranc
523
571
  | `CLAUDE_MEM_DIR` | Custom data directory. All databases, runtime files, and managed resources are stored here. | `~/.claude-mem-lite/` |
524
572
  | `CLAUDE_MEM_MODEL` | LLM model for background calls (episode extraction, session summaries). Accepts `haiku` or `sonnet`. | `haiku` |
525
573
  | `CLAUDE_MEM_DEBUG` | Enable debug logging (`1` to enable). | _(disabled)_ |
574
+ | `MEM_QUIET_HOOKS` | Low-noise hooks. `1` drops the `File Lessons` / `Key Context` sections from SessionStart injection, the lesson suffix from `[mem] Related memories`, and the `WHEN TO USE` / `Decision rules` blocks from MCP server instructions. IDs and the `Recent` table still surface so `mem_get(ids=[…])` remains reachable. Intended for users running the invited-memory adopt path or who otherwise want minimal auto-injection. | _(disabled)_ |
575
+ | `MEM_NO_ADOPT_HINT` | Silences the one-line "Invited-memory 未启用:`claude-mem-lite adopt`…" hint that SessionStart appends when the current project hasn't been adopted. The hint self-clears once `adopt` runs; set this env to suppress it without adopting. | _(disabled)_ |
526
576
 
527
577
  ## License
528
578
 
package/README.zh-CN.md CHANGED
@@ -94,6 +94,10 @@
94
94
  - **LLM 并发控制** -- 基于文件的信号量将后台 worker 限制为 2 个并发 LLM 调用,防止资源争用
95
95
  - **stdin 溢出保护** -- Hook 输入在 256KB 处截断,对超大工具输出使用正则挽救关键信息
96
96
  - **跨会话交接** -- 在 `/clear` 或 `/exit` 时捕获会话状态(请求、已完成工作、后续步骤、关键文件),下次会话检测到继续意图时自动注入上下文(支持显式关键词和 FTS5 术语重叠匹配)
97
+ - **插件缓存 hook 自愈** -- Claude Code runtime 从 `~/.claude/plugins/cache/<mp>/<plugin>/<ver>/hooks/hooks.json` 读取插件 hook,而非 marketplace 源。当 `install.mjs` 写入 `settings.json` 的 hooks 与残留 cache `hooks.json` 同时存在(例如曾装过 marketplace 版本,或插件被 Claude Code 自动升级重建 cache),runtime 会注册两套 hook → 每次 SessionStart / UserPromptSubmit 都触发两份。`install.mjs` 和 `hook-update.mjs` 现在会清理每个 cache 版本目录下的 `hooks.json`;`hook.mjs session-start` 每次启动自愈(通过 `hasInstallManagedHooks` 门控,不影响纯插件模式用户);`install.mjs status` 会报告 cache 污染状况(自 v2.31.1 / v2.31.2 起)。
98
+ - **Git-SHA 延续锚点**(v2.31.0)-- handoff 记录包含 `git_sha_at_handoff` 字段,任何匹配当前 `HEAD` 的 handoff 都视为延续会话,不受 TTL 限制。代码状态比时钟时间更能反映上下文延续。
99
+ - **启动面板**(v2.31.0)-- SessionStart hook 将 `git status` + `~/.claude/tasks/*.json` + `~/.claude/plans/*.md` + 最近 /exit 交接 + 最近事件数聚合为一个结构化块,通过 `hookSpecificOutput.additionalContext` 注入。
100
+ - **活动命名空间**(v2.31.0)-- 为非 memdir 类型(`bugfix` / `lesson` / `bug` / `discovery` / `refactor` / `feature` / `observation` / `decision`)启用独立的 `events` 表 + FTS5,与 observations 表的 `WHAT_NOT_TO_SAVE` 语义解耦。CLI:`claude-mem-lite activity save|search|recent|show`;斜杠命令:`/lesson`、`/bug`。`hook-llm` 通过 `persistHaikuSummary` 路由非 memdir 摘要,observations → events 升级路径是事务原子的。
97
101
 
98
102
  ## 平台支持
99
103
 
@@ -222,6 +226,8 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
222
226
  /mem timeline <query> # 围绕匹配结果浏览时间线
223
227
  /mem browse # 分层记忆仪表盘
224
228
  /mem <query> # search 的简写
229
+ /lesson <text> # 保存非显而易见的经验到 events 表(v2.31.0)
230
+ /bug <text> # 记录已知 bug + 复现步骤到 events 表(v2.31.0)
225
231
  ```
226
232
 
227
233
  ### 高效搜索工作流
@@ -232,6 +238,44 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
232
238
  3. mem_get(ids=[12345, 12346]) -> 完整详情
233
239
  ```
234
240
 
241
+ ### Invited Memory(邀请式记忆,v2.32+)
242
+
243
+ Opt-in 机制——向项目 memdir(`~/.claude/projects/<encoded>/memory/MEMORY.md`)
244
+ 注入单行 sentinel 包围的插件契约,Claude Code 会把它作为 **user-memory** 加载
245
+ 到系统提示——比 MCP server instructions(被框定为 tool metadata)具有更高的
246
+ instruction-following 权威。
247
+
248
+ ```bash
249
+ claude-mem-lite adopt # 当前项目注入
250
+ claude-mem-lite adopt --all # 扫描 ~/.claude/projects/* 全部注入
251
+ claude-mem-lite adopt --status # 列出所有已 adopt 的项目 + 版本号
252
+ claude-mem-lite adopt --dry-run # 只打印不写入
253
+ claude-mem-lite unadopt # 精确移除
254
+ ```
255
+
256
+ Slash 命令 `/adopt` 和 `/unadopt` 是上述 CLI 的包装。
257
+
258
+ **Adopt 后会发生什么:**
259
+ - `MEMORY.md` 新增一段 `<!-- claude-mem-lite:begin v1 -->…<!-- claude-mem-lite:end -->`
260
+ 包裹的 `## 插件契约`,含一行 ≤150 字符、指向 `mem_recall` / `mem_save` 关键参数
261
+ 的动作锚条目。
262
+ - 生成 `plugin_claude_mem_lite.md` 详情文件(按需读取,不自动加载)。
263
+ - 保守 hook 层自动瘦身:MCP server instructions 去掉 `WHEN TO USE` 段,
264
+ SessionStart 注入去掉 `File Lessons` / `Key Context`。`#ID` 引用与 `Recent`
265
+ 表保留,`mem_get` 仍可随时展开。
266
+
267
+ **安全性:**
268
+ - Hash 守护:你手动改了 sentinel 段 → 下一次 adopt 报 `UserEditedError`,
269
+ 除非显式 `--force`。
270
+ - 预算门:MEMORY.md 已 >180 行时拒绝新增(避开 Claude Code 200 行截断)。
271
+ - `install` 只在从 claude-mem-lite 源码仓库运行时 auto-adopt(靠 git remote 判别);
272
+ 其它用户需显式调用。
273
+ - 保守 hook 层源码永不删——条件瘦身仅基于 sentinel 存在性做 runtime 判断,
274
+ 未 adopt 的项目仍看完整 verbose 输出。
275
+
276
+ 完整设计见 `docs/plans/2026-04-16-invited-memory-pattern.md`(含其它插件
277
+ 可复用的模板)。
278
+
235
279
  ## 数据库结构
236
280
 
237
281
  五张核心表 + FTS5 虚拟表用于搜索:
@@ -500,6 +544,8 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
500
544
  | `CLAUDE_MEM_DIR` | 自定义数据目录。所有数据库、运行时文件和托管资源均存储在此。 | `~/.claude-mem-lite/` |
501
545
  | `CLAUDE_MEM_MODEL` | 后台 LLM 调用模型(Episode 提取、会话总结、调度)。可选 `haiku` 或 `sonnet`。 | `haiku` |
502
546
  | `CLAUDE_MEM_DEBUG` | 启用调试日志(设为 `1` 启用)。 | _(禁用)_ |
547
+ | `MEM_QUIET_HOOKS` | 低噪声 hook。设为 `1` 时,SessionStart 注入去掉 `File Lessons` / `Key Context` 两节,`[mem] Related memories` 去掉 lesson 后缀,MCP server instructions 去掉 `WHEN TO USE` / `Decision rules` 两段。ID 与 `Recent` 表仍保留,`mem_get(ids=[…])` 可继续展开细节。适用于启用了 invited-memory adopt 流程或偏好最小化自动注入的用户。 | _(禁用)_ |
548
+ | `MEM_NO_ADOPT_HINT` | 静音当前项目未 adopt 时 SessionStart 追加的那一行 "Invited-memory 未启用…" 提示。一旦运行 `adopt` 该提示自动消失;此 env 让偏好保守层的用户在不 adopt 的情况下也能静音。 | _(禁用)_ |
503
549
 
504
550
  ## 许可证
505
551
 
package/adopt-cli.mjs ADDED
@@ -0,0 +1,166 @@
1
+ // Phase C (Invited-Memory plan, T11): CLI handlers for
2
+ // claude-mem-lite adopt [--all] [--force] [--dry-run] [--status]
3
+ // claude-mem-lite unadopt [--all]
4
+ //
5
+ // adopt = write sentinel section into MEMORY.md + drop plugin_claude_mem_lite.md
6
+ // unadopt = precise sentinel removal + doc cleanup
7
+ // --all = scan every project under ~/.claude/projects/*/memory/
8
+ // --force = override UserEditedError
9
+ // --dry-run = print intent without writing
10
+ // --status = list all adopted projects + versions
11
+
12
+ import { existsSync, readdirSync, statSync } from 'fs';
13
+ import { homedir } from 'os';
14
+ import { join } from 'path';
15
+ import {
16
+ memdirPath, writePluginSection, removePluginSection,
17
+ writePluginDoc, removePluginDoc,
18
+ isAdopted, readMemoryIndex,
19
+ UserEditedError, BudgetExceededError,
20
+ } from './memdir.mjs';
21
+ import {
22
+ PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, getIndexLine, getDetailDoc,
23
+ } from './adopt-content.mjs';
24
+
25
+ function log(msg) { console.log(msg); }
26
+
27
+ function detectCwd() {
28
+ return process.env.CLAUDE_PROJECT_DIR || process.env.PWD || process.cwd();
29
+ }
30
+
31
+ function projectsRoot() {
32
+ return join(homedir(), '.claude', 'projects');
33
+ }
34
+
35
+ function listAllMemdirs() {
36
+ const base = projectsRoot();
37
+ if (!existsSync(base)) return [];
38
+ const out = [];
39
+ for (const name of readdirSync(base)) {
40
+ const memdir = join(base, name, 'memory');
41
+ try {
42
+ if (existsSync(memdir) && statSync(memdir).isDirectory()) {
43
+ out.push({ projectSlug: name, memdir });
44
+ }
45
+ } catch { /* ignore entries we can't stat */ }
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function hasFlag(args, flag) { return Array.isArray(args) && args.includes(flag); }
51
+
52
+ /**
53
+ * cmdAdopt — write sentinel section + plugin doc to memdir.
54
+ * Exit code 1 on any hard failure; skipped (--all + UserEditedError) doesn't
55
+ * fail the batch.
56
+ */
57
+ export function cmdAdopt(args = []) {
58
+ if (hasFlag(args, '--status')) return statusAll();
59
+
60
+ const all = hasFlag(args, '--all');
61
+ const force = hasFlag(args, '--force');
62
+ const dryRun = hasFlag(args, '--dry-run');
63
+
64
+ const targets = all
65
+ ? listAllMemdirs().map((m) => m.memdir)
66
+ : [memdirPath(detectCwd())];
67
+
68
+ if (targets.length === 0) {
69
+ log('[adopt] no memdirs to adopt (use without --all for current project)');
70
+ return;
71
+ }
72
+
73
+ let created = 0, updated = 0, unchanged = 0, skipped = 0, failed = 0;
74
+ for (const memdir of targets) {
75
+ const r = adoptOne(memdir, { force, dryRun, all });
76
+ if (r.action === 'created') created++;
77
+ else if (r.action === 'updated') updated++;
78
+ else if (r.action === 'unchanged') unchanged++;
79
+ else if (r.action === 'skipped') skipped++;
80
+ else if (r.action === 'dry-run') unchanged++;
81
+ else failed++;
82
+ }
83
+
84
+ log('');
85
+ log(`[adopt] ${targets.length} target(s): ${created} created, ${updated} updated, ${unchanged} unchanged, ${skipped} skipped, ${failed} failed`);
86
+ if (failed > 0) process.exitCode = 1;
87
+ }
88
+
89
+ function adoptOne(memdir, { force, dryRun, all }) {
90
+ const contentLine = getIndexLine();
91
+ const version = CURRENT_SENTINEL_VERSION;
92
+
93
+ if (dryRun) {
94
+ log(`[adopt --dry-run] ${memdir}`);
95
+ log(` MEMORY.md line: ${contentLine}`);
96
+ log(` detail file: plugin_claude_mem_lite.md (${getDetailDoc().length} chars)`);
97
+ return { action: 'dry-run' };
98
+ }
99
+
100
+ try {
101
+ const r = writePluginSection(memdir, { slug: PLUGIN_SLUG, version, contentLine, force });
102
+ writePluginDoc(memdir, PLUGIN_SLUG, getDetailDoc());
103
+ log(`[adopt] ${memdir} → ${r.action}`);
104
+ return r;
105
+ } catch (e) {
106
+ if (e instanceof UserEditedError && all) {
107
+ log(`[adopt] ${memdir} → skipped (user-edited; pass --force to override)`);
108
+ return { action: 'skipped' };
109
+ }
110
+ if (e instanceof UserEditedError) {
111
+ log(`[adopt] ${memdir} → refused: ${e.message}`);
112
+ log('[adopt] pass --force to overwrite, or edit/uninstall manually.');
113
+ return { action: 'failed' };
114
+ }
115
+ if (e instanceof BudgetExceededError) {
116
+ log(`[adopt] ${memdir} → failed: ${e.message}`);
117
+ return { action: 'failed' };
118
+ }
119
+ log(`[adopt] ${memdir} → error: ${e.message}`);
120
+ return { action: 'failed' };
121
+ }
122
+ }
123
+
124
+ function statusAll() {
125
+ const dirs = listAllMemdirs();
126
+ log('[adopt --status] scanning ~/.claude/projects/*/memory/');
127
+ if (dirs.length === 0) { log(' (no memdirs found)'); return; }
128
+ let adopted = 0;
129
+ for (const { projectSlug, memdir } of dirs) {
130
+ if (isAdopted(memdir, PLUGIN_SLUG)) {
131
+ const idx = readMemoryIndex(memdir, PLUGIN_SLUG);
132
+ log(` ✓ ${projectSlug} (${idx.version})`);
133
+ adopted++;
134
+ }
135
+ }
136
+ log('');
137
+ log(`[adopt --status] ${adopted}/${dirs.length} adopted`);
138
+ }
139
+
140
+ /**
141
+ * cmdUnadopt — precise removal of sentinel section + plugin doc.
142
+ * Exit code stays 0: unadopt is idempotent; "absent" isn't an error.
143
+ */
144
+ export function cmdUnadopt(args = []) {
145
+ const all = hasFlag(args, '--all');
146
+ const targets = all
147
+ ? listAllMemdirs().map((m) => m.memdir)
148
+ : [memdirPath(detectCwd())];
149
+
150
+ if (targets.length === 0) {
151
+ log('[unadopt] no memdirs found');
152
+ return;
153
+ }
154
+
155
+ let removed = 0, absent = 0;
156
+ for (const memdir of targets) {
157
+ const r = removePluginSection(memdir, PLUGIN_SLUG);
158
+ removePluginDoc(memdir, PLUGIN_SLUG);
159
+ if (r.action === 'removed') removed++;
160
+ else absent++;
161
+ log(`[unadopt] ${memdir} → ${r.action}`);
162
+ }
163
+
164
+ log('');
165
+ log(`[unadopt] ${targets.length} target(s): ${removed} removed, ${absent} absent`);
166
+ }
@@ -0,0 +1,75 @@
1
+ // Phase C (Invited-Memory plan, T10): Content generators for the
2
+ // claude-mem-lite sentinel line and its companion plugin_claude_mem_lite.md
3
+ // detail doc. Kept separate so the strings are testable without side effects
4
+ // and can evolve without touching memdir.mjs primitives.
5
+ //
6
+ // Bumping CURRENT_SENTINEL_VERSION: pick the next vN and update
7
+ // docs/plans/2026-04-16-invited-memory-pattern.md so installs do a version-
8
+ // bump replace instead of treating the old content as a user edit.
9
+
10
+ export const PLUGIN_SLUG = 'claude-mem-lite';
11
+ export const CURRENT_SENTINEL_VERSION = 'v1';
12
+
13
+ /**
14
+ * One-line entry injected into MEMORY.md's sentinel section. Must stay ≤150
15
+ * chars so it clears Claude Code's 200-line MEMORY.md cap with headroom.
16
+ */
17
+ export function getIndexLine() {
18
+ return '- [claude-mem-lite](plugin_claude_mem_lite.md) — Edit 前 `mem_recall(file=…)`;bugfix 后 `mem_save(type="bugfix", lesson_learned, importance=2)`';
19
+ }
20
+
21
+ /**
22
+ * Full detail doc rendered into `memdir/plugin_claude_mem_lite.md`. Not
23
+ * auto-loaded by Claude Code — the MEMORY.md index line points to it and
24
+ * Claude reads it on demand when the injected hint suggests relevance.
25
+ */
26
+ export function getDetailDoc() {
27
+ return `# claude-mem-lite 插件契约
28
+
29
+ > 由 \`claude-mem-lite adopt\` 生成;卸载用 \`claude-mem-lite unadopt\`。
30
+ > 设计背景见 docs/plans/2026-04-16-invited-memory-pattern.md。
31
+
32
+ ## 何时调用 MCP 工具
33
+
34
+ | 时机 | 工具 | 关键参数 |
35
+ |------|------|----------|
36
+ | Edit / Write 前 | \`mem_recall\` | \`file="<路径>"\` —— 过往 bugfix 与教训 |
37
+ | Test failure / error | \`mem_search\` | \`query="<错误关键词>", obs_type="bugfix"\` |
38
+ | Refactor 前 | \`mem_search\` | \`query="<模块>", obs_type="refactor"\` |
39
+ | 新功能起手 | \`mem_search\` | \`query="<功能区域>"\` —— 找 prior art |
40
+ | Bugfix 解决后 | \`mem_save\` | \`type="bugfix", lesson_learned="<根因+修法>", importance=2\` |
41
+ | 架构决策后 | \`mem_save\` | \`type="decision", lesson_learned="<取舍理由>", importance=2\` |
42
+ | 上下文提到 #NN | \`mem_get\` | \`ids=[NN]\` |
43
+
44
+ ## Decision rules(替代多步 search)
45
+
46
+ - "最近做了啥" → \`mem_recent\`
47
+ - "<文件> 有哪些记忆" → \`mem_recall\`
48
+ - "#NN 前后发生了啥" → \`mem_timeline\`
49
+ - "清理过期记忆" → \`mem_maintain\`
50
+ - "FTS 索引健康吗" → \`mem_fts_check\`
51
+ - "按 tier 浏览" → \`mem_browse\`
52
+ - "备份导出" → \`mem_export\`
53
+
54
+ ## CLI 速查
55
+
56
+ | 命令 | 用途 |
57
+ |------|------|
58
+ | \`claude-mem-lite search "query"\` | FTS5 全文搜索 |
59
+ | \`claude-mem-lite search "err" --type bugfix\` | 按类型过滤 |
60
+ | \`claude-mem-lite recall "file.mjs"\` | 文件相关记忆 |
61
+ | \`claude-mem-lite recent 5\` | 最近 5 条 |
62
+ | \`claude-mem-lite get 42,43\` | 按 ID 展开 |
63
+ | \`claude-mem-lite timeline --anchor 42\` | 时间线上下文 |
64
+
65
+ ## 质量门槛
66
+
67
+ - \`mem_save\` 的 \`lesson_learned\` 不要写 \`none\`——写不出教训就保持 NULL
68
+ - \`decision\` 命中率 72.7% vs \`change\` 16.5%——一条好 decision ≈ 20 条 change
69
+ - 一般搜索跳过 \`obs_type\` 让系统自动路由;特定意图再过滤
70
+
71
+ ## 卸载
72
+
73
+ \`claude-mem-lite unadopt\` 精确移除 sentinel 段 + 本文件;其它 MEMORY.md 内容不动。
74
+ `;
75
+ }
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: adopt
3
+ description: "Use when: user asks to increase claude-mem-lite's tool-invocation rate in the current project, or wants to install the invited-memory sentinel so Claude Code auto-loads the contract as user-memory. Writes a single sentinel-wrapped line to ~/.claude/projects/<encoded>/memory/MEMORY.md plus a plugin_claude_mem_lite.md detail file. Run /unadopt to remove."
4
+ ---
5
+
6
+ # /adopt
7
+
8
+ Install the **invited-memory** sentinel into the current project's Claude Code
9
+ memdir (`~/.claude/projects/<encoded>/memory/`). The sentinel line carries the
10
+ MCP-tool triggers (`mem_recall` / `mem_save`) at system-prompt authority
11
+ (framed as "user's auto-memory") so Claude Code is more likely to call them
12
+ proactively than when those same triggers live in MCP server instructions.
13
+
14
+ ## What it writes
15
+
16
+ 1. **`MEMORY.md`** — ONE sentinel-wrapped line under a `## 插件契约` header.
17
+ Idempotent: re-running replaces the block only if the version bumped.
18
+ 2. **`plugin_claude_mem_lite.md`** — Detailed contract (CLI cheatsheet +
19
+ MCP decision rules). Not auto-loaded; Claude reads it on demand when the
20
+ MEMORY.md pointer surfaces in context.
21
+ 3. **`.plugin_claude_mem_lite_state.json`** — Sidecar with sentinel body hash
22
+ for user-edit detection.
23
+
24
+ ## Flags
25
+
26
+ - `--force` — overwrite a sentinel block that was manually edited
27
+ - `--dry-run` — print intended writes without touching disk
28
+ - `--all` — adopt every memdir under `~/.claude/projects/*/memory/`
29
+ - `--status` — list all adopted projects with their sentinel versions
30
+
31
+ ## Removal
32
+
33
+ `/unadopt` precisely removes the sentinel block + plugin doc + state file.
34
+ User content outside the sentinel is preserved.
35
+
36
+ ## Conservative layer still active
37
+
38
+ Adoption does NOT remove the hook-based injection (SessionStart context,
39
+ UserPromptSubmit related-memory). Those remain as fallback for older Claude
40
+ Code versions. Post-adopt the MCP `WHEN TO USE` section + the `File Lessons` /
41
+ `Key Context` sections auto-trim since the sentinel line already carries the
42
+ triggers at higher authority.
43
+
44
+ !claude-mem-lite adopt $ARGUMENTS
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: bug
3
+ description: "Use when: logging a known bug + repro steps you can't fix right now. Writes to the mem events table (NOT memdir). Skip for bugs you're actively fixing in the current turn — just fix them."
4
+ ---
5
+
6
+ # /bug
7
+
8
+ Record a known bug + reproduction steps. Writes to the mem `events` table
9
+ with `event_type='bug'`. Does NOT touch memdir. Useful for bugs you can't
10
+ fix immediately but want future sessions (or yourself after `/clear`) to
11
+ know about and avoid re-investigating.
12
+
13
+ ## When to use
14
+
15
+ - Bug is known but fix is blocked (waiting on upstream, needs discussion).
16
+ - Bug is intermittent / race condition; you want a repro recipe recorded.
17
+ - Edge case affecting only part of users; not priority now but must not
18
+ be silently re-discovered.
19
+
20
+ Don't use for bugs you're actively fixing in the current turn — just fix
21
+ them. Use `/lesson` afterward if there's a non-obvious root cause worth
22
+ recording.
23
+
24
+ ## Arguments
25
+
26
+ - Positional text: short bug description.
27
+ - `--file <path>` or `--files f1,f2,...`: affected files. Triggers
28
+ pre-tool-recall warning when these files are edited later.
29
+ - `--repro "step1; step2; step3"`: reproduction steps, semicolon-separated.
30
+ - `--severity low|med|high`: maps to importance `1|2|3`. Default `med`.
31
+
32
+ ## Execution
33
+
34
+ Map severity to importance:
35
+
36
+ - `low` → importance 1
37
+ - `med` → importance 2 (default)
38
+ - `high` → importance 3
39
+
40
+ Build the body as `<description>\n\nRepro:\n<repro-steps>` (or just
41
+ `<description>` if `--repro` is absent).
42
+
43
+ Run via Bash:
44
+
45
+ claude-mem-lite activity save \
46
+ --type bug \
47
+ --title "<first 60 chars of description>" \
48
+ --body "<body>" \
49
+ [--file <path> | --files f1,f2,...] \
50
+ --importance <1|2|3>
51
+
52
+ Confirm to user with: `Bug logged: activity #<id>`.
53
+
54
+ ## Examples
55
+
56
+ - `/bug intermittent test flake when DB is under load --repro "run pnpm test -- --retry=0; fails ~1 in 20 runs when CPU is busy"`.
57
+ - `/bug --file src/auth.mjs --severity high "session refresh drops the X-Forwarded-For header"` — no repro steps, just a high-severity note.
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: lesson
3
+ description: "Use when: capturing a non-obvious lesson/gotcha/workaround after a tricky fix or surprising behavior. Writes to the mem events table (NOT memdir). Skip for typos, renames, or user-preference rules."
4
+ ---
5
+
6
+ # /lesson
7
+
8
+ Record a lesson / gotcha / workaround. Writes to the mem `events` table
9
+ with `event_type='lesson'`. Does NOT touch memdir — so lessons never end up
10
+ in the L1 system-prompt memory section and do not conflict with the
11
+ `WHAT_NOT_TO_SAVE` semantics sdscc enforces.
12
+
13
+ ## When to use
14
+
15
+ After you (or the user) solve a tricky bug or discover a non-obvious behavior:
16
+
17
+ - "you need to call X before Y or the test hangs"
18
+ - "don't use this import path — it shadows the real module"
19
+ - "the API returns null when you pass empty string, even though docs say it throws"
20
+
21
+ Don't use for trivial fixes (typos, renames), for rules that belong in memdir
22
+ (user preferences, project-wide conventions), or for general project notes
23
+ (use `/mem:memory` or `mem_save` instead).
24
+
25
+ ## Arguments
26
+
27
+ - Positional text: the lesson description (what went wrong + root cause + fix).
28
+ - `--file <path>`: scope to a specific file. The lesson will surface via
29
+ pre-tool-recall the next time the file is opened via Edit/Write.
30
+ - `--files f1,f2,f3`: comma-separated list for multiple files.
31
+ - `--importance <1|2|3>`: default `2`. Use `3` for critical lessons you always
32
+ want surfaced; `1` for minor gotchas.
33
+
34
+ ## Execution
35
+
36
+ Run via Bash — the CLI takes the lesson text as positional args and stores
37
+ the full text as the title (the `activity save` command uses title-as-body
38
+ when `--body` is absent):
39
+
40
+ claude-mem-lite activity save \
41
+ --type lesson \
42
+ --title "<first 60 chars of text>" \
43
+ --body "<full text>" \
44
+ [--file <path> | --files f1,f2,...] \
45
+ --importance <1|2|3>
46
+
47
+ Confirm to user with: `Lesson saved: activity #<id>`.
48
+
49
+ ## Examples
50
+
51
+ - `/lesson fix a bug now` — stores a short lesson for the current project; no file scope.
52
+ - `/lesson --file src/auth.mjs "session cookies must be httpOnly or the e2e tests bleed across browsers"` — file-scoped.
53
+ - `/lesson --files src/a.mjs,src/b.mjs --importance 3 "both modules share a cache via WeakMap; clearing one invalidates the other"` — multi-file, high importance.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: unadopt
3
+ description: "Use when: user wants to remove the invited-memory sentinel from the current project (or all projects with --all). Cleans MEMORY.md sentinel block + plugin_claude_mem_lite.md + state sidecar. User content outside the sentinel is preserved. Benign no-op on never-adopted memdirs."
4
+ ---
5
+
6
+ # /unadopt
7
+
8
+ Remove the claude-mem-lite invited-memory sentinel from the current project's
9
+ Claude Code memdir. Opposite of `/adopt`.
10
+
11
+ ## What it removes
12
+
13
+ 1. The `<!-- claude-mem-lite:begin vN --> ... <!-- claude-mem-lite:end -->`
14
+ block from `MEMORY.md` (plus the preceding `## 插件契约` header line it owns).
15
+ 2. `plugin_claude_mem_lite.md` detail file.
16
+ 3. `.plugin_claude_mem_lite_state.json` sidecar.
17
+
18
+ Everything else in `MEMORY.md` is preserved byte-for-byte.
19
+
20
+ ## Flags
21
+
22
+ - `--all` — unadopt every memdir under `~/.claude/projects/*/memory/`
23
+
24
+ ## Aftermath
25
+
26
+ Once unadopted, the conservative hook layer (SessionStart `File Lessons` /
27
+ `Key Context`, MCP instructions `WHEN TO USE`) goes back to verbose mode —
28
+ Claude Code will see the full injection again on the next session start.
29
+
30
+ !claude-mem-lite unadopt $ARGUMENTS
package/hook-context.mjs CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  debugLog, debugCatch,
9
9
  DECAY_HALF_LIFE_BY_TYPE, DEFAULT_DECAY_HALF_LIFE_MS, notLowSignalTitleClause,
10
10
  } from './utils.mjs';
11
- import { STALE_SESSION_MS, FALLBACK_OBS_WINDOW_MS } from './hook-shared.mjs';
11
+ import { STALE_SESSION_MS, FALLBACK_OBS_WINDOW_MS, effectiveQuiet } from './hook-shared.mjs';
12
12
  import { extractUnfinishedSummary } from './hook-handoff.mjs';
13
13
 
14
14
  /**
@@ -315,18 +315,25 @@ export function buildSessionContextLines(db, project, now = new Date(), currentC
315
315
  keyContext.push(`- [${o.type || 'discovery'}] ${truncate(clean, 80)} (#${o.id})${lesson}`);
316
316
  }
317
317
 
318
- if (fileLessons.length > 0) {
318
+ // Phase A (QUIET_HOOKS) + Phase D (adopted sentinel): drop descriptive
319
+ // File Lessons / Key Context sections when the user has opted into low-noise
320
+ // hooks OR adopted invited-memory (MEMORY.md sentinel carries the triggers
321
+ // at higher system-prompt authority). The Recent table still fires so #IDs
322
+ // remain reachable via mem_get.
323
+ const quiet = effectiveQuiet();
324
+ if (fileLessons.length > 0 && !quiet) {
319
325
  summaryLines.push('### File Lessons');
320
326
  summaryLines.push(...fileLessons.slice(0, 5));
321
327
  summaryLines.push('');
322
328
  }
323
- if (keyContext.length > 0) {
329
+ if (keyContext.length > 0 && !quiet) {
324
330
  summaryLines.push('### Key Context');
325
331
  summaryLines.push(...keyContext.slice(0, 5));
326
332
  summaryLines.push('');
327
333
  }
328
- } else if (!latestSummary) {
329
- // Fallback: no summary AND no key observations — show recent activity
334
+ } else if (!latestSummary && !effectiveQuiet()) {
335
+ // Fallback: no summary AND no key observations — show recent activity.
336
+ // Skipped under QUIET_HOOKS since the Recent table already carries titles.
330
337
  const recentObs = (observations.length >= 3 ? observations : fallbackObs).slice(0, 3);
331
338
  if (recentObs.length > 0) {
332
339
  summaryLines.push('### Recent Activity');