sapper-iq 1.1.10 → 1.1.12
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 +134 -4
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'); }
|
|
@@ -262,11 +360,11 @@ CRITICAL: You are working in the CURRENT DIRECTORY. Always use relative paths!
|
|
|
262
360
|
STRATEGY FOR FILE READING:
|
|
263
361
|
1. Start with [TOOL:LIST].[/TOOL] to see what exists
|
|
264
362
|
2. READ FILES BASED ON TASK:
|
|
265
|
-
- Quick overview: Read 2-
|
|
363
|
+
- Quick overview: Read 2-8 key files (README, package.json, main entry)
|
|
266
364
|
- Deep analysis: Read ALL relevant files (entire src/ folder, all components)
|
|
267
365
|
- User asks "read all": Read ALL files they mention
|
|
268
366
|
3. Use format: [TOOL:TYPE]path]content[/TOOL]
|
|
269
|
-
4.
|
|
367
|
+
4. MANDATORY: You MUST finish reading ALL requested files before providing ANY analysis or summary. Do NOT stop to explain - keep reading until done!
|
|
270
368
|
|
|
271
369
|
READING GUIDELINES:
|
|
272
370
|
- If user says "analyze src folder" → Read ALL files in src/
|
|
@@ -307,7 +405,8 @@ IMPORTANT RULES:
|
|
|
307
405
|
- Be concise. Do not generate repetitive lists or filler text.
|
|
308
406
|
- If a list exceeds 10 items, summarize instead of listing everything.
|
|
309
407
|
- Never repeat the same content multiple times.
|
|
310
|
-
- Stop writing when you've made your point
|
|
408
|
+
- Stop writing when you've made your point.
|
|
409
|
+
- BATCH READING: When asked to read multiple files, call ALL [TOOL:READ] commands in ONE response. Do NOT stop to analyze between files.`
|
|
311
410
|
}];
|
|
312
411
|
}
|
|
313
412
|
|
|
@@ -378,6 +477,7 @@ Do NOT just display content. Actually WRITE files using the tool.`
|
|
|
378
477
|
// Handle help command
|
|
379
478
|
if (input.toLowerCase() === '/help') {
|
|
380
479
|
console.log(chalk.cyan('\n📚 SAPPER COMMANDS:'));
|
|
480
|
+
console.log(chalk.white(' /scan') + chalk.gray(' - Scan entire codebase and add to context'));
|
|
381
481
|
console.log(chalk.white(' /reset, /clear') + chalk.gray(' - Clear all context and start fresh'));
|
|
382
482
|
console.log(chalk.white(' /prune') + chalk.gray(' - Remove old messages, keep last 4'));
|
|
383
483
|
console.log(chalk.white(' /context') + chalk.gray(' - Show current context size'));
|
|
@@ -407,6 +507,36 @@ Do NOT just display content. Actually WRITE files using the tool.`
|
|
|
407
507
|
continue;
|
|
408
508
|
}
|
|
409
509
|
|
|
510
|
+
// Handle codebase scan command
|
|
511
|
+
if (input.toLowerCase() === '/scan') {
|
|
512
|
+
console.log(chalk.cyan('\n🔍 Scanning codebase...'));
|
|
513
|
+
const scanResult = scanCodebase('.');
|
|
514
|
+
|
|
515
|
+
if (scanResult.files.length === 0) {
|
|
516
|
+
console.log(chalk.yellow('No code files found in current directory.'));
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const formattedScan = formatScanResults(scanResult);
|
|
521
|
+
const includedCount = scanResult.files.filter(f => !f.skipped).length;
|
|
522
|
+
const skippedCount = scanResult.files.filter(f => f.skipped).length;
|
|
523
|
+
|
|
524
|
+
console.log(chalk.green(`✅ Scanned ${includedCount} files (~${Math.round(scanResult.totalSize/1024)}KB)`));
|
|
525
|
+
if (skippedCount > 0) {
|
|
526
|
+
console.log(chalk.yellow(`⏭️ Skipped ${skippedCount} files (too large or limit reached)`));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Add scan to context
|
|
530
|
+
messages.push({
|
|
531
|
+
role: 'user',
|
|
532
|
+
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.`
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
fs.writeFileSync(CONTEXT_FILE, JSON.stringify(messages));
|
|
536
|
+
console.log(chalk.gray('📝 Codebase added to context. AI now has full picture.\n'));
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
410
540
|
messages.push({ role: 'user', content: input });
|
|
411
541
|
|
|
412
542
|
let toolRounds = 0; // Prevent infinite loops
|
|
@@ -429,7 +559,7 @@ Do NOT just display content. Actually WRITE files using the tool.`
|
|
|
429
559
|
spinner.stop();
|
|
430
560
|
|
|
431
561
|
let msg = '';
|
|
432
|
-
const MAX_RESPONSE_LENGTH =
|
|
562
|
+
const MAX_RESPONSE_LENGTH = 29000; // Guard against infinite loops (increased for multi-file reads)
|
|
433
563
|
|
|
434
564
|
process.stdout.write(chalk.white('Sapper: '));
|
|
435
565
|
for await (const chunk of response) {
|