claude-memory-layer 1.0.37 → 1.0.39

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 CHANGED
@@ -17,8 +17,8 @@ import * as os11 from "os";
17
17
  import * as os5 from "os";
18
18
 
19
19
  // src/core/engine/memory-service-composition.ts
20
- import * as os from "os";
21
- import * as path6 from "path";
20
+ import * as os2 from "os";
21
+ import * as path7 from "path";
22
22
 
23
23
  // src/core/metadata-extractor.ts
24
24
  function createToolObservationEmbedding(toolName, metadata, success) {
@@ -1529,8 +1529,8 @@ function createEndlessMemoryServices(options) {
1529
1529
  }
1530
1530
 
1531
1531
  // src/core/engine/memory-engine-services.ts
1532
- import * as fs5 from "fs";
1533
- import * as path4 from "path";
1532
+ import * as fs6 from "fs";
1533
+ import * as path5 from "path";
1534
1534
 
1535
1535
  // src/extensions/vector/embedder.ts
1536
1536
  var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
@@ -2208,14 +2208,43 @@ function makeDedupeKey(content, sessionId) {
2208
2208
  return `${sessionId}:${contentHash}`;
2209
2209
  }
2210
2210
 
2211
+ // src/core/sqlite-event-store.ts
2212
+ import * as nodePath2 from "path";
2213
+
2214
+ // src/core/registry/project-path.ts
2215
+ import * as crypto2 from "crypto";
2216
+ import * as fs3 from "fs";
2217
+ import * as os from "os";
2218
+ import * as path3 from "path";
2219
+ function normalizeProjectPath(projectPath) {
2220
+ const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
2221
+ try {
2222
+ return fs3.realpathSync(expanded);
2223
+ } catch {
2224
+ return path3.resolve(expanded);
2225
+ }
2226
+ }
2227
+ function hashProjectPath(projectPath) {
2228
+ const normalizedPath = normalizeProjectPath(projectPath);
2229
+ return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
2230
+ }
2231
+ function getProjectStoragePath(projectPath) {
2232
+ const hash = hashProjectPath(projectPath);
2233
+ return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
2234
+ }
2235
+ function resolveProjectStoragePath(projectOrHash) {
2236
+ const isHash = /^[a-f0-9]{8}$/.test(projectOrHash);
2237
+ return isHash ? path3.join(os.homedir(), ".claude-code", "memory", "projects", projectOrHash) : getProjectStoragePath(projectOrHash);
2238
+ }
2239
+
2211
2240
  // src/core/sqlite-wrapper.ts
2212
2241
  import Database from "better-sqlite3";
2213
- import * as fs3 from "fs";
2242
+ import * as fs4 from "fs";
2214
2243
  import * as nodePath from "path";
2215
2244
  function createSQLiteDatabase(path22, options) {
2216
2245
  const dir = nodePath.dirname(path22);
2217
- if (!fs3.existsSync(dir)) {
2218
- fs3.mkdirSync(dir, { recursive: true });
2246
+ if (!fs4.existsSync(dir)) {
2247
+ fs4.mkdirSync(dir, { recursive: true });
2219
2248
  }
2220
2249
  const db = new Database(path22, {
2221
2250
  readonly: options?.readonly ?? false
@@ -2264,8 +2293,8 @@ function toSQLiteTimestamp(date) {
2264
2293
  }
2265
2294
 
2266
2295
  // src/core/markdown-mirror.ts
2267
- import * as fs4 from "fs/promises";
2268
- import * as path3 from "path";
2296
+ import * as fs5 from "fs/promises";
2297
+ import * as path4 from "path";
2269
2298
  var DEFAULT_NAMESPACE = "default";
2270
2299
  var DEFAULT_CATEGORY = "uncategorized";
2271
2300
  function sanitizeSegment2(input, fallback) {
@@ -2294,7 +2323,7 @@ function buildMirrorPath2(rootDir, event) {
2294
2323
  const yyyy = d.getFullYear();
2295
2324
  const mm = String(d.getMonth() + 1).padStart(2, "0");
2296
2325
  const dd = String(d.getDate()).padStart(2, "0");
2297
- return path3.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
2326
+ return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
2298
2327
  }
2299
2328
  function formatMirrorEntry(event) {
2300
2329
  const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
@@ -2315,8 +2344,8 @@ var MarkdownMirror2 = class {
2315
2344
  }
2316
2345
  async append(event) {
2317
2346
  const outPath = buildMirrorPath2(this.rootDir, event);
2318
- await fs4.mkdir(path3.dirname(outPath), { recursive: true });
2319
- await fs4.appendFile(outPath, formatMirrorEntry(event), "utf8");
2347
+ await fs5.mkdir(path4.dirname(outPath), { recursive: true });
2348
+ await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
2320
2349
  return outPath;
2321
2350
  }
2322
2351
  };
@@ -2337,6 +2366,135 @@ function emptyOutboxRecoveryResult() {
2337
2366
  vector: { recoveredProcessing: 0, retriedFailed: 0 }
2338
2367
  };
2339
2368
  }
2369
+ function isRecord(value) {
2370
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2371
+ }
2372
+ function getNestedRecord(root, path22) {
2373
+ let cursor = root;
2374
+ for (const key of path22) {
2375
+ if (!isRecord(cursor))
2376
+ return void 0;
2377
+ cursor = cursor[key];
2378
+ }
2379
+ return isRecord(cursor) ? cursor : void 0;
2380
+ }
2381
+ function getNestedString(root, path22) {
2382
+ let cursor = root;
2383
+ for (const key of path22) {
2384
+ if (!isRecord(cursor))
2385
+ return void 0;
2386
+ cursor = cursor[key];
2387
+ }
2388
+ return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
2389
+ }
2390
+ function metadataProjectHash(metadata) {
2391
+ return getNestedString(metadata, ["scope", "project", "hash"]);
2392
+ }
2393
+ function metadataProjectPaths(metadata) {
2394
+ const candidates = [
2395
+ getNestedString(metadata, ["projectPath"]),
2396
+ getNestedString(metadata, ["sourceProjectPath"]),
2397
+ getNestedString(metadata, ["scope", "project", "path"])
2398
+ ];
2399
+ const paths = [];
2400
+ for (const value of candidates) {
2401
+ if (value && !paths.includes(value))
2402
+ paths.push(value);
2403
+ }
2404
+ return paths;
2405
+ }
2406
+ function metadataProjectPath(metadata) {
2407
+ return metadataProjectPaths(metadata)[0];
2408
+ }
2409
+ function isActiveQuarantinedMetadata(metadata) {
2410
+ const quarantine = getNestedRecord(metadata, ["quarantine"]);
2411
+ return quarantine?.status === "active";
2412
+ }
2413
+ function activeQuarantineStatusExpression(column = "metadata") {
2414
+ return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
2415
+ }
2416
+ function notActiveQuarantinedSql(column = "metadata") {
2417
+ return `${activeQuarantineStatusExpression(column)} != 'active'`;
2418
+ }
2419
+ function maybeQuarantinePredicate(options, column = "metadata") {
2420
+ return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
2421
+ }
2422
+ function safeParseMetadataValue(value) {
2423
+ if (!value)
2424
+ return void 0;
2425
+ if (typeof value === "object")
2426
+ return isRecord(value) ? value : void 0;
2427
+ if (typeof value !== "string")
2428
+ return void 0;
2429
+ try {
2430
+ const parsed = JSON.parse(value);
2431
+ return isRecord(parsed) ? parsed : void 0;
2432
+ } catch {
2433
+ return void 0;
2434
+ }
2435
+ }
2436
+ function isImportedOrLegacyScopedMetadata(metadata) {
2437
+ if (!metadata)
2438
+ return false;
2439
+ return Boolean(
2440
+ metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
2441
+ );
2442
+ }
2443
+ function addMetadataTag(metadata, tag) {
2444
+ const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
2445
+ if (!current.includes(tag))
2446
+ metadata.tags = [...current, tag];
2447
+ }
2448
+ function buildRepairResult(projectHash, dryRun) {
2449
+ return {
2450
+ dryRun,
2451
+ projectHash,
2452
+ scanned: 0,
2453
+ repaired: 0,
2454
+ quarantined: 0,
2455
+ alreadyScoped: 0,
2456
+ skipped: 0,
2457
+ samples: []
2458
+ };
2459
+ }
2460
+ function normalizeRepoName(value) {
2461
+ return value.replace(/\.git$/i, "").trim().toLowerCase();
2462
+ }
2463
+ function projectBasename(projectPath) {
2464
+ if (!projectPath)
2465
+ return void 0;
2466
+ const trimmed = projectPath.replace(/[\\/]+$/, "");
2467
+ const basename7 = nodePath2.basename(trimmed);
2468
+ return basename7 ? normalizeRepoName(basename7) : void 0;
2469
+ }
2470
+ function isProjectScopeRepairExplanation(content) {
2471
+ const normalized = content.toLowerCase();
2472
+ const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
2473
+ const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
2474
+ return hasRepairContext && hasExplanationContext;
2475
+ }
2476
+ function hasConflictingContentProjectHint(content, projectPath) {
2477
+ const currentName = projectBasename(projectPath);
2478
+ if (!currentName)
2479
+ return false;
2480
+ if (isProjectScopeRepairExplanation(content))
2481
+ return false;
2482
+ const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
2483
+ let githubMatch;
2484
+ while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
2485
+ const repo = normalizeRepoName(githubMatch[2] || "");
2486
+ if (repo && repo !== currentName)
2487
+ return true;
2488
+ }
2489
+ const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
2490
+ let workspaceMatch;
2491
+ while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
2492
+ const repo = normalizeRepoName(workspaceMatch[1] || "");
2493
+ if (repo && repo !== currentName)
2494
+ return true;
2495
+ }
2496
+ return false;
2497
+ }
2340
2498
  var SQLiteEventStore = class {
2341
2499
  db;
2342
2500
  initialized = false;
@@ -2835,11 +2993,11 @@ var SQLiteEventStore = class {
2835
2993
  /**
2836
2994
  * Get events by session ID
2837
2995
  */
2838
- async getSessionEvents(sessionId) {
2996
+ async getSessionEvents(sessionId, options) {
2839
2997
  await this.initialize();
2840
2998
  const rows = sqliteAll(
2841
2999
  this.db,
2842
- `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
3000
+ `SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
2843
3001
  [sessionId]
2844
3002
  );
2845
3003
  return rows.map(this.rowToEvent);
@@ -2847,11 +3005,11 @@ var SQLiteEventStore = class {
2847
3005
  /**
2848
3006
  * Get recent events
2849
3007
  */
2850
- async getRecentEvents(limit = 100) {
3008
+ async getRecentEvents(limit = 100, options) {
2851
3009
  await this.initialize();
2852
3010
  const rows = sqliteAll(
2853
3011
  this.db,
2854
- `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
3012
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
2855
3013
  [limit]
2856
3014
  );
2857
3015
  return rows.map(this.rowToEvent);
@@ -2859,11 +3017,11 @@ var SQLiteEventStore = class {
2859
3017
  /**
2860
3018
  * Get event by ID
2861
3019
  */
2862
- async getEvent(id) {
3020
+ async getEvent(id, options) {
2863
3021
  await this.initialize();
2864
3022
  const row = sqliteGet(
2865
3023
  this.db,
2866
- `SELECT * FROM events WHERE id = ?`,
3024
+ `SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
2867
3025
  [id]
2868
3026
  );
2869
3027
  if (!row)
@@ -2873,11 +3031,11 @@ var SQLiteEventStore = class {
2873
3031
  /**
2874
3032
  * Get events since a timestamp (for sync)
2875
3033
  */
2876
- async getEventsSince(timestamp, limit = 1e3) {
3034
+ async getEventsSince(timestamp, limit = 1e3, options) {
2877
3035
  await this.initialize();
2878
3036
  const rows = sqliteAll(
2879
3037
  this.db,
2880
- `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
3038
+ `SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
2881
3039
  [timestamp, limit]
2882
3040
  );
2883
3041
  return rows.map(this.rowToEvent);
@@ -2886,11 +3044,11 @@ var SQLiteEventStore = class {
2886
3044
  * Get events since a SQLite rowid (for robust incremental replication).
2887
3045
  * Rowid is monotonic for append-only tables, independent of client timestamps.
2888
3046
  */
2889
- async getEventsSinceRowid(lastRowid, limit = 1e3) {
3047
+ async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
2890
3048
  await this.initialize();
2891
3049
  const rows = sqliteAll(
2892
3050
  this.db,
2893
- `SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
3051
+ `SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
2894
3052
  [lastRowid, limit]
2895
3053
  );
2896
3054
  return rows.map((row) => ({
@@ -3125,19 +3283,19 @@ var SQLiteEventStore = class {
3125
3283
  /**
3126
3284
  * Count total events
3127
3285
  */
3128
- async countEvents() {
3286
+ async countEvents(options) {
3129
3287
  await this.initialize();
3130
- const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
3288
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
3131
3289
  return row?.count || 0;
3132
3290
  }
3133
3291
  /**
3134
3292
  * Get events page in timestamp ascending order (stable migration/reindex scans)
3135
3293
  */
3136
- async getEventsPage(limit = 1e3, offset = 0) {
3294
+ async getEventsPage(limit = 1e3, offset = 0, options) {
3137
3295
  await this.initialize();
3138
3296
  const rows = sqliteAll(
3139
3297
  this.db,
3140
- `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
3298
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
3141
3299
  [limit, offset]
3142
3300
  );
3143
3301
  return rows.map(this.rowToEvent);
@@ -3211,6 +3369,145 @@ var SQLiteEventStore = class {
3211
3369
  result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
3212
3370
  return result;
3213
3371
  }
3372
+ /**
3373
+ * Repair legacy imported events that predate canonical project scope metadata.
3374
+ *
3375
+ * Same-project legacy rows are tagged with scope.project.hash. Rows that look
3376
+ * imported but cannot be proven to belong to this project are quarantined so
3377
+ * dashboard default reads/search do not surface cross-project contamination.
3378
+ */
3379
+ async repairLegacyProjectScope(options = {}) {
3380
+ await this.initialize();
3381
+ const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
3382
+ if (!projectHash) {
3383
+ throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
3384
+ }
3385
+ if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
3386
+ throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
3387
+ }
3388
+ const dryRun = options.dryRun === true;
3389
+ const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
3390
+ const result = buildRepairResult(projectHash, dryRun);
3391
+ const rows = sqliteAll(
3392
+ this.db,
3393
+ `SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
3394
+ FROM events e
3395
+ LEFT JOIN sessions s ON s.id = e.session_id
3396
+ ORDER BY e.timestamp ASC`,
3397
+ []
3398
+ );
3399
+ const sample = (entry) => {
3400
+ if (result.samples.length < 20)
3401
+ result.samples.push(entry);
3402
+ };
3403
+ for (const row of rows) {
3404
+ result.scanned++;
3405
+ let metadata = {};
3406
+ let metadataParseInvalid = false;
3407
+ if (row.metadata) {
3408
+ const parsed = safeParseMetadataValue(row.metadata);
3409
+ if (parsed) {
3410
+ metadata = parsed;
3411
+ } else {
3412
+ metadataParseInvalid = true;
3413
+ }
3414
+ }
3415
+ if (isActiveQuarantinedMetadata(metadata)) {
3416
+ result.skipped++;
3417
+ continue;
3418
+ }
3419
+ const currentHash = metadataProjectHash(metadata);
3420
+ const explicitPath = metadataProjectPath(metadata);
3421
+ const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
3422
+ const candidatePaths = metadataProjectPaths(metadata);
3423
+ if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
3424
+ candidatePaths.push(sessionProjectPath);
3425
+ }
3426
+ const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
3427
+ const pathHashes = candidatePaths.map((candidate) => {
3428
+ try {
3429
+ return { path: candidate, hash: hashProjectPath(candidate) };
3430
+ } catch {
3431
+ return { path: candidate, hash: void 0 };
3432
+ }
3433
+ });
3434
+ const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
3435
+ const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
3436
+ let action = "skipped";
3437
+ let reason;
3438
+ let observedProjectHash;
3439
+ if (foreignPath) {
3440
+ action = "quarantined";
3441
+ reason = "project-path-mismatch";
3442
+ observedProjectHash = foreignPath.hash;
3443
+ } else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
3444
+ action = "quarantined";
3445
+ reason = "content-project-mismatch";
3446
+ } else if (currentHash === projectHash) {
3447
+ result.alreadyScoped++;
3448
+ continue;
3449
+ } else if (currentHash && currentHash !== projectHash) {
3450
+ action = "quarantined";
3451
+ reason = "scope-hash-mismatch";
3452
+ observedProjectHash = currentHash;
3453
+ } else if (matchingPath) {
3454
+ action = "repaired";
3455
+ reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
3456
+ } else if (candidatePaths.length > 0) {
3457
+ action = "quarantined";
3458
+ reason = "project-path-mismatch";
3459
+ } else if (importedOrLegacy) {
3460
+ action = "quarantined";
3461
+ reason = "missing-project-scope";
3462
+ }
3463
+ if (action === "skipped" || !reason) {
3464
+ result.skipped++;
3465
+ continue;
3466
+ }
3467
+ if (action === "repaired") {
3468
+ const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
3469
+ const project = isRecord(scope.project) ? { ...scope.project } : {};
3470
+ project.hash = projectHash;
3471
+ scope.project = project;
3472
+ metadata.scope = scope;
3473
+ metadata.repair = {
3474
+ ...isRecord(metadata.repair) ? metadata.repair : {},
3475
+ legacyProjectScope: {
3476
+ action,
3477
+ reason,
3478
+ repairedAt: nowIso
3479
+ }
3480
+ };
3481
+ addMetadataTag(metadata, `proj:${projectHash}`);
3482
+ result.repaired++;
3483
+ } else {
3484
+ metadata.quarantine = {
3485
+ ...isRecord(metadata.quarantine) ? metadata.quarantine : {},
3486
+ status: "active",
3487
+ category: "project-scope",
3488
+ reason,
3489
+ detectedAt: nowIso,
3490
+ expectedProjectHash: projectHash,
3491
+ ...observedProjectHash ? { observedProjectHash } : {}
3492
+ };
3493
+ metadata.repair = {
3494
+ ...isRecord(metadata.repair) ? metadata.repair : {},
3495
+ legacyProjectScope: {
3496
+ action,
3497
+ reason,
3498
+ repairedAt: nowIso
3499
+ }
3500
+ };
3501
+ addMetadataTag(metadata, "quarantine:project-scope");
3502
+ result.quarantined++;
3503
+ }
3504
+ sample({ eventId: row.id, action, reason });
3505
+ if (!dryRun) {
3506
+ sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
3507
+ }
3508
+ }
3509
+ return result;
3510
+ }
3214
3511
  /**
3215
3512
  * Get embedding/vector outbox health statistics
3216
3513
  */
@@ -3258,7 +3555,11 @@ var SQLiteEventStore = class {
3258
3555
  await this.initialize();
3259
3556
  const rows = sqliteAll(
3260
3557
  this.db,
3261
- `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
3558
+ `SELECT ml.level, COUNT(*) as count
3559
+ FROM memory_levels ml
3560
+ INNER JOIN events e ON e.id = ml.event_id
3561
+ WHERE ${notActiveQuarantinedSql("e.metadata")}
3562
+ GROUP BY ml.level`
3262
3563
  );
3263
3564
  return rows;
3264
3565
  }
@@ -3274,6 +3575,7 @@ var SQLiteEventStore = class {
3274
3575
  `SELECT e.* FROM events e
3275
3576
  INNER JOIN memory_levels ml ON e.id = ml.event_id
3276
3577
  WHERE ml.level = ?
3578
+ AND ${notActiveQuarantinedSql("e.metadata")}
3277
3579
  ORDER BY e.timestamp DESC
3278
3580
  LIMIT ? OFFSET ?`,
3279
3581
  [level, limit, offset]
@@ -3366,12 +3668,13 @@ var SQLiteEventStore = class {
3366
3668
  /**
3367
3669
  * Get most accessed memories (falls back to recent events if none accessed)
3368
3670
  */
3369
- async getMostAccessed(limit = 10) {
3671
+ async getMostAccessed(limit = 10, options) {
3370
3672
  await this.initialize();
3371
3673
  let rows = sqliteAll(
3372
3674
  this.db,
3373
3675
  `SELECT * FROM events
3374
3676
  WHERE access_count > 0
3677
+ AND ${maybeQuarantinePredicate(options)}
3375
3678
  ORDER BY access_count DESC, last_accessed_at DESC
3376
3679
  LIMIT ?`,
3377
3680
  [limit]
@@ -3380,6 +3683,7 @@ var SQLiteEventStore = class {
3380
3683
  rows = sqliteAll(
3381
3684
  this.db,
3382
3685
  `SELECT * FROM events
3686
+ WHERE ${maybeQuarantinePredicate(options)}
3383
3687
  ORDER BY timestamp DESC
3384
3688
  LIMIT ?`,
3385
3689
  [limit]
@@ -3510,6 +3814,7 @@ var SQLiteEventStore = class {
3510
3814
  FROM memory_helpfulness mh
3511
3815
  JOIN events e ON e.id = mh.event_id
3512
3816
  WHERE mh.measured_at IS NOT NULL
3817
+ AND ${notActiveQuarantinedSql("e.metadata")}
3513
3818
  GROUP BY mh.event_id
3514
3819
  ORDER BY avg_score DESC
3515
3820
  LIMIT ?`,
@@ -3574,6 +3879,7 @@ var SQLiteEventStore = class {
3574
3879
  FROM events_fts fts
3575
3880
  JOIN events e ON e.id = fts.event_id
3576
3881
  WHERE events_fts MATCH ?
3882
+ AND ${notActiveQuarantinedSql("e.metadata")}
3577
3883
  ORDER BY fts.rank
3578
3884
  LIMIT ?`,
3579
3885
  [searchTerms, limit]
@@ -3588,6 +3894,7 @@ var SQLiteEventStore = class {
3588
3894
  this.db,
3589
3895
  `SELECT *, 0 as rank FROM events
3590
3896
  WHERE content LIKE ?
3897
+ AND ${notActiveQuarantinedSql()}
3591
3898
  ORDER BY timestamp DESC
3592
3899
  LIMIT ?`,
3593
3900
  [likePattern, limit]
@@ -3794,6 +4101,7 @@ var SQLiteEventStore = class {
3794
4101
  `SELECT turn_id, MIN(timestamp) as min_ts
3795
4102
  FROM events
3796
4103
  WHERE session_id = ? AND turn_id IS NOT NULL
4104
+ AND ${maybeQuarantinePredicate(options)}
3797
4105
  GROUP BY turn_id
3798
4106
  ORDER BY min_ts DESC
3799
4107
  LIMIT ? OFFSET ?`,
@@ -3801,7 +4109,7 @@ var SQLiteEventStore = class {
3801
4109
  );
3802
4110
  const turns = [];
3803
4111
  for (const turnRow of turnRows) {
3804
- const events = await this.getEventsByTurn(turnRow.turn_id);
4112
+ const events = await this.getEventsByTurn(turnRow.turn_id, options);
3805
4113
  const promptEvent = events.find((e) => e.eventType === "user_prompt");
3806
4114
  const toolEvents = events.filter((e) => e.eventType === "tool_observation");
3807
4115
  const hasResponse = events.some((e) => e.eventType === "agent_response");
@@ -3820,11 +4128,11 @@ var SQLiteEventStore = class {
3820
4128
  /**
3821
4129
  * Get all events for a specific turn_id
3822
4130
  */
3823
- async getEventsByTurn(turnId) {
4131
+ async getEventsByTurn(turnId, options) {
3824
4132
  await this.initialize();
3825
4133
  const rows = sqliteAll(
3826
4134
  this.db,
3827
- `SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
4135
+ `SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
3828
4136
  [turnId]
3829
4137
  );
3830
4138
  return rows.map(this.rowToEvent);
@@ -3832,13 +4140,14 @@ var SQLiteEventStore = class {
3832
4140
  /**
3833
4141
  * Count total turns for a session
3834
4142
  */
3835
- async countSessionTurns(sessionId) {
4143
+ async countSessionTurns(sessionId, options) {
3836
4144
  await this.initialize();
3837
4145
  const row = sqliteGet(
3838
4146
  this.db,
3839
4147
  `SELECT COUNT(DISTINCT turn_id) as count
3840
4148
  FROM events
3841
- WHERE session_id = ? AND turn_id IS NOT NULL`,
4149
+ WHERE session_id = ? AND turn_id IS NOT NULL
4150
+ AND ${maybeQuarantinePredicate(options)}`,
3842
4151
  [sessionId]
3843
4152
  );
3844
4153
  return row?.count || 0;
@@ -3930,7 +4239,7 @@ var SQLiteEventStore = class {
3930
4239
  content: row.content,
3931
4240
  canonicalKey: row.canonical_key,
3932
4241
  dedupeKey: row.dedupe_key,
3933
- metadata: row.metadata ? JSON.parse(row.metadata) : void 0
4242
+ metadata: safeParseMetadataValue(row.metadata)
3934
4243
  };
3935
4244
  if (row.access_count !== void 0) {
3936
4245
  event.access_count = row.access_count;
@@ -4525,6 +4834,10 @@ var MemoryQueryService = class {
4525
4834
  await this.initialize();
4526
4835
  return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
4527
4836
  }
4837
+ async repairLegacyProjectScope(options) {
4838
+ await this.initialize();
4839
+ return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
4840
+ }
4528
4841
  async getStats() {
4529
4842
  await this.initialize();
4530
4843
  const deps = this.getStatsDeps();
@@ -6147,18 +6460,18 @@ function assertDefaultRetrieverStore(eventStore) {
6147
6460
  function createMemoryEngineServices(options) {
6148
6461
  const factories = options.factories ?? {};
6149
6462
  const storagePath = options.storagePath;
6150
- if (!options.readOnly && !fs5.existsSync(storagePath)) {
6151
- fs5.mkdirSync(storagePath, { recursive: true });
6463
+ if (!options.readOnly && !fs6.existsSync(storagePath)) {
6464
+ fs6.mkdirSync(storagePath, { recursive: true });
6152
6465
  }
6153
6466
  const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
6154
- path4.join(storagePath, "events.sqlite"),
6467
+ path5.join(storagePath, "events.sqlite"),
6155
6468
  {
6156
6469
  readonly: options.readOnly,
6157
6470
  markdownMirrorRoot: storagePath
6158
6471
  }
6159
6472
  );
6160
6473
  const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
6161
- path4.join(storagePath, "vectors")
6474
+ path5.join(storagePath, "vectors")
6162
6475
  );
6163
6476
  const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6164
6477
  const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
@@ -6569,8 +6882,8 @@ function createMemoryRuntimeService(deps) {
6569
6882
  }
6570
6883
 
6571
6884
  // src/extensions/shared-memory/shared-memory-services.ts
6572
- import * as fs6 from "fs";
6573
- import * as path5 from "path";
6885
+ import * as fs7 from "fs";
6886
+ import * as path6 from "path";
6574
6887
 
6575
6888
  // src/core/shared-event-store.ts
6576
6889
  var SharedEventStore = class {
@@ -7258,7 +7571,7 @@ var SharedMemoryServices = class {
7258
7571
  this.ensureDirectory(sharedPath, { allowCreate: true });
7259
7572
  const store = await this.openStore(sharedPath);
7260
7573
  this.sharedVectorStore = this.factories.createSharedVectorStore(
7261
- path5.join(sharedPath, "vectors")
7574
+ path6.join(sharedPath, "vectors")
7262
7575
  );
7263
7576
  await this.sharedVectorStore.initialize();
7264
7577
  this.sharedPromoter = this.factories.createSharedPromoter(
@@ -7331,7 +7644,7 @@ var SharedMemoryServices = class {
7331
7644
  async createOpenStorePromise(sharedPath) {
7332
7645
  if (!this.sharedEventStore) {
7333
7646
  const sharedEventStore = this.factories.createSharedEventStore(
7334
- path5.join(sharedPath, "shared.duckdb")
7647
+ path6.join(sharedPath, "shared.duckdb")
7335
7648
  );
7336
7649
  await sharedEventStore.initialize();
7337
7650
  this.sharedEventStore = sharedEventStore;
@@ -7351,9 +7664,9 @@ var SharedMemoryServices = class {
7351
7664
  }
7352
7665
  get factories() {
7353
7666
  return {
7354
- existsSync: this.options.factories?.existsSync ?? fs6.existsSync,
7667
+ existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
7355
7668
  mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
7356
- fs6.mkdirSync(targetPath, { recursive: true });
7669
+ fs7.mkdirSync(targetPath, { recursive: true });
7357
7670
  }),
7358
7671
  createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
7359
7672
  createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
@@ -7468,37 +7781,11 @@ function createMemoryServiceComposition(options) {
7468
7781
  }
7469
7782
  function defaultExpandPath(targetPath) {
7470
7783
  if (targetPath.startsWith("~")) {
7471
- return path6.join(os.homedir(), targetPath.slice(1));
7784
+ return path7.join(os2.homedir(), targetPath.slice(1));
7472
7785
  }
7473
7786
  return targetPath;
7474
7787
  }
7475
7788
 
7476
- // src/core/registry/project-path.ts
7477
- import * as crypto2 from "crypto";
7478
- import * as fs7 from "fs";
7479
- import * as os2 from "os";
7480
- import * as path7 from "path";
7481
- function normalizeProjectPath(projectPath) {
7482
- const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
7483
- try {
7484
- return fs7.realpathSync(expanded);
7485
- } catch {
7486
- return path7.resolve(expanded);
7487
- }
7488
- }
7489
- function hashProjectPath(projectPath) {
7490
- const normalizedPath = normalizeProjectPath(projectPath);
7491
- return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
7492
- }
7493
- function getProjectStoragePath(projectPath) {
7494
- const hash = hashProjectPath(projectPath);
7495
- return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
7496
- }
7497
- function resolveProjectStoragePath(projectOrHash) {
7498
- const isHash = /^[a-f0-9]{8}$/.test(projectOrHash);
7499
- return isHash ? path7.join(os2.homedir(), ".claude-code", "memory", "projects", projectOrHash) : getProjectStoragePath(projectOrHash);
7500
- }
7501
-
7502
7789
  // src/core/registry/session-registry.ts
7503
7790
  import * as fs8 from "fs";
7504
7791
  import * as os3 from "os";
@@ -7825,6 +8112,9 @@ var MemoryService = class {
7825
8112
  async recoverStuckOutboxItems(options) {
7826
8113
  return this.queryService.recoverStuckOutboxItems(options);
7827
8114
  }
8115
+ async repairLegacyProjectScope(options) {
8116
+ return this.queryService.repairLegacyProjectScope(options);
8117
+ }
7828
8118
  async getRetrievalTraceStats() {
7829
8119
  return this.retrievalAnalyticsService.getRetrievalTraceStats();
7830
8120
  }
@@ -8554,7 +8844,7 @@ import * as os7 from "os";
8554
8844
  import * as readline2 from "readline";
8555
8845
  import { createHash as createHash3, randomUUID as randomUUID9 } from "crypto";
8556
8846
  var CODEX_VALIDATION_DEFAULT_MAX_CONTENT_CHARS = 1e4;
8557
- function isRecord(value) {
8847
+ function isRecord2(value) {
8558
8848
  return typeof value === "object" && value !== null;
8559
8849
  }
8560
8850
  function normalizeMaybeRealpath(p) {
@@ -8571,7 +8861,7 @@ function extractCodexContentText(content, maxContentChars = CODEX_VALIDATION_DEF
8571
8861
  texts.push(content);
8572
8862
  } else if (Array.isArray(content)) {
8573
8863
  for (const block of content) {
8574
- if (!isRecord(block))
8864
+ if (!isRecord2(block))
8575
8865
  continue;
8576
8866
  const b = block;
8577
8867
  const t = typeof b.type === "string" ? b.type : "";
@@ -8659,7 +8949,7 @@ async function readCodexSessionMeta(filePath) {
8659
8949
  const obj = JSON.parse(line);
8660
8950
  if (obj.type !== "session_meta")
8661
8951
  continue;
8662
- if (!isRecord(obj.payload))
8952
+ if (!isRecord2(obj.payload))
8663
8953
  break;
8664
8954
  const payload = obj.payload;
8665
8955
  const sessionId = typeof payload.id === "string" ? payload.id : null;
@@ -8797,7 +9087,7 @@ async function normalizeCodexSessionFile(filePath, options = {}) {
8797
9087
  }
8798
9088
  if (entry.type === "session_meta")
8799
9089
  continue;
8800
- if (entry.type !== "response_item" || !isRecord(entry.payload)) {
9090
+ if (entry.type !== "response_item" || !isRecord2(entry.payload)) {
8801
9091
  summary.skippedUnsupportedRecords += 1;
8802
9092
  continue;
8803
9093
  }
@@ -8952,7 +9242,7 @@ var CodexSessionHistoryImporter = class {
8952
9242
  const obj = JSON.parse(line);
8953
9243
  if (obj.type !== "session_meta")
8954
9244
  continue;
8955
- if (!isRecord(obj.payload))
9245
+ if (!isRecord2(obj.payload))
8956
9246
  break;
8957
9247
  const payload = obj.payload;
8958
9248
  const sessionId = typeof payload.id === "string" ? payload.id : null;
@@ -9168,7 +9458,7 @@ var CodexSessionHistoryImporter = class {
9168
9458
  try {
9169
9459
  const entry = JSON.parse(line);
9170
9460
  result.totalMessages++;
9171
- if (entry.type === "response_item" && isRecord(entry.payload)) {
9461
+ if (entry.type === "response_item" && isRecord2(entry.payload)) {
9172
9462
  const payload = entry.payload;
9173
9463
  if (payload.type !== "message")
9174
9464
  continue;
@@ -9360,10 +9650,10 @@ var SENSITIVE_PATTERNS = [
9360
9650
  // Redact the whole URI so usernames, credentials, hosts, paths, and query
9361
9651
  // params do not leak either.
9362
9652
  /\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>/@]+@[^\s'"`<>]+/gi,
9363
- /password\s*[:=]\s*['"]?[^\s'"]+/gi,
9364
- /api[_-]?key\s*[:=]\s*['"]?[^\s'"]+/gi,
9365
- /secret\s*[:=]\s*['"]?[^\s'"]+/gi,
9366
- /token\s*[:=]\s*['"]?[^\s'"]+/gi,
9653
+ /(?:[\w.-]+[-_])?password\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
9654
+ /(?:[\w.-]+[-_])?api[-_]?key\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
9655
+ /(?:[\w.-]+[-_])?secret\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
9656
+ /(?:[\w.-]+[-_])?token\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
9367
9657
  /bearer\s+[a-zA-Z0-9\-_.]+/gi,
9368
9658
  /AWS[_-]?ACCESS[_-]?KEY[_-]?ID\s*[:=]\s*['"]?[A-Z0-9]+/gi,
9369
9659
  /AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY\s*[:=]\s*['"]?[^\s'"]+/gi,
@@ -9373,6 +9663,35 @@ var SENSITIVE_PATTERNS = [
9373
9663
  /sk-[a-zA-Z0-9]{48}/g
9374
9664
  // OpenAI API Key
9375
9665
  ];
9666
+ var CLI_SECRET_OPTION_PATTERNS = [
9667
+ /(--(?:[a-z0-9]+[-_])*(?:password|secret|api[-_]?key|token|bearer)(?:[-_][a-z0-9]+)*(?:\s+|=))(?:"[^"]*"|'[^']*'|[^\s'"`<>]+)/gi
9668
+ ];
9669
+ var URL_FOLLOWING_SECRET_PATTERN = /((?:https?:\/\/[^\s'"`<>]+)\s*\r?\n\s*)([A-Za-z0-9!@#$%^&*._+\-~:=/]{6,})(?=\s*(?:\r?\n|$))/gi;
9670
+ function looksLikePastedSecret(value) {
9671
+ return value.length >= 8 && /(?:\d|[^A-Za-z0-9])/.test(value);
9672
+ }
9673
+ function maskUrlFollowingSecret(value) {
9674
+ let count = 0;
9675
+ const content = value.replace(URL_FOLLOWING_SECRET_PATTERN, (_match, prefix, secret) => {
9676
+ if (!looksLikePastedSecret(secret))
9677
+ return `${prefix}${secret}`;
9678
+ count++;
9679
+ return `${prefix}[REDACTED]`;
9680
+ });
9681
+ return { content, count };
9682
+ }
9683
+ function maskCliSecretOptions(value) {
9684
+ let count = 0;
9685
+ let filtered = value;
9686
+ for (const pattern of CLI_SECRET_OPTION_PATTERNS) {
9687
+ pattern.lastIndex = 0;
9688
+ filtered = filtered.replace(pattern, (_match, prefix) => {
9689
+ count++;
9690
+ return `${prefix}[REDACTED]`;
9691
+ });
9692
+ }
9693
+ return { content: filtered, count };
9694
+ }
9376
9695
  function applyPrivacyFilter(content, config) {
9377
9696
  let filtered = content;
9378
9697
  let privateTagCount = 0;
@@ -9386,6 +9705,12 @@ function applyPrivacyFilter(content, config) {
9386
9705
  filtered = tagResult.filtered;
9387
9706
  privateTagCount = tagResult.stats.count;
9388
9707
  }
9708
+ const cliResult = maskCliSecretOptions(filtered);
9709
+ filtered = cliResult.content;
9710
+ patternMatchCount += cliResult.count;
9711
+ const urlSecretResult = maskUrlFollowingSecret(filtered);
9712
+ filtered = urlSecretResult.content;
9713
+ patternMatchCount += urlSecretResult.count;
9389
9714
  for (const pattern of SENSITIVE_PATTERNS) {
9390
9715
  pattern.lastIndex = 0;
9391
9716
  const matches = filtered.match(pattern);
@@ -9397,13 +9722,13 @@ function applyPrivacyFilter(content, config) {
9397
9722
  for (const patternStr of config.excludePatterns || []) {
9398
9723
  try {
9399
9724
  const regex = new RegExp(
9400
- `(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
9725
+ `(^|[^\\w-])(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
9401
9726
  "gi"
9402
9727
  );
9403
9728
  const matches = filtered.match(regex);
9404
9729
  if (matches) {
9405
9730
  patternMatchCount += matches.length;
9406
- filtered = filtered.replace(regex, "[REDACTED]");
9731
+ filtered = filtered.replace(regex, "$1[REDACTED]");
9407
9732
  }
9408
9733
  } catch {
9409
9734
  }
@@ -11309,7 +11634,7 @@ function computeKpiMetrics(events, usefulRecallRate) {
11309
11634
  };
11310
11635
  }
11311
11636
  statsRouter.get("/shared", async (c) => {
11312
- const memoryService = getServiceFromQuery(c);
11637
+ const memoryService = getLightweightServiceFromQuery(c);
11313
11638
  try {
11314
11639
  await memoryService.initialize();
11315
11640
  const sharedStats = await memoryService.getSharedStoreStats();
@@ -11333,8 +11658,7 @@ statsRouter.get("/shared", async (c) => {
11333
11658
  }
11334
11659
  });
11335
11660
  statsRouter.get("/endless", async (c) => {
11336
- const projectPath = c.req.query("project") || process.cwd();
11337
- const memoryService = getMemoryServiceForProject(projectPath);
11661
+ const memoryService = getLightweightServiceFromQuery(c);
11338
11662
  try {
11339
11663
  await memoryService.initialize();
11340
11664
  const status = await memoryService.getEndlessModeStatus();
@@ -11366,7 +11690,7 @@ statsRouter.get("/levels/:level", async (c) => {
11366
11690
  if (!validLevels.includes(level)) {
11367
11691
  return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
11368
11692
  }
11369
- const memoryService = getServiceFromQuery(c);
11693
+ const memoryService = getLightweightServiceFromQuery(c);
11370
11694
  try {
11371
11695
  await memoryService.initialize();
11372
11696
  let events = await memoryService.getEventsByLevel(level, { limit: limit * 2, offset });
@@ -11458,7 +11782,7 @@ statsRouter.get("/", async (c) => {
11458
11782
  });
11459
11783
  statsRouter.get("/most-accessed", async (c) => {
11460
11784
  const limit = parseInt(c.req.query("limit") || "10", 10);
11461
- const memoryService = getServiceFromQuery(c);
11785
+ const memoryService = getLightweightServiceFromQuery(c);
11462
11786
  try {
11463
11787
  await memoryService.initialize();
11464
11788
  console.log("[most-accessed] Fetching most accessed memories, limit:", limit);
@@ -11489,7 +11813,7 @@ statsRouter.get("/most-accessed", async (c) => {
11489
11813
  });
11490
11814
  statsRouter.get("/timeline", async (c) => {
11491
11815
  const days = parseInt(c.req.query("days") || "7", 10);
11492
- const memoryService = getServiceFromQuery(c);
11816
+ const memoryService = getLightweightServiceFromQuery(c);
11493
11817
  try {
11494
11818
  await memoryService.initialize();
11495
11819
  const recentEvents = await memoryService.getRecentEvents(1e4);
@@ -11521,7 +11845,7 @@ statsRouter.get("/timeline", async (c) => {
11521
11845
  });
11522
11846
  statsRouter.get("/helpfulness", async (c) => {
11523
11847
  const limit = parseInt(c.req.query("limit") || "10", 10);
11524
- const memoryService = getServiceFromQuery(c);
11848
+ const memoryService = getLightweightServiceFromQuery(c);
11525
11849
  try {
11526
11850
  await memoryService.initialize();
11527
11851
  const stats = await memoryService.getHelpfulnessStats();
@@ -11578,7 +11902,7 @@ statsRouter.get("/usefulness", async (c) => {
11578
11902
  });
11579
11903
  statsRouter.get("/retrieval-traces", async (c) => {
11580
11904
  const limit = parseInt(c.req.query("limit") || "50", 10);
11581
- const memoryService = getServiceFromQuery(c);
11905
+ const memoryService = getLightweightServiceFromQuery(c);
11582
11906
  try {
11583
11907
  await memoryService.initialize();
11584
11908
  const traces = await memoryService.getRecentRetrievalTraces(limit);
@@ -11632,7 +11956,7 @@ statsRouter.get("/retrieval-traces", async (c) => {
11632
11956
  statsRouter.get("/retrieval-review-queue", async (c) => {
11633
11957
  const limit = parseStatsLimit(c.req.query("limit"), 10, 50);
11634
11958
  const scanLimit = parseStatsLimit(c.req.query("scanLimit"), 500, 5e3);
11635
- const memoryService = getServiceFromQuery(c);
11959
+ const memoryService = getLightweightServiceFromQuery(c);
11636
11960
  try {
11637
11961
  await memoryService.initialize();
11638
11962
  const traces = await memoryService.getRecentRetrievalTraces(scanLimit);
@@ -11666,7 +11990,7 @@ statsRouter.get("/retrieval-review-queue", async (c) => {
11666
11990
  statsRouter.get("/kpi", async (c) => {
11667
11991
  const rawWindow = c.req.query("window") || "7d";
11668
11992
  const window = rawWindow === "24h" || rawWindow === "30d" ? rawWindow : "7d";
11669
- const memoryService = getServiceFromQuery(c);
11993
+ const memoryService = getLightweightServiceFromQuery(c);
11670
11994
  try {
11671
11995
  await memoryService.initialize();
11672
11996
  const now = Date.now();
@@ -13632,6 +13956,54 @@ function resolveDashboardCommandOptions(options) {
13632
13956
  };
13633
13957
  }
13634
13958
 
13959
+ // src/apps/cli/repair-command.ts
13960
+ function resolveLegacyProjectScopeRepairOptions(options) {
13961
+ if (options.project !== void 0 && options.project.trim().length === 0) {
13962
+ throw new Error("legacy project-scope repair --project must not be empty");
13963
+ }
13964
+ if (options.projectHash !== void 0 && options.projectHash.trim().length === 0) {
13965
+ throw new Error("legacy project-scope repair --project-hash must not be empty");
13966
+ }
13967
+ const projectPath = typeof options.project === "string" && options.project.length > 0 ? options.project : void 0;
13968
+ const projectHash = typeof options.projectHash === "string" && options.projectHash.length > 0 ? options.projectHash : void 0;
13969
+ if (!projectPath && !projectHash) {
13970
+ throw new Error("legacy project-scope repair requires --project or --project-hash");
13971
+ }
13972
+ if (projectHash && !/^[a-f0-9]{8}$/.test(projectHash)) {
13973
+ throw new Error("legacy project-scope repair --project-hash must be an 8-character lowercase hex hash");
13974
+ }
13975
+ if (projectPath && projectHash && hashProjectPath(projectPath) !== projectHash) {
13976
+ throw new Error("legacy project-scope repair --project and --project-hash refer to different project stores");
13977
+ }
13978
+ return {
13979
+ projectPath,
13980
+ projectHash,
13981
+ dryRun: options.apply !== true
13982
+ };
13983
+ }
13984
+ function formatLegacyProjectScopeRepairResult(result) {
13985
+ const lines = [
13986
+ "Legacy project-scope repair",
13987
+ `Mode: ${result.dryRun ? "dry-run" : "apply"}`,
13988
+ `Project: ${result.projectHash}`,
13989
+ `Scanned: ${result.scanned}`,
13990
+ `Already scoped: ${result.alreadyScoped}`,
13991
+ `Repaired: ${result.repaired}`,
13992
+ `Quarantined: ${result.quarantined}`,
13993
+ `Skipped: ${result.skipped}`
13994
+ ];
13995
+ if (result.samples.length > 0) {
13996
+ lines.push("Samples:");
13997
+ for (const sample of result.samples) {
13998
+ lines.push(`- ${sample.eventId} ${sample.action} ${sample.reason}`);
13999
+ }
14000
+ }
14001
+ if (result.dryRun) {
14002
+ lines.push("Dry-run only. Re-run with --apply to mutate event metadata.");
14003
+ }
14004
+ return lines.join("\n");
14005
+ }
14006
+
13635
14007
  // src/core/external-market-context.ts
13636
14008
  var MAX_RENDERED_ITEMS = 8;
13637
14009
  var MAX_FRED_SERIES = 10;
@@ -14242,7 +14614,7 @@ async function runMarketContextCommand(options) {
14242
14614
  }
14243
14615
  }
14244
14616
  var program = new Command();
14245
- program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.37");
14617
+ program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.39");
14246
14618
  program.command("market-context").description("Fetch read-only DART/FRED/Finnhub context with structured MarketContextSnapshot bull/bear/risk/catalyst analysis").option("--company <name>", "Company name for DART fallback search and report subject").option("--dart-corp-code <code>", "Exact DART corp_code for issuer-specific filings").option("--symbol <ticker>", "Listed ticker for Finnhub company profile").option("--providers <list>", "Comma-separated providers: dart,fred,finnhub").option("--fred-series <list>", "Comma-separated FRED series IDs").option("--json", "Print structured JSON including analysis.marketSnapshot").option("--no-snapshot", "Disable MarketContextSnapshot and DART company snapshot analysis").action(async (options) => {
14247
14619
  try {
14248
14620
  await runMarketContextCommand(options);
@@ -14498,6 +14870,46 @@ program.command("process").description("Process pending embeddings").option("-p,
14498
14870
  process.exit(1);
14499
14871
  }
14500
14872
  });
14873
+ var repairCommand = program.command("repair").description("Repair or quarantine legacy memory metadata");
14874
+ repairCommand.command("legacy-project-scope").description("Dry-run or apply project-scope repair/quarantine for legacy imported events").option("-p, --project <path>", "Project path (defaults to cwd unless --project-hash is used)").option("--project-hash <hash>", "Project storage hash for hash-only repair flows").option("--apply", "Apply metadata changes (default is dry-run)").action(async (options) => {
14875
+ try {
14876
+ const projectPath = options.project !== void 0 ? options.project : !options.projectHash ? process.cwd() : void 0;
14877
+ const repairOptions = resolveLegacyProjectScopeRepairOptions({
14878
+ project: projectPath,
14879
+ projectHash: options.projectHash,
14880
+ apply: options.apply
14881
+ });
14882
+ const storagePath = projectPath ? getProjectStoragePath(projectPath) : resolveProjectStoragePath(repairOptions.projectHash);
14883
+ const dbPath = path21.join(storagePath, "events.sqlite");
14884
+ if (repairOptions.dryRun && !fs18.existsSync(dbPath)) {
14885
+ const projectHash = repairOptions.projectHash || hashProjectPath(repairOptions.projectPath);
14886
+ console.log(formatLegacyProjectScopeRepairResult({
14887
+ dryRun: true,
14888
+ projectHash,
14889
+ scanned: 0,
14890
+ repaired: 0,
14891
+ quarantined: 0,
14892
+ alreadyScoped: 0,
14893
+ skipped: 0,
14894
+ samples: []
14895
+ }));
14896
+ return;
14897
+ }
14898
+ const store = new SQLiteEventStore(dbPath, {
14899
+ readonly: repairOptions.dryRun
14900
+ });
14901
+ try {
14902
+ const result = await store.repairLegacyProjectScope(repairOptions);
14903
+ console.log(formatLegacyProjectScopeRepairResult(result));
14904
+ } finally {
14905
+ await store.close().catch(() => void 0);
14906
+ }
14907
+ } catch (error) {
14908
+ const message = error instanceof Error ? error.message : String(error);
14909
+ console.error(`Repair failed: ${message}`);
14910
+ process.exit(1);
14911
+ }
14912
+ });
14501
14913
  program.command("mongo-sync").description("Sync events with MongoDB for multi-server collaboration (optional)").option("-p, --project <path>", "Project path (defaults to cwd)").option("--mongo-uri <uri>", "MongoDB connection URI (env: CLAUDE_MEMORY_MONGO_URI)").option("--mongo-db <name>", "MongoDB database name (env: CLAUDE_MEMORY_MONGO_DB)").option("--mongo-project <key>", "Remote project key (env: CLAUDE_MEMORY_MONGO_PROJECT, default: basename(projectPath))").option("--direction <dir>", "push|pull|both", "both").option("--batch-size <n>", "Batch size", "500").option("--interval <ms>", "Watch interval ms", "30000").option("--watch", "Run continuously").action(async (options) => {
14502
14914
  const projectPath = options.project || process.cwd();
14503
14915
  const mongoUri = options.mongoUri || process.env.CLAUDE_MEMORY_MONGO_URI;