repomemory 0.1.0 → 0.2.0

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 (62) hide show
  1. package/README.md +151 -104
  2. package/dist/commands/analyze.d.ts +2 -0
  3. package/dist/commands/analyze.d.ts.map +1 -1
  4. package/dist/commands/analyze.js +162 -188
  5. package/dist/commands/analyze.js.map +1 -1
  6. package/dist/commands/dashboard.d.ts +5 -0
  7. package/dist/commands/dashboard.d.ts.map +1 -0
  8. package/dist/commands/dashboard.js +520 -0
  9. package/dist/commands/dashboard.js.map +1 -0
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +33 -34
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/serve.d.ts.map +1 -1
  14. package/dist/commands/serve.js +2 -1
  15. package/dist/commands/serve.js.map +1 -1
  16. package/dist/commands/setup.d.ts.map +1 -1
  17. package/dist/commands/setup.js +151 -35
  18. package/dist/commands/setup.js.map +1 -1
  19. package/dist/commands/status.d.ts +4 -0
  20. package/dist/commands/status.d.ts.map +1 -0
  21. package/dist/commands/status.js +87 -0
  22. package/dist/commands/status.js.map +1 -0
  23. package/dist/commands/sync.d.ts.map +1 -1
  24. package/dist/commands/sync.js +57 -27
  25. package/dist/commands/sync.js.map +1 -1
  26. package/dist/commands/wizard.d.ts +4 -0
  27. package/dist/commands/wizard.d.ts.map +1 -0
  28. package/dist/commands/wizard.js +184 -0
  29. package/dist/commands/wizard.js.map +1 -0
  30. package/dist/index.js +37 -42
  31. package/dist/index.js.map +1 -1
  32. package/dist/lib/ai-provider.d.ts +11 -0
  33. package/dist/lib/ai-provider.d.ts.map +1 -1
  34. package/dist/lib/ai-provider.js +138 -69
  35. package/dist/lib/ai-provider.js.map +1 -1
  36. package/dist/lib/config.d.ts +11 -15
  37. package/dist/lib/config.d.ts.map +1 -1
  38. package/dist/lib/config.js +33 -21
  39. package/dist/lib/config.js.map +1 -1
  40. package/dist/lib/context-store.d.ts +11 -0
  41. package/dist/lib/context-store.d.ts.map +1 -1
  42. package/dist/lib/context-store.js +51 -18
  43. package/dist/lib/context-store.js.map +1 -1
  44. package/dist/lib/git.d.ts +1 -0
  45. package/dist/lib/git.d.ts.map +1 -1
  46. package/dist/lib/git.js +34 -20
  47. package/dist/lib/git.js.map +1 -1
  48. package/dist/lib/json-repair.d.ts +24 -0
  49. package/dist/lib/json-repair.d.ts.map +1 -0
  50. package/dist/lib/json-repair.js +140 -0
  51. package/dist/lib/json-repair.js.map +1 -0
  52. package/dist/lib/repo-scanner.d.ts.map +1 -1
  53. package/dist/lib/repo-scanner.js +103 -26
  54. package/dist/lib/repo-scanner.js.map +1 -1
  55. package/dist/lib/search.d.ts +10 -4
  56. package/dist/lib/search.d.ts.map +1 -1
  57. package/dist/lib/search.js +136 -51
  58. package/dist/lib/search.js.map +1 -1
  59. package/dist/mcp/server.d.ts.map +1 -1
  60. package/dist/mcp/server.js +128 -54
  61. package/dist/mcp/server.js.map +1 -1
  62. package/package.json +20 -8
@@ -1,17 +1,50 @@
1
- import Database from "better-sqlite3";
1
+ import { readFileSync, writeFileSync, existsSync } from "fs";
2
2
  import { join } from "path";
3
+ let initSqlJsPromise = null;
4
+ async function getSqlJs() {
5
+ if (!initSqlJsPromise) {
6
+ initSqlJsPromise = import("sql.js").then((mod) => {
7
+ const initSqlJs = mod.default;
8
+ return initSqlJs();
9
+ });
10
+ }
11
+ return initSqlJsPromise;
12
+ }
3
13
  export class SearchIndex {
4
- db;
14
+ db = null;
15
+ dbPath;
5
16
  store;
17
+ initialized = false;
6
18
  constructor(contextDir, store) {
7
- const dbPath = join(contextDir, ".search.db");
8
- this.db = new Database(dbPath);
19
+ this.dbPath = join(contextDir, ".search.db");
9
20
  this.store = store;
10
- this.init();
11
21
  }
12
- init() {
13
- this.db.pragma("journal_mode = WAL");
14
- this.db.exec(`
22
+ async ensureDb() {
23
+ if (this.db)
24
+ return this.db;
25
+ const SQL = await getSqlJs();
26
+ if (existsSync(this.dbPath)) {
27
+ try {
28
+ const buffer = readFileSync(this.dbPath);
29
+ this.db = new SQL.Database(buffer);
30
+ }
31
+ catch {
32
+ this.db = new SQL.Database();
33
+ }
34
+ }
35
+ else {
36
+ this.db = new SQL.Database();
37
+ }
38
+ if (!this.initialized) {
39
+ this.initSchema();
40
+ this.initialized = true;
41
+ }
42
+ return this.db;
43
+ }
44
+ initSchema() {
45
+ if (!this.db)
46
+ return;
47
+ this.db.run(`
15
48
  CREATE TABLE IF NOT EXISTS documents (
16
49
  id INTEGER PRIMARY KEY AUTOINCREMENT,
17
50
  category TEXT NOT NULL,
@@ -23,7 +56,7 @@ export class SearchIndex {
23
56
  UNIQUE(category, filename)
24
57
  )
25
58
  `);
26
- this.db.exec(`
59
+ this.db.run(`
27
60
  CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
28
61
  title,
29
62
  content,
@@ -33,20 +66,19 @@ export class SearchIndex {
33
66
  tokenize='porter unicode61'
34
67
  )
35
68
  `);
36
- // Triggers to keep FTS in sync
37
- this.db.exec(`
69
+ this.db.run(`
38
70
  CREATE TRIGGER IF NOT EXISTS documents_ai AFTER INSERT ON documents BEGIN
39
71
  INSERT INTO documents_fts(rowid, title, content, category)
40
72
  VALUES (new.id, new.title, new.content, new.category);
41
73
  END
42
74
  `);
43
- this.db.exec(`
75
+ this.db.run(`
44
76
  CREATE TRIGGER IF NOT EXISTS documents_ad AFTER DELETE ON documents BEGIN
45
77
  INSERT INTO documents_fts(documents_fts, rowid, title, content, category)
46
78
  VALUES ('delete', old.id, old.title, old.content, old.category);
47
79
  END
48
80
  `);
49
- this.db.exec(`
81
+ this.db.run(`
50
82
  CREATE TRIGGER IF NOT EXISTS documents_au AFTER UPDATE ON documents BEGIN
51
83
  INSERT INTO documents_fts(documents_fts, rowid, title, content, category)
52
84
  VALUES ('delete', old.id, old.title, old.content, old.category);
@@ -55,38 +87,53 @@ export class SearchIndex {
55
87
  END
56
88
  `);
57
89
  }
58
- rebuild() {
59
- // Clear existing data
60
- this.db.exec("DELETE FROM documents");
90
+ async rebuild() {
91
+ const db = await this.ensureDb();
92
+ db.run("DELETE FROM documents");
61
93
  const entries = this.store.listEntries();
62
- const insert = this.db.prepare(`
63
- INSERT OR REPLACE INTO documents (category, filename, title, content, relative_path, updated_at)
64
- VALUES (?, ?, ?, ?, ?, ?)
65
- `);
66
- const tx = this.db.transaction((entries) => {
67
- for (const entry of entries) {
68
- insert.run(entry.category, entry.filename, entry.title, entry.content, entry.relativePath, entry.lastModified.toISOString());
69
- }
70
- });
71
- tx(entries);
94
+ for (const entry of entries) {
95
+ db.run(`INSERT OR REPLACE INTO documents (category, filename, title, content, relative_path, updated_at)
96
+ VALUES (?, ?, ?, ?, ?, ?)`, [
97
+ entry.category,
98
+ entry.filename,
99
+ entry.title,
100
+ entry.content,
101
+ entry.relativePath,
102
+ entry.lastModified.toISOString(),
103
+ ]);
104
+ }
105
+ this.save();
106
+ }
107
+ async indexEntry(entry) {
108
+ const db = await this.ensureDb();
109
+ // Upsert: delete then insert to trigger FTS sync
110
+ db.run("DELETE FROM documents WHERE category = ? AND filename = ?", [entry.category, entry.filename]);
111
+ db.run(`INSERT INTO documents (category, filename, title, content, relative_path, updated_at)
112
+ VALUES (?, ?, ?, ?, ?, ?)`, [
113
+ entry.category,
114
+ entry.filename,
115
+ entry.title,
116
+ entry.content,
117
+ entry.relativePath,
118
+ entry.lastModified.toISOString(),
119
+ ]);
120
+ this.save();
72
121
  }
73
- indexEntry(entry) {
74
- this.db
75
- .prepare(`
76
- INSERT OR REPLACE INTO documents (category, filename, title, content, relative_path, updated_at)
77
- VALUES (?, ?, ?, ?, ?, ?)
78
- `)
79
- .run(entry.category, entry.filename, entry.title, entry.content, entry.relativePath, entry.lastModified.toISOString());
122
+ async removeEntry(category, filename) {
123
+ const db = await this.ensureDb();
124
+ db.run("DELETE FROM documents WHERE category = ? AND filename = ?", [category, filename]);
125
+ this.save();
80
126
  }
81
- search(query, category, limit = 10) {
82
- // Escape special FTS5 characters
83
- const escapedQuery = query
127
+ async search(query, category, limit = 10) {
128
+ const db = await this.ensureDb();
129
+ // Build FTS5 query: use AND semantics (implicit AND in FTS5)
130
+ const terms = query
84
131
  .replace(/['"]/g, "")
85
132
  .split(/\s+/)
86
133
  .filter(Boolean)
87
134
  .map((term) => `"${term}"`)
88
- .join(" OR ");
89
- if (!escapedQuery)
135
+ .join(" ");
136
+ if (!terms)
90
137
  return [];
91
138
  let sql = `
92
139
  SELECT
@@ -100,27 +147,51 @@ export class SearchIndex {
100
147
  JOIN documents d ON d.id = documents_fts.rowid
101
148
  WHERE documents_fts MATCH ?
102
149
  `;
103
- const params = [escapedQuery];
150
+ const params = [terms];
104
151
  if (category) {
105
152
  sql += " AND d.category = ?";
106
153
  params.push(category);
107
154
  }
108
155
  sql += " ORDER BY rank LIMIT ?";
109
156
  params.push(limit);
110
- const rows = this.db.prepare(sql).all(...params);
111
- return rows.map((row) => ({
112
- category: row.category,
113
- filename: row.filename,
114
- title: row.title,
115
- snippet: this.extractSnippet(row.content, query),
116
- score: row.score,
117
- relativePath: row.relative_path,
157
+ try {
158
+ const results = db.exec(sql, params);
159
+ if (!results.length || !results[0].values.length) {
160
+ // Fallback: try OR semantics if AND returned nothing
161
+ const orTerms = query
162
+ .replace(/['"]/g, "")
163
+ .split(/\s+/)
164
+ .filter(Boolean)
165
+ .map((term) => `"${term}"`)
166
+ .join(" OR ");
167
+ if (orTerms !== terms) {
168
+ params[0] = orTerms;
169
+ const orResults = db.exec(sql, params);
170
+ if (orResults.length && orResults[0].values.length) {
171
+ return this.mapResults(orResults[0], query);
172
+ }
173
+ }
174
+ return [];
175
+ }
176
+ return this.mapResults(results[0], query);
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ mapResults(result, query) {
183
+ return result.values.map((row) => ({
184
+ category: row[0],
185
+ filename: row[1],
186
+ title: row[2],
187
+ snippet: this.extractSnippet(row[3], query),
188
+ score: row[5],
189
+ relativePath: row[4],
118
190
  }));
119
191
  }
120
192
  extractSnippet(content, query, maxLength = 500) {
121
193
  const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
122
194
  const lines = content.split("\n");
123
- // Find lines containing query terms
124
195
  const matchingLines = [];
125
196
  for (let i = 0; i < lines.length; i++) {
126
197
  const lower = lines[i].toLowerCase();
@@ -132,16 +203,30 @@ export class SearchIndex {
132
203
  if (matchingLines.length === 0) {
133
204
  return content.slice(0, maxLength);
134
205
  }
135
- // Sort by match count, take best matches
136
206
  matchingLines.sort((a, b) => b.matchCount - a.matchCount);
137
- // Build snippet from best matching region
138
207
  const bestMatch = matchingLines[0];
139
208
  const start = Math.max(0, bestMatch.index - 2);
140
209
  const end = Math.min(lines.length, bestMatch.index + 5);
141
210
  return lines.slice(start, end).join("\n").slice(0, maxLength);
142
211
  }
212
+ save() {
213
+ if (!this.db)
214
+ return;
215
+ try {
216
+ const data = this.db.export();
217
+ writeFileSync(this.dbPath, Buffer.from(data));
218
+ }
219
+ catch {
220
+ // Silently fail — search degradation is acceptable
221
+ }
222
+ }
143
223
  close() {
144
- this.db.close();
224
+ if (this.db) {
225
+ this.save();
226
+ this.db.close();
227
+ this.db = null;
228
+ }
229
+ this.initialized = false;
145
230
  }
146
231
  }
147
232
  //# sourceMappingURL=search.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAwXf"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAI1D,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAgdf"}
@@ -3,14 +3,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
4
  import { ContextStore } from "../lib/context-store.js";
5
5
  import { SearchIndex } from "../lib/search.js";
6
+ const VALID_CATEGORIES = ["facts", "decisions", "regressions", "sessions", "changelog"];
6
7
  export async function startMcpServer(repoRoot, config) {
7
8
  const store = new ContextStore(repoRoot, config);
8
9
  let searchIndex = null;
9
- // Initialize search index if .context exists
10
10
  if (store.exists()) {
11
11
  try {
12
12
  searchIndex = new SearchIndex(store.path, store);
13
- searchIndex.rebuild();
13
+ await searchIndex.rebuild();
14
14
  }
15
15
  catch (e) {
16
16
  console.error("Warning: Could not initialize search index:", e);
@@ -18,7 +18,7 @@ export async function startMcpServer(repoRoot, config) {
18
18
  }
19
19
  const server = new Server({
20
20
  name: "repomemory",
21
- version: "0.1.0",
21
+ version: "0.2.0",
22
22
  }, {
23
23
  capabilities: {
24
24
  tools: {},
@@ -31,7 +31,7 @@ export async function startMcpServer(repoRoot, config) {
31
31
  tools: [
32
32
  {
33
33
  name: "context_search",
34
- description: "Search the repository's persistent knowledge base. Returns relevant facts, decisions, regressions, and session notes. Use this at the start of a task to find relevant context, or when you need to understand why something is the way it is.",
34
+ description: "Search the repository's persistent knowledge base. Returns relevant facts, decisions, regressions, and session notes. Use this FIRST at the start of every task to find relevant context, or when you need to understand why something is the way it is. This prevents re-discovering architecture and re-debating past decisions.",
35
35
  inputSchema: {
36
36
  type: "object",
37
37
  properties: {
@@ -41,7 +41,7 @@ export async function startMcpServer(repoRoot, config) {
41
41
  },
42
42
  category: {
43
43
  type: "string",
44
- enum: ["facts", "decisions", "regressions", "sessions", "changelog"],
44
+ enum: VALID_CATEGORIES,
45
45
  description: "Optional: filter results to a specific category. Omit to search all.",
46
46
  },
47
47
  limit: {
@@ -60,7 +60,7 @@ export async function startMcpServer(repoRoot, config) {
60
60
  properties: {
61
61
  category: {
62
62
  type: "string",
63
- enum: ["facts", "decisions", "regressions", "sessions", "changelog"],
63
+ enum: VALID_CATEGORIES,
64
64
  description: `Category for the knowledge:
65
65
  - facts: Architecture, patterns, how things work
66
66
  - decisions: Why something was chosen (include alternatives considered)
@@ -84,15 +84,34 @@ export async function startMcpServer(repoRoot, config) {
84
84
  required: ["category", "filename", "content"],
85
85
  },
86
86
  },
87
+ {
88
+ name: "context_delete",
89
+ description: "Delete a knowledge entry from the repository context. Use this to remove stale or incorrect information. Knowledge quality matters more than quantity — prune aggressively.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ category: {
94
+ type: "string",
95
+ enum: VALID_CATEGORIES,
96
+ description: "The category of the entry to delete.",
97
+ },
98
+ filename: {
99
+ type: "string",
100
+ description: "The filename to delete (with or without .md extension).",
101
+ },
102
+ },
103
+ required: ["category", "filename"],
104
+ },
105
+ },
87
106
  {
88
107
  name: "context_list",
89
- description: "List all knowledge entries in the repository context, optionally filtered by category. Returns filenames and titles for browsing.",
108
+ description: "List all knowledge entries in the repository context, optionally filtered by category. Returns filenames, titles, and age for browsing. Use this to understand what knowledge already exists before writing new entries.",
90
109
  inputSchema: {
91
110
  type: "object",
92
111
  properties: {
93
112
  category: {
94
113
  type: "string",
95
- enum: ["facts", "decisions", "regressions", "sessions", "changelog"],
114
+ enum: VALID_CATEGORIES,
96
115
  description: "Optional: filter to a specific category.",
97
116
  },
98
117
  },
@@ -124,22 +143,29 @@ export async function startMcpServer(repoRoot, config) {
124
143
  switch (name) {
125
144
  case "context_search": {
126
145
  const { query, category, limit = 5 } = args;
146
+ // Validate category if provided
147
+ if (category && !VALID_CATEGORIES.includes(category)) {
148
+ return {
149
+ content: [{
150
+ type: "text",
151
+ text: `Invalid category: ${category}. Valid categories: ${VALID_CATEGORIES.join(", ")}`,
152
+ }],
153
+ isError: true,
154
+ };
155
+ }
127
156
  if (!store.exists()) {
128
157
  return {
129
- content: [
130
- {
158
+ content: [{
131
159
  type: "text",
132
160
  text: "No .context/ directory found. Run `repomemory init && repomemory analyze` to set up.",
133
- },
134
- ],
161
+ }],
135
162
  };
136
163
  }
137
- // Rebuild index if needed
138
164
  if (!searchIndex) {
139
165
  searchIndex = new SearchIndex(store.path, store);
140
- searchIndex.rebuild();
166
+ await searchIndex.rebuild();
141
167
  }
142
- const results = searchIndex.search(query, category, limit);
168
+ const results = await searchIndex.search(query, category, limit);
143
169
  if (results.length === 0) {
144
170
  // Fallback to simple text search
145
171
  const entries = store.listEntries(category);
@@ -150,12 +176,10 @@ export async function startMcpServer(repoRoot, config) {
150
176
  .slice(0, limit);
151
177
  if (matched.length === 0) {
152
178
  return {
153
- content: [
154
- {
179
+ content: [{
155
180
  type: "text",
156
181
  text: `No results found for "${query}"${category ? ` in ${category}` : ""}. Try a different query or browse with context_list.`,
157
- },
158
- ],
182
+ }],
159
183
  };
160
184
  }
161
185
  const text = matched
@@ -170,6 +194,16 @@ export async function startMcpServer(repoRoot, config) {
170
194
  }
171
195
  case "context_write": {
172
196
  const { category, filename, content, append = false } = args;
197
+ // Validate category
198
+ if (!VALID_CATEGORIES.includes(category)) {
199
+ return {
200
+ content: [{
201
+ type: "text",
202
+ text: `Invalid category: ${category}. Valid categories: ${VALID_CATEGORIES.join(", ")}`,
203
+ }],
204
+ isError: true,
205
+ };
206
+ }
173
207
  if (!store.exists()) {
174
208
  store.scaffold();
175
209
  }
@@ -180,40 +214,79 @@ export async function startMcpServer(repoRoot, config) {
180
214
  else {
181
215
  relativePath = store.writeEntry(category, filename, content);
182
216
  }
183
- // Update search index
217
+ // Incremental index update (not full rebuild)
184
218
  if (searchIndex) {
185
- searchIndex.rebuild();
219
+ const entries = store.listEntries(category);
220
+ const entry = entries.find((e) => e.relativePath === relativePath || e.filename === filename + ".md");
221
+ if (entry) {
222
+ await searchIndex.indexEntry(entry);
223
+ }
186
224
  }
187
225
  return {
188
- content: [
189
- {
226
+ content: [{
190
227
  type: "text",
191
- text: `✓ Written to ${relativePath}${append ? " (appended)" : ""}. This knowledge will persist across sessions.`,
192
- },
193
- ],
228
+ text: `\u2713 Written to ${relativePath}${append ? " (appended)" : ""}. This knowledge will persist across sessions.`,
229
+ }],
230
+ };
231
+ }
232
+ case "context_delete": {
233
+ const { category, filename } = args;
234
+ if (!VALID_CATEGORIES.includes(category)) {
235
+ return {
236
+ content: [{
237
+ type: "text",
238
+ text: `Invalid category: ${category}. Valid categories: ${VALID_CATEGORIES.join(", ")}`,
239
+ }],
240
+ isError: true,
241
+ };
242
+ }
243
+ const fname = filename.endsWith(".md") ? filename : filename + ".md";
244
+ const deleted = store.deleteEntry(category, fname);
245
+ if (!deleted) {
246
+ return {
247
+ content: [{
248
+ type: "text",
249
+ text: `File not found: ${category}/${fname}. Use context_list to see available files.`,
250
+ }],
251
+ };
252
+ }
253
+ // Remove from search index
254
+ if (searchIndex) {
255
+ await searchIndex.removeEntry(category, fname);
256
+ }
257
+ return {
258
+ content: [{
259
+ type: "text",
260
+ text: `\u2713 Deleted ${category}/${fname}. Stale knowledge removed.`,
261
+ }],
194
262
  };
195
263
  }
196
264
  case "context_list": {
197
265
  const { category } = (args || {});
266
+ if (category && !VALID_CATEGORIES.includes(category)) {
267
+ return {
268
+ content: [{
269
+ type: "text",
270
+ text: `Invalid category: ${category}. Valid categories: ${VALID_CATEGORIES.join(", ")}`,
271
+ }],
272
+ isError: true,
273
+ };
274
+ }
198
275
  if (!store.exists()) {
199
276
  return {
200
- content: [
201
- {
277
+ content: [{
202
278
  type: "text",
203
279
  text: "No .context/ directory found. Run `repomemory init` first.",
204
- },
205
- ],
280
+ }],
206
281
  };
207
282
  }
208
283
  const entries = store.listEntries(category);
209
284
  if (entries.length === 0) {
210
285
  return {
211
- content: [
212
- {
286
+ content: [{
213
287
  type: "text",
214
288
  text: `No entries found${category ? ` in ${category}` : ""}. Run \`repomemory analyze\` to populate, or use context_write to add entries.`,
215
- },
216
- ],
289
+ }],
217
290
  };
218
291
  }
219
292
  const grouped = {};
@@ -228,7 +301,7 @@ export async function startMcpServer(repoRoot, config) {
228
301
  for (const entry of catEntries) {
229
302
  const sizeKb = (entry.sizeBytes / 1024).toFixed(1);
230
303
  const age = getRelativeTime(entry.lastModified);
231
- text += `- **${entry.filename}** ${entry.title} (${sizeKb}KB, ${age})\n`;
304
+ text += `- **${entry.filename}** \u2014 ${entry.title} (${sizeKb}KB, ${age})\n`;
232
305
  }
233
306
  text += "\n";
234
307
  }
@@ -240,36 +313,30 @@ export async function startMcpServer(repoRoot, config) {
240
313
  const content = store.readEntry(category, fname);
241
314
  if (!content) {
242
315
  return {
243
- content: [
244
- {
316
+ content: [{
245
317
  type: "text",
246
318
  text: `File not found: ${category}/${fname}. Use context_list to see available files.`,
247
- },
248
- ],
319
+ }],
249
320
  };
250
321
  }
251
322
  return {
252
- content: [
253
- {
323
+ content: [{
254
324
  type: "text",
255
325
  text: `# ${category}/${fname}\n\n${content}`,
256
- },
257
- ],
326
+ }],
258
327
  };
259
328
  }
260
329
  default:
261
330
  return {
262
- content: [
263
- {
331
+ content: [{
264
332
  type: "text",
265
333
  text: `Unknown tool: ${name}`,
266
- },
267
- ],
334
+ }],
268
335
  isError: true,
269
336
  };
270
337
  }
271
338
  });
272
- // --- Resources (expose .context/ files as MCP resources) ---
339
+ // --- Resources ---
273
340
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
274
341
  if (!store.exists()) {
275
342
  return { resources: [] };
@@ -296,21 +363,28 @@ export async function startMcpServer(repoRoot, config) {
296
363
  throw new Error(`Resource not found: ${uri}`);
297
364
  }
298
365
  return {
299
- contents: [
300
- {
366
+ contents: [{
301
367
  uri,
302
368
  mimeType: "text/markdown",
303
369
  text: content,
304
- },
305
- ],
370
+ }],
306
371
  };
307
372
  });
308
- // Connect via stdio
373
+ // Graceful shutdown
374
+ const cleanup = () => {
375
+ if (searchIndex) {
376
+ searchIndex.close();
377
+ searchIndex = null;
378
+ }
379
+ process.exit(0);
380
+ };
381
+ process.on("SIGTERM", cleanup);
382
+ process.on("SIGINT", cleanup);
309
383
  const transport = new StdioServerTransport();
310
384
  await server.connect(transport);
311
385
  }
312
386
  function getRelativeTime(date) {
313
- const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
387
+ const seconds = Math.max(0, Math.floor((Date.now() - date.getTime()) / 1000));
314
388
  if (seconds < 60)
315
389
  return "just now";
316
390
  if (seconds < 3600)