gnosys 5.6.0 → 5.7.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 (72) hide show
  1. package/README.md +15 -2
  2. package/dist/cli.js +879 -464
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +96 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/audit.d.ts +13 -0
  7. package/dist/lib/audit.d.ts.map +1 -1
  8. package/dist/lib/audit.js +42 -0
  9. package/dist/lib/audit.js.map +1 -1
  10. package/dist/lib/chat/llmTurn.d.ts +20 -2
  11. package/dist/lib/chat/llmTurn.d.ts.map +1 -1
  12. package/dist/lib/chat/llmTurn.js +58 -16
  13. package/dist/lib/chat/llmTurn.js.map +1 -1
  14. package/dist/lib/chat/render.d.ts.map +1 -1
  15. package/dist/lib/chat/render.js +18 -0
  16. package/dist/lib/chat/render.js.map +1 -1
  17. package/dist/lib/chat/toolFence.d.ts +54 -0
  18. package/dist/lib/chat/toolFence.d.ts.map +1 -0
  19. package/dist/lib/chat/toolFence.js +90 -0
  20. package/dist/lib/chat/toolFence.js.map +1 -0
  21. package/dist/lib/chat/tools.d.ts +48 -0
  22. package/dist/lib/chat/tools.d.ts.map +1 -0
  23. package/dist/lib/chat/tools.js +338 -0
  24. package/dist/lib/chat/tools.js.map +1 -0
  25. package/dist/lib/db.d.ts +58 -0
  26. package/dist/lib/db.d.ts.map +1 -1
  27. package/dist/lib/db.js +154 -38
  28. package/dist/lib/db.js.map +1 -1
  29. package/dist/lib/heartbeat.d.ts +31 -0
  30. package/dist/lib/heartbeat.d.ts.map +1 -0
  31. package/dist/lib/heartbeat.js +91 -0
  32. package/dist/lib/heartbeat.js.map +1 -0
  33. package/dist/lib/idFormat.d.ts +41 -0
  34. package/dist/lib/idFormat.d.ts.map +1 -0
  35. package/dist/lib/idFormat.js +66 -0
  36. package/dist/lib/idFormat.js.map +1 -0
  37. package/dist/lib/progress.d.ts +54 -0
  38. package/dist/lib/progress.d.ts.map +1 -0
  39. package/dist/lib/progress.js +92 -0
  40. package/dist/lib/progress.js.map +1 -0
  41. package/dist/lib/remote.d.ts +37 -1
  42. package/dist/lib/remote.d.ts.map +1 -1
  43. package/dist/lib/remote.js +163 -28
  44. package/dist/lib/remote.js.map +1 -1
  45. package/dist/lib/remoteWizard.d.ts.map +1 -1
  46. package/dist/lib/remoteWizard.js +13 -17
  47. package/dist/lib/remoteWizard.js.map +1 -1
  48. package/dist/lib/setup/sections/ides.d.ts +20 -0
  49. package/dist/lib/setup/sections/ides.d.ts.map +1 -0
  50. package/dist/lib/setup/sections/ides.js +124 -0
  51. package/dist/lib/setup/sections/ides.js.map +1 -0
  52. package/dist/lib/setup/sections/preferences.d.ts +30 -0
  53. package/dist/lib/setup/sections/preferences.d.ts.map +1 -0
  54. package/dist/lib/setup/sections/preferences.js +128 -0
  55. package/dist/lib/setup/sections/preferences.js.map +1 -0
  56. package/dist/lib/setup/sections/routing.d.ts +21 -0
  57. package/dist/lib/setup/sections/routing.d.ts.map +1 -0
  58. package/dist/lib/setup/sections/routing.js +160 -0
  59. package/dist/lib/setup/sections/routing.js.map +1 -0
  60. package/dist/lib/setup/summary.d.ts +42 -0
  61. package/dist/lib/setup/summary.d.ts.map +1 -0
  62. package/dist/lib/setup/summary.js +206 -0
  63. package/dist/lib/setup/summary.js.map +1 -0
  64. package/dist/lib/timeline.d.ts +7 -0
  65. package/dist/lib/timeline.d.ts.map +1 -1
  66. package/dist/lib/timeline.js +19 -5
  67. package/dist/lib/timeline.js.map +1 -1
  68. package/dist/lib/upgrade.d.ts +38 -0
  69. package/dist/lib/upgrade.d.ts.map +1 -0
  70. package/dist/lib/upgrade.js +61 -0
  71. package/dist/lib/upgrade.js.map +1 -0
  72. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@
11
11
  import dotenv from "dotenv";
12
12
  import path from "path";
13
13
  import { readFileSync } from "fs";
14
+ import { fileURLToPath } from "url";
14
15
  const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
15
16
  try {
16
17
  const envFile = readFileSync(path.join(home, ".config", "gnosys", ".env"), "utf8");
@@ -23,6 +24,19 @@ try {
23
24
  catch {
24
25
  // .env file not found — that's fine, env vars may be set elsewhere
25
26
  }
27
+ // v5.7.1 (#15): read our running version so the upgrade-marker watcher
28
+ // can detect when a newer global install lands and exit for client respawn.
29
+ const __filenameMcp = fileURLToPath(import.meta.url);
30
+ const __dirnameMcp = path.dirname(__filenameMcp);
31
+ const RUNNING_VERSION = (() => {
32
+ try {
33
+ const raw = readFileSync(path.resolve(__dirnameMcp, "..", "package.json"), "utf8");
34
+ return JSON.parse(raw).version || "0.0.0";
35
+ }
36
+ catch {
37
+ return "0.0.0";
38
+ }
39
+ })();
26
40
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
41
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
28
42
  import { z } from "zod";
@@ -73,6 +87,38 @@ function formatMcpError(action, err) {
73
87
  }
74
88
  return `Error ${action}: ${err instanceof Error ? err.message : String(err)}`;
75
89
  }
90
+ /**
91
+ * v5.7.1 (#15): poll `~/.gnosys/last-upgrade-at` and exit when a newer
92
+ * version has been installed. The MCP client (Claude Code, Cursor, etc.)
93
+ * sees the clean exit and respawns the process, which then runs the new
94
+ * global binary.
95
+ *
96
+ * The 10s cadence is a deliberate trade-off: latency is small, the FS
97
+ * check is one stat (~µs), and an upgrade that landed mid-session gets
98
+ * picked up before the next agent turn in almost every case.
99
+ */
100
+ function startUpgradeMarkerWatcher() {
101
+ const check = async () => {
102
+ try {
103
+ const { shouldRestartMcp, readUpgradeMarker } = await import("./lib/upgrade.js");
104
+ if (shouldRestartMcp(RUNNING_VERSION)) {
105
+ const marker = readUpgradeMarker();
106
+ const newVersion = marker?.version || "newer";
107
+ console.error(`gnosys MCP: upgrading from v${RUNNING_VERSION} → v${newVersion} — restarting`);
108
+ // Clean exit so the MCP host respawns us against the upgraded binary.
109
+ process.exit(0);
110
+ }
111
+ }
112
+ catch {
113
+ // Stat failed — non-critical. Try again on the next tick.
114
+ }
115
+ };
116
+ // Immediate boot-time check, then poll.
117
+ void check();
118
+ const timer = setInterval(() => void check(), 10_000);
119
+ // Don't block process exit on this timer.
120
+ timer.unref();
121
+ }
76
122
  // These are initialized in main() after resolver runs
77
123
  let search = null;
78
124
  let tagRegistry = null;
@@ -150,6 +196,38 @@ async function resolveToolContext(projectRoot) {
150
196
  projectId,
151
197
  };
152
198
  }
199
+ /**
200
+ * v5.7.1 (#13): Resolve scope + projectId for a memory write.
201
+ *
202
+ * Previously every write was hard-coded to scope="project" with whatever
203
+ * `ctx.projectId` happened to resolve to — including null. That produced
204
+ * "project-scope, no project" orphans visible cross-project.
205
+ *
206
+ * Now: derive scope from the explicit `store` argument and refuse to
207
+ * create a project-scoped memory when no project identity is reachable,
208
+ * telling the caller exactly how to fix it.
209
+ */
210
+ function resolveWriteScope(ctx, targetStore) {
211
+ const scope = targetStore || "project";
212
+ if (scope === "global" || scope === "personal") {
213
+ return { ok: true, scope, projectId: null };
214
+ }
215
+ if (!ctx.projectId) {
216
+ return {
217
+ ok: false,
218
+ error: [
219
+ "Cannot write a project-scoped memory: no project identity is reachable.",
220
+ "",
221
+ "Pick one:",
222
+ " • Pass projectRoot=<path-to-project> to anchor this memory to that project",
223
+ " • Pass store='global' to write to shared org knowledge",
224
+ " • Pass store='personal' for cross-project personal notes",
225
+ " • Run gnosys_init in the target directory to register it as a project",
226
+ ].join("\n"),
227
+ };
228
+ }
229
+ return { ok: true, scope: "project", projectId: ctx.projectId };
230
+ }
153
231
  // ─── Tool: gnosys_discover ──────────────────────────────────────────────
154
232
  server.tool("gnosys_discover", "Discover relevant memories by describing what you're working on. Searches relevance keyword clouds across all stores. Returns lightweight metadata (title, path, relevance keywords) — NO file contents. Use gnosys_read to load specific memories you need. Call this FIRST when starting a task to find what Gnosys knows.", {
155
233
  query: z
@@ -442,7 +520,12 @@ server.tool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structu
442
520
  isError: true,
443
521
  };
444
522
  }
445
- const id = ctx.centralDb.getNextId(result.category, ctx.projectId ?? undefined);
523
+ // v5.7.1 (#13): determine scope/projectId before writing
524
+ const scopeResult = resolveWriteScope(ctx, targetStore);
525
+ if (!scopeResult.ok) {
526
+ return { content: [{ type: "text", text: scopeResult.error }], isError: true };
527
+ }
528
+ const id = ctx.centralDb.getNextId(result.category, scopeResult.projectId ?? undefined);
446
529
  const today = new Date().toISOString().split("T")[0];
447
530
  const frontmatter = {
448
531
  id,
@@ -461,7 +544,7 @@ server.tool("gnosys_add", "Add a new memory. Accepts raw text — an LLM structu
461
544
  };
462
545
  const content = `# ${result.title}\n\n${result.content}`;
463
546
  // Write to DB only (SQLite is sole source of truth)
464
- syncMemoryToDb(ctx.centralDb, frontmatter, content, undefined, ctx.projectId, "project");
547
+ syncMemoryToDb(ctx.centralDb, frontmatter, content, undefined, scopeResult.projectId, scopeResult.scope);
465
548
  auditToDb(ctx.centralDb, "write", id, { tool: "gnosys_add", category: result.category });
466
549
  // Rebuild search index across all stores
467
550
  if (ctx.search) {
@@ -531,7 +614,12 @@ server.tool("gnosys_add_structured", "Add a memory with structured input (no LLM
531
614
  isError: true,
532
615
  };
533
616
  }
534
- const id = ctx.centralDb.getNextId(category, ctx.projectId ?? undefined);
617
+ // v5.7.1 (#13): determine scope/projectId before writing
618
+ const scopeResult = resolveWriteScope(ctx, targetStore);
619
+ if (!scopeResult.ok) {
620
+ return { content: [{ type: "text", text: scopeResult.error }], isError: true };
621
+ }
622
+ const id = ctx.centralDb.getNextId(category, scopeResult.projectId ?? undefined);
535
623
  const today = new Date().toISOString().split("T")[0];
536
624
  const frontmatter = {
537
625
  id,
@@ -550,7 +638,7 @@ server.tool("gnosys_add_structured", "Add a memory with structured input (no LLM
550
638
  };
551
639
  const fullContent = `# ${title}\n\n${content}`;
552
640
  // Write to DB only (SQLite is sole source of truth)
553
- syncMemoryToDb(ctx.centralDb, frontmatter, fullContent, undefined, ctx.projectId, "project");
641
+ syncMemoryToDb(ctx.centralDb, frontmatter, fullContent, undefined, scopeResult.projectId, scopeResult.scope);
554
642
  auditToDb(ctx.centralDb, "write", id, { tool: "gnosys_add_structured", category });
555
643
  if (ctx.search)
556
644
  await reindexAllStores();
@@ -2644,6 +2732,10 @@ This marks the conversation checkpoint so the next /gnosys-memorize only process
2644
2732
  });
2645
2733
  // ─── Start the server ────────────────────────────────────────────────────
2646
2734
  async function main() {
2735
+ // v5.7.1 (#15): start the upgrade-marker watcher BEFORE anything else.
2736
+ // If `gnosys upgrade` was run on this machine while the MCP was idle,
2737
+ // pick that up immediately instead of serving stale tool handlers.
2738
+ startUpgradeMarkerWatcher();
2647
2739
  // v3.0: Initialize central DB at ~/.gnosys/gnosys.db
2648
2740
  try {
2649
2741
  centralDb = GnosysDB.openCentral();