claude-memory-layer 1.0.29 → 1.0.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-layer",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "Claude Code plugin that learns from conversations to provide personalized assistance",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -40,18 +40,16 @@
40
40
  "node": ">=18.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@hono/node-server": "^1.13.0",
43
+ "@hono/node-server": "^1.19.14",
44
+ "@huggingface/transformers": "^3.8.1",
44
45
  "@lancedb/lancedb": "^0.5.0",
45
46
  "@modelcontextprotocol/sdk": "^1.29.0",
46
47
  "better-sqlite3": "^12.6.2",
47
48
  "commander": "^12.0.0",
48
- "hono": "^4.0.0",
49
+ "hono": "^4.12.18",
49
50
  "mongodb": "^6.14.0",
50
51
  "zod": "^3.22.0"
51
52
  },
52
- "optionalDependencies": {
53
- "@huggingface/transformers": "^3.8.1"
54
- },
55
53
  "devDependencies": {
56
54
  "@types/better-sqlite3": "^7.6.13",
57
55
  "@types/node": "^20.11.0",
@@ -45,8 +45,6 @@ function detectCudaMajor({ env = process.env, execFileSyncImpl = execFileSync }
45
45
 
46
46
  function isSkipRequested(env = process.env) {
47
47
  if (env[SKIP_ENV] === '1' || env[REPAIR_GUARD_ENV] === '1') return true;
48
- if (env.npm_config_optional === 'false') return true;
49
- if (String(env.npm_config_omit || '').split(',').map((item) => item.trim()).includes('optional')) return true;
50
48
  return false;
51
49
  }
52
50
 
@@ -60,10 +58,9 @@ function isEmbeddingBackendAvailable(rootDir = process.cwd()) {
60
58
  }
61
59
  }
62
60
 
63
- function shouldAttemptAutoInstall({ platform, arch, cudaMajor, transformersAvailable, skipRequested }) {
61
+ function shouldAttemptAutoInstall({ platform, arch, transformersAvailable, skipRequested }) {
64
62
  return platform === 'linux' &&
65
63
  arch === 'x64' &&
66
- cudaMajor === 11 &&
67
64
  !transformersAvailable &&
68
65
  !skipRequested;
69
66
  }
@@ -104,7 +101,7 @@ function runPostinstall({
104
101
  return { attempted: false, cudaMajor, transformersAvailable, skipRequested };
105
102
  }
106
103
 
107
- log('[claude-memory-layer] CUDA 11 detected and optional embedding backend is missing. Installing CPU-only embedding backend...');
104
+ log('[claude-memory-layer] Required embedding backend is missing on Linux x64. Repairing with CPU-only ONNX Runtime...');
108
105
 
109
106
  const npmCommand = platform === 'win32' ? 'npm.cmd' : 'npm';
110
107
  const result = spawnSyncImpl(npmCommand, createNpmInstallArgs(), {
@@ -114,13 +111,13 @@ function runPostinstall({
114
111
  });
115
112
 
116
113
  if (result.error || result.status !== 0) {
117
- warn('[claude-memory-layer] Optional embedding backend repair failed. Claude Memory Layer is installed, but semantic/vector embeddings may be unavailable until you run:');
114
+ warn('[claude-memory-layer] Required embedding backend repair failed. Semantic/vector embeddings may be unavailable until you run:');
118
115
  warn(` ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest`);
119
116
  if (result.error) warn(` ${result.error.message}`);
120
117
  return { attempted: true, success: false, cudaMajor, transformersAvailable, skipRequested };
121
118
  }
122
119
 
123
- log('[claude-memory-layer] Optional embedding backend installed with CPU-only ONNX Runtime.');
120
+ log('[claude-memory-layer] Required embedding backend repaired with CPU-only ONNX Runtime.');
124
121
  return { attempted: true, success: true, cudaMajor, transformersAvailable, skipRequested };
125
122
  }
126
123
 
@@ -59,6 +59,10 @@ import {
59
59
  renderExternalMarketContextReport,
60
60
  type ExternalMarketProvider
61
61
  } from '../../core/external-market-context.js';
62
+ import {
63
+ DEFAULT_EMBEDDING_FALLBACK_MODEL,
64
+ DEFAULT_EMBEDDING_MODEL
65
+ } from '../../extensions/vector/embedder.js';
62
66
 
63
67
  // ============================================================
64
68
  // Hook Installation Utilities
@@ -1025,7 +1029,7 @@ program
1025
1029
  .option('-l, --limit <number>', 'Limit messages per session')
1026
1030
  .option('--session-limit <number>', 'Limit recent matching sessions to import')
1027
1031
  .option('-f, --force', 'Force reimport: delete existing events and reimport with turn_id grouping')
1028
- .option('--embedding-model <name>', 'Embedding model override (default: jinaai/jina-embeddings-v5-text-nano-text-matching, or env CLAUDE_MEMORY_EMBEDDING_MODEL; fallback env: CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL)')
1032
+ .option('--embedding-model <name>', `Embedding model override (default: ${DEFAULT_EMBEDDING_MODEL}, or env CLAUDE_MEMORY_EMBEDDING_MODEL; fallback: ${DEFAULT_EMBEDDING_FALLBACK_MODEL} or env CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL)`)
1029
1033
  .option('-v, --verbose', 'Show detailed progress')
1030
1034
  .action(async (options) => {
1031
1035
  const startTime = Date.now();
package/src/core/types.ts CHANGED
@@ -152,8 +152,8 @@ export const ConfigSchema = z.object({
152
152
  }).default({}),
153
153
  embedding: z.object({
154
154
  provider: z.enum(['local', 'openai']).default('local'),
155
- model: z.string().default('jinaai/jina-embeddings-v5-text-nano-text-matching'),
156
- openaiModel: z.string().default('jinaai/jina-embeddings-v5-text-nano-text-matching'),
155
+ model: z.string().default('Xenova/multilingual-e5-small'),
156
+ openaiModel: z.string().default('Xenova/multilingual-e5-small'),
157
157
  batchSize: z.number().default(32)
158
158
  }).default({}),
159
159
  retrieval: z.object({
@@ -14,13 +14,16 @@ type FeatureExtractionPipelineFactory = (
14
14
  model: string
15
15
  ) => Promise<NonNullable<Embedder['pipeline']>>;
16
16
 
17
+ export const DEFAULT_EMBEDDING_MODEL = 'Xenova/multilingual-e5-small';
18
+ export const DEFAULT_EMBEDDING_FALLBACK_MODEL = 'intfloat/multilingual-e5-small';
19
+
17
20
  export class Embedder {
18
21
  private pipeline: ((input: string, options?: Record<string, unknown>) => Promise<{ data: Float32Array }>) | null = null;
19
22
  private readonly modelName: string;
20
23
  private activeModelName: string;
21
24
  private initialized = false;
22
25
 
23
- constructor(modelName: string = 'jinaai/jina-embeddings-v5-text-nano-text-matching') {
26
+ constructor(modelName: string = DEFAULT_EMBEDDING_MODEL) {
24
27
  this.modelName = modelName;
25
28
  this.activeModelName = modelName;
26
29
  }
@@ -31,7 +34,16 @@ export class Embedder {
31
34
  async initialize(): Promise<void> {
32
35
  if (this.initialized) return;
33
36
 
34
- const pipeline = await withSuppressedKnownTransformersWarnings(() => loadTransformersPipeline());
37
+ const pipeline = await withSuppressedKnownTransformersWarnings(async () => {
38
+ try {
39
+ return await loadTransformersPipeline();
40
+ } catch (error) {
41
+ if (isMissingTransformersDependencyError(error)) {
42
+ throw createEmbeddingBackendUnavailableError(error);
43
+ }
44
+ throw error;
45
+ }
46
+ });
35
47
 
36
48
  try {
37
49
  this.pipeline = await withSuppressedKnownTransformersWarnings(() => pipeline('feature-extraction', this.modelName));
@@ -39,7 +51,7 @@ export class Embedder {
39
51
  this.initialized = true;
40
52
  return;
41
53
  } catch (primaryError) {
42
- const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || 'onnx-community/embeddinggemma-300m-ONNX';
54
+ const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || DEFAULT_EMBEDDING_FALLBACK_MODEL;
43
55
  if (fallbackModel === this.modelName) {
44
56
  throw primaryError;
45
57
  }
@@ -186,6 +198,31 @@ export function isKnownBenignTransformersWarning(message: string): boolean {
186
198
  message.includes('dtype not specified for "model"');
187
199
  }
188
200
 
201
+ export function isMissingTransformersDependencyError(error: unknown): boolean {
202
+ const maybeError = error as { code?: unknown; message?: unknown } | null;
203
+ const message = typeof maybeError?.message === 'string' ? maybeError.message : '';
204
+ return maybeError?.code === 'ERR_MODULE_NOT_FOUND' &&
205
+ message.includes("@huggingface/transformers");
206
+ }
207
+
208
+ export function createEmbeddingBackendUnavailableError(cause: unknown): Error & { cause?: unknown } {
209
+ const error = new Error(
210
+ [
211
+ 'Required embedding backend is not installed.',
212
+ '',
213
+ 'Claude Memory Layer requires @huggingface/transformers for local semantic/vector embeddings.',
214
+ 'The backend runs on CPU-only ONNX Runtime; CUDA is not required.',
215
+ 'Reinstall globally with:',
216
+ ' ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest',
217
+ '',
218
+ 'If you are inside a local checkout or package directory, repair only the backend with:',
219
+ ' ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install --no-save --no-package-lock --omit=dev @huggingface/transformers@3.8.1'
220
+ ].join('\n')
221
+ ) as Error & { cause?: unknown };
222
+ error.cause = cause;
223
+ return error;
224
+ }
225
+
189
226
  async function loadTransformersPipeline(): Promise<FeatureExtractionPipelineFactory> {
190
227
  // Keep @huggingface/transformers lazy so importing MemoryService or pure
191
228
  // adapter helpers does not eagerly dlopen onnxruntime native bindings.
@@ -16,6 +16,7 @@ type SpawnCall = {
16
16
  type PostinstallEmbeddingBackend = {
17
17
  EMBEDDING_BACKEND_PACKAGE: string;
18
18
  parseCudaMajor(output: string): number | null;
19
+ isSkipRequested(env: NodeJS.ProcessEnv): boolean;
19
20
  shouldAttemptAutoInstall(input: {
20
21
  platform: NodeJS.Platform;
21
22
  arch: string;
@@ -42,20 +43,29 @@ function loadPostinstallModule(): PostinstallEmbeddingBackend {
42
43
  }
43
44
 
44
45
  describe('embedding backend postinstall repair', () => {
45
- it('keeps the install-time embedding backend optional and registers postinstall repair', () => {
46
+ it('requires the embedding backend at install time and registers a broken-install repair hook', () => {
46
47
  const pkg = JSON.parse(readFileSync('package.json', 'utf-8')) as {
47
48
  scripts: Record<string, string>;
48
49
  dependencies?: Record<string, string>;
49
50
  optionalDependencies?: Record<string, string>;
50
51
  };
51
52
 
52
- expect(pkg.dependencies).not.toHaveProperty('@huggingface/transformers');
53
- expect(pkg.optionalDependencies).toMatchObject({
53
+ expect(pkg.dependencies).toMatchObject({
54
54
  '@huggingface/transformers': '^3.8.1'
55
55
  });
56
+ expect(pkg.optionalDependencies ?? {}).not.toHaveProperty('@huggingface/transformers');
56
57
  expect(pkg.scripts.postinstall).toBe('node scripts/postinstall-embedding-backend.cjs');
57
58
  });
58
59
 
60
+ it('only skips required-backend repair when the explicit repair guards are set', () => {
61
+ const postinstall = loadPostinstallModule();
62
+
63
+ expect(postinstall.isSkipRequested({ CLAUDE_MEMORY_LAYER_SKIP_EMBEDDING_POSTINSTALL: '1' })).toBe(true);
64
+ expect(postinstall.isSkipRequested({ CLAUDE_MEMORY_LAYER_EMBEDDING_POSTINSTALL_REPAIR: '1' })).toBe(true);
65
+ expect(postinstall.isSkipRequested({ npm_config_optional: 'false' })).toBe(false);
66
+ expect(postinstall.isSkipRequested({ npm_config_omit: 'optional' })).toBe(false);
67
+ });
68
+
59
69
  it('detects CUDA major version from nvcc output', () => {
60
70
  const postinstall = loadPostinstallModule();
61
71
 
@@ -64,7 +74,7 @@ describe('embedding backend postinstall repair', () => {
64
74
  expect(postinstall.parseCudaMajor('nvcc: NVIDIA (R) Cuda compiler driver')).toBeNull();
65
75
  });
66
76
 
67
- it('only auto-installs the embedding backend for Linux x64 CUDA 11 when it is missing', () => {
77
+ it('auto-installs a missing embedding backend for Linux x64 even when CUDA cannot be detected', () => {
68
78
  const postinstall = loadPostinstallModule();
69
79
 
70
80
  expect(postinstall.shouldAttemptAutoInstall({
@@ -78,31 +88,39 @@ describe('embedding backend postinstall repair', () => {
78
88
  expect(postinstall.shouldAttemptAutoInstall({
79
89
  platform: 'linux',
80
90
  arch: 'x64',
81
- cudaMajor: 11,
82
- transformersAvailable: true,
91
+ cudaMajor: null,
92
+ transformersAvailable: false,
83
93
  skipRequested: false
84
- })).toBe(false);
94
+ })).toBe(true);
85
95
 
86
96
  expect(postinstall.shouldAttemptAutoInstall({
87
97
  platform: 'linux',
88
- arch: 'arm64',
89
- cudaMajor: 11,
98
+ arch: 'x64',
99
+ cudaMajor: 12,
90
100
  transformersAvailable: false,
91
101
  skipRequested: false
92
- })).toBe(false);
102
+ })).toBe(true);
93
103
 
94
104
  expect(postinstall.shouldAttemptAutoInstall({
95
- platform: 'darwin',
105
+ platform: 'linux',
96
106
  arch: 'x64',
97
- cudaMajor: 11,
98
- transformersAvailable: false,
107
+ cudaMajor: null,
108
+ transformersAvailable: true,
99
109
  skipRequested: false
100
110
  })).toBe(false);
101
111
 
102
112
  expect(postinstall.shouldAttemptAutoInstall({
103
113
  platform: 'linux',
114
+ arch: 'arm64',
115
+ cudaMajor: null,
116
+ transformersAvailable: false,
117
+ skipRequested: false
118
+ })).toBe(false);
119
+
120
+ expect(postinstall.shouldAttemptAutoInstall({
121
+ platform: 'darwin',
104
122
  arch: 'x64',
105
- cudaMajor: 12,
123
+ cudaMajor: null,
106
124
  transformersAvailable: false,
107
125
  skipRequested: false
108
126
  })).toBe(false);
@@ -110,7 +128,7 @@ describe('embedding backend postinstall repair', () => {
110
128
  expect(postinstall.shouldAttemptAutoInstall({
111
129
  platform: 'linux',
112
130
  arch: 'x64',
113
- cudaMajor: 11,
131
+ cudaMajor: null,
114
132
  transformersAvailable: false,
115
133
  skipRequested: true
116
134
  })).toBe(false);
@@ -132,7 +150,7 @@ describe('embedding backend postinstall repair', () => {
132
150
  ]);
133
151
  });
134
152
 
135
- it('runs the automatic repair command when Linux x64 CUDA 11 skipped the optional backend', () => {
153
+ it('runs the repair command when Linux x64 is missing the required backend without detectable CUDA', () => {
136
154
  const postinstall = loadPostinstallModule();
137
155
  const rootDir = mkdtempSync(join(tmpdir(), 'cml-postinstall-test-'));
138
156
  const calls: SpawnCall[] = [];
@@ -145,7 +163,7 @@ describe('embedding backend postinstall repair', () => {
145
163
  env: {},
146
164
  platform: 'linux',
147
165
  arch: 'x64',
148
- execFileSyncImpl: () => 'Cuda compilation tools, release 11.8, V11.8.89',
166
+ execFileSyncImpl: () => '',
149
167
  spawnSyncImpl: (cmd, args, options) => {
150
168
  calls.push({ cmd, args, env: options.env });
151
169
  return { status: 0 };
@@ -154,7 +172,7 @@ describe('embedding backend postinstall repair', () => {
154
172
  warn: () => undefined
155
173
  });
156
174
 
157
- expect(result).toMatchObject({ attempted: true, success: true, cudaMajor: 11, transformersAvailable: false });
175
+ expect(result).toMatchObject({ attempted: true, success: true, cudaMajor: null, transformersAvailable: false });
158
176
  expect(calls).toHaveLength(1);
159
177
  expect(calls[0]?.cmd).toBe('npm');
160
178
  expect(calls[0]?.args).toEqual(postinstall.createNpmInstallArgs());
@@ -1,11 +1,42 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import {
4
+ DEFAULT_EMBEDDING_MODEL,
5
+ Embedder,
6
+ createEmbeddingBackendUnavailableError,
4
7
  isKnownBenignTransformersWarning,
8
+ isMissingTransformersDependencyError,
5
9
  withSuppressedKnownTransformersWarnings
6
10
  } from '../../src/extensions/vector/embedder.js';
11
+ import { ConfigSchema } from '../../src/core/types.js';
7
12
 
8
13
  describe('Embedder warning suppression', () => {
14
+ it('uses a CPU-friendly multilingual Korean-capable default embedding model', () => {
15
+ expect(DEFAULT_EMBEDDING_MODEL).toBe('Xenova/multilingual-e5-small');
16
+ expect(new Embedder().getModelName()).toBe(DEFAULT_EMBEDDING_MODEL);
17
+
18
+ const parsedConfig = ConfigSchema.parse({});
19
+ expect(parsedConfig.embedding.model).toBe(DEFAULT_EMBEDDING_MODEL);
20
+ expect(parsedConfig.embedding.openaiModel).toBe(DEFAULT_EMBEDDING_MODEL);
21
+ });
22
+
23
+ it('turns missing required @huggingface/transformers errors into actionable install guidance', () => {
24
+ const missingBackendError = Object.assign(
25
+ new Error("Cannot find package '@huggingface/transformers' imported from /tmp/dist/cli/index.js"),
26
+ { code: 'ERR_MODULE_NOT_FOUND' }
27
+ );
28
+
29
+ expect(isMissingTransformersDependencyError(missingBackendError)).toBe(true);
30
+ expect(isMissingTransformersDependencyError(new Error('network failure'))).toBe(false);
31
+
32
+ const friendly = createEmbeddingBackendUnavailableError(missingBackendError);
33
+ expect(friendly.message).toContain('Required embedding backend is not installed');
34
+ expect(friendly.message).toContain('Claude Memory Layer requires @huggingface/transformers');
35
+ expect(friendly.message).toContain('ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest');
36
+ expect(friendly.message).toContain('ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install --no-save --no-package-lock --omit=dev @huggingface/transformers@3.8.1');
37
+ expect(friendly.cause).toBe(missingBackendError);
38
+ });
39
+
9
40
  it('recognizes known benign transformer warnings', () => {
10
41
  expect(isKnownBenignTransformersWarning('Unknown model class "eurobert", attempting to construct from base class.')).toBe(true);
11
42
  expect(isKnownBenignTransformersWarning('dtype not specified for "model". Using the default dtype (fp32).')).toBe(true);