natureco-cli 2.19.2 → 2.20.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/src/commands/code.js +266 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "2.19.2",
3
+ "version": "2.20.1",
4
4
  "description": "NatureCo AI Bot Terminal Interface",
5
5
  "main": "bin/natureco.js",
6
6
  "bin": {
@@ -2,6 +2,7 @@ const path = require('path');
2
2
  const os = require('os');
3
3
  const fs = require('fs');
4
4
  const readline = require('readline');
5
+ const { execSync } = require('child_process');
5
6
  const inquirer = require('inquirer');
6
7
  const chalk = require('chalk');
7
8
  const { getApiKey, getConfig } = require('../utils/config');
@@ -37,7 +38,131 @@ function stopSpinner(timer, label, success = true) {
37
38
  process.stdout.write(`\r ${success ? chalk.green('✓') : chalk.red('✗')} ${chalk.cyan(label)}\n`);
38
39
  }
39
40
 
40
- // ── Streaming fetch ───────────────────────────────────────────────────────────
41
+ // ── Proje indexing ────────────────────────────────────────────────────────────
42
+ const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '__pycache__', '.venv', 'venv', 'target', '.wrangler']);
43
+
44
+ function scanDir(dir, maxDepth, depth = 0) {
45
+ const results = [];
46
+ if (depth > maxDepth) return results;
47
+ let entries;
48
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return results; }
49
+ for (const entry of entries) {
50
+ if (entry.name.startsWith('.') && depth > 0) continue;
51
+ if (IGNORE_DIRS.has(entry.name)) continue;
52
+ const rel = depth === 0 ? entry.name : path.join(path.relative(dir.split(path.sep).slice(0, -depth).join(path.sep) || dir, dir), entry.name).replace(/\\/g, '/');
53
+ if (entry.isDirectory()) {
54
+ const sub = scanDir(path.join(dir, entry.name), maxDepth, depth + 1);
55
+ results.push(...sub.map(f => entry.name + '/' + f));
56
+ } else {
57
+ results.push(entry.name);
58
+ }
59
+ }
60
+ return results;
61
+ }
62
+
63
+ async function indexProject(projectDir) {
64
+ const index = {
65
+ dir: projectDir,
66
+ files: [],
67
+ type: 'unknown',
68
+ mainFiles: [],
69
+ packageJson: null,
70
+ gitBranch: null,
71
+ gitStatus: null,
72
+ };
73
+
74
+ // Dosyaları tara (max 2 seviye, ignore listesi hariç)
75
+ index.files = scanDir(projectDir, 2);
76
+
77
+ // Proje tipini tespit et
78
+ const fileSet = new Set(index.files);
79
+ if (fileSet.has('package.json')) {
80
+ try {
81
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf8'));
82
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
83
+ index.packageJson = {
84
+ name: pkg.name || path.basename(projectDir),
85
+ version: pkg.version || '0.0.0',
86
+ scripts: pkg.scripts || {},
87
+ dependencies: Object.keys(pkg.dependencies || {}).slice(0, 15),
88
+ };
89
+ if (deps.react || deps['react-dom']) index.type = 'react';
90
+ else if (deps.next) index.type = 'nextjs';
91
+ else if (deps.vue) index.type = 'vue';
92
+ else if (deps.express || deps.fastify || deps.koa) index.type = 'node-server';
93
+ else index.type = 'node';
94
+ } catch {}
95
+ } else if (fileSet.has('requirements.txt') || index.files.some(f => f.endsWith('.py'))) {
96
+ index.type = 'python';
97
+ } else if (fileSet.has('Cargo.toml')) {
98
+ index.type = 'rust';
99
+ } else if (fileSet.has('go.mod')) {
100
+ index.type = 'go';
101
+ } else if (index.files.some(f => f.endsWith('.ts') || f.endsWith('.tsx'))) {
102
+ index.type = 'typescript';
103
+ }
104
+
105
+ // Ana dosyaları bul
106
+ const mainCandidates = [
107
+ 'index.js', 'index.ts', 'main.js', 'main.ts', 'app.js', 'app.ts',
108
+ 'server.js', 'server.ts', 'src/index.js', 'src/index.ts',
109
+ 'src/main.ts', 'src/App.tsx', 'src/app.tsx',
110
+ ];
111
+ index.mainFiles = mainCandidates.filter(f => fileSet.has(f));
112
+
113
+ // Git bilgisi
114
+ try {
115
+ index.gitBranch = execSync('git branch --show-current', { cwd: projectDir, stdio: 'pipe' }).toString().trim();
116
+ const statusRaw = execSync('git status --short', { cwd: projectDir, stdio: 'pipe' }).toString().trim();
117
+ index.gitStatus = statusRaw ? statusRaw.split('\n').slice(0, 5) : [];
118
+ } catch {}
119
+
120
+ return index;
121
+ }
122
+
123
+ function buildIndexPrompt(index) {
124
+ const lines = [
125
+ `Proje Bilgisi:`,
126
+ `- Tip: ${index.type.toUpperCase()}`,
127
+ `- Dizin: ${index.dir}`,
128
+ `- Dosyalar (${index.files.length} toplam): ${index.files.slice(0, 40).join(', ')}`,
129
+ `- Ana dosyalar: ${index.mainFiles.join(', ') || 'bulunamadı'}`,
130
+ ];
131
+ if (index.packageJson) {
132
+ lines.push(`- Paket: ${index.packageJson.name} v${index.packageJson.version}`);
133
+ const scripts = Object.keys(index.packageJson.scripts);
134
+ if (scripts.length) lines.push(`- Scripts: ${scripts.join(', ')}`);
135
+ if (index.packageJson.dependencies.length) lines.push(`- Bağımlılıklar: ${index.packageJson.dependencies.join(', ')}`);
136
+ }
137
+ if (index.gitBranch) {
138
+ lines.push(`- Git branch: ${index.gitBranch}`);
139
+ lines.push(`- Değişiklikler: ${index.gitStatus?.length ? index.gitStatus.join(', ') : 'temiz'}`);
140
+ }
141
+ return lines.join('\n');
142
+ }
143
+
144
+ // ── Git helpers ───────────────────────────────────────────────────────────────
145
+ async function generateCommitMessage(diff, providerConfig) {
146
+ const body = {
147
+ model: providerConfig.model,
148
+ messages: [
149
+ { role: 'system', content: 'Sen bir git commit mesajı üreticisin. Conventional Commits formatında (feat/fix/refactor/chore vb.) kısa ve açıklayıcı bir commit mesajı yaz. Sadece mesajı yaz, başka hiçbir şey yazma.' },
150
+ { role: 'user', content: `Bu diff için commit mesajı üret:\n\n${diff}` },
151
+ ],
152
+ temperature: 0.3,
153
+ max_tokens: 100,
154
+ stream: false,
155
+ };
156
+ const res = await fetch(`${providerConfig.url}/chat/completions`, {
157
+ method: 'POST',
158
+ headers: { 'Authorization': `Bearer ${providerConfig.apiKey}`, 'Content-Type': 'application/json' },
159
+ body: JSON.stringify(body),
160
+ });
161
+ const data = await res.json();
162
+ return (data.choices?.[0]?.message?.content || 'chore: update files').trim().replace(/^["']|["']$/g, '');
163
+ }
164
+
165
+
41
166
  async function streamMessage(providerConfig, messages, tools) {
42
167
  const endpoint = `${providerConfig.url}/chat/completions`;
43
168
  const body = {
@@ -67,17 +192,15 @@ async function streamMessage(providerConfig, messages, tools) {
67
192
  const reader = response.body.getReader();
68
193
  const decoder = new TextDecoder();
69
194
  let fullText = '';
70
- let toolCallsBuffer = {}; // id -> { name, arguments }
195
+ let toolCallsBuffer = {};
71
196
 
72
197
  process.stdout.write('\n');
73
198
 
74
199
  while (true) {
75
200
  const { done, value } = await reader.read();
76
201
  if (done) break;
77
-
78
202
  const chunk = decoder.decode(value);
79
203
  const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
80
-
81
204
  for (const line of lines) {
82
205
  const data = line.slice(6).trim();
83
206
  if (data === '[DONE]') continue;
@@ -85,20 +208,11 @@ async function streamMessage(providerConfig, messages, tools) {
85
208
  const json = JSON.parse(data);
86
209
  const delta = json.choices?.[0]?.delta;
87
210
  if (!delta) continue;
88
-
89
- // Text content
90
- if (delta.content) {
91
- process.stdout.write(delta.content);
92
- fullText += delta.content;
93
- }
94
-
95
- // Tool call deltas
211
+ if (delta.content) { process.stdout.write(delta.content); fullText += delta.content; }
96
212
  if (delta.tool_calls) {
97
213
  for (const tc of delta.tool_calls) {
98
214
  const idx = tc.index ?? 0;
99
- if (!toolCallsBuffer[idx]) {
100
- toolCallsBuffer[idx] = { id: tc.id || '', name: '', arguments: '' };
101
- }
215
+ if (!toolCallsBuffer[idx]) toolCallsBuffer[idx] = { id: tc.id || '', name: '', arguments: '' };
102
216
  if (tc.id) toolCallsBuffer[idx].id = tc.id;
103
217
  if (tc.function?.name) toolCallsBuffer[idx].name += tc.function.name;
104
218
  if (tc.function?.arguments) toolCallsBuffer[idx].arguments += tc.function.arguments;
@@ -121,12 +235,11 @@ async function streamMessage(providerConfig, messages, tools) {
121
235
  return { text: fullText, toolCalls };
122
236
  }
123
237
 
124
- // ── Tool execution with spinner + confirm ─────────────────────────────────────
238
+ // ── Tool execution ────────────────────────────────────────────────────────────
125
239
  const DANGEROUS = ['rm ', 'rmdir', 'delete', 'DROP', 'truncate'];
126
240
 
127
241
  async function runToolCall(toolCall, stats) {
128
242
  const inputPreview = JSON.stringify(toolCall.input).slice(0, 60);
129
- const label = `${toolCall.name} ${chalk.gray(inputPreview)}`;
130
243
 
131
244
  const needsConfirm =
132
245
  toolCall.name === 'write_file' ||
@@ -149,7 +262,15 @@ async function runToolCall(toolCall, stats) {
149
262
  stopSpinner(spinner, toolCall.name, result.success !== false);
150
263
 
151
264
  if (result.success !== false) {
152
- if (toolCall.name === 'write_file') stats.filesChanged++;
265
+ if (toolCall.name === 'write_file') {
266
+ stats.filesChanged++;
267
+ stats.changedFiles.push(toolCall.input.path || '?');
268
+ console.log(chalk.green(` ✓ ${toolCall.input.path} güncellendi`));
269
+ if (stats.changedFiles.length === 1) {
270
+ // İlk değişiklikte ipucu göster
271
+ console.log(chalk.gray(` 💡 git add . && /commit ile kaydet`));
272
+ }
273
+ }
153
274
  if (toolCall.name === 'bash') stats.commandsRun++;
154
275
  stats.toolCallCount++;
155
276
  }
@@ -157,6 +278,40 @@ async function runToolCall(toolCall, stats) {
157
278
  return result;
158
279
  }
159
280
 
281
+ // ── Test runner ───────────────────────────────────────────────────────────────
282
+ async function runTests(projectIndex, conversationMessages, tools, providerConfig, displayBotName) {
283
+ const testScript = projectIndex.packageJson?.scripts?.test;
284
+ if (!testScript || testScript.includes('no test')) return;
285
+
286
+ const { confirm } = await inquirer.prompt([{
287
+ type: 'confirm', name: 'confirm',
288
+ message: chalk.yellow(' 🧪 Testleri çalıştıralım mı?'), default: true,
289
+ }]);
290
+ if (!confirm) return;
291
+
292
+ console.log(chalk.gray('\n npm test çalışıyor...\n'));
293
+ try {
294
+ const output = execSync('npm test', {
295
+ cwd: process.cwd(), timeout: 30000, stdio: 'pipe',
296
+ }).toString();
297
+ console.log(chalk.green(' ✅ Testler geçti!\n'));
298
+ } catch (err) {
299
+ const errOutput = (err.stdout?.toString() || err.message || '').slice(0, 500);
300
+ console.log(chalk.red(' ❌ Test hatası:\n'));
301
+ console.log(chalk.gray(' ' + errOutput.split('\n').join('\n ')));
302
+ console.log();
303
+
304
+ const { fix } = await inquirer.prompt([{
305
+ type: 'confirm', name: 'fix',
306
+ message: chalk.yellow(' Agent test hatasını düzeltsin mi?'), default: true,
307
+ }]);
308
+ if (fix) {
309
+ return `Test hatası oluştu:\n${errOutput}\nBu hatayı düzelt.`;
310
+ }
311
+ }
312
+ return null;
313
+ }
314
+
160
315
  // ── Main ──────────────────────────────────────────────────────────────────────
161
316
  async function code(targetFile, options = {}) {
162
317
  const apiKey = getApiKey();
@@ -202,25 +357,23 @@ async function code(targetFile, options = {}) {
202
357
 
203
358
  const shortModel = providerConfig.model.split('/').pop().split('-').slice(0, 3).join('-');
204
359
 
205
- // ── Proje context — dizini otomatik tara ────────────────────────────────────
206
- let projectContext = '';
207
- try {
208
- const dirResult = await executeTool('list_dir', { path: process.cwd() });
209
- if (dirResult.success !== false) {
210
- projectContext = `\nMevcut proje dizini: ${process.cwd()}\nDosyalar:\n${(dirResult.output || '').slice(0, 800)}`;
211
- }
212
- } catch {}
360
+ // ── Proje indexing ───────────────────────────────────────────────────────────
361
+ const indexSpinner = startSpinner('Proje indexleniyor...');
362
+ const projectIndex = await indexProject(process.cwd());
363
+ stopSpinner(indexSpinner, `${projectIndex.type.toUpperCase()} projesi — ${projectIndex.files.length} dosya`, true);
213
364
 
214
365
  // ── Sistem prompt ────────────────────────────────────────────────────────────
215
366
  const agentsPrompt = getAgentsPrompt();
216
367
  const memoryPrompt = getMemoryPrompt(bot.id);
368
+ const indexPrompt = buildIndexPrompt(projectIndex);
217
369
 
218
370
  let systemPrompt = `Sen NatureCo Code Agent'sın — güçlü bir AI coding asistanı.
219
371
  Kullanıcının projesinde gerçekten çalış: dosyaları oku, değiştir, komutları çalıştır.
220
372
  Her adımı Türkçe açıkla. Değişiklik yapmadan önce ne yapacağını söyle.
221
373
  Hataları yakala ve düzelt. İş bitince özet sun.
222
- Mevcut dizin: ${process.cwd()}
223
- Kullanıcı: ${userName}${projectContext}`;
374
+ Kullanıcı: ${userName}
375
+
376
+ ${indexPrompt}`;
224
377
 
225
378
  if (agentsPrompt) systemPrompt += `\n\n## Project Instructions\n${agentsPrompt}`;
226
379
  if (memoryPrompt) systemPrompt += '\n\n' + memoryPrompt;
@@ -238,12 +391,10 @@ Kullanıcı: ${userName}${projectContext}`;
238
391
  // ── Session ──────────────────────────────────────────────────────────────────
239
392
  const session = createSession(bot.id, bot.name);
240
393
  const conversationMessages = [{ role: 'system', content: systemPrompt }];
241
- const stats = { filesChanged: 0, commandsRun: 0, toolCallCount: 0, messageCount: 0 };
394
+ const stats = { filesChanged: 0, commandsRun: 0, toolCallCount: 0, messageCount: 0, changedFiles: [] };
242
395
 
243
- // ── MCP sunucularını başlat ──────────────────────────────────────────────────
244
396
  await startMcpServers().catch(() => {});
245
397
 
246
- // ── Tool definitions ─────────────────────────────────────────────────────────
247
398
  const localTools = getToolDefinitions();
248
399
  const tools = localTools.map(t => ({
249
400
  type: 'function',
@@ -271,6 +422,11 @@ Kullanıcı: ${userName}${projectContext}`;
271
422
  console.log();
272
423
  console.log(centerText(chalk.cyan(`(\\_/) ${displayBotName} hazır`) + chalk.gray(` · v${version}`)));
273
424
  console.log(sep());
425
+ console.log(centerText(chalk.gray(`📁 ${projectIndex.type.toUpperCase()} projesi · ${projectIndex.files.length} dosya`)));
426
+ if (projectIndex.gitBranch) {
427
+ const changes = projectIndex.gitStatus?.length || 0;
428
+ console.log(centerText(chalk.gray(`🌿 ${projectIndex.gitBranch} · ${changes} değişiklik`)));
429
+ }
274
430
  if (targetFile) console.log(centerText(chalk.gray(`📄 ${targetFile}`)));
275
431
  console.log(centerText(chalk.gray(`${shortModel} · /help · Ctrl+C çıkış`)));
276
432
  console.log(sep());
@@ -278,11 +434,21 @@ Kullanıcı: ${userName}${projectContext}`;
278
434
 
279
435
  // ── Session özeti ─────────────────────────────────────────────────────────
280
436
  function showSummary() {
281
- console.log(chalk.gray('\n─── Agent Session Özeti ───'));
437
+ const w = process.stdout.columns || 120;
438
+ console.log(chalk.gray('\n' + '─'.repeat(w)));
439
+ console.log(chalk.gray(' Agent Session Özeti'));
440
+ console.log(chalk.gray('─'.repeat(w)));
282
441
  console.log(` ${chalk.green('✓')} ${stats.filesChanged} dosya değiştirildi`);
283
442
  console.log(` ${chalk.green('✓')} ${stats.commandsRun} komut çalıştırıldı`);
284
443
  console.log(` ${chalk.green('✓')} ${stats.toolCallCount} tool çağrısı yapıldı`);
285
- console.log(` ${chalk.cyan('◉')} ${stats.messageCount} mesaj\n`);
444
+ console.log(` ${chalk.cyan('◉')} ${stats.messageCount} mesaj`);
445
+ if (stats.changedFiles.length > 0) {
446
+ console.log();
447
+ console.log(chalk.cyan(` 📝 Değiştirilen dosyalar (${stats.changedFiles.length}):`));
448
+ stats.changedFiles.forEach(f => console.log(chalk.white(` ${f}`)));
449
+ }
450
+ console.log(chalk.gray('─'.repeat(w)));
451
+ console.log();
286
452
  }
287
453
 
288
454
  // ── Mesaj gönder + tool loop ──────────────────────────────────────────────
@@ -290,7 +456,6 @@ Kullanıcı: ${userName}${projectContext}`;
290
456
  userMessage = userMessage.trim();
291
457
  if (!userMessage) return;
292
458
 
293
- // Slash komutları
294
459
  if (userMessage.startsWith('/')) {
295
460
  const [cmd] = userMessage.slice(1).split(' ');
296
461
  switch (cmd.toLowerCase()) {
@@ -300,6 +465,9 @@ Kullanıcı: ${userName}${projectContext}`;
300
465
  ['/clear', 'Ekranı temizle'],
301
466
  ['/summary', 'Session özetini göster'],
302
467
  ['/done', 'Bitir ve özet göster'],
468
+ ['/index', 'Projeyi yeniden indexle'],
469
+ ['/git', 'Git durumu ve son commitler'],
470
+ ['/commit', 'Staged değişiklikleri AI ile commit et'],
303
471
  ['/help', 'Bu yardım'],
304
472
  ].forEach(([c, d]) => console.log(' ' + chalk.cyan(c.padEnd(12)) + chalk.gray(d)));
305
473
  console.log(chalk.gray(' Ctrl+C'.padEnd(14) + 'Çıkış'));
@@ -313,6 +481,58 @@ Kullanıcı: ${userName}${projectContext}`;
313
481
  showSummary();
314
482
  if (cmd === 'done') process.exit(0);
315
483
  return;
484
+ case 'index': {
485
+ const sp = startSpinner('Proje yeniden indexleniyor...');
486
+ const newIndex = await indexProject(process.cwd());
487
+ stopSpinner(sp, `${newIndex.files.length} dosya indexlendi`, true);
488
+ // Sistem prompt'u güncelle
489
+ conversationMessages[0].content = conversationMessages[0].content.replace(
490
+ /Proje Bilgisi:[\s\S]*?(?=\n\n|$)/,
491
+ buildIndexPrompt(newIndex)
492
+ );
493
+ return;
494
+ }
495
+ case 'git': {
496
+ try {
497
+ const status = execSync('git status --short', { cwd: process.cwd(), stdio: 'pipe' }).toString().trim();
498
+ const log = execSync('git log --oneline -5', { cwd: process.cwd(), stdio: 'pipe' }).toString().trim();
499
+ console.log(chalk.yellow('\n Git Durumu:'));
500
+ console.log(status ? chalk.gray(' ' + status.split('\n').join('\n ')) : chalk.gray(' temiz'));
501
+ console.log(chalk.yellow('\n Son Commitler:'));
502
+ console.log(chalk.gray(' ' + log.split('\n').join('\n ')));
503
+ console.log();
504
+ } catch (e) {
505
+ console.log(chalk.red(' Git bulunamadı veya bu dizin bir git repo değil.\n'));
506
+ }
507
+ return;
508
+ }
509
+ case 'commit': {
510
+ try {
511
+ const diff = execSync('git diff --staged', { cwd: process.cwd(), stdio: 'pipe' }).toString();
512
+ if (!diff.trim()) {
513
+ console.log(chalk.red('\n Staged değişiklik yok.'));
514
+ console.log(chalk.gray(' Önce: git add .\n'));
515
+ return;
516
+ }
517
+ const sp = startSpinner('Commit mesajı üretiliyor...');
518
+ const commitMsg = await generateCommitMessage(diff.slice(0, 2000), providerConfig);
519
+ stopSpinner(sp, 'Commit mesajı hazır', true);
520
+ console.log(chalk.cyan(`\n Önerilen: ${chalk.white(commitMsg)}\n`));
521
+ const { ok } = await inquirer.prompt([{
522
+ type: 'confirm', name: 'ok',
523
+ message: ' Commit edilsin mi?', default: true,
524
+ }]);
525
+ if (ok) {
526
+ execSync(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, { cwd: process.cwd(), stdio: 'pipe' });
527
+ console.log(chalk.green(' ✓ Commit yapıldı!\n'));
528
+ } else {
529
+ console.log(chalk.gray(' İptal edildi.\n'));
530
+ }
531
+ } catch (e) {
532
+ console.log(chalk.red(` Git hatası: ${e.message}\n`));
533
+ }
534
+ return;
535
+ }
316
536
  default:
317
537
  console.log(chalk.red(`Bilinmeyen komut: /${cmd}\n`));
318
538
  return;
@@ -326,10 +546,10 @@ Kullanıcı: ${userName}${projectContext}`;
326
546
 
327
547
  stats.messageCount++;
328
548
  console.log(chalk.white('You ') + userMessage);
329
-
330
- // Mesajı history'e ekle
331
549
  conversationMessages.push({ role: 'user', content: userMessage });
332
550
 
551
+ const prevFilesChanged = stats.filesChanged;
552
+
333
553
  // ── Streaming + tool loop (max 20 iter) ──────────────────────────────────
334
554
  let iter = 0;
335
555
  while (iter < 20) {
@@ -341,11 +561,10 @@ Kullanıcı: ${userName}${projectContext}`;
341
561
  streamResult = await streamMessage(providerConfig, conversationMessages, tools);
342
562
  } catch (err) {
343
563
  console.log(chalk.red(`\nError: ${err.message}\n`));
344
- conversationMessages.pop(); // başarısız user mesajını geri al
564
+ conversationMessages.pop();
345
565
  return;
346
566
  }
347
567
 
348
- // Assistant mesajını history'e ekle
349
568
  const assistantMsg = { role: 'assistant', content: streamResult.text || '' };
350
569
  if (streamResult.toolCalls?.length) {
351
570
  assistantMsg.tool_calls = streamResult.toolCalls.map(tc => ({
@@ -356,10 +575,8 @@ Kullanıcı: ${userName}${projectContext}`;
356
575
  }
357
576
  conversationMessages.push(assistantMsg);
358
577
 
359
- // Tool call yoksa bitti
360
578
  if (!streamResult.toolCalls?.length) break;
361
579
 
362
- // Tool call'ları çalıştır
363
580
  console.log(chalk.yellow(`\n🔧 ${streamResult.toolCalls.length} tool çalıştırılıyor...\n`));
364
581
 
365
582
  for (const toolCall of streamResult.toolCalls) {
@@ -385,6 +602,15 @@ Kullanıcı: ${userName}${projectContext}`;
385
602
  addToHistory(bot.id, userMessage, lastAssistant.content, null);
386
603
  addMessageToSession(bot.id, session.id, userMessage, lastAssistant.content);
387
604
  }
605
+
606
+ // ── Test runner — dosya değiştiyse sor ───────────────────────────────────
607
+ if (stats.filesChanged > prevFilesChanged && projectIndex.packageJson?.scripts?.test) {
608
+ const fixPrompt = await runTests(projectIndex, conversationMessages, tools, providerConfig, displayBotName);
609
+ if (fixPrompt) {
610
+ // Test hatasını agent'a gönder
611
+ await handleMessage(fixPrompt);
612
+ }
613
+ }
388
614
  }
389
615
 
390
616
  // ── Input loop ───────────────────────────────────────────────────────────────