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/index.js CHANGED
@@ -402,7 +402,7 @@ function closeAllDatabases() {
402
402
  _dbCache.delete(key);
403
403
  }
404
404
  }
405
- 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;
405
+ 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;
406
406
  var init_sqlite_db = __esm({
407
407
  "src/store/sqlite-db.ts"() {
408
408
  "use strict";
@@ -1048,6 +1048,79 @@ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
1048
1048
  created_at TEXT NOT NULL,
1049
1049
  updated_at TEXT NOT NULL
1050
1050
  );
1051
+ `;
1052
+ CREATE_EVIDENCE_CARDS_TABLE = `
1053
+ CREATE TABLE IF NOT EXISTS evidence_cards (
1054
+ id TEXT PRIMARY KEY,
1055
+ project_id TEXT NOT NULL,
1056
+ candidate_kind TEXT NOT NULL,
1057
+ candidate_id TEXT NOT NULL,
1058
+ title TEXT NOT NULL,
1059
+ summary TEXT NOT NULL DEFAULT '',
1060
+ entity_name TEXT,
1061
+ source_kind TEXT NOT NULL,
1062
+ source_ref TEXT NOT NULL,
1063
+ locator TEXT,
1064
+ captured_hash TEXT,
1065
+ session_id TEXT,
1066
+ status TEXT NOT NULL DEFAULT 'active',
1067
+ verification TEXT NOT NULL DEFAULT 'unverified',
1068
+ freshness TEXT NOT NULL DEFAULT 'current',
1069
+ stale_reason TEXT,
1070
+ files_json TEXT NOT NULL DEFAULT '[]',
1071
+ related_entities_json TEXT NOT NULL DEFAULT '[]',
1072
+ created_at TEXT NOT NULL,
1073
+ updated_at TEXT NOT NULL,
1074
+ UNIQUE(project_id, candidate_kind, candidate_id)
1075
+ );
1076
+ `;
1077
+ CREATE_EVIDENCE_CARD_EVENTS_TABLE = `
1078
+ CREATE TABLE IF NOT EXISTS evidence_card_events (
1079
+ id TEXT PRIMARY KEY,
1080
+ project_id TEXT NOT NULL,
1081
+ card_id TEXT NOT NULL,
1082
+ kind TEXT NOT NULL,
1083
+ detail TEXT,
1084
+ created_at TEXT NOT NULL,
1085
+ FOREIGN KEY (card_id) REFERENCES evidence_cards(id) ON DELETE CASCADE
1086
+ );
1087
+ `;
1088
+ CREATE_MEMORY_FEEDBACK_STATES_TABLE = `
1089
+ CREATE TABLE IF NOT EXISTS memory_feedback_states (
1090
+ project_id TEXT NOT NULL,
1091
+ candidate_kind TEXT NOT NULL,
1092
+ candidate_id TEXT NOT NULL,
1093
+ weight REAL NOT NULL DEFAULT 1.0,
1094
+ lifecycle_status TEXT NOT NULL DEFAULT 'active',
1095
+ updated_at TEXT NOT NULL,
1096
+ PRIMARY KEY (project_id, candidate_kind, candidate_id)
1097
+ );
1098
+ `;
1099
+ CREATE_MEMORY_FEEDBACK_EVENTS_TABLE = `
1100
+ CREATE TABLE IF NOT EXISTS memory_feedback_events (
1101
+ id TEXT PRIMARY KEY,
1102
+ project_id TEXT NOT NULL,
1103
+ candidate_kind TEXT NOT NULL,
1104
+ candidate_id TEXT NOT NULL,
1105
+ signal TEXT NOT NULL,
1106
+ actor TEXT,
1107
+ note TEXT,
1108
+ source_ref TEXT NOT NULL,
1109
+ target_event_id TEXT,
1110
+ delta REAL NOT NULL DEFAULT 0,
1111
+ observed_at TEXT NOT NULL
1112
+ );
1113
+ `;
1114
+ CREATE_MCP_BINDINGS_TABLE = `
1115
+ CREATE TABLE IF NOT EXISTS mcp_bindings (
1116
+ handle_id TEXT PRIMARY KEY,
1117
+ project_id TEXT NOT NULL,
1118
+ project_root TEXT NOT NULL,
1119
+ data_dir TEXT NOT NULL,
1120
+ created_at TEXT NOT NULL,
1121
+ last_used_at TEXT NOT NULL,
1122
+ expires_at TEXT
1123
+ );
1051
1124
  `;
1052
1125
  CREATE_INDEXES = `
1053
1126
  CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
@@ -1110,6 +1183,12 @@ CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_target
1110
1183
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
1111
1184
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
1112
1185
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
1186
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC);
1187
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id);
1188
+ CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at);
1189
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC);
1190
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_events_candidate ON memory_feedback_events(project_id, candidate_kind, candidate_id, observed_at DESC);
1191
+ CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC);
1113
1192
  CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1114
1193
  ON maintenance_jobs(project_id, kind, dedupe_key)
1115
1194
  WHERE status IN ('pending', 'running', 'retry');
@@ -1265,6 +1344,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1265
1344
  }
1266
1345
  db2.exec("CREATE INDEX IF NOT EXISTS idx_media_jobs_source_asset ON media_jobs(project_id, source_asset_id, status)");
1267
1346
  }
1347
+ },
1348
+ {
1349
+ id: "1.8-context-control-plane",
1350
+ apply: (db2) => {
1351
+ db2.exec(CREATE_EVIDENCE_CARDS_TABLE);
1352
+ db2.exec(CREATE_EVIDENCE_CARD_EVENTS_TABLE);
1353
+ db2.exec(CREATE_MEMORY_FEEDBACK_STATES_TABLE);
1354
+ db2.exec(CREATE_MEMORY_FEEDBACK_EVENTS_TABLE);
1355
+ db2.exec(CREATE_MCP_BINDINGS_TABLE);
1356
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC)");
1357
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id)");
1358
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at)");
1359
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC)");
1360
+ 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)");
1361
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC)");
1362
+ }
1268
1363
  }
1269
1364
  ];
1270
1365
  _dbCache = /* @__PURE__ */ new Map();
@@ -2793,6 +2888,7 @@ function loadYamlConfig(projectRoot) {
2793
2888
  llm: { ...userConfig.llm, ...projectConfig.llm },
2794
2889
  agent: { ...userConfig.agent, ...projectConfig.agent },
2795
2890
  embedding: { ...userConfig.embedding, ...projectConfig.embedding },
2891
+ rerank: { ...userConfig.rerank, ...projectConfig.rerank },
2796
2892
  git: { ...userConfig.git, ...projectConfig.git },
2797
2893
  codegraph: { ...userConfig.codegraph, ...projectConfig.codegraph },
2798
2894
  behavior: { ...userConfig.behavior, ...projectConfig.behavior },
@@ -3068,6 +3164,7 @@ function mergeTomlConfig(base, override) {
3068
3164
  llm: { ...base.memory?.llm, ...override.memory?.llm }
3069
3165
  },
3070
3166
  embedding: { ...base.embedding, ...override.embedding },
3167
+ rerank: { ...base.rerank, ...override.rerank },
3071
3168
  hooks: { ...base.hooks, ...override.hooks },
3072
3169
  git: { ...base.git, ...override.git },
3073
3170
  codegraph: { ...base.codegraph, ...override.codegraph },
@@ -3160,6 +3257,8 @@ var init_toml_loader = __esm({
3160
3257
  var dotenv_loader_exports = {};
3161
3258
  __export(dotenv_loader_exports, {
3162
3259
  getLoadedEnvFiles: () => getLoadedEnvFiles,
3260
+ getLoadedEnvKeys: () => getLoadedEnvKeys,
3261
+ getLoadedEnvValue: () => getLoadedEnvValue,
3163
3262
  loadDotenv: () => loadDotenv,
3164
3263
  resetDotenv: () => resetDotenv
3165
3264
  });
@@ -3171,6 +3270,8 @@ function loadEnvFile(filePath) {
3171
3270
  const parsed = parse(readFileSync6(filePath, "utf-8"));
3172
3271
  for (const [key, value] of Object.entries(parsed)) {
3173
3272
  if (!(key in process.env)) {
3273
+ loadedEnvKeys.add(key);
3274
+ loadedEnvValues.set(key, value);
3174
3275
  process.env[key] = value;
3175
3276
  injectedKeys.add(key);
3176
3277
  }
@@ -3183,6 +3284,8 @@ function loadDotenv(projectRoot, options = {}) {
3183
3284
  resetDotenv();
3184
3285
  }
3185
3286
  loadedEnvFiles.length = 0;
3287
+ loadedEnvKeys.clear();
3288
+ loadedEnvValues.clear();
3186
3289
  if (projectRoot) {
3187
3290
  loadEnvFile(getProjectDotenvPath(projectRoot));
3188
3291
  }
@@ -3198,11 +3301,18 @@ function resetDotenv() {
3198
3301
  dotenvLoaded = false;
3199
3302
  dotenvProjectRoot = null;
3200
3303
  loadedEnvFiles.length = 0;
3304
+ loadedEnvKeys.clear();
3201
3305
  }
3202
3306
  function getLoadedEnvFiles() {
3203
3307
  return loadedEnvFiles;
3204
3308
  }
3205
- var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, injectedKeys;
3309
+ function getLoadedEnvKeys() {
3310
+ return loadedEnvKeys;
3311
+ }
3312
+ function getLoadedEnvValue(key) {
3313
+ return loadedEnvValues.get(key);
3314
+ }
3315
+ var dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, loadedEnvKeys, loadedEnvValues, injectedKeys;
3206
3316
  var init_dotenv_loader = __esm({
3207
3317
  "src/config/dotenv-loader.ts"() {
3208
3318
  "use strict";
@@ -3211,6 +3321,8 @@ var init_dotenv_loader = __esm({
3211
3321
  dotenvLoaded = false;
3212
3322
  dotenvProjectRoot = null;
3213
3323
  loadedEnvFiles = [];
3324
+ loadedEnvKeys = /* @__PURE__ */ new Set();
3325
+ loadedEnvValues = /* @__PURE__ */ new Map();
3214
3326
  injectedKeys = /* @__PURE__ */ new Set();
3215
3327
  }
3216
3328
  });
@@ -3223,6 +3335,7 @@ __export(resolved_config_exports, {
3223
3335
  getResolvedConfigForCwd: () => getResolvedConfigForCwd,
3224
3336
  getResolvedEmbeddingLane: () => getResolvedEmbeddingLane,
3225
3337
  getResolvedMemoryLane: () => getResolvedMemoryLane,
3338
+ getResolvedRerankLane: () => getResolvedRerankLane,
3226
3339
  resetResolvedConfigCache: () => resetResolvedConfigCache
3227
3340
  });
3228
3341
  import { homedir as homedir5 } from "os";
@@ -3255,6 +3368,16 @@ function getResolvedConfig(options = {}) {
3255
3368
  legacy.llm?.baseUrl
3256
3369
  );
3257
3370
  const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
3371
+ const memoryLlmApiKey = first(
3372
+ process.env.MEMORIX_LLM_API_KEY,
3373
+ process.env.MEMORIX_API_KEY,
3374
+ toml.memory?.llm?.api_key,
3375
+ yaml.llm?.apiKey,
3376
+ legacy.llm?.apiKey,
3377
+ process.env.OPENAI_API_KEY,
3378
+ process.env.ANTHROPIC_API_KEY,
3379
+ openRouterMemoryLlmApiKey
3380
+ );
3258
3381
  const resolved = {
3259
3382
  agent: {
3260
3383
  provider: first(
@@ -3295,16 +3418,7 @@ function getResolvedConfig(options = {}) {
3295
3418
  provider: memoryLlmProvider,
3296
3419
  model: memoryLlmModel,
3297
3420
  baseUrl: memoryLlmBaseUrl,
3298
- apiKey: first(
3299
- process.env.MEMORIX_LLM_API_KEY,
3300
- process.env.MEMORIX_API_KEY,
3301
- toml.memory?.llm?.api_key,
3302
- yaml.llm?.apiKey,
3303
- legacy.llm?.apiKey,
3304
- process.env.OPENAI_API_KEY,
3305
- process.env.ANTHROPIC_API_KEY,
3306
- openRouterMemoryLlmApiKey
3307
- )
3421
+ apiKey: memoryLlmApiKey
3308
3422
  }
3309
3423
  },
3310
3424
  embedding: {
@@ -3314,6 +3428,12 @@ function getResolvedConfig(options = {}) {
3314
3428
  apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
3315
3429
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions)
3316
3430
  },
3431
+ rerank: resolveRerankLane({
3432
+ toml,
3433
+ yaml,
3434
+ memoryLlmApiKey,
3435
+ memoryLlmBaseUrl
3436
+ }),
3317
3437
  git: {
3318
3438
  autoHook: firstBool(toml.git?.auto_hook, yaml.git?.autoHook),
3319
3439
  ingestOnCommit: firstBool(toml.git?.ingest_on_commit, yaml.git?.ingestOnCommit),
@@ -3383,6 +3503,9 @@ function getResolvedMemoryLane(options = {}) {
3383
3503
  function getResolvedEmbeddingLane(options = {}) {
3384
3504
  return getResolvedConfig(options).embedding;
3385
3505
  }
3506
+ function getResolvedRerankLane(options = {}) {
3507
+ return getResolvedConfig(options).rerank;
3508
+ }
3386
3509
  function resetResolvedConfigCache() {
3387
3510
  }
3388
3511
  function first(...values) {
@@ -3425,6 +3548,10 @@ function getEnvSourceNames() {
3425
3548
  "MEMORIX_EMBEDDING_BASE_URL",
3426
3549
  "MEMORIX_EMBEDDING_MODEL",
3427
3550
  "MEMORIX_EMBEDDING_DIMENSIONS",
3551
+ "MEMORIX_RERANK_PROVIDER",
3552
+ "MEMORIX_RERANK_MODEL",
3553
+ "MEMORIX_RERANK_BASE_URL",
3554
+ "MEMORIX_RERANK_API_KEY",
3428
3555
  "MEMORIX_CODEGRAPH_EXTERNAL_CONTEXT",
3429
3556
  "MEMORIX_CODEGRAPH_EXTERNAL_COMMAND",
3430
3557
  "MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS",
@@ -3448,6 +3575,63 @@ function normalizeExternalContext(value) {
3448
3575
  if (normalized === "auto" || normalized === "off") return normalized;
3449
3576
  return void 0;
3450
3577
  }
3578
+ function normalizeRerankProvider(value) {
3579
+ const normalized = value?.trim().toLowerCase();
3580
+ if (normalized === "http") return "http";
3581
+ return "off";
3582
+ }
3583
+ function resolveRerankLane(args) {
3584
+ const provider2 = normalizeRerankProvider(first(
3585
+ process.env.MEMORIX_RERANK_PROVIDER,
3586
+ args.toml.rerank?.provider,
3587
+ args.yaml.rerank?.provider,
3588
+ "off"
3589
+ ));
3590
+ const model = first(
3591
+ process.env.MEMORIX_RERANK_MODEL,
3592
+ args.toml.rerank?.model,
3593
+ args.yaml.rerank?.model
3594
+ );
3595
+ const explicitBaseUrl = first(
3596
+ process.env.MEMORIX_RERANK_BASE_URL,
3597
+ args.toml.rerank?.base_url,
3598
+ args.yaml.rerank?.baseUrl
3599
+ );
3600
+ const baseUrl = provider2 === "http" ? first(explicitBaseUrl, args.memoryLlmBaseUrl) : explicitBaseUrl;
3601
+ const dedicatedApiKey = first(
3602
+ process.env.MEMORIX_RERANK_API_KEY,
3603
+ args.toml.rerank?.api_key,
3604
+ args.yaml.rerank?.apiKey
3605
+ );
3606
+ const sameEndpoint = provider2 === "http" && sameTrustedEndpoint(baseUrl, args.memoryLlmBaseUrl);
3607
+ const inheritedApiKey = sameEndpoint ? args.memoryLlmApiKey : void 0;
3608
+ const apiKey = first(dedicatedApiKey, inheritedApiKey);
3609
+ const canSendRequest = provider2 === "http" && Boolean(baseUrl) && (Boolean(apiKey) || !args.memoryLlmApiKey || sameEndpoint);
3610
+ return { provider: provider2, model, baseUrl, apiKey, canSendRequest };
3611
+ }
3612
+ function sameTrustedEndpoint(left, right) {
3613
+ const a = normalizeTrustedEndpoint(left);
3614
+ const b = normalizeTrustedEndpoint(right);
3615
+ return Boolean(a && b && a === b);
3616
+ }
3617
+ function normalizeTrustedEndpoint(value) {
3618
+ const trimmed = value?.trim();
3619
+ if (!trimmed) return void 0;
3620
+ try {
3621
+ const url = new URL(trimmed);
3622
+ const protocol = url.protocol.toLowerCase();
3623
+ const hostname = url.hostname.toLowerCase();
3624
+ const port = url.port;
3625
+ let pathname = url.pathname.replace(/\/+$/, "");
3626
+ if (pathname.toLowerCase().endsWith("/rerank")) {
3627
+ pathname = pathname.slice(0, -"/rerank".length).replace(/\/+$/, "");
3628
+ }
3629
+ return `${protocol}//${hostname}${port ? `:${port}` : ""}${pathname}`;
3630
+ } catch {
3631
+ const fallback = trimmed.replace(/\/+$/, "").replace(/\/rerank$/i, "").replace(/\/+$/, "");
3632
+ return fallback.toLowerCase() || void 0;
3633
+ }
3634
+ }
3451
3635
  var init_resolved_config = __esm({
3452
3636
  "src/config/resolved-config.ts"() {
3453
3637
  "use strict";
@@ -5683,9 +5867,27 @@ __export(provider_exports2, {
5683
5867
  getLLMConfig: () => getLLMConfig,
5684
5868
  initLLM: () => initLLM,
5685
5869
  isLLMEnabled: () => isLLMEnabled,
5870
+ normalizeOpenAICompatibleBaseUrl: () => normalizeOpenAICompatibleBaseUrl,
5686
5871
  parseLLMTimeoutMs: () => parseLLMTimeoutMs,
5687
5872
  setLLMConfig: () => setLLMConfig
5688
5873
  });
5874
+ function normalizeOpenAICompatibleBaseUrl(baseUrl) {
5875
+ const base = baseUrl.trim().replace(/\/+$/, "");
5876
+ if (!base) return base;
5877
+ let pathname = "";
5878
+ try {
5879
+ pathname = new URL(base).pathname.replace(/\/+$/, "");
5880
+ } catch {
5881
+ pathname = base.split(/[?#]/, 1)[0].replace(/\/+$/, "");
5882
+ }
5883
+ const lastSegment = pathname.split("/").filter(Boolean).at(-1);
5884
+ if (lastSegment && OPENAI_API_VERSION_SEGMENT.test(lastSegment)) {
5885
+ return base;
5886
+ }
5887
+ const queryOrHash = base.match(/[?#].*$/)?.[0] ?? "";
5888
+ const pathWithoutQuery = base.slice(0, base.length - queryOrHash.length).replace(/\/+$/, "");
5889
+ return `${pathWithoutQuery}/v1${queryOrHash}`;
5890
+ }
5689
5891
  function parseLLMTimeoutMs(raw) {
5690
5892
  if (raw === void 0 || raw.trim() === "") return LLM_TIMEOUT_DEFAULT_MS;
5691
5893
  const parsed = Number(raw);
@@ -5789,8 +5991,7 @@ async function callLLM(systemPrompt, userMessage, signal) {
5789
5991
  }
5790
5992
  async function callOpenAICompatible(systemPrompt, userMessage, signal) {
5791
5993
  const config = currentConfig;
5792
- let base = config.baseUrl.replace(/\/+$/, "");
5793
- if (!base.endsWith("/v1")) base += "/v1";
5994
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5794
5995
  const url = `${base}/chat/completions`;
5795
5996
  const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
5796
5997
  const response = await fetch(url, {
@@ -5869,8 +6070,7 @@ async function callLLMWithTools(messages, tools, signal) {
5869
6070
  }
5870
6071
  async function callOpenAIWithTools(messages, tools, signal) {
5871
6072
  const config = currentConfig;
5872
- let base = config.baseUrl.replace(/\/+$/, "");
5873
- if (!base.endsWith("/v1")) base += "/v1";
6073
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
5874
6074
  const url = `${base}/chat/completions`;
5875
6075
  const openaiMessages = messages.map((msg) => {
5876
6076
  if (msg.role === "tool") {
@@ -6020,8 +6220,7 @@ async function callAnthropicWithTools(messages, tools, signal) {
6020
6220
  }
6021
6221
  async function* callOpenAIWithToolsStream(messages, tools) {
6022
6222
  const config = currentConfig;
6023
- let base = config.baseUrl.replace(/\/+$/, "");
6024
- if (!base.endsWith("/v1")) base += "/v1";
6223
+ const base = normalizeOpenAICompatibleBaseUrl(config.baseUrl);
6025
6224
  const url = `${base}/chat/completions`;
6026
6225
  const openaiMessages = messages.map((msg) => {
6027
6226
  if (msg.role === "tool") {
@@ -6269,7 +6468,7 @@ async function* callAnthropicWithToolsStream(messages, tools) {
6269
6468
  }
6270
6469
  };
6271
6470
  }
6272
- var LLM_TIMEOUT_DEFAULT_MS, LLM_TIMEOUT_MIN_MS, LLM_TIMEOUT_MAX_MS, MAX_LLM_RESPONSE_BYTES, LLM_CALL_TIMEOUT_MS, PROVIDER_DEFAULTS, currentConfig;
6471
+ 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;
6273
6472
  var init_provider2 = __esm({
6274
6473
  "src/llm/provider.ts"() {
6275
6474
  "use strict";
@@ -6279,6 +6478,7 @@ var init_provider2 = __esm({
6279
6478
  LLM_TIMEOUT_MIN_MS = 1e3;
6280
6479
  LLM_TIMEOUT_MAX_MS = 3e5;
6281
6480
  MAX_LLM_RESPONSE_BYTES = 2 * 1024 * 1024;
6481
+ OPENAI_API_VERSION_SEGMENT = /^v\d+(?:[a-z][a-z0-9-]*)?$/i;
6282
6482
  LLM_CALL_TIMEOUT_MS = parseLLMTimeoutMs(process.env.MEMORIX_LLM_TIMEOUT_MS);
6283
6483
  PROVIDER_DEFAULTS = {
6284
6484
  openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4.1-nano" },
@@ -6338,6 +6538,48 @@ Rules:
6338
6538
  }
6339
6539
  });
6340
6540
 
6541
+ // src/rerank/policy.ts
6542
+ function parseRerankTimeoutMs(raw) {
6543
+ const value = raw?.trim();
6544
+ if (!value) return DEFAULT_RERANK_TIMEOUT_MS;
6545
+ const parsed = Number(value);
6546
+ if (!Number.isInteger(parsed) || Number.isNaN(parsed)) {
6547
+ console.warn(
6548
+ `[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.`
6549
+ );
6550
+ return DEFAULT_RERANK_TIMEOUT_MS;
6551
+ }
6552
+ if (parsed < RERANK_TIMEOUT_MIN_MS) return RERANK_TIMEOUT_MIN_MS;
6553
+ if (parsed > RERANK_TIMEOUT_MAX_MS) return RERANK_TIMEOUT_MAX_MS;
6554
+ return parsed;
6555
+ }
6556
+ function resolveRerankTimeoutMs(explicitTimeoutMs) {
6557
+ if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
6558
+ return explicitTimeoutMs;
6559
+ }
6560
+ return parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
6561
+ }
6562
+ function shouldAttemptHttpRerank(input) {
6563
+ return input.quality === "thorough" && input.hasQuery && input.candidateCount >= MIN_RERANK_CANDIDATES;
6564
+ }
6565
+ function shouldAttemptLlmRerank(input) {
6566
+ if (input.httpRerankConfigured) return false;
6567
+ if (input.quality !== "thorough" || input.tier !== "heavy" || !input.hasQuery) return false;
6568
+ if (input.candidateCount < MIN_RERANK_CANDIDATES) return false;
6569
+ return input.topScore > 0 && input.secondScore / input.topScore > 0.7;
6570
+ }
6571
+ var DEFAULT_RERANK_TIMEOUT_MS, RERANK_TIMEOUT_MIN_MS, RERANK_TIMEOUT_MAX_MS, MIN_RERANK_CANDIDATES;
6572
+ var init_policy = __esm({
6573
+ "src/rerank/policy.ts"() {
6574
+ "use strict";
6575
+ init_esm_shims();
6576
+ DEFAULT_RERANK_TIMEOUT_MS = 3e4;
6577
+ RERANK_TIMEOUT_MIN_MS = 1e3;
6578
+ RERANK_TIMEOUT_MAX_MS = 3e5;
6579
+ MIN_RERANK_CANDIDATES = 3;
6580
+ }
6581
+ });
6582
+
6341
6583
  // src/timeout.ts
6342
6584
  function withTimeout(promise, ms, label) {
6343
6585
  return new Promise((resolve2, reject) => {
@@ -6623,6 +6865,148 @@ var init_aliases = __esm({
6623
6865
  }
6624
6866
  });
6625
6867
 
6868
+ // src/rerank/http-provider.ts
6869
+ function buildRerankUrl(baseUrl) {
6870
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
6871
+ if (trimmed.endsWith("/rerank")) return trimmed;
6872
+ return `${trimmed}/rerank`;
6873
+ }
6874
+ function parseRerankResponse(payload) {
6875
+ const rows = extractRows(payload);
6876
+ if (rows.length === 0) {
6877
+ throw new Error("Neural rerank response contained no ranked results");
6878
+ }
6879
+ return rows.sort((a, b) => b.score - a.score);
6880
+ }
6881
+ function extractRows(payload) {
6882
+ if (Array.isArray(payload)) {
6883
+ return payload.map(rowFromUnknown).filter((row) => row !== null);
6884
+ }
6885
+ if (payload && typeof payload === "object" && "results" in payload) {
6886
+ const results = payload.results;
6887
+ if (Array.isArray(results)) {
6888
+ return results.map(rowFromUnknown).filter((row) => row !== null);
6889
+ }
6890
+ }
6891
+ throw new Error("Neural rerank response was not Cohere or TEI shaped");
6892
+ }
6893
+ function rowFromUnknown(value) {
6894
+ if (!value || typeof value !== "object") return null;
6895
+ const row = value;
6896
+ if (typeof row.index !== "number" || !Number.isFinite(row.index)) return null;
6897
+ const score = typeof row.relevance_score === "number" ? row.relevance_score : typeof row.score === "number" ? row.score : null;
6898
+ if (score === null || !Number.isFinite(score)) return null;
6899
+ return { index: row.index, score };
6900
+ }
6901
+ function buildRerankBody(query, documents, model) {
6902
+ const body = {
6903
+ query,
6904
+ documents,
6905
+ top_n: documents.length,
6906
+ return_documents: false
6907
+ };
6908
+ const trimmed = model?.trim();
6909
+ if (trimmed) body.model = trimmed;
6910
+ return body;
6911
+ }
6912
+ async function rerankViaHttp(query, documents, config) {
6913
+ if (documents.length === 0) return [];
6914
+ const url = buildRerankUrl(config.baseUrl);
6915
+ const headers = {
6916
+ "content-type": "application/json"
6917
+ };
6918
+ if (config.apiKey) {
6919
+ headers.authorization = `Bearer ${config.apiKey}`;
6920
+ }
6921
+ const controller = new AbortController();
6922
+ const timeoutMs = resolveRerankTimeoutMs(config.timeoutMs);
6923
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6924
+ try {
6925
+ const fetchImpl = config.fetchImpl ?? fetch;
6926
+ const response = await fetchImpl(url, {
6927
+ method: "POST",
6928
+ headers,
6929
+ signal: controller.signal,
6930
+ body: JSON.stringify(buildRerankBody(query, documents, config.model))
6931
+ });
6932
+ if (!response.ok) {
6933
+ throw new Error(`Neural rerank HTTP ${response.status} from ${url}`);
6934
+ }
6935
+ return parseRerankResponse(await response.json());
6936
+ } finally {
6937
+ clearTimeout(timer);
6938
+ }
6939
+ }
6940
+ var init_http_provider = __esm({
6941
+ "src/rerank/http-provider.ts"() {
6942
+ "use strict";
6943
+ init_esm_shims();
6944
+ init_policy();
6945
+ }
6946
+ });
6947
+
6948
+ // src/rerank/index.ts
6949
+ var rerank_exports = {};
6950
+ __export(rerank_exports, {
6951
+ DEFAULT_RERANK_TIMEOUT_MS: () => DEFAULT_RERANK_TIMEOUT_MS,
6952
+ MIN_RERANK_CANDIDATES: () => MIN_RERANK_CANDIDATES,
6953
+ isNeuralRerankEnabled: () => isNeuralRerankEnabled,
6954
+ neuralRerankCandidates: () => neuralRerankCandidates,
6955
+ parseRerankTimeoutMs: () => parseRerankTimeoutMs,
6956
+ resolveRerankTimeoutMs: () => resolveRerankTimeoutMs,
6957
+ shouldAttemptHttpRerank: () => shouldAttemptHttpRerank,
6958
+ shouldAttemptLlmRerank: () => shouldAttemptLlmRerank
6959
+ });
6960
+ function isNeuralRerankEnabled(options = {}) {
6961
+ const lane = getResolvedRerankLane(options);
6962
+ return lane.provider === "http" && Boolean(lane.baseUrl);
6963
+ }
6964
+ async function neuralRerankCandidates(query, candidates, options = {}) {
6965
+ if (!isNeuralRerankEnabled(options) || candidates.length === 0) return null;
6966
+ const lane = getResolvedRerankLane(options);
6967
+ if (!lane.canSendRequest || !lane.baseUrl) return null;
6968
+ const toRerank = candidates.slice(0, MAX_RERANK);
6969
+ const rest = candidates.slice(MAX_RERANK);
6970
+ const documents = toRerank.map((candidate) => candidateText(candidate));
6971
+ const ranked = await rerankViaHttp(query, documents, {
6972
+ provider: "http",
6973
+ model: lane.model,
6974
+ baseUrl: lane.baseUrl,
6975
+ apiKey: lane.apiKey,
6976
+ timeoutMs: resolveRerankTimeoutMs()
6977
+ });
6978
+ const seen = /* @__PURE__ */ new Set();
6979
+ const reranked = [];
6980
+ for (const row of ranked) {
6981
+ const candidate = toRerank[row.index];
6982
+ if (!candidate || seen.has(row.index)) continue;
6983
+ reranked.push(candidate);
6984
+ seen.add(row.index);
6985
+ }
6986
+ for (const [index, candidate] of toRerank.entries()) {
6987
+ if (!seen.has(index)) reranked.push(candidate);
6988
+ }
6989
+ reranked.push(...rest);
6990
+ return reranked;
6991
+ }
6992
+ function candidateText(candidate) {
6993
+ const narrative = candidate.narrative?.trim();
6994
+ if (narrative) return `${candidate.title} \u2014 ${narrative.slice(0, 200)}`;
6995
+ return candidate.title;
6996
+ }
6997
+ var MAX_RERANK;
6998
+ var init_rerank = __esm({
6999
+ "src/rerank/index.ts"() {
7000
+ "use strict";
7001
+ init_esm_shims();
7002
+ init_resolved_config();
7003
+ init_http_provider();
7004
+ init_policy();
7005
+ init_policy();
7006
+ MAX_RERANK = 10;
7007
+ }
7008
+ });
7009
+
6626
7010
  // src/llm/quality.ts
6627
7011
  var quality_exports = {};
6628
7012
  __export(quality_exports, {
@@ -6662,9 +7046,9 @@ async function rerankResults(query, candidates) {
6662
7046
  if (!isLLMEnabled() || candidates.length <= 2) {
6663
7047
  return { reranked: candidates, usedLLM: false };
6664
7048
  }
6665
- const MAX_RERANK = 10;
6666
- const toRerank = candidates.slice(0, MAX_RERANK);
6667
- const rest = candidates.slice(MAX_RERANK);
7049
+ const MAX_RERANK2 = 10;
7050
+ const toRerank = candidates.slice(0, MAX_RERANK2);
7051
+ const rest = candidates.slice(MAX_RERANK2);
6668
7052
  try {
6669
7053
  const candidateList = toRerank.map(
6670
7054
  (c) => `[ID: ${c.id}] (${c.type}) ${c.title}${c.narrative ? ` \u2014 ${c.narrative.substring(0, 100)}` : ""}`
@@ -7557,46 +7941,78 @@ async function searchObservations(options) {
7557
7941
  }
7558
7942
  }
7559
7943
  }
7560
- const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
7561
- const top = intermediate[0]?.score ?? 0;
7562
- const second = intermediate[1]?.score ?? 0;
7563
- return top > 0 && second / top > 0.7;
7564
- })();
7565
- if (shouldRerank) {
7566
- try {
7567
- const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7568
- const narrativeMap = /* @__PURE__ */ new Map();
7569
- for (const hit of results.hits) {
7570
- const doc = hit.document;
7571
- narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7572
- }
7573
- const RERANK_TOP_K = 5;
7574
- const toRerank = intermediate.slice(0, RERANK_TOP_K);
7575
- const candidates = toRerank.map((e2, index) => ({
7576
- id: `r${index + 1}`,
7577
- title: e2.title,
7578
- type: e2.type,
7579
- score: e2.score,
7580
- narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7581
- }));
7582
- const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
7583
- const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
7584
- const { reranked, usedLLM } = await withTimeout(
7585
- rerankResults2(originalQuery, candidates),
7586
- RERANK_TIMEOUT_MS,
7587
- "LLM rerank"
7588
- );
7589
- mark(`rerank(usedLLM=${usedLLM})`);
7590
- if (usedLLM) {
7591
- lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
7592
- const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7593
- const rerankedTop = reranked.map((r2) => candidateMap.get(r2.id)).filter((e2) => e2 != null);
7594
- if (rerankedTop.length > 0) {
7595
- intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7944
+ const httpGate = shouldAttemptHttpRerank({
7945
+ quality,
7946
+ hasQuery: Boolean(hasQuery),
7947
+ candidateCount: intermediate.length
7948
+ });
7949
+ const llmGateWithoutHttp = shouldAttemptLlmRerank({
7950
+ quality,
7951
+ tier,
7952
+ hasQuery: Boolean(hasQuery),
7953
+ candidateCount: intermediate.length,
7954
+ topScore: intermediate[0]?.score ?? 0,
7955
+ secondScore: intermediate[1]?.score ?? 0,
7956
+ httpRerankConfigured: false
7957
+ });
7958
+ if (httpGate || llmGateWithoutHttp) {
7959
+ const narrativeMap = /* @__PURE__ */ new Map();
7960
+ for (const hit of results.hits) {
7961
+ const doc = hit.document;
7962
+ narrativeMap.set(makeEntryKey(doc.projectId, doc.observationId), doc.narrative);
7963
+ }
7964
+ const RERANK_TOP_K = 5;
7965
+ const toRerank = intermediate.slice(0, RERANK_TOP_K);
7966
+ const candidates = toRerank.map((e2, index) => ({
7967
+ id: `r${index + 1}`,
7968
+ title: e2.title,
7969
+ type: e2.type,
7970
+ score: e2.score,
7971
+ narrative: narrativeMap.get(makeEntryKey(e2.projectId, e2.id))
7972
+ }));
7973
+ const RERANK_TIMEOUT_MS = parseRerankTimeoutMs(process.env.MEMORIX_RERANK_TIMEOUT_MS);
7974
+ const candidateMap = new Map(candidates.map((candidate, index) => [candidate.id, toRerank[index]]));
7975
+ const { isNeuralRerankEnabled: isNeuralRerankEnabled2, neuralRerankCandidates: neuralRerankCandidates2 } = await Promise.resolve().then(() => (init_rerank(), rerank_exports));
7976
+ const httpConfigured = isNeuralRerankEnabled2();
7977
+ if (httpConfigured && httpGate) {
7978
+ try {
7979
+ const neural = await withTimeout(
7980
+ neuralRerankCandidates2(originalQuery, candidates),
7981
+ RERANK_TIMEOUT_MS,
7982
+ "neural rerank"
7983
+ );
7984
+ if (neural && neural.length > 0) {
7985
+ const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
7986
+ if (rerankedTop.length > 0) {
7987
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
7988
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
7989
+ mark("rerank(usedNeural=true)");
7990
+ }
7596
7991
  }
7992
+ } catch {
7993
+ console.error("[memorix] neural rerank failed or timed out; keeping original order");
7597
7994
  }
7598
- } catch (error) {
7599
- console.error("[memorix] LLM rerank failed or timed out, using original order");
7995
+ } else if (!httpConfigured && llmGateWithoutHttp) {
7996
+ try {
7997
+ const { rerankResults: rerankResults2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
7998
+ const { reranked, usedLLM } = await withTimeout(
7999
+ rerankResults2(originalQuery, candidates),
8000
+ RERANK_TIMEOUT_MS,
8001
+ "LLM rerank"
8002
+ );
8003
+ mark(`rerank(usedLLM=${usedLLM})`);
8004
+ if (usedLLM) {
8005
+ lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
8006
+ const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
8007
+ if (rerankedTop.length > 0) {
8008
+ intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
8009
+ }
8010
+ }
8011
+ } catch {
8012
+ console.error("[memorix] LLM rerank failed or timed out, using original order");
8013
+ }
8014
+ } else {
8015
+ mark(`rerank(skipped,tier=${tier})`);
7600
8016
  }
7601
8017
  } else {
7602
8018
  mark(`rerank(skipped,tier=${tier})`);
@@ -7777,6 +8193,7 @@ var init_orama_store = __esm({
7777
8193
  init_project_affinity();
7778
8194
  init_intent_detector();
7779
8195
  init_query_expansion();
8196
+ init_policy();
7780
8197
  init_timeout();
7781
8198
  db = null;
7782
8199
  dbInitPromise = null;
@@ -8996,6 +9413,226 @@ var init_lifecycle = __esm({
8996
9413
  }
8997
9414
  });
8998
9415
 
9416
+ // src/store/evidence-store.ts
9417
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
9418
+ function safeJson(value, fallback) {
9419
+ if (typeof value !== "string" || value.length === 0) return fallback;
9420
+ try {
9421
+ return JSON.parse(value);
9422
+ } catch {
9423
+ return fallback;
9424
+ }
9425
+ }
9426
+ function rowToCard(row) {
9427
+ return {
9428
+ id: String(row.id),
9429
+ projectId: String(row.project_id),
9430
+ candidateKind: row.candidate_kind,
9431
+ candidateId: String(row.candidate_id),
9432
+ title: String(row.title),
9433
+ summary: String(row.summary ?? ""),
9434
+ ...row.entity_name ? { entityName: String(row.entity_name) } : {},
9435
+ sourceKind: String(row.source_kind),
9436
+ sourceRef: String(row.source_ref),
9437
+ ...row.locator ? { locator: String(row.locator) } : {},
9438
+ ...row.captured_hash ? { capturedHash: String(row.captured_hash) } : {},
9439
+ ...row.session_id ? { sessionId: String(row.session_id) } : {},
9440
+ status: String(row.status ?? "active"),
9441
+ verification: row.verification,
9442
+ freshness: row.freshness,
9443
+ ...row.stale_reason ? { staleReason: String(row.stale_reason) } : {},
9444
+ files: safeJson(row.files_json, []),
9445
+ relatedEntities: safeJson(row.related_entities_json, []),
9446
+ createdAt: String(row.created_at),
9447
+ updatedAt: String(row.updated_at)
9448
+ };
9449
+ }
9450
+ function observationHash(observation) {
9451
+ return createHash3("sha256").update(JSON.stringify({
9452
+ projectId: observation.projectId,
9453
+ id: observation.id,
9454
+ title: observation.title,
9455
+ narrative: observation.narrative,
9456
+ facts: observation.facts,
9457
+ files: observation.filesModified,
9458
+ relatedEntities: observation.relatedEntities
9459
+ })).digest("hex");
9460
+ }
9461
+ function sourceKind(observation) {
9462
+ if (observation.source === "git" || observation.sourceDetail === "git-ingest") return "git";
9463
+ if (observation.sourceDetail === "hook") return "hook";
9464
+ return observation.source ?? "agent";
9465
+ }
9466
+ var EvidenceCardStore;
9467
+ var init_evidence_store = __esm({
9468
+ "src/store/evidence-store.ts"() {
9469
+ "use strict";
9470
+ init_esm_shims();
9471
+ init_sqlite_db();
9472
+ EvidenceCardStore = class {
9473
+ db = null;
9474
+ async init(dataDir) {
9475
+ this.db = getDatabase(dataDir);
9476
+ }
9477
+ requireDb() {
9478
+ if (!this.db) throw new Error("EvidenceCardStore is not initialized.");
9479
+ return this.db;
9480
+ }
9481
+ upsertObservation(observation) {
9482
+ const db2 = this.requireDb();
9483
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
9484
+ const card = {
9485
+ id: `evidence:observation:${observation.projectId}:${observation.id}`,
9486
+ projectId: observation.projectId,
9487
+ candidateKind: "observation",
9488
+ candidateId: String(observation.id),
9489
+ title: observation.title,
9490
+ summary: observation.narrative.slice(0, 500),
9491
+ entityName: observation.entityName,
9492
+ sourceKind: sourceKind(observation),
9493
+ sourceRef: `observation:${observation.id}`,
9494
+ locator: observation.filesModified?.join(", ") || `observation:${observation.id}`,
9495
+ capturedHash: observationHash(observation),
9496
+ ...observation.sessionId ? { sessionId: observation.sessionId } : {},
9497
+ status: observation.status ?? "active",
9498
+ verification: observation.status === "resolved" ? "verified" : "unverified",
9499
+ freshness: "current",
9500
+ files: observation.filesModified ?? [],
9501
+ relatedEntities: observation.relatedEntities ?? [],
9502
+ createdAt: observation.createdAt,
9503
+ updatedAt: now3
9504
+ };
9505
+ db2.prepare(`
9506
+ INSERT INTO evidence_cards (
9507
+ id, project_id, candidate_kind, candidate_id, title, summary, entity_name,
9508
+ source_kind, source_ref, locator, captured_hash, session_id, status,
9509
+ verification, freshness, stale_reason, files_json, related_entities_json,
9510
+ created_at, updated_at
9511
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
9512
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
9513
+ title = excluded.title,
9514
+ summary = excluded.summary,
9515
+ entity_name = excluded.entity_name,
9516
+ source_kind = excluded.source_kind,
9517
+ source_ref = excluded.source_ref,
9518
+ locator = excluded.locator,
9519
+ captured_hash = excluded.captured_hash,
9520
+ session_id = excluded.session_id,
9521
+ status = excluded.status,
9522
+ freshness = 'current',
9523
+ stale_reason = NULL,
9524
+ files_json = excluded.files_json,
9525
+ related_entities_json = excluded.related_entities_json,
9526
+ updated_at = excluded.updated_at
9527
+ `).run(
9528
+ card.id,
9529
+ card.projectId,
9530
+ card.candidateKind,
9531
+ card.candidateId,
9532
+ card.title,
9533
+ card.summary,
9534
+ card.entityName ?? null,
9535
+ card.sourceKind,
9536
+ card.sourceRef,
9537
+ card.locator ?? null,
9538
+ card.capturedHash ?? null,
9539
+ card.sessionId ?? null,
9540
+ card.status,
9541
+ card.verification,
9542
+ card.freshness,
9543
+ JSON.stringify(card.files),
9544
+ JSON.stringify(card.relatedEntities),
9545
+ card.createdAt,
9546
+ card.updatedAt
9547
+ );
9548
+ return this.get(card.projectId, card.candidateKind, card.candidateId) ?? card;
9549
+ }
9550
+ get(projectId, candidateKind, candidateId) {
9551
+ const row = this.requireDb().prepare(`
9552
+ SELECT * FROM evidence_cards WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
9553
+ `).get(projectId, candidateKind, candidateId);
9554
+ return row ? rowToCard(row) : void 0;
9555
+ }
9556
+ list(projectId, options = {}) {
9557
+ const limit2 = Math.min(100, Math.max(1, Math.floor(options.limit ?? 20)));
9558
+ const clauses = ["project_id = ?"];
9559
+ const args = [projectId];
9560
+ if (options.freshness) {
9561
+ clauses.push("freshness = ?");
9562
+ args.push(options.freshness);
9563
+ }
9564
+ if (options.status) {
9565
+ clauses.push("status = ?");
9566
+ args.push(options.status);
9567
+ }
9568
+ args.push(limit2);
9569
+ const rows = this.requireDb().prepare(`
9570
+ SELECT * FROM evidence_cards WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC, id DESC LIMIT ?
9571
+ `).all(...args);
9572
+ return rows.map(rowToCard);
9573
+ }
9574
+ syncObservations(observations2) {
9575
+ return observations2.map((observation) => this.upsertObservation(observation));
9576
+ }
9577
+ markStaleForPaths(projectId, paths, reason = "source file changed") {
9578
+ const normalized = new Set(paths.map((value) => value.replace(/\\/g, "/").toLowerCase()));
9579
+ if (normalized.size === 0) return 0;
9580
+ const db2 = this.requireDb();
9581
+ const rows = db2.prepare(`SELECT * FROM evidence_cards WHERE project_id = ? AND freshness != 'stale'`).all(projectId);
9582
+ const update2 = db2.prepare(`
9583
+ UPDATE evidence_cards SET freshness = 'stale', stale_reason = ?, updated_at = ?
9584
+ WHERE id = ?
9585
+ `);
9586
+ let changed = 0;
9587
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
9588
+ const run = db2.transaction(() => {
9589
+ for (const row of rows) {
9590
+ const files = safeJson(row.files_json, []);
9591
+ if (!files.some((file) => normalized.has(file.replace(/\\/g, "/").toLowerCase()))) continue;
9592
+ update2.run(reason, now3, row.id);
9593
+ changed++;
9594
+ }
9595
+ });
9596
+ run();
9597
+ return changed;
9598
+ }
9599
+ setVerification(projectId, cardId, verification) {
9600
+ this.requireDb().prepare(`
9601
+ UPDATE evidence_cards SET verification = ?, updated_at = ? WHERE project_id = ? AND id = ?
9602
+ `).run(verification, (/* @__PURE__ */ new Date()).toISOString(), projectId, cardId);
9603
+ }
9604
+ recordEvent(projectId, cardId, kind, detail) {
9605
+ const event = {
9606
+ id: randomUUID3(),
9607
+ projectId,
9608
+ cardId,
9609
+ kind,
9610
+ ...detail ? { detail: detail.slice(0, 2e3) } : {},
9611
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
9612
+ };
9613
+ this.requireDb().prepare(`
9614
+ INSERT INTO evidence_card_events (id, project_id, card_id, kind, detail, created_at)
9615
+ VALUES (?, ?, ?, ?, ?, ?)
9616
+ `).run(event.id, event.projectId, event.cardId, event.kind, event.detail ?? null, event.createdAt);
9617
+ return event;
9618
+ }
9619
+ listEvents(cardId, limit2 = 50) {
9620
+ const rows = this.requireDb().prepare(`
9621
+ SELECT * FROM evidence_card_events WHERE card_id = ? ORDER BY created_at DESC LIMIT ?
9622
+ `).all(cardId, Math.min(200, Math.max(1, Math.floor(limit2))));
9623
+ return rows.map((row) => ({
9624
+ id: String(row.id),
9625
+ projectId: String(row.project_id),
9626
+ cardId: String(row.card_id),
9627
+ kind: String(row.kind),
9628
+ ...row.detail ? { detail: String(row.detail) } : {},
9629
+ createdAt: String(row.created_at)
9630
+ }));
9631
+ }
9632
+ };
9633
+ }
9634
+ });
9635
+
8999
9636
  // src/runtime/isolated-maintenance.ts
9000
9637
  import { spawn } from "child_process";
9001
9638
  import { existsSync as existsSync8 } from "fs";
@@ -9157,7 +9794,7 @@ var workspace_store_exports = {};
9157
9794
  __export(workspace_store_exports, {
9158
9795
  KnowledgeWorkspaceStore: () => KnowledgeWorkspaceStore
9159
9796
  });
9160
- import { randomUUID as randomUUID3 } from "crypto";
9797
+ import { randomUUID as randomUUID4 } from "crypto";
9161
9798
  function arrayValue(raw) {
9162
9799
  if (typeof raw !== "string" || !raw) return [];
9163
9800
  try {
@@ -9298,7 +9935,7 @@ var init_workspace_store = __esm({
9298
9935
  }
9299
9936
  createProposal(input) {
9300
9937
  const proposal = {
9301
- id: input.id ?? randomUUID3(),
9938
+ id: input.id ?? randomUUID4(),
9302
9939
  ...input,
9303
9940
  status: input.status ?? "pending",
9304
9941
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -9351,13 +9988,13 @@ __export(workspace_exports, {
9351
9988
  loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
9352
9989
  resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
9353
9990
  });
9354
- import { createHash as createHash3 } from "crypto";
9991
+ import { createHash as createHash4 } from "crypto";
9355
9992
  import { execFile } from "child_process";
9356
9993
  import { promises as fs9 } from "fs";
9357
9994
  import path13 from "path";
9358
9995
  import { promisify } from "util";
9359
9996
  function hash(value) {
9360
- return createHash3("sha256").update(value).digest("hex");
9997
+ return createHash4("sha256").update(value).digest("hex");
9361
9998
  }
9362
9999
  function workspaceId(projectId, mode) {
9363
10000
  return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
@@ -9957,7 +10594,7 @@ var media_store_exports = {};
9957
10594
  __export(media_store_exports, {
9958
10595
  MediaStore: () => MediaStore
9959
10596
  });
9960
- import { randomUUID as randomUUID4 } from "crypto";
10597
+ import { randomUUID as randomUUID5 } from "crypto";
9961
10598
  function asOptionalText(value) {
9962
10599
  return typeof value === "string" && value.length > 0 ? value : void 0;
9963
10600
  }
@@ -10127,7 +10764,7 @@ var init_media_store = __esm({
10127
10764
  return { asset: this.getAsset(input.projectId, existing.id), deduplicated: true };
10128
10765
  }
10129
10766
  const asset = {
10130
- id: randomUUID4(),
10767
+ id: randomUUID5(),
10131
10768
  ...input,
10132
10769
  createdAt: now3,
10133
10770
  updatedAt: now3
@@ -10207,7 +10844,7 @@ var init_media_store = __esm({
10207
10844
  `).get(input.assetId, input.observationId ?? null, input.role);
10208
10845
  if (existing) return rowToLink(existing);
10209
10846
  const link = {
10210
- id: randomUUID4(),
10847
+ id: randomUUID5(),
10211
10848
  assetId: input.assetId,
10212
10849
  projectId: input.projectId,
10213
10850
  ...input.observationId !== void 0 ? { observationId: input.observationId } : {},
@@ -10246,7 +10883,7 @@ var init_media_store = __esm({
10246
10883
  return rowToDerivation(this.db.prepare("SELECT * FROM media_derivations WHERE id = ?").get(existing.id));
10247
10884
  }
10248
10885
  const derivation = {
10249
- id: randomUUID4(),
10886
+ id: randomUUID5(),
10250
10887
  ...input,
10251
10888
  content,
10252
10889
  ...error ? { error } : {},
@@ -10333,7 +10970,7 @@ var init_media_store = __esm({
10333
10970
  createJob(input) {
10334
10971
  const now3 = Date.now();
10335
10972
  const job = {
10336
- id: randomUUID4(),
10973
+ id: randomUUID5(),
10337
10974
  projectId: input.projectId,
10338
10975
  kind: input.kind,
10339
10976
  status: "queued",
@@ -10509,14 +11146,14 @@ __export(asset_store_exports, {
10509
11146
  readMediaAsset: () => readMediaAsset,
10510
11147
  removeMediaAsset: () => removeMediaAsset
10511
11148
  });
10512
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
11149
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
10513
11150
  import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
10514
11151
  import path14 from "path";
10515
11152
  function mediaRoot(dataDir) {
10516
11153
  return path14.resolve(dataDir, "media");
10517
11154
  }
10518
11155
  function projectStorageKey(projectId) {
10519
- return createHash4("sha256").update(projectId).digest("hex").slice(0, 16);
11156
+ return createHash5("sha256").update(projectId).digest("hex").slice(0, 16);
10520
11157
  }
10521
11158
  function validateUnderRoot(root, candidate) {
10522
11159
  const resolved = path14.resolve(candidate);
@@ -10576,14 +11213,14 @@ async function readHeader(filePath, bytes = 32) {
10576
11213
  }
10577
11214
  async function sha256File(filePath) {
10578
11215
  const bytes = await readFile3(filePath);
10579
- return createHash4("sha256").update(bytes).digest("hex");
11216
+ return createHash5("sha256").update(bytes).digest("hex");
10580
11217
  }
10581
11218
  function destinationFor(root, projectId, sha256, extension3) {
10582
11219
  const relative3 = path14.join("assets", projectStorageKey(projectId), sha256.slice(0, 2), `${sha256}${extension3}`);
10583
11220
  return { relative: relative3, absolute: validateUnderRoot(root, path14.join(root, relative3)) };
10584
11221
  }
10585
11222
  function deletionStagingPath(root, assetId) {
10586
- return validateUnderRoot(root, path14.join(root, ".trash", `${assetId}.${randomUUID5()}.pending-delete`));
11223
+ return validateUnderRoot(root, path14.join(root, ".trash", `${assetId}.${randomUUID6()}.pending-delete`));
10587
11224
  }
10588
11225
  async function destinationExists(destination) {
10589
11226
  try {
@@ -10597,7 +11234,7 @@ async function destinationExists(destination) {
10597
11234
  async function persistFile(source, destination) {
10598
11235
  await mkdir3(path14.dirname(destination), { recursive: true });
10599
11236
  if (await destinationExists(destination)) return false;
10600
- const temporary = `${destination}.${randomUUID5()}.tmp`;
11237
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
10601
11238
  try {
10602
11239
  await copyFile(source, temporary);
10603
11240
  await rename2(temporary, destination);
@@ -10613,7 +11250,7 @@ async function persistFile(source, destination) {
10613
11250
  async function persistBuffer(bytes, destination) {
10614
11251
  await mkdir3(path14.dirname(destination), { recursive: true });
10615
11252
  if (await destinationExists(destination)) return false;
10616
- const temporary = `${destination}.${randomUUID5()}.tmp`;
11253
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
10617
11254
  try {
10618
11255
  await writeFile3(temporary, bytes, { flag: "wx" });
10619
11256
  await rename2(temporary, destination);
@@ -10682,7 +11319,7 @@ async function importMediaBuffer(input) {
10682
11319
  if (input.bytes.length === 0) throw new Error("Media import rejects empty payloads");
10683
11320
  if (input.bytes.length > maxBytes) throw new Error(`Media payload is too large (${input.bytes.length} bytes; limit ${maxBytes})`);
10684
11321
  const detected = detectMedia(input.bytes.subarray(0, 32));
10685
- const sha256 = createHash4("sha256").update(input.bytes).digest("hex");
11322
+ const sha256 = createHash5("sha256").update(input.bytes).digest("hex");
10686
11323
  return importKnownMedia({
10687
11324
  dataDir: input.dataDir,
10688
11325
  projectId: input.projectId,
@@ -10700,7 +11337,7 @@ async function readMediaAsset(dataDir, asset, maxBytes = DEFAULT_MAX_MEDIA_BYTES
10700
11337
  if (asset.byteSize > maxBytes) throw new Error(`Media asset exceeds read limit (${asset.byteSize} bytes; limit ${maxBytes})`);
10701
11338
  const bytes = await readFile3(getMediaAssetPath(dataDir, asset));
10702
11339
  if (bytes.length !== asset.byteSize) throw new Error(`Media asset size no longer matches metadata: ${asset.id}`);
10703
- const sha256 = createHash4("sha256").update(bytes).digest("hex");
11340
+ const sha256 = createHash5("sha256").update(bytes).digest("hex");
10704
11341
  if (sha256 !== asset.sha256) throw new Error(`Media asset hash no longer matches metadata: ${asset.id}`);
10705
11342
  return bytes;
10706
11343
  }
@@ -63570,7 +64207,7 @@ var require_gaxios = __commonJS({
63570
64207
  var retry_js_1 = require_retry3();
63571
64208
  var stream_1 = __require("stream");
63572
64209
  var interceptor_js_1 = require_interceptor();
63573
- var randomUUID17 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
64210
+ var randomUUID19 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
63574
64211
  var HTTP_STATUS_NO_CONTENT = 204;
63575
64212
  var Gaxios = class {
63576
64213
  agentCache = /* @__PURE__ */ new Map();
@@ -63843,7 +64480,7 @@ var require_gaxios = __commonJS({
63843
64480
  */
63844
64481
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
63845
64482
  if (opts.multipart?.length) {
63846
- const boundary = await randomUUID17();
64483
+ const boundary = await randomUUID19();
63847
64484
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
63848
64485
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
63849
64486
  } else if (shouldDirectlyPassData) {
@@ -76070,7 +76707,7 @@ var require_websocket = __commonJS({
76070
76707
  var http3 = __require("http");
76071
76708
  var net = __require("net");
76072
76709
  var tls = __require("tls");
76073
- var { randomBytes: randomBytes2, createHash: createHash16 } = __require("crypto");
76710
+ var { randomBytes: randomBytes2, createHash: createHash17 } = __require("crypto");
76074
76711
  var { Duplex, Readable: Readable2 } = __require("stream");
76075
76712
  var { URL: URL2 } = __require("url");
76076
76713
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -76738,7 +77375,7 @@ var require_websocket = __commonJS({
76738
77375
  abortHandshake(websocket, socket, "Invalid Upgrade header");
76739
77376
  return;
76740
77377
  }
76741
- const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
77378
+ const digest2 = createHash17("sha1").update(key + GUID).digest("base64");
76742
77379
  if (res.headers["sec-websocket-accept"] !== digest2) {
76743
77380
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
76744
77381
  return;
@@ -77110,7 +77747,7 @@ var require_websocket_server = __commonJS({
77110
77747
  var EventEmitter2 = __require("events");
77111
77748
  var http3 = __require("http");
77112
77749
  var { Duplex } = __require("stream");
77113
- var { createHash: createHash16 } = __require("crypto");
77750
+ var { createHash: createHash17 } = __require("crypto");
77114
77751
  var extension3 = require_extension();
77115
77752
  var PerMessageDeflate2 = require_permessage_deflate();
77116
77753
  var subprotocol2 = require_subprotocol();
@@ -77417,7 +78054,7 @@ var require_websocket_server = __commonJS({
77417
78054
  );
77418
78055
  }
77419
78056
  if (this._state > RUNNING) return abortHandshake(socket, 503);
77420
- const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
78057
+ const digest2 = createHash17("sha1").update(key + GUID).digest("base64");
77421
78058
  const headers = [
77422
78059
  "HTTP/1.1 101 Switching Protocols",
77423
78060
  "Upgrade: websocket",
@@ -144671,6 +145308,186 @@ var init_audio_jobs = __esm({
144671
145308
  }
144672
145309
  });
144673
145310
 
145311
+ // src/memory/feedback-model.ts
145312
+ function feedbackWeightMultiplier(weight) {
145313
+ return Math.max(0.35, Math.min(1.4, 0.35 + Math.max(0, Math.min(2, weight)) * 0.525));
145314
+ }
145315
+ var init_feedback_model = __esm({
145316
+ "src/memory/feedback-model.ts"() {
145317
+ "use strict";
145318
+ init_esm_shims();
145319
+ }
145320
+ });
145321
+
145322
+ // src/memory/feedback.ts
145323
+ var feedback_exports = {};
145324
+ __export(feedback_exports, {
145325
+ FEEDBACK_DELTAS: () => FEEDBACK_DELTAS,
145326
+ MemoryFeedbackStore: () => MemoryFeedbackStore,
145327
+ applyMemoryFeedback: () => applyMemoryFeedback,
145328
+ feedbackWeightMultiplier: () => feedbackWeightMultiplier
145329
+ });
145330
+ import { randomUUID as randomUUID7 } from "crypto";
145331
+ function applyMemoryFeedback(current, event) {
145332
+ const delta = event.signal === "revoke" ? 0 : FEEDBACK_DELTAS[event.signal];
145333
+ const weight = Math.max(0, Math.min(2, current.weight + delta));
145334
+ const status = (event.signal === "code-conflict" || event.signal === "user-correction" || event.signal === "weaken") && weight < 0.5 ? "archived" : current.status;
145335
+ return { weight, status, audit: [...current.audit, { ...event }] };
145336
+ }
145337
+ function rowToEvent(row) {
145338
+ return {
145339
+ id: String(row.id),
145340
+ projectId: String(row.project_id),
145341
+ candidateKind: row.candidate_kind,
145342
+ candidateId: String(row.candidate_id),
145343
+ signal: row.signal,
145344
+ ...row.observed_at ? { at: String(row.observed_at) } : { at: (/* @__PURE__ */ new Date()).toISOString() },
145345
+ ...row.actor ? { actor: String(row.actor) } : {},
145346
+ ...row.note ? { note: String(row.note) } : {},
145347
+ ...row.source_ref ? { sourceRef: String(row.source_ref) } : {},
145348
+ ...row.target_event_id ? { targetEventId: String(row.target_event_id) } : {},
145349
+ delta: Number(row.delta ?? 0)
145350
+ };
145351
+ }
145352
+ var FEEDBACK_DELTAS, MemoryFeedbackStore;
145353
+ var init_feedback = __esm({
145354
+ "src/memory/feedback.ts"() {
145355
+ "use strict";
145356
+ init_esm_shims();
145357
+ init_sqlite_db();
145358
+ init_feedback_model();
145359
+ FEEDBACK_DELTAS = {
145360
+ "user-correction": -0.35,
145361
+ "verification-success": 0.25,
145362
+ "verification-failure": -0.25,
145363
+ "code-change": -0.15,
145364
+ used: 0.1,
145365
+ "not-used": -0.05,
145366
+ "code-conflict": -0.4,
145367
+ strengthen: 0.2,
145368
+ weaken: -0.2
145369
+ };
145370
+ MemoryFeedbackStore = class {
145371
+ db = null;
145372
+ async init(dataDir) {
145373
+ this.db = getDatabase(dataDir);
145374
+ }
145375
+ requireDb() {
145376
+ if (!this.db) throw new Error("MemoryFeedbackStore is not initialized.");
145377
+ return this.db;
145378
+ }
145379
+ loadEvents(projectId, candidateKind, candidateId) {
145380
+ return this.requireDb().prepare(`
145381
+ SELECT * FROM memory_feedback_events
145382
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
145383
+ ORDER BY observed_at ASC, rowid ASC
145384
+ `).all(projectId, candidateKind, candidateId).map(rowToEvent);
145385
+ }
145386
+ rebuild(projectId, candidateKind, candidateId) {
145387
+ const events = this.loadEvents(projectId, candidateKind, candidateId);
145388
+ const revoked = new Set(events.filter((event) => event.signal === "revoke" && event.targetEventId).map((event) => event.targetEventId));
145389
+ let state = { weight: 1, status: "active", audit: [] };
145390
+ for (const event of events) {
145391
+ if (event.signal === "revoke" || revoked.has(event.id)) {
145392
+ state.audit.push(event);
145393
+ continue;
145394
+ }
145395
+ state = applyMemoryFeedback(state, event);
145396
+ }
145397
+ this.requireDb().prepare(`
145398
+ INSERT INTO memory_feedback_states (project_id, candidate_kind, candidate_id, weight, lifecycle_status, updated_at)
145399
+ VALUES (?, ?, ?, ?, ?, ?)
145400
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
145401
+ weight = excluded.weight, lifecycle_status = excluded.lifecycle_status, updated_at = excluded.updated_at
145402
+ `).run(projectId, candidateKind, candidateId, state.weight, state.status, (/* @__PURE__ */ new Date()).toISOString());
145403
+ return state;
145404
+ }
145405
+ getState(projectId, candidateKind, candidateId) {
145406
+ const row = this.requireDb().prepare(`
145407
+ SELECT weight, lifecycle_status FROM memory_feedback_states
145408
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
145409
+ `).get(projectId, candidateKind, candidateId);
145410
+ if (!row) return { weight: 1, status: "active", audit: [] };
145411
+ return {
145412
+ weight: Number(row.weight ?? 1),
145413
+ status: row.lifecycle_status ?? "active",
145414
+ audit: this.loadEvents(projectId, candidateKind, candidateId)
145415
+ };
145416
+ }
145417
+ listStates(projectId, candidateKind = "observation") {
145418
+ return this.requireDb().prepare(`
145419
+ SELECT candidate_id, weight, lifecycle_status, updated_at
145420
+ FROM memory_feedback_states WHERE project_id = ? AND candidate_kind = ?
145421
+ ORDER BY updated_at DESC
145422
+ `).all(projectId, candidateKind).map((row) => ({
145423
+ candidateId: String(row.candidate_id),
145424
+ weight: Number(row.weight),
145425
+ status: row.lifecycle_status,
145426
+ updatedAt: String(row.updated_at)
145427
+ }));
145428
+ }
145429
+ weights(projectId, candidateKind = "observation") {
145430
+ const states = /* @__PURE__ */ new Map();
145431
+ for (const row of this.listStates(projectId, candidateKind)) {
145432
+ states.set(row.candidateId, this.getState(projectId, candidateKind, row.candidateId));
145433
+ }
145434
+ return states;
145435
+ }
145436
+ record(input) {
145437
+ if (input.signal === "revoke" && !input.targetEventId) {
145438
+ throw new Error("A revoke feedback event requires targetEventId.");
145439
+ }
145440
+ if (input.signal === "revoke") {
145441
+ const target = this.requireDb().prepare(`
145442
+ SELECT 1 FROM memory_feedback_events
145443
+ WHERE id = ? AND project_id = ? AND candidate_kind = ? AND candidate_id = ?
145444
+ `).get(input.targetEventId, input.projectId, input.candidateKind, input.candidateId);
145445
+ if (!target) throw new Error("The feedback event to revoke was not found in the same candidate scope.");
145446
+ }
145447
+ const event = {
145448
+ id: randomUUID7(),
145449
+ projectId: input.projectId,
145450
+ candidateKind: input.candidateKind,
145451
+ candidateId: input.candidateId,
145452
+ signal: input.signal,
145453
+ sourceRef: input.sourceRef,
145454
+ ...input.actor ? { actor: input.actor } : {},
145455
+ ...input.note ? { note: input.note.slice(0, 2e3) } : {},
145456
+ ...input.targetEventId ? { targetEventId: input.targetEventId } : {},
145457
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
145458
+ delta: input.signal === "revoke" ? 0 : FEEDBACK_DELTAS[input.signal]
145459
+ };
145460
+ this.requireDb().prepare(`
145461
+ INSERT INTO memory_feedback_events (
145462
+ id, project_id, candidate_kind, candidate_id, signal, actor, note,
145463
+ source_ref, target_event_id, delta, observed_at
145464
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145465
+ `).run(
145466
+ event.id,
145467
+ event.projectId,
145468
+ event.candidateKind,
145469
+ event.candidateId,
145470
+ event.signal,
145471
+ event.actor ?? null,
145472
+ event.note ?? null,
145473
+ event.sourceRef,
145474
+ event.targetEventId ?? null,
145475
+ event.delta,
145476
+ event.at
145477
+ );
145478
+ return { event, state: this.rebuild(input.projectId, input.candidateKind, input.candidateId) };
145479
+ }
145480
+ audit(projectId, candidateKind, candidateId, limit2 = 100) {
145481
+ return this.requireDb().prepare(`
145482
+ SELECT * FROM memory_feedback_events
145483
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
145484
+ ORDER BY observed_at DESC, rowid DESC LIMIT ?
145485
+ `).all(projectId, candidateKind, candidateId, Math.min(200, Math.max(1, Math.floor(limit2)))).map(rowToEvent);
145486
+ }
145487
+ };
145488
+ }
145489
+ });
145490
+
144674
145491
  // src/memory/retention.ts
144675
145492
  var retention_exports = {};
144676
145493
  __export(retention_exports, {
@@ -144712,6 +145529,7 @@ function getEffectiveRetentionDays(doc) {
144712
145529
  }
144713
145530
  function isImmune(doc) {
144714
145531
  if (doc.type === "probe") return false;
145532
+ if (doc.feedbackStatus === "archived") return false;
144715
145533
  if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
144716
145534
  if (doc.valueCategory === "core") return true;
144717
145535
  const importance = getImportanceLevel(doc);
@@ -144744,7 +145562,8 @@ function calculateRelevance(doc, referenceTime) {
144744
145562
  const decayFactor = Math.exp(-ageDays / retention);
144745
145563
  const accessCount = doc.accessCount ?? 0;
144746
145564
  const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
144747
- let totalScore = base * decayFactor * accessBoost;
145565
+ const feedbackMultiplier = doc.feedbackWeight === void 0 ? 1 : feedbackWeightMultiplier(doc.feedbackWeight);
145566
+ let totalScore = base * decayFactor * accessBoost * feedbackMultiplier;
144748
145567
  const immune = isImmune(doc);
144749
145568
  if (immune) {
144750
145569
  totalScore = Math.max(totalScore, 0.5);
@@ -144756,7 +145575,8 @@ function calculateRelevance(doc, referenceTime) {
144756
145575
  decayFactor,
144757
145576
  accessBoost,
144758
145577
  ageDays,
144759
- isImmune: immune
145578
+ isImmune: immune,
145579
+ feedbackMultiplier
144760
145580
  };
144761
145581
  }
144762
145582
  function rankByRelevance(docs, referenceTime) {
@@ -144764,6 +145584,7 @@ function rankByRelevance(docs, referenceTime) {
144764
145584
  }
144765
145585
  function getRetentionZone(doc, referenceTime) {
144766
145586
  const now3 = referenceTime ?? /* @__PURE__ */ new Date();
145587
+ if (doc.feedbackStatus === "archived") return "archive-candidate";
144767
145588
  const importance = getImportanceLevel(doc);
144768
145589
  const retention = getEffectiveRetentionDays(doc);
144769
145590
  const createdAt = new Date(doc.createdAt);
@@ -144835,7 +145656,7 @@ function explainRetention(doc, referenceTime) {
144835
145656
  summary: parts.join(" ; ")
144836
145657
  };
144837
145658
  }
144838
- function toRetentionDocument(obs, accessMap) {
145659
+ function toRetentionDocument(obs, accessMap, feedback) {
144839
145660
  const access3 = accessMap?.get(obs.id);
144840
145661
  return {
144841
145662
  id: `obs-${obs.id}`,
@@ -144857,11 +145678,12 @@ function toRetentionDocument(obs, accessMap) {
144857
145678
  sourceDetail: obs.sourceDetail ?? "",
144858
145679
  valueCategory: obs.valueCategory ?? "",
144859
145680
  admissionState: obs.admissionState ?? "",
144860
- admissionReason: obs.admissionReason ?? ""
145681
+ admissionReason: obs.admissionReason ?? "",
145682
+ ...feedback ? { feedbackWeight: feedback.weight, feedbackStatus: feedback.status } : {}
144861
145683
  };
144862
145684
  }
144863
145685
  function projectObservationRetention(observation, options = {}) {
144864
- const document2 = toRetentionDocument(observation, options.accessMap);
145686
+ const document2 = toRetentionDocument(observation, options.accessMap, options.feedback);
144865
145687
  const relevance = calculateRelevance(document2, options.referenceTime);
144866
145688
  const explanation = explainRetention(document2, options.referenceTime);
144867
145689
  return {
@@ -144886,7 +145708,17 @@ function summarizeRetentionProjections(projections) {
144886
145708
  }
144887
145709
  return { active, stale, archiveCandidates, immune };
144888
145710
  }
144889
- async function archiveExpiredBatch(_projectDir, options) {
145711
+ async function loadFeedbackStore(dataDir) {
145712
+ try {
145713
+ const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
145714
+ const store = new MemoryFeedbackStore2();
145715
+ await store.init(dataDir);
145716
+ return store;
145717
+ } catch {
145718
+ return void 0;
145719
+ }
145720
+ }
145721
+ async function archiveExpiredBatch(projectDir2, options) {
144890
145722
  const store = getObservationStore();
144891
145723
  const limit2 = Math.min(500, Math.max(1, Math.floor(options.limit ?? 100)));
144892
145724
  const afterId = Math.max(0, Math.floor(options.afterId ?? 0));
@@ -144895,11 +145727,13 @@ async function archiveExpiredBatch(_projectDir, options) {
144895
145727
  afterId,
144896
145728
  limit: limit2 + 1
144897
145729
  });
145730
+ const feedback = await loadFeedbackStore(projectDir2);
144898
145731
  const hasMore = page.length > limit2;
144899
145732
  const scanned = hasMore ? page.slice(0, limit2) : page;
144900
145733
  const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => projectObservationRetention(observation, {
144901
145734
  accessMap: options.accessMap,
144902
- referenceTime: options.referenceTime
145735
+ referenceTime: options.referenceTime,
145736
+ ...feedback ? { feedback: feedback.getState(options.projectId, "observation", String(observation.id)) } : {}
144903
145737
  }).zone === "archive-candidate").map((observation) => observation.id);
144904
145738
  const archived = candidateIds.length === 0 ? 0 : await store.atomic(async (tx) => {
144905
145739
  const updates = await Promise.all(
@@ -144930,6 +145764,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
144930
145764
  const remaining = reader2 ? remainingObservations.filter((observation) => canManageObservation(observation, reader2)).length : remainingObservations.length;
144931
145765
  return { archived, remaining };
144932
145766
  }
145767
+ const feedback = await loadFeedbackStore(projectDir2);
144933
145768
  return await store.atomic(async (tx) => {
144934
145769
  const allObs = await tx.loadAll();
144935
145770
  const activeObs = allObs.filter(
@@ -144937,7 +145772,11 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
144937
145772
  );
144938
145773
  const archivedIds = [];
144939
145774
  for (const obs of activeObs) {
144940
- const retention = projectObservationRetention(obs, { accessMap, referenceTime });
145775
+ const retention = projectObservationRetention(obs, {
145776
+ accessMap,
145777
+ referenceTime,
145778
+ ...feedback ? { feedback: feedback.getState(obs.projectId, "observation", String(obs.id)) } : {}
145779
+ });
144941
145780
  if (retention.zone === "archive-candidate") {
144942
145781
  archivedIds.push(obs.id);
144943
145782
  }
@@ -144957,6 +145796,7 @@ var init_retention = __esm({
144957
145796
  init_esm_shims();
144958
145797
  init_obs_store();
144959
145798
  init_visibility();
145799
+ init_feedback_model();
144960
145800
  RETENTION_DAYS = {
144961
145801
  critical: 365,
144962
145802
  high: 180,
@@ -144997,7 +145837,7 @@ var owner_exports = {};
144997
145837
  __export(owner_exports, {
144998
145838
  resolveLocalMemoryOwner: () => resolveLocalMemoryOwner
144999
145839
  });
145000
- import { randomUUID as randomUUID6 } from "crypto";
145840
+ import { randomUUID as randomUUID8 } from "crypto";
145001
145841
  import { promises as fs12 } from "fs";
145002
145842
  import path20 from "path";
145003
145843
  function ownerPath(dataDir) {
@@ -145027,7 +145867,7 @@ async function resolveLocalMemoryOwner(dataDir, options = {}) {
145027
145867
  await fs12.mkdir(path20.dirname(filePath), { recursive: true });
145028
145868
  const created = {
145029
145869
  version: 1,
145030
- id: "local-" + randomUUID6(),
145870
+ id: "local-" + randomUUID8(),
145031
145871
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
145032
145872
  };
145033
145873
  try {
@@ -145050,7 +145890,7 @@ var init_owner = __esm({
145050
145890
  });
145051
145891
 
145052
145892
  // src/memory/long-term-store.ts
145053
- import { randomUUID as randomUUID7 } from "crypto";
145893
+ import { randomUUID as randomUUID9 } from "crypto";
145054
145894
  function optionalText2(value) {
145055
145895
  return typeof value === "string" && value.trim() ? value : void 0;
145056
145896
  }
@@ -145101,7 +145941,7 @@ function rowToEvidence(row) {
145101
145941
  createdAt: row.createdAt
145102
145942
  };
145103
145943
  }
145104
- function rowToEvent(row) {
145944
+ function rowToEvent2(row) {
145105
145945
  return {
145106
145946
  id: row.id,
145107
145947
  memoryId: row.memoryId,
@@ -145210,7 +146050,7 @@ var init_long_term_store = __esm({
145210
146050
  }
145211
146051
  insertEvidence(input) {
145212
146052
  const evidence = {
145213
- id: randomUUID7(),
146053
+ id: randomUUID9(),
145214
146054
  ...input,
145215
146055
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
145216
146056
  };
@@ -145237,7 +146077,7 @@ var init_long_term_store = __esm({
145237
146077
  }
145238
146078
  recordEvent(input) {
145239
146079
  const event = {
145240
- id: randomUUID7(),
146080
+ id: randomUUID9(),
145241
146081
  ...input,
145242
146082
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
145243
146083
  };
@@ -145258,7 +146098,7 @@ var init_long_term_store = __esm({
145258
146098
  listEvents(memoryId) {
145259
146099
  return this.db.prepare(`
145260
146100
  SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
145261
- `).all(memoryId).map(rowToEvent);
146101
+ `).all(memoryId).map(rowToEvent2);
145262
146102
  }
145263
146103
  markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
145264
146104
  const unique2 = [...new Set(ids.filter(Boolean))];
@@ -145327,7 +146167,7 @@ __export(long_term_exports, {
145327
146167
  selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
145328
146168
  supersedeLongTermMemory: () => supersedeLongTermMemory
145329
146169
  });
145330
- import { randomUUID as randomUUID8 } from "crypto";
146170
+ import { randomUUID as randomUUID10 } from "crypto";
145331
146171
  function isOneOf(value, values) {
145332
146172
  return typeof value === "string" && values.includes(value);
145333
146173
  }
@@ -145509,7 +146349,7 @@ async function createManualLongTermMemory(input) {
145509
146349
  const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
145510
146350
  if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
145511
146351
  assertScope(input.scope);
145512
- const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID8(), relation: "supports" }];
146352
+ const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID10(), relation: "supports" }];
145513
146353
  return createLongTermMemoryWithDataDir({
145514
146354
  dataDir: input.dataDir,
145515
146355
  originProjectId: input.projectId,
@@ -145564,7 +146404,7 @@ async function createLongTermMemoryWithDataDir(input) {
145564
146404
  assertPortableEvidence(input.scope, portability, evidenceInputs);
145565
146405
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
145566
146406
  const memory = {
145567
- id: randomUUID8(),
146407
+ id: randomUUID10(),
145568
146408
  originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
145569
146409
  ownerId: compactText(input.ownerId, "owner id", 160),
145570
146410
  scope: input.scope,
@@ -145882,9 +146722,9 @@ var init_long_term = __esm({
145882
146722
  });
145883
146723
 
145884
146724
  // src/codegraph/ids.ts
145885
- import { createHash as createHash5 } from "crypto";
146725
+ import { createHash as createHash6 } from "crypto";
145886
146726
  function digest(input) {
145887
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
146727
+ return createHash6("sha256").update(input).digest("hex").slice(0, 16);
145888
146728
  }
145889
146729
  function normalizeCodePath(path37) {
145890
146730
  return path37.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
@@ -145919,7 +146759,7 @@ var store_exports = {};
145919
146759
  __export(store_exports, {
145920
146760
  CodeGraphStore: () => CodeGraphStore
145921
146761
  });
145922
- import { randomUUID as randomUUID9 } from "crypto";
146762
+ import { randomUUID as randomUUID11 } from "crypto";
145923
146763
  import { posix as pathPosix } from "path";
145924
146764
  function rowToFile(row) {
145925
146765
  return {
@@ -146636,7 +147476,7 @@ var init_store = __esm({
146636
147476
  * so an interrupted scan cannot be advertised as complete.
146637
147477
  */
146638
147478
  recordCodeStateSnapshot(input) {
146639
- const id = randomUUID9();
147479
+ const id = randomUUID11();
146640
147480
  let snapshot;
146641
147481
  const tx = this.db.transaction(() => {
146642
147482
  const previous = this.db.prepare(
@@ -146714,7 +147554,7 @@ var init_store = __esm({
146714
147554
 
146715
147555
  // src/codegraph/code-state.ts
146716
147556
  import { execFile as execFile2 } from "child_process";
146717
- import { createHash as createHash6 } from "crypto";
147557
+ import { createHash as createHash7 } from "crypto";
146718
147558
  import { promisify as promisify3 } from "util";
146719
147559
  async function runGit(rootPath, args) {
146720
147560
  try {
@@ -146743,7 +147583,7 @@ function changedPathCount(status) {
146743
147583
  return count2;
146744
147584
  }
146745
147585
  function fingerprint(baseRevision, status) {
146746
- return createHash6("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
147586
+ return createHash7("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
146747
147587
  }
146748
147588
  async function collectCodeStateSnapshot(input) {
146749
147589
  const [revision, status] = await Promise.all([
@@ -146843,11 +147683,11 @@ __export(lite_provider_exports, {
146843
147683
  indexProjectLite: () => indexProjectLite,
146844
147684
  refreshProjectLite: () => refreshProjectLite
146845
147685
  });
146846
- import { createHash as createHash7 } from "crypto";
147686
+ import { createHash as createHash8 } from "crypto";
146847
147687
  import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
146848
147688
  import { join as join4, relative } from "path";
146849
147689
  function hashText(text2) {
146850
- return createHash7("sha256").update(text2).digest("hex");
147690
+ return createHash8("sha256").update(text2).digest("hex");
146851
147691
  }
146852
147692
  function extension2(path37) {
146853
147693
  const index = path37.lastIndexOf(".");
@@ -147414,7 +148254,7 @@ var claim_store_exports = {};
147414
148254
  __export(claim_store_exports, {
147415
148255
  ClaimStore: () => ClaimStore
147416
148256
  });
147417
- import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
148257
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
147418
148258
  function optionalText3(value) {
147419
148259
  return typeof value === "string" && value ? value : void 0;
147420
148260
  }
@@ -147453,7 +148293,7 @@ function rowToEvidence2(row) {
147453
148293
  createdAt: row.createdAt
147454
148294
  };
147455
148295
  }
147456
- function rowToEvent2(row) {
148296
+ function rowToEvent3(row) {
147457
148297
  return {
147458
148298
  id: row.id,
147459
148299
  projectId: row.projectId,
@@ -147467,7 +148307,7 @@ function rowToEvent2(row) {
147467
148307
  };
147468
148308
  }
147469
148309
  function evidenceKey(input) {
147470
- return createHash8("sha256").update(JSON.stringify([
148310
+ return createHash9("sha256").update(JSON.stringify([
147471
148311
  input.evidenceKind,
147472
148312
  input.evidenceId,
147473
148313
  input.relation,
@@ -147598,7 +148438,7 @@ var init_claim_store = __esm({
147598
148438
  locator, capturedHash, evidenceKey, createdAt
147599
148439
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
147600
148440
  `).run(
147601
- randomUUID10(),
148441
+ randomUUID12(),
147602
148442
  input.claimId,
147603
148443
  input.evidenceKind,
147604
148444
  input.evidenceId,
@@ -147620,7 +148460,7 @@ var init_claim_store = __esm({
147620
148460
  }
147621
148461
  recordEvent(input) {
147622
148462
  const event = {
147623
- id: randomUUID10(),
148463
+ id: randomUUID12(),
147624
148464
  ...input,
147625
148465
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
147626
148466
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -147648,7 +148488,7 @@ var init_claim_store = __esm({
147648
148488
  SELECT * FROM knowledge_claim_events
147649
148489
  WHERE claimId = ?
147650
148490
  ORDER BY createdAt ASC, id
147651
- `).all(claimId).map(rowToEvent2);
148491
+ `).all(claimId).map(rowToEvent3);
147652
148492
  }
147653
148493
  };
147654
148494
  }
@@ -147665,7 +148505,7 @@ __export(claims_exports, {
147665
148505
  supersedeClaim: () => supersedeClaim,
147666
148506
  writeClaim: () => writeClaim
147667
148507
  });
147668
- import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
148508
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
147669
148509
  function compactText2(value, field) {
147670
148510
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
147671
148511
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
@@ -147677,7 +148517,7 @@ function normalizedTerm(value) {
147677
148517
  return compactText2(value, "identity").toLocaleLowerCase("en-US");
147678
148518
  }
147679
148519
  function hashIdentity(parts) {
147680
- return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
148520
+ return createHash10("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
147681
148521
  }
147682
148522
  function buildClaimIdentity(input) {
147683
148523
  const subject = normalizedTerm(input.subject);
@@ -147751,7 +148591,7 @@ function normalizeClaimInput(input) {
147751
148591
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
147752
148592
  return {
147753
148593
  claim: {
147754
- id: randomUUID11(),
148594
+ id: randomUUID13(),
147755
148595
  projectId,
147756
148596
  subject,
147757
148597
  predicate,
@@ -147918,7 +148758,7 @@ function supersedeClaim(store, input) {
147918
148758
  });
147919
148759
  }
147920
148760
  function hashObservation(observation) {
147921
- return createHash9("sha256").update(JSON.stringify([
148761
+ return createHash10("sha256").update(JSON.stringify([
147922
148762
  observation.title,
147923
148763
  observation.narrative,
147924
148764
  observation.facts,
@@ -148162,11 +149002,11 @@ var init_frontmatter = __esm({
148162
149002
  });
148163
149003
 
148164
149004
  // src/knowledge/markdown.ts
148165
- import { createHash as createHash10 } from "crypto";
149005
+ import { createHash as createHash11 } from "crypto";
148166
149006
  import { promises as fs13 } from "fs";
148167
149007
  import path21 from "path";
148168
149008
  function hashContent(content) {
148169
- return createHash10("sha256").update(content).digest("hex");
149009
+ return createHash11("sha256").update(content).digest("hex");
148170
149010
  }
148171
149011
  function stringField(data, key) {
148172
149012
  const value = data[key];
@@ -148292,11 +149132,11 @@ __export(wiki_exports, {
148292
149132
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
148293
149133
  readKnowledgePage: () => readKnowledgePage
148294
149134
  });
148295
- import { createHash as createHash11 } from "crypto";
149135
+ import { createHash as createHash12 } from "crypto";
148296
149136
  import { promises as fs14 } from "fs";
148297
149137
  import path22 from "path";
148298
149138
  function hash2(value) {
148299
- return createHash11("sha256").update(value).digest("hex");
149139
+ return createHash12("sha256").update(value).digest("hex");
148300
149140
  }
148301
149141
  function now() {
148302
149142
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -148840,7 +149680,7 @@ var workflow_store_exports = {};
148840
149680
  __export(workflow_store_exports, {
148841
149681
  WorkflowStore: () => WorkflowStore
148842
149682
  });
148843
- import { randomUUID as randomUUID12 } from "crypto";
149683
+ import { randomUUID as randomUUID14 } from "crypto";
148844
149684
  function parseJson(value, fallback) {
148845
149685
  if (typeof value !== "string" || !value) return fallback;
148846
149686
  try {
@@ -148940,7 +149780,7 @@ var init_workflow_store = __esm({
148940
149780
  }
148941
149781
  recordRun(input) {
148942
149782
  const run = {
148943
- id: randomUUID12(),
149783
+ id: randomUUID14(),
148944
149784
  workflowId: input.workflowId,
148945
149785
  projectId: input.projectId,
148946
149786
  task: input.task.trim(),
@@ -149018,7 +149858,7 @@ var init_outcome_types = __esm({
149018
149858
  });
149019
149859
 
149020
149860
  // src/knowledge/outcome-store.ts
149021
- import { randomUUID as randomUUID13 } from "crypto";
149861
+ import { randomUUID as randomUUID15 } from "crypto";
149022
149862
  function optionalText5(value) {
149023
149863
  return typeof value === "string" && value.trim() ? value : void 0;
149024
149864
  }
@@ -149062,7 +149902,7 @@ var init_outcome_store = __esm({
149062
149902
  }
149063
149903
  record(input) {
149064
149904
  const signal = {
149065
- id: randomUUID13(),
149905
+ id: randomUUID15(),
149066
149906
  projectId: text(input.projectId, "project id"),
149067
149907
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
149068
149908
  candidateId: text(input.candidateId, "candidate id"),
@@ -149489,11 +150329,11 @@ __export(workflows_exports, {
149489
150329
  syncCanonicalWorkflows: () => syncCanonicalWorkflows,
149490
150330
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
149491
150331
  });
149492
- import { createHash as createHash12 } from "crypto";
150332
+ import { createHash as createHash13 } from "crypto";
149493
150333
  import { promises as fs15 } from "fs";
149494
150334
  import path23 from "path";
149495
150335
  function hash3(value) {
149496
- return createHash12("sha256").update(value).digest("hex");
150336
+ return createHash13("sha256").update(value).digest("hex");
149497
150337
  }
149498
150338
  function now2() {
149499
150339
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -150890,6 +151730,16 @@ async function bindObservationCodeRefsBestEffort(observation) {
150890
151730
  } catch {
150891
151731
  }
150892
151732
  }
151733
+ async function persistEvidenceCard(observation) {
151734
+ if (!projectDir) return;
151735
+ try {
151736
+ const evidence = new EvidenceCardStore();
151737
+ await evidence.init(projectDir);
151738
+ evidence.upsertObservation(observation);
151739
+ } catch (error) {
151740
+ console.error(`[memorix] Evidence card projection failed for obs-${observation.id}: ${error instanceof Error ? error.message : String(error)}`);
151741
+ }
151742
+ }
150893
151743
  function queueClaimDerivation(observation) {
150894
151744
  const dataDir = projectDir;
150895
151745
  const isExplicit = observation.sourceDetail === "explicit";
@@ -151236,6 +152086,7 @@ async function storeObservation(input) {
151236
152086
  }
151237
152087
  if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
151238
152088
  await bindObservationCodeRefsBestEffort(observation);
152089
+ await persistEvidenceCard(observation);
151239
152090
  queueObservationQualification(observation);
151240
152091
  if (resolveObservationVisibility(observation) === "project") {
151241
152092
  queueClaimDerivation(observation);
@@ -151328,6 +152179,7 @@ async function upsertObservation(existing, input, now3) {
151328
152179
  await store.update(existing);
151329
152180
  }
151330
152181
  await bindObservationCodeRefsBestEffort(existing);
152182
+ await persistEvidenceCard(existing);
151331
152183
  queueObservationQualification(existing);
151332
152184
  if (resolveObservationVisibility(existing) === "project") {
151333
152185
  queueClaimDerivation(existing);
@@ -151461,6 +152313,7 @@ async function resolveObservations(ids, status = "resolved") {
151461
152313
  await Promise.all(changed.map((observation) => tx.update(observation)));
151462
152314
  });
151463
152315
  }
152316
+ for (const observation of changed) await persistEvidenceCard(observation);
151464
152317
  return { resolved, notFound };
151465
152318
  }
151466
152319
  function getProjectObservations(projectId) {
@@ -151798,6 +152651,7 @@ var init_observations = __esm({
151798
152651
  init_secret_filter();
151799
152652
  init_lifecycle();
151800
152653
  init_visibility();
152654
+ init_evidence_store();
151801
152655
  observations = [];
151802
152656
  nextId = 1;
151803
152657
  projectDir = null;
@@ -152629,9 +153483,27 @@ function normalizeMemoryBrowseQuery(query) {
152629
153483
  async function compactSearch(options, surface = "mcp") {
152630
153484
  await ensureFreshIndex();
152631
153485
  const searchOptions = { ...options, query: normalizeMemoryBrowseQuery(options.query) };
152632
- const entries = (await searchObservations(searchOptions)).map(
153486
+ let entries = (await searchObservations(searchOptions)).map(
152633
153487
  (entry) => entry.projectId || !options.projectId ? entry : { ...entry, projectId: options.projectId }
152634
153488
  );
153489
+ if (options.projectId) {
153490
+ try {
153491
+ const feedback = new MemoryFeedbackStore();
153492
+ await feedback.init(await getProjectDataDir(options.projectId));
153493
+ const states = feedback.weights(options.projectId, "observation");
153494
+ entries = entries.map((entry) => {
153495
+ const state = states.get(String(entry.id));
153496
+ if (!state) return entry;
153497
+ return {
153498
+ ...entry,
153499
+ score: (entry.score ?? 0) * feedbackWeightMultiplier(state.weight),
153500
+ feedbackWeight: state.weight,
153501
+ feedbackStatus: state.status
153502
+ };
153503
+ }).filter((entry) => entry.feedbackStatus !== "archived" || options.status === "all").sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
153504
+ } catch {
153505
+ }
153506
+ }
152635
153507
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId, surface);
152636
153508
  if (entries.length === 0 && options.projectId) {
152637
153509
  const allObservations = filterReadableObservations(getAllObservations(), options.reader);
@@ -152950,6 +153822,8 @@ var init_engine3 = __esm({
152950
153822
  init_mini_skills();
152951
153823
  init_secret_filter();
152952
153824
  init_visibility();
153825
+ init_persistence();
153826
+ init_feedback();
152953
153827
  }
152954
153828
  });
152955
153829
 
@@ -153571,9 +154445,9 @@ var init_graph_context = __esm({
153571
154445
  });
153572
154446
 
153573
154447
  // src/rules/utils.ts
153574
- import { createHash as createHash13 } from "crypto";
154448
+ import { createHash as createHash14 } from "crypto";
153575
154449
  function hashContent2(content) {
153576
- return createHash13("sha256").update(content.trim()).digest("hex").substring(0, 16);
154450
+ return createHash14("sha256").update(content.trim()).digest("hex").substring(0, 16);
153577
154451
  }
153578
154452
  function generateRuleId(source, filePath) {
153579
154453
  const sanitized = filePath.replace(/[\/\\]/g, "-").replace(/^\./, "");
@@ -156004,6 +156878,10 @@ var init_tool_profile = __esm({
156004
156878
  // multimodal support to one additional schema rather than enabling lite.
156005
156879
  memorix_session_start: ["micro", "lite", "team", "full"],
156006
156880
  memorix_media: ["micro", "lite", "team", "full"],
156881
+ // Evidence and feedback are deliberate operator/context-control surfaces;
156882
+ // keep the default micro profile at its nine-tool token budget.
156883
+ memorix_evidence: ["lite", "team", "full"],
156884
+ memorix_feedback: ["lite", "team", "full"],
156007
156885
  // ── lite: extended solo memory surface ────────────────────────────
156008
156886
  memorix_graph_context: ["lite", "team", "full"],
156009
156887
  memorix_timeline: ["lite", "team", "full"],
@@ -159462,7 +160340,7 @@ var compaction_checkpoint_store_exports = {};
159462
160340
  __export(compaction_checkpoint_store_exports, {
159463
160341
  CompactionCheckpointStore: () => CompactionCheckpointStore
159464
160342
  });
159465
- import { createHash as createHash14, randomUUID as randomUUID14 } from "crypto";
160343
+ import { createHash as createHash15, randomUUID as randomUUID16 } from "crypto";
159466
160344
  function optionalText7(value) {
159467
160345
  return typeof value === "string" && value ? value : void 0;
159468
160346
  }
@@ -159504,7 +160382,7 @@ function sourceKeyFor(input, summary) {
159504
160382
  input.tokensBefore ?? "",
159505
160383
  input.firstKeptEntryId ?? ""
159506
160384
  ].join("\0");
159507
- return `derived:${createHash14("sha256").update(material).digest("hex").slice(0, 32)}`;
160385
+ return `derived:${createHash15("sha256").update(material).digest("hex").slice(0, 32)}`;
159508
160386
  }
159509
160387
  function rowToCheckpoint(row) {
159510
160388
  return {
@@ -159563,7 +160441,7 @@ var init_compaction_checkpoint_store = __esm({
159563
160441
  if (pending) return rowToCheckpoint(pending);
159564
160442
  const now3 = input.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
159565
160443
  const checkpoint = {
159566
- id: randomUUID14(),
160444
+ id: randomUUID16(),
159567
160445
  projectId: input.projectId,
159568
160446
  sessionId: input.sessionId,
159569
160447
  agent: input.agent,
@@ -159571,7 +160449,7 @@ var init_compaction_checkpoint_store = __esm({
159571
160449
  captureKind: "preflight",
159572
160450
  reason: safeReason(input.reason),
159573
160451
  sourceEvent: input.sourceEvent,
159574
- sourceKey: `pre:${randomUUID14()}`,
160452
+ sourceKey: `pre:${randomUUID16()}`,
159575
160453
  transcriptAvailable: Boolean(input.transcriptAvailable),
159576
160454
  status: "active",
159577
160455
  preCapturedAt: now3,
@@ -159637,7 +160515,7 @@ var init_compaction_checkpoint_store = __esm({
159637
160515
  return this.get(checkpoint2.id);
159638
160516
  }
159639
160517
  const checkpoint = {
159640
- id: randomUUID14(),
160518
+ id: randomUUID16(),
159641
160519
  projectId: input.projectId,
159642
160520
  sessionId: input.sessionId,
159643
160521
  agent: input.agent,
@@ -161001,7 +161879,7 @@ __export(team_store_exports, {
161001
161879
  isTeamStoreInitialized: () => isTeamStoreInitialized,
161002
161880
  resetTeamStore: () => resetTeamStore
161003
161881
  });
161004
- import { randomUUID as randomUUID15 } from "crypto";
161882
+ import { randomUUID as randomUUID17 } from "crypto";
161005
161883
  import path30 from "path";
161006
161884
  import fs18 from "fs";
161007
161885
  function getTeamStore() {
@@ -161290,7 +162168,7 @@ var init_team_store = __esm({
161290
162168
  * reactivate it (preserving agent_id). Otherwise create a new one.
161291
162169
  */
161292
162170
  registerAgent(input) {
161293
- const instanceId = input.instanceId || randomUUID15();
162171
+ const instanceId = input.instanceId || randomUUID17();
161294
162172
  const now3 = Date.now();
161295
162173
  const existing = this.stmtAgentFindByInstance.get(
161296
162174
  input.projectId,
@@ -161316,7 +162194,7 @@ var init_team_store = __esm({
161316
162194
  this.eventBus?.emit("agent:joined", { agentId: row2.agent_id, projectId: input.projectId, agentType: input.agentType });
161317
162195
  return row2;
161318
162196
  }
161319
- const agentId = randomUUID15();
162197
+ const agentId = randomUUID17();
161320
162198
  this.stmtAgentUpsert.run({
161321
162199
  agent_id: agentId,
161322
162200
  project_id: input.projectId,
@@ -161418,7 +162296,7 @@ var init_team_store = __esm({
161418
162296
  return { error: `Recipient agent '${input.recipientAgentId}' not found \u2014 cannot send to unknown agent` };
161419
162297
  }
161420
162298
  }
161421
- const id = randomUUID15();
162299
+ const id = randomUUID17();
161422
162300
  const now3 = Date.now();
161423
162301
  const row = {
161424
162302
  id,
@@ -161468,7 +162346,7 @@ var init_team_store = __esm({
161468
162346
  // Task Operations (with atomic claim semantics)
161469
162347
  // ═══════════════════════════════════════════════════════════════════
161470
162348
  createTask(input) {
161471
- const taskId = randomUUID15();
162349
+ const taskId = randomUUID17();
161472
162350
  const now3 = Date.now();
161473
162351
  const row = {
161474
162352
  task_id: taskId,
@@ -161740,7 +162618,7 @@ var init_team_store = __esm({
161740
162618
  for (const [agentId, msgs] of Object.entries(snap.messages.inboxes)) {
161741
162619
  for (const msg of msgs) {
161742
162620
  this.stmtMsgInsert.run({
161743
- id: msg.id ?? randomUUID15(),
162621
+ id: msg.id ?? randomUUID17(),
161744
162622
  project_id: "migrated",
161745
162623
  sender_agent_id: msg.from ?? "unknown",
161746
162624
  recipient_agent_id: msg.to === "__broadcast__" ? null : msg.to ?? agentId,
@@ -162611,6 +163489,23 @@ var init_maintenance = __esm({
162611
163489
  }
162612
163490
  });
162613
163491
 
163492
+ // src/dashboard/config-provenance.ts
163493
+ function resolveDashboardKeySource({ value, envKeys, configSource }) {
163494
+ for (const envKey of envKeys) {
163495
+ const envValue = process.env[envKey];
163496
+ if (envValue !== value) continue;
163497
+ return getLoadedEnvValue(envKey) === envValue ? ".env" : `env:${envKey}`;
163498
+ }
163499
+ return configSource;
163500
+ }
163501
+ var init_config_provenance = __esm({
163502
+ "src/dashboard/config-provenance.ts"() {
163503
+ "use strict";
163504
+ init_esm_shims();
163505
+ init_dotenv_loader();
163506
+ }
163507
+ });
163508
+
162614
163509
  // src/dashboard/project-classification.ts
162615
163510
  var project_classification_exports = {};
162616
163511
  __export(project_classification_exports, {
@@ -163390,6 +164285,20 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163390
164285
  sendJson(res, observations2);
163391
164286
  break;
163392
164287
  }
164288
+ case "/evidence": {
164289
+ const observations2 = filterDashboardObservations(
164290
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
164291
+ effectiveProjectId
164292
+ );
164293
+ const evidenceStore = new EvidenceCardStore();
164294
+ await evidenceStore.init(effectiveDataDir);
164295
+ evidenceStore.syncObservations(observations2);
164296
+ const query = url.searchParams.get("q") || "";
164297
+ const limit2 = Number(url.searchParams.get("limit") || 20);
164298
+ 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()));
164299
+ sendJson(res, { cards, projectId: effectiveProjectId });
164300
+ break;
164301
+ }
163393
164302
  case "/sessions": {
163394
164303
  const allSessions = await getSessionStore().loadAll();
163395
164304
  const sessions = filterByProject(allSessions, effectiveProjectId);
@@ -163437,8 +164346,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163437
164346
  filesModified: o.filesModified
163438
164347
  }));
163439
164348
  const retentionReferenceTime = new Date(now3);
164349
+ const feedback = new MemoryFeedbackStore();
164350
+ await feedback.init(effectiveDataDir).catch(() => void 0);
163440
164351
  const retention = summarizeRetentionProjections(
163441
- observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, { referenceTime: retentionReferenceTime }))
164352
+ observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, {
164353
+ referenceTime: retentionReferenceTime,
164354
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
164355
+ }))
163442
164356
  );
163443
164357
  const retentionSummary = {
163444
164358
  active: retention.active,
@@ -163532,9 +164446,14 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163532
164446
  effectiveProjectId
163533
164447
  );
163534
164448
  const referenceTime = /* @__PURE__ */ new Date();
164449
+ const feedback = new MemoryFeedbackStore();
164450
+ await feedback.init(effectiveDataDir).catch(() => void 0);
163535
164451
  const rows = observations2.filter((observation) => observation.type !== "probe").map((observation) => ({
163536
164452
  observation,
163537
- retention: projectObservationRetention(observation, { referenceTime })
164453
+ retention: projectObservationRetention(observation, {
164454
+ referenceTime,
164455
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
164456
+ })
163538
164457
  })).sort((a, b) => b.retention.displayScore - a.retention.displayScore);
163539
164458
  const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
163540
164459
  const scored = rows.map(({ observation, retention }) => ({
@@ -163605,23 +164524,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163605
164524
  const os6 = await import("os");
163606
164525
  const { existsSync: existsSync21 } = await import("fs");
163607
164526
  const { join: join26 } = await import("path");
163608
- let yml = {};
163609
164527
  const configProjectRoot = effectiveProjectRoot;
163610
- try {
163611
- const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
163612
- yml = configProjectRoot ? loadYamlConfig2(configProjectRoot) : loadYamlConfig2(null);
163613
- } catch {
163614
- }
163615
- if (configProjectRoot) {
163616
- try {
163617
- const { loadDotenv: loadDotenv2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
163618
- loadDotenv2(configProjectRoot);
163619
- } catch {
163620
- }
163621
- }
164528
+ const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
163622
164529
  const files = {
163623
164530
  "project memorix.yml": { exists: false, path: "", unavailable: !configProjectRoot },
163624
164531
  "user memorix.yml": { exists: false, path: "" },
164532
+ "project memorix.toml": { exists: false, path: "", unavailable: !configProjectRoot },
164533
+ "user config.toml": { exists: false, path: "" },
163625
164534
  "project .env": { exists: false, path: "", unavailable: !configProjectRoot },
163626
164535
  "user .env": { exists: false, path: "" },
163627
164536
  "legacy config.json": { exists: false, path: "" }
@@ -163631,6 +164540,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163631
164540
  const paths = {
163632
164541
  "project memorix.yml": configProjectRoot ? join26(configProjectRoot, "memorix.yml") : null,
163633
164542
  "user memorix.yml": join26(home, ".memorix", "memorix.yml"),
164543
+ "project memorix.toml": configProjectRoot ? join26(configProjectRoot, "memorix.toml") : null,
164544
+ "user config.toml": join26(home, ".memorix", "config.toml"),
163634
164545
  "project .env": configProjectRoot ? join26(configProjectRoot, ".env") : null,
163635
164546
  "user .env": join26(home, ".memorix", ".env"),
163636
164547
  "legacy config.json": join26(home, ".memorix", "config.json")
@@ -163645,67 +164556,61 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163645
164556
  } catch {
163646
164557
  }
163647
164558
  const values = [];
163648
- const getEnvSource = async (envKey, ymlSource) => {
163649
- if (process.env[envKey]) {
163650
- try {
163651
- const { getLoadedEnvFiles: getLoadedEnvFiles2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
163652
- const envFiles = getLoadedEnvFiles2();
163653
- if (envFiles.length > 0) return `.env (${envKey})`;
163654
- } catch {
163655
- }
163656
- return `env:${envKey}`;
163657
- }
163658
- return ymlSource ?? "default";
163659
- };
163660
- const llmProvider = process.env.MEMORIX_LLM_PROVIDER || yml.llm?.provider;
163661
- if (llmProvider) values.push({ key: "llm.provider", value: llmProvider, source: await getEnvSource("MEMORIX_LLM_PROVIDER", yml.llm?.provider ? "memorix.yml" : void 0) });
163662
- const llmModel = process.env.MEMORIX_LLM_MODEL || yml.llm?.model;
163663
- if (llmModel) values.push({ key: "llm.model", value: llmModel, source: await getEnvSource("MEMORIX_LLM_MODEL", yml.llm?.model ? "memorix.yml" : void 0) });
163664
- const llmKey = process.env.MEMORIX_LLM_API_KEY || process.env.MEMORIX_API_KEY || yml.llm?.apiKey || process.env.OPENAI_API_KEY;
164559
+ const tomlSource = configProjectRoot && files["project memorix.toml"].exists ? "memorix.toml" : files["user config.toml"].exists ? "config.toml" : "default";
164560
+ const legacySource = files["project memorix.yml"].exists || files["user memorix.yml"].exists ? "memorix.yml" : files["legacy config.json"].exists ? "config.json" : "default";
164561
+ const configSource = resolved.sources.toml.length > 0 ? tomlSource : resolved.sources.legacy.length > 0 ? legacySource : "default";
164562
+ const source = (envKey) => resolveDashboardKeySource({
164563
+ value: process.env[envKey] ?? "",
164564
+ envKeys: [envKey],
164565
+ configSource
164566
+ });
164567
+ if (resolved.memory.llm.provider) values.push({ key: "llm.provider", value: resolved.memory.llm.provider, source: source("MEMORIX_LLM_PROVIDER") });
164568
+ if (resolved.memory.llm.model) values.push({ key: "llm.model", value: resolved.memory.llm.model, source: source("MEMORIX_LLM_MODEL") });
164569
+ const llmKey = resolved.memory.llm.apiKey;
164570
+ const memoryKeyEnv = [
164571
+ "MEMORIX_LLM_API_KEY",
164572
+ "MEMORIX_API_KEY",
164573
+ "OPENAI_API_KEY",
164574
+ "ANTHROPIC_API_KEY",
164575
+ ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : []
164576
+ ];
163665
164577
  if (llmKey) {
163666
- let src = "unknown";
163667
- if (process.env.MEMORIX_LLM_API_KEY) src = await getEnvSource("MEMORIX_LLM_API_KEY");
163668
- else if (process.env.MEMORIX_API_KEY) src = await getEnvSource("MEMORIX_API_KEY");
163669
- else if (yml.llm?.apiKey) src = "memorix.yml (move to .env!)";
163670
- else if (process.env.OPENAI_API_KEY) src = await getEnvSource("OPENAI_API_KEY");
164578
+ const src = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
163671
164579
  values.push({ key: "llm.apiKey", value: "****" + llmKey.slice(-4), source: src, sensitive: true });
163672
164580
  } else {
163673
164581
  values.push({ key: "llm.apiKey", value: "not set", source: "none" });
163674
164582
  }
163675
- const agentProvider = process.env.MEMORIX_AGENT_PROVIDER || process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
163676
- if (agentProvider) {
163677
- let source;
163678
- if (process.env.MEMORIX_AGENT_PROVIDER) source = await getEnvSource("MEMORIX_AGENT_PROVIDER");
163679
- else if (process.env.MEMORIX_AGENT_LLM_PROVIDER) source = `${await getEnvSource("MEMORIX_AGENT_LLM_PROVIDER")} (legacy)`;
163680
- else source = yml.agent?.provider ? "memorix.yml" : "default";
163681
- values.push({ key: "agent.provider", value: agentProvider, source });
163682
- }
163683
- const agentModel = process.env.MEMORIX_AGENT_MODEL || process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
163684
- if (agentModel) {
163685
- let source;
163686
- if (process.env.MEMORIX_AGENT_MODEL) source = await getEnvSource("MEMORIX_AGENT_MODEL");
163687
- else if (process.env.MEMORIX_AGENT_LLM_MODEL) source = `${await getEnvSource("MEMORIX_AGENT_LLM_MODEL")} (legacy)`;
163688
- else source = yml.agent?.model ? "memorix.yml" : "default";
163689
- values.push({ key: "agent.model", value: agentModel, source });
163690
- }
163691
- const agentKey = process.env.MEMORIX_AGENT_API_KEY || process.env.MEMORIX_AGENT_LLM_API_KEY || yml.agent?.apiKey;
164583
+ if (resolved.agent.provider) {
164584
+ values.push({ key: "agent.provider", value: resolved.agent.provider, source: source("MEMORIX_AGENT_PROVIDER") });
164585
+ }
164586
+ if (resolved.agent.model) {
164587
+ values.push({ key: "agent.model", value: resolved.agent.model, source: source("MEMORIX_AGENT_MODEL") });
164588
+ }
164589
+ const agentKey = resolved.agent.apiKey;
163692
164590
  if (agentKey) {
163693
- let src = "unknown";
163694
- if (process.env.MEMORIX_AGENT_API_KEY) src = await getEnvSource("MEMORIX_AGENT_API_KEY");
163695
- else if (process.env.MEMORIX_AGENT_LLM_API_KEY) src = `${await getEnvSource("MEMORIX_AGENT_LLM_API_KEY")} (legacy)`;
163696
- else if (yml.agent?.apiKey) src = "memorix.yml (move to .env!)";
164591
+ const src = resolveDashboardKeySource({
164592
+ value: agentKey,
164593
+ envKeys: ["MEMORIX_AGENT_API_KEY", "MEMORIX_AGENT_LLM_API_KEY"],
164594
+ configSource
164595
+ });
163697
164596
  values.push({ key: "agent.apiKey", value: "****" + agentKey.slice(-4), source: src, sensitive: true });
163698
164597
  } else {
163699
- values.push({ key: "agent.apiKey", value: "fallback to llm.apiKey", source: "default" });
163700
- }
163701
- const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
163702
- values.push({ key: "embedding.provider", value: embProvider, source: await getEnvSource("MEMORIX_EMBEDDING", yml.embedding?.provider ? "memorix.yml" : void 0) });
163703
- values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
163704
- values.push({ key: "git.skipMergeCommits", value: String(yml.git?.skipMergeCommits ?? true), source: yml.git?.skipMergeCommits !== void 0 ? "memorix.yml" : "default" });
163705
- if (yml.behavior?.formationMode) values.push({ key: "behavior.formationMode", value: yml.behavior.formationMode, source: "memorix.yml" });
163706
- if (yml.behavior?.sessionInject) values.push({ key: "behavior.sessionInject", value: yml.behavior.sessionInject, source: "memorix.yml" });
163707
- values.push({ key: "server.transport", value: yml.server?.transport || "stdio", source: yml.server?.transport ? "memorix.yml" : "default" });
163708
- values.push({ key: "server.dashboard", value: String(yml.server?.dashboard ?? true), source: yml.server?.dashboard !== void 0 ? "memorix.yml" : "default" });
164598
+ values.push({
164599
+ key: "agent.apiKey",
164600
+ value: "fallback to llm.apiKey",
164601
+ source: llmKey ? resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource }) : "default"
164602
+ });
164603
+ }
164604
+ values.push({ key: "embedding.provider", value: resolved.embedding.provider || "off", source: source("MEMORIX_EMBEDDING") });
164605
+ if (resolved.embedding.model) values.push({ key: "embedding.model", value: resolved.embedding.model, source: source("MEMORIX_EMBEDDING_MODEL") });
164606
+ if (resolved.rerank.provider !== "off") {
164607
+ values.push({ key: "rerank.provider", value: resolved.rerank.provider, source: source("MEMORIX_RERANK_PROVIDER") });
164608
+ if (resolved.rerank.model) values.push({ key: "rerank.model", value: resolved.rerank.model, source: source("MEMORIX_RERANK_MODEL") });
164609
+ }
164610
+ values.push({ key: "git.autoHook", value: String(resolved.git.autoHook ?? false), source: configSource });
164611
+ values.push({ key: "git.skipMergeCommits", value: String(resolved.git.skipMergeCommits ?? true), source: configSource });
164612
+ values.push({ key: "server.transport", value: resolved.server.transport || "stdio", source: configSource });
164613
+ values.push({ key: "server.dashboard", value: String(resolved.server.dashboard ?? true), source: configSource });
163709
164614
  sendJson(res, { files, values });
163710
164615
  break;
163711
164616
  }
@@ -164153,10 +165058,14 @@ var init_server = __esm({
164153
165058
  init_dotenv_loader();
164154
165059
  init_yaml_loader();
164155
165060
  init_yaml_loader();
165061
+ init_resolved_config();
164156
165062
  init_graph_scope();
164157
165063
  init_retention();
164158
165064
  init_visibility();
165065
+ init_evidence_store();
165066
+ init_feedback();
164159
165067
  init_maintenance();
165068
+ init_config_provenance();
164160
165069
  MIME_TYPES = {
164161
165070
  ".html": "text/html; charset=utf-8",
164162
165071
  ".css": "text/css; charset=utf-8",
@@ -164624,7 +165533,7 @@ __export(planner_exports, {
164624
165533
  materializeTaskGraph: () => materializeTaskGraph,
164625
165534
  seedAutonomousPipeline: () => seedAutonomousPipeline
164626
165535
  });
164627
- import { randomUUID as randomUUID16 } from "crypto";
165536
+ import { randomUUID as randomUUID18 } from "crypto";
164628
165537
  function isPlannerTask(metadata) {
164629
165538
  if (!metadata) return null;
164630
165539
  try {
@@ -164682,7 +165591,7 @@ function seedAutonomousPipeline(teamStore, projectId, config, opts) {
164682
165591
  const maxIterations = config.maxIterations ?? 3;
164683
165592
  const taskBudget = config.taskBudget ?? 15;
164684
165593
  const structuredPlan = opts?.structuredPlan ?? true;
164685
- const pipelineId = randomUUID16();
165594
+ const pipelineId = randomUUID18();
164686
165595
  const meta = {
164687
165596
  plannerType: "plan",
164688
165597
  pipelineId,
@@ -165217,8 +166126,7 @@ async function callVisionLLM(systemPrompt, imageBase64, mimeType) {
165217
166126
  if (!apiKey) {
165218
166127
  throw new Error("No LLM API key configured for image analysis.");
165219
166128
  }
165220
- let baseUrl = getLLMBaseUrl("https://api.openai.com/v1").replace(/\/+$/, "");
165221
- if (!baseUrl.endsWith("/v1")) baseUrl += "/v1";
166129
+ const baseUrl = normalizeOpenAICompatibleBaseUrl(getLLMBaseUrl("https://api.openai.com/v1"));
165222
166130
  const model = getLLMModel("gpt-4o");
165223
166131
  const response = await fetch(`${baseUrl}/chat/completions`, {
165224
166132
  method: "POST",
@@ -165355,8 +166263,8 @@ var init_official_skills = __esm({
165355
166263
  "",
165356
166264
  "| Situation | Prefer | CLI fallback |",
165357
166265
  "|---|---|---|",
165358
- '| 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` |',
165359
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
166266
+ '| 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` |',
166267
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --fallback --brief-json` |',
165360
166268
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
165361
166269
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
165362
166270
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -165369,7 +166277,7 @@ var init_official_skills = __esm({
165369
166277
  "",
165370
166278
  "- 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.",
165371
166279
  "- 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.",
165372
- '- 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.',
166280
+ '- 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.',
165373
166281
  "- 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.",
165374
166282
  "- 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.",
165375
166283
  "- 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.",
@@ -166628,7 +167536,7 @@ alwaysApply: true
166628
167536
  "",
166629
167537
  `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.`,
166630
167538
  "",
166631
- '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.',
167539
+ '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.',
166632
167540
  "",
166633
167541
  "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.",
166634
167542
  "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.",
@@ -166926,7 +167834,7 @@ __export(server_exports2, {
166926
167834
  createMemorixServer: () => createMemorixServer,
166927
167835
  shouldAwaitProjectRuntime: () => shouldAwaitProjectRuntime
166928
167836
  });
166929
- import { createHash as createHash15 } from "crypto";
167837
+ import { createHash as createHash16 } from "crypto";
166930
167838
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
166931
167839
  import { z as z626 } from "zod";
166932
167840
  function formatFormationStageDurations(stageDurationsMs) {
@@ -167010,7 +167918,7 @@ function coerceObjectArray(val) {
167010
167918
  return [];
167011
167919
  }
167012
167920
  function createDeterministicInstanceId(projectId, agentType, agentName) {
167013
- const digest2 = createHash15("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
167921
+ const digest2 = createHash16("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
167014
167922
  return `auto-${digest2}`;
167015
167923
  }
167016
167924
  function shouldAwaitProjectRuntime(toolName) {
@@ -167302,7 +168210,7 @@ The path should point to a directory containing a .git folder.`
167302
168210
  };
167303
168211
  const server = existingServer ?? new McpServer({
167304
168212
  name: "memorix",
167305
- version: true ? "1.7.2" : "1.0.1"
168213
+ version: true ? "1.8.0" : "1.0.1"
167306
168214
  });
167307
168215
  const originalRegisterTool = server.registerTool.bind(server);
167308
168216
  server.registerTool = ((name, ...args) => {
@@ -170481,7 +171389,7 @@ ${json2}
170481
171389
  dashboardRunning = true;
170482
171390
  const { exec: execCmd } = await import("child_process");
170483
171391
  const openCmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
170484
- execCmd(openCmd, () => {
171392
+ execCmd(openCmd, { windowsHide: true }, () => {
170485
171393
  });
170486
171394
  return {
170487
171395
  content: [{
@@ -170505,6 +171413,113 @@ ${json2}
170505
171413
  }
170506
171414
  }
170507
171415
  );
171416
+ server.registerTool(
171417
+ "memorix_evidence",
171418
+ {
171419
+ title: "Evidence Cards",
171420
+ 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.",
171421
+ inputSchema: {
171422
+ action: z626.enum(["list", "get", "sync", "stale", "events"]).describe("Evidence operation"),
171423
+ candidateKind: z626.enum(["observation", "code", "git", "test", "document", "workflow", "graph"]).optional().default("observation"),
171424
+ candidateId: z626.string().optional().describe("Candidate id, usually an observation id"),
171425
+ query: z626.string().optional().describe("Optional title/source query for list"),
171426
+ paths: z626.array(z626.string()).max(200).optional().describe("Changed source paths for stale"),
171427
+ reason: z626.string().max(500).optional().describe("Why evidence became stale"),
171428
+ limit: z626.number().int().positive().max(100).optional().default(20)
171429
+ }
171430
+ },
171431
+ async ({ action, candidateKind, candidateId, query, paths, reason, limit: limit2 }) => {
171432
+ const unresolved = requireResolvedProject("inspect evidence cards for the current project");
171433
+ if (unresolved) return unresolved;
171434
+ const cards = new EvidenceCardStore();
171435
+ await cards.init(projectDir2);
171436
+ if (action === "sync") {
171437
+ const observations2 = await getObservationStore().loadByProject(project.id);
171438
+ const synced = cards.syncObservations(observations2);
171439
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, synced: synced.length, cards: synced.slice(0, limit2 ?? 20) }, null, 2) }] };
171440
+ }
171441
+ if (action === "stale") {
171442
+ const changed = cards.markStaleForPaths(project.id, paths ?? [], reason ?? "source file changed");
171443
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, staleMarked: changed, paths: paths ?? [] }, null, 2) }] };
171444
+ }
171445
+ if (action === "get") {
171446
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence get." }], isError: true };
171447
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
171448
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
171449
+ return { content: [{ type: "text", text: JSON.stringify({ card, events: cards.listEvents(card.id), projectId: project.id }, null, 2) }] };
171450
+ }
171451
+ if (action === "events") {
171452
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence events." }], isError: true };
171453
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
171454
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
171455
+ return { content: [{ type: "text", text: JSON.stringify(cards.listEvents(card.id, limit2), null, 2) }] };
171456
+ }
171457
+ const needle = query?.trim().toLowerCase();
171458
+ 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);
171459
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, cards: listed }, null, 2) }] };
171460
+ }
171461
+ );
171462
+ server.registerTool(
171463
+ "memorix_feedback",
171464
+ {
171465
+ title: "Memory Feedback",
171466
+ 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.",
171467
+ inputSchema: {
171468
+ action: z626.enum(["record", "show", "audit"]).describe("Feedback operation"),
171469
+ candidateKind: z626.enum(["observation", "claim", "durable-memory", "workflow"]).optional().default("observation"),
171470
+ candidateId: z626.string().describe("Memory or knowledge candidate id"),
171471
+ signal: z626.enum(["user-correction", "verification-success", "verification-failure", "code-change", "used", "not-used", "code-conflict", "strengthen", "weaken", "revoke"]).optional(),
171472
+ sourceRef: z626.string().max(500).optional().describe("Source of this feedback, such as test:run-42 or user:chat"),
171473
+ actor: z626.string().max(200).optional(),
171474
+ note: z626.string().max(2e3).optional(),
171475
+ targetEventId: z626.string().optional().describe("Existing event id when action=record and signal=revoke"),
171476
+ limit: z626.number().int().positive().max(200).optional().default(100)
171477
+ }
171478
+ },
171479
+ async ({ action, candidateKind, candidateId, signal, sourceRef, actor, note, targetEventId, limit: limit2 }) => {
171480
+ const unresolved = requireResolvedProject("record memory feedback for the current project");
171481
+ if (unresolved) return unresolved;
171482
+ const feedback = new MemoryFeedbackStore();
171483
+ await feedback.init(projectDir2);
171484
+ const kind = candidateKind ?? "observation";
171485
+ if (action === "show") {
171486
+ return { content: [{ type: "text", text: JSON.stringify(feedback.getState(project.id, kind, candidateId), null, 2) }] };
171487
+ }
171488
+ if (action === "audit") {
171489
+ return { content: [{ type: "text", text: JSON.stringify(feedback.audit(project.id, kind, candidateId, limit2), null, 2) }] };
171490
+ }
171491
+ if (!signal || !sourceRef?.trim()) {
171492
+ return { content: [{ type: "text", text: "record requires signal and sourceRef." }], isError: true };
171493
+ }
171494
+ try {
171495
+ const result = feedback.record({
171496
+ projectId: project.id,
171497
+ candidateKind: kind,
171498
+ candidateId,
171499
+ signal,
171500
+ sourceRef,
171501
+ ...actor || currentAgentId ? { actor: actor ?? currentAgentId } : {},
171502
+ ...note ? { note } : {},
171503
+ ...targetEventId ? { targetEventId } : {}
171504
+ });
171505
+ if (kind === "observation") {
171506
+ const cards = new EvidenceCardStore();
171507
+ await cards.init(projectDir2);
171508
+ const card = cards.get(project.id, "observation", candidateId);
171509
+ if (card) {
171510
+ const verification = signal === "verification-success" || signal === "strengthen" ? "verified" : signal === "user-correction" || signal === "code-conflict" || signal === "weaken" ? "conflicted" : card.verification;
171511
+ cards.recordEvent(project.id, card.id, `feedback:${signal}`, note);
171512
+ if (verification !== card.verification) {
171513
+ cards.setVerification(project.id, card.id, verification);
171514
+ }
171515
+ }
171516
+ }
171517
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
171518
+ } catch (error) {
171519
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true };
171520
+ }
171521
+ }
171522
+ );
170508
171523
  if (teamFeaturesEnabled) {
170509
171524
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
170510
171525
  initTeamStoreForProject = initTeamStore2;
@@ -171558,6 +172573,8 @@ var init_server2 = __esm({
171558
172573
  init_launch_root();
171559
172574
  init_aliases();
171560
172575
  init_persistence();
172576
+ init_evidence_store();
172577
+ init_feedback();
171561
172578
  init_syncer();
171562
172579
  init_engine4();
171563
172580
  init_tool_profile();
@@ -171742,7 +172759,7 @@ var init_serve_shared = __esm({
171742
172759
 
171743
172760
  // src/cli/version.ts
171744
172761
  function getCliVersion() {
171745
- return true ? "1.7.2" : pkg.version;
172762
+ return true ? "1.8.0" : pkg.version;
171746
172763
  }
171747
172764
  var init_version3 = __esm({
171748
172765
  "src/cli/version.ts"() {