sapper-iq 1.1.11 → 1.1.13
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 +172 -55
package/package.json
CHANGED
package/sapper.mjs
CHANGED
|
@@ -99,6 +99,104 @@ const IGNORE_DIRS = new Set([
|
|
|
99
99
|
'.idea', '.vscode', 'vendor', 'target', '.gradle'
|
|
100
100
|
]);
|
|
101
101
|
|
|
102
|
+
// File extensions to include when scanning codebase
|
|
103
|
+
const CODE_EXTENSIONS = new Set([
|
|
104
|
+
'.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rs', '.rb', '.php',
|
|
105
|
+
'.c', '.cpp', '.h', '.hpp', '.cs', '.swift', '.kt', '.scala', '.vue', '.svelte',
|
|
106
|
+
'.css', '.scss', '.sass', '.less', '.html', '.htm', '.json', '.yaml', '.yml',
|
|
107
|
+
'.toml', '.xml', '.md', '.txt', '.sh', '.bash', '.zsh', '.sql', '.graphql',
|
|
108
|
+
'.env.example', '.gitignore', '.dockerignore', 'Dockerfile', 'Makefile',
|
|
109
|
+
'.prisma', '.proto'
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// Max file size to include (skip large files like bundled/minified)
|
|
113
|
+
const MAX_FILE_SIZE = 100000; // 100KB per file
|
|
114
|
+
const MAX_TOTAL_SCAN_SIZE = 1000000; // 1000KB total scan limit
|
|
115
|
+
|
|
116
|
+
// Scan entire codebase and return summary
|
|
117
|
+
function scanCodebase(dir = '.', depth = 0, maxDepth = 5) {
|
|
118
|
+
if (depth > maxDepth) return { files: [], totalSize: 0 };
|
|
119
|
+
|
|
120
|
+
let files = [];
|
|
121
|
+
let totalSize = 0;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
125
|
+
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
const fullPath = dir === '.' ? entry.name : `${dir}/${entry.name}`;
|
|
128
|
+
|
|
129
|
+
// Skip ignored directories
|
|
130
|
+
if (entry.isDirectory()) {
|
|
131
|
+
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
|
|
132
|
+
const subResult = scanCodebase(fullPath, depth + 1, maxDepth);
|
|
133
|
+
files = files.concat(subResult.files);
|
|
134
|
+
totalSize += subResult.totalSize;
|
|
135
|
+
} else {
|
|
136
|
+
// Check if file should be included
|
|
137
|
+
const ext = entry.name.includes('.') ? '.' + entry.name.split('.').pop() : entry.name;
|
|
138
|
+
const isCodeFile = CODE_EXTENSIONS.has(ext.toLowerCase()) || CODE_EXTENSIONS.has(entry.name);
|
|
139
|
+
|
|
140
|
+
if (!isCodeFile) continue;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const stats = fs.statSync(fullPath);
|
|
144
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
145
|
+
files.push({ path: fullPath, size: stats.size, skipped: true, reason: 'too large' });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (totalSize + stats.size > MAX_TOTAL_SCAN_SIZE) {
|
|
149
|
+
files.push({ path: fullPath, size: stats.size, skipped: true, reason: 'total limit reached' });
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
154
|
+
files.push({ path: fullPath, size: stats.size, content });
|
|
155
|
+
totalSize += stats.size;
|
|
156
|
+
} catch (e) {
|
|
157
|
+
files.push({ path: fullPath, skipped: true, reason: e.message });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
// Directory not readable
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { files, totalSize };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Format scan results for AI context
|
|
169
|
+
function formatScanResults(scanResult) {
|
|
170
|
+
let output = `\n══════════════════════════════════════\n`;
|
|
171
|
+
output += `📁 CODEBASE SCAN (${scanResult.files.length} files, ~${Math.round(scanResult.totalSize/1024)}KB)\n`;
|
|
172
|
+
output += `══════════════════════════════════════\n\n`;
|
|
173
|
+
|
|
174
|
+
// First list all files
|
|
175
|
+
output += `FILE TREE:\n`;
|
|
176
|
+
for (const file of scanResult.files) {
|
|
177
|
+
if (file.skipped) {
|
|
178
|
+
output += ` ⏭️ ${file.path} (skipped: ${file.reason})\n`;
|
|
179
|
+
} else {
|
|
180
|
+
output += ` 📄 ${file.path} (${Math.round(file.size/1024)}KB)\n`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
output += `\n══════════════════════════════════════\n`;
|
|
185
|
+
output += `FILE CONTENTS:\n`;
|
|
186
|
+
output += `══════════════════════════════════════\n\n`;
|
|
187
|
+
|
|
188
|
+
// Then include contents
|
|
189
|
+
for (const file of scanResult.files) {
|
|
190
|
+
if (file.skipped) continue;
|
|
191
|
+
output += `┌─── ${file.path} ───\n`;
|
|
192
|
+
output += file.content;
|
|
193
|
+
if (!file.content.endsWith('\n')) output += '\n';
|
|
194
|
+
output += `└─── END ${file.path} ───\n\n`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return output;
|
|
198
|
+
}
|
|
199
|
+
|
|
102
200
|
const tools = {
|
|
103
201
|
read: (path) => {
|
|
104
202
|
try { return fs.readFileSync(path.trim(), 'utf8'); }
|
|
@@ -252,63 +350,51 @@ async function runSapper() {
|
|
|
252
350
|
if (messages.length === 0) {
|
|
253
351
|
messages = [{
|
|
254
352
|
role: 'system',
|
|
255
|
-
content: `You are Sapper, a
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
-
|
|
289
|
-
-
|
|
290
|
-
-
|
|
291
|
-
- MKDIR: Create directory
|
|
292
|
-
- SHELL: Run terminal command (requires confirmation)
|
|
293
|
-
|
|
294
|
-
SMART WORKFLOW:
|
|
295
|
-
1. For unknown codebases: [TOOL:SEARCH]main|index|app[/TOOL] to find entry points
|
|
296
|
-
2. To find where something is defined: [TOOL:SEARCH]function myFunc[/TOOL]
|
|
297
|
-
3. SEARCH returns file paths + line numbers - then READ specific files
|
|
298
|
-
|
|
299
|
-
PATCH vs WRITE:
|
|
300
|
-
- Use PATCH for small changes (1-10 lines): [TOOL:PATCH]path]old|||new[/TOOL]
|
|
301
|
-
- Use WRITE only for new files or complete rewrites
|
|
353
|
+
content: `You are Sapper, a coding assistant that ONLY does what the user asks.
|
|
354
|
+
|
|
355
|
+
GOLDEN RULE: Do EXACTLY what the user asks. Nothing more, nothing less.
|
|
356
|
+
- NEVER add features the user didn't ask for.
|
|
357
|
+
- ALWAYS confirm with the user before writing/patching files or running shell commands.
|
|
358
|
+
- KEEP responses concise and to the point.
|
|
359
|
+
TOOLS (use these to interact with files):
|
|
360
|
+
|
|
361
|
+
[TOOL:LIST]path[/TOOL]
|
|
362
|
+
→ List files in a directory
|
|
363
|
+
→ Example: [TOOL:LIST].[/TOOL]
|
|
364
|
+
|
|
365
|
+
[TOOL:READ]path[/TOOL]
|
|
366
|
+
→ Read a file's contents
|
|
367
|
+
→ Example: [TOOL:READ]./package.json[/TOOL]
|
|
368
|
+
|
|
369
|
+
[TOOL:WRITE]path]content[/TOOL]
|
|
370
|
+
→ Create or overwrite a file (needs user confirmation)
|
|
371
|
+
→ Example: [TOOL:WRITE]./index.js]console.log("hello")[/TOOL]
|
|
372
|
+
|
|
373
|
+
[TOOL:PATCH]path]old_text|||new_text[/TOOL]
|
|
374
|
+
→ Replace specific text in a file (needs user confirmation)
|
|
375
|
+
→ Example: [TOOL:PATCH]./app.js]old code|||new code[/TOOL]
|
|
376
|
+
|
|
377
|
+
[TOOL:SEARCH]pattern[/TOOL]
|
|
378
|
+
→ Search for text across all files
|
|
379
|
+
→ Example: [TOOL:SEARCH]function login[/TOOL]
|
|
380
|
+
|
|
381
|
+
[TOOL:SHELL]command[/TOOL]
|
|
382
|
+
→ Run a terminal command (needs user confirmation)
|
|
383
|
+
→ Example: [TOOL:SHELL]npm install express[/TOOL]
|
|
384
|
+
|
|
385
|
+
PATH RULES:
|
|
386
|
+
- Always use relative paths: ./file.js, ./src/app.js
|
|
387
|
+
- NEVER use absolute paths like /file.js
|
|
388
|
+
- Use . for current directory
|
|
302
389
|
|
|
303
390
|
WORKFLOW:
|
|
304
|
-
1.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- BATCH READING: When asked to read multiple files, call ALL [TOOL:READ] commands in ONE response. Do NOT stop to analyze between files.`
|
|
391
|
+
1. Understand exactly what user wants
|
|
392
|
+
2. Use LIST to see existing files if needed
|
|
393
|
+
3. Use READ to check existing code if needed
|
|
394
|
+
4. Use WRITE/PATCH to make changes
|
|
395
|
+
5. Be concise in explanations
|
|
396
|
+
|
|
397
|
+
CRITICAL: Stay focused. If user asks for X, deliver X only.`
|
|
312
398
|
}];
|
|
313
399
|
}
|
|
314
400
|
|
|
@@ -379,6 +465,7 @@ Do NOT just display content. Actually WRITE files using the tool.`
|
|
|
379
465
|
// Handle help command
|
|
380
466
|
if (input.toLowerCase() === '/help') {
|
|
381
467
|
console.log(chalk.cyan('\n📚 SAPPER COMMANDS:'));
|
|
468
|
+
console.log(chalk.white(' /scan') + chalk.gray(' - Scan entire codebase and add to context'));
|
|
382
469
|
console.log(chalk.white(' /reset, /clear') + chalk.gray(' - Clear all context and start fresh'));
|
|
383
470
|
console.log(chalk.white(' /prune') + chalk.gray(' - Remove old messages, keep last 4'));
|
|
384
471
|
console.log(chalk.white(' /context') + chalk.gray(' - Show current context size'));
|
|
@@ -408,6 +495,36 @@ Do NOT just display content. Actually WRITE files using the tool.`
|
|
|
408
495
|
continue;
|
|
409
496
|
}
|
|
410
497
|
|
|
498
|
+
// Handle codebase scan command
|
|
499
|
+
if (input.toLowerCase() === '/scan') {
|
|
500
|
+
console.log(chalk.cyan('\n🔍 Scanning codebase...'));
|
|
501
|
+
const scanResult = scanCodebase('.');
|
|
502
|
+
|
|
503
|
+
if (scanResult.files.length === 0) {
|
|
504
|
+
console.log(chalk.yellow('No code files found in current directory.'));
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const formattedScan = formatScanResults(scanResult);
|
|
509
|
+
const includedCount = scanResult.files.filter(f => !f.skipped).length;
|
|
510
|
+
const skippedCount = scanResult.files.filter(f => f.skipped).length;
|
|
511
|
+
|
|
512
|
+
console.log(chalk.green(`✅ Scanned ${includedCount} files (~${Math.round(scanResult.totalSize/1024)}KB)`));
|
|
513
|
+
if (skippedCount > 0) {
|
|
514
|
+
console.log(chalk.yellow(`⏭️ Skipped ${skippedCount} files (too large or limit reached)`));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Add scan to context
|
|
518
|
+
messages.push({
|
|
519
|
+
role: 'user',
|
|
520
|
+
content: `I've scanned the entire codebase. Here are all the files:\n${formattedScan}\n\nYou now have the full codebase context. Use this information to help me.`
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
|
|
524
|
+
console.log(chalk.gray('📝 Codebase added to context. AI now has full picture.\n'));
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
|
|
411
528
|
messages.push({ role: 'user', content: input });
|
|
412
529
|
|
|
413
530
|
let toolRounds = 0; // Prevent infinite loops
|