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/index.js
CHANGED
|
@@ -27,8 +27,8 @@ import { Hono } from "hono";
|
|
|
27
27
|
import * as os5 from "os";
|
|
28
28
|
|
|
29
29
|
// src/core/engine/memory-service-composition.ts
|
|
30
|
-
import * as
|
|
31
|
-
import * as
|
|
30
|
+
import * as os2 from "os";
|
|
31
|
+
import * as path7 from "path";
|
|
32
32
|
|
|
33
33
|
// src/core/metadata-extractor.ts
|
|
34
34
|
function createToolObservationEmbedding(toolName, metadata, success) {
|
|
@@ -1539,8 +1539,8 @@ function createEndlessMemoryServices(options) {
|
|
|
1539
1539
|
}
|
|
1540
1540
|
|
|
1541
1541
|
// src/core/engine/memory-engine-services.ts
|
|
1542
|
-
import * as
|
|
1543
|
-
import * as
|
|
1542
|
+
import * as fs6 from "fs";
|
|
1543
|
+
import * as path5 from "path";
|
|
1544
1544
|
|
|
1545
1545
|
// src/extensions/vector/embedder.ts
|
|
1546
1546
|
var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
|
|
@@ -2218,14 +2218,43 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2218
2218
|
return `${sessionId}:${contentHash}`;
|
|
2219
2219
|
}
|
|
2220
2220
|
|
|
2221
|
+
// src/core/sqlite-event-store.ts
|
|
2222
|
+
import * as nodePath2 from "path";
|
|
2223
|
+
|
|
2224
|
+
// src/core/registry/project-path.ts
|
|
2225
|
+
import * as crypto2 from "crypto";
|
|
2226
|
+
import * as fs3 from "fs";
|
|
2227
|
+
import * as os from "os";
|
|
2228
|
+
import * as path3 from "path";
|
|
2229
|
+
function normalizeProjectPath(projectPath) {
|
|
2230
|
+
const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
2231
|
+
try {
|
|
2232
|
+
return fs3.realpathSync(expanded);
|
|
2233
|
+
} catch {
|
|
2234
|
+
return path3.resolve(expanded);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
function hashProjectPath(projectPath) {
|
|
2238
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
2239
|
+
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
2240
|
+
}
|
|
2241
|
+
function getProjectStoragePath(projectPath) {
|
|
2242
|
+
const hash = hashProjectPath(projectPath);
|
|
2243
|
+
return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
|
|
2244
|
+
}
|
|
2245
|
+
function resolveProjectStoragePath(projectOrHash) {
|
|
2246
|
+
const isHash = /^[a-f0-9]{8}$/.test(projectOrHash);
|
|
2247
|
+
return isHash ? path3.join(os.homedir(), ".claude-code", "memory", "projects", projectOrHash) : getProjectStoragePath(projectOrHash);
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2221
2250
|
// src/core/sqlite-wrapper.ts
|
|
2222
2251
|
import Database from "better-sqlite3";
|
|
2223
|
-
import * as
|
|
2252
|
+
import * as fs4 from "fs";
|
|
2224
2253
|
import * as nodePath from "path";
|
|
2225
2254
|
function createSQLiteDatabase(path14, options) {
|
|
2226
2255
|
const dir = nodePath.dirname(path14);
|
|
2227
|
-
if (!
|
|
2228
|
-
|
|
2256
|
+
if (!fs4.existsSync(dir)) {
|
|
2257
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2229
2258
|
}
|
|
2230
2259
|
const db = new Database(path14, {
|
|
2231
2260
|
readonly: options?.readonly ?? false
|
|
@@ -2274,8 +2303,8 @@ function toSQLiteTimestamp(date) {
|
|
|
2274
2303
|
}
|
|
2275
2304
|
|
|
2276
2305
|
// src/core/markdown-mirror.ts
|
|
2277
|
-
import * as
|
|
2278
|
-
import * as
|
|
2306
|
+
import * as fs5 from "fs/promises";
|
|
2307
|
+
import * as path4 from "path";
|
|
2279
2308
|
var DEFAULT_NAMESPACE = "default";
|
|
2280
2309
|
var DEFAULT_CATEGORY = "uncategorized";
|
|
2281
2310
|
function sanitizeSegment2(input, fallback) {
|
|
@@ -2304,7 +2333,7 @@ function buildMirrorPath2(rootDir, event) {
|
|
|
2304
2333
|
const yyyy = d.getFullYear();
|
|
2305
2334
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
2306
2335
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
2307
|
-
return
|
|
2336
|
+
return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
|
|
2308
2337
|
}
|
|
2309
2338
|
function formatMirrorEntry(event) {
|
|
2310
2339
|
const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
|
|
@@ -2325,8 +2354,8 @@ var MarkdownMirror2 = class {
|
|
|
2325
2354
|
}
|
|
2326
2355
|
async append(event) {
|
|
2327
2356
|
const outPath = buildMirrorPath2(this.rootDir, event);
|
|
2328
|
-
await
|
|
2329
|
-
await
|
|
2357
|
+
await fs5.mkdir(path4.dirname(outPath), { recursive: true });
|
|
2358
|
+
await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
|
|
2330
2359
|
return outPath;
|
|
2331
2360
|
}
|
|
2332
2361
|
};
|
|
@@ -2347,6 +2376,135 @@ function emptyOutboxRecoveryResult() {
|
|
|
2347
2376
|
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2348
2377
|
};
|
|
2349
2378
|
}
|
|
2379
|
+
function isRecord(value) {
|
|
2380
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2381
|
+
}
|
|
2382
|
+
function getNestedRecord(root, path14) {
|
|
2383
|
+
let cursor = root;
|
|
2384
|
+
for (const key of path14) {
|
|
2385
|
+
if (!isRecord(cursor))
|
|
2386
|
+
return void 0;
|
|
2387
|
+
cursor = cursor[key];
|
|
2388
|
+
}
|
|
2389
|
+
return isRecord(cursor) ? cursor : void 0;
|
|
2390
|
+
}
|
|
2391
|
+
function getNestedString(root, path14) {
|
|
2392
|
+
let cursor = root;
|
|
2393
|
+
for (const key of path14) {
|
|
2394
|
+
if (!isRecord(cursor))
|
|
2395
|
+
return void 0;
|
|
2396
|
+
cursor = cursor[key];
|
|
2397
|
+
}
|
|
2398
|
+
return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
|
|
2399
|
+
}
|
|
2400
|
+
function metadataProjectHash(metadata) {
|
|
2401
|
+
return getNestedString(metadata, ["scope", "project", "hash"]);
|
|
2402
|
+
}
|
|
2403
|
+
function metadataProjectPaths(metadata) {
|
|
2404
|
+
const candidates = [
|
|
2405
|
+
getNestedString(metadata, ["projectPath"]),
|
|
2406
|
+
getNestedString(metadata, ["sourceProjectPath"]),
|
|
2407
|
+
getNestedString(metadata, ["scope", "project", "path"])
|
|
2408
|
+
];
|
|
2409
|
+
const paths = [];
|
|
2410
|
+
for (const value of candidates) {
|
|
2411
|
+
if (value && !paths.includes(value))
|
|
2412
|
+
paths.push(value);
|
|
2413
|
+
}
|
|
2414
|
+
return paths;
|
|
2415
|
+
}
|
|
2416
|
+
function metadataProjectPath(metadata) {
|
|
2417
|
+
return metadataProjectPaths(metadata)[0];
|
|
2418
|
+
}
|
|
2419
|
+
function isActiveQuarantinedMetadata(metadata) {
|
|
2420
|
+
const quarantine = getNestedRecord(metadata, ["quarantine"]);
|
|
2421
|
+
return quarantine?.status === "active";
|
|
2422
|
+
}
|
|
2423
|
+
function activeQuarantineStatusExpression(column = "metadata") {
|
|
2424
|
+
return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
|
|
2425
|
+
}
|
|
2426
|
+
function notActiveQuarantinedSql(column = "metadata") {
|
|
2427
|
+
return `${activeQuarantineStatusExpression(column)} != 'active'`;
|
|
2428
|
+
}
|
|
2429
|
+
function maybeQuarantinePredicate(options, column = "metadata") {
|
|
2430
|
+
return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
|
|
2431
|
+
}
|
|
2432
|
+
function safeParseMetadataValue(value) {
|
|
2433
|
+
if (!value)
|
|
2434
|
+
return void 0;
|
|
2435
|
+
if (typeof value === "object")
|
|
2436
|
+
return isRecord(value) ? value : void 0;
|
|
2437
|
+
if (typeof value !== "string")
|
|
2438
|
+
return void 0;
|
|
2439
|
+
try {
|
|
2440
|
+
const parsed = JSON.parse(value);
|
|
2441
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2442
|
+
} catch {
|
|
2443
|
+
return void 0;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
function isImportedOrLegacyScopedMetadata(metadata) {
|
|
2447
|
+
if (!metadata)
|
|
2448
|
+
return false;
|
|
2449
|
+
return Boolean(
|
|
2450
|
+
metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
function addMetadataTag(metadata, tag) {
|
|
2454
|
+
const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
|
|
2455
|
+
if (!current.includes(tag))
|
|
2456
|
+
metadata.tags = [...current, tag];
|
|
2457
|
+
}
|
|
2458
|
+
function buildRepairResult(projectHash, dryRun) {
|
|
2459
|
+
return {
|
|
2460
|
+
dryRun,
|
|
2461
|
+
projectHash,
|
|
2462
|
+
scanned: 0,
|
|
2463
|
+
repaired: 0,
|
|
2464
|
+
quarantined: 0,
|
|
2465
|
+
alreadyScoped: 0,
|
|
2466
|
+
skipped: 0,
|
|
2467
|
+
samples: []
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
function normalizeRepoName(value) {
|
|
2471
|
+
return value.replace(/\.git$/i, "").trim().toLowerCase();
|
|
2472
|
+
}
|
|
2473
|
+
function projectBasename(projectPath) {
|
|
2474
|
+
if (!projectPath)
|
|
2475
|
+
return void 0;
|
|
2476
|
+
const trimmed = projectPath.replace(/[\\/]+$/, "");
|
|
2477
|
+
const basename3 = nodePath2.basename(trimmed);
|
|
2478
|
+
return basename3 ? normalizeRepoName(basename3) : void 0;
|
|
2479
|
+
}
|
|
2480
|
+
function isProjectScopeRepairExplanation(content) {
|
|
2481
|
+
const normalized = content.toLowerCase();
|
|
2482
|
+
const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
|
|
2483
|
+
const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
|
|
2484
|
+
return hasRepairContext && hasExplanationContext;
|
|
2485
|
+
}
|
|
2486
|
+
function hasConflictingContentProjectHint(content, projectPath) {
|
|
2487
|
+
const currentName = projectBasename(projectPath);
|
|
2488
|
+
if (!currentName)
|
|
2489
|
+
return false;
|
|
2490
|
+
if (isProjectScopeRepairExplanation(content))
|
|
2491
|
+
return false;
|
|
2492
|
+
const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
|
|
2493
|
+
let githubMatch;
|
|
2494
|
+
while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
|
|
2495
|
+
const repo = normalizeRepoName(githubMatch[2] || "");
|
|
2496
|
+
if (repo && repo !== currentName)
|
|
2497
|
+
return true;
|
|
2498
|
+
}
|
|
2499
|
+
const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
|
|
2500
|
+
let workspaceMatch;
|
|
2501
|
+
while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
|
|
2502
|
+
const repo = normalizeRepoName(workspaceMatch[1] || "");
|
|
2503
|
+
if (repo && repo !== currentName)
|
|
2504
|
+
return true;
|
|
2505
|
+
}
|
|
2506
|
+
return false;
|
|
2507
|
+
}
|
|
2350
2508
|
var SQLiteEventStore = class {
|
|
2351
2509
|
db;
|
|
2352
2510
|
initialized = false;
|
|
@@ -2845,11 +3003,11 @@ var SQLiteEventStore = class {
|
|
|
2845
3003
|
/**
|
|
2846
3004
|
* Get events by session ID
|
|
2847
3005
|
*/
|
|
2848
|
-
async getSessionEvents(sessionId) {
|
|
3006
|
+
async getSessionEvents(sessionId, options) {
|
|
2849
3007
|
await this.initialize();
|
|
2850
3008
|
const rows = sqliteAll(
|
|
2851
3009
|
this.db,
|
|
2852
|
-
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
3010
|
+
`SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
2853
3011
|
[sessionId]
|
|
2854
3012
|
);
|
|
2855
3013
|
return rows.map(this.rowToEvent);
|
|
@@ -2857,11 +3015,11 @@ var SQLiteEventStore = class {
|
|
|
2857
3015
|
/**
|
|
2858
3016
|
* Get recent events
|
|
2859
3017
|
*/
|
|
2860
|
-
async getRecentEvents(limit = 100) {
|
|
3018
|
+
async getRecentEvents(limit = 100, options) {
|
|
2861
3019
|
await this.initialize();
|
|
2862
3020
|
const rows = sqliteAll(
|
|
2863
3021
|
this.db,
|
|
2864
|
-
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
3022
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
|
|
2865
3023
|
[limit]
|
|
2866
3024
|
);
|
|
2867
3025
|
return rows.map(this.rowToEvent);
|
|
@@ -2869,11 +3027,11 @@ var SQLiteEventStore = class {
|
|
|
2869
3027
|
/**
|
|
2870
3028
|
* Get event by ID
|
|
2871
3029
|
*/
|
|
2872
|
-
async getEvent(id) {
|
|
3030
|
+
async getEvent(id, options) {
|
|
2873
3031
|
await this.initialize();
|
|
2874
3032
|
const row = sqliteGet(
|
|
2875
3033
|
this.db,
|
|
2876
|
-
`SELECT * FROM events WHERE id =
|
|
3034
|
+
`SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
|
|
2877
3035
|
[id]
|
|
2878
3036
|
);
|
|
2879
3037
|
if (!row)
|
|
@@ -2883,11 +3041,11 @@ var SQLiteEventStore = class {
|
|
|
2883
3041
|
/**
|
|
2884
3042
|
* Get events since a timestamp (for sync)
|
|
2885
3043
|
*/
|
|
2886
|
-
async getEventsSince(timestamp, limit = 1e3) {
|
|
3044
|
+
async getEventsSince(timestamp, limit = 1e3, options) {
|
|
2887
3045
|
await this.initialize();
|
|
2888
3046
|
const rows = sqliteAll(
|
|
2889
3047
|
this.db,
|
|
2890
|
-
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
3048
|
+
`SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
|
|
2891
3049
|
[timestamp, limit]
|
|
2892
3050
|
);
|
|
2893
3051
|
return rows.map(this.rowToEvent);
|
|
@@ -2896,11 +3054,11 @@ var SQLiteEventStore = class {
|
|
|
2896
3054
|
* Get events since a SQLite rowid (for robust incremental replication).
|
|
2897
3055
|
* Rowid is monotonic for append-only tables, independent of client timestamps.
|
|
2898
3056
|
*/
|
|
2899
|
-
async getEventsSinceRowid(lastRowid, limit = 1e3) {
|
|
3057
|
+
async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
|
|
2900
3058
|
await this.initialize();
|
|
2901
3059
|
const rows = sqliteAll(
|
|
2902
3060
|
this.db,
|
|
2903
|
-
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
|
|
3061
|
+
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
|
|
2904
3062
|
[lastRowid, limit]
|
|
2905
3063
|
);
|
|
2906
3064
|
return rows.map((row) => ({
|
|
@@ -3135,19 +3293,19 @@ var SQLiteEventStore = class {
|
|
|
3135
3293
|
/**
|
|
3136
3294
|
* Count total events
|
|
3137
3295
|
*/
|
|
3138
|
-
async countEvents() {
|
|
3296
|
+
async countEvents(options) {
|
|
3139
3297
|
await this.initialize();
|
|
3140
|
-
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
3298
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
|
|
3141
3299
|
return row?.count || 0;
|
|
3142
3300
|
}
|
|
3143
3301
|
/**
|
|
3144
3302
|
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
3145
3303
|
*/
|
|
3146
|
-
async getEventsPage(limit = 1e3, offset = 0) {
|
|
3304
|
+
async getEventsPage(limit = 1e3, offset = 0, options) {
|
|
3147
3305
|
await this.initialize();
|
|
3148
3306
|
const rows = sqliteAll(
|
|
3149
3307
|
this.db,
|
|
3150
|
-
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3308
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3151
3309
|
[limit, offset]
|
|
3152
3310
|
);
|
|
3153
3311
|
return rows.map(this.rowToEvent);
|
|
@@ -3221,6 +3379,145 @@ var SQLiteEventStore = class {
|
|
|
3221
3379
|
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
3222
3380
|
return result;
|
|
3223
3381
|
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Repair legacy imported events that predate canonical project scope metadata.
|
|
3384
|
+
*
|
|
3385
|
+
* Same-project legacy rows are tagged with scope.project.hash. Rows that look
|
|
3386
|
+
* imported but cannot be proven to belong to this project are quarantined so
|
|
3387
|
+
* dashboard default reads/search do not surface cross-project contamination.
|
|
3388
|
+
*/
|
|
3389
|
+
async repairLegacyProjectScope(options = {}) {
|
|
3390
|
+
await this.initialize();
|
|
3391
|
+
const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
|
|
3392
|
+
if (!projectHash) {
|
|
3393
|
+
throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
|
|
3394
|
+
}
|
|
3395
|
+
if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
|
|
3396
|
+
throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
|
|
3397
|
+
}
|
|
3398
|
+
const dryRun = options.dryRun === true;
|
|
3399
|
+
const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
|
|
3400
|
+
const result = buildRepairResult(projectHash, dryRun);
|
|
3401
|
+
const rows = sqliteAll(
|
|
3402
|
+
this.db,
|
|
3403
|
+
`SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
|
|
3404
|
+
FROM events e
|
|
3405
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
3406
|
+
ORDER BY e.timestamp ASC`,
|
|
3407
|
+
[]
|
|
3408
|
+
);
|
|
3409
|
+
const sample = (entry) => {
|
|
3410
|
+
if (result.samples.length < 20)
|
|
3411
|
+
result.samples.push(entry);
|
|
3412
|
+
};
|
|
3413
|
+
for (const row of rows) {
|
|
3414
|
+
result.scanned++;
|
|
3415
|
+
let metadata = {};
|
|
3416
|
+
let metadataParseInvalid = false;
|
|
3417
|
+
if (row.metadata) {
|
|
3418
|
+
const parsed = safeParseMetadataValue(row.metadata);
|
|
3419
|
+
if (parsed) {
|
|
3420
|
+
metadata = parsed;
|
|
3421
|
+
} else {
|
|
3422
|
+
metadataParseInvalid = true;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (isActiveQuarantinedMetadata(metadata)) {
|
|
3426
|
+
result.skipped++;
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
const currentHash = metadataProjectHash(metadata);
|
|
3430
|
+
const explicitPath = metadataProjectPath(metadata);
|
|
3431
|
+
const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
|
|
3432
|
+
const candidatePaths = metadataProjectPaths(metadata);
|
|
3433
|
+
if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
|
|
3434
|
+
candidatePaths.push(sessionProjectPath);
|
|
3435
|
+
}
|
|
3436
|
+
const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
|
|
3437
|
+
const pathHashes = candidatePaths.map((candidate) => {
|
|
3438
|
+
try {
|
|
3439
|
+
return { path: candidate, hash: hashProjectPath(candidate) };
|
|
3440
|
+
} catch {
|
|
3441
|
+
return { path: candidate, hash: void 0 };
|
|
3442
|
+
}
|
|
3443
|
+
});
|
|
3444
|
+
const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
|
|
3445
|
+
const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
|
|
3446
|
+
let action = "skipped";
|
|
3447
|
+
let reason;
|
|
3448
|
+
let observedProjectHash;
|
|
3449
|
+
if (foreignPath) {
|
|
3450
|
+
action = "quarantined";
|
|
3451
|
+
reason = "project-path-mismatch";
|
|
3452
|
+
observedProjectHash = foreignPath.hash;
|
|
3453
|
+
} else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
|
|
3454
|
+
action = "quarantined";
|
|
3455
|
+
reason = "content-project-mismatch";
|
|
3456
|
+
} else if (currentHash === projectHash) {
|
|
3457
|
+
result.alreadyScoped++;
|
|
3458
|
+
continue;
|
|
3459
|
+
} else if (currentHash && currentHash !== projectHash) {
|
|
3460
|
+
action = "quarantined";
|
|
3461
|
+
reason = "scope-hash-mismatch";
|
|
3462
|
+
observedProjectHash = currentHash;
|
|
3463
|
+
} else if (matchingPath) {
|
|
3464
|
+
action = "repaired";
|
|
3465
|
+
reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
|
|
3466
|
+
} else if (candidatePaths.length > 0) {
|
|
3467
|
+
action = "quarantined";
|
|
3468
|
+
reason = "project-path-mismatch";
|
|
3469
|
+
} else if (importedOrLegacy) {
|
|
3470
|
+
action = "quarantined";
|
|
3471
|
+
reason = "missing-project-scope";
|
|
3472
|
+
}
|
|
3473
|
+
if (action === "skipped" || !reason) {
|
|
3474
|
+
result.skipped++;
|
|
3475
|
+
continue;
|
|
3476
|
+
}
|
|
3477
|
+
if (action === "repaired") {
|
|
3478
|
+
const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
|
|
3479
|
+
const project = isRecord(scope.project) ? { ...scope.project } : {};
|
|
3480
|
+
project.hash = projectHash;
|
|
3481
|
+
scope.project = project;
|
|
3482
|
+
metadata.scope = scope;
|
|
3483
|
+
metadata.repair = {
|
|
3484
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3485
|
+
legacyProjectScope: {
|
|
3486
|
+
action,
|
|
3487
|
+
reason,
|
|
3488
|
+
repairedAt: nowIso
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
addMetadataTag(metadata, `proj:${projectHash}`);
|
|
3492
|
+
result.repaired++;
|
|
3493
|
+
} else {
|
|
3494
|
+
metadata.quarantine = {
|
|
3495
|
+
...isRecord(metadata.quarantine) ? metadata.quarantine : {},
|
|
3496
|
+
status: "active",
|
|
3497
|
+
category: "project-scope",
|
|
3498
|
+
reason,
|
|
3499
|
+
detectedAt: nowIso,
|
|
3500
|
+
expectedProjectHash: projectHash,
|
|
3501
|
+
...observedProjectHash ? { observedProjectHash } : {}
|
|
3502
|
+
};
|
|
3503
|
+
metadata.repair = {
|
|
3504
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3505
|
+
legacyProjectScope: {
|
|
3506
|
+
action,
|
|
3507
|
+
reason,
|
|
3508
|
+
repairedAt: nowIso
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
addMetadataTag(metadata, "quarantine:project-scope");
|
|
3512
|
+
result.quarantined++;
|
|
3513
|
+
}
|
|
3514
|
+
sample({ eventId: row.id, action, reason });
|
|
3515
|
+
if (!dryRun) {
|
|
3516
|
+
sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
return result;
|
|
3520
|
+
}
|
|
3224
3521
|
/**
|
|
3225
3522
|
* Get embedding/vector outbox health statistics
|
|
3226
3523
|
*/
|
|
@@ -3268,7 +3565,11 @@ var SQLiteEventStore = class {
|
|
|
3268
3565
|
await this.initialize();
|
|
3269
3566
|
const rows = sqliteAll(
|
|
3270
3567
|
this.db,
|
|
3271
|
-
`SELECT level, COUNT(*) as count
|
|
3568
|
+
`SELECT ml.level, COUNT(*) as count
|
|
3569
|
+
FROM memory_levels ml
|
|
3570
|
+
INNER JOIN events e ON e.id = ml.event_id
|
|
3571
|
+
WHERE ${notActiveQuarantinedSql("e.metadata")}
|
|
3572
|
+
GROUP BY ml.level`
|
|
3272
3573
|
);
|
|
3273
3574
|
return rows;
|
|
3274
3575
|
}
|
|
@@ -3284,6 +3585,7 @@ var SQLiteEventStore = class {
|
|
|
3284
3585
|
`SELECT e.* FROM events e
|
|
3285
3586
|
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
3286
3587
|
WHERE ml.level = ?
|
|
3588
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3287
3589
|
ORDER BY e.timestamp DESC
|
|
3288
3590
|
LIMIT ? OFFSET ?`,
|
|
3289
3591
|
[level, limit, offset]
|
|
@@ -3376,12 +3678,13 @@ var SQLiteEventStore = class {
|
|
|
3376
3678
|
/**
|
|
3377
3679
|
* Get most accessed memories (falls back to recent events if none accessed)
|
|
3378
3680
|
*/
|
|
3379
|
-
async getMostAccessed(limit = 10) {
|
|
3681
|
+
async getMostAccessed(limit = 10, options) {
|
|
3380
3682
|
await this.initialize();
|
|
3381
3683
|
let rows = sqliteAll(
|
|
3382
3684
|
this.db,
|
|
3383
3685
|
`SELECT * FROM events
|
|
3384
3686
|
WHERE access_count > 0
|
|
3687
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3385
3688
|
ORDER BY access_count DESC, last_accessed_at DESC
|
|
3386
3689
|
LIMIT ?`,
|
|
3387
3690
|
[limit]
|
|
@@ -3390,6 +3693,7 @@ var SQLiteEventStore = class {
|
|
|
3390
3693
|
rows = sqliteAll(
|
|
3391
3694
|
this.db,
|
|
3392
3695
|
`SELECT * FROM events
|
|
3696
|
+
WHERE ${maybeQuarantinePredicate(options)}
|
|
3393
3697
|
ORDER BY timestamp DESC
|
|
3394
3698
|
LIMIT ?`,
|
|
3395
3699
|
[limit]
|
|
@@ -3520,6 +3824,7 @@ var SQLiteEventStore = class {
|
|
|
3520
3824
|
FROM memory_helpfulness mh
|
|
3521
3825
|
JOIN events e ON e.id = mh.event_id
|
|
3522
3826
|
WHERE mh.measured_at IS NOT NULL
|
|
3827
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3523
3828
|
GROUP BY mh.event_id
|
|
3524
3829
|
ORDER BY avg_score DESC
|
|
3525
3830
|
LIMIT ?`,
|
|
@@ -3584,6 +3889,7 @@ var SQLiteEventStore = class {
|
|
|
3584
3889
|
FROM events_fts fts
|
|
3585
3890
|
JOIN events e ON e.id = fts.event_id
|
|
3586
3891
|
WHERE events_fts MATCH ?
|
|
3892
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3587
3893
|
ORDER BY fts.rank
|
|
3588
3894
|
LIMIT ?`,
|
|
3589
3895
|
[searchTerms, limit]
|
|
@@ -3598,6 +3904,7 @@ var SQLiteEventStore = class {
|
|
|
3598
3904
|
this.db,
|
|
3599
3905
|
`SELECT *, 0 as rank FROM events
|
|
3600
3906
|
WHERE content LIKE ?
|
|
3907
|
+
AND ${notActiveQuarantinedSql()}
|
|
3601
3908
|
ORDER BY timestamp DESC
|
|
3602
3909
|
LIMIT ?`,
|
|
3603
3910
|
[likePattern, limit]
|
|
@@ -3804,6 +4111,7 @@ var SQLiteEventStore = class {
|
|
|
3804
4111
|
`SELECT turn_id, MIN(timestamp) as min_ts
|
|
3805
4112
|
FROM events
|
|
3806
4113
|
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4114
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3807
4115
|
GROUP BY turn_id
|
|
3808
4116
|
ORDER BY min_ts DESC
|
|
3809
4117
|
LIMIT ? OFFSET ?`,
|
|
@@ -3811,7 +4119,7 @@ var SQLiteEventStore = class {
|
|
|
3811
4119
|
);
|
|
3812
4120
|
const turns = [];
|
|
3813
4121
|
for (const turnRow of turnRows) {
|
|
3814
|
-
const events = await this.getEventsByTurn(turnRow.turn_id);
|
|
4122
|
+
const events = await this.getEventsByTurn(turnRow.turn_id, options);
|
|
3815
4123
|
const promptEvent = events.find((e) => e.eventType === "user_prompt");
|
|
3816
4124
|
const toolEvents = events.filter((e) => e.eventType === "tool_observation");
|
|
3817
4125
|
const hasResponse = events.some((e) => e.eventType === "agent_response");
|
|
@@ -3830,11 +4138,11 @@ var SQLiteEventStore = class {
|
|
|
3830
4138
|
/**
|
|
3831
4139
|
* Get all events for a specific turn_id
|
|
3832
4140
|
*/
|
|
3833
|
-
async getEventsByTurn(turnId) {
|
|
4141
|
+
async getEventsByTurn(turnId, options) {
|
|
3834
4142
|
await this.initialize();
|
|
3835
4143
|
const rows = sqliteAll(
|
|
3836
4144
|
this.db,
|
|
3837
|
-
`SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
|
|
4145
|
+
`SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3838
4146
|
[turnId]
|
|
3839
4147
|
);
|
|
3840
4148
|
return rows.map(this.rowToEvent);
|
|
@@ -3842,13 +4150,14 @@ var SQLiteEventStore = class {
|
|
|
3842
4150
|
/**
|
|
3843
4151
|
* Count total turns for a session
|
|
3844
4152
|
*/
|
|
3845
|
-
async countSessionTurns(sessionId) {
|
|
4153
|
+
async countSessionTurns(sessionId, options) {
|
|
3846
4154
|
await this.initialize();
|
|
3847
4155
|
const row = sqliteGet(
|
|
3848
4156
|
this.db,
|
|
3849
4157
|
`SELECT COUNT(DISTINCT turn_id) as count
|
|
3850
4158
|
FROM events
|
|
3851
|
-
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4159
|
+
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4160
|
+
AND ${maybeQuarantinePredicate(options)}`,
|
|
3852
4161
|
[sessionId]
|
|
3853
4162
|
);
|
|
3854
4163
|
return row?.count || 0;
|
|
@@ -3940,7 +4249,7 @@ var SQLiteEventStore = class {
|
|
|
3940
4249
|
content: row.content,
|
|
3941
4250
|
canonicalKey: row.canonical_key,
|
|
3942
4251
|
dedupeKey: row.dedupe_key,
|
|
3943
|
-
metadata:
|
|
4252
|
+
metadata: safeParseMetadataValue(row.metadata)
|
|
3944
4253
|
};
|
|
3945
4254
|
if (row.access_count !== void 0) {
|
|
3946
4255
|
event.access_count = row.access_count;
|
|
@@ -4535,6 +4844,10 @@ var MemoryQueryService = class {
|
|
|
4535
4844
|
await this.initialize();
|
|
4536
4845
|
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
4537
4846
|
}
|
|
4847
|
+
async repairLegacyProjectScope(options) {
|
|
4848
|
+
await this.initialize();
|
|
4849
|
+
return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
|
|
4850
|
+
}
|
|
4538
4851
|
async getStats() {
|
|
4539
4852
|
await this.initialize();
|
|
4540
4853
|
const deps = this.getStatsDeps();
|
|
@@ -6157,18 +6470,18 @@ function assertDefaultRetrieverStore(eventStore) {
|
|
|
6157
6470
|
function createMemoryEngineServices(options) {
|
|
6158
6471
|
const factories = options.factories ?? {};
|
|
6159
6472
|
const storagePath = options.storagePath;
|
|
6160
|
-
if (!options.readOnly && !
|
|
6161
|
-
|
|
6473
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
6474
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
6162
6475
|
}
|
|
6163
6476
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
6164
|
-
|
|
6477
|
+
path5.join(storagePath, "events.sqlite"),
|
|
6165
6478
|
{
|
|
6166
6479
|
readonly: options.readOnly,
|
|
6167
6480
|
markdownMirrorRoot: storagePath
|
|
6168
6481
|
}
|
|
6169
6482
|
);
|
|
6170
6483
|
const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
|
|
6171
|
-
|
|
6484
|
+
path5.join(storagePath, "vectors")
|
|
6172
6485
|
);
|
|
6173
6486
|
const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6174
6487
|
const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
|
|
@@ -6579,8 +6892,8 @@ function createMemoryRuntimeService(deps) {
|
|
|
6579
6892
|
}
|
|
6580
6893
|
|
|
6581
6894
|
// src/extensions/shared-memory/shared-memory-services.ts
|
|
6582
|
-
import * as
|
|
6583
|
-
import * as
|
|
6895
|
+
import * as fs7 from "fs";
|
|
6896
|
+
import * as path6 from "path";
|
|
6584
6897
|
|
|
6585
6898
|
// src/core/shared-event-store.ts
|
|
6586
6899
|
var SharedEventStore = class {
|
|
@@ -7268,7 +7581,7 @@ var SharedMemoryServices = class {
|
|
|
7268
7581
|
this.ensureDirectory(sharedPath, { allowCreate: true });
|
|
7269
7582
|
const store = await this.openStore(sharedPath);
|
|
7270
7583
|
this.sharedVectorStore = this.factories.createSharedVectorStore(
|
|
7271
|
-
|
|
7584
|
+
path6.join(sharedPath, "vectors")
|
|
7272
7585
|
);
|
|
7273
7586
|
await this.sharedVectorStore.initialize();
|
|
7274
7587
|
this.sharedPromoter = this.factories.createSharedPromoter(
|
|
@@ -7341,7 +7654,7 @@ var SharedMemoryServices = class {
|
|
|
7341
7654
|
async createOpenStorePromise(sharedPath) {
|
|
7342
7655
|
if (!this.sharedEventStore) {
|
|
7343
7656
|
const sharedEventStore = this.factories.createSharedEventStore(
|
|
7344
|
-
|
|
7657
|
+
path6.join(sharedPath, "shared.duckdb")
|
|
7345
7658
|
);
|
|
7346
7659
|
await sharedEventStore.initialize();
|
|
7347
7660
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -7361,9 +7674,9 @@ var SharedMemoryServices = class {
|
|
|
7361
7674
|
}
|
|
7362
7675
|
get factories() {
|
|
7363
7676
|
return {
|
|
7364
|
-
existsSync: this.options.factories?.existsSync ??
|
|
7677
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
7365
7678
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
7366
|
-
|
|
7679
|
+
fs7.mkdirSync(targetPath, { recursive: true });
|
|
7367
7680
|
}),
|
|
7368
7681
|
createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
|
|
7369
7682
|
createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
|
|
@@ -7478,37 +7791,11 @@ function createMemoryServiceComposition(options) {
|
|
|
7478
7791
|
}
|
|
7479
7792
|
function defaultExpandPath(targetPath) {
|
|
7480
7793
|
if (targetPath.startsWith("~")) {
|
|
7481
|
-
return
|
|
7794
|
+
return path7.join(os2.homedir(), targetPath.slice(1));
|
|
7482
7795
|
}
|
|
7483
7796
|
return targetPath;
|
|
7484
7797
|
}
|
|
7485
7798
|
|
|
7486
|
-
// src/core/registry/project-path.ts
|
|
7487
|
-
import * as crypto2 from "crypto";
|
|
7488
|
-
import * as fs7 from "fs";
|
|
7489
|
-
import * as os2 from "os";
|
|
7490
|
-
import * as path7 from "path";
|
|
7491
|
-
function normalizeProjectPath(projectPath) {
|
|
7492
|
-
const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
|
|
7493
|
-
try {
|
|
7494
|
-
return fs7.realpathSync(expanded);
|
|
7495
|
-
} catch {
|
|
7496
|
-
return path7.resolve(expanded);
|
|
7497
|
-
}
|
|
7498
|
-
}
|
|
7499
|
-
function hashProjectPath(projectPath) {
|
|
7500
|
-
const normalizedPath = normalizeProjectPath(projectPath);
|
|
7501
|
-
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
7502
|
-
}
|
|
7503
|
-
function getProjectStoragePath(projectPath) {
|
|
7504
|
-
const hash = hashProjectPath(projectPath);
|
|
7505
|
-
return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
|
|
7506
|
-
}
|
|
7507
|
-
function resolveProjectStoragePath(projectOrHash) {
|
|
7508
|
-
const isHash = /^[a-f0-9]{8}$/.test(projectOrHash);
|
|
7509
|
-
return isHash ? path7.join(os2.homedir(), ".claude-code", "memory", "projects", projectOrHash) : getProjectStoragePath(projectOrHash);
|
|
7510
|
-
}
|
|
7511
|
-
|
|
7512
7799
|
// src/core/registry/session-registry.ts
|
|
7513
7800
|
import * as fs8 from "fs";
|
|
7514
7801
|
import * as os3 from "os";
|
|
@@ -7810,6 +8097,9 @@ var MemoryService = class {
|
|
|
7810
8097
|
async recoverStuckOutboxItems(options) {
|
|
7811
8098
|
return this.queryService.recoverStuckOutboxItems(options);
|
|
7812
8099
|
}
|
|
8100
|
+
async repairLegacyProjectScope(options) {
|
|
8101
|
+
return this.queryService.repairLegacyProjectScope(options);
|
|
8102
|
+
}
|
|
7813
8103
|
async getRetrievalTraceStats() {
|
|
7814
8104
|
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
7815
8105
|
}
|