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,106 @@
1
+ /**
2
+ * Prompt Handler Coverage Tests
3
+ *
4
+ * Tests for handlers/prompts/index.ts uncovered paths:
5
+ * - execQuery with empty result set
6
+ * - execQuery with multi-row results
7
+ * - getPrompt with unknown name (throws)
8
+ * - getPrompts listing
9
+ */
10
+
11
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
12
+ import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
13
+ import { execQuery, getPrompts, getPrompt } from '../../src/handlers/prompts/index.js'
14
+
15
+ describe('Prompt Handlers - Coverage', () => {
16
+ let db: SqliteAdapter
17
+ const testDbPath = './test-prompts-cov.db'
18
+
19
+ beforeAll(async () => {
20
+ db = new SqliteAdapter(testDbPath)
21
+ await db.initialize()
22
+ // Seed entries for query tests
23
+ db.createEntry({ content: 'Prompt test entry 1', tags: ['prompt-test'] })
24
+ db.createEntry({ content: 'Prompt test entry 2', tags: ['prompt-test'] })
25
+ })
26
+
27
+ afterAll(() => {
28
+ db.close()
29
+ try {
30
+ const fs = require('node:fs')
31
+ if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
32
+ } catch {
33
+ // Ignore cleanup errors
34
+ }
35
+ })
36
+
37
+ // ========================================================================
38
+ // execQuery
39
+ // ========================================================================
40
+
41
+ describe('execQuery', () => {
42
+ it('should return rows for a valid query', () => {
43
+ const rows = execQuery(db, 'SELECT id, content FROM memory_journal LIMIT 2')
44
+ expect(rows.length).toBe(2)
45
+ expect(rows[0]).toHaveProperty('id')
46
+ expect(rows[0]).toHaveProperty('content')
47
+ })
48
+
49
+ it('should return empty array for no-match query', () => {
50
+ const rows = execQuery(
51
+ db,
52
+ "SELECT id FROM memory_journal WHERE content = 'nonexistent_xyz_12345'"
53
+ )
54
+ expect(rows).toEqual([])
55
+ })
56
+
57
+ it('should handle parameterized queries', () => {
58
+ const rows = execQuery(
59
+ db,
60
+ 'SELECT id, content FROM memory_journal WHERE content LIKE ?',
61
+ ['%Prompt test%']
62
+ )
63
+ expect(rows.length).toBe(2)
64
+ })
65
+
66
+ it('should return empty array for query that returns no columns', () => {
67
+ const rows = execQuery(db, 'SELECT id FROM memory_journal WHERE id = -1')
68
+ expect(rows).toEqual([])
69
+ })
70
+ })
71
+
72
+ // ========================================================================
73
+ // getPrompts
74
+ // ========================================================================
75
+
76
+ describe('getPrompts', () => {
77
+ it('should return all prompt definitions', () => {
78
+ const prompts = getPrompts()
79
+ expect(prompts.length).toBeGreaterThan(0)
80
+
81
+ for (const p of prompts) {
82
+ const prompt = p as { name: string; description: string }
83
+ expect(typeof prompt.name).toBe('string')
84
+ expect(typeof prompt.description).toBe('string')
85
+ }
86
+ })
87
+ })
88
+
89
+ // ========================================================================
90
+ // getPrompt
91
+ // ========================================================================
92
+
93
+ describe('getPrompt', () => {
94
+ it('should throw for unknown prompt name', () => {
95
+ expect(() => getPrompt('nonexistent_prompt', {}, db)).toThrow('Unknown prompt')
96
+ })
97
+
98
+ it('should return messages for a valid prompt', () => {
99
+ const prompts = getPrompts()
100
+ const firstName = (prompts[0] as { name: string }).name
101
+ const result = getPrompt(firstName, {}, db)
102
+ expect(result.messages).toBeDefined()
103
+ expect(result.messages.length).toBeGreaterThan(0)
104
+ })
105
+ })
106
+ })
@@ -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
  })
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Resource Handler Coverage Tests — Targeted Gap Closure
3
+ *
4
+ * Tests for remaining uncovered lines in:
5
+ * - handlers/resources/index.ts: getBaseUri non-memory URL, template match + ResourceResult
6
+ * - handlers/resources/templates.ts: invalid params for template resources
7
+ * - handlers/resources/core.ts: CI status branches, scheduler section
8
+ */
9
+
10
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
11
+ import { readResource } from '../../src/handlers/resources/index.js'
12
+ import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
13
+
14
+ describe('Resource Handler Coverage', () => {
15
+ let db: SqliteAdapter
16
+ const testDbPath = './test-resource-coverage.db'
17
+
18
+ beforeAll(async () => {
19
+ db = new SqliteAdapter(testDbPath)
20
+ await db.initialize()
21
+
22
+ // Create entries with project/issue/PR links for template tests
23
+ db.createEntry({
24
+ content: 'Resource coverage test entry',
25
+ tags: ['resource-test'],
26
+ projectNumber: 42,
27
+ issueNumber: 7,
28
+ prNumber: 3,
29
+ })
30
+ })
31
+
32
+ afterAll(() => {
33
+ db.close()
34
+ try {
35
+ const fs = require('node:fs')
36
+ if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
37
+ } catch {
38
+ // cleanup
39
+ }
40
+ })
41
+
42
+ // ========================================================================
43
+ // handlers/resources/index.ts — getBaseUri, template matching
44
+ // ========================================================================
45
+
46
+ describe('readResource - base URI parsing', () => {
47
+ it('should handle memory:// URIs with query parameters', async () => {
48
+ const result = await readResource('memory://recent?limit=5&format=brief', db)
49
+ expect(result.data).toBeDefined()
50
+ })
51
+
52
+ it('should throw for unknown resource URI', async () => {
53
+ await expect(readResource('memory://nonexistent-resource-xyz', db)).rejects.toThrow(
54
+ 'Unknown resource'
55
+ )
56
+ })
57
+
58
+ it('should throw for non-memory URI schemes', async () => {
59
+ await expect(readResource('https://example.com/unknown', db)).rejects.toThrow(
60
+ 'Unknown resource'
61
+ )
62
+ })
63
+ })
64
+
65
+ // ========================================================================
66
+ // handlers/resources/templates.ts — invalid template params (lines 33, 67, 100)
67
+ // ========================================================================
68
+
69
+ describe('template resources — invalid params', () => {
70
+ it('should return error for invalid project timeline number', async () => {
71
+ const result = await readResource('memory://projects/abc/timeline', db)
72
+ expect(result.data).toHaveProperty('error')
73
+ })
74
+
75
+ it('should return entries for valid project timeline', async () => {
76
+ const result = await readResource('memory://projects/42/timeline', db)
77
+ const data = result.data as { projectNumber: number; entries: unknown[] }
78
+ expect(data.projectNumber).toBe(42)
79
+ expect(data.entries).toBeDefined()
80
+ })
81
+
82
+ it('should return error for invalid issue entries number', async () => {
83
+ const result = await readResource('memory://issues/abc/entries', db)
84
+ expect(result.data).toHaveProperty('error')
85
+ })
86
+
87
+ it('should return entries for valid issue entries', async () => {
88
+ const result = await readResource('memory://issues/7/entries', db)
89
+ const data = result.data as { issueNumber: number; entries: unknown[] }
90
+ expect(data.issueNumber).toBe(7)
91
+ })
92
+
93
+ it('should return error for invalid PR entries number', async () => {
94
+ const result = await readResource('memory://prs/abc/entries', db)
95
+ expect(result.data).toHaveProperty('error')
96
+ })
97
+
98
+ it('should return entries for valid PR entries', async () => {
99
+ const result = await readResource('memory://prs/3/entries', db)
100
+ const data = result.data as { prNumber: number; entries: unknown[] }
101
+ expect(data.prNumber).toBe(3)
102
+ })
103
+
104
+ it('should return error for invalid PR timeline number', async () => {
105
+ const result = await readResource('memory://prs/abc/timeline', db)
106
+ expect(result.data).toHaveProperty('error')
107
+ })
108
+
109
+ it('should return timeline without GitHub for valid PR', async () => {
110
+ const result = await readResource('memory://prs/3/timeline', db)
111
+ const data = result.data as {
112
+ prNumber: number
113
+ prMetadata: null
114
+ timelineNote: string
115
+ }
116
+ expect(data.prNumber).toBe(3)
117
+ expect(data.prMetadata).toBeNull()
118
+ expect(data.timelineNote).toContain('unavailable')
119
+ })
120
+
121
+ it('should return error for invalid kanban project number', async () => {
122
+ const result = await readResource('memory://kanban/abc', db)
123
+ expect(result.data).toHaveProperty('error')
124
+ })
125
+
126
+ it('should return error for kanban without github', async () => {
127
+ const result = await readResource('memory://kanban/1', db)
128
+ const data = result.data as { error: string }
129
+ expect(data.error).toContain('not available')
130
+ })
131
+
132
+ it('should return error for invalid kanban diagram number', async () => {
133
+ const result = await readResource('memory://kanban/abc/diagram', db)
134
+ expect(result.data).toHaveProperty('error')
135
+ })
136
+
137
+ it('should return mermaid fallback for diagram without github', async () => {
138
+ const result = await readResource('memory://kanban/1/diagram', db)
139
+ const data = result.data as { format: string; diagram: string }
140
+ expect(data.format).toBe('mermaid')
141
+ expect(data.diagram).toContain('not available')
142
+ })
143
+ })
144
+
145
+ // Note: health endpoint is served by HttpTransport, not as a memory:// resource.
146
+
147
+ // ========================================================================
148
+ // handlers/resources/core.ts — instructions resource
149
+ // ========================================================================
150
+
151
+ describe('core resources — instructions', () => {
152
+ it('should return instructions text', async () => {
153
+ const result = await readResource('memory://instructions', db)
154
+ expect(result.data).toBeDefined()
155
+ expect(typeof result.data).toBe('string')
156
+ })
157
+ })
158
+
159
+ // ========================================================================
160
+ // handlers/resources/core.ts — briefing resource
161
+ // ========================================================================
162
+
163
+ describe('core resources — briefing', () => {
164
+ it('should return briefing data', async () => {
165
+ const result = await readResource('memory://briefing', db)
166
+ expect(result.data).toBeDefined()
167
+ })
168
+ })
169
+
170
+ // ========================================================================
171
+ // handlers/resources/core.ts — recent with entry_type filter
172
+ // ========================================================================
173
+
174
+ describe('core resources — recent with filters', () => {
175
+ it('should return recent entries with query params', async () => {
176
+ const result = await readResource('memory://recent?limit=3', db)
177
+ const data = result.data as { entries: unknown[] }
178
+ expect(data.entries).toBeDefined()
179
+ })
180
+ })
181
+ })
@@ -76,7 +76,6 @@ describe('Resource Handlers', () => {
76
76
  expect(uris).toContain('memory://recent')
77
77
  expect(uris).toContain('memory://significant')
78
78
  expect(uris).toContain('memory://tags')
79
- expect(uris).toContain('memory://team/recent')
80
79
  })
81
80
 
82
81
  it('should include template resources', () => {
@@ -122,7 +121,7 @@ describe('Resource Handlers', () => {
122
121
  const result = await readResource('memory://instructions', db, undefined, null)
123
122
 
124
123
  const text = result.data as string
125
- expect(text).toContain('Active Tools (39)')
124
+ expect(text).toContain('Active Tools (42)')
126
125
  })
127
126
 
128
127
  it('should read memory://recent', async () => {
@@ -203,13 +202,6 @@ describe('Resource Handlers', () => {
203
202
  expect(data.timestamp).toBeDefined()
204
203
  })
205
204
 
206
- it('should read memory://team/recent', async () => {
207
- const result = await readResource('memory://team/recent', db)
208
-
209
- const data = result.data as { entries: unknown[]; count: number }
210
- expect(data.entries).toBeDefined()
211
- })
212
-
213
205
  it('should read memory://graph/recent', async () => {
214
206
  const result = await readResource('memory://graph/recent', db)
215
207
 
@@ -335,5 +327,37 @@ describe('Resource Handlers', () => {
335
327
  const data = result.data as { prNumber: number }
336
328
  expect(data.prNumber).toBe(15)
337
329
  })
330
+
331
+ it('should handle resource URI with query parameters', async () => {
332
+ // Query params should be stripped for matching but the full URI is passed to the handler
333
+ const result = await readResource('memory://recent?limit=5', db)
334
+ const data = result.data as { entries: unknown[]; count: number }
335
+ expect(data.entries).toBeDefined()
336
+ })
337
+
338
+ it('should handle resource URI with hash fragment', async () => {
339
+ const result = await readResource('memory://recent#section', db)
340
+ const data = result.data as { entries: unknown[]; count: number }
341
+ expect(data.entries).toBeDefined()
342
+ })
343
+
344
+ it('should handle template URI with query parameters', async () => {
345
+ const result = await readResource('memory://projects/42/timeline?sort=asc', db)
346
+ const data = result.data as { projectNumber: number }
347
+ expect(data.projectNumber).toBe(42)
348
+ })
349
+
350
+ it('should return no-github diagram for kanban without integration', async () => {
351
+ const result = await readResource(
352
+ 'memory://kanban/1/diagram',
353
+ db,
354
+ undefined,
355
+ undefined,
356
+ null // no github
357
+ )
358
+ const data = result.data as { format: string; diagram: string; message: string }
359
+ expect(data.format).toBe('mermaid')
360
+ expect(data.diagram).toContain('GitHub integration not available')
361
+ })
338
362
  })
339
363
  })
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Search Tool Handler Coverage Tests
3
+ *
4
+ * Tests for search tool uncovered code paths:
5
+ * - search_entries with teamDb (cross-database merge)
6
+ * - search_by_date_range with teamDb
7
+ * - search_entries with GitHub filters
8
+ * - search_entries with no query and no filters (recent entries path)
9
+ * - search_by_date_range with entry_type filter
10
+ * - mergeAndDedup helper (dedup, sort, limit)
11
+ */
12
+
13
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
14
+ import { callTool } from '../../src/handlers/tools/index.js'
15
+ import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
16
+
17
+ describe('Search Tool Handlers - Coverage', () => {
18
+ let db: SqliteAdapter
19
+ let teamDb: SqliteAdapter
20
+ const testDbPath = './test-search-cov.db'
21
+ const teamDbPath = './test-search-team-cov.db'
22
+
23
+ beforeAll(async () => {
24
+ db = new SqliteAdapter(testDbPath)
25
+ await db.initialize()
26
+ teamDb = new SqliteAdapter(teamDbPath)
27
+ await teamDb.initialize()
28
+
29
+ // Seed personal entries
30
+ db.createEntry({
31
+ content: 'Personal alpha entry',
32
+ entryType: 'personal_reflection',
33
+ tags: ['alpha'],
34
+ projectNumber: 42,
35
+ issueNumber: 7,
36
+ prNumber: 10,
37
+ })
38
+ db.createEntry({
39
+ content: 'Personal beta entry',
40
+ entryType: 'project_decision',
41
+ tags: ['beta'],
42
+ isPersonal: true,
43
+ })
44
+
45
+ // Seed team entries
46
+ teamDb.createEntry({
47
+ content: 'Team gamma entry',
48
+ entryType: 'personal_reflection',
49
+ tags: ['gamma'],
50
+ projectNumber: 42,
51
+ })
52
+ teamDb.createEntry({
53
+ content: 'Team delta entry',
54
+ entryType: 'project_decision',
55
+ tags: ['delta'],
56
+ })
57
+ // Duplicate content to test dedup
58
+ teamDb.createEntry({
59
+ content: 'Personal alpha entry',
60
+ entryType: 'personal_reflection',
61
+ tags: ['dup'],
62
+ })
63
+ })
64
+
65
+ afterAll(() => {
66
+ db.close()
67
+ teamDb.close()
68
+ try {
69
+ const fs = require('node:fs')
70
+ for (const p of [testDbPath, teamDbPath]) {
71
+ if (fs.existsSync(p)) fs.unlinkSync(p)
72
+ }
73
+ } catch {
74
+ // Ignore cleanup errors
75
+ }
76
+ })
77
+
78
+ // ========================================================================
79
+ // search_entries — cross-database merge
80
+ // ========================================================================
81
+
82
+ describe('search_entries with teamDb', () => {
83
+ it('should merge personal and team results', async () => {
84
+ const result = (await callTool(
85
+ 'search_entries',
86
+ { query: 'entry', limit: 10 },
87
+ db,
88
+ undefined,
89
+ undefined,
90
+ undefined,
91
+ undefined,
92
+ teamDb
93
+ )) as { entries: unknown[]; count: number }
94
+
95
+ // Should have entries from both DBs (deduped)
96
+ expect(result.count).toBeGreaterThan(0)
97
+ expect(result.entries.length).toBe(result.count)
98
+ })
99
+
100
+ it('should deduplicate entries with same content', async () => {
101
+ const result = (await callTool(
102
+ 'search_entries',
103
+ { query: 'alpha', limit: 50 },
104
+ db,
105
+ undefined,
106
+ undefined,
107
+ undefined,
108
+ undefined,
109
+ teamDb
110
+ )) as { entries: { content: string }[]; count: number }
111
+
112
+ // "Personal alpha entry" exists in both DBs — should be deduped
113
+ const alphaEntries = result.entries.filter((e) =>
114
+ e.content.includes('Personal alpha entry')
115
+ )
116
+ expect(alphaEntries.length).toBe(1)
117
+ })
118
+
119
+ it('should merge recent entries (no query, no filters)', async () => {
120
+ const result = (await callTool(
121
+ 'search_entries',
122
+ { limit: 10 },
123
+ db,
124
+ undefined,
125
+ undefined,
126
+ undefined,
127
+ undefined,
128
+ teamDb
129
+ )) as { entries: unknown[]; count: number }
130
+
131
+ expect(result.count).toBeGreaterThan(0)
132
+ })
133
+ })
134
+
135
+ // ========================================================================
136
+ // search_entries — GitHub filters
137
+ // ========================================================================
138
+
139
+ describe('search_entries with filters', () => {
140
+ it('should filter by project_number', async () => {
141
+ const result = (await callTool(
142
+ 'search_entries',
143
+ { project_number: 42, limit: 10 },
144
+ db
145
+ )) as { entries: unknown[]; count: number }
146
+
147
+ expect(result.count).toBeGreaterThan(0)
148
+ })
149
+
150
+ it('should filter by issue_number', async () => {
151
+ const result = (await callTool(
152
+ 'search_entries',
153
+ { issue_number: 7, limit: 10 },
154
+ db
155
+ )) as { entries: unknown[]; count: number }
156
+
157
+ expect(result.count).toBeGreaterThan(0)
158
+ })
159
+
160
+ it('should filter by pr_number', async () => {
161
+ const result = (await callTool('search_entries', { pr_number: 10, limit: 10 }, db)) as {
162
+ entries: unknown[]
163
+ count: number
164
+ }
165
+
166
+ expect(result.count).toBeGreaterThan(0)
167
+ })
168
+
169
+ it('should filter by is_personal', async () => {
170
+ const result = (await callTool(
171
+ 'search_entries',
172
+ { is_personal: true, limit: 10 },
173
+ db
174
+ )) as { entries: unknown[]; count: number }
175
+
176
+ expect(result.count).toBeGreaterThan(0)
177
+ })
178
+
179
+ it('should handle combined filters with teamDb', async () => {
180
+ const result = (await callTool(
181
+ 'search_entries',
182
+ { project_number: 42, limit: 10 },
183
+ db,
184
+ undefined,
185
+ undefined,
186
+ undefined,
187
+ undefined,
188
+ teamDb
189
+ )) as { entries: unknown[]; count: number }
190
+
191
+ expect(result.count).toBeGreaterThan(0)
192
+ })
193
+ })
194
+
195
+ // ========================================================================
196
+ // search_by_date_range — cross-database merge
197
+ // ========================================================================
198
+
199
+ describe('search_by_date_range with teamDb', () => {
200
+ it('should merge personal and team results by date', async () => {
201
+ const today = new Date().toISOString().split('T')[0]!
202
+ const result = (await callTool(
203
+ 'search_by_date_range',
204
+ { start_date: today, end_date: today },
205
+ db,
206
+ undefined,
207
+ undefined,
208
+ undefined,
209
+ undefined,
210
+ teamDb
211
+ )) as { entries: unknown[]; count: number }
212
+
213
+ expect(result.count).toBeGreaterThan(0)
214
+ })
215
+
216
+ it('should apply entry_type filter', async () => {
217
+ const today = new Date().toISOString().split('T')[0]!
218
+ const result = (await callTool(
219
+ 'search_by_date_range',
220
+ {
221
+ start_date: today,
222
+ end_date: today,
223
+ entry_type: 'project_decision',
224
+ },
225
+ db
226
+ )) as { entries: unknown[]; count: number }
227
+
228
+ expect(result.count).toBeGreaterThan(0)
229
+ })
230
+
231
+ it('should apply tags filter', async () => {
232
+ const today = new Date().toISOString().split('T')[0]!
233
+ const result = (await callTool(
234
+ 'search_by_date_range',
235
+ {
236
+ start_date: today,
237
+ end_date: today,
238
+ tags: ['alpha'],
239
+ },
240
+ db
241
+ )) as { entries: unknown[]; count: number }
242
+
243
+ expect(result.count).toBeGreaterThan(0)
244
+ })
245
+
246
+ it('should return Zod error for invalid date format', async () => {
247
+ const result = (await callTool(
248
+ 'search_by_date_range',
249
+ { start_date: 'not-a-date', end_date: 'invalid' },
250
+ db
251
+ )) as { error: string }
252
+
253
+ expect(result.error).toBeDefined()
254
+ })
255
+ })
256
+
257
+ // ========================================================================
258
+ // search_entries — Zod error handling
259
+ // ========================================================================
260
+
261
+ describe('search_entries error handling', () => {
262
+ it('should return error for invalid pr_status enum', async () => {
263
+ const result = (await callTool(
264
+ 'search_entries',
265
+ { pr_status: 'invalid_status', limit: 5 },
266
+ db
267
+ )) as { error: string }
268
+
269
+ expect(result.error).toBeDefined()
270
+ })
271
+ })
272
+ })