@gotza02/sequential-thinking 2026.2.20 → 2026.2.21
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/dist/codestore.d.ts +28 -0
- package/dist/graph.d.ts +60 -0
- package/dist/graph.js +19 -0
- package/dist/http-server.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib.d.ts +67 -0
- package/dist/lib.js +323 -83
- package/dist/notes.d.ts +25 -0
- package/dist/system_test.d.ts +1 -0
- package/dist/test_ts_req.d.ts +1 -0
- package/dist/tools/codestore.d.ts +3 -0
- package/dist/tools/coding.d.ts +3 -0
- package/dist/tools/filesystem.d.ts +2 -0
- package/dist/tools/graph.d.ts +3 -0
- package/dist/tools/graph.js +18 -0
- package/dist/tools/human.d.ts +65 -0
- package/dist/tools/human.js +305 -0
- package/dist/tools/notes.d.ts +3 -0
- package/dist/tools/thinking.d.ts +3 -0
- package/dist/tools/thinking.js +137 -65
- package/dist/tools/web.d.ts +2 -0
- package/dist/utils.d.ts +32 -0
- package/package.json +3 -1
- package/dist/chaos.test.js +0 -72
- package/dist/codestore.test.js +0 -59
- package/dist/coding.test.js +0 -140
- package/dist/e2e.test.js +0 -122
- package/dist/filesystem.test.js +0 -189
- package/dist/graph.test.js +0 -150
- package/dist/graph_repro.test.js +0 -63
- package/dist/integration.test.js +0 -58
- package/dist/notes.test.js +0 -74
- package/dist/registration.test.js +0 -39
- package/dist/repro_dollar.js +0 -30
- package/dist/repro_dollar_simple.js +0 -22
- package/dist/repro_history.js +0 -41
- package/dist/repro_path.js +0 -17
- package/dist/repro_search.test.js +0 -79
- package/dist/repro_ts_req.js +0 -3
- package/dist/server.test.js +0 -127
- package/dist/stress.test.js +0 -68
- package/dist/utils.test.js +0 -40
- package/dist/verify_cache.test.js +0 -27
- package/dist/verify_edit.test.js +0 -66
- package/dist/verify_notes.test.js +0 -36
- package/dist/verify_viz.test.js +0 -25
- package/dist/web_fallback.test.js +0 -103
- package/dist/web_read.test.js +0 -60
package/dist/graph.test.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { ProjectKnowledgeGraph } from './graph.js';
|
|
3
|
-
import * as fs from 'fs/promises';
|
|
4
|
-
vi.mock('fs/promises');
|
|
5
|
-
describe('ProjectKnowledgeGraph', () => {
|
|
6
|
-
let graph;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
graph = new ProjectKnowledgeGraph();
|
|
9
|
-
vi.resetAllMocks();
|
|
10
|
-
// Default mocks for stat and writeFile
|
|
11
|
-
fs.stat.mockResolvedValue({
|
|
12
|
-
isDirectory: () => true,
|
|
13
|
-
mtimeMs: 1000
|
|
14
|
-
});
|
|
15
|
-
fs.writeFile.mockResolvedValue(undefined);
|
|
16
|
-
});
|
|
17
|
-
it('should ignore imports in comments', async () => {
|
|
18
|
-
const mockFiles = ['/app/index.ts', '/app/utils.ts', '/app/oldUtils.ts'];
|
|
19
|
-
const mockContentIndex = `
|
|
20
|
-
import { something } from './utils';
|
|
21
|
-
// import { oldThing } from './oldUtils';
|
|
22
|
-
/* import { other } from './other' */
|
|
23
|
-
`;
|
|
24
|
-
const mockContentUtils = 'export const something = 1;';
|
|
25
|
-
fs.readdir.mockResolvedValue([
|
|
26
|
-
{ name: 'index.ts', isDirectory: () => false },
|
|
27
|
-
{ name: 'utils.ts', isDirectory: () => false },
|
|
28
|
-
{ name: 'oldUtils.ts', isDirectory: () => false }
|
|
29
|
-
]);
|
|
30
|
-
fs.readFile.mockImplementation(async (path) => {
|
|
31
|
-
if (path.includes('graph_cache.json'))
|
|
32
|
-
return '{}'; // Mock empty cache
|
|
33
|
-
if (path.includes('index.ts'))
|
|
34
|
-
return mockContentIndex;
|
|
35
|
-
return '';
|
|
36
|
-
});
|
|
37
|
-
// Mock resolvePath behavior indirectly by mocking existing files check in graph.build logic
|
|
38
|
-
// But since graph.ts uses fs.readdir recursively, we need to mock that structure.
|
|
39
|
-
// For simplicity in this unit test, we'll mock 'getAllFiles' if it were public,
|
|
40
|
-
// but since it's private, we have to mock fs structure carefully or rely on the implementation.
|
|
41
|
-
// Let's rely on the fact that build calls getAllFiles which calls readdir.
|
|
42
|
-
// We need to ensure 'utils.ts' and 'oldUtils.ts' resolution is tested.
|
|
43
|
-
// Actually, since we mock readFile, the file existence check in resolvePath uses "this.nodes.has".
|
|
44
|
-
// "this.nodes" is populated by getAllFiles.
|
|
45
|
-
// So we need getAllFiles to return both index.ts and utils.ts.
|
|
46
|
-
// And NOT oldUtils.ts so we can see if it tries to resolve it?
|
|
47
|
-
// Actually, if it tries to resolve 'oldUtils', it might fail if not in nodes.
|
|
48
|
-
// But the bug is that it SHOULD NOT even try to resolve 'oldUtils' because it's commented out.
|
|
49
|
-
await graph.build('/app');
|
|
50
|
-
const relationships = graph.getRelationships('/app/index.ts');
|
|
51
|
-
expect(relationships?.imports).toContain('utils.ts');
|
|
52
|
-
expect(relationships?.imports).not.toContain('oldUtils.ts');
|
|
53
|
-
});
|
|
54
|
-
it('should resolve .js imports to .ts files', async () => {
|
|
55
|
-
const mockContentIndex = `import { something } from './lib.js';`;
|
|
56
|
-
fs.readdir.mockResolvedValue([
|
|
57
|
-
{ name: 'index.ts', isDirectory: () => false },
|
|
58
|
-
{ name: 'lib.ts', isDirectory: () => false }
|
|
59
|
-
]);
|
|
60
|
-
fs.readFile.mockImplementation(async (filePath) => {
|
|
61
|
-
if (filePath.includes('graph_cache.json'))
|
|
62
|
-
return '{}';
|
|
63
|
-
if (filePath.endsWith('index.ts'))
|
|
64
|
-
return mockContentIndex;
|
|
65
|
-
return '';
|
|
66
|
-
});
|
|
67
|
-
await graph.build('/app');
|
|
68
|
-
const relationships = graph.getRelationships('/app/index.ts');
|
|
69
|
-
// The output of getRelationships.imports is relative paths.
|
|
70
|
-
// If imports ./lib.js, and we have lib.ts, it should resolve to /app/lib.ts
|
|
71
|
-
// And path.relative('/app', '/app/lib.ts') is 'lib.ts'
|
|
72
|
-
expect(relationships?.imports).toContain('lib.ts');
|
|
73
|
-
});
|
|
74
|
-
it('should resolve .js imports to .jsx files', async () => {
|
|
75
|
-
const mockContentIndex = `import { Button } from './Button.js';`;
|
|
76
|
-
fs.readdir.mockResolvedValue([
|
|
77
|
-
{ name: 'index.js', isDirectory: () => false },
|
|
78
|
-
{ name: 'Button.jsx', isDirectory: () => false }
|
|
79
|
-
]);
|
|
80
|
-
fs.readFile.mockImplementation(async (filePath) => {
|
|
81
|
-
if (filePath.includes('graph_cache.json'))
|
|
82
|
-
return '{}';
|
|
83
|
-
if (filePath.endsWith('index.js'))
|
|
84
|
-
return mockContentIndex;
|
|
85
|
-
return '';
|
|
86
|
-
});
|
|
87
|
-
await graph.build('/app');
|
|
88
|
-
const relationships = graph.getRelationships('/app/index.js');
|
|
89
|
-
expect(relationships?.imports).toContain('Button.jsx');
|
|
90
|
-
});
|
|
91
|
-
it('should handle circular dependencies', async () => {
|
|
92
|
-
const contentA = `import { b } from './b'; export const a = 1;`;
|
|
93
|
-
const contentB = `import { a } from './a'; export const b = 2;`;
|
|
94
|
-
fs.readdir.mockResolvedValue([
|
|
95
|
-
{ name: 'a.ts', isDirectory: () => false },
|
|
96
|
-
{ name: 'b.ts', isDirectory: () => false }
|
|
97
|
-
]);
|
|
98
|
-
fs.readFile.mockImplementation(async (filePath) => {
|
|
99
|
-
if (filePath.includes('graph_cache.json'))
|
|
100
|
-
return '{}';
|
|
101
|
-
if (filePath.endsWith('a.ts'))
|
|
102
|
-
return contentA;
|
|
103
|
-
if (filePath.endsWith('b.ts'))
|
|
104
|
-
return contentB;
|
|
105
|
-
return '';
|
|
106
|
-
});
|
|
107
|
-
await graph.build('/app');
|
|
108
|
-
const relA = graph.getRelationships('/app/a.ts');
|
|
109
|
-
const relB = graph.getRelationships('/app/b.ts');
|
|
110
|
-
expect(relA?.imports).toContain('b.ts');
|
|
111
|
-
expect(relA?.importedBy).toContain('b.ts');
|
|
112
|
-
expect(relB?.imports).toContain('a.ts');
|
|
113
|
-
expect(relB?.importedBy).toContain('a.ts');
|
|
114
|
-
});
|
|
115
|
-
it('should gracefully handle missing imports', async () => {
|
|
116
|
-
const contentA = `import { ghost } from './ghost';`;
|
|
117
|
-
fs.readdir.mockResolvedValue([
|
|
118
|
-
{ name: 'a.ts', isDirectory: () => false }
|
|
119
|
-
]);
|
|
120
|
-
fs.readFile.mockImplementation(async (filePath) => {
|
|
121
|
-
if (filePath.includes('graph_cache.json'))
|
|
122
|
-
return '{}';
|
|
123
|
-
if (filePath.endsWith('a.ts'))
|
|
124
|
-
return contentA;
|
|
125
|
-
return '';
|
|
126
|
-
});
|
|
127
|
-
await graph.build('/app');
|
|
128
|
-
const relA = graph.getRelationships('/app/a.ts');
|
|
129
|
-
// ghost.ts doesn't exist, so imports should be empty (filtered out)
|
|
130
|
-
expect(relA?.imports).toHaveLength(0);
|
|
131
|
-
});
|
|
132
|
-
it('should ignore directory traversal attempts outside root', async () => {
|
|
133
|
-
// If we pretend root is /app, and we try to import ../outside
|
|
134
|
-
const contentA = `import { secret } from '../secret';`;
|
|
135
|
-
fs.readdir.mockResolvedValue([
|
|
136
|
-
{ name: 'a.ts', isDirectory: () => false }
|
|
137
|
-
]);
|
|
138
|
-
fs.readFile.mockImplementation(async (filePath) => {
|
|
139
|
-
if (filePath.includes('graph_cache.json'))
|
|
140
|
-
return '{}';
|
|
141
|
-
if (filePath.endsWith('a.ts'))
|
|
142
|
-
return contentA;
|
|
143
|
-
return '';
|
|
144
|
-
});
|
|
145
|
-
await graph.build('/app');
|
|
146
|
-
const relA = graph.getRelationships('/app/a.ts');
|
|
147
|
-
// Should be empty because '../secret' is not in the scanned file list (nodes)
|
|
148
|
-
expect(relA?.imports).toHaveLength(0);
|
|
149
|
-
});
|
|
150
|
-
});
|
package/dist/graph_repro.test.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { ProjectKnowledgeGraph } from './graph.js';
|
|
3
|
-
import * as fs from 'fs/promises';
|
|
4
|
-
vi.mock('fs/promises');
|
|
5
|
-
describe('ProjectKnowledgeGraph Reproduction', () => {
|
|
6
|
-
let graph;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
graph = new ProjectKnowledgeGraph();
|
|
9
|
-
vi.resetAllMocks();
|
|
10
|
-
});
|
|
11
|
-
it('should resolve absolute imports from project root', async () => {
|
|
12
|
-
// Scenario: /app/src/feature/a.ts imports 'src/shared/b.ts'
|
|
13
|
-
const mockContentA = `import { b } from 'src/shared/b';`;
|
|
14
|
-
fs.readdir.mockResolvedValue([
|
|
15
|
-
{ name: 'src', isDirectory: () => true },
|
|
16
|
-
{ name: 'feature', isDirectory: () => true },
|
|
17
|
-
{ name: 'shared', isDirectory: () => true },
|
|
18
|
-
{ name: 'a.ts', isDirectory: () => false },
|
|
19
|
-
{ name: 'b.ts', isDirectory: () => false }
|
|
20
|
-
]);
|
|
21
|
-
// Mock getAllFiles behavior by manually setting up the file system structure conceptually
|
|
22
|
-
// Since getAllFiles is recursive and hard to mock perfectly with just readdir,
|
|
23
|
-
// we'll focus on the result of getAllFiles which populates the graph.
|
|
24
|
-
// Wait, the test uses the REAL getAllFiles, so we must mock readdir correctly.
|
|
25
|
-
// Let's simplify: Flat structure for test.
|
|
26
|
-
// Root: /app
|
|
27
|
-
// Files: /app/a.ts, /app/b.ts
|
|
28
|
-
// a.ts content: "import ... from 'b'" (no dot)
|
|
29
|
-
// OR
|
|
30
|
-
// a.ts content: "import ... from 'app/b'" (if root is /)
|
|
31
|
-
// Let's try the 'src/...' pattern which is common.
|
|
32
|
-
// Root: /root
|
|
33
|
-
// File: /root/src/index.ts -> imports 'src/utils'
|
|
34
|
-
// File: /root/src/utils.ts
|
|
35
|
-
const rootDir = '/root';
|
|
36
|
-
const indexFile = '/root/src/index.ts';
|
|
37
|
-
const utilsFile = '/root/src/utils.ts';
|
|
38
|
-
// Mock readdir to simulate:
|
|
39
|
-
// /root -> [src]
|
|
40
|
-
// /root/src -> [index.ts, utils.ts]
|
|
41
|
-
fs.readdir.mockImplementation(async (dir) => {
|
|
42
|
-
if (dir === '/root')
|
|
43
|
-
return [{ name: 'src', isDirectory: () => true }];
|
|
44
|
-
if (dir === '/root/src')
|
|
45
|
-
return [
|
|
46
|
-
{ name: 'index.ts', isDirectory: () => false },
|
|
47
|
-
{ name: 'utils.ts', isDirectory: () => false }
|
|
48
|
-
];
|
|
49
|
-
return [];
|
|
50
|
-
});
|
|
51
|
-
fs.readFile.mockImplementation(async (filePath) => {
|
|
52
|
-
if (filePath === indexFile)
|
|
53
|
-
return `import { u } from 'src/utils';`;
|
|
54
|
-
return '';
|
|
55
|
-
});
|
|
56
|
-
await graph.build(rootDir);
|
|
57
|
-
const relationships = graph.getRelationships(indexFile);
|
|
58
|
-
// We expect 'src/utils.ts' to be in imports.
|
|
59
|
-
// Note: graph.getRelationships returns relative paths.
|
|
60
|
-
// relative('/root', '/root/src/utils.ts') -> 'src/utils.ts'
|
|
61
|
-
expect(relationships?.imports).toContain('src/utils.ts');
|
|
62
|
-
});
|
|
63
|
-
});
|
package/dist/integration.test.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
// We need to import the register functions to get the callbacks,
|
|
3
|
-
// but we can just use the classes directly for this integration logic test.
|
|
4
|
-
// Actually, testing the *interaction* via the tool layer is better.
|
|
5
|
-
import { registerThinkingTools } from './tools/thinking.js';
|
|
6
|
-
import { registerGraphTools } from './tools/graph.js';
|
|
7
|
-
import { registerNoteTools } from './tools/notes.js';
|
|
8
|
-
describe('Integration Workflow', () => {
|
|
9
|
-
let toolCallbacks = {};
|
|
10
|
-
const mockServer = {
|
|
11
|
-
tool: (name, desc, schema, cb) => {
|
|
12
|
-
toolCallbacks[name] = cb;
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
// Mocks
|
|
16
|
-
const mockThinking = { processThought: vi.fn(), clearHistory: vi.fn() };
|
|
17
|
-
const mockGraph = { build: vi.fn(), getRelationships: vi.fn() };
|
|
18
|
-
const mockNotes = { addNote: vi.fn() };
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
toolCallbacks = {};
|
|
21
|
-
registerThinkingTools(mockServer, mockThinking);
|
|
22
|
-
registerGraphTools(mockServer, mockGraph);
|
|
23
|
-
registerNoteTools(mockServer, mockNotes);
|
|
24
|
-
vi.clearAllMocks();
|
|
25
|
-
});
|
|
26
|
-
it('should support a full analysis workflow', async () => {
|
|
27
|
-
// Step 1: Start Thinking
|
|
28
|
-
mockThinking.processThought.mockResolvedValue({
|
|
29
|
-
content: [{ type: "text", text: "Thought processed" }]
|
|
30
|
-
});
|
|
31
|
-
await toolCallbacks['sequentialthinking']({
|
|
32
|
-
thought: "Analyze architecture",
|
|
33
|
-
thoughtNumber: 1,
|
|
34
|
-
totalThoughts: 5,
|
|
35
|
-
nextThoughtNeeded: true,
|
|
36
|
-
thoughtType: 'analysis'
|
|
37
|
-
});
|
|
38
|
-
expect(mockThinking.processThought).toHaveBeenCalledWith(expect.objectContaining({
|
|
39
|
-
thought: "Analyze architecture"
|
|
40
|
-
}));
|
|
41
|
-
// Step 2: Build Graph
|
|
42
|
-
mockGraph.build.mockResolvedValue({ nodeCount: 10, totalFiles: 20 });
|
|
43
|
-
await toolCallbacks['build_project_graph']({ path: '.' });
|
|
44
|
-
expect(mockGraph.build).toHaveBeenCalled();
|
|
45
|
-
// Step 3: Get Relationships
|
|
46
|
-
mockGraph.getRelationships.mockReturnValue({ imports: ['utils.ts'] });
|
|
47
|
-
const relResult = await toolCallbacks['get_file_relationships']({ filePath: 'src/index.ts' });
|
|
48
|
-
expect(JSON.parse(relResult.content[0].text).imports).toContain('utils.ts');
|
|
49
|
-
// Step 4: Add Note
|
|
50
|
-
mockNotes.addNote.mockResolvedValue({ id: '123', title: 'Arch Note' });
|
|
51
|
-
await toolCallbacks['manage_notes']({
|
|
52
|
-
action: 'add',
|
|
53
|
-
title: 'Architecture Review',
|
|
54
|
-
content: 'Found circular deps'
|
|
55
|
-
});
|
|
56
|
-
expect(mockNotes.addNote).toHaveBeenCalledWith('Architecture Review', 'Found circular deps', undefined, undefined, undefined);
|
|
57
|
-
});
|
|
58
|
-
});
|
package/dist/notes.test.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
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
|
-
describe('NotesManager', () => {
|
|
6
|
-
let manager;
|
|
7
|
-
const testPath = 'test_notes.json';
|
|
8
|
-
let mockStore = '[]';
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
mockStore = '[]';
|
|
11
|
-
fs.readFile.mockImplementation(async () => mockStore);
|
|
12
|
-
fs.writeFile.mockImplementation(async (path, data) => {
|
|
13
|
-
mockStore = data;
|
|
14
|
-
});
|
|
15
|
-
manager = new NotesManager(testPath);
|
|
16
|
-
});
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
vi.clearAllMocks();
|
|
19
|
-
});
|
|
20
|
-
it('should add a note correctly', async () => {
|
|
21
|
-
const note = await manager.addNote("Test Title", "Test Content", ["tag1"]);
|
|
22
|
-
expect(note.title).toBe("Test Title");
|
|
23
|
-
expect(note.tags).toContain("tag1");
|
|
24
|
-
expect(JSON.parse(mockStore)).toHaveLength(1);
|
|
25
|
-
});
|
|
26
|
-
it('should list notes with sorting by priority', async () => {
|
|
27
|
-
await manager.addNote("Low Prio", "Content", [], "low");
|
|
28
|
-
await manager.addNote("High Prio", "Content", [], "high");
|
|
29
|
-
const notes = await manager.listNotes();
|
|
30
|
-
expect(notes[0].priority).toBe('high');
|
|
31
|
-
expect(notes[1].priority).toBe('low');
|
|
32
|
-
});
|
|
33
|
-
it('should filter notes by tag', async () => {
|
|
34
|
-
await manager.addNote("Note 1", "Content", ["react"]);
|
|
35
|
-
await manager.addNote("Note 2", "Content", ["vue"]);
|
|
36
|
-
const reactNotes = await manager.listNotes('react');
|
|
37
|
-
expect(reactNotes).toHaveLength(1);
|
|
38
|
-
expect(reactNotes[0].title).toBe("Note 1");
|
|
39
|
-
});
|
|
40
|
-
it('should search notes by query', async () => {
|
|
41
|
-
await manager.addNote("Deploy Script", "Run npm build", ["devops"]);
|
|
42
|
-
await manager.addNote("Meeting Notes", "Discuss API", ["meeting"]);
|
|
43
|
-
const results = await manager.searchNotes("build");
|
|
44
|
-
expect(results).toHaveLength(1);
|
|
45
|
-
expect(results[0].title).toBe("Deploy Script");
|
|
46
|
-
});
|
|
47
|
-
it('should update a note', async () => {
|
|
48
|
-
const note = await manager.addNote("Original", "Content");
|
|
49
|
-
const updated = await manager.updateNote(note.id, { title: "Updated" });
|
|
50
|
-
expect(updated?.title).toBe("Updated");
|
|
51
|
-
expect(updated?.content).toBe("Content"); // Should remain
|
|
52
|
-
const list = await manager.listNotes();
|
|
53
|
-
expect(list[0].title).toBe("Updated");
|
|
54
|
-
});
|
|
55
|
-
it('should delete a note', async () => {
|
|
56
|
-
const note = await manager.addNote("To Delete", "Content");
|
|
57
|
-
const success = await manager.deleteNote(note.id);
|
|
58
|
-
expect(success).toBe(true);
|
|
59
|
-
const list = await manager.listNotes();
|
|
60
|
-
expect(list).toHaveLength(0);
|
|
61
|
-
});
|
|
62
|
-
it('should hide expired notes by default', async () => {
|
|
63
|
-
// Expired yesterday
|
|
64
|
-
const yesterday = new Date();
|
|
65
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
66
|
-
await manager.addNote("Expired", "Content", [], "medium", yesterday.toISOString());
|
|
67
|
-
await manager.addNote("Active", "Content");
|
|
68
|
-
const list = await manager.listNotes();
|
|
69
|
-
expect(list).toHaveLength(1);
|
|
70
|
-
expect(list[0].title).toBe("Active");
|
|
71
|
-
const all = await manager.listNotes(undefined, true);
|
|
72
|
-
expect(all).toHaveLength(2);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
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
|
-
});
|
package/dist/repro_dollar.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { registerFileSystemTools } from './tools/filesystem.js';
|
|
3
|
-
// Mock server
|
|
4
|
-
const server = new McpServer({ name: "test", version: "1" });
|
|
5
|
-
// Mock tool registration to capture the handler
|
|
6
|
-
let editHandler;
|
|
7
|
-
server.tool = (name, desc, schema, handler) => {
|
|
8
|
-
if (name === 'edit_file')
|
|
9
|
-
editHandler = handler;
|
|
10
|
-
return undefined;
|
|
11
|
-
};
|
|
12
|
-
registerFileSystemTools(server);
|
|
13
|
-
async function test() {
|
|
14
|
-
console.log("Testing edit_file with dollar signs...");
|
|
15
|
-
const result = await editHandler({
|
|
16
|
-
path: 'test_dollar.txt',
|
|
17
|
-
oldText: 'OLD',
|
|
18
|
-
newText: '$100' // This usually becomes empty or weird if interpreted as regex replacement
|
|
19
|
-
});
|
|
20
|
-
const fs = await import('fs/promises');
|
|
21
|
-
const content = await fs.readFile('test_dollar.txt', 'utf-8');
|
|
22
|
-
console.log("File content:", content.trim());
|
|
23
|
-
if (content.trim() === 'Price: $100') {
|
|
24
|
-
console.log("PASS: $ preserved");
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
console.log("FAIL: $ corrupted");
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
test();
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
async function test() {
|
|
2
|
-
console.log("Testing edit_file logic...");
|
|
3
|
-
// Simulate the logic in filesystem.ts
|
|
4
|
-
const content = "Price: OLD";
|
|
5
|
-
const oldText = "OLD";
|
|
6
|
-
const newText = "$&"; // Should be "$&" literally, but replace will make it "OLD"
|
|
7
|
-
// Logic from tool
|
|
8
|
-
// If allowMultiple is false, it passes string directly
|
|
9
|
-
const buggedResult = content.replace(oldText, newText);
|
|
10
|
-
console.log(`Original: "${content}"`);
|
|
11
|
-
console.log(`Old: "${oldText}"`);
|
|
12
|
-
console.log(`New: "${newText}"`);
|
|
13
|
-
console.log(`Result: "${buggedResult}"`);
|
|
14
|
-
if (buggedResult === "Price: OLD") {
|
|
15
|
-
console.log("FAIL: $& was interpreted as the matched string");
|
|
16
|
-
}
|
|
17
|
-
else if (buggedResult === "Price: $&") {
|
|
18
|
-
console.log("PASS: $& was preserved literally");
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
test();
|
|
22
|
-
export {};
|
package/dist/repro_history.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { SequentialThinkingServer } from './lib.js';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
async function test() {
|
|
4
|
-
const server = new SequentialThinkingServer('test_history_bug.json');
|
|
5
|
-
await server.clearHistory();
|
|
6
|
-
// Create thoughts: 1, 2, 3, 4
|
|
7
|
-
await server.processThought({ thought: "1", thoughtNumber: 1, totalThoughts: 4, nextThoughtNeeded: true });
|
|
8
|
-
await server.processThought({ thought: "2", thoughtNumber: 2, totalThoughts: 4, nextThoughtNeeded: true });
|
|
9
|
-
await server.processThought({ thought: "3", thoughtNumber: 3, totalThoughts: 4, nextThoughtNeeded: true });
|
|
10
|
-
// Thought 4 branches from 3
|
|
11
|
-
await server.processThought({
|
|
12
|
-
thought: "4",
|
|
13
|
-
thoughtNumber: 4,
|
|
14
|
-
totalThoughts: 4,
|
|
15
|
-
nextThoughtNeeded: false,
|
|
16
|
-
branchFromThought: 3,
|
|
17
|
-
branchId: "test"
|
|
18
|
-
});
|
|
19
|
-
// Verify initial state
|
|
20
|
-
// History: [1, 2, 3, 4(from 3)]
|
|
21
|
-
// Summarize 2-3
|
|
22
|
-
await server.archiveHistory(2, 3, "Summary of 2 and 3");
|
|
23
|
-
// Expected History: [1, Summary(2), 4(3)]
|
|
24
|
-
// Thought 4 should now contain "branchFromThought: 2" (pointing to summary)
|
|
25
|
-
// Or if it broke, it might still say 3 (which is itself!) or not be updated.
|
|
26
|
-
// Read file manually to check JSON content
|
|
27
|
-
const data = JSON.parse(await fs.readFile('test_history_bug.json', 'utf-8'));
|
|
28
|
-
const lastThought = data[data.length - 1];
|
|
29
|
-
console.log("Last Thought:", lastThought);
|
|
30
|
-
if (lastThought.branchFromThought === 3) {
|
|
31
|
-
console.log("FAIL: branchFromThought was NOT updated. It points to 3 (which is now itself).");
|
|
32
|
-
}
|
|
33
|
-
else if (lastThought.branchFromThought === 2) {
|
|
34
|
-
console.log("PASS: branchFromThought was updated to point to Summary.");
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
console.log(`FAIL: branchFromThought is ${lastThought.branchFromThought} (Unknown state)`);
|
|
38
|
-
}
|
|
39
|
-
await fs.unlink('test_history_bug.json');
|
|
40
|
-
}
|
|
41
|
-
test();
|
package/dist/repro_path.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
async function test() {
|
|
3
|
-
console.log("Testing path traversal vulnerability...");
|
|
4
|
-
const cwd = process.cwd();
|
|
5
|
-
const maliciousPath = '/etc/passwd'; // Or any file outside cwd
|
|
6
|
-
const resolved = path.resolve(maliciousPath);
|
|
7
|
-
console.log(`CWD: ${cwd}`);
|
|
8
|
-
console.log(`Input: ${maliciousPath}`);
|
|
9
|
-
console.log(`Resolved: ${resolved}`);
|
|
10
|
-
if (!resolved.startsWith(cwd)) {
|
|
11
|
-
console.log("FAIL: Path is outside CWD and would be allowed by current logic.");
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
console.log("PASS: Path is inside CWD.");
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
test();
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
async function searchCodeLogic(pattern, searchPath = '.') {
|
|
5
|
-
try {
|
|
6
|
-
const resolvedPath = path.resolve(searchPath);
|
|
7
|
-
const stat = await fs.stat(resolvedPath);
|
|
8
|
-
if (stat.isFile()) {
|
|
9
|
-
const content = await fs.readFile(resolvedPath, 'utf-8');
|
|
10
|
-
if (content.includes(pattern)) {
|
|
11
|
-
return {
|
|
12
|
-
content: [{ type: "text", text: `Found "${pattern}" in:\n${resolvedPath}` }]
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
return {
|
|
17
|
-
content: [{ type: "text", text: `No matches found for "${pattern}"` }]
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
async function searchDir(dir) {
|
|
22
|
-
const results = [];
|
|
23
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
24
|
-
for (const entry of entries) {
|
|
25
|
-
const fullPath = path.join(dir, entry.name);
|
|
26
|
-
if (entry.isDirectory()) {
|
|
27
|
-
if (['node_modules', '.git', 'dist', 'coverage', '.gemini'].includes(entry.name))
|
|
28
|
-
continue;
|
|
29
|
-
results.push(...await searchDir(fullPath));
|
|
30
|
-
}
|
|
31
|
-
else if (new RegExp('\\.(ts|js|json|md|txt|html|css|py|java|c|cpp|h|rs|go)$').test(entry.name)) {
|
|
32
|
-
const content = await fs.readFile(fullPath, 'utf-8');
|
|
33
|
-
if (content.includes(pattern)) {
|
|
34
|
-
results.push(fullPath);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return results;
|
|
39
|
-
}
|
|
40
|
-
const matches = await searchDir(resolvedPath);
|
|
41
|
-
const joinedMatches = matches.join('\n');
|
|
42
|
-
return {
|
|
43
|
-
content: [{
|
|
44
|
-
type: "text",
|
|
45
|
-
text: matches.length > 0 ? `Found "${pattern}" in:\n${joinedMatches}` : `No matches found for "${pattern}"`
|
|
46
|
-
}]
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
return {
|
|
51
|
-
content: [{ type: "text", text: `Search Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
52
|
-
isError: true
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
describe('search_code tool', () => {
|
|
57
|
-
const testDir = path.join(__dirname, 'test_search_env');
|
|
58
|
-
beforeEach(async () => {
|
|
59
|
-
await fs.mkdir(testDir, { recursive: true });
|
|
60
|
-
await fs.writeFile(path.join(testDir, 'target.ts'), 'function myFunction() { return "found me"; }');
|
|
61
|
-
await fs.writeFile(path.join(testDir, 'other.ts'), 'const x = 10;');
|
|
62
|
-
await fs.mkdir(path.join(testDir, 'nested'), { recursive: true });
|
|
63
|
-
await fs.writeFile(path.join(testDir, 'nested', 'deep.ts'), 'export const secret = "found me too";');
|
|
64
|
-
await fs.mkdir(path.join(testDir, 'node_modules'), { recursive: true });
|
|
65
|
-
await fs.writeFile(path.join(testDir, 'node_modules', 'ignored.ts'), 'found me');
|
|
66
|
-
});
|
|
67
|
-
afterEach(async () => {
|
|
68
|
-
await fs.rm(testDir, { recursive: true, force: true });
|
|
69
|
-
});
|
|
70
|
-
// ... (previous tests)
|
|
71
|
-
it('should handle single file path', async () => {
|
|
72
|
-
// This test will FAIL with current implementation (simulated here with the fix applied? No, I need to test failure first)
|
|
73
|
-
// Wait, I updated searchCodeLogic above with the FIX.
|
|
74
|
-
// So this test checks if the FIX works.
|
|
75
|
-
const result = await searchCodeLogic('found me', path.join(testDir, 'target.ts'));
|
|
76
|
-
expect(result.isError).toBeUndefined();
|
|
77
|
-
expect(result.content[0].text).toContain('target.ts');
|
|
78
|
-
});
|
|
79
|
-
});
|
package/dist/repro_ts_req.js
DELETED