memorix 1.2.0 → 1.2.2

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 (212) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +18 -4
  3. package/README.zh-CN.md +18 -4
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15919 -14055
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1997 -1021
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8481 -8005
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +30 -1
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +2022 -1024
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +27 -5
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/DEVELOPMENT.md +4 -0
  23. package/docs/README.md +1 -1
  24. package/docs/SETUP.md +7 -1
  25. package/docs/dev-log/progress.txt +91 -11
  26. package/docs/knowledge/workflows/memorix-release.md +57 -0
  27. package/package.json +1 -1
  28. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  29. package/src/audit/index.ts +156 -156
  30. package/src/cli/command-guide.ts +192 -0
  31. package/src/cli/commands/audit-list.ts +89 -89
  32. package/src/cli/commands/audit.ts +9 -4
  33. package/src/cli/commands/background.ts +659 -659
  34. package/src/cli/commands/cleanup.ts +5 -1
  35. package/src/cli/commands/codegraph.ts +17 -8
  36. package/src/cli/commands/context.ts +3 -2
  37. package/src/cli/commands/doctor.ts +4 -2
  38. package/src/cli/commands/explain.ts +9 -3
  39. package/src/cli/commands/formation.ts +48 -48
  40. package/src/cli/commands/git-hook-install.ts +111 -111
  41. package/src/cli/commands/handoff.ts +75 -61
  42. package/src/cli/commands/hooks-status.ts +63 -63
  43. package/src/cli/commands/identity.ts +116 -0
  44. package/src/cli/commands/ingest-commit.ts +153 -153
  45. package/src/cli/commands/ingest-image.ts +71 -69
  46. package/src/cli/commands/ingest-log.ts +180 -180
  47. package/src/cli/commands/ingest.ts +44 -44
  48. package/src/cli/commands/integrate-shared.ts +15 -15
  49. package/src/cli/commands/knowledge.ts +40 -0
  50. package/src/cli/commands/lock.ts +93 -92
  51. package/src/cli/commands/memory.ts +58 -21
  52. package/src/cli/commands/message.ts +123 -118
  53. package/src/cli/commands/operator-shared.ts +98 -3
  54. package/src/cli/commands/poll.ts +74 -64
  55. package/src/cli/commands/purge-all-memory.ts +85 -85
  56. package/src/cli/commands/purge-project-memory.ts +83 -83
  57. package/src/cli/commands/reasoning.ts +135 -121
  58. package/src/cli/commands/retention.ts +9 -4
  59. package/src/cli/commands/serve-http.ts +22 -43
  60. package/src/cli/commands/serve-shared.ts +118 -118
  61. package/src/cli/commands/session.ts +29 -3
  62. package/src/cli/commands/setup.ts +9 -3
  63. package/src/cli/commands/skills.ts +124 -119
  64. package/src/cli/commands/status.ts +4 -3
  65. package/src/cli/commands/task.ts +193 -184
  66. package/src/cli/commands/team.ts +14 -10
  67. package/src/cli/commands/transfer.ts +108 -55
  68. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  69. package/src/cli/identity.ts +89 -0
  70. package/src/cli/index.ts +96 -19
  71. package/src/cli/invocation.ts +115 -0
  72. package/src/cli/tui/ChatView.tsx +234 -234
  73. package/src/cli/tui/CommandBar.tsx +312 -312
  74. package/src/cli/tui/ContextRail.tsx +118 -118
  75. package/src/cli/tui/HeaderBar.tsx +72 -72
  76. package/src/cli/tui/LogoBanner.tsx +51 -51
  77. package/src/cli/tui/Sidebar.tsx +179 -179
  78. package/src/cli/tui/chat-service.ts +41 -18
  79. package/src/cli/tui/data.ts +23 -44
  80. package/src/cli/tui/index.ts +41 -41
  81. package/src/cli/tui/markdown-render.tsx +371 -371
  82. package/src/cli/tui/operator-context.ts +60 -0
  83. package/src/cli/tui/use-mouse.ts +157 -157
  84. package/src/cli/tui/useNavigation.ts +56 -56
  85. package/src/cli/tui/views/MemoryView.tsx +10 -8
  86. package/src/cli/update-checker.ts +211 -211
  87. package/src/cli/version.ts +7 -7
  88. package/src/cli/workbench.ts +1 -1
  89. package/src/codegraph/auto-context.ts +34 -17
  90. package/src/codegraph/context-pack.ts +1 -0
  91. package/src/codegraph/current-facts.ts +19 -1
  92. package/src/codegraph/project-context.ts +2 -0
  93. package/src/codegraph/task-lens.ts +49 -5
  94. package/src/compact/engine.ts +26 -10
  95. package/src/compact/index-format.ts +25 -2
  96. package/src/compact/token-budget.ts +74 -74
  97. package/src/dashboard/project-classification.ts +64 -64
  98. package/src/dashboard/server.ts +58 -52
  99. package/src/embedding/fastembed-provider.ts +142 -142
  100. package/src/embedding/transformers-provider.ts +111 -111
  101. package/src/git/extractor.ts +209 -209
  102. package/src/git/hooks-path.ts +85 -85
  103. package/src/hooks/admission.ts +117 -0
  104. package/src/hooks/handler.ts +98 -91
  105. package/src/hooks/pattern-detector.ts +173 -173
  106. package/src/hooks/significance-filter.ts +250 -250
  107. package/src/knowledge/claims.ts +51 -1
  108. package/src/knowledge/context-assembly.ts +97 -0
  109. package/src/knowledge/types.ts +1 -0
  110. package/src/knowledge/workflows.ts +34 -3
  111. package/src/knowledge/workset.ts +179 -10
  112. package/src/llm/memory-manager.ts +328 -328
  113. package/src/llm/provider.ts +885 -885
  114. package/src/llm/quality.ts +248 -248
  115. package/src/memory/admission.ts +57 -0
  116. package/src/memory/attribution-guard.ts +249 -249
  117. package/src/memory/auto-relations.ts +21 -0
  118. package/src/memory/consolidation.ts +13 -2
  119. package/src/memory/disclosure-policy.ts +140 -135
  120. package/src/memory/entity-extractor.ts +197 -197
  121. package/src/memory/export-import.ts +11 -3
  122. package/src/memory/formation/evaluate.ts +217 -217
  123. package/src/memory/formation/extract.ts +361 -361
  124. package/src/memory/formation/index.ts +417 -417
  125. package/src/memory/formation/resolve.ts +344 -344
  126. package/src/memory/formation/types.ts +315 -315
  127. package/src/memory/freshness.ts +122 -122
  128. package/src/memory/graph-context.ts +8 -2
  129. package/src/memory/graph-scope.ts +46 -0
  130. package/src/memory/graph.ts +197 -197
  131. package/src/memory/observations.ts +162 -4
  132. package/src/memory/quality-audit.ts +2 -0
  133. package/src/memory/refs.ts +94 -94
  134. package/src/memory/retention.ts +22 -2
  135. package/src/memory/secret-filter.ts +79 -79
  136. package/src/memory/session.ts +5 -2
  137. package/src/memory/visibility.ts +80 -0
  138. package/src/multimodal/image-loader.ts +143 -143
  139. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  140. package/src/orchestrate/adapters/claude.ts +111 -111
  141. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  142. package/src/orchestrate/adapters/codex.ts +41 -41
  143. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  144. package/src/orchestrate/adapters/gemini.ts +42 -42
  145. package/src/orchestrate/adapters/index.ts +73 -73
  146. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  147. package/src/orchestrate/adapters/opencode.ts +47 -47
  148. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  149. package/src/orchestrate/adapters/types.ts +77 -77
  150. package/src/orchestrate/capability-router.ts +284 -284
  151. package/src/orchestrate/context-compact.ts +188 -188
  152. package/src/orchestrate/cost-tracker.ts +219 -219
  153. package/src/orchestrate/error-recovery.ts +191 -191
  154. package/src/orchestrate/evidence.ts +140 -140
  155. package/src/orchestrate/ledger.ts +110 -110
  156. package/src/orchestrate/memorix-bridge.ts +378 -340
  157. package/src/orchestrate/output-budget.ts +80 -80
  158. package/src/orchestrate/permission.ts +152 -152
  159. package/src/orchestrate/pipeline-trace.ts +131 -131
  160. package/src/orchestrate/prompt-builder.ts +155 -155
  161. package/src/orchestrate/ring-buffer.ts +37 -37
  162. package/src/orchestrate/task-graph.ts +389 -389
  163. package/src/orchestrate/verify-gate.ts +33 -10
  164. package/src/orchestrate/worktree.ts +232 -232
  165. package/src/project/aliases.ts +374 -374
  166. package/src/project/detector.ts +268 -268
  167. package/src/rules/adapters/claude-code.ts +99 -99
  168. package/src/rules/adapters/codex.ts +97 -97
  169. package/src/rules/adapters/copilot.ts +124 -124
  170. package/src/rules/adapters/cursor.ts +114 -114
  171. package/src/rules/adapters/kiro.ts +126 -126
  172. package/src/rules/adapters/trae.ts +56 -56
  173. package/src/rules/adapters/windsurf.ts +83 -83
  174. package/src/rules/syncer.ts +235 -235
  175. package/src/runtime/control-plane-maintenance.ts +1 -0
  176. package/src/runtime/isolated-maintenance.ts +1 -0
  177. package/src/runtime/lifecycle.ts +18 -0
  178. package/src/runtime/maintenance-jobs.ts +1 -0
  179. package/src/runtime/maintenance-runner.ts +2 -0
  180. package/src/runtime/project-maintenance.ts +89 -0
  181. package/src/sdk.ts +334 -304
  182. package/src/search/intent-detector.ts +289 -289
  183. package/src/search/query-expansion.ts +52 -52
  184. package/src/server/formation-timeout.ts +27 -27
  185. package/src/server.ts +334 -93
  186. package/src/skills/mini-skills.ts +386 -386
  187. package/src/store/chat-store.ts +119 -119
  188. package/src/store/graph-store.ts +249 -249
  189. package/src/store/mini-skill-store.ts +349 -349
  190. package/src/store/orama-store.ts +61 -6
  191. package/src/store/persistence-json.ts +212 -212
  192. package/src/store/persistence.ts +291 -291
  193. package/src/store/project-affinity.ts +195 -195
  194. package/src/store/sqlite-db.ts +23 -1
  195. package/src/store/sqlite-store.ts +12 -2
  196. package/src/team/event-bus.ts +76 -76
  197. package/src/team/file-locks.ts +173 -173
  198. package/src/team/handoff.ts +168 -161
  199. package/src/team/messages.ts +203 -203
  200. package/src/team/poll.ts +132 -132
  201. package/src/team/tasks.ts +211 -211
  202. package/src/types.ts +51 -0
  203. package/src/wiki/generator.ts +2 -0
  204. package/src/workspace/mcp-adapters/codex.ts +191 -191
  205. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  206. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  207. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  208. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  209. package/src/workspace/mcp-adapters/trae.ts +134 -134
  210. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  211. package/src/workspace/sanitizer.ts +60 -60
  212. package/src/workspace/workflow-sync.ts +131 -131
package/dist/index.js CHANGED
@@ -209,7 +209,11 @@ CREATE TABLE IF NOT EXISTS observations (
209
209
  relatedCommits TEXT,
210
210
  relatedEntities TEXT,
211
211
  sourceDetail TEXT,
212
- valueCategory TEXT
212
+ valueCategory TEXT,
213
+ admissionState TEXT,
214
+ admissionReason TEXT,
215
+ visibility TEXT,
216
+ sharedWithAgentIds TEXT
213
217
  );
214
218
  `;
215
219
  CREATE_MINI_SKILLS_TABLE = `
@@ -632,6 +636,8 @@ CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId)
632
636
  CREATE INDEX IF NOT EXISTS idx_observations_topicKey ON observations(projectId, topicKey);
633
637
  CREATE INDEX IF NOT EXISTS idx_observations_status ON observations(status);
634
638
  CREATE INDEX IF NOT EXISTS idx_observations_project_status_id ON observations(projectId, status, id);
639
+ CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id);
640
+ CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id);
635
641
  CREATE INDEX IF NOT EXISTS idx_mini_skills_projectId ON mini_skills(projectId);
636
642
  CREATE INDEX IF NOT EXISTS idx_sessions_projectId ON sessions(projectId);
637
643
  CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(projectId, status);
@@ -676,6 +682,22 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
676
682
  WHERE status IN ('pending', 'running', 'retry');
677
683
  `;
678
684
  SCHEMA_MIGRATIONS = [
685
+ {
686
+ id: "1.2.2-observation-admission",
687
+ apply: (db2) => {
688
+ addColumnIfMissing(db2, "observations", "admissionState", "admissionState TEXT");
689
+ addColumnIfMissing(db2, "observations", "admissionReason", "admissionReason TEXT");
690
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id)");
691
+ }
692
+ },
693
+ {
694
+ id: "1.2.2-observation-visibility",
695
+ apply: (db2) => {
696
+ addColumnIfMissing(db2, "observations", "visibility", "visibility TEXT");
697
+ addColumnIfMissing(db2, "observations", "sharedWithAgentIds", "sharedWithAgentIds TEXT");
698
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id)");
699
+ }
700
+ },
679
701
  {
680
702
  id: "1.2-code-state-snapshots",
681
703
  apply: (db2) => {
@@ -2118,6 +2140,58 @@ var init_mini_skills = __esm({
2118
2140
  }
2119
2141
  });
2120
2142
 
2143
+ // src/memory/visibility.ts
2144
+ function resolveObservationVisibility(record) {
2145
+ switch (record.visibility) {
2146
+ case "personal":
2147
+ case "team":
2148
+ case "project":
2149
+ return record.visibility;
2150
+ default:
2151
+ return "project";
2152
+ }
2153
+ }
2154
+ function sharedAgentIds(record) {
2155
+ if (Array.isArray(record.sharedWithAgentIds)) return record.sharedWithAgentIds;
2156
+ if (typeof record.sharedWithAgentIds !== "string" || !record.sharedWithAgentIds) return [];
2157
+ try {
2158
+ const parsed = JSON.parse(record.sharedWithAgentIds);
2159
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
2160
+ } catch {
2161
+ return [];
2162
+ }
2163
+ }
2164
+ function canReadObservation(record, reader) {
2165
+ if (!reader) return true;
2166
+ const sameProject = reader.projectId === record.projectId;
2167
+ const visibility = resolveObservationVisibility(record);
2168
+ if (visibility === "project") return !reader.projectId || sameProject;
2169
+ if (!sameProject || !reader.agentId) return false;
2170
+ if (visibility === "team") return reader.isTeamMember === true;
2171
+ return record.createdByAgentId === reader.agentId || sharedAgentIds(record).includes(reader.agentId);
2172
+ }
2173
+ function canManageObservation(record, reader) {
2174
+ if (!reader) return true;
2175
+ if (!reader.projectId || reader.projectId !== record.projectId) return false;
2176
+ switch (resolveObservationVisibility(record)) {
2177
+ case "project":
2178
+ return true;
2179
+ case "team":
2180
+ return reader.isTeamMember === true;
2181
+ case "personal":
2182
+ return Boolean(reader.agentId && record.createdByAgentId === reader.agentId);
2183
+ }
2184
+ }
2185
+ function filterReadableObservations(observations2, reader) {
2186
+ return reader ? observations2.filter((observation) => canReadObservation(observation, reader)) : [...observations2];
2187
+ }
2188
+ var init_visibility = __esm({
2189
+ "src/memory/visibility.ts"() {
2190
+ "use strict";
2191
+ init_esm_shims();
2192
+ }
2193
+ });
2194
+
2121
2195
  // src/config/config-paths.ts
2122
2196
  import { join } from "path";
2123
2197
  function getGlobalConfigTomlPath(homeDir) {
@@ -5345,7 +5419,8 @@ __export(orama_store_exports, {
5345
5419
  makeOramaObservationId: () => makeOramaObservationId,
5346
5420
  removeObservation: () => removeObservation,
5347
5421
  resetDb: () => resetDb,
5348
- searchObservations: () => searchObservations
5422
+ searchObservations: () => searchObservations,
5423
+ updateObservationMetadata: () => updateObservationMetadata
5349
5424
  });
5350
5425
  import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
5351
5426
  function getLastSearchMode(projectId) {
@@ -5423,6 +5498,11 @@ async function initializeDb(options, generation) {
5423
5498
  source: "string",
5424
5499
  sourceDetail: "string",
5425
5500
  valueCategory: "string",
5501
+ admissionState: "string",
5502
+ admissionReason: "string",
5503
+ visibility: "string",
5504
+ createdByAgentId: "string",
5505
+ sharedWithAgentIds: "string",
5426
5506
  documentType: "string",
5427
5507
  knowledgeLayer: "string"
5428
5508
  };
@@ -5561,6 +5641,13 @@ async function hydrateIndex(observations2, options = {}) {
5561
5641
  lastAccessedAt: obs.lastAccessedAt || "",
5562
5642
  status: obs.status ?? "active",
5563
5643
  source: obs.source || "agent",
5644
+ sourceDetail: obs.sourceDetail ?? "",
5645
+ valueCategory: obs.valueCategory ?? "",
5646
+ admissionState: obs.admissionState ?? "",
5647
+ admissionReason: obs.admissionReason ?? "",
5648
+ visibility: obs.visibility ?? "project",
5649
+ createdByAgentId: obs.createdByAgentId ?? "",
5650
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
5564
5651
  documentType: "observation",
5565
5652
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
5566
5653
  ...compatibleVector ? { embedding: compatibleVector } : {}
@@ -5594,6 +5681,16 @@ async function insertObservation(doc) {
5594
5681
  await insert2(database, doc);
5595
5682
  rememberObservationDoc(doc);
5596
5683
  }
5684
+ async function updateObservationMetadata(projectId, observationId2, patch) {
5685
+ const database = await getDb();
5686
+ const id = makeOramaObservationId(projectId, observationId2);
5687
+ const existing = getByID(database, id);
5688
+ if (!existing) return false;
5689
+ const next = { ...existing, ...patch };
5690
+ await update(database, id, next);
5691
+ rememberObservationDoc(next);
5692
+ return true;
5693
+ }
5597
5694
  async function removeObservation(oramaId) {
5598
5695
  const database = await getDb();
5599
5696
  await remove2(database, oramaId);
@@ -5747,7 +5844,7 @@ async function searchObservations(options) {
5747
5844
  if (!projectIds) return true;
5748
5845
  const doc = hit.document;
5749
5846
  return projectIds.includes(doc.projectId);
5750
- }).filter((hit) => {
5847
+ }).filter((hit) => canReadObservation(hit.document, options.reader)).filter((hit) => {
5751
5848
  if (statusFilter === "all") return true;
5752
5849
  const doc = hit.document;
5753
5850
  return (doc.status || "active") === statusFilter;
@@ -5778,6 +5875,8 @@ async function searchObservations(options) {
5778
5875
  source: doc.source || "agent",
5779
5876
  sourceDetail: doc.sourceDetail || void 0,
5780
5877
  valueCategory: doc.valueCategory || void 0,
5878
+ admissionState: doc.admissionState || void 0,
5879
+ visibility: doc.visibility || void 0,
5781
5880
  entityName: doc.entityName || void 0,
5782
5881
  documentType: doc.documentType || "observation",
5783
5882
  knowledgeLayer: doc.knowledgeLayer || "project-truth",
@@ -5821,6 +5920,12 @@ async function searchObservations(options) {
5821
5920
  score: isCommandStyleEntry(entry.title) ? entry.score * 0.3 : entry.score
5822
5921
  }));
5823
5922
  }
5923
+ if (hasQuery) {
5924
+ const qualifiedEntries = intermediate.filter(
5925
+ (entry) => entry.admissionState !== "candidate" && entry.admissionState !== "ephemeral"
5926
+ );
5927
+ if (qualifiedEntries.length > 0) intermediate = qualifiedEntries;
5928
+ }
5824
5929
  if (intentResult?.preferChronological) {
5825
5930
  intermediate.sort((a, b) => new Date(b.rawTime).getTime() - new Date(a.rawTime).getTime());
5826
5931
  } else {
@@ -5964,7 +6069,8 @@ async function searchObservations(options) {
5964
6069
  }
5965
6070
  let entries = intermediate.map(({ rawTime: _, _isCommandLog: _c, ...rest }) => rest);
5966
6071
  for (const hit of results.hits) {
5967
- rememberObservationDoc(hit.document);
6072
+ const doc = hit.document;
6073
+ if (canReadObservation(doc, options.reader)) rememberObservationDoc(doc);
5968
6074
  }
5969
6075
  if (hasQuery && originalQuery) {
5970
6076
  const queryLower = originalQuery.toLowerCase();
@@ -6008,7 +6114,8 @@ async function searchObservations(options) {
6008
6114
  entries = applyTokenBudget(entries, options.maxTokens);
6009
6115
  }
6010
6116
  if (options.trackAccess !== false) {
6011
- const hitDocs = results.hits.map((h) => ({ id: h.id, doc: h.document }));
6117
+ const returnedKeys = new Set(entries.map((entry) => makeEntryKey(entry.projectId, entry.id)));
6118
+ const hitDocs = results.hits.map((hit) => ({ id: hit.id, doc: hit.document })).filter(({ doc }) => returnedKeys.has(makeEntryKey(doc.projectId, doc.observationId)));
6012
6119
  recordAccessBatch(hitDocs).catch(() => {
6013
6120
  });
6014
6121
  }
@@ -6040,11 +6147,11 @@ async function getObservationsByIds(ids, projectId) {
6040
6147
  }
6041
6148
  return results;
6042
6149
  }
6043
- async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
6150
+ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
6044
6151
  const { withFreshIndex: withFreshIndex2 } = await Promise.resolve().then(() => (init_freshness(), freshness_exports));
6045
6152
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
6046
6153
  const rawObs = await withFreshIndex2(() => getAllObservations2());
6047
- const allObs = projectId ? rawObs.filter((o) => o.projectId === projectId) : rawObs;
6154
+ const allObs = (projectId ? rawObs.filter((o) => o.projectId === projectId) : rawObs).filter((observation) => canReadObservation(observation, reader));
6048
6155
  const sorted = allObs.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
6049
6156
  const anchorIndex = sorted.findIndex((o) => o.id === anchorId);
6050
6157
  if (anchorIndex === -1) {
@@ -6061,7 +6168,9 @@ async function getTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3)
6061
6168
  tokens: obs.tokens,
6062
6169
  source: obs.source || void 0,
6063
6170
  sourceDetail: obs.sourceDetail || void 0,
6064
- valueCategory: obs.valueCategory || void 0
6171
+ valueCategory: obs.valueCategory || void 0,
6172
+ admissionState: obs.admissionState || void 0,
6173
+ visibility: obs.visibility || void 0
6065
6174
  };
6066
6175
  };
6067
6176
  const before = sorted.slice(Math.max(0, anchorIndex - depthBefore), anchorIndex).map(toIndexEntry);
@@ -6129,6 +6238,7 @@ var init_orama_store = __esm({
6129
6238
  init_esm_shims();
6130
6239
  init_types();
6131
6240
  init_mini_skills();
6241
+ init_visibility();
6132
6242
  init_provider();
6133
6243
  init_project_affinity();
6134
6244
  init_intent_detector();
@@ -6185,6 +6295,10 @@ function obsToRow(obs) {
6185
6295
  relatedEntities: obs.relatedEntities ? JSON.stringify(obs.relatedEntities) : null,
6186
6296
  sourceDetail: obs.sourceDetail ?? null,
6187
6297
  valueCategory: obs.valueCategory ?? null,
6298
+ admissionState: obs.admissionState ?? null,
6299
+ admissionReason: obs.admissionReason ?? null,
6300
+ visibility: obs.visibility ?? null,
6301
+ sharedWithAgentIds: obs.sharedWithAgentIds ? JSON.stringify(obs.sharedWithAgentIds) : null,
6188
6302
  createdByAgentId: obs.createdByAgentId ?? null,
6189
6303
  writeGeneration: obs.writeGeneration ?? 0
6190
6304
  };
@@ -6215,6 +6329,10 @@ function rowToObs(row) {
6215
6329
  ...row.relatedEntities ? { relatedEntities: safeJsonParse3(row.relatedEntities, []) } : {},
6216
6330
  ...row.sourceDetail ? { sourceDetail: row.sourceDetail } : {},
6217
6331
  ...row.valueCategory ? { valueCategory: row.valueCategory } : {},
6332
+ ...row.admissionState ? { admissionState: row.admissionState } : {},
6333
+ ...row.admissionReason ? { admissionReason: row.admissionReason } : {},
6334
+ ...row.visibility ? { visibility: row.visibility } : {},
6335
+ ...row.sharedWithAgentIds ? { sharedWithAgentIds: safeJsonParse3(row.sharedWithAgentIds, []) } : {},
6218
6336
  ...row.createdByAgentId ? { createdByAgentId: row.createdByAgentId } : {},
6219
6337
  ...row.writeGeneration ? { writeGeneration: row.writeGeneration } : {}
6220
6338
  };
@@ -6266,12 +6384,14 @@ var init_sqlite_store = __esm({
6266
6384
  (id, entityName, type, title, narrative, facts, filesModified, concepts, tokens,
6267
6385
  createdAt, updatedAt, projectId, hasCausalLanguage, topicKey, revisionCount,
6268
6386
  sessionId, status, progress, source, commitHash, relatedCommits, relatedEntities,
6269
- sourceDetail, valueCategory, createdByAgentId, writeGeneration)
6387
+ sourceDetail, valueCategory, admissionState, admissionReason, visibility, sharedWithAgentIds,
6388
+ createdByAgentId, writeGeneration)
6270
6389
  VALUES
6271
6390
  (@id, @entityName, @type, @title, @narrative, @facts, @filesModified, @concepts, @tokens,
6272
6391
  @createdAt, @updatedAt, @projectId, @hasCausalLanguage, @topicKey, @revisionCount,
6273
6392
  @sessionId, @status, @progress, @source, @commitHash, @relatedCommits, @relatedEntities,
6274
- @sourceDetail, @valueCategory, @createdByAgentId, @writeGeneration)
6393
+ @sourceDetail, @valueCategory, @admissionState, @admissionReason, @visibility, @sharedWithAgentIds,
6394
+ @createdByAgentId, @writeGeneration)
6275
6395
  `);
6276
6396
  this.stmtUpdate = this.stmtInsert;
6277
6397
  this.stmtSetStatus = this.db.prepare(`UPDATE observations SET status = ? WHERE id = ?`);
@@ -6823,6 +6943,7 @@ var init_maintenance_jobs = __esm({
6823
6943
  "retention-archive",
6824
6944
  "consolidation",
6825
6945
  "codegraph-refresh",
6946
+ "observation-qualify",
6826
6947
  "claim-derive",
6827
6948
  "claim-requalification",
6828
6949
  "knowledge-compile",
@@ -7181,7 +7302,8 @@ __export(lifecycle_exports, {
7181
7302
  enqueueClaimDerivation: () => enqueueClaimDerivation,
7182
7303
  enqueueClaimRequalification: () => enqueueClaimRequalification,
7183
7304
  enqueueCodegraphRefresh: () => enqueueCodegraphRefresh,
7184
- enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups
7305
+ enqueueKnowledgeFollowups: () => enqueueKnowledgeFollowups,
7306
+ enqueueObservationQualification: () => enqueueObservationQualification
7185
7307
  });
7186
7308
  function queueFor(input) {
7187
7309
  return input.queue ?? new MaintenanceJobStore(input.dataDir);
@@ -7216,6 +7338,14 @@ function enqueueClaimDerivation(input) {
7216
7338
  payload: { observationId: input.observationId }
7217
7339
  });
7218
7340
  }
7341
+ function enqueueObservationQualification(input) {
7342
+ queueFor(input).enqueue({
7343
+ projectId: input.projectId,
7344
+ kind: "observation-qualify",
7345
+ dedupeKey: "observation-qualify",
7346
+ payload: { source: input.source, limit: 100 }
7347
+ });
7348
+ }
7219
7349
  function enqueueKnowledgeFollowups(input) {
7220
7350
  const queue = queueFor(input);
7221
7351
  const payload = {
@@ -8080,6 +8210,7 @@ __export(observations_exports, {
8080
8210
  resolveObservations: () => resolveObservations,
8081
8211
  storeObservation: () => storeObservation,
8082
8212
  suggestTopicKey: () => suggestTopicKey,
8213
+ updateObservationAdmission: () => updateObservationAdmission,
8083
8214
  withFreshObservations: () => withFreshObservations
8084
8215
  });
8085
8216
  function logEmbeddingFailureOnce(key, message) {
@@ -8146,6 +8277,18 @@ function queueClaimDerivation(observation) {
8146
8277
  } catch {
8147
8278
  }
8148
8279
  }
8280
+ function queueObservationQualification(observation) {
8281
+ const dataDir = projectDir;
8282
+ if (!dataDir || observation.admissionState !== "candidate") return;
8283
+ try {
8284
+ enqueueObservationQualification({
8285
+ dataDir,
8286
+ projectId: observation.projectId,
8287
+ source: "automatic-capture:" + observation.id
8288
+ });
8289
+ } catch {
8290
+ }
8291
+ }
8149
8292
  function isVectorCompatibleWithCurrentIndex(embedding) {
8150
8293
  if (!embedding) return false;
8151
8294
  const vectorDimensions = getVectorDimensions();
@@ -8201,6 +8344,9 @@ async function storeObservation(input) {
8201
8344
  (o) => o.topicKey === input.topicKey && o.projectId === input.projectId
8202
8345
  );
8203
8346
  if (existing) {
8347
+ if (input.visibilityReader && !canManageObservation(existing, input.visibilityReader)) {
8348
+ throw new Error("Cannot update a memory outside this session's write scope.");
8349
+ }
8204
8350
  return { observation: await upsertObservation(existing, input, now3), upserted: true };
8205
8351
  }
8206
8352
  }
@@ -8237,6 +8383,9 @@ async function storeObservation(input) {
8237
8383
  if (input.topicKey) {
8238
8384
  const diskExisting = await tx.findByTopicKey(input.projectId, input.topicKey);
8239
8385
  if (diskExisting) {
8386
+ if (input.visibilityReader && !canManageObservation(diskExisting, input.visibilityReader)) {
8387
+ throw new Error("Cannot update a memory outside this session's write scope.");
8388
+ }
8240
8389
  upsertedInsideLock = true;
8241
8390
  observation = diskExisting;
8242
8391
  return;
@@ -8267,6 +8416,10 @@ async function storeObservation(input) {
8267
8416
  relatedEntities: input.relatedEntities,
8268
8417
  sourceDetail: input.sourceDetail,
8269
8418
  valueCategory: input.valueCategory,
8419
+ admissionState: input.admissionState,
8420
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : void 0,
8421
+ visibility: input.visibility ?? "project",
8422
+ sharedWithAgentIds: input.sharedWithAgentIds,
8270
8423
  createdByAgentId: input.createdByAgentId,
8271
8424
  // Predict the generation that atomic() will commit after this callback.
8272
8425
  // bumpGeneration() runs after fn(tx) returns, incrementing by 1.
@@ -8313,6 +8466,10 @@ async function storeObservation(input) {
8313
8466
  relatedEntities: input.relatedEntities,
8314
8467
  sourceDetail: input.sourceDetail,
8315
8468
  valueCategory: input.valueCategory,
8469
+ admissionState: input.admissionState,
8470
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : void 0,
8471
+ visibility: input.visibility ?? "project",
8472
+ sharedWithAgentIds: input.sharedWithAgentIds,
8316
8473
  createdByAgentId: input.createdByAgentId,
8317
8474
  writeGeneration: 0
8318
8475
  };
@@ -8336,7 +8493,12 @@ async function storeObservation(input) {
8336
8493
  status: "active",
8337
8494
  source: input.source ?? "agent",
8338
8495
  sourceDetail: input.sourceDetail ?? "",
8339
- valueCategory: input.valueCategory ?? ""
8496
+ valueCategory: input.valueCategory ?? "",
8497
+ admissionState: input.admissionState ?? "",
8498
+ admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : "",
8499
+ visibility: input.visibility ?? "project",
8500
+ createdByAgentId: input.createdByAgentId ?? "",
8501
+ sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? [])
8340
8502
  };
8341
8503
  await insertObservation(doc);
8342
8504
  };
@@ -8345,7 +8507,10 @@ async function storeObservation(input) {
8345
8507
  return { observation: await upsertObservation(observation, input, now3), upserted: true };
8346
8508
  }
8347
8509
  await bindObservationCodeRefsBestEffort(observation);
8348
- queueClaimDerivation(observation);
8510
+ queueObservationQualification(observation);
8511
+ if (resolveObservationVisibility(observation) === "project") {
8512
+ queueClaimDerivation(observation);
8513
+ }
8349
8514
  const obsId = observation.id;
8350
8515
  vectorMissingIds.add(obsId);
8351
8516
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
@@ -8418,6 +8583,10 @@ async function upsertObservation(existing, input, now3) {
8418
8583
  if (input.progress) existing.progress = input.progress;
8419
8584
  if (input.sourceDetail !== void 0) existing.sourceDetail = input.sourceDetail;
8420
8585
  if (input.valueCategory !== void 0) existing.valueCategory = input.valueCategory;
8586
+ if (input.admissionState !== void 0) existing.admissionState = input.admissionState;
8587
+ if (input.admissionReason !== void 0) existing.admissionReason = sanitizeCredentials(input.admissionReason);
8588
+ if (input.visibility !== void 0) existing.visibility = input.visibility;
8589
+ if (input.sharedWithAgentIds !== void 0) existing.sharedWithAgentIds = input.sharedWithAgentIds;
8421
8590
  const doc = {
8422
8591
  id: makeOramaObservationId(existing.projectId, existing.id),
8423
8592
  observationId: existing.id,
@@ -8436,7 +8605,12 @@ async function upsertObservation(existing, input, now3) {
8436
8605
  status: "active",
8437
8606
  source: existing.source ?? "agent",
8438
8607
  sourceDetail: existing.sourceDetail ?? "",
8439
- valueCategory: existing.valueCategory ?? ""
8608
+ valueCategory: existing.valueCategory ?? "",
8609
+ admissionState: existing.admissionState ?? "",
8610
+ admissionReason: existing.admissionReason ?? "",
8611
+ visibility: existing.visibility ?? "project",
8612
+ createdByAgentId: existing.createdByAgentId ?? "",
8613
+ sharedWithAgentIds: JSON.stringify(existing.sharedWithAgentIds ?? [])
8440
8614
  };
8441
8615
  const oramaId = makeOramaObservationId(existing.projectId, existing.id);
8442
8616
  try {
@@ -8459,7 +8633,10 @@ async function upsertObservation(existing, input, now3) {
8459
8633
  await store.update(existing);
8460
8634
  }
8461
8635
  await bindObservationCodeRefsBestEffort(existing);
8462
- queueClaimDerivation(existing);
8636
+ queueObservationQualification(existing);
8637
+ if (resolveObservationVisibility(existing) === "project") {
8638
+ queueClaimDerivation(existing);
8639
+ }
8463
8640
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
8464
8641
  const obsId = existing.id;
8465
8642
  vectorMissingIds.add(obsId);
@@ -8498,6 +8675,56 @@ async function upsertObservation(existing, input, now3) {
8498
8675
  function getObservation(id, projectId) {
8499
8676
  return observations.find((o) => o.id === id && (projectId ? o.projectId === projectId : true));
8500
8677
  }
8678
+ async function updateObservationAdmission(input) {
8679
+ await ensureFreshObservations();
8680
+ const cached = observations.find(
8681
+ (observation) => observation.id === input.observationId && (!input.projectId || observation.projectId === input.projectId)
8682
+ );
8683
+ if (!cached) return void 0;
8684
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
8685
+ const admissionReason = sanitizeCredentials(input.admissionReason).slice(0, 240);
8686
+ const apply = (current) => {
8687
+ if (input.projectId && current.projectId !== input.projectId) return void 0;
8688
+ if (input.expectedState && current.admissionState !== input.expectedState) return void 0;
8689
+ return {
8690
+ ...current,
8691
+ admissionState: input.admissionState,
8692
+ admissionReason,
8693
+ ...input.visibility !== void 0 ? { visibility: input.visibility } : {},
8694
+ updatedAt: now3
8695
+ };
8696
+ };
8697
+ let updated;
8698
+ if (projectDir) {
8699
+ const store = getObservationStore();
8700
+ await store.atomic(async (tx) => {
8701
+ const current = await tx.getById(input.observationId);
8702
+ if (!current) return;
8703
+ const next = apply(current);
8704
+ if (!next) return;
8705
+ await tx.update(next);
8706
+ updated = next;
8707
+ });
8708
+ } else {
8709
+ updated = apply(cached);
8710
+ }
8711
+ if (!updated) return void 0;
8712
+ observations = observations.map(
8713
+ (observation) => observation.id === updated.id && observation.projectId === updated.projectId ? updated : observation
8714
+ );
8715
+ try {
8716
+ await updateObservationMetadata(updated.projectId, updated.id, {
8717
+ admissionState: updated.admissionState,
8718
+ admissionReason: updated.admissionReason,
8719
+ ...updated.visibility !== void 0 ? { visibility: updated.visibility } : {}
8720
+ });
8721
+ } catch {
8722
+ }
8723
+ if (updated.admissionState === "qualified" && resolveObservationVisibility(updated) === "project") {
8724
+ queueClaimDerivation(updated);
8725
+ }
8726
+ return updated;
8727
+ }
8501
8728
  async function resolveObservations(ids, status = "resolved") {
8502
8729
  const resolved = [];
8503
8730
  const notFound = [];
@@ -8537,7 +8764,12 @@ async function resolveObservations(ids, status = "resolved") {
8537
8764
  status,
8538
8765
  source: obs.source ?? "agent",
8539
8766
  sourceDetail: obs.sourceDetail ?? "",
8540
- valueCategory: obs.valueCategory ?? ""
8767
+ valueCategory: obs.valueCategory ?? "",
8768
+ admissionState: obs.admissionState ?? "",
8769
+ admissionReason: obs.admissionReason ?? "",
8770
+ visibility: obs.visibility ?? "project",
8771
+ createdByAgentId: obs.createdByAgentId ?? "",
8772
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
8541
8773
  };
8542
8774
  await insertObservation(doc);
8543
8775
  const obsId = obs.id;
@@ -8667,6 +8899,11 @@ async function reindexObservations() {
8667
8899
  source: obs.source ?? "agent",
8668
8900
  sourceDetail: obs.sourceDetail ?? "",
8669
8901
  valueCategory: obs.valueCategory ?? "",
8902
+ admissionState: obs.admissionState ?? "",
8903
+ admissionReason: obs.admissionReason ?? "",
8904
+ visibility: obs.visibility ?? "project",
8905
+ createdByAgentId: obs.createdByAgentId ?? "",
8906
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
8670
8907
  ...compatibleEmbedding ? { embedding: compatibleEmbedding } : {}
8671
8908
  };
8672
8909
  await insertObservation(doc);
@@ -8739,6 +8976,7 @@ async function probeSearchIndex(projectId) {
8739
8976
  await searchObservations({
8740
8977
  query: "semantic memory retrieval status",
8741
8978
  projectId,
8979
+ reader: { projectId },
8742
8980
  limit: 1,
8743
8981
  status: "all",
8744
8982
  trackAccess: false
@@ -8804,6 +9042,11 @@ async function backfillVectorEmbeddings(options = {}) {
8804
9042
  source: obs.source ?? "agent",
8805
9043
  sourceDetail: obs.sourceDetail ?? "",
8806
9044
  valueCategory: obs.valueCategory ?? "",
9045
+ admissionState: obs.admissionState ?? "",
9046
+ admissionReason: obs.admissionReason ?? "",
9047
+ visibility: obs.visibility ?? "project",
9048
+ createdByAgentId: obs.createdByAgentId ?? "",
9049
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
8807
9050
  embedding
8808
9051
  };
8809
9052
  await insertObservation(doc);
@@ -8845,6 +9088,7 @@ var init_observations = __esm({
8845
9088
  init_provider();
8846
9089
  init_secret_filter();
8847
9090
  init_lifecycle();
9091
+ init_visibility();
8848
9092
  observations = [];
8849
9093
  nextId = 1;
8850
9094
  projectDir = null;
@@ -9062,6 +9306,36 @@ var init_session_store = __esm({
9062
9306
  }
9063
9307
  });
9064
9308
 
9309
+ // src/memory/graph-scope.ts
9310
+ function projectGraphEntityNames(observations2) {
9311
+ const entityNames = /* @__PURE__ */ new Set();
9312
+ for (const observation of observations2) {
9313
+ if ((observation.status ?? "active") !== "active") continue;
9314
+ const entityName = observation.entityName?.trim();
9315
+ if (entityName) entityNames.add(entityName);
9316
+ for (const relatedEntity of observation.relatedEntities ?? []) {
9317
+ const name = relatedEntity.trim();
9318
+ if (name) entityNames.add(name);
9319
+ }
9320
+ }
9321
+ return entityNames;
9322
+ }
9323
+ function scopeKnowledgeGraphToProject(graph, observations2) {
9324
+ const entityNames = projectGraphEntityNames(observations2);
9325
+ const entities = graph.entities.filter((entity) => entityNames.has(entity.name));
9326
+ const visibleNames = new Set(entities.map((entity) => entity.name));
9327
+ const relations = graph.relations.filter(
9328
+ (relation) => visibleNames.has(relation.from) && visibleNames.has(relation.to)
9329
+ );
9330
+ return { entities, relations, entityNames };
9331
+ }
9332
+ var init_graph_scope = __esm({
9333
+ "src/memory/graph-scope.ts"() {
9334
+ "use strict";
9335
+ init_esm_shims();
9336
+ }
9337
+ });
9338
+
9065
9339
  // src/memory/disclosure-policy.ts
9066
9340
  function resolveEvidenceBasis(fields) {
9067
9341
  if (fields.commitHash || fields.source === "git" || fields.sourceDetail === "git-ingest") {
@@ -9094,8 +9368,9 @@ function resolveSourceDetail(sourceDetail, source) {
9094
9368
  return void 0;
9095
9369
  }
9096
9370
  function classifyLayer(fields) {
9097
- const { valueCategory } = fields;
9371
+ const { valueCategory, admissionState } = fields;
9098
9372
  const sd = resolveSourceDetail(fields.sourceDetail, fields.source);
9373
+ if (admissionState === "candidate" || admissionState === "ephemeral") return "L1";
9099
9374
  if (valueCategory === "core") return "L2";
9100
9375
  if (sd === "hook") return "L1";
9101
9376
  if (sd === "git-ingest") return "L3";
@@ -9153,6 +9428,7 @@ function getEffectiveRetentionDays(doc) {
9153
9428
  }
9154
9429
  function isImmune(doc) {
9155
9430
  if (doc.type === "probe") return false;
9431
+ if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return false;
9156
9432
  if (doc.valueCategory === "core") return true;
9157
9433
  const importance = getImportanceLevel(doc);
9158
9434
  if (importance === "critical") return true;
@@ -9162,6 +9438,7 @@ function isImmune(doc) {
9162
9438
  }
9163
9439
  function getImmunityReason(doc) {
9164
9440
  if (doc.type === "probe") return null;
9441
+ if (doc.admissionState === "candidate" || doc.admissionState === "ephemeral") return null;
9165
9442
  if (doc.valueCategory === "core") return "core valueCategory (formation-classified)";
9166
9443
  const importance = getImportanceLevel(doc);
9167
9444
  if (importance === "critical") return "critical importance";
@@ -9294,7 +9571,9 @@ function toRetentionDocument(obs, accessMap) {
9294
9571
  status: obs.status ?? "active",
9295
9572
  source: obs.source ?? "agent",
9296
9573
  sourceDetail: obs.sourceDetail ?? "",
9297
- valueCategory: obs.valueCategory ?? ""
9574
+ valueCategory: obs.valueCategory ?? "",
9575
+ admissionState: obs.admissionState ?? "",
9576
+ admissionReason: obs.admissionReason ?? ""
9298
9577
  };
9299
9578
  }
9300
9579
  async function archiveExpiredBatch(_projectDir, options) {
@@ -9308,7 +9587,7 @@ async function archiveExpiredBatch(_projectDir, options) {
9308
9587
  });
9309
9588
  const hasMore = page.length > limit;
9310
9589
  const scanned = hasMore ? page.slice(0, limit) : page;
9311
- const candidateIds = scanned.filter((observation) => getRetentionZone(
9590
+ const candidateIds = scanned.filter((observation) => !options.reader || canManageObservation(observation, options.reader)).filter((observation) => getRetentionZone(
9312
9591
  toRetentionDocument(observation, options.accessMap),
9313
9592
  options.referenceTime
9314
9593
  ) === "archive-candidate").map((observation) => observation.id);
@@ -9321,7 +9600,7 @@ async function archiveExpiredBatch(_projectDir, options) {
9321
9600
  const nextCursor = hasMore && scanned.length > 0 ? scanned[scanned.length - 1].id : void 0;
9322
9601
  return nextCursor === void 0 ? { archived, scanned: scanned.length } : { archived, scanned: scanned.length, nextCursor };
9323
9602
  }
9324
- async function archiveExpired(projectDir2, referenceTime, accessMap, projectId) {
9603
+ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId, reader) {
9325
9604
  const store = getObservationStore();
9326
9605
  if (projectId) {
9327
9606
  let afterId;
@@ -9331,12 +9610,14 @@ async function archiveExpired(projectDir2, referenceTime, accessMap, projectId)
9331
9610
  projectId,
9332
9611
  afterId,
9333
9612
  referenceTime,
9334
- accessMap
9613
+ accessMap,
9614
+ reader
9335
9615
  });
9336
9616
  archived += batch.archived;
9337
9617
  afterId = batch.nextCursor;
9338
9618
  } while (afterId !== void 0);
9339
- const remaining = await store.countByProject(projectId, { status: "active" });
9619
+ const remainingObservations = await store.loadByProject(projectId, { status: "active" });
9620
+ const remaining = reader ? remainingObservations.filter((observation) => canManageObservation(observation, reader)).length : remainingObservations.length;
9340
9621
  return { archived, remaining };
9341
9622
  }
9342
9623
  return await store.atomic(async (tx) => {
@@ -9366,6 +9647,7 @@ var init_retention = __esm({
9366
9647
  "use strict";
9367
9648
  init_esm_shims();
9368
9649
  init_obs_store();
9650
+ init_visibility();
9369
9651
  RETENTION_DAYS = {
9370
9652
  critical: 365,
9371
9653
  high: 180,
@@ -9401,6 +9683,49 @@ var init_retention = __esm({
9401
9683
  }
9402
9684
  });
9403
9685
 
9686
+ // src/memory/admission.ts
9687
+ var admission_exports = {};
9688
+ __export(admission_exports, {
9689
+ isCandidateObservation: () => isCandidateObservation,
9690
+ isEligibleForAutomaticDelivery: () => isEligibleForAutomaticDelivery,
9691
+ isEligibleForKnowledgePromotion: () => isEligibleForKnowledgePromotion,
9692
+ qualifyCandidateFromCurrentCode: () => qualifyCandidateFromCurrentCode
9693
+ });
9694
+ function isEligibleForAutomaticDelivery(observation) {
9695
+ return observation.admissionState !== "ephemeral" && observation.admissionState !== "candidate";
9696
+ }
9697
+ function isCandidateObservation(observation) {
9698
+ return observation.admissionState === "candidate";
9699
+ }
9700
+ function isEligibleForKnowledgePromotion(observation) {
9701
+ return isEligibleForAutomaticDelivery(observation) && observation.valueCategory !== "ephemeral" && resolveObservationVisibility(observation) === "project";
9702
+ }
9703
+ function qualifyCandidateFromCurrentCode(input) {
9704
+ if (!isCandidateObservation(input.observation)) return void 0;
9705
+ if (input.observation.valueCategory === "ephemeral") return void 0;
9706
+ if (input.currentCodeReferenceCount <= 0) return void 0;
9707
+ const typeLabel = DURABLE_AUTOMATIC_TYPES.has(input.observation.type) ? "high-value automatic record" : "automatic record";
9708
+ return {
9709
+ admissionState: "qualified",
9710
+ admissionReason: `${typeLabel} qualified against ${input.currentCodeReferenceCount} current Code Memory reference(s)`
9711
+ };
9712
+ }
9713
+ var DURABLE_AUTOMATIC_TYPES;
9714
+ var init_admission = __esm({
9715
+ "src/memory/admission.ts"() {
9716
+ "use strict";
9717
+ init_esm_shims();
9718
+ init_visibility();
9719
+ DURABLE_AUTOMATIC_TYPES = /* @__PURE__ */ new Set([
9720
+ "decision",
9721
+ "gotcha",
9722
+ "problem-solution",
9723
+ "trade-off",
9724
+ "why-it-exists"
9725
+ ]);
9726
+ }
9727
+ });
9728
+
9404
9729
  // src/workspace/workflow-sync.ts
9405
9730
  import matter9 from "gray-matter";
9406
9731
  var WorkflowSyncer;
@@ -10801,6 +11126,7 @@ var init_isolated_maintenance = __esm({
10801
11126
  "retention-archive",
10802
11127
  "consolidation",
10803
11128
  "codegraph-refresh",
11129
+ "observation-qualify",
10804
11130
  "claim-derive",
10805
11131
  "claim-requalification",
10806
11132
  "knowledge-compile",
@@ -11236,9 +11562,10 @@ async function loadConsolidationPage(projectId, options) {
11236
11562
  return hasMore && observations2.length > 0 ? { observations: observations2, nextCursor: observations2[observations2.length - 1].id } : { observations: observations2 };
11237
11563
  }
11238
11564
  function findClusters(observations2, threshold) {
11239
- if (observations2.length < MIN_CLUSTER_SIZE) return [];
11565
+ const eligible = observations2.filter(isEligibleForAutomaticDelivery).filter((observation) => resolveObservationVisibility(observation) === "project");
11566
+ if (eligible.length < MIN_CLUSTER_SIZE) return [];
11240
11567
  const groups = /* @__PURE__ */ new Map();
11241
- for (const obs of observations2) {
11568
+ for (const obs of eligible) {
11242
11569
  const key = `${obs.entityName}::${obs.type}`;
11243
11570
  const group = groups.get(key) ?? [];
11244
11571
  group.push(obs);
@@ -11375,6 +11702,8 @@ var init_consolidation = __esm({
11375
11702
  "use strict";
11376
11703
  init_esm_shims();
11377
11704
  init_obs_store();
11705
+ init_admission();
11706
+ init_visibility();
11378
11707
  DEFAULT_SIMILARITY_THRESHOLD = 0.45;
11379
11708
  HIGH_VALUE_SIMILARITY_THRESHOLD = 0.85;
11380
11709
  HIGH_VALUE_TYPES = /* @__PURE__ */ new Set(["gotcha", "decision", "trade-off", "reasoning", "problem-solution"]);
@@ -12174,6 +12503,7 @@ __export(claims_exports, {
12174
12503
  buildClaimIdentity: () => buildClaimIdentity,
12175
12504
  deriveLowRiskClaimsFromObservation: () => deriveLowRiskClaimsFromObservation,
12176
12505
  requalifyClaimsForCodeState: () => requalifyClaimsForCodeState,
12506
+ reviewClaim: () => reviewClaim,
12177
12507
  selectClaimsForTask: () => selectClaimsForTask,
12178
12508
  supersedeClaim: () => supersedeClaim,
12179
12509
  writeClaim: () => writeClaim
@@ -12360,6 +12690,39 @@ function writeClaim(store, input) {
12360
12690
  };
12361
12691
  });
12362
12692
  }
12693
+ function reviewClaim(store, input) {
12694
+ if (input.reviewState !== "approved" && input.reviewState !== "rejected") {
12695
+ throw new Error("A claim review must approve or reject the claim");
12696
+ }
12697
+ const detail = compactText(input.detail, "review detail");
12698
+ return store.transaction(() => {
12699
+ const claim = store.getClaim(input.claimId);
12700
+ if (!claim) throw new Error("Claim was not found");
12701
+ if (input.reviewState === "approved" && claim.status === "superseded") {
12702
+ throw new Error("A superseded claim cannot be approved");
12703
+ }
12704
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
12705
+ const nextStatus = input.reviewState === "rejected" ? "unknown" : claim.status === "unknown" && claim.reviewState === "rejected" ? "active" : claim.status;
12706
+ const updated = {
12707
+ ...claim,
12708
+ status: nextStatus,
12709
+ reviewState: input.reviewState,
12710
+ updatedAt: now3
12711
+ };
12712
+ store.updateClaim(updated);
12713
+ store.recordEvent({
12714
+ projectId: claim.projectId,
12715
+ claimId: claim.id,
12716
+ kind: "reviewed",
12717
+ fromStatus: claim.status,
12718
+ toStatus: updated.status,
12719
+ detail: "Review: " + claim.reviewState + " -> " + input.reviewState + ". " + detail,
12720
+ createdAt: now3
12721
+ });
12722
+ reconcileConflicts(store, claim.projectId, claim.conflictKey, now3);
12723
+ return store.getClaim(claim.id) ?? updated;
12724
+ });
12725
+ }
12363
12726
  function supersedeClaim(store, input) {
12364
12727
  const evidence = validateEvidence(input.evidence);
12365
12728
  return store.transaction(() => {
@@ -12448,7 +12811,7 @@ function deriveLowRiskClaimsFromObservation(store, observation, codeStore) {
12448
12811
  scope: "project",
12449
12812
  confidence,
12450
12813
  observedAt: observation.updatedAt ?? observation.createdAt,
12451
- reviewState: "approved",
12814
+ reviewState: isGit ? "approved" : "needs-review",
12452
12815
  origin: isGit ? "git" : "derived",
12453
12816
  evidence
12454
12817
  });
@@ -13324,81 +13687,425 @@ var init_workflow_store = __esm({
13324
13687
  }
13325
13688
  });
13326
13689
 
13327
- // src/knowledge/workflows.ts
13328
- var workflows_exports = {};
13329
- __export(workflows_exports, {
13330
- WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
13331
- applyWorkflowAdapter: () => applyWorkflowAdapter,
13332
- importWindsurfWorkflows: () => importWindsurfWorkflows,
13333
- parseWorkflowMarkdown: () => parseWorkflowMarkdown,
13334
- previewWorkflowAdapter: () => previewWorkflowAdapter,
13335
- recordWorkflowRun: () => recordWorkflowRun,
13336
- renderWorkflowMarkdown: () => renderWorkflowMarkdown,
13337
- selectWorkflows: () => selectWorkflows,
13338
- selectWorkspaceWorkflows: () => selectWorkspaceWorkflows,
13339
- syncCanonicalWorkflows: () => syncCanonicalWorkflows,
13340
- writeCanonicalWorkflow: () => writeCanonicalWorkflow
13690
+ // src/codegraph/task-lens.ts
13691
+ var task_lens_exports = {};
13692
+ __export(task_lens_exports, {
13693
+ containsTaskKeyword: () => containsTaskKeyword,
13694
+ lensPathCandidates: () => lensPathCandidates,
13695
+ lensVerificationHints: () => lensVerificationHints,
13696
+ rankLensPaths: () => rankLensPaths,
13697
+ rankLensSources: () => rankLensSources,
13698
+ resolveTaskLens: () => resolveTaskLens,
13699
+ scoreLensSource: () => scoreLensSource,
13700
+ shouldShowLensSource: () => shouldShowLensSource
13341
13701
  });
13342
- import { createHash as createHash12 } from "crypto";
13343
- import { promises as fs14 } from "fs";
13344
- import path17 from "path";
13345
- import matter11 from "gray-matter";
13346
- function hash3(value) {
13347
- return createHash12("sha256").update(value).digest("hex");
13348
- }
13349
- function now2() {
13350
- return (/* @__PURE__ */ new Date()).toISOString();
13351
- }
13352
- function slug(value) {
13353
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 72);
13354
- return normalized || "workflow";
13702
+ function normalizePath2(path25) {
13703
+ return path25.replace(/\\/g, "/");
13355
13704
  }
13356
- function titleFromName(name) {
13357
- return name.replace(/[-_]+/g, " ").split(" ").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ") || "Imported Workflow";
13705
+ function tokenize2(text) {
13706
+ return (text.toLowerCase().match(/[a-z0-9_./-]+|[\u4e00-\u9fff]+/g) ?? []).map((token) => token.trim()).filter((token) => token.length > 1 && !STOP_WORDS.has(token));
13358
13707
  }
13359
- function requiredText(data, key) {
13360
- const value = data[key];
13361
- if (typeof value !== "string" || !value.trim()) {
13362
- throw new Error("workflow frontmatter requires " + key);
13708
+ function containsTaskKeyword(text, keyword) {
13709
+ const matcher = /^[a-z0-9_-]+$/i.test(keyword) ? new RegExp(`(^|[^a-z0-9_-])${keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?=[^a-z0-9_-]|$)`, "gi") : new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
13710
+ let match;
13711
+ while ((match = matcher.exec(text)) !== null) {
13712
+ const keywordIndex = match.index + (match[1]?.length ?? 0);
13713
+ if (!isNegatedTaskKeyword(text, keywordIndex)) return true;
13363
13714
  }
13364
- return value.trim();
13715
+ return false;
13365
13716
  }
13366
- function optionalText4(data, key) {
13367
- const value = data[key];
13368
- if (value === void 0 || value === null || value === "") return void 0;
13369
- if (typeof value !== "string") throw new Error("workflow frontmatter field " + key + " must be text");
13370
- return value.trim() || void 0;
13717
+ function isNegatedTaskKeyword(text, keywordIndex) {
13718
+ const boundary = Math.max(
13719
+ text.lastIndexOf(".", keywordIndex - 1),
13720
+ text.lastIndexOf("!", keywordIndex - 1),
13721
+ text.lastIndexOf("?", keywordIndex - 1),
13722
+ text.lastIndexOf(";", keywordIndex - 1),
13723
+ text.lastIndexOf("\n", keywordIndex - 1),
13724
+ text.lastIndexOf("\u3002", keywordIndex - 1),
13725
+ text.lastIndexOf("\uFF01", keywordIndex - 1),
13726
+ text.lastIndexOf("\uFF1F", keywordIndex - 1),
13727
+ text.lastIndexOf("\uFF1B", keywordIndex - 1)
13728
+ );
13729
+ let prefix = text.slice(boundary + 1, keywordIndex).toLowerCase();
13730
+ const contrast = /(?:,|\uff0c)\s*(?:but|however|instead|yet|\u4f46\u662f|\u4f46|\u800c\u662f)\s*/gi;
13731
+ let contrastMatch;
13732
+ let contrastEnd = -1;
13733
+ while ((contrastMatch = contrast.exec(prefix)) !== null) contrastEnd = contrast.lastIndex;
13734
+ if (contrastEnd >= 0) prefix = prefix.slice(contrastEnd);
13735
+ if (/\b(?:do|does|did|should|must|can|could|will|would|may|might)\s+not\b/.test(prefix)) return true;
13736
+ if (/\b(?:don't|dont|never|without|avoid|skip)\b/.test(prefix)) return true;
13737
+ if (/(?:^|[\s,])no\s+(?:[a-z0-9_-]+\s*){0,4}$/i.test(prefix)) return true;
13738
+ const chineseClauseStart = Math.max(prefix.lastIndexOf(","), prefix.lastIndexOf("\uFF0C"), prefix.lastIndexOf("\u3001"));
13739
+ const chineseClause = prefix.slice(chineseClauseStart + 1);
13740
+ return /(?:\u4e0d\u8981|\u4e0d\u5e94|\u4e0d\u53ef|\u4e0d\u80fd|\u4e0d\u4f1a|\u7981\u6b62|\u52ff|\u4e0d)\s*(?:\u7acb\u5373|\u76f4\u63a5|\u518d|\u73b0\u5728|\u64c5\u81ea|\u5148)?\s*$/.test(chineseClause);
13371
13741
  }
13372
- function optionalStringArray(data, key) {
13373
- const value = data[key];
13374
- if (value === void 0 || value === null) return [];
13375
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
13376
- throw new Error("workflow frontmatter field " + key + " must be a string array");
13742
+ function resolveTaskLens(task) {
13743
+ const normalized = (task ?? "").toLowerCase();
13744
+ if (!normalized.trim()) return LENSES.general;
13745
+ let best = {
13746
+ id: "general",
13747
+ score: 0,
13748
+ priority: Number.MAX_SAFE_INTEGER
13749
+ };
13750
+ for (const id of LENS_PRIORITY) {
13751
+ const score = KEYWORDS[id].reduce((sum, keyword) => sum + (containsTaskKeyword(normalized, keyword) ? 1 : 0), 0);
13752
+ const priority = LENS_PRIORITY.indexOf(id);
13753
+ if (score > best.score || score === best.score && score > 0 && priority < best.priority) {
13754
+ best = { id, score, priority };
13755
+ }
13377
13756
  }
13378
- return [...new Set(value.map((item) => item.trim()).filter(Boolean))];
13757
+ return best.score > 0 ? LENSES[best.id] : LENSES.general;
13379
13758
  }
13380
- function optionalNumber(data, key, fallback) {
13381
- const value = data[key];
13382
- if (value === void 0 || value === null || value === "") return fallback;
13383
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
13384
- throw new Error("workflow frontmatter field " + key + " must be a positive integer");
13759
+ function pathKindScore(path25, lens) {
13760
+ const normalized = normalizePath2(path25).toLowerCase();
13761
+ const name = normalized.split("/").pop() ?? normalized;
13762
+ const inDocs = normalized.startsWith("docs/") || name === "readme.md" || name.includes("readme");
13763
+ const isTest = /(^|\/)(tests?|__tests__|spec|fixtures?)\//.test(normalized) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(normalized) || /\.(test|spec)\.py$/.test(normalized);
13764
+ const isSource = normalized.startsWith("src/") || normalized.includes("/src/");
13765
+ const isPackage = name === "package.json" || name === "package-lock.json" || name === "pnpm-lock.yaml" || name === "yarn.lock";
13766
+ const isChangelog = name === "changelog.md" || name === "changes.md";
13767
+ const isWorkflow = normalized.startsWith(".github/workflows/");
13768
+ switch (lens.id) {
13769
+ case "bugfix":
13770
+ return (isTest ? 80 : 0) + (isSource ? 50 : 0) + (normalized.includes("debug") ? 20 : 0);
13771
+ case "test":
13772
+ return (isTest ? 90 : 0) + (isSource ? 45 : 0);
13773
+ case "release":
13774
+ return (isChangelog ? 100 : 0) + (isPackage ? 90 : 0) + (isWorkflow ? 70 : 0) + (inDocs ? 35 : 0);
13775
+ case "onboarding":
13776
+ return (name === "readme.md" ? 100 : 0) + (inDocs ? 80 : 0) + (isPackage ? 45 : 0) + (isSource ? 20 : 0);
13777
+ case "docs":
13778
+ return (inDocs ? 100 : 0) + (isChangelog ? 60 : 0) + (isSource ? 25 : 0);
13779
+ case "refactor":
13780
+ return (isSource ? 70 : 0) + (isTest ? 65 : 0);
13781
+ case "feature":
13782
+ return (isSource ? 80 : 0) + (isTest ? 45 : 0) + (inDocs ? 20 : 0);
13783
+ default:
13784
+ if (isSource || isTest) return 60;
13785
+ if (normalized.startsWith("packages/") && normalized.includes("/src/")) return 45;
13786
+ if (inDocs) return 20;
13787
+ return 0;
13385
13788
  }
13386
- return value;
13387
13789
  }
13388
- function optionalStatus(data) {
13389
- const value = data.status;
13390
- if (value === void 0 || value === null || value === "") return "draft";
13391
- if (typeof value !== "string" || !STATUS_VALUES.includes(value)) {
13392
- throw new Error("workflow frontmatter field status is invalid");
13393
- }
13394
- return value;
13790
+ function tokenScore(text, tokens) {
13791
+ if (tokens.length === 0) return 0;
13792
+ const normalized = text.toLowerCase();
13793
+ return tokens.reduce((sum, token) => sum + (normalized.includes(token) ? 12 : 0), 0);
13395
13794
  }
13396
- function normalizeAgents(values) {
13397
- const unknown = values.filter((value) => !KNOWN_AGENTS.includes(value));
13398
- if (unknown.length > 0) {
13399
- throw new Error("workflow frontmatter has unsupported agent: " + unknown[0]);
13400
- }
13401
- return values;
13795
+ function sourceTaskMatchScore(source, task) {
13796
+ const tokens = tokenize2(task ?? "");
13797
+ if (tokens.length === 0) return 0;
13798
+ const text = [
13799
+ source.title,
13800
+ source.path ?? "",
13801
+ source.symbol ?? ""
13802
+ ].join("\n").toLowerCase();
13803
+ const matches = tokens.filter((token) => text.includes(token)).length;
13804
+ if (matches >= 2) return 40;
13805
+ if (matches === 1) return 24;
13806
+ return 0;
13807
+ }
13808
+ function fallbackPathRank(path25) {
13809
+ const normalized = normalizePath2(path25);
13810
+ if (normalized.startsWith("src/")) return 0;
13811
+ if (normalized.startsWith("tests/") || normalized.startsWith("test/")) return 1;
13812
+ if (normalized.startsWith("packages/") && normalized.includes("/src/")) return 2;
13813
+ if (normalized.startsWith("docs/") || normalized.toLowerCase() === "readme.md") return 3;
13814
+ return 4;
13815
+ }
13816
+ function rankLensPaths(paths, lens, task) {
13817
+ const tokens = tokenize2(task ?? "");
13818
+ return [...new Set(paths.map(normalizePath2))].map((path25, index) => ({
13819
+ path: path25,
13820
+ index,
13821
+ score: pathKindScore(path25, lens) + tokenScore(path25, tokens),
13822
+ fallback: fallbackPathRank(path25)
13823
+ })).sort((a, b) => b.score - a.score || a.fallback - b.fallback || a.index - b.index || a.path.localeCompare(b.path)).map((item) => item.path);
13824
+ }
13825
+ function lensPathCandidates(lens) {
13826
+ switch (lens.id) {
13827
+ case "release":
13828
+ return ["CHANGELOG.md", "package.json", "package-lock.json", ".github/workflows/ci.yml", ".github/workflows/test.yml", "README.md"];
13829
+ case "onboarding":
13830
+ return ["README.md", "docs/README.md", "docs/API_REFERENCE.md", "package.json"];
13831
+ case "docs":
13832
+ return ["README.md", "README.zh-CN.md", "docs/README.md", "docs/API_REFERENCE.md", "CHANGELOG.md"];
13833
+ case "test":
13834
+ case "bugfix":
13835
+ return ["tests", "test"];
13836
+ default:
13837
+ return [];
13838
+ }
13839
+ }
13840
+ function scoreLensSource(source, lens, task) {
13841
+ const tokens = tokenize2(task ?? "");
13842
+ const text = [source.title, source.type, source.path ?? "", source.symbol ?? ""].join("\n");
13843
+ return pathKindScore(source.path ?? "", lens) + tokenScore(text, tokens) + sourceTaskMatchScore(source, task);
13844
+ }
13845
+ function rankLensSources(sources, lens, task) {
13846
+ return [...sources].map((source, index) => ({
13847
+ source,
13848
+ index,
13849
+ score: scoreLensSource(source, lens, task)
13850
+ })).sort((a, b) => b.score - a.score || a.source.observationId - b.source.observationId || a.index - b.index).map((item) => item.source);
13851
+ }
13852
+ function lensVerificationHints(lens) {
13853
+ switch (lens.id) {
13854
+ case "bugfix":
13855
+ return [
13856
+ "run the smallest failing test or repro first",
13857
+ "inspect the changed code path before trusting old memory"
13858
+ ];
13859
+ case "test":
13860
+ return [
13861
+ "run the exact focused test file or test name first",
13862
+ "inspect fixtures and harness setup before changing assertions"
13863
+ ];
13864
+ case "release":
13865
+ return [
13866
+ "run build, tests, package smoke, and publish dry-run where available",
13867
+ "verify package metadata, changelog, and Git state before publishing"
13868
+ ];
13869
+ case "onboarding":
13870
+ return [
13871
+ "read the docs/start files first, then inspect only the code paths needed for the task",
13872
+ "treat old implementation memories as leads until current code confirms them"
13873
+ ];
13874
+ case "refactor":
13875
+ return [
13876
+ "inspect call sites and tests before editing shared code",
13877
+ "run the narrow affected test plus one regression smoke"
13878
+ ];
13879
+ case "docs":
13880
+ return [
13881
+ "check headings, links, commands, and examples against current code",
13882
+ "run the smallest docs or package smoke available"
13883
+ ];
13884
+ case "feature":
13885
+ return [
13886
+ "inspect the closest existing implementation pattern before adding new code",
13887
+ "run focused tests plus one user-flow smoke after changes"
13888
+ ];
13889
+ default:
13890
+ return [
13891
+ "inspect the Start here files before editing",
13892
+ "run the smallest relevant test or smoke command after changes"
13893
+ ];
13894
+ }
13895
+ }
13896
+ function shouldShowLensSource(source, lens, task) {
13897
+ if (lens.id === "general") return true;
13898
+ const score = scoreLensSource(source, lens, task);
13899
+ if (lens.id === "onboarding" || lens.id === "release" || lens.id === "docs") return score >= 40;
13900
+ return score > 0;
13901
+ }
13902
+ var STOP_WORDS, LENSES, KEYWORDS, LENS_PRIORITY;
13903
+ var init_task_lens = __esm({
13904
+ "src/codegraph/task-lens.ts"() {
13905
+ "use strict";
13906
+ init_esm_shims();
13907
+ STOP_WORDS = /* @__PURE__ */ new Set([
13908
+ "a",
13909
+ "an",
13910
+ "and",
13911
+ "as",
13912
+ "for",
13913
+ "in",
13914
+ "into",
13915
+ "of",
13916
+ "on",
13917
+ "onto",
13918
+ "the",
13919
+ "this",
13920
+ "that",
13921
+ "to",
13922
+ "with",
13923
+ "work",
13924
+ "project",
13925
+ "continue",
13926
+ "\u7EE7\u7EED",
13927
+ "\u9879\u76EE"
13928
+ ]);
13929
+ LENSES = {
13930
+ bugfix: {
13931
+ id: "bugfix",
13932
+ description: "debug the failure with current code and the smallest repro first",
13933
+ sourceLimit: 6,
13934
+ cautionLimit: 4,
13935
+ hideUnrelatedCautionDetails: true,
13936
+ hideUnrelatedReliableDetails: false
13937
+ },
13938
+ feature: {
13939
+ id: "feature",
13940
+ description: "build a scoped feature from nearby source, types, and user flow",
13941
+ sourceLimit: 6,
13942
+ cautionLimit: 3,
13943
+ hideUnrelatedCautionDetails: true,
13944
+ hideUnrelatedReliableDetails: false
13945
+ },
13946
+ release: {
13947
+ id: "release",
13948
+ description: "prepare a release using current metadata, changelog, build, and package checks",
13949
+ sourceLimit: 4,
13950
+ cautionLimit: 2,
13951
+ hideUnrelatedCautionDetails: true,
13952
+ hideUnrelatedReliableDetails: true
13953
+ },
13954
+ onboarding: {
13955
+ id: "onboarding",
13956
+ description: "understand the project shape before trusting old implementation details",
13957
+ sourceLimit: 4,
13958
+ cautionLimit: 2,
13959
+ hideUnrelatedCautionDetails: true,
13960
+ hideUnrelatedReliableDetails: true
13961
+ },
13962
+ refactor: {
13963
+ id: "refactor",
13964
+ description: "change structure carefully by reading shared code, call sites, and tests",
13965
+ sourceLimit: 6,
13966
+ cautionLimit: 4,
13967
+ hideUnrelatedCautionDetails: true,
13968
+ hideUnrelatedReliableDetails: false
13969
+ },
13970
+ docs: {
13971
+ id: "docs",
13972
+ description: "update documentation against current code and public entry points",
13973
+ sourceLimit: 5,
13974
+ cautionLimit: 2,
13975
+ hideUnrelatedCautionDetails: true,
13976
+ hideUnrelatedReliableDetails: true
13977
+ },
13978
+ test: {
13979
+ id: "test",
13980
+ description: "work from tests, fixtures, harnesses, and the related source files",
13981
+ sourceLimit: 6,
13982
+ cautionLimit: 3,
13983
+ hideUnrelatedCautionDetails: true,
13984
+ hideUnrelatedReliableDetails: false
13985
+ },
13986
+ general: {
13987
+ id: "general",
13988
+ description: "balanced project handoff with current facts, code memory, and verification hints",
13989
+ sourceLimit: 8,
13990
+ cautionLimit: 5,
13991
+ hideUnrelatedCautionDetails: false,
13992
+ hideUnrelatedReliableDetails: false
13993
+ }
13994
+ };
13995
+ KEYWORDS = {
13996
+ bugfix: [
13997
+ "bug",
13998
+ "crash",
13999
+ "incident",
14000
+ "debug",
14001
+ "error",
14002
+ "fail",
14003
+ "failing",
14004
+ "fix",
14005
+ "issue",
14006
+ "regression",
14007
+ "repro",
14008
+ "\u62A5\u9519",
14009
+ "\u5D29\u6E83",
14010
+ "\u6545\u969C",
14011
+ "\u5931\u8D25",
14012
+ "\u4FEE\u590D",
14013
+ "\u95EE\u9898"
14014
+ ],
14015
+ feature: ["add", "build", "feature", "implement", "new", "support", "\u65B0\u589E", "\u5B9E\u73B0", "\u652F\u6301", "\u529F\u80FD"],
14016
+ release: ["bump", "changelog", "npm", "pack", "publish", "release", "version", "\u53D1\u7248", "\u53D1\u5E03", "\u7248\u672C"],
14017
+ onboarding: ["architecture", "handoff", "onboard", "overview", "understand", "\u63A5\u624B", "\u4E86\u89E3", "\u7406\u89E3", "\u67B6\u6784"],
14018
+ refactor: ["cleanup", "migrate", "refactor", "rename", "restructure", "\u91CD\u6784", "\u8FC1\u79FB", "\u6539\u9020"],
14019
+ docs: ["doc", "docs", "readme", "\u6587\u6863", "\u8BF4\u660E"],
14020
+ test: ["coverage", "fixture", "smoke", "spec", "test", "tests", "testing", "vitest", "\u6D4B\u8BD5"]
14021
+ };
14022
+ LENS_PRIORITY = [
14023
+ "bugfix",
14024
+ "release",
14025
+ "test",
14026
+ "refactor",
14027
+ "feature",
14028
+ "docs",
14029
+ "onboarding"
14030
+ ];
14031
+ }
14032
+ });
14033
+
14034
+ // src/knowledge/workflows.ts
14035
+ var workflows_exports = {};
14036
+ __export(workflows_exports, {
14037
+ WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
14038
+ applyWorkflowAdapter: () => applyWorkflowAdapter,
14039
+ importWindsurfWorkflows: () => importWindsurfWorkflows,
14040
+ parseWorkflowMarkdown: () => parseWorkflowMarkdown,
14041
+ previewWorkflowAdapter: () => previewWorkflowAdapter,
14042
+ recordWorkflowRun: () => recordWorkflowRun,
14043
+ renderWorkflowMarkdown: () => renderWorkflowMarkdown,
14044
+ selectWorkflows: () => selectWorkflows,
14045
+ selectWorkspaceWorkflows: () => selectWorkspaceWorkflows,
14046
+ syncCanonicalWorkflows: () => syncCanonicalWorkflows,
14047
+ writeCanonicalWorkflow: () => writeCanonicalWorkflow
14048
+ });
14049
+ import { createHash as createHash12 } from "crypto";
14050
+ import { promises as fs14 } from "fs";
14051
+ import path17 from "path";
14052
+ import matter11 from "gray-matter";
14053
+ function hash3(value) {
14054
+ return createHash12("sha256").update(value).digest("hex");
14055
+ }
14056
+ function now2() {
14057
+ return (/* @__PURE__ */ new Date()).toISOString();
14058
+ }
14059
+ function slug(value) {
14060
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 72);
14061
+ return normalized || "workflow";
14062
+ }
14063
+ function titleFromName(name) {
14064
+ return name.replace(/[-_]+/g, " ").split(" ").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ") || "Imported Workflow";
14065
+ }
14066
+ function requiredText(data, key) {
14067
+ const value = data[key];
14068
+ if (typeof value !== "string" || !value.trim()) {
14069
+ throw new Error("workflow frontmatter requires " + key);
14070
+ }
14071
+ return value.trim();
14072
+ }
14073
+ function optionalText4(data, key) {
14074
+ const value = data[key];
14075
+ if (value === void 0 || value === null || value === "") return void 0;
14076
+ if (typeof value !== "string") throw new Error("workflow frontmatter field " + key + " must be text");
14077
+ return value.trim() || void 0;
14078
+ }
14079
+ function optionalStringArray(data, key) {
14080
+ const value = data[key];
14081
+ if (value === void 0 || value === null) return [];
14082
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
14083
+ throw new Error("workflow frontmatter field " + key + " must be a string array");
14084
+ }
14085
+ return [...new Set(value.map((item) => item.trim()).filter(Boolean))];
14086
+ }
14087
+ function optionalNumber(data, key, fallback) {
14088
+ const value = data[key];
14089
+ if (value === void 0 || value === null || value === "") return fallback;
14090
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
14091
+ throw new Error("workflow frontmatter field " + key + " must be a positive integer");
14092
+ }
14093
+ return value;
14094
+ }
14095
+ function optionalStatus(data) {
14096
+ const value = data.status;
14097
+ if (value === void 0 || value === null || value === "") return "draft";
14098
+ if (typeof value !== "string" || !STATUS_VALUES.includes(value)) {
14099
+ throw new Error("workflow frontmatter field status is invalid");
14100
+ }
14101
+ return value;
14102
+ }
14103
+ function normalizeAgents(values) {
14104
+ const unknown = values.filter((value) => !KNOWN_AGENTS.includes(value));
14105
+ if (unknown.length > 0) {
14106
+ throw new Error("workflow frontmatter has unsupported agent: " + unknown[0]);
14107
+ }
14108
+ return values;
13402
14109
  }
13403
14110
  function phaseFromValue(value, index) {
13404
14111
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -13646,20 +14353,27 @@ function taskScore(workflow, task) {
13646
14353
  const text = task.toLowerCase();
13647
14354
  let score = 0;
13648
14355
  const reasons = [];
14356
+ const matchedLenses = /* @__PURE__ */ new Set();
14357
+ const matchedTriggers = /* @__PURE__ */ new Set();
13649
14358
  for (const lens of workflow.taskLenses) {
13650
14359
  const terms = LENS_TERMS[lens.toLowerCase()] ?? [lens.toLowerCase()];
13651
- if (terms.some((term) => text.includes(term))) {
14360
+ if (terms.some((term) => containsTaskKeyword(task, term))) {
13652
14361
  score += 20;
13653
14362
  reasons.push("matches " + lens + " workflow");
14363
+ matchedLenses.add(lens.toLowerCase());
13654
14364
  }
13655
14365
  }
13656
14366
  for (const trigger of workflow.triggers) {
13657
14367
  const term = trigger.toLowerCase().trim();
13658
- if (term.length >= 2 && text.includes(term)) {
14368
+ if (term.length >= 2 && containsTaskKeyword(task, term)) {
13659
14369
  score += 8;
13660
14370
  reasons.push('matches trigger "' + trigger + '"');
14371
+ matchedTriggers.add(term);
13661
14372
  }
13662
14373
  }
14374
+ const hasSpecificLens = workflow.taskLenses.some((lens) => !GENERIC_WORKFLOW_LENSES.has(lens.toLowerCase()));
14375
+ const hasSpecificMatch = [...matchedLenses].some((lens) => !GENERIC_WORKFLOW_LENSES.has(lens)) || [...matchedTriggers].some((trigger) => !GENERIC_WORKFLOW_TERMS.has(trigger));
14376
+ if (hasSpecificLens && !hasSpecificMatch) return { score: 0, reasons: [] };
13663
14377
  return { score, reasons: [...new Set(reasons)] };
13664
14378
  }
13665
14379
  function selectWorkflows(input) {
@@ -13677,9 +14391,28 @@ function selectWorkflows(input) {
13677
14391
  return selected.slice(0, Math.max(1, Math.min(input.limit ?? 2, 2)));
13678
14392
  }
13679
14393
  function importedWorkflowSpec(input) {
14394
+ let sourceMatter;
14395
+ try {
14396
+ sourceMatter = matter11(input.raw);
14397
+ } catch (error) {
14398
+ throw new Error("Malformed Windsurf workflow Markdown: " + (error instanceof Error ? error.message : String(error)));
14399
+ }
14400
+ if (sourceMatter.data.id !== void 0) {
14401
+ const id = requiredText(sourceMatter.data, "id");
14402
+ const sourcePath2 = "workflows/" + slug(id) + ".md";
14403
+ const canonical = parsedWorkflow(sourceMatter.data, sourceMatter.content, {
14404
+ workspaceId: input.workspace.id,
14405
+ sourcePath: sourcePath2,
14406
+ contentHash: hash3(input.raw)
14407
+ });
14408
+ return materializeWorkflow({
14409
+ ...canonical,
14410
+ importedFrom: input.sourcePath.replace(/\\/g, "/")
14411
+ });
14412
+ }
13680
14413
  const entry = new WorkflowSyncer().parseWindsurfWorkflow(input.sourceName, input.raw);
13681
14414
  const content = entry.content.trim() || "## Execute\n\nFollow the imported workflow.";
13682
- const lenses = inferTaskLenses(entry.name + "\n" + entry.description + "\n" + content);
14415
+ const lenses = inferTaskLenses(entry.name + "\n" + entry.description);
13683
14416
  const createdAt = now2();
13684
14417
  const sourcePath = "workflows/" + slug(entry.name) + ".md";
13685
14418
  const spec = {
@@ -13900,7 +14633,7 @@ async function selectWorkspaceWorkflows(input) {
13900
14633
  errors: synced.errors
13901
14634
  };
13902
14635
  }
13903
- var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS;
14636
+ var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
13904
14637
  var init_workflows = __esm({
13905
14638
  "src/knowledge/workflows.ts"() {
13906
14639
  "use strict";
@@ -13909,6 +14642,7 @@ var init_workflows = __esm({
13909
14642
  init_workflow_sync();
13910
14643
  init_workspace();
13911
14644
  init_workflow_store();
14645
+ init_task_lens();
13912
14646
  KNOWN_AGENTS = [
13913
14647
  "windsurf",
13914
14648
  "cursor",
@@ -13940,6 +14674,8 @@ var init_workflows = __esm({
13940
14674
  refactor: ["refactor", "cleanup", "restructure", "\u91CD\u6784", "\u6574\u7406"],
13941
14675
  test: ["test", "verify", "smoke", "\u6D4B\u8BD5", "\u9A8C\u8BC1"]
13942
14676
  };
14677
+ GENERIC_WORKFLOW_LENSES = /* @__PURE__ */ new Set(["review", "test"]);
14678
+ GENERIC_WORKFLOW_TERMS = /* @__PURE__ */ new Set(["review", "audit", "test", "verify", "smoke"]);
13943
14679
  }
13944
14680
  });
13945
14681
 
@@ -13986,6 +14722,15 @@ function claimDerivationCursor(payload) {
13986
14722
  const value = payload.cursor;
13987
14723
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
13988
14724
  }
14725
+ function observationQualificationBatchSize(payload) {
14726
+ const value = payload.limit;
14727
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE;
14728
+ return Math.min(500, Math.max(1, Math.floor(value)));
14729
+ }
14730
+ function observationQualificationCursor(payload) {
14731
+ const value = payload.cursor;
14732
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
14733
+ }
13989
14734
  function observationId(payload) {
13990
14735
  const value = payload.observationId;
13991
14736
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
@@ -14164,6 +14909,71 @@ function createProjectMaintenanceHandler(projectId, projectDir2, projectRoot, op
14164
14909
  ...snapshot?.id ? { snapshotId: snapshot.id } : {},
14165
14910
  ...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
14166
14911
  });
14912
+ enqueueObservationQualification({
14913
+ projectId,
14914
+ dataDir: projectDir2,
14915
+ source: "codegraph-refresh",
14916
+ ...options.maintenanceQueue ? { queue: options.maintenanceQueue } : {}
14917
+ });
14918
+ return { action: "complete" };
14919
+ }
14920
+ if (job.kind === "observation-qualify") {
14921
+ const [
14922
+ { CodeGraphStore: CodeGraphStore2 },
14923
+ { bindObservationToCode: bindObservationToCode2 },
14924
+ { getObservationStore: getObservationStore2 },
14925
+ { qualifyCandidateFromCurrentCode: qualifyCandidateFromCurrentCode2 },
14926
+ { updateObservationAdmission: updateObservationAdmission2 }
14927
+ ] = await Promise.all([
14928
+ Promise.resolve().then(() => (init_store(), store_exports)),
14929
+ Promise.resolve().then(() => (init_binder(), binder_exports)),
14930
+ Promise.resolve().then(() => (init_obs_store(), obs_store_exports)),
14931
+ Promise.resolve().then(() => (init_admission(), admission_exports)),
14932
+ Promise.resolve().then(() => (init_observations(), observations_exports))
14933
+ ]);
14934
+ const codeStore = new CodeGraphStore2();
14935
+ await codeStore.init(projectDir2);
14936
+ const limit = observationQualificationBatchSize(job.payload);
14937
+ const page = await getObservationStore2().loadByProject(projectId, {
14938
+ status: "active",
14939
+ afterId: observationQualificationCursor(job.payload),
14940
+ limit: limit + 1
14941
+ });
14942
+ const hasMore = page.length > limit;
14943
+ const observations2 = hasMore ? page.slice(0, limit) : page;
14944
+ const indexedAtMs = Date.parse(codeStore.status(projectId).indexedAt ?? "");
14945
+ for (const observation of observations2) {
14946
+ if (observation.admissionState !== "candidate") continue;
14947
+ if (!Number.isFinite(indexedAtMs) || indexedAtMs < Date.parse(observation.createdAt)) continue;
14948
+ await bindObservationToCode2(codeStore, observation);
14949
+ const currentCodeReferenceCount = codeStore.listObservationRefs(projectId, observation.id).filter((reference) => reference.status === "current").length;
14950
+ const qualification = qualifyCandidateFromCurrentCode2({
14951
+ observation,
14952
+ currentCodeReferenceCount
14953
+ });
14954
+ if (!qualification) continue;
14955
+ await updateObservationAdmission2({
14956
+ observationId: observation.id,
14957
+ projectId,
14958
+ expectedState: "candidate",
14959
+ // Automatic capture earns project visibility only after a current
14960
+ // Code Memory link exists. Explicit/private records retain scope.
14961
+ ...observation.sourceDetail === "hook" ? { visibility: "project" } : {},
14962
+ ...qualification
14963
+ });
14964
+ }
14965
+ if (hasMore && observations2.length > 0) {
14966
+ return {
14967
+ action: "reschedule",
14968
+ delayMs: 0,
14969
+ resetAttempts: true,
14970
+ payload: {
14971
+ ...job.payload,
14972
+ cursor: observations2[observations2.length - 1].id,
14973
+ limit
14974
+ }
14975
+ };
14976
+ }
14167
14977
  return { action: "complete" };
14168
14978
  }
14169
14979
  if (job.kind === "claim-derive") {
@@ -14329,7 +15139,7 @@ function createProjectMaintenanceDispatcher(projectId, projectDir2, projectRoot,
14329
15139
  return isolatedRunner({ job, projectRoot, dataDir: projectDir2 });
14330
15140
  };
14331
15141
  }
14332
- var DEFAULT_VECTOR_BATCH_SIZE, DEFAULT_RETENTION_BATCH_SIZE, DEFAULT_CONSOLIDATION_BATCH_SIZE, DEFAULT_CODEGRAPH_MAX_FILES, DEFAULT_CLAIM_DERIVATION_BATCH_SIZE, VECTOR_RETRY_DELAY_MS, DEDUP_PER_PAIR_TIMEOUT_MS;
15142
+ var DEFAULT_VECTOR_BATCH_SIZE, DEFAULT_RETENTION_BATCH_SIZE, DEFAULT_CONSOLIDATION_BATCH_SIZE, DEFAULT_CODEGRAPH_MAX_FILES, DEFAULT_CLAIM_DERIVATION_BATCH_SIZE, DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE, VECTOR_RETRY_DELAY_MS, DEDUP_PER_PAIR_TIMEOUT_MS;
14333
15143
  var init_project_maintenance = __esm({
14334
15144
  "src/runtime/project-maintenance.ts"() {
14335
15145
  "use strict";
@@ -14341,6 +15151,7 @@ var init_project_maintenance = __esm({
14341
15151
  DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
14342
15152
  DEFAULT_CODEGRAPH_MAX_FILES = 5e3;
14343
15153
  DEFAULT_CLAIM_DERIVATION_BATCH_SIZE = 100;
15154
+ DEFAULT_OBSERVATION_QUALIFICATION_BATCH_SIZE = 100;
14344
15155
  VECTOR_RETRY_DELAY_MS = 5e3;
14345
15156
  DEDUP_PER_PAIR_TIMEOUT_MS = 5e3;
14346
15157
  }
@@ -14582,9 +15393,9 @@ async function getSessionContext(projectDir2, projectId, limit = 3) {
14582
15393
  return true;
14583
15394
  });
14584
15395
  })() : l2Scored).slice(0, 5).map(({ obs }) => obs);
14585
- const l1HookObs = projectObs.filter((obs) => classifyLayer(obs) === "L1").sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 3);
15396
+ const l1HookObs = projectObs.filter((obs) => isEligibleForAutomaticDelivery(obs) && classifyLayer(obs) === "L1").sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 3);
14586
15397
  const l3GitCount = projectObs.filter((obs) => classifyLayer(obs) === "L3").length;
14587
- const totalHookCount = projectObs.filter((obs) => classifyLayer(obs) === "L1").length;
15398
+ const totalHookCount = projectObs.filter((obs) => isEligibleForAutomaticDelivery(obs) && classifyLayer(obs) === "L1").length;
14588
15399
  const activeEntities = [
14589
15400
  ...new Set(l2Obs.map((o) => o.entityName).filter((n) => !!n && n.trim().length > 0))
14590
15401
  ].slice(0, 5);
@@ -14710,6 +15521,7 @@ var init_session = __esm({
14710
15521
  "src/memory/session.ts"() {
14711
15522
  "use strict";
14712
15523
  init_esm_shims();
15524
+ init_admission();
14713
15525
  init_disclosure_policy();
14714
15526
  init_aliases();
14715
15527
  init_obs_store();
@@ -14862,27 +15674,80 @@ function workflowOutput(selection) {
14862
15674
  cautions: selection.cautions.map((caution) => short(caution, 22))
14863
15675
  };
14864
15676
  }
14865
- function appendLine(lines, candidate, maxTokens, omitted, omittedKind) {
15677
+ function appendLine(lines, candidate, maxTokens, omitted, omittedKind, selected, receiptSelection) {
14866
15678
  const next = lines.length ? lines.join("\n") + "\n" + candidate : candidate;
14867
15679
  if (countTextTokens(next) <= maxTokens) {
14868
15680
  lines.push(candidate);
15681
+ if (receiptSelection) selected?.push(receiptSelection);
14869
15682
  return true;
14870
15683
  }
14871
15684
  omitted.push(omittedKind);
14872
15685
  return false;
14873
15686
  }
15687
+ function freshnessForMemory(status) {
15688
+ if (status === "current" || status === "suspect" || status === "stale") return status;
15689
+ return "unknown";
15690
+ }
15691
+ function receiptOmissionKind(raw) {
15692
+ if (raw.includes("task")) return "task";
15693
+ if (raw.includes("fact")) return "current-fact";
15694
+ if (raw.includes("state")) return "code-state";
15695
+ if (raw.includes("semantic")) return "semantic-code";
15696
+ if (raw.includes("start")) return "start-here";
15697
+ if (raw.includes("memory")) return "memory";
15698
+ if (raw.includes("claim")) return "claim";
15699
+ if (raw.includes("knowledge-page")) return "knowledge-page";
15700
+ if (raw.includes("workflow")) return "workflow";
15701
+ if (raw.includes("verification")) return "verification";
15702
+ if (raw.includes("caution")) return "caution";
15703
+ return void 0;
15704
+ }
15705
+ function receiptOmissions(omitted, hiddenCautionMemoryCount) {
15706
+ const counts = /* @__PURE__ */ new Map();
15707
+ for (const raw of omitted) {
15708
+ const kind = receiptOmissionKind(raw);
15709
+ if (!kind || raw.endsWith("-heading")) continue;
15710
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
15711
+ }
15712
+ const receipt = [...counts.entries()].map(([kind, count2]) => ({
15713
+ kind,
15714
+ reason: "token-budget",
15715
+ count: count2
15716
+ }));
15717
+ if (hiddenCautionMemoryCount > 0) {
15718
+ receipt.push({
15719
+ kind: "caution",
15720
+ reason: "hidden-by-task-lens",
15721
+ count: hiddenCautionMemoryCount
15722
+ });
15723
+ }
15724
+ return receipt;
15725
+ }
15726
+ function scheduledActions(cautions) {
15727
+ return cautions.filter((caution) => caution.kind === "codegraph-refresh-queued").map((caution) => short(caution.message, 28));
15728
+ }
14874
15729
  function renderTaskWorksetPrompt(input) {
14875
15730
  const maxTokens = input.budget?.maxTokens ?? 180;
14876
15731
  const omitted = [];
15732
+ const selected = [];
14877
15733
  const lines = ["Memorix Autopilot Brief"];
14878
15734
  const task = short(input.task || "Continue the current task.", 34);
14879
- appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail");
15735
+ appendLine(lines, "Task: " + task, maxTokens, omitted, "task-detail", selected, {
15736
+ kind: "task",
15737
+ reason: "current task supplied by the caller",
15738
+ trust: "source-backed"
15739
+ });
14880
15740
  appendLine(lines, "Task lens: " + input.lens, maxTokens, omitted, "lens");
14881
15741
  if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
14882
15742
  appendLine(lines, "", maxTokens, omitted, "caution-heading");
14883
15743
  appendLine(lines, "Cautions", maxTokens, omitted, "caution-heading");
14884
15744
  for (const caution of input.cautions.slice(0, 6)) {
14885
- appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution");
15745
+ appendLine(lines, "- " + short(caution.message, 22), maxTokens, omitted, "caution", selected, {
15746
+ kind: "caution",
15747
+ id: "caution:" + caution.kind,
15748
+ reason: "current project caution",
15749
+ trust: "source-backed"
15750
+ });
14886
15751
  }
14887
15752
  for (const memory of input.cautionMemory.slice(0, 3)) {
14888
15753
  const location = memory.path ? memory.path + (memory.symbol ? "#" + memory.symbol : "") : "no current code location";
@@ -14892,7 +15757,15 @@ function renderTaskWorksetPrompt(input) {
14892
15757
  "- #" + memory.id + " " + memory.status + ": " + short(memory.title, 18) + " (" + location + reason + ")",
14893
15758
  maxTokens,
14894
15759
  omitted,
14895
- "caution-memory"
15760
+ "caution-memory",
15761
+ selected,
15762
+ {
15763
+ kind: "memory",
15764
+ id: "memory:" + memory.id,
15765
+ reason: "task-relevant memory requiring source verification",
15766
+ freshness: freshnessForMemory(memory.status),
15767
+ trust: "historical"
15768
+ }
14896
15769
  );
14897
15770
  }
14898
15771
  if (input.hiddenCautionMemoryCount > 0) {
@@ -14903,13 +15776,22 @@ function renderTaskWorksetPrompt(input) {
14903
15776
  appendLine(lines, "", maxTokens, omitted, "facts-heading");
14904
15777
  appendLine(lines, "Current project facts", maxTokens, omitted, "facts-heading");
14905
15778
  for (const fact of input.currentFacts.slice(0, 4)) {
14906
- appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact");
15779
+ appendLine(lines, "- " + short(fact, 40), maxTokens, omitted, "current-fact", selected, {
15780
+ kind: "current-fact",
15781
+ reason: "current project state",
15782
+ trust: "source-backed"
15783
+ });
14907
15784
  }
14908
15785
  }
14909
15786
  if (input.codeState) {
14910
15787
  appendLine(lines, "", maxTokens, omitted, "state-heading");
14911
15788
  appendLine(lines, "Project state", maxTokens, omitted, "state-heading");
14912
- appendLine(lines, input.codeState, maxTokens, omitted, "code-state");
15789
+ appendLine(lines, input.codeState, maxTokens, omitted, "code-state", selected, {
15790
+ kind: "code-state",
15791
+ ...input.provenance.snapshotId ? { id: "snapshot:" + input.provenance.snapshotId } : {},
15792
+ reason: "latest available Code State snapshot",
15793
+ trust: "source-backed"
15794
+ });
14913
15795
  }
14914
15796
  if (input.semanticCode && (input.semanticCode.entryPoints.length > 0 || input.semanticCode.relations.length > 0)) {
14915
15797
  appendLine(lines, "", maxTokens, omitted, "semantic-code-heading");
@@ -14921,7 +15803,14 @@ function renderTaskWorksetPrompt(input) {
14921
15803
  "- " + location + ": " + short(relation.from.name, 12) + " " + short(relation.kind, 8) + " " + short(relation.to.name, 12),
14922
15804
  maxTokens,
14923
15805
  omitted,
14924
- "semantic-relation"
15806
+ "semantic-relation",
15807
+ selected,
15808
+ {
15809
+ kind: "semantic-code",
15810
+ id: "code:" + location,
15811
+ reason: "validated optional semantic code relation",
15812
+ trust: "derived"
15813
+ }
14925
15814
  );
14926
15815
  }
14927
15816
  if (input.semanticCode.relations.length === 0) {
@@ -14932,7 +15821,14 @@ function renderTaskWorksetPrompt(input) {
14932
15821
  "- " + location + ": " + short(entry.name, 16) + " (" + short(entry.kind, 8) + ")",
14933
15822
  maxTokens,
14934
15823
  omitted,
14935
- "semantic-entry"
15824
+ "semantic-entry",
15825
+ selected,
15826
+ {
15827
+ kind: "semantic-code",
15828
+ id: "code:" + location,
15829
+ reason: "validated optional semantic code entry point",
15830
+ trust: "derived"
15831
+ }
14936
15832
  );
14937
15833
  }
14938
15834
  }
@@ -14941,7 +15837,12 @@ function renderTaskWorksetPrompt(input) {
14941
15837
  appendLine(lines, "", maxTokens, omitted, "start-heading");
14942
15838
  appendLine(lines, "Start here", maxTokens, omitted, "start-heading");
14943
15839
  for (const source of input.startHere.slice(0, 5)) {
14944
- appendLine(lines, "- " + source, maxTokens, omitted, "start-here");
15840
+ appendLine(lines, "- " + source, maxTokens, omitted, "start-here", selected, {
15841
+ kind: "start-here",
15842
+ id: "path:" + source,
15843
+ reason: "task-lensed starting point",
15844
+ trust: "derived"
15845
+ });
14945
15846
  }
14946
15847
  }
14947
15848
  if (input.reliableMemory.length > 0) {
@@ -14954,7 +15855,15 @@ function renderTaskWorksetPrompt(input) {
14954
15855
  "- #" + memory.id + " " + memory.type + ": " + short(memory.title, 18) + " (" + location + ")",
14955
15856
  maxTokens,
14956
15857
  omitted,
14957
- "reliable-memory"
15858
+ "reliable-memory",
15859
+ selected,
15860
+ {
15861
+ kind: "memory",
15862
+ id: "memory:" + memory.id,
15863
+ reason: "current code-bound memory",
15864
+ freshness: freshnessForMemory(memory.status),
15865
+ trust: "historical"
15866
+ }
14958
15867
  );
14959
15868
  }
14960
15869
  }
@@ -14962,10 +15871,25 @@ function renderTaskWorksetPrompt(input) {
14962
15871
  appendLine(lines, "", maxTokens, omitted, "knowledge-heading");
14963
15872
  appendLine(lines, "Project knowledge", maxTokens, omitted, "knowledge-heading");
14964
15873
  for (const claim of input.claims.slice(0, 3)) {
14965
- appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim");
15874
+ appendLine(lines, "- " + claim.assertion + " [" + claim.id + "]", maxTokens, omitted, "claim", selected, {
15875
+ kind: "claim",
15876
+ id: "claim:" + claim.id,
15877
+ reason: "source-qualified task match",
15878
+ trust: "source-backed"
15879
+ });
14966
15880
  }
14967
15881
  for (const page of input.pages.slice(0, 2)) {
14968
- appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page");
15882
+ const supportsDeliveredClaim = page.claimIds.some((claimId) => selected.some((item) => item.kind === "claim" && item.id === "claim:" + claimId));
15883
+ if (!supportsDeliveredClaim) {
15884
+ omitted.push("knowledge-page-dependency");
15885
+ continue;
15886
+ }
15887
+ appendLine(lines, "- page: " + page.relativePath, maxTokens, omitted, "knowledge-page", selected, {
15888
+ kind: "knowledge-page",
15889
+ id: "page:" + page.id,
15890
+ reason: "approved page linked to a selected claim",
15891
+ trust: "source-backed"
15892
+ });
14969
15893
  }
14970
15894
  }
14971
15895
  if (input.workflows.length > 0) {
@@ -14977,7 +15901,14 @@ function renderTaskWorksetPrompt(input) {
14977
15901
  "- " + workflow.title + ": " + workflow.firstPhase.title + " - " + workflow.firstPhase.instructions,
14978
15902
  maxTokens,
14979
15903
  omitted,
14980
- "workflow"
15904
+ "workflow",
15905
+ selected,
15906
+ {
15907
+ kind: "workflow",
15908
+ id: "workflow:" + workflow.id,
15909
+ reason: "task-matching project workflow",
15910
+ trust: "source-backed"
15911
+ }
14981
15912
  );
14982
15913
  }
14983
15914
  }
@@ -14985,16 +15916,23 @@ function renderTaskWorksetPrompt(input) {
14985
15916
  appendLine(lines, "", maxTokens, omitted, "verification-heading");
14986
15917
  appendLine(lines, "Verify", maxTokens, omitted, "verification-heading");
14987
15918
  for (const check of input.verification.slice(0, 4)) {
14988
- appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification");
15919
+ appendLine(lines, "- " + short(check, 20), maxTokens, omitted, "verification", selected, {
15920
+ kind: "verification",
15921
+ reason: "task-lensed verification guidance",
15922
+ trust: "derived"
15923
+ });
14989
15924
  }
14990
15925
  }
14991
15926
  return {
14992
15927
  prompt: lines.join("\n"),
14993
15928
  tokenCount: countTextTokens(lines.join("\n")),
14994
- omitted: unique(omitted)
15929
+ omitted: unique(omitted),
15930
+ omittedItems: omitted,
15931
+ selected
14995
15932
  };
14996
15933
  }
14997
15934
  async function buildTaskWorkset(input) {
15935
+ const startedAt = Date.now();
14998
15936
  const task = input.task?.trim() ?? "";
14999
15937
  const maxTokens = Math.max(96, Math.min(Math.floor(input.maxTokens ?? 180), 320));
15000
15938
  const cautions = [...input.runtimeCautions ?? [], ...snapshotCautions(input)];
@@ -15096,6 +16034,18 @@ async function buildTaskWorkset(input) {
15096
16034
  ...base,
15097
16035
  budget: { maxTokens }
15098
16036
  });
16037
+ const receipt = {
16038
+ version: "1.2.2",
16039
+ target: input.deliveryTarget ?? "project-context",
16040
+ elapsedMs: Math.max(0, Date.now() - startedAt),
16041
+ budget: {
16042
+ maxTokens,
16043
+ tokenCount: rendered.tokenCount
16044
+ },
16045
+ selected: rendered.selected,
16046
+ omitted: receiptOmissions(rendered.omittedItems, base.hiddenCautionMemoryCount),
16047
+ scheduledActions: scheduledActions(normalizedCautions)
16048
+ };
15099
16049
  return {
15100
16050
  ...base,
15101
16051
  budget: {
@@ -15103,6 +16053,7 @@ async function buildTaskWorkset(input) {
15103
16053
  tokenCount: rendered.tokenCount,
15104
16054
  omitted: rendered.omitted
15105
16055
  },
16056
+ receipt,
15106
16057
  prompt: rendered.prompt
15107
16058
  };
15108
16059
  }
@@ -15124,7 +16075,8 @@ var init_workset = __esm({
15124
16075
  // src/codegraph/current-facts.ts
15125
16076
  var current_facts_exports = {};
15126
16077
  __export(current_facts_exports, {
15127
- collectCurrentProjectFacts: () => collectCurrentProjectFacts
16078
+ collectCurrentProjectFacts: () => collectCurrentProjectFacts,
16079
+ formatGitFact: () => formatGitFact
15128
16080
  });
15129
16081
  import { execFileSync } from "child_process";
15130
16082
  import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
@@ -15169,11 +16121,16 @@ function runGit2(rootPath, args) {
15169
16121
  }
15170
16122
  }
15171
16123
  function readGitFacts(rootPath) {
15172
- const branch = runGit2(rootPath, ["branch", "--show-current"]);
16124
+ const available = runGit2(rootPath, ["rev-parse", "--is-inside-work-tree"]) === "true";
16125
+ if (!available) {
16126
+ return { available: false, dirty: false, detached: false };
16127
+ }
16128
+ const branch = runGit2(rootPath, ["symbolic-ref", "--quiet", "--short", "HEAD"]) ?? runGit2(rootPath, ["branch", "--show-current"]);
15173
16129
  const commit = runGit2(rootPath, ["rev-parse", "--short", "HEAD"]);
15174
16130
  const latestCommit = runGit2(rootPath, ["log", "-1", "--pretty=%s"]);
15175
16131
  const dirty = Boolean(runGit2(rootPath, ["status", "--porcelain"]));
15176
16132
  return {
16133
+ available: true,
15177
16134
  ...branch ? { branch } : {},
15178
16135
  ...commit ? { commit } : {},
15179
16136
  ...latestCommit ? { latestCommit } : {},
@@ -15181,6 +16138,15 @@ function readGitFacts(rootPath) {
15181
16138
  detached: !branch
15182
16139
  };
15183
16140
  }
16141
+ function formatGitFact(git) {
16142
+ if (!git.available) return "Git: unavailable";
16143
+ const parts = [];
16144
+ if (git.detached) parts.push("detached HEAD");
16145
+ else if (git.branch) parts.push("branch " + git.branch);
16146
+ if (git.commit) parts.push("commit " + git.commit);
16147
+ parts.push(git.dirty ? "dirty worktree" : "clean worktree");
16148
+ return "Git: " + parts.join(", ");
16149
+ }
15184
16150
  function parseProgressNote(content) {
15185
16151
  const lastUpdated = content.match(/Last updated\*\*:\s*(\d{4}-\d{2}-\d{2})/i) ?? content.match(/Last updated:\s*(\d{4}-\d{2}-\d{2})/i);
15186
16152
  const branchHint = content.match(/Branch\*\*:\s*([^\r\n]+)/i) ?? content.match(/Branch:\s*([^\r\n]+)/i);
@@ -15791,376 +16757,63 @@ function collectGraph(store, projectId, observations2, exclude) {
15791
16757
  status: "unbound",
15792
16758
  reason: "Recorded file hint; Code Memory refresh is pending."
15793
16759
  });
15794
- freshness.unbound++;
15795
- }
15796
- }
15797
- }
15798
- return {
15799
- files,
15800
- symbols,
15801
- refs,
15802
- freshness,
15803
- sources,
15804
- suggestedReads: compactSuggestedReads(suggestedReads, 8, exclude)
15805
- };
15806
- }
15807
- function overviewFromGraph(input) {
15808
- const status = input.store.status(input.project.id);
15809
- return {
15810
- project: input.project,
15811
- code: {
15812
- provider: status.provider,
15813
- files: status.files,
15814
- symbols: status.symbols,
15815
- edges: status.edges,
15816
- refs: status.refs,
15817
- ...status.indexedAt ? { indexedAt: status.indexedAt } : {},
15818
- languages: countLanguages(input.graph.files),
15819
- ...status.latestSnapshot ? { latestSnapshot: status.latestSnapshot } : {}
15820
- },
15821
- memory: {
15822
- total: input.observations.filter((obs) => obs.projectId === input.project.id).length,
15823
- active: input.active.length
15824
- },
15825
- freshness: input.graph.freshness,
15826
- suggestedReads: input.graph.suggestedReads
15827
- };
15828
- }
15829
- function buildProjectContextExplain(input) {
15830
- const active = activeObservations(input.observations, input.project.id);
15831
- const graph = collectGraph(input.store, input.project.id, active, input.exclude);
15832
- const overview = overviewFromGraph({
15833
- project: input.project,
15834
- store: input.store,
15835
- observations: input.observations,
15836
- active,
15837
- graph
15838
- });
15839
- return {
15840
- project: input.project,
15841
- sources: graph.sources.sort((a, b) => a.observationId - b.observationId || (a.path ?? "").localeCompare(b.path ?? "")),
15842
- overview
15843
- };
15844
- }
15845
- var init_project_context = __esm({
15846
- "src/codegraph/project-context.ts"() {
15847
- "use strict";
15848
- init_esm_shims();
15849
- init_freshness2();
15850
- init_exclude();
15851
- }
15852
- });
15853
-
15854
- // src/codegraph/task-lens.ts
15855
- var task_lens_exports = {};
15856
- __export(task_lens_exports, {
15857
- lensPathCandidates: () => lensPathCandidates,
15858
- lensVerificationHints: () => lensVerificationHints,
15859
- rankLensPaths: () => rankLensPaths,
15860
- rankLensSources: () => rankLensSources,
15861
- resolveTaskLens: () => resolveTaskLens,
15862
- scoreLensSource: () => scoreLensSource,
15863
- shouldShowLensSource: () => shouldShowLensSource
15864
- });
15865
- function normalizePath2(path25) {
15866
- return path25.replace(/\\/g, "/");
15867
- }
15868
- function tokenize2(text) {
15869
- return (text.toLowerCase().match(/[a-z0-9_./-]+|[\u4e00-\u9fff]+/g) ?? []).map((token) => token.trim()).filter((token) => token.length > 1 && !STOP_WORDS.has(token));
15870
- }
15871
- function containsKeyword(text, keyword) {
15872
- if (/^[a-z0-9_-]+$/i.test(keyword)) {
15873
- return new RegExp(`(^|[^a-z0-9_-])${keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([^a-z0-9_-]|$)`, "i").test(text);
15874
- }
15875
- return text.includes(keyword);
15876
- }
15877
- function resolveTaskLens(task) {
15878
- const normalized = (task ?? "").toLowerCase();
15879
- if (!normalized.trim()) return LENSES.general;
15880
- let best = {
15881
- id: "general",
15882
- score: 0,
15883
- priority: Number.MAX_SAFE_INTEGER
15884
- };
15885
- for (const id of LENS_PRIORITY) {
15886
- const score = KEYWORDS[id].reduce((sum, keyword) => sum + (containsKeyword(normalized, keyword) ? 1 : 0), 0);
15887
- const priority = LENS_PRIORITY.indexOf(id);
15888
- if (score > best.score || score === best.score && score > 0 && priority < best.priority) {
15889
- best = { id, score, priority };
15890
- }
15891
- }
15892
- return best.score > 0 ? LENSES[best.id] : LENSES.general;
15893
- }
15894
- function pathKindScore(path25, lens) {
15895
- const normalized = normalizePath2(path25).toLowerCase();
15896
- const name = normalized.split("/").pop() ?? normalized;
15897
- const inDocs = normalized.startsWith("docs/") || name === "readme.md" || name.includes("readme");
15898
- const isTest = /(^|\/)(tests?|__tests__|spec|fixtures?)\//.test(normalized) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(normalized) || /\.(test|spec)\.py$/.test(normalized);
15899
- const isSource = normalized.startsWith("src/") || normalized.includes("/src/");
15900
- const isPackage = name === "package.json" || name === "package-lock.json" || name === "pnpm-lock.yaml" || name === "yarn.lock";
15901
- const isChangelog = name === "changelog.md" || name === "changes.md";
15902
- const isWorkflow = normalized.startsWith(".github/workflows/");
15903
- switch (lens.id) {
15904
- case "bugfix":
15905
- return (isTest ? 80 : 0) + (isSource ? 50 : 0) + (normalized.includes("debug") ? 20 : 0);
15906
- case "test":
15907
- return (isTest ? 90 : 0) + (isSource ? 45 : 0);
15908
- case "release":
15909
- return (isChangelog ? 100 : 0) + (isPackage ? 90 : 0) + (isWorkflow ? 70 : 0) + (inDocs ? 35 : 0);
15910
- case "onboarding":
15911
- return (name === "readme.md" ? 100 : 0) + (inDocs ? 80 : 0) + (isPackage ? 45 : 0) + (isSource ? 20 : 0);
15912
- case "docs":
15913
- return (inDocs ? 100 : 0) + (isChangelog ? 60 : 0) + (isSource ? 25 : 0);
15914
- case "refactor":
15915
- return (isSource ? 70 : 0) + (isTest ? 65 : 0);
15916
- case "feature":
15917
- return (isSource ? 80 : 0) + (isTest ? 45 : 0) + (inDocs ? 20 : 0);
15918
- default:
15919
- if (isSource || isTest) return 60;
15920
- if (normalized.startsWith("packages/") && normalized.includes("/src/")) return 45;
15921
- if (inDocs) return 20;
15922
- return 0;
15923
- }
15924
- }
15925
- function tokenScore(text, tokens) {
15926
- if (tokens.length === 0) return 0;
15927
- const normalized = text.toLowerCase();
15928
- return tokens.reduce((sum, token) => sum + (normalized.includes(token) ? 12 : 0), 0);
15929
- }
15930
- function sourceTaskMatchScore(source, task) {
15931
- const tokens = tokenize2(task ?? "");
15932
- if (tokens.length === 0) return 0;
15933
- const text = [
15934
- source.title,
15935
- source.path ?? "",
15936
- source.symbol ?? ""
15937
- ].join("\n").toLowerCase();
15938
- const matches = tokens.filter((token) => text.includes(token)).length;
15939
- if (matches >= 2) return 40;
15940
- if (matches === 1) return 24;
15941
- return 0;
15942
- }
15943
- function fallbackPathRank(path25) {
15944
- const normalized = normalizePath2(path25);
15945
- if (normalized.startsWith("src/")) return 0;
15946
- if (normalized.startsWith("tests/") || normalized.startsWith("test/")) return 1;
15947
- if (normalized.startsWith("packages/") && normalized.includes("/src/")) return 2;
15948
- if (normalized.startsWith("docs/") || normalized.toLowerCase() === "readme.md") return 3;
15949
- return 4;
15950
- }
15951
- function rankLensPaths(paths, lens, task) {
15952
- const tokens = tokenize2(task ?? "");
15953
- return [...new Set(paths.map(normalizePath2))].map((path25, index) => ({
15954
- path: path25,
15955
- index,
15956
- score: pathKindScore(path25, lens) + tokenScore(path25, tokens),
15957
- fallback: fallbackPathRank(path25)
15958
- })).sort((a, b) => b.score - a.score || a.fallback - b.fallback || a.index - b.index || a.path.localeCompare(b.path)).map((item) => item.path);
15959
- }
15960
- function lensPathCandidates(lens) {
15961
- switch (lens.id) {
15962
- case "release":
15963
- return ["CHANGELOG.md", "package.json", "package-lock.json", ".github/workflows/ci.yml", ".github/workflows/test.yml", "README.md"];
15964
- case "onboarding":
15965
- return ["README.md", "docs/README.md", "docs/API_REFERENCE.md", "package.json"];
15966
- case "docs":
15967
- return ["README.md", "README.zh-CN.md", "docs/README.md", "docs/API_REFERENCE.md", "CHANGELOG.md"];
15968
- case "test":
15969
- case "bugfix":
15970
- return ["tests", "test"];
15971
- default:
15972
- return [];
15973
- }
15974
- }
15975
- function scoreLensSource(source, lens, task) {
15976
- const tokens = tokenize2(task ?? "");
15977
- const text = [source.title, source.type, source.path ?? "", source.symbol ?? ""].join("\n");
15978
- return pathKindScore(source.path ?? "", lens) + tokenScore(text, tokens) + sourceTaskMatchScore(source, task);
15979
- }
15980
- function rankLensSources(sources, lens, task) {
15981
- return [...sources].map((source, index) => ({
15982
- source,
15983
- index,
15984
- score: scoreLensSource(source, lens, task)
15985
- })).sort((a, b) => b.score - a.score || a.source.observationId - b.source.observationId || a.index - b.index).map((item) => item.source);
15986
- }
15987
- function lensVerificationHints(lens) {
15988
- switch (lens.id) {
15989
- case "bugfix":
15990
- return [
15991
- "run the smallest failing test or repro first",
15992
- "inspect the changed code path before trusting old memory"
15993
- ];
15994
- case "test":
15995
- return [
15996
- "run the exact focused test file or test name first",
15997
- "inspect fixtures and harness setup before changing assertions"
15998
- ];
15999
- case "release":
16000
- return [
16001
- "run build, tests, package smoke, and publish dry-run where available",
16002
- "verify package metadata, changelog, and Git state before publishing"
16003
- ];
16004
- case "onboarding":
16005
- return [
16006
- "read the docs/start files first, then inspect only the code paths needed for the task",
16007
- "treat old implementation memories as leads until current code confirms them"
16008
- ];
16009
- case "refactor":
16010
- return [
16011
- "inspect call sites and tests before editing shared code",
16012
- "run the narrow affected test plus one regression smoke"
16013
- ];
16014
- case "docs":
16015
- return [
16016
- "check headings, links, commands, and examples against current code",
16017
- "run the smallest docs or package smoke available"
16018
- ];
16019
- case "feature":
16020
- return [
16021
- "inspect the closest existing implementation pattern before adding new code",
16022
- "run focused tests plus one user-flow smoke after changes"
16023
- ];
16024
- default:
16025
- return [
16026
- "inspect the Start here files before editing",
16027
- "run the smallest relevant test or smoke command after changes"
16028
- ];
16760
+ freshness.unbound++;
16761
+ }
16762
+ }
16029
16763
  }
16764
+ return {
16765
+ files,
16766
+ symbols,
16767
+ refs,
16768
+ freshness,
16769
+ sources,
16770
+ suggestedReads: compactSuggestedReads(suggestedReads, 8, exclude)
16771
+ };
16030
16772
  }
16031
- function shouldShowLensSource(source, lens, task) {
16032
- if (lens.id === "general") return true;
16033
- const score = scoreLensSource(source, lens, task);
16034
- if (lens.id === "onboarding" || lens.id === "release" || lens.id === "docs") return score >= 40;
16035
- return score > 0;
16773
+ function overviewFromGraph(input) {
16774
+ const status = input.store.status(input.project.id);
16775
+ return {
16776
+ project: input.project,
16777
+ code: {
16778
+ provider: status.provider,
16779
+ files: status.files,
16780
+ symbols: status.symbols,
16781
+ edges: status.edges,
16782
+ refs: status.refs,
16783
+ ...status.indexedAt ? { indexedAt: status.indexedAt } : {},
16784
+ languages: countLanguages(input.graph.files),
16785
+ ...status.latestSnapshot ? { latestSnapshot: status.latestSnapshot } : {}
16786
+ },
16787
+ memory: {
16788
+ total: input.observations.filter((obs) => obs.projectId === input.project.id).length,
16789
+ active: input.active.length
16790
+ },
16791
+ freshness: input.graph.freshness,
16792
+ suggestedReads: input.graph.suggestedReads
16793
+ };
16036
16794
  }
16037
- var STOP_WORDS, LENSES, KEYWORDS, LENS_PRIORITY;
16038
- var init_task_lens = __esm({
16039
- "src/codegraph/task-lens.ts"() {
16795
+ function buildProjectContextExplain(input) {
16796
+ const active = activeObservations(input.observations, input.project.id);
16797
+ const graph = collectGraph(input.store, input.project.id, active, input.exclude);
16798
+ const overview = overviewFromGraph({
16799
+ project: input.project,
16800
+ store: input.store,
16801
+ observations: input.observations,
16802
+ active,
16803
+ graph
16804
+ });
16805
+ return {
16806
+ project: input.project,
16807
+ sources: graph.sources.sort((a, b) => a.observationId - b.observationId || (a.path ?? "").localeCompare(b.path ?? "")),
16808
+ overview
16809
+ };
16810
+ }
16811
+ var init_project_context = __esm({
16812
+ "src/codegraph/project-context.ts"() {
16040
16813
  "use strict";
16041
16814
  init_esm_shims();
16042
- STOP_WORDS = /* @__PURE__ */ new Set([
16043
- "a",
16044
- "an",
16045
- "and",
16046
- "as",
16047
- "for",
16048
- "in",
16049
- "into",
16050
- "of",
16051
- "on",
16052
- "onto",
16053
- "the",
16054
- "this",
16055
- "that",
16056
- "to",
16057
- "with",
16058
- "work",
16059
- "project",
16060
- "continue",
16061
- "\u7EE7\u7EED",
16062
- "\u9879\u76EE"
16063
- ]);
16064
- LENSES = {
16065
- bugfix: {
16066
- id: "bugfix",
16067
- description: "debug the failure with current code and the smallest repro first",
16068
- sourceLimit: 6,
16069
- cautionLimit: 4,
16070
- hideUnrelatedCautionDetails: true,
16071
- hideUnrelatedReliableDetails: false
16072
- },
16073
- feature: {
16074
- id: "feature",
16075
- description: "build a scoped feature from nearby source, types, and user flow",
16076
- sourceLimit: 6,
16077
- cautionLimit: 3,
16078
- hideUnrelatedCautionDetails: true,
16079
- hideUnrelatedReliableDetails: false
16080
- },
16081
- release: {
16082
- id: "release",
16083
- description: "prepare a release using current metadata, changelog, build, and package checks",
16084
- sourceLimit: 4,
16085
- cautionLimit: 2,
16086
- hideUnrelatedCautionDetails: true,
16087
- hideUnrelatedReliableDetails: true
16088
- },
16089
- onboarding: {
16090
- id: "onboarding",
16091
- description: "understand the project shape before trusting old implementation details",
16092
- sourceLimit: 4,
16093
- cautionLimit: 2,
16094
- hideUnrelatedCautionDetails: true,
16095
- hideUnrelatedReliableDetails: true
16096
- },
16097
- refactor: {
16098
- id: "refactor",
16099
- description: "change structure carefully by reading shared code, call sites, and tests",
16100
- sourceLimit: 6,
16101
- cautionLimit: 4,
16102
- hideUnrelatedCautionDetails: true,
16103
- hideUnrelatedReliableDetails: false
16104
- },
16105
- docs: {
16106
- id: "docs",
16107
- description: "update documentation against current code and public entry points",
16108
- sourceLimit: 5,
16109
- cautionLimit: 2,
16110
- hideUnrelatedCautionDetails: true,
16111
- hideUnrelatedReliableDetails: true
16112
- },
16113
- test: {
16114
- id: "test",
16115
- description: "work from tests, fixtures, harnesses, and the related source files",
16116
- sourceLimit: 6,
16117
- cautionLimit: 3,
16118
- hideUnrelatedCautionDetails: true,
16119
- hideUnrelatedReliableDetails: false
16120
- },
16121
- general: {
16122
- id: "general",
16123
- description: "balanced project handoff with current facts, code memory, and verification hints",
16124
- sourceLimit: 8,
16125
- cautionLimit: 5,
16126
- hideUnrelatedCautionDetails: false,
16127
- hideUnrelatedReliableDetails: false
16128
- }
16129
- };
16130
- KEYWORDS = {
16131
- bugfix: [
16132
- "bug",
16133
- "crash",
16134
- "debug",
16135
- "error",
16136
- "fail",
16137
- "failing",
16138
- "fix",
16139
- "issue",
16140
- "regression",
16141
- "repro",
16142
- "\u62A5\u9519",
16143
- "\u5D29\u6E83",
16144
- "\u5931\u8D25",
16145
- "\u4FEE\u590D",
16146
- "\u95EE\u9898"
16147
- ],
16148
- feature: ["add", "build", "feature", "implement", "new", "support", "\u65B0\u589E", "\u5B9E\u73B0", "\u652F\u6301", "\u529F\u80FD"],
16149
- release: ["bump", "changelog", "npm", "pack", "publish", "release", "version", "\u53D1\u7248", "\u53D1\u5E03", "\u7248\u672C"],
16150
- onboarding: ["architecture", "handoff", "onboard", "overview", "understand", "\u63A5\u624B", "\u4E86\u89E3", "\u7406\u89E3", "\u67B6\u6784"],
16151
- refactor: ["cleanup", "migrate", "refactor", "rename", "restructure", "\u91CD\u6784", "\u8FC1\u79FB", "\u6539\u9020"],
16152
- docs: ["doc", "docs", "readme", "\u6587\u6863", "\u8BF4\u660E"],
16153
- test: ["coverage", "fixture", "smoke", "spec", "test", "tests", "testing", "vitest", "\u6D4B\u8BD5"]
16154
- };
16155
- LENS_PRIORITY = [
16156
- "bugfix",
16157
- "release",
16158
- "test",
16159
- "refactor",
16160
- "feature",
16161
- "docs",
16162
- "onboarding"
16163
- ];
16815
+ init_freshness2();
16816
+ init_exclude();
16164
16817
  }
16165
16818
  });
16166
16819
 
@@ -16178,6 +16831,9 @@ import path19 from "path";
16178
16831
  function activeProjectObservations(observations2, projectId) {
16179
16832
  return observations2.filter((obs) => obs.projectId === projectId && (obs.status ?? "active") === "active");
16180
16833
  }
16834
+ function deliveryEligibleProjectObservations(observations2, projectId) {
16835
+ return activeProjectObservations(observations2, projectId).filter((observation) => isEligibleForAutomaticDelivery(observation));
16836
+ }
16181
16837
  function decideRefresh(input) {
16182
16838
  if (input.mode === "never") {
16183
16839
  return { performed: false, reason: "disabled", message: "Automatic project scan disabled." };
@@ -16252,13 +16908,27 @@ async function buildAutoProjectContext(input) {
16252
16908
  activeProjectObservations(input.observations, input.project.id)
16253
16909
  );
16254
16910
  try {
16255
- const { enqueueClaimRequalification: enqueueClaimRequalification2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16911
+ const { MaintenanceTargetStore: MaintenanceTargetStore2 } = await Promise.resolve().then(() => (init_maintenance_targets(), maintenance_targets_exports));
16912
+ new MaintenanceTargetStore2(input.dataDir).register({
16913
+ projectId: input.project.id,
16914
+ projectRoot: input.project.rootPath,
16915
+ dataDir: input.dataDir
16916
+ });
16917
+ const {
16918
+ enqueueClaimRequalification: enqueueClaimRequalification2,
16919
+ enqueueObservationQualification: enqueueObservationQualification2
16920
+ } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
16256
16921
  enqueueClaimRequalification2({
16257
16922
  dataDir: input.dataDir,
16258
16923
  projectId: input.project.id,
16259
16924
  source: "foreground-refresh",
16260
16925
  snapshotId: store.latestSnapshot(input.project.id)?.id
16261
16926
  });
16927
+ enqueueObservationQualification2({
16928
+ dataDir: input.dataDir,
16929
+ projectId: input.project.id,
16930
+ source: "foreground-refresh"
16931
+ });
16262
16932
  } catch {
16263
16933
  }
16264
16934
  refresh = { ...refresh, backfill };
@@ -16275,7 +16945,9 @@ async function buildAutoProjectContext(input) {
16275
16945
  const explain = buildProjectContextExplain({
16276
16946
  project: input.project,
16277
16947
  store,
16278
- observations: input.observations,
16948
+ // Binding sees all active evidence, but automatic delivery sees only
16949
+ // evidence that has passed the control-plane admission gate.
16950
+ observations: deliveryEligibleProjectObservations(input.observations, input.project.id),
16279
16951
  exclude
16280
16952
  });
16281
16953
  const overview = explain.overview;
@@ -16366,7 +17038,8 @@ async function buildAutoProjectContext(input) {
16366
17038
  suspect: overview.freshness.suspect,
16367
17039
  stale: overview.freshness.stale
16368
17040
  },
16369
- runtimeCautions
17041
+ runtimeCautions,
17042
+ ...input.deliveryTarget ? { deliveryTarget: input.deliveryTarget } : {}
16370
17043
  });
16371
17044
  return {
16372
17045
  project: input.project,
@@ -16459,15 +17132,7 @@ function formatCurrentFactsLines(facts) {
16459
17132
  if (facts.latestChangelog) {
16460
17133
  lines.push(`- Latest changelog: ${facts.latestChangelog.version}${facts.latestChangelog.date ? ` (${facts.latestChangelog.date})` : ""}`);
16461
17134
  }
16462
- const gitParts = [];
16463
- if (facts.git.detached) {
16464
- gitParts.push("detached HEAD");
16465
- } else if (facts.git.branch) {
16466
- gitParts.push(`branch ${facts.git.branch}`);
16467
- }
16468
- if (facts.git.commit) gitParts.push(`commit ${facts.git.commit}`);
16469
- gitParts.push(facts.git.dirty ? "dirty worktree" : "clean worktree");
16470
- lines.push(`- Git: ${gitParts.join(", ")}`);
17135
+ lines.push("- " + formatGitFact(facts.git));
16471
17136
  if (facts.git.latestCommit) lines.push(`- Latest commit: ${facts.git.latestCommit}`);
16472
17137
  lines.push("- Current facts above outrank progress/dev-log files when they conflict.");
16473
17138
  if (facts.staleNotes.length > 0) {
@@ -16489,11 +17154,7 @@ function worksetFactLines(facts) {
16489
17154
  if (facts.latestChangelog) {
16490
17155
  lines.push("Latest changelog: " + facts.latestChangelog.version + (facts.latestChangelog.date ? " (" + facts.latestChangelog.date + ")" : ""));
16491
17156
  }
16492
- const gitParts = [];
16493
- if (facts.git.branch) gitParts.push("branch " + facts.git.branch);
16494
- if (facts.git.commit) gitParts.push("commit " + facts.git.commit);
16495
- gitParts.push(facts.git.dirty ? "dirty worktree" : "clean worktree");
16496
- lines.push("Git: " + gitParts.join(", "));
17157
+ lines.push(formatGitFact(facts.git));
16497
17158
  for (const note of facts.staleNotes.slice(0, 1)) {
16498
17159
  lines.push(
16499
17160
  "Historical note: " + note.path + (note.branchHint ? " (branch hint " + note.branchHint + "; " + note.reason + ")" : " (" + note.reason + ")")
@@ -16612,6 +17273,7 @@ var init_auto_context = __esm({
16612
17273
  init_external_provider();
16613
17274
  init_project_context();
16614
17275
  init_store();
17276
+ init_admission();
16615
17277
  init_task_lens();
16616
17278
  DEFAULT_MAX_AGE_MS = 10 * 60 * 1e3;
16617
17279
  }
@@ -16804,7 +17466,8 @@ async function attachTaskWorkset(input) {
16804
17466
  suspect: input.pack.warnings.filter((warning) => warning.status === "suspect").length,
16805
17467
  stale: input.pack.warnings.filter((warning) => warning.status === "stale").length
16806
17468
  },
16807
- runtimeCautions: input.runtimeCautions
17469
+ runtimeCautions: input.runtimeCautions,
17470
+ deliveryTarget: "context-pack"
16808
17471
  });
16809
17472
  return { ...input.pack, workset };
16810
17473
  }
@@ -18216,11 +18879,14 @@ __export(export_import_exports, {
18216
18879
  exportAsMarkdown: () => exportAsMarkdown,
18217
18880
  importFromJson: () => importFromJson
18218
18881
  });
18219
- async function exportAsJson(projectDir2, projectId) {
18882
+ async function exportAsJson(projectDir2, projectId, reader) {
18220
18883
  const store = getObservationStore();
18221
18884
  const allObs = await store.loadAll();
18222
18885
  const allSessions = await getSessionStore().loadAll();
18223
- const projectObs = allObs.filter((o) => o.projectId === projectId);
18886
+ const projectObs = filterReadableObservations(
18887
+ allObs.filter((o) => o.projectId === projectId),
18888
+ reader
18889
+ );
18224
18890
  const projectSessions = allSessions.filter((s) => s.projectId === projectId);
18225
18891
  const typeBreakdown = {};
18226
18892
  for (const obs of projectObs) {
@@ -18239,8 +18905,8 @@ async function exportAsJson(projectDir2, projectId) {
18239
18905
  }
18240
18906
  };
18241
18907
  }
18242
- async function exportAsMarkdown(projectDir2, projectId) {
18243
- const data = await exportAsJson(projectDir2, projectId);
18908
+ async function exportAsMarkdown(projectDir2, projectId, reader) {
18909
+ const data = await exportAsJson(projectDir2, projectId, reader);
18244
18910
  const lines = [];
18245
18911
  lines.push(`# Memorix Export: ${projectId}`);
18246
18912
  lines.push(`Exported: ${data.exportedAt}`);
@@ -18342,6 +19008,7 @@ var init_export_import = __esm({
18342
19008
  "src/memory/export-import.ts"() {
18343
19009
  "use strict";
18344
19010
  init_esm_shims();
19011
+ init_visibility();
18345
19012
  init_obs_store();
18346
19013
  init_session_store();
18347
19014
  OBSERVATION_ICONS2 = {
@@ -18518,6 +19185,7 @@ function isEligible(o, projectId) {
18518
19185
  if (isCommandLog2(o)) return false;
18519
19186
  if (isInactive(o)) return false;
18520
19187
  if (isOtherProject(o, projectId)) return false;
19188
+ if (!isEligibleForKnowledgePromotion(o)) return false;
18521
19189
  if (o.valueCategory === "ephemeral") return false;
18522
19190
  if (!contextualHasSubstance(o)) return false;
18523
19191
  return true;
@@ -18666,6 +19334,7 @@ var init_generator = __esm({
18666
19334
  "src/wiki/generator.ts"() {
18667
19335
  "use strict";
18668
19336
  init_esm_shims();
19337
+ init_admission();
18669
19338
  COMMAND_LOG_TITLE4 = /^(Ran:|Command:|Executed:)\s/i;
18670
19339
  SECTION_DEFS = [
18671
19340
  {
@@ -18952,6 +19621,9 @@ function sendError(res, message, status = 500) {
18952
19621
  function filterByProject(items, projectId) {
18953
19622
  return items.filter((item) => item.projectId === projectId);
18954
19623
  }
19624
+ function filterDashboardObservations(observations2, projectId) {
19625
+ return filterReadableObservations(observations2, { projectId });
19626
+ }
18955
19627
  function isActiveStatus(status) {
18956
19628
  return (status ?? "active") === "active";
18957
19629
  }
@@ -18975,13 +19647,8 @@ function prepareDashboardConfig(projectRoot) {
18975
19647
  }
18976
19648
  }
18977
19649
  function computeProjectGraphCounts(allEntities, allRelations, projectObs) {
18978
- const entityNames = new Set(
18979
- projectObs.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
18980
- );
18981
- const entities = allEntities.filter((e) => entityNames.has(e.name));
18982
- const entityNameSet = new Set(entities.map((e) => e.name));
18983
- const relations = allRelations.filter((r) => entityNameSet.has(r.from) && entityNameSet.has(r.to));
18984
- return { entities: entities.length, relations: relations.length, entityNames };
19650
+ const scoped = scopeKnowledgeGraphToProject({ entities: allEntities, relations: allRelations }, projectObs);
19651
+ return { entities: scoped.entities.length, relations: scoped.relations.length, entityNames: scoped.entityNames };
18985
19652
  }
18986
19653
  async function handleApi(req, res, dataDir, projectId, projectName, baseDir, projectRoot, projectResolved, mode = "standalone", port = 3210) {
18987
19654
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
@@ -19004,7 +19671,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19004
19671
  switch (apiPath) {
19005
19672
  case "/projects": {
19006
19673
  try {
19007
- const allObs = await getObservationStore().loadAll();
19674
+ const allObs = filterReadableObservations(
19675
+ await getObservationStore().loadAll(),
19676
+ {}
19677
+ );
19008
19678
  const projectSet = /* @__PURE__ */ new Map();
19009
19679
  for (const obs of allObs) {
19010
19680
  if (!isActiveStatus(obs.status)) continue;
@@ -19053,18 +19723,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19053
19723
  await initGraphStore(effectiveDataDir);
19054
19724
  const gStore = getGraphStore();
19055
19725
  const graph = { entities: gStore.loadEntities(), relations: gStore.loadRelations() };
19056
- const graphObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19057
- const projectEntityNames = new Set(
19058
- graphObs.filter((o) => o.entityName).map((o) => o.entityName)
19726
+ const graphObs = filterDashboardObservations(
19727
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19728
+ effectiveProjectId
19059
19729
  );
19060
- const entities = graph.entities.filter((e) => projectEntityNames.has(e.name));
19061
- const entityNameSet = new Set(entities.map((e) => e.name));
19062
- const relations = graph.relations.filter((r) => entityNameSet.has(r.from) && entityNameSet.has(r.to));
19063
- sendJson(res, { entities, relations });
19730
+ const scoped = scopeKnowledgeGraphToProject(graph, graphObs);
19731
+ sendJson(res, { entities: scoped.entities, relations: scoped.relations });
19064
19732
  break;
19065
19733
  }
19066
19734
  case "/observations": {
19067
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19735
+ const observations2 = filterDashboardObservations(
19736
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19737
+ effectiveProjectId
19738
+ );
19068
19739
  sendJson(res, observations2);
19069
19740
  break;
19070
19741
  }
@@ -19077,7 +19748,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19077
19748
  case "/stats": {
19078
19749
  await initGraphStore(effectiveDataDir);
19079
19750
  const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19080
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19751
+ const observations2 = filterDashboardObservations(
19752
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19753
+ effectiveProjectId
19754
+ );
19081
19755
  const nextId2 = await getObservationStore().loadIdCounter();
19082
19756
  const projectGraphCounts = computeProjectGraphCounts(graph.entities, graph.relations, observations2);
19083
19757
  const typeCounts = {};
@@ -19195,7 +19869,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19195
19869
  break;
19196
19870
  }
19197
19871
  case "/retention": {
19198
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19872
+ const observations2 = filterDashboardObservations(
19873
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19874
+ effectiveProjectId
19875
+ );
19199
19876
  const now3 = Date.now();
19200
19877
  const scored = observations2.filter((obs) => obs.type !== "probe").map((obs) => {
19201
19878
  const age = now3 - new Date(obs.createdAt || now3).getTime();
@@ -19233,7 +19910,10 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19233
19910
  const { generateKnowledgeBase: generateKnowledgeBase2 } = await Promise.resolve().then(() => (init_generator(), generator_exports));
19234
19911
  const { initMiniSkillStore: initMiniSkillStore2, getMiniSkillStore: getMiniSkillStore2 } = await Promise.resolve().then(() => (init_mini_skill_store(), mini_skill_store_exports));
19235
19912
  await initMiniSkillStore2(effectiveDataDir);
19236
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19913
+ const allObs = filterDashboardObservations(
19914
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19915
+ effectiveProjectId
19916
+ );
19237
19917
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19238
19918
  const overview = generateKnowledgeBase2({
19239
19919
  projectId: effectiveProjectId,
@@ -19249,22 +19929,19 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19249
19929
  const { initGraphStore: initGraphStore2, getGraphStore: getGraphStore2 } = await Promise.resolve().then(() => (init_graph_store(), graph_store_exports));
19250
19930
  await initMiniSkillStore2(effectiveDataDir);
19251
19931
  await initGraphStore2(effectiveDataDir);
19252
- const allObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19932
+ const allObs = filterDashboardObservations(
19933
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
19934
+ effectiveProjectId
19935
+ );
19253
19936
  const skills = await getMiniSkillStore2().loadByProject(effectiveProjectId);
19254
19937
  const fullGraph = { entities: getGraphStore2().loadEntities(), relations: getGraphStore2().loadRelations() };
19255
- const graphObs = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19256
- const projectEntityNames = new Set(
19257
- graphObs.filter((o) => o.entityName).map((o) => o.entityName)
19258
- );
19259
- const scopedEntities = fullGraph.entities.filter((e) => projectEntityNames.has(e.name));
19260
- const scopedEntityNameSet = new Set(scopedEntities.map((e) => e.name));
19261
- const scopedRelations = fullGraph.relations.filter((r) => scopedEntityNameSet.has(r.from) && scopedEntityNameSet.has(r.to));
19938
+ const scoped = scopeKnowledgeGraphToProject(fullGraph, allObs);
19262
19939
  const graph = generateKnowledgeGraph2({
19263
19940
  projectId: effectiveProjectId,
19264
19941
  observations: allObs,
19265
19942
  miniSkills: skills,
19266
- graphEntities: scopedEntities,
19267
- graphRelations: scopedRelations
19943
+ graphEntities: scoped.entities,
19944
+ graphRelations: scoped.relations
19268
19945
  });
19269
19946
  sendJson(res, graph);
19270
19947
  break;
@@ -19378,7 +20055,7 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19378
20055
  break;
19379
20056
  }
19380
20057
  case "/identity": {
19381
- const allObs = await getObservationStore().loadAll();
20058
+ const allObs = filterReadableObservations(await getObservationStore().loadAll(), {});
19382
20059
  const allProjectIds = [...new Set(allObs.map((o) => o.projectId).filter(Boolean))];
19383
20060
  let classifyProjectId2 = () => "real";
19384
20061
  let isDirtyProjectId2 = () => false;
@@ -19463,6 +20140,8 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19463
20140
  sendError(res, "Observation not found", 404);
19464
20141
  } else if (matchObs.projectId !== effectiveProjectId) {
19465
20142
  sendError(res, `Observation #${obsId} belongs to project "${matchObs.projectId}", not "${effectiveProjectId}"`, 403);
20143
+ } else if (!canManageObservation(matchObs, { projectId: effectiveProjectId })) {
20144
+ sendError(res, `Observation #${obsId} is not manageable from the unbound dashboard.`, 403);
19466
20145
  } else {
19467
20146
  await obsStore.remove(obsId);
19468
20147
  try {
@@ -19484,18 +20163,16 @@ async function handleApi(req, res, dataDir, projectId, projectName, baseDir, pro
19484
20163
  if (apiPath === "/export") {
19485
20164
  await initGraphStore(effectiveDataDir);
19486
20165
  const fullGraph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
19487
- const observations2 = await getObservationStore().loadByProject(effectiveProjectId, { status: "active" });
19488
- const nextId2 = await getObservationStore().loadIdCounter();
19489
- const exportEntityNames = new Set(
19490
- observations2.filter((o) => (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
20166
+ const observations2 = filterDashboardObservations(
20167
+ await getObservationStore().loadByProject(effectiveProjectId, { status: "active" }),
20168
+ effectiveProjectId
19491
20169
  );
19492
- const exportEntities = fullGraph.entities.filter((e) => exportEntityNames.has(e.name));
19493
- const exportEntitySet = new Set(exportEntities.map((e) => e.name));
19494
- const exportRelations = fullGraph.relations.filter((r) => exportEntitySet.has(r.from) && exportEntitySet.has(r.to));
20170
+ const nextId2 = await getObservationStore().loadIdCounter();
20171
+ const scoped = scopeKnowledgeGraphToProject(fullGraph, observations2);
19495
20172
  const exportData = {
19496
20173
  project: { id: effectiveProjectId, name: effectiveProjectName },
19497
20174
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
19498
- graph: { entities: exportEntities, relations: exportRelations },
20175
+ graph: { entities: scoped.entities, relations: scoped.relations },
19499
20176
  observations: observations2,
19500
20177
  nextId: nextId2
19501
20178
  };
@@ -19812,6 +20489,8 @@ var init_server = __esm({
19812
20489
  init_dotenv_loader();
19813
20490
  init_yaml_loader();
19814
20491
  init_yaml_loader();
20492
+ init_graph_scope();
20493
+ init_visibility();
19815
20494
  MIME_TYPES = {
19816
20495
  ".html": "text/html; charset=utf-8",
19817
20496
  ".css": "text/css; charset=utf-8",
@@ -20589,6 +21268,11 @@ async function createHandoffArtifact(input, storeObservation2, teamStore) {
20589
21268
  sourceDetail: "explicit",
20590
21269
  valueCategory: "core",
20591
21270
  // immune to retention archival
21271
+ // A direct handoff is not a project-wide memo. The sender retains access
21272
+ // and the explicit recipient receives a read grant. Broadcast handoffs
21273
+ // are deliberately visible only to active team members.
21274
+ visibility: input.toAgentId ? "personal" : "team",
21275
+ ...input.toAgentId ? { sharedWithAgentIds: [input.toAgentId] } : {},
20592
21276
  createdByAgentId: input.fromAgentId
20593
21277
  });
20594
21278
  try {
@@ -22528,6 +23212,24 @@ async function createAutoRelations(obs, extracted, graphManager) {
22528
23212
  const relationType = inferRelationType(obs);
22529
23213
  const relations = [];
22530
23214
  const selfName = obs.entityName.toLowerCase();
23215
+ const explicitRelated = [...new Set((obs.relatedEntities ?? []).map((name) => name.trim()).filter((name) => name && name.toLowerCase() !== selfName))];
23216
+ if (explicitRelated.length > 0) {
23217
+ await graphManager.createEntities(explicitRelated.map((name) => ({
23218
+ name,
23219
+ entityType: "related",
23220
+ observations: []
23221
+ })));
23222
+ for (const name of explicitRelated) {
23223
+ const matchedEntity = graphManager.findEntityByName(name);
23224
+ if (matchedEntity) {
23225
+ relations.push({
23226
+ from: obs.entityName,
23227
+ to: matchedEntity.name,
23228
+ relationType: "related_entity"
23229
+ });
23230
+ }
23231
+ }
23232
+ }
22531
23233
  const candidates = [
22532
23234
  ...extracted.identifiers,
22533
23235
  ...extracted.files.map((f) => f.split("/").pop()?.replace(/\.\w+$/, "") ?? ""),
@@ -22569,6 +23271,8 @@ async function createAutoRelations(obs, extracted, graphManager) {
22569
23271
 
22570
23272
  // src/server.ts
22571
23273
  init_entity_extractor();
23274
+ init_graph_scope();
23275
+ init_visibility();
22572
23276
 
22573
23277
  // src/compact/engine.ts
22574
23278
  init_esm_shims();
@@ -22694,7 +23398,15 @@ function formatTimeline(timeline) {
22694
23398
  function formatObservationDetail(doc) {
22695
23399
  const icon = getTypeIcon(doc.type);
22696
23400
  const lines = [];
22697
- const header = buildProvenanceHeader(doc.sourceDetail, doc.valueCategory, doc.source, doc.commitHash, doc.relatedCommits);
23401
+ const header = buildProvenanceHeader(
23402
+ doc.sourceDetail,
23403
+ doc.valueCategory,
23404
+ doc.admissionState,
23405
+ doc.admissionReason,
23406
+ doc.source,
23407
+ doc.commitHash,
23408
+ doc.relatedCommits
23409
+ );
22698
23410
  if (header) {
22699
23411
  lines.push(header);
22700
23412
  lines.push("");
@@ -22729,7 +23441,7 @@ function formatObservationDetail(doc) {
22729
23441
  }
22730
23442
  return lines.join("\n");
22731
23443
  }
22732
- function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash, relatedCommits) {
23444
+ function buildProvenanceHeader(sourceDetail, valueCategory, admissionState, admissionReason, source, commitHash, relatedCommits) {
22733
23445
  const sd = resolveSourceDetail(sourceDetail, source);
22734
23446
  if (!sd) return "";
22735
23447
  const label = sourceKindLabel(sd);
@@ -22740,11 +23452,21 @@ function buildProvenanceHeader(sourceDetail, valueCategory, source, commitHash,
22740
23452
  if (verificationLine) {
22741
23453
  lines.push(verificationLine);
22742
23454
  }
22743
- if (valueCategory === "core") {
23455
+ if (valueCategory === "core" && admissionState !== "candidate" && admissionState !== "ephemeral") {
22744
23456
  lines.push("[CORE] Core \u2014 immune to decay");
23457
+ } else if (valueCategory === "core") {
23458
+ lines.push("[CORE] Core candidate \u2014 not durable until qualification completes");
22745
23459
  } else if (valueCategory === "ephemeral") {
22746
23460
  lines.push("[WARN] Ephemeral \u2014 short-lived signal");
22747
23461
  }
23462
+ if (admissionState === "candidate") {
23463
+ lines.push("[PENDING] Candidate \u2014 excluded from automatic context until current Code Memory qualifies it");
23464
+ } else if (admissionState === "ephemeral") {
23465
+ lines.push("[TRACE] Ephemeral capture \u2014 excluded from automatic context");
23466
+ } else if (admissionState === "qualified") {
23467
+ lines.push("[QUALIFIED] Automatic evidence passed current Code Memory qualification");
23468
+ }
23469
+ if (admissionReason) lines.push(`Admission: ${redactCredentials(admissionReason)}`);
22748
23470
  return lines.join("\n");
22749
23471
  }
22750
23472
  function sourceKindLabel(sd) {
@@ -22825,6 +23547,7 @@ init_obs_store();
22825
23547
  init_mini_skill_store();
22826
23548
  init_mini_skills();
22827
23549
  init_secret_filter();
23550
+ init_visibility();
22828
23551
  function normalizeMemoryBrowseQuery(query) {
22829
23552
  const trimmed = query.trim();
22830
23553
  if (!trimmed) return "";
@@ -22867,11 +23590,13 @@ async function compactSearch(options) {
22867
23590
  );
22868
23591
  let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId);
22869
23592
  if (entries.length === 0 && options.projectId) {
22870
- const allObservations = getAllObservations();
23593
+ const allObservations = filterReadableObservations(getAllObservations(), options.reader);
22871
23594
  const projectAliases = new Set(await resolveAliases(options.projectId).catch(() => [options.projectId]));
22872
23595
  const projectHasStoredMemory = allObservations.some((obs) => obs.projectId && projectAliases.has(obs.projectId));
22873
23596
  if (!projectHasStoredMemory) {
22874
- formatted = `This project does not have any Memorix memories yet.
23597
+ formatted = options.reader ? `No Memorix memories are available to this session yet.
23598
+
23599
+ The tool call worked, but this session has no project-visible memory to retrieve.` : `This project does not have any Memorix memories yet.
22875
23600
 
22876
23601
  It looks like a fresh project: the tool call worked, but there is nothing stored to retrieve yet.
22877
23602
 
@@ -22885,8 +23610,8 @@ This project does have stored Memorix memories, but none matched the current que
22885
23610
  const totalTokens = countTextTokens(formatted);
22886
23611
  return { entries, formatted, totalTokens };
22887
23612
  }
22888
- async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3) {
22889
- const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter);
23613
+ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter = 3, reader) {
23614
+ const result = await getTimeline(anchorId, projectId, depthBefore, depthAfter, reader);
22890
23615
  const timeline = {
22891
23616
  anchorId,
22892
23617
  anchorEntry: result.anchor,
@@ -22897,7 +23622,7 @@ async function compactTimeline(anchorId, projectId, depthBefore = 3, depthAfter
22897
23622
  const totalTokens = countTextTokens(formatted);
22898
23623
  return { timeline, formatted, totalTokens };
22899
23624
  }
22900
- async function compactDetail(idsOrRefs) {
23625
+ async function compactDetail(idsOrRefs, options = {}) {
22901
23626
  const invalidRefs = [];
22902
23627
  const parsedRefs = [];
22903
23628
  for (let i = 0; i < idsOrRefs.length; i++) {
@@ -22954,7 +23679,7 @@ async function compactDetail(idsOrRefs) {
22954
23679
  const missingRefs = [];
22955
23680
  for (const ref of refs) {
22956
23681
  const obs = getObservation(ref.id, ref.projectId);
22957
- if (obs && (ref.projectId ? obs.projectId === ref.projectId : true)) {
23682
+ if (obs && (ref.projectId ? obs.projectId === ref.projectId : true) && canReadObservation(obs, options.reader)) {
22958
23683
  documentMap.set(toRefKey(ref), {
22959
23684
  id: makeOramaObservationId(obs.projectId, obs.id),
22960
23685
  observationId: obs.id,
@@ -22973,7 +23698,12 @@ async function compactDetail(idsOrRefs) {
22973
23698
  status: obs.status ?? "active",
22974
23699
  source: obs.source ?? "agent",
22975
23700
  sourceDetail: obs.sourceDetail ?? "",
22976
- valueCategory: obs.valueCategory ?? ""
23701
+ valueCategory: obs.valueCategory ?? "",
23702
+ admissionState: obs.admissionState ?? "",
23703
+ admissionReason: obs.admissionReason ?? "",
23704
+ visibility: obs.visibility ?? "project",
23705
+ createdByAgentId: obs.createdByAgentId ?? "",
23706
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
22977
23707
  });
22978
23708
  } else {
22979
23709
  missingRefs.push(ref);
@@ -22983,17 +23713,17 @@ async function compactDetail(idsOrRefs) {
22983
23713
  for (const ref of missingRefs) {
22984
23714
  const fallbackDocs = await getObservationsByIds([ref.id], ref.projectId);
22985
23715
  const doc = fallbackDocs[0];
22986
- if (doc) {
23716
+ if (doc && canReadObservation(doc, options.reader)) {
22987
23717
  documentMap.set(toRefKey(ref), doc);
22988
23718
  continue;
22989
23719
  }
22990
23720
  const stored = await getPersistedObservation(ref);
22991
- if (stored) {
23721
+ if (stored && canReadObservation(stored, options.reader)) {
22992
23722
  documentMap.set(toRefKey(ref), observationToDocument(stored));
22993
23723
  }
22994
23724
  }
22995
23725
  }
22996
- const allObs = getAllObservations();
23726
+ const allObs = filterReadableObservations(getAllObservations(), options.reader);
22997
23727
  const crossRefMap = /* @__PURE__ */ new Map();
22998
23728
  for (const ref of refs) {
22999
23729
  const obs = getObservation(ref.id, ref.projectId);
@@ -23107,7 +23837,12 @@ function observationToDocument(obs) {
23107
23837
  status: obs.status ?? "active",
23108
23838
  source: obs.source ?? "agent",
23109
23839
  sourceDetail: obs.sourceDetail ?? "",
23110
- valueCategory: obs.valueCategory ?? ""
23840
+ valueCategory: obs.valueCategory ?? "",
23841
+ admissionState: obs.admissionState ?? "",
23842
+ admissionReason: obs.admissionReason ?? "",
23843
+ visibility: obs.visibility ?? "project",
23844
+ createdByAgentId: obs.createdByAgentId ?? "",
23845
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
23111
23846
  };
23112
23847
  }
23113
23848
  function formatMiniSkillDetail(skill2, provenanceStatus) {
@@ -23226,7 +23961,9 @@ function auditMemoryQuality(observations2, options) {
23226
23961
  status: obs.status ?? "active",
23227
23962
  source: obs.source ?? "agent",
23228
23963
  sourceDetail: obs.sourceDetail ?? "",
23229
- valueCategory: obs.valueCategory ?? ""
23964
+ valueCategory: obs.valueCategory ?? "",
23965
+ admissionState: obs.admissionState ?? "",
23966
+ admissionReason: obs.admissionReason ?? ""
23230
23967
  }, options.referenceTime) !== "active").map((obs) => makeEntry(obs, "Outside active retention zone"));
23231
23968
  const summary = {
23232
23969
  total: scoped.length,
@@ -23268,6 +24005,7 @@ function auditMemoryQuality(observations2, options) {
23268
24005
 
23269
24006
  // src/memory/graph-context.ts
23270
24007
  init_retention();
24008
+ init_admission();
23271
24009
  function formatGraphContextPrompt(packet) {
23272
24010
  const lines = [
23273
24011
  "## Memory Context Packet",
@@ -23425,7 +24163,9 @@ function scoreForPacket(obs, queryTokens, referenceTime, broadMemoryQuery) {
23425
24163
  status: obs.status ?? "active",
23426
24164
  source: obs.source ?? "agent",
23427
24165
  sourceDetail: obs.sourceDetail ?? "",
23428
- valueCategory: obs.valueCategory ?? ""
24166
+ valueCategory: obs.valueCategory ?? "",
24167
+ admissionState: obs.admissionState ?? "",
24168
+ admissionReason: obs.admissionReason ?? ""
23429
24169
  }, referenceTime) !== "active") {
23430
24170
  score -= 2;
23431
24171
  }
@@ -23519,8 +24259,11 @@ function buildGraphContextPacket(observations2, options) {
23519
24259
  ...audit.issues.orphans.map((entry) => entry.id),
23520
24260
  ...audit.issues.retentionCandidates.map((entry) => entry.id)
23521
24261
  ]);
23522
- const filteredObservations = observations2.filter((obs) => obs.projectId === options.projectId && !riskIds.has(obs.id));
23523
- const baseObservations = filteredObservations.length > 0 ? filteredObservations : observations2;
24262
+ const deliveryEligible = observations2.filter(
24263
+ (obs) => obs.projectId === options.projectId && isEligibleForAutomaticDelivery(obs)
24264
+ );
24265
+ const filteredObservations = deliveryEligible.filter((obs) => !riskIds.has(obs.id));
24266
+ const baseObservations = filteredObservations.length > 0 ? filteredObservations : deliveryEligible;
23524
24267
  const memories = pickMemories(baseObservations, options.projectId, options.query, options.limit ?? 5, referenceTime);
23525
24268
  const entities = buildEntities(memories);
23526
24269
  const entityNames = new Set(entities.map((entity) => entity.name));
@@ -26007,6 +26750,8 @@ async function createMemorixServer(cwd, existingServer, sharedTeam, options = {}
26007
26750
  let projectResolutionError = null;
26008
26751
  let explicitProjectBound = false;
26009
26752
  let currentAgentId;
26753
+ let teamStore;
26754
+ let initTeamStoreForProject;
26010
26755
  if (detectedProject) {
26011
26756
  rawProject = detectedProject;
26012
26757
  } else {
@@ -26254,7 +26999,7 @@ The path should point to a directory containing a .git folder.`
26254
26999
  };
26255
27000
  const server = existingServer ?? new McpServer({
26256
27001
  name: "memorix",
26257
- version: true ? "1.2.0" : "1.0.1"
27002
+ version: true ? "1.2.2" : "1.0.1"
26258
27003
  });
26259
27004
  const originalRegisterTool = server.registerTool.bind(server);
26260
27005
  server.registerTool = ((name, ...args) => {
@@ -26274,11 +27019,26 @@ The path should point to a directory containing a .git folder.`
26274
27019
  }
26275
27020
  return originalRegisterTool(name, ...args);
26276
27021
  });
27022
+ const getObservationReader = (scope = "project") => {
27023
+ let isTeamMember = false;
27024
+ if (teamFeaturesEnabled && currentAgentId) {
27025
+ try {
27026
+ const agent = teamStore.getAgent(currentAgentId);
27027
+ isTeamMember = agent?.project_id === project.id && agent.status === "active";
27028
+ } catch {
27029
+ }
27030
+ }
27031
+ return {
27032
+ ...scope === "project" ? { projectId: project.id } : {},
27033
+ ...currentAgentId ? { agentId: currentAgentId } : {},
27034
+ isTeamMember
27035
+ };
27036
+ };
26277
27037
  server.registerTool(
26278
27038
  "memorix_store",
26279
27039
  {
26280
27040
  title: "Store Memory",
26281
- description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Stored memories persist across sessions and are shared with other IDEs and agents (Cursor, Windsurf, Claude Code, Codex, Copilot, Gemini CLI, OpenCode, OpenClaw, Hermes Agent, Oh-my-Pi, Kiro, Antigravity, Trae) via the same local data directory.",
27041
+ description: "Store a new observation/memory. Automatically indexed for search. Use type to classify: gotcha ([GOTCHA] critical pitfall), decision ([DECISION] architecture choice), problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), session-request ([SESSION] original goal). Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity.",
26282
27042
  inputSchema: {
26283
27043
  entityName: z2.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
26284
27044
  type: z2.enum(OBSERVATION_TYPES).describe("Observation type for classification"),
@@ -26296,361 +27056,214 @@ The path should point to a directory containing a .git folder.`
26296
27056
  completion: z2.number().optional().describe("Completion percentage 0-100")
26297
27057
  }).optional().describe("Progress tracking for task/feature observations"),
26298
27058
  relatedCommits: z2.array(z2.string()).optional().describe("Git commit hashes this memory relates to (links ground truth \u2194 reasoning)"),
26299
- relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references")
27059
+ relatedEntities: z2.array(z2.string()).optional().describe("Other entity names this memory cross-references"),
27060
+ visibility: z2.enum(["personal", "project", "team"]).optional().describe(
27061
+ "Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true."
27062
+ )
26300
27063
  }
26301
27064
  },
26302
- async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities }) => {
27065
+ async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
26303
27066
  const unresolved = requireResolvedProject("store memory in the current project");
26304
27067
  if (unresolved) return unresolved;
26305
- return withFreshIndex(async () => {
26306
- let entityName = rawEntityName;
26307
- let type = rawType;
26308
- let title = rawTitle;
26309
- let safeFacts = facts ? coerceStringArray(facts) : void 0;
26310
- const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
26311
- const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
26312
- let formationMode = "active";
26313
- if (process.env.MEMORIX_FORMATION_MODE) {
26314
- formationMode = process.env.MEMORIX_FORMATION_MODE;
26315
- } else {
26316
- try {
26317
- const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
26318
- formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
26319
- } catch {
26320
- }
26321
- }
26322
- const useFormation = formationMode === "active";
26323
- let formationResult = null;
26324
- let formationNote = "";
26325
- if (useFormation && !topicKey && !progress) {
26326
- let currentFormationStage = "setup";
26327
- const completedFormationStages = {};
26328
- const formationStartTime = Date.now();
26329
- const onFormationStageEvent = (event) => {
26330
- if (event.status === "start") {
26331
- currentFormationStage = event.stage;
26332
- return;
26333
- }
26334
- currentFormationStage = event.stage;
26335
- if (event.stageDurationMs !== void 0) {
26336
- completedFormationStages[event.stage] = event.stageDurationMs;
26337
- }
26338
- };
26339
- try {
26340
- const formationConfig = {
26341
- mode: "active",
26342
- useLLM: isLLMEnabled(),
26343
- minValueScore: 0.3,
26344
- searchMemories: async (q, limit, pid) => {
26345
- const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
26346
- if (result.entries.length === 0) return [];
26347
- const details = await compactDetail(result.entries.map((e) => e.id));
26348
- return details.documents.map((d, i) => ({
26349
- id: Number(d.id.replace("obs-", "")),
26350
- observationId: d.observationId,
26351
- title: d.title,
26352
- narrative: d.narrative,
26353
- facts: d.facts,
26354
- entityName: d.entityName,
26355
- type: d.type,
26356
- score: result.entries[i]?.score ?? 0
26357
- }));
26358
- },
26359
- getObservation: (id) => {
26360
- const o = getObservation(id);
26361
- if (!o) return null;
26362
- return {
26363
- id: o.id,
26364
- entityName: o.entityName,
26365
- type: o.type,
26366
- title: o.title,
26367
- narrative: o.narrative,
26368
- facts: o.facts,
26369
- topicKey: o.topicKey
26370
- };
26371
- },
26372
- getEntityNames: () => graphManager.getEntityNames(),
26373
- onStageEvent: onFormationStageEvent
26374
- };
26375
- formationResult = await withTimeout2(
26376
- runFormation({
26377
- entityName,
26378
- type,
26379
- title,
26380
- narrative,
26381
- facts: safeFacts,
26382
- projectId: project.id,
26383
- source: "explicit"
26384
- }, formationConfig),
26385
- FORMATION_TIMEOUT_MS,
26386
- "Formation pipeline"
26387
- );
26388
- const modeIcon = "[FAST]";
26389
- formationNote = `
26390
- ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
26391
- if (formationResult.extraction.extractedFacts.length > 0) {
26392
- formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
26393
- }
26394
- if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
26395
- if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
26396
- if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
26397
- } catch (formationErr) {
26398
- const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
26399
- const elapsedMs = Date.now() - formationStartTime;
26400
- const stageSummary = formatFormationStageDurations(completedFormationStages);
26401
- console.error(
26402
- `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
26403
- );
26404
- if (!isTimeout && formationErr instanceof Error) {
26405
- console.error(`[memorix] Formation error: ${formationErr.message}`);
27068
+ const requestedVisibility = visibility ?? "project";
27069
+ const reader = getObservationReader();
27070
+ if (requestedVisibility !== "project" && !currentAgentId) {
27071
+ return {
27072
+ content: [{ type: "text", text: "Personal or team memory requires memorix_session_start with joinTeam=true so Memorix can bind an owner." }],
27073
+ isError: true
27074
+ };
27075
+ }
27076
+ if (requestedVisibility === "team" && !reader.isTeamMember) {
27077
+ return {
27078
+ content: [{ type: "text", text: "Team memory requires an active coordination membership in the current project." }],
27079
+ isError: true
27080
+ };
27081
+ }
27082
+ try {
27083
+ return await withFreshIndex(async () => {
27084
+ let entityName = rawEntityName;
27085
+ let type = rawType;
27086
+ let title = rawTitle;
27087
+ let safeFacts = facts ? coerceStringArray(facts) : void 0;
27088
+ const safeFiles = filesModified ? coerceStringArray(filesModified) : void 0;
27089
+ const safeConcepts = concepts ? coerceStringArray(concepts) : void 0;
27090
+ let formationMode = "active";
27091
+ if (process.env.MEMORIX_FORMATION_MODE) {
27092
+ formationMode = process.env.MEMORIX_FORMATION_MODE;
27093
+ } else {
27094
+ try {
27095
+ const { getBehaviorConfig: getBehaviorConfig2 } = await Promise.resolve().then(() => (init_behavior(), behavior_exports));
27096
+ formationMode = getBehaviorConfig2({ projectRoot: project.rootPath }).formationMode;
27097
+ } catch {
26406
27098
  }
26407
- formationNote = `
26408
- [WARN] Formation ${isTimeout ? "timed out" : "failed"} \u2014 storing base observation without enrichment`;
26409
27099
  }
26410
- }
26411
- if (useFormation && formationResult && formationResult.resolution.action !== "new") {
26412
- const { action: action2, targetId, reason } = formationResult.resolution;
26413
- if (action2 === "merge" && targetId) {
26414
- const targetObs = getObservation(targetId);
26415
- if (targetObs) {
26416
- await storeObservation({
26417
- entityName: targetObs.entityName,
26418
- type: targetObs.type,
26419
- title: formationResult.title,
26420
- narrative: formationResult.narrative,
26421
- facts: formationResult.facts,
26422
- filesModified: safeFiles,
26423
- concepts: safeConcepts,
26424
- projectId: project.id,
26425
- topicKey: targetObs.topicKey,
26426
- progress,
26427
- sourceDetail: "explicit",
26428
- createdByAgentId: currentAgentId
26429
- });
26430
- return {
26431
- content: [{
26432
- type: "text",
26433
- text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26434
- }]
26435
- };
26436
- }
26437
- } else if (action2 === "evolve" && targetId) {
26438
- const targetObs = getObservation(targetId);
26439
- if (targetObs) {
26440
- await storeObservation({
26441
- entityName: targetObs.entityName,
26442
- type: targetObs.type,
26443
- title: formationResult.title,
26444
- narrative: formationResult.narrative,
26445
- facts: formationResult.facts,
26446
- filesModified: safeFiles,
26447
- concepts: safeConcepts,
26448
- projectId: project.id,
26449
- topicKey: targetObs.topicKey,
26450
- progress,
26451
- sourceDetail: "explicit",
26452
- createdByAgentId: currentAgentId
26453
- });
26454
- return {
26455
- content: [{
26456
- type: "text",
26457
- text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
26458
- }]
26459
- };
26460
- }
26461
- } else if (action2 === "discard") {
26462
- return {
26463
- content: [{
26464
- type: "text",
26465
- text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
26466
- }]
27100
+ const useFormation = formationMode === "active";
27101
+ let formationResult = null;
27102
+ let formationNote = "";
27103
+ if (useFormation && !topicKey && !progress) {
27104
+ let currentFormationStage = "setup";
27105
+ const completedFormationStages = {};
27106
+ const formationStartTime = Date.now();
27107
+ const onFormationStageEvent = (event) => {
27108
+ if (event.status === "start") {
27109
+ currentFormationStage = event.stage;
27110
+ return;
27111
+ }
27112
+ currentFormationStage = event.stage;
27113
+ if (event.stageDurationMs !== void 0) {
27114
+ completedFormationStages[event.stage] = event.stageDurationMs;
27115
+ }
26467
27116
  };
26468
- }
26469
- }
26470
- let compactAction = "";
26471
- let compactMerged = false;
26472
- if (!useFormation && !topicKey && !progress) {
26473
- try {
26474
- const searchResult = await compactSearch({
26475
- query: `${title} ${narrative.substring(0, 200)}`,
26476
- limit: 5,
26477
- projectId: project.id,
26478
- status: "active"
26479
- });
26480
- const similarEntries = searchResult.entries.map((e) => e);
26481
- if (similarEntries.length > 0) {
26482
- const similarIds = similarEntries.map((e) => e.id);
26483
- const details = await compactDetail(similarIds);
26484
- const existingMemories = details.documents.map((d, i) => ({
26485
- id: d.observationId,
26486
- title: d.title,
26487
- narrative: d.narrative,
26488
- facts: d.facts,
26489
- score: similarEntries[i]?.score ?? 0
26490
- }));
26491
- const decision = await withTimeout2(
26492
- compactOnWrite(
26493
- { title, narrative, facts: safeFacts ?? [] },
26494
- existingMemories
26495
- ),
26496
- COMPACT_ON_WRITE_TIMEOUT_MS,
26497
- "Compact-on-write"
26498
- );
26499
- if (decision.action === "UPDATE" && decision.targetId) {
26500
- const targetObs = getObservation(decision.targetId);
26501
- if (targetObs) {
26502
- await storeObservation({
26503
- entityName: targetObs.entityName,
26504
- type: targetObs.type,
26505
- title: decision.mergedNarrative ? title : targetObs.title,
26506
- narrative: decision.mergedNarrative ?? narrative,
26507
- facts: decision.mergedFacts ?? safeFacts,
26508
- filesModified: safeFiles,
26509
- concepts: safeConcepts,
26510
- projectId: project.id,
26511
- topicKey: targetObs.topicKey,
26512
- progress,
26513
- sourceDetail: "explicit",
26514
- createdByAgentId: currentAgentId
26515
- });
26516
- compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
26517
- compactMerged = true;
27117
+ try {
27118
+ const formationConfig = {
27119
+ mode: "active",
27120
+ useLLM: isLLMEnabled(),
27121
+ minValueScore: 0.3,
27122
+ searchMemories: async (q, limit, pid) => {
27123
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27124
+ if (result.entries.length === 0) return [];
27125
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27126
+ return details.documents.map((d, i) => ({
27127
+ id: Number(d.id.replace("obs-", "")),
27128
+ observationId: d.observationId,
27129
+ title: d.title,
27130
+ narrative: d.narrative,
27131
+ facts: d.facts,
27132
+ entityName: d.entityName,
27133
+ type: d.type,
27134
+ score: result.entries[i]?.score ?? 0
27135
+ }));
27136
+ },
27137
+ getObservation: (id) => {
27138
+ const o = getObservation(id);
27139
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
26518
27140
  return {
26519
- content: [{
26520
- type: "text",
26521
- text: `${compactAction}
26522
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
26523
- }]
27141
+ id: o.id,
27142
+ entityName: o.entityName,
27143
+ type: o.type,
27144
+ title: o.title,
27145
+ narrative: o.narrative,
27146
+ facts: o.facts,
27147
+ topicKey: o.topicKey
26524
27148
  };
26525
- }
26526
- } else if (decision.action === "NONE") {
27149
+ },
27150
+ getEntityNames: () => graphManager.getEntityNames(),
27151
+ onStageEvent: onFormationStageEvent
27152
+ };
27153
+ formationResult = await withTimeout2(
27154
+ runFormation({
27155
+ entityName,
27156
+ type,
27157
+ title,
27158
+ narrative,
27159
+ facts: safeFacts,
27160
+ projectId: project.id,
27161
+ source: "explicit"
27162
+ }, formationConfig),
27163
+ FORMATION_TIMEOUT_MS,
27164
+ "Formation pipeline"
27165
+ );
27166
+ const modeIcon = "[FAST]";
27167
+ formationNote = `
27168
+ ${modeIcon} Formation[active]: ${formationResult.evaluation.category} (${formationResult.evaluation.score.toFixed(2)}) | ${formationResult.resolution.action} | ${formationResult.pipeline.durationMs}ms`;
27169
+ if (formationResult.extraction.extractedFacts.length > 0) {
27170
+ formationNote += ` | +${formationResult.extraction.extractedFacts.length} facts`;
27171
+ }
27172
+ if (formationResult.extraction.titleImproved) formationNote += " | title\u2191";
27173
+ if (formationResult.extraction.entityResolved) formationNote += ` | entity\u2192${formationResult.entityName}`;
27174
+ if (formationResult.extraction.typeCorrected) formationNote += ` | type\u2192${formationResult.type}`;
27175
+ } catch (formationErr) {
27176
+ const isTimeout = formationErr instanceof Error && formationErr.message.includes("timed out");
27177
+ const elapsedMs = Date.now() - formationStartTime;
27178
+ const stageSummary = formatFormationStageDurations(completedFormationStages);
27179
+ console.error(
27180
+ `[memorix] Formation ${isTimeout ? "timed out" : "failed"} in memorix_store after ${elapsedMs}ms/${FORMATION_TIMEOUT_MS}ms at stage ${currentFormationStage}${stageSummary ? ` | completed: ${stageSummary}` : ""}`
27181
+ );
27182
+ if (!isTimeout && formationErr instanceof Error) {
27183
+ console.error(`[memorix] Formation error: ${formationErr.message}`);
27184
+ }
27185
+ formationNote = `
27186
+ [WARN] Formation ${isTimeout ? "timed out" : "failed"} \u2014 storing base observation without enrichment`;
27187
+ }
27188
+ }
27189
+ if (useFormation && formationResult && formationResult.resolution.action !== "new") {
27190
+ const { action: action2, targetId, reason } = formationResult.resolution;
27191
+ if (action2 === "merge" && targetId) {
27192
+ const targetObs = getObservation(targetId);
27193
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27194
+ await storeObservation({
27195
+ entityName: targetObs.entityName,
27196
+ type: targetObs.type,
27197
+ title: formationResult.title,
27198
+ narrative: formationResult.narrative,
27199
+ facts: formationResult.facts,
27200
+ filesModified: safeFiles,
27201
+ concepts: safeConcepts,
27202
+ projectId: project.id,
27203
+ topicKey: targetObs.topicKey,
27204
+ progress,
27205
+ sourceDetail: "explicit",
27206
+ createdByAgentId: currentAgentId,
27207
+ visibility,
27208
+ visibilityReader: reader
27209
+ });
26527
27210
  return {
26528
27211
  content: [{
26529
27212
  type: "text",
26530
- text: `[SKIP] Compact SKIP: ${decision.reason}
26531
- Existing memory #${decision.targetId} already covers this.
26532
- Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27213
+ text: `[UPDATED] Formation MERGE: merged into #${targetId} (${reason})${formationNote}`
26533
27214
  }]
26534
27215
  };
26535
- } else if (decision.action === "DELETE" && decision.targetId) {
26536
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
26537
- await resolveObservations2([decision.targetId], "resolved");
26538
- compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
26539
27216
  }
26540
- if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
26541
- const currentFacts = safeFacts ?? [];
26542
- const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
26543
- if (newFacts.length > 0) {
26544
- compactAction += ` | +${newFacts.length} LLM-extracted facts`;
26545
- }
27217
+ } else if (action2 === "evolve" && targetId) {
27218
+ const targetObs = getObservation(targetId);
27219
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27220
+ await storeObservation({
27221
+ entityName: targetObs.entityName,
27222
+ type: targetObs.type,
27223
+ title: formationResult.title,
27224
+ narrative: formationResult.narrative,
27225
+ facts: formationResult.facts,
27226
+ filesModified: safeFiles,
27227
+ concepts: safeConcepts,
27228
+ projectId: project.id,
27229
+ topicKey: targetObs.topicKey,
27230
+ progress,
27231
+ sourceDetail: "explicit",
27232
+ createdByAgentId: currentAgentId,
27233
+ visibility,
27234
+ visibilityReader: reader
27235
+ });
27236
+ return {
27237
+ content: [{
27238
+ type: "text",
27239
+ text: `[UPDATED] Formation EVOLVE: evolved #${targetId} (${reason})${formationNote}`
27240
+ }]
27241
+ };
26546
27242
  }
27243
+ } else if (action2 === "discard") {
27244
+ return {
27245
+ content: [{
27246
+ type: "text",
27247
+ text: `[SKIP] Formation DISCARD: ${reason}${formationNote}`
27248
+ }]
27249
+ };
26547
27250
  }
26548
- } catch {
26549
- }
26550
- }
26551
- if (formationResult && formationResult.resolution.action === "new") {
26552
- const llmFacts = formationResult.extraction.extractedFacts;
26553
- if (llmFacts.length > 0) {
26554
- const currentFacts = safeFacts ?? [];
26555
- const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
26556
- const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
26557
- if (newFacts.length > 0) {
26558
- safeFacts = [...currentFacts, ...newFacts];
26559
- }
26560
- }
26561
- if (formationResult.extraction.titleImproved && formationResult.title) {
26562
- title = formationResult.title;
26563
- }
26564
- if (formationResult.extraction.typeCorrected && formationResult.type) {
26565
- type = formationResult.type;
26566
- }
26567
- if (formationResult.extraction.entityResolved && formationResult.entityName) {
26568
- entityName = formationResult.entityName;
26569
- }
26570
- }
26571
- await graphManager.createEntities([
26572
- { name: entityName, entityType: "auto", observations: [] }
26573
- ]);
26574
- let sessionId;
26575
- try {
26576
- const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
26577
- const active = await getActiveSession2(projectDir2, project.id);
26578
- if (active) sessionId = active.id;
26579
- } catch {
26580
- }
26581
- let finalNarrative = narrative;
26582
- let compressionNote = "";
26583
- try {
26584
- const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
26585
- const { compressed, saved, usedLLM } = await withTimeout2(
26586
- compressNarrative2(narrative, safeFacts, type),
26587
- COMPRESSION_TIMEOUT_MS,
26588
- "Narrative compression"
26589
- );
26590
- if (usedLLM && saved > 0) {
26591
- finalNarrative = compressed;
26592
- compressionNote = ` | compressed -${saved} tokens`;
26593
- }
26594
- } catch {
26595
- }
26596
- let attributionWarning = "";
26597
- try {
26598
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
26599
- if (attrCheck.suspicious) {
26600
- attributionWarning = `
26601
- [WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
26602
27251
  }
26603
- } catch {
26604
- }
26605
- const { observation: obs, upserted } = await storeObservation({
26606
- entityName,
26607
- type,
26608
- title,
26609
- narrative: finalNarrative,
26610
- facts: safeFacts,
26611
- filesModified: safeFiles,
26612
- concepts: safeConcepts,
26613
- projectId: project.id,
26614
- topicKey,
26615
- sessionId,
26616
- progress,
26617
- relatedCommits,
26618
- relatedEntities,
26619
- sourceDetail: "explicit",
26620
- valueCategory: formationResult?.evaluation.category,
26621
- createdByAgentId: currentAgentId
26622
- });
26623
- await graphManager.addObservations([
26624
- { entityName, contents: [`[#${obs.id}] ${title}`] }
26625
- ]);
26626
- const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
26627
- const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
26628
- const enrichmentParts = [];
26629
- const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
26630
- const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
26631
- if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
26632
- if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
26633
- if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
26634
- if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
26635
- if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
26636
- const enrichment = enrichmentParts.length > 0 ? `
26637
- Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26638
- const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
26639
- if (!useFormation && !topicKey && !progress) {
26640
- const shadowFormation = async () => {
26641
- let oldCompactDecision = null;
27252
+ let compactAction = "";
27253
+ let compactMerged = false;
27254
+ if (!useFormation && !topicKey && !progress) {
26642
27255
  try {
26643
- const compactStart = Date.now();
26644
27256
  const searchResult = await compactSearch({
26645
27257
  query: `${title} ${narrative.substring(0, 200)}`,
26646
27258
  limit: 5,
26647
27259
  projectId: project.id,
26648
- status: "active"
27260
+ status: "active",
27261
+ reader
26649
27262
  });
26650
27263
  const similarEntries = searchResult.entries.map((e) => e);
26651
27264
  if (similarEntries.length > 0) {
26652
27265
  const similarIds = similarEntries.map((e) => e.id);
26653
- const details = await compactDetail(similarIds);
27266
+ const details = await compactDetail(similarIds, { reader });
26654
27267
  const existingMemories = details.documents.map((d, i) => ({
26655
27268
  id: d.observationId,
26656
27269
  title: d.title,
@@ -26658,78 +27271,266 @@ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
26658
27271
  facts: d.facts,
26659
27272
  score: similarEntries[i]?.score ?? 0
26660
27273
  }));
26661
- const decision = await compactOnWrite(
26662
- { title, narrative, facts: safeFacts ?? [] },
26663
- existingMemories
27274
+ const decision = await withTimeout2(
27275
+ compactOnWrite(
27276
+ { title, narrative, facts: safeFacts ?? [] },
27277
+ existingMemories
27278
+ ),
27279
+ COMPACT_ON_WRITE_TIMEOUT_MS,
27280
+ "Compact-on-write"
26664
27281
  );
26665
- oldCompactDecision = {
26666
- action: decision.action,
26667
- targetId: decision.targetId,
26668
- reason: decision.reason,
26669
- durationMs: Date.now() - compactStart
26670
- };
27282
+ if (decision.action === "UPDATE" && decision.targetId) {
27283
+ const targetObs = getObservation(decision.targetId);
27284
+ if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
27285
+ await storeObservation({
27286
+ entityName: targetObs.entityName,
27287
+ type: targetObs.type,
27288
+ title: decision.mergedNarrative ? title : targetObs.title,
27289
+ narrative: decision.mergedNarrative ?? narrative,
27290
+ facts: decision.mergedFacts ?? safeFacts,
27291
+ filesModified: safeFiles,
27292
+ concepts: safeConcepts,
27293
+ projectId: project.id,
27294
+ topicKey: targetObs.topicKey,
27295
+ progress,
27296
+ sourceDetail: "explicit",
27297
+ createdByAgentId: currentAgentId,
27298
+ visibility,
27299
+ visibilityReader: reader
27300
+ });
27301
+ compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
27302
+ compactMerged = true;
27303
+ return {
27304
+ content: [{
27305
+ type: "text",
27306
+ text: `${compactAction}
27307
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27308
+ }]
27309
+ };
27310
+ }
27311
+ } else if (decision.action === "NONE") {
27312
+ return {
27313
+ content: [{
27314
+ type: "text",
27315
+ text: `[SKIP] Compact SKIP: ${decision.reason}
27316
+ Existing memory #${decision.targetId} already covers this.
27317
+ Mode: ${decision.usedLLM ? "LLM" : "heuristic"}`
27318
+ }]
27319
+ };
27320
+ } else if (decision.action === "DELETE" && decision.targetId) {
27321
+ const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27322
+ await resolveObservations2([decision.targetId], "resolved");
27323
+ compactAction = ` | Compact: resolved outdated #${decision.targetId}`;
27324
+ }
27325
+ if (decision.enrichedFacts && decision.enrichedFacts.length > 0) {
27326
+ const currentFacts = safeFacts ?? [];
27327
+ const newFacts = decision.enrichedFacts.filter((f) => !currentFacts.includes(f));
27328
+ if (newFacts.length > 0) {
27329
+ compactAction += ` | +${newFacts.length} LLM-extracted facts`;
27330
+ }
27331
+ }
26671
27332
  }
26672
27333
  } catch {
26673
27334
  }
26674
- const formationConfig = {
26675
- mode: formationMode,
26676
- useLLM: isLLMEnabled(),
26677
- minValueScore: 0.3,
26678
- searchMemories: async (q, limit, pid) => {
26679
- const result = await compactSearch({ query: q, limit, projectId: pid, status: "active" });
26680
- if (result.entries.length === 0) return [];
26681
- const details = await compactDetail(result.entries.map((e) => e.id));
26682
- return details.documents.map((d, i) => ({
26683
- id: Number(d.id.replace("obs-", "")),
26684
- observationId: d.observationId,
26685
- title: d.title,
26686
- narrative: d.narrative,
26687
- facts: d.facts,
26688
- entityName: d.entityName,
26689
- type: d.type,
26690
- score: result.entries[i]?.score ?? 0
26691
- }));
26692
- },
26693
- getObservation: (id) => {
26694
- const o = getObservation(id);
26695
- if (!o) return null;
26696
- return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
26697
- },
26698
- getEntityNames: () => graphManager.getEntityNames()
26699
- };
26700
- const formed = await withTimeout2(
26701
- runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
26702
- FORMATION_TIMEOUT_MS,
26703
- "Shadow formation"
27335
+ }
27336
+ if (formationResult && formationResult.resolution.action === "new") {
27337
+ const llmFacts = formationResult.extraction.extractedFacts;
27338
+ if (llmFacts.length > 0) {
27339
+ const currentFacts = safeFacts ?? [];
27340
+ const currentLower = new Set(currentFacts.map((f) => f.toLowerCase().trim()));
27341
+ const newFacts = llmFacts.filter((f) => !currentLower.has(f.toLowerCase().trim()));
27342
+ if (newFacts.length > 0) {
27343
+ safeFacts = [...currentFacts, ...newFacts];
27344
+ }
27345
+ }
27346
+ if (formationResult.extraction.titleImproved && formationResult.title) {
27347
+ title = formationResult.title;
27348
+ }
27349
+ if (formationResult.extraction.typeCorrected && formationResult.type) {
27350
+ type = formationResult.type;
27351
+ }
27352
+ if (formationResult.extraction.entityResolved && formationResult.entityName) {
27353
+ entityName = formationResult.entityName;
27354
+ }
27355
+ }
27356
+ await graphManager.createEntities([
27357
+ { name: entityName, entityType: "auto", observations: [] }
27358
+ ]);
27359
+ let sessionId;
27360
+ try {
27361
+ const { getActiveSession: getActiveSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
27362
+ const active = await getActiveSession2(projectDir2, project.id);
27363
+ if (active) sessionId = active.id;
27364
+ } catch {
27365
+ }
27366
+ let finalNarrative = narrative;
27367
+ let compressionNote = "";
27368
+ try {
27369
+ const { compressNarrative: compressNarrative2 } = await Promise.resolve().then(() => (init_quality(), quality_exports));
27370
+ const { compressed, saved, usedLLM } = await withTimeout2(
27371
+ compressNarrative2(narrative, safeFacts, type),
27372
+ COMPRESSION_TIMEOUT_MS,
27373
+ "Narrative compression"
26704
27374
  );
26705
- const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
26706
- if (oldCompactDecision) {
26707
- recordBeforeAfterMetrics2({
26708
- formationAction: formed.resolution.action,
26709
- formationTargetId: formed.resolution.targetId,
26710
- oldCompactAction: oldCompactDecision.action,
26711
- oldCompactTargetId: oldCompactDecision.targetId,
26712
- oldCompactReason: oldCompactDecision.reason,
26713
- formationValueScore: formed.evaluation.score,
26714
- formationValueCategory: formed.evaluation.category,
26715
- formationDurationMs: formed.pipeline.durationMs,
26716
- compactDurationMs: oldCompactDecision.durationMs
26717
- });
27375
+ if (usedLLM && saved > 0) {
27376
+ finalNarrative = compressed;
27377
+ compressionNote = ` | compressed -${saved} tokens`;
26718
27378
  }
26719
- };
26720
- shadowFormation().catch(() => {
27379
+ } catch {
27380
+ }
27381
+ let attributionWarning = "";
27382
+ try {
27383
+ const attrCheck = await checkProjectAttribution(
27384
+ entityName,
27385
+ project.id,
27386
+ filterReadableObservations(getAllObservations(), reader)
27387
+ );
27388
+ if (attrCheck.suspicious) {
27389
+ attributionWarning = `
27390
+ [WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
27391
+ }
27392
+ } catch {
27393
+ }
27394
+ const { observation: obs, upserted } = await storeObservation({
27395
+ entityName,
27396
+ type,
27397
+ title,
27398
+ narrative: finalNarrative,
27399
+ facts: safeFacts,
27400
+ filesModified: safeFiles,
27401
+ concepts: safeConcepts,
27402
+ projectId: project.id,
27403
+ topicKey,
27404
+ sessionId,
27405
+ progress,
27406
+ relatedCommits,
27407
+ relatedEntities,
27408
+ sourceDetail: "explicit",
27409
+ valueCategory: formationResult?.evaluation.category,
27410
+ createdByAgentId: currentAgentId,
27411
+ visibility,
27412
+ visibilityReader: reader
26721
27413
  });
26722
- }
26723
- return {
26724
- content: [
26725
- {
26726
- type: "text",
26727
- text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
27414
+ await graphManager.addObservations([
27415
+ { entityName, contents: [`[#${obs.id}] ${title}`] }
27416
+ ]);
27417
+ const extracted = extractEntities([title, narrative, ...safeFacts ?? []].join(" "));
27418
+ const autoRelCount = await createAutoRelations(obs, extracted, graphManager);
27419
+ const enrichmentParts = [];
27420
+ const autoFiles = obs.filesModified.filter((f) => !(safeFiles ?? []).includes(f));
27421
+ const autoConcepts = obs.concepts.filter((c) => !(safeConcepts ?? []).includes(c));
27422
+ if (autoFiles.length > 0) enrichmentParts.push(`+${autoFiles.length} files extracted`);
27423
+ if (autoConcepts.length > 0) enrichmentParts.push(`+${autoConcepts.length} concepts enriched`);
27424
+ if (autoRelCount > 0) enrichmentParts.push(`+${autoRelCount} relations auto-created`);
27425
+ if (obs.hasCausalLanguage) enrichmentParts.push("causal language detected");
27426
+ if (upserted) enrichmentParts.push(`topic upserted (rev ${obs.revisionCount ?? 1})`);
27427
+ const enrichment = enrichmentParts.length > 0 ? `
27428
+ Auto-enriched: ${enrichmentParts.join(", ")}` : "";
27429
+ const action = upserted ? "[UPDATED] Updated" : "[OK] Stored";
27430
+ if (!useFormation && !topicKey && !progress) {
27431
+ const shadowFormation = async () => {
27432
+ let oldCompactDecision = null;
27433
+ try {
27434
+ const compactStart = Date.now();
27435
+ const searchResult = await compactSearch({
27436
+ query: `${title} ${narrative.substring(0, 200)}`,
27437
+ limit: 5,
27438
+ projectId: project.id,
27439
+ status: "active",
27440
+ reader
27441
+ });
27442
+ const similarEntries = searchResult.entries.map((e) => e);
27443
+ if (similarEntries.length > 0) {
27444
+ const similarIds = similarEntries.map((e) => e.id);
27445
+ const details = await compactDetail(similarIds, { reader });
27446
+ const existingMemories = details.documents.map((d, i) => ({
27447
+ id: d.observationId,
27448
+ title: d.title,
27449
+ narrative: d.narrative,
27450
+ facts: d.facts,
27451
+ score: similarEntries[i]?.score ?? 0
27452
+ }));
27453
+ const decision = await compactOnWrite(
27454
+ { title, narrative, facts: safeFacts ?? [] },
27455
+ existingMemories
27456
+ );
27457
+ oldCompactDecision = {
27458
+ action: decision.action,
27459
+ targetId: decision.targetId,
27460
+ reason: decision.reason,
27461
+ durationMs: Date.now() - compactStart
27462
+ };
27463
+ }
27464
+ } catch {
27465
+ }
27466
+ const formationConfig = {
27467
+ mode: formationMode,
27468
+ useLLM: isLLMEnabled(),
27469
+ minValueScore: 0.3,
27470
+ searchMemories: async (q, limit, pid) => {
27471
+ const result = await compactSearch({ query: q, limit, projectId: pid, status: "active", reader });
27472
+ if (result.entries.length === 0) return [];
27473
+ const details = await compactDetail(result.entries.map((e) => e.id), { reader });
27474
+ return details.documents.map((d, i) => ({
27475
+ id: Number(d.id.replace("obs-", "")),
27476
+ observationId: d.observationId,
27477
+ title: d.title,
27478
+ narrative: d.narrative,
27479
+ facts: d.facts,
27480
+ entityName: d.entityName,
27481
+ type: d.type,
27482
+ score: result.entries[i]?.score ?? 0
27483
+ }));
27484
+ },
27485
+ getObservation: (id) => {
27486
+ const o = getObservation(id);
27487
+ if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
27488
+ return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
27489
+ },
27490
+ getEntityNames: () => graphManager.getEntityNames()
27491
+ };
27492
+ const formed = await withTimeout2(
27493
+ runFormation({ entityName, type, title, narrative, facts: safeFacts, projectId: project.id, source: "explicit", topicKey }, formationConfig),
27494
+ FORMATION_TIMEOUT_MS,
27495
+ "Shadow formation"
27496
+ );
27497
+ const { recordBeforeAfterMetrics: recordBeforeAfterMetrics2 } = await Promise.resolve().then(() => (init_formation(), formation_exports));
27498
+ if (oldCompactDecision) {
27499
+ recordBeforeAfterMetrics2({
27500
+ formationAction: formed.resolution.action,
27501
+ formationTargetId: formed.resolution.targetId,
27502
+ oldCompactAction: oldCompactDecision.action,
27503
+ oldCompactTargetId: oldCompactDecision.targetId,
27504
+ oldCompactReason: oldCompactDecision.reason,
27505
+ formationValueScore: formed.evaluation.score,
27506
+ formationValueCategory: formed.evaluation.category,
27507
+ formationDurationMs: formed.pipeline.durationMs,
27508
+ compactDurationMs: oldCompactDecision.durationMs
27509
+ });
27510
+ }
27511
+ };
27512
+ shadowFormation().catch(() => {
27513
+ });
27514
+ }
27515
+ return {
27516
+ content: [
27517
+ {
27518
+ type: "text",
27519
+ text: `${action} observation #${obs.id} "${title}" (~${obs.tokens} tokens)
26728
27520
  Entity: ${entityName} | Type: ${type} | Project: ${project.id}${obs.topicKey ? ` | Topic: ${obs.topicKey}` : ""}${compactAction}${compressionNote}${enrichment}${formationNote}${attributionWarning}`
26729
- }
26730
- ]
27521
+ }
27522
+ ]
27523
+ };
27524
+ });
27525
+ } catch (error) {
27526
+ return {
27527
+ content: [{
27528
+ type: "text",
27529
+ text: error instanceof Error ? error.message : "Failed to store memory."
27530
+ }],
27531
+ isError: true
26731
27532
  };
26732
- });
27533
+ }
26733
27534
  }
26734
27535
  );
26735
27536
  server.registerTool(
@@ -26801,7 +27602,8 @@ Use this as the \`topicKey\` parameter in \`memorix_store\` to enable upsert beh
26801
27602
  // Use scope: 'global' to explicitly search all projects.
26802
27603
  projectId: scope === "global" ? void 0 : project.id,
26803
27604
  status: status ?? "active",
26804
- source
27605
+ source,
27606
+ reader: getObservationReader(scope === "global" ? "global" : "project")
26805
27607
  });
26806
27608
  const timeoutPromise = new Promise(
26807
27609
  (_, reject) => setTimeout(() => reject(new Error(`Search timeout after ${TIMEOUT_MS}ms`)), TIMEOUT_MS)
@@ -26859,7 +27661,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26859
27661
  const unresolved = requireResolvedProject("build graph context for the current project");
26860
27662
  if (unresolved) return unresolved;
26861
27663
  const { getObservationStore: getObservationStore2 } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
26862
- const observations2 = await getObservationStore2().loadByProject(project.id);
27664
+ const observations2 = filterReadableObservations(
27665
+ await getObservationStore2().loadByProject(project.id),
27666
+ getObservationReader()
27667
+ );
26863
27668
  const packet = buildGraphContextPacket(observations2, {
26864
27669
  projectId: project.id,
26865
27670
  query,
@@ -26911,7 +27716,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26911
27716
  Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)),
26912
27717
  Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports))
26913
27718
  ]);
26914
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27719
+ const observations2 = filterReadableObservations(
27720
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27721
+ getObservationReader()
27722
+ );
26915
27723
  const context = await buildAutoProjectContext2({
26916
27724
  project,
26917
27725
  dataDir: projectDir2,
@@ -26986,7 +27794,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
26986
27794
  { getResolvedConfig: getResolvedConfig2 },
26987
27795
  { getExternalCodeGraphContext: getExternalCodeGraphContext2 },
26988
27796
  { getObservationStore: getObservationStore2 },
26989
- { collectCurrentProjectFacts: collectCurrentProjectFacts2 },
27797
+ { collectCurrentProjectFacts: collectCurrentProjectFacts2, formatGitFact: formatGitFact2 },
26990
27798
  { resolveTaskLens: resolveTaskLens2 }
26991
27799
  ] = await Promise.all([
26992
27800
  Promise.resolve().then(() => (init_store(), store_exports)),
@@ -27001,7 +27809,10 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27001
27809
  await store.init(projectDir2);
27002
27810
  const codegraphConfig = getResolvedConfig2({ projectRoot: project.rootPath }).codegraph;
27003
27811
  const exclude = codegraphConfig.excludePatterns;
27004
- const observations2 = await getObservationStore2().loadByProject(project.id, { status: "active" });
27812
+ const observations2 = filterReadableObservations(
27813
+ await getObservationStore2().loadByProject(project.id, { status: "active" }),
27814
+ getObservationReader()
27815
+ );
27005
27816
  observations2.reverse();
27006
27817
  const basePack = assembleContextPackForTask2({
27007
27818
  store,
@@ -27027,7 +27838,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27027
27838
  if (currentFacts.latestChangelog) {
27028
27839
  worksetFacts.push("Latest changelog: " + currentFacts.latestChangelog.version + (currentFacts.latestChangelog.date ? " (" + currentFacts.latestChangelog.date + ")" : ""));
27029
27840
  }
27030
- worksetFacts.push("Git: " + (currentFacts.git.branch ? "branch " + currentFacts.git.branch + ", " : "") + (currentFacts.git.dirty ? "dirty worktree" : "clean worktree"));
27841
+ worksetFacts.push(formatGitFact2(currentFacts.git));
27031
27842
  const codeState = snapshot ? "- Code state: " + (snapshot.baseRevision ? snapshot.baseRevision.slice(0, 12) : "Git unavailable") + ", " + snapshot.worktreeState + " worktree, epoch " + snapshot.sourceEpoch : "- Code state: no completed snapshot yet";
27032
27843
  const pack = await attachTaskWorkset2({
27033
27844
  pack: basePack,
@@ -27059,11 +27870,13 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27059
27870
  "memorix_knowledge",
27060
27871
  {
27061
27872
  title: "Knowledge Workspace",
27062
- description: "Manage the reviewable project Knowledge Workspace. Use it only for deliberate knowledge operations: initialize a local or versioned workspace, inspect status, compile source-backed proposals, lint, apply a reviewed proposal, or manage canonical workflows. It is intentionally absent from the default micro/lite profiles.",
27873
+ description: "Manage the reviewable project Knowledge Workspace. Use it only for deliberate knowledge operations: initialize a local or versioned workspace, review source-backed claims, compile proposals, lint, apply a reviewed proposal, or manage canonical workflows. It is intentionally absent from the default micro/lite profiles.",
27063
27874
  inputSchema: {
27064
27875
  action: z2.enum([
27065
27876
  "workspace_init",
27066
27877
  "status",
27878
+ "claim_list",
27879
+ "claim_review",
27067
27880
  "compile",
27068
27881
  "lint",
27069
27882
  "proposal_apply",
@@ -27078,6 +27891,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27078
27891
  path: z2.string().optional().describe("Explicit versioned workspace path, used only by workspace_init"),
27079
27892
  proposalId: z2.string().optional().describe("Pending proposal id for proposal_apply"),
27080
27893
  allowManualOverwrite: z2.boolean().optional().default(false).describe("Explicitly allow proposal_apply to replace a manually edited page"),
27894
+ claimId: z2.string().optional().describe("Source-backed claim id for claim_review"),
27895
+ claimReviewState: z2.enum(["approved", "rejected"]).optional().describe("Deliberate review verdict for claim_review"),
27896
+ reviewDetail: z2.string().max(2e3).optional().describe("Evidence check performed before approving or rejecting a claim"),
27081
27897
  workflowId: z2.string().optional().describe("Canonical workflow id for workflow preview, apply, or run"),
27082
27898
  agent: z2.string().optional().describe("Target agent for a workflow adapter"),
27083
27899
  task: z2.string().optional().describe("Task text for workflow selection or a workflow run"),
@@ -27094,6 +27910,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27094
27910
  path: workspacePath,
27095
27911
  proposalId,
27096
27912
  allowManualOverwrite,
27913
+ claimId,
27914
+ claimReviewState,
27915
+ reviewDetail,
27097
27916
  workflowId,
27098
27917
  agent,
27099
27918
  task,
@@ -27116,6 +27935,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27116
27935
  };
27117
27936
  const [
27118
27937
  { ClaimStore: ClaimStore2 },
27938
+ { reviewClaim: reviewClaim2 },
27119
27939
  { CodeGraphStore: CodeGraphStore2 },
27120
27940
  { initializeKnowledgeWorkspace: initializeKnowledgeWorkspace2, loadKnowledgeWorkspace: loadKnowledgeWorkspace2 },
27121
27941
  { KnowledgeWorkspaceStore: KnowledgeWorkspaceStore2 },
@@ -27131,6 +27951,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27131
27951
  }
27132
27952
  ] = await Promise.all([
27133
27953
  Promise.resolve().then(() => (init_claim_store(), claim_store_exports)),
27954
+ Promise.resolve().then(() => (init_claims(), claims_exports)),
27134
27955
  Promise.resolve().then(() => (init_store(), store_exports)),
27135
27956
  Promise.resolve().then(() => (init_workspace(), workspace_exports)),
27136
27957
  Promise.resolve().then(() => (init_workspace_store(), workspace_store_exports)),
@@ -27180,10 +28001,52 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27180
28001
  targetPath: proposal.targetPath,
27181
28002
  reason: proposal.reason,
27182
28003
  createdAt: proposal.createdAt
27183
- }))
28004
+ })),
28005
+ reviewableClaims: claims.listClaims(project.id, { limit: 100 }).filter((claim) => claim.reviewState === "needs-review").map((claim) => ({ id: claim.id, subject: claim.subject, predicate: claim.predicate, objectValue: claim.objectValue }))
27184
28006
  }
27185
28007
  });
27186
28008
  }
28009
+ if (action === "claim_list") {
28010
+ return text({
28011
+ claims: claims.listClaims(project.id, { limit: 100 }).map((claim) => ({
28012
+ id: claim.id,
28013
+ subject: claim.subject,
28014
+ predicate: claim.predicate,
28015
+ objectValue: claim.objectValue,
28016
+ status: claim.status,
28017
+ reviewState: claim.reviewState,
28018
+ origin: claim.origin,
28019
+ confidence: claim.confidence,
28020
+ evidenceCount: claims.listEvidence(claim.id).length
28021
+ })),
28022
+ next: "Approve only after checking the linked evidence. Rejected claims stay out of retrieval and publication."
28023
+ });
28024
+ }
28025
+ if (action === "claim_review") {
28026
+ const requestedClaimId = requireText(claimId, "claimId");
28027
+ const detail = requireText(reviewDetail, "reviewDetail");
28028
+ if (!requestedClaimId || !claimReviewState || !detail) {
28029
+ return text({ error: "claimId, claimReviewState, and reviewDetail are required for claim_review." }, true);
28030
+ }
28031
+ const existing = claims.getClaim(requestedClaimId);
28032
+ if (!existing || existing.projectId !== project.id) {
28033
+ return text({ error: "Claim was not found for the current project." }, true);
28034
+ }
28035
+ const claim = reviewClaim2(claims, {
28036
+ claimId: requestedClaimId,
28037
+ reviewState: claimReviewState,
28038
+ detail
28039
+ });
28040
+ return text({
28041
+ claim: {
28042
+ id: claim.id,
28043
+ status: claim.status,
28044
+ reviewState: claim.reviewState,
28045
+ updatedAt: claim.updatedAt
28046
+ },
28047
+ next: claim.reviewState === "approved" ? "This approved source-backed claim can now be considered by knowledge compilation." : "This rejected claim is excluded from retrieval and knowledge compilation."
28048
+ });
28049
+ }
27187
28050
  if (action === "compile") {
27188
28051
  const result = await compileKnowledgeWorkspace2({ workspace, claims });
27189
28052
  return text({
@@ -27222,7 +28085,13 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27222
28085
  if (action === "workflow_import") {
27223
28086
  const result = await importWindsurfWorkflows2({ workspace, projectRoot });
27224
28087
  return text({
27225
- imported: result.imported.map((workflow2) => ({ id: workflow2.id, title: workflow2.title, sourcePath: workflow2.sourcePath })),
28088
+ imported: result.imported.map((workflow2) => ({
28089
+ id: workflow2.id,
28090
+ title: workflow2.title,
28091
+ sourcePath: workflow2.sourcePath,
28092
+ importedFrom: workflow2.importedFrom,
28093
+ verificationGates: workflow2.verificationGates
28094
+ })),
27226
28095
  skipped: result.skipped
27227
28096
  });
27228
28097
  }
@@ -27234,7 +28103,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27234
28103
  title: workflow2.title,
27235
28104
  status: workflow2.status,
27236
28105
  taskLenses: workflow2.taskLenses,
27237
- sourcePath: workflow2.sourcePath
28106
+ sourcePath: workflow2.sourcePath,
28107
+ importedFrom: workflow2.importedFrom,
28108
+ verificationGates: workflow2.verificationGates
27238
28109
  })),
27239
28110
  parseErrors: synced.errors
27240
28111
  });
@@ -27313,9 +28184,15 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27313
28184
  }
27314
28185
  },
27315
28186
  async ({ ids, status }) => {
27316
- const { resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28187
+ const { resolveObservations: resolveObservations2, getObservation: getObservation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27317
28188
  const safeIds = (Array.isArray(ids) ? ids : [ids]).map((id) => coerceNumber(id, 0)).filter((id) => id > 0);
27318
- const result = await resolveObservations2(safeIds, status ?? "resolved");
28189
+ const reader = getObservationReader();
28190
+ const authorizedIds = safeIds.filter((id) => {
28191
+ const observation = getObservation2(id, project.id);
28192
+ return observation ? canManageObservation(observation, reader) : false;
28193
+ });
28194
+ const result = await resolveObservations2(authorizedIds, status ?? "resolved");
28195
+ const deniedCount = safeIds.length - authorizedIds.length;
27319
28196
  const parts = [];
27320
28197
  if (result.resolved.length > 0) {
27321
28198
  parts.push(`[OK] Resolved ${result.resolved.length} observation(s): #${result.resolved.join(", #")}`);
@@ -27323,6 +28200,9 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27323
28200
  if (result.notFound.length > 0) {
27324
28201
  parts.push(`[WARN] Not found: #${result.notFound.join(", #")}`);
27325
28202
  }
28203
+ if (deniedCount > 0) {
28204
+ parts.push(`[WARN] Skipped ${deniedCount} observation(s) outside this session's write scope.`);
28205
+ }
27326
28206
  parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
27327
28207
  parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
27328
28208
  return {
@@ -27352,6 +28232,7 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27352
28232
  async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
27353
28233
  const unresolved = requireResolvedProject("store reasoning in the current project");
27354
28234
  if (unresolved) return unresolved;
28235
+ const reader = getObservationReader();
27355
28236
  return withFreshIndex(async () => {
27356
28237
  const narrativeParts = [rationale];
27357
28238
  if (alternatives && alternatives.length > 0) {
@@ -27374,7 +28255,11 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27374
28255
  ]);
27375
28256
  let reasoningAttributionWarning = "";
27376
28257
  try {
27377
- const attrCheck = await checkProjectAttribution(entityName, project.id, getAllObservations());
28258
+ const attrCheck = await checkProjectAttribution(
28259
+ entityName,
28260
+ project.id,
28261
+ filterReadableObservations(getAllObservations(), reader)
28262
+ );
27378
28263
  if (attrCheck.suspicious) {
27379
28264
  reasoningAttributionWarning = `
27380
28265
  [WARN] Attribution notice: entity "${entityName}" has 0 observations in "${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" (confidence: ${attrCheck.confidence}). Verify the correct project is bound before storing.`;
@@ -27394,7 +28279,8 @@ _Search mode: ${getLastSearchMode2(project.id)}_`;
27394
28279
  relatedCommits,
27395
28280
  relatedEntities,
27396
28281
  sourceDetail: "explicit",
27397
- createdByAgentId: currentAgentId
28282
+ createdByAgentId: currentAgentId,
28283
+ visibility: "project"
27398
28284
  });
27399
28285
  await graphManager.addObservations([
27400
28286
  { entityName, contents: [`[#${obs.id}] [REASONING] ${decision}`] }
@@ -27426,7 +28312,11 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27426
28312
  const minCount = threshold ?? 2;
27427
28313
  let entries;
27428
28314
  try {
27429
- entries = await auditProjectObservations(project.id, await withFreshIndex(() => getAllObservations()), minCount);
28315
+ entries = await auditProjectObservations(
28316
+ project.id,
28317
+ await withFreshIndex(() => filterReadableObservations(getAllObservations(), getObservationReader())),
28318
+ minCount
28319
+ );
27430
28320
  } catch (err) {
27431
28321
  return {
27432
28322
  content: [{
@@ -27491,7 +28381,8 @@ Entity: ${entityName} | ${facts.length} facts | ${obs.tokens} tokens${reasoningA
27491
28381
  limit: safeLimit,
27492
28382
  type: "reasoning",
27493
28383
  projectId: scope === "global" ? void 0 : project.id,
27494
- status: "active"
28384
+ status: "active",
28385
+ reader: getObservationReader(scope === "global" ? "global" : "project")
27495
28386
  }));
27496
28387
  if (result.entries.length === 0) {
27497
28388
  return {
@@ -27516,7 +28407,11 @@ ${result.formatted}` }]
27516
28407
  },
27517
28408
  async ({ query, dryRun }) => {
27518
28409
  const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27519
- const allObs = await withFreshIndex(() => getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id));
28410
+ const reader = getObservationReader();
28411
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28412
+ getAllObservations2().filter((o) => (o.status ?? "active") === "active" && o.projectId === project.id),
28413
+ reader
28414
+ ).filter((observation) => canManageObservation(observation, reader)));
27520
28415
  if (allObs.length < 2) {
27521
28416
  return { content: [{ type: "text", text: "Not enough active memories to deduplicate." }] };
27522
28417
  }
@@ -27530,7 +28425,7 @@ ${result.formatted}` }]
27530
28425
  }
27531
28426
  let candidates;
27532
28427
  if (query) {
27533
- const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active" });
28428
+ const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: "active", reader });
27534
28429
  const idSet = new Set(searchResult.entries.map((e) => e.id));
27535
28430
  candidates = allObs.filter((o) => idSet.has(o.id));
27536
28431
  } else {
@@ -27620,7 +28515,8 @@ ${actions.join("\n")}`
27620
28515
  safeAnchor,
27621
28516
  project.id,
27622
28517
  safeBefore,
27623
- safeAfter
28518
+ safeAfter,
28519
+ getObservationReader()
27624
28520
  );
27625
28521
  return {
27626
28522
  content: [
@@ -27654,12 +28550,14 @@ ${actions.join("\n")}`
27654
28550
  const safeTypedRefs = coerceStringArray(typedRefs);
27655
28551
  let result;
27656
28552
  try {
28553
+ const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id) || safeTypedRefs.some((ref) => ref.includes("@") && !ref.endsWith(`@${project.id}`));
28554
+ const reader = getObservationReader(hasCrossProjectRef ? "global" : "project");
27657
28555
  if (safeTypedRefs.length > 0) {
27658
- result = await compactDetail(safeTypedRefs);
28556
+ result = await compactDetail(safeTypedRefs, { reader });
27659
28557
  } else if (safeRefs.length > 0) {
27660
- result = await compactDetail(safeRefs);
28558
+ result = await compactDetail(safeRefs, { reader });
27661
28559
  } else {
27662
- result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })));
28560
+ result = await compactDetail(safeIds.map((id) => ({ id, projectId: project.id })), { reader });
27663
28561
  }
27664
28562
  } catch (err) {
27665
28563
  return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
@@ -27685,11 +28583,15 @@ ${actions.join("\n")}`
27685
28583
  },
27686
28584
  async (args) => {
27687
28585
  const action = args.action ?? "report";
27688
- const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, archiveExpired: archiveExpired2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
28586
+ const { getRetentionSummary: getRetentionSummary2, getArchiveCandidates: getArchiveCandidates2, rankByRelevance: rankByRelevance2, getRetentionZone: getRetentionZone2, explainRetention: explainRetention2 } = await Promise.resolve().then(() => (init_retention(), retention_exports));
27689
28587
  const { getDb: getDb2 } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
27690
28588
  const { search: search2 } = await import("@orama/orama");
27691
- const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
27692
- const allObs = await withFreshIndex(() => getAllObservations2());
28589
+ const { getAllObservations: getAllObservations2, resolveObservations: resolveObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28590
+ const reader = getObservationReader();
28591
+ const allObs = await withFreshIndex(() => filterReadableObservations(
28592
+ getAllObservations2().filter((observation) => observation.projectId === project.id),
28593
+ reader
28594
+ ));
27693
28595
  const accessMap = /* @__PURE__ */ new Map();
27694
28596
  try {
27695
28597
  const database = await getDb2();
@@ -27706,20 +28608,6 @@ ${actions.join("\n")}`
27706
28608
  }
27707
28609
  } catch {
27708
28610
  }
27709
- if (action === "archive") {
27710
- const result = await archiveExpired2(projectDir2, void 0, accessMap, project.id);
27711
- if (result.archived === 0) {
27712
- return {
27713
- content: [{ type: "text", text: "[OK] No expired observations to archive. All memories are within their retention period." }]
27714
- };
27715
- }
27716
- return {
27717
- content: [{ type: "text", text: `[ARCHIVED] Archived ${result.archived} expired observations (status set to 'archived' in-place)
27718
- ${result.remaining} active observations remaining.
27719
-
27720
- Archived memories are hidden from default search but can be found with status: "all".` }]
27721
- };
27722
- }
27723
28611
  const docs = allObs.map((obs) => ({
27724
28612
  id: `obs-${obs.id}`,
27725
28613
  observationId: obs.id,
@@ -27738,13 +28626,34 @@ Archived memories are hidden from default search but can be found with status: "
27738
28626
  status: obs.status ?? "active",
27739
28627
  source: obs.source ?? "agent",
27740
28628
  sourceDetail: obs.sourceDetail ?? "",
27741
- valueCategory: obs.valueCategory ?? ""
28629
+ valueCategory: obs.valueCategory ?? "",
28630
+ admissionState: obs.admissionState ?? "",
28631
+ admissionReason: obs.admissionReason ?? "",
28632
+ visibility: obs.visibility ?? "project",
28633
+ createdByAgentId: obs.createdByAgentId ?? "",
28634
+ sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? [])
27742
28635
  }));
27743
28636
  if (docs.length === 0) {
27744
28637
  return {
27745
28638
  content: [{ type: "text", text: "No observations found for this project." }]
27746
28639
  };
27747
28640
  }
28641
+ if (action === "archive") {
28642
+ const managedIds = new Set(
28643
+ allObs.filter((observation) => canManageObservation(observation, reader)).map((observation) => observation.id)
28644
+ );
28645
+ const candidates2 = getArchiveCandidates2(docs).filter((document) => managedIds.has(document.observationId));
28646
+ if (candidates2.length === 0) {
28647
+ return {
28648
+ content: [{ type: "text", text: "[OK] No expired memories in this session's write scope to archive." }]
28649
+ };
28650
+ }
28651
+ const result = await resolveObservations2(candidates2.map((document) => document.observationId), "archived");
28652
+ return {
28653
+ content: [{ type: "text", text: `[ARCHIVED] Archived ${result.resolved.length} expired observation(s) in this session's write scope.
28654
+ ${Math.max(0, managedIds.size - result.resolved.length)} visible writable observations remaining.` }]
28655
+ };
28656
+ }
27748
28657
  if (action === "stale") {
27749
28658
  const staleDocs = docs.filter((d) => getRetentionZone2(d) === "stale");
27750
28659
  if (staleDocs.length === 0) {
@@ -28063,13 +28972,14 @@ Archived memories are hidden from default search but can be found with status: "
28063
28972
  async function scopeGraphToProject(graph) {
28064
28973
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28065
28974
  const allObs = await withFreshIndex(() => getAllObservations2());
28066
- const projectEntityNames = new Set(
28067
- allObs.filter((o) => o.projectId === project.id && (o.status ?? "active") === "active" && o.entityName).map((o) => o.entityName)
28975
+ const scoped = scopeKnowledgeGraphToProject(
28976
+ graph,
28977
+ filterReadableObservations(
28978
+ allObs.filter((observation) => observation.projectId === project.id),
28979
+ getObservationReader()
28980
+ )
28068
28981
  );
28069
- const entities = graph.entities.filter((e) => projectEntityNames.has(e.name));
28070
- const entityNameSet = new Set(entities.map((e) => e.name));
28071
- const relations = graph.relations.filter((r) => entityNameSet.has(r.from) && entityNameSet.has(r.to));
28072
- return { entities, relations };
28982
+ return { entities: scoped.entities, relations: scoped.relations };
28073
28983
  }
28074
28984
  server.registerTool(
28075
28985
  "read_graph",
@@ -28342,7 +29252,10 @@ ${skill2.content}` }]
28342
29252
  };
28343
29253
  }
28344
29254
  const { getObservationStore: getStore } = await Promise.resolve().then(() => (init_obs_store(), obs_store_exports));
28345
- const allObs = await getStore().loadAll();
29255
+ const allObs = filterReadableObservations(
29256
+ (await getStore().loadAll()).filter((observation) => observation.projectId === project.id),
29257
+ getObservationReader()
29258
+ );
28346
29259
  const obsData = allObs.map((o) => ({
28347
29260
  id: o.id || 0,
28348
29261
  entityName: o.entityName || "unknown",
@@ -28441,7 +29354,11 @@ ${skill2.content}` }]
28441
29354
  }
28442
29355
  const { getAllObservations: getAllObservations2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
28443
29356
  const allObs = await withFreshIndex(() => getAllObservations2());
28444
- const matched = allObs.filter((o) => observationIds.includes(o.id));
29357
+ const reader = getObservationReader();
29358
+ const matched = filterReadableObservations(
29359
+ allObs.filter((observation) => observation.projectId === project.id && observationIds.includes(observation.id)),
29360
+ reader
29361
+ );
28445
29362
  if (matched.length === 0) {
28446
29363
  return { content: [{ type: "text", text: `No observations found for IDs: [${observationIds.join(", ")}]. Use \`memorix_search\` to find valid IDs.` }], isError: true };
28447
29364
  }
@@ -28449,6 +29366,13 @@ ${skill2.content}` }]
28449
29366
  if (nonActive.length > 0) {
28450
29367
  return { content: [{ type: "text", text: `Cannot promote: ${nonActive.length} observation(s) are not active: ${nonActive.map((o) => `#${o.id} (${o.status})`).join(", ")}. Only active observations can be promoted to permanent knowledge.` }], isError: true };
28451
29368
  }
29369
+ const nonProjectShared = matched.filter((observation) => resolveObservationVisibility(observation) !== "project");
29370
+ if (nonProjectShared.length > 0) {
29371
+ return {
29372
+ content: [{ type: "text", text: `Cannot promote private or team-scoped observations: ${nonProjectShared.map((observation) => `#${observation.id}`).join(", ")}. Promote only deliberate project-shared knowledge.` }],
29373
+ isError: true
29374
+ };
29375
+ }
28452
29376
  const skill2 = await promoteToMiniSkill2(projectDir2, project.id, matched, { trigger, instruction, tags });
28453
29377
  const lines = [
28454
29378
  `[OK] Created mini-skill #${skill2.id}`,
@@ -28607,8 +29531,11 @@ Ensure the path points to a directory containing a .git folder (or a subdirector
28607
29531
  const lastSeen = registeredAgent.last_seen_obs_generation;
28608
29532
  const store = getObservationStore();
28609
29533
  const currentGen = store.getGeneration();
28610
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
28611
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29534
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
29535
+ getAllObservations().filter(
29536
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
29537
+ ),
29538
+ getObservationReader()
28612
29539
  ));
28613
29540
  const wm = computeWatermark2(lastSeen, currentGen, projectObs.length);
28614
29541
  if (wm.newObservationCount > 0) {
@@ -28777,7 +29704,7 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28777
29704
  "memorix_transfer",
28778
29705
  {
28779
29706
  title: "Transfer Memories",
28780
- description: 'Export or import project memories. Action "export": export observations and sessions (JSON or Markdown). Action "import": import from a JSON export (re-assigns IDs, skips duplicate topicKeys).',
29707
+ description: 'Export or import project memories. Action "export": export observations visible to the current agent and project sessions (JSON or Markdown). Action "import": import from a JSON export (re-assigns IDs, skips duplicate topicKeys).',
28781
29708
  inputSchema: {
28782
29709
  action: z2.enum(["export", "import"]).describe("Operation: export or import"),
28783
29710
  format: z2.enum(["json", "markdown"]).optional().describe("Export format (for export, default: json)"),
@@ -28788,10 +29715,10 @@ ${summary ? "Summary saved for next session context injection." : "No summary pr
28788
29715
  if (action === "export") {
28789
29716
  const { exportAsJson: exportAsJson2, exportAsMarkdown: exportAsMarkdown2 } = await Promise.resolve().then(() => (init_export_import(), export_import_exports));
28790
29717
  if (format === "markdown") {
28791
- const md = await exportAsMarkdown2(projectDir2, project.id);
29718
+ const md = await exportAsMarkdown2(projectDir2, project.id, getObservationReader());
28792
29719
  return { content: [{ type: "text", text: md }] };
28793
29720
  }
28794
- const data = await exportAsJson2(projectDir2, project.id);
29721
+ const data = await exportAsJson2(projectDir2, project.id, getObservationReader());
28795
29722
  const json = JSON.stringify(data, null, 2);
28796
29723
  return {
28797
29724
  content: [{
@@ -28988,8 +29915,6 @@ ${json}
28988
29915
  }
28989
29916
  }
28990
29917
  );
28991
- let teamStore;
28992
- let initTeamStoreForProject;
28993
29918
  if (teamFeaturesEnabled) {
28994
29919
  const { initTeamStore: initTeamStore2 } = await Promise.resolve().then(() => (init_team_store(), team_store_exports));
28995
29920
  initTeamStoreForProject = initTeamStore2;
@@ -29262,7 +30187,7 @@ ${lines.join("\n")}` }] };
29262
30187
  description: `Send, broadcast, or read messages between agents. Durable: messages survive restarts and reach inactive recipients. Action "send": direct message to one agent. Action "broadcast": message all agents. Action "inbox": read this agent's inbox.`,
29263
30188
  inputSchema: {
29264
30189
  action: z2.enum(["send", "broadcast", "inbox"]).describe("Operation to perform"),
29265
- from: z2.string().optional().describe("Sender agent ID (for send/broadcast)"),
30190
+ from: z2.string().optional().describe("Your sender agent ID. Omit it to use this session identity."),
29266
30191
  to: z2.string().optional().describe("Receiver agent ID (for send)"),
29267
30192
  type: z2.enum(["request", "response", "info", "announcement", "contract", "error", "handoff"]).optional().describe("Message type (for send/broadcast)"),
29268
30193
  content: z2.string().optional().describe("Message content (for send/broadcast)"),
@@ -29273,13 +30198,20 @@ ${lines.join("\n")}` }] };
29273
30198
  }
29274
30199
  },
29275
30200
  async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
30201
+ const requireCurrentTeamAgent = () => {
30202
+ if (!currentAgentId) return null;
30203
+ const agent = teamStore.getAgent(currentAgentId);
30204
+ return agent?.project_id === project.id && agent.status === "active" ? agent : null;
30205
+ };
29276
30206
  if (action === "send") {
29277
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for send" }], isError: true };
30207
+ const sender = requireCurrentTeamAgent();
30208
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for send" }], isError: true };
30209
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29278
30210
  if (!to && !toRole) return { content: [{ type: "text", text: "[ERROR] either to (agent ID) or toRole is required for send" }], isError: true };
29279
30211
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29280
30212
  const msg = teamStore.sendMessage({
29281
30213
  projectId: project.id,
29282
- senderAgentId: from,
30214
+ senderAgentId: currentAgentId,
29283
30215
  recipientAgentId: to ?? null,
29284
30216
  type: msgType,
29285
30217
  content,
@@ -29291,11 +30223,13 @@ ${lines.join("\n")}` }] };
29291
30223
  return { content: [{ type: "text", text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}\u2026${toRole ? ` [role: ${toRole}]` : ""}` }] };
29292
30224
  }
29293
30225
  if (action === "broadcast") {
29294
- if (!from || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] from, type, and content required for broadcast" }], isError: true };
30226
+ const sender = requireCurrentTeamAgent();
30227
+ if (!sender || !msgType || !content) return { content: [{ type: "text", text: "[ERROR] active session identity, type, and content required for broadcast" }], isError: true };
30228
+ if (from && from !== currentAgentId) return { content: [{ type: "text", text: "[ERROR] from must match the current session identity" }], isError: true };
29295
30229
  if (content.length > 1e4) return { content: [{ type: "text", text: "[ERROR] Message too large (max 10KB)" }], isError: true };
29296
30230
  const msg = teamStore.sendMessage({
29297
30231
  projectId: project.id,
29298
- senderAgentId: from,
30232
+ senderAgentId: currentAgentId,
29299
30233
  recipientAgentId: null,
29300
30234
  type: msgType,
29301
30235
  content
@@ -29303,8 +30237,12 @@ ${lines.join("\n")}` }] };
29303
30237
  if ("error" in msg) return { content: [{ type: "text", text: `[ERROR] ${msg.error}` }], isError: true };
29304
30238
  return { content: [{ type: "text", text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}\u2026` }] };
29305
30239
  }
29306
- const inboxId = agentId || from || "";
29307
- if (!inboxId) return { content: [{ type: "text", text: "[ERROR] agentId required for inbox" }], isError: true };
30240
+ const inboxAgent = requireCurrentTeamAgent();
30241
+ if (!inboxAgent) return { content: [{ type: "text", text: "[ERROR] active session identity required for inbox" }], isError: true };
30242
+ if (agentId && agentId !== currentAgentId || from && from !== currentAgentId) {
30243
+ return { content: [{ type: "text", text: "[ERROR] inbox access is limited to the current session identity" }], isError: true };
30244
+ }
30245
+ const inboxId = currentAgentId;
29308
30246
  const inbox = teamStore.getInbox(project.id, inboxId);
29309
30247
  const unread = teamStore.getUnreadCount(project.id, inboxId);
29310
30248
  if (inbox.length === 0) return { content: [{ type: "text", text: "Inbox empty" }] };
@@ -29332,24 +30270,34 @@ ${lines.join("\n")}` }] };
29332
30270
  },
29333
30271
  async ({ agentId, markInboxRead }) => {
29334
30272
  const { computeWatermark: computeWatermark2, computePoll: computePoll2 } = await Promise.resolve().then(() => (init_poll(), poll_exports));
30273
+ if (agentId && agentId !== currentAgentId) {
30274
+ return {
30275
+ content: [{ type: "text", text: "[ERROR] agentId must match the current session identity." }],
30276
+ isError: true
30277
+ };
30278
+ }
30279
+ const effectiveAgentId = currentAgentId;
29335
30280
  let watermark = computeWatermark2(0, 0, 0);
29336
- if (agentId) {
29337
- const agent = teamStore.getAgent(agentId);
30281
+ if (effectiveAgentId) {
30282
+ const agent = teamStore.getAgent(effectiveAgentId);
29338
30283
  if (agent) {
29339
30284
  const lastSeen = agent.last_seen_obs_generation;
29340
30285
  const store = getObservationStore();
29341
30286
  const currentGen = store.getGeneration();
29342
- const projectObs = await withFreshIndex(() => getAllObservations().filter(
29343
- (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30287
+ const projectObs = await withFreshIndex(() => filterReadableObservations(
30288
+ getAllObservations().filter(
30289
+ (o) => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen
30290
+ ),
30291
+ getObservationReader()
29344
30292
  ));
29345
30293
  watermark = computeWatermark2(lastSeen, currentGen, projectObs.length);
29346
- teamStore.updateWatermark(agentId, currentGen);
29347
- teamStore.heartbeat(agentId);
30294
+ teamStore.updateWatermark(effectiveAgentId, currentGen);
30295
+ teamStore.heartbeat(effectiveAgentId);
29348
30296
  }
29349
30297
  }
29350
- const poll = computePoll2(teamStore, project.id, agentId ?? null, watermark);
29351
- if (markInboxRead && agentId) {
29352
- teamStore.markAllRead(project.id, agentId);
30298
+ const poll = computePoll2(teamStore, project.id, effectiveAgentId ?? null, watermark);
30299
+ if (markInboxRead && effectiveAgentId) {
30300
+ teamStore.markAllRead(project.id, effectiveAgentId);
29353
30301
  }
29354
30302
  const lines = [];
29355
30303
  if (poll.agent) {
@@ -29409,7 +30357,7 @@ ${lines.join("\n")}` }] };
29409
30357
  "memorix_handoff",
29410
30358
  {
29411
30359
  title: "Team Handoff \u2014 Agent Context Transfer",
29412
- description: "Create a structured handoff artifact when passing work to another agent. The handoff is stored as a durable observation (searchable, immune to archival) and a notification message is sent to the recipient. Use this when completing a task and another agent should continue, or when you want to leave context for whoever works on this next.",
30360
+ description: "Create a structured handoff artifact when passing work to another agent. A targeted handoff is visible only to its sender and recipient; a broadcast handoff is team-visible. The handoff is stored as a durable observation (immune to archival) and a notification message is sent to the recipient. Use this when completing a task and another agent should continue, or when you want to leave context for whoever works on this next.",
29413
30361
  inputSchema: {
29414
30362
  fromAgentId: z2.string().describe("Your agent ID (from team_manage join or session_start with joinTeam=true)"),
29415
30363
  summary: z2.string().describe("Human-readable summary of what you did and what needs to happen next"),
@@ -29422,6 +30370,34 @@ ${lines.join("\n")}` }] };
29422
30370
  },
29423
30371
  async ({ fromAgentId, summary, context, toAgentId, taskId, filesModified, concepts }) => {
29424
30372
  const { createHandoffArtifact: createHandoffArtifact2 } = await Promise.resolve().then(() => (init_handoff(), handoff_exports));
30373
+ if (!currentAgentId) {
30374
+ return {
30375
+ content: [{ type: "text", text: "Create a coordination identity first: call memorix_session_start with joinTeam=true." }],
30376
+ isError: true
30377
+ };
30378
+ }
30379
+ if (fromAgentId !== currentAgentId) {
30380
+ return {
30381
+ content: [{ type: "text", text: "fromAgentId must match the identity returned for this session. Memorix will not create a handoff on behalf of another agent." }],
30382
+ isError: true
30383
+ };
30384
+ }
30385
+ const sender = teamStore.getAgent(currentAgentId);
30386
+ if (!sender || sender.project_id !== project.id || sender.status !== "active") {
30387
+ return {
30388
+ content: [{ type: "text", text: "The current coordination identity is not an active member of this project." }],
30389
+ isError: true
30390
+ };
30391
+ }
30392
+ if (toAgentId) {
30393
+ const recipient = teamStore.getAgent(toAgentId);
30394
+ if (!recipient || recipient.project_id !== project.id) {
30395
+ return {
30396
+ content: [{ type: "text", text: "The handoff recipient must be an agent registered in the current project." }],
30397
+ isError: true
30398
+ };
30399
+ }
30400
+ }
29425
30401
  const result = await createHandoffArtifact2(
29426
30402
  {
29427
30403
  projectId: project.id,