gnosys 5.4.0 → 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.
- package/README.md +80 -8
- package/dist/cli.js +70 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.js +136 -97
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +0 -5
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +71 -9
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/dashboard.d.ts.map +1 -1
- package/dist/lib/dashboard.js +137 -94
- package/dist/lib/dashboard.js.map +1 -1
- package/dist/lib/db.d.ts +48 -3
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +144 -17
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/ingest.d.ts.map +1 -1
- package/dist/lib/ingest.js +24 -4
- package/dist/lib/ingest.js.map +1 -1
- package/dist/lib/lock.d.ts +5 -0
- package/dist/lib/lock.d.ts.map +1 -1
- package/dist/lib/lock.js +9 -0
- package/dist/lib/lock.js.map +1 -1
- package/dist/lib/remote.d.ts +15 -0
- package/dist/lib/remote.d.ts.map +1 -1
- package/dist/lib/remote.js +85 -0
- package/dist/lib/remote.js.map +1 -1
- package/dist/lib/store.d.ts +2 -0
- package/dist/lib/store.d.ts.map +1 -1
- package/dist/lib/store.js +4 -11
- package/dist/lib/store.js.map +1 -1
- 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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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: [
|
|
518
|
-
|
|
558
|
+
content: [
|
|
559
|
+
{
|
|
560
|
+
type: "text",
|
|
561
|
+
text: `Memory added to [${writeTarget.label}]: **${title}**\nID: ${id}`,
|
|
562
|
+
},
|
|
563
|
+
],
|
|
519
564
|
};
|
|
520
565
|
}
|
|
521
|
-
|
|
566
|
+
catch (err) {
|
|
522
567
|
return {
|
|
523
|
-
content: [{ type: "text", text: "
|
|
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
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
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
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
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
|
-
|
|
2309
|
-
|
|
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
|
-
|
|
2312
|
-
|
|
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
|
-
|
|
2327
|
-
|
|
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
|
-
|
|
2330
|
-
|
|
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
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
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
|
-
|
|
2353
|
-
|
|
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.", {
|