@gotza02/sequential-thinking 2026.2.30 → 2026.2.32
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 +1 -0
- package/SYSTEM_INSTRUCTION.md +8 -1
- package/dist/codestore.d.ts +102 -2
- package/dist/codestore.js +499 -17
- package/dist/graph.d.ts +10 -1
- package/dist/graph.js +471 -93
- package/dist/http-server.d.ts +15 -0
- package/dist/http-server.js +732 -70
- package/dist/lib.js +84 -28
- package/dist/notes.d.ts +39 -2
- package/dist/notes.js +233 -24
- package/dist/system_test.js +1 -1
- package/dist/tools/codestore.js +1 -1
- package/dist/tools/filesystem.js +359 -40
- package/dist/tools/thinking.js +7 -1
- package/package.json +1 -1
package/dist/tools/filesystem.js
CHANGED
|
@@ -2,15 +2,114 @@ import { z } from "zod";
|
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { execAsync, validatePath } from "../utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Default file extensions to search
|
|
7
|
+
* Covers common programming languages, config files, and text formats
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_SEARCH_EXTENSIONS = [
|
|
10
|
+
// Programming languages
|
|
11
|
+
'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',
|
|
12
|
+
'py', 'pyw', 'pyi',
|
|
13
|
+
'java', 'kt', 'kts', 'scala',
|
|
14
|
+
'c', 'cpp', 'cc', 'cxx', 'h', 'hpp', 'hxx',
|
|
15
|
+
'cs', 'vb',
|
|
16
|
+
'go', 'rs',
|
|
17
|
+
'php', 'phtml',
|
|
18
|
+
'rb', 'gemspec',
|
|
19
|
+
'swift', 'm', 'mm',
|
|
20
|
+
'dart',
|
|
21
|
+
'lua', 'pl', 'pm',
|
|
22
|
+
'r', 'R',
|
|
23
|
+
'sh', 'bash', 'zsh', 'fish', 'ps1',
|
|
24
|
+
// Markup & Data
|
|
25
|
+
'html', 'htm', 'xhtml', 'xml', 'svg',
|
|
26
|
+
'css', 'scss', 'sass', 'less',
|
|
27
|
+
'json', 'yaml', 'yml', 'toml', 'ini', 'conf', 'cfg',
|
|
28
|
+
'md', 'markdown', 'rst', 'txt',
|
|
29
|
+
'vue', 'svelte', 'jsx',
|
|
30
|
+
// Config & Build
|
|
31
|
+
'gradle', 'pom', 'xml', // Java build
|
|
32
|
+
'cabal', // Haskell
|
|
33
|
+
'csproj', 'sln', // C#
|
|
34
|
+
'pro', // Lua
|
|
35
|
+
'gemfile', 'rake', // Ruby
|
|
36
|
+
'dockerfile', 'makefile', 'cmake',
|
|
37
|
+
'env', 'env.example',
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Default directories to skip during search
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_SKIP_DIRS = [
|
|
43
|
+
'node_modules',
|
|
44
|
+
'.git',
|
|
45
|
+
'.svn',
|
|
46
|
+
'.hg',
|
|
47
|
+
'dist',
|
|
48
|
+
'build',
|
|
49
|
+
'out',
|
|
50
|
+
'coverage',
|
|
51
|
+
'.nyc_output',
|
|
52
|
+
'.gemini',
|
|
53
|
+
'.next',
|
|
54
|
+
'.nuxt',
|
|
55
|
+
'.cache',
|
|
56
|
+
'.turbo',
|
|
57
|
+
'.parcel',
|
|
58
|
+
'.vscode',
|
|
59
|
+
'.idea',
|
|
60
|
+
'vendor',
|
|
61
|
+
'target',
|
|
62
|
+
'bin',
|
|
63
|
+
'obj',
|
|
64
|
+
'__pycache__',
|
|
65
|
+
'venv',
|
|
66
|
+
'.venv',
|
|
67
|
+
'virtualenv',
|
|
68
|
+
'node_modules_cache',
|
|
69
|
+
'.tsbuildinfo',
|
|
70
|
+
'tmp',
|
|
71
|
+
'temp',
|
|
72
|
+
'.tmp'
|
|
73
|
+
];
|
|
74
|
+
/**
|
|
75
|
+
* Binary file extensions to always skip
|
|
76
|
+
*/
|
|
77
|
+
const BINARY_EXTENSIONS = [
|
|
78
|
+
'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', 'svg',
|
|
79
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
80
|
+
'zip', 'tar', 'gz', 'bz2', 'rar', '7z',
|
|
81
|
+
'exe', 'dll', 'so', 'dylib', 'lib', 'a',
|
|
82
|
+
'mp3', 'mp4', 'wav', 'ogg', 'flac',
|
|
83
|
+
'ttf', 'otf', 'woff', 'woff2', 'eot',
|
|
84
|
+
'class', 'jar', 'war', 'ear',
|
|
85
|
+
'pyc', 'pyo',
|
|
86
|
+
'node'
|
|
87
|
+
];
|
|
5
88
|
export function registerFileSystemTools(server) {
|
|
6
|
-
//
|
|
89
|
+
// =========================================================================
|
|
90
|
+
// SHELL EXECUTE
|
|
91
|
+
// =========================================================================
|
|
7
92
|
server.tool("shell_execute", "Execute a shell command. SECURITY WARNING: Use this ONLY for safe, non-destructive commands. Avoid 'rm -rf /', format, or destructive operations.", {
|
|
8
93
|
command: z.string().describe("The bash command to execute")
|
|
9
94
|
}, async ({ command }) => {
|
|
10
|
-
|
|
95
|
+
// Dangerous command patterns to block
|
|
96
|
+
const dangerousPatterns = [
|
|
97
|
+
new RegExp('rm\\s+-rf?\\s+[\\/~]'), // rm -rf /, rm -rf ~
|
|
98
|
+
new RegExp('rm\\s+-rf?\\s+\\.'), // rm -rf .
|
|
99
|
+
/mkfs/, // Format filesystem
|
|
100
|
+
/dd\s+if=/, // dd direct disk write
|
|
101
|
+
/chmod\s+000\s+\//, // chmod 000 /
|
|
102
|
+
/chown\s+-R\s+root:/, // chown -R root:
|
|
103
|
+
new RegExp('.*>.*\\\\/dev\\\\/.*\\\\s+\\\\w'), // Redirect to device with write
|
|
104
|
+
/fdisk/, // Partition manipulation
|
|
105
|
+
/format/, // Windows format command
|
|
106
|
+
];
|
|
11
107
|
if (dangerousPatterns.some(p => p.test(command))) {
|
|
12
108
|
return {
|
|
13
|
-
content: [{
|
|
109
|
+
content: [{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: "Error: Dangerous command pattern detected. Execution blocked for safety."
|
|
112
|
+
}],
|
|
14
113
|
isError: true
|
|
15
114
|
};
|
|
16
115
|
}
|
|
@@ -25,12 +124,17 @@ export function registerFileSystemTools(server) {
|
|
|
25
124
|
}
|
|
26
125
|
catch (error) {
|
|
27
126
|
return {
|
|
28
|
-
content: [{
|
|
127
|
+
content: [{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: `Shell Error: ${error instanceof Error ? error.message : String(error)}`
|
|
130
|
+
}],
|
|
29
131
|
isError: true
|
|
30
132
|
};
|
|
31
133
|
}
|
|
32
134
|
});
|
|
33
|
-
//
|
|
135
|
+
// =========================================================================
|
|
136
|
+
// READ FILE
|
|
137
|
+
// =========================================================================
|
|
34
138
|
server.tool("read_file", "Read the contents of a file.", {
|
|
35
139
|
path: z.string().describe("Path to the file")
|
|
36
140
|
}, async ({ path }) => {
|
|
@@ -43,64 +147,150 @@ export function registerFileSystemTools(server) {
|
|
|
43
147
|
}
|
|
44
148
|
catch (error) {
|
|
45
149
|
return {
|
|
46
|
-
content: [{
|
|
150
|
+
content: [{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: `Read Error: ${error instanceof Error ? error.message : String(error)}`
|
|
153
|
+
}],
|
|
47
154
|
isError: true
|
|
48
155
|
};
|
|
49
156
|
}
|
|
50
157
|
});
|
|
51
|
-
//
|
|
52
|
-
|
|
158
|
+
// =========================================================================
|
|
159
|
+
// WRITE FILE
|
|
160
|
+
// =========================================================================
|
|
161
|
+
server.tool("write_file", "Write content to a file (overwrites existing). Use with caution.", {
|
|
53
162
|
path: z.string().describe("Path to the file"),
|
|
54
163
|
content: z.string().describe("Content to write")
|
|
55
164
|
}, async ({ path, content }) => {
|
|
56
165
|
try {
|
|
57
166
|
const safePath = validatePath(path);
|
|
167
|
+
// Additional safety: don't overwrite system directories
|
|
168
|
+
const systemDirs = ['/etc', '/usr', '/bin', '/sbin', '/boot', '/lib', '/lib64'];
|
|
169
|
+
const isSystemPath = systemDirs.some(sysDir => safePath.startsWith(sysDir));
|
|
170
|
+
if (isSystemPath) {
|
|
171
|
+
return {
|
|
172
|
+
content: [{
|
|
173
|
+
type: "text",
|
|
174
|
+
text: `Error: Cannot write to system directory: ${safePath}`
|
|
175
|
+
}],
|
|
176
|
+
isError: true
|
|
177
|
+
};
|
|
178
|
+
}
|
|
58
179
|
await fs.writeFile(safePath, content, 'utf-8');
|
|
59
180
|
return {
|
|
60
|
-
content: [{
|
|
181
|
+
content: [{
|
|
182
|
+
type: "text",
|
|
183
|
+
text: `Successfully wrote to ${safePath} (${content.length} bytes)`
|
|
184
|
+
}]
|
|
61
185
|
};
|
|
62
186
|
}
|
|
63
187
|
catch (error) {
|
|
64
188
|
return {
|
|
65
|
-
content: [{
|
|
189
|
+
content: [{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: `Write Error: ${error instanceof Error ? error.message : String(error)}`
|
|
192
|
+
}],
|
|
66
193
|
isError: true
|
|
67
194
|
};
|
|
68
195
|
}
|
|
69
196
|
});
|
|
70
|
-
//
|
|
71
|
-
|
|
197
|
+
// =========================================================================
|
|
198
|
+
// SEARCH CODE
|
|
199
|
+
// =========================================================================
|
|
200
|
+
server.tool("search_code", "Search for a text pattern in project files with advanced options (regex, case sensitivity, file filtering).", {
|
|
72
201
|
pattern: z.string().describe("The text or regex pattern to search for"),
|
|
73
202
|
path: z.string().optional().default('.').describe("Root directory to search"),
|
|
74
203
|
useRegex: z.boolean().optional().default(false).describe("Treat pattern as a regular expression"),
|
|
75
|
-
caseSensitive: z.boolean().optional().default(false).describe("Match case sensitive
|
|
76
|
-
|
|
204
|
+
caseSensitive: z.boolean().optional().default(false).describe("Match case sensitive"),
|
|
205
|
+
extensions: z.string().optional().describe("Comma-separated file extensions to search (e.g., 'ts,js,py'). Default: common code files"),
|
|
206
|
+
excludeExtensions: z.string().optional().describe("Comma-separated extensions to exclude (e.g., 'md,txt')"),
|
|
207
|
+
skipDirs: z.string().optional().describe("Comma-separated directories to skip (e.g., 'dist,test')"),
|
|
208
|
+
allFiles: z.boolean().optional().default(false).describe("Search all text files (ignores extension filter)"),
|
|
209
|
+
contextLines: z.number().optional().describe("Number of context lines to show around matches"),
|
|
210
|
+
maxResults: z.number().optional().default(1000).describe("Maximum number of matches to return")
|
|
211
|
+
}, async ({ pattern, path: searchPath, useRegex, caseSensitive, extensions, excludeExtensions, skipDirs, allFiles, contextLines, maxResults }) => {
|
|
77
212
|
try {
|
|
78
213
|
const resolvedPath = validatePath(searchPath || '.');
|
|
79
214
|
const stats = await fs.stat(resolvedPath);
|
|
80
215
|
const results = [];
|
|
216
|
+
let matchCount = 0;
|
|
217
|
+
// Parse extensions
|
|
218
|
+
const includedExts = extensions
|
|
219
|
+
? extensions.toLowerCase().split(',').map(e => e.trim().replace(/^\./, ''))
|
|
220
|
+
: DEFAULT_SEARCH_EXTENSIONS;
|
|
221
|
+
const excludedExts = excludeExtensions
|
|
222
|
+
? excludeExtensions.toLowerCase().split(',').map(e => e.trim().replace(/^\./, ''))
|
|
223
|
+
: BINARY_EXTENSIONS;
|
|
224
|
+
const skipDirList = skipDirs
|
|
225
|
+
? skipDirs.split(',').map(d => d.trim())
|
|
226
|
+
: DEFAULT_SKIP_DIRS;
|
|
227
|
+
const shouldSearchFile = (fileName) => {
|
|
228
|
+
const ext = fileName.includes('.') ? fileName.split('.').pop()?.toLowerCase() : '';
|
|
229
|
+
const baseName = fileName.split('/').pop() || '';
|
|
230
|
+
// Always skip binary extensions
|
|
231
|
+
if (ext && excludedExts.includes(ext)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
// Skip common binary/temp files by name
|
|
235
|
+
if (baseName.startsWith('.') || baseName.endsWith('~')) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (allFiles) {
|
|
239
|
+
// For all files, still skip known binary types
|
|
240
|
+
return !BINARY_EXTENSIONS.includes(ext || '');
|
|
241
|
+
}
|
|
242
|
+
// Use extension filter
|
|
243
|
+
return ext ? includedExts.includes(ext) : false;
|
|
244
|
+
};
|
|
245
|
+
const shouldSkipDir = (dirName) => {
|
|
246
|
+
return skipDirList.includes(dirName);
|
|
247
|
+
};
|
|
81
248
|
const searchFile = async (filePath) => {
|
|
249
|
+
if (matchCount >= maxResults)
|
|
250
|
+
return;
|
|
82
251
|
try {
|
|
83
252
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
253
|
+
// Skip binary-like files (high proportion of non-printable chars)
|
|
254
|
+
const nonPrintableRatio = (content.match(/[\x00-\x08\x0E-\x1F]/g) || []).length / content.length;
|
|
255
|
+
if (nonPrintableRatio > 0.3) {
|
|
256
|
+
return; // Likely binary file
|
|
257
|
+
}
|
|
84
258
|
const lines = content.split('\n');
|
|
85
259
|
let regex;
|
|
86
260
|
if (useRegex) {
|
|
87
261
|
regex = new RegExp(pattern, caseSensitive ? 'g' : 'gi');
|
|
88
262
|
}
|
|
89
263
|
else {
|
|
90
|
-
|
|
91
|
-
const escaped = pattern.replace(/[.*+?^${}()|[\\]/g, '\\$&');
|
|
264
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
92
265
|
regex = new RegExp(escaped, caseSensitive ? 'g' : 'gi');
|
|
93
266
|
}
|
|
94
267
|
lines.forEach((line, index) => {
|
|
268
|
+
if (matchCount >= maxResults)
|
|
269
|
+
return;
|
|
270
|
+
regex.lastIndex = 0;
|
|
95
271
|
if (regex.test(line)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
272
|
+
matchCount++;
|
|
273
|
+
const lineNum = index + 1;
|
|
274
|
+
const trimmedLine = line.trim();
|
|
275
|
+
if (contextLines && contextLines > 0) {
|
|
276
|
+
// Include context lines
|
|
277
|
+
const start = Math.max(0, index - contextLines);
|
|
278
|
+
const end = Math.min(lines.length - 1, index + contextLines);
|
|
279
|
+
let context = '';
|
|
280
|
+
for (let i = start; i <= end; i++) {
|
|
281
|
+
const prefix = i === index ? '>' : ' ';
|
|
282
|
+
context += `${prefix} ${i + 1}: ${lines[i]}\n`;
|
|
283
|
+
}
|
|
284
|
+
results.push(`${filePath}:\n${context}`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
results.push(`${filePath}:${lineNum}: ${trimmedLine}`);
|
|
288
|
+
}
|
|
99
289
|
}
|
|
100
290
|
});
|
|
101
291
|
}
|
|
102
292
|
catch (err) {
|
|
103
|
-
// Ignore read errors (binary files etc)
|
|
293
|
+
// Ignore read errors (binary files, permissions, etc.)
|
|
104
294
|
}
|
|
105
295
|
};
|
|
106
296
|
if (stats.isFile()) {
|
|
@@ -108,73 +298,202 @@ export function registerFileSystemTools(server) {
|
|
|
108
298
|
}
|
|
109
299
|
else {
|
|
110
300
|
const searchDir = async (dir) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
301
|
+
try {
|
|
302
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
303
|
+
for (const entry of entries) {
|
|
304
|
+
if (matchCount >= maxResults)
|
|
305
|
+
break;
|
|
306
|
+
const fullPath = path.join(dir, entry.name);
|
|
307
|
+
if (entry.isDirectory()) {
|
|
308
|
+
if (shouldSkipDir(entry.name))
|
|
309
|
+
continue;
|
|
310
|
+
await searchDir(fullPath);
|
|
311
|
+
}
|
|
312
|
+
else if (entry.isFile()) {
|
|
313
|
+
if (shouldSearchFile(entry.name)) {
|
|
314
|
+
await searchFile(fullPath);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
121
317
|
}
|
|
122
318
|
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
// Skip directories we can't read
|
|
321
|
+
}
|
|
123
322
|
};
|
|
124
323
|
await searchDir(resolvedPath);
|
|
125
324
|
}
|
|
325
|
+
const responseText = results.length > 0
|
|
326
|
+
? `Found ${Math.min(matchCount, maxResults)} match${matchCount > 1 ? 'es' : ''} for "${pattern}":\n\n${results.slice(0, maxResults).join('\n\n')}`
|
|
327
|
+
: `No matches found for "${pattern}"`;
|
|
126
328
|
return {
|
|
127
329
|
content: [{
|
|
128
330
|
type: "text",
|
|
129
|
-
text:
|
|
331
|
+
text: responseText
|
|
130
332
|
}]
|
|
131
333
|
};
|
|
132
334
|
}
|
|
133
335
|
catch (error) {
|
|
134
336
|
return {
|
|
135
|
-
content: [{
|
|
337
|
+
content: [{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: `Search Error: ${error instanceof Error ? error.message : String(error)}`
|
|
340
|
+
}],
|
|
136
341
|
isError: true
|
|
137
342
|
};
|
|
138
343
|
}
|
|
139
344
|
});
|
|
140
|
-
//
|
|
345
|
+
// =========================================================================
|
|
346
|
+
// EDIT FILE
|
|
347
|
+
// =========================================================================
|
|
141
348
|
server.tool("edit_file", "Replace a specific string in a file with a new string. Use this for surgical edits to avoid overwriting the whole file.", {
|
|
142
349
|
path: z.string().describe("Path to the file"),
|
|
143
350
|
oldText: z.string().describe("The exact text segment to replace"),
|
|
144
351
|
newText: z.string().describe("The new text to insert"),
|
|
145
|
-
allowMultiple: z.boolean().optional().default(false).describe("Allow replacing multiple occurrences
|
|
352
|
+
allowMultiple: z.boolean().optional().default(false).describe("Allow replacing multiple occurrences")
|
|
146
353
|
}, async ({ path, oldText, newText, allowMultiple }) => {
|
|
147
354
|
try {
|
|
148
355
|
const safePath = validatePath(path);
|
|
356
|
+
// Additional safety: don't edit system files
|
|
357
|
+
const systemDirs = ['/etc', '/usr', '/bin', '/sbin', '/boot', '/lib', '/lib64'];
|
|
358
|
+
const isSystemPath = systemDirs.some(sysDir => safePath.startsWith(sysDir));
|
|
359
|
+
if (isSystemPath) {
|
|
360
|
+
return {
|
|
361
|
+
content: [{
|
|
362
|
+
type: "text",
|
|
363
|
+
text: `Error: Cannot edit system directory file: ${safePath}`
|
|
364
|
+
}],
|
|
365
|
+
isError: true
|
|
366
|
+
};
|
|
367
|
+
}
|
|
149
368
|
const content = await fs.readFile(safePath, 'utf-8');
|
|
150
369
|
// Check occurrences
|
|
151
|
-
|
|
152
|
-
const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\\]/g, '\\$&');
|
|
370
|
+
const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
153
371
|
const regex = new RegExp(escapeRegExp(oldText), 'g');
|
|
154
372
|
const matchCount = (content.match(regex) || []).length;
|
|
155
373
|
if (matchCount === 0) {
|
|
156
374
|
return {
|
|
157
|
-
content: [{
|
|
375
|
+
content: [{
|
|
376
|
+
type: "text",
|
|
377
|
+
text: "Error: 'oldText' not found in the file. Please ensure exact matching (including whitespace/indentation)."
|
|
378
|
+
}],
|
|
158
379
|
isError: true
|
|
159
380
|
};
|
|
160
381
|
}
|
|
161
382
|
if (matchCount > 1 && !allowMultiple) {
|
|
162
383
|
return {
|
|
163
|
-
content: [{
|
|
384
|
+
content: [{
|
|
385
|
+
type: "text",
|
|
386
|
+
text: `Error: Found ${matchCount} occurrences of 'oldText'. Set 'allowMultiple' to true if you intend to replace all, or provide more unique context in 'oldText'.\n\nTip: Include more surrounding context to make the match unique.`
|
|
387
|
+
}],
|
|
164
388
|
isError: true
|
|
165
389
|
};
|
|
166
390
|
}
|
|
167
391
|
const newContent = content.replace(allowMultiple ? regex : oldText, () => newText);
|
|
168
392
|
await fs.writeFile(safePath, newContent, 'utf-8');
|
|
169
393
|
return {
|
|
170
|
-
content: [{
|
|
394
|
+
content: [{
|
|
395
|
+
type: "text",
|
|
396
|
+
text: `Successfully replaced ${allowMultiple ? matchCount : 1} occurrence(s) in ${safePath}\n\nSize changed: ${content.length} → ${newContent.length} bytes`
|
|
397
|
+
}]
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
return {
|
|
402
|
+
content: [{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: `Edit Error: ${error instanceof Error ? error.message : String(error)}`
|
|
405
|
+
}],
|
|
406
|
+
isError: true
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// =========================================================================
|
|
411
|
+
// LIST DIRECTORY
|
|
412
|
+
// =========================================================================
|
|
413
|
+
server.tool("list_directory", "List files and directories in a given path.", {
|
|
414
|
+
path: z.string().optional().default('.').describe("Path to the directory"),
|
|
415
|
+
showHidden: z.boolean().optional().default(false).describe("Show hidden files/directories"),
|
|
416
|
+
includeStats: z.boolean().optional().default(false).describe("Include file statistics (size, mtime)")
|
|
417
|
+
}, async ({ path: dirPath, showHidden, includeStats }) => {
|
|
418
|
+
try {
|
|
419
|
+
const safePath = validatePath(dirPath || '.');
|
|
420
|
+
const stats = await fs.stat(safePath);
|
|
421
|
+
if (!stats.isDirectory()) {
|
|
422
|
+
return {
|
|
423
|
+
content: [{
|
|
424
|
+
type: "text",
|
|
425
|
+
text: `Error: ${safePath} is not a directory`
|
|
426
|
+
}],
|
|
427
|
+
isError: true
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const entries = await fs.readdir(safePath, { withFileTypes: true });
|
|
431
|
+
let result = '';
|
|
432
|
+
for (const entry of entries) {
|
|
433
|
+
// Skip hidden files unless requested
|
|
434
|
+
if (!showHidden && entry.name.startsWith('.')) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const fullPath = path.join(safePath, entry.name);
|
|
438
|
+
const icon = entry.isDirectory() ? '📁 ' : entry.isSymbolicLink() ? '🔗 ' : '📄 ';
|
|
439
|
+
let line = `${icon}${entry.name}`;
|
|
440
|
+
if (includeStats) {
|
|
441
|
+
try {
|
|
442
|
+
const entryStats = await fs.stat(fullPath);
|
|
443
|
+
const size = entryStats.size;
|
|
444
|
+
const mtime = entryStats.mtime.toISOString();
|
|
445
|
+
line += ` (${size} bytes, ${mtime})`;
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
// Skip stats for files we can't read
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
result += line + '\n';
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
content: [{
|
|
455
|
+
type: "text",
|
|
456
|
+
text: result || `(empty directory)`
|
|
457
|
+
}]
|
|
171
458
|
};
|
|
172
459
|
}
|
|
173
460
|
catch (error) {
|
|
174
461
|
return {
|
|
175
|
-
content: [{
|
|
462
|
+
content: [{
|
|
463
|
+
type: "text",
|
|
464
|
+
text: `List Error: ${error instanceof Error ? error.message : String(error)}`
|
|
465
|
+
}],
|
|
176
466
|
isError: true
|
|
177
467
|
};
|
|
178
468
|
}
|
|
179
469
|
});
|
|
470
|
+
// =========================================================================
|
|
471
|
+
// FILE EXISTS
|
|
472
|
+
// =========================================================================
|
|
473
|
+
server.tool("file_exists", "Check if a file or directory exists.", {
|
|
474
|
+
path: z.string().describe("Path to check")
|
|
475
|
+
}, async ({ path }) => {
|
|
476
|
+
try {
|
|
477
|
+
const safePath = validatePath(path);
|
|
478
|
+
await fs.access(safePath);
|
|
479
|
+
const stats = await fs.stat(safePath);
|
|
480
|
+
const type = stats.isDirectory() ? 'directory' :
|
|
481
|
+
stats.isFile() ? 'file' :
|
|
482
|
+
stats.isSymbolicLink() ? 'symlink' : 'unknown';
|
|
483
|
+
return {
|
|
484
|
+
content: [{
|
|
485
|
+
type: "text",
|
|
486
|
+
text: `Exists: ${safePath} (${type})`
|
|
487
|
+
}]
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
return {
|
|
492
|
+
content: [{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: `Does not exist: ${path}`
|
|
495
|
+
}]
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
});
|
|
180
499
|
}
|
package/dist/tools/thinking.js
CHANGED
|
@@ -66,7 +66,13 @@ If stuck in a loop:
|
|
|
66
66
|
1. Do NOT continue linear thoughts
|
|
67
67
|
2. Create a NEW BRANCH ('branchFromThought') from before the error
|
|
68
68
|
3. State "Stuck detected, branching to explore Approach B"
|
|
69
|
-
4. Change your assumptions completely
|
|
69
|
+
4. Change your assumptions completely
|
|
70
|
+
|
|
71
|
+
THE RULE OF 3:
|
|
72
|
+
If you have tried to fix the same problem 3 times and failed:
|
|
73
|
+
1. STOP immediately.
|
|
74
|
+
2. Do not attempt a 4th fix in the same chain.
|
|
75
|
+
3. Branch back to the analysis phase before the first fix attempt.`, {
|
|
70
76
|
thought: z.string().describe("Your current thinking step"),
|
|
71
77
|
thoughtType: z.enum([
|
|
72
78
|
'analysis', 'planning', 'execution', 'observation',
|