memory-journal-mcp 4.4.2 → 4.5.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 (73) hide show
  1. package/.github/workflows/lint-and-test.yml +1 -1
  2. package/.github/workflows/security-update.yml +1 -1
  3. package/CHANGELOG.md +81 -1
  4. package/DOCKER_README.md +57 -7
  5. package/Dockerfile +17 -17
  6. package/README.md +65 -6
  7. package/SECURITY.md +27 -35
  8. package/dist/cli.js +10 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/constants/ServerInstructions.d.ts +5 -1
  11. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  12. package/dist/constants/ServerInstructions.js +137 -83
  13. package/dist/constants/ServerInstructions.js.map +1 -1
  14. package/dist/database/SqliteAdapter.d.ts +2 -1
  15. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  16. package/dist/database/SqliteAdapter.js +15 -8
  17. package/dist/database/SqliteAdapter.js.map +1 -1
  18. package/dist/handlers/resources/index.d.ts +3 -1
  19. package/dist/handlers/resources/index.d.ts.map +1 -1
  20. package/dist/handlers/resources/index.js +5 -2
  21. package/dist/handlers/resources/index.js.map +1 -1
  22. package/dist/handlers/tools/index.d.ts.map +1 -1
  23. package/dist/handlers/tools/index.js +63 -16
  24. package/dist/handlers/tools/index.js.map +1 -1
  25. package/dist/server/McpServer.d.ts +2 -0
  26. package/dist/server/McpServer.d.ts.map +1 -1
  27. package/dist/server/McpServer.js +43 -2
  28. package/dist/server/McpServer.js.map +1 -1
  29. package/dist/server/Scheduler.d.ts +91 -0
  30. package/dist/server/Scheduler.d.ts.map +1 -0
  31. package/dist/server/Scheduler.js +201 -0
  32. package/dist/server/Scheduler.js.map +1 -0
  33. package/dist/utils/logger.d.ts.map +1 -1
  34. package/dist/utils/logger.js +6 -3
  35. package/dist/utils/logger.js.map +1 -1
  36. package/dist/utils/security-utils.d.ts +0 -21
  37. package/dist/utils/security-utils.d.ts.map +1 -1
  38. package/dist/utils/security-utils.js +0 -47
  39. package/dist/utils/security-utils.js.map +1 -1
  40. package/hooks/README.md +107 -0
  41. package/hooks/cursor/hooks.json +10 -0
  42. package/hooks/cursor/memory-journal.mdc +22 -0
  43. package/hooks/cursor/session-end.sh +19 -0
  44. package/hooks/kilo-code/session-end-mode.json +11 -0
  45. package/hooks/kiro/session-end.md +13 -0
  46. package/package.json +8 -8
  47. package/releases/v4.5.0.md +116 -0
  48. package/scripts/generate-server-instructions.ts +176 -0
  49. package/scripts/server-instructions-function-body.ts +77 -0
  50. package/server.json +3 -3
  51. package/src/cli.ts +26 -0
  52. package/src/constants/ServerInstructions.ts +137 -83
  53. package/src/constants/server-instructions.md +262 -0
  54. package/src/database/SqliteAdapter.ts +22 -8
  55. package/src/handlers/resources/index.ts +8 -2
  56. package/src/handlers/tools/index.ts +70 -20
  57. package/src/server/McpServer.ts +60 -2
  58. package/src/server/Scheduler.ts +278 -0
  59. package/src/utils/logger.ts +6 -3
  60. package/src/utils/security-utils.ts +0 -52
  61. package/tests/constants/server-instructions.test.ts +26 -0
  62. package/tests/database/sqlite-adapter.test.ts +84 -0
  63. package/tests/filtering/tool-filter.test.ts +46 -0
  64. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  65. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  66. package/tests/handlers/prompt-handlers.test.ts +40 -0
  67. package/tests/handlers/resource-handlers.test.ts +32 -0
  68. package/tests/handlers/tool-handlers.test.ts +13 -2
  69. package/tests/security/sql-injection.test.ts +3 -54
  70. package/tests/server/mcp-server.test.ts +491 -5
  71. package/tests/server/scheduler.test.ts +400 -0
  72. package/tests/vector/vector-search-manager.test.ts +60 -0
  73. package/.vscode/settings.json +0 -84
@@ -87,5 +87,45 @@ describe('Prompt Handlers', () => {
87
87
  it('should throw for unknown prompt name', () => {
88
88
  expect(() => getPrompt('nonexistent_prompt_xyz', {}, db)).toThrow()
89
89
  })
90
+
91
+ it('should return messages for analyze-period with date arguments', () => {
92
+ const today = new Date().toISOString().split('T')[0]!
93
+ const result = getPrompt('analyze-period', { start_date: today, end_date: today }, db)
94
+
95
+ expect(result.messages).toBeDefined()
96
+ expect(result.messages.length).toBeGreaterThan(0)
97
+ const text = (result.messages[0]?.content as { text: string }).text
98
+ expect(text).toContain(today)
99
+ })
100
+
101
+ it('should return messages for find-related with query argument', () => {
102
+ const result = getPrompt('find-related', { query: 'test' }, db)
103
+
104
+ expect(result.messages).toBeDefined()
105
+ expect(result.messages.length).toBeGreaterThan(0)
106
+ const text = (result.messages[0]?.content as { text: string }).text
107
+ expect(text).toContain('test')
108
+ })
109
+
110
+ it('should return messages for get-context-bundle prompt', () => {
111
+ const result = getPrompt('get-context-bundle', {}, db)
112
+
113
+ expect(result.messages).toBeDefined()
114
+ expect(result.messages.length).toBeGreaterThan(0)
115
+ })
116
+
117
+ it('should return messages for goal-tracker prompt', () => {
118
+ const result = getPrompt('goal-tracker', {}, db)
119
+
120
+ expect(result.messages).toBeDefined()
121
+ expect(result.messages.length).toBeGreaterThan(0)
122
+ })
123
+
124
+ it('should return messages for prepare-retro with days argument', () => {
125
+ const result = getPrompt('prepare-retro', { days: '14' }, db)
126
+
127
+ expect(result.messages).toBeDefined()
128
+ expect(result.messages.length).toBeGreaterThan(0)
129
+ })
90
130
  })
91
131
  })
@@ -335,5 +335,37 @@ describe('Resource Handlers', () => {
335
335
  const data = result.data as { prNumber: number }
336
336
  expect(data.prNumber).toBe(15)
337
337
  })
338
+
339
+ it('should handle resource URI with query parameters', async () => {
340
+ // Query params should be stripped for matching but the full URI is passed to the handler
341
+ const result = await readResource('memory://recent?limit=5', db)
342
+ const data = result.data as { entries: unknown[]; count: number }
343
+ expect(data.entries).toBeDefined()
344
+ })
345
+
346
+ it('should handle resource URI with hash fragment', async () => {
347
+ const result = await readResource('memory://recent#section', db)
348
+ const data = result.data as { entries: unknown[]; count: number }
349
+ expect(data.entries).toBeDefined()
350
+ })
351
+
352
+ it('should handle template URI with query parameters', async () => {
353
+ const result = await readResource('memory://projects/42/timeline?sort=asc', db)
354
+ const data = result.data as { projectNumber: number }
355
+ expect(data.projectNumber).toBe(42)
356
+ })
357
+
358
+ it('should return no-github diagram for kanban without integration', async () => {
359
+ const result = await readResource(
360
+ 'memory://kanban/1/diagram',
361
+ db,
362
+ undefined,
363
+ undefined,
364
+ null // no github
365
+ )
366
+ const data = result.data as { format: string; diagram: string; message: string }
367
+ expect(data.format).toBe('mermaid')
368
+ expect(data.diagram).toContain('GitHub integration not available')
369
+ })
338
370
  })
339
371
  })
@@ -96,7 +96,7 @@ describe('Tool Handlers', () => {
96
96
  'create_entry',
97
97
  {
98
98
  content: 'Full tool entry',
99
- entry_type: 'decision',
99
+ entry_type: 'project_decision',
100
100
  tags: ['tool-tag-a', 'tool-tag-b'],
101
101
  is_personal: false,
102
102
  significance_type: 'milestone',
@@ -105,9 +105,20 @@ describe('Tool Handlers', () => {
105
105
  )) as { success: boolean; entry: { entryType: string; tags: string[] } }
106
106
 
107
107
  expect(result.success).toBe(true)
108
- expect(result.entry.entryType).toBe('decision')
108
+ expect(result.entry.entryType).toBe('project_decision')
109
109
  expect(result.entry.tags).toContain('tool-tag-a')
110
110
  })
111
+
112
+ it('should set isPersonal to false when share_with_team is true', async () => {
113
+ const result = (await callTool(
114
+ 'create_entry',
115
+ { content: 'Team shared entry', share_with_team: true },
116
+ db
117
+ )) as { success: boolean; entry: { isPersonal: boolean } }
118
+
119
+ expect(result.success).toBe(true)
120
+ expect(result.entry.isPersonal).toBe(false)
121
+ })
111
122
  })
112
123
 
113
124
  describe('callTool - create_entry_minimal', () => {
@@ -10,11 +10,8 @@ import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
10
10
  import {
11
11
  validateDateFormatPattern,
12
12
  sanitizeSearchQuery,
13
- containsSqlInjection,
14
- assertNoSqlInjection,
15
13
  assertNoPathTraversal,
16
14
  InvalidDateFormatError,
17
- SqlInjectionError,
18
15
  PathTraversalError,
19
16
  } from '../../src/utils/security-utils.js'
20
17
 
@@ -120,52 +117,6 @@ describe('Security Utilities', () => {
120
117
  })
121
118
  })
122
119
 
123
- describe('containsSqlInjection', () => {
124
- it('should detect stacked query injection', () => {
125
- expect(containsSqlInjection('; DROP TABLE users')).toBe(true)
126
- expect(containsSqlInjection("'; DELETE FROM logs --")).toBe(true)
127
- })
128
-
129
- it('should detect comment injection', () => {
130
- expect(containsSqlInjection('value -- comment')).toBe(true)
131
- expect(containsSqlInjection('test /* block */ comment')).toBe(true)
132
- })
133
-
134
- it('should detect UNION injection', () => {
135
- expect(containsSqlInjection("' UNION SELECT * FROM users")).toBe(true)
136
- expect(containsSqlInjection("' UNION ALL SELECT 1")).toBe(true)
137
- })
138
-
139
- it('should detect boolean bypass', () => {
140
- expect(containsSqlInjection("' OR '1'='1")).toBe(true)
141
- })
142
-
143
- it('should detect SQLite-specific attacks', () => {
144
- expect(containsSqlInjection("ATTACH DATABASE 'mal.db' AS x")).toBe(true)
145
- expect(containsSqlInjection("load_extension('evil.so')")).toBe(true)
146
- })
147
-
148
- it('should not flag safe inputs', () => {
149
- for (const input of SAFE_INPUTS) {
150
- expect(containsSqlInjection(input)).toBe(false)
151
- }
152
- })
153
- })
154
-
155
- describe('assertNoSqlInjection', () => {
156
- it('should throw SqlInjectionError for injection attempts', () => {
157
- for (const payload of INJECTION_PAYLOADS) {
158
- expect(() => assertNoSqlInjection(payload)).toThrow(SqlInjectionError)
159
- }
160
- })
161
-
162
- it('should not throw for safe inputs', () => {
163
- for (const input of SAFE_INPUTS) {
164
- expect(() => assertNoSqlInjection(input)).not.toThrow()
165
- }
166
- })
167
- })
168
-
169
120
  describe('assertNoPathTraversal', () => {
170
121
  it('should throw for path traversal attempts', () => {
171
122
  expect(() => assertNoPathTraversal('../secret')).toThrow(PathTraversalError)
@@ -314,16 +265,14 @@ describe('SqliteAdapter SQL Injection Protection', () => {
314
265
  describe('restoreFromFile - Path Traversal Protection', () => {
315
266
  it('should reject filenames with path traversal', async () => {
316
267
  await expect(db.restoreFromFile('../../../etc/passwd')).rejects.toThrow(
317
- 'Invalid backup filename: path separators not allowed'
268
+ PathTraversalError
318
269
  )
319
270
 
320
271
  await expect(db.restoreFromFile('..\\..\\windows\\system32')).rejects.toThrow(
321
- 'Invalid backup filename: path separators not allowed'
272
+ PathTraversalError
322
273
  )
323
274
 
324
- await expect(db.restoreFromFile('/etc/passwd')).rejects.toThrow(
325
- 'Invalid backup filename: path separators not allowed'
326
- )
275
+ await expect(db.restoreFromFile('/etc/passwd')).rejects.toThrow(PathTraversalError)
327
276
  })
328
277
  })
329
278