@gotza02/sequential-thinking 10000.2.1 → 10000.2.2

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.
@@ -10,47 +10,10 @@ import { getGlobalCache, CacheService } from '../core/cache.js';
10
10
  import { formatMatchesTable, formatScore, formatMatchStatus } from '../utils/formatter.js';
11
11
  import { CACHE_CONFIG, LEAGUES } from '../core/constants.js';
12
12
  import { logger } from '../../../utils.js';
13
- // ============= Helper Functions =============
14
- /**
15
- * Get date context for search queries
16
- */
17
- function getDateContext() {
18
- const now = new Date();
19
- const month = now.toLocaleDateString('en-US', { month: 'long' });
20
- const year = now.getFullYear();
21
- return ` ${month} ${year}`;
22
- }
23
- /**
24
- * Normalize team names for better matching
25
- */
26
- function normalizeTeamName(name) {
27
- return name
28
- .toLowerCase()
29
- .replace(/\b(fc|afc|sc|cf|united|utd|city|town|athletic|albion|rovers|wanderers|olympic|real)\b/g, '')
30
- .replace(/\s+/g, ' ')
31
- .trim();
32
- }
33
- /**
34
- * Check if a team name matches exactly or is contained within another name
35
- * Uses more strict matching to avoid false positives
36
- */
37
- function isTeamMatch(teamName, matchName) {
38
- const normalizedSearch = normalizeTeamName(teamName);
39
- const normalizedMatch = normalizeTeamName(matchName);
40
- // Exact match
41
- if (normalizedSearch === normalizedMatch)
42
- return true;
43
- // Check if search is contained as a word in match
44
- const searchWords = normalizedSearch.split(/\s+/).filter(w => w.length > 2);
45
- const matchWords = normalizedMatch.split(/\s+/).filter(w => w.length > 2);
46
- // Must have at least one significant word in common
47
- const commonWords = searchWords.filter(w => matchWords.includes(w));
48
- // For longer names, require more matching words
49
- if (searchWords.length >= 2) {
50
- return commonWords.length >= Math.min(2, searchWords.length);
51
- }
52
- return commonWords.length > 0;
53
- }
13
+ import { getDateContext, isTeamMatch, getAnalysisInstructions } from './match-helpers.js';
14
+ // Note: calculateAdaptiveTTL, calculateProbabilityRange, calculateFormPoints,
15
+ // calculateExpectedGoals, calculateProbabilitiesFromForm, and getLeagueId
16
+ // are defined locally in this file with specific implementations for match analysis
54
17
  /**
55
18
  * Try to find match ID from API by team names and league
56
19
  */
@@ -367,65 +330,6 @@ async function scrapeContentWithErrorHandling(url, type) {
367
330
  return `${type.charAt(0).toUpperCase() + type.slice(1)} scrape failed: ${errorMsg}`;
368
331
  }
369
332
  }
370
- /**
371
- * Get analysis framework instructions
372
- */
373
- function getAnalysisInstructions() {
374
- return `INSTRUCTIONS: Act as a World-Class Football Analysis Panel. Provide a deep, non-obvious analysis using this framework:\n\n` +
375
- `1. 📊 THE DATA SCIENTIST (Quantitative):\n - Analyze xG trends & Possession stats.\n - Assess Home/Away variance.\n\n` +
376
- `2. 🧠 THE TACTICAL SCOUT (Qualitative):\n - Stylistic Matchup (Press vs Block).\n - Key Battles.\n\n` +
377
- `3. 🚑 THE PHYSIO (Physical Condition):\n - FATIGUE CHECK: Days rest? Travel distance?\n - Squad Depth: Who has the better bench?\n\n` +
378
- `4. 🎯 SET PIECE ANALYST:\n - Corners/Free Kicks: Strong vs Weak?\n - Who is most likely to score from a header?\n\n` +
379
- `5. 💎 THE INSIDER (External Factors):\n - Market Movements (Odds dropping?).\n - Referee & Weather impact.\n\n` +
380
- `6. 🕵️ THE SKEPTIC & SCENARIOS:\n - Why might the favorite LOSE?\n - Game Script: "If Team A scores first..."\n\n` +
381
- `🏆 FINAL VERDICT:\n - Asian Handicap Leans\n - Goal Line (Over/Under)\n - The "Value Pick"`;
382
- }
383
- /**
384
- * Calculate adaptive TTL based on data type and match timing
385
- */
386
- function calculateAdaptiveTTL(dataType, matchDate) {
387
- const now = Date.now();
388
- const hoursUntilMatch = matchDate
389
- ? (matchDate.getTime() - now) / (1000 * 60 * 60)
390
- : Infinity;
391
- switch (dataType) {
392
- case 'odds':
393
- // Odds change frequently - short TTL
394
- if (hoursUntilMatch < 1)
395
- return 60 * 1000; // 1 minute
396
- if (hoursUntilMatch < 24)
397
- return 5 * 60 * 1000; // 5 minutes
398
- return 15 * 60 * 1000; // 15 minutes
399
- case 'lineups':
400
- // Lineups announced ~1 hour before match
401
- if (hoursUntilMatch < 2)
402
- return 5 * 60 * 1000; // 5 minutes
403
- return 60 * 60 * 1000; // 1 hour
404
- case 'news':
405
- // News changes throughout the day
406
- if (hoursUntilMatch < 24)
407
- return 15 * 60 * 1000; // 15 minutes
408
- return 60 * 60 * 1000; // 1 hour
409
- case 'stats':
410
- case 'h2h':
411
- // Historical stats don't change
412
- return 24 * 60 * 60 * 1000; // 24 hours
413
- case 'form':
414
- // Form updates after each match
415
- if (hoursUntilMatch < 0)
416
- return 60 * 1000; // Live match - 1 minute
417
- return 6 * 60 * 60 * 1000; // 6 hours
418
- case 'match':
419
- // Match data changes based on timing
420
- if (hoursUntilMatch < 0)
421
- return 60 * 1000; // Live - 1 minute
422
- if (hoursUntilMatch < 1)
423
- return 5 * 60 * 1000; // 5 minutes
424
- return 30 * 60 * 1000; // 30 minutes
425
- default:
426
- return 30 * 60 * 1000; // 30 minutes default
427
- }
428
- }
429
333
  /**
430
334
  * Perform comprehensive match analysis using web search
431
335
  */
@@ -487,28 +391,6 @@ async function scrapeDeepDiveSources(candidateUrls) {
487
391
  }
488
392
  return output;
489
393
  }
490
- /**
491
- * Calculate probability range with uncertainty quantification
492
- */
493
- function calculateProbabilityRange(baseProbability, dataQuality) {
494
- // Higher data quality = narrower range
495
- const qualityFactor = dataQuality.score / 100;
496
- const baseUncertainty = 0.15; // 15% base uncertainty
497
- // Adjust uncertainty based on data quality (better quality = smaller range)
498
- const adjustedUncertainty = baseUncertainty * (1 - qualityFactor * 0.7);
499
- // Calculate range using standard error formula
500
- // For binomial proportion: SE = sqrt(p*(1-p)/n)
501
- // We use a simplified version based on data quality
502
- const mid = baseProbability;
503
- // Margin decreases with better data quality
504
- // Using 1.96 for ~95% confidence interval (2 standard deviations)
505
- const margin = adjustedUncertainty * Math.sqrt(mid * (1 - mid) + 0.1);
506
- return {
507
- low: Math.max(0, mid - margin * 1.96),
508
- mid: Math.min(1, Math.max(0, mid)),
509
- high: Math.min(1, mid + margin * 1.96),
510
- };
511
- }
512
394
  /**
513
395
  * Detect value bets from predictions and odds
514
396
  */
@@ -1139,6 +1021,21 @@ Requires API provider with match ID.`, {
1139
1021
  });
1140
1022
  }
1141
1023
  // ============= Helper Functions =============
1024
+ /**
1025
+ * Calculate probability range with uncertainty quantification
1026
+ */
1027
+ function calculateProbabilityRange(baseProbability, dataQuality) {
1028
+ const qualityFactor = dataQuality.score / 100;
1029
+ const baseUncertainty = 0.15;
1030
+ const adjustedUncertainty = baseUncertainty * (1 - qualityFactor * 0.7);
1031
+ const mid = baseProbability;
1032
+ const margin = adjustedUncertainty * Math.sqrt(mid * (1 - mid) + 0.1);
1033
+ return {
1034
+ low: Math.max(0, mid - margin * 1.96),
1035
+ mid: Math.min(1, Math.max(0, mid)),
1036
+ high: Math.min(1, mid + margin * 1.96),
1037
+ };
1038
+ }
1142
1039
  /**
1143
1040
  * Get league ID from league name
1144
1041
  */
@@ -91,7 +91,7 @@ function registerLegacyAnalyzeMatch(server) {
91
91
  const data = await response.json();
92
92
  const items = data.web?.results || [];
93
93
  items.forEach((item) => candidateUrls.push(item.url));
94
- resultsText = items.map((item) => `- [${item.title}](${item.url}): ${item.description}`).join('\n');
94
+ resultsText = items.map((item) => `- [${item.title}](${item.url}): ${item.description ?? ''}`).join('\n');
95
95
  }
96
96
  else if (searchProvider === 'exa') {
97
97
  const response = await fetchWithRetry('https://api.exa.ai/search', {
@@ -107,7 +107,7 @@ function registerLegacyAnalyzeMatch(server) {
107
107
  const data = await response.json();
108
108
  const items = data.results || [];
109
109
  items.forEach((item) => candidateUrls.push(item.url));
110
- resultsText = items.map((item) => `- [${item.title}](${item.url}): ${item.text || item.title}`).join('\n');
110
+ resultsText = items.map((item) => `- [${item.title}](${item.url}): ${item.text ?? item.title}`).join('\n');
111
111
  }
112
112
  else if (searchProvider === 'google') {
113
113
  const response = await fetchWithRetry(`https://www.googleapis.com/customsearch/v1?key=${process.env.GOOGLE_SEARCH_API_KEY}&cx=${process.env.GOOGLE_SEARCH_CX}&q=${encodeURIComponent(q.query)}&num=4`);
@@ -116,7 +116,7 @@ function registerLegacyAnalyzeMatch(server) {
116
116
  const data = await response.json();
117
117
  const items = data.items || [];
118
118
  items.forEach((item) => candidateUrls.push(item.link));
119
- resultsText = items.map((item) => `- [${item.title}](${item.link}): ${item.snippet}`).join('\n');
119
+ resultsText = items.map((item) => `- [${item.title}](${item.link}): ${item.snippet ?? ''}`).join('\n');
120
120
  }
121
121
  return `### ${q.type}\n${resultsText}\n`;
122
122
  }
package/dist/utils.d.ts CHANGED
@@ -1,63 +1,130 @@
1
+ declare const CONFIG: {
2
+ readonly COMMAND_TIMEOUT: 60000;
3
+ readonly MAX_BUFFER: number;
4
+ readonly MAX_PATTERN_LENGTH: 500;
5
+ readonly MAX_QUANTIFIERS: 20;
6
+ readonly MAX_LOCKS: 1000;
7
+ readonly DNS_TIMEOUT: 5000;
8
+ readonly MAX_INPUT_LENGTH: 10000;
9
+ readonly RETRY: {
10
+ readonly MAX_RETRIES: 3;
11
+ readonly BASE_DELAY: 1000;
12
+ readonly BACKOFF_MULTIPLIER: 2;
13
+ };
14
+ };
15
+ /**
16
+ * Whitelist of safe commands. Consider loading from config file for flexibility.
17
+ */
18
+ declare const SAFE_COMMANDS: ReadonlySet<string>;
19
+ export declare class SecurityError extends Error {
20
+ readonly code: string;
21
+ constructor(message: string, code: string);
22
+ }
23
+ export declare class ValidationError extends Error {
24
+ readonly field?: string | undefined;
25
+ constructor(message: string, field?: string | undefined);
26
+ }
27
+ export declare class TimeoutError extends Error {
28
+ readonly timeout: number;
29
+ constructor(message: string, timeout: number);
30
+ }
31
+ interface ParsedCommand {
32
+ command: string;
33
+ args: string[];
34
+ }
1
35
  /**
2
- * Execute a shell command with security protections against command injection.
3
- * Uses spawn() instead of exec() to avoid shell interpretation.
4
- *
5
- * @param command - The command string to execute
6
- * @param options - Execution options (timeout, maxBuffer)
7
- * @returns Promise with stdout and stderr
8
- * @throws Error if command contains shell metacharacters or is not whitelisted
36
+ * Robust command parser supporting quotes and escapes.
37
+ * State machine implementation for correctness.
9
38
  */
10
- export declare function execAsync(command: string, options?: {
39
+ declare function parseCommand(command: string): ParsedCommand;
40
+ export interface ExecOptions {
11
41
  timeout?: number;
12
42
  maxBuffer?: number;
13
- }): Promise<{
43
+ cwd?: string;
44
+ env?: NodeJS.ProcessEnv;
45
+ }
46
+ export interface ExecResult {
14
47
  stdout: string;
15
48
  stderr: string;
16
- }>;
49
+ exitCode: number;
50
+ executionTime: number;
51
+ }
52
+ /**
53
+ * Execute command securely using spawn (no shell interpretation).
54
+ */
55
+ export declare function execAsync(command: string, options?: ExecOptions): Promise<ExecResult>;
17
56
  export declare class AsyncMutex {
18
- private mutex;
19
- lock(): Promise<() => void>;
20
- dispatch<T>(fn: (() => T | PromiseLike<T>)): Promise<T>;
57
+ private queue;
58
+ private locked;
59
+ acquire(): Promise<() => void>;
60
+ private release;
61
+ dispatch<T>(fn: () => T | PromiseLike<T>): Promise<T>;
62
+ }
63
+ export declare class FileLock {
64
+ private locks;
65
+ private queue;
66
+ private readonly maxLocks;
67
+ constructor(maxLocks?: 1000);
68
+ acquire(filePath: string, timeout?: number): Promise<{
69
+ release: () => void;
70
+ }>;
71
+ private waitForLock;
72
+ private release;
73
+ private cleanupStaleLocks;
74
+ getLockCount(): number;
75
+ getQueueCount(): number;
21
76
  }
77
+ export declare const fileLock: FileLock;
22
78
  export declare function validatePath(requestedPath: string): string;
79
+ declare function isPrivateIPv4(ip: string): boolean;
80
+ declare function isPrivateIPv6(ip: string): boolean;
23
81
  export declare function validatePublicUrl(urlString: string): Promise<void>;
24
82
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
83
+ interface LoggerOptions {
84
+ level?: LogLevel;
85
+ useColors?: boolean;
86
+ output?: 'stdout' | 'stderr';
87
+ }
25
88
  declare class Logger {
26
89
  private level;
27
- constructor();
90
+ private useColors;
91
+ private output;
92
+ private static readonly LEVELS;
93
+ private static readonly COLORS;
94
+ constructor(options?: LoggerOptions);
28
95
  private shouldLog;
29
- debug(msg: string, ...args: any[]): void;
30
- info(msg: string, ...args: any[]): void;
31
- warn(msg: string, ...args: any[]): void;
32
- error(msg: string, ...args: any[]): void;
96
+ private formatMessage;
97
+ private print;
98
+ debug(msg: string, ...args: unknown[]): void;
99
+ info(msg: string, ...args: unknown[]): void;
100
+ warn(msg: string, ...args: unknown[]): void;
101
+ error(msg: string, ...args: unknown[]): void;
102
+ setLevel(level: LogLevel): void;
33
103
  }
34
104
  export declare const logger: Logger;
35
- /**
36
- * Validate a regex pattern for ReDoS vulnerabilities
37
- * @throws Error if pattern is unsafe
38
- */
105
+ export declare class ReDoSError extends SecurityError {
106
+ constructor(message: string);
107
+ }
39
108
  export declare function validateRegexPattern(pattern: string): void;
40
- /**
41
- * Create a RegExp object with ReDoS protection
42
- * @param pattern - The regex pattern
43
- * @param flags - Regex flags (g, i, m, etc.)
44
- * @returns Safe RegExp object
45
- * @throws Error if pattern is unsafe
46
- */
47
109
  export declare function createSafeRegex(pattern: string, flags?: string): RegExp;
48
- export declare const DEFAULT_HEADERS: {
49
- 'User-Agent': string;
50
- Accept: string;
51
- 'Accept-Language': string;
52
- };
53
- export declare function fetchWithRetry(url: string, options?: any, retries?: number, backoff?: number): Promise<Response>;
54
- export declare function withRetry(fn: () => Promise<any>, maxRetries?: number, baseDelay?: number): Promise<any>;
55
- export declare class FileLock {
56
- locks: Map<any, any>;
57
- acquire(filePath: string, timeout?: number): Promise<{
58
- release: () => void;
59
- }>;
110
+ export declare const DEFAULT_HEADERS: Readonly<Record<string, string>>;
111
+ export interface FetchOptions extends RequestInit {
112
+ retries?: number;
113
+ backoff?: number;
114
+ validateUrl?: boolean;
60
115
  }
61
- export declare const fileLock: FileLock;
62
- export declare function sanitizeInput(input: any, maxLength?: number): string;
63
- export {};
116
+ export declare function fetchWithRetry(url: string, options?: FetchOptions): Promise<Response>;
117
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: {
118
+ maxRetries?: number;
119
+ baseDelay?: number;
120
+ onRetry?: (error: Error, attempt: number) => void;
121
+ shouldRetry?: (error: Error) => boolean;
122
+ }): Promise<T>;
123
+ export interface SanitizeOptions {
124
+ maxLength?: number;
125
+ allowedPattern?: RegExp;
126
+ removeNull?: boolean;
127
+ trim?: boolean;
128
+ }
129
+ export declare function sanitizeInput(input: unknown, options?: SanitizeOptions): string;
130
+ export { CONFIG, SAFE_COMMANDS, parseCommand, isPrivateIPv4, isPrivateIPv6, Logger, };