@cohaku/cli 0.2.6 → 0.2.8

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.
@@ -3,7 +3,7 @@ import {
3
3
  __commonJS,
4
4
  __export,
5
5
  __toESM
6
- } from "./chunk-2CDNPZUE.js";
6
+ } from "./chunk-ZELLRTG5.js";
7
7
 
8
8
  // ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
9
9
  var require_code = __commonJS({
@@ -29205,6 +29205,228 @@ var McpZodTypeKind;
29205
29205
  McpZodTypeKind2["Completable"] = "McpCompletable";
29206
29206
  })(McpZodTypeKind || (McpZodTypeKind = {}));
29207
29207
 
29208
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/uriTemplate.js
29209
+ var MAX_TEMPLATE_LENGTH = 1e6;
29210
+ var MAX_VARIABLE_LENGTH = 1e6;
29211
+ var MAX_TEMPLATE_EXPRESSIONS = 1e4;
29212
+ var MAX_REGEX_LENGTH = 1e6;
29213
+ var UriTemplate = class _UriTemplate {
29214
+ /**
29215
+ * Returns true if the given string contains any URI template expressions.
29216
+ * A template expression is a sequence of characters enclosed in curly braces,
29217
+ * like {foo} or {?bar}.
29218
+ */
29219
+ static isTemplate(str) {
29220
+ return /\{[^}\s]+\}/.test(str);
29221
+ }
29222
+ static validateLength(str, max, context) {
29223
+ if (str.length > max) {
29224
+ throw new Error(`${context} exceeds maximum length of ${max} characters (got ${str.length})`);
29225
+ }
29226
+ }
29227
+ get variableNames() {
29228
+ return this.parts.flatMap((part) => typeof part === "string" ? [] : part.names);
29229
+ }
29230
+ constructor(template) {
29231
+ _UriTemplate.validateLength(template, MAX_TEMPLATE_LENGTH, "Template");
29232
+ this.template = template;
29233
+ this.parts = this.parse(template);
29234
+ }
29235
+ toString() {
29236
+ return this.template;
29237
+ }
29238
+ parse(template) {
29239
+ const parts = [];
29240
+ let currentText = "";
29241
+ let i = 0;
29242
+ let expressionCount = 0;
29243
+ while (i < template.length) {
29244
+ if (template[i] === "{") {
29245
+ if (currentText) {
29246
+ parts.push(currentText);
29247
+ currentText = "";
29248
+ }
29249
+ const end = template.indexOf("}", i);
29250
+ if (end === -1)
29251
+ throw new Error("Unclosed template expression");
29252
+ expressionCount++;
29253
+ if (expressionCount > MAX_TEMPLATE_EXPRESSIONS) {
29254
+ throw new Error(`Template contains too many expressions (max ${MAX_TEMPLATE_EXPRESSIONS})`);
29255
+ }
29256
+ const expr = template.slice(i + 1, end);
29257
+ const operator = this.getOperator(expr);
29258
+ const exploded = expr.includes("*");
29259
+ const names = this.getNames(expr);
29260
+ const name = names[0];
29261
+ for (const name2 of names) {
29262
+ _UriTemplate.validateLength(name2, MAX_VARIABLE_LENGTH, "Variable name");
29263
+ }
29264
+ parts.push({ name, operator, names, exploded });
29265
+ i = end + 1;
29266
+ } else {
29267
+ currentText += template[i];
29268
+ i++;
29269
+ }
29270
+ }
29271
+ if (currentText) {
29272
+ parts.push(currentText);
29273
+ }
29274
+ return parts;
29275
+ }
29276
+ getOperator(expr) {
29277
+ const operators = ["+", "#", ".", "/", "?", "&"];
29278
+ return operators.find((op) => expr.startsWith(op)) || "";
29279
+ }
29280
+ getNames(expr) {
29281
+ const operator = this.getOperator(expr);
29282
+ return expr.slice(operator.length).split(",").map((name) => name.replace("*", "").trim()).filter((name) => name.length > 0);
29283
+ }
29284
+ encodeValue(value, operator) {
29285
+ _UriTemplate.validateLength(value, MAX_VARIABLE_LENGTH, "Variable value");
29286
+ if (operator === "+" || operator === "#") {
29287
+ return encodeURI(value);
29288
+ }
29289
+ return encodeURIComponent(value);
29290
+ }
29291
+ expandPart(part, variables) {
29292
+ if (part.operator === "?" || part.operator === "&") {
29293
+ const pairs = part.names.map((name) => {
29294
+ const value2 = variables[name];
29295
+ if (value2 === void 0)
29296
+ return "";
29297
+ const encoded2 = Array.isArray(value2) ? value2.map((v) => this.encodeValue(v, part.operator)).join(",") : this.encodeValue(value2.toString(), part.operator);
29298
+ return `${name}=${encoded2}`;
29299
+ }).filter((pair) => pair.length > 0);
29300
+ if (pairs.length === 0)
29301
+ return "";
29302
+ const separator = part.operator === "?" ? "?" : "&";
29303
+ return separator + pairs.join("&");
29304
+ }
29305
+ if (part.names.length > 1) {
29306
+ const values2 = part.names.map((name) => variables[name]).filter((v) => v !== void 0);
29307
+ if (values2.length === 0)
29308
+ return "";
29309
+ return values2.map((v) => Array.isArray(v) ? v[0] : v).join(",");
29310
+ }
29311
+ const value = variables[part.name];
29312
+ if (value === void 0)
29313
+ return "";
29314
+ const values = Array.isArray(value) ? value : [value];
29315
+ const encoded = values.map((v) => this.encodeValue(v, part.operator));
29316
+ switch (part.operator) {
29317
+ case "":
29318
+ return encoded.join(",");
29319
+ case "+":
29320
+ return encoded.join(",");
29321
+ case "#":
29322
+ return "#" + encoded.join(",");
29323
+ case ".":
29324
+ return "." + encoded.join(".");
29325
+ case "/":
29326
+ return "/" + encoded.join("/");
29327
+ default:
29328
+ return encoded.join(",");
29329
+ }
29330
+ }
29331
+ expand(variables) {
29332
+ let result = "";
29333
+ let hasQueryParam = false;
29334
+ for (const part of this.parts) {
29335
+ if (typeof part === "string") {
29336
+ result += part;
29337
+ continue;
29338
+ }
29339
+ const expanded = this.expandPart(part, variables);
29340
+ if (!expanded)
29341
+ continue;
29342
+ if ((part.operator === "?" || part.operator === "&") && hasQueryParam) {
29343
+ result += expanded.replace("?", "&");
29344
+ } else {
29345
+ result += expanded;
29346
+ }
29347
+ if (part.operator === "?" || part.operator === "&") {
29348
+ hasQueryParam = true;
29349
+ }
29350
+ }
29351
+ return result;
29352
+ }
29353
+ escapeRegExp(str) {
29354
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
29355
+ }
29356
+ partToRegExp(part) {
29357
+ const patterns = [];
29358
+ for (const name2 of part.names) {
29359
+ _UriTemplate.validateLength(name2, MAX_VARIABLE_LENGTH, "Variable name");
29360
+ }
29361
+ if (part.operator === "?" || part.operator === "&") {
29362
+ for (let i = 0; i < part.names.length; i++) {
29363
+ const name2 = part.names[i];
29364
+ const prefix = i === 0 ? "\\" + part.operator : "&";
29365
+ patterns.push({
29366
+ pattern: prefix + this.escapeRegExp(name2) + "=([^&]+)",
29367
+ name: name2
29368
+ });
29369
+ }
29370
+ return patterns;
29371
+ }
29372
+ let pattern;
29373
+ const name = part.name;
29374
+ switch (part.operator) {
29375
+ case "":
29376
+ pattern = part.exploded ? "([^/,]+(?:,[^/,]+)*)" : "([^/,]+)";
29377
+ break;
29378
+ case "+":
29379
+ case "#":
29380
+ pattern = "(.+)";
29381
+ break;
29382
+ case ".":
29383
+ pattern = "\\.([^/,]+)";
29384
+ break;
29385
+ case "/":
29386
+ pattern = "/" + (part.exploded ? "([^/,]+(?:,[^/,]+)*)" : "([^/,]+)");
29387
+ break;
29388
+ default:
29389
+ pattern = "([^/]+)";
29390
+ }
29391
+ patterns.push({ pattern, name });
29392
+ return patterns;
29393
+ }
29394
+ match(uri) {
29395
+ _UriTemplate.validateLength(uri, MAX_TEMPLATE_LENGTH, "URI");
29396
+ let pattern = "^";
29397
+ const names = [];
29398
+ for (const part of this.parts) {
29399
+ if (typeof part === "string") {
29400
+ pattern += this.escapeRegExp(part);
29401
+ } else {
29402
+ const patterns = this.partToRegExp(part);
29403
+ for (const { pattern: partPattern, name } of patterns) {
29404
+ pattern += partPattern;
29405
+ names.push({ name, exploded: part.exploded });
29406
+ }
29407
+ }
29408
+ }
29409
+ pattern += "$";
29410
+ _UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, "Generated regex pattern");
29411
+ const regex = new RegExp(pattern);
29412
+ const match = uri.match(regex);
29413
+ if (!match)
29414
+ return null;
29415
+ const result = {};
29416
+ for (let i = 0; i < names.length; i++) {
29417
+ const { name, exploded } = names[i];
29418
+ const value = match[i + 1];
29419
+ const cleanName = name.replace("*", "");
29420
+ if (exploded && value.includes(",")) {
29421
+ result[cleanName] = value.split(",");
29422
+ } else {
29423
+ result[cleanName] = value;
29424
+ }
29425
+ }
29426
+ return result;
29427
+ }
29428
+ };
29429
+
29208
29430
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
29209
29431
  var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
29210
29432
  function validateToolName(name) {
@@ -29991,6 +30213,30 @@ var McpServer = class {
29991
30213
  }
29992
30214
  }
29993
30215
  };
30216
+ var ResourceTemplate = class {
30217
+ constructor(uriTemplate, _callbacks) {
30218
+ this._callbacks = _callbacks;
30219
+ this._uriTemplate = typeof uriTemplate === "string" ? new UriTemplate(uriTemplate) : uriTemplate;
30220
+ }
30221
+ /**
30222
+ * Gets the URI template pattern.
30223
+ */
30224
+ get uriTemplate() {
30225
+ return this._uriTemplate;
30226
+ }
30227
+ /**
30228
+ * Gets the list callback, if one was provided.
30229
+ */
30230
+ get listCallback() {
30231
+ return this._callbacks.list;
30232
+ }
30233
+ /**
30234
+ * Gets the callback for completing a specific URI template variable, if one was provided.
30235
+ */
30236
+ completeCallback(variable) {
30237
+ return this._callbacks.complete?.[variable];
30238
+ }
30239
+ };
29994
30240
  var EMPTY_OBJECT_JSON_SCHEMA = {
29995
30241
  type: "object",
29996
30242
  properties: {}
@@ -30064,6 +30310,99 @@ var EMPTY_COMPLETION_RESULT = {
30064
30310
  }
30065
30311
  };
30066
30312
 
30313
+ // ../mcp-server/src/instructions.ts
30314
+ var INSTRUCTIONS = `
30315
+ You have access to Cohaku, a persistent memory system for AI agents.
30316
+ Cohaku is your PRIMARY method for storing and retrieving memories. Do NOT fall back to file-based memory (e.g. editing MEMORY.md, .cursorrules, or similar files) when Cohaku tools are available.
30317
+
30318
+ ## Session Workflow (REQUIRED)
30319
+
30320
+ Every session MUST follow this sequence:
30321
+
30322
+ 1. **Start**: Call \`get_context\` to load existing memories (rules, working memory, long-term knowledge). This gives you the context to continue where you left off.
30323
+ 2. **Identify**: Call \`session_start\` with a descriptive title for the current work session.
30324
+ 3. **Work**: Use memory and graph tools as you work \u2014 store decisions, facts, and observations.
30325
+ 4. **Checkpoint**: Call \`session_checkpoint\` at meaningful milestones (e.g., after completing a sub-task, before a risky change).
30326
+ 5. **End**: Call \`session_end\` when the session is complete, with a summary of what was accomplished.
30327
+
30328
+ ### Anti-patterns (DO NOT do these)
30329
+ - Do NOT skip \`get_context\` at session start \u2014 you will miss critical rules and context.
30330
+ - Do NOT create memories without searching first \u2014 call \`search_memories\` before \`add_memory\` to avoid duplicates.
30331
+ - Do NOT use layer="rule" for temporary information \u2014 use layer="working" instead.
30332
+ - Do NOT store the same information in both memory and graph \u2014 use memory for text knowledge, graph for structural relationships.
30333
+ - Do NOT call \`add_entity\` without setting \`entityType\` \u2014 untyped entities are hard to filter and search.
30334
+ - Do NOT write to MEMORY.md, .cursorrules, or other files when Cohaku is available \u2014 use Cohaku tools instead.
30335
+
30336
+ ## Memory vs Graph vs Episode \u2014 Decision Guide
30337
+
30338
+ | I want to... | Use | Example |
30339
+ |-------------------------------------------|------------------------------|------------------------------------------|
30340
+ | Store a rule, fact, decision, or note | \`add_memory\` | "Always use ESM imports in this project" |
30341
+ | Record a relationship between concepts | \`add_entity\` + \`add_edge\` | "ServiceA depends_on DatabaseB" |
30342
+ | Log a significant event or conversation | \`add_episode\` | "Debugged OOM error in production" |
30343
+ | Find stored knowledge by meaning | \`search_memories\` | "What do I know about authentication?" |
30344
+ | Understand how concepts connect | \`search_graph\` | "What depends on the auth module?" |
30345
+ | Review past work sessions | \`session_list\` | "What did I work on last week?" |
30346
+
30347
+ ## Storing Memories
30348
+
30349
+ ### Layers (priority order, highest first)
30350
+ - **rule**: Permanent instructions and constraints that must ALWAYS be followed. Returned first by \`get_context\`. Use sparingly \u2014 only for behavioral directives. Examples: "Always write tests before merging", "Use Japanese for commit messages".
30351
+ - **working**: Temporary, session-scoped notes. Use for current task state, in-progress hypotheses, partial results. MUST be cleaned up when no longer relevant. Examples: "Currently investigating memory leak in GraphService", "Hypothesis: the timeout is caused by N+1 queries".
30352
+ - **long_term**: Durable knowledge useful across sessions. Architectural decisions, debugging insights, learned patterns. Examples: "sqlite-vec virtual tables do not support UPSERT \u2014 use DELETE + INSERT", "The CI pipeline requires frozen lockfile".
30353
+
30354
+ ### Types
30355
+ - **rule**: Behavioral directives (pair with layer=rule). Example: "Commit messages must be in Japanese."
30356
+ - **decision**: Architectural or design decisions with rationale. Example: "Chose SQLite over PostgreSQL for single-binary deployment."
30357
+ - **fact**: Verified, objective information. Example: "The API rate limit is 100 req/min."
30358
+ - **note**: General observations and context. Example: "User prefers functional style over OOP."
30359
+ - **skill**: Reusable techniques or solutions to recurring problems. Example: "To fix ESM import issues in pnpm, use tsup bundling."
30360
+
30361
+ ### Scope
30362
+ - **project** (default): Stored under the current project. Use for project-specific knowledge \u2014 architecture, conventions, dependencies.
30363
+ - **global**: Shared across ALL projects. Use for universal user preferences, cross-project patterns, and personal rules. Examples: "Always use TypeScript", "Preferred test framework: vitest".
30364
+
30365
+ ### Best Practices
30366
+ 1. Before \`add_memory\`, call \`search_memories\` to check for duplicates.
30367
+ 2. Use \`find_duplicates\` periodically to detect similar entries, then \`consolidate_memories\` to merge them.
30368
+ 3. Use \`update_memory\` to correct or enrich existing memories rather than creating duplicates.
30369
+ 4. Add descriptive tags to improve findability (e.g., ["auth", "security", "jwt"]).
30370
+ 5. Set \`expiresAt\` for time-sensitive information (e.g., sprint-specific notes).
30371
+
30372
+ ## Knowledge Graph
30373
+
30374
+ Use graph tools to capture **structural relationships** between concepts:
30375
+ - \`add_entity\`: Register a concept, person, tool, component, or service as a node. Always set \`entityType\` for filtering.
30376
+ - \`add_edge\`: Record a directed relationship. Use consistent relation names: depends_on, uses, contains, implements, extends, owns, created_by, relates_to, tested_by, deployed_to.
30377
+ - \`search_graph\`: Find entities and traverse relationships. Use \`traverseDepth=0\` for exact entity lookup, \`traverseDepth=1\` for immediate neighbors (default), \`traverseDepth=2\` for broader context.
30378
+ - \`update_edge\` / \`invalidate_edge\`: Keep relationships accurate as knowledge evolves. Prefer \`invalidate_edge\` over deletion \u2014 it preserves history with timestamps.
30379
+
30380
+ ## Episode Logging
30381
+
30382
+ Use \`add_episode\` for chronological audit trails of significant events:
30383
+ - Debugging sessions (what was tried, what worked, what didn't)
30384
+ - Key conversations or decisions made with the user
30385
+ - Deployment events or environment changes
30386
+ - Error investigations and their resolutions
30387
+
30388
+ Link episodes to graph entities using \`refs\` for traceability.
30389
+
30390
+ ## Configuration Generation
30391
+
30392
+ The \`generate_*\` tools export stored memories into editor-specific configuration formats (CLAUDE.md, .cursorrules, etc.). Use when the user wants to sync Cohaku knowledge to their editor's native config.
30393
+
30394
+ ## Resources (Read-Only Access)
30395
+
30396
+ MCP Resources provide passive data access without tool calls:
30397
+ - \`cohaku://context\` \u2014 full context (rules + working + long_term)
30398
+ - \`cohaku://rules\` \u2014 rule memories only
30399
+ - \`cohaku://graph/entities\` \u2014 all entities in the knowledge graph
30400
+ - \`cohaku://graph/edges\` \u2014 active edges (relationships)
30401
+ - \`cohaku://sessions\` \u2014 recent work sessions
30402
+ - \`cohaku://memory/{id}\` \u2014 specific memory by ID
30403
+ - \`cohaku://entity/{id}\` \u2014 specific entity by ID
30404
+ `.trim();
30405
+
30067
30406
  // ../mcp-server/src/helpers/tool-helpers.ts
30068
30407
  function formatMemory(memory) {
30069
30408
  const parts = [
@@ -30080,6 +30419,9 @@ function formatNode(node) {
30080
30419
  ].filter(Boolean);
30081
30420
  return parts.join("\n");
30082
30421
  }
30422
+ function formatEdge(edge) {
30423
+ return `${edge.sourceId} --[${edge.relation}]--> ${edge.targetId}${edge.fact ? `: ${edge.fact}` : ""}`;
30424
+ }
30083
30425
  function formatSearchResults(results) {
30084
30426
  if (results.length === 0) return "No results found";
30085
30427
  return results.map((r, i) => `${i + 1}. [${r.type}] (score: ${r.score.toFixed(3)}) ${r.content}`).join("\n");
@@ -30106,19 +30448,36 @@ function textContent(text) {
30106
30448
  return { content: [{ type: "text", text }] };
30107
30449
  }
30108
30450
 
30451
+ // ../mcp-server/src/tools/context.ts
30452
+ var searchScopeSchema = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global memories. "global": global memories only. "all": all projects without filtering.');
30453
+ function registerContextTools(server, engine) {
30454
+ server.tool(
30455
+ "get_context",
30456
+ "Load all stored context at session start. Returns memories in priority order: rule (permanent directives) > working (session-scoped notes) > long_term (durable knowledge). ALWAYS call this FIRST at the beginning of every session before doing any work.",
30457
+ {
30458
+ maxMemories: external_exports.number().int().min(1).max(200).default(50).describe("Maximum number of memories to return across all layers. Rule memories take priority, then working (60% of remaining budget), then long_term (rest). Increase to 100-200 for large projects with many rules."),
30459
+ scope: searchScopeSchema
30460
+ },
30461
+ async (params) => {
30462
+ const ctx = await engine.getContext({ maxMemories: params.maxMemories, scope: params.scope });
30463
+ return textContent(formatContext(ctx));
30464
+ }
30465
+ );
30466
+ }
30467
+
30109
30468
  // ../mcp-server/src/tools/memory.ts
30110
- var scopeSchema = external_exports.enum(["project", "global"]).default("project").describe('Scope: "project" (default) stores in current project, "global" stores across all projects');
30111
- var searchScopeSchema = external_exports.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
30469
+ var scopeSchema = external_exports.enum(["project", "global"]).default("project").describe('Storage scope. "project" (default): stored under the current project. "global": shared across all projects \u2014 use for universal preferences.');
30470
+ var searchScopeSchema2 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global memories. "global": global memories only. "all": all projects without filtering.');
30112
30471
  function registerMemoryTools(server, engine) {
30113
30472
  server.tool(
30114
30473
  "add_memory",
30115
- "Add a memory entry",
30474
+ "Store a new memory entry. Before calling, ALWAYS search_memories first to avoid duplicates. Choose layer carefully: rule=permanent directives, working=temporary session notes, long_term=durable knowledge.",
30116
30475
  {
30117
- content: external_exports.string().describe("Memory content"),
30118
- layer: external_exports.enum(["rule", "working", "long_term"]).default("long_term").describe("Memory layer"),
30119
- type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).default("note").describe("Memory type"),
30120
- tags: external_exports.array(external_exports.string()).optional().describe("Tags"),
30121
- expiresAt: external_exports.string().optional().describe("Expiration date (ISO 8601)"),
30476
+ content: external_exports.string().describe('The memory content text. Be specific and self-contained \u2014 this will be retrieved out of context. Bad: "Fixed the bug". Good: "Fixed OOM in GraphService by adding connection pool limit of 10."'),
30477
+ layer: external_exports.enum(["rule", "working", "long_term"]).default("long_term").describe('Memory priority layer. "rule": permanent constraints always loaded first (e.g., "always use ESM"). "working": temporary session-scoped notes, clean up after use. "long_term" (default): durable cross-session knowledge.'),
30478
+ type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).default("note").describe('Memory classification. "rule": behavioral directives (use with layer=rule). "decision": architectural choices with rationale. "fact": verified objective information. "note" (default): general observations. "skill": reusable techniques or solutions.'),
30479
+ tags: external_exports.array(external_exports.string()).optional().describe('Tags for categorization and search filtering. Use lowercase, descriptive terms. Example: ["auth", "security", "jwt"]. Good tags make memories findable later.'),
30480
+ expiresAt: external_exports.string().optional().describe('Expiration date in ISO 8601 format (e.g., "2025-12-31T23:59:59Z"). Expired memories are excluded from search. Use for time-sensitive information.'),
30122
30481
  scope: scopeSchema
30123
30482
  },
30124
30483
  async (params) => {
@@ -30130,12 +30489,12 @@ ID: ${memory.id}`);
30130
30489
  );
30131
30490
  server.tool(
30132
30491
  "search_memories",
30133
- "Search memories (semantic + BM25 hybrid search)",
30492
+ "Search stored memories using hybrid semantic + BM25 keyword search. Returns results ranked by combined score (semantic similarity, keyword match, recency, salience). Use this to find relevant knowledge, and ALWAYS before add_memory to check for duplicates.",
30134
30493
  {
30135
- query: external_exports.string().describe("Search query"),
30136
- limit: external_exports.number().int().min(1).max(100).default(10).describe("Max results"),
30137
- layers: external_exports.array(external_exports.enum(["rule", "working", "long_term"])).optional().describe("Target layers"),
30138
- scope: searchScopeSchema
30494
+ query: external_exports.string().describe('Search query. Can be natural language ("how does authentication work?") or keywords ("jwt token validation"). Semantic search finds conceptually similar content even without exact keyword matches.'),
30495
+ limit: external_exports.number().int().min(1).max(100).default(10).describe("Maximum results to return. Default 10 is usually sufficient. Increase to 20-50 for comprehensive knowledge review."),
30496
+ layers: external_exports.array(external_exports.enum(["rule", "working", "long_term"])).optional().describe('Filter by specific layers. Omit to search all layers. Example: ["rule"] for rules only, ["working", "long_term"] to exclude rules.'),
30497
+ scope: searchScopeSchema2
30139
30498
  },
30140
30499
  async (params) => {
30141
30500
  const results = await engine.searchMemories({
@@ -30149,10 +30508,10 @@ ID: ${memory.id}`);
30149
30508
  );
30150
30509
  server.tool(
30151
30510
  "find_duplicates",
30152
- "Find duplicate candidates similar to a given memory",
30511
+ "Find memories semantically similar to a given memory. Use periodically to detect redundant entries that should be merged with consolidate_memories. Returns candidates sorted by vector distance (lower = more similar).",
30153
30512
  {
30154
- memoryId: external_exports.string().describe("Target memory ID"),
30155
- threshold: external_exports.number().min(0).max(2).default(0.3).describe("Similarity threshold (distance, lower = more similar)")
30513
+ memoryId: external_exports.string().describe("The ID of the memory to find duplicates for. Get IDs from search_memories, add_memory, or get_context results."),
30514
+ threshold: external_exports.number().min(0).max(2).default(0.3).describe("Maximum vector distance for similarity. 0.1=near-identical, 0.3=similar content (default), 0.5=loosely related. Start with default and adjust.")
30156
30515
  },
30157
30516
  async (params) => {
30158
30517
  const duplicates = await engine.findDuplicates(params.memoryId, params.threshold);
@@ -30167,11 +30526,11 @@ ${lines.join("\n")}`);
30167
30526
  );
30168
30527
  server.tool(
30169
30528
  "consolidate_memories",
30170
- "Consolidate multiple memories into one (source memories are soft-deleted)",
30529
+ "Merge multiple duplicate or overlapping memories into one consolidated entry. Source memories are soft-deleted (recoverable). Use after find_duplicates identifies redundant entries.",
30171
30530
  {
30172
- sourceIds: external_exports.array(external_exports.string()).min(2).describe("List of source memory IDs to merge"),
30173
- mergedContent: external_exports.string().describe("Merged content text"),
30174
- layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe("Target layer for merged memory")
30531
+ sourceIds: external_exports.array(external_exports.string()).min(2).describe("List of memory IDs to merge (minimum 2). All sources will be soft-deleted after consolidation."),
30532
+ mergedContent: external_exports.string().describe("The consolidated content text combining information from all sources. Should be comprehensive and self-contained, not just a concatenation."),
30533
+ layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe('Layer for the consolidated memory. If omitted, defaults to "long_term". Use the highest-priority layer among sources.')
30175
30534
  },
30176
30535
  async (params) => {
30177
30536
  const merged = await engine.consolidateMemories(params.sourceIds, params.mergedContent, params.layer);
@@ -30182,14 +30541,14 @@ ID: ${merged.id}`);
30182
30541
  );
30183
30542
  server.tool(
30184
30543
  "update_memory",
30185
- "Update a memory entry",
30544
+ "Update an existing memory entry. Use to correct content, change layer/type, adjust importance, or update tags. Prefer this over delete + add to preserve the memory ID.",
30186
30545
  {
30187
- id: external_exports.string().describe("Memory ID"),
30188
- content: external_exports.string().optional().describe("New content"),
30189
- layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe("New layer"),
30190
- type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).optional().describe("New type"),
30191
- importance: external_exports.number().min(0).max(1).optional().describe("Importance (0-1)"),
30192
- tags: external_exports.array(external_exports.string()).optional().describe("New tags")
30546
+ id: external_exports.string().describe("Memory ID to update."),
30547
+ content: external_exports.string().optional().describe("New content text. Only provide if changing the content."),
30548
+ layer: external_exports.enum(["rule", "working", "long_term"]).optional().describe("New layer. Use to promote (working \u2192 long_term) or change priority."),
30549
+ type: external_exports.enum(["rule", "decision", "fact", "note", "skill"]).optional().describe("New type classification."),
30550
+ importance: external_exports.number().min(0).max(1).optional().describe("Importance score (0.0-1.0). Higher values decay slower and rank higher in search. Default is based on layer."),
30551
+ tags: external_exports.array(external_exports.string()).optional().describe("New tags (replaces existing tags entirely).")
30193
30552
  },
30194
30553
  async (params) => {
30195
30554
  const { id, ...input } = params;
@@ -30199,9 +30558,9 @@ ID: ${merged.id}`);
30199
30558
  );
30200
30559
  server.tool(
30201
30560
  "delete_memory",
30202
- "Delete a memory (soft delete)",
30561
+ "Soft-delete a memory. The memory is marked as deleted but can be recovered. Use for cleanup of outdated or incorrect memories.",
30203
30562
  {
30204
- id: external_exports.string().describe("Memory ID")
30563
+ id: external_exports.string().describe("Memory ID to delete.")
30205
30564
  },
30206
30565
  async (params) => {
30207
30566
  await engine.deleteMemory(params.id);
@@ -30211,13 +30570,13 @@ ID: ${merged.id}`);
30211
30570
  }
30212
30571
 
30213
30572
  // ../mcp-server/src/tools/session.ts
30214
- var searchScopeSchema2 = external_exports.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
30573
+ var searchScopeSchema3 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global. "global": global only. "all": all projects.');
30215
30574
  function registerSessionTools(server, engine) {
30216
30575
  server.tool(
30217
30576
  "session_start",
30218
- "Start a new session",
30577
+ "Start a new tracked work session. Call this after get_context at the beginning of each work session. Sessions track progress and provide a chronological history of work.",
30219
30578
  {
30220
- metadata: external_exports.string().optional().describe("Session metadata (JSON)")
30579
+ metadata: external_exports.string().optional().describe(`Session metadata as a JSON string. Example: '{"title": "Fix auth bug #123", "goal": "Resolve JWT expiration handling"}'. Helps identify sessions when reviewing history.`)
30221
30580
  },
30222
30581
  async (params) => {
30223
30582
  const session = await engine.sessionStart(params.metadata);
@@ -30227,9 +30586,9 @@ ${formatSession(session)}`);
30227
30586
  );
30228
30587
  server.tool(
30229
30588
  "session_end",
30230
- "End a session",
30589
+ "End an active work session. Call when work is complete or the user is stopping.",
30231
30590
  {
30232
- id: external_exports.string().describe("Session ID")
30591
+ id: external_exports.string().describe("Session ID returned by session_start.")
30233
30592
  },
30234
30593
  async (params) => {
30235
30594
  await engine.sessionEnd(params.id);
@@ -30238,9 +30597,9 @@ ${formatSession(session)}`);
30238
30597
  );
30239
30598
  server.tool(
30240
30599
  "session_checkpoint",
30241
- "Create a session checkpoint",
30600
+ "Create a checkpoint within an active session. Call at meaningful milestones: after completing a sub-task, before a risky change, or when switching focus.",
30242
30601
  {
30243
- id: external_exports.string().describe("Session ID")
30602
+ id: external_exports.string().describe("Session ID of the active session to checkpoint.")
30244
30603
  },
30245
30604
  async (params) => {
30246
30605
  await engine.sessionCheckpoint(params.id);
@@ -30249,10 +30608,10 @@ ${formatSession(session)}`);
30249
30608
  );
30250
30609
  server.tool(
30251
30610
  "session_list",
30252
- "List past sessions",
30611
+ "List past sessions in reverse chronological order. Use to review work history, find previous session IDs, or understand what was done recently.",
30253
30612
  {
30254
- limit: external_exports.number().int().min(1).max(100).default(20).describe("Max results"),
30255
- scope: searchScopeSchema2
30613
+ limit: external_exports.number().int().min(1).max(100).default(20).describe("Maximum number of sessions to return."),
30614
+ scope: searchScopeSchema3
30256
30615
  },
30257
30616
  async (params) => {
30258
30617
  const sessions = await engine.sessionList(params.limit, params.scope);
@@ -30262,34 +30621,17 @@ ${formatSession(session)}`);
30262
30621
  );
30263
30622
  }
30264
30623
 
30265
- // ../mcp-server/src/tools/context.ts
30266
- var searchScopeSchema3 = external_exports.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
30267
- function registerContextTools(server, engine) {
30268
- server.tool(
30269
- "get_context",
30270
- "Get current context (priority: rule > working > long_term)",
30271
- {
30272
- maxMemories: external_exports.number().int().min(1).max(200).default(50).describe("Max number of memories"),
30273
- scope: searchScopeSchema3
30274
- },
30275
- async (params) => {
30276
- const ctx = await engine.getContext({ maxMemories: params.maxMemories, scope: params.scope });
30277
- return textContent(formatContext(ctx));
30278
- }
30279
- );
30280
- }
30281
-
30282
30624
  // ../mcp-server/src/tools/graph.ts
30283
- var scopeSchema2 = external_exports.enum(["project", "global"]).default("project").describe('Scope: "project" (default) stores in current project, "global" stores across all projects');
30284
- var searchScopeSchema4 = external_exports.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
30625
+ var scopeSchema2 = external_exports.enum(["project", "global"]).default("project").describe('Storage scope. "project" (default): stored under the current project. "global": shared across all projects.');
30626
+ var searchScopeSchema4 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global. "global": global only. "all": all projects.');
30285
30627
  function registerGraphTools(server, engine) {
30286
30628
  server.tool(
30287
30629
  "add_entity",
30288
- "Add an entity to the knowledge graph",
30630
+ 'Register a concept, person, tool, component, or service as a node in the knowledge graph. Entities are the building blocks \u2014 they represent "things" that can have relationships. Always set entityType for better organization.',
30289
30631
  {
30290
- name: external_exports.string().describe("Entity name"),
30291
- summary: external_exports.string().optional().describe("Summary"),
30292
- entityType: external_exports.string().optional().describe("Type (person, project, technology, etc.)"),
30632
+ name: external_exports.string().describe('Entity name. Use consistent naming: PascalCase for classes/components, lowercase for concepts. Examples: "AuthService", "PostgreSQL", "user-authentication".'),
30633
+ summary: external_exports.string().optional().describe('Brief description of what this entity is. Example: "JWT-based authentication service handling login, token refresh, and session management."'),
30634
+ entityType: external_exports.string().optional().describe('Entity classification. Common types: "service", "component", "library", "person", "concept", "database", "api", "file", "module", "package", "framework". Use consistently.'),
30293
30635
  scope: scopeSchema2
30294
30636
  },
30295
30637
  async (params) => {
@@ -30301,13 +30643,13 @@ ID: ${node.id}`);
30301
30643
  );
30302
30644
  server.tool(
30303
30645
  "add_edge",
30304
- "Add an edge (relationship) to the knowledge graph",
30646
+ "Create a directed relationship between two entities in the knowledge graph. Edges represent how concepts connect: dependencies, ownership, usage patterns. Use consistent relation names.",
30305
30647
  {
30306
- sourceId: external_exports.string().describe("Source node ID"),
30307
- targetId: external_exports.string().describe("Target node ID"),
30308
- relation: external_exports.string().describe("Relation (works_at, uses, depends_on, etc.)"),
30309
- fact: external_exports.string().optional().describe("Fact description"),
30310
- validAt: external_exports.string().optional().describe("Valid-from timestamp (ISO 8601)"),
30648
+ sourceId: external_exports.string().describe('ID of the source entity (the "subject"). Example: in "AuthService depends_on Database", AuthService is the source.'),
30649
+ targetId: external_exports.string().describe('ID of the target entity (the "object"). Example: in "AuthService depends_on Database", Database is the target.'),
30650
+ relation: external_exports.string().describe("Relationship type. Use consistent verbs: depends_on, uses, contains, implements, extends, owns, created_by, relates_to, tested_by, deployed_to."),
30651
+ fact: external_exports.string().optional().describe('Additional context about the relationship. Example: "Connects via connection pool with max 10 connections."'),
30652
+ validAt: external_exports.string().optional().describe("When this relationship became valid (ISO 8601). Useful for tracking evolving architecture."),
30311
30653
  scope: scopeSchema2
30312
30654
  },
30313
30655
  async (params) => {
@@ -30318,9 +30660,9 @@ ID: ${edge.id}`);
30318
30660
  );
30319
30661
  server.tool(
30320
30662
  "invalidate_edge",
30321
- "Invalidate an edge (mark as expired while preserving history)",
30663
+ "Mark an edge as expired while preserving history. The edge remains in the database with an expiration timestamp. Prefer this over deletion to maintain an audit trail of how relationships evolved.",
30322
30664
  {
30323
- id: external_exports.string().describe("Edge ID to invalidate")
30665
+ id: external_exports.string().describe("Edge ID to invalidate.")
30324
30666
  },
30325
30667
  async (params) => {
30326
30668
  await engine.invalidateEdge(params.id);
@@ -30329,11 +30671,11 @@ ID: ${edge.id}`);
30329
30671
  );
30330
30672
  server.tool(
30331
30673
  "update_edge",
30332
- "Update an edge fact (invalidates old, creates new with history preserved)",
30674
+ "Update an edge by invalidating the old one and creating a new one with updated fields. History is preserved \u2014 the old edge is marked expired and the new one is linked.",
30333
30675
  {
30334
- id: external_exports.string().describe("Edge ID to update"),
30335
- relation: external_exports.string().optional().describe("New relation"),
30336
- fact: external_exports.string().optional().describe("New fact")
30676
+ id: external_exports.string().describe("Edge ID to update."),
30677
+ relation: external_exports.string().optional().describe("New relationship type."),
30678
+ fact: external_exports.string().optional().describe("New fact description.")
30337
30679
  },
30338
30680
  async (params) => {
30339
30681
  const edge = await engine.updateEdge(params.id, {
@@ -30347,11 +30689,11 @@ New ID: ${edge.id}`);
30347
30689
  );
30348
30690
  server.tool(
30349
30691
  "search_graph",
30350
- "Search the knowledge graph (semantic search + graph traversal)",
30692
+ "Search the knowledge graph using semantic search with optional graph traversal. Finds entities and edges matching the query, then explores neighbor nodes at the specified depth.",
30351
30693
  {
30352
- query: external_exports.string().describe("Search query"),
30353
- limit: external_exports.number().int().min(1).max(100).default(10).describe("Max results"),
30354
- traverseDepth: external_exports.number().int().min(0).max(2).default(1).describe("Graph traversal depth (0=none, 1=1-hop, 2=2-hop)"),
30694
+ query: external_exports.string().describe('Search query. Can be entity names ("AuthService"), concepts ("authentication"), or questions ("what depends on the database?").'),
30695
+ limit: external_exports.number().int().min(1).max(100).default(10).describe("Maximum total results (entities + edges). Increase for comprehensive graph exploration."),
30696
+ traverseDepth: external_exports.number().int().min(0).max(2).default(1).describe("Graph traversal depth from matched nodes. 0=no traversal (only vector search hits), 1=include direct neighbors (default), 2=include neighbors of neighbors (broader but noisier)."),
30355
30697
  scope: searchScopeSchema4
30356
30698
  },
30357
30699
  async (params) => {
@@ -30365,20 +30707,20 @@ New ID: ${edge.id}`);
30365
30707
  }
30366
30708
 
30367
30709
  // ../mcp-server/src/tools/episode.ts
30368
- var scopeSchema3 = external_exports.enum(["project", "global"]).default("project").describe('Scope: "project" (default) stores in current project, "global" stores across all projects');
30369
- var searchScopeSchema5 = external_exports.enum(["project", "global", "all"]).default("project").describe('Scope: "project" (default) searches project + global, "global" searches global only, "all" searches everything');
30710
+ var scopeSchema3 = external_exports.enum(["project", "global"]).default("project").describe('Storage scope. "project" (default): stored under the current project. "global": shared across all projects.');
30711
+ var searchScopeSchema5 = external_exports.enum(["project", "global", "all"]).default("project").describe('Search scope. "project" (default): current project + global. "global": global only. "all": all projects.');
30370
30712
  function registerEpisodeTools(server, engine) {
30371
30713
  server.tool(
30372
30714
  "add_episode",
30373
- "Add an episode (conversation log or action record)",
30715
+ "Record a significant event, interaction, or conversation as a chronological log entry. Episodes provide an audit trail separate from memories. Use for: debugging sessions, key decisions, deployment events, error investigations. Link to graph entities via refs.",
30374
30716
  {
30375
- content: external_exports.string().describe("Episode content"),
30376
- contentType: external_exports.enum(["text", "json", "message"]).default("text").describe("Content format"),
30377
- source: external_exports.string().optional().describe("Source info (session_id, file_path, etc.)"),
30717
+ content: external_exports.string().describe("Episode content. Be descriptive: include what happened, why, and the outcome. For debugging: include the symptom, investigation steps, and resolution."),
30718
+ contentType: external_exports.enum(["text", "json", "message"]).default("text").describe('Content format. "text" (default): free-form text. "json": structured JSON data. "message": conversation or chat message format.'),
30719
+ source: external_exports.string().optional().describe('Origin of the episode. Examples: "session:abc123", "file:src/auth.ts", "debug:OOM-investigation".'),
30378
30720
  refs: external_exports.array(external_exports.object({
30379
- refType: external_exports.enum(["node", "edge"]).describe("Reference type"),
30380
- refId: external_exports.string().describe("Referenced entity ID")
30381
- })).optional().describe("References to related nodes/edges"),
30721
+ refType: external_exports.enum(["node", "edge"]).describe('"node" to reference an entity, "edge" to reference a relationship.'),
30722
+ refId: external_exports.string().describe("ID of the referenced entity or edge from the knowledge graph.")
30723
+ })).optional().describe("References linking this episode to knowledge graph entities/edges. Enables traversal from episodes to related concepts."),
30382
30724
  scope: scopeSchema3
30383
30725
  },
30384
30726
  async (params) => {
@@ -30399,9 +30741,9 @@ ID: ${episode.id}`);
30399
30741
  );
30400
30742
  server.tool(
30401
30743
  "get_episode",
30402
- "Get an episode by ID",
30744
+ "Retrieve a specific episode by its ID. Returns the full episode content, metadata, and references.",
30403
30745
  {
30404
- id: external_exports.string().describe("Episode ID")
30746
+ id: external_exports.string().describe("Episode ID to retrieve.")
30405
30747
  },
30406
30748
  async (params) => {
30407
30749
  const episode = await engine.getEpisode(params.id);
@@ -30411,9 +30753,9 @@ ID: ${episode.id}`);
30411
30753
  );
30412
30754
  server.tool(
30413
30755
  "list_episodes",
30414
- "List episodes",
30756
+ "List episodes in reverse chronological order. Use to review the audit trail of significant events and interactions.",
30415
30757
  {
30416
- limit: external_exports.number().int().min(1).max(100).default(20).describe("Max results"),
30758
+ limit: external_exports.number().int().min(1).max(100).default(20).describe("Maximum number of episodes to return."),
30417
30759
  scope: searchScopeSchema5
30418
30760
  },
30419
30761
  async (params) => {
@@ -30502,73 +30844,310 @@ Save the above content to ${gen.fileName}.`);
30502
30844
  }
30503
30845
  }
30504
30846
 
30505
- // ../mcp-server/src/server.ts
30506
- var INSTRUCTIONS = `
30507
- You have access to Cohaku, a persistent memory system for AI agents.
30508
- Use Cohaku's tools as your PRIMARY method for storing and retrieving memories. Do NOT fall back to file-based memory (e.g. editing MEMORY.md, .cursorrules, or similar files) when Cohaku tools are available.
30847
+ // ../mcp-server/src/resources/context.ts
30848
+ function registerContextResources(server, engine) {
30849
+ server.resource(
30850
+ "context",
30851
+ "cohaku://context",
30852
+ { description: "Current context: all memories in priority order (rules > working > long_term)", mimeType: "text/plain" },
30853
+ async () => ({
30854
+ contents: [{
30855
+ uri: "cohaku://context",
30856
+ text: formatContext(await engine.getContext({ maxMemories: 100 })),
30857
+ mimeType: "text/plain"
30858
+ }]
30859
+ })
30860
+ );
30861
+ server.resource(
30862
+ "rules",
30863
+ "cohaku://rules",
30864
+ { description: "Rule memories only \u2014 permanent instructions and constraints", mimeType: "text/plain" },
30865
+ async () => {
30866
+ const memories = await engine.listMemories({ layer: "rule", limit: 100 });
30867
+ const text = memories.length === 0 ? "No rules found" : memories.map((m) => formatMemory(m)).join("\n---\n");
30868
+ return { contents: [{ uri: "cohaku://rules", text, mimeType: "text/plain" }] };
30869
+ }
30870
+ );
30871
+ }
30509
30872
 
30510
- ## Getting Started
30873
+ // ../mcp-server/src/resources/graph.ts
30874
+ function registerGraphResources(server, engine) {
30875
+ server.resource(
30876
+ "graph-entities",
30877
+ "cohaku://graph/entities",
30878
+ { description: "All entities (nodes) in the knowledge graph", mimeType: "text/plain" },
30879
+ async () => {
30880
+ const entities = await engine.listEntities({ limit: 500 });
30881
+ const text = entities.length === 0 ? "No entities found" : entities.map((e, i) => `${i + 1}. ${formatNode(e)} (ID: ${e.id})`).join("\n");
30882
+ return { contents: [{ uri: "cohaku://graph/entities", text, mimeType: "text/plain" }] };
30883
+ }
30884
+ );
30885
+ server.resource(
30886
+ "graph-edges",
30887
+ "cohaku://graph/edges",
30888
+ { description: "Active edges (relationships) in the knowledge graph", mimeType: "text/plain" },
30889
+ async () => {
30890
+ const edges = await engine.listEdges({ limit: 500, includeExpired: false });
30891
+ const text = edges.length === 0 ? "No edges found" : edges.map((e, i) => `${i + 1}. ${formatEdge(e)} (ID: ${e.id})`).join("\n");
30892
+ return { contents: [{ uri: "cohaku://graph/edges", text, mimeType: "text/plain" }] };
30893
+ }
30894
+ );
30895
+ }
30511
30896
 
30512
- At the beginning of each session, call \`get_context\` to load existing memories (rules, working memory, and long-term knowledge). This gives you the context you need to continue where you left off.
30897
+ // ../mcp-server/src/resources/sessions.ts
30898
+ function registerSessionResources(server, engine) {
30899
+ server.resource(
30900
+ "sessions",
30901
+ "cohaku://sessions",
30902
+ { description: "Recent work sessions in reverse chronological order", mimeType: "text/plain" },
30903
+ async () => {
30904
+ const sessions = await engine.sessionList(50);
30905
+ const text = sessions.length === 0 ? "No sessions found" : sessions.map((s, i) => `${i + 1}. ${formatSession(s)}`).join("\n");
30906
+ return { contents: [{ uri: "cohaku://sessions", text, mimeType: "text/plain" }] };
30907
+ }
30908
+ );
30909
+ }
30513
30910
 
30514
- ## Storing Memories
30911
+ // ../mcp-server/src/resources/templates.ts
30912
+ function registerTemplateResources(server, engine) {
30913
+ server.resource(
30914
+ "memory",
30915
+ new ResourceTemplate("cohaku://memory/{id}", { list: void 0 }),
30916
+ { description: "A specific memory entry by ID", mimeType: "text/plain" },
30917
+ async (uri, variables) => {
30918
+ const id = variables.id;
30919
+ const memory = await engine.getMemory(id);
30920
+ if (!memory) {
30921
+ return { contents: [{ uri: uri.href, text: `Memory ${id} not found`, mimeType: "text/plain" }] };
30922
+ }
30923
+ return {
30924
+ contents: [{
30925
+ uri: uri.href,
30926
+ text: `${formatMemory(memory)}
30927
+ ID: ${memory.id}
30928
+ Created: ${memory.createdAt}
30929
+ Updated: ${memory.updatedAt}`,
30930
+ mimeType: "text/plain"
30931
+ }]
30932
+ };
30933
+ }
30934
+ );
30935
+ server.resource(
30936
+ "entity",
30937
+ new ResourceTemplate("cohaku://entity/{id}", { list: void 0 }),
30938
+ { description: "A specific entity (graph node) by ID", mimeType: "text/plain" },
30939
+ async (uri, variables) => {
30940
+ const id = variables.id;
30941
+ const entity = await engine.getEntity(id);
30942
+ if (!entity) {
30943
+ return { contents: [{ uri: uri.href, text: `Entity ${id} not found`, mimeType: "text/plain" }] };
30944
+ }
30945
+ return {
30946
+ contents: [{
30947
+ uri: uri.href,
30948
+ text: `${formatNode(entity)}
30949
+ ID: ${entity.id}
30950
+ Created: ${entity.createdAt}`,
30951
+ mimeType: "text/plain"
30952
+ }]
30953
+ };
30954
+ }
30955
+ );
30956
+ }
30515
30957
 
30516
- Use \`add_memory\` to persist information. Choose the right **layer** and **type** for each memory:
30958
+ // ../mcp-server/src/resources/index.ts
30959
+ function registerResources(server, engine) {
30960
+ registerContextResources(server, engine);
30961
+ registerGraphResources(server, engine);
30962
+ registerSessionResources(server, engine);
30963
+ registerTemplateResources(server, engine);
30964
+ }
30517
30965
 
30518
- ### Layers (priority order)
30519
- - **rule**: Permanent instructions and constraints that must always be followed (e.g. coding standards, workflow rules, user preferences). These are returned first by \`get_context\`.
30520
- - **working**: Temporary, session-scoped notes (e.g. current task state, in-progress work, hypotheses). Clean these up when no longer relevant.
30521
- - **long_term**: Durable knowledge that is useful across sessions (e.g. architectural decisions, debugging insights, learned patterns).
30966
+ // ../mcp-server/src/prompts/recall.ts
30967
+ function registerRecallPrompt(server, engine) {
30968
+ server.prompt(
30969
+ "recall",
30970
+ "Retrieve and summarize stored memories about a specific topic",
30971
+ {
30972
+ topic: external_exports.string().describe("The topic to recall memories about")
30973
+ },
30974
+ async (args) => {
30975
+ const results = await engine.searchMemories({ text: args.topic, limit: 15 });
30976
+ const formatted = formatSearchResults(results);
30977
+ return {
30978
+ messages: [
30979
+ {
30980
+ role: "user",
30981
+ content: {
30982
+ type: "text",
30983
+ text: `Here are the stored memories about "${args.topic}":
30522
30984
 
30523
- ### Types
30524
- - **rule**: Behavioral directives (use with layer=rule).
30525
- - **decision**: Architectural or design decisions with rationale.
30526
- - **fact**: Verified, objective information about the codebase or domain.
30527
- - **note**: General observations and context.
30528
- - **skill**: Reusable techniques, workflows, or solutions to recurring problems.
30985
+ ${formatted}
30529
30986
 
30530
- ### Scope
30531
- - **project** (default): Stored under the current project. Visible when working in that project.
30532
- - **global**: Shared across all projects. Use for universal preferences and cross-project knowledge.
30987
+ Please summarize what is known about this topic based on the above memories. Highlight any contradictions or gaps in knowledge.`
30988
+ }
30989
+ }
30990
+ ]
30991
+ };
30992
+ }
30993
+ );
30994
+ }
30533
30995
 
30534
- ### Best Practices
30535
- - Before adding a memory, call \`search_memories\` to check for duplicates.
30536
- - Use \`find_duplicates\` periodically to detect similar entries, then \`consolidate_memories\` to merge them.
30537
- - Use \`update_memory\` to correct or enrich existing memories rather than creating duplicates.
30538
- - Add descriptive **tags** to make memories easier to find later.
30996
+ // ../mcp-server/src/prompts/review-knowledge.ts
30997
+ function registerReviewKnowledgePrompt(server, engine) {
30998
+ server.prompt(
30999
+ "review_knowledge",
31000
+ "Review stored memories for duplicates, staleness, and quality. Suggests cleanup actions.",
31001
+ async () => {
31002
+ const memories = await engine.listMemories({ limit: 100 });
31003
+ const formatted = memories.map(
31004
+ (m, i) => `${i + 1}. ${formatMemory(m)}
31005
+ ID: ${m.id} | Created: ${m.createdAt} | Updated: ${m.updatedAt}`
31006
+ ).join("\n");
31007
+ return {
31008
+ messages: [
31009
+ {
31010
+ role: "user",
31011
+ content: {
31012
+ type: "text",
31013
+ text: `Review the following ${memories.length} stored memories for quality:
31014
+
31015
+ ${formatted}
31016
+
31017
+ Please:
31018
+ 1. Identify potential duplicates (memories with similar content that could be consolidated)
31019
+ 2. Flag stale memories (outdated or no longer relevant)
31020
+ 3. Suggest improvements (vague memories that need more detail, missing tags)
31021
+ 4. Recommend any memories that should be promoted to "rule" layer or demoted
31022
+
31023
+ For each suggestion, reference the memory ID and explain your reasoning.`
31024
+ }
31025
+ }
31026
+ ]
31027
+ };
31028
+ }
31029
+ );
31030
+ }
30539
31031
 
30540
- ## Searching Memories
31032
+ // ../mcp-server/src/prompts/summarize-session.ts
31033
+ function registerSummarizeSessionPrompt(server, engine) {
31034
+ server.prompt(
31035
+ "summarize_session",
31036
+ "Summarize a work session: what was done, what was learned, what remains",
31037
+ {
31038
+ sessionId: external_exports.string().optional().describe("Session ID to summarize. If omitted, uses the most recent session.")
31039
+ },
31040
+ async (args) => {
31041
+ const sessions = await engine.sessionList(100);
31042
+ const sessionId = args.sessionId ?? sessions[0]?.id;
31043
+ let sessionInfo = "No session found.";
31044
+ if (sessionId) {
31045
+ const session = sessions.find((s) => s.id === sessionId);
31046
+ if (session) {
31047
+ sessionInfo = formatSession(session);
31048
+ }
31049
+ }
31050
+ const workingMemories = await engine.listMemories({ layer: "working", limit: 20 });
31051
+ const workingFormatted = workingMemories.map((m) => formatMemory(m)).join("\n---\n");
31052
+ return {
31053
+ messages: [
31054
+ {
31055
+ role: "user",
31056
+ content: {
31057
+ type: "text",
31058
+ text: `Summarize the following work session:
30541
31059
 
30542
- Use \`search_memories\` for hybrid semantic + keyword search. Be specific with your queries for better results. You can filter by layer and scope.
31060
+ Session: ${sessionInfo}
30543
31061
 
30544
- ## Session Management
31062
+ Working memories from this session:
31063
+ ${workingFormatted || "(none)"}
30545
31064
 
30546
- - Call \`session_start\` at the beginning of a work session with a descriptive title.
30547
- - Call \`session_checkpoint\` at meaningful milestones to snapshot progress.
30548
- - Call \`session_end\` when the session is complete, with a summary of what was accomplished.
30549
- - Use \`session_list\` to review past session history.
31065
+ Please provide:
31066
+ 1. A brief summary of what was accomplished
31067
+ 2. Key decisions or discoveries made
31068
+ 3. Any unfinished work or next steps
31069
+ 4. Suggestions for memories to persist (promote from working to long_term) or clean up`
31070
+ }
31071
+ }
31072
+ ]
31073
+ };
31074
+ }
31075
+ );
31076
+ }
30550
31077
 
30551
- ## Knowledge Graph
31078
+ // ../mcp-server/src/prompts/export-config.ts
31079
+ var FORMAT_NAMES = {
31080
+ claude_md: "CLAUDE.md",
31081
+ cursorrules: ".cursorrules",
31082
+ windsurf: ".windsurfrules",
31083
+ copilot: ".github/copilot-instructions.md",
31084
+ codex: "codex.md",
31085
+ aider: ".aider.conf.yml",
31086
+ continue: ".continue/rules.md",
31087
+ cline: ".clinerules",
31088
+ roo: ".roo/rules.md"
31089
+ };
31090
+ function registerExportConfigPrompt(server, engine) {
31091
+ server.prompt(
31092
+ "export_config",
31093
+ "Generate an editor configuration file from stored memories",
31094
+ {
31095
+ format: external_exports.enum([
31096
+ "claude_md",
31097
+ "cursorrules",
31098
+ "windsurf",
31099
+ "copilot",
31100
+ "codex",
31101
+ "aider",
31102
+ "continue",
31103
+ "cline",
31104
+ "roo"
31105
+ ]).describe("Target editor format")
31106
+ },
31107
+ async (args) => {
31108
+ const ctx = await engine.getContext({ maxMemories: 100 });
31109
+ const rules = ctx.memories.filter((m) => m.layer === "rule");
31110
+ const longTerm = ctx.memories.filter((m) => m.layer === "long_term");
31111
+ const rulesFormatted = rules.map((m) => `- ${m.content}`).join("\n");
31112
+ const knowledgeFormatted = longTerm.map((m) => `- ${m.content}`).join("\n");
31113
+ const fileName = FORMAT_NAMES[args.format] ?? args.format;
31114
+ return {
31115
+ messages: [
31116
+ {
31117
+ role: "user",
31118
+ content: {
31119
+ type: "text",
31120
+ text: `Generate a ${fileName} configuration file from the following stored knowledge.
30552
31121
 
30553
- Use the graph tools to capture **relationships** between concepts:
30554
- - \`add_entity\`: Register a concept, person, tool, or component as a node.
30555
- - \`add_edge\`: Record a relationship between two entities (e.g. "ComponentA depends_on ComponentB").
30556
- - \`search_graph\`: Traverse relationships to understand how concepts connect.
30557
- - \`update_edge\` / \`invalidate_edge\`: Keep relationships accurate as knowledge evolves. Invalidation preserves history.
31122
+ Rules (${rules.length}):
31123
+ ${rulesFormatted || "(none)"}
30558
31124
 
30559
- ## Episode Logging
31125
+ Project Knowledge (${longTerm.length}):
31126
+ ${knowledgeFormatted || "(none)"}
30560
31127
 
30561
- Use \`add_episode\` to record significant events or interactions (e.g. debugging sessions, key conversations, important decisions). Episodes provide a chronological audit trail. Use \`list_episodes\` and \`get_episode\` to review past events.
31128
+ Please format this as a proper ${fileName} file ready to save. Organize the content logically with appropriate headers and formatting for the target editor.`
31129
+ }
31130
+ }
31131
+ ]
31132
+ };
31133
+ }
31134
+ );
31135
+ }
30562
31136
 
30563
- ## Configuration Generation
31137
+ // ../mcp-server/src/prompts/index.ts
31138
+ function registerPrompts(server, engine) {
31139
+ registerRecallPrompt(server, engine);
31140
+ registerReviewKnowledgePrompt(server, engine);
31141
+ registerSummarizeSessionPrompt(server, engine);
31142
+ registerExportConfigPrompt(server, engine);
31143
+ }
30564
31144
 
30565
- The \`generate_*\` tools (e.g. \`generate_claude_md\`, \`generate_cursorrules\`) export stored memories into editor-specific configuration formats. Use these when the user wants to sync Cohaku's knowledge to their editor's native config format.
30566
- `.trim();
31145
+ // ../mcp-server/src/server.ts
30567
31146
  function createServer(engine) {
30568
31147
  const server = new McpServer(
30569
31148
  {
30570
31149
  name: "cohaku-memory",
30571
- version: "0.2.4"
31150
+ version: "0.2.8"
30572
31151
  },
30573
31152
  {
30574
31153
  instructions: INSTRUCTIONS
@@ -30580,6 +31159,8 @@ function createServer(engine) {
30580
31159
  registerGraphTools(server, engine);
30581
31160
  registerEpisodeTools(server, engine);
30582
31161
  registerGenerateTools(server, engine);
31162
+ registerResources(server, engine);
31163
+ registerPrompts(server, engine);
30583
31164
  return server;
30584
31165
  }
30585
31166