gnosys 5.11.3 → 5.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/cli.js +332 -5151
  2. package/dist/index.js +364 -235
  3. package/dist/lib/addCommand.d.ts +9 -0
  4. package/dist/lib/addCommand.js +103 -0
  5. package/dist/lib/addStructuredCommand.d.ts +16 -0
  6. package/dist/lib/addStructuredCommand.js +103 -0
  7. package/dist/lib/ambiguityCommand.d.ts +4 -0
  8. package/dist/lib/ambiguityCommand.js +36 -0
  9. package/dist/lib/apiKeyVault.d.ts +78 -0
  10. package/dist/lib/apiKeyVault.js +447 -0
  11. package/dist/lib/askCommand.d.ts +13 -0
  12. package/dist/lib/askCommand.js +145 -0
  13. package/dist/lib/audioExtract.js +4 -1
  14. package/dist/lib/auditCommand.d.ts +7 -0
  15. package/dist/lib/auditCommand.js +27 -0
  16. package/dist/lib/backupCommand.d.ts +6 -0
  17. package/dist/lib/backupCommand.js +54 -0
  18. package/dist/lib/bootstrapCommand.d.ts +15 -0
  19. package/dist/lib/bootstrapCommand.js +51 -0
  20. package/dist/lib/briefingCommand.d.ts +7 -0
  21. package/dist/lib/briefingCommand.js +92 -0
  22. package/dist/lib/centralizeCommand.d.ts +5 -0
  23. package/dist/lib/centralizeCommand.js +16 -0
  24. package/dist/lib/chatCommand.d.ts +12 -0
  25. package/dist/lib/chatCommand.js +46 -0
  26. package/dist/lib/checkCommand.d.ts +4 -0
  27. package/dist/lib/checkCommand.js +133 -0
  28. package/dist/lib/clientReadOverlay.d.ts +27 -0
  29. package/dist/lib/clientReadOverlay.js +73 -0
  30. package/dist/lib/clientReadResolve.d.ts +32 -0
  31. package/dist/lib/clientReadResolve.js +84 -0
  32. package/dist/lib/commitContextCommand.d.ts +9 -0
  33. package/dist/lib/commitContextCommand.js +142 -0
  34. package/dist/lib/config.d.ts +43 -3
  35. package/dist/lib/config.js +58 -57
  36. package/dist/lib/configCommand.d.ts +10 -0
  37. package/dist/lib/configCommand.js +321 -0
  38. package/dist/lib/connectCommand.d.ts +8 -0
  39. package/dist/lib/connectCommand.js +19 -0
  40. package/dist/lib/db.d.ts +52 -0
  41. package/dist/lib/db.js +169 -1
  42. package/dist/lib/dearchiveCommand.d.ts +7 -0
  43. package/dist/lib/dearchiveCommand.js +41 -0
  44. package/dist/lib/discoverCommand.d.ts +9 -0
  45. package/dist/lib/discoverCommand.js +87 -0
  46. package/dist/lib/doctorCommand.d.ts +6 -0
  47. package/dist/lib/doctorCommand.js +256 -0
  48. package/dist/lib/dream.d.ts +42 -2
  49. package/dist/lib/dream.js +290 -30
  50. package/dist/lib/dreamCommand.d.ts +10 -0
  51. package/dist/lib/dreamCommand.js +195 -0
  52. package/dist/lib/dreamLaunchd.d.ts +2 -0
  53. package/dist/lib/dreamLaunchd.js +72 -0
  54. package/dist/lib/dreamLogCommand.d.ts +10 -0
  55. package/dist/lib/dreamLogCommand.js +58 -0
  56. package/dist/lib/dreamReport.d.ts +7 -0
  57. package/dist/lib/dreamReport.js +114 -0
  58. package/dist/lib/dreamRunLog.d.ts +121 -0
  59. package/dist/lib/dreamRunLog.js +212 -0
  60. package/dist/lib/embeddings.js +3 -0
  61. package/dist/lib/exportCommand.d.ts +18 -0
  62. package/dist/lib/exportCommand.js +101 -0
  63. package/dist/lib/fsearchCommand.d.ts +8 -0
  64. package/dist/lib/fsearchCommand.js +44 -0
  65. package/dist/lib/graphCommand.d.ts +4 -0
  66. package/dist/lib/graphCommand.js +68 -0
  67. package/dist/lib/helperGenerateCommand.d.ts +5 -0
  68. package/dist/lib/helperGenerateCommand.js +27 -0
  69. package/dist/lib/historyCommand.d.ts +5 -0
  70. package/dist/lib/historyCommand.js +51 -0
  71. package/dist/lib/hybridSearchCommand.d.ts +12 -0
  72. package/dist/lib/hybridSearchCommand.js +95 -0
  73. package/dist/lib/ideMcpInstall.d.ts +30 -0
  74. package/dist/lib/ideMcpInstall.js +102 -0
  75. package/dist/lib/importCommand.d.ts +16 -0
  76. package/dist/lib/importCommand.js +89 -0
  77. package/dist/lib/importProjectCommand.d.ts +6 -0
  78. package/dist/lib/importProjectCommand.js +43 -0
  79. package/dist/lib/ingestCommand.d.ts +13 -0
  80. package/dist/lib/ingestCommand.js +95 -0
  81. package/dist/lib/installOutput.d.ts +36 -0
  82. package/dist/lib/installOutput.js +55 -0
  83. package/dist/lib/lensCommand.d.ts +20 -0
  84. package/dist/lib/lensCommand.js +61 -0
  85. package/dist/lib/lensing.d.ts +1 -0
  86. package/dist/lib/lensing.js +50 -9
  87. package/dist/lib/linksCommand.d.ts +7 -0
  88. package/dist/lib/linksCommand.js +48 -0
  89. package/dist/lib/listCommand.d.ts +8 -0
  90. package/dist/lib/listCommand.js +74 -0
  91. package/dist/lib/llm.d.ts +1 -1
  92. package/dist/lib/llm.js +26 -8
  93. package/dist/lib/localDiskCheck.d.ts +17 -0
  94. package/dist/lib/localDiskCheck.js +54 -0
  95. package/dist/lib/machineConfig.d.ts +11 -1
  96. package/dist/lib/machineConfig.js +16 -0
  97. package/dist/lib/machineRegistry.d.ts +61 -0
  98. package/dist/lib/machineRegistry.js +80 -0
  99. package/dist/lib/maintainCommand.d.ts +8 -0
  100. package/dist/lib/maintainCommand.js +34 -0
  101. package/dist/lib/masterLease.d.ts +20 -0
  102. package/dist/lib/masterLease.js +68 -0
  103. package/dist/lib/migrateCommand.d.ts +7 -0
  104. package/dist/lib/migrateCommand.js +158 -0
  105. package/dist/lib/migrateDbCommand.d.ts +9 -0
  106. package/dist/lib/migrateDbCommand.js +94 -0
  107. package/dist/lib/modelValidation.d.ts +5 -0
  108. package/dist/lib/modelValidation.js +27 -0
  109. package/dist/lib/openrouterTiers.d.ts +29 -0
  110. package/dist/lib/openrouterTiers.js +113 -0
  111. package/dist/lib/prefCommand.d.ts +10 -0
  112. package/dist/lib/prefCommand.js +118 -0
  113. package/dist/lib/projectsCommand.d.ts +8 -0
  114. package/dist/lib/projectsCommand.js +131 -0
  115. package/dist/lib/readCommand.d.ts +7 -0
  116. package/dist/lib/readCommand.js +62 -0
  117. package/dist/lib/recall.d.ts +3 -0
  118. package/dist/lib/recall.js +19 -4
  119. package/dist/lib/recallCommand.d.ts +11 -0
  120. package/dist/lib/recallCommand.js +112 -0
  121. package/dist/lib/reflectCommand.d.ts +8 -0
  122. package/dist/lib/reflectCommand.js +61 -0
  123. package/dist/lib/reindexCommand.d.ts +4 -0
  124. package/dist/lib/reindexCommand.js +34 -0
  125. package/dist/lib/reindexGraphCommand.d.ts +4 -0
  126. package/dist/lib/reindexGraphCommand.js +12 -0
  127. package/dist/lib/reinforceCommand.d.ts +8 -0
  128. package/dist/lib/reinforceCommand.js +40 -0
  129. package/dist/lib/remote.d.ts +5 -1
  130. package/dist/lib/remote.js +5 -1
  131. package/dist/lib/remoteWizard.d.ts +24 -5
  132. package/dist/lib/remoteWizard.js +308 -319
  133. package/dist/lib/restoreCommand.d.ts +5 -0
  134. package/dist/lib/restoreCommand.js +35 -0
  135. package/dist/lib/sandboxStartCommand.d.ts +6 -0
  136. package/dist/lib/sandboxStartCommand.js +25 -0
  137. package/dist/lib/sandboxStatusCommand.d.ts +4 -0
  138. package/dist/lib/sandboxStatusCommand.js +24 -0
  139. package/dist/lib/sandboxStopCommand.d.ts +4 -0
  140. package/dist/lib/sandboxStopCommand.js +21 -0
  141. package/dist/lib/searchCommand.d.ts +9 -0
  142. package/dist/lib/searchCommand.js +90 -0
  143. package/dist/lib/semanticSearchCommand.d.ts +8 -0
  144. package/dist/lib/semanticSearchCommand.js +52 -0
  145. package/dist/lib/setup/configSetRender.js +2 -0
  146. package/dist/lib/setup/providerGlyphs.d.ts +19 -0
  147. package/dist/lib/setup/providerGlyphs.js +42 -0
  148. package/dist/lib/setup/remoteRender.d.ts +31 -1
  149. package/dist/lib/setup/remoteRender.js +95 -4
  150. package/dist/lib/setup/sections/ides.d.ts +7 -0
  151. package/dist/lib/setup/sections/ides.js +24 -2
  152. package/dist/lib/setup/sections/providers.d.ts +17 -0
  153. package/dist/lib/setup/sections/providers.js +255 -0
  154. package/dist/lib/setup/sections/routing.d.ts +2 -6
  155. package/dist/lib/setup/sections/routing.js +33 -85
  156. package/dist/lib/setup/sections/taskRoutingEditor.d.ts +17 -0
  157. package/dist/lib/setup/sections/taskRoutingEditor.js +149 -0
  158. package/dist/lib/setup/summary.d.ts +9 -0
  159. package/dist/lib/setup/summary.js +51 -37
  160. package/dist/lib/setup/ui/status.d.ts +1 -0
  161. package/dist/lib/setup/ui/status.js +2 -0
  162. package/dist/lib/setup.d.ts +108 -3
  163. package/dist/lib/setup.js +813 -303
  164. package/dist/lib/setupKeys.d.ts +42 -0
  165. package/dist/lib/setupKeys.js +564 -0
  166. package/dist/lib/setupRemoteCommand.d.ts +4 -0
  167. package/dist/lib/setupRemoteCommand.js +28 -0
  168. package/dist/lib/setupRemotePullCommand.d.ts +5 -0
  169. package/dist/lib/setupRemotePullCommand.js +52 -0
  170. package/dist/lib/setupRemotePushCommand.d.ts +5 -0
  171. package/dist/lib/setupRemotePushCommand.js +57 -0
  172. package/dist/lib/setupRemoteResolveCommand.d.ts +4 -0
  173. package/dist/lib/setupRemoteResolveCommand.js +48 -0
  174. package/dist/lib/setupRemoteStatusCommand.d.ts +4 -0
  175. package/dist/lib/setupRemoteStatusCommand.js +73 -0
  176. package/dist/lib/setupRemoteSyncCommand.d.ts +6 -0
  177. package/dist/lib/setupRemoteSyncCommand.js +65 -0
  178. package/dist/lib/setupSyncProjectsCommand.d.ts +4 -0
  179. package/dist/lib/setupSyncProjectsCommand.js +292 -0
  180. package/dist/lib/staleCommand.d.ts +8 -0
  181. package/dist/lib/staleCommand.js +34 -0
  182. package/dist/lib/statsCommand.d.ts +6 -0
  183. package/dist/lib/statsCommand.js +142 -0
  184. package/dist/lib/statusCommand.d.ts +18 -0
  185. package/dist/lib/statusCommand.js +250 -0
  186. package/dist/lib/storesCommand.d.ts +2 -0
  187. package/dist/lib/storesCommand.js +4 -0
  188. package/dist/lib/syncClient.d.ts +47 -0
  189. package/dist/lib/syncClient.js +212 -0
  190. package/dist/lib/syncCommand.d.ts +6 -0
  191. package/dist/lib/syncCommand.js +57 -0
  192. package/dist/lib/syncDoctorCommand.d.ts +5 -0
  193. package/dist/lib/syncDoctorCommand.js +100 -0
  194. package/dist/lib/syncIngest.d.ts +19 -0
  195. package/dist/lib/syncIngest.js +152 -0
  196. package/dist/lib/syncIngestLaunchd.d.ts +8 -0
  197. package/dist/lib/syncIngestLaunchd.js +93 -0
  198. package/dist/lib/syncIngestStartup.d.ts +5 -0
  199. package/dist/lib/syncIngestStartup.js +29 -0
  200. package/dist/lib/syncIngestSystemd.d.ts +10 -0
  201. package/dist/lib/syncIngestSystemd.js +97 -0
  202. package/dist/lib/syncIngestTimer.d.ts +8 -0
  203. package/dist/lib/syncIngestTimer.js +27 -0
  204. package/dist/lib/syncIngestTimerCommand.d.ts +7 -0
  205. package/dist/lib/syncIngestTimerCommand.js +83 -0
  206. package/dist/lib/syncLock.d.ts +6 -0
  207. package/dist/lib/syncLock.js +74 -0
  208. package/dist/lib/syncSnapshot.d.ts +30 -0
  209. package/dist/lib/syncSnapshot.js +184 -0
  210. package/dist/lib/syncStaging.d.ts +81 -0
  211. package/dist/lib/syncStaging.js +239 -0
  212. package/dist/lib/tagsAddCommand.d.ts +8 -0
  213. package/dist/lib/tagsAddCommand.js +18 -0
  214. package/dist/lib/tagsCommand.d.ts +4 -0
  215. package/dist/lib/tagsCommand.js +16 -0
  216. package/dist/lib/timelineCommand.d.ts +7 -0
  217. package/dist/lib/timelineCommand.js +49 -0
  218. package/dist/lib/traceCommand.d.ts +6 -0
  219. package/dist/lib/traceCommand.js +39 -0
  220. package/dist/lib/traverseCommand.d.ts +6 -0
  221. package/dist/lib/traverseCommand.js +58 -0
  222. package/dist/lib/updateCommand.d.ts +13 -0
  223. package/dist/lib/updateCommand.js +67 -0
  224. package/dist/lib/updateStatusCommand.d.ts +5 -0
  225. package/dist/lib/updateStatusCommand.js +38 -0
  226. package/dist/lib/webAddCommand.d.ts +8 -0
  227. package/dist/lib/webAddCommand.js +55 -0
  228. package/dist/lib/webBuildCommand.d.ts +10 -0
  229. package/dist/lib/webBuildCommand.js +65 -0
  230. package/dist/lib/webBuildIndexCommand.d.ts +8 -0
  231. package/dist/lib/webBuildIndexCommand.js +37 -0
  232. package/dist/lib/webIngestCommand.d.ts +11 -0
  233. package/dist/lib/webIngestCommand.js +51 -0
  234. package/dist/lib/webInitCommand.d.ts +9 -0
  235. package/dist/lib/webInitCommand.js +167 -0
  236. package/dist/lib/webRemoveCommand.d.ts +5 -0
  237. package/dist/lib/webRemoveCommand.js +41 -0
  238. package/dist/lib/webStatusCommand.d.ts +5 -0
  239. package/dist/lib/webStatusCommand.js +94 -0
  240. package/dist/lib/webUpdateCommand.d.ts +7 -0
  241. package/dist/lib/webUpdateCommand.js +72 -0
  242. package/dist/lib/workingSetCommand.d.ts +6 -0
  243. package/dist/lib/workingSetCommand.js +37 -0
  244. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -58,6 +58,10 @@ import { setPreference, getPreference, getAllPreferences, deletePreference, KNOW
58
58
  import { syncRules, generateRulesBlock } from "./lib/rulesGen.js";
59
59
  import { federatedSearch, detectAmbiguity, generateBriefing, generateAllBriefings, getWorkingSet, formatWorkingSet, detectCurrentProject } from "./lib/federated.js";
60
60
  import { generatePortfolio, formatPortfolioCompact, formatPortfolioMarkdown, generateStatusPrompt } from "./lib/portfolio.js";
61
+ import { applyPendingOverlay, mergeOverlayDiscoverResults, mergeOverlaySearchResults, pendingAddToDbMemory, } from "./lib/clientReadOverlay.js";
62
+ import { readMachineConfig } from "./lib/machineConfig.js";
63
+ import { getConfiguredRemotePath } from "./lib/remote.js";
64
+ import { closeClientReadContext, openClientReadContext, } from "./lib/syncClient.js";
61
65
  // Initialize resolver (discovers all layered stores)
62
66
  const resolver = new GnosysResolver();
63
67
  let config = DEFAULT_CONFIG;
@@ -136,14 +140,30 @@ let askEngine = null;
136
140
  let gnosysDb = null;
137
141
  /** v3.0: Central DB at ~/.gnosys/gnosys.db */
138
142
  let centralDb = null;
139
- /** v2.0: Dream scheduler (idle-time consolidation) */
140
- let dreamScheduler = null;
141
143
  // ─── Multi-Project Support ───────────────────────────────────────────────
142
144
  // Each tool call can optionally pass a `projectRoot` to target a specific
143
145
  // project's .gnosys store. This is STATELESS — no race conditions when
144
146
  // multiple agents call tools in parallel.
145
147
  /** Common Zod schema fragment for projectRoot parameter */
146
148
  const projectRootParam = z.string().optional().describe("Optional project root path for multi-project support. When provided, this tool operates on projectRoot/.gnosys instead of the default store. Use gnosys_stores to see all available stores.");
149
+ function applyClientReadToCentralDb(localDb) {
150
+ if (!localDb?.isAvailable()) {
151
+ return { centralDb: localDb, clientRead: null };
152
+ }
153
+ const mc = readMachineConfig();
154
+ if (!mc?.remote.enabled || mc.remote.role !== "client") {
155
+ return { centralDb: localDb, clientRead: null };
156
+ }
157
+ const masterPath = getConfiguredRemotePath(localDb);
158
+ if (!masterPath)
159
+ return { centralDb: localDb, clientRead: null };
160
+ const clientRead = openClientReadContext(localDb, masterPath, mc.machineId);
161
+ return { centralDb: clientRead.db, clientRead };
162
+ }
163
+ function releaseClientReadFromContext(ctx) {
164
+ if (ctx.clientRead)
165
+ closeClientReadContext(ctx.clientRead);
166
+ }
147
167
  async function resolveToolContext(projectRoot) {
148
168
  if (!projectRoot) {
149
169
  // Default context — use module-level state
@@ -155,6 +175,7 @@ async function resolveToolContext(projectRoot) {
155
175
  const identity = await readProjectIdentity(parentDir);
156
176
  projectId = identity?.projectId || null;
157
177
  }
178
+ const applied = applyClientReadToCentralDb(centralDb);
158
179
  return {
159
180
  resolver,
160
181
  store: writeTarget?.store || null,
@@ -162,8 +183,9 @@ async function resolveToolContext(projectRoot) {
162
183
  config,
163
184
  search,
164
185
  gnosysDb,
165
- centralDb,
186
+ centralDb: applied.centralDb,
166
187
  projectId,
188
+ clientRead: applied.clientRead,
167
189
  };
168
190
  }
169
191
  // Scoped context — resolve for this specific project
@@ -192,6 +214,7 @@ async function resolveToolContext(projectRoot) {
192
214
  // Removed: new GnosysDB(scopedStorePath) which created an empty
193
215
  // gnosys.db in the project's .gnosys/ directory.
194
216
  }
217
+ const applied = applyClientReadToCentralDb(centralDb);
195
218
  return {
196
219
  resolver: scopedResolver,
197
220
  store: scopedWriteTarget?.store || null,
@@ -199,8 +222,9 @@ async function resolveToolContext(projectRoot) {
199
222
  config: scopedConfig,
200
223
  search: scopedSearch,
201
224
  gnosysDb: scopedDb,
202
- centralDb,
225
+ centralDb: applied.centralDb,
203
226
  projectId,
227
+ clientRead: applied.clientRead,
204
228
  };
205
229
  }
206
230
  /**
@@ -244,50 +268,65 @@ regTool("gnosys_discover", "Discover relevant memories by describing what you're
244
268
  projectRoot: projectRootParam,
245
269
  }, async ({ query, limit, projectRoot }) => {
246
270
  const ctx = await resolveToolContext(projectRoot);
247
- // v2.0 DB-backed fast path
248
- if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
249
- const results = ctx.centralDb.discoverFts(query, limit || 20);
271
+ try {
272
+ // v2.0 DB-backed fast path
273
+ if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
274
+ const lim = limit || 20;
275
+ let results = ctx.centralDb.discoverFts(query, lim);
276
+ if (ctx.clientRead?.pendingOverlay.length) {
277
+ results = mergeOverlayDiscoverResults(results, ctx.clientRead.pendingOverlay, query, lim, (p) => ({
278
+ id: p.id,
279
+ title: p.title,
280
+ relevance: "",
281
+ rank: 0,
282
+ project_id: p.project_id,
283
+ }));
284
+ }
285
+ if (results.length === 0) {
286
+ return {
287
+ content: [{ type: "text", text: `No memories found for "${query}". Try different keywords.` }],
288
+ };
289
+ }
290
+ const formatted = results
291
+ .map((r) => `**${r.title}**\n ID: ${r.id}${r.relevance ? `\n Relevance: ${r.relevance}` : ""}`)
292
+ .join("\n\n");
293
+ return {
294
+ content: [{ type: "text", text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.` }],
295
+ };
296
+ }
297
+ // v1.x legacy path
298
+ if (!ctx.search) {
299
+ return {
300
+ content: [{ type: "text", text: "Search index not initialized." }],
301
+ isError: true,
302
+ };
303
+ }
304
+ const results = ctx.search.discover(query, limit || 20);
250
305
  if (results.length === 0) {
251
306
  return {
252
- content: [{ type: "text", text: `No memories found for "${query}". Try different keywords.` }],
307
+ content: [
308
+ {
309
+ type: "text",
310
+ text: `No memories found for "${query}". Try different keywords or use gnosys_search for full-text search.`,
311
+ },
312
+ ],
253
313
  };
254
314
  }
255
315
  const formatted = results
256
- .map((r) => `**${r.title}**\n ID: ${r.id}${r.relevance ? `\n Relevance: ${r.relevance}` : ""}`)
316
+ .map((r) => `**${r.title}**\n Path: ${r.relative_path}${r.relevance ? `\n Relevance: ${r.relevance}` : ""}`)
257
317
  .join("\n\n");
258
- return {
259
- content: [{ type: "text", text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.` }],
260
- };
261
- }
262
- // v1.x legacy path
263
- if (!ctx.search) {
264
- return {
265
- content: [{ type: "text", text: "Search index not initialized." }],
266
- isError: true,
267
- };
268
- }
269
- const results = ctx.search.discover(query, limit || 20);
270
- if (results.length === 0) {
271
318
  return {
272
319
  content: [
273
320
  {
274
321
  type: "text",
275
- text: `No memories found for "${query}". Try different keywords or use gnosys_search for full-text search.`,
322
+ text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.`,
276
323
  },
277
324
  ],
278
325
  };
279
326
  }
280
- const formatted = results
281
- .map((r) => `**${r.title}**\n Path: ${r.relative_path}${r.relevance ? `\n Relevance: ${r.relevance}` : ""}`)
282
- .join("\n\n");
283
- return {
284
- content: [
285
- {
286
- type: "text",
287
- text: `Found ${results.length} relevant memories for "${query}":\n\n${formatted}\n\nUse gnosys_read to load any of these.`,
288
- },
289
- ],
290
- };
327
+ finally {
328
+ releaseClientReadFromContext(ctx);
329
+ }
291
330
  });
292
331
  // ─── Tool: gnosys_read ───────────────────────────────────────────────────
293
332
  regTool("gnosys_read", "Read a specific memory. Accepts a memory ID (e.g., 'arch-012') or layer-prefixed path (e.g., 'project:decisions/why-not-rag.md'). Without a prefix, searches all stores in precedence order.", {
@@ -295,62 +334,72 @@ regTool("gnosys_read", "Read a specific memory. Accepts a memory ID (e.g., 'arch
295
334
  projectRoot: projectRootParam,
296
335
  }, async ({ path: memPath, projectRoot }) => {
297
336
  const ctx = await resolveToolContext(projectRoot);
298
- // v2.0 DB-backed fast path: try reading by memory ID from gnosys.db first
299
- if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
300
- const dbMem = ctx.centralDb.getMemory(memPath);
301
- if (dbMem) {
302
- const tags = dbMem.tags || "[]";
303
- const headerLines = [
304
- `---`,
305
- `id: ${dbMem.id}`,
306
- `title: '${dbMem.title}'`,
307
- `category: ${dbMem.category}`,
308
- `tags: ${tags}`,
309
- `relevance: ${dbMem.relevance}`,
310
- `author: ${dbMem.author}`,
311
- `authority: ${dbMem.authority}`,
312
- `confidence: ${dbMem.confidence}`,
313
- `status: ${dbMem.status}`,
314
- `tier: ${dbMem.tier}`,
315
- `created: '${dbMem.created}'`,
316
- `modified: '${dbMem.modified}'`,
317
- ];
318
- if (dbMem.source_file) {
319
- headerLines.push(`source_file: ${dbMem.source_file}${dbMem.source_page != null ? ` (page ${Number(dbMem.source_page)})` : ""}`);
337
+ try {
338
+ // v2.0 DB-backed fast path: try reading by memory ID from gnosys.db first
339
+ if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
340
+ let dbMem = ctx.centralDb.getMemory(memPath);
341
+ if (!dbMem && ctx.clientRead?.pendingOverlay.length) {
342
+ const pending = ctx.clientRead.pendingOverlay.find((p) => p.id === memPath);
343
+ if (pending)
344
+ dbMem = pendingAddToDbMemory(pending);
345
+ }
346
+ if (dbMem) {
347
+ const tags = dbMem.tags || "[]";
348
+ const headerLines = [
349
+ `---`,
350
+ `id: ${dbMem.id}`,
351
+ `title: '${dbMem.title}'`,
352
+ `category: ${dbMem.category}`,
353
+ `tags: ${tags}`,
354
+ `relevance: ${dbMem.relevance}`,
355
+ `author: ${dbMem.author}`,
356
+ `authority: ${dbMem.authority}`,
357
+ `confidence: ${dbMem.confidence}`,
358
+ `status: ${dbMem.status}`,
359
+ `tier: ${dbMem.tier}`,
360
+ `created: '${dbMem.created}'`,
361
+ `modified: '${dbMem.modified}'`,
362
+ ];
363
+ if (dbMem.source_file) {
364
+ headerLines.push(`source_file: ${dbMem.source_file}${dbMem.source_page != null ? ` (page ${Number(dbMem.source_page)})` : ""}`);
365
+ }
366
+ if (dbMem.source_path)
367
+ headerLines.push(`source_path: ${dbMem.source_path}`);
368
+ headerLines.push(`---`);
369
+ const header = headerLines.join("\n");
370
+ return {
371
+ content: [{ type: "text", text: `[Source: gnosys.db]\n\n${header}\n\n${dbMem.content}` }],
372
+ };
320
373
  }
321
- if (dbMem.source_path)
322
- headerLines.push(`source_path: ${dbMem.source_path}`);
323
- headerLines.push(`---`);
324
- const header = headerLines.join("\n");
374
+ // Not found in db — fall through to legacy path
375
+ }
376
+ // v1.x legacy path
377
+ const memory = await ctx.resolver.readMemory(memPath);
378
+ if (!memory) {
325
379
  return {
326
- content: [{ type: "text", text: `[Source: gnosys.db]\n\n${header}\n\n${dbMem.content}` }],
380
+ content: [{ type: "text", text: `Memory not found: ${memPath}` }],
381
+ isError: true,
327
382
  };
328
383
  }
329
- // Not found in db — fall through to legacy path
330
- }
331
- // v1.x legacy path
332
- const memory = await ctx.resolver.readMemory(memPath);
333
- if (!memory) {
384
+ let raw;
385
+ try {
386
+ raw = await fs.readFile(memory.filePath, "utf-8");
387
+ }
388
+ catch (err) {
389
+ return { content: [{ type: "text", text: formatMcpError("reading memory", err) }], isError: true };
390
+ }
334
391
  return {
335
- content: [{ type: "text", text: `Memory not found: ${memPath}` }],
336
- isError: true,
392
+ content: [
393
+ {
394
+ type: "text",
395
+ text: `[Source: ${memory.sourceLabel}]\n\n${raw}`,
396
+ },
397
+ ],
337
398
  };
338
399
  }
339
- let raw;
340
- try {
341
- raw = await fs.readFile(memory.filePath, "utf-8");
342
- }
343
- catch (err) {
344
- return { content: [{ type: "text", text: formatMcpError("reading memory", err) }], isError: true };
400
+ finally {
401
+ releaseClientReadFromContext(ctx);
345
402
  }
346
- return {
347
- content: [
348
- {
349
- type: "text",
350
- text: `[Source: ${memory.sourceLabel}]\n\n${raw}`,
351
- },
352
- ],
353
- };
354
403
  });
355
404
  // ─── Tool: gnosys_search ─────────────────────────────────────────────────
356
405
  regTool("gnosys_search", "Search memories by keyword across all stores. Returns matching file paths with relevance snippets.", {
@@ -359,50 +408,65 @@ regTool("gnosys_search", "Search memories by keyword across all stores. Returns
359
408
  projectRoot: projectRootParam,
360
409
  }, async ({ query, limit, projectRoot }) => {
361
410
  const ctx = await resolveToolContext(projectRoot);
362
- // v2.0 DB-backed fast path
363
- if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
364
- const results = ctx.centralDb.searchFts(query, limit || 20);
411
+ try {
412
+ // v2.0 DB-backed fast path
413
+ if (ctx.centralDb?.isAvailable() && ctx.centralDb?.isMigrated()) {
414
+ const lim = limit || 20;
415
+ let results = ctx.centralDb.searchFts(query, lim);
416
+ if (ctx.clientRead?.pendingOverlay.length) {
417
+ results = mergeOverlaySearchResults(results, ctx.clientRead.pendingOverlay, query, lim, (p) => ({
418
+ id: p.id,
419
+ title: p.title,
420
+ snippet: p.content.substring(0, 200),
421
+ rank: 0,
422
+ project_id: p.project_id,
423
+ }));
424
+ }
425
+ if (results.length === 0) {
426
+ return {
427
+ content: [{ type: "text", text: `No results for "${query}". Try different keywords.` }],
428
+ };
429
+ }
430
+ const formatted = results
431
+ .map((r) => `**${r.title}** (${r.id})\n${r.snippet.replace(/>>>/g, "**").replace(/<<</g, "**")}`)
432
+ .join("\n\n");
433
+ return {
434
+ content: [{ type: "text", text: `Found ${results.length} results for "${query}":\n\n${formatted}` }],
435
+ };
436
+ }
437
+ // v1.x legacy path
438
+ if (!ctx.search) {
439
+ return {
440
+ content: [{ type: "text", text: "Search index not initialized." }],
441
+ isError: true,
442
+ };
443
+ }
444
+ const results = ctx.search.search(query, limit || 20);
365
445
  if (results.length === 0) {
366
446
  return {
367
- content: [{ type: "text", text: `No results for "${query}". Try different keywords.` }],
447
+ content: [
448
+ {
449
+ type: "text",
450
+ text: `No results for "${query}". Try different keywords or use gnosys_discover.`,
451
+ },
452
+ ],
368
453
  };
369
454
  }
370
455
  const formatted = results
371
- .map((r) => `**${r.title}** (${r.id})\n${r.snippet.replace(/>>>/g, "**").replace(/<<</g, "**")}`)
456
+ .map((r) => `**${r.title}** (${r.relative_path})\n${r.snippet.replace(/>>>/g, "**").replace(/<<</g, "**")}`)
372
457
  .join("\n\n");
373
- return {
374
- content: [{ type: "text", text: `Found ${results.length} results for "${query}":\n\n${formatted}` }],
375
- };
376
- }
377
- // v1.x legacy path
378
- if (!ctx.search) {
379
- return {
380
- content: [{ type: "text", text: "Search index not initialized." }],
381
- isError: true,
382
- };
383
- }
384
- const results = ctx.search.search(query, limit || 20);
385
- if (results.length === 0) {
386
458
  return {
387
459
  content: [
388
460
  {
389
461
  type: "text",
390
- text: `No results for "${query}". Try different keywords or use gnosys_discover.`,
462
+ text: `Found ${results.length} results for "${query}":\n\n${formatted}`,
391
463
  },
392
464
  ],
393
465
  };
394
466
  }
395
- const formatted = results
396
- .map((r) => `**${r.title}** (${r.relative_path})\n${r.snippet.replace(/>>>/g, "**").replace(/<<</g, "**")}`)
397
- .join("\n\n");
398
- return {
399
- content: [
400
- {
401
- type: "text",
402
- text: `Found ${results.length} results for "${query}":\n\n${formatted}`,
403
- },
404
- ],
405
- };
467
+ finally {
468
+ releaseClientReadFromContext(ctx);
469
+ }
406
470
  });
407
471
  // ─── Tool: gnosys_list ───────────────────────────────────────────────────
408
472
  regTool("gnosys_list", "List memories across all stores, optionally filtered by category, tag, or store layer.", {
@@ -413,41 +477,76 @@ regTool("gnosys_list", "List memories across all stores, optionally filtered by
413
477
  projectRoot: projectRootParam,
414
478
  }, async ({ category, tag, store: storeFilter, status, projectRoot }) => {
415
479
  const ctx = await resolveToolContext(projectRoot);
416
- // DB-first: read from central DB instead of scanning markdown files
417
- const db = ctx.centralDb;
418
- if (db?.isAvailable()) {
419
- let dbMemories = status === "active" || !status
420
- ? db.getActiveMemories()
421
- : db.getAllMemories();
422
- // Apply filters on DB results
423
- if (status && status !== "active") {
424
- dbMemories = dbMemories.filter((m) => m.status === status);
480
+ try {
481
+ // DB-first: read from central DB instead of scanning markdown files
482
+ const db = ctx.centralDb;
483
+ if (db?.isAvailable()) {
484
+ let dbMemories = status === "active" || !status
485
+ ? db.getActiveMemories()
486
+ : db.getAllMemories();
487
+ if (ctx.clientRead?.pendingOverlay.length && (status === "active" || !status)) {
488
+ dbMemories = applyPendingOverlay(dbMemories, ctx.clientRead.pendingOverlay, new Set()).memories;
489
+ }
490
+ // Apply filters on DB results
491
+ if (status && status !== "active") {
492
+ dbMemories = dbMemories.filter((m) => m.status === status);
493
+ }
494
+ if (storeFilter) {
495
+ dbMemories = dbMemories.filter((m) => m.scope === storeFilter);
496
+ }
497
+ if (category) {
498
+ dbMemories = dbMemories.filter((m) => m.category === category);
499
+ }
500
+ if (tag) {
501
+ dbMemories = dbMemories.filter((m) => {
502
+ try {
503
+ const parsed = JSON.parse(m.tags || "[]");
504
+ const tagList = Array.isArray(parsed)
505
+ ? parsed
506
+ : Object.values(parsed).flat();
507
+ return tagList.includes(tag);
508
+ }
509
+ catch {
510
+ return false;
511
+ }
512
+ });
513
+ }
514
+ // Filter by project if we have a project ID (so scoped queries only see their project)
515
+ if (ctx.projectId && !storeFilter) {
516
+ dbMemories = dbMemories.filter((m) => m.project_id === ctx.projectId || m.scope !== "project");
517
+ }
518
+ const lines = dbMemories.map((m) => `- [${m.scope}] **${m.title}** (${m.category}/${m.id}) [${m.status}]`);
519
+ return {
520
+ content: [
521
+ {
522
+ type: "text",
523
+ text: lines.length > 0
524
+ ? `${lines.length} memories:\n\n${lines.join("\n")}`
525
+ : "No memories match the filter.",
526
+ },
527
+ ],
528
+ };
425
529
  }
530
+ // Fallback: read from markdown files if central DB unavailable
531
+ let memories = await ctx.resolver.getAllMemories();
426
532
  if (storeFilter) {
427
- dbMemories = dbMemories.filter((m) => m.scope === storeFilter);
533
+ memories = memories.filter((m) => m.sourceLayer === storeFilter || m.sourceLabel === storeFilter);
428
534
  }
429
535
  if (category) {
430
- dbMemories = dbMemories.filter((m) => m.category === category);
536
+ memories = memories.filter((m) => m.frontmatter.category === category);
431
537
  }
432
538
  if (tag) {
433
- dbMemories = dbMemories.filter((m) => {
434
- try {
435
- const parsed = JSON.parse(m.tags || "[]");
436
- const tagList = Array.isArray(parsed)
437
- ? parsed
438
- : Object.values(parsed).flat();
439
- return tagList.includes(tag);
440
- }
441
- catch {
442
- return false;
443
- }
539
+ memories = memories.filter((m) => {
540
+ const tags = Array.isArray(m.frontmatter.tags)
541
+ ? m.frontmatter.tags
542
+ : Object.values(m.frontmatter.tags).flat();
543
+ return tags.includes(tag);
444
544
  });
445
545
  }
446
- // Filter by project if we have a project ID (so scoped queries only see their project)
447
- if (ctx.projectId && !storeFilter) {
448
- dbMemories = dbMemories.filter((m) => m.project_id === ctx.projectId || m.scope !== "project");
546
+ if (status) {
547
+ memories = memories.filter((m) => m.frontmatter.status === status);
449
548
  }
450
- const lines = dbMemories.map((m) => `- [${m.scope}] **${m.title}** (${m.category}/${m.id}) [${m.status}]`);
549
+ const lines = memories.map((m) => `- [${m.sourceLabel}] **${m.frontmatter.title}** (${m.relativePath}) [${m.frontmatter.status}]`);
451
550
  return {
452
551
  content: [
453
552
  {
@@ -459,36 +558,9 @@ regTool("gnosys_list", "List memories across all stores, optionally filtered by
459
558
  ],
460
559
  };
461
560
  }
462
- // Fallback: read from markdown files if central DB unavailable
463
- let memories = await ctx.resolver.getAllMemories();
464
- if (storeFilter) {
465
- memories = memories.filter((m) => m.sourceLayer === storeFilter || m.sourceLabel === storeFilter);
466
- }
467
- if (category) {
468
- memories = memories.filter((m) => m.frontmatter.category === category);
469
- }
470
- if (tag) {
471
- memories = memories.filter((m) => {
472
- const tags = Array.isArray(m.frontmatter.tags)
473
- ? m.frontmatter.tags
474
- : Object.values(m.frontmatter.tags).flat();
475
- return tags.includes(tag);
476
- });
477
- }
478
- if (status) {
479
- memories = memories.filter((m) => m.frontmatter.status === status);
561
+ finally {
562
+ releaseClientReadFromContext(ctx);
480
563
  }
481
- const lines = memories.map((m) => `- [${m.sourceLabel}] **${m.frontmatter.title}** (${m.relativePath}) [${m.frontmatter.status}]`);
482
- return {
483
- content: [
484
- {
485
- type: "text",
486
- text: lines.length > 0
487
- ? `${lines.length} memories:\n\n${lines.join("\n")}`
488
- : "No memories match the filter.",
489
- },
490
- ],
491
- };
492
564
  });
493
565
  // ─── Tool: gnosys_add ────────────────────────────────────────────────────
494
566
  regTool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structures it into an atomic memory. Writes to the project store by default. Use store='personal' for cross-project knowledge, or store='global' to explicitly write to shared org knowledge.", {
@@ -1905,8 +1977,6 @@ regTool("gnosys_dream", "Run a Dream Mode cycle — idle-time consolidation that
1905
1977
  ],
1906
1978
  };
1907
1979
  }
1908
- // Record activity to reset idle timer (if scheduler is running)
1909
- dreamScheduler?.recordActivity();
1910
1980
  const dreamConfig = {
1911
1981
  enabled: true,
1912
1982
  idleMinutes: 0, // Run immediately (manual trigger)
@@ -1924,6 +1994,34 @@ regTool("gnosys_dream", "Run a Dream Mode cycle — idle-time consolidation that
1924
1994
  const report = await engine.dream((phase, detail) => {
1925
1995
  console.error(`[dream:${phase}] ${detail}`);
1926
1996
  });
1997
+ const { appendDreamRun } = await import("./lib/dreamRunLog.js");
1998
+ appendDreamRun({
1999
+ ...report,
2000
+ id: report.id || `dream-${Date.now()}`,
2001
+ trigger: report.trigger || "manual",
2002
+ status: report.aborted ? "aborted" : report.errors.length > 0 ? "failed" : "completed",
2003
+ machine: report.machine || { hostname: "unknown" },
2004
+ provider: report.provider || dreamConfig.provider,
2005
+ phases: report.phases || [],
2006
+ llmCalls: report.llmCalls || [],
2007
+ totals: report.totals || {
2008
+ llmCallsMade: 0,
2009
+ llmCallsSkipped: 0,
2010
+ estimatedInputTokens: 0,
2011
+ estimatedOutputTokens: 0,
2012
+ estimatedCostUsd: 0,
2013
+ },
2014
+ effectiveness: report.effectiveness || {
2015
+ usefulOutputScore: 0,
2016
+ costPerUsefulOutput: null,
2017
+ decaysApplied: report.decayUpdated,
2018
+ summariesGenerated: report.summariesGenerated,
2019
+ summariesUpdated: report.summariesUpdated,
2020
+ reviewSuggestions: report.reviewSuggestions.length,
2021
+ relationshipsDiscovered: report.relationshipsDiscovered,
2022
+ },
2023
+ gates: [],
2024
+ });
1927
2025
  return {
1928
2026
  content: [
1929
2027
  {
@@ -2072,8 +2170,6 @@ regResource("gnosys_recall", "gnosys://recall", {
2072
2170
  priority: 1, // Highest priority — always inject
2073
2171
  },
2074
2172
  }, async () => {
2075
- // Record activity for dream scheduler (this fires on every turn)
2076
- dreamScheduler?.recordActivity();
2077
2173
  if (!search) {
2078
2174
  return {
2079
2175
  contents: [
@@ -2086,23 +2182,31 @@ regResource("gnosys_recall", "gnosys://recall", {
2086
2182
  };
2087
2183
  }
2088
2184
  const storePath = resolver.getWriteTarget()?.store.getStorePath() || "";
2089
- const result = await recall("*", {
2090
- limit: config.recall?.maxMemories || 8,
2091
- search,
2092
- resolver,
2093
- storePath,
2094
- recallConfig: config.recall,
2095
- gnosysDb: gnosysDb || undefined,
2096
- });
2097
- return {
2098
- contents: [
2099
- {
2100
- uri: "gnosys://recall",
2101
- mimeType: "text/markdown",
2102
- text: formatRecall(result),
2103
- },
2104
- ],
2105
- };
2185
+ const applied = applyClientReadToCentralDb(centralDb);
2186
+ try {
2187
+ const result = await recall("*", {
2188
+ limit: config.recall?.maxMemories || 8,
2189
+ search,
2190
+ resolver,
2191
+ storePath,
2192
+ recallConfig: config.recall,
2193
+ gnosysDb: applied.centralDb || undefined,
2194
+ pendingOverlay: applied.clientRead?.pendingOverlay,
2195
+ });
2196
+ return {
2197
+ contents: [
2198
+ {
2199
+ uri: "gnosys://recall",
2200
+ mimeType: "text/markdown",
2201
+ text: formatRecall(result),
2202
+ },
2203
+ ],
2204
+ };
2205
+ }
2206
+ finally {
2207
+ if (applied.clientRead)
2208
+ closeClientReadContext(applied.clientRead);
2209
+ }
2106
2210
  });
2107
2211
  // ─── Tool: gnosys_recall (query-specific fallback) ──────────────────────
2108
2212
  // For hosts that don't support MCP Resources, or when the agent wants to
@@ -2118,28 +2222,34 @@ regTool("gnosys_recall", "Fast memory recall — inject relevant memories as con
2118
2222
  }, async ({ query, limit, traceId, aggressive, projectRoot }) => {
2119
2223
  try {
2120
2224
  const ctx = await resolveToolContext(projectRoot);
2121
- if (!ctx.search) {
2225
+ try {
2226
+ if (!ctx.search) {
2227
+ return {
2228
+ content: [{ type: "text", text: "<gnosys: no-strong-recall-needed>" }],
2229
+ };
2230
+ }
2231
+ const storePath = ctx.resolver.getWriteTarget()?.store.getStorePath() || "";
2232
+ const recallConfig = {
2233
+ ...ctx.config.recall,
2234
+ ...(aggressive !== undefined ? { aggressive } : {}),
2235
+ };
2236
+ const result = await recall(query, {
2237
+ limit: Math.min(limit || recallConfig.maxMemories, 15),
2238
+ search: ctx.search,
2239
+ resolver: ctx.resolver,
2240
+ storePath,
2241
+ traceId,
2242
+ recallConfig,
2243
+ gnosysDb: ctx.centralDb || undefined,
2244
+ pendingOverlay: ctx.clientRead?.pendingOverlay,
2245
+ });
2122
2246
  return {
2123
- content: [{ type: "text", text: "<gnosys: no-strong-recall-needed>" }],
2247
+ content: [{ type: "text", text: formatRecall(result) }],
2124
2248
  };
2125
2249
  }
2126
- const storePath = ctx.resolver.getWriteTarget()?.store.getStorePath() || "";
2127
- const recallConfig = {
2128
- ...ctx.config.recall,
2129
- ...(aggressive !== undefined ? { aggressive } : {}),
2130
- };
2131
- const result = await recall(query, {
2132
- limit: Math.min(limit || recallConfig.maxMemories, 15),
2133
- search: ctx.search,
2134
- resolver: ctx.resolver,
2135
- storePath,
2136
- traceId,
2137
- recallConfig,
2138
- gnosysDb: ctx.centralDb || undefined,
2139
- });
2140
- return {
2141
- content: [{ type: "text", text: formatRecall(result) }],
2142
- };
2250
+ finally {
2251
+ releaseClientReadFromContext(ctx);
2252
+ }
2143
2253
  }
2144
2254
  catch (err) {
2145
2255
  return { content: [{ type: "text", text: formatMcpError("recalling memories", err) }], isError: true };
@@ -2383,28 +2493,34 @@ regTool("gnosys_federated_search", "Search across all scopes (project → user
2383
2493
  projectRoot: z.string().optional().describe("Project root directory for context detection"),
2384
2494
  includeGlobal: z.boolean().optional().describe("Include global-scope memories (default: true)"),
2385
2495
  }, async ({ query, limit, projectRoot, includeGlobal }) => {
2386
- if (!centralDb?.isAvailable()) {
2387
- return { content: [{ type: "text", text: "Central DB not available. Run gnosys_init first." }], isError: true };
2496
+ const ctx = await resolveToolContext(projectRoot);
2497
+ try {
2498
+ if (!ctx.centralDb?.isAvailable()) {
2499
+ return { content: [{ type: "text", text: "Central DB not available. Run gnosys_init first." }], isError: true };
2500
+ }
2501
+ // Auto-detect current project
2502
+ const projectId = await detectCurrentProject(ctx.centralDb, projectRoot || undefined);
2503
+ const results = federatedSearch(ctx.centralDb, query, {
2504
+ limit: limit || 20,
2505
+ projectId,
2506
+ includeGlobal: includeGlobal !== false,
2507
+ });
2508
+ if (results.length === 0) {
2509
+ return { content: [{ type: "text", text: `No results for "${query}" across any scope.` }] };
2510
+ }
2511
+ const lines = results.map((r, i) => {
2512
+ const projectLabel = r.projectName ? ` [${r.projectName}]` : "";
2513
+ const boostLabel = r.boosts.length > 0 ? ` (${r.boosts.join(", ")})` : "";
2514
+ return `${i + 1}. **${r.title}** (${r.category})${projectLabel}\n scope: ${r.scope} | score: ${r.score.toFixed(4)}${boostLabel}\n ${r.snippet}`;
2515
+ });
2516
+ const contextNote = projectId ? `Context: project ${projectId}` : "Context: no project detected";
2517
+ return {
2518
+ content: [{ type: "text", text: `${contextNote}\n\n${lines.join("\n\n")}` }],
2519
+ };
2388
2520
  }
2389
- // Auto-detect current project
2390
- const projectId = await detectCurrentProject(centralDb, projectRoot || undefined);
2391
- const results = federatedSearch(centralDb, query, {
2392
- limit: limit || 20,
2393
- projectId,
2394
- includeGlobal: includeGlobal !== false,
2395
- });
2396
- if (results.length === 0) {
2397
- return { content: [{ type: "text", text: `No results for "${query}" across any scope.` }] };
2521
+ finally {
2522
+ releaseClientReadFromContext(ctx);
2398
2523
  }
2399
- const lines = results.map((r, i) => {
2400
- const projectLabel = r.projectName ? ` [${r.projectName}]` : "";
2401
- const boostLabel = r.boosts.length > 0 ? ` (${r.boosts.join(", ")})` : "";
2402
- return `${i + 1}. **${r.title}** (${r.category})${projectLabel}\n scope: ${r.scope} | score: ${r.score.toFixed(4)}${boostLabel}\n ${r.snippet}`;
2403
- });
2404
- const contextNote = projectId ? `Context: project ${projectId}` : "Context: no project detected";
2405
- return {
2406
- content: [{ type: "text", text: `${contextNote}\n\n${lines.join("\n\n")}` }],
2407
- };
2408
2524
  });
2409
2525
  // ─── Tool: gnosys_detect_ambiguity ──────────────────────────────────────
2410
2526
  regTool("gnosys_detect_ambiguity", "Check if a query matches memories in multiple projects. Use before write operations to confirm the target project when ambiguity exists.", {
@@ -2500,7 +2616,8 @@ regTool("gnosys_remote_status", "Check the status of remote sync (multi-machine)
2500
2616
  if (!localDb.isAvailable()) {
2501
2617
  return { content: [{ type: "text", text: "Local DB not available." }], isError: true };
2502
2618
  }
2503
- const remotePath = localDb.getMeta("remote_path");
2619
+ const { getConfiguredRemotePath } = await import("./lib/remote.js");
2620
+ const remotePath = getConfiguredRemotePath(localDb);
2504
2621
  if (!remotePath) {
2505
2622
  return {
2506
2623
  content: [{
@@ -2509,6 +2626,14 @@ regTool("gnosys_remote_status", "Check the status of remote sync (multi-machine)
2509
2626
  }],
2510
2627
  };
2511
2628
  }
2629
+ const { readMachineConfig } = await import("./lib/machineConfig.js");
2630
+ if (readMachineConfig()?.remote.role) {
2631
+ const { getV13SyncStatus } = await import("./lib/syncClient.js");
2632
+ const v13 = getV13SyncStatus(localDb);
2633
+ return {
2634
+ content: [{ type: "text", text: JSON.stringify(v13, null, 2) }],
2635
+ };
2636
+ }
2512
2637
  const { RemoteSync } = await import("./lib/remote.js");
2513
2638
  const sync = new RemoteSync(localDb, remotePath);
2514
2639
  const status = await sync.getStatus();
@@ -2887,11 +3012,10 @@ async function initHeavyDeps() {
2887
3012
  const embCount = embeddings.hasEmbeddings() ? embeddings.count() : 0;
2888
3013
  console.error(`Hybrid search: ${embCount > 0 ? `ready (${embCount} embeddings)` : "available (run gnosys_reindex to build embeddings)"}`);
2889
3014
  console.error(`Ask engine: ${askEngine.isLLMAvailable ? `ready (${askEngine.providerName}/${askEngine.modelName})` : "disabled (configure LLM provider)"}`);
2890
- // Dream mode (only constructed if enabled; designation gate inside start()).
3015
+ // Dream mode scheduling is machine-level now (`gnosys dream run --scheduled`
3016
+ // via launchd). MCP may still run manual dream tool calls, but it no longer
3017
+ // owns an idle timer per connection.
2891
3018
  if (gnosysDb && config.dream?.enabled) {
2892
- const { GnosysDreamEngine, DreamScheduler } = await import("./lib/dream.js");
2893
- const dreamEngine = new GnosysDreamEngine(gnosysDb, config, config.dream);
2894
- dreamScheduler = new DreamScheduler(dreamEngine, config.dream);
2895
3019
  // Layer 3: probe the dream provider if this machine is the dream node.
2896
3020
  try {
2897
3021
  const designated = gnosysDb.getDreamMachineId();
@@ -2933,9 +3057,8 @@ async function initHeavyDeps() {
2933
3057
  }
2934
3058
  }
2935
3059
  catch {
2936
- // Probe failed — non-fatal. Continue with scheduler start.
3060
+ // Probe failed — non-fatal.
2937
3061
  }
2938
- dreamScheduler.start();
2939
3062
  const designated = gnosysDb.getDreamMachineId();
2940
3063
  const localId = gnosysDb.getMeta("machine_id");
2941
3064
  if (!designated) {
@@ -2945,7 +3068,7 @@ async function initHeavyDeps() {
2945
3068
  console.error(`Dream Mode: enabled — designated to '${designated}'. This machine (${localId || "?"}) will not dream.`);
2946
3069
  }
2947
3070
  else {
2948
- console.error(`Dream Mode: enabled on this machine (idle ${config.dream.idleMinutes}min, max ${config.dream.maxRuntimeMinutes}min)`);
3071
+ console.error(`Dream Mode: enabled on this machine (scheduled outside MCP via launchd, max ${config.dream.maxRuntimeMinutes}min)`);
2949
3072
  }
2950
3073
  }
2951
3074
  else {
@@ -2985,6 +3108,12 @@ export async function startMcpServer() {
2985
3108
  console.error("Gnosys MCP server starting.");
2986
3109
  console.error("Active stores:");
2987
3110
  console.error(resolver.getSummary());
3111
+ // v13: background ingest sweep on master-role MCP startup (non-blocking).
3112
+ void import("./lib/syncIngestStartup.js")
3113
+ .then(({ maybeRunStartupIngestSweep }) => maybeRunStartupIngestSweep())
3114
+ .catch((err) => {
3115
+ console.error(`[sync] Startup ingest sweep failed: ${err instanceof Error ? err.message : err}`);
3116
+ });
2988
3117
  // Initialize search from the first writable store. Everything in this
2989
3118
  // block is FAST — opening the search index + tag registry + loading
2990
3119
  // gnosys.json. The slow stuff (LLM providers, transformers embeddings,