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
package/dist/sdk.js CHANGED
@@ -437,7 +437,7 @@ function isSqliteAvailable() {
437
437
  return false;
438
438
  }
439
439
  }
440
- 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;
440
+ 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;
441
441
  var init_sqlite_db = __esm({
442
442
  "src/store/sqlite-db.ts"() {
443
443
  "use strict";
@@ -1083,6 +1083,79 @@ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
1083
1083
  created_at TEXT NOT NULL,
1084
1084
  updated_at TEXT NOT NULL
1085
1085
  );
1086
+ `;
1087
+ CREATE_EVIDENCE_CARDS_TABLE = `
1088
+ CREATE TABLE IF NOT EXISTS evidence_cards (
1089
+ id TEXT PRIMARY KEY,
1090
+ project_id TEXT NOT NULL,
1091
+ candidate_kind TEXT NOT NULL,
1092
+ candidate_id TEXT NOT NULL,
1093
+ title TEXT NOT NULL,
1094
+ summary TEXT NOT NULL DEFAULT '',
1095
+ entity_name TEXT,
1096
+ source_kind TEXT NOT NULL,
1097
+ source_ref TEXT NOT NULL,
1098
+ locator TEXT,
1099
+ captured_hash TEXT,
1100
+ session_id TEXT,
1101
+ status TEXT NOT NULL DEFAULT 'active',
1102
+ verification TEXT NOT NULL DEFAULT 'unverified',
1103
+ freshness TEXT NOT NULL DEFAULT 'current',
1104
+ stale_reason TEXT,
1105
+ files_json TEXT NOT NULL DEFAULT '[]',
1106
+ related_entities_json TEXT NOT NULL DEFAULT '[]',
1107
+ created_at TEXT NOT NULL,
1108
+ updated_at TEXT NOT NULL,
1109
+ UNIQUE(project_id, candidate_kind, candidate_id)
1110
+ );
1111
+ `;
1112
+ CREATE_EVIDENCE_CARD_EVENTS_TABLE = `
1113
+ CREATE TABLE IF NOT EXISTS evidence_card_events (
1114
+ id TEXT PRIMARY KEY,
1115
+ project_id TEXT NOT NULL,
1116
+ card_id TEXT NOT NULL,
1117
+ kind TEXT NOT NULL,
1118
+ detail TEXT,
1119
+ created_at TEXT NOT NULL,
1120
+ FOREIGN KEY (card_id) REFERENCES evidence_cards(id) ON DELETE CASCADE
1121
+ );
1122
+ `;
1123
+ CREATE_MEMORY_FEEDBACK_STATES_TABLE = `
1124
+ CREATE TABLE IF NOT EXISTS memory_feedback_states (
1125
+ project_id TEXT NOT NULL,
1126
+ candidate_kind TEXT NOT NULL,
1127
+ candidate_id TEXT NOT NULL,
1128
+ weight REAL NOT NULL DEFAULT 1.0,
1129
+ lifecycle_status TEXT NOT NULL DEFAULT 'active',
1130
+ updated_at TEXT NOT NULL,
1131
+ PRIMARY KEY (project_id, candidate_kind, candidate_id)
1132
+ );
1133
+ `;
1134
+ CREATE_MEMORY_FEEDBACK_EVENTS_TABLE = `
1135
+ CREATE TABLE IF NOT EXISTS memory_feedback_events (
1136
+ id TEXT PRIMARY KEY,
1137
+ project_id TEXT NOT NULL,
1138
+ candidate_kind TEXT NOT NULL,
1139
+ candidate_id TEXT NOT NULL,
1140
+ signal TEXT NOT NULL,
1141
+ actor TEXT,
1142
+ note TEXT,
1143
+ source_ref TEXT NOT NULL,
1144
+ target_event_id TEXT,
1145
+ delta REAL NOT NULL DEFAULT 0,
1146
+ observed_at TEXT NOT NULL
1147
+ );
1148
+ `;
1149
+ CREATE_MCP_BINDINGS_TABLE = `
1150
+ CREATE TABLE IF NOT EXISTS mcp_bindings (
1151
+ handle_id TEXT PRIMARY KEY,
1152
+ project_id TEXT NOT NULL,
1153
+ project_root TEXT NOT NULL,
1154
+ data_dir TEXT NOT NULL,
1155
+ created_at TEXT NOT NULL,
1156
+ last_used_at TEXT NOT NULL,
1157
+ expires_at TEXT
1158
+ );
1086
1159
  `;
1087
1160
  CREATE_INDEXES = `
1088
1161
  CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
@@ -1145,6 +1218,12 @@ CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_target
1145
1218
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
1146
1219
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
1147
1220
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
1221
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC);
1222
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id);
1223
+ CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at);
1224
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC);
1225
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_events_candidate ON memory_feedback_events(project_id, candidate_kind, candidate_id, observed_at DESC);
1226
+ CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC);
1148
1227
  CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1149
1228
  ON maintenance_jobs(project_id, kind, dedupe_key)
1150
1229
  WHERE status IN ('pending', 'running', 'retry');
@@ -1300,6 +1379,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1300
1379
  }
1301
1380
  db2.exec("CREATE INDEX IF NOT EXISTS idx_media_jobs_source_asset ON media_jobs(project_id, source_asset_id, status)");
1302
1381
  }
1382
+ },
1383
+ {
1384
+ id: "1.8-context-control-plane",
1385
+ apply: (db2) => {
1386
+ db2.exec(CREATE_EVIDENCE_CARDS_TABLE);
1387
+ db2.exec(CREATE_EVIDENCE_CARD_EVENTS_TABLE);
1388
+ db2.exec(CREATE_MEMORY_FEEDBACK_STATES_TABLE);
1389
+ db2.exec(CREATE_MEMORY_FEEDBACK_EVENTS_TABLE);
1390
+ db2.exec(CREATE_MCP_BINDINGS_TABLE);
1391
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC)");
1392
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id)");
1393
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at)");
1394
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC)");
1395
+ 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)");
1396
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC)");
1397
+ }
1303
1398
  }
1304
1399
  ];
1305
1400
  _dbCache = /* @__PURE__ */ new Map();
@@ -2781,6 +2876,7 @@ function loadYamlConfig(projectRoot) {
2781
2876
  llm: { ...userConfig.llm, ...projectConfig.llm },
2782
2877
  agent: { ...userConfig.agent, ...projectConfig.agent },
2783
2878
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
2879
+ rerank: { ...userConfig.rerank, ...projectConfig.rerank },
2784
2880
  git: { ...userConfig.git, ...projectConfig.git },
2785
2881
  codegraph: { ...userConfig.codegraph, ...projectConfig.codegraph },
2786
2882
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
@@ -3056,6 +3152,7 @@ function mergeTomlConfig(base, override) {
3056
3152
  llm: { ...base.memory?.llm, ...override.memory?.llm }
3057
3153
  },
3058
3154
  embedding: { ...base.embedding, ...override.embedding },
3155
+ rerank: { ...base.rerank, ...override.rerank },
3059
3156
  hooks: { ...base.hooks, ...override.hooks },
3060
3157
  git: { ...base.git, ...override.git },
3061
3158
  codegraph: { ...base.codegraph, ...override.codegraph },
@@ -3148,6 +3245,8 @@ var init_toml_loader = __esm({
3148
3245
  var dotenv_loader_exports = {};
3149
3246
  __export(dotenv_loader_exports, {
3150
3247
  getLoadedEnvFiles: () => getLoadedEnvFiles,
3248
+ getLoadedEnvKeys: () => getLoadedEnvKeys,
3249
+ getLoadedEnvValue: () => getLoadedEnvValue,
3151
3250
  loadDotenv: () => loadDotenv,
3152
3251
  resetDotenv: () => resetDotenv
3153
3252
  });
@@ -3159,6 +3258,8 @@ function loadEnvFile(filePath) {
3159
3258
  const parsed = parse(readFileSync6(filePath, "utf-8"));
3160
3259
  for (const [key, value] of Object.entries(parsed)) {
3161
3260
  if (!(key in process.env)) {
3261
+ loadedEnvKeys.add(key);
3262
+ loadedEnvValues.set(key, value);
3162
3263
  process.env[key] = value;
3163
3264
  injectedKeys.add(key);
3164
3265
  }
@@ -3171,6 +3272,8 @@ function loadDotenv(projectRoot, options = {}) {
3171
3272
  resetDotenv();
3172
3273
  }
3173
3274
  loadedEnvFiles.length = 0;
3275
+ loadedEnvKeys.clear();
3276
+ loadedEnvValues.clear();
3174
3277
  if (projectRoot) {
3175
3278
  loadEnvFile(getProjectDotenvPath(projectRoot));
3176
3279
  }
@@ -3186,11 +3289,18 @@ function resetDotenv() {
3186
3289
  dotenvLoaded = false;
3187
3290
  dotenvProjectRoot = null;
3188
3291
  loadedEnvFiles.length = 0;
3292
+ loadedEnvKeys.clear();
3189
3293
  }
3190
3294
  function getLoadedEnvFiles() {
3191
3295
  return loadedEnvFiles;
3192
3296
  }
3193
- var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, injectedKeys;
3297
+ function getLoadedEnvKeys() {
3298
+ return loadedEnvKeys;
3299
+ }
3300
+ function getLoadedEnvValue(key) {
3301
+ return loadedEnvValues.get(key);
3302
+ }
3303
+ var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, loadedEnvKeys, loadedEnvValues, injectedKeys;
3194
3304
  var init_dotenv_loader = __esm({
3195
3305
  "src/config/dotenv-loader.ts"() {
3196
3306
  "use strict";
@@ -3199,6 +3309,8 @@ var init_dotenv_loader = __esm({
3199
3309
  dotenvLoaded = false;
3200
3310
  dotenvProjectRoot = null;
3201
3311
  loadedEnvFiles = [];
3312
+ loadedEnvKeys = /* @__PURE__ */ new Set();
3313
+ loadedEnvValues = /* @__PURE__ */ new Map();
3202
3314
  injectedKeys = /* @__PURE__ */ new Set();
3203
3315
  }
3204
3316
  });
@@ -3211,6 +3323,7 @@ __export(resolved_config_exports, {
3211
3323
  getResolvedConfigForCwd: () => getResolvedConfigForCwd,
3212
3324
  getResolvedEmbeddingLane: () => getResolvedEmbeddingLane,
3213
3325
  getResolvedMemoryLane: () => getResolvedMemoryLane,
3326
+ getResolvedRerankLane: () => getResolvedRerankLane,
3214
3327
  resetResolvedConfigCache: () => resetResolvedConfigCache
3215
3328
  });
3216
3329
  import { homedir as homedir5 } from "os";
@@ -3243,6 +3356,16 @@ function getResolvedConfig(options = {}) {
3243
3356
  legacy.llm?.baseUrl
3244
3357
  );
3245
3358
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3359
+ const memoryLlmApiKey = first(
3360
+ process.env.MEMORIX_LLM_API_KEY,
3361
+ process.env.MEMORIX_API_KEY,
3362
+ toml.memory?.llm?.api_key,
3363
+ yaml.llm?.apiKey,
3364
+ legacy.llm?.apiKey,
3365
+ process.env.OPENAI_API_KEY,
3366
+ process.env.ANTHROPIC_API_KEY,
3367
+ openRouterMemoryLlmApiKey
3368
+ );
3246
3369
  const resolved = {
3247
3370
  agent: {
3248
3371
  provider: first(
@@ -3283,16 +3406,7 @@ function getResolvedConfig(options = {}) {
3283
3406
  provider: memoryLlmProvider,
3284
3407
  model: memoryLlmModel,
3285
3408
  baseUrl: memoryLlmBaseUrl,
3286
- apiKey: first(
3287
- process.env.MEMORIX_LLM_API_KEY,
3288
- process.env.MEMORIX_API_KEY,
3289
- toml.memory?.llm?.api_key,
3290
- yaml.llm?.apiKey,
3291
- legacy.llm?.apiKey,
3292
- process.env.OPENAI_API_KEY,
3293
- process.env.ANTHROPIC_API_KEY,
3294
- openRouterMemoryLlmApiKey
3295
- )
3409
+ apiKey: memoryLlmApiKey
3296
3410
  }
3297
3411
  },
3298
3412
  embedding: {
@@ -3302,6 +3416,12 @@ function getResolvedConfig(options = {}) {
3302
3416
  apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
3303
3417
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
3304
3418
  },
3419
+ rerank: resolveRerankLane({
3420
+ toml,
3421
+ yaml,
3422
+ memoryLlmApiKey,
3423
+ memoryLlmBaseUrl
3424
+ }),
3305
3425
  git: {
3306
3426
  autoHook: firstBool(toml.git?.auto_hook, yaml.git?.autoHook),
3307
3427
  ingestOnCommit: firstBool(toml.git?.ingest_on_commit, yaml.git?.ingestOnCommit),
@@ -3371,6 +3491,9 @@ function getResolvedMemoryLane(options = {}) {
3371
3491
  function getResolvedEmbeddingLane(options = {}) {
3372
3492
  return getResolvedConfig(options).embedding;
3373
3493
  }
3494
+ function getResolvedRerankLane(options = {}) {
3495
+ return getResolvedConfig(options).rerank;
3496
+ }
3374
3497
  function resetResolvedConfigCache() {
3375
3498
  }
3376
3499
  function first(...values) {
@@ -3413,6 +3536,10 @@ function getEnvSourceNames() {
3413
3536
  "MEMORIX_EMBEDDING_BASE_URL",
3414
3537
  "MEMORIX_EMBEDDING_MODEL",
3415
3538
  "MEMORIX_EMBEDDING_DIMENSIONS",
3539
+ "MEMORIX_RERANK_PROVIDER",
3540
+ "MEMORIX_RERANK_MODEL",
3541
+ "MEMORIX_RERANK_BASE_URL",
3542
+ "MEMORIX_RERANK_API_KEY",
3416
3543
  "MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
3417
3544
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
3418
3545
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
@@ -3436,6 +3563,63 @@ function normalizeExternalContext(value) {
3436
3563
  if (normalized === "auto" || normalized === "off") return normalized;
3437
3564
  return void 0;
3438
3565
  }
3566
+ function normalizeRerankProvider(value) {
3567
+ const normalized = value?.trim().toLowerCase();
3568
+ if (normalized === "http") return "http";
3569
+ return "off";
3570
+ }
3571
+ function resolveRerankLane(args) {
3572
+ const provider2 = normalizeRerankProvider(first(
3573
+ process.env.MEMORIX_RERANK_PROVIDER,
3574
+ args.toml.rerank?.provider,
3575
+ args.yaml.rerank?.provider,
3576
+ "off"
3577
+ ));
3578
+ const model = first(
3579
+ process.env.MEMORIX_RERANK_MODEL,
3580
+ args.toml.rerank?.model,
3581
+ args.yaml.rerank?.model
3582
+ );
3583
+ const explicitBaseUrl = first(
3584
+ process.env.MEMORIX_RERANK_BASE_URL,
3585
+ args.toml.rerank?.base_url,
3586
+ args.yaml.rerank?.baseUrl
3587
+ );
3588
+ const baseUrl = provider2 === "http" ? first(explicitBaseUrl, args.memoryLlmBaseUrl) : explicitBaseUrl;
3589
+ const dedicatedApiKey = first(
3590
+ process.env.MEMORIX_RERANK_API_KEY,
3591
+ args.toml.rerank?.api_key,
3592
+ args.yaml.rerank?.apiKey
3593
+ );
3594
+ const sameEndpoint = provider2 === "http" && sameTrustedEndpoint(baseUrl, args.memoryLlmBaseUrl);
3595
+ const inheritedApiKey = sameEndpoint ? args.memoryLlmApiKey : void 0;
3596
+ const apiKey = first(dedicatedApiKey, inheritedApiKey);
3597
+ const canSendRequest = provider2 === "http" && Boolean(baseUrl) && (Boolean(apiKey) || !args.memoryLlmApiKey || sameEndpoint);
3598
+ return { provider: provider2, model, baseUrl, apiKey, canSendRequest };
3599
+ }
3600
+ function sameTrustedEndpoint(left, right) {
3601
+ const a = normalizeTrustedEndpoint(left);
3602
+ const b = normalizeTrustedEndpoint(right);
3603
+ return Boolean(a && b && a === b);
3604
+ }
3605
+ function normalizeTrustedEndpoint(value) {
3606
+ const trimmed = value?.trim();
3607
+ if (!trimmed) return void 0;
3608
+ try {
3609
+ const url = new URL(trimmed);
3610
+ const protocol = url.protocol.toLowerCase();
3611
+ const hostname = url.hostname.toLowerCase();
3612
+ const port = url.port;
3613
+ let pathname = url.pathname.replace(/\/+$/, "");
3614
+ if (pathname.toLowerCase().endsWith("/rerank")) {
3615
+ pathname = pathname.slice(0, -"/rerank".length).replace(/\/+$/, "");
3616
+ }
3617
+ return `${protocol}//${hostname}${port ? `:${port}` : ""}${pathname}`;
3618
+ } catch {
3619
+ const fallback = trimmed.replace(/\/+$/, "").replace(/\/rerank$/i, "").replace(/\/+$/, "");
3620
+ return fallback.toLowerCase() || void 0;
3621
+ }
3622
+ }
3439
3623
  var init_resolved_config = __esm({
3440
3624
  "src/config/resolved-config.ts"() {
3441
3625
  "use strict";
@@ -5671,9 +5855,27 @@ __export(provider_exports2, {
5671
5855
  getLLMConfig: () => getLLMConfig,
5672
5856
  initLLM: () => initLLM,
5673
5857
  isLLMEnabled: () => isLLMEnabled,
5858
+ normalizeOpenAICompatibleBaseUrl: () => normalizeOpenAICompatibleBaseUrl,
5674
5859
  parseLLMTimeoutMs: () => parseLLMTimeoutMs,
5675
5860
  setLLMConfig: () => setLLMConfig
5676
5861
  });
5862
+ function normalizeOpenAICompatibleBaseUrl(baseUrl) {
5863
+ const base = baseUrl.trim().replace(/\/+$/, "");
5864
+ if (!base) return base;
5865
+ let pathname = "";
5866
+ try {
5867
+ pathname = new URL(base).pathname.replace(/\/+$/, "");
5868
+ } catch {
5869
+ pathname = base.split(/[?#]/, 1)[0].replace(/\/+$/, "");
5870
+ }
5871
+ const lastSegment = pathname.split("/").filter(Boolean).at(-1);
5872
+ if (lastSegment && OPENAI_API_VERSION_SEGMENT.test(lastSegment)) {
5873
+ return base;
5874
+ }
5875
+ const queryOrHash = base.match(/[?#].*$/)?.[0] ?? "";
5876
+ const pathWithoutQuery = base.slice(0, base.length - queryOrHash.length).replace(/\/+$/, "");
5877
+ return `${pathWithoutQuery}/v1${queryOrHash}`;
5878
+ }
5677
5879
  function parseLLMTimeoutMs(raw) {
5678
5880
  if (raw === void 0 || raw.trim() === "") return LLM_TIMEOUT_DEFAULT_MS;
5679
5881
  const parsed = Number(raw);
@@ -5777,8 +5979,7 @@ async function callLLM(systemPrompt, userMessage, signal) {
5777
5979
  }
5778
5980
  async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5779
5981
  const config = currentConfig;
5780
- let base = config.baseUrl.replace(/\/+$/, "");
5781
- if (!base.endsWith("/v1")) base += "/v1";
5982
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5782
5983
  const url = `${base}/chat/completions`;
5783
5984
  const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5784
5985
  const response = await fetch(url, {
@@ -5857,8 +6058,7 @@ async function callLLMWithTools(messages, tools, signal) {
5857
6058
  }
5858
6059
  async function callOpenAIWithTools(messages, tools, signal) {
5859
6060
  const config = currentConfig;
5860
- let base = config.baseUrl.replace(/\/+$/, "");
5861
- if (!base.endsWith("/v1")) base += "/v1";
6061
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5862
6062
  const url = `${base}/chat/completions`;
5863
6063
  const openaiMessages = messages.map((msg) => {
5864
6064
  if (msg.role === "tool") {
@@ -6008,8 +6208,7 @@ async function callAnthropicWithTools(messages, tools, signal) {
6008
6208
  }
6009
6209
  async function* callOpenAIWithToolsStream(messages, tools) {
6010
6210
  const config = currentConfig;
6011
- let base = config.baseUrl.replace(/\/+$/, "");
6012
- if (!base.endsWith("/v1")) base += "/v1";
6211
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
6013
6212
  const url = `${base}/chat/completions`;
6014
6213
  const openaiMessages = messages.map((msg) => {
6015
6214
  if (msg.role === "tool") {
@@ -6257,7 +6456,7 @@ async function* callAnthropicWithToolsStream(messages, tools) {
6257
6456
  }
6258
6457
  };
6259
6458
  }
6260
- var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
6459
+ 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;
6261
6460
  var init_provider2 = __esm({
6262
6461
  "src/llm/provider.ts"() {
6263
6462
  "use strict";
@@ -6267,6 +6466,7 @@ var init_provider2 = __esm({
6267
6466
  LLM_TIMEOUT_MIN_MS = 1e3;
6268
6467
  LLM_TIMEOUT_MAX_MS = 3e5;
6269
6468
  MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
6469
+ OPENAI_API_VERSION_SEGMENT = /^v\d+(?:[a-z][a-z0-9-]*)?$/i;
6270
6470
  LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
6271
6471
  PROVIDER_DEFAULTS = {
6272
6472
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
@@ -6326,6 +6526,48 @@ Rules:
6326
6526
  }
6327
6527
  });
6328
6528
 
6529
+ // src/rerank/policy.ts
6530
+ function parseRerankTimeoutMs(raw) {
6531
+ const value = raw?.trim();
6532
+ if (!value) return DEFAULT_RERANK_TIMEOUT_MS;
6533
+ const parsed = Number(value);
6534
+ if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
6535
+ console.warn(
6536
+ `[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.`
6537
+ );
6538
+ return DEFAULT_RERANK_TIMEOUT_MS;
6539
+ }
6540
+ if (parsed < RERANK_TIMEOUT_MIN_MS) return RERANK_TIMEOUT_MIN_MS;
6541
+ if (parsed > RERANK_TIMEOUT_MAX_MS) return RERANK_TIMEOUT_MAX_MS;
6542
+ return parsed;
6543
+ }
6544
+ function resolveRerankTimeoutMs(explicitTimeoutMs) {
6545
+ if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
6546
+ return explicitTimeoutMs;
6547
+ }
6548
+ return parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
6549
+ }
6550
+ function shouldAttemptHttpRerank(input) {
6551
+ return input.quality === "thorough" && input.hasQuery && input.candidateCount >= MIN_RERANK_CANDIDATES;
6552
+ }
6553
+ function shouldAttemptLlmRerank(input) {
6554
+ if (input.httpRerankConfigured) return false;
6555
+ if (input.quality !== "thorough" || input.tier !== "heavy" || !input.hasQuery) return false;
6556
+ if (input.candidateCount < MIN_RERANK_CANDIDATES) return false;
6557
+ return input.topScore > 0 && input.secondScore / input.topScore > 0.7;
6558
+ }
6559
+ var DEFAULT_RERANK_TIMEOUT_MS, RERANK_TIMEOUT_MIN_MS, RERANK_TIMEOUT_MAX_MS, MIN_RERANK_CANDIDATES;
6560
+ var init_policy = __esm({
6561
+ "src/rerank/policy.ts"() {
6562
+ "use strict";
6563
+ init_esm_shims();
6564
+ DEFAULT_RERANK_TIMEOUT_MS = 3e4;
6565
+ RERANK_TIMEOUT_MIN_MS = 1e3;
6566
+ RERANK_TIMEOUT_MAX_MS = 3e5;
6567
+ MIN_RERANK_CANDIDATES = 3;
6568
+ }
6569
+ });
6570
+
6329
6571
  // src/timeout.ts
6330
6572
  function withTimeout(promise, ms, label) {
6331
6573
  return new Promise((resolve2, reject) => {
@@ -6611,6 +6853,148 @@ var init_aliases = __esm({
6611
6853
  }
6612
6854
  });
6613
6855
 
6856
+ // src/rerank/http-provider.ts
6857
+ function buildRerankUrl(baseUrl) {
6858
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
6859
+ if (trimmed.endsWith("/rerank")) return trimmed;
6860
+ return `${trimmed}/rerank`;
6861
+ }
6862
+ function parseRerankResponse(payload) {
6863
+ const rows = extractRows(payload);
6864
+ if (rows.length === 0) {
6865
+ throw new Error("Neural rerank response contained no ranked results");
6866
+ }
6867
+ return rows.sort((a, b) => b.score - a.score);
6868
+ }
6869
+ function extractRows(payload) {
6870
+ if (Array.isArray(payload)) {
6871
+ return payload.map(rowFromUnknown).filter((row) => row !== null);
6872
+ }
6873
+ if (payload && typeof payload === "object" && "results" in payload) {
6874
+ const results = payload.results;
6875
+ if (Array.isArray(results)) {
6876
+ return results.map(rowFromUnknown).filter((row) => row !== null);
6877
+ }
6878
+ }
6879
+ throw new Error("Neural rerank response was not Cohere or TEI shaped");
6880
+ }
6881
+ function rowFromUnknown(value) {
6882
+ if (!value || typeof value !== "object") return null;
6883
+ const row = value;
6884
+ if (typeof row.index !== "number" || !Number.isFinite(row.index)) return null;
6885
+ const score = typeof row.relevance_score === "number" ? row.relevance_score : typeof row.score === "number" ? row.score : null;
6886
+ if (score === null || !Number.isFinite(score)) return null;
6887
+ return { index: row.index, score };
6888
+ }
6889
+ function buildRerankBody(query, documents, model) {
6890
+ const body = {
6891
+ query,
6892
+ documents,
6893
+ top_n: documents.length,
6894
+ return_documents: false
6895
+ };
6896
+ const trimmed = model?.trim();
6897
+ if (trimmed) body.model = trimmed;
6898
+ return body;
6899
+ }
6900
+ async function rerankViaHttp(query, documents, config) {
6901
+ if (documents.length === 0) return [];
6902
+ const url = buildRerankUrl(config.baseUrl);
6903
+ const headers = {
6904
+ "content-type": "application/json"
6905
+ };
6906
+ if (config.apiKey) {
6907
+ headers.authorization = `Bearer ${config.apiKey}`;
6908
+ }
6909
+ const controller = new AbortController();
6910
+ const timeoutMs = resolveRerankTimeoutMs(config.timeoutMs);
6911
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6912
+ try {
6913
+ const fetchImpl = config.fetchImpl ?? fetch;
6914
+ const response = await fetchImpl(url, {
6915
+ method: "POST",
6916
+ headers,
6917
+ signal: controller.signal,
6918
+ body: JSON.stringify(buildRerankBody(query, documents, config.model))
6919
+ });
6920
+ if (!response.ok) {
6921
+ throw new Error(`Neural rerank HTTP ${response.status} from ${url}`);
6922
+ }
6923
+ return parseRerankResponse(await response.json());
6924
+ } finally {
6925
+ clearTimeout(timer);
6926
+ }
6927
+ }
6928
+ var init_http_provider = __esm({
6929
+ "src/rerank/http-provider.ts"() {
6930
+ "use strict";
6931
+ init_esm_shims();
6932
+ init_policy();
6933
+ }
6934
+ });
6935
+
6936
+ // src/rerank/index.ts
6937
+ var rerank_exports = {};
6938
+ __export(rerank_exports, {
6939
+ DEFAULT_RERANK_TIMEOUT_MS: () => DEFAULT_RERANK_TIMEOUT_MS,
6940
+ MIN_RERANK_CANDIDATES: () => MIN_RERANK_CANDIDATES,
6941
+ isNeuralRerankEnabled: () => isNeuralRerankEnabled,
6942
+ neuralRerankCandidates: () => neuralRerankCandidates,
6943
+ parseRerankTimeoutMs: () => parseRerankTimeoutMs,
6944
+ resolveRerankTimeoutMs: () => resolveRerankTimeoutMs,
6945
+ shouldAttemptHttpRerank: () => shouldAttemptHttpRerank,
6946
+ shouldAttemptLlmRerank: () => shouldAttemptLlmRerank
6947
+ });
6948
+ function isNeuralRerankEnabled(options = {}) {
6949
+ const lane = getResolvedRerankLane(options);
6950
+ return lane.provider === "http" && Boolean(lane.baseUrl);
6951
+ }
6952
+ async function neuralRerankCandidates(query, candidates, options = {}) {
6953
+ if (!isNeuralRerankEnabled(options) || candidates.length === 0) return null;
6954
+ const lane = getResolvedRerankLane(options);
6955
+ if (!lane.canSendRequest || !lane.baseUrl) return null;
6956
+ const toRerank = candidates.slice(0, MAX_RERANK);
6957
+ const rest = candidates.slice(MAX_RERANK);
6958
+ const documents = toRerank.map((candidate) => candidateText(candidate));
6959
+ const ranked = await rerankViaHttp(query, documents, {
6960
+ provider: "http",
6961
+ model: lane.model,
6962
+ baseUrl: lane.baseUrl,
6963
+ apiKey: lane.apiKey,
6964
+ timeoutMs: resolveRerankTimeoutMs()
6965
+ });
6966
+ const seen = /* @__PURE__ */ new Set();
6967
+ const reranked = [];
6968
+ for (const row of ranked) {
6969
+ const candidate = toRerank[row.index];
6970
+ if (!candidate || seen.has(row.index)) continue;
6971
+ reranked.push(candidate);
6972
+ seen.add(row.index);
6973
+ }
6974
+ for (const [index, candidate] of toRerank.entries()) {
6975
+ if (!seen.has(index)) reranked.push(candidate);
6976
+ }
6977
+ reranked.push(...rest);
6978
+ return reranked;
6979
+ }
6980
+ function candidateText(candidate) {
6981
+ const narrative = candidate.narrative?.trim();
6982
+ if (narrative) return `${candidate.title} \u2014 ${narrative.slice(0, 200)}`;
6983
+ return candidate.title;
6984
+ }
6985
+ var MAX_RERANK;
6986
+ var init_rerank = __esm({
6987
+ "src/rerank/index.ts"() {
6988
+ "use strict";
6989
+ init_esm_shims();
6990
+ init_resolved_config();
6991
+ init_http_provider();
6992
+ init_policy();
6993
+ init_policy();
6994
+ MAX_RERANK = 10;
6995
+ }
6996
+ });
6997
+
6614
6998
  // src/llm/quality.ts
6615
6999
  var quality_exports = {};
6616
7000
  __export(quality_exports, {
@@ -6650,9 +7034,9 @@ async function rerankResults(query, candidates) {
6650
7034
  if (!isLLMEnabled() || candidates.length <= 2) {
6651
7035
  return { reranked: candidates, usedLLM: false };
6652
7036
  }
6653
- const MAX_RERANK = 10;
6654
- const toRerank = candidates.slice(0, MAX_RERANK);
6655
- const rest = candidates.slice(MAX_RERANK);
7037
+ const MAX_RERANK2 = 10;
7038
+ const toRerank = candidates.slice(0, MAX_RERANK2);
7039
+ const rest = candidates.slice(MAX_RERANK2);
6656
7040
  try {
6657
7041
  const candidateList = toRerank.map(
6658
7042
  (c) => `[ID: ${c.id}] (${c.type}) ${c.title}${c.narrative ? ` \u2014 ${c.narrative.substring(0, 100)}` : ""}`
@@ -7545,46 +7929,78 @@ async function searchObservations(options) {
7545
7929
  }
7546
7930
  }
7547
7931
  }
7548
- const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
7549
- const top = intermediate[0]?.score ?? 0;
7550
- const second = intermediate[1]?.score ?? 0;
7551
- return top > 0 && second / top > 0.7;
7552
- })();
7553
- if (shouldRerank) {
7554
- try {
7555
- const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7556
- const narrativeMap = /* @__PURE__ */ new Map();
7557
- for (const hit of results.hits) {
7558
- const doc = hit.document;
7559
- narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7560
- }
7561
- const RERANK_TOP_K = 5;
7562
- const toRerank = intermediate.slice(0, RERANK_TOP_K);
7563
- const candidates = toRerank.map((e2, index) => ({
7564
- id: `r${index + 1}`,
7565
- title: e2.title,
7566
- type: e2.type,
7567
- score: e2.score,
7568
- narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7569
- }));
7570
- const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
7571
- const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
7572
- const { reranked, usedLLM } = await withTimeout(
7573
- rerankResults2(originalQuery, candidates),
7574
- RERANK_TIMEOUT_MS,
7575
- "LLM rerank"
7576
- );
7577
- mark(`rerank(usedLLM=${usedLLM})`);
7578
- if (usedLLM) {
7579
- lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7580
- const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7581
- const rerankedTop = reranked.map((r2) => candidateMap.get(r2.id)).filter((e2) => e2 != null);
7582
- if (rerankedTop.length > 0) {
7583
- intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7932
+ const httpGate = shouldAttemptHttpRerank({
7933
+ quality,
7934
+ hasQuery: Boolean(hasQuery),
7935
+ candidateCount: intermediate.length
7936
+ });
7937
+ const llmGateWithoutHttp = shouldAttemptLlmRerank({
7938
+ quality,
7939
+ tier,
7940
+ hasQuery: Boolean(hasQuery),
7941
+ candidateCount: intermediate.length,
7942
+ topScore: intermediate[0]?.score ?? 0,
7943
+ secondScore: intermediate[1]?.score ?? 0,
7944
+ httpRerankConfigured: false
7945
+ });
7946
+ if (httpGate || llmGateWithoutHttp) {
7947
+ const narrativeMap = /* @__PURE__ */ new Map();
7948
+ for (const hit of results.hits) {
7949
+ const doc = hit.document;
7950
+ narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7951
+ }
7952
+ const RERANK_TOP_K = 5;
7953
+ const toRerank = intermediate.slice(0, RERANK_TOP_K);
7954
+ const candidates = toRerank.map((e2, index) => ({
7955
+ id: `r${index + 1}`,
7956
+ title: e2.title,
7957
+ type: e2.type,
7958
+ score: e2.score,
7959
+ narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7960
+ }));
7961
+ const RERANK_TIMEOUT_MS = parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
7962
+ const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7963
+ const { isNeuralRerankEnabled: isNeuralRerankEnabled2, neuralRerankCandidates: neuralRerankCandidates2 } = await Promise.resolve().then(() => (init_rerank(), rerank_exports));
7964
+ const httpConfigured = isNeuralRerankEnabled2();
7965
+ if (httpConfigured && httpGate) {
7966
+ try {
7967
+ const neural = await withTimeout(
7968
+ neuralRerankCandidates2(originalQuery, candidates),
7969
+ RERANK_TIMEOUT_MS,
7970
+ "neural rerank"
7971
+ );
7972
+ if (neural && neural.length > 0) {
7973
+ const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7974
+ if (rerankedTop.length > 0) {
7975
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7976
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
7977
+ mark("rerank(usedNeural=true)");
7978
+ }
7584
7979
  }
7980
+ } catch {
7981
+ console.error("[memorix] neural rerank failed or timed out; keeping original order");
7585
7982
  }
7586
- } catch (error) {
7587
- console.error("[memorix] LLM rerank failed or timed out, using original order");
7983
+ } else if (!httpConfigured && llmGateWithoutHttp) {
7984
+ try {
7985
+ const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7986
+ const { reranked, usedLLM } = await withTimeout(
7987
+ rerankResults2(originalQuery, candidates),
7988
+ RERANK_TIMEOUT_MS,
7989
+ "LLM rerank"
7990
+ );
7991
+ mark(`rerank(usedLLM=${usedLLM})`);
7992
+ if (usedLLM) {
7993
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7994
+ const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7995
+ if (rerankedTop.length > 0) {
7996
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7997
+ }
7998
+ }
7999
+ } catch {
8000
+ console.error("[memorix] LLM rerank failed or timed out, using original order");
8001
+ }
8002
+ } else {
8003
+ mark(`rerank(skipped,tier=${tier})`);
7588
8004
  }
7589
8005
  } else {
7590
8006
  mark(`rerank(skipped,tier=${tier})`);
@@ -7765,6 +8181,7 @@ var init_orama_store = __esm({
7765
8181
  init_project_affinity();
7766
8182
  init_intent_detector();
7767
8183
  init_query_expansion();
8184
+ init_policy();
7768
8185
  init_timeout();
7769
8186
  db = null;
7770
8187
  dbInitPromise = null;
@@ -8984,6 +9401,226 @@ var init_lifecycle = __esm({
8984
9401
  }
8985
9402
  });
8986
9403
 
9404
+ // src/store/evidence-store.ts
9405
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
9406
+ function safeJson(value, fallback) {
9407
+ if (typeof value !== "string" || value.length === 0) return fallback;
9408
+ try {
9409
+ return JSON.parse(value);
9410
+ } catch {
9411
+ return fallback;
9412
+ }
9413
+ }
9414
+ function rowToCard(row) {
9415
+ return {
9416
+ id: String(row.id),
9417
+ projectId: String(row.project_id),
9418
+ candidateKind: row.candidate_kind,
9419
+ candidateId: String(row.candidate_id),
9420
+ title: String(row.title),
9421
+ summary: String(row.summary ?? ""),
9422
+ ...row.entity_name ? { entityName: String(row.entity_name) } : {},
9423
+ sourceKind: String(row.source_kind),
9424
+ sourceRef: String(row.source_ref),
9425
+ ...row.locator ? { locator: String(row.locator) } : {},
9426
+ ...row.captured_hash ? { capturedHash: String(row.captured_hash) } : {},
9427
+ ...row.session_id ? { sessionId: String(row.session_id) } : {},
9428
+ status: String(row.status ?? "active"),
9429
+ verification: row.verification,
9430
+ freshness: row.freshness,
9431
+ ...row.stale_reason ? { staleReason: String(row.stale_reason) } : {},
9432
+ files: safeJson(row.files_json, []),
9433
+ relatedEntities: safeJson(row.related_entities_json, []),
9434
+ createdAt: String(row.created_at),
9435
+ updatedAt: String(row.updated_at)
9436
+ };
9437
+ }
9438
+ function observationHash(observation) {
9439
+ return createHash3("sha256").update(JSON.stringify({
9440
+ projectId: observation.projectId,
9441
+ id: observation.id,
9442
+ title: observation.title,
9443
+ narrative: observation.narrative,
9444
+ facts: observation.facts,
9445
+ files: observation.filesModified,
9446
+ relatedEntities: observation.relatedEntities
9447
+ })).digest("hex");
9448
+ }
9449
+ function sourceKind(observation) {
9450
+ if (observation.source === "git" || observation.sourceDetail === "git-ingest") return "git";
9451
+ if (observation.sourceDetail === "hook") return "hook";
9452
+ return observation.source ?? "agent";
9453
+ }
9454
+ var EvidenceCardStore;
9455
+ var init_evidence_store = __esm({
9456
+ "src/store/evidence-store.ts"() {
9457
+ "use strict";
9458
+ init_esm_shims();
9459
+ init_sqlite_db();
9460
+ EvidenceCardStore = class {
9461
+ db = null;
9462
+ async init(dataDir) {
9463
+ this.db = getDatabase(dataDir);
9464
+ }
9465
+ requireDb() {
9466
+ if (!this.db) throw new Error("EvidenceCardStore is not initialized.");
9467
+ return this.db;
9468
+ }
9469
+ upsertObservation(observation) {
9470
+ const db2 = this.requireDb();
9471
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
9472
+ const card = {
9473
+ id: `evidence:observation:${observation.projectId}:${observation.id}`,
9474
+ projectId: observation.projectId,
9475
+ candidateKind: "observation",
9476
+ candidateId: String(observation.id),
9477
+ title: observation.title,
9478
+ summary: observation.narrative.slice(0, 500),
9479
+ entityName: observation.entityName,
9480
+ sourceKind: sourceKind(observation),
9481
+ sourceRef: `observation:${observation.id}`,
9482
+ locator: observation.filesModified?.join(", ") || `observation:${observation.id}`,
9483
+ capturedHash: observationHash(observation),
9484
+ ...observation.sessionId ? { sessionId: observation.sessionId } : {},
9485
+ status: observation.status ?? "active",
9486
+ verification: observation.status === "resolved" ? "verified" : "unverified",
9487
+ freshness: "current",
9488
+ files: observation.filesModified ?? [],
9489
+ relatedEntities: observation.relatedEntities ?? [],
9490
+ createdAt: observation.createdAt,
9491
+ updatedAt: now3
9492
+ };
9493
+ db2.prepare(`
9494
+ INSERT INTO evidence_cards (
9495
+ id, project_id, candidate_kind, candidate_id, title, summary, entity_name,
9496
+ source_kind, source_ref, locator, captured_hash, session_id, status,
9497
+ verification, freshness, stale_reason, files_json, related_entities_json,
9498
+ created_at, updated_at
9499
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
9500
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
9501
+ title = excluded.title,
9502
+ summary = excluded.summary,
9503
+ entity_name = excluded.entity_name,
9504
+ source_kind = excluded.source_kind,
9505
+ source_ref = excluded.source_ref,
9506
+ locator = excluded.locator,
9507
+ captured_hash = excluded.captured_hash,
9508
+ session_id = excluded.session_id,
9509
+ status = excluded.status,
9510
+ freshness = 'current',
9511
+ stale_reason = NULL,
9512
+ files_json = excluded.files_json,
9513
+ related_entities_json = excluded.related_entities_json,
9514
+ updated_at = excluded.updated_at
9515
+ `).run(
9516
+ card.id,
9517
+ card.projectId,
9518
+ card.candidateKind,
9519
+ card.candidateId,
9520
+ card.title,
9521
+ card.summary,
9522
+ card.entityName ?? null,
9523
+ card.sourceKind,
9524
+ card.sourceRef,
9525
+ card.locator ?? null,
9526
+ card.capturedHash ?? null,
9527
+ card.sessionId ?? null,
9528
+ card.status,
9529
+ card.verification,
9530
+ card.freshness,
9531
+ JSON.stringify(card.files),
9532
+ JSON.stringify(card.relatedEntities),
9533
+ card.createdAt,
9534
+ card.updatedAt
9535
+ );
9536
+ return this.get(card.projectId, card.candidateKind, card.candidateId) ?? card;
9537
+ }
9538
+ get(projectId, candidateKind, candidateId) {
9539
+ const row = this.requireDb().prepare(`
9540
+ SELECT * FROM evidence_cards WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
9541
+ `).get(projectId, candidateKind, candidateId);
9542
+ return row ? rowToCard(row) : void 0;
9543
+ }
9544
+ list(projectId, options = {}) {
9545
+ const limit2 = Math.min(100, Math.max(1, Math.floor(options.limit ?? 20)));
9546
+ const clauses = ["project_id = ?"];
9547
+ const args = [projectId];
9548
+ if (options.freshness) {
9549
+ clauses.push("freshness = ?");
9550
+ args.push(options.freshness);
9551
+ }
9552
+ if (options.status) {
9553
+ clauses.push("status = ?");
9554
+ args.push(options.status);
9555
+ }
9556
+ args.push(limit2);
9557
+ const rows = this.requireDb().prepare(`
9558
+ SELECT * FROM evidence_cards WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC, id DESC LIMIT ?
9559
+ `).all(...args);
9560
+ return rows.map(rowToCard);
9561
+ }
9562
+ syncObservations(observations2) {
9563
+ return observations2.map((observation) => this.upsertObservation(observation));
9564
+ }
9565
+ markStaleForPaths(projectId, paths, reason = "source file changed") {
9566
+ const normalized = new Set(paths.map((value) => value.replace(/\\/g, "/").toLowerCase()));
9567
+ if (normalized.size === 0) return 0;
9568
+ const db2 = this.requireDb();
9569
+ const rows = db2.prepare(`SELECT * FROM evidence_cards WHERE project_id = ? AND freshness != 'stale'`).all(projectId);
9570
+ const update2 = db2.prepare(`
9571
+ UPDATE evidence_cards SET freshness = 'stale', stale_reason = ?, updated_at = ?
9572
+ WHERE id = ?
9573
+ `);
9574
+ let changed = 0;
9575
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
9576
+ const run = db2.transaction(() => {
9577
+ for (const row of rows) {
9578
+ const files = safeJson(row.files_json, []);
9579
+ if (!files.some((file) => normalized.has(file.replace(/\\/g, "/").toLowerCase()))) continue;
9580
+ update2.run(reason, now3, row.id);
9581
+ changed++;
9582
+ }
9583
+ });
9584
+ run();
9585
+ return changed;
9586
+ }
9587
+ setVerification(projectId, cardId, verification) {
9588
+ this.requireDb().prepare(`
9589
+ UPDATE evidence_cards SET verification = ?, updated_at = ? WHERE project_id = ? AND id = ?
9590
+ `).run(verification, (/* @__PURE__ */ new Date()).toISOString(), projectId, cardId);
9591
+ }
9592
+ recordEvent(projectId, cardId, kind, detail) {
9593
+ const event = {
9594
+ id: randomUUID3(),
9595
+ projectId,
9596
+ cardId,
9597
+ kind,
9598
+ ...detail ? { detail: detail.slice(0, 2e3) } : {},
9599
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
9600
+ };
9601
+ this.requireDb().prepare(`
9602
+ INSERT INTO evidence_card_events (id, project_id, card_id, kind, detail, created_at)
9603
+ VALUES (?, ?, ?, ?, ?, ?)
9604
+ `).run(event.id, event.projectId, event.cardId, event.kind, event.detail ?? null, event.createdAt);
9605
+ return event;
9606
+ }
9607
+ listEvents(cardId, limit2 = 50) {
9608
+ const rows = this.requireDb().prepare(`
9609
+ SELECT * FROM evidence_card_events WHERE card_id = ? ORDER BY created_at DESC LIMIT ?
9610
+ `).all(cardId, Math.min(200, Math.max(1, Math.floor(limit2))));
9611
+ return rows.map((row) => ({
9612
+ id: String(row.id),
9613
+ projectId: String(row.project_id),
9614
+ cardId: String(row.card_id),
9615
+ kind: String(row.kind),
9616
+ ...row.detail ? { detail: String(row.detail) } : {},
9617
+ createdAt: String(row.created_at)
9618
+ }));
9619
+ }
9620
+ };
9621
+ }
9622
+ });
9623
+
8987
9624
  // src/runtime/isolated-maintenance.ts
8988
9625
  import { spawn } from "child_process";
8989
9626
  import { existsSync as existsSync8 } from "fs";
@@ -9145,7 +9782,7 @@ var workspace_store_exports = {};
9145
9782
  __export(workspace_store_exports, {
9146
9783
  KnowledgeWorkspaceStore: () => KnowledgeWorkspaceStore
9147
9784
  });
9148
- import { randomUUID as randomUUID3 } from "crypto";
9785
+ import { randomUUID as randomUUID4 } from "crypto";
9149
9786
  function arrayValue(raw) {
9150
9787
  if (typeof raw !== "string" || !raw) return [];
9151
9788
  try {
@@ -9286,7 +9923,7 @@ var init_workspace_store = __esm({
9286
9923
  }
9287
9924
  createProposal(input) {
9288
9925
  const proposal = {
9289
- id: input.id ?? randomUUID3(),
9926
+ id: input.id ?? randomUUID4(),
9290
9927
  ...input,
9291
9928
  status: input.status ?? "pending",
9292
9929
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -9339,13 +9976,13 @@ __export(workspace_exports, {
9339
9976
  loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
9340
9977
  resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
9341
9978
  });
9342
- import { createHash as createHash3 } from "crypto";
9979
+ import { createHash as createHash4 } from "crypto";
9343
9980
  import { execFile } from "child_process";
9344
9981
  import { promises as fs9 } from "fs";
9345
9982
  import path13 from "path";
9346
9983
  import { promisify } from "util";
9347
9984
  function hash(value) {
9348
- return createHash3("sha256").update(value).digest("hex");
9985
+ return createHash4("sha256").update(value).digest("hex");
9349
9986
  }
9350
9987
  function workspaceId(projectId, mode) {
9351
9988
  return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
@@ -9945,7 +10582,7 @@ var media_store_exports = {};
9945
10582
  __export(media_store_exports, {
9946
10583
  MediaStore: () => MediaStore
9947
10584
  });
9948
- import { randomUUID as randomUUID4 } from "crypto";
10585
+ import { randomUUID as randomUUID5 } from "crypto";
9949
10586
  function asOptionalText(value) {
9950
10587
  return typeof value === "string" && value.length > 0 ? value : void 0;
9951
10588
  }
@@ -10115,7 +10752,7 @@ var init_media_store = __esm({
10115
10752
  return { asset: this.getAsset(input.projectId, existing.id), deduplicated: true };
10116
10753
  }
10117
10754
  const asset = {
10118
- id: randomUUID4(),
10755
+ id: randomUUID5(),
10119
10756
  ...input,
10120
10757
  createdAt: now3,
10121
10758
  updatedAt: now3
@@ -10195,7 +10832,7 @@ var init_media_store = __esm({
10195
10832
  `).get(input.assetId, input.observationId ?? null, input.role);
10196
10833
  if (existing) return rowToLink(existing);
10197
10834
  const link = {
10198
- id: randomUUID4(),
10835
+ id: randomUUID5(),
10199
10836
  assetId: input.assetId,
10200
10837
  projectId: input.projectId,
10201
10838
  ...input.observationId !== void 0 ? { observationId: input.observationId } : {},
@@ -10234,7 +10871,7 @@ var init_media_store = __esm({
10234
10871
  return rowToDerivation(this.db.prepare("SELECT * FROM media_derivations WHERE id = ?").get(existing.id));
10235
10872
  }
10236
10873
  const derivation = {
10237
- id: randomUUID4(),
10874
+ id: randomUUID5(),
10238
10875
  ...input,
10239
10876
  content,
10240
10877
  ...error ? { error } : {},
@@ -10321,7 +10958,7 @@ var init_media_store = __esm({
10321
10958
  createJob(input) {
10322
10959
  const now3 = Date.now();
10323
10960
  const job = {
10324
- id: randomUUID4(),
10961
+ id: randomUUID5(),
10325
10962
  projectId: input.projectId,
10326
10963
  kind: input.kind,
10327
10964
  status: "queued",
@@ -10497,14 +11134,14 @@ __export(asset_store_exports, {
10497
11134
  readMediaAsset: () => readMediaAsset,
10498
11135
  removeMediaAsset: () => removeMediaAsset
10499
11136
  });
10500
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
11137
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
10501
11138
  import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
10502
11139
  import path14 from "path";
10503
11140
  function mediaRoot(dataDir) {
10504
11141
  return path14.resolve(dataDir, "media");
10505
11142
  }
10506
11143
  function projectStorageKey(projectId) {
10507
- return createHash4("sha256").update(projectId).digest("hex").slice(0, 16);
11144
+ return createHash5("sha256").update(projectId).digest("hex").slice(0, 16);
10508
11145
  }
10509
11146
  function validateUnderRoot(root, candidate) {
10510
11147
  const resolved = path14.resolve(candidate);
@@ -10564,14 +11201,14 @@ async function readHeader(filePath, bytes = 32) {
10564
11201
  }
10565
11202
  async function sha256File(filePath) {
10566
11203
  const bytes = await readFile3(filePath);
10567
- return createHash4("sha256").update(bytes).digest("hex");
11204
+ return createHash5("sha256").update(bytes).digest("hex");
10568
11205
  }
10569
11206
  function destinationFor(root, projectId, sha256, extension3) {
10570
11207
  const relative3 = path14.join("assets", projectStorageKey(projectId), sha256.slice(0, 2), `${sha256}${extension3}`);
10571
11208
  return { relative: relative3, absolute: validateUnderRoot(root, path14.join(root, relative3)) };
10572
11209
  }
10573
11210
  function deletionStagingPath(root, assetId) {
10574
- return validateUnderRoot(root, path14.join(root, ".trash", `${assetId}.${randomUUID5()}.pending-delete`));
11211
+ return validateUnderRoot(root, path14.join(root, ".trash", `${assetId}.${randomUUID6()}.pending-delete`));
10575
11212
  }
10576
11213
  async function destinationExists(destination) {
10577
11214
  try {
@@ -10585,7 +11222,7 @@ async function destinationExists(destination) {
10585
11222
  async function persistFile(source, destination) {
10586
11223
  await mkdir3(path14.dirname(destination), { recursive: true });
10587
11224
  if (await destinationExists(destination)) return false;
10588
- const temporary = `${destination}.${randomUUID5()}.tmp`;
11225
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
10589
11226
  try {
10590
11227
  await copyFile(source, temporary);
10591
11228
  await rename2(temporary, destination);
@@ -10601,7 +11238,7 @@ async function persistFile(source, destination) {
10601
11238
  async function persistBuffer(bytes, destination) {
10602
11239
  await mkdir3(path14.dirname(destination), { recursive: true });
10603
11240
  if (await destinationExists(destination)) return false;
10604
- const temporary = `${destination}.${randomUUID5()}.tmp`;
11241
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
10605
11242
  try {
10606
11243
  await writeFile3(temporary, bytes, { flag: "wx" });
10607
11244
  await rename2(temporary, destination);
@@ -10670,7 +11307,7 @@ async function importMediaBuffer(input) {
10670
11307
  if (input.bytes.length === 0) throw new Error("Media import rejects empty payloads");
10671
11308
  if (input.bytes.length > maxBytes) throw new Error(`Media payload is too large (${input.bytes.length} bytes; limit ${maxBytes})`);
10672
11309
  const detected = detectMedia(input.bytes.subarray(0, 32));
10673
- const sha256 = createHash4("sha256").update(input.bytes).digest("hex");
11310
+ const sha256 = createHash5("sha256").update(input.bytes).digest("hex");
10674
11311
  return importKnownMedia({
10675
11312
  dataDir: input.dataDir,
10676
11313
  projectId: input.projectId,
@@ -10688,7 +11325,7 @@ async function readMediaAsset(dataDir, asset, maxBytes = DEFAULT_MAX_MEDIA_BYTES
10688
11325
  if (asset.byteSize > maxBytes) throw new Error(`Media asset exceeds read limit (${asset.byteSize} bytes; limit ${maxBytes})`);
10689
11326
  const bytes = await readFile3(getMediaAssetPath(dataDir, asset));
10690
11327
  if (bytes.length !== asset.byteSize) throw new Error(`Media asset size no longer matches metadata: ${asset.id}`);
10691
- const sha256 = createHash4("sha256").update(bytes).digest("hex");
11328
+ const sha256 = createHash5("sha256").update(bytes).digest("hex");
10692
11329
  if (sha256 !== asset.sha256) throw new Error(`Media asset hash no longer matches metadata: ${asset.id}`);
10693
11330
  return bytes;
10694
11331
  }
@@ -63558,7 +64195,7 @@ var require_gaxios = __commonJS({
63558
64195
  var retry_js_1 = require_retry3();
63559
64196
  var stream_1 = __require("stream");
63560
64197
  var interceptor_js_1 = require_interceptor();
63561
- var randomUUID17 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
64198
+ var randomUUID19 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
63562
64199
  var HTTP_STATUS_NO_CONTENT = 204;
63563
64200
  var Gaxios = class {
63564
64201
  agentCache = /* @__PURE__ */ new Map();
@@ -63831,7 +64468,7 @@ var require_gaxios = __commonJS({
63831
64468
  */
63832
64469
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
63833
64470
  if (opts.multipart?.length) {
63834
- const boundary = await randomUUID17();
64471
+ const boundary = await randomUUID19();
63835
64472
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
63836
64473
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
63837
64474
  } else if (shouldDirectlyPassData) {
@@ -76058,7 +76695,7 @@ var require_websocket = __commonJS({
76058
76695
  var http3 = __require("http");
76059
76696
  var net = __require("net");
76060
76697
  var tls = __require("tls");
76061
- var { randomBytes: randomBytes2, createHash: createHash16 } = __require("crypto");
76698
+ var { randomBytes: randomBytes2, createHash: createHash17 } = __require("crypto");
76062
76699
  var { Duplex, Readable: Readable2 } = __require("stream");
76063
76700
  var { URL: URL2 } = __require("url");
76064
76701
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -76726,7 +77363,7 @@ var require_websocket = __commonJS({
76726
77363
  abortHandshake(websocket, socket, "Invalid Upgrade header");
76727
77364
  return;
76728
77365
  }
76729
- const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
77366
+ const digest2 = createHash17("sha1").update(key + GUID).digest("base64");
76730
77367
  if (res.headers["sec-websocket-accept"] !== digest2) {
76731
77368
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
76732
77369
  return;
@@ -77098,7 +77735,7 @@ var require_websocket_server = __commonJS({
77098
77735
  var EventEmitter2 = __require("events");
77099
77736
  var http3 = __require("http");
77100
77737
  var { Duplex } = __require("stream");
77101
- var { createHash: createHash16 } = __require("crypto");
77738
+ var { createHash: createHash17 } = __require("crypto");
77102
77739
  var extension3 = require_extension();
77103
77740
  var PerMessageDeflate2 = require_permessage_deflate();
77104
77741
  var subprotocol2 = require_subprotocol();
@@ -77405,7 +78042,7 @@ var require_websocket_server = __commonJS({
77405
78042
  );
77406
78043
  }
77407
78044
  if (this._state > RUNNING) return abortHandshake(socket, 503);
77408
- const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
78045
+ const digest2 = createHash17("sha1").update(key + GUID).digest("base64");
77409
78046
  const headers = [
77410
78047
  "HTTP/1.1 101 Switching Protocols",
77411
78048
  "Upgrade: websocket",
@@ -144659,6 +145296,186 @@ var init_audio_jobs = __esm({
144659
145296
  }
144660
145297
  });
144661
145298
 
145299
+ // src/memory/feedback-model.ts
145300
+ function feedbackWeightMultiplier(weight) {
145301
+ return Math.max(0.35, Math.min(1.4, 0.35 + Math.max(0, Math.min(2, weight)) * 0.525));
145302
+ }
145303
+ var init_feedback_model = __esm({
145304
+ "src/memory/feedback-model.ts"() {
145305
+ "use strict";
145306
+ init_esm_shims();
145307
+ }
145308
+ });
145309
+
145310
+ // src/memory/feedback.ts
145311
+ var feedback_exports = {};
145312
+ __export(feedback_exports, {
145313
+ FEEDBACK_DELTAS: () => FEEDBACK_DELTAS,
145314
+ MemoryFeedbackStore: () => MemoryFeedbackStore,
145315
+ applyMemoryFeedback: () => applyMemoryFeedback,
145316
+ feedbackWeightMultiplier: () => feedbackWeightMultiplier
145317
+ });
145318
+ import { randomUUID as randomUUID7 } from "crypto";
145319
+ function applyMemoryFeedback(current, event) {
145320
+ const delta = event.signal === "revoke" ? 0 : FEEDBACK_DELTAS[event.signal];
145321
+ const weight = Math.max(0, Math.min(2, current.weight + delta));
145322
+ const status = (event.signal === "code-conflict" || event.signal === "user-correction" || event.signal === "weaken") && weight < 0.5 ? "archived" : current.status;
145323
+ return { weight, status, audit: [...current.audit, { ...event }] };
145324
+ }
145325
+ function rowToEvent(row) {
145326
+ return {
145327
+ id: String(row.id),
145328
+ projectId: String(row.project_id),
145329
+ candidateKind: row.candidate_kind,
145330
+ candidateId: String(row.candidate_id),
145331
+ signal: row.signal,
145332
+ ...row.observed_at ? { at: String(row.observed_at) } : { at: (/* @__PURE__ */ new Date()).toISOString() },
145333
+ ...row.actor ? { actor: String(row.actor) } : {},
145334
+ ...row.note ? { note: String(row.note) } : {},
145335
+ ...row.source_ref ? { sourceRef: String(row.source_ref) } : {},
145336
+ ...row.target_event_id ? { targetEventId: String(row.target_event_id) } : {},
145337
+ delta: Number(row.delta ?? 0)
145338
+ };
145339
+ }
145340
+ var FEEDBACK_DELTAS, MemoryFeedbackStore;
145341
+ var init_feedback = __esm({
145342
+ "src/memory/feedback.ts"() {
145343
+ "use strict";
145344
+ init_esm_shims();
145345
+ init_sqlite_db();
145346
+ init_feedback_model();
145347
+ FEEDBACK_DELTAS = {
145348
+ "user-correction": -0.35,
145349
+ "verification-success": 0.25,
145350
+ "verification-failure": -0.25,
145351
+ "code-change": -0.15,
145352
+ used: 0.1,
145353
+ "not-used": -0.05,
145354
+ "code-conflict": -0.4,
145355
+ strengthen: 0.2,
145356
+ weaken: -0.2
145357
+ };
145358
+ MemoryFeedbackStore = class {
145359
+ db = null;
145360
+ async init(dataDir) {
145361
+ this.db = getDatabase(dataDir);
145362
+ }
145363
+ requireDb() {
145364
+ if (!this.db) throw new Error("MemoryFeedbackStore is not initialized.");
145365
+ return this.db;
145366
+ }
145367
+ loadEvents(projectId, candidateKind, candidateId) {
145368
+ return this.requireDb().prepare(`
145369
+ SELECT * FROM memory_feedback_events
145370
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
145371
+ ORDER BY observed_at ASC, rowid ASC
145372
+ `).all(projectId, candidateKind, candidateId).map(rowToEvent);
145373
+ }
145374
+ rebuild(projectId, candidateKind, candidateId) {
145375
+ const events = this.loadEvents(projectId, candidateKind, candidateId);
145376
+ const revoked = new Set(events.filter((event) => event.signal === "revoke" && event.targetEventId).map((event) => event.targetEventId));
145377
+ let state = { weight: 1, status: "active", audit: [] };
145378
+ for (const event of events) {
145379
+ if (event.signal === "revoke" || revoked.has(event.id)) {
145380
+ state.audit.push(event);
145381
+ continue;
145382
+ }
145383
+ state = applyMemoryFeedback(state, event);
145384
+ }
145385
+ this.requireDb().prepare(`
145386
+ INSERT INTO memory_feedback_states (project_id, candidate_kind, candidate_id, weight, lifecycle_status, updated_at)
145387
+ VALUES (?, ?, ?, ?, ?, ?)
145388
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
145389
+ weight = excluded.weight, lifecycle_status = excluded.lifecycle_status, updated_at = excluded.updated_at
145390
+ `).run(projectId, candidateKind, candidateId, state.weight, state.status, (/* @__PURE__ */ new Date()).toISOString());
145391
+ return state;
145392
+ }
145393
+ getState(projectId, candidateKind, candidateId) {
145394
+ const row = this.requireDb().prepare(`
145395
+ SELECT weight, lifecycle_status FROM memory_feedback_states
145396
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
145397
+ `).get(projectId, candidateKind, candidateId);
145398
+ if (!row) return { weight: 1, status: "active", audit: [] };
145399
+ return {
145400
+ weight: Number(row.weight ?? 1),
145401
+ status: row.lifecycle_status ?? "active",
145402
+ audit: this.loadEvents(projectId, candidateKind, candidateId)
145403
+ };
145404
+ }
145405
+ listStates(projectId, candidateKind = "observation") {
145406
+ return this.requireDb().prepare(`
145407
+ SELECT candidate_id, weight, lifecycle_status, updated_at
145408
+ FROM memory_feedback_states WHERE project_id = ? AND candidate_kind = ?
145409
+ ORDER BY updated_at DESC
145410
+ `).all(projectId, candidateKind).map((row) => ({
145411
+ candidateId: String(row.candidate_id),
145412
+ weight: Number(row.weight),
145413
+ status: row.lifecycle_status,
145414
+ updatedAt: String(row.updated_at)
145415
+ }));
145416
+ }
145417
+ weights(projectId, candidateKind = "observation") {
145418
+ const states = /* @__PURE__ */ new Map();
145419
+ for (const row of this.listStates(projectId, candidateKind)) {
145420
+ states.set(row.candidateId, this.getState(projectId, candidateKind, row.candidateId));
145421
+ }
145422
+ return states;
145423
+ }
145424
+ record(input) {
145425
+ if (input.signal === "revoke" && !input.targetEventId) {
145426
+ throw new Error("A revoke feedback event requires targetEventId.");
145427
+ }
145428
+ if (input.signal === "revoke") {
145429
+ const target = this.requireDb().prepare(`
145430
+ SELECT 1 FROM memory_feedback_events
145431
+ WHERE id = ? AND project_id = ? AND candidate_kind = ? AND candidate_id = ?
145432
+ `).get(input.targetEventId, input.projectId, input.candidateKind, input.candidateId);
145433
+ if (!target) throw new Error("The feedback event to revoke was not found in the same candidate scope.");
145434
+ }
145435
+ const event = {
145436
+ id: randomUUID7(),
145437
+ projectId: input.projectId,
145438
+ candidateKind: input.candidateKind,
145439
+ candidateId: input.candidateId,
145440
+ signal: input.signal,
145441
+ sourceRef: input.sourceRef,
145442
+ ...input.actor ? { actor: input.actor } : {},
145443
+ ...input.note ? { note: input.note.slice(0, 2e3) } : {},
145444
+ ...input.targetEventId ? { targetEventId: input.targetEventId } : {},
145445
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
145446
+ delta: input.signal === "revoke" ? 0 : FEEDBACK_DELTAS[input.signal]
145447
+ };
145448
+ this.requireDb().prepare(`
145449
+ INSERT INTO memory_feedback_events (
145450
+ id, project_id, candidate_kind, candidate_id, signal, actor, note,
145451
+ source_ref, target_event_id, delta, observed_at
145452
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145453
+ `).run(
145454
+ event.id,
145455
+ event.projectId,
145456
+ event.candidateKind,
145457
+ event.candidateId,
145458
+ event.signal,
145459
+ event.actor ?? null,
145460
+ event.note ?? null,
145461
+ event.sourceRef,
145462
+ event.targetEventId ?? null,
145463
+ event.delta,
145464
+ event.at
145465
+ );
145466
+ return { event, state: this.rebuild(input.projectId, input.candidateKind, input.candidateId) };
145467
+ }
145468
+ audit(projectId, candidateKind, candidateId, limit2 = 100) {
145469
+ return this.requireDb().prepare(`
145470
+ SELECT * FROM memory_feedback_events
145471
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
145472
+ ORDER BY observed_at DESC, rowid DESC LIMIT ?
145473
+ `).all(projectId, candidateKind, candidateId, Math.min(200, Math.max(1, Math.floor(limit2)))).map(rowToEvent);
145474
+ }
145475
+ };
145476
+ }
145477
+ });
145478
+
144662
145479
  // src/memory/retention.ts
144663
145480
  var retention_exports = {};
144664
145481
  __export(retention_exports, {
@@ -144700,6 +145517,7 @@ function getEffectiveRetentionDays(doc) {
144700
145517
  }
144701
145518
  function isImmune(doc) {
144702
145519
  if (doc.type === "probe") return false;
145520
+ if (doc.feedbackStatus === "archived") return false;
144703
145521
  if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
144704
145522
  if (doc.valueCategory === "core") return true;
144705
145523
  const importance = getImportanceLevel(doc);
@@ -144732,7 +145550,8 @@ function calculateRelevance(doc, referenceTime) {
144732
145550
  const decayFactor = Math.exp(-ageDays / retention);
144733
145551
  const accessCount = doc.accessCount ?? 0;
144734
145552
  const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
144735
- let totalScore = base * decayFactor * accessBoost;
145553
+ const feedbackMultiplier = doc.feedbackWeight === void 0 ? 1 : feedbackWeightMultiplier(doc.feedbackWeight);
145554
+ let totalScore = base * decayFactor * accessBoost * feedbackMultiplier;
144736
145555
  const immune = isImmune(doc);
144737
145556
  if (immune) {
144738
145557
  totalScore = Math.max(totalScore, 0.5);
@@ -144744,7 +145563,8 @@ function calculateRelevance(doc, referenceTime) {
144744
145563
  decayFactor,
144745
145564
  accessBoost,
144746
145565
  ageDays,
144747
- isImmune: immune
145566
+ isImmune: immune,
145567
+ feedbackMultiplier
144748
145568
  };
144749
145569
  }
144750
145570
  function rankByRelevance(docs, referenceTime) {
@@ -144752,6 +145572,7 @@ function rankByRelevance(docs, referenceTime) {
144752
145572
  }
144753
145573
  function getRetentionZone(doc, referenceTime) {
144754
145574
  const now3 = referenceTime ?? /* @__PURE__ */ new Date();
145575
+ if (doc.feedbackStatus === "archived") return "archive-candidate";
144755
145576
  const importance = getImportanceLevel(doc);
144756
145577
  const retention = getEffectiveRetentionDays(doc);
144757
145578
  const createdAt = new Date(doc.createdAt);
@@ -144823,7 +145644,7 @@ function explainRetention(doc, referenceTime) {
144823
145644
  summary: parts.join(" ; ")
144824
145645
  };
144825
145646
  }
144826
- function toRetentionDocument(obs, accessMap) {
145647
+ function toRetentionDocument(obs, accessMap, feedback) {
144827
145648
  const access3 = accessMap?.get(obs.id);
144828
145649
  return {
144829
145650
  id: `obs-${obs.id}`,
@@ -144845,11 +145666,12 @@ function toRetentionDocument(obs, accessMap) {
144845
145666
  sourceDetail: obs.sourceDetail ?? "",
144846
145667
  valueCategory: obs.valueCategory ?? "",
144847
145668
  admissionState: obs.admissionState ?? "",
144848
- admissionReason: obs.admissionReason ?? ""
145669
+ admissionReason: obs.admissionReason ?? "",
145670
+ ...feedback ? { feedbackWeight: feedback.weight, feedbackStatus: feedback.status } : {}
144849
145671
  };
144850
145672
  }
144851
145673
  function projectObservationRetention(observation, options = {}) {
144852
- const document2 = toRetentionDocument(observation, options.accessMap);
145674
+ const document2 = toRetentionDocument(observation, options.accessMap, options.feedback);
144853
145675
  const relevance = calculateRelevance(document2, options.referenceTime);
144854
145676
  const explanation = explainRetention(document2, options.referenceTime);
144855
145677
  return {
@@ -144874,7 +145696,17 @@ function summarizeRetentionProjections(projections) {
144874
145696
  }
144875
145697
  return { active, stale, archiveCandidates, immune };
144876
145698
  }
144877
- async function archiveExpiredBatch(_projectDir, options) {
145699
+ async function loadFeedbackStore(dataDir) {
145700
+ try {
145701
+ const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
145702
+ const store = new MemoryFeedbackStore2();
145703
+ await store.init(dataDir);
145704
+ return store;
145705
+ } catch {
145706
+ return void 0;
145707
+ }
145708
+ }
145709
+ async function archiveExpiredBatch(projectDir2, options) {
144878
145710
  const store = getObservationStore();
144879
145711
  const limit2 = Math.min(500, Math.max(1, Math.floor(options.limit ?? 100)));
144880
145712
  const afterId = Math.max(0, Math.floor(options.afterId ?? 0));
@@ -144883,11 +145715,13 @@ async function archiveExpiredBatch(_projectDir, options) {
144883
145715
  afterId,
144884
145716
  limit: limit2 + 1
144885
145717
  });
145718
+ const feedback = await loadFeedbackStore(projectDir2);
144886
145719
  const hasMore = page.length > limit2;
144887
145720
  const scanned = hasMore ? page.slice(0, limit2) : page;
144888
145721
  const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => projectObservationRetention(observation, {
144889
145722
  accessMap: options.accessMap,
144890
- referenceTime: options.referenceTime
145723
+ referenceTime: options.referenceTime,
145724
+ ...feedback ? { feedback: feedback.getState(options.projectId, "observation", String(observation.id)) } : {}
144891
145725
  }).zone === "archive-candidate").map((observation) => observation.id);
144892
145726
  const archived = candidateIds.length === 0 ? 0 : await store.atomic(async (tx) => {
144893
145727
  const updates = await Promise.all(
@@ -144918,6 +145752,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
144918
145752
  const remaining = reader2 ? remainingObservations.filter((observation) => canManageObservation(observation, reader2)).length : remainingObservations.length;
144919
145753
  return { archived, remaining };
144920
145754
  }
145755
+ const feedback = await loadFeedbackStore(projectDir2);
144921
145756
  return await store.atomic(async (tx) => {
144922
145757
  const allObs = await tx.loadAll();
144923
145758
  const activeObs = allObs.filter(
@@ -144925,7 +145760,11 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
144925
145760
  );
144926
145761
  const archivedIds = [];
144927
145762
  for (const obs of activeObs) {
144928
- const retention = projectObservationRetention(obs, { accessMap, referenceTime });
145763
+ const retention = projectObservationRetention(obs, {
145764
+ accessMap,
145765
+ referenceTime,
145766
+ ...feedback ? { feedback: feedback.getState(obs.projectId, "observation", String(obs.id)) } : {}
145767
+ });
144929
145768
  if (retention.zone === "archive-candidate") {
144930
145769
  archivedIds.push(obs.id);
144931
145770
  }
@@ -144945,6 +145784,7 @@ var init_retention = __esm({
144945
145784
  init_esm_shims();
144946
145785
  init_obs_store();
144947
145786
  init_visibility();
145787
+ init_feedback_model();
144948
145788
  RETENTION_DAYS = {
144949
145789
  critical: 365,
144950
145790
  high: 180,
@@ -144985,7 +145825,7 @@ var owner_exports = {};
144985
145825
  __export(owner_exports, {
144986
145826
  resolveLocalMemoryOwner: () => resolveLocalMemoryOwner
144987
145827
  });
144988
- import { randomUUID as randomUUID6 } from "crypto";
145828
+ import { randomUUID as randomUUID8 } from "crypto";
144989
145829
  import { promises as fs12 } from "fs";
144990
145830
  import path20 from "path";
144991
145831
  function ownerPath(dataDir) {
@@ -145015,7 +145855,7 @@ async function resolveLocalMemoryOwner(dataDir, options = {}) {
145015
145855
  await fs12.mkdir(path20.dirname(filePath), { recursive: true });
145016
145856
  const created = {
145017
145857
  version: 1,
145018
- id: "local-" + randomUUID6(),
145858
+ id: "local-" + randomUUID8(),
145019
145859
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
145020
145860
  };
145021
145861
  try {
@@ -145038,7 +145878,7 @@ var init_owner = __esm({
145038
145878
  });
145039
145879
 
145040
145880
  // src/memory/long-term-store.ts
145041
- import { randomUUID as randomUUID7 } from "crypto";
145881
+ import { randomUUID as randomUUID9 } from "crypto";
145042
145882
  function optionalText2(value) {
145043
145883
  return typeof value === "string" && value.trim() ? value : void 0;
145044
145884
  }
@@ -145089,7 +145929,7 @@ function rowToEvidence(row) {
145089
145929
  createdAt: row.createdAt
145090
145930
  };
145091
145931
  }
145092
- function rowToEvent(row) {
145932
+ function rowToEvent2(row) {
145093
145933
  return {
145094
145934
  id: row.id,
145095
145935
  memoryId: row.memoryId,
@@ -145198,7 +146038,7 @@ var init_long_term_store = __esm({
145198
146038
  }
145199
146039
  insertEvidence(input) {
145200
146040
  const evidence = {
145201
- id: randomUUID7(),
146041
+ id: randomUUID9(),
145202
146042
  ...input,
145203
146043
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
145204
146044
  };
@@ -145225,7 +146065,7 @@ var init_long_term_store = __esm({
145225
146065
  }
145226
146066
  recordEvent(input) {
145227
146067
  const event = {
145228
- id: randomUUID7(),
146068
+ id: randomUUID9(),
145229
146069
  ...input,
145230
146070
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
145231
146071
  };
@@ -145246,7 +146086,7 @@ var init_long_term_store = __esm({
145246
146086
  listEvents(memoryId) {
145247
146087
  return this.db.prepare(`
145248
146088
  SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
145249
- `).all(memoryId).map(rowToEvent);
146089
+ `).all(memoryId).map(rowToEvent2);
145250
146090
  }
145251
146091
  markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
145252
146092
  const unique2 = [...new Set(ids.filter(Boolean))];
@@ -145315,7 +146155,7 @@ __export(long_term_exports, {
145315
146155
  selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
145316
146156
  supersedeLongTermMemory: () => supersedeLongTermMemory
145317
146157
  });
145318
- import { randomUUID as randomUUID8 } from "crypto";
146158
+ import { randomUUID as randomUUID10 } from "crypto";
145319
146159
  function isOneOf(value, values) {
145320
146160
  return typeof value === "string" && values.includes(value);
145321
146161
  }
@@ -145497,7 +146337,7 @@ async function createManualLongTermMemory(input) {
145497
146337
  const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
145498
146338
  if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
145499
146339
  assertScope(input.scope);
145500
- const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID8(), relation: "supports" }];
146340
+ const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID10(), relation: "supports" }];
145501
146341
  return createLongTermMemoryWithDataDir({
145502
146342
  dataDir: input.dataDir,
145503
146343
  originProjectId: input.projectId,
@@ -145552,7 +146392,7 @@ async function createLongTermMemoryWithDataDir(input) {
145552
146392
  assertPortableEvidence(input.scope, portability, evidenceInputs);
145553
146393
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
145554
146394
  const memory = {
145555
- id: randomUUID8(),
146395
+ id: randomUUID10(),
145556
146396
  originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
145557
146397
  ownerId: compactText(input.ownerId, "owner id", 160),
145558
146398
  scope: input.scope,
@@ -145870,9 +146710,9 @@ var init_long_term = __esm({
145870
146710
  });
145871
146711
 
145872
146712
  // src/codegraph/ids.ts
145873
- import { createHash as createHash5 } from "crypto";
146713
+ import { createHash as createHash6 } from "crypto";
145874
146714
  function digest(input) {
145875
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
146715
+ return createHash6("sha256").update(input).digest("hex").slice(0, 16);
145876
146716
  }
145877
146717
  function normalizeCodePath(path35) {
145878
146718
  return path35.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
@@ -145907,7 +146747,7 @@ var store_exports = {};
145907
146747
  __export(store_exports, {
145908
146748
  CodeGraphStore: () => CodeGraphStore
145909
146749
  });
145910
- import { randomUUID as randomUUID9 } from "crypto";
146750
+ import { randomUUID as randomUUID11 } from "crypto";
145911
146751
  import { posix as pathPosix } from "path";
145912
146752
  function rowToFile(row) {
145913
146753
  return {
@@ -146624,7 +147464,7 @@ var init_store = __esm({
146624
147464
  * so an interrupted scan cannot be advertised as complete.
146625
147465
  */
146626
147466
  recordCodeStateSnapshot(input) {
146627
- const id = randomUUID9();
147467
+ const id = randomUUID11();
146628
147468
  let snapshot;
146629
147469
  const tx = this.db.transaction(() => {
146630
147470
  const previous = this.db.prepare(
@@ -146702,7 +147542,7 @@ var init_store = __esm({
146702
147542
 
146703
147543
  // src/codegraph/code-state.ts
146704
147544
  import { execFile as execFile2 } from "child_process";
146705
- import { createHash as createHash6 } from "crypto";
147545
+ import { createHash as createHash7 } from "crypto";
146706
147546
  import { promisify as promisify3 } from "util";
146707
147547
  async function runGit(rootPath, args) {
146708
147548
  try {
@@ -146731,7 +147571,7 @@ function changedPathCount(status) {
146731
147571
  return count2;
146732
147572
  }
146733
147573
  function fingerprint(baseRevision, status) {
146734
- return createHash6("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
147574
+ return createHash7("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
146735
147575
  }
146736
147576
  async function collectCodeStateSnapshot(input) {
146737
147577
  const [revision, status] = await Promise.all([
@@ -146831,11 +147671,11 @@ __export(lite_provider_exports, {
146831
147671
  indexProjectLite: () => indexProjectLite,
146832
147672
  refreshProjectLite: () => refreshProjectLite
146833
147673
  });
146834
- import { createHash as createHash7 } from "crypto";
147674
+ import { createHash as createHash8 } from "crypto";
146835
147675
  import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
146836
147676
  import { join as join4, relative } from "path";
146837
147677
  function hashText(text2) {
146838
- return createHash7("sha256").update(text2).digest("hex");
147678
+ return createHash8("sha256").update(text2).digest("hex");
146839
147679
  }
146840
147680
  function extension2(path35) {
146841
147681
  const index = path35.lastIndexOf(".");
@@ -147402,7 +148242,7 @@ var claim_store_exports = {};
147402
148242
  __export(claim_store_exports, {
147403
148243
  ClaimStore: () => ClaimStore
147404
148244
  });
147405
- import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
148245
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
147406
148246
  function optionalText3(value) {
147407
148247
  return typeof value === "string" && value ? value : void 0;
147408
148248
  }
@@ -147441,7 +148281,7 @@ function rowToEvidence2(row) {
147441
148281
  createdAt: row.createdAt
147442
148282
  };
147443
148283
  }
147444
- function rowToEvent2(row) {
148284
+ function rowToEvent3(row) {
147445
148285
  return {
147446
148286
  id: row.id,
147447
148287
  projectId: row.projectId,
@@ -147455,7 +148295,7 @@ function rowToEvent2(row) {
147455
148295
  };
147456
148296
  }
147457
148297
  function evidenceKey(input) {
147458
- return createHash8("sha256").update(JSON.stringify([
148298
+ return createHash9("sha256").update(JSON.stringify([
147459
148299
  input.evidenceKind,
147460
148300
  input.evidenceId,
147461
148301
  input.relation,
@@ -147586,7 +148426,7 @@ var init_claim_store = __esm({
147586
148426
  locator, capturedHash, evidenceKey, createdAt
147587
148427
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
147588
148428
  `).run(
147589
- randomUUID10(),
148429
+ randomUUID12(),
147590
148430
  input.claimId,
147591
148431
  input.evidenceKind,
147592
148432
  input.evidenceId,
@@ -147608,7 +148448,7 @@ var init_claim_store = __esm({
147608
148448
  }
147609
148449
  recordEvent(input) {
147610
148450
  const event = {
147611
- id: randomUUID10(),
148451
+ id: randomUUID12(),
147612
148452
  ...input,
147613
148453
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
147614
148454
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -147636,7 +148476,7 @@ var init_claim_store = __esm({
147636
148476
  SELECT * FROM knowledge_claim_events
147637
148477
  WHERE claimId = ?
147638
148478
  ORDER BY createdAt ASC, id
147639
- `).all(claimId).map(rowToEvent2);
148479
+ `).all(claimId).map(rowToEvent3);
147640
148480
  }
147641
148481
  };
147642
148482
  }
@@ -147653,7 +148493,7 @@ __export(claims_exports, {
147653
148493
  supersedeClaim: () => supersedeClaim,
147654
148494
  writeClaim: () => writeClaim
147655
148495
  });
147656
- import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
148496
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
147657
148497
  function compactText2(value, field) {
147658
148498
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
147659
148499
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
@@ -147665,7 +148505,7 @@ function normalizedTerm(value) {
147665
148505
  return compactText2(value, "identity").toLocaleLowerCase("en-US");
147666
148506
  }
147667
148507
  function hashIdentity(parts) {
147668
- return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
148508
+ return createHash10("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
147669
148509
  }
147670
148510
  function buildClaimIdentity(input) {
147671
148511
  const subject = normalizedTerm(input.subject);
@@ -147739,7 +148579,7 @@ function normalizeClaimInput(input) {
147739
148579
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
147740
148580
  return {
147741
148581
  claim: {
147742
- id: randomUUID11(),
148582
+ id: randomUUID13(),
147743
148583
  projectId,
147744
148584
  subject,
147745
148585
  predicate,
@@ -147906,7 +148746,7 @@ function supersedeClaim(store, input) {
147906
148746
  });
147907
148747
  }
147908
148748
  function hashObservation(observation) {
147909
- return createHash9("sha256").update(JSON.stringify([
148749
+ return createHash10("sha256").update(JSON.stringify([
147910
148750
  observation.title,
147911
148751
  observation.narrative,
147912
148752
  observation.facts,
@@ -148150,11 +148990,11 @@ var init_frontmatter = __esm({
148150
148990
  });
148151
148991
 
148152
148992
  // src/knowledge/markdown.ts
148153
- import { createHash as createHash10 } from "crypto";
148993
+ import { createHash as createHash11 } from "crypto";
148154
148994
  import { promises as fs13 } from "fs";
148155
148995
  import path21 from "path";
148156
148996
  function hashContent(content) {
148157
- return createHash10("sha256").update(content).digest("hex");
148997
+ return createHash11("sha256").update(content).digest("hex");
148158
148998
  }
148159
148999
  function stringField(data, key) {
148160
149000
  const value = data[key];
@@ -148280,11 +149120,11 @@ __export(wiki_exports, {
148280
149120
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
148281
149121
  readKnowledgePage: () => readKnowledgePage
148282
149122
  });
148283
- import { createHash as createHash11 } from "crypto";
149123
+ import { createHash as createHash12 } from "crypto";
148284
149124
  import { promises as fs14 } from "fs";
148285
149125
  import path22 from "path";
148286
149126
  function hash2(value) {
148287
- return createHash11("sha256").update(value).digest("hex");
149127
+ return createHash12("sha256").update(value).digest("hex");
148288
149128
  }
148289
149129
  function now() {
148290
149130
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -148828,7 +149668,7 @@ var workflow_store_exports = {};
148828
149668
  __export(workflow_store_exports, {
148829
149669
  WorkflowStore: () => WorkflowStore
148830
149670
  });
148831
- import { randomUUID as randomUUID12 } from "crypto";
149671
+ import { randomUUID as randomUUID14 } from "crypto";
148832
149672
  function parseJson(value, fallback) {
148833
149673
  if (typeof value !== "string" || !value) return fallback;
148834
149674
  try {
@@ -148928,7 +149768,7 @@ var init_workflow_store = __esm({
148928
149768
  }
148929
149769
  recordRun(input) {
148930
149770
  const run = {
148931
- id: randomUUID12(),
149771
+ id: randomUUID14(),
148932
149772
  workflowId: input.workflowId,
148933
149773
  projectId: input.projectId,
148934
149774
  task: input.task.trim(),
@@ -149006,7 +149846,7 @@ var init_outcome_types = __esm({
149006
149846
  });
149007
149847
 
149008
149848
  // src/knowledge/outcome-store.ts
149009
- import { randomUUID as randomUUID13 } from "crypto";
149849
+ import { randomUUID as randomUUID15 } from "crypto";
149010
149850
  function optionalText5(value) {
149011
149851
  return typeof value === "string" && value.trim() ? value : void 0;
149012
149852
  }
@@ -149050,7 +149890,7 @@ var init_outcome_store = __esm({
149050
149890
  }
149051
149891
  record(input) {
149052
149892
  const signal = {
149053
- id: randomUUID13(),
149893
+ id: randomUUID15(),
149054
149894
  projectId: text(input.projectId, "project id"),
149055
149895
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
149056
149896
  candidateId: text(input.candidateId, "candidate id"),
@@ -149477,11 +150317,11 @@ __export(workflows_exports, {
149477
150317
  syncCanonicalWorkflows: () => syncCanonicalWorkflows,
149478
150318
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
149479
150319
  });
149480
- import { createHash as createHash12 } from "crypto";
150320
+ import { createHash as createHash13 } from "crypto";
149481
150321
  import { promises as fs15 } from "fs";
149482
150322
  import path23 from "path";
149483
150323
  function hash3(value) {
149484
- return createHash12("sha256").update(value).digest("hex");
150324
+ return createHash13("sha256").update(value).digest("hex");
149485
150325
  }
149486
150326
  function now2() {
149487
150327
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -150878,6 +151718,16 @@ async function bindObservationCodeRefsBestEffort(observation) {
150878
151718
  } catch {
150879
151719
  }
150880
151720
  }
151721
+ async function persistEvidenceCard(observation) {
151722
+ if (!projectDir) return;
151723
+ try {
151724
+ const evidence = new EvidenceCardStore();
151725
+ await evidence.init(projectDir);
151726
+ evidence.upsertObservation(observation);
151727
+ } catch (error) {
151728
+ console.error(`[memorix] Evidence card projection failed for obs-${observation.id}: ${error instanceof Error ? error.message : String(error)}`);
151729
+ }
151730
+ }
150881
151731
  function queueClaimDerivation(observation) {
150882
151732
  const dataDir = projectDir;
150883
151733
  const isExplicit = observation.sourceDetail === "explicit";
@@ -151224,6 +152074,7 @@ async function storeObservation(input) {
151224
152074
  }
151225
152075
  if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
151226
152076
  await bindObservationCodeRefsBestEffort(observation);
152077
+ await persistEvidenceCard(observation);
151227
152078
  queueObservationQualification(observation);
151228
152079
  if (resolveObservationVisibility(observation) === "project") {
151229
152080
  queueClaimDerivation(observation);
@@ -151316,6 +152167,7 @@ async function upsertObservation(existing, input, now3) {
151316
152167
  await store.update(existing);
151317
152168
  }
151318
152169
  await bindObservationCodeRefsBestEffort(existing);
152170
+ await persistEvidenceCard(existing);
151319
152171
  queueObservationQualification(existing);
151320
152172
  if (resolveObservationVisibility(existing) === "project") {
151321
152173
  queueClaimDerivation(existing);
@@ -151449,6 +152301,7 @@ async function resolveObservations(ids, status = "resolved") {
151449
152301
  await Promise.all(changed.map((observation) => tx.update(observation)));
151450
152302
  });
151451
152303
  }
152304
+ for (const observation of changed) await persistEvidenceCard(observation);
151452
152305
  return { resolved, notFound };
151453
152306
  }
151454
152307
  function getProjectObservations(projectId) {
@@ -151786,6 +152639,7 @@ var init_observations = __esm({
151786
152639
  init_secret_filter();
151787
152640
  init_lifecycle();
151788
152641
  init_visibility();
152642
+ init_evidence_store();
151789
152643
  observations = [];
151790
152644
  nextId = 1;
151791
152645
  projectDir = null;
@@ -155465,7 +156319,7 @@ var compaction_checkpoint_store_exports = {};
155465
156319
  __export(compaction_checkpoint_store_exports, {
155466
156320
  CompactionCheckpointStore: () => CompactionCheckpointStore
155467
156321
  });
155468
- import { createHash as createHash14, randomUUID as randomUUID14 } from "crypto";
156322
+ import { createHash as createHash15, randomUUID as randomUUID16 } from "crypto";
155469
156323
  function optionalText7(value) {
155470
156324
  return typeof value === "string" && value ? value : void 0;
155471
156325
  }
@@ -155507,7 +156361,7 @@ function sourceKeyFor(input, summary) {
155507
156361
  input.tokensBefore ?? "",
155508
156362
  input.firstKeptEntryId ?? ""
155509
156363
  ].join("\0");
155510
- return `derived:${createHash14("sha256").update(material).digest("hex").slice(0, 32)}`;
156364
+ return `derived:${createHash15("sha256").update(material).digest("hex").slice(0, 32)}`;
155511
156365
  }
155512
156366
  function rowToCheckpoint(row) {
155513
156367
  return {
@@ -155566,7 +156420,7 @@ var init_compaction_checkpoint_store = __esm({
155566
156420
  if (pending) return rowToCheckpoint(pending);
155567
156421
  const now3 = input.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
155568
156422
  const checkpoint = {
155569
- id: randomUUID14(),
156423
+ id: randomUUID16(),
155570
156424
  projectId: input.projectId,
155571
156425
  sessionId: input.sessionId,
155572
156426
  agent: input.agent,
@@ -155574,7 +156428,7 @@ var init_compaction_checkpoint_store = __esm({
155574
156428
  captureKind: "preflight",
155575
156429
  reason: safeReason(input.reason),
155576
156430
  sourceEvent: input.sourceEvent,
155577
- sourceKey: `pre:${randomUUID14()}`,
156431
+ sourceKey: `pre:${randomUUID16()}`,
155578
156432
  transcriptAvailable: Boolean(input.transcriptAvailable),
155579
156433
  status: "active",
155580
156434
  preCapturedAt: now3,
@@ -155640,7 +156494,7 @@ var init_compaction_checkpoint_store = __esm({
155640
156494
  return this.get(checkpoint2.id);
155641
156495
  }
155642
156496
  const checkpoint = {
155643
- id: randomUUID14(),
156497
+ id: randomUUID16(),
155644
156498
  projectId: input.projectId,
155645
156499
  sessionId: input.sessionId,
155646
156500
  agent: input.agent,
@@ -157004,7 +157858,7 @@ __export(team_store_exports, {
157004
157858
  isTeamStoreInitialized: () => isTeamStoreInitialized,
157005
157859
  resetTeamStore: () => resetTeamStore
157006
157860
  });
157007
- import { randomUUID as randomUUID15 } from "crypto";
157861
+ import { randomUUID as randomUUID17 } from "crypto";
157008
157862
  import path30 from "path";
157009
157863
  import fs18 from "fs";
157010
157864
  function getTeamStore() {
@@ -157293,7 +158147,7 @@ var init_team_store = __esm({
157293
158147
  * reactivate it (preserving agent_id). Otherwise create a new one.
157294
158148
  */
157295
158149
  registerAgent(input) {
157296
- const instanceId = input.instanceId || randomUUID15();
158150
+ const instanceId = input.instanceId || randomUUID17();
157297
158151
  const now3 = Date.now();
157298
158152
  const existing = this.stmtAgentFindByInstance.get(
157299
158153
  input.projectId,
@@ -157319,7 +158173,7 @@ var init_team_store = __esm({
157319
158173
  this.eventBus?.emit("agent:joined", { agentId: row2.agent_id, projectId: input.projectId, agentType: input.agentType });
157320
158174
  return row2;
157321
158175
  }
157322
- const agentId = randomUUID15();
158176
+ const agentId = randomUUID17();
157323
158177
  this.stmtAgentUpsert.run({
157324
158178
  agent_id: agentId,
157325
158179
  project_id: input.projectId,
@@ -157421,7 +158275,7 @@ var init_team_store = __esm({
157421
158275
  return { error: `Recipient agent '${input.recipientAgentId}' not found \u2014 cannot send to unknown agent` };
157422
158276
  }
157423
158277
  }
157424
- const id = randomUUID15();
158278
+ const id = randomUUID17();
157425
158279
  const now3 = Date.now();
157426
158280
  const row = {
157427
158281
  id,
@@ -157471,7 +158325,7 @@ var init_team_store = __esm({
157471
158325
  // Task Operations (with atomic claim semantics)
157472
158326
  // ═══════════════════════════════════════════════════════════════════
157473
158327
  createTask(input) {
157474
- const taskId = randomUUID15();
158328
+ const taskId = randomUUID17();
157475
158329
  const now3 = Date.now();
157476
158330
  const row = {
157477
158331
  task_id: taskId,
@@ -157743,7 +158597,7 @@ var init_team_store = __esm({
157743
158597
  for (const [agentId, msgs] of Object.entries(snap.messages.inboxes)) {
157744
158598
  for (const msg of msgs) {
157745
158599
  this.stmtMsgInsert.run({
157746
- id: msg.id ?? randomUUID15(),
158600
+ id: msg.id ?? randomUUID17(),
157747
158601
  project_id: "migrated",
157748
158602
  sender_agent_id: msg.from ?? "unknown",
157749
158603
  recipient_agent_id: msg.to === "__broadcast__" ? null : msg.to ?? agentId,
@@ -158614,6 +159468,23 @@ var init_maintenance = __esm({
158614
159468
  }
158615
159469
  });
158616
159470
 
159471
+ // src/dashboard/config-provenance.ts
159472
+ function resolveDashboardKeySource({ value, envKeys, configSource }) {
159473
+ for (const envKey of envKeys) {
159474
+ const envValue = process.env[envKey];
159475
+ if (envValue !== value) continue;
159476
+ return getLoadedEnvValue(envKey) === envValue ? ".env" : `env:${envKey}`;
159477
+ }
159478
+ return configSource;
159479
+ }
159480
+ var init_config_provenance = __esm({
159481
+ "src/dashboard/config-provenance.ts"() {
159482
+ "use strict";
159483
+ init_esm_shims();
159484
+ init_dotenv_loader();
159485
+ }
159486
+ });
159487
+
158617
159488
  // src/dashboard/project-classification.ts
158618
159489
  var project_classification_exports = {};
158619
159490
  __export(project_classification_exports, {
@@ -159393,6 +160264,20 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
159393
160264
  sendJson(res, observations2);
159394
160265
  break;
159395
160266
  }
160267
+ case "/evidence": {
160268
+ const observations2 = filterDashboardObservations(
160269
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
160270
+ effectiveProjectId
160271
+ );
160272
+ const evidenceStore = new EvidenceCardStore();
160273
+ await evidenceStore.init(effectiveDataDir);
160274
+ evidenceStore.syncObservations(observations2);
160275
+ const query = url.searchParams.get("q") || "";
160276
+ const limit2 = Number(url.searchParams.get("limit") || 20);
160277
+ const cards = evidenceStore.list(effectiveProjectId, { limit: Math.min(100, Math.max(1, limit2)) }).filter((card) => !query || [card.title, card.summary, card.sourceRef, card.locator ?? ""].join(" ").toLowerCase().includes(query.toLowerCase()));
160278
+ sendJson(res, { cards, projectId: effectiveProjectId });
160279
+ break;
160280
+ }
159396
160281
  case "/sessions": {
159397
160282
  const allSessions = await getSessionStore().loadAll();
159398
160283
  const sessions = filterByProject(allSessions, effectiveProjectId);
@@ -159440,8 +160325,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
159440
160325
  filesModified: o.filesModified
159441
160326
  }));
159442
160327
  const retentionReferenceTime = new Date(now3);
160328
+ const feedback = new MemoryFeedbackStore();
160329
+ await feedback.init(effectiveDataDir).catch(() => void 0);
159443
160330
  const retention = summarizeRetentionProjections(
159444
- observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, { referenceTime: retentionReferenceTime }))
160331
+ observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, {
160332
+ referenceTime: retentionReferenceTime,
160333
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
160334
+ }))
159445
160335
  );
159446
160336
  const retentionSummary = {
159447
160337
  active: retention.active,
@@ -159535,9 +160425,14 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
159535
160425
  effectiveProjectId
159536
160426
  );
159537
160427
  const referenceTime = /* @__PURE__ */ new Date();
160428
+ const feedback = new MemoryFeedbackStore();
160429
+ await feedback.init(effectiveDataDir).catch(() => void 0);
159538
160430
  const rows = observations2.filter((observation) => observation.type !== "probe").map((observation) => ({
159539
160431
  observation,
159540
- retention: projectObservationRetention(observation, { referenceTime })
160432
+ retention: projectObservationRetention(observation, {
160433
+ referenceTime,
160434
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
160435
+ })
159541
160436
  })).sort((a, b) => b.retention.displayScore - a.retention.displayScore);
159542
160437
  const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
159543
160438
  const scored = rows.map(({ observation, retention }) => ({
@@ -159608,23 +160503,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
159608
160503
  const os6 = await import("os");
159609
160504
  const { existsSync: existsSync21 } = await import("fs");
159610
160505
  const { join: join25 } = await import("path");
159611
- let yml = {};
159612
160506
  const configProjectRoot = effectiveProjectRoot;
159613
- try {
159614
- const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
159615
- yml = configProjectRoot ? loadYamlConfig2(configProjectRoot) : loadYamlConfig2(null);
159616
- } catch {
159617
- }
159618
- if (configProjectRoot) {
159619
- try {
159620
- const { loadDotenv: loadDotenv2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
159621
- loadDotenv2(configProjectRoot);
159622
- } catch {
159623
- }
159624
- }
160507
+ const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
159625
160508
  const files = {
159626
160509
  "project memorix.yml": { exists: false, path: "", unavailable: !configProjectRoot },
159627
160510
  "user memorix.yml": { exists: false, path: "" },
160511
+ "project memorix.toml": { exists: false, path: "", unavailable: !configProjectRoot },
160512
+ "user config.toml": { exists: false, path: "" },
159628
160513
  "project .env": { exists: false, path: "", unavailable: !configProjectRoot },
159629
160514
  "user .env": { exists: false, path: "" },
159630
160515
  "legacy config.json": { exists: false, path: "" }
@@ -159634,6 +160519,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
159634
160519
  const paths = {
159635
160520
  "project memorix.yml": configProjectRoot ? join25(configProjectRoot, "memorix.yml") : null,
159636
160521
  "user memorix.yml": join25(home, ".memorix", "memorix.yml"),
160522
+ "project memorix.toml": configProjectRoot ? join25(configProjectRoot, "memorix.toml") : null,
160523
+ "user config.toml": join25(home, ".memorix", "config.toml"),
159637
160524
  "project .env": configProjectRoot ? join25(configProjectRoot, ".env") : null,
159638
160525
  "user .env": join25(home, ".memorix", ".env"),
159639
160526
  "legacy config.json": join25(home, ".memorix", "config.json")
@@ -159648,67 +160535,61 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
159648
160535
  } catch {
159649
160536
  }
159650
160537
  const values = [];
159651
- const getEnvSource = async (envKey, ymlSource) => {
159652
- if (process.env[envKey]) {
159653
- try {
159654
- const { getLoadedEnvFiles: getLoadedEnvFiles2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
159655
- const envFiles = getLoadedEnvFiles2();
159656
- if (envFiles.length > 0) return `.env (${envKey})`;
159657
- } catch {
159658
- }
159659
- return `env:${envKey}`;
159660
- }
159661
- return ymlSource ?? "default";
159662
- };
159663
- const llmProvider = process.env.MEMORIX_LLM_PROVIDER || yml.llm?.provider;
159664
- if (llmProvider) values.push({ key: "llm.provider", value: llmProvider, source: await getEnvSource("MEMORIX_LLM_PROVIDER", yml.llm?.provider ? "memorix.yml" : void 0) });
159665
- const llmModel = process.env.MEMORIX_LLM_MODEL || yml.llm?.model;
159666
- if (llmModel) values.push({ key: "llm.model", value: llmModel, source: await getEnvSource("MEMORIX_LLM_MODEL", yml.llm?.model ? "memorix.yml" : void 0) });
159667
- const llmKey = process.env.MEMORIX_LLM_API_KEY || process.env.MEMORIX_API_KEY || yml.llm?.apiKey || process.env.OPENAI_API_KEY;
160538
+ const tomlSource = configProjectRoot && files["project memorix.toml"].exists ? "memorix.toml" : files["user config.toml"].exists ? "config.toml" : "default";
160539
+ const legacySource = files["project memorix.yml"].exists || files["user memorix.yml"].exists ? "memorix.yml" : files["legacy config.json"].exists ? "config.json" : "default";
160540
+ const configSource = resolved.sources.toml.length > 0 ? tomlSource : resolved.sources.legacy.length > 0 ? legacySource : "default";
160541
+ const source = (envKey) => resolveDashboardKeySource({
160542
+ value: process.env[envKey] ?? "",
160543
+ envKeys: [envKey],
160544
+ configSource
160545
+ });
160546
+ if (resolved.memory.llm.provider) values.push({ key: "llm.provider", value: resolved.memory.llm.provider, source: source("MEMORIX_LLM_PROVIDER") });
160547
+ if (resolved.memory.llm.model) values.push({ key: "llm.model", value: resolved.memory.llm.model, source: source("MEMORIX_LLM_MODEL") });
160548
+ const llmKey = resolved.memory.llm.apiKey;
160549
+ const memoryKeyEnv = [
160550
+ "MEMORIX_LLM_API_KEY",
160551
+ "MEMORIX_API_KEY",
160552
+ "OPENAI_API_KEY",
160553
+ "ANTHROPIC_API_KEY",
160554
+ ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : []
160555
+ ];
159668
160556
  if (llmKey) {
159669
- let src = "unknown";
159670
- if (process.env.MEMORIX_LLM_API_KEY) src = await getEnvSource("MEMORIX_LLM_API_KEY");
159671
- else if (process.env.MEMORIX_API_KEY) src = await getEnvSource("MEMORIX_API_KEY");
159672
- else if (yml.llm?.apiKey) src = "memorix.yml (move to .env!)";
159673
- else if (process.env.OPENAI_API_KEY) src = await getEnvSource("OPENAI_API_KEY");
160557
+ const src = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
159674
160558
  values.push({ key: "llm.apiKey", value: "****" + llmKey.slice(-4), source: src, sensitive: true });
159675
160559
  } else {
159676
160560
  values.push({ key: "llm.apiKey", value: "not set", source: "none" });
159677
160561
  }
159678
- const agentProvider = process.env.MEMORIX_AGENT_PROVIDER || process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
159679
- if (agentProvider) {
159680
- let source;
159681
- if (process.env.MEMORIX_AGENT_PROVIDER) source = await getEnvSource("MEMORIX_AGENT_PROVIDER");
159682
- else if (process.env.MEMORIX_AGENT_LLM_PROVIDER) source = `${await getEnvSource("MEMORIX_AGENT_LLM_PROVIDER")} (legacy)`;
159683
- else source = yml.agent?.provider ? "memorix.yml" : "default";
159684
- values.push({ key: "agent.provider", value: agentProvider, source });
159685
- }
159686
- const agentModel = process.env.MEMORIX_AGENT_MODEL || process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
159687
- if (agentModel) {
159688
- let source;
159689
- if (process.env.MEMORIX_AGENT_MODEL) source = await getEnvSource("MEMORIX_AGENT_MODEL");
159690
- else if (process.env.MEMORIX_AGENT_LLM_MODEL) source = `${await getEnvSource("MEMORIX_AGENT_LLM_MODEL")} (legacy)`;
159691
- else source = yml.agent?.model ? "memorix.yml" : "default";
159692
- values.push({ key: "agent.model", value: agentModel, source });
159693
- }
159694
- const agentKey = process.env.MEMORIX_AGENT_API_KEY || process.env.MEMORIX_AGENT_LLM_API_KEY || yml.agent?.apiKey;
160562
+ if (resolved.agent.provider) {
160563
+ values.push({ key: "agent.provider", value: resolved.agent.provider, source: source("MEMORIX_AGENT_PROVIDER") });
160564
+ }
160565
+ if (resolved.agent.model) {
160566
+ values.push({ key: "agent.model", value: resolved.agent.model, source: source("MEMORIX_AGENT_MODEL") });
160567
+ }
160568
+ const agentKey = resolved.agent.apiKey;
159695
160569
  if (agentKey) {
159696
- let src = "unknown";
159697
- if (process.env.MEMORIX_AGENT_API_KEY) src = await getEnvSource("MEMORIX_AGENT_API_KEY");
159698
- else if (process.env.MEMORIX_AGENT_LLM_API_KEY) src = `${await getEnvSource("MEMORIX_AGENT_LLM_API_KEY")} (legacy)`;
159699
- else if (yml.agent?.apiKey) src = "memorix.yml (move to .env!)";
160570
+ const src = resolveDashboardKeySource({
160571
+ value: agentKey,
160572
+ envKeys: ["MEMORIX_AGENT_API_KEY", "MEMORIX_AGENT_LLM_API_KEY"],
160573
+ configSource
160574
+ });
159700
160575
  values.push({ key: "agent.apiKey", value: "****" + agentKey.slice(-4), source: src, sensitive: true });
159701
160576
  } else {
159702
- values.push({ key: "agent.apiKey", value: "fallback to llm.apiKey", source: "default" });
159703
- }
159704
- const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
159705
- values.push({ key: "embedding.provider", value: embProvider, source: await getEnvSource("MEMORIX_EMBEDDING", yml.embedding?.provider ? "memorix.yml" : void 0) });
159706
- values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
159707
- values.push({ key: "git.skipMergeCommits", value: String(yml.git?.skipMergeCommits ?? true), source: yml.git?.skipMergeCommits !== void 0 ? "memorix.yml" : "default" });
159708
- if (yml.behavior?.formationMode) values.push({ key: "behavior.formationMode", value: yml.behavior.formationMode, source: "memorix.yml" });
159709
- if (yml.behavior?.sessionInject) values.push({ key: "behavior.sessionInject", value: yml.behavior.sessionInject, source: "memorix.yml" });
159710
- values.push({ key: "server.transport", value: yml.server?.transport || "stdio", source: yml.server?.transport ? "memorix.yml" : "default" });
159711
- values.push({ key: "server.dashboard", value: String(yml.server?.dashboard ?? true), source: yml.server?.dashboard !== void 0 ? "memorix.yml" : "default" });
160577
+ values.push({
160578
+ key: "agent.apiKey",
160579
+ value: "fallback to llm.apiKey",
160580
+ source: llmKey ? resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource }) : "default"
160581
+ });
160582
+ }
160583
+ values.push({ key: "embedding.provider", value: resolved.embedding.provider || "off", source: source("MEMORIX_EMBEDDING") });
160584
+ if (resolved.embedding.model) values.push({ key: "embedding.model", value: resolved.embedding.model, source: source("MEMORIX_EMBEDDING_MODEL") });
160585
+ if (resolved.rerank.provider !== "off") {
160586
+ values.push({ key: "rerank.provider", value: resolved.rerank.provider, source: source("MEMORIX_RERANK_PROVIDER") });
160587
+ if (resolved.rerank.model) values.push({ key: "rerank.model", value: resolved.rerank.model, source: source("MEMORIX_RERANK_MODEL") });
160588
+ }
160589
+ values.push({ key: "git.autoHook", value: String(resolved.git.autoHook ?? false), source: configSource });
160590
+ values.push({ key: "git.skipMergeCommits", value: String(resolved.git.skipMergeCommits ?? true), source: configSource });
160591
+ values.push({ key: "server.transport", value: resolved.server.transport || "stdio", source: configSource });
160592
+ values.push({ key: "server.dashboard", value: String(resolved.server.dashboard ?? true), source: configSource });
159712
160593
  sendJson(res, { files, values });
159713
160594
  break;
159714
160595
  }
@@ -160156,10 +161037,14 @@ var init_server = __esm({
160156
161037
  init_dotenv_loader();
160157
161038
  init_yaml_loader();
160158
161039
  init_yaml_loader();
161040
+ init_resolved_config();
160159
161041
  init_graph_scope();
160160
161042
  init_retention();
160161
161043
  init_visibility();
161044
+ init_evidence_store();
161045
+ init_feedback();
160162
161046
  init_maintenance();
161047
+ init_config_provenance();
160163
161048
  MIME_TYPES = {
160164
161049
  ".html": "text/html; charset=utf-8",
160165
161050
  ".css": "text/css; charset=utf-8",
@@ -160627,7 +161512,7 @@ __export(planner_exports, {
160627
161512
  materializeTaskGraph: () => materializeTaskGraph,
160628
161513
  seedAutonomousPipeline: () => seedAutonomousPipeline
160629
161514
  });
160630
- import { randomUUID as randomUUID16 } from "crypto";
161515
+ import { randomUUID as randomUUID18 } from "crypto";
160631
161516
  function isPlannerTask(metadata) {
160632
161517
  if (!metadata) return null;
160633
161518
  try {
@@ -160685,7 +161570,7 @@ function seedAutonomousPipeline(teamStore, projectId, config, opts) {
160685
161570
  const maxIterations = config.maxIterations ?? 3;
160686
161571
  const taskBudget = config.taskBudget ?? 15;
160687
161572
  const structuredPlan = opts?.structuredPlan ?? true;
160688
- const pipelineId = randomUUID16();
161573
+ const pipelineId = randomUUID18();
160689
161574
  const meta = {
160690
161575
  plannerType: "plan",
160691
161576
  pipelineId,
@@ -161220,8 +162105,7 @@ async function callVisionLLM(systemPrompt, imageBase64, mimeType) {
161220
162105
  if (!apiKey) {
161221
162106
  throw new Error("No LLM API key configured for image analysis.");
161222
162107
  }
161223
- let baseUrl = getLLMBaseUrl("https://api.openai.com/v1").replace(/\/+$/, "");
161224
- if (!baseUrl.endsWith("/v1")) baseUrl += "/v1";
162108
+ const baseUrl = normalizeOpenAICompatibleBaseUrl(getLLMBaseUrl("https://api.openai.com/v1"));
161225
162109
  const model = getLLMModel("gpt-4o");
161226
162110
  const response = await fetch(`${baseUrl}/chat/completions`, {
161227
162111
  method: "POST",
@@ -161358,8 +162242,8 @@ var init_official_skills = __esm({
161358
162242
  "",
161359
162243
  "| Situation | Prefer | CLI fallback |",
161360
162244
  "|---|---|---|",
161361
- '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --brief-json` |',
161362
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
162245
+ '| Starting a coding task and needing the best task-lensed project map | `memorix_project_context` with the user\'s task | `memorix context "<task>" --fallback --brief-json` |',
162246
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --fallback --brief-json` |',
161363
162247
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
161364
162248
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
161365
162249
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -161372,7 +162256,7 @@ var init_official_skills = __esm({
161372
162256
  "",
161373
162257
  "- Default first step for non-trivial coding work: call `memorix_project_context` before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Pass the user's actual task so Memorix can choose a bugfix, feature, release, onboarding, refactor, docs, test, or general brief. Start by reading its suggested files.",
161374
162258
  "- In Claude Code headless/print-mode, an initial MCP `pending` status is not a failure by itself. Prefer MCP first: use the client's tool discovery/dynamic loading to find `memorix_project_context` when it is not in the first visible tool list.",
161375
- '- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --brief-json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --brief-json` instead. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
162259
+ '- Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run `memorix resume "<task>" --fallback --brief-json` exactly once before inspecting files, Git history, progress notes, or guessing. The absence of `.memorix` or visible memory files never proves project memory is empty. For a new task, use `memorix context "<task>" --fallback --brief-json` instead. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally; do not probe help, enumerate commands, or chain broad searches.',
161376
162260
  "- After a successful `memorix_project_context`, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use Context Pack, search, or detail only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.",
161377
162261
  "- If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.",
161378
162262
  "- Use `memorix_search` for a specific decision, bug, file, or prior change that the project context did not answer. Fetch detail only for a result you still need after the brief.",
@@ -162631,7 +163515,7 @@ alwaysApply: true
162631
163515
  "",
162632
163516
  `Default first step for non-trivial coding work: call \`memorix_project_context\` with the user's actual task before progress files, dev-log reads, ad-hoc file reads, or git archaeology. Memorix will choose a task-lensed brief (bugfix, feature, release, onboarding, refactor, docs, test, or general). When the task is continuing prior work, the same brief also includes a bounded prior-work projection. Treat its "Start here" files as the first ${contextNoun} files to inspect.`,
162633
163517
  "",
162634
- 'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: `memorix resume "<task>" --brief-json`. For a new task, use `memorix context "<task>" --brief-json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
163518
+ 'If the MCP tool is not visible yet but the client supports tool discovery or dynamic loading, search/select `memorix_project_context` first. Continuation fallback is mandatory: when the user asks to continue, resume, take over, or explain prior work and MCP cannot be called in this turn, run exactly one CLI brief with the user\'s real task before inspecting files, Git history, progress notes, or guessing: `memorix resume "<task>" --fallback --brief-json`. For a new task, use `memorix context "<task>" --fallback --brief-json` instead. The absence of `.memorix` or visible memory files never proves project memory is empty. Use `--json` only when a diagnostic needs the detailed legacy payload. If that one command fails, report it and proceed normally. Do not probe help, enumerate commands, chain broad searches, wait indefinitely on MCP startup, or hand-write tool-call syntax.',
162635
163519
  "",
162636
163520
  "After a successful `memorix_project_context` result, the brief is the default retrieval boundary. Do not call more Memorix retrieval tools after a complete brief. Use `memorix_context_pack`, `memorix_search`, or `memorix_detail` only when the brief lacks a specific reference, freshness field, or fact needed for the task, or when the user explicitly asks for deeper history. In MCP, name that missing fact in `purpose` when intentionally expanding beyond the brief. Do not retrieve the same decision twice just to confirm an already-complete brief.",
162637
163521
  "If the user asks for read-only work or says not to modify files, do not call `memorix_store` just to record an assessment. Store only when the user explicitly asks to preserve it.",
@@ -162934,7 +163818,7 @@ init_freshness();
162934
163818
  init_obs_store();
162935
163819
  init_mini_skill_store();
162936
163820
  init_session_store();
162937
- import { createHash as createHash15 } from "crypto";
163821
+ import { createHash as createHash16 } from "crypto";
162938
163822
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
162939
163823
  import { z as z626 } from "zod";
162940
163824
 
@@ -163414,6 +164298,8 @@ init_mini_skill_store();
163414
164298
  init_mini_skills();
163415
164299
  init_secret_filter();
163416
164300
  init_visibility();
164301
+ init_persistence();
164302
+ init_feedback();
163417
164303
  function normalizeMemoryBrowseQuery(query) {
163418
164304
  const trimmed = query.trim();
163419
164305
  if (!trimmed) return "";
@@ -163451,9 +164337,27 @@ function normalizeMemoryBrowseQuery(query) {
163451
164337
  async function compactSearch(options, surface = "mcp") {
163452
164338
  await ensureFreshIndex();
163453
164339
  const searchOptions = { ...options, query: normalizeMemoryBrowseQuery(options.query) };
163454
- const entries = (await searchObservations(searchOptions)).map(
164340
+ let entries = (await searchObservations(searchOptions)).map(
163455
164341
  (entry) => entry.projectId || !options.projectId ? entry : { ...entry, projectId: options.projectId }
163456
164342
  );
164343
+ if (options.projectId) {
164344
+ try {
164345
+ const feedback = new MemoryFeedbackStore();
164346
+ await feedback.init(await getProjectDataDir(options.projectId));
164347
+ const states = feedback.weights(options.projectId, "observation");
164348
+ entries = entries.map((entry) => {
164349
+ const state = states.get(String(entry.id));
164350
+ if (!state) return entry;
164351
+ return {
164352
+ ...entry,
164353
+ score: (entry.score ?? 0) * feedbackWeightMultiplier(state.weight),
164354
+ feedbackWeight: state.weight,
164355
+ feedbackStatus: state.status
164356
+ };
164357
+ }).filter((entry) => entry.feedbackStatus !== "archived" || options.status === "all").sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
164358
+ } catch {
164359
+ }
164360
+ }
163457
164361
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId, surface);
163458
164362
  if (entries.length === 0 && options.projectId) {
163459
164363
  const allObservations = filterReadableObservations(getAllObservations(), options.reader);
@@ -164282,6 +165186,8 @@ init_detector();
164282
165186
  init_launch_root();
164283
165187
  init_aliases();
164284
165188
  init_persistence();
165189
+ init_evidence_store();
165190
+ init_feedback();
164285
165191
 
164286
165192
  // src/rules/syncer.ts
164287
165193
  init_esm_shims();
@@ -164294,9 +165200,9 @@ init_frontmatter();
164294
165200
 
164295
165201
  // src/rules/utils.ts
164296
165202
  init_esm_shims();
164297
- import { createHash as createHash13 } from "crypto";
165203
+ import { createHash as createHash14 } from "crypto";
164298
165204
  function hashContent2(content) {
164299
- return createHash13("sha256").update(content.trim()).digest("hex").substring(0, 16);
165205
+ return createHash14("sha256").update(content.trim()).digest("hex").substring(0, 16);
164300
165206
  }
164301
165207
  function generateRuleId(source, filePath) {
164302
165208
  const sanitized = filePath.replace(/[\/\\]/g, "-").replace(/^\./, "");
@@ -166498,6 +167404,10 @@ var TOOL_PROFILES = Object.freeze({
166498
167404
  // multimodal support to one additional schema rather than enabling lite.
166499
167405
  memorix_session_start: ["micro", "lite", "team", "full"],
166500
167406
  memorix_media: ["micro", "lite", "team", "full"],
167407
+ // Evidence and feedback are deliberate operator/context-control surfaces;
167408
+ // keep the default micro profile at its nine-tool token budget.
167409
+ memorix_evidence: ["lite", "team", "full"],
167410
+ memorix_feedback: ["lite", "team", "full"],
166501
167411
  // ── lite: extended solo memory surface ────────────────────────────
166502
167412
  memorix_graph_context: ["lite", "team", "full"],
166503
167413
  memorix_timeline: ["lite", "team", "full"],
@@ -166796,7 +167706,7 @@ function coerceObjectArray(val) {
166796
167706
  return [];
166797
167707
  }
166798
167708
  function createDeterministicInstanceId(projectId, agentType, agentName) {
166799
- const digest2 = createHash15("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
167709
+ const digest2 = createHash16("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
166800
167710
  return `auto-${digest2}`;
166801
167711
  }
166802
167712
  var BOOTSTRAP_SAFE_TOOL_NAMES = /* @__PURE__ */ new Set([
@@ -167097,7 +168007,7 @@ The path should point to a directory containing a .git folder.`
167097
168007
  };
167098
168008
  const server = existingServer ?? new McpServer({
167099
168009
  name: "memorix",
167100
- version: true ? "1.7.2" : "1.0.1"
168010
+ version: true ? "1.8.0" : "1.0.1"
167101
168011
  });
167102
168012
  const originalRegisterTool = server.registerTool.bind(server);
167103
168013
  server.registerTool = ((name, ...args) => {
@@ -170276,7 +171186,7 @@ ${json2}
170276
171186
  dashboardRunning = true;
170277
171187
  const { exec: execCmd } = await import("child_process");
170278
171188
  const openCmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
170279
- execCmd(openCmd, () => {
171189
+ execCmd(openCmd, { windowsHide: true }, () => {
170280
171190
  });
170281
171191
  return {
170282
171192
  content: [{
@@ -170300,6 +171210,113 @@ ${json2}
170300
171210
  }
170301
171211
  }
170302
171212
  );
171213
+ server.registerTool(
171214
+ "memorix_evidence",
171215
+ {
171216
+ title: "Evidence Cards",
171217
+ description: "Inspect the bounded, project-scoped provenance cards behind Memorix context. Use list for a compact index, get/events for one source, sync to repair the projection, and stale when known source files changed. Cards expose sourceRef, locator, capturedHash, and freshness.",
171218
+ inputSchema: {
171219
+ action: z626.enum(["list", "get", "sync", "stale", "events"]).describe("Evidence operation"),
171220
+ candidateKind: z626.enum(["observation", "code", "git", "test", "document", "workflow", "graph"]).optional().default("observation"),
171221
+ candidateId: z626.string().optional().describe("Candidate id, usually an observation id"),
171222
+ query: z626.string().optional().describe("Optional title/source query for list"),
171223
+ paths: z626.array(z626.string()).max(200).optional().describe("Changed source paths for stale"),
171224
+ reason: z626.string().max(500).optional().describe("Why evidence became stale"),
171225
+ limit: z626.number().int().positive().max(100).optional().default(20)
171226
+ }
171227
+ },
171228
+ async ({ action, candidateKind, candidateId, query, paths, reason, limit: limit2 }) => {
171229
+ const unresolved = requireResolvedProject("inspect evidence cards for the current project");
171230
+ if (unresolved) return unresolved;
171231
+ const cards = new EvidenceCardStore();
171232
+ await cards.init(projectDir2);
171233
+ if (action === "sync") {
171234
+ const observations2 = await getObservationStore().loadByProject(project.id);
171235
+ const synced = cards.syncObservations(observations2);
171236
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, synced: synced.length, cards: synced.slice(0, limit2 ?? 20) }, null, 2) }] };
171237
+ }
171238
+ if (action === "stale") {
171239
+ const changed = cards.markStaleForPaths(project.id, paths ?? [], reason ?? "source file changed");
171240
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, staleMarked: changed, paths: paths ?? [] }, null, 2) }] };
171241
+ }
171242
+ if (action === "get") {
171243
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence get." }], isError: true };
171244
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
171245
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
171246
+ return { content: [{ type: "text", text: JSON.stringify({ card, events: cards.listEvents(card.id), projectId: project.id }, null, 2) }] };
171247
+ }
171248
+ if (action === "events") {
171249
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence events." }], isError: true };
171250
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
171251
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
171252
+ return { content: [{ type: "text", text: JSON.stringify(cards.listEvents(card.id, limit2), null, 2) }] };
171253
+ }
171254
+ const needle = query?.trim().toLowerCase();
171255
+ const listed = cards.list(project.id, { limit: Math.max(limit2 ?? 20, 100) }).filter((card) => !needle || [card.title, card.summary, card.sourceRef, card.locator ?? ""].join(" ").toLowerCase().includes(needle)).slice(0, limit2 ?? 20);
171256
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, cards: listed }, null, 2) }] };
171257
+ }
171258
+ );
171259
+ server.registerTool(
171260
+ "memorix_feedback",
171261
+ {
171262
+ title: "Memory Feedback",
171263
+ description: "Record or inspect auditable memory outcomes. Positive verification/use strengthens recall; correction, conflict, source change, or weaken lowers it. Use revoke with targetEventId to undo one prior signal without deleting audit history.",
171264
+ inputSchema: {
171265
+ action: z626.enum(["record", "show", "audit"]).describe("Feedback operation"),
171266
+ candidateKind: z626.enum(["observation", "claim", "durable-memory", "workflow"]).optional().default("observation"),
171267
+ candidateId: z626.string().describe("Memory or knowledge candidate id"),
171268
+ signal: z626.enum(["user-correction", "verification-success", "verification-failure", "code-change", "used", "not-used", "code-conflict", "strengthen", "weaken", "revoke"]).optional(),
171269
+ sourceRef: z626.string().max(500).optional().describe("Source of this feedback, such as test:run-42 or user:chat"),
171270
+ actor: z626.string().max(200).optional(),
171271
+ note: z626.string().max(2e3).optional(),
171272
+ targetEventId: z626.string().optional().describe("Existing event id when action=record and signal=revoke"),
171273
+ limit: z626.number().int().positive().max(200).optional().default(100)
171274
+ }
171275
+ },
171276
+ async ({ action, candidateKind, candidateId, signal, sourceRef, actor, note, targetEventId, limit: limit2 }) => {
171277
+ const unresolved = requireResolvedProject("record memory feedback for the current project");
171278
+ if (unresolved) return unresolved;
171279
+ const feedback = new MemoryFeedbackStore();
171280
+ await feedback.init(projectDir2);
171281
+ const kind = candidateKind ?? "observation";
171282
+ if (action === "show") {
171283
+ return { content: [{ type: "text", text: JSON.stringify(feedback.getState(project.id, kind, candidateId), null, 2) }] };
171284
+ }
171285
+ if (action === "audit") {
171286
+ return { content: [{ type: "text", text: JSON.stringify(feedback.audit(project.id, kind, candidateId, limit2), null, 2) }] };
171287
+ }
171288
+ if (!signal || !sourceRef?.trim()) {
171289
+ return { content: [{ type: "text", text: "record requires signal and sourceRef." }], isError: true };
171290
+ }
171291
+ try {
171292
+ const result = feedback.record({
171293
+ projectId: project.id,
171294
+ candidateKind: kind,
171295
+ candidateId,
171296
+ signal,
171297
+ sourceRef,
171298
+ ...actor || currentAgentId ? { actor: actor ?? currentAgentId } : {},
171299
+ ...note ? { note } : {},
171300
+ ...targetEventId ? { targetEventId } : {}
171301
+ });
171302
+ if (kind === "observation") {
171303
+ const cards = new EvidenceCardStore();
171304
+ await cards.init(projectDir2);
171305
+ const card = cards.get(project.id, "observation", candidateId);
171306
+ if (card) {
171307
+ const verification = signal === "verification-success" || signal === "strengthen" ? "verified" : signal === "user-correction" || signal === "code-conflict" || signal === "weaken" ? "conflicted" : card.verification;
171308
+ cards.recordEvent(project.id, card.id, `feedback:${signal}`, note);
171309
+ if (verification !== card.verification) {
171310
+ cards.setVerification(project.id, card.id, verification);
171311
+ }
171312
+ }
171313
+ }
171314
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
171315
+ } catch (error) {
171316
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true };
171317
+ }
171318
+ }
171319
+ );
170303
171320
  if (teamFeaturesEnabled) {
170304
171321
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
170305
171322
  initTeamStoreForProject = initTeamStore2;