claude-mem-lite 6.19.4 → 6.21.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +57 -21
- package/README.zh-CN.md +45 -20
- package/adopt-cli.mjs +156 -31
- package/adopt-content.mjs +20 -7
- package/bash-utils.mjs +45 -7
- package/claudemd.mjs +132 -55
- package/cli/common.mjs +11 -1
- package/commands/adopt.md +16 -6
- package/commands/unadopt.md +21 -11
- package/haiku-client.mjs +115 -27
- package/hook-context.mjs +14 -8
- package/hook-episode.mjs +75 -7
- package/hook-handoff.mjs +98 -7
- package/hook-llm.mjs +22 -30
- package/hook-memory.mjs +4 -8
- package/hook-optimize.mjs +27 -5
- package/hook-shared.mjs +89 -1
- package/hook-update.mjs +5 -2
- package/hook.mjs +345 -45
- package/install.mjs +187 -30
- package/lib/bash-file-targets.mjs +16 -1
- package/lib/citation-tracker.mjs +71 -2
- package/lib/cite-back-hint.mjs +52 -4
- package/lib/cooldown-path.mjs +13 -0
- package/lib/data-paths.mjs +23 -1
- package/lib/deferred-work.mjs +1 -1
- package/lib/delete-core.mjs +34 -5
- package/lib/export-columns.mjs +1 -0
- package/lib/git-state.mjs +10 -1
- package/lib/handoff-constants.mjs +4 -0
- package/lib/hook-prune.mjs +53 -17
- package/lib/hook-stdin.mjs +7 -1
- package/lib/llm-provider-probe.mjs +50 -1
- package/lib/local-steering.mjs +386 -0
- package/lib/maintain-core.mjs +206 -38
- package/lib/mcp-ownership.mjs +23 -0
- package/lib/observation-write.mjs +6 -1
- package/lib/plan-reader.mjs +2 -4
- package/lib/private-strip.mjs +31 -19
- package/lib/project-rekey.mjs +178 -0
- package/lib/prompt-admission.mjs +68 -0
- package/lib/quiet-scope.mjs +45 -3
- package/lib/recall-core.mjs +36 -7
- package/lib/save-nudge.mjs +3 -2
- package/lib/search-core.mjs +4 -0
- package/lib/task-reader.mjs +3 -5
- package/lib/tmp-fixture-sweep.mjs +2 -1
- package/lib/verify-apply-core.mjs +9 -2
- package/mem-cli.mjs +52 -22
- package/memdir.mjs +45 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +5 -1
- package/project-utils.mjs +24 -4
- package/schema.mjs +32 -1
- package/scripts/post-tool-recall.js +5 -3
- package/scripts/post-tool-use.sh +46 -20
- package/scripts/pre-tool-recall.js +13 -6
- package/scripts/prompt-search-utils.mjs +5 -27
- package/scripts/setup.sh +7 -4
- package/search-scoring.mjs +13 -6
- package/server.mjs +58 -9
- package/source-files.mjs +6 -0
- package/tool-schemas.mjs +14 -1
- package/utils.mjs +43 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.
|
|
12
|
+
"version": "6.21.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.
|
|
3
|
+
"version": "6.21.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
|
@@ -88,7 +88,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
88
88
|
- **Observation relations** -- Bidirectional links between related observations based on file overlap
|
|
89
89
|
- **User prompt capture** -- Records user prompts via UserPromptSubmit hook for intent tracking
|
|
90
90
|
- **Read file tracking** -- Tracks files read during sessions for richer episode context
|
|
91
|
-
- **
|
|
91
|
+
- **Degraded fallback when the LLM fails** -- An episode the deterministic rules already rate notable (an error fixed, a config or schema file changed) is saved with inferred metadata instead of being discarded. A routine edit the rules rate as noise (`Modified a.js, b.js` with no lesson) is dropped — as it is when the model answers without a lesson — so a failing provider loses those episodes; `claude-mem-lite doctor` warns when the `claude` CLI it would call does not resolve
|
|
92
92
|
- **Two-tier dedup** -- Jaccard similarity (5-minute window) + MinHash signatures (7-day cross-session window) prevent duplicates
|
|
93
93
|
- **Synonym expansion** -- Abbreviations like `K8s`, `DB`, `auth` automatically expand to full forms in FTS5 search (100+ pairs including CJK↔EN cross-language mappings)
|
|
94
94
|
- **CJK synonym extraction** -- Unsegmented Chinese text is scanned for known vocabulary words (数据库→database, 搜索→search, etc.) enabling cross-language memory recall
|
|
@@ -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
|
|
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
|
-
>
|
|
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
|
|
|
@@ -237,6 +237,35 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
|
|
|
237
237
|
repos/ # Shallow-cloned source repos
|
|
238
238
|
```
|
|
239
239
|
|
|
240
|
+
## Upgrading to 6.21.0
|
|
241
|
+
|
|
242
|
+
**Projects whose names are not plain ASCII get a new id, and what they stored moves once.** No
|
|
243
|
+
schema-version change: 6.20.0 still opens the database after this release has. Pin
|
|
244
|
+
`claude-mem-lite@6.20.0` before upgrading to avoid the move; after it, 6.20.0 names these
|
|
245
|
+
directories by their old ids again, so their moved rows are listed only with
|
|
246
|
+
`--project <new id>`.
|
|
247
|
+
|
|
248
|
+
- **Which projects.** Every character outside ASCII letters, digits and `_.-` used to become `-`,
|
|
249
|
+
so `~/projects/博客` and `~/projects/商城` were both `projects----` and shared one memory.
|
|
250
|
+
Letters, marks and digits of every script are now kept (`projects--博客`). An id whose parent
|
|
251
|
+
and directory names are both plain ASCII keeps its id byte for byte; a name with letters,
|
|
252
|
+
marks or digits of another script, or with a character outside the Basic Multilingual Plane
|
|
253
|
+
such as `🚀`, changes id.
|
|
254
|
+
- **At a project's first session start its data moves.** When no stored file path shows another
|
|
255
|
+
directory using the old id, the project takes everything under it, deferred items and session
|
|
256
|
+
history included. Otherwise only the memories whose file paths lie inside the directory move;
|
|
257
|
+
the rest stay under the old id. A one-time notice says what moved and how to list what stayed
|
|
258
|
+
(`claude-mem-lite recent 50 --project <old id>`).
|
|
259
|
+
- **Not in this release:** directories with the same parent and name in different repositories
|
|
260
|
+
(`~/a/packages/api` and `~/b/packages/api`) still share one id.
|
|
261
|
+
- **Also changed:** two sessions open in one project keep separate memory sessions (handoffs,
|
|
262
|
+
summaries and unsaved tool activity no longer mix); a session's follow-up prompts no longer
|
|
263
|
+
resume another session; maintenance hides idle memories for 7 days before queuing them for
|
|
264
|
+
deletion, in every project; an importance you set is no longer changed by reads, the access
|
|
265
|
+
boost or re-enrich; `recall` / `mem_recall` rank the current project and the exact path first
|
|
266
|
+
(`--project` / `project` keep one project); `<private>` fails closed on an unclosed, nested or
|
|
267
|
+
attributed tag. Full list: CHANGELOG.md.
|
|
268
|
+
|
|
240
269
|
## Upgrading to 6.19.0
|
|
241
270
|
|
|
242
271
|
**Search output changes; no switch.** No schema change and no migration, so reverting is
|
|
@@ -543,12 +572,12 @@ automatically on the next SessionStart.
|
|
|
543
572
|
|
|
544
573
|
```bash
|
|
545
574
|
claude-mem-lite adopt # install for current project
|
|
546
|
-
claude-mem-lite adopt --all #
|
|
575
|
+
claude-mem-lite adopt --all # legacy clean-up: strip the old memory-dir sentinel in every project
|
|
547
576
|
claude-mem-lite adopt --status # list adopted/disabled projects + current gating snapshot
|
|
548
577
|
claude-mem-lite adopt --dry-run # preview without writing
|
|
549
578
|
claude-mem-lite adopt --disable # opt out of auto-adopt for current project (writes .mem-no-auto-adopt sentinel)
|
|
550
579
|
claude-mem-lite adopt --enable # re-arm auto-adopt for current project (deletes the sentinel)
|
|
551
|
-
claude-mem-lite unadopt # remove
|
|
580
|
+
claude-mem-lite unadopt # remove the CLAUDE.md block + doc, and the CLAUDE.local.md block (not written again)
|
|
552
581
|
```
|
|
553
582
|
|
|
554
583
|
Slash commands `/adopt` and `/unadopt` wrap the same CLI.
|
|
@@ -580,24 +609,31 @@ Slash commands `/adopt` and `/unadopt` wrap the same CLI.
|
|
|
580
609
|
`/exit` + fresh session is enough. Same caveat applies to `unadopt`.
|
|
581
610
|
|
|
582
611
|
**Safety:**
|
|
583
|
-
-
|
|
584
|
-
|
|
612
|
+
- Regenerated, not hand-edit-safe: `adopt` rewrites the managed block to the shipped
|
|
613
|
+
template, and so does every SessionStart whenever the block differs from it — hand edits
|
|
614
|
+
included. Keep your own notes outside the `claude-mem-lite:begin…end` markers, or set
|
|
615
|
+
`CLAUDE_MEM_NO_TEMPLATE_REFRESH=1` to freeze the block.
|
|
585
616
|
- Slug-scoped & dedup-guarded: only the `claude-mem-lite:begin…end` region is
|
|
586
617
|
ever rewritten, and duplicate / CRLF-orphaned copies are collapsed to one.
|
|
587
618
|
Unlike the legacy `MEMORY.md` scheme there is no line-budget gate — `CLAUDE.md`
|
|
588
619
|
has no truncation cap.
|
|
589
|
-
- **Auto-adopt runs on EVERY SessionStart, for any install path
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
`
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
620
|
+
- **Auto-adopt runs on EVERY SessionStart, for any install path, and no longer adds
|
|
621
|
+
the block to `CLAUDE.md` (next release after 6.19.4).** A git project without the
|
|
622
|
+
block gets it in `CLAUDE.local.md` at the git root (kept out of git via
|
|
623
|
+
`.git/info/exclude`), or in SessionStart context outside git; a project that
|
|
624
|
+
carries the `CLAUDE.md` block (explicit `adopt`, or an older version's auto-adopt)
|
|
625
|
+
has it kept in sync — refreshed when the shipped template changes (freeze with
|
|
626
|
+
`CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`) — and gets no local or injected copy, also in
|
|
627
|
+
sessions started from a subdirectory. A `CLAUDE.local.md` block the plugin created
|
|
628
|
+
and you (or `unadopt`) removed is not written back; `adopt --enable` re-arms it.
|
|
629
|
+
Per-project opt-out: `claude-mem-lite adopt --disable` (writes a durable
|
|
630
|
+
`<memdir>/.mem-no-auto-adopt` sentinel that survives marker deletion / plugin
|
|
631
|
+
reinstalls; run at the repository root it also covers sessions started in its
|
|
632
|
+
subdirectories; it removes the local block).
|
|
633
|
+
Global opt-out: `MEM_NO_AUTO_ADOPT=1` (blocks already written stay until `unadopt`).
|
|
598
634
|
- The fallback hook layer is never removed from source — conditional trim is
|
|
599
|
-
runtime-gated on
|
|
600
|
-
full verbose output.
|
|
635
|
+
runtime-gated on steering being delivered (block present, or injection on), so
|
|
636
|
+
projects that opted out get the full verbose output.
|
|
601
637
|
|
|
602
638
|
See [the invited-memory design][invited-memory] for the full design (including the
|
|
603
639
|
reusable template other plugins can follow). It is a development-time document and
|
|
@@ -703,7 +739,7 @@ Episodes are batched related operations (edits to the same file group) that get
|
|
|
703
739
|
Episode buffer -> Flush to JSON -> claude -p --model haiku -> Structured observation -> SQLite
|
|
704
740
|
```
|
|
705
741
|
|
|
706
|
-
Each observation includes type, title, narrative, concepts, facts, importance (1-3), and is automatically deduplicated via two tiers: Jaccard similarity (>70% within 5 minutes) and MinHash signatures (>80% within 7 days across sessions). If the LLM call fails,
|
|
742
|
+
Each observation includes type, title, narrative, concepts, facts, importance (1-3), and is automatically deduplicated via two tiers: Jaccard similarity (>70% within 5 minutes) and MinHash signatures (>80% within 7 days across sessions). If the LLM call fails, an episode the rules rate notable is saved with inferred metadata; a routine edit the rules rate as noise is dropped. Related observations are linked via `related_ids` based on FTS5 title similarity and file overlap.
|
|
707
743
|
|
|
708
744
|
## Management Commands
|
|
709
745
|
|
|
@@ -1020,8 +1056,8 @@ claude-mem-lite.
|
|
|
1020
1056
|
| `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
1057
|
| `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
1058
|
| `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`
|
|
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)_ |
|
|
1059
|
+
| `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)_ |
|
|
1060
|
+
| `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
1061
|
|
|
1026
1062
|
### What gets injected into your context
|
|
1027
1063
|
|
package/README.zh-CN.md
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
- **观察关联** -- 基于文件重叠自动建立观察之间的双向链接
|
|
68
68
|
- **用户提示捕获** -- 通过 UserPromptSubmit 钩子记录用户提示,追踪用户意图
|
|
69
69
|
- **Read 文件追踪** -- 追踪会话中读取的文件,丰富 episode 上下文
|
|
70
|
-
-
|
|
70
|
+
- **LLM 失败时的降级保存** -- 规则已判定为有价值的 episode(修掉了报错、改了配置或 schema 文件)用推断的元数据保存,而非丢弃;规则判为噪声的日常编辑(`Modified a.js, b.js` 且没有 lesson)会被丢掉——模型成功但没给 lesson 时也一样——所以 LLM 不可用期间这类 episode 会丢失。`claude-mem-lite doctor` 会在要调用的 `claude` CLI 不存在时告警
|
|
71
71
|
- **两级去重** -- Jaccard 相似度(5 分钟窗口)+ MinHash 签名(7 天跨会话窗口)双重防重
|
|
72
72
|
- **同义词扩展** -- 缩写如 `K8s`、`DB`、`auth` 在 FTS5 搜索时自动扩展为全称(48+ 对)
|
|
73
73
|
- **伪相关反馈(PRF)** -- 首轮结果作为种子扩展查询,提升召回率
|
|
@@ -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
|
|
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
|
-
>
|
|
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/`,原目录保持不变
|
|
@@ -199,6 +199,27 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
|
|
|
199
199
|
repos/ # 浅克隆的源代码仓库
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
## 升级到 6.21.0
|
|
203
|
+
|
|
204
|
+
**名字不是纯 ASCII 的项目会换一个新标识,它们存下的内容会搬一次。** 没有 schema 版本变更:
|
|
205
|
+
本版本打开过的数据库,6.20.0 仍能打开。想避免搬迁,请在**升级之前**固定 `claude-mem-lite@6.20.0`;
|
|
206
|
+
升级之后再回退,6.20.0 会按旧标识称呼这些目录,搬走的行只能用 `--project <新标识>` 列出。
|
|
207
|
+
|
|
208
|
+
- **哪些项目。** 以前 ASCII 字母、数字和 `_.-` 以外的字符都变成 `-`,所以 `~/projects/博客` 和
|
|
209
|
+
`~/projects/商城` 都是 `projects----`,共用一份记忆。现在任何文字的字母、组合符和数字都会保留
|
|
210
|
+
(`projects--博客`)。父目录名和目录名都是纯 ASCII 的,标识逐字节不变;名字里有其他文字的字母、
|
|
211
|
+
组合符或数字,或有基本多文种平面以外的字符(如 `🚀`)时,标识会变。
|
|
212
|
+
- **项目第一次启动会话时搬迁数据。** 如果旧标识下没有任何文件路径显示另一个同样使用该旧标识的目录,
|
|
213
|
+
就把旧标识下的全部内容搬过来,包括待办和会话历史。否则只搬文件路径落在本目录里的记忆,其余留在
|
|
214
|
+
旧标识下。会提示一次搬了什么,以及怎么列出留下的内容(`claude-mem-lite recent 50 --project <旧标识>`)。
|
|
215
|
+
- **本版本不包含:** 不同仓库里父目录和目录名都相同的目录(`~/a/packages/api` 与 `~/b/packages/api`)
|
|
216
|
+
仍共用一个标识。
|
|
217
|
+
- **其他变化:** 同一项目同时开两个会话,各自的交接、摘要和未保存的工具记录不再混在一起;会话里的
|
|
218
|
+
跟进提示不再续上别的会话;维护会先把闲置记忆隐藏 7 天,再排队删除,所有项目一致;你设置的 importance
|
|
219
|
+
不再被读取、访问提升或 re-enrich 改动;`recall` / `mem_recall` 当前项目和完全匹配的路径排在前面
|
|
220
|
+
(`--project` / `project` 只看一个项目);`<private>` 在未闭合、嵌套或带属性时一律按私密处理。
|
|
221
|
+
完整列表见 CHANGELOG.md。
|
|
222
|
+
|
|
202
223
|
## 升级到 6.19.0
|
|
203
224
|
|
|
204
225
|
**搜索输出有变化,没有开关。** 没有 schema 变更、不需要迁移,回退只需固定 `claude-mem-lite@6.18.0`。
|
|
@@ -451,12 +472,12 @@ instruction-following 权威。
|
|
|
451
472
|
|
|
452
473
|
```bash
|
|
453
474
|
claude-mem-lite adopt # 当前项目注入
|
|
454
|
-
claude-mem-lite adopt --all #
|
|
475
|
+
claude-mem-lite adopt --all # 旧方案清理:在所有项目里删除旧的 memory 目录哨兵
|
|
455
476
|
claude-mem-lite adopt --status # 列出已 adopt / 已禁用项目 + 当前 gate 快照
|
|
456
477
|
claude-mem-lite adopt --dry-run # 只打印不写入
|
|
457
478
|
claude-mem-lite adopt --disable # 当前项目关闭 auto-adopt(写 .mem-no-auto-adopt 哨兵)
|
|
458
479
|
claude-mem-lite adopt --enable # 当前项目重新启用 auto-adopt(删哨兵)
|
|
459
|
-
claude-mem-lite unadopt #
|
|
480
|
+
claude-mem-lite unadopt # 移除 CLAUDE.md 托管块和详情文档,以及 CLAUDE.local.md 托管块(之后不再写回)
|
|
460
481
|
```
|
|
461
482
|
|
|
462
483
|
Slash 命令 `/adopt` 和 `/unadopt` 是上述 CLI 的包装。
|
|
@@ -479,18 +500,22 @@ Slash 命令 `/adopt` 和 `/unadopt` 是上述 CLI 的包装。
|
|
|
479
500
|
同理。
|
|
480
501
|
|
|
481
502
|
**安全性:**
|
|
482
|
-
-
|
|
483
|
-
|
|
484
|
-
-
|
|
485
|
-
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
503
|
+
- 托管块会被重新生成,不保留手改:`adopt` 会把它改写成出货模板,每次 SessionStart
|
|
504
|
+
只要它和模板不同也会改写——手动编辑的内容一样被覆盖。自己的笔记写在
|
|
505
|
+
`claude-mem-lite:begin…end` 标记之外,或设 `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1` 冻结。
|
|
506
|
+
- 按 slug 限定、去重:只改写 `claude-mem-lite:begin…end` 这一段,重复或 CRLF 残留的
|
|
507
|
+
副本会合并成一份。旧的 `MEMORY.md` 方案才有行数预算,`CLAUDE.md` 没有截断上限。
|
|
508
|
+
- **任何安装路径每次 SessionStart 都自动 adopt,且不再把托管块加进 `CLAUDE.md`(6.19.4
|
|
509
|
+
之后的下一个版本起)。** 没有托管块的 git 项目,引导写进 git 根目录的 `CLAUDE.local.md`
|
|
510
|
+
(经 `.git/info/exclude` 排除在 git 之外),不在 git 里时注入 SessionStart 上下文;带
|
|
511
|
+
`CLAUDE.md` 托管块的项目(显式 adopt 或旧版本写入)保持同步,出货模板变化会刷新(用
|
|
512
|
+
`CLAUDE_MEM_NO_TEMPLATE_REFRESH=1` 冻结),且不会再叠加本地副本或注入,从子目录启动的会话
|
|
513
|
+
也一样。插件创建过、又被你(或 `unadopt`)删掉的 `CLAUDE.local.md` 托管块不会被写回;
|
|
514
|
+
`adopt --enable` 可重新启用。项目级关闭:`claude-mem-lite adopt --disable`(写
|
|
515
|
+
`<memdir>/.mem-no-auto-adopt` 哨兵,存活于 marker 删除 / 插件重装;在仓库根目录运行时,对从子目录启动的
|
|
516
|
+
会话同样生效;并删除本地托管块)。全局关闭:`export MEM_NO_AUTO_ADOPT=1`(已写入的托管块保留,直到 `unadopt`)。
|
|
517
|
+
- 保守 hook 层源码永不删——条件瘦身按"引导已送达"(有托管块或注入开启)做 runtime
|
|
518
|
+
判断,关闭了引导的项目仍看完整 verbose 输出。
|
|
494
519
|
|
|
495
520
|
完整设计见 `docs/plans/2026-04-16-invited-memory-pattern.md`(含其它插件
|
|
496
521
|
可复用的模板)。
|
|
@@ -577,7 +602,7 @@ Episode 是一批相关操作(对同一组文件的编辑),由后台 LLM w
|
|
|
577
602
|
Episode 缓冲区 -> 刷新为 JSON -> claude -p --model haiku -> 结构化观察 -> SQLite
|
|
578
603
|
```
|
|
579
604
|
|
|
580
|
-
每条观察包含类型、标题、叙述、概念、事实和重要度(1-3),并通过两级机制自动去重:Jaccard 相似度(5 分钟内 >70%)和 MinHash 签名(7 天跨会话 >80%)。LLM
|
|
605
|
+
每条观察包含类型、标题、叙述、概念、事实和重要度(1-3),并通过两级机制自动去重:Jaccard 相似度(5 分钟内 >70%)和 MinHash 签名(7 天跨会话 >80%)。LLM 调用失败时,规则判定有价值的 episode 用推断的元数据保存降级记录,规则判为噪声的日常编辑会被丢掉。相关观察通过 FTS5 标题相似度和文件重叠自动建立 `related_ids` 链接。
|
|
581
606
|
|
|
582
607
|
## 管理命令
|
|
583
608
|
|
|
@@ -786,8 +811,8 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
|
|
|
786
811
|
| `CLAUDE_MEM_BASH_RECALL` | 在查看(`cat`、`sed -n`、`head`…)或写入(`sed -i`、`cat > f`、python 补丁…)文件的 Bash 命令执行前做文件召回,与 Read / Edit 召回相同。bash 预过滤让其他命令不启动 Node。设为 `off` 只关闭这一路。 | _(开启)_ |
|
|
787
812
|
| `CLAUDE_MEM_LESSON_GROUNDING` | 自动捕获的 event 只有在教训引用了本窗口自己的诊断文字(失败输出行、编辑新增的注释或提交信息)时才保留教训;否则保留这一行但去掉教训,importance 降为 1,低于所有注入面的门槛。设为 `off` 保留未引用原文的教训。 | _(开启)_ |
|
|
788
813
|
| `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`
|
|
790
|
-
| `MEM_NO_ADOPT_HINT` |
|
|
814
|
+
| `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 删除)。 | _(禁用)_ |
|
|
815
|
+
| `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
816
|
|
|
792
817
|
## 许可证
|
|
793
818
|
|
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 {
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
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
|
-
|
|
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.
|
|
219
|
-
*
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|