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