@gotza02/sequential-thinking 10000.0.0 → 10000.0.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 (51) hide show
  1. package/README.md +6 -0
  2. package/dist/chaos.test.d.ts +1 -0
  3. package/dist/chaos.test.js +73 -0
  4. package/dist/codestore.test.d.ts +1 -0
  5. package/dist/codestore.test.js +65 -0
  6. package/dist/coding.test.d.ts +1 -0
  7. package/dist/coding.test.js +140 -0
  8. package/dist/e2e.test.d.ts +1 -0
  9. package/dist/e2e.test.js +122 -0
  10. package/dist/filesystem.test.d.ts +1 -0
  11. package/dist/filesystem.test.js +190 -0
  12. package/dist/graph.test.d.ts +1 -0
  13. package/dist/graph.test.js +150 -0
  14. package/dist/graph_extra.test.d.ts +1 -0
  15. package/dist/graph_extra.test.js +93 -0
  16. package/dist/graph_repro.test.d.ts +1 -0
  17. package/dist/graph_repro.test.js +50 -0
  18. package/dist/human.test.d.ts +1 -0
  19. package/dist/human.test.js +221 -0
  20. package/dist/integration.test.d.ts +1 -0
  21. package/dist/integration.test.js +58 -0
  22. package/dist/knowledge.test.d.ts +1 -0
  23. package/dist/knowledge.test.js +105 -0
  24. package/dist/lib.js +1 -0
  25. package/dist/notes.test.d.ts +1 -0
  26. package/dist/notes.test.js +84 -0
  27. package/dist/registration.test.d.ts +1 -0
  28. package/dist/registration.test.js +39 -0
  29. package/dist/server.test.d.ts +1 -0
  30. package/dist/server.test.js +127 -0
  31. package/dist/stress.test.d.ts +1 -0
  32. package/dist/stress.test.js +72 -0
  33. package/dist/tools/codestore_tools.test.d.ts +1 -0
  34. package/dist/tools/codestore_tools.test.js +115 -0
  35. package/dist/tools/filesystem.js +1 -0
  36. package/dist/tools/sports/core/constants.d.ts +2 -1
  37. package/dist/tools/sports/core/constants.js +18 -6
  38. package/dist/tools/sports/providers/scraper.d.ts +6 -1
  39. package/dist/tools/sports/providers/scraper.js +63 -8
  40. package/dist/tools/sports/tools/match.js +44 -21
  41. package/dist/tools/sports/tracker.test.d.ts +1 -0
  42. package/dist/tools/sports/tracker.test.js +100 -0
  43. package/dist/utils.test.d.ts +1 -0
  44. package/dist/utils.test.js +40 -0
  45. package/dist/verify_cache.test.d.ts +1 -0
  46. package/dist/verify_cache.test.js +185 -0
  47. package/dist/web_fallback.test.d.ts +1 -0
  48. package/dist/web_fallback.test.js +103 -0
  49. package/dist/web_read.test.d.ts +1 -0
  50. package/dist/web_read.test.js +60 -0
  51. package/package.json +7 -6
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { KnowledgeGraphManager } from './knowledge.js';
3
+ import * as fs from 'fs/promises';
4
+ vi.mock('fs/promises');
5
+ vi.mock('fs', () => ({
6
+ existsSync: vi.fn(() => true),
7
+ statSync: vi.fn(() => ({ mtimeMs: 123456789 })),
8
+ readFileSync: vi.fn()
9
+ }));
10
+ describe('KnowledgeGraphManager', () => {
11
+ let manager;
12
+ const testPath = 'test_graph.json';
13
+ let mockStore = '{}';
14
+ beforeEach(() => {
15
+ mockStore = '{}';
16
+ fs.readFile.mockImplementation(async () => mockStore);
17
+ fs.writeFile.mockImplementation(async (path, data) => {
18
+ mockStore = data;
19
+ });
20
+ fs.stat.mockImplementation(async () => ({ mtimeMs: 123456789 }));
21
+ fs.rename.mockImplementation(async () => { });
22
+ manager = new KnowledgeGraphManager(testPath);
23
+ });
24
+ afterEach(() => {
25
+ vi.clearAllMocks();
26
+ });
27
+ it('should add a node correctly', async () => {
28
+ const node = await manager.addNode({
29
+ label: 'Player',
30
+ name: 'Trent Alexander-Arnold',
31
+ properties: { position: 'RB' }
32
+ });
33
+ expect(node.id).toBeDefined();
34
+ expect(node.name).toBe('Trent Alexander-Arnold');
35
+ // Verify storage update
36
+ const stored = JSON.parse(mockStore);
37
+ expect(stored.nodes).toHaveLength(1);
38
+ });
39
+ it('should deduplicate nodes by name and label', async () => {
40
+ const n1 = await manager.addNode({ label: 'Team', name: 'Liverpool', properties: {} });
41
+ const n2 = await manager.addNode({ label: 'Team', name: 'liverpool', properties: { league: 'PL' } });
42
+ expect(n1.id).toBe(n2.id); // Should be same ID
43
+ expect(n2.properties.league).toBe('PL'); // Should update properties
44
+ const stored = JSON.parse(mockStore);
45
+ expect(stored.nodes).toHaveLength(1);
46
+ });
47
+ it('should create relationships (edges)', async () => {
48
+ const player = await manager.addNode({ label: 'Player', name: 'Salah', properties: {} });
49
+ const team = await manager.addNode({ label: 'Team', name: 'Liverpool', properties: {} });
50
+ const edge = await manager.addEdge({
51
+ source: player.id,
52
+ target: team.id,
53
+ relation: 'PLAYS_FOR'
54
+ });
55
+ expect(edge.relation).toBe('PLAYS_FOR');
56
+ const stored = JSON.parse(mockStore);
57
+ expect(stored.edges).toHaveLength(1);
58
+ });
59
+ it('should traverse the graph (User Scenario: Tactic Impact)', async () => {
60
+ // Setup the scenario: Player Injury -> Tactic -> Win Probability
61
+ const player = await manager.addNode({
62
+ label: 'Player',
63
+ name: 'Trent',
64
+ properties: { status: 'Injured' }
65
+ });
66
+ const tactic = await manager.addNode({
67
+ label: 'Tactic',
68
+ name: 'Right-Side Attack',
69
+ properties: {}
70
+ });
71
+ const prediction = await manager.addNode({
72
+ label: 'Prediction',
73
+ name: 'Win Chance',
74
+ properties: {}
75
+ });
76
+ await manager.addEdge({ source: player.id, target: tactic.id, relation: 'AFFECTS' });
77
+ await manager.addEdge({ source: tactic.id, target: prediction.id, relation: 'IMPACTS' });
78
+ // Query starting from Trent
79
+ const result = await manager.query({
80
+ startNodeId: player.id,
81
+ maxDepth: 2
82
+ });
83
+ // Should find all 3 nodes
84
+ expect(result.nodes).toHaveLength(3);
85
+ const names = result.nodes.map(n => n.name);
86
+ expect(names).toContain('Trent');
87
+ expect(names).toContain('Right-Side Attack');
88
+ expect(names).toContain('Win Chance');
89
+ // Should find edges
90
+ expect(result.edges).toHaveLength(2);
91
+ });
92
+ it('should filter query by relation type', async () => {
93
+ const n1 = await manager.addNode({ label: 'A', name: 'A', properties: {} });
94
+ const n2 = await manager.addNode({ label: 'B', name: 'B', properties: {} });
95
+ const n3 = await manager.addNode({ label: 'C', name: 'C', properties: {} });
96
+ await manager.addEdge({ source: n1.id, target: n2.id, relation: 'FRIEND' });
97
+ await manager.addEdge({ source: n1.id, target: n3.id, relation: 'ENEMY' });
98
+ const result = await manager.query({
99
+ startNodeId: n1.id,
100
+ relationType: 'FRIEND'
101
+ });
102
+ expect(result.nodes).toHaveLength(2); // A and B
103
+ expect(result.nodes.map(n => n.name)).not.toContain('C');
104
+ });
105
+ });
package/dist/lib.js CHANGED
@@ -497,6 +497,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
497
497
  "🚨 CONFIDENCE CRITICAL (<50). Execution blocked to prevent further damage.",
498
498
  "🛑 STOP: You are likely in a loop or making repeated errors.",
499
499
  "👉 REQUIRED ACTION: You must switch thoughtType to 'reflexion' to critique your errors.",
500
+ "⚠️ REMEMBER: You MUST provide a 'thought' string explaining your reflection. Do not leave it empty.",
500
501
  recoveryAdvice
501
502
  ],
502
503
  confidenceScore: this.confidenceScore
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { NotesManager } from './notes.js';
3
+ import * as fs from 'fs/promises';
4
+ vi.mock('fs/promises');
5
+ vi.mock('fs', () => ({
6
+ existsSync: vi.fn(() => true),
7
+ statSync: vi.fn(() => ({ mtimeMs: 123456789 })),
8
+ readFileSync: vi.fn()
9
+ }));
10
+ describe('NotesManager', () => {
11
+ let manager;
12
+ const testPath = 'test_notes.json';
13
+ let mockStore = '[]';
14
+ beforeEach(() => {
15
+ mockStore = '[]';
16
+ fs.readFile.mockImplementation(async () => mockStore);
17
+ fs.writeFile.mockImplementation(async (path, data) => {
18
+ mockStore = data;
19
+ });
20
+ fs.stat.mockImplementation(async () => ({ mtimeMs: 123456789 }));
21
+ manager = new NotesManager(testPath);
22
+ });
23
+ afterEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+ it('should add a note correctly', async () => {
27
+ const note = await manager.addNote("Test Title", "Test Content", ["tag1"]);
28
+ expect(note.title).toBe("Test Title");
29
+ expect(note.tags).toContain("tag1");
30
+ // Check new format
31
+ const stored = JSON.parse(mockStore);
32
+ const storedNotes = stored.notes || stored;
33
+ expect(storedNotes).toHaveLength(1);
34
+ });
35
+ it('should list notes with sorting by priority', async () => {
36
+ await manager.addNote("Low Prio", "Content", [], "low");
37
+ await manager.addNote("High Prio", "Content", [], "high");
38
+ const notes = await manager.listNotes();
39
+ expect(notes).toHaveLength(2);
40
+ expect(notes[0].priority).toBe('high');
41
+ expect(notes[1].priority).toBe('low');
42
+ });
43
+ it('should filter notes by tag', async () => {
44
+ await manager.addNote("Note 1", "Content", ["react"]);
45
+ await manager.addNote("Note 2", "Content", ["vue"]);
46
+ const reactNotes = await manager.listNotes('react');
47
+ expect(reactNotes).toHaveLength(1);
48
+ expect(reactNotes[0].title).toBe("Note 1");
49
+ });
50
+ it('should search notes by query', async () => {
51
+ await manager.addNote("Deploy Script", "Run npm build", ["devops"]);
52
+ await manager.addNote("Meeting Notes", "Discuss API", ["meeting"]);
53
+ const results = await manager.searchNotes("build");
54
+ expect(results).toHaveLength(1);
55
+ expect(results[0].title).toBe("Deploy Script");
56
+ });
57
+ it('should update a note', async () => {
58
+ const note = await manager.addNote("Original", "Content");
59
+ const updated = await manager.updateNote(note.id, { title: "Updated" });
60
+ expect(updated?.title).toBe("Updated");
61
+ expect(updated?.content).toBe("Content"); // Should remain
62
+ const list = await manager.listNotes();
63
+ expect(list[0].title).toBe("Updated");
64
+ });
65
+ it('should delete a note', async () => {
66
+ const note = await manager.addNote("To Delete", "Content");
67
+ const success = await manager.deleteNote(note.id);
68
+ expect(success).toBe(true);
69
+ const list = await manager.listNotes();
70
+ expect(list).toHaveLength(0);
71
+ });
72
+ it('should hide expired notes by default', async () => {
73
+ // Expired yesterday
74
+ const yesterday = new Date();
75
+ yesterday.setDate(yesterday.getDate() - 1);
76
+ await manager.addNote("Expired", "Content", [], "medium", yesterday.toISOString());
77
+ await manager.addNote("Active", "Content");
78
+ const list = await manager.listNotes();
79
+ expect(list).toHaveLength(1);
80
+ expect(list[0].title).toBe("Active");
81
+ const all = await manager.listNotes(undefined, true);
82
+ expect(all).toHaveLength(2);
83
+ });
84
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { registerThinkingTools } from './tools/thinking.js';
3
+ import { registerGraphTools } from './tools/graph.js';
4
+ import { registerNoteTools } from './tools/notes.js';
5
+ import { registerWebTools } from './tools/web.js';
6
+ import { registerFileSystemTools } from './tools/filesystem.js';
7
+ import { registerCodingTools } from './tools/coding.js';
8
+ import { registerCodeDbTools } from './tools/codestore.js';
9
+ describe('Tool Registration', () => {
10
+ it('should register all tools without duplicates', () => {
11
+ const registeredTools = new Set();
12
+ const mockServer = {
13
+ tool: (name, desc, schema, cb) => {
14
+ if (registeredTools.has(name)) {
15
+ throw new Error(`Duplicate tool name: ${name}`);
16
+ }
17
+ registeredTools.add(name);
18
+ }
19
+ };
20
+ // Mock dependencies
21
+ const mockThinking = { processThought: vi.fn(), clearHistory: vi.fn(), archiveHistory: vi.fn() };
22
+ const mockGraph = { build: vi.fn(), getRelationships: vi.fn(), getSummary: vi.fn(), toMermaid: vi.fn() };
23
+ const mockNotes = {};
24
+ const mockCodeDb = {};
25
+ registerThinkingTools(mockServer, mockThinking);
26
+ registerGraphTools(mockServer, mockGraph);
27
+ registerNoteTools(mockServer, mockNotes);
28
+ registerWebTools(mockServer);
29
+ registerFileSystemTools(mockServer);
30
+ registerCodingTools(mockServer, mockGraph);
31
+ registerCodeDbTools(mockServer, mockCodeDb);
32
+ expect(registeredTools.has('sequentialthinking')).toBe(true);
33
+ expect(registeredTools.has('build_project_graph')).toBe(true);
34
+ expect(registeredTools.has('read_file')).toBe(true);
35
+ expect(registeredTools.has('web_search')).toBe(true);
36
+ // ... and so on
37
+ console.log('Registered tools:', Array.from(registeredTools).join(', '));
38
+ });
39
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,127 @@
1
+ import { describe, it, expect, beforeEach, afterAll } from 'vitest';
2
+ import { SequentialThinkingServer } from './lib.js';
3
+ import * as fs from 'fs';
4
+ describe('SequentialThinkingServer', () => {
5
+ let server;
6
+ const testStoragePath = 'test_thoughts.json';
7
+ beforeEach(() => {
8
+ if (fs.existsSync(testStoragePath)) {
9
+ fs.unlinkSync(testStoragePath);
10
+ }
11
+ server = new SequentialThinkingServer(testStoragePath);
12
+ });
13
+ afterAll(() => {
14
+ if (fs.existsSync(testStoragePath)) {
15
+ fs.unlinkSync(testStoragePath);
16
+ }
17
+ });
18
+ it('should process a basic linear thought', async () => {
19
+ const input = {
20
+ thought: "First step",
21
+ thoughtNumber: 1,
22
+ totalThoughts: 3,
23
+ nextThoughtNeeded: true,
24
+ thoughtType: 'analysis'
25
+ };
26
+ const result = await server.processThought(input);
27
+ expect(result.isError).toBeUndefined();
28
+ const content = JSON.parse(result.content[0].text);
29
+ expect(content.thoughtNumber).toBe(1);
30
+ expect(content.thoughtHistoryLength).toBe(1);
31
+ });
32
+ it('should handle branching correctly', async () => {
33
+ // Initial thought
34
+ await server.processThought({
35
+ thought: "Root thought",
36
+ thoughtNumber: 1,
37
+ totalThoughts: 3,
38
+ nextThoughtNeeded: true
39
+ });
40
+ // Branch 1
41
+ const branch1Input = {
42
+ thought: "Alternative A",
43
+ thoughtNumber: 2,
44
+ totalThoughts: 3,
45
+ nextThoughtNeeded: true,
46
+ branchFromThought: 1,
47
+ branchId: "branch-A",
48
+ thoughtType: 'generation'
49
+ };
50
+ const result1 = await server.processThought(branch1Input);
51
+ const content1 = JSON.parse(result1.content[0].text);
52
+ expect(content1.branches).toContain("1-branch-A");
53
+ // Branch 2
54
+ const branch2Input = {
55
+ thought: "Alternative B",
56
+ thoughtNumber: 2,
57
+ totalThoughts: 3,
58
+ nextThoughtNeeded: true,
59
+ branchFromThought: 1,
60
+ branchId: "branch-B",
61
+ thoughtType: 'generation'
62
+ };
63
+ const result2 = await server.processThought(branch2Input);
64
+ const content2 = JSON.parse(result2.content[0].text);
65
+ expect(content2.branches).toContain("1-branch-B");
66
+ expect(content2.branches.length).toBe(2);
67
+ });
68
+ it('should handle evaluation with scores', async () => {
69
+ const input = {
70
+ thought: "Evaluating option X",
71
+ thoughtNumber: 3,
72
+ totalThoughts: 5,
73
+ nextThoughtNeeded: true,
74
+ thoughtType: 'evaluation',
75
+ score: 8,
76
+ options: ['Option X', 'Option Y']
77
+ };
78
+ const result = await server.processThought(input);
79
+ expect(result.isError).toBeUndefined();
80
+ // Since we don't return the score in the simple JSON response (only in logs or history),
81
+ // we mainly check that it doesn't crash and processes correctly.
82
+ // If we exposed history in the response, we could check that too.
83
+ });
84
+ it('should adjust totalThoughts if thoughtNumber exceeds it', async () => {
85
+ const input = {
86
+ thought: "Unexpected long process",
87
+ thoughtNumber: 6,
88
+ totalThoughts: 5,
89
+ nextThoughtNeeded: true
90
+ };
91
+ const result = await server.processThought(input);
92
+ const content = JSON.parse(result.content[0].text);
93
+ expect(content.totalThoughts).toBe(6);
94
+ });
95
+ it('should clear thought history', async () => {
96
+ await server.processThought({
97
+ thought: "To be forgotten",
98
+ thoughtNumber: 1,
99
+ totalThoughts: 1,
100
+ nextThoughtNeeded: false
101
+ });
102
+ await server.clearHistory();
103
+ // Since we can't easily peek into private state, we'll process a new thought
104
+ // and check if thoughtHistoryLength is 1 (meaning it started over or is just this one)
105
+ const result = await server.processThought({
106
+ thought: "Fresh start",
107
+ thoughtNumber: 1,
108
+ totalThoughts: 1,
109
+ nextThoughtNeeded: false
110
+ });
111
+ const content = JSON.parse(result.content[0].text);
112
+ expect(content.thoughtHistoryLength).toBe(1);
113
+ });
114
+ it('should summarize history correctly', async () => {
115
+ // Add 3 thoughts
116
+ await server.processThought({ thought: "T1", thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true });
117
+ await server.processThought({ thought: "T2", thoughtNumber: 2, totalThoughts: 3, nextThoughtNeeded: true });
118
+ await server.processThought({ thought: "T3", thoughtNumber: 3, totalThoughts: 3, nextThoughtNeeded: false });
119
+ const result = await server.archiveHistory(1, 2, "Summary of T1 and T2");
120
+ expect(result.newHistoryLength).toBe(2); // Summary + T3
121
+ expect(result.summaryInsertedAt).toBe(1);
122
+ });
123
+ it('should throw error when summarizing invalid range', async () => {
124
+ await server.processThought({ thought: "T1", thoughtNumber: 1, totalThoughts: 1, nextThoughtNeeded: false });
125
+ await expect(server.archiveHistory(1, 5, "Invalid")).rejects.toThrow();
126
+ });
127
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { SequentialThinkingServer } from './lib.js';
3
+ import { ProjectKnowledgeGraph } from './graph.js';
4
+ import * as fs from 'fs/promises';
5
+ import * as path from 'path';
6
+ // Mock fs for graph test
7
+ vi.mock('fs/promises', async (importOriginal) => {
8
+ const actual = await importOriginal();
9
+ return {
10
+ ...actual,
11
+ readdir: vi.fn(),
12
+ readFile: vi.fn(),
13
+ writeFile: vi.fn(),
14
+ rename: vi.fn(),
15
+ unlink: vi.fn(),
16
+ stat: vi.fn().mockImplementation(async (p) => ({
17
+ isDirectory: () => p === path.resolve('.') || p === '.',
18
+ mtimeMs: Date.now()
19
+ }))
20
+ };
21
+ });
22
+ // Mock existsSync and readFileSync from 'fs' (non-promise) for SequentialThinkingServer
23
+ vi.mock('fs', async () => {
24
+ return {
25
+ existsSync: () => false,
26
+ readFileSync: () => '[]',
27
+ unlinkSync: () => { }
28
+ };
29
+ });
30
+ describe('Stress Testing', () => {
31
+ it('should handle 1000 sequential thoughts', async () => {
32
+ // We use a real instance but mocked fs
33
+ const server = new SequentialThinkingServer('stress_thoughts.json');
34
+ const startTime = Date.now();
35
+ for (let i = 1; i <= 1000; i++) {
36
+ await server.processThought({
37
+ thought: `Thought ${i}`,
38
+ thoughtNumber: i,
39
+ totalThoughts: 1000,
40
+ nextThoughtNeeded: i < 1000
41
+ });
42
+ }
43
+ const duration = Date.now() - startTime;
44
+ console.log(`Processed 1000 thoughts in ${duration}ms`);
45
+ expect(duration).toBeLessThan(15000); // Should be fast enough (< 15s)
46
+ }, 15000);
47
+ it('should handle large graph construction', async () => {
48
+ const graph = new ProjectKnowledgeGraph();
49
+ // Mock 1000 files
50
+ const files = Array.from({ length: 1000 }, (_, i) => `file${i}.ts`);
51
+ // Mock readdir to return these files (recursively? no, just flat for stress test)
52
+ // logic in getAllFiles is recursive. We need to mock it to return all at once or handle recursion.
53
+ // Actually, ProjectKnowledgeGraph.getAllFiles calls readdir.
54
+ // Let's mock readdir to return files for root, and then nothing for subdirs.
55
+ fs.readdir.mockImplementation(async (dir) => {
56
+ if (dir === path.resolve('.')) {
57
+ return files.map(f => ({
58
+ name: f,
59
+ isDirectory: () => false
60
+ }));
61
+ }
62
+ return [];
63
+ });
64
+ fs.readFile.mockResolvedValue("import { x } from './file0';");
65
+ const startTime = Date.now();
66
+ const result = await graph.build('.');
67
+ const duration = Date.now() - startTime;
68
+ console.log(`Built graph of ${result.totalFiles} files in ${duration}ms`);
69
+ expect(result.totalFiles).toBe(1000);
70
+ expect(duration).toBeLessThan(5000);
71
+ });
72
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { registerCodeDbTools } from './codestore.js';
3
+ describe('CodeStore Tools', () => {
4
+ let mockServer;
5
+ let mockDb;
6
+ let registeredTools;
7
+ beforeEach(() => {
8
+ registeredTools = new Map();
9
+ mockServer = {
10
+ tool: (name, description, schema, handler) => {
11
+ registeredTools.set(name, handler);
12
+ }
13
+ };
14
+ mockDb = {
15
+ addSnippet: vi.fn(),
16
+ searchSnippets: vi.fn(),
17
+ listAllPatterns: vi.fn(),
18
+ learnPattern: vi.fn()
19
+ };
20
+ registerCodeDbTools(mockServer, mockDb);
21
+ });
22
+ describe('add_code_snippet', () => {
23
+ it('should add a snippet successfully', async () => {
24
+ const handler = registeredTools.get('add_code_snippet');
25
+ const mockSnippet = { id: '123', title: 'Test', code: 'console.log()', language: 'ts' };
26
+ mockDb.addSnippet.mockResolvedValue(mockSnippet);
27
+ const result = await handler({
28
+ title: 'Test',
29
+ code: 'console.log()',
30
+ language: 'ts',
31
+ description: 'test desc'
32
+ });
33
+ expect(mockDb.addSnippet).toHaveBeenCalledWith(expect.objectContaining({
34
+ title: 'Test',
35
+ code: 'console.log()'
36
+ }));
37
+ expect(result.content[0].text).toContain('Snippet saved with ID: 123');
38
+ });
39
+ it('should handle errors gracefully', async () => {
40
+ const handler = registeredTools.get('add_code_snippet');
41
+ mockDb.addSnippet.mockRejectedValue(new Error('Database error'));
42
+ const result = await handler({
43
+ title: 'Test',
44
+ code: 'console.log()',
45
+ language: 'ts',
46
+ description: 'test desc'
47
+ });
48
+ expect(result.isError).toBe(true);
49
+ expect(result.content[0].text).toContain('Error adding snippet: Database error');
50
+ });
51
+ });
52
+ describe('search_code_db', () => {
53
+ it('should search snippets and patterns', async () => {
54
+ const handler = registeredTools.get('search_code_db');
55
+ const mockSnippets = [{
56
+ snippet: {
57
+ id: '1',
58
+ title: 'Auth',
59
+ code: 'login()',
60
+ language: 'ts',
61
+ description: 'Login flow',
62
+ tags: [],
63
+ updatedAt: '2023-01-01'
64
+ },
65
+ relevance: 1
66
+ }];
67
+ const mockPatterns = { 'auth-flow': 'How auth works' };
68
+ mockDb.searchSnippets.mockResolvedValue(mockSnippets);
69
+ mockDb.listAllPatterns.mockResolvedValue(mockPatterns);
70
+ const result = await handler({ query: 'auth' });
71
+ expect(mockDb.searchSnippets).toHaveBeenCalledWith('auth');
72
+ expect(mockDb.listAllPatterns).toHaveBeenCalled();
73
+ expect(result.content[0].text).toContain('SNIPPETS FOUND');
74
+ expect(result.content[0].text).toContain('Login flow');
75
+ expect(result.content[0].text).toContain('PATTERNS FOUND');
76
+ expect(result.content[0].text).toContain('auth-flow');
77
+ });
78
+ it('should report no results found', async () => {
79
+ const handler = registeredTools.get('search_code_db');
80
+ mockDb.searchSnippets.mockResolvedValue([]);
81
+ mockDb.listAllPatterns.mockResolvedValue({});
82
+ const result = await handler({ query: 'xyz' });
83
+ expect(result.content[0].text).toContain('No results found in the Code Database');
84
+ });
85
+ it('should handle errors gracefully', async () => {
86
+ const handler = registeredTools.get('search_code_db');
87
+ mockDb.searchSnippets.mockRejectedValue(new Error('Search failed'));
88
+ const result = await handler({ query: 'test' });
89
+ expect(result.isError).toBe(true);
90
+ expect(result.content[0].text).toContain('Error searching code DB: Search failed');
91
+ });
92
+ });
93
+ describe('learn_architecture_pattern', () => {
94
+ it('should learn a pattern successfully', async () => {
95
+ const handler = registeredTools.get('learn_architecture_pattern');
96
+ mockDb.learnPattern.mockResolvedValue(undefined);
97
+ const result = await handler({
98
+ name: 'test-pattern',
99
+ description: 'test description'
100
+ });
101
+ expect(mockDb.learnPattern).toHaveBeenCalledWith('test-pattern', 'test description');
102
+ expect(result.content[0].text).toContain("Pattern 'test-pattern' learned");
103
+ });
104
+ it('should handle errors gracefully', async () => {
105
+ const handler = registeredTools.get('learn_architecture_pattern');
106
+ mockDb.learnPattern.mockRejectedValue(new Error('Learn failed'));
107
+ const result = await handler({
108
+ name: 'test-pattern',
109
+ description: 'test description'
110
+ });
111
+ expect(result.isError).toBe(true);
112
+ expect(result.content[0].text).toContain('Error learning pattern: Learn failed');
113
+ });
114
+ });
115
+ });
@@ -251,6 +251,7 @@ export function registerFileSystemTools(server) {
251
251
  try {
252
252
  const content = await fs.readFile(filePath, 'utf-8');
253
253
  // Skip binary-like files (high proportion of non-printable chars)
254
+ // eslint-disable-next-line no-control-regex
254
255
  const nonPrintableRatio = (content.match(/[\x00-\x08\x0E-\x1F]/g) || []).length / content.length;
255
256
  if (nonPrintableRatio > 0.3) {
256
257
  return; // Likely binary file
@@ -31,7 +31,8 @@ export declare const CACHE_CONFIG: {
31
31
  readonly MAX_SIZE: 1000;
32
32
  };
33
33
  export declare const SCRAPER_CONFIG: {
34
- readonly PRIORITY_DOMAINS: readonly ["whoscored.com", "sofascore.com", "flashscore.com", "understat.com", "fbref.com", "bbc.co.uk/sport", "skysports.com", "sportsmole.co.uk", "goal.com", "thesquareball.net"];
34
+ readonly PRIORITY_DOMAINS: readonly ["understat.com", "bbc.co.uk/sport", "sportsmole.co.uk", "skysports.com", "goal.com", "thesquareball.net", "fbref.com", "whoscored.com", "sofascore.com", "flashscore.com"];
35
+ readonly NEWS_DOMAINS: readonly ["bbc.co.uk/sport", "skysports.com", "sportsmole.co.uk", "standard.co.uk/sport", "goal.com", "espn.co.uk/football", "talksport.com", "mirror.co.uk/sport", "dailymail.co.uk/sport"];
35
36
  readonly TIMEOUT: 15000;
36
37
  readonly USER_AGENT: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
37
38
  readonly MAX_CONTENT_LENGTH: 50000;
@@ -44,16 +44,28 @@ export const CACHE_CONFIG = {
44
44
  export const SCRAPER_CONFIG = {
45
45
  // Priority domains for sports data
46
46
  PRIORITY_DOMAINS: [
47
- 'whoscored.com',
48
- 'sofascore.com',
49
- 'flashscore.com',
50
- 'understat.com',
51
- 'fbref.com',
47
+ 'understat.com', // High accuracy (Smart Extractor)
48
+ 'bbc.co.uk/sport', // Text-heavy, reliable
49
+ 'sportsmole.co.uk', // Text-heavy, reliable
50
+ 'skysports.com', // Good text/stats mix
51
+ 'goal.com', // Text-heavy
52
+ 'thesquareball.net', // Text-heavy
53
+ 'fbref.com', // Structured data
54
+ 'whoscored.com', // Hard to scrape (JS heavy)
55
+ 'sofascore.com', // Hard to scrape (JS heavy)
56
+ 'flashscore.com', // Hard to scrape (JS heavy)
57
+ ],
58
+ // News-focused domains (for lineups, injuries)
59
+ NEWS_DOMAINS: [
52
60
  'bbc.co.uk/sport',
53
61
  'skysports.com',
54
62
  'sportsmole.co.uk',
63
+ 'standard.co.uk/sport',
55
64
  'goal.com',
56
- 'thesquareball.net'
65
+ 'espn.co.uk/football',
66
+ 'talksport.com',
67
+ 'mirror.co.uk/sport',
68
+ 'dailymail.co.uk/sport'
57
69
  ],
58
70
  // Scraping timeout in milliseconds
59
71
  TIMEOUT: 15000,
@@ -20,7 +20,8 @@ export declare class ScraperProvider extends ScraperProviderBase {
20
20
  */
21
21
  protected scrape<T>(url: string, extractor?: (html: string) => T): Promise<APIResponse<T>>;
22
22
  /**
23
- * Specialized extractor for Understat (Example)
23
+ * Specialized extractor for Understat
24
+ * Extracts xG, shots, and other advanced metrics from the JSON data embedded in script tags.
24
25
  */
25
26
  private extractUnderstatData;
26
27
  /**
@@ -72,3 +73,7 @@ export declare function scrapeMatchContent(url: string): Promise<APIResponse<str
72
73
  * Find best URL for match data from search results
73
74
  */
74
75
  export declare function findBestMatchUrl(urls: string[]): string | null;
76
+ /**
77
+ * Find best URL for news/lineups from search results
78
+ */
79
+ export declare function findBestNewsUrl(urls: string[]): string | null;