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 },
@@ -487,7 +495,7 @@ function closeAllDatabases() {
487
495
  _dbCache.delete(key);
488
496
  }
489
497
  }
490
- 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;
498
+ 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;
491
499
  var init_sqlite_db = __esm({
492
500
  "src/store/sqlite-db.ts"() {
493
501
  "use strict";
@@ -1133,6 +1141,79 @@ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
1133
1141
  created_at TEXT NOT NULL,
1134
1142
  updated_at TEXT NOT NULL
1135
1143
  );
1144
+ `;
1145
+ CREATE_EVIDENCE_CARDS_TABLE = `
1146
+ CREATE TABLE IF NOT EXISTS evidence_cards (
1147
+ id TEXT PRIMARY KEY,
1148
+ project_id TEXT NOT NULL,
1149
+ candidate_kind TEXT NOT NULL,
1150
+ candidate_id TEXT NOT NULL,
1151
+ title TEXT NOT NULL,
1152
+ summary TEXT NOT NULL DEFAULT '',
1153
+ entity_name TEXT,
1154
+ source_kind TEXT NOT NULL,
1155
+ source_ref TEXT NOT NULL,
1156
+ locator TEXT,
1157
+ captured_hash TEXT,
1158
+ session_id TEXT,
1159
+ status TEXT NOT NULL DEFAULT 'active',
1160
+ verification TEXT NOT NULL DEFAULT 'unverified',
1161
+ freshness TEXT NOT NULL DEFAULT 'current',
1162
+ stale_reason TEXT,
1163
+ files_json TEXT NOT NULL DEFAULT '[]',
1164
+ related_entities_json TEXT NOT NULL DEFAULT '[]',
1165
+ created_at TEXT NOT NULL,
1166
+ updated_at TEXT NOT NULL,
1167
+ UNIQUE(project_id, candidate_kind, candidate_id)
1168
+ );
1169
+ `;
1170
+ CREATE_EVIDENCE_CARD_EVENTS_TABLE = `
1171
+ CREATE TABLE IF NOT EXISTS evidence_card_events (
1172
+ id TEXT PRIMARY KEY,
1173
+ project_id TEXT NOT NULL,
1174
+ card_id TEXT NOT NULL,
1175
+ kind TEXT NOT NULL,
1176
+ detail TEXT,
1177
+ created_at TEXT NOT NULL,
1178
+ FOREIGN KEY (card_id) REFERENCES evidence_cards(id) ON DELETE CASCADE
1179
+ );
1180
+ `;
1181
+ CREATE_MEMORY_FEEDBACK_STATES_TABLE = `
1182
+ CREATE TABLE IF NOT EXISTS memory_feedback_states (
1183
+ project_id TEXT NOT NULL,
1184
+ candidate_kind TEXT NOT NULL,
1185
+ candidate_id TEXT NOT NULL,
1186
+ weight REAL NOT NULL DEFAULT 1.0,
1187
+ lifecycle_status TEXT NOT NULL DEFAULT 'active',
1188
+ updated_at TEXT NOT NULL,
1189
+ PRIMARY KEY (project_id, candidate_kind, candidate_id)
1190
+ );
1191
+ `;
1192
+ CREATE_MEMORY_FEEDBACK_EVENTS_TABLE = `
1193
+ CREATE TABLE IF NOT EXISTS memory_feedback_events (
1194
+ id TEXT PRIMARY KEY,
1195
+ project_id TEXT NOT NULL,
1196
+ candidate_kind TEXT NOT NULL,
1197
+ candidate_id TEXT NOT NULL,
1198
+ signal TEXT NOT NULL,
1199
+ actor TEXT,
1200
+ note TEXT,
1201
+ source_ref TEXT NOT NULL,
1202
+ target_event_id TEXT,
1203
+ delta REAL NOT NULL DEFAULT 0,
1204
+ observed_at TEXT NOT NULL
1205
+ );
1206
+ `;
1207
+ CREATE_MCP_BINDINGS_TABLE = `
1208
+ CREATE TABLE IF NOT EXISTS mcp_bindings (
1209
+ handle_id TEXT PRIMARY KEY,
1210
+ project_id TEXT NOT NULL,
1211
+ project_root TEXT NOT NULL,
1212
+ data_dir TEXT NOT NULL,
1213
+ created_at TEXT NOT NULL,
1214
+ last_used_at TEXT NOT NULL,
1215
+ expires_at TEXT
1216
+ );
1136
1217
  `;
1137
1218
  CREATE_INDEXES = `
1138
1219
  CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
@@ -1195,6 +1276,12 @@ CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_target
1195
1276
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
1196
1277
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
1197
1278
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
1279
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC);
1280
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id);
1281
+ CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at);
1282
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC);
1283
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_events_candidate ON memory_feedback_events(project_id, candidate_kind, candidate_id, observed_at DESC);
1284
+ CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC);
1198
1285
  CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1199
1286
  ON maintenance_jobs(project_id, kind, dedupe_key)
1200
1287
  WHERE status IN ('pending', 'running', 'retry');
@@ -1350,6 +1437,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1350
1437
  }
1351
1438
  db2.exec("CREATE INDEX IF NOT EXISTS idx_media_jobs_source_asset ON media_jobs(project_id, source_asset_id, status)");
1352
1439
  }
1440
+ },
1441
+ {
1442
+ id: "1.8-context-control-plane",
1443
+ apply: (db2) => {
1444
+ db2.exec(CREATE_EVIDENCE_CARDS_TABLE);
1445
+ db2.exec(CREATE_EVIDENCE_CARD_EVENTS_TABLE);
1446
+ db2.exec(CREATE_MEMORY_FEEDBACK_STATES_TABLE);
1447
+ db2.exec(CREATE_MEMORY_FEEDBACK_EVENTS_TABLE);
1448
+ db2.exec(CREATE_MCP_BINDINGS_TABLE);
1449
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC)");
1450
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id)");
1451
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at)");
1452
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC)");
1453
+ 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)");
1454
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC)");
1455
+ }
1353
1456
  }
1354
1457
  ];
1355
1458
  _dbCache = /* @__PURE__ */ new Map();
@@ -2819,6 +2922,7 @@ function mergeTomlConfig(base, override) {
2819
2922
  llm: { ...base.memory?.llm, ...override.memory?.llm }
2820
2923
  },
2821
2924
  embedding: { ...base.embedding, ...override.embedding },
2925
+ rerank: { ...base.rerank, ...override.rerank },
2822
2926
  hooks: { ...base.hooks, ...override.hooks },
2823
2927
  git: { ...base.git, ...override.git },
2824
2928
  codegraph: { ...base.codegraph, ...override.codegraph },
@@ -2915,6 +3019,7 @@ __export(resolved_config_exports, {
2915
3019
  getResolvedConfigForCwd: () => getResolvedConfigForCwd,
2916
3020
  getResolvedEmbeddingLane: () => getResolvedEmbeddingLane,
2917
3021
  getResolvedMemoryLane: () => getResolvedMemoryLane,
3022
+ getResolvedRerankLane: () => getResolvedRerankLane,
2918
3023
  resetResolvedConfigCache: () => resetResolvedConfigCache
2919
3024
  });
2920
3025
  import { homedir as homedir5 } from "os";
@@ -2947,6 +3052,16 @@ function getResolvedConfig(options = {}) {
2947
3052
  legacy.llm?.baseUrl
2948
3053
  );
2949
3054
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3055
+ const memoryLlmApiKey = first(
3056
+ process.env.MEMORIX_LLM_API_KEY,
3057
+ process.env.MEMORIX_API_KEY,
3058
+ toml.memory?.llm?.api_key,
3059
+ yaml.llm?.apiKey,
3060
+ legacy.llm?.apiKey,
3061
+ process.env.OPENAI_API_KEY,
3062
+ process.env.ANTHROPIC_API_KEY,
3063
+ openRouterMemoryLlmApiKey
3064
+ );
2950
3065
  const resolved = {
2951
3066
  agent: {
2952
3067
  provider: first(
@@ -2987,16 +3102,7 @@ function getResolvedConfig(options = {}) {
2987
3102
  provider: memoryLlmProvider,
2988
3103
  model: memoryLlmModel,
2989
3104
  baseUrl: memoryLlmBaseUrl,
2990
- apiKey: first(
2991
- process.env.MEMORIX_LLM_API_KEY,
2992
- process.env.MEMORIX_API_KEY,
2993
- toml.memory?.llm?.api_key,
2994
- yaml.llm?.apiKey,
2995
- legacy.llm?.apiKey,
2996
- process.env.OPENAI_API_KEY,
2997
- process.env.ANTHROPIC_API_KEY,
2998
- openRouterMemoryLlmApiKey
2999
- )
3105
+ apiKey: memoryLlmApiKey
3000
3106
  }
3001
3107
  },
3002
3108
  embedding: {
@@ -3006,6 +3112,12 @@ function getResolvedConfig(options = {}) {
3006
3112
  apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
3007
3113
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
3008
3114
  },
3115
+ rerank: resolveRerankLane({
3116
+ toml,
3117
+ yaml,
3118
+ memoryLlmApiKey,
3119
+ memoryLlmBaseUrl
3120
+ }),
3009
3121
  git: {
3010
3122
  autoHook: firstBool(toml.git?.auto_hook, yaml.git?.autoHook),
3011
3123
  ingestOnCommit: firstBool(toml.git?.ingest_on_commit, yaml.git?.ingestOnCommit),
@@ -3075,6 +3187,9 @@ function getResolvedMemoryLane(options = {}) {
3075
3187
  function getResolvedEmbeddingLane(options = {}) {
3076
3188
  return getResolvedConfig(options).embedding;
3077
3189
  }
3190
+ function getResolvedRerankLane(options = {}) {
3191
+ return getResolvedConfig(options).rerank;
3192
+ }
3078
3193
  function resetResolvedConfigCache() {
3079
3194
  }
3080
3195
  function first(...values) {
@@ -3117,6 +3232,10 @@ function getEnvSourceNames() {
3117
3232
  "MEMORIX_EMBEDDING_BASE_URL",
3118
3233
  "MEMORIX_EMBEDDING_MODEL",
3119
3234
  "MEMORIX_EMBEDDING_DIMENSIONS",
3235
+ "MEMORIX_RERANK_PROVIDER",
3236
+ "MEMORIX_RERANK_MODEL",
3237
+ "MEMORIX_RERANK_BASE_URL",
3238
+ "MEMORIX_RERANK_API_KEY",
3120
3239
  "MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
3121
3240
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
3122
3241
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
@@ -3140,6 +3259,63 @@ function normalizeExternalContext(value) {
3140
3259
  if (normalized === "auto" || normalized === "off") return normalized;
3141
3260
  return void 0;
3142
3261
  }
3262
+ function normalizeRerankProvider(value) {
3263
+ const normalized = value?.trim().toLowerCase();
3264
+ if (normalized === "http") return "http";
3265
+ return "off";
3266
+ }
3267
+ function resolveRerankLane(args) {
3268
+ const provider2 = normalizeRerankProvider(first(
3269
+ process.env.MEMORIX_RERANK_PROVIDER,
3270
+ args.toml.rerank?.provider,
3271
+ args.yaml.rerank?.provider,
3272
+ "off"
3273
+ ));
3274
+ const model = first(
3275
+ process.env.MEMORIX_RERANK_MODEL,
3276
+ args.toml.rerank?.model,
3277
+ args.yaml.rerank?.model
3278
+ );
3279
+ const explicitBaseUrl = first(
3280
+ process.env.MEMORIX_RERANK_BASE_URL,
3281
+ args.toml.rerank?.base_url,
3282
+ args.yaml.rerank?.baseUrl
3283
+ );
3284
+ const baseUrl = provider2 === "http" ? first(explicitBaseUrl, args.memoryLlmBaseUrl) : explicitBaseUrl;
3285
+ const dedicatedApiKey = first(
3286
+ process.env.MEMORIX_RERANK_API_KEY,
3287
+ args.toml.rerank?.api_key,
3288
+ args.yaml.rerank?.apiKey
3289
+ );
3290
+ const sameEndpoint = provider2 === "http" && sameTrustedEndpoint(baseUrl, args.memoryLlmBaseUrl);
3291
+ const inheritedApiKey = sameEndpoint ? args.memoryLlmApiKey : void 0;
3292
+ const apiKey = first(dedicatedApiKey, inheritedApiKey);
3293
+ const canSendRequest = provider2 === "http" && Boolean(baseUrl) && (Boolean(apiKey) || !args.memoryLlmApiKey || sameEndpoint);
3294
+ return { provider: provider2, model, baseUrl, apiKey, canSendRequest };
3295
+ }
3296
+ function sameTrustedEndpoint(left, right) {
3297
+ const a = normalizeTrustedEndpoint(left);
3298
+ const b = normalizeTrustedEndpoint(right);
3299
+ return Boolean(a && b && a === b);
3300
+ }
3301
+ function normalizeTrustedEndpoint(value) {
3302
+ const trimmed = value?.trim();
3303
+ if (!trimmed) return void 0;
3304
+ try {
3305
+ const url = new URL(trimmed);
3306
+ const protocol = url.protocol.toLowerCase();
3307
+ const hostname = url.hostname.toLowerCase();
3308
+ const port = url.port;
3309
+ let pathname = url.pathname.replace(/\/+$/, "");
3310
+ if (pathname.toLowerCase().endsWith("/rerank")) {
3311
+ pathname = pathname.slice(0, -"/rerank".length).replace(/\/+$/, "");
3312
+ }
3313
+ return `${protocol}//${hostname}${port ? `:${port}` : ""}${pathname}`;
3314
+ } catch {
3315
+ const fallback = trimmed.replace(/\/+$/, "").replace(/\/rerank$/i, "").replace(/\/+$/, "");
3316
+ return fallback.toLowerCase() || void 0;
3317
+ }
3318
+ }
3143
3319
  var init_resolved_config = __esm({
3144
3320
  "src/config/resolved-config.ts"() {
3145
3321
  "use strict";
@@ -5375,9 +5551,27 @@ __export(provider_exports2, {
5375
5551
  getLLMConfig: () => getLLMConfig,
5376
5552
  initLLM: () => initLLM,
5377
5553
  isLLMEnabled: () => isLLMEnabled,
5554
+ normalizeOpenAICompatibleBaseUrl: () => normalizeOpenAICompatibleBaseUrl,
5378
5555
  parseLLMTimeoutMs: () => parseLLMTimeoutMs,
5379
5556
  setLLMConfig: () => setLLMConfig
5380
5557
  });
5558
+ function normalizeOpenAICompatibleBaseUrl(baseUrl) {
5559
+ const base = baseUrl.trim().replace(/\/+$/, "");
5560
+ if (!base) return base;
5561
+ let pathname = "";
5562
+ try {
5563
+ pathname = new URL(base).pathname.replace(/\/+$/, "");
5564
+ } catch {
5565
+ pathname = base.split(/[?#]/, 1)[0].replace(/\/+$/, "");
5566
+ }
5567
+ const lastSegment = pathname.split("/").filter(Boolean).at(-1);
5568
+ if (lastSegment && OPENAI_API_VERSION_SEGMENT.test(lastSegment)) {
5569
+ return base;
5570
+ }
5571
+ const queryOrHash = base.match(/[?#].*$/)?.[0] ?? "";
5572
+ const pathWithoutQuery = base.slice(0, base.length - queryOrHash.length).replace(/\/+$/, "");
5573
+ return `${pathWithoutQuery}/v1${queryOrHash}`;
5574
+ }
5381
5575
  function parseLLMTimeoutMs(raw) {
5382
5576
  if (raw === void 0 || raw.trim() === "") return LLM_TIMEOUT_DEFAULT_MS;
5383
5577
  const parsed = Number(raw);
@@ -5481,8 +5675,7 @@ async function callLLM(systemPrompt, userMessage, signal) {
5481
5675
  }
5482
5676
  async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5483
5677
  const config = currentConfig;
5484
- let base = config.baseUrl.replace(/\/+$/, "");
5485
- if (!base.endsWith("/v1")) base += "/v1";
5678
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5486
5679
  const url = `${base}/chat/completions`;
5487
5680
  const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5488
5681
  const response = await fetch(url, {
@@ -5561,8 +5754,7 @@ async function callLLMWithTools(messages, tools, signal) {
5561
5754
  }
5562
5755
  async function callOpenAIWithTools(messages, tools, signal) {
5563
5756
  const config = currentConfig;
5564
- let base = config.baseUrl.replace(/\/+$/, "");
5565
- if (!base.endsWith("/v1")) base += "/v1";
5757
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5566
5758
  const url = `${base}/chat/completions`;
5567
5759
  const openaiMessages = messages.map((msg) => {
5568
5760
  if (msg.role === "tool") {
@@ -5712,8 +5904,7 @@ async function callAnthropicWithTools(messages, tools, signal) {
5712
5904
  }
5713
5905
  async function* callOpenAIWithToolsStream(messages, tools) {
5714
5906
  const config = currentConfig;
5715
- let base = config.baseUrl.replace(/\/+$/, "");
5716
- if (!base.endsWith("/v1")) base += "/v1";
5907
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5717
5908
  const url = `${base}/chat/completions`;
5718
5909
  const openaiMessages = messages.map((msg) => {
5719
5910
  if (msg.role === "tool") {
@@ -5961,7 +6152,7 @@ async function* callAnthropicWithToolsStream(messages, tools) {
5961
6152
  }
5962
6153
  };
5963
6154
  }
5964
- var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
6155
+ 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;
5965
6156
  var init_provider2 = __esm({
5966
6157
  "src/llm/provider.ts"() {
5967
6158
  "use strict";
@@ -5971,6 +6162,7 @@ var init_provider2 = __esm({
5971
6162
  LLM_TIMEOUT_MIN_MS = 1e3;
5972
6163
  LLM_TIMEOUT_MAX_MS = 3e5;
5973
6164
  MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
6165
+ OPENAI_API_VERSION_SEGMENT = /^v\d+(?:[a-z][a-z0-9-]*)?$/i;
5974
6166
  LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
5975
6167
  PROVIDER_DEFAULTS = {
5976
6168
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
@@ -6030,6 +6222,48 @@ Rules:
6030
6222
  }
6031
6223
  });
6032
6224
 
6225
+ // src/rerank/policy.ts
6226
+ function parseRerankTimeoutMs(raw) {
6227
+ const value = raw?.trim();
6228
+ if (!value) return DEFAULT_RERANK_TIMEOUT_MS;
6229
+ const parsed = Number(value);
6230
+ if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
6231
+ console.warn(
6232
+ `[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.`
6233
+ );
6234
+ return DEFAULT_RERANK_TIMEOUT_MS;
6235
+ }
6236
+ if (parsed < RERANK_TIMEOUT_MIN_MS) return RERANK_TIMEOUT_MIN_MS;
6237
+ if (parsed > RERANK_TIMEOUT_MAX_MS) return RERANK_TIMEOUT_MAX_MS;
6238
+ return parsed;
6239
+ }
6240
+ function resolveRerankTimeoutMs(explicitTimeoutMs) {
6241
+ if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
6242
+ return explicitTimeoutMs;
6243
+ }
6244
+ return parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
6245
+ }
6246
+ function shouldAttemptHttpRerank(input) {
6247
+ return input.quality === "thorough" && input.hasQuery && input.candidateCount >= MIN_RERANK_CANDIDATES;
6248
+ }
6249
+ function shouldAttemptLlmRerank(input) {
6250
+ if (input.httpRerankConfigured) return false;
6251
+ if (input.quality !== "thorough" || input.tier !== "heavy" || !input.hasQuery) return false;
6252
+ if (input.candidateCount < MIN_RERANK_CANDIDATES) return false;
6253
+ return input.topScore > 0 && input.secondScore / input.topScore > 0.7;
6254
+ }
6255
+ var DEFAULT_RERANK_TIMEOUT_MS, RERANK_TIMEOUT_MIN_MS, RERANK_TIMEOUT_MAX_MS, MIN_RERANK_CANDIDATES;
6256
+ var init_policy = __esm({
6257
+ "src/rerank/policy.ts"() {
6258
+ "use strict";
6259
+ init_esm_shims();
6260
+ DEFAULT_RERANK_TIMEOUT_MS = 3e4;
6261
+ RERANK_TIMEOUT_MIN_MS = 1e3;
6262
+ RERANK_TIMEOUT_MAX_MS = 3e5;
6263
+ MIN_RERANK_CANDIDATES = 3;
6264
+ }
6265
+ });
6266
+
6033
6267
  // src/timeout.ts
6034
6268
  function withTimeout(promise, ms, label) {
6035
6269
  return new Promise((resolve, reject) => {
@@ -6315,6 +6549,148 @@ var init_aliases = __esm({
6315
6549
  }
6316
6550
  });
6317
6551
 
6552
+ // src/rerank/http-provider.ts
6553
+ function buildRerankUrl(baseUrl) {
6554
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
6555
+ if (trimmed.endsWith("/rerank")) return trimmed;
6556
+ return `${trimmed}/rerank`;
6557
+ }
6558
+ function parseRerankResponse(payload) {
6559
+ const rows = extractRows(payload);
6560
+ if (rows.length === 0) {
6561
+ throw new Error("Neural rerank response contained no ranked results");
6562
+ }
6563
+ return rows.sort((a, b) => b.score - a.score);
6564
+ }
6565
+ function extractRows(payload) {
6566
+ if (Array.isArray(payload)) {
6567
+ return payload.map(rowFromUnknown).filter((row) => row !== null);
6568
+ }
6569
+ if (payload && typeof payload === "object" && "results" in payload) {
6570
+ const results = payload.results;
6571
+ if (Array.isArray(results)) {
6572
+ return results.map(rowFromUnknown).filter((row) => row !== null);
6573
+ }
6574
+ }
6575
+ throw new Error("Neural rerank response was not Cohere or TEI shaped");
6576
+ }
6577
+ function rowFromUnknown(value) {
6578
+ if (!value || typeof value !== "object") return null;
6579
+ const row = value;
6580
+ if (typeof row.index !== "number" || !Number.isFinite(row.index)) return null;
6581
+ const score = typeof row.relevance_score === "number" ? row.relevance_score : typeof row.score === "number" ? row.score : null;
6582
+ if (score === null || !Number.isFinite(score)) return null;
6583
+ return { index: row.index, score };
6584
+ }
6585
+ function buildRerankBody(query, documents, model) {
6586
+ const body = {
6587
+ query,
6588
+ documents,
6589
+ top_n: documents.length,
6590
+ return_documents: false
6591
+ };
6592
+ const trimmed = model?.trim();
6593
+ if (trimmed) body.model = trimmed;
6594
+ return body;
6595
+ }
6596
+ async function rerankViaHttp(query, documents, config) {
6597
+ if (documents.length === 0) return [];
6598
+ const url = buildRerankUrl(config.baseUrl);
6599
+ const headers = {
6600
+ "content-type": "application/json"
6601
+ };
6602
+ if (config.apiKey) {
6603
+ headers.authorization = `Bearer ${config.apiKey}`;
6604
+ }
6605
+ const controller = new AbortController();
6606
+ const timeoutMs = resolveRerankTimeoutMs(config.timeoutMs);
6607
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6608
+ try {
6609
+ const fetchImpl = config.fetchImpl ?? fetch;
6610
+ const response = await fetchImpl(url, {
6611
+ method: "POST",
6612
+ headers,
6613
+ signal: controller.signal,
6614
+ body: JSON.stringify(buildRerankBody(query, documents, config.model))
6615
+ });
6616
+ if (!response.ok) {
6617
+ throw new Error(`Neural rerank HTTP ${response.status} from ${url}`);
6618
+ }
6619
+ return parseRerankResponse(await response.json());
6620
+ } finally {
6621
+ clearTimeout(timer);
6622
+ }
6623
+ }
6624
+ var init_http_provider = __esm({
6625
+ "src/rerank/http-provider.ts"() {
6626
+ "use strict";
6627
+ init_esm_shims();
6628
+ init_policy();
6629
+ }
6630
+ });
6631
+
6632
+ // src/rerank/index.ts
6633
+ var rerank_exports = {};
6634
+ __export(rerank_exports, {
6635
+ DEFAULT_RERANK_TIMEOUT_MS: () => DEFAULT_RERANK_TIMEOUT_MS,
6636
+ MIN_RERANK_CANDIDATES: () => MIN_RERANK_CANDIDATES,
6637
+ isNeuralRerankEnabled: () => isNeuralRerankEnabled,
6638
+ neuralRerankCandidates: () => neuralRerankCandidates,
6639
+ parseRerankTimeoutMs: () => parseRerankTimeoutMs,
6640
+ resolveRerankTimeoutMs: () => resolveRerankTimeoutMs,
6641
+ shouldAttemptHttpRerank: () => shouldAttemptHttpRerank,
6642
+ shouldAttemptLlmRerank: () => shouldAttemptLlmRerank
6643
+ });
6644
+ function isNeuralRerankEnabled(options = {}) {
6645
+ const lane = getResolvedRerankLane(options);
6646
+ return lane.provider === "http" && Boolean(lane.baseUrl);
6647
+ }
6648
+ async function neuralRerankCandidates(query, candidates, options = {}) {
6649
+ if (!isNeuralRerankEnabled(options) || candidates.length === 0) return null;
6650
+ const lane = getResolvedRerankLane(options);
6651
+ if (!lane.canSendRequest || !lane.baseUrl) return null;
6652
+ const toRerank = candidates.slice(0, MAX_RERANK);
6653
+ const rest = candidates.slice(MAX_RERANK);
6654
+ const documents = toRerank.map((candidate) => candidateText(candidate));
6655
+ const ranked = await rerankViaHttp(query, documents, {
6656
+ provider: "http",
6657
+ model: lane.model,
6658
+ baseUrl: lane.baseUrl,
6659
+ apiKey: lane.apiKey,
6660
+ timeoutMs: resolveRerankTimeoutMs()
6661
+ });
6662
+ const seen = /* @__PURE__ */ new Set();
6663
+ const reranked = [];
6664
+ for (const row of ranked) {
6665
+ const candidate = toRerank[row.index];
6666
+ if (!candidate || seen.has(row.index)) continue;
6667
+ reranked.push(candidate);
6668
+ seen.add(row.index);
6669
+ }
6670
+ for (const [index, candidate] of toRerank.entries()) {
6671
+ if (!seen.has(index)) reranked.push(candidate);
6672
+ }
6673
+ reranked.push(...rest);
6674
+ return reranked;
6675
+ }
6676
+ function candidateText(candidate) {
6677
+ const narrative = candidate.narrative?.trim();
6678
+ if (narrative) return `${candidate.title} \u2014 ${narrative.slice(0, 200)}`;
6679
+ return candidate.title;
6680
+ }
6681
+ var MAX_RERANK;
6682
+ var init_rerank = __esm({
6683
+ "src/rerank/index.ts"() {
6684
+ "use strict";
6685
+ init_esm_shims();
6686
+ init_resolved_config();
6687
+ init_http_provider();
6688
+ init_policy();
6689
+ init_policy();
6690
+ MAX_RERANK = 10;
6691
+ }
6692
+ });
6693
+
6318
6694
  // src/llm/quality.ts
6319
6695
  var quality_exports = {};
6320
6696
  __export(quality_exports, {
@@ -6354,9 +6730,9 @@ async function rerankResults(query, candidates) {
6354
6730
  if (!isLLMEnabled() || candidates.length <= 2) {
6355
6731
  return { reranked: candidates, usedLLM: false };
6356
6732
  }
6357
- const MAX_RERANK = 10;
6358
- const toRerank = candidates.slice(0, MAX_RERANK);
6359
- const rest = candidates.slice(MAX_RERANK);
6733
+ const MAX_RERANK2 = 10;
6734
+ const toRerank = candidates.slice(0, MAX_RERANK2);
6735
+ const rest = candidates.slice(MAX_RERANK2);
6360
6736
  try {
6361
6737
  const candidateList = toRerank.map(
6362
6738
  (c) => `[ID: ${c.id}] (${c.type}) ${c.title}${c.narrative ? ` \u2014 ${c.narrative.substring(0, 100)}` : ""}`
@@ -7249,46 +7625,78 @@ async function searchObservations(options) {
7249
7625
  }
7250
7626
  }
7251
7627
  }
7252
- const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
7253
- const top = intermediate[0]?.score ?? 0;
7254
- const second = intermediate[1]?.score ?? 0;
7255
- return top > 0 && second / top > 0.7;
7256
- })();
7257
- if (shouldRerank) {
7258
- try {
7259
- const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7260
- const narrativeMap = /* @__PURE__ */ new Map();
7261
- for (const hit of results.hits) {
7262
- const doc = hit.document;
7263
- narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7264
- }
7265
- const RERANK_TOP_K = 5;
7266
- const toRerank = intermediate.slice(0, RERANK_TOP_K);
7267
- const candidates = toRerank.map((e2, index) => ({
7268
- id: `r${index + 1}`,
7269
- title: e2.title,
7270
- type: e2.type,
7271
- score: e2.score,
7272
- narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7273
- }));
7274
- const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
7275
- const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
7276
- const { reranked, usedLLM } = await withTimeout(
7277
- rerankResults2(originalQuery, candidates),
7278
- RERANK_TIMEOUT_MS,
7279
- "LLM rerank"
7280
- );
7281
- mark(`rerank(usedLLM=${usedLLM})`);
7282
- if (usedLLM) {
7283
- lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7284
- const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7285
- const rerankedTop = reranked.map((r2) => candidateMap.get(r2.id)).filter((e2) => e2 != null);
7286
- if (rerankedTop.length > 0) {
7287
- intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7628
+ const httpGate = shouldAttemptHttpRerank({
7629
+ quality,
7630
+ hasQuery: Boolean(hasQuery),
7631
+ candidateCount: intermediate.length
7632
+ });
7633
+ const llmGateWithoutHttp = shouldAttemptLlmRerank({
7634
+ quality,
7635
+ tier,
7636
+ hasQuery: Boolean(hasQuery),
7637
+ candidateCount: intermediate.length,
7638
+ topScore: intermediate[0]?.score ?? 0,
7639
+ secondScore: intermediate[1]?.score ?? 0,
7640
+ httpRerankConfigured: false
7641
+ });
7642
+ if (httpGate || llmGateWithoutHttp) {
7643
+ const narrativeMap = /* @__PURE__ */ new Map();
7644
+ for (const hit of results.hits) {
7645
+ const doc = hit.document;
7646
+ narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7647
+ }
7648
+ const RERANK_TOP_K = 5;
7649
+ const toRerank = intermediate.slice(0, RERANK_TOP_K);
7650
+ const candidates = toRerank.map((e2, index) => ({
7651
+ id: `r${index + 1}`,
7652
+ title: e2.title,
7653
+ type: e2.type,
7654
+ score: e2.score,
7655
+ narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7656
+ }));
7657
+ const RERANK_TIMEOUT_MS = parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
7658
+ const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7659
+ const { isNeuralRerankEnabled: isNeuralRerankEnabled2, neuralRerankCandidates: neuralRerankCandidates2 } = await Promise.resolve().then(() => (init_rerank(), rerank_exports));
7660
+ const httpConfigured = isNeuralRerankEnabled2();
7661
+ if (httpConfigured && httpGate) {
7662
+ try {
7663
+ const neural = await withTimeout(
7664
+ neuralRerankCandidates2(originalQuery, candidates),
7665
+ RERANK_TIMEOUT_MS,
7666
+ "neural rerank"
7667
+ );
7668
+ if (neural && neural.length > 0) {
7669
+ const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7670
+ if (rerankedTop.length > 0) {
7671
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7672
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
7673
+ mark("rerank(usedNeural=true)");
7674
+ }
7288
7675
  }
7676
+ } catch {
7677
+ console.error("[memorix] neural rerank failed or timed out; keeping original order");
7289
7678
  }
7290
- } catch (error) {
7291
- console.error("[memorix] LLM rerank failed or timed out, using original order");
7679
+ } else if (!httpConfigured && llmGateWithoutHttp) {
7680
+ try {
7681
+ const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7682
+ const { reranked, usedLLM } = await withTimeout(
7683
+ rerankResults2(originalQuery, candidates),
7684
+ RERANK_TIMEOUT_MS,
7685
+ "LLM rerank"
7686
+ );
7687
+ mark(`rerank(usedLLM=${usedLLM})`);
7688
+ if (usedLLM) {
7689
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7690
+ const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7691
+ if (rerankedTop.length > 0) {
7692
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7693
+ }
7694
+ }
7695
+ } catch {
7696
+ console.error("[memorix] LLM rerank failed or timed out, using original order");
7697
+ }
7698
+ } else {
7699
+ mark(`rerank(skipped,tier=${tier})`);
7292
7700
  }
7293
7701
  } else {
7294
7702
  mark(`rerank(skipped,tier=${tier})`);
@@ -7469,6 +7877,7 @@ var init_orama_store = __esm({
7469
7877
  init_project_affinity();
7470
7878
  init_intent_detector();
7471
7879
  init_query_expansion();
7880
+ init_policy();
7472
7881
  init_timeout();
7473
7882
  db = null;
7474
7883
  dbInitPromise = null;
@@ -8156,8 +8565,228 @@ var init_lifecycle = __esm({
8156
8565
  }
8157
8566
  });
8158
8567
 
8568
+ // src/store/evidence-store.ts
8569
+ import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
8570
+ function safeJson(value, fallback) {
8571
+ if (typeof value !== "string" || value.length === 0) return fallback;
8572
+ try {
8573
+ return JSON.parse(value);
8574
+ } catch {
8575
+ return fallback;
8576
+ }
8577
+ }
8578
+ function rowToCard(row) {
8579
+ return {
8580
+ id: String(row.id),
8581
+ projectId: String(row.project_id),
8582
+ candidateKind: row.candidate_kind,
8583
+ candidateId: String(row.candidate_id),
8584
+ title: String(row.title),
8585
+ summary: String(row.summary ?? ""),
8586
+ ...row.entity_name ? { entityName: String(row.entity_name) } : {},
8587
+ sourceKind: String(row.source_kind),
8588
+ sourceRef: String(row.source_ref),
8589
+ ...row.locator ? { locator: String(row.locator) } : {},
8590
+ ...row.captured_hash ? { capturedHash: String(row.captured_hash) } : {},
8591
+ ...row.session_id ? { sessionId: String(row.session_id) } : {},
8592
+ status: String(row.status ?? "active"),
8593
+ verification: row.verification,
8594
+ freshness: row.freshness,
8595
+ ...row.stale_reason ? { staleReason: String(row.stale_reason) } : {},
8596
+ files: safeJson(row.files_json, []),
8597
+ relatedEntities: safeJson(row.related_entities_json, []),
8598
+ createdAt: String(row.created_at),
8599
+ updatedAt: String(row.updated_at)
8600
+ };
8601
+ }
8602
+ function observationHash(observation) {
8603
+ return createHash3("sha256").update(JSON.stringify({
8604
+ projectId: observation.projectId,
8605
+ id: observation.id,
8606
+ title: observation.title,
8607
+ narrative: observation.narrative,
8608
+ facts: observation.facts,
8609
+ files: observation.filesModified,
8610
+ relatedEntities: observation.relatedEntities
8611
+ })).digest("hex");
8612
+ }
8613
+ function sourceKind(observation) {
8614
+ if (observation.source === "git" || observation.sourceDetail === "git-ingest") return "git";
8615
+ if (observation.sourceDetail === "hook") return "hook";
8616
+ return observation.source ?? "agent";
8617
+ }
8618
+ var EvidenceCardStore;
8619
+ var init_evidence_store = __esm({
8620
+ "src/store/evidence-store.ts"() {
8621
+ "use strict";
8622
+ init_esm_shims();
8623
+ init_sqlite_db();
8624
+ EvidenceCardStore = class {
8625
+ db = null;
8626
+ async init(dataDir) {
8627
+ this.db = getDatabase(dataDir);
8628
+ }
8629
+ requireDb() {
8630
+ if (!this.db) throw new Error("EvidenceCardStore is not initialized.");
8631
+ return this.db;
8632
+ }
8633
+ upsertObservation(observation) {
8634
+ const db2 = this.requireDb();
8635
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8636
+ const card = {
8637
+ id: `evidence:observation:${observation.projectId}:${observation.id}`,
8638
+ projectId: observation.projectId,
8639
+ candidateKind: "observation",
8640
+ candidateId: String(observation.id),
8641
+ title: observation.title,
8642
+ summary: observation.narrative.slice(0, 500),
8643
+ entityName: observation.entityName,
8644
+ sourceKind: sourceKind(observation),
8645
+ sourceRef: `observation:${observation.id}`,
8646
+ locator: observation.filesModified?.join(", ") || `observation:${observation.id}`,
8647
+ capturedHash: observationHash(observation),
8648
+ ...observation.sessionId ? { sessionId: observation.sessionId } : {},
8649
+ status: observation.status ?? "active",
8650
+ verification: observation.status === "resolved" ? "verified" : "unverified",
8651
+ freshness: "current",
8652
+ files: observation.filesModified ?? [],
8653
+ relatedEntities: observation.relatedEntities ?? [],
8654
+ createdAt: observation.createdAt,
8655
+ updatedAt: now3
8656
+ };
8657
+ db2.prepare(`
8658
+ INSERT INTO evidence_cards (
8659
+ id, project_id, candidate_kind, candidate_id, title, summary, entity_name,
8660
+ source_kind, source_ref, locator, captured_hash, session_id, status,
8661
+ verification, freshness, stale_reason, files_json, related_entities_json,
8662
+ created_at, updated_at
8663
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
8664
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
8665
+ title = excluded.title,
8666
+ summary = excluded.summary,
8667
+ entity_name = excluded.entity_name,
8668
+ source_kind = excluded.source_kind,
8669
+ source_ref = excluded.source_ref,
8670
+ locator = excluded.locator,
8671
+ captured_hash = excluded.captured_hash,
8672
+ session_id = excluded.session_id,
8673
+ status = excluded.status,
8674
+ freshness = 'current',
8675
+ stale_reason = NULL,
8676
+ files_json = excluded.files_json,
8677
+ related_entities_json = excluded.related_entities_json,
8678
+ updated_at = excluded.updated_at
8679
+ `).run(
8680
+ card.id,
8681
+ card.projectId,
8682
+ card.candidateKind,
8683
+ card.candidateId,
8684
+ card.title,
8685
+ card.summary,
8686
+ card.entityName ?? null,
8687
+ card.sourceKind,
8688
+ card.sourceRef,
8689
+ card.locator ?? null,
8690
+ card.capturedHash ?? null,
8691
+ card.sessionId ?? null,
8692
+ card.status,
8693
+ card.verification,
8694
+ card.freshness,
8695
+ JSON.stringify(card.files),
8696
+ JSON.stringify(card.relatedEntities),
8697
+ card.createdAt,
8698
+ card.updatedAt
8699
+ );
8700
+ return this.get(card.projectId, card.candidateKind, card.candidateId) ?? card;
8701
+ }
8702
+ get(projectId, candidateKind, candidateId) {
8703
+ const row = this.requireDb().prepare(`
8704
+ SELECT * FROM evidence_cards WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
8705
+ `).get(projectId, candidateKind, candidateId);
8706
+ return row ? rowToCard(row) : void 0;
8707
+ }
8708
+ list(projectId, options = {}) {
8709
+ const limit2 = Math.min(100, Math.max(1, Math.floor(options.limit ?? 20)));
8710
+ const clauses = ["project_id = ?"];
8711
+ const args = [projectId];
8712
+ if (options.freshness) {
8713
+ clauses.push("freshness = ?");
8714
+ args.push(options.freshness);
8715
+ }
8716
+ if (options.status) {
8717
+ clauses.push("status = ?");
8718
+ args.push(options.status);
8719
+ }
8720
+ args.push(limit2);
8721
+ const rows = this.requireDb().prepare(`
8722
+ SELECT * FROM evidence_cards WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC, id DESC LIMIT ?
8723
+ `).all(...args);
8724
+ return rows.map(rowToCard);
8725
+ }
8726
+ syncObservations(observations2) {
8727
+ return observations2.map((observation) => this.upsertObservation(observation));
8728
+ }
8729
+ markStaleForPaths(projectId, paths, reason = "source file changed") {
8730
+ const normalized = new Set(paths.map((value) => value.replace(/\\/g, "/").toLowerCase()));
8731
+ if (normalized.size === 0) return 0;
8732
+ const db2 = this.requireDb();
8733
+ const rows = db2.prepare(`SELECT * FROM evidence_cards WHERE project_id = ? AND freshness != 'stale'`).all(projectId);
8734
+ const update2 = db2.prepare(`
8735
+ UPDATE evidence_cards SET freshness = 'stale', stale_reason = ?, updated_at = ?
8736
+ WHERE id = ?
8737
+ `);
8738
+ let changed = 0;
8739
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8740
+ const run = db2.transaction(() => {
8741
+ for (const row of rows) {
8742
+ const files = safeJson(row.files_json, []);
8743
+ if (!files.some((file) => normalized.has(file.replace(/\\/g, "/").toLowerCase()))) continue;
8744
+ update2.run(reason, now3, row.id);
8745
+ changed++;
8746
+ }
8747
+ });
8748
+ run();
8749
+ return changed;
8750
+ }
8751
+ setVerification(projectId, cardId, verification) {
8752
+ this.requireDb().prepare(`
8753
+ UPDATE evidence_cards SET verification = ?, updated_at = ? WHERE project_id = ? AND id = ?
8754
+ `).run(verification, (/* @__PURE__ */ new Date()).toISOString(), projectId, cardId);
8755
+ }
8756
+ recordEvent(projectId, cardId, kind, detail) {
8757
+ const event = {
8758
+ id: randomUUID4(),
8759
+ projectId,
8760
+ cardId,
8761
+ kind,
8762
+ ...detail ? { detail: detail.slice(0, 2e3) } : {},
8763
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
8764
+ };
8765
+ this.requireDb().prepare(`
8766
+ INSERT INTO evidence_card_events (id, project_id, card_id, kind, detail, created_at)
8767
+ VALUES (?, ?, ?, ?, ?, ?)
8768
+ `).run(event.id, event.projectId, event.cardId, event.kind, event.detail ?? null, event.createdAt);
8769
+ return event;
8770
+ }
8771
+ listEvents(cardId, limit2 = 50) {
8772
+ const rows = this.requireDb().prepare(`
8773
+ SELECT * FROM evidence_card_events WHERE card_id = ? ORDER BY created_at DESC LIMIT ?
8774
+ `).all(cardId, Math.min(200, Math.max(1, Math.floor(limit2))));
8775
+ return rows.map((row) => ({
8776
+ id: String(row.id),
8777
+ projectId: String(row.project_id),
8778
+ cardId: String(row.card_id),
8779
+ kind: String(row.kind),
8780
+ ...row.detail ? { detail: String(row.detail) } : {},
8781
+ createdAt: String(row.created_at)
8782
+ }));
8783
+ }
8784
+ };
8785
+ }
8786
+ });
8787
+
8159
8788
  // src/knowledge/workspace-store.ts
8160
- import { randomUUID as randomUUID4 } from "crypto";
8789
+ import { randomUUID as randomUUID5 } from "crypto";
8161
8790
  function arrayValue(raw) {
8162
8791
  if (typeof raw !== "string" || !raw) return [];
8163
8792
  try {
@@ -8298,7 +8927,7 @@ var init_workspace_store = __esm({
8298
8927
  }
8299
8928
  createProposal(input) {
8300
8929
  const proposal = {
8301
- id: input.id ?? randomUUID4(),
8930
+ id: input.id ?? randomUUID5(),
8302
8931
  ...input,
8303
8932
  status: input.status ?? "pending",
8304
8933
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -8351,13 +8980,13 @@ __export(workspace_exports, {
8351
8980
  loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
8352
8981
  resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
8353
8982
  });
8354
- import { createHash as createHash3 } from "crypto";
8983
+ import { createHash as createHash4 } from "crypto";
8355
8984
  import { execFile } from "child_process";
8356
8985
  import { promises as fs6 } from "fs";
8357
8986
  import path9 from "path";
8358
8987
  import { promisify } from "util";
8359
8988
  function hash(value) {
8360
- return createHash3("sha256").update(value).digest("hex");
8989
+ return createHash4("sha256").update(value).digest("hex");
8361
8990
  }
8362
8991
  function workspaceId(projectId, mode) {
8363
8992
  return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
@@ -8964,14 +9593,14 @@ var init_types2 = __esm({
8964
9593
  });
8965
9594
 
8966
9595
  // src/media/asset-store.ts
8967
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
9596
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
8968
9597
  import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
8969
9598
  import path10 from "path";
8970
9599
  function mediaRoot(dataDir) {
8971
9600
  return path10.resolve(dataDir, "media");
8972
9601
  }
8973
9602
  function projectStorageKey(projectId) {
8974
- return createHash4("sha256").update(projectId).digest("hex").slice(0, 16);
9603
+ return createHash5("sha256").update(projectId).digest("hex").slice(0, 16);
8975
9604
  }
8976
9605
  function validateUnderRoot(root, candidate) {
8977
9606
  const resolved = path10.resolve(candidate);
@@ -9035,7 +9664,7 @@ async function destinationExists(destination) {
9035
9664
  async function persistBuffer(bytes, destination) {
9036
9665
  await mkdir3(path10.dirname(destination), { recursive: true });
9037
9666
  if (await destinationExists(destination)) return false;
9038
- const temporary = `${destination}.${randomUUID5()}.tmp`;
9667
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
9039
9668
  try {
9040
9669
  await writeFile3(temporary, bytes, { flag: "wx" });
9041
9670
  await rename2(temporary, destination);
@@ -9080,7 +9709,7 @@ async function importMediaBuffer(input) {
9080
9709
  if (input.bytes.length === 0) throw new Error("Media import rejects empty payloads");
9081
9710
  if (input.bytes.length > maxBytes) throw new Error(`Media payload is too large (${input.bytes.length} bytes; limit ${maxBytes})`);
9082
9711
  const detected = detectMedia(input.bytes.subarray(0, 32));
9083
- const sha256 = createHash4("sha256").update(input.bytes).digest("hex");
9712
+ const sha256 = createHash5("sha256").update(input.bytes).digest("hex");
9084
9713
  return importKnownMedia({
9085
9714
  dataDir: input.dataDir,
9086
9715
  projectId: input.projectId,
@@ -9098,7 +9727,7 @@ async function readMediaAsset(dataDir, asset, maxBytes = DEFAULT_MAX_MEDIA_BYTES
9098
9727
  if (asset.byteSize > maxBytes) throw new Error(`Media asset exceeds read limit (${asset.byteSize} bytes; limit ${maxBytes})`);
9099
9728
  const bytes = await readFile3(getMediaAssetPath(dataDir, asset));
9100
9729
  if (bytes.length !== asset.byteSize) throw new Error(`Media asset size no longer matches metadata: ${asset.id}`);
9101
- const sha256 = createHash4("sha256").update(bytes).digest("hex");
9730
+ const sha256 = createHash5("sha256").update(bytes).digest("hex");
9102
9731
  if (sha256 !== asset.sha256) throw new Error(`Media asset hash no longer matches metadata: ${asset.id}`);
9103
9732
  return bytes;
9104
9733
  }
@@ -61849,7 +62478,7 @@ var require_gaxios = __commonJS({
61849
62478
  var retry_js_1 = require_retry3();
61850
62479
  var stream_1 = __require("stream");
61851
62480
  var interceptor_js_1 = require_interceptor();
61852
- var randomUUID14 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
62481
+ var randomUUID16 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
61853
62482
  var HTTP_STATUS_NO_CONTENT = 204;
61854
62483
  var Gaxios = class {
61855
62484
  agentCache = /* @__PURE__ */ new Map();
@@ -62122,7 +62751,7 @@ var require_gaxios = __commonJS({
62122
62751
  */
62123
62752
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
62124
62753
  if (opts.multipart?.length) {
62125
- const boundary = await randomUUID14();
62754
+ const boundary = await randomUUID16();
62126
62755
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
62127
62756
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
62128
62757
  } else if (shouldDirectlyPassData) {
@@ -74349,7 +74978,7 @@ var require_websocket = __commonJS({
74349
74978
  var http3 = __require("http");
74350
74979
  var net = __require("net");
74351
74980
  var tls = __require("tls");
74352
- var { randomBytes, createHash: createHash13 } = __require("crypto");
74981
+ var { randomBytes, createHash: createHash14 } = __require("crypto");
74353
74982
  var { Duplex, Readable: Readable2 } = __require("stream");
74354
74983
  var { URL: URL2 } = __require("url");
74355
74984
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -75017,7 +75646,7 @@ var require_websocket = __commonJS({
75017
75646
  abortHandshake(websocket, socket, "Invalid Upgrade header");
75018
75647
  return;
75019
75648
  }
75020
- const digest2 = createHash13("sha1").update(key + GUID).digest("base64");
75649
+ const digest2 = createHash14("sha1").update(key + GUID).digest("base64");
75021
75650
  if (res.headers["sec-websocket-accept"] !== digest2) {
75022
75651
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
75023
75652
  return;
@@ -75389,7 +76018,7 @@ var require_websocket_server = __commonJS({
75389
76018
  var EventEmitter = __require("events");
75390
76019
  var http3 = __require("http");
75391
76020
  var { Duplex } = __require("stream");
75392
- var { createHash: createHash13 } = __require("crypto");
76021
+ var { createHash: createHash14 } = __require("crypto");
75393
76022
  var extension3 = require_extension();
75394
76023
  var PerMessageDeflate2 = require_permessage_deflate();
75395
76024
  var subprotocol2 = require_subprotocol();
@@ -75696,7 +76325,7 @@ var require_websocket_server = __commonJS({
75696
76325
  );
75697
76326
  }
75698
76327
  if (this._state > RUNNING) return abortHandshake(socket, 503);
75699
- const digest2 = createHash13("sha1").update(key + GUID).digest("base64");
76328
+ const digest2 = createHash14("sha1").update(key + GUID).digest("base64");
75700
76329
  const headers = [
75701
76330
  "HTTP/1.1 101 Switching Protocols",
75702
76331
  "Upgrade: websocket",
@@ -142540,6 +143169,186 @@ var init_video_jobs = __esm({
142540
143169
  }
142541
143170
  });
142542
143171
 
143172
+ // src/memory/feedback-model.ts
143173
+ function feedbackWeightMultiplier(weight) {
143174
+ return Math.max(0.35, Math.min(1.4, 0.35 + Math.max(0, Math.min(2, weight)) * 0.525));
143175
+ }
143176
+ var init_feedback_model = __esm({
143177
+ "src/memory/feedback-model.ts"() {
143178
+ "use strict";
143179
+ init_esm_shims();
143180
+ }
143181
+ });
143182
+
143183
+ // src/memory/feedback.ts
143184
+ var feedback_exports = {};
143185
+ __export(feedback_exports, {
143186
+ FEEDBACK_DELTAS: () => FEEDBACK_DELTAS,
143187
+ MemoryFeedbackStore: () => MemoryFeedbackStore,
143188
+ applyMemoryFeedback: () => applyMemoryFeedback,
143189
+ feedbackWeightMultiplier: () => feedbackWeightMultiplier
143190
+ });
143191
+ import { randomUUID as randomUUID7 } from "crypto";
143192
+ function applyMemoryFeedback(current, event) {
143193
+ const delta = event.signal === "revoke" ? 0 : FEEDBACK_DELTAS[event.signal];
143194
+ const weight = Math.max(0, Math.min(2, current.weight + delta));
143195
+ const status = (event.signal === "code-conflict" || event.signal === "user-correction" || event.signal === "weaken") && weight < 0.5 ? "archived" : current.status;
143196
+ return { weight, status, audit: [...current.audit, { ...event }] };
143197
+ }
143198
+ function rowToEvent(row) {
143199
+ return {
143200
+ id: String(row.id),
143201
+ projectId: String(row.project_id),
143202
+ candidateKind: row.candidate_kind,
143203
+ candidateId: String(row.candidate_id),
143204
+ signal: row.signal,
143205
+ ...row.observed_at ? { at: String(row.observed_at) } : { at: (/* @__PURE__ */ new Date()).toISOString() },
143206
+ ...row.actor ? { actor: String(row.actor) } : {},
143207
+ ...row.note ? { note: String(row.note) } : {},
143208
+ ...row.source_ref ? { sourceRef: String(row.source_ref) } : {},
143209
+ ...row.target_event_id ? { targetEventId: String(row.target_event_id) } : {},
143210
+ delta: Number(row.delta ?? 0)
143211
+ };
143212
+ }
143213
+ var FEEDBACK_DELTAS, MemoryFeedbackStore;
143214
+ var init_feedback = __esm({
143215
+ "src/memory/feedback.ts"() {
143216
+ "use strict";
143217
+ init_esm_shims();
143218
+ init_sqlite_db();
143219
+ init_feedback_model();
143220
+ FEEDBACK_DELTAS = {
143221
+ "user-correction": -0.35,
143222
+ "verification-success": 0.25,
143223
+ "verification-failure": -0.25,
143224
+ "code-change": -0.15,
143225
+ used: 0.1,
143226
+ "not-used": -0.05,
143227
+ "code-conflict": -0.4,
143228
+ strengthen: 0.2,
143229
+ weaken: -0.2
143230
+ };
143231
+ MemoryFeedbackStore = class {
143232
+ db = null;
143233
+ async init(dataDir) {
143234
+ this.db = getDatabase(dataDir);
143235
+ }
143236
+ requireDb() {
143237
+ if (!this.db) throw new Error("MemoryFeedbackStore is not initialized.");
143238
+ return this.db;
143239
+ }
143240
+ loadEvents(projectId, candidateKind, candidateId) {
143241
+ return this.requireDb().prepare(`
143242
+ SELECT * FROM memory_feedback_events
143243
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
143244
+ ORDER BY observed_at ASC, rowid ASC
143245
+ `).all(projectId, candidateKind, candidateId).map(rowToEvent);
143246
+ }
143247
+ rebuild(projectId, candidateKind, candidateId) {
143248
+ const events = this.loadEvents(projectId, candidateKind, candidateId);
143249
+ const revoked = new Set(events.filter((event) => event.signal === "revoke" && event.targetEventId).map((event) => event.targetEventId));
143250
+ let state = { weight: 1, status: "active", audit: [] };
143251
+ for (const event of events) {
143252
+ if (event.signal === "revoke" || revoked.has(event.id)) {
143253
+ state.audit.push(event);
143254
+ continue;
143255
+ }
143256
+ state = applyMemoryFeedback(state, event);
143257
+ }
143258
+ this.requireDb().prepare(`
143259
+ INSERT INTO memory_feedback_states (project_id, candidate_kind, candidate_id, weight, lifecycle_status, updated_at)
143260
+ VALUES (?, ?, ?, ?, ?, ?)
143261
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
143262
+ weight = excluded.weight, lifecycle_status = excluded.lifecycle_status, updated_at = excluded.updated_at
143263
+ `).run(projectId, candidateKind, candidateId, state.weight, state.status, (/* @__PURE__ */ new Date()).toISOString());
143264
+ return state;
143265
+ }
143266
+ getState(projectId, candidateKind, candidateId) {
143267
+ const row = this.requireDb().prepare(`
143268
+ SELECT weight, lifecycle_status FROM memory_feedback_states
143269
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
143270
+ `).get(projectId, candidateKind, candidateId);
143271
+ if (!row) return { weight: 1, status: "active", audit: [] };
143272
+ return {
143273
+ weight: Number(row.weight ?? 1),
143274
+ status: row.lifecycle_status ?? "active",
143275
+ audit: this.loadEvents(projectId, candidateKind, candidateId)
143276
+ };
143277
+ }
143278
+ listStates(projectId, candidateKind = "observation") {
143279
+ return this.requireDb().prepare(`
143280
+ SELECT candidate_id, weight, lifecycle_status, updated_at
143281
+ FROM memory_feedback_states WHERE project_id = ? AND candidate_kind = ?
143282
+ ORDER BY updated_at DESC
143283
+ `).all(projectId, candidateKind).map((row) => ({
143284
+ candidateId: String(row.candidate_id),
143285
+ weight: Number(row.weight),
143286
+ status: row.lifecycle_status,
143287
+ updatedAt: String(row.updated_at)
143288
+ }));
143289
+ }
143290
+ weights(projectId, candidateKind = "observation") {
143291
+ const states = /* @__PURE__ */ new Map();
143292
+ for (const row of this.listStates(projectId, candidateKind)) {
143293
+ states.set(row.candidateId, this.getState(projectId, candidateKind, row.candidateId));
143294
+ }
143295
+ return states;
143296
+ }
143297
+ record(input) {
143298
+ if (input.signal === "revoke" && !input.targetEventId) {
143299
+ throw new Error("A revoke feedback event requires targetEventId.");
143300
+ }
143301
+ if (input.signal === "revoke") {
143302
+ const target = this.requireDb().prepare(`
143303
+ SELECT 1 FROM memory_feedback_events
143304
+ WHERE id = ? AND project_id = ? AND candidate_kind = ? AND candidate_id = ?
143305
+ `).get(input.targetEventId, input.projectId, input.candidateKind, input.candidateId);
143306
+ if (!target) throw new Error("The feedback event to revoke was not found in the same candidate scope.");
143307
+ }
143308
+ const event = {
143309
+ id: randomUUID7(),
143310
+ projectId: input.projectId,
143311
+ candidateKind: input.candidateKind,
143312
+ candidateId: input.candidateId,
143313
+ signal: input.signal,
143314
+ sourceRef: input.sourceRef,
143315
+ ...input.actor ? { actor: input.actor } : {},
143316
+ ...input.note ? { note: input.note.slice(0, 2e3) } : {},
143317
+ ...input.targetEventId ? { targetEventId: input.targetEventId } : {},
143318
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
143319
+ delta: input.signal === "revoke" ? 0 : FEEDBACK_DELTAS[input.signal]
143320
+ };
143321
+ this.requireDb().prepare(`
143322
+ INSERT INTO memory_feedback_events (
143323
+ id, project_id, candidate_kind, candidate_id, signal, actor, note,
143324
+ source_ref, target_event_id, delta, observed_at
143325
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
143326
+ `).run(
143327
+ event.id,
143328
+ event.projectId,
143329
+ event.candidateKind,
143330
+ event.candidateId,
143331
+ event.signal,
143332
+ event.actor ?? null,
143333
+ event.note ?? null,
143334
+ event.sourceRef,
143335
+ event.targetEventId ?? null,
143336
+ event.delta,
143337
+ event.at
143338
+ );
143339
+ return { event, state: this.rebuild(input.projectId, input.candidateKind, input.candidateId) };
143340
+ }
143341
+ audit(projectId, candidateKind, candidateId, limit2 = 100) {
143342
+ return this.requireDb().prepare(`
143343
+ SELECT * FROM memory_feedback_events
143344
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
143345
+ ORDER BY observed_at DESC, rowid DESC LIMIT ?
143346
+ `).all(projectId, candidateKind, candidateId, Math.min(200, Math.max(1, Math.floor(limit2)))).map(rowToEvent);
143347
+ }
143348
+ };
143349
+ }
143350
+ });
143351
+
142543
143352
  // src/memory/retention.ts
142544
143353
  var retention_exports = {};
142545
143354
  __export(retention_exports, {
@@ -142581,6 +143390,7 @@ function getEffectiveRetentionDays(doc) {
142581
143390
  }
142582
143391
  function isImmune(doc) {
142583
143392
  if (doc.type === "probe") return false;
143393
+ if (doc.feedbackStatus === "archived") return false;
142584
143394
  if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
142585
143395
  if (doc.valueCategory === "core") return true;
142586
143396
  const importance = getImportanceLevel(doc);
@@ -142613,7 +143423,8 @@ function calculateRelevance(doc, referenceTime) {
142613
143423
  const decayFactor = Math.exp(-ageDays / retention);
142614
143424
  const accessCount = doc.accessCount ?? 0;
142615
143425
  const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
142616
- let totalScore = base * decayFactor * accessBoost;
143426
+ const feedbackMultiplier = doc.feedbackWeight === void 0 ? 1 : feedbackWeightMultiplier(doc.feedbackWeight);
143427
+ let totalScore = base * decayFactor * accessBoost * feedbackMultiplier;
142617
143428
  const immune = isImmune(doc);
142618
143429
  if (immune) {
142619
143430
  totalScore = Math.max(totalScore, 0.5);
@@ -142625,7 +143436,8 @@ function calculateRelevance(doc, referenceTime) {
142625
143436
  decayFactor,
142626
143437
  accessBoost,
142627
143438
  ageDays,
142628
- isImmune: immune
143439
+ isImmune: immune,
143440
+ feedbackMultiplier
142629
143441
  };
142630
143442
  }
142631
143443
  function rankByRelevance(docs, referenceTime) {
@@ -142633,6 +143445,7 @@ function rankByRelevance(docs, referenceTime) {
142633
143445
  }
142634
143446
  function getRetentionZone(doc, referenceTime) {
142635
143447
  const now3 = referenceTime ?? /* @__PURE__ */ new Date();
143448
+ if (doc.feedbackStatus === "archived") return "archive-candidate";
142636
143449
  const importance = getImportanceLevel(doc);
142637
143450
  const retention = getEffectiveRetentionDays(doc);
142638
143451
  const createdAt = new Date(doc.createdAt);
@@ -142704,7 +143517,7 @@ function explainRetention(doc, referenceTime) {
142704
143517
  summary: parts.join(" ; ")
142705
143518
  };
142706
143519
  }
142707
- function toRetentionDocument(obs, accessMap) {
143520
+ function toRetentionDocument(obs, accessMap, feedback) {
142708
143521
  const access2 = accessMap?.get(obs.id);
142709
143522
  return {
142710
143523
  id: `obs-${obs.id}`,
@@ -142726,11 +143539,12 @@ function toRetentionDocument(obs, accessMap) {
142726
143539
  sourceDetail: obs.sourceDetail ?? "",
142727
143540
  valueCategory: obs.valueCategory ?? "",
142728
143541
  admissionState: obs.admissionState ?? "",
142729
- admissionReason: obs.admissionReason ?? ""
143542
+ admissionReason: obs.admissionReason ?? "",
143543
+ ...feedback ? { feedbackWeight: feedback.weight, feedbackStatus: feedback.status } : {}
142730
143544
  };
142731
143545
  }
142732
143546
  function projectObservationRetention(observation, options = {}) {
142733
- const document2 = toRetentionDocument(observation, options.accessMap);
143547
+ const document2 = toRetentionDocument(observation, options.accessMap, options.feedback);
142734
143548
  const relevance = calculateRelevance(document2, options.referenceTime);
142735
143549
  const explanation = explainRetention(document2, options.referenceTime);
142736
143550
  return {
@@ -142755,7 +143569,17 @@ function summarizeRetentionProjections(projections) {
142755
143569
  }
142756
143570
  return { active, stale, archiveCandidates, immune };
142757
143571
  }
142758
- async function archiveExpiredBatch(_projectDir, options) {
143572
+ async function loadFeedbackStore(dataDir) {
143573
+ try {
143574
+ const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
143575
+ const store = new MemoryFeedbackStore2();
143576
+ await store.init(dataDir);
143577
+ return store;
143578
+ } catch {
143579
+ return void 0;
143580
+ }
143581
+ }
143582
+ async function archiveExpiredBatch(projectDir2, options) {
142759
143583
  const store = getObservationStore();
142760
143584
  const limit2 = Math.min(500, Math.max(1, Math.floor(options.limit ?? 100)));
142761
143585
  const afterId = Math.max(0, Math.floor(options.afterId ?? 0));
@@ -142764,11 +143588,13 @@ async function archiveExpiredBatch(_projectDir, options) {
142764
143588
  afterId,
142765
143589
  limit: limit2 + 1
142766
143590
  });
143591
+ const feedback = await loadFeedbackStore(projectDir2);
142767
143592
  const hasMore = page.length > limit2;
142768
143593
  const scanned = hasMore ? page.slice(0, limit2) : page;
142769
143594
  const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => projectObservationRetention(observation, {
142770
143595
  accessMap: options.accessMap,
142771
- referenceTime: options.referenceTime
143596
+ referenceTime: options.referenceTime,
143597
+ ...feedback ? { feedback: feedback.getState(options.projectId, "observation", String(observation.id)) } : {}
142772
143598
  }).zone === "archive-candidate").map((observation) => observation.id);
142773
143599
  const archived = candidateIds.length === 0 ? 0 : await store.atomic(async (tx) => {
142774
143600
  const updates = await Promise.all(
@@ -142799,6 +143625,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
142799
143625
  const remaining = reader ? remainingObservations.filter((observation) => canManageObservation(observation, reader)).length : remainingObservations.length;
142800
143626
  return { archived, remaining };
142801
143627
  }
143628
+ const feedback = await loadFeedbackStore(projectDir2);
142802
143629
  return await store.atomic(async (tx) => {
142803
143630
  const allObs = await tx.loadAll();
142804
143631
  const activeObs = allObs.filter(
@@ -142806,7 +143633,11 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
142806
143633
  );
142807
143634
  const archivedIds = [];
142808
143635
  for (const obs of activeObs) {
142809
- const retention = projectObservationRetention(obs, { accessMap, referenceTime });
143636
+ const retention = projectObservationRetention(obs, {
143637
+ accessMap,
143638
+ referenceTime,
143639
+ ...feedback ? { feedback: feedback.getState(obs.projectId, "observation", String(obs.id)) } : {}
143640
+ });
142810
143641
  if (retention.zone === "archive-candidate") {
142811
143642
  archivedIds.push(obs.id);
142812
143643
  }
@@ -142826,6 +143657,7 @@ var init_retention = __esm({
142826
143657
  init_esm_shims();
142827
143658
  init_obs_store();
142828
143659
  init_visibility();
143660
+ init_feedback_model();
142829
143661
  RETENTION_DAYS = {
142830
143662
  critical: 365,
142831
143663
  high: 180,
@@ -142862,7 +143694,7 @@ var init_retention = __esm({
142862
143694
  });
142863
143695
 
142864
143696
  // src/memory/owner.ts
142865
- import { randomUUID as randomUUID6 } from "crypto";
143697
+ import { randomUUID as randomUUID8 } from "crypto";
142866
143698
  import { promises as fs9 } from "fs";
142867
143699
  import path15 from "path";
142868
143700
  function ownerPath(dataDir) {
@@ -142892,7 +143724,7 @@ async function resolveLocalMemoryOwner(dataDir, options = {}) {
142892
143724
  await fs9.mkdir(path15.dirname(filePath), { recursive: true });
142893
143725
  const created = {
142894
143726
  version: 1,
142895
- id: "local-" + randomUUID6(),
143727
+ id: "local-" + randomUUID8(),
142896
143728
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
142897
143729
  };
142898
143730
  try {
@@ -142915,7 +143747,7 @@ var init_owner = __esm({
142915
143747
  });
142916
143748
 
142917
143749
  // src/memory/long-term-store.ts
142918
- import { randomUUID as randomUUID7 } from "crypto";
143750
+ import { randomUUID as randomUUID9 } from "crypto";
142919
143751
  function optionalText2(value) {
142920
143752
  return typeof value === "string" && value.trim() ? value : void 0;
142921
143753
  }
@@ -142966,7 +143798,7 @@ function rowToEvidence(row) {
142966
143798
  createdAt: row.createdAt
142967
143799
  };
142968
143800
  }
142969
- function rowToEvent(row) {
143801
+ function rowToEvent2(row) {
142970
143802
  return {
142971
143803
  id: row.id,
142972
143804
  memoryId: row.memoryId,
@@ -143075,7 +143907,7 @@ var init_long_term_store = __esm({
143075
143907
  }
143076
143908
  insertEvidence(input) {
143077
143909
  const evidence = {
143078
- id: randomUUID7(),
143910
+ id: randomUUID9(),
143079
143911
  ...input,
143080
143912
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
143081
143913
  };
@@ -143102,7 +143934,7 @@ var init_long_term_store = __esm({
143102
143934
  }
143103
143935
  recordEvent(input) {
143104
143936
  const event = {
143105
- id: randomUUID7(),
143937
+ id: randomUUID9(),
143106
143938
  ...input,
143107
143939
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
143108
143940
  };
@@ -143123,7 +143955,7 @@ var init_long_term_store = __esm({
143123
143955
  listEvents(memoryId) {
143124
143956
  return this.db.prepare(`
143125
143957
  SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
143126
- `).all(memoryId).map(rowToEvent);
143958
+ `).all(memoryId).map(rowToEvent2);
143127
143959
  }
143128
143960
  markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
143129
143961
  const unique = [...new Set(ids.filter(Boolean))];
@@ -143192,7 +144024,7 @@ __export(long_term_exports, {
143192
144024
  selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
143193
144025
  supersedeLongTermMemory: () => supersedeLongTermMemory
143194
144026
  });
143195
- import { randomUUID as randomUUID8 } from "crypto";
144027
+ import { randomUUID as randomUUID10 } from "crypto";
143196
144028
  function isOneOf(value, values) {
143197
144029
  return typeof value === "string" && values.includes(value);
143198
144030
  }
@@ -143374,7 +144206,7 @@ async function createManualLongTermMemory(input) {
143374
144206
  const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
143375
144207
  if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
143376
144208
  assertScope(input.scope);
143377
- const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID8(), relation: "supports" }];
144209
+ const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID10(), relation: "supports" }];
143378
144210
  return createLongTermMemoryWithDataDir({
143379
144211
  dataDir: input.dataDir,
143380
144212
  originProjectId: input.projectId,
@@ -143429,7 +144261,7 @@ async function createLongTermMemoryWithDataDir(input) {
143429
144261
  assertPortableEvidence(input.scope, portability, evidenceInputs);
143430
144262
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
143431
144263
  const memory = {
143432
- id: randomUUID8(),
144264
+ id: randomUUID10(),
143433
144265
  originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
143434
144266
  ownerId: compactText(input.ownerId, "owner id", 160),
143435
144267
  scope: input.scope,
@@ -143747,9 +144579,9 @@ var init_long_term = __esm({
143747
144579
  });
143748
144580
 
143749
144581
  // src/codegraph/ids.ts
143750
- import { createHash as createHash5 } from "crypto";
144582
+ import { createHash as createHash6 } from "crypto";
143751
144583
  function digest(input) {
143752
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
144584
+ return createHash6("sha256").update(input).digest("hex").slice(0, 16);
143753
144585
  }
143754
144586
  function normalizeCodePath(path22) {
143755
144587
  return path22.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
@@ -143784,7 +144616,7 @@ var store_exports = {};
143784
144616
  __export(store_exports, {
143785
144617
  CodeGraphStore: () => CodeGraphStore
143786
144618
  });
143787
- import { randomUUID as randomUUID9 } from "crypto";
144619
+ import { randomUUID as randomUUID11 } from "crypto";
143788
144620
  import { posix as pathPosix } from "path";
143789
144621
  function rowToFile(row) {
143790
144622
  return {
@@ -144501,7 +145333,7 @@ var init_store = __esm({
144501
145333
  * so an interrupted scan cannot be advertised as complete.
144502
145334
  */
144503
145335
  recordCodeStateSnapshot(input) {
144504
- const id = randomUUID9();
145336
+ const id = randomUUID11();
144505
145337
  let snapshot;
144506
145338
  const tx = this.db.transaction(() => {
144507
145339
  const previous = this.db.prepare(
@@ -144579,7 +145411,7 @@ var init_store = __esm({
144579
145411
 
144580
145412
  // src/codegraph/code-state.ts
144581
145413
  import { execFile as execFile2 } from "child_process";
144582
- import { createHash as createHash6 } from "crypto";
145414
+ import { createHash as createHash7 } from "crypto";
144583
145415
  import { promisify as promisify3 } from "util";
144584
145416
  async function runGit(rootPath, args) {
144585
145417
  try {
@@ -144608,7 +145440,7 @@ function changedPathCount(status) {
144608
145440
  return count2;
144609
145441
  }
144610
145442
  function fingerprint(baseRevision, status) {
144611
- return createHash6("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
145443
+ return createHash7("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
144612
145444
  }
144613
145445
  async function collectCodeStateSnapshot(input) {
144614
145446
  const [revision, status] = await Promise.all([
@@ -144708,11 +145540,11 @@ __export(lite_provider_exports, {
144708
145540
  indexProjectLite: () => indexProjectLite,
144709
145541
  refreshProjectLite: () => refreshProjectLite
144710
145542
  });
144711
- import { createHash as createHash7 } from "crypto";
145543
+ import { createHash as createHash8 } from "crypto";
144712
145544
  import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
144713
145545
  import { join as join4, relative } from "path";
144714
145546
  function hashText(text2) {
144715
- return createHash7("sha256").update(text2).digest("hex");
145547
+ return createHash8("sha256").update(text2).digest("hex");
144716
145548
  }
144717
145549
  function extension2(path22) {
144718
145550
  const index = path22.lastIndexOf(".");
@@ -145279,7 +146111,7 @@ var claim_store_exports = {};
145279
146111
  __export(claim_store_exports, {
145280
146112
  ClaimStore: () => ClaimStore
145281
146113
  });
145282
- import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
146114
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
145283
146115
  function optionalText3(value) {
145284
146116
  return typeof value === "string" && value ? value : void 0;
145285
146117
  }
@@ -145318,7 +146150,7 @@ function rowToEvidence2(row) {
145318
146150
  createdAt: row.createdAt
145319
146151
  };
145320
146152
  }
145321
- function rowToEvent2(row) {
146153
+ function rowToEvent3(row) {
145322
146154
  return {
145323
146155
  id: row.id,
145324
146156
  projectId: row.projectId,
@@ -145332,7 +146164,7 @@ function rowToEvent2(row) {
145332
146164
  };
145333
146165
  }
145334
146166
  function evidenceKey(input) {
145335
- return createHash8("sha256").update(JSON.stringify([
146167
+ return createHash9("sha256").update(JSON.stringify([
145336
146168
  input.evidenceKind,
145337
146169
  input.evidenceId,
145338
146170
  input.relation,
@@ -145463,7 +146295,7 @@ var init_claim_store = __esm({
145463
146295
  locator, capturedHash, evidenceKey, createdAt
145464
146296
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145465
146297
  `).run(
145466
- randomUUID10(),
146298
+ randomUUID12(),
145467
146299
  input.claimId,
145468
146300
  input.evidenceKind,
145469
146301
  input.evidenceId,
@@ -145485,7 +146317,7 @@ var init_claim_store = __esm({
145485
146317
  }
145486
146318
  recordEvent(input) {
145487
146319
  const event = {
145488
- id: randomUUID10(),
146320
+ id: randomUUID12(),
145489
146321
  ...input,
145490
146322
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
145491
146323
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -145513,7 +146345,7 @@ var init_claim_store = __esm({
145513
146345
  SELECT * FROM knowledge_claim_events
145514
146346
  WHERE claimId = ?
145515
146347
  ORDER BY createdAt ASC, id
145516
- `).all(claimId).map(rowToEvent2);
146348
+ `).all(claimId).map(rowToEvent3);
145517
146349
  }
145518
146350
  };
145519
146351
  }
@@ -145530,7 +146362,7 @@ __export(claims_exports, {
145530
146362
  supersedeClaim: () => supersedeClaim,
145531
146363
  writeClaim: () => writeClaim
145532
146364
  });
145533
- import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
146365
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
145534
146366
  function compactText2(value, field) {
145535
146367
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
145536
146368
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
@@ -145542,7 +146374,7 @@ function normalizedTerm(value) {
145542
146374
  return compactText2(value, "identity").toLocaleLowerCase("en-US");
145543
146375
  }
145544
146376
  function hashIdentity(parts) {
145545
- return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
146377
+ return createHash10("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
145546
146378
  }
145547
146379
  function buildClaimIdentity(input) {
145548
146380
  const subject = normalizedTerm(input.subject);
@@ -145616,7 +146448,7 @@ function normalizeClaimInput(input) {
145616
146448
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
145617
146449
  return {
145618
146450
  claim: {
145619
- id: randomUUID11(),
146451
+ id: randomUUID13(),
145620
146452
  projectId,
145621
146453
  subject,
145622
146454
  predicate,
@@ -145783,7 +146615,7 @@ function supersedeClaim(store, input) {
145783
146615
  });
145784
146616
  }
145785
146617
  function hashObservation(observation) {
145786
- return createHash9("sha256").update(JSON.stringify([
146618
+ return createHash10("sha256").update(JSON.stringify([
145787
146619
  observation.title,
145788
146620
  observation.narrative,
145789
146621
  observation.facts,
@@ -146027,11 +146859,11 @@ var init_frontmatter = __esm({
146027
146859
  });
146028
146860
 
146029
146861
  // src/knowledge/markdown.ts
146030
- import { createHash as createHash10 } from "crypto";
146862
+ import { createHash as createHash11 } from "crypto";
146031
146863
  import { promises as fs10 } from "fs";
146032
146864
  import path16 from "path";
146033
146865
  function hashContent(content) {
146034
- return createHash10("sha256").update(content).digest("hex");
146866
+ return createHash11("sha256").update(content).digest("hex");
146035
146867
  }
146036
146868
  function stringField(data, key) {
146037
146869
  const value = data[key];
@@ -146157,11 +146989,11 @@ __export(wiki_exports, {
146157
146989
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
146158
146990
  readKnowledgePage: () => readKnowledgePage
146159
146991
  });
146160
- import { createHash as createHash11 } from "crypto";
146992
+ import { createHash as createHash12 } from "crypto";
146161
146993
  import { promises as fs11 } from "fs";
146162
146994
  import path17 from "path";
146163
146995
  function hash2(value) {
146164
- return createHash11("sha256").update(value).digest("hex");
146996
+ return createHash12("sha256").update(value).digest("hex");
146165
146997
  }
146166
146998
  function now() {
146167
146999
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -146701,7 +147533,7 @@ var init_workflow_sync = __esm({
146701
147533
  });
146702
147534
 
146703
147535
  // src/knowledge/workflow-store.ts
146704
- import { randomUUID as randomUUID12 } from "crypto";
147536
+ import { randomUUID as randomUUID14 } from "crypto";
146705
147537
  function parseJson(value, fallback) {
146706
147538
  if (typeof value !== "string" || !value) return fallback;
146707
147539
  try {
@@ -146801,7 +147633,7 @@ var init_workflow_store = __esm({
146801
147633
  }
146802
147634
  recordRun(input) {
146803
147635
  const run = {
146804
- id: randomUUID12(),
147636
+ id: randomUUID14(),
146805
147637
  workflowId: input.workflowId,
146806
147638
  projectId: input.projectId,
146807
147639
  task: input.task.trim(),
@@ -146869,7 +147701,7 @@ var init_outcome_types = __esm({
146869
147701
  });
146870
147702
 
146871
147703
  // src/knowledge/outcome-store.ts
146872
- import { randomUUID as randomUUID13 } from "crypto";
147704
+ import { randomUUID as randomUUID15 } from "crypto";
146873
147705
  function optionalText5(value) {
146874
147706
  return typeof value === "string" && value.trim() ? value : void 0;
146875
147707
  }
@@ -146913,7 +147745,7 @@ var init_outcome_store = __esm({
146913
147745
  }
146914
147746
  record(input) {
146915
147747
  const signal = {
146916
- id: randomUUID13(),
147748
+ id: randomUUID15(),
146917
147749
  projectId: text(input.projectId, "project id"),
146918
147750
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
146919
147751
  candidateId: text(input.candidateId, "candidate id"),
@@ -147018,11 +147850,11 @@ __export(workflows_exports, {
147018
147850
  syncCanonicalWorkflows: () => syncCanonicalWorkflows,
147019
147851
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
147020
147852
  });
147021
- import { createHash as createHash12 } from "crypto";
147853
+ import { createHash as createHash13 } from "crypto";
147022
147854
  import { promises as fs12 } from "fs";
147023
147855
  import path18 from "path";
147024
147856
  function hash3(value) {
147025
- return createHash12("sha256").update(value).digest("hex");
147857
+ return createHash13("sha256").update(value).digest("hex");
147026
147858
  }
147027
147859
  function now2() {
147028
147860
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -148404,6 +149236,16 @@ async function bindObservationCodeRefsBestEffort(observation) {
148404
149236
  } catch {
148405
149237
  }
148406
149238
  }
149239
+ async function persistEvidenceCard(observation) {
149240
+ if (!projectDir) return;
149241
+ try {
149242
+ const evidence = new EvidenceCardStore();
149243
+ await evidence.init(projectDir);
149244
+ evidence.upsertObservation(observation);
149245
+ } catch (error) {
149246
+ console.error(`[memorix] Evidence card projection failed for obs-${observation.id}: ${error instanceof Error ? error.message : String(error)}`);
149247
+ }
149248
+ }
148407
149249
  function queueClaimDerivation(observation) {
148408
149250
  const dataDir = projectDir;
148409
149251
  const isExplicit = observation.sourceDetail === "explicit";
@@ -148750,6 +149592,7 @@ async function storeObservation(input) {
148750
149592
  }
148751
149593
  if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
148752
149594
  await bindObservationCodeRefsBestEffort(observation);
149595
+ await persistEvidenceCard(observation);
148753
149596
  queueObservationQualification(observation);
148754
149597
  if (resolveObservationVisibility(observation) === "project") {
148755
149598
  queueClaimDerivation(observation);
@@ -148842,6 +149685,7 @@ async function upsertObservation(existing, input, now3) {
148842
149685
  await store.update(existing);
148843
149686
  }
148844
149687
  await bindObservationCodeRefsBestEffort(existing);
149688
+ await persistEvidenceCard(existing);
148845
149689
  queueObservationQualification(existing);
148846
149690
  if (resolveObservationVisibility(existing) === "project") {
148847
149691
  queueClaimDerivation(existing);
@@ -148975,6 +149819,7 @@ async function resolveObservations(ids, status = "resolved") {
148975
149819
  await Promise.all(changed.map((observation) => tx.update(observation)));
148976
149820
  });
148977
149821
  }
149822
+ for (const observation of changed) await persistEvidenceCard(observation);
148978
149823
  return { resolved, notFound };
148979
149824
  }
148980
149825
  function getProjectObservations(projectId) {
@@ -149312,6 +150157,7 @@ var init_observations = __esm({
149312
150157
  init_secret_filter();
149313
150158
  init_lifecycle();
149314
150159
  init_visibility();
150160
+ init_evidence_store();
149315
150161
  observations = [];
149316
150162
  nextId = 1;
149317
150163
  projectDir = null;