@theihtisham/agent-shadow-brain 7.1.0 → 7.1.4

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 (294) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +125 -450
  3. package/dist/activity-cli.d.ts +3 -0
  4. package/dist/activity-cli.d.ts.map +1 -0
  5. package/dist/activity-cli.js +78 -0
  6. package/dist/activity-cli.js.map +1 -0
  7. package/dist/adapters/index.d.ts +2 -1
  8. package/dist/adapters/index.d.ts.map +1 -1
  9. package/dist/adapters/index.js +3 -2
  10. package/dist/adapters/index.js.map +1 -1
  11. package/dist/brain/activity-journal.d.ts +82 -0
  12. package/dist/brain/activity-journal.d.ts.map +1 -0
  13. package/dist/brain/activity-journal.js +315 -0
  14. package/dist/brain/activity-journal.js.map +1 -0
  15. package/dist/brain/activity-reporting.d.ts +14 -0
  16. package/dist/brain/activity-reporting.d.ts.map +1 -0
  17. package/dist/brain/activity-reporting.js +17 -0
  18. package/dist/brain/activity-reporting.js.map +1 -0
  19. package/dist/brain/auto-update.js +1 -1
  20. package/dist/brain/brain-portability.js +1 -1
  21. package/dist/brain/client-config.d.ts +19 -0
  22. package/dist/brain/client-config.d.ts.map +1 -0
  23. package/dist/brain/client-config.js +40 -0
  24. package/dist/brain/client-config.js.map +1 -0
  25. package/dist/brain/client-integrations.d.ts +63 -0
  26. package/dist/brain/client-integrations.d.ts.map +1 -0
  27. package/dist/brain/client-integrations.js +79 -0
  28. package/dist/brain/client-integrations.js.map +1 -0
  29. package/dist/brain/context-assembly.d.ts +84 -0
  30. package/dist/brain/context-assembly.d.ts.map +1 -0
  31. package/dist/brain/context-assembly.js +529 -0
  32. package/dist/brain/context-assembly.js.map +1 -0
  33. package/dist/brain/context-request.d.ts +4 -0
  34. package/dist/brain/context-request.d.ts.map +1 -0
  35. package/dist/brain/context-request.js +22 -0
  36. package/dist/brain/context-request.js.map +1 -0
  37. package/dist/brain/context-retrieval.d.ts +88 -0
  38. package/dist/brain/context-retrieval.d.ts.map +1 -0
  39. package/dist/brain/context-retrieval.js +300 -0
  40. package/dist/brain/context-retrieval.js.map +1 -0
  41. package/dist/brain/feature-settings.d.ts +58 -0
  42. package/dist/brain/feature-settings.d.ts.map +1 -0
  43. package/dist/brain/feature-settings.js +242 -0
  44. package/dist/brain/feature-settings.js.map +1 -0
  45. package/dist/brain/file-identity.d.ts +4 -0
  46. package/dist/brain/file-identity.d.ts.map +1 -0
  47. package/dist/brain/file-identity.js +27 -0
  48. package/dist/brain/file-identity.js.map +1 -0
  49. package/dist/brain/global-brain.d.ts +18 -1
  50. package/dist/brain/global-brain.d.ts.map +1 -1
  51. package/dist/brain/global-brain.js +77 -10
  52. package/dist/brain/global-brain.js.map +1 -1
  53. package/dist/brain/mcp-server.d.ts +30 -1
  54. package/dist/brain/mcp-server.d.ts.map +1 -1
  55. package/dist/brain/mcp-server.js +378 -28
  56. package/dist/brain/mcp-server.js.map +1 -1
  57. package/dist/brain/mcp-stdio.d.ts +12 -0
  58. package/dist/brain/mcp-stdio.d.ts.map +1 -0
  59. package/dist/brain/mcp-stdio.js +140 -0
  60. package/dist/brain/mcp-stdio.js.map +1 -0
  61. package/dist/brain/memory-annotations.d.ts +97 -0
  62. package/dist/brain/memory-annotations.d.ts.map +1 -0
  63. package/dist/brain/memory-annotations.js +389 -0
  64. package/dist/brain/memory-annotations.js.map +1 -0
  65. package/dist/brain/memory-graph.d.ts.map +1 -1
  66. package/dist/brain/memory-graph.js +12 -23
  67. package/dist/brain/memory-graph.js.map +1 -1
  68. package/dist/brain/memory-inspection.d.ts +72 -0
  69. package/dist/brain/memory-inspection.d.ts.map +1 -0
  70. package/dist/brain/memory-inspection.js +131 -0
  71. package/dist/brain/memory-inspection.js.map +1 -0
  72. package/dist/brain/memory-mcp-content.d.ts +44 -0
  73. package/dist/brain/memory-mcp-content.d.ts.map +1 -0
  74. package/dist/brain/memory-mcp-content.js +91 -0
  75. package/dist/brain/memory-mcp-content.js.map +1 -0
  76. package/dist/brain/memory-sources.d.ts +8 -0
  77. package/dist/brain/memory-sources.d.ts.map +1 -0
  78. package/dist/brain/memory-sources.js +27 -0
  79. package/dist/brain/memory-sources.js.map +1 -0
  80. package/dist/brain/orchestrator.js +1 -1
  81. package/dist/brain/project-feedback.d.ts +63 -0
  82. package/dist/brain/project-feedback.d.ts.map +1 -0
  83. package/dist/brain/project-feedback.js +289 -0
  84. package/dist/brain/project-feedback.js.map +1 -0
  85. package/dist/brain/project-hub-contract.d.ts +326 -0
  86. package/dist/brain/project-hub-contract.d.ts.map +1 -0
  87. package/dist/brain/project-hub-contract.js +56 -0
  88. package/dist/brain/project-hub-contract.js.map +1 -0
  89. package/dist/brain/project-hub-mcp.d.ts +168 -0
  90. package/dist/brain/project-hub-mcp.d.ts.map +1 -0
  91. package/dist/brain/project-hub-mcp.js +41 -0
  92. package/dist/brain/project-hub-mcp.js.map +1 -0
  93. package/dist/brain/project-hub-pdf-worker.d.ts +13 -0
  94. package/dist/brain/project-hub-pdf-worker.d.ts.map +1 -0
  95. package/dist/brain/project-hub-pdf-worker.js +84 -0
  96. package/dist/brain/project-hub-pdf-worker.js.map +1 -0
  97. package/dist/brain/project-hub-store.d.ts +38 -0
  98. package/dist/brain/project-hub-store.d.ts.map +1 -0
  99. package/dist/brain/project-hub-store.js +189 -0
  100. package/dist/brain/project-hub-store.js.map +1 -0
  101. package/dist/brain/project-hub.d.ts +127 -0
  102. package/dist/brain/project-hub.d.ts.map +1 -0
  103. package/dist/brain/project-hub.js +381 -0
  104. package/dist/brain/project-hub.js.map +1 -0
  105. package/dist/brain/project-kit-inspection.d.ts +28 -0
  106. package/dist/brain/project-kit-inspection.d.ts.map +1 -0
  107. package/dist/brain/project-kit-inspection.js +200 -0
  108. package/dist/brain/project-kit-inspection.js.map +1 -0
  109. package/dist/brain/project-kit-paths.d.ts +13 -0
  110. package/dist/brain/project-kit-paths.d.ts.map +1 -0
  111. package/dist/brain/project-kit-paths.js +141 -0
  112. package/dist/brain/project-kit-paths.js.map +1 -0
  113. package/dist/brain/project-kit.d.ts +41 -0
  114. package/dist/brain/project-kit.d.ts.map +1 -0
  115. package/dist/brain/project-kit.js +255 -0
  116. package/dist/brain/project-kit.js.map +1 -0
  117. package/dist/brain/project-memory.d.ts +38 -0
  118. package/dist/brain/project-memory.d.ts.map +1 -0
  119. package/dist/brain/project-memory.js +76 -0
  120. package/dist/brain/project-memory.js.map +1 -0
  121. package/dist/brain/subconscious.d.ts +21 -24
  122. package/dist/brain/subconscious.d.ts.map +1 -1
  123. package/dist/brain/subconscious.js +82 -218
  124. package/dist/brain/subconscious.js.map +1 -1
  125. package/dist/brain/workflow-skills.d.ts +5 -5
  126. package/dist/brain/workflow-skills.d.ts.map +1 -1
  127. package/dist/brain/workflow-skills.js +3 -2
  128. package/dist/brain/workflow-skills.js.map +1 -1
  129. package/dist/cli.js +40 -4
  130. package/dist/cli.js.map +1 -1
  131. package/dist/context-cli.d.ts +3 -0
  132. package/dist/context-cli.d.ts.map +1 -0
  133. package/dist/context-cli.js +90 -0
  134. package/dist/context-cli.js.map +1 -0
  135. package/dist/curation-cli.d.ts +4 -0
  136. package/dist/curation-cli.d.ts.map +1 -0
  137. package/dist/curation-cli.js +118 -0
  138. package/dist/curation-cli.js.map +1 -0
  139. package/dist/dashboard/brain-hub-actions.d.ts +2 -0
  140. package/dist/dashboard/brain-hub-actions.d.ts.map +1 -0
  141. package/dist/dashboard/brain-hub-actions.js +137 -0
  142. package/dist/dashboard/brain-hub-actions.js.map +1 -0
  143. package/dist/dashboard/brain-hub-client.d.ts +2 -0
  144. package/dist/dashboard/brain-hub-client.d.ts.map +1 -0
  145. package/dist/dashboard/brain-hub-client.js +335 -0
  146. package/dist/dashboard/brain-hub-client.js.map +1 -0
  147. package/dist/dashboard/brain-hub-graph.d.ts +2 -0
  148. package/dist/dashboard/brain-hub-graph.d.ts.map +1 -0
  149. package/dist/dashboard/brain-hub-graph.js +176 -0
  150. package/dist/dashboard/brain-hub-graph.js.map +1 -0
  151. package/dist/dashboard/brain-hub-html.d.ts +2 -0
  152. package/dist/dashboard/brain-hub-html.d.ts.map +1 -0
  153. package/dist/dashboard/brain-hub-html.js +30 -0
  154. package/dist/dashboard/brain-hub-html.js.map +1 -0
  155. package/dist/dashboard/brain-hub-project-views.d.ts +2 -0
  156. package/dist/dashboard/brain-hub-project-views.d.ts.map +1 -0
  157. package/dist/dashboard/brain-hub-project-views.js +79 -0
  158. package/dist/dashboard/brain-hub-project-views.js.map +1 -0
  159. package/dist/dashboard/brain-hub-views.d.ts +2 -0
  160. package/dist/dashboard/brain-hub-views.d.ts.map +1 -0
  161. package/dist/dashboard/brain-hub-views.js +150 -0
  162. package/dist/dashboard/brain-hub-views.js.map +1 -0
  163. package/dist/dashboard/demo-workspace.d.ts +11 -0
  164. package/dist/dashboard/demo-workspace.d.ts.map +1 -0
  165. package/dist/dashboard/demo-workspace.js +178 -0
  166. package/dist/dashboard/demo-workspace.js.map +1 -0
  167. package/dist/dashboard/live-workspace-html.d.ts +2 -0
  168. package/dist/dashboard/live-workspace-html.d.ts.map +1 -0
  169. package/dist/dashboard/live-workspace-html.js +1042 -0
  170. package/dist/dashboard/live-workspace-html.js.map +1 -0
  171. package/dist/dashboard/project-feedback-html.d.ts +5 -0
  172. package/dist/dashboard/project-feedback-html.d.ts.map +1 -0
  173. package/dist/dashboard/project-feedback-html.js +216 -0
  174. package/dist/dashboard/project-feedback-html.js.map +1 -0
  175. package/dist/dashboard/project-hub-api.d.ts +5 -0
  176. package/dist/dashboard/project-hub-api.d.ts.map +1 -0
  177. package/dist/dashboard/project-hub-api.js +121 -0
  178. package/dist/dashboard/project-hub-api.js.map +1 -0
  179. package/dist/dashboard/project-kit-html.d.ts +4 -0
  180. package/dist/dashboard/project-kit-html.d.ts.map +1 -0
  181. package/dist/dashboard/project-kit-html.js +359 -0
  182. package/dist/dashboard/project-kit-html.js.map +1 -0
  183. package/dist/dashboard/project-workspace.d.ts +16 -0
  184. package/dist/dashboard/project-workspace.d.ts.map +1 -0
  185. package/dist/dashboard/project-workspace.js +106 -0
  186. package/dist/dashboard/project-workspace.js.map +1 -0
  187. package/dist/dashboard/server.d.ts +2 -0
  188. package/dist/dashboard/server.d.ts.map +1 -1
  189. package/dist/dashboard/server.js +65 -19
  190. package/dist/dashboard/server.js.map +1 -1
  191. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  192. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  193. package/dist/dashboard/v6-dashboard-html.js +34 -56
  194. package/dist/dashboard/v6-dashboard-html.js.map +1 -1
  195. package/dist/dashboard/workspace-api.d.ts +22 -0
  196. package/dist/dashboard/workspace-api.d.ts.map +1 -0
  197. package/dist/dashboard/workspace-api.js +429 -0
  198. package/dist/dashboard/workspace-api.js.map +1 -0
  199. package/dist/dashboard/workspace-html.d.ts +2 -0
  200. package/dist/dashboard/workspace-html.d.ts.map +1 -0
  201. package/dist/dashboard/workspace-html.js +2538 -0
  202. package/dist/dashboard/workspace-html.js.map +1 -0
  203. package/dist/dashboard/workspace-status.d.ts +50 -0
  204. package/dist/dashboard/workspace-status.d.ts.map +1 -0
  205. package/dist/dashboard/workspace-status.js +22 -0
  206. package/dist/dashboard/workspace-status.js.map +1 -0
  207. package/dist/index.d.ts +20 -0
  208. package/dist/index.d.ts.map +1 -1
  209. package/dist/index.js +15 -0
  210. package/dist/index.js.map +1 -1
  211. package/dist/integration-cli.d.ts +3 -0
  212. package/dist/integration-cli.d.ts.map +1 -0
  213. package/dist/integration-cli.js +16 -0
  214. package/dist/integration-cli.js.map +1 -0
  215. package/dist/project-cli.d.ts +3 -0
  216. package/dist/project-cli.d.ts.map +1 -0
  217. package/dist/project-cli.js +100 -0
  218. package/dist/project-cli.js.map +1 -0
  219. package/dist/types.d.ts +12 -3
  220. package/dist/types.d.ts.map +1 -1
  221. package/docs/COMPARISON.md +22 -0
  222. package/docs/COMPONENTS.md +66 -0
  223. package/docs/CONTEXT_LAB.md +79 -0
  224. package/docs/FEATURES_AND_LIMITS.md +24 -0
  225. package/docs/INTEGRATIONS.md +68 -0
  226. package/docs/LEARNING.md +46 -0
  227. package/docs/PROJECT_KIT.md +93 -0
  228. package/docs/QUICKSTART.md +44 -0
  229. package/docs/README.md +33 -0
  230. package/docs/ROADMAP-v7.md +147 -0
  231. package/docs/SHARING.md +13 -0
  232. package/docs/SHOWCASE.md +87 -0
  233. package/docs/STATS.md +21 -0
  234. package/docs/TROUBLESHOOTING.md +42 -0
  235. package/docs/UPGRADE_PLAN_v7.0.0.md +203 -0
  236. package/docs/VIRAL-PLAYBOOK.md +259 -0
  237. package/docs/VISUAL_GUIDE_v7.1.4.md +70 -0
  238. package/docs/WORKFLOW_INTEGRATION.md +19 -1
  239. package/docs/WORKSPACE.md +37 -0
  240. package/docs/assets/7.1.4-collaboration-design-preview.png +0 -0
  241. package/docs/assets/7.1.4-comparison.png +0 -0
  242. package/docs/assets/7.1.4-context-lab.png +0 -0
  243. package/docs/assets/7.1.4-control-room-dense-preview.png +0 -0
  244. package/docs/assets/7.1.4-control-room-focus-preview.png +0 -0
  245. package/docs/assets/7.1.4-features.png +0 -0
  246. package/docs/assets/7.1.4-graph.png +0 -0
  247. package/docs/assets/7.1.4-hub-control-room.png +0 -0
  248. package/docs/assets/7.1.4-hub-empty.png +0 -0
  249. package/docs/assets/7.1.4-hub-knowledge.png +0 -0
  250. package/docs/assets/7.1.4-hub-learning.png +0 -0
  251. package/docs/assets/7.1.4-hub-mobile.png +0 -0
  252. package/docs/assets/7.1.4-hub-public-docs-source.png +0 -0
  253. package/docs/assets/7.1.4-hub-public-docs.gif +0 -0
  254. package/docs/assets/7.1.4-hub-public-docs.png +0 -0
  255. package/docs/assets/7.1.4-hub-setup.png +0 -0
  256. package/docs/assets/7.1.4-hub-walkthrough.gif +0 -0
  257. package/docs/assets/7.1.4-integrations.png +0 -0
  258. package/docs/assets/7.1.4-knowledge-design-preview.png +0 -0
  259. package/docs/assets/7.1.4-learning-design-preview.png +0 -0
  260. package/docs/assets/7.1.4-learning.png +0 -0
  261. package/docs/assets/7.1.4-live.png +0 -0
  262. package/docs/assets/7.1.4-memory-review.png +0 -0
  263. package/docs/assets/7.1.4-mobile.png +0 -0
  264. package/docs/assets/7.1.4-models-design-preview.png +0 -0
  265. package/docs/assets/7.1.4-outcome-ranking.png +0 -0
  266. package/docs/assets/7.1.4-overview.png +0 -0
  267. package/docs/assets/7.1.4-project-kit.png +0 -0
  268. package/docs/assets/7.1.4-project-skill-preview.png +0 -0
  269. package/docs/assets/7.1.4-replay.png +0 -0
  270. package/docs/assets/7.1.4-setup-design-preview.png +0 -0
  271. package/docs/assets/7.1.4-usage-design-preview.png +0 -0
  272. package/docs/assets/context-flow-v7.2.png +0 -0
  273. package/docs/assets/context-v7.1.3.png +0 -0
  274. package/docs/assets/features-v7.1.3.png +0 -0
  275. package/docs/assets/graph-v7.1.3.png +0 -0
  276. package/docs/assets/integrations-v7.1.3.png +0 -0
  277. package/docs/assets/live-v7.1.3.png +0 -0
  278. package/docs/assets/live-v7.1.4.gif +0 -0
  279. package/docs/assets/memory-detail-v7.1.3.png +0 -0
  280. package/docs/assets/overview-v7.1.3.png +0 -0
  281. package/docs/assets/workspace-graph-v7.2.png +0 -0
  282. package/docs/assets/workspace-overview-v7.2.png +0 -0
  283. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.json +1818 -0
  284. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.md +36 -0
  285. package/docs/launch/COMPONENT_MAP_v7.1.4.md +307 -0
  286. package/docs/launch/PROJECT_KIT_PLAN_v7.1.4.md +110 -0
  287. package/docs/launch/PROJECT_LEARNING_PLAN_v7.1.4.md +63 -0
  288. package/docs/launch/RELEASE_NOTES_v7.1.3.md +40 -0
  289. package/docs/launch/RELEASE_NOTES_v7.1.4.md +30 -0
  290. package/docs/launch/RELEASE_NOTES_v7.2.0.md +24 -0
  291. package/docs/launch/RESEARCH_AND_PLAN_v7.1.4.md +255 -0
  292. package/package.json +43 -126
  293. package/skills/shadow-research/SKILL.md +2 -0
  294. package/skills/shadow-workflow/SKILL.md +6 -0
@@ -0,0 +1,242 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { setTimeout as delay } from 'node:timers/promises';
6
+ import { matchesFileIdentity } from './file-identity.js';
7
+ export const FEATURE_DEFINITIONS = [
8
+ {
9
+ id: 'contextAssembly',
10
+ label: 'Context packs',
11
+ description: 'Assemble project memories into a bounded, explained context pack.',
12
+ defaultEnabled: true,
13
+ },
14
+ {
15
+ id: 'crossProjectContext',
16
+ label: 'Cross-project context',
17
+ description: 'Allow context requests to include memories from other projects when explicitly selected.',
18
+ defaultEnabled: false,
19
+ },
20
+ {
21
+ id: 'automaticBriefing',
22
+ label: 'Session briefings',
23
+ description: 'Generate the automatic project briefing at session start.',
24
+ defaultEnabled: true,
25
+ },
26
+ {
27
+ id: 'liveActivity',
28
+ label: 'Live dashboard activity',
29
+ description: 'Show activity updates and the recent activity feed in the dashboard.',
30
+ defaultEnabled: true,
31
+ },
32
+ {
33
+ id: 'activityRecording',
34
+ label: 'Record agent activity',
35
+ description: 'Record bounded tool timing and explicitly reported session phases. Arguments, memory text, and credentials are not captured.',
36
+ defaultEnabled: true,
37
+ },
38
+ ];
39
+ export const DEFAULT_FEATURE_FLAGS = Object.freeze({
40
+ contextAssembly: true,
41
+ crossProjectContext: false,
42
+ automaticBriefing: true,
43
+ liveActivity: true,
44
+ activityRecording: true,
45
+ });
46
+ export const FEATURE_SETTINGS_FILENAME = 'feature-settings.json';
47
+ const FEATURE_IDS = new Set(FEATURE_DEFINITIONS.map(({ id }) => id));
48
+ const ORIGINAL_FEATURE_IDS = ['contextAssembly', 'crossProjectContext', 'automaticBriefing', 'liveActivity'];
49
+ const LOCK_TIMEOUT_MS = 3000;
50
+ const LOCK_POLL_MS = 20;
51
+ export class FeatureSettingsError extends Error {
52
+ constructor(code, message) {
53
+ super(message);
54
+ this.code = code;
55
+ this.name = 'FeatureSettingsError';
56
+ }
57
+ }
58
+ function errorCode(error) {
59
+ return error?.code;
60
+ }
61
+ function isRecord(value) {
62
+ if (!value || typeof value !== 'object')
63
+ return false;
64
+ const prototype = Object.getPrototypeOf(value);
65
+ return prototype === Object.prototype || prototype === null;
66
+ }
67
+ function isFeatureMap(value) {
68
+ return isRecord(value) && Object.entries(value).every(([key, enabled]) => FEATURE_IDS.has(key) && typeof enabled === 'boolean');
69
+ }
70
+ function defaultSnapshot() {
71
+ return { schemaVersion: 1, revision: 0, updatedAt: null, features: { ...DEFAULT_FEATURE_FLAGS } };
72
+ }
73
+ function parseSnapshot(raw) {
74
+ try {
75
+ const data = JSON.parse(raw);
76
+ if (isRecord(data) && data.schemaVersion === 1 && typeof data.revision === 'number' &&
77
+ Number.isSafeInteger(data.revision) && data.revision >= 1 &&
78
+ typeof data.updatedAt === 'string' && Number.isFinite(Date.parse(data.updatedAt)) &&
79
+ isFeatureMap(data.features) && ORIGINAL_FEATURE_IDS.every(id => Object.hasOwn(data.features, id)) &&
80
+ [FEATURE_IDS.size - 1, FEATURE_IDS.size].includes(Object.keys(data.features).length)) {
81
+ return {
82
+ schemaVersion: 1, revision: data.revision, updatedAt: data.updatedAt,
83
+ features: { ...DEFAULT_FEATURE_FLAGS, ...data.features },
84
+ };
85
+ }
86
+ }
87
+ catch { /* Use a safe diagnostic without stored contents. */ }
88
+ throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_STORE', 'Feature settings are malformed or use an unsupported schema.');
89
+ }
90
+ /** Read and patch the local feature settings file without accessing provider configuration. */
91
+ export class FeatureSettingsStore {
92
+ constructor(options = {}) {
93
+ this.filePath = path.resolve(options.filePath ?? path.join(os.homedir(), '.shadow-brain', FEATURE_SETTINGS_FILENAME));
94
+ this.lockTimeoutMs = typeof options.lockTimeoutMs === 'number' && Number.isFinite(options.lockTimeoutMs)
95
+ ? Math.max(0, Math.min(options.lockTimeoutMs, 60000))
96
+ : LOCK_TIMEOUT_MS;
97
+ }
98
+ get() {
99
+ let raw;
100
+ try {
101
+ const info = fs.lstatSync(this.filePath, { throwIfNoEntry: false });
102
+ if (!info)
103
+ return defaultSnapshot();
104
+ if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
105
+ throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_STORE', 'Feature settings must be a regular local file.');
106
+ }
107
+ raw = fs.readFileSync(this.filePath, 'utf8');
108
+ }
109
+ catch (error) {
110
+ if (error instanceof FeatureSettingsError)
111
+ throw error;
112
+ throw new FeatureSettingsError('FEATURE_SETTINGS_READ_FAILED', 'Could not read feature settings.');
113
+ }
114
+ return parseSnapshot(raw);
115
+ }
116
+ isEnabled(id) {
117
+ if (!FEATURE_IDS.has(id)) {
118
+ throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_PATCH', 'Unknown feature setting.');
119
+ }
120
+ return this.get().features[id];
121
+ }
122
+ async update(patch, expectedRevision) {
123
+ if (!isFeatureMap(patch) || (expectedRevision !== undefined &&
124
+ (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0))) {
125
+ throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_PATCH', 'Feature settings accept only known feature IDs and boolean values.');
126
+ }
127
+ const changes = { ...patch };
128
+ if (!Object.keys(changes).length)
129
+ return this.checkRevision(this.get(), expectedRevision);
130
+ try {
131
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
132
+ }
133
+ catch {
134
+ throw new FeatureSettingsError('FEATURE_SETTINGS_WRITE_FAILED', 'Could not open the feature settings directory.');
135
+ }
136
+ const release = await this.acquireLock();
137
+ try {
138
+ const current = this.checkRevision(this.get(), expectedRevision);
139
+ if (Object.keys(changes).every(id => changes[id] === current.features[id]))
140
+ return current;
141
+ if (current.revision === Number.MAX_SAFE_INTEGER) {
142
+ throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_STORE', 'Feature settings revision has reached its limit.');
143
+ }
144
+ const next = {
145
+ schemaVersion: 1,
146
+ revision: current.revision + 1,
147
+ updatedAt: new Date().toISOString(),
148
+ features: { ...current.features, ...changes },
149
+ };
150
+ this.replace(next);
151
+ return next;
152
+ }
153
+ finally {
154
+ release();
155
+ }
156
+ }
157
+ checkRevision(snapshot, expectedRevision) {
158
+ if (expectedRevision !== undefined && expectedRevision !== snapshot.revision) {
159
+ throw new FeatureSettingsError('FEATURE_SETTINGS_CONFLICT', 'Feature settings changed. Refresh them and retry the update.');
160
+ }
161
+ return snapshot;
162
+ }
163
+ async acquireLock() {
164
+ const lock = this.filePath + '.lock';
165
+ const deadline = performance.now() + this.lockTimeoutMs;
166
+ for (;;) {
167
+ let fd;
168
+ try {
169
+ fd = fs.openSync(lock, 'wx', 0o600);
170
+ }
171
+ catch (error) {
172
+ if (errorCode(error) !== 'EEXIST') {
173
+ throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_FAILED', 'Could not acquire the feature settings lock.');
174
+ }
175
+ const remaining = deadline - performance.now();
176
+ if (remaining <= 0)
177
+ break;
178
+ await delay(Math.min(remaining, LOCK_POLL_MS));
179
+ if (performance.now() >= deadline)
180
+ break;
181
+ continue;
182
+ }
183
+ let owner;
184
+ try {
185
+ owner = fs.fstatSync(fd, { bigint: true });
186
+ }
187
+ catch {
188
+ try {
189
+ fs.closeSync(fd);
190
+ }
191
+ catch { /* Preserve the original lock error. */ }
192
+ throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_FAILED', 'Could not identify the feature settings lock.');
193
+ }
194
+ return () => {
195
+ try {
196
+ fs.closeSync(fd);
197
+ const current = fs.lstatSync(lock, { bigint: true, throwIfNoEntry: false });
198
+ if (!matchesFileIdentity(lock, owner, current)) {
199
+ throw new Error('Lock ownership changed.');
200
+ }
201
+ fs.unlinkSync(lock);
202
+ }
203
+ catch {
204
+ throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_FAILED', 'Could not release the feature settings lock.');
205
+ }
206
+ };
207
+ }
208
+ throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_TIMEOUT', 'Timed out waiting for the feature settings lock.');
209
+ }
210
+ replace(snapshot) {
211
+ const temporary = this.filePath + '.' + randomUUID() + '.tmp';
212
+ let created = false;
213
+ try {
214
+ const fd = fs.openSync(temporary, 'wx', 0o600);
215
+ created = true;
216
+ try {
217
+ fs.writeFileSync(fd, JSON.stringify(snapshot, null, 2) + '\n');
218
+ fs.fsyncSync(fd);
219
+ }
220
+ finally {
221
+ fs.closeSync(fd);
222
+ }
223
+ fs.renameSync(temporary, this.filePath);
224
+ created = false;
225
+ }
226
+ catch {
227
+ throw new FeatureSettingsError('FEATURE_SETTINGS_WRITE_FAILED', 'Could not save feature settings.');
228
+ }
229
+ finally {
230
+ if (created) {
231
+ try {
232
+ fs.unlinkSync(temporary);
233
+ }
234
+ catch { /* Preserve the original write error. */ }
235
+ }
236
+ }
237
+ }
238
+ }
239
+ export function getFeatureSettings() {
240
+ return new FeatureSettingsStore();
241
+ }
242
+ //# sourceMappingURL=feature-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-settings.js","sourceRoot":"","sources":["../../src/brain/feature-settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC;QACE,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,mEAAmE;QAChF,cAAc,EAAE,IAAI;KACrB;IACD;QACE,EAAE,EAAE,qBAAqB;QACzB,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EAAE,0FAA0F;QACvG,cAAc,EAAE,KAAK;KACtB;IACD;QACE,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,2DAA2D;QACxE,cAAc,EAAE,IAAI;KACrB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,sEAAsE;QACnF,cAAc,EAAE,IAAI;KACrB;IACD;QACE,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EAAE,8HAA8H;QAC3I,cAAc,EAAE,IAAI;KACrB;CACO,CAAC;AAKX,MAAM,CAAC,MAAM,qBAAqB,GAA2B,MAAM,CAAC,MAAM,CAAC;IACzE,eAAe,EAAE,IAAI;IACrB,mBAAmB,EAAE,KAAK;IAC1B,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,iBAAiB,EAAE,IAAI;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAcjE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAS,mBAAmB,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7E,MAAM,oBAAoB,GAAG,CAAC,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,cAAc,CAAC,CAAC;AAC7G,MAAM,eAAe,GAAG,IAAI,CAAC;AAC7B,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YACW,IAE2F,EACpG,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QALN,SAAI,GAAJ,IAAI,CAEuF;QAIpG,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAQ,KAA2C,EAAE,IAAI,CAAC;AAC5D,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC;AAClI,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,GAAG,qBAAqB,EAAE,EAAE,CAAC;AACpG,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;YAC/E,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC;YACzD,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACjF,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAkB,EAAE,EAAE,CAAC,CAAC;YAC3G,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACzF,OAAO;gBACL,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS;gBACpE,QAAQ,EAAE,EAAE,GAAG,qBAAqB,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE;aACzD,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,oDAAoD,CAAC,CAAC;IAChE,MAAM,IAAI,oBAAoB,CAAC,gCAAgC,EAAE,8DAA8D,CAAC,CAAC;AACnI,CAAC;AAED,+FAA+F;AAC/F,MAAM,OAAO,oBAAoB;IAI/B,YAAY,UAAkC,EAAE;QAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,yBAAyB,CAAC,CAAC,CAAC;QACtH,IAAI,CAAC,aAAa,GAAG,OAAO,OAAO,CAAC,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC;YACtG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,KAAM,CAAC,CAAC;YACtD,CAAC,CAAC,eAAe,CAAC;IACtB,CAAC;IAED,GAAG;QACD,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,IAAI,CAAC,IAAI;gBAAE,OAAO,eAAe,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChE,MAAM,IAAI,oBAAoB,CAAC,gCAAgC,EAAE,gDAAgD,CAAC,CAAC;YACrH,CAAC;YACD,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,oBAAoB;gBAAE,MAAM,KAAK,CAAC;YACvD,MAAM,IAAI,oBAAoB,CAAC,8BAA8B,EAAE,kCAAkC,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,SAAS,CAAC,EAAa;QACrB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,oBAAoB,CAAC,gCAAgC,EAAE,0BAA0B,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAc,EAAE,gBAAyB;QACpD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,KAAK,SAAS;YACvD,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,oBAAoB,CAAC,gCAAgC,EAAE,oEAAoE,CAAC,CAAC;QACzI,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC1F,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,oBAAoB,CAAC,+BAA+B,EAAE,gDAAgD,CAAC,CAAC;QACpH,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC;YACjE,IAAK,MAAM,CAAC,IAAI,CAAC,OAAO,CAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;gBAAE,OAAO,OAAO,CAAC;YAC5G,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACjD,MAAM,IAAI,oBAAoB,CAAC,gCAAgC,EAAE,kDAAkD,CAAC,CAAC;YACvH,CAAC;YACD,MAAM,IAAI,GAA4B;gBACpC,aAAa,EAAE,CAAC;gBAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ,GAAG,CAAC;gBAC9B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,OAAO,EAAE;aAC9C,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,QAAiC,EAAE,gBAAyB;QAChF,IAAI,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,KAAK,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC7E,MAAM,IAAI,oBAAoB,CAAC,2BAA2B,EAAE,8DAA8D,CAAC,CAAC;QAC9H,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,KAAK,CAAC,WAAW;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACrC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC;QACxD,SAAS,CAAC;YACR,IAAI,EAAU,CAAC;YACf,IAAI,CAAC;gBACH,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACtC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;oBAClC,MAAM,IAAI,oBAAoB,CAAC,8BAA8B,EAAE,8CAA8C,CAAC,CAAC;gBACjH,CAAC;gBACD,MAAM,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;gBAC/C,IAAI,SAAS,IAAI,CAAC;oBAAE,MAAM;gBAC1B,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;gBAC/C,IAAI,WAAW,CAAC,GAAG,EAAE,IAAI,QAAQ;oBAAE,MAAM;gBACzC,SAAS;YACX,CAAC;YACD,IAAI,KAAqB,CAAC;YAC1B,IAAI,CAAC;gBACH,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC;oBAAC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,uCAAuC,CAAC,CAAC;gBAC3E,MAAM,IAAI,oBAAoB,CAAC,8BAA8B,EAAE,+CAA+C,CAAC,CAAC;YAClH,CAAC;YACD,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC;oBACH,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;oBACjB,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;oBAC5E,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;wBAC/C,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;oBAC7C,CAAC;oBACD,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBACtB,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,IAAI,oBAAoB,CAAC,8BAA8B,EAAE,8CAA8C,CAAC,CAAC;gBACjH,CAAC;YACH,CAAC,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,oBAAoB,CAAC,+BAA+B,EAAE,kDAAkD,CAAC,CAAC;IACtH,CAAC;IAEO,OAAO,CAAC,QAAiC;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,GAAG,GAAG,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC;QAC9D,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC/C,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,CAAC;gBACH,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;gBAC/D,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC;oBAAS,CAAC;gBACT,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC;YACD,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,oBAAoB,CAAC,+BAA+B,EAAE,kCAAkC,CAAC,CAAC;QACtG,CAAC;gBAAS,CAAC;YACT,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC;oBAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,wCAAwC,CAAC,CAAC;YACtF,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,IAAI,oBAAoB,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import * as fs from 'node:fs';
2
+ /** Match a regular opened file to its current project or lock path. */
3
+ export declare function matchesFileIdentity(filename: string, handle: fs.BigIntStats, named: fs.BigIntStats | undefined): boolean;
4
+ //# sourceMappingURL=file-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-identity.d.ts","sourceRoot":"","sources":["../../src/brain/file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,GAAG,SAAS,GAAG,OAAO,CAiBxH"}
@@ -0,0 +1,27 @@
1
+ import * as fs from 'node:fs';
2
+ /** Match a regular opened file to its current project or lock path. */
3
+ export function matchesFileIdentity(filename, handle, named) {
4
+ if (!named?.isFile() || !handle.isFile() || handle.nlink !== 1n || named.nlink !== 1n || handle.ino === 0n || named.ino !== handle.ino)
5
+ return false;
6
+ if (named.dev === handle.dev)
7
+ return true;
8
+ // Some Windows Node releases report device zero for path stats, but expose the volume through an open handle.
9
+ if (process.platform !== 'win32' || named.dev !== 0n || handle.dev === 0n)
10
+ return false;
11
+ let fd;
12
+ try {
13
+ fd = fs.openSync(filename, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
14
+ const reopened = fs.fstatSync(fd, { bigint: true });
15
+ const current = fs.lstatSync(filename, { bigint: true, throwIfNoEntry: false });
16
+ return reopened.isFile() && reopened.nlink === 1n && reopened.dev === handle.dev && reopened.ino === handle.ino &&
17
+ current?.isFile() === true && current.nlink === 1n && current.dev === named.dev && current.ino === named.ino;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ finally {
23
+ if (fd !== undefined)
24
+ fs.closeSync(fd);
25
+ }
26
+ }
27
+ //# sourceMappingURL=file-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-identity.js","sourceRoot":"","sources":["../../src/brain/file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,uEAAuE;AACvE,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,MAAsB,EAAE,KAAiC;IAC7G,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACrJ,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1C,8GAA8G;IAC9G,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACxF,IAAI,EAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;QACnF,MAAM,QAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;QAChF,OAAO,QAAQ,CAAC,MAAM,EAAE,IAAI,QAAQ,CAAC,KAAK,KAAK,EAAE,IAAI,QAAQ,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;YAC7G,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC;IACjH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,SAAS;YAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;AACH,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { AgentTool, BrainTimelineEvent, GlobalBrainConfig, GlobalBrainStats, GlobalBrainStorageHealth, GlobalEntry, ScoredGlobalEntry } from '../types.js';
2
+ import { type ContextSearchMemory } from './context-retrieval.js';
2
3
  type EntryInput = Omit<GlobalEntry, 'id' | 'createdAt' | 'lastAccessed' | 'accessCount'>;
3
4
  interface RecallOptions {
4
5
  projectId?: string;
@@ -7,7 +8,10 @@ interface RecallOptions {
7
8
  keywords?: string[];
8
9
  limit?: number;
9
10
  minImportance?: number;
11
+ /** Exclude curated memories before matching, corroboration, and candidate limits. */
12
+ excludeIds?: ReadonlySet<string>;
10
13
  }
14
+ export declare function normalizeRecallKeywords(keywords: unknown): string[];
11
15
  /** Local JSON store shared by cooperating Global Brain processes. */
12
16
  export declare class GlobalBrain {
13
17
  private static instance;
@@ -24,6 +28,10 @@ export declare class GlobalBrain {
24
28
  private lastError;
25
29
  private constructor();
26
30
  static getInstance(config?: Partial<GlobalBrainConfig>): GlobalBrain;
31
+ /** Explicit independent store. Does not replace or consult the global singleton. */
32
+ static createIsolated(config: Partial<GlobalBrainConfig> & {
33
+ dbPath: string;
34
+ }): GlobalBrain;
27
35
  init(): Promise<void>;
28
36
  private initialize;
29
37
  /** Generate a stable project ID from absolute path. */
@@ -33,9 +41,10 @@ export declare class GlobalBrain {
33
41
  /** Queue a write. A failed attempt resolves false and retains the entry for a later sync. */
34
42
  write(entry: EntryInput): Promise<boolean>;
35
43
  /** Stage an entry for later sync and return its ID immediately. */
36
- writeSync(entry: EntryInput): string;
44
+ writeSync(entry: EntryInput, id?: string): string;
37
45
  private requireWritable;
38
46
  private prepareEntry;
47
+ private requireSameExplicitEntry;
39
48
  private publishWrite;
40
49
  /** Recall matching entries across projects and agents. */
41
50
  recall(opts?: RecallOptions): GlobalEntry[];
@@ -43,6 +52,8 @@ export declare class GlobalBrain {
43
52
  recallScored(opts?: RecallOptions & {
44
53
  minConfidence?: number;
45
54
  }): ScoredGlobalEntry[];
55
+ /** Read the scoped fields used by context ranking without exposing mutable memory records. */
56
+ contextEntries(opts: Pick<RecallOptions, 'projectId' | 'minImportance' | 'excludeIds'>): IterableIterator<ContextSearchMemory>;
46
57
  private findMatches;
47
58
  private compareEntries;
48
59
  private recordRecall;
@@ -50,6 +61,12 @@ export declare class GlobalBrain {
50
61
  /** Bump an entry's access count without replacing its persisted snapshot. */
51
62
  touch(id: string): void;
52
63
  recallByIds(ids: string[]): GlobalEntry[];
64
+ /** Read one memory without exposing mutable in-memory state or recording a recall. */
65
+ getEntry(id: string): GlobalEntry | undefined;
66
+ /** Hash the actual read view, including staged entries, without committing or recording access. */
67
+ readFingerprint(projectId?: string): string;
68
+ /** Refresh the local read view without acknowledging or committing pending operations. */
69
+ refresh(): void;
53
70
  /** Chronological memory stream for dashboards and handoffs. */
54
71
  timeline(opts?: Pick<RecallOptions, 'projectId' | 'agentTool' | 'category' | 'limit'>): BrainTimelineEvent[];
55
72
  getStats(): GlobalBrainStats;
@@ -1 +1 @@
1
- {"version":3,"file":"global-brain.d.ts","sourceRoot":"","sources":["../../src/brain/global-brain.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"global-brain.d.ts","sourceRoot":"","sources":["../../src/brain/global-brain.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EAEX,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAsBvF,KAAK,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC,CAAC;AAiDzF,UAAU,aAAa;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClC;AAqFD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CASnE;AAkBD,qEAAqE;AACrE,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0B;IAC5D,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,SAAS,CAA+C;IAEhE,OAAO;IAkBP,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,WAAW;IAKpE,oFAAoF;IACpF,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW;IAKrF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,OAAO,CAAC,UAAU;IAoBlB,uDAAuD;IACvD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAK5C,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM;IAYvD,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAMrC,6FAA6F;IACvF,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhD,mEAAmE;IACnE,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAgBjD,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,wBAAwB;IAShC,OAAO,CAAC,YAAY;IAkBpB,0DAA0D;IAC1D,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,WAAW,EAAE;IAW/C,qFAAqF;IACrF,YAAY,CAAC,IAAI,GAAE,aAAa,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,iBAAiB,EAAE;IA8BxF,8FAA8F;IAC7F,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,eAAe,GAAG,YAAY,CAAC,GAAG,gBAAgB,CAAC,mBAAmB,CAAC;IAU/H,OAAO,CAAC,WAAW;IAiCnB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,iBAAiB;IAsBzB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAUvB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE;IASzC,sFAAsF;IACtF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAK7C,mGAAmG;IACnG,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAU3C,0FAA0F;IAC1F,OAAO,IAAI,IAAI;IAKf,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,GAAE,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAM,GAAG,kBAAkB,EAAE;IAahH,QAAQ,IAAI,gBAAgB;IAW5B,OAAO,CAAC,iBAAiB;IAIzB,gDAAgD;IAChD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,gBAAgB,IAAI,wBAAwB;IAW5C,iFAAiF;IAC3E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAmBb,aAAa;IA8C3B,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,eAAe;IAqDvB,OAAO,CAAC,iBAAiB;YAgBX,WAAW;IAmDzB,OAAO,CAAC,eAAe;IAyBjB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAchC;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,WAAW,CAE/E"}
@@ -2,8 +2,11 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import * as os from 'os';
4
4
  import * as crypto from 'crypto';
5
+ import { isDeepStrictEqual } from 'node:util';
5
6
  import { setTimeout as delay } from 'node:timers/promises';
6
7
  import { getBrainBus } from './brain-bus.js';
8
+ import { contextSearchMemory } from './context-retrieval.js';
9
+ import { matchesFileIdentity } from './file-identity.js';
7
10
  const DEFAULT_DB_PATH = path.join(os.homedir(), '.shadow-brain', 'global.json');
8
11
  const LOCK_TIMEOUT_MS = 5000;
9
12
  const LOCK_POLL_MS = 20;
@@ -89,7 +92,7 @@ function hydrateEntry(entry) {
89
92
  function normalizeText(value) {
90
93
  return value.trim().replace(/\s+/g, ' ').toLowerCase();
91
94
  }
92
- function normalizeKeywords(keywords) {
95
+ export function normalizeRecallKeywords(keywords) {
93
96
  if (!Array.isArray(keywords))
94
97
  return [];
95
98
  const unique = new Set();
@@ -148,6 +151,12 @@ export class GlobalBrain {
148
151
  GlobalBrain.instance ?? (GlobalBrain.instance = new GlobalBrain(config));
149
152
  return GlobalBrain.instance;
150
153
  }
154
+ /** Explicit independent store. Does not replace or consult the global singleton. */
155
+ static createIsolated(config) {
156
+ if (typeof config.dbPath !== 'string' || !config.dbPath.trim())
157
+ throw new TypeError('An explicit memory-store path is required.');
158
+ return new GlobalBrain(config);
159
+ }
151
160
  async init() {
152
161
  this.initialize();
153
162
  }
@@ -211,9 +220,17 @@ export class GlobalBrain {
211
220
  });
212
221
  }
213
222
  /** Stage an entry for later sync and return its ID immediately. */
214
- writeSync(entry) {
223
+ writeSync(entry, id) {
215
224
  this.requireWritable();
216
- const stored = this.prepareEntry(entry);
225
+ if (id !== undefined && (typeof id !== 'string' || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/u.test(id))) {
226
+ throw new TypeError('An explicit memory ID must be a UUID.');
227
+ }
228
+ const stored = this.prepareEntry(entry, id);
229
+ const existing = this.entries.get(stored.id);
230
+ if (existing) {
231
+ this.requireSameExplicitEntry(existing, stored);
232
+ return stored.id;
233
+ }
217
234
  this.pendingOperations.push({ kind: 'add', entry: stored, queued: false });
218
235
  this.entries.set(stored.id, hydrateEntry(structuredClone(stored)));
219
236
  return stored.id;
@@ -224,12 +241,12 @@ export class GlobalBrain {
224
241
  if (this.shuttingDown)
225
242
  throw new Error('GlobalBrain is shutting down');
226
243
  }
227
- prepareEntry(entry) {
244
+ prepareEntry(entry, id = crypto.randomUUID()) {
228
245
  const now = new Date();
229
246
  // Snapshot caller input now so later mutations or serialization errors cannot block a commit.
230
247
  try {
231
248
  const data = JSON.parse(JSON.stringify({
232
- ...entry, id: crypto.randomUUID(), createdAt: now, lastAccessed: now, accessCount: 0,
249
+ ...entry, id, createdAt: now, lastAccessed: now, accessCount: 0,
233
250
  }));
234
251
  if (hasUniqueKeys([data], 'id', ['projectId', 'projectName', 'agentTool', 'category', 'content'])) {
235
252
  return data;
@@ -238,6 +255,14 @@ export class GlobalBrain {
238
255
  catch { /* Report an invalid input without exposing its contents. */ }
239
256
  throw new StorageError('STORE_INVALID_ENTRY', 'The memory entry cannot be stored as JSON.');
240
257
  }
258
+ requireSameExplicitEntry(previous, next) {
259
+ const stable = ({ createdAt, lastAccessed, accessCount, ...entry }) => ({
260
+ ...entry, importance: boundedSignal(entry.importance), metadata: isRecord(entry.metadata) ? entry.metadata : {},
261
+ });
262
+ if (!isDeepStrictEqual(stable(previous), stable(next))) {
263
+ throw new StorageError('STORE_INVALID_ENTRY', 'This memory ID is already assigned to a different record.');
264
+ }
265
+ }
241
266
  publishWrite(entry) {
242
267
  try {
243
268
  getBrainBus().publish({
@@ -260,7 +285,7 @@ export class GlobalBrain {
260
285
  recall(opts = {}) {
261
286
  if (!this.initialized)
262
287
  return [];
263
- const keywords = normalizeKeywords(opts.keywords);
288
+ const keywords = normalizeRecallKeywords(opts.keywords);
264
289
  const result = this.findMatches(opts, keywords)
265
290
  .sort((a, b) => b.relevance - a.relevance || this.compareEntries(a.entry, b.entry))
266
291
  .slice(0, nonNegativeInt(opts.limit, 20))
@@ -272,7 +297,7 @@ export class GlobalBrain {
272
297
  recallScored(opts = {}) {
273
298
  if (!this.initialized)
274
299
  return [];
275
- const keywords = normalizeKeywords(opts.keywords);
300
+ const keywords = normalizeRecallKeywords(opts.keywords);
276
301
  const candidates = this.findMatches(opts, keywords);
277
302
  const evidence = new Map();
278
303
  for (const { entry } of candidates) {
@@ -280,6 +305,8 @@ export class GlobalBrain {
280
305
  evidence.set(evidenceKey(entry), new Set());
281
306
  }
282
307
  for (const entry of this.entries.values()) {
308
+ if (opts.excludeIds?.has(entry.id))
309
+ continue;
283
310
  evidence.get(evidenceKey(entry))?.add(entry.agentTool);
284
311
  }
285
312
  const now = Date.now();
@@ -298,10 +325,25 @@ export class GlobalBrain {
298
325
  this.recordRecall(result.length);
299
326
  return result;
300
327
  }
328
+ /** Read the scoped fields used by context ranking without exposing mutable memory records. */
329
+ *contextEntries(opts) {
330
+ if (!this.initialized)
331
+ return;
332
+ const minImportance = boundedSignal(opts.minImportance);
333
+ for (const entry of this.entries.values()) {
334
+ if (opts.projectId && entry.projectId !== opts.projectId)
335
+ continue;
336
+ if (opts.excludeIds?.has(entry.id) || entry.importance < minImportance)
337
+ continue;
338
+ yield contextSearchMemory(entry);
339
+ }
340
+ }
301
341
  findMatches(opts, keywords) {
302
342
  const minImportance = boundedSignal(opts.minImportance);
303
343
  const matches = [];
304
344
  for (const entry of this.entries.values()) {
345
+ if (opts.excludeIds?.has(entry.id))
346
+ continue;
305
347
  if (opts.projectId && entry.projectId !== opts.projectId)
306
348
  continue;
307
349
  if (opts.agentTool && entry.agentTool !== opts.agentTool)
@@ -392,6 +434,27 @@ export class GlobalBrain {
392
434
  }
393
435
  return out;
394
436
  }
437
+ /** Read one memory without exposing mutable in-memory state or recording a recall. */
438
+ getEntry(id) {
439
+ const entry = this.entries.get(id);
440
+ return entry ? structuredClone(entry) : undefined;
441
+ }
442
+ /** Hash the actual read view, including staged entries, without committing or recording access. */
443
+ readFingerprint(projectId) {
444
+ const hash = crypto.createHash('sha256');
445
+ for (const entry of this.entries.values()) {
446
+ if (projectId !== undefined && entry.projectId !== projectId)
447
+ continue;
448
+ hash.update(JSON.stringify(entry));
449
+ hash.update('\n');
450
+ }
451
+ return hash.digest('hex');
452
+ }
453
+ /** Refresh the local read view without acknowledging or committing pending operations. */
454
+ refresh() {
455
+ this.initialize();
456
+ this.refreshSnapshot();
457
+ }
395
458
  /** Chronological memory stream for dashboards and handoffs. */
396
459
  timeline(opts = {}) {
397
460
  if (!this.initialized)
@@ -541,7 +604,8 @@ export class GlobalBrain {
541
604
  refreshSnapshot() {
542
605
  try {
543
606
  const { data, sizeMB } = this.readLatestSnapshot();
544
- this.adoptSnapshot(data, sizeMB, this.hydrateEntries(data));
607
+ const view = this.pendingOperations.length ? this.mergeOperations(data, this.pendingOperations) : data;
608
+ this.adoptSnapshot(view, sizeMB, this.hydrateEntries(view));
545
609
  if (this.lastError?.code !== 'STORE_LOCK_FAILED')
546
610
  this.lastError = null;
547
611
  }
@@ -556,7 +620,10 @@ export class GlobalBrain {
556
620
  for (const operation of operations) {
557
621
  switch (operation.kind) {
558
622
  case 'add': {
559
- if (!entries.has(operation.entry.id))
623
+ const previous = entries.get(operation.entry.id);
624
+ if (previous)
625
+ this.requireSameExplicitEntry(previous, operation.entry);
626
+ else
560
627
  entries.set(operation.entry.id, operation.entry);
561
628
  break;
562
629
  }
@@ -650,7 +717,7 @@ export class GlobalBrain {
650
717
  // A failed token write can leave partial bytes. Check the exclusive file's identity.
651
718
  try {
652
719
  const current = fs.lstatSync(lockPath, { bigint: true });
653
- if (identity && current.dev === identity.dev && current.ino === identity.ino)
720
+ if (identity && matchesFileIdentity(lockPath, identity, current))
654
721
  fs.unlinkSync(lockPath);
655
722
  }
656
723
  catch { /* report the lock initialization error */ }