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 +506 -94
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +309 -22
- package/dist/core/index.js.map +3 -3
- package/dist/hooks/post-tool-use.js +399 -74
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +357 -67
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +357 -67
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +357 -67
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +398 -73
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +357 -67
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +318 -28
- package/dist/index.js.map +3 -3
- package/dist/mcp/index.js +403 -78
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +370 -81
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +370 -81
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +357 -67
- package/dist/services/memory-service.js.map +4 -4
- package/package.json +1 -1
package/dist/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
|
|
10440
|
-
import * as
|
|
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
|
|
11952
|
-
import * as
|
|
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
|
|
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 (!
|
|
12637
|
-
|
|
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
|
|
12687
|
-
import * as
|
|
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
|
|
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
|
|
12738
|
-
await
|
|
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
|
|
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:
|
|
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 && !
|
|
16599
|
-
|
|
16907
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
16908
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
16600
16909
|
}
|
|
16601
16910
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
16602
|
-
|
|
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
|
-
|
|
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
|
|
17021
|
-
import * as
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
18111
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
17803
18112
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
17804
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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" &&
|
|
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[_
|
|
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
|
-
|
|
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
|
}
|