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/cli/index.js +497 -84
- 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 +361 -71
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +361 -71
- 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/hooks/stop.js
CHANGED
|
@@ -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 createToolObservationEmbedding(toolName, metadata, success) {
|
|
@@ -1522,8 +1522,8 @@ function createEndlessMemoryServices(options) {
|
|
|
1522
1522
|
}
|
|
1523
1523
|
|
|
1524
1524
|
// src/core/engine/memory-engine-services.ts
|
|
1525
|
-
import * as
|
|
1526
|
-
import * as
|
|
1525
|
+
import * as fs6 from "fs";
|
|
1526
|
+
import * as path5 from "path";
|
|
1527
1527
|
|
|
1528
1528
|
// src/extensions/vector/embedder.ts
|
|
1529
1529
|
var DEFAULT_EMBEDDING_MODEL = "Xenova/multilingual-e5-small";
|
|
@@ -2201,14 +2201,39 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2201
2201
|
return `${sessionId}:${contentHash}`;
|
|
2202
2202
|
}
|
|
2203
2203
|
|
|
2204
|
+
// src/core/sqlite-event-store.ts
|
|
2205
|
+
import * as nodePath2 from "path";
|
|
2206
|
+
|
|
2207
|
+
// src/core/registry/project-path.ts
|
|
2208
|
+
import * as crypto2 from "crypto";
|
|
2209
|
+
import * as fs3 from "fs";
|
|
2210
|
+
import * as os from "os";
|
|
2211
|
+
import * as path3 from "path";
|
|
2212
|
+
function normalizeProjectPath(projectPath) {
|
|
2213
|
+
const expanded = projectPath.startsWith("~") ? path3.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
2214
|
+
try {
|
|
2215
|
+
return fs3.realpathSync(expanded);
|
|
2216
|
+
} catch {
|
|
2217
|
+
return path3.resolve(expanded);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function hashProjectPath(projectPath) {
|
|
2221
|
+
const normalizedPath = normalizeProjectPath(projectPath);
|
|
2222
|
+
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
2223
|
+
}
|
|
2224
|
+
function getProjectStoragePath(projectPath) {
|
|
2225
|
+
const hash = hashProjectPath(projectPath);
|
|
2226
|
+
return path3.join(os.homedir(), ".claude-code", "memory", "projects", hash);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2204
2229
|
// src/core/sqlite-wrapper.ts
|
|
2205
2230
|
import Database from "better-sqlite3";
|
|
2206
|
-
import * as
|
|
2231
|
+
import * as fs4 from "fs";
|
|
2207
2232
|
import * as nodePath from "path";
|
|
2208
2233
|
function createSQLiteDatabase(path12, options) {
|
|
2209
2234
|
const dir = nodePath.dirname(path12);
|
|
2210
|
-
if (!
|
|
2211
|
-
|
|
2235
|
+
if (!fs4.existsSync(dir)) {
|
|
2236
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2212
2237
|
}
|
|
2213
2238
|
const db = new Database(path12, {
|
|
2214
2239
|
readonly: options?.readonly ?? false
|
|
@@ -2257,8 +2282,8 @@ function toSQLiteTimestamp(date) {
|
|
|
2257
2282
|
}
|
|
2258
2283
|
|
|
2259
2284
|
// src/core/markdown-mirror.ts
|
|
2260
|
-
import * as
|
|
2261
|
-
import * as
|
|
2285
|
+
import * as fs5 from "fs/promises";
|
|
2286
|
+
import * as path4 from "path";
|
|
2262
2287
|
var DEFAULT_NAMESPACE = "default";
|
|
2263
2288
|
var DEFAULT_CATEGORY = "uncategorized";
|
|
2264
2289
|
function sanitizeSegment2(input, fallback) {
|
|
@@ -2287,7 +2312,7 @@ function buildMirrorPath2(rootDir, event) {
|
|
|
2287
2312
|
const yyyy = d.getFullYear();
|
|
2288
2313
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
2289
2314
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
2290
|
-
return
|
|
2315
|
+
return path4.join(rootDir, "memory", namespace, ...categories, `${yyyy}-${mm}-${dd}.md`);
|
|
2291
2316
|
}
|
|
2292
2317
|
function formatMirrorEntry(event) {
|
|
2293
2318
|
const category = Array.isArray(event.metadata?.categoryPath) ? event.metadata.categoryPath.join("/") : String(event.metadata?.category ?? event.eventType);
|
|
@@ -2308,8 +2333,8 @@ var MarkdownMirror2 = class {
|
|
|
2308
2333
|
}
|
|
2309
2334
|
async append(event) {
|
|
2310
2335
|
const outPath = buildMirrorPath2(this.rootDir, event);
|
|
2311
|
-
await
|
|
2312
|
-
await
|
|
2336
|
+
await fs5.mkdir(path4.dirname(outPath), { recursive: true });
|
|
2337
|
+
await fs5.appendFile(outPath, formatMirrorEntry(event), "utf8");
|
|
2313
2338
|
return outPath;
|
|
2314
2339
|
}
|
|
2315
2340
|
};
|
|
@@ -2330,6 +2355,135 @@ function emptyOutboxRecoveryResult() {
|
|
|
2330
2355
|
vector: { recoveredProcessing: 0, retriedFailed: 0 }
|
|
2331
2356
|
};
|
|
2332
2357
|
}
|
|
2358
|
+
function isRecord(value) {
|
|
2359
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2360
|
+
}
|
|
2361
|
+
function getNestedRecord(root, path12) {
|
|
2362
|
+
let cursor = root;
|
|
2363
|
+
for (const key of path12) {
|
|
2364
|
+
if (!isRecord(cursor))
|
|
2365
|
+
return void 0;
|
|
2366
|
+
cursor = cursor[key];
|
|
2367
|
+
}
|
|
2368
|
+
return isRecord(cursor) ? cursor : void 0;
|
|
2369
|
+
}
|
|
2370
|
+
function getNestedString(root, path12) {
|
|
2371
|
+
let cursor = root;
|
|
2372
|
+
for (const key of path12) {
|
|
2373
|
+
if (!isRecord(cursor))
|
|
2374
|
+
return void 0;
|
|
2375
|
+
cursor = cursor[key];
|
|
2376
|
+
}
|
|
2377
|
+
return typeof cursor === "string" && cursor.length > 0 ? cursor : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
function metadataProjectHash(metadata) {
|
|
2380
|
+
return getNestedString(metadata, ["scope", "project", "hash"]);
|
|
2381
|
+
}
|
|
2382
|
+
function metadataProjectPaths(metadata) {
|
|
2383
|
+
const candidates = [
|
|
2384
|
+
getNestedString(metadata, ["projectPath"]),
|
|
2385
|
+
getNestedString(metadata, ["sourceProjectPath"]),
|
|
2386
|
+
getNestedString(metadata, ["scope", "project", "path"])
|
|
2387
|
+
];
|
|
2388
|
+
const paths = [];
|
|
2389
|
+
for (const value of candidates) {
|
|
2390
|
+
if (value && !paths.includes(value))
|
|
2391
|
+
paths.push(value);
|
|
2392
|
+
}
|
|
2393
|
+
return paths;
|
|
2394
|
+
}
|
|
2395
|
+
function metadataProjectPath(metadata) {
|
|
2396
|
+
return metadataProjectPaths(metadata)[0];
|
|
2397
|
+
}
|
|
2398
|
+
function isActiveQuarantinedMetadata(metadata) {
|
|
2399
|
+
const quarantine = getNestedRecord(metadata, ["quarantine"]);
|
|
2400
|
+
return quarantine?.status === "active";
|
|
2401
|
+
}
|
|
2402
|
+
function activeQuarantineStatusExpression(column = "metadata") {
|
|
2403
|
+
return `COALESCE(json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '$.quarantine.status'), '')`;
|
|
2404
|
+
}
|
|
2405
|
+
function notActiveQuarantinedSql(column = "metadata") {
|
|
2406
|
+
return `${activeQuarantineStatusExpression(column)} != 'active'`;
|
|
2407
|
+
}
|
|
2408
|
+
function maybeQuarantinePredicate(options, column = "metadata") {
|
|
2409
|
+
return options?.includeQuarantined ? "1=1" : notActiveQuarantinedSql(column);
|
|
2410
|
+
}
|
|
2411
|
+
function safeParseMetadataValue(value) {
|
|
2412
|
+
if (!value)
|
|
2413
|
+
return void 0;
|
|
2414
|
+
if (typeof value === "object")
|
|
2415
|
+
return isRecord(value) ? value : void 0;
|
|
2416
|
+
if (typeof value !== "string")
|
|
2417
|
+
return void 0;
|
|
2418
|
+
try {
|
|
2419
|
+
const parsed = JSON.parse(value);
|
|
2420
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2421
|
+
} catch {
|
|
2422
|
+
return void 0;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
function isImportedOrLegacyScopedMetadata(metadata) {
|
|
2426
|
+
if (!metadata)
|
|
2427
|
+
return false;
|
|
2428
|
+
return Boolean(
|
|
2429
|
+
metadata.importedFrom || metadata.sourceSessionId || metadata.sourceSessionHash || metadata.hermesSource || metadata.projectPath || metadata.sourceProjectPath || metadata.source === "hermes" || metadata.source === "claude" || metadata.source === "codex"
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
function addMetadataTag(metadata, tag) {
|
|
2433
|
+
const current = Array.isArray(metadata.tags) ? metadata.tags.filter((value) => typeof value === "string") : [];
|
|
2434
|
+
if (!current.includes(tag))
|
|
2435
|
+
metadata.tags = [...current, tag];
|
|
2436
|
+
}
|
|
2437
|
+
function buildRepairResult(projectHash, dryRun) {
|
|
2438
|
+
return {
|
|
2439
|
+
dryRun,
|
|
2440
|
+
projectHash,
|
|
2441
|
+
scanned: 0,
|
|
2442
|
+
repaired: 0,
|
|
2443
|
+
quarantined: 0,
|
|
2444
|
+
alreadyScoped: 0,
|
|
2445
|
+
skipped: 0,
|
|
2446
|
+
samples: []
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
function normalizeRepoName(value) {
|
|
2450
|
+
return value.replace(/\.git$/i, "").trim().toLowerCase();
|
|
2451
|
+
}
|
|
2452
|
+
function projectBasename(projectPath) {
|
|
2453
|
+
if (!projectPath)
|
|
2454
|
+
return void 0;
|
|
2455
|
+
const trimmed = projectPath.replace(/[\\/]+$/, "");
|
|
2456
|
+
const basename2 = nodePath2.basename(trimmed);
|
|
2457
|
+
return basename2 ? normalizeRepoName(basename2) : void 0;
|
|
2458
|
+
}
|
|
2459
|
+
function isProjectScopeRepairExplanation(content) {
|
|
2460
|
+
const normalized = content.toLowerCase();
|
|
2461
|
+
const hasRepairContext = /project[- ]scope|mis[- ]scoped|quarantine|contamination|legacy|오염|격리|repair/.test(normalized);
|
|
2462
|
+
const hasExplanationContext = /example|detector|trap|not a .*project task|기억|메모리|설명|수정|검증/.test(normalized);
|
|
2463
|
+
return hasRepairContext && hasExplanationContext;
|
|
2464
|
+
}
|
|
2465
|
+
function hasConflictingContentProjectHint(content, projectPath) {
|
|
2466
|
+
const currentName = projectBasename(projectPath);
|
|
2467
|
+
if (!currentName)
|
|
2468
|
+
return false;
|
|
2469
|
+
if (isProjectScopeRepairExplanation(content))
|
|
2470
|
+
return false;
|
|
2471
|
+
const githubRepoPattern = /github\.com[:/]([^/\s`'"#)]+)\/([^/\s`'"#)]+)(?:\.git)?/gi;
|
|
2472
|
+
let githubMatch;
|
|
2473
|
+
while ((githubMatch = githubRepoPattern.exec(content)) !== null) {
|
|
2474
|
+
const repo = normalizeRepoName(githubMatch[2] || "");
|
|
2475
|
+
if (repo && repo !== currentName)
|
|
2476
|
+
return true;
|
|
2477
|
+
}
|
|
2478
|
+
const workspacePathPattern = /\/workspace\/([^/\s`'"#)]+)/gi;
|
|
2479
|
+
let workspaceMatch;
|
|
2480
|
+
while ((workspaceMatch = workspacePathPattern.exec(content)) !== null) {
|
|
2481
|
+
const repo = normalizeRepoName(workspaceMatch[1] || "");
|
|
2482
|
+
if (repo && repo !== currentName)
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2333
2487
|
var SQLiteEventStore = class {
|
|
2334
2488
|
db;
|
|
2335
2489
|
initialized = false;
|
|
@@ -2828,11 +2982,11 @@ var SQLiteEventStore = class {
|
|
|
2828
2982
|
/**
|
|
2829
2983
|
* Get events by session ID
|
|
2830
2984
|
*/
|
|
2831
|
-
async getSessionEvents(sessionId) {
|
|
2985
|
+
async getSessionEvents(sessionId, options) {
|
|
2832
2986
|
await this.initialize();
|
|
2833
2987
|
const rows = sqliteAll(
|
|
2834
2988
|
this.db,
|
|
2835
|
-
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
2989
|
+
`SELECT * FROM events WHERE session_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
2836
2990
|
[sessionId]
|
|
2837
2991
|
);
|
|
2838
2992
|
return rows.map(this.rowToEvent);
|
|
@@ -2840,11 +2994,11 @@ var SQLiteEventStore = class {
|
|
|
2840
2994
|
/**
|
|
2841
2995
|
* Get recent events
|
|
2842
2996
|
*/
|
|
2843
|
-
async getRecentEvents(limit = 100) {
|
|
2997
|
+
async getRecentEvents(limit = 100, options) {
|
|
2844
2998
|
await this.initialize();
|
|
2845
2999
|
const rows = sqliteAll(
|
|
2846
3000
|
this.db,
|
|
2847
|
-
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
3001
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp DESC LIMIT ?`,
|
|
2848
3002
|
[limit]
|
|
2849
3003
|
);
|
|
2850
3004
|
return rows.map(this.rowToEvent);
|
|
@@ -2852,11 +3006,11 @@ var SQLiteEventStore = class {
|
|
|
2852
3006
|
/**
|
|
2853
3007
|
* Get event by ID
|
|
2854
3008
|
*/
|
|
2855
|
-
async getEvent(id) {
|
|
3009
|
+
async getEvent(id, options) {
|
|
2856
3010
|
await this.initialize();
|
|
2857
3011
|
const row = sqliteGet(
|
|
2858
3012
|
this.db,
|
|
2859
|
-
`SELECT * FROM events WHERE id =
|
|
3013
|
+
`SELECT * FROM events WHERE id = ? AND ${maybeQuarantinePredicate(options)}`,
|
|
2860
3014
|
[id]
|
|
2861
3015
|
);
|
|
2862
3016
|
if (!row)
|
|
@@ -2866,11 +3020,11 @@ var SQLiteEventStore = class {
|
|
|
2866
3020
|
/**
|
|
2867
3021
|
* Get events since a timestamp (for sync)
|
|
2868
3022
|
*/
|
|
2869
|
-
async getEventsSince(timestamp, limit = 1e3) {
|
|
3023
|
+
async getEventsSince(timestamp, limit = 1e3, options) {
|
|
2870
3024
|
await this.initialize();
|
|
2871
3025
|
const rows = sqliteAll(
|
|
2872
3026
|
this.db,
|
|
2873
|
-
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
3027
|
+
`SELECT * FROM events WHERE timestamp > ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ?`,
|
|
2874
3028
|
[timestamp, limit]
|
|
2875
3029
|
);
|
|
2876
3030
|
return rows.map(this.rowToEvent);
|
|
@@ -2879,11 +3033,11 @@ var SQLiteEventStore = class {
|
|
|
2879
3033
|
* Get events since a SQLite rowid (for robust incremental replication).
|
|
2880
3034
|
* Rowid is monotonic for append-only tables, independent of client timestamps.
|
|
2881
3035
|
*/
|
|
2882
|
-
async getEventsSinceRowid(lastRowid, limit = 1e3) {
|
|
3036
|
+
async getEventsSinceRowid(lastRowid, limit = 1e3, options) {
|
|
2883
3037
|
await this.initialize();
|
|
2884
3038
|
const rows = sqliteAll(
|
|
2885
3039
|
this.db,
|
|
2886
|
-
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? ORDER BY rowid ASC LIMIT ?`,
|
|
3040
|
+
`SELECT rowid as _rowid, * FROM events WHERE rowid > ? AND ${maybeQuarantinePredicate(options)} ORDER BY rowid ASC LIMIT ?`,
|
|
2887
3041
|
[lastRowid, limit]
|
|
2888
3042
|
);
|
|
2889
3043
|
return rows.map((row) => ({
|
|
@@ -3118,19 +3272,19 @@ var SQLiteEventStore = class {
|
|
|
3118
3272
|
/**
|
|
3119
3273
|
* Count total events
|
|
3120
3274
|
*/
|
|
3121
|
-
async countEvents() {
|
|
3275
|
+
async countEvents(options) {
|
|
3122
3276
|
await this.initialize();
|
|
3123
|
-
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events`);
|
|
3277
|
+
const row = sqliteGet(this.db, `SELECT COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`);
|
|
3124
3278
|
return row?.count || 0;
|
|
3125
3279
|
}
|
|
3126
3280
|
/**
|
|
3127
3281
|
* Get events page in timestamp ascending order (stable migration/reindex scans)
|
|
3128
3282
|
*/
|
|
3129
|
-
async getEventsPage(limit = 1e3, offset = 0) {
|
|
3283
|
+
async getEventsPage(limit = 1e3, offset = 0, options) {
|
|
3130
3284
|
await this.initialize();
|
|
3131
3285
|
const rows = sqliteAll(
|
|
3132
3286
|
this.db,
|
|
3133
|
-
`SELECT * FROM events ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3287
|
+
`SELECT * FROM events WHERE ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC LIMIT ? OFFSET ?`,
|
|
3134
3288
|
[limit, offset]
|
|
3135
3289
|
);
|
|
3136
3290
|
return rows.map(this.rowToEvent);
|
|
@@ -3204,6 +3358,145 @@ var SQLiteEventStore = class {
|
|
|
3204
3358
|
result.vector.retriedFailed = Number(vectorRetried.changes ?? 0);
|
|
3205
3359
|
return result;
|
|
3206
3360
|
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Repair legacy imported events that predate canonical project scope metadata.
|
|
3363
|
+
*
|
|
3364
|
+
* Same-project legacy rows are tagged with scope.project.hash. Rows that look
|
|
3365
|
+
* imported but cannot be proven to belong to this project are quarantined so
|
|
3366
|
+
* dashboard default reads/search do not surface cross-project contamination.
|
|
3367
|
+
*/
|
|
3368
|
+
async repairLegacyProjectScope(options = {}) {
|
|
3369
|
+
await this.initialize();
|
|
3370
|
+
const projectHash = options.projectHash || (options.projectPath ? hashProjectPath(options.projectPath) : void 0);
|
|
3371
|
+
if (!projectHash) {
|
|
3372
|
+
throw new Error("repairLegacyProjectScope requires projectPath or projectHash");
|
|
3373
|
+
}
|
|
3374
|
+
if (options.projectPath && options.projectHash && hashProjectPath(options.projectPath) !== options.projectHash) {
|
|
3375
|
+
throw new Error("repairLegacyProjectScope projectPath and projectHash refer to different project stores");
|
|
3376
|
+
}
|
|
3377
|
+
const dryRun = options.dryRun === true;
|
|
3378
|
+
const nowIso = (options.now || /* @__PURE__ */ new Date()).toISOString();
|
|
3379
|
+
const result = buildRepairResult(projectHash, dryRun);
|
|
3380
|
+
const rows = sqliteAll(
|
|
3381
|
+
this.db,
|
|
3382
|
+
`SELECT e.id, e.content, e.metadata, s.project_path as session_project_path
|
|
3383
|
+
FROM events e
|
|
3384
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
3385
|
+
ORDER BY e.timestamp ASC`,
|
|
3386
|
+
[]
|
|
3387
|
+
);
|
|
3388
|
+
const sample = (entry) => {
|
|
3389
|
+
if (result.samples.length < 20)
|
|
3390
|
+
result.samples.push(entry);
|
|
3391
|
+
};
|
|
3392
|
+
for (const row of rows) {
|
|
3393
|
+
result.scanned++;
|
|
3394
|
+
let metadata = {};
|
|
3395
|
+
let metadataParseInvalid = false;
|
|
3396
|
+
if (row.metadata) {
|
|
3397
|
+
const parsed = safeParseMetadataValue(row.metadata);
|
|
3398
|
+
if (parsed) {
|
|
3399
|
+
metadata = parsed;
|
|
3400
|
+
} else {
|
|
3401
|
+
metadataParseInvalid = true;
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
if (isActiveQuarantinedMetadata(metadata)) {
|
|
3405
|
+
result.skipped++;
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
const currentHash = metadataProjectHash(metadata);
|
|
3409
|
+
const explicitPath = metadataProjectPath(metadata);
|
|
3410
|
+
const sessionProjectPath = typeof row.session_project_path === "string" && row.session_project_path.length > 0 ? row.session_project_path : void 0;
|
|
3411
|
+
const candidatePaths = metadataProjectPaths(metadata);
|
|
3412
|
+
if (sessionProjectPath && !candidatePaths.includes(sessionProjectPath)) {
|
|
3413
|
+
candidatePaths.push(sessionProjectPath);
|
|
3414
|
+
}
|
|
3415
|
+
const importedOrLegacy = metadataParseInvalid || isImportedOrLegacyScopedMetadata(metadata) || Boolean(sessionProjectPath);
|
|
3416
|
+
const pathHashes = candidatePaths.map((candidate) => {
|
|
3417
|
+
try {
|
|
3418
|
+
return { path: candidate, hash: hashProjectPath(candidate) };
|
|
3419
|
+
} catch {
|
|
3420
|
+
return { path: candidate, hash: void 0 };
|
|
3421
|
+
}
|
|
3422
|
+
});
|
|
3423
|
+
const matchingPath = pathHashes.find((candidate) => candidate.hash === projectHash);
|
|
3424
|
+
const foreignPath = pathHashes.find((candidate) => candidate.hash && candidate.hash !== projectHash);
|
|
3425
|
+
let action = "skipped";
|
|
3426
|
+
let reason;
|
|
3427
|
+
let observedProjectHash;
|
|
3428
|
+
if (foreignPath) {
|
|
3429
|
+
action = "quarantined";
|
|
3430
|
+
reason = "project-path-mismatch";
|
|
3431
|
+
observedProjectHash = foreignPath.hash;
|
|
3432
|
+
} else if (currentHash === projectHash && importedOrLegacy && hasConflictingContentProjectHint(row.content, options.projectPath)) {
|
|
3433
|
+
action = "quarantined";
|
|
3434
|
+
reason = "content-project-mismatch";
|
|
3435
|
+
} else if (currentHash === projectHash) {
|
|
3436
|
+
result.alreadyScoped++;
|
|
3437
|
+
continue;
|
|
3438
|
+
} else if (currentHash && currentHash !== projectHash) {
|
|
3439
|
+
action = "quarantined";
|
|
3440
|
+
reason = "scope-hash-mismatch";
|
|
3441
|
+
observedProjectHash = currentHash;
|
|
3442
|
+
} else if (matchingPath) {
|
|
3443
|
+
action = "repaired";
|
|
3444
|
+
reason = matchingPath.path === sessionProjectPath && matchingPath.path !== explicitPath ? "session-project-path" : "same-project-path";
|
|
3445
|
+
} else if (candidatePaths.length > 0) {
|
|
3446
|
+
action = "quarantined";
|
|
3447
|
+
reason = "project-path-mismatch";
|
|
3448
|
+
} else if (importedOrLegacy) {
|
|
3449
|
+
action = "quarantined";
|
|
3450
|
+
reason = "missing-project-scope";
|
|
3451
|
+
}
|
|
3452
|
+
if (action === "skipped" || !reason) {
|
|
3453
|
+
result.skipped++;
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
if (action === "repaired") {
|
|
3457
|
+
const scope = isRecord(metadata.scope) ? { ...metadata.scope } : {};
|
|
3458
|
+
const project = isRecord(scope.project) ? { ...scope.project } : {};
|
|
3459
|
+
project.hash = projectHash;
|
|
3460
|
+
scope.project = project;
|
|
3461
|
+
metadata.scope = scope;
|
|
3462
|
+
metadata.repair = {
|
|
3463
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3464
|
+
legacyProjectScope: {
|
|
3465
|
+
action,
|
|
3466
|
+
reason,
|
|
3467
|
+
repairedAt: nowIso
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
addMetadataTag(metadata, `proj:${projectHash}`);
|
|
3471
|
+
result.repaired++;
|
|
3472
|
+
} else {
|
|
3473
|
+
metadata.quarantine = {
|
|
3474
|
+
...isRecord(metadata.quarantine) ? metadata.quarantine : {},
|
|
3475
|
+
status: "active",
|
|
3476
|
+
category: "project-scope",
|
|
3477
|
+
reason,
|
|
3478
|
+
detectedAt: nowIso,
|
|
3479
|
+
expectedProjectHash: projectHash,
|
|
3480
|
+
...observedProjectHash ? { observedProjectHash } : {}
|
|
3481
|
+
};
|
|
3482
|
+
metadata.repair = {
|
|
3483
|
+
...isRecord(metadata.repair) ? metadata.repair : {},
|
|
3484
|
+
legacyProjectScope: {
|
|
3485
|
+
action,
|
|
3486
|
+
reason,
|
|
3487
|
+
repairedAt: nowIso
|
|
3488
|
+
}
|
|
3489
|
+
};
|
|
3490
|
+
addMetadataTag(metadata, "quarantine:project-scope");
|
|
3491
|
+
result.quarantined++;
|
|
3492
|
+
}
|
|
3493
|
+
sample({ eventId: row.id, action, reason });
|
|
3494
|
+
if (!dryRun) {
|
|
3495
|
+
sqliteRun(this.db, `UPDATE events SET metadata = ? WHERE id = ?`, [JSON.stringify(metadata), row.id]);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
return result;
|
|
3499
|
+
}
|
|
3207
3500
|
/**
|
|
3208
3501
|
* Get embedding/vector outbox health statistics
|
|
3209
3502
|
*/
|
|
@@ -3251,7 +3544,11 @@ var SQLiteEventStore = class {
|
|
|
3251
3544
|
await this.initialize();
|
|
3252
3545
|
const rows = sqliteAll(
|
|
3253
3546
|
this.db,
|
|
3254
|
-
`SELECT level, COUNT(*) as count
|
|
3547
|
+
`SELECT ml.level, COUNT(*) as count
|
|
3548
|
+
FROM memory_levels ml
|
|
3549
|
+
INNER JOIN events e ON e.id = ml.event_id
|
|
3550
|
+
WHERE ${notActiveQuarantinedSql("e.metadata")}
|
|
3551
|
+
GROUP BY ml.level`
|
|
3255
3552
|
);
|
|
3256
3553
|
return rows;
|
|
3257
3554
|
}
|
|
@@ -3267,6 +3564,7 @@ var SQLiteEventStore = class {
|
|
|
3267
3564
|
`SELECT e.* FROM events e
|
|
3268
3565
|
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
3269
3566
|
WHERE ml.level = ?
|
|
3567
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3270
3568
|
ORDER BY e.timestamp DESC
|
|
3271
3569
|
LIMIT ? OFFSET ?`,
|
|
3272
3570
|
[level, limit, offset]
|
|
@@ -3359,12 +3657,13 @@ var SQLiteEventStore = class {
|
|
|
3359
3657
|
/**
|
|
3360
3658
|
* Get most accessed memories (falls back to recent events if none accessed)
|
|
3361
3659
|
*/
|
|
3362
|
-
async getMostAccessed(limit = 10) {
|
|
3660
|
+
async getMostAccessed(limit = 10, options) {
|
|
3363
3661
|
await this.initialize();
|
|
3364
3662
|
let rows = sqliteAll(
|
|
3365
3663
|
this.db,
|
|
3366
3664
|
`SELECT * FROM events
|
|
3367
3665
|
WHERE access_count > 0
|
|
3666
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3368
3667
|
ORDER BY access_count DESC, last_accessed_at DESC
|
|
3369
3668
|
LIMIT ?`,
|
|
3370
3669
|
[limit]
|
|
@@ -3373,6 +3672,7 @@ var SQLiteEventStore = class {
|
|
|
3373
3672
|
rows = sqliteAll(
|
|
3374
3673
|
this.db,
|
|
3375
3674
|
`SELECT * FROM events
|
|
3675
|
+
WHERE ${maybeQuarantinePredicate(options)}
|
|
3376
3676
|
ORDER BY timestamp DESC
|
|
3377
3677
|
LIMIT ?`,
|
|
3378
3678
|
[limit]
|
|
@@ -3503,6 +3803,7 @@ var SQLiteEventStore = class {
|
|
|
3503
3803
|
FROM memory_helpfulness mh
|
|
3504
3804
|
JOIN events e ON e.id = mh.event_id
|
|
3505
3805
|
WHERE mh.measured_at IS NOT NULL
|
|
3806
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3506
3807
|
GROUP BY mh.event_id
|
|
3507
3808
|
ORDER BY avg_score DESC
|
|
3508
3809
|
LIMIT ?`,
|
|
@@ -3567,6 +3868,7 @@ var SQLiteEventStore = class {
|
|
|
3567
3868
|
FROM events_fts fts
|
|
3568
3869
|
JOIN events e ON e.id = fts.event_id
|
|
3569
3870
|
WHERE events_fts MATCH ?
|
|
3871
|
+
AND ${notActiveQuarantinedSql("e.metadata")}
|
|
3570
3872
|
ORDER BY fts.rank
|
|
3571
3873
|
LIMIT ?`,
|
|
3572
3874
|
[searchTerms, limit]
|
|
@@ -3581,6 +3883,7 @@ var SQLiteEventStore = class {
|
|
|
3581
3883
|
this.db,
|
|
3582
3884
|
`SELECT *, 0 as rank FROM events
|
|
3583
3885
|
WHERE content LIKE ?
|
|
3886
|
+
AND ${notActiveQuarantinedSql()}
|
|
3584
3887
|
ORDER BY timestamp DESC
|
|
3585
3888
|
LIMIT ?`,
|
|
3586
3889
|
[likePattern, limit]
|
|
@@ -3787,6 +4090,7 @@ var SQLiteEventStore = class {
|
|
|
3787
4090
|
`SELECT turn_id, MIN(timestamp) as min_ts
|
|
3788
4091
|
FROM events
|
|
3789
4092
|
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4093
|
+
AND ${maybeQuarantinePredicate(options)}
|
|
3790
4094
|
GROUP BY turn_id
|
|
3791
4095
|
ORDER BY min_ts DESC
|
|
3792
4096
|
LIMIT ? OFFSET ?`,
|
|
@@ -3794,7 +4098,7 @@ var SQLiteEventStore = class {
|
|
|
3794
4098
|
);
|
|
3795
4099
|
const turns = [];
|
|
3796
4100
|
for (const turnRow of turnRows) {
|
|
3797
|
-
const events = await this.getEventsByTurn(turnRow.turn_id);
|
|
4101
|
+
const events = await this.getEventsByTurn(turnRow.turn_id, options);
|
|
3798
4102
|
const promptEvent = events.find((e) => e.eventType === "user_prompt");
|
|
3799
4103
|
const toolEvents = events.filter((e) => e.eventType === "tool_observation");
|
|
3800
4104
|
const hasResponse = events.some((e) => e.eventType === "agent_response");
|
|
@@ -3813,11 +4117,11 @@ var SQLiteEventStore = class {
|
|
|
3813
4117
|
/**
|
|
3814
4118
|
* Get all events for a specific turn_id
|
|
3815
4119
|
*/
|
|
3816
|
-
async getEventsByTurn(turnId) {
|
|
4120
|
+
async getEventsByTurn(turnId, options) {
|
|
3817
4121
|
await this.initialize();
|
|
3818
4122
|
const rows = sqliteAll(
|
|
3819
4123
|
this.db,
|
|
3820
|
-
`SELECT * FROM events WHERE turn_id = ? ORDER BY timestamp ASC`,
|
|
4124
|
+
`SELECT * FROM events WHERE turn_id = ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3821
4125
|
[turnId]
|
|
3822
4126
|
);
|
|
3823
4127
|
return rows.map(this.rowToEvent);
|
|
@@ -3825,13 +4129,14 @@ var SQLiteEventStore = class {
|
|
|
3825
4129
|
/**
|
|
3826
4130
|
* Count total turns for a session
|
|
3827
4131
|
*/
|
|
3828
|
-
async countSessionTurns(sessionId) {
|
|
4132
|
+
async countSessionTurns(sessionId, options) {
|
|
3829
4133
|
await this.initialize();
|
|
3830
4134
|
const row = sqliteGet(
|
|
3831
4135
|
this.db,
|
|
3832
4136
|
`SELECT COUNT(DISTINCT turn_id) as count
|
|
3833
4137
|
FROM events
|
|
3834
|
-
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4138
|
+
WHERE session_id = ? AND turn_id IS NOT NULL
|
|
4139
|
+
AND ${maybeQuarantinePredicate(options)}`,
|
|
3835
4140
|
[sessionId]
|
|
3836
4141
|
);
|
|
3837
4142
|
return row?.count || 0;
|
|
@@ -3923,7 +4228,7 @@ var SQLiteEventStore = class {
|
|
|
3923
4228
|
content: row.content,
|
|
3924
4229
|
canonicalKey: row.canonical_key,
|
|
3925
4230
|
dedupeKey: row.dedupe_key,
|
|
3926
|
-
metadata:
|
|
4231
|
+
metadata: safeParseMetadataValue(row.metadata)
|
|
3927
4232
|
};
|
|
3928
4233
|
if (row.access_count !== void 0) {
|
|
3929
4234
|
event.access_count = row.access_count;
|
|
@@ -4518,6 +4823,10 @@ var MemoryQueryService = class {
|
|
|
4518
4823
|
await this.initialize();
|
|
4519
4824
|
return this.getMaintenanceStore("recoverStuckOutboxItems").recoverStuckOutboxItems(options);
|
|
4520
4825
|
}
|
|
4826
|
+
async repairLegacyProjectScope(options) {
|
|
4827
|
+
await this.initialize();
|
|
4828
|
+
return this.getMaintenanceStore("repairLegacyProjectScope").repairLegacyProjectScope(options);
|
|
4829
|
+
}
|
|
4521
4830
|
async getStats() {
|
|
4522
4831
|
await this.initialize();
|
|
4523
4832
|
const deps = this.getStatsDeps();
|
|
@@ -6140,18 +6449,18 @@ function assertDefaultRetrieverStore(eventStore) {
|
|
|
6140
6449
|
function createMemoryEngineServices(options) {
|
|
6141
6450
|
const factories = options.factories ?? {};
|
|
6142
6451
|
const storagePath = options.storagePath;
|
|
6143
|
-
if (!options.readOnly && !
|
|
6144
|
-
|
|
6452
|
+
if (!options.readOnly && !fs6.existsSync(storagePath)) {
|
|
6453
|
+
fs6.mkdirSync(storagePath, { recursive: true });
|
|
6145
6454
|
}
|
|
6146
6455
|
const sqliteStore = (factories.createSQLiteEventStore ?? defaultCreateSQLiteEventStore)(
|
|
6147
|
-
|
|
6456
|
+
path5.join(storagePath, "events.sqlite"),
|
|
6148
6457
|
{
|
|
6149
6458
|
readonly: options.readOnly,
|
|
6150
6459
|
markdownMirrorRoot: storagePath
|
|
6151
6460
|
}
|
|
6152
6461
|
);
|
|
6153
6462
|
const vectorStore = (factories.createVectorStore ?? defaultCreateVectorStore)(
|
|
6154
|
-
|
|
6463
|
+
path5.join(storagePath, "vectors")
|
|
6155
6464
|
);
|
|
6156
6465
|
const embeddingModel = options.embeddingModel || process.env.CLAUDE_MEMORY_EMBEDDING_MODEL;
|
|
6157
6466
|
const embedder = embeddingModel ? (factories.createEmbedder ?? defaultCreateEmbedder)(embeddingModel) : (factories.getDefaultEmbedder ?? getDefaultEmbedder)();
|
|
@@ -6562,8 +6871,8 @@ function createMemoryRuntimeService(deps) {
|
|
|
6562
6871
|
}
|
|
6563
6872
|
|
|
6564
6873
|
// src/extensions/shared-memory/shared-memory-services.ts
|
|
6565
|
-
import * as
|
|
6566
|
-
import * as
|
|
6874
|
+
import * as fs7 from "fs";
|
|
6875
|
+
import * as path6 from "path";
|
|
6567
6876
|
|
|
6568
6877
|
// src/core/shared-event-store.ts
|
|
6569
6878
|
var SharedEventStore = class {
|
|
@@ -7251,7 +7560,7 @@ var SharedMemoryServices = class {
|
|
|
7251
7560
|
this.ensureDirectory(sharedPath, { allowCreate: true });
|
|
7252
7561
|
const store = await this.openStore(sharedPath);
|
|
7253
7562
|
this.sharedVectorStore = this.factories.createSharedVectorStore(
|
|
7254
|
-
|
|
7563
|
+
path6.join(sharedPath, "vectors")
|
|
7255
7564
|
);
|
|
7256
7565
|
await this.sharedVectorStore.initialize();
|
|
7257
7566
|
this.sharedPromoter = this.factories.createSharedPromoter(
|
|
@@ -7324,7 +7633,7 @@ var SharedMemoryServices = class {
|
|
|
7324
7633
|
async createOpenStorePromise(sharedPath) {
|
|
7325
7634
|
if (!this.sharedEventStore) {
|
|
7326
7635
|
const sharedEventStore = this.factories.createSharedEventStore(
|
|
7327
|
-
|
|
7636
|
+
path6.join(sharedPath, "shared.duckdb")
|
|
7328
7637
|
);
|
|
7329
7638
|
await sharedEventStore.initialize();
|
|
7330
7639
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -7344,9 +7653,9 @@ var SharedMemoryServices = class {
|
|
|
7344
7653
|
}
|
|
7345
7654
|
get factories() {
|
|
7346
7655
|
return {
|
|
7347
|
-
existsSync: this.options.factories?.existsSync ??
|
|
7656
|
+
existsSync: this.options.factories?.existsSync ?? fs7.existsSync,
|
|
7348
7657
|
mkdirSync: this.options.factories?.mkdirSync ?? ((targetPath) => {
|
|
7349
|
-
|
|
7658
|
+
fs7.mkdirSync(targetPath, { recursive: true });
|
|
7350
7659
|
}),
|
|
7351
7660
|
createSharedEventStore: this.options.factories?.createSharedEventStore ?? createSharedEventStore,
|
|
7352
7661
|
createSharedStore: this.options.factories?.createSharedStore ?? createSharedStore,
|
|
@@ -7461,33 +7770,11 @@ function createMemoryServiceComposition(options) {
|
|
|
7461
7770
|
}
|
|
7462
7771
|
function defaultExpandPath(targetPath) {
|
|
7463
7772
|
if (targetPath.startsWith("~")) {
|
|
7464
|
-
return
|
|
7773
|
+
return path7.join(os2.homedir(), targetPath.slice(1));
|
|
7465
7774
|
}
|
|
7466
7775
|
return targetPath;
|
|
7467
7776
|
}
|
|
7468
7777
|
|
|
7469
|
-
// src/core/registry/project-path.ts
|
|
7470
|
-
import * as crypto2 from "crypto";
|
|
7471
|
-
import * as fs7 from "fs";
|
|
7472
|
-
import * as os2 from "os";
|
|
7473
|
-
import * as path7 from "path";
|
|
7474
|
-
function normalizeProjectPath(projectPath) {
|
|
7475
|
-
const expanded = projectPath.startsWith("~") ? path7.join(os2.homedir(), projectPath.slice(1)) : projectPath;
|
|
7476
|
-
try {
|
|
7477
|
-
return fs7.realpathSync(expanded);
|
|
7478
|
-
} catch {
|
|
7479
|
-
return path7.resolve(expanded);
|
|
7480
|
-
}
|
|
7481
|
-
}
|
|
7482
|
-
function hashProjectPath(projectPath) {
|
|
7483
|
-
const normalizedPath = normalizeProjectPath(projectPath);
|
|
7484
|
-
return crypto2.createHash("sha256").update(normalizedPath).digest("hex").slice(0, 8);
|
|
7485
|
-
}
|
|
7486
|
-
function getProjectStoragePath(projectPath) {
|
|
7487
|
-
const hash = hashProjectPath(projectPath);
|
|
7488
|
-
return path7.join(os2.homedir(), ".claude-code", "memory", "projects", hash);
|
|
7489
|
-
}
|
|
7490
|
-
|
|
7491
7778
|
// src/core/registry/session-registry.ts
|
|
7492
7779
|
import * as fs8 from "fs";
|
|
7493
7780
|
import * as os3 from "os";
|
|
@@ -7789,6 +8076,9 @@ var MemoryService = class {
|
|
|
7789
8076
|
async recoverStuckOutboxItems(options) {
|
|
7790
8077
|
return this.queryService.recoverStuckOutboxItems(options);
|
|
7791
8078
|
}
|
|
8079
|
+
async repairLegacyProjectScope(options) {
|
|
8080
|
+
return this.queryService.repairLegacyProjectScope(options);
|
|
8081
|
+
}
|
|
7792
8082
|
async getRetrievalTraceStats() {
|
|
7793
8083
|
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
7794
8084
|
}
|
|
@@ -8174,10 +8464,10 @@ var SENSITIVE_PATTERNS = [
|
|
|
8174
8464
|
// Redact the whole URI so usernames, credentials, hosts, paths, and query
|
|
8175
8465
|
// params do not leak either.
|
|
8176
8466
|
/\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`<>/@]+@[^\s'"`<>]+/gi,
|
|
8177
|
-
/password\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8178
|
-
/api[_
|
|
8179
|
-
/secret\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8180
|
-
/token\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
8467
|
+
/(?:[\w.-]+[-_])?password\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8468
|
+
/(?:[\w.-]+[-_])?api[-_]?key\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8469
|
+
/(?:[\w.-]+[-_])?secret\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8470
|
+
/(?:[\w.-]+[-_])?token\s*[:=]\s*(?!\[REDACTED\])['"]?[^\s'"]+/gi,
|
|
8181
8471
|
/bearer\s+[a-zA-Z0-9\-_.]+/gi,
|
|
8182
8472
|
/AWS[_-]?ACCESS[_-]?KEY[_-]?ID\s*[:=]\s*['"]?[A-Z0-9]+/gi,
|
|
8183
8473
|
/AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY\s*[:=]\s*['"]?[^\s'"]+/gi,
|
|
@@ -8187,6 +8477,35 @@ var SENSITIVE_PATTERNS = [
|
|
|
8187
8477
|
/sk-[a-zA-Z0-9]{48}/g
|
|
8188
8478
|
// OpenAI API Key
|
|
8189
8479
|
];
|
|
8480
|
+
var CLI_SECRET_OPTION_PATTERNS = [
|
|
8481
|
+
/(--(?:[a-z0-9]+[-_])*(?:password|secret|api[-_]?key|token|bearer)(?:[-_][a-z0-9]+)*(?:\s+|=))(?:"[^"]*"|'[^']*'|[^\s'"`<>]+)/gi
|
|
8482
|
+
];
|
|
8483
|
+
var URL_FOLLOWING_SECRET_PATTERN = /((?:https?:\/\/[^\s'"`<>]+)\s*\r?\n\s*)([A-Za-z0-9!@#$%^&*._+\-~:=/]{6,})(?=\s*(?:\r?\n|$))/gi;
|
|
8484
|
+
function looksLikePastedSecret(value) {
|
|
8485
|
+
return value.length >= 8 && /(?:\d|[^A-Za-z0-9])/.test(value);
|
|
8486
|
+
}
|
|
8487
|
+
function maskUrlFollowingSecret(value) {
|
|
8488
|
+
let count = 0;
|
|
8489
|
+
const content = value.replace(URL_FOLLOWING_SECRET_PATTERN, (_match, prefix, secret) => {
|
|
8490
|
+
if (!looksLikePastedSecret(secret))
|
|
8491
|
+
return `${prefix}${secret}`;
|
|
8492
|
+
count++;
|
|
8493
|
+
return `${prefix}[REDACTED]`;
|
|
8494
|
+
});
|
|
8495
|
+
return { content, count };
|
|
8496
|
+
}
|
|
8497
|
+
function maskCliSecretOptions(value) {
|
|
8498
|
+
let count = 0;
|
|
8499
|
+
let filtered = value;
|
|
8500
|
+
for (const pattern of CLI_SECRET_OPTION_PATTERNS) {
|
|
8501
|
+
pattern.lastIndex = 0;
|
|
8502
|
+
filtered = filtered.replace(pattern, (_match, prefix) => {
|
|
8503
|
+
count++;
|
|
8504
|
+
return `${prefix}[REDACTED]`;
|
|
8505
|
+
});
|
|
8506
|
+
}
|
|
8507
|
+
return { content: filtered, count };
|
|
8508
|
+
}
|
|
8190
8509
|
function applyPrivacyFilter(content, config) {
|
|
8191
8510
|
let filtered = content;
|
|
8192
8511
|
let privateTagCount = 0;
|
|
@@ -8200,6 +8519,12 @@ function applyPrivacyFilter(content, config) {
|
|
|
8200
8519
|
filtered = tagResult.filtered;
|
|
8201
8520
|
privateTagCount = tagResult.stats.count;
|
|
8202
8521
|
}
|
|
8522
|
+
const cliResult = maskCliSecretOptions(filtered);
|
|
8523
|
+
filtered = cliResult.content;
|
|
8524
|
+
patternMatchCount += cliResult.count;
|
|
8525
|
+
const urlSecretResult = maskUrlFollowingSecret(filtered);
|
|
8526
|
+
filtered = urlSecretResult.content;
|
|
8527
|
+
patternMatchCount += urlSecretResult.count;
|
|
8203
8528
|
for (const pattern of SENSITIVE_PATTERNS) {
|
|
8204
8529
|
pattern.lastIndex = 0;
|
|
8205
8530
|
const matches = filtered.match(pattern);
|
|
@@ -8211,13 +8536,13 @@ function applyPrivacyFilter(content, config) {
|
|
|
8211
8536
|
for (const patternStr of config.excludePatterns || []) {
|
|
8212
8537
|
try {
|
|
8213
8538
|
const regex = new RegExp(
|
|
8214
|
-
`(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
8539
|
+
`(^|[^\\w-])(${patternStr})\\s*[:=]\\s*['"]?[^\\s'"]+`,
|
|
8215
8540
|
"gi"
|
|
8216
8541
|
);
|
|
8217
8542
|
const matches = filtered.match(regex);
|
|
8218
8543
|
if (matches) {
|
|
8219
8544
|
patternMatchCount += matches.length;
|
|
8220
|
-
filtered = filtered.replace(regex, "[REDACTED]");
|
|
8545
|
+
filtered = filtered.replace(regex, "$1[REDACTED]");
|
|
8221
8546
|
}
|
|
8222
8547
|
} catch {
|
|
8223
8548
|
}
|