claude-memory-layer 1.0.53 → 1.0.54
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/README.md +5 -5
- package/dist/cli/index.js +922 -524
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +437 -164
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +502 -159
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +432 -134
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +496 -152
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +503 -152
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +494 -154
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +503 -159
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +484 -193
- package/dist/index.js.map +4 -4
- package/dist/mcp/index.js +577 -231
- package/dist/mcp/index.js.map +4 -4
- package/dist/server/api/index.js +691 -354
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +742 -368
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +419 -133
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/assets/js/overview.js +34 -2
- package/package.json +5 -2
package/dist/cli/index.js
CHANGED
|
@@ -9,8 +9,8 @@ const __dirname = dirname(__filename);
|
|
|
9
9
|
// src/apps/cli/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import { exec } from "child_process";
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
12
|
+
import * as fs19 from "fs";
|
|
13
|
+
import * as path25 from "path";
|
|
14
14
|
import * as os13 from "os";
|
|
15
15
|
|
|
16
16
|
// src/services/memory-service.ts
|
|
@@ -165,7 +165,13 @@ function toDate(value) {
|
|
|
165
165
|
return new Date(String(value));
|
|
166
166
|
}
|
|
167
167
|
function createDatabase(dbPath, options) {
|
|
168
|
-
|
|
168
|
+
const db = new BetterSqlite3(dbPath, { readonly: options?.readOnly });
|
|
169
|
+
db.pragma("busy_timeout = 5000");
|
|
170
|
+
if (!options?.readOnly) {
|
|
171
|
+
db.pragma("journal_mode = WAL");
|
|
172
|
+
db.pragma("synchronous = NORMAL");
|
|
173
|
+
}
|
|
174
|
+
return db;
|
|
169
175
|
}
|
|
170
176
|
function dbRun(db, sql, params = []) {
|
|
171
177
|
db.prepare(sql).run(...params);
|
|
@@ -398,19 +404,29 @@ var ConsolidatedStore = class {
|
|
|
398
404
|
*/
|
|
399
405
|
async create(input) {
|
|
400
406
|
const memoryId = randomUUID2();
|
|
401
|
-
|
|
402
|
-
|
|
407
|
+
const sourceEvents = Array.isArray(input.sourceEvents) ? input.sourceEvents : [];
|
|
408
|
+
const insertMemory = this.db.prepare(
|
|
403
409
|
`INSERT INTO consolidated_memories
|
|
404
410
|
(memory_id, summary, topics, source_events, confidence, created_at)
|
|
405
|
-
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
406
|
-
|
|
411
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`
|
|
412
|
+
);
|
|
413
|
+
const insertEvent = this.db.prepare(
|
|
414
|
+
`INSERT OR IGNORE INTO consolidated_memory_events (memory_id, event_id) VALUES (?, ?)`
|
|
415
|
+
);
|
|
416
|
+
this.db.transaction(() => {
|
|
417
|
+
insertMemory.run(
|
|
407
418
|
memoryId,
|
|
408
419
|
input.summary,
|
|
409
420
|
JSON.stringify(input.topics),
|
|
410
|
-
JSON.stringify(
|
|
421
|
+
JSON.stringify(sourceEvents),
|
|
411
422
|
input.confidence
|
|
412
|
-
|
|
413
|
-
|
|
423
|
+
);
|
|
424
|
+
for (const eventId of sourceEvents) {
|
|
425
|
+
if (typeof eventId === "string" && eventId.length > 0) {
|
|
426
|
+
insertEvent.run(memoryId, eventId);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
})();
|
|
414
430
|
return memoryId;
|
|
415
431
|
}
|
|
416
432
|
/**
|
|
@@ -516,11 +532,12 @@ var ConsolidatedStore = class {
|
|
|
516
532
|
* Delete a consolidated memory
|
|
517
533
|
*/
|
|
518
534
|
async delete(memoryId) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
535
|
+
const deleteEvents = this.db.prepare(`DELETE FROM consolidated_memory_events WHERE memory_id = ?`);
|
|
536
|
+
const deleteMemory = this.db.prepare(`DELETE FROM consolidated_memories WHERE memory_id = ?`);
|
|
537
|
+
this.db.transaction(() => {
|
|
538
|
+
deleteEvents.run(memoryId);
|
|
539
|
+
deleteMemory.run(memoryId);
|
|
540
|
+
})();
|
|
524
541
|
}
|
|
525
542
|
/**
|
|
526
543
|
* Create a long-term rule promoted from stable summaries
|
|
@@ -633,16 +650,16 @@ var ConsolidatedStore = class {
|
|
|
633
650
|
* Check if source events are already consolidated
|
|
634
651
|
*/
|
|
635
652
|
async isAlreadyConsolidated(eventIds) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return
|
|
653
|
+
if (eventIds.length === 0) return false;
|
|
654
|
+
const placeholders2 = eventIds.map(() => "?").join(",");
|
|
655
|
+
const rows = await dbAll(
|
|
656
|
+
this.db,
|
|
657
|
+
`SELECT memory_id FROM consolidated_memory_events
|
|
658
|
+
WHERE event_id IN (${placeholders2})
|
|
659
|
+
LIMIT 1`,
|
|
660
|
+
eventIds
|
|
661
|
+
);
|
|
662
|
+
return rows.length > 0;
|
|
646
663
|
}
|
|
647
664
|
/**
|
|
648
665
|
* Get the last consolidation time
|
|
@@ -2178,6 +2195,23 @@ function makeDedupeKey(content, sessionId) {
|
|
|
2178
2195
|
return `${sessionId}:${contentHash}`;
|
|
2179
2196
|
}
|
|
2180
2197
|
|
|
2198
|
+
// src/core/citation-generator.ts
|
|
2199
|
+
import { createHash as createHash2 } from "crypto";
|
|
2200
|
+
var ID_LENGTH = 6;
|
|
2201
|
+
var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
2202
|
+
function generateCitationId(eventId) {
|
|
2203
|
+
const hash = createHash2("sha256").update(eventId).digest();
|
|
2204
|
+
let id = "";
|
|
2205
|
+
for (let i = 0; i < ID_LENGTH; i++) {
|
|
2206
|
+
id += CHARSET[hash[i] % CHARSET.length];
|
|
2207
|
+
}
|
|
2208
|
+
return id;
|
|
2209
|
+
}
|
|
2210
|
+
function parseCitationId(formatted) {
|
|
2211
|
+
const match = formatted.match(/\[?mem:([A-Za-z0-9]{6})\]?/);
|
|
2212
|
+
return match ? match[1] : null;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2181
2215
|
// src/core/sqlite-event-store.ts
|
|
2182
2216
|
import * as nodePath2 from "path";
|
|
2183
2217
|
|
|
@@ -2211,12 +2245,12 @@ function resolveProjectStoragePath(projectOrHash) {
|
|
|
2211
2245
|
import Database from "better-sqlite3";
|
|
2212
2246
|
import * as fs4 from "fs";
|
|
2213
2247
|
import * as nodePath from "path";
|
|
2214
|
-
function createSQLiteDatabase(
|
|
2215
|
-
const dir = nodePath.dirname(
|
|
2248
|
+
function createSQLiteDatabase(path26, options) {
|
|
2249
|
+
const dir = nodePath.dirname(path26);
|
|
2216
2250
|
if (!fs4.existsSync(dir)) {
|
|
2217
2251
|
fs4.mkdirSync(dir, { recursive: true });
|
|
2218
2252
|
}
|
|
2219
|
-
const db = new Database(
|
|
2253
|
+
const db = new Database(path26, {
|
|
2220
2254
|
readonly: options?.readonly ?? false
|
|
2221
2255
|
});
|
|
2222
2256
|
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
@@ -2226,17 +2260,28 @@ function createSQLiteDatabase(path25, options) {
|
|
|
2226
2260
|
}
|
|
2227
2261
|
return db;
|
|
2228
2262
|
}
|
|
2263
|
+
var statementCache = /* @__PURE__ */ new WeakMap();
|
|
2264
|
+
function prepareCached(db, sql) {
|
|
2265
|
+
let cache = statementCache.get(db);
|
|
2266
|
+
if (!cache) {
|
|
2267
|
+
cache = /* @__PURE__ */ new Map();
|
|
2268
|
+
statementCache.set(db, cache);
|
|
2269
|
+
}
|
|
2270
|
+
let stmt = cache.get(sql);
|
|
2271
|
+
if (!stmt) {
|
|
2272
|
+
stmt = db.prepare(sql);
|
|
2273
|
+
cache.set(sql, stmt);
|
|
2274
|
+
}
|
|
2275
|
+
return stmt;
|
|
2276
|
+
}
|
|
2229
2277
|
function sqliteRun(db, sql, params = []) {
|
|
2230
|
-
|
|
2231
|
-
return stmt.run(...params);
|
|
2278
|
+
return prepareCached(db, sql).run(...params);
|
|
2232
2279
|
}
|
|
2233
2280
|
function sqliteAll(db, sql, params = []) {
|
|
2234
|
-
|
|
2235
|
-
return stmt.all(...params);
|
|
2281
|
+
return prepareCached(db, sql).all(...params);
|
|
2236
2282
|
}
|
|
2237
2283
|
function sqliteGet(db, sql, params = []) {
|
|
2238
|
-
|
|
2239
|
-
return stmt.get(...params);
|
|
2284
|
+
return prepareCached(db, sql).get(...params);
|
|
2240
2285
|
}
|
|
2241
2286
|
function sqliteExec(db, sql) {
|
|
2242
2287
|
db.exec(sql);
|
|
@@ -2658,6 +2703,7 @@ function normalizeQueryRewriteKind(value) {
|
|
|
2658
2703
|
var REWRITTEN_QUERY_REWRITE_KIND_SQL = `LOWER(TRIM(COALESCE(query_rewrite_kind, 'none'))) IN ('follow-up-context', 'intent-rewrite')`;
|
|
2659
2704
|
var DEFAULT_OUTBOX_STUCK_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
2660
2705
|
var DEFAULT_OUTBOX_MAX_RETRIES = 3;
|
|
2706
|
+
var SQLITE_SCHEMA_VERSION = 1;
|
|
2661
2707
|
function emptyOutboxRecoveryResult() {
|
|
2662
2708
|
return {
|
|
2663
2709
|
embedding: { recoveredProcessing: 0, retriedFailed: 0 },
|
|
@@ -2667,17 +2713,17 @@ function emptyOutboxRecoveryResult() {
|
|
|
2667
2713
|
function isRecord(value) {
|
|
2668
2714
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2669
2715
|
}
|
|
2670
|
-
function getNestedRecord(root,
|
|
2716
|
+
function getNestedRecord(root, path26) {
|
|
2671
2717
|
let cursor = root;
|
|
2672
|
-
for (const key of
|
|
2718
|
+
for (const key of path26) {
|
|
2673
2719
|
if (!isRecord(cursor)) return void 0;
|
|
2674
2720
|
cursor = cursor[key];
|
|
2675
2721
|
}
|
|
2676
2722
|
return isRecord(cursor) ? cursor : void 0;
|
|
2677
2723
|
}
|
|
2678
|
-
function getNestedString(root,
|
|
2724
|
+
function getNestedString(root, path26) {
|
|
2679
2725
|
let cursor = root;
|
|
2680
|
-
for (const key of
|
|
2726
|
+
for (const key of path26) {
|
|
2681
2727
|
if (!isRecord(cursor)) return void 0;
|
|
2682
2728
|
cursor = cursor[key];
|
|
2683
2729
|
}
|
|
@@ -3002,6 +3048,15 @@ var SQLiteEventStore = class {
|
|
|
3002
3048
|
access_count INTEGER DEFAULT 0
|
|
3003
3049
|
);
|
|
3004
3050
|
|
|
3051
|
+
-- Junction: consolidated memory -> source event id (indexed lookup that
|
|
3052
|
+
-- replaces per-event source_events LIKE scans during consolidation).
|
|
3053
|
+
CREATE TABLE IF NOT EXISTS consolidated_memory_events (
|
|
3054
|
+
memory_id TEXT NOT NULL,
|
|
3055
|
+
event_id TEXT NOT NULL,
|
|
3056
|
+
PRIMARY KEY (memory_id, event_id)
|
|
3057
|
+
);
|
|
3058
|
+
CREATE INDEX IF NOT EXISTS idx_cme_event ON consolidated_memory_events(event_id);
|
|
3059
|
+
|
|
3005
3060
|
-- Continuity Log table (tracks context transitions)
|
|
3006
3061
|
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
3007
3062
|
log_id TEXT PRIMARY KEY,
|
|
@@ -3361,11 +3416,17 @@ var SQLiteEventStore = class {
|
|
|
3361
3416
|
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
3362
3417
|
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
3363
3418
|
END;
|
|
3419
|
+
|
|
3420
|
+
-- Reverse index for citation ids (a non-reversible hash of event id), so
|
|
3421
|
+
-- citation lookups are O(1) instead of scanning recent events and hashing
|
|
3422
|
+
-- each one. Populated lazily/self-healing by getEventByCitationId.
|
|
3423
|
+
CREATE TABLE IF NOT EXISTS event_citations (
|
|
3424
|
+
event_id TEXT PRIMARY KEY,
|
|
3425
|
+
citation_id TEXT NOT NULL
|
|
3426
|
+
);
|
|
3427
|
+
CREATE INDEX IF NOT EXISTS idx_event_citations_citation ON event_citations(citation_id);
|
|
3364
3428
|
`);
|
|
3365
|
-
|
|
3366
|
-
sqliteExec(this.db, `ALTER TABLE memory_action_edges ADD COLUMN source TEXT NOT NULL DEFAULT 'manual';`);
|
|
3367
|
-
} catch {
|
|
3368
|
-
}
|
|
3429
|
+
this.addColumnIfMissing("memory_action_edges", "source", `TEXT NOT NULL DEFAULT 'manual'`);
|
|
3369
3430
|
try {
|
|
3370
3431
|
const edgeIndexes = sqliteAll(this.db, `PRAGMA index_list(memory_action_edges)`, []);
|
|
3371
3432
|
const hasSourceAwareUnique = edgeIndexes.some((index) => {
|
|
@@ -3402,55 +3463,17 @@ var SQLiteEventStore = class {
|
|
|
3402
3463
|
}
|
|
3403
3464
|
} catch {
|
|
3404
3465
|
}
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
try {
|
|
3410
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN candidate_details_json TEXT;`);
|
|
3411
|
-
} catch {
|
|
3412
|
-
}
|
|
3413
|
-
try {
|
|
3414
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN raw_query_text TEXT;`);
|
|
3415
|
-
} catch {
|
|
3416
|
-
}
|
|
3417
|
-
try {
|
|
3418
|
-
sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN query_rewrite_kind TEXT;`);
|
|
3419
|
-
} catch {
|
|
3420
|
-
}
|
|
3466
|
+
this.addColumnIfMissing("retrieval_traces", "selected_details_json", "TEXT");
|
|
3467
|
+
this.addColumnIfMissing("retrieval_traces", "candidate_details_json", "TEXT");
|
|
3468
|
+
this.addColumnIfMissing("retrieval_traces", "raw_query_text", "TEXT");
|
|
3469
|
+
this.addColumnIfMissing("retrieval_traces", "query_rewrite_kind", "TEXT");
|
|
3421
3470
|
try {
|
|
3422
3471
|
sqliteExec(this.db, `CREATE INDEX IF NOT EXISTS idx_retrieval_traces_query_rewrite_kind ON retrieval_traces(query_rewrite_kind);`);
|
|
3423
3472
|
} catch {
|
|
3424
3473
|
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
try {
|
|
3429
|
-
sqliteExec(this.db, `
|
|
3430
|
-
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
3431
|
-
`);
|
|
3432
|
-
} catch (err) {
|
|
3433
|
-
console.error("Error adding access_count column:", err);
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
if (!columnNames.includes("last_accessed_at")) {
|
|
3437
|
-
try {
|
|
3438
|
-
sqliteExec(this.db, `
|
|
3439
|
-
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
3440
|
-
`);
|
|
3441
|
-
} catch (err) {
|
|
3442
|
-
console.error("Error adding last_accessed_at column:", err);
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
if (!columnNames.includes("turn_id")) {
|
|
3446
|
-
try {
|
|
3447
|
-
sqliteExec(this.db, `
|
|
3448
|
-
ALTER TABLE events ADD COLUMN turn_id TEXT;
|
|
3449
|
-
`);
|
|
3450
|
-
} catch (err) {
|
|
3451
|
-
console.error("Error adding turn_id column:", err);
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3474
|
+
this.addColumnIfMissing("events", "access_count", "INTEGER DEFAULT 0");
|
|
3475
|
+
this.addColumnIfMissing("events", "last_accessed_at", "TEXT");
|
|
3476
|
+
this.addColumnIfMissing("events", "turn_id", "TEXT");
|
|
3454
3477
|
try {
|
|
3455
3478
|
sqliteExec(this.db, `
|
|
3456
3479
|
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
@@ -3467,8 +3490,18 @@ var SQLiteEventStore = class {
|
|
|
3467
3490
|
sqliteExec(this.db, `
|
|
3468
3491
|
CREATE INDEX IF NOT EXISTS idx_events_turn_id ON events(turn_id);
|
|
3469
3492
|
`);
|
|
3470
|
-
} catch
|
|
3493
|
+
} catch {
|
|
3494
|
+
}
|
|
3495
|
+
const cmeCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM consolidated_memory_events`, []);
|
|
3496
|
+
if ((cmeCount?.c ?? 0) === 0) {
|
|
3497
|
+
sqliteExec(this.db, `
|
|
3498
|
+
INSERT OR IGNORE INTO consolidated_memory_events (memory_id, event_id)
|
|
3499
|
+
SELECT cm.memory_id, je.value
|
|
3500
|
+
FROM consolidated_memories cm, json_each(cm.source_events) je
|
|
3501
|
+
WHERE json_valid(cm.source_events);
|
|
3502
|
+
`);
|
|
3471
3503
|
}
|
|
3504
|
+
sqliteExec(this.db, `PRAGMA user_version = ${SQLITE_SCHEMA_VERSION}`);
|
|
3472
3505
|
this.initialized = true;
|
|
3473
3506
|
}
|
|
3474
3507
|
/**
|
|
@@ -3600,6 +3633,67 @@ var SQLiteEventStore = class {
|
|
|
3600
3633
|
);
|
|
3601
3634
|
return rows.map(this.rowToEvent);
|
|
3602
3635
|
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Aggregate event counts by type via SQL GROUP BY, instead of loading rows
|
|
3638
|
+
* into memory and counting in JS. Counts all events (the old recent-window
|
|
3639
|
+
* scan also under-counted stores larger than its cap).
|
|
3640
|
+
*/
|
|
3641
|
+
async getEventTypeCounts(options) {
|
|
3642
|
+
await this.initialize();
|
|
3643
|
+
const rows = sqliteAll(
|
|
3644
|
+
this.db,
|
|
3645
|
+
`SELECT event_type, COUNT(*) as count FROM events WHERE ${maybeQuarantinePredicate(options)} GROUP BY event_type`,
|
|
3646
|
+
[]
|
|
3647
|
+
);
|
|
3648
|
+
return rows.map((row) => ({ eventType: row.event_type, count: row.count }));
|
|
3649
|
+
}
|
|
3650
|
+
/**
|
|
3651
|
+
* Fetch every event at/after an ISO timestamp (ascending), with no row cap.
|
|
3652
|
+
* For window-scoped analytics (KPI/usefulness) this fetches exactly the events
|
|
3653
|
+
* the window needs instead of an arbitrary "most recent N" slice that both
|
|
3654
|
+
* over-fetches sparse windows and truncates very active ones.
|
|
3655
|
+
*/
|
|
3656
|
+
async getEventsAfter(sinceIso, options) {
|
|
3657
|
+
await this.initialize();
|
|
3658
|
+
const rows = sqliteAll(
|
|
3659
|
+
this.db,
|
|
3660
|
+
`SELECT * FROM events WHERE timestamp >= ? AND ${maybeQuarantinePredicate(options)} ORDER BY timestamp ASC`,
|
|
3661
|
+
[sinceIso]
|
|
3662
|
+
);
|
|
3663
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
3664
|
+
}
|
|
3665
|
+
/** Count distinct sessions via SQL instead of materializing a Set in JS. */
|
|
3666
|
+
async getDistinctSessionCount(options) {
|
|
3667
|
+
await this.initialize();
|
|
3668
|
+
const row = sqliteGet(
|
|
3669
|
+
this.db,
|
|
3670
|
+
`SELECT COUNT(DISTINCT session_id) as count FROM events WHERE ${maybeQuarantinePredicate(options)}`,
|
|
3671
|
+
[]
|
|
3672
|
+
);
|
|
3673
|
+
return row?.count ?? 0;
|
|
3674
|
+
}
|
|
3675
|
+
/**
|
|
3676
|
+
* Per-day event counts (with type breakdown) since an ISO timestamp, computed
|
|
3677
|
+
* by SQL GROUP BY on the date prefix. Timestamps are stored as ISO-8601
|
|
3678
|
+
* strings, so substr(...,1,10) is the UTC day and `timestamp >= ?` compares
|
|
3679
|
+
* lexicographically.
|
|
3680
|
+
*/
|
|
3681
|
+
async getDailyEventCounts(sinceIso, options) {
|
|
3682
|
+
await this.initialize();
|
|
3683
|
+
return sqliteAll(
|
|
3684
|
+
this.db,
|
|
3685
|
+
`SELECT substr(timestamp, 1, 10) as day,
|
|
3686
|
+
COUNT(*) as total,
|
|
3687
|
+
SUM(CASE WHEN event_type = 'user_prompt' THEN 1 ELSE 0 END) as prompts,
|
|
3688
|
+
SUM(CASE WHEN event_type = 'agent_response' THEN 1 ELSE 0 END) as responses,
|
|
3689
|
+
SUM(CASE WHEN event_type = 'tool_observation' THEN 1 ELSE 0 END) as tools
|
|
3690
|
+
FROM events
|
|
3691
|
+
WHERE timestamp >= ? AND ${maybeQuarantinePredicate(options)}
|
|
3692
|
+
GROUP BY day
|
|
3693
|
+
ORDER BY day ASC`,
|
|
3694
|
+
[sinceIso]
|
|
3695
|
+
);
|
|
3696
|
+
}
|
|
3603
3697
|
/**
|
|
3604
3698
|
* Get event by ID
|
|
3605
3699
|
*/
|
|
@@ -3613,6 +3707,71 @@ var SQLiteEventStore = class {
|
|
|
3613
3707
|
if (!row) return null;
|
|
3614
3708
|
return this.rowToEvent(row);
|
|
3615
3709
|
}
|
|
3710
|
+
/**
|
|
3711
|
+
* Batch-fetch events by id in a single query, applying the same quarantine
|
|
3712
|
+
* predicate as getEvent. Lets hot paths replace N sequential round-trips with
|
|
3713
|
+
* one. Missing ids are simply absent from the result (order not guaranteed).
|
|
3714
|
+
*/
|
|
3715
|
+
async getEvents(ids, options) {
|
|
3716
|
+
await this.initialize();
|
|
3717
|
+
if (ids.length === 0) return [];
|
|
3718
|
+
const placeholders2 = ids.map(() => "?").join(",");
|
|
3719
|
+
const rows = sqliteAll(
|
|
3720
|
+
this.db,
|
|
3721
|
+
`SELECT * FROM events WHERE id IN (${placeholders2}) AND ${maybeQuarantinePredicate(options)}`,
|
|
3722
|
+
ids
|
|
3723
|
+
);
|
|
3724
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Resolve an event from its citation id via the reverse index, instead of
|
|
3728
|
+
* scanning recent events and hashing each one. The index is self-healing: a
|
|
3729
|
+
* miss triggers a one-time backfill of any events not yet indexed (covering
|
|
3730
|
+
* freshly appended events) before retrying.
|
|
3731
|
+
*/
|
|
3732
|
+
async getEventByCitationId(citationId, options) {
|
|
3733
|
+
await this.initialize();
|
|
3734
|
+
if (!citationId) return null;
|
|
3735
|
+
let row = sqliteGet(
|
|
3736
|
+
this.db,
|
|
3737
|
+
`SELECT event_id FROM event_citations WHERE citation_id = ? LIMIT 1`,
|
|
3738
|
+
[citationId]
|
|
3739
|
+
);
|
|
3740
|
+
if (!row) {
|
|
3741
|
+
this.indexMissingCitations();
|
|
3742
|
+
row = sqliteGet(
|
|
3743
|
+
this.db,
|
|
3744
|
+
`SELECT event_id FROM event_citations WHERE citation_id = ? LIMIT 1`,
|
|
3745
|
+
[citationId]
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
if (!row) return null;
|
|
3749
|
+
return this.getEvent(row.event_id, options);
|
|
3750
|
+
}
|
|
3751
|
+
/**
|
|
3752
|
+
* Backfill citation ids for any events not yet present in event_citations.
|
|
3753
|
+
* Gated on a cheap count comparison so the common (fully-indexed) path does no
|
|
3754
|
+
* work, and the scan only runs when there are genuinely new events to index.
|
|
3755
|
+
*/
|
|
3756
|
+
indexMissingCitations() {
|
|
3757
|
+
const eventCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM events`, [])?.c ?? 0;
|
|
3758
|
+
const indexedCount = sqliteGet(this.db, `SELECT COUNT(*) as c FROM event_citations`, [])?.c ?? 0;
|
|
3759
|
+
if (indexedCount >= eventCount) return;
|
|
3760
|
+
const unindexed = sqliteAll(
|
|
3761
|
+
this.db,
|
|
3762
|
+
`SELECT e.id FROM events e
|
|
3763
|
+
LEFT JOIN event_citations ec ON ec.event_id = e.id
|
|
3764
|
+
WHERE ec.event_id IS NULL`,
|
|
3765
|
+
[]
|
|
3766
|
+
);
|
|
3767
|
+
if (unindexed.length === 0) return;
|
|
3768
|
+
const insert = this.db.prepare(`INSERT OR IGNORE INTO event_citations (event_id, citation_id) VALUES (?, ?)`);
|
|
3769
|
+
this.db.transaction(() => {
|
|
3770
|
+
for (const { id } of unindexed) {
|
|
3771
|
+
insert.run(id, generateCitationId(id));
|
|
3772
|
+
}
|
|
3773
|
+
})();
|
|
3774
|
+
}
|
|
3616
3775
|
/**
|
|
3617
3776
|
* Get events since a timestamp (for sync)
|
|
3618
3777
|
*/
|
|
@@ -3814,25 +3973,22 @@ var SQLiteEventStore = class {
|
|
|
3814
3973
|
*/
|
|
3815
3974
|
async getPendingOutboxItems(limit = 32) {
|
|
3816
3975
|
await this.initialize();
|
|
3817
|
-
const
|
|
3818
|
-
this.db,
|
|
3819
|
-
`SELECT * FROM embedding_outbox
|
|
3820
|
-
WHERE status = 'pending'
|
|
3821
|
-
ORDER BY created_at
|
|
3822
|
-
LIMIT ?`,
|
|
3823
|
-
[limit]
|
|
3824
|
-
);
|
|
3825
|
-
if (pending.length === 0) return [];
|
|
3826
|
-
const ids = pending.map((r) => r.id);
|
|
3827
|
-
const placeholders2 = ids.map(() => "?").join(",");
|
|
3828
|
-
sqliteRun(
|
|
3976
|
+
const claimed = sqliteAll(
|
|
3829
3977
|
this.db,
|
|
3830
3978
|
`UPDATE embedding_outbox
|
|
3831
3979
|
SET status = 'processing', processed_at = datetime('now'), error_message = NULL
|
|
3832
|
-
WHERE id IN (
|
|
3833
|
-
|
|
3980
|
+
WHERE id IN (
|
|
3981
|
+
SELECT id FROM embedding_outbox
|
|
3982
|
+
WHERE status = 'pending'
|
|
3983
|
+
ORDER BY created_at
|
|
3984
|
+
LIMIT ?
|
|
3985
|
+
)
|
|
3986
|
+
RETURNING *`,
|
|
3987
|
+
[limit]
|
|
3834
3988
|
);
|
|
3835
|
-
|
|
3989
|
+
if (claimed.length === 0) return [];
|
|
3990
|
+
claimed.sort((a, b) => String(a.created_at).localeCompare(String(b.created_at)));
|
|
3991
|
+
return claimed.map((row) => ({
|
|
3836
3992
|
id: row.id,
|
|
3837
3993
|
eventId: row.event_id,
|
|
3838
3994
|
content: row.content,
|
|
@@ -4436,7 +4592,7 @@ var SQLiteEventStore = class {
|
|
|
4436
4592
|
const promptEvents = sessionEvents.filter((e) => e.event_type === "user_prompt");
|
|
4437
4593
|
const toolEvents = sessionEvents.filter((e) => e.event_type === "tool_observation");
|
|
4438
4594
|
let toolSuccessCount = 0;
|
|
4439
|
-
|
|
4595
|
+
const toolTotalCount = toolEvents.length;
|
|
4440
4596
|
for (const t of toolEvents) {
|
|
4441
4597
|
try {
|
|
4442
4598
|
const content = JSON.parse(t.content);
|
|
@@ -4599,10 +4755,14 @@ var SQLiteEventStore = class {
|
|
|
4599
4755
|
* Rebuild FTS index from existing events
|
|
4600
4756
|
* Call this once after upgrading to FTS5
|
|
4601
4757
|
*/
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4758
|
+
/**
|
|
4759
|
+
* Drop and recreate the events_fts virtual table + sync triggers, repopulating
|
|
4760
|
+
* from the current events table. Recreating the table (instead of issuing
|
|
4761
|
+
* DELETEs against it) sidesteps a quirk where older migrated FTS5 tables fail
|
|
4762
|
+
* with `no such column: T.event_id` on synthetic deletes. Safe to call inside
|
|
4763
|
+
* a transaction (pure DDL + INSERT...SELECT, no transaction control).
|
|
4764
|
+
*/
|
|
4765
|
+
recreateEventsFtsTable() {
|
|
4606
4766
|
sqliteExec(this.db, `
|
|
4607
4767
|
DROP TRIGGER IF EXISTS events_fts_insert;
|
|
4608
4768
|
DROP TRIGGER IF EXISTS events_fts_delete;
|
|
@@ -4631,6 +4791,12 @@ var SQLiteEventStore = class {
|
|
|
4631
4791
|
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
4632
4792
|
END;
|
|
4633
4793
|
`);
|
|
4794
|
+
}
|
|
4795
|
+
async rebuildFtsIndex() {
|
|
4796
|
+
await this.initialize();
|
|
4797
|
+
const countRow = sqliteGet(this.db, "SELECT COUNT(*) as count FROM events", []);
|
|
4798
|
+
const totalEvents = countRow?.count ?? 0;
|
|
4799
|
+
this.recreateEventsFtsTable();
|
|
4634
4800
|
return totalEvents;
|
|
4635
4801
|
}
|
|
4636
4802
|
/**
|
|
@@ -4648,6 +4814,35 @@ var SQLiteEventStore = class {
|
|
|
4648
4814
|
return false;
|
|
4649
4815
|
}
|
|
4650
4816
|
}
|
|
4817
|
+
hasTable(tableName) {
|
|
4818
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tableName)) return false;
|
|
4819
|
+
try {
|
|
4820
|
+
const rows = sqliteAll(
|
|
4821
|
+
this.db,
|
|
4822
|
+
`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`,
|
|
4823
|
+
[tableName]
|
|
4824
|
+
);
|
|
4825
|
+
return rows.length > 0;
|
|
4826
|
+
} catch {
|
|
4827
|
+
return false;
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
/**
|
|
4831
|
+
* Add a column only when its table exists and the column is missing.
|
|
4832
|
+
*
|
|
4833
|
+
* Unlike a blind `try { ALTER } catch {}`, this distinguishes "already
|
|
4834
|
+
* migrated" / "table not present yet" (both safely skipped) from a genuine
|
|
4835
|
+
* failure (disk full, corruption), which is allowed to surface instead of
|
|
4836
|
+
* being silently swallowed as if the migration had succeeded.
|
|
4837
|
+
*/
|
|
4838
|
+
addColumnIfMissing(table, column, definition) {
|
|
4839
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(column)) {
|
|
4840
|
+
throw new Error(`Invalid identifier in migration: ${table}.${column}`);
|
|
4841
|
+
}
|
|
4842
|
+
if (!this.hasTable(table)) return;
|
|
4843
|
+
if (this.hasTableColumn(table, column)) return;
|
|
4844
|
+
sqliteExec(this.db, `ALTER TABLE ${table} ADD COLUMN ${column} ${definition};`);
|
|
4845
|
+
}
|
|
4651
4846
|
async recordRetrievalTrace(input) {
|
|
4652
4847
|
await this.initialize();
|
|
4653
4848
|
const traceId = randomUUID6();
|
|
@@ -4733,6 +4928,45 @@ var SQLiteEventStore = class {
|
|
|
4733
4928
|
FROM retrieval_traces`,
|
|
4734
4929
|
[]
|
|
4735
4930
|
);
|
|
4931
|
+
const strategyColumnSql = this.hasTableColumn("retrieval_traces", "strategy") ? "COALESCE(NULLIF(TRIM(strategy), ''), 'unknown')" : "'unknown'";
|
|
4932
|
+
const strategyRows = sqliteAll(
|
|
4933
|
+
this.db,
|
|
4934
|
+
`SELECT
|
|
4935
|
+
${strategyColumnSql} as strategy,
|
|
4936
|
+
COUNT(*) as total_queries,
|
|
4937
|
+
SUM(CASE WHEN selected_count > 0 THEN 1 ELSE 0 END) as queries_with_selection,
|
|
4938
|
+
SUM(CASE WHEN ${rewrittenQueryRewriteKindSql} THEN 1 ELSE 0 END) as rewritten_queries,
|
|
4939
|
+
SUM(candidate_count) as total_candidate_count,
|
|
4940
|
+
SUM(selected_count) as total_selected_count,
|
|
4941
|
+
AVG(candidate_count) as avg_candidate_count,
|
|
4942
|
+
AVG(selected_count) as avg_selected_count,
|
|
4943
|
+
CASE
|
|
4944
|
+
WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
|
|
4945
|
+
ELSE 0
|
|
4946
|
+
END as selection_rate
|
|
4947
|
+
FROM retrieval_traces
|
|
4948
|
+
GROUP BY ${strategyColumnSql}
|
|
4949
|
+
ORDER BY total_queries DESC, strategy ASC`,
|
|
4950
|
+
[]
|
|
4951
|
+
);
|
|
4952
|
+
const strategyBreakdown = strategyRows.map((strategyRow) => {
|
|
4953
|
+
const strategyTotalQueries = Number(strategyRow.total_queries || 0);
|
|
4954
|
+
const strategyQueriesWithSelection = Number(strategyRow.queries_with_selection || 0);
|
|
4955
|
+
const strategyRewrittenQueries = Number(strategyRow.rewritten_queries || 0);
|
|
4956
|
+
return {
|
|
4957
|
+
strategy: String(strategyRow.strategy || "unknown"),
|
|
4958
|
+
totalQueries: strategyTotalQueries,
|
|
4959
|
+
queriesWithSelection: strategyQueriesWithSelection,
|
|
4960
|
+
rewrittenQueries: strategyRewrittenQueries,
|
|
4961
|
+
rewriteRate: strategyTotalQueries > 0 ? strategyRewrittenQueries / strategyTotalQueries : 0,
|
|
4962
|
+
totalCandidateCount: Number(strategyRow.total_candidate_count || 0),
|
|
4963
|
+
totalSelectedCount: Number(strategyRow.total_selected_count || 0),
|
|
4964
|
+
avgCandidateCount: Number(strategyRow.avg_candidate_count || 0),
|
|
4965
|
+
avgSelectedCount: Number(strategyRow.avg_selected_count || 0),
|
|
4966
|
+
selectionRate: Number(strategyRow.selection_rate || 0),
|
|
4967
|
+
queryYieldRate: strategyTotalQueries > 0 ? strategyQueriesWithSelection / strategyTotalQueries : 0
|
|
4968
|
+
};
|
|
4969
|
+
});
|
|
4736
4970
|
const totalQueries = Number(row?.total_queries || 0);
|
|
4737
4971
|
const rewrittenQueries = Number(row?.rewritten_queries || 0);
|
|
4738
4972
|
const rawQueries = Math.max(0, totalQueries - rewrittenQueries);
|
|
@@ -4750,7 +4984,8 @@ var SQLiteEventStore = class {
|
|
|
4750
4984
|
rewrittenSelectionRate: rewrittenQueries > 0 ? rewrittenQueriesWithSelection / rewrittenQueries : 0,
|
|
4751
4985
|
rawSelectionRate: rawQueries > 0 ? rawQueriesWithSelection / rawQueries : 0,
|
|
4752
4986
|
avgSelectedCountForRewrittenQueries: Number(row?.avg_selected_count_for_rewritten_queries || 0),
|
|
4753
|
-
avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0)
|
|
4987
|
+
avgSelectedCountForRawQueries: Number(row?.avg_selected_count_for_raw_queries || 0),
|
|
4988
|
+
strategyBreakdown
|
|
4754
4989
|
};
|
|
4755
4990
|
} catch (err) {
|
|
4756
4991
|
if (err?.message?.includes("no such table")) {
|
|
@@ -4766,7 +5001,8 @@ var SQLiteEventStore = class {
|
|
|
4766
5001
|
rewrittenSelectionRate: 0,
|
|
4767
5002
|
rawSelectionRate: 0,
|
|
4768
5003
|
avgSelectedCountForRewrittenQueries: 0,
|
|
4769
|
-
avgSelectedCountForRawQueries: 0
|
|
5004
|
+
avgSelectedCountForRawQueries: 0,
|
|
5005
|
+
strategyBreakdown: []
|
|
4770
5006
|
};
|
|
4771
5007
|
}
|
|
4772
5008
|
throw err;
|
|
@@ -4883,38 +5119,19 @@ var SQLiteEventStore = class {
|
|
|
4883
5119
|
if (events.length === 0) return 0;
|
|
4884
5120
|
const eventIds = events.map((e) => e.id);
|
|
4885
5121
|
const placeholders2 = eventIds.map(() => "?").join(",");
|
|
4886
|
-
const
|
|
4887
|
-
|
|
4888
|
-
|
|
5122
|
+
const relatedTables = ["event_dedup", "memory_levels", "embedding_queue", "embedding_outbox", "vector_outbox", "event_citations"].filter((table) => this.hasTable(table) && this.hasTableColumn(table, "event_id"));
|
|
5123
|
+
const runDelete = this.db.transaction(() => {
|
|
5124
|
+
for (const triggerName of ["events_fts_delete", "events_fts_update", "events_fts_insert"]) {
|
|
4889
5125
|
sqliteRun(this.db, `DROP TRIGGER IF EXISTS ${triggerName}`);
|
|
4890
|
-
ftsTriggersDropped.push(triggerName);
|
|
4891
|
-
} catch {
|
|
4892
5126
|
}
|
|
4893
|
-
|
|
4894
|
-
for (const table of ["event_dedup", "memory_levels", "embedding_queue", "embedding_outbox", "vector_outbox"]) {
|
|
4895
|
-
try {
|
|
5127
|
+
for (const table of relatedTables) {
|
|
4896
5128
|
sqliteRun(this.db, `DELETE FROM ${table} WHERE event_id IN (${placeholders2})`, eventIds);
|
|
4897
|
-
} catch {
|
|
4898
5129
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN
|
|
4905
|
-
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
4906
|
-
END`);
|
|
4907
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
|
|
4908
|
-
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
4909
|
-
END`);
|
|
4910
|
-
sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
|
|
4911
|
-
DELETE FROM events_fts WHERE rowid = OLD.rowid;
|
|
4912
|
-
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
4913
|
-
END`);
|
|
4914
|
-
} catch {
|
|
4915
|
-
}
|
|
4916
|
-
}
|
|
4917
|
-
return result.changes || 0;
|
|
5130
|
+
const result = sqliteRun(this.db, `DELETE FROM events WHERE session_id = ?`, [sessionId]);
|
|
5131
|
+
this.recreateEventsFtsTable();
|
|
5132
|
+
return result.changes || 0;
|
|
5133
|
+
});
|
|
5134
|
+
return runDelete();
|
|
4918
5135
|
}
|
|
4919
5136
|
/**
|
|
4920
5137
|
* Convert database row to MemoryEvent
|
|
@@ -7678,9 +7895,9 @@ function safeParseObject(value) {
|
|
|
7678
7895
|
return void 0;
|
|
7679
7896
|
}
|
|
7680
7897
|
}
|
|
7681
|
-
function nestedString(root,
|
|
7898
|
+
function nestedString(root, path26) {
|
|
7682
7899
|
let cursor = root;
|
|
7683
|
-
for (const key of
|
|
7900
|
+
for (const key of path26) {
|
|
7684
7901
|
if (!isRecord2(cursor)) return void 0;
|
|
7685
7902
|
cursor = cursor[key];
|
|
7686
7903
|
}
|
|
@@ -7755,12 +7972,12 @@ var GraphPathService = class {
|
|
|
7755
7972
|
}
|
|
7756
7973
|
}
|
|
7757
7974
|
}
|
|
7758
|
-
const paths = Array.from(bestByTarget.entries()).map(([key,
|
|
7975
|
+
const paths = Array.from(bestByTarget.entries()).map(([key, path26]) => ({
|
|
7759
7976
|
target: graph.node(nodeFromKey(key)),
|
|
7760
|
-
hops:
|
|
7761
|
-
totalCost:
|
|
7762
|
-
scoreContribution:
|
|
7763
|
-
steps:
|
|
7977
|
+
hops: path26.hops,
|
|
7978
|
+
totalCost: path26.totalCost,
|
|
7979
|
+
scoreContribution: path26.totalCost > 0 ? 1 / path26.totalCost : 0,
|
|
7980
|
+
steps: path26.steps
|
|
7764
7981
|
})).sort((a, b) => b.scoreContribution - a.scoreContribution || a.hops - b.hops || a.target.name.localeCompare(b.target.name)).slice(0, maxResults);
|
|
7765
7982
|
return { startNodes, effectiveMaxHops, paths };
|
|
7766
7983
|
}
|
|
@@ -8537,7 +8754,7 @@ var SessionActorRepository = class {
|
|
|
8537
8754
|
};
|
|
8538
8755
|
|
|
8539
8756
|
// src/core/operations/perspective-observation-repository.ts
|
|
8540
|
-
import { createHash as
|
|
8757
|
+
import { createHash as createHash4, randomUUID as randomUUID11 } from "crypto";
|
|
8541
8758
|
function projectHashToStorage4(projectHash) {
|
|
8542
8759
|
return projectHash ?? "";
|
|
8543
8760
|
}
|
|
@@ -8574,7 +8791,7 @@ function sanitizeStoredRecord(value) {
|
|
|
8574
8791
|
return sanitizeGovernanceAuditValue(value);
|
|
8575
8792
|
}
|
|
8576
8793
|
function stableHash(value) {
|
|
8577
|
-
return
|
|
8794
|
+
return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
8578
8795
|
}
|
|
8579
8796
|
function evidenceHash(sourceEventIds, sourceObservationIds) {
|
|
8580
8797
|
return stableHash({
|
|
@@ -9082,23 +9299,6 @@ function createPerspectiveDeriver(options) {
|
|
|
9082
9299
|
return new PerspectiveDeriver(options);
|
|
9083
9300
|
}
|
|
9084
9301
|
|
|
9085
|
-
// src/core/citation-generator.ts
|
|
9086
|
-
import { createHash as createHash4 } from "crypto";
|
|
9087
|
-
var ID_LENGTH = 6;
|
|
9088
|
-
var CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
9089
|
-
function generateCitationId(eventId) {
|
|
9090
|
-
const hash = createHash4("sha256").update(eventId).digest();
|
|
9091
|
-
let id = "";
|
|
9092
|
-
for (let i = 0; i < ID_LENGTH; i++) {
|
|
9093
|
-
id += CHARSET[hash[i] % CHARSET.length];
|
|
9094
|
-
}
|
|
9095
|
-
return id;
|
|
9096
|
-
}
|
|
9097
|
-
function parseCitationId(formatted) {
|
|
9098
|
-
const match = formatted.match(/\[?mem:([A-Za-z0-9]{6})\]?/);
|
|
9099
|
-
return match ? match[1] : null;
|
|
9100
|
-
}
|
|
9101
|
-
|
|
9102
9302
|
// src/core/operations/perspective-session-actor-backfill.ts
|
|
9103
9303
|
var DEFAULT_EVENT_LIMIT = 1e4;
|
|
9104
9304
|
var DEFAULT_SAMPLE_LIMIT2 = 10;
|
|
@@ -9892,6 +10092,30 @@ var MemoryQueryService = class {
|
|
|
9892
10092
|
score: 1 - (r.rank - minRank) / rankRange
|
|
9893
10093
|
})).filter((r) => !options?.minScore || r.score >= options.minScore);
|
|
9894
10094
|
}
|
|
10095
|
+
async getEvent(id) {
|
|
10096
|
+
await this.initialize();
|
|
10097
|
+
return this.queryStore.getEvent(id);
|
|
10098
|
+
}
|
|
10099
|
+
async getEventByCitationId(citationId) {
|
|
10100
|
+
await this.initialize();
|
|
10101
|
+
return this.getMaintenanceStore("getEventByCitationId").getEventByCitationId(citationId);
|
|
10102
|
+
}
|
|
10103
|
+
async getEventsAfter(sinceIso) {
|
|
10104
|
+
await this.initialize();
|
|
10105
|
+
return this.getMaintenanceStore("getEventsAfter").getEventsAfter(sinceIso);
|
|
10106
|
+
}
|
|
10107
|
+
async getEventTypeCounts() {
|
|
10108
|
+
await this.initialize();
|
|
10109
|
+
return this.getMaintenanceStore("getEventTypeCounts").getEventTypeCounts();
|
|
10110
|
+
}
|
|
10111
|
+
async getDistinctSessionCount() {
|
|
10112
|
+
await this.initialize();
|
|
10113
|
+
return this.getMaintenanceStore("getDistinctSessionCount").getDistinctSessionCount();
|
|
10114
|
+
}
|
|
10115
|
+
async getDailyEventCounts(sinceIso) {
|
|
10116
|
+
await this.initialize();
|
|
10117
|
+
return this.getMaintenanceStore("getDailyEventCounts").getDailyEventCounts(sinceIso);
|
|
10118
|
+
}
|
|
9895
10119
|
async getSessionHistory(sessionId) {
|
|
9896
10120
|
await this.initialize();
|
|
9897
10121
|
return this.queryStore.getSessionEvents(sessionId);
|
|
@@ -10716,12 +10940,12 @@ var Retriever = class {
|
|
|
10716
10940
|
direction: "both"
|
|
10717
10941
|
});
|
|
10718
10942
|
const titleByEntityId = new Map(startNodes.map((node) => [node.entityId, node.title]));
|
|
10719
|
-
for (const
|
|
10720
|
-
if (
|
|
10721
|
-
const target = await this.eventStore.getEvent(
|
|
10943
|
+
for (const path26 of expansion.paths) {
|
|
10944
|
+
if (path26.target.type !== "event") continue;
|
|
10945
|
+
const target = await this.eventStore.getEvent(path26.target.id);
|
|
10722
10946
|
if (!target) continue;
|
|
10723
|
-
const graphPath = toRetrievalGraphPathDebug(
|
|
10724
|
-
const score = graphPathScore(
|
|
10947
|
+
const graphPath = toRetrievalGraphPathDebug(path26, titleByEntityId);
|
|
10948
|
+
const score = graphPathScore(path26, opts.hopPenalty);
|
|
10725
10949
|
const existing = byId.get(target.id);
|
|
10726
10950
|
const graphPaths = mergeGraphPaths(existing?.graphPaths ?? [], [graphPath]);
|
|
10727
10951
|
const graphLane = {
|
|
@@ -10730,7 +10954,7 @@ var Retriever = class {
|
|
|
10730
10954
|
score
|
|
10731
10955
|
};
|
|
10732
10956
|
const row = {
|
|
10733
|
-
id: existing?.id ?? `graph-path-${
|
|
10957
|
+
id: existing?.id ?? `graph-path-${path26.hops}-${target.id}`,
|
|
10734
10958
|
eventId: target.id,
|
|
10735
10959
|
content: target.content,
|
|
10736
10960
|
score: Math.max(existing?.score ?? 0, score),
|
|
@@ -10860,11 +11084,12 @@ var Retriever = class {
|
|
|
10860
11084
|
if (!scope && projectScopeMode === "global" && facetFilters === null) return results;
|
|
10861
11085
|
const normalizedIncludes = (scope?.contentIncludes || []).map((s) => s.toLowerCase());
|
|
10862
11086
|
const filtered = [];
|
|
11087
|
+
const eventMap = await this.fetchEventsByIds(results.map((r) => r.eventId));
|
|
10863
11088
|
for (const result of results) {
|
|
10864
11089
|
if (scope?.sessionId && result.sessionId !== scope.sessionId) continue;
|
|
10865
11090
|
if (scope?.sessionIdPrefix && !result.sessionId.startsWith(scope.sessionIdPrefix)) continue;
|
|
10866
11091
|
if (scope?.eventTypes && scope.eventTypes.length > 0 && !scope.eventTypes.includes(result.eventType)) continue;
|
|
10867
|
-
const event =
|
|
11092
|
+
const event = eventMap.get(result.eventId);
|
|
10868
11093
|
if (!event) continue;
|
|
10869
11094
|
if (scope?.canonicalKeyPrefix && !event.canonicalKey.startsWith(scope.canonicalKeyPrefix)) continue;
|
|
10870
11095
|
if (normalizedIncludes.length > 0) {
|
|
@@ -10973,10 +11198,30 @@ var Retriever = class {
|
|
|
10973
11198
|
async retrieveRecent(limit = 100) {
|
|
10974
11199
|
return this.eventStore.getRecentEvents(limit);
|
|
10975
11200
|
}
|
|
11201
|
+
/**
|
|
11202
|
+
* Batch-load events by id into a Map in a single query, de-duplicating ids.
|
|
11203
|
+
* Replaces per-result getEvent round-trips in the scope/enrich hot paths.
|
|
11204
|
+
*/
|
|
11205
|
+
async fetchEventsByIds(ids) {
|
|
11206
|
+
const map = /* @__PURE__ */ new Map();
|
|
11207
|
+
if (ids.length === 0) return map;
|
|
11208
|
+
const unique2 = [...new Set(ids)];
|
|
11209
|
+
if (typeof this.eventStore.getEvents === "function") {
|
|
11210
|
+
const events = await this.eventStore.getEvents(unique2);
|
|
11211
|
+
for (const event of events) map.set(event.id, event);
|
|
11212
|
+
} else {
|
|
11213
|
+
const events = await Promise.all(unique2.map((id) => this.eventStore.getEvent(id)));
|
|
11214
|
+
for (const event of events) {
|
|
11215
|
+
if (event) map.set(event.id, event);
|
|
11216
|
+
}
|
|
11217
|
+
}
|
|
11218
|
+
return map;
|
|
11219
|
+
}
|
|
10976
11220
|
async enrichResults(results, options, query) {
|
|
10977
11221
|
const memories = [];
|
|
11222
|
+
const eventMap = await this.fetchEventsByIds(results.map((r) => r.eventId));
|
|
10978
11223
|
for (const result of results) {
|
|
10979
|
-
const event =
|
|
11224
|
+
const event = eventMap.get(result.eventId);
|
|
10980
11225
|
if (!event) continue;
|
|
10981
11226
|
if (this.graduation) {
|
|
10982
11227
|
this.graduation.recordAccess(event.id, options.sessionId || "unknown", result.score);
|
|
@@ -11049,8 +11294,8 @@ _Context:_ ${sessionContext}`;
|
|
|
11049
11294
|
}
|
|
11050
11295
|
matchesMetadataScope(metadata, expected) {
|
|
11051
11296
|
if (!metadata) return false;
|
|
11052
|
-
return Object.entries(expected).every(([
|
|
11053
|
-
const actual =
|
|
11297
|
+
return Object.entries(expected).every(([path26, value]) => {
|
|
11298
|
+
const actual = path26.split(".").reduce((acc, key) => {
|
|
11054
11299
|
if (typeof acc !== "object" || acc === null) return void 0;
|
|
11055
11300
|
return acc[key];
|
|
11056
11301
|
}, metadata);
|
|
@@ -11101,22 +11346,22 @@ function uniqueEntityStartNodes(candidates) {
|
|
|
11101
11346
|
}
|
|
11102
11347
|
return nodes;
|
|
11103
11348
|
}
|
|
11104
|
-
function toRetrievalGraphPathDebug(
|
|
11105
|
-
const firstStep =
|
|
11349
|
+
function toRetrievalGraphPathDebug(path26, titleByEntityId) {
|
|
11350
|
+
const firstStep = path26.steps[0];
|
|
11106
11351
|
const startNode = firstStep?.direction === "incoming" ? firstStep.to : firstStep?.from;
|
|
11107
11352
|
const startEntityId = startNode?.type === "entity" ? startNode.id : "";
|
|
11108
11353
|
return {
|
|
11109
11354
|
startEntityId,
|
|
11110
11355
|
startEntityTitle: titleByEntityId.get(startEntityId) ?? startNode?.name,
|
|
11111
|
-
targetId:
|
|
11112
|
-
targetType:
|
|
11113
|
-
hops:
|
|
11114
|
-
relationPath:
|
|
11356
|
+
targetId: path26.target.id,
|
|
11357
|
+
targetType: path26.target.type,
|
|
11358
|
+
hops: path26.hops,
|
|
11359
|
+
relationPath: path26.steps.map((step) => step.relationType)
|
|
11115
11360
|
};
|
|
11116
11361
|
}
|
|
11117
|
-
function graphPathScore(
|
|
11118
|
-
const base = Math.min(0.95, Math.max(0,
|
|
11119
|
-
return Math.max(0.05, base - hopPenalty * Math.max(0,
|
|
11362
|
+
function graphPathScore(path26, hopPenalty) {
|
|
11363
|
+
const base = Math.min(0.95, Math.max(0, path26.scoreContribution));
|
|
11364
|
+
return Math.max(0.05, base - hopPenalty * Math.max(0, path26.hops - 1));
|
|
11120
11365
|
}
|
|
11121
11366
|
function clampGraphHops(maxHops) {
|
|
11122
11367
|
if (!Number.isFinite(maxHops)) return 2;
|
|
@@ -11124,14 +11369,14 @@ function clampGraphHops(maxHops) {
|
|
|
11124
11369
|
}
|
|
11125
11370
|
function mergeGraphPaths(existing, incoming) {
|
|
11126
11371
|
const byKey = /* @__PURE__ */ new Map();
|
|
11127
|
-
for (const
|
|
11128
|
-
const key = [
|
|
11129
|
-
if (!byKey.has(key)) byKey.set(key,
|
|
11372
|
+
for (const path26 of [...existing, ...incoming]) {
|
|
11373
|
+
const key = [path26.startEntityId, path26.targetType, path26.targetId, path26.hops, ...path26.relationPath].join("\0");
|
|
11374
|
+
if (!byKey.has(key)) byKey.set(key, path26);
|
|
11130
11375
|
}
|
|
11131
11376
|
return [...byKey.values()].sort((a, b) => a.hops - b.hops || compareStable(graphPathSignature(a), graphPathSignature(b))).slice(0, 3);
|
|
11132
11377
|
}
|
|
11133
|
-
function graphPathSignature(
|
|
11134
|
-
return [
|
|
11378
|
+
function graphPathSignature(path26) {
|
|
11379
|
+
return [path26.startEntityId, path26.targetType, path26.targetId, path26.hops, ...path26.relationPath].join("|");
|
|
11135
11380
|
}
|
|
11136
11381
|
function compareStable(a, b) {
|
|
11137
11382
|
if (a < b) return -1;
|
|
@@ -13730,19 +13975,47 @@ var MemoryService = class {
|
|
|
13730
13975
|
return this.queryService.rebuildFtsIndex();
|
|
13731
13976
|
}
|
|
13732
13977
|
/**
|
|
13733
|
-
* Get
|
|
13978
|
+
* Get a single event by id (indexed lookup).
|
|
13734
13979
|
*/
|
|
13735
|
-
async
|
|
13736
|
-
return this.queryService.
|
|
13980
|
+
async getEvent(id) {
|
|
13981
|
+
return this.queryService.getEvent(id);
|
|
13737
13982
|
}
|
|
13738
13983
|
/**
|
|
13739
|
-
*
|
|
13984
|
+
* Resolve an event from its citation id via the reverse index.
|
|
13740
13985
|
*/
|
|
13741
|
-
async
|
|
13742
|
-
return this.queryService.
|
|
13986
|
+
async getEventByCitationId(citationId) {
|
|
13987
|
+
return this.queryService.getEventByCitationId(citationId);
|
|
13743
13988
|
}
|
|
13744
|
-
/**
|
|
13745
|
-
|
|
13989
|
+
/** All events at/after an ISO timestamp (ascending, uncapped). */
|
|
13990
|
+
async getEventsAfter(sinceIso) {
|
|
13991
|
+
return this.queryService.getEventsAfter(sinceIso);
|
|
13992
|
+
}
|
|
13993
|
+
/** Aggregate event counts by type (SQL GROUP BY). */
|
|
13994
|
+
async getEventTypeCounts() {
|
|
13995
|
+
return this.queryService.getEventTypeCounts();
|
|
13996
|
+
}
|
|
13997
|
+
/** Count distinct sessions (SQL). */
|
|
13998
|
+
async getDistinctSessionCount() {
|
|
13999
|
+
return this.queryService.getDistinctSessionCount();
|
|
14000
|
+
}
|
|
14001
|
+
/** Per-day event counts with type breakdown since an ISO timestamp (SQL). */
|
|
14002
|
+
async getDailyEventCounts(sinceIso) {
|
|
14003
|
+
return this.queryService.getDailyEventCounts(sinceIso);
|
|
14004
|
+
}
|
|
14005
|
+
/**
|
|
14006
|
+
* Get session history
|
|
14007
|
+
*/
|
|
14008
|
+
async getSessionHistory(sessionId) {
|
|
14009
|
+
return this.queryService.getSessionHistory(sessionId);
|
|
14010
|
+
}
|
|
14011
|
+
/**
|
|
14012
|
+
* Get recent events
|
|
14013
|
+
*/
|
|
14014
|
+
async getRecentEvents(limit = 100) {
|
|
14015
|
+
return this.queryService.getRecentEvents(limit);
|
|
14016
|
+
}
|
|
14017
|
+
/**
|
|
14018
|
+
* Get memory statistics
|
|
13746
14019
|
*/
|
|
13747
14020
|
async getOutboxStats(options) {
|
|
13748
14021
|
return this.queryService.getOutboxStats(options);
|
|
@@ -14058,6 +14331,20 @@ import * as path11 from "path";
|
|
|
14058
14331
|
import * as os6 from "os";
|
|
14059
14332
|
import * as readline from "readline";
|
|
14060
14333
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
14334
|
+
|
|
14335
|
+
// src/services/turn-buffering.ts
|
|
14336
|
+
var MIN_SUBSTANTIVE_RESPONSE_LEN = 100;
|
|
14337
|
+
var MAX_AGENT_RESPONSE_LEN = 1e4;
|
|
14338
|
+
function mergeAgentResponseBlocks(blocks) {
|
|
14339
|
+
const substantive = blocks.filter((block) => block.length >= MIN_SUBSTANTIVE_RESPONSE_LEN);
|
|
14340
|
+
if (substantive.length > 0) return substantive.join("\n\n");
|
|
14341
|
+
return blocks.reduce((longest, block) => longest.length >= block.length ? longest : block, "");
|
|
14342
|
+
}
|
|
14343
|
+
function truncateAgentResponse(text) {
|
|
14344
|
+
return text.length > MAX_AGENT_RESPONSE_LEN ? `${text.slice(0, MAX_AGENT_RESPONSE_LEN)}...[truncated]` : text;
|
|
14345
|
+
}
|
|
14346
|
+
|
|
14347
|
+
// src/services/session-history-importer.ts
|
|
14061
14348
|
function isClaudeLocalCommandArtifact(content) {
|
|
14062
14349
|
const trimmed = content.trim();
|
|
14063
14350
|
return /^<local-command-(stdout|stderr)>/.test(trimmed) || /^<command-(name|message)>/.test(trimmed) || trimmed.includes("<command-name>") && trimmed.includes("<local-command-stdout>");
|
|
@@ -14189,6 +14476,34 @@ var SessionHistoryImporter = class {
|
|
|
14189
14476
|
/**
|
|
14190
14477
|
* Import a specific session file
|
|
14191
14478
|
*/
|
|
14479
|
+
/**
|
|
14480
|
+
* Pre-flight check used before a destructive force-reimport: returns true only
|
|
14481
|
+
* if the file can be opened and contains at least one line that parses as JSON.
|
|
14482
|
+
* Streams the file and short-circuits on the first valid record.
|
|
14483
|
+
*/
|
|
14484
|
+
async hasParseableContent(filePath) {
|
|
14485
|
+
let stream;
|
|
14486
|
+
let rl;
|
|
14487
|
+
try {
|
|
14488
|
+
stream = fs9.createReadStream(filePath);
|
|
14489
|
+
rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
14490
|
+
for await (const line of rl) {
|
|
14491
|
+
const trimmed = line.trim();
|
|
14492
|
+
if (!trimmed) continue;
|
|
14493
|
+
try {
|
|
14494
|
+
JSON.parse(trimmed);
|
|
14495
|
+
return true;
|
|
14496
|
+
} catch {
|
|
14497
|
+
}
|
|
14498
|
+
}
|
|
14499
|
+
return false;
|
|
14500
|
+
} catch {
|
|
14501
|
+
return false;
|
|
14502
|
+
} finally {
|
|
14503
|
+
rl?.close();
|
|
14504
|
+
stream?.close();
|
|
14505
|
+
}
|
|
14506
|
+
}
|
|
14192
14507
|
async importSessionFile(filePath, options = {}) {
|
|
14193
14508
|
const result = {
|
|
14194
14509
|
totalSessions: 1,
|
|
@@ -14204,6 +14519,10 @@ var SessionHistoryImporter = class {
|
|
|
14204
14519
|
}
|
|
14205
14520
|
const sessionId = path11.basename(filePath, ".jsonl");
|
|
14206
14521
|
if (options.force) {
|
|
14522
|
+
if (!await this.hasParseableContent(filePath)) {
|
|
14523
|
+
result.errors.push(`Skipped force reimport (no parseable records): ${filePath}`);
|
|
14524
|
+
return result;
|
|
14525
|
+
}
|
|
14207
14526
|
const deleted = await this.memoryService.deleteSessionEvents(sessionId);
|
|
14208
14527
|
if (options.verbose && deleted > 0) {
|
|
14209
14528
|
console.log(` Deleted ${deleted} existing events for session ${sessionId}`);
|
|
@@ -14225,13 +14544,12 @@ var SessionHistoryImporter = class {
|
|
|
14225
14544
|
let lastTimestamp;
|
|
14226
14545
|
const flushTextBuffer = async () => {
|
|
14227
14546
|
if (textBuffer.length === 0 || !currentTurnId) return;
|
|
14228
|
-
const
|
|
14229
|
-
const merged = substantive.length > 0 ? substantive.join("\n\n") : textBuffer.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
14547
|
+
const merged = mergeAgentResponseBlocks(textBuffer);
|
|
14230
14548
|
if (!merged) {
|
|
14231
14549
|
textBuffer = [];
|
|
14232
14550
|
return;
|
|
14233
14551
|
}
|
|
14234
|
-
const truncated = merged
|
|
14552
|
+
const truncated = truncateAgentResponse(merged);
|
|
14235
14553
|
const appendResult = await this.memoryService.storeAgentResponse(
|
|
14236
14554
|
sessionId,
|
|
14237
14555
|
truncated,
|
|
@@ -14245,55 +14563,60 @@ var SessionHistoryImporter = class {
|
|
|
14245
14563
|
lineCount++;
|
|
14246
14564
|
textBuffer = [];
|
|
14247
14565
|
};
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14566
|
+
try {
|
|
14567
|
+
for await (const line of rl) {
|
|
14568
|
+
if (lineCount >= limit) break;
|
|
14569
|
+
try {
|
|
14570
|
+
const entry = JSON.parse(line);
|
|
14571
|
+
result.totalMessages++;
|
|
14572
|
+
const msgClass = classifyEntry(entry);
|
|
14573
|
+
if (msgClass === "user_prompt") {
|
|
14574
|
+
await flushTextBuffer();
|
|
14575
|
+
const content = this.extractContent(entry);
|
|
14576
|
+
if (!content) continue;
|
|
14577
|
+
if (!isWorthStoringPrompt(content)) {
|
|
14578
|
+
result.skippedDuplicates++;
|
|
14579
|
+
continue;
|
|
14580
|
+
}
|
|
14581
|
+
currentTurnId = randomUUID14();
|
|
14582
|
+
const appendResult = await this.memoryService.storeUserPrompt(
|
|
14583
|
+
sessionId,
|
|
14584
|
+
content,
|
|
14585
|
+
{ importedFrom: filePath, originalTimestamp: entry.timestamp, turnId: currentTurnId }
|
|
14586
|
+
);
|
|
14587
|
+
if (appendResult.success && appendResult.isDuplicate) {
|
|
14588
|
+
result.skippedDuplicates++;
|
|
14589
|
+
} else {
|
|
14590
|
+
result.importedPrompts++;
|
|
14591
|
+
}
|
|
14592
|
+
lineCount++;
|
|
14593
|
+
} else if (msgClass === "agent_text") {
|
|
14594
|
+
const content = this.extractContent(entry);
|
|
14595
|
+
if (content) {
|
|
14596
|
+
textBuffer.push(content);
|
|
14597
|
+
lastTimestamp = entry.timestamp;
|
|
14598
|
+
}
|
|
14272
14599
|
}
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14600
|
+
const now = Date.now();
|
|
14601
|
+
if (now - lastProgressAt > 200) {
|
|
14602
|
+
lastProgressAt = now;
|
|
14603
|
+
onProgress?.({
|
|
14604
|
+
phase: "session-progress",
|
|
14605
|
+
sessionIndex,
|
|
14606
|
+
messagesProcessed: result.totalMessages,
|
|
14607
|
+
imported: result.importedPrompts + result.importedResponses,
|
|
14608
|
+
skipped: result.skippedDuplicates
|
|
14609
|
+
});
|
|
14279
14610
|
}
|
|
14611
|
+
} catch (parseError) {
|
|
14612
|
+
result.errors.push(`Parse error on line: ${parseError}`);
|
|
14280
14613
|
}
|
|
14281
|
-
const now = Date.now();
|
|
14282
|
-
if (now - lastProgressAt > 200) {
|
|
14283
|
-
lastProgressAt = now;
|
|
14284
|
-
onProgress?.({
|
|
14285
|
-
phase: "session-progress",
|
|
14286
|
-
sessionIndex,
|
|
14287
|
-
messagesProcessed: result.totalMessages,
|
|
14288
|
-
imported: result.importedPrompts + result.importedResponses,
|
|
14289
|
-
skipped: result.skippedDuplicates
|
|
14290
|
-
});
|
|
14291
|
-
}
|
|
14292
|
-
} catch (parseError) {
|
|
14293
|
-
result.errors.push(`Parse error on line: ${parseError}`);
|
|
14294
14614
|
}
|
|
14615
|
+
await flushTextBuffer();
|
|
14616
|
+
} finally {
|
|
14617
|
+
rl.close();
|
|
14618
|
+
fileStream.close();
|
|
14295
14619
|
}
|
|
14296
|
-
await flushTextBuffer();
|
|
14297
14620
|
await this.memoryService.endSession(sessionId);
|
|
14298
14621
|
if (options.projectPath) {
|
|
14299
14622
|
registerSession(sessionId, options.projectPath);
|
|
@@ -14804,29 +15127,6 @@ var CodexSessionHistoryImporter = class {
|
|
|
14804
15127
|
getSessionsRoot() {
|
|
14805
15128
|
return this.sessionsRoot;
|
|
14806
15129
|
}
|
|
14807
|
-
listSessionFilesRecursive(rootDir) {
|
|
14808
|
-
if (!fs10.existsSync(rootDir)) return [];
|
|
14809
|
-
const out = [];
|
|
14810
|
-
const stack = [rootDir];
|
|
14811
|
-
while (stack.length > 0) {
|
|
14812
|
-
const dir = stack.pop();
|
|
14813
|
-
let entries;
|
|
14814
|
-
try {
|
|
14815
|
-
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
14816
|
-
} catch {
|
|
14817
|
-
continue;
|
|
14818
|
-
}
|
|
14819
|
-
for (const ent of entries) {
|
|
14820
|
-
const fullPath = path12.join(dir, ent.name);
|
|
14821
|
-
if (ent.isDirectory()) {
|
|
14822
|
-
stack.push(fullPath);
|
|
14823
|
-
} else if (ent.isFile() && ent.name.endsWith(".jsonl")) {
|
|
14824
|
-
out.push(fullPath);
|
|
14825
|
-
}
|
|
14826
|
-
}
|
|
14827
|
-
}
|
|
14828
|
-
return out;
|
|
14829
|
-
}
|
|
14830
15130
|
async readSessionMeta(filePath) {
|
|
14831
15131
|
const fileStream = fs10.createReadStream(filePath, { encoding: "utf-8" });
|
|
14832
15132
|
const rl = readline2.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
@@ -14876,7 +15176,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
14876
15176
|
}
|
|
14877
15177
|
const normalizedTarget = normalizeMaybeRealpath(projectPath);
|
|
14878
15178
|
onProgress?.({ phase: "scan", message: "Scanning Codex session files..." });
|
|
14879
|
-
const sessionFiles =
|
|
15179
|
+
const sessionFiles = listCodexSessionFilesRecursive(sessionsRoot);
|
|
14880
15180
|
const matchingFiles = [];
|
|
14881
15181
|
for (const filePath of sessionFiles) {
|
|
14882
15182
|
try {
|
|
@@ -14941,7 +15241,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
14941
15241
|
return result;
|
|
14942
15242
|
}
|
|
14943
15243
|
onProgress?.({ phase: "scan", message: "Scanning all Codex sessions..." });
|
|
14944
|
-
const sessionFiles =
|
|
15244
|
+
const sessionFiles = listCodexSessionFilesRecursive(sessionsRoot);
|
|
14945
15245
|
const selectedFiles = selectRecentCodexSessionFiles(sessionFiles, options.sessionLimit);
|
|
14946
15246
|
onProgress?.({ phase: "scan", message: `Found ${sessionFiles.length} Codex session file(s)` });
|
|
14947
15247
|
const totalLimit = normalizePositiveImportLimit(options.limit);
|
|
@@ -15020,13 +15320,12 @@ var CodexSessionHistoryImporter = class {
|
|
|
15020
15320
|
return;
|
|
15021
15321
|
}
|
|
15022
15322
|
if (textBuffer.length === 0 || !currentTurnId) return;
|
|
15023
|
-
const
|
|
15024
|
-
const merged = substantive.length > 0 ? substantive.join("\n\n") : textBuffer.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
15323
|
+
const merged = mergeAgentResponseBlocks(textBuffer);
|
|
15025
15324
|
if (!merged) {
|
|
15026
15325
|
textBuffer = [];
|
|
15027
15326
|
return;
|
|
15028
15327
|
}
|
|
15029
|
-
const truncated = merged
|
|
15328
|
+
const truncated = truncateAgentResponse(merged);
|
|
15030
15329
|
const appendResult = await this.memoryService.storeAgentResponse(
|
|
15031
15330
|
sessionId,
|
|
15032
15331
|
truncated,
|
|
@@ -15110,7 +15409,7 @@ var CodexSessionHistoryImporter = class {
|
|
|
15110
15409
|
async listAvailableSessions(projectPath) {
|
|
15111
15410
|
const sessionsRoot = this.getSessionsRoot();
|
|
15112
15411
|
if (!fs10.existsSync(sessionsRoot)) return [];
|
|
15113
|
-
const files =
|
|
15412
|
+
const files = listCodexSessionFilesRecursive(sessionsRoot);
|
|
15114
15413
|
const sessions = [];
|
|
15115
15414
|
const normalizedTarget = projectPath ? normalizeMaybeRealpath(projectPath) : null;
|
|
15116
15415
|
for (const filePath of files) {
|
|
@@ -15551,9 +15850,7 @@ var HermesSessionHistoryImporter = class {
|
|
|
15551
15850
|
return;
|
|
15552
15851
|
}
|
|
15553
15852
|
if (textBuffer.length === 0 || !currentTurnId) return;
|
|
15554
|
-
const
|
|
15555
|
-
const substantive = contentBlocks.filter((content) => content.length >= 100);
|
|
15556
|
-
const merged = substantive.length > 0 ? substantive.join("\n\n") : contentBlocks.reduce((a, b) => a.length >= b.length ? a : b, "");
|
|
15853
|
+
const merged = mergeAgentResponseBlocks(textBuffer.map((item) => item.content));
|
|
15557
15854
|
if (!merged) {
|
|
15558
15855
|
textBuffer = [];
|
|
15559
15856
|
return;
|
|
@@ -16053,8 +16350,8 @@ import { getCookie, setCookie, deleteCookie } from "hono/cookie";
|
|
|
16053
16350
|
import { logger } from "hono/logger";
|
|
16054
16351
|
import { serve } from "@hono/node-server";
|
|
16055
16352
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
16056
|
-
import * as
|
|
16057
|
-
import * as
|
|
16353
|
+
import * as path18 from "path";
|
|
16354
|
+
import * as fs16 from "fs";
|
|
16058
16355
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
16059
16356
|
import { fileURLToPath as fileUrlToPath } from "url";
|
|
16060
16357
|
|
|
@@ -16065,6 +16362,11 @@ import { Hono as Hono11 } from "hono";
|
|
|
16065
16362
|
import { Hono } from "hono";
|
|
16066
16363
|
|
|
16067
16364
|
// src/apps/server/api/utils.ts
|
|
16365
|
+
function jsonError(c, error, options = {}) {
|
|
16366
|
+
const status = options.status ?? 500;
|
|
16367
|
+
console.error(`[api] ${c.req.method} ${c.req.path} failed:`, error);
|
|
16368
|
+
return c.json({ error: options.message ?? "Internal server error" }, status);
|
|
16369
|
+
}
|
|
16068
16370
|
function getServiceFromQuery(c) {
|
|
16069
16371
|
const project = c.req.query("project") || c.req.query("projectId");
|
|
16070
16372
|
if (project) {
|
|
@@ -16176,7 +16478,7 @@ sessionsRouter.get("/", async (c) => {
|
|
|
16176
16478
|
hasMore: end < total
|
|
16177
16479
|
});
|
|
16178
16480
|
} catch (error) {
|
|
16179
|
-
return c
|
|
16481
|
+
return jsonError(c, error);
|
|
16180
16482
|
} finally {
|
|
16181
16483
|
await memoryService.shutdown();
|
|
16182
16484
|
}
|
|
@@ -16212,7 +16514,7 @@ sessionsRouter.get("/:id", async (c) => {
|
|
|
16212
16514
|
stats: eventsByType
|
|
16213
16515
|
});
|
|
16214
16516
|
} catch (error) {
|
|
16215
|
-
return c
|
|
16517
|
+
return jsonError(c, error);
|
|
16216
16518
|
} finally {
|
|
16217
16519
|
await memoryService.shutdown();
|
|
16218
16520
|
}
|
|
@@ -16278,7 +16580,7 @@ eventsRouter.get("/", async (c) => {
|
|
|
16278
16580
|
hasMore: offset + limit < total
|
|
16279
16581
|
});
|
|
16280
16582
|
} catch (error) {
|
|
16281
|
-
return c
|
|
16583
|
+
return jsonError(c, error);
|
|
16282
16584
|
} finally {
|
|
16283
16585
|
await memoryService.shutdown();
|
|
16284
16586
|
}
|
|
@@ -16288,12 +16590,11 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
16288
16590
|
const memoryService = getLightweightServiceFromQuery(c);
|
|
16289
16591
|
try {
|
|
16290
16592
|
await memoryService.initialize();
|
|
16291
|
-
const
|
|
16292
|
-
const event = recentEvents.find((e) => e.id === id);
|
|
16593
|
+
const event = await memoryService.getEvent(id);
|
|
16293
16594
|
if (!event) {
|
|
16294
16595
|
return c.json({ error: "Event not found" }, 404);
|
|
16295
16596
|
}
|
|
16296
|
-
const sessionEvents =
|
|
16597
|
+
const sessionEvents = (await memoryService.getSessionHistory(event.sessionId)).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
16297
16598
|
const eventIndex = sessionEvents.findIndex((e) => e.id === id);
|
|
16298
16599
|
const start = Math.max(0, eventIndex - 2);
|
|
16299
16600
|
const end = Math.min(sessionEvents.length, eventIndex + 3);
|
|
@@ -16315,7 +16616,7 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
16315
16616
|
}))
|
|
16316
16617
|
});
|
|
16317
16618
|
} catch (error) {
|
|
16318
|
-
return c
|
|
16619
|
+
return jsonError(c, error);
|
|
16319
16620
|
} finally {
|
|
16320
16621
|
await memoryService.shutdown();
|
|
16321
16622
|
}
|
|
@@ -16362,7 +16663,7 @@ searchRouter.post("/", async (c) => {
|
|
|
16362
16663
|
}
|
|
16363
16664
|
});
|
|
16364
16665
|
} catch (error) {
|
|
16365
|
-
return c
|
|
16666
|
+
return jsonError(c, error);
|
|
16366
16667
|
} finally {
|
|
16367
16668
|
await memoryService.shutdown();
|
|
16368
16669
|
}
|
|
@@ -16405,7 +16706,7 @@ searchRouter.post("/disclosure", async (c) => {
|
|
|
16405
16706
|
throw error;
|
|
16406
16707
|
}
|
|
16407
16708
|
} catch (error) {
|
|
16408
|
-
return c
|
|
16709
|
+
return jsonError(c, error);
|
|
16409
16710
|
} finally {
|
|
16410
16711
|
await memoryService?.shutdown();
|
|
16411
16712
|
}
|
|
@@ -16425,7 +16726,7 @@ searchRouter.get("/disclosure/:resultId/expand", async (c) => {
|
|
|
16425
16726
|
}
|
|
16426
16727
|
return c.json(result);
|
|
16427
16728
|
} catch (error) {
|
|
16428
|
-
return c
|
|
16729
|
+
return jsonError(c, error);
|
|
16429
16730
|
} finally {
|
|
16430
16731
|
await memoryService.shutdown();
|
|
16431
16732
|
}
|
|
@@ -16440,7 +16741,7 @@ searchRouter.get("/disclosure/:resultId/source", async (c) => {
|
|
|
16440
16741
|
}
|
|
16441
16742
|
return c.json(result);
|
|
16442
16743
|
} catch (error) {
|
|
16443
|
-
return c
|
|
16744
|
+
return jsonError(c, error);
|
|
16444
16745
|
} finally {
|
|
16445
16746
|
await memoryService.shutdown();
|
|
16446
16747
|
}
|
|
@@ -16469,7 +16770,7 @@ searchRouter.get("/", async (c) => {
|
|
|
16469
16770
|
}
|
|
16470
16771
|
});
|
|
16471
16772
|
} catch (error) {
|
|
16472
|
-
return c
|
|
16773
|
+
return jsonError(c, error);
|
|
16473
16774
|
} finally {
|
|
16474
16775
|
await memoryService.shutdown();
|
|
16475
16776
|
}
|
|
@@ -16477,9 +16778,89 @@ searchRouter.get("/", async (c) => {
|
|
|
16477
16778
|
|
|
16478
16779
|
// src/apps/server/api/stats.ts
|
|
16479
16780
|
import { Hono as Hono4 } from "hono";
|
|
16480
|
-
import * as
|
|
16781
|
+
import * as fs14 from "fs";
|
|
16481
16782
|
import * as os9 from "os";
|
|
16783
|
+
import * as path16 from "path";
|
|
16784
|
+
|
|
16785
|
+
// src/apps/server/api/stats-metrics.ts
|
|
16786
|
+
import * as fs13 from "fs";
|
|
16482
16787
|
import * as path15 from "path";
|
|
16788
|
+
var DEFAULT_KPI_THRESHOLDS = {
|
|
16789
|
+
usefulRecallRateMin: 0.45,
|
|
16790
|
+
reworkRateMax: 0.25,
|
|
16791
|
+
postChangeFailureRateMax: 0.2,
|
|
16792
|
+
avgCompletionTurnsMax: 12,
|
|
16793
|
+
memoryHitRateMin: 0.35
|
|
16794
|
+
};
|
|
16795
|
+
function loadKpiThresholds() {
|
|
16796
|
+
try {
|
|
16797
|
+
const filePath = path15.resolve(process.cwd(), "config", "kpi-thresholds.json");
|
|
16798
|
+
if (!fs13.existsSync(filePath)) return DEFAULT_KPI_THRESHOLDS;
|
|
16799
|
+
const parsed = JSON.parse(fs13.readFileSync(filePath, "utf-8"));
|
|
16800
|
+
return {
|
|
16801
|
+
usefulRecallRateMin: Number(parsed.usefulRecallRateMin ?? DEFAULT_KPI_THRESHOLDS.usefulRecallRateMin),
|
|
16802
|
+
reworkRateMax: Number(parsed.reworkRateMax ?? DEFAULT_KPI_THRESHOLDS.reworkRateMax),
|
|
16803
|
+
postChangeFailureRateMax: Number(parsed.postChangeFailureRateMax ?? DEFAULT_KPI_THRESHOLDS.postChangeFailureRateMax),
|
|
16804
|
+
avgCompletionTurnsMax: Number(parsed.avgCompletionTurnsMax ?? DEFAULT_KPI_THRESHOLDS.avgCompletionTurnsMax),
|
|
16805
|
+
memoryHitRateMin: Number(parsed.memoryHitRateMin ?? DEFAULT_KPI_THRESHOLDS.memoryHitRateMin)
|
|
16806
|
+
};
|
|
16807
|
+
} catch {
|
|
16808
|
+
return DEFAULT_KPI_THRESHOLDS;
|
|
16809
|
+
}
|
|
16810
|
+
}
|
|
16811
|
+
function windowToMs(window) {
|
|
16812
|
+
if (window === "24h") return 24 * 60 * 60 * 1e3;
|
|
16813
|
+
if (window === "7d") return 7 * 24 * 60 * 60 * 1e3;
|
|
16814
|
+
return 30 * 24 * 60 * 60 * 1e3;
|
|
16815
|
+
}
|
|
16816
|
+
function inWindow(e, now, window) {
|
|
16817
|
+
return now - e.timestamp.getTime() <= windowToMs(window);
|
|
16818
|
+
}
|
|
16819
|
+
function isEditToolName(name) {
|
|
16820
|
+
return ["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(name);
|
|
16821
|
+
}
|
|
16822
|
+
function parseToolPayload(e) {
|
|
16823
|
+
if (e.eventType !== "tool_observation") return null;
|
|
16824
|
+
try {
|
|
16825
|
+
const payload = JSON.parse(e.content);
|
|
16826
|
+
return {
|
|
16827
|
+
toolName: payload?.toolName,
|
|
16828
|
+
success: payload?.success,
|
|
16829
|
+
filePath: payload?.metadata?.filePath,
|
|
16830
|
+
command: payload?.metadata?.command
|
|
16831
|
+
};
|
|
16832
|
+
} catch {
|
|
16833
|
+
return {
|
|
16834
|
+
toolName: e.metadata?.toolName,
|
|
16835
|
+
success: e.metadata?.success,
|
|
16836
|
+
filePath: e.metadata?.filePath,
|
|
16837
|
+
command: e.metadata?.command
|
|
16838
|
+
};
|
|
16839
|
+
}
|
|
16840
|
+
}
|
|
16841
|
+
function isTestLikeCommand(command) {
|
|
16842
|
+
if (!command) return false;
|
|
16843
|
+
return /(test|jest|vitest|pytest|go test|cargo test|lint|eslint|build|tsc)/i.test(command);
|
|
16844
|
+
}
|
|
16845
|
+
function safeRatio(num, den) {
|
|
16846
|
+
if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0) return 0;
|
|
16847
|
+
return num / den;
|
|
16848
|
+
}
|
|
16849
|
+
function round2(value, digits = 4) {
|
|
16850
|
+
const factor = 10 ** digits;
|
|
16851
|
+
return Math.round(value * factor) / factor;
|
|
16852
|
+
}
|
|
16853
|
+
function computeSessionTurnCount(sessionEvents) {
|
|
16854
|
+
const turnIds = /* @__PURE__ */ new Set();
|
|
16855
|
+
for (const e of sessionEvents) {
|
|
16856
|
+
const turnId = e.metadata?.turnId;
|
|
16857
|
+
if (typeof turnId === "string" && turnId.length > 0) turnIds.add(turnId);
|
|
16858
|
+
}
|
|
16859
|
+
if (turnIds.size > 0) return turnIds.size;
|
|
16860
|
+
return sessionEvents.filter((e) => e.eventType === "user_prompt").length;
|
|
16861
|
+
}
|
|
16862
|
+
|
|
16863
|
+
// src/apps/server/api/stats.ts
|
|
16483
16864
|
var statsRouter = new Hono4();
|
|
16484
16865
|
var OPERATION_STATS_TABLES = [
|
|
16485
16866
|
"memory_facets",
|
|
@@ -16506,7 +16887,7 @@ function operationsStatsHomeDir() {
|
|
|
16506
16887
|
}
|
|
16507
16888
|
function projectStoragePathForOperationsStats(projectOrHash) {
|
|
16508
16889
|
const projectHash = /^[a-f0-9]{8}$/.test(projectOrHash) ? projectOrHash : hashProjectPath(projectOrHash);
|
|
16509
|
-
return
|
|
16890
|
+
return path16.join(operationsStatsHomeDir(), ".claude-code", "memory", "projects", projectHash);
|
|
16510
16891
|
}
|
|
16511
16892
|
function getOperationsStatsContext(project) {
|
|
16512
16893
|
if (project && project.trim().length > 0) {
|
|
@@ -16516,13 +16897,13 @@ function getOperationsStatsContext(project) {
|
|
|
16516
16897
|
return {
|
|
16517
16898
|
projectHash,
|
|
16518
16899
|
storagePath: storagePath2,
|
|
16519
|
-
dbPath:
|
|
16900
|
+
dbPath: path16.join(storagePath2, "events.sqlite")
|
|
16520
16901
|
};
|
|
16521
16902
|
}
|
|
16522
|
-
const storagePath =
|
|
16903
|
+
const storagePath = path16.join(operationsStatsHomeDir(), ".claude-code", "memory");
|
|
16523
16904
|
return {
|
|
16524
16905
|
storagePath,
|
|
16525
|
-
dbPath:
|
|
16906
|
+
dbPath: path16.join(storagePath, "events.sqlite")
|
|
16526
16907
|
};
|
|
16527
16908
|
}
|
|
16528
16909
|
function countRowValue(db, sql, params = []) {
|
|
@@ -16540,6 +16921,23 @@ function sanitizeAggregateLabel(value) {
|
|
|
16540
16921
|
const trimmed = sanitized.trim();
|
|
16541
16922
|
return trimmed.length > 0 ? trimmed.slice(0, 96) : "unknown";
|
|
16542
16923
|
}
|
|
16924
|
+
var SAFE_RETRIEVAL_TRACE_STRATEGIES = /* @__PURE__ */ new Set([
|
|
16925
|
+
"auto",
|
|
16926
|
+
"deep",
|
|
16927
|
+
"fast",
|
|
16928
|
+
"hybrid",
|
|
16929
|
+
"keyword",
|
|
16930
|
+
"semantic",
|
|
16931
|
+
"mcp-context-pack",
|
|
16932
|
+
"session-start-hook",
|
|
16933
|
+
"unknown"
|
|
16934
|
+
]);
|
|
16935
|
+
function normalizeRetrievalTraceStrategy(value) {
|
|
16936
|
+
const label = sanitizeAggregateLabel(value);
|
|
16937
|
+
if (label === "[REDACTED]") return "unknown";
|
|
16938
|
+
const normalized = label.trim().toLowerCase();
|
|
16939
|
+
return SAFE_RETRIEVAL_TRACE_STRATEGIES.has(normalized) ? normalized : "unknown";
|
|
16940
|
+
}
|
|
16543
16941
|
function emptyOperationsStatsPayload(context, databaseExists, missingTables, windowDays) {
|
|
16544
16942
|
return {
|
|
16545
16943
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16953,80 +17351,6 @@ function buildLessonConfidenceBuckets(db, projectHash) {
|
|
|
16953
17351
|
}).length
|
|
16954
17352
|
}));
|
|
16955
17353
|
}
|
|
16956
|
-
var DEFAULT_KPI_THRESHOLDS = {
|
|
16957
|
-
usefulRecallRateMin: 0.45,
|
|
16958
|
-
reworkRateMax: 0.25,
|
|
16959
|
-
postChangeFailureRateMax: 0.2,
|
|
16960
|
-
avgCompletionTurnsMax: 12,
|
|
16961
|
-
memoryHitRateMin: 0.35
|
|
16962
|
-
};
|
|
16963
|
-
function loadKpiThresholds() {
|
|
16964
|
-
try {
|
|
16965
|
-
const filePath = path15.resolve(process.cwd(), "config", "kpi-thresholds.json");
|
|
16966
|
-
if (!fs13.existsSync(filePath)) return DEFAULT_KPI_THRESHOLDS;
|
|
16967
|
-
const parsed = JSON.parse(fs13.readFileSync(filePath, "utf-8"));
|
|
16968
|
-
return {
|
|
16969
|
-
usefulRecallRateMin: Number(parsed.usefulRecallRateMin ?? DEFAULT_KPI_THRESHOLDS.usefulRecallRateMin),
|
|
16970
|
-
reworkRateMax: Number(parsed.reworkRateMax ?? DEFAULT_KPI_THRESHOLDS.reworkRateMax),
|
|
16971
|
-
postChangeFailureRateMax: Number(parsed.postChangeFailureRateMax ?? DEFAULT_KPI_THRESHOLDS.postChangeFailureRateMax),
|
|
16972
|
-
avgCompletionTurnsMax: Number(parsed.avgCompletionTurnsMax ?? DEFAULT_KPI_THRESHOLDS.avgCompletionTurnsMax),
|
|
16973
|
-
memoryHitRateMin: Number(parsed.memoryHitRateMin ?? DEFAULT_KPI_THRESHOLDS.memoryHitRateMin)
|
|
16974
|
-
};
|
|
16975
|
-
} catch {
|
|
16976
|
-
return DEFAULT_KPI_THRESHOLDS;
|
|
16977
|
-
}
|
|
16978
|
-
}
|
|
16979
|
-
function windowToMs(window) {
|
|
16980
|
-
if (window === "24h") return 24 * 60 * 60 * 1e3;
|
|
16981
|
-
if (window === "7d") return 7 * 24 * 60 * 60 * 1e3;
|
|
16982
|
-
return 30 * 24 * 60 * 60 * 1e3;
|
|
16983
|
-
}
|
|
16984
|
-
function inWindow(e, now, window) {
|
|
16985
|
-
return now - e.timestamp.getTime() <= windowToMs(window);
|
|
16986
|
-
}
|
|
16987
|
-
function isEditToolName(name) {
|
|
16988
|
-
return ["Write", "Edit", "MultiEdit", "NotebookEdit"].includes(name);
|
|
16989
|
-
}
|
|
16990
|
-
function parseToolPayload(e) {
|
|
16991
|
-
if (e.eventType !== "tool_observation") return null;
|
|
16992
|
-
try {
|
|
16993
|
-
const payload = JSON.parse(e.content);
|
|
16994
|
-
return {
|
|
16995
|
-
toolName: payload?.toolName,
|
|
16996
|
-
success: payload?.success,
|
|
16997
|
-
filePath: payload?.metadata?.filePath,
|
|
16998
|
-
command: payload?.metadata?.command
|
|
16999
|
-
};
|
|
17000
|
-
} catch {
|
|
17001
|
-
return {
|
|
17002
|
-
toolName: e.metadata?.toolName,
|
|
17003
|
-
success: e.metadata?.success,
|
|
17004
|
-
filePath: e.metadata?.filePath,
|
|
17005
|
-
command: e.metadata?.command
|
|
17006
|
-
};
|
|
17007
|
-
}
|
|
17008
|
-
}
|
|
17009
|
-
function isTestLikeCommand(command) {
|
|
17010
|
-
if (!command) return false;
|
|
17011
|
-
return /(test|jest|vitest|pytest|go test|cargo test|lint|eslint|build|tsc)/i.test(command);
|
|
17012
|
-
}
|
|
17013
|
-
function safeRatio(num, den) {
|
|
17014
|
-
if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0) return 0;
|
|
17015
|
-
return num / den;
|
|
17016
|
-
}
|
|
17017
|
-
function round2(value, digits = 4) {
|
|
17018
|
-
const factor = 10 ** digits;
|
|
17019
|
-
return Math.round(value * factor) / factor;
|
|
17020
|
-
}
|
|
17021
|
-
function computeSessionTurnCount(sessionEvents) {
|
|
17022
|
-
const turnIds = /* @__PURE__ */ new Set();
|
|
17023
|
-
for (const e of sessionEvents) {
|
|
17024
|
-
const turnId = e.metadata?.turnId;
|
|
17025
|
-
if (typeof turnId === "string" && turnId.length > 0) turnIds.add(turnId);
|
|
17026
|
-
}
|
|
17027
|
-
if (turnIds.size > 0) return turnIds.size;
|
|
17028
|
-
return sessionEvents.filter((e) => e.eventType === "user_prompt").length;
|
|
17029
|
-
}
|
|
17030
17354
|
function normalizeQueryRewriteKind2(value) {
|
|
17031
17355
|
const normalized = (value || "").trim().toLowerCase();
|
|
17032
17356
|
if (normalized === "follow-up-context" || normalized === "intent-rewrite") return normalized;
|
|
@@ -17054,6 +17378,63 @@ function getTraceSelectedCount(trace) {
|
|
|
17054
17378
|
function getTraceCandidateCount(trace) {
|
|
17055
17379
|
return Number(trace.candidateCount ?? trace.candidateEventIds?.length ?? trace.candidateDetails?.length ?? 0);
|
|
17056
17380
|
}
|
|
17381
|
+
function normalizeStatsNumber(value) {
|
|
17382
|
+
const numberValue = Number(value || 0);
|
|
17383
|
+
return Number.isFinite(numberValue) ? Math.max(0, numberValue) : 0;
|
|
17384
|
+
}
|
|
17385
|
+
function sanitizeRetrievalTraceStrategyBreakdown(value) {
|
|
17386
|
+
if (!Array.isArray(value)) return [];
|
|
17387
|
+
const byStrategy = /* @__PURE__ */ new Map();
|
|
17388
|
+
for (const item of value) {
|
|
17389
|
+
const row = item && typeof item === "object" ? item : {};
|
|
17390
|
+
const strategy = normalizeRetrievalTraceStrategy(row.strategy ?? "unknown");
|
|
17391
|
+
const bucket = byStrategy.get(strategy) ?? {
|
|
17392
|
+
strategy,
|
|
17393
|
+
totalQueries: 0,
|
|
17394
|
+
queriesWithSelection: 0,
|
|
17395
|
+
rewrittenQueries: 0,
|
|
17396
|
+
totalCandidateCount: 0,
|
|
17397
|
+
totalSelectedCount: 0
|
|
17398
|
+
};
|
|
17399
|
+
bucket.totalQueries += normalizeStatsNumber(row.totalQueries);
|
|
17400
|
+
bucket.queriesWithSelection += normalizeStatsNumber(row.queriesWithSelection);
|
|
17401
|
+
bucket.rewrittenQueries += normalizeStatsNumber(row.rewrittenQueries);
|
|
17402
|
+
bucket.totalCandidateCount += normalizeStatsNumber(row.totalCandidateCount);
|
|
17403
|
+
bucket.totalSelectedCount += normalizeStatsNumber(row.totalSelectedCount);
|
|
17404
|
+
byStrategy.set(strategy, bucket);
|
|
17405
|
+
}
|
|
17406
|
+
return Array.from(byStrategy.values()).map((row) => ({
|
|
17407
|
+
strategy: row.strategy,
|
|
17408
|
+
totalQueries: row.totalQueries,
|
|
17409
|
+
queriesWithSelection: row.queriesWithSelection,
|
|
17410
|
+
rewrittenQueries: row.rewrittenQueries,
|
|
17411
|
+
rewriteRate: normalizeMetric(safeRatio(row.rewrittenQueries, row.totalQueries)),
|
|
17412
|
+
totalCandidateCount: row.totalCandidateCount,
|
|
17413
|
+
totalSelectedCount: row.totalSelectedCount,
|
|
17414
|
+
avgCandidateCount: safeRatio(row.totalCandidateCount, row.totalQueries),
|
|
17415
|
+
avgSelectedCount: safeRatio(row.totalSelectedCount, row.totalQueries),
|
|
17416
|
+
selectionRate: normalizeMetric(safeRatio(row.totalSelectedCount, row.totalCandidateCount)),
|
|
17417
|
+
queryYieldRate: normalizeMetric(safeRatio(row.queriesWithSelection, row.totalQueries))
|
|
17418
|
+
}));
|
|
17419
|
+
}
|
|
17420
|
+
function sanitizeRetrievalTraceStats(stats) {
|
|
17421
|
+
const s = stats && typeof stats === "object" ? stats : {};
|
|
17422
|
+
return {
|
|
17423
|
+
totalQueries: Number(s.totalQueries || 0),
|
|
17424
|
+
avgCandidateCount: Number(s.avgCandidateCount || 0),
|
|
17425
|
+
avgSelectedCount: Number(s.avgSelectedCount || 0),
|
|
17426
|
+
selectionRate: normalizeMetric(s.selectionRate),
|
|
17427
|
+
rewrittenQueries: Number(s.rewrittenQueries || 0),
|
|
17428
|
+
rewriteRate: normalizeMetric(s.rewriteRate),
|
|
17429
|
+
rewrittenQueriesWithSelection: Number(s.rewrittenQueriesWithSelection || 0),
|
|
17430
|
+
rawQueriesWithSelection: Number(s.rawQueriesWithSelection || 0),
|
|
17431
|
+
rewrittenSelectionRate: normalizeMetric(s.rewrittenSelectionRate),
|
|
17432
|
+
rawSelectionRate: normalizeMetric(s.rawSelectionRate),
|
|
17433
|
+
avgSelectedCountForRewrittenQueries: Number(s.avgSelectedCountForRewrittenQueries || 0),
|
|
17434
|
+
avgSelectedCountForRawQueries: Number(s.avgSelectedCountForRawQueries || 0),
|
|
17435
|
+
strategyBreakdown: sanitizeRetrievalTraceStrategyBreakdown(s.strategyBreakdown)
|
|
17436
|
+
};
|
|
17437
|
+
}
|
|
17057
17438
|
function makeRetrievalReviewItem(trace) {
|
|
17058
17439
|
const candidateCount = getTraceCandidateCount(trace);
|
|
17059
17440
|
const selectedCount = getTraceSelectedCount(trace);
|
|
@@ -17107,7 +17488,7 @@ function makeRetrievalReviewItem(trace) {
|
|
|
17107
17488
|
action,
|
|
17108
17489
|
queryRewriteKind,
|
|
17109
17490
|
rewritten,
|
|
17110
|
-
strategy: trace.strategy
|
|
17491
|
+
strategy: normalizeRetrievalTraceStrategy(trace.strategy ?? "unknown"),
|
|
17111
17492
|
candidateCount,
|
|
17112
17493
|
selectedCount,
|
|
17113
17494
|
candidateEventIds: (trace.candidateEventIds || []).slice(0, 5),
|
|
@@ -17534,7 +17915,7 @@ statsRouter.get("/levels/:level", async (c) => {
|
|
|
17534
17915
|
hasMore: events.length === limit
|
|
17535
17916
|
});
|
|
17536
17917
|
} catch (error) {
|
|
17537
|
-
return c
|
|
17918
|
+
return jsonError(c, error);
|
|
17538
17919
|
} finally {
|
|
17539
17920
|
await memoryService.shutdown();
|
|
17540
17921
|
}
|
|
@@ -17543,7 +17924,7 @@ statsRouter.get("/operations", async (c) => {
|
|
|
17543
17924
|
const context = getOperationsStatsContext(c.req.query("project") || c.req.query("projectId"));
|
|
17544
17925
|
const windowDays = parseStatsLimit(c.req.query("windowDays"), 30, 365);
|
|
17545
17926
|
const topFacetValues = parseStatsLimit(c.req.query("topFacetValues"), 5, 25);
|
|
17546
|
-
const databaseExists =
|
|
17927
|
+
const databaseExists = fs14.existsSync(context.dbPath);
|
|
17547
17928
|
if (!databaseExists) {
|
|
17548
17929
|
return c.json(emptyOperationsStatsPayload(context, false, [...OPERATION_STATS_TABLES], windowDays));
|
|
17549
17930
|
}
|
|
@@ -17631,7 +18012,7 @@ statsRouter.get("/perspective", async (c) => {
|
|
|
17631
18012
|
const windowDays = parseStatsLimit(c.req.query("windowDays"), 30, 365);
|
|
17632
18013
|
const contradictionLimit = parseStatsLimit(c.req.query("contradictionLimit"), 10, 50);
|
|
17633
18014
|
const graphLimit = parseStatsLimit(c.req.query("graphLimit"), 10, 50);
|
|
17634
|
-
const databaseExists =
|
|
18015
|
+
const databaseExists = fs14.existsSync(context.dbPath);
|
|
17635
18016
|
if (!databaseExists) {
|
|
17636
18017
|
return c.json(emptyPerspectiveStatsPayload(context, false, [...PERSPECTIVE_STATS_TABLES], windowDays));
|
|
17637
18018
|
}
|
|
@@ -17707,33 +18088,29 @@ statsRouter.get("/", async (c) => {
|
|
|
17707
18088
|
const memoryService = getLightweightServiceFromQuery(c);
|
|
17708
18089
|
try {
|
|
17709
18090
|
await memoryService.initialize();
|
|
17710
|
-
const
|
|
17711
|
-
const
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
const
|
|
17719
|
-
const eventsByDay =
|
|
17720
|
-
|
|
17721
|
-
acc[day] = (acc[day] || 0) + 1;
|
|
17722
|
-
return acc;
|
|
17723
|
-
}, {});
|
|
17724
|
-
const retrievalTrace = await memoryService.getRetrievalTraceStats();
|
|
18091
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
|
|
18092
|
+
const [stats, typeCounts, sessionTotal, dailyCounts, rawTrace] = await Promise.all([
|
|
18093
|
+
memoryService.getStats(),
|
|
18094
|
+
memoryService.getEventTypeCounts(),
|
|
18095
|
+
memoryService.getDistinctSessionCount(),
|
|
18096
|
+
memoryService.getDailyEventCounts(sevenDaysAgo),
|
|
18097
|
+
memoryService.getRetrievalTraceStats()
|
|
18098
|
+
]);
|
|
18099
|
+
const eventsByType = Object.fromEntries(typeCounts.map((t) => [t.eventType, t.count]));
|
|
18100
|
+
const eventsByDay = Object.fromEntries(dailyCounts.map((d) => [d.day, d.total]));
|
|
18101
|
+
const retrievalTrace = sanitizeRetrievalTraceStats(rawTrace);
|
|
17725
18102
|
return c.json({
|
|
17726
18103
|
storage: {
|
|
17727
18104
|
eventCount: stats.totalEvents,
|
|
17728
18105
|
vectorCount: stats.vectorCount
|
|
17729
18106
|
},
|
|
17730
18107
|
sessions: {
|
|
17731
|
-
total:
|
|
18108
|
+
total: sessionTotal
|
|
17732
18109
|
},
|
|
17733
18110
|
eventsByType,
|
|
17734
18111
|
activity: {
|
|
17735
18112
|
daily: eventsByDay,
|
|
17736
|
-
total7Days:
|
|
18113
|
+
total7Days: dailyCounts.reduce((sum, d) => sum + d.total, 0)
|
|
17737
18114
|
},
|
|
17738
18115
|
memory: {
|
|
17739
18116
|
heapUsed: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
|
@@ -17743,7 +18120,7 @@ statsRouter.get("/", async (c) => {
|
|
|
17743
18120
|
retrievalTrace
|
|
17744
18121
|
});
|
|
17745
18122
|
} catch (error) {
|
|
17746
|
-
return c
|
|
18123
|
+
return jsonError(c, error);
|
|
17747
18124
|
} finally {
|
|
17748
18125
|
await memoryService.shutdown();
|
|
17749
18126
|
}
|
|
@@ -17780,30 +18157,22 @@ statsRouter.get("/most-accessed", async (c) => {
|
|
|
17780
18157
|
}
|
|
17781
18158
|
});
|
|
17782
18159
|
statsRouter.get("/timeline", async (c) => {
|
|
17783
|
-
const
|
|
18160
|
+
const parsedDays = parseInt(c.req.query("days") || "7", 10);
|
|
18161
|
+
const days = Number.isFinite(parsedDays) && parsedDays > 0 ? parsedDays : 7;
|
|
17784
18162
|
const memoryService = getLightweightServiceFromQuery(c);
|
|
17785
18163
|
try {
|
|
17786
18164
|
await memoryService.initialize();
|
|
17787
|
-
const
|
|
17788
|
-
const
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
if (e.eventType === "user_prompt") acc[day].prompts++;
|
|
17797
|
-
if (e.eventType === "agent_response") acc[day].responses++;
|
|
17798
|
-
if (e.eventType === "tool_observation") acc[day].tools++;
|
|
17799
|
-
return acc;
|
|
17800
|
-
}, {});
|
|
17801
|
-
return c.json({
|
|
17802
|
-
days,
|
|
17803
|
-
daily: Object.values(daily).sort((a, b) => a.date.localeCompare(b.date))
|
|
17804
|
-
});
|
|
18165
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
18166
|
+
const daily = (await memoryService.getDailyEventCounts(cutoff)).map((d) => ({
|
|
18167
|
+
date: d.day,
|
|
18168
|
+
total: d.total,
|
|
18169
|
+
prompts: d.prompts,
|
|
18170
|
+
responses: d.responses,
|
|
18171
|
+
tools: d.tools
|
|
18172
|
+
}));
|
|
18173
|
+
return c.json({ days, daily });
|
|
17805
18174
|
} catch (error) {
|
|
17806
|
-
return c
|
|
18175
|
+
return jsonError(c, error);
|
|
17807
18176
|
} finally {
|
|
17808
18177
|
await memoryService.shutdown();
|
|
17809
18178
|
}
|
|
@@ -17846,16 +18215,14 @@ statsRouter.get("/usefulness", async (c) => {
|
|
|
17846
18215
|
try {
|
|
17847
18216
|
await memoryService.initialize();
|
|
17848
18217
|
const now = Date.now();
|
|
17849
|
-
const eventLimit = 2e4;
|
|
17850
18218
|
const traceLimit = 5e3;
|
|
17851
18219
|
const windowStart = new Date(now - windowToMs(window));
|
|
17852
18220
|
const [events, helpfulness, traces] = await Promise.all([
|
|
17853
|
-
memoryService.
|
|
18221
|
+
memoryService.getEventsAfter(windowStart.toISOString()),
|
|
17854
18222
|
memoryService.getHelpfulnessStats(windowStart),
|
|
17855
18223
|
memoryService.getRecentRetrievalTraces(traceLimit)
|
|
17856
18224
|
]);
|
|
17857
18225
|
return c.json(computeMemoryUsefulnessSummary(events, helpfulness, traces, now, window, {
|
|
17858
|
-
eventsLimit: eventLimit,
|
|
17859
18226
|
tracesLimit: traceLimit
|
|
17860
18227
|
}));
|
|
17861
18228
|
} catch (error) {
|
|
@@ -17873,7 +18240,7 @@ statsRouter.get("/retrieval-traces", async (c) => {
|
|
|
17873
18240
|
const traces = await memoryService.getRecentRetrievalTraces(limit);
|
|
17874
18241
|
const traceStats = await memoryService.getRetrievalTraceStats();
|
|
17875
18242
|
return c.json({
|
|
17876
|
-
stats: traceStats,
|
|
18243
|
+
stats: sanitizeRetrievalTraceStats(traceStats),
|
|
17877
18244
|
traces: traces.map((t) => {
|
|
17878
18245
|
const queryRewriteKind = normalizeQueryRewriteKind2(t.queryRewriteKind);
|
|
17879
18246
|
return {
|
|
@@ -17882,7 +18249,7 @@ statsRouter.get("/retrieval-traces", async (c) => {
|
|
|
17882
18249
|
projectHash: t.projectHash || null,
|
|
17883
18250
|
queryRewriteKind,
|
|
17884
18251
|
rewritten: queryRewriteKind !== "none",
|
|
17885
|
-
strategy: t.strategy
|
|
18252
|
+
strategy: normalizeRetrievalTraceStrategy(t.strategy ?? "unknown"),
|
|
17886
18253
|
candidateEventIds: t.candidateEventIds,
|
|
17887
18254
|
selectedEventIds: t.selectedEventIds,
|
|
17888
18255
|
candidateDetails: t.candidateDetails || [],
|
|
@@ -17909,7 +18276,8 @@ statsRouter.get("/retrieval-traces", async (c) => {
|
|
|
17909
18276
|
rewrittenSelectionRate: 0,
|
|
17910
18277
|
rawSelectionRate: 0,
|
|
17911
18278
|
avgSelectedCountForRewrittenQueries: 0,
|
|
17912
|
-
avgSelectedCountForRawQueries: 0
|
|
18279
|
+
avgSelectedCountForRawQueries: 0,
|
|
18280
|
+
strategyBreakdown: []
|
|
17913
18281
|
},
|
|
17914
18282
|
traces: [],
|
|
17915
18283
|
error: error.message
|
|
@@ -17960,7 +18328,8 @@ statsRouter.get("/kpi", async (c) => {
|
|
|
17960
18328
|
await memoryService.initialize();
|
|
17961
18329
|
const now = Date.now();
|
|
17962
18330
|
const thresholds = loadKpiThresholds();
|
|
17963
|
-
const
|
|
18331
|
+
const lookbackMs = Math.max(2 * windowToMs(window), 30 * 24 * 60 * 60 * 1e3);
|
|
18332
|
+
const allEvents = await memoryService.getEventsAfter(new Date(now - lookbackMs).toISOString());
|
|
17964
18333
|
const events = allEvents.filter((e) => inWindow(e, now, window));
|
|
17965
18334
|
const helpfulness = await memoryService.getHelpfulnessStats();
|
|
17966
18335
|
const usefulRecallRate = helpfulness.totalEvaluated > 0 ? round2(safeRatio(helpfulness.helpful, helpfulness.totalEvaluated)) : 0;
|
|
@@ -18069,7 +18438,7 @@ statsRouter.get("/kpi", async (c) => {
|
|
|
18069
18438
|
alerts
|
|
18070
18439
|
});
|
|
18071
18440
|
} catch (error) {
|
|
18072
|
-
return c
|
|
18441
|
+
return jsonError(c, error);
|
|
18073
18442
|
} finally {
|
|
18074
18443
|
await memoryService.shutdown();
|
|
18075
18444
|
}
|
|
@@ -18120,11 +18489,7 @@ citationsRouter.get("/:id", async (c) => {
|
|
|
18120
18489
|
const memoryService = getServiceFromQuery(c);
|
|
18121
18490
|
try {
|
|
18122
18491
|
await memoryService.initialize();
|
|
18123
|
-
const
|
|
18124
|
-
const event = recentEvents.find((e) => {
|
|
18125
|
-
const eventCitationId = generateCitationId(e.id);
|
|
18126
|
-
return eventCitationId === citationId;
|
|
18127
|
-
});
|
|
18492
|
+
const event = await memoryService.getEventByCitationId(citationId);
|
|
18128
18493
|
if (!event) {
|
|
18129
18494
|
return c.json({ error: "Citation not found" }, 404);
|
|
18130
18495
|
}
|
|
@@ -18143,7 +18508,7 @@ citationsRouter.get("/:id", async (c) => {
|
|
|
18143
18508
|
}
|
|
18144
18509
|
});
|
|
18145
18510
|
} catch (error) {
|
|
18146
|
-
return c
|
|
18511
|
+
return jsonError(c, error);
|
|
18147
18512
|
} finally {
|
|
18148
18513
|
await memoryService.shutdown();
|
|
18149
18514
|
}
|
|
@@ -18154,15 +18519,11 @@ citationsRouter.get("/:id/related", async (c) => {
|
|
|
18154
18519
|
const memoryService = getServiceFromQuery(c);
|
|
18155
18520
|
try {
|
|
18156
18521
|
await memoryService.initialize();
|
|
18157
|
-
const
|
|
18158
|
-
const event = recentEvents.find((e) => {
|
|
18159
|
-
const eventCitationId = generateCitationId(e.id);
|
|
18160
|
-
return eventCitationId === citationId;
|
|
18161
|
-
});
|
|
18522
|
+
const event = await memoryService.getEventByCitationId(citationId);
|
|
18162
18523
|
if (!event) {
|
|
18163
18524
|
return c.json({ error: "Citation not found" }, 404);
|
|
18164
18525
|
}
|
|
18165
|
-
const sessionEvents =
|
|
18526
|
+
const sessionEvents = (await memoryService.getSessionHistory(event.sessionId)).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
18166
18527
|
const eventIndex = sessionEvents.findIndex((e) => e.id === event.id);
|
|
18167
18528
|
const prev = eventIndex > 0 ? sessionEvents[eventIndex - 1] : null;
|
|
18168
18529
|
const next = eventIndex < sessionEvents.length - 1 ? sessionEvents[eventIndex + 1] : null;
|
|
@@ -18181,7 +18542,7 @@ citationsRouter.get("/:id/related", async (c) => {
|
|
|
18181
18542
|
} : null
|
|
18182
18543
|
});
|
|
18183
18544
|
} catch (error) {
|
|
18184
|
-
return c
|
|
18545
|
+
return jsonError(c, error);
|
|
18185
18546
|
} finally {
|
|
18186
18547
|
await memoryService.shutdown();
|
|
18187
18548
|
}
|
|
@@ -18224,7 +18585,7 @@ turnsRouter.get("/", async (c) => {
|
|
|
18224
18585
|
hasMore: offset + limit < totalTurns
|
|
18225
18586
|
});
|
|
18226
18587
|
} catch (error) {
|
|
18227
|
-
return c
|
|
18588
|
+
return jsonError(c, error);
|
|
18228
18589
|
} finally {
|
|
18229
18590
|
await memoryService.shutdown();
|
|
18230
18591
|
}
|
|
@@ -18275,7 +18636,7 @@ turnsRouter.get("/:turnId", async (c) => {
|
|
|
18275
18636
|
totalEvents: events.length
|
|
18276
18637
|
});
|
|
18277
18638
|
} catch (error) {
|
|
18278
|
-
return c
|
|
18639
|
+
return jsonError(c, error);
|
|
18279
18640
|
} finally {
|
|
18280
18641
|
await memoryService.shutdown();
|
|
18281
18642
|
}
|
|
@@ -18302,8 +18663,8 @@ turnsRouter.post("/backfill", async (c) => {
|
|
|
18302
18663
|
|
|
18303
18664
|
// src/apps/server/api/projects.ts
|
|
18304
18665
|
import { Hono as Hono7 } from "hono";
|
|
18305
|
-
import * as
|
|
18306
|
-
import * as
|
|
18666
|
+
import * as fs15 from "fs";
|
|
18667
|
+
import * as path17 from "path";
|
|
18307
18668
|
import * as os10 from "os";
|
|
18308
18669
|
var projectsRouter = new Hono7();
|
|
18309
18670
|
projectsRouter.get("/:hash/detail", async (c) => {
|
|
@@ -18332,7 +18693,7 @@ projectsRouter.get("/:hash/detail", async (c) => {
|
|
|
18332
18693
|
return c.json({
|
|
18333
18694
|
project: {
|
|
18334
18695
|
hash,
|
|
18335
|
-
projectName: projectPath ?
|
|
18696
|
+
projectName: projectPath ? path17.basename(projectPath) : `unknown (${hash})`,
|
|
18336
18697
|
registered: Boolean(projectPath)
|
|
18337
18698
|
},
|
|
18338
18699
|
storage: {
|
|
@@ -18360,13 +18721,13 @@ projectsRouter.get("/:hash/detail", async (c) => {
|
|
|
18360
18721
|
});
|
|
18361
18722
|
projectsRouter.get("/", async (c) => {
|
|
18362
18723
|
try {
|
|
18363
|
-
const projectsDir =
|
|
18364
|
-
if (!
|
|
18724
|
+
const projectsDir = path17.join(os10.homedir(), ".claude-code", "memory", "projects");
|
|
18725
|
+
if (!fs15.existsSync(projectsDir)) {
|
|
18365
18726
|
return c.json({ projects: [] });
|
|
18366
18727
|
}
|
|
18367
|
-
const projectHashes =
|
|
18368
|
-
const fullPath =
|
|
18369
|
-
return
|
|
18728
|
+
const projectHashes = fs15.readdirSync(projectsDir).filter((name) => {
|
|
18729
|
+
const fullPath = path17.join(projectsDir, name);
|
|
18730
|
+
return fs15.statSync(fullPath).isDirectory();
|
|
18370
18731
|
});
|
|
18371
18732
|
const registry = loadSessionRegistry();
|
|
18372
18733
|
const hashToPath = /* @__PURE__ */ new Map();
|
|
@@ -18376,17 +18737,17 @@ projectsRouter.get("/", async (c) => {
|
|
|
18376
18737
|
}
|
|
18377
18738
|
}
|
|
18378
18739
|
const projects = projectHashes.map((hash) => {
|
|
18379
|
-
const dirPath =
|
|
18380
|
-
const dbPath =
|
|
18740
|
+
const dirPath = path17.join(projectsDir, hash);
|
|
18741
|
+
const dbPath = path17.join(dirPath, "events.sqlite");
|
|
18381
18742
|
let dbSize = 0;
|
|
18382
|
-
if (
|
|
18383
|
-
dbSize =
|
|
18743
|
+
if (fs15.existsSync(dbPath)) {
|
|
18744
|
+
dbSize = fs15.statSync(dbPath).size;
|
|
18384
18745
|
}
|
|
18385
18746
|
const projectPath = hashToPath.get(hash) || `unknown (${hash})`;
|
|
18386
18747
|
return {
|
|
18387
18748
|
hash,
|
|
18388
18749
|
projectPath,
|
|
18389
|
-
projectName:
|
|
18750
|
+
projectName: path17.basename(projectPath),
|
|
18390
18751
|
dbSize,
|
|
18391
18752
|
dbSizeHuman: formatBytes(dbSize)
|
|
18392
18753
|
};
|
|
@@ -18515,7 +18876,7 @@ chatRouter.post("/", async (c) => {
|
|
|
18515
18876
|
}
|
|
18516
18877
|
});
|
|
18517
18878
|
} catch (error) {
|
|
18518
|
-
return c
|
|
18879
|
+
return jsonError(c, error);
|
|
18519
18880
|
} finally {
|
|
18520
18881
|
await memoryService.shutdown();
|
|
18521
18882
|
}
|
|
@@ -19156,20 +19517,20 @@ playgroundRouter.post("/dry-run", async (c) => {
|
|
|
19156
19517
|
var apiRouter = new Hono11().route("/sessions", sessionsRouter).route("/events", eventsRouter).route("/search", searchRouter).route("/stats", statsRouter).route("/citations", citationsRouter).route("/turns", turnsRouter).route("/projects", projectsRouter).route("/chat", chatRouter).route("/health", healthRouter).route("/playground", playgroundRouter);
|
|
19157
19518
|
|
|
19158
19519
|
// src/apps/server/index.ts
|
|
19159
|
-
var moduleDir =
|
|
19520
|
+
var moduleDir = path18.dirname(fileUrlToPath(import.meta.url));
|
|
19160
19521
|
var DASHBOARD_SESSION_COOKIE = "cml_dashboard_session";
|
|
19161
19522
|
var DEFAULT_SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
|
|
19162
19523
|
function resolveUiPath() {
|
|
19163
19524
|
const candidates = [
|
|
19164
19525
|
// Built server: dist/server/index.js -> dist/ui
|
|
19165
|
-
|
|
19526
|
+
path18.resolve(moduleDir, "../ui"),
|
|
19166
19527
|
// Source/dev server: src/apps/server/index.ts -> src/apps/dashboard
|
|
19167
|
-
|
|
19528
|
+
path18.resolve(moduleDir, "../dashboard"),
|
|
19168
19529
|
// Fallback when running from a repository root.
|
|
19169
|
-
|
|
19170
|
-
|
|
19530
|
+
path18.resolve(process.cwd(), "dist/ui"),
|
|
19531
|
+
path18.resolve(process.cwd(), "src/apps/dashboard")
|
|
19171
19532
|
];
|
|
19172
|
-
return candidates.find((candidate) =>
|
|
19533
|
+
return candidates.find((candidate) => fs16.existsSync(path18.join(candidate, "index.html"))) ?? candidates[0];
|
|
19173
19534
|
}
|
|
19174
19535
|
function normalizeDashboardHost(host = "localhost") {
|
|
19175
19536
|
const normalized = host.trim().toLowerCase();
|
|
@@ -19310,14 +19671,51 @@ function createAuthOptions(options) {
|
|
|
19310
19671
|
return {
|
|
19311
19672
|
password: options.password ?? "",
|
|
19312
19673
|
cookieMaxAgeSeconds: options.cookieMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
19313
|
-
now: options.now ?? Date.now
|
|
19674
|
+
now: options.now ?? Date.now,
|
|
19675
|
+
allowedOrigins: options.allowedOrigins ?? []
|
|
19314
19676
|
};
|
|
19315
19677
|
}
|
|
19678
|
+
function resolveAllowedOrigins(explicit) {
|
|
19679
|
+
if (explicit && explicit.length > 0) return explicit;
|
|
19680
|
+
const raw = process.env.DASHBOARD_ALLOWED_ORIGINS;
|
|
19681
|
+
if (!raw) return [];
|
|
19682
|
+
return raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
19683
|
+
}
|
|
19684
|
+
function selfOrigins(c) {
|
|
19685
|
+
const origins = [];
|
|
19686
|
+
try {
|
|
19687
|
+
origins.push(new URL(c.req.url).origin);
|
|
19688
|
+
} catch {
|
|
19689
|
+
}
|
|
19690
|
+
const host = c.req.header("host");
|
|
19691
|
+
if (host) origins.push(`${isSecureRequest(c) ? "https" : "http"}://${host}`);
|
|
19692
|
+
return origins;
|
|
19693
|
+
}
|
|
19694
|
+
function isAllowedMutationOrigin(c, origin, allowedOrigins) {
|
|
19695
|
+
return allowedOrigins.includes(origin) || selfOrigins(c).includes(origin);
|
|
19696
|
+
}
|
|
19316
19697
|
function createDashboardApp(options = {}) {
|
|
19317
19698
|
const app2 = new Hono12();
|
|
19318
19699
|
const authOptions = createAuthOptions(options);
|
|
19319
|
-
|
|
19700
|
+
const allowedOrigins = resolveAllowedOrigins(options.allowedOrigins);
|
|
19701
|
+
if (allowedOrigins.length > 0) {
|
|
19702
|
+
app2.use("*", cors({
|
|
19703
|
+
origin: (origin) => allowedOrigins.includes(origin) ? origin : null,
|
|
19704
|
+
credentials: true
|
|
19705
|
+
}));
|
|
19706
|
+
}
|
|
19320
19707
|
app2.use("*", logger());
|
|
19708
|
+
app2.use("/api/*", async (c, next) => {
|
|
19709
|
+
const method = c.req.method;
|
|
19710
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") {
|
|
19711
|
+
return next();
|
|
19712
|
+
}
|
|
19713
|
+
const origin = c.req.header("origin");
|
|
19714
|
+
if (origin && !isAllowedMutationOrigin(c, origin, allowedOrigins)) {
|
|
19715
|
+
return c.json({ error: "Cross-origin request blocked" }, 403);
|
|
19716
|
+
}
|
|
19717
|
+
return next();
|
|
19718
|
+
});
|
|
19321
19719
|
app2.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
19322
19720
|
app2.get("/api/auth/status", (c) => c.json({
|
|
19323
19721
|
enabled: Boolean(authOptions.password),
|
|
@@ -19360,13 +19758,13 @@ function createDashboardApp(options = {}) {
|
|
|
19360
19758
|
}
|
|
19361
19759
|
app2.route("/api", apiRouter);
|
|
19362
19760
|
const uiPath = resolveUiPath();
|
|
19363
|
-
if (
|
|
19761
|
+
if (fs16.existsSync(uiPath)) {
|
|
19364
19762
|
app2.use("/*", serveStatic({ root: uiPath }));
|
|
19365
19763
|
}
|
|
19366
19764
|
app2.get("*", (c) => {
|
|
19367
|
-
const indexPath =
|
|
19368
|
-
if (
|
|
19369
|
-
return c.html(
|
|
19765
|
+
const indexPath = path18.join(uiPath, "index.html");
|
|
19766
|
+
if (fs16.existsSync(indexPath)) {
|
|
19767
|
+
return c.html(fs16.readFileSync(indexPath, "utf-8"));
|
|
19370
19768
|
}
|
|
19371
19769
|
return c.text('UI not built. Run "npm run build:ui" first.', 404);
|
|
19372
19770
|
});
|
|
@@ -19824,7 +20222,7 @@ function formatMetadataValue(value) {
|
|
|
19824
20222
|
}
|
|
19825
20223
|
|
|
19826
20224
|
// src/apps/cli/mcp-install.ts
|
|
19827
|
-
import { existsSync as
|
|
20225
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
19828
20226
|
import { dirname as dirname7, join as join18 } from "node:path";
|
|
19829
20227
|
import { homedir as homedir11 } from "node:os";
|
|
19830
20228
|
function getDefaultClaudeDesktopConfigPath(platform = process.platform, env = process.env, home = homedir11()) {
|
|
@@ -19837,7 +20235,7 @@ function getDefaultClaudeDesktopConfigPath(platform = process.platform, env = pr
|
|
|
19837
20235
|
return join18(env.XDG_CONFIG_HOME ?? join18(home, ".config"), "claude", "claude_desktop_config.json");
|
|
19838
20236
|
}
|
|
19839
20237
|
function readJsonConfig(configPath) {
|
|
19840
|
-
if (!
|
|
20238
|
+
if (!existsSync14(configPath)) {
|
|
19841
20239
|
return {};
|
|
19842
20240
|
}
|
|
19843
20241
|
const content = readFileSync6(configPath, "utf8").trim();
|
|
@@ -19886,7 +20284,7 @@ function installMcpServer(options = {}) {
|
|
|
19886
20284
|
}
|
|
19887
20285
|
|
|
19888
20286
|
// src/apps/cli/claude-settings-hooks.ts
|
|
19889
|
-
import * as
|
|
20287
|
+
import * as path19 from "path";
|
|
19890
20288
|
var REQUIRED_HOOK_FILES = [
|
|
19891
20289
|
"user-prompt-submit.js",
|
|
19892
20290
|
"post-tool-use.js",
|
|
@@ -19905,7 +20303,7 @@ function shellQuotePathForNode(filePath) {
|
|
|
19905
20303
|
return `'${filePath.replace(/'/g, `'\\''`)}'`;
|
|
19906
20304
|
}
|
|
19907
20305
|
function buildHookCommand(pluginPath, fileName) {
|
|
19908
|
-
return `node ${shellQuotePathForNode(
|
|
20306
|
+
return `node ${shellQuotePathForNode(path19.join(pluginPath, "hooks", fileName))}`;
|
|
19909
20307
|
}
|
|
19910
20308
|
function getHooksConfig(pluginPath) {
|
|
19911
20309
|
const makeHook = (fileName) => [
|
|
@@ -19976,8 +20374,8 @@ function mergePluginHooksIntoSettings(settings, pluginPath) {
|
|
|
19976
20374
|
}
|
|
19977
20375
|
|
|
19978
20376
|
// src/apps/cli/codex-validation-output.ts
|
|
19979
|
-
import * as
|
|
19980
|
-
import * as
|
|
20377
|
+
import * as fs17 from "fs";
|
|
20378
|
+
import * as path20 from "path";
|
|
19981
20379
|
function formatNumber(value) {
|
|
19982
20380
|
return Number.isFinite(value) ? value.toLocaleString("en-US") : String(value);
|
|
19983
20381
|
}
|
|
@@ -20051,14 +20449,14 @@ function formatCodexValidationReport(report, format = "markdown") {
|
|
|
20051
20449
|
return lines.join("\n");
|
|
20052
20450
|
}
|
|
20053
20451
|
function writeCodexValidationReport(outputPath, report, format = "markdown") {
|
|
20054
|
-
const dir =
|
|
20055
|
-
|
|
20056
|
-
|
|
20452
|
+
const dir = path20.dirname(outputPath);
|
|
20453
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
20454
|
+
fs17.writeFileSync(outputPath, formatCodexValidationReport(report, format), "utf8");
|
|
20057
20455
|
}
|
|
20058
20456
|
|
|
20059
20457
|
// src/apps/cli/hermes-validation-output.ts
|
|
20060
|
-
import * as
|
|
20061
|
-
import * as
|
|
20458
|
+
import * as fs18 from "fs";
|
|
20459
|
+
import * as path21 from "path";
|
|
20062
20460
|
function formatNumber2(value) {
|
|
20063
20461
|
return Number.isFinite(value) ? value.toLocaleString("en-US") : String(value);
|
|
20064
20462
|
}
|
|
@@ -20128,9 +20526,9 @@ function formatHermesValidationReport(report, format = "markdown") {
|
|
|
20128
20526
|
return lines.join("\n");
|
|
20129
20527
|
}
|
|
20130
20528
|
function writeHermesValidationReport(outputPath, report, format = "markdown") {
|
|
20131
|
-
const dir =
|
|
20132
|
-
|
|
20133
|
-
|
|
20529
|
+
const dir = path21.dirname(outputPath);
|
|
20530
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
20531
|
+
fs18.writeFileSync(outputPath, formatHermesValidationReport(report, format), "utf8");
|
|
20134
20532
|
}
|
|
20135
20533
|
|
|
20136
20534
|
// src/apps/cli/codex-import-runner.ts
|
|
@@ -20817,7 +21215,7 @@ function redactSecrets(input) {
|
|
|
20817
21215
|
}
|
|
20818
21216
|
|
|
20819
21217
|
// src/apps/cli/process-command.ts
|
|
20820
|
-
import * as
|
|
21218
|
+
import * as path22 from "node:path";
|
|
20821
21219
|
function resolveProcessCommandOptions(options, cwd = process.cwd(), deps = {}) {
|
|
20822
21220
|
const explicitProject = options.project;
|
|
20823
21221
|
if (explicitProject !== void 0 && explicitProject.trim().length === 0) {
|
|
@@ -20838,7 +21236,7 @@ function resolveProcessCommandOptions(options, cwd = process.cwd(), deps = {}) {
|
|
|
20838
21236
|
projectPath,
|
|
20839
21237
|
recoverStuck,
|
|
20840
21238
|
dryRunRecovery,
|
|
20841
|
-
lockPath: explicitLockPath ??
|
|
21239
|
+
lockPath: explicitLockPath ?? path22.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
|
|
20842
21240
|
};
|
|
20843
21241
|
}
|
|
20844
21242
|
function formatProcessLockBusy(input) {
|
|
@@ -21016,12 +21414,12 @@ function formatDuration2(ms) {
|
|
|
21016
21414
|
}
|
|
21017
21415
|
|
|
21018
21416
|
// src/apps/cli/mongo-sync-command.ts
|
|
21019
|
-
import * as
|
|
21417
|
+
import * as path24 from "node:path";
|
|
21020
21418
|
|
|
21021
21419
|
// src/core/worker-lock.ts
|
|
21022
|
-
import { closeSync, existsSync as
|
|
21420
|
+
import { closeSync, existsSync as existsSync15, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
21023
21421
|
import * as os12 from "node:os";
|
|
21024
|
-
import * as
|
|
21422
|
+
import * as path23 from "node:path";
|
|
21025
21423
|
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
21026
21424
|
var WorkerLock = class {
|
|
21027
21425
|
lockPath;
|
|
@@ -21030,7 +21428,7 @@ var WorkerLock = class {
|
|
|
21030
21428
|
now;
|
|
21031
21429
|
isProcessRunning;
|
|
21032
21430
|
acquired = false;
|
|
21033
|
-
constructor(lockPath =
|
|
21431
|
+
constructor(lockPath = path23.join(os12.tmpdir(), "claude-memory-layer", "vector-worker.lock"), options = {}) {
|
|
21034
21432
|
this.lockPath = lockPath;
|
|
21035
21433
|
this.pid = options.pid ?? process.pid;
|
|
21036
21434
|
this.ownerId = options.ownerId ?? randomUUID18();
|
|
@@ -21043,7 +21441,7 @@ var WorkerLock = class {
|
|
|
21043
21441
|
acquire() {
|
|
21044
21442
|
let staleRecovered = false;
|
|
21045
21443
|
for (; ; ) {
|
|
21046
|
-
mkdirSync10(
|
|
21444
|
+
mkdirSync10(path23.dirname(this.lockPath), { recursive: true });
|
|
21047
21445
|
const createResult = this.tryCreateLockFile();
|
|
21048
21446
|
if (createResult === "created") {
|
|
21049
21447
|
this.acquired = true;
|
|
@@ -21077,7 +21475,7 @@ var WorkerLock = class {
|
|
|
21077
21475
|
}
|
|
21078
21476
|
}
|
|
21079
21477
|
release() {
|
|
21080
|
-
if (!this.acquired || !
|
|
21478
|
+
if (!this.acquired || !existsSync15(this.lockPath)) return false;
|
|
21081
21479
|
const existing = this.readPayload();
|
|
21082
21480
|
if (!this.isOwnedPayload(existing)) return false;
|
|
21083
21481
|
try {
|
|
@@ -21175,7 +21573,7 @@ function resolveMongoSyncProcessOptions(options, cwd = process.cwd(), deps = {})
|
|
|
21175
21573
|
projectPath,
|
|
21176
21574
|
processAfterSync: options.processAfterSync === true,
|
|
21177
21575
|
processIntervalMs,
|
|
21178
|
-
lockPath: explicitLockPath ??
|
|
21576
|
+
lockPath: explicitLockPath ?? path24.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
|
|
21179
21577
|
};
|
|
21180
21578
|
}
|
|
21181
21579
|
function createMongoSyncPostProcessor(options, deps = {}) {
|
|
@@ -21258,28 +21656,28 @@ function parsePositiveIntegerOption2(raw, flagName) {
|
|
|
21258
21656
|
}
|
|
21259
21657
|
|
|
21260
21658
|
// src/apps/cli/index.ts
|
|
21261
|
-
var CLAUDE_SETTINGS_PATH =
|
|
21659
|
+
var CLAUDE_SETTINGS_PATH = path25.join(os13.homedir(), ".claude", "settings.json");
|
|
21262
21660
|
function getPluginPath() {
|
|
21263
21661
|
const possiblePaths = [
|
|
21264
|
-
|
|
21662
|
+
path25.join(__dirname, ".."),
|
|
21265
21663
|
// When running from dist/cli
|
|
21266
|
-
|
|
21664
|
+
path25.join(__dirname, "../..", "dist"),
|
|
21267
21665
|
// When running from src
|
|
21268
|
-
|
|
21666
|
+
path25.join(process.cwd(), "dist")
|
|
21269
21667
|
// Current working directory
|
|
21270
21668
|
];
|
|
21271
21669
|
for (const p of possiblePaths) {
|
|
21272
|
-
const hooksPath =
|
|
21273
|
-
if (
|
|
21670
|
+
const hooksPath = path25.join(p, "hooks", "user-prompt-submit.js");
|
|
21671
|
+
if (fs19.existsSync(hooksPath)) {
|
|
21274
21672
|
return p;
|
|
21275
21673
|
}
|
|
21276
21674
|
}
|
|
21277
|
-
return
|
|
21675
|
+
return path25.join(os13.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
|
|
21278
21676
|
}
|
|
21279
21677
|
function loadClaudeSettings() {
|
|
21280
21678
|
try {
|
|
21281
|
-
if (
|
|
21282
|
-
const content =
|
|
21679
|
+
if (fs19.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
21680
|
+
const content = fs19.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
21283
21681
|
return JSON.parse(content);
|
|
21284
21682
|
}
|
|
21285
21683
|
} catch (error) {
|
|
@@ -21288,13 +21686,13 @@ function loadClaudeSettings() {
|
|
|
21288
21686
|
return {};
|
|
21289
21687
|
}
|
|
21290
21688
|
function saveClaudeSettings(settings) {
|
|
21291
|
-
const dir =
|
|
21292
|
-
if (!
|
|
21293
|
-
|
|
21689
|
+
const dir = path25.dirname(CLAUDE_SETTINGS_PATH);
|
|
21690
|
+
if (!fs19.existsSync(dir)) {
|
|
21691
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
21294
21692
|
}
|
|
21295
21693
|
const tempPath = CLAUDE_SETTINGS_PATH + ".tmp";
|
|
21296
|
-
|
|
21297
|
-
|
|
21694
|
+
fs19.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
|
|
21695
|
+
fs19.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
|
|
21298
21696
|
}
|
|
21299
21697
|
function parseCommaList(value) {
|
|
21300
21698
|
if (!value) return void 0;
|
|
@@ -21354,7 +21752,7 @@ async function runCodexValidationCommand(options) {
|
|
|
21354
21752
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}
|
|
21355
21753
|
`);
|
|
21356
21754
|
if (options.output) {
|
|
21357
|
-
const outputPath =
|
|
21755
|
+
const outputPath = path25.resolve(options.output);
|
|
21358
21756
|
writeCodexValidationReport(outputPath, report, format);
|
|
21359
21757
|
console.log(`
|
|
21360
21758
|
Report written to ${outputPath}`);
|
|
@@ -21374,7 +21772,7 @@ async function runHermesValidationCommand(options) {
|
|
|
21374
21772
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}
|
|
21375
21773
|
`);
|
|
21376
21774
|
if (options.output) {
|
|
21377
|
-
const outputPath =
|
|
21775
|
+
const outputPath = path25.resolve(options.output);
|
|
21378
21776
|
writeHermesValidationReport(outputPath, report, format);
|
|
21379
21777
|
console.log(`
|
|
21380
21778
|
Report written to ${outputPath}`);
|
|
@@ -21408,18 +21806,18 @@ var OPERATION_PRIVACY_CONFIG = {
|
|
|
21408
21806
|
}
|
|
21409
21807
|
};
|
|
21410
21808
|
function resolveOperationProject(project) {
|
|
21411
|
-
const projectPath =
|
|
21809
|
+
const projectPath = path25.resolve(project ?? process.cwd());
|
|
21412
21810
|
const projectHash = hashProjectPath(projectPath);
|
|
21413
21811
|
const storagePath = getProjectStoragePath(projectPath);
|
|
21414
21812
|
return {
|
|
21415
21813
|
projectPath,
|
|
21416
21814
|
projectHash,
|
|
21417
21815
|
storagePath,
|
|
21418
|
-
dbPath:
|
|
21816
|
+
dbPath: path25.join(storagePath, "events.sqlite")
|
|
21419
21817
|
};
|
|
21420
21818
|
}
|
|
21421
21819
|
function openOperationReadDatabase(context) {
|
|
21422
|
-
if (!
|
|
21820
|
+
if (!fs19.existsSync(context.dbPath)) return null;
|
|
21423
21821
|
return createSQLiteDatabase(context.dbPath, { readonly: true, walMode: false });
|
|
21424
21822
|
}
|
|
21425
21823
|
async function withOperationWriteDatabase(context, callback) {
|
|
@@ -21629,7 +22027,7 @@ async function runOperationCli(action, label) {
|
|
|
21629
22027
|
}
|
|
21630
22028
|
}
|
|
21631
22029
|
var program = new Command();
|
|
21632
|
-
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.
|
|
22030
|
+
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.54");
|
|
21633
22031
|
program.command("market-context").description("Fetch read-only DART/FRED/Finnhub context with structured MarketContextSnapshot bull/bear/risk/catalyst analysis").option("--company <name>", "Company name for DART fallback search and report subject").option("--dart-corp-code <code>", "Exact DART corp_code for issuer-specific filings").option("--symbol <ticker>", "Listed ticker for Finnhub company profile").option("--providers <list>", "Comma-separated providers: dart,fred,finnhub").option("--fred-series <list>", "Comma-separated FRED series IDs").option("--json", "Print structured JSON including analysis.marketSnapshot").option("--no-snapshot", "Disable MarketContextSnapshot and DART company snapshot analysis").action(async (options) => {
|
|
21634
22032
|
try {
|
|
21635
22033
|
await runMarketContextCommand(options);
|
|
@@ -21642,7 +22040,7 @@ program.command("install").description("Install hooks into Claude Code settings"
|
|
|
21642
22040
|
try {
|
|
21643
22041
|
const pluginPath = options.path || getPluginPath();
|
|
21644
22042
|
const missingHooks = REQUIRED_HOOK_FILES.filter(
|
|
21645
|
-
(file) => !
|
|
22043
|
+
(file) => !fs19.existsSync(path25.join(pluginPath, "hooks", file))
|
|
21646
22044
|
);
|
|
21647
22045
|
if (missingHooks.length > 0) {
|
|
21648
22046
|
console.error(`
|
|
@@ -21708,7 +22106,7 @@ program.command("status").description("Check plugin installation status").action
|
|
|
21708
22106
|
console.log(` PostToolUse: ${hasPostToolHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
21709
22107
|
console.log(` Stop: ${hasStopHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
21710
22108
|
console.log(` SessionEnd: ${hasSessionEndHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
21711
|
-
const hooksExist = REQUIRED_HOOK_FILES.every((file) =>
|
|
22109
|
+
const hooksExist = REQUIRED_HOOK_FILES.every((file) => fs19.existsSync(path25.join(pluginPath, "hooks", file)));
|
|
21712
22110
|
console.log(`
|
|
21713
22111
|
Plugin files: ${hooksExist ? "\u2705 Found" : "\u274C Not found"}`);
|
|
21714
22112
|
console.log(` Path: ${pluginPath}`);
|
|
@@ -21943,8 +22341,8 @@ repairCommand.command("legacy-project-scope").description("Dry-run or apply proj
|
|
|
21943
22341
|
apply: options.apply
|
|
21944
22342
|
});
|
|
21945
22343
|
const storagePath = projectPath ? getProjectStoragePath(projectPath) : resolveProjectStoragePath(repairOptions.projectHash);
|
|
21946
|
-
const dbPath =
|
|
21947
|
-
if (repairOptions.dryRun && !
|
|
22344
|
+
const dbPath = path25.join(storagePath, "events.sqlite");
|
|
22345
|
+
if (repairOptions.dryRun && !fs19.existsSync(dbPath)) {
|
|
21948
22346
|
const projectHash = repairOptions.projectHash || hashProjectPath(repairOptions.projectPath);
|
|
21949
22347
|
console.log(formatLegacyProjectScopeRepairResult({
|
|
21950
22348
|
dryRun: true,
|
|
@@ -22191,8 +22589,8 @@ retentionCommand.command("audit").description("Run a dry-run retention audit for
|
|
|
22191
22589
|
});
|
|
22192
22590
|
const projectHash = auditOptions.projectHash ?? hashProjectPath(auditOptions.projectPath);
|
|
22193
22591
|
const storagePath = auditOptions.projectPath ? getProjectStoragePath(auditOptions.projectPath) : resolveProjectStoragePath(projectHash);
|
|
22194
|
-
const dbPath =
|
|
22195
|
-
if (!
|
|
22592
|
+
const dbPath = path25.join(storagePath, "events.sqlite");
|
|
22593
|
+
if (!fs19.existsSync(dbPath)) {
|
|
22196
22594
|
const emptyReport = emptyRetentionAuditReport(projectHash, auditOptions.limit);
|
|
22197
22595
|
console.log(formatRetentionAuditReport(emptyReport, { json: auditOptions.json }));
|
|
22198
22596
|
return;
|
|
@@ -22219,7 +22617,7 @@ program.command("mongo-sync").description("Sync events with MongoDB for multi-se
|
|
|
22219
22617
|
const projectPath = options.project || process.cwd();
|
|
22220
22618
|
const mongoUri = options.mongoUri || process.env.CLAUDE_MEMORY_MONGO_URI;
|
|
22221
22619
|
const mongoDb = options.mongoDb || process.env.CLAUDE_MEMORY_MONGO_DB;
|
|
22222
|
-
const projectKey = options.mongoProject || process.env.CLAUDE_MEMORY_MONGO_PROJECT ||
|
|
22620
|
+
const projectKey = options.mongoProject || process.env.CLAUDE_MEMORY_MONGO_PROJECT || path25.basename(projectPath);
|
|
22223
22621
|
const direction = String(options.direction || "both").toLowerCase();
|
|
22224
22622
|
if (!mongoUri || !mongoDb) {
|
|
22225
22623
|
console.error("\n\u274C MongoDB sync is not configured.");
|
|
@@ -22249,14 +22647,14 @@ program.command("mongo-sync").description("Sync events with MongoDB for multi-se
|
|
|
22249
22647
|
}
|
|
22250
22648
|
})();
|
|
22251
22649
|
const storagePath = getProjectStoragePath(projectPath);
|
|
22252
|
-
if (!
|
|
22253
|
-
|
|
22650
|
+
if (!fs19.existsSync(storagePath)) {
|
|
22651
|
+
fs19.mkdirSync(storagePath, { recursive: true });
|
|
22254
22652
|
}
|
|
22255
22653
|
const postProcessor = createMongoSyncPostProcessor(processOptions, {
|
|
22256
22654
|
log: (message) => process.stdout.write(`${message}
|
|
22257
22655
|
`)
|
|
22258
22656
|
});
|
|
22259
|
-
const sqliteStore = new SQLiteEventStore(
|
|
22657
|
+
const sqliteStore = new SQLiteEventStore(path25.join(storagePath, "events.sqlite"));
|
|
22260
22658
|
const worker = new MongoSyncWorker(sqliteStore, {
|
|
22261
22659
|
uri: mongoUri,
|
|
22262
22660
|
dbName: mongoDb,
|
|
@@ -22316,7 +22714,7 @@ function renderProgress(event) {
|
|
|
22316
22714
|
break;
|
|
22317
22715
|
case "session-start": {
|
|
22318
22716
|
const pct = Math.round(event.sessionIndex / event.totalSessions * 100);
|
|
22319
|
-
const sessionName =
|
|
22717
|
+
const sessionName = path25.basename(event.filePath, ".jsonl").slice(0, 8);
|
|
22320
22718
|
process.stdout.write(
|
|
22321
22719
|
`\r \u{1F4C4} [${event.sessionIndex + 1}/${event.totalSessions}] ${pct}% | Session ${sessionName}... `
|
|
22322
22720
|
);
|
|
@@ -22391,9 +22789,9 @@ async function listMarkdownFiles(root) {
|
|
|
22391
22789
|
const stack = [root];
|
|
22392
22790
|
while (stack.length > 0) {
|
|
22393
22791
|
const dir = stack.pop();
|
|
22394
|
-
const entries = await
|
|
22792
|
+
const entries = await fs19.promises.readdir(dir, { withFileTypes: true });
|
|
22395
22793
|
for (const e of entries) {
|
|
22396
|
-
const full =
|
|
22794
|
+
const full = path25.join(dir, e.name);
|
|
22397
22795
|
if (e.isDirectory()) stack.push(full);
|
|
22398
22796
|
else if (e.isFile() && e.name.endsWith(".md") && e.name !== "_index.md") out.push(full);
|
|
22399
22797
|
}
|
|
@@ -22401,8 +22799,8 @@ async function listMarkdownFiles(root) {
|
|
|
22401
22799
|
return out.sort();
|
|
22402
22800
|
}
|
|
22403
22801
|
function deriveNamespaceCategory(sourceRoot, filePath) {
|
|
22404
|
-
const rel =
|
|
22405
|
-
const dirSeg =
|
|
22802
|
+
const rel = path25.relative(sourceRoot, filePath);
|
|
22803
|
+
const dirSeg = path25.dirname(rel).split(path25.sep).filter(Boolean);
|
|
22406
22804
|
if (dirSeg.length >= 2) {
|
|
22407
22805
|
const namespace = sanitizeSegment3(dirSeg[0], "default");
|
|
22408
22806
|
const categoryPath = dirSeg.slice(1).map((s) => sanitizeSegment3(s, "uncategorized"));
|
|
@@ -22421,10 +22819,10 @@ function extractImportEvidence(markdown) {
|
|
|
22421
22819
|
program.command("organize-import [sourceDir]").description("Import existing markdown memory files, or bootstrap knowledge docs from codebase/git when markdown is missing").option("-p, --project <path>", "Project path (defaults to cwd)").option("--session <id>", "Session id for imported events (default: import:organized)").option("--limit <n>", "Limit number of files to import").option("--dry-run", "Preview mapping without writing").option("--bootstrap", "Force-generate structured markdown from codebase + git history before import").option("--bootstrap-if-empty", "Auto-bootstrap when source has no markdown files (default: true)", true).option("--no-bootstrap-if-empty", "Disable auto-bootstrap when source has no markdown files").option("--force-bootstrap", "Run bootstrap even when markdown files exist").option("--repo <path>", "Repository root for bootstrap analysis (default: project path)").option("--out <path>", "Output directory for generated bootstrap markdown (default: <sourceDir>/bootstrap-kb)").option("--since <range>", 'Git history range for bootstrap (default: "180 days ago")').option("--max-commits <n>", "Max commits to analyze for bootstrap (default: 1000)").option("--incremental", "Use previous bootstrap manifest as baseline for incremental updates (default: true)", true).option("--no-incremental", "Disable incremental bootstrap; regenerate full snapshot").action(async (sourceDir, options) => {
|
|
22422
22820
|
const projectPath = options.project || process.cwd();
|
|
22423
22821
|
const sessionId = options.session || "import:organized";
|
|
22424
|
-
const sourceRoot =
|
|
22425
|
-
const repoPath =
|
|
22426
|
-
if (!
|
|
22427
|
-
|
|
22822
|
+
const sourceRoot = path25.resolve(sourceDir || options.out || projectPath);
|
|
22823
|
+
const repoPath = path25.resolve(options.repo || projectPath);
|
|
22824
|
+
if (!fs19.existsSync(sourceRoot)) {
|
|
22825
|
+
fs19.mkdirSync(sourceRoot, { recursive: true });
|
|
22428
22826
|
}
|
|
22429
22827
|
const service = getMemoryServiceForProject(projectPath);
|
|
22430
22828
|
try {
|
|
@@ -22434,7 +22832,7 @@ program.command("organize-import [sourceDir]").description("Import existing mark
|
|
|
22434
22832
|
const hasMarkdown = files.length > 0;
|
|
22435
22833
|
const shouldBootstrap = Boolean(options.forceBootstrap || options.bootstrap || !hasMarkdown && options.bootstrapIfEmpty);
|
|
22436
22834
|
if (shouldBootstrap) {
|
|
22437
|
-
const outDir =
|
|
22835
|
+
const outDir = path25.resolve(options.out || path25.join(sourceRoot, "bootstrap-kb"));
|
|
22438
22836
|
const since = options.since || "180 days ago";
|
|
22439
22837
|
const maxCommits = options.maxCommits ? Math.max(1, parseInt(options.maxCommits, 10)) : 1e3;
|
|
22440
22838
|
console.log("\n\u{1F9E0} Bootstrapping markdown knowledge base...");
|
|
@@ -22473,13 +22871,13 @@ program.command("organize-import [sourceDir]").description("Import existing mark
|
|
|
22473
22871
|
let imported = 0;
|
|
22474
22872
|
let skipped = 0;
|
|
22475
22873
|
for (const file of targets) {
|
|
22476
|
-
const text = await
|
|
22874
|
+
const text = await fs19.promises.readFile(file, "utf8");
|
|
22477
22875
|
if (!text.trim()) {
|
|
22478
22876
|
skipped += 1;
|
|
22479
22877
|
continue;
|
|
22480
22878
|
}
|
|
22481
22879
|
const { namespace, categoryPath } = deriveNamespaceCategory(activeSourceRoot, file);
|
|
22482
|
-
const rel =
|
|
22880
|
+
const rel = path25.relative(activeSourceRoot, file);
|
|
22483
22881
|
const evidence = extractImportEvidence(text);
|
|
22484
22882
|
if (options.dryRun) {
|
|
22485
22883
|
console.log(`- ${rel} -> namespace=${namespace} category=${categoryPath.join("/")} confidence=${evidence.confidence || "n/a"} sources=${evidence.sources.length}`);
|