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