claude-memory-layer 1.0.19 → 1.0.21

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/dist/cli/index.js CHANGED
@@ -1613,6 +1613,33 @@ var SQLiteEventStore = class {
1613
1613
  ids
1614
1614
  );
1615
1615
  }
1616
+ /**
1617
+ * Clear embedding outbox (used for embedding model migration)
1618
+ */
1619
+ async clearEmbeddingOutbox() {
1620
+ await this.initialize();
1621
+ sqliteRun(this.db, `DELETE FROM embedding_outbox`);
1622
+ }
1623
+ /**
1624
+ * Count total events
1625
+ */
1626
+ async countEvents() {
1627
+ await this.initialize();
1628
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
1629
+ return row?.count || 0;
1630
+ }
1631
+ /**
1632
+ * Get events page in timestamp ascending order (stable migration/reindex scans)
1633
+ */
1634
+ async getEventsPage(limit = 1e3, offset = 0) {
1635
+ await this.initialize();
1636
+ const rows = sqliteAll(
1637
+ this.db,
1638
+ `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
1639
+ [limit, offset]
1640
+ );
1641
+ return rows.map(this.rowToEvent);
1642
+ }
1616
1643
  /**
1617
1644
  * Mark outbox items as failed
1618
1645
  */
@@ -2578,6 +2605,23 @@ var VectorStore = class {
2578
2605
  const result = await this.table.countRows();
2579
2606
  return result;
2580
2607
  }
2608
+ /**
2609
+ * Clear all vectors (used for embedding model migration)
2610
+ */
2611
+ async clearAll() {
2612
+ await this.initialize();
2613
+ if (!this.db)
2614
+ return;
2615
+ try {
2616
+ if (typeof this.db.dropTable === "function") {
2617
+ await this.db.dropTable(this.tableName);
2618
+ } else if (typeof this.db.drop_table === "function") {
2619
+ await this.db.drop_table(this.tableName);
2620
+ }
2621
+ } catch {
2622
+ }
2623
+ this.table = null;
2624
+ }
2581
2625
  /**
2582
2626
  * Check if vector exists for event
2583
2627
  */
@@ -2590,13 +2634,15 @@ var VectorStore = class {
2590
2634
  };
2591
2635
 
2592
2636
  // src/core/embedder.ts
2593
- import { pipeline } from "@xenova/transformers";
2637
+ import { pipeline } from "@huggingface/transformers";
2594
2638
  var Embedder = class {
2595
2639
  pipeline = null;
2596
2640
  modelName;
2641
+ activeModelName;
2597
2642
  initialized = false;
2598
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2643
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano-text-matching") {
2599
2644
  this.modelName = modelName;
2645
+ this.activeModelName = modelName;
2600
2646
  }
2601
2647
  /**
2602
2648
  * Initialize the embedding pipeline
@@ -2604,8 +2650,21 @@ var Embedder = class {
2604
2650
  async initialize() {
2605
2651
  if (this.initialized)
2606
2652
  return;
2607
- this.pipeline = await pipeline("feature-extraction", this.modelName);
2608
- this.initialized = true;
2653
+ try {
2654
+ this.pipeline = await pipeline("feature-extraction", this.modelName);
2655
+ this.activeModelName = this.modelName;
2656
+ this.initialized = true;
2657
+ return;
2658
+ } catch (primaryError) {
2659
+ const fallbackModel = process.env.CLAUDE_MEMORY_EMBEDDING_FALLBACK_MODEL || "onnx-community/embeddinggemma-300m-ONNX";
2660
+ if (fallbackModel === this.modelName) {
2661
+ throw primaryError;
2662
+ }
2663
+ console.warn(`[Embedder] Primary model failed (${this.modelName}). Falling back to ${fallbackModel}`);
2664
+ this.pipeline = await pipeline("feature-extraction", fallbackModel);
2665
+ this.activeModelName = fallbackModel;
2666
+ this.initialized = true;
2667
+ }
2609
2668
  }
2610
2669
  /**
2611
2670
  * Generate embedding for a single text
@@ -2622,7 +2681,7 @@ var Embedder = class {
2622
2681
  const vector = Array.from(output.data);
2623
2682
  return {
2624
2683
  vector,
2625
- model: this.modelName,
2684
+ model: this.activeModelName,
2626
2685
  dimensions: vector.length
2627
2686
  };
2628
2687
  }
@@ -2646,7 +2705,7 @@ var Embedder = class {
2646
2705
  const vector = Array.from(output.data);
2647
2706
  results.push({
2648
2707
  vector,
2649
- model: this.modelName,
2708
+ model: this.activeModelName,
2650
2709
  dimensions: vector.length
2651
2710
  });
2652
2711
  }
@@ -2670,13 +2729,14 @@ var Embedder = class {
2670
2729
  * Get model name
2671
2730
  */
2672
2731
  getModelName() {
2673
- return this.modelName;
2732
+ return this.activeModelName;
2674
2733
  }
2675
2734
  };
2676
2735
  var defaultEmbedder = null;
2677
2736
  function getDefaultEmbedder() {
2737
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2678
2738
  if (!defaultEmbedder) {
2679
- defaultEmbedder = new Embedder();
2739
+ defaultEmbedder = new Embedder(envModel || void 0);
2680
2740
  }
2681
2741
  return defaultEmbedder;
2682
2742
  }
@@ -5932,8 +5992,10 @@ var MemoryService = class {
5932
5992
  readOnly;
5933
5993
  lightweightMode;
5934
5994
  mdMirror;
5995
+ storagePath;
5935
5996
  constructor(config) {
5936
5997
  const storagePath = this.expandPath(config.storagePath);
5998
+ this.storagePath = storagePath;
5937
5999
  this.readOnly = config.readOnly ?? false;
5938
6000
  this.lightweightMode = config.lightweightMode ?? false;
5939
6001
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -5969,7 +6031,8 @@ var MemoryService = class {
5969
6031
  );
5970
6032
  }
5971
6033
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
5972
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
6034
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6035
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
5973
6036
  this.matcher = getDefaultMatcher();
5974
6037
  this.retriever = createRetriever(
5975
6038
  this.sqliteStore,
@@ -6913,6 +6976,89 @@ var MemoryService = class {
6913
6976
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6914
6977
  this.graduation.recordAccess(eventId, sessionId, confidence);
6915
6978
  }
6979
+ getEmbeddingModelName() {
6980
+ return this.embedder.getModelName();
6981
+ }
6982
+ /**
6983
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
6984
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
6985
+ */
6986
+ async ensureEmbeddingModelForImport(options) {
6987
+ await this.initialize();
6988
+ const currentModel = this.getEmbeddingModelName();
6989
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
6990
+ let previousModel = null;
6991
+ try {
6992
+ if (fs4.existsSync(metaPath)) {
6993
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
6994
+ previousModel = parsed?.model || null;
6995
+ }
6996
+ } catch {
6997
+ previousModel = null;
6998
+ }
6999
+ const stats = await this.getStats();
7000
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
7001
+ if (!previousModel && !hasExistingVectors) {
7002
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
7003
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
7004
+ }
7005
+ const modelChanged = previousModel !== currentModel;
7006
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
7007
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
7008
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
7009
+ }
7010
+ if (options?.autoMigrate === false) {
7011
+ return {
7012
+ changed: true,
7013
+ previousModel,
7014
+ currentModel,
7015
+ enqueued: 0,
7016
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7017
+ };
7018
+ }
7019
+ const wasRunning = this.vectorWorker?.isRunning() || false;
7020
+ if (wasRunning)
7021
+ this.vectorWorker?.stop();
7022
+ await this.vectorStore.clearAll();
7023
+ await this.sqliteStore.clearEmbeddingOutbox();
7024
+ const pageSize = 1e3;
7025
+ let offset = 0;
7026
+ let enqueued = 0;
7027
+ while (true) {
7028
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
7029
+ if (page.length === 0)
7030
+ break;
7031
+ for (const event of page) {
7032
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
7033
+ enqueued += 1;
7034
+ }
7035
+ offset += page.length;
7036
+ if (page.length < pageSize)
7037
+ break;
7038
+ }
7039
+ fs4.writeFileSync(
7040
+ metaPath,
7041
+ JSON.stringify(
7042
+ {
7043
+ model: currentModel,
7044
+ previousModel,
7045
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7046
+ enqueued
7047
+ },
7048
+ null,
7049
+ 2
7050
+ )
7051
+ );
7052
+ if (wasRunning)
7053
+ this.vectorWorker?.start();
7054
+ return {
7055
+ changed: true,
7056
+ previousModel,
7057
+ currentModel,
7058
+ enqueued,
7059
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7060
+ };
7061
+ }
6916
7062
  /**
6917
7063
  * Backward-compatible alias used by some hooks
6918
7064
  */
@@ -7899,6 +8045,7 @@ eventsRouter.get("/", async (c) => {
7899
8045
  const eventType = c.req.query("type");
7900
8046
  const level = c.req.query("level");
7901
8047
  const sort = c.req.query("sort") || "recent";
8048
+ const q = (c.req.query("q") || "").trim().toLowerCase();
7902
8049
  const limit = parseInt(c.req.query("limit") || "100", 10);
7903
8050
  const offset = parseInt(c.req.query("offset") || "0", 10);
7904
8051
  const memoryService = getServiceFromQuery(c);
@@ -7916,6 +8063,9 @@ eventsRouter.get("/", async (c) => {
7916
8063
  if (eventType) {
7917
8064
  events = events.filter((e) => e.eventType === eventType);
7918
8065
  }
8066
+ if (q) {
8067
+ events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
8068
+ }
7919
8069
  if (sort === "accessed") {
7920
8070
  events.sort((a, b) => {
7921
8071
  const aTime = a.last_accessed_at || "";
@@ -9559,7 +9709,7 @@ function getHooksConfig(pluginPath) {
9559
9709
  };
9560
9710
  }
9561
9711
  var program = new Command();
9562
- program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.19");
9712
+ program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.21");
9563
9713
  program.command("install").description("Install hooks into Claude Code settings").option("--path <path>", "Custom plugin path (defaults to auto-detect)").action(async (options) => {
9564
9714
  try {
9565
9715
  const pluginPath = options.path || getPluginPath();
@@ -10051,9 +10201,12 @@ program.command("organize-import [sourceDir]").description("Import existing mark
10051
10201
  process.exit(1);
10052
10202
  }
10053
10203
  });
10054
- program.command("import").description("Import existing Claude Code conversation history").option("-p, --project <path>", "Import from specific project path").option("-s, --session <file>", "Import specific session file (JSONL)").option("-a, --all", "Import all sessions from all projects").option("-l, --limit <number>", "Limit messages per session").option("-f, --force", "Force reimport: delete existing events and reimport with turn_id grouping").option("-v, --verbose", "Show detailed progress").action(async (options) => {
10204
+ program.command("import").description("Import existing Claude Code conversation history").option("-p, --project <path>", "Import from specific project path").option("-s, --session <file>", "Import specific session file (JSONL)").option("-a, --all", "Import all sessions from all projects").option("-l, --limit <number>", "Limit messages per session").option("-f, --force", "Force reimport: delete existing events and reimport with turn_id grouping").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)").option("-v, --verbose", "Show detailed progress").action(async (options) => {
10055
10205
  const startTime = Date.now();
10056
10206
  const targetProjectPath = options.project || process.cwd();
10207
+ if (options.embeddingModel) {
10208
+ process.env.CLAUDE_MEMORY_EMBEDDING_MODEL = options.embeddingModel;
10209
+ }
10057
10210
  const service = getMemoryServiceForProject(targetProjectPath);
10058
10211
  const importer = createSessionHistoryImporter(service);
10059
10212
  const importOpts = {
@@ -10065,7 +10218,16 @@ program.command("import").description("Import existing Claude Code conversation
10065
10218
  try {
10066
10219
  console.log("\n\u23F3 Initializing memory service...");
10067
10220
  await service.initialize();
10068
- console.log(" \u2705 Ready\n");
10221
+ console.log(` \u2705 Ready (embedder: ${service.getEmbeddingModelName()})
10222
+ `);
10223
+ const migration = await service.ensureEmbeddingModelForImport({ autoMigrate: true });
10224
+ if (migration.changed) {
10225
+ console.log("\u{1F501} Embedding model migration detected/required");
10226
+ console.log(` Previous: ${migration.previousModel || "legacy-unknown"}`);
10227
+ console.log(` Current: ${migration.currentModel}`);
10228
+ console.log(` Re-queued embeddings: ${migration.enqueued}`);
10229
+ console.log(" (Import will continue and process embeddings with the new model)\n");
10230
+ }
10069
10231
  if (options.force) {
10070
10232
  console.log("\u{1F504} Force mode: existing events will be deleted and reimported with turn_id grouping\n");
10071
10233
  }
@@ -10088,6 +10250,14 @@ program.command("import").description("Import existing Claude Code conversation
10088
10250
  const globalService = getDefaultMemoryService();
10089
10251
  const globalImporter = createSessionHistoryImporter(globalService);
10090
10252
  await globalService.initialize();
10253
+ console.log(` \u2705 Global service ready (embedder: ${globalService.getEmbeddingModelName()})`);
10254
+ const globalMigration = await globalService.ensureEmbeddingModelForImport({ autoMigrate: true });
10255
+ if (globalMigration.changed) {
10256
+ console.log("\u{1F501} Global embedding migration detected");
10257
+ console.log(` Previous: ${globalMigration.previousModel || "legacy-unknown"}`);
10258
+ console.log(` Current: ${globalMigration.currentModel}`);
10259
+ console.log(` Re-queued embeddings: ${globalMigration.enqueued}`);
10260
+ }
10091
10261
  result = await globalImporter.importAll(importOpts);
10092
10262
  console.log("\n\u{1F9E0} Processing embeddings...");
10093
10263
  const embedCount2 = await globalService.processPendingEmbeddings();