gnosys 5.2.7 → 5.2.10

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/dist/index.js CHANGED
@@ -53,6 +53,7 @@ import { createProjectIdentity, readProjectIdentity } from "./lib/projectIdentit
53
53
  import { setPreference, getPreference, getAllPreferences, deletePreference } from "./lib/preferences.js";
54
54
  import { syncRules } from "./lib/rulesGen.js";
55
55
  import { federatedSearch, detectAmbiguity, generateBriefing, generateAllBriefings, getWorkingSet, formatWorkingSet, detectCurrentProject } from "./lib/federated.js";
56
+ import { generatePortfolio, formatPortfolioCompact, formatPortfolioMarkdown, generateStatusPrompt } from "./lib/portfolio.js";
56
57
  // Initialize resolver (discovers all layered stores)
57
58
  const resolver = new GnosysResolver();
58
59
  let config = DEFAULT_CONFIG;
@@ -815,27 +816,37 @@ server.tool("gnosys_update", "Update an existing memory's frontmatter and/or con
815
816
  projectRoot: projectRootParam,
816
817
  }, async ({ path: memPath, title, tags, status, confidence, relevance, supersedes, superseded_by, content: newContent, projectRoot, }) => {
817
818
  const ctx = await resolveToolContext(projectRoot);
818
- const memory = await ctx.resolver.readMemory(memPath);
819
- if (!memory) {
819
+ if (!ctx.centralDb?.isAvailable()) {
820
820
  return {
821
- content: [{ type: "text", text: `Memory not found: ${memPath}` }],
821
+ content: [{ type: "text", text: "Database not available. Cannot update memory." }],
822
822
  isError: true,
823
823
  };
824
824
  }
825
- // Find the source store and check if writable
826
- const sourceStore = ctx.resolver
827
- .getStores()
828
- .find((s) => s.label === memory.sourceLabel);
829
- if (!sourceStore?.writable) {
830
- return {
831
- content: [
832
- {
833
- type: "text",
834
- text: `Cannot update: store [${memory.sourceLabel}] is read-only.`,
835
- },
836
- ],
837
- isError: true,
838
- };
825
+ // DB-first lookup: resolve memory ID from central DB (mirrors gnosys_read pattern)
826
+ let memoryId;
827
+ let currentTitle;
828
+ const dbMem = ctx.centralDb.getMemory(memPath);
829
+ if (dbMem) {
830
+ memoryId = dbMem.id;
831
+ currentTitle = dbMem.title;
832
+ }
833
+ else {
834
+ // Fallback to legacy file resolver
835
+ const memory = await ctx.resolver.readMemory(memPath);
836
+ if (!memory) {
837
+ return {
838
+ content: [{ type: "text", text: `Memory not found: ${memPath}` }],
839
+ isError: true,
840
+ };
841
+ }
842
+ if (!memory.frontmatter.id) {
843
+ return {
844
+ content: [{ type: "text", text: `Memory has no ID: ${memPath}` }],
845
+ isError: true,
846
+ };
847
+ }
848
+ memoryId = memory.frontmatter.id;
849
+ currentTitle = memory.frontmatter.title || memPath;
839
850
  }
840
851
  // Build updates object — only include defined fields
841
852
  const updates = {};
@@ -853,20 +864,7 @@ server.tool("gnosys_update", "Update an existing memory's frontmatter and/or con
853
864
  updates.supersedes = supersedes;
854
865
  if (superseded_by !== undefined)
855
866
  updates.superseded_by = superseded_by;
856
- const fullContent = newContent ? `# ${title || memory.frontmatter.title}\n\n${newContent}` : undefined;
857
- const memoryId = memory.frontmatter.id;
858
- if (!memoryId) {
859
- return {
860
- content: [{ type: "text", text: `Memory has no ID: ${memPath}` }],
861
- isError: true,
862
- };
863
- }
864
- if (!ctx.centralDb?.isAvailable()) {
865
- return {
866
- content: [{ type: "text", text: "Database not available. Cannot update memory." }],
867
- isError: true,
868
- };
869
- }
867
+ const fullContent = newContent ? `# ${title || currentTitle}\n\n${newContent}` : undefined;
870
868
  // Write update to DB only (SQLite is sole source of truth)
871
869
  syncUpdateToDb(ctx.centralDb, memoryId, updates, fullContent);
872
870
  auditToDb(ctx.centralDb, "write", memoryId, { tool: "gnosys_update", changed: Object.keys(updates) });
@@ -880,7 +878,7 @@ server.tool("gnosys_update", "Update an existing memory's frontmatter and/or con
880
878
  const changedFields = Object.keys(updates);
881
879
  if (newContent)
882
880
  changedFields.push("content");
883
- const updatedTitle = title || memory.frontmatter.title;
881
+ const updatedTitle = title || currentTitle;
884
882
  return {
885
883
  content: [
886
884
  {
@@ -1101,6 +1099,30 @@ server.tool("gnosys_history", "View version history for a memory. Shows what cha
1101
1099
  projectRoot: projectRootParam,
1102
1100
  }, async ({ path: memPath, limit, projectRoot }) => {
1103
1101
  const ctx = await resolveToolContext(projectRoot);
1102
+ // DB-first: resolve memory ID and show timestamps
1103
+ if (ctx.centralDb?.isAvailable()) {
1104
+ const dbMem = ctx.centralDb.getMemory(memPath);
1105
+ if (dbMem) {
1106
+ // Query audit_log for this memory
1107
+ const audits = ctx.centralDb.getAuditLog(dbMem.id, limit || 20);
1108
+ if (audits.length > 0) {
1109
+ const lines = audits.map((e) => `- ${e.timestamp.split("T")[0]} — ${e.operation}${e.details ? ` (${e.details})` : ""}`);
1110
+ return {
1111
+ content: [{
1112
+ type: "text",
1113
+ text: `History for **${dbMem.title}** (${dbMem.id}, ${audits.length} entries):\n\nCreated: ${dbMem.created}\nModified: ${dbMem.modified}\n\n${lines.join("\n")}`,
1114
+ }],
1115
+ };
1116
+ }
1117
+ return {
1118
+ content: [{
1119
+ type: "text",
1120
+ text: `Memory found: **${dbMem.title}** (${dbMem.id})\nCreated: ${dbMem.created}\nModified: ${dbMem.modified}\nNo audit history recorded.`,
1121
+ }],
1122
+ };
1123
+ }
1124
+ }
1125
+ // Legacy file-based fallback
1104
1126
  const memory = await ctx.resolver.readMemory(memPath);
1105
1127
  if (!memory) {
1106
1128
  return { content: [{ type: "text", text: `Memory not found: ${memPath}` }], isError: true };
@@ -1260,6 +1282,34 @@ server.tool("gnosys_links", "Show wikilinks for a specific memory — outgoing [
1260
1282
  projectRoot: projectRootParam,
1261
1283
  }, async ({ path: memPath, projectRoot }) => {
1262
1284
  const ctx = await resolveToolContext(projectRoot);
1285
+ // DB-first: resolve memory ID and check relationships table
1286
+ if (ctx.centralDb?.isAvailable()) {
1287
+ const dbMem = ctx.centralDb.getMemory(memPath);
1288
+ if (dbMem) {
1289
+ const outRels = ctx.centralDb.getRelationshipsFrom(dbMem.id);
1290
+ const inRels = ctx.centralDb.getRelationshipsTo(dbMem.id);
1291
+ if (outRels.length > 0 || inRels.length > 0) {
1292
+ const parts = [`Links for **${dbMem.title}** (${dbMem.id}):\n`];
1293
+ if (outRels.length > 0) {
1294
+ parts.push(`Outgoing (${outRels.length}):`);
1295
+ for (const r of outRels) {
1296
+ const target = ctx.centralDb.getMemory(r.target_id);
1297
+ parts.push(` → ${r.rel_type} → ${target ? target.title : r.target_id}`);
1298
+ }
1299
+ }
1300
+ if (inRels.length > 0) {
1301
+ parts.push(`\nIncoming (${inRels.length}):`);
1302
+ for (const r of inRels) {
1303
+ const source = ctx.centralDb.getMemory(r.source_id);
1304
+ parts.push(` ← ${r.rel_type} ← ${source ? source.title : r.source_id}`);
1305
+ }
1306
+ }
1307
+ return { content: [{ type: "text", text: parts.join("\n") }] };
1308
+ }
1309
+ return { content: [{ type: "text", text: `Memory found: **${dbMem.title}** (${dbMem.id})\nNo links or relationships recorded.` }] };
1310
+ }
1311
+ }
1312
+ // Legacy file-based fallback
1263
1313
  const memory = await ctx.resolver.readMemory(memPath);
1264
1314
  if (!memory) {
1265
1315
  return { content: [{ type: "text", text: `Memory not found: ${memPath}` }], isError: true };
@@ -2221,6 +2271,40 @@ server.tool("gnosys_briefing", "Generate a project briefing — a summary of mem
2221
2271
  ].join("\n");
2222
2272
  return { content: [{ type: "text", text }] };
2223
2273
  });
2274
+ // ─── Tool: gnosys_portfolio ─────────────────────────────────────────────
2275
+ server.tool("gnosys_portfolio", "Portfolio dashboard — shows all registered projects with memory counts, categories, status snapshots, roadmap items, and recent activity. Use for cross-project status overview.", {
2276
+ format: z.enum(["compact", "full"]).optional().describe("Output format: compact (default) or full markdown"),
2277
+ }, async ({ format }) => {
2278
+ if (!centralDb?.isAvailable()) {
2279
+ return { content: [{ type: "text", text: "Central DB not available." }], isError: true };
2280
+ }
2281
+ const report = generatePortfolio(centralDb);
2282
+ if (report.projects.length === 0) {
2283
+ return { content: [{ type: "text", text: "No projects with active memories found." }] };
2284
+ }
2285
+ const text = format === "full"
2286
+ ? formatPortfolioMarkdown(report)
2287
+ : formatPortfolioCompact(report);
2288
+ return { content: [{ type: "text", text }] };
2289
+ });
2290
+ // ─── Tool: gnosys_update_status ─────────────────────────────────────────
2291
+ 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.", {
2292
+ projectRoot: z.string().optional().describe("Project root for auto-detection"),
2293
+ }, async ({ projectRoot }) => {
2294
+ if (!centralDb?.isAvailable()) {
2295
+ return { content: [{ type: "text", text: "Central DB not available." }], isError: true };
2296
+ }
2297
+ const pid = await detectCurrentProject(centralDb, projectRoot || undefined);
2298
+ if (!pid) {
2299
+ return { content: [{ type: "text", text: "No project detected from current directory." }], isError: true };
2300
+ }
2301
+ const project = centralDb.getProject(pid);
2302
+ if (!project) {
2303
+ return { content: [{ type: "text", text: `Project not found: ${pid}` }], isError: true };
2304
+ }
2305
+ const prompt = generateStatusPrompt(project.name, project.working_directory);
2306
+ return { content: [{ type: "text", text: prompt }] };
2307
+ });
2224
2308
  // ─── Tool: gnosys_working_set ───────────────────────────────────────────
2225
2309
  server.tool("gnosys_working_set", "Get the implicit working set — recently modified memories for the current project. These represent the active context and get boosted in federated search.", {
2226
2310
  projectRoot: z.string().optional().describe("Project root for auto-detection"),