memory-journal-mcp 4.4.2 → 5.0.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 (291) hide show
  1. package/.github/workflows/codeql.yml +1 -6
  2. package/.github/workflows/docker-publish.yml +15 -49
  3. package/.github/workflows/lint-and-test.yml +1 -1
  4. package/.github/workflows/secrets-scanning.yml +4 -3
  5. package/.github/workflows/security-update.yml +3 -3
  6. package/CHANGELOG.md +213 -0
  7. package/CONTRIBUTING.md +132 -97
  8. package/DOCKER_README.md +184 -235
  9. package/Dockerfile +27 -24
  10. package/README.md +218 -190
  11. package/SECURITY.md +27 -35
  12. package/dist/cli.js +16 -1
  13. package/dist/cli.js.map +1 -1
  14. package/dist/constants/ServerInstructions.d.ts +5 -1
  15. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  16. package/dist/constants/ServerInstructions.js +133 -73
  17. package/dist/constants/ServerInstructions.js.map +1 -1
  18. package/dist/constants/icons.d.ts +2 -2
  19. package/dist/constants/icons.d.ts.map +1 -1
  20. package/dist/constants/icons.js +7 -6
  21. package/dist/constants/icons.js.map +1 -1
  22. package/dist/database/SqliteAdapter.d.ts +37 -24
  23. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  24. package/dist/database/SqliteAdapter.js +319 -157
  25. package/dist/database/SqliteAdapter.js.map +1 -1
  26. package/dist/database/schema.d.ts +45 -0
  27. package/dist/database/schema.d.ts.map +1 -0
  28. package/dist/database/schema.js +92 -0
  29. package/dist/database/schema.js.map +1 -0
  30. package/dist/filtering/ToolFilter.d.ts +1 -1
  31. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  32. package/dist/filtering/ToolFilter.js +13 -2
  33. package/dist/filtering/ToolFilter.js.map +1 -1
  34. package/dist/github/GitHubIntegration.d.ts.map +1 -1
  35. package/dist/github/GitHubIntegration.js +1 -3
  36. package/dist/github/GitHubIntegration.js.map +1 -1
  37. package/dist/handlers/prompts/github.d.ts +12 -0
  38. package/dist/handlers/prompts/github.d.ts.map +1 -0
  39. package/dist/handlers/prompts/github.js +178 -0
  40. package/dist/handlers/prompts/github.js.map +1 -0
  41. package/dist/handlers/prompts/index.d.ts +23 -2
  42. package/dist/handlers/prompts/index.d.ts.map +1 -1
  43. package/dist/handlers/prompts/index.js +7 -432
  44. package/dist/handlers/prompts/index.js.map +1 -1
  45. package/dist/handlers/prompts/workflow.d.ts +12 -0
  46. package/dist/handlers/prompts/workflow.d.ts.map +1 -0
  47. package/dist/handlers/prompts/workflow.js +277 -0
  48. package/dist/handlers/prompts/workflow.js.map +1 -0
  49. package/dist/handlers/resources/core.d.ts +11 -0
  50. package/dist/handlers/resources/core.d.ts.map +1 -0
  51. package/dist/handlers/resources/core.js +433 -0
  52. package/dist/handlers/resources/core.js.map +1 -0
  53. package/dist/handlers/resources/github.d.ts +11 -0
  54. package/dist/handlers/resources/github.d.ts.map +1 -0
  55. package/dist/handlers/resources/github.js +314 -0
  56. package/dist/handlers/resources/github.js.map +1 -0
  57. package/dist/handlers/resources/graph.d.ts +11 -0
  58. package/dist/handlers/resources/graph.d.ts.map +1 -0
  59. package/dist/handlers/resources/graph.js +204 -0
  60. package/dist/handlers/resources/graph.js.map +1 -0
  61. package/dist/handlers/resources/index.d.ts +5 -20
  62. package/dist/handlers/resources/index.d.ts.map +1 -1
  63. package/dist/handlers/resources/index.js +16 -1278
  64. package/dist/handlers/resources/index.js.map +1 -1
  65. package/dist/handlers/resources/shared.d.ts +60 -0
  66. package/dist/handlers/resources/shared.d.ts.map +1 -0
  67. package/dist/handlers/resources/shared.js +49 -0
  68. package/dist/handlers/resources/shared.js.map +1 -0
  69. package/dist/handlers/resources/team.d.ts +13 -0
  70. package/dist/handlers/resources/team.d.ts.map +1 -0
  71. package/dist/handlers/resources/team.js +119 -0
  72. package/dist/handlers/resources/team.js.map +1 -0
  73. package/dist/handlers/resources/templates.d.ts +13 -0
  74. package/dist/handlers/resources/templates.d.ts.map +1 -0
  75. package/dist/handlers/resources/templates.js +310 -0
  76. package/dist/handlers/resources/templates.js.map +1 -0
  77. package/dist/handlers/tools/admin.d.ts +8 -0
  78. package/dist/handlers/tools/admin.d.ts.map +1 -0
  79. package/dist/handlers/tools/admin.js +270 -0
  80. package/dist/handlers/tools/admin.js.map +1 -0
  81. package/dist/handlers/tools/analytics.d.ts +8 -0
  82. package/dist/handlers/tools/analytics.d.ts.map +1 -0
  83. package/dist/handlers/tools/analytics.js +256 -0
  84. package/dist/handlers/tools/analytics.js.map +1 -0
  85. package/dist/handlers/tools/backup.d.ts +8 -0
  86. package/dist/handlers/tools/backup.d.ts.map +1 -0
  87. package/dist/handlers/tools/backup.js +224 -0
  88. package/dist/handlers/tools/backup.js.map +1 -0
  89. package/dist/handlers/tools/core.d.ts +9 -0
  90. package/dist/handlers/tools/core.d.ts.map +1 -0
  91. package/dist/handlers/tools/core.js +326 -0
  92. package/dist/handlers/tools/core.js.map +1 -0
  93. package/dist/handlers/tools/export.d.ts +8 -0
  94. package/dist/handlers/tools/export.d.ts.map +1 -0
  95. package/dist/handlers/tools/export.js +89 -0
  96. package/dist/handlers/tools/export.js.map +1 -0
  97. package/dist/handlers/tools/github/helpers.d.ts +34 -0
  98. package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
  99. package/dist/handlers/tools/github/helpers.js +52 -0
  100. package/dist/handlers/tools/github/helpers.js.map +1 -0
  101. package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
  102. package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
  103. package/dist/handlers/tools/github/insights-tools.js +104 -0
  104. package/dist/handlers/tools/github/insights-tools.js.map +1 -0
  105. package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
  106. package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
  107. package/dist/handlers/tools/github/issue-tools.js +359 -0
  108. package/dist/handlers/tools/github/issue-tools.js.map +1 -0
  109. package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
  110. package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
  111. package/dist/handlers/tools/github/kanban-tools.js +108 -0
  112. package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
  113. package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
  114. package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
  115. package/dist/handlers/tools/github/milestone-tools.js +302 -0
  116. package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
  117. package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
  118. package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
  119. package/dist/handlers/tools/github/mutation-tools.js +15 -0
  120. package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
  121. package/dist/handlers/tools/github/read-tools.d.ts +8 -0
  122. package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
  123. package/dist/handlers/tools/github/read-tools.js +260 -0
  124. package/dist/handlers/tools/github/read-tools.js.map +1 -0
  125. package/dist/handlers/tools/github/schemas.d.ts +467 -0
  126. package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
  127. package/dist/handlers/tools/github/schemas.js +335 -0
  128. package/dist/handlers/tools/github/schemas.js.map +1 -0
  129. package/dist/handlers/tools/github.d.ts +14 -0
  130. package/dist/handlers/tools/github.d.ts.map +1 -0
  131. package/dist/handlers/tools/github.js +28 -0
  132. package/dist/handlers/tools/github.js.map +1 -0
  133. package/dist/handlers/tools/index.d.ts +15 -20
  134. package/dist/handlers/tools/index.d.ts.map +1 -1
  135. package/dist/handlers/tools/index.js +117 -2909
  136. package/dist/handlers/tools/index.js.map +1 -1
  137. package/dist/handlers/tools/relationships.d.ts +8 -0
  138. package/dist/handlers/tools/relationships.d.ts.map +1 -0
  139. package/dist/handlers/tools/relationships.js +308 -0
  140. package/dist/handlers/tools/relationships.js.map +1 -0
  141. package/dist/handlers/tools/schemas.d.ts +108 -0
  142. package/dist/handlers/tools/schemas.d.ts.map +1 -0
  143. package/dist/handlers/tools/schemas.js +122 -0
  144. package/dist/handlers/tools/schemas.js.map +1 -0
  145. package/dist/handlers/tools/search.d.ts +8 -0
  146. package/dist/handlers/tools/search.d.ts.map +1 -0
  147. package/dist/handlers/tools/search.js +282 -0
  148. package/dist/handlers/tools/search.js.map +1 -0
  149. package/dist/handlers/tools/team.d.ts +11 -0
  150. package/dist/handlers/tools/team.d.ts.map +1 -0
  151. package/dist/handlers/tools/team.js +239 -0
  152. package/dist/handlers/tools/team.js.map +1 -0
  153. package/dist/server/McpServer.d.ts +4 -0
  154. package/dist/server/McpServer.d.ts.map +1 -1
  155. package/dist/server/McpServer.js +48 -297
  156. package/dist/server/McpServer.js.map +1 -1
  157. package/dist/server/Scheduler.d.ts +91 -0
  158. package/dist/server/Scheduler.d.ts.map +1 -0
  159. package/dist/server/Scheduler.js +201 -0
  160. package/dist/server/Scheduler.js.map +1 -0
  161. package/dist/transports/http.d.ts +66 -0
  162. package/dist/transports/http.d.ts.map +1 -0
  163. package/dist/transports/http.js +519 -0
  164. package/dist/transports/http.js.map +1 -0
  165. package/dist/types/entities.d.ts +101 -0
  166. package/dist/types/entities.d.ts.map +1 -0
  167. package/dist/types/entities.js +5 -0
  168. package/dist/types/entities.js.map +1 -0
  169. package/dist/types/filtering.d.ts +34 -0
  170. package/dist/types/filtering.d.ts.map +1 -0
  171. package/dist/types/filtering.js +5 -0
  172. package/dist/types/filtering.js.map +1 -0
  173. package/dist/types/github.d.ts +166 -0
  174. package/dist/types/github.d.ts.map +1 -0
  175. package/dist/types/github.js +5 -0
  176. package/dist/types/github.js.map +1 -0
  177. package/dist/types/index.d.ts +35 -292
  178. package/dist/types/index.d.ts.map +1 -1
  179. package/dist/types/index.js +2 -2
  180. package/dist/types/index.js.map +1 -1
  181. package/dist/utils/error-helpers.d.ts +37 -0
  182. package/dist/utils/error-helpers.d.ts.map +1 -0
  183. package/dist/utils/error-helpers.js +47 -0
  184. package/dist/utils/error-helpers.js.map +1 -0
  185. package/dist/utils/logger.d.ts.map +1 -1
  186. package/dist/utils/logger.js +6 -3
  187. package/dist/utils/logger.js.map +1 -1
  188. package/dist/utils/security-utils.d.ts +0 -21
  189. package/dist/utils/security-utils.d.ts.map +1 -1
  190. package/dist/utils/security-utils.js +0 -47
  191. package/dist/utils/security-utils.js.map +1 -1
  192. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  193. package/dist/vector/VectorSearchManager.js +9 -32
  194. package/dist/vector/VectorSearchManager.js.map +1 -1
  195. package/docker-compose.yml +11 -2
  196. package/hooks/README.md +107 -0
  197. package/hooks/cursor/hooks.json +10 -0
  198. package/hooks/cursor/memory-journal.mdc +22 -0
  199. package/hooks/cursor/session-end.sh +19 -0
  200. package/hooks/kilo-code/session-end-mode.json +11 -0
  201. package/hooks/kiro/session-end.md +13 -0
  202. package/mcp-config-example.json +1 -0
  203. package/package.json +11 -9
  204. package/playwright.config.ts +29 -0
  205. package/releases/v4.5.0.md +116 -0
  206. package/releases/v5.0.0.md +105 -0
  207. package/scripts/generate-server-instructions.ts +176 -0
  208. package/scripts/server-instructions-function-body.ts +77 -0
  209. package/server.json +3 -3
  210. package/src/cli.ts +45 -1
  211. package/src/constants/ServerInstructions.ts +133 -73
  212. package/src/constants/icons.ts +8 -7
  213. package/src/constants/server-instructions.md +268 -0
  214. package/src/database/SqliteAdapter.ts +358 -192
  215. package/src/database/schema.ts +125 -0
  216. package/src/filtering/ToolFilter.ts +13 -2
  217. package/src/github/GitHubIntegration.ts +1 -3
  218. package/src/handlers/prompts/github.ts +209 -0
  219. package/src/handlers/prompts/index.ts +10 -499
  220. package/src/handlers/prompts/workflow.ts +314 -0
  221. package/src/handlers/resources/core.ts +528 -0
  222. package/src/handlers/resources/github.ts +358 -0
  223. package/src/handlers/resources/graph.ts +254 -0
  224. package/src/handlers/resources/index.ts +23 -1570
  225. package/src/handlers/resources/shared.ts +103 -0
  226. package/src/handlers/resources/team.ts +133 -0
  227. package/src/handlers/resources/templates.ts +374 -0
  228. package/src/handlers/tools/admin.ts +285 -0
  229. package/src/handlers/tools/analytics.ts +301 -0
  230. package/src/handlers/tools/backup.ts +242 -0
  231. package/src/handlers/tools/core.ts +350 -0
  232. package/src/handlers/tools/export.ts +115 -0
  233. package/src/handlers/tools/github/helpers.ts +86 -0
  234. package/src/handlers/tools/github/insights-tools.ts +119 -0
  235. package/src/handlers/tools/github/issue-tools.ts +439 -0
  236. package/src/handlers/tools/github/kanban-tools.ts +134 -0
  237. package/src/handlers/tools/github/milestone-tools.ts +392 -0
  238. package/src/handlers/tools/github/mutation-tools.ts +17 -0
  239. package/src/handlers/tools/github/read-tools.ts +328 -0
  240. package/src/handlers/tools/github/schemas.ts +369 -0
  241. package/src/handlers/tools/github.ts +36 -0
  242. package/src/handlers/tools/index.ts +144 -3325
  243. package/src/handlers/tools/relationships.ts +358 -0
  244. package/src/handlers/tools/schemas.ts +132 -0
  245. package/src/handlers/tools/search.ts +343 -0
  246. package/src/handlers/tools/team.ts +273 -0
  247. package/src/server/McpServer.ts +63 -358
  248. package/src/server/Scheduler.ts +278 -0
  249. package/src/transports/http.ts +635 -0
  250. package/src/types/entities.ts +145 -0
  251. package/src/types/filtering.ts +54 -0
  252. package/src/types/github.ts +180 -0
  253. package/src/types/index.ts +67 -375
  254. package/src/utils/error-helpers.ts +52 -0
  255. package/src/utils/logger.ts +6 -3
  256. package/src/utils/security-utils.ts +0 -52
  257. package/src/vector/VectorSearchManager.ts +9 -33
  258. package/tests/constants/icons.test.ts +1 -2
  259. package/tests/constants/server-instructions.test.ts +30 -4
  260. package/tests/database/sqlite-adapter.test.ts +91 -7
  261. package/tests/e2e/auth.spec.ts +154 -0
  262. package/tests/e2e/health.spec.ts +63 -0
  263. package/tests/e2e/protocols.spec.ts +134 -0
  264. package/tests/e2e/resources.spec.ts +103 -0
  265. package/tests/e2e/scheduler.spec.ts +79 -0
  266. package/tests/e2e/security.spec.ts +91 -0
  267. package/tests/e2e/sessions.spec.ts +95 -0
  268. package/tests/e2e/stateless.spec.ts +121 -0
  269. package/tests/e2e/tools.spec.ts +111 -0
  270. package/tests/filtering/tool-filter.test.ts +46 -0
  271. package/tests/handlers/error-path-coverage.test.ts +324 -0
  272. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  273. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  274. package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
  275. package/tests/handlers/prompt-handlers.test.ts +40 -0
  276. package/tests/handlers/resource-handler-coverage.test.ts +181 -0
  277. package/tests/handlers/resource-handlers.test.ts +33 -9
  278. package/tests/handlers/search-tool-handlers.test.ts +272 -0
  279. package/tests/handlers/targeted-gap-closure.test.ts +387 -0
  280. package/tests/handlers/team-resource-handlers.test.ts +156 -0
  281. package/tests/handlers/team-tool-handlers.test.ts +301 -0
  282. package/tests/handlers/tool-handler-coverage.test.ts +469 -0
  283. package/tests/handlers/tool-handlers.test.ts +2 -2
  284. package/tests/security/sql-injection.test.ts +3 -54
  285. package/tests/server/mcp-server.test.ts +503 -8
  286. package/tests/server/scheduler.test.ts +400 -0
  287. package/tests/transports/http-transport.test.ts +620 -0
  288. package/tests/vector/vector-search-manager.test.ts +60 -0
  289. package/vitest.config.ts +4 -1
  290. package/.memory-journal-team.db +0 -0
  291. package/.vscode/settings.json +0 -84
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Memory Journal MCP Server - Scheduler
3
+ *
4
+ * Lightweight in-process scheduler for periodic maintenance jobs.
5
+ * Only meaningful for HTTP/SSE transport (long-lived server processes).
6
+ * Uses setInterval for simplicity — no external dependencies.
7
+ */
8
+
9
+ import type { SqliteAdapter } from '../database/SqliteAdapter.js'
10
+ import type { VectorSearchManager } from '../vector/VectorSearchManager.js'
11
+ import { logger } from '../utils/logger.js'
12
+
13
+ // ============================================================================
14
+ // Types
15
+ // ============================================================================
16
+
17
+ /** Scheduler configuration options */
18
+ export interface SchedulerOptions {
19
+ /** Automated backup interval in minutes (0 = disabled) */
20
+ backupIntervalMinutes: number
21
+ /** Max backups to retain during automated cleanup */
22
+ keepBackups: number
23
+ /** Database optimize interval in minutes (0 = disabled) */
24
+ vacuumIntervalMinutes: number
25
+ /** Vector index rebuild interval in minutes (0 = disabled) */
26
+ rebuildIndexIntervalMinutes: number
27
+ }
28
+
29
+ /** Status of a single scheduled job */
30
+ export interface JobStatus {
31
+ name: string
32
+ enabled: boolean
33
+ intervalMinutes: number
34
+ lastRun: string | null
35
+ lastResult: 'success' | 'error' | null
36
+ lastError: string | null
37
+ nextRun: string | null
38
+ runCount: number
39
+ }
40
+
41
+ /** Overall scheduler status */
42
+ export interface SchedulerStatus {
43
+ active: boolean
44
+ jobs: JobStatus[]
45
+ }
46
+
47
+ /** Internal timer tracking for a job */
48
+ interface JobTimer {
49
+ name: string
50
+ intervalMinutes: number
51
+ timer: ReturnType<typeof setInterval>
52
+ lastRun: Date | null
53
+ lastResult: 'success' | 'error' | null
54
+ lastError: string | null
55
+ runCount: number
56
+ }
57
+
58
+ // ============================================================================
59
+ // Scheduler
60
+ // ============================================================================
61
+
62
+ /**
63
+ * Scheduler — runs periodic maintenance jobs for long-lived server processes.
64
+ *
65
+ * Jobs:
66
+ * - **backup**: Exports database to timestamped file, then prunes old backups.
67
+ * - **vacuum**: Runs `PRAGMA optimize` and flushes database to disk.
68
+ * - **rebuild-index**: Rebuilds vector search index from all entries.
69
+ */
70
+ export class Scheduler {
71
+ private readonly options: SchedulerOptions
72
+ private readonly db: SqliteAdapter
73
+ private readonly vectorManager: VectorSearchManager | null
74
+ private readonly timers: JobTimer[] = []
75
+ private started = false
76
+
77
+ constructor(options: SchedulerOptions, db: SqliteAdapter, vectorManager?: VectorSearchManager) {
78
+ this.options = options
79
+ this.db = db
80
+ this.vectorManager = vectorManager ?? null
81
+ }
82
+
83
+ /**
84
+ * Start all enabled scheduled jobs.
85
+ * Each job runs on its own interval and failures are isolated.
86
+ */
87
+ start(): void {
88
+ if (this.started) {
89
+ logger.warning('Scheduler already started, ignoring duplicate start()', {
90
+ module: 'Scheduler',
91
+ })
92
+ return
93
+ }
94
+ this.started = true
95
+
96
+ const { backupIntervalMinutes, vacuumIntervalMinutes, rebuildIndexIntervalMinutes } =
97
+ this.options
98
+
99
+ if (backupIntervalMinutes > 0) {
100
+ this.scheduleJob('backup', backupIntervalMinutes, () => this.runBackup())
101
+ }
102
+
103
+ if (vacuumIntervalMinutes > 0) {
104
+ this.scheduleJob('vacuum', vacuumIntervalMinutes, () => this.runVacuumOptimize())
105
+ }
106
+
107
+ if (rebuildIndexIntervalMinutes > 0) {
108
+ if (this.vectorManager) {
109
+ this.scheduleJob('rebuild-index', rebuildIndexIntervalMinutes, () =>
110
+ this.runRebuildIndex()
111
+ )
112
+ } else {
113
+ logger.warning(
114
+ 'rebuild-index-interval specified but vector manager not available, skipping',
115
+ { module: 'Scheduler' }
116
+ )
117
+ }
118
+ }
119
+
120
+ if (this.timers.length > 0) {
121
+ const summary = this.timers.map((t) => `${t.name} (${String(t.intervalMinutes)}min)`)
122
+ logger.info(`Scheduler started: ${summary.join(', ')}`, { module: 'Scheduler' })
123
+ } else {
124
+ logger.info('Scheduler started with no jobs enabled', { module: 'Scheduler' })
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Stop all scheduled jobs and clear timers.
130
+ * Safe to call multiple times.
131
+ */
132
+ stop(): void {
133
+ for (const job of this.timers) {
134
+ clearInterval(job.timer)
135
+ }
136
+ if (this.timers.length > 0) {
137
+ logger.info(`Scheduler stopped, cleared ${String(this.timers.length)} job(s)`, {
138
+ module: 'Scheduler',
139
+ })
140
+ }
141
+ this.timers.length = 0
142
+ this.started = false
143
+ }
144
+
145
+ /**
146
+ * Get the current status of all scheduled jobs.
147
+ */
148
+ getStatus(): SchedulerStatus {
149
+ return {
150
+ active: this.started,
151
+ jobs: this.timers.map((t) => ({
152
+ name: t.name,
153
+ enabled: true,
154
+ intervalMinutes: t.intervalMinutes,
155
+ lastRun: t.lastRun?.toISOString() ?? null,
156
+ lastResult: t.lastResult,
157
+ lastError: t.lastError,
158
+ nextRun: t.lastRun
159
+ ? new Date(t.lastRun.getTime() + t.intervalMinutes * 60_000).toISOString()
160
+ : new Date(Date.now() + t.intervalMinutes * 60_000).toISOString(),
161
+ runCount: t.runCount,
162
+ })),
163
+ }
164
+ }
165
+
166
+ // ========================================================================
167
+ // Private — Job scheduling
168
+ // ========================================================================
169
+
170
+ /**
171
+ * Schedule a recurring job.
172
+ */
173
+ private scheduleJob(name: string, intervalMinutes: number, fn: () => Promise<void>): void {
174
+ const intervalMs = intervalMinutes * 60_000
175
+
176
+ const jobTimer: JobTimer = {
177
+ name,
178
+ intervalMinutes,
179
+ timer: setInterval(() => {
180
+ void this.executeJob(jobTimer, fn)
181
+ }, intervalMs),
182
+ lastRun: null,
183
+ lastResult: null,
184
+ lastError: null,
185
+ runCount: 0,
186
+ }
187
+
188
+ this.timers.push(jobTimer)
189
+ }
190
+
191
+ /**
192
+ * Execute a job with error isolation and status tracking.
193
+ */
194
+ private async executeJob(job: JobTimer, fn: () => Promise<void>): Promise<void> {
195
+ const startTime = Date.now()
196
+ try {
197
+ await fn()
198
+ job.lastRun = new Date(startTime)
199
+ job.lastResult = 'success'
200
+ job.lastError = null
201
+ job.runCount++
202
+ } catch (error) {
203
+ job.lastRun = new Date(startTime)
204
+ job.lastResult = 'error'
205
+ job.lastError = error instanceof Error ? error.message : String(error)
206
+ job.runCount++
207
+ logger.error(`Scheduled job '${job.name}' failed`, {
208
+ module: 'Scheduler',
209
+ operation: job.name,
210
+ error: job.lastError,
211
+ })
212
+ }
213
+ }
214
+
215
+ // ========================================================================
216
+ // Private — Job implementations
217
+ // ========================================================================
218
+
219
+ /**
220
+ * Backup job: export database to file, then cleanup old backups.
221
+ */
222
+ private async runBackup(): Promise<void> {
223
+ const result = this.db.exportToFile()
224
+ logger.info('Scheduled backup created', {
225
+ module: 'Scheduler',
226
+ operation: 'backup',
227
+ context: { filename: result.filename, sizeBytes: result.sizeBytes },
228
+ })
229
+
230
+ const cleanup = this.db.deleteOldBackups(this.options.keepBackups)
231
+ if (cleanup.deleted.length > 0) {
232
+ logger.info(
233
+ `Backup cleanup: deleted ${String(cleanup.deleted.length)}, kept ${String(cleanup.kept)}`,
234
+ {
235
+ module: 'Scheduler',
236
+ operation: 'backup-cleanup',
237
+ }
238
+ )
239
+ }
240
+
241
+ await Promise.resolve()
242
+ }
243
+
244
+ /**
245
+ * Vacuum/optimize job: run PRAGMA optimize and flush to disk.
246
+ *
247
+ * Note: sql.js uses an in-memory database. PRAGMA optimize updates
248
+ * internal statistics, and flushSave() ensures the disk file is current.
249
+ * A full VACUUM on sql.js only compacts the in-memory representation.
250
+ */
251
+ private async runVacuumOptimize(): Promise<void> {
252
+ const rawDb = this.db.getRawDb()
253
+ rawDb.run('PRAGMA optimize')
254
+ this.db.flushSave()
255
+ logger.info('Scheduled database optimize completed', {
256
+ module: 'Scheduler',
257
+ operation: 'vacuum',
258
+ })
259
+
260
+ await Promise.resolve()
261
+ }
262
+
263
+ /**
264
+ * Rebuild index job: full vector index rebuild from database entries.
265
+ */
266
+ private async runRebuildIndex(): Promise<void> {
267
+ if (!this.vectorManager) {
268
+ return
269
+ }
270
+
271
+ const count = await this.vectorManager.rebuildIndex(this.db)
272
+ logger.info(`Scheduled vector index rebuild: ${String(count)} entries indexed`, {
273
+ module: 'Scheduler',
274
+ operation: 'rebuild-index',
275
+ context: { entriesIndexed: count },
276
+ })
277
+ }
278
+ }