import {
  eq,
  llmUsage,
  memories,
  memoryEmbeddings
} from "./chunk-QDA5M3AJ.js";
import {
  getProviderConfig,
  hasProviderConfigured,
  init_keychain
} from "./chunk-DNFKAHS6.js";

// src/embeddings/embeddings.ts
import { createHash as createHash2 } from "crypto";

// src/types.ts
import { z } from "zod";
var MemoryStatus = z.enum([
  "transient",
  "candidate",
  "active",
  "rejected"
]);
var MemoryType = z.enum([
  "rule",
  "command",
  "gotcha",
  "decision",
  "review_pattern"
]);
var MemoryScope = z.enum(["session", "path", "repo", "team", "global"]);
var MemorySource = z.enum([
  "user_correction",
  "user_reported_review",
  "repo_scan",
  "config_parse"
]);
var FeedbackOutcome = z.enum([
  "followed",
  "overridden",
  "ignored",
  "contradicted"
]);
var EvidenceEntry = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("session_correction"),
    session: z.string(),
    timestamp: z.string(),
    context: z.string().optional()
  }),
  z.object({
    type: z.literal("review_feedback"),
    reported_by_user: z.boolean(),
    reviewer: z.string().optional(),
    timestamp: z.string(),
    context: z.string().optional()
  }),
  z.object({
    type: z.literal("repo_scan"),
    file: z.string(),
    timestamp: z.string()
  }),
  z.object({
    type: z.literal("repeated_correction"),
    count: z.number(),
    sessions: z.array(z.string()),
    timestamp: z.string()
  })
]);
var CaptureContextToolCall = z.object({
  name: z.string(),
  path: z.string().optional(),
  exit_code: z.number().optional()
});
var CaptureContext = z.object({
  prev_assistant_text: z.string().optional(),
  recent_tool_calls: z.array(CaptureContextToolCall).max(5).optional(),
  repo: z.string().nullable().optional(),
  path: z.string().nullable().optional(),
  agent: z.string().optional()
});
var MemoryItem = z.object({
  id: z.string().uuid(),
  type: MemoryType,
  text: z.string(),
  scope: MemoryScope,
  path_scope: z.string().nullable(),
  repo: z.string().nullable(),
  status: MemoryStatus,
  confidence: z.number().min(0).max(1),
  source: MemorySource,
  evidence: z.array(EvidenceEntry),
  capture_context: CaptureContext.nullable(),
  supersedes: z.string().uuid().nullable(),
  created_at: z.string(),
  updated_at: z.string(),
  last_validated_at: z.string().nullable(),
  last_injected_at: z.string().nullable(),
  injection_count: z.number().int().nonnegative(),
  override_count: z.number().int().nonnegative(),
  repetition_count: z.number().int().nonnegative(),
  auto_inject: z.boolean()
});
var FeedbackEvent = z.object({
  id: z.string().uuid(),
  memory_id: z.string().uuid(),
  session_id: z.string(),
  injected: z.boolean(),
  outcome: FeedbackOutcome,
  timestamp: z.string()
});
var MemoryInjection = z.object({
  id: z.string().uuid(),
  memory_id: z.string().uuid(),
  session_id: z.string(),
  repo: z.string().nullable(),
  injected_at: z.string(),
  outcome: FeedbackOutcome.nullable(),
  outcome_at: z.string().nullable()
});
var MaintenanceTaskKind = z.enum([
  "summarize_history",
  "merge_duplicates",
  "refine_candidate",
  "summarize_session",
  "synthesize_repo",
  "verify_capture",
  "extract_rules_from_prompt"
]);
var MaintenanceTaskStatus = z.enum([
  "pending",
  "claimed",
  "submitted",
  "completed",
  "abandoned"
]);
var MaintenanceTask = z.object({
  id: z.string().uuid(),
  kind: MaintenanceTaskKind,
  status: MaintenanceTaskStatus,
  priority: z.number().int(),
  repo: z.string().nullable(),
  target_key: z.string(),
  payload: z.record(z.string(), z.unknown()),
  result: z.record(z.string(), z.unknown()).nullable(),
  failure_reason: z.string().nullable(),
  claimed_by: z.string().nullable(),
  claimed_at: z.string().nullable(),
  claim_expires_at: z.string().nullable(),
  submitted_at: z.string().nullable(),
  completed_at: z.string().nullable(),
  created_at: z.string(),
  attempts: z.number().int().nonnegative(),
  max_attempts: z.number().int().positive()
});
var ACTIVITY_SOURCE_PATTERN = /^(cli|daemon|mcp|system|hook)(:[a-z0-9][a-z0-9._-]*)?$/;
var ActivitySource = z.string().regex(ACTIVITY_SOURCE_PATTERN);
function tagActivitySource(transport, agent) {
  if (!agent) return transport;
  const normalized = agent.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
  if (!normalized) return transport;
  return `${transport}:${normalized}`;
}
var ActivityEventType = z.enum([
  "compile",
  "query",
  "scan",
  "correction",
  "review",
  "feedback",
  "signal",
  "session_start",
  "session_event",
  "session_end",
  "tool_call"
]);
var ActivityEvent = z.object({
  id: z.string().uuid(),
  session_id: z.string().nullable(),
  repo: z.string().nullable(),
  path: z.string().nullable(),
  source: ActivitySource,
  event_type: ActivityEventType,
  memory_ids: z.array(z.string().uuid()),
  request: z.record(z.string(), z.unknown()),
  result: z.record(z.string(), z.unknown()),
  created_at: z.string()
});
var ActivityEventQuery = z.object({
  repo: z.string().optional(),
  session_id: z.string().optional(),
  source: ActivitySource.optional(),
  event_type: ActivityEventType.optional(),
  since: z.string().optional(),
  limit: z.number().int().positive().optional(),
  offset: z.number().int().nonnegative().optional()
});
var HookCallEvent = z.enum([
  "session_started",
  "prompt_submitted",
  "tool_invoked",
  "session_ended"
]);
var HookCall = z.object({
  id: z.string().uuid(),
  event: HookCallEvent,
  agent: z.string(),
  duration_ms: z.number().int().nonnegative(),
  ok: z.boolean(),
  created_at: z.string()
});
var HookCallStatsQuery = z.object({
  agent: z.string().optional(),
  event: HookCallEvent.optional(),
  limit: z.number().int().positive().optional()
});
var HookCallStatsRow = z.object({
  event: HookCallEvent,
  agent: z.string(),
  total_calls: z.number().int().nonnegative(),
  ok_calls: z.number().int().nonnegative(),
  error_calls: z.number().int().nonnegative(),
  avg_duration_ms: z.number().nonnegative(),
  max_duration_ms: z.number().int().nonnegative(),
  last_called_at: z.string()
});
var CONFIDENCE = {
  /** Below this → transient, never stored durably */
  TRANSIENT_MAX: 0.3,
  /** Below this → candidate, stored but not injected */
  CANDIDATE_MAX: 0.6,
  /** At or above this → active, injected when scope matches */
  ACTIVE_MIN: 0.6
};
var PROMOTION = {
  /** User explicitly confirms */
  EXPLICIT_CONFIRM: 0.8,
  /** Same correction repeats */
  REPEAT_CORRECTION: 0.2,
  /** User-reported review feedback */
  REVIEW_FEEDBACK: 0.3,
  /** Passive gain per use without override */
  PASSIVE_GAIN: 0.05
};
var CompilerConfig = z.object({
  confidence_threshold: z.number().default(0.6),
  max_lines: z.number().default(15),
  max_commands: z.number().default(3),
  max_gotchas: z.number().default(3),
  max_history_snippets: z.number().default(2),
  token_budget: z.number().default(2e3),
  include_candidates: z.boolean().default(false)
});
var MemoryQuery = z.object({
  repo: z.string().optional(),
  path: z.string().optional(),
  scope: MemoryScope.optional(),
  type: MemoryType.optional(),
  status: MemoryStatus.optional(),
  min_confidence: z.number().optional(),
  semantic_query: z.string().optional(),
  auto_inject: z.boolean().optional(),
  limit: z.number().int().positive().optional(),
  offset: z.number().int().nonnegative().optional()
});
var SyncConfig = z.object({
  remote_url: z.string().url(),
  api_key: z.string(),
  team_id: z.string().optional(),
  auto_sync: z.boolean().default(false),
  sync_interval_seconds: z.number().default(300)
});
var SyncDirection = z.enum(["push", "pull", "both"]);
var SyncResult = z.object({
  pushed: z.number(),
  pulled: z.number(),
  conflicts: z.number(),
  errors: z.array(z.string())
});
var TeamMember = z.object({
  id: z.string().uuid(),
  team_id: z.string().uuid(),
  user_id: z.string(),
  role: z.enum(["owner", "admin", "member"]),
  joined_at: z.string()
});
var EmbeddingConfig = z.object({
  provider: z.enum(["nomic", "multilingual-e5", "bge-small-en-v1.5", "all-MiniLM-L6-v2"]).default("nomic"),
  model: z.string().default("nomic-ai/nomic-embed-text-v1.5"),
  dimensions: z.number().default(512),
  version: z.string().default("v1"),
  similarity_threshold: z.number().default(0.8)
});
var HistorySnippetKind = z.enum([
  "session_summary",
  "correction_summary",
  "decision_summary",
  "review_summary",
  "compile_summary",
  "repo_synthesis"
]);
var HistorySnippet = z.object({
  id: z.string().uuid(),
  repo: z.string().nullable(),
  session_id: z.string().nullable(),
  kind: HistorySnippetKind,
  text: z.string(),
  source_activity_ids: z.array(z.string().uuid()),
  created_at: z.string(),
  updated_at: z.string()
});
var EvalSession = z.object({
  id: z.string().uuid(),
  repo: z.string(),
  started_at: z.string(),
  ended_at: z.string().nullable(),
  memories_injected: z.number(),
  memories_followed: z.number(),
  memories_overridden: z.number(),
  user_corrections: z.number(),
  test_passes: z.number(),
  test_failures: z.number()
});
var MaintenanceEvalMetrics = z.object({
  total_completed: z.number(),
  total_abandoned: z.number(),
  abandon_rate: z.number(),
  mean_completion_ms: z.number().nullable(),
  completed_by_kind: z.record(z.number()),
  merge_precision: z.number().nullable(),
  merge_rollbacks: z.number()
});
var EvalMetrics = z.object({
  total_sessions: z.number(),
  injection_rate: z.number(),
  follow_rate: z.number(),
  override_rate: z.number(),
  correction_frequency: z.number(),
  avg_confidence_at_injection: z.number(),
  memory_effectiveness: z.number(),
  maintenance: MaintenanceEvalMetrics.optional()
});
var RetrievalEvalCase = z.object({
  name: z.string(),
  repo: z.string(),
  path: z.string().optional(),
  query_text: z.string().default(""),
  include_candidates: z.boolean().default(false),
  confidence_threshold: z.number().optional(),
  max_lines: z.number().optional(),
  max_commands: z.number().optional(),
  max_gotchas: z.number().optional(),
  token_budget: z.number().optional(),
  expected_all_texts: z.array(z.string()).default([]),
  expected_any_texts: z.array(z.string()).default([]),
  forbidden_texts: z.array(z.string()).default([]),
  min_included: z.number().int().nonnegative().optional(),
  max_included: z.number().int().nonnegative().optional()
});
var RetrievalEvalFile = z.object({
  cases: z.array(RetrievalEvalCase)
});
var ImplicitSignal = z.object({
  id: z.string().uuid(),
  memory_id: z.string().uuid(),
  session_id: z.string(),
  signal_type: z.enum([
    "test_pass",
    "test_fail",
    "file_unchanged",
    "file_rewritten",
    "task_accepted",
    "task_rejected"
  ]),
  timestamp: z.string(),
  context: z.string().optional()
});
var RecallConfig = z.object({
  sync: SyncConfig.optional(),
  embeddings: EmbeddingConfig.optional()
});
var PolicyRule = z.object({
  id: z.string().uuid(),
  org_id: z.string(),
  rule_type: z.enum([
    "min_confidence",
    "require_approval",
    "allowed_sources",
    "blocked_scopes",
    "auto_approve_pattern",
    "max_active_per_repo",
    "require_evidence_count"
  ]),
  config: z.record(z.unknown()),
  enabled: z.boolean(),
  created_at: z.string(),
  updated_at: z.string()
});
var ApprovalStatus = z.enum(["pending", "approved", "denied"]);
var ApprovalRequest = z.object({
  id: z.string().uuid(),
  memory_id: z.string().uuid(),
  org_id: z.string(),
  requested_by: z.string(),
  status: ApprovalStatus,
  reviewed_by: z.string().nullable(),
  reason: z.string().nullable(),
  created_at: z.string(),
  resolved_at: z.string().nullable()
});
var HealthScore = z.object({
  memory_id: z.string().uuid(),
  score: z.number().min(0).max(1),
  confidence_component: z.number(),
  freshness_component: z.number(),
  follow_rate_component: z.number(),
  signal_ratio_component: z.number(),
  computed_at: z.string()
});
var Contradiction = z.object({
  id: z.string().uuid(),
  memory_a_id: z.string().uuid(),
  memory_b_id: z.string().uuid(),
  contradiction_type: z.enum([
    "direct_negation",
    "conflicting_rules",
    "scope_overlap",
    "superseded"
  ]),
  severity: z.enum(["low", "medium", "high"]),
  description: z.string(),
  resolved: z.boolean(),
  resolution: z.string().nullable(),
  detected_at: z.string(),
  resolved_at: z.string().nullable()
});
var PruneConfig = z.object({
  repo: z.string().optional(),
  stale_days: z.number().default(90),
  rejected_retention_days: z.number().default(30),
  transient_retention_days: z.number().default(7),
  min_health_score: z.number().default(0.2),
  dry_run: z.boolean().default(false)
});
var AuditAction = z.enum([
  "created",
  "promoted",
  "demoted",
  "rejected",
  "confirmed",
  "reactivated",
  "edited",
  "pruned",
  "policy_applied",
  "approval_requested",
  "approval_resolved",
  "contradiction_detected",
  "contradiction_resolved",
  "rolled_back"
]);
var AuditEntry = z.object({
  id: z.string().uuid(),
  memory_id: z.string().uuid(),
  action: AuditAction,
  actor: z.string(),
  before_snapshot: z.string().nullable(),
  after_snapshot: z.string().nullable(),
  reason: z.string().nullable(),
  timestamp: z.string()
});

// src/embeddings/cache.ts
import { existsSync, mkdirSync, readdirSync, statSync } from "fs";
import { homedir } from "os";
import { join } from "path";
function getEmbeddingCacheRoot() {
  if (process.platform === "linux" && process.env.XDG_CACHE_HOME) {
    return join(process.env.XDG_CACHE_HOME, "recall", "models");
  }
  return join(homedir(), ".recall", "models");
}
function getEmbeddingCachePath(config) {
  return join(getEmbeddingCacheRoot(), config.provider, ...config.model.split("/"));
}
function ensureEmbeddingCachePath(config) {
  const cachePath = getEmbeddingCachePath(config);
  mkdirSync(cachePath, { recursive: true });
  return cachePath;
}
function getDirectorySize(path) {
  if (!existsSync(path)) return 0;
  const stat = statSync(path);
  if (!stat.isDirectory()) return stat.size;
  let total = 0;
  for (const entry of readdirSync(path, { withFileTypes: true })) {
    total += getDirectorySize(join(path, entry.name));
  }
  return total;
}
function formatBytes(bytes) {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 ** 2) return `${(bytes / 1024).toFixed(1)} KB`;
  if (bytes < 1024 ** 3) return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
  return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
}

// src/embeddings/providers/all-minilm-l6-v2.ts
import { pipeline } from "@huggingface/transformers";
var MINILM_MODEL = "Xenova/all-MiniLM-L6-v2";
var MINILM_DIMENSIONS = 384;
var extractorPromise = null;
function getModel(config) {
  return config.model || MINILM_MODEL;
}
function getDimensions(config) {
  const dimensions = config.dimensions || MINILM_DIMENSIONS;
  if (!Number.isInteger(dimensions) || dimensions <= 0) {
    throw new Error(`Invalid all-MiniLM-L6-v2 embedding dimensions: ${dimensions}`);
  }
  if (dimensions > MINILM_DIMENSIONS) {
    throw new Error(`all-MiniLM-L6-v2 embeddings support at most ${MINILM_DIMENSIONS} dimensions`);
  }
  return dimensions;
}
async function getExtractor(config) {
  const cacheDir = ensureEmbeddingCachePath({
    provider: config.provider,
    model: getModel(config)
  });
  extractorPromise ??= pipeline("feature-extraction", getModel(config), {
    cache_dir: cacheDir,
    dtype: "q8"
  });
  return extractorPromise;
}
function tensorToEmbeddings(tensor) {
  const [rows, columns] = tensor.dims.length === 1 ? [1, tensor.dims[0]] : tensor.dims;
  if (!rows || !columns) {
    throw new Error(`Unexpected all-MiniLM-L6-v2 tensor shape: [${tensor.dims.join(", ")}]`);
  }
  const embeddings = [];
  for (let row = 0; row < rows; row++) {
    const start = row * columns;
    const end = start + columns;
    embeddings.push(Float32Array.from(tensor.data.subarray(start, end)));
  }
  return embeddings;
}
async function embedTexts(texts, config) {
  if (texts.length === 0) return [];
  const extractor = await getExtractor(config);
  const embeddings = await extractor(texts, {
    pooling: "mean",
    normalize: true
  });
  return tensorToEmbeddings(
    embeddings.dims.at(-1) === getDimensions(config) ? embeddings : embeddings.slice(null, [0, getDimensions(config)]).normalize(2, -1)
  );
}
function createAllMiniLmL6V2Provider(config) {
  return {
    async embed(text, _purpose = "document") {
      const [embedding] = await embedTexts([text], config);
      return embedding;
    },
    async embedBatch(texts, _purpose = "document") {
      return embedTexts(texts, config);
    },
    async prepare() {
      await getExtractor(config);
    },
    metadata() {
      const dims = getDimensions(config);
      return {
        model: getModel(config),
        dimensions: dims,
        canonical_dimensions: dims,
        index_dimensions: dims,
        version: config.version,
        estimated_size_mb: 23
      };
    }
  };
}

// src/embeddings/providers/bge-small-en-v1.5.ts
import { pipeline as pipeline2 } from "@huggingface/transformers";
var BGE_MODEL = "Xenova/bge-small-en-v1.5";
var BGE_DIMENSIONS = 384;
var extractorPromise2 = null;
function getModel2(config) {
  return config.model || BGE_MODEL;
}
function getDimensions2(config) {
  const dimensions = config.dimensions || BGE_DIMENSIONS;
  if (!Number.isInteger(dimensions) || dimensions <= 0) {
    throw new Error(`Invalid bge-small-en-v1.5 embedding dimensions: ${dimensions}`);
  }
  if (dimensions > BGE_DIMENSIONS) {
    throw new Error(`bge-small-en-v1.5 embeddings support at most ${BGE_DIMENSIONS} dimensions`);
  }
  return dimensions;
}
async function getExtractor2(config) {
  const cacheDir = ensureEmbeddingCachePath({
    provider: config.provider,
    model: getModel2(config)
  });
  extractorPromise2 ??= pipeline2("feature-extraction", getModel2(config), {
    cache_dir: cacheDir,
    dtype: "q8"
  });
  return extractorPromise2;
}
function tensorToEmbeddings2(tensor) {
  const [rows, columns] = tensor.dims.length === 1 ? [1, tensor.dims[0]] : tensor.dims;
  if (!rows || !columns) {
    throw new Error(`Unexpected bge-small-en-v1.5 tensor shape: [${tensor.dims.join(", ")}]`);
  }
  const embeddings = [];
  for (let row = 0; row < rows; row++) {
    const start = row * columns;
    const end = start + columns;
    embeddings.push(Float32Array.from(tensor.data.subarray(start, end)));
  }
  return embeddings;
}
async function embedTexts2(texts, config) {
  if (texts.length === 0) return [];
  const extractor = await getExtractor2(config);
  const embeddings = await extractor(texts, {
    pooling: "mean",
    normalize: true
  });
  return tensorToEmbeddings2(
    embeddings.dims.at(-1) === getDimensions2(config) ? embeddings : embeddings.slice(null, [0, getDimensions2(config)]).normalize(2, -1)
  );
}
function createBgeSmallEnV15Provider(config) {
  return {
    async embed(text, _purpose = "document") {
      const [embedding] = await embedTexts2([text], config);
      return embedding;
    },
    async embedBatch(texts, _purpose = "document") {
      return embedTexts2(texts, config);
    },
    async prepare() {
      await getExtractor2(config);
    },
    metadata() {
      const dims = getDimensions2(config);
      return {
        model: getModel2(config),
        dimensions: dims,
        canonical_dimensions: dims,
        index_dimensions: dims,
        version: config.version,
        estimated_size_mb: 133
      };
    }
  };
}

// src/embeddings/providers/multilingual-e5.ts
import { pipeline as pipeline3 } from "@huggingface/transformers";
var MULTILINGUAL_E5_MODEL = "Xenova/multilingual-e5-small";
var MULTILINGUAL_E5_NATIVE_DIMENSIONS = 384;
var MULTILINGUAL_E5_PREFIXES = {
  document: "passage: ",
  query: "query: "
};
var extractorPromise3 = null;
function getModel3(config) {
  return config.model || MULTILINGUAL_E5_MODEL;
}
function getDimensions3(config) {
  const dimensions = config.dimensions || MULTILINGUAL_E5_NATIVE_DIMENSIONS;
  if (!Number.isInteger(dimensions) || dimensions <= 0) {
    throw new Error(`Invalid multilingual-e5 embedding dimensions: ${dimensions}`);
  }
  if (dimensions > MULTILINGUAL_E5_NATIVE_DIMENSIONS) {
    throw new Error(`multilingual-e5 embeddings support at most ${MULTILINGUAL_E5_NATIVE_DIMENSIONS} dimensions`);
  }
  return dimensions;
}
function prefixTexts(texts, purpose) {
  const prefix = MULTILINGUAL_E5_PREFIXES[purpose];
  return texts.map((text) => `${prefix}${text}`);
}
async function getExtractor3(config) {
  const cacheDir = ensureEmbeddingCachePath({
    provider: config.provider,
    model: getModel3(config)
  });
  extractorPromise3 ??= pipeline3("feature-extraction", getModel3(config), {
    cache_dir: cacheDir,
    dtype: "q8"
  });
  return extractorPromise3;
}
function tensorToEmbeddings3(tensor) {
  const [rows, columns] = tensor.dims.length === 1 ? [1, tensor.dims[0]] : tensor.dims;
  if (!rows || !columns) {
    throw new Error(`Unexpected multilingual-e5 tensor shape: [${tensor.dims.join(", ")}]`);
  }
  const embeddings = [];
  for (let row = 0; row < rows; row++) {
    const start = row * columns;
    const end = start + columns;
    embeddings.push(Float32Array.from(tensor.data.subarray(start, end)));
  }
  return embeddings;
}
async function embedTexts3(texts, config, purpose) {
  if (texts.length === 0) return [];
  const extractor = await getExtractor3(config);
  const embeddings = await extractor(prefixTexts(texts, purpose), {
    pooling: "mean",
    normalize: true
  });
  return tensorToEmbeddings3(
    embeddings.dims.at(-1) === getDimensions3(config) ? embeddings : embeddings.slice(null, [0, getDimensions3(config)]).normalize(2, -1)
  );
}
function createMultilingualE5Provider(config) {
  return {
    async embed(text, purpose = "document") {
      const [embedding] = await embedTexts3([text], config, purpose);
      return embedding;
    },
    async embedBatch(texts, purpose = "document") {
      return embedTexts3(texts, config, purpose);
    },
    async prepare() {
      await getExtractor3(config);
    },
    metadata() {
      const dims = getDimensions3(config);
      return {
        model: getModel3(config),
        dimensions: dims,
        canonical_dimensions: dims,
        index_dimensions: dims,
        version: config.version,
        task_prefix: `${MULTILINGUAL_E5_PREFIXES.document.trim()} | ${MULTILINGUAL_E5_PREFIXES.query.trim()}`,
        estimated_size_mb: 113
      };
    }
  };
}

// src/embeddings/providers/nomic.ts
import { layer_norm, pipeline as pipeline4 } from "@huggingface/transformers";
var NOMIC_MODEL = "nomic-ai/nomic-embed-text-v1.5";
var NOMIC_NATIVE_DIMENSIONS = 768;
var NOMIC_PREFIXES = {
  document: "search_document: ",
  query: "search_query: "
};
var extractorPromise4 = null;
function getModel4(config) {
  return config.model || NOMIC_MODEL;
}
function getDimensions4(config) {
  const dimensions = config.dimensions || 512;
  if (!Number.isInteger(dimensions) || dimensions <= 0) {
    throw new Error(`Invalid nomic embedding dimensions: ${dimensions}`);
  }
  if (dimensions > NOMIC_NATIVE_DIMENSIONS) {
    throw new Error(`Nomic embeddings support at most ${NOMIC_NATIVE_DIMENSIONS} dimensions`);
  }
  return dimensions;
}
function prefixTexts2(texts, purpose) {
  const prefix = NOMIC_PREFIXES[purpose];
  return texts.map((text) => `${prefix}${text}`);
}
async function getExtractor4(config) {
  const cacheDir = ensureEmbeddingCachePath({
    provider: config.provider,
    model: getModel4(config)
  });
  extractorPromise4 ??= pipeline4("feature-extraction", getModel4(config), {
    cache_dir: cacheDir,
    dtype: "q8"
  });
  return extractorPromise4;
}
function tensorToEmbeddings4(tensor) {
  const [rows, columns] = tensor.dims.length === 1 ? [1, tensor.dims[0]] : tensor.dims;
  if (!rows || !columns) {
    throw new Error(`Unexpected nomic tensor shape: [${tensor.dims.join(", ")}]`);
  }
  const embeddings = [];
  for (let row = 0; row < rows; row++) {
    const start = row * columns;
    const end = start + columns;
    embeddings.push(Float32Array.from(tensor.data.subarray(start, end)));
  }
  return embeddings;
}
async function embedTexts4(texts, config, purpose) {
  if (texts.length === 0) return [];
  const extractor = await getExtractor4(config);
  const rawEmbeddings = await extractor(prefixTexts2(texts, purpose), {
    pooling: "mean"
  });
  const nativeDimensions = rawEmbeddings.dims.at(-1);
  if (!nativeDimensions) {
    throw new Error("Nomic extractor returned an embedding tensor without dimensions");
  }
  const normalized = layer_norm(rawEmbeddings, [nativeDimensions]).normalize(2, -1);
  return tensorToEmbeddings4(normalized);
}
function createNomicProvider(config) {
  return {
    async embed(text, purpose = "document") {
      const [embedding] = await embedTexts4([text], config, purpose);
      return embedding;
    },
    async embedBatch(texts, purpose = "document") {
      return embedTexts4(texts, config, purpose);
    },
    async prepare() {
      await getExtractor4(config);
    },
    metadata() {
      const indexDimensions = getDimensions4(config);
      return {
        model: getModel4(config),
        dimensions: indexDimensions,
        canonical_dimensions: NOMIC_NATIVE_DIMENSIONS,
        index_dimensions: indexDimensions,
        version: config.version,
        task_prefix: `${NOMIC_PREFIXES.document.trim()} | ${NOMIC_PREFIXES.query.trim()}`,
        estimated_size_mb: 140
      };
    }
  };
}

// src/embeddings/providers/index.ts
function resolveProvider(config) {
  switch (config.provider) {
    case "all-MiniLM-L6-v2":
      return createAllMiniLmL6V2Provider(config);
    case "bge-small-en-v1.5":
      return createBgeSmallEnV15Provider(config);
    case "multilingual-e5":
      return createMultilingualE5Provider(config);
    case "nomic":
      return createNomicProvider(config);
    default:
      throw new Error(`Unsupported embedding provider: ${config.provider}`);
  }
}

// src/vector/sqlite-vec.ts
import * as sqliteVec from "sqlite-vec";
var VEC_MEMORY_INDEX = "vec_memory_index";
var loadedClients = /* @__PURE__ */ new WeakSet();
function getSqlite(db) {
  return db.$client;
}
function hasMemoryVecIndex(db) {
  return Boolean(
    getSqlite(db).prepare("select 1 from sqlite_master where type = 'table' and name = ?").get(VEC_MEMORY_INDEX)
  );
}
function ensureSqliteVecLoaded(db) {
  const sqlite = getSqlite(db);
  if (loadedClients.has(sqlite)) return;
  sqliteVec.load(sqlite);
  loadedClients.add(sqlite);
}
function getMemoryVecDimension(rows) {
  const dimensions = [...new Set(rows.map((row) => row.index_dimensions))];
  if (dimensions.length === 0) return null;
  if (dimensions.length > 1) {
    throw new Error(
      `sqlite-vec index rebuild refused mixed memory embedding dimensions: ${dimensions.join(", ")}.`
    );
  }
  return dimensions[0];
}
function ensureMemoryVecIndex(db, dimensions) {
  ensureSqliteVecLoaded(db);
  const sqlite = getSqlite(db);
  const existing = sqlite.prepare("select sql from sqlite_master where type = 'table' and name = ?").get(VEC_MEMORY_INDEX);
  const expectedDimension = `float[${dimensions}]`;
  if (existing?.sql && !existing.sql.includes(expectedDimension)) {
    throw new Error(
      `sqlite-vec index dimension mismatch. Expected ${expectedDimension}. Run \`recall embeddings rebuild-index\`.`
    );
  }
  sqlite.exec(`
    create virtual table if not exists ${VEC_MEMORY_INDEX} using vec0(
      embedding float[${dimensions}] distance_metric=cosine,
      memory_id text,
      repo text,
      status text,
      type text,
      scope text
    );
  `);
}
function dropMemoryVecIndex(db) {
  ensureSqliteVecLoaded(db);
  getSqlite(db).exec(`drop table if exists ${VEC_MEMORY_INDEX};`);
}
function upsertMemoryVecRow(db, memory, embeddingRow) {
  ensureMemoryVecIndex(db, embeddingRow.index_dimensions);
  const sqlite = getSqlite(db);
  sqlite.prepare(`delete from ${VEC_MEMORY_INDEX} where memory_id = ?`).run(memory.id);
  sqlite.prepare(`
    insert into ${VEC_MEMORY_INDEX} (
      embedding,
      memory_id,
      repo,
      status,
      type,
      scope
    ) values (?, ?, ?, ?, ?, ?)
  `).run(
    projectIndexBuffer(embeddingRow.embedding, embeddingRow.index_dimensions),
    memory.id,
    memory.repo ?? "",
    memory.status,
    memory.type,
    memory.scope
  );
}
function removeMemoryVecRow(db, memoryId, config) {
  ensureSqliteVecLoaded(db);
  if (!hasMemoryVecIndex(db)) return;
  getSqlite(db).prepare(`delete from ${VEC_MEMORY_INDEX} where memory_id = ?`).run(memoryId);
}
function rebuildMemoryVecIndex(db, config, options = {}) {
  const rows = db.select({
    id: memories.id,
    repo: memories.repo,
    status: memories.status,
    type: memories.type,
    scope: memories.scope,
    index_dimensions: memoryEmbeddings.index_dimensions,
    embedding: memoryEmbeddings.embedding
  }).from(memories).innerJoin(memoryEmbeddings, eq(memoryEmbeddings.memory_id, memories.id)).all().filter((row) => !options.repo || row.repo === options.repo);
  const storedDimension = getMemoryVecDimension(rows);
  const targetDimension = storedDimension ?? config.dimensions;
  if (options.repo) {
    if (rows.length > 0) {
      ensureMemoryVecIndex(db, targetDimension);
    }
    if (!hasMemoryVecIndex(db)) return 0;
    getSqlite(db).prepare(`delete from ${VEC_MEMORY_INDEX} where repo = ?`).run(options.repo);
    if (rows.length === 0) return 0;
  } else {
    dropMemoryVecIndex(db);
    ensureMemoryVecIndex(db, targetDimension);
  }
  const sqlite = getSqlite(db);
  const stmt = sqlite.prepare(`
    insert into ${VEC_MEMORY_INDEX} (
      embedding,
      memory_id,
      repo,
      status,
      type,
      scope
    ) values (?, ?, ?, ?, ?, ?)
  `);
  const insertMany = sqlite.transaction((batch) => {
    for (const row of batch) {
      stmt.run(
        projectIndexBuffer(row.embedding, row.index_dimensions),
        row.id,
        row.repo ?? "",
        row.status,
        row.type,
        row.scope
      );
    }
  });
  insertMany(rows);
  return rows.length;
}
function projectIndexBuffer(buffer, indexDimensions) {
  const embedding = new Float32Array(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength / Float32Array.BYTES_PER_ELEMENT
  );
  if (embedding.length === indexDimensions) {
    return buffer;
  }
  if (embedding.length < indexDimensions) {
    throw new Error(`Canonical embedding width ${embedding.length} is smaller than index width ${indexDimensions}.`);
  }
  const sliced = embedding.slice(0, indexDimensions);
  let norm = 0;
  for (const value of sliced) norm += value * value;
  const scale = Math.sqrt(norm) || 1;
  for (let i = 0; i < sliced.length; i++) {
    sliced[i] /= scale;
  }
  return Buffer.from(sliced.buffer, sliced.byteOffset, sliced.byteLength);
}
function verifyMemoryVecIndex(db, options = {}) {
  ensureSqliteVecLoaded(db);
  const sqlite = getSqlite(db);
  const exists = sqlite.prepare("select 1 from sqlite_master where type = 'table' and name = ?").get(VEC_MEMORY_INDEX);
  const expectedRows = db.select({
    memory_id: memoryEmbeddings.memory_id,
    repo: memories.repo
  }).from(memoryEmbeddings).innerJoin(memories, eq(memories.id, memoryEmbeddings.memory_id)).all().filter((row) => !options.repo || row.repo === options.repo);
  let indexed = 0;
  if (exists) {
    if (options.repo) {
      const result = sqlite.prepare(`select count(*) as count from ${VEC_MEMORY_INDEX} where repo = ?`).get(options.repo);
      indexed = result.count;
    } else {
      const result = sqlite.prepare(`select count(*) as count from ${VEC_MEMORY_INDEX}`).get();
      indexed = result.count;
    }
  }
  return {
    expected: expectedRows.length,
    indexed,
    drift: expectedRows.length - indexed
  };
}
function searchMemoryVecIndex(db, queryEmbedding, options = {}) {
  ensureSqliteVecLoaded(db);
  const sqlite = getSqlite(db);
  const exists = sqlite.prepare("select 1 from sqlite_master where type = 'table' and name = ?").get(VEC_MEMORY_INDEX);
  if (!exists) return [];
  const limit = options.limit ?? 10;
  if (options.repo) {
    return sqlite.prepare(`
      select memory_id, distance
      from ${VEC_MEMORY_INDEX}
      where embedding match ?
        and k = ?
        and repo = ?
      order by distance
    `).all(queryEmbedding, limit, options.repo);
  }
  return sqlite.prepare(`
    select memory_id, distance
    from ${VEC_MEMORY_INDEX}
    where embedding match ?
      and k = ?
    order by distance
  `).all(queryEmbedding, limit);
}

// src/vector/synonyms.ts
import { readFileSync, existsSync as existsSync2 } from "fs";

// src/data/synonyms.en.json
var synonyms_en_default = {
  $schema: "Bidirectional synonym groups for FTS5 query-time expansion. Each group becomes a parenthesized OR-clause when any of its members appears in the query. English-first; override path via RECALL_SYNONYMS_PATH.",
  groups: [
    ["degree", "diploma", "qualification", "credential"],
    ["graduate", "graduated", "graduating", "graduation", "finished", "completed", "earned"],
    ["school", "university", "college", "campus", "uni"],
    ["study", "studied", "studying", "learn", "learned", "learning", "major", "majored"],
    ["course", "class", "subject"],
    ["job", "career", "occupation", "employment", "work", "working"],
    ["company", "employer", "firm", "organization", "org"],
    ["boss", "manager", "supervisor", "lead"],
    ["coworker", "colleague", "teammate"],
    ["salary", "income", "pay", "wage", "earnings"],
    ["promote", "promoted", "promotion", "advance", "advancement"],
    ["interview", "interviewed", "interviewing"],
    ["resume", "cv", "curriculum"],
    ["commute", "commuted", "commuting", "travel", "traveled", "trip"],
    ["drive", "drove", "driving", "ride", "riding"],
    ["car", "vehicle", "auto", "automobile"],
    ["bike", "bicycle", "cycling", "cycle"],
    ["walk", "walked", "walking", "hike", "hiked", "hiking", "stroll"],
    ["flight", "flew", "flying", "plane", "airplane", "airline"],
    ["train", "rail", "metro", "subway"],
    ["bus", "coach"],
    ["taxi", "cab", "uber", "lyft", "rideshare"],
    ["doctor", "physician", "gp", "md"],
    ["dentist", "orthodontist"],
    ["therapist", "counselor", "psychologist"],
    ["sick", "ill", "unwell", "feverish"],
    ["medicine", "medication", "drug", "prescription", "pill"],
    ["appointment", "visit", "checkup", "consultation"],
    ["weight", "mass", "kg", "kilograms", "pounds", "lbs"],
    ["height", "tall", "stature"],
    ["sleep", "slept", "sleeping", "nap", "rest"],
    ["exercise", "workout", "training", "gym", "fitness"],
    ["run", "ran", "running", "jog", "jogged", "jogging"],
    ["swim", "swam", "swimming", "swimmer"],
    ["yoga", "pilates", "stretching"],
    ["meal", "dinner", "lunch", "breakfast", "supper"],
    ["eat", "ate", "eaten", "eating", "consume", "consumed"],
    ["drink", "drank", "drinking", "beverage"],
    ["coffee", "espresso", "latte", "cappuccino"],
    ["tea", "matcha", "chai"],
    ["wine", "beer", "alcohol", "liquor", "drink"],
    ["restaurant", "diner", "cafe", "bistro", "eatery"],
    ["cook", "cooked", "cooking", "prepare", "prepared"],
    ["recipe", "dish", "cuisine"],
    ["bought", "purchased", "acquired", "got"],
    ["sold", "selling", "sell"],
    ["paid", "pay", "paying", "spent", "spending"],
    ["money", "cash", "funds"],
    ["save", "saved", "saving", "savings", "budget"],
    ["bank", "account", "savings"],
    ["credit", "debit", "card"],
    ["tax", "taxes", "irs", "filing"],
    ["loan", "mortgage", "debt", "loan"],
    ["invest", "invested", "investing", "investment", "investments"],
    ["stocks", "shares", "equity", "etf", "mutual"],
    ["house", "home", "apartment", "flat", "condo", "residence"],
    ["rent", "rented", "renting", "lease", "leased", "leasing"],
    ["bedroom", "room", "studio"],
    ["kitchen", "cooking"],
    ["bathroom", "restroom", "washroom"],
    ["family", "relatives"],
    ["parent", "parents"],
    ["mom", "mother", "mum"],
    ["dad", "father", "papa"],
    ["sister", "brother", "sibling"],
    ["spouse", "wife", "husband", "partner"],
    ["child", "kid", "son", "daughter", "children", "kids"],
    ["friend", "buddy", "pal", "mate"],
    ["neighbor", "neighbour"],
    ["boyfriend", "girlfriend", "partner", "significant"],
    ["wedding", "marriage", "marry", "married"],
    ["birthday", "anniversary"],
    ["holiday", "vacation", "trip", "getaway"],
    ["weekend", "saturday", "sunday"],
    ["morning", "am", "dawn"],
    ["evening", "night", "pm", "dusk"],
    ["soon", "shortly", "quickly", "imminently"],
    ["recent", "recently", "lately"],
    ["yesterday", "previous"],
    ["tomorrow", "next"],
    ["plan", "planned", "planning", "scheduled", "intend", "intended"],
    ["meeting", "call", "sync", "standup", "huddle"],
    ["project", "initiative", "effort"],
    ["task", "todo", "ticket", "issue", "story"],
    ["deadline", "due", "cutoff"],
    ["release", "launch", "ship", "shipping", "deploy", "deployed"],
    ["customer", "client", "user", "buyer"],
    ["product", "service", "offering"],
    ["feature", "capability", "function"],
    ["bug", "defect", "issue", "problem", "error"],
    ["fix", "fixed", "fixing", "repair", "repaired", "resolve", "resolved"],
    ["build", "built", "building", "make", "made", "create", "created"],
    ["test", "tested", "testing", "qa", "verify", "verified"],
    ["deploy", "deployed", "deployment", "release", "released"],
    ["server", "host", "backend", "api"],
    ["database", "db", "storage"],
    ["frontend", "ui", "client", "interface"],
    ["mobile", "ios", "android", "phone"],
    ["desktop", "mac", "windows", "linux"],
    ["movie", "film", "show", "series"],
    ["book", "novel", "memoir", "biography"],
    ["read", "reading", "reader"],
    ["watch", "watched", "watching", "viewed"],
    ["song", "music", "track", "album"],
    ["concert", "gig", "show", "performance"],
    ["game", "gaming", "play", "played", "playing"],
    ["sport", "athletic"],
    ["football", "soccer"],
    ["basketball", "hoops"],
    ["tennis", "racquet"],
    ["dog", "puppy", "canine"],
    ["cat", "kitten", "feline"],
    ["pet", "animal"],
    ["weather", "climate", "forecast"],
    ["rain", "rainy", "raining"],
    ["snow", "snowy", "snowing"],
    ["sunny", "sun", "sunshine"],
    ["cold", "chilly", "freezing"],
    ["hot", "warm", "heat"],
    ["language", "tongue", "linguistic"],
    ["english", "spanish", "french", "german", "italian", "portuguese", "japanese", "chinese", "korean", "russian"],
    ["computer", "laptop", "machine", "pc"],
    ["phone", "mobile", "cellphone", "smartphone"],
    ["app", "application", "software", "program"],
    ["website", "site", "webpage", "url"],
    ["email", "mail", "inbox"],
    ["message", "msg", "text", "chat"],
    ["call", "phone", "ring", "called"],
    ["happy", "glad", "pleased", "delighted", "joyful"],
    ["sad", "unhappy", "depressed", "down"],
    ["angry", "mad", "frustrated", "annoyed", "upset"],
    ["surprised", "amazed", "astonished", "shocked"],
    ["love", "loved", "loving", "adore", "adored"],
    ["like", "liked", "enjoy", "enjoyed"],
    ["hate", "hated", "dislike", "disliked", "despise"],
    ["want", "wanted", "wanting", "wish", "wished", "desire", "desired"],
    ["need", "needed", "require", "required"],
    ["help", "helped", "helping", "assist", "assisted"],
    ["ask", "asked", "asking", "question", "queried"],
    ["tell", "told", "say", "said", "mention", "mentioned"],
    ["think", "thought", "thinking", "believe", "believed"],
    ["know", "knew", "knowing", "aware", "understood"],
    ["see", "saw", "seen", "view", "viewed", "look", "looked"],
    ["find", "found", "finding", "locate", "located"],
    ["get", "got", "getting", "receive", "received", "obtain", "obtained"],
    ["give", "gave", "given", "giving", "provide", "provided"],
    ["take", "took", "taken", "taking"],
    ["start", "started", "starting", "begin", "began", "begun"],
    ["stop", "stopped", "stopping", "end", "ended", "finish", "finished"],
    ["change", "changed", "changing", "modify", "modified", "alter", "altered"],
    ["increase", "increased", "growth", "grow", "grew"],
    ["decrease", "decreased", "drop", "dropped", "shrink"],
    ["big", "large", "huge", "enormous"],
    ["small", "tiny", "little", "minor"],
    ["fast", "quick", "rapid", "swift"],
    ["slow", "gradual"],
    ["good", "great", "excellent", "wonderful", "fantastic"],
    ["bad", "poor", "terrible", "awful", "horrible"],
    ["new", "fresh", "recent", "latest"],
    ["old", "ancient", "former", "previous"],
    ["plant", "tree", "flower", "garden"],
    ["beach", "ocean", "sea", "shore"],
    ["mountain", "hill", "peak", "summit"],
    ["forest", "wood", "woods", "jungle"],
    ["lake", "pond", "river", "stream"],
    ["city", "town", "village", "metropolis"],
    ["country", "nation", "state"],
    ["wedding", "marriage", "matrimony"],
    ["birthday", "bday"],
    ["gift", "present"]
  ]
};

// src/vector/synonyms.ts
var cached = null;
function loadSynonymsFromFile(path) {
  try {
    const raw = readFileSync(path, "utf-8");
    const parsed = JSON.parse(raw);
    if (!parsed.groups || !Array.isArray(parsed.groups)) return null;
    return parsed;
  } catch {
    return null;
  }
}
function loadBundled() {
  const data = synonyms_en_default;
  if (!data.groups || !Array.isArray(data.groups)) return null;
  return { groups: data.groups };
}
function buildMap(files) {
  const map = /* @__PURE__ */ new Map();
  for (const file of files) {
    for (const group of file.groups) {
      const lowered = group.map((t) => t.toLowerCase());
      for (const token of lowered) {
        const existing = map.get(token);
        const merged = existing ? [...existing, ...lowered] : [...lowered];
        const unique = [...new Set(merged)].filter((t) => t !== token);
        map.set(token, unique);
      }
    }
  }
  return map;
}
function getSynonymMap() {
  if (cached) return cached;
  if (process.env.RECALL_SYNONYMS === "false") {
    cached = /* @__PURE__ */ new Map();
    return cached;
  }
  const files = [];
  const bundled = loadBundled();
  if (bundled) files.push(bundled);
  const overridePath = process.env.RECALL_SYNONYMS_PATH;
  if (overridePath && existsSync2(overridePath)) {
    const override = loadSynonymsFromFile(overridePath);
    if (override) files.push(override);
  }
  cached = buildMap(files);
  return cached;
}
function getSynonyms(token) {
  const map = getSynonymMap();
  return map.get(token.toLowerCase()) ?? [];
}

// src/vector/sqlite-fts.ts
var FTS_MEMORY_INDEX = "fts_memory_index";
var FTS_TOKENIZER = `porter unicode61 remove_diacritics 2`;
function getSqlite2(db) {
  return db.$client;
}
function shouldIndexLexically(memory) {
  return memory.status !== "rejected" && memory.status !== "transient";
}
function isPrefixable(token) {
  return token.length >= 4 && /^[A-Za-z]+$/.test(token);
}
function emitToken(token, prefixDisabled) {
  return !prefixDisabled && isPrefixable(token) ? `"${token}"*` : `"${token}"`;
}
function expandTokenWithSynonyms(token, prefixDisabled, synonymsDisabled) {
  const base = emitToken(token, prefixDisabled);
  if (synonymsDisabled) return base;
  const syns = getSynonyms(token);
  if (syns.length === 0) return base;
  const alts = syns.map((s) => emitToken(s, prefixDisabled));
  return `(${[base, ...alts].join(" OR ")})`;
}
function buildFtsQuery(query) {
  const tokens = query.match(/[A-Za-z0-9_.:/-]+/g)?.map((token) => token.replace(/"/g, '""')).filter(Boolean) ?? [];
  if (tokens.length === 0) return null;
  const join3 = process.env.RECALL_FTS_MODE === "or" ? " OR " : " AND ";
  const prefixDisabled = process.env.RECALL_FTS_PREFIX === "false";
  const synonymsDisabled = process.env.RECALL_SYNONYMS === "false";
  return tokens.map(
    (token) => expandTokenWithSynonyms(token, prefixDisabled, synonymsDisabled)
  ).join(join3);
}
function getFtsCreateSql(db, table) {
  const row = getSqlite2(db).prepare("select sql from sqlite_master where type = 'table' and name = ?").get(table);
  return row?.sql ?? null;
}
function ensureMemoryFtsIndex(db) {
  const sqlite = getSqlite2(db);
  const existing = getFtsCreateSql(db, FTS_MEMORY_INDEX);
  const needsMigration = existing !== null && !existing.includes("porter");
  if (needsMigration) {
    sqlite.exec(`drop table if exists ${FTS_MEMORY_INDEX};`);
  }
  sqlite.exec(`
    create virtual table if not exists ${FTS_MEMORY_INDEX} using fts5(
      memory_id UNINDEXED,
      text,
      repo UNINDEXED,
      status UNINDEXED,
      type UNINDEXED,
      scope UNINDEXED,
      path_scope UNINDEXED,
      tokenize="${FTS_TOKENIZER}"
    );
  `);
  if (needsMigration) {
    rebuildMemoryFtsIndex(db);
  }
}
function dropMemoryFtsIndex(db) {
  getSqlite2(db).exec(`drop table if exists ${FTS_MEMORY_INDEX};`);
}
function removeMemoryFtsRow(db, memoryId) {
  const sqlite = getSqlite2(db);
  const exists = sqlite.prepare("select 1 from sqlite_master where type = 'table' and name = ?").get(FTS_MEMORY_INDEX);
  if (!exists) return;
  sqlite.prepare(`delete from ${FTS_MEMORY_INDEX} where memory_id = ?`).run(memoryId);
}
function upsertMemoryFtsRow(db, memory) {
  ensureMemoryFtsIndex(db);
  if (!shouldIndexLexically(memory)) {
    removeMemoryFtsRow(db, memory.id);
    return;
  }
  const sqlite = getSqlite2(db);
  sqlite.prepare(`delete from ${FTS_MEMORY_INDEX} where memory_id = ?`).run(memory.id);
  sqlite.prepare(`
    insert into ${FTS_MEMORY_INDEX} (
      memory_id,
      text,
      repo,
      status,
      type,
      scope,
      path_scope
    ) values (?, ?, ?, ?, ?, ?, ?)
  `).run(
    memory.id,
    memory.text,
    memory.repo ?? "",
    memory.status,
    memory.type,
    memory.scope,
    memory.path_scope ?? ""
  );
}
function syncMemoryFtsIndex(db, memoryId) {
  const memory = db.select().from(memories).where(eq(memories.id, memoryId)).get();
  if (!memory) {
    removeMemoryFtsRow(db, memoryId);
    return "removed";
  }
  upsertMemoryFtsRow(db, memory);
  return shouldIndexLexically(memory) ? "stored" : "removed";
}
function rebuildMemoryFtsIndex(db, options = {}) {
  if (options.repo) {
    ensureMemoryFtsIndex(db);
    getSqlite2(db).prepare(`delete from ${FTS_MEMORY_INDEX} where repo = ?`).run(options.repo);
  } else {
    dropMemoryFtsIndex(db);
    ensureMemoryFtsIndex(db);
  }
  const rows = db.select().from(memories).all().filter((row) => !options.repo || row.repo === options.repo).filter((row) => shouldIndexLexically(row));
  const sqlite = getSqlite2(db);
  const stmt = sqlite.prepare(`
    insert into ${FTS_MEMORY_INDEX} (
      memory_id,
      text,
      repo,
      status,
      type,
      scope,
      path_scope
    ) values (?, ?, ?, ?, ?, ?, ?)
  `);
  const insertMany = sqlite.transaction((batch) => {
    for (const row of batch) {
      stmt.run(
        row.id,
        row.text,
        row.repo ?? "",
        row.status,
        row.type,
        row.scope,
        row.path_scope ?? ""
      );
    }
  });
  insertMany(rows);
  return rows.length;
}
function verifyMemoryFtsIndex(db, options = {}) {
  const sqlite = getSqlite2(db);
  const exists = sqlite.prepare("select 1 from sqlite_master where type = 'table' and name = ?").get(FTS_MEMORY_INDEX);
  const expected = db.select().from(memories).all().filter((row) => !options.repo || row.repo === options.repo).filter((row) => shouldIndexLexically(row)).length;
  let indexed = 0;
  if (exists) {
    if (options.repo) {
      const result = sqlite.prepare(`select count(*) as count from ${FTS_MEMORY_INDEX} where repo = ?`).get(options.repo);
      indexed = result.count;
    } else {
      const result = sqlite.prepare(`select count(*) as count from ${FTS_MEMORY_INDEX}`).get();
      indexed = result.count;
    }
  }
  return {
    expected,
    indexed,
    drift: expected - indexed
  };
}
function searchMemoryFtsIndex(db, query, options = {}) {
  ensureMemoryFtsIndex(db);
  const sqlite = getSqlite2(db);
  const limit = options.limit ?? 10;
  const ftsQuery = buildFtsQuery(query);
  if (!ftsQuery) return [];
  if (options.repo) {
    return sqlite.prepare(`
      select memory_id, bm25(${FTS_MEMORY_INDEX}) as lexical_rank
      from ${FTS_MEMORY_INDEX}
      where ${FTS_MEMORY_INDEX} match ?
        and repo = ?
      order by lexical_rank
      limit ?
    `).all(ftsQuery, options.repo, limit);
  }
  return sqlite.prepare(`
    select memory_id, bm25(${FTS_MEMORY_INDEX}) as lexical_rank
    from ${FTS_MEMORY_INDEX}
    where ${FTS_MEMORY_INDEX} match ?
    order by lexical_rank
    limit ?
  `).all(ftsQuery, limit);
}

// src/embeddings/hyde.ts
import { createHash } from "crypto";
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
import { dirname } from "path";

// src/llm/client.ts
init_keychain();
import { randomUUID } from "crypto";
var LlmCredentialError = class extends Error {
};
var LlmRequestError = class extends Error {
};
var DEFAULT_MODELS = {
  openai: "gpt-4o-mini",
  anthropic: "claude-haiku-4-5-20251001",
  // For Azure the "model" is the deployment name, set by the user when they
  // provisioned the deployment. We leave it empty so we always fall through
  // to the deployment from AzureOpenAiConfig.
  "azure-openai": ""
};
var COST_PER_M_TOKENS = {
  "gpt-4o-mini": { input: 0.15, output: 0.6 },
  "gpt-4o": { input: 2.5, output: 10 },
  "claude-haiku-4-5-20251001": { input: 1, output: 5 },
  "claude-sonnet-4-6": { input: 3, output: 15 },
  "claude-opus-4-7": { input: 15, output: 75 }
};
async function callLlm(db, input) {
  const provider = input.provider;
  const config = getProviderConfig(provider);
  if (!config) {
    throw new LlmCredentialError(missingCredentialMessage(provider));
  }
  const model = input.model ?? (provider === "azure-openai" ? config.deployment : DEFAULT_MODELS[provider]);
  const started = Date.now();
  let result = null;
  let errorMessage;
  try {
    if (provider === "openai") {
      result = await callOpenAi(config.key, model, input);
    } else if (provider === "anthropic") {
      result = await callAnthropic(config.key, model, input);
    } else {
      result = await callAzureOpenAi(config, model, input);
    }
    return result;
  } catch (err) {
    errorMessage = err instanceof Error ? err.message : String(err);
    throw err;
  } finally {
    try {
      await recordUsage(db, {
        provider,
        model,
        task_kind: input.task_kind,
        task_id: input.task_id ?? null,
        repo: input.repo ?? null,
        usage: result?.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, cost_usd: null },
        duration_ms: Date.now() - started,
        ok: Boolean(result),
        error: errorMessage
      });
    } catch {
    }
  }
}
async function callOpenAi(apiKey, model, input) {
  const started = Date.now();
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      model,
      messages: [
        { role: "system", content: input.system },
        { role: "user", content: input.user }
      ],
      max_completion_tokens: input.max_output_tokens ?? 2048,
      temperature: input.temperature ?? 0
    })
  });
  if (!response.ok) {
    const body = await safeText(response);
    throw new LlmRequestError(`OpenAI ${response.status}: ${body.slice(0, 400)}`);
  }
  const payload = await response.json();
  const text = payload.choices?.[0]?.message?.content?.trim() ?? "";
  const prompt_tokens = payload.usage?.prompt_tokens ?? 0;
  const completion_tokens = payload.usage?.completion_tokens ?? 0;
  const total_tokens = payload.usage?.total_tokens ?? prompt_tokens + completion_tokens;
  return {
    text,
    model,
    provider: "openai",
    duration_ms: Date.now() - started,
    usage: {
      prompt_tokens,
      completion_tokens,
      total_tokens,
      cost_usd: computeCost(model, prompt_tokens, completion_tokens)
    }
  };
}
async function callAzureOpenAi(config, deployment, input) {
  const started = Date.now();
  const url = `${config.endpoint}/openai/deployments/${encodeURIComponent(deployment)}/chat/completions?api-version=${encodeURIComponent(config.api_version)}`;
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "api-key": config.key
    },
    body: JSON.stringify({
      messages: [
        { role: "system", content: input.system },
        { role: "user", content: input.user }
      ],
      max_completion_tokens: input.max_output_tokens ?? 2048,
      temperature: input.temperature ?? 0
    })
  });
  if (!response.ok) {
    const body = await safeText(response);
    throw new LlmRequestError(`Azure OpenAI ${response.status}: ${body.slice(0, 400)}`);
  }
  const payload = await response.json();
  const text = payload.choices?.[0]?.message?.content?.trim() ?? "";
  const prompt_tokens = payload.usage?.prompt_tokens ?? 0;
  const completion_tokens = payload.usage?.completion_tokens ?? 0;
  const total_tokens = payload.usage?.total_tokens ?? prompt_tokens + completion_tokens;
  return {
    text,
    model: deployment,
    provider: "azure-openai",
    duration_ms: Date.now() - started,
    usage: {
      prompt_tokens,
      completion_tokens,
      total_tokens,
      cost_usd: computeCost(deployment, prompt_tokens, completion_tokens)
    }
  };
}
async function callAnthropic(apiKey, model, input) {
  const started = Date.now();
  const response = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": apiKey,
      "anthropic-version": "2023-06-01"
    },
    body: JSON.stringify({
      model,
      system: input.system,
      messages: [{ role: "user", content: input.user }],
      max_tokens: input.max_output_tokens ?? 2048,
      temperature: input.temperature ?? 0
    })
  });
  if (!response.ok) {
    const body = await safeText(response);
    throw new LlmRequestError(`Anthropic ${response.status}: ${body.slice(0, 400)}`);
  }
  const payload = await response.json();
  const text = (payload.content ?? []).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("").trim();
  const prompt_tokens = payload.usage?.input_tokens ?? 0;
  const completion_tokens = payload.usage?.output_tokens ?? 0;
  return {
    text,
    model,
    provider: "anthropic",
    duration_ms: Date.now() - started,
    usage: {
      prompt_tokens,
      completion_tokens,
      total_tokens: prompt_tokens + completion_tokens,
      cost_usd: computeCost(model, prompt_tokens, completion_tokens)
    }
  };
}
function computeCost(model, inputTokens, outputTokens) {
  const rates = COST_PER_M_TOKENS[model];
  if (!rates) return null;
  return inputTokens / 1e6 * rates.input + outputTokens / 1e6 * rates.output;
}
async function recordUsage(db, row) {
  await db.insert(llmUsage).values({
    id: randomUUID(),
    provider: row.provider,
    model: row.model,
    task_kind: row.task_kind,
    task_id: row.task_id,
    repo: row.repo,
    prompt_tokens: row.usage.prompt_tokens,
    completion_tokens: row.usage.completion_tokens,
    total_tokens: row.usage.total_tokens,
    cost_usd: row.usage.cost_usd ?? null,
    duration_ms: row.duration_ms,
    ok: row.ok,
    error: row.error ?? null,
    created_at: (/* @__PURE__ */ new Date()).toISOString()
  });
}
function missingCredentialMessage(provider) {
  switch (provider) {
    case "openai":
      return `No API key for provider "openai". Set it via \`recall maintenance credentials set openai <key>\` or the OPENAI_API_KEY env var.`;
    case "anthropic":
      return `No API key for provider "anthropic". Set it via \`recall maintenance credentials set anthropic <key>\` or the ANTHROPIC_API_KEY env var.`;
    case "azure-openai":
      return `Azure OpenAI is not fully configured. Run \`recall maintenance credentials set azure --endpoint <url> --deployment <name> --api-version <version> <key>\` or set AZURE_OPENAI_{ENDPOINT,DEPLOYMENT,API_VERSION,API_KEY}.`;
  }
}
async function safeText(response) {
  try {
    return await response.text();
  } catch {
    return "";
  }
}

// src/embeddings/hyde.ts
init_keychain();
var HYDE_SYSTEM_PROMPT = "Generate a single short sentence that plausibly answers the user's question. Do not include preamble, qualifications, or follow-up questions. If the question is unanswerable without specific personal context, produce a sentence in the format of a likely answer using neutral placeholders. Output only the sentence.";
var MEMORY_CACHE_MAX = 512;
var memoryCache = /* @__PURE__ */ new Map();
var diskCacheLoaded = false;
var diskCache = {};
function resolveProvider2() {
  const order = ["anthropic", "azure-openai", "openai"];
  for (const p of order) if (hasProviderConfigured(p)) return p;
  return null;
}
function defaultModel(provider) {
  if (provider === "anthropic") return "claude-haiku-4-5-20251001";
  if (provider === "openai") return "gpt-4o-mini";
  return "";
}
function hashKey(provider, model, query) {
  return createHash("sha256").update(`${provider}::${model}::${query}`).digest("hex");
}
function loadDiskCache() {
  if (diskCacheLoaded) return;
  diskCacheLoaded = true;
  const path = process.env.RECALL_HYDE_CACHE_PATH;
  if (!path || !existsSync3(path)) return;
  try {
    diskCache = JSON.parse(readFileSync2(path, "utf-8"));
  } catch {
    diskCache = {};
  }
}
function persistDiskCache() {
  const path = process.env.RECALL_HYDE_CACHE_PATH;
  if (!path) return;
  try {
    mkdirSync2(dirname(path), { recursive: true });
    writeFileSync(path, JSON.stringify(diskCache, null, 2));
  } catch {
  }
}
function isHydeCandidate(query) {
  const trimmed = query.trim();
  if (!trimmed.endsWith("?")) return false;
  if (trimmed.split(/\s+/).length < 3) return false;
  if (/[\\/]|::|\.[a-z]{1,4}\b/.test(trimmed)) return false;
  return true;
}
async function generateHydeText(db, query) {
  if (process.env.RECALL_HYDE !== "true") return null;
  if (!isHydeCandidate(query)) return null;
  const provider = resolveProvider2();
  if (!provider) return null;
  const model = process.env.RECALL_HYDE_MODEL ?? defaultModel(provider);
  const key = hashKey(provider, model, query);
  if (memoryCache.has(key)) return memoryCache.get(key) ?? null;
  loadDiskCache();
  if (diskCache[key]) {
    if (memoryCache.size > MEMORY_CACHE_MAX) memoryCache.clear();
    memoryCache.set(key, diskCache[key]);
    return diskCache[key];
  }
  try {
    const result = await callLlm(db, {
      provider,
      model: model || void 0,
      system: HYDE_SYSTEM_PROMPT,
      user: query,
      max_output_tokens: 96,
      temperature: 0,
      task_kind: "hyde"
    });
    const text = result.text.trim();
    if (text.length === 0) return null;
    if (memoryCache.size > MEMORY_CACHE_MAX) memoryCache.clear();
    memoryCache.set(key, text);
    diskCache[key] = text;
    persistDiskCache();
    return text;
  } catch {
    return null;
  }
}

// src/embeddings/reranker.ts
import { mkdirSync as mkdirSync3 } from "fs";
import { join as join2 } from "path";
import {
  pipeline as pipeline5
} from "@huggingface/transformers";
var DEFAULT_MODEL = "Xenova/ms-marco-MiniLM-L-6-v2";
var DEFAULT_TOP_K = 50;
var rerankerPromise = null;
function getModel5() {
  return process.env.RECALL_RERANK_MODEL?.trim() || DEFAULT_MODEL;
}
async function getReranker() {
  const model = getModel5();
  const cacheDir = join2(getEmbeddingCacheRoot(), "rerank", ...model.split("/"));
  mkdirSync3(cacheDir, { recursive: true });
  rerankerPromise ??= pipeline5("text-classification", model, {
    cache_dir: cacheDir,
    dtype: "q8"
  });
  return rerankerPromise;
}
function isRerankerEnabled() {
  return process.env.RECALL_RERANK === "true";
}
function rerankerTopK() {
  const env = process.env.RECALL_RERANK_TOP_K;
  if (!env) return DEFAULT_TOP_K;
  const parsed = Number.parseInt(env, 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TOP_K;
}
async function rerankPairs(query, documents) {
  if (documents.length === 0) return [];
  const reranker = await getReranker();
  const pairs = documents.map((doc) => ({ text: query, text_pair: doc }));
  const results = await reranker(pairs);
  return documents.map((_, i) => {
    const slot = results[i];
    if (Array.isArray(slot)) {
      return slot[0]?.score ?? 0;
    }
    return slot?.score ?? 0;
  });
}

// src/embeddings/embeddings.ts
var EMBEDDING_BATCH_SIZE = 100;
var MIN_HYBRID_VECTOR_SIMILARITY = (() => {
  const raw = process.env.RECALL_HYBRID_MIN_SIM;
  if (!raw) return 0.7;
  const parsed = parseFloat(raw);
  if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) return 0.7;
  return parsed;
})();
var pendingEmbeddingJobs = /* @__PURE__ */ new Set();
var EMBEDDING_DEFAULTS = {
  nomic: {
    model: "nomic-ai/nomic-embed-text-v1.5",
    dimensions: 512
  },
  "multilingual-e5": {
    model: "Xenova/multilingual-e5-small",
    dimensions: 384
  },
  "bge-small-en-v1.5": {
    model: "Xenova/bge-small-en-v1.5",
    dimensions: 384
  },
  "all-MiniLM-L6-v2": {
    model: "Xenova/all-MiniLM-L6-v2",
    dimensions: 384
  }
};
function loadEmbeddingConfigFromEnv() {
  if (process.env.RECALL_EMBEDDINGS_DISABLED === "true") return null;
  const requested = process.env.RECALL_EMBEDDING_PROVIDER;
  const provider = requested === "multilingual-e5" ? "multilingual-e5" : requested === "bge-small-en-v1.5" ? "bge-small-en-v1.5" : requested === "all-MiniLM-L6-v2" ? "all-MiniLM-L6-v2" : "nomic";
  const defaults = EMBEDDING_DEFAULTS[provider];
  return {
    provider,
    model: process.env.RECALL_EMBEDDING_MODEL ?? defaults.model,
    dimensions: parseInt(process.env.RECALL_EMBEDDING_DIMS ?? `${defaults.dimensions}`, 10),
    version: process.env.RECALL_EMBEDDING_VERSION ?? "v1",
    similarity_threshold: parseFloat(process.env.RECALL_SIMILARITY_THRESHOLD ?? "0.8")
  };
}
function getEmbeddingModelInfo(config = loadEmbeddingConfigFromEnv()) {
  if (!config) return null;
  const provider = resolveProvider(config);
  const metadata = provider.metadata();
  const cachePath = getEmbeddingCachePath({
    provider: config.provider,
    model: metadata.model
  });
  const sizeBytes = getDirectorySize(cachePath);
  return {
    provider: config.provider,
    model: metadata.model,
    dimensions: metadata.dimensions,
    canonical_dimensions: metadata.canonical_dimensions,
    index_dimensions: metadata.index_dimensions,
    version: metadata.version,
    task_prefix: metadata.task_prefix,
    estimated_size_mb: metadata.estimated_size_mb,
    cache_path: cachePath,
    cached: sizeBytes > 0,
    size_bytes: sizeBytes,
    size_label: formatBytes(sizeBytes)
  };
}
async function ensureEmbeddingProviderReady(config = loadEmbeddingConfigFromEnv()) {
  if (!config) return null;
  const provider = resolveProvider(config);
  if (provider.prepare) {
    await provider.prepare();
  } else {
    await provider.embed("recall provider warmup", "document");
  }
  return getEmbeddingModelInfo(config);
}
function getEmbeddingVersion(config) {
  return config.version || `${config.provider}:${config.model}:${config.dimensions}`;
}
function hashMemoryText(text) {
  return createHash2("sha256").update(text).digest("hex");
}
function shouldEmbedMemory(row) {
  if (row.status === "transient") return false;
  if (row.status === "rejected") {
    return row.source === "user_correction" || row.source === "user_reported_review";
  }
  return row.confidence >= CONFIDENCE.TRANSIENT_MAX;
}
function serializeEmbedding(embedding) {
  return Buffer.from(
    embedding.buffer,
    embedding.byteOffset,
    embedding.byteLength
  );
}
function deserializeEmbedding(buffer) {
  return new Float32Array(
    buffer.buffer,
    buffer.byteOffset,
    buffer.byteLength / Float32Array.BYTES_PER_ELEMENT
  );
}
function rowNeedsEmbeddingRefresh(row, existing, config) {
  const metadata = resolveProvider(config).metadata();
  if (!existing) return true;
  return existing.model !== config.model || existing.embedding_dimensions !== metadata.canonical_dimensions || existing.index_dimensions !== metadata.index_dimensions || existing.version !== getEmbeddingVersion(config) || existing.content_hash !== hashMemoryText(row.text);
}
function projectEmbeddingToIndex(embedding, indexDimensions) {
  if (embedding.length === indexDimensions) {
    return embedding;
  }
  if (embedding.length < indexDimensions) {
    throw new Error(`Embedding width ${embedding.length} is smaller than index width ${indexDimensions}.`);
  }
  const sliced = embedding.slice(0, indexDimensions);
  let norm = 0;
  for (const value of sliced) norm += value * value;
  const scale = Math.sqrt(norm) || 1;
  for (let i = 0; i < sliced.length; i++) {
    sliced[i] /= scale;
  }
  return sliced;
}
async function generateEmbedding(text, config, purpose = "document") {
  return resolveProvider(config).embed(text, purpose);
}
async function generateEmbeddings(texts, config, purpose = "document") {
  if (texts.length === 0) return [];
  return resolveProvider(config).embedBatch(texts, purpose);
}
function storeEmbedding(db, memoryId, text, embedding, config) {
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const metadata = resolveProvider(config).metadata();
  const payload = {
    memory_id: memoryId,
    model: config.model,
    embedding_dimensions: metadata.canonical_dimensions,
    index_dimensions: metadata.index_dimensions,
    version: getEmbeddingVersion(config),
    content_hash: hashMemoryText(text),
    updated_at: now,
    embedding: serializeEmbedding(embedding)
  };
  db.insert(memoryEmbeddings).values(payload).onConflictDoUpdate({
    target: memoryEmbeddings.memory_id,
    set: {
      model: payload.model,
      embedding_dimensions: payload.embedding_dimensions,
      index_dimensions: payload.index_dimensions,
      version: payload.version,
      content_hash: payload.content_hash,
      updated_at: payload.updated_at,
      embedding: payload.embedding
    }
  }).run();
}
function removeStoredEmbedding(db, memoryId) {
  const result = db.delete(memoryEmbeddings).where(eq(memoryEmbeddings.memory_id, memoryId)).run();
  return result.changes > 0;
}
function loadEmbedding(db, memoryId) {
  const row = db.select({ embedding: memoryEmbeddings.embedding }).from(memoryEmbeddings).where(eq(memoryEmbeddings.memory_id, memoryId)).get();
  if (!row?.embedding) return null;
  return deserializeEmbedding(row.embedding);
}
async function syncMemoryEmbedding(db, memoryId, config) {
  const memory = db.select().from(memories).where(eq(memories.id, memoryId)).get();
  if (!memory || !shouldEmbedMemory(memory)) {
    removeStoredEmbedding(db, memoryId);
    removeMemoryVecRow(db, memoryId, config);
    return "removed";
  }
  const existing = db.select().from(memoryEmbeddings).where(eq(memoryEmbeddings.memory_id, memoryId)).get();
  if (!rowNeedsEmbeddingRefresh(memory, existing, config)) {
    if (existing) {
      upsertMemoryVecRow(db, memory, existing);
    }
    return "skipped";
  }
  const embedding = await generateEmbedding(memory.text, config, "document");
  const stillExists = db.select({ id: memories.id }).from(memories).where(eq(memories.id, memoryId)).get();
  if (!stillExists) {
    removeStoredEmbedding(db, memoryId);
    removeMemoryVecRow(db, memoryId, config);
    return "removed";
  }
  storeEmbedding(db, memory.id, memory.text, embedding, config);
  const refreshed = db.select().from(memoryEmbeddings).where(eq(memoryEmbeddings.memory_id, memory.id)).get();
  if (!refreshed) {
    throw new Error(`Failed to reload embedding row for ${memory.id}`);
  }
  upsertMemoryVecRow(db, memory, refreshed);
  return existing ? "updated" : "stored";
}
function queueMemoryEmbeddingSync(db, memoryId, config = loadEmbeddingConfigFromEnv()) {
  syncMemoryFtsIndex(db, memoryId);
  if (!config) return null;
  const job = syncMemoryEmbedding(db, memoryId, config).then(() => void 0).catch((error) => {
    const message = error instanceof Error ? error.message : String(error);
    console.error(`[recall] embedding sync failed for ${memoryId.slice(0, 8)}: ${message}`);
  }).finally(() => {
    pendingEmbeddingJobs.delete(job);
  });
  pendingEmbeddingJobs.add(job);
  return job;
}
async function flushEmbeddingJobs() {
  if (pendingEmbeddingJobs.size === 0) return;
  await Promise.allSettled([...pendingEmbeddingJobs]);
}
async function bootstrapEmbeddings(db, config, options = {}) {
  const rows = db.select().from(memories).all();
  const existingRows = db.select().from(memoryEmbeddings).all();
  const existingById = new Map(existingRows.map((row) => [row.memory_id, row]));
  const eligible = rows.filter((row) => {
    if (options.repo && row.repo !== options.repo) return false;
    return shouldEmbedMemory(row);
  });
  const pending = eligible.filter((row) => rowNeedsEmbeddingRefresh(row, existingById.get(row.id), config));
  for (const row of rows) {
    if (options.repo && row.repo !== options.repo) continue;
    if (!shouldEmbedMemory(row)) {
      removeStoredEmbedding(db, row.id);
      removeMemoryVecRow(db, row.id, config);
    }
  }
  let total = 0;
  for (let i = 0; i < pending.length; i += EMBEDDING_BATCH_SIZE) {
    const batch = pending.slice(i, i + EMBEDDING_BATCH_SIZE);
    const embeddings = await generateEmbeddings(
      batch.map((row) => row.text),
      config,
      "document"
    );
    for (let j = 0; j < batch.length; j++) {
      storeEmbedding(db, batch[j].id, batch[j].text, embeddings[j], config);
      total++;
    }
  }
  rebuildMemoryFtsIndex(db, options);
  rebuildMemoryVecIndex(db, config, options);
  return total;
}
function verifyEmbeddings(db, config, options = {}) {
  const rows = db.select().from(memories).all();
  const embeddingRows = db.select().from(memoryEmbeddings).all();
  const embeddingById = new Map(embeddingRows.map((row) => [row.memory_id, row]));
  let eligible = 0;
  let stale = 0;
  for (const row of rows) {
    if (options.repo && row.repo !== options.repo) continue;
    if (!shouldEmbedMemory(row)) continue;
    eligible++;
    if (rowNeedsEmbeddingRefresh(row, embeddingById.get(row.id), config)) {
      stale++;
    }
  }
  const vec = verifyMemoryVecIndex(db, options);
  const fts = verifyMemoryFtsIndex(db, options);
  return {
    eligible,
    stored: embeddingRows.filter((row) => {
      if (!options.repo) return true;
      const memory = rows.find((item) => item.id === row.memory_id);
      return memory?.repo === options.repo;
    }).length,
    stale,
    indexed: vec.indexed,
    index_drift: vec.drift,
    lexical_indexed: fts.indexed,
    lexical_drift: fts.drift
  };
}
function rebuildEmbeddingIndex(db, config, options = {}) {
  const lexicalRows = rebuildMemoryFtsIndex(db, options);
  const vectorRows = config ? rebuildMemoryVecIndex(db, config, options) : 0;
  return {
    vector_rows: vectorRows,
    lexical_rows: lexicalRows
  };
}
function lexicalRankToScore(rank, position) {
  const safeRank = Number.isFinite(rank) ? Math.abs(rank) : position + 1;
  return 1 / (1 + safeRank + position);
}
var DEFAULT_LEX_WEIGHT = 0.35;
var DEFAULT_VEC_WEIGHT = 0.65;
var DEFAULT_RRF_K = 60;
var DEFAULT_RRF_LEX_WEIGHT = 1.25;
var DEFAULT_RRF_VEC_WEIGHT = 0.75;
function readFloat(name, fallback) {
  const raw = process.env[name];
  if (!raw) return fallback;
  const value = Number.parseFloat(raw);
  return Number.isFinite(value) ? value : fallback;
}
async function hybridSearch(db, query, config, options = {}) {
  const limit = options.limit ?? 10;
  const minSimilarity = config ? Math.max(config.similarity_threshold, MIN_HYBRID_VECTOR_SIMILARITY) : null;
  const armLimit = Math.max(limit * 10, 50);
  const lexicalMatches = searchMemoryFtsIndex(db, query, {
    repo: options.repo,
    limit: armLimit
  });
  const hydeText = config ? await generateHydeText(db, query) : null;
  const vecEmbedQuery = hydeText ?? query;
  const semanticMatches = config ? searchMemoryVecIndex(
    db,
    projectEmbeddingToIndex(
      await generateEmbedding(vecEmbedQuery, config, "query"),
      resolveProvider(config).metadata().index_dimensions
    ),
    { repo: options.repo, limit: armLimit }
  ) : [];
  const rowsById = new Map(
    db.select().from(memories).all().map((row) => [row.id, row])
  );
  const fusionMode = process.env.RECALL_FUSION === "weighted" ? "weighted" : "rrf";
  const rrfK = readFloat("RECALL_RRF_K", DEFAULT_RRF_K);
  const rrfLexW = readFloat("RECALL_RRF_LEX_WEIGHT", DEFAULT_RRF_LEX_WEIGHT);
  const rrfVecW = readFloat("RECALL_RRF_VEC_WEIGHT", DEFAULT_RRF_VEC_WEIGHT);
  const lexW = readFloat("RECALL_LEX_WEIGHT", DEFAULT_LEX_WEIGHT);
  const vecW = readFloat("RECALL_VEC_WEIGHT", DEFAULT_VEC_WEIGHT);
  const merged = /* @__PURE__ */ new Map();
  for (let i = 0; i < lexicalMatches.length; i++) {
    const match = lexicalMatches[i];
    const row = rowsById.get(match.memory_id);
    if (!row || !shouldEmbedMemory(row)) continue;
    const lexicalScore = lexicalRankToScore(match.lexical_rank, i);
    const initialScore = fusionMode === "rrf" ? rrfLexW / (rrfK + i + 1) : lexicalScore * lexW;
    merged.set(match.memory_id, {
      memory: rowToMemory(row),
      similarity: 0,
      lexical_score: lexicalScore,
      score: initialScore
    });
  }
  for (let i = 0; i < semanticMatches.length; i++) {
    const match = semanticMatches[i];
    const row = rowsById.get(match.memory_id);
    if (!row || !shouldEmbedMemory(row)) continue;
    const similarity = Math.max(0, 1 - match.distance);
    if (minSimilarity !== null && similarity < minSimilarity) continue;
    const existing = merged.get(match.memory_id);
    const vecContribution = fusionMode === "rrf" ? rrfVecW / (rrfK + i + 1) : similarity * vecW;
    if (existing) {
      existing.similarity = similarity;
      existing.score += vecContribution;
    } else {
      merged.set(match.memory_id, {
        memory: rowToMemory(row),
        similarity,
        lexical_score: 0,
        score: vecContribution
      });
    }
  }
  const sorted = [...merged.values()].sort((a, b) => b.score - a.score);
  if (isRerankerEnabled() && sorted.length > 1) {
    const topK = Math.min(rerankerTopK(), sorted.length);
    const candidates = sorted.slice(0, topK);
    try {
      const scores = await rerankPairs(
        query,
        candidates.map((c) => c.memory.text)
      );
      const rescored = candidates.map((c, i) => ({ ...c, score: scores[i] ?? 0 })).sort((a, b) => b.score - a.score);
      return rescored.slice(0, limit);
    } catch {
    }
  }
  return sorted.slice(0, limit);
}
function cosineSimilarity(a, b) {
  if (a.length !== b.length) return 0;
  let dot = 0;
  let normA = 0;
  let normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
  return denom === 0 ? 0 : dot / denom;
}
async function semanticSearch(db, query, config, options = {}) {
  const queryEmbedding = projectEmbeddingToIndex(
    await generateEmbedding(query, config, "query"),
    resolveProvider(config).metadata().index_dimensions
  );
  const matches = searchMemoryVecIndex(db, queryEmbedding, options);
  if (matches.length === 0) return [];
  const rowsById = new Map(
    db.select().from(memories).all().map((row) => [row.id, row])
  );
  return matches.map((match) => {
    const row = rowsById.get(match.memory_id);
    if (!row || !shouldEmbedMemory(row)) return null;
    const similarity = 1 - match.distance;
    if (similarity < config.similarity_threshold) return null;
    return {
      memory: rowToMemory(row),
      similarity
    };
  }).filter((item) => item !== null).sort((a, b) => b.similarity - a.similarity);
}
async function findSemanticDuplicates(db, text, config, threshold, options = {}) {
  const queryEmbedding = await generateEmbedding(text, config, "query");
  const dupThreshold = threshold ?? config.similarity_threshold;
  const rows = db.select().from(memories).all();
  const embeddingsById = new Map(
    db.select().from(memoryEmbeddings).all().map((row) => [row.memory_id, row])
  );
  const duplicates = [];
  for (const row of rows) {
    if (options.repo && row.repo !== options.repo) continue;
    if (options.type && row.type !== options.type) continue;
    if (row.status === "rejected") continue;
    if (!shouldEmbedMemory(row)) continue;
    const embeddingRow = embeddingsById.get(row.id);
    if (!embeddingRow?.embedding) continue;
    const similarity = cosineSimilarity(
      queryEmbedding,
      deserializeEmbedding(embeddingRow.embedding)
    );
    if (similarity >= dupThreshold) {
      duplicates.push({ id: row.id, text: row.text, similarity });
    }
  }
  return duplicates.sort((a, b) => b.similarity - a.similarity).slice(0, options.limit ?? 10);
}
async function findSimilarRejectedExemplar(db, text, config, threshold) {
  const queryEmbedding = await generateEmbedding(text, config, "query");
  const rejectedRows = db.select().from(memories).where(eq(memories.status, "rejected")).all().filter((row) => row.source === "user_correction" || row.source === "user_reported_review");
  if (rejectedRows.length === 0) return null;
  const embeddingsById = new Map(
    db.select().from(memoryEmbeddings).all().map((row) => [row.memory_id, row])
  );
  let best = null;
  for (const row of rejectedRows) {
    const embeddingRow = embeddingsById.get(row.id);
    if (!embeddingRow?.embedding) continue;
    const similarity = cosineSimilarity(
      queryEmbedding,
      deserializeEmbedding(embeddingRow.embedding)
    );
    if (similarity >= threshold && (!best || similarity > best.similarity)) {
      best = { id: row.id, text: row.text, similarity };
    }
  }
  return best;
}
function rowToMemory(row) {
  const evidence = typeof row.evidence === "string" ? JSON.parse(row.evidence) : Array.isArray(row.evidence) ? row.evidence : [];
  const captureContext = typeof row.capture_context === "string" ? JSON.parse(row.capture_context) : row.capture_context ?? null;
  return {
    id: row.id,
    type: row.type,
    text: row.text,
    scope: row.scope,
    path_scope: row.path_scope,
    repo: row.repo,
    status: row.status,
    confidence: row.confidence,
    source: row.source,
    evidence,
    capture_context: captureContext,
    supersedes: row.supersedes,
    created_at: row.created_at,
    updated_at: row.updated_at,
    last_validated_at: row.last_validated_at,
    last_injected_at: row.last_injected_at,
    injection_count: row.injection_count,
    override_count: row.override_count,
    repetition_count: row.repetition_count,
    auto_inject: row.auto_inject
  };
}

export {
  getEmbeddingCacheRoot,
  tagActivitySource,
  CONFIDENCE,
  PROMOTION,
  RetrievalEvalCase,
  RetrievalEvalFile,
  resolveProvider,
  getSynonyms,
  searchMemoryFtsIndex,
  LlmCredentialError,
  callLlm,
  loadEmbeddingConfigFromEnv,
  getEmbeddingModelInfo,
  ensureEmbeddingProviderReady,
  projectEmbeddingToIndex,
  generateEmbedding,
  generateEmbeddings,
  storeEmbedding,
  removeStoredEmbedding,
  loadEmbedding,
  syncMemoryEmbedding,
  queueMemoryEmbeddingSync,
  flushEmbeddingJobs,
  bootstrapEmbeddings,
  verifyEmbeddings,
  rebuildEmbeddingIndex,
  hybridSearch,
  cosineSimilarity,
  semanticSearch,
  findSemanticDuplicates,
  findSimilarRejectedExemplar
};
//# sourceMappingURL=chunk-JRIM7VBY.js.map