gnosys 5.3.3 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +80 -8
  2. package/dist/cli.js +225 -46
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +136 -97
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/config.d.ts +0 -5
  7. package/dist/lib/config.d.ts.map +1 -1
  8. package/dist/lib/config.js +71 -9
  9. package/dist/lib/config.js.map +1 -1
  10. package/dist/lib/dashboard.d.ts.map +1 -1
  11. package/dist/lib/dashboard.js +137 -94
  12. package/dist/lib/dashboard.js.map +1 -1
  13. package/dist/lib/db.d.ts +54 -6
  14. package/dist/lib/db.d.ts.map +1 -1
  15. package/dist/lib/db.js +157 -25
  16. package/dist/lib/db.js.map +1 -1
  17. package/dist/lib/ingest.d.ts.map +1 -1
  18. package/dist/lib/ingest.js +24 -4
  19. package/dist/lib/ingest.js.map +1 -1
  20. package/dist/lib/lock.d.ts +5 -0
  21. package/dist/lib/lock.d.ts.map +1 -1
  22. package/dist/lib/lock.js +9 -0
  23. package/dist/lib/lock.js.map +1 -1
  24. package/dist/lib/modelValidation.d.ts +22 -0
  25. package/dist/lib/modelValidation.d.ts.map +1 -0
  26. package/dist/lib/modelValidation.js +157 -0
  27. package/dist/lib/modelValidation.js.map +1 -0
  28. package/dist/lib/paths.d.ts +32 -0
  29. package/dist/lib/paths.d.ts.map +1 -0
  30. package/dist/lib/paths.js +44 -0
  31. package/dist/lib/paths.js.map +1 -0
  32. package/dist/lib/remote.d.ts +15 -0
  33. package/dist/lib/remote.d.ts.map +1 -1
  34. package/dist/lib/remote.js +85 -0
  35. package/dist/lib/remote.js.map +1 -1
  36. package/dist/lib/remoteWizard.d.ts +2 -1
  37. package/dist/lib/remoteWizard.d.ts.map +1 -1
  38. package/dist/lib/remoteWizard.js +6 -3
  39. package/dist/lib/remoteWizard.js.map +1 -1
  40. package/dist/lib/setup.d.ts +25 -0
  41. package/dist/lib/setup.d.ts.map +1 -1
  42. package/dist/lib/setup.js +459 -25
  43. package/dist/lib/setup.js.map +1 -1
  44. package/dist/lib/store.d.ts +2 -0
  45. package/dist/lib/store.d.ts.map +1 -1
  46. package/dist/lib/store.js +4 -11
  47. package/dist/lib/store.js.map +1 -1
  48. package/dist/postinstall.js +2 -2
  49. package/dist/postinstall.js.map +1 -1
  50. package/dist/sandbox/helper-template.d.ts.map +1 -1
  51. package/dist/sandbox/helper-template.js +8 -2
  52. package/dist/sandbox/helper-template.js.map +1 -1
  53. package/dist/sandbox/server.d.ts.map +1 -1
  54. package/dist/sandbox/server.js +2 -2
  55. package/dist/sandbox/server.js.map +1 -1
  56. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -62,6 +62,17 @@ const server = new McpServer({
62
62
  name: "gnosys",
63
63
  version: "2.0.0",
64
64
  });
65
+ /**
66
+ * v5.4.1: Format MCP errors. Detects DB corruption and replaces the raw
67
+ * "database disk image is malformed" with actionable recovery instructions.
68
+ * Use this in catch blocks instead of inlining error.message.
69
+ */
70
+ function formatMcpError(action, err) {
71
+ if (GnosysDB.isCorruptionError(err)) {
72
+ return `Error ${action}: ${err instanceof Error ? err.message : String(err)}\n\n${GnosysDB.corruptionRecoveryInstructions()}`;
73
+ }
74
+ return `Error ${action}: ${err instanceof Error ? err.message : String(err)}`;
75
+ }
65
76
  // These are initialized in main() after resolver runs
66
77
  let search = null;
67
78
  let tagRegistry = null;
@@ -480,12 +491,7 @@ server.tool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structu
480
491
  }
481
492
  catch (err) {
482
493
  return {
483
- content: [
484
- {
485
- type: "text",
486
- text: `Error adding memory: ${err instanceof Error ? err.message : String(err)}`,
487
- },
488
- ],
494
+ content: [{ type: "text", text: formatMcpError("adding memory", err) }],
489
495
  isError: true,
490
496
  };
491
497
  }
@@ -510,51 +516,59 @@ server.tool("gnosys_add_structured", "Add a memory with structured input (no LLM
510
516
  confidence: z.number().min(0).max(1).optional(),
511
517
  projectRoot: projectRootParam,
512
518
  }, async ({ title, category, tags, relevance, content, store: targetStore, author, authority, confidence, projectRoot }) => {
513
- const ctx = await resolveToolContext(projectRoot);
514
- const writeTarget = ctx.resolver.getWriteTarget(targetStore || undefined);
515
- if (!writeTarget) {
519
+ try {
520
+ const ctx = await resolveToolContext(projectRoot);
521
+ const writeTarget = ctx.resolver.getWriteTarget(targetStore || undefined);
522
+ if (!writeTarget) {
523
+ return {
524
+ content: [{ type: "text", text: "No writable store found." }],
525
+ isError: true,
526
+ };
527
+ }
528
+ if (!ctx.centralDb?.isAvailable()) {
529
+ return {
530
+ content: [{ type: "text", text: "Database not available. Cannot write memory." }],
531
+ isError: true,
532
+ };
533
+ }
534
+ const id = ctx.centralDb.getNextId(category, ctx.projectId ?? undefined);
535
+ const today = new Date().toISOString().split("T")[0];
536
+ const frontmatter = {
537
+ id,
538
+ title,
539
+ category,
540
+ tags: tags,
541
+ relevance: relevance || "",
542
+ author: author || "ai",
543
+ authority: authority || "observed",
544
+ confidence: confidence || 0.8,
545
+ created: today,
546
+ modified: today,
547
+ last_reviewed: today,
548
+ status: "active",
549
+ supersedes: null,
550
+ };
551
+ const fullContent = `# ${title}\n\n${content}`;
552
+ // Write to DB only (SQLite is sole source of truth)
553
+ syncMemoryToDb(ctx.centralDb, frontmatter, fullContent, undefined, ctx.projectId, "project");
554
+ auditToDb(ctx.centralDb, "write", id, { tool: "gnosys_add_structured", category });
555
+ if (ctx.search)
556
+ await reindexAllStores();
516
557
  return {
517
- content: [{ type: "text", text: "No writable store found." }],
518
- isError: true,
558
+ content: [
559
+ {
560
+ type: "text",
561
+ text: `Memory added to [${writeTarget.label}]: **${title}**\nID: ${id}`,
562
+ },
563
+ ],
519
564
  };
520
565
  }
521
- if (!ctx.centralDb?.isAvailable()) {
566
+ catch (err) {
522
567
  return {
523
- content: [{ type: "text", text: "Database not available. Cannot write memory." }],
568
+ content: [{ type: "text", text: formatMcpError("adding structured memory", err) }],
524
569
  isError: true,
525
570
  };
526
571
  }
527
- const id = ctx.centralDb.getNextId(category, ctx.projectId ?? undefined);
528
- const today = new Date().toISOString().split("T")[0];
529
- const frontmatter = {
530
- id,
531
- title,
532
- category,
533
- tags: tags,
534
- relevance: relevance || "",
535
- author: author || "ai",
536
- authority: authority || "observed",
537
- confidence: confidence || 0.8,
538
- created: today,
539
- modified: today,
540
- last_reviewed: today,
541
- status: "active",
542
- supersedes: null,
543
- };
544
- const fullContent = `# ${title}\n\n${content}`;
545
- // Write to DB only (SQLite is sole source of truth)
546
- syncMemoryToDb(ctx.centralDb, frontmatter, fullContent, undefined, ctx.projectId, "project");
547
- auditToDb(ctx.centralDb, "write", id, { tool: "gnosys_add_structured", category });
548
- if (ctx.search)
549
- await reindexAllStores();
550
- return {
551
- content: [
552
- {
553
- type: "text",
554
- text: `Memory added to [${writeTarget.label}]: **${title}**\nID: ${id}`,
555
- },
556
- ],
557
- };
558
572
  });
559
573
  // ─── Tool: gnosys_tags ───────────────────────────────────────────────────
560
574
  server.tool("gnosys_tags", "List all tags in the registry, grouped by category.", { projectRoot: projectRootParam }, async ({ projectRoot }) => {
@@ -2282,81 +2296,106 @@ server.tool("gnosys_portfolio", "Portfolio dashboard — shows all registered pr
2282
2296
  });
2283
2297
  // ─── Remote sync tools (v5.3.0) ─────────────────────────────────────────
2284
2298
  server.tool("gnosys_remote_status", "Check the status of remote sync (multi-machine). Returns pending pushes, pulls, conflicts, and reachability. Agents should surface this to the user when there are pending changes or conflicts.", {}, async () => {
2285
- if (!centralDb?.isAvailable()) {
2286
- return { content: [{ type: "text", text: "Central DB not available." }], isError: true };
2287
- }
2288
- const remotePath = centralDb.getMeta("remote_path");
2289
- if (!remotePath) {
2299
+ // Sync operations need explicit local DB access (not auto-routed remote).
2300
+ const localDb = GnosysDB.openLocal();
2301
+ try {
2302
+ if (!localDb.isAvailable()) {
2303
+ return { content: [{ type: "text", text: "Local DB not available." }], isError: true };
2304
+ }
2305
+ const remotePath = localDb.getMeta("remote_path");
2306
+ if (!remotePath) {
2307
+ return {
2308
+ content: [{
2309
+ type: "text",
2310
+ text: JSON.stringify({ configured: false, message: "Remote sync not configured." }, null, 2),
2311
+ }],
2312
+ };
2313
+ }
2314
+ const { RemoteSync } = await import("./lib/remote.js");
2315
+ const sync = new RemoteSync(localDb, remotePath);
2316
+ const status = await sync.getStatus();
2317
+ sync.closeRemote();
2290
2318
  return {
2291
- content: [{
2292
- type: "text",
2293
- text: JSON.stringify({ configured: false, message: "Remote sync not configured." }, null, 2),
2294
- }],
2319
+ content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
2295
2320
  };
2296
2321
  }
2297
- const { RemoteSync } = await import("./lib/remote.js");
2298
- const sync = new RemoteSync(centralDb, remotePath);
2299
- const status = await sync.getStatus();
2300
- sync.closeRemote();
2301
- return {
2302
- content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
2303
- };
2322
+ finally {
2323
+ localDb.close();
2324
+ }
2304
2325
  });
2305
2326
  server.tool("gnosys_remote_push", "Push local memory changes to the remote (NAS) database. Uses skip-and-flag for conflicts by default. Call this when the user has approved pushing local changes.", {
2306
2327
  newerWins: z.boolean().optional().describe("Auto-resolve conflicts by taking the newer version"),
2307
2328
  }, async ({ newerWins }) => {
2308
- if (!centralDb?.isAvailable()) {
2309
- return { content: [{ type: "text", text: "Central DB not available." }], isError: true };
2329
+ const localDb = GnosysDB.openLocal();
2330
+ try {
2331
+ if (!localDb.isAvailable()) {
2332
+ return { content: [{ type: "text", text: "Local DB not available." }], isError: true };
2333
+ }
2334
+ const remotePath = localDb.getMeta("remote_path");
2335
+ if (!remotePath) {
2336
+ return { content: [{ type: "text", text: "Remote not configured. Run 'gnosys remote configure'." }], isError: true };
2337
+ }
2338
+ const { RemoteSync } = await import("./lib/remote.js");
2339
+ const sync = new RemoteSync(localDb, remotePath);
2340
+ const result = await sync.push({ strategy: newerWins ? "newer-wins" : "skip-and-flag" });
2341
+ sync.closeRemote();
2342
+ return {
2343
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2344
+ };
2310
2345
  }
2311
- const remotePath = centralDb.getMeta("remote_path");
2312
- if (!remotePath) {
2313
- return { content: [{ type: "text", text: "Remote not configured. Run 'gnosys remote configure'." }], isError: true };
2346
+ finally {
2347
+ localDb.close();
2314
2348
  }
2315
- const { RemoteSync } = await import("./lib/remote.js");
2316
- const sync = new RemoteSync(centralDb, remotePath);
2317
- const result = await sync.push({ strategy: newerWins ? "newer-wins" : "skip-and-flag" });
2318
- sync.closeRemote();
2319
- return {
2320
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2321
- };
2322
2349
  });
2323
2350
  server.tool("gnosys_remote_pull", "Pull remote memory changes to the local database. Uses skip-and-flag for conflicts by default. Call this when the user wants the latest from the remote.", {
2324
2351
  newerWins: z.boolean().optional().describe("Auto-resolve conflicts by taking the newer version"),
2325
2352
  }, async ({ newerWins }) => {
2326
- if (!centralDb?.isAvailable()) {
2327
- return { content: [{ type: "text", text: "Central DB not available." }], isError: true };
2353
+ const localDb = GnosysDB.openLocal();
2354
+ try {
2355
+ if (!localDb.isAvailable()) {
2356
+ return { content: [{ type: "text", text: "Local DB not available." }], isError: true };
2357
+ }
2358
+ const remotePath = localDb.getMeta("remote_path");
2359
+ if (!remotePath) {
2360
+ return { content: [{ type: "text", text: "Remote not configured." }], isError: true };
2361
+ }
2362
+ const { RemoteSync } = await import("./lib/remote.js");
2363
+ const sync = new RemoteSync(localDb, remotePath);
2364
+ const result = await sync.pull({ strategy: newerWins ? "newer-wins" : "skip-and-flag" });
2365
+ sync.closeRemote();
2366
+ return {
2367
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2368
+ };
2328
2369
  }
2329
- const remotePath = centralDb.getMeta("remote_path");
2330
- if (!remotePath) {
2331
- return { content: [{ type: "text", text: "Remote not configured." }], isError: true };
2370
+ finally {
2371
+ localDb.close();
2332
2372
  }
2333
- const { RemoteSync } = await import("./lib/remote.js");
2334
- const sync = new RemoteSync(centralDb, remotePath);
2335
- const result = await sync.pull({ strategy: newerWins ? "newer-wins" : "skip-and-flag" });
2336
- sync.closeRemote();
2337
- return {
2338
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
2339
- };
2340
2373
  });
2341
2374
  server.tool("gnosys_remote_resolve", "Resolve a sync conflict by choosing which version to keep. Use after gnosys_remote_status reveals conflicts. The agent should present the local and remote versions to the user and call this with their choice.", {
2342
2375
  memoryId: z.string().describe("Memory ID with the conflict"),
2343
2376
  choice: z.enum(["local", "remote"]).describe("Which version to keep"),
2344
2377
  }, async ({ memoryId, choice }) => {
2345
- if (!centralDb?.isAvailable()) {
2346
- return { content: [{ type: "text", text: "Central DB not available." }], isError: true };
2347
- }
2348
- const remotePath = centralDb.getMeta("remote_path");
2349
- if (!remotePath) {
2350
- return { content: [{ type: "text", text: "Remote not configured." }], isError: true };
2378
+ const localDb = GnosysDB.openLocal();
2379
+ try {
2380
+ if (!localDb.isAvailable()) {
2381
+ return { content: [{ type: "text", text: "Local DB not available." }], isError: true };
2382
+ }
2383
+ const remotePath = localDb.getMeta("remote_path");
2384
+ if (!remotePath) {
2385
+ return { content: [{ type: "text", text: "Remote not configured." }], isError: true };
2386
+ }
2387
+ const { RemoteSync } = await import("./lib/remote.js");
2388
+ const sync = new RemoteSync(localDb, remotePath);
2389
+ const result = await sync.resolve(memoryId, choice);
2390
+ sync.closeRemote();
2391
+ if (result.ok) {
2392
+ return { content: [{ type: "text", text: `Resolved ${memoryId}: kept ${choice} version.` }] };
2393
+ }
2394
+ return { content: [{ type: "text", text: `Failed to resolve: ${result.error}` }], isError: true };
2351
2395
  }
2352
- const { RemoteSync } = await import("./lib/remote.js");
2353
- const sync = new RemoteSync(centralDb, remotePath);
2354
- const result = await sync.resolve(memoryId, choice);
2355
- sync.closeRemote();
2356
- if (result.ok) {
2357
- return { content: [{ type: "text", text: `Resolved ${memoryId}: kept ${choice} version.` }] };
2396
+ finally {
2397
+ localDb.close();
2358
2398
  }
2359
- return { content: [{ type: "text", text: `Failed to resolve: ${result.error}` }], isError: true };
2360
2399
  });
2361
2400
  // ─── Tool: gnosys_update_status ─────────────────────────────────────────
2362
2401
  server.tool("gnosys_update_status", "Get the prompt/template for writing a dashboard-compatible status memory for this project. Returns instructions for creating a landscape memory with the correct heading format so the portfolio dashboard can parse it. Run this, then follow the instructions to analyze and write the status.", {