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.
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/security-update.yml +1 -1
- package/CHANGELOG.md +81 -1
- package/DOCKER_README.md +57 -7
- package/Dockerfile +17 -17
- package/README.md +65 -6
- package/SECURITY.md +27 -35
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +137 -83
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +2 -1
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +15 -8
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/handlers/resources/index.d.ts +3 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +5 -2
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +63 -16
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/server/McpServer.d.ts +2 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +43 -2
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/package.json +8 -8
- package/releases/v4.5.0.md +116 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +26 -0
- package/src/constants/ServerInstructions.ts +137 -83
- package/src/constants/server-instructions.md +262 -0
- package/src/database/SqliteAdapter.ts +22 -8
- package/src/handlers/resources/index.ts +8 -2
- package/src/handlers/tools/index.ts +70 -20
- package/src/server/McpServer.ts +60 -2
- package/src/server/Scheduler.ts +278 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/tests/constants/server-instructions.test.ts +26 -0
- package/tests/database/sqlite-adapter.test.ts +84 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handlers.test.ts +32 -0
- package/tests/handlers/tool-handlers.test.ts +13 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +491 -5
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/.vscode/settings.json +0 -84
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Scheduler module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
6
|
+
import { Scheduler, type SchedulerOptions } from '../../src/server/Scheduler.js'
|
|
7
|
+
|
|
8
|
+
// Mock logger
|
|
9
|
+
vi.mock('../../src/utils/logger.js', () => ({
|
|
10
|
+
logger: {
|
|
11
|
+
info: vi.fn(),
|
|
12
|
+
warning: vi.fn(),
|
|
13
|
+
error: vi.fn(),
|
|
14
|
+
debug: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a mock SqliteAdapter with the methods used by Scheduler.
|
|
20
|
+
*/
|
|
21
|
+
function createMockDb() {
|
|
22
|
+
return {
|
|
23
|
+
exportToFile: vi.fn().mockReturnValue({
|
|
24
|
+
filename: 'backup_2026-03-01.db',
|
|
25
|
+
path: '/data/backups/backup_2026-03-01.db',
|
|
26
|
+
sizeBytes: 4096,
|
|
27
|
+
}),
|
|
28
|
+
deleteOldBackups: vi.fn().mockReturnValue({
|
|
29
|
+
deleted: ['old_backup.db'],
|
|
30
|
+
kept: 5,
|
|
31
|
+
}),
|
|
32
|
+
getRawDb: vi.fn().mockReturnValue({
|
|
33
|
+
run: vi.fn(),
|
|
34
|
+
}),
|
|
35
|
+
flushSave: vi.fn(),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a mock VectorSearchManager with rebuildIndex.
|
|
41
|
+
*/
|
|
42
|
+
function createMockVectorManager() {
|
|
43
|
+
return {
|
|
44
|
+
rebuildIndex: vi.fn().mockResolvedValue(42),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function defaultOptions(overrides: Partial<SchedulerOptions> = {}): SchedulerOptions {
|
|
49
|
+
return {
|
|
50
|
+
backupIntervalMinutes: 0,
|
|
51
|
+
keepBackups: 5,
|
|
52
|
+
vacuumIntervalMinutes: 0,
|
|
53
|
+
rebuildIndexIntervalMinutes: 0,
|
|
54
|
+
...overrides,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('Scheduler', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.useFakeTimers()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
vi.useRealTimers()
|
|
65
|
+
vi.restoreAllMocks()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// ========================================================================
|
|
69
|
+
// Construction & start/stop
|
|
70
|
+
// ========================================================================
|
|
71
|
+
|
|
72
|
+
describe('start and stop', () => {
|
|
73
|
+
it('should start with no jobs when all intervals are 0', () => {
|
|
74
|
+
const db = createMockDb()
|
|
75
|
+
const scheduler = new Scheduler(defaultOptions(), db as never)
|
|
76
|
+
scheduler.start()
|
|
77
|
+
|
|
78
|
+
const status = scheduler.getStatus()
|
|
79
|
+
expect(status.active).toBe(true)
|
|
80
|
+
expect(status.jobs).toHaveLength(0)
|
|
81
|
+
|
|
82
|
+
scheduler.stop()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should create timers for enabled jobs', () => {
|
|
86
|
+
const db = createMockDb()
|
|
87
|
+
const vectorManager = createMockVectorManager()
|
|
88
|
+
const scheduler = new Scheduler(
|
|
89
|
+
defaultOptions({
|
|
90
|
+
backupIntervalMinutes: 60,
|
|
91
|
+
vacuumIntervalMinutes: 120,
|
|
92
|
+
rebuildIndexIntervalMinutes: 180,
|
|
93
|
+
}),
|
|
94
|
+
db as never,
|
|
95
|
+
vectorManager as never
|
|
96
|
+
)
|
|
97
|
+
scheduler.start()
|
|
98
|
+
|
|
99
|
+
const status = scheduler.getStatus()
|
|
100
|
+
expect(status.active).toBe(true)
|
|
101
|
+
expect(status.jobs).toHaveLength(3)
|
|
102
|
+
expect(status.jobs.map((j) => j.name)).toEqual(['backup', 'vacuum', 'rebuild-index'])
|
|
103
|
+
|
|
104
|
+
scheduler.stop()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should ignore duplicate start calls', () => {
|
|
108
|
+
const db = createMockDb()
|
|
109
|
+
const scheduler = new Scheduler(
|
|
110
|
+
defaultOptions({ backupIntervalMinutes: 60 }),
|
|
111
|
+
db as never
|
|
112
|
+
)
|
|
113
|
+
scheduler.start()
|
|
114
|
+
scheduler.start() // duplicate — should be ignored
|
|
115
|
+
|
|
116
|
+
const status = scheduler.getStatus()
|
|
117
|
+
expect(status.jobs).toHaveLength(1) // still just 1
|
|
118
|
+
|
|
119
|
+
scheduler.stop()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should clear all timers on stop', () => {
|
|
123
|
+
const db = createMockDb()
|
|
124
|
+
const scheduler = new Scheduler(
|
|
125
|
+
defaultOptions({ backupIntervalMinutes: 30, vacuumIntervalMinutes: 60 }),
|
|
126
|
+
db as never
|
|
127
|
+
)
|
|
128
|
+
scheduler.start()
|
|
129
|
+
expect(scheduler.getStatus().jobs).toHaveLength(2)
|
|
130
|
+
|
|
131
|
+
scheduler.stop()
|
|
132
|
+
expect(scheduler.getStatus().active).toBe(false)
|
|
133
|
+
expect(scheduler.getStatus().jobs).toHaveLength(0)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('should be safe to stop multiple times', () => {
|
|
137
|
+
const db = createMockDb()
|
|
138
|
+
const scheduler = new Scheduler(
|
|
139
|
+
defaultOptions({ backupIntervalMinutes: 30 }),
|
|
140
|
+
db as never
|
|
141
|
+
)
|
|
142
|
+
scheduler.start()
|
|
143
|
+
scheduler.stop()
|
|
144
|
+
scheduler.stop() // should not throw
|
|
145
|
+
|
|
146
|
+
expect(scheduler.getStatus().active).toBe(false)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should skip rebuild-index when vectorManager is not provided', () => {
|
|
150
|
+
const db = createMockDb()
|
|
151
|
+
const scheduler = new Scheduler(
|
|
152
|
+
defaultOptions({ rebuildIndexIntervalMinutes: 60 }),
|
|
153
|
+
db as never
|
|
154
|
+
// no vectorManager
|
|
155
|
+
)
|
|
156
|
+
scheduler.start()
|
|
157
|
+
|
|
158
|
+
const status = scheduler.getStatus()
|
|
159
|
+
expect(status.jobs).toHaveLength(0) // skipped
|
|
160
|
+
|
|
161
|
+
scheduler.stop()
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// ========================================================================
|
|
166
|
+
// Job execution
|
|
167
|
+
// ========================================================================
|
|
168
|
+
|
|
169
|
+
describe('backup job', () => {
|
|
170
|
+
it('should call exportToFile and deleteOldBackups on interval', async () => {
|
|
171
|
+
const db = createMockDb()
|
|
172
|
+
const scheduler = new Scheduler(
|
|
173
|
+
defaultOptions({ backupIntervalMinutes: 1, keepBackups: 3 }),
|
|
174
|
+
db as never
|
|
175
|
+
)
|
|
176
|
+
scheduler.start()
|
|
177
|
+
|
|
178
|
+
// Advance by 1 minute
|
|
179
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
180
|
+
|
|
181
|
+
expect(db.exportToFile).toHaveBeenCalledOnce()
|
|
182
|
+
expect(db.deleteOldBackups).toHaveBeenCalledWith(3)
|
|
183
|
+
|
|
184
|
+
// Advance another minute
|
|
185
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
186
|
+
|
|
187
|
+
expect(db.exportToFile).toHaveBeenCalledTimes(2)
|
|
188
|
+
expect(db.deleteOldBackups).toHaveBeenCalledTimes(2)
|
|
189
|
+
|
|
190
|
+
scheduler.stop()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should track job status after successful run', async () => {
|
|
194
|
+
const db = createMockDb()
|
|
195
|
+
const scheduler = new Scheduler(
|
|
196
|
+
defaultOptions({ backupIntervalMinutes: 1 }),
|
|
197
|
+
db as never
|
|
198
|
+
)
|
|
199
|
+
scheduler.start()
|
|
200
|
+
|
|
201
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
202
|
+
|
|
203
|
+
const status = scheduler.getStatus()
|
|
204
|
+
const backupJob = status.jobs.find((j) => j.name === 'backup')
|
|
205
|
+
expect(backupJob).toBeDefined()
|
|
206
|
+
expect(backupJob!.lastResult).toBe('success')
|
|
207
|
+
expect(backupJob!.lastError).toBeNull()
|
|
208
|
+
expect(backupJob!.runCount).toBe(1)
|
|
209
|
+
expect(backupJob!.lastRun).toBeTruthy()
|
|
210
|
+
|
|
211
|
+
scheduler.stop()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should track error status when backup fails', async () => {
|
|
215
|
+
const db = createMockDb()
|
|
216
|
+
db.exportToFile.mockImplementation(() => {
|
|
217
|
+
throw new Error('Disk full')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const scheduler = new Scheduler(
|
|
221
|
+
defaultOptions({ backupIntervalMinutes: 1 }),
|
|
222
|
+
db as never
|
|
223
|
+
)
|
|
224
|
+
scheduler.start()
|
|
225
|
+
|
|
226
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
227
|
+
|
|
228
|
+
const status = scheduler.getStatus()
|
|
229
|
+
const backupJob = status.jobs.find((j) => j.name === 'backup')
|
|
230
|
+
expect(backupJob!.lastResult).toBe('error')
|
|
231
|
+
expect(backupJob!.lastError).toBe('Disk full')
|
|
232
|
+
expect(backupJob!.runCount).toBe(1)
|
|
233
|
+
|
|
234
|
+
scheduler.stop()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should continue running after a failure', async () => {
|
|
238
|
+
const db = createMockDb()
|
|
239
|
+
db.exportToFile
|
|
240
|
+
.mockImplementationOnce(() => {
|
|
241
|
+
throw new Error('Disk full')
|
|
242
|
+
})
|
|
243
|
+
.mockReturnValue({
|
|
244
|
+
filename: 'backup.db',
|
|
245
|
+
path: '/data/backups/backup.db',
|
|
246
|
+
sizeBytes: 4096,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
const scheduler = new Scheduler(
|
|
250
|
+
defaultOptions({ backupIntervalMinutes: 1 }),
|
|
251
|
+
db as never
|
|
252
|
+
)
|
|
253
|
+
scheduler.start()
|
|
254
|
+
|
|
255
|
+
// First run — fails
|
|
256
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
257
|
+
expect(scheduler.getStatus().jobs[0].lastResult).toBe('error')
|
|
258
|
+
|
|
259
|
+
// Second run — succeeds
|
|
260
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
261
|
+
expect(scheduler.getStatus().jobs[0].lastResult).toBe('success')
|
|
262
|
+
expect(scheduler.getStatus().jobs[0].runCount).toBe(2)
|
|
263
|
+
|
|
264
|
+
scheduler.stop()
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
describe('vacuum job', () => {
|
|
269
|
+
it('should call PRAGMA optimize and flushSave on interval', async () => {
|
|
270
|
+
const db = createMockDb()
|
|
271
|
+
const rawDb = db.getRawDb()
|
|
272
|
+
|
|
273
|
+
const scheduler = new Scheduler(
|
|
274
|
+
defaultOptions({ vacuumIntervalMinutes: 1 }),
|
|
275
|
+
db as never
|
|
276
|
+
)
|
|
277
|
+
scheduler.start()
|
|
278
|
+
|
|
279
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
280
|
+
|
|
281
|
+
expect(rawDb.run).toHaveBeenCalledWith('PRAGMA optimize')
|
|
282
|
+
expect(db.flushSave).toHaveBeenCalledOnce()
|
|
283
|
+
|
|
284
|
+
scheduler.stop()
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
describe('rebuild-index job', () => {
|
|
289
|
+
it('should call vectorManager.rebuildIndex on interval', async () => {
|
|
290
|
+
const db = createMockDb()
|
|
291
|
+
const vectorManager = createMockVectorManager()
|
|
292
|
+
|
|
293
|
+
const scheduler = new Scheduler(
|
|
294
|
+
defaultOptions({ rebuildIndexIntervalMinutes: 1 }),
|
|
295
|
+
db as never,
|
|
296
|
+
vectorManager as never
|
|
297
|
+
)
|
|
298
|
+
scheduler.start()
|
|
299
|
+
|
|
300
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
301
|
+
|
|
302
|
+
expect(vectorManager.rebuildIndex).toHaveBeenCalledWith(db)
|
|
303
|
+
|
|
304
|
+
const status = scheduler.getStatus()
|
|
305
|
+
const job = status.jobs.find((j) => j.name === 'rebuild-index')
|
|
306
|
+
expect(job!.lastResult).toBe('success')
|
|
307
|
+
|
|
308
|
+
scheduler.stop()
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
// ========================================================================
|
|
313
|
+
// getStatus
|
|
314
|
+
// ========================================================================
|
|
315
|
+
|
|
316
|
+
describe('getStatus', () => {
|
|
317
|
+
it('should return inactive status before start', () => {
|
|
318
|
+
const db = createMockDb()
|
|
319
|
+
const scheduler = new Scheduler(
|
|
320
|
+
defaultOptions({ backupIntervalMinutes: 60 }),
|
|
321
|
+
db as never
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
const status = scheduler.getStatus()
|
|
325
|
+
expect(status.active).toBe(false)
|
|
326
|
+
expect(status.jobs).toHaveLength(0)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('should include nextRun for jobs that have not run yet', () => {
|
|
330
|
+
const db = createMockDb()
|
|
331
|
+
const scheduler = new Scheduler(
|
|
332
|
+
defaultOptions({ backupIntervalMinutes: 60 }),
|
|
333
|
+
db as never
|
|
334
|
+
)
|
|
335
|
+
scheduler.start()
|
|
336
|
+
|
|
337
|
+
const status = scheduler.getStatus()
|
|
338
|
+
const job = status.jobs[0]
|
|
339
|
+
expect(job.lastRun).toBeNull()
|
|
340
|
+
expect(job.nextRun).toBeTruthy()
|
|
341
|
+
|
|
342
|
+
scheduler.stop()
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('should correctly report interval for each job', () => {
|
|
346
|
+
const db = createMockDb()
|
|
347
|
+
const vectorManager = createMockVectorManager()
|
|
348
|
+
const scheduler = new Scheduler(
|
|
349
|
+
defaultOptions({
|
|
350
|
+
backupIntervalMinutes: 10,
|
|
351
|
+
vacuumIntervalMinutes: 20,
|
|
352
|
+
rebuildIndexIntervalMinutes: 30,
|
|
353
|
+
}),
|
|
354
|
+
db as never,
|
|
355
|
+
vectorManager as never
|
|
356
|
+
)
|
|
357
|
+
scheduler.start()
|
|
358
|
+
|
|
359
|
+
const status = scheduler.getStatus()
|
|
360
|
+
expect(status.jobs[0].intervalMinutes).toBe(10)
|
|
361
|
+
expect(status.jobs[1].intervalMinutes).toBe(20)
|
|
362
|
+
expect(status.jobs[2].intervalMinutes).toBe(30)
|
|
363
|
+
|
|
364
|
+
scheduler.stop()
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// ========================================================================
|
|
369
|
+
// Error isolation
|
|
370
|
+
// ========================================================================
|
|
371
|
+
|
|
372
|
+
describe('error isolation', () => {
|
|
373
|
+
it('should not stop other jobs when one fails', async () => {
|
|
374
|
+
const db = createMockDb()
|
|
375
|
+
db.exportToFile.mockImplementation(() => {
|
|
376
|
+
throw new Error('Backup failed')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
const scheduler = new Scheduler(
|
|
380
|
+
defaultOptions({
|
|
381
|
+
backupIntervalMinutes: 1,
|
|
382
|
+
vacuumIntervalMinutes: 1,
|
|
383
|
+
}),
|
|
384
|
+
db as never
|
|
385
|
+
)
|
|
386
|
+
scheduler.start()
|
|
387
|
+
|
|
388
|
+
await vi.advanceTimersByTimeAsync(60_000)
|
|
389
|
+
|
|
390
|
+
// Backup failed but vacuum should still have run
|
|
391
|
+
const status = scheduler.getStatus()
|
|
392
|
+
const backupJob = status.jobs.find((j) => j.name === 'backup')
|
|
393
|
+
const vacuumJob = status.jobs.find((j) => j.name === 'vacuum')
|
|
394
|
+
expect(backupJob!.lastResult).toBe('error')
|
|
395
|
+
expect(vacuumJob!.lastResult).toBe('success')
|
|
396
|
+
|
|
397
|
+
scheduler.stop()
|
|
398
|
+
})
|
|
399
|
+
})
|
|
400
|
+
})
|
|
@@ -331,5 +331,65 @@ describe('VectorSearchManager', () => {
|
|
|
331
331
|
const indexed = await vm.rebuildIndex(mockDb as any)
|
|
332
332
|
expect(indexed).toBe(0)
|
|
333
333
|
})
|
|
334
|
+
|
|
335
|
+
it('should recover from corrupted index', async () => {
|
|
336
|
+
await initManager(vm)
|
|
337
|
+
mockEmbedderFn.mockResolvedValue({ data: fakeEmbedding(0) })
|
|
338
|
+
|
|
339
|
+
// First listItems call throws (corrupted index)
|
|
340
|
+
mockListItems.mockRejectedValueOnce(new Error('Corrupted index'))
|
|
341
|
+
// After recreation, listItems returns empty
|
|
342
|
+
mockListItems.mockResolvedValueOnce([])
|
|
343
|
+
mockInsertItem.mockResolvedValue(undefined)
|
|
344
|
+
|
|
345
|
+
const mockDb = {
|
|
346
|
+
getActiveEntryCount: vi.fn().mockReturnValue(1),
|
|
347
|
+
getEntriesPage: vi.fn().mockReturnValue([{ id: 1, content: 'Entry' }]),
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
351
|
+
const indexed = await vm.rebuildIndex(mockDb as any)
|
|
352
|
+
expect(indexed).toBe(1)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('should skip entries with embedding failures', async () => {
|
|
356
|
+
await initManager(vm)
|
|
357
|
+
// First call succeeds, second fails
|
|
358
|
+
mockEmbedderFn
|
|
359
|
+
.mockResolvedValueOnce({ data: fakeEmbedding(1) })
|
|
360
|
+
.mockRejectedValueOnce(new Error('Embedding failed'))
|
|
361
|
+
mockListItems.mockResolvedValue([])
|
|
362
|
+
mockInsertItem.mockResolvedValue(undefined)
|
|
363
|
+
|
|
364
|
+
const mockDb = {
|
|
365
|
+
getActiveEntryCount: vi.fn().mockReturnValue(2),
|
|
366
|
+
getEntriesPage: vi.fn().mockReturnValue([
|
|
367
|
+
{ id: 1, content: 'Good entry' },
|
|
368
|
+
{ id: 2, content: 'Will fail embedding' },
|
|
369
|
+
]),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
373
|
+
const indexed = await vm.rebuildIndex(mockDb as any)
|
|
374
|
+
// Only 1 should be indexed (the other failed)
|
|
375
|
+
expect(indexed).toBe(1)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// ========================================================================
|
|
380
|
+
// Initialize Error
|
|
381
|
+
// ========================================================================
|
|
382
|
+
|
|
383
|
+
describe('initialize error', () => {
|
|
384
|
+
it('should rethrow pipeline errors', async () => {
|
|
385
|
+
const { pipeline: pipelineMock } = await import('@xenova/transformers')
|
|
386
|
+
;(pipelineMock as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
|
387
|
+
new Error('Model not found')
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
const vm2 = new VectorSearchManager('/tmp/test-error.db')
|
|
391
|
+
await expect(vm2.initialize()).rejects.toThrow('Model not found')
|
|
392
|
+
expect(vm2.isInitialized()).toBe(false)
|
|
393
|
+
})
|
|
334
394
|
})
|
|
335
395
|
})
|
package/.vscode/settings.json
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
3
|
-
// ESLint Configuration
|
|
4
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
5
|
-
"eslint.enable": true,
|
|
6
|
-
"eslint.useFlatConfig": true,
|
|
7
|
-
"eslint.validate": [
|
|
8
|
-
"javascript",
|
|
9
|
-
"javascriptreact",
|
|
10
|
-
"typescript",
|
|
11
|
-
"typescriptreact",
|
|
12
|
-
"json",
|
|
13
|
-
"jsonc",
|
|
14
|
-
"markdown"
|
|
15
|
-
],
|
|
16
|
-
// Run ESLint on save (provides feedback to agent)
|
|
17
|
-
"eslint.run": "onSave",
|
|
18
|
-
// Show severity inline
|
|
19
|
-
"eslint.rules.customizations": [
|
|
20
|
-
{
|
|
21
|
-
"rule": "*",
|
|
22
|
-
"severity": "warn"
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
26
|
-
// Prettier Configuration
|
|
27
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
28
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
29
|
-
"editor.formatOnSave": true,
|
|
30
|
-
"[typescript]": {
|
|
31
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
32
|
-
},
|
|
33
|
-
"[javascript]": {
|
|
34
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
35
|
-
},
|
|
36
|
-
"[json]": {
|
|
37
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
38
|
-
},
|
|
39
|
-
"[jsonc]": {
|
|
40
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
41
|
-
},
|
|
42
|
-
"[markdown]": {
|
|
43
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
44
|
-
},
|
|
45
|
-
"[yaml]": {
|
|
46
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
47
|
-
},
|
|
48
|
-
"[html]": {
|
|
49
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
50
|
-
},
|
|
51
|
-
"[css]": {
|
|
52
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
53
|
-
},
|
|
54
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
55
|
-
// Error Lens Configuration
|
|
56
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
57
|
-
"errorLens.enabled": true,
|
|
58
|
-
"errorLens.enabledDiagnosticLevels": ["error", "warning", "info"],
|
|
59
|
-
"errorLens.messageEnabled": true,
|
|
60
|
-
"errorLens.statusBarMessageEnabled": true,
|
|
61
|
-
"errorLens.gutterIconsEnabled": true,
|
|
62
|
-
// Delay to avoid flicker during active typing
|
|
63
|
-
"errorLens.delay": 500,
|
|
64
|
-
// Exclude noisy hints if needed
|
|
65
|
-
"errorLens.excludeBySource": [],
|
|
66
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
67
|
-
// TypeScript Configuration (Built-in)
|
|
68
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
69
|
-
"typescript.preferences.importModuleSpecifier": "relative",
|
|
70
|
-
"typescript.updateImportsOnFileMove.enabled": "always",
|
|
71
|
-
"typescript.suggest.autoImports": true,
|
|
72
|
-
"typescript.tsdk": "node_modules/typescript/lib",
|
|
73
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
74
|
-
// Editor Behavior
|
|
75
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
76
|
-
"editor.codeActionsOnSave": {
|
|
77
|
-
"source.fixAll.eslint": "explicit",
|
|
78
|
-
"source.organizeImports": "never"
|
|
79
|
-
},
|
|
80
|
-
"files.trimTrailingWhitespace": true,
|
|
81
|
-
"files.insertFinalNewline": true,
|
|
82
|
-
"files.trimFinalNewlines": true,
|
|
83
|
-
"files.eol": "\n"
|
|
84
|
-
}
|