natureco-cli 5.38.0 → 5.40.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to NatureCo CLI will be documented in this file.
4
4
 
5
+ ## [5.40.0] - 2026-07-08 — "CROSS-SESSION HAFIZA bozulması düzeltildi (gerçek macOS SSH testinde bulundu)"
6
+
7
+ Gerçek MacBook'a SSH ile bağlanıp canlı test yapılırken bulundu: kullanıcı "projemin gizli kod adı ONYX-7'yi hatırla" dediğinde, yeni oturumda HATIRLANMIYORDU. Kök neden zincirleme çözüldü.
8
+
9
+ ### 🐛 Cross-session hafıza (KRİTİK — kullanıcının en önemli özelliği)
10
+ - **Otomatik fact-extraction masum ifadeleri bozuyordu (asıl kök neden)**: `repl.js` her oturum sonunda regex ile fact çıkarıyordu; pattern `ad[ıi]m?` — **`m` OPSİYONEL** — "kod **adı**", "proje **adı**", "dosya **adı**" gibi tamlamaları "kullanıcı adı" sanıyor, değeri de `toLowerCase()`+`\w+` ile bozuyordu: **"gizli kod adı ZEPHYR-9" → "Kullanici ad: zephyr"**. Agent `memory_write` ile DOĞRU kaydetse bile üzerine bu YANLIŞ fact yazılıyor, recall bozuluyordu. Artık: `m` zorunlu ("benim adım/adım/ismim X" yakalanır, "kod adı" YAKALANMAZ), değer orijinal case'de korunur (ZEPHYR-9 bozulmaz), aşırı geniş `bana .* de` kaldırıldı. Ayrıca konum eki `[dt]e`→`[dt][ae]` (da/de/ta/te), tercih/konum nesnesi fiilden ÖNCE (Türkçe dil bilgisi). Fonksiyon module-level `extractPreferenceFacts`'e taşındı + 5 regresyon testi.
11
+ - **loadUserMemory sıralama**: flat fact'ler dosya sırasıyla ilk 15'i alınıyordu → çok fact olunca EN YENİ kayıt (yeni öğrenilen kod adı) sysMsg'e girmeden kesiliyordu. Artık skor + tarihe göre sıralanıp ilk 25 alınır (en güncel/önemli garanti).
12
+ - **Limit tutarsızlığı**: `repl.js` fact'leri 15'e, `memory_write` 50'ye (MAX_FACTS) kesiyordu — repl'in 15 sert limiti yeni fact'leri sessizce siliyordu. İkisi 50'de (NATURECO_MAX_FACTS) birleştirildi.
13
+ - **memory kaydetme kuralı** (agentic sysMsg): "spesifik değerleri (kod/isim/sayı/tarih) AYNEN koru, doğru etiketle (proje kod adı ≠ kullanıcı adı)" kuralı eklendi.
14
+
15
+ ### ✓ Gerçek macOS (Darwin 25.5.0, arm64) SSH doğrulaması
16
+ node v26.4.0 + natureco kurulu. Doğrulanan: grep_search (ripgrep yolu), code_execution (python3+node), git (log+enjeksiyon+remote-guard), cron_create (oluştur+`cron list`'te görünür+remove), http_request/duckduckgo/memory_tree, agentic chat + write_file, **ve düzeltme sonrası**: "kod adı FALCON-3" → doğru kaydedildi + yeni oturumda hatırlandı. 532 test yeşil (repl-memory regresyon +5), ESLint temiz.
17
+
18
+ ## [5.39.0] - 2026-07-08 — "CROSS-PLATFORM: grep_search + social_open Windows'ta kırıktı, düzeltildi"
19
+
20
+ Platform-uyumluluk denetimi: 90 aracın 18'i platform-özel. Çekirdek chat/code araçlarının Windows VE macOS'ta çalışması hedeflendi. 2 çekirdek/computer-use aracı saf Windows'ta kırıktı.
21
+
22
+ ### 🐛 Cross-platform düzeltmeler
23
+ - **grep_search saf Windows'ta tamamen kırıktı (ÇEKİRDEK araç)**: komut tespiti `spawn('which', ...)` + fallback `spawn('grep', ...)` kullanıyordu — `which` ve `grep` Windows'ta YOK (Git Bash olmadan). Ripgrep kurulu değilse arama hiç çalışmıyordu (bu makinede E2E'de çalışması yalnızca Git Bash kurulu olduğu içindi). Artık: (a) komut tespiti `rg --version` ile (which/where farkını bypass eder), (b) fallback **saf Node.js** dizin-tarama + regex (hiçbir Unix komutu gerekmez, node_modules/.git atlar, glob filtre, ikili-dosya atlama, 2MB sınırı). ripgrep varsa yine hız için kullanılır.
24
+ - **social_open Windows/Linux'ta hard-red**: `if (!IS_MAC) return {error:"sadece macOS"}` → tamamen reddediyordu. Artık platformlar arası URL açma: macOS `open`, Windows `cmd /c start`, Linux `xdg-open`.
25
+
26
+ ### ✓ Doğrulanan cross-platform durum (denetim)
27
+ - **Zaten cross-platform**: list_dir/read_file/write_file/edit_file (saf Node fs), http_request (Node http), git (v5.38 execFileSync, git PATH'te standart), code_execution (v5.38 py/python/node fallback), web_search/duckduckgo (Node http).
28
+ - **bash**: `shell:true` = platform-native shell (Windows cmd.exe, *nix sh) — doğru; agent OS'u sysMsg'den bilir (`İşletim sistemi: <platform>`) → platform-uygun komut seçer.
29
+ - **Tasarım gereği macOS-only (8 araç)**: mac_app_open/quit, mac_notify, macos_screenshot, notes/reminder/calendar_add, mac_alarm — osascript tabanlı; Windows'ta net "sadece macOS" mesajı (kullanıcının Mac'inde çalışır).
30
+
31
+ Doğrulama: grep_search Node fallback E2E (tool:node, rg'siz çalıştı); 527 test yeşil (6 yeni: grep-search Node fallback — dizin atlama, glob, maxResults, case-insensitive, geçersiz-regex), ESLint temiz.
32
+
5
33
  ## [5.38.0] - 2026-07-08 — "FİNAL DOĞRULAMA: git/code_execution/http_request düzeltmeleri + git enjeksiyon açığı kapatıldı"
6
34
 
7
35
  Final doğrulama re-run'inde (agent yolu E2E) 3 fonksiyonel sorun + 1 güvenlik açığı bulundu ve düzeltildi.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.38.0",
3
+ "version": "5.40.0",
4
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.",
5
5
  "bin": {
6
6
  "natureco": "bin/natureco.js"
@@ -20,6 +20,32 @@ const path = require('path');
20
20
  const os = require('os');
21
21
  const https = require('https');
22
22
  const { spawn } = require('child_process');
23
+
24
+ /**
25
+ * v5.40: Oturum-sonu pattern-tabanli isim/tercih cikarimi — SAF fonksiyon (test
26
+ * edilebilir; regresyon kilidi). Eskiden persistSessionToMemory icinde gomuluydu
27
+ * ve "ad[ıi]m?" (m OPSIYONEL) pattern'i "kod adı", "proje adı", "dosya adı" gibi
28
+ * MASUM tamlamalari "kullanici adi" saniyor + degeri lowercase/\w ile bozuyordu
29
+ * ("gizli kod adı ZEPHYR-9" → "Kullanici ad: zephyr"). Bu, agent memory_write ile
30
+ * DOGRU kaydetse bile uzerine YANLIS fact yazip cross-session recall'i bozuyordu.
31
+ * @param {string} content Kullanici mesaji (orijinal case)
32
+ * @returns {Array<{key:string, category:string, value:string}>}
33
+ */
34
+ function extractPreferenceFacts(content) {
35
+ const patterns = [
36
+ // ad: "benim adım X" / "adım X" / "ismim X" — m ZORUNLU; "kod adı" (adı) YAKALANMAZ.
37
+ { match: /(?:benim\s+ad[ıi]m|(?:^|\s)ad[ıi]m|[iı]smim)\s+([A-Za-zÇĞİÖŞÜçğıöşü][\wÇĞİÖŞÜçğıöşü.-]*)/i, category: 'personal', key: 'ad' },
38
+ // tercih/konum: nesne fiilden ONCE gelir (TR) — deger orijinal case korunur.
39
+ { match: /([A-Za-zÇĞİÖŞÜçğıöşü][\wÇĞİÖŞÜçğıöşü.-]*)['’]?[ıi]?\s*(?:seviyorum|hoşlan[ıi]yorum|beğeniyorum)/i, category: 'preference', key: 'sevilen' },
40
+ { match: /([A-Za-zÇĞİÖŞÜçğıöşü][\wÇĞİÖŞÜçğıöşü.-]*)['’]?[dt][ae]\s+(?:yaşıyorum|oturuyorum|kalıyorum)/i, category: 'location', key: 'yer' },
41
+ ];
42
+ const out = [];
43
+ for (const p of patterns) {
44
+ const m = (content || '').match(p.match);
45
+ if (m) out.push({ key: p.key, category: p.category, value: `Kullanici ${p.key}: ${m[1].trim()}` });
46
+ }
47
+ return out;
48
+ }
23
49
  const chalk = require('chalk');
24
50
  const tui = require('../utils/tui');
25
51
  const { loadToolDefinitions, toOpenAIFormat, executeTool } = require('../utils/tools');
@@ -1252,20 +1278,12 @@ async function startRepl(args) {
1252
1278
  // Bot adı sorgulanmış olabilir, mevcut adı koru
1253
1279
  }
1254
1280
 
1255
- // Kisilik tercihleri (genel pattern'ler)
1256
- const prefPatterns = [
1257
- { match: /(?:benim ad[ıi]m?|bana\s+.*de|ad[ıi]m?)\s+(\w+)/i, category: 'personal', key: 'ad' },
1258
- { match: /(?:seviyorum|hoşlan[ıi]yorum|beğeniyorum)\s+(\w+)/i, category: 'preference', key: 'sevilen' },
1259
- { match: /(?:yaşıyorum|oturuyorum|kalıyorum)\s+(\w+)/i, category: 'location', key: 'yer' },
1260
- ];
1261
- for (const p of prefPatterns) {
1262
- const m2 = msg.content.match(p.match);
1263
- if (m2) {
1264
- const val = m2[1].toLowerCase();
1265
- const fact = `Kullanici ${p.key}: ${val}`;
1266
- if (!(memory.facts || []).some(f => f.value === fact)) {
1267
- newFacts.push({ value: fact, score: 6, category: p.category, createdAt: new Date().toISOString() });
1268
- }
1281
+ // v5.40: Kisilik/isim cikarimi module-level SAF extractPreferenceFacts'e
1282
+ // tasindi (test edilebilir + regresyon kilidi). "kod adı"/"proje adı" gibi
1283
+ // masum tamlamalari artik yanlis yakalamiyor; deger orijinal case'de kalir.
1284
+ for (const pf of extractPreferenceFacts(msg.content)) {
1285
+ if (!(memory.facts || []).some(f => (f.value || '').toLowerCase() === pf.value.toLowerCase())) {
1286
+ newFacts.push({ value: pf.value, score: 6, category: pf.category, createdAt: new Date().toISOString() });
1269
1287
  }
1270
1288
  }
1271
1289
  }
@@ -1285,11 +1303,13 @@ async function startRepl(args) {
1285
1303
  factsAdded = uniqueFacts.length;
1286
1304
  }
1287
1305
 
1288
- // Decay (eski fact'leri dusuk skora dusur)
1289
- if (memory.facts && memory.facts.length > 15) {
1290
- // Max 15 fact tut, en dusuk skorlu olanlari sil
1291
- memory.facts.sort((a, b) => (b.score || 5) - (a.score || 5));
1292
- memory.facts = memory.facts.slice(0, 15);
1306
+ // Decay: soft-cap. v5.40: memory_write ile UYUMLU (eski 15 sert limiti yeni
1307
+ // fact'leri sessizce kesiyordu recall kaybina yol aciyordu). 50 = MAX_FACTS.
1308
+ const CAP = (() => { const r = parseInt(process.env.NATURECO_MAX_FACTS || '', 10); return Number.isFinite(r) && r > 0 ? r : 50; })();
1309
+ if (memory.facts && memory.facts.length > CAP) {
1310
+ // En yuksek skor + en yeni once; dusukleri sil.
1311
+ memory.facts.sort((a, b) => ((b.score || 5) - (a.score || 5)) || String(b.updatedAt || b.createdAt || '').localeCompare(String(a.updatedAt || a.createdAt || '')));
1312
+ memory.facts = memory.facts.slice(0, CAP);
1293
1313
  }
1294
1314
  } catch (e) {
1295
1315
  // Sessizce devam et, kritik degil
@@ -1643,3 +1663,5 @@ async function startRepl(args) {
1643
1663
  }
1644
1664
 
1645
1665
  module.exports = startRepl;
1666
+ // v5.40: test icin — cross-session hafiza bozulma regresyonu (kod adı ≠ kullanici adi)
1667
+ module.exports.extractPreferenceFacts = extractPreferenceFacts;
@@ -36,23 +36,75 @@ async function grepSearch({ pattern, path: searchPath = null, caseSensitive = fa
36
36
  }
37
37
  }
38
38
  }
39
- // ripgrep varsa onu kullan, yoksa fallback grep
39
+ // ripgrep varsa onu kullan (hizli), yoksa SAF NODE fallback.
40
+ // v5.39: eski fallback `grep` komutuydu → Windows'ta yok (Git Bash gerekir).
41
+ // Artik hicbir Unix komutuna bagimli degil: rg opsiyonel hizlandirma.
40
42
  const useRipgrep = await checkCommand('rg');
41
43
 
42
44
  if (useRipgrep) {
43
45
  return await grepWithRipgrep(pattern, cwd, caseSensitive, includePattern, maxResults);
44
46
  }
45
- return await grepWithFallback(pattern, cwd, caseSensitive, maxResults);
47
+ return grepWithNode(pattern, cwd, caseSensitive, includePattern, maxResults);
46
48
  }
47
49
 
48
50
  async function checkCommand(cmd) {
51
+ // v5.39: `which`/`where` platform-farkini bypass et — komutu dogrudan --version ile
52
+ // dene. PATH'te varsa 0 doner. (which Windows'ta yok, where *nix'te yok.)
49
53
  return new Promise((resolve) => {
50
- const proc = spawn('which', [cmd]);
51
- proc.on('close', code => resolve(code === 0));
52
- proc.on('error', () => resolve(false));
54
+ let done = false;
55
+ const fin = (v) => { if (!done) { done = true; resolve(v); } };
56
+ try {
57
+ const proc = spawn(cmd, ['--version'], { stdio: 'ignore' });
58
+ proc.on('close', code => fin(code === 0));
59
+ proc.on('error', () => fin(false));
60
+ } catch { fin(false); }
53
61
  });
54
62
  }
55
63
 
64
+ // v5.39: SAF NODE icerik aramasi — platformdan ve harici komuttan bagimsiz.
65
+ function grepWithNode(pattern, cwd, caseSensitive, includePattern, maxResults) {
66
+ const results = [];
67
+ let re;
68
+ const flags = caseSensitive ? 'g' : 'gi';
69
+ try { re = new RegExp(pattern, flags); }
70
+ catch { re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags); }
71
+ let globRe = null;
72
+ if (includePattern) {
73
+ const esc = includePattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
74
+ globRe = new RegExp('^' + esc + '$', 'i');
75
+ }
76
+ const IGNORE = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.cache', '.turbo']);
77
+ const walk = (dir) => {
78
+ if (results.length >= maxResults) return;
79
+ let entries;
80
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
81
+ for (const e of entries) {
82
+ if (results.length >= maxResults) break;
83
+ const full = path.join(dir, e.name);
84
+ if (e.isDirectory()) { if (!IGNORE.has(e.name)) walk(full); continue; }
85
+ if (!e.isFile()) continue;
86
+ if (globRe && !globRe.test(e.name)) continue;
87
+ let content;
88
+ try {
89
+ if (fs.statSync(full).size > 2 * 1024 * 1024) continue; // 2MB üstü atla
90
+ content = fs.readFileSync(full, 'utf8');
91
+ } catch { continue; }
92
+ if (content.includes('\x00')) continue; // ikili dosya (null byte)
93
+ const lines = content.split(/\r?\n/);
94
+ for (let i = 0; i < lines.length; i++) {
95
+ re.lastIndex = 0;
96
+ if (re.test(lines[i])) {
97
+ results.push({ file: full, line: i + 1, text: lines[i].trim().slice(0, 300) });
98
+ if (results.length >= maxResults) break;
99
+ }
100
+ }
101
+ }
102
+ };
103
+ const start = (fs.existsSync(cwd) && fs.statSync(cwd).isFile()) ? path.dirname(cwd) : cwd;
104
+ walk(start);
105
+ return { success: true, pattern, tool: 'node', count: results.length, results };
106
+ }
107
+
56
108
  async function grepWithRipgrep(pattern, cwd, caseSensitive, includePattern, maxResults) {
57
109
  return new Promise((resolve) => {
58
110
  const args = [
@@ -125,49 +177,6 @@ async function grepWithRipgrep(pattern, cwd, caseSensitive, includePattern, maxR
125
177
  });
126
178
  }
127
179
 
128
- async function grepWithFallback(pattern, cwd, caseSensitive, maxResults) {
129
- return new Promise((resolve) => {
130
- const args = ['-r', '-n', caseSensitive ? '' : '-i'];
131
- if (process.platform === 'darwin') args.push('-E');
132
- else args.push('-E');
133
- args.push(pattern);
134
- args.push(cwd);
135
-
136
- const proc = spawn('grep', args);
137
- let stdout = '';
138
- let stderr = '';
139
- // v5.6.32: Memory taşmasını önle - max 5MB output
140
- let stdoutBytes = 0;
141
- const MAX_OUTPUT = 5 * 1024 * 1024; // 5MB
142
- let truncated = false;
143
- const addStdout = (d) => {
144
- if (truncated) return;
145
- stdoutBytes += d.length;
146
- if (stdoutBytes > MAX_OUTPUT) {
147
- truncated = true;
148
- stdout += '\n[OUTPUT TRUNCATED - exceeded 5MB limit]';
149
- return;
150
- }
151
- stdout += d.toString();
152
- };
153
- proc.stdout.on('data', addStdout);
154
-
155
- proc.on('close', (code) => {
156
- const results = [];
157
- const lines = stdout.split('\n').filter(Boolean);
158
- for (const line of lines) {
159
- if (results.length >= maxResults) break;
160
- const match = line.match(/^(.+?):(\d+):(.*)$/);
161
- if (match) {
162
- results.push({ file: match[1], line: parseInt(match[2]), text: match[3] });
163
- }
164
- }
165
- resolve({ success: true, pattern, tool: 'grep', count: results.length, results });
166
- });
167
- proc.on('error', (e) => resolve({ success: false, error: e.message }));
168
- });
169
- }
170
-
171
180
  module.exports = {
172
181
  name: 'grep_search',
173
182
  description: 'Dosya içeriklerinde pattern ara (ripgrep veya grep). Örn: pattern="TODO", path="~/projects".',
@@ -185,4 +194,7 @@ module.exports = {
185
194
  async execute(params) {
186
195
  return await grepSearch(params);
187
196
  },
197
+
198
+ // v5.39: test icin — saf Node fallback'i rg'den bagimsiz dogrulamak icin
199
+ _grepWithNode: grepWithNode,
188
200
  };
@@ -124,9 +124,14 @@ function detectPlatform(input) {
124
124
  };
125
125
  }
126
126
 
127
- async function socialOpen(params) {
128
- if (!IS_MAC) return { success: false, error: "Henüz sadece macOS destekleniyor" };
127
+ // v5.39: platformlar arası URL açma — macOS `open`, Windows `start`, Linux `xdg-open`.
128
+ function openUrlProc(url, browserApp) {
129
+ if (IS_MAC) return spawn("open", browserApp ? ["-a", browserApp, url] : [url]);
130
+ if (process.platform === "win32") return spawn("cmd", ["/c", "start", "", url], { windowsHide: true });
131
+ return spawn("xdg-open", [url]); // linux + diğer *nix
132
+ }
129
133
 
134
+ async function socialOpen(params) {
130
135
  const { query, platform, username } = params;
131
136
 
132
137
  if (!query && !platform && !username) {
@@ -146,20 +151,19 @@ async function socialOpen(params) {
146
151
  note = detected.note;
147
152
  }
148
153
 
149
- const browser = getOpenBrowser();
154
+ const browser = IS_MAC ? getOpenBrowser() : null; // pgrep sadece macOS
150
155
  return new Promise((resolve) => {
151
- const args = browser ? ["-a", browser, url] : [url];
152
- const proc = spawn("open", args);
156
+ const proc = openUrlProc(url, browser);
153
157
  proc.on("close", (code) => {
154
158
  if (code === 0) {
155
159
  resolve({
156
160
  success: true,
157
161
  message: browser
158
162
  ? `${browser}'da yeni sekmede açıldı`
159
- : "Yeni tarayıcı penceresinde açıldı",
163
+ : "Varsayılan tarayıcıda açıldı",
160
164
  platform: platformName,
161
165
  url,
162
- browser: browser || "new",
166
+ browser: browser || "default",
163
167
  ...(note ? { note } : {}),
164
168
  });
165
169
  } else {
@@ -61,12 +61,18 @@ function loadUserMemory(username) {
61
61
  const key = v.trim().toLowerCase();
62
62
  if (!key || seen.has(key)) continue;
63
63
  seen.add(key);
64
- facts.push(v.trim());
64
+ // v5.40: skor + tarih koru — sysMsg'e EN GUNCEL/ONEMLI fact'ler girsin.
65
+ // Eski hali dosya sirasiyla ilk 15'i aliyordu → cok fact olunca en YENI
66
+ // kayit (or. yeni ogrenilen kod adi) kesiliyor, recall basarisiz oluyordu.
67
+ facts.push({ v: v.trim(), score: (f && typeof f.score === 'number') ? f.score : 0, t: (f && (f.updatedAt || f.createdAt)) || '' });
65
68
  }
66
69
  }
70
+ // v5.40: yuksek skor + en yeni once
71
+ facts.sort((a, b) => (b.score - a.score) || String(b.t).localeCompare(String(a.t)));
72
+ const factVals = facts.map(f => f.v);
67
73
  // isim memory.name'de yoksa fact'lerden cikar
68
74
  if (!name) {
69
- for (const f of facts) {
75
+ for (const f of factVals) {
70
76
  const match = f.toLowerCase().match(/(?:kullanici\s*adi?|kullanıcı\s*adı?|isim|name)\s*:?\s*(.+)/);
71
77
  if (match && match[1].trim().length > 2) { name = match[1].trim(); break; }
72
78
  }
@@ -74,7 +80,7 @@ function loadUserMemory(username) {
74
80
  const parts = [];
75
81
  if (name) parts.push(`Kullanici adi: ${name}`);
76
82
  if (botName) parts.push(`Bot adi: ${botName}`);
77
- if (facts.length > 0) parts.push(`Bilinenler: ${facts.slice(0, 15).join('; ')}`);
83
+ if (factVals.length > 0) parts.push(`Bilinenler: ${factVals.slice(0, 25).join('; ')}`);
78
84
  return parts.join('\n');
79
85
  } catch {}
80
86
  return '';
@@ -273,6 +279,7 @@ async function workflow(params) {
273
279
  '- memory_tree: AGAC-HAFIZA — zengin/kategorize kalici bilgi (proje, karar, teknik not, kisisel detay). action: index|read|search|append; username="' + memUser + '"; append icin: root (1-kisisel|2-teknik|3-kararlar) + branch (dal basligi) + content (yaprak). YENI oturumda hatirlanir.',
274
280
  '\nKurallar:',
275
281
  '- Kullanici kalici bilgi paylasirsa (isim, tercih, parola, proje, karar, onemli detay) ya da "hatirla / not al / kaydet" derse HEMEN KAYDET (username="' + memUser + '"): kisa tek bilgi → memory_write; zengin/kategorize bilgi → memory_tree(append, dogru root/branch). Trivial/gecici seyleri kaydetme.',
282
+ '- KAYIT KALITESI (onemli): fact\'i kullanicinin SOYLEDIGI ANLAMI ve SPESIFIK DEGERLERI koruyarak yaz. Kod/isim/sayi/tarih/parola gibi degerleri AYNEN gecir (or. "gizli kod adi ONYX-7" → fact: "Projenin gizli kod adi ONYX-7" — "ONYX-7"yi "onyx" yapma, kisaltma). Neyin ne oldugunu DOGRU etiketle: proje kod adi ≠ kullanici adi, sifre ≠ isim. Yanlis ozetleme yeni oturumda yanlis hatirlamaya yol acar.',
276
283
  '- Kullaniciya ozel bir sey sorulunca (gecmis, proje, tercih, karar) once memory_tree(search/read) ile ilgili kok/dali OKU (hedefli, tum hafizayi degil). Tek primary: bilgi tek yerde yasar; digerine sadece "bkz:". Credential/secret ASLA duz metin.',
277
284
  '- Bir is YARIM kalirsa ya da kullanici "sonra / yarin / daha sonra yapalim" derse memory_tree(action:append, root:"3-kararlar", branch:"Bekleyen İşler", content:"...") ile kaydet — yeni oturum basinda otomatik hatirlatilir. Bir bekleyen is TAMAMLANINCA memory_tree(action:remove, root:"3-kararlar", query:"<anahtar kelime>") ile kaldir.',
278
285
  '- Guncel/internet bilgisi gerektiren bir soru gelirse ONCE duckduckgo_search ile ara ("internet erisimim yok" DEME — bu arac her zaman calisir, API key gerekmez).',