@gotza02/sequential-thinking 10000.2.0 → 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.
Files changed (35) hide show
  1. package/README.md.bak +197 -0
  2. package/dist/.gemini_graph_cache.json.bak +1641 -0
  3. package/dist/graph.d.ts +7 -0
  4. package/dist/graph.js +136 -113
  5. package/dist/intelligent-code.d.ts +8 -0
  6. package/dist/intelligent-code.js +152 -125
  7. package/dist/intelligent-code.test.d.ts +1 -0
  8. package/dist/intelligent-code.test.js +104 -0
  9. package/dist/lib/formatters.d.ts +9 -0
  10. package/dist/lib/formatters.js +119 -0
  11. package/dist/lib/validators.d.ts +45 -0
  12. package/dist/lib/validators.js +232 -0
  13. package/dist/lib.js +23 -265
  14. package/dist/tools/sports/core/base.d.ts +3 -2
  15. package/dist/tools/sports/core/base.js +12 -10
  16. package/dist/tools/sports/core/cache.d.ts +9 -0
  17. package/dist/tools/sports/core/cache.js +25 -3
  18. package/dist/tools/sports/core/types.d.ts +6 -2
  19. package/dist/tools/sports/providers/api.d.ts +4 -0
  20. package/dist/tools/sports/providers/api.js +110 -27
  21. package/dist/tools/sports/tools/betting.js +16 -16
  22. package/dist/tools/sports/tools/league.d.ts +2 -7
  23. package/dist/tools/sports/tools/league.js +198 -8
  24. package/dist/tools/sports/tools/live.js +80 -38
  25. package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
  26. package/dist/tools/sports/tools/match-calculations.js +171 -0
  27. package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
  28. package/dist/tools/sports/tools/match-helpers.js +57 -0
  29. package/dist/tools/sports/tools/match.js +227 -125
  30. package/dist/tools/sports.js +3 -3
  31. package/dist/utils.d.ts +111 -44
  32. package/dist/utils.js +510 -305
  33. package/dist/utils.test.js +3 -3
  34. package/package.json +1 -1
  35. package/CLAUDE.md +0 -231
package/dist/lib.js CHANGED
@@ -1,10 +1,11 @@
1
- import chalk from 'chalk';
2
1
  import * as fs from 'fs/promises';
3
2
  import { existsSync, readFileSync } from 'fs';
4
3
  import * as path from 'path';
5
4
  import { AsyncMutex } from './utils.js';
6
5
  import { ContextManager } from './core/ContextManager.js';
7
6
  import { RuleManager } from './core/RuleManager.js';
7
+ import { formatThought, generateMermaid } from './lib/formatters.js';
8
+ import { detectLoopsAndStalling, validateSolution, checkAntiInsanity, checkConfidenceCritical, calculateConfidenceScore } from './lib/validators.js';
8
9
  export class SequentialThinkingServer {
9
10
  thoughtHistory = [];
10
11
  blocks = [];
@@ -390,79 +391,7 @@ export class SequentialThinkingServer {
390
391
  }
391
392
  }
392
393
  formatThought(thoughtData) {
393
- const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption, blockId, relatedToolCall, complexity } = thoughtData;
394
- const typeEmoji = {
395
- 'analysis': 'šŸ”',
396
- 'planning': 'šŸ“‹',
397
- 'critique': '😈',
398
- 'execution': '⚔',
399
- 'observation': 'šŸ‘ļø',
400
- 'hypothesis': 'šŸ’”',
401
- 'reflexion': 'šŸ”„',
402
- 'solution': 'āœ…',
403
- 'generation': 'šŸ’­',
404
- 'evaluation': 'āš–ļø',
405
- 'selection': 'šŸŽÆ'
406
- };
407
- let prefix = '';
408
- let context = '';
409
- const type = thoughtType || 'analysis';
410
- const emoji = typeEmoji[type] || 'šŸ’­';
411
- if (type === 'reflexion' || isRevision) {
412
- prefix = chalk.yellow(`${emoji} Reflexion`);
413
- if (revisesThought)
414
- context += ` (revising #${revisesThought})`;
415
- }
416
- else if (type === 'critique') {
417
- prefix = chalk.redBright(`${emoji} Critique`);
418
- }
419
- else if (type === 'execution') {
420
- prefix = chalk.magenta(`${emoji} Execution`);
421
- if (relatedToolCall)
422
- context += ` [Tool: ${relatedToolCall}]`;
423
- }
424
- else if (type === 'observation') {
425
- prefix = chalk.cyan(`${emoji} Observation`);
426
- }
427
- else if (type === 'planning') {
428
- prefix = chalk.blue(`${emoji} Planning`);
429
- }
430
- else if (type === 'solution') {
431
- prefix = chalk.green(`${emoji} Solution`);
432
- }
433
- else if (branchFromThought) {
434
- prefix = chalk.green(`🌿 Branch`);
435
- context = ` (from #${branchFromThought}, ID: ${branchId})`;
436
- }
437
- else {
438
- prefix = chalk.blue(`${emoji} ${type.charAt(0).toUpperCase() + type.slice(1)}`);
439
- }
440
- if (blockId && blockId !== 'default' && blockId !== 'legacy-default') {
441
- context += ` [Block: ${blockId}]`;
442
- }
443
- if (complexity)
444
- context += ` [${complexity.toUpperCase()}]`;
445
- if (score)
446
- context += ` (Score: ${score})`;
447
- if (selectedOption)
448
- context += ` (Selected: ${selectedOption})`;
449
- const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
450
- const borderLength = Math.max(header.length, Math.min(thought.length, 80)) + 4;
451
- const border = '─'.repeat(borderLength);
452
- let extraContent = '';
453
- if (options && options.length > 0) {
454
- extraContent += `\n│ Options:\n` + options.map(o => `│ - ${o}`).join('\n');
455
- }
456
- // Wrap long thoughts
457
- const wrappedThought = thought.length > 76
458
- ? thought.match(/.{1,76}/g)?.map(line => `│ ${line.padEnd(borderLength - 2)} │`).join('\n') || thought
459
- : `│ ${thought.padEnd(borderLength - 2)} │`;
460
- return `
461
- ā”Œ${border}┐
462
- │ ${header.padEnd(borderLength - 2)} │
463
- ā”œ${border}┤
464
- ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrappedThought : `│ ${thought.padEnd(borderLength - 2)} │`}${extraContent}
465
- ā””${border}ā”˜`;
394
+ return formatThought(thoughtData);
466
395
  }
467
396
  // --- Extracted Helper Methods for processThought ---
468
397
  /**
@@ -515,102 +444,24 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
515
444
  * Detect loops, stalling, and other thinking pattern issues
516
445
  */
517
446
  detectLoopsAndStalling(input, blockThoughts, recentInBlock, lastThought, complexity) {
518
- const warnings = [];
519
- let isStallingOrLooping = false;
520
- const thinkingTypes = ['analysis', 'planning', 'hypothesis', 'generation', 'critique'];
521
- // Rule 1: Stalling Detection (Adaptive)
522
- if (complexity !== 'low') {
523
- if (recentInBlock.length >= SequentialThinkingServer.STALLING_THRESHOLD &&
524
- recentInBlock.every(t => thinkingTypes.includes(t.thoughtType || 'analysis')) &&
525
- thinkingTypes.includes(input.thoughtType || 'analysis')) {
526
- warnings.push(`āš ļø STALLING DETECTED: You have been thinking for 4 consecutive steps without execution. Consider changing thoughtType to 'execution' and calling a tool to make progress.`);
527
- isStallingOrLooping = true;
528
- }
529
- }
530
- // Rule 2: Repeating Action Detection
531
- if (input.thoughtType === 'execution') {
532
- // Check Immune System Rules
533
- const ruleWarnings = this.ruleManager.checkRules(input.thought);
534
- ruleWarnings.forEach(w => warnings.push(w));
535
- if (recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
536
- warnings.push(`šŸ›‘ LOOP DETECTED: You are attempting to execute the exact same action again. You MUST change your strategy or create a branch with a different approach.`);
537
- isStallingOrLooping = true;
538
- }
539
- }
540
- // Rule 3: Missing Observation
541
- if (lastThought &&
542
- lastThought.thoughtType === 'execution' &&
543
- input.thoughtType !== 'observation' &&
544
- !input.branchFromThought) {
545
- warnings.push(`šŸ’” INTERLEAVED HINT: Your last step was 'execution'. The next step SHOULD be 'observation' to record and analyze the tool result before continuing.`);
546
- }
547
- // Rule 4: Skipping Planning (Adaptive)
548
- if (complexity !== 'low') {
549
- const hasPlanning = blockThoughts.some(t => t.thoughtType === 'planning');
550
- if (!hasPlanning &&
551
- blockThoughts.length >= 2 &&
552
- input.thoughtType === 'execution') {
553
- warnings.push(`šŸ“‹ PLANNING HINT: You're about to execute without a planning step. Consider adding a 'planning' thought first to outline your approach.`);
554
- }
555
- }
556
- // Rule 5: Devil's Advocate (Critique Phase)
557
- if (complexity === 'high' && input.thoughtType === 'execution') {
558
- const hasCritique = blockThoughts.some(t => t.thoughtType === 'critique');
559
- if (!hasCritique) {
560
- warnings.push(`šŸ›‘ CRITIQUE REQUIRED: High complexity task requires a 'critique' step before execution. Analyze risks and flaws in your plan first.`);
561
- }
562
- }
563
- // Rule 6: Ending without Verification
564
- if (input.nextThoughtNeeded === false) {
565
- const hasVerification = this.thoughtHistory.some(t => t.thought.toLowerCase().includes('test') ||
566
- t.thought.toLowerCase().includes('verify') ||
567
- t.thought.toLowerCase().includes('check') ||
568
- t.thoughtType === 'observation');
569
- if (!hasVerification) {
570
- warnings.push(`🚨 CRITICAL: You are ending without explicit verification/testing. Consider adding an 'observation' or verification step.`);
571
- }
572
- }
573
- // Rule 7: Same type loop
574
- if (complexity !== 'low' && recentInBlock.length >= SequentialThinkingServer.STALLING_THRESHOLD &&
575
- recentInBlock.every(t => t.thoughtType === input.thoughtType)) {
576
- warnings.push(`āš ļø TYPE LOOP: You've used '${input.thoughtType}' for 4 consecutive steps. Consider using a different thought type to progress.`);
577
- }
578
- // Rule 8: Struggle Detection
579
- const executionCount = blockThoughts.filter(t => t.thoughtType === 'execution').length;
580
- if (executionCount >= SequentialThinkingServer.EXECUTION_STRUGGLE_THRESHOLD && input.thoughtType === 'execution') {
581
- warnings.push(`šŸ›‘ STRUGGLE DETECTED: You have executed ${SequentialThinkingServer.EXECUTION_STRUGGLE_THRESHOLD}+ commands in this block without reaching a solution. If you are fixing a bug and it's not working, STOP. Do not try a 4th time linearly. Use 'branchFromThought' to try a completely different approach.`);
582
- }
583
- // Rule 9: Premature Solution Detection
584
- const hasObservation = blockThoughts.some(t => t.thoughtType === 'observation');
585
- if (input.thoughtType === 'solution' && !hasObservation && blockThoughts.length < 4 && complexity !== 'low') {
586
- warnings.push(`šŸ¤” PREMATURE SOLUTION: You are concluding this block very quickly without any 'observation'. Are you hallucinating? Please verify with a tool first.`);
587
- }
588
- return { isStallingOrLooping, warnings };
447
+ return detectLoopsAndStalling(input, {
448
+ thoughtHistory: this.thoughtHistory,
449
+ blockThoughts,
450
+ recentInBlock,
451
+ lastThought,
452
+ complexity
453
+ }, (thought) => this.ruleManager.checkRules(thought));
589
454
  }
590
455
  /**
591
456
  * Validate solution before accepting
592
457
  */
593
458
  async validateSolution(input, blockThoughts, complexity, warnings) {
594
- const observations = blockThoughts.filter(t => t.thoughtType === 'observation');
595
- if (observations.length === 0) {
596
- warnings.push(`🚫 UNVERIFIED SOLUTION: You are attempting to solve without ANY observation/tool output in this block.`);
597
- }
598
- else {
599
- const lastObservation = observations[observations.length - 1];
600
- const failureKeywords = ['error', 'fail', 'exception', 'undefined', 'not found', 'enoent'];
601
- const hasFailure = failureKeywords.some(kw => lastObservation.toolResult?.toLowerCase().includes(kw));
602
- if (hasFailure) {
603
- warnings.push(`āš ļø FAILURE DETECTED: The last observation contains failure signals ("${lastObservation.toolResult?.substring(0, 50)}..."). Fix the error first.`);
604
- }
605
- // Smart Search Verification
606
- const searchTools = ['web_search', 'google_web_search', 'read_webpage', 'search_file_content', 'google_search'];
607
- const isSearch = lastObservation.relatedToolCall && searchTools.some(tool => lastObservation.relatedToolCall.includes(tool));
608
- if (isSearch) {
609
- warnings.push(`šŸ•µļø DATA INTEGRITY CHECK: You are concluding a search task. Verify: Did you actually find the specific answer?`);
610
- }
611
- // Double Verification for High Complexity
612
- if (complexity === 'high' && observations.length < 2) {
613
- warnings.push(`āš–ļø DOUBLE VERIFICATION REQUIRED: High complexity tasks require at least 2 distinct observations/verifications before conclusion.`);
459
+ const validationWarnings = validateSolution(input, blockThoughts, complexity);
460
+ warnings.push(...validationWarnings);
461
+ // Handle high complexity double verification penalty
462
+ if (complexity === 'high') {
463
+ const observations = blockThoughts.filter(t => t.thoughtType === 'observation');
464
+ if (observations.length < 2 && validationWarnings.some(w => w.includes('DOUBLE VERIFICATION'))) {
614
465
  this.confidenceScore -= 10;
615
466
  }
616
467
  }
@@ -628,23 +479,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
628
479
  * Update confidence score based on thought type and warnings
629
480
  */
630
481
  updateConfidenceScore(input, warnings) {
631
- if (warnings.length > 0) {
632
- this.confidenceScore = Math.max(0, this.confidenceScore - (5 * warnings.length));
633
- }
634
- if (input.thoughtType === 'reflexion') {
635
- this.confidenceScore = Math.min(100, this.confidenceScore + 10);
636
- }
637
- else if (input.thoughtType === 'observation') {
638
- const isError = input.toolResult?.toLowerCase().includes('error');
639
- if (isError)
640
- this.confidenceScore -= 10;
641
- else
642
- this.confidenceScore = Math.min(100, this.confidenceScore + 10);
643
- }
644
- else if (input.thoughtType === 'solution' && warnings.length === 0) {
645
- // Bonus for clean solution
646
- this.confidenceScore = Math.min(100, this.confidenceScore + 20);
647
- }
482
+ this.confidenceScore = calculateConfidenceScore(this.confidenceScore, input, warnings.length);
648
483
  }
649
484
  // --- Helper Methods for processThought ---
650
485
  /**
@@ -680,27 +515,10 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
680
515
  * Check anti-insanity - prevent repeating failed executions
681
516
  */
682
517
  checkAntiInsanity(input, historyForCheck) {
683
- const warnings = [];
684
- if (input.thoughtType === 'execution') {
685
- const failedExecutions = historyForCheck.filter(t => {
686
- if (t.thoughtType !== 'execution')
687
- return false;
688
- // Find the observation immediately following this execution
689
- const nextThought = this.thoughtHistory.find(h => h.thoughtNumber === t.thoughtNumber + 1);
690
- if (nextThought && nextThought.thoughtType === 'observation') {
691
- const result = nextThought.toolResult?.toLowerCase() || "";
692
- // Check for failure signals
693
- return result.includes("error") || result.includes("fail") ||
694
- result.includes("exception") || result.includes("enoent");
695
- }
696
- return false;
697
- });
698
- const isRepeatedFailure = failedExecutions.some(t => t.thought.trim() === input.thought.trim());
699
- if (isRepeatedFailure) {
700
- const failedItem = failedExecutions.find(t => t.thought.trim() === input.thought.trim());
701
- warnings.push(`ā›” INSANITY CHECK: You are trying to run a command that ALREADY FAILED in thought #${failedItem?.thoughtNumber}. STOP. Do not repeat mistakes. Try a different parameter or tool.`);
702
- this.confidenceScore -= 15;
703
- }
518
+ const warnings = checkAntiInsanity(input, historyForCheck, this.thoughtHistory);
519
+ // Apply confidence penalty for repeated failures
520
+ if (warnings.some(w => w.includes('INSANITY CHECK'))) {
521
+ this.confidenceScore -= 15;
704
522
  }
705
523
  return warnings;
706
524
  }
@@ -708,36 +526,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
708
526
  * Check if confidence is critical and return critical stop response if needed
709
527
  */
710
528
  checkConfidenceCritical(input, historyForCheck) {
711
- if (this.confidenceScore >= SequentialThinkingServer.CONFIDENCE_CRITICAL_THRESHOLD ||
712
- input.thoughtType === 'reflexion' ||
713
- input.thoughtType === 'analysis') {
714
- return null;
715
- }
716
- // Smart Branching: Find the last valid planning point
717
- const lastGoodPlan = historyForCheck
718
- .slice()
719
- .reverse()
720
- .find(t => t.thoughtType === 'planning' || t.thoughtType === 'hypothesis');
721
- const recoveryAdvice = lastGoodPlan
722
- ? `RECOMMENDATION: Branch from thought #${lastGoodPlan.thoughtNumber} to try a new approach.`
723
- : `RECOMMENDATION: Use 'reflexion' to analyze why current attempts are failing.`;
724
- return {
725
- content: [{
726
- type: "text",
727
- text: JSON.stringify({
728
- status: "CRITICAL_STOP",
729
- feedback: [
730
- "🚨 CONFIDENCE CRITICAL (<50). Execution blocked to prevent further damage.",
731
- "šŸ›‘ STOP: You are likely in a loop or making repeated errors.",
732
- "šŸ‘‰ REQUIRED ACTION: You must switch thoughtType to 'reflexion' to critique your errors.",
733
- "āš ļø REMEMBER: You MUST provide a 'thought' string explaining your reflection. Do not leave it empty.",
734
- recoveryAdvice
735
- ],
736
- confidenceScore: this.confidenceScore
737
- }, null, 2)
738
- }],
739
- isError: true
740
- };
529
+ return checkConfidenceCritical(this.confidenceScore, input, historyForCheck);
741
530
  }
742
531
  // --- 2. The New Brain: Process Thought with Interleaved Logic (Opus 4.5 Style) ---
743
532
  async processThought(input) {
@@ -847,38 +636,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
847
636
  return 'THINKING';
848
637
  }
849
638
  generateMermaid() {
850
- let diagram = 'graph TD\n';
851
- const recentThoughts = this.thoughtHistory.slice(-15); // Last 15 for readability
852
- recentThoughts.forEach((t, i) => {
853
- const id = `T${t.thoughtNumber}`;
854
- const typeIcon = {
855
- 'analysis': 'šŸ”',
856
- 'planning': 'šŸ“‹',
857
- 'critique': '😈',
858
- 'execution': '⚔',
859
- 'observation': 'šŸ‘ļø',
860
- 'hypothesis': 'šŸ’”',
861
- 'reflexion': 'šŸ”„',
862
- 'solution': 'āœ…',
863
- 'generation': 'šŸ’­',
864
- 'evaluation': 'āš–ļø',
865
- 'selection': 'šŸŽÆ'
866
- }[t.thoughtType || 'analysis'] || 'šŸ’­';
867
- const label = `${typeIcon} ${t.thoughtNumber}`;
868
- diagram += ` ${id}("${label}")\n`;
869
- if (i > 0) {
870
- const prevT = recentThoughts[i - 1];
871
- const prevId = `T${prevT.thoughtNumber}`;
872
- if (t.branchFromThought) {
873
- diagram += ` T${t.branchFromThought} -->|branch: ${t.branchId}| ${id}\n`;
874
- }
875
- else {
876
- const edgeLabel = t.thoughtType === 'observation' ? '|result|' : '';
877
- diagram += ` ${prevId} -->${edgeLabel} ${id}\n`;
878
- }
879
- }
880
- });
881
- return diagram;
639
+ return generateMermaid(this.thoughtHistory);
882
640
  }
883
641
  // Public method to start a new block explicitly
884
642
  startNewBlock(blockId, topic) {
@@ -73,7 +73,8 @@ export declare abstract class APIProviderBase implements IDataProvider {
73
73
  protected rateLimitUntil: Date | null;
74
74
  protected lastCall: Date | null;
75
75
  protected callCount: number;
76
- constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number);
76
+ constructor(type: ProviderType, apiKey: string, baseUrl: string, rateLimit?: number, // calls per minute
77
+ cache?: CacheService);
77
78
  /**
78
79
  * Check if provider has valid credentials
79
80
  */
@@ -131,7 +132,7 @@ export declare abstract class APIProviderBase implements IDataProvider {
131
132
  export declare abstract class ScraperProviderBase implements IDataProvider {
132
133
  protected cache: CacheService;
133
134
  readonly type: ProviderType;
134
- constructor();
135
+ constructor(cache?: CacheService);
135
136
  isAvailable(): boolean;
136
137
  getStatus(): ProviderStatus;
137
138
  abstract getMatch(matchId: string): Promise<APIResponse<Match>>;
@@ -2,7 +2,7 @@
2
2
  * SPORTS MODULE BASE CLASSES
3
3
  * Abstract base classes and interfaces for data providers
4
4
  */
5
- import { CacheService } from './cache.js';
5
+ import { CacheService, getGlobalCache } from './cache.js';
6
6
  import { logger } from '../../../utils.js';
7
7
  /**
8
8
  * Abstract base class for API-based providers
@@ -16,13 +16,13 @@ export class APIProviderBase {
16
16
  rateLimitUntil = null;
17
17
  lastCall = null;
18
18
  callCount = 0;
19
- constructor(type, apiKey, baseUrl, rateLimit = 10 // calls per minute
20
- ) {
19
+ constructor(type, apiKey, baseUrl, rateLimit = 10, // calls per minute
20
+ cache) {
21
21
  this.type = type;
22
22
  this.apiKey = apiKey;
23
23
  this.baseUrl = baseUrl;
24
24
  this.rateLimit = rateLimit;
25
- this.cache = new CacheService();
25
+ this.cache = cache || getGlobalCache();
26
26
  }
27
27
  /**
28
28
  * Check if provider has valid credentials
@@ -86,7 +86,9 @@ export class APIProviderBase {
86
86
  // Handle rate limiting
87
87
  if (response.status === 429) {
88
88
  const retryAfter = response.headers.get('Retry-After');
89
- this.rateLimitUntil = new Date(Date.now() + (retryAfter ? parseInt(retryAfter) * 1000 : 60000));
89
+ const retrySeconds = retryAfter ? parseInt(retryAfter, 10) : NaN;
90
+ const delayMs = isNaN(retrySeconds) ? 60000 : retrySeconds * 1000;
91
+ this.rateLimitUntil = new Date(Date.now() + delayMs);
90
92
  return {
91
93
  success: false,
92
94
  error: 'Rate limited',
@@ -127,8 +129,8 @@ export class APIProviderBase {
127
129
  export class ScraperProviderBase {
128
130
  cache;
129
131
  type = 'scraper';
130
- constructor() {
131
- this.cache = new CacheService();
132
+ constructor(cache) {
133
+ this.cache = cache || getGlobalCache();
132
134
  }
133
135
  isAvailable() {
134
136
  return true; // Scraping is always "available"
@@ -181,8 +183,8 @@ export class ScraperProviderBase {
181
183
  export class FallbackProvider {
182
184
  providers;
183
185
  cache;
184
- type = 'scraper';
185
- constructor(providers, cache = new CacheService()) {
186
+ type = 'api-football'; // Most common API type
187
+ constructor(providers, cache = getGlobalCache()) {
186
188
  this.providers = providers;
187
189
  this.cache = cache;
188
190
  }
@@ -275,7 +277,7 @@ export class FallbackProvider {
275
277
  export class SportsToolBase {
276
278
  cache;
277
279
  constructor(cache) {
278
- this.cache = cache || new CacheService();
280
+ this.cache = cache || getGlobalCache();
279
281
  }
280
282
  /**
281
283
  * Format error message for user
@@ -12,7 +12,12 @@ export declare class CacheService {
12
12
  private maxSize;
13
13
  private savePending;
14
14
  private saveTimer;
15
+ cleanupInterval: NodeJS.Timeout | null;
15
16
  constructor(cachePath?: string, maxAge?: number, maxSize?: number);
17
+ /**
18
+ * Initialize cache - must be called after construction
19
+ */
20
+ initialize(): Promise<void>;
16
21
  /**
17
22
  * Generate a cache key from components
18
23
  */
@@ -37,6 +42,10 @@ export declare class CacheService {
37
42
  * Clear all cache entries
38
43
  */
39
44
  clear(): void;
45
+ /**
46
+ * Stop cleanup interval and clear all resources
47
+ */
48
+ dispose(): void;
40
49
  /**
41
50
  * Get cache statistics
42
51
  */
@@ -16,11 +16,17 @@ export class CacheService {
16
16
  maxSize;
17
17
  savePending = false;
18
18
  saveTimer = null;
19
+ cleanupInterval = null;
19
20
  constructor(cachePath = CACHE_CONFIG.CACHE_PATH, maxAge = CACHE_CONFIG.DEFAULT_TTL, maxSize = CACHE_CONFIG.MAX_SIZE) {
20
21
  this.cachePath = path.resolve(cachePath);
21
22
  this.maxAge = maxAge;
22
23
  this.maxSize = maxSize;
23
- this.loadFromFile();
24
+ }
25
+ /**
26
+ * Initialize cache - must be called after construction
27
+ */
28
+ async initialize() {
29
+ await this.loadFromFile();
24
30
  }
25
31
  /**
26
32
  * Generate a cache key from components
@@ -95,6 +101,20 @@ export class CacheService {
95
101
  logger.info('Cache cleared');
96
102
  this.scheduleSave();
97
103
  }
104
+ /**
105
+ * Stop cleanup interval and clear all resources
106
+ */
107
+ dispose() {
108
+ if (this.cleanupInterval) {
109
+ clearInterval(this.cleanupInterval);
110
+ this.cleanupInterval = null;
111
+ }
112
+ if (this.saveTimer) {
113
+ clearTimeout(this.saveTimer);
114
+ this.saveTimer = null;
115
+ }
116
+ this.cache.clear();
117
+ }
98
118
  /**
99
119
  * Get cache statistics
100
120
  */
@@ -288,8 +308,10 @@ let globalCache = null;
288
308
  export function getGlobalCache() {
289
309
  if (!globalCache) {
290
310
  globalCache = new CacheService();
311
+ // Initialize async (fire and forget - will be ready on next tick)
312
+ globalCache.initialize().catch(err => logger.error('Cache init failed:', err));
291
313
  // Cleanup expired entries every 5 minutes
292
- setInterval(() => {
314
+ globalCache.cleanupInterval = setInterval(() => {
293
315
  globalCache?.cleanup();
294
316
  }, 5 * 60 * 1000);
295
317
  }
@@ -300,7 +322,7 @@ export function getGlobalCache() {
300
322
  */
301
323
  export function resetGlobalCache() {
302
324
  if (globalCache) {
303
- globalCache.clear();
325
+ globalCache.dispose();
304
326
  }
305
327
  globalCache = null;
306
328
  }
@@ -138,7 +138,9 @@ export interface TableEntry {
138
138
  drawn: number;
139
139
  lost: number;
140
140
  goalsFor: number;
141
- against: number;
141
+ goalsAgainst: number;
142
+ /** @deprecated Use goalsAgainst instead */
143
+ against?: number;
142
144
  };
143
145
  awayRecord?: {
144
146
  played: number;
@@ -146,7 +148,9 @@ export interface TableEntry {
146
148
  drawn: number;
147
149
  lost: number;
148
150
  goalsFor: number;
149
- against: number;
151
+ goalsAgainst: number;
152
+ /** @deprecated Use goalsAgainst instead */
153
+ against?: number;
150
154
  };
151
155
  lastFive?: {
152
156
  result: 'W' | 'D' | 'L';
@@ -61,6 +61,10 @@ export declare class FootballDataProvider extends APIProviderBase {
61
61
  export declare class SportsDBProvider extends APIProviderBase {
62
62
  constructor();
63
63
  protected getAuthHeaders(): Record<string, string>;
64
+ /**
65
+ * Override callAPI to inject API key in URL for TheSportsDB
66
+ */
67
+ protected callAPI<T>(endpoint: string, options?: RequestInit): Promise<APIResponse<T>>;
64
68
  protected transformResponse<T>(data: any): T;
65
69
  getMatch(matchId: string): Promise<APIResponse<Match>>;
66
70
  getLiveMatches(): Promise<APIResponse<Match[]>>;