claude-memory-layer 1.0.39 → 1.0.41

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.
@@ -2772,6 +2772,127 @@ var SQLiteEventStore = class {
2772
2772
  updated_at TEXT DEFAULT (datetime('now'))
2773
2773
  );
2774
2774
 
2775
+ -- Memory Operations: facet assignments (derived, rebuildable projection)
2776
+ CREATE TABLE IF NOT EXISTS memory_facets (
2777
+ id TEXT PRIMARY KEY,
2778
+ target_type TEXT NOT NULL,
2779
+ target_id TEXT NOT NULL,
2780
+ dimension TEXT NOT NULL,
2781
+ value TEXT NOT NULL,
2782
+ confidence REAL NOT NULL DEFAULT 1.0,
2783
+ source TEXT NOT NULL DEFAULT 'manual',
2784
+ evidence_event_ids TEXT NOT NULL DEFAULT '[]',
2785
+ project_hash TEXT NOT NULL DEFAULT '',
2786
+ created_at TEXT NOT NULL,
2787
+ updated_at TEXT NOT NULL,
2788
+ UNIQUE(target_type, target_id, dimension, value, source, project_hash)
2789
+ );
2790
+
2791
+ -- Memory Operations: operational action projection
2792
+ CREATE TABLE IF NOT EXISTS memory_actions (
2793
+ action_id TEXT PRIMARY KEY,
2794
+ project_hash TEXT NOT NULL,
2795
+ title TEXT NOT NULL,
2796
+ status TEXT NOT NULL DEFAULT 'pending',
2797
+ priority INTEGER NOT NULL DEFAULT 0,
2798
+ source_event_ids TEXT NOT NULL DEFAULT '[]',
2799
+ related_entity_ids TEXT NOT NULL DEFAULT '[]',
2800
+ current_checkpoint_id TEXT,
2801
+ lease_id TEXT,
2802
+ created_at TEXT NOT NULL,
2803
+ updated_at TEXT NOT NULL
2804
+ );
2805
+
2806
+ -- Memory Operations: action dependency/reference edges
2807
+ CREATE TABLE IF NOT EXISTS memory_action_edges (
2808
+ edge_id TEXT PRIMARY KEY,
2809
+ src_action_id TEXT NOT NULL,
2810
+ rel_type TEXT NOT NULL,
2811
+ dst_type TEXT NOT NULL,
2812
+ dst_id TEXT NOT NULL,
2813
+ confidence REAL NOT NULL DEFAULT 1.0,
2814
+ source TEXT NOT NULL DEFAULT 'manual',
2815
+ created_at TEXT NOT NULL,
2816
+ UNIQUE(src_action_id, rel_type, dst_type, dst_id, source)
2817
+ );
2818
+
2819
+ -- Memory Operations: short-lived leases for operational work
2820
+ CREATE TABLE IF NOT EXISTS memory_leases (
2821
+ lease_id TEXT PRIMARY KEY,
2822
+ target_type TEXT NOT NULL,
2823
+ target_id TEXT NOT NULL,
2824
+ holder TEXT NOT NULL,
2825
+ expires_at TEXT NOT NULL,
2826
+ metadata_json TEXT,
2827
+ created_at TEXT NOT NULL,
2828
+ renewed_at TEXT,
2829
+ released_at TEXT
2830
+ );
2831
+
2832
+ -- Memory Operations: resumable checkpoints for delegated or long-running work
2833
+ CREATE TABLE IF NOT EXISTS memory_checkpoints (
2834
+ checkpoint_id TEXT PRIMARY KEY,
2835
+ project_hash TEXT NOT NULL,
2836
+ action_id TEXT,
2837
+ session_id TEXT,
2838
+ title TEXT NOT NULL,
2839
+ summary TEXT NOT NULL,
2840
+ state_json TEXT NOT NULL,
2841
+ source_event_ids TEXT NOT NULL DEFAULT '[]',
2842
+ created_at TEXT NOT NULL,
2843
+ expires_at TEXT
2844
+ );
2845
+
2846
+ -- Memory Operations: retention lifecycle score projection
2847
+ CREATE TABLE IF NOT EXISTS memory_retention_scores (
2848
+ score_id TEXT PRIMARY KEY,
2849
+ target_type TEXT NOT NULL,
2850
+ target_id TEXT NOT NULL,
2851
+ project_hash TEXT NOT NULL,
2852
+ policy_version TEXT NOT NULL,
2853
+ decision TEXT NOT NULL,
2854
+ lifecycle_score REAL NOT NULL,
2855
+ factors_json TEXT NOT NULL,
2856
+ reasons_json TEXT NOT NULL,
2857
+ dry_run_diff_json TEXT NOT NULL,
2858
+ source_event_ids TEXT NOT NULL DEFAULT '[]',
2859
+ evaluated_at TEXT NOT NULL,
2860
+ created_at TEXT NOT NULL,
2861
+ updated_at TEXT NOT NULL,
2862
+ UNIQUE(target_type, target_id, project_hash, policy_version)
2863
+ );
2864
+
2865
+ -- Memory Operations: procedural lessons derived from successful workflows
2866
+ CREATE TABLE IF NOT EXISTS memory_lessons (
2867
+ lesson_id TEXT PRIMARY KEY,
2868
+ project_hash TEXT NOT NULL DEFAULT '',
2869
+ name TEXT NOT NULL,
2870
+ trigger TEXT NOT NULL,
2871
+ steps_json TEXT NOT NULL,
2872
+ confidence REAL NOT NULL,
2873
+ source_session_ids TEXT NOT NULL DEFAULT '[]',
2874
+ source_event_ids TEXT NOT NULL DEFAULT '[]',
2875
+ failure_modes_json TEXT NOT NULL DEFAULT '[]',
2876
+ skill_candidate INTEGER NOT NULL DEFAULT 0,
2877
+ created_at TEXT NOT NULL,
2878
+ updated_at TEXT NOT NULL,
2879
+ UNIQUE(project_hash, name)
2880
+ );
2881
+
2882
+ -- Memory Operations: governance/audit trail for state-changing operations
2883
+ CREATE TABLE IF NOT EXISTS memory_governance_audit (
2884
+ audit_id TEXT PRIMARY KEY,
2885
+ operation TEXT NOT NULL,
2886
+ actor TEXT NOT NULL,
2887
+ project_hash TEXT,
2888
+ target_type TEXT NOT NULL,
2889
+ target_id TEXT NOT NULL,
2890
+ before_json TEXT,
2891
+ after_json TEXT,
2892
+ source_event_ids TEXT NOT NULL DEFAULT '[]',
2893
+ created_at TEXT NOT NULL
2894
+ );
2895
+
2775
2896
  -- Create indexes
2776
2897
  CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
2777
2898
  CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
@@ -2798,6 +2919,23 @@ var SQLiteEventStore = class {
2798
2919
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_created_at ON retrieval_traces(created_at DESC);
2799
2920
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_project_hash ON retrieval_traces(project_hash);
2800
2921
  CREATE INDEX IF NOT EXISTS idx_retrieval_traces_session_id ON retrieval_traces(session_id);
2922
+ CREATE INDEX IF NOT EXISTS idx_memory_facets_project_dimension_value ON memory_facets(project_hash, dimension, value);
2923
+ CREATE INDEX IF NOT EXISTS idx_memory_facets_target ON memory_facets(target_type, target_id);
2924
+ CREATE INDEX IF NOT EXISTS idx_memory_facets_dimension_value_confidence ON memory_facets(dimension, value, confidence DESC);
2925
+ CREATE INDEX IF NOT EXISTS idx_memory_actions_project_status_priority ON memory_actions(project_hash, status, priority DESC, updated_at DESC);
2926
+ CREATE INDEX IF NOT EXISTS idx_memory_action_edges_src ON memory_action_edges(src_action_id, rel_type);
2927
+ CREATE INDEX IF NOT EXISTS idx_memory_action_edges_dst ON memory_action_edges(dst_type, dst_id);
2928
+ CREATE INDEX IF NOT EXISTS idx_memory_leases_target_expires ON memory_leases(target_type, target_id, expires_at);
2929
+ CREATE INDEX IF NOT EXISTS idx_memory_checkpoints_project_action_created ON memory_checkpoints(project_hash, action_id, created_at DESC);
2930
+ CREATE INDEX IF NOT EXISTS idx_memory_checkpoints_project_session_created ON memory_checkpoints(project_hash, session_id, created_at DESC);
2931
+ CREATE INDEX IF NOT EXISTS idx_memory_retention_scores_project_decision_score ON memory_retention_scores(project_hash, decision, lifecycle_score ASC, evaluated_at DESC);
2932
+ CREATE INDEX IF NOT EXISTS idx_memory_retention_scores_target ON memory_retention_scores(target_type, target_id, project_hash);
2933
+ CREATE INDEX IF NOT EXISTS idx_memory_retention_scores_policy_evaluated ON memory_retention_scores(policy_version, evaluated_at DESC);
2934
+ CREATE INDEX IF NOT EXISTS idx_memory_lessons_project_confidence ON memory_lessons(project_hash, confidence DESC, updated_at DESC);
2935
+ CREATE INDEX IF NOT EXISTS idx_memory_lessons_skill_candidate ON memory_lessons(project_hash, skill_candidate, confidence DESC);
2936
+ CREATE INDEX IF NOT EXISTS idx_memory_lessons_updated ON memory_lessons(updated_at DESC);
2937
+ CREATE INDEX IF NOT EXISTS idx_memory_governance_audit_project_operation ON memory_governance_audit(project_hash, operation, created_at DESC);
2938
+ CREATE INDEX IF NOT EXISTS idx_memory_governance_audit_target ON memory_governance_audit(target_type, target_id, created_at DESC);
2801
2939
 
2802
2940
  -- FTS5 Full-Text Search for fast keyword search
2803
2941
  CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
@@ -2820,6 +2958,48 @@ var SQLiteEventStore = class {
2820
2958
  INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
2821
2959
  END;
2822
2960
  `);
2961
+ try {
2962
+ sqliteExec(this.db, `ALTER TABLE memory_action_edges ADD COLUMN source TEXT NOT NULL DEFAULT 'manual';`);
2963
+ } catch {
2964
+ }
2965
+ try {
2966
+ const edgeIndexes = sqliteAll(this.db, `PRAGMA index_list(memory_action_edges)`, []);
2967
+ const hasSourceAwareUnique = edgeIndexes.some((index) => {
2968
+ if (Number(index.unique) !== 1)
2969
+ return false;
2970
+ if (!/^[A-Za-z0-9_]+$/.test(index.name))
2971
+ return false;
2972
+ const escapedName = index.name.replace(/"/g, '""');
2973
+ const columns = sqliteAll(this.db, 'PRAGMA index_info("' + escapedName + '")', []).map((column) => column.name);
2974
+ return columns.length === 5 && columns[0] === "src_action_id" && columns[1] === "rel_type" && columns[2] === "dst_type" && columns[3] === "dst_id" && columns[4] === "source";
2975
+ });
2976
+ if (!hasSourceAwareUnique) {
2977
+ sqliteExec(this.db, `
2978
+ DROP TABLE IF EXISTS memory_action_edges_v2;
2979
+ CREATE TABLE memory_action_edges_v2 (
2980
+ edge_id TEXT PRIMARY KEY,
2981
+ src_action_id TEXT NOT NULL,
2982
+ rel_type TEXT NOT NULL,
2983
+ dst_type TEXT NOT NULL,
2984
+ dst_id TEXT NOT NULL,
2985
+ confidence REAL NOT NULL DEFAULT 1.0,
2986
+ source TEXT NOT NULL DEFAULT 'manual',
2987
+ created_at TEXT NOT NULL,
2988
+ UNIQUE(src_action_id, rel_type, dst_type, dst_id, source)
2989
+ );
2990
+ INSERT OR IGNORE INTO memory_action_edges_v2 (
2991
+ edge_id, src_action_id, rel_type, dst_type, dst_id, confidence, source, created_at
2992
+ )
2993
+ SELECT edge_id, src_action_id, rel_type, dst_type, dst_id, confidence, source, created_at
2994
+ FROM memory_action_edges;
2995
+ DROP TABLE memory_action_edges;
2996
+ ALTER TABLE memory_action_edges_v2 RENAME TO memory_action_edges;
2997
+ CREATE INDEX IF NOT EXISTS idx_memory_action_edges_src ON memory_action_edges(src_action_id, rel_type);
2998
+ CREATE INDEX IF NOT EXISTS idx_memory_action_edges_dst ON memory_action_edges(dst_type, dst_id);
2999
+ `);
3000
+ }
3001
+ } catch {
3002
+ }
2823
3003
  try {
2824
3004
  sqliteExec(this.db, `ALTER TABLE retrieval_traces ADD COLUMN selected_details_json TEXT;`);
2825
3005
  } catch {
@@ -4888,6 +5068,896 @@ var MemoryQueryService = class {
4888
5068
  }
4889
5069
  };
4890
5070
 
5071
+ // src/core/operations/facet-repository.ts
5072
+ import { randomUUID as randomUUID7 } from "crypto";
5073
+
5074
+ // src/core/operations/facets.ts
5075
+ import { z } from "zod";
5076
+ var FacetTargetTypeSchema = z.enum([
5077
+ "event",
5078
+ "entity",
5079
+ "edge",
5080
+ "consolidated_memory",
5081
+ "lesson",
5082
+ "action"
5083
+ ]);
5084
+ var BUILT_IN_FACET_DIMENSIONS = [
5085
+ "kind",
5086
+ "workflow",
5087
+ "artifact",
5088
+ "source",
5089
+ "privacy",
5090
+ "quality",
5091
+ "retention",
5092
+ "project"
5093
+ ];
5094
+ var customDimensionPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
5095
+ var TrimmedStringSchema = z.preprocess(
5096
+ (value) => typeof value === "string" ? value.trim() : value,
5097
+ z.string().min(1)
5098
+ );
5099
+ var FacetDimensionSchema = z.preprocess(
5100
+ (value) => typeof value === "string" ? value.trim() : value,
5101
+ z.string().min(1).max(64).refine((value) => BUILT_IN_FACET_DIMENSIONS.indexOf(value) !== -1 || customDimensionPattern.test(value), {
5102
+ message: "Facet dimension must be built-in or lowercase kebab-case"
5103
+ })
5104
+ );
5105
+ var FacetSourceSchema = z.enum(["manual", "imported", "derived", "llm", "system"]).default("manual");
5106
+ var EvidenceEventIdsSchema = z.preprocess(
5107
+ (value) => Array.isArray(value) ? value.map((item) => typeof item === "string" ? item.trim() : item).filter(Boolean) : value,
5108
+ z.array(z.string().min(1)).default([])
5109
+ );
5110
+ var OptionalTrimmedStringSchema = z.preprocess(
5111
+ (value) => typeof value === "string" ? value.trim() : value,
5112
+ z.string().min(1).optional()
5113
+ );
5114
+ var MemoryFacetAssignmentInputSchema = z.object({
5115
+ targetType: FacetTargetTypeSchema,
5116
+ targetId: TrimmedStringSchema,
5117
+ dimension: FacetDimensionSchema,
5118
+ value: TrimmedStringSchema,
5119
+ confidence: z.number().min(0).max(1).default(1),
5120
+ source: FacetSourceSchema,
5121
+ evidenceEventIds: EvidenceEventIdsSchema,
5122
+ projectHash: OptionalTrimmedStringSchema,
5123
+ actor: OptionalTrimmedStringSchema
5124
+ });
5125
+ var MemoryFacetAssignmentSchema = MemoryFacetAssignmentInputSchema.extend({
5126
+ id: z.string().min(1),
5127
+ createdAt: z.date(),
5128
+ updatedAt: z.date()
5129
+ });
5130
+ var FacetRemoveInputSchema = z.object({
5131
+ targetType: FacetTargetTypeSchema,
5132
+ targetId: TrimmedStringSchema,
5133
+ dimension: FacetDimensionSchema,
5134
+ value: TrimmedStringSchema,
5135
+ source: FacetSourceSchema,
5136
+ projectHash: OptionalTrimmedStringSchema,
5137
+ actor: OptionalTrimmedStringSchema
5138
+ });
5139
+ var FacetQuerySchema = z.object({
5140
+ targetType: FacetTargetTypeSchema.optional(),
5141
+ targetId: OptionalTrimmedStringSchema,
5142
+ dimension: FacetDimensionSchema.optional(),
5143
+ value: OptionalTrimmedStringSchema,
5144
+ source: FacetSourceSchema.optional(),
5145
+ projectHash: OptionalTrimmedStringSchema,
5146
+ limit: z.number().int().positive().max(500).default(100)
5147
+ });
5148
+ function parseFacetAssignmentInput(input) {
5149
+ return MemoryFacetAssignmentInputSchema.parse(input);
5150
+ }
5151
+ function parseFacetRemoveInput(input) {
5152
+ return FacetRemoveInputSchema.parse(input);
5153
+ }
5154
+ function parseFacetQuery(input) {
5155
+ return FacetQuerySchema.parse(input ?? {});
5156
+ }
5157
+
5158
+ // src/core/operations/governance-audit.ts
5159
+ import { randomUUID as randomUUID6 } from "crypto";
5160
+ var MEMORY_GOVERNANCE_AUDIT_OPERATIONS = [
5161
+ "facet_tag",
5162
+ "action_update",
5163
+ "lease_acquire",
5164
+ "checkpoint_create",
5165
+ "retention_score",
5166
+ "quarantine",
5167
+ "verify",
5168
+ "lesson_promote"
5169
+ ];
5170
+ function normalizeRequiredString(value, fieldName) {
5171
+ const normalized = value.trim();
5172
+ if (!normalized) {
5173
+ throw new Error(`${fieldName} is required`);
5174
+ }
5175
+ return normalized;
5176
+ }
5177
+ function normalizeOptionalString(value) {
5178
+ const normalized = value?.trim();
5179
+ return normalized ? normalized : void 0;
5180
+ }
5181
+ var REDACTED = "[REDACTED]";
5182
+ var sensitiveKeyPattern = /(?:api[_-]?key|secret|password|passwd|token|access[_-]?token|client[_-]?secret|crtfc[_-]?key|hashkey|serviceKey)/i;
5183
+ var POSIX_ABSOLUTE_PATH_PATTERN = /(^|[^A-Za-z0-9._\/\\-])\/(?!\/)[^\n\r"'<>|`]*/g;
5184
+ var WINDOWS_DRIVE_PATH_PATTERN = /(^|[^A-Za-z0-9._\/\\-])(?:[A-Za-z]:[\\/][^\n\r"'<>|`]*)/g;
5185
+ var WINDOWS_UNC_PATH_PATTERN = /(^|[^A-Za-z0-9._\/\\-])(?:\\\\[^\\\n\r"'<>|`]+\\[^\n\r"'<>|`]*)/g;
5186
+ var credentialQueryPattern = /\b((?:api[_-]?key|token|access[_-]?token|client[_-]?secret|crtfc[_-]?key|hashkey|serviceKey)=)[^&\s`"'<>]+/gi;
5187
+ var credentialAssignmentPattern = /\b((?:api[_-]?key|secret|password|passwd|token|access[_-]?token|client[_-]?secret|crtfc[_-]?key|hashkey|serviceKey)\s*[:=]\s*)[^\s`"'<>},]+/gi;
5188
+ function redactAbsolutePaths(value) {
5189
+ return value.replace(WINDOWS_UNC_PATH_PATTERN, (_match, prefix) => `${prefix}${REDACTED}`).replace(WINDOWS_DRIVE_PATH_PATTERN, (_match, prefix) => `${prefix}${REDACTED}`).replace(POSIX_ABSOLUTE_PATH_PATTERN, (_match, prefix) => `${prefix}${REDACTED}`);
5190
+ }
5191
+ function sanitizeAuditString(value) {
5192
+ return redactAbsolutePaths(value).replace(credentialQueryPattern, `$1${REDACTED}`).replace(credentialAssignmentPattern, `$1${REDACTED}`);
5193
+ }
5194
+ function sanitizeGovernanceAuditValue(value, key) {
5195
+ if (key && sensitiveKeyPattern.test(key)) {
5196
+ return REDACTED;
5197
+ }
5198
+ if (typeof value === "string") {
5199
+ return sanitizeAuditString(value);
5200
+ }
5201
+ if (value instanceof Date) {
5202
+ return sanitizeAuditString(value.toISOString());
5203
+ }
5204
+ if (Array.isArray(value)) {
5205
+ return value.map((item) => sanitizeGovernanceAuditValue(item));
5206
+ }
5207
+ if (value && typeof value === "object") {
5208
+ const sanitized = {};
5209
+ for (const [entryKey, entryValue] of Object.entries(value)) {
5210
+ sanitized[sanitizeAuditString(entryKey)] = sanitizeGovernanceAuditValue(entryValue, entryKey);
5211
+ }
5212
+ return sanitized;
5213
+ }
5214
+ return value;
5215
+ }
5216
+ function sanitizeAuditJson(value) {
5217
+ if (value === void 0)
5218
+ return void 0;
5219
+ return sanitizeGovernanceAuditValue(value);
5220
+ }
5221
+ function normalizeSourceEventIds(sourceEventIds) {
5222
+ return (sourceEventIds || []).map((sourceEventId) => sanitizeAuditString(sourceEventId.trim())).filter((sourceEventId) => sourceEventId.length > 0);
5223
+ }
5224
+ function normalizeOperation(operation) {
5225
+ if (MEMORY_GOVERNANCE_AUDIT_OPERATIONS.indexOf(operation) === -1) {
5226
+ throw new Error(`Unsupported governance audit operation: ${operation}`);
5227
+ }
5228
+ return operation;
5229
+ }
5230
+ async function writeGovernanceAuditEntry(db, input) {
5231
+ const entry = {
5232
+ auditId: randomUUID6(),
5233
+ operation: normalizeOperation(input.operation),
5234
+ actor: sanitizeAuditString(normalizeRequiredString(input.actor, "actor")),
5235
+ projectHash: normalizeOptionalString(input.projectHash),
5236
+ targetType: normalizeRequiredString(input.targetType, "targetType"),
5237
+ targetId: sanitizeAuditString(normalizeRequiredString(input.targetId, "targetId")),
5238
+ beforeJson: sanitizeAuditJson(input.beforeJson),
5239
+ afterJson: sanitizeAuditJson(input.afterJson),
5240
+ sourceEventIds: normalizeSourceEventIds(input.sourceEventIds),
5241
+ createdAt: /* @__PURE__ */ new Date()
5242
+ };
5243
+ sqliteRun(
5244
+ db,
5245
+ `INSERT INTO memory_governance_audit (
5246
+ audit_id, operation, actor, project_hash, target_type, target_id,
5247
+ before_json, after_json, source_event_ids, created_at
5248
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5249
+ [
5250
+ entry.auditId,
5251
+ entry.operation,
5252
+ entry.actor,
5253
+ entry.projectHash ?? null,
5254
+ entry.targetType,
5255
+ entry.targetId,
5256
+ entry.beforeJson === void 0 ? null : JSON.stringify(entry.beforeJson),
5257
+ entry.afterJson === void 0 ? null : JSON.stringify(entry.afterJson),
5258
+ JSON.stringify(entry.sourceEventIds),
5259
+ entry.createdAt.toISOString()
5260
+ ]
5261
+ );
5262
+ return entry;
5263
+ }
5264
+
5265
+ // src/core/operations/facet-repository.ts
5266
+ function parseStringArray(value) {
5267
+ if (typeof value !== "string")
5268
+ return [];
5269
+ try {
5270
+ const parsed = JSON.parse(value);
5271
+ if (!Array.isArray(parsed))
5272
+ return [];
5273
+ return parsed.filter((item) => typeof item === "string" && item.length > 0);
5274
+ } catch {
5275
+ return [];
5276
+ }
5277
+ }
5278
+ function projectHashToStorage(projectHash) {
5279
+ return projectHash ?? "";
5280
+ }
5281
+ function rowToFacet(row) {
5282
+ const projectHash = typeof row.project_hash === "string" && row.project_hash.length > 0 ? row.project_hash : void 0;
5283
+ return {
5284
+ id: row.id,
5285
+ targetType: FacetTargetTypeSchema.parse(row.target_type),
5286
+ targetId: row.target_id,
5287
+ dimension: row.dimension,
5288
+ value: row.value,
5289
+ confidence: Number(row.confidence),
5290
+ source: FacetSourceSchema.parse(row.source),
5291
+ evidenceEventIds: parseStringArray(row.evidence_event_ids),
5292
+ projectHash,
5293
+ createdAt: toDateFromSQLite(row.created_at),
5294
+ updatedAt: toDateFromSQLite(row.updated_at)
5295
+ };
5296
+ }
5297
+ function facetToAuditJson(facet) {
5298
+ return {
5299
+ id: facet.id,
5300
+ targetType: facet.targetType,
5301
+ targetId: facet.targetId,
5302
+ dimension: facet.dimension,
5303
+ value: facet.value,
5304
+ confidence: facet.confidence,
5305
+ source: facet.source,
5306
+ evidenceEventIds: facet.evidenceEventIds,
5307
+ projectHash: facet.projectHash,
5308
+ createdAt: facet.createdAt.toISOString(),
5309
+ updatedAt: facet.updatedAt.toISOString()
5310
+ };
5311
+ }
5312
+ var FacetRepository = class {
5313
+ constructor(db) {
5314
+ this.db = db;
5315
+ }
5316
+ async assign(input) {
5317
+ const assignment = parseFacetAssignmentInput(input);
5318
+ const existing = this.findByUniqueKey(assignment);
5319
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5320
+ if (existing) {
5321
+ sqliteRun(
5322
+ this.db,
5323
+ `UPDATE memory_facets
5324
+ SET confidence = ?, evidence_event_ids = ?, project_hash = ?, updated_at = ?
5325
+ WHERE id = ?`,
5326
+ [
5327
+ assignment.confidence,
5328
+ JSON.stringify(assignment.evidenceEventIds),
5329
+ projectHashToStorage(assignment.projectHash),
5330
+ now,
5331
+ existing.id
5332
+ ]
5333
+ );
5334
+ const saved2 = this.getById(existing.id);
5335
+ await this.auditAssignment(assignment, existing, saved2);
5336
+ return saved2;
5337
+ }
5338
+ const id = randomUUID7();
5339
+ sqliteRun(
5340
+ this.db,
5341
+ `INSERT INTO memory_facets (
5342
+ id, target_type, target_id, dimension, value, confidence, source,
5343
+ evidence_event_ids, project_hash, created_at, updated_at
5344
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5345
+ [
5346
+ id,
5347
+ assignment.targetType,
5348
+ assignment.targetId,
5349
+ assignment.dimension,
5350
+ assignment.value,
5351
+ assignment.confidence,
5352
+ assignment.source,
5353
+ JSON.stringify(assignment.evidenceEventIds),
5354
+ projectHashToStorage(assignment.projectHash),
5355
+ now,
5356
+ now
5357
+ ]
5358
+ );
5359
+ const saved = this.getById(id);
5360
+ await this.auditAssignment(assignment, null, saved);
5361
+ return saved;
5362
+ }
5363
+ async remove(input) {
5364
+ const removeInput = parseFacetRemoveInput(input);
5365
+ const { sql, params } = this.removeSql(removeInput);
5366
+ const result = sqliteRun(this.db, sql, params);
5367
+ return result.changes > 0;
5368
+ }
5369
+ async query(input) {
5370
+ const query = parseFacetQuery(input);
5371
+ const { sql, params } = this.querySql(query);
5372
+ const rows = sqliteAll(this.db, sql, params);
5373
+ return rows.map(rowToFacet);
5374
+ }
5375
+ async listForTarget(targetType, targetId) {
5376
+ const parsedTargetType = FacetTargetTypeSchema.parse(targetType);
5377
+ const trimmedTargetId = targetId.trim();
5378
+ if (!trimmedTargetId) {
5379
+ throw new Error("targetId is required");
5380
+ }
5381
+ return this.query({ targetType: parsedTargetType, targetId: trimmedTargetId, limit: 500 });
5382
+ }
5383
+ getById(id) {
5384
+ const row = sqliteGet(this.db, `SELECT * FROM memory_facets WHERE id = ?`, [id]);
5385
+ if (!row) {
5386
+ throw new Error(`Memory facet not found after write: ${id}`);
5387
+ }
5388
+ return rowToFacet(row);
5389
+ }
5390
+ findByUniqueKey(input) {
5391
+ const row = sqliteGet(
5392
+ this.db,
5393
+ `SELECT * FROM memory_facets
5394
+ WHERE target_type = ? AND target_id = ? AND dimension = ? AND value = ? AND source = ? AND project_hash = ?`,
5395
+ [
5396
+ input.targetType,
5397
+ input.targetId,
5398
+ input.dimension,
5399
+ input.value,
5400
+ input.source,
5401
+ projectHashToStorage(input.projectHash)
5402
+ ]
5403
+ );
5404
+ return row ? rowToFacet(row) : null;
5405
+ }
5406
+ async auditAssignment(input, before, after) {
5407
+ await writeGovernanceAuditEntry(this.db, {
5408
+ operation: "facet_tag",
5409
+ actor: input.actor ?? "cml-core",
5410
+ projectHash: input.projectHash,
5411
+ targetType: input.targetType,
5412
+ targetId: input.targetId,
5413
+ beforeJson: before ? facetToAuditJson(before) : void 0,
5414
+ afterJson: facetToAuditJson(after),
5415
+ sourceEventIds: input.evidenceEventIds
5416
+ });
5417
+ }
5418
+ querySql(query) {
5419
+ const clauses = [];
5420
+ const params = [];
5421
+ if (query.targetType) {
5422
+ clauses.push("target_type = ?");
5423
+ params.push(query.targetType);
5424
+ }
5425
+ if (query.targetId) {
5426
+ clauses.push("target_id = ?");
5427
+ params.push(query.targetId);
5428
+ }
5429
+ if (query.dimension) {
5430
+ clauses.push("dimension = ?");
5431
+ params.push(query.dimension);
5432
+ }
5433
+ if (query.value) {
5434
+ clauses.push("value = ?");
5435
+ params.push(query.value);
5436
+ }
5437
+ if (query.source) {
5438
+ clauses.push("source = ?");
5439
+ params.push(query.source);
5440
+ }
5441
+ if (query.projectHash) {
5442
+ clauses.push("project_hash = ?");
5443
+ params.push(query.projectHash);
5444
+ }
5445
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
5446
+ params.push(query.limit);
5447
+ return {
5448
+ sql: `SELECT * FROM memory_facets ${where} ORDER BY confidence DESC, updated_at DESC LIMIT ?`,
5449
+ params
5450
+ };
5451
+ }
5452
+ removeSql(input) {
5453
+ const clauses = [
5454
+ "target_type = ?",
5455
+ "target_id = ?",
5456
+ "dimension = ?",
5457
+ "value = ?",
5458
+ "source = ?",
5459
+ "project_hash = ?"
5460
+ ];
5461
+ const params = [
5462
+ input.targetType,
5463
+ input.targetId,
5464
+ input.dimension,
5465
+ input.value,
5466
+ input.source,
5467
+ projectHashToStorage(input.projectHash)
5468
+ ];
5469
+ return {
5470
+ sql: `DELETE FROM memory_facets WHERE ${clauses.join(" AND ")}`,
5471
+ params
5472
+ };
5473
+ }
5474
+ };
5475
+
5476
+ // src/core/operations/graph-path-service.ts
5477
+ var DEFAULT_WEIGHT = 0.5;
5478
+ var MAX_HOPS = 2;
5479
+ var DEFAULT_MAX_RESULTS = 20;
5480
+ var MAX_RESULTS = 100;
5481
+ var GraphPathService = class {
5482
+ constructor(db) {
5483
+ this.db = db;
5484
+ }
5485
+ expand(input) {
5486
+ const graph = this.loadGraph(input.direction ?? "both");
5487
+ const effectiveMaxHops = normalizeMaxHops(input.maxHops);
5488
+ const maxResults = normalizeMaxResults(input.maxResults);
5489
+ const startNodes = input.startNodes.map((node) => graph.node(node));
5490
+ const startKeys = new Set(input.startNodes.map(nodeKey));
5491
+ const bestByTarget = /* @__PURE__ */ new Map();
5492
+ const queue = startNodes.map((node) => ({
5493
+ key: nodeKey(node),
5494
+ hops: 0,
5495
+ totalCost: 0,
5496
+ steps: [],
5497
+ visited: /* @__PURE__ */ new Set([nodeKey(node)])
5498
+ }));
5499
+ while (queue.length > 0) {
5500
+ queue.sort((a, b) => a.totalCost - b.totalCost || a.hops - b.hops || a.key.localeCompare(b.key));
5501
+ const current = queue.shift();
5502
+ if (current.hops >= effectiveMaxHops)
5503
+ continue;
5504
+ for (const edge of graph.adjacency.get(current.key) ?? []) {
5505
+ if (current.visited.has(edge.toKey))
5506
+ continue;
5507
+ const nextHops = current.hops + 1;
5508
+ const nextTotalCost = current.totalCost + edge.step.cost;
5509
+ const nextSteps = [...current.steps, edge.step];
5510
+ const nextSignature = pathSignature(nextSteps);
5511
+ const existing = bestByTarget.get(edge.toKey);
5512
+ if (!existing || isBetterPath(nextTotalCost, nextHops, nextSignature, existing)) {
5513
+ if (!startKeys.has(edge.toKey)) {
5514
+ bestByTarget.set(edge.toKey, { hops: nextHops, totalCost: nextTotalCost, signature: nextSignature, steps: nextSteps });
5515
+ }
5516
+ const nextVisited = new Set(current.visited);
5517
+ nextVisited.add(edge.toKey);
5518
+ queue.push({
5519
+ key: edge.toKey,
5520
+ hops: nextHops,
5521
+ totalCost: nextTotalCost,
5522
+ steps: nextSteps,
5523
+ visited: nextVisited
5524
+ });
5525
+ }
5526
+ }
5527
+ }
5528
+ const paths = Array.from(bestByTarget.entries()).map(([key, path12]) => ({
5529
+ target: graph.node(nodeFromKey(key)),
5530
+ hops: path12.hops,
5531
+ totalCost: path12.totalCost,
5532
+ scoreContribution: path12.totalCost > 0 ? 1 / path12.totalCost : 0,
5533
+ steps: path12.steps
5534
+ })).sort((a, b) => b.scoreContribution - a.scoreContribution || a.hops - b.hops || a.target.name.localeCompare(b.target.name)).slice(0, maxResults);
5535
+ return { startNodes, effectiveMaxHops, paths };
5536
+ }
5537
+ loadGraph(direction) {
5538
+ const entityLabels = new Map(
5539
+ sqliteAll(this.db, `SELECT entity_id, title FROM entities WHERE status = 'active'`).map((row) => [row.entity_id, row.title])
5540
+ );
5541
+ const labelNode = (node) => ({
5542
+ ...node,
5543
+ name: node.type === "entity" ? entityLabels.get(node.id) ?? node.id : node.id
5544
+ });
5545
+ const adjacency = /* @__PURE__ */ new Map();
5546
+ const edges = sqliteAll(
5547
+ this.db,
5548
+ `SELECT edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json FROM edges`
5549
+ );
5550
+ for (const edge of edges) {
5551
+ const src = labelNode({ type: edge.src_type, id: edge.src_id });
5552
+ const dst = labelNode({ type: edge.dst_type, id: edge.dst_id });
5553
+ const weight = edgeWeight(edge.meta_json);
5554
+ const cost = 1 / weight;
5555
+ const baseStep = {
5556
+ edgeId: edge.edge_id,
5557
+ relationType: edge.rel_type,
5558
+ from: src,
5559
+ to: dst,
5560
+ weight,
5561
+ cost,
5562
+ scoreContribution: weight
5563
+ };
5564
+ if (direction === "outgoing" || direction === "both") {
5565
+ addTraversal(adjacency, nodeKey(src), {
5566
+ toKey: nodeKey(dst),
5567
+ step: { ...baseStep, direction: "outgoing" }
5568
+ });
5569
+ }
5570
+ if (direction === "incoming" || direction === "both") {
5571
+ addTraversal(adjacency, nodeKey(dst), {
5572
+ toKey: nodeKey(src),
5573
+ step: { ...baseStep, direction: "incoming" }
5574
+ });
5575
+ }
5576
+ }
5577
+ return { adjacency, node: labelNode };
5578
+ }
5579
+ };
5580
+ function addTraversal(adjacency, fromKey, edge) {
5581
+ const edges = adjacency.get(fromKey) ?? [];
5582
+ edges.push(edge);
5583
+ adjacency.set(fromKey, edges);
5584
+ }
5585
+ function normalizeMaxHops(maxHops) {
5586
+ if (maxHops === void 0)
5587
+ return 1;
5588
+ if (!Number.isFinite(maxHops))
5589
+ return MAX_HOPS;
5590
+ return Math.min(Math.max(0, Math.trunc(maxHops)), MAX_HOPS);
5591
+ }
5592
+ function normalizeMaxResults(maxResults) {
5593
+ if (maxResults === void 0)
5594
+ return DEFAULT_MAX_RESULTS;
5595
+ if (!Number.isFinite(maxResults))
5596
+ return DEFAULT_MAX_RESULTS;
5597
+ return Math.min(Math.max(0, Math.trunc(maxResults)), MAX_RESULTS);
5598
+ }
5599
+ function isBetterPath(totalCost, hops, signature, existing) {
5600
+ return totalCost < existing.totalCost || totalCost === existing.totalCost && hops < existing.hops || totalCost === existing.totalCost && hops === existing.hops && signature < existing.signature;
5601
+ }
5602
+ function pathSignature(steps) {
5603
+ return steps.map((step) => `${step.edgeId}:${step.direction}:${nodeKey(step.from)}>${nodeKey(step.to)}`).join("|");
5604
+ }
5605
+ function edgeWeight(metaJson) {
5606
+ const meta = parseMeta(metaJson);
5607
+ const raw = meta.weight;
5608
+ if (typeof raw === "number" && Number.isFinite(raw) && raw > 0)
5609
+ return raw;
5610
+ if (typeof raw === "string") {
5611
+ const parsed = Number(raw);
5612
+ if (Number.isFinite(parsed) && parsed > 0)
5613
+ return parsed;
5614
+ }
5615
+ return DEFAULT_WEIGHT;
5616
+ }
5617
+ function parseMeta(metaJson) {
5618
+ if (!metaJson)
5619
+ return {};
5620
+ try {
5621
+ const parsed = JSON.parse(metaJson);
5622
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
5623
+ } catch {
5624
+ return {};
5625
+ }
5626
+ }
5627
+ function nodeKey(node) {
5628
+ return `${node.type}:${node.id}`;
5629
+ }
5630
+ function nodeFromKey(key) {
5631
+ const index = key.indexOf(":");
5632
+ if (index === -1)
5633
+ return { type: "entity", id: key };
5634
+ return { type: key.slice(0, index), id: key.slice(index + 1) };
5635
+ }
5636
+
5637
+ // src/core/operations/query-entity-extractor.ts
5638
+ var DEFAULT_MAX_CANDIDATES = 20;
5639
+ var MAX_CANDIDATES = 100;
5640
+ var MAX_CANDIDATE_TEXT_LENGTH = 200;
5641
+ var SOURCE_PRIORITY = {
5642
+ entity_alias: 0,
5643
+ quoted: 1,
5644
+ file_path: 2,
5645
+ package_identifier: 3,
5646
+ capitalized_term: 4
5647
+ };
5648
+ var SOURCE_CONFIDENCE = {
5649
+ entity_alias: 0.95,
5650
+ quoted: 0.85,
5651
+ file_path: 0.8,
5652
+ package_identifier: 0.75,
5653
+ capitalized_term: 0.6
5654
+ };
5655
+ var SENTENCE_START_STOPWORDS = /* @__PURE__ */ new Set([
5656
+ "A",
5657
+ "An",
5658
+ "And",
5659
+ "Are",
5660
+ "Can",
5661
+ "Compare",
5662
+ "Does",
5663
+ "Explain",
5664
+ "Find",
5665
+ "How",
5666
+ "I",
5667
+ "If",
5668
+ "In",
5669
+ "Is",
5670
+ "List",
5671
+ "Please",
5672
+ "Should",
5673
+ "Show",
5674
+ "Tell",
5675
+ "The",
5676
+ "This",
5677
+ "Use",
5678
+ "What",
5679
+ "When",
5680
+ "Where",
5681
+ "Which",
5682
+ "Why",
5683
+ "With"
5684
+ ]);
5685
+ var QueryEntityExtractor = class {
5686
+ constructor(db) {
5687
+ this.db = db;
5688
+ }
5689
+ extract(query, options = {}) {
5690
+ const maxCandidates = normalizeMaxCandidates(options.maxCandidates);
5691
+ const candidates = [];
5692
+ const quotedRanges = this.extractQuoted(query, candidates);
5693
+ if (options.includeAliases !== false) {
5694
+ this.extractKnownAliases(query, candidates);
5695
+ }
5696
+ this.extractFilePaths(query, candidates);
5697
+ this.extractPackageIdentifiers(query, candidates);
5698
+ this.extractCapitalizedTerms(query, candidates, quotedRanges);
5699
+ return {
5700
+ query,
5701
+ candidates: dedupeAndSort(candidates).slice(0, maxCandidates).map(stripPriority)
5702
+ };
5703
+ }
5704
+ extractQuoted(query, candidates) {
5705
+ const ranges = [];
5706
+ const regex = /(["'`])((?:(?!\1)[^\n]){2,200})\1/g;
5707
+ let match;
5708
+ while ((match = regex.exec(query)) !== null) {
5709
+ const text = cleanCandidateText(match[2] ?? "");
5710
+ if (!isUsefulCandidate(text))
5711
+ continue;
5712
+ const start = match.index + 1;
5713
+ const end = start + text.length;
5714
+ ranges.push([match.index, match.index + match[0].length]);
5715
+ pushCandidate(candidates, {
5716
+ text,
5717
+ source: "quoted",
5718
+ start,
5719
+ end
5720
+ });
5721
+ }
5722
+ return ranges;
5723
+ }
5724
+ extractKnownAliases(query, candidates) {
5725
+ if (!this.db)
5726
+ return;
5727
+ const rows = sqliteAll(
5728
+ this.db,
5729
+ `SELECT
5730
+ a.entity_id,
5731
+ a.entity_type,
5732
+ a.canonical_key AS alias_key,
5733
+ e.canonical_key AS entity_canonical_key,
5734
+ e.title
5735
+ FROM entity_aliases a
5736
+ JOIN entities e ON e.entity_id = a.entity_id
5737
+ WHERE e.status = 'active'
5738
+ ORDER BY e.title COLLATE NOCASE, a.canonical_key COLLATE NOCASE`
5739
+ );
5740
+ const normalizedQuery = normalizeForContainment(query);
5741
+ const seenAliases = /* @__PURE__ */ new Set();
5742
+ for (const row of rows) {
5743
+ const aliasLabels = uniqueStrings([
5744
+ row.title,
5745
+ aliasLabelFromCanonicalKey(row.alias_key),
5746
+ aliasLabelFromCanonicalKey(row.entity_canonical_key)
5747
+ ]).filter(isUsefulCandidate);
5748
+ for (const alias of aliasLabels) {
5749
+ const normalizedAlias = normalizeForContainment(alias);
5750
+ if (!normalizedAlias || !containsPhrase(normalizedQuery, normalizedAlias))
5751
+ continue;
5752
+ const aliasKey = `${row.entity_id}:${normalizedAlias}`;
5753
+ if (seenAliases.has(aliasKey))
5754
+ continue;
5755
+ seenAliases.add(aliasKey);
5756
+ const range = findRange(query, alias);
5757
+ pushCandidate(candidates, {
5758
+ text: row.title,
5759
+ source: "entity_alias",
5760
+ start: range.start,
5761
+ end: range.end,
5762
+ entityId: row.entity_id,
5763
+ entityType: row.entity_type,
5764
+ canonicalKey: row.entity_canonical_key,
5765
+ matchedAlias: normalizedAlias
5766
+ });
5767
+ }
5768
+ }
5769
+ }
5770
+ extractFilePaths(query, candidates) {
5771
+ const regex = /(^|[\s([{<])((?:\.{1,2}\/|~\/|\/)?(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\.[A-Za-z0-9][A-Za-z0-9._-]*)(?=$|[\s)\]},>`.,;:!?])/g;
5772
+ let match;
5773
+ while ((match = regex.exec(query)) !== null) {
5774
+ const text = cleanCandidateText(match[2] ?? "");
5775
+ if (!isUsefulCandidate(text))
5776
+ continue;
5777
+ const start = match.index + (match[1]?.length ?? 0);
5778
+ pushCandidate(candidates, {
5779
+ text,
5780
+ source: "file_path",
5781
+ start,
5782
+ end: start + text.length
5783
+ });
5784
+ }
5785
+ }
5786
+ extractPackageIdentifiers(query, candidates) {
5787
+ const regex = /(^|[\s([{<`])(@[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*|[a-z0-9][a-z0-9._]*[-_][a-z0-9._-]*)(?=$|[\s)\]},>`.,;:!?])/gi;
5788
+ let match;
5789
+ while ((match = regex.exec(query)) !== null) {
5790
+ const text = cleanCandidateText(match[2] ?? "");
5791
+ if (!isUsefulCandidate(text) || text.includes("/.") || text.includes("./"))
5792
+ continue;
5793
+ const start = match.index + (match[1]?.length ?? 0);
5794
+ pushCandidate(candidates, {
5795
+ text,
5796
+ source: "package_identifier",
5797
+ start,
5798
+ end: start + text.length
5799
+ });
5800
+ }
5801
+ }
5802
+ extractCapitalizedTerms(query, candidates, ignoredRanges) {
5803
+ const tokens = collectCapitalizedTokens(query).filter((token) => !isInsideAnyRange(token.start, ignoredRanges)).filter((token) => !SENTENCE_START_STOPWORDS.has(token.text));
5804
+ const groups = [];
5805
+ let current = [];
5806
+ for (const token of tokens) {
5807
+ const previous = current[current.length - 1];
5808
+ if (previous && query.slice(previous.end, token.start).match(/^\s+$/)) {
5809
+ current.push(token);
5810
+ } else {
5811
+ if (current.length > 0)
5812
+ groups.push(current);
5813
+ current = [token];
5814
+ }
5815
+ }
5816
+ if (current.length > 0)
5817
+ groups.push(current);
5818
+ for (const group of groups) {
5819
+ if (group.length === 1 && !isStrongSingleCapitalized(group[0].text))
5820
+ continue;
5821
+ const start = group[0].start;
5822
+ const end = group[group.length - 1].end;
5823
+ const text = query.slice(start, end);
5824
+ if (!isUsefulCandidate(text))
5825
+ continue;
5826
+ pushCandidate(candidates, {
5827
+ text,
5828
+ source: "capitalized_term",
5829
+ start,
5830
+ end
5831
+ });
5832
+ }
5833
+ }
5834
+ };
5835
+ function collectCapitalizedTokens(query) {
5836
+ const regex = /\b(?:[A-Z]{2,}[A-Z0-9]*|[A-Z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*)\b/g;
5837
+ const tokens = [];
5838
+ let match;
5839
+ while ((match = regex.exec(query)) !== null) {
5840
+ tokens.push({ text: match[0], start: match.index, end: match.index + match[0].length });
5841
+ }
5842
+ return tokens;
5843
+ }
5844
+ function pushCandidate(candidates, input) {
5845
+ const text = cleanCandidateText(input.text);
5846
+ if (!isUsefulCandidate(text))
5847
+ return;
5848
+ const source = input.source;
5849
+ candidates.push({
5850
+ ...input,
5851
+ text,
5852
+ normalized: normalizeCandidate(text),
5853
+ confidence: input.confidence ?? SOURCE_CONFIDENCE[source],
5854
+ priority: SOURCE_PRIORITY[source]
5855
+ });
5856
+ }
5857
+ function dedupeAndSort(candidates) {
5858
+ const sorted = [...candidates].sort(compareCandidates);
5859
+ const seenAliasKeys = /* @__PURE__ */ new Set();
5860
+ const seenNormalized = /* @__PURE__ */ new Set();
5861
+ const result = [];
5862
+ for (const candidate of sorted) {
5863
+ if (candidate.source === "entity_alias") {
5864
+ const aliasKey = `alias:${candidate.entityId ?? ""}:${normalizeCandidate(candidate.matchedAlias ?? candidate.text)}`;
5865
+ if (seenAliasKeys.has(aliasKey))
5866
+ continue;
5867
+ seenAliasKeys.add(aliasKey);
5868
+ seenNormalized.add(candidate.normalized);
5869
+ result.push(candidate);
5870
+ continue;
5871
+ }
5872
+ if (seenNormalized.has(candidate.normalized))
5873
+ continue;
5874
+ seenNormalized.add(candidate.normalized);
5875
+ result.push(candidate);
5876
+ }
5877
+ return result.sort(compareCandidates);
5878
+ }
5879
+ function compareCandidates(a, b) {
5880
+ return a.priority - b.priority || a.start - b.start || a.end - b.end || compareStrings(a.text, b.text) || compareStrings(a.entityId ?? "", b.entityId ?? "") || compareStrings(a.matchedAlias ?? "", b.matchedAlias ?? "");
5881
+ }
5882
+ function compareStrings(a, b) {
5883
+ if (a === b)
5884
+ return 0;
5885
+ return a < b ? -1 : 1;
5886
+ }
5887
+ function stripPriority(candidate) {
5888
+ const { priority: _priority, ...publicCandidate } = candidate;
5889
+ return publicCandidate;
5890
+ }
5891
+ function normalizeMaxCandidates(maxCandidates) {
5892
+ if (maxCandidates === void 0)
5893
+ return DEFAULT_MAX_CANDIDATES;
5894
+ if (!Number.isFinite(maxCandidates))
5895
+ return DEFAULT_MAX_CANDIDATES;
5896
+ return Math.min(Math.max(0, Math.trunc(maxCandidates)), MAX_CANDIDATES);
5897
+ }
5898
+ function cleanCandidateText(text) {
5899
+ return text.normalize("NFKC").replace(/\s+/g, " ").trim().replace(/[.,;:!?]+$/g, "");
5900
+ }
5901
+ function normalizeCandidate(text) {
5902
+ return cleanCandidateText(text).toLowerCase();
5903
+ }
5904
+ function normalizeForContainment(text) {
5905
+ return text.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}@/._-]+/gu, " ").replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim();
5906
+ }
5907
+ function containsPhrase(normalizedHaystack, normalizedNeedle) {
5908
+ return ` ${normalizedHaystack} `.includes(` ${normalizedNeedle} `);
5909
+ }
5910
+ function aliasLabelFromCanonicalKey(canonicalKey) {
5911
+ const raw = canonicalKey.includes(":") ? canonicalKey.slice(canonicalKey.lastIndexOf(":") + 1) : canonicalKey;
5912
+ return raw.replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim();
5913
+ }
5914
+ function findRange(query, alias) {
5915
+ const normalizedAlias = normalizeForContainment(alias);
5916
+ const directIndex = query.toLowerCase().indexOf(alias.toLowerCase());
5917
+ if (directIndex >= 0)
5918
+ return { start: directIndex, end: directIndex + alias.length };
5919
+ const normalizedQuery = normalizeForContainment(query);
5920
+ const normalizedIndex = normalizedQuery.indexOf(normalizedAlias);
5921
+ if (normalizedIndex < 0)
5922
+ return { start: 0, end: 0 };
5923
+ const queryLower = query.toLowerCase();
5924
+ const words = normalizedAlias.split(" ").filter(Boolean);
5925
+ if (words.length === 0)
5926
+ return { start: 0, end: 0 };
5927
+ const first = queryLower.indexOf(words[0]);
5928
+ const lastWord = words[words.length - 1];
5929
+ const last = queryLower.indexOf(lastWord, first >= 0 ? first : 0);
5930
+ if (first >= 0 && last >= 0)
5931
+ return { start: first, end: last + lastWord.length };
5932
+ return { start: normalizedIndex, end: normalizedIndex + normalizedAlias.length };
5933
+ }
5934
+ function isUsefulCandidate(text) {
5935
+ const cleaned = cleanCandidateText(text);
5936
+ return cleaned.length >= 2 && cleaned.length <= MAX_CANDIDATE_TEXT_LENGTH && /[\p{L}\p{N}]/u.test(cleaned);
5937
+ }
5938
+ function isInsideAnyRange(index, ranges) {
5939
+ return ranges.some(([start, end]) => index >= start && index < end);
5940
+ }
5941
+ function isStrongSingleCapitalized(text) {
5942
+ if (/^[A-Z]{2,}[A-Z0-9]*$/.test(text))
5943
+ return true;
5944
+ if (/^[A-Z][a-z]+[A-Z][A-Za-z0-9]*$/.test(text))
5945
+ return true;
5946
+ return text.length >= 4 && !SENTENCE_START_STOPWORDS.has(text);
5947
+ }
5948
+ function uniqueStrings(values) {
5949
+ const seen = /* @__PURE__ */ new Set();
5950
+ const result = [];
5951
+ for (const value of values) {
5952
+ const key = normalizeForContainment(value);
5953
+ if (!key || seen.has(key))
5954
+ continue;
5955
+ seen.add(key);
5956
+ result.push(value);
5957
+ }
5958
+ return result;
5959
+ }
5960
+
4891
5961
  // src/core/retrieval-quality.ts
4892
5962
  var COMMAND_ARTIFACT_PATTERNS = [
4893
5963
  /<\/?(?:local-command-(?:stdout|stderr)|command-(?:name|message))\b/i,
@@ -5250,6 +6320,7 @@ var Retriever = class {
5250
6320
  sharedVectorStore;
5251
6321
  graduation;
5252
6322
  queryRewriter;
6323
+ queryGraphExpansionEnabled;
5253
6324
  constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
5254
6325
  this.eventStore = eventStore;
5255
6326
  this.vectorStore = vectorStore;
@@ -5257,6 +6328,7 @@ var Retriever = class {
5257
6328
  this.matcher = matcher;
5258
6329
  this.sharedStore = sharedOptions?.sharedStore;
5259
6330
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
6331
+ this.queryGraphExpansionEnabled = sharedOptions?.queryGraphExpansionEnabled === true;
5260
6332
  }
5261
6333
  setGraduationPipeline(graduation) {
5262
6334
  this.graduation = graduation;
@@ -5302,7 +6374,8 @@ var Retriever = class {
5302
6374
  graphHop: opts.graphHop,
5303
6375
  projectScopeMode: opts.projectScopeMode,
5304
6376
  projectHash: opts.projectHash,
5305
- allowedProjectHashes: opts.allowedProjectHashes
6377
+ allowedProjectHashes: opts.allowedProjectHashes,
6378
+ facets: opts.facets
5306
6379
  });
5307
6380
  fallbackTrace.push(`stage:primary:${primaryStrategy}`);
5308
6381
  if (fallbackEnabled && this.shouldFallback(current.matchResult, current.results) && primaryStrategy !== "deep") {
@@ -5319,7 +6392,8 @@ var Retriever = class {
5319
6392
  graphHop: opts.graphHop,
5320
6393
  projectScopeMode: opts.projectScopeMode,
5321
6394
  projectHash: opts.projectHash,
5322
- allowedProjectHashes: opts.allowedProjectHashes
6395
+ allowedProjectHashes: opts.allowedProjectHashes,
6396
+ facets: opts.facets
5323
6397
  });
5324
6398
  fallbackTrace.push("fallback:deep");
5325
6399
  }
@@ -5337,7 +6411,8 @@ var Retriever = class {
5337
6411
  graphHop: opts.graphHop,
5338
6412
  projectScopeMode: opts.projectScopeMode,
5339
6413
  projectHash: opts.projectHash,
5340
- allowedProjectHashes: opts.allowedProjectHashes
6414
+ allowedProjectHashes: opts.allowedProjectHashes,
6415
+ facets: opts.facets
5341
6416
  });
5342
6417
  fallbackTrace.push("fallback:scope-expanded");
5343
6418
  }
@@ -5347,7 +6422,8 @@ var Retriever = class {
5347
6422
  scope: opts.scope,
5348
6423
  projectScopeMode: opts.projectScopeMode,
5349
6424
  projectHash: opts.projectHash,
5350
- allowedProjectHashes: opts.allowedProjectHashes
6425
+ allowedProjectHashes: opts.allowedProjectHashes,
6426
+ facets: opts.facets
5351
6427
  });
5352
6428
  const filteredSummary = this.applyQualityFilters(scopedSummary, {
5353
6429
  query,
@@ -5368,20 +6444,8 @@ var Retriever = class {
5368
6444
  totalTokens: this.estimateTokens(context),
5369
6445
  context,
5370
6446
  fallbackTrace,
5371
- selectedDebug: current.results.slice(0, opts.topK).map((r) => ({
5372
- eventId: r.eventId,
5373
- score: r.score,
5374
- semanticScore: r.semanticScore,
5375
- lexicalScore: r.lexicalScore,
5376
- recencyScore: r.recencyScore
5377
- })),
5378
- candidateDebug: (current.candidateResults || []).slice(0, Math.max(opts.topK * 3, 20)).map((r) => ({
5379
- eventId: r.eventId,
5380
- score: r.score,
5381
- semanticScore: r.semanticScore,
5382
- lexicalScore: r.lexicalScore,
5383
- recencyScore: r.recencyScore
5384
- })),
6447
+ selectedDebug: current.results.slice(0, opts.topK).map((r) => this.debugDetailForResult(r)),
6448
+ candidateDebug: (current.candidateResults || []).slice(0, Math.max(opts.topK * 3, 20)).map((r) => this.debugDetailForResult(r)),
5385
6449
  rawQueryText: current.queryRewriteKind ? query : void 0,
5386
6450
  effectiveQueryText: current.effectiveQueryText,
5387
6451
  queryRewriteKind: current.queryRewriteKind
@@ -5449,7 +6513,9 @@ var Retriever = class {
5449
6513
  }
5450
6514
  }
5451
6515
  const expandedResults = input.graphHop?.enabled === false ? initialResults : await this.expandGraphHops(initialResults, {
5452
- maxHops: Math.max(1, input.graphHop?.maxHops ?? 1),
6516
+ query,
6517
+ queryGraphEnabled: this.queryGraphExpansionEnabled,
6518
+ maxHops: clampGraphHops(input.graphHop?.maxHops ?? 1),
5453
6519
  hopPenalty: Math.max(0, input.graphHop?.hopPenalty ?? 0.08),
5454
6520
  limit: input.topK * 4
5455
6521
  });
@@ -5458,7 +6524,8 @@ var Retriever = class {
5458
6524
  scope: input.scope,
5459
6525
  projectScopeMode: input.projectScopeMode,
5460
6526
  projectHash: input.projectHash,
5461
- allowedProjectHashes: input.allowedProjectHashes
6527
+ allowedProjectHashes: input.allowedProjectHashes,
6528
+ facets: input.facets
5462
6529
  });
5463
6530
  const qualityFiltered = this.applyQualityFilters(filtered, {
5464
6531
  query,
@@ -5473,9 +6540,13 @@ var Retriever = class {
5473
6540
  if (isCurrentStateQuery(options.query)) {
5474
6541
  filtered = filtered.filter((result) => !isStaleOrSupersededContent(result.content));
5475
6542
  }
5476
- filtered = filtered.filter((result) => hasDiscriminativeTermOverlap(options.query, result.content));
6543
+ filtered = filtered.filter(
6544
+ (result) => this.isGraphPathResult(result) || hasDiscriminativeTermOverlap(options.query, result.content)
6545
+ );
5477
6546
  if (shouldApplyTechnicalGuard(options.query)) {
5478
- filtered = filtered.filter((result) => hasTechnicalTermOverlap(options.query, result.content));
6547
+ filtered = filtered.filter(
6548
+ (result) => this.isGraphPathResult(result) || hasTechnicalTermOverlap(options.query, result.content)
6549
+ );
5479
6550
  }
5480
6551
  if (filtered.length <= 2)
5481
6552
  return filtered;
@@ -5538,7 +6609,61 @@ var Retriever = class {
5538
6609
  if (frontier.length === 0 || byId.size >= opts.limit)
5539
6610
  break;
5540
6611
  }
5541
- return [...byId.values()].sort((a, b) => b.score - a.score).slice(0, opts.limit);
6612
+ if (opts.queryGraphEnabled) {
6613
+ await this.expandQueryGraphPaths(opts.query, byId, opts);
6614
+ }
6615
+ return [...byId.values()].sort((a, b) => b.score - a.score || compareStable(a.eventId, b.eventId)).slice(0, opts.limit);
6616
+ }
6617
+ async expandQueryGraphPaths(query, byId, opts) {
6618
+ if (!query.trim() || !this.eventStore.getDatabase)
6619
+ return;
6620
+ try {
6621
+ const db = this.eventStore.getDatabase();
6622
+ const extraction = new QueryEntityExtractor(db).extract(query, {
6623
+ maxCandidates: Math.min(8, opts.limit),
6624
+ includeAliases: true
6625
+ });
6626
+ const startCandidates = extraction.candidates.filter((candidate) => candidate.entityId).slice(0, 8);
6627
+ const startNodes = uniqueEntityStartNodes(startCandidates);
6628
+ if (startNodes.length === 0)
6629
+ return;
6630
+ const expansion = new GraphPathService(db).expand({
6631
+ startNodes: startNodes.map((node) => ({ type: "entity", id: node.entityId })),
6632
+ maxHops: opts.maxHops,
6633
+ maxResults: opts.limit,
6634
+ direction: "both"
6635
+ });
6636
+ const titleByEntityId = new Map(startNodes.map((node) => [node.entityId, node.title]));
6637
+ for (const path12 of expansion.paths) {
6638
+ if (path12.target.type !== "event")
6639
+ continue;
6640
+ const target = await this.eventStore.getEvent(path12.target.id);
6641
+ if (!target)
6642
+ continue;
6643
+ const graphPath = toRetrievalGraphPathDebug(path12, titleByEntityId);
6644
+ const score = graphPathScore(path12, opts.hopPenalty);
6645
+ const existing = byId.get(target.id);
6646
+ const graphPaths = mergeGraphPaths(existing?.graphPaths ?? [], [graphPath]);
6647
+ const row = {
6648
+ id: existing?.id ?? `graph-path-${path12.hops}-${target.id}`,
6649
+ eventId: target.id,
6650
+ content: target.content,
6651
+ score: Math.max(existing?.score ?? 0, score),
6652
+ sessionId: target.sessionId,
6653
+ eventType: target.eventType,
6654
+ timestamp: target.timestamp.toISOString(),
6655
+ semanticScore: existing?.semanticScore,
6656
+ lexicalScore: existing?.lexicalScore,
6657
+ recencyScore: existing?.recencyScore,
6658
+ facetMatches: existing?.facetMatches,
6659
+ graphPaths
6660
+ };
6661
+ byId.set(row.eventId, row);
6662
+ if (byId.size >= opts.limit)
6663
+ break;
6664
+ }
6665
+ } catch {
6666
+ }
5542
6667
  }
5543
6668
  shouldFallback(matchResult, results) {
5544
6669
  if (results.length === 0)
@@ -5629,12 +6754,13 @@ var Retriever = class {
5629
6754
  async applyScopeFilters(results, options) {
5630
6755
  const scope = options?.scope;
5631
6756
  const projectScopeMode = options?.projectScopeMode ?? "global";
6757
+ const facetFilters = this.normalizeFacetFilters(options?.facets);
5632
6758
  const allowedProjectHashes = new Set(
5633
6759
  [options?.projectHash, ...options?.allowedProjectHashes || []].filter(
5634
6760
  (value) => typeof value === "string" && value.length > 0
5635
6761
  )
5636
6762
  );
5637
- if (!scope && projectScopeMode === "global")
6763
+ if (!scope && projectScopeMode === "global" && facetFilters === null)
5638
6764
  return results;
5639
6765
  const normalizedIncludes = (scope?.contentIncludes || []).map((s) => s.toLowerCase());
5640
6766
  const filtered = [];
@@ -5660,14 +6786,83 @@ var Retriever = class {
5660
6786
  const projectHash = this.extractProjectHash(event.metadata);
5661
6787
  filtered.push({ result, projectHash });
5662
6788
  }
6789
+ let scopedResults;
5663
6790
  if (projectScopeMode === "global" || allowedProjectHashes.size === 0) {
5664
- return filtered.map((x) => x.result);
6791
+ scopedResults = filtered.map((x) => x.result);
6792
+ } else {
6793
+ const projectMatched = filtered.filter((x) => x.projectHash && allowedProjectHashes.has(x.projectHash));
6794
+ scopedResults = projectScopeMode === "strict" ? projectMatched.map((x) => x.result) : (projectMatched.length > 0 ? projectMatched : filtered).map((x) => x.result);
6795
+ }
6796
+ return this.applyFacetFilters(scopedResults, {
6797
+ facets: facetFilters,
6798
+ projectHash: options?.projectHash
6799
+ });
6800
+ }
6801
+ normalizeFacetFilters(facets) {
6802
+ if (!facets || facets.length === 0)
6803
+ return null;
6804
+ const normalized = [];
6805
+ for (const facet of facets) {
6806
+ const parsedDimension = FacetDimensionSchema.safeParse(facet.dimension);
6807
+ const value = typeof facet.value === "string" ? facet.value.trim() : "";
6808
+ if (!parsedDimension.success || !value)
6809
+ return [];
6810
+ normalized.push({ dimension: parsedDimension.data, value });
6811
+ }
6812
+ return normalized;
6813
+ }
6814
+ async applyFacetFilters(results, options) {
6815
+ if (options.facets === null)
6816
+ return results;
6817
+ if (options.facets.length === 0)
6818
+ return [];
6819
+ if (!options.projectHash)
6820
+ return [];
6821
+ if (!this.eventStore.getDatabase)
6822
+ return [];
6823
+ const repo = new FacetRepository(this.eventStore.getDatabase());
6824
+ const filtered = [];
6825
+ for (const result of results) {
6826
+ const matches = [];
6827
+ let matchedAll = true;
6828
+ for (const facet of options.facets) {
6829
+ const rows = await repo.query({
6830
+ targetType: "event",
6831
+ targetId: result.eventId,
6832
+ dimension: facet.dimension,
6833
+ value: facet.value,
6834
+ projectHash: options.projectHash
6835
+ });
6836
+ if (rows.length === 0) {
6837
+ matchedAll = false;
6838
+ break;
6839
+ }
6840
+ matches.push(facet);
6841
+ }
6842
+ if (matchedAll) {
6843
+ filtered.push({ ...result, facetMatches: matches });
6844
+ }
5665
6845
  }
5666
- const projectMatched = filtered.filter((x) => x.projectHash && allowedProjectHashes.has(x.projectHash));
5667
- if (projectScopeMode === "strict") {
5668
- return projectMatched.map((x) => x.result);
6846
+ return filtered;
6847
+ }
6848
+ debugDetailForResult(result) {
6849
+ const detail = {
6850
+ eventId: result.eventId,
6851
+ score: result.score,
6852
+ semanticScore: result.semanticScore,
6853
+ lexicalScore: result.lexicalScore,
6854
+ recencyScore: result.recencyScore
6855
+ };
6856
+ if (result.facetMatches && result.facetMatches.length > 0) {
6857
+ detail.facetMatches = result.facetMatches;
6858
+ }
6859
+ if (result.graphPaths && result.graphPaths.length > 0) {
6860
+ detail.graphPaths = result.graphPaths;
5669
6861
  }
5670
- return (projectMatched.length > 0 ? projectMatched : filtered).map((x) => x.result);
6862
+ return detail;
6863
+ }
6864
+ isGraphPathResult(result) {
6865
+ return (result.graphPaths || []).length > 0;
5671
6866
  }
5672
6867
  extractProjectHash(metadata) {
5673
6868
  if (!metadata || typeof metadata !== "object")
@@ -5811,8 +7006,60 @@ _Context:_ ${sessionContext}`;
5811
7006
  return Math.ceil(text.length / 4);
5812
7007
  }
5813
7008
  };
5814
- function createRetriever(eventStore, vectorStore, embedder, matcher) {
5815
- return new Retriever(eventStore, vectorStore, embedder, matcher);
7009
+ function uniqueEntityStartNodes(candidates) {
7010
+ const seen = /* @__PURE__ */ new Set();
7011
+ const nodes = [];
7012
+ for (const candidate of candidates) {
7013
+ if (!candidate.entityId || seen.has(candidate.entityId))
7014
+ continue;
7015
+ seen.add(candidate.entityId);
7016
+ nodes.push({ entityId: candidate.entityId, title: candidate.text });
7017
+ }
7018
+ return nodes;
7019
+ }
7020
+ function toRetrievalGraphPathDebug(path12, titleByEntityId) {
7021
+ const firstStep = path12.steps[0];
7022
+ const startNode = firstStep?.direction === "incoming" ? firstStep.to : firstStep?.from;
7023
+ const startEntityId = startNode?.type === "entity" ? startNode.id : "";
7024
+ return {
7025
+ startEntityId,
7026
+ startEntityTitle: titleByEntityId.get(startEntityId) ?? startNode?.name,
7027
+ targetId: path12.target.id,
7028
+ targetType: path12.target.type,
7029
+ hops: path12.hops,
7030
+ relationPath: path12.steps.map((step) => step.relationType)
7031
+ };
7032
+ }
7033
+ function graphPathScore(path12, hopPenalty) {
7034
+ const base = Math.min(0.95, Math.max(0, path12.scoreContribution));
7035
+ return Math.max(0.05, base - hopPenalty * Math.max(0, path12.hops - 1));
7036
+ }
7037
+ function clampGraphHops(maxHops) {
7038
+ if (!Number.isFinite(maxHops))
7039
+ return 2;
7040
+ return Math.min(Math.max(0, Math.trunc(maxHops)), 2);
7041
+ }
7042
+ function mergeGraphPaths(existing, incoming) {
7043
+ const byKey = /* @__PURE__ */ new Map();
7044
+ for (const path12 of [...existing, ...incoming]) {
7045
+ const key = [path12.startEntityId, path12.targetType, path12.targetId, path12.hops, ...path12.relationPath].join("\0");
7046
+ if (!byKey.has(key))
7047
+ byKey.set(key, path12);
7048
+ }
7049
+ return [...byKey.values()].sort((a, b) => a.hops - b.hops || compareStable(graphPathSignature(a), graphPathSignature(b))).slice(0, 3);
7050
+ }
7051
+ function graphPathSignature(path12) {
7052
+ return [path12.startEntityId, path12.targetType, path12.targetId, path12.hops, ...path12.relationPath].join("|");
7053
+ }
7054
+ function compareStable(a, b) {
7055
+ if (a < b)
7056
+ return -1;
7057
+ if (a > b)
7058
+ return 1;
7059
+ return 0;
7060
+ }
7061
+ function createRetriever(eventStore, vectorStore, embedder, matcher, sharedOptions) {
7062
+ return new Retriever(eventStore, vectorStore, embedder, matcher, sharedOptions);
5816
7063
  }
5817
7064
 
5818
7065
  // src/core/engine/retrieval-analytics-service.ts
@@ -5998,10 +7245,15 @@ var RetrievalDisclosureService = class {
5998
7245
  {
5999
7246
  semanticScore: debug?.semanticScore,
6000
7247
  lexicalScore: debug?.lexicalScore,
6001
- recencyScore: debug?.recencyScore
7248
+ recencyScore: debug?.recencyScore,
7249
+ ...debug?.facetMatches && debug.facetMatches.length > 0 ? { facetMatches: debug.facetMatches } : {},
7250
+ ...debug?.graphPaths && debug.graphPaths.length > 0 ? { graphPaths: this.sanitizeGraphPaths(debug.graphPaths) } : {}
6002
7251
  }
6003
7252
  );
6004
7253
  }
7254
+ sanitizeGraphPaths(graphPaths) {
7255
+ return sanitizeGovernanceAuditValue(graphPaths ?? []);
7256
+ }
6005
7257
  eventToEnvelope(event, score, reasons, extraMetadata) {
6006
7258
  return {
6007
7259
  id: toDisclosureResultId(event.id),
@@ -6061,6 +7313,10 @@ var RetrievalDisclosureService = class {
6061
7313
  reasons.add("keyword_match");
6062
7314
  if ((debug?.recencyScore ?? 0) > 0)
6063
7315
  reasons.add("recent_relevance");
7316
+ if ((debug?.facetMatches || []).length > 0)
7317
+ reasons.add("facet_match");
7318
+ if ((debug?.graphPaths || []).length > 0)
7319
+ reasons.add("entity_overlap");
6064
7320
  if ((result.fallbackTrace || []).some((step) => step === "fallback:summary"))
6065
7321
  reasons.add("summary_fallback");
6066
7322
  if (memory.sessionContext)
@@ -6188,6 +7444,7 @@ var RetrievalOrchestrator = class {
6188
7444
  const rerankWeights = lightweightFastRead ? void 0 : await this.getRerankWeights(options?.adaptiveRerank === true);
6189
7445
  const projectHash = this.deps.getProjectHash();
6190
7446
  const projectScopeMode = retrieverOptions.projectScopeMode ?? (projectHash ? "strict" : "global");
7447
+ const graphHop = this.resolveGraphHopOptions(retrieverOptions.graphHop);
6191
7448
  let result;
6192
7449
  if (retrieverOptions.includeShared && this.deps.hasSharedStore()) {
6193
7450
  result = await this.deps.retriever.retrieveUnified(query, {
@@ -6195,6 +7452,7 @@ var RetrievalOrchestrator = class {
6195
7452
  intentRewrite: retrieverOptions.intentRewrite === true,
6196
7453
  rerankWeights,
6197
7454
  includeShared: true,
7455
+ graphHop,
6198
7456
  projectHash: projectHash || void 0,
6199
7457
  projectScopeMode,
6200
7458
  allowedProjectHashes: retrieverOptions.allowedProjectHashes
@@ -6204,6 +7462,7 @@ var RetrievalOrchestrator = class {
6204
7462
  ...retrieverOptions,
6205
7463
  intentRewrite: retrieverOptions.intentRewrite === true,
6206
7464
  rerankWeights,
7465
+ graphHop,
6207
7466
  projectHash: projectHash || void 0,
6208
7467
  projectScopeMode,
6209
7468
  allowedProjectHashes: retrieverOptions.allowedProjectHashes
@@ -6265,6 +7524,29 @@ var RetrievalOrchestrator = class {
6265
7524
  await this.deps.initialize();
6266
7525
  await this.deps.accessStore.recordRetrieval(eventId, sessionId, score, query);
6267
7526
  }
7527
+ resolveGraphHopOptions(callerOptions) {
7528
+ const graphExpansion = this.deps.memoryOperationsConfig?.graphExpansion;
7529
+ const durableOptions = graphExpansion?.enabled === true ? { enabled: true, maxHops: graphExpansion.maxHops } : void 0;
7530
+ if (!callerOptions)
7531
+ return durableOptions;
7532
+ if (!graphExpansion)
7533
+ return callerOptions;
7534
+ if (graphExpansion.enabled !== true) {
7535
+ return {
7536
+ ...callerOptions,
7537
+ enabled: false,
7538
+ maxHops: graphExpansion.maxHops ?? callerOptions.maxHops
7539
+ };
7540
+ }
7541
+ return {
7542
+ enabled: callerOptions.enabled === false ? false : true,
7543
+ maxHops: Math.min(
7544
+ graphExpansion.maxHops ?? Number.POSITIVE_INFINITY,
7545
+ callerOptions.maxHops ?? graphExpansion.maxHops ?? 1
7546
+ ),
7547
+ hopPenalty: callerOptions.hopPenalty
7548
+ };
7549
+ }
6268
7550
  async recordAutomaticTrace(query, result, options, projectHash) {
6269
7551
  const selectedEventIds = result.memories.map((memory) => memory.event.id);
6270
7552
  const selectedDetails = (result.selectedDebug || []).map((detail) => ({
@@ -6406,7 +7688,8 @@ function createRetrievalServices(deps) {
6406
7688
  deps.eventStore,
6407
7689
  deps.vectorStore,
6408
7690
  deps.embedder,
6409
- deps.matcher
7691
+ deps.matcher,
7692
+ { queryGraphExpansionEnabled: deps.memoryOperationsConfig?.graphExpansion?.enabled === true }
6410
7693
  );
6411
7694
  const retrievalOrchestrator = createRetrievalOrchestrator({
6412
7695
  initialize: deps.initialize,
@@ -6414,7 +7697,8 @@ function createRetrievalServices(deps) {
6414
7697
  traceStore: deps.eventStore,
6415
7698
  accessStore: deps.eventStore,
6416
7699
  getProjectHash: deps.getProjectHash,
6417
- hasSharedStore: deps.hasSharedStore
7700
+ hasSharedStore: deps.hasSharedStore,
7701
+ memoryOperationsConfig: deps.memoryOperationsConfig
6418
7702
  });
6419
7703
  const retrievalDisclosureService = createRetrievalDisclosureService({
6420
7704
  initialize: deps.initialize,
@@ -6433,13 +7717,14 @@ function createRetrievalServices(deps) {
6433
7717
  retrievalAnalyticsService
6434
7718
  };
6435
7719
  }
6436
- function defaultCreateRetriever(eventStore, vectorStore, embedder, matcher) {
7720
+ function defaultCreateRetriever(eventStore, vectorStore, embedder, matcher, options) {
6437
7721
  assertDefaultRetrieverStore(eventStore);
6438
7722
  return createRetriever(
6439
7723
  eventStore,
6440
7724
  vectorStore,
6441
7725
  embedder,
6442
- matcher
7726
+ matcher,
7727
+ options
6443
7728
  );
6444
7729
  }
6445
7730
  function assertDefaultRetrieverStore(eventStore) {
@@ -6485,6 +7770,7 @@ function createMemoryEngineServices(options) {
6485
7770
  matcher,
6486
7771
  getProjectHash: options.getProjectHash,
6487
7772
  hasSharedStore: options.hasSharedStore,
7773
+ memoryOperationsConfig: options.memoryOperationsConfig,
6488
7774
  sharedStore: options.sharedStore
6489
7775
  });
6490
7776
  const ingestService = new MemoryIngestService({
@@ -6971,7 +8257,7 @@ function createSharedEventStore(dbPath) {
6971
8257
  }
6972
8258
 
6973
8259
  // src/core/shared-promoter.ts
6974
- import { randomUUID as randomUUID6 } from "crypto";
8260
+ import { randomUUID as randomUUID8 } from "crypto";
6975
8261
  var SharedPromoter = class {
6976
8262
  constructor(sharedStore, sharedVectorStore, embedder, config) {
6977
8263
  this.sharedStore = sharedStore;
@@ -7039,7 +8325,7 @@ var SharedPromoter = class {
7039
8325
  const embeddingContent = this.createEmbeddingContent(input);
7040
8326
  const embedding = await this.embedder.embed(embeddingContent);
7041
8327
  await this.sharedVectorStore.upsert({
7042
- id: randomUUID6(),
8328
+ id: randomUUID8(),
7043
8329
  entryId,
7044
8330
  entryType: "troubleshooting",
7045
8331
  content: embeddingContent,
@@ -7155,7 +8441,7 @@ function createSharedPromoter(sharedStore, sharedVectorStore, embedder, config)
7155
8441
  }
7156
8442
 
7157
8443
  // src/core/shared-store.ts
7158
- import { randomUUID as randomUUID7 } from "crypto";
8444
+ import { randomUUID as randomUUID9 } from "crypto";
7159
8445
  var SharedStore = class {
7160
8446
  constructor(sharedEventStore) {
7161
8447
  this.sharedEventStore = sharedEventStore;
@@ -7167,7 +8453,7 @@ var SharedStore = class {
7167
8453
  * Promote a verified troubleshooting entry to shared storage
7168
8454
  */
7169
8455
  async promoteEntry(input) {
7170
- const entryId = randomUUID7();
8456
+ const entryId = randomUUID9();
7171
8457
  await dbRun(
7172
8458
  this.db,
7173
8459
  `INSERT INTO shared_troubleshooting (
@@ -7701,6 +8987,7 @@ function createMemoryServiceComposition(options) {
7701
8987
  getProjectHash: options.getProjectHash,
7702
8988
  getProjectPath: options.getProjectPath,
7703
8989
  hasSharedStore: () => sharedMemoryServices?.isEnabled() ?? false,
8990
+ memoryOperationsConfig: options.config.operations,
7704
8991
  sharedStore: {
7705
8992
  get: (entryId) => sharedMemoryServices?.getEntryForDisclosure(entryId) ?? Promise.resolve(null)
7706
8993
  },
@@ -7821,6 +9108,18 @@ var DEFAULT_ENABLED_SHARED_STORE_CONFIG = {
7821
9108
  sharedStoragePath: SHARED_STORAGE_PATH
7822
9109
  };
7823
9110
  var DEFAULT_SHARED_STORAGE_PATH = SHARED_STORAGE_PATH;
9111
+ var DISABLED_MEMORY_OPERATIONS_CONFIG = {
9112
+ enabled: false,
9113
+ facets: { enabled: true },
9114
+ actions: { enabled: true },
9115
+ retention: { enabled: false, policyVersion: "v1" },
9116
+ graphExpansion: { enabled: false, maxHops: 1 },
9117
+ lessons: { enabled: false }
9118
+ };
9119
+ var DEFAULT_ENABLED_MEMORY_OPERATIONS_CONFIG = {
9120
+ ...DISABLED_MEMORY_OPERATIONS_CONFIG,
9121
+ enabled: true
9122
+ };
7824
9123
 
7825
9124
  // src/services/memory-service-registry.ts
7826
9125
  import * as path10 from "path";