natureco-cli 5.62.0 → 5.64.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/bin/natureco.js +10 -3
  3. package/package.json +8 -5
  4. package/scripts/benchmark-startup.js +26 -0
  5. package/src/commands/backup.js +3 -3
  6. package/src/commands/chat.js +17 -17
  7. package/src/commands/code.js +49 -14
  8. package/src/commands/code_v5.js +15 -1
  9. package/src/commands/gateway-server.js +128 -24
  10. package/src/commands/git.js +2 -2
  11. package/src/commands/pairing.js +2 -22
  12. package/src/commands/repl.js +118 -112
  13. package/src/tools/agentic-runner.js +41 -33
  14. package/src/tools/memory_write.js +16 -12
  15. package/src/tools/structural_patch.js +25 -0
  16. package/src/tools/workflow.js +10 -2
  17. package/src/utils/agent-core.js +51 -0
  18. package/src/utils/agent-workspace.js +24 -0
  19. package/src/utils/api.js +12 -8
  20. package/src/utils/channel-sdk.js +212 -0
  21. package/src/utils/code-intelligence.js +69 -0
  22. package/src/utils/coding-session.js +54 -0
  23. package/src/utils/delivery-store.js +34 -0
  24. package/src/utils/i18n.js +7 -1
  25. package/src/utils/json-schema.js +43 -0
  26. package/src/utils/lsp-client.js +129 -0
  27. package/src/utils/memory-record.js +49 -0
  28. package/src/utils/pairing-store.js +55 -0
  29. package/src/utils/pattern-detector.js +13 -3
  30. package/src/utils/plugin-registry.js +3 -3
  31. package/src/utils/process-errors.js +14 -7
  32. package/src/utils/runtime-health.js +28 -0
  33. package/src/utils/secret-store.js +90 -0
  34. package/src/utils/secure-sync.js +63 -0
  35. package/src/utils/skill-lifecycle.js +59 -0
  36. package/src/utils/structural-patch.js +68 -0
  37. package/src/utils/sub-agent.js +13 -2
  38. package/src/utils/test-failure-analyzer.js +64 -0
  39. package/src/utils/tool-execution-gateway.js +56 -0
  40. package/src/utils/tool-manifest.js +31 -0
  41. package/src/utils/tool-path-policy.js +49 -0
  42. package/src/utils/tool-result.js +17 -0
  43. package/src/utils/tool-runner.js +81 -53
  44. package/src/utils/tools.js +30 -42
package/CHANGELOG.md CHANGED
@@ -1,6 +1,39 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to NatureCo CLI will be documented in this file.
3
+ All notable changes to NatureCo CLI will be documented in this file.
4
+
5
+ ## [5.64.0] - 2026-07-13 — Secure unified agent and operations foundation
6
+
7
+ ### Added
8
+ - Unified `AgentCore`, `ToolExecutionGateway`, single tool manifest, mandatory JSON Schema validation and standardized tool results.
9
+ - Conflict-safe structural patching, atomic rollback, coding `/undo`, `/retry`, `/compact`, local code intelligence and LSP JSON-RPC client.
10
+ - Provenance/confidence/TTL memory records, conflict resolution, approved skill promotion, versioning and rollback.
11
+ - Shared channel SDK with pairing-by-default, persistent idempotent delivery queue, retries, dead letters, reconnect supervision, health and metrics.
12
+ - macOS Keychain, Windows DPAPI, Linux Secret Service and AES-256-GCM encrypted secret fallback.
13
+ - Encrypted multi-device sync primitives with authenticated envelopes and vector-clock conflict detection.
14
+ - Isolated sub-agent worktrees, test-failure analysis/repair loop, startup benchmark and TR/EN catalog snapshots.
15
+
16
+ ### Security
17
+ - Fixed bulk-write guardrail bypasses, protected sensitive paths across execution origins and enabled guardrail hard-stop.
18
+ - Removed high-risk shell string interpolation from iMessage delivery, backups, plugin installation/cloning and AI-generated Git commits.
19
+ - Added channel pairing gates, non-interactive fail-closed permission handling and process-listener cleanup.
20
+
21
+ ### Tests
22
+ - 73 test files, 711 passing tests (3 skipped), zero high-severity audit findings, CLI smoke and package checks.
23
+ - Windows `--version` median reduced from ~366 ms to ~75 ms, meeting the <100 ms target.
24
+ - Real Windows/macOS/Linux GitHub Actions matrix with lint, tests, smoke, audit and package verification.
25
+
26
+ ## [5.63.0] - 2026-07-12 — Complete English REPL localization
27
+
28
+ ### Changed
29
+ - Completed the interactive REPL's Turkish/English localization across help, session headers, memory, plan review, identity prompts, command descriptions and workflow summaries.
30
+ - Localized default user, assistant and empty-session labels for new English installations without changing existing saved personas.
31
+ - Added English identity-question handling and language-aware internal plan/workflow instructions so responses remain in the selected interface language.
32
+ - Removed the unused legacy `mattermost` client, upgraded `node-telegram-bot-api` to 1.1.2 and Discord to 14.26.5, and pinned its compatible patched `undici` 6.27.0 runtime.
33
+
34
+ ### Tests
35
+ - Added a dedicated English REPL regression suite that prevents untranslated Turkish help text from returning.
36
+ - Full validation: 49 test files, 640 passing tests (3 skipped), ESLint, CLI smoke test and npm package dry run.
4
37
 
5
38
  ## [5.51.4] - 2026-07-11 — "account: OTP kodu magiclink tipini de dener"
6
39
 
package/bin/natureco.js CHANGED
@@ -1,6 +1,13 @@
1
- #!/usr/bin/env node
2
-
3
- const { Command } = require('commander');
1
+ #!/usr/bin/env node
2
+
3
+ // Fast path: version checks are used by shells, installers and health probes.
4
+ // Avoid loading Commander and ~100 command modules for a constant-time answer.
5
+ if (process.argv.length === 3 && (process.argv[2] === '--version' || process.argv[2] === '-V')) {
6
+ process.stdout.write(require('../package.json').version + '\n');
7
+ process.exit(0);
8
+ }
9
+
10
+ const { Command } = require('commander');
4
11
  const chalk = require('chalk');
5
12
  const packageJson = require('../package.json');
6
13
  // Küresel çökme/EPIPE yakalayıcıları — ham Node stack yerine düzgün mesaj + audit log
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.62.0",
4
- "description": "OpenClaw'dan daha güvenli, daha hızlı, daha ucuz AI agent CLI. Multi-agent, self-evolving skills, audit log, maliyet optimizasyonu ve NatureCo platform-native.",
3
+ "version": "5.64.0",
4
+ "description": "Terminal-native AI agent CLI with bilingual TR/EN UI, multi-agent orchestration, persistent memory, secure tools and messaging integrations.",
5
5
  "bin": {
6
6
  "natureco": "bin/natureco.js"
7
7
  },
@@ -22,6 +22,7 @@
22
22
  "test:unit": "vitest run",
23
23
  "test:watch": "vitest",
24
24
  "test:coverage": "vitest run --coverage",
25
+ "perf:startup": "node scripts/benchmark-startup.js",
25
26
  "lint": "eslint src/ bin/ test/",
26
27
  "lint:fix": "eslint src/ bin/ test/ --fix",
27
28
  "lint:errors-only": "eslint src/ bin/ test/ --quiet",
@@ -74,11 +75,10 @@
74
75
  "@whiskeysockets/baileys": "^7.0.0-rc10",
75
76
  "chalk": "^4.1.2",
76
77
  "commander": "^11.1.0",
77
- "discord.js": "^14.26.4",
78
+ "discord.js": "^14.26.5",
78
79
  "irc": "^0.5.2",
79
- "mattermost": "^3.4.0",
80
80
  "node-cron": "^4.2.1",
81
- "node-telegram-bot-api": "^0.67.0",
81
+ "node-telegram-bot-api": "^1.1.2",
82
82
  "openai": "^6.45.0",
83
83
  "pino": "^8.21.0",
84
84
  "qrcode": "^1.5.4",
@@ -93,5 +93,8 @@
93
93
  "eslint": "^10.6.0",
94
94
  "globals": "^15.15.0",
95
95
  "vitest": "^4.1.9"
96
+ },
97
+ "overrides": {
98
+ "undici": "6.27.0"
96
99
  }
97
100
  }
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { spawnSync } = require('child_process');
4
+ const path = require('path');
5
+
6
+ const bin = path.join(__dirname, '..', 'bin', 'natureco.js');
7
+ function measure(args, runs = 7) {
8
+ const samples = [];
9
+ for (let i = 0; i < runs; i++) {
10
+ const start = process.hrtime.bigint();
11
+ const result = spawnSync(process.execPath, [bin, ...args], { encoding: 'utf8', env: { ...process.env, NATURECO_NO_UPDATE_CHECK: '1', FORCE_COLOR: '0' } });
12
+ const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
13
+ if (result.status !== 0) throw new Error(`${args.join(' ')} failed: ${result.stderr}`);
14
+ samples.push(durationMs);
15
+ }
16
+ samples.sort((a, b) => a - b);
17
+ return { min: samples[0], median: samples[Math.floor(samples.length / 2)], p95: samples[Math.min(samples.length - 1, Math.ceil(samples.length * 0.95) - 1)], samples };
18
+ }
19
+
20
+ const version = measure(['--version']);
21
+ const report = { timestamp: new Date().toISOString(), node: process.version, platform: process.platform, version };
22
+ console.log(JSON.stringify(report, null, 2));
23
+ if (version.median >= 100) {
24
+ console.error(`--version median ${version.median.toFixed(1)}ms exceeds 100ms target`);
25
+ process.exitCode = 1;
26
+ }
@@ -4,7 +4,7 @@ const { getLang: _gl } = require('../utils/i18n');
4
4
  const L = (tr, en) => (_gl() === 'en' ? en : tr);
5
5
  const path = require('path');
6
6
  const os = require('os');
7
- const { execSync } = require('child_process');
7
+ const { execFileSync } = require('child_process');
8
8
 
9
9
  const NATURECO_DIR = path.join(os.homedir(), '.natureco');
10
10
 
@@ -32,7 +32,7 @@ function createBackup() {
32
32
 
33
33
  if (fs.existsSync(NATURECO_DIR)) {
34
34
  try {
35
- execSync(`tar -czf "${backupFile}" -C "${path.dirname(NATURECO_DIR)}" "${path.basename(NATURECO_DIR)}"`, {
35
+ execFileSync('tar', ['-czf', backupFile, '-C', path.dirname(NATURECO_DIR), path.basename(NATURECO_DIR)], {
36
36
  stdio: 'pipe',
37
37
  encoding: 'utf8',
38
38
  timeout: 30000
@@ -97,7 +97,7 @@ function restoreBackup(file) {
97
97
 
98
98
  console.log(chalk.yellow(`\n ⚠️ Restoring ${file}...\n`));
99
99
  try {
100
- execSync(`tar -xzf "${backupFile}" -C "${os.homedir()}"`, { stdio: 'pipe', timeout: 15000 });
100
+ execFileSync('tar', ['-xzf', backupFile, '-C', os.homedir()], { stdio: 'pipe', timeout: 15000 });
101
101
  console.log(chalk.green(' ✅ Restore complete\n'));
102
102
  } catch {
103
103
  console.log(chalk.red(' ❌ Restore failed\n'));
@@ -87,8 +87,8 @@ async function chat(botName, options = {}) {
87
87
  console.log('');
88
88
  console.log(tui.styled(' 🌿 NatureCo Chat v4.6+', { color: tui.PALETTE.primary, bold: true }));
89
89
  console.log(tui.styled(' ' + '─'.repeat(56), { color: tui.PALETTE.border }));
90
- console.log(tui.C.muted(' Chat komutu artık ') + tui.C.brand('repl') + tui.C.muted(' komutunu çağırıyor (Phase 9 TUI engine)'));
91
- console.log(tui.C.muted(' Tüm özellikler korundu: memory, sessions, hooks, custom commands'));
90
+ console.log(tui.C.muted(L(' Chat komutu artık ', ' The chat command now ')) + tui.C.brand('repl') + tui.C.muted(L(' komutunu çağırıyor (Phase 9 TUI engine)', ' command (Phase 9 TUI engine)')));
91
+ console.log(tui.C.muted(L(' Tüm özellikler korundu: memory, sessions, hooks, custom commands', ' All features preserved: memory, sessions, hooks, custom commands')));
92
92
  console.log('');
93
93
  return repl(replArgs);
94
94
  }
@@ -97,11 +97,11 @@ async function chat(botName, options = {}) {
97
97
  if (options.list) {
98
98
  const sessions = listSessions('chat');
99
99
  if (!sessions.length) {
100
- console.log(chalk.gray('\nKayıtlı oturum yok.\n'));
100
+ console.log(chalk.gray(L('\nKayıtlı oturum yok.\n', '\nNo saved sessions.\n')));
101
101
  return;
102
102
  }
103
103
  sessions.forEach(s => {
104
- console.log(` [${s.id}] ${s.savedAt.slice(0, 10)} — ${s.preview || '(boş)'} (${s.messageCount} mesaj)`);
104
+ console.log(` [${s.id}] ${s.savedAt.slice(0, 10)} — ${s.preview || L('(boş)', '(empty)')} (${s.messageCount} ${L('mesaj', 'messages')})`);
105
105
  });
106
106
  console.log();
107
107
  return;
@@ -130,7 +130,7 @@ async function chat(botName, options = {}) {
130
130
  const { selectedBot } = await inquirer.prompt([{
131
131
  type: 'list',
132
132
  name: 'selectedBot',
133
- message: 'Bot seçin:',
133
+ message: L('Bot seçin:', 'Select bot:'),
134
134
  choices: botList.bots.map(b => ({ name: b.name, value: b.id })),
135
135
  }]);
136
136
  bot = botList.bots.find(b => b.id === selectedBot);
@@ -174,7 +174,7 @@ async function chat(botName, options = {}) {
174
174
  } else if (options.continue) {
175
175
  const last = loadLastSession('chat');
176
176
  if (last) {
177
- console.log(chalk.cyan(` ↻ Son oturum yüklendi (${last.messages.length} mesaj)\n`));
177
+ console.log(chalk.cyan(` ↻ ${L('Son oturum yüklendi', 'Last session loaded')} (${last.messages.length} ${L('mesaj', 'messages')})\n`));
178
178
  }
179
179
  session = createSession(bot.id, bot.name);
180
180
  } else {
@@ -195,7 +195,7 @@ async function chat(botName, options = {}) {
195
195
  console.clear();
196
196
  console.log(centerText(ASCII_LOGO.map((line, i) => i < 5 ? chalk.green(line) : chalk.gray(line)).join('\n')));
197
197
  console.log();
198
- console.log(centerText(chalk.cyan(`(\\_/) Hoş geldin, ${userName} · ${displayBotName} hazır · v${version}`)));
198
+ console.log(centerText(chalk.cyan(`(\\_/) ${L('Hoş geldin', 'Welcome')}, ${userName} · ${displayBotName} ${L('hazır', 'ready')} · v${version}`)));
199
199
  console.log(chalk.gray('─'.repeat(process.stdout.columns || 120)));
200
200
  console.log();
201
201
 
@@ -224,7 +224,7 @@ async function chat(botName, options = {}) {
224
224
  });
225
225
  }
226
226
 
227
- console.log(centerText(chalk.gray(`${shortModel} · /help için yardım · Ctrl+C çıkış`)));
227
+ console.log(centerText(chalk.gray(`${shortModel} · /help ${L('için yardım', 'for help')} · Ctrl+C ${L('çıkış', 'exit')}`)));
228
228
  console.log(chalk.gray('─'.repeat(process.stdout.columns || 120)));
229
229
  console.log();
230
230
 
@@ -273,7 +273,7 @@ async function chat(botName, options = {}) {
273
273
  bot = newBot;
274
274
  conversationId = null;
275
275
  session = createSession(bot.id, bot.name);
276
- console.log(chalk.green(`Bot değişti: ${newBot.name}`));
276
+ console.log(chalk.green(`${L('Bot değişti', 'Bot changed')}: ${newBot.name}`));
277
277
  } else {
278
278
  console.log(chalk.red(`${L('Bot bulunamadı', 'Bot not found')}: ${newName}`));
279
279
  }
@@ -282,18 +282,18 @@ async function chat(botName, options = {}) {
282
282
  return;
283
283
  case 'skills':
284
284
  const skills = getSkills();
285
- if (!skills.length) console.log(chalk.gray('Yüklü skill yok.'));
285
+ if (!skills.length) console.log(chalk.gray(L('Yüklü skill yok.', 'No skills installed.')));
286
286
  else skills.forEach(s => console.log(chalk.cyan(`· ${s.name}`) + chalk.gray(` ${s.description}`)));
287
287
  console.log();
288
288
  return;
289
289
  case 'memory':
290
290
  if (args[0] === 'clear') {
291
291
  clearMemory(bot.id);
292
- console.log(chalk.green('✓ Hafıza temizlendi'));
292
+ console.log(chalk.green(L('✓ Hafıza temizlendi', '✓ Memory cleared')));
293
293
  } else {
294
294
  const m = loadMemory(bot.id);
295
295
  if (m.botName) console.log(chalk.cyan('Bot: ') + m.botName);
296
- if (m.name) console.log(chalk.cyan('İsim: ') + m.name);
296
+ if (m.name) console.log(chalk.cyan(L('İsim: ', 'Name: ')) + m.name);
297
297
  (m.facts || []).slice(0, 8).forEach(f => {
298
298
  const v = typeof f === 'string' ? f : f.value;
299
299
  console.log(chalk.gray(`· ${v}`));
@@ -317,7 +317,7 @@ async function chat(botName, options = {}) {
317
317
  return;
318
318
  case 'commands':
319
319
  const cmds = getCommands();
320
- if (!cmds.length) console.log(chalk.gray('Özel komut yok.'));
320
+ if (!cmds.length) console.log(chalk.gray(L('Özel komut yok.', 'No custom commands.')));
321
321
  else cmds.forEach(c => console.log(chalk.cyan(`/${c.name}`)));
322
322
  console.log();
323
323
  return;
@@ -406,10 +406,10 @@ async function chat(botName, options = {}) {
406
406
  saveSession('chat', historyMessages, { botId: bot.id, botName: bot.name });
407
407
  }
408
408
  if (filesChanged > 0 || commandsRun > 0 || messagesCount > 0) {
409
- console.log(chalk.gray('\n─── Session Özeti ───'));
410
- if (filesChanged > 0) console.log(chalk.green(` ✓ ${filesChanged} dosya değiştirildi`));
411
- if (commandsRun > 0) console.log(chalk.green(` ✓ ${commandsRun} komut çalıştırıldı`));
412
- console.log(chalk.cyan(` ✓ ${messagesCount} mesaj gönderildi`));
409
+ console.log(chalk.gray(L('\n─── Session Özeti ───', '\n─── Session Summary ───')));
410
+ if (filesChanged > 0) console.log(chalk.green(` ✓ ${filesChanged} ${L('dosya değiştirildi', 'files changed')}`));
411
+ if (commandsRun > 0) console.log(chalk.green(` ✓ ${commandsRun} ${L('komut çalıştırıldı', 'commands run')}`));
412
+ console.log(chalk.cyan(` ✓ ${messagesCount} ${L('mesaj gönderildi', 'messages sent')}`));
413
413
  console.log();
414
414
  }
415
415
  console.log(chalk.gray('👋 Goodbye!\n'));
@@ -2,7 +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
+ const { execSync, execFileSync } = require('child_process');
6
6
  const inquirer = require('../utils/inquirer-wrapper');
7
7
  const TB = require('../utils/token-budget');
8
8
  const chalk = require('chalk');
@@ -14,7 +14,11 @@ const { getMemoryPrompt, loadMemory } = require('../utils/memory');
14
14
  const { getAgentsPrompt } = require('../utils/agents');
15
15
  const { createSession, addMessageToSession } = require('../utils/sessions');
16
16
  const { addToHistory } = require('../utils/history');
17
- const { getToolDefinitions, executeTool } = require('../utils/tool-runner');
17
+ const { getToolDefinitions, executeTool } = require('../utils/tool-runner');
18
+ const { AgentCore } = require('../utils/agent-core');
19
+ const { CodingSession } = require('../utils/coding-session');
20
+
21
+ const agentCore = new AgentCore({ maxIterations: 10 });
18
22
 
19
23
  let rl = null;
20
24
 
@@ -224,12 +228,17 @@ async function streamMessage(providerConfig, messages, tools) {
224
228
  // ── Tool execution ────────────────────────────────────────────────────────────
225
229
  const DANGEROUS = [/\brm\b/, /\brmdir\b/, /\bdelete\b/i, /\bdrop\b/i, /\btruncate\b/i];
226
230
 
227
- async function runToolCall(toolCall, stats, dryRun = false) {
231
+ async function runToolCall(toolCall, stats, dryRun = false, codingSession = null) {
232
+ const guard = agentCore.assess({ name: toolCall.name, input: toolCall.input });
233
+ if (guard.blocked) return { success: false, error: guard.reason || `blocked_by_guardrails: ${toolCall.name}` };
228
234
  const inputPreview = JSON.stringify(toolCall.input).slice(0, 60);
229
235
 
230
- const needsConfirm =
236
+ const needsConfirm =
231
237
  toolCall.name === 'write_file' ||
232
- (toolCall.name === 'bash' && DANGEROUS.some(re => re.test(toolCall.input.command || '')));
238
+ (toolCall.name === 'bash' && DANGEROUS.some(re => re.test(toolCall.input.command || '')));
239
+
240
+ const risk = codingSession?.riskSummary(toolCall);
241
+ if (risk && risk.level !== 'low') console.log(chalk.gray(` Risk: ${risk.level} (${risk.risks.join(', ')})`));
233
242
 
234
243
  // Dry-run: write_file'ı engelle, diff göster
235
244
  if (dryRun && toolCall.name === 'write_file') {
@@ -261,8 +270,12 @@ async function runToolCall(toolCall, stats, dryRun = false) {
261
270
  }
262
271
  }
263
272
 
264
- const spinner = startSpinner(`${toolCall.name} ${inputPreview}`);
265
- const result = await executeTool(toolCall.name, toolCall.input);
273
+ const spinner = startSpinner(`${toolCall.name} ${inputPreview}`);
274
+ if (!dryRun && codingSession && (toolCall.name === 'write_file' || toolCall.name === 'edit_file')) {
275
+ codingSession.capture(toolCall.input.path);
276
+ }
277
+ const result = await executeTool(toolCall.name, toolCall.input);
278
+ agentCore.record({ name: toolCall.name, input: toolCall.input }, result);
266
279
  stopSpinner(spinner, toolCall.name, result.success !== false);
267
280
 
268
281
  if (result.success !== false) {
@@ -310,7 +323,8 @@ async function runTests(projectIndex, conversationMessages, tools, providerConfi
310
323
  }
311
324
 
312
325
  // ── Main ──────────────────────────────────────────────────────────────────────
313
- async function code(targetFile, options = {}) {
326
+ async function code(targetFile, options = {}) {
327
+ const codingSession = new CodingSession();
314
328
  const workDir = options.dir || process.cwd();
315
329
  const apiKey = getApiKey();
316
330
  const config = getConfig();
@@ -507,15 +521,35 @@ ${indexPrompt}`);
507
521
  ['/run <cmd>',L('Komutu çalıştır, hata varsa düzelt', 'Run command, fix errors if any')],
508
522
  ['/test', L('Testleri çalıştır, hata varsa düzelt', 'Run tests, fix errors if any')],
509
523
  ['/git', L('Git durumu ve son commitler', 'Git status and recent commits')],
510
- ['/commit', L('Staged değişiklikleri AI ile commit et', 'Commit staged changes with AI')],
524
+ ['/commit', L('Staged değişiklikleri AI ile commit et', 'Commit staged changes with AI')],
525
+ ['/undo', L('Son dosya değişikliğini geri al', 'Undo the last file change')],
526
+ ['/retry', L('Son isteği yeniden çalıştır', 'Retry the last request')],
527
+ ['/compact', L('Konuşma bağlamını sıkıştır', 'Compact conversation context')],
511
528
  ['/help', L('Bu yardım', 'This help')],
512
529
  ].forEach(([c, d]) => console.log(' ' + chalk.cyan(c.padEnd(12)) + chalk.gray(d)));
513
530
  console.log(chalk.gray(' Ctrl+C'.padEnd(14) + L('Çıkış', 'Exit')));
514
531
  console.log();
515
532
  return;
516
- case 'clear':
533
+ case 'clear':
517
534
  console.clear();
518
- return;
535
+ return;
536
+ case 'undo': {
537
+ const undone = codingSession.undo();
538
+ console.log(undone.ok ? chalk.green(` ✓ ${undone.path}`) : chalk.yellow(` ${undone.error}`));
539
+ return;
540
+ }
541
+ case 'compact': {
542
+ const compacted = codingSession.compact(conversationMessages);
543
+ conversationMessages.splice(0, conversationMessages.length, ...compacted.messages);
544
+ console.log(chalk.green(` ✓ Context compacted: ${compacted.before} → ${compacted.after}`));
545
+ return;
546
+ }
547
+ case 'retry': {
548
+ const previous = codingSession.retryMessage();
549
+ if (!previous) console.log(chalk.yellow(L(' Tekrarlanacak istek yok.', ' No request to retry.')));
550
+ else await handleMessage(previous);
551
+ return;
552
+ }
519
553
  case 'summary':
520
554
  case 'done': {
521
555
  const sum = await generateSummary(conversationMessages, providerConfig);
@@ -591,7 +625,7 @@ ${indexPrompt}`);
591
625
  console.log(chalk.cyan(`\n ${L('Önerilen', 'Suggested')}: ${chalk.white(commitMsg)}\n`));
592
626
  const ok = await confirmAction(L('Commit edilsin mi?', 'Commit?'));
593
627
  if (ok) {
594
- execSync(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, { cwd: workDir, stdio: 'pipe' });
628
+ execFileSync('git', ['commit', '-m', commitMsg], { cwd: workDir, stdio: 'pipe' });
595
629
  console.log(chalk.green(L(' ✓ Commit yapıldı!\n', ' ✓ Committed!\n')));
596
630
  } else {
597
631
  console.log(chalk.gray(L(' İptal edildi.\n', ' Cancelled.\n')));
@@ -607,7 +641,8 @@ ${indexPrompt}`);
607
641
  }
608
642
  }
609
643
 
610
- stats.messageCount++;
644
+ stats.messageCount++;
645
+ codingSession.rememberUserMessage(userMessage);
611
646
  console.log(chalk.white('You ') + userMessage);
612
647
  conversationMessages.push({ role: 'user', content: userMessage });
613
648
 
@@ -655,7 +690,7 @@ ${indexPrompt}`);
655
690
 
656
691
  for (let ti = 0; ti < streamResult.toolCalls.length; ti++) {
657
692
  const toolCall = streamResult.toolCalls[ti];
658
- const result = await runToolCall(toolCall, stats, options.dryRun);
693
+ const result = await runToolCall(toolCall, stats, options.dryRun, codingSession);
659
694
  const resultStr = result.success !== false
660
695
  ? (result.output || JSON.stringify(result))
661
696
  : `${L('Hata', 'Error')}: ${result.error}`;
@@ -33,6 +33,9 @@ const { getFallbackChain } = require("../utils/fallback-chain");
33
33
  const { getTaskManager } = require("../utils/tasks");
34
34
  const { buildTiers, assemble, discoverProjectRules } = require("../utils/system-prompt");
35
35
  const { buildSkillIndex } = require("../utils/skill-index");
36
+ const { AgentCore } = require("../utils/agent-core");
37
+
38
+ const agentCore = new AgentCore({ maxIterations: 10 });
36
39
 
37
40
  const IS_MAC = os.platform() === "darwin";
38
41
  const MAX_ITERATIONS = 10;
@@ -624,9 +627,16 @@ async function codeV5(targetPath) {
624
627
  const PARALLEL_SAFE_TOOLS = new Set(['read_file', 'file_search', 'grep_search', 'web_search', 'web_readability', 'duckduckgo_search', 'exa_search', 'searxng_search', 'firecrawl', 'memory_search', 'memory']);
625
628
 
626
629
  async function processToolCalls(reply, config, toolDefs, messages, onToolResult) {
630
+ agentCore.startIteration();
631
+ const coreBlocked = new Map();
627
632
  for (const tc of reply.tool_calls) {
628
633
  let args = {};
629
634
  try { args = JSON.parse(tc.function.arguments || "{}"); } catch {}
635
+ const guard = agentCore.assess({ name: tc.function.name, input: args });
636
+ if (guard.blocked) {
637
+ coreBlocked.set(tc.id, guard.reason || "blocked_by_guardrails");
638
+ continue;
639
+ }
630
640
  const risk = assessRisk(tc.function.name, args);
631
641
 
632
642
  // Built-in risk assessment
@@ -687,7 +697,11 @@ async function processToolCalls(reply, config, toolDefs, messages, onToolResult)
687
697
 
688
698
  messages.push({ role: "assistant", content: reply.content || null, tool_calls: reply.tool_calls });
689
699
 
690
- const parsed = reply.tool_calls.map(tc => {
700
+ for (const [id, reason] of coreBlocked) {
701
+ messages.push({ role: "tool", tool_call_id: id, content: "ERROR: " + reason });
702
+ }
703
+
704
+ const parsed = reply.tool_calls.filter(tc => !coreBlocked.has(tc.id)).map(tc => {
691
705
  let args = {};
692
706
  try { args = JSON.parse(tc.function.arguments || "{}"); } catch {}
693
707
  return { name: tc.function.name, args, id: tc.id };