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 +161 -6
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +49 -4
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +134 -3
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/session-end.js +134 -3
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +134 -3
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +134 -3
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +134 -3
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/server/api/index.js +138 -3
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +138 -3
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +134 -3
- package/dist/services/memory-service.js.map +2 -2
- package/dist/ui/app.js +126 -0
- package/dist/ui/index.html +39 -0
- package/dist/ui/style.css +7 -0
- package/package.json +1 -1
- package/src/cli/index.ts +23 -1
- package/src/core/embedder.ts +3 -2
- package/src/core/sqlite-event-store.ts +32 -0
- package/src/core/types.ts +2 -2
- package/src/core/vector-store.ts +20 -0
- package/src/server/api/events.ts +6 -0
- package/src/services/memory-service.ts +112 -2
- package/src/ui/app.js +126 -0
- package/src/ui/index.html +39 -0
- package/src/ui/style.css +7 -0
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 = "
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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();
|