@mrxkun/mcfast-mcp 1.4.4 → 2.0.0
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 +129 -52
- package/package.json +3 -3
- package/src/index.js +403 -119
- package/src/strategies/edit-strategy.js +80 -0
- package/src/strategies/search-strategy.js +52 -0
package/README.md
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
# @mrxkun/mcfast-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**mcfast v2.0** - Supercharge your AI coding agent with intelligent, unified tools.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- `search_filesystem`: Smartly detects and uses `ripgrep` (fastest), `git grep`, or `grep` to search your entire codebase in milliseconds.
|
|
11
|
-
- `search_code_ai`: Semantic/fuzzy search powered by cloud AI when you need to find concepts, not just keywords.
|
|
12
|
-
- **📂 Smart File Listing**:
|
|
13
|
-
- `list_files_fast`: Instantly list project files while automatically respecting your `.gitignore` rules (powered by `fast-glob`).
|
|
14
|
-
- **🧠 Intelligent Editing**:
|
|
15
|
-
- `apply_fast`: Applies complex code edits using the Mercury Coder Cloud API.
|
|
16
|
-
- `apply_search_replace`: Perfect for simple, surgical string replacements.
|
|
17
|
-
- `reapply`: Automatically fixes failed edits by analyzing errors.
|
|
18
|
-
- **📊 Audit Logging**:
|
|
19
|
-
- All local tool usage is audited and viewable in the mcfast Dashboard.
|
|
20
|
-
|
|
21
|
-
## 🚀 Installation
|
|
7
|
+
```bash
|
|
8
|
+
npx -y @mrxkun/mcfast-mcp
|
|
9
|
+
```
|
|
22
10
|
|
|
23
|
-
|
|
11
|
+
## Quick Start
|
|
24
12
|
|
|
25
|
-
###
|
|
13
|
+
### Claude Desktop / Cursor / Windsurf
|
|
26
14
|
|
|
27
|
-
Add
|
|
15
|
+
Add to your MCP configuration:
|
|
28
16
|
|
|
29
17
|
```json
|
|
30
18
|
{
|
|
@@ -40,50 +28,139 @@ Add this to your `claude_desktop_config.json` or Cursor MCP settings:
|
|
|
40
28
|
}
|
|
41
29
|
```
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
Get your free token at [mcfast.vercel.app](https://mcfast.vercel.app)
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
## Tools (v2.0)
|
|
34
|
+
|
|
35
|
+
mcfast v2.0 features **5 unified tools** with intelligent auto-detection:
|
|
36
|
+
|
|
37
|
+
### 🎯 Core Tools
|
|
38
|
+
|
|
39
|
+
#### `edit` - Universal File Editing
|
|
40
|
+
Intelligently edits files with automatic strategy detection:
|
|
41
|
+
- **Search/Replace**: Detects patterns like "Replace X with Y" → uses deterministic replacement
|
|
42
|
+
- **Placeholder Merge**: Detects `// ... existing code ...` → uses token-efficient merging
|
|
43
|
+
- **Mercury AI**: Falls back to complex refactoring via Mercury Coder Cloud
|
|
44
|
+
|
|
45
|
+
**Replaces:** `apply_fast`, `edit_file`, `apply_search_replace`
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// Example: Simple replacement
|
|
49
|
+
{
|
|
50
|
+
instruction: "Replace 'foo' with 'bar'",
|
|
51
|
+
files: { "app.js": "const foo = 1;" }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Example: Placeholder-based
|
|
55
|
+
{
|
|
56
|
+
instruction: "Add error handling",
|
|
57
|
+
code_edit: "try {\n // ... existing code ...\n} catch (e) { ... }",
|
|
58
|
+
files: { "app.js": "..." }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Example: Complex refactoring
|
|
62
|
+
{
|
|
63
|
+
instruction: "Refactor authentication to use JWT tokens",
|
|
64
|
+
files: { "auth.js": "...", "middleware.js": "..." }
|
|
65
|
+
}
|
|
47
66
|
```
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
#### `search` - Unified Code Search
|
|
69
|
+
Automatically selects the best search strategy:
|
|
70
|
+
- **Local**: When files are in context (fastest, in-memory)
|
|
71
|
+
- **AI Semantic**: For complex natural language queries
|
|
72
|
+
- **Filesystem**: Fast grep-based codebase-wide search (ripgrep → git grep → grep)
|
|
50
73
|
|
|
51
|
-
|
|
74
|
+
**Replaces:** `search_code`, `search_code_ai`, `search_filesystem`
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// Example: Local search
|
|
52
78
|
{
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
query: "authentication",
|
|
80
|
+
files: { "app.js": "...", "auth.js": "..." }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Example: Semantic search
|
|
84
|
+
{
|
|
85
|
+
query: "find where user authentication is handled"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Example: Filesystem search
|
|
89
|
+
{
|
|
90
|
+
query: "TODO",
|
|
91
|
+
path: "/project/src"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### `read` - File Reading
|
|
96
|
+
Read file contents with optional line ranges to save tokens.
|
|
97
|
+
|
|
98
|
+
**Replaces:** `read_file`
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
{
|
|
102
|
+
path: "/path/to/file.js",
|
|
103
|
+
start_line: 50,
|
|
104
|
+
end_line: 100
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### `list_files` - Directory Listing
|
|
109
|
+
List files in a directory (recursive) respecting `.gitignore`.
|
|
110
|
+
|
|
111
|
+
**Replaces:** `list_files_fast`
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
{
|
|
115
|
+
path: "/project/src",
|
|
116
|
+
depth: 3
|
|
61
117
|
}
|
|
62
118
|
```
|
|
63
119
|
|
|
64
|
-
|
|
120
|
+
#### `reapply` - Smart Retry
|
|
121
|
+
Automatically retries failed edits with adjusted strategy (max 3 attempts).
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
{
|
|
125
|
+
instruction: "Original instruction that failed",
|
|
126
|
+
files: { "app.js": "..." },
|
|
127
|
+
errorContext: "Error message from previous attempt"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Backward Compatibility
|
|
132
|
+
|
|
133
|
+
**All legacy tool names still work!** They automatically redirect to the new unified tools:
|
|
134
|
+
|
|
135
|
+
- `apply_fast` → `edit`
|
|
136
|
+
- `edit_file` → `edit`
|
|
137
|
+
- `apply_search_replace` → `edit`
|
|
138
|
+
- `search_code` → `search`
|
|
139
|
+
- `search_code_ai` → `search`
|
|
140
|
+
- `search_filesystem` → `search`
|
|
141
|
+
- `read_file` → `read`
|
|
142
|
+
- `list_files_fast` → `list_files`
|
|
143
|
+
|
|
144
|
+
## Features
|
|
65
145
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
146
|
+
✅ **Auto-Detection** - Tools automatically choose the best strategy
|
|
147
|
+
✅ **Token Optimization** - Placeholder merging and line-range reading save tokens
|
|
148
|
+
✅ **Cloud Processing** - Heavy AST parsing offloaded to Mercury Coder Cloud
|
|
149
|
+
✅ **Deterministic** - Reduces hallucinations with syntax verification
|
|
150
|
+
✅ **Universal** - Works with any MCP-enabled AI (Claude, Cursor, Windsurf, etc.)
|
|
70
151
|
|
|
71
|
-
##
|
|
152
|
+
## Performance
|
|
72
153
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
| `list_files_fast` | **(NEW)** Lists files recursively, respecting `.gitignore`. |
|
|
77
|
-
| `apply_fast` | Applies multi-file edits using AI. |
|
|
78
|
-
| `apply_search_replace` | Simple find-and-replace. |
|
|
79
|
-
| `search_code_ai` | AI-powered semantic search. |
|
|
80
|
-
| `edit_file` | Direct file write (with cloud logging). |
|
|
154
|
+
- **Speed**: 10,000+ tokens/sec for local operations
|
|
155
|
+
- **Accuracy**: 98% success rate for cloud edits
|
|
156
|
+
- **Efficiency**: 50% token reduction with placeholder-based editing
|
|
81
157
|
|
|
82
|
-
##
|
|
158
|
+
## Privacy & Security
|
|
83
159
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
160
|
+
- **Zero Persistence**: Code processed in memory, discarded immediately
|
|
161
|
+
- **Cloud Masking**: Your `MCFAST_TOKEN` never exposed in logs
|
|
162
|
+
- **Open Source**: Audit the source at [github.com/ndpmmo/mcfast](https://github.com/ndpmmo/mcfast)
|
|
86
163
|
|
|
87
|
-
##
|
|
164
|
+
## License
|
|
88
165
|
|
|
89
|
-
MIT © mrxkun
|
|
166
|
+
MIT © [mrxkun](https://github.com/mrxkun)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrxkun/mcfast-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Ultra-fast code editing
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Ultra-fast code editing with 5 unified tools and intelligent auto-detection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcfast-mcp": "src/index.js"
|
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
"fast-glob": "^3.3.3",
|
|
32
32
|
"ignore": "^7.0.5"
|
|
33
33
|
}
|
|
34
|
-
}
|
|
34
|
+
}
|
package/src/index.js
CHANGED
|
@@ -13,11 +13,140 @@ import path from "path";
|
|
|
13
13
|
import { exec } from "child_process";
|
|
14
14
|
import { promisify } from "util";
|
|
15
15
|
import fg from "fast-glob";
|
|
16
|
+
import { detectEditStrategy, extractSearchReplace } from './strategies/edit-strategy.js';
|
|
17
|
+
import { detectSearchStrategy } from './strategies/search-strategy.js';
|
|
16
18
|
|
|
17
19
|
const execAsync = promisify(exec);
|
|
18
20
|
|
|
19
21
|
const API_URL = "https://mcfast.vercel.app/api/v1";
|
|
20
22
|
const TOKEN = process.env.MCFAST_TOKEN;
|
|
23
|
+
const VERBOSE = process.env.MCFAST_VERBOSE !== 'false'; // Default: true
|
|
24
|
+
|
|
25
|
+
// ANSI Color Codes for Terminal Output
|
|
26
|
+
const colors = {
|
|
27
|
+
reset: '\x1b[0m',
|
|
28
|
+
bold: '\x1b[1m',
|
|
29
|
+
dim: '\x1b[2m',
|
|
30
|
+
cyan: '\x1b[36m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
magenta: '\x1b[35m',
|
|
35
|
+
gray: '\x1b[90m',
|
|
36
|
+
white: '\x1b[37m',
|
|
37
|
+
bgBlack: '\x1b[40m',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Tool icons (v2.0 - 4 core tools)
|
|
41
|
+
const toolIcons = {
|
|
42
|
+
edit: '✏️',
|
|
43
|
+
search: '🔍',
|
|
44
|
+
read: '📖',
|
|
45
|
+
list_files: '📁',
|
|
46
|
+
reapply: '🔁',
|
|
47
|
+
// Legacy aliases (backward compatibility)
|
|
48
|
+
apply_fast: '⚡',
|
|
49
|
+
apply_search_replace: '🔄',
|
|
50
|
+
search_code_ai: '🔍',
|
|
51
|
+
search_code: '🔎',
|
|
52
|
+
search_filesystem: '📂',
|
|
53
|
+
list_files_fast: '📁',
|
|
54
|
+
edit_file: '✏️',
|
|
55
|
+
read_file: '📖',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Backward compatibility mapping
|
|
59
|
+
const TOOL_ALIASES = {
|
|
60
|
+
'apply_fast': 'edit',
|
|
61
|
+
'edit_file': 'edit',
|
|
62
|
+
'apply_search_replace': 'edit',
|
|
63
|
+
'search_code': 'search',
|
|
64
|
+
'search_code_ai': 'search',
|
|
65
|
+
'search_filesystem': 'search',
|
|
66
|
+
'list_files_fast': 'list_files',
|
|
67
|
+
'read_file': 'read'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pretty-print tool call to stderr for terminal visibility
|
|
72
|
+
*/
|
|
73
|
+
function prettyLogToolCall(toolName, args) {
|
|
74
|
+
if (!VERBOSE) return;
|
|
75
|
+
|
|
76
|
+
const icon = toolIcons[toolName] || '🔧';
|
|
77
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
78
|
+
|
|
79
|
+
// Header
|
|
80
|
+
console.error(`\n${colors.cyan}${colors.bold}○ mcfast_${toolName}${colors.reset} ${colors.dim}[${timestamp}]${colors.reset}`);
|
|
81
|
+
|
|
82
|
+
// Format arguments nicely
|
|
83
|
+
if (args) {
|
|
84
|
+
const formattedArgs = formatArgs(args, toolName);
|
|
85
|
+
console.error(formattedArgs);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Format arguments for display
|
|
91
|
+
*/
|
|
92
|
+
function formatArgs(args, toolName) {
|
|
93
|
+
let output = '';
|
|
94
|
+
|
|
95
|
+
for (const [key, value] of Object.entries(args)) {
|
|
96
|
+
if (key === 'files' && typeof value === 'object') {
|
|
97
|
+
// Show file paths only, not content
|
|
98
|
+
const filePaths = Object.keys(value);
|
|
99
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${colors.dim}[${filePaths.length} file(s)]${colors.reset}\n`;
|
|
100
|
+
filePaths.slice(0, 5).forEach(fp => {
|
|
101
|
+
output += ` ${colors.gray}• ${fp}${colors.reset}\n`;
|
|
102
|
+
});
|
|
103
|
+
if (filePaths.length > 5) {
|
|
104
|
+
output += ` ${colors.dim}... and ${filePaths.length - 5} more${colors.reset}\n`;
|
|
105
|
+
}
|
|
106
|
+
} else if (key === 'instruction' || key === 'query') {
|
|
107
|
+
// Show instruction/query with truncation
|
|
108
|
+
const truncated = String(value).length > 200
|
|
109
|
+
? String(value).substring(0, 200) + '...'
|
|
110
|
+
: String(value);
|
|
111
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${colors.white}${truncated}${colors.reset}\n`;
|
|
112
|
+
} else if (key === 'path') {
|
|
113
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${colors.cyan}${value}${colors.reset}\n`;
|
|
114
|
+
} else if (key === 'content') {
|
|
115
|
+
// Don't show full content, just length
|
|
116
|
+
const len = String(value).length;
|
|
117
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${colors.dim}[${len} chars]${colors.reset}\n`;
|
|
118
|
+
} else if (typeof value === 'boolean') {
|
|
119
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${value ? colors.green : colors.dim}${value}${colors.reset}\n`;
|
|
120
|
+
} else if (typeof value === 'number') {
|
|
121
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${colors.magenta}${value}${colors.reset}\n`;
|
|
122
|
+
} else {
|
|
123
|
+
const strVal = String(value).length > 100
|
|
124
|
+
? String(value).substring(0, 100) + '...'
|
|
125
|
+
: String(value);
|
|
126
|
+
output += ` ${colors.yellow}${key}${colors.reset}: ${strVal}\n`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return output;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Log tool result to stderr
|
|
135
|
+
*/
|
|
136
|
+
function prettyLogResult(toolName, success, latencyMs, summary) {
|
|
137
|
+
if (!VERBOSE) return;
|
|
138
|
+
|
|
139
|
+
const icon = success ? colors.green + '✓' : colors.yellow + '⚠';
|
|
140
|
+
const status = success ? `${colors.green}success${colors.reset}` : `${colors.yellow}warning${colors.reset}`;
|
|
141
|
+
|
|
142
|
+
console.error(` ${icon}${colors.reset} ${status} ${colors.dim}(${latencyMs}ms)${colors.reset}`);
|
|
143
|
+
if (summary) {
|
|
144
|
+
const truncatedSummary = summary.length > 100 ? summary.substring(0, 100) + '...' : summary;
|
|
145
|
+
console.error(` ${colors.dim}→ ${truncatedSummary}${colors.reset}\n`);
|
|
146
|
+
} else {
|
|
147
|
+
console.error('');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
21
150
|
|
|
22
151
|
// Token validation moved to request handlers for better UX (prevents server crash)
|
|
23
152
|
if (!TOKEN) {
|
|
@@ -43,134 +172,103 @@ const server = new Server(
|
|
|
43
172
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
44
173
|
return {
|
|
45
174
|
tools: [
|
|
175
|
+
// CORE TOOL 1: edit (consolidates apply_fast + edit_file + apply_search_replace)
|
|
46
176
|
{
|
|
47
|
-
name: "
|
|
48
|
-
description: "
|
|
177
|
+
name: "edit",
|
|
178
|
+
description: "**PRIMARY TOOL FOR EDITING FILES** - Intelligently edits files with automatic strategy detection. Consolidates apply_fast, edit_file, and apply_search_replace into one smart tool. Supports: (1) Simple search/replace, (2) Placeholder-based editing with '// ... existing code ...', (3) Complex multi-file refactoring via Mercury AI.",
|
|
49
179
|
inputSchema: {
|
|
50
180
|
type: "object",
|
|
51
181
|
properties: {
|
|
52
182
|
instruction: {
|
|
53
183
|
type: "string",
|
|
54
|
-
description: "
|
|
184
|
+
description: "Natural language instruction describing changes. For search/replace, use 'Replace X with Y' format."
|
|
55
185
|
},
|
|
56
186
|
files: {
|
|
57
187
|
type: "object",
|
|
58
|
-
description: "
|
|
188
|
+
description: "Map of file paths to CURRENT file contents.",
|
|
59
189
|
additionalProperties: { type: "string" }
|
|
60
190
|
},
|
|
191
|
+
code_edit: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Optional: Partial code snippet with '// ... existing code ...' placeholders for token-efficient editing."
|
|
194
|
+
},
|
|
61
195
|
dryRun: {
|
|
62
196
|
type: "boolean",
|
|
63
|
-
description: "If true, returns
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
required: ["instruction", "files"],
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: "apply_search_replace",
|
|
71
|
-
description: "Apply targeted search/replace edits. Faster and cheaper for simple replacements.",
|
|
72
|
-
inputSchema: {
|
|
73
|
-
type: "object",
|
|
74
|
-
properties: {
|
|
75
|
-
files: {
|
|
76
|
-
type: "object",
|
|
77
|
-
description: "Map of file paths to content.",
|
|
78
|
-
additionalProperties: { type: "string" }
|
|
79
|
-
},
|
|
80
|
-
search: { type: "string", description: "Exact string to search for." },
|
|
81
|
-
replace: { type: "string", description: "String to replace with." }
|
|
197
|
+
description: "If true, returns diff preview without applying changes."
|
|
198
|
+
}
|
|
82
199
|
},
|
|
83
|
-
required: ["
|
|
200
|
+
required: ["instruction", "files"]
|
|
84
201
|
}
|
|
85
202
|
},
|
|
203
|
+
// CORE TOOL 2: search (consolidates search_code + search_code_ai + search_filesystem)
|
|
86
204
|
{
|
|
87
|
-
name: "
|
|
88
|
-
description: "
|
|
205
|
+
name: "search",
|
|
206
|
+
description: "**UNIFIED CODE SEARCH** - Automatically selects the best search strategy: (1) Local search if files provided, (2) AI semantic search for complex queries, (3) Filesystem grep for fast codebase-wide search.",
|
|
89
207
|
inputSchema: {
|
|
90
208
|
type: "object",
|
|
91
209
|
properties: {
|
|
92
210
|
query: {
|
|
93
211
|
type: "string",
|
|
94
|
-
description: "Search query (
|
|
212
|
+
description: "Search query (literal string or natural language)"
|
|
95
213
|
},
|
|
96
214
|
files: {
|
|
97
215
|
type: "object",
|
|
98
|
-
description: "Map of
|
|
216
|
+
description: "Optional: Map of files to search within (triggers local search)",
|
|
99
217
|
additionalProperties: { type: "string" }
|
|
100
218
|
},
|
|
101
|
-
|
|
102
|
-
type: "
|
|
103
|
-
description: "
|
|
104
|
-
}
|
|
219
|
+
path: {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "Optional: Directory path for filesystem search"
|
|
222
|
+
},
|
|
223
|
+
mode: {
|
|
224
|
+
type: "string",
|
|
225
|
+
enum: ["auto", "local", "ai", "filesystem"],
|
|
226
|
+
description: "Search mode (default: auto-detect)"
|
|
227
|
+
},
|
|
228
|
+
regex: { type: "boolean", description: "Treat query as regex (local mode only)" },
|
|
229
|
+
caseSensitive: { type: "boolean", description: "Case sensitive search" },
|
|
230
|
+
contextLines: { type: "number", description: "Lines of context around matches (default: 2)" }
|
|
105
231
|
},
|
|
106
|
-
required: ["query"
|
|
232
|
+
required: ["query"]
|
|
107
233
|
}
|
|
108
234
|
},
|
|
235
|
+
// CORE TOOL 3: read
|
|
109
236
|
{
|
|
110
|
-
name: "
|
|
111
|
-
description: "
|
|
237
|
+
name: "read",
|
|
238
|
+
description: "Read file contents with optional line range support to save tokens. Use this before editing files or to understand code structure.",
|
|
112
239
|
inputSchema: {
|
|
113
240
|
type: "object",
|
|
114
241
|
properties: {
|
|
115
|
-
path: { type: "string", description: "Absolute
|
|
116
|
-
|
|
117
|
-
|
|
242
|
+
path: { type: "string", description: "Absolute path to the file" },
|
|
243
|
+
start_line: { type: "number", description: "Start line (1-indexed, inclusive)" },
|
|
244
|
+
end_line: { type: "number", description: "End line (1-indexed, inclusive)" }
|
|
118
245
|
},
|
|
119
|
-
required: ["path"
|
|
246
|
+
required: ["path"]
|
|
120
247
|
}
|
|
121
248
|
},
|
|
249
|
+
// CORE TOOL 4: list_files
|
|
122
250
|
{
|
|
123
|
-
name: "
|
|
124
|
-
description: "
|
|
251
|
+
name: "list_files",
|
|
252
|
+
description: "List all files in a directory (recursive) respecting .gitignore. Use this to understand project structure.",
|
|
125
253
|
inputSchema: {
|
|
126
254
|
type: "object",
|
|
127
255
|
properties: {
|
|
128
|
-
path: { type: "string", description: "Root directory path
|
|
129
|
-
depth: { type: "number", description: "Max depth to traverse (default: 5)
|
|
256
|
+
path: { type: "string", description: "Root directory path (default: current dir)" },
|
|
257
|
+
depth: { type: "number", description: "Max depth to traverse (default: 5)" }
|
|
130
258
|
}
|
|
131
259
|
}
|
|
132
260
|
},
|
|
133
|
-
|
|
134
|
-
name: "search_filesystem",
|
|
135
|
-
description: "Deep, high-performance filesystem search using ripgrep -> git grep -> grep fallback. Best for finding code when you don't know the file location.",
|
|
136
|
-
inputSchema: {
|
|
137
|
-
type: "object",
|
|
138
|
-
properties: {
|
|
139
|
-
query: { type: "string", description: "Search pattern (literal or regex)" },
|
|
140
|
-
path: { type: "string", description: "Directory to search in (default: current cwd)" },
|
|
141
|
-
include: { type: "string", description: "Glob pattern to include (e.g. **/*.ts)" },
|
|
142
|
-
exclude: { type: "array", items: { type: "string" }, description: "Patterns to exclude" },
|
|
143
|
-
isRegex: { type: "boolean", description: "Treat query as regex (default: false)" },
|
|
144
|
-
caseSensitive: { type: "boolean", description: "Case sensitive search (default: false)" }
|
|
145
|
-
},
|
|
146
|
-
required: ["query"]
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
name: "search_code",
|
|
151
|
-
description: "Fast local search within the PROVIDED files object. Useful when you already have file contents in context.",
|
|
152
|
-
inputSchema: {
|
|
153
|
-
type: "object",
|
|
154
|
-
properties: {
|
|
155
|
-
query: { type: "string", description: "Search pattern (literal or regex)" },
|
|
156
|
-
files: { type: "object", description: "Map of file paths to content to search within.", additionalProperties: { type: "string" } },
|
|
157
|
-
regex: { type: "boolean", description: "Treat query as regex (default: false)" },
|
|
158
|
-
caseSensitive: { type: "boolean", description: "Case sensitive search (default: false)" },
|
|
159
|
-
contextLines: { type: "number", description: "Lines of context around matches (default: 2)" }
|
|
160
|
-
},
|
|
161
|
-
required: ["query", "files"]
|
|
162
|
-
}
|
|
163
|
-
},
|
|
261
|
+
// CORE TOOL 5: reapply (unchanged)
|
|
164
262
|
{
|
|
165
263
|
name: "reapply",
|
|
166
|
-
description: "Smart retry for failed
|
|
264
|
+
description: "Smart retry for failed edits. Analyzes error context and re-attempts with adjusted strategy. Max 3 retries.",
|
|
167
265
|
inputSchema: {
|
|
168
266
|
type: "object",
|
|
169
267
|
properties: {
|
|
170
268
|
instruction: { type: "string", description: "Original instruction that failed" },
|
|
171
269
|
files: { type: "object", description: "Current file contents", additionalProperties: { type: "string" } },
|
|
172
|
-
errorContext: { type: "string", description: "
|
|
173
|
-
attempt: { type: "number", description: "Current attempt number
|
|
270
|
+
errorContext: { type: "string", description: "Error description" },
|
|
271
|
+
attempt: { type: "number", description: "Current attempt number" }
|
|
174
272
|
},
|
|
175
273
|
required: ["instruction", "files"]
|
|
176
274
|
}
|
|
@@ -204,26 +302,25 @@ async function getFiles(dir, depth = 5, currentDepth = 0) {
|
|
|
204
302
|
}
|
|
205
303
|
|
|
206
304
|
/**
|
|
207
|
-
* MCP Prompts Definition (
|
|
305
|
+
* MCP Prompts Definition (v2.0 - Updated for Unified Tools)
|
|
208
306
|
*/
|
|
209
307
|
const PROMPTS = {
|
|
210
308
|
"workflow_guide": {
|
|
211
|
-
description: "Standard workflow for using mcfast tools effectively
|
|
309
|
+
description: "Standard workflow for using mcfast v2.0 tools effectively.",
|
|
212
310
|
messages: [{
|
|
213
311
|
role: "user",
|
|
214
312
|
content: {
|
|
215
313
|
type: "text",
|
|
216
|
-
text: `You are an expert developer using the 'mcfast' toolkit. Follow this strategy for every task:
|
|
314
|
+
text: `You are an expert developer using the 'mcfast v2.0' toolkit. Follow this strategy for every task:
|
|
217
315
|
|
|
218
|
-
1. **Understand Structure:** Use '
|
|
219
|
-
2. **Locate Logic:** Use '
|
|
316
|
+
1. **Understand Structure:** Use 'list_files' first to see the project layout.
|
|
317
|
+
2. **Locate Logic:** Use 'search' to find relevant code sections (auto-detects best strategy).
|
|
220
318
|
3. **Plan & Execute:**
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
- Simple Replace: Use 'apply_search_replace'.
|
|
319
|
+
- Any edit: Use 'edit' (auto-detects: search/replace, placeholder merge, or Mercury AI).
|
|
320
|
+
- Read before editing: Use 'read' with line ranges to save tokens.
|
|
224
321
|
4. **Verify:** Check the output or run tests if possible.
|
|
225
322
|
|
|
226
|
-
|
|
323
|
+
The 'edit' tool automatically chooses the best strategy based on your instruction.`
|
|
227
324
|
}
|
|
228
325
|
}]
|
|
229
326
|
},
|
|
@@ -233,18 +330,18 @@ Always assume you have write access. 'apply_fast' and 'edit_file' write to disk
|
|
|
233
330
|
role: "user",
|
|
234
331
|
content: {
|
|
235
332
|
type: "text",
|
|
236
|
-
text: `Refactoring Strategy with mcfast:
|
|
333
|
+
text: `Refactoring Strategy with mcfast v2.0:
|
|
237
334
|
|
|
238
335
|
1. **Analysis:**
|
|
239
|
-
- Use '
|
|
240
|
-
- Use '
|
|
336
|
+
- Use 'list_files' to identify all files involved.
|
|
337
|
+
- Use 'search' to find usages of the symbol/logic to refactor.
|
|
241
338
|
|
|
242
339
|
2. **Execution (Atomic):**
|
|
243
|
-
- Use '
|
|
244
|
-
- '
|
|
340
|
+
- Use 'edit' for the main logic change. Pass ALL related file contents in the 'files' argument.
|
|
341
|
+
- The 'edit' tool is transactional for multiple files.
|
|
245
342
|
|
|
246
343
|
3. **Cleanup:**
|
|
247
|
-
- Use '
|
|
344
|
+
- Use 'edit' for any minor touch-ups or to update imports.`
|
|
248
345
|
}
|
|
249
346
|
}]
|
|
250
347
|
},
|
|
@@ -256,10 +353,10 @@ Always assume you have write access. 'apply_fast' and 'edit_file' write to disk
|
|
|
256
353
|
type: "text",
|
|
257
354
|
text: `Debugging Protocol:
|
|
258
355
|
|
|
259
|
-
1. **Trace:** Use '
|
|
260
|
-
2. **Context:**
|
|
356
|
+
1. **Trace:** Use 'search' with the error message or function name.
|
|
357
|
+
2. **Context:** Use 'read' to view the surrounding code.
|
|
261
358
|
3. **Hypothesis:** Formulate a fix.
|
|
262
|
-
4. **Fix:** Use '
|
|
359
|
+
4. **Fix:** Use 'edit' for single-file or multi-file fixes.`
|
|
263
360
|
}
|
|
264
361
|
}]
|
|
265
362
|
}
|
|
@@ -281,35 +378,74 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
281
378
|
});
|
|
282
379
|
|
|
283
380
|
/**
|
|
284
|
-
* Tool Execution Handler
|
|
381
|
+
* Tool Execution Handler (v2.0 - Unified Tools)
|
|
285
382
|
*/
|
|
286
383
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
287
|
-
|
|
384
|
+
let { name, arguments: args } = request.params;
|
|
385
|
+
const startTime = Date.now();
|
|
386
|
+
|
|
387
|
+
// Backward compatibility: map legacy tool names to new names
|
|
388
|
+
const originalName = name;
|
|
389
|
+
if (TOOL_ALIASES[name]) {
|
|
390
|
+
name = TOOL_ALIASES[name];
|
|
391
|
+
console.error(`${colors.dim}[COMPAT] ${originalName} → ${name}${colors.reset}`);
|
|
392
|
+
}
|
|
288
393
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
394
|
+
// Pretty-print tool call to terminal
|
|
395
|
+
prettyLogToolCall(originalName, args);
|
|
396
|
+
|
|
397
|
+
let result;
|
|
398
|
+
let success = true;
|
|
399
|
+
let summary = '';
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
// CORE TOOL 1: edit (unified editing with auto-detection)
|
|
403
|
+
if (name === "edit") {
|
|
404
|
+
result = await handleEdit(args);
|
|
405
|
+
summary = 'File edit completed';
|
|
406
|
+
}
|
|
407
|
+
// CORE TOOL 2: search (unified search with auto-detection)
|
|
408
|
+
else if (name === "search") {
|
|
409
|
+
result = await handleSearch(args);
|
|
410
|
+
summary = 'Search completed';
|
|
411
|
+
}
|
|
412
|
+
// CORE TOOL 3: read
|
|
413
|
+
else if (name === "read") {
|
|
414
|
+
result = await handleRead(args);
|
|
415
|
+
summary = 'File read completed';
|
|
416
|
+
}
|
|
417
|
+
// CORE TOOL 4: list_files
|
|
418
|
+
else if (name === "list_files") {
|
|
419
|
+
result = await handleListFiles(args);
|
|
420
|
+
summary = 'Directory listing completed';
|
|
421
|
+
}
|
|
422
|
+
// CORE TOOL 5: reapply
|
|
423
|
+
else if (name === "reapply") {
|
|
424
|
+
result = await handleReapply(args);
|
|
425
|
+
summary = 'Retry edit applied';
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
throw new Error(`Tool not found: ${name}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check for errors in result
|
|
432
|
+
if (result?.isError) {
|
|
433
|
+
success = false;
|
|
434
|
+
summary = result.content?.[0]?.text?.substring(0, 100) || 'Error occurred';
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
success = false;
|
|
438
|
+
summary = error.message;
|
|
439
|
+
result = {
|
|
440
|
+
content: [{ type: "text", text: `❌ Error: ${error.message}` }],
|
|
441
|
+
isError: true
|
|
442
|
+
};
|
|
310
443
|
}
|
|
311
444
|
|
|
312
|
-
|
|
445
|
+
// Log result
|
|
446
|
+
prettyLogResult(originalName, success, Date.now() - startTime, summary);
|
|
447
|
+
|
|
448
|
+
return result;
|
|
313
449
|
});
|
|
314
450
|
|
|
315
451
|
// Smart retry handler
|
|
@@ -348,6 +484,82 @@ async function handleReapply({ instruction, files, errorContext = "", attempt =
|
|
|
348
484
|
}
|
|
349
485
|
}
|
|
350
486
|
|
|
487
|
+
/**
|
|
488
|
+
* UNIFIED HANDLER 1: handleEdit (v2.0)
|
|
489
|
+
* Consolidates: apply_fast + edit_file + apply_search_replace
|
|
490
|
+
* Auto-detects best strategy based on input
|
|
491
|
+
*/
|
|
492
|
+
async function handleEdit({ instruction, files, code_edit, dryRun = false }) {
|
|
493
|
+
const strategy = detectEditStrategy({ instruction, code_edit, files });
|
|
494
|
+
|
|
495
|
+
console.error(`${colors.cyan}[EDIT STRATEGY]${colors.reset} ${strategy}`);
|
|
496
|
+
|
|
497
|
+
// Strategy 1: Search/Replace (deterministic, fastest)
|
|
498
|
+
if (strategy === 'search_replace') {
|
|
499
|
+
const extracted = extractSearchReplace(instruction);
|
|
500
|
+
if (extracted) {
|
|
501
|
+
return await handleApplyFast({
|
|
502
|
+
instruction: `Replace checking for exact match:\nSEARCH:\n${extracted.search}\n\nREPLACE WITH:\n${extracted.replace}`,
|
|
503
|
+
files,
|
|
504
|
+
dryRun,
|
|
505
|
+
toolName: 'edit'
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Strategy 2: Placeholder Merge (token-efficient)
|
|
511
|
+
if (strategy === 'placeholder_merge' && code_edit) {
|
|
512
|
+
// Use edit_file logic for placeholder-based editing
|
|
513
|
+
// For now, we'll route to Mercury with a hint
|
|
514
|
+
return await handleApplyFast({
|
|
515
|
+
instruction: `${instruction}\n\nUSE PLACEHOLDER MERGE STRATEGY. Code snippet:\n${code_edit}`,
|
|
516
|
+
files,
|
|
517
|
+
dryRun,
|
|
518
|
+
toolName: 'edit'
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Strategy 3: Mercury Intelligent (most flexible, default)
|
|
523
|
+
return await handleApplyFast({
|
|
524
|
+
instruction,
|
|
525
|
+
files,
|
|
526
|
+
dryRun,
|
|
527
|
+
toolName: 'edit'
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* UNIFIED HANDLER 2: handleSearch (v2.0)
|
|
533
|
+
* Consolidates: search_code + search_code_ai + search_filesystem
|
|
534
|
+
* Auto-detects best strategy based on input
|
|
535
|
+
*/
|
|
536
|
+
async function handleSearch({ query, files, path, mode = 'auto', regex = false, caseSensitive = false, contextLines = 2 }) {
|
|
537
|
+
const detectedMode = mode === 'auto' ? detectSearchStrategy({ query, files, path, mode }) : mode;
|
|
538
|
+
|
|
539
|
+
console.error(`${colors.cyan}[SEARCH STRATEGY]${colors.reset} ${detectedMode}`);
|
|
540
|
+
|
|
541
|
+
// Strategy 1: Local search (if files provided)
|
|
542
|
+
if (detectedMode === 'local' && files) {
|
|
543
|
+
return await handleSearchCode({ query, files, regex, caseSensitive, contextLines });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Strategy 2: AI semantic search (for complex queries)
|
|
547
|
+
if (detectedMode === 'ai' && files) {
|
|
548
|
+
return await handleSearchCodeAI({ query, files, contextLines });
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Strategy 3: Filesystem search (fast grep-based)
|
|
552
|
+
return await handleSearchFilesystem({ query, path, isRegex: regex, caseSensitive });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* UNIFIED HANDLER 3: handleRead (v2.0)
|
|
557
|
+
* Renamed from handleReadFile for consistency
|
|
558
|
+
*/
|
|
559
|
+
async function handleRead({ path: filePath, start_line, end_line }) {
|
|
560
|
+
return await handleReadFile({ path: filePath, start_line, end_line });
|
|
561
|
+
}
|
|
562
|
+
|
|
351
563
|
// Local search implementation (no API required)
|
|
352
564
|
async function reportAudit(params) {
|
|
353
565
|
if (!TOKEN) return;
|
|
@@ -786,6 +998,78 @@ async function handleEditFile({ path: filePath, content, instruction = "" }) {
|
|
|
786
998
|
}
|
|
787
999
|
}
|
|
788
1000
|
|
|
1001
|
+
|
|
1002
|
+
async function handleReadFile({ path: filePath, start_line, end_line }) {
|
|
1003
|
+
const start = Date.now();
|
|
1004
|
+
try {
|
|
1005
|
+
// Resolve absolute path
|
|
1006
|
+
const absolutePath = path.resolve(filePath);
|
|
1007
|
+
|
|
1008
|
+
// Check if file exists and is a file
|
|
1009
|
+
const stats = await fs.stat(absolutePath);
|
|
1010
|
+
if (!stats.isFile()) {
|
|
1011
|
+
throw new Error(`Path is not a file: ${absolutePath}`);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Read file content
|
|
1015
|
+
const content = await fs.readFile(absolutePath, 'utf8');
|
|
1016
|
+
|
|
1017
|
+
const lines = content.split('\n');
|
|
1018
|
+
const totalLines = lines.length;
|
|
1019
|
+
|
|
1020
|
+
let outputContent = content;
|
|
1021
|
+
let lineRangeInfo = `(Total ${totalLines} lines)`;
|
|
1022
|
+
|
|
1023
|
+
let startLine = start_line ? parseInt(start_line) : 1;
|
|
1024
|
+
let endLine = end_line ? parseInt(end_line) : totalLines;
|
|
1025
|
+
|
|
1026
|
+
// Validate range
|
|
1027
|
+
if (startLine < 1) startLine = 1;
|
|
1028
|
+
if (endLine > totalLines) endLine = totalLines;
|
|
1029
|
+
if (startLine > endLine) {
|
|
1030
|
+
throw new Error(`Invalid line range: start_line (${startLine}) > end_line (${endLine})`);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Slice content if range specified
|
|
1034
|
+
if (start_line || end_line) {
|
|
1035
|
+
outputContent = lines.slice(startLine - 1, endLine).join('\n');
|
|
1036
|
+
lineRangeInfo = `(Lines ${startLine}-${endLine} of ${totalLines})`;
|
|
1037
|
+
} else if (totalLines > 2000) {
|
|
1038
|
+
// Optional: warn if reading huge file without range?
|
|
1039
|
+
// For now, we allow it but it might be truncated by the client/LLM window.
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const output = `📄 File: ${filePath} ${lineRangeInfo}\n----------------------------------------\n${outputContent}`;
|
|
1043
|
+
|
|
1044
|
+
reportAudit({
|
|
1045
|
+
tool: 'read_file',
|
|
1046
|
+
instruction: filePath,
|
|
1047
|
+
status: 'success',
|
|
1048
|
+
latency_ms: Date.now() - start,
|
|
1049
|
+
files_count: 1,
|
|
1050
|
+
input_tokens: Math.ceil(filePath.length / 4),
|
|
1051
|
+
output_tokens: Math.ceil(output.length / 4)
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
return {
|
|
1055
|
+
content: [{ type: "text", text: output }]
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
reportAudit({
|
|
1060
|
+
tool: 'read_file',
|
|
1061
|
+
instruction: filePath,
|
|
1062
|
+
status: 'error',
|
|
1063
|
+
error_message: error.message,
|
|
1064
|
+
latency_ms: Date.now() - start
|
|
1065
|
+
});
|
|
1066
|
+
return {
|
|
1067
|
+
content: [{ type: "text", text: `❌ Error reading file: ${error.message}` }],
|
|
1068
|
+
isError: true
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
789
1073
|
async function handleApplyFast({ instruction, files, dryRun, toolName }) {
|
|
790
1074
|
if (!TOKEN) {
|
|
791
1075
|
return {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit Strategy Auto-Detection
|
|
3
|
+
* Determines the best editing strategy based on input parameters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect if instruction is a simple search/replace
|
|
8
|
+
*/
|
|
9
|
+
export function isSearchReplace(instruction) {
|
|
10
|
+
if (!instruction) return false;
|
|
11
|
+
|
|
12
|
+
const lower = instruction.toLowerCase();
|
|
13
|
+
|
|
14
|
+
// Pattern 1: "Replace X with Y"
|
|
15
|
+
const replacePattern = /replace\s+["'](.+?)["']\s+with\s+["'](.+?)["']/i;
|
|
16
|
+
if (replacePattern.test(instruction)) return true;
|
|
17
|
+
|
|
18
|
+
// Pattern 2: "Change X to Y"
|
|
19
|
+
const changePattern = /change\s+["'](.+?)["']\s+to\s+["'](.+?)["']/i;
|
|
20
|
+
if (changePattern.test(instruction)) return true;
|
|
21
|
+
|
|
22
|
+
// Pattern 3: Contains both "search" and "replace" keywords
|
|
23
|
+
if (lower.includes('search') && lower.includes('replace')) return true;
|
|
24
|
+
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract search and replace terms from instruction
|
|
30
|
+
*/
|
|
31
|
+
export function extractSearchReplace(instruction) {
|
|
32
|
+
const replacePattern = /replace\s+["'](.+?)["']\s+with\s+["'](.+?)["']/i;
|
|
33
|
+
const changePattern = /change\s+["'](.+?)["']\s+to\s+["'](.+?)["']/i;
|
|
34
|
+
|
|
35
|
+
let match = instruction.match(replacePattern);
|
|
36
|
+
if (match) {
|
|
37
|
+
return { search: match[1], replace: match[2] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
match = instruction.match(changePattern);
|
|
41
|
+
if (match) {
|
|
42
|
+
return { search: match[1], replace: match[2] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Detect if code_edit contains placeholders
|
|
50
|
+
*/
|
|
51
|
+
export function hasPlaceholders(codeEdit) {
|
|
52
|
+
if (!codeEdit) return false;
|
|
53
|
+
|
|
54
|
+
const placeholderPatterns = [
|
|
55
|
+
/\/\/\s*\.\.\.\s*(?:existing|keep|rest|remaining)[\w\s]*\.\.\./i,
|
|
56
|
+
/\/\*\s*\.\.\.\s*(?:existing|keep|rest|remaining)[\w\s]*\.\.\.\s*\*\//i,
|
|
57
|
+
/#\s*\.\.\.\s*(?:existing|keep|rest|remaining)[\w\s]*\.\.\./i
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
return placeholderPatterns.some(pattern => pattern.test(codeEdit));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Determine the best edit strategy
|
|
65
|
+
* @returns {'search_replace' | 'placeholder_merge' | 'mercury_intelligent'}
|
|
66
|
+
*/
|
|
67
|
+
export function detectEditStrategy({ instruction, code_edit, files }) {
|
|
68
|
+
// Priority 1: Search/Replace (fastest, deterministic)
|
|
69
|
+
if (isSearchReplace(instruction)) {
|
|
70
|
+
return 'search_replace';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Priority 2: Placeholder Merge (token-efficient)
|
|
74
|
+
if (code_edit && hasPlaceholders(code_edit)) {
|
|
75
|
+
return 'placeholder_merge';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Priority 3: Mercury Intelligent (most flexible)
|
|
79
|
+
return 'mercury_intelligent';
|
|
80
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Strategy Auto-Detection
|
|
3
|
+
* Determines the best search strategy based on input parameters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect if query is semantic/complex (requires AI)
|
|
8
|
+
*/
|
|
9
|
+
export function isSemanticQuery(query) {
|
|
10
|
+
if (!query) return false;
|
|
11
|
+
|
|
12
|
+
const lower = query.toLowerCase();
|
|
13
|
+
|
|
14
|
+
// Indicators of semantic search
|
|
15
|
+
const semanticIndicators = [
|
|
16
|
+
'find where',
|
|
17
|
+
'locate the',
|
|
18
|
+
'show me',
|
|
19
|
+
'what does',
|
|
20
|
+
'how does',
|
|
21
|
+
'why is',
|
|
22
|
+
'search for code that',
|
|
23
|
+
'find functions that',
|
|
24
|
+
'locate classes that'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
return semanticIndicators.some(indicator => lower.includes(indicator));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Determine the best search strategy
|
|
32
|
+
* @returns {'local' | 'ai' | 'filesystem'}
|
|
33
|
+
*/
|
|
34
|
+
export function detectSearchStrategy({ query, files, path, mode }) {
|
|
35
|
+
// If mode is explicitly set, use it
|
|
36
|
+
if (mode && mode !== 'auto') {
|
|
37
|
+
return mode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Priority 1: Local search (if files provided in context)
|
|
41
|
+
if (files && Object.keys(files).length > 0) {
|
|
42
|
+
return 'local';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Priority 2: AI search (if query is semantic/complex)
|
|
46
|
+
if (isSemanticQuery(query)) {
|
|
47
|
+
return 'ai';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Priority 3: Filesystem search (fast grep-based)
|
|
51
|
+
return 'filesystem';
|
|
52
|
+
}
|