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 +182 -12
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +70 -10
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +155 -9
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/session-end.js +155 -9
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +155 -9
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +155 -9
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +155 -9
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/server/api/index.js +159 -9
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +159 -9
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +155 -9
- 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 +2 -2
- package/scripts/build.ts +1 -0
- package/src/cli/index.ts +23 -1
- package/src/core/embedder.ts +25 -8
- 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
|
*/
|
|
@@ -2590,13 +2634,15 @@ var VectorStore = class {
|
|
|
2590
2634
|
};
|
|
2591
2635
|
|
|
2592
2636
|
// src/core/embedder.ts
|
|
2593
|
-
import { pipeline } from "@
|
|
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 = "
|
|
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
|
-
|
|
2608
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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();
|