memorix 1.7.2 → 1.8.0

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.
Files changed (90) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/assets/star-history-dark.svg +4 -4
  5. package/assets/star-history-light.svg +4 -4
  6. package/assets/star-history.json +47 -3
  7. package/dist/cli/index.js +1801 -254
  8. package/dist/dashboard/static/app.js +88 -4
  9. package/dist/dashboard/static/index.html +10 -0
  10. package/dist/dashboard/static/style.css +86 -0
  11. package/dist/index.js +1241 -224
  12. package/dist/maintenance-runner.js +972 -126
  13. package/dist/media-audio-runner.js +967 -121
  14. package/dist/media-video-runner.js +972 -126
  15. package/dist/memcode-runtime/CHANGELOG.md +59 -1
  16. package/dist/sdk.js +1240 -223
  17. package/dist/types.d.ts +8 -0
  18. package/dist/vector-backfill-runner.js +972 -126
  19. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  20. package/docs/1.8.0-RELEASE-CANDIDATE-SPEC.md +75 -0
  21. package/docs/API_REFERENCE.md +37 -1
  22. package/docs/ARCHITECTURE.md +1 -1
  23. package/docs/CONFIGURATION.md +57 -1
  24. package/docs/DASHBOARD.md +10 -0
  25. package/docs/DEVELOPMENT.md +3 -2
  26. package/docs/INTEGRATIONS.md +1 -1
  27. package/docs/PERFORMANCE.md +3 -1
  28. package/docs/README.md +11 -1
  29. package/docs/SETUP.md +2 -1
  30. package/package.json +1 -1
  31. package/plugins/antigravity/memorix/rules/memorix.md +1 -1
  32. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  33. package/plugins/claude/memorix/SKILL.md +1 -1
  34. package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +2 -2
  35. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  36. package/plugins/codebuddy/memorix/skills/memorix-memory/SKILL.md +2 -2
  37. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  38. package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +2 -2
  39. package/plugins/copilot/memorix/plugin.json +1 -1
  40. package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +2 -2
  41. package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +2 -2
  42. package/plugins/gemini/memorix/GEMINI.md +1 -1
  43. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  44. package/plugins/omp/memorix/package.json +1 -1
  45. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  46. package/plugins/pi/memorix/package.json +1 -1
  47. package/plugins/pi/memorix/skills/memorix-memory/SKILL.md +2 -2
  48. package/server.json +2 -2
  49. package/src/cli/capability-map.ts +2 -0
  50. package/src/cli/commands/config-migrate.ts +7 -0
  51. package/src/cli/commands/context.ts +11 -0
  52. package/src/cli/commands/doctor.ts +5 -1
  53. package/src/cli/commands/evidence.ts +56 -0
  54. package/src/cli/commands/feedback.ts +58 -0
  55. package/src/cli/commands/init.ts +17 -0
  56. package/src/cli/commands/resume.ts +2 -0
  57. package/src/cli/commands/serve-http.ts +201 -12
  58. package/src/cli/commands/status.ts +2 -0
  59. package/src/cli/fallback-budget.ts +59 -0
  60. package/src/cli/index.ts +5 -1
  61. package/src/compact/engine.ts +26 -1
  62. package/src/config/dotenv-loader.ts +17 -0
  63. package/src/config/resolved-config.ts +110 -12
  64. package/src/config/toml-loader.ts +8 -0
  65. package/src/config/yaml-loader.ts +9 -0
  66. package/src/dashboard/config-provenance.ts +17 -0
  67. package/src/dashboard/evidence.ts +63 -0
  68. package/src/dashboard/server.ts +93 -68
  69. package/src/dashboard/static/index.html +10 -0
  70. package/src/dashboard/static/style.css +86 -0
  71. package/src/hooks/installers/index.ts +1 -1
  72. package/src/hooks/official-skills.ts +3 -3
  73. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  74. package/src/llm/provider.ts +33 -7
  75. package/src/memory/feedback-model.ts +4 -0
  76. package/src/memory/feedback.ts +220 -0
  77. package/src/memory/observations.ts +19 -0
  78. package/src/memory/retention.ts +38 -5
  79. package/src/multimodal/image-loader.ts +6 -3
  80. package/src/rerank/http-provider.ts +125 -0
  81. package/src/rerank/index.ts +81 -0
  82. package/src/rerank/policy.ts +88 -0
  83. package/src/server/mcp-binding-store.ts +79 -0
  84. package/src/server/mcp-diagnostics.ts +40 -0
  85. package/src/server/tool-profile.ts +4 -0
  86. package/src/server.ts +121 -1
  87. package/src/store/evidence-store.ts +261 -0
  88. package/src/store/orama-store.ts +79 -53
  89. package/src/store/sqlite-db.ts +104 -0
  90. package/src/types.ts +8 -0
@@ -95,6 +95,8 @@ function loadEnvFile(filePath) {
95
95
  const parsed = parse(readFileSync(filePath, "utf-8"));
96
96
  for (const [key, value] of Object.entries(parsed)) {
97
97
  if (!(key in process.env)) {
98
+ loadedEnvKeys.add(key);
99
+ loadedEnvValues.set(key, value);
98
100
  process.env[key] = value;
99
101
  injectedKeys.add(key);
100
102
  }
@@ -107,6 +109,8 @@ function loadDotenv(projectRoot, options = {}) {
107
109
  resetDotenv();
108
110
  }
109
111
  loadedEnvFiles.length = 0;
112
+ loadedEnvKeys.clear();
113
+ loadedEnvValues.clear();
110
114
  if (projectRoot) {
111
115
  loadEnvFile(getProjectDotenvPath(projectRoot));
112
116
  }
@@ -122,11 +126,12 @@ function resetDotenv() {
122
126
  dotenvLoaded = false;
123
127
  dotenvProjectRoot = null;
124
128
  loadedEnvFiles.length = 0;
129
+ loadedEnvKeys.clear();
125
130
  }
126
131
  function getLoadedEnvFiles() {
127
132
  return loadedEnvFiles;
128
133
  }
129
- var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, injectedKeys;
134
+ var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, loadedEnvKeys, loadedEnvValues, injectedKeys;
130
135
  var init_dotenv_loader = __esm({
131
136
  "src/config/dotenv-loader.ts"() {
132
137
  "use strict";
@@ -135,6 +140,8 @@ var init_dotenv_loader = __esm({
135
140
  dotenvLoaded = false;
136
141
  dotenvProjectRoot = null;
137
142
  loadedEnvFiles = [];
143
+ loadedEnvKeys = /* @__PURE__ */ new Set();
144
+ loadedEnvValues = /* @__PURE__ */ new Map();
138
145
  injectedKeys = /* @__PURE__ */ new Set();
139
146
  }
140
147
  });
@@ -176,6 +183,7 @@ function loadYamlConfig(projectRoot) {
176
183
  llm: { ...userConfig.llm, ...projectConfig.llm },
177
184
  agent: { ...userConfig.agent, ...projectConfig.agent },
178
185
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
186
+ rerank: { ...userConfig.rerank, ...projectConfig.rerank },
179
187
  git: { ...userConfig.git, ...projectConfig.git },
180
188
  codegraph: { ...userConfig.codegraph, ...projectConfig.codegraph },
181
189
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
@@ -517,7 +525,7 @@ function closeAllDatabases() {
517
525
  _dbCache.delete(key);
518
526
  }
519
527
  }
520
- var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
528
+ var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_EVIDENCE_CARDS_TABLE, CREATE_EVIDENCE_CARD_EVENTS_TABLE, CREATE_MEMORY_FEEDBACK_STATES_TABLE, CREATE_MEMORY_FEEDBACK_EVENTS_TABLE, CREATE_MCP_BINDINGS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
521
529
  var init_sqlite_db = __esm({
522
530
  "src/store/sqlite-db.ts"() {
523
531
  "use strict";
@@ -1163,6 +1171,79 @@ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
1163
1171
  created_at TEXT NOT NULL,
1164
1172
  updated_at TEXT NOT NULL
1165
1173
  );
1174
+ `;
1175
+ CREATE_EVIDENCE_CARDS_TABLE = `
1176
+ CREATE TABLE IF NOT EXISTS evidence_cards (
1177
+ id TEXT PRIMARY KEY,
1178
+ project_id TEXT NOT NULL,
1179
+ candidate_kind TEXT NOT NULL,
1180
+ candidate_id TEXT NOT NULL,
1181
+ title TEXT NOT NULL,
1182
+ summary TEXT NOT NULL DEFAULT '',
1183
+ entity_name TEXT,
1184
+ source_kind TEXT NOT NULL,
1185
+ source_ref TEXT NOT NULL,
1186
+ locator TEXT,
1187
+ captured_hash TEXT,
1188
+ session_id TEXT,
1189
+ status TEXT NOT NULL DEFAULT 'active',
1190
+ verification TEXT NOT NULL DEFAULT 'unverified',
1191
+ freshness TEXT NOT NULL DEFAULT 'current',
1192
+ stale_reason TEXT,
1193
+ files_json TEXT NOT NULL DEFAULT '[]',
1194
+ related_entities_json TEXT NOT NULL DEFAULT '[]',
1195
+ created_at TEXT NOT NULL,
1196
+ updated_at TEXT NOT NULL,
1197
+ UNIQUE(project_id, candidate_kind, candidate_id)
1198
+ );
1199
+ `;
1200
+ CREATE_EVIDENCE_CARD_EVENTS_TABLE = `
1201
+ CREATE TABLE IF NOT EXISTS evidence_card_events (
1202
+ id TEXT PRIMARY KEY,
1203
+ project_id TEXT NOT NULL,
1204
+ card_id TEXT NOT NULL,
1205
+ kind TEXT NOT NULL,
1206
+ detail TEXT,
1207
+ created_at TEXT NOT NULL,
1208
+ FOREIGN KEY (card_id) REFERENCES evidence_cards(id) ON DELETE CASCADE
1209
+ );
1210
+ `;
1211
+ CREATE_MEMORY_FEEDBACK_STATES_TABLE = `
1212
+ CREATE TABLE IF NOT EXISTS memory_feedback_states (
1213
+ project_id TEXT NOT NULL,
1214
+ candidate_kind TEXT NOT NULL,
1215
+ candidate_id TEXT NOT NULL,
1216
+ weight REAL NOT NULL DEFAULT 1.0,
1217
+ lifecycle_status TEXT NOT NULL DEFAULT 'active',
1218
+ updated_at TEXT NOT NULL,
1219
+ PRIMARY KEY (project_id, candidate_kind, candidate_id)
1220
+ );
1221
+ `;
1222
+ CREATE_MEMORY_FEEDBACK_EVENTS_TABLE = `
1223
+ CREATE TABLE IF NOT EXISTS memory_feedback_events (
1224
+ id TEXT PRIMARY KEY,
1225
+ project_id TEXT NOT NULL,
1226
+ candidate_kind TEXT NOT NULL,
1227
+ candidate_id TEXT NOT NULL,
1228
+ signal TEXT NOT NULL,
1229
+ actor TEXT,
1230
+ note TEXT,
1231
+ source_ref TEXT NOT NULL,
1232
+ target_event_id TEXT,
1233
+ delta REAL NOT NULL DEFAULT 0,
1234
+ observed_at TEXT NOT NULL
1235
+ );
1236
+ `;
1237
+ CREATE_MCP_BINDINGS_TABLE = `
1238
+ CREATE TABLE IF NOT EXISTS mcp_bindings (
1239
+ handle_id TEXT PRIMARY KEY,
1240
+ project_id TEXT NOT NULL,
1241
+ project_root TEXT NOT NULL,
1242
+ data_dir TEXT NOT NULL,
1243
+ created_at TEXT NOT NULL,
1244
+ last_used_at TEXT NOT NULL,
1245
+ expires_at TEXT
1246
+ );
1166
1247
  `;
1167
1248
  CREATE_INDEXES = `
1168
1249
  CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
@@ -1225,6 +1306,12 @@ CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_target
1225
1306
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
1226
1307
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
1227
1308
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
1309
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC);
1310
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id);
1311
+ CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at);
1312
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC);
1313
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_events_candidate ON memory_feedback_events(project_id, candidate_kind, candidate_id, observed_at DESC);
1314
+ CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC);
1228
1315
  CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1229
1316
  ON maintenance_jobs(project_id, kind, dedupe_key)
1230
1317
  WHERE status IN ('pending', 'running', 'retry');
@@ -1380,6 +1467,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1380
1467
  }
1381
1468
  db2.exec("CREATE INDEX IF NOT EXISTS idx_media_jobs_source_asset ON media_jobs(project_id, source_asset_id, status)");
1382
1469
  }
1470
+ },
1471
+ {
1472
+ id: "1.8-context-control-plane",
1473
+ apply: (db2) => {
1474
+ db2.exec(CREATE_EVIDENCE_CARDS_TABLE);
1475
+ db2.exec(CREATE_EVIDENCE_CARD_EVENTS_TABLE);
1476
+ db2.exec(CREATE_MEMORY_FEEDBACK_STATES_TABLE);
1477
+ db2.exec(CREATE_MEMORY_FEEDBACK_EVENTS_TABLE);
1478
+ db2.exec(CREATE_MCP_BINDINGS_TABLE);
1479
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC)");
1480
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id)");
1481
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at)");
1482
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC)");
1483
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_feedback_events_candidate ON memory_feedback_events(project_id, candidate_kind, candidate_id, observed_at DESC)");
1484
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC)");
1485
+ }
1383
1486
  }
1384
1487
  ];
1385
1488
  _dbCache = /* @__PURE__ */ new Map();
@@ -1836,6 +1939,7 @@ function mergeTomlConfig(base, override) {
1836
1939
  llm: { ...base.memory?.llm, ...override.memory?.llm }
1837
1940
  },
1838
1941
  embedding: { ...base.embedding, ...override.embedding },
1942
+ rerank: { ...base.rerank, ...override.rerank },
1839
1943
  hooks: { ...base.hooks, ...override.hooks },
1840
1944
  git: { ...base.git, ...override.git },
1841
1945
  codegraph: { ...base.codegraph, ...override.codegraph },
@@ -1932,6 +2036,7 @@ __export(resolved_config_exports, {
1932
2036
  getResolvedConfigForCwd: () => getResolvedConfigForCwd,
1933
2037
  getResolvedEmbeddingLane: () => getResolvedEmbeddingLane,
1934
2038
  getResolvedMemoryLane: () => getResolvedMemoryLane,
2039
+ getResolvedRerankLane: () => getResolvedRerankLane,
1935
2040
  resetResolvedConfigCache: () => resetResolvedConfigCache
1936
2041
  });
1937
2042
  import { homedir as homedir5 } from "os";
@@ -1964,6 +2069,16 @@ function getResolvedConfig(options = {}) {
1964
2069
  legacy.llm?.baseUrl
1965
2070
  );
1966
2071
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
2072
+ const memoryLlmApiKey = first(
2073
+ process.env.MEMORIX_LLM_API_KEY,
2074
+ process.env.MEMORIX_API_KEY,
2075
+ toml.memory?.llm?.api_key,
2076
+ yaml.llm?.apiKey,
2077
+ legacy.llm?.apiKey,
2078
+ process.env.OPENAI_API_KEY,
2079
+ process.env.ANTHROPIC_API_KEY,
2080
+ openRouterMemoryLlmApiKey
2081
+ );
1967
2082
  const resolved = {
1968
2083
  agent: {
1969
2084
  provider: first(
@@ -2004,16 +2119,7 @@ function getResolvedConfig(options = {}) {
2004
2119
  provider: memoryLlmProvider,
2005
2120
  model: memoryLlmModel,
2006
2121
  baseUrl: memoryLlmBaseUrl,
2007
- apiKey: first(
2008
- process.env.MEMORIX_LLM_API_KEY,
2009
- process.env.MEMORIX_API_KEY,
2010
- toml.memory?.llm?.api_key,
2011
- yaml.llm?.apiKey,
2012
- legacy.llm?.apiKey,
2013
- process.env.OPENAI_API_KEY,
2014
- process.env.ANTHROPIC_API_KEY,
2015
- openRouterMemoryLlmApiKey
2016
- )
2122
+ apiKey: memoryLlmApiKey
2017
2123
  }
2018
2124
  },
2019
2125
  embedding: {
@@ -2023,6 +2129,12 @@ function getResolvedConfig(options = {}) {
2023
2129
  apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
2024
2130
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
2025
2131
  },
2132
+ rerank: resolveRerankLane({
2133
+ toml,
2134
+ yaml,
2135
+ memoryLlmApiKey,
2136
+ memoryLlmBaseUrl
2137
+ }),
2026
2138
  git: {
2027
2139
  autoHook: firstBool(toml.git?.auto_hook, yaml.git?.autoHook),
2028
2140
  ingestOnCommit: firstBool(toml.git?.ingest_on_commit, yaml.git?.ingestOnCommit),
@@ -2092,6 +2204,9 @@ function getResolvedMemoryLane(options = {}) {
2092
2204
  function getResolvedEmbeddingLane(options = {}) {
2093
2205
  return getResolvedConfig(options).embedding;
2094
2206
  }
2207
+ function getResolvedRerankLane(options = {}) {
2208
+ return getResolvedConfig(options).rerank;
2209
+ }
2095
2210
  function resetResolvedConfigCache() {
2096
2211
  }
2097
2212
  function first(...values) {
@@ -2134,6 +2249,10 @@ function getEnvSourceNames() {
2134
2249
  "MEMORIX_EMBEDDING_BASE_URL",
2135
2250
  "MEMORIX_EMBEDDING_MODEL",
2136
2251
  "MEMORIX_EMBEDDING_DIMENSIONS",
2252
+ "MEMORIX_RERANK_PROVIDER",
2253
+ "MEMORIX_RERANK_MODEL",
2254
+ "MEMORIX_RERANK_BASE_URL",
2255
+ "MEMORIX_RERANK_API_KEY",
2137
2256
  "MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
2138
2257
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
2139
2258
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
@@ -2157,6 +2276,63 @@ function normalizeExternalContext(value) {
2157
2276
  if (normalized === "auto" || normalized === "off") return normalized;
2158
2277
  return void 0;
2159
2278
  }
2279
+ function normalizeRerankProvider(value) {
2280
+ const normalized = value?.trim().toLowerCase();
2281
+ if (normalized === "http") return "http";
2282
+ return "off";
2283
+ }
2284
+ function resolveRerankLane(args) {
2285
+ const provider2 = normalizeRerankProvider(first(
2286
+ process.env.MEMORIX_RERANK_PROVIDER,
2287
+ args.toml.rerank?.provider,
2288
+ args.yaml.rerank?.provider,
2289
+ "off"
2290
+ ));
2291
+ const model = first(
2292
+ process.env.MEMORIX_RERANK_MODEL,
2293
+ args.toml.rerank?.model,
2294
+ args.yaml.rerank?.model
2295
+ );
2296
+ const explicitBaseUrl = first(
2297
+ process.env.MEMORIX_RERANK_BASE_URL,
2298
+ args.toml.rerank?.base_url,
2299
+ args.yaml.rerank?.baseUrl
2300
+ );
2301
+ const baseUrl = provider2 === "http" ? first(explicitBaseUrl, args.memoryLlmBaseUrl) : explicitBaseUrl;
2302
+ const dedicatedApiKey = first(
2303
+ process.env.MEMORIX_RERANK_API_KEY,
2304
+ args.toml.rerank?.api_key,
2305
+ args.yaml.rerank?.apiKey
2306
+ );
2307
+ const sameEndpoint = provider2 === "http" && sameTrustedEndpoint(baseUrl, args.memoryLlmBaseUrl);
2308
+ const inheritedApiKey = sameEndpoint ? args.memoryLlmApiKey : void 0;
2309
+ const apiKey = first(dedicatedApiKey, inheritedApiKey);
2310
+ const canSendRequest = provider2 === "http" && Boolean(baseUrl) && (Boolean(apiKey) || !args.memoryLlmApiKey || sameEndpoint);
2311
+ return { provider: provider2, model, baseUrl, apiKey, canSendRequest };
2312
+ }
2313
+ function sameTrustedEndpoint(left, right) {
2314
+ const a = normalizeTrustedEndpoint(left);
2315
+ const b = normalizeTrustedEndpoint(right);
2316
+ return Boolean(a && b && a === b);
2317
+ }
2318
+ function normalizeTrustedEndpoint(value) {
2319
+ const trimmed = value?.trim();
2320
+ if (!trimmed) return void 0;
2321
+ try {
2322
+ const url = new URL(trimmed);
2323
+ const protocol = url.protocol.toLowerCase();
2324
+ const hostname = url.hostname.toLowerCase();
2325
+ const port = url.port;
2326
+ let pathname = url.pathname.replace(/\/+$/, "");
2327
+ if (pathname.toLowerCase().endsWith("/rerank")) {
2328
+ pathname = pathname.slice(0, -"/rerank".length).replace(/\/+$/, "");
2329
+ }
2330
+ return `${protocol}//${hostname}${port ? `:${port}` : ""}${pathname}`;
2331
+ } catch {
2332
+ const fallback = trimmed.replace(/\/+$/, "").replace(/\/rerank$/i, "").replace(/\/+$/, "");
2333
+ return fallback.toLowerCase() || void 0;
2334
+ }
2335
+ }
2160
2336
  var init_resolved_config = __esm({
2161
2337
  "src/config/resolved-config.ts"() {
2162
2338
  "use strict";
@@ -4392,9 +4568,27 @@ __export(provider_exports2, {
4392
4568
  getLLMConfig: () => getLLMConfig,
4393
4569
  initLLM: () => initLLM,
4394
4570
  isLLMEnabled: () => isLLMEnabled,
4571
+ normalizeOpenAICompatibleBaseUrl: () => normalizeOpenAICompatibleBaseUrl,
4395
4572
  parseLLMTimeoutMs: () => parseLLMTimeoutMs,
4396
4573
  setLLMConfig: () => setLLMConfig
4397
4574
  });
4575
+ function normalizeOpenAICompatibleBaseUrl(baseUrl) {
4576
+ const base = baseUrl.trim().replace(/\/+$/, "");
4577
+ if (!base) return base;
4578
+ let pathname = "";
4579
+ try {
4580
+ pathname = new URL(base).pathname.replace(/\/+$/, "");
4581
+ } catch {
4582
+ pathname = base.split(/[?#]/, 1)[0].replace(/\/+$/, "");
4583
+ }
4584
+ const lastSegment = pathname.split("/").filter(Boolean).at(-1);
4585
+ if (lastSegment && OPENAI_API_VERSION_SEGMENT.test(lastSegment)) {
4586
+ return base;
4587
+ }
4588
+ const queryOrHash = base.match(/[?#].*$/)?.[0] ?? "";
4589
+ const pathWithoutQuery = base.slice(0, base.length - queryOrHash.length).replace(/\/+$/, "");
4590
+ return `${pathWithoutQuery}/v1${queryOrHash}`;
4591
+ }
4398
4592
  function parseLLMTimeoutMs(raw) {
4399
4593
  if (raw === void 0 || raw.trim() === "") return LLM_TIMEOUT_DEFAULT_MS;
4400
4594
  const parsed = Number(raw);
@@ -4498,8 +4692,7 @@ async function callLLM(systemPrompt, userMessage, signal) {
4498
4692
  }
4499
4693
  async function callOpenAICompatible(systemPrompt, userMessage, signal) {
4500
4694
  const config = currentConfig;
4501
- let base = config.baseUrl.replace(/\/+$/, "");
4502
- if (!base.endsWith("/v1")) base += "/v1";
4695
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
4503
4696
  const url = `${base}/chat/completions`;
4504
4697
  const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
4505
4698
  const response = await fetch(url, {
@@ -4578,8 +4771,7 @@ async function callLLMWithTools(messages, tools, signal) {
4578
4771
  }
4579
4772
  async function callOpenAIWithTools(messages, tools, signal) {
4580
4773
  const config = currentConfig;
4581
- let base = config.baseUrl.replace(/\/+$/, "");
4582
- if (!base.endsWith("/v1")) base += "/v1";
4774
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
4583
4775
  const url = `${base}/chat/completions`;
4584
4776
  const openaiMessages = messages.map((msg) => {
4585
4777
  if (msg.role === "tool") {
@@ -4729,8 +4921,7 @@ async function callAnthropicWithTools(messages, tools, signal) {
4729
4921
  }
4730
4922
  async function* callOpenAIWithToolsStream(messages, tools) {
4731
4923
  const config = currentConfig;
4732
- let base = config.baseUrl.replace(/\/+$/, "");
4733
- if (!base.endsWith("/v1")) base += "/v1";
4924
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
4734
4925
  const url = `${base}/chat/completions`;
4735
4926
  const openaiMessages = messages.map((msg) => {
4736
4927
  if (msg.role === "tool") {
@@ -4978,7 +5169,7 @@ async function* callAnthropicWithToolsStream(messages, tools) {
4978
5169
  }
4979
5170
  };
4980
5171
  }
4981
- var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
5172
+ var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, OPENAI_API_VERSION_SEGMENT, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
4982
5173
  var init_provider2 = __esm({
4983
5174
  "src/llm/provider.ts"() {
4984
5175
  "use strict";
@@ -4988,6 +5179,7 @@ var init_provider2 = __esm({
4988
5179
  LLM_TIMEOUT_MIN_MS = 1e3;
4989
5180
  LLM_TIMEOUT_MAX_MS = 3e5;
4990
5181
  MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
5182
+ OPENAI_API_VERSION_SEGMENT = /^v\d+(?:[a-z][a-z0-9-]*)?$/i;
4991
5183
  LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
4992
5184
  PROVIDER_DEFAULTS = {
4993
5185
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
@@ -5047,6 +5239,48 @@ Rules:
5047
5239
  }
5048
5240
  });
5049
5241
 
5242
+ // src/rerank/policy.ts
5243
+ function parseRerankTimeoutMs(raw) {
5244
+ const value = raw?.trim();
5245
+ if (!value) return DEFAULT_RERANK_TIMEOUT_MS;
5246
+ const parsed = Number(value);
5247
+ if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
5248
+ console.warn(
5249
+ `[memorix] MEMORIX_RERANK_TIMEOUT_MS="${raw}" is invalid (must be a positive integer between ${RERANK_TIMEOUT_MIN_MS}-${RERANK_TIMEOUT_MAX_MS}ms). Using default ${DEFAULT_RERANK_TIMEOUT_MS}ms.`
5250
+ );
5251
+ return DEFAULT_RERANK_TIMEOUT_MS;
5252
+ }
5253
+ if (parsed < RERANK_TIMEOUT_MIN_MS) return RERANK_TIMEOUT_MIN_MS;
5254
+ if (parsed > RERANK_TIMEOUT_MAX_MS) return RERANK_TIMEOUT_MAX_MS;
5255
+ return parsed;
5256
+ }
5257
+ function resolveRerankTimeoutMs(explicitTimeoutMs) {
5258
+ if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
5259
+ return explicitTimeoutMs;
5260
+ }
5261
+ return parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
5262
+ }
5263
+ function shouldAttemptHttpRerank(input) {
5264
+ return input.quality === "thorough" && input.hasQuery && input.candidateCount >= MIN_RERANK_CANDIDATES;
5265
+ }
5266
+ function shouldAttemptLlmRerank(input) {
5267
+ if (input.httpRerankConfigured) return false;
5268
+ if (input.quality !== "thorough" || input.tier !== "heavy" || !input.hasQuery) return false;
5269
+ if (input.candidateCount < MIN_RERANK_CANDIDATES) return false;
5270
+ return input.topScore > 0 && input.secondScore / input.topScore > 0.7;
5271
+ }
5272
+ var DEFAULT_RERANK_TIMEOUT_MS, RERANK_TIMEOUT_MIN_MS, RERANK_TIMEOUT_MAX_MS, MIN_RERANK_CANDIDATES;
5273
+ var init_policy = __esm({
5274
+ "src/rerank/policy.ts"() {
5275
+ "use strict";
5276
+ init_esm_shims();
5277
+ DEFAULT_RERANK_TIMEOUT_MS = 3e4;
5278
+ RERANK_TIMEOUT_MIN_MS = 1e3;
5279
+ RERANK_TIMEOUT_MAX_MS = 3e5;
5280
+ MIN_RERANK_CANDIDATES = 3;
5281
+ }
5282
+ });
5283
+
5050
5284
  // src/timeout.ts
5051
5285
  function withTimeout(promise, ms, label) {
5052
5286
  return new Promise((resolve, reject) => {
@@ -5332,6 +5566,148 @@ var init_aliases = __esm({
5332
5566
  }
5333
5567
  });
5334
5568
 
5569
+ // src/rerank/http-provider.ts
5570
+ function buildRerankUrl(baseUrl) {
5571
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
5572
+ if (trimmed.endsWith("/rerank")) return trimmed;
5573
+ return `${trimmed}/rerank`;
5574
+ }
5575
+ function parseRerankResponse(payload) {
5576
+ const rows = extractRows(payload);
5577
+ if (rows.length === 0) {
5578
+ throw new Error("Neural rerank response contained no ranked results");
5579
+ }
5580
+ return rows.sort((a, b) => b.score - a.score);
5581
+ }
5582
+ function extractRows(payload) {
5583
+ if (Array.isArray(payload)) {
5584
+ return payload.map(rowFromUnknown).filter((row) => row !== null);
5585
+ }
5586
+ if (payload && typeof payload === "object" && "results" in payload) {
5587
+ const results = payload.results;
5588
+ if (Array.isArray(results)) {
5589
+ return results.map(rowFromUnknown).filter((row) => row !== null);
5590
+ }
5591
+ }
5592
+ throw new Error("Neural rerank response was not Cohere or TEI shaped");
5593
+ }
5594
+ function rowFromUnknown(value) {
5595
+ if (!value || typeof value !== "object") return null;
5596
+ const row = value;
5597
+ if (typeof row.index !== "number" || !Number.isFinite(row.index)) return null;
5598
+ const score = typeof row.relevance_score === "number" ? row.relevance_score : typeof row.score === "number" ? row.score : null;
5599
+ if (score === null || !Number.isFinite(score)) return null;
5600
+ return { index: row.index, score };
5601
+ }
5602
+ function buildRerankBody(query, documents, model) {
5603
+ const body = {
5604
+ query,
5605
+ documents,
5606
+ top_n: documents.length,
5607
+ return_documents: false
5608
+ };
5609
+ const trimmed = model?.trim();
5610
+ if (trimmed) body.model = trimmed;
5611
+ return body;
5612
+ }
5613
+ async function rerankViaHttp(query, documents, config) {
5614
+ if (documents.length === 0) return [];
5615
+ const url = buildRerankUrl(config.baseUrl);
5616
+ const headers = {
5617
+ "content-type": "application/json"
5618
+ };
5619
+ if (config.apiKey) {
5620
+ headers.authorization = `Bearer ${config.apiKey}`;
5621
+ }
5622
+ const controller = new AbortController();
5623
+ const timeoutMs = resolveRerankTimeoutMs(config.timeoutMs);
5624
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
5625
+ try {
5626
+ const fetchImpl = config.fetchImpl ?? fetch;
5627
+ const response = await fetchImpl(url, {
5628
+ method: "POST",
5629
+ headers,
5630
+ signal: controller.signal,
5631
+ body: JSON.stringify(buildRerankBody(query, documents, config.model))
5632
+ });
5633
+ if (!response.ok) {
5634
+ throw new Error(`Neural rerank HTTP ${response.status} from ${url}`);
5635
+ }
5636
+ return parseRerankResponse(await response.json());
5637
+ } finally {
5638
+ clearTimeout(timer);
5639
+ }
5640
+ }
5641
+ var init_http_provider = __esm({
5642
+ "src/rerank/http-provider.ts"() {
5643
+ "use strict";
5644
+ init_esm_shims();
5645
+ init_policy();
5646
+ }
5647
+ });
5648
+
5649
+ // src/rerank/index.ts
5650
+ var rerank_exports = {};
5651
+ __export(rerank_exports, {
5652
+ DEFAULT_RERANK_TIMEOUT_MS: () => DEFAULT_RERANK_TIMEOUT_MS,
5653
+ MIN_RERANK_CANDIDATES: () => MIN_RERANK_CANDIDATES,
5654
+ isNeuralRerankEnabled: () => isNeuralRerankEnabled,
5655
+ neuralRerankCandidates: () => neuralRerankCandidates,
5656
+ parseRerankTimeoutMs: () => parseRerankTimeoutMs,
5657
+ resolveRerankTimeoutMs: () => resolveRerankTimeoutMs,
5658
+ shouldAttemptHttpRerank: () => shouldAttemptHttpRerank,
5659
+ shouldAttemptLlmRerank: () => shouldAttemptLlmRerank
5660
+ });
5661
+ function isNeuralRerankEnabled(options = {}) {
5662
+ const lane = getResolvedRerankLane(options);
5663
+ return lane.provider === "http" && Boolean(lane.baseUrl);
5664
+ }
5665
+ async function neuralRerankCandidates(query, candidates, options = {}) {
5666
+ if (!isNeuralRerankEnabled(options) || candidates.length === 0) return null;
5667
+ const lane = getResolvedRerankLane(options);
5668
+ if (!lane.canSendRequest || !lane.baseUrl) return null;
5669
+ const toRerank = candidates.slice(0, MAX_RERANK);
5670
+ const rest = candidates.slice(MAX_RERANK);
5671
+ const documents = toRerank.map((candidate) => candidateText(candidate));
5672
+ const ranked = await rerankViaHttp(query, documents, {
5673
+ provider: "http",
5674
+ model: lane.model,
5675
+ baseUrl: lane.baseUrl,
5676
+ apiKey: lane.apiKey,
5677
+ timeoutMs: resolveRerankTimeoutMs()
5678
+ });
5679
+ const seen = /* @__PURE__ */ new Set();
5680
+ const reranked = [];
5681
+ for (const row of ranked) {
5682
+ const candidate = toRerank[row.index];
5683
+ if (!candidate || seen.has(row.index)) continue;
5684
+ reranked.push(candidate);
5685
+ seen.add(row.index);
5686
+ }
5687
+ for (const [index, candidate] of toRerank.entries()) {
5688
+ if (!seen.has(index)) reranked.push(candidate);
5689
+ }
5690
+ reranked.push(...rest);
5691
+ return reranked;
5692
+ }
5693
+ function candidateText(candidate) {
5694
+ const narrative = candidate.narrative?.trim();
5695
+ if (narrative) return `${candidate.title} \u2014 ${narrative.slice(0, 200)}`;
5696
+ return candidate.title;
5697
+ }
5698
+ var MAX_RERANK;
5699
+ var init_rerank = __esm({
5700
+ "src/rerank/index.ts"() {
5701
+ "use strict";
5702
+ init_esm_shims();
5703
+ init_resolved_config();
5704
+ init_http_provider();
5705
+ init_policy();
5706
+ init_policy();
5707
+ MAX_RERANK = 10;
5708
+ }
5709
+ });
5710
+
5335
5711
  // src/llm/quality.ts
5336
5712
  var quality_exports = {};
5337
5713
  __export(quality_exports, {
@@ -5371,9 +5747,9 @@ async function rerankResults(query, candidates) {
5371
5747
  if (!isLLMEnabled() || candidates.length <= 2) {
5372
5748
  return { reranked: candidates, usedLLM: false };
5373
5749
  }
5374
- const MAX_RERANK = 10;
5375
- const toRerank = candidates.slice(0, MAX_RERANK);
5376
- const rest = candidates.slice(MAX_RERANK);
5750
+ const MAX_RERANK2 = 10;
5751
+ const toRerank = candidates.slice(0, MAX_RERANK2);
5752
+ const rest = candidates.slice(MAX_RERANK2);
5377
5753
  try {
5378
5754
  const candidateList = toRerank.map(
5379
5755
  (c) => `[ID: ${c.id}] (${c.type}) ${c.title}${c.narrative ? ` \u2014 ${c.narrative.substring(0, 100)}` : ""}`
@@ -6266,46 +6642,78 @@ async function searchObservations(options) {
6266
6642
  }
6267
6643
  }
6268
6644
  }
6269
- const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
6270
- const top = intermediate[0]?.score ?? 0;
6271
- const second = intermediate[1]?.score ?? 0;
6272
- return top > 0 && second / top > 0.7;
6273
- })();
6274
- if (shouldRerank) {
6275
- try {
6276
- const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
6277
- const narrativeMap = /* @__PURE__ */ new Map();
6278
- for (const hit of results.hits) {
6279
- const doc = hit.document;
6280
- narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
6281
- }
6282
- const RERANK_TOP_K = 5;
6283
- const toRerank = intermediate.slice(0, RERANK_TOP_K);
6284
- const candidates = toRerank.map((e2, index) => ({
6285
- id: `r${index + 1}`,
6286
- title: e2.title,
6287
- type: e2.type,
6288
- score: e2.score,
6289
- narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
6290
- }));
6291
- const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
6292
- const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
6293
- const { reranked, usedLLM } = await withTimeout(
6294
- rerankResults2(originalQuery, candidates),
6295
- RERANK_TIMEOUT_MS,
6296
- "LLM rerank"
6297
- );
6298
- mark(`rerank(usedLLM=${usedLLM})`);
6299
- if (usedLLM) {
6300
- lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
6301
- const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
6302
- const rerankedTop = reranked.map((r2) => candidateMap.get(r2.id)).filter((e2) => e2 != null);
6303
- if (rerankedTop.length > 0) {
6304
- intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
6645
+ const httpGate = shouldAttemptHttpRerank({
6646
+ quality,
6647
+ hasQuery: Boolean(hasQuery),
6648
+ candidateCount: intermediate.length
6649
+ });
6650
+ const llmGateWithoutHttp = shouldAttemptLlmRerank({
6651
+ quality,
6652
+ tier,
6653
+ hasQuery: Boolean(hasQuery),
6654
+ candidateCount: intermediate.length,
6655
+ topScore: intermediate[0]?.score ?? 0,
6656
+ secondScore: intermediate[1]?.score ?? 0,
6657
+ httpRerankConfigured: false
6658
+ });
6659
+ if (httpGate || llmGateWithoutHttp) {
6660
+ const narrativeMap = /* @__PURE__ */ new Map();
6661
+ for (const hit of results.hits) {
6662
+ const doc = hit.document;
6663
+ narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
6664
+ }
6665
+ const RERANK_TOP_K = 5;
6666
+ const toRerank = intermediate.slice(0, RERANK_TOP_K);
6667
+ const candidates = toRerank.map((e2, index) => ({
6668
+ id: `r${index + 1}`,
6669
+ title: e2.title,
6670
+ type: e2.type,
6671
+ score: e2.score,
6672
+ narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
6673
+ }));
6674
+ const RERANK_TIMEOUT_MS = parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
6675
+ const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
6676
+ const { isNeuralRerankEnabled: isNeuralRerankEnabled2, neuralRerankCandidates: neuralRerankCandidates2 } = await Promise.resolve().then(() => (init_rerank(), rerank_exports));
6677
+ const httpConfigured = isNeuralRerankEnabled2();
6678
+ if (httpConfigured && httpGate) {
6679
+ try {
6680
+ const neural = await withTimeout(
6681
+ neuralRerankCandidates2(originalQuery, candidates),
6682
+ RERANK_TIMEOUT_MS,
6683
+ "neural rerank"
6684
+ );
6685
+ if (neural && neural.length > 0) {
6686
+ const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
6687
+ if (rerankedTop.length > 0) {
6688
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
6689
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
6690
+ mark("rerank(usedNeural=true)");
6691
+ }
6305
6692
  }
6693
+ } catch {
6694
+ console.error("[memorix] neural rerank failed or timed out; keeping original order");
6306
6695
  }
6307
- } catch (error) {
6308
- console.error("[memorix] LLM rerank failed or timed out, using original order");
6696
+ } else if (!httpConfigured && llmGateWithoutHttp) {
6697
+ try {
6698
+ const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
6699
+ const { reranked, usedLLM } = await withTimeout(
6700
+ rerankResults2(originalQuery, candidates),
6701
+ RERANK_TIMEOUT_MS,
6702
+ "LLM rerank"
6703
+ );
6704
+ mark(`rerank(usedLLM=${usedLLM})`);
6705
+ if (usedLLM) {
6706
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
6707
+ const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
6708
+ if (rerankedTop.length > 0) {
6709
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
6710
+ }
6711
+ }
6712
+ } catch {
6713
+ console.error("[memorix] LLM rerank failed or timed out, using original order");
6714
+ }
6715
+ } else {
6716
+ mark(`rerank(skipped,tier=${tier})`);
6309
6717
  }
6310
6718
  } else {
6311
6719
  mark(`rerank(skipped,tier=${tier})`);
@@ -6486,6 +6894,7 @@ var init_orama_store = __esm({
6486
6894
  init_project_affinity();
6487
6895
  init_intent_detector();
6488
6896
  init_query_expansion();
6897
+ init_policy();
6489
6898
  init_timeout();
6490
6899
  db = null;
6491
6900
  dbInitPromise = null;
@@ -7674,8 +8083,228 @@ var init_lifecycle = __esm({
7674
8083
  }
7675
8084
  });
7676
8085
 
8086
+ // src/store/evidence-store.ts
8087
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
8088
+ function safeJson(value, fallback) {
8089
+ if (typeof value !== "string" || value.length === 0) return fallback;
8090
+ try {
8091
+ return JSON.parse(value);
8092
+ } catch {
8093
+ return fallback;
8094
+ }
8095
+ }
8096
+ function rowToCard(row) {
8097
+ return {
8098
+ id: String(row.id),
8099
+ projectId: String(row.project_id),
8100
+ candidateKind: row.candidate_kind,
8101
+ candidateId: String(row.candidate_id),
8102
+ title: String(row.title),
8103
+ summary: String(row.summary ?? ""),
8104
+ ...row.entity_name ? { entityName: String(row.entity_name) } : {},
8105
+ sourceKind: String(row.source_kind),
8106
+ sourceRef: String(row.source_ref),
8107
+ ...row.locator ? { locator: String(row.locator) } : {},
8108
+ ...row.captured_hash ? { capturedHash: String(row.captured_hash) } : {},
8109
+ ...row.session_id ? { sessionId: String(row.session_id) } : {},
8110
+ status: String(row.status ?? "active"),
8111
+ verification: row.verification,
8112
+ freshness: row.freshness,
8113
+ ...row.stale_reason ? { staleReason: String(row.stale_reason) } : {},
8114
+ files: safeJson(row.files_json, []),
8115
+ relatedEntities: safeJson(row.related_entities_json, []),
8116
+ createdAt: String(row.created_at),
8117
+ updatedAt: String(row.updated_at)
8118
+ };
8119
+ }
8120
+ function observationHash(observation) {
8121
+ return createHash3("sha256").update(JSON.stringify({
8122
+ projectId: observation.projectId,
8123
+ id: observation.id,
8124
+ title: observation.title,
8125
+ narrative: observation.narrative,
8126
+ facts: observation.facts,
8127
+ files: observation.filesModified,
8128
+ relatedEntities: observation.relatedEntities
8129
+ })).digest("hex");
8130
+ }
8131
+ function sourceKind(observation) {
8132
+ if (observation.source === "git" || observation.sourceDetail === "git-ingest") return "git";
8133
+ if (observation.sourceDetail === "hook") return "hook";
8134
+ return observation.source ?? "agent";
8135
+ }
8136
+ var EvidenceCardStore;
8137
+ var init_evidence_store = __esm({
8138
+ "src/store/evidence-store.ts"() {
8139
+ "use strict";
8140
+ init_esm_shims();
8141
+ init_sqlite_db();
8142
+ EvidenceCardStore = class {
8143
+ db = null;
8144
+ async init(dataDir) {
8145
+ this.db = getDatabase(dataDir);
8146
+ }
8147
+ requireDb() {
8148
+ if (!this.db) throw new Error("EvidenceCardStore is not initialized.");
8149
+ return this.db;
8150
+ }
8151
+ upsertObservation(observation) {
8152
+ const db2 = this.requireDb();
8153
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8154
+ const card = {
8155
+ id: `evidence:observation:${observation.projectId}:${observation.id}`,
8156
+ projectId: observation.projectId,
8157
+ candidateKind: "observation",
8158
+ candidateId: String(observation.id),
8159
+ title: observation.title,
8160
+ summary: observation.narrative.slice(0, 500),
8161
+ entityName: observation.entityName,
8162
+ sourceKind: sourceKind(observation),
8163
+ sourceRef: `observation:${observation.id}`,
8164
+ locator: observation.filesModified?.join(", ") || `observation:${observation.id}`,
8165
+ capturedHash: observationHash(observation),
8166
+ ...observation.sessionId ? { sessionId: observation.sessionId } : {},
8167
+ status: observation.status ?? "active",
8168
+ verification: observation.status === "resolved" ? "verified" : "unverified",
8169
+ freshness: "current",
8170
+ files: observation.filesModified ?? [],
8171
+ relatedEntities: observation.relatedEntities ?? [],
8172
+ createdAt: observation.createdAt,
8173
+ updatedAt: now3
8174
+ };
8175
+ db2.prepare(`
8176
+ INSERT INTO evidence_cards (
8177
+ id, project_id, candidate_kind, candidate_id, title, summary, entity_name,
8178
+ source_kind, source_ref, locator, captured_hash, session_id, status,
8179
+ verification, freshness, stale_reason, files_json, related_entities_json,
8180
+ created_at, updated_at
8181
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
8182
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
8183
+ title = excluded.title,
8184
+ summary = excluded.summary,
8185
+ entity_name = excluded.entity_name,
8186
+ source_kind = excluded.source_kind,
8187
+ source_ref = excluded.source_ref,
8188
+ locator = excluded.locator,
8189
+ captured_hash = excluded.captured_hash,
8190
+ session_id = excluded.session_id,
8191
+ status = excluded.status,
8192
+ freshness = 'current',
8193
+ stale_reason = NULL,
8194
+ files_json = excluded.files_json,
8195
+ related_entities_json = excluded.related_entities_json,
8196
+ updated_at = excluded.updated_at
8197
+ `).run(
8198
+ card.id,
8199
+ card.projectId,
8200
+ card.candidateKind,
8201
+ card.candidateId,
8202
+ card.title,
8203
+ card.summary,
8204
+ card.entityName ?? null,
8205
+ card.sourceKind,
8206
+ card.sourceRef,
8207
+ card.locator ?? null,
8208
+ card.capturedHash ?? null,
8209
+ card.sessionId ?? null,
8210
+ card.status,
8211
+ card.verification,
8212
+ card.freshness,
8213
+ JSON.stringify(card.files),
8214
+ JSON.stringify(card.relatedEntities),
8215
+ card.createdAt,
8216
+ card.updatedAt
8217
+ );
8218
+ return this.get(card.projectId, card.candidateKind, card.candidateId) ?? card;
8219
+ }
8220
+ get(projectId, candidateKind, candidateId) {
8221
+ const row = this.requireDb().prepare(`
8222
+ SELECT * FROM evidence_cards WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
8223
+ `).get(projectId, candidateKind, candidateId);
8224
+ return row ? rowToCard(row) : void 0;
8225
+ }
8226
+ list(projectId, options = {}) {
8227
+ const limit2 = Math.min(100, Math.max(1, Math.floor(options.limit ?? 20)));
8228
+ const clauses = ["project_id = ?"];
8229
+ const args = [projectId];
8230
+ if (options.freshness) {
8231
+ clauses.push("freshness = ?");
8232
+ args.push(options.freshness);
8233
+ }
8234
+ if (options.status) {
8235
+ clauses.push("status = ?");
8236
+ args.push(options.status);
8237
+ }
8238
+ args.push(limit2);
8239
+ const rows = this.requireDb().prepare(`
8240
+ SELECT * FROM evidence_cards WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC, id DESC LIMIT ?
8241
+ `).all(...args);
8242
+ return rows.map(rowToCard);
8243
+ }
8244
+ syncObservations(observations2) {
8245
+ return observations2.map((observation) => this.upsertObservation(observation));
8246
+ }
8247
+ markStaleForPaths(projectId, paths, reason = "source file changed") {
8248
+ const normalized = new Set(paths.map((value) => value.replace(/\\/g, "/").toLowerCase()));
8249
+ if (normalized.size === 0) return 0;
8250
+ const db2 = this.requireDb();
8251
+ const rows = db2.prepare(`SELECT * FROM evidence_cards WHERE project_id = ? AND freshness != 'stale'`).all(projectId);
8252
+ const update2 = db2.prepare(`
8253
+ UPDATE evidence_cards SET freshness = 'stale', stale_reason = ?, updated_at = ?
8254
+ WHERE id = ?
8255
+ `);
8256
+ let changed = 0;
8257
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8258
+ const run = db2.transaction(() => {
8259
+ for (const row of rows) {
8260
+ const files = safeJson(row.files_json, []);
8261
+ if (!files.some((file) => normalized.has(file.replace(/\\/g, "/").toLowerCase()))) continue;
8262
+ update2.run(reason, now3, row.id);
8263
+ changed++;
8264
+ }
8265
+ });
8266
+ run();
8267
+ return changed;
8268
+ }
8269
+ setVerification(projectId, cardId, verification) {
8270
+ this.requireDb().prepare(`
8271
+ UPDATE evidence_cards SET verification = ?, updated_at = ? WHERE project_id = ? AND id = ?
8272
+ `).run(verification, (/* @__PURE__ */ new Date()).toISOString(), projectId, cardId);
8273
+ }
8274
+ recordEvent(projectId, cardId, kind, detail) {
8275
+ const event = {
8276
+ id: randomUUID3(),
8277
+ projectId,
8278
+ cardId,
8279
+ kind,
8280
+ ...detail ? { detail: detail.slice(0, 2e3) } : {},
8281
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8282
+ };
8283
+ this.requireDb().prepare(`
8284
+ INSERT INTO evidence_card_events (id, project_id, card_id, kind, detail, created_at)
8285
+ VALUES (?, ?, ?, ?, ?, ?)
8286
+ `).run(event.id, event.projectId, event.cardId, event.kind, event.detail ?? null, event.createdAt);
8287
+ return event;
8288
+ }
8289
+ listEvents(cardId, limit2 = 50) {
8290
+ const rows = this.requireDb().prepare(`
8291
+ SELECT * FROM evidence_card_events WHERE card_id = ? ORDER BY created_at DESC LIMIT ?
8292
+ `).all(cardId, Math.min(200, Math.max(1, Math.floor(limit2))));
8293
+ return rows.map((row) => ({
8294
+ id: String(row.id),
8295
+ projectId: String(row.project_id),
8296
+ cardId: String(row.card_id),
8297
+ kind: String(row.kind),
8298
+ ...row.detail ? { detail: String(row.detail) } : {},
8299
+ createdAt: String(row.created_at)
8300
+ }));
8301
+ }
8302
+ };
8303
+ }
8304
+ });
8305
+
7677
8306
  // src/knowledge/workspace-store.ts
7678
- import { randomUUID as randomUUID3 } from "crypto";
8307
+ import { randomUUID as randomUUID4 } from "crypto";
7679
8308
  function arrayValue(raw) {
7680
8309
  if (typeof raw !== "string" || !raw) return [];
7681
8310
  try {
@@ -7816,7 +8445,7 @@ var init_workspace_store = __esm({
7816
8445
  }
7817
8446
  createProposal(input) {
7818
8447
  const proposal = {
7819
- id: input.id ?? randomUUID3(),
8448
+ id: input.id ?? randomUUID4(),
7820
8449
  ...input,
7821
8450
  status: input.status ?? "pending",
7822
8451
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -7869,13 +8498,13 @@ __export(workspace_exports, {
7869
8498
  loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
7870
8499
  resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
7871
8500
  });
7872
- import { createHash as createHash3 } from "crypto";
8501
+ import { createHash as createHash4 } from "crypto";
7873
8502
  import { execFile } from "child_process";
7874
8503
  import { promises as fs6 } from "fs";
7875
8504
  import path9 from "path";
7876
8505
  import { promisify } from "util";
7877
8506
  function hash(value) {
7878
- return createHash3("sha256").update(value).digest("hex");
8507
+ return createHash4("sha256").update(value).digest("hex");
7879
8508
  }
7880
8509
  function workspaceId(projectId, mode) {
7881
8510
  return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
@@ -8471,7 +9100,7 @@ Respond in JSON only:
8471
9100
  });
8472
9101
 
8473
9102
  // src/media/media-store.ts
8474
- import { randomUUID as randomUUID4 } from "crypto";
9103
+ import { randomUUID as randomUUID5 } from "crypto";
8475
9104
  function asOptionalText(value) {
8476
9105
  return typeof value === "string" && value.length > 0 ? value : void 0;
8477
9106
  }
@@ -8641,7 +9270,7 @@ var init_media_store = __esm({
8641
9270
  return { asset: this.getAsset(input.projectId, existing.id), deduplicated: true };
8642
9271
  }
8643
9272
  const asset = {
8644
- id: randomUUID4(),
9273
+ id: randomUUID5(),
8645
9274
  ...input,
8646
9275
  createdAt: now3,
8647
9276
  updatedAt: now3
@@ -8721,7 +9350,7 @@ var init_media_store = __esm({
8721
9350
  `).get(input.assetId, input.observationId ?? null, input.role);
8722
9351
  if (existing) return rowToLink(existing);
8723
9352
  const link = {
8724
- id: randomUUID4(),
9353
+ id: randomUUID5(),
8725
9354
  assetId: input.assetId,
8726
9355
  projectId: input.projectId,
8727
9356
  ...input.observationId !== void 0 ? { observationId: input.observationId } : {},
@@ -8760,7 +9389,7 @@ var init_media_store = __esm({
8760
9389
  return rowToDerivation(this.db.prepare("SELECT * FROM media_derivations WHERE id = ?").get(existing.id));
8761
9390
  }
8762
9391
  const derivation = {
8763
- id: randomUUID4(),
9392
+ id: randomUUID5(),
8764
9393
  ...input,
8765
9394
  content,
8766
9395
  ...error ? { error } : {},
@@ -8847,7 +9476,7 @@ var init_media_store = __esm({
8847
9476
  createJob(input) {
8848
9477
  const now3 = Date.now();
8849
9478
  const job = {
8850
- id: randomUUID4(),
9479
+ id: randomUUID5(),
8851
9480
  projectId: input.projectId,
8852
9481
  kind: input.kind,
8853
9482
  status: "queued",
@@ -9009,14 +9638,14 @@ var init_types2 = __esm({
9009
9638
  });
9010
9639
 
9011
9640
  // src/media/asset-store.ts
9012
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
9641
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
9013
9642
  import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
9014
9643
  import path10 from "path";
9015
9644
  function mediaRoot(dataDir) {
9016
9645
  return path10.resolve(dataDir, "media");
9017
9646
  }
9018
9647
  function projectStorageKey(projectId) {
9019
- return createHash4("sha256").update(projectId).digest("hex").slice(0, 16);
9648
+ return createHash5("sha256").update(projectId).digest("hex").slice(0, 16);
9020
9649
  }
9021
9650
  function validateUnderRoot(root, candidate) {
9022
9651
  const resolved = path10.resolve(candidate);
@@ -9080,7 +9709,7 @@ async function destinationExists(destination) {
9080
9709
  async function persistBuffer(bytes, destination) {
9081
9710
  await mkdir3(path10.dirname(destination), { recursive: true });
9082
9711
  if (await destinationExists(destination)) return false;
9083
- const temporary = `${destination}.${randomUUID5()}.tmp`;
9712
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
9084
9713
  try {
9085
9714
  await writeFile3(temporary, bytes, { flag: "wx" });
9086
9715
  await rename2(temporary, destination);
@@ -9125,7 +9754,7 @@ async function importMediaBuffer(input) {
9125
9754
  if (input.bytes.length === 0) throw new Error("Media import rejects empty payloads");
9126
9755
  if (input.bytes.length > maxBytes) throw new Error(`Media payload is too large (${input.bytes.length} bytes; limit ${maxBytes})`);
9127
9756
  const detected = detectMedia(input.bytes.subarray(0, 32));
9128
- const sha256 = createHash4("sha256").update(input.bytes).digest("hex");
9757
+ const sha256 = createHash5("sha256").update(input.bytes).digest("hex");
9129
9758
  return importKnownMedia({
9130
9759
  dataDir: input.dataDir,
9131
9760
  projectId: input.projectId,
@@ -9143,7 +9772,7 @@ async function readMediaAsset(dataDir, asset, maxBytes = DEFAULT_MAX_MEDIA_BYTES
9143
9772
  if (asset.byteSize > maxBytes) throw new Error(`Media asset exceeds read limit (${asset.byteSize} bytes; limit ${maxBytes})`);
9144
9773
  const bytes = await readFile3(getMediaAssetPath(dataDir, asset));
9145
9774
  if (bytes.length !== asset.byteSize) throw new Error(`Media asset size no longer matches metadata: ${asset.id}`);
9146
- const sha256 = createHash4("sha256").update(bytes).digest("hex");
9775
+ const sha256 = createHash5("sha256").update(bytes).digest("hex");
9147
9776
  if (sha256 !== asset.sha256) throw new Error(`Media asset hash no longer matches metadata: ${asset.id}`);
9148
9777
  return bytes;
9149
9778
  }
@@ -61894,7 +62523,7 @@ var require_gaxios = __commonJS({
61894
62523
  var retry_js_1 = require_retry3();
61895
62524
  var stream_1 = __require("stream");
61896
62525
  var interceptor_js_1 = require_interceptor();
61897
- var randomUUID14 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
62526
+ var randomUUID16 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
61898
62527
  var HTTP_STATUS_NO_CONTENT = 204;
61899
62528
  var Gaxios = class {
61900
62529
  agentCache = /* @__PURE__ */ new Map();
@@ -62167,7 +62796,7 @@ var require_gaxios = __commonJS({
62167
62796
  */
62168
62797
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
62169
62798
  if (opts.multipart?.length) {
62170
- const boundary = await randomUUID14();
62799
+ const boundary = await randomUUID16();
62171
62800
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
62172
62801
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
62173
62802
  } else if (shouldDirectlyPassData) {
@@ -74394,7 +75023,7 @@ var require_websocket = __commonJS({
74394
75023
  var http3 = __require("http");
74395
75024
  var net = __require("net");
74396
75025
  var tls = __require("tls");
74397
- var { randomBytes, createHash: createHash13 } = __require("crypto");
75026
+ var { randomBytes, createHash: createHash14 } = __require("crypto");
74398
75027
  var { Duplex, Readable: Readable2 } = __require("stream");
74399
75028
  var { URL: URL2 } = __require("url");
74400
75029
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -75062,7 +75691,7 @@ var require_websocket = __commonJS({
75062
75691
  abortHandshake(websocket, socket, "Invalid Upgrade header");
75063
75692
  return;
75064
75693
  }
75065
- const digest2 = createHash13("sha1").update(key + GUID).digest("base64");
75694
+ const digest2 = createHash14("sha1").update(key + GUID).digest("base64");
75066
75695
  if (res.headers["sec-websocket-accept"] !== digest2) {
75067
75696
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
75068
75697
  return;
@@ -75434,7 +76063,7 @@ var require_websocket_server = __commonJS({
75434
76063
  var EventEmitter = __require("events");
75435
76064
  var http3 = __require("http");
75436
76065
  var { Duplex } = __require("stream");
75437
- var { createHash: createHash13 } = __require("crypto");
76066
+ var { createHash: createHash14 } = __require("crypto");
75438
76067
  var extension3 = require_extension();
75439
76068
  var PerMessageDeflate2 = require_permessage_deflate();
75440
76069
  var subprotocol2 = require_subprotocol();
@@ -75741,7 +76370,7 @@ var require_websocket_server = __commonJS({
75741
76370
  );
75742
76371
  }
75743
76372
  if (this._state > RUNNING) return abortHandshake(socket, 503);
75744
- const digest2 = createHash13("sha1").update(key + GUID).digest("base64");
76373
+ const digest2 = createHash14("sha1").update(key + GUID).digest("base64");
75745
76374
  const headers = [
75746
76375
  "HTTP/1.1 101 Switching Protocols",
75747
76376
  "Upgrade: websocket",
@@ -142879,6 +143508,186 @@ var init_audio_jobs = __esm({
142879
143508
  }
142880
143509
  });
142881
143510
 
143511
+ // src/memory/feedback-model.ts
143512
+ function feedbackWeightMultiplier(weight) {
143513
+ return Math.max(0.35, Math.min(1.4, 0.35 + Math.max(0, Math.min(2, weight)) * 0.525));
143514
+ }
143515
+ var init_feedback_model = __esm({
143516
+ "src/memory/feedback-model.ts"() {
143517
+ "use strict";
143518
+ init_esm_shims();
143519
+ }
143520
+ });
143521
+
143522
+ // src/memory/feedback.ts
143523
+ var feedback_exports = {};
143524
+ __export(feedback_exports, {
143525
+ FEEDBACK_DELTAS: () => FEEDBACK_DELTAS,
143526
+ MemoryFeedbackStore: () => MemoryFeedbackStore,
143527
+ applyMemoryFeedback: () => applyMemoryFeedback,
143528
+ feedbackWeightMultiplier: () => feedbackWeightMultiplier
143529
+ });
143530
+ import { randomUUID as randomUUID7 } from "crypto";
143531
+ function applyMemoryFeedback(current, event) {
143532
+ const delta = event.signal === "revoke" ? 0 : FEEDBACK_DELTAS[event.signal];
143533
+ const weight = Math.max(0, Math.min(2, current.weight + delta));
143534
+ const status = (event.signal === "code-conflict" || event.signal === "user-correction" || event.signal === "weaken") && weight < 0.5 ? "archived" : current.status;
143535
+ return { weight, status, audit: [...current.audit, { ...event }] };
143536
+ }
143537
+ function rowToEvent(row) {
143538
+ return {
143539
+ id: String(row.id),
143540
+ projectId: String(row.project_id),
143541
+ candidateKind: row.candidate_kind,
143542
+ candidateId: String(row.candidate_id),
143543
+ signal: row.signal,
143544
+ ...row.observed_at ? { at: String(row.observed_at) } : { at: (/* @__PURE__ */ new Date()).toISOString() },
143545
+ ...row.actor ? { actor: String(row.actor) } : {},
143546
+ ...row.note ? { note: String(row.note) } : {},
143547
+ ...row.source_ref ? { sourceRef: String(row.source_ref) } : {},
143548
+ ...row.target_event_id ? { targetEventId: String(row.target_event_id) } : {},
143549
+ delta: Number(row.delta ?? 0)
143550
+ };
143551
+ }
143552
+ var FEEDBACK_DELTAS, MemoryFeedbackStore;
143553
+ var init_feedback = __esm({
143554
+ "src/memory/feedback.ts"() {
143555
+ "use strict";
143556
+ init_esm_shims();
143557
+ init_sqlite_db();
143558
+ init_feedback_model();
143559
+ FEEDBACK_DELTAS = {
143560
+ "user-correction": -0.35,
143561
+ "verification-success": 0.25,
143562
+ "verification-failure": -0.25,
143563
+ "code-change": -0.15,
143564
+ used: 0.1,
143565
+ "not-used": -0.05,
143566
+ "code-conflict": -0.4,
143567
+ strengthen: 0.2,
143568
+ weaken: -0.2
143569
+ };
143570
+ MemoryFeedbackStore = class {
143571
+ db = null;
143572
+ async init(dataDir) {
143573
+ this.db = getDatabase(dataDir);
143574
+ }
143575
+ requireDb() {
143576
+ if (!this.db) throw new Error("MemoryFeedbackStore is not initialized.");
143577
+ return this.db;
143578
+ }
143579
+ loadEvents(projectId, candidateKind, candidateId) {
143580
+ return this.requireDb().prepare(`
143581
+ SELECT * FROM memory_feedback_events
143582
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
143583
+ ORDER BY observed_at ASC, rowid ASC
143584
+ `).all(projectId, candidateKind, candidateId).map(rowToEvent);
143585
+ }
143586
+ rebuild(projectId, candidateKind, candidateId) {
143587
+ const events = this.loadEvents(projectId, candidateKind, candidateId);
143588
+ const revoked = new Set(events.filter((event) => event.signal === "revoke" && event.targetEventId).map((event) => event.targetEventId));
143589
+ let state = { weight: 1, status: "active", audit: [] };
143590
+ for (const event of events) {
143591
+ if (event.signal === "revoke" || revoked.has(event.id)) {
143592
+ state.audit.push(event);
143593
+ continue;
143594
+ }
143595
+ state = applyMemoryFeedback(state, event);
143596
+ }
143597
+ this.requireDb().prepare(`
143598
+ INSERT INTO memory_feedback_states (project_id, candidate_kind, candidate_id, weight, lifecycle_status, updated_at)
143599
+ VALUES (?, ?, ?, ?, ?, ?)
143600
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
143601
+ weight = excluded.weight, lifecycle_status = excluded.lifecycle_status, updated_at = excluded.updated_at
143602
+ `).run(projectId, candidateKind, candidateId, state.weight, state.status, (/* @__PURE__ */ new Date()).toISOString());
143603
+ return state;
143604
+ }
143605
+ getState(projectId, candidateKind, candidateId) {
143606
+ const row = this.requireDb().prepare(`
143607
+ SELECT weight, lifecycle_status FROM memory_feedback_states
143608
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
143609
+ `).get(projectId, candidateKind, candidateId);
143610
+ if (!row) return { weight: 1, status: "active", audit: [] };
143611
+ return {
143612
+ weight: Number(row.weight ?? 1),
143613
+ status: row.lifecycle_status ?? "active",
143614
+ audit: this.loadEvents(projectId, candidateKind, candidateId)
143615
+ };
143616
+ }
143617
+ listStates(projectId, candidateKind = "observation") {
143618
+ return this.requireDb().prepare(`
143619
+ SELECT candidate_id, weight, lifecycle_status, updated_at
143620
+ FROM memory_feedback_states WHERE project_id = ? AND candidate_kind = ?
143621
+ ORDER BY updated_at DESC
143622
+ `).all(projectId, candidateKind).map((row) => ({
143623
+ candidateId: String(row.candidate_id),
143624
+ weight: Number(row.weight),
143625
+ status: row.lifecycle_status,
143626
+ updatedAt: String(row.updated_at)
143627
+ }));
143628
+ }
143629
+ weights(projectId, candidateKind = "observation") {
143630
+ const states = /* @__PURE__ */ new Map();
143631
+ for (const row of this.listStates(projectId, candidateKind)) {
143632
+ states.set(row.candidateId, this.getState(projectId, candidateKind, row.candidateId));
143633
+ }
143634
+ return states;
143635
+ }
143636
+ record(input) {
143637
+ if (input.signal === "revoke" && !input.targetEventId) {
143638
+ throw new Error("A revoke feedback event requires targetEventId.");
143639
+ }
143640
+ if (input.signal === "revoke") {
143641
+ const target = this.requireDb().prepare(`
143642
+ SELECT 1 FROM memory_feedback_events
143643
+ WHERE id = ? AND project_id = ? AND candidate_kind = ? AND candidate_id = ?
143644
+ `).get(input.targetEventId, input.projectId, input.candidateKind, input.candidateId);
143645
+ if (!target) throw new Error("The feedback event to revoke was not found in the same candidate scope.");
143646
+ }
143647
+ const event = {
143648
+ id: randomUUID7(),
143649
+ projectId: input.projectId,
143650
+ candidateKind: input.candidateKind,
143651
+ candidateId: input.candidateId,
143652
+ signal: input.signal,
143653
+ sourceRef: input.sourceRef,
143654
+ ...input.actor ? { actor: input.actor } : {},
143655
+ ...input.note ? { note: input.note.slice(0, 2e3) } : {},
143656
+ ...input.targetEventId ? { targetEventId: input.targetEventId } : {},
143657
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
143658
+ delta: input.signal === "revoke" ? 0 : FEEDBACK_DELTAS[input.signal]
143659
+ };
143660
+ this.requireDb().prepare(`
143661
+ INSERT INTO memory_feedback_events (
143662
+ id, project_id, candidate_kind, candidate_id, signal, actor, note,
143663
+ source_ref, target_event_id, delta, observed_at
143664
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
143665
+ `).run(
143666
+ event.id,
143667
+ event.projectId,
143668
+ event.candidateKind,
143669
+ event.candidateId,
143670
+ event.signal,
143671
+ event.actor ?? null,
143672
+ event.note ?? null,
143673
+ event.sourceRef,
143674
+ event.targetEventId ?? null,
143675
+ event.delta,
143676
+ event.at
143677
+ );
143678
+ return { event, state: this.rebuild(input.projectId, input.candidateKind, input.candidateId) };
143679
+ }
143680
+ audit(projectId, candidateKind, candidateId, limit2 = 100) {
143681
+ return this.requireDb().prepare(`
143682
+ SELECT * FROM memory_feedback_events
143683
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
143684
+ ORDER BY observed_at DESC, rowid DESC LIMIT ?
143685
+ `).all(projectId, candidateKind, candidateId, Math.min(200, Math.max(1, Math.floor(limit2)))).map(rowToEvent);
143686
+ }
143687
+ };
143688
+ }
143689
+ });
143690
+
142882
143691
  // src/memory/retention.ts
142883
143692
  var retention_exports = {};
142884
143693
  __export(retention_exports, {
@@ -142920,6 +143729,7 @@ function getEffectiveRetentionDays(doc) {
142920
143729
  }
142921
143730
  function isImmune(doc) {
142922
143731
  if (doc.type === "probe") return false;
143732
+ if (doc.feedbackStatus === "archived") return false;
142923
143733
  if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
142924
143734
  if (doc.valueCategory === "core") return true;
142925
143735
  const importance = getImportanceLevel(doc);
@@ -142952,7 +143762,8 @@ function calculateRelevance(doc, referenceTime) {
142952
143762
  const decayFactor = Math.exp(-ageDays / retention);
142953
143763
  const accessCount = doc.accessCount ?? 0;
142954
143764
  const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
142955
- let totalScore = base * decayFactor * accessBoost;
143765
+ const feedbackMultiplier = doc.feedbackWeight === void 0 ? 1 : feedbackWeightMultiplier(doc.feedbackWeight);
143766
+ let totalScore = base * decayFactor * accessBoost * feedbackMultiplier;
142956
143767
  const immune = isImmune(doc);
142957
143768
  if (immune) {
142958
143769
  totalScore = Math.max(totalScore, 0.5);
@@ -142964,7 +143775,8 @@ function calculateRelevance(doc, referenceTime) {
142964
143775
  decayFactor,
142965
143776
  accessBoost,
142966
143777
  ageDays,
142967
- isImmune: immune
143778
+ isImmune: immune,
143779
+ feedbackMultiplier
142968
143780
  };
142969
143781
  }
142970
143782
  function rankByRelevance(docs, referenceTime) {
@@ -142972,6 +143784,7 @@ function rankByRelevance(docs, referenceTime) {
142972
143784
  }
142973
143785
  function getRetentionZone(doc, referenceTime) {
142974
143786
  const now3 = referenceTime ?? /* @__PURE__ */ new Date();
143787
+ if (doc.feedbackStatus === "archived") return "archive-candidate";
142975
143788
  const importance = getImportanceLevel(doc);
142976
143789
  const retention = getEffectiveRetentionDays(doc);
142977
143790
  const createdAt = new Date(doc.createdAt);
@@ -143043,7 +143856,7 @@ function explainRetention(doc, referenceTime) {
143043
143856
  summary: parts.join(" ; ")
143044
143857
  };
143045
143858
  }
143046
- function toRetentionDocument(obs, accessMap) {
143859
+ function toRetentionDocument(obs, accessMap, feedback) {
143047
143860
  const access2 = accessMap?.get(obs.id);
143048
143861
  return {
143049
143862
  id: `obs-${obs.id}`,
@@ -143065,11 +143878,12 @@ function toRetentionDocument(obs, accessMap) {
143065
143878
  sourceDetail: obs.sourceDetail ?? "",
143066
143879
  valueCategory: obs.valueCategory ?? "",
143067
143880
  admissionState: obs.admissionState ?? "",
143068
- admissionReason: obs.admissionReason ?? ""
143881
+ admissionReason: obs.admissionReason ?? "",
143882
+ ...feedback ? { feedbackWeight: feedback.weight, feedbackStatus: feedback.status } : {}
143069
143883
  };
143070
143884
  }
143071
143885
  function projectObservationRetention(observation, options = {}) {
143072
- const document2 = toRetentionDocument(observation, options.accessMap);
143886
+ const document2 = toRetentionDocument(observation, options.accessMap, options.feedback);
143073
143887
  const relevance = calculateRelevance(document2, options.referenceTime);
143074
143888
  const explanation = explainRetention(document2, options.referenceTime);
143075
143889
  return {
@@ -143094,7 +143908,17 @@ function summarizeRetentionProjections(projections) {
143094
143908
  }
143095
143909
  return { active, stale, archiveCandidates, immune };
143096
143910
  }
143097
- async function archiveExpiredBatch(_projectDir, options) {
143911
+ async function loadFeedbackStore(dataDir) {
143912
+ try {
143913
+ const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
143914
+ const store = new MemoryFeedbackStore2();
143915
+ await store.init(dataDir);
143916
+ return store;
143917
+ } catch {
143918
+ return void 0;
143919
+ }
143920
+ }
143921
+ async function archiveExpiredBatch(projectDir2, options) {
143098
143922
  const store = getObservationStore();
143099
143923
  const limit2 = Math.min(500, Math.max(1, Math.floor(options.limit ?? 100)));
143100
143924
  const afterId = Math.max(0, Math.floor(options.afterId ?? 0));
@@ -143103,11 +143927,13 @@ async function archiveExpiredBatch(_projectDir, options) {
143103
143927
  afterId,
143104
143928
  limit: limit2 + 1
143105
143929
  });
143930
+ const feedback = await loadFeedbackStore(projectDir2);
143106
143931
  const hasMore = page.length > limit2;
143107
143932
  const scanned = hasMore ? page.slice(0, limit2) : page;
143108
143933
  const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => projectObservationRetention(observation, {
143109
143934
  accessMap: options.accessMap,
143110
- referenceTime: options.referenceTime
143935
+ referenceTime: options.referenceTime,
143936
+ ...feedback ? { feedback: feedback.getState(options.projectId, "observation", String(observation.id)) } : {}
143111
143937
  }).zone === "archive-candidate").map((observation) => observation.id);
143112
143938
  const archived = candidateIds.length === 0 ? 0 : await store.atomic(async (tx) => {
143113
143939
  const updates = await Promise.all(
@@ -143138,6 +143964,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
143138
143964
  const remaining = reader ? remainingObservations.filter((observation) => canManageObservation(observation, reader)).length : remainingObservations.length;
143139
143965
  return { archived, remaining };
143140
143966
  }
143967
+ const feedback = await loadFeedbackStore(projectDir2);
143141
143968
  return await store.atomic(async (tx) => {
143142
143969
  const allObs = await tx.loadAll();
143143
143970
  const activeObs = allObs.filter(
@@ -143145,7 +143972,11 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
143145
143972
  );
143146
143973
  const archivedIds = [];
143147
143974
  for (const obs of activeObs) {
143148
- const retention = projectObservationRetention(obs, { accessMap, referenceTime });
143975
+ const retention = projectObservationRetention(obs, {
143976
+ accessMap,
143977
+ referenceTime,
143978
+ ...feedback ? { feedback: feedback.getState(obs.projectId, "observation", String(obs.id)) } : {}
143979
+ });
143149
143980
  if (retention.zone === "archive-candidate") {
143150
143981
  archivedIds.push(obs.id);
143151
143982
  }
@@ -143165,6 +143996,7 @@ var init_retention = __esm({
143165
143996
  init_esm_shims();
143166
143997
  init_obs_store();
143167
143998
  init_visibility();
143999
+ init_feedback_model();
143168
144000
  RETENTION_DAYS = {
143169
144001
  critical: 365,
143170
144002
  high: 180,
@@ -143201,7 +144033,7 @@ var init_retention = __esm({
143201
144033
  });
143202
144034
 
143203
144035
  // src/memory/owner.ts
143204
- import { randomUUID as randomUUID6 } from "crypto";
144036
+ import { randomUUID as randomUUID8 } from "crypto";
143205
144037
  import { promises as fs9 } from "fs";
143206
144038
  import path16 from "path";
143207
144039
  function ownerPath(dataDir) {
@@ -143231,7 +144063,7 @@ async function resolveLocalMemoryOwner(dataDir, options = {}) {
143231
144063
  await fs9.mkdir(path16.dirname(filePath), { recursive: true });
143232
144064
  const created = {
143233
144065
  version: 1,
143234
- id: "local-" + randomUUID6(),
144066
+ id: "local-" + randomUUID8(),
143235
144067
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
143236
144068
  };
143237
144069
  try {
@@ -143254,7 +144086,7 @@ var init_owner = __esm({
143254
144086
  });
143255
144087
 
143256
144088
  // src/memory/long-term-store.ts
143257
- import { randomUUID as randomUUID7 } from "crypto";
144089
+ import { randomUUID as randomUUID9 } from "crypto";
143258
144090
  function optionalText2(value) {
143259
144091
  return typeof value === "string" && value.trim() ? value : void 0;
143260
144092
  }
@@ -143305,7 +144137,7 @@ function rowToEvidence(row) {
143305
144137
  createdAt: row.createdAt
143306
144138
  };
143307
144139
  }
143308
- function rowToEvent(row) {
144140
+ function rowToEvent2(row) {
143309
144141
  return {
143310
144142
  id: row.id,
143311
144143
  memoryId: row.memoryId,
@@ -143414,7 +144246,7 @@ var init_long_term_store = __esm({
143414
144246
  }
143415
144247
  insertEvidence(input) {
143416
144248
  const evidence = {
143417
- id: randomUUID7(),
144249
+ id: randomUUID9(),
143418
144250
  ...input,
143419
144251
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
143420
144252
  };
@@ -143441,7 +144273,7 @@ var init_long_term_store = __esm({
143441
144273
  }
143442
144274
  recordEvent(input) {
143443
144275
  const event = {
143444
- id: randomUUID7(),
144276
+ id: randomUUID9(),
143445
144277
  ...input,
143446
144278
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
143447
144279
  };
@@ -143462,7 +144294,7 @@ var init_long_term_store = __esm({
143462
144294
  listEvents(memoryId) {
143463
144295
  return this.db.prepare(`
143464
144296
  SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
143465
- `).all(memoryId).map(rowToEvent);
144297
+ `).all(memoryId).map(rowToEvent2);
143466
144298
  }
143467
144299
  markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
143468
144300
  const unique = [...new Set(ids.filter(Boolean))];
@@ -143531,7 +144363,7 @@ __export(long_term_exports, {
143531
144363
  selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
143532
144364
  supersedeLongTermMemory: () => supersedeLongTermMemory
143533
144365
  });
143534
- import { randomUUID as randomUUID8 } from "crypto";
144366
+ import { randomUUID as randomUUID10 } from "crypto";
143535
144367
  function isOneOf(value, values) {
143536
144368
  return typeof value === "string" && values.includes(value);
143537
144369
  }
@@ -143713,7 +144545,7 @@ async function createManualLongTermMemory(input) {
143713
144545
  const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
143714
144546
  if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
143715
144547
  assertScope(input.scope);
143716
- const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID8(), relation: "supports" }];
144548
+ const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID10(), relation: "supports" }];
143717
144549
  return createLongTermMemoryWithDataDir({
143718
144550
  dataDir: input.dataDir,
143719
144551
  originProjectId: input.projectId,
@@ -143768,7 +144600,7 @@ async function createLongTermMemoryWithDataDir(input) {
143768
144600
  assertPortableEvidence(input.scope, portability, evidenceInputs);
143769
144601
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
143770
144602
  const memory = {
143771
- id: randomUUID8(),
144603
+ id: randomUUID10(),
143772
144604
  originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
143773
144605
  ownerId: compactText(input.ownerId, "owner id", 160),
143774
144606
  scope: input.scope,
@@ -144086,9 +144918,9 @@ var init_long_term = __esm({
144086
144918
  });
144087
144919
 
144088
144920
  // src/codegraph/ids.ts
144089
- import { createHash as createHash5 } from "crypto";
144921
+ import { createHash as createHash6 } from "crypto";
144090
144922
  function digest(input) {
144091
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
144923
+ return createHash6("sha256").update(input).digest("hex").slice(0, 16);
144092
144924
  }
144093
144925
  function normalizeCodePath(path22) {
144094
144926
  return path22.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
@@ -144123,7 +144955,7 @@ var store_exports = {};
144123
144955
  __export(store_exports, {
144124
144956
  CodeGraphStore: () => CodeGraphStore
144125
144957
  });
144126
- import { randomUUID as randomUUID9 } from "crypto";
144958
+ import { randomUUID as randomUUID11 } from "crypto";
144127
144959
  import { posix as pathPosix } from "path";
144128
144960
  function rowToFile(row) {
144129
144961
  return {
@@ -144840,7 +145672,7 @@ var init_store = __esm({
144840
145672
  * so an interrupted scan cannot be advertised as complete.
144841
145673
  */
144842
145674
  recordCodeStateSnapshot(input) {
144843
- const id = randomUUID9();
145675
+ const id = randomUUID11();
144844
145676
  let snapshot;
144845
145677
  const tx = this.db.transaction(() => {
144846
145678
  const previous = this.db.prepare(
@@ -144918,7 +145750,7 @@ var init_store = __esm({
144918
145750
 
144919
145751
  // src/codegraph/code-state.ts
144920
145752
  import { execFile as execFile2 } from "child_process";
144921
- import { createHash as createHash6 } from "crypto";
145753
+ import { createHash as createHash7 } from "crypto";
144922
145754
  import { promisify as promisify3 } from "util";
144923
145755
  async function runGit(rootPath, args) {
144924
145756
  try {
@@ -144947,7 +145779,7 @@ function changedPathCount(status) {
144947
145779
  return count2;
144948
145780
  }
144949
145781
  function fingerprint(baseRevision, status) {
144950
- return createHash6("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
145782
+ return createHash7("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
144951
145783
  }
144952
145784
  async function collectCodeStateSnapshot(input) {
144953
145785
  const [revision, status] = await Promise.all([
@@ -145047,11 +145879,11 @@ __export(lite_provider_exports, {
145047
145879
  indexProjectLite: () => indexProjectLite,
145048
145880
  refreshProjectLite: () => refreshProjectLite
145049
145881
  });
145050
- import { createHash as createHash7 } from "crypto";
145882
+ import { createHash as createHash8 } from "crypto";
145051
145883
  import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
145052
145884
  import { join as join4, relative } from "path";
145053
145885
  function hashText(text2) {
145054
- return createHash7("sha256").update(text2).digest("hex");
145886
+ return createHash8("sha256").update(text2).digest("hex");
145055
145887
  }
145056
145888
  function extension2(path22) {
145057
145889
  const index = path22.lastIndexOf(".");
@@ -145618,7 +146450,7 @@ var claim_store_exports = {};
145618
146450
  __export(claim_store_exports, {
145619
146451
  ClaimStore: () => ClaimStore
145620
146452
  });
145621
- import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
146453
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
145622
146454
  function optionalText3(value) {
145623
146455
  return typeof value === "string" && value ? value : void 0;
145624
146456
  }
@@ -145657,7 +146489,7 @@ function rowToEvidence2(row) {
145657
146489
  createdAt: row.createdAt
145658
146490
  };
145659
146491
  }
145660
- function rowToEvent2(row) {
146492
+ function rowToEvent3(row) {
145661
146493
  return {
145662
146494
  id: row.id,
145663
146495
  projectId: row.projectId,
@@ -145671,7 +146503,7 @@ function rowToEvent2(row) {
145671
146503
  };
145672
146504
  }
145673
146505
  function evidenceKey(input) {
145674
- return createHash8("sha256").update(JSON.stringify([
146506
+ return createHash9("sha256").update(JSON.stringify([
145675
146507
  input.evidenceKind,
145676
146508
  input.evidenceId,
145677
146509
  input.relation,
@@ -145802,7 +146634,7 @@ var init_claim_store = __esm({
145802
146634
  locator, capturedHash, evidenceKey, createdAt
145803
146635
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145804
146636
  `).run(
145805
- randomUUID10(),
146637
+ randomUUID12(),
145806
146638
  input.claimId,
145807
146639
  input.evidenceKind,
145808
146640
  input.evidenceId,
@@ -145824,7 +146656,7 @@ var init_claim_store = __esm({
145824
146656
  }
145825
146657
  recordEvent(input) {
145826
146658
  const event = {
145827
- id: randomUUID10(),
146659
+ id: randomUUID12(),
145828
146660
  ...input,
145829
146661
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
145830
146662
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -145852,7 +146684,7 @@ var init_claim_store = __esm({
145852
146684
  SELECT * FROM knowledge_claim_events
145853
146685
  WHERE claimId = ?
145854
146686
  ORDER BY createdAt ASC, id
145855
- `).all(claimId).map(rowToEvent2);
146687
+ `).all(claimId).map(rowToEvent3);
145856
146688
  }
145857
146689
  };
145858
146690
  }
@@ -145869,7 +146701,7 @@ __export(claims_exports, {
145869
146701
  supersedeClaim: () => supersedeClaim,
145870
146702
  writeClaim: () => writeClaim
145871
146703
  });
145872
- import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
146704
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
145873
146705
  function compactText2(value, field) {
145874
146706
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
145875
146707
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
@@ -145881,7 +146713,7 @@ function normalizedTerm(value) {
145881
146713
  return compactText2(value, "identity").toLocaleLowerCase("en-US");
145882
146714
  }
145883
146715
  function hashIdentity(parts) {
145884
- return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
146716
+ return createHash10("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
145885
146717
  }
145886
146718
  function buildClaimIdentity(input) {
145887
146719
  const subject = normalizedTerm(input.subject);
@@ -145955,7 +146787,7 @@ function normalizeClaimInput(input) {
145955
146787
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
145956
146788
  return {
145957
146789
  claim: {
145958
- id: randomUUID11(),
146790
+ id: randomUUID13(),
145959
146791
  projectId,
145960
146792
  subject,
145961
146793
  predicate,
@@ -146122,7 +146954,7 @@ function supersedeClaim(store, input) {
146122
146954
  });
146123
146955
  }
146124
146956
  function hashObservation(observation) {
146125
- return createHash9("sha256").update(JSON.stringify([
146957
+ return createHash10("sha256").update(JSON.stringify([
146126
146958
  observation.title,
146127
146959
  observation.narrative,
146128
146960
  observation.facts,
@@ -146366,11 +147198,11 @@ var init_frontmatter = __esm({
146366
147198
  });
146367
147199
 
146368
147200
  // src/knowledge/markdown.ts
146369
- import { createHash as createHash10 } from "crypto";
147201
+ import { createHash as createHash11 } from "crypto";
146370
147202
  import { promises as fs10 } from "fs";
146371
147203
  import path17 from "path";
146372
147204
  function hashContent(content) {
146373
- return createHash10("sha256").update(content).digest("hex");
147205
+ return createHash11("sha256").update(content).digest("hex");
146374
147206
  }
146375
147207
  function stringField(data, key) {
146376
147208
  const value = data[key];
@@ -146496,11 +147328,11 @@ __export(wiki_exports, {
146496
147328
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
146497
147329
  readKnowledgePage: () => readKnowledgePage
146498
147330
  });
146499
- import { createHash as createHash11 } from "crypto";
147331
+ import { createHash as createHash12 } from "crypto";
146500
147332
  import { promises as fs11 } from "fs";
146501
147333
  import path18 from "path";
146502
147334
  function hash2(value) {
146503
- return createHash11("sha256").update(value).digest("hex");
147335
+ return createHash12("sha256").update(value).digest("hex");
146504
147336
  }
146505
147337
  function now() {
146506
147338
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -147040,7 +147872,7 @@ var init_workflow_sync = __esm({
147040
147872
  });
147041
147873
 
147042
147874
  // src/knowledge/workflow-store.ts
147043
- import { randomUUID as randomUUID12 } from "crypto";
147875
+ import { randomUUID as randomUUID14 } from "crypto";
147044
147876
  function parseJson(value, fallback) {
147045
147877
  if (typeof value !== "string" || !value) return fallback;
147046
147878
  try {
@@ -147140,7 +147972,7 @@ var init_workflow_store = __esm({
147140
147972
  }
147141
147973
  recordRun(input) {
147142
147974
  const run = {
147143
- id: randomUUID12(),
147975
+ id: randomUUID14(),
147144
147976
  workflowId: input.workflowId,
147145
147977
  projectId: input.projectId,
147146
147978
  task: input.task.trim(),
@@ -147208,7 +148040,7 @@ var init_outcome_types = __esm({
147208
148040
  });
147209
148041
 
147210
148042
  // src/knowledge/outcome-store.ts
147211
- import { randomUUID as randomUUID13 } from "crypto";
148043
+ import { randomUUID as randomUUID15 } from "crypto";
147212
148044
  function optionalText5(value) {
147213
148045
  return typeof value === "string" && value.trim() ? value : void 0;
147214
148046
  }
@@ -147252,7 +148084,7 @@ var init_outcome_store = __esm({
147252
148084
  }
147253
148085
  record(input) {
147254
148086
  const signal = {
147255
- id: randomUUID13(),
148087
+ id: randomUUID15(),
147256
148088
  projectId: text(input.projectId, "project id"),
147257
148089
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
147258
148090
  candidateId: text(input.candidateId, "candidate id"),
@@ -147357,11 +148189,11 @@ __export(workflows_exports, {
147357
148189
  syncCanonicalWorkflows: () => syncCanonicalWorkflows,
147358
148190
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
147359
148191
  });
147360
- import { createHash as createHash12 } from "crypto";
148192
+ import { createHash as createHash13 } from "crypto";
147361
148193
  import { promises as fs12 } from "fs";
147362
148194
  import path19 from "path";
147363
148195
  function hash3(value) {
147364
- return createHash12("sha256").update(value).digest("hex");
148196
+ return createHash13("sha256").update(value).digest("hex");
147365
148197
  }
147366
148198
  function now2() {
147367
148199
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -148743,6 +149575,16 @@ async function bindObservationCodeRefsBestEffort(observation) {
148743
149575
  } catch {
148744
149576
  }
148745
149577
  }
149578
+ async function persistEvidenceCard(observation) {
149579
+ if (!projectDir) return;
149580
+ try {
149581
+ const evidence = new EvidenceCardStore();
149582
+ await evidence.init(projectDir);
149583
+ evidence.upsertObservation(observation);
149584
+ } catch (error) {
149585
+ console.error(`[memorix] Evidence card projection failed for obs-${observation.id}: ${error instanceof Error ? error.message : String(error)}`);
149586
+ }
149587
+ }
148746
149588
  function queueClaimDerivation(observation) {
148747
149589
  const dataDir = projectDir;
148748
149590
  const isExplicit = observation.sourceDetail === "explicit";
@@ -149089,6 +149931,7 @@ async function storeObservation(input) {
149089
149931
  }
149090
149932
  if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
149091
149933
  await bindObservationCodeRefsBestEffort(observation);
149934
+ await persistEvidenceCard(observation);
149092
149935
  queueObservationQualification(observation);
149093
149936
  if (resolveObservationVisibility(observation) === "project") {
149094
149937
  queueClaimDerivation(observation);
@@ -149181,6 +150024,7 @@ async function upsertObservation(existing, input, now3) {
149181
150024
  await store.update(existing);
149182
150025
  }
149183
150026
  await bindObservationCodeRefsBestEffort(existing);
150027
+ await persistEvidenceCard(existing);
149184
150028
  queueObservationQualification(existing);
149185
150029
  if (resolveObservationVisibility(existing) === "project") {
149186
150030
  queueClaimDerivation(existing);
@@ -149314,6 +150158,7 @@ async function resolveObservations(ids, status = "resolved") {
149314
150158
  await Promise.all(changed.map((observation) => tx.update(observation)));
149315
150159
  });
149316
150160
  }
150161
+ for (const observation of changed) await persistEvidenceCard(observation);
149317
150162
  return { resolved, notFound };
149318
150163
  }
149319
150164
  function getProjectObservations(projectId) {
@@ -149651,6 +150496,7 @@ var init_observations = __esm({
149651
150496
  init_secret_filter();
149652
150497
  init_lifecycle();
149653
150498
  init_visibility();
150499
+ init_evidence_store();
149654
150500
  observations = [];
149655
150501
  nextId = 1;
149656
150502
  projectDir = null;