sapper-iq 1.0.25 → 1.1.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/package.json +1 -1
- package/sapper.mjs +110 -9
package/package.json
CHANGED
package/sapper.mjs
CHANGED
|
@@ -64,16 +64,52 @@ function recreateReadline() {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Directories to ignore when listing files
|
|
68
|
+
const IGNORE_DIRS = new Set([
|
|
69
|
+
'node_modules', '.git', '.svn', '.hg', 'dist', 'build',
|
|
70
|
+
'.next', '.nuxt', '__pycache__', '.cache', 'coverage',
|
|
71
|
+
'.idea', '.vscode', 'vendor', 'target', '.gradle'
|
|
72
|
+
]);
|
|
73
|
+
|
|
67
74
|
const tools = {
|
|
68
75
|
read: (path) => {
|
|
69
76
|
try { return fs.readFileSync(path.trim(), 'utf8'); }
|
|
70
77
|
catch (error) { return `Error reading file: ${error.message}`; }
|
|
71
78
|
},
|
|
72
|
-
|
|
79
|
+
patch: async (path, oldText, newText) => {
|
|
80
|
+
const trimmedPath = path.trim();
|
|
73
81
|
try {
|
|
74
|
-
fs.
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
},
|
|
101
|
+
write: async (path, content) => {
|
|
102
|
+
const trimmedPath = path.trim();
|
|
103
|
+
console.log(chalk.yellow.bold(`\n[WRITE] Sapper wants to write to: `) + chalk.white(trimmedPath));
|
|
104
|
+
console.log(chalk.gray(`Content preview (first 200 chars):\n${content?.substring(0, 200)}${content?.length > 200 ? '...' : ''}`));
|
|
105
|
+
const confirm = await safeQuestion(chalk.yellow('Allow write? (y/n): '));
|
|
106
|
+
if (confirm.toLowerCase() === 'y') {
|
|
107
|
+
try {
|
|
108
|
+
fs.writeFileSync(trimmedPath, content);
|
|
109
|
+
return `Successfully saved changes to ${trimmedPath}`;
|
|
110
|
+
} catch (error) { return `Error writing file: ${error.message}`; }
|
|
111
|
+
}
|
|
112
|
+
return "Write blocked by user.";
|
|
77
113
|
},
|
|
78
114
|
mkdir: (path) => {
|
|
79
115
|
try {
|
|
@@ -100,8 +136,18 @@ const tools = {
|
|
|
100
136
|
return "Command blocked by user.";
|
|
101
137
|
},
|
|
102
138
|
list: (path) => {
|
|
103
|
-
try {
|
|
104
|
-
|
|
139
|
+
try {
|
|
140
|
+
const dir = path.trim() || '.';
|
|
141
|
+
const entries = fs.readdirSync(dir);
|
|
142
|
+
// Filter out ignored directories
|
|
143
|
+
const filtered = entries.filter(entry => {
|
|
144
|
+
if (IGNORE_DIRS.has(entry)) return false;
|
|
145
|
+
// Also skip hidden files/folders (starting with .) except current dir
|
|
146
|
+
if (entry.startsWith('.') && entry !== '.') return false;
|
|
147
|
+
return true;
|
|
148
|
+
});
|
|
149
|
+
return filtered.length > 0 ? filtered.join('\n') : '(empty or all files filtered)';
|
|
150
|
+
} catch (e) { return `Error: ${e.message}`; }
|
|
105
151
|
}
|
|
106
152
|
};
|
|
107
153
|
|
|
@@ -175,9 +221,13 @@ READING GUIDELINES:
|
|
|
175
221
|
TOOL FORMAT (CRITICAL - FOLLOW EXACTLY):
|
|
176
222
|
✅ CORRECT: [TOOL:LIST].[/TOOL]
|
|
177
223
|
✅ CORRECT: [TOOL:READ]./file.js[/TOOL]
|
|
178
|
-
✅ CORRECT: [TOOL:
|
|
224
|
+
✅ CORRECT: [TOOL:WRITE]./file.js]full content here[/TOOL]
|
|
225
|
+
✅ CORRECT: [TOOL:PATCH]./file.js]old code|||new code[/TOOL]
|
|
179
226
|
❌ WRONG: [TOOL:LIST].[/] - missing TOOL at end!
|
|
180
|
-
|
|
227
|
+
|
|
228
|
+
PATCH vs WRITE:
|
|
229
|
+
- Use PATCH for small changes (1-10 lines): [TOOL:PATCH]path]old|||new[/TOOL]
|
|
230
|
+
- Use WRITE only for new files or complete rewrites
|
|
181
231
|
|
|
182
232
|
WORKFLOW:
|
|
183
233
|
1. LIST directory → 2. READ files (as many as needed) → 3. ANALYZE and RESPOND`
|
|
@@ -204,6 +254,48 @@ WORKFLOW:
|
|
|
204
254
|
continue;
|
|
205
255
|
}
|
|
206
256
|
|
|
257
|
+
// Handle prune command - summarize and clear old context
|
|
258
|
+
if (input.toLowerCase() === '/prune') {
|
|
259
|
+
if (messages.length <= 5) {
|
|
260
|
+
console.log(chalk.yellow('Context is already small, nothing to prune.'));
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Keep system prompt + last 4 messages
|
|
265
|
+
const systemPrompt = messages[0];
|
|
266
|
+
const recentMessages = messages.slice(-4);
|
|
267
|
+
|
|
268
|
+
// Count what we're removing
|
|
269
|
+
const removedCount = messages.length - 5;
|
|
270
|
+
|
|
271
|
+
messages = [systemPrompt, ...recentMessages];
|
|
272
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
|
|
273
|
+
console.log(chalk.green(`✅ Pruned ${removedCount} old messages. Kept system prompt + last 4 messages.`));
|
|
274
|
+
console.log(chalk.gray(`Context size: ${messages.length} messages\n`));
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Handle help command
|
|
279
|
+
if (input.toLowerCase() === '/help') {
|
|
280
|
+
console.log(chalk.cyan('\n📚 SAPPER COMMANDS:'));
|
|
281
|
+
console.log(chalk.white(' /reset, /clear') + chalk.gray(' - Clear all context and start fresh'));
|
|
282
|
+
console.log(chalk.white(' /prune') + chalk.gray(' - Remove old messages, keep last 4'));
|
|
283
|
+
console.log(chalk.white(' /context') + chalk.gray(' - Show current context size'));
|
|
284
|
+
console.log(chalk.white(' /help') + chalk.gray(' - Show this help message'));
|
|
285
|
+
console.log(chalk.white(' exit') + chalk.gray(' - Exit Sapper\n'));
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Handle context size command
|
|
290
|
+
if (input.toLowerCase() === '/context') {
|
|
291
|
+
const contextSize = JSON.stringify(messages).length;
|
|
292
|
+
console.log(chalk.cyan(`\n📊 Context: ${messages.length} messages, ~${Math.round(contextSize/1024)}KB`));
|
|
293
|
+
if (contextSize > 50000) {
|
|
294
|
+
console.log(chalk.yellow('⚠️ Context is large! Consider using /prune'));
|
|
295
|
+
}
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
207
299
|
messages.push({ role: 'user', content: input });
|
|
208
300
|
|
|
209
301
|
let toolRounds = 0; // Prevent infinite loops
|
|
@@ -258,7 +350,16 @@ WORKFLOW:
|
|
|
258
350
|
if (type.toLowerCase() === 'list') result = tools.list(path);
|
|
259
351
|
else if (type.toLowerCase() === 'read') result = tools.read(path);
|
|
260
352
|
else if (type.toLowerCase() === 'mkdir') result = tools.mkdir(path);
|
|
261
|
-
else if (type.toLowerCase() === 'write') result = tools.write(path, content);
|
|
353
|
+
else if (type.toLowerCase() === 'write') result = await tools.write(path, content);
|
|
354
|
+
else if (type.toLowerCase() === 'patch') {
|
|
355
|
+
// PATCH format: [TOOL:PATCH]path]OLD_TEXT|||NEW_TEXT[/TOOL]
|
|
356
|
+
const parts = content?.split('|||');
|
|
357
|
+
if (parts && parts.length === 2) {
|
|
358
|
+
result = await tools.patch(path, parts[0], parts[1]);
|
|
359
|
+
} else {
|
|
360
|
+
result = 'Error: PATCH requires format [TOOL:PATCH]path]OLD_TEXT|||NEW_TEXT[/TOOL]';
|
|
361
|
+
}
|
|
362
|
+
}
|
|
262
363
|
else if (type.toLowerCase() === 'shell') result = await tools.shell(path);
|
|
263
364
|
|
|
264
365
|
messages.push({ role: 'user', content: `RESULT (${path}): ${result}` });
|