sapper-iq 1.0.26 → 1.1.1
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/package.json +1 -1
- package/sapper.mjs +117 -3
package/package.json
CHANGED
package/sapper.mjs
CHANGED
|
@@ -76,6 +76,28 @@ const tools = {
|
|
|
76
76
|
try { return fs.readFileSync(path.trim(), 'utf8'); }
|
|
77
77
|
catch (error) { return `Error reading file: ${error.message}`; }
|
|
78
78
|
},
|
|
79
|
+
patch: async (path, oldText, newText) => {
|
|
80
|
+
const trimmedPath = path.trim();
|
|
81
|
+
try {
|
|
82
|
+
const content = fs.readFileSync(trimmedPath, 'utf8');
|
|
83
|
+
if (!content.includes(oldText)) {
|
|
84
|
+
return `Error: Could not find the text to replace in ${trimmedPath}. Make sure oldText matches exactly (including whitespace).`;
|
|
85
|
+
}
|
|
86
|
+
const newContent = content.replace(oldText, newText);
|
|
87
|
+
|
|
88
|
+
// Show diff preview
|
|
89
|
+
console.log(chalk.yellow.bold(`\n[PATCH] ${trimmedPath}`));
|
|
90
|
+
console.log(chalk.red('- ' + oldText.split('\n').join('\n- ')));
|
|
91
|
+
console.log(chalk.green('+ ' + newText.split('\n').join('\n+ ')));
|
|
92
|
+
|
|
93
|
+
const confirm = await safeQuestion(chalk.yellow('Apply this patch? (y/n): '));
|
|
94
|
+
if (confirm.toLowerCase() === 'y') {
|
|
95
|
+
fs.writeFileSync(trimmedPath, newContent);
|
|
96
|
+
return `Successfully patched ${trimmedPath}`;
|
|
97
|
+
}
|
|
98
|
+
return 'Patch rejected by user.';
|
|
99
|
+
} catch (error) { return `Error patching file: ${error.message}`; }
|
|
100
|
+
},
|
|
79
101
|
write: async (path, content) => {
|
|
80
102
|
const trimmedPath = path.trim();
|
|
81
103
|
console.log(chalk.yellow.bold(`\n[WRITE] Sapper wants to write to: `) + chalk.white(trimmedPath));
|
|
@@ -126,6 +148,27 @@ const tools = {
|
|
|
126
148
|
});
|
|
127
149
|
return filtered.length > 0 ? filtered.join('\n') : '(empty or all files filtered)';
|
|
128
150
|
} catch (e) { return `Error: ${e.message}`; }
|
|
151
|
+
},
|
|
152
|
+
search: (pattern) => {
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
const excludeDirs = Array.from(IGNORE_DIRS).join(',');
|
|
155
|
+
// Use grep to search for pattern, excluding ignored directories
|
|
156
|
+
const cmd = `grep -rEin "${pattern.replace(/"/g, '\\"')}" . --exclude-dir={${excludeDirs}} --include="*.{js,ts,jsx,tsx,py,java,go,rs,rb,php,c,cpp,h,css,scss,html,json,md,txt,yml,yaml,toml,sh}" 2>/dev/null | head -50`;
|
|
157
|
+
|
|
158
|
+
const proc = spawn('sh', ['-c', cmd], { cwd: process.cwd() });
|
|
159
|
+
let output = '';
|
|
160
|
+
|
|
161
|
+
proc.stdout.on('data', (data) => { output += data.toString(); });
|
|
162
|
+
proc.stderr.on('data', (data) => { output += data.toString(); });
|
|
163
|
+
|
|
164
|
+
proc.on('close', () => {
|
|
165
|
+
if (output.trim()) {
|
|
166
|
+
resolve(`Found matches:\n${output.trim()}`);
|
|
167
|
+
} else {
|
|
168
|
+
resolve(`No matches found for: ${pattern}`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
129
172
|
}
|
|
130
173
|
};
|
|
131
174
|
|
|
@@ -199,12 +242,31 @@ READING GUIDELINES:
|
|
|
199
242
|
TOOL FORMAT (CRITICAL - FOLLOW EXACTLY):
|
|
200
243
|
✅ CORRECT: [TOOL:LIST].[/TOOL]
|
|
201
244
|
✅ CORRECT: [TOOL:READ]./file.js[/TOOL]
|
|
202
|
-
✅ CORRECT: [TOOL:
|
|
245
|
+
✅ CORRECT: [TOOL:SEARCH]functionName[/TOOL]
|
|
246
|
+
✅ CORRECT: [TOOL:WRITE]./file.js]full content here[/TOOL]
|
|
247
|
+
✅ CORRECT: [TOOL:PATCH]./file.js]old code|||new code[/TOOL]
|
|
203
248
|
❌ WRONG: [TOOL:LIST].[/] - missing TOOL at end!
|
|
204
|
-
|
|
249
|
+
|
|
250
|
+
AVAILABLE TOOLS:
|
|
251
|
+
- LIST: List directory contents
|
|
252
|
+
- READ: Read file contents
|
|
253
|
+
- SEARCH: Find text/code across all files (grep-like, returns file:line:match)
|
|
254
|
+
- WRITE: Create or overwrite entire file (requires confirmation)
|
|
255
|
+
- PATCH: Make small edits to existing file (requires confirmation)
|
|
256
|
+
- MKDIR: Create directory
|
|
257
|
+
- SHELL: Run terminal command (requires confirmation)
|
|
258
|
+
|
|
259
|
+
SMART WORKFLOW:
|
|
260
|
+
1. For unknown codebases: [TOOL:SEARCH]main|index|app[/TOOL] to find entry points
|
|
261
|
+
2. To find where something is defined: [TOOL:SEARCH]function myFunc[/TOOL]
|
|
262
|
+
3. SEARCH returns file paths + line numbers - then READ specific files
|
|
263
|
+
|
|
264
|
+
PATCH vs WRITE:
|
|
265
|
+
- Use PATCH for small changes (1-10 lines): [TOOL:PATCH]path]old|||new[/TOOL]
|
|
266
|
+
- Use WRITE only for new files or complete rewrites
|
|
205
267
|
|
|
206
268
|
WORKFLOW:
|
|
207
|
-
1. LIST
|
|
269
|
+
1. LIST or SEARCH → 2. READ relevant files → 3. ANALYZE and RESPOND`
|
|
208
270
|
}];
|
|
209
271
|
}
|
|
210
272
|
|
|
@@ -228,6 +290,48 @@ WORKFLOW:
|
|
|
228
290
|
continue;
|
|
229
291
|
}
|
|
230
292
|
|
|
293
|
+
// Handle prune command - summarize and clear old context
|
|
294
|
+
if (input.toLowerCase() === '/prune') {
|
|
295
|
+
if (messages.length <= 5) {
|
|
296
|
+
console.log(chalk.yellow('Context is already small, nothing to prune.'));
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Keep system prompt + last 4 messages
|
|
301
|
+
const systemPrompt = messages[0];
|
|
302
|
+
const recentMessages = messages.slice(-4);
|
|
303
|
+
|
|
304
|
+
// Count what we're removing
|
|
305
|
+
const removedCount = messages.length - 5;
|
|
306
|
+
|
|
307
|
+
messages = [systemPrompt, ...recentMessages];
|
|
308
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
|
|
309
|
+
console.log(chalk.green(`✅ Pruned ${removedCount} old messages. Kept system prompt + last 4 messages.`));
|
|
310
|
+
console.log(chalk.gray(`Context size: ${messages.length} messages\n`));
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Handle help command
|
|
315
|
+
if (input.toLowerCase() === '/help') {
|
|
316
|
+
console.log(chalk.cyan('\n📚 SAPPER COMMANDS:'));
|
|
317
|
+
console.log(chalk.white(' /reset, /clear') + chalk.gray(' - Clear all context and start fresh'));
|
|
318
|
+
console.log(chalk.white(' /prune') + chalk.gray(' - Remove old messages, keep last 4'));
|
|
319
|
+
console.log(chalk.white(' /context') + chalk.gray(' - Show current context size'));
|
|
320
|
+
console.log(chalk.white(' /help') + chalk.gray(' - Show this help message'));
|
|
321
|
+
console.log(chalk.white(' exit') + chalk.gray(' - Exit Sapper\n'));
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Handle context size command
|
|
326
|
+
if (input.toLowerCase() === '/context') {
|
|
327
|
+
const contextSize = JSON.stringify(messages).length;
|
|
328
|
+
console.log(chalk.cyan(`\n📊 Context: ${messages.length} messages, ~${Math.round(contextSize/1024)}KB`));
|
|
329
|
+
if (contextSize > 50000) {
|
|
330
|
+
console.log(chalk.yellow('⚠️ Context is large! Consider using /prune'));
|
|
331
|
+
}
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
231
335
|
messages.push({ role: 'user', content: input });
|
|
232
336
|
|
|
233
337
|
let toolRounds = 0; // Prevent infinite loops
|
|
@@ -283,6 +387,16 @@ WORKFLOW:
|
|
|
283
387
|
else if (type.toLowerCase() === 'read') result = tools.read(path);
|
|
284
388
|
else if (type.toLowerCase() === 'mkdir') result = tools.mkdir(path);
|
|
285
389
|
else if (type.toLowerCase() === 'write') result = await tools.write(path, content);
|
|
390
|
+
else if (type.toLowerCase() === 'patch') {
|
|
391
|
+
// PATCH format: [TOOL:PATCH]path]OLD_TEXT|||NEW_TEXT[/TOOL]
|
|
392
|
+
const parts = content?.split('|||');
|
|
393
|
+
if (parts && parts.length === 2) {
|
|
394
|
+
result = await tools.patch(path, parts[0], parts[1]);
|
|
395
|
+
} else {
|
|
396
|
+
result = 'Error: PATCH requires format [TOOL:PATCH]path]OLD_TEXT|||NEW_TEXT[/TOOL]';
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
else if (type.toLowerCase() === 'search') result = await tools.search(path);
|
|
286
400
|
else if (type.toLowerCase() === 'shell') result = await tools.shell(path);
|
|
287
401
|
|
|
288
402
|
messages.push({ role: 'user', content: `RESULT (${path}): ${result}` });
|