memory-journal-mcp 4.4.2 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/security-update.yml +1 -1
- package/CHANGELOG.md +81 -1
- package/DOCKER_README.md +57 -7
- package/Dockerfile +17 -17
- package/README.md +65 -6
- package/SECURITY.md +27 -35
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +137 -83
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +2 -1
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +15 -8
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/handlers/resources/index.d.ts +3 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +5 -2
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +63 -16
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/server/McpServer.d.ts +2 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +43 -2
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/package.json +8 -8
- package/releases/v4.5.0.md +116 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +26 -0
- package/src/constants/ServerInstructions.ts +137 -83
- package/src/constants/server-instructions.md +262 -0
- package/src/database/SqliteAdapter.ts +22 -8
- package/src/handlers/resources/index.ts +8 -2
- package/src/handlers/tools/index.ts +70 -20
- package/src/server/McpServer.ts +60 -2
- package/src/server/Scheduler.ts +278 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/tests/constants/server-instructions.test.ts +26 -0
- package/tests/database/sqlite-adapter.test.ts +84 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handlers.test.ts +32 -0
- package/tests/handlers/tool-handlers.test.ts +13 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +491 -5
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/.vscode/settings.json +0 -84
|
@@ -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
|
})
|