claude-mem-lite 6.3.0 → 6.5.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 +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +22 -2
- package/README.zh-CN.md +19 -2
- package/hook-context.mjs +17 -2
- package/hook-shared.mjs +48 -3
- package/hook-update.mjs +6 -1
- package/hook.mjs +35 -1
- package/install.mjs +431 -40
- package/lib/data-paths.mjs +33 -0
- package/lib/db-backup.mjs +42 -17
- package/lib/db-unusable.mjs +178 -0
- package/lib/plugin-key.mjs +4 -1
- package/lib/record-once.mjs +71 -0
- package/lib/save-observation.mjs +16 -5
- package/lib/schema-skew.mjs +21 -30
- package/lib/timeline-core.mjs +54 -15
- package/mem-cli.mjs +33 -32
- package/npm-shrinkwrap.json +2 -2
- package/package.json +5 -1
- package/schema.mjs +15 -22
- package/scripts/hook-launcher.mjs +25 -4
- package/server.mjs +59 -1
- package/source-files.mjs +7 -0
- package/tool-schemas.mjs +74 -47
package/mem-cli.mjs
CHANGED
|
@@ -799,7 +799,7 @@ function renderObsRows(db, ids, requestedFields) {
|
|
|
799
799
|
}
|
|
800
800
|
parts.push(lines.join('\n'));
|
|
801
801
|
}
|
|
802
|
-
return { text: parts.join('\n\n'), count: rows.length };
|
|
802
|
+
return { text: parts.join('\n\n'), count: rows.length, foundIds: rows.map((r) => r.id) };
|
|
803
803
|
}
|
|
804
804
|
|
|
805
805
|
function renderSessionRows(db, ids) {
|
|
@@ -819,7 +819,7 @@ function renderSessionRows(db, ids) {
|
|
|
819
819
|
}
|
|
820
820
|
parts.push(lines.join('\n'));
|
|
821
821
|
}
|
|
822
|
-
return { text: parts.join('\n\n'), count: rows.length };
|
|
822
|
+
return { text: parts.join('\n\n'), count: rows.length, foundIds: rows.map((r) => r.id) };
|
|
823
823
|
}
|
|
824
824
|
|
|
825
825
|
// The CLI's established labels for the prompt/event detail faces. Sharing the FIELD SET
|
|
@@ -850,7 +850,7 @@ function renderPromptRows(db, ids) {
|
|
|
850
850
|
}
|
|
851
851
|
parts.push(lines.join('\n'));
|
|
852
852
|
}
|
|
853
|
-
return { text: parts.join('\n\n'), count: rows.length };
|
|
853
|
+
return { text: parts.join('\n\n'), count: rows.length, foundIds: rows.map((r) => r.id) };
|
|
854
854
|
}
|
|
855
855
|
|
|
856
856
|
function renderEventRows(db, ids) {
|
|
@@ -867,7 +867,7 @@ function renderEventRows(db, ids) {
|
|
|
867
867
|
}
|
|
868
868
|
parts.push(lines.join('\n'));
|
|
869
869
|
}
|
|
870
|
-
return { text: parts.join('\n\n'), count: rows.length };
|
|
870
|
+
return { text: parts.join('\n\n'), count: rows.length, foundIds: rows.map((r) => r.id) };
|
|
871
871
|
}
|
|
872
872
|
|
|
873
873
|
function cmdGet(db, args) {
|
|
@@ -954,34 +954,21 @@ function cmdGet(db, args) {
|
|
|
954
954
|
);
|
|
955
955
|
}
|
|
956
956
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
957
|
+
// Per-bucket misses are collected as they happen, not inferred afterwards, so the note
|
|
958
|
+
// below can name an id with the prefix the caller typed.
|
|
959
|
+
const missing = [];
|
|
960
|
+
const takeSection = (result, ids, prefix) => {
|
|
961
|
+
if (result) {
|
|
962
|
+
sections.push(result.text);
|
|
963
|
+
totalFound += result.count;
|
|
962
964
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
}
|
|
971
|
-
if (bySrc.prompt.length > 0) {
|
|
972
|
-
const s = renderPromptRows(db, bySrc.prompt);
|
|
973
|
-
if (s) {
|
|
974
|
-
sections.push(s.text);
|
|
975
|
-
totalFound += s.count;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (bySrc.event.length > 0) {
|
|
979
|
-
const s = renderEventRows(db, bySrc.event);
|
|
980
|
-
if (s) {
|
|
981
|
-
sections.push(s.text);
|
|
982
|
-
totalFound += s.count;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
965
|
+
const found = new Set(result?.foundIds ?? []);
|
|
966
|
+
missing.push(...ids.filter((id) => !found.has(id)).map((id) => `${prefix}${id}`));
|
|
967
|
+
};
|
|
968
|
+
if (bySrc.obs.length > 0) takeSection(renderObsRows(db, bySrc.obs, requestedFields), bySrc.obs, '#');
|
|
969
|
+
if (bySrc.session.length > 0) takeSection(renderSessionRows(db, bySrc.session), bySrc.session, 'S#');
|
|
970
|
+
if (bySrc.prompt.length > 0) takeSection(renderPromptRows(db, bySrc.prompt), bySrc.prompt, 'P#');
|
|
971
|
+
if (bySrc.event.length > 0) takeSection(renderEventRows(db, bySrc.event), bySrc.event, 'E#');
|
|
985
972
|
|
|
986
973
|
if (totalFound === 0) {
|
|
987
974
|
// Deferred-only request that found nothing — the source-probe below is
|
|
@@ -1011,6 +998,15 @@ function cmdGet(db, args) {
|
|
|
1011
998
|
}
|
|
1012
999
|
|
|
1013
1000
|
out(sections.join('\n\n'));
|
|
1001
|
+
|
|
1002
|
+
// Partial misses were silent here while every sibling reported them: mem_get appends
|
|
1003
|
+
// "Note: ID(s) … not found.", CLI `delete` prints "not found and will be skipped", and the
|
|
1004
|
+
// total-miss branch above fails loudly. So `get 12,13,14` returning two records read as a
|
|
1005
|
+
// complete answer, and nothing said which id was absent. stderr, not stdout: `get` output
|
|
1006
|
+
// is piped, and the D#/unparseable notices above already use that channel.
|
|
1007
|
+
if (missing.length > 0) {
|
|
1008
|
+
process.stderr.write(`[mem] Note: ID(s) ${missing.join(', ')} not found\n`);
|
|
1009
|
+
}
|
|
1014
1010
|
}
|
|
1015
1011
|
|
|
1016
1012
|
function cmdTimeline(db, args) {
|
|
@@ -1193,7 +1189,7 @@ function cmdSave(db, args) {
|
|
|
1193
1189
|
if (text === null) return;
|
|
1194
1190
|
if (!text.trim()) {
|
|
1195
1191
|
fail(
|
|
1196
|
-
'[mem] Usage: claude-mem-lite save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2] [--lesson T] [--closes-deferred 1,D#42] [--supersedes 8754,E#10524] — content may also be passed via --text/--content "<text>"',
|
|
1192
|
+
'[mem] Usage: claude-mem-lite save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2] [--lesson T] [--closes-deferred 1,D#42] [--supersedes 8754,E#10524] [--force] — content may also be passed via --text/--content "<text>"',
|
|
1197
1193
|
);
|
|
1198
1194
|
return;
|
|
1199
1195
|
}
|
|
@@ -1306,6 +1302,9 @@ function cmdSave(db, args) {
|
|
|
1306
1302
|
files: saveFiles,
|
|
1307
1303
|
lesson_learned: rawLesson,
|
|
1308
1304
|
supersedes: supersedesIds || undefined,
|
|
1305
|
+
// Opt-in only. The 5-minute near-duplicate window stays the default; this is for a
|
|
1306
|
+
// caller who read "Skipped: similar to existing #N" and means the second one.
|
|
1307
|
+
force: flags.force === true || flags.force === 'true',
|
|
1309
1308
|
},
|
|
1310
1309
|
{ closesTokens, project },
|
|
1311
1310
|
));
|
|
@@ -3142,6 +3141,8 @@ Commands:
|
|
|
3142
3141
|
--project P Project name
|
|
3143
3142
|
--files f1,f2 Comma-separated file paths
|
|
3144
3143
|
--lesson T Lesson learned (≤500 chars; alias: --lesson-learned)
|
|
3144
|
+
--force Save even if it looks like a near-duplicate of something
|
|
3145
|
+
saved in the last 5 minutes (that guard is on by default)
|
|
3145
3146
|
--closes-deferred 1,D#42 Close deferred items in same transaction
|
|
3146
3147
|
|
|
3147
3148
|
defer <action> First-class deferred work (v2.70+)
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-mem-lite",
|
|
9
|
-
"version": "6.
|
|
9
|
+
"version": "6.5.0",
|
|
10
10
|
"os": [
|
|
11
11
|
"darwin",
|
|
12
12
|
"linux",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.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
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"format": "prettier --write \"**/*.{mjs,js}\"",
|
|
25
25
|
"format:check": "prettier --check \"**/*.{mjs,js}\"",
|
|
26
26
|
"dead-code": "knip",
|
|
27
|
+
"validate:manifests": "node scripts/validate-plugin-manifests.mjs",
|
|
27
28
|
"audit:metrics": "node scripts/audit-metrics.mjs --md",
|
|
28
29
|
"audit:baseline": "node scripts/audit-metrics.mjs --run-tests --md",
|
|
29
30
|
"test": "vitest run",
|
|
@@ -89,6 +90,7 @@
|
|
|
89
90
|
"lib/err-sampler.mjs",
|
|
90
91
|
"lib/hook-telemetry.mjs",
|
|
91
92
|
"lib/resolve-data-dir.mjs",
|
|
93
|
+
"lib/data-paths.mjs",
|
|
92
94
|
"lib/export-columns.mjs",
|
|
93
95
|
"lib/file-intel.mjs",
|
|
94
96
|
"lib/reread-guard.mjs",
|
|
@@ -103,6 +105,8 @@
|
|
|
103
105
|
"lib/binding-probe.mjs",
|
|
104
106
|
"lib/install-shape.mjs",
|
|
105
107
|
"lib/schema-skew.mjs",
|
|
108
|
+
"lib/db-unusable.mjs",
|
|
109
|
+
"lib/record-once.mjs",
|
|
106
110
|
"lib/hook-stdin.mjs",
|
|
107
111
|
"lib/plugin-key.mjs",
|
|
108
112
|
"lib/hook-stdout.mjs",
|
package/schema.mjs
CHANGED
|
@@ -7,22 +7,21 @@ import { homedir } from 'os';
|
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, chmodSync } from 'fs';
|
|
9
9
|
import { OBS_FTS_COLUMNS, debugCatch } from './utils.mjs';
|
|
10
|
-
import { resolveDataDir } from './lib/resolve-data-dir.mjs';
|
|
11
10
|
// Imported, never re-declared: a hand-copied marker string is this repo's twin-drift
|
|
12
11
|
// class, and every consumer of the forward-incompat throw keys on this exact value.
|
|
13
12
|
// schema-skew.mjs imports nothing local, so this closes no cycle.
|
|
14
13
|
import { SCHEMA_SKEW_CODE } from './lib/schema-skew.mjs';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
export
|
|
14
|
+
import { isFtsCorruptionError } from './lib/db-unusable.mjs';
|
|
15
|
+
|
|
16
|
+
// The three location constants now live in lib/data-paths.mjs — a leaf module with no
|
|
17
|
+
// package imports — and are re-exported here so every existing importer is unchanged.
|
|
18
|
+
// This file statically imports better-sqlite3, so holding a path constant here made the
|
|
19
|
+
// native driver a load-time dependency of anything that wanted one; that is what put the
|
|
20
|
+
// Ed25519-verified repair path out of reach on a tree with no node_modules. Imported AND
|
|
21
|
+
// re-exported (not `export … from`) because schema.mjs uses DB_DIR / DB_PATH itself.
|
|
22
|
+
// See lib/data-paths.mjs and tests/repair-path-no-native-dep.test.mjs.
|
|
23
|
+
import { DB_DIR, DB_PATH, CODE_DIR } from './lib/data-paths.mjs';
|
|
24
|
+
export { DB_DIR, DB_PATH, CODE_DIR };
|
|
26
25
|
|
|
27
26
|
// Increment when schema changes (tables, columns, indexes, FTS, migrations)
|
|
28
27
|
//
|
|
@@ -1269,16 +1268,10 @@ export function isDbCorruptionError(err) {
|
|
|
1269
1268
|
return /SQLITE_CORRUPT|SQLITE_NOTADB|malformed|not a database|disk image/i.test(text);
|
|
1270
1269
|
}
|
|
1271
1270
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
* thing that separates them, and matching on message alone is what conflated them.
|
|
1277
|
-
* The remedy is rebuildFTS, which the FTS content lets us do losslessly.
|
|
1278
|
-
*/
|
|
1279
|
-
export function isFtsCorruptionError(err) {
|
|
1280
|
-
return /SQLITE_CORRUPT_VTAB/i.test(`${err?.code || ''}`);
|
|
1281
|
-
}
|
|
1271
|
+
// Definition moved to lib/db-unusable.mjs (v6.5.0) so the hook path's own classifier and this
|
|
1272
|
+
// one cannot drift; re-exported here because four call sites and a test import it from
|
|
1273
|
+
// schema.mjs. Same pattern as DB_DIR / DB_PATH above.
|
|
1274
|
+
export { isFtsCorruptionError };
|
|
1282
1275
|
|
|
1283
1276
|
/**
|
|
1284
1277
|
* ensureDb with corruption-gated WAL recovery. Was inlined in server.mjs only,
|
|
@@ -31,6 +31,7 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
31
31
|
import { dirname, join, isAbsolute } from 'node:path';
|
|
32
32
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
33
33
|
import { homedir } from 'node:os';
|
|
34
|
+
import { createHash } from 'node:crypto';
|
|
34
35
|
|
|
35
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
37
|
const INSTALL_DIR = join(__dirname, '..');
|
|
@@ -65,7 +66,16 @@ const DATA_DIR = MEM_DIR && isAbsolute(MEM_DIR) ? MEM_DIR : join(homedir(), '.cl
|
|
|
65
66
|
// `tests/runtime-dir-single-home.test.mjs` asserts this file still carries the rule.
|
|
66
67
|
const RUNTIME_DIR = join(DATA_DIR, 'runtime'); // runtime-dir:stays-put — serves swap-in-progress only; HOOK_RUNTIME_DIR carries the hook markers
|
|
67
68
|
const HOOK_RUNTIME_DIR = process.env.CLAUDE_MEM_RUNTIME_DIR || RUNTIME_DIR;
|
|
68
|
-
|
|
69
|
+
// Per CODE HOME, not per machine. The runtime dir is data-dir-relative and therefore SHARED
|
|
70
|
+
// by every install shape on this box — a plugin cache version, a managed ~/.claude-mem-lite,
|
|
71
|
+
// a dev checkout. With one global name, a failed heal attempt for root A silenced root B's
|
|
72
|
+
// heal for the next six hours, and the two are repaired by different commands. The suffix is
|
|
73
|
+
// derived from INSTALL_DIR, which is what `cli.mjs repair` below actually acts on.
|
|
74
|
+
//
|
|
75
|
+
// node:crypto only — the pure-`node:` charter above still holds. No migration: a pre-6.4.0
|
|
76
|
+
// unsuffixed marker is simply ignored, which costs at most one extra heal attempt once.
|
|
77
|
+
const INSTALL_KEY = createHash('sha256').update(INSTALL_DIR).digest('hex').slice(0, 12);
|
|
78
|
+
const HEAL_MARKER = join(HOOK_RUNTIME_DIR, `hook-launcher-lastheal-${INSTALL_KEY}`);
|
|
69
79
|
const HEAL_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
|
70
80
|
// Observable breakage state: written when the launcher degrades a broken install
|
|
71
81
|
// to exit 0, cleared once the install is confirmed healthy. `doctor` reads it so
|
|
@@ -89,6 +99,14 @@ const BROKEN_MARKER = join(HOOK_RUNTIME_DIR, 'hook-launcher-broken');
|
|
|
89
99
|
// SESSION-START only — never on the per-tool hot path, where an npm run would
|
|
90
100
|
// stall the user's edit.
|
|
91
101
|
// Marker dir: HOOK_RUNTIME_DIR (see its definition above for why it is override-aware).
|
|
102
|
+
// BOTH stay machine-wide, unlike HEAL_MARKER above, and the difference is the SUBJECT of the
|
|
103
|
+
// repair. HEAL_MARKER gates `cli.mjs repair`, which fixes THIS install dir. This pair gates
|
|
104
|
+
// the native-binding rebuild, and `install.mjs rebuildBinding()` iterates
|
|
105
|
+
// `shape.runtimeRoots` — "Every code home on this machine, not just the one this file sits
|
|
106
|
+
// in", as its own comment puts it, which is also what install.mjs tells the user. Keying it
|
|
107
|
+
// per code home would let N homes each spawn npm within one 6h window for a repair that
|
|
108
|
+
// already covered all of them. A first cut of this change did exactly that; pre-ship review
|
|
109
|
+
// caught that the justifying comment was false for the command it gates.
|
|
92
110
|
const NB_BROKEN_MARKER = join(HOOK_RUNTIME_DIR, 'native-binding-broken');
|
|
93
111
|
const NB_HEAL_MARKER = join(HOOK_RUNTIME_DIR, 'native-binding-lastheal');
|
|
94
112
|
// Literal, not imported: the pure-`node:` charter above forbids importing lib/
|
|
@@ -111,10 +129,13 @@ const CLI_REPAIR = `node ${join(INSTALL_DIR, 'cli.mjs')} repair`;
|
|
|
111
129
|
|
|
112
130
|
// Last-resort recovery string for users whose `cli.mjs repair` path
|
|
113
131
|
// itself failed (install.mjs missing / repair errored / retry still drifting).
|
|
114
|
-
// Duplicated
|
|
115
|
-
//
|
|
132
|
+
// Duplicated from install.mjs::MANUAL_TARBALL_FALLBACK — the pure-`node:` charter above
|
|
133
|
+
// forbids importing it, and this path is reachable exactly when local scripts are broken.
|
|
134
|
+
// Pinned to that constant by tests/manual-fallback-sync.test.mjs, which also fails if a
|
|
135
|
+
// fifth surface starts hardcoding its own. Resolves the latest RELEASE tag rather than
|
|
136
|
+
// `/tarball` (the default branch, i.e. unreleased WIP).
|
|
116
137
|
const TARBALL_FALLBACK =
|
|
117
|
-
'T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/
|
|
138
|
+
'T=$(mktemp -d) && U=$(curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/releases/latest | grep -o \'"tarball_url"[^,]*\' | cut -d\'"\' -f4) && curl -sL "$U" | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install';
|
|
118
139
|
|
|
119
140
|
const [, , entryArg, ...rest] = process.argv;
|
|
120
141
|
if (!entryArg) {
|
package/server.mjs
CHANGED
|
@@ -7,7 +7,15 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
7
7
|
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
8
8
|
import { truncate, typeIcon, inferProject, fmtDate, debugLog, debugCatch } from './utils.mjs';
|
|
9
9
|
import { resolveProject as _resolveProjectShared } from './project-utils.mjs';
|
|
10
|
-
import { ensureDbWithWalRecovery, DB_PATH, DB_DIR } from './schema.mjs';
|
|
10
|
+
import { ensureDbWithWalRecovery, DB_PATH, DB_DIR, CODE_DIR } from './schema.mjs';
|
|
11
|
+
// schema.mjs already imports this module for SCHEMA_SKEW_CODE, so it is in the graph before
|
|
12
|
+
// the DB is touched — a static import here adds no cold-start cost.
|
|
13
|
+
import {
|
|
14
|
+
isSchemaSkewError,
|
|
15
|
+
schemaSkewFromError,
|
|
16
|
+
schemaSkewRemedy,
|
|
17
|
+
formatSchemaSkewNotice,
|
|
18
|
+
} from './lib/schema-skew.mjs';
|
|
11
19
|
import { reRankWithContext, runIdleCleanup, buildServerInstructions } from './search-scoring.mjs';
|
|
12
20
|
import { searchObservationsHybrid } from './search-engine.mjs';
|
|
13
21
|
import {
|
|
@@ -153,6 +161,54 @@ try {
|
|
|
153
161
|
info: (m) => debugLog('INFO', 'server', m),
|
|
154
162
|
});
|
|
155
163
|
} catch (err) {
|
|
164
|
+
// Schema skew gets the same treatment as the CLI (mem-cli.mjs) and for the same reason:
|
|
165
|
+
// the raw message ends in `npm i -g claude-mem-lite@latest`, which repairs nothing on the
|
|
166
|
+
// plugin-cache install that actually hits this.
|
|
167
|
+
//
|
|
168
|
+
// This branch has to live HERE rather than in scripts/launch.mjs, which already knows how
|
|
169
|
+
// to format the notice. server.mjs opens the DB while it is being imported, so the throw
|
|
170
|
+
// is caught by this block and `process.exit(1)` runs before the launcher's own catch can
|
|
171
|
+
// ever see it — the launcher's handler is unreachable for this one error. Re-throwing
|
|
172
|
+
// instead would fix the plugin path and break the npm one, because install.mjs registers
|
|
173
|
+
// `claude mcp add ... -- node <SERVER_PATH>`, launching this file with no launcher above
|
|
174
|
+
// it to catch anything. Emitting from here covers both, and the launcher's copy stays as
|
|
175
|
+
// the handler for failures that happen before this module is reached.
|
|
176
|
+
if (isSchemaSkewError(err)) {
|
|
177
|
+
const skew = schemaSkewFromError(err) || { dbVersion: null, binaryVersion: null };
|
|
178
|
+
let shape = { managed: false, activePluginVersion: null };
|
|
179
|
+
let dev = false;
|
|
180
|
+
try {
|
|
181
|
+
const [shapeMod, updateMod] = await Promise.all([
|
|
182
|
+
import('./lib/install-shape.mjs'),
|
|
183
|
+
import('./hook-update.mjs'),
|
|
184
|
+
]);
|
|
185
|
+
// CODE_DIR, not DB_DIR: `hasManagedCodeInstall` looks for server.mjs + hook.mjs inside
|
|
186
|
+
// whatever it is handed, and DB_DIR follows CLAUDE_MEM_DIR. Handing it the relocated DATA
|
|
187
|
+
// dir reports `managed: false` for a machine that has a managed install, so the remedy
|
|
188
|
+
// came out as "could not identify this install" or, with any plugin cache present, the
|
|
189
|
+
// plugin commands printed under a line naming ~/.claude-mem-lite. The three sibling call
|
|
190
|
+
// sites (mem-cli.mjs, hook.mjs, scripts/launch.mjs) all pass the CODE dir.
|
|
191
|
+
shape = shapeMod.detectInstallShape({ installDir: CODE_DIR });
|
|
192
|
+
dev = updateMod.isDevMode();
|
|
193
|
+
} catch {
|
|
194
|
+
/* shape unknown → schemaSkewRemedy answers 'unknown', which is its job */
|
|
195
|
+
}
|
|
196
|
+
const codeHome = process.env.CLAUDE_PLUGIN_ROOT || import.meta.dirname;
|
|
197
|
+
console.error(
|
|
198
|
+
formatSchemaSkewNotice({
|
|
199
|
+
dbVersion: skew.dbVersion,
|
|
200
|
+
binaryVersion: skew.binaryVersion,
|
|
201
|
+
remedy: schemaSkewRemedy({
|
|
202
|
+
managed: shape.managed,
|
|
203
|
+
activePluginVersion: shape.activePluginVersion,
|
|
204
|
+
dev,
|
|
205
|
+
root: codeHome,
|
|
206
|
+
}),
|
|
207
|
+
codeHome,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
156
212
|
// Fatal: log and exit with descriptive message (Claude Code shows stderr)
|
|
157
213
|
console.error(`[claude-mem-lite] FATAL: Database cannot be opened: ${err.message}`);
|
|
158
214
|
if (err.walRecoveryAttempted) {
|
|
@@ -1082,6 +1138,8 @@ server.registerTool(
|
|
|
1082
1138
|
files: args.files || [],
|
|
1083
1139
|
lesson_learned: clampSaveText(args.lesson_learned, SAVE_TEXT_LIMITS.lesson_learned),
|
|
1084
1140
|
supersedes: args.supersedes,
|
|
1141
|
+
// Opt-in only — the near-duplicate window stays the default. CLI parity: `--force`.
|
|
1142
|
+
force: args.force === true,
|
|
1085
1143
|
},
|
|
1086
1144
|
{ closesTokens: args.closes_deferred, project },
|
|
1087
1145
|
));
|
package/source-files.mjs
CHANGED
|
@@ -55,6 +55,11 @@ export const SOURCE_FILES = [
|
|
|
55
55
|
// AND hook scripts (pre-tool-recall / post-tool-recall) — ship it
|
|
56
56
|
// or auto-update leaves schema + every hook with ERR_MODULE_NOT_FOUND on each fire.
|
|
57
57
|
'lib/resolve-data-dir.mjs',
|
|
58
|
+
// DB_DIR / DB_PATH / CODE_DIR. Statically imported by schema.mjs (which re-exports all
|
|
59
|
+
// three) and by hook-update.mjs, which takes them from HERE so the verified repair path
|
|
60
|
+
// stays loadable without better-sqlite3. Missing from the manifest → auto-update leaves
|
|
61
|
+
// schema.mjs and the repair path with ERR_MODULE_NOT_FOUND on every fire.
|
|
62
|
+
'lib/data-paths.mjs',
|
|
58
63
|
// lib/ — statically imported by hook-llm.mjs (activity) + hook-handoff.mjs (git-state, task-reader);
|
|
59
64
|
// dynamically imported by hook.mjs (startup-dashboard) + mem-cli.mjs (doctor-benchmark, plan-reader).
|
|
60
65
|
'lib/activity.mjs',
|
|
@@ -132,6 +137,8 @@ export const SOURCE_FILES = [
|
|
|
132
137
|
// ERR_MODULE_NOT_FOUND on the command users run when something is already wrong.
|
|
133
138
|
'lib/install-shape.mjs',
|
|
134
139
|
'lib/schema-skew.mjs',
|
|
140
|
+
'lib/db-unusable.mjs',
|
|
141
|
+
'lib/record-once.mjs',
|
|
135
142
|
// Single-envelope stdout for hook processes — imported by hook.mjs. Claude Code
|
|
136
143
|
// parses hook stdout as ONE JSON document; missing from the manifest → an updated
|
|
137
144
|
// install throws ERR_MODULE_NOT_FOUND on every hook fire.
|