claude-mem-lite 6.19.3 → 6.20.0

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.
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "claude-mem-lite",
12
- "version": "6.19.3",
12
+ "version": "6.20.0",
13
13
  "source": "./",
14
14
  "homepage": "https://github.com/sdsrss/claude-mem-lite",
15
15
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "6.19.3",
3
+ "version": "6.20.0",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/README.md CHANGED
@@ -167,9 +167,9 @@ Plugin mode manages its own hooks/runtime. On session start it only **checks and
167
167
 
168
168
  > **The plugin install is complete on its own** — hooks, MCP tools, and the bundled slash commands (`/mem`, `/lesson`, `/bug`, `/adopt`) all run from the plugin with no second step. The slash commands invoke the bundled CLI by an absolute path resolved from the plugin directory (`node "${CLAUDE_PLUGIN_ROOT}/cli.mjs" <cmd>`), so they work without anything on your `PATH`. A global `claude-mem-lite` **shell** command (for running queries yourself in a terminal) is **optional** — `npm i -g claude-mem-lite` — and is a *separate* npm install: the plugin's auto-update does **not** refresh it, so re-run `npm i -g claude-mem-lite@latest` if you want that shell command kept in sync. You do **not** need it for the plugin to be fully functional.
169
169
 
170
- > **Auto-adopt writes into your project, on every SessionStart (v3.13+).** The plugin adds a slug-scoped **managed block** to your project's own **`<cwd>/CLAUDE.md`** — a file that is normally committed to git — plus a `<cwd>/.claude/plugin_claude_mem_lite.md` detail file. The block is a system-authority pointer that boosts Claude's proactive use of `mem_recall` / `mem_save`. Everything outside the block is preserved verbatim, and it coexists with other plugins' blocks in the same file ([details](#invited-memory-v232)). This happens on **every** SessionStart, not just the first: the sync is idempotent and re-applies the block if it is edited away, and refreshes it when the shipped template changes. It applies regardless of install path (npm, npx, `/plugin`, manual), so **no manual `/adopt` is needed**.
170
+ > **Auto-adopt no longer adds its block to your project's `CLAUDE.md` (next release after 6.19.4).** On every SessionStart the plugin delivers its steering text — the trigger table that boosts Claude's proactive use of `mem_recall` / `mem_save`. In a git repository that does not already carry the block it keeps a managed block in `CLAUDE.local.md` at the repository root (Claude Code loads that file like `CLAUDE.md`, subagents included) and adds the file to the repository's `.git/info/exclude` unless your ignore rules already cover it, so git does not list or commit it; you get a **one-time notice** the first time it is written. Claude Code reads instruction files before the plugin's startup hook runs, so the session that creates the file gets the text added to its context instead, once (its subagents do not see it). Outside git, in a repository rooted at `$HOME`, where `CLAUDE.local.md` is tracked or a symbolic link, or where the repository root is an npm package that `npm publish` would ship it with (no `"private": true`, no `files` list leaving it out, and without one no `.npmignore` naming it), nothing is written and the text is **injected** into each session's context, with a one-time notice suggesting `/adopt` (not at `$HOME`). The detail file the block points at lives in the plugin's data directory, named with a `~/` path. `.git/info/exclude` is read by git only: docker build contexts, archives and other packagers can pick `CLAUDE.local.md` up (a publishable npm package root does not keep it: a block written before the directory became a package is taken out at the next session start). Earlier versions added the block to your project's own `<cwd>/CLAUDE.md` (a file that is normally committed) plus a `<cwd>/.claude/plugin_claude_mem_lite.md` detail file, unasked, in every project you opened; those projects keep the block, and the first session of this version refreshes it because the text changed — to move one to the local file, run `claude-mem-lite unadopt` there and commit the removal. Why not inject everywhere: in our sandbox evaluation (one project, Claude Opus 5.5) the agent made 1.5 proactive memory saves per 8-session run with injected steering against 5.25 with the block in `CLAUDE.local.md` or `CLAUDE.md`, and subagents never saw injected text. It applies regardless of install path (npm, npx, `/plugin`, manual); silence both notices with `MEM_NO_ADOPT_HINT=1`.
171
171
  >
172
- > Opt out per project with `claude-mem-lite adopt --disable` (`--enable` to re-arm), globally with `export MEM_NO_AUTO_ADOPT=1`, or freeze an already-adopted block against template refreshes with `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`. `claude-mem-lite unadopt` removes the block and the detail file. Manual `/adopt` remains available for re-applying after edits and for the `--all` batch path.
172
+ > **Want the block in `CLAUDE.md` instead** (for example to share it with your team)? Run `claude-mem-lite adopt`: it writes the slug-scoped managed block into `<cwd>/CLAUDE.md` plus the detail file, preserving everything outside the block, and removes the `CLAUDE.local.md` copy. A project that carries the block is kept in sync on **every SessionStart** — refreshed when the shipped template changes — and gets no local or injected copy on top of it, also in sessions started from a subdirectory. A `CLAUDE.local.md` block the plugin created and you deleted (or `claude-mem-lite unadopt` removed) is not written back — the text is injected instead — until `claude-mem-lite adopt --enable`. Turn steering off per project with `claude-mem-lite adopt --disable` (it also removes the `CLAUDE.local.md` block; a `CLAUDE.md` block stays until `unadopt`; `--enable` re-arms) or globally with `export MEM_NO_AUTO_ADOPT=1` (blocks already written stay and keep loading until `unadopt`); freeze the blocks against template refreshes with `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`. `claude-mem-lite unadopt` removes the `CLAUDE.md` block, the detail file, and the `CLAUDE.local.md` block with its exclude entry.
173
173
 
174
174
  ### Method 2: npx (one-liner)
175
175
 
@@ -543,12 +543,12 @@ automatically on the next SessionStart.
543
543
 
544
544
  ```bash
545
545
  claude-mem-lite adopt # install for current project
546
- claude-mem-lite adopt --all # install for every project under ~/.claude/projects/
546
+ claude-mem-lite adopt --all # legacy clean-up: strip the old memory-dir sentinel in every project
547
547
  claude-mem-lite adopt --status # list adopted/disabled projects + current gating snapshot
548
548
  claude-mem-lite adopt --dry-run # preview without writing
549
549
  claude-mem-lite adopt --disable # opt out of auto-adopt for current project (writes .mem-no-auto-adopt sentinel)
550
550
  claude-mem-lite adopt --enable # re-arm auto-adopt for current project (deletes the sentinel)
551
- claude-mem-lite unadopt # remove sentinel + doc (runtime marker stays to honor the explicit removal)
551
+ claude-mem-lite unadopt # remove the CLAUDE.md block + doc, and the CLAUDE.local.md block (not written again)
552
552
  ```
553
553
 
554
554
  Slash commands `/adopt` and `/unadopt` wrap the same CLI.
@@ -586,18 +586,23 @@ Slash commands `/adopt` and `/unadopt` wrap the same CLI.
586
586
  ever rewritten, and duplicate / CRLF-orphaned copies are collapsed to one.
587
587
  Unlike the legacy `MEMORY.md` scheme there is no line-budget gate — `CLAUDE.md`
588
588
  has no truncation cap.
589
- - **Auto-adopt runs on EVERY SessionStart, for any install path (v2.82.1+;
590
- target moved from the memdir to `<cwd>/CLAUDE.md` in v3.13).** The sync is
591
- idempotent — it re-applies the managed block if it was edited away and
592
- refreshes it when the shipped template changes (freeze with
593
- `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`). Per-project opt-out: `claude-mem-lite adopt --disable`
594
- (writes a durable `<memdir>/.mem-no-auto-adopt` sentinel that survives marker
595
- deletion / plugin reinstalls). Global opt-out: `MEM_NO_AUTO_ADOPT=1`.
596
- Pre-v2.82.1 the `CLAUDE_PLUGIN_ROOT` gate left auto-adopt unreachable for
597
- every `install.mjs`-written hook (the common path) — see CHANGELOG v2.82.1.
589
+ - **Auto-adopt runs on EVERY SessionStart, for any install path, and no longer adds
590
+ the block to `CLAUDE.md` (next release after 6.19.4).** A git project without the
591
+ block gets it in `CLAUDE.local.md` at the git root (kept out of git via
592
+ `.git/info/exclude`), or in SessionStart context outside git; a project that
593
+ carries the `CLAUDE.md` block (explicit `adopt`, or an older version's auto-adopt)
594
+ has it kept in sync — refreshed when the shipped template changes (freeze with
595
+ `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`) — and gets no local or injected copy, also in
596
+ sessions started from a subdirectory. A `CLAUDE.local.md` block the plugin created
597
+ and you (or `unadopt`) removed is not written back; `adopt --enable` re-arms it.
598
+ Per-project opt-out: `claude-mem-lite adopt --disable` (writes a durable
599
+ `<memdir>/.mem-no-auto-adopt` sentinel that survives marker deletion / plugin
600
+ reinstalls; run at the repository root it also covers sessions started in its
601
+ subdirectories; it removes the local block).
602
+ Global opt-out: `MEM_NO_AUTO_ADOPT=1` (blocks already written stay until `unadopt`).
598
603
  - The fallback hook layer is never removed from source — conditional trim is
599
- runtime-gated on sentinel presence, so projects without adoption get the
600
- full verbose output.
604
+ runtime-gated on steering being delivered (block present, or injection on), so
605
+ projects that opted out get the full verbose output.
601
606
 
602
607
  See [the invited-memory design][invited-memory] for the full design (including the
603
608
  reusable template other plugins can follow). It is a development-time document and
@@ -1020,8 +1025,8 @@ claude-mem-lite.
1020
1025
  | `CLAUDE_MEM_BASH_RECALL` | File recall before a Bash command that views (`cat`, `sed -n`, `head`…) or writes (`sed -i`, `cat > f`, a python patch…) a file, like the Read / Edit recall. A bash prefilter keeps Node from starting for other commands. `off` disables this leg only. | _(on)_ |
1021
1026
  | `CLAUDE_MEM_LESSON_GROUNDING` | An auto-captured event keeps its lesson only when the lesson quotes the window's own diagnosis (a failing output line, a comment the edit added, or the commit message); otherwise the row is kept without it at importance 1, below every injection face. `off` keeps unquoted lessons. | _(on)_ |
1022
1027
  | `CLAUDE_MEM_LESSON_OUTPUT_CAP` | An auto-captured lesson that shares four consecutive words with TOOL OUTPUT (a command's printed text or a tool's response, which whoever controls that output can write) is kept off the injection faces: an event keeps its row and lesson, searchable, at importance 1; a `change` observation, whose importance later reads can raise, loses the lesson, and the lesson-less row is then dropped like any other (kept only with `CLAUDE_MEM_KEEP_LOW_SIGNAL=1`). Four shared words of filler ("is not in the") count too, so a lesson quoting your own comment or commit message is demoted when it also happens to share such a run with output in the same window. The row's title is not checked. `off` restores the model's importance and the lesson. | _(on)_ |
1023
- | `MEM_NO_AUTO_ADOPT` | Global opt-out for auto-adopt (v2.82.0+). `1` prevents the per-SessionStart auto-write of the `CLAUDE.md` managed block across **all** projects. For per-project opt-out use `claude-mem-lite adopt --disable` instead (writes a durable `<memdir>/.mem-no-auto-adopt` sentinel that survives marker deletion). | _(disabled)_ |
1024
- | `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. Since v2.82.1 auto-adopt runs on every SessionStart for any install path, so this hint typically surfaces only when you've explicitly opted out (`MEM_NO_AUTO_ADOPT=1` or `claude-mem-lite adopt --disable`). | _(disabled)_ |
1028
+ | `MEM_NO_AUTO_ADOPT` | Global opt-out for auto-adopt (v2.82.0+). `1` stops auto-adopt across **all** projects — no injected steering, no new `CLAUDE.local.md`, and no sync of an existing `CLAUDE.md` or `CLAUDE.local.md` block (those stay, and keep loading, until `claude-mem-lite unadopt`). For per-project opt-out use `claude-mem-lite adopt --disable` instead (writes a durable `<memdir>/.mem-no-auto-adopt` sentinel that survives marker deletion). | _(disabled)_ |
1029
+ | `MEM_NO_ADOPT_HINT` | Silences the one-time notices shown to you the first time a project gets `CLAUDE.local.md` or is steered by injection (the latter suggests `/adopt`, except at `$HOME`), and the one-line "Invited-memory 未启用:`claude-mem-lite adopt`…" hint that SessionStart appends when the current project hasn't been adopted. Since v2.82.1 auto-adopt runs on every SessionStart for any install path, so this hint typically surfaces only when you've explicitly opted out (`MEM_NO_AUTO_ADOPT=1` or `claude-mem-lite adopt --disable`). | _(disabled)_ |
1025
1030
 
1026
1031
  ### What gets injected into your context
1027
1032
 
package/README.zh-CN.md CHANGED
@@ -154,9 +154,9 @@ node install.mjs install
154
154
  1. **安装依赖** -- `npm install --omit=dev`(编译原生 `better-sqlite3`)
155
155
  2. **注册 MCP 服务器** -- `mem-lite` 服务器,包含 18 个工具(9 个核心通过 `tools/list` 暴露 + 9 个隐藏但可调;完整表见 Usage 段)。v2.78 前服务器名为通用的 `mem`,现已改名为 `mem-lite` 避免与用户其它 `.mcp.json` 冲突;工具名(`mem_search`/`mem_recall` 等)保持不变。
156
156
 
157
- > **自动 adopt 会写进你的项目,且每次 SessionStart 都跑(v3.13+)。** 插件向**项目自己的 `<cwd>/CLAUDE.md`**(通常是会进 git 的文件)写入一个 slug 限定的**托管块**,外加 `<cwd>/.claude/plugin_claude_mem_lite.md` 详情文件。该块是一条提升 Claude 主动调用 `mem_recall` / `mem_save` 的 system-authority 指针;块以外的内容逐字保留,也能与其它插件的块共存于同一文件。这是**每次** SessionStart 都做的幂等同步,不只是第一次——块被删掉会重新写回,出货模板变了会刷新。**任何安装路径都生效**(npm、npx、`/plugin`、手动),**无需再手动跑 `/adopt`**。
157
+ > **自动 adopt 不再把托管块加进你项目的 `CLAUDE.md`(6.19.4 之后的下一个版本起)。** 每次 SessionStart,插件都会送达引导文本(提升 Claude 主动调用 `mem_recall` / `mem_save` 的触发表)。在还没有托管块的 git 仓库里,它把托管块写进仓库根目录的 `CLAUDE.local.md`(Claude Code 像加载 `CLAUDE.md` 一样加载它,子代理也能看到),并把这个文件加进仓库的 `.git/info/exclude`(你的 ignore 规则已覆盖时不加),所以 git 不列出、也不提交它;第一次写入时你会看到一条**一次性提示**。Claude Code 在插件的启动钩子运行之前就读取了指令文件,所以创建这个文件的那个会话改为在上下文里注入一次引导(该会话的子代理看不到)。不在 git 仓库里、仓库根是 `$HOME`、`CLAUDE.local.md` 已被 git 跟踪或是符号链接,或者仓库根是一个 `npm publish` 会把它带上的 npm 包(没有 `"private": true`,没有把它排除在外的 `files` 列表,没有 `files` 列表时也没有列出它的 `.npmignore`)时,什么都不写,改为每个会话把引导**注入**上下文,并一次性提示可以运行 `/adopt`(在 `$HOME` 下不提示)。托管块指向的详情文件放在插件自己的数据目录,以 `~/` 路径书写。`.git/info/exclude` 只对 git 生效:docker 构建上下文、归档和其他打包工具都可能把 `CLAUDE.local.md` 打进去(可发布的 npm 包根目录不会保留这个文件:目录变成 npm 包之前写入的托管块,会在下一次会话启动时移除)。旧版本会在你打开的每个项目里,未经询问就向项目自己的 `<cwd>/CLAUDE.md`(通常会进 git)写入托管块,外加 `<cwd>/.claude/plugin_claude_mem_lite.md` 详情文件;这些项目保留托管块,本版本的第一个会话会因为文本变化刷新它——想把某个项目迁到本地文件,在那里运行 `claude-mem-lite unadopt` 并提交这次删除。为什么不全部改成注入:沙箱实测中(一个项目,Claude Opus 5.5),8 个会话里模型主动记录的次数,注入时平均 1.5 次,写在 `CLAUDE.local.md` 或 `CLAUDE.md` 时 5.25 次;而且子代理完全看不到注入的文本。**任何安装路径都生效**(npm、npx、`/plugin`、手动);两条提示都可以用 `MEM_NO_ADOPT_HINT=1` 关闭。
158
158
  >
159
- > 关闭方式:项目级 `claude-mem-lite adopt --disable`(重新启用用 `--enable`);全局 `export MEM_NO_AUTO_ADOPT=1`;只冻结模板刷新用 `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`。`claude-mem-lite unadopt` 可移除托管块与详情文件。手动 `/adopt` 仍保留用于编辑后重写或 `--all` 批量场景。
159
+ > **想把托管块改写进 `CLAUDE.md`**(例如与团队共享)?运行 `claude-mem-lite adopt`:它向 `<cwd>/CLAUDE.md` 写入 slug 限定的托管块和详情文件,块以外的内容逐字保留,并删除 `CLAUDE.local.md` 里的那份。带托管块的项目在**每次 SessionStart** 都会同步,出货模板变了会刷新,且不会再叠加本地副本或注入,从子目录启动的会话也一样。插件创建过、又被你删掉(或被 `claude-mem-lite unadopt` 删掉)的 `CLAUDE.local.md` 托管块不会被写回,改为注入,直到运行 `claude-mem-lite adopt --enable`。关闭方式:项目级 `claude-mem-lite adopt --disable`(同时删除 `CLAUDE.local.md` 托管块;`CLAUDE.md` 托管块保留到 `unadopt`;重新启用用 `--enable`);全局 `export MEM_NO_AUTO_ADOPT=1`(已写入的托管块保留并继续加载,直到 `unadopt`);只冻结模板刷新用 `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`。`claude-mem-lite unadopt` 会移除 `CLAUDE.md` 托管块、详情文件,以及 `CLAUDE.local.md` 托管块和它的 exclude 条目。
160
160
  3. **配置钩子** -- 全部七个生命周期事件:`SessionStart`、`PreCompact`、`PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`Stop`、`UserPromptSubmit`
161
161
  4. **创建数据目录** -- `~/.claude-mem-lite/`(隐藏目录),存放数据库与运行时文件
162
162
  5. **自动迁移** -- 自动检测 `~/.claude-mem/`(原版 claude-mem)或 `~/claude-mem-lite/`(v0.5 前的非隐藏目录),将数据库和运行时文件迁移到 `~/.claude-mem-lite/`,原目录保持不变
@@ -451,12 +451,12 @@ instruction-following 权威。
451
451
 
452
452
  ```bash
453
453
  claude-mem-lite adopt # 当前项目注入
454
- claude-mem-lite adopt --all # 扫描 ~/.claude/projects/* 全部注入
454
+ claude-mem-lite adopt --all # 旧方案清理:在所有项目里删除旧的 memory 目录哨兵
455
455
  claude-mem-lite adopt --status # 列出已 adopt / 已禁用项目 + 当前 gate 快照
456
456
  claude-mem-lite adopt --dry-run # 只打印不写入
457
457
  claude-mem-lite adopt --disable # 当前项目关闭 auto-adopt(写 .mem-no-auto-adopt 哨兵)
458
458
  claude-mem-lite adopt --enable # 当前项目重新启用 auto-adopt(删哨兵)
459
- claude-mem-lite unadopt # 精确移除 sentinel + 详情文档(runtime marker 保留以尊重显式撤销)
459
+ claude-mem-lite unadopt # 移除 CLAUDE.md 托管块和详情文档,以及 CLAUDE.local.md 托管块(之后不再写回)
460
460
  ```
461
461
 
462
462
  Slash 命令 `/adopt` 和 `/unadopt` 是上述 CLI 的包装。
@@ -482,15 +482,17 @@ Slash 命令 `/adopt` 和 `/unadopt` 是上述 CLI 的包装。
482
482
  - Hash 守护:你手动改了 sentinel 段 → 下一次 adopt 报 `UserEditedError`,
483
483
  除非显式 `--force`。
484
484
  - 预算门:MEMORY.md 已 >180 行时拒绝新增(避开 Claude Code 200 行截断)。
485
- - **任何安装路径每次 SessionStart 都自动 adopt(v2.82.1+;v3.13 起写入目标由
486
- memdir 改为 `<cwd>/CLAUDE.md`)。** 同步是幂等的——托管块被删会写回,出货模板
487
- 变化会刷新(用 `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1` 冻结)。项目级关闭:
488
- `claude-mem-lite adopt --disable`(写 `<memdir>/.mem-no-auto-adopt` 哨兵,
489
- 存活于 marker 删除 / 插件重装)。全局关闭:`export MEM_NO_AUTO_ADOPT=1`。
490
- v2.82.1 前因 `CLAUDE_PLUGIN_ROOT` gate 与 `install.mjs` 写出的 hook 命令
491
- 不匹配,auto-adopt 实质 5 周零触发——见 CHANGELOG v2.82.1。
492
- - 保守 hook 层源码永不删——条件瘦身仅基于 sentinel 存在性做 runtime 判断,
493
- 未 adopt 的项目仍看完整 verbose 输出。
485
+ - **任何安装路径每次 SessionStart 都自动 adopt,且不再把托管块加进 `CLAUDE.md`(6.19.4
486
+ 之后的下一个版本起)。** 没有托管块的 git 项目,引导写进 git 根目录的 `CLAUDE.local.md`
487
+ (经 `.git/info/exclude` 排除在 git 之外),不在 git 里时注入 SessionStart 上下文;带
488
+ `CLAUDE.md` 托管块的项目(显式 adopt 或旧版本写入)保持同步,出货模板变化会刷新(用
489
+ `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1` 冻结),且不会再叠加本地副本或注入,从子目录启动的会话
490
+ 也一样。插件创建过、又被你(或 `unadopt`)删掉的 `CLAUDE.local.md` 托管块不会被写回;
491
+ `adopt --enable` 可重新启用。项目级关闭:`claude-mem-lite adopt --disable`(写
492
+ `<memdir>/.mem-no-auto-adopt` 哨兵,存活于 marker 删除 / 插件重装;在仓库根目录运行时,对从子目录启动的
493
+ 会话同样生效;并删除本地托管块)。全局关闭:`export MEM_NO_AUTO_ADOPT=1`(已写入的托管块保留,直到 `unadopt`)。
494
+ - 保守 hook 层源码永不删——条件瘦身按"引导已送达"(有托管块或注入开启)做 runtime
495
+ 判断,关闭了引导的项目仍看完整 verbose 输出。
494
496
 
495
497
  完整设计见 `docs/plans/2026-04-16-invited-memory-pattern.md`(含其它插件
496
498
  可复用的模板)。
@@ -786,8 +788,8 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
786
788
  | `CLAUDE_MEM_BASH_RECALL` | 在查看(`cat`、`sed -n`、`head`…)或写入(`sed -i`、`cat > f`、python 补丁…)文件的 Bash 命令执行前做文件召回,与 Read / Edit 召回相同。bash 预过滤让其他命令不启动 Node。设为 `off` 只关闭这一路。 | _(开启)_ |
787
789
  | `CLAUDE_MEM_LESSON_GROUNDING` | 自动捕获的 event 只有在教训引用了本窗口自己的诊断文字(失败输出行、编辑新增的注释或提交信息)时才保留教训;否则保留这一行但去掉教训,importance 降为 1,低于所有注入面的门槛。设为 `off` 保留未引用原文的教训。 | _(开启)_ |
788
790
  | `CLAUDE_MEM_LESSON_OUTPUT_CAP` | 自动捕获的教训如果和**工具输出**(命令打印的文字或工具返回的内容,能控制这段输出的人就能写它)有连续 4 个词相同,就不会进入注入面:event 保留这一行和教训、仍可搜索,importance 降为 1;`change` 类 observation 的 importance 之后会被读取次数抬高,所以改为去掉教训,没有教训的这一行随后会像其他同类行一样被丢弃(只有设了 `CLAUDE_MEM_KEEP_LOW_SIGNAL=1` 才保留)。连续 4 个虚词(例如 "is not in the")也算,所以引用你自己的注释或提交信息的教训,只要碰巧和同一窗口的输出共有这样一串词,也会被降级。这一行的标题不在检查范围内。设为 `off` 恢复模型给出的 importance 和教训。 | _(开启)_ |
789
- | `MEM_NO_AUTO_ADOPT` | auto-adopt 全局关闭开关(v2.82.0+)。设为 `1` 阻止每次 SessionStart 在**所有**项目自动写入 `CLAUDE.md` 托管块。项目级关闭走 `claude-mem-lite adopt --disable`(写 `<memdir>/.mem-no-auto-adopt` 哨兵,存活于 marker 删除)。 | _(禁用)_ |
790
- | `MEM_NO_ADOPT_HINT` | 静音当前项目未 adopt 时 SessionStart 追加的那一行 "Invited-memory 未启用…" 提示。v2.82.1 起任何安装路径每次 SessionStart 都自动 adopt,所以该提示一般只在你显式 opt out(`MEM_NO_AUTO_ADOPT=1` 或 `claude-mem-lite adopt --disable`)的项目才会出现。 | _(禁用)_ |
791
+ | `MEM_NO_AUTO_ADOPT` | auto-adopt 全局关闭开关(v2.82.0+)。设为 `1` 在**所有**项目停止自动 adopt——不注入引导文本、不新建 `CLAUDE.local.md`,也不同步已有的 `CLAUDE.md` 或 `CLAUDE.local.md` 托管块(这些托管块会保留并继续加载,直到 `claude-mem-lite unadopt`)。项目级关闭走 `claude-mem-lite adopt --disable`(写 `<memdir>/.mem-no-auto-adopt` 哨兵,存活于 marker 删除)。 | _(禁用)_ |
792
+ | `MEM_NO_ADOPT_HINT` | 静音项目第一次得到 `CLAUDE.local.md`、或第一次以注入方式被引导时给你的一次性提示(后者建议运行 `/adopt`,在 `$HOME` 下除外),以及当前项目未 adopt 时 SessionStart 追加的那一行 "Invited-memory 未启用…" 提示。v2.82.1 起任何安装路径每次 SessionStart 都自动 adopt,所以该提示一般只在你显式 opt out(`MEM_NO_AUTO_ADOPT=1` 或 `claude-mem-lite adopt --disable`)的项目才会出现。 | _(禁用)_ |
791
793
 
792
794
  ## 许可证
793
795
 
package/adopt-cli.mjs CHANGED
@@ -14,10 +14,14 @@
14
14
  // every memdir). New-scheme adoption happens per-project on SessionStart (cwd known).
15
15
 
16
16
  import { existsSync, readdirSync, statSync, mkdirSync, writeFileSync, unlinkSync, readFileSync } from 'fs';
17
- import { homedir } from 'os';
18
- import { join, isAbsolute } from 'path';
17
+ import { claudeConfigDir, claudeStatePath } from './lib/data-paths.mjs';
18
+ import { join, isAbsolute, resolve } from 'path';
19
19
  import {
20
20
  memdirPath,
21
+ disableSentinelPath,
22
+ isAutoAdoptDisabled,
23
+ isAutoAdoptDisabledFor,
24
+ legacyMemdirPath,
21
25
  removePluginSection,
22
26
  removePluginDoc,
23
27
  isAdopted as memdirIsAdopted,
@@ -29,12 +33,36 @@ import {
29
33
  isAdopted as claudeMdIsAdopted,
30
34
  hasResidue as claudeMdHasResidue,
31
35
  needsRefresh,
36
+ readBlock,
32
37
  migrateLegacyMemoryDir,
33
38
  hasLegacyMemdirSentinel,
34
39
  claudeMdPath,
35
40
  detailDocPath,
36
41
  } from './claudemd.mjs';
37
42
  import { PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, buildClaudeMdBlock, getDetailDoc } from './adopt-content.mjs';
43
+ import {
44
+ localSteeringRoot,
45
+ readLocalSteering,
46
+ writeLocalSteering,
47
+ removeLocalSteering,
48
+ ensureSteeringDetailDoc,
49
+ localMdPath,
50
+ forgetLocalSteering,
51
+ tildePath,
52
+ } from './lib/local-steering.mjs';
53
+
54
+ /**
55
+ * Remove the auto-written CLAUDE.local.md block for the project at `cwd`, if there is one.
56
+ * @returns {{action: 'removed'|'partial'|'absent', residue?: string, path?: string}}
57
+ */
58
+ function dropLocalSteering(cwd) {
59
+ const root = localSteeringRoot(cwd);
60
+ if (!root) return { action: 'absent' };
61
+ // Runs even when the block is already gone (deleted by hand): removeLocalSteering then drops
62
+ // the exclude lines it added (pre-tag defect review, mutation M7).
63
+ const r = removeLocalSteering(root, PLUGIN_SLUG);
64
+ return r.action === 'absent' ? { action: 'absent' } : { ...r, path: localMdPath(root) };
65
+ }
38
66
 
39
67
  function log(msg) {
40
68
  console.log(msg);
@@ -45,7 +73,7 @@ function detectCwd() {
45
73
  }
46
74
 
47
75
  function projectsRoot() {
48
- return join(homedir(), '.claude', 'projects');
76
+ return join(claudeConfigDir(), 'projects');
49
77
  }
50
78
 
51
79
  function listAllMemdirs() {
@@ -66,7 +94,7 @@ function listAllMemdirs() {
66
94
  }
67
95
 
68
96
  function claudeConfigPath() {
69
- return join(homedir(), '.claude.json');
97
+ return claudeStatePath();
70
98
  }
71
99
 
72
100
  // Real adopted-project paths come from Claude Code's own ~/.claude.json `projects`
@@ -93,23 +121,9 @@ function hasFlag(args, flag) {
93
121
  }
94
122
 
95
123
  // ─── Per-project auto-adopt opt-out sentinel ─────────────────────────────────
96
- // `<memdir>/.mem-no-auto-adopt` is the durable, project-scoped escape hatch.
97
- // Survives marker deletion, sentinel removal, and plugin reinstalls — that's
98
- // the point: "user said no for this project" should not be reversible by
99
- // `rm ~/.claude-mem-lite/runtime/.auto-adopt-*`. Managed via
100
- // `claude-mem-lite adopt --disable` / `--enable`. silentAutoAdopt checks it
101
- // at entry and skips WITHOUT writing the runtime marker, so toggling
102
- // `--enable` re-arms auto-adopt on the next SessionStart. Kept in the memdir
103
- // (not the project tree) so it survives `unadopt` cleaning out .claude/.
104
- const DISABLE_SENTINEL_BASENAME = '.mem-no-auto-adopt';
105
-
106
- export function disableSentinelPath(memdir) {
107
- return join(memdir, DISABLE_SENTINEL_BASENAME);
108
- }
109
-
110
- export function isAutoAdoptDisabled(memdir) {
111
- return existsSync(disableSentinelPath(memdir));
112
- }
124
+ // The `.mem-no-auto-adopt` escape hatch lives in memdir.mjs since report §9-A: lib/quiet-scope.mjs
125
+ // has to ask it too (injected steering counts as adopted), and lib/ may not import this face.
126
+ export { disableSentinelPath, isAutoAdoptDisabled };
113
127
 
114
128
  /**
115
129
  * cmdAdopt — write the CLAUDE.md managed block + detail doc for the current
@@ -151,7 +165,15 @@ function adoptOne(cwd, { force, dryRun }) {
151
165
  const mig = migrateLegacyMemoryDir(cwd, PLUGIN_SLUG, { force });
152
166
  const r = writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
153
167
  const migNote = mig.action === 'removed' ? ' (+migrated legacy memdir)' : '';
154
- log(`[adopt] ${cwd} → ${r.action}${migNote}`);
168
+ // CLAUDE.md now carries the block; a CLAUDE.local.md copy would load it twice.
169
+ const local = dropLocalSteering(cwd);
170
+ const localNote =
171
+ local.action === 'absent'
172
+ ? ''
173
+ : local.action === 'skipped-symlink'
174
+ ? ` (left ${local.path} alone: it is a symlink)`
175
+ : ` (+removed the block from ${local.path})`;
176
+ log(`[adopt] ${cwd} → ${r.action}${migNote}${localNote}`);
155
177
  return r;
156
178
  } catch (e) {
157
179
  log(`[adopt] ${cwd} → error: ${e.message}`);
@@ -215,14 +237,19 @@ function migrateAll(args) {
215
237
  * existing users whose marker predates v3.13 still migrate). Order:
216
238
  * 1. respect per-project `.mem-no-auto-adopt` opt-out → skip.
217
239
  * 2. migrate legacy memory-dir sentinel away (idempotent; no-op once gone).
218
- * 3. adopt the CLAUDE.md scheme if absent; else refresh if shipped content
219
- * drifted (unless CLAUDE_MEM_NO_TEMPLATE_REFRESH=1).
240
+ * 3. a managed block in CLAUDE.md → keep it in sync, refreshing if shipped content drifted
241
+ * (unless CLAUDE_MEM_NO_TEMPLATE_REFRESH=1), and drop a local copy (no double steering).
242
+ * 4. otherwise, inside a git work tree → the block in <top-level>/CLAUDE.local.md, kept
243
+ * out of commits via info/exclude; return 'local' (`written` says what changed). In a
244
+ * subdirectory, a root CLAUDE.md block → 'already-adopted', a root opt-out → 'disabled'.
245
+ * 5. otherwise (no git, $HOME, a tracked or symlinked CLAUDE.local.md, an npm-publishable
246
+ * root, any git failure) → write nothing, return 'inject' (the caller puts the steering
247
+ * into SessionStart context) — or 'already-adopted' when that file carries the block.
220
248
  * Silent: never logs, never throws. Returns { ok, action, reason } for debugLog.
221
249
  */
222
250
  export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
223
- const memdir = memdirPath(cwd);
224
251
  try {
225
- if (isAutoAdoptDisabled(memdir)) {
252
+ if (isAutoAdoptDisabledFor(cwd)) {
226
253
  return { ok: true, action: 'disabled', reason: 'disabled-by-sentinel' };
227
254
  }
228
255
  migrateLegacyMemoryDir(cwd, PLUGIN_SLUG);
@@ -231,6 +258,50 @@ export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
231
258
  const doc = getDetailDoc();
232
259
  const version = CURRENT_SENTINEL_VERSION;
233
260
 
261
+ // Report §9-A (docs/audits/20260929-sandbox-usage-eval.md): a project with NO managed
262
+ // block is no longer written into. The first SessionStart used to add CLAUDE.md and
263
+ // .claude/plugin_claude_mem_lite.md to every repository the user opened — 4 of 4 sandbox
264
+ // repos, swept into the next `git add -A` — and the startup dashboard then reported them
265
+ // as the user's uncommitted work. The same text now rides SessionStart context
266
+ // ('inject'); only an explicit `adopt` writes files. A project that already carries the
267
+ // block (adopted explicitly, or by an older version) is kept in sync exactly as before,
268
+ // including a half state whose detail doc went missing.
269
+ //
270
+ // r3 (tasks/specs/sandbox-eval-l3.md, report §8.5): injection kept the repository clean but
271
+ // cost most of the proactive memory writes (1.5 vs 5.25 per trajectory) and never reached
272
+ // subagents (0/12). Inside a git work tree the block now goes to CLAUDE.local.md, which the
273
+ // host loads like CLAUDE.md and info/exclude keeps out of commits (5.25 writes, 12/12).
274
+ const hasBlock = readBlock(cwd, PLUGIN_SLUG).body !== null;
275
+ if (!hasBlock) {
276
+ if (markerDir && markerKey) writeMarker(markerDir, markerKey);
277
+ const root = localSteeringRoot(cwd);
278
+ if (root) {
279
+ // A session started below the top-level (pre-tag claims review P2-3, P1-4): the host
280
+ // loads the root's CLAUDE.md as an ancestor, so a block there already steers this
281
+ // session; and an opt-out recorded for the root covers the file that lives there.
282
+ if (resolve(root) !== resolve(cwd)) {
283
+ if (readBlock(root, PLUGIN_SLUG).body !== null)
284
+ return { ok: true, action: 'already-adopted', reason: 'root-claude-md' };
285
+ // Off for the project means off here too: no file, no injected copy, no /adopt offer
286
+ // (delta review P2-2; lib/quiet-scope.mjs mirrors it).
287
+ if (isAutoAdoptDisabledFor(root)) return { ok: true, action: 'disabled', reason: 'root-disabled' };
288
+ }
289
+ const localBlock = buildClaudeMdBlock({ detailDocRef: tildePath(ensureSteeringDetailDoc()) });
290
+ const r = writeLocalSteering(root, {
291
+ slug: PLUGIN_SLUG,
292
+ version,
293
+ block: localBlock,
294
+ frozen: process.env.CLAUDE_MEM_NO_TEMPLATE_REFRESH === '1',
295
+ });
296
+ if (r.action !== 'refused') return { ok: true, action: 'local', written: r.action };
297
+ // A refused file that carries the block anyway (tracked, or behind a link) is loaded by
298
+ // the host: injecting it too would load it twice.
299
+ if (r.present) return { ok: true, action: 'already-adopted', reason: `local-${r.reason}` };
300
+ return { ok: true, action: 'inject', reason: `local-${r.reason}` };
301
+ }
302
+ return { ok: true, action: 'inject' };
303
+ }
304
+ dropLocalSteering(cwd);
234
305
  let action = 'already-adopted';
235
306
  if (!claudeMdIsAdopted(cwd, PLUGIN_SLUG)) {
236
307
  writeManaged(cwd, { slug: PLUGIN_SLUG, version, block, doc });
@@ -267,11 +338,29 @@ export function hasAutoAdoptMarker(markerDir, markerKey) {
267
338
  /**
268
339
  * cmdDisable — `claude-mem-lite adopt --disable [--all]`.
269
340
  * Writes `<memdir>/.mem-no-auto-adopt` so SessionStart auto-adopt skips this
270
- * project permanently. Does NOT remove an existing block — pair with `unadopt`.
341
+ * project permanently. Does NOT remove a CLAUDE.md block (the user asked for that one, by
342
+ * running adopt) — pair with `unadopt`. DOES remove the CLAUDE.local.md block, which
343
+ * auto-adopt wrote on its own: "turn the guidance off here" has to mean it stops loading.
271
344
  */
272
345
  function cmdDisable(args) {
273
346
  const all = hasFlag(args, '--all');
274
- const targets = all ? listAllMemdirs().map((m) => m.memdir) : [memdirPath(detectCwd())];
347
+ const localTargets = all ? listKnownProjectDirs() : [detectCwd()];
348
+ for (const dir of localTargets) {
349
+ const r = dropLocalSteering(dir);
350
+ if (r.action !== 'absent') log(`[adopt --disable] ${r.path} → ${r.action}`);
351
+ if (r.residue) log(` ⚠ ${r.residue}`);
352
+ }
353
+ // Known projects too, not only memdirs that already exist: Claude Code creates `memory/`
354
+ // only when its auto-memory is used, and a project without one was left armed (pre-tag
355
+ // defect review P2-4).
356
+ const targets = all
357
+ ? [
358
+ ...new Set([
359
+ ...listAllMemdirs().map((m) => m.memdir),
360
+ ...listKnownProjectDirs().map((d) => memdirPath(d)),
361
+ ]),
362
+ ]
363
+ : [memdirPath(detectCwd())];
275
364
 
276
365
  if (targets.length === 0) {
277
366
  log('[adopt --disable] no memdirs found');
@@ -304,7 +393,19 @@ function cmdDisable(args) {
304
393
  */
305
394
  function cmdEnable(args) {
306
395
  const all = hasFlag(args, '--all');
307
- const targets = all ? listAllMemdirs().map((m) => m.memdir) : [memdirPath(detectCwd())];
396
+ // Re-arm the CLAUDE.local.md block too: a block the user or `unadopt` removed is not written
397
+ // back until this forgets that it was (lib/local-steering.mjs).
398
+ for (const dir of all ? listKnownProjectDirs() : [detectCwd()]) {
399
+ const root = localSteeringRoot(dir);
400
+ if (root && forgetLocalSteering(root))
401
+ log(`[adopt --enable] ${localMdPath(root)} → will be written again`);
402
+ }
403
+ // The legacy ~/.claude memdir too: isAutoAdoptDisabledFor still honours a sentinel an earlier
404
+ // version left there, so --enable must be able to remove it.
405
+ const cwdNow = detectCwd();
406
+ const targets = all
407
+ ? listAllMemdirs().map((m) => m.memdir)
408
+ : [memdirPath(cwdNow), legacyMemdirPath(cwdNow)].filter(Boolean);
308
409
 
309
410
  if (targets.length === 0) {
310
411
  log('[adopt --enable] no memdirs found');
@@ -342,6 +443,11 @@ function statusAll() {
342
443
  log('[adopt --status] current project:');
343
444
  log(` cwd: ${cwd}`);
344
445
  log(` CLAUDE.md: ${adoptedHere ? `✓ adopted (${CURRENT_SENTINEL_VERSION})` : '✗ not adopted'}`);
446
+ const localRoot = localSteeringRoot(cwd);
447
+ const localHere = localRoot && readLocalSteering(localRoot, PLUGIN_SLUG).body !== null;
448
+ log(
449
+ ` local: ${localHere ? `✓ ${localMdPath(localRoot)} (auto-written, excluded from git)` : localRoot ? '✗ none' : '— none here: not a git work tree, or its root is $HOME or / (steering is injected at session start)'}`,
450
+ );
345
451
  if (hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG)) {
346
452
  log(' legacy: ⚠ memory-dir sentinel still present (migrates on next SessionStart, or run `adopt`)');
347
453
  }
@@ -404,8 +510,21 @@ function unadoptAll(args) {
404
510
  // 1. New scheme: scrub CLAUDE.md managed blocks across known project paths.
405
511
  const projectDirs = listKnownProjectDirs();
406
512
  let blocks = 0,
407
- partial = 0;
513
+ partial = 0,
514
+ locals = 0;
408
515
  for (const dir of projectDirs) {
516
+ // The CLAUDE.local.md block auto-adopt writes (r3) is swept first and independently: a
517
+ // project carries one or the other, and either way nothing of ours should survive.
518
+ const root = localSteeringRoot(dir);
519
+ if (root && readLocalSteering(root, PLUGIN_SLUG).body !== null) {
520
+ if (dryRun) log(`[unadopt --all --dry-run] ${localMdPath(root)} → would-remove`);
521
+ else {
522
+ const lr = removeLocalSteering(root, PLUGIN_SLUG);
523
+ log(`[unadopt --all] ${localMdPath(root)} → ${lr.action}`);
524
+ if (lr.residue) log(` ⚠ ${lr.residue}`);
525
+ }
526
+ locals++;
527
+ }
409
528
  // hasResidue, not isAdopted: the sweep must also catch PARTIAL residue
410
529
  // (block without detail doc, or an orphaned doc/state sidecar) —
411
530
  // isAdopted's block-AND-doc gate skipped those projects forever.
@@ -452,7 +571,7 @@ function unadoptAll(args) {
452
571
  log('');
453
572
  const partialNote = partial > 0 ? ` (+${partial} partial-residue cleanup(s))` : '';
454
573
  log(
455
- `[unadopt --all] ${dryRun ? 'would remove' : 'removed'} ${blocks} CLAUDE.md block(s)${partialNote} across ${projectDirs.length} known project(s); ${legacy} legacy memory-dir sentinel(s) ${dryRun ? 'pending' : 'cleaned'}.`,
574
+ `[unadopt --all] ${dryRun ? 'would remove' : 'removed'} ${blocks} CLAUDE.md block(s)${partialNote} and ${locals} CLAUDE.local.md block(s) across ${projectDirs.length} known project(s); ${legacy} legacy memory-dir sentinel(s) ${dryRun ? 'pending' : 'cleaned'}.`,
456
575
  );
457
576
  if (projectDirs.length === 0) {
458
577
  log(
@@ -472,6 +591,9 @@ export function cmdUnadopt(args = []) {
472
591
 
473
592
  const cwd = detectCwd();
474
593
  if (dryRun) {
594
+ const root = localSteeringRoot(cwd);
595
+ if (root && readLocalSteering(root, PLUGIN_SLUG).body !== null)
596
+ log(`[unadopt --dry-run] would-remove the block in ${localMdPath(root)}`);
475
597
  const blockState = claudeMdHasResidue(cwd, PLUGIN_SLUG)
476
598
  ? 'would-remove CLAUDE.md block + detail doc'
477
599
  : 'no CLAUDE.md block';
@@ -488,6 +610,9 @@ export function cmdUnadopt(args = []) {
488
610
  const mig = migrateLegacyMemoryDir(cwd, PLUGIN_SLUG, { force });
489
611
  const migNote = mig.action === 'removed' ? ' (+cleaned legacy memdir)' : '';
490
612
  log(`[unadopt] ${cwd} → ${r.action}${migNote}`);
613
+ const local = dropLocalSteering(cwd);
614
+ if (local.action !== 'absent') log(`[unadopt] ${local.path} → ${local.action}`);
615
+ if (local.residue) log(` ⚠ ${local.residue}`);
491
616
  // 'partial' is the outcome that used to print as 'absent': the sidecar files are gone but
492
617
  // an unpaired sentinel still holds steering text in the user's CLAUDE.md, and only they can
493
618
  // decide where that text ends. Silence here is what let it survive every sweep.
package/adopt-content.mjs CHANGED
@@ -39,7 +39,7 @@ const CLI = 'claude-mem-lite';
39
39
  * per-project-type variation. Keep it tight (cheap always-loaded context); the
40
40
  * full tables + rules live in the detail doc this block points to.
41
41
  */
42
- export function buildClaudeMdBlock() {
42
+ export function buildClaudeMdBlock({ detailDocRef = '.claude/plugin_claude_mem_lite.md' } = {}) {
43
43
  // Intentionally machine-stable: MCP tool names only, NO CLI_INVOKE (that
44
44
  // resolves to an absolute path that differs per install — it would make this
45
45
  // committed/refreshed block churn across machines). The detail doc holds the
@@ -50,15 +50,16 @@ PreToolUse hooks already run \`mem_recall\` for past lessons before Read/Edit/Wr
50
50
 
51
51
  | When | Call |
52
52
  |------|------|
53
- | Before Edit/Write | hook already recalled; if an injected \`#NN\` lesson changed what you did, name \`#NN\` once where you say so (citing = adopting; uncited lessons decay; skip ones that did not apply) |
54
- | After fixing a non-trivial bug | \`mem_save(type="bugfix", lesson_learned="<root cause + fix>", importance=2)\` |
53
+ | Before Edit/Write | hook already recalled; if an injected \`#NN\` lesson changed what you did, add the bare tag \`(#NN)\` once at the end of the sentence describing that change (citing = adopting; uncited lessons decay; skip ones that did not apply). No other mention of memory ids, saves or the memory store in replies to the user |
54
+ | A recalled memory drives an answer or a design choice | check its claim in the code or \`git log\` first: \`#NN\` and \`E#NN\` rows are notes from past sessions, many written automatically, so they can be wrong, and they describe the code as it was. If the code disagrees, trust the code and replace the note: \`mem_save(..., supersedes=[NN])\`, or \`supersedes=["E#NN"]\` for an event |
55
+ | After fixing a non-trivial bug | \`mem_save(type="bugfix", lesson_learned="<root cause + fix, only what this change's diff shows>", importance=2)\` |
55
56
  | After a non-obvious architecture decision | \`mem_save(type="decision", lesson_learned="<constraint + tradeoff>")\` |
56
57
  | Deferring to a future session | \`mem_defer({title, priority:1|2|3, detail})\`; when fixed, add \`closes_deferred=[N]\` to \`mem_save\` |
57
58
  | Looking up past work / history | \`mem_search "keywords"\` · \`mem_recent\` · \`mem_timeline\` |
58
59
 
59
60
  Path cost is round-trips, not milliseconds: the PreToolUse hook above already recalls (0 calls) — prefer it. For an explicit query, if these \`mem_*\` tools are deferred behind ToolSearch this session, the Bash CLI \`${CLI}\` is one call vs two (ToolSearch + call); the MCP server instructions carry the absolute path to use when it is not on PATH.
60
61
 
61
- Full tool + CLI tables, citation/decay rules, and save discipline → \`.claude/plugin_claude_mem_lite.md\``;
62
+ Full tool + CLI tables, citation/decay rules, and save discipline → \`${detailDocRef}\``;
62
63
  }
63
64
 
64
65
  /**
@@ -71,7 +72,7 @@ export function getDetailDoc() {
71
72
  return `# claude-mem-lite 插件契约(完整)
72
73
 
73
74
  > 由 \`${CLI} adopt\` 生成、随版本自动刷新;卸载用 \`${CLI} unadopt\`。
74
- > 精炼触发表在项目 \`CLAUDE.md\` 的 \`claude-mem-lite\` 托管块里;本文件是其展开。
75
+ > 精炼触发表由 SessionStart 注入会话上下文(显式 \`${CLI} adopt\` 过的项目则写在 \`CLAUDE.md\` 的 \`claude-mem-lite\` 托管块里);本文件是其展开。
75
76
  > 设计背景见 docs/CLAUDE-MD-STEERING-PLAN.md。
76
77
 
77
78
  > **本文下方所有命令写作 \`${CLI} <cmd>\`。** 该名字只在全局装过
@@ -88,13 +89,25 @@ PreToolUse hook 在你 Read / Edit / Write 文件前已自动 \`mem_recall\` 该
88
89
  lesson 也注入。
89
90
  - Read→Edit 同文件共享 cooldown(不重复注入正文),但 Read 注入后的首个 Edit 会把 lesson **ID**
90
91
  以一行 ack 指令重新浮出。看到 \`#NN [bugfix] …\` 这类行时:**某条 lesson 改变了你的做法,就在描述
91
- 那处改动时顺带提一次 \`#NN\`**;没用上的 lesson 不必提,也不要逐条列出。纯工具回合不算;
92
+ 那处改动的句子末尾加一个裸标签 \`(#NN)\`**;没用上的 lesson 不必提,也不要逐条列出。纯工具回合不算;
92
93
  把 ID 记在工作记忆里,写回时引用。
94
+ - 给用户的回复里,除了这个 \`(#NN)\` 标签,不要再提记忆编号:不要报告保存、延期得到的编号
95
+ (如"已记进项目记忆,编号 #1"),也不要讨论记忆库本身(如"和记忆库里 #1 的记录一致")。
93
96
  - 系统按会话追踪引用:被引用的 lesson 在召回排序里上浮,被注入却未引用的下沉(有界的排序乘数);
94
97
  反复注入却从未被引用的,后台维护会把它的 importance 降到 2(无 lesson 的降到 1)。
95
98
  写成 \`#NN n/a\` 的驳回不算采纳:排序上与未引用相同,同样下沉——所以不必写。
96
99
  引用是给系统的反馈,不是合规仪式——注入池据此自调。
97
100
 
101
+ ## 记忆是旧笔记,不是现在的代码
102
+
103
+ - \`E#NN\` 是后台根据会话自动写的事件摘要,可能写错:2026-09 的沙箱实测里 43 条中有 6 条事实错误、13 条部分错误。
104
+ \`#NN\` 可能是 agent 主动保存的笔记,\`#NN\` 也可能是后台自动整理的会话摘要;主动保存的也可能说得超出当时那次改动的实际范围。两者描述的都是保存那一刻的代码。
105
+ - 用一条记忆回答"之前做了什么、为什么",或据此做设计决定之前,先在代码或 \`git log -S\` / \`git show\` 里核对它的具体说法。
106
+ - 代码与记忆矛盾时,以代码为准,回复里按代码说;然后用
107
+ \`mem_save(type=<原类型>, title=..., lesson_learned="<按代码更正后的说法>", supersedes=[NN])\`
108
+ 替换那条记忆(事件写 \`supersedes=["E#NN"]\`),被替换的记录不再被召回。只更正你在代码里亲眼核实过的那一点;拿不准就不写。
109
+ - 保存教训时只写这次 diff 能证明的内容:修了什么、为什么这样修;之后才做的或打算做的,不写进去。
110
+
98
111
  ## 何时主动调用 MCP 工具
99
112
 
100
113
  \`tools/list\` 默认暴露 6 个核心工具 + 3 个 defer 工具:
@@ -172,7 +185,7 @@ PreToolUse hook 在你 Read / Edit / Write 文件前已自动 \`mem_recall\` 该
172
185
 
173
186
  | 命令 | 签名(含硬约束) |
174
187
  |------|------------------|
175
- | 存观测 | \`${CLI} save "<text>" --type bugfix\\|decision --lesson "<≤500 字符>" [--importance 1-3] [--closes-deferred N]\` — \`<text>\` **必填定位参数**;\`--lesson\` 超 500 直接 fail |
188
+ | 存观测 | \`${CLI} save "<text>" --type bugfix\\|decision --lesson "<≤500 字符>" [--importance 1-3] [--closes-deferred N] [--supersedes 12,E#34]\` — \`<text>\` **必填定位参数**;\`--lesson\` 超 500 直接 fail;\`--supersedes\` 替换代码已推翻的旧记忆 |
176
189
  | 推迟工作 | \`${CLI} defer add "<title ≤200>" [--priority 1\\|2\\|3] [--detail "<约束+为何推迟>"]\` — 标题 >200 挪到 \`--detail\` |
177
190
  | 改某条 | \`${CLI} update <id> [--lesson "<≤500>"] [--title T] [--type T] [--importance 1-3] [--narrative T] [--concepts "a b c"]\` |
178
191
  | 事件日志 | \`${CLI} activity save --type <bugfix\\|lesson\\|bug\\|discovery\\|refactor\\|feature\\|observation\\|decision> "<title>" [--body T] [--files f1,f2]\` |