memory-journal-mcp 4.3.0 → 4.4.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 (109) hide show
  1. package/.dockerignore +131 -122
  2. package/.gitattributes +29 -0
  3. package/.github/workflows/docker-publish.yml +1 -1
  4. package/.github/workflows/lint-and-test.yml +1 -2
  5. package/.github/workflows/secrets-scanning.yml +0 -1
  6. package/.github/workflows/security-update.yml +6 -6
  7. package/.vscode/settings.json +17 -15
  8. package/CHANGELOG.md +1065 -11
  9. package/DOCKER_README.md +51 -33
  10. package/Dockerfile +14 -12
  11. package/README.md +68 -33
  12. package/SECURITY.md +225 -220
  13. package/dist/cli.js +7 -0
  14. package/dist/cli.js.map +1 -1
  15. package/dist/constants/ServerInstructions.d.ts +1 -1
  16. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  17. package/dist/constants/ServerInstructions.js +70 -26
  18. package/dist/constants/ServerInstructions.js.map +1 -1
  19. package/dist/constants/icons.d.ts +2 -0
  20. package/dist/constants/icons.d.ts.map +1 -1
  21. package/dist/constants/icons.js +6 -0
  22. package/dist/constants/icons.js.map +1 -1
  23. package/dist/database/SqliteAdapter.d.ts +51 -10
  24. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  25. package/dist/database/SqliteAdapter.js +143 -43
  26. package/dist/database/SqliteAdapter.js.map +1 -1
  27. package/dist/filtering/ToolFilter.d.ts +1 -1
  28. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  29. package/dist/filtering/ToolFilter.js +7 -1
  30. package/dist/filtering/ToolFilter.js.map +1 -1
  31. package/dist/github/GitHubIntegration.d.ts +74 -2
  32. package/dist/github/GitHubIntegration.d.ts.map +1 -1
  33. package/dist/github/GitHubIntegration.js +508 -7
  34. package/dist/github/GitHubIntegration.js.map +1 -1
  35. package/dist/handlers/prompts/index.js +1 -0
  36. package/dist/handlers/prompts/index.js.map +1 -1
  37. package/dist/handlers/resources/index.d.ts.map +1 -1
  38. package/dist/handlers/resources/index.js +257 -13
  39. package/dist/handlers/resources/index.js.map +1 -1
  40. package/dist/handlers/tools/index.d.ts.map +1 -1
  41. package/dist/handlers/tools/index.js +595 -8
  42. package/dist/handlers/tools/index.js.map +1 -1
  43. package/dist/server/McpServer.d.ts +2 -0
  44. package/dist/server/McpServer.d.ts.map +1 -1
  45. package/dist/server/McpServer.js +69 -26
  46. package/dist/server/McpServer.js.map +1 -1
  47. package/dist/types/index.d.ts +97 -0
  48. package/dist/types/index.d.ts.map +1 -1
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/utils/logger.d.ts +1 -0
  51. package/dist/utils/logger.d.ts.map +1 -1
  52. package/dist/utils/logger.js +8 -1
  53. package/dist/utils/logger.js.map +1 -1
  54. package/dist/utils/progress-utils.d.ts +18 -3
  55. package/dist/utils/progress-utils.d.ts.map +1 -1
  56. package/dist/utils/progress-utils.js.map +1 -1
  57. package/dist/utils/security-utils.d.ts +91 -0
  58. package/dist/utils/security-utils.d.ts.map +1 -0
  59. package/dist/utils/security-utils.js +184 -0
  60. package/dist/utils/security-utils.js.map +1 -0
  61. package/dist/vector/VectorSearchManager.d.ts +2 -1
  62. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  63. package/dist/vector/VectorSearchManager.js +100 -34
  64. package/dist/vector/VectorSearchManager.js.map +1 -1
  65. package/docker-compose.yml +46 -37
  66. package/mcp-config-example.json +0 -2
  67. package/package.json +21 -14
  68. package/releases/v4.3.1.md +69 -0
  69. package/releases/v4.4.0.md +120 -0
  70. package/server.json +3 -3
  71. package/src/cli.ts +11 -0
  72. package/src/constants/ServerInstructions.ts +70 -26
  73. package/src/constants/icons.ts +7 -0
  74. package/src/database/SqliteAdapter.ts +165 -44
  75. package/src/filtering/ToolFilter.ts +7 -1
  76. package/src/github/GitHubIntegration.ts +588 -8
  77. package/src/handlers/prompts/index.ts +1 -0
  78. package/src/handlers/resources/index.ts +318 -12
  79. package/src/handlers/tools/index.ts +686 -13
  80. package/src/server/McpServer.ts +79 -37
  81. package/src/types/index.ts +98 -0
  82. package/src/utils/logger.ts +10 -1
  83. package/src/utils/progress-utils.ts +17 -6
  84. package/src/utils/security-utils.ts +205 -0
  85. package/src/vector/VectorSearchManager.ts +110 -39
  86. package/tests/constants/icons.test.ts +102 -0
  87. package/tests/constants/server-instructions.test.ts +549 -0
  88. package/tests/database/sqlite-adapter.bench.ts +63 -0
  89. package/tests/database/sqlite-adapter.test.ts +555 -0
  90. package/tests/filtering/tool-filter.test.ts +266 -0
  91. package/tests/github/github-integration.test.ts +1024 -0
  92. package/tests/handlers/github-resource-handlers.test.ts +473 -0
  93. package/tests/handlers/github-tool-handlers.test.ts +556 -0
  94. package/tests/handlers/prompt-handlers.test.ts +91 -0
  95. package/tests/handlers/resource-handlers.test.ts +339 -0
  96. package/tests/handlers/tool-handlers.test.ts +497 -0
  97. package/tests/handlers/vector-tool-handlers.test.ts +238 -0
  98. package/tests/security/sql-injection.test.ts +347 -0
  99. package/tests/server/mcp-server.bench.ts +55 -0
  100. package/tests/server/mcp-server.test.ts +675 -0
  101. package/tests/utils/logger.test.ts +180 -0
  102. package/tests/utils/mcp-logger.test.ts +212 -0
  103. package/tests/utils/progress-utils.test.ts +156 -0
  104. package/tests/utils/security-utils.test.ts +82 -0
  105. package/tests/vector/vector-search-manager.test.ts +335 -0
  106. package/tests/vector/vector-search.bench.ts +53 -0
  107. package/vitest.config.ts +15 -0
  108. package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
  109. package/.github/workflows/dependabot-auto-merge.yml +0 -42
@@ -20,6 +20,12 @@ const DEFAULT_MODEL = 'Xenova/all-MiniLM-L6-v2'
20
20
  /** Embedding dimensions for all-MiniLM-L6-v2 */
21
21
  const EMBEDDING_DIMENSIONS = 384
22
22
 
23
+ /** Number of entries to embed concurrently during rebuild */
24
+ const REBUILD_BATCH_SIZE = 5
25
+
26
+ /** Number of entries to fetch per page during rebuild */
27
+ const REBUILD_PAGE_SIZE = 200
28
+
23
29
  /** Search result with similarity score */
24
30
  export interface SemanticSearchResult {
25
31
  entryId: number
@@ -225,7 +231,8 @@ export class VectorSearchManager {
225
231
  }
226
232
 
227
233
  /**
228
- * Rebuild index from database entries
234
+ * Rebuild index from database entries.
235
+ * Uses paginated fetching and parallel batch embedding for performance.
229
236
  * @param db - Database adapter
230
237
  * @param progress - Optional progress context for notifications
231
238
  */
@@ -240,66 +247,130 @@ export class VectorSearchManager {
240
247
 
241
248
  logger.info('Rebuilding vector index from database...', { module: 'VectorSearch' })
242
249
 
243
- // Step 1: Get all entries from database
244
- const entries = db.getRecentEntries(10000) // Get up to 10k entries
245
- const dbIds = new Set(entries.map((e) => String(e.id)))
250
+ // Step 1: Get total entry count and build ID set for orphan detection
251
+ const totalEntries = db.getActiveEntryCount()
252
+ const dbIds = new Set<string>()
253
+
254
+ // Collect all active entry IDs via pagination (avoids loading all content at once)
255
+ for (let offset = 0; offset < totalEntries; offset += REBUILD_PAGE_SIZE) {
256
+ const page = db.getEntriesPage(offset, REBUILD_PAGE_SIZE)
257
+ for (const entry of page) {
258
+ dbIds.add(String(entry.id))
259
+ }
260
+ }
261
+
262
+ // Step 2: Clean up existing index items
263
+ // If the index is corrupted (e.g., from a process kill), recreate it
264
+ try {
265
+ const indexItems = await this.index.listItems()
266
+ let orphansRemoved = 0
267
+ for (const item of indexItems) {
268
+ if (!dbIds.has(item.id)) {
269
+ try {
270
+ await this.index.deleteItem(item.id)
271
+ orphansRemoved++
272
+ } catch {
273
+ // Ignore errors during cleanup
274
+ }
275
+ }
276
+ }
277
+ if (orphansRemoved > 0) {
278
+ logger.info(`Cleaned up ${String(orphansRemoved)} orphaned vector entries`, {
279
+ module: 'VectorSearch',
280
+ })
281
+ }
246
282
 
247
- // Step 2: Clean up orphaned entries from the index
248
- // These are vector entries for database records that were permanently deleted
249
- const indexItems = await this.index.listItems()
250
- let orphansRemoved = 0
251
- for (const item of indexItems) {
252
- if (!dbIds.has(item.id)) {
283
+ // Step 3: Delete all remaining items to prepare for clean re-index
284
+ // This avoids the double-delete overhead of calling addEntry (upsert) per item
285
+ const remainingItems = await this.index.listItems()
286
+ for (const item of remainingItems) {
253
287
  try {
254
288
  await this.index.deleteItem(item.id)
255
- orphansRemoved++
256
289
  } catch {
257
- // Ignore errors during cleanup
290
+ // Ignore
258
291
  }
259
292
  }
260
- }
261
- if (orphansRemoved > 0) {
262
- logger.info(`Cleaned up ${String(orphansRemoved)} orphaned vector entries`, {
293
+ } catch (indexError) {
294
+ // Index files are corrupted — recreate from scratch
295
+ logger.warning('Vector index corrupted, recreating...', {
263
296
  module: 'VectorSearch',
297
+ error: indexError instanceof Error ? indexError.message : String(indexError),
264
298
  })
299
+ // Delete and recreate the vectra index directory
300
+ if (fs.existsSync(this.indexPath)) {
301
+ fs.rmSync(this.indexPath, { recursive: true, force: true })
302
+ fs.mkdirSync(this.indexPath, { recursive: true })
303
+ }
304
+ this.index = new LocalIndex(this.indexPath)
305
+ await this.index.createIndex()
306
+ logger.info('Recreated vector index after corruption', { module: 'VectorSearch' })
265
307
  }
266
308
 
267
- // Step 3: Re-index all current entries
268
- const total = entries.length
269
-
270
- // Send initial progress
271
- await sendProgress(progress, 0, total, 'Starting vector index rebuild...')
309
+ // Step 4: Re-index all entries using paginated fetch
310
+ // Embeddings are generated in parallel batches (CPU-bound, safe),
311
+ // but vectra insertions are sequential (file I/O, not concurrency-safe)
312
+ await sendProgress(progress, 0, totalEntries, 'Starting vector index rebuild...')
272
313
 
273
314
  let indexed = 0
274
- for (const entry of entries) {
275
- // Delete existing item first to avoid "already exists" error
276
- try {
277
- await this.index.deleteItem(String(entry.id))
278
- } catch {
279
- // Item may not exist, ignore
280
- }
315
+ for (let offset = 0; offset < totalEntries; offset += REBUILD_PAGE_SIZE) {
316
+ const page = db.getEntriesPage(offset, REBUILD_PAGE_SIZE)
317
+
318
+ // Generate embeddings in parallel batches
319
+ for (let i = 0; i < page.length; i += REBUILD_BATCH_SIZE) {
320
+ const batch = page.slice(i, i + REBUILD_BATCH_SIZE)
321
+
322
+ // Parallel embedding generation
323
+ const embeddings = await Promise.all(
324
+ batch.map(async (entry) => {
325
+ try {
326
+ return { entry, embedding: await this.generateEmbedding(entry.content) }
327
+ } catch {
328
+ return { entry, embedding: null }
329
+ }
330
+ })
331
+ )
281
332
 
282
- const success = await this.addEntry(entry.id, entry.content)
283
- if (success) indexed++
333
+ // Sequential vectra insertion (file I/O not concurrency-safe)
334
+ for (const { entry, embedding } of embeddings) {
335
+ if (embedding !== null) {
336
+ try {
337
+ await this.index.insertItem({
338
+ id: String(entry.id),
339
+ vector: embedding,
340
+ metadata: {
341
+ entryId: entry.id,
342
+ contentPreview: entry.content.slice(0, 100),
343
+ },
344
+ })
345
+ indexed++
346
+ } catch (error) {
347
+ logger.error('Failed to insert entry into vector index', {
348
+ module: 'VectorSearch',
349
+ entityId: entry.id,
350
+ error: error instanceof Error ? error.message : String(error),
351
+ })
352
+ }
353
+ }
354
+ }
284
355
 
285
- // Report progress every 10 entries to avoid flooding
286
- if (indexed % 10 === 0 || indexed === total) {
287
- await sendProgress(
288
- progress,
289
- indexed,
290
- total,
291
- `Indexed ${String(indexed)} of ${String(total)} entries`
292
- )
356
+ // Report progress every 10 entries to avoid flooding
357
+ if (indexed % 10 === 0 || indexed === totalEntries) {
358
+ await sendProgress(
359
+ progress,
360
+ indexed,
361
+ totalEntries,
362
+ `Indexed ${String(indexed)} of ${String(totalEntries)} entries`
363
+ )
364
+ }
293
365
  }
294
366
  }
295
367
 
296
368
  // Force index to refresh by re-listing items
297
369
  // This ensures the internal query structures are updated and ready for search
298
- // The 100ms delay was insufficient because it didn't refresh the internal state
299
370
  await this.index.listItems()
300
371
 
301
372
  // Final progress
302
- await sendProgress(progress, indexed, total, 'Vector index rebuild complete')
373
+ await sendProgress(progress, indexed, totalEntries, 'Vector index rebuild complete')
303
374
 
304
375
  logger.info(`Rebuilt vector index with ${String(indexed)} entries`, {
305
376
  module: 'VectorSearch',
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Icons Tests
3
+ *
4
+ * Tests icon constants and getToolIcon function.
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest'
8
+ import {
9
+ ICON_CORE,
10
+ ICON_SEARCH,
11
+ ICON_ANALYTICS,
12
+ ICON_RELATIONSHIPS,
13
+ ICON_EXPORT,
14
+ ICON_ADMIN,
15
+ ICON_GITHUB,
16
+ ICON_BACKUP,
17
+ ICON_BRIEFING,
18
+ ICON_CLOCK,
19
+ ICON_TAG,
20
+ ICON_TEAM,
21
+ ICON_STAR,
22
+ ICON_ISSUE,
23
+ ICON_PR,
24
+ ICON_PROMPT,
25
+ getToolIcon,
26
+ } from '../../src/constants/icons.js'
27
+
28
+ // ============================================================================
29
+ // Tool Group Icons
30
+ // ============================================================================
31
+
32
+ describe('Tool group icons', () => {
33
+ const toolIcons = [
34
+ { name: 'ICON_CORE', icon: ICON_CORE },
35
+ { name: 'ICON_SEARCH', icon: ICON_SEARCH },
36
+ { name: 'ICON_ANALYTICS', icon: ICON_ANALYTICS },
37
+ { name: 'ICON_RELATIONSHIPS', icon: ICON_RELATIONSHIPS },
38
+ { name: 'ICON_EXPORT', icon: ICON_EXPORT },
39
+ { name: 'ICON_ADMIN', icon: ICON_ADMIN },
40
+ { name: 'ICON_GITHUB', icon: ICON_GITHUB },
41
+ { name: 'ICON_BACKUP', icon: ICON_BACKUP },
42
+ ]
43
+
44
+ it.each(toolIcons)('$name should have valid SVG data URI', ({ icon }) => {
45
+ expect(icon.src).toMatch(/^data:image\/svg\+xml/)
46
+ expect(icon.mimeType).toBe('image/svg+xml')
47
+ expect(icon.sizes).toContain('24x24')
48
+ })
49
+ })
50
+
51
+ // ============================================================================
52
+ // Resource Icons
53
+ // ============================================================================
54
+
55
+ describe('Resource icons', () => {
56
+ const resourceIcons = [
57
+ { name: 'ICON_BRIEFING', icon: ICON_BRIEFING },
58
+ { name: 'ICON_CLOCK', icon: ICON_CLOCK },
59
+ { name: 'ICON_TAG', icon: ICON_TAG },
60
+ { name: 'ICON_TEAM', icon: ICON_TEAM },
61
+ { name: 'ICON_STAR', icon: ICON_STAR },
62
+ { name: 'ICON_ISSUE', icon: ICON_ISSUE },
63
+ { name: 'ICON_PR', icon: ICON_PR },
64
+ { name: 'ICON_PROMPT', icon: ICON_PROMPT },
65
+ ]
66
+
67
+ it.each(resourceIcons)('$name should have valid SVG data URI', ({ icon }) => {
68
+ expect(icon.src).toMatch(/^data:image\/svg\+xml/)
69
+ expect(icon.mimeType).toBe('image/svg+xml')
70
+ expect(icon.sizes).toContain('24x24')
71
+ })
72
+ })
73
+
74
+ // ============================================================================
75
+ // getToolIcon
76
+ // ============================================================================
77
+
78
+ describe('getToolIcon', () => {
79
+ it('should return icon array for valid groups', () => {
80
+ const groups = [
81
+ 'core',
82
+ 'search',
83
+ 'analytics',
84
+ 'relationships',
85
+ 'export',
86
+ 'admin',
87
+ 'github',
88
+ 'backup',
89
+ ]
90
+
91
+ for (const group of groups) {
92
+ const icons = getToolIcon(group)
93
+ expect(icons).toBeDefined()
94
+ expect(Array.isArray(icons)).toBe(true)
95
+ expect(icons!.length).toBeGreaterThan(0)
96
+ }
97
+ })
98
+
99
+ it('should return undefined for unknown group', () => {
100
+ expect(getToolIcon('nonexistent')).toBeUndefined()
101
+ })
102
+ })