@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.
- package/README.md +6 -0
- package/dist/chaos.test.d.ts +1 -0
- package/dist/chaos.test.js +73 -0
- package/dist/codestore.test.d.ts +1 -0
- package/dist/codestore.test.js +65 -0
- package/dist/coding.test.d.ts +1 -0
- package/dist/coding.test.js +140 -0
- package/dist/e2e.test.d.ts +1 -0
- package/dist/e2e.test.js +122 -0
- package/dist/filesystem.test.d.ts +1 -0
- package/dist/filesystem.test.js +190 -0
- package/dist/graph.test.d.ts +1 -0
- package/dist/graph.test.js +150 -0
- package/dist/graph_extra.test.d.ts +1 -0
- package/dist/graph_extra.test.js +93 -0
- package/dist/graph_repro.test.d.ts +1 -0
- package/dist/graph_repro.test.js +50 -0
- package/dist/human.test.d.ts +1 -0
- package/dist/human.test.js +221 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.js +58 -0
- package/dist/knowledge.test.d.ts +1 -0
- package/dist/knowledge.test.js +105 -0
- package/dist/lib.js +1 -0
- package/dist/notes.test.d.ts +1 -0
- package/dist/notes.test.js +84 -0
- package/dist/registration.test.d.ts +1 -0
- package/dist/registration.test.js +39 -0
- package/dist/server.test.d.ts +1 -0
- package/dist/server.test.js +127 -0
- package/dist/stress.test.d.ts +1 -0
- package/dist/stress.test.js +72 -0
- package/dist/tools/codestore_tools.test.d.ts +1 -0
- package/dist/tools/codestore_tools.test.js +115 -0
- package/dist/tools/filesystem.js +1 -0
- package/dist/tools/sports/core/constants.d.ts +2 -1
- package/dist/tools/sports/core/constants.js +18 -6
- package/dist/tools/sports/providers/scraper.d.ts +6 -1
- package/dist/tools/sports/providers/scraper.js +63 -8
- package/dist/tools/sports/tools/match.js +44 -21
- package/dist/tools/sports/tracker.test.d.ts +1 -0
- package/dist/tools/sports/tracker.test.js +100 -0
- package/dist/utils.test.d.ts +1 -0
- package/dist/utils.test.js +40 -0
- package/dist/verify_cache.test.d.ts +1 -0
- package/dist/verify_cache.test.js +185 -0
- package/dist/web_fallback.test.d.ts +1 -0
- package/dist/web_fallback.test.js +103 -0
- package/dist/web_read.test.d.ts +1 -0
- package/dist/web_read.test.js +60 -0
- 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
|
+
});
|
package/dist/tools/filesystem.js
CHANGED
|
@@ -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 ["
|
|
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
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
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
|
-
'
|
|
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
|
|
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;
|