claude-memory-layer 1.0.19 → 1.0.20

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
  */
@@ -2595,7 +2639,7 @@ var Embedder = class {
2595
2639
  pipeline = null;
2596
2640
  modelName;
2597
2641
  initialized = false;
2598
- constructor(modelName = "Xenova/all-MiniLM-L6-v2") {
2642
+ constructor(modelName = "jinaai/jina-embeddings-v5-text-nano") {
2599
2643
  this.modelName = modelName;
2600
2644
  }
2601
2645
  /**
@@ -2675,8 +2719,9 @@ var Embedder = class {
2675
2719
  };
2676
2720
  var defaultEmbedder = null;
2677
2721
  function getDefaultEmbedder() {
2722
+ const envModel = process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
2678
2723
  if (!defaultEmbedder) {
2679
- defaultEmbedder = new Embedder();
2724
+ defaultEmbedder = new Embedder(envModel || void 0);
2680
2725
  }
2681
2726
  return defaultEmbedder;
2682
2727
  }
@@ -5932,8 +5977,10 @@ var MemoryService = class {
5932
5977
  readOnly;
5933
5978
  lightweightMode;
5934
5979
  mdMirror;
5980
+ storagePath;
5935
5981
  constructor(config) {
5936
5982
  const storagePath = this.expandPath(config.storagePath);
5983
+ this.storagePath = storagePath;
5937
5984
  this.readOnly = config.readOnly ?? false;
5938
5985
  this.lightweightMode = config.lightweightMode ?? false;
5939
5986
  this.mdMirror = new MarkdownMirror2(process.cwd());
@@ -5969,7 +6016,8 @@ var MemoryService = class {
5969
6016
  );
5970
6017
  }
5971
6018
  this.vectorStore = new VectorStore(path3.join(storagePath, "vectors"));
5972
- this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
6019
+ const embeddingModel = config.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6020
+ this.embedder = embeddingModel ? new Embedder(embeddingModel) : getDefaultEmbedder();
5973
6021
  this.matcher = getDefaultMatcher();
5974
6022
  this.retriever = createRetriever(
5975
6023
  this.sqliteStore,
@@ -6913,6 +6961,89 @@ var MemoryService = class {
6913
6961
  recordMemoryAccess(eventId, sessionId, confidence = 1) {
6914
6962
  this.graduation.recordAccess(eventId, sessionId, confidence);
6915
6963
  }
6964
+ getEmbeddingModelName() {
6965
+ return this.embedder.getModelName();
6966
+ }
6967
+ /**
6968
+ * Ensure embedding model metadata is in sync and optionally migrate vectors.
6969
+ * Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
6970
+ */
6971
+ async ensureEmbeddingModelForImport(options) {
6972
+ await this.initialize();
6973
+ const currentModel = this.getEmbeddingModelName();
6974
+ const metaPath = path3.join(this.storagePath, "embedding-meta.json");
6975
+ let previousModel = null;
6976
+ try {
6977
+ if (fs4.existsSync(metaPath)) {
6978
+ const parsed = JSON.parse(fs4.readFileSync(metaPath, "utf-8"));
6979
+ previousModel = parsed?.model || null;
6980
+ }
6981
+ } catch {
6982
+ previousModel = null;
6983
+ }
6984
+ const stats = await this.getStats();
6985
+ const hasExistingVectors = (stats.vectorCount || 0) > 0;
6986
+ if (!previousModel && !hasExistingVectors) {
6987
+ fs4.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
6988
+ return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: "initialized-meta" };
6989
+ }
6990
+ const modelChanged = previousModel !== currentModel;
6991
+ const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
6992
+ if (!modelChanged && !legacyUnknownButVectorsExist) {
6993
+ return { changed: false, previousModel, currentModel, enqueued: 0 };
6994
+ }
6995
+ if (options?.autoMigrate === false) {
6996
+ return {
6997
+ changed: true,
6998
+ previousModel,
6999
+ currentModel,
7000
+ enqueued: 0,
7001
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7002
+ };
7003
+ }
7004
+ const wasRunning = this.vectorWorker?.isRunning() || false;
7005
+ if (wasRunning)
7006
+ this.vectorWorker?.stop();
7007
+ await this.vectorStore.clearAll();
7008
+ await this.sqliteStore.clearEmbeddingOutbox();
7009
+ const pageSize = 1e3;
7010
+ let offset = 0;
7011
+ let enqueued = 0;
7012
+ while (true) {
7013
+ const page = await this.sqliteStore.getEventsPage(pageSize, offset);
7014
+ if (page.length === 0)
7015
+ break;
7016
+ for (const event of page) {
7017
+ await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
7018
+ enqueued += 1;
7019
+ }
7020
+ offset += page.length;
7021
+ if (page.length < pageSize)
7022
+ break;
7023
+ }
7024
+ fs4.writeFileSync(
7025
+ metaPath,
7026
+ JSON.stringify(
7027
+ {
7028
+ model: currentModel,
7029
+ previousModel,
7030
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
7031
+ enqueued
7032
+ },
7033
+ null,
7034
+ 2
7035
+ )
7036
+ );
7037
+ if (wasRunning)
7038
+ this.vectorWorker?.start();
7039
+ return {
7040
+ changed: true,
7041
+ previousModel,
7042
+ currentModel,
7043
+ enqueued,
7044
+ reason: legacyUnknownButVectorsExist ? "legacy-vectors-without-meta" : "model-mismatch"
7045
+ };
7046
+ }
6916
7047
  /**
6917
7048
  * Backward-compatible alias used by some hooks
6918
7049
  */
@@ -7899,6 +8030,7 @@ eventsRouter.get("/", async (c) => {
7899
8030
  const eventType = c.req.query("type");
7900
8031
  const level = c.req.query("level");
7901
8032
  const sort = c.req.query("sort") || "recent";
8033
+ const q = (c.req.query("q") || "").trim().toLowerCase();
7902
8034
  const limit = parseInt(c.req.query("limit") || "100", 10);
7903
8035
  const offset = parseInt(c.req.query("offset") || "0", 10);
7904
8036
  const memoryService = getServiceFromQuery(c);
@@ -7916,6 +8048,9 @@ eventsRouter.get("/", async (c) => {
7916
8048
  if (eventType) {
7917
8049
  events = events.filter((e) => e.eventType === eventType);
7918
8050
  }
8051
+ if (q) {
8052
+ events = events.filter((e) => (e.content || "").toLowerCase().includes(q));
8053
+ }
7919
8054
  if (sort === "accessed") {
7920
8055
  events.sort((a, b) => {
7921
8056
  const aTime = a.last_accessed_at || "";
@@ -9559,7 +9694,7 @@ function getHooksConfig(pluginPath) {
9559
9694
  };
9560
9695
  }
9561
9696
  var program = new Command();
9562
- program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.19");
9697
+ program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.20");
9563
9698
  program.command("install").description("Install hooks into Claude Code settings").option("--path <path>", "Custom plugin path (defaults to auto-detect)").action(async (options) => {
9564
9699
  try {
9565
9700
  const pluginPath = options.path || getPluginPath();
@@ -10051,9 +10186,12 @@ program.command("organize-import [sourceDir]").description("Import existing mark
10051
10186
  process.exit(1);
10052
10187
  }
10053
10188
  });
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) => {
10189
+ 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, or env CLAUDE_MEMORY_EMBEDDING_MODEL)").option("-v, --verbose", "Show detailed progress").action(async (options) => {
10055
10190
  const startTime = Date.now();
10056
10191
  const targetProjectPath = options.project || process.cwd();
10192
+ if (options.embeddingModel) {
10193
+ process.env.CLAUDE_MEMORY_EMBEDDING_MODEL = options.embeddingModel;
10194
+ }
10057
10195
  const service = getMemoryServiceForProject(targetProjectPath);
10058
10196
  const importer = createSessionHistoryImporter(service);
10059
10197
  const importOpts = {
@@ -10065,7 +10203,16 @@ program.command("import").description("Import existing Claude Code conversation
10065
10203
  try {
10066
10204
  console.log("\n\u23F3 Initializing memory service...");
10067
10205
  await service.initialize();
10068
- console.log(" \u2705 Ready\n");
10206
+ console.log(` \u2705 Ready (embedder: ${service.getEmbeddingModelName()})
10207
+ `);
10208
+ const migration = await service.ensureEmbeddingModelForImport({ autoMigrate: true });
10209
+ if (migration.changed) {
10210
+ console.log("\u{1F501} Embedding model migration detected/required");
10211
+ console.log(` Previous: ${migration.previousModel || "legacy-unknown"}`);
10212
+ console.log(` Current: ${migration.currentModel}`);
10213
+ console.log(` Re-queued embeddings: ${migration.enqueued}`);
10214
+ console.log(" (Import will continue and process embeddings with the new model)\n");
10215
+ }
10069
10216
  if (options.force) {
10070
10217
  console.log("\u{1F504} Force mode: existing events will be deleted and reimported with turn_id grouping\n");
10071
10218
  }
@@ -10088,6 +10235,14 @@ program.command("import").description("Import existing Claude Code conversation
10088
10235
  const globalService = getDefaultMemoryService();
10089
10236
  const globalImporter = createSessionHistoryImporter(globalService);
10090
10237
  await globalService.initialize();
10238
+ console.log(` \u2705 Global service ready (embedder: ${globalService.getEmbeddingModelName()})`);
10239
+ const globalMigration = await globalService.ensureEmbeddingModelForImport({ autoMigrate: true });
10240
+ if (globalMigration.changed) {
10241
+ console.log("\u{1F501} Global embedding migration detected");
10242
+ console.log(` Previous: ${globalMigration.previousModel || "legacy-unknown"}`);
10243
+ console.log(` Current: ${globalMigration.currentModel}`);
10244
+ console.log(` Re-queued embeddings: ${globalMigration.enqueued}`);
10245
+ }
10091
10246
  result = await globalImporter.importAll(importOpts);
10092
10247
  console.log("\n\u{1F9E0} Processing embeddings...");
10093
10248
  const embedCount2 = await globalService.processPendingEmbeddings();