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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/sapper.mjs +117 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapper-iq",
3
- "version": "1.0.26",
3
+ "version": "1.1.1",
4
4
  "description": "AI-powered development assistant that executes commands and builds projects",
5
5
  "main": "sapper.mjs",
6
6
  "bin": {
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:LIST]./src[/TOOL] then read all files found
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
- ❌ WRONG: [TOOL:LIST]/[/TOOL] - wrong directory!
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 directory → 2. READ files (as many as needed) → 3. ANALYZE and RESPOND`
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}` });