memorix 1.7.3 → 1.8.1

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 (73) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/cli/index.js +1488 -270
  5. package/dist/dashboard/static/app.js +125 -27
  6. package/dist/dashboard/static/index.html +11 -1
  7. package/dist/dashboard/static/style.css +86 -0
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +927 -240
  10. package/dist/maintenance-runner.js +607 -68
  11. package/dist/media-audio-runner.js +602 -63
  12. package/dist/media-video-runner.js +607 -68
  13. package/dist/memcode-runtime/CHANGELOG.md +41 -0
  14. package/dist/sdk.js +926 -239
  15. package/dist/types.d.ts +8 -0
  16. package/dist/vector-backfill-runner.js +607 -68
  17. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  18. package/docs/1.8.0-RELEASE-CANDIDATE-SPEC.md +75 -0
  19. package/docs/API_REFERENCE.md +39 -1
  20. package/docs/ARCHITECTURE.md +3 -1
  21. package/docs/DASHBOARD.md +16 -0
  22. package/docs/DEVELOPMENT.md +3 -2
  23. package/docs/INTEGRATIONS.md +1 -1
  24. package/docs/README.md +15 -2
  25. package/package.json +1 -1
  26. package/plugins/antigravity/memorix/rules/memorix.md +1 -1
  27. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  28. package/plugins/claude/memorix/SKILL.md +1 -1
  29. package/plugins/claude/memorix/skills/memorix-memory/SKILL.md +2 -2
  30. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  31. package/plugins/codebuddy/memorix/skills/memorix-memory/SKILL.md +2 -2
  32. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  33. package/plugins/codex/memorix/skills/memorix-memory/SKILL.md +2 -2
  34. package/plugins/copilot/memorix/plugin.json +1 -1
  35. package/plugins/copilot/memorix/skills/memorix-memory/SKILL.md +2 -2
  36. package/plugins/cursor/memorix/skills/memorix-memory/SKILL.md +2 -2
  37. package/plugins/gemini/memorix/GEMINI.md +1 -1
  38. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  39. package/plugins/omp/memorix/package.json +1 -1
  40. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  41. package/plugins/pi/memorix/package.json +1 -1
  42. package/plugins/pi/memorix/skills/memorix-memory/SKILL.md +2 -2
  43. package/server.json +2 -2
  44. package/src/cli/capability-map.ts +2 -0
  45. package/src/cli/commands/context.ts +11 -0
  46. package/src/cli/commands/dashboard.ts +1 -1
  47. package/src/cli/commands/evidence.ts +56 -0
  48. package/src/cli/commands/feedback.ts +58 -0
  49. package/src/cli/commands/resume.ts +2 -0
  50. package/src/cli/commands/serve-http.ts +229 -13
  51. package/src/cli/fallback-budget.ts +59 -0
  52. package/src/cli/index.ts +5 -1
  53. package/src/compact/engine.ts +26 -1
  54. package/src/config/dotenv-loader.ts +17 -0
  55. package/src/dashboard/config-provenance.ts +17 -0
  56. package/src/dashboard/evidence.ts +63 -0
  57. package/src/dashboard/server.ts +178 -154
  58. package/src/dashboard/static/index.html +11 -1
  59. package/src/dashboard/static/style.css +86 -0
  60. package/src/hooks/installers/index.ts +1 -1
  61. package/src/hooks/official-skills.ts +3 -3
  62. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  63. package/src/memory/feedback-model.ts +4 -0
  64. package/src/memory/feedback.ts +220 -0
  65. package/src/memory/observations.ts +19 -0
  66. package/src/memory/retention.ts +38 -5
  67. package/src/server/mcp-binding-store.ts +79 -0
  68. package/src/server/mcp-diagnostics.ts +40 -0
  69. package/src/server/tool-profile.ts +4 -0
  70. package/src/server.ts +125 -5
  71. package/src/store/evidence-store.ts +261 -0
  72. package/src/store/sqlite-db.ts +104 -0
  73. 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();
@@ -3162,6 +3257,8 @@ var init_toml_loader = __esm({
3162
3257
  var dotenv_loader_exports = {};
3163
3258
  __export(dotenv_loader_exports, {
3164
3259
  getLoadedEnvFiles: () => getLoadedEnvFiles,
3260
+ getLoadedEnvKeys: () => getLoadedEnvKeys,
3261
+ getLoadedEnvValue: () => getLoadedEnvValue,
3165
3262
  loadDotenv: () => loadDotenv,
3166
3263
  resetDotenv: () => resetDotenv
3167
3264
  });
@@ -3173,6 +3270,8 @@ function loadEnvFile(filePath) {
3173
3270
  const parsed = parse(readFileSync6(filePath, "utf-8"));
3174
3271
  for (const [key, value] of Object.entries(parsed)) {
3175
3272
  if (!(key in process.env)) {
3273
+ loadedEnvKeys.add(key);
3274
+ loadedEnvValues.set(key, value);
3176
3275
  process.env[key] = value;
3177
3276
  injectedKeys.add(key);
3178
3277
  }
@@ -3185,6 +3284,8 @@ function loadDotenv(projectRoot, options = {}) {
3185
3284
  resetDotenv();
3186
3285
  }
3187
3286
  loadedEnvFiles.length = 0;
3287
+ loadedEnvKeys.clear();
3288
+ loadedEnvValues.clear();
3188
3289
  if (projectRoot) {
3189
3290
  loadEnvFile(getProjectDotenvPath(projectRoot));
3190
3291
  }
@@ -3200,11 +3301,18 @@ function resetDotenv() {
3200
3301
  dotenvLoaded = false;
3201
3302
  dotenvProjectRoot = null;
3202
3303
  loadedEnvFiles.length = 0;
3304
+ loadedEnvKeys.clear();
3203
3305
  }
3204
3306
  function getLoadedEnvFiles() {
3205
3307
  return loadedEnvFiles;
3206
3308
  }
3207
- 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;
3208
3316
  var init_dotenv_loader = __esm({
3209
3317
  "src/config/dotenv-loader.ts"() {
3210
3318
  "use strict";
@@ -3213,6 +3321,8 @@ var init_dotenv_loader = __esm({
3213
3321
  dotenvLoaded = false;
3214
3322
  dotenvProjectRoot = null;
3215
3323
  loadedEnvFiles = [];
3324
+ loadedEnvKeys = /* @__PURE__ */ new Set();
3325
+ loadedEnvValues = /* @__PURE__ */ new Map();
3216
3326
  injectedKeys = /* @__PURE__ */ new Set();
3217
3327
  }
3218
3328
  });
@@ -9303,6 +9413,226 @@ var init_lifecycle = __esm({
9303
9413
  }
9304
9414
  });
9305
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
+
9306
9636
  // src/runtime/isolated-maintenance.ts
9307
9637
  import { spawn } from "child_process";
9308
9638
  import { existsSync as existsSync8 } from "fs";
@@ -9464,7 +9794,7 @@ var workspace_store_exports = {};
9464
9794
  __export(workspace_store_exports, {
9465
9795
  KnowledgeWorkspaceStore: () => KnowledgeWorkspaceStore
9466
9796
  });
9467
- import { randomUUID as randomUUID3 } from "crypto";
9797
+ import { randomUUID as randomUUID4 } from "crypto";
9468
9798
  function arrayValue(raw) {
9469
9799
  if (typeof raw !== "string" || !raw) return [];
9470
9800
  try {
@@ -9605,7 +9935,7 @@ var init_workspace_store = __esm({
9605
9935
  }
9606
9936
  createProposal(input) {
9607
9937
  const proposal = {
9608
- id: input.id ?? randomUUID3(),
9938
+ id: input.id ?? randomUUID4(),
9609
9939
  ...input,
9610
9940
  status: input.status ?? "pending",
9611
9941
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -9658,13 +9988,13 @@ __export(workspace_exports, {
9658
9988
  loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
9659
9989
  resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
9660
9990
  });
9661
- import { createHash as createHash3 } from "crypto";
9991
+ import { createHash as createHash4 } from "crypto";
9662
9992
  import { execFile } from "child_process";
9663
9993
  import { promises as fs9 } from "fs";
9664
9994
  import path13 from "path";
9665
9995
  import { promisify } from "util";
9666
9996
  function hash(value) {
9667
- return createHash3("sha256").update(value).digest("hex");
9997
+ return createHash4("sha256").update(value).digest("hex");
9668
9998
  }
9669
9999
  function workspaceId(projectId, mode) {
9670
10000
  return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
@@ -10264,7 +10594,7 @@ var media_store_exports = {};
10264
10594
  __export(media_store_exports, {
10265
10595
  MediaStore: () => MediaStore
10266
10596
  });
10267
- import { randomUUID as randomUUID4 } from "crypto";
10597
+ import { randomUUID as randomUUID5 } from "crypto";
10268
10598
  function asOptionalText(value) {
10269
10599
  return typeof value === "string" && value.length > 0 ? value : void 0;
10270
10600
  }
@@ -10434,7 +10764,7 @@ var init_media_store = __esm({
10434
10764
  return { asset: this.getAsset(input.projectId, existing.id), deduplicated: true };
10435
10765
  }
10436
10766
  const asset = {
10437
- id: randomUUID4(),
10767
+ id: randomUUID5(),
10438
10768
  ...input,
10439
10769
  createdAt: now3,
10440
10770
  updatedAt: now3
@@ -10514,7 +10844,7 @@ var init_media_store = __esm({
10514
10844
  `).get(input.assetId, input.observationId ?? null, input.role);
10515
10845
  if (existing) return rowToLink(existing);
10516
10846
  const link = {
10517
- id: randomUUID4(),
10847
+ id: randomUUID5(),
10518
10848
  assetId: input.assetId,
10519
10849
  projectId: input.projectId,
10520
10850
  ...input.observationId !== void 0 ? { observationId: input.observationId } : {},
@@ -10553,7 +10883,7 @@ var init_media_store = __esm({
10553
10883
  return rowToDerivation(this.db.prepare("SELECT * FROM media_derivations WHERE id = ?").get(existing.id));
10554
10884
  }
10555
10885
  const derivation = {
10556
- id: randomUUID4(),
10886
+ id: randomUUID5(),
10557
10887
  ...input,
10558
10888
  content,
10559
10889
  ...error ? { error } : {},
@@ -10640,7 +10970,7 @@ var init_media_store = __esm({
10640
10970
  createJob(input) {
10641
10971
  const now3 = Date.now();
10642
10972
  const job = {
10643
- id: randomUUID4(),
10973
+ id: randomUUID5(),
10644
10974
  projectId: input.projectId,
10645
10975
  kind: input.kind,
10646
10976
  status: "queued",
@@ -10816,14 +11146,14 @@ __export(asset_store_exports, {
10816
11146
  readMediaAsset: () => readMediaAsset,
10817
11147
  removeMediaAsset: () => removeMediaAsset
10818
11148
  });
10819
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
11149
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
10820
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";
10821
11151
  import path14 from "path";
10822
11152
  function mediaRoot(dataDir) {
10823
11153
  return path14.resolve(dataDir, "media");
10824
11154
  }
10825
11155
  function projectStorageKey(projectId) {
10826
- return createHash4("sha256").update(projectId).digest("hex").slice(0, 16);
11156
+ return createHash5("sha256").update(projectId).digest("hex").slice(0, 16);
10827
11157
  }
10828
11158
  function validateUnderRoot(root, candidate) {
10829
11159
  const resolved = path14.resolve(candidate);
@@ -10883,14 +11213,14 @@ async function readHeader(filePath, bytes = 32) {
10883
11213
  }
10884
11214
  async function sha256File(filePath) {
10885
11215
  const bytes = await readFile3(filePath);
10886
- return createHash4("sha256").update(bytes).digest("hex");
11216
+ return createHash5("sha256").update(bytes).digest("hex");
10887
11217
  }
10888
11218
  function destinationFor(root, projectId, sha256, extension3) {
10889
11219
  const relative3 = path14.join("assets", projectStorageKey(projectId), sha256.slice(0, 2), `${sha256}${extension3}`);
10890
11220
  return { relative: relative3, absolute: validateUnderRoot(root, path14.join(root, relative3)) };
10891
11221
  }
10892
11222
  function deletionStagingPath(root, assetId) {
10893
- return validateUnderRoot(root, path14.join(root, ".trash", `${assetId}.${randomUUID5()}.pending-delete`));
11223
+ return validateUnderRoot(root, path14.join(root, ".trash", `${assetId}.${randomUUID6()}.pending-delete`));
10894
11224
  }
10895
11225
  async function destinationExists(destination) {
10896
11226
  try {
@@ -10904,7 +11234,7 @@ async function destinationExists(destination) {
10904
11234
  async function persistFile(source, destination) {
10905
11235
  await mkdir3(path14.dirname(destination), { recursive: true });
10906
11236
  if (await destinationExists(destination)) return false;
10907
- const temporary = `${destination}.${randomUUID5()}.tmp`;
11237
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
10908
11238
  try {
10909
11239
  await copyFile(source, temporary);
10910
11240
  await rename2(temporary, destination);
@@ -10920,7 +11250,7 @@ async function persistFile(source, destination) {
10920
11250
  async function persistBuffer(bytes, destination) {
10921
11251
  await mkdir3(path14.dirname(destination), { recursive: true });
10922
11252
  if (await destinationExists(destination)) return false;
10923
- const temporary = `${destination}.${randomUUID5()}.tmp`;
11253
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
10924
11254
  try {
10925
11255
  await writeFile3(temporary, bytes, { flag: "wx" });
10926
11256
  await rename2(temporary, destination);
@@ -10989,7 +11319,7 @@ async function importMediaBuffer(input) {
10989
11319
  if (input.bytes.length === 0) throw new Error("Media import rejects empty payloads");
10990
11320
  if (input.bytes.length > maxBytes) throw new Error(`Media payload is too large (${input.bytes.length} bytes; limit ${maxBytes})`);
10991
11321
  const detected = detectMedia(input.bytes.subarray(0, 32));
10992
- const sha256 = createHash4("sha256").update(input.bytes).digest("hex");
11322
+ const sha256 = createHash5("sha256").update(input.bytes).digest("hex");
10993
11323
  return importKnownMedia({
10994
11324
  dataDir: input.dataDir,
10995
11325
  projectId: input.projectId,
@@ -11007,7 +11337,7 @@ async function readMediaAsset(dataDir, asset, maxBytes = DEFAULT_MAX_MEDIA_BYTES
11007
11337
  if (asset.byteSize > maxBytes) throw new Error(`Media asset exceeds read limit (${asset.byteSize} bytes; limit ${maxBytes})`);
11008
11338
  const bytes = await readFile3(getMediaAssetPath(dataDir, asset));
11009
11339
  if (bytes.length !== asset.byteSize) throw new Error(`Media asset size no longer matches metadata: ${asset.id}`);
11010
- const sha256 = createHash4("sha256").update(bytes).digest("hex");
11340
+ const sha256 = createHash5("sha256").update(bytes).digest("hex");
11011
11341
  if (sha256 !== asset.sha256) throw new Error(`Media asset hash no longer matches metadata: ${asset.id}`);
11012
11342
  return bytes;
11013
11343
  }
@@ -63877,7 +64207,7 @@ var require_gaxios = __commonJS({
63877
64207
  var retry_js_1 = require_retry3();
63878
64208
  var stream_1 = __require("stream");
63879
64209
  var interceptor_js_1 = require_interceptor();
63880
- var randomUUID17 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
64210
+ var randomUUID19 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
63881
64211
  var HTTP_STATUS_NO_CONTENT = 204;
63882
64212
  var Gaxios = class {
63883
64213
  agentCache = /* @__PURE__ */ new Map();
@@ -64150,7 +64480,7 @@ var require_gaxios = __commonJS({
64150
64480
  */
64151
64481
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
64152
64482
  if (opts.multipart?.length) {
64153
- const boundary = await randomUUID17();
64483
+ const boundary = await randomUUID19();
64154
64484
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
64155
64485
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
64156
64486
  } else if (shouldDirectlyPassData) {
@@ -76377,7 +76707,7 @@ var require_websocket = __commonJS({
76377
76707
  var http3 = __require("http");
76378
76708
  var net = __require("net");
76379
76709
  var tls = __require("tls");
76380
- var { randomBytes: randomBytes2, createHash: createHash16 } = __require("crypto");
76710
+ var { randomBytes: randomBytes2, createHash: createHash17 } = __require("crypto");
76381
76711
  var { Duplex, Readable: Readable2 } = __require("stream");
76382
76712
  var { URL: URL2 } = __require("url");
76383
76713
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -77045,7 +77375,7 @@ var require_websocket = __commonJS({
77045
77375
  abortHandshake(websocket, socket, "Invalid Upgrade header");
77046
77376
  return;
77047
77377
  }
77048
- const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
77378
+ const digest2 = createHash17("sha1").update(key + GUID).digest("base64");
77049
77379
  if (res.headers["sec-websocket-accept"] !== digest2) {
77050
77380
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
77051
77381
  return;
@@ -77417,7 +77747,7 @@ var require_websocket_server = __commonJS({
77417
77747
  var EventEmitter2 = __require("events");
77418
77748
  var http3 = __require("http");
77419
77749
  var { Duplex } = __require("stream");
77420
- var { createHash: createHash16 } = __require("crypto");
77750
+ var { createHash: createHash17 } = __require("crypto");
77421
77751
  var extension3 = require_extension();
77422
77752
  var PerMessageDeflate2 = require_permessage_deflate();
77423
77753
  var subprotocol2 = require_subprotocol();
@@ -77724,7 +78054,7 @@ var require_websocket_server = __commonJS({
77724
78054
  );
77725
78055
  }
77726
78056
  if (this._state > RUNNING) return abortHandshake(socket, 503);
77727
- const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
78057
+ const digest2 = createHash17("sha1").update(key + GUID).digest("base64");
77728
78058
  const headers = [
77729
78059
  "HTTP/1.1 101 Switching Protocols",
77730
78060
  "Upgrade: websocket",
@@ -144978,6 +145308,186 @@ var init_audio_jobs = __esm({
144978
145308
  }
144979
145309
  });
144980
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
+
144981
145491
  // src/memory/retention.ts
144982
145492
  var retention_exports = {};
144983
145493
  __export(retention_exports, {
@@ -145019,6 +145529,7 @@ function getEffectiveRetentionDays(doc) {
145019
145529
  }
145020
145530
  function isImmune(doc) {
145021
145531
  if (doc.type === "probe") return false;
145532
+ if (doc.feedbackStatus === "archived") return false;
145022
145533
  if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
145023
145534
  if (doc.valueCategory === "core") return true;
145024
145535
  const importance = getImportanceLevel(doc);
@@ -145051,7 +145562,8 @@ function calculateRelevance(doc, referenceTime) {
145051
145562
  const decayFactor = Math.exp(-ageDays / retention);
145052
145563
  const accessCount = doc.accessCount ?? 0;
145053
145564
  const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
145054
- let totalScore = base * decayFactor * accessBoost;
145565
+ const feedbackMultiplier = doc.feedbackWeight === void 0 ? 1 : feedbackWeightMultiplier(doc.feedbackWeight);
145566
+ let totalScore = base * decayFactor * accessBoost * feedbackMultiplier;
145055
145567
  const immune = isImmune(doc);
145056
145568
  if (immune) {
145057
145569
  totalScore = Math.max(totalScore, 0.5);
@@ -145063,7 +145575,8 @@ function calculateRelevance(doc, referenceTime) {
145063
145575
  decayFactor,
145064
145576
  accessBoost,
145065
145577
  ageDays,
145066
- isImmune: immune
145578
+ isImmune: immune,
145579
+ feedbackMultiplier
145067
145580
  };
145068
145581
  }
145069
145582
  function rankByRelevance(docs, referenceTime) {
@@ -145071,6 +145584,7 @@ function rankByRelevance(docs, referenceTime) {
145071
145584
  }
145072
145585
  function getRetentionZone(doc, referenceTime) {
145073
145586
  const now3 = referenceTime ?? /* @__PURE__ */ new Date();
145587
+ if (doc.feedbackStatus === "archived") return "archive-candidate";
145074
145588
  const importance = getImportanceLevel(doc);
145075
145589
  const retention = getEffectiveRetentionDays(doc);
145076
145590
  const createdAt = new Date(doc.createdAt);
@@ -145142,7 +145656,7 @@ function explainRetention(doc, referenceTime) {
145142
145656
  summary: parts.join(" ; ")
145143
145657
  };
145144
145658
  }
145145
- function toRetentionDocument(obs, accessMap) {
145659
+ function toRetentionDocument(obs, accessMap, feedback) {
145146
145660
  const access3 = accessMap?.get(obs.id);
145147
145661
  return {
145148
145662
  id: `obs-${obs.id}`,
@@ -145164,11 +145678,12 @@ function toRetentionDocument(obs, accessMap) {
145164
145678
  sourceDetail: obs.sourceDetail ?? "",
145165
145679
  valueCategory: obs.valueCategory ?? "",
145166
145680
  admissionState: obs.admissionState ?? "",
145167
- admissionReason: obs.admissionReason ?? ""
145681
+ admissionReason: obs.admissionReason ?? "",
145682
+ ...feedback ? { feedbackWeight: feedback.weight, feedbackStatus: feedback.status } : {}
145168
145683
  };
145169
145684
  }
145170
145685
  function projectObservationRetention(observation, options = {}) {
145171
- const document2 = toRetentionDocument(observation, options.accessMap);
145686
+ const document2 = toRetentionDocument(observation, options.accessMap, options.feedback);
145172
145687
  const relevance = calculateRelevance(document2, options.referenceTime);
145173
145688
  const explanation = explainRetention(document2, options.referenceTime);
145174
145689
  return {
@@ -145193,7 +145708,17 @@ function summarizeRetentionProjections(projections) {
145193
145708
  }
145194
145709
  return { active, stale, archiveCandidates, immune };
145195
145710
  }
145196
- 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) {
145197
145722
  const store = getObservationStore();
145198
145723
  const limit2 = Math.min(500, Math.max(1, Math.floor(options.limit ?? 100)));
145199
145724
  const afterId = Math.max(0, Math.floor(options.afterId ?? 0));
@@ -145202,11 +145727,13 @@ async function archiveExpiredBatch(_projectDir, options) {
145202
145727
  afterId,
145203
145728
  limit: limit2 + 1
145204
145729
  });
145730
+ const feedback = await loadFeedbackStore(projectDir2);
145205
145731
  const hasMore = page.length > limit2;
145206
145732
  const scanned = hasMore ? page.slice(0, limit2) : page;
145207
145733
  const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => projectObservationRetention(observation, {
145208
145734
  accessMap: options.accessMap,
145209
- referenceTime: options.referenceTime
145735
+ referenceTime: options.referenceTime,
145736
+ ...feedback ? { feedback: feedback.getState(options.projectId, "observation", String(observation.id)) } : {}
145210
145737
  }).zone === "archive-candidate").map((observation) => observation.id);
145211
145738
  const archived = candidateIds.length === 0 ? 0 : await store.atomic(async (tx) => {
145212
145739
  const updates = await Promise.all(
@@ -145237,6 +145764,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
145237
145764
  const remaining = reader2 ? remainingObservations.filter((observation) => canManageObservation(observation, reader2)).length : remainingObservations.length;
145238
145765
  return { archived, remaining };
145239
145766
  }
145767
+ const feedback = await loadFeedbackStore(projectDir2);
145240
145768
  return await store.atomic(async (tx) => {
145241
145769
  const allObs = await tx.loadAll();
145242
145770
  const activeObs = allObs.filter(
@@ -145244,7 +145772,11 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
145244
145772
  );
145245
145773
  const archivedIds = [];
145246
145774
  for (const obs of activeObs) {
145247
- 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
+ });
145248
145780
  if (retention.zone === "archive-candidate") {
145249
145781
  archivedIds.push(obs.id);
145250
145782
  }
@@ -145264,6 +145796,7 @@ var init_retention = __esm({
145264
145796
  init_esm_shims();
145265
145797
  init_obs_store();
145266
145798
  init_visibility();
145799
+ init_feedback_model();
145267
145800
  RETENTION_DAYS = {
145268
145801
  critical: 365,
145269
145802
  high: 180,
@@ -145304,7 +145837,7 @@ var owner_exports = {};
145304
145837
  __export(owner_exports, {
145305
145838
  resolveLocalMemoryOwner: () => resolveLocalMemoryOwner
145306
145839
  });
145307
- import { randomUUID as randomUUID6 } from "crypto";
145840
+ import { randomUUID as randomUUID8 } from "crypto";
145308
145841
  import { promises as fs12 } from "fs";
145309
145842
  import path20 from "path";
145310
145843
  function ownerPath(dataDir) {
@@ -145334,7 +145867,7 @@ async function resolveLocalMemoryOwner(dataDir, options = {}) {
145334
145867
  await fs12.mkdir(path20.dirname(filePath), { recursive: true });
145335
145868
  const created = {
145336
145869
  version: 1,
145337
- id: "local-" + randomUUID6(),
145870
+ id: "local-" + randomUUID8(),
145338
145871
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
145339
145872
  };
145340
145873
  try {
@@ -145357,7 +145890,7 @@ var init_owner = __esm({
145357
145890
  });
145358
145891
 
145359
145892
  // src/memory/long-term-store.ts
145360
- import { randomUUID as randomUUID7 } from "crypto";
145893
+ import { randomUUID as randomUUID9 } from "crypto";
145361
145894
  function optionalText2(value) {
145362
145895
  return typeof value === "string" && value.trim() ? value : void 0;
145363
145896
  }
@@ -145408,7 +145941,7 @@ function rowToEvidence(row) {
145408
145941
  createdAt: row.createdAt
145409
145942
  };
145410
145943
  }
145411
- function rowToEvent(row) {
145944
+ function rowToEvent2(row) {
145412
145945
  return {
145413
145946
  id: row.id,
145414
145947
  memoryId: row.memoryId,
@@ -145517,7 +146050,7 @@ var init_long_term_store = __esm({
145517
146050
  }
145518
146051
  insertEvidence(input) {
145519
146052
  const evidence = {
145520
- id: randomUUID7(),
146053
+ id: randomUUID9(),
145521
146054
  ...input,
145522
146055
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
145523
146056
  };
@@ -145544,7 +146077,7 @@ var init_long_term_store = __esm({
145544
146077
  }
145545
146078
  recordEvent(input) {
145546
146079
  const event = {
145547
- id: randomUUID7(),
146080
+ id: randomUUID9(),
145548
146081
  ...input,
145549
146082
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
145550
146083
  };
@@ -145565,7 +146098,7 @@ var init_long_term_store = __esm({
145565
146098
  listEvents(memoryId) {
145566
146099
  return this.db.prepare(`
145567
146100
  SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
145568
- `).all(memoryId).map(rowToEvent);
146101
+ `).all(memoryId).map(rowToEvent2);
145569
146102
  }
145570
146103
  markAccess(ids, at = (/* @__PURE__ */ new Date()).toISOString()) {
145571
146104
  const unique2 = [...new Set(ids.filter(Boolean))];
@@ -145634,7 +146167,7 @@ __export(long_term_exports, {
145634
146167
  selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
145635
146168
  supersedeLongTermMemory: () => supersedeLongTermMemory
145636
146169
  });
145637
- import { randomUUID as randomUUID8 } from "crypto";
146170
+ import { randomUUID as randomUUID10 } from "crypto";
145638
146171
  function isOneOf(value, values) {
145639
146172
  return typeof value === "string" && values.includes(value);
145640
146173
  }
@@ -145816,7 +146349,7 @@ async function createManualLongTermMemory(input) {
145816
146349
  const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
145817
146350
  if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
145818
146351
  assertScope(input.scope);
145819
- 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" }];
145820
146353
  return createLongTermMemoryWithDataDir({
145821
146354
  dataDir: input.dataDir,
145822
146355
  originProjectId: input.projectId,
@@ -145871,7 +146404,7 @@ async function createLongTermMemoryWithDataDir(input) {
145871
146404
  assertPortableEvidence(input.scope, portability, evidenceInputs);
145872
146405
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
145873
146406
  const memory = {
145874
- id: randomUUID8(),
146407
+ id: randomUUID10(),
145875
146408
  originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
145876
146409
  ownerId: compactText(input.ownerId, "owner id", 160),
145877
146410
  scope: input.scope,
@@ -146189,9 +146722,9 @@ var init_long_term = __esm({
146189
146722
  });
146190
146723
 
146191
146724
  // src/codegraph/ids.ts
146192
- import { createHash as createHash5 } from "crypto";
146725
+ import { createHash as createHash6 } from "crypto";
146193
146726
  function digest(input) {
146194
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
146727
+ return createHash6("sha256").update(input).digest("hex").slice(0, 16);
146195
146728
  }
146196
146729
  function normalizeCodePath(path37) {
146197
146730
  return path37.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
@@ -146226,7 +146759,7 @@ var store_exports = {};
146226
146759
  __export(store_exports, {
146227
146760
  CodeGraphStore: () => CodeGraphStore
146228
146761
  });
146229
- import { randomUUID as randomUUID9 } from "crypto";
146762
+ import { randomUUID as randomUUID11 } from "crypto";
146230
146763
  import { posix as pathPosix } from "path";
146231
146764
  function rowToFile(row) {
146232
146765
  return {
@@ -146943,7 +147476,7 @@ var init_store = __esm({
146943
147476
  * so an interrupted scan cannot be advertised as complete.
146944
147477
  */
146945
147478
  recordCodeStateSnapshot(input) {
146946
- const id = randomUUID9();
147479
+ const id = randomUUID11();
146947
147480
  let snapshot;
146948
147481
  const tx = this.db.transaction(() => {
146949
147482
  const previous = this.db.prepare(
@@ -147021,7 +147554,7 @@ var init_store = __esm({
147021
147554
 
147022
147555
  // src/codegraph/code-state.ts
147023
147556
  import { execFile as execFile2 } from "child_process";
147024
- import { createHash as createHash6 } from "crypto";
147557
+ import { createHash as createHash7 } from "crypto";
147025
147558
  import { promisify as promisify3 } from "util";
147026
147559
  async function runGit(rootPath, args) {
147027
147560
  try {
@@ -147050,7 +147583,7 @@ function changedPathCount(status) {
147050
147583
  return count2;
147051
147584
  }
147052
147585
  function fingerprint(baseRevision, status) {
147053
- return createHash6("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
147586
+ return createHash7("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
147054
147587
  }
147055
147588
  async function collectCodeStateSnapshot(input) {
147056
147589
  const [revision, status] = await Promise.all([
@@ -147150,11 +147683,11 @@ __export(lite_provider_exports, {
147150
147683
  indexProjectLite: () => indexProjectLite,
147151
147684
  refreshProjectLite: () => refreshProjectLite
147152
147685
  });
147153
- import { createHash as createHash7 } from "crypto";
147686
+ import { createHash as createHash8 } from "crypto";
147154
147687
  import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
147155
147688
  import { join as join4, relative } from "path";
147156
147689
  function hashText(text2) {
147157
- return createHash7("sha256").update(text2).digest("hex");
147690
+ return createHash8("sha256").update(text2).digest("hex");
147158
147691
  }
147159
147692
  function extension2(path37) {
147160
147693
  const index = path37.lastIndexOf(".");
@@ -147721,7 +148254,7 @@ var claim_store_exports = {};
147721
148254
  __export(claim_store_exports, {
147722
148255
  ClaimStore: () => ClaimStore
147723
148256
  });
147724
- import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
148257
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
147725
148258
  function optionalText3(value) {
147726
148259
  return typeof value === "string" && value ? value : void 0;
147727
148260
  }
@@ -147760,7 +148293,7 @@ function rowToEvidence2(row) {
147760
148293
  createdAt: row.createdAt
147761
148294
  };
147762
148295
  }
147763
- function rowToEvent2(row) {
148296
+ function rowToEvent3(row) {
147764
148297
  return {
147765
148298
  id: row.id,
147766
148299
  projectId: row.projectId,
@@ -147774,7 +148307,7 @@ function rowToEvent2(row) {
147774
148307
  };
147775
148308
  }
147776
148309
  function evidenceKey(input) {
147777
- return createHash8("sha256").update(JSON.stringify([
148310
+ return createHash9("sha256").update(JSON.stringify([
147778
148311
  input.evidenceKind,
147779
148312
  input.evidenceId,
147780
148313
  input.relation,
@@ -147905,7 +148438,7 @@ var init_claim_store = __esm({
147905
148438
  locator, capturedHash, evidenceKey, createdAt
147906
148439
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
147907
148440
  `).run(
147908
- randomUUID10(),
148441
+ randomUUID12(),
147909
148442
  input.claimId,
147910
148443
  input.evidenceKind,
147911
148444
  input.evidenceId,
@@ -147927,7 +148460,7 @@ var init_claim_store = __esm({
147927
148460
  }
147928
148461
  recordEvent(input) {
147929
148462
  const event = {
147930
- id: randomUUID10(),
148463
+ id: randomUUID12(),
147931
148464
  ...input,
147932
148465
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
147933
148466
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -147955,7 +148488,7 @@ var init_claim_store = __esm({
147955
148488
  SELECT * FROM knowledge_claim_events
147956
148489
  WHERE claimId = ?
147957
148490
  ORDER BY createdAt ASC, id
147958
- `).all(claimId).map(rowToEvent2);
148491
+ `).all(claimId).map(rowToEvent3);
147959
148492
  }
147960
148493
  };
147961
148494
  }
@@ -147972,7 +148505,7 @@ __export(claims_exports, {
147972
148505
  supersedeClaim: () => supersedeClaim,
147973
148506
  writeClaim: () => writeClaim
147974
148507
  });
147975
- import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
148508
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
147976
148509
  function compactText2(value, field) {
147977
148510
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
147978
148511
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
@@ -147984,7 +148517,7 @@ function normalizedTerm(value) {
147984
148517
  return compactText2(value, "identity").toLocaleLowerCase("en-US");
147985
148518
  }
147986
148519
  function hashIdentity(parts) {
147987
- return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
148520
+ return createHash10("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
147988
148521
  }
147989
148522
  function buildClaimIdentity(input) {
147990
148523
  const subject = normalizedTerm(input.subject);
@@ -148058,7 +148591,7 @@ function normalizeClaimInput(input) {
148058
148591
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
148059
148592
  return {
148060
148593
  claim: {
148061
- id: randomUUID11(),
148594
+ id: randomUUID13(),
148062
148595
  projectId,
148063
148596
  subject,
148064
148597
  predicate,
@@ -148225,7 +148758,7 @@ function supersedeClaim(store, input) {
148225
148758
  });
148226
148759
  }
148227
148760
  function hashObservation(observation) {
148228
- return createHash9("sha256").update(JSON.stringify([
148761
+ return createHash10("sha256").update(JSON.stringify([
148229
148762
  observation.title,
148230
148763
  observation.narrative,
148231
148764
  observation.facts,
@@ -148469,11 +149002,11 @@ var init_frontmatter = __esm({
148469
149002
  });
148470
149003
 
148471
149004
  // src/knowledge/markdown.ts
148472
- import { createHash as createHash10 } from "crypto";
149005
+ import { createHash as createHash11 } from "crypto";
148473
149006
  import { promises as fs13 } from "fs";
148474
149007
  import path21 from "path";
148475
149008
  function hashContent(content) {
148476
- return createHash10("sha256").update(content).digest("hex");
149009
+ return createHash11("sha256").update(content).digest("hex");
148477
149010
  }
148478
149011
  function stringField(data, key) {
148479
149012
  const value = data[key];
@@ -148599,11 +149132,11 @@ __export(wiki_exports, {
148599
149132
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
148600
149133
  readKnowledgePage: () => readKnowledgePage
148601
149134
  });
148602
- import { createHash as createHash11 } from "crypto";
149135
+ import { createHash as createHash12 } from "crypto";
148603
149136
  import { promises as fs14 } from "fs";
148604
149137
  import path22 from "path";
148605
149138
  function hash2(value) {
148606
- return createHash11("sha256").update(value).digest("hex");
149139
+ return createHash12("sha256").update(value).digest("hex");
148607
149140
  }
148608
149141
  function now() {
148609
149142
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -149147,7 +149680,7 @@ var workflow_store_exports = {};
149147
149680
  __export(workflow_store_exports, {
149148
149681
  WorkflowStore: () => WorkflowStore
149149
149682
  });
149150
- import { randomUUID as randomUUID12 } from "crypto";
149683
+ import { randomUUID as randomUUID14 } from "crypto";
149151
149684
  function parseJson(value, fallback) {
149152
149685
  if (typeof value !== "string" || !value) return fallback;
149153
149686
  try {
@@ -149247,7 +149780,7 @@ var init_workflow_store = __esm({
149247
149780
  }
149248
149781
  recordRun(input) {
149249
149782
  const run = {
149250
- id: randomUUID12(),
149783
+ id: randomUUID14(),
149251
149784
  workflowId: input.workflowId,
149252
149785
  projectId: input.projectId,
149253
149786
  task: input.task.trim(),
@@ -149325,7 +149858,7 @@ var init_outcome_types = __esm({
149325
149858
  });
149326
149859
 
149327
149860
  // src/knowledge/outcome-store.ts
149328
- import { randomUUID as randomUUID13 } from "crypto";
149861
+ import { randomUUID as randomUUID15 } from "crypto";
149329
149862
  function optionalText5(value) {
149330
149863
  return typeof value === "string" && value.trim() ? value : void 0;
149331
149864
  }
@@ -149369,7 +149902,7 @@ var init_outcome_store = __esm({
149369
149902
  }
149370
149903
  record(input) {
149371
149904
  const signal = {
149372
- id: randomUUID13(),
149905
+ id: randomUUID15(),
149373
149906
  projectId: text(input.projectId, "project id"),
149374
149907
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
149375
149908
  candidateId: text(input.candidateId, "candidate id"),
@@ -149796,11 +150329,11 @@ __export(workflows_exports, {
149796
150329
  syncCanonicalWorkflows: () => syncCanonicalWorkflows,
149797
150330
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
149798
150331
  });
149799
- import { createHash as createHash12 } from "crypto";
150332
+ import { createHash as createHash13 } from "crypto";
149800
150333
  import { promises as fs15 } from "fs";
149801
150334
  import path23 from "path";
149802
150335
  function hash3(value) {
149803
- return createHash12("sha256").update(value).digest("hex");
150336
+ return createHash13("sha256").update(value).digest("hex");
149804
150337
  }
149805
150338
  function now2() {
149806
150339
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -151197,6 +151730,16 @@ async function bindObservationCodeRefsBestEffort(observation) {
151197
151730
  } catch {
151198
151731
  }
151199
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
+ }
151200
151743
  function queueClaimDerivation(observation) {
151201
151744
  const dataDir = projectDir;
151202
151745
  const isExplicit = observation.sourceDetail === "explicit";
@@ -151543,6 +152086,7 @@ async function storeObservation(input) {
151543
152086
  }
151544
152087
  if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
151545
152088
  await bindObservationCodeRefsBestEffort(observation);
152089
+ await persistEvidenceCard(observation);
151546
152090
  queueObservationQualification(observation);
151547
152091
  if (resolveObservationVisibility(observation) === "project") {
151548
152092
  queueClaimDerivation(observation);
@@ -151635,6 +152179,7 @@ async function upsertObservation(existing, input, now3) {
151635
152179
  await store.update(existing);
151636
152180
  }
151637
152181
  await bindObservationCodeRefsBestEffort(existing);
152182
+ await persistEvidenceCard(existing);
151638
152183
  queueObservationQualification(existing);
151639
152184
  if (resolveObservationVisibility(existing) === "project") {
151640
152185
  queueClaimDerivation(existing);
@@ -151768,6 +152313,7 @@ async function resolveObservations(ids, status = "resolved") {
151768
152313
  await Promise.all(changed.map((observation) => tx.update(observation)));
151769
152314
  });
151770
152315
  }
152316
+ for (const observation of changed) await persistEvidenceCard(observation);
151771
152317
  return { resolved, notFound };
151772
152318
  }
151773
152319
  function getProjectObservations(projectId) {
@@ -152105,6 +152651,7 @@ var init_observations = __esm({
152105
152651
  init_secret_filter();
152106
152652
  init_lifecycle();
152107
152653
  init_visibility();
152654
+ init_evidence_store();
152108
152655
  observations = [];
152109
152656
  nextId = 1;
152110
152657
  projectDir = null;
@@ -152936,9 +153483,27 @@ function normalizeMemoryBrowseQuery(query) {
152936
153483
  async function compactSearch(options, surface = "mcp") {
152937
153484
  await ensureFreshIndex();
152938
153485
  const searchOptions = { ...options, query: normalizeMemoryBrowseQuery(options.query) };
152939
- const entries = (await searchObservations(searchOptions)).map(
153486
+ let entries = (await searchObservations(searchOptions)).map(
152940
153487
  (entry) => entry.projectId || !options.projectId ? entry : { ...entry, projectId: options.projectId }
152941
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
+ }
152942
153507
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId, surface);
152943
153508
  if (entries.length === 0 && options.projectId) {
152944
153509
  const allObservations = filterReadableObservations(getAllObservations(), options.reader);
@@ -153257,6 +153822,8 @@ var init_engine3 = __esm({
153257
153822
  init_mini_skills();
153258
153823
  init_secret_filter();
153259
153824
  init_visibility();
153825
+ init_persistence();
153826
+ init_feedback();
153260
153827
  }
153261
153828
  });
153262
153829
 
@@ -153878,9 +154445,9 @@ var init_graph_context = __esm({
153878
154445
  });
153879
154446
 
153880
154447
  // src/rules/utils.ts
153881
- import { createHash as createHash13 } from "crypto";
154448
+ import { createHash as createHash14 } from "crypto";
153882
154449
  function hashContent2(content) {
153883
- return createHash13("sha256").update(content.trim()).digest("hex").substring(0, 16);
154450
+ return createHash14("sha256").update(content.trim()).digest("hex").substring(0, 16);
153884
154451
  }
153885
154452
  function generateRuleId(source, filePath) {
153886
154453
  const sanitized = filePath.replace(/[\/\\]/g, "-").replace(/^\./, "");
@@ -156311,6 +156878,10 @@ var init_tool_profile = __esm({
156311
156878
  // multimodal support to one additional schema rather than enabling lite.
156312
156879
  memorix_session_start: ["micro", "lite", "team", "full"],
156313
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"],
156314
156885
  // ── lite: extended solo memory surface ────────────────────────────
156315
156886
  memorix_graph_context: ["lite", "team", "full"],
156316
156887
  memorix_timeline: ["lite", "team", "full"],
@@ -159769,7 +160340,7 @@ var compaction_checkpoint_store_exports = {};
159769
160340
  __export(compaction_checkpoint_store_exports, {
159770
160341
  CompactionCheckpointStore: () => CompactionCheckpointStore
159771
160342
  });
159772
- import { createHash as createHash14, randomUUID as randomUUID14 } from "crypto";
160343
+ import { createHash as createHash15, randomUUID as randomUUID16 } from "crypto";
159773
160344
  function optionalText7(value) {
159774
160345
  return typeof value === "string" && value ? value : void 0;
159775
160346
  }
@@ -159811,7 +160382,7 @@ function sourceKeyFor(input, summary) {
159811
160382
  input.tokensBefore ?? "",
159812
160383
  input.firstKeptEntryId ?? ""
159813
160384
  ].join("\0");
159814
- return `derived:${createHash14("sha256").update(material).digest("hex").slice(0, 32)}`;
160385
+ return `derived:${createHash15("sha256").update(material).digest("hex").slice(0, 32)}`;
159815
160386
  }
159816
160387
  function rowToCheckpoint(row) {
159817
160388
  return {
@@ -159870,7 +160441,7 @@ var init_compaction_checkpoint_store = __esm({
159870
160441
  if (pending) return rowToCheckpoint(pending);
159871
160442
  const now3 = input.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
159872
160443
  const checkpoint = {
159873
- id: randomUUID14(),
160444
+ id: randomUUID16(),
159874
160445
  projectId: input.projectId,
159875
160446
  sessionId: input.sessionId,
159876
160447
  agent: input.agent,
@@ -159878,7 +160449,7 @@ var init_compaction_checkpoint_store = __esm({
159878
160449
  captureKind: "preflight",
159879
160450
  reason: safeReason(input.reason),
159880
160451
  sourceEvent: input.sourceEvent,
159881
- sourceKey: `pre:${randomUUID14()}`,
160452
+ sourceKey: `pre:${randomUUID16()}`,
159882
160453
  transcriptAvailable: Boolean(input.transcriptAvailable),
159883
160454
  status: "active",
159884
160455
  preCapturedAt: now3,
@@ -159944,7 +160515,7 @@ var init_compaction_checkpoint_store = __esm({
159944
160515
  return this.get(checkpoint2.id);
159945
160516
  }
159946
160517
  const checkpoint = {
159947
- id: randomUUID14(),
160518
+ id: randomUUID16(),
159948
160519
  projectId: input.projectId,
159949
160520
  sessionId: input.sessionId,
159950
160521
  agent: input.agent,
@@ -161308,7 +161879,7 @@ __export(team_store_exports, {
161308
161879
  isTeamStoreInitialized: () => isTeamStoreInitialized,
161309
161880
  resetTeamStore: () => resetTeamStore
161310
161881
  });
161311
- import { randomUUID as randomUUID15 } from "crypto";
161882
+ import { randomUUID as randomUUID17 } from "crypto";
161312
161883
  import path30 from "path";
161313
161884
  import fs18 from "fs";
161314
161885
  function getTeamStore() {
@@ -161597,7 +162168,7 @@ var init_team_store = __esm({
161597
162168
  * reactivate it (preserving agent_id). Otherwise create a new one.
161598
162169
  */
161599
162170
  registerAgent(input) {
161600
- const instanceId = input.instanceId || randomUUID15();
162171
+ const instanceId = input.instanceId || randomUUID17();
161601
162172
  const now3 = Date.now();
161602
162173
  const existing = this.stmtAgentFindByInstance.get(
161603
162174
  input.projectId,
@@ -161623,7 +162194,7 @@ var init_team_store = __esm({
161623
162194
  this.eventBus?.emit("agent:joined", { agentId: row2.agent_id, projectId: input.projectId, agentType: input.agentType });
161624
162195
  return row2;
161625
162196
  }
161626
- const agentId = randomUUID15();
162197
+ const agentId = randomUUID17();
161627
162198
  this.stmtAgentUpsert.run({
161628
162199
  agent_id: agentId,
161629
162200
  project_id: input.projectId,
@@ -161725,7 +162296,7 @@ var init_team_store = __esm({
161725
162296
  return { error: `Recipient agent '${input.recipientAgentId}' not found \u2014 cannot send to unknown agent` };
161726
162297
  }
161727
162298
  }
161728
- const id = randomUUID15();
162299
+ const id = randomUUID17();
161729
162300
  const now3 = Date.now();
161730
162301
  const row = {
161731
162302
  id,
@@ -161775,7 +162346,7 @@ var init_team_store = __esm({
161775
162346
  // Task Operations (with atomic claim semantics)
161776
162347
  // ═══════════════════════════════════════════════════════════════════
161777
162348
  createTask(input) {
161778
- const taskId = randomUUID15();
162349
+ const taskId = randomUUID17();
161779
162350
  const now3 = Date.now();
161780
162351
  const row = {
161781
162352
  task_id: taskId,
@@ -162047,7 +162618,7 @@ var init_team_store = __esm({
162047
162618
  for (const [agentId, msgs] of Object.entries(snap.messages.inboxes)) {
162048
162619
  for (const msg of msgs) {
162049
162620
  this.stmtMsgInsert.run({
162050
- id: msg.id ?? randomUUID15(),
162621
+ id: msg.id ?? randomUUID17(),
162051
162622
  project_id: "migrated",
162052
162623
  sender_agent_id: msg.from ?? "unknown",
162053
162624
  recipient_agent_id: msg.to === "__broadcast__" ? null : msg.to ?? agentId,
@@ -162918,6 +163489,23 @@ var init_maintenance = __esm({
162918
163489
  }
162919
163490
  });
162920
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
+
162921
163509
  // src/dashboard/project-classification.ts
162922
163510
  var project_classification_exports = {};
162923
163511
  __export(project_classification_exports, {
@@ -163697,6 +164285,20 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163697
164285
  sendJson(res, observations2);
163698
164286
  break;
163699
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
+ }
163700
164302
  case "/sessions": {
163701
164303
  const allSessions = await getSessionStore().loadAll();
163702
164304
  const sessions = filterByProject(allSessions, effectiveProjectId);
@@ -163744,8 +164346,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163744
164346
  filesModified: o.filesModified
163745
164347
  }));
163746
164348
  const retentionReferenceTime = new Date(now3);
164349
+ const feedback = new MemoryFeedbackStore();
164350
+ await feedback.init(effectiveDataDir).catch(() => void 0);
163747
164351
  const retention = summarizeRetentionProjections(
163748
- 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
+ }))
163749
164356
  );
163750
164357
  const retentionSummary = {
163751
164358
  active: retention.active,
@@ -163839,9 +164446,14 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163839
164446
  effectiveProjectId
163840
164447
  );
163841
164448
  const referenceTime = /* @__PURE__ */ new Date();
164449
+ const feedback = new MemoryFeedbackStore();
164450
+ await feedback.init(effectiveDataDir).catch(() => void 0);
163842
164451
  const rows = observations2.filter((observation) => observation.type !== "probe").map((observation) => ({
163843
164452
  observation,
163844
- retention: projectObservationRetention(observation, { referenceTime })
164453
+ retention: projectObservationRetention(observation, {
164454
+ referenceTime,
164455
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
164456
+ })
163845
164457
  })).sort((a, b) => b.retention.displayScore - a.retention.displayScore);
163846
164458
  const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
163847
164459
  const scored = rows.map(({ observation, retention }) => ({
@@ -163912,23 +164524,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163912
164524
  const os6 = await import("os");
163913
164525
  const { existsSync: existsSync21 } = await import("fs");
163914
164526
  const { join: join26 } = await import("path");
163915
- let yml = {};
163916
164527
  const configProjectRoot = effectiveProjectRoot;
163917
- try {
163918
- const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
163919
- yml = configProjectRoot ? loadYamlConfig2(configProjectRoot) : loadYamlConfig2(null);
163920
- } catch {
163921
- }
163922
- if (configProjectRoot) {
163923
- try {
163924
- const { loadDotenv: loadDotenv2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
163925
- loadDotenv2(configProjectRoot);
163926
- } catch {
163927
- }
163928
- }
164528
+ const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
163929
164529
  const files = {
163930
164530
  "project memorix.yml": { exists: false, path: "", unavailable: !configProjectRoot },
163931
164531
  "user memorix.yml": { exists: false, path: "" },
164532
+ "project memorix.toml": { exists: false, path: "", unavailable: !configProjectRoot },
164533
+ "user config.toml": { exists: false, path: "" },
163932
164534
  "project .env": { exists: false, path: "", unavailable: !configProjectRoot },
163933
164535
  "user .env": { exists: false, path: "" },
163934
164536
  "legacy config.json": { exists: false, path: "" }
@@ -163938,6 +164540,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163938
164540
  const paths = {
163939
164541
  "project memorix.yml": configProjectRoot ? join26(configProjectRoot, "memorix.yml") : null,
163940
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"),
163941
164545
  "project .env": configProjectRoot ? join26(configProjectRoot, ".env") : null,
163942
164546
  "user .env": join26(home, ".memorix", ".env"),
163943
164547
  "legacy config.json": join26(home, ".memorix", "config.json")
@@ -163952,67 +164556,61 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
163952
164556
  } catch {
163953
164557
  }
163954
164558
  const values = [];
163955
- const getEnvSource = async (envKey, ymlSource) => {
163956
- if (process.env[envKey]) {
163957
- try {
163958
- const { getLoadedEnvFiles: getLoadedEnvFiles2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
163959
- const envFiles = getLoadedEnvFiles2();
163960
- if (envFiles.length > 0) return `.env (${envKey})`;
163961
- } catch {
163962
- }
163963
- return `env:${envKey}`;
163964
- }
163965
- return ymlSource ?? "default";
163966
- };
163967
- const llmProvider = process.env.MEMORIX_LLM_PROVIDER || yml.llm?.provider;
163968
- if (llmProvider) values.push({ key: "llm.provider", value: llmProvider, source: await getEnvSource("MEMORIX_LLM_PROVIDER", yml.llm?.provider ? "memorix.yml" : void 0) });
163969
- const llmModel = process.env.MEMORIX_LLM_MODEL || yml.llm?.model;
163970
- if (llmModel) values.push({ key: "llm.model", value: llmModel, source: await getEnvSource("MEMORIX_LLM_MODEL", yml.llm?.model ? "memorix.yml" : void 0) });
163971
- 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
+ ];
163972
164577
  if (llmKey) {
163973
- let src = "unknown";
163974
- if (process.env.MEMORIX_LLM_API_KEY) src = await getEnvSource("MEMORIX_LLM_API_KEY");
163975
- else if (process.env.MEMORIX_API_KEY) src = await getEnvSource("MEMORIX_API_KEY");
163976
- else if (yml.llm?.apiKey) src = "memorix.yml (move to .env!)";
163977
- else if (process.env.OPENAI_API_KEY) src = await getEnvSource("OPENAI_API_KEY");
164578
+ const src = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
163978
164579
  values.push({ key: "llm.apiKey", value: "****" + llmKey.slice(-4), source: src, sensitive: true });
163979
164580
  } else {
163980
164581
  values.push({ key: "llm.apiKey", value: "not set", source: "none" });
163981
164582
  }
163982
- const agentProvider = process.env.MEMORIX_AGENT_PROVIDER || process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
163983
- if (agentProvider) {
163984
- let source;
163985
- if (process.env.MEMORIX_AGENT_PROVIDER) source = await getEnvSource("MEMORIX_AGENT_PROVIDER");
163986
- else if (process.env.MEMORIX_AGENT_LLM_PROVIDER) source = `${await getEnvSource("MEMORIX_AGENT_LLM_PROVIDER")} (legacy)`;
163987
- else source = yml.agent?.provider ? "memorix.yml" : "default";
163988
- values.push({ key: "agent.provider", value: agentProvider, source });
163989
- }
163990
- const agentModel = process.env.MEMORIX_AGENT_MODEL || process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
163991
- if (agentModel) {
163992
- let source;
163993
- if (process.env.MEMORIX_AGENT_MODEL) source = await getEnvSource("MEMORIX_AGENT_MODEL");
163994
- else if (process.env.MEMORIX_AGENT_LLM_MODEL) source = `${await getEnvSource("MEMORIX_AGENT_LLM_MODEL")} (legacy)`;
163995
- else source = yml.agent?.model ? "memorix.yml" : "default";
163996
- values.push({ key: "agent.model", value: agentModel, source });
163997
- }
163998
- 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;
163999
164590
  if (agentKey) {
164000
- let src = "unknown";
164001
- if (process.env.MEMORIX_AGENT_API_KEY) src = await getEnvSource("MEMORIX_AGENT_API_KEY");
164002
- else if (process.env.MEMORIX_AGENT_LLM_API_KEY) src = `${await getEnvSource("MEMORIX_AGENT_LLM_API_KEY")} (legacy)`;
164003
- 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
+ });
164004
164596
  values.push({ key: "agent.apiKey", value: "****" + agentKey.slice(-4), source: src, sensitive: true });
164005
164597
  } else {
164006
- values.push({ key: "agent.apiKey", value: "fallback to llm.apiKey", source: "default" });
164007
- }
164008
- const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
164009
- values.push({ key: "embedding.provider", value: embProvider, source: await getEnvSource("MEMORIX_EMBEDDING", yml.embedding?.provider ? "memorix.yml" : void 0) });
164010
- values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
164011
- values.push({ key: "git.skipMergeCommits", value: String(yml.git?.skipMergeCommits ?? true), source: yml.git?.skipMergeCommits !== void 0 ? "memorix.yml" : "default" });
164012
- if (yml.behavior?.formationMode) values.push({ key: "behavior.formationMode", value: yml.behavior.formationMode, source: "memorix.yml" });
164013
- if (yml.behavior?.sessionInject) values.push({ key: "behavior.sessionInject", value: yml.behavior.sessionInject, source: "memorix.yml" });
164014
- values.push({ key: "server.transport", value: yml.server?.transport || "stdio", source: yml.server?.transport ? "memorix.yml" : "default" });
164015
- 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 });
164016
164614
  sendJson(res, { files, values });
164017
164615
  break;
164018
164616
  }
@@ -164247,6 +164845,37 @@ function normalizeDashboardTask(task) {
164247
164845
  preferredRole: task.preferred_role ?? task.preferredRole ?? null
164248
164846
  };
164249
164847
  }
164848
+ function emptyTeamSnapshot(mode, scope, status) {
164849
+ return {
164850
+ status,
164851
+ mode,
164852
+ readOnly: true,
164853
+ scope,
164854
+ agents: [],
164855
+ activeCount: 0,
164856
+ recentCount: 0,
164857
+ historicalCount: 0,
164858
+ totalAgents: 0,
164859
+ recentWindowDays: 7,
164860
+ locks: [],
164861
+ tasks: [],
164862
+ availableTasks: 0,
164863
+ sessions: 0,
164864
+ roles: [],
164865
+ roleOccupancy: [],
164866
+ handoffs: [],
164867
+ openTasks: 0,
164868
+ openHandoffs: 0,
164869
+ totalUnread: 0,
164870
+ activeSessions: 0,
164871
+ error: status === "error" ? "Coordination state unavailable" : "Coordination state is incomplete",
164872
+ errorCode: status === "error" ? "COORDINATION_STATE_UNAVAILABLE" : "COORDINATION_STATE_DEGRADED"
164873
+ };
164874
+ }
164875
+ function logTeamSnapshotFailure(scope, status) {
164876
+ const safeScope = scope === "global" ? "global" : "project";
164877
+ console.error("[dashboard] coordination snapshot unavailable", { status, scope: safeScope });
164878
+ }
164250
164879
  async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
164251
164880
  try {
164252
164881
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
@@ -164273,8 +164902,9 @@ async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
164273
164902
  const roleOccupancy = effectiveProjectId ? teamStore.getRoleOccupancy(effectiveProjectId) : [];
164274
164903
  const handoffs = effectiveProjectId ? teamStore.listHandoffs(effectiveProjectId) : [];
164275
164904
  return {
164905
+ status: "ok",
164276
164906
  mode,
164277
- readOnly: mode === "standalone",
164907
+ readOnly: true,
164278
164908
  scope,
164279
164909
  agents: withTier,
164280
164910
  activeCount,
@@ -164295,28 +164925,8 @@ async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
164295
164925
  activeSessions: activeCount
164296
164926
  };
164297
164927
  } catch {
164298
- return {
164299
- mode,
164300
- readOnly: mode === "standalone",
164301
- scope,
164302
- agents: [],
164303
- activeCount: 0,
164304
- recentCount: 0,
164305
- historicalCount: 0,
164306
- totalAgents: 0,
164307
- recentWindowDays: 7,
164308
- locks: [],
164309
- tasks: [],
164310
- availableTasks: 0,
164311
- sessions: 0,
164312
- roles: [],
164313
- roleOccupancy: [],
164314
- handoffs: [],
164315
- openTasks: 0,
164316
- openHandoffs: 0,
164317
- totalUnread: 0,
164318
- activeSessions: 0
164319
- };
164928
+ logTeamSnapshotFailure(scope, "error");
164929
+ return emptyTeamSnapshot(mode, scope, "error");
164320
164930
  }
164321
164931
  }
164322
164932
  function readBody(req) {
@@ -164335,13 +164945,12 @@ function readBody(req) {
164335
164945
  req.on("error", reject);
164336
164946
  });
164337
164947
  }
164338
- async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true, teamInstances, projectRoot = null, projectResolved = true) {
164948
+ async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true, projectRoot = null, projectResolved = true) {
164339
164949
  await initObservationStore(dataDir);
164340
164950
  await initSessionStore(dataDir);
164341
164951
  const resolvedStaticDir = staticDir;
164342
164952
  const baseDir = getBaseDataDir();
164343
- const isControlPlane = !!teamInstances;
164344
- const state = { projectId, projectName, dataDir, projectRoot, projectResolved, mode: isControlPlane ? "control-plane" : "standalone", port };
164953
+ const state = { projectId, projectName, dataDir, projectRoot, projectResolved, mode: "standalone", port };
164345
164954
  const server = createServer(async (req, res) => {
164346
164955
  const url = req.url || "/";
164347
164956
  if (url.startsWith("/api/set-current-project") && req.method === "POST") {
@@ -164363,54 +164972,20 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
164363
164972
  }
164364
164973
  return;
164365
164974
  }
164366
- if (url.startsWith("/api/team")) {
164367
- if (!teamInstances) {
164368
- const parsedUrl = new URL(url, `http://127.0.0.1:${port}`);
164369
- const scope = parsedUrl.searchParams.get("scope") || "project";
164370
- sendJson(res, await buildTeamSnapshot(state.dataDir, state.projectId, scope, state.mode));
164371
- return;
164372
- }
164373
- try {
164374
- teamInstances.fileLocks.cleanExpired();
164375
- const agents = teamInstances.registry.listAgents();
164376
- const locks = teamInstances.fileLocks.listLocks();
164377
- const tasks = teamInstances.taskManager.list();
164378
- const available = teamInstances.taskManager.getAvailable();
164379
- let roles = [];
164380
- let roleOccupancy = [];
164381
- let handoffs = [];
164382
- try {
164383
- const { getTeamStore: getTeamStore2, isTeamStoreInitialized: isTeamStoreInitialized2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
164384
- if (isTeamStoreInitialized2()) {
164385
- const teamStore = getTeamStore2();
164386
- const projectId2 = state.projectId;
164387
- roles = teamStore.listRoles(projectId2);
164388
- roleOccupancy = teamStore.getRoleOccupancy(projectId2);
164389
- handoffs = teamStore.listHandoffs(projectId2);
164390
- }
164391
- } catch {
164392
- }
164975
+ const parsedUrl = new URL(url, `http://127.0.0.1:${port}`);
164976
+ if (parsedUrl.pathname === "/api/team") {
164977
+ if (req.method !== "GET") {
164393
164978
  sendJson(res, {
164394
- agents: agents.map((a) => ({
164395
- ...a,
164396
- unread: teamInstances.messageBus.getUnreadCount(a.id)
164397
- })),
164398
- activeCount: teamInstances.registry.getActiveCount(),
164399
- locks,
164400
- tasks,
164401
- availableTasks: available.length,
164402
- roles,
164403
- roleOccupancy,
164404
- handoffs,
164405
- // Resume data for "Continue this project" area
164406
- openTasks: tasks.filter((t2) => t2.status === "pending" || t2.status === "in_progress").length,
164407
- openHandoffs: handoffs.filter((h2) => h2.handoff_status === "open" || h2.handoffStatus === "open").length,
164408
- totalUnread: agents.reduce((sum, a) => sum + teamInstances.messageBus.getUnreadCount(a.id), 0),
164409
- activeSessions: agents.filter((a) => a.status === "active").length
164410
- });
164411
- } catch {
164412
- sendJson(res, { agents: [], activeCount: 0, locks: [], tasks: [], availableTasks: 0, roles: [], roleOccupancy: [], handoffs: [] });
164979
+ status: "error",
164980
+ readOnly: true,
164981
+ error: "Coordination status is read-only",
164982
+ errorCode: "COORDINATION_STATUS_READ_ONLY"
164983
+ }, 405);
164984
+ return;
164413
164985
  }
164986
+ const scope = parsedUrl.searchParams.get("scope") || "project";
164987
+ const snapshot = await buildTeamSnapshot(state.dataDir, state.projectId, scope, state.mode);
164988
+ sendJson(res, snapshot, snapshot.status === "error" ? 503 : 200);
164414
164989
  return;
164415
164990
  }
164416
164991
  if (url.startsWith("/api/")) {
@@ -164431,13 +165006,12 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
164431
165006
  server.listen(port, "127.0.0.1", () => {
164432
165007
  const url = `http://127.0.0.1:${port}`;
164433
165008
  const resolvedLabel = projectResolved ? "resolved" : "unresolved";
164434
- const modeLabel = isControlPlane ? "Control Plane" : "Standalone";
165009
+ const modeLabel = "Standalone";
164435
165010
  console.error(` Memorix Dashboard [${modeLabel}]`);
164436
165011
  console.error(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
164437
165012
  console.error(` Mode: ${modeLabel}`);
164438
165013
  console.error(` Project: ${projectName} (${projectId}) [${resolvedLabel}]`);
164439
165014
  console.error(` Local: ${url}`);
164440
- if (isControlPlane) console.error(` MCP: ${url}/mcp`);
164441
165015
  console.error(` Data dir: ${dataDir}`);
164442
165016
  console.error(`
164443
165017
  Press Ctrl+C to stop
@@ -164460,10 +165034,14 @@ var init_server = __esm({
164460
165034
  init_dotenv_loader();
164461
165035
  init_yaml_loader();
164462
165036
  init_yaml_loader();
165037
+ init_resolved_config();
164463
165038
  init_graph_scope();
164464
165039
  init_retention();
164465
165040
  init_visibility();
165041
+ init_evidence_store();
165042
+ init_feedback();
164466
165043
  init_maintenance();
165044
+ init_config_provenance();
164467
165045
  MIME_TYPES = {
164468
165046
  ".html": "text/html; charset=utf-8",
164469
165047
  ".css": "text/css; charset=utf-8",
@@ -164931,7 +165509,7 @@ __export(planner_exports, {
164931
165509
  materializeTaskGraph: () => materializeTaskGraph,
164932
165510
  seedAutonomousPipeline: () => seedAutonomousPipeline
164933
165511
  });
164934
- import { randomUUID as randomUUID16 } from "crypto";
165512
+ import { randomUUID as randomUUID18 } from "crypto";
164935
165513
  function isPlannerTask(metadata) {
164936
165514
  if (!metadata) return null;
164937
165515
  try {
@@ -164989,7 +165567,7 @@ function seedAutonomousPipeline(teamStore, projectId, config, opts) {
164989
165567
  const maxIterations = config.maxIterations ?? 3;
164990
165568
  const taskBudget = config.taskBudget ?? 15;
164991
165569
  const structuredPlan = opts?.structuredPlan ?? true;
164992
- const pipelineId = randomUUID16();
165570
+ const pipelineId = randomUUID18();
164993
165571
  const meta = {
164994
165572
  plannerType: "plan",
164995
165573
  pipelineId,
@@ -165661,8 +166239,8 @@ var init_official_skills = __esm({
165661
166239
  "",
165662
166240
  "| Situation | Prefer | CLI fallback |",
165663
166241
  "|---|---|---|",
165664
- '| 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` |',
165665
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
166242
+ '| 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` |',
166243
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --fallback --brief-json` |',
165666
166244
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
165667
166245
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
165668
166246
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -165675,7 +166253,7 @@ var init_official_skills = __esm({
165675
166253
  "",
165676
166254
  "- 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.",
165677
166255
  "- 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.",
165678
- '- 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.',
166256
+ '- 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.',
165679
166257
  "- 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.",
165680
166258
  "- 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.",
165681
166259
  "- 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.",
@@ -166934,7 +167512,7 @@ alwaysApply: true
166934
167512
  "",
166935
167513
  `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.`,
166936
167514
  "",
166937
- '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.',
167515
+ '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.',
166938
167516
  "",
166939
167517
  "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.",
166940
167518
  "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.",
@@ -167232,7 +167810,7 @@ __export(server_exports2, {
167232
167810
  createMemorixServer: () => createMemorixServer,
167233
167811
  shouldAwaitProjectRuntime: () => shouldAwaitProjectRuntime
167234
167812
  });
167235
- import { createHash as createHash15 } from "crypto";
167813
+ import { createHash as createHash16 } from "crypto";
167236
167814
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
167237
167815
  import { z as z626 } from "zod";
167238
167816
  function formatFormationStageDurations(stageDurationsMs) {
@@ -167316,7 +167894,7 @@ function coerceObjectArray(val) {
167316
167894
  return [];
167317
167895
  }
167318
167896
  function createDeterministicInstanceId(projectId, agentType, agentName) {
167319
- const digest2 = createHash15("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
167897
+ const digest2 = createHash16("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
167320
167898
  return `auto-${digest2}`;
167321
167899
  }
167322
167900
  function shouldAwaitProjectRuntime(toolName) {
@@ -167608,7 +168186,7 @@ The path should point to a directory containing a .git folder.`
167608
168186
  };
167609
168187
  const server = existingServer ?? new McpServer({
167610
168188
  name: "memorix",
167611
- version: true ? "1.7.3" : "1.0.1"
168189
+ version: true ? "1.8.1" : "1.0.1"
167612
168190
  });
167613
168191
  const originalRegisterTool = server.registerTool.bind(server);
167614
168192
  server.registerTool = ((name, ...args) => {
@@ -170761,7 +171339,7 @@ ${json2}
170761
171339
  }
170762
171340
  }
170763
171341
  console.error(`[memorix] Dashboard staticDir: ${staticDir}`);
170764
- startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, void 0, project.rootPath, projectResolved).then(() => {
171342
+ startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, project.rootPath, projectResolved).then(() => {
170765
171343
  dashboardRunning = true;
170766
171344
  }).catch((err) => {
170767
171345
  console.error("[memorix] Dashboard error:", err);
@@ -170787,7 +171365,7 @@ ${json2}
170787
171365
  dashboardRunning = true;
170788
171366
  const { exec: execCmd } = await import("child_process");
170789
171367
  const openCmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
170790
- execCmd(openCmd, () => {
171368
+ execCmd(openCmd, { windowsHide: true }, () => {
170791
171369
  });
170792
171370
  return {
170793
171371
  content: [{
@@ -170811,6 +171389,113 @@ ${json2}
170811
171389
  }
170812
171390
  }
170813
171391
  );
171392
+ server.registerTool(
171393
+ "memorix_evidence",
171394
+ {
171395
+ title: "Evidence Cards",
171396
+ 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.",
171397
+ inputSchema: {
171398
+ action: z626.enum(["list", "get", "sync", "stale", "events"]).describe("Evidence operation"),
171399
+ candidateKind: z626.enum(["observation", "code", "git", "test", "document", "workflow", "graph"]).optional().default("observation"),
171400
+ candidateId: z626.string().optional().describe("Candidate id, usually an observation id"),
171401
+ query: z626.string().optional().describe("Optional title/source query for list"),
171402
+ paths: z626.array(z626.string()).max(200).optional().describe("Changed source paths for stale"),
171403
+ reason: z626.string().max(500).optional().describe("Why evidence became stale"),
171404
+ limit: z626.number().int().positive().max(100).optional().default(20)
171405
+ }
171406
+ },
171407
+ async ({ action, candidateKind, candidateId, query, paths, reason, limit: limit2 }) => {
171408
+ const unresolved = requireResolvedProject("inspect evidence cards for the current project");
171409
+ if (unresolved) return unresolved;
171410
+ const cards = new EvidenceCardStore();
171411
+ await cards.init(projectDir2);
171412
+ if (action === "sync") {
171413
+ const observations2 = await getObservationStore().loadByProject(project.id);
171414
+ const synced = cards.syncObservations(observations2);
171415
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, synced: synced.length, cards: synced.slice(0, limit2 ?? 20) }, null, 2) }] };
171416
+ }
171417
+ if (action === "stale") {
171418
+ const changed = cards.markStaleForPaths(project.id, paths ?? [], reason ?? "source file changed");
171419
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, staleMarked: changed, paths: paths ?? [] }, null, 2) }] };
171420
+ }
171421
+ if (action === "get") {
171422
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence get." }], isError: true };
171423
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
171424
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
171425
+ return { content: [{ type: "text", text: JSON.stringify({ card, events: cards.listEvents(card.id), projectId: project.id }, null, 2) }] };
171426
+ }
171427
+ if (action === "events") {
171428
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence events." }], isError: true };
171429
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
171430
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
171431
+ return { content: [{ type: "text", text: JSON.stringify(cards.listEvents(card.id, limit2), null, 2) }] };
171432
+ }
171433
+ const needle = query?.trim().toLowerCase();
171434
+ 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);
171435
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, cards: listed }, null, 2) }] };
171436
+ }
171437
+ );
171438
+ server.registerTool(
171439
+ "memorix_feedback",
171440
+ {
171441
+ title: "Memory Feedback",
171442
+ 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.",
171443
+ inputSchema: {
171444
+ action: z626.enum(["record", "show", "audit"]).describe("Feedback operation"),
171445
+ candidateKind: z626.enum(["observation", "claim", "durable-memory", "workflow"]).optional().default("observation"),
171446
+ candidateId: z626.string().describe("Memory or knowledge candidate id"),
171447
+ signal: z626.enum(["user-correction", "verification-success", "verification-failure", "code-change", "used", "not-used", "code-conflict", "strengthen", "weaken", "revoke"]).optional(),
171448
+ sourceRef: z626.string().max(500).optional().describe("Source of this feedback, such as test:run-42 or user:chat"),
171449
+ actor: z626.string().max(200).optional(),
171450
+ note: z626.string().max(2e3).optional(),
171451
+ targetEventId: z626.string().optional().describe("Existing event id when action=record and signal=revoke"),
171452
+ limit: z626.number().int().positive().max(200).optional().default(100)
171453
+ }
171454
+ },
171455
+ async ({ action, candidateKind, candidateId, signal, sourceRef, actor, note, targetEventId, limit: limit2 }) => {
171456
+ const unresolved = requireResolvedProject("record memory feedback for the current project");
171457
+ if (unresolved) return unresolved;
171458
+ const feedback = new MemoryFeedbackStore();
171459
+ await feedback.init(projectDir2);
171460
+ const kind = candidateKind ?? "observation";
171461
+ if (action === "show") {
171462
+ return { content: [{ type: "text", text: JSON.stringify(feedback.getState(project.id, kind, candidateId), null, 2) }] };
171463
+ }
171464
+ if (action === "audit") {
171465
+ return { content: [{ type: "text", text: JSON.stringify(feedback.audit(project.id, kind, candidateId, limit2), null, 2) }] };
171466
+ }
171467
+ if (!signal || !sourceRef?.trim()) {
171468
+ return { content: [{ type: "text", text: "record requires signal and sourceRef." }], isError: true };
171469
+ }
171470
+ try {
171471
+ const result = feedback.record({
171472
+ projectId: project.id,
171473
+ candidateKind: kind,
171474
+ candidateId,
171475
+ signal,
171476
+ sourceRef,
171477
+ ...actor || currentAgentId ? { actor: actor ?? currentAgentId } : {},
171478
+ ...note ? { note } : {},
171479
+ ...targetEventId ? { targetEventId } : {}
171480
+ });
171481
+ if (kind === "observation") {
171482
+ const cards = new EvidenceCardStore();
171483
+ await cards.init(projectDir2);
171484
+ const card = cards.get(project.id, "observation", candidateId);
171485
+ if (card) {
171486
+ const verification = signal === "verification-success" || signal === "strengthen" ? "verified" : signal === "user-correction" || signal === "code-conflict" || signal === "weaken" ? "conflicted" : card.verification;
171487
+ cards.recordEvent(project.id, card.id, `feedback:${signal}`, note);
171488
+ if (verification !== card.verification) {
171489
+ cards.setVerification(project.id, card.id, verification);
171490
+ }
171491
+ }
171492
+ }
171493
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
171494
+ } catch (error) {
171495
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true };
171496
+ }
171497
+ }
171498
+ );
170814
171499
  if (teamFeaturesEnabled) {
170815
171500
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
170816
171501
  initTeamStoreForProject = initTeamStore2;
@@ -170996,7 +171681,7 @@ ${lines.join("\n")}` }] };
170996
171681
  server.registerTool(
170997
171682
  "team_task",
170998
171683
  {
170999
- title: "Task Board",
171684
+ title: "Task Status",
171000
171685
  description: 'Create, claim, complete, or list tasks in the team task board. Supports dependencies. Action "create": create a task. Action "claim": assign to yourself (atomic, race-safe). Action "complete": mark done with result. Action "list": show tasks.',
171001
171686
  inputSchema: {
171002
171687
  action: z626.enum(["create", "claim", "complete", "list"]).describe("Operation to perform"),
@@ -171864,6 +172549,8 @@ var init_server2 = __esm({
171864
172549
  init_launch_root();
171865
172550
  init_aliases();
171866
172551
  init_persistence();
172552
+ init_evidence_store();
172553
+ init_feedback();
171867
172554
  init_syncer();
171868
172555
  init_engine4();
171869
172556
  init_tool_profile();
@@ -172048,7 +172735,7 @@ var init_serve_shared = __esm({
172048
172735
 
172049
172736
  // src/cli/version.ts
172050
172737
  function getCliVersion() {
172051
- return true ? "1.7.3" : pkg.version;
172738
+ return true ? "1.8.1" : pkg.version;
172052
172739
  }
172053
172740
  var init_version3 = __esm({
172054
172741
  "src/cli/version.ts"() {