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/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.7.3" : pkg.version;
3235
+ return true ? "1.8.1" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -9465,7 +9465,7 @@ function closeAllDatabases() {
9465
9465
  _dbCache.delete(key);
9466
9466
  }
9467
9467
  }
9468
- 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;
9468
+ 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;
9469
9469
  var init_sqlite_db = __esm({
9470
9470
  "src/store/sqlite-db.ts"() {
9471
9471
  "use strict";
@@ -10111,6 +10111,79 @@ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
10111
10111
  created_at TEXT NOT NULL,
10112
10112
  updated_at TEXT NOT NULL
10113
10113
  );
10114
+ `;
10115
+ CREATE_EVIDENCE_CARDS_TABLE = `
10116
+ CREATE TABLE IF NOT EXISTS evidence_cards (
10117
+ id TEXT PRIMARY KEY,
10118
+ project_id TEXT NOT NULL,
10119
+ candidate_kind TEXT NOT NULL,
10120
+ candidate_id TEXT NOT NULL,
10121
+ title TEXT NOT NULL,
10122
+ summary TEXT NOT NULL DEFAULT '',
10123
+ entity_name TEXT,
10124
+ source_kind TEXT NOT NULL,
10125
+ source_ref TEXT NOT NULL,
10126
+ locator TEXT,
10127
+ captured_hash TEXT,
10128
+ session_id TEXT,
10129
+ status TEXT NOT NULL DEFAULT 'active',
10130
+ verification TEXT NOT NULL DEFAULT 'unverified',
10131
+ freshness TEXT NOT NULL DEFAULT 'current',
10132
+ stale_reason TEXT,
10133
+ files_json TEXT NOT NULL DEFAULT '[]',
10134
+ related_entities_json TEXT NOT NULL DEFAULT '[]',
10135
+ created_at TEXT NOT NULL,
10136
+ updated_at TEXT NOT NULL,
10137
+ UNIQUE(project_id, candidate_kind, candidate_id)
10138
+ );
10139
+ `;
10140
+ CREATE_EVIDENCE_CARD_EVENTS_TABLE = `
10141
+ CREATE TABLE IF NOT EXISTS evidence_card_events (
10142
+ id TEXT PRIMARY KEY,
10143
+ project_id TEXT NOT NULL,
10144
+ card_id TEXT NOT NULL,
10145
+ kind TEXT NOT NULL,
10146
+ detail TEXT,
10147
+ created_at TEXT NOT NULL,
10148
+ FOREIGN KEY (card_id) REFERENCES evidence_cards(id) ON DELETE CASCADE
10149
+ );
10150
+ `;
10151
+ CREATE_MEMORY_FEEDBACK_STATES_TABLE = `
10152
+ CREATE TABLE IF NOT EXISTS memory_feedback_states (
10153
+ project_id TEXT NOT NULL,
10154
+ candidate_kind TEXT NOT NULL,
10155
+ candidate_id TEXT NOT NULL,
10156
+ weight REAL NOT NULL DEFAULT 1.0,
10157
+ lifecycle_status TEXT NOT NULL DEFAULT 'active',
10158
+ updated_at TEXT NOT NULL,
10159
+ PRIMARY KEY (project_id, candidate_kind, candidate_id)
10160
+ );
10161
+ `;
10162
+ CREATE_MEMORY_FEEDBACK_EVENTS_TABLE = `
10163
+ CREATE TABLE IF NOT EXISTS memory_feedback_events (
10164
+ id TEXT PRIMARY KEY,
10165
+ project_id TEXT NOT NULL,
10166
+ candidate_kind TEXT NOT NULL,
10167
+ candidate_id TEXT NOT NULL,
10168
+ signal TEXT NOT NULL,
10169
+ actor TEXT,
10170
+ note TEXT,
10171
+ source_ref TEXT NOT NULL,
10172
+ target_event_id TEXT,
10173
+ delta REAL NOT NULL DEFAULT 0,
10174
+ observed_at TEXT NOT NULL
10175
+ );
10176
+ `;
10177
+ CREATE_MCP_BINDINGS_TABLE = `
10178
+ CREATE TABLE IF NOT EXISTS mcp_bindings (
10179
+ handle_id TEXT PRIMARY KEY,
10180
+ project_id TEXT NOT NULL,
10181
+ project_root TEXT NOT NULL,
10182
+ data_dir TEXT NOT NULL,
10183
+ created_at TEXT NOT NULL,
10184
+ last_used_at TEXT NOT NULL,
10185
+ expires_at TEXT
10186
+ );
10114
10187
  `;
10115
10188
  CREATE_INDEXES = `
10116
10189
  CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId);
@@ -10173,6 +10246,12 @@ CREATE INDEX IF NOT EXISTS idx_maintenance_targets_updated ON maintenance_target
10173
10246
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_project_recent ON compaction_checkpoints(project_id, status, completed_at DESC, pre_captured_at DESC);
10174
10247
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_session_pending ON compaction_checkpoints(project_id, session_id, agent, phase, status, pre_captured_at DESC);
10175
10248
  CREATE INDEX IF NOT EXISTS idx_compaction_checkpoints_source ON compaction_checkpoints(project_id, source_key);
10249
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC);
10250
+ CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id);
10251
+ CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at);
10252
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC);
10253
+ CREATE INDEX IF NOT EXISTS idx_memory_feedback_events_candidate ON memory_feedback_events(project_id, candidate_kind, candidate_id, observed_at DESC);
10254
+ CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC);
10176
10255
  CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
10177
10256
  ON maintenance_jobs(project_id, kind, dedupe_key)
10178
10257
  WHERE status IN ('pending', 'running', 'retry');
@@ -10328,6 +10407,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
10328
10407
  }
10329
10408
  db2.exec("CREATE INDEX IF NOT EXISTS idx_media_jobs_source_asset ON media_jobs(project_id, source_asset_id, status)");
10330
10409
  }
10410
+ },
10411
+ {
10412
+ id: "1.8-context-control-plane",
10413
+ apply: (db2) => {
10414
+ db2.exec(CREATE_EVIDENCE_CARDS_TABLE);
10415
+ db2.exec(CREATE_EVIDENCE_CARD_EVENTS_TABLE);
10416
+ db2.exec(CREATE_MEMORY_FEEDBACK_STATES_TABLE);
10417
+ db2.exec(CREATE_MEMORY_FEEDBACK_EVENTS_TABLE);
10418
+ db2.exec(CREATE_MCP_BINDINGS_TABLE);
10419
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_project ON evidence_cards(project_id, status, updated_at DESC)");
10420
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_cards_candidate ON evidence_cards(project_id, candidate_kind, candidate_id)");
10421
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_evidence_card_events_card ON evidence_card_events(card_id, created_at)");
10422
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_feedback_states_project ON memory_feedback_states(project_id, candidate_kind, updated_at DESC)");
10423
+ 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)");
10424
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_mcp_bindings_project ON mcp_bindings(project_id, last_used_at DESC)");
10425
+ }
10331
10426
  }
10332
10427
  ];
10333
10428
  _dbCache = /* @__PURE__ */ new Map();
@@ -21514,6 +21609,8 @@ var require_main = __commonJS({
21514
21609
  var dotenv_loader_exports = {};
21515
21610
  __export(dotenv_loader_exports, {
21516
21611
  getLoadedEnvFiles: () => getLoadedEnvFiles,
21612
+ getLoadedEnvKeys: () => getLoadedEnvKeys,
21613
+ getLoadedEnvValue: () => getLoadedEnvValue,
21517
21614
  loadDotenv: () => loadDotenv,
21518
21615
  resetDotenv: () => resetDotenv
21519
21616
  });
@@ -21524,6 +21621,8 @@ function loadEnvFile(filePath) {
21524
21621
  const parsed = (0, import_dotenv.parse)(readFileSync7(filePath, "utf-8"));
21525
21622
  for (const [key, value] of Object.entries(parsed)) {
21526
21623
  if (!(key in process.env)) {
21624
+ loadedEnvKeys.add(key);
21625
+ loadedEnvValues.set(key, value);
21527
21626
  process.env[key] = value;
21528
21627
  injectedKeys.add(key);
21529
21628
  }
@@ -21536,6 +21635,8 @@ function loadDotenv(projectRoot, options = {}) {
21536
21635
  resetDotenv();
21537
21636
  }
21538
21637
  loadedEnvFiles.length = 0;
21638
+ loadedEnvKeys.clear();
21639
+ loadedEnvValues.clear();
21539
21640
  if (projectRoot) {
21540
21641
  loadEnvFile(getProjectDotenvPath(projectRoot));
21541
21642
  }
@@ -21551,11 +21652,18 @@ function resetDotenv() {
21551
21652
  dotenvLoaded = false;
21552
21653
  dotenvProjectRoot = null;
21553
21654
  loadedEnvFiles.length = 0;
21655
+ loadedEnvKeys.clear();
21554
21656
  }
21555
21657
  function getLoadedEnvFiles() {
21556
21658
  return loadedEnvFiles;
21557
21659
  }
21558
- var import_dotenv, dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, injectedKeys;
21660
+ function getLoadedEnvKeys() {
21661
+ return loadedEnvKeys;
21662
+ }
21663
+ function getLoadedEnvValue(key) {
21664
+ return loadedEnvValues.get(key);
21665
+ }
21666
+ var import_dotenv, dotenvLoaded, dotenvProjectRoot, loadedEnvFiles, loadedEnvKeys, loadedEnvValues, injectedKeys;
21559
21667
  var init_dotenv_loader = __esm({
21560
21668
  "src/config/dotenv-loader.ts"() {
21561
21669
  "use strict";
@@ -21565,6 +21673,8 @@ var init_dotenv_loader = __esm({
21565
21673
  dotenvLoaded = false;
21566
21674
  dotenvProjectRoot = null;
21567
21675
  loadedEnvFiles = [];
21676
+ loadedEnvKeys = /* @__PURE__ */ new Set();
21677
+ loadedEnvValues = /* @__PURE__ */ new Map();
21568
21678
  injectedKeys = /* @__PURE__ */ new Set();
21569
21679
  }
21570
21680
  });
@@ -26608,6 +26718,234 @@ var init_lifecycle = __esm({
26608
26718
  }
26609
26719
  });
26610
26720
 
26721
+ // src/store/evidence-store.ts
26722
+ var evidence_store_exports = {};
26723
+ __export(evidence_store_exports, {
26724
+ EvidenceCardStore: () => EvidenceCardStore,
26725
+ buildEvidenceCardId: () => buildEvidenceCardId
26726
+ });
26727
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
26728
+ function safeJson(value, fallback) {
26729
+ if (typeof value !== "string" || value.length === 0) return fallback;
26730
+ try {
26731
+ return JSON.parse(value);
26732
+ } catch {
26733
+ return fallback;
26734
+ }
26735
+ }
26736
+ function rowToCard(row) {
26737
+ return {
26738
+ id: String(row.id),
26739
+ projectId: String(row.project_id),
26740
+ candidateKind: row.candidate_kind,
26741
+ candidateId: String(row.candidate_id),
26742
+ title: String(row.title),
26743
+ summary: String(row.summary ?? ""),
26744
+ ...row.entity_name ? { entityName: String(row.entity_name) } : {},
26745
+ sourceKind: String(row.source_kind),
26746
+ sourceRef: String(row.source_ref),
26747
+ ...row.locator ? { locator: String(row.locator) } : {},
26748
+ ...row.captured_hash ? { capturedHash: String(row.captured_hash) } : {},
26749
+ ...row.session_id ? { sessionId: String(row.session_id) } : {},
26750
+ status: String(row.status ?? "active"),
26751
+ verification: row.verification,
26752
+ freshness: row.freshness,
26753
+ ...row.stale_reason ? { staleReason: String(row.stale_reason) } : {},
26754
+ files: safeJson(row.files_json, []),
26755
+ relatedEntities: safeJson(row.related_entities_json, []),
26756
+ createdAt: String(row.created_at),
26757
+ updatedAt: String(row.updated_at)
26758
+ };
26759
+ }
26760
+ function observationHash(observation) {
26761
+ return createHash3("sha256").update(JSON.stringify({
26762
+ projectId: observation.projectId,
26763
+ id: observation.id,
26764
+ title: observation.title,
26765
+ narrative: observation.narrative,
26766
+ facts: observation.facts,
26767
+ files: observation.filesModified,
26768
+ relatedEntities: observation.relatedEntities
26769
+ })).digest("hex");
26770
+ }
26771
+ function sourceKind(observation) {
26772
+ if (observation.source === "git" || observation.sourceDetail === "git-ingest") return "git";
26773
+ if (observation.sourceDetail === "hook") return "hook";
26774
+ return observation.source ?? "agent";
26775
+ }
26776
+ function buildEvidenceCardId(projectId, candidateKind, candidateId) {
26777
+ return `evidence:${candidateKind}:${projectId}:${candidateId}`;
26778
+ }
26779
+ var EvidenceCardStore;
26780
+ var init_evidence_store = __esm({
26781
+ "src/store/evidence-store.ts"() {
26782
+ "use strict";
26783
+ init_esm_shims();
26784
+ init_sqlite_db();
26785
+ EvidenceCardStore = class {
26786
+ db = null;
26787
+ async init(dataDir) {
26788
+ this.db = getDatabase(dataDir);
26789
+ }
26790
+ requireDb() {
26791
+ if (!this.db) throw new Error("EvidenceCardStore is not initialized.");
26792
+ return this.db;
26793
+ }
26794
+ upsertObservation(observation) {
26795
+ const db2 = this.requireDb();
26796
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
26797
+ const card = {
26798
+ id: `evidence:observation:${observation.projectId}:${observation.id}`,
26799
+ projectId: observation.projectId,
26800
+ candidateKind: "observation",
26801
+ candidateId: String(observation.id),
26802
+ title: observation.title,
26803
+ summary: observation.narrative.slice(0, 500),
26804
+ entityName: observation.entityName,
26805
+ sourceKind: sourceKind(observation),
26806
+ sourceRef: `observation:${observation.id}`,
26807
+ locator: observation.filesModified?.join(", ") || `observation:${observation.id}`,
26808
+ capturedHash: observationHash(observation),
26809
+ ...observation.sessionId ? { sessionId: observation.sessionId } : {},
26810
+ status: observation.status ?? "active",
26811
+ verification: observation.status === "resolved" ? "verified" : "unverified",
26812
+ freshness: "current",
26813
+ files: observation.filesModified ?? [],
26814
+ relatedEntities: observation.relatedEntities ?? [],
26815
+ createdAt: observation.createdAt,
26816
+ updatedAt: now3
26817
+ };
26818
+ db2.prepare(`
26819
+ INSERT INTO evidence_cards (
26820
+ id, project_id, candidate_kind, candidate_id, title, summary, entity_name,
26821
+ source_kind, source_ref, locator, captured_hash, session_id, status,
26822
+ verification, freshness, stale_reason, files_json, related_entities_json,
26823
+ created_at, updated_at
26824
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)
26825
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
26826
+ title = excluded.title,
26827
+ summary = excluded.summary,
26828
+ entity_name = excluded.entity_name,
26829
+ source_kind = excluded.source_kind,
26830
+ source_ref = excluded.source_ref,
26831
+ locator = excluded.locator,
26832
+ captured_hash = excluded.captured_hash,
26833
+ session_id = excluded.session_id,
26834
+ status = excluded.status,
26835
+ freshness = 'current',
26836
+ stale_reason = NULL,
26837
+ files_json = excluded.files_json,
26838
+ related_entities_json = excluded.related_entities_json,
26839
+ updated_at = excluded.updated_at
26840
+ `).run(
26841
+ card.id,
26842
+ card.projectId,
26843
+ card.candidateKind,
26844
+ card.candidateId,
26845
+ card.title,
26846
+ card.summary,
26847
+ card.entityName ?? null,
26848
+ card.sourceKind,
26849
+ card.sourceRef,
26850
+ card.locator ?? null,
26851
+ card.capturedHash ?? null,
26852
+ card.sessionId ?? null,
26853
+ card.status,
26854
+ card.verification,
26855
+ card.freshness,
26856
+ JSON.stringify(card.files),
26857
+ JSON.stringify(card.relatedEntities),
26858
+ card.createdAt,
26859
+ card.updatedAt
26860
+ );
26861
+ return this.get(card.projectId, card.candidateKind, card.candidateId) ?? card;
26862
+ }
26863
+ get(projectId, candidateKind, candidateId) {
26864
+ const row = this.requireDb().prepare(`
26865
+ SELECT * FROM evidence_cards WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
26866
+ `).get(projectId, candidateKind, candidateId);
26867
+ return row ? rowToCard(row) : void 0;
26868
+ }
26869
+ list(projectId, options = {}) {
26870
+ const limit2 = Math.min(100, Math.max(1, Math.floor(options.limit ?? 20)));
26871
+ const clauses = ["project_id = ?"];
26872
+ const args = [projectId];
26873
+ if (options.freshness) {
26874
+ clauses.push("freshness = ?");
26875
+ args.push(options.freshness);
26876
+ }
26877
+ if (options.status) {
26878
+ clauses.push("status = ?");
26879
+ args.push(options.status);
26880
+ }
26881
+ args.push(limit2);
26882
+ const rows = this.requireDb().prepare(`
26883
+ SELECT * FROM evidence_cards WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC, id DESC LIMIT ?
26884
+ `).all(...args);
26885
+ return rows.map(rowToCard);
26886
+ }
26887
+ syncObservations(observations2) {
26888
+ return observations2.map((observation) => this.upsertObservation(observation));
26889
+ }
26890
+ markStaleForPaths(projectId, paths, reason = "source file changed") {
26891
+ const normalized = new Set(paths.map((value) => value.replace(/\\/g, "/").toLowerCase()));
26892
+ if (normalized.size === 0) return 0;
26893
+ const db2 = this.requireDb();
26894
+ const rows = db2.prepare(`SELECT * FROM evidence_cards WHERE project_id = ? AND freshness != 'stale'`).all(projectId);
26895
+ const update2 = db2.prepare(`
26896
+ UPDATE evidence_cards SET freshness = 'stale', stale_reason = ?, updated_at = ?
26897
+ WHERE id = ?
26898
+ `);
26899
+ let changed = 0;
26900
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
26901
+ const run = db2.transaction(() => {
26902
+ for (const row of rows) {
26903
+ const files = safeJson(row.files_json, []);
26904
+ if (!files.some((file2) => normalized.has(file2.replace(/\\/g, "/").toLowerCase()))) continue;
26905
+ update2.run(reason, now3, row.id);
26906
+ changed++;
26907
+ }
26908
+ });
26909
+ run();
26910
+ return changed;
26911
+ }
26912
+ setVerification(projectId, cardId, verification) {
26913
+ this.requireDb().prepare(`
26914
+ UPDATE evidence_cards SET verification = ?, updated_at = ? WHERE project_id = ? AND id = ?
26915
+ `).run(verification, (/* @__PURE__ */ new Date()).toISOString(), projectId, cardId);
26916
+ }
26917
+ recordEvent(projectId, cardId, kind, detail) {
26918
+ const event = {
26919
+ id: randomUUID3(),
26920
+ projectId,
26921
+ cardId,
26922
+ kind,
26923
+ ...detail ? { detail: detail.slice(0, 2e3) } : {},
26924
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
26925
+ };
26926
+ this.requireDb().prepare(`
26927
+ INSERT INTO evidence_card_events (id, project_id, card_id, kind, detail, created_at)
26928
+ VALUES (?, ?, ?, ?, ?, ?)
26929
+ `).run(event.id, event.projectId, event.cardId, event.kind, event.detail ?? null, event.createdAt);
26930
+ return event;
26931
+ }
26932
+ listEvents(cardId, limit2 = 50) {
26933
+ const rows = this.requireDb().prepare(`
26934
+ SELECT * FROM evidence_card_events WHERE card_id = ? ORDER BY created_at DESC LIMIT ?
26935
+ `).all(cardId, Math.min(200, Math.max(1, Math.floor(limit2))));
26936
+ return rows.map((row) => ({
26937
+ id: String(row.id),
26938
+ projectId: String(row.project_id),
26939
+ cardId: String(row.card_id),
26940
+ kind: String(row.kind),
26941
+ ...row.detail ? { detail: String(row.detail) } : {},
26942
+ createdAt: String(row.created_at)
26943
+ }));
26944
+ }
26945
+ };
26946
+ }
26947
+ });
26948
+
26611
26949
  // src/runtime/isolated-maintenance.ts
26612
26950
  import { spawn } from "child_process";
26613
26951
  import { existsSync as existsSync10 } from "fs";
@@ -26769,7 +27107,7 @@ var workspace_store_exports = {};
26769
27107
  __export(workspace_store_exports, {
26770
27108
  KnowledgeWorkspaceStore: () => KnowledgeWorkspaceStore
26771
27109
  });
26772
- import { randomUUID as randomUUID3 } from "crypto";
27110
+ import { randomUUID as randomUUID4 } from "crypto";
26773
27111
  function arrayValue(raw) {
26774
27112
  if (typeof raw !== "string" || !raw) return [];
26775
27113
  try {
@@ -26910,7 +27248,7 @@ var init_workspace_store = __esm({
26910
27248
  }
26911
27249
  createProposal(input) {
26912
27250
  const proposal = {
26913
- id: input.id ?? randomUUID3(),
27251
+ id: input.id ?? randomUUID4(),
26914
27252
  ...input,
26915
27253
  status: input.status ?? "pending",
26916
27254
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -26963,13 +27301,13 @@ __export(workspace_exports, {
26963
27301
  loadKnowledgeWorkspace: () => loadKnowledgeWorkspace,
26964
27302
  resolveKnowledgeWorkspaceFile: () => resolveKnowledgeWorkspaceFile
26965
27303
  });
26966
- import { createHash as createHash3 } from "crypto";
27304
+ import { createHash as createHash4 } from "crypto";
26967
27305
  import { execFile } from "child_process";
26968
27306
  import { promises as fs8 } from "fs";
26969
27307
  import path12 from "path";
26970
27308
  import { promisify } from "util";
26971
27309
  function hash(value) {
26972
- return createHash3("sha256").update(value).digest("hex");
27310
+ return createHash4("sha256").update(value).digest("hex");
26973
27311
  }
26974
27312
  function workspaceId(projectId, mode) {
26975
27313
  return "workspace:" + hash(projectId + ":" + mode).slice(0, 24);
@@ -27569,7 +27907,7 @@ var media_store_exports = {};
27569
27907
  __export(media_store_exports, {
27570
27908
  MediaStore: () => MediaStore
27571
27909
  });
27572
- import { randomUUID as randomUUID4 } from "crypto";
27910
+ import { randomUUID as randomUUID5 } from "crypto";
27573
27911
  function asOptionalText(value) {
27574
27912
  return typeof value === "string" && value.length > 0 ? value : void 0;
27575
27913
  }
@@ -27739,7 +28077,7 @@ var init_media_store = __esm({
27739
28077
  return { asset: this.getAsset(input.projectId, existing.id), deduplicated: true };
27740
28078
  }
27741
28079
  const asset = {
27742
- id: randomUUID4(),
28080
+ id: randomUUID5(),
27743
28081
  ...input,
27744
28082
  createdAt: now3,
27745
28083
  updatedAt: now3
@@ -27819,7 +28157,7 @@ var init_media_store = __esm({
27819
28157
  `).get(input.assetId, input.observationId ?? null, input.role);
27820
28158
  if (existing) return rowToLink(existing);
27821
28159
  const link = {
27822
- id: randomUUID4(),
28160
+ id: randomUUID5(),
27823
28161
  assetId: input.assetId,
27824
28162
  projectId: input.projectId,
27825
28163
  ...input.observationId !== void 0 ? { observationId: input.observationId } : {},
@@ -27858,7 +28196,7 @@ var init_media_store = __esm({
27858
28196
  return rowToDerivation(this.db.prepare("SELECT * FROM media_derivations WHERE id = ?").get(existing.id));
27859
28197
  }
27860
28198
  const derivation = {
27861
- id: randomUUID4(),
28199
+ id: randomUUID5(),
27862
28200
  ...input,
27863
28201
  content,
27864
28202
  ...error40 ? { error: error40 } : {},
@@ -27945,7 +28283,7 @@ var init_media_store = __esm({
27945
28283
  createJob(input) {
27946
28284
  const now3 = Date.now();
27947
28285
  const job = {
27948
- id: randomUUID4(),
28286
+ id: randomUUID5(),
27949
28287
  projectId: input.projectId,
27950
28288
  kind: input.kind,
27951
28289
  status: "queued",
@@ -28121,14 +28459,14 @@ __export(asset_store_exports, {
28121
28459
  readMediaAsset: () => readMediaAsset,
28122
28460
  removeMediaAsset: () => removeMediaAsset
28123
28461
  });
28124
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
28462
+ import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
28125
28463
  import { copyFile, lstat, mkdir as mkdir3, open, readFile as readFile3, readdir, rename as rename2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
28126
28464
  import path13 from "path";
28127
28465
  function mediaRoot(dataDir) {
28128
28466
  return path13.resolve(dataDir, "media");
28129
28467
  }
28130
28468
  function projectStorageKey(projectId) {
28131
- return createHash4("sha256").update(projectId).digest("hex").slice(0, 16);
28469
+ return createHash5("sha256").update(projectId).digest("hex").slice(0, 16);
28132
28470
  }
28133
28471
  function validateUnderRoot(root, candidate2) {
28134
28472
  const resolved = path13.resolve(candidate2);
@@ -28188,14 +28526,14 @@ async function readHeader(filePath, bytes = 32) {
28188
28526
  }
28189
28527
  async function sha256File(filePath) {
28190
28528
  const bytes = await readFile3(filePath);
28191
- return createHash4("sha256").update(bytes).digest("hex");
28529
+ return createHash5("sha256").update(bytes).digest("hex");
28192
28530
  }
28193
28531
  function destinationFor(root, projectId, sha2562, extension3) {
28194
28532
  const relative3 = path13.join("assets", projectStorageKey(projectId), sha2562.slice(0, 2), `${sha2562}${extension3}`);
28195
28533
  return { relative: relative3, absolute: validateUnderRoot(root, path13.join(root, relative3)) };
28196
28534
  }
28197
28535
  function deletionStagingPath(root, assetId) {
28198
- return validateUnderRoot(root, path13.join(root, ".trash", `${assetId}.${randomUUID5()}.pending-delete`));
28536
+ return validateUnderRoot(root, path13.join(root, ".trash", `${assetId}.${randomUUID6()}.pending-delete`));
28199
28537
  }
28200
28538
  async function destinationExists(destination) {
28201
28539
  try {
@@ -28209,7 +28547,7 @@ async function destinationExists(destination) {
28209
28547
  async function persistFile(source, destination) {
28210
28548
  await mkdir3(path13.dirname(destination), { recursive: true });
28211
28549
  if (await destinationExists(destination)) return false;
28212
- const temporary = `${destination}.${randomUUID5()}.tmp`;
28550
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
28213
28551
  try {
28214
28552
  await copyFile(source, temporary);
28215
28553
  await rename2(temporary, destination);
@@ -28225,7 +28563,7 @@ async function persistFile(source, destination) {
28225
28563
  async function persistBuffer(bytes, destination) {
28226
28564
  await mkdir3(path13.dirname(destination), { recursive: true });
28227
28565
  if (await destinationExists(destination)) return false;
28228
- const temporary = `${destination}.${randomUUID5()}.tmp`;
28566
+ const temporary = `${destination}.${randomUUID6()}.tmp`;
28229
28567
  try {
28230
28568
  await writeFile3(temporary, bytes, { flag: "wx" });
28231
28569
  await rename2(temporary, destination);
@@ -28294,7 +28632,7 @@ async function importMediaBuffer(input) {
28294
28632
  if (input.bytes.length === 0) throw new Error("Media import rejects empty payloads");
28295
28633
  if (input.bytes.length > maxBytes) throw new Error(`Media payload is too large (${input.bytes.length} bytes; limit ${maxBytes})`);
28296
28634
  const detected = detectMedia(input.bytes.subarray(0, 32));
28297
- const sha2562 = createHash4("sha256").update(input.bytes).digest("hex");
28635
+ const sha2562 = createHash5("sha256").update(input.bytes).digest("hex");
28298
28636
  return importKnownMedia({
28299
28637
  dataDir: input.dataDir,
28300
28638
  projectId: input.projectId,
@@ -28312,7 +28650,7 @@ async function readMediaAsset(dataDir, asset, maxBytes = DEFAULT_MAX_MEDIA_BYTES
28312
28650
  if (asset.byteSize > maxBytes) throw new Error(`Media asset exceeds read limit (${asset.byteSize} bytes; limit ${maxBytes})`);
28313
28651
  const bytes = await readFile3(getMediaAssetPath(dataDir, asset));
28314
28652
  if (bytes.length !== asset.byteSize) throw new Error(`Media asset size no longer matches metadata: ${asset.id}`);
28315
- const sha2562 = createHash4("sha256").update(bytes).digest("hex");
28653
+ const sha2562 = createHash5("sha256").update(bytes).digest("hex");
28316
28654
  if (sha2562 !== asset.sha256) throw new Error(`Media asset hash no longer matches metadata: ${asset.id}`);
28317
28655
  return bytes;
28318
28656
  }
@@ -81182,7 +81520,7 @@ var require_gaxios = __commonJS({
81182
81520
  var retry_js_1 = require_retry3();
81183
81521
  var stream_1 = __require("stream");
81184
81522
  var interceptor_js_1 = require_interceptor();
81185
- var randomUUID17 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
81523
+ var randomUUID20 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
81186
81524
  var HTTP_STATUS_NO_CONTENT = 204;
81187
81525
  var Gaxios = class {
81188
81526
  agentCache = /* @__PURE__ */ new Map();
@@ -81455,7 +81793,7 @@ var require_gaxios = __commonJS({
81455
81793
  */
81456
81794
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
81457
81795
  if (opts.multipart?.length) {
81458
- const boundary = await randomUUID17();
81796
+ const boundary = await randomUUID20();
81459
81797
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
81460
81798
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
81461
81799
  } else if (shouldDirectlyPassData) {
@@ -93682,7 +94020,7 @@ var require_websocket = __commonJS({
93682
94020
  var http3 = __require("http");
93683
94021
  var net = __require("net");
93684
94022
  var tls = __require("tls");
93685
- var { randomBytes: randomBytes3, createHash: createHash20 } = __require("crypto");
94023
+ var { randomBytes: randomBytes3, createHash: createHash22 } = __require("crypto");
93686
94024
  var { Duplex, Readable: Readable3 } = __require("stream");
93687
94025
  var { URL: URL2 } = __require("url");
93688
94026
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -94350,7 +94688,7 @@ var require_websocket = __commonJS({
94350
94688
  abortHandshake(websocket, socket, "Invalid Upgrade header");
94351
94689
  return;
94352
94690
  }
94353
- const digest2 = createHash20("sha1").update(key + GUID).digest("base64");
94691
+ const digest2 = createHash22("sha1").update(key + GUID).digest("base64");
94354
94692
  if (res.headers["sec-websocket-accept"] !== digest2) {
94355
94693
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
94356
94694
  return;
@@ -94722,7 +95060,7 @@ var require_websocket_server = __commonJS({
94722
95060
  var EventEmitter2 = __require("events");
94723
95061
  var http3 = __require("http");
94724
95062
  var { Duplex } = __require("stream");
94725
- var { createHash: createHash20 } = __require("crypto");
95063
+ var { createHash: createHash22 } = __require("crypto");
94726
95064
  var extension3 = require_extension();
94727
95065
  var PerMessageDeflate2 = require_permessage_deflate();
94728
95066
  var subprotocol2 = require_subprotocol();
@@ -95029,7 +95367,7 @@ var require_websocket_server = __commonJS({
95029
95367
  );
95030
95368
  }
95031
95369
  if (this._state > RUNNING) return abortHandshake(socket, 503);
95032
- const digest2 = createHash20("sha1").update(key + GUID).digest("base64");
95370
+ const digest2 = createHash22("sha1").update(key + GUID).digest("base64");
95033
95371
  const headers = [
95034
95372
  "HTTP/1.1 101 Switching Protocols",
95035
95373
  "Upgrade: websocket",
@@ -168432,8 +168770,8 @@ var init_session_resources = __esm({
168432
168770
  });
168433
168771
 
168434
168772
  // packages/ai/dist/utils/abort-signals.js
168435
- function combineAbortSignals(signals) {
168436
- const activeSignals = signals.filter((signal) => signal !== void 0);
168773
+ function combineAbortSignals(signals2) {
168774
+ const activeSignals = signals2.filter((signal) => signal !== void 0);
168437
168775
  if (activeSignals.length === 0) {
168438
168776
  return { cleanup: () => {
168439
168777
  } };
@@ -178146,6 +178484,186 @@ var init_audio_jobs = __esm({
178146
178484
  }
178147
178485
  });
178148
178486
 
178487
+ // src/memory/feedback-model.ts
178488
+ function feedbackWeightMultiplier(weight) {
178489
+ return Math.max(0.35, Math.min(1.4, 0.35 + Math.max(0, Math.min(2, weight)) * 0.525));
178490
+ }
178491
+ var init_feedback_model = __esm({
178492
+ "src/memory/feedback-model.ts"() {
178493
+ "use strict";
178494
+ init_esm_shims();
178495
+ }
178496
+ });
178497
+
178498
+ // src/memory/feedback.ts
178499
+ var feedback_exports = {};
178500
+ __export(feedback_exports, {
178501
+ FEEDBACK_DELTAS: () => FEEDBACK_DELTAS,
178502
+ MemoryFeedbackStore: () => MemoryFeedbackStore,
178503
+ applyMemoryFeedback: () => applyMemoryFeedback,
178504
+ feedbackWeightMultiplier: () => feedbackWeightMultiplier
178505
+ });
178506
+ import { randomUUID as randomUUID7 } from "crypto";
178507
+ function applyMemoryFeedback(current, event) {
178508
+ const delta = event.signal === "revoke" ? 0 : FEEDBACK_DELTAS[event.signal];
178509
+ const weight = Math.max(0, Math.min(2, current.weight + delta));
178510
+ const status = (event.signal === "code-conflict" || event.signal === "user-correction" || event.signal === "weaken") && weight < 0.5 ? "archived" : current.status;
178511
+ return { weight, status, audit: [...current.audit, { ...event }] };
178512
+ }
178513
+ function rowToEvent(row) {
178514
+ return {
178515
+ id: String(row.id),
178516
+ projectId: String(row.project_id),
178517
+ candidateKind: row.candidate_kind,
178518
+ candidateId: String(row.candidate_id),
178519
+ signal: row.signal,
178520
+ ...row.observed_at ? { at: String(row.observed_at) } : { at: (/* @__PURE__ */ new Date()).toISOString() },
178521
+ ...row.actor ? { actor: String(row.actor) } : {},
178522
+ ...row.note ? { note: String(row.note) } : {},
178523
+ ...row.source_ref ? { sourceRef: String(row.source_ref) } : {},
178524
+ ...row.target_event_id ? { targetEventId: String(row.target_event_id) } : {},
178525
+ delta: Number(row.delta ?? 0)
178526
+ };
178527
+ }
178528
+ var FEEDBACK_DELTAS, MemoryFeedbackStore;
178529
+ var init_feedback = __esm({
178530
+ "src/memory/feedback.ts"() {
178531
+ "use strict";
178532
+ init_esm_shims();
178533
+ init_sqlite_db();
178534
+ init_feedback_model();
178535
+ FEEDBACK_DELTAS = {
178536
+ "user-correction": -0.35,
178537
+ "verification-success": 0.25,
178538
+ "verification-failure": -0.25,
178539
+ "code-change": -0.15,
178540
+ used: 0.1,
178541
+ "not-used": -0.05,
178542
+ "code-conflict": -0.4,
178543
+ strengthen: 0.2,
178544
+ weaken: -0.2
178545
+ };
178546
+ MemoryFeedbackStore = class {
178547
+ db = null;
178548
+ async init(dataDir) {
178549
+ this.db = getDatabase(dataDir);
178550
+ }
178551
+ requireDb() {
178552
+ if (!this.db) throw new Error("MemoryFeedbackStore is not initialized.");
178553
+ return this.db;
178554
+ }
178555
+ loadEvents(projectId, candidateKind, candidateId) {
178556
+ return this.requireDb().prepare(`
178557
+ SELECT * FROM memory_feedback_events
178558
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
178559
+ ORDER BY observed_at ASC, rowid ASC
178560
+ `).all(projectId, candidateKind, candidateId).map(rowToEvent);
178561
+ }
178562
+ rebuild(projectId, candidateKind, candidateId) {
178563
+ const events = this.loadEvents(projectId, candidateKind, candidateId);
178564
+ const revoked = new Set(events.filter((event) => event.signal === "revoke" && event.targetEventId).map((event) => event.targetEventId));
178565
+ let state = { weight: 1, status: "active", audit: [] };
178566
+ for (const event of events) {
178567
+ if (event.signal === "revoke" || revoked.has(event.id)) {
178568
+ state.audit.push(event);
178569
+ continue;
178570
+ }
178571
+ state = applyMemoryFeedback(state, event);
178572
+ }
178573
+ this.requireDb().prepare(`
178574
+ INSERT INTO memory_feedback_states (project_id, candidate_kind, candidate_id, weight, lifecycle_status, updated_at)
178575
+ VALUES (?, ?, ?, ?, ?, ?)
178576
+ ON CONFLICT(project_id, candidate_kind, candidate_id) DO UPDATE SET
178577
+ weight = excluded.weight, lifecycle_status = excluded.lifecycle_status, updated_at = excluded.updated_at
178578
+ `).run(projectId, candidateKind, candidateId, state.weight, state.status, (/* @__PURE__ */ new Date()).toISOString());
178579
+ return state;
178580
+ }
178581
+ getState(projectId, candidateKind, candidateId) {
178582
+ const row = this.requireDb().prepare(`
178583
+ SELECT weight, lifecycle_status FROM memory_feedback_states
178584
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
178585
+ `).get(projectId, candidateKind, candidateId);
178586
+ if (!row) return { weight: 1, status: "active", audit: [] };
178587
+ return {
178588
+ weight: Number(row.weight ?? 1),
178589
+ status: row.lifecycle_status ?? "active",
178590
+ audit: this.loadEvents(projectId, candidateKind, candidateId)
178591
+ };
178592
+ }
178593
+ listStates(projectId, candidateKind = "observation") {
178594
+ return this.requireDb().prepare(`
178595
+ SELECT candidate_id, weight, lifecycle_status, updated_at
178596
+ FROM memory_feedback_states WHERE project_id = ? AND candidate_kind = ?
178597
+ ORDER BY updated_at DESC
178598
+ `).all(projectId, candidateKind).map((row) => ({
178599
+ candidateId: String(row.candidate_id),
178600
+ weight: Number(row.weight),
178601
+ status: row.lifecycle_status,
178602
+ updatedAt: String(row.updated_at)
178603
+ }));
178604
+ }
178605
+ weights(projectId, candidateKind = "observation") {
178606
+ const states = /* @__PURE__ */ new Map();
178607
+ for (const row of this.listStates(projectId, candidateKind)) {
178608
+ states.set(row.candidateId, this.getState(projectId, candidateKind, row.candidateId));
178609
+ }
178610
+ return states;
178611
+ }
178612
+ record(input) {
178613
+ if (input.signal === "revoke" && !input.targetEventId) {
178614
+ throw new Error("A revoke feedback event requires targetEventId.");
178615
+ }
178616
+ if (input.signal === "revoke") {
178617
+ const target = this.requireDb().prepare(`
178618
+ SELECT 1 FROM memory_feedback_events
178619
+ WHERE id = ? AND project_id = ? AND candidate_kind = ? AND candidate_id = ?
178620
+ `).get(input.targetEventId, input.projectId, input.candidateKind, input.candidateId);
178621
+ if (!target) throw new Error("The feedback event to revoke was not found in the same candidate scope.");
178622
+ }
178623
+ const event = {
178624
+ id: randomUUID7(),
178625
+ projectId: input.projectId,
178626
+ candidateKind: input.candidateKind,
178627
+ candidateId: input.candidateId,
178628
+ signal: input.signal,
178629
+ sourceRef: input.sourceRef,
178630
+ ...input.actor ? { actor: input.actor } : {},
178631
+ ...input.note ? { note: input.note.slice(0, 2e3) } : {},
178632
+ ...input.targetEventId ? { targetEventId: input.targetEventId } : {},
178633
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
178634
+ delta: input.signal === "revoke" ? 0 : FEEDBACK_DELTAS[input.signal]
178635
+ };
178636
+ this.requireDb().prepare(`
178637
+ INSERT INTO memory_feedback_events (
178638
+ id, project_id, candidate_kind, candidate_id, signal, actor, note,
178639
+ source_ref, target_event_id, delta, observed_at
178640
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
178641
+ `).run(
178642
+ event.id,
178643
+ event.projectId,
178644
+ event.candidateKind,
178645
+ event.candidateId,
178646
+ event.signal,
178647
+ event.actor ?? null,
178648
+ event.note ?? null,
178649
+ event.sourceRef,
178650
+ event.targetEventId ?? null,
178651
+ event.delta,
178652
+ event.at
178653
+ );
178654
+ return { event, state: this.rebuild(input.projectId, input.candidateKind, input.candidateId) };
178655
+ }
178656
+ audit(projectId, candidateKind, candidateId, limit2 = 100) {
178657
+ return this.requireDb().prepare(`
178658
+ SELECT * FROM memory_feedback_events
178659
+ WHERE project_id = ? AND candidate_kind = ? AND candidate_id = ?
178660
+ ORDER BY observed_at DESC, rowid DESC LIMIT ?
178661
+ `).all(projectId, candidateKind, candidateId, Math.min(200, Math.max(1, Math.floor(limit2)))).map(rowToEvent);
178662
+ }
178663
+ };
178664
+ }
178665
+ });
178666
+
178149
178667
  // src/memory/retention.ts
178150
178668
  var retention_exports = {};
178151
178669
  __export(retention_exports, {
@@ -178187,6 +178705,7 @@ function getEffectiveRetentionDays(doc) {
178187
178705
  }
178188
178706
  function isImmune(doc) {
178189
178707
  if (doc.type === "probe") return false;
178708
+ if (doc.feedbackStatus === "archived") return false;
178190
178709
  if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
178191
178710
  if (doc.valueCategory === "core") return true;
178192
178711
  const importance = getImportanceLevel(doc);
@@ -178219,7 +178738,8 @@ function calculateRelevance(doc, referenceTime) {
178219
178738
  const decayFactor = Math.exp(-ageDays / retention);
178220
178739
  const accessCount = doc.accessCount ?? 0;
178221
178740
  const accessBoost = Math.min(2, 1 + 0.1 * accessCount);
178222
- let totalScore = base * decayFactor * accessBoost;
178741
+ const feedbackMultiplier = doc.feedbackWeight === void 0 ? 1 : feedbackWeightMultiplier(doc.feedbackWeight);
178742
+ let totalScore = base * decayFactor * accessBoost * feedbackMultiplier;
178223
178743
  const immune = isImmune(doc);
178224
178744
  if (immune) {
178225
178745
  totalScore = Math.max(totalScore, 0.5);
@@ -178231,7 +178751,8 @@ function calculateRelevance(doc, referenceTime) {
178231
178751
  decayFactor,
178232
178752
  accessBoost,
178233
178753
  ageDays,
178234
- isImmune: immune
178754
+ isImmune: immune,
178755
+ feedbackMultiplier
178235
178756
  };
178236
178757
  }
178237
178758
  function rankByRelevance(docs, referenceTime) {
@@ -178239,6 +178760,7 @@ function rankByRelevance(docs, referenceTime) {
178239
178760
  }
178240
178761
  function getRetentionZone(doc, referenceTime) {
178241
178762
  const now3 = referenceTime ?? /* @__PURE__ */ new Date();
178763
+ if (doc.feedbackStatus === "archived") return "archive-candidate";
178242
178764
  const importance = getImportanceLevel(doc);
178243
178765
  const retention = getEffectiveRetentionDays(doc);
178244
178766
  const createdAt = new Date(doc.createdAt);
@@ -178310,7 +178832,7 @@ function explainRetention(doc, referenceTime) {
178310
178832
  summary: parts.join(" ; ")
178311
178833
  };
178312
178834
  }
178313
- function toRetentionDocument(obs, accessMap) {
178835
+ function toRetentionDocument(obs, accessMap, feedback) {
178314
178836
  const access4 = accessMap?.get(obs.id);
178315
178837
  return {
178316
178838
  id: `obs-${obs.id}`,
@@ -178332,11 +178854,12 @@ function toRetentionDocument(obs, accessMap) {
178332
178854
  sourceDetail: obs.sourceDetail ?? "",
178333
178855
  valueCategory: obs.valueCategory ?? "",
178334
178856
  admissionState: obs.admissionState ?? "",
178335
- admissionReason: obs.admissionReason ?? ""
178857
+ admissionReason: obs.admissionReason ?? "",
178858
+ ...feedback ? { feedbackWeight: feedback.weight, feedbackStatus: feedback.status } : {}
178336
178859
  };
178337
178860
  }
178338
178861
  function projectObservationRetention(observation, options = {}) {
178339
- const document2 = toRetentionDocument(observation, options.accessMap);
178862
+ const document2 = toRetentionDocument(observation, options.accessMap, options.feedback);
178340
178863
  const relevance = calculateRelevance(document2, options.referenceTime);
178341
178864
  const explanation = explainRetention(document2, options.referenceTime);
178342
178865
  return {
@@ -178361,7 +178884,17 @@ function summarizeRetentionProjections(projections) {
178361
178884
  }
178362
178885
  return { active, stale, archiveCandidates, immune };
178363
178886
  }
178364
- async function archiveExpiredBatch(_projectDir, options) {
178887
+ async function loadFeedbackStore(dataDir) {
178888
+ try {
178889
+ const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
178890
+ const store2 = new MemoryFeedbackStore2();
178891
+ await store2.init(dataDir);
178892
+ return store2;
178893
+ } catch {
178894
+ return void 0;
178895
+ }
178896
+ }
178897
+ async function archiveExpiredBatch(projectDir2, options) {
178365
178898
  const store2 = getObservationStore();
178366
178899
  const limit2 = Math.min(500, Math.max(1, Math.floor(options.limit ?? 100)));
178367
178900
  const afterId = Math.max(0, Math.floor(options.afterId ?? 0));
@@ -178370,11 +178903,13 @@ async function archiveExpiredBatch(_projectDir, options) {
178370
178903
  afterId,
178371
178904
  limit: limit2 + 1
178372
178905
  });
178906
+ const feedback = await loadFeedbackStore(projectDir2);
178373
178907
  const hasMore = page.length > limit2;
178374
178908
  const scanned = hasMore ? page.slice(0, limit2) : page;
178375
178909
  const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => projectObservationRetention(observation, {
178376
178910
  accessMap: options.accessMap,
178377
- referenceTime: options.referenceTime
178911
+ referenceTime: options.referenceTime,
178912
+ ...feedback ? { feedback: feedback.getState(options.projectId, "observation", String(observation.id)) } : {}
178378
178913
  }).zone === "archive-candidate").map((observation) => observation.id);
178379
178914
  const archived = candidateIds.length === 0 ? 0 : await store2.atomic(async (tx) => {
178380
178915
  const updates = await Promise.all(
@@ -178405,6 +178940,7 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
178405
178940
  const remaining = reader2 ? remainingObservations.filter((observation) => canManageObservation(observation, reader2)).length : remainingObservations.length;
178406
178941
  return { archived, remaining };
178407
178942
  }
178943
+ const feedback = await loadFeedbackStore(projectDir2);
178408
178944
  return await store2.atomic(async (tx) => {
178409
178945
  const allObs = await tx.loadAll();
178410
178946
  const activeObs = allObs.filter(
@@ -178412,7 +178948,11 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId,
178412
178948
  );
178413
178949
  const archivedIds = [];
178414
178950
  for (const obs of activeObs) {
178415
- const retention = projectObservationRetention(obs, { accessMap, referenceTime });
178951
+ const retention = projectObservationRetention(obs, {
178952
+ accessMap,
178953
+ referenceTime,
178954
+ ...feedback ? { feedback: feedback.getState(obs.projectId, "observation", String(obs.id)) } : {}
178955
+ });
178416
178956
  if (retention.zone === "archive-candidate") {
178417
178957
  archivedIds.push(obs.id);
178418
178958
  }
@@ -178432,6 +178972,7 @@ var init_retention = __esm({
178432
178972
  init_esm_shims();
178433
178973
  init_obs_store();
178434
178974
  init_visibility();
178975
+ init_feedback_model();
178435
178976
  RETENTION_DAYS = {
178436
178977
  critical: 365,
178437
178978
  high: 180,
@@ -178472,7 +179013,7 @@ var owner_exports = {};
178472
179013
  __export(owner_exports, {
178473
179014
  resolveLocalMemoryOwner: () => resolveLocalMemoryOwner
178474
179015
  });
178475
- import { randomUUID as randomUUID6 } from "crypto";
179016
+ import { randomUUID as randomUUID8 } from "crypto";
178476
179017
  import { promises as fs11 } from "fs";
178477
179018
  import path19 from "path";
178478
179019
  function ownerPath(dataDir) {
@@ -178502,7 +179043,7 @@ async function resolveLocalMemoryOwner(dataDir, options = {}) {
178502
179043
  await fs11.mkdir(path19.dirname(filePath), { recursive: true });
178503
179044
  const created = {
178504
179045
  version: 1,
178505
- id: "local-" + randomUUID6(),
179046
+ id: "local-" + randomUUID8(),
178506
179047
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
178507
179048
  };
178508
179049
  try {
@@ -178529,7 +179070,7 @@ var long_term_store_exports = {};
178529
179070
  __export(long_term_store_exports, {
178530
179071
  LongTermMemoryStore: () => LongTermMemoryStore
178531
179072
  });
178532
- import { randomUUID as randomUUID7 } from "crypto";
179073
+ import { randomUUID as randomUUID9 } from "crypto";
178533
179074
  function optionalText2(value) {
178534
179075
  return typeof value === "string" && value.trim() ? value : void 0;
178535
179076
  }
@@ -178580,7 +179121,7 @@ function rowToEvidence(row) {
178580
179121
  createdAt: row.createdAt
178581
179122
  };
178582
179123
  }
178583
- function rowToEvent(row) {
179124
+ function rowToEvent2(row) {
178584
179125
  return {
178585
179126
  id: row.id,
178586
179127
  memoryId: row.memoryId,
@@ -178689,7 +179230,7 @@ var init_long_term_store = __esm({
178689
179230
  }
178690
179231
  insertEvidence(input) {
178691
179232
  const evidence = {
178692
- id: randomUUID7(),
179233
+ id: randomUUID9(),
178693
179234
  ...input,
178694
179235
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
178695
179236
  };
@@ -178716,7 +179257,7 @@ var init_long_term_store = __esm({
178716
179257
  }
178717
179258
  recordEvent(input) {
178718
179259
  const event = {
178719
- id: randomUUID7(),
179260
+ id: randomUUID9(),
178720
179261
  ...input,
178721
179262
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
178722
179263
  };
@@ -178737,7 +179278,7 @@ var init_long_term_store = __esm({
178737
179278
  listEvents(memoryId) {
178738
179279
  return this.db.prepare(`
178739
179280
  SELECT * FROM long_term_memory_events WHERE memoryId = ? ORDER BY createdAt ASC, rowid ASC
178740
- `).all(memoryId).map(rowToEvent);
179281
+ `).all(memoryId).map(rowToEvent2);
178741
179282
  }
178742
179283
  markAccess(ids, at2 = (/* @__PURE__ */ new Date()).toISOString()) {
178743
179284
  const unique3 = [...new Set(ids.filter(Boolean))];
@@ -178806,7 +179347,7 @@ __export(long_term_exports, {
178806
179347
  selectLongTermMemoriesForTask: () => selectLongTermMemoriesForTask,
178807
179348
  supersedeLongTermMemory: () => supersedeLongTermMemory
178808
179349
  });
178809
- import { randomUUID as randomUUID8 } from "crypto";
179350
+ import { randomUUID as randomUUID10 } from "crypto";
178810
179351
  function isOneOf(value, values) {
178811
179352
  return typeof value === "string" && values.includes(value);
178812
179353
  }
@@ -178988,7 +179529,7 @@ async function createManualLongTermMemory(input) {
178988
179529
  const owner = await resolveLocalMemoryOwner(input.dataDir, { create: true });
178989
179530
  if (!owner) throw new Error("Memorix could not resolve a local memory owner.");
178990
179531
  assertScope(input.scope);
178991
- const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID8(), relation: "supports" }];
179532
+ const evidence = input.scope === "user" ? [{ kind: "user", referenceId: owner.id, relation: "user-confirmed" }] : [{ kind: "manual", referenceId: "manual:" + randomUUID10(), relation: "supports" }];
178992
179533
  return createLongTermMemoryWithDataDir({
178993
179534
  dataDir: input.dataDir,
178994
179535
  originProjectId: input.projectId,
@@ -179043,7 +179584,7 @@ async function createLongTermMemoryWithDataDir(input) {
179043
179584
  assertPortableEvidence(input.scope, portability, evidenceInputs);
179044
179585
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
179045
179586
  const memory = {
179046
- id: randomUUID8(),
179587
+ id: randomUUID10(),
179047
179588
  originProjectId: compactText(input.originProjectId, "origin project id", 1e3),
179048
179589
  ownerId: compactText(input.ownerId, "owner id", 160),
179049
179590
  scope: input.scope,
@@ -179361,9 +179902,9 @@ var init_long_term = __esm({
179361
179902
  });
179362
179903
 
179363
179904
  // src/codegraph/ids.ts
179364
- import { createHash as createHash5 } from "crypto";
179905
+ import { createHash as createHash6 } from "crypto";
179365
179906
  function digest(input) {
179366
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
179907
+ return createHash6("sha256").update(input).digest("hex").slice(0, 16);
179367
179908
  }
179368
179909
  function normalizeCodePath(path48) {
179369
179910
  return path48.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
@@ -179398,7 +179939,7 @@ var store_exports = {};
179398
179939
  __export(store_exports, {
179399
179940
  CodeGraphStore: () => CodeGraphStore
179400
179941
  });
179401
- import { randomUUID as randomUUID9 } from "crypto";
179942
+ import { randomUUID as randomUUID11 } from "crypto";
179402
179943
  import { posix as pathPosix } from "path";
179403
179944
  function rowToFile(row) {
179404
179945
  return {
@@ -180115,7 +180656,7 @@ var init_store = __esm({
180115
180656
  * so an interrupted scan cannot be advertised as complete.
180116
180657
  */
180117
180658
  recordCodeStateSnapshot(input) {
180118
- const id = randomUUID9();
180659
+ const id = randomUUID11();
180119
180660
  let snapshot;
180120
180661
  const tx = this.db.transaction(() => {
180121
180662
  const previous = this.db.prepare(
@@ -180193,7 +180734,7 @@ var init_store = __esm({
180193
180734
 
180194
180735
  // src/codegraph/code-state.ts
180195
180736
  import { execFile as execFile2 } from "child_process";
180196
- import { createHash as createHash6 } from "crypto";
180737
+ import { createHash as createHash7 } from "crypto";
180197
180738
  import { promisify as promisify3 } from "util";
180198
180739
  async function runGit(rootPath, args) {
180199
180740
  try {
@@ -180222,7 +180763,7 @@ function changedPathCount(status) {
180222
180763
  return count3;
180223
180764
  }
180224
180765
  function fingerprint(baseRevision, status) {
180225
- return createHash6("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
180766
+ return createHash7("sha256").update(baseRevision ?? "").update("\0").update(status).digest("hex");
180226
180767
  }
180227
180768
  async function collectCodeStateSnapshot(input) {
180228
180769
  const [revision, status] = await Promise.all([
@@ -180322,11 +180863,11 @@ __export(lite_provider_exports, {
180322
180863
  indexProjectLite: () => indexProjectLite,
180323
180864
  refreshProjectLite: () => refreshProjectLite
180324
180865
  });
180325
- import { createHash as createHash7 } from "crypto";
180866
+ import { createHash as createHash8 } from "crypto";
180326
180867
  import { readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
180327
180868
  import { join as join5, relative } from "path";
180328
180869
  function hashText(text2) {
180329
- return createHash7("sha256").update(text2).digest("hex");
180870
+ return createHash8("sha256").update(text2).digest("hex");
180330
180871
  }
180331
180872
  function extension2(path48) {
180332
180873
  const index = path48.lastIndexOf(".");
@@ -180893,7 +181434,7 @@ var claim_store_exports = {};
180893
181434
  __export(claim_store_exports, {
180894
181435
  ClaimStore: () => ClaimStore
180895
181436
  });
180896
- import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
181437
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
180897
181438
  function optionalText3(value) {
180898
181439
  return typeof value === "string" && value ? value : void 0;
180899
181440
  }
@@ -180932,7 +181473,7 @@ function rowToEvidence2(row) {
180932
181473
  createdAt: row.createdAt
180933
181474
  };
180934
181475
  }
180935
- function rowToEvent2(row) {
181476
+ function rowToEvent3(row) {
180936
181477
  return {
180937
181478
  id: row.id,
180938
181479
  projectId: row.projectId,
@@ -180946,7 +181487,7 @@ function rowToEvent2(row) {
180946
181487
  };
180947
181488
  }
180948
181489
  function evidenceKey(input) {
180949
- return createHash8("sha256").update(JSON.stringify([
181490
+ return createHash9("sha256").update(JSON.stringify([
180950
181491
  input.evidenceKind,
180951
181492
  input.evidenceId,
180952
181493
  input.relation,
@@ -181077,7 +181618,7 @@ var init_claim_store = __esm({
181077
181618
  locator, capturedHash, evidenceKey, createdAt
181078
181619
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
181079
181620
  `).run(
181080
- randomUUID10(),
181621
+ randomUUID12(),
181081
181622
  input.claimId,
181082
181623
  input.evidenceKind,
181083
181624
  input.evidenceId,
@@ -181099,7 +181640,7 @@ var init_claim_store = __esm({
181099
181640
  }
181100
181641
  recordEvent(input) {
181101
181642
  const event = {
181102
- id: randomUUID10(),
181643
+ id: randomUUID12(),
181103
181644
  ...input,
181104
181645
  ...input.detail ? { detail: sanitizeCredentials(input.detail) } : {},
181105
181646
  createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
@@ -181127,7 +181668,7 @@ var init_claim_store = __esm({
181127
181668
  SELECT * FROM knowledge_claim_events
181128
181669
  WHERE claimId = ?
181129
181670
  ORDER BY createdAt ASC, id
181130
- `).all(claimId).map(rowToEvent2);
181671
+ `).all(claimId).map(rowToEvent3);
181131
181672
  }
181132
181673
  };
181133
181674
  }
@@ -181144,7 +181685,7 @@ __export(claims_exports, {
181144
181685
  supersedeClaim: () => supersedeClaim,
181145
181686
  writeClaim: () => writeClaim
181146
181687
  });
181147
- import { createHash as createHash9, randomUUID as randomUUID11 } from "crypto";
181688
+ import { createHash as createHash10, randomUUID as randomUUID13 } from "crypto";
181148
181689
  function compactText2(value, field) {
181149
181690
  if (typeof value !== "string") throw new Error("Claim " + field + " must be text");
181150
181691
  const sanitized = sanitizeCredentials(value).trim().replace(/\s+/g, " ");
@@ -181156,7 +181697,7 @@ function normalizedTerm(value) {
181156
181697
  return compactText2(value, "identity").toLocaleLowerCase("en-US");
181157
181698
  }
181158
181699
  function hashIdentity(parts) {
181159
- return createHash9("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
181700
+ return createHash10("sha256").update(parts.join(String.fromCharCode(31))).digest("hex");
181160
181701
  }
181161
181702
  function buildClaimIdentity(input) {
181162
181703
  const subject = normalizedTerm(input.subject);
@@ -181230,7 +181771,7 @@ function normalizeClaimInput(input) {
181230
181771
  const now3 = (/* @__PURE__ */ new Date()).toISOString();
181231
181772
  return {
181232
181773
  claim: {
181233
- id: randomUUID11(),
181774
+ id: randomUUID13(),
181234
181775
  projectId,
181235
181776
  subject,
181236
181777
  predicate,
@@ -181397,7 +181938,7 @@ function supersedeClaim(store2, input) {
181397
181938
  });
181398
181939
  }
181399
181940
  function hashObservation(observation) {
181400
- return createHash9("sha256").update(JSON.stringify([
181941
+ return createHash10("sha256").update(JSON.stringify([
181401
181942
  observation.title,
181402
181943
  observation.narrative,
181403
181944
  observation.facts,
@@ -181641,11 +182182,11 @@ var init_frontmatter = __esm({
181641
182182
  });
181642
182183
 
181643
182184
  // src/knowledge/markdown.ts
181644
- import { createHash as createHash10 } from "crypto";
182185
+ import { createHash as createHash11 } from "crypto";
181645
182186
  import { promises as fs12 } from "fs";
181646
182187
  import path20 from "path";
181647
182188
  function hashContent(content) {
181648
- return createHash10("sha256").update(content).digest("hex");
182189
+ return createHash11("sha256").update(content).digest("hex");
181649
182190
  }
181650
182191
  function stringField(data, key) {
181651
182192
  const value = data[key];
@@ -181771,11 +182312,11 @@ __export(wiki_exports, {
181771
182312
  lintKnowledgeWorkspace: () => lintKnowledgeWorkspace,
181772
182313
  readKnowledgePage: () => readKnowledgePage
181773
182314
  });
181774
- import { createHash as createHash11 } from "crypto";
182315
+ import { createHash as createHash12 } from "crypto";
181775
182316
  import { promises as fs13 } from "fs";
181776
182317
  import path21 from "path";
181777
182318
  function hash2(value) {
181778
- return createHash11("sha256").update(value).digest("hex");
182319
+ return createHash12("sha256").update(value).digest("hex");
181779
182320
  }
181780
182321
  function now() {
181781
182322
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -182319,7 +182860,7 @@ var workflow_store_exports = {};
182319
182860
  __export(workflow_store_exports, {
182320
182861
  WorkflowStore: () => WorkflowStore
182321
182862
  });
182322
- import { randomUUID as randomUUID12 } from "crypto";
182863
+ import { randomUUID as randomUUID14 } from "crypto";
182323
182864
  function parseJson(value, fallback) {
182324
182865
  if (typeof value !== "string" || !value) return fallback;
182325
182866
  try {
@@ -182419,7 +182960,7 @@ var init_workflow_store = __esm({
182419
182960
  }
182420
182961
  recordRun(input) {
182421
182962
  const run = {
182422
- id: randomUUID12(),
182963
+ id: randomUUID14(),
182423
182964
  workflowId: input.workflowId,
182424
182965
  projectId: input.projectId,
182425
182966
  task: input.task.trim(),
@@ -182497,7 +183038,7 @@ var init_outcome_types = __esm({
182497
183038
  });
182498
183039
 
182499
183040
  // src/knowledge/outcome-store.ts
182500
- import { randomUUID as randomUUID13 } from "crypto";
183041
+ import { randomUUID as randomUUID15 } from "crypto";
182501
183042
  function optionalText5(value) {
182502
183043
  return typeof value === "string" && value.trim() ? value : void 0;
182503
183044
  }
@@ -182541,7 +183082,7 @@ var init_outcome_store = __esm({
182541
183082
  }
182542
183083
  record(input) {
182543
183084
  const signal = {
182544
- id: randomUUID13(),
183085
+ id: randomUUID15(),
182545
183086
  projectId: text(input.projectId, "project id"),
182546
183087
  candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
182547
183088
  candidateId: text(input.candidateId, "candidate id"),
@@ -182968,11 +183509,11 @@ __export(workflows_exports, {
182968
183509
  syncCanonicalWorkflows: () => syncCanonicalWorkflows,
182969
183510
  writeCanonicalWorkflow: () => writeCanonicalWorkflow
182970
183511
  });
182971
- import { createHash as createHash12 } from "crypto";
183512
+ import { createHash as createHash13 } from "crypto";
182972
183513
  import { promises as fs14 } from "fs";
182973
183514
  import path22 from "path";
182974
183515
  function hash3(value) {
182975
- return createHash12("sha256").update(value).digest("hex");
183516
+ return createHash13("sha256").update(value).digest("hex");
182976
183517
  }
182977
183518
  function now2() {
182978
183519
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -184369,6 +184910,16 @@ async function bindObservationCodeRefsBestEffort(observation) {
184369
184910
  } catch {
184370
184911
  }
184371
184912
  }
184913
+ async function persistEvidenceCard(observation) {
184914
+ if (!projectDir) return;
184915
+ try {
184916
+ const evidence = new EvidenceCardStore();
184917
+ await evidence.init(projectDir);
184918
+ evidence.upsertObservation(observation);
184919
+ } catch (error40) {
184920
+ console.error(`[memorix] Evidence card projection failed for obs-${observation.id}: ${error40 instanceof Error ? error40.message : String(error40)}`);
184921
+ }
184922
+ }
184372
184923
  function queueClaimDerivation(observation) {
184373
184924
  const dataDir = projectDir;
184374
184925
  const isExplicit = observation.sourceDetail === "explicit";
@@ -184715,6 +185266,7 @@ async function storeObservation(input) {
184715
185266
  }
184716
185267
  if (!persistedCallbackInvoked) await input.onPersisted?.(observation);
184717
185268
  await bindObservationCodeRefsBestEffort(observation);
185269
+ await persistEvidenceCard(observation);
184718
185270
  queueObservationQualification(observation);
184719
185271
  if (resolveObservationVisibility(observation) === "project") {
184720
185272
  queueClaimDerivation(observation);
@@ -184807,6 +185359,7 @@ async function upsertObservation(existing, input, now3) {
184807
185359
  await store2.update(existing);
184808
185360
  }
184809
185361
  await bindObservationCodeRefsBestEffort(existing);
185362
+ await persistEvidenceCard(existing);
184810
185363
  queueObservationQualification(existing);
184811
185364
  if (resolveObservationVisibility(existing) === "project") {
184812
185365
  queueClaimDerivation(existing);
@@ -184940,6 +185493,7 @@ async function resolveObservations(ids, status = "resolved") {
184940
185493
  await Promise.all(changed.map((observation) => tx.update(observation)));
184941
185494
  });
184942
185495
  }
185496
+ for (const observation of changed) await persistEvidenceCard(observation);
184943
185497
  return { resolved, notFound };
184944
185498
  }
184945
185499
  function getProjectObservations(projectId) {
@@ -185277,6 +185831,7 @@ var init_observations = __esm({
185277
185831
  init_secret_filter();
185278
185832
  init_lifecycle();
185279
185833
  init_visibility();
185834
+ init_evidence_store();
185280
185835
  observations = [];
185281
185836
  nextId = 1;
185282
185837
  projectDir = null;
@@ -186709,9 +187264,27 @@ function normalizeMemoryBrowseQuery(query) {
186709
187264
  async function compactSearch(options, surface = "mcp") {
186710
187265
  await ensureFreshIndex();
186711
187266
  const searchOptions = { ...options, query: normalizeMemoryBrowseQuery(options.query) };
186712
- const entries = (await searchObservations(searchOptions)).map(
187267
+ let entries = (await searchObservations(searchOptions)).map(
186713
187268
  (entry) => entry.projectId || !options.projectId ? entry : { ...entry, projectId: options.projectId }
186714
187269
  );
187270
+ if (options.projectId) {
187271
+ try {
187272
+ const feedback = new MemoryFeedbackStore();
187273
+ await feedback.init(await getProjectDataDir(options.projectId));
187274
+ const states = feedback.weights(options.projectId, "observation");
187275
+ entries = entries.map((entry) => {
187276
+ const state = states.get(String(entry.id));
187277
+ if (!state) return entry;
187278
+ return {
187279
+ ...entry,
187280
+ score: (entry.score ?? 0) * feedbackWeightMultiplier(state.weight),
187281
+ feedbackWeight: state.weight,
187282
+ feedbackStatus: state.status
187283
+ };
187284
+ }).filter((entry) => entry.feedbackStatus !== "archived" || options.status === "all").sort((a4, b4) => (b4.score ?? 0) - (a4.score ?? 0));
187285
+ } catch {
187286
+ }
187287
+ }
186715
187288
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId, surface);
186716
187289
  if (entries.length === 0 && options.projectId) {
186717
187290
  const allObservations = filterReadableObservations(getAllObservations(), options.reader);
@@ -187030,6 +187603,8 @@ var init_engine3 = __esm({
187030
187603
  init_mini_skills();
187031
187604
  init_secret_filter();
187032
187605
  init_visibility();
187606
+ init_persistence();
187607
+ init_feedback();
187033
187608
  }
187034
187609
  });
187035
187610
 
@@ -187044,7 +187619,7 @@ __export(team_store_exports, {
187044
187619
  isTeamStoreInitialized: () => isTeamStoreInitialized,
187045
187620
  resetTeamStore: () => resetTeamStore
187046
187621
  });
187047
- import { randomUUID as randomUUID14 } from "crypto";
187622
+ import { randomUUID as randomUUID16 } from "crypto";
187048
187623
  import path24 from "path";
187049
187624
  import fs15 from "fs";
187050
187625
  function getTeamStore() {
@@ -187333,7 +187908,7 @@ var init_team_store = __esm({
187333
187908
  * reactivate it (preserving agent_id). Otherwise create a new one.
187334
187909
  */
187335
187910
  registerAgent(input) {
187336
- const instanceId = input.instanceId || randomUUID14();
187911
+ const instanceId = input.instanceId || randomUUID16();
187337
187912
  const now3 = Date.now();
187338
187913
  const existing = this.stmtAgentFindByInstance.get(
187339
187914
  input.projectId,
@@ -187359,7 +187934,7 @@ var init_team_store = __esm({
187359
187934
  this.eventBus?.emit("agent:joined", { agentId: row2.agent_id, projectId: input.projectId, agentType: input.agentType });
187360
187935
  return row2;
187361
187936
  }
187362
- const agentId = randomUUID14();
187937
+ const agentId = randomUUID16();
187363
187938
  this.stmtAgentUpsert.run({
187364
187939
  agent_id: agentId,
187365
187940
  project_id: input.projectId,
@@ -187461,7 +188036,7 @@ var init_team_store = __esm({
187461
188036
  return { error: `Recipient agent '${input.recipientAgentId}' not found \u2014 cannot send to unknown agent` };
187462
188037
  }
187463
188038
  }
187464
- const id = randomUUID14();
188039
+ const id = randomUUID16();
187465
188040
  const now3 = Date.now();
187466
188041
  const row = {
187467
188042
  id,
@@ -187511,7 +188086,7 @@ var init_team_store = __esm({
187511
188086
  // Task Operations (with atomic claim semantics)
187512
188087
  // ═══════════════════════════════════════════════════════════════════
187513
188088
  createTask(input) {
187514
- const taskId = randomUUID14();
188089
+ const taskId = randomUUID16();
187515
188090
  const now3 = Date.now();
187516
188091
  const row = {
187517
188092
  task_id: taskId,
@@ -187783,7 +188358,7 @@ var init_team_store = __esm({
187783
188358
  for (const [agentId, msgs] of Object.entries(snap.messages.inboxes)) {
187784
188359
  for (const msg of msgs) {
187785
188360
  this.stmtMsgInsert.run({
187786
- id: msg.id ?? randomUUID14(),
188361
+ id: msg.id ?? randomUUID16(),
187787
188362
  project_id: "migrated",
187788
188363
  sender_agent_id: msg.from ?? "unknown",
187789
188364
  recipient_agent_id: msg.to === "__broadcast__" ? null : msg.to ?? agentId,
@@ -192216,8 +192791,8 @@ var init_official_skills = __esm({
192216
192791
  "",
192217
192792
  "| Situation | Prefer | CLI fallback |",
192218
192793
  "|---|---|---|",
192219
- '| 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` |',
192220
- '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --brief-json` |',
192794
+ '| 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` |',
192795
+ '| Continuing prior project work | `memorix_project_context` with the user\'s task | `memorix resume "<task>" --fallback --brief-json` |',
192221
192796
  '| Need structured refs/freshness for code-bound memories | `memorix_context_pack` | `memorix codegraph context-pack --task "<topic>"` |',
192222
192797
  '| Explicit memory graph question | `memorix_graph_context` | `memorix memory graph-context --query "<topic>"` |',
192223
192798
  '| Specific past decision, bug, file, or change | `memorix_search` | `memorix memory search --query "<topic>"` |',
@@ -192230,7 +192805,7 @@ var init_official_skills = __esm({
192230
192805
  "",
192231
192806
  "- 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.",
192232
192807
  "- 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.",
192233
- '- 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.',
192808
+ '- 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.',
192234
192809
  "- 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.",
192235
192810
  "- 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.",
192236
192811
  "- 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.",
@@ -193489,7 +194064,7 @@ alwaysApply: true
193489
194064
  "",
193490
194065
  `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.`,
193491
194066
  "",
193492
- '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.',
194067
+ '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.',
193493
194068
  "",
193494
194069
  "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.",
193495
194070
  "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.",
@@ -197997,7 +198572,7 @@ var compaction_checkpoint_store_exports = {};
197997
198572
  __export(compaction_checkpoint_store_exports, {
197998
198573
  CompactionCheckpointStore: () => CompactionCheckpointStore
197999
198574
  });
198000
- import { createHash as createHash13, randomUUID as randomUUID15 } from "crypto";
198575
+ import { createHash as createHash14, randomUUID as randomUUID17 } from "crypto";
198001
198576
  function optionalText7(value) {
198002
198577
  return typeof value === "string" && value ? value : void 0;
198003
198578
  }
@@ -198039,7 +198614,7 @@ function sourceKeyFor(input, summary) {
198039
198614
  input.tokensBefore ?? "",
198040
198615
  input.firstKeptEntryId ?? ""
198041
198616
  ].join("\0");
198042
- return `derived:${createHash13("sha256").update(material).digest("hex").slice(0, 32)}`;
198617
+ return `derived:${createHash14("sha256").update(material).digest("hex").slice(0, 32)}`;
198043
198618
  }
198044
198619
  function rowToCheckpoint(row) {
198045
198620
  return {
@@ -198098,7 +198673,7 @@ var init_compaction_checkpoint_store = __esm({
198098
198673
  if (pending) return rowToCheckpoint(pending);
198099
198674
  const now3 = input.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
198100
198675
  const checkpoint = {
198101
- id: randomUUID15(),
198676
+ id: randomUUID17(),
198102
198677
  projectId: input.projectId,
198103
198678
  sessionId: input.sessionId,
198104
198679
  agent: input.agent,
@@ -198106,7 +198681,7 @@ var init_compaction_checkpoint_store = __esm({
198106
198681
  captureKind: "preflight",
198107
198682
  reason: safeReason(input.reason),
198108
198683
  sourceEvent: input.sourceEvent,
198109
- sourceKey: `pre:${randomUUID15()}`,
198684
+ sourceKey: `pre:${randomUUID17()}`,
198110
198685
  transcriptAvailable: Boolean(input.transcriptAvailable),
198111
198686
  status: "active",
198112
198687
  preCapturedAt: now3,
@@ -198172,7 +198747,7 @@ var init_compaction_checkpoint_store = __esm({
198172
198747
  return this.get(checkpoint2.id);
198173
198748
  }
198174
198749
  const checkpoint = {
198175
- id: randomUUID15(),
198750
+ id: randomUUID17(),
198176
198751
  projectId: input.projectId,
198177
198752
  sessionId: input.sessionId,
198178
198753
  agent: input.agent,
@@ -198920,6 +199495,59 @@ var init_context_receipt = __esm({
198920
199495
  }
198921
199496
  });
198922
199497
 
199498
+ // src/cli/fallback-budget.ts
199499
+ import { createHash as createHash15 } from "crypto";
199500
+ function fallbackRequestKey(projectId, task) {
199501
+ const normalized = (task ?? "").trim().toLowerCase().replace(/\s+/g, " ");
199502
+ return createHash15("sha256").update(`${projectId}
199503
+ ${normalized}`).digest("hex").slice(0, 32);
199504
+ }
199505
+ var DEFAULT_FALLBACK_TTL_MS, CliFallbackBudget;
199506
+ var init_fallback_budget = __esm({
199507
+ "src/cli/fallback-budget.ts"() {
199508
+ "use strict";
199509
+ init_esm_shims();
199510
+ init_sqlite_db();
199511
+ DEFAULT_FALLBACK_TTL_MS = 10 * 60 * 1e3;
199512
+ CliFallbackBudget = class {
199513
+ db = null;
199514
+ async init(dataDir) {
199515
+ this.db = getDatabase(dataDir);
199516
+ this.db.exec(`
199517
+ CREATE TABLE IF NOT EXISTS cli_fallback_attempts (
199518
+ project_id TEXT NOT NULL,
199519
+ request_key TEXT NOT NULL,
199520
+ count INTEGER NOT NULL DEFAULT 0,
199521
+ last_attempt_at INTEGER NOT NULL,
199522
+ expires_at INTEGER NOT NULL,
199523
+ PRIMARY KEY (project_id, request_key)
199524
+ )
199525
+ `);
199526
+ }
199527
+ claim(projectId, task, now3 = Date.now(), ttlMs = DEFAULT_FALLBACK_TTL_MS) {
199528
+ if (!this.db) throw new Error("CliFallbackBudget is not initialized.");
199529
+ const key = fallbackRequestKey(projectId, task);
199530
+ const current = this.db.prepare(`
199531
+ SELECT count, expires_at FROM cli_fallback_attempts WHERE project_id = ? AND request_key = ?
199532
+ `).get(projectId, key);
199533
+ const count3 = Number(current?.count ?? 0);
199534
+ if (current && Number(current.expires_at) > now3 && count3 >= 1) {
199535
+ return { allowed: false, key, count: count3, reason: "Fallback budget exhausted for this task. Stop probing and continue with the current project files." };
199536
+ }
199537
+ this.db.prepare(`
199538
+ INSERT INTO cli_fallback_attempts (project_id, request_key, count, last_attempt_at, expires_at)
199539
+ VALUES (?, ?, 1, ?, ?)
199540
+ ON CONFLICT(project_id, request_key) DO UPDATE SET
199541
+ count = CASE WHEN excluded.expires_at > cli_fallback_attempts.expires_at THEN 1 ELSE cli_fallback_attempts.count + 1 END,
199542
+ last_attempt_at = excluded.last_attempt_at,
199543
+ expires_at = CASE WHEN excluded.expires_at > cli_fallback_attempts.expires_at THEN excluded.expires_at ELSE cli_fallback_attempts.expires_at END
199544
+ `).run(projectId, key, now3, now3 + Math.max(1e3, ttlMs));
199545
+ return { allowed: true, key, count: 1 };
199546
+ }
199547
+ };
199548
+ }
199549
+ });
199550
+
198923
199551
  // src/cli/commands/context.ts
198924
199552
  var context_exports = {};
198925
199553
  __export(context_exports, {
@@ -198948,6 +199576,7 @@ var init_context2 = __esm({
198948
199576
  init_context_receipt();
198949
199577
  init_workflows2();
198950
199578
  init_operator_shared();
199579
+ init_fallback_budget();
198951
199580
  context_default = defineCommand({
198952
199581
  meta: {
198953
199582
  name: "context",
@@ -198964,13 +199593,23 @@ var init_context2 = __esm({
198964
199593
  refresh: { type: "string", description: "Project scan policy: auto, always, or never" },
198965
199594
  agent: { type: "string", description: "Optional target agent for compatible workflow selection" },
198966
199595
  json: { type: "boolean", description: "Emit machine-readable JSON output" },
198967
- briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON" }
199596
+ briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON" },
199597
+ fallback: { type: "boolean", description: "Mark this as the one-shot MCP-unavailable fallback for the task" }
198968
199598
  },
198969
199599
  run: async ({ args }) => {
198970
199600
  const asJson = !!args.json || !!args.briefJson;
198971
199601
  const task = args.task?.trim() || args.input?.trim() || void 0;
198972
199602
  try {
198973
199603
  const { project, dataDir, reader: reader2 } = await getCliProjectContext();
199604
+ if (args.fallback) {
199605
+ const budget = new CliFallbackBudget();
199606
+ await budget.init(dataDir);
199607
+ const claim = budget.claim(project.id, task);
199608
+ if (!claim.allowed) {
199609
+ emitError(claim.reason ?? "CLI fallback budget exhausted for this task.", asJson);
199610
+ return;
199611
+ }
199612
+ }
198974
199613
  const context = await buildAutoProjectContext({
198975
199614
  project,
198976
199615
  dataDir,
@@ -199028,7 +199667,8 @@ var init_resume = __esm({
199028
199667
  refresh: { type: "string", description: "Project scan policy: auto, always, or never" },
199029
199668
  agent: { type: "string", description: "Optional target agent for compatible workflow selection" },
199030
199669
  json: { type: "boolean", description: "Emit machine-readable JSON output" },
199031
- briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON" }
199670
+ briefJson: { type: "boolean", description: "Emit only the bounded agent brief and receipt JSON" },
199671
+ fallback: { type: "boolean", description: "Mark this as the one-shot MCP-unavailable fallback for the task" }
199032
199672
  },
199033
199673
  async run({ args }) {
199034
199674
  await context_default.run?.({
@@ -199039,6 +199679,7 @@ var init_resume = __esm({
199039
199679
  agent: args.agent,
199040
199680
  json: args.json,
199041
199681
  briefJson: args.briefJson,
199682
+ fallback: args.fallback,
199042
199683
  resume: true
199043
199684
  },
199044
199685
  rawArgs: [],
@@ -200294,6 +200935,166 @@ var init_knowledge = __esm({
200294
200935
  }
200295
200936
  });
200296
200937
 
200938
+ // src/cli/commands/evidence.ts
200939
+ var evidence_exports = {};
200940
+ __export(evidence_exports, {
200941
+ default: () => evidence_default
200942
+ });
200943
+ var evidence_default;
200944
+ var init_evidence = __esm({
200945
+ "src/cli/commands/evidence.ts"() {
200946
+ "use strict";
200947
+ init_esm_shims();
200948
+ init_dist2();
200949
+ init_obs_store();
200950
+ init_evidence_store();
200951
+ init_operator_shared();
200952
+ evidence_default = defineCommand({
200953
+ meta: { name: "evidence", description: "Inspect project-scoped evidence cards and provenance" },
200954
+ args: {
200955
+ action: { type: "positional", description: "list, get, sync, stale, or events", required: false },
200956
+ id: { type: "string", description: "Candidate id, usually an observation id" },
200957
+ paths: { type: "string", description: "Comma-separated changed paths for stale" },
200958
+ reason: { type: "string", description: "Why the source became stale" },
200959
+ query: { type: "string", description: "Search title/source fields" },
200960
+ limit: { type: "string", description: "Maximum cards/events" },
200961
+ json: { type: "boolean", description: "Emit JSON" }
200962
+ },
200963
+ async run({ args }) {
200964
+ const action = args.action || "list";
200965
+ const asJson = !!args.json;
200966
+ try {
200967
+ const context = action === "list" || action === "get" || action === "events" ? await getCliReadContext() : await getCliProjectContext({ searchIndex: false });
200968
+ const { project, dataDir } = context;
200969
+ const store2 = new EvidenceCardStore();
200970
+ await store2.init(dataDir);
200971
+ const limit2 = Math.min(100, Math.max(1, Number(args.limit || 20)));
200972
+ if (action === "sync") {
200973
+ const synced = store2.syncObservations(await getObservationStore().loadByProject(project.id));
200974
+ emitResult({ project, synced: synced.length }, `Synced ${synced.length} evidence card(s).`, asJson);
200975
+ return;
200976
+ }
200977
+ if (action === "stale") {
200978
+ const paths = String(args.paths || "").split(",").map((value) => value.trim()).filter(Boolean);
200979
+ const changed = store2.markStaleForPaths(project.id, paths, String(args.reason || "source file changed"));
200980
+ emitResult({ project, staleMarked: changed, paths }, `Marked ${changed} evidence card(s) stale.`, asJson);
200981
+ return;
200982
+ }
200983
+ const id = String(args.id || "").trim();
200984
+ if (action === "get" || action === "events") {
200985
+ if (!id) {
200986
+ emitError(`--id is required for evidence ${action}.`, asJson);
200987
+ return;
200988
+ }
200989
+ const card = store2.get(project.id, "observation", id);
200990
+ if (!card) {
200991
+ emitError(`Evidence card for observation ${id} was not found.`, asJson);
200992
+ return;
200993
+ }
200994
+ const result = action === "events" ? store2.listEvents(card.id, limit2) : { card, events: store2.listEvents(card.id) };
200995
+ emitResult({ project, result }, JSON.stringify(result, null, 2), asJson);
200996
+ return;
200997
+ }
200998
+ if (action !== "list") {
200999
+ emitError("action must be list, get, sync, stale, or events.", asJson);
201000
+ return;
201001
+ }
201002
+ const query = String(args.query || "").trim().toLowerCase();
201003
+ const cards = store2.list(project.id, { limit: 100 }).filter((card) => !query || [card.title, card.summary, card.sourceRef, card.locator || ""].join(" ").toLowerCase().includes(query)).slice(0, limit2);
201004
+ emitResult({ project, cards }, cards.length === 0 ? "No evidence cards found." : cards.map((card) => `- ${card.candidateId} ${card.title} [${card.freshness}] ${card.sourceRef}`).join("\n"), asJson);
201005
+ } catch (error40) {
201006
+ emitError(error40 instanceof Error ? error40.message : String(error40), asJson);
201007
+ }
201008
+ }
201009
+ });
201010
+ }
201011
+ });
201012
+
201013
+ // src/cli/commands/feedback.ts
201014
+ var feedback_exports2 = {};
201015
+ __export(feedback_exports2, {
201016
+ default: () => feedback_default
201017
+ });
201018
+ var signals, feedback_default;
201019
+ var init_feedback2 = __esm({
201020
+ "src/cli/commands/feedback.ts"() {
201021
+ "use strict";
201022
+ init_esm_shims();
201023
+ init_dist2();
201024
+ init_feedback();
201025
+ init_operator_shared();
201026
+ signals = ["user-correction", "verification-success", "verification-failure", "code-change", "used", "not-used", "code-conflict", "strengthen", "weaken", "revoke"];
201027
+ feedback_default = defineCommand({
201028
+ meta: { name: "feedback", description: "Record and audit memory feedback" },
201029
+ args: {
201030
+ action: { type: "positional", description: "record, show, or audit", required: false },
201031
+ id: { type: "string", description: "Candidate id" },
201032
+ kind: { type: "string", description: "observation, claim, durable-memory, or workflow" },
201033
+ signal: { type: "string", description: "Feedback signal" },
201034
+ source: { type: "string", description: "Source reference for the feedback" },
201035
+ actor: { type: "string", description: "Actor id or human label" },
201036
+ note: { type: "string", description: "Short audit note" },
201037
+ targetEventId: { type: "string", description: "Event id to revoke" },
201038
+ limit: { type: "string", description: "Maximum audit rows" },
201039
+ json: { type: "boolean", description: "Emit JSON" }
201040
+ },
201041
+ async run({ args }) {
201042
+ const action = args.action || "show";
201043
+ const asJson = !!args.json;
201044
+ try {
201045
+ const context = action === "show" || action === "audit" ? await getCliReadContext() : await getCliProjectContext({ searchIndex: false });
201046
+ const { project, dataDir } = context;
201047
+ const id = String(args.id || "").trim();
201048
+ const kind = String(args.kind || "observation").trim() || "observation";
201049
+ if (!id) {
201050
+ emitError("--id is required.", asJson);
201051
+ return;
201052
+ }
201053
+ if (!["observation", "claim", "durable-memory", "workflow"].includes(kind)) {
201054
+ emitError("Invalid --kind.", asJson);
201055
+ return;
201056
+ }
201057
+ const store2 = new MemoryFeedbackStore();
201058
+ await store2.init(dataDir);
201059
+ if (action === "show") {
201060
+ const result2 = store2.getState(project.id, kind, id);
201061
+ emitResult({ project, result: result2 }, JSON.stringify(result2, null, 2), asJson);
201062
+ return;
201063
+ }
201064
+ if (action === "audit") {
201065
+ const result2 = store2.audit(project.id, kind, id, Number(args.limit || 100));
201066
+ emitResult({ project, result: result2 }, JSON.stringify(result2, null, 2), asJson);
201067
+ return;
201068
+ }
201069
+ if (action !== "record") {
201070
+ emitError("action must be record, show, or audit.", asJson);
201071
+ return;
201072
+ }
201073
+ const signal = String(args.signal || "").trim();
201074
+ const sourceRef = String(args.source || "").trim();
201075
+ if (!signals.includes(signal) || !sourceRef) {
201076
+ emitError(`record requires --signal (${signals.join(", ")}) and --source.`, asJson);
201077
+ return;
201078
+ }
201079
+ const result = store2.record({
201080
+ projectId: project.id,
201081
+ candidateKind: kind,
201082
+ candidateId: id,
201083
+ signal,
201084
+ sourceRef,
201085
+ ...args.actor ? { actor: String(args.actor) } : {},
201086
+ ...args.note ? { note: String(args.note) } : {},
201087
+ ...args.targetEventId ? { targetEventId: String(args.targetEventId) } : {}
201088
+ });
201089
+ emitResult({ project, result }, `Recorded ${signal} for ${kind}:${id}; weight=${result.state.weight.toFixed(2)}.`, asJson);
201090
+ } catch (error40) {
201091
+ emitError(error40 instanceof Error ? error40.message : String(error40), asJson);
201092
+ }
201093
+ }
201094
+ });
201095
+ }
201096
+ });
201097
+
200297
201098
  // src/cli/commands/reasoning.ts
200298
201099
  var reasoning_exports = {};
200299
201100
  __export(reasoning_exports, {
@@ -203494,7 +204295,7 @@ __export(planner_exports, {
203494
204295
  materializeTaskGraph: () => materializeTaskGraph,
203495
204296
  seedAutonomousPipeline: () => seedAutonomousPipeline
203496
204297
  });
203497
- import { randomUUID as randomUUID16 } from "crypto";
204298
+ import { randomUUID as randomUUID18 } from "crypto";
203498
204299
  function isPlannerTask(metadata) {
203499
204300
  if (!metadata) return null;
203500
204301
  try {
@@ -203552,7 +204353,7 @@ function seedAutonomousPipeline(teamStore, projectId, config2, opts) {
203552
204353
  const maxIterations = config2.maxIterations ?? 3;
203553
204354
  const taskBudget = config2.taskBudget ?? 15;
203554
204355
  const structuredPlan = opts?.structuredPlan ?? true;
203555
- const pipelineId = randomUUID16();
204356
+ const pipelineId = randomUUID18();
203556
204357
  const meta = {
203557
204358
  plannerType: "plan",
203558
204359
  pipelineId,
@@ -204471,11 +205272,11 @@ __export(receipt_service_exports, {
204471
205272
  buildHandoffReceipt: () => buildHandoffReceipt,
204472
205273
  formatHandoffReceipt: () => formatHandoffReceipt
204473
205274
  });
204474
- import { createHash as createHash14 } from "crypto";
205275
+ import { createHash as createHash16 } from "crypto";
204475
205276
  import { existsSync as existsSync21, readFileSync as readFileSync13 } from "fs";
204476
205277
  import path36 from "path";
204477
205278
  function sha256(value) {
204478
- return `sha256:${createHash14("sha256").update(value).digest("hex")}`;
205279
+ return `sha256:${createHash16("sha256").update(value).digest("hex")}`;
204479
205280
  }
204480
205281
  function hashId(projectId, id) {
204481
205282
  return sha256(`${projectId}:obs:${id}`);
@@ -204696,6 +205497,10 @@ var init_tool_profile = __esm({
204696
205497
  // multimodal support to one additional schema rather than enabling lite.
204697
205498
  memorix_session_start: ["micro", "lite", "team", "full"],
204698
205499
  memorix_media: ["micro", "lite", "team", "full"],
205500
+ // Evidence and feedback are deliberate operator/context-control surfaces;
205501
+ // keep the default micro profile at its nine-tool token budget.
205502
+ memorix_evidence: ["lite", "team", "full"],
205503
+ memorix_feedback: ["lite", "team", "full"],
204699
205504
  // ── lite: extended solo memory surface ────────────────────────────
204700
205505
  memorix_graph_context: ["lite", "team", "full"],
204701
205506
  memorix_timeline: ["lite", "team", "full"],
@@ -216650,9 +217455,9 @@ var init_graph_scope = __esm({
216650
217455
  });
216651
217456
 
216652
217457
  // src/rules/utils.ts
216653
- import { createHash as createHash15 } from "crypto";
217458
+ import { createHash as createHash17 } from "crypto";
216654
217459
  function hashContent2(content) {
216655
- return createHash15("sha256").update(content.trim()).digest("hex").substring(0, 16);
217460
+ return createHash17("sha256").update(content.trim()).digest("hex").substring(0, 16);
216656
217461
  }
216657
217462
  function generateRuleId(source, filePath) {
216658
217463
  const sanitized = filePath.replace(/[\/\\]/g, "-").replace(/^\./, "");
@@ -218356,6 +219161,18 @@ var init_cleanup = __esm({
218356
219161
  });
218357
219162
 
218358
219163
  // src/dashboard/maintenance.ts
219164
+ var maintenance_exports = {};
219165
+ __export(maintenance_exports, {
219166
+ DashboardMaintenanceError: () => DashboardMaintenanceError,
219167
+ executeCleanup: () => executeCleanup,
219168
+ executeConsolidate: () => executeConsolidate,
219169
+ executeDeduplicate: () => executeDeduplicate,
219170
+ executeRetentionArchive: () => executeRetentionArchive,
219171
+ previewCleanup: () => previewCleanup,
219172
+ previewConsolidate: () => previewConsolidate,
219173
+ previewDeduplicate: () => previewDeduplicate,
219174
+ previewRetentionArchive: () => previewRetentionArchive
219175
+ });
218359
219176
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
218360
219177
  function reader(projectId) {
218361
219178
  return { projectId };
@@ -218597,6 +219414,23 @@ var init_maintenance = __esm({
218597
219414
  }
218598
219415
  });
218599
219416
 
219417
+ // src/dashboard/config-provenance.ts
219418
+ function resolveDashboardKeySource({ value, envKeys, configSource }) {
219419
+ for (const envKey of envKeys) {
219420
+ const envValue = process.env[envKey];
219421
+ if (envValue !== value) continue;
219422
+ return getLoadedEnvValue(envKey) === envValue ? ".env" : `env:${envKey}`;
219423
+ }
219424
+ return configSource;
219425
+ }
219426
+ var init_config_provenance = __esm({
219427
+ "src/dashboard/config-provenance.ts"() {
219428
+ "use strict";
219429
+ init_esm_shims();
219430
+ init_dotenv_loader();
219431
+ }
219432
+ });
219433
+
218600
219434
  // src/dashboard/project-classification.ts
218601
219435
  var project_classification_exports = {};
218602
219436
  __export(project_classification_exports, {
@@ -219376,6 +220210,20 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
219376
220210
  sendJson(res, observations2);
219377
220211
  break;
219378
220212
  }
220213
+ case "/evidence": {
220214
+ const observations2 = filterDashboardObservations(
220215
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
220216
+ effectiveProjectId
220217
+ );
220218
+ const evidenceStore = new EvidenceCardStore();
220219
+ await evidenceStore.init(effectiveDataDir);
220220
+ evidenceStore.syncObservations(observations2);
220221
+ const query = url2.searchParams.get("q") || "";
220222
+ const limit2 = Number(url2.searchParams.get("limit") || 20);
220223
+ 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()));
220224
+ sendJson(res, { cards, projectId: effectiveProjectId });
220225
+ break;
220226
+ }
219379
220227
  case "/sessions": {
219380
220228
  const allSessions = await getSessionStore().loadAll();
219381
220229
  const sessions = filterByProject(allSessions, effectiveProjectId);
@@ -219423,8 +220271,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
219423
220271
  filesModified: o4.filesModified
219424
220272
  }));
219425
220273
  const retentionReferenceTime = new Date(now3);
220274
+ const feedback = new MemoryFeedbackStore();
220275
+ await feedback.init(effectiveDataDir).catch(() => void 0);
219426
220276
  const retention = summarizeRetentionProjections(
219427
- observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, { referenceTime: retentionReferenceTime }))
220277
+ observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, {
220278
+ referenceTime: retentionReferenceTime,
220279
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
220280
+ }))
219428
220281
  );
219429
220282
  const retentionSummary = {
219430
220283
  active: retention.active,
@@ -219518,9 +220371,14 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
219518
220371
  effectiveProjectId
219519
220372
  );
219520
220373
  const referenceTime = /* @__PURE__ */ new Date();
220374
+ const feedback = new MemoryFeedbackStore();
220375
+ await feedback.init(effectiveDataDir).catch(() => void 0);
219521
220376
  const rows = observations2.filter((observation) => observation.type !== "probe").map((observation) => ({
219522
220377
  observation,
219523
- retention: projectObservationRetention(observation, { referenceTime })
220378
+ retention: projectObservationRetention(observation, {
220379
+ referenceTime,
220380
+ feedback: feedback.getState(effectiveProjectId, "observation", String(observation.id))
220381
+ })
219524
220382
  })).sort((a4, b4) => b4.retention.displayScore - a4.retention.displayScore);
219525
220383
  const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
219526
220384
  const scored = rows.map(({ observation, retention }) => ({
@@ -219591,23 +220449,13 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
219591
220449
  const os9 = await import("os");
219592
220450
  const { existsSync: existsSync34 } = await import("fs");
219593
220451
  const { join: join34 } = await import("path");
219594
- let yml = {};
219595
220452
  const configProjectRoot = effectiveProjectRoot;
219596
- try {
219597
- const { loadYamlConfig: loadYamlConfig2 } = await Promise.resolve().then(() => (init_yaml_loader(), yaml_loader_exports));
219598
- yml = configProjectRoot ? loadYamlConfig2(configProjectRoot) : loadYamlConfig2(null);
219599
- } catch {
219600
- }
219601
- if (configProjectRoot) {
219602
- try {
219603
- const { loadDotenv: loadDotenv2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
219604
- loadDotenv2(configProjectRoot);
219605
- } catch {
219606
- }
219607
- }
220453
+ const resolved = getResolvedConfig({ projectRoot: configProjectRoot });
219608
220454
  const files = {
219609
220455
  "project memorix.yml": { exists: false, path: "", unavailable: !configProjectRoot },
219610
220456
  "user memorix.yml": { exists: false, path: "" },
220457
+ "project memorix.toml": { exists: false, path: "", unavailable: !configProjectRoot },
220458
+ "user config.toml": { exists: false, path: "" },
219611
220459
  "project .env": { exists: false, path: "", unavailable: !configProjectRoot },
219612
220460
  "user .env": { exists: false, path: "" },
219613
220461
  "legacy config.json": { exists: false, path: "" }
@@ -219617,6 +220465,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
219617
220465
  const paths = {
219618
220466
  "project memorix.yml": configProjectRoot ? join34(configProjectRoot, "memorix.yml") : null,
219619
220467
  "user memorix.yml": join34(home, ".memorix", "memorix.yml"),
220468
+ "project memorix.toml": configProjectRoot ? join34(configProjectRoot, "memorix.toml") : null,
220469
+ "user config.toml": join34(home, ".memorix", "config.toml"),
219620
220470
  "project .env": configProjectRoot ? join34(configProjectRoot, ".env") : null,
219621
220471
  "user .env": join34(home, ".memorix", ".env"),
219622
220472
  "legacy config.json": join34(home, ".memorix", "config.json")
@@ -219631,67 +220481,61 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
219631
220481
  } catch {
219632
220482
  }
219633
220483
  const values = [];
219634
- const getEnvSource = async (envKey, ymlSource) => {
219635
- if (process.env[envKey]) {
219636
- try {
219637
- const { getLoadedEnvFiles: getLoadedEnvFiles2 } = await Promise.resolve().then(() => (init_dotenv_loader(), dotenv_loader_exports));
219638
- const envFiles = getLoadedEnvFiles2();
219639
- if (envFiles.length > 0) return `.env (${envKey})`;
219640
- } catch {
219641
- }
219642
- return `env:${envKey}`;
219643
- }
219644
- return ymlSource ?? "default";
219645
- };
219646
- const llmProvider = process.env.MEMORIX_LLM_PROVIDER || yml.llm?.provider;
219647
- if (llmProvider) values.push({ key: "llm.provider", value: llmProvider, source: await getEnvSource("MEMORIX_LLM_PROVIDER", yml.llm?.provider ? "memorix.yml" : void 0) });
219648
- const llmModel = process.env.MEMORIX_LLM_MODEL || yml.llm?.model;
219649
- if (llmModel) values.push({ key: "llm.model", value: llmModel, source: await getEnvSource("MEMORIX_LLM_MODEL", yml.llm?.model ? "memorix.yml" : void 0) });
219650
- const llmKey = process.env.MEMORIX_LLM_API_KEY || process.env.MEMORIX_API_KEY || yml.llm?.apiKey || process.env.OPENAI_API_KEY;
220484
+ const tomlSource = configProjectRoot && files["project memorix.toml"].exists ? "memorix.toml" : files["user config.toml"].exists ? "config.toml" : "default";
220485
+ const legacySource = files["project memorix.yml"].exists || files["user memorix.yml"].exists ? "memorix.yml" : files["legacy config.json"].exists ? "config.json" : "default";
220486
+ const configSource = resolved.sources.toml.length > 0 ? tomlSource : resolved.sources.legacy.length > 0 ? legacySource : "default";
220487
+ const source = (envKey) => resolveDashboardKeySource({
220488
+ value: process.env[envKey] ?? "",
220489
+ envKeys: [envKey],
220490
+ configSource
220491
+ });
220492
+ if (resolved.memory.llm.provider) values.push({ key: "llm.provider", value: resolved.memory.llm.provider, source: source("MEMORIX_LLM_PROVIDER") });
220493
+ if (resolved.memory.llm.model) values.push({ key: "llm.model", value: resolved.memory.llm.model, source: source("MEMORIX_LLM_MODEL") });
220494
+ const llmKey = resolved.memory.llm.apiKey;
220495
+ const memoryKeyEnv = [
220496
+ "MEMORIX_LLM_API_KEY",
220497
+ "MEMORIX_API_KEY",
220498
+ "OPENAI_API_KEY",
220499
+ "ANTHROPIC_API_KEY",
220500
+ ...resolved.memory.llm.provider === "openrouter" || resolved.memory.llm.baseUrl?.includes("openrouter.ai") ? ["OPENROUTER_API_KEY"] : []
220501
+ ];
219651
220502
  if (llmKey) {
219652
- let src2 = "unknown";
219653
- if (process.env.MEMORIX_LLM_API_KEY) src2 = await getEnvSource("MEMORIX_LLM_API_KEY");
219654
- else if (process.env.MEMORIX_API_KEY) src2 = await getEnvSource("MEMORIX_API_KEY");
219655
- else if (yml.llm?.apiKey) src2 = "memorix.yml (move to .env!)";
219656
- else if (process.env.OPENAI_API_KEY) src2 = await getEnvSource("OPENAI_API_KEY");
220503
+ const src2 = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
219657
220504
  values.push({ key: "llm.apiKey", value: "****" + llmKey.slice(-4), source: src2, sensitive: true });
219658
220505
  } else {
219659
220506
  values.push({ key: "llm.apiKey", value: "not set", source: "none" });
219660
220507
  }
219661
- const agentProvider = process.env.MEMORIX_AGENT_PROVIDER || process.env.MEMORIX_AGENT_LLM_PROVIDER || yml.agent?.provider;
219662
- if (agentProvider) {
219663
- let source;
219664
- if (process.env.MEMORIX_AGENT_PROVIDER) source = await getEnvSource("MEMORIX_AGENT_PROVIDER");
219665
- else if (process.env.MEMORIX_AGENT_LLM_PROVIDER) source = `${await getEnvSource("MEMORIX_AGENT_LLM_PROVIDER")} (legacy)`;
219666
- else source = yml.agent?.provider ? "memorix.yml" : "default";
219667
- values.push({ key: "agent.provider", value: agentProvider, source });
219668
- }
219669
- const agentModel = process.env.MEMORIX_AGENT_MODEL || process.env.MEMORIX_AGENT_LLM_MODEL || yml.agent?.model;
219670
- if (agentModel) {
219671
- let source;
219672
- if (process.env.MEMORIX_AGENT_MODEL) source = await getEnvSource("MEMORIX_AGENT_MODEL");
219673
- else if (process.env.MEMORIX_AGENT_LLM_MODEL) source = `${await getEnvSource("MEMORIX_AGENT_LLM_MODEL")} (legacy)`;
219674
- else source = yml.agent?.model ? "memorix.yml" : "default";
219675
- values.push({ key: "agent.model", value: agentModel, source });
219676
- }
219677
- const agentKey = process.env.MEMORIX_AGENT_API_KEY || process.env.MEMORIX_AGENT_LLM_API_KEY || yml.agent?.apiKey;
220508
+ if (resolved.agent.provider) {
220509
+ values.push({ key: "agent.provider", value: resolved.agent.provider, source: source("MEMORIX_AGENT_PROVIDER") });
220510
+ }
220511
+ if (resolved.agent.model) {
220512
+ values.push({ key: "agent.model", value: resolved.agent.model, source: source("MEMORIX_AGENT_MODEL") });
220513
+ }
220514
+ const agentKey = resolved.agent.apiKey;
219678
220515
  if (agentKey) {
219679
- let src2 = "unknown";
219680
- if (process.env.MEMORIX_AGENT_API_KEY) src2 = await getEnvSource("MEMORIX_AGENT_API_KEY");
219681
- else if (process.env.MEMORIX_AGENT_LLM_API_KEY) src2 = `${await getEnvSource("MEMORIX_AGENT_LLM_API_KEY")} (legacy)`;
219682
- else if (yml.agent?.apiKey) src2 = "memorix.yml (move to .env!)";
220516
+ const src2 = resolveDashboardKeySource({
220517
+ value: agentKey,
220518
+ envKeys: ["MEMORIX_AGENT_API_KEY", "MEMORIX_AGENT_LLM_API_KEY"],
220519
+ configSource
220520
+ });
219683
220521
  values.push({ key: "agent.apiKey", value: "****" + agentKey.slice(-4), source: src2, sensitive: true });
219684
220522
  } else {
219685
- values.push({ key: "agent.apiKey", value: "fallback to llm.apiKey", source: "default" });
219686
- }
219687
- const embProvider = process.env.MEMORIX_EMBEDDING || yml.embedding?.provider || "off";
219688
- values.push({ key: "embedding.provider", value: embProvider, source: await getEnvSource("MEMORIX_EMBEDDING", yml.embedding?.provider ? "memorix.yml" : void 0) });
219689
- values.push({ key: "git.autoHook", value: String(yml.git?.autoHook ?? false), source: yml.git?.autoHook !== void 0 ? "memorix.yml" : "default" });
219690
- values.push({ key: "git.skipMergeCommits", value: String(yml.git?.skipMergeCommits ?? true), source: yml.git?.skipMergeCommits !== void 0 ? "memorix.yml" : "default" });
219691
- if (yml.behavior?.formationMode) values.push({ key: "behavior.formationMode", value: yml.behavior.formationMode, source: "memorix.yml" });
219692
- if (yml.behavior?.sessionInject) values.push({ key: "behavior.sessionInject", value: yml.behavior.sessionInject, source: "memorix.yml" });
219693
- values.push({ key: "server.transport", value: yml.server?.transport || "stdio", source: yml.server?.transport ? "memorix.yml" : "default" });
219694
- values.push({ key: "server.dashboard", value: String(yml.server?.dashboard ?? true), source: yml.server?.dashboard !== void 0 ? "memorix.yml" : "default" });
220523
+ values.push({
220524
+ key: "agent.apiKey",
220525
+ value: "fallback to llm.apiKey",
220526
+ source: llmKey ? resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource }) : "default"
220527
+ });
220528
+ }
220529
+ values.push({ key: "embedding.provider", value: resolved.embedding.provider || "off", source: source("MEMORIX_EMBEDDING") });
220530
+ if (resolved.embedding.model) values.push({ key: "embedding.model", value: resolved.embedding.model, source: source("MEMORIX_EMBEDDING_MODEL") });
220531
+ if (resolved.rerank.provider !== "off") {
220532
+ values.push({ key: "rerank.provider", value: resolved.rerank.provider, source: source("MEMORIX_RERANK_PROVIDER") });
220533
+ if (resolved.rerank.model) values.push({ key: "rerank.model", value: resolved.rerank.model, source: source("MEMORIX_RERANK_MODEL") });
220534
+ }
220535
+ values.push({ key: "git.autoHook", value: String(resolved.git.autoHook ?? false), source: configSource });
220536
+ values.push({ key: "git.skipMergeCommits", value: String(resolved.git.skipMergeCommits ?? true), source: configSource });
220537
+ values.push({ key: "server.transport", value: resolved.server.transport || "stdio", source: configSource });
220538
+ values.push({ key: "server.dashboard", value: String(resolved.server.dashboard ?? true), source: configSource });
219695
220539
  sendJson(res, { files, values });
219696
220540
  break;
219697
220541
  }
@@ -219926,6 +220770,37 @@ function normalizeDashboardTask(task) {
219926
220770
  preferredRole: task.preferred_role ?? task.preferredRole ?? null
219927
220771
  };
219928
220772
  }
220773
+ function emptyTeamSnapshot(mode, scope, status) {
220774
+ return {
220775
+ status,
220776
+ mode,
220777
+ readOnly: true,
220778
+ scope,
220779
+ agents: [],
220780
+ activeCount: 0,
220781
+ recentCount: 0,
220782
+ historicalCount: 0,
220783
+ totalAgents: 0,
220784
+ recentWindowDays: 7,
220785
+ locks: [],
220786
+ tasks: [],
220787
+ availableTasks: 0,
220788
+ sessions: 0,
220789
+ roles: [],
220790
+ roleOccupancy: [],
220791
+ handoffs: [],
220792
+ openTasks: 0,
220793
+ openHandoffs: 0,
220794
+ totalUnread: 0,
220795
+ activeSessions: 0,
220796
+ error: status === "error" ? "Coordination state unavailable" : "Coordination state is incomplete",
220797
+ errorCode: status === "error" ? "COORDINATION_STATE_UNAVAILABLE" : "COORDINATION_STATE_DEGRADED"
220798
+ };
220799
+ }
220800
+ function logTeamSnapshotFailure(scope, status) {
220801
+ const safeScope = scope === "global" ? "global" : "project";
220802
+ console.error("[dashboard] coordination snapshot unavailable", { status, scope: safeScope });
220803
+ }
219929
220804
  async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
219930
220805
  try {
219931
220806
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
@@ -219952,8 +220827,9 @@ async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
219952
220827
  const roleOccupancy = effectiveProjectId ? teamStore.getRoleOccupancy(effectiveProjectId) : [];
219953
220828
  const handoffs = effectiveProjectId ? teamStore.listHandoffs(effectiveProjectId) : [];
219954
220829
  return {
220830
+ status: "ok",
219955
220831
  mode,
219956
- readOnly: mode === "standalone",
220832
+ readOnly: true,
219957
220833
  scope,
219958
220834
  agents: withTier,
219959
220835
  activeCount,
@@ -219974,28 +220850,8 @@ async function buildTeamSnapshot(dataDir, projectId, scope, mode) {
219974
220850
  activeSessions: activeCount
219975
220851
  };
219976
220852
  } catch {
219977
- return {
219978
- mode,
219979
- readOnly: mode === "standalone",
219980
- scope,
219981
- agents: [],
219982
- activeCount: 0,
219983
- recentCount: 0,
219984
- historicalCount: 0,
219985
- totalAgents: 0,
219986
- recentWindowDays: 7,
219987
- locks: [],
219988
- tasks: [],
219989
- availableTasks: 0,
219990
- sessions: 0,
219991
- roles: [],
219992
- roleOccupancy: [],
219993
- handoffs: [],
219994
- openTasks: 0,
219995
- openHandoffs: 0,
219996
- totalUnread: 0,
219997
- activeSessions: 0
219998
- };
220853
+ logTeamSnapshotFailure(scope, "error");
220854
+ return emptyTeamSnapshot(mode, scope, "error");
219999
220855
  }
220000
220856
  }
220001
220857
  function readBody(req) {
@@ -220014,13 +220870,12 @@ function readBody(req) {
220014
220870
  req.on("error", reject);
220015
220871
  });
220016
220872
  }
220017
- async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true, teamInstances, projectRoot = null, projectResolved = true) {
220873
+ async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true, projectRoot = null, projectResolved = true) {
220018
220874
  await initObservationStore(dataDir);
220019
220875
  await initSessionStore(dataDir);
220020
220876
  const resolvedStaticDir = staticDir;
220021
220877
  const baseDir = getBaseDataDir();
220022
- const isControlPlane = !!teamInstances;
220023
- const state = { projectId, projectName, dataDir, projectRoot, projectResolved, mode: isControlPlane ? "control-plane" : "standalone", port };
220878
+ const state = { projectId, projectName, dataDir, projectRoot, projectResolved, mode: "standalone", port };
220024
220879
  const server = createServer(async (req, res) => {
220025
220880
  const url2 = req.url || "/";
220026
220881
  if (url2.startsWith("/api/set-current-project") && req.method === "POST") {
@@ -220042,54 +220897,20 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
220042
220897
  }
220043
220898
  return;
220044
220899
  }
220045
- if (url2.startsWith("/api/team")) {
220046
- if (!teamInstances) {
220047
- const parsedUrl = new URL(url2, `http://127.0.0.1:${port}`);
220048
- const scope = parsedUrl.searchParams.get("scope") || "project";
220049
- sendJson(res, await buildTeamSnapshot(state.dataDir, state.projectId, scope, state.mode));
220050
- return;
220051
- }
220052
- try {
220053
- teamInstances.fileLocks.cleanExpired();
220054
- const agents = teamInstances.registry.listAgents();
220055
- const locks = teamInstances.fileLocks.listLocks();
220056
- const tasks = teamInstances.taskManager.list();
220057
- const available = teamInstances.taskManager.getAvailable();
220058
- let roles = [];
220059
- let roleOccupancy = [];
220060
- let handoffs = [];
220061
- try {
220062
- const { getTeamStore: getTeamStore2, isTeamStoreInitialized: isTeamStoreInitialized2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
220063
- if (isTeamStoreInitialized2()) {
220064
- const teamStore = getTeamStore2();
220065
- const projectId2 = state.projectId;
220066
- roles = teamStore.listRoles(projectId2);
220067
- roleOccupancy = teamStore.getRoleOccupancy(projectId2);
220068
- handoffs = teamStore.listHandoffs(projectId2);
220069
- }
220070
- } catch {
220071
- }
220900
+ const parsedUrl = new URL(url2, `http://127.0.0.1:${port}`);
220901
+ if (parsedUrl.pathname === "/api/team") {
220902
+ if (req.method !== "GET") {
220072
220903
  sendJson(res, {
220073
- agents: agents.map((a4) => ({
220074
- ...a4,
220075
- unread: teamInstances.messageBus.getUnreadCount(a4.id)
220076
- })),
220077
- activeCount: teamInstances.registry.getActiveCount(),
220078
- locks,
220079
- tasks,
220080
- availableTasks: available.length,
220081
- roles,
220082
- roleOccupancy,
220083
- handoffs,
220084
- // Resume data for "Continue this project" area
220085
- openTasks: tasks.filter((t4) => t4.status === "pending" || t4.status === "in_progress").length,
220086
- openHandoffs: handoffs.filter((h5) => h5.handoff_status === "open" || h5.handoffStatus === "open").length,
220087
- totalUnread: agents.reduce((sum, a4) => sum + teamInstances.messageBus.getUnreadCount(a4.id), 0),
220088
- activeSessions: agents.filter((a4) => a4.status === "active").length
220089
- });
220090
- } catch {
220091
- sendJson(res, { agents: [], activeCount: 0, locks: [], tasks: [], availableTasks: 0, roles: [], roleOccupancy: [], handoffs: [] });
220904
+ status: "error",
220905
+ readOnly: true,
220906
+ error: "Coordination status is read-only",
220907
+ errorCode: "COORDINATION_STATUS_READ_ONLY"
220908
+ }, 405);
220909
+ return;
220092
220910
  }
220911
+ const scope = parsedUrl.searchParams.get("scope") || "project";
220912
+ const snapshot = await buildTeamSnapshot(state.dataDir, state.projectId, scope, state.mode);
220913
+ sendJson(res, snapshot, snapshot.status === "error" ? 503 : 200);
220093
220914
  return;
220094
220915
  }
220095
220916
  if (url2.startsWith("/api/")) {
@@ -220110,13 +220931,12 @@ async function startDashboard(dataDir, port, staticDir, projectId, projectName,
220110
220931
  server.listen(port, "127.0.0.1", () => {
220111
220932
  const url2 = `http://127.0.0.1:${port}`;
220112
220933
  const resolvedLabel = projectResolved ? "resolved" : "unresolved";
220113
- const modeLabel = isControlPlane ? "Control Plane" : "Standalone";
220934
+ const modeLabel = "Standalone";
220114
220935
  console.error(` Memorix Dashboard [${modeLabel}]`);
220115
220936
  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`);
220116
220937
  console.error(` Mode: ${modeLabel}`);
220117
220938
  console.error(` Project: ${projectName} (${projectId}) [${resolvedLabel}]`);
220118
220939
  console.error(` Local: ${url2}`);
220119
- if (isControlPlane) console.error(` MCP: ${url2}/mcp`);
220120
220940
  console.error(` Data dir: ${dataDir}`);
220121
220941
  console.error(`
220122
220942
  Press Ctrl+C to stop
@@ -220139,10 +220959,14 @@ var init_server3 = __esm({
220139
220959
  init_dotenv_loader();
220140
220960
  init_yaml_loader();
220141
220961
  init_yaml_loader();
220962
+ init_resolved_config();
220142
220963
  init_graph_scope();
220143
220964
  init_retention();
220144
220965
  init_visibility();
220966
+ init_evidence_store();
220967
+ init_feedback();
220145
220968
  init_maintenance();
220969
+ init_config_provenance();
220146
220970
  MIME_TYPES = {
220147
220971
  ".html": "text/html; charset=utf-8",
220148
220972
  ".css": "text/css; charset=utf-8",
@@ -260174,7 +260998,7 @@ __export(server_exports2, {
260174
260998
  createMemorixServer: () => createMemorixServer,
260175
260999
  shouldAwaitProjectRuntime: () => shouldAwaitProjectRuntime
260176
261000
  });
260177
- import { createHash as createHash16 } from "crypto";
261001
+ import { createHash as createHash18 } from "crypto";
260178
261002
  function formatFormationStageDurations(stageDurationsMs) {
260179
261003
  const orderedStages = ["extract", "resolve", "evaluate"];
260180
261004
  const parts = orderedStages.filter((stage) => stageDurationsMs[stage] !== void 0).map((stage) => `${stage}=${stageDurationsMs[stage]}ms`);
@@ -260256,7 +261080,7 @@ function coerceObjectArray(val) {
260256
261080
  return [];
260257
261081
  }
260258
261082
  function createDeterministicInstanceId(projectId, agentType, agentName) {
260259
- const digest2 = createHash16("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
261083
+ const digest2 = createHash18("sha256").update(projectId).update("\n").update(agentType).update("\n").update(agentName ?? "").digest("hex").slice(0, 24);
260260
261084
  return `auto-${digest2}`;
260261
261085
  }
260262
261086
  function shouldAwaitProjectRuntime(toolName) {
@@ -260548,7 +261372,7 @@ The path should point to a directory containing a .git folder.`
260548
261372
  };
260549
261373
  const server = existingServer ?? new McpServer({
260550
261374
  name: "memorix",
260551
- version: true ? "1.7.3" : "1.0.1"
261375
+ version: true ? "1.8.1" : "1.0.1"
260552
261376
  });
260553
261377
  const originalRegisterTool = server.registerTool.bind(server);
260554
261378
  server.registerTool = ((name, ...args) => {
@@ -263701,7 +264525,7 @@ ${json4}
263701
264525
  }
263702
264526
  }
263703
264527
  console.error(`[memorix] Dashboard staticDir: ${staticDir}`);
263704
- startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, void 0, project.rootPath, projectResolved).then(() => {
264528
+ startDashboard2(projectDir2, portNum, staticDir, project.id, project.name, false, project.rootPath, projectResolved).then(() => {
263705
264529
  dashboardRunning = true;
263706
264530
  }).catch((err) => {
263707
264531
  console.error("[memorix] Dashboard error:", err);
@@ -263727,7 +264551,7 @@ ${json4}
263727
264551
  dashboardRunning = true;
263728
264552
  const { exec: execCmd } = await import("child_process");
263729
264553
  const openCmd = process.platform === "win32" ? `start "" "${url2}"` : process.platform === "darwin" ? `open "${url2}"` : `xdg-open "${url2}"`;
263730
- execCmd(openCmd, () => {
264554
+ execCmd(openCmd, { windowsHide: true }, () => {
263731
264555
  });
263732
264556
  return {
263733
264557
  content: [{
@@ -263751,6 +264575,113 @@ ${json4}
263751
264575
  }
263752
264576
  }
263753
264577
  );
264578
+ server.registerTool(
264579
+ "memorix_evidence",
264580
+ {
264581
+ title: "Evidence Cards",
264582
+ 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.",
264583
+ inputSchema: {
264584
+ action: external_exports2.enum(["list", "get", "sync", "stale", "events"]).describe("Evidence operation"),
264585
+ candidateKind: external_exports2.enum(["observation", "code", "git", "test", "document", "workflow", "graph"]).optional().default("observation"),
264586
+ candidateId: external_exports2.string().optional().describe("Candidate id, usually an observation id"),
264587
+ query: external_exports2.string().optional().describe("Optional title/source query for list"),
264588
+ paths: external_exports2.array(external_exports2.string()).max(200).optional().describe("Changed source paths for stale"),
264589
+ reason: external_exports2.string().max(500).optional().describe("Why evidence became stale"),
264590
+ limit: external_exports2.number().int().positive().max(100).optional().default(20)
264591
+ }
264592
+ },
264593
+ async ({ action, candidateKind, candidateId, query, paths, reason, limit: limit2 }) => {
264594
+ const unresolved = requireResolvedProject("inspect evidence cards for the current project");
264595
+ if (unresolved) return unresolved;
264596
+ const cards = new EvidenceCardStore();
264597
+ await cards.init(projectDir2);
264598
+ if (action === "sync") {
264599
+ const observations2 = await getObservationStore().loadByProject(project.id);
264600
+ const synced = cards.syncObservations(observations2);
264601
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, synced: synced.length, cards: synced.slice(0, limit2 ?? 20) }, null, 2) }] };
264602
+ }
264603
+ if (action === "stale") {
264604
+ const changed = cards.markStaleForPaths(project.id, paths ?? [], reason ?? "source file changed");
264605
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, staleMarked: changed, paths: paths ?? [] }, null, 2) }] };
264606
+ }
264607
+ if (action === "get") {
264608
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence get." }], isError: true };
264609
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
264610
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
264611
+ return { content: [{ type: "text", text: JSON.stringify({ card, events: cards.listEvents(card.id), projectId: project.id }, null, 2) }] };
264612
+ }
264613
+ if (action === "events") {
264614
+ if (!candidateId?.trim()) return { content: [{ type: "text", text: "candidateId is required for evidence events." }], isError: true };
264615
+ const card = cards.get(project.id, candidateKind ?? "observation", candidateId.trim());
264616
+ if (!card) return { content: [{ type: "text", text: "Evidence card not found." }], isError: true };
264617
+ return { content: [{ type: "text", text: JSON.stringify(cards.listEvents(card.id, limit2), null, 2) }] };
264618
+ }
264619
+ const needle = query?.trim().toLowerCase();
264620
+ 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);
264621
+ return { content: [{ type: "text", text: JSON.stringify({ projectId: project.id, cards: listed }, null, 2) }] };
264622
+ }
264623
+ );
264624
+ server.registerTool(
264625
+ "memorix_feedback",
264626
+ {
264627
+ title: "Memory Feedback",
264628
+ 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.",
264629
+ inputSchema: {
264630
+ action: external_exports2.enum(["record", "show", "audit"]).describe("Feedback operation"),
264631
+ candidateKind: external_exports2.enum(["observation", "claim", "durable-memory", "workflow"]).optional().default("observation"),
264632
+ candidateId: external_exports2.string().describe("Memory or knowledge candidate id"),
264633
+ signal: external_exports2.enum(["user-correction", "verification-success", "verification-failure", "code-change", "used", "not-used", "code-conflict", "strengthen", "weaken", "revoke"]).optional(),
264634
+ sourceRef: external_exports2.string().max(500).optional().describe("Source of this feedback, such as test:run-42 or user:chat"),
264635
+ actor: external_exports2.string().max(200).optional(),
264636
+ note: external_exports2.string().max(2e3).optional(),
264637
+ targetEventId: external_exports2.string().optional().describe("Existing event id when action=record and signal=revoke"),
264638
+ limit: external_exports2.number().int().positive().max(200).optional().default(100)
264639
+ }
264640
+ },
264641
+ async ({ action, candidateKind, candidateId, signal, sourceRef, actor, note, targetEventId, limit: limit2 }) => {
264642
+ const unresolved = requireResolvedProject("record memory feedback for the current project");
264643
+ if (unresolved) return unresolved;
264644
+ const feedback = new MemoryFeedbackStore();
264645
+ await feedback.init(projectDir2);
264646
+ const kind = candidateKind ?? "observation";
264647
+ if (action === "show") {
264648
+ return { content: [{ type: "text", text: JSON.stringify(feedback.getState(project.id, kind, candidateId), null, 2) }] };
264649
+ }
264650
+ if (action === "audit") {
264651
+ return { content: [{ type: "text", text: JSON.stringify(feedback.audit(project.id, kind, candidateId, limit2), null, 2) }] };
264652
+ }
264653
+ if (!signal || !sourceRef?.trim()) {
264654
+ return { content: [{ type: "text", text: "record requires signal and sourceRef." }], isError: true };
264655
+ }
264656
+ try {
264657
+ const result = feedback.record({
264658
+ projectId: project.id,
264659
+ candidateKind: kind,
264660
+ candidateId,
264661
+ signal,
264662
+ sourceRef,
264663
+ ...actor || currentAgentId ? { actor: actor ?? currentAgentId } : {},
264664
+ ...note ? { note } : {},
264665
+ ...targetEventId ? { targetEventId } : {}
264666
+ });
264667
+ if (kind === "observation") {
264668
+ const cards = new EvidenceCardStore();
264669
+ await cards.init(projectDir2);
264670
+ const card = cards.get(project.id, "observation", candidateId);
264671
+ if (card) {
264672
+ const verification = signal === "verification-success" || signal === "strengthen" ? "verified" : signal === "user-correction" || signal === "code-conflict" || signal === "weaken" ? "conflicted" : card.verification;
264673
+ cards.recordEvent(project.id, card.id, `feedback:${signal}`, note);
264674
+ if (verification !== card.verification) {
264675
+ cards.setVerification(project.id, card.id, verification);
264676
+ }
264677
+ }
264678
+ }
264679
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
264680
+ } catch (error40) {
264681
+ return { content: [{ type: "text", text: error40 instanceof Error ? error40.message : String(error40) }], isError: true };
264682
+ }
264683
+ }
264684
+ );
263754
264685
  if (teamFeaturesEnabled) {
263755
264686
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
263756
264687
  initTeamStoreForProject = initTeamStore2;
@@ -263936,7 +264867,7 @@ ${lines.join("\n")}` }] };
263936
264867
  server.registerTool(
263937
264868
  "team_task",
263938
264869
  {
263939
- title: "Task Board",
264870
+ title: "Task Status",
263940
264871
  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.',
263941
264872
  inputSchema: {
263942
264873
  action: external_exports2.enum(["create", "claim", "complete", "list"]).describe("Operation to perform"),
@@ -264806,6 +265737,8 @@ var init_server4 = __esm({
264806
265737
  init_launch_root();
264807
265738
  init_aliases();
264808
265739
  init_persistence();
265740
+ init_evidence_store();
265741
+ init_feedback();
264809
265742
  init_syncer();
264810
265743
  init_engine5();
264811
265744
  init_tool_profile();
@@ -265292,6 +266225,35 @@ var init_serve = __esm({
265292
266225
  }
265293
266226
  });
265294
266227
 
266228
+ // src/server/mcp-diagnostics.ts
266229
+ function getMcpDiagnostics() {
266230
+ return {
266231
+ serverName: MCP_SERVER_NAME,
266232
+ protocol: {
266233
+ current: CURRENT_MCP_PROTOCOL_VERSION,
266234
+ legacy: LEGACY_MCP_PROTOCOL_VERSION,
266235
+ statelessRequests: "supported",
266236
+ statefulStreamableHttp: "supported"
266237
+ },
266238
+ capabilities: {
266239
+ tasks: "unsupported",
266240
+ polling: "unsupported",
266241
+ listCache: "supported",
266242
+ explicitProjectHandle: "supported"
266243
+ }
266244
+ };
266245
+ }
266246
+ var CURRENT_MCP_PROTOCOL_VERSION, LEGACY_MCP_PROTOCOL_VERSION, MCP_SERVER_NAME;
266247
+ var init_mcp_diagnostics = __esm({
266248
+ "src/server/mcp-diagnostics.ts"() {
266249
+ "use strict";
266250
+ init_esm_shims();
266251
+ CURRENT_MCP_PROTOCOL_VERSION = "2026-07-28";
266252
+ LEGACY_MCP_PROTOCOL_VERSION = "2024-11-05";
266253
+ MCP_SERVER_NAME = "io.github.AVIDS2/memorix";
266254
+ }
266255
+ });
266256
+
265295
266257
  // src/cli/port.ts
265296
266258
  function parseTcpPort(value, defaultPort) {
265297
266259
  const raw = value?.trim() || String(defaultPort);
@@ -267388,6 +268350,79 @@ var init_streamableHttp = __esm({
267388
268350
  }
267389
268351
  });
267390
268352
 
268353
+ // src/server/mcp-binding-store.ts
268354
+ var mcp_binding_store_exports = {};
268355
+ __export(mcp_binding_store_exports, {
268356
+ McpBindingStore: () => McpBindingStore
268357
+ });
268358
+ import { randomUUID as randomUUID19 } from "crypto";
268359
+ function rowToBinding(row) {
268360
+ return {
268361
+ handleId: String(row.handle_id),
268362
+ projectId: String(row.project_id),
268363
+ projectRoot: String(row.project_root),
268364
+ dataDir: String(row.data_dir),
268365
+ createdAt: String(row.created_at),
268366
+ lastUsedAt: String(row.last_used_at),
268367
+ ...row.expires_at ? { expiresAt: String(row.expires_at) } : {}
268368
+ };
268369
+ }
268370
+ var McpBindingStore;
268371
+ var init_mcp_binding_store = __esm({
268372
+ "src/server/mcp-binding-store.ts"() {
268373
+ "use strict";
268374
+ init_esm_shims();
268375
+ init_sqlite_db();
268376
+ McpBindingStore = class {
268377
+ db = null;
268378
+ async init(dataDir) {
268379
+ this.db = getDatabase(dataDir);
268380
+ }
268381
+ requireDb() {
268382
+ if (!this.db) throw new Error("McpBindingStore is not initialized.");
268383
+ return this.db;
268384
+ }
268385
+ create(input) {
268386
+ const now3 = /* @__PURE__ */ new Date();
268387
+ const binding = {
268388
+ handleId: `mxh_${randomUUID19().replace(/-/g, "")}`,
268389
+ projectId: input.projectId,
268390
+ projectRoot: input.projectRoot,
268391
+ dataDir: input.dataDir,
268392
+ createdAt: now3.toISOString(),
268393
+ lastUsedAt: now3.toISOString(),
268394
+ ...input.ttlMs && input.ttlMs > 0 ? { expiresAt: new Date(now3.getTime() + input.ttlMs).toISOString() } : {}
268395
+ };
268396
+ this.requireDb().prepare(`
268397
+ INSERT INTO mcp_bindings (handle_id, project_id, project_root, data_dir, created_at, last_used_at, expires_at)
268398
+ VALUES (?, ?, ?, ?, ?, ?, ?)
268399
+ `).run(binding.handleId, binding.projectId, binding.projectRoot, binding.dataDir, binding.createdAt, binding.lastUsedAt, binding.expiresAt ?? null);
268400
+ return binding;
268401
+ }
268402
+ get(handleId) {
268403
+ const row = this.requireDb().prepare(`SELECT * FROM mcp_bindings WHERE handle_id = ?`).get(handleId);
268404
+ if (!row) return void 0;
268405
+ const binding = rowToBinding(row);
268406
+ if (binding.expiresAt && Date.parse(binding.expiresAt) <= Date.now()) {
268407
+ this.delete(handleId);
268408
+ return void 0;
268409
+ }
268410
+ return binding;
268411
+ }
268412
+ touch(handleId) {
268413
+ const binding = this.get(handleId);
268414
+ if (!binding) return void 0;
268415
+ const lastUsedAt = (/* @__PURE__ */ new Date()).toISOString();
268416
+ this.requireDb().prepare(`UPDATE mcp_bindings SET last_used_at = ? WHERE handle_id = ?`).run(lastUsedAt, handleId);
268417
+ return { ...binding, lastUsedAt };
268418
+ }
268419
+ delete(handleId) {
268420
+ this.requireDb().prepare(`DELETE FROM mcp_bindings WHERE handle_id = ?`).run(handleId);
268421
+ }
268422
+ };
268423
+ }
268424
+ });
268425
+
267391
268426
  // src/runtime/control-plane-maintenance.ts
267392
268427
  var control_plane_maintenance_exports = {};
267393
268428
  __export(control_plane_maintenance_exports, {
@@ -267455,6 +268490,7 @@ var serve_http_exports = {};
267455
268490
  __export(serve_http_exports, {
267456
268491
  DEFAULT_SESSION_TIMEOUT_MS: () => DEFAULT_SESSION_TIMEOUT_MS,
267457
268492
  EXPIRED_SESSION_TTL_MS: () => EXPIRED_SESSION_TTL_MS,
268493
+ MCP_SDK_COMPAT_PROTOCOL_VERSION: () => MCP_SDK_COMPAT_PROTOCOL_VERSION,
267458
268494
  _testing: () => _testing2,
267459
268495
  default: () => serve_http_default,
267460
268496
  parseSessionTimeoutMs: () => parseSessionTimeoutMs,
@@ -267476,7 +268512,7 @@ function resolveServeHttpDefaults(argsPort, configured) {
267476
268512
  const transportNotice = configured.transport === "stdio" ? '[memorix] config server.transport = "stdio"; serve-http continues on HTTP (the explicit command wins).' : void 0;
267477
268513
  return { port, transportNotice };
267478
268514
  }
267479
- var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, serve_http_default, _testing2;
268515
+ var DEFAULT_SESSION_TIMEOUT_MS, EXPIRED_SESSION_TTL_MS, MCP_SDK_COMPAT_PROTOCOL_VERSION, serve_http_default, _testing2;
267480
268516
  var init_serve_http = __esm({
267481
268517
  "src/cli/commands/serve-http.ts"() {
267482
268518
  "use strict";
@@ -267486,10 +268522,12 @@ var init_serve_http = __esm({
267486
268522
  init_graph_scope();
267487
268523
  init_retention();
267488
268524
  init_visibility();
268525
+ init_mcp_diagnostics();
267489
268526
  init_port();
267490
268527
  init_mcp_root_path();
267491
268528
  DEFAULT_SESSION_TIMEOUT_MS = 12 * 60 * 60 * 1e3;
267492
268529
  EXPIRED_SESSION_TTL_MS = 10 * 60 * 1e3;
268530
+ MCP_SDK_COMPAT_PROTOCOL_VERSION = "2025-11-25";
267493
268531
  serve_http_default = defineCommand({
267494
268532
  meta: {
267495
268533
  name: "serve-http",
@@ -267519,7 +268557,7 @@ var init_serve_http = __esm({
267519
268557
  },
267520
268558
  run: async ({ args }) => {
267521
268559
  const { createServer: createServer3 } = await import("http");
267522
- const { randomUUID: randomUUID17 } = await import("crypto");
268560
+ const { randomUUID: randomUUID20 } = await import("crypto");
267523
268561
  const { StreamableHTTPServerTransport: StreamableHTTPServerTransport2 } = await Promise.resolve().then(() => (init_streamableHttp(), streamableHttp_exports));
267524
268562
  const { isInitializeRequest: isInitializeRequest2 } = await Promise.resolve().then(() => (init_types8(), types_exports3));
267525
268563
  const { createMemorixServer: createMemorixServer2 } = await Promise.resolve().then(() => (init_server4(), server_exports2));
@@ -267527,6 +268565,8 @@ var init_serve_http = __esm({
267527
268565
  const { findGitInSubdirs: findGitInSubdirs2 } = await Promise.resolve().then(() => (init_detector(), detector_exports));
267528
268566
  const { writeFileSync: writeFileSync12, mkdirSync: mkdirSync12 } = await import("fs");
267529
268567
  const { homedir: homedir36 } = await import("os");
268568
+ const { McpBindingStore: McpBindingStore2 } = await Promise.resolve().then(() => (init_mcp_binding_store(), mcp_binding_store_exports));
268569
+ const { MemoryFeedbackStore: MemoryFeedbackStore2 } = await Promise.resolve().then(() => (init_feedback(), feedback_exports));
267530
268570
  let configuredDefaults = {};
267531
268571
  try {
267532
268572
  const { getServerConfig: getServerConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -267579,9 +268619,8 @@ var init_serve_http = __esm({
267579
268619
  teamStoreCache.set(dataDir, store2);
267580
268620
  return store2;
267581
268621
  }
267582
- const detectedProj = serveResolution.detectedProject ?? detectorMod.detectProject(projectRoot);
267583
- const teamDataDir = await persistMod.getProjectDataDir(detectedProj?.id ?? "__unresolved__");
267584
- const sharedTeamStore = await getTeamStore2(teamDataDir);
268622
+ const statelessBindingStore = new McpBindingStore2();
268623
+ await statelessBindingStore.init(persistMod.getBaseDataDir());
267585
268624
  const sessions = /* @__PURE__ */ new Map();
267586
268625
  const sessionLastActivity = /* @__PURE__ */ new Map();
267587
268626
  const expiredSessions = /* @__PURE__ */ new Map();
@@ -267721,12 +268760,91 @@ var init_serve_http = __esm({
267721
268760
  res.setHeader("Vary", "Origin");
267722
268761
  }
267723
268762
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
267724
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Last-Event-Id");
267725
- res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
268763
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id, Mcp-Project-Handle, Mcp-Project-Root, Mcp-Stateless, Last-Event-Id");
268764
+ res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id, Mcp-Project-Handle, Mcp-Protocol-Version, Mcp-Method, Mcp-Name");
268765
+ res.setHeader("Mcp-Protocol-Version", req.headers["mcp-protocol-version"] || CURRENT_MCP_PROTOCOL_VERSION);
268766
+ res.setHeader("Mcp-Method", req.method || "UNKNOWN");
268767
+ res.setHeader("Mcp-Name", MCP_SERVER_NAME);
268768
+ }
268769
+ async function handleStatelessPost(req, res, body) {
268770
+ const handleId = typeof req.headers["mcp-project-handle"] === "string" ? req.headers["mcp-project-handle"] : void 0;
268771
+ const persisted = handleId ? statelessBindingStore.touch(handleId) : void 0;
268772
+ if (handleId && !persisted) {
268773
+ res.writeHead(404, { "Content-Type": "application/json" });
268774
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32001, message: "MCP project handle not found or expired; initialize again." }, id: null }));
268775
+ return;
268776
+ }
268777
+ const requestedRoot = typeof req.headers["mcp-project-root"] === "string" ? req.headers["mcp-project-root"] : void 0;
268778
+ const root = persisted?.projectRoot ?? requestedRoot ?? projectRoot;
268779
+ if (!persisted && !isInitializeRequest2(body)) {
268780
+ res.writeHead(400, { "Content-Type": "application/json" });
268781
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Stateless MCP requests require Mcp-Project-Handle; initialize first." }, id: null }));
268782
+ return;
268783
+ }
268784
+ const { createMemorixServer: createMemorixServer3 } = await Promise.resolve().then(() => (init_server4(), server_exports2));
268785
+ const { createProjectBindingController: createProjectBindingController3 } = await Promise.resolve().then(() => (init_request_context(), request_context_exports));
268786
+ const binding = createProjectBindingController3(root);
268787
+ const { server, projectId, handleTransportClose } = await createMemorixServer3(
268788
+ root,
268789
+ void 0,
268790
+ void 0,
268791
+ {
268792
+ allowUntrackedFallback: false,
268793
+ deferProjectInitUntilBound: true,
268794
+ deferProjectRuntimeInit: true,
268795
+ dashboardMode: "control-plane",
268796
+ dashboardPort: port,
268797
+ toolProfile,
268798
+ projectBinding: binding
268799
+ }
268800
+ );
268801
+ const transport = new StreamableHTTPServerTransport2({
268802
+ // No Mcp-Session-Id: the protocol state is carried by our explicit
268803
+ // durable project handle and reconstructed on every request.
268804
+ sessionIdGenerator: void 0,
268805
+ enableJsonResponse: true
268806
+ });
268807
+ try {
268808
+ await server.connect(transport);
268809
+ let effectiveHandle = persisted;
268810
+ if (!effectiveHandle && projectId !== "__unresolved__") {
268811
+ effectiveHandle = statelessBindingStore.create({
268812
+ projectId,
268813
+ projectRoot: root,
268814
+ dataDir: await persistMod.getProjectDataDir(projectId)
268815
+ });
268816
+ }
268817
+ if (effectiveHandle) res.setHeader("Mcp-Project-Handle", effectiveHandle.handleId);
268818
+ const sdkBody = body && typeof body === "object" ? JSON.parse(JSON.stringify(body)) : body;
268819
+ const sdkRequest = sdkBody;
268820
+ if (sdkRequest && typeof sdkRequest === "object" && sdkRequest.method === "initialize" && sdkRequest.params) {
268821
+ sdkRequest.params.protocolVersion = MCP_SDK_COMPAT_PROTOCOL_VERSION;
268822
+ }
268823
+ if (req.headers["mcp-protocol-version"]) {
268824
+ req.headers["mcp-protocol-version"] = MCP_SDK_COMPAT_PROTOCOL_VERSION;
268825
+ for (let i4 = 0; i4 < req.rawHeaders.length; i4 += 2) {
268826
+ if (req.rawHeaders[i4].toLowerCase() === "mcp-protocol-version") {
268827
+ req.rawHeaders[i4 + 1] = MCP_SDK_COMPAT_PROTOCOL_VERSION;
268828
+ break;
268829
+ }
268830
+ }
268831
+ }
268832
+ patchMcpResponse(res);
268833
+ await transport.handleRequest(req, res, sdkBody);
268834
+ } finally {
268835
+ handleTransportClose();
268836
+ await transport.close().catch(() => void 0);
268837
+ }
267726
268838
  }
267727
268839
  async function handlePost(req, res) {
267728
268840
  const sessionId = req.headers["mcp-session-id"];
267729
268841
  const body = await parseBody(req);
268842
+ const requestedProtocol = req.headers["mcp-protocol-version"];
268843
+ const statelessRequested = req.headers["mcp-stateless"] === "true" || requestedProtocol === CURRENT_MCP_PROTOCOL_VERSION;
268844
+ if (!sessionId && statelessRequested) {
268845
+ await handleStatelessPost(req, res, body);
268846
+ return;
268847
+ }
267730
268848
  if (sessionId && sessions.has(sessionId)) {
267731
268849
  sessionLastActivity.set(sessionId, Date.now());
267732
268850
  patchMcpResponse(res);
@@ -267740,7 +268858,7 @@ var init_serve_http = __esm({
267740
268858
  if (!sessionId && isInitializeRequest2(body)) {
267741
268859
  let createdState = null;
267742
268860
  const transport = new StreamableHTTPServerTransport2({
267743
- sessionIdGenerator: () => randomUUID17(),
268861
+ sessionIdGenerator: () => randomUUID20(),
267744
268862
  enableJsonResponse: true,
267745
268863
  onsessioninitialized: (sid) => {
267746
268864
  if (createdState) sessions.set(sid, createdState);
@@ -267773,7 +268891,7 @@ var init_serve_http = __esm({
267773
268891
  const { server, switchProject, handleTransportClose } = await createMemorixServer2(
267774
268892
  projectRoot,
267775
268893
  void 0,
267776
- { teamStore: sharedTeamStore },
268894
+ void 0,
267777
268895
  {
267778
268896
  allowUntrackedFallback: false,
267779
268897
  deferProjectInitUntilBound: true,
@@ -267915,6 +269033,18 @@ var init_serve_http = __esm({
267915
269033
  { projectId }
267916
269034
  );
267917
269035
  }
269036
+ async function loadProjectFeedback(dataDir, projectId) {
269037
+ const result = /* @__PURE__ */ new Map();
269038
+ try {
269039
+ const feedback = new MemoryFeedbackStore2();
269040
+ await feedback.init(dataDir);
269041
+ for (const state of feedback.listStates(projectId, "observation")) {
269042
+ result.set(state.candidateId, { weight: state.weight, status: state.status });
269043
+ }
269044
+ } catch {
269045
+ }
269046
+ return result;
269047
+ }
267918
269048
  const cliDir = pathModule.default.dirname(fileURLToPath10(import.meta.url));
267919
269049
  const dashStaticDir = pathModule.default.join(cliDir, "..", "dashboard", "static");
267920
269050
  const MIME_TYPES2 = {
@@ -267973,9 +269103,50 @@ var init_serve_http = __esm({
267973
269103
  sendJson2({ ok: true, scope: "per-request" });
267974
269104
  return;
267975
269105
  }
269106
+ const maintenanceMatch = apiPath.match(/^\/maintenance\/(cleanup|deduplicate|consolidate|retention)\/(preview|execute)$/);
269107
+ if (maintenanceMatch) {
269108
+ if (req.method !== "POST") {
269109
+ sendJson2({ error: "Maintenance actions require POST." }, 405);
269110
+ return;
269111
+ }
269112
+ const bodyValue = await parseBody(req);
269113
+ const body = bodyValue && typeof bodyValue === "object" && !Array.isArray(bodyValue) ? bodyValue : {};
269114
+ const { projectId: maintenanceProjectId, dataDir: maintenanceDataDir } = await resolveRequestProject(url2);
269115
+ const maintenanceStore = await getDashboardObservationStore(maintenanceDataDir);
269116
+ const context = {
269117
+ dataDir: maintenanceDataDir,
269118
+ projectId: maintenanceProjectId,
269119
+ projectRoot: maintenanceProjectId === defaultProject.id ? projectRoot : null,
269120
+ store: maintenanceStore
269121
+ };
269122
+ const maintenance = await Promise.resolve().then(() => (init_maintenance(), maintenance_exports));
269123
+ const action = maintenanceMatch[1];
269124
+ const phase = maintenanceMatch[2];
269125
+ let result;
269126
+ if (action === "cleanup") {
269127
+ result = phase === "preview" ? await maintenance.previewCleanup(context, body.includeNoise === true) : await maintenance.executeCleanup(context, body.payload, body.token);
269128
+ } else if (action === "deduplicate") {
269129
+ result = phase === "preview" ? await maintenance.previewDeduplicate(context) : await maintenance.executeDeduplicate(context, body.payload, body.token);
269130
+ } else if (action === "consolidate") {
269131
+ result = phase === "preview" ? await maintenance.previewConsolidate(context) : await maintenance.executeConsolidate(context, body.payload, body.token);
269132
+ } else {
269133
+ result = phase === "preview" ? await maintenance.previewRetentionArchive(context) : await maintenance.executeRetentionArchive(context, body.payload, body.token);
269134
+ }
269135
+ sendJson2(result);
269136
+ return;
269137
+ }
267976
269138
  if (apiPath === "/team") {
267977
- const { projectId: teamProjectId } = await resolveRequestProject(url2);
267978
- const ts2 = sharedTeamStore;
269139
+ if (req.method !== "GET") {
269140
+ sendJson2({
269141
+ status: "error",
269142
+ readOnly: true,
269143
+ error: "Coordination status is read-only",
269144
+ errorCode: "COORDINATION_STATUS_READ_ONLY"
269145
+ }, 405);
269146
+ return;
269147
+ }
269148
+ const { projectId: teamProjectId, dataDir: teamDataDir } = await resolveRequestProject(url2);
269149
+ const ts2 = await getTeamStore2(teamDataDir);
267979
269150
  const scope = url2.searchParams.get("scope") || "project";
267980
269151
  const effectiveTeamProjectId = scope === "global" ? void 0 : teamProjectId;
267981
269152
  const agents = effectiveTeamProjectId ? ts2.listAgents(effectiveTeamProjectId) : ts2.listAllAgents();
@@ -268038,8 +269209,9 @@ var init_serve_http = __esm({
268038
269209
  const handoffs = effectiveTeamProjectId ? ts2.listHandoffs(effectiveTeamProjectId) : [];
268039
269210
  const openHandoffs = handoffs.filter((handoff) => handoff.status !== "completed").length;
268040
269211
  sendJson2({
269212
+ status: "ok",
268041
269213
  mode: "control-plane",
268042
- readOnly: false,
269214
+ readOnly: true,
268043
269215
  scope,
268044
269216
  agents: withTier,
268045
269217
  // Primary headline number
@@ -268076,6 +269248,7 @@ var init_serve_http = __esm({
268076
269248
  await initGraphStore2(statsDataDir);
268077
269249
  const graph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
268078
269250
  const observations2 = await loadDashboardProjectObservations(statsDataDir, statsProjectId, "active");
269251
+ const feedback = await loadProjectFeedback(statsDataDir, statsProjectId);
268079
269252
  const statsStore = await getDashboardObservationStore(statsDataDir);
268080
269253
  const nextId2 = await statsStore.loadIdCounter();
268081
269254
  const typeCounts = {};
@@ -268110,7 +269283,10 @@ var init_serve_http = __esm({
268110
269283
  filesModified: o4.filesModified
268111
269284
  }));
268112
269285
  const retention = summarizeRetentionProjections(
268113
- observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, { referenceTime: new Date(now3) }))
269286
+ observations2.filter((observation) => observation.type !== "probe").map((observation) => projectObservationRetention(observation, {
269287
+ referenceTime: new Date(now3),
269288
+ feedback: feedback.get(String(observation.id))
269289
+ }))
268114
269290
  );
268115
269291
  const retentionSummary = {
268116
269292
  active: retention.active,
@@ -268217,6 +269393,19 @@ var init_serve_http = __esm({
268217
269393
  sendJson2(await loadDashboardProjectObservations(obsDataDir, obsProjectId, "active"));
268218
269394
  return;
268219
269395
  }
269396
+ if (apiPath === "/evidence") {
269397
+ const { projectId: evidenceProjectId, dataDir: evidenceDataDir } = await resolveRequestProject(url2);
269398
+ const { EvidenceCardStore: EvidenceCardStore2 } = await Promise.resolve().then(() => (init_evidence_store(), evidence_store_exports));
269399
+ const evidenceStore = new EvidenceCardStore2();
269400
+ await evidenceStore.init(evidenceDataDir);
269401
+ const observations2 = await loadDashboardProjectObservations(evidenceDataDir, evidenceProjectId, "active");
269402
+ evidenceStore.syncObservations(observations2);
269403
+ const query = (url2.searchParams.get("q") || "").trim().toLowerCase();
269404
+ const limit2 = Math.min(100, Math.max(1, Number(url2.searchParams.get("limit") || 20)));
269405
+ const cards = evidenceStore.list(evidenceProjectId, { limit: 100 }).filter((card) => !query || [card.title, card.summary, card.sourceRef, card.locator ?? ""].join(" ").toLowerCase().includes(query)).slice(0, limit2).map((card) => ({ ...card, events: evidenceStore.listEvents(card.id, 8) }));
269406
+ sendJson2({ projectId: evidenceProjectId, cards });
269407
+ return;
269408
+ }
268220
269409
  if (apiPath === "/graph") {
268221
269410
  const { projectId: graphProjectId, dataDir: graphDataDir } = await resolveRequestProject(url2);
268222
269411
  const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
@@ -268238,10 +269427,11 @@ var init_serve_http = __esm({
268238
269427
  if (apiPath === "/retention") {
268239
269428
  const { projectId: retProjectId, dataDir: retDataDir } = await resolveRequestProject(url2);
268240
269429
  const observations2 = await loadDashboardProjectObservations(retDataDir, retProjectId, "active");
269430
+ const feedback = await loadProjectFeedback(retDataDir, retProjectId);
268241
269431
  const referenceTime = /* @__PURE__ */ new Date();
268242
269432
  const rows = observations2.filter((observation) => observation.type !== "probe").map((observation) => ({
268243
269433
  observation,
268244
- retention: projectObservationRetention(observation, { referenceTime })
269434
+ retention: projectObservationRetention(observation, { referenceTime, feedback: feedback.get(String(observation.id)) })
268245
269435
  })).sort((a4, b4) => b4.retention.displayScore - a4.retention.displayScore);
268246
269436
  const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
268247
269437
  const items = rows.map(({ observation, retention }) => ({
@@ -268594,6 +269784,23 @@ var init_serve_http = __esm({
268594
269784
  }
268595
269785
  sendJson2({ error: "Not found" }, 404);
268596
269786
  } catch (err) {
269787
+ if (apiPath === "/team") {
269788
+ const scope = url2.searchParams.get("scope") === "global" ? "global" : "project";
269789
+ console.error("[memorix] coordination snapshot unavailable", { status: "error", scope });
269790
+ sendJson2({
269791
+ status: "error",
269792
+ mode: "control-plane",
269793
+ readOnly: true,
269794
+ scope,
269795
+ agents: [],
269796
+ locks: [],
269797
+ tasks: [],
269798
+ handoffs: [],
269799
+ error: "Coordination state unavailable",
269800
+ errorCode: "COORDINATION_STATE_UNAVAILABLE"
269801
+ }, 503);
269802
+ return;
269803
+ }
268597
269804
  sendJson2({ error: err instanceof Error ? err.message : "Unknown error" }, 500);
268598
269805
  }
268599
269806
  }
@@ -268618,6 +269825,11 @@ var init_serve_http = __esm({
268618
269825
  }));
268619
269826
  return;
268620
269827
  }
269828
+ if (url2.pathname === "/protocol-diagnostics") {
269829
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "public, max-age=60" });
269830
+ res.end(JSON.stringify(getMcpDiagnostics()));
269831
+ return;
269832
+ }
268621
269833
  if (url2.pathname === "/mcp") {
268622
269834
  const accept = req.headers["accept"] || "";
268623
269835
  const needs = [];
@@ -270492,10 +271704,10 @@ __export(handler_exports, {
270492
271704
  resetCooldowns: () => resetCooldowns,
270493
271705
  runHook: () => runHook
270494
271706
  });
270495
- import { createHash as createHash17 } from "crypto";
271707
+ import { createHash as createHash19 } from "crypto";
270496
271708
  function deriveHookActorId(input) {
270497
271709
  const material = `${input.agent ?? "unknown"}\0${input.sessionId ?? "unknown"}`;
270498
- return `hook:${createHash17("sha256").update(material).digest("hex").slice(0, 24)}`;
271710
+ return `hook:${createHash19("sha256").update(material).digest("hex").slice(0, 24)}`;
270499
271711
  }
270500
271712
  function classifyTool(input) {
270501
271713
  if (input.event === "post_edit") return "file_modify";
@@ -272440,10 +273652,10 @@ var init_ingest = __esm({
272440
273652
  });
272441
273653
 
272442
273654
  // src/media/embedding.ts
272443
- import { createHash as createHash18 } from "crypto";
273655
+ import { createHash as createHash20 } from "crypto";
272444
273656
  function profileKey(provider2, asset) {
272445
273657
  const identity = `${provider2.name}\0${provider2.dimensions}\0${asset.kind}`;
272446
- return `media:${createHash18("sha256").update(identity).digest("hex").slice(0, 20)}`;
273658
+ return `media:${createHash20("sha256").update(identity).digest("hex").slice(0, 20)}`;
272447
273659
  }
272448
273660
  function supportsAsset(provider2, asset) {
272449
273661
  return !!provider2.embedInput && (provider2.supportedModalities?.includes(asset.kind) ?? false);
@@ -275475,7 +276687,7 @@ var init_dashboard = __esm({
275475
276687
  }
275476
276688
  const cliDir = path45.dirname(fileURLToPath9(import.meta.url));
275477
276689
  const staticDir = path45.join(cliDir, "..", "dashboard", "static");
275478
- await startDashboard2(dataDir, port, staticDir, project.id, project.name, true, void 0, project.rootPath, true);
276690
+ await startDashboard2(dataDir, port, staticDir, project.id, project.name, true, project.rootPath, true);
275479
276691
  await new Promise(() => {
275480
276692
  });
275481
276693
  }
@@ -278056,7 +279268,7 @@ function safeWrite(path48, content) {
278056
279268
  } catch {
278057
279269
  }
278058
279270
  }
278059
- var init_evidence = __esm({
279271
+ var init_evidence2 = __esm({
278060
279272
  "src/orchestrate/evidence.ts"() {
278061
279273
  "use strict";
278062
279274
  init_esm_shims();
@@ -278182,7 +279394,7 @@ var init_permission = __esm({
278182
279394
  });
278183
279395
 
278184
279396
  // src/orchestrate/memorix-bridge.ts
278185
- import { createHash as createHash19 } from "crypto";
279397
+ import { createHash as createHash21 } from "crypto";
278186
279398
  async function getStoreObservation() {
278187
279399
  if (!_storeObservation) {
278188
279400
  const mod = await Promise.resolve().then(() => (init_observations(), observations_exports));
@@ -278198,7 +279410,7 @@ async function getSearchObservations() {
278198
279410
  return _searchObservations;
278199
279411
  }
278200
279412
  function automaticActorId(...parts) {
278201
- return `orchestrator:${createHash19("sha256").update(parts.join("\0")).digest("hex").slice(0, 24)}`;
279413
+ return `orchestrator:${createHash21("sha256").update(parts.join("\0")).digest("hex").slice(0, 24)}`;
278202
279414
  }
278203
279415
  function isEligibleAutomaticLesson(entry) {
278204
279416
  return entry.admissionState !== "candidate" && entry.admissionState !== "ephemeral";
@@ -278345,7 +279557,7 @@ function sanitizeErrorPattern(text2) {
278345
279557
  }
278346
279558
  function hashErrorPattern(errorOutput) {
278347
279559
  const sanitized = sanitizeErrorPattern(errorOutput).slice(0, 500);
278348
- return createHash19("sha256").update(sanitized).digest("hex").slice(0, 12);
279560
+ return createHash21("sha256").update(sanitized).digest("hex").slice(0, 12);
278349
279561
  }
278350
279562
  function sleep6(ms2) {
278351
279563
  return new Promise((resolve6) => setTimeout(() => resolve6([]), ms2));
@@ -279351,7 +280563,7 @@ var init_coordinator = __esm({
279351
280563
  init_verify_gate();
279352
280564
  init_error_recovery();
279353
280565
  init_cost_tracker();
279354
- init_evidence();
280566
+ init_evidence2();
279355
280567
  init_permission();
279356
280568
  init_memorix_bridge();
279357
280569
  }
@@ -284602,6 +285814,8 @@ var main2 = defineCommand({
284602
285814
  codegraph: () => Promise.resolve().then(() => (init_codegraph(), codegraph_exports)).then((m6) => m6.default),
284603
285815
  checkpoint: () => Promise.resolve().then(() => (init_checkpoint2(), checkpoint_exports)).then((m6) => m6.default),
284604
285816
  knowledge: () => Promise.resolve().then(() => (init_knowledge(), knowledge_exports)).then((m6) => m6.default),
285817
+ evidence: () => Promise.resolve().then(() => (init_evidence(), evidence_exports)).then((m6) => m6.default),
285818
+ feedback: () => Promise.resolve().then(() => (init_feedback2(), feedback_exports2)).then((m6) => m6.default),
284605
285819
  reasoning: () => Promise.resolve().then(() => (init_reasoning(), reasoning_exports)).then((m6) => m6.default),
284606
285820
  retention: () => Promise.resolve().then(() => (init_retention2(), retention_exports2)).then((m6) => m6.default),
284607
285821
  formation: () => Promise.resolve().then(() => (init_formation2(), formation_exports2)).then((m6) => m6.default),
@@ -284700,6 +285914,8 @@ var main2 = defineCommand({
284700
285914
  "lock",
284701
285915
  "handoff",
284702
285916
  "poll",
285917
+ "evidence",
285918
+ "feedback",
284703
285919
  "receipt",
284704
285920
  "serve",
284705
285921
  "serve-http",
@@ -284757,6 +285973,8 @@ var main2 = defineCommand({
284757
285973
  console.error(" codegraph Refresh/status/context-pack for CodeGraph Memory");
284758
285974
  console.error(" checkpoint Inspect native compact continuity checkpoints");
284759
285975
  console.error(" knowledge Review source-backed knowledge pages and project workflows");
285976
+ console.error(" evidence Inspect source-backed evidence cards");
285977
+ console.error(" feedback Record or audit memory feedback");
284760
285978
  console.error(" reasoning Store/search decision rationale");
284761
285979
  console.error(" retention Inspect stale/archive status");
284762
285980
  console.error(" formation Inspect Memory Formation metrics");