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/mcp/index.js CHANGED
@@ -10436,8 +10436,8 @@ import * as path14 from "node:path";
10436
10436
  import * as os5 from "os";
10437
10437
 
10438
10438
  // src/core/engine/memory-service-composition.ts
10439
- import * as os from "os";
10440
- import * as path6 from "path";
10439
+ import * as os2 from "os";
10440
+ import * as path7 from "path";
10441
10441
 
10442
10442
  // src/core/metadata-extractor.ts
10443
10443
  function createToolObservationEmbedding(toolName, metadata, success) {
@@ -11948,8 +11948,8 @@ function createEndlessMemoryServices(options) {
11948
11948
  }
11949
11949
 
11950
11950
  // src/core/engine/memory-engine-services.ts
11951
- import * as fs5 from "fs";
11952
- import * as path4 from "path";
11951
+ import * as fs6 from "fs";
11952
+ import * as path5 from "path";
11953
11953
 
11954
11954
  // src/extensions/vector/embedder.ts
11955
11955
  var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
@@ -12627,14 +12627,39 @@ function makeDedupeKey(content, sessionId) {
12627
12627
  return `${sessionId}:${contentHash}`;
12628
12628
  }
12629
12629
 
12630
+ // src/core/sqlite-event-store.ts
12631
+ import * as nodePath2 from "path";
12632
+
12633
+ // src/core/registry/project-path.ts
12634
+ import * as crypto2 from "crypto";
12635
+ import * as fs3 from "fs";
12636
+ import * as os from "os";
12637
+ import * as path3 from "path";
12638
+ function normalizeProjectPath(projectPath) {
12639
+ const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
12640
+ try {
12641
+ return fs3.realpathSync(expanded);
12642
+ } catch {
12643
+ return path3.resolve(expanded);
12644
+ }
12645
+ }
12646
+ function hashProjectPath(projectPath) {
12647
+ const normalizedPath = normalizeProjectPath(projectPath);
12648
+ return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
12649
+ }
12650
+ function getProjectStoragePath(projectPath) {
12651
+ const hash = hashProjectPath(projectPath);
12652
+ return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
12653
+ }
12654
+
12630
12655
  // src/core/sqlite-wrapper.ts
12631
12656
  import Database from "better-sqlite3";
12632
- import * as fs3 from "fs";
12657
+ import * as fs4 from "fs";
12633
12658
  import * as nodePath from "path";
12634
12659
  function createSQLiteDatabase(path15, options) {
12635
12660
  const dir = nodePath.dirname(path15);
12636
- if (!fs3.existsSync(dir)) {
12637
- fs3.mkdirSync(dir, { recursive: true });
12661
+ if (!fs4.existsSync(dir)) {
12662
+ fs4.mkdirSync(dir, { recursive: true });
12638
12663
  }
12639
12664
  const db = new Database(path15, {
12640
12665
  readonly: options?.readonly ?? false
@@ -12683,8 +12708,8 @@ function toSQLiteTimestamp(date) {
12683
12708
  }
12684
12709
 
12685
12710
  // src/core/markdown-mirror.ts
12686
- import * as fs4 from "fs/promises";
12687
- import * as path3 from "path";
12711
+ import * as fs5 from "fs/promises";
12712
+ import * as path4 from "path";
12688
12713
  var DEFAULT_NAMESPACE = "default";
12689
12714
  var DEFAULT_CATEGORY = "uncategorized";
12690
12715
  function sanitizeSegment2(input, fallback) {
@@ -12713,7 +12738,7 @@ function buildMirrorPath2(rootDir, event) {
12713
12738
  const yyyy = d.getFullYear();
12714
12739
  const mm = String(d.getMonth() + 1).padStart(2, "0");
12715
12740
  const dd = String(d.getDate()).padStart(2, "0");
12716
- return path3.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
12741
+ return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
12717
12742
  }
12718
12743
  function formatMirrorEntry(event) {
12719
12744
  const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
@@ -12734,8 +12759,8 @@ var MarkdownMirror2 = class {
12734
12759
  }
12735
12760
  async append(event) {
12736
12761
  const outPath = buildMirrorPath2(this.rootDir, event);
12737
- await fs4.mkdir(path3.dirname(outPath), { recursive: true });
12738
- await fs4.appendFile(outPath, formatMirrorEntry(event), "utf8");
12762
+ await fs5.mkdir(path4.dirname(outPath), { recursive: true });
12763
+ await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
12739
12764
  return outPath;
12740
12765
  }
12741
12766
  };
@@ -12756,6 +12781,135 @@ function emptyOutboxRecoveryResult() {
12756
12781
  vector: { recoveredProcessing: 0, retriedFailed: 0 }
12757
12782
  };
12758
12783
  }
12784
+ function isRecord(value) {
12785
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12786
+ }
12787
+ function getNestedRecord(root, path15) {
12788
+ let cursor = root;
12789
+ for (const key of path15) {
12790
+ if (!isRecord(cursor))
12791
+ return void 0;
12792
+ cursor = cursor[key];
12793
+ }
12794
+ return isRecord(cursor) ? cursor : void 0;
12795
+ }
12796
+ function getNestedString(root, path15) {
12797
+ let cursor = root;
12798
+ for (const key of path15) {
12799
+ if (!isRecord(cursor))
12800
+ return void 0;
12801
+ cursor = cursor[key];
12802
+ }
12803
+ return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
12804
+ }
12805
+ function metadataProjectHash(metadata) {
12806
+ return getNestedString(metadata, ["scope", "project", "hash"]);
12807
+ }
12808
+ function metadataProjectPaths(metadata) {
12809
+ const candidates = [
12810
+ getNestedString(metadata, ["projectPath"]),
12811
+ getNestedString(metadata, ["sourceProjectPath"]),
12812
+ getNestedString(metadata, ["scope", "project", "path"])
12813
+ ];
12814
+ const paths = [];
12815
+ for (const value of candidates) {
12816
+ if (value && !paths.includes(value))
12817
+ paths.push(value);
12818
+ }
12819
+ return paths;
12820
+ }
12821
+ function metadataProjectPath(metadata) {
12822
+ return metadataProjectPaths(metadata)[0];
12823
+ }
12824
+ function isActiveQuarantinedMetadata(metadata) {
12825
+ const quarantine = getNestedRecord(metadata, ["quarantine"]);
12826
+ return quarantine?.status === "active";
12827
+ }
12828
+ function activeQuarantineStatusExpression(column = "metadata") {
12829
+ return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
12830
+ }
12831
+ function notActiveQuarantinedSql(column = "metadata") {
12832
+ return `${activeQuarantineStatusExpression(column)} != 'active'`;
12833
+ }
12834
+ function maybeQuarantinePredicate(options, column = "metadata") {
12835
+ return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
12836
+ }
12837
+ function safeParseMetadataValue(value) {
12838
+ if (!value)
12839
+ return void 0;
12840
+ if (typeof value === "object")
12841
+ return isRecord(value) ? value : void 0;
12842
+ if (typeof value !== "string")
12843
+ return void 0;
12844
+ try {
12845
+ const parsed = JSON.parse(value);
12846
+ return isRecord(parsed) ? parsed : void 0;
12847
+ } catch {
12848
+ return void 0;
12849
+ }
12850
+ }
12851
+ function isImportedOrLegacyScopedMetadata(metadata) {
12852
+ if (!metadata)
12853
+ return false;
12854
+ return Boolean(
12855
+ metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
12856
+ );
12857
+ }
12858
+ function addMetadataTag(metadata, tag) {
12859
+ const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
12860
+ if (!current.includes(tag))
12861
+ metadata.tags = [...current, tag];
12862
+ }
12863
+ function buildRepairResult(projectHash, dryRun) {
12864
+ return {
12865
+ dryRun,
12866
+ projectHash,
12867
+ scanned: 0,
12868
+ repaired: 0,
12869
+ quarantined: 0,
12870
+ alreadyScoped: 0,
12871
+ skipped: 0,
12872
+ samples: []
12873
+ };
12874
+ }
12875
+ function normalizeRepoName(value) {
12876
+ return value.replace(/\.git$/i, "").trim().toLowerCase();
12877
+ }
12878
+ function projectBasename(projectPath) {
12879
+ if (!projectPath)
12880
+ return void 0;
12881
+ const trimmed = projectPath.replace(/[\\/]+$/, "");
12882
+ const basename4 = nodePath2.basename(trimmed);
12883
+ return basename4 ? normalizeRepoName(basename4) : void 0;
12884
+ }
12885
+ function isProjectScopeRepairExplanation(content) {
12886
+ const normalized = content.toLowerCase();
12887
+ const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
12888
+ const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
12889
+ return hasRepairContext && hasExplanationContext;
12890
+ }
12891
+ function hasConflictingContentProjectHint(content, projectPath) {
12892
+ const currentName = projectBasename(projectPath);
12893
+ if (!currentName)
12894
+ return false;
12895
+ if (isProjectScopeRepairExplanation(content))
12896
+ return false;
12897
+ const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
12898
+ let githubMatch;
12899
+ while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
12900
+ const repo = normalizeRepoName(githubMatch[2] || "");
12901
+ if (repo && repo !== currentName)
12902
+ return true;
12903
+ }
12904
+ const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
12905
+ let workspaceMatch;
12906
+ while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
12907
+ const repo = normalizeRepoName(workspaceMatch[1] || "");
12908
+ if (repo && repo !== currentName)
12909
+ return true;
12910
+ }
12911
+ return false;
12912
+ }
12759
12913
  var SQLiteEventStore = class {
12760
12914
  db;
12761
12915
  initialized = false;
@@ -13254,11 +13408,11 @@ var SQLiteEventStore = class {
13254
13408
  /**
13255
13409
  * Get events by session ID
13256
13410
  */
13257
- async getSessionEvents(sessionId) {
13411
+ async getSessionEvents(sessionId, options) {
13258
13412
  await this.initialize();
13259
13413
  const rows = sqliteAll(
13260
13414
  this.db,
13261
- `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
13415
+ `SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
13262
13416
  [sessionId]
13263
13417
  );
13264
13418
  return rows.map(this.rowToEvent);
@@ -13266,11 +13420,11 @@ var SQLiteEventStore = class {
13266
13420
  /**
13267
13421
  * Get recent events
13268
13422
  */
13269
- async getRecentEvents(limit = 100) {
13423
+ async getRecentEvents(limit = 100, options) {
13270
13424
  await this.initialize();
13271
13425
  const rows = sqliteAll(
13272
13426
  this.db,
13273
- `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
13427
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
13274
13428
  [limit]
13275
13429
  );
13276
13430
  return rows.map(this.rowToEvent);
@@ -13278,11 +13432,11 @@ var SQLiteEventStore = class {
13278
13432
  /**
13279
13433
  * Get event by ID
13280
13434
  */
13281
- async getEvent(id) {
13435
+ async getEvent(id, options) {
13282
13436
  await this.initialize();
13283
13437
  const row = sqliteGet(
13284
13438
  this.db,
13285
- `SELECT * FROM events WHERE id = ?`,
13439
+ `SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
13286
13440
  [id]
13287
13441
  );
13288
13442
  if (!row)
@@ -13292,11 +13446,11 @@ var SQLiteEventStore = class {
13292
13446
  /**
13293
13447
  * Get events since a timestamp (for sync)
13294
13448
  */
13295
- async getEventsSince(timestamp, limit = 1e3) {
13449
+ async getEventsSince(timestamp, limit = 1e3, options) {
13296
13450
  await this.initialize();
13297
13451
  const rows = sqliteAll(
13298
13452
  this.db,
13299
- `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
13453
+ `SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
13300
13454
  [timestamp, limit]
13301
13455
  );
13302
13456
  return rows.map(this.rowToEvent);
@@ -13305,11 +13459,11 @@ var SQLiteEventStore = class {
13305
13459
  * Get events since a SQLite rowid (for robust incremental replication).
13306
13460
  * Rowid is monotonic for append-only tables, independent of client timestamps.
13307
13461
  */
13308
- async getEventsSinceRowid(lastRowid, limit = 1e3) {
13462
+ async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
13309
13463
  await this.initialize();
13310
13464
  const rows = sqliteAll(
13311
13465
  this.db,
13312
- `SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
13466
+ `SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
13313
13467
  [lastRowid, limit]
13314
13468
  );
13315
13469
  return rows.map((row) => ({
@@ -13544,19 +13698,19 @@ var SQLiteEventStore = class {
13544
13698
  /**
13545
13699
  * Count total events
13546
13700
  */
13547
- async countEvents() {
13701
+ async countEvents(options) {
13548
13702
  await this.initialize();
13549
- const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
13703
+ const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
13550
13704
  return row?.count || 0;
13551
13705
  }
13552
13706
  /**
13553
13707
  * Get events page in timestamp ascending order (stable migration/reindex scans)
13554
13708
  */
13555
- async getEventsPage(limit = 1e3, offset = 0) {
13709
+ async getEventsPage(limit = 1e3, offset = 0, options) {
13556
13710
  await this.initialize();
13557
13711
  const rows = sqliteAll(
13558
13712
  this.db,
13559
- `SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
13713
+ `SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
13560
13714
  [limit, offset]
13561
13715
  );
13562
13716
  return rows.map(this.rowToEvent);
@@ -13630,6 +13784,145 @@ var SQLiteEventStore = class {
13630
13784
  result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
13631
13785
  return result;
13632
13786
  }
13787
+ /**
13788
+ * Repair legacy imported events that predate canonical project scope metadata.
13789
+ *
13790
+ * Same-project legacy rows are tagged with scope.project.hash. Rows that look
13791
+ * imported but cannot be proven to belong to this project are quarantined so
13792
+ * dashboard default reads/search do not surface cross-project contamination.
13793
+ */
13794
+ async repairLegacyProjectScope(options = {}) {
13795
+ await this.initialize();
13796
+ const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
13797
+ if (!projectHash) {
13798
+ throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
13799
+ }
13800
+ if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
13801
+ throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
13802
+ }
13803
+ const dryRun = options.dryRun === true;
13804
+ const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
13805
+ const result = buildRepairResult(projectHash, dryRun);
13806
+ const rows = sqliteAll(
13807
+ this.db,
13808
+ `SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
13809
+ FROM events e
13810
+ LEFT JOIN sessions s ON s.id = e.session_id
13811
+ ORDER BY e.timestamp ASC`,
13812
+ []
13813
+ );
13814
+ const sample = (entry) => {
13815
+ if (result.samples.length < 20)
13816
+ result.samples.push(entry);
13817
+ };
13818
+ for (const row of rows) {
13819
+ result.scanned++;
13820
+ let metadata = {};
13821
+ let metadataParseInvalid = false;
13822
+ if (row.metadata) {
13823
+ const parsed = safeParseMetadataValue(row.metadata);
13824
+ if (parsed) {
13825
+ metadata = parsed;
13826
+ } else {
13827
+ metadataParseInvalid = true;
13828
+ }
13829
+ }
13830
+ if (isActiveQuarantinedMetadata(metadata)) {
13831
+ result.skipped++;
13832
+ continue;
13833
+ }
13834
+ const currentHash = metadataProjectHash(metadata);
13835
+ const explicitPath = metadataProjectPath(metadata);
13836
+ const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
13837
+ const candidatePaths = metadataProjectPaths(metadata);
13838
+ if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
13839
+ candidatePaths.push(sessionProjectPath);
13840
+ }
13841
+ const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
13842
+ const pathHashes = candidatePaths.map((candidate) => {
13843
+ try {
13844
+ return { path: candidate, hash: hashProjectPath(candidate) };
13845
+ } catch {
13846
+ return { path: candidate, hash: void 0 };
13847
+ }
13848
+ });
13849
+ const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
13850
+ const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
13851
+ let action = "skipped";
13852
+ let reason;
13853
+ let observedProjectHash;
13854
+ if (foreignPath) {
13855
+ action = "quarantined";
13856
+ reason = "project-path-mismatch";
13857
+ observedProjectHash = foreignPath.hash;
13858
+ } else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
13859
+ action = "quarantined";
13860
+ reason = "content-project-mismatch";
13861
+ } else if (currentHash === projectHash) {
13862
+ result.alreadyScoped++;
13863
+ continue;
13864
+ } else if (currentHash && currentHash !== projectHash) {
13865
+ action = "quarantined";
13866
+ reason = "scope-hash-mismatch";
13867
+ observedProjectHash = currentHash;
13868
+ } else if (matchingPath) {
13869
+ action = "repaired";
13870
+ reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
13871
+ } else if (candidatePaths.length > 0) {
13872
+ action = "quarantined";
13873
+ reason = "project-path-mismatch";
13874
+ } else if (importedOrLegacy) {
13875
+ action = "quarantined";
13876
+ reason = "missing-project-scope";
13877
+ }
13878
+ if (action === "skipped" || !reason) {
13879
+ result.skipped++;
13880
+ continue;
13881
+ }
13882
+ if (action === "repaired") {
13883
+ const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
13884
+ const project = isRecord(scope.project) ? { ...scope.project } : {};
13885
+ project.hash = projectHash;
13886
+ scope.project = project;
13887
+ metadata.scope = scope;
13888
+ metadata.repair = {
13889
+ ...isRecord(metadata.repair) ? metadata.repair : {},
13890
+ legacyProjectScope: {
13891
+ action,
13892
+ reason,
13893
+ repairedAt: nowIso
13894
+ }
13895
+ };
13896
+ addMetadataTag(metadata, `proj:${projectHash}`);
13897
+ result.repaired++;
13898
+ } else {
13899
+ metadata.quarantine = {
13900
+ ...isRecord(metadata.quarantine) ? metadata.quarantine : {},
13901
+ status: "active",
13902
+ category: "project-scope",
13903
+ reason,
13904
+ detectedAt: nowIso,
13905
+ expectedProjectHash: projectHash,
13906
+ ...observedProjectHash ? { observedProjectHash } : {}
13907
+ };
13908
+ metadata.repair = {
13909
+ ...isRecord(metadata.repair) ? metadata.repair : {},
13910
+ legacyProjectScope: {
13911
+ action,
13912
+ reason,
13913
+ repairedAt: nowIso
13914
+ }
13915
+ };
13916
+ addMetadataTag(metadata, "quarantine:project-scope");
13917
+ result.quarantined++;
13918
+ }
13919
+ sample({ eventId: row.id, action, reason });
13920
+ if (!dryRun) {
13921
+ sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
13922
+ }
13923
+ }
13924
+ return result;
13925
+ }
13633
13926
  /**
13634
13927
  * Get embedding/vector outbox health statistics
13635
13928
  */
@@ -13677,7 +13970,11 @@ var SQLiteEventStore = class {
13677
13970
  await this.initialize();
13678
13971
  const rows = sqliteAll(
13679
13972
  this.db,
13680
- `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
13973
+ `SELECT ml.level, COUNT(*) as count
13974
+ FROM memory_levels ml
13975
+ INNER JOIN events e ON e.id = ml.event_id
13976
+ WHERE ${notActiveQuarantinedSql("e.metadata")}
13977
+ GROUP BY ml.level`
13681
13978
  );
13682
13979
  return rows;
13683
13980
  }
@@ -13693,6 +13990,7 @@ var SQLiteEventStore = class {
13693
13990
  `SELECT e.* FROM events e
13694
13991
  INNER JOIN memory_levels ml ON e.id = ml.event_id
13695
13992
  WHERE ml.level = ?
13993
+ AND ${notActiveQuarantinedSql("e.metadata")}
13696
13994
  ORDER BY e.timestamp DESC
13697
13995
  LIMIT ? OFFSET ?`,
13698
13996
  [level, limit, offset]
@@ -13785,12 +14083,13 @@ var SQLiteEventStore = class {
13785
14083
  /**
13786
14084
  * Get most accessed memories (falls back to recent events if none accessed)
13787
14085
  */
13788
- async getMostAccessed(limit = 10) {
14086
+ async getMostAccessed(limit = 10, options) {
13789
14087
  await this.initialize();
13790
14088
  let rows = sqliteAll(
13791
14089
  this.db,
13792
14090
  `SELECT * FROM events
13793
14091
  WHERE access_count > 0
14092
+ AND ${maybeQuarantinePredicate(options)}
13794
14093
  ORDER BY access_count DESC, last_accessed_at DESC
13795
14094
  LIMIT ?`,
13796
14095
  [limit]
@@ -13799,6 +14098,7 @@ var SQLiteEventStore = class {
13799
14098
  rows = sqliteAll(
13800
14099
  this.db,
13801
14100
  `SELECT * FROM events
14101
+ WHERE ${maybeQuarantinePredicate(options)}
13802
14102
  ORDER BY timestamp DESC
13803
14103
  LIMIT ?`,
13804
14104
  [limit]
@@ -13929,6 +14229,7 @@ var SQLiteEventStore = class {
13929
14229
  FROM memory_helpfulness mh
13930
14230
  JOIN events e ON e.id = mh.event_id
13931
14231
  WHERE mh.measured_at IS NOT NULL
14232
+ AND ${notActiveQuarantinedSql("e.metadata")}
13932
14233
  GROUP BY mh.event_id
13933
14234
  ORDER BY avg_score DESC
13934
14235
  LIMIT ?`,
@@ -13993,6 +14294,7 @@ var SQLiteEventStore = class {
13993
14294
  FROM events_fts fts
13994
14295
  JOIN events e ON e.id = fts.event_id
13995
14296
  WHERE events_fts MATCH ?
14297
+ AND ${notActiveQuarantinedSql("e.metadata")}
13996
14298
  ORDER BY fts.rank
13997
14299
  LIMIT ?`,
13998
14300
  [searchTerms, limit]
@@ -14007,6 +14309,7 @@ var SQLiteEventStore = class {
14007
14309
  this.db,
14008
14310
  `SELECT *, 0 as rank FROM events
14009
14311
  WHERE content LIKE ?
14312
+ AND ${notActiveQuarantinedSql()}
14010
14313
  ORDER BY timestamp DESC
14011
14314
  LIMIT ?`,
14012
14315
  [likePattern, limit]
@@ -14213,6 +14516,7 @@ var SQLiteEventStore = class {
14213
14516
  `SELECT turn_id, MIN(timestamp) as min_ts
14214
14517
  FROM events
14215
14518
  WHERE session_id = ? AND turn_id IS NOT NULL
14519
+ AND ${maybeQuarantinePredicate(options)}
14216
14520
  GROUP BY turn_id
14217
14521
  ORDER BY min_ts DESC
14218
14522
  LIMIT ? OFFSET ?`,
@@ -14220,7 +14524,7 @@ var SQLiteEventStore = class {
14220
14524
  );
14221
14525
  const turns = [];
14222
14526
  for (const turnRow of turnRows) {
14223
- const events = await this.getEventsByTurn(turnRow.turn_id);
14527
+ const events = await this.getEventsByTurn(turnRow.turn_id, options);
14224
14528
  const promptEvent = events.find((e) => e.eventType === "user_prompt");
14225
14529
  const toolEvents = events.filter((e) => e.eventType === "tool_observation");
14226
14530
  const hasResponse = events.some((e) => e.eventType === "agent_response");
@@ -14239,11 +14543,11 @@ var SQLiteEventStore = class {
14239
14543
  /**
14240
14544
  * Get all events for a specific turn_id
14241
14545
  */
14242
- async getEventsByTurn(turnId) {
14546
+ async getEventsByTurn(turnId, options) {
14243
14547
  await this.initialize();
14244
14548
  const rows = sqliteAll(
14245
14549
  this.db,
14246
- `SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
14550
+ `SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
14247
14551
  [turnId]
14248
14552
  );
14249
14553
  return rows.map(this.rowToEvent);
@@ -14251,13 +14555,14 @@ var SQLiteEventStore = class {
14251
14555
  /**
14252
14556
  * Count total turns for a session
14253
14557
  */
14254
- async countSessionTurns(sessionId) {
14558
+ async countSessionTurns(sessionId, options) {
14255
14559
  await this.initialize();
14256
14560
  const row = sqliteGet(
14257
14561
  this.db,
14258
14562
  `SELECT COUNT(DISTINCT turn_id) as count
14259
14563
  FROM events
14260
- WHERE session_id = ? AND turn_id IS NOT NULL`,
14564
+ WHERE session_id = ? AND turn_id IS NOT NULL
14565
+ AND ${maybeQuarantinePredicate(options)}`,
14261
14566
  [sessionId]
14262
14567
  );
14263
14568
  return row?.count || 0;
@@ -14349,7 +14654,7 @@ var SQLiteEventStore = class {
14349
14654
  content: row.content,
14350
14655
  canonicalKey: row.canonical_key,
14351
14656
  dedupeKey: row.dedupe_key,
14352
- metadata: row.metadata ? JSON.parse(row.metadata) : void 0
14657
+ metadata: safeParseMetadataValue(row.metadata)
14353
14658
  };
14354
14659
  if (row.access_count !== void 0) {
14355
14660
  event.access_count = row.access_count;
@@ -14944,6 +15249,10 @@ var MemoryQueryService = class {
14944
15249
  await this.initialize();
14945
15250
  return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
14946
15251
  }
15252
+ async repairLegacyProjectScope(options) {
15253
+ await this.initialize();
15254
+ return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
15255
+ }
14947
15256
  async getStats() {
14948
15257
  await this.initialize();
14949
15258
  const deps = this.getStatsDeps();
@@ -16595,18 +16904,18 @@ function assertDefaultRetrieverStore(eventStore) {
16595
16904
  function createMemoryEngineServices(options) {
16596
16905
  const factories = options.factories ?? {};
16597
16906
  const storagePath = options.storagePath;
16598
- if (!options.readOnly && !fs5.existsSync(storagePath)) {
16599
- fs5.mkdirSync(storagePath, { recursive: true });
16907
+ if (!options.readOnly && !fs6.existsSync(storagePath)) {
16908
+ fs6.mkdirSync(storagePath, { recursive: true });
16600
16909
  }
16601
16910
  const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
16602
- path4.join(storagePath, "events.sqlite"),
16911
+ path5.join(storagePath, "events.sqlite"),
16603
16912
  {
16604
16913
  readonly: options.readOnly,
16605
16914
  markdownMirrorRoot: storagePath
16606
16915
  }
16607
16916
  );
16608
16917
  const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
16609
- path4.join(storagePath, "vectors")
16918
+ path5.join(storagePath, "vectors")
16610
16919
  );
16611
16920
  const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
16612
16921
  const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
@@ -17017,8 +17326,8 @@ function createMemoryRuntimeService(deps) {
17017
17326
  }
17018
17327
 
17019
17328
  // src/extensions/shared-memory/shared-memory-services.ts
17020
- import * as fs6 from "fs";
17021
- import * as path5 from "path";
17329
+ import * as fs7 from "fs";
17330
+ import * as path6 from "path";
17022
17331
 
17023
17332
  // src/core/shared-event-store.ts
17024
17333
  var SharedEventStore = class {
@@ -17706,7 +18015,7 @@ var SharedMemoryServices = class {
17706
18015
  this.ensureDirectory(sharedPath, { allowCreate: true });
17707
18016
  const store = await this.openStore(sharedPath);
17708
18017
  this.sharedVectorStore = this.factories.createSharedVectorStore(
17709
- path5.join(sharedPath, "vectors")
18018
+ path6.join(sharedPath, "vectors")
17710
18019
  );
17711
18020
  await this.sharedVectorStore.initialize();
17712
18021
  this.sharedPromoter = this.factories.createSharedPromoter(
@@ -17779,7 +18088,7 @@ var SharedMemoryServices = class {
17779
18088
  async createOpenStorePromise(sharedPath) {
17780
18089
  if (!this.sharedEventStore) {
17781
18090
  const sharedEventStore = this.factories.createSharedEventStore(
17782
- path5.join(sharedPath, "shared.duckdb")
18091
+ path6.join(sharedPath, "shared.duckdb")
17783
18092
  );
17784
18093
  await sharedEventStore.initialize();
17785
18094
  this.sharedEventStore = sharedEventStore;
@@ -17799,9 +18108,9 @@ var SharedMemoryServices = class {
17799
18108
  }
17800
18109
  get factories() {
17801
18110
  return {
17802
- existsSync: this.options.factories?.existsSync ?? fs6.existsSync,
18111
+ existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
17803
18112
  mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
17804
- fs6.mkdirSync(targetPath, { recursive: true });
18113
+ fs7.mkdirSync(targetPath, { recursive: true });
17805
18114
  }),
17806
18115
  createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
17807
18116
  createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
@@ -17916,33 +18225,11 @@ function createMemoryServiceComposition(options) {
17916
18225
  }
17917
18226
  function defaultExpandPath(targetPath) {
17918
18227
  if (targetPath.startsWith("~")) {
17919
- return path6.join(os.homedir(), targetPath.slice(1));
18228
+ return path7.join(os2.homedir(), targetPath.slice(1));
17920
18229
  }
17921
18230
  return targetPath;
17922
18231
  }
17923
18232
 
17924
- // src/core/registry/project-path.ts
17925
- import * as crypto2 from "crypto";
17926
- import * as fs7 from "fs";
17927
- import * as os2 from "os";
17928
- import * as path7 from "path";
17929
- function normalizeProjectPath(projectPath) {
17930
- const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
17931
- try {
17932
- return fs7.realpathSync(expanded);
17933
- } catch {
17934
- return path7.resolve(expanded);
17935
- }
17936
- }
17937
- function hashProjectPath(projectPath) {
17938
- const normalizedPath = normalizeProjectPath(projectPath);
17939
- return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
17940
- }
17941
- function getProjectStoragePath(projectPath) {
17942
- const hash = hashProjectPath(projectPath);
17943
- return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
17944
- }
17945
-
17946
18233
  // src/core/registry/session-registry.ts
17947
18234
  import * as fs8 from "fs";
17948
18235
  import * as os3 from "os";
@@ -18269,6 +18556,9 @@ var MemoryService = class {
18269
18556
  async recoverStuckOutboxItems(options) {
18270
18557
  return this.queryService.recoverStuckOutboxItems(options);
18271
18558
  }
18559
+ async repairLegacyProjectScope(options) {
18560
+ return this.queryService.repairLegacyProjectScope(options);
18561
+ }
18272
18562
  async getRetrievalTraceStats() {
18273
18563
  return this.retrievalAnalyticsService.getRetrievalTraceStats();
18274
18564
  }
@@ -18998,7 +19288,7 @@ import * as os7 from "os";
18998
19288
  import * as readline2 from "readline";
18999
19289
  import { createHash as createHash3, randomUUID as randomUUID9 } from "crypto";
19000
19290
  var CODEX_VALIDATION_DEFAULT_MAX_CONTENT_CHARS = 1e4;
19001
- function isRecord(value) {
19291
+ function isRecord2(value) {
19002
19292
  return typeof value === "object" && value !== null;
19003
19293
  }
19004
19294
  function normalizeMaybeRealpath(p) {
@@ -19015,7 +19305,7 @@ function extractCodexContentText(content, maxContentChars = CODEX_VALIDATION_DEF
19015
19305
  texts.push(content);
19016
19306
  } else if (Array.isArray(content)) {
19017
19307
  for (const block of content) {
19018
- if (!isRecord(block))
19308
+ if (!isRecord2(block))
19019
19309
  continue;
19020
19310
  const b = block;
19021
19311
  const t = typeof b.type === "string" ? b.type : "";
@@ -19110,7 +19400,7 @@ var CodexSessionHistoryImporter = class {
19110
19400
  const obj = JSON.parse(line);
19111
19401
  if (obj.type !== "session_meta")
19112
19402
  continue;
19113
- if (!isRecord(obj.payload))
19403
+ if (!isRecord2(obj.payload))
19114
19404
  break;
19115
19405
  const payload = obj.payload;
19116
19406
  const sessionId = typeof payload.id === "string" ? payload.id : null;
@@ -19326,7 +19616,7 @@ var CodexSessionHistoryImporter = class {
19326
19616
  try {
19327
19617
  const entry = JSON.parse(line);
19328
19618
  result.totalMessages++;
19329
- if (entry.type === "response_item" && isRecord(entry.payload)) {
19619
+ if (entry.type === "response_item" && isRecord2(entry.payload)) {
19330
19620
  const payload = entry.payload;
19331
19621
  if (payload.type !== "message")
19332
19622
  continue;
@@ -19518,10 +19808,10 @@ var SENSITIVE_PATTERNS = [
19518
19808
  // Redact the whole URI so usernames, credentials, hosts, paths, and query
19519
19809
  // params do not leak either.
19520
19810
  /\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>/@]+@[^\s'"`<>]+/gi,
19521
- /password\s*[:=]\s*['"]?[^\s'"]+/gi,
19522
- /api[_-]?key\s*[:=]\s*['"]?[^\s'"]+/gi,
19523
- /secret\s*[:=]\s*['"]?[^\s'"]+/gi,
19524
- /token\s*[:=]\s*['"]?[^\s'"]+/gi,
19811
+ /(?:[\w.-]+[-_])?password\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
19812
+ /(?:[\w.-]+[-_])?api[-_]?key\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
19813
+ /(?:[\w.-]+[-_])?secret\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
19814
+ /(?:[\w.-]+[-_])?token\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
19525
19815
  /bearer\s+[a-zA-Z0-9\-_.]+/gi,
19526
19816
  /AWS[_-]?ACCESS[_-]?KEY[_-]?ID\s*[:=]\s*['"]?[A-Z0-9]+/gi,
19527
19817
  /AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY\s*[:=]\s*['"]?[^\s'"]+/gi,
@@ -19531,8 +19821,37 @@ var SENSITIVE_PATTERNS = [
19531
19821
  /sk-[a-zA-Z0-9]{48}/g
19532
19822
  // OpenAI API Key
19533
19823
  ];
19534
- function maskSensitiveString(value) {
19824
+ var CLI_SECRET_OPTION_PATTERNS = [
19825
+ /(--(?:[a-z0-9]+[-_])*(?:password|secret|api[-_]?key|token|bearer)(?:[-_][a-z0-9]+)*(?:\s+|=))(?:"[^"]*"|'[^']*'|[^\s'"`<>]+)/gi
19826
+ ];
19827
+ var URL_FOLLOWING_SECRET_PATTERN = /((?:https?:\/\/[^\s'"`<>]+)\s*\r?\n\s*)([A-Za-z0-9!@#$%^&*._+\-~:=/]{6,})(?=\s*(?:\r?\n|$))/gi;
19828
+ function looksLikePastedSecret(value) {
19829
+ return value.length >= 8 && /(?:\d|[^A-Za-z0-9])/.test(value);
19830
+ }
19831
+ function maskUrlFollowingSecret(value) {
19832
+ let count = 0;
19833
+ const content = value.replace(URL_FOLLOWING_SECRET_PATTERN, (_match, prefix, secret) => {
19834
+ if (!looksLikePastedSecret(secret))
19835
+ return `${prefix}${secret}`;
19836
+ count++;
19837
+ return `${prefix}[REDACTED]`;
19838
+ });
19839
+ return { content, count };
19840
+ }
19841
+ function maskCliSecretOptions(value) {
19842
+ let count = 0;
19535
19843
  let filtered = value;
19844
+ for (const pattern of CLI_SECRET_OPTION_PATTERNS) {
19845
+ pattern.lastIndex = 0;
19846
+ filtered = filtered.replace(pattern, (_match, prefix) => {
19847
+ count++;
19848
+ return `${prefix}[REDACTED]`;
19849
+ });
19850
+ }
19851
+ return { content: filtered, count };
19852
+ }
19853
+ function maskSensitiveString(value) {
19854
+ let filtered = maskCliSecretOptions(value).content;
19536
19855
  for (const pattern of SENSITIVE_PATTERNS) {
19537
19856
  pattern.lastIndex = 0;
19538
19857
  filtered = filtered.replace(pattern, "[REDACTED]");
@@ -19552,6 +19871,12 @@ function applyPrivacyFilter(content, config) {
19552
19871
  filtered = tagResult.filtered;
19553
19872
  privateTagCount = tagResult.stats.count;
19554
19873
  }
19874
+ const cliResult = maskCliSecretOptions(filtered);
19875
+ filtered = cliResult.content;
19876
+ patternMatchCount += cliResult.count;
19877
+ const urlSecretResult = maskUrlFollowingSecret(filtered);
19878
+ filtered = urlSecretResult.content;
19879
+ patternMatchCount += urlSecretResult.count;
19555
19880
  for (const pattern of SENSITIVE_PATTERNS) {
19556
19881
  pattern.lastIndex = 0;
19557
19882
  const matches = filtered.match(pattern);
@@ -19563,13 +19888,13 @@ function applyPrivacyFilter(content, config) {
19563
19888
  for (const patternStr of config.excludePatterns || []) {
19564
19889
  try {
19565
19890
  const regex = new RegExp(
19566
- `(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
19891
+ `(^|[^\\w-])(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
19567
19892
  "gi"
19568
19893
  );
19569
19894
  const matches = filtered.match(regex);
19570
19895
  if (matches) {
19571
19896
  patternMatchCount += matches.length;
19572
- filtered = filtered.replace(regex, "[REDACTED]");
19897
+ filtered = filtered.replace(regex, "$1[REDACTED]");
19573
19898
  }
19574
19899
  } catch {
19575
19900
  }