snow-ai 0.2.8 → 0.2.10
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/mcp/filesystem.d.ts +43 -3
- package/dist/mcp/filesystem.js +228 -22
- package/package.json +1 -1
- package/readme.md +54 -1
package/dist/mcp/filesystem.d.ts
CHANGED
|
@@ -11,6 +11,35 @@ interface SearchResult {
|
|
|
11
11
|
matches: SearchMatch[];
|
|
12
12
|
searchedFiles: number;
|
|
13
13
|
}
|
|
14
|
+
interface StructureAnalysis {
|
|
15
|
+
bracketBalance: {
|
|
16
|
+
curly: {
|
|
17
|
+
open: number;
|
|
18
|
+
close: number;
|
|
19
|
+
balanced: boolean;
|
|
20
|
+
};
|
|
21
|
+
round: {
|
|
22
|
+
open: number;
|
|
23
|
+
close: number;
|
|
24
|
+
balanced: boolean;
|
|
25
|
+
};
|
|
26
|
+
square: {
|
|
27
|
+
open: number;
|
|
28
|
+
close: number;
|
|
29
|
+
balanced: boolean;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
htmlTags?: {
|
|
33
|
+
unclosedTags: string[];
|
|
34
|
+
unopenedTags: string[];
|
|
35
|
+
balanced: boolean;
|
|
36
|
+
};
|
|
37
|
+
indentationWarnings: string[];
|
|
38
|
+
codeBlockBoundary?: {
|
|
39
|
+
isInCompleteBlock: boolean;
|
|
40
|
+
suggestion?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
14
43
|
/**
|
|
15
44
|
* Filesystem MCP Service
|
|
16
45
|
* Provides basic file operations: read, create, and delete files
|
|
@@ -18,6 +47,16 @@ interface SearchResult {
|
|
|
18
47
|
export declare class FilesystemMCPService {
|
|
19
48
|
private basePath;
|
|
20
49
|
constructor(basePath?: string);
|
|
50
|
+
/**
|
|
51
|
+
* Analyze code structure for balance and completeness
|
|
52
|
+
* Helps AI identify bracket mismatches, unclosed tags, and boundary issues
|
|
53
|
+
*/
|
|
54
|
+
private analyzeCodeStructure;
|
|
55
|
+
/**
|
|
56
|
+
* Find smart context boundaries for editing
|
|
57
|
+
* Expands context to include complete code blocks when possible
|
|
58
|
+
*/
|
|
59
|
+
private findSmartContextBoundaries;
|
|
21
60
|
/**
|
|
22
61
|
* Get the content of a file with specified line range
|
|
23
62
|
* @param filePath - Path to the file (relative to base path or absolute)
|
|
@@ -76,8 +115,8 @@ export declare class FilesystemMCPService {
|
|
|
76
115
|
}>;
|
|
77
116
|
/**
|
|
78
117
|
* Edit a file by replacing lines within a specified range
|
|
79
|
-
*
|
|
80
|
-
* For larger changes, make multiple
|
|
118
|
+
* BEST PRACTICE: Keep edits small and focused (≤15 lines recommended) for better accuracy.
|
|
119
|
+
* For larger changes, make multiple parallel edits to non-overlapping sections instead of one large edit.
|
|
81
120
|
*
|
|
82
121
|
* @param filePath - Path to the file to edit
|
|
83
122
|
* @param startLine - Starting line number (1-indexed, inclusive) - get from filesystem_read output
|
|
@@ -85,7 +124,7 @@ export declare class FilesystemMCPService {
|
|
|
85
124
|
* @param newContent - New content to replace the specified lines (WITHOUT line numbers)
|
|
86
125
|
* @param contextLines - Number of context lines to return before and after the edit (default: 8)
|
|
87
126
|
* @returns Object containing success message, precise before/after comparison, and diagnostics
|
|
88
|
-
* @throws Error if file editing fails
|
|
127
|
+
* @throws Error if file editing fails
|
|
89
128
|
*/
|
|
90
129
|
editFile(filePath: string, startLine: number, endLine: number, newContent: string, contextLines?: number): Promise<{
|
|
91
130
|
message: string;
|
|
@@ -96,6 +135,7 @@ export declare class FilesystemMCPService {
|
|
|
96
135
|
contextEndLine: number;
|
|
97
136
|
totalLines: number;
|
|
98
137
|
linesModified: number;
|
|
138
|
+
structureAnalysis?: StructureAnalysis;
|
|
99
139
|
diagnostics?: Diagnostic[];
|
|
100
140
|
}>;
|
|
101
141
|
/**
|
package/dist/mcp/filesystem.js
CHANGED
|
@@ -17,6 +17,178 @@ export class FilesystemMCPService {
|
|
|
17
17
|
});
|
|
18
18
|
this.basePath = resolve(basePath);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Analyze code structure for balance and completeness
|
|
22
|
+
* Helps AI identify bracket mismatches, unclosed tags, and boundary issues
|
|
23
|
+
*/
|
|
24
|
+
analyzeCodeStructure(_content, filePath, editedLines) {
|
|
25
|
+
const analysis = {
|
|
26
|
+
bracketBalance: {
|
|
27
|
+
curly: { open: 0, close: 0, balanced: true },
|
|
28
|
+
round: { open: 0, close: 0, balanced: true },
|
|
29
|
+
square: { open: 0, close: 0, balanced: true }
|
|
30
|
+
},
|
|
31
|
+
indentationWarnings: []
|
|
32
|
+
};
|
|
33
|
+
// Count brackets in the edited content
|
|
34
|
+
const editedContent = editedLines.join('\n');
|
|
35
|
+
// Remove string literals and comments to avoid false positives
|
|
36
|
+
const cleanContent = editedContent
|
|
37
|
+
.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/g, '""') // Remove strings
|
|
38
|
+
.replace(/\/\/.*$/gm, '') // Remove single-line comments
|
|
39
|
+
.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
|
|
40
|
+
// Count brackets
|
|
41
|
+
analysis.bracketBalance.curly.open = (cleanContent.match(/\{/g) || []).length;
|
|
42
|
+
analysis.bracketBalance.curly.close = (cleanContent.match(/\}/g) || []).length;
|
|
43
|
+
analysis.bracketBalance.curly.balanced =
|
|
44
|
+
analysis.bracketBalance.curly.open === analysis.bracketBalance.curly.close;
|
|
45
|
+
analysis.bracketBalance.round.open = (cleanContent.match(/\(/g) || []).length;
|
|
46
|
+
analysis.bracketBalance.round.close = (cleanContent.match(/\)/g) || []).length;
|
|
47
|
+
analysis.bracketBalance.round.balanced =
|
|
48
|
+
analysis.bracketBalance.round.open === analysis.bracketBalance.round.close;
|
|
49
|
+
analysis.bracketBalance.square.open = (cleanContent.match(/\[/g) || []).length;
|
|
50
|
+
analysis.bracketBalance.square.close = (cleanContent.match(/\]/g) || []).length;
|
|
51
|
+
analysis.bracketBalance.square.balanced =
|
|
52
|
+
analysis.bracketBalance.square.open === analysis.bracketBalance.square.close;
|
|
53
|
+
// HTML/JSX tag analysis (for .html, .jsx, .tsx, .vue files)
|
|
54
|
+
const isMarkupFile = /\.(html|jsx|tsx|vue)$/i.test(filePath);
|
|
55
|
+
if (isMarkupFile) {
|
|
56
|
+
const tagPattern = /<\/?([a-zA-Z][a-zA-Z0-9-]*)[^>]*>/g;
|
|
57
|
+
const selfClosingPattern = /<[a-zA-Z][a-zA-Z0-9-]*[^>]*\/>/g;
|
|
58
|
+
// Remove self-closing tags
|
|
59
|
+
const contentWithoutSelfClosing = cleanContent.replace(selfClosingPattern, '');
|
|
60
|
+
const tags = [];
|
|
61
|
+
const unclosedTags = [];
|
|
62
|
+
const unopenedTags = [];
|
|
63
|
+
let match;
|
|
64
|
+
while ((match = tagPattern.exec(contentWithoutSelfClosing)) !== null) {
|
|
65
|
+
const isClosing = match[0]?.startsWith('</');
|
|
66
|
+
const tagName = match[1]?.toLowerCase();
|
|
67
|
+
if (!tagName)
|
|
68
|
+
continue;
|
|
69
|
+
if (isClosing) {
|
|
70
|
+
const lastOpenTag = tags.pop();
|
|
71
|
+
if (!lastOpenTag || lastOpenTag !== tagName) {
|
|
72
|
+
unopenedTags.push(tagName);
|
|
73
|
+
if (lastOpenTag)
|
|
74
|
+
tags.push(lastOpenTag); // Put it back
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
tags.push(tagName);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
unclosedTags.push(...tags);
|
|
82
|
+
analysis.htmlTags = {
|
|
83
|
+
unclosedTags,
|
|
84
|
+
unopenedTags,
|
|
85
|
+
balanced: unclosedTags.length === 0 && unopenedTags.length === 0
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Check indentation consistency
|
|
89
|
+
const lines = editedContent.split('\n');
|
|
90
|
+
const indents = lines
|
|
91
|
+
.filter(line => line.trim().length > 0)
|
|
92
|
+
.map(line => {
|
|
93
|
+
const match = line.match(/^(\s*)/);
|
|
94
|
+
return match ? match[1] : '';
|
|
95
|
+
})
|
|
96
|
+
.filter((indent) => indent !== undefined);
|
|
97
|
+
// Detect mixed tabs/spaces
|
|
98
|
+
const hasTabs = indents.some(indent => indent.includes('\t'));
|
|
99
|
+
const hasSpaces = indents.some(indent => indent.includes(' '));
|
|
100
|
+
if (hasTabs && hasSpaces) {
|
|
101
|
+
analysis.indentationWarnings.push('Mixed tabs and spaces detected');
|
|
102
|
+
}
|
|
103
|
+
// Detect inconsistent indentation levels (spaces only)
|
|
104
|
+
if (!hasTabs && hasSpaces) {
|
|
105
|
+
const spaceCounts = indents
|
|
106
|
+
.filter(indent => indent.length > 0)
|
|
107
|
+
.map(indent => indent.length);
|
|
108
|
+
if (spaceCounts.length > 1) {
|
|
109
|
+
const gcd = spaceCounts.reduce((a, b) => {
|
|
110
|
+
while (b !== 0) {
|
|
111
|
+
const temp = b;
|
|
112
|
+
b = a % b;
|
|
113
|
+
a = temp;
|
|
114
|
+
}
|
|
115
|
+
return a;
|
|
116
|
+
});
|
|
117
|
+
const hasInconsistent = spaceCounts.some(count => count % gcd !== 0 && gcd > 1);
|
|
118
|
+
if (hasInconsistent) {
|
|
119
|
+
analysis.indentationWarnings.push(`Inconsistent indentation (expected multiples of ${gcd} spaces)`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Check if edit is at a code block boundary
|
|
124
|
+
const lastLine = editedLines[editedLines.length - 1]?.trim() || '';
|
|
125
|
+
const firstLine = editedLines[0]?.trim() || '';
|
|
126
|
+
const endsWithOpenBrace = lastLine.endsWith('{') || lastLine.endsWith('(') || lastLine.endsWith('[');
|
|
127
|
+
const startsWithCloseBrace = firstLine.startsWith('}') || firstLine.startsWith(')') || firstLine.startsWith(']');
|
|
128
|
+
if (endsWithOpenBrace || startsWithCloseBrace) {
|
|
129
|
+
analysis.codeBlockBoundary = {
|
|
130
|
+
isInCompleteBlock: false,
|
|
131
|
+
suggestion: endsWithOpenBrace
|
|
132
|
+
? 'Edit ends with an opening bracket - ensure the closing bracket is included in a subsequent edit or already exists in the file'
|
|
133
|
+
: 'Edit starts with a closing bracket - ensure the opening bracket exists before this edit'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return analysis;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Find smart context boundaries for editing
|
|
140
|
+
* Expands context to include complete code blocks when possible
|
|
141
|
+
*/
|
|
142
|
+
findSmartContextBoundaries(lines, startLine, endLine, requestedContext) {
|
|
143
|
+
const totalLines = lines.length;
|
|
144
|
+
let contextStart = Math.max(1, startLine - requestedContext);
|
|
145
|
+
let contextEnd = Math.min(totalLines, endLine + requestedContext);
|
|
146
|
+
let extended = false;
|
|
147
|
+
// Try to find the start of the enclosing block
|
|
148
|
+
let bracketDepth = 0;
|
|
149
|
+
for (let i = startLine - 1; i >= Math.max(0, startLine - 50); i--) {
|
|
150
|
+
const line = lines[i];
|
|
151
|
+
if (!line)
|
|
152
|
+
continue;
|
|
153
|
+
const trimmed = line.trim();
|
|
154
|
+
// Count brackets (simple approach)
|
|
155
|
+
const openBrackets = (line.match(/\{/g) || []).length;
|
|
156
|
+
const closeBrackets = (line.match(/\}/g) || []).length;
|
|
157
|
+
bracketDepth += closeBrackets - openBrackets;
|
|
158
|
+
// If we find a function/class/block definition with balanced brackets
|
|
159
|
+
if (bracketDepth === 0 &&
|
|
160
|
+
(trimmed.match(/^(function|class|const|let|var|if|for|while|async|export)\s/i) ||
|
|
161
|
+
trimmed.match(/=>\s*\{/) ||
|
|
162
|
+
trimmed.match(/^\w+\s*\(/))) {
|
|
163
|
+
if (i + 1 < contextStart) {
|
|
164
|
+
contextStart = i + 1;
|
|
165
|
+
extended = true;
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Try to find the end of the enclosing block
|
|
171
|
+
bracketDepth = 0;
|
|
172
|
+
for (let i = endLine - 1; i < Math.min(totalLines, endLine + 50); i++) {
|
|
173
|
+
const line = lines[i];
|
|
174
|
+
if (!line)
|
|
175
|
+
continue;
|
|
176
|
+
const trimmed = line.trim();
|
|
177
|
+
// Count brackets
|
|
178
|
+
const openBrackets = (line.match(/\{/g) || []).length;
|
|
179
|
+
const closeBrackets = (line.match(/\}/g) || []).length;
|
|
180
|
+
bracketDepth += openBrackets - closeBrackets;
|
|
181
|
+
// If we find a closing bracket at depth 0
|
|
182
|
+
if (bracketDepth === 0 && trimmed.startsWith('}')) {
|
|
183
|
+
if (i + 1 > contextEnd) {
|
|
184
|
+
contextEnd = i + 1;
|
|
185
|
+
extended = true;
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { start: contextStart, end: contextEnd, extended };
|
|
191
|
+
}
|
|
20
192
|
/**
|
|
21
193
|
* Get the content of a file with specified line range
|
|
22
194
|
* @param filePath - Path to the file (relative to base path or absolute)
|
|
@@ -206,8 +378,8 @@ export class FilesystemMCPService {
|
|
|
206
378
|
}
|
|
207
379
|
/**
|
|
208
380
|
* Edit a file by replacing lines within a specified range
|
|
209
|
-
*
|
|
210
|
-
* For larger changes, make multiple
|
|
381
|
+
* BEST PRACTICE: Keep edits small and focused (≤15 lines recommended) for better accuracy.
|
|
382
|
+
* For larger changes, make multiple parallel edits to non-overlapping sections instead of one large edit.
|
|
211
383
|
*
|
|
212
384
|
* @param filePath - Path to the file to edit
|
|
213
385
|
* @param startLine - Starting line number (1-indexed, inclusive) - get from filesystem_read output
|
|
@@ -215,7 +387,7 @@ export class FilesystemMCPService {
|
|
|
215
387
|
* @param newContent - New content to replace the specified lines (WITHOUT line numbers)
|
|
216
388
|
* @param contextLines - Number of context lines to return before and after the edit (default: 8)
|
|
217
389
|
* @returns Object containing success message, precise before/after comparison, and diagnostics
|
|
218
|
-
* @throws Error if file editing fails
|
|
390
|
+
* @throws Error if file editing fails
|
|
219
391
|
*/
|
|
220
392
|
async editFile(filePath, startLine, endLine, newContent, contextLines = 8) {
|
|
221
393
|
try {
|
|
@@ -240,18 +412,7 @@ export class FilesystemMCPService {
|
|
|
240
412
|
}
|
|
241
413
|
// Adjust endLine if it exceeds file length
|
|
242
414
|
const adjustedEndLine = Math.min(endLine, totalLines);
|
|
243
|
-
// ENFORCE SMALL EDITS: Limit to max 15 lines per edit for precision
|
|
244
415
|
const linesToModify = adjustedEndLine - startLine + 1;
|
|
245
|
-
const MAX_LINES_PER_EDIT = 15;
|
|
246
|
-
if (linesToModify > MAX_LINES_PER_EDIT) {
|
|
247
|
-
throw new Error(`❌ Edit range too large (${linesToModify} lines). Maximum allowed: ${MAX_LINES_PER_EDIT} lines.\n\n` +
|
|
248
|
-
`💡 Best Practice: Make SMALL, PRECISE edits instead of large changes.\n` +
|
|
249
|
-
` - Break your changes into multiple sequential edits\n` +
|
|
250
|
-
` - Each edit should modify at most ${MAX_LINES_PER_EDIT} lines\n` +
|
|
251
|
-
` - This ensures accuracy and prevents syntax errors\n\n` +
|
|
252
|
-
`Current request: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
|
|
253
|
-
`Suggested approach: Split into ${Math.ceil(linesToModify / MAX_LINES_PER_EDIT)} smaller edits`);
|
|
254
|
-
}
|
|
255
416
|
// Backup file before editing
|
|
256
417
|
await incrementalSnapshotManager.backupFile(fullPath);
|
|
257
418
|
// Extract the lines that will be replaced (for comparison)
|
|
@@ -261,9 +422,10 @@ export class FilesystemMCPService {
|
|
|
261
422
|
const paddedNum = String(lineNum).padStart(String(adjustedEndLine).length, ' ');
|
|
262
423
|
return `${paddedNum}→${line}`;
|
|
263
424
|
}).join('\n');
|
|
264
|
-
// Calculate context range
|
|
265
|
-
const
|
|
266
|
-
const
|
|
425
|
+
// Calculate context range using smart boundary detection
|
|
426
|
+
const smartBoundaries = this.findSmartContextBoundaries(lines, startLine, adjustedEndLine, contextLines);
|
|
427
|
+
const contextStart = smartBoundaries.start;
|
|
428
|
+
const contextEnd = smartBoundaries.end;
|
|
267
429
|
// Extract old content for context (including the lines to be replaced)
|
|
268
430
|
const oldContextLines = lines.slice(contextStart - 1, contextEnd);
|
|
269
431
|
const oldContent = oldContextLines.map((line, idx) => {
|
|
@@ -289,6 +451,8 @@ export class FilesystemMCPService {
|
|
|
289
451
|
}).join('\n');
|
|
290
452
|
// Write the modified content back to file
|
|
291
453
|
await fs.writeFile(fullPath, modifiedLines.join('\n'), 'utf-8');
|
|
454
|
+
// Analyze code structure of the edited content
|
|
455
|
+
const structureAnalysis = this.analyzeCodeStructure(modifiedLines.join('\n'), filePath, newContentLines);
|
|
292
456
|
// Try to get diagnostics from VS Code after editing
|
|
293
457
|
let diagnostics = [];
|
|
294
458
|
try {
|
|
@@ -302,14 +466,16 @@ export class FilesystemMCPService {
|
|
|
302
466
|
const result = {
|
|
303
467
|
message: `✅ File edited successfully: ${filePath}\n` +
|
|
304
468
|
` Replaced: lines ${startLine}-${adjustedEndLine} (${linesToModify} lines)\n` +
|
|
305
|
-
` Result: ${newContentLines.length} new lines
|
|
469
|
+
` Result: ${newContentLines.length} new lines` +
|
|
470
|
+
(smartBoundaries.extended ? `\n 📍 Context auto-extended to show complete code block (lines ${contextStart}-${newContextEnd})` : ''),
|
|
306
471
|
oldContent,
|
|
307
472
|
newContent: newContextContent,
|
|
308
473
|
replacedLines: replacedContent,
|
|
309
474
|
contextStartLine: contextStart,
|
|
310
475
|
contextEndLine: newContextEnd,
|
|
311
476
|
totalLines: newTotalLines,
|
|
312
|
-
linesModified: linesToModify
|
|
477
|
+
linesModified: linesToModify,
|
|
478
|
+
structureAnalysis
|
|
313
479
|
};
|
|
314
480
|
// Add diagnostics if any were found
|
|
315
481
|
if (diagnostics.length > 0) {
|
|
@@ -331,6 +497,46 @@ export class FilesystemMCPService {
|
|
|
331
497
|
result.message += `\n\n ⚡ TIP: Review the errors above and make another small edit to fix them`;
|
|
332
498
|
}
|
|
333
499
|
}
|
|
500
|
+
// Add structure analysis warnings to the message
|
|
501
|
+
const structureWarnings = [];
|
|
502
|
+
// Check bracket balance
|
|
503
|
+
if (!structureAnalysis.bracketBalance.curly.balanced) {
|
|
504
|
+
const diff = structureAnalysis.bracketBalance.curly.open - structureAnalysis.bracketBalance.curly.close;
|
|
505
|
+
structureWarnings.push(`Curly brackets: ${diff > 0 ? `${diff} unclosed {` : `${Math.abs(diff)} extra }`}`);
|
|
506
|
+
}
|
|
507
|
+
if (!structureAnalysis.bracketBalance.round.balanced) {
|
|
508
|
+
const diff = structureAnalysis.bracketBalance.round.open - structureAnalysis.bracketBalance.round.close;
|
|
509
|
+
structureWarnings.push(`Round brackets: ${diff > 0 ? `${diff} unclosed (` : `${Math.abs(diff)} extra )`}`);
|
|
510
|
+
}
|
|
511
|
+
if (!structureAnalysis.bracketBalance.square.balanced) {
|
|
512
|
+
const diff = structureAnalysis.bracketBalance.square.open - structureAnalysis.bracketBalance.square.close;
|
|
513
|
+
structureWarnings.push(`Square brackets: ${diff > 0 ? `${diff} unclosed [` : `${Math.abs(diff)} extra ]`}`);
|
|
514
|
+
}
|
|
515
|
+
// Check HTML tags
|
|
516
|
+
if (structureAnalysis.htmlTags && !structureAnalysis.htmlTags.balanced) {
|
|
517
|
+
if (structureAnalysis.htmlTags.unclosedTags.length > 0) {
|
|
518
|
+
structureWarnings.push(`Unclosed HTML tags: ${structureAnalysis.htmlTags.unclosedTags.join(', ')}`);
|
|
519
|
+
}
|
|
520
|
+
if (structureAnalysis.htmlTags.unopenedTags.length > 0) {
|
|
521
|
+
structureWarnings.push(`Unopened closing tags: ${structureAnalysis.htmlTags.unopenedTags.join(', ')}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// Check indentation
|
|
525
|
+
if (structureAnalysis.indentationWarnings.length > 0) {
|
|
526
|
+
structureWarnings.push(...structureAnalysis.indentationWarnings.map(w => `Indentation: ${w}`));
|
|
527
|
+
}
|
|
528
|
+
// Add code block boundary warnings
|
|
529
|
+
if (structureAnalysis.codeBlockBoundary && structureAnalysis.codeBlockBoundary.suggestion) {
|
|
530
|
+
structureWarnings.push(`Boundary: ${structureAnalysis.codeBlockBoundary.suggestion}`);
|
|
531
|
+
}
|
|
532
|
+
// Format structure warnings
|
|
533
|
+
if (structureWarnings.length > 0) {
|
|
534
|
+
result.message += `\n\n🔍 Structure Analysis:\n`;
|
|
535
|
+
structureWarnings.forEach(warning => {
|
|
536
|
+
result.message += ` ⚠️ ${warning}\n`;
|
|
537
|
+
});
|
|
538
|
+
result.message += `\n 💡 TIP: These warnings help identify potential issues. If intentional (e.g., opening a block), you can ignore them.`;
|
|
539
|
+
}
|
|
334
540
|
return result;
|
|
335
541
|
}
|
|
336
542
|
catch (error) {
|
|
@@ -451,7 +657,7 @@ export const filesystemService = new FilesystemMCPService();
|
|
|
451
657
|
export const mcpTools = [
|
|
452
658
|
{
|
|
453
659
|
name: 'filesystem_read',
|
|
454
|
-
description: 'Read the content of a file within specified line range. The returned content includes line numbers (format: "lineNum→content") for precise editing. You MUST specify startLine and endLine. To read the entire file, use startLine=1 and a large endLine value (e.g.,
|
|
660
|
+
description: 'Read the content of a file within specified line range. The returned content includes line numbers (format: "lineNum→content") for precise editing. You MUST specify startLine and endLine. To read the entire file, use startLine=1 and a large endLine value (e.g., 500). IMPORTANT: When you need to edit a file, you MUST read it first to see the exact line numbers and current content. NOTE: If the path points to a directory, this tool will automatically list its contents instead of throwing an error.',
|
|
455
661
|
inputSchema: {
|
|
456
662
|
type: 'object',
|
|
457
663
|
properties: {
|
|
@@ -524,7 +730,7 @@ export const mcpTools = [
|
|
|
524
730
|
},
|
|
525
731
|
{
|
|
526
732
|
name: 'filesystem_edit',
|
|
527
|
-
description: '🎯 PREFERRED tool for precise file editing. **
|
|
733
|
+
description: '🎯 PREFERRED tool for precise file editing with intelligent feedback. **BEST PRACTICES**: (1) Use SMALL, INCREMENTAL edits (recommended ≤15 lines per edit) - SAFER and MORE ACCURATE, preventing syntax errors. (2) For large changes, make MULTIPLE PARALLEL edits to different sections instead of one large edit. (3) Must use exact line numbers Code boundaries should not be redundant or missing, such as `{}` or HTML tags causing syntax errors. **WORKFLOW**: (1) Read target section with filesystem_read, (2) Edit small sections, (3) Review auto-generated structure analysis and diagnostics, (4) Make parallel edits to non-overlapping ranges if needed. **SMART FEATURES**: Auto-detects bracket/tag mismatches, indentation issues, and code block boundaries. Context auto-extends to show complete functions/classes when detected.',
|
|
528
734
|
inputSchema: {
|
|
529
735
|
type: 'object',
|
|
530
736
|
properties: {
|
|
@@ -538,7 +744,7 @@ export const mcpTools = [
|
|
|
538
744
|
},
|
|
539
745
|
endLine: {
|
|
540
746
|
type: 'number',
|
|
541
|
-
description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive). MUST match exact line number from filesystem_read output.
|
|
747
|
+
description: '⚠️ CRITICAL: Ending line number (1-indexed, inclusive). MUST match exact line number from filesystem_read output. 💡 TIP: Keep edits small (≤15 lines recommended) for better accuracy.'
|
|
542
748
|
},
|
|
543
749
|
newContent: {
|
|
544
750
|
type: 'string',
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -6,4 +6,57 @@
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
$ npm install --global snow-ai
|
|
9
|
-
```
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Start
|
|
12
|
+
```bash
|
|
13
|
+
$ snow
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Update
|
|
17
|
+
```bash
|
|
18
|
+
$ snow --update
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Config example `./User/.snow/config.json`
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"openai": {
|
|
25
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
26
|
+
"apiKey": "your-api-key",
|
|
27
|
+
"requestMethod": "responses",
|
|
28
|
+
"advancedModel": "gpt-5-codex",
|
|
29
|
+
"basicModel": "gpt-5-codex",
|
|
30
|
+
"maxContextTokens": 200000,
|
|
31
|
+
"compactModel": {
|
|
32
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
33
|
+
"apiKey": "your-api-key",
|
|
34
|
+
"modelName": "gpt-4.1-mini"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Uninstall
|
|
41
|
+
```bash
|
|
42
|
+
$ npm uninstall --global snow-ai
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Install VSCode Extension
|
|
46
|
+
|
|
47
|
+
* download [VSIX/snow-cli-0.2.5.vsix](https://github.com/MayDay-wpf/snow-cli/blob/main/VSIX/snow-cli-0.2.5.vsix)
|
|
48
|
+
|
|
49
|
+
* open VSCode, click `Extensions` -> `Install from VSIX...` -> select `snow-cli-0.2.5.vsix`
|
|
50
|
+
|
|
51
|
+
## Live View
|
|
52
|
+
* **Welcome & Settings**
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
* **Agent**
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
* **Commands**
|
|
61
|
+
|
|
62
|
+

|