claude-mem-lite 6.13.5 → 6.14.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 +28 -2
- package/README.zh-CN.md +22 -2
- package/bash-utils.mjs +127 -27
- package/cli/doctor.mjs +1 -1
- package/hook-context.mjs +2 -2
- package/hook-episode.mjs +2 -2
- package/hook-handoff.mjs +2 -2
- package/hook-llm.mjs +128 -24
- package/hook-optimize.mjs +7 -10
- package/hook.mjs +182 -49
- package/hooks/hooks.json +10 -0
- package/install.mjs +18 -2
- package/lib/activity.mjs +5 -3
- package/lib/bash-file-targets.mjs +897 -0
- package/lib/browse-core.mjs +1 -1
- package/lib/cite-back-hint.mjs +4 -5
- package/lib/compress-core.mjs +1 -1
- package/lib/deferred-work.mjs +4 -4
- package/lib/doctor-drift.mjs +1 -0
- package/lib/episode-input-filter.mjs +428 -0
- package/lib/error-recall-gate.mjs +128 -0
- package/lib/events-injection.mjs +1 -1
- package/lib/fast-summary.mjs +87 -61
- package/lib/get-core.mjs +4 -4
- package/lib/hook-prune.mjs +3 -1
- package/lib/maintain-core.mjs +1 -1
- package/lib/recent-core.mjs +1 -1
- package/lib/timeline-core.mjs +11 -9
- package/mem-cli.mjs +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +6 -1
- package/scripts/post-tool-use.sh +26 -0
- package/scripts/pre-tool-recall-bash.sh +102 -0
- package/scripts/pre-tool-recall.js +56 -1
- package/scripts/user-prompt-search.js +1 -1
- package/server.mjs +3 -1
- package/source-files.mjs +11 -0
- package/utils.mjs +26 -4
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.
|
|
12
|
+
"version": "6.14.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. Hybrid FTS5 + TF-IDF 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.14.0",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF 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
|
@@ -138,7 +138,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
138
138
|
|----------|--------|-------|
|
|
139
139
|
| **Linux** | Supported | Primary development and testing platform; the whole CI matrix runs here |
|
|
140
140
|
| **macOS** | Supported | Fully compatible (Intel and Apple Silicon) |
|
|
141
|
-
| **Windows** | Installs, not CI-covered | The MCP server, the CLI and the `node` hooks work (`better-sqlite3` ships `win32-x64` and `win32-arm64` prebuilds, so nothing is compiled). **
|
|
141
|
+
| **Windows** | Installs, not CI-covered | The MCP server, the CLI and the `node` hooks work (`better-sqlite3` ships `win32-x64` and `win32-arm64` prebuilds, so nothing is compiled). **Four hook commands run under `bash`** — `setup.sh`, `post-tool-use.sh`, `pre-agent-inject.sh`, `pre-tool-recall-bash.sh` — and need Git for Windows or WSL on `PATH`; `claude-mem-lite doctor` reports it when `bash` cannot be found. No GitHub Actions runner exercises Windows, so this rests on user reports ([#28](https://github.com/sdsrss/claude-mem-lite/issues/28)), not on a green pipeline |
|
|
142
142
|
| **WSL2** | Untested | Linux under the hood, so it should behave as the Linux row; nobody has reported either way |
|
|
143
143
|
|
|
144
144
|
From v5.1.0 through v6.1.0, `package.json` declared `os: ["darwin", "linux"]`. That is an npm *install*
|
|
@@ -152,7 +152,7 @@ is still outside it gets a message naming both sides of the mismatch instead of
|
|
|
152
152
|
- **Node.js** >= 22
|
|
153
153
|
- **Claude Code** CLI installed and configured (`claude` command available)
|
|
154
154
|
- **SQLite3** support (provided by `better-sqlite3` 13, which ships prebuilt binaries for 8 platforms — no compiler needed on any of them; a platform it has no prebuild for falls back to building from source)
|
|
155
|
-
- **Platform**: Linux or macOS; Windows installs and runs but is not CI-covered and needs Git Bash or WSL for
|
|
155
|
+
- **Platform**: Linux or macOS; Windows installs and runs but is not CI-covered and needs Git Bash or WSL for four hooks (see [Platform Support](#platform-support))
|
|
156
156
|
|
|
157
157
|
## Installation
|
|
158
158
|
|
|
@@ -237,6 +237,29 @@ 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.14.0
|
|
241
|
+
|
|
242
|
+
**Five defaults change; three have an off switch.** No schema change and no migration: an
|
|
243
|
+
older build still opens the database, so reverting everything is pinning
|
|
244
|
+
`claude-mem-lite@6.13.6`.
|
|
245
|
+
|
|
246
|
+
- **File recall now also fires before Bash commands that view or write a file** (`cat`,
|
|
247
|
+
`sed -n`, `head`; `sed -i`, `cat > f`, a python patch) — on recent models most reads and
|
|
248
|
+
edits go through Bash, and the recall face with the highest measured cite rate had almost
|
|
249
|
+
stopped firing. A **fourth hook command runs under `bash`**, `pre-tool-recall-bash.sh`; on
|
|
250
|
+
Windows it needs Git Bash or WSL like the other three. Off: `CLAUDE_MEM_BASH_RECALL=off`.
|
|
251
|
+
- **Error recall stays quiet on a failure you meant to cause** (running a test file you just
|
|
252
|
+
wrote or edited) and on commands that only print data and exit 0. No switch; pin 6.13.6 to
|
|
253
|
+
revert. `error_recall` counts in `citation-stats` drop from here on — do not compare them
|
|
254
|
+
across this version.
|
|
255
|
+
- **Auto-captured lessons must quote what happened.** The episode summarizer ignores
|
|
256
|
+
mutation-test probes, the agent's own failing inline scripts and subagent calls that do not
|
|
257
|
+
edit the project, and a lesson that quotes nothing from its window is dropped (the event is
|
|
258
|
+
kept, at importance 1). Off: `CLAUDE_MEM_EPISODE_INPUT_FILTER=off` /
|
|
259
|
+
`CLAUDE_MEM_LESSON_GROUNDING=off`.
|
|
260
|
+
- **The `[mem] episode flushed: N entries` line is no longer injected.** The hints that
|
|
261
|
+
followed it are unchanged.
|
|
262
|
+
|
|
240
263
|
## Upgrading to 6.13.0
|
|
241
264
|
|
|
242
265
|
**One default changes: SessionStart no longer injects `### Key Events`.** That section listed
|
|
@@ -916,6 +939,9 @@ claude-mem-lite.
|
|
|
916
939
|
| `CLAUDE_MEM_DEBUG` | Enable debug logging (`1` to enable). | _(disabled)_ |
|
|
917
940
|
| `MEM_QUIET_HOOKS` | Low-noise hooks. `1` drops the `File Lessons` / `Key Context` sections from SessionStart injection, the lesson suffix from `[mem] Related memories`, and the `WHEN TO USE` / `Decision rules` blocks from MCP server instructions. IDs and the `Recent` table still surface so `mem_get(ids=[…])` remains reachable. Intended for users running the invited-memory adopt path or who otherwise want minimal auto-injection. **Since v2.82.0 this env no longer gates auto-adopt — use `MEM_NO_AUTO_ADOPT=1` for that.** | _(disabled)_ |
|
|
918
941
|
| `CLAUDE_MEM_SESSION_EVENTS` | `1`/`on` restores the SessionStart `### Key Events` section (recent high-importance rows from the `events` table). **Off by default since v6.13.0**: an audit of 30 events read 2 accurate and 16 wrong. The UserPromptSubmit events block and PreToolUse recall are query-matched and stay on; `mem_search` still reaches every event. | _(off)_ |
|
|
942
|
+
| `CLAUDE_MEM_EPISODE_INPUT_FILTER` | What the episode summarizer may learn from. A subagent's tool calls stay out of the episode buffer unless they edit a file inside the project, and mutation probes (mutate → RED run → restore) and the agent's own failing inline scripts (a patch's `anchor not found`) are dropped before a window is saved or summarized — replayed over the 30 audited events, this removes 3 of the 16 that were wrong outright; with `CLAUDE_MEM_LESSON_GROUNDING` none of the 16 lessons is injected. `off` restores the unfiltered input. | _(on)_ |
|
|
943
|
+
| `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)_ |
|
|
944
|
+
| `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)_ |
|
|
919
945
|
| `MEM_NO_AUTO_ADOPT` | Global opt-out for auto-adopt (v2.82.0+). `1` prevents the per-SessionStart auto-write of the `CLAUDE.md` managed block across **all** projects. For per-project opt-out use `claude-mem-lite adopt --disable` instead (writes a durable `<memdir>/.mem-no-auto-adopt` sentinel that survives marker deletion). | _(disabled)_ |
|
|
920
946
|
| `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)_ |
|
|
921
947
|
|
package/README.zh-CN.md
CHANGED
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
|------|------|------|
|
|
104
104
|
| **Linux** | 支持 | 主要开发和测试平台;整个 CI 矩阵都跑在这里 |
|
|
105
105
|
| **macOS** | 支持 | 完全兼容(Intel 和 Apple Silicon) |
|
|
106
|
-
| **Windows** | 可安装,但无 CI 覆盖 | MCP server、CLI 和 `node` 类 hook 均可用(`better-sqlite3` 自带 `win32-x64` / `win32-arm64`
|
|
106
|
+
| **Windows** | 可安装,但无 CI 覆盖 | MCP server、CLI 和 `node` 类 hook 均可用(`better-sqlite3` 自带 `win32-x64` / `win32-arm64` 预编译产物,无需编译)。**有四个 hook 命令走 `bash`** —— `setup.sh`、`post-tool-use.sh`、`pre-agent-inject.sh`、`pre-tool-recall-bash.sh` —— 需要 PATH 上有 Git for Windows 或 WSL;`bash` 找不到时 `claude-mem-lite doctor` 会报出来。GitHub Actions 没有 Windows runner,所以这一行依据的是用户报告([#28](https://github.com/sdsrss/claude-mem-lite/issues/28))而不是绿色流水线 |
|
|
107
107
|
| **WSL2** | 未测试 | 底层就是 Linux,预期与 Linux 行一致;但无人报告过实际结果 |
|
|
108
108
|
|
|
109
109
|
v5.1.0 到 v6.1.0 之间,`package.json` 声明的是 `os: ["darwin", "linux"]`。那是 npm 的**安装**门禁,不是运行时检查:
|
|
@@ -116,7 +116,7 @@ v5.1.0 到 v6.1.0 之间,`package.json` 声明的是 `os: ["darwin", "linux"]`
|
|
|
116
116
|
- **Node.js** >= 22(v4.0.0 起:better-sqlite3 13 要求 >=22,Node 20 已于 2026-04 EOL;`package.json` 的 `engines` 是唯一事实来源)
|
|
117
117
|
- **Claude Code** CLI 已安装并配置(`claude` 命令可用)
|
|
118
118
|
- **SQLite3** 支持(由 `better-sqlite3` 13 提供,它自带 8 个平台的预编译产物,这些平台上都不需要编译器;没有对应预编译产物的平台才会回退到源码编译)
|
|
119
|
-
- **平台**:Linux 或 macOS;Windows 可安装运行,但无 CI
|
|
119
|
+
- **平台**:Linux 或 macOS;Windows 可安装运行,但无 CI 覆盖,且四个 hook 需要 Git Bash 或 WSL(参见[平台支持](#平台支持))
|
|
120
120
|
|
|
121
121
|
## 安装
|
|
122
122
|
|
|
@@ -199,6 +199,23 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
|
|
|
199
199
|
repos/ # 浅克隆的源代码仓库
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
## 升级到 6.14.0
|
|
203
|
+
|
|
204
|
+
**五个默认行为变化,其中三个有关闭开关。** 没有 schema 变更、没有迁移:旧版本仍能打开数据库,
|
|
205
|
+
全部回退就是固定到 `claude-mem-lite@6.13.6`。
|
|
206
|
+
|
|
207
|
+
- **查看或写入文件的 Bash 命令执行前,也会做文件召回**(`cat`、`sed -n`、`head`;`sed -i`、
|
|
208
|
+
`cat > f`、python 补丁)。新模型的读写大多走 Bash,实测引用率最高的召回面几乎不再触发。
|
|
209
|
+
**第四个 hook 命令走 `bash`**:`pre-tool-recall-bash.sh`;在 Windows 上与另外三个一样需要
|
|
210
|
+
Git Bash 或 WSL。关闭:`CLAUDE_MEM_BASH_RECALL=off`。
|
|
211
|
+
- **error recall 不再回应你故意制造的失败**(刚写好或刚改过的测试文件跑红),也不回应只打印数据、
|
|
212
|
+
退出码为 0 的命令。没有开关,回退请固定 6.13.6。从这个版本起 `citation-stats` 里的
|
|
213
|
+
`error_recall` 计数会下降,不要跨版本直接比较。
|
|
214
|
+
- **自动捕获的教训必须引用原文。** episode 摘要器忽略变异测试探针、agent 自己失败的内联脚本,以及
|
|
215
|
+
不修改项目的子代理调用;不引用窗口内任何原文的教训会被丢弃(event 保留,importance 降为 1)。
|
|
216
|
+
关闭:`CLAUDE_MEM_EPISODE_INPUT_FILTER=off` / `CLAUDE_MEM_LESSON_GROUNDING=off`。
|
|
217
|
+
- **不再注入 `[mem] episode flushed: N entries` 这一行。** 它后面的提示不变。
|
|
218
|
+
|
|
202
219
|
## 升级到 6.13.0
|
|
203
220
|
|
|
204
221
|
**只有一个默认行为变化:SessionStart 不再注入 `### Key Events`。** 这一节在每个会话开头列出
|
|
@@ -702,6 +719,9 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
|
|
|
702
719
|
| `CLAUDE_MEM_DEBUG` | 启用调试日志(设为 `1` 启用)。 | _(禁用)_ |
|
|
703
720
|
| `MEM_QUIET_HOOKS` | 低噪声 hook。设为 `1` 时,SessionStart 注入去掉 `File Lessons` / `Key Context` 两节,`[mem] Related memories` 去掉 lesson 后缀,MCP server instructions 去掉 `WHEN TO USE` / `Decision rules` 两段。ID 与 `Recent` 表仍保留,`mem_get(ids=[…])` 可继续展开细节。适用于启用了 invited-memory adopt 流程或偏好最小化自动注入的用户。**v2.82.0 起此 env 不再阻挡 auto-adopt——如需关闭 auto-adopt 用 `MEM_NO_AUTO_ADOPT=1`。** | _(禁用)_ |
|
|
704
721
|
| `CLAUDE_MEM_SESSION_EVENTS` | 设为 `1`/`on` 时恢复 SessionStart 的 `### Key Events` 一节(`events` 表中最近的高重要度条目)。**v6.13.0 起默认关闭**:对 30 条 event 的核验只有 2 条属实、16 条错误。UserPromptSubmit 的 events 块与 PreToolUse 召回按查询匹配,保持开启;`mem_search` 仍可检索全部 event。 | _(关闭)_ |
|
|
722
|
+
| `CLAUDE_MEM_EPISODE_INPUT_FILTER` | 决定 episode 摘要器能从哪些输入里学。子代理的工具调用不进入 episode 缓冲(修改项目内文件的除外);变异探针(改文件 → 跑红 → 还原)和 agent 自己失败的内联脚本(补丁的 `anchor not found`)在保存或摘要前被剔除——在 30 条已核验 event 上重放,这一项直接去掉 16 条错误中的 3 条;配合 `CLAUDE_MEM_LESSON_GROUNDING`,16 条错误教训一条都不会被注入。设为 `off` 恢复未过滤的输入。 | _(开启)_ |
|
|
723
|
+
| `CLAUDE_MEM_BASH_RECALL` | 在查看(`cat`、`sed -n`、`head`…)或写入(`sed -i`、`cat > f`、python 补丁…)文件的 Bash 命令执行前做文件召回,与 Read / Edit 召回相同。bash 预过滤让其他命令不启动 Node。设为 `off` 只关闭这一路。 | _(开启)_ |
|
|
724
|
+
| `CLAUDE_MEM_LESSON_GROUNDING` | 自动捕获的 event 只有在教训引用了本窗口自己的诊断文字(失败输出行、编辑新增的注释或提交信息)时才保留教训;否则保留这一行但去掉教训,importance 降为 1,低于所有注入面的门槛。设为 `off` 保留未引用原文的教训。 | _(开启)_ |
|
|
705
725
|
| `MEM_NO_AUTO_ADOPT` | auto-adopt 全局关闭开关(v2.82.0+)。设为 `1` 阻止每次 SessionStart 在**所有**项目自动写入 `CLAUDE.md` 托管块。项目级关闭走 `claude-mem-lite adopt --disable`(写 `<memdir>/.mem-no-auto-adopt` 哨兵,存活于 marker 删除)。 | _(禁用)_ |
|
|
706
726
|
| `MEM_NO_ADOPT_HINT` | 静音当前项目未 adopt 时 SessionStart 追加的那一行 "Invited-memory 未启用…" 提示。v2.82.1 起任何安装路径每次 SessionStart 都自动 adopt,所以该提示一般只在你显式 opt out(`MEM_NO_AUTO_ADOPT=1` 或 `claude-mem-lite adopt --disable`)的项目才会出现。 | _(禁用)_ |
|
|
707
727
|
|
package/bash-utils.mjs
CHANGED
|
@@ -9,6 +9,8 @@ import { basename } from 'path';
|
|
|
9
9
|
// import this file. Cold-start scripts are unaffected — scripts/pre-tool-recall.js
|
|
10
10
|
// deliberately imports nothing from the utils.mjs barrel that re-exports this.
|
|
11
11
|
import { toolEditPath } from './lib/file-edge-match.mjs';
|
|
12
|
+
import { bashFileTargets, isTransientPath, isScratchCommandPath } from './lib/bash-file-targets.mjs';
|
|
13
|
+
export { isTransientPath };
|
|
12
14
|
|
|
13
15
|
// Read/search commands whose output legitimately contains "error"-like keywords without
|
|
14
16
|
// being a failure. Matched against the PRIMARY command (see isReadOnlyCommand).
|
|
@@ -522,11 +524,89 @@ function commandKind(cmd, depth = 0) {
|
|
|
522
524
|
return sawRead ? 'read' : 'neutral';
|
|
523
525
|
}
|
|
524
526
|
|
|
527
|
+
// ─── Data printers and writers (error-recall N2) ─────────────────────────────
|
|
528
|
+
|
|
529
|
+
// Interpreters whose INLINE program (`-e`/`-c`/a heredoc on stdin) is the agent's own
|
|
530
|
+
// throwaway code. Run by a script FILE they are a program like any other.
|
|
531
|
+
const INLINE_INTERPRETER_RE = /^(?:node|nodejs|bun|python(?:\d+(?:\.\d+)?)?)$/;
|
|
532
|
+
|
|
533
|
+
// A redirection operator token (`>`, `2>>`, `&>`, `<`, `<<-`), optionally glued to its target.
|
|
534
|
+
const REDIRECT_TOKEN_RE = /^(?:\d*|&)(?:>>?\|?|<<?-?)(.*)$/;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* True when one simple command runs an INLINE program or prints a CI log — the shapes
|
|
538
|
+
* whose output at exit 0 is data (a transcript line, a log excerpt, a script's own
|
|
539
|
+
* `assert`), not a failure of anything the corpus could explain.
|
|
540
|
+
*/
|
|
541
|
+
function isPrinterElement(text) {
|
|
542
|
+
const toks = shellWords(text);
|
|
543
|
+
let i = 0;
|
|
544
|
+
while (i < toks.length && (/^\w+=/.test(toks[i]) || CMD_WRAPPERS.has(toks[i]))) i++;
|
|
545
|
+
const prog = toks[i];
|
|
546
|
+
if (!prog) return false;
|
|
547
|
+
const args = toks.slice(i + 1);
|
|
548
|
+
// `gh run view … --log[-failed]`: a CI log is somebody else's failure, reprinted. `--json`
|
|
549
|
+
// is gh's structured READ (list/view/status only), typically feeding a run id to the former.
|
|
550
|
+
if (prog === 'gh')
|
|
551
|
+
return args.some((a) => a === '--log' || a === '--log-failed' || /^--json(?:=|$)/.test(a));
|
|
552
|
+
if (!INLINE_INTERPRETER_RE.test(prog)) return false;
|
|
553
|
+
const py = prog.startsWith('python');
|
|
554
|
+
for (let k = 0; k < args.length; k++) {
|
|
555
|
+
const a = args[k];
|
|
556
|
+
if (py ? a === '-c' || a === '-' : /^-(?:e|p|pe|ep)$|^--(?:eval|print)(?:=|$)/.test(a)) return true;
|
|
557
|
+
const redirect = REDIRECT_TOKEN_RE.exec(a);
|
|
558
|
+
if (redirect) {
|
|
559
|
+
if (!redirect[1]) k++; // `<< EOF` / `> f`: the next word is the operand, not a script
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
// A positional is a script file (or `-m`'s module): a program, not an inline print.
|
|
563
|
+
if (!a.startsWith('-') || (py && a === '-m')) return false;
|
|
564
|
+
}
|
|
565
|
+
// No positional at all: the program comes from stdin, i.e. a heredoc.
|
|
566
|
+
return /<</.test(text);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** 'print' | 'read' | 'neutral' | 'other' — commandKind with printers admitted. */
|
|
570
|
+
function printKind(cmd, depth = 0) {
|
|
571
|
+
if (depth > MAX_SUBST_DEPTH) return 'other';
|
|
572
|
+
const parsed = splitStatements(cmd);
|
|
573
|
+
if (!parsed) return 'other';
|
|
574
|
+
let sawPrint = false;
|
|
575
|
+
for (const body of parsed.subs) {
|
|
576
|
+
const kind = printKind(body, depth + 1);
|
|
577
|
+
if (kind === 'other') return 'other';
|
|
578
|
+
if (kind === 'print') sawPrint = true;
|
|
579
|
+
}
|
|
580
|
+
for (const pipeline of parsed.statements) {
|
|
581
|
+
for (const element of pipeline) {
|
|
582
|
+
if (isPrinterElement(element)) {
|
|
583
|
+
sawPrint = true;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (classifySimpleCommand(element) === 'other') return 'other';
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return sawPrint ? 'print' : 'read';
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* True when the command's only programs are inline scripts (`node -e/-p`, `python3 -c`,
|
|
594
|
+
* `python3 - <<EOF`, a heredoc-fed interpreter) or `gh … --log[-failed]`, plus reads
|
|
595
|
+
* (isReadOnlyCommand's verbs) and neutral set-up. Parsed with the same statement/pipeline
|
|
596
|
+
* splitter, so `node -e '…'; npm test` and `python3 - <<PY … PY` followed by a vitest run
|
|
597
|
+
* are NOT printers: the real run is judged on its own.
|
|
598
|
+
*
|
|
599
|
+
* Deliberately a SEPARATE predicate from isReadOnlyCommand, not a new verb in it: that one
|
|
600
|
+
* also decides `isError` for the episode narrative and the bugfix save-nudge, and an inline
|
|
601
|
+
* script that crashes is a real error there. Only error-recall's injection is narrowed.
|
|
602
|
+
*/
|
|
603
|
+
export function isDataPrintingCommand(cmd) {
|
|
604
|
+
if (typeof cmd !== 'string' || !cmd) return false;
|
|
605
|
+
return printKind(stripNonCommands(cmd)) === 'print';
|
|
606
|
+
}
|
|
607
|
+
|
|
525
608
|
// Paths excluded from observation capture (ephemeral / virtual filesystems) — applied
|
|
526
609
|
// uniformly to both command-parsed paths and direct file_path/path/filePath fields.
|
|
527
|
-
function isExcludedPath(p) {
|
|
528
|
-
return p.startsWith('/dev/') || p.startsWith('/proc/') || p.startsWith('/tmp/');
|
|
529
|
-
}
|
|
530
610
|
|
|
531
611
|
/**
|
|
532
612
|
* Detect significance signals in a Bash command and its response.
|
|
@@ -887,16 +967,24 @@ export function planErrorRecall(cmd, response) {
|
|
|
887
967
|
// ─── File Paths ──────────────────────────────────────────────────────────────
|
|
888
968
|
|
|
889
969
|
/**
|
|
890
|
-
*
|
|
891
|
-
*
|
|
970
|
+
* Files a tool call touched, split so callers can tell an edit from a read.
|
|
971
|
+
* `files`: every file edge (direct path fields + all Bash targets);
|
|
972
|
+
* `writes`: the subset a Bash command WROTE (`sed -i`, `cat > f`, a python patch, …) —
|
|
973
|
+
* the Bash counterpart of an Edit/Write `file_path`, consumed via `entryEditedFiles`.
|
|
892
974
|
* @param {object} input Tool input object
|
|
893
|
-
* @
|
|
975
|
+
* @param {{cwd?: string|null, projectDir?: string|null}} [opts] the hook's `cwd`, to resolve
|
|
976
|
+
* relative Bash paths; the project root, whose files count even when it lives under /tmp
|
|
977
|
+
* @returns {{files: string[], writes: string[]}}
|
|
894
978
|
*/
|
|
895
|
-
export function
|
|
979
|
+
export function extractFileTargets(input, opts = {}) {
|
|
896
980
|
const paths = [];
|
|
897
|
-
|
|
981
|
+
const writes = [];
|
|
982
|
+
if (!input || typeof input !== 'object') return { files: [], writes: [] };
|
|
983
|
+
// Direct fields (Edit/Write file_path) are kept even under /tmp — an explicit edit to a
|
|
898
984
|
// /tmp path is real work the user chose to make, unlike a /tmp path that merely appears as
|
|
899
985
|
// a transient argument inside a Bash command (excluded as noise in the command branch below).
|
|
986
|
+
// Session-scoped paths (isTransientPath) are the exception: they are the agent's own
|
|
987
|
+
// scratch, not the user's work.
|
|
900
988
|
//
|
|
901
989
|
// `toolEditPath`, not a fourth hand-spelling of the same rule: this function knew
|
|
902
990
|
// file_path/path/filePath and not `notebook_path`, while hooks.json matches PostToolUse
|
|
@@ -905,27 +993,39 @@ export function extractFilePaths(input) {
|
|
|
905
993
|
// files, so the observation built from it got no observation_files edge and no file-keyed
|
|
906
994
|
// recall could reach it. Same root cause as R12 B-2; the fourth site, and the one its
|
|
907
995
|
// own follow-up note did not name.
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (input.
|
|
912
|
-
|
|
913
|
-
//
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
996
|
+
for (const p of [toolEditPath(input), input.path, input.filePath]) {
|
|
997
|
+
if (typeof p === 'string' && p && !isTransientPath(p)) paths.push(p);
|
|
998
|
+
}
|
|
999
|
+
if (typeof input.command === 'string' && input.command) {
|
|
1000
|
+
// Shell-aware: cwd prefixes, relative and quoted paths, write-verb targets and
|
|
1001
|
+
// interpreter-script literals (lib/bash-file-targets.mjs). The `cd` target itself is
|
|
1002
|
+
// never an edge — it made every `cd <repo> && …` command look like it touched the
|
|
1003
|
+
// repo root and nothing else.
|
|
1004
|
+
const keep = (p) =>
|
|
1005
|
+
!isScratchCommandPath(p, opts.projectDir) &&
|
|
1006
|
+
!isTransientPath(p) &&
|
|
1007
|
+
// Skip single-component paths like /exit, /clear — likely slash commands, not files
|
|
1008
|
+
(p.indexOf('/', 1) !== -1 || /\.\w+$/.test(p));
|
|
1009
|
+
const t = bashFileTargets(input.command, { cwd: opts.cwd || null });
|
|
1010
|
+
for (const p of t.writes) {
|
|
1011
|
+
if (!keep(p)) continue;
|
|
1012
|
+
writes.push(p);
|
|
1013
|
+
paths.push(p);
|
|
926
1014
|
}
|
|
1015
|
+
for (const p of [...t.reads, ...t.mentions]) if (keep(p)) paths.push(p);
|
|
927
1016
|
}
|
|
928
|
-
return [...new Set(paths)];
|
|
1017
|
+
return { files: [...new Set(paths)], writes: [...new Set(writes)] };
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Extract file paths from tool input (file_path, path, filePath, or command args).
|
|
1022
|
+
* Deduplicates and excludes /dev/, /proc/, /tmp/ command paths and session-scoped paths.
|
|
1023
|
+
* @param {object} input Tool input object
|
|
1024
|
+
* @param {{cwd?: string|null}} [opts] the hook's `cwd`, to resolve relative Bash paths
|
|
1025
|
+
* @returns {string[]} Unique array of file paths
|
|
1026
|
+
*/
|
|
1027
|
+
export function extractFilePaths(input, opts = {}) {
|
|
1028
|
+
return extractFileTargets(input, opts).files;
|
|
929
1029
|
}
|
|
930
1030
|
|
|
931
1031
|
// ─── Episode Logic ───────────────────────────────────────────────────────────
|
package/cli/doctor.mjs
CHANGED
package/hook-context.mjs
CHANGED
|
@@ -850,11 +850,11 @@ export function buildSessionContextLines(
|
|
|
850
850
|
`
|
|
851
851
|
SELECT id, title, priority,
|
|
852
852
|
ROW_NUMBER() OVER (
|
|
853
|
-
ORDER BY priority DESC, created_at_epoch ASC
|
|
853
|
+
ORDER BY priority DESC, created_at_epoch ASC, id ASC
|
|
854
854
|
) AS ordinal
|
|
855
855
|
FROM deferred_work
|
|
856
856
|
WHERE project = ? AND status = 'open'
|
|
857
|
-
ORDER BY priority DESC, created_at_epoch ASC
|
|
857
|
+
ORDER BY priority DESC, created_at_epoch ASC, id ASC
|
|
858
858
|
LIMIT 5
|
|
859
859
|
`,
|
|
860
860
|
)
|
package/hook-episode.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
statSync,
|
|
15
15
|
constants as fsConstants,
|
|
16
16
|
} from 'fs';
|
|
17
|
-
import { inferProject,
|
|
17
|
+
import { inferProject, isEditEntry } from './utils.mjs';
|
|
18
18
|
import { RUNTIME_DIR } from './hook-shared.mjs';
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -454,7 +454,7 @@ export function explainSignificance(episode) {
|
|
|
454
454
|
const base = { readCount, grepCount, grepDecisive: false };
|
|
455
455
|
|
|
456
456
|
// 1. File edits → always significant (code changes matter)
|
|
457
|
-
if (entries.some(
|
|
457
|
+
if (entries.some(isEditEntry)) return { ...base, significant: true, rule: 1 };
|
|
458
458
|
|
|
459
459
|
// 2. Test/build errors → significant (actionable failures)
|
|
460
460
|
// Plain bash errors without edits are noise (e.g. typos, exploration errors)
|
package/hook-handoff.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isSpecificTerm,
|
|
11
11
|
scrubSecrets,
|
|
12
12
|
LOW_SIGNAL_TITLE,
|
|
13
|
-
|
|
13
|
+
isEditEntry,
|
|
14
14
|
isMetaTriggerPrompt,
|
|
15
15
|
notLowSignalTitleClause,
|
|
16
16
|
safeText,
|
|
@@ -262,7 +262,7 @@ export function buildAndSaveHandoff(db, sessionId, project, type, episodeSnapsho
|
|
|
262
262
|
if (episodeSnapshot?.entries) {
|
|
263
263
|
const seenDescs = new Set();
|
|
264
264
|
const pendingDescs = episodeSnapshot.entries
|
|
265
|
-
.filter((e) => e.isError ||
|
|
265
|
+
.filter((e) => e.isError || isEditEntry(e))
|
|
266
266
|
.map((e) => e.desc)
|
|
267
267
|
.filter((d) => {
|
|
268
268
|
if (seenDescs.has(d)) return false;
|