claude-mem-lite 6.23.2 → 6.24.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 +29 -4
- package/README.zh-CN.md +2 -2
- package/cli/activity.mjs +10 -1
- package/haiku-client.mjs +17 -1
- package/hook-optimize.mjs +29 -19
- package/hook-shared.mjs +6 -3
- package/hook-update.mjs +124 -50
- package/hook.mjs +6 -1
- package/install.mjs +82 -31
- package/lib/anthropic-base-url.mjs +9 -2
- package/lib/data-paths.mjs +10 -0
- package/lib/err-sampler.mjs +2 -1
- package/lib/hook-telemetry.mjs +2 -0
- package/lib/install-shape.mjs +5 -5
- package/lib/llm-provider-probe.mjs +20 -4
- package/lib/maintain-core.mjs +11 -4
- package/lib/metrics.mjs +2 -0
- package/lib/native-binding-hint.mjs +5 -0
- package/lib/proc-lock.mjs +6 -1
- package/lib/proxy-fetch.mjs +88 -14
- package/lib/record-once.mjs +3 -1
- package/lib/save-enrich.mjs +2 -1
- package/lib/search-telemetry.mjs +34 -22
- package/lib/time-constants.mjs +7 -0
- package/lib/worker-data-dir.mjs +34 -0
- package/npm-shrinkwrap.json +20 -3133
- package/package.json +5 -4
- package/plugin-cache-guard.mjs +5 -8
- package/schema.mjs +12 -3
- package/scripts/post-tool-use.sh +7 -3
- package/scripts/setup.sh +16 -3
- package/server.mjs +4 -4
- package/source-files.mjs +4 -0
- package/utils.mjs +5 -2
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.
|
|
12
|
+
"version": "6.24.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.24.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
|
@@ -189,7 +189,7 @@ cd claude-mem-lite
|
|
|
189
189
|
node install.mjs install
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
`install` copies the source files to `~/.claude-mem-lite/`, as the npx method does. Update with `node install.mjs update`, which installs the latest signed release. `git pull && node install.mjs install` installs the checked-out branch instead: it can be ahead of the latest release, and no signature is checked.
|
|
193
193
|
|
|
194
194
|
### What happens during installation
|
|
195
195
|
|
|
@@ -237,6 +237,27 @@ 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.24.0
|
|
241
|
+
|
|
242
|
+
**If you set `CLAUDE_CONFIG_DIR`, the installer, updates and hooks now use that directory, as
|
|
243
|
+
Claude Code does.** No schema-version change: 6.23.3 still opens the database after this
|
|
244
|
+
release has. Without `CLAUDE_CONFIG_DIR` nothing in this item changes.
|
|
245
|
+
|
|
246
|
+
- **Who is affected.** Claude Code keeps `settings.json`, `plugins/` and `.claude.json` in
|
|
247
|
+
`CLAUDE_CONFIG_DIR` when it is set. The plugin kept using `~/.claude` and `~/.claude.json`:
|
|
248
|
+
`install` wrote its hooks where Claude Code never reads them, and with two profiles the
|
|
249
|
+
session-start setup and `self-update` pruned the other profile's plugin cache and edited its
|
|
250
|
+
`.claude.json`.
|
|
251
|
+
- **What to do.** Installed with npm or npx: run `claude-mem-lite install` once more with the
|
|
252
|
+
variable set, so the hooks land in your config directory. Plugin installs need nothing. Hook
|
|
253
|
+
entries an older version wrote into `~/.claude/settings.json` stay there; if no other profile
|
|
254
|
+
uses `~/.claude`, `env -u CLAUDE_CONFIG_DIR claude-mem-lite cleanup-hooks` removes them.
|
|
255
|
+
- **Also in this release:** `npm install -g claude-mem-lite` and `npx` no longer install the
|
|
256
|
+
development toolchain (96 packages / 57 MB instead of 298 / 541 MB). A confirmed
|
|
257
|
+
`maintain execute --ops purge_stale` now deletes every row its preview counted in one run,
|
|
258
|
+
behind one snapshot. `doctor` warns about a failing update check and about an
|
|
259
|
+
`ANTHROPIC_BASE_URL` that ends in `/v1`. Details in CHANGELOG.md.
|
|
260
|
+
|
|
240
261
|
## Upgrading to 6.23.1
|
|
241
262
|
|
|
242
263
|
**If you set `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY`, background LLM calls now go to that
|
|
@@ -568,9 +589,13 @@ depends on the Claude Code version. Allow the server once in the `settings.json`
|
|
|
568
589
|
{ "permissions": { "allow": ["mcp__plugin_claude-mem-lite_mem-lite"] } }
|
|
569
590
|
```
|
|
570
591
|
|
|
571
|
-
|
|
592
|
+
For a single run, `claude -p … --allowedTools "mcp__plugin_claude-mem-lite_mem-lite"` does the
|
|
593
|
+
same (both forms checked on 2.1.292, 2026-10-07: no refusal, and `mem_search` answered). That is
|
|
594
|
+
the server's name under a plugin install. An npx or git-clone install registers it as
|
|
572
595
|
`mem-lite`, so the rule there is `mcp__mem-lite`. An interactive session asks instead; answering
|
|
573
|
-
"always allow" there records a rule of its own.
|
|
596
|
+
"always allow" there records a rule of its own. The plugin does not tell the model to fall back
|
|
597
|
+
to the CLI when a call is refused: the refusal is Claude Code's permission check for a tool you
|
|
598
|
+
have not allowed, and routing the same action through Bash would sidestep it.
|
|
574
599
|
|
|
575
600
|
### Skill Commands (in Claude Code chat)
|
|
576
601
|
|
|
@@ -1091,7 +1116,7 @@ claude-mem-lite.
|
|
|
1091
1116
|
| `CLAUDE_MEM_DIR` | Custom data directory. All databases, runtime files, and managed resources are stored here. | `~/.claude-mem-lite/` |
|
|
1092
1117
|
| `CLAUDE_MEM_MODEL` | LLM model for background calls (episode extraction, session summaries). Accepts `haiku` or `sonnet`. | `haiku` |
|
|
1093
1118
|
| `ANTHROPIC_API_KEY` | Anthropic API key. When set, all background LLM calls go directly to the Anthropic Messages API (with prompt caching) - or to the `ANTHROPIC_BASE_URL` gateway when that is set. Highest priority. | _(unset → CLI)_ |
|
|
1094
|
-
| `ANTHROPIC_BASE_URL` | Base URL for the direct Messages API when an Anthropic-compatible gateway serves the models (Azure AI Foundry, LiteLLM, Bedrock/Vertex proxies). No `/v1` suffix — the endpoint path is appended. The `claude -p` fallback reads the same variable, so one value covers both transports. Plain `http://` is accepted only for a loopback host; anything else would send the API key unencrypted. A value that is set but unusable skips the direct API leg, so background calls fall back to the `claude -p` CLI, and doctor warns. | `https://api.anthropic.com` |
|
|
1119
|
+
| `ANTHROPIC_BASE_URL` | Base URL for the direct Messages API when an Anthropic-compatible gateway serves the models (Azure AI Foundry, LiteLLM, Bedrock/Vertex proxies). No `/v1` suffix — the endpoint path is appended. The `claude -p` fallback reads the same variable, so one value covers both transports. Plain `http://` is accepted only for a loopback host; anything else would send the API key unencrypted. That request always goes straight to the loopback host, even when `NODE_USE_ENV_PROXY=1` sends other requests through `HTTP_PROXY`. A value that is set but unusable skips the direct API leg, so background calls fall back to the `claude -p` CLI, and doctor warns. | `https://api.anthropic.com` |
|
|
1095
1120
|
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | Model ID or gateway deployment name for the `haiku` tier, honored on the direct API **only when `ANTHROPIC_BASE_URL` is set**. Without a base URL it stays CLI-only, so the Bedrock/Vertex form of this alias is never sent to `api.anthropic.com`. Use it when the gateway routes on deployment names rather than Anthropic model IDs (Azure Foundry deployments). The `claude -p` fallback resolves its `--model haiku` alias through it too. | built-in `claude-haiku-4-5-…` |
|
|
1096
1121
|
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | Same as above for the `sonnet` tier. | built-in `claude-sonnet-4-5-…` |
|
|
1097
1122
|
| `OPENROUTER_API_KEY` | OpenRouter API key (OpenAI-compatible). Used for background LLM calls when `ANTHROPIC_API_KEY` is **not** set. If neither key is set, calls fall back to the `claude -p` CLI. | _(unset)_ |
|
package/README.zh-CN.md
CHANGED
|
@@ -147,7 +147,7 @@ cd claude-mem-lite
|
|
|
147
147
|
node install.mjs install
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
`install` 会把源文件复制到 `~/.claude-mem-lite/`,与 npx 方式相同。用 `node install.mjs update` 更新,它安装最新的已签名发布。`git pull && node install.mjs install` 安装的则是当前检出的分支:可能领先于最新发布,也不校验签名。
|
|
151
151
|
|
|
152
152
|
### 安装过程
|
|
153
153
|
|
|
@@ -833,7 +833,7 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
|
|
|
833
833
|
| `CLAUDE_MEM_DIR` | 自定义数据目录。所有数据库与运行时文件均存储在此。 | `~/.claude-mem-lite/` |
|
|
834
834
|
| `CLAUDE_MEM_MODEL` | 后台 LLM 调用模型(Episode 提取、会话总结、调度)。可选 `haiku` 或 `sonnet`。 | `haiku` |
|
|
835
835
|
| `ANTHROPIC_API_KEY` | Anthropic API key。设置后所有后台 LLM 调用直连 Anthropic Messages API(带 prompt caching);设了 `ANTHROPIC_BASE_URL` 时改发往那个网关。优先级最高。 | _(未设 → CLI)_ |
|
|
836
|
-
| `ANTHROPIC_BASE_URL` | 由兼容 Anthropic 的网关(Azure AI Foundry、LiteLLM、Bedrock/Vertex 代理)提供模型时,直连 Messages API 使用的基础 URL。不要带 `/v1` 后缀,端点路径会自动拼上。`claude -p` 兜底读取同一个变量,所以一个值同时作用于两条路径。明文 `http://` 只接受回环主机,否则 API key
|
|
836
|
+
| `ANTHROPIC_BASE_URL` | 由兼容 Anthropic 的网关(Azure AI Foundry、LiteLLM、Bedrock/Vertex 代理)提供模型时,直连 Messages API 使用的基础 URL。不要带 `/v1` 后缀,端点路径会自动拼上。`claude -p` 兜底读取同一个变量,所以一个值同时作用于两条路径。明文 `http://` 只接受回环主机,否则 API key 会以明文发送。这个请求总是直连回环主机,即使 `NODE_USE_ENV_PROXY=1` 让其他请求走 `HTTP_PROXY`。设了但不可用的值会跳过直连这一路,后台调用改走 `claude -p`,doctor 会提示。 | `https://api.anthropic.com` |
|
|
837
837
|
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | `haiku` 档的模型 ID 或网关部署名,**仅在设了 `ANTHROPIC_BASE_URL` 时**用于直连 API。没有基础 URL 时只给 CLI 用,所以这个别名的 Bedrock/Vertex 写法不会被发往 `api.anthropic.com`。网关按部署名而不是 Anthropic 模型 ID 路由时(Azure Foundry 部署)使用。`claude -p` 兜底的 `--model haiku` 别名也经由它解析。 | 内置 `claude-haiku-4-5-…` |
|
|
838
838
|
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | 同上,用于 `sonnet` 档。 | 内置 `claude-sonnet-4-5-…` |
|
|
839
839
|
| `OPENROUTER_API_KEY` | OpenRouter API key(OpenAI 兼容)。当**未设** `ANTHROPIC_API_KEY` 时用于后台 LLM 调用;两者都未设则回退到 `claude -p` CLI。 | _(未设)_ |
|
package/cli/activity.mjs
CHANGED
|
@@ -188,7 +188,16 @@ export async function cmdActivity(db, args) {
|
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
// The opt-in search telemetry keeps a title per returned result; an explicit delete drops the
|
|
192
|
+
// deleted events' titles, as lib/delete-core.mjs does for observations (D#245 N6).
|
|
193
|
+
const result = db.transaction(() => {
|
|
194
|
+
if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'search_results'").get()) {
|
|
195
|
+
db.prepare(
|
|
196
|
+
`UPDATE search_results SET snapshot_label = NULL WHERE source = 'event' AND result_id IN (${placeholders})`,
|
|
197
|
+
).run(...ids);
|
|
198
|
+
}
|
|
199
|
+
return db.prepare(`DELETE FROM events WHERE id IN (${placeholders})`).run(...ids);
|
|
200
|
+
})();
|
|
192
201
|
out(`[mem] Deleted ${result.changes} event(s).`);
|
|
193
202
|
return;
|
|
194
203
|
}
|
package/haiku-client.mjs
CHANGED
|
@@ -20,7 +20,8 @@ import { randomUUID } from 'crypto';
|
|
|
20
20
|
import { debugLog, debugCatch, parseJsonFromLLM } from './utils.mjs';
|
|
21
21
|
import { DB_DIR } from './schema.mjs';
|
|
22
22
|
import { resolveRuntimeDir } from './lib/resolve-data-dir.mjs';
|
|
23
|
-
import {
|
|
23
|
+
import { workerDirGone } from './lib/worker-data-dir.mjs';
|
|
24
|
+
import { httpConnectProxyFor, postViaConnectProxy, postDirectHttp } from './lib/proxy-fetch.mjs';
|
|
24
25
|
import { resolveAnthropicBaseUrl } from './lib/anthropic-base-url.mjs';
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -40,6 +41,9 @@ import { resolveAnthropicBaseUrl } from './lib/anthropic-base-url.mjs';
|
|
|
40
41
|
*/
|
|
41
42
|
function cliSpawnCwd() {
|
|
42
43
|
const dir = join(resolveRuntimeDir(DB_DIR), 'cli-cwd');
|
|
44
|
+
// A background worker whose data dir was removed during its API attempt must not re-create
|
|
45
|
+
// it here (D#265); the spawn then fails on the missing cwd, and the worker gives up.
|
|
46
|
+
if (workerDirGone(DB_DIR)) return dir;
|
|
43
47
|
try {
|
|
44
48
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
45
49
|
} catch {
|
|
@@ -553,8 +557,20 @@ async function callModelAPI(prompt, model, { timeout, maxTokens, temperature = D
|
|
|
553
557
|
'x-api-key': apiKey,
|
|
554
558
|
'anthropic-version': '2023-06-01',
|
|
555
559
|
};
|
|
560
|
+
// Plain http is loopback-only (resolveAnthropicBaseUrl), and loopback must stay on this
|
|
561
|
+
// machine: native fetch would hand it, x-api-key in cleartext, to HTTP_PROXY under
|
|
562
|
+
// NODE_USE_ENV_PROXY=1. (D#250) Parsed, not a prefix test: a prefix test let `HTTP://127.0.0.1`
|
|
563
|
+
// through to the proxy when the resolver still returned the raw text (b59e0cec, D#259).
|
|
564
|
+
const plainHttp = new URL(apiUrl).protocol === 'http:';
|
|
556
565
|
const send = (payload) => {
|
|
557
566
|
const json = JSON.stringify(payload);
|
|
567
|
+
if (plainHttp) {
|
|
568
|
+
return postDirectHttp(apiUrl, {
|
|
569
|
+
headers: apiHeaders,
|
|
570
|
+
body: json,
|
|
571
|
+
timeout: Math.max(1, deadline - Date.now()),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
558
574
|
const apiProxy = httpConnectProxyFor(apiUrl);
|
|
559
575
|
return apiProxy
|
|
560
576
|
? postViaConnectProxy(apiProxy, apiUrl, {
|
package/hook-optimize.mjs
CHANGED
|
@@ -485,7 +485,7 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
485
485
|
// A compression keeper is never hidden: hiding it hides every member compressed into it
|
|
486
486
|
// (the same rule NOT_COMPRESSION_KEEPER_SQL enforces on the maintenance writers). It falls
|
|
487
487
|
// through to the normal update instead: floored to importance 1 and stamped, so it is not
|
|
488
|
-
// re-sent.
|
|
488
|
+
// re-sent. That update keeps its stored title and narrative in both scopes (keepStoredText).
|
|
489
489
|
const isKeeper = !!db
|
|
490
490
|
.prepare('SELECT 1 FROM observations WHERE compressed_into = ? LIMIT 1')
|
|
491
491
|
.get(cand.id);
|
|
@@ -494,12 +494,8 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
494
494
|
const humanSet =
|
|
495
495
|
(db.prepare('SELECT importance_set_at FROM observations WHERE id = ?').get(cand.id)
|
|
496
496
|
?.importance_set_at ?? null) !== null;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
scope !== 'wide' &&
|
|
500
|
-
!isKeeper &&
|
|
501
|
-
!humanSet
|
|
502
|
-
) {
|
|
497
|
+
const scoredZero = parsed.importance === 0 || parsed.importance === '0';
|
|
498
|
+
if (scoredZero && scope !== 'wide' && !isKeeper && !humanSet) {
|
|
503
499
|
// D#12, and this one is not a stale-write guard — it is a POINTER guard.
|
|
504
500
|
// `compressed_into` is the child -> keeper link, and COMPRESSED_AUTO is -1. If a
|
|
505
501
|
// concurrent cluster-merge or smart-compress adopts this row during the 45 s Haiku
|
|
@@ -511,7 +507,8 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
511
507
|
const res = db
|
|
512
508
|
.prepare(
|
|
513
509
|
`UPDATE observations SET compressed_into = ${COMPRESSED_AUTO}, optimized_at = ?
|
|
514
|
-
WHERE id = ? AND ${liveObsFilterSql('')} AND optimized_at IS NULL AND ${NOT_COMPRESSION_KEEPER_SQL}
|
|
510
|
+
WHERE id = ? AND ${liveObsFilterSql('')} AND optimized_at IS NULL AND ${NOT_COMPRESSION_KEEPER_SQL}
|
|
511
|
+
AND importance_set_at IS NULL`,
|
|
515
512
|
)
|
|
516
513
|
.run(Date.now(), cand.id);
|
|
517
514
|
if (res.changes === 0) {
|
|
@@ -561,9 +558,15 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
561
558
|
// For `narrow`, the truncate stays on LLM output but NOT on the preserve-on-empty
|
|
562
559
|
// fallback — truncating the row's own stored narrative to buy nothing was the same
|
|
563
560
|
// bug in miniature.
|
|
561
|
+
//
|
|
562
|
+
// A reply that scored the row 0 reaches this point only for a row that may not be hidden
|
|
563
|
+
// (a compression keeper, or an importance a person set). It judged the row worthless, so
|
|
564
|
+
// its title and narrative do not replace the stored ones either (D#207: 'Weekly summary:
|
|
565
|
+
// auth refactor' became 'Weekly summary', narrative 'x').
|
|
564
566
|
const isWide = scope === 'wide';
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
+
const keepStoredText = isWide || scoredZero;
|
|
568
|
+
const title = keepStoredText ? cand.title : truncate(scrubSecrets(parsed.title || ''), 120);
|
|
569
|
+
const narrative = keepStoredText
|
|
567
570
|
? cand.narrative
|
|
568
571
|
: parsed.narrative
|
|
569
572
|
? truncate(scrubSecrets(parsed.narrative), 500)
|
|
@@ -571,9 +574,10 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
571
574
|
// Floor at the stored importance: re-enrich adds a lesson, it must never silently downgrade
|
|
572
575
|
// a user-set/promoted importance (the UPDATE also sets optimized_at → the loss is permanent).
|
|
573
576
|
// Upgrades are still honored — except over an importance a person set (D10), which stays.
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
+
// Decided in the UPDATE itself, on the row as it is then: the candidate's importance was read
|
|
578
|
+
// before the model call, and writing it back undid a person's change made during the call
|
|
579
|
+
// (D#206).
|
|
580
|
+
const modelImportance = clampImportance(parsed.importance);
|
|
577
581
|
|
|
578
582
|
const bigramText = cjkBigrams((title || '') + ' ' + (narrative || ''));
|
|
579
583
|
const textField = [conceptsText, factsText, searchAliases || '', bigramText].filter(Boolean).join(' ');
|
|
@@ -607,10 +611,10 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
607
611
|
// Narrow replaces title and narrative with model text, so an explicit save it rewrites (one
|
|
608
612
|
// whose save-time enrich failed) moves to the re-enrich writer's id, as a cluster-merge
|
|
609
613
|
// keeper does (D#146, D#138). Wide keeps the stored title and narrative (it fills the lesson
|
|
610
|
-
// and the side fields), so the row stays an explicit save
|
|
611
|
-
// best-effort.
|
|
614
|
+
// and the side fields), so the row stays an explicit save, and so does a row whose stored
|
|
615
|
+
// text a 0 reply left in place. The writer's session row is best-effort.
|
|
612
616
|
let rewriteSessionId = null;
|
|
613
|
-
if (!
|
|
617
|
+
if (!keepStoredText) {
|
|
614
618
|
const cur = db.prepare('SELECT memory_session_id FROM observations WHERE id = ?').get(cand.id);
|
|
615
619
|
if (cur?.memory_session_id?.startsWith(MANUAL_SESSION_ID_PREFIX)) {
|
|
616
620
|
const enrichSessionId = writerSessionId('enrich-', cand.project);
|
|
@@ -629,7 +633,10 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
629
633
|
.prepare(
|
|
630
634
|
`
|
|
631
635
|
UPDATE observations SET type=?, title=?, narrative=?, concepts=?, facts=?,
|
|
632
|
-
text=?,
|
|
636
|
+
text=?,
|
|
637
|
+
importance = CASE WHEN importance_set_at IS NOT NULL THEN importance
|
|
638
|
+
ELSE MAX(?, COALESCE(NULLIF(importance, 0), 1)) END,
|
|
639
|
+
lesson_learned=?, search_aliases=?, minhash_sig=?, optimized_at=?,
|
|
633
640
|
scope=COALESCE(?, scope), memory_session_id = COALESCE(?, memory_session_id)
|
|
634
641
|
WHERE id = ? AND ${liveObsFilterSql('')} AND optimized_at IS NULL
|
|
635
642
|
`,
|
|
@@ -641,7 +648,7 @@ scope: ${SCOPE_PROMPT_LEGEND}`;
|
|
|
641
648
|
safe.concepts,
|
|
642
649
|
safe.facts,
|
|
643
650
|
safe.text,
|
|
644
|
-
|
|
651
|
+
modelImportance,
|
|
645
652
|
safe.lesson_learned,
|
|
646
653
|
safe.search_aliases,
|
|
647
654
|
minhashSig,
|
|
@@ -1300,6 +1307,8 @@ Return ONLY valid JSON:
|
|
|
1300
1307
|
const bigramText = cjkBigrams((title || '') + ' ' + (narrative || ''));
|
|
1301
1308
|
const textField = [conceptsText, factsText, bigramText].filter(Boolean).join(' ');
|
|
1302
1309
|
const minhashSig = computeMinHash((title || '') + ' ' + (narrative || ''));
|
|
1310
|
+
// An importance a person set on the keeper stays (D10); the UPDATE decides that on the row as
|
|
1311
|
+
// it is then, since a person can set it while this call waits on the model (D#198).
|
|
1303
1312
|
const importance = Math.max(clampImportance(parsed.importance || 2), maxClusterImportance);
|
|
1304
1313
|
|
|
1305
1314
|
// Scrub LLM-output cluster-merge text fields at the UPDATE boundary.
|
|
@@ -1382,7 +1391,8 @@ Return ONLY valid JSON:
|
|
|
1382
1391
|
db.prepare(
|
|
1383
1392
|
`
|
|
1384
1393
|
UPDATE observations SET title=?, narrative=?, concepts=?, facts=?, text=?,
|
|
1385
|
-
importance
|
|
1394
|
+
importance = CASE WHEN importance_set_at IS NOT NULL THEN importance ELSE ? END,
|
|
1395
|
+
lesson_learned=?, minhash_sig=?, optimized_at=?,
|
|
1386
1396
|
memory_session_id = COALESCE(?, memory_session_id)
|
|
1387
1397
|
WHERE id = ?
|
|
1388
1398
|
`,
|
package/hook-shared.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './lib/schema-skew.mjs';
|
|
31
31
|
import { isDbUnusableError, DB_UNUSABLE_MARKER_PREFIX } from './lib/db-unusable.mjs';
|
|
32
32
|
import { shouldRecordOnce } from './lib/record-once.mjs';
|
|
33
|
+
import { backgroundWorkerEnv, workerDirGone } from './lib/worker-data-dir.mjs';
|
|
33
34
|
import { hookSessionId } from './lib/provenance.mjs';
|
|
34
35
|
import { PROJECT_REKEY_MARKER_PREFIX } from './lib/project-rekey.mjs';
|
|
35
36
|
// Audit 2026-09-05 P1-2 (carried from 2026-09-02 P2-9): `callLLM`, the quiet/adoption
|
|
@@ -378,9 +379,11 @@ export function sweepStaleProjectMarkers(
|
|
|
378
379
|
// exists, so harden here too: create 0700, and chmod a pre-existing dir a prior version
|
|
379
380
|
// created at the default umask. A 0700 dir blocks traversal to every file inside,
|
|
380
381
|
// current and future, regardless of individual file mode (audit sec P3-2 2026-07-24).
|
|
382
|
+
// A background worker whose data dir was removed while it ran does not re-create it (D#265).
|
|
381
383
|
try {
|
|
382
|
-
if (!existsSync(RUNTIME_DIR))
|
|
383
|
-
|
|
384
|
+
if (!existsSync(RUNTIME_DIR)) {
|
|
385
|
+
if (!workerDirGone(DB_DIR)) mkdirSync(RUNTIME_DIR, { recursive: true, mode: 0o700 });
|
|
386
|
+
} else chmodSync(RUNTIME_DIR, 0o700);
|
|
384
387
|
} catch {}
|
|
385
388
|
|
|
386
389
|
// ─── Session ID Management ───────────────────────────────────────────────────
|
|
@@ -604,7 +607,7 @@ export function spawnBackground(bgEvent, ...extraArgs) {
|
|
|
604
607
|
const child = spawn(process.execPath, args, {
|
|
605
608
|
detached: true,
|
|
606
609
|
stdio: 'ignore',
|
|
607
|
-
env:
|
|
610
|
+
env: backgroundWorkerEnv(),
|
|
608
611
|
});
|
|
609
612
|
child.on('error', (err) => {
|
|
610
613
|
debugCatch(err, 'spawnBackground');
|