claude-mem-lite 6.29.2 → 6.30.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 +9 -11
- package/README.zh-CN.md +18 -14
- package/adopt-cli.mjs +56 -20
- package/adopt-content.mjs +1 -2
- package/claudemd.mjs +31 -5
- package/cli/activity.mjs +3 -2
- package/cli/common.mjs +35 -4
- package/cli/fts-check.mjs +2 -1
- package/cli/verify-apply.mjs +4 -4
- package/cli.mjs +5 -1
- package/commands/adopt.md +7 -7
- package/commands/bug.md +3 -3
- package/commands/lesson.md +3 -3
- package/commands/mem.md +2 -2
- package/commands/unadopt.md +2 -2
- package/commands/update.md +2 -2
- package/commands/verify.md +2 -4
- package/haiku-client.mjs +6 -1
- package/hook-context.mjs +2 -2
- package/hook-llm.mjs +22 -16
- package/hook-optimize.mjs +180 -176
- package/hook-shared.mjs +7 -0
- package/hook.mjs +18 -11
- package/install.mjs +12 -10
- package/lib/activity.mjs +1 -1
- package/lib/db-unusable.mjs +43 -12
- package/lib/edge-attribution.mjs +1 -1
- package/lib/get-core.mjs +3 -3
- package/lib/local-steering.mjs +14 -3
- package/lib/maintain-core.mjs +35 -1
- package/lib/project-rekey.mjs +6 -2
- package/lib/quiet-scope.mjs +6 -2
- package/lib/save-nudge.mjs +6 -3
- package/lib/schema-skew.mjs +4 -2
- package/lib/startup-dashboard.mjs +3 -2
- package/lib/stats-quality.mjs +6 -4
- package/lib/upgrade-banner.mjs +2 -1
- package/mem-cli.mjs +58 -36
- package/memdir.mjs +1 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
- package/search-scoring.mjs +6 -6
- package/secret-scrub.mjs +4 -1
- package/server.mjs +24 -9
- package/tool-schemas.mjs +2 -2
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.
|
|
12
|
+
"version": "6.30.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.30.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
|
@@ -124,7 +124,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
124
124
|
<br>Original behaviour and the measurements behind it via explicit keywords or FTS5 term overlap. **The `/clear` and `/compact` arm fires since v5.4.0** (R10-P1-1); before that it had never once written a row — `session_handoffs` on the maintainer's install held 4 `exit` rows and **0** `clear` rows. Two host facts settled it, both measured rather than assumed. (1) `Stop` runs at the end of every assistant *turn*, not once per session, and it deleted the session file that SessionStart reads to learn which session just ended — so the branch was unreachable, and mem sessions were minted per turn (58 prompts over 16 host sessions produced 56 mem sessions and 56 summary rows, 2026-09-07). (2) Claude Code **rotates its session id across `/clear`**: of 21 real transcripts, 12 carry a `/clear` command record, and in 12/12 that record's timestamp precedes its own file's first record by ~0.1s — the command is issued in the old session and replayed into a new file under a new id. So `Stop` no longer deletes the file, SessionStart asks the host's `source` (`startup`/`clear`/`compact`/`resume`) instead of guessing from the file, and the handoff's prompt lookup falls back to the unscoped set when the new session's id matches none. Revert path: `CLAUDE_MEM_LEGACY_STOP_UNLINK=1`
|
|
125
125
|
- **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
|
|
126
126
|
- **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`
|
|
127
|
-
- **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`. `hook-llm` routes non-memdir summary types through `persistHaikuSummary` so upgrades from observations→events are atomic. (v3.39: the `/lesson` and `/bug` slash commands were redirected from this events table to searchable **observations** — `mem_search` never read the events table, so explicit saves were unfindable; the events table remains the auto-capture activity log.)
|
|
127
|
+
- **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`. `hook-llm` routes non-memdir summary types through `persistHaikuSummary` so upgrades from observations→events are atomic. (v3.39: the `/lesson` and `/claude-mem-lite:bug` slash commands were redirected from this events table to searchable **observations** — `mem_search` never read the events table, so explicit saves were unfindable; the events table remains the auto-capture activity log.)
|
|
128
128
|
- **In-place observation updates** -- `mem_update` tool modifies existing observations atomically (field update + FTS text rebuild in one transaction), preserving original IDs and references
|
|
129
129
|
- **Bulk export** -- `mem_export` tool exports observations as JSON or JSONL, with project/type/date filtering and 1000-row pagination cap with batch guidance
|
|
130
130
|
- **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
|
|
@@ -165,7 +165,7 @@ is still outside it gets a message naming both sides of the mismatch instead of
|
|
|
165
165
|
|
|
166
166
|
Plugin mode manages its own hooks/runtime. On session start it only **checks and reports** new claude-mem-lite versions; it does **not** self-overwrite plugin files in place. Update plugin-mode installs through Claude's plugin workflow.
|
|
167
167
|
|
|
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.
|
|
168
|
+
> **The plugin install is complete on its own** — hooks, MCP tools, and the bundled slash commands (`/mem`, `/lesson`, `/claude-mem-lite: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
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`, also in subagents that load project instructions; the built-in Explore and Plan agents load none) 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). Where Claude Code reads an `AGENTS.md` as the project's instructions, it stops reading it once a `CLAUDE.md` or `CLAUDE.local.md` exists in the session's directory or above, so there nothing is written and the text is **injected** (subagents do not see it), with a one-time notice that names that `AGENTS.md`. With `CLAUDE_MEM_RULES_STEERING=1` the block goes to `.claude/rules/claude-mem-lite.md` at the repository root instead — excluded from git the same way, loaded by Claude Code as project instructions, also in subagents that load project instructions, and it leaves `AGENTS.md` loading — with its own one-time notice; it is not the default because in a pre-registered A/B it did not beat injection by the bar set in advance (`docs/audits/20261006-d212-ab.md`). 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`). With the variable set, the same holds where the rules file cannot be written (a symbolic link on its path, a tracked or foreign file of that name, an npm package that would ship it). The notice beside an `AGENTS.md` names it, why there is no file, and the ways to a file that keep it loading, each only where it works: Project instructions = `claude-md-and-agents-md` in `/config`, after which `CLAUDE.local.md` is written (not where `CLAUDE.local.md` is refused too: a package root, or a file you removed), and `/adopt`, whose `CLAUDE.md` imports the `AGENTS.md` beside it (not offered when an `AGENTS.md` above that directory or in a subdirectory would stop loading). 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` or the rules file up (a publishable npm package root does not keep `CLAUDE.local.md`: a block written before is taken out at the next session start; in a project whose `AGENTS.md` it would switch off, the block comes out and is injected, or, with `CLAUDE_MEM_RULES_STEERING=1`, moves to the rules file where that can be written). 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 these notices with `MEM_NO_ADOPT_HINT=1`.
|
|
171
171
|
>
|
|
@@ -684,9 +684,9 @@ have not allowed, and routing the same action through Bash would sidestep it.
|
|
|
684
684
|
/mem timeline <query> # Browse timeline around a match
|
|
685
685
|
/mem browse # Tier-grouped memory dashboard
|
|
686
686
|
/mem <query> # Shorthand for search
|
|
687
|
-
/lesson <text> # Save a non-obvious lesson
|
|
688
|
-
/bug <text>
|
|
689
|
-
/verify
|
|
687
|
+
/lesson <text> # Save a non-obvious lesson as a searchable observation
|
|
688
|
+
/claude-mem-lite:bug <text> # Log a known bug + repro steps as a searchable observation
|
|
689
|
+
/claude-mem-lite:verify # Check memories against the current code; correct stale ones after you approve
|
|
690
690
|
```
|
|
691
691
|
|
|
692
692
|
### Efficient Search Workflow
|
|
@@ -701,12 +701,10 @@ have not allowed, and routing the same action through Bash would sidestep it.
|
|
|
701
701
|
|
|
702
702
|
Opt-in mechanism that installs a slug-scoped managed block into the project's
|
|
703
703
|
own `CLAUDE.md` (plus an on-demand detail doc under `.claude/`) so Claude Code
|
|
704
|
-
loads the plugin's MCP-tool triggers as **project instructions
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
instructions) rather than polluting `MEMORY.md`, which is reserved for the user's
|
|
709
|
-
own memories. The pre-v3.13 scheme wrote into `MEMORY.md`; it is migrated away
|
|
704
|
+
loads the plugin's MCP-tool triggers as **project instructions**, delivered in a
|
|
705
|
+
message after the system prompt. Steering lives in `CLAUDE.md` (the canonical home
|
|
706
|
+
for project instructions) rather than in the memdir `MEMORY.md`, which is reserved
|
|
707
|
+
for the user's own memories. The pre-v3.13 scheme wrote into `MEMORY.md`; it is migrated away
|
|
710
708
|
automatically on the next SessionStart.
|
|
711
709
|
|
|
712
710
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
- **插件缓存 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 起)。
|
|
96
96
|
- **Git-SHA 延续锚点**(v2.31.0)-- handoff 记录包含 `git_sha_at_handoff` 字段,任何匹配当前 `HEAD` 的 handoff 都视为延续会话,不受 TTL 限制。代码状态比时钟时间更能反映上下文延续。
|
|
97
97
|
- **启动面板**(v2.31.0)-- SessionStart hook 将 `git status` + `~/.claude/tasks/*.json` + `~/.claude/plans/*.md` + 最近 /exit 交接 + 最近事件数聚合为一个结构化块,通过 `hookSpecificOutput.additionalContext` 注入。
|
|
98
|
-
- **活动命名空间**(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`。`hook-llm` 通过 `persistHaikuSummary` 路由非 memdir 摘要,observations → events 升级路径是事务原子的。(v3.39:`/lesson`、`/bug` 斜杠命令已从此 events 表重定向到可搜索的 **observations**——`mem_search` 从不读 events 表,显式保存因此搜不到;events 表仍作自动捕获活动日志。)
|
|
98
|
+
- **活动命名空间**(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`。`hook-llm` 通过 `persistHaikuSummary` 路由非 memdir 摘要,observations → events 升级路径是事务原子的。(v3.39:`/lesson`、`/claude-mem-lite:bug` 斜杠命令已从此 events 表重定向到可搜索的 **observations**——`mem_search` 从不读 events 表,显式保存因此搜不到;events 表仍作自动捕获活动日志。)
|
|
99
99
|
|
|
100
100
|
## 平台支持
|
|
101
101
|
|
|
@@ -129,7 +129,7 @@ v5.1.0 到 v6.1.0 之间,`package.json` 声明的是 `os: ["darwin", "linux"]`
|
|
|
129
129
|
|
|
130
130
|
插件模式会管理自己的运行时与钩子。SessionStart 时它现在只会**检查并提示**新版本,不会直接覆盖插件目录中的文件。插件模式请通过 Claude 的插件更新流程完成升级。
|
|
131
131
|
|
|
132
|
-
> **插件安装本身即完整** —— hooks、MCP 工具、以及捆绑的 slash 命令(`/mem`、`/lesson`、`/bug`、`/adopt`)全部从插件内运行,无需第二步。slash 命令以从插件目录解析出的绝对路径调用捆绑 CLI(`node "${CLAUDE_PLUGIN_ROOT}/cli.mjs" <cmd>`),因此不依赖 `PATH` 上的任何东西。全局 `claude-mem-lite` **shell** 命令(用于你自己在终端里跑查询)是**可选**的 —— `npm i -g claude-mem-lite` —— 且是**独立**的 npm 安装:插件的自动更新**不会**刷新它,想保持同步就重新跑 `npm i -g claude-mem-lite@latest`。插件要完整工作**并不需要**它。
|
|
132
|
+
> **插件安装本身即完整** —— hooks、MCP 工具、以及捆绑的 slash 命令(`/mem`、`/lesson`、`/claude-mem-lite:bug`、`/adopt`)全部从插件内运行,无需第二步。slash 命令以从插件目录解析出的绝对路径调用捆绑 CLI(`node "${CLAUDE_PLUGIN_ROOT}/cli.mjs" <cmd>`),因此不依赖 `PATH` 上的任何东西。全局 `claude-mem-lite` **shell** 命令(用于你自己在终端里跑查询)是**可选**的 —— `npm i -g claude-mem-lite` —— 且是**独立**的 npm 安装:插件的自动更新**不会**刷新它,想保持同步就重新跑 `npm i -g claude-mem-lite@latest`。插件要完整工作**并不需要**它。
|
|
133
133
|
|
|
134
134
|
### 方式二:npx(一行命令)
|
|
135
135
|
|
|
@@ -533,9 +533,9 @@ README 和 `docs/ARCHITECTURE.md` 都钉在它上面。)
|
|
|
533
533
|
/mem timeline <query> # 围绕匹配结果浏览时间线
|
|
534
534
|
/mem browse # 分层记忆仪表盘
|
|
535
535
|
/mem <query> # search 的简写
|
|
536
|
-
/lesson <text> #
|
|
537
|
-
/bug <text>
|
|
538
|
-
/verify
|
|
536
|
+
/lesson <text> # 把非显而易见的经验存为可搜索的 observation
|
|
537
|
+
/claude-mem-lite:bug <text> # 把已知 bug + 复现步骤记为可搜索的 observation
|
|
538
|
+
/claude-mem-lite:verify # 对照当前代码核查记忆;你确认后才改正过期的记忆
|
|
539
539
|
```
|
|
540
540
|
|
|
541
541
|
### 高效搜索工作流
|
|
@@ -548,10 +548,11 @@ README 和 `docs/ARCHITECTURE.md` 都钉在它上面。)
|
|
|
548
548
|
|
|
549
549
|
### Invited Memory(邀请式记忆,v2.32+)
|
|
550
550
|
|
|
551
|
-
Opt-in
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
551
|
+
Opt-in 机制——在项目自己的 `CLAUDE.md` 里写入一个按 slug 限定的托管块(外加 `.claude/` 下
|
|
552
|
+
按需读取的详情文档),让 Claude Code 把插件的 MCP 工具触发规则作为**项目指令**加载(在系统
|
|
553
|
+
提示之后的一条消息里送达)。引导放在 `CLAUDE.md`(项目指令的正规位置),不去占用 memdir 里
|
|
554
|
+
留给用户自己记忆的 `MEMORY.md`。v3.13 之前的方案写入
|
|
555
|
+
`MEMORY.md`;下一次 SessionStart 会自动迁走。
|
|
555
556
|
|
|
556
557
|
```bash
|
|
557
558
|
claude-mem-lite adopt # 当前项目注入
|
|
@@ -566,16 +567,19 @@ claude-mem-lite unadopt # 移除 CLAUDE.md 托管块和详情文档
|
|
|
566
567
|
Slash 命令 `/adopt` 和 `/unadopt` 是上述 CLI 的包装。
|
|
567
568
|
|
|
568
569
|
**Adopt 后会发生什么:**
|
|
569
|
-
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
570
|
+
- `<cwd>/CLAUDE.md` 新增一段 `<!-- claude-mem-lite:begin v1 -->…<!-- claude-mem-lite:end -->`
|
|
571
|
+
包裹、带独立 `## claude-mem-lite — persistent memory` 标题的托管块,内含指向
|
|
572
|
+
`mem_recall` / `mem_save` / `mem_defer` 及其关键参数的触发表。只管理这一段,`CLAUDE.md`
|
|
573
|
+
其余内容原样保留,可与其他插件(如 `code-graph-mcp`)的托管块共存于同一文件。
|
|
574
|
+
- 生成 `<cwd>/.claude/plugin_claude_mem_lite.md` 详情文件(不自动加载;`CLAUDE.md` 托管块
|
|
575
|
+
指向它时按需读取)。出货内容变化(版本升级或模板变更)时托管块自动刷新,除非设
|
|
576
|
+
`CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`。
|
|
573
577
|
- 保守 hook 层自动瘦身:MCP server instructions 去掉 `WHEN TO USE` 段,
|
|
574
578
|
SessionStart 注入去掉 `File Lessons` / `Key Context`。`#ID` 引用与 `Recent`
|
|
575
579
|
表保留,`mem_get` 仍可随时展开。
|
|
576
580
|
|
|
577
581
|
**什么时候生效?**
|
|
578
|
-
-
|
|
582
|
+
- `CLAUDE.md` 托管块和 hook 层瘦身(`File Lessons` / `Key Context` / lesson
|
|
579
583
|
后缀)**下一次 SessionStart** 生效(adopt 的项目下任一新会话)。
|
|
580
584
|
- MCP server instructions 是**服务启动时构建一次**,MCP 协议无 "push" 机制,
|
|
581
585
|
所以 `WHEN TO USE` / `Decision rules` 两段的瘦身**只在 Claude Code 重启后**
|
package/adopt-cli.mjs
CHANGED
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
claudeMdPath,
|
|
51
51
|
detailDocPath,
|
|
52
52
|
addAgentsImports,
|
|
53
|
-
|
|
53
|
+
holdsOnlyPluginLines,
|
|
54
54
|
straySentinelLines,
|
|
55
55
|
} from './claudemd.mjs';
|
|
56
56
|
import { PLUGIN_SLUG, CURRENT_SENTINEL_VERSION, buildClaudeMdBlock, getDetailDoc } from './adopt-content.mjs';
|
|
@@ -77,7 +77,9 @@ import {
|
|
|
77
77
|
isSharedAncestor,
|
|
78
78
|
samePath,
|
|
79
79
|
localFileLinked,
|
|
80
|
+
userInstructionFiles,
|
|
80
81
|
} from './lib/local-steering.mjs';
|
|
82
|
+
import { CLI_INVOKE } from './cli-path.mjs';
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
85
|
* Remove the auto-written local block (CLAUDE.local.md, or the D#212 rules file) for the project at
|
|
@@ -226,7 +228,7 @@ function adoptOne(cwd, { force, dryRun }) {
|
|
|
226
228
|
const sharedNote = (verb) =>
|
|
227
229
|
switchesOffBelow
|
|
228
230
|
? [
|
|
229
|
-
` ⚠ ${claudeMdPath(cwd)} ${verb} Claude Code reading the AGENTS.md of every project below ${cwd} that has one; to steer one project, run adopt inside it instead${verb === 'stops' ?
|
|
231
|
+
` ⚠ ${claudeMdPath(cwd)} ${verb} Claude Code reading the AGENTS.md of every project below ${cwd} that has one; to steer one project, run adopt inside it instead${verb === 'stops' ? ` (\`${CLI_INVOKE} unadopt\` here takes this back)` : ''}, or set Project instructions to claude-md-and-agents-md in /config`,
|
|
230
232
|
]
|
|
231
233
|
: [];
|
|
232
234
|
|
|
@@ -262,7 +264,12 @@ function adoptOne(cwd, { force, dryRun }) {
|
|
|
262
264
|
: local.action === 'failed'
|
|
263
265
|
? ` (could not remove the block from ${local.path}, so sessions here load it twice)`
|
|
264
266
|
: ` (+removed the block from ${local.path})`;
|
|
265
|
-
|
|
267
|
+
// The block's disposition alone read "unchanged" over a rewritten detail doc (D#470).
|
|
268
|
+
const docNote =
|
|
269
|
+
r.action === 'unchanged' && r.docAction !== 'unchanged'
|
|
270
|
+
? ` (+${r.docAction === 'created' ? 'wrote' : 'refreshed'} detail doc)`
|
|
271
|
+
: '';
|
|
272
|
+
log(`[adopt] ${cwd} → ${r.action}${docNote}${migNote}${localNote}${importNote}`);
|
|
266
273
|
for (const line of elsewhere('stops')) log(line);
|
|
267
274
|
for (const line of sharedNote('stops')) log(line);
|
|
268
275
|
// The copy that could not come out loads beside CLAUDE.md: said, and not a success (R3-4).
|
|
@@ -373,13 +380,17 @@ export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
|
|
|
373
380
|
// cost most of the proactive memory writes (1.5 vs 5.25 per trajectory) and never reached
|
|
374
381
|
// subagents (0/12). Inside a git work tree the block now goes to CLAUDE.local.md, which the
|
|
375
382
|
// host loads like CLAUDE.md and info/exclude keeps out of commits (5.25 writes, 12/12).
|
|
376
|
-
|
|
383
|
+
// managed-only loads no project CLAUDE.md either (code.claude.com/docs/en/memory): a block in
|
|
384
|
+
// one steers nothing, so the session goes on to the refused local file and injects, and the
|
|
385
|
+
// file stays as it is (v6.30.0 pre-tag defects review P2-2).
|
|
386
|
+
const managedOnly = userInstructionFiles() === 'managed-only';
|
|
387
|
+
const hasBlock = !managedOnly && blockIn(cwd);
|
|
377
388
|
if (!hasBlock) {
|
|
378
389
|
if (markerDir && markerKey) writeMarker(markerDir, markerKey);
|
|
379
390
|
// A damaged sentinel line (`… --> (team note)`) leaves the block's text in CLAUDE.md, which
|
|
380
391
|
// the host loads, while the block no longer parses: a copy written to CLAUDE.local.md or
|
|
381
392
|
// injected here was a second one. Neither; `adopt` names the line and the fix (D#369 B14).
|
|
382
|
-
const stray = straySentinelLines(claudeMdPath(cwd), PLUGIN_SLUG);
|
|
393
|
+
const stray = managedOnly ? [] : straySentinelLines(claudeMdPath(cwd), PLUGIN_SLUG);
|
|
383
394
|
if (stray.length > 0) return { ok: true, action: 'damaged', reason: 'stray-sentinel', lines: stray };
|
|
384
395
|
const root = localSteeringRoot(cwd);
|
|
385
396
|
if (root) {
|
|
@@ -387,7 +398,13 @@ export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
|
|
|
387
398
|
// loads the root's CLAUDE.md as an ancestor, so a block there already steers this
|
|
388
399
|
// session; and an opt-out recorded for the root covers the file that lives there.
|
|
389
400
|
if (!samePath(root, cwd)) {
|
|
390
|
-
|
|
401
|
+
const above = managedOnly ? null : blockAbove(cwd, root);
|
|
402
|
+
if (above)
|
|
403
|
+
return {
|
|
404
|
+
ok: true,
|
|
405
|
+
action: 'already-adopted',
|
|
406
|
+
reason: samePath(above, root) ? 'root-claude-md' : 'ancestor-claude-md',
|
|
407
|
+
};
|
|
391
408
|
// Off for the project means off here too: no file, no injected copy, no /adopt offer
|
|
392
409
|
// (delta review P2-2; lib/quiet-scope.mjs mirrors it).
|
|
393
410
|
if (isAutoAdoptDisabledFor(root)) return { ok: true, action: 'disabled', reason: 'root-disabled' };
|
|
@@ -421,7 +438,11 @@ export function silentAutoAdopt({ cwd, markerDir, markerKey }) {
|
|
|
421
438
|
agentsMd: r.agentsMd,
|
|
422
439
|
};
|
|
423
440
|
}
|
|
424
|
-
|
|
441
|
+
// managed-only loads no project CLAUDE.md, so the hook does not offer /adopt (v6.30.0 pre-tag
|
|
442
|
+
// claims review P2-5; inside a git work tree the reason is local-managed-only).
|
|
443
|
+
return managedOnly
|
|
444
|
+
? { ok: true, action: 'inject', reason: 'managed-only' }
|
|
445
|
+
: { ok: true, action: 'inject' };
|
|
425
446
|
}
|
|
426
447
|
dropLocalSteering(cwd, { atRootOnly: true, keepTracked: true });
|
|
427
448
|
let action = 'already-adopted';
|
|
@@ -462,8 +483,8 @@ function syncAgentsImports(cwd) {
|
|
|
462
483
|
// project below it, each asking to approve an import from outside its directory.
|
|
463
484
|
if (isSharedAncestor(cwd)) return null;
|
|
464
485
|
// The cheap check first: most CLAUDE.md files hold the user's own lines, and the full answer
|
|
465
|
-
// asks git.
|
|
466
|
-
if (!
|
|
486
|
+
// asks git. Another plugin's managed block counts as plugin text, not the user's (D#220).
|
|
487
|
+
if (!holdsOnlyPluginLines(claudeMdPath(cwd), PLUGIN_SLUG)) return null;
|
|
467
488
|
const agents = agentsMdForNewClaudeMd(cwd, PLUGIN_SLUG);
|
|
468
489
|
if (!agents) return null;
|
|
469
490
|
const imported =
|
|
@@ -501,6 +522,16 @@ function blockIn(dir) {
|
|
|
501
522
|
}
|
|
502
523
|
}
|
|
503
524
|
|
|
525
|
+
// The nearest directory above `cwd`, up to and including `root`, whose CLAUDE.md carries the block:
|
|
526
|
+
// the host loads it as an ancestor. `adopt` run in packages/ and a session in packages/web/src got a
|
|
527
|
+
// root CLAUDE.local.md on top of packages/CLAUDE.md, the block twice (D#177(2)).
|
|
528
|
+
function blockAbove(cwd, root) {
|
|
529
|
+
for (let d = dirname(cwd); ; d = dirname(d)) {
|
|
530
|
+
if (blockIn(d)) return d;
|
|
531
|
+
if (samePath(d, root) || dirname(d) === d) return null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
504
535
|
function writeMarker(markerDir, markerKey) {
|
|
505
536
|
if (!existsSync(markerDir)) mkdirSync(markerDir, { recursive: true });
|
|
506
537
|
const path = join(markerDir, `.auto-adopt-${markerKey}`);
|
|
@@ -620,9 +651,11 @@ function cmdEnable(args) {
|
|
|
620
651
|
function localSteeringStatus(cwd) {
|
|
621
652
|
const root = localSteeringRoot(cwd);
|
|
622
653
|
const runs = autoAdoptRuns(cwd, root);
|
|
623
|
-
// CLAUDE.md first: where it carries the block, no local file is written
|
|
624
|
-
//
|
|
625
|
-
if (blockIn(cwd) || (root &&
|
|
654
|
+
// CLAUDE.md first: where it carries the block, no local file is written, in a git work tree or not
|
|
655
|
+
// (pre-tag delta review of the D#212 repairs, D11).
|
|
656
|
+
if (blockIn(cwd) || (root && !samePath(root, cwd) && blockAbove(cwd, root))) {
|
|
657
|
+
if (userInstructionFiles() === 'managed-only')
|
|
658
|
+
return '⚠ CLAUDE.md carries the block, but Project instructions is set to managed-only, which does not load it';
|
|
626
659
|
const cur = root ? readLocalSteering(root, PLUGIN_SLUG) : null;
|
|
627
660
|
if (cur === null || cur.body === null) return '— none: CLAUDE.md carries the block';
|
|
628
661
|
const kept = localFileLinked(root, cur.path)
|
|
@@ -642,7 +675,7 @@ function localSteeringStatus(cwd) {
|
|
|
642
675
|
}
|
|
643
676
|
// Before "not a git work tree": with auto-adopt off nothing is injected either (R3-13).
|
|
644
677
|
if (isAutoAdoptDisabledFor(cwd) || (root && isAutoAdoptDisabledFor(root)))
|
|
645
|
-
return
|
|
678
|
+
return `— none: auto-adopt is off for this project (\`${CLI_INVOKE} adopt --enable\` turns it back on)`;
|
|
646
679
|
if (process.env.MEM_NO_AUTO_ADOPT === '1')
|
|
647
680
|
return '— none: MEM_NO_AUTO_ADOPT=1, so no local file is written';
|
|
648
681
|
if (!root)
|
|
@@ -656,10 +689,10 @@ function localSteeringStatus(cwd) {
|
|
|
656
689
|
const plan = planLocalSteering(root, PLUGIN_SLUG, cwd);
|
|
657
690
|
const beside = plan.agentsMd ?? 'the AGENTS.md';
|
|
658
691
|
if (plan.file === RULES_MD && plan.refusal === 'off')
|
|
659
|
-
return `${removed}; beside ${beside} a file comes back only with CLAUDE_MEM_RULES_STEERING=1 and then
|
|
692
|
+
return `${removed}; beside ${beside} a file comes back only with CLAUDE_MEM_RULES_STEERING=1 and then \`${CLI_INVOKE} adopt --enable\``;
|
|
660
693
|
if (plan.file === RULES_MD && plan.refusal)
|
|
661
694
|
return `${removed}; beside ${beside}, ${rulesWhy(plan.refusal)}`;
|
|
662
|
-
return `${removed}; after
|
|
695
|
+
return `${removed}; after \`${CLI_INVOKE} adopt --enable\` the next session may write it again`;
|
|
663
696
|
}
|
|
664
697
|
const plan = planLocalSteering(root, PLUGIN_SLUG, cwd);
|
|
665
698
|
if (!plan.refusal)
|
|
@@ -671,7 +704,7 @@ function localSteeringStatus(cwd) {
|
|
|
671
704
|
ways.settingGivesFile
|
|
672
705
|
? 'set Project instructions to claude-md-and-agents-md in /config and CLAUDE.local.md is written instead'
|
|
673
706
|
: null,
|
|
674
|
-
ways.adoptImports ?
|
|
707
|
+
ways.adoptImports ? `run \`${CLI_INVOKE} adopt\`, whose CLAUDE.md imports AGENTS.md` : null,
|
|
675
708
|
].filter(Boolean);
|
|
676
709
|
return `✗ not written: Claude Code stops reading ${plan.agentsMd ?? 'the AGENTS.md'} once a CLAUDE.local.md exists, and ${rulesWhy(plan.refusal)} (steering is injected at session start${alt.length ? `; ${alt.join(', or ')}` : ''})`;
|
|
677
710
|
}
|
|
@@ -685,6 +718,9 @@ function currentLocalLine(root, cwd, cur, runs) {
|
|
|
685
718
|
return `✓ ${cur.path} (behind a symbolic link; the plugin leaves it as it is)`;
|
|
686
719
|
if (trackedByGit(root, rel)) return `✓ ${cur.path} (tracked by git; the plugin leaves it as it is)`;
|
|
687
720
|
const plan = runs ? planLocalSteering(root, PLUGIN_SLUG, cwd) : null;
|
|
721
|
+
// Left in place under managed-only (writeLocalSteering, D#219), not removed.
|
|
722
|
+
if (plan?.refusal === 'managed-only')
|
|
723
|
+
return `⚠ ${cur.path} (auto-written, but CLAUDE.local.md ${LOCAL_REFUSAL_TEXT['managed-only']}; the steering is injected, and the file stays for when the setting changes)`;
|
|
688
724
|
if (plan?.refusal) {
|
|
689
725
|
const why =
|
|
690
726
|
plan.file === LOCAL_MD
|
|
@@ -764,7 +800,7 @@ function statusAll() {
|
|
|
764
800
|
`[adopt --status] scanned ${dirs.length} memdir(s): ${legacy} with legacy sentinel (await migration), ${disabled} auto-adopt-disabled.`,
|
|
765
801
|
);
|
|
766
802
|
if (legacy > 0)
|
|
767
|
-
log(
|
|
803
|
+
log(`[adopt --status] run \`${CLI_INVOKE} adopt --all\` to sweep legacy memory-dir sentinels now.`);
|
|
768
804
|
|
|
769
805
|
const known = listKnownProjectDirs();
|
|
770
806
|
let adoptedCount = 0;
|
|
@@ -773,7 +809,7 @@ function statusAll() {
|
|
|
773
809
|
`[adopt --status] known projects (~/.claude.json): ${known.length} scanned, ${adoptedCount} with a CLAUDE.md managed block or partial residue (detail doc/state).`,
|
|
774
810
|
);
|
|
775
811
|
if (adoptedCount > 0)
|
|
776
|
-
log(
|
|
812
|
+
log(`[adopt --status] run \`${CLI_INVOKE} unadopt --all\` to remove every CLAUDE.md block.`);
|
|
777
813
|
|
|
778
814
|
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ? 'set' : 'unset';
|
|
779
815
|
const noAutoAdopt = process.env.MEM_NO_AUTO_ADOPT === '1' ? '1 (opt-out)' : 'unset';
|
|
@@ -783,7 +819,7 @@ function statusAll() {
|
|
|
783
819
|
` CLAUDE_PLUGIN_ROOT = ${pluginRoot} (any install path is consent; gate is the per-project opt-out below)`,
|
|
784
820
|
);
|
|
785
821
|
log(` MEM_NO_AUTO_ADOPT = ${noAutoAdopt} (global escape hatch)`);
|
|
786
|
-
log(
|
|
822
|
+
log(`Per-project opt-out: \`${CLI_INVOKE} adopt --disable\` (run --enable to re-arm).`);
|
|
787
823
|
}
|
|
788
824
|
|
|
789
825
|
/**
|
|
@@ -852,7 +888,7 @@ function unadoptAll(args) {
|
|
|
852
888
|
if (failures > 0) process.exitCode = 1;
|
|
853
889
|
if (projectDirs.length === 0) {
|
|
854
890
|
log(
|
|
855
|
-
|
|
891
|
+
`[unadopt --all] no known projects found in ~/.claude.json — if a project was adopted but never opened in Claude Code, run \`${CLI_INVOKE} unadopt\` from inside it.`,
|
|
856
892
|
);
|
|
857
893
|
}
|
|
858
894
|
}
|
package/adopt-content.mjs
CHANGED
|
@@ -73,7 +73,6 @@ export function getDetailDoc() {
|
|
|
73
73
|
|
|
74
74
|
> 由 \`${CLI} adopt\` 生成、随版本自动刷新;卸载用 \`${CLI} unadopt\`。
|
|
75
75
|
> 精炼触发表由 SessionStart 注入会话上下文(显式 \`${CLI} adopt\` 过的项目则写在 \`CLAUDE.md\` 的 \`claude-mem-lite\` 托管块里);本文件是其展开。
|
|
76
|
-
> 设计背景见 docs/CLAUDE-MD-STEERING-PLAN.md。
|
|
77
76
|
|
|
78
77
|
> **本文下方所有命令写作 \`${CLI} <cmd>\`。** 该名字只在全局装过
|
|
79
78
|
> (\`npm i -g claude-mem-lite\`)时才在 PATH 上;否则用等价的
|
|
@@ -159,7 +158,7 @@ PreToolUse hook 在你 Read / Edit / Write 文件前已自动 \`mem_recall\` 该
|
|
|
159
158
|
|
|
160
159
|
| 场景 | CLI |
|
|
161
160
|
|------|-----|
|
|
162
|
-
| 清理过期记忆 | \`${CLI} maintain scan
|
|
161
|
+
| 清理过期记忆 | \`${CLI} maintain scan\` 预览;清理本身交给用户启动的 \`/claude-mem-lite:update\` |
|
|
163
162
|
| 深度优化(Haiku) | \`${CLI} optimize\`(默认 preview;\`--run\` 执行,\`--task re-enrich,normalize,cluster-merge,smart-compress\`) |
|
|
164
163
|
| 压缩旧条目 | \`${CLI} compress\`(默认 preview;\`--execute\` 执行,\`--age-days N\`) |
|
|
165
164
|
| FTS5 索引检查 / 重建 | \`${CLI} fts-check <check\\|rebuild>\` |
|
package/claudemd.mjs
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// project-tree managed block at <cwd>/CLAUDE.md plus an on-demand detail doc
|
|
3
3
|
// at <cwd>/.claude/plugin_<slug>.md.
|
|
4
4
|
//
|
|
5
|
-
// Why here and not memdir.mjs:
|
|
6
|
-
// memory-dir MEMORY.md
|
|
7
|
-
// canonical home for project instructions) — seeding MEMORY.md just pollutes an
|
|
5
|
+
// Why here and not memdir.mjs: steering belongs in CLAUDE.md (the canonical home
|
|
6
|
+
// for project instructions) — seeding the memory-dir MEMORY.md just pollutes an
|
|
8
7
|
// index meant for the user's own memories. This module mirrors the design
|
|
9
8
|
// shipped by the sibling code-graph-mcp plugin (claude-plugin/scripts/adopt.js)
|
|
10
9
|
// and the oh-my-claudecode versioned `<!-- :begin vN -->` managed-block pattern.
|
|
@@ -182,6 +181,31 @@ export function holdsOnlyOwnLines(p, slug) {
|
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
|
|
184
|
+
// Another plugin's managed block: the same sentinel shape under any slug (code-graph-mcp writes
|
|
185
|
+
// `<!-- code-graph-mcp:begin v2 -->` … `<!-- code-graph-mcp:end -->`). The backreference pairs a
|
|
186
|
+
// begin with its own slug's end, so an unpaired line of one slug never closes another's block.
|
|
187
|
+
const ANY_BLOCK_RE_G =
|
|
188
|
+
/<!-- ([a-z0-9][a-z0-9._-]*):begin v\d+ -->\r?\n(?:(?!<!-- \1:(?:begin|end))[\s\S])*?\r?\n<!-- \1:end -->/g;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Whether the instructions file at `p` holds no line a person wrote: this plugin's blocks and
|
|
192
|
+
* imports, and other plugins' managed blocks. For "did plugin text alone switch AGENTS.md off"
|
|
193
|
+
* (D#220), never for deleting the file — another plugin's block is not ours to remove, and
|
|
194
|
+
* holdsOnlyOwnLines answers that question. False for a missing file.
|
|
195
|
+
* @param {string} p absolute path
|
|
196
|
+
* @param {string} slug
|
|
197
|
+
* @returns {boolean}
|
|
198
|
+
*/
|
|
199
|
+
export function holdsOnlyPluginLines(p, slug) {
|
|
200
|
+
try {
|
|
201
|
+
if (!existsSync(p)) return false;
|
|
202
|
+
const rest = readFileSync(p, 'utf8').replace(blockRegexG(slug), '').replace(ANY_BLOCK_RE_G, '');
|
|
203
|
+
return rest.trim() === '' || isOwnImportsOnly(rest, slug);
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
185
209
|
/**
|
|
186
210
|
* Put adopt's AGENTS.md imports at the top of <cwd>/CLAUDE.md (created if absent), unless the
|
|
187
211
|
* marker is there already. `imports` are paths relative to `cwd` (`AGENTS.md`,
|
|
@@ -366,7 +390,8 @@ export function needsRefresh(cwd, { slug, version, block, doc }) {
|
|
|
366
390
|
* CLAUDE.md is created if absent. Only our slug-scoped region is rewritten;
|
|
367
391
|
* everything else in the file is preserved verbatim.
|
|
368
392
|
*
|
|
369
|
-
* @returns {{action: 'created'|'updated'|'unchanged'}}
|
|
393
|
+
* @returns {{action: 'created'|'updated'|'unchanged', docAction: 'created'|'updated'|'unchanged'}}
|
|
394
|
+
* (block disposition, and the detail doc's)
|
|
370
395
|
*/
|
|
371
396
|
export function writeManaged(cwd, { slug, version, block, doc }) {
|
|
372
397
|
const { action } = writeBlockAt(claudeMdPath(cwd), { slug, version, block });
|
|
@@ -379,6 +404,7 @@ export function writeManaged(cwd, { slug, version, block, doc }) {
|
|
|
379
404
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
380
405
|
const existingDoc = existsSync(dp) ? readFileSync(dp, 'utf8') : null;
|
|
381
406
|
if (existingDoc !== docContent) atomicWrite(dp, docContent);
|
|
407
|
+
const docAction = existingDoc === null ? 'created' : existingDoc === docContent ? 'unchanged' : 'updated';
|
|
382
408
|
|
|
383
409
|
// Only when what the sidecar records changed: it is committed alongside the block in a team
|
|
384
410
|
// repo, and a fresh writtenAt on every re-run of `adopt` left a diff each time (E2E
|
|
@@ -394,7 +420,7 @@ export function writeManaged(cwd, { slug, version, block, doc }) {
|
|
|
394
420
|
if (!(prev?.version === version && prev?.blockHash === blockHash && prev?.docHash === docHash)) {
|
|
395
421
|
writeState(cwd, slug, { version, blockHash, docHash, writtenAt: new Date().toISOString() });
|
|
396
422
|
}
|
|
397
|
-
return { action };
|
|
423
|
+
return { action, docAction };
|
|
398
424
|
}
|
|
399
425
|
|
|
400
426
|
/**
|
package/cli/activity.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { parseArgs, out, fail, rejectBareStringFlags } from './common.mjs';
|
|
|
15
15
|
import { parseIntFlag, isNumericToken } from '../lib/cli-flags.mjs';
|
|
16
16
|
import { snapshotDb } from '../lib/db-backup.mjs';
|
|
17
17
|
import { isBlankText } from '../lib/blank-text.mjs';
|
|
18
|
+
import { CLI_INVOKE } from '../cli-path.mjs';
|
|
18
19
|
|
|
19
20
|
function formatActivityResults(rows) {
|
|
20
21
|
if (!rows || rows.length === 0) return '(no events)';
|
|
@@ -31,7 +32,7 @@ function eventIdOf(token) {
|
|
|
31
32
|
export async function cmdActivity(db, args) {
|
|
32
33
|
const sub = args[0];
|
|
33
34
|
if (!sub) {
|
|
34
|
-
fail(
|
|
35
|
+
fail(`[mem] Usage: ${CLI_INVOKE} activity <save|search|recent|show|delete|promote> ...`);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -171,7 +172,7 @@ export async function cmdActivity(db, args) {
|
|
|
171
172
|
// This command lets users prune them by ID without dropping to raw SQL.
|
|
172
173
|
const idStr = positional.join(',').trim();
|
|
173
174
|
if (!idStr) {
|
|
174
|
-
fail(
|
|
175
|
+
fail(`[mem] Usage: ${CLI_INVOKE} activity delete <id1,id2,...> [--confirm]`);
|
|
175
176
|
return;
|
|
176
177
|
}
|
|
177
178
|
const tokens = idStr
|
package/cli/common.mjs
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// consistent.
|
|
10
10
|
|
|
11
11
|
import { editDistance } from '../lib/edit-distance.mjs';
|
|
12
|
-
import { neutralizeContextDelimiters, neutralizeSkillDelimiters } from '../format-utils.mjs';
|
|
12
|
+
import { neutralizeContextDelimiters, neutralizeSkillDelimiters, truncate } from '../format-utils.mjs';
|
|
13
|
+
import { CLI_INVOKE } from '../cli-path.mjs';
|
|
13
14
|
|
|
14
15
|
// ─── Argument Parsing ────────────────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -250,7 +251,7 @@ export function inertFilterFlagNotice(cmd, inert) {
|
|
|
250
251
|
const verb = inert.length > 1 ? 'were' : 'was';
|
|
251
252
|
return (
|
|
252
253
|
`[mem] ${names} ${verb} ignored — \`${cmd}\` does not filter on ${inert.length > 1 ? 'them' : 'it'}, ` +
|
|
253
|
-
`so the results above are UNFILTERED. Run "
|
|
254
|
+
`so the results above are UNFILTERED. Run "${CLI_INVOKE} help" for the flags this command reads.`
|
|
254
255
|
);
|
|
255
256
|
}
|
|
256
257
|
|
|
@@ -596,11 +597,16 @@ export function obsFieldLabel(field) {
|
|
|
596
597
|
* 2026-08-14 A4). Say what the rows are and what deletes them — and say nothing about
|
|
597
598
|
* compression, which is a different sentinel with a different lifecycle.
|
|
598
599
|
*
|
|
600
|
+
* purge_stale deletes only the queued rows older than its retain window (D#450), so the line
|
|
601
|
+
* also gives the number a purge at the default window deletes now.
|
|
602
|
+
*
|
|
599
603
|
* @param {number} n stats.pendingPurge
|
|
604
|
+
* @param {number} due stats.pendingPurgeDue
|
|
605
|
+
* @param {number} retainDays the default retain window `due` was counted at
|
|
600
606
|
* @returns {string} the full indented line, identical on both surfaces.
|
|
601
607
|
*/
|
|
602
|
-
export function formatPendingPurgeLine(n) {
|
|
603
|
-
return ` Pending purge (idle-marked): ${n} (rows maintenance queued: hidden and still idle 7 days later, or queued by an earlier version — purge_stale deletes
|
|
608
|
+
export function formatPendingPurgeLine(n, due, retainDays) {
|
|
609
|
+
return ` Pending purge (idle-marked): ${n} (rows maintenance queued: hidden and still idle 7 days later, or queued by an earlier version — purge_stale deletes those older than its retain window: ${due} at the default ${retainDays} days)`;
|
|
604
610
|
}
|
|
605
611
|
|
|
606
612
|
/**
|
|
@@ -642,3 +648,28 @@ export function formatProbeHints(probe) {
|
|
|
642
648
|
if (probe.event?.length > 0) hints.push(`E#${probe.event.join(', E#')} (event)`);
|
|
643
649
|
return hints;
|
|
644
650
|
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The line under one side of a scan's Needs-review pair (D#461), from reviewRows: type, project,
|
|
654
|
+
* date, the lesson or that there is none, and the start of the narrative — enough to tell two rows
|
|
655
|
+
* with near-identical titles apart without `get`, which counts as an access.
|
|
656
|
+
* @param {{id: number, type?: string, project?: string, created_at_epoch?: number,
|
|
657
|
+
* narrative?: string|null, lesson_learned?: string|null}|undefined} row
|
|
658
|
+
* @returns {string}
|
|
659
|
+
*/
|
|
660
|
+
export function formatReviewSide(row) {
|
|
661
|
+
if (!row) return '';
|
|
662
|
+
const flat = (v, n) =>
|
|
663
|
+
truncate(
|
|
664
|
+
String(v ?? '')
|
|
665
|
+
.replace(/\s+/g, ' ')
|
|
666
|
+
.trim(),
|
|
667
|
+
n,
|
|
668
|
+
);
|
|
669
|
+
const lesson =
|
|
670
|
+
row.lesson_learned && row.lesson_learned !== 'none'
|
|
671
|
+
? `lesson: ${flat(row.lesson_learned, 100)}`
|
|
672
|
+
: 'no lesson';
|
|
673
|
+
const date = row.created_at_epoch ? new Date(row.created_at_epoch).toISOString().slice(0, 10) : '?';
|
|
674
|
+
return `#${row.id} ${row.type || '?'} · ${row.project} · ${date} · ${lesson} · ${flat(row.narrative, 120) || '(no narrative)'}`;
|
|
675
|
+
}
|
package/cli/fts-check.mjs
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
import { checkFTSIntegrity, rebuildFTS } from '../schema.mjs';
|
|
5
5
|
import { parseArgs, out, fail } from './common.mjs';
|
|
6
|
+
import { CLI_INVOKE } from '../cli-path.mjs';
|
|
6
7
|
|
|
7
8
|
export function cmdFtsCheck(db, args) {
|
|
8
9
|
const { positional } = parseArgs(args);
|
|
9
10
|
const action = positional[0];
|
|
10
11
|
if (!action) {
|
|
11
|
-
fail(
|
|
12
|
+
fail(`[mem] Usage: ${CLI_INVOKE} fts-check <check|rebuild>`);
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
if (!['check', 'rebuild'].includes(action)) {
|
package/cli/verify-apply.mjs
CHANGED
|
@@ -23,12 +23,12 @@ import {
|
|
|
23
23
|
markUndone,
|
|
24
24
|
} from '../lib/verify-apply-core.mjs';
|
|
25
25
|
import { parseArgs, out, fail, rejectBareStringFlags } from './common.mjs';
|
|
26
|
-
import { shellWord } from '../cli-path.mjs';
|
|
26
|
+
import { shellWord, CLI_INVOKE } from '../cli-path.mjs';
|
|
27
27
|
|
|
28
28
|
const USAGE =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
`[mem] Usage: ${CLI_INVOKE} verify-apply <proposals.json> [--project P] (dry run)\n` +
|
|
30
|
+
` ${CLI_INVOKE} verify-apply <proposals.json> [--project P] --apply --digest <d>\n` +
|
|
31
|
+
` ${CLI_INVOKE} verify-apply --undo <backup.json>`;
|
|
32
32
|
|
|
33
33
|
const SNIPPET_CONTEXT = 60;
|
|
34
34
|
const BACKUP_DIR = join(DB_DIR, 'backups');
|
package/cli.mjs
CHANGED
|
@@ -338,8 +338,12 @@ if (cmd === '--version' || cmd === '-v' || cmd === '-V' || cmd === 'version') {
|
|
|
338
338
|
if (best && bestDist <= 2) {
|
|
339
339
|
process.stderr.write(`[mem] Did you mean: ${best}?\n`);
|
|
340
340
|
} else {
|
|
341
|
+
// By path (D#462: the bare name is on PATH only after a global npm install). Imported here,
|
|
342
|
+
// not at the top: this entry takes no static local imports (D#26), and a module it cannot
|
|
343
|
+
// load leaves the old wording rather than no hint.
|
|
344
|
+
const { CLI_INVOKE } = await import('./cli-path.mjs').catch(() => ({ CLI_INVOKE: 'claude-mem-lite' }));
|
|
341
345
|
process.stderr.write(
|
|
342
|
-
|
|
346
|
+
`[mem] Run "${CLI_INVOKE} help" for CLI commands or "${CLI_INVOKE} install" for setup\n`,
|
|
343
347
|
);
|
|
344
348
|
}
|
|
345
349
|
process.exitCode = 1;
|