gnosys 5.7.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.
- package/README.md +15 -2
- package/dist/cli.js +188 -108
- package/dist/cli.js.map +1 -1
- package/dist/index.js +96 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/db.d.ts +20 -0
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +36 -12
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/heartbeat.d.ts +31 -0
- package/dist/lib/heartbeat.d.ts.map +1 -0
- package/dist/lib/heartbeat.js +91 -0
- package/dist/lib/heartbeat.js.map +1 -0
- package/dist/lib/idFormat.d.ts +41 -0
- package/dist/lib/idFormat.d.ts.map +1 -0
- package/dist/lib/idFormat.js +66 -0
- package/dist/lib/idFormat.js.map +1 -0
- package/dist/lib/progress.d.ts +54 -0
- package/dist/lib/progress.d.ts.map +1 -0
- package/dist/lib/progress.js +92 -0
- package/dist/lib/progress.js.map +1 -0
- package/dist/lib/remote.d.ts +14 -1
- package/dist/lib/remote.d.ts.map +1 -1
- package/dist/lib/remote.js +75 -28
- package/dist/lib/remote.js.map +1 -1
- package/dist/lib/upgrade.d.ts +38 -0
- package/dist/lib/upgrade.d.ts.map +1 -0
- package/dist/lib/upgrade.js +61 -0
- package/dist/lib/upgrade.js.map +1 -0
- 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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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();
|