@jcyamacho/agent-memory 0.0.15 → 0.0.17

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.
Files changed (3) hide show
  1. package/README.md +14 -62
  2. package/dist/index.js +89 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,69 +27,17 @@ Codex CLI:
27
27
  codex mcp add memory -- npx -y @jcyamacho/agent-memory
28
28
  ```
29
29
 
30
- Example MCP server config:
31
-
32
- ```json
33
- {
34
- "mcpServers": {
35
- "memory": {
36
- "command": "npx",
37
- "args": [
38
- "-y",
39
- "@jcyamacho/agent-memory"
40
- ]
41
- }
42
- }
43
- }
44
- ```
45
-
46
- With a custom database path:
47
-
48
- ```json
49
- {
50
- "mcpServers": {
51
- "memory": {
52
- "command": "npx",
53
- "args": [
54
- "-y",
55
- "@jcyamacho/agent-memory"
56
- ],
57
- "env": {
58
- "AGENT_MEMORY_DB_PATH": "/absolute/path/to/memory.db"
59
- }
60
- }
61
- }
62
- }
63
- ```
64
-
65
- With a custom model cache path:
66
-
67
- ```json
68
- {
69
- "mcpServers": {
70
- "memory": {
71
- "command": "npx",
72
- "args": [
73
- "-y",
74
- "@jcyamacho/agent-memory"
75
- ],
76
- "env": {
77
- "AGENT_MEMORY_MODELS_CACHE_PATH": "/absolute/path/to/models"
78
- }
79
- }
80
- }
81
- }
82
- ```
83
-
84
30
  Optional LLM instructions to reinforce the MCP's built-in guidance:
85
31
 
86
32
  ```text
87
- Use `recall` at conversation start and before design choices, conventions, or
88
- edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not
89
- questions or sentences. `recall` is lexical-first; if it misses, retry once
90
- with overlapping alternate terms. Use `remember` for one durable fact, then
91
- use `revise` instead of duplicates and `forget` for wrong or obsolete
92
- memories. Always pass workspace unless the memory is truly global.
33
+ Use `memory_recall` at conversation start and before design choices,
34
+ conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact
35
+ phrases, not
36
+ questions or sentences. `memory_recall` is lexical-first; if it misses, retry once
37
+ with overlapping alternate terms. Use `memory_remember` for one durable fact, then
38
+ use `memory_revise` instead of duplicates and `memory_forget` for wrong or obsolete
39
+ memories. Always pass workspace unless the memory is truly global. Git worktree
40
+ paths are canonicalized to the main repo root on save and recall.
93
41
  ```
94
42
 
95
43
  ## What It Stores
@@ -147,6 +95,10 @@ If you omit `workspace`, recall still uses text relevance, embedding similarity,
147
95
  and recency. For best results, pass `workspace` whenever you have one. Save
148
96
  memories without a workspace only when they apply across all projects.
149
97
 
98
+ When you save a memory from a git worktree, `agent-memory` stores the main repo
99
+ root as the workspace. `recall` applies the same normalization to incoming
100
+ workspace queries so linked worktrees still match repo-scoped memories exactly.
101
+
150
102
  ## Database location
151
103
 
152
104
  By default, the SQLite database is created at:
@@ -187,8 +139,8 @@ Set `AGENT_MEMORY_MODELS_CACHE_PATH` when you want to:
187
139
  - share the model cache across reinstalls or multiple clients
188
140
  - store model downloads somewhere easier to inspect or manage
189
141
 
190
- Beta note: schema changes are not migrated. If you are upgrading from an older
191
- beta, delete the existing memory DB and let the server create a new one.
142
+ Schema changes are migrated automatically, including workspace normalization for
143
+ existing git worktree memories when the original path can still be resolved.
192
144
 
193
145
  ## Development
194
146
 
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.15";
12467
+ var version2 = "0.0.17";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -20146,19 +20146,22 @@ var MAX_LIST_LIMIT = 100;
20146
20146
  class MemoryService {
20147
20147
  repository;
20148
20148
  embeddingService;
20149
- constructor(repository, embeddingService) {
20149
+ workspaceResolver;
20150
+ constructor(repository, embeddingService, workspaceResolver) {
20150
20151
  this.repository = repository;
20151
20152
  this.embeddingService = embeddingService;
20153
+ this.workspaceResolver = workspaceResolver;
20152
20154
  }
20153
20155
  async create(input) {
20154
20156
  const content = input.content.trim();
20155
20157
  if (!content) {
20156
20158
  throw new ValidationError("Memory content is required.");
20157
20159
  }
20160
+ const workspace = await this.workspaceResolver.resolve(input.workspace);
20158
20161
  const memory = await this.repository.create({
20159
20162
  content,
20160
20163
  embedding: await this.embeddingService.createVector(content),
20161
- workspace: normalizeOptionalString(input.workspace)
20164
+ workspace
20162
20165
  });
20163
20166
  return toPublicMemoryRecord(memory);
20164
20167
  }
@@ -20184,7 +20187,7 @@ class MemoryService {
20184
20187
  return memory ? toPublicMemoryRecord(memory) : undefined;
20185
20188
  }
20186
20189
  async list(input) {
20187
- const workspace = normalizeOptionalString(input.workspace);
20190
+ const workspace = await this.workspaceResolver.resolve(input.workspace);
20188
20191
  const page = await this.repository.list({
20189
20192
  workspace,
20190
20193
  workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
@@ -20202,7 +20205,7 @@ class MemoryService {
20202
20205
  throw new ValidationError("At least one search term is required.");
20203
20206
  }
20204
20207
  const requestedLimit = normalizeLimit(input.limit);
20205
- const workspace = normalizeOptionalString(input.workspace);
20208
+ const workspace = await this.workspaceResolver.resolve(input.workspace);
20206
20209
  const normalizedQuery = {
20207
20210
  terms,
20208
20211
  limit: requestedLimit * RECALL_CANDIDATE_LIMIT_MULTIPLIER,
@@ -20259,10 +20262,6 @@ function normalizeListLimit(value) {
20259
20262
  }
20260
20263
  return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20261
20264
  }
20262
- function normalizeOptionalString(value) {
20263
- const trimmed = value?.trim();
20264
- return trimmed ? trimmed : undefined;
20265
- }
20266
20265
  function normalizeTerms(terms) {
20267
20266
  const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20268
20267
  return [...new Set(normalizedTerms)];
@@ -20272,7 +20271,7 @@ function normalizeTerms(terms) {
20272
20271
  var recallInputSchema = {
20273
20272
  terms: array(string2()).min(1).describe("Search terms for lexical memory lookup. Pass 2-5 short anchor-heavy terms or exact phrases as separate entries. Prefer identifiers, commands, file paths, package names, and conventions likely to appear verbatim in the memory. Avoid vague words, full sentences, and repeating the workspace name. If recall misses, retry once with overlapping alternate terms."),
20274
20273
  limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum matches to return. Keep this small when you only need the strongest hits."),
20275
- workspace: string2().optional().describe("Pass the current working directory. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
20274
+ workspace: string2().optional().describe("Pass the current working directory. Git worktree paths are normalized to the main repo root for matching. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
20276
20275
  updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp."),
20277
20276
  updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp.")
20278
20277
  };
@@ -20286,7 +20285,7 @@ ${content}
20286
20285
  }
20287
20286
  function registerRecallTool(server, memory) {
20288
20287
  server.registerTool("recall", {
20289
- description: "Retrieve relevant memories for the current task. Use at conversation start and before design choices, conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not questions or full sentences. `recall` is lexical-first; semantic reranking only reorders lexical matches. If it misses, retry once with overlapping alternate terms. Pass workspace. Returns `<memories>...</memories>` or a no-match hint.",
20288
+ description: "Retrieve relevant memories for the current task. Use at conversation start and before design choices, conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not questions or full sentences. `recall` is lexical-first; semantic reranking only reorders lexical matches. If it misses, retry once with overlapping alternate terms. Pass workspace; git worktree paths are normalized to the main repo root for matching. Returns `<memories>...</memories>` or a no-match hint.",
20290
20289
  inputSchema: recallInputSchema
20291
20290
  }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20292
20291
  try {
@@ -20313,7 +20312,7 @@ ${results.map(toMemoryXml).join(`
20313
20312
  // src/mcp/tools/remember.ts
20314
20313
  var rememberInputSchema = {
20315
20314
  content: string2().describe("One durable fact to save. Use a single self-contained sentence or short note with concrete nouns, identifiers, commands, file paths, or exact phrases the agent is likely to reuse."),
20316
- workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Omit only for truly global memories.")
20315
+ workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Git worktree paths are saved as the main repo root automatically. Omit only for truly global memories.")
20317
20316
  };
20318
20317
  function registerRememberTool(server, memory) {
20319
20318
  server.registerTool("remember", {
@@ -20370,7 +20369,7 @@ var SERVER_INSTRUCTIONS = [
20370
20369
  "Use `remember` for one durable fact when the user states a preference, corrects you, or a reusable project decision becomes clear.",
20371
20370
  "Call `recall` before `remember`; if the fact already exists, use `revise` instead of creating a duplicate.",
20372
20371
  "Use `revise` to correct an existing memory and `forget` to remove a wrong or obsolete one.",
20373
- "Pass workspace for project-scoped calls. Omit it only for truly global memories."
20372
+ "Pass workspace for project-scoped calls. Git worktree paths are canonicalized to the main repo root on save and recall. Omit workspace only for truly global memories."
20374
20373
  ].join(" ");
20375
20374
  function createMcpServer(memory, version3) {
20376
20375
  const server = new McpServer({
@@ -20502,7 +20501,7 @@ function toUint8Array(value) {
20502
20501
  }
20503
20502
 
20504
20503
  // src/sqlite/migrations/002-add-memory-embedding.ts
20505
- function createAddMemoryEmbeddingMigration(embeddingService = new EmbeddingService) {
20504
+ function createAddMemoryEmbeddingMigration(embeddingService) {
20506
20505
  return {
20507
20506
  version: 2,
20508
20507
  async up(database) {
@@ -20531,13 +20530,33 @@ function createAddMemoryEmbeddingMigration(embeddingService = new EmbeddingServi
20531
20530
  }
20532
20531
  };
20533
20532
  }
20534
- var addMemoryEmbeddingMigration = createAddMemoryEmbeddingMigration();
20533
+
20534
+ // src/sqlite/migrations/003-normalize-workspaces.ts
20535
+ function createNormalizeWorkspaceMigration(workspaceResolver) {
20536
+ return {
20537
+ version: 3,
20538
+ async up(database) {
20539
+ const rows = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace").all();
20540
+ const updateStatement = database.prepare("UPDATE memories SET workspace = ? WHERE workspace = ?");
20541
+ for (const row of rows) {
20542
+ const normalizedWorkspace = await workspaceResolver.resolve(row.workspace);
20543
+ if (!normalizedWorkspace || normalizedWorkspace === row.workspace) {
20544
+ continue;
20545
+ }
20546
+ updateStatement.run(normalizedWorkspace, row.workspace);
20547
+ }
20548
+ }
20549
+ };
20550
+ }
20535
20551
 
20536
20552
  // src/sqlite/migrations/index.ts
20537
- function createMemoryMigrations(embeddingService = new EmbeddingService) {
20538
- return [createMemorySchemaMigration, createAddMemoryEmbeddingMigration(embeddingService)];
20553
+ function createMemoryMigrations(options) {
20554
+ return [
20555
+ createMemorySchemaMigration,
20556
+ createAddMemoryEmbeddingMigration(options.embeddingService),
20557
+ createNormalizeWorkspaceMigration(options.workspaceResolver)
20558
+ ];
20539
20559
  }
20540
- var MEMORY_MIGRATIONS = createMemoryMigrations();
20541
20560
 
20542
20561
  // src/sqlite/db.ts
20543
20562
  var PRAGMA_STATEMENTS = [
@@ -20546,12 +20565,12 @@ var PRAGMA_STATEMENTS = [
20546
20565
  "foreign_keys = ON",
20547
20566
  "busy_timeout = 5000"
20548
20567
  ];
20549
- async function openMemoryDatabase(databasePath, options = {}) {
20568
+ async function openMemoryDatabase(databasePath, options) {
20550
20569
  let database;
20551
20570
  try {
20552
20571
  mkdirSync2(dirname(databasePath), { recursive: true });
20553
20572
  database = new Database(databasePath);
20554
- await initializeMemoryDatabase(database, createMemoryMigrations(options.embeddingService));
20573
+ await initializeMemoryDatabase(database, createMemoryMigrations(options));
20555
20574
  return database;
20556
20575
  } catch (error2) {
20557
20576
  database?.close();
@@ -20563,7 +20582,7 @@ async function openMemoryDatabase(databasePath, options = {}) {
20563
20582
  });
20564
20583
  }
20565
20584
  }
20566
- async function initializeMemoryDatabase(database, migrations = MEMORY_MIGRATIONS) {
20585
+ async function initializeMemoryDatabase(database, migrations) {
20567
20586
  try {
20568
20587
  applyPragmas(database);
20569
20588
  await runSqliteMigrations(database, migrations);
@@ -24195,12 +24214,59 @@ function startWebServer(memory, options) {
24195
24214
  return serve({ fetch: app.fetch, port: options.port });
24196
24215
  }
24197
24216
 
24217
+ // src/workspace-resolver.ts
24218
+ import { execFile } from "node:child_process";
24219
+ import { basename, dirname as dirname2 } from "node:path";
24220
+ import { promisify } from "node:util";
24221
+ var execFileAsync = promisify(execFile);
24222
+ function createGitWorkspaceResolver(options = {}) {
24223
+ const getGitCommonDir = options.getGitCommonDir ?? defaultGetGitCommonDir;
24224
+ const cache = new Map;
24225
+ return {
24226
+ async resolve(workspace) {
24227
+ const trimmed = normalizeOptionalString(workspace);
24228
+ if (!trimmed) {
24229
+ return;
24230
+ }
24231
+ const cached2 = cache.get(trimmed);
24232
+ if (cached2) {
24233
+ return cached2;
24234
+ }
24235
+ const pending = resolveWorkspace(trimmed, getGitCommonDir);
24236
+ cache.set(trimmed, pending);
24237
+ return pending;
24238
+ }
24239
+ };
24240
+ }
24241
+ async function resolveWorkspace(workspace, getGitCommonDir) {
24242
+ try {
24243
+ const gitCommonDir = (await getGitCommonDir(workspace)).trim();
24244
+ if (basename(gitCommonDir) !== ".git") {
24245
+ return workspace;
24246
+ }
24247
+ return dirname2(gitCommonDir);
24248
+ } catch {
24249
+ return workspace;
24250
+ }
24251
+ }
24252
+ async function defaultGetGitCommonDir(cwd) {
24253
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
24254
+ cwd
24255
+ });
24256
+ return stdout.trim();
24257
+ }
24258
+ function normalizeOptionalString(value) {
24259
+ const trimmed = value?.trim();
24260
+ return trimmed ? trimmed : undefined;
24261
+ }
24262
+
24198
24263
  // src/index.ts
24199
24264
  var config2 = resolveConfig();
24200
24265
  var embeddingService = new EmbeddingService({ modelsCachePath: config2.modelsCachePath });
24201
- var database = await openMemoryDatabase(config2.databasePath, { embeddingService });
24266
+ var workspaceResolver = createGitWorkspaceResolver();
24267
+ var database = await openMemoryDatabase(config2.databasePath, { embeddingService, workspaceResolver });
24202
24268
  var repository2 = new SqliteMemoryRepository(database);
24203
- var memoryService = new MemoryService(repository2, embeddingService);
24269
+ var memoryService = new MemoryService(repository2, embeddingService, workspaceResolver);
24204
24270
  if (config2.uiMode) {
24205
24271
  let shutdown = function() {
24206
24272
  server.close();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jcyamacho/agent-memory",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.15",
4
+ "version": "0.0.17",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },