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
|
@@ -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
|
|
14
|
-
import * as
|
|
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
|
|
1645
|
-
import * as
|
|
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
|
|
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 (!
|
|
2330
|
-
|
|
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
|
|
2380
|
-
import * as
|
|
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
|
|
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
|
|
2431
|
-
await
|
|
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
|
|
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:
|
|
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 && !
|
|
6263
|
-
|
|
6571
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
6572
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
6264
6573
|
}
|
|
6265
6574
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
6266
|
-
|
|
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
|
-
|
|
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
|
|
6685
|
-
import * as
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
7775
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
7467
7776
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
7468
|
-
|
|
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
|
|
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[_
|
|
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
|
-
|
|
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
|
}
|