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
@@ -506,6 +506,53 @@ describe('GitHub Tool Handlers', () => {
506
506
  expect(result.journalEntry.id).toBeGreaterThan(0)
507
507
  })
508
508
 
509
+ it('should close issue with move_to_done', async () => {
510
+ const github = createMockGitHub({
511
+ getProjectKanban: vi.fn().mockResolvedValue({
512
+ projectId: 'PVT_1',
513
+ projectTitle: 'Board',
514
+ statusFieldId: 'FIELD_1',
515
+ statusOptions: [
516
+ { id: 'OPT_DONE', name: 'Done' },
517
+ { id: 'OPT_TODO', name: 'Todo' },
518
+ ],
519
+ columns: [
520
+ {
521
+ status: 'Todo',
522
+ items: [
523
+ {
524
+ id: 'PVTITEM_ISSUE1',
525
+ title: 'Test Issue',
526
+ type: 'ISSUE',
527
+ number: 1,
528
+ },
529
+ ],
530
+ },
531
+ ],
532
+ totalItems: 1,
533
+ }),
534
+ })
535
+
536
+ const result = (await callTool(
537
+ 'close_github_issue_with_entry',
538
+ {
539
+ issue_number: 1,
540
+ resolution_notes: 'Done!',
541
+ move_to_done: true,
542
+ project_number: 1,
543
+ },
544
+ db,
545
+ undefined,
546
+ github
547
+ )) as {
548
+ success: boolean
549
+ issue: { number: number }
550
+ kanbanMove?: { success: boolean }
551
+ }
552
+
553
+ expect(result.success).toBe(true)
554
+ })
555
+
509
556
  it('should return error when issue not found', async () => {
510
557
  const github = createMockGitHub({
511
558
  getIssue: vi.fn().mockResolvedValue(null),
@@ -553,4 +600,856 @@ describe('GitHub Tool Handlers', () => {
553
600
  expect(result.error).toContain('GitHub integration not available')
554
601
  })
555
602
  })
603
+
604
+ // ========================================================================
605
+ // Repository Insights
606
+ // ========================================================================
607
+
608
+ describe('get_repo_insights', () => {
609
+ it('should return stars section by default', async () => {
610
+ const github = createMockGitHub({
611
+ getRepoStats: vi.fn().mockResolvedValue({
612
+ stars: 42,
613
+ forks: 5,
614
+ watchers: 3,
615
+ openIssues: 2,
616
+ size: 100,
617
+ defaultBranch: 'main',
618
+ }),
619
+ })
620
+
621
+ const result = (await callTool(
622
+ 'get_repo_insights',
623
+ {},
624
+ db,
625
+ undefined,
626
+ github
627
+ )) as Record<string, unknown>
628
+
629
+ expect(result['owner']).toBe('testowner')
630
+ expect(result['repo']).toBe('testrepo')
631
+ expect(result['stars']).toBe(42)
632
+ expect(result['forks']).toBe(5)
633
+ })
634
+
635
+ it('should return traffic section', async () => {
636
+ const github = createMockGitHub({
637
+ getTrafficData: vi.fn().mockResolvedValue({
638
+ views: { total: 100, uniques: 50 },
639
+ clones: { total: 20, uniques: 10 },
640
+ }),
641
+ })
642
+
643
+ const result = (await callTool(
644
+ 'get_repo_insights',
645
+ { sections: 'traffic' },
646
+ db,
647
+ undefined,
648
+ github
649
+ )) as Record<string, unknown>
650
+
651
+ expect(result['traffic']).toBeDefined()
652
+ })
653
+
654
+ it('should return referrers section', async () => {
655
+ const github = createMockGitHub({
656
+ getTopReferrers: vi
657
+ .fn()
658
+ .mockResolvedValue([{ referrer: 'google.com', count: 10, uniques: 5 }]),
659
+ })
660
+
661
+ const result = (await callTool(
662
+ 'get_repo_insights',
663
+ { sections: 'referrers' },
664
+ db,
665
+ undefined,
666
+ github
667
+ )) as Record<string, unknown>
668
+
669
+ expect(result['referrers']).toBeDefined()
670
+ })
671
+
672
+ it('should return paths section', async () => {
673
+ const github = createMockGitHub({
674
+ getPopularPaths: vi
675
+ .fn()
676
+ .mockResolvedValue([
677
+ { path: '/readme', title: 'README', count: 50, uniques: 25 },
678
+ ]),
679
+ })
680
+
681
+ const result = (await callTool(
682
+ 'get_repo_insights',
683
+ { sections: 'paths' },
684
+ db,
685
+ undefined,
686
+ github
687
+ )) as Record<string, unknown>
688
+
689
+ expect(result['paths']).toBeDefined()
690
+ })
691
+
692
+ it('should return all sections', async () => {
693
+ const github = createMockGitHub({
694
+ getRepoStats: vi.fn().mockResolvedValue({
695
+ stars: 42,
696
+ forks: 5,
697
+ watchers: 3,
698
+ openIssues: 2,
699
+ size: 100,
700
+ defaultBranch: 'main',
701
+ }),
702
+ getTrafficData: vi.fn().mockResolvedValue({
703
+ views: { total: 100, uniques: 50 },
704
+ clones: { total: 20, uniques: 10 },
705
+ }),
706
+ getTopReferrers: vi.fn().mockResolvedValue([]),
707
+ getPopularPaths: vi.fn().mockResolvedValue([]),
708
+ })
709
+
710
+ const result = (await callTool(
711
+ 'get_repo_insights',
712
+ { sections: 'all' },
713
+ db,
714
+ undefined,
715
+ github
716
+ )) as Record<string, unknown>
717
+
718
+ expect(result['stars']).toBe(42)
719
+ expect(result['traffic']).toBeDefined()
720
+ expect(result['referrers']).toBeDefined()
721
+ expect(result['paths']).toBeDefined()
722
+ // 'all' section includes size and defaultBranch
723
+ expect(result['size']).toBe(100)
724
+ expect(result['defaultBranch']).toBe('main')
725
+ })
726
+
727
+ it('should return error when no github', async () => {
728
+ const result = (await callTool('get_repo_insights', {}, db, undefined, undefined)) as {
729
+ error: string
730
+ }
731
+
732
+ expect(result.error).toContain('GitHub integration not available')
733
+ })
734
+
735
+ it('should return error when no owner/repo detected', async () => {
736
+ const github = createMockGitHub({
737
+ getRepoInfo: vi.fn().mockResolvedValue({
738
+ owner: null,
739
+ repo: null,
740
+ branch: null,
741
+ }),
742
+ })
743
+
744
+ const result = (await callTool('get_repo_insights', {}, db, undefined, github)) as {
745
+ error: string
746
+ requiresUserInput: boolean
747
+ }
748
+
749
+ expect(result.error).toContain('Could not auto-detect')
750
+ expect(result.requiresUserInput).toBe(true)
751
+ })
752
+ })
753
+
754
+ // ========================================================================
755
+ // Milestone edge cases
756
+ // ========================================================================
757
+
758
+ describe('milestone edge cases', () => {
759
+ it('get_github_milestones should return error when no repo', async () => {
760
+ const github = createMockGitHub({
761
+ getRepoInfo: vi.fn().mockResolvedValue({
762
+ owner: null,
763
+ repo: null,
764
+ branch: null,
765
+ }),
766
+ })
767
+
768
+ const result = (await callTool('get_github_milestones', {}, db, undefined, github)) as {
769
+ error: string
770
+ }
771
+
772
+ expect(result.error).toBeDefined()
773
+ })
774
+
775
+ it('get_github_milestone should return not found', async () => {
776
+ const github = createMockGitHub({
777
+ getMilestone: vi.fn().mockResolvedValue(null),
778
+ })
779
+
780
+ const result = (await callTool(
781
+ 'get_github_milestone',
782
+ { milestone_number: 999 },
783
+ db,
784
+ undefined,
785
+ github
786
+ )) as { error: string }
787
+
788
+ expect(result.error).toContain('not found')
789
+ })
790
+
791
+ it('create_github_milestone should return error when creation fails', async () => {
792
+ const github = createMockGitHub({
793
+ createMilestone: vi.fn().mockResolvedValue(null),
794
+ })
795
+
796
+ const result = (await callTool(
797
+ 'create_github_milestone',
798
+ { title: 'Will fail' },
799
+ db,
800
+ undefined,
801
+ github
802
+ )) as { error: string }
803
+
804
+ expect(result.error).toContain('Failed')
805
+ })
806
+
807
+ it('create_github_milestone with due date', async () => {
808
+ const github = createMockGitHub()
809
+
810
+ const result = (await callTool(
811
+ 'create_github_milestone',
812
+ { title: 'v3.0', due_on: '2026-06-01' },
813
+ db,
814
+ undefined,
815
+ github
816
+ )) as { success: boolean; milestone: { number: number } }
817
+
818
+ expect(result.success).toBe(true)
819
+ })
820
+
821
+ it('update_github_milestone should return error when update fails', async () => {
822
+ const github = createMockGitHub({
823
+ updateMilestone: vi.fn().mockResolvedValue(null),
824
+ })
825
+
826
+ const result = (await callTool(
827
+ 'update_github_milestone',
828
+ { milestone_number: 1, title: 'Will fail' },
829
+ db,
830
+ undefined,
831
+ github
832
+ )) as { error: string }
833
+
834
+ expect(result.error).toContain('Failed')
835
+ })
836
+
837
+ it('delete_github_milestone should return error when delete fails', async () => {
838
+ const github = createMockGitHub({
839
+ deleteMilestone: vi.fn().mockResolvedValue({ success: false }),
840
+ })
841
+
842
+ const result = (await callTool(
843
+ 'delete_github_milestone',
844
+ { milestone_number: 1, confirm: true },
845
+ db,
846
+ undefined,
847
+ github
848
+ )) as { success: boolean; message: string }
849
+
850
+ expect(result.success).toBe(false)
851
+ expect(result.message).toContain('Failed')
852
+ })
853
+
854
+ it('delete_github_milestone without confirm is rejected by zod', async () => {
855
+ const github = createMockGitHub()
856
+
857
+ // confirm must be literal true, passing false should fail
858
+ try {
859
+ await callTool(
860
+ 'delete_github_milestone',
861
+ { milestone_number: 1, confirm: false },
862
+ db,
863
+ undefined,
864
+ github
865
+ )
866
+ // If we get here, check the result for error
867
+ } catch {
868
+ // Expected: zod validation failure
869
+ }
870
+ })
871
+
872
+ it('get_github_milestones should return error when no github', async () => {
873
+ const result = (await callTool(
874
+ 'get_github_milestones',
875
+ {},
876
+ db,
877
+ undefined,
878
+ undefined
879
+ )) as { error: string }
880
+
881
+ expect(result.error).toContain('GitHub integration not available')
882
+ })
883
+ })
884
+
885
+ // ========================================================================
886
+ // Backup tools
887
+ // ========================================================================
888
+
889
+ describe('backup_journal', () => {
890
+ it('should create a backup', async () => {
891
+ const result = (await callTool('backup_journal', {}, db)) as {
892
+ success: boolean
893
+ filename: string
894
+ }
895
+
896
+ expect(result.success).toBe(true)
897
+ expect(result.filename).toBeDefined()
898
+ })
899
+
900
+ it('should create a backup with custom name', async () => {
901
+ const result = (await callTool('backup_journal', { name: 'my-test-backup' }, db)) as {
902
+ success: boolean
903
+ filename: string
904
+ }
905
+
906
+ expect(result.success).toBe(true)
907
+ })
908
+ })
909
+
910
+ describe('list_backups', () => {
911
+ it('should list backups', async () => {
912
+ const result = (await callTool('list_backups', {}, db)) as {
913
+ backups: unknown[]
914
+ total: number
915
+ }
916
+
917
+ expect(result.backups).toBeDefined()
918
+ expect(typeof result.total).toBe('number')
919
+ })
920
+ })
921
+
922
+ describe('cleanup_backups', () => {
923
+ it('should cleanup old backups', async () => {
924
+ const result = (await callTool('cleanup_backups', { keep_count: 5 }, db)) as {
925
+ success: boolean
926
+ keptCount: number
927
+ }
928
+
929
+ expect(result.success).toBe(true)
930
+ expect(typeof result.keptCount).toBe('number')
931
+ })
932
+ })
933
+
934
+ // ========================================================================
935
+ // create_github_issue_with_entry - project integration paths
936
+ // ========================================================================
937
+
938
+ describe('create_github_issue_with_entry - project integration', () => {
939
+ it('should add issue to project and set initial status', async () => {
940
+ const github = createMockGitHub({
941
+ getProjectKanban: vi.fn().mockResolvedValue({
942
+ projectId: 'PVT_1',
943
+ projectTitle: 'Board',
944
+ statusFieldId: 'FIELD_1',
945
+ statusOptions: [
946
+ { id: 'OPT_BACKLOG', name: 'Backlog' },
947
+ { id: 'OPT_DONE', name: 'Done' },
948
+ ],
949
+ columns: [],
950
+ totalItems: 0,
951
+ }),
952
+ addProjectItem: vi.fn().mockResolvedValue({ success: true, itemId: 'PVTITEM_NEW' }),
953
+ moveProjectItem: vi.fn().mockResolvedValue({ success: true }),
954
+ })
955
+
956
+ const result = (await callTool(
957
+ 'create_github_issue_with_entry',
958
+ {
959
+ title: 'Project Issue',
960
+ journal_content: 'Added to project',
961
+ project_number: 1,
962
+ initial_status: 'Backlog',
963
+ },
964
+ db,
965
+ undefined,
966
+ github
967
+ )) as {
968
+ success: boolean
969
+ project?: { added: boolean; initialStatus?: { set: boolean } }
970
+ }
971
+
972
+ expect(result.success).toBe(true)
973
+ expect(result.project?.added).toBe(true)
974
+ expect(result.project?.initialStatus?.set).toBe(true)
975
+ })
976
+
977
+ it('should handle project not found when adding issue', async () => {
978
+ const github = createMockGitHub({
979
+ getProjectKanban: vi.fn().mockResolvedValue(null),
980
+ })
981
+
982
+ const result = (await callTool(
983
+ 'create_github_issue_with_entry',
984
+ {
985
+ title: 'Issue for missing project',
986
+ journal_content: 'Test',
987
+ project_number: 999,
988
+ },
989
+ db,
990
+ undefined,
991
+ github
992
+ )) as { success: boolean; project?: { added: boolean; error: string } }
993
+
994
+ expect(result.success).toBe(true) // Issue still created
995
+ expect(result.project?.added).toBe(false)
996
+ expect(result.project?.error).toContain('not found')
997
+ })
998
+
999
+ it('should handle addProjectItem failure', async () => {
1000
+ const github = createMockGitHub({
1001
+ getProjectKanban: vi.fn().mockResolvedValue({
1002
+ projectId: 'PVT_1',
1003
+ projectTitle: 'Board',
1004
+ statusFieldId: 'FIELD_1',
1005
+ statusOptions: [],
1006
+ columns: [],
1007
+ totalItems: 0,
1008
+ }),
1009
+ addProjectItem: vi
1010
+ .fn()
1011
+ .mockResolvedValue({ success: false, error: 'Permission denied' }),
1012
+ })
1013
+
1014
+ const result = (await callTool(
1015
+ 'create_github_issue_with_entry',
1016
+ {
1017
+ title: 'Issue add fail',
1018
+ journal_content: 'Test',
1019
+ project_number: 1,
1020
+ },
1021
+ db,
1022
+ undefined,
1023
+ github
1024
+ )) as { success: boolean; project?: { added: boolean; error: string } }
1025
+
1026
+ expect(result.success).toBe(true)
1027
+ expect(result.project?.added).toBe(false)
1028
+ })
1029
+
1030
+ it('should handle initial_status not found on board', async () => {
1031
+ const github = createMockGitHub({
1032
+ getProjectKanban: vi.fn().mockResolvedValue({
1033
+ projectId: 'PVT_1',
1034
+ projectTitle: 'Board',
1035
+ statusFieldId: 'FIELD_1',
1036
+ statusOptions: [{ id: 'OPT_TODO', name: 'Todo' }],
1037
+ columns: [],
1038
+ totalItems: 0,
1039
+ }),
1040
+ addProjectItem: vi.fn().mockResolvedValue({ success: true, itemId: 'PVTITEM_NEW' }),
1041
+ })
1042
+
1043
+ const result = (await callTool(
1044
+ 'create_github_issue_with_entry',
1045
+ {
1046
+ title: 'Issue bad status',
1047
+ journal_content: 'Test',
1048
+ project_number: 1,
1049
+ initial_status: 'Nonexistent',
1050
+ },
1051
+ db,
1052
+ undefined,
1053
+ github
1054
+ )) as {
1055
+ success: boolean
1056
+ project?: { added: boolean; initialStatus?: { set: boolean; error: string } }
1057
+ }
1058
+
1059
+ expect(result.success).toBe(true)
1060
+ expect(result.project?.added).toBe(true)
1061
+ expect(result.project?.initialStatus?.set).toBe(false)
1062
+ expect(result.project?.initialStatus?.error).toContain('not found')
1063
+ })
1064
+
1065
+ it('should handle moveProjectItem failure for initial status', async () => {
1066
+ const github = createMockGitHub({
1067
+ getProjectKanban: vi.fn().mockResolvedValue({
1068
+ projectId: 'PVT_1',
1069
+ projectTitle: 'Board',
1070
+ statusFieldId: 'FIELD_1',
1071
+ statusOptions: [{ id: 'OPT_BACKLOG', name: 'Backlog' }],
1072
+ columns: [],
1073
+ totalItems: 0,
1074
+ }),
1075
+ addProjectItem: vi.fn().mockResolvedValue({ success: true, itemId: 'PVTITEM_NEW' }),
1076
+ moveProjectItem: vi
1077
+ .fn()
1078
+ .mockResolvedValue({ success: false, error: 'Move failed' }),
1079
+ })
1080
+
1081
+ const result = (await callTool(
1082
+ 'create_github_issue_with_entry',
1083
+ {
1084
+ title: 'Issue move fail',
1085
+ journal_content: 'Test',
1086
+ project_number: 1,
1087
+ initial_status: 'Backlog',
1088
+ },
1089
+ db,
1090
+ undefined,
1091
+ github
1092
+ )) as {
1093
+ success: boolean
1094
+ project?: { added: boolean; initialStatus?: { set: boolean; error: string } }
1095
+ }
1096
+
1097
+ expect(result.success).toBe(true)
1098
+ expect(result.project?.initialStatus?.set).toBe(false)
1099
+ })
1100
+
1101
+ it('should handle createIssue returning null', async () => {
1102
+ const github = createMockGitHub({
1103
+ createIssue: vi.fn().mockResolvedValue(null),
1104
+ })
1105
+
1106
+ const result = (await callTool(
1107
+ 'create_github_issue_with_entry',
1108
+ { title: 'Will fail', journal_content: 'Test' },
1109
+ db,
1110
+ undefined,
1111
+ github
1112
+ )) as { error: string }
1113
+
1114
+ expect(result.error).toContain('Failed to create')
1115
+ })
1116
+ })
1117
+
1118
+ // ========================================================================
1119
+ // close_github_issue_with_entry - Kanban edge cases
1120
+ // ========================================================================
1121
+
1122
+ describe('close_github_issue_with_entry - Kanban edge cases', () => {
1123
+ it('should return kanban error when move_to_done with no project_number', async () => {
1124
+ const github = createMockGitHub()
1125
+
1126
+ const result = (await callTool(
1127
+ 'close_github_issue_with_entry',
1128
+ {
1129
+ issue_number: 1,
1130
+ resolution_notes: 'Done!',
1131
+ move_to_done: true,
1132
+ // No project_number and no defaultProjectNumber
1133
+ },
1134
+ db,
1135
+ undefined,
1136
+ github
1137
+ )) as { success: boolean; kanban?: { moved: boolean; error: string } }
1138
+
1139
+ expect(result.success).toBe(true) // Issue still closes
1140
+ expect(result.kanban?.moved).toBe(false)
1141
+ expect(result.kanban?.error).toContain('project_number required')
1142
+ })
1143
+
1144
+ it('should handle kanban board not found', async () => {
1145
+ const github = createMockGitHub({
1146
+ getProjectKanban: vi.fn().mockResolvedValue(null),
1147
+ })
1148
+
1149
+ const result = (await callTool(
1150
+ 'close_github_issue_with_entry',
1151
+ {
1152
+ issue_number: 1,
1153
+ resolution_notes: 'Done!',
1154
+ move_to_done: true,
1155
+ project_number: 999,
1156
+ },
1157
+ db,
1158
+ undefined,
1159
+ github
1160
+ )) as { success: boolean; kanban?: { moved: boolean; error: string } }
1161
+
1162
+ expect(result.success).toBe(true)
1163
+ expect(result.kanban?.moved).toBe(false)
1164
+ expect(result.kanban?.error).toContain('not found')
1165
+ })
1166
+
1167
+ it('should handle issue not found on project board', async () => {
1168
+ const github = createMockGitHub({
1169
+ getProjectKanban: vi.fn().mockResolvedValue({
1170
+ projectId: 'PVT_1',
1171
+ projectTitle: 'Board',
1172
+ statusFieldId: 'FIELD_1',
1173
+ statusOptions: [{ id: 'OPT_DONE', name: 'Done' }],
1174
+ columns: [
1175
+ {
1176
+ status: 'Todo',
1177
+ items: [
1178
+ { id: 'PVTITEM_OTHER', title: 'Other', type: 'ISSUE', number: 99 },
1179
+ ],
1180
+ },
1181
+ ],
1182
+ totalItems: 1,
1183
+ }),
1184
+ })
1185
+
1186
+ const result = (await callTool(
1187
+ 'close_github_issue_with_entry',
1188
+ {
1189
+ issue_number: 1,
1190
+ resolution_notes: 'Done!',
1191
+ move_to_done: true,
1192
+ project_number: 1,
1193
+ },
1194
+ db,
1195
+ undefined,
1196
+ github
1197
+ )) as { success: boolean; kanban?: { moved: boolean; error: string } }
1198
+
1199
+ expect(result.success).toBe(true)
1200
+ expect(result.kanban?.moved).toBe(false)
1201
+ expect(result.kanban?.error).toContain('not found on project board')
1202
+ })
1203
+
1204
+ it('should handle "Done" column not found on board', async () => {
1205
+ const github = createMockGitHub({
1206
+ getProjectKanban: vi.fn().mockResolvedValue({
1207
+ projectId: 'PVT_1',
1208
+ projectTitle: 'Board',
1209
+ statusFieldId: 'FIELD_1',
1210
+ statusOptions: [{ id: 'OPT_TODO', name: 'Todo' }], // No "Done" status
1211
+ columns: [
1212
+ {
1213
+ status: 'Todo',
1214
+ items: [
1215
+ {
1216
+ id: 'PVTITEM_ISSUE1',
1217
+ title: 'Test Issue',
1218
+ type: 'ISSUE',
1219
+ number: 1,
1220
+ },
1221
+ ],
1222
+ },
1223
+ ],
1224
+ totalItems: 1,
1225
+ }),
1226
+ })
1227
+
1228
+ const result = (await callTool(
1229
+ 'close_github_issue_with_entry',
1230
+ {
1231
+ issue_number: 1,
1232
+ resolution_notes: 'Done!',
1233
+ move_to_done: true,
1234
+ project_number: 1,
1235
+ },
1236
+ db,
1237
+ undefined,
1238
+ github
1239
+ )) as { success: boolean; kanban?: { moved: boolean; error: string } }
1240
+
1241
+ expect(result.success).toBe(true)
1242
+ expect(result.kanban?.moved).toBe(false)
1243
+ expect(result.kanban?.error).toContain('"Done" status column not found')
1244
+ })
1245
+
1246
+ it('should handle closeIssue returning null (API failure)', async () => {
1247
+ const github = createMockGitHub({
1248
+ closeIssue: vi.fn().mockResolvedValue(null),
1249
+ })
1250
+
1251
+ const result = (await callTool(
1252
+ 'close_github_issue_with_entry',
1253
+ { issue_number: 1, resolution_notes: 'Will fail' },
1254
+ db,
1255
+ undefined,
1256
+ github
1257
+ )) as { error: string }
1258
+
1259
+ expect(result.error).toContain('Failed to close')
1260
+ })
1261
+
1262
+ it('should handle no-repo detection on close', async () => {
1263
+ const github = createMockGitHub({
1264
+ getRepoInfo: vi.fn().mockResolvedValue({
1265
+ owner: null,
1266
+ repo: null,
1267
+ branch: null,
1268
+ }),
1269
+ })
1270
+
1271
+ const result = (await callTool(
1272
+ 'close_github_issue_with_entry',
1273
+ { issue_number: 1 },
1274
+ db,
1275
+ undefined,
1276
+ github
1277
+ )) as { error: string; requiresUserInput: boolean }
1278
+
1279
+ expect(result.error).toContain('Could not auto-detect')
1280
+ expect(result.requiresUserInput).toBe(true)
1281
+ })
1282
+ })
1283
+
1284
+ // ========================================================================
1285
+ // Milestone tools - no-github and no-repo error paths
1286
+ // ========================================================================
1287
+
1288
+ describe('milestone tools - additional error paths', () => {
1289
+ it('get_github_milestone should return error when no github', async () => {
1290
+ const result = (await callTool(
1291
+ 'get_github_milestone',
1292
+ { milestone_number: 1 },
1293
+ db,
1294
+ undefined,
1295
+ undefined
1296
+ )) as { error: string }
1297
+
1298
+ expect(result.error).toContain('GitHub integration not available')
1299
+ })
1300
+
1301
+ it('get_github_milestone should return error when no repo detected', async () => {
1302
+ const github = createMockGitHub({
1303
+ getRepoInfo: vi.fn().mockResolvedValue({
1304
+ owner: null,
1305
+ repo: null,
1306
+ branch: null,
1307
+ }),
1308
+ })
1309
+
1310
+ const result = (await callTool(
1311
+ 'get_github_milestone',
1312
+ { milestone_number: 1 },
1313
+ db,
1314
+ undefined,
1315
+ github
1316
+ )) as { error: string; requiresUserInput: boolean }
1317
+
1318
+ expect(result.error).toContain('Could not auto-detect')
1319
+ expect(result.requiresUserInput).toBe(true)
1320
+ })
1321
+
1322
+ it('create_github_milestone should return error when no repo detected', async () => {
1323
+ const github = createMockGitHub({
1324
+ getRepoInfo: vi.fn().mockResolvedValue({
1325
+ owner: null,
1326
+ repo: null,
1327
+ branch: null,
1328
+ }),
1329
+ })
1330
+
1331
+ const result = (await callTool(
1332
+ 'create_github_milestone',
1333
+ { title: 'No repo' },
1334
+ db,
1335
+ undefined,
1336
+ github
1337
+ )) as { error: string; requiresUserInput: boolean }
1338
+
1339
+ expect(result.error).toContain('Could not auto-detect')
1340
+ expect(result.requiresUserInput).toBe(true)
1341
+ })
1342
+
1343
+ it('create_github_milestone should return error when no github', async () => {
1344
+ const result = (await callTool(
1345
+ 'create_github_milestone',
1346
+ { title: 'No github' },
1347
+ db,
1348
+ undefined,
1349
+ undefined
1350
+ )) as { error: string }
1351
+
1352
+ expect(result.error).toContain('GitHub integration not available')
1353
+ })
1354
+
1355
+ it('update_github_milestone should return error when no github', async () => {
1356
+ const result = (await callTool(
1357
+ 'update_github_milestone',
1358
+ { milestone_number: 1, title: 'No github' },
1359
+ db,
1360
+ undefined,
1361
+ undefined
1362
+ )) as { error: string }
1363
+
1364
+ expect(result.error).toContain('GitHub integration not available')
1365
+ })
1366
+
1367
+ it('update_github_milestone should return error when no repo detected', async () => {
1368
+ const github = createMockGitHub({
1369
+ getRepoInfo: vi.fn().mockResolvedValue({
1370
+ owner: null,
1371
+ repo: null,
1372
+ branch: null,
1373
+ }),
1374
+ })
1375
+
1376
+ const result = (await callTool(
1377
+ 'update_github_milestone',
1378
+ { milestone_number: 1, title: 'No repo' },
1379
+ db,
1380
+ undefined,
1381
+ github
1382
+ )) as { error: string; requiresUserInput: boolean }
1383
+
1384
+ expect(result.error).toContain('Could not auto-detect')
1385
+ expect(result.requiresUserInput).toBe(true)
1386
+ })
1387
+
1388
+ it('delete_github_milestone should return error when no github', async () => {
1389
+ const result = (await callTool(
1390
+ 'delete_github_milestone',
1391
+ { milestone_number: 1, confirm: true },
1392
+ db,
1393
+ undefined,
1394
+ undefined
1395
+ )) as { error: string }
1396
+
1397
+ expect(result.error).toContain('GitHub integration not available')
1398
+ })
1399
+
1400
+ it('delete_github_milestone should return error when no repo detected', async () => {
1401
+ const github = createMockGitHub({
1402
+ getRepoInfo: vi.fn().mockResolvedValue({
1403
+ owner: null,
1404
+ repo: null,
1405
+ branch: null,
1406
+ }),
1407
+ })
1408
+
1409
+ const result = (await callTool(
1410
+ 'delete_github_milestone',
1411
+ { milestone_number: 1, confirm: true },
1412
+ db,
1413
+ undefined,
1414
+ github
1415
+ )) as { error: string; requiresUserInput: boolean }
1416
+
1417
+ expect(result.error).toContain('Could not auto-detect')
1418
+ expect(result.requiresUserInput).toBe(true)
1419
+ })
1420
+ })
1421
+
1422
+ // ========================================================================
1423
+ // restore_backup
1424
+ // ========================================================================
1425
+
1426
+ describe('restore_backup', () => {
1427
+ it('should restore from a backup file', async () => {
1428
+ // First create a backup to restore from
1429
+ const backupResult = (await callTool(
1430
+ 'backup_journal',
1431
+ { name: 'restore-test' },
1432
+ db
1433
+ )) as { success: boolean; filename: string }
1434
+
1435
+ expect(backupResult.success).toBe(true)
1436
+
1437
+ const result = (await callTool(
1438
+ 'restore_backup',
1439
+ { filename: backupResult.filename, confirm: true },
1440
+ db
1441
+ )) as {
1442
+ success: boolean
1443
+ message: string
1444
+ restoredFrom: string
1445
+ previousEntryCount: number
1446
+ newEntryCount: number
1447
+ }
1448
+
1449
+ expect(result.success).toBe(true)
1450
+ expect(result.message).toContain('restored')
1451
+ expect(typeof result.previousEntryCount).toBe('number')
1452
+ expect(typeof result.newEntryCount).toBe('number')
1453
+ })
1454
+ })
556
1455
  })