@gotza02/sequential-thinking 10000.0.2 → 10000.0.4

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.
@@ -3,4 +3,8 @@
3
3
  * Tools for match analysis and live scores
4
4
  */
5
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ /**
7
+ * Perform comprehensive match analysis using web search
8
+ */
9
+ export declare function performMatchAnalysis(homeTeam: string, awayTeam: string, league?: string, context?: string): Promise<string>;
6
10
  export declare function registerMatchTools(server: McpServer): void;
@@ -65,7 +65,7 @@ async function findMatchId(homeTeam, awayTeam, league) {
65
65
  /**
66
66
  * Perform comprehensive match analysis using web search
67
67
  */
68
- async function performMatchAnalysis(homeTeam, awayTeam, league, context) {
68
+ export async function performMatchAnalysis(homeTeam, awayTeam, league, context) {
69
69
  const leagueStr = league ? `${league} ` : '';
70
70
  const baseQuery = `${leagueStr}${homeTeam} vs ${awayTeam}`;
71
71
  const dateQuery = getDateContext();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { performMatchAnalysis } from './match.js';
3
+ // Mock dependencies
4
+ const mockSearchProvider = {
5
+ search: vi.fn(),
6
+ };
7
+ vi.mock('../providers/search.js', () => ({
8
+ getSearchProvider: () => mockSearchProvider,
9
+ SearchProvider: class {
10
+ },
11
+ }));
12
+ vi.mock('../providers/scraper.js', () => ({
13
+ scrapeMatchContent: vi.fn(),
14
+ findBestMatchUrl: vi.fn(),
15
+ findBestNewsUrl: vi.fn(),
16
+ }));
17
+ vi.mock('../providers/api.js', () => ({
18
+ createAPIProvider: () => ({
19
+ searchTeams: vi.fn().mockResolvedValue({ success: false }),
20
+ getLiveMatches: vi.fn().mockResolvedValue({ success: false }),
21
+ getStatus: () => ({ available: false })
22
+ })
23
+ }));
24
+ // Import mocked modules to setup return values
25
+ import { scrapeMatchContent, findBestMatchUrl, findBestNewsUrl } from '../providers/scraper.js';
26
+ describe('Match Analysis Tool', () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ // Default mock behaviors
30
+ mockSearchProvider.search.mockResolvedValue([
31
+ { title: 'Result 1', url: 'https://example.com/1', snippet: 'Snippet 1' }
32
+ ]);
33
+ scrapeMatchContent.mockResolvedValue({ success: true, data: 'Scraped Content' });
34
+ findBestMatchUrl.mockReturnValue(null);
35
+ findBestNewsUrl.mockReturnValue(null);
36
+ });
37
+ it('should perform basic analysis with search results', async () => {
38
+ const result = await performMatchAnalysis('Arsenal', 'Chelsea');
39
+ expect(mockSearchProvider.search).toHaveBeenCalled();
40
+ expect(result).toContain('Arsenal vs Chelsea');
41
+ expect(result).toContain('Result 1');
42
+ expect(result).toContain('INSTRUCTIONS: Act as a World-Class Football Analysis Panel');
43
+ });
44
+ it('should scrape stats URL when found', async () => {
45
+ findBestMatchUrl.mockReturnValue('https://understat.com/match/123');
46
+ const result = await performMatchAnalysis('Arsenal', 'Chelsea');
47
+ expect(findBestMatchUrl).toHaveBeenCalled();
48
+ expect(scrapeMatchContent).toHaveBeenCalledWith('https://understat.com/match/123');
49
+ expect(result).toContain('DEEP DIVE ANALYSIS: STATS & DATA');
50
+ expect(result).toContain('Scraped Content');
51
+ });
52
+ it('should perform dual-source scraping (Stats + News)', async () => {
53
+ // Setup distinct URLs for stats and news
54
+ findBestMatchUrl.mockReturnValue('https://understat.com/match/123');
55
+ findBestNewsUrl.mockReturnValue('https://bbc.com/sport/football/456');
56
+ // Mock scraping to return different content based on URL
57
+ scrapeMatchContent.mockImplementation(async (url) => {
58
+ if (url.includes('understat'))
59
+ return { success: true, data: 'STATS_DATA_HERE' };
60
+ if (url.includes('bbc'))
61
+ return { success: true, data: 'NEWS_DATA_HERE' };
62
+ return { success: false };
63
+ });
64
+ const result = await performMatchAnalysis('Arsenal', 'Chelsea');
65
+ expect(scrapeMatchContent).toHaveBeenCalledTimes(2);
66
+ expect(result).toContain('DEEP DIVE ANALYSIS: STATS & DATA');
67
+ expect(result).toContain('STATS_DATA_HERE');
68
+ expect(result).toContain('DEEP DIVE ANALYSIS: NEWS & LINEUPS');
69
+ expect(result).toContain('NEWS_DATA_HERE');
70
+ });
71
+ it('should not scrape news if it is the same as stats URL', async () => {
72
+ const url = 'https://example.com/match';
73
+ findBestMatchUrl.mockReturnValue(url);
74
+ findBestNewsUrl.mockReturnValue(url); // Same URL
75
+ await performMatchAnalysis('Arsenal', 'Chelsea');
76
+ expect(scrapeMatchContent).toHaveBeenCalledTimes(1); // Should only scrape once
77
+ });
78
+ it('should handle scraper failures gracefully', async () => {
79
+ findBestMatchUrl.mockReturnValue('https://bad-url.com');
80
+ scrapeMatchContent.mockResolvedValue({ success: false, error: 'Timeout' });
81
+ const result = await performMatchAnalysis('Arsenal', 'Chelsea');
82
+ expect(result).toContain('Stats scrape failed: Timeout');
83
+ // Should still contain the main analysis structure
84
+ expect(result).toContain('INSTRUCTIONS: Act as a World-Class Football Analysis Panel');
85
+ });
86
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotza02/sequential-thinking",
3
- "version": "10000.0.2",
3
+ "version": "10000.0.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -44,7 +44,8 @@
44
44
  "jsdom": "^27.4.0",
45
45
  "turndown": "^7.2.2",
46
46
  "typescript": "^5.9.3",
47
- "yargs": "^18.0.0"
47
+ "yargs": "^18.0.0",
48
+ "zod": "^3.22.4"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/cors": "^2.8.19",