claude-memory-layer 1.0.37 → 1.0.38
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 +497 -84
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +309 -22
- package/dist/core/index.js.map +3 -3
- package/dist/hooks/post-tool-use.js +399 -74
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +357 -67
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +357 -67
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +357 -67
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +398 -73
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +357 -67
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +318 -28
- package/dist/index.js.map +3 -3
- package/dist/mcp/index.js +403 -78
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +361 -71
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +361 -71
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +357 -67
- package/dist/services/memory-service.js.map +4 -4
- package/package.json +1 -1
package/dist/server/api/index.js
CHANGED
|
@@ -15,8 +15,8 @@ import { Hono } from "hono";
|
|
|
15
15
|
import * as os5 from "os";
|
|
16
16
|
|
|
17
17
|
// src/core/engine/memory-service-composition.ts
|
|
18
|
-
import * as
|
|
19
|
-
import * as
|
|
18
|
+
import * as os2 from "os";
|
|
19
|
+
import * as path7 from "path";
|
|
20
20
|
|
|
21
21
|
// src/core/metadata-extractor.ts
|
|
22
22
|
function createToolObservationEmbedding(toolName, metadata, success) {
|
|
@@ -1527,8 +1527,8 @@ function createEndlessMemoryServices(options) {
|
|
|
1527
1527
|
}
|
|
1528
1528
|
|
|
1529
1529
|
// src/core/engine/memory-engine-services.ts
|
|
1530
|
-
import * as
|
|
1531
|
-
import * as
|
|
1530
|
+
import * as fs6 from "fs";
|
|
1531
|
+
import * as path5 from "path";
|
|
1532
1532
|
|
|
1533
1533
|
// src/extensions/vector/embedder.ts
|
|
1534
1534
|
var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
|
|
@@ -2206,14 +2206,43 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2206
2206
|
return `${sessionId}:${contentHash}`;
|
|
2207
2207
|
}
|
|
2208
2208
|
|
|
2209
|
+
// src/core/sqlite-event-store.ts
|
|
2210
|
+
import * as nodePath2 from "path";
|
|
2211
|
+
|
|
2212
|
+
// src/core/registry/project-path.ts
|
|
2213
|
+
import * as crypto2 from "crypto";
|
|
2214
|
+
import * as fs3 from "fs";
|
|
2215
|
+
import * as os from "os";
|
|
2216
|
+
import * as path3 from "path";
|
|
2217
|
+
function normalizeProjectPath(projectPath) {
|
|
2218
|
+
const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
2219
|
+
try {
|
|
2220
|
+
return fs3.realpathSync(expanded);
|
|
2221
|
+
} catch {
|
|
2222
|
+
return path3.resolve(expanded);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function hashProjectPath(projectPath) {
|
|
2226
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
2227
|
+
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
2228
|
+
}
|
|
2229
|
+
function getProjectStoragePath(projectPath) {
|
|
2230
|
+
const hash = hashProjectPath(projectPath);
|
|
2231
|
+
return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
|
|
2232
|
+
}
|
|
2233
|
+
function resolveProjectStoragePath(projectOrHash) {
|
|
2234
|
+
const isHash = /^[a-f0-9]{8}$/.test(projectOrHash);
|
|
2235
|
+
return isHash ? path3.join(os.homedir(), ".claude-code", "memory", "projects", projectOrHash) : getProjectStoragePath(projectOrHash);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2209
2238
|
// src/core/sqlite-wrapper.ts
|
|
2210
2239
|
import Database from "better-sqlite3";
|
|
2211
|
-
import * as
|
|
2240
|
+
import * as fs4 from "fs";
|
|
2212
2241
|
import * as nodePath from "path";
|
|
2213
2242
|
function createSQLiteDatabase(path13, options) {
|
|
2214
2243
|
const dir = nodePath.dirname(path13);
|
|
2215
|
-
if (!
|
|
2216
|
-
|
|
2244
|
+
if (!fs4.existsSync(dir)) {
|
|
2245
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2217
2246
|
}
|
|
2218
2247
|
const db = new Database(path13, {
|
|
2219
2248
|
readonly: options?.readonly ?? false
|
|
@@ -2262,8 +2291,8 @@ function toSQLiteTimestamp(date) {
|
|
|
2262
2291
|
}
|
|
2263
2292
|
|
|
2264
2293
|
// src/core/markdown-mirror.ts
|
|
2265
|
-
import * as
|
|
2266
|
-
import * as
|
|
2294
|
+
import * as fs5 from "fs/promises";
|
|
2295
|
+
import * as path4 from "path";
|
|
2267
2296
|
var DEFAULT_NAMESPACE = "default";
|
|
2268
2297
|
var DEFAULT_CATEGORY = "uncategorized";
|
|
2269
2298
|
function sanitizeSegment2(input, fallback) {
|
|
@@ -2292,7 +2321,7 @@ function buildMirrorPath2(rootDir, event) {
|
|
|
2292
2321
|
const yyyy = d.getFullYear();
|
|
2293
2322
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
2294
2323
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
2295
|
-
return
|
|
2324
|
+
return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
|
|
2296
2325
|
}
|
|
2297
2326
|
function formatMirrorEntry(event) {
|
|
2298
2327
|
const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
|
|
@@ -2313,8 +2342,8 @@ var MarkdownMirror2 = class {
|
|
|
2313
2342
|
}
|
|
2314
2343
|
async append(event) {
|
|
2315
2344
|
const outPath = buildMirrorPath2(this.rootDir, event);
|
|
2316
|
-
await
|
|
2317
|
-
await
|
|
2345
|
+
await fs5.mkdir(path4.dirname(outPath), { recursive: true });
|
|
2346
|
+
await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
|
|
2318
2347
|
return outPath;
|
|
2319
2348
|
}
|
|
2320
2349
|
};
|
|
@@ -2335,6 +2364,135 @@ function emptyOutboxRecoveryResult() {
|
|
|
2335
2364
|
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2336
2365
|
};
|
|
2337
2366
|
}
|
|
2367
|
+
function isRecord(value) {
|
|
2368
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2369
|
+
}
|
|
2370
|
+
function getNestedRecord(root, path13) {
|
|
2371
|
+
let cursor = root;
|
|
2372
|
+
for (const key of path13) {
|
|
2373
|
+
if (!isRecord(cursor))
|
|
2374
|
+
return void 0;
|
|
2375
|
+
cursor = cursor[key];
|
|
2376
|
+
}
|
|
2377
|
+
return isRecord(cursor) ? cursor : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
function getNestedString(root, path13) {
|
|
2380
|
+
let cursor = root;
|
|
2381
|
+
for (const key of path13) {
|
|
2382
|
+
if (!isRecord(cursor))
|
|
2383
|
+
return void 0;
|
|
2384
|
+
cursor = cursor[key];
|
|
2385
|
+
}
|
|
2386
|
+
return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
|
|
2387
|
+
}
|
|
2388
|
+
function metadataProjectHash(metadata) {
|
|
2389
|
+
return getNestedString(metadata, ["scope", "project", "hash"]);
|
|
2390
|
+
}
|
|
2391
|
+
function metadataProjectPaths(metadata) {
|
|
2392
|
+
const candidates = [
|
|
2393
|
+
getNestedString(metadata, ["projectPath"]),
|
|
2394
|
+
getNestedString(metadata, ["sourceProjectPath"]),
|
|
2395
|
+
getNestedString(metadata, ["scope", "project", "path"])
|
|
2396
|
+
];
|
|
2397
|
+
const paths = [];
|
|
2398
|
+
for (const value of candidates) {
|
|
2399
|
+
if (value && !paths.includes(value))
|
|
2400
|
+
paths.push(value);
|
|
2401
|
+
}
|
|
2402
|
+
return paths;
|
|
2403
|
+
}
|
|
2404
|
+
function metadataProjectPath(metadata) {
|
|
2405
|
+
return metadataProjectPaths(metadata)[0];
|
|
2406
|
+
}
|
|
2407
|
+
function isActiveQuarantinedMetadata(metadata) {
|
|
2408
|
+
const quarantine = getNestedRecord(metadata, ["quarantine"]);
|
|
2409
|
+
return quarantine?.status === "active";
|
|
2410
|
+
}
|
|
2411
|
+
function activeQuarantineStatusExpression(column = "metadata") {
|
|
2412
|
+
return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
|
|
2413
|
+
}
|
|
2414
|
+
function notActiveQuarantinedSql(column = "metadata") {
|
|
2415
|
+
return `${activeQuarantineStatusExpression(column)} != 'active'`;
|
|
2416
|
+
}
|
|
2417
|
+
function maybeQuarantinePredicate(options, column = "metadata") {
|
|
2418
|
+
return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
|
|
2419
|
+
}
|
|
2420
|
+
function safeParseMetadataValue(value) {
|
|
2421
|
+
if (!value)
|
|
2422
|
+
return void 0;
|
|
2423
|
+
if (typeof value === "object")
|
|
2424
|
+
return isRecord(value) ? value : void 0;
|
|
2425
|
+
if (typeof value !== "string")
|
|
2426
|
+
return void 0;
|
|
2427
|
+
try {
|
|
2428
|
+
const parsed = JSON.parse(value);
|
|
2429
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2430
|
+
} catch {
|
|
2431
|
+
return void 0;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
function isImportedOrLegacyScopedMetadata(metadata) {
|
|
2435
|
+
if (!metadata)
|
|
2436
|
+
return false;
|
|
2437
|
+
return Boolean(
|
|
2438
|
+
metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
function addMetadataTag(metadata, tag) {
|
|
2442
|
+
const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
|
|
2443
|
+
if (!current.includes(tag))
|
|
2444
|
+
metadata.tags = [...current, tag];
|
|
2445
|
+
}
|
|
2446
|
+
function buildRepairResult(projectHash, dryRun) {
|
|
2447
|
+
return {
|
|
2448
|
+
dryRun,
|
|
2449
|
+
projectHash,
|
|
2450
|
+
scanned: 0,
|
|
2451
|
+
repaired: 0,
|
|
2452
|
+
quarantined: 0,
|
|
2453
|
+
alreadyScoped: 0,
|
|
2454
|
+
skipped: 0,
|
|
2455
|
+
samples: []
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
function normalizeRepoName(value) {
|
|
2459
|
+
return value.replace(/\.git$/i, "").trim().toLowerCase();
|
|
2460
|
+
}
|
|
2461
|
+
function projectBasename(projectPath) {
|
|
2462
|
+
if (!projectPath)
|
|
2463
|
+
return void 0;
|
|
2464
|
+
const trimmed = projectPath.replace(/[\\/]+$/, "");
|
|
2465
|
+
const basename3 = nodePath2.basename(trimmed);
|
|
2466
|
+
return basename3 ? normalizeRepoName(basename3) : void 0;
|
|
2467
|
+
}
|
|
2468
|
+
function isProjectScopeRepairExplanation(content) {
|
|
2469
|
+
const normalized = content.toLowerCase();
|
|
2470
|
+
const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
|
|
2471
|
+
const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
|
|
2472
|
+
return hasRepairContext && hasExplanationContext;
|
|
2473
|
+
}
|
|
2474
|
+
function hasConflictingContentProjectHint(content, projectPath) {
|
|
2475
|
+
const currentName = projectBasename(projectPath);
|
|
2476
|
+
if (!currentName)
|
|
2477
|
+
return false;
|
|
2478
|
+
if (isProjectScopeRepairExplanation(content))
|
|
2479
|
+
return false;
|
|
2480
|
+
const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
|
|
2481
|
+
let githubMatch;
|
|
2482
|
+
while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
|
|
2483
|
+
const repo = normalizeRepoName(githubMatch[2] || "");
|
|
2484
|
+
if (repo && repo !== currentName)
|
|
2485
|
+
return true;
|
|
2486
|
+
}
|
|
2487
|
+
const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
|
|
2488
|
+
let workspaceMatch;
|
|
2489
|
+
while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
|
|
2490
|
+
const repo = normalizeRepoName(workspaceMatch[1] || "");
|
|
2491
|
+
if (repo && repo !== currentName)
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2338
2496
|
var SQLiteEventStore = class {
|
|
2339
2497
|
db;
|
|
2340
2498
|
initialized = false;
|
|
@@ -2833,11 +2991,11 @@ var SQLiteEventStore = class {
|
|
|
2833
2991
|
/**
|
|
2834
2992
|
* Get events by session ID
|
|
2835
2993
|
*/
|
|
2836
|
-
async getSessionEvents(sessionId) {
|
|
2994
|
+
async getSessionEvents(sessionId, options) {
|
|
2837
2995
|
await this.initialize();
|
|
2838
2996
|
const rows = sqliteAll(
|
|
2839
2997
|
this.db,
|
|
2840
|
-
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
2998
|
+
`SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
2841
2999
|
[sessionId]
|
|
2842
3000
|
);
|
|
2843
3001
|
return rows.map(this.rowToEvent);
|
|
@@ -2845,11 +3003,11 @@ var SQLiteEventStore = class {
|
|
|
2845
3003
|
/**
|
|
2846
3004
|
* Get recent events
|
|
2847
3005
|
*/
|
|
2848
|
-
async getRecentEvents(limit = 100) {
|
|
3006
|
+
async getRecentEvents(limit = 100, options) {
|
|
2849
3007
|
await this.initialize();
|
|
2850
3008
|
const rows = sqliteAll(
|
|
2851
3009
|
this.db,
|
|
2852
|
-
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
3010
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
|
|
2853
3011
|
[limit]
|
|
2854
3012
|
);
|
|
2855
3013
|
return rows.map(this.rowToEvent);
|
|
@@ -2857,11 +3015,11 @@ var SQLiteEventStore = class {
|
|
|
2857
3015
|
/**
|
|
2858
3016
|
* Get event by ID
|
|
2859
3017
|
*/
|
|
2860
|
-
async getEvent(id) {
|
|
3018
|
+
async getEvent(id, options) {
|
|
2861
3019
|
await this.initialize();
|
|
2862
3020
|
const row = sqliteGet(
|
|
2863
3021
|
this.db,
|
|
2864
|
-
`SELECT * FROM events WHERE id =
|
|
3022
|
+
`SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
|
|
2865
3023
|
[id]
|
|
2866
3024
|
);
|
|
2867
3025
|
if (!row)
|
|
@@ -2871,11 +3029,11 @@ var SQLiteEventStore = class {
|
|
|
2871
3029
|
/**
|
|
2872
3030
|
* Get events since a timestamp (for sync)
|
|
2873
3031
|
*/
|
|
2874
|
-
async getEventsSince(timestamp, limit = 1e3) {
|
|
3032
|
+
async getEventsSince(timestamp, limit = 1e3, options) {
|
|
2875
3033
|
await this.initialize();
|
|
2876
3034
|
const rows = sqliteAll(
|
|
2877
3035
|
this.db,
|
|
2878
|
-
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
3036
|
+
`SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
|
|
2879
3037
|
[timestamp, limit]
|
|
2880
3038
|
);
|
|
2881
3039
|
return rows.map(this.rowToEvent);
|
|
@@ -2884,11 +3042,11 @@ var SQLiteEventStore = class {
|
|
|
2884
3042
|
* Get events since a SQLite rowid (for robust incremental replication).
|
|
2885
3043
|
* Rowid is monotonic for append-only tables, independent of client timestamps.
|
|
2886
3044
|
*/
|
|
2887
|
-
async getEventsSinceRowid(lastRowid, limit = 1e3) {
|
|
3045
|
+
async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
|
|
2888
3046
|
await this.initialize();
|
|
2889
3047
|
const rows = sqliteAll(
|
|
2890
3048
|
this.db,
|
|
2891
|
-
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
|
|
3049
|
+
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
|
|
2892
3050
|
[lastRowid, limit]
|
|
2893
3051
|
);
|
|
2894
3052
|
return rows.map((row) => ({
|
|
@@ -3123,19 +3281,19 @@ var SQLiteEventStore = class {
|
|
|
3123
3281
|
/**
|
|
3124
3282
|
* Count total events
|
|
3125
3283
|
*/
|
|
3126
|
-
async countEvents() {
|
|
3284
|
+
async countEvents(options) {
|
|
3127
3285
|
await this.initialize();
|
|
3128
|
-
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
3286
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
|
|
3129
3287
|
return row?.count || 0;
|
|
3130
3288
|
}
|
|
3131
3289
|
/**
|
|
3132
3290
|
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
3133
3291
|
*/
|
|
3134
|
-
async getEventsPage(limit = 1e3, offset = 0) {
|
|
3292
|
+
async getEventsPage(limit = 1e3, offset = 0, options) {
|
|
3135
3293
|
await this.initialize();
|
|
3136
3294
|
const rows = sqliteAll(
|
|
3137
3295
|
this.db,
|
|
3138
|
-
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3296
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3139
3297
|
[limit, offset]
|
|
3140
3298
|
);
|
|
3141
3299
|
return rows.map(this.rowToEvent);
|
|
@@ -3209,6 +3367,145 @@ var SQLiteEventStore = class {
|
|
|
3209
3367
|
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
3210
3368
|
return result;
|
|
3211
3369
|
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Repair legacy imported events that predate canonical project scope metadata.
|
|
3372
|
+
*
|
|
3373
|
+
* Same-project legacy rows are tagged with scope.project.hash. Rows that look
|
|
3374
|
+
* imported but cannot be proven to belong to this project are quarantined so
|
|
3375
|
+
* dashboard default reads/search do not surface cross-project contamination.
|
|
3376
|
+
*/
|
|
3377
|
+
async repairLegacyProjectScope(options = {}) {
|
|
3378
|
+
await this.initialize();
|
|
3379
|
+
const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
|
|
3380
|
+
if (!projectHash) {
|
|
3381
|
+
throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
|
|
3382
|
+
}
|
|
3383
|
+
if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
|
|
3384
|
+
throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
|
|
3385
|
+
}
|
|
3386
|
+
const dryRun = options.dryRun === true;
|
|
3387
|
+
const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
|
|
3388
|
+
const result = buildRepairResult(projectHash, dryRun);
|
|
3389
|
+
const rows = sqliteAll(
|
|
3390
|
+
this.db,
|
|
3391
|
+
`SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
|
|
3392
|
+
FROM events e
|
|
3393
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
3394
|
+
ORDER BY e.timestamp ASC`,
|
|
3395
|
+
[]
|
|
3396
|
+
);
|
|
3397
|
+
const sample = (entry) => {
|
|
3398
|
+
if (result.samples.length < 20)
|
|
3399
|
+
result.samples.push(entry);
|
|
3400
|
+
};
|
|
3401
|
+
for (const row of rows) {
|
|
3402
|
+
result.scanned++;
|
|
3403
|
+
let metadata = {};
|
|
3404
|
+
let metadataParseInvalid = false;
|
|
3405
|
+
if (row.metadata) {
|
|
3406
|
+
const parsed = safeParseMetadataValue(row.metadata);
|
|
3407
|
+
if (parsed) {
|
|
3408
|
+
metadata = parsed;
|
|
3409
|
+
} else {
|
|
3410
|
+
metadataParseInvalid = true;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
if (isActiveQuarantinedMetadata(metadata)) {
|
|
3414
|
+
result.skipped++;
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
const currentHash = metadataProjectHash(metadata);
|
|
3418
|
+
const explicitPath = metadataProjectPath(metadata);
|
|
3419
|
+
const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
|
|
3420
|
+
const candidatePaths = metadataProjectPaths(metadata);
|
|
3421
|
+
if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
|
|
3422
|
+
candidatePaths.push(sessionProjectPath);
|
|
3423
|
+
}
|
|
3424
|
+
const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
|
|
3425
|
+
const pathHashes = candidatePaths.map((candidate) => {
|
|
3426
|
+
try {
|
|
3427
|
+
return { path: candidate, hash: hashProjectPath(candidate) };
|
|
3428
|
+
} catch {
|
|
3429
|
+
return { path: candidate, hash: void 0 };
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3432
|
+
const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
|
|
3433
|
+
const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
|
|
3434
|
+
let action = "skipped";
|
|
3435
|
+
let reason;
|
|
3436
|
+
let observedProjectHash;
|
|
3437
|
+
if (foreignPath) {
|
|
3438
|
+
action = "quarantined";
|
|
3439
|
+
reason = "project-path-mismatch";
|
|
3440
|
+
observedProjectHash = foreignPath.hash;
|
|
3441
|
+
} else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
|
|
3442
|
+
action = "quarantined";
|
|
3443
|
+
reason = "content-project-mismatch";
|
|
3444
|
+
} else if (currentHash === projectHash) {
|
|
3445
|
+
result.alreadyScoped++;
|
|
3446
|
+
continue;
|
|
3447
|
+
} else if (currentHash && currentHash !== projectHash) {
|
|
3448
|
+
action = "quarantined";
|
|
3449
|
+
reason = "scope-hash-mismatch";
|
|
3450
|
+
observedProjectHash = currentHash;
|
|
3451
|
+
} else if (matchingPath) {
|
|
3452
|
+
action = "repaired";
|
|
3453
|
+
reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
|
|
3454
|
+
} else if (candidatePaths.length > 0) {
|
|
3455
|
+
action = "quarantined";
|
|
3456
|
+
reason = "project-path-mismatch";
|
|
3457
|
+
} else if (importedOrLegacy) {
|
|
3458
|
+
action = "quarantined";
|
|
3459
|
+
reason = "missing-project-scope";
|
|
3460
|
+
}
|
|
3461
|
+
if (action === "skipped" || !reason) {
|
|
3462
|
+
result.skipped++;
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
if (action === "repaired") {
|
|
3466
|
+
const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
|
|
3467
|
+
const project = isRecord(scope.project) ? { ...scope.project } : {};
|
|
3468
|
+
project.hash = projectHash;
|
|
3469
|
+
scope.project = project;
|
|
3470
|
+
metadata.scope = scope;
|
|
3471
|
+
metadata.repair = {
|
|
3472
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3473
|
+
legacyProjectScope: {
|
|
3474
|
+
action,
|
|
3475
|
+
reason,
|
|
3476
|
+
repairedAt: nowIso
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
addMetadataTag(metadata, `proj:${projectHash}`);
|
|
3480
|
+
result.repaired++;
|
|
3481
|
+
} else {
|
|
3482
|
+
metadata.quarantine = {
|
|
3483
|
+
...isRecord(metadata.quarantine) ? metadata.quarantine : {},
|
|
3484
|
+
status: "active",
|
|
3485
|
+
category: "project-scope",
|
|
3486
|
+
reason,
|
|
3487
|
+
detectedAt: nowIso,
|
|
3488
|
+
expectedProjectHash: projectHash,
|
|
3489
|
+
...observedProjectHash ? { observedProjectHash } : {}
|
|
3490
|
+
};
|
|
3491
|
+
metadata.repair = {
|
|
3492
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3493
|
+
legacyProjectScope: {
|
|
3494
|
+
action,
|
|
3495
|
+
reason,
|
|
3496
|
+
repairedAt: nowIso
|
|
3497
|
+
}
|
|
3498
|
+
};
|
|
3499
|
+
addMetadataTag(metadata, "quarantine:project-scope");
|
|
3500
|
+
result.quarantined++;
|
|
3501
|
+
}
|
|
3502
|
+
sample({ eventId: row.id, action, reason });
|
|
3503
|
+
if (!dryRun) {
|
|
3504
|
+
sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
return result;
|
|
3508
|
+
}
|
|
3212
3509
|
/**
|
|
3213
3510
|
* Get embedding/vector outbox health statistics
|
|
3214
3511
|
*/
|
|
@@ -3256,7 +3553,11 @@ var SQLiteEventStore = class {
|
|
|
3256
3553
|
await this.initialize();
|
|
3257
3554
|
const rows = sqliteAll(
|
|
3258
3555
|
this.db,
|
|
3259
|
-
`SELECT level, COUNT(*) as count
|
|
3556
|
+
`SELECT ml.level, COUNT(*) as count
|
|
3557
|
+
FROM memory_levels ml
|
|
3558
|
+
INNER JOIN events e ON e.id = ml.event_id
|
|
3559
|
+
WHERE ${notActiveQuarantinedSql("e.metadata")}
|
|
3560
|
+
GROUP BY ml.level`
|
|
3260
3561
|
);
|
|
3261
3562
|
return rows;
|
|
3262
3563
|
}
|
|
@@ -3272,6 +3573,7 @@ var SQLiteEventStore = class {
|
|
|
3272
3573
|
`SELECT e.* FROM events e
|
|
3273
3574
|
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
3274
3575
|
WHERE ml.level = ?
|
|
3576
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3275
3577
|
ORDER BY e.timestamp DESC
|
|
3276
3578
|
LIMIT ? OFFSET ?`,
|
|
3277
3579
|
[level, limit, offset]
|
|
@@ -3364,12 +3666,13 @@ var SQLiteEventStore = class {
|
|
|
3364
3666
|
/**
|
|
3365
3667
|
* Get most accessed memories (falls back to recent events if none accessed)
|
|
3366
3668
|
*/
|
|
3367
|
-
async getMostAccessed(limit = 10) {
|
|
3669
|
+
async getMostAccessed(limit = 10, options) {
|
|
3368
3670
|
await this.initialize();
|
|
3369
3671
|
let rows = sqliteAll(
|
|
3370
3672
|
this.db,
|
|
3371
3673
|
`SELECT * FROM events
|
|
3372
3674
|
WHERE access_count > 0
|
|
3675
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3373
3676
|
ORDER BY access_count DESC, last_accessed_at DESC
|
|
3374
3677
|
LIMIT ?`,
|
|
3375
3678
|
[limit]
|
|
@@ -3378,6 +3681,7 @@ var SQLiteEventStore = class {
|
|
|
3378
3681
|
rows = sqliteAll(
|
|
3379
3682
|
this.db,
|
|
3380
3683
|
`SELECT * FROM events
|
|
3684
|
+
WHERE ${maybeQuarantinePredicate(options)}
|
|
3381
3685
|
ORDER BY timestamp DESC
|
|
3382
3686
|
LIMIT ?`,
|
|
3383
3687
|
[limit]
|
|
@@ -3508,6 +3812,7 @@ var SQLiteEventStore = class {
|
|
|
3508
3812
|
FROM memory_helpfulness mh
|
|
3509
3813
|
JOIN events e ON e.id = mh.event_id
|
|
3510
3814
|
WHERE mh.measured_at IS NOT NULL
|
|
3815
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3511
3816
|
GROUP BY mh.event_id
|
|
3512
3817
|
ORDER BY avg_score DESC
|
|
3513
3818
|
LIMIT ?`,
|
|
@@ -3572,6 +3877,7 @@ var SQLiteEventStore = class {
|
|
|
3572
3877
|
FROM events_fts fts
|
|
3573
3878
|
JOIN events e ON e.id = fts.event_id
|
|
3574
3879
|
WHERE events_fts MATCH ?
|
|
3880
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3575
3881
|
ORDER BY fts.rank
|
|
3576
3882
|
LIMIT ?`,
|
|
3577
3883
|
[searchTerms, limit]
|
|
@@ -3586,6 +3892,7 @@ var SQLiteEventStore = class {
|
|
|
3586
3892
|
this.db,
|
|
3587
3893
|
`SELECT *, 0 as rank FROM events
|
|
3588
3894
|
WHERE content LIKE ?
|
|
3895
|
+
AND ${notActiveQuarantinedSql()}
|
|
3589
3896
|
ORDER BY timestamp DESC
|
|
3590
3897
|
LIMIT ?`,
|
|
3591
3898
|
[likePattern, limit]
|
|
@@ -3792,6 +4099,7 @@ var SQLiteEventStore = class {
|
|
|
3792
4099
|
`SELECT turn_id, MIN(timestamp) as min_ts
|
|
3793
4100
|
FROM events
|
|
3794
4101
|
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4102
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3795
4103
|
GROUP BY turn_id
|
|
3796
4104
|
ORDER BY min_ts DESC
|
|
3797
4105
|
LIMIT ? OFFSET ?`,
|
|
@@ -3799,7 +4107,7 @@ var SQLiteEventStore = class {
|
|
|
3799
4107
|
);
|
|
3800
4108
|
const turns = [];
|
|
3801
4109
|
for (const turnRow of turnRows) {
|
|
3802
|
-
const events = await this.getEventsByTurn(turnRow.turn_id);
|
|
4110
|
+
const events = await this.getEventsByTurn(turnRow.turn_id, options);
|
|
3803
4111
|
const promptEvent = events.find((e) => e.eventType === "user_prompt");
|
|
3804
4112
|
const toolEvents = events.filter((e) => e.eventType === "tool_observation");
|
|
3805
4113
|
const hasResponse = events.some((e) => e.eventType === "agent_response");
|
|
@@ -3818,11 +4126,11 @@ var SQLiteEventStore = class {
|
|
|
3818
4126
|
/**
|
|
3819
4127
|
* Get all events for a specific turn_id
|
|
3820
4128
|
*/
|
|
3821
|
-
async getEventsByTurn(turnId) {
|
|
4129
|
+
async getEventsByTurn(turnId, options) {
|
|
3822
4130
|
await this.initialize();
|
|
3823
4131
|
const rows = sqliteAll(
|
|
3824
4132
|
this.db,
|
|
3825
|
-
`SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
|
|
4133
|
+
`SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3826
4134
|
[turnId]
|
|
3827
4135
|
);
|
|
3828
4136
|
return rows.map(this.rowToEvent);
|
|
@@ -3830,13 +4138,14 @@ var SQLiteEventStore = class {
|
|
|
3830
4138
|
/**
|
|
3831
4139
|
* Count total turns for a session
|
|
3832
4140
|
*/
|
|
3833
|
-
async countSessionTurns(sessionId) {
|
|
4141
|
+
async countSessionTurns(sessionId, options) {
|
|
3834
4142
|
await this.initialize();
|
|
3835
4143
|
const row = sqliteGet(
|
|
3836
4144
|
this.db,
|
|
3837
4145
|
`SELECT COUNT(DISTINCT turn_id) as count
|
|
3838
4146
|
FROM events
|
|
3839
|
-
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4147
|
+
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4148
|
+
AND ${maybeQuarantinePredicate(options)}`,
|
|
3840
4149
|
[sessionId]
|
|
3841
4150
|
);
|
|
3842
4151
|
return row?.count || 0;
|
|
@@ -3928,7 +4237,7 @@ var SQLiteEventStore = class {
|
|
|
3928
4237
|
content: row.content,
|
|
3929
4238
|
canonicalKey: row.canonical_key,
|
|
3930
4239
|
dedupeKey: row.dedupe_key,
|
|
3931
|
-
metadata:
|
|
4240
|
+
metadata: safeParseMetadataValue(row.metadata)
|
|
3932
4241
|
};
|
|
3933
4242
|
if (row.access_count !== void 0) {
|
|
3934
4243
|
event.access_count = row.access_count;
|
|
@@ -4523,6 +4832,10 @@ var MemoryQueryService = class {
|
|
|
4523
4832
|
await this.initialize();
|
|
4524
4833
|
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
4525
4834
|
}
|
|
4835
|
+
async repairLegacyProjectScope(options) {
|
|
4836
|
+
await this.initialize();
|
|
4837
|
+
return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
|
|
4838
|
+
}
|
|
4526
4839
|
async getStats() {
|
|
4527
4840
|
await this.initialize();
|
|
4528
4841
|
const deps = this.getStatsDeps();
|
|
@@ -6145,18 +6458,18 @@ function assertDefaultRetrieverStore(eventStore) {
|
|
|
6145
6458
|
function createMemoryEngineServices(options) {
|
|
6146
6459
|
const factories = options.factories ?? {};
|
|
6147
6460
|
const storagePath = options.storagePath;
|
|
6148
|
-
if (!options.readOnly && !
|
|
6149
|
-
|
|
6461
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
6462
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
6150
6463
|
}
|
|
6151
6464
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
6152
|
-
|
|
6465
|
+
path5.join(storagePath, "events.sqlite"),
|
|
6153
6466
|
{
|
|
6154
6467
|
readonly: options.readOnly,
|
|
6155
6468
|
markdownMirrorRoot: storagePath
|
|
6156
6469
|
}
|
|
6157
6470
|
);
|
|
6158
6471
|
const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
|
|
6159
|
-
|
|
6472
|
+
path5.join(storagePath, "vectors")
|
|
6160
6473
|
);
|
|
6161
6474
|
const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6162
6475
|
const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
|
|
@@ -6567,8 +6880,8 @@ function createMemoryRuntimeService(deps) {
|
|
|
6567
6880
|
}
|
|
6568
6881
|
|
|
6569
6882
|
// src/extensions/shared-memory/shared-memory-services.ts
|
|
6570
|
-
import * as
|
|
6571
|
-
import * as
|
|
6883
|
+
import * as fs7 from "fs";
|
|
6884
|
+
import * as path6 from "path";
|
|
6572
6885
|
|
|
6573
6886
|
// src/core/shared-event-store.ts
|
|
6574
6887
|
var SharedEventStore = class {
|
|
@@ -7256,7 +7569,7 @@ var SharedMemoryServices = class {
|
|
|
7256
7569
|
this.ensureDirectory(sharedPath, { allowCreate: true });
|
|
7257
7570
|
const store = await this.openStore(sharedPath);
|
|
7258
7571
|
this.sharedVectorStore = this.factories.createSharedVectorStore(
|
|
7259
|
-
|
|
7572
|
+
path6.join(sharedPath, "vectors")
|
|
7260
7573
|
);
|
|
7261
7574
|
await this.sharedVectorStore.initialize();
|
|
7262
7575
|
this.sharedPromoter = this.factories.createSharedPromoter(
|
|
@@ -7329,7 +7642,7 @@ var SharedMemoryServices = class {
|
|
|
7329
7642
|
async createOpenStorePromise(sharedPath) {
|
|
7330
7643
|
if (!this.sharedEventStore) {
|
|
7331
7644
|
const sharedEventStore = this.factories.createSharedEventStore(
|
|
7332
|
-
|
|
7645
|
+
path6.join(sharedPath, "shared.duckdb")
|
|
7333
7646
|
);
|
|
7334
7647
|
await sharedEventStore.initialize();
|
|
7335
7648
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -7349,9 +7662,9 @@ var SharedMemoryServices = class {
|
|
|
7349
7662
|
}
|
|
7350
7663
|
get factories() {
|
|
7351
7664
|
return {
|
|
7352
|
-
existsSync: this.options.factories?.existsSync ??
|
|
7665
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
7353
7666
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
7354
|
-
|
|
7667
|
+
fs7.mkdirSync(targetPath, { recursive: true });
|
|
7355
7668
|
}),
|
|
7356
7669
|
createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
|
|
7357
7670
|
createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
|
|
@@ -7466,37 +7779,11 @@ function createMemoryServiceComposition(options) {
|
|
|
7466
7779
|
}
|
|
7467
7780
|
function defaultExpandPath(targetPath) {
|
|
7468
7781
|
if (targetPath.startsWith("~")) {
|
|
7469
|
-
return
|
|
7782
|
+
return path7.join(os2.homedir(), targetPath.slice(1));
|
|
7470
7783
|
}
|
|
7471
7784
|
return targetPath;
|
|
7472
7785
|
}
|
|
7473
7786
|
|
|
7474
|
-
// src/core/registry/project-path.ts
|
|
7475
|
-
import * as crypto2 from "crypto";
|
|
7476
|
-
import * as fs7 from "fs";
|
|
7477
|
-
import * as os2 from "os";
|
|
7478
|
-
import * as path7 from "path";
|
|
7479
|
-
function normalizeProjectPath(projectPath) {
|
|
7480
|
-
const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
|
|
7481
|
-
try {
|
|
7482
|
-
return fs7.realpathSync(expanded);
|
|
7483
|
-
} catch {
|
|
7484
|
-
return path7.resolve(expanded);
|
|
7485
|
-
}
|
|
7486
|
-
}
|
|
7487
|
-
function hashProjectPath(projectPath) {
|
|
7488
|
-
const normalizedPath = normalizeProjectPath(projectPath);
|
|
7489
|
-
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
7490
|
-
}
|
|
7491
|
-
function getProjectStoragePath(projectPath) {
|
|
7492
|
-
const hash = hashProjectPath(projectPath);
|
|
7493
|
-
return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
|
|
7494
|
-
}
|
|
7495
|
-
function resolveProjectStoragePath(projectOrHash) {
|
|
7496
|
-
const isHash = /^[a-f0-9]{8}$/.test(projectOrHash);
|
|
7497
|
-
return isHash ? path7.join(os2.homedir(), ".claude-code", "memory", "projects", projectOrHash) : getProjectStoragePath(projectOrHash);
|
|
7498
|
-
}
|
|
7499
|
-
|
|
7500
7787
|
// src/core/registry/session-registry.ts
|
|
7501
7788
|
import * as fs8 from "fs";
|
|
7502
7789
|
import * as os3 from "os";
|
|
@@ -7798,6 +8085,9 @@ var MemoryService = class {
|
|
|
7798
8085
|
async recoverStuckOutboxItems(options) {
|
|
7799
8086
|
return this.queryService.recoverStuckOutboxItems(options);
|
|
7800
8087
|
}
|
|
8088
|
+
async repairLegacyProjectScope(options) {
|
|
8089
|
+
return this.queryService.repairLegacyProjectScope(options);
|
|
8090
|
+
}
|
|
7801
8091
|
async getRetrievalTraceStats() {
|
|
7802
8092
|
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
7803
8093
|
}
|