@memstack/mcp 0.7.3 → 0.8.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/dist/index.js CHANGED
@@ -1,59 +1,209 @@
1
- import { MemStack, AnthropicLLMAdapter, OpenAILLMAdapter, OpenAIEmbeddingAdapter, RedisStorageAdapter, SQLiteStorageAdapter, PostgresStorageAdapter, DiskStorageAdapter, MarkdownStorageAdapter, InMemoryStorageAdapter } from '@memstack/core';
2
- import { readFileSync } from 'fs';
1
+ import { readFileSync, realpathSync, existsSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { resolve, join, dirname } from 'path';
4
+ import { MemStack, projectNamespace, defaultRecallNamespaces, GLOBAL_NAMESPACE, MemStackError, InMemoryStorageAdapter, HarnessMemory, AnthropicLLMAdapter, OpenAILLMAdapter, OpenAIEmbeddingAdapter, RedisStorageAdapter, SQLiteStorageAdapter, PostgresStorageAdapter, DiskStorageAdapter, MarkdownStorageAdapter } from '@memstack/core';
5
+ import { execFileSync } from 'child_process';
6
+ import { createHash } from 'crypto';
3
7
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
8
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { z } from 'zod';
5
10
 
6
11
  // ../config-env/dist/index.js
7
- function buildLLMAdapter() {
8
- if (!process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY) {
12
+ var STORAGE_TYPES = ["memory", "markdown", "disk", "postgres", "sqlite", "redis"];
13
+ function memstackHome() {
14
+ return process.env.MEMSTACK_HOME ?? join(homedir(), ".memstack");
15
+ }
16
+ function configFilePath(home = memstackHome()) {
17
+ return join(home, "config.json");
18
+ }
19
+ function readConfigFile(path = configFilePath()) {
20
+ if (!existsSync(path)) return void 0;
21
+ let parsed;
22
+ try {
23
+ parsed = JSON.parse(readFileSync(path, "utf8"));
24
+ } catch (error) {
25
+ throw new Error(`${path} is not valid JSON: ${error.message}`);
26
+ }
27
+ return validateConfigFile(parsed, path);
28
+ }
29
+ function validateConfigFile(value, path) {
30
+ const fail = (message) => {
31
+ throw new Error(`Invalid ${path}: ${message}`);
32
+ };
33
+ if (!isObject(value)) fail("expected a JSON object");
34
+ const config = value;
35
+ if (config.version !== 1) fail(`unsupported version ${JSON.stringify(config.version)}, expected 1`);
36
+ if (config.llm !== void 0) {
37
+ if (!isObject(config.llm)) fail("llm must be an object");
38
+ const llm = config.llm;
39
+ if (llm.provider !== "openai-compatible" && llm.provider !== "anthropic") {
40
+ fail('llm.provider must be "openai-compatible" or "anthropic"');
41
+ }
42
+ if (typeof llm.apiKey !== "string" || !llm.apiKey) fail("llm.apiKey must be a non-empty string");
43
+ for (const key of ["baseURL", "model"]) {
44
+ if (llm[key] !== void 0 && typeof llm[key] !== "string") fail(`llm.${key} must be a string`);
45
+ }
46
+ }
47
+ if (config.storage !== void 0) {
48
+ if (!isObject(config.storage)) fail("storage must be an object");
49
+ const storage = config.storage;
50
+ if (!STORAGE_TYPES.includes(storage.type)) {
51
+ fail(`storage.type must be one of ${STORAGE_TYPES.join(", ")}`);
52
+ }
53
+ for (const key of ["path", "url"]) {
54
+ if (storage[key] !== void 0 && typeof storage[key] !== "string") fail(`storage.${key} must be a string`);
55
+ }
56
+ }
57
+ return config;
58
+ }
59
+ function isObject(value) {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+ var PIN_FILE = ".memstack.json";
63
+ var PIN_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
64
+ function resolveProject(cwd = process.cwd()) {
65
+ const dir = realpathSync(resolve(cwd));
66
+ const root = git(dir, ["rev-parse", "--show-toplevel"]);
67
+ const base = root ?? dir;
68
+ const pinned = readPin(base);
69
+ const fallbacks = transitionalIds(dir, root);
70
+ if (pinned) return { id: pinned, source: "pin", key: pinned, root: base, previousIds: [] };
71
+ if (!root) return { ...identity("directory", dir), root: dir, previousIds: [] };
72
+ const shallow = git(root, ["rev-parse", "--is-shallow-repository"]) === "true";
73
+ const rootCommit = shallow ? null : firstCommit(root);
74
+ if (rootCommit) return { ...identity("root-commit", rootCommit), root, previousIds: fallbacks };
75
+ const remote = git(root, ["config", "--get", "remote.origin.url"]);
76
+ const normalized = shallow && remote ? normalizeRemote(remote) : null;
77
+ if (normalized) return { ...identity("remote", normalized), root, previousIds: fallbacks };
78
+ const gitId = identity("git", gitRepoKey(root));
79
+ return { ...gitId, root, previousIds: fallbacks.filter((id) => id !== gitId.id) };
80
+ }
81
+ function normalizeRemote(url) {
82
+ const value = url.trim();
83
+ if (!value) return null;
84
+ let host;
85
+ let path;
86
+ const scp = value.match(/^(?:[^@/]+@)?([^:/]+):(?!\/)(.+)$/);
87
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
88
+ let parsed;
89
+ try {
90
+ parsed = new URL(value);
91
+ } catch {
92
+ return null;
93
+ }
94
+ if (parsed.protocol === "file:" || !parsed.hostname) return null;
95
+ host = parsed.hostname;
96
+ path = decodeURIComponent(parsed.pathname);
97
+ } else if (scp) {
98
+ host = scp[1];
99
+ path = scp[2];
100
+ } else {
101
+ return null;
102
+ }
103
+ path = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
104
+ if (!path) return null;
105
+ return `${host.toLowerCase()}/${path}`;
106
+ }
107
+ function readPin(base) {
108
+ const path = join(base, PIN_FILE);
109
+ if (!existsSync(path)) return null;
110
+ let project;
111
+ try {
112
+ project = JSON.parse(readFileSync(path, "utf8")).project;
113
+ } catch (error) {
114
+ throw new Error(`${path} is not valid JSON: ${error.message}`);
115
+ }
116
+ if (project === void 0) return null;
117
+ if (typeof project !== "string" || !PIN_PATTERN.test(project)) {
118
+ throw new Error(`${path}: "project" must use letters, digits, '.', '_', or '-' and be at most 64 characters.`);
119
+ }
120
+ return project;
121
+ }
122
+ function firstCommit(root) {
123
+ const out = git(root, ["rev-list", "--max-parents=0", "--first-parent", "HEAD"]);
124
+ return out ? out.split("\n").pop().trim() : null;
125
+ }
126
+ function gitRepoKey(root) {
127
+ const commonDir = git(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
128
+ return commonDir ? dirname(realpathSync(commonDir)) : root;
129
+ }
130
+ function transitionalIds(dir, root) {
131
+ const ids = /* @__PURE__ */ new Set();
132
+ if (root) ids.add(identity("git", gitRepoKey(root)).id);
133
+ ids.add(identity("directory", dir).id);
134
+ return [...ids];
135
+ }
136
+ function identity(source, key) {
137
+ const id = createHash("sha256").update(`${source}:${key}`).digest("hex").slice(0, 16);
138
+ return { id, source, key };
139
+ }
140
+ function git(cwd, args) {
141
+ try {
142
+ const out = execFileSync("git", ["-C", cwd, ...args], {
143
+ encoding: "utf8",
144
+ stdio: ["ignore", "pipe", "ignore"],
145
+ timeout: 5e3
146
+ }).trim();
147
+ return out || null;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ function isMissingModule(error) {
153
+ const code = error?.code;
154
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
155
+ }
156
+ function buildLLMAdapter(env) {
157
+ if (!env.ANTHROPIC_API_KEY && !env.OPENAI_API_KEY) {
9
158
  throw new Error("At least one of OPENAI_API_KEY or ANTHROPIC_API_KEY must be set");
10
159
  }
11
- if (process.env.ANTHROPIC_API_KEY) {
12
- return new AnthropicLLMAdapter({ apiKey: process.env.ANTHROPIC_API_KEY });
160
+ if (env.ANTHROPIC_API_KEY) {
161
+ return new AnthropicLLMAdapter({ apiKey: env.ANTHROPIC_API_KEY, defaultModel: env.MEMSTACK_LLM_MODEL });
13
162
  }
14
163
  return new OpenAILLMAdapter({
15
- apiKey: process.env.OPENAI_API_KEY ?? "",
16
- baseURL: process.env.MEMSTACK_OPENAI_BASE_URL,
17
- defaultModel: process.env.MEMSTACK_LLM_MODEL
164
+ apiKey: env.OPENAI_API_KEY ?? "",
165
+ baseURL: env.MEMSTACK_OPENAI_BASE_URL,
166
+ defaultModel: env.MEMSTACK_LLM_MODEL
18
167
  });
19
168
  }
20
- function buildEmbeddingAdapter() {
21
- if (process.env.MEMSTACK_OPENAI_BASE_URL) return void 0;
22
- if (process.env.OPENAI_API_KEY) {
23
- return new OpenAIEmbeddingAdapter({ apiKey: process.env.OPENAI_API_KEY });
169
+ function buildEmbeddingAdapter(env) {
170
+ if (env.MEMSTACK_OPENAI_BASE_URL) return void 0;
171
+ if (env.OPENAI_API_KEY) {
172
+ return new OpenAIEmbeddingAdapter({ apiKey: env.OPENAI_API_KEY });
24
173
  }
25
174
  return void 0;
26
175
  }
27
- async function buildStorageAdapter() {
28
- const storage = process.env.MEMSTACK_STORAGE ?? "memory";
176
+ async function buildStorageAdapter(env) {
177
+ const storage = env.MEMSTACK_STORAGE ?? "memory";
29
178
  switch (storage) {
30
179
  case "memory":
31
180
  return new InMemoryStorageAdapter();
32
181
  case "markdown":
33
182
  return new MarkdownStorageAdapter({
34
- dir: process.env.MEMSTACK_DIR ?? "./memories"
183
+ dir: env.MEMSTACK_DIR ?? "./memories"
35
184
  });
36
185
  case "disk":
37
186
  return new DiskStorageAdapter({
38
- storageDir: process.env.MEMSTACK_DIR ?? "./memstack-data"
187
+ storageDir: env.MEMSTACK_DIR ?? "./memstack-data"
39
188
  });
40
189
  case "postgres":
41
- if (!process.env.DATABASE_URL) {
190
+ if (!env.DATABASE_URL) {
42
191
  throw new Error("postgres storage requires DATABASE_URL");
43
192
  }
44
193
  return new PostgresStorageAdapter({
45
- connectionString: process.env.DATABASE_URL
194
+ connectionString: env.DATABASE_URL
46
195
  });
47
196
  case "sqlite": {
48
197
  try {
49
198
  const BetterSqlite3 = await import('better-sqlite3');
50
199
  const db = new BetterSqlite3.default(
51
- process.env.SQLITE_PATH ?? "./memstack.db"
200
+ env.SQLITE_PATH ?? "./memstack.db"
52
201
  );
53
202
  return new SQLiteStorageAdapter({ db });
54
- } catch {
203
+ } catch (error) {
204
+ if (!isMissingModule(error)) throw error;
55
205
  throw new Error(
56
- "SQLite requires better-sqlite3. Install: npm install better-sqlite3"
206
+ "SQLite requires better-sqlite3. Install: npm install better-sqlite3@^11.10.0"
57
207
  );
58
208
  }
59
209
  }
@@ -61,12 +211,13 @@ async function buildStorageAdapter() {
61
211
  try {
62
212
  const Redis = await import('ioredis');
63
213
  const client = new Redis.default(
64
- process.env.REDIS_URL ?? "redis://localhost:6379"
214
+ env.REDIS_URL ?? "redis://localhost:6379"
65
215
  );
66
216
  return new RedisStorageAdapter({ redis: client });
67
- } catch {
217
+ } catch (error) {
218
+ if (!isMissingModule(error)) throw error;
68
219
  throw new Error(
69
- "Redis requires ioredis. Install: npm install ioredis"
220
+ "Redis requires ioredis. Install: npm install ioredis@^5.11.1"
70
221
  );
71
222
  }
72
223
  }
@@ -75,11 +226,39 @@ async function buildStorageAdapter() {
75
226
  }
76
227
  }
77
228
  async function loadConfigFromEnv() {
78
- const llm = buildLLMAdapter();
79
- const embedding = buildEmbeddingAdapter();
80
- const storage = await buildStorageAdapter();
81
- const embedOnStore = process.env.MEMSTACK_EMBED_ON_STORE !== "false";
82
- const defaultActorId = process.env.MEMSTACK_ACTOR ?? "default";
229
+ return buildConfig(process.env);
230
+ }
231
+ async function loadConfig(options = {}) {
232
+ return buildConfig(mergeConfigFile(readConfigFile(options.configPath), process.env));
233
+ }
234
+ function mergeConfigFile(file, env) {
235
+ const merged = { ...env };
236
+ const envHasLLM = LLM_ENV.some((key) => env[key] !== void 0);
237
+ if (file?.llm && !envHasLLM) {
238
+ if (file.llm.provider === "anthropic") merged.ANTHROPIC_API_KEY = file.llm.apiKey;
239
+ else {
240
+ merged.OPENAI_API_KEY = file.llm.apiKey;
241
+ merged.MEMSTACK_OPENAI_BASE_URL = file.llm.baseURL;
242
+ }
243
+ merged.MEMSTACK_LLM_MODEL = file.llm.model;
244
+ }
245
+ if (file?.storage && env.MEMSTACK_STORAGE === void 0) {
246
+ const { type, path, url } = file.storage;
247
+ merged.MEMSTACK_STORAGE = type;
248
+ if (type === "sqlite") merged.SQLITE_PATH = path;
249
+ if (type === "disk" || type === "markdown") merged.MEMSTACK_DIR = path;
250
+ if (type === "postgres") merged.DATABASE_URL = url;
251
+ if (type === "redis") merged.REDIS_URL = url;
252
+ }
253
+ return merged;
254
+ }
255
+ var LLM_ENV = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "MEMSTACK_OPENAI_BASE_URL", "MEMSTACK_LLM_MODEL"];
256
+ async function buildConfig(env) {
257
+ const llm = buildLLMAdapter(env);
258
+ const embedding = buildEmbeddingAdapter(env);
259
+ const storage = await buildStorageAdapter(env);
260
+ const embedOnStore = env.MEMSTACK_EMBED_ON_STORE !== "false";
261
+ const defaultActorId = env.MEMSTACK_ACTOR ?? "default";
83
262
  return {
84
263
  config: { llm, embedding, storage, defaults: { embedOnStore } },
85
264
  defaultActorId
@@ -87,7 +266,7 @@ async function loadConfigFromEnv() {
87
266
  }
88
267
 
89
268
  // src/config.ts
90
- async function loadConfig() {
269
+ async function loadConfig2() {
91
270
  return loadConfigFromEnv();
92
271
  }
93
272
  var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
@@ -102,7 +281,7 @@ var TOOLS = [
102
281
  actorId: { type: "string", description: "Actor ID. Defaults to the current session actor." },
103
282
  importance: { type: "number", description: "Importance score (0.0-1.0). Auto-scored if omitted." },
104
283
  tags: { type: "array", items: { type: "string" }, description: "Tags for categorization. Auto-extracted if omitted." },
105
- memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection). Default: interaction." },
284
+ memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection, preference, decision, instruction). Default: interaction." },
106
285
  metadata: { type: "object", description: "Additional metadata key-value pairs" }
107
286
  },
108
287
  required: ["content"]
@@ -118,7 +297,7 @@ var TOOLS = [
118
297
  actorId: { type: "string", description: "Actor ID. Defaults to the current session actor." },
119
298
  importance: { type: "number", description: "Importance score 0.0-1.0. Default: 0.5." },
120
299
  tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
121
- memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection). Default: interaction." },
300
+ memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection, preference, decision, instruction). Default: interaction." },
122
301
  metadata: { type: "object", description: "Additional metadata key-value pairs" }
123
302
  },
124
303
  required: ["content"]
@@ -140,7 +319,7 @@ var TOOLS = [
140
319
  actorId: { type: "string", description: "Actor ID. Defaults to the current session actor." },
141
320
  importance: { type: "number", description: "Importance score 0.0-1.0. Default: 0.5." },
142
321
  tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
143
- memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection). Default: interaction." },
322
+ memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection, preference, decision, instruction). Default: interaction." },
144
323
  metadata: { type: "object", description: "Additional metadata key-value pairs" }
145
324
  },
146
325
  required: ["content"]
@@ -710,7 +889,180 @@ function createServer({
710
889
  });
711
890
  return server;
712
891
  }
892
+ var pkg2 = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
893
+ var KINDS = [
894
+ "fact",
895
+ "preference",
896
+ "decision",
897
+ "instruction",
898
+ "observation",
899
+ "interaction",
900
+ "summary",
901
+ "reflection"
902
+ ];
903
+ var HARNESS_INSTRUCTIONS = [
904
+ "MemStack is this project's persistent memory, shared with other coding agents.",
905
+ "When the user asks you to remember something, call memory_store. Do not write it into README, AGENTS.md, or other project files unless the user asks for a file change.",
906
+ "Also call memory_store when you learn a durable fact, decision, preference, or rule.",
907
+ "Call memory_retrieve at the start of a task and before decisions that depend on project history, conventions, or preferences.",
908
+ "Never store secrets, credentials, or personal data.",
909
+ "",
910
+ 'Store one self-contained statement per memory, e.g. "This project uses Hono for the API". Memories are project-scoped; use scope "global" only for preferences that apply to every project. memory_retrieve takes natural-language questions. Cite memory IDs when you rely on them, and delete memories that turn out to be wrong with memory_delete.'
911
+ ].join("\n");
912
+ var StoreArgs = z.object({
913
+ content: z.string().trim().min(1).max(8e3).describe("One self-contained statement to remember."),
914
+ kind: z.enum(KINDS).default("fact").describe("What kind of memory this is."),
915
+ importance: z.number().min(0).max(1).optional().describe("0 to 1. Default 0.5; use 0.8+ for rules that must not be missed."),
916
+ tags: z.array(z.string().trim().min(1).max(40)).max(10).optional().describe("Optional topic tags. MemStack adds its own."),
917
+ scope: z.enum(["project", "global"]).default("project").describe('"global" only for preferences that apply to every project.')
918
+ }).strict();
919
+ var RetrieveArgs = z.object({
920
+ query: z.string().max(1e3).optional().describe("Natural-language question or keywords. Omit to get the most important memories."),
921
+ limit: z.number().int().min(1).max(25).default(10).describe("Maximum memories to return."),
922
+ kinds: z.array(z.enum(KINDS)).optional().describe("Only return these kinds.")
923
+ }).strict();
924
+ var IdArgs = z.object({ id: z.string().min(1).max(200) }).strict();
925
+ var NoArgs = z.object({}).strict();
926
+ var TOOLS2 = [
927
+ {
928
+ name: "memory_store",
929
+ description: "Save to project memory. Use this whenever the user asks you to remember something, instead of editing project files, and for durable facts, decisions, preferences, or rules. Later sessions and other agents can recall it.",
930
+ schema: StoreArgs
931
+ },
932
+ {
933
+ name: "memory_retrieve",
934
+ description: "Recall memories for this project (and global preferences) relevant to a question. Fast and local; call it freely.",
935
+ schema: RetrieveArgs
936
+ },
937
+ { name: "memory_get", description: "Get one memory by ID.", schema: IdArgs },
938
+ { name: "memory_delete", description: "Delete a memory that is wrong or outdated, by ID.", schema: IdArgs },
939
+ { name: "memory_stats", description: "Show the current project and how many memories it has.", schema: NoArgs }
940
+ ];
941
+ var RECALL_MAX_CHARS = 8e3;
942
+ function createHarnessServer({ memory, projectId, harness, cwd }) {
943
+ const server = new Server(
944
+ { name: "memstack", version: pkg2.version },
945
+ { capabilities: { tools: {} }, instructions: HARNESS_INSTRUCTIONS }
946
+ );
947
+ const project = projectNamespace(projectId);
948
+ const namespaces = defaultRecallNamespaces(projectId);
949
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
950
+ tools: TOOLS2.map(({ name, description, schema }) => ({
951
+ name,
952
+ description,
953
+ inputSchema: z.toJSONSchema(schema, { io: "input" })
954
+ }))
955
+ }));
956
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
957
+ const tool = TOOLS2.find((t) => t.name === request.params.name);
958
+ if (!tool) return failure(`Unknown tool: ${request.params.name}`);
959
+ const parsed = tool.schema.safeParse(request.params.arguments ?? {});
960
+ if (!parsed.success) {
961
+ return failure(`Invalid arguments for ${tool.name}: ${z.prettifyError(parsed.error)}`);
962
+ }
963
+ try {
964
+ switch (tool.name) {
965
+ case "memory_store": {
966
+ const args = parsed.data;
967
+ const stored = await memory.remember({
968
+ namespace: args.scope === "global" ? GLOBAL_NAMESPACE : project,
969
+ content: args.content,
970
+ kind: args.kind,
971
+ importance: args.importance,
972
+ tags: args.tags,
973
+ source: {
974
+ harness: harness ?? server.getClientVersion()?.name ?? "unknown",
975
+ project: projectId,
976
+ cwd
977
+ }
978
+ });
979
+ return text(`Stored ${stored.id} (${args.scope}, ${stored.memoryType}). Tags: ${stored.tags.join(", ") || "none"}.`);
980
+ }
981
+ case "memory_retrieve": {
982
+ const args = parsed.data;
983
+ const { hits, fallback } = await memory.recall({
984
+ namespaces,
985
+ query: args.query,
986
+ kinds: args.kinds,
987
+ limit: args.limit,
988
+ maxChars: RECALL_MAX_CHARS
989
+ });
990
+ if (hits.length === 0) return text("No memories yet for this project.");
991
+ const header = fallback && args.query ? `Nothing matched "${args.query}". The most important memories instead:` : `${hits.length} ${hits.length === 1 ? "memory" : "memories"}:`;
992
+ return text([header, ...hits.map(({ memory: m }) => formatMemory(m, projectId))].join("\n"));
993
+ }
994
+ case "memory_get": {
995
+ const { id } = parsed.data;
996
+ const found = await memory.get(id, namespaces);
997
+ return found ? text(formatMemory(found, projectId, true)) : failure(`Memory not found in this project: ${id}`);
998
+ }
999
+ case "memory_delete": {
1000
+ const { id } = parsed.data;
1001
+ await memory.forget(id, namespaces);
1002
+ return text(`Deleted ${id}.`);
1003
+ }
1004
+ case "memory_stats": {
1005
+ const counts = await memory.stats(namespaces);
1006
+ return text(
1007
+ `Project ${projectId}${cwd ? ` (${cwd})` : ""}: ${counts[project] ?? 0} memories. Global: ${counts[GLOBAL_NAMESPACE] ?? 0}.`
1008
+ );
1009
+ }
1010
+ }
1011
+ } catch (error) {
1012
+ if (error instanceof MemStackError && error.code === "NOT_FOUND") {
1013
+ return failure(`Memory not found in this project: ${parsed.data.id ?? ""}`);
1014
+ }
1015
+ return failure(error instanceof Error ? error.message : String(error));
1016
+ }
1017
+ });
1018
+ return server;
1019
+ }
1020
+ function formatMemory(m, projectId, detailed = false) {
1021
+ const scope = m.actorId === GLOBAL_NAMESPACE ? "global" : "project";
1022
+ const source = m.metadata?.source?.harness;
1023
+ const meta = [m.memoryType, scope, source && `from ${source}`, m.createdAt.toISOString().slice(0, 10)].filter(Boolean);
1024
+ const line = `- [${m.id}] ${m.content} (${meta.join(", ")})`;
1025
+ if (!detailed) return line;
1026
+ return `${line}
1027
+ importance: ${m.importance}
1028
+ tags: ${m.tags.join(", ") || "none"}
1029
+ project: ${scope === "project" ? projectId : "all"}`;
1030
+ }
1031
+ function text(message) {
1032
+ return { content: [{ type: "text", text: message }] };
1033
+ }
1034
+ function failure(message) {
1035
+ return { content: [{ type: "text", text: message }], isError: true };
1036
+ }
1037
+ var HOOK_MAX_CHARS = 6e3;
1038
+ var HOOK_LIMIT = 15;
1039
+ async function sessionStartContext(input, env = process.env) {
1040
+ const cwd = env.CLAUDE_PROJECT_DIR ?? input.cwd ?? process.cwd();
1041
+ const project = resolveProject(cwd);
1042
+ const { config } = await loadConfig();
1043
+ const storage = config.storage ?? new InMemoryStorageAdapter();
1044
+ try {
1045
+ const memory = new HarnessMemory({ storage, llm: config.llm, autoTags: false });
1046
+ await memory.adoptProjects(project.previousIds, project.id);
1047
+ const { hits } = await memory.recall({
1048
+ namespaces: defaultRecallNamespaces(project.id),
1049
+ limit: HOOK_LIMIT,
1050
+ maxChars: HOOK_MAX_CHARS
1051
+ });
1052
+ if (hits.length === 0) return "";
1053
+ return [
1054
+ `MemStack project memory (project ${project.id}), most important first. Follow these unless the user says otherwise; use memory_retrieve for more and memory_store to save new facts.`,
1055
+ ...hits.map(({ memory: m }) => formatLine(m))
1056
+ ].join("\n");
1057
+ } finally {
1058
+ await storage.close();
1059
+ }
1060
+ }
1061
+ function formatLine(m) {
1062
+ const scope = m.actorId === GLOBAL_NAMESPACE ? "global" : "project";
1063
+ return `- [${m.id}] ${m.content} (${m.memoryType}, ${scope})`;
1064
+ }
713
1065
 
714
- export { createServer, loadConfig };
1066
+ export { HARNESS_INSTRUCTIONS, HOOK_MAX_CHARS, createHarnessServer, createServer, loadConfig2 as loadConfig, sessionStartContext };
715
1067
  //# sourceMappingURL=index.js.map
716
1068
  //# sourceMappingURL=index.js.map