natureco-cli 5.44.1 → 5.45.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,15 @@
2
2
 
3
3
  All notable changes to NatureCo CLI will be documented in this file.
4
4
 
5
+ ## [5.45.0] - 2026-07-08 — "MEMORY: Urðr lint + fallback search entegre edildi"
6
+
7
+ ### Added
8
+ - **`natureco memory lint`** — Urðr standardından (natureco-official/urdr) türetilen, LLM'siz sağlık denetimi. Düz hafızada (`<user>.json`) ve ağaç hafızada (`tree/<user>/`) **YİNELENEN** (aynı bilgi iki kez) ve **ÇELİŞEN** (aynı konu, farklı değer — ör. iki farklı favori renk ya da iki farklı proje kod adı) kayıtları Jaccard benzerliğiyle yakalar. Bu, recall'ın "yanlış hatırlanan değeri" döndürmesinin kök nedeni. Gerçek kullanıcı hafızasında test edildi (çelişki yakalandı).
9
+ - **`natureco memory search`'e ağaç fallback'i** — düz hafızada bulunamazsa, ağaç hafızada branch-aware tam tarama yapar (Urðr `search.mjs` portu). Yanlış-kök tahmini yüzünden bilgi "erişilemez" kalmaz. LLM'siz, cross-platform. Sonuçlar `dosya › ## dal › yaprak` biçiminde.
10
+ - `src/utils/memory-lint.js` — `lintFacts`/`lintUser`/`searchTree` (5 regresyon testi). 576 test yeşil.
11
+
12
+ Bu, Urðr'nin kanıtlanmış hafıza-güvenilirlik araçlarını NatureCo'nun gerçek memory-tree'sine bağlar; native root isimleri (`0-index.md`, `1-kisisel.md` …) zaten tanınıyor.
13
+
5
14
  ## [5.44.1] - 2026-07-08 — "MEMORY: açık 'hatırla' komutu HAM kaydedilir (kayıp önlenir)"
6
15
 
7
16
  ### Added
package/bin/natureco.js CHANGED
@@ -130,7 +130,7 @@ ${chalk.yellow('💻 Geliştirici Araçları')}
130
130
 
131
131
  ${chalk.yellow('📊 Yönetim & Sistem')}
132
132
  ${chalk.cyan('dashboard')} Web kontrol paneli (open|status|url)
133
- ${chalk.cyan('memory')} Hafıza yönetimi (status|list|search|show|clear|index|export|import|semantic|wiki)
133
+ ${chalk.cyan('memory')} Hafıza yönetimi (status|list|search|lint|show|clear|index|export|import|semantic|wiki)
134
134
  ${chalk.cyan('logs')} Log yönetimi (tail|show|search|clear|path)
135
135
  ${chalk.cyan('status')} Sistem durumu (run|simple|usage)
136
136
  ${chalk.cyan('plugins')} Plugin yönetimi (list|install|uninstall|enable|disable|info|update|search|doctor|registry|marketplace)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "natureco-cli",
3
- "version": "5.44.1",
3
+ "version": "5.45.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"
@@ -19,6 +19,7 @@ async function memoryCmd(args) {
19
19
  if (action === 'index') return indexMemory();
20
20
  if (action === 'export') return exportMemoryCmd(params[0], params[1]);
21
21
  if (action === 'import') return importMemoryCmd(params[0], params[1]);
22
+ if (action === 'lint') return lintMemoryCmd(params[0]);
22
23
  if (action === 'semantic') return semanticSearchCmd(params.join(' '));
23
24
  // Wiki pages
24
25
  if (action === 'wiki') return wikiCmd(params[0], params.slice(1));
@@ -141,14 +142,24 @@ function searchMemory(query) {
141
142
  });
142
143
  });
143
144
 
144
- console.log(chalk.cyan.bold(`\n "${query}" için ${results.length} sonuç\n`));
145
- if (results.length === 0) {
146
- console.log(chalk.gray(' Sonuç bulunamadı.\n'));
147
- return;
148
- }
145
+ console.log(chalk.cyan.bold(`\n "${query}" için ${results.length} sonuç (düz hafıza)\n`));
149
146
  results.forEach(r => {
150
147
  console.log(chalk.white(` [${r.bot}] `) + chalk.gray(`${r.field}: `) + chalk.white(r.value));
151
148
  });
149
+
150
+ // v5.45: ağaç hafızada branch-aware FALLBACK (Urðr) — düz hafızada bulunamasa bile
151
+ // bilgi "erişilemez" kalmasın (yanlış-kök tahmini kurtarma ağı). LLM'siz.
152
+ try {
153
+ const { searchTree } = require('../utils/memory-lint');
154
+ let u; try { u = require('../utils/config').getConfig().userName; } catch {}
155
+ const treeHits = searchTree(u || 'default', query);
156
+ if (treeHits.length) {
157
+ console.log(chalk.cyan(`\n Ağaç hafızada ${treeHits.length} sonuç:`));
158
+ treeHits.slice(0, 15).forEach(h => console.log(chalk.gray(` ${h.file} › ## ${h.branch} › `) + chalk.white(h.text)));
159
+ } else if (results.length === 0) {
160
+ console.log(chalk.gray(' Sonuç bulunamadı (düz + ağaç hafıza).'));
161
+ }
162
+ } catch { if (results.length === 0) console.log(chalk.gray(' Sonuç bulunamadı.')); }
152
163
  console.log('');
153
164
  }
154
165
 
@@ -404,4 +415,41 @@ function semanticSearchMemory(query, botId) {
404
415
  return results;
405
416
  }
406
417
 
418
+ // v5.45: memory lint — Urðr-derived duplicate/conflict audit (flat + tree).
419
+ // Catches "same fact stored twice" and "same subject, different value" (the drift that
420
+ // makes recall return the wrong remembered value, e.g. two different project code names).
421
+ function lintMemoryCmd(user) {
422
+ const { lintUser } = require('../utils/memory-lint');
423
+ let u = user;
424
+ if (!u) { try { u = require('../utils/config').getConfig().userName; } catch {} }
425
+ const { flatFile, flatCount, flatFindings, treeFindings } = lintUser(u || 'default');
426
+
427
+ console.log(chalk.cyan(`\n 🧠 Memory Lint · ${u || 'default'}\n`) + chalk.gray(' ' + '─'.repeat(52)));
428
+ console.log(chalk.gray(` flat: ${flatCount} fact · ${path.basename(flatFile)}`));
429
+
430
+ const all = [...flatFindings, ...treeFindings];
431
+ const dups = all.filter((f) => f.level === 'duplicate');
432
+ const conflicts = all.filter((f) => f.level === 'conflict');
433
+
434
+ if (all.length === 0) {
435
+ console.log(chalk.green('\n ✓ Temiz — yinelenen veya çelişen kayıt yok.\n'));
436
+ return;
437
+ }
438
+ if (dups.length) {
439
+ console.log(chalk.yellow(`\n ⚠ ${dups.length} olası YİNELENEN (aynı bilgi iki kez):`));
440
+ for (const f of dups.slice(0, 10)) {
441
+ console.log(` ${chalk.gray(`(%${Math.round(f.sim * 100)})`)} ${f.a}`);
442
+ console.log(` ${chalk.gray(' ≈')} ${f.b}${f.file ? chalk.gray(' [' + f.file + ']') : ''}`);
443
+ }
444
+ }
445
+ if (conflicts.length) {
446
+ console.log(chalk.red(`\n ⚠ ${conflicts.length} olası ÇELİŞKİ (aynı konu, farklı değer):`));
447
+ for (const f of conflicts.slice(0, 10)) {
448
+ console.log(` ${chalk.gray(`(%${Math.round(f.sim * 100)})`)} ${f.a}`);
449
+ console.log(` ${chalk.gray(' ↔')} ${f.b}${f.file ? chalk.gray(' [' + f.file + ']') : ''}`);
450
+ }
451
+ }
452
+ console.log(chalk.gray(`\n Öneri: eskiyen/yanlış kaydı düzeltin — "natureco memory clear" veya elle. Tek doğru kalsın.\n`));
453
+ }
454
+
407
455
  module.exports = memoryCmd;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * memory-lint — flat + tree memory health audit (Urðr-derived, LLM-free)
3
+ *
4
+ * Ported from the Urðr standard (natureco-official/urdr scripts/lint.mjs) into NatureCo's
5
+ * real memory layout so users are protected from the drift that erodes recall:
6
+ * - DUPLICATE facts (Jaccard ≥ 0.85) — the same thing stored twice, slightly reworded.
7
+ * - CONFLICTING facts (Jaccard 0.5–0.85) — same subject, different value, e.g.
8
+ * "favori rengi kırmızı" vs "Favori rengi mavidir", or two different project code names.
9
+ * This is exactly why recall can return the "wrong" remembered value.
10
+ *
11
+ * Flat memory: ~/.natureco/memory/<user>.json (facts: [{value, ...}])
12
+ * Tree memory: ~/.natureco/memory/tree/<user>/*.md (## branch → leaves)
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+
19
+ const MEMORY_DIR = path.join(os.homedir(), '.natureco', 'memory');
20
+ const DUP = 0.85;
21
+ const CONFLICT = 0.5;
22
+
23
+ const STOP = new Set(['ve', 'ile', 'için', 'icin', 'bir', 'bu', 'da', 'de', 'the', 'and', 'for',
24
+ 'kullanici', 'kullanıcı', 'kullanicinin', 'kullanıcının', 'benim', 'onun', 'çok', 'cok']);
25
+
26
+ function tokens(text) {
27
+ return new Set(
28
+ String(text || '').toLowerCase()
29
+ .replace(/[*_`|]/g, ' ')
30
+ .replace(/\b\d{2}\.\d{2}\.\d{4}\b/g, ' ')
31
+ .split(/[^a-z0-9çğıöşü-]+/i)
32
+ .filter((w) => w.length > 2 && !STOP.has(w))
33
+ );
34
+ }
35
+
36
+ function jaccard(a, b) {
37
+ if (a.size === 0 || b.size === 0) return 0;
38
+ let inter = 0;
39
+ for (const x of a) if (b.has(x)) inter++;
40
+ return inter / (a.size + b.size - inter);
41
+ }
42
+
43
+ /**
44
+ * Lint an array of fact objects/strings. Returns findings, most-similar first.
45
+ * @returns {Array<{level:'duplicate'|'conflict', sim:number, a:string, b:string}>}
46
+ */
47
+ function lintFacts(facts) {
48
+ const vals = (facts || []).map((f) => (f && f.value != null ? f.value : f)).filter((v) => typeof v === 'string' && v.trim());
49
+ const toks = vals.map(tokens);
50
+ const findings = [];
51
+ for (let i = 0; i < vals.length; i++) {
52
+ for (let j = i + 1; j < vals.length; j++) {
53
+ const sim = jaccard(toks[i], toks[j]);
54
+ if (sim >= DUP) findings.push({ level: 'duplicate', sim, a: vals[i], b: vals[j] });
55
+ else if (sim >= CONFLICT) findings.push({ level: 'conflict', sim, a: vals[i], b: vals[j] });
56
+ }
57
+ }
58
+ return findings.sort((x, y) => y.sim - x.sim);
59
+ }
60
+
61
+ /** Parse a tree root file into branches → leaves and lint for duplicates/conflicts within it. */
62
+ function lintTreeFile(file) {
63
+ let content;
64
+ try { content = fs.readFileSync(file, 'utf8'); } catch { return []; }
65
+ const leaves = [];
66
+ let branch = '(root)';
67
+ for (const line of content.split(/\r?\n/)) {
68
+ const h = line.match(/^##\s+(.+?)\s*$/);
69
+ if (h) { branch = h[1]; continue; }
70
+ const t = line.trim();
71
+ if (!t || t.startsWith('<!--') || t.startsWith('#') || t === '---' || /^_no entries yet\._$/i.test(t) || t.startsWith('>')) continue;
72
+ leaves.push({ value: t, branch });
73
+ }
74
+ return lintFacts(leaves).map((f) => ({ ...f, file: path.basename(file) }));
75
+ }
76
+
77
+ /** Full audit for a user: flat facts + every tree root file. */
78
+ function lintUser(user) {
79
+ const uname = (user || 'default').toLowerCase();
80
+ const flatFile = path.join(MEMORY_DIR, `${uname}.json`);
81
+ let flatFindings = [], flatCount = 0;
82
+ try {
83
+ const facts = (JSON.parse(fs.readFileSync(flatFile, 'utf8')).facts) || [];
84
+ flatCount = facts.length;
85
+ flatFindings = lintFacts(facts);
86
+ } catch {}
87
+
88
+ const treeDir = path.join(MEMORY_DIR, 'tree', uname);
89
+ const treeFindings = [];
90
+ try {
91
+ for (const f of fs.readdirSync(treeDir)) {
92
+ if (/^(?:(?:root|kök|kok)-)?\d[-_].*\.md$/i.test(f)) treeFindings.push(...lintTreeFile(path.join(treeDir, f)));
93
+ }
94
+ } catch {}
95
+
96
+ return { flatFile, flatCount, flatFindings, treeFindings };
97
+ }
98
+
99
+ /**
100
+ * v5.45: Branch-aware fallback search over a user's memory tree (Urðr search.mjs port).
101
+ * The hierarchical 4-step lookup is primary; this is the safety net so a wrong-root guess
102
+ * never makes stored info read as "forgotten". LLM-free, cross-platform.
103
+ * @returns {Array<{file, branch, text}>}
104
+ */
105
+ function searchTree(user, query, opts = {}) {
106
+ const max = opts.max || 25;
107
+ const treeDir = path.join(MEMORY_DIR, 'tree', (user || 'default').toLowerCase());
108
+ if (!query || !String(query).trim()) return [];
109
+ let re;
110
+ try { re = new RegExp(query, 'i'); }
111
+ catch { re = new RegExp(String(query).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); }
112
+ let files;
113
+ try { files = fs.readdirSync(treeDir).filter((f) => /^(?:(?:root|kök|kok)-)?\d[-_].*\.md$/i.test(f)); }
114
+ catch { return []; }
115
+ const out = [];
116
+ for (const file of files.sort()) {
117
+ let content;
118
+ try { content = fs.readFileSync(path.join(treeDir, file), 'utf8'); } catch { continue; }
119
+ let branch = '(root)';
120
+ for (const line of content.split(/\r?\n/)) {
121
+ const h = line.match(/^##\s+(.+?)\s*$/);
122
+ if (h) { branch = h[1]; continue; }
123
+ const t = line.trim();
124
+ if (!t || t.startsWith('<!--') || t.startsWith('#') || t === '---' || /^_no entries yet\._$/i.test(t) || t.startsWith('>')) continue;
125
+ if (re.test(line)) { out.push({ file, branch, text: t.slice(0, 300) }); if (out.length >= max) return out; }
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+
131
+ module.exports = { lintFacts, lintTreeFile, lintUser, searchTree, tokens, jaccard };