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.
@@ -10,8 +10,8 @@ const __dirname = dirname(__filename);
10
10
  import * as os5 from "os";
11
11
 
12
12
  // src/core/engine/memory-service-composition.ts
13
- import * as os from "os";
14
- import * as path6 from "path";
13
+ import * as os2 from "os";
14
+ import * as path7 from "path";
15
15
 
16
16
  // src/core/metadata-extractor.ts
17
17
  function getFileType(filePath) {
@@ -1641,8 +1641,8 @@ function createEndlessMemoryServices(options) {
1641
1641
  }
1642
1642
 
1643
1643
  // src/core/engine/memory-engine-services.ts
1644
- import * as fs5 from "fs";
1645
- import * as path4 from "path";
1644
+ import * as fs6 from "fs";
1645
+ import * as path5 from "path";
1646
1646
 
1647
1647
  // src/extensions/vector/embedder.ts
1648
1648
  var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
@@ -2320,14 +2320,39 @@ function makeDedupeKey(content, sessionId) {
2320
2320
  return `${sessionId}:${contentHash}`;
2321
2321
  }
2322
2322
 
2323
+ // src/core/sqlite-event-store.ts
2324
+ import * as nodePath2 from "path";
2325
+
2326
+ // src/core/registry/project-path.ts
2327
+ import * as crypto2 from "crypto";
2328
+ import * as fs3 from "fs";
2329
+ import * as os from "os";
2330
+ import * as path3 from "path";
2331
+ function normalizeProjectPath(projectPath) {
2332
+ const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
2333
+ try {
2334
+ return fs3.realpathSync(expanded);
2335
+ } catch {
2336
+ return path3.resolve(expanded);
2337
+ }
2338
+ }
2339
+ function hashProjectPath(projectPath) {
2340
+ const normalizedPath = normalizeProjectPath(projectPath);
2341
+ return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
2342
+ }
2343
+ function getProjectStoragePath(projectPath) {
2344
+ const hash = hashProjectPath(projectPath);
2345
+ return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
2346
+ }
2347
+
2323
2348
  // src/core/sqlite-wrapper.ts
2324
2349
  import Database from "better-sqlite3";
2325
- import * as fs3 from "fs";
2350
+ import * as fs4 from "fs";
2326
2351
  import * as nodePath from "path";
2327
2352
  function createSQLiteDatabase(path12, options) {
2328
2353
  const dir = nodePath.dirname(path12);
2329
- if (!fs3.existsSync(dir)) {
2330
- fs3.mkdirSync(dir, { recursive: true });
2354
+ if (!fs4.existsSync(dir)) {
2355
+ fs4.mkdirSync(dir, { recursive: true });
2331
2356
  }
2332
2357
  const db = new Database(path12, {
2333
2358
  readonly: options?.readonly ?? false
@@ -2376,8 +2401,8 @@ function toSQLiteTimestamp(date) {
2376
2401
  }
2377
2402
 
2378
2403
  // src/core/markdown-mirror.ts
2379
- import * as fs4 from "fs/promises";
2380
- import * as path3 from "path";
2404
+ import * as fs5 from "fs/promises";
2405
+ import * as path4 from "path";
2381
2406
  var DEFAULT_NAMESPACE = "default";
2382
2407
  var DEFAULT_CATEGORY = "uncategorized";
2383
2408
  function sanitizeSegment2(input, fallback) {
@@ -2406,7 +2431,7 @@ function buildMirrorPath2(rootDir, event) {
2406
2431
  const yyyy = d.getFullYear();
2407
2432
  const mm = String(d.getMonth() + 1).padStart(2, "0");
2408
2433
  const dd = String(d.getDate()).padStart(2, "0");
2409
- return path3.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
2434
+ return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
2410
2435
  }
2411
2436
  function formatMirrorEntry(event) {
2412
2437
  const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
@@ -2427,8 +2452,8 @@ var MarkdownMirror2 = class {
2427
2452
  }
2428
2453
  async append(event) {
2429
2454
  const outPath = buildMirrorPath2(this.rootDir, event);
2430
- await fs4.mkdir(path3.dirname(outPath), { recursive: true });
2431
- await fs4.appendFile(outPath, formatMirrorEntry(event), "utf8");
2455
+ await fs5.mkdir(path4.dirname(outPath), { recursive: true });
2456
+ await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
2432
2457
  return outPath;
2433
2458
  }
2434
2459
  };
@@ -2449,6 +2474,135 @@ function emptyOutboxRecoveryResult() {
2449
2474
  vector: { recoveredProcessing: 0, retriedFailed: 0 }
2450
2475
  };
2451
2476
  }
2477
+ function isRecord(value) {
2478
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2479
+ }
2480
+ function getNestedRecord(root, path12) {
2481
+ let cursor = root;
2482
+ for (const key of path12) {
2483
+ if (!isRecord(cursor))
2484
+ return void 0;
2485
+ cursor = cursor[key];
2486
+ }
2487
+ return isRecord(cursor) ? cursor : void 0;
2488
+ }
2489
+ function getNestedString(root, path12) {
2490
+ let cursor = root;
2491
+ for (const key of path12) {
2492
+ if (!isRecord(cursor))
2493
+ return void 0;
2494
+ cursor = cursor[key];
2495
+ }
2496
+ return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
2497
+ }
2498
+ function metadataProjectHash(metadata) {
2499
+ return getNestedString(metadata, ["scope", "project", "hash"]);
2500
+ }
2501
+ function metadataProjectPaths(metadata) {
2502
+ const candidates = [
2503
+ getNestedString(metadata, ["projectPath"]),
2504
+ getNestedString(metadata, ["sourceProjectPath"]),
2505
+ getNestedString(metadata, ["scope", "project", "path"])
2506
+ ];
2507
+ const paths = [];
2508
+ for (const value of candidates) {
2509
+ if (value && !paths.includes(value))
2510
+ paths.push(value);
2511
+ }
2512
+ return paths;
2513
+ }
2514
+ function metadataProjectPath(metadata) {
2515
+ return metadataProjectPaths(metadata)[0];
2516
+ }
2517
+ function isActiveQuarantinedMetadata(metadata) {
2518
+ const quarantine = getNestedRecord(metadata, ["quarantine"]);
2519
+ return quarantine?.status === "active";
2520
+ }
2521
+ function activeQuarantineStatusExpression(column = "metadata") {
2522
+ return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
2523
+ }
2524
+ function notActiveQuarantinedSql(column = "metadata") {
2525
+ return `${activeQuarantineStatusExpression(column)} != 'active'`;
2526
+ }
2527
+ function maybeQuarantinePredicate(options, column = "metadata") {
2528
+ return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
2529
+ }
2530
+ function safeParseMetadataValue(value) {
2531
+ if (!value)
2532
+ return void 0;
2533
+ if (typeof value === "object")
2534
+ return isRecord(value) ? value : void 0;
2535
+ if (typeof value !== "string")
2536
+ return void 0;
2537
+ try {
2538
+ const parsed = JSON.parse(value);
2539
+ return isRecord(parsed) ? parsed : void 0;
2540
+ } catch {
2541
+ return void 0;
2542
+ }
2543
+ }
2544
+ function isImportedOrLegacyScopedMetadata(metadata) {
2545
+ if (!metadata)
2546
+ return false;
2547
+ return Boolean(
2548
+ metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
2549
+ );
2550
+ }
2551
+ function addMetadataTag(metadata, tag) {
2552
+ const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
2553
+ if (!current.includes(tag))
2554
+ metadata.tags = [...current, tag];
2555
+ }
2556
+ function buildRepairResult(projectHash, dryRun) {
2557
+ return {
2558
+ dryRun,
2559
+ projectHash,
2560
+ scanned: 0,
2561
+ repaired: 0,
2562
+ quarantined: 0,
2563
+ alreadyScoped: 0,
2564
+ skipped: 0,
2565
+ samples: []
2566
+ };
2567
+ }
2568
+ function normalizeRepoName(value) {
2569
+ return value.replace(/\.git$/i, "").trim().toLowerCase();
2570
+ }
2571
+ function projectBasename(projectPath) {
2572
+ if (!projectPath)
2573
+ return void 0;
2574
+ const trimmed = projectPath.replace(/[\\/]+$/, "");
2575
+ const basename2 = nodePath2.basename(trimmed);
2576
+ return basename2 ? normalizeRepoName(basename2) : void 0;
2577
+ }
2578
+ function isProjectScopeRepairExplanation(content) {
2579
+ const normalized = content.toLowerCase();
2580
+ const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
2581
+ const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
2582
+ return hasRepairContext && hasExplanationContext;
2583
+ }
2584
+ function hasConflictingContentProjectHint(content, projectPath) {
2585
+ const currentName = projectBasename(projectPath);
2586
+ if (!currentName)
2587
+ return false;
2588
+ if (isProjectScopeRepairExplanation(content))
2589
+ return false;
2590
+ const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
2591
+ let githubMatch;
2592
+ while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
2593
+ const repo = normalizeRepoName(githubMatch[2] || "");
2594
+ if (repo && repo !== currentName)
2595
+ return true;
2596
+ }
2597
+ const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
2598
+ let workspaceMatch;
2599
+ while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
2600
+ const repo = normalizeRepoName(workspaceMatch[1] || "");
2601
+ if (repo && repo !== currentName)
2602
+ return true;
2603
+ }
2604
+ return false;
2605
+ }
2452
2606
  var SQLiteEventStore = class {
2453
2607
  db;
2454
2608
  initialized = false;
@@ -2947,11 +3101,11 @@ var SQLiteEventStore = class {
2947
3101
  /**
2948
3102
  * Get events by session ID
2949
3103
  */
2950
- async getSessionEvents(sessionId) {
3104
+ async getSessionEvents(sessionId, options) {
2951
3105
  await this.initialize();
2952
3106
  const rows = sqliteAll(
2953
3107
  this.db,
2954
- `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
3108
+ `SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
2955
3109
  [sessionId]
2956
3110
  );
2957
3111
  return rows.map(this.rowToEvent);
@@ -2959,11 +3113,11 @@ var SQLiteEventStore = class {
2959
3113
  /**
2960
3114
  * Get recent events
2961
3115
  */
2962
- async getRecentEvents(limit = 100) {
3116
+ async getRecentEvents(limit = 100, options) {
2963
3117
  await this.initialize();
2964
3118
  const rows = sqliteAll(
2965
3119
  this.db,
2966
- `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
3120
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
2967
3121
  [limit]
2968
3122
  );
2969
3123
  return rows.map(this.rowToEvent);
@@ -2971,11 +3125,11 @@ var SQLiteEventStore = class {
2971
3125
  /**
2972
3126
  * Get event by ID
2973
3127
  */
2974
- async getEvent(id) {
3128
+ async getEvent(id, options) {
2975
3129
  await this.initialize();
2976
3130
  const row = sqliteGet(
2977
3131
  this.db,
2978
- `SELECT * FROM events WHERE id = ?`,
3132
+ `SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
2979
3133
  [id]
2980
3134
  );
2981
3135
  if (!row)
@@ -2985,11 +3139,11 @@ var SQLiteEventStore = class {
2985
3139
  /**
2986
3140
  * Get events since a timestamp (for sync)
2987
3141
  */
2988
- async getEventsSince(timestamp, limit = 1e3) {
3142
+ async getEventsSince(timestamp, limit = 1e3, options) {
2989
3143
  await this.initialize();
2990
3144
  const rows = sqliteAll(
2991
3145
  this.db,
2992
- `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
3146
+ `SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
2993
3147
  [timestamp, limit]
2994
3148
  );
2995
3149
  return rows.map(this.rowToEvent);
@@ -2998,11 +3152,11 @@ var SQLiteEventStore = class {
2998
3152
  * Get events since a SQLite rowid (for robust incremental replication).
2999
3153
  * Rowid is monotonic for append-only tables, independent of client timestamps.
3000
3154
  */
3001
- async getEventsSinceRowid(lastRowid, limit = 1e3) {
3155
+ async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
3002
3156
  await this.initialize();
3003
3157
  const rows = sqliteAll(
3004
3158
  this.db,
3005
- `SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
3159
+ `SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
3006
3160
  [lastRowid, limit]
3007
3161
  );
3008
3162
  return rows.map((row) => ({
@@ -3237,19 +3391,19 @@ var SQLiteEventStore = class {
3237
3391
  /**
3238
3392
  * Count total events
3239
3393
  */
3240
- async countEvents() {
3394
+ async countEvents(options) {
3241
3395
  await this.initialize();
3242
- const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
3396
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
3243
3397
  return row?.count || 0;
3244
3398
  }
3245
3399
  /**
3246
3400
  * Get events page in timestamp ascending order (stable migration/reindex scans)
3247
3401
  */
3248
- async getEventsPage(limit = 1e3, offset = 0) {
3402
+ async getEventsPage(limit = 1e3, offset = 0, options) {
3249
3403
  await this.initialize();
3250
3404
  const rows = sqliteAll(
3251
3405
  this.db,
3252
- `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
3406
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
3253
3407
  [limit, offset]
3254
3408
  );
3255
3409
  return rows.map(this.rowToEvent);
@@ -3323,6 +3477,145 @@ var SQLiteEventStore = class {
3323
3477
  result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
3324
3478
  return result;
3325
3479
  }
3480
+ /**
3481
+ * Repair legacy imported events that predate canonical project scope metadata.
3482
+ *
3483
+ * Same-project legacy rows are tagged with scope.project.hash. Rows that look
3484
+ * imported but cannot be proven to belong to this project are quarantined so
3485
+ * dashboard default reads/search do not surface cross-project contamination.
3486
+ */
3487
+ async repairLegacyProjectScope(options = {}) {
3488
+ await this.initialize();
3489
+ const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
3490
+ if (!projectHash) {
3491
+ throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
3492
+ }
3493
+ if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
3494
+ throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
3495
+ }
3496
+ const dryRun = options.dryRun === true;
3497
+ const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
3498
+ const result = buildRepairResult(projectHash, dryRun);
3499
+ const rows = sqliteAll(
3500
+ this.db,
3501
+ `SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
3502
+ FROM events e
3503
+ LEFT JOIN sessions s ON s.id = e.session_id
3504
+ ORDER BY e.timestamp ASC`,
3505
+ []
3506
+ );
3507
+ const sample = (entry) => {
3508
+ if (result.samples.length < 20)
3509
+ result.samples.push(entry);
3510
+ };
3511
+ for (const row of rows) {
3512
+ result.scanned++;
3513
+ let metadata = {};
3514
+ let metadataParseInvalid = false;
3515
+ if (row.metadata) {
3516
+ const parsed = safeParseMetadataValue(row.metadata);
3517
+ if (parsed) {
3518
+ metadata = parsed;
3519
+ } else {
3520
+ metadataParseInvalid = true;
3521
+ }
3522
+ }
3523
+ if (isActiveQuarantinedMetadata(metadata)) {
3524
+ result.skipped++;
3525
+ continue;
3526
+ }
3527
+ const currentHash = metadataProjectHash(metadata);
3528
+ const explicitPath = metadataProjectPath(metadata);
3529
+ const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
3530
+ const candidatePaths = metadataProjectPaths(metadata);
3531
+ if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
3532
+ candidatePaths.push(sessionProjectPath);
3533
+ }
3534
+ const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
3535
+ const pathHashes = candidatePaths.map((candidate) => {
3536
+ try {
3537
+ return { path: candidate, hash: hashProjectPath(candidate) };
3538
+ } catch {
3539
+ return { path: candidate, hash: void 0 };
3540
+ }
3541
+ });
3542
+ const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
3543
+ const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
3544
+ let action = "skipped";
3545
+ let reason;
3546
+ let observedProjectHash;
3547
+ if (foreignPath) {
3548
+ action = "quarantined";
3549
+ reason = "project-path-mismatch";
3550
+ observedProjectHash = foreignPath.hash;
3551
+ } else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
3552
+ action = "quarantined";
3553
+ reason = "content-project-mismatch";
3554
+ } else if (currentHash === projectHash) {
3555
+ result.alreadyScoped++;
3556
+ continue;
3557
+ } else if (currentHash && currentHash !== projectHash) {
3558
+ action = "quarantined";
3559
+ reason = "scope-hash-mismatch";
3560
+ observedProjectHash = currentHash;
3561
+ } else if (matchingPath) {
3562
+ action = "repaired";
3563
+ reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
3564
+ } else if (candidatePaths.length > 0) {
3565
+ action = "quarantined";
3566
+ reason = "project-path-mismatch";
3567
+ } else if (importedOrLegacy) {
3568
+ action = "quarantined";
3569
+ reason = "missing-project-scope";
3570
+ }
3571
+ if (action === "skipped" || !reason) {
3572
+ result.skipped++;
3573
+ continue;
3574
+ }
3575
+ if (action === "repaired") {
3576
+ const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
3577
+ const project = isRecord(scope.project) ? { ...scope.project } : {};
3578
+ project.hash = projectHash;
3579
+ scope.project = project;
3580
+ metadata.scope = scope;
3581
+ metadata.repair = {
3582
+ ...isRecord(metadata.repair) ? metadata.repair : {},
3583
+ legacyProjectScope: {
3584
+ action,
3585
+ reason,
3586
+ repairedAt: nowIso
3587
+ }
3588
+ };
3589
+ addMetadataTag(metadata, `proj:${projectHash}`);
3590
+ result.repaired++;
3591
+ } else {
3592
+ metadata.quarantine = {
3593
+ ...isRecord(metadata.quarantine) ? metadata.quarantine : {},
3594
+ status: "active",
3595
+ category: "project-scope",
3596
+ reason,
3597
+ detectedAt: nowIso,
3598
+ expectedProjectHash: projectHash,
3599
+ ...observedProjectHash ? { observedProjectHash } : {}
3600
+ };
3601
+ metadata.repair = {
3602
+ ...isRecord(metadata.repair) ? metadata.repair : {},
3603
+ legacyProjectScope: {
3604
+ action,
3605
+ reason,
3606
+ repairedAt: nowIso
3607
+ }
3608
+ };
3609
+ addMetadataTag(metadata, "quarantine:project-scope");
3610
+ result.quarantined++;
3611
+ }
3612
+ sample({ eventId: row.id, action, reason });
3613
+ if (!dryRun) {
3614
+ sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
3615
+ }
3616
+ }
3617
+ return result;
3618
+ }
3326
3619
  /**
3327
3620
  * Get embedding/vector outbox health statistics
3328
3621
  */
@@ -3370,7 +3663,11 @@ var SQLiteEventStore = class {
3370
3663
  await this.initialize();
3371
3664
  const rows = sqliteAll(
3372
3665
  this.db,
3373
- `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
3666
+ `SELECT ml.level, COUNT(*) as count
3667
+ FROM memory_levels ml
3668
+ INNER JOIN events e ON e.id = ml.event_id
3669
+ WHERE ${notActiveQuarantinedSql("e.metadata")}
3670
+ GROUP BY ml.level`
3374
3671
  );
3375
3672
  return rows;
3376
3673
  }
@@ -3386,6 +3683,7 @@ var SQLiteEventStore = class {
3386
3683
  `SELECT e.* FROM events e
3387
3684
  INNER JOIN memory_levels ml ON e.id = ml.event_id
3388
3685
  WHERE ml.level = ?
3686
+ AND ${notActiveQuarantinedSql("e.metadata")}
3389
3687
  ORDER BY e.timestamp DESC
3390
3688
  LIMIT ? OFFSET ?`,
3391
3689
  [level, limit, offset]
@@ -3478,12 +3776,13 @@ var SQLiteEventStore = class {
3478
3776
  /**
3479
3777
  * Get most accessed memories (falls back to recent events if none accessed)
3480
3778
  */
3481
- async getMostAccessed(limit = 10) {
3779
+ async getMostAccessed(limit = 10, options) {
3482
3780
  await this.initialize();
3483
3781
  let rows = sqliteAll(
3484
3782
  this.db,
3485
3783
  `SELECT * FROM events
3486
3784
  WHERE access_count > 0
3785
+ AND ${maybeQuarantinePredicate(options)}
3487
3786
  ORDER BY access_count DESC, last_accessed_at DESC
3488
3787
  LIMIT ?`,
3489
3788
  [limit]
@@ -3492,6 +3791,7 @@ var SQLiteEventStore = class {
3492
3791
  rows = sqliteAll(
3493
3792
  this.db,
3494
3793
  `SELECT * FROM events
3794
+ WHERE ${maybeQuarantinePredicate(options)}
3495
3795
  ORDER BY timestamp DESC
3496
3796
  LIMIT ?`,
3497
3797
  [limit]
@@ -3622,6 +3922,7 @@ var SQLiteEventStore = class {
3622
3922
  FROM memory_helpfulness mh
3623
3923
  JOIN events e ON e.id = mh.event_id
3624
3924
  WHERE mh.measured_at IS NOT NULL
3925
+ AND ${notActiveQuarantinedSql("e.metadata")}
3625
3926
  GROUP BY mh.event_id
3626
3927
  ORDER BY avg_score DESC
3627
3928
  LIMIT ?`,
@@ -3686,6 +3987,7 @@ var SQLiteEventStore = class {
3686
3987
  FROM events_fts fts
3687
3988
  JOIN events e ON e.id = fts.event_id
3688
3989
  WHERE events_fts MATCH ?
3990
+ AND ${notActiveQuarantinedSql("e.metadata")}
3689
3991
  ORDER BY fts.rank
3690
3992
  LIMIT ?`,
3691
3993
  [searchTerms, limit]
@@ -3700,6 +4002,7 @@ var SQLiteEventStore = class {
3700
4002
  this.db,
3701
4003
  `SELECT *, 0 as rank FROM events
3702
4004
  WHERE content LIKE ?
4005
+ AND ${notActiveQuarantinedSql()}
3703
4006
  ORDER BY timestamp DESC
3704
4007
  LIMIT ?`,
3705
4008
  [likePattern, limit]
@@ -3906,6 +4209,7 @@ var SQLiteEventStore = class {
3906
4209
  `SELECT turn_id, MIN(timestamp) as min_ts
3907
4210
  FROM events
3908
4211
  WHERE session_id = ? AND turn_id IS NOT NULL
4212
+ AND ${maybeQuarantinePredicate(options)}
3909
4213
  GROUP BY turn_id
3910
4214
  ORDER BY min_ts DESC
3911
4215
  LIMIT ? OFFSET ?`,
@@ -3913,7 +4217,7 @@ var SQLiteEventStore = class {
3913
4217
  );
3914
4218
  const turns = [];
3915
4219
  for (const turnRow of turnRows) {
3916
- const events = await this.getEventsByTurn(turnRow.turn_id);
4220
+ const events = await this.getEventsByTurn(turnRow.turn_id, options);
3917
4221
  const promptEvent = events.find((e) => e.eventType === "user_prompt");
3918
4222
  const toolEvents = events.filter((e) => e.eventType === "tool_observation");
3919
4223
  const hasResponse = events.some((e) => e.eventType === "agent_response");
@@ -3932,11 +4236,11 @@ var SQLiteEventStore = class {
3932
4236
  /**
3933
4237
  * Get all events for a specific turn_id
3934
4238
  */
3935
- async getEventsByTurn(turnId) {
4239
+ async getEventsByTurn(turnId, options) {
3936
4240
  await this.initialize();
3937
4241
  const rows = sqliteAll(
3938
4242
  this.db,
3939
- `SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
4243
+ `SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
3940
4244
  [turnId]
3941
4245
  );
3942
4246
  return rows.map(this.rowToEvent);
@@ -3944,13 +4248,14 @@ var SQLiteEventStore = class {
3944
4248
  /**
3945
4249
  * Count total turns for a session
3946
4250
  */
3947
- async countSessionTurns(sessionId) {
4251
+ async countSessionTurns(sessionId, options) {
3948
4252
  await this.initialize();
3949
4253
  const row = sqliteGet(
3950
4254
  this.db,
3951
4255
  `SELECT COUNT(DISTINCT turn_id) as count
3952
4256
  FROM events
3953
- WHERE session_id = ? AND turn_id IS NOT NULL`,
4257
+ WHERE session_id = ? AND turn_id IS NOT NULL
4258
+ AND ${maybeQuarantinePredicate(options)}`,
3954
4259
  [sessionId]
3955
4260
  );
3956
4261
  return row?.count || 0;
@@ -4042,7 +4347,7 @@ var SQLiteEventStore = class {
4042
4347
  content: row.content,
4043
4348
  canonicalKey: row.canonical_key,
4044
4349
  dedupeKey: row.dedupe_key,
4045
- metadata: row.metadata ? JSON.parse(row.metadata) : void 0
4350
+ metadata: safeParseMetadataValue(row.metadata)
4046
4351
  };
4047
4352
  if (row.access_count !== void 0) {
4048
4353
  event.access_count = row.access_count;
@@ -4637,6 +4942,10 @@ var MemoryQueryService = class {
4637
4942
  await this.initialize();
4638
4943
  return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
4639
4944
  }
4945
+ async repairLegacyProjectScope(options) {
4946
+ await this.initialize();
4947
+ return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
4948
+ }
4640
4949
  async getStats() {
4641
4950
  await this.initialize();
4642
4951
  const deps = this.getStatsDeps();
@@ -6259,18 +6568,18 @@ function assertDefaultRetrieverStore(eventStore) {
6259
6568
  function createMemoryEngineServices(options) {
6260
6569
  const factories = options.factories ?? {};
6261
6570
  const storagePath = options.storagePath;
6262
- if (!options.readOnly && !fs5.existsSync(storagePath)) {
6263
- fs5.mkdirSync(storagePath, { recursive: true });
6571
+ if (!options.readOnly && !fs6.existsSync(storagePath)) {
6572
+ fs6.mkdirSync(storagePath, { recursive: true });
6264
6573
  }
6265
6574
  const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
6266
- path4.join(storagePath, "events.sqlite"),
6575
+ path5.join(storagePath, "events.sqlite"),
6267
6576
  {
6268
6577
  readonly: options.readOnly,
6269
6578
  markdownMirrorRoot: storagePath
6270
6579
  }
6271
6580
  );
6272
6581
  const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
6273
- path4.join(storagePath, "vectors")
6582
+ path5.join(storagePath, "vectors")
6274
6583
  );
6275
6584
  const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
6276
6585
  const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
@@ -6681,8 +6990,8 @@ function createMemoryRuntimeService(deps) {
6681
6990
  }
6682
6991
 
6683
6992
  // src/extensions/shared-memory/shared-memory-services.ts
6684
- import * as fs6 from "fs";
6685
- import * as path5 from "path";
6993
+ import * as fs7 from "fs";
6994
+ import * as path6 from "path";
6686
6995
 
6687
6996
  // src/core/shared-event-store.ts
6688
6997
  var SharedEventStore = class {
@@ -7370,7 +7679,7 @@ var SharedMemoryServices = class {
7370
7679
  this.ensureDirectory(sharedPath, { allowCreate: true });
7371
7680
  const store = await this.openStore(sharedPath);
7372
7681
  this.sharedVectorStore = this.factories.createSharedVectorStore(
7373
- path5.join(sharedPath, "vectors")
7682
+ path6.join(sharedPath, "vectors")
7374
7683
  );
7375
7684
  await this.sharedVectorStore.initialize();
7376
7685
  this.sharedPromoter = this.factories.createSharedPromoter(
@@ -7443,7 +7752,7 @@ var SharedMemoryServices = class {
7443
7752
  async createOpenStorePromise(sharedPath) {
7444
7753
  if (!this.sharedEventStore) {
7445
7754
  const sharedEventStore = this.factories.createSharedEventStore(
7446
- path5.join(sharedPath, "shared.duckdb")
7755
+ path6.join(sharedPath, "shared.duckdb")
7447
7756
  );
7448
7757
  await sharedEventStore.initialize();
7449
7758
  this.sharedEventStore = sharedEventStore;
@@ -7463,9 +7772,9 @@ var SharedMemoryServices = class {
7463
7772
  }
7464
7773
  get factories() {
7465
7774
  return {
7466
- existsSync: this.options.factories?.existsSync ?? fs6.existsSync,
7775
+ existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
7467
7776
  mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
7468
- fs6.mkdirSync(targetPath, { recursive: true });
7777
+ fs7.mkdirSync(targetPath, { recursive: true });
7469
7778
  }),
7470
7779
  createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
7471
7780
  createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
@@ -7580,33 +7889,11 @@ function createMemoryServiceComposition(options) {
7580
7889
  }
7581
7890
  function defaultExpandPath(targetPath) {
7582
7891
  if (targetPath.startsWith("~")) {
7583
- return path6.join(os.homedir(), targetPath.slice(1));
7892
+ return path7.join(os2.homedir(), targetPath.slice(1));
7584
7893
  }
7585
7894
  return targetPath;
7586
7895
  }
7587
7896
 
7588
- // src/core/registry/project-path.ts
7589
- import * as crypto2 from "crypto";
7590
- import * as fs7 from "fs";
7591
- import * as os2 from "os";
7592
- import * as path7 from "path";
7593
- function normalizeProjectPath(projectPath) {
7594
- const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
7595
- try {
7596
- return fs7.realpathSync(expanded);
7597
- } catch {
7598
- return path7.resolve(expanded);
7599
- }
7600
- }
7601
- function hashProjectPath(projectPath) {
7602
- const normalizedPath = normalizeProjectPath(projectPath);
7603
- return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
7604
- }
7605
- function getProjectStoragePath(projectPath) {
7606
- const hash = hashProjectPath(projectPath);
7607
- return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
7608
- }
7609
-
7610
7897
  // src/core/registry/session-registry.ts
7611
7898
  import * as fs8 from "fs";
7612
7899
  import * as os3 from "os";
@@ -7908,6 +8195,9 @@ var MemoryService = class {
7908
8195
  async recoverStuckOutboxItems(options) {
7909
8196
  return this.queryService.recoverStuckOutboxItems(options);
7910
8197
  }
8198
+ async repairLegacyProjectScope(options) {
8199
+ return this.queryService.repairLegacyProjectScope(options);
8200
+ }
7911
8201
  async getRetrievalTraceStats() {
7912
8202
  return this.retrievalAnalyticsService.getRetrievalTraceStats();
7913
8203
  }
@@ -8293,10 +8583,10 @@ var SENSITIVE_PATTERNS = [
8293
8583
  // Redact the whole URI so usernames, credentials, hosts, paths, and query
8294
8584
  // params do not leak either.
8295
8585
  /\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>/@]+@[^\s'"`<>]+/gi,
8296
- /password\s*[:=]\s*['"]?[^\s'"]+/gi,
8297
- /api[_-]?key\s*[:=]\s*['"]?[^\s'"]+/gi,
8298
- /secret\s*[:=]\s*['"]?[^\s'"]+/gi,
8299
- /token\s*[:=]\s*['"]?[^\s'"]+/gi,
8586
+ /(?:[\w.-]+[-_])?password\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
8587
+ /(?:[\w.-]+[-_])?api[-_]?key\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
8588
+ /(?:[\w.-]+[-_])?secret\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
8589
+ /(?:[\w.-]+[-_])?token\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
8300
8590
  /bearer\s+[a-zA-Z0-9\-_.]+/gi,
8301
8591
  /AWS[_-]?ACCESS[_-]?KEY[_-]?ID\s*[:=]\s*['"]?[A-Z0-9]+/gi,
8302
8592
  /AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY\s*[:=]\s*['"]?[^\s'"]+/gi,
@@ -8306,8 +8596,37 @@ var SENSITIVE_PATTERNS = [
8306
8596
  /sk-[a-zA-Z0-9]{48}/g
8307
8597
  // OpenAI API Key
8308
8598
  ];
8309
- function maskSensitiveString(value) {
8599
+ var CLI_SECRET_OPTION_PATTERNS = [
8600
+ /(--(?:[a-z0-9]+[-_])*(?:password|secret|api[-_]?key|token|bearer)(?:[-_][a-z0-9]+)*(?:\s+|=))(?:"[^"]*"|'[^']*'|[^\s'"`<>]+)/gi
8601
+ ];
8602
+ var URL_FOLLOWING_SECRET_PATTERN = /((?:https?:\/\/[^\s'"`<>]+)\s*\r?\n\s*)([A-Za-z0-9!@#$%^&*._+\-~:=/]{6,})(?=\s*(?:\r?\n|$))/gi;
8603
+ function looksLikePastedSecret(value) {
8604
+ return value.length >= 8 && /(?:\d|[^A-Za-z0-9])/.test(value);
8605
+ }
8606
+ function maskUrlFollowingSecret(value) {
8607
+ let count = 0;
8608
+ const content = value.replace(URL_FOLLOWING_SECRET_PATTERN, (_match, prefix, secret) => {
8609
+ if (!looksLikePastedSecret(secret))
8610
+ return `${prefix}${secret}`;
8611
+ count++;
8612
+ return `${prefix}[REDACTED]`;
8613
+ });
8614
+ return { content, count };
8615
+ }
8616
+ function maskCliSecretOptions(value) {
8617
+ let count = 0;
8310
8618
  let filtered = value;
8619
+ for (const pattern of CLI_SECRET_OPTION_PATTERNS) {
8620
+ pattern.lastIndex = 0;
8621
+ filtered = filtered.replace(pattern, (_match, prefix) => {
8622
+ count++;
8623
+ return `${prefix}[REDACTED]`;
8624
+ });
8625
+ }
8626
+ return { content: filtered, count };
8627
+ }
8628
+ function maskSensitiveString(value) {
8629
+ let filtered = maskCliSecretOptions(value).content;
8311
8630
  for (const pattern of SENSITIVE_PATTERNS) {
8312
8631
  pattern.lastIndex = 0;
8313
8632
  filtered = filtered.replace(pattern, "[REDACTED]");
@@ -8327,6 +8646,12 @@ function applyPrivacyFilter(content, config) {
8327
8646
  filtered = tagResult.filtered;
8328
8647
  privateTagCount = tagResult.stats.count;
8329
8648
  }
8649
+ const cliResult = maskCliSecretOptions(filtered);
8650
+ filtered = cliResult.content;
8651
+ patternMatchCount += cliResult.count;
8652
+ const urlSecretResult = maskUrlFollowingSecret(filtered);
8653
+ filtered = urlSecretResult.content;
8654
+ patternMatchCount += urlSecretResult.count;
8330
8655
  for (const pattern of SENSITIVE_PATTERNS) {
8331
8656
  pattern.lastIndex = 0;
8332
8657
  const matches = filtered.match(pattern);
@@ -8338,13 +8663,13 @@ function applyPrivacyFilter(content, config) {
8338
8663
  for (const patternStr of config.excludePatterns || []) {
8339
8664
  try {
8340
8665
  const regex = new RegExp(
8341
- `(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
8666
+ `(^|[^\\w-])(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
8342
8667
  "gi"
8343
8668
  );
8344
8669
  const matches = filtered.match(regex);
8345
8670
  if (matches) {
8346
8671
  patternMatchCount += matches.length;
8347
- filtered = filtered.replace(regex, "[REDACTED]");
8672
+ filtered = filtered.replace(regex, "$1[REDACTED]");
8348
8673
  }
8349
8674
  } catch {
8350
8675
  }