memory-journal-mcp 4.4.2 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.github/workflows/lint-and-test.yml +1 -1
  2. package/.github/workflows/security-update.yml +1 -1
  3. package/CHANGELOG.md +81 -1
  4. package/DOCKER_README.md +57 -7
  5. package/Dockerfile +17 -17
  6. package/README.md +65 -6
  7. package/SECURITY.md +27 -35
  8. package/dist/cli.js +10 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/constants/ServerInstructions.d.ts +5 -1
  11. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  12. package/dist/constants/ServerInstructions.js +137 -83
  13. package/dist/constants/ServerInstructions.js.map +1 -1
  14. package/dist/database/SqliteAdapter.d.ts +2 -1
  15. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  16. package/dist/database/SqliteAdapter.js +15 -8
  17. package/dist/database/SqliteAdapter.js.map +1 -1
  18. package/dist/handlers/resources/index.d.ts +3 -1
  19. package/dist/handlers/resources/index.d.ts.map +1 -1
  20. package/dist/handlers/resources/index.js +5 -2
  21. package/dist/handlers/resources/index.js.map +1 -1
  22. package/dist/handlers/tools/index.d.ts.map +1 -1
  23. package/dist/handlers/tools/index.js +63 -16
  24. package/dist/handlers/tools/index.js.map +1 -1
  25. package/dist/server/McpServer.d.ts +2 -0
  26. package/dist/server/McpServer.d.ts.map +1 -1
  27. package/dist/server/McpServer.js +43 -2
  28. package/dist/server/McpServer.js.map +1 -1
  29. package/dist/server/Scheduler.d.ts +91 -0
  30. package/dist/server/Scheduler.d.ts.map +1 -0
  31. package/dist/server/Scheduler.js +201 -0
  32. package/dist/server/Scheduler.js.map +1 -0
  33. package/dist/utils/logger.d.ts.map +1 -1
  34. package/dist/utils/logger.js +6 -3
  35. package/dist/utils/logger.js.map +1 -1
  36. package/dist/utils/security-utils.d.ts +0 -21
  37. package/dist/utils/security-utils.d.ts.map +1 -1
  38. package/dist/utils/security-utils.js +0 -47
  39. package/dist/utils/security-utils.js.map +1 -1
  40. package/hooks/README.md +107 -0
  41. package/hooks/cursor/hooks.json +10 -0
  42. package/hooks/cursor/memory-journal.mdc +22 -0
  43. package/hooks/cursor/session-end.sh +19 -0
  44. package/hooks/kilo-code/session-end-mode.json +11 -0
  45. package/hooks/kiro/session-end.md +13 -0
  46. package/package.json +8 -8
  47. package/releases/v4.5.0.md +116 -0
  48. package/scripts/generate-server-instructions.ts +176 -0
  49. package/scripts/server-instructions-function-body.ts +77 -0
  50. package/server.json +3 -3
  51. package/src/cli.ts +26 -0
  52. package/src/constants/ServerInstructions.ts +137 -83
  53. package/src/constants/server-instructions.md +262 -0
  54. package/src/database/SqliteAdapter.ts +22 -8
  55. package/src/handlers/resources/index.ts +8 -2
  56. package/src/handlers/tools/index.ts +70 -20
  57. package/src/server/McpServer.ts +60 -2
  58. package/src/server/Scheduler.ts +278 -0
  59. package/src/utils/logger.ts +6 -3
  60. package/src/utils/security-utils.ts +0 -52
  61. package/tests/constants/server-instructions.test.ts +26 -0
  62. package/tests/database/sqlite-adapter.test.ts +84 -0
  63. package/tests/filtering/tool-filter.test.ts +46 -0
  64. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  65. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  66. package/tests/handlers/prompt-handlers.test.ts +40 -0
  67. package/tests/handlers/resource-handlers.test.ts +32 -0
  68. package/tests/handlers/tool-handlers.test.ts +13 -2
  69. package/tests/security/sql-injection.test.ts +3 -54
  70. package/tests/server/mcp-server.test.ts +491 -5
  71. package/tests/server/scheduler.test.ts +400 -0
  72. package/tests/vector/vector-search-manager.test.ts +60 -0
  73. package/.vscode/settings.json +0 -84
@@ -470,4 +470,457 @@ describe('GitHub Resource Handlers', () => {
470
470
  expect(data.more.repoInsights).toBe('memory://github/insights')
471
471
  })
472
472
  })
473
+
474
+ // ========================================================================
475
+ // memory://github/insights
476
+ // ========================================================================
477
+
478
+ describe('memory://github/insights', () => {
479
+ it('should return insights with stars and traffic', async () => {
480
+ const github = createMockGitHub()
481
+ const result = await readResource(
482
+ 'memory://github/insights',
483
+ db,
484
+ undefined,
485
+ undefined,
486
+ github
487
+ )
488
+
489
+ const data = result.data as {
490
+ repository: string
491
+ stars: number
492
+ forks: number
493
+ clones14d?: number
494
+ views14d?: number
495
+ }
496
+
497
+ expect(data.repository).toBe('testowner/testrepo')
498
+ expect(data.stars).toBe(42)
499
+ expect(data.forks).toBe(7)
500
+ expect(data.clones14d).toBe(120)
501
+ expect(data.views14d).toBe(500)
502
+ })
503
+
504
+ it('should return error when no github', async () => {
505
+ const result = await readResource(
506
+ 'memory://github/insights',
507
+ db,
508
+ undefined,
509
+ undefined,
510
+ null
511
+ )
512
+
513
+ const data = result.data as { error: string }
514
+ expect(data.error).toContain('GitHub integration not available')
515
+ })
516
+
517
+ it('should return error when no owner/repo', async () => {
518
+ const github = createMockGitHub({
519
+ getRepoInfo: vi.fn().mockResolvedValue({ owner: null, repo: null, branch: null }),
520
+ })
521
+
522
+ const result = await readResource(
523
+ 'memory://github/insights',
524
+ db,
525
+ undefined,
526
+ undefined,
527
+ github
528
+ )
529
+
530
+ const data = result.data as { error: string }
531
+ expect(data.error).toContain('Could not detect repository')
532
+ })
533
+
534
+ it('should handle traffic data failure gracefully', async () => {
535
+ const github = createMockGitHub({
536
+ getTrafficData: vi.fn().mockRejectedValue(new Error('403')),
537
+ })
538
+
539
+ const result = await readResource(
540
+ 'memory://github/insights',
541
+ db,
542
+ undefined,
543
+ undefined,
544
+ github
545
+ )
546
+
547
+ const data = result.data as {
548
+ stars: number
549
+ hint?: string
550
+ }
551
+
552
+ expect(data.stars).toBe(42)
553
+ expect(data.hint).toBeDefined()
554
+ })
555
+ })
556
+
557
+ // ========================================================================
558
+ // memory://graph/actions
559
+ // ========================================================================
560
+
561
+ describe('memory://graph/actions', () => {
562
+ it('should return mermaid diagram with workflow runs', async () => {
563
+ const github = createMockGitHub()
564
+ const result = await readResource(
565
+ 'memory://graph/actions',
566
+ db,
567
+ undefined,
568
+ undefined,
569
+ github
570
+ )
571
+
572
+ const data = result.data as {
573
+ format: string
574
+ diagram: string
575
+ workflowRunCount: number
576
+ }
577
+
578
+ expect(data.format).toBe('mermaid')
579
+ expect(data.diagram).toContain('graph LR')
580
+ expect(data.workflowRunCount).toBe(1)
581
+ })
582
+
583
+ it('should return fallback when no github', async () => {
584
+ const result = await readResource(
585
+ 'memory://graph/actions',
586
+ db,
587
+ undefined,
588
+ undefined,
589
+ null
590
+ )
591
+
592
+ const data = result.data as { format: string; diagram: string; message: string }
593
+ expect(data.diagram).toContain('NoGitHub')
594
+ })
595
+
596
+ it('should return fallback when no repo detected', async () => {
597
+ const github = createMockGitHub({
598
+ getRepoInfo: vi.fn().mockResolvedValue({ owner: null, repo: null, branch: null }),
599
+ })
600
+
601
+ const result = await readResource(
602
+ 'memory://graph/actions',
603
+ db,
604
+ undefined,
605
+ undefined,
606
+ github
607
+ )
608
+
609
+ const data = result.data as { diagram: string }
610
+ expect(data.diagram).toContain('NoRepo')
611
+ })
612
+
613
+ it('should return fallback when no workflow runs', async () => {
614
+ const github = createMockGitHub({
615
+ getWorkflowRuns: vi.fn().mockResolvedValue([]),
616
+ })
617
+
618
+ const result = await readResource(
619
+ 'memory://graph/actions',
620
+ db,
621
+ undefined,
622
+ undefined,
623
+ github
624
+ )
625
+
626
+ const data = result.data as { diagram: string }
627
+ expect(data.diagram).toContain('NoRuns')
628
+ })
629
+ })
630
+
631
+ // ========================================================================
632
+ // memory://actions/recent
633
+ // ========================================================================
634
+
635
+ describe('memory://actions/recent', () => {
636
+ it('should return entries from GitHub API when available', async () => {
637
+ const github = createMockGitHub()
638
+ const result = await readResource(
639
+ 'memory://actions/recent',
640
+ db,
641
+ undefined,
642
+ undefined,
643
+ github
644
+ )
645
+
646
+ const data = result.data as {
647
+ entries: unknown[]
648
+ count: number
649
+ source: string
650
+ }
651
+
652
+ expect(data.source).toBe('github_api')
653
+ expect(data.count).toBe(1)
654
+ })
655
+
656
+ it('should fallback to database when no github', async () => {
657
+ const result = await readResource(
658
+ 'memory://actions/recent',
659
+ db,
660
+ undefined,
661
+ undefined,
662
+ null
663
+ )
664
+
665
+ const data = result.data as { source: string }
666
+ expect(data.source).toBe('database')
667
+ })
668
+
669
+ it('should fallback to database when github API fails', async () => {
670
+ const github = createMockGitHub({
671
+ getRepoInfo: vi.fn().mockRejectedValue(new Error('API error')),
672
+ })
673
+
674
+ const result = await readResource(
675
+ 'memory://actions/recent',
676
+ db,
677
+ undefined,
678
+ undefined,
679
+ github
680
+ )
681
+
682
+ const data = result.data as { source: string }
683
+ expect(data.source).toBe('database')
684
+ })
685
+ })
686
+
687
+ // ========================================================================
688
+ // memory://prs/{n}/timeline
689
+ // ========================================================================
690
+
691
+ describe('memory://prs/{n}/timeline', () => {
692
+ it('should return timeline with PR metadata from GitHub', async () => {
693
+ const github = createMockGitHub({
694
+ getPullRequest: vi.fn().mockResolvedValue({
695
+ number: 10,
696
+ title: 'Feature PR',
697
+ state: 'open',
698
+ draft: false,
699
+ mergedAt: null,
700
+ closedAt: null,
701
+ author: 'dev1',
702
+ headBranch: 'feature',
703
+ baseBranch: 'main',
704
+ }),
705
+ })
706
+
707
+ const result = await readResource(
708
+ 'memory://prs/10/timeline',
709
+ db,
710
+ undefined,
711
+ undefined,
712
+ github
713
+ )
714
+
715
+ const data = result.data as {
716
+ prNumber: number
717
+ prMetadata: { title: string; state: string }
718
+ timelineNote: string
719
+ }
720
+
721
+ expect(data.prNumber).toBe(10)
722
+ expect(data.prMetadata).toBeDefined()
723
+ expect(data.prMetadata.title).toBe('Feature PR')
724
+ expect(data.timelineNote).toContain('open')
725
+ })
726
+
727
+ it('should return timeline without PR metadata when no github', async () => {
728
+ const result = await readResource(
729
+ 'memory://prs/10/timeline',
730
+ db,
731
+ undefined,
732
+ undefined,
733
+ null
734
+ )
735
+
736
+ const data = result.data as {
737
+ prNumber: number
738
+ prMetadata: unknown
739
+ timelineNote: string
740
+ }
741
+
742
+ expect(data.prNumber).toBe(10)
743
+ expect(data.prMetadata).toBeNull()
744
+ expect(data.timelineNote).toContain('unavailable')
745
+ })
746
+
747
+ it('should return error for invalid PR number', async () => {
748
+ const result = await readResource(
749
+ 'memory://prs/abc/timeline',
750
+ db,
751
+ undefined,
752
+ undefined,
753
+ null
754
+ )
755
+
756
+ const data = result.data as { error: string }
757
+ expect(data.error).toContain('Invalid PR number')
758
+ })
759
+ })
760
+
761
+ // ========================================================================
762
+ // memory://health
763
+ // ========================================================================
764
+
765
+ describe('memory://health', () => {
766
+ it('should return health status with vector and scheduler info', async () => {
767
+ const mockVectorManager = {
768
+ getStats: vi.fn().mockResolvedValue({
769
+ itemCount: 50,
770
+ modelName: 'test-model',
771
+ dimensions: 384,
772
+ }),
773
+ }
774
+ const mockScheduler = {
775
+ getStatus: vi.fn().mockReturnValue({
776
+ active: true,
777
+ jobs: [{ name: 'backup', intervalMinutes: 60 }],
778
+ }),
779
+ }
780
+
781
+ const result = await readResource(
782
+ 'memory://health',
783
+ db,
784
+ mockVectorManager as any,
785
+ null,
786
+ null,
787
+ mockScheduler as any
788
+ )
789
+
790
+ const data = result.data as {
791
+ vectorIndex: { available: boolean; itemCount: number }
792
+ scheduler: { active: boolean }
793
+ toolFilter: { active: boolean }
794
+ }
795
+
796
+ expect(data.vectorIndex).toBeDefined()
797
+ expect(data.vectorIndex.available).toBe(true)
798
+ expect(data.vectorIndex.itemCount).toBe(50)
799
+ expect(data.scheduler.active).toBe(true)
800
+ expect(data.toolFilter.active).toBe(false) // filterConfig is null
801
+ })
802
+
803
+ it('should handle vector manager error gracefully', async () => {
804
+ const mockVectorManager = {
805
+ getStats: vi.fn().mockRejectedValue(new Error('Not initialized')),
806
+ }
807
+
808
+ const result = await readResource(
809
+ 'memory://health',
810
+ db,
811
+ mockVectorManager as any,
812
+ null,
813
+ null,
814
+ null
815
+ )
816
+
817
+ const data = result.data as {
818
+ vectorIndex: { available: boolean }
819
+ }
820
+
821
+ expect(data.vectorIndex.available).toBe(false)
822
+ })
823
+ })
824
+
825
+ // ========================================================================
826
+ // memory://github/status CI edge cases
827
+ // ========================================================================
828
+
829
+ describe('memory://github/status CI edge cases', () => {
830
+ it('should report failing CI when latest completed run failed', async () => {
831
+ const github = createMockGitHub({
832
+ getWorkflowRuns: vi.fn().mockResolvedValue([
833
+ {
834
+ id: 200,
835
+ name: 'CI',
836
+ status: 'completed',
837
+ conclusion: 'failure',
838
+ url: 'url',
839
+ headBranch: 'main',
840
+ headSha: 'def5678',
841
+ createdAt: '2025-01-02T00:00:00Z',
842
+ updatedAt: '2025-01-02T01:00:00Z',
843
+ },
844
+ ]),
845
+ })
846
+
847
+ const result = await readResource(
848
+ 'memory://github/status',
849
+ db,
850
+ undefined,
851
+ undefined,
852
+ github
853
+ )
854
+
855
+ const data = result.data as {
856
+ ci: { status: string }
857
+ }
858
+
859
+ expect(data.ci.status).toBe('failing')
860
+ })
861
+
862
+ it('should report pending CI when runs are in progress', async () => {
863
+ const github = createMockGitHub({
864
+ getWorkflowRuns: vi.fn().mockResolvedValue([
865
+ {
866
+ id: 300,
867
+ name: 'CI',
868
+ status: 'in_progress',
869
+ conclusion: null,
870
+ url: 'url',
871
+ headBranch: 'main',
872
+ headSha: 'ghi9012',
873
+ createdAt: '2025-01-03T00:00:00Z',
874
+ updatedAt: '2025-01-03T00:30:00Z',
875
+ },
876
+ ]),
877
+ })
878
+
879
+ const result = await readResource(
880
+ 'memory://github/status',
881
+ db,
882
+ undefined,
883
+ undefined,
884
+ github
885
+ )
886
+
887
+ const data = result.data as {
888
+ ci: { status: string }
889
+ }
890
+
891
+ expect(data.ci.status).toBe('pending')
892
+ })
893
+
894
+ it('should include kanban summary and milestones in status', async () => {
895
+ const github = createMockGitHub({
896
+ getProjectKanban: vi.fn().mockResolvedValue({
897
+ projectId: 'PVT_1',
898
+ columns: [
899
+ { status: 'Todo', items: [{ id: 'I1' }, { id: 'I2' }] },
900
+ { status: 'Done', items: [{ id: 'I3' }] },
901
+ ],
902
+ statusOptions: [],
903
+ totalItems: 3,
904
+ }),
905
+ })
906
+
907
+ const result = await readResource(
908
+ 'memory://github/status',
909
+ db,
910
+ undefined,
911
+ undefined,
912
+ github
913
+ )
914
+
915
+ const data = result.data as {
916
+ kanbanSummary: Record<string, number> | null
917
+ milestones: unknown[] | null
918
+ }
919
+
920
+ expect(data.kanbanSummary).toBeDefined()
921
+ expect(data.kanbanSummary!['Todo']).toBe(2)
922
+ expect(data.kanbanSummary!['Done']).toBe(1)
923
+ expect(data.milestones).toBeDefined()
924
+ })
925
+ })
473
926
  })