entroly-wasm 0.19.1 → 0.19.3

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../js/cli.js');
package/index.js ADDED
@@ -0,0 +1,84 @@
1
+ // Entroly — Information-theoretic context optimization for JavaScript/TypeScript.
2
+ //
3
+ // Usage:
4
+ // const { EntrolyEngine, autoIndex, EntrolyConfig } = require('entroly-wasm');
5
+ //
6
+ // const engine = new EntrolyEngine();
7
+ // autoIndex(engine); // auto-discover and ingest codebase
8
+ // engine.optimize(8000, "fix the auth bug");
9
+ //
10
+ // Or as MCP server:
11
+ // npx entroly-wasm serve
12
+
13
+ let WasmEntrolyEngine;
14
+ try {
15
+ ({ WasmEntrolyEngine } = require('./pkg/entroly_wasm'));
16
+ } catch (err) {
17
+ if (err && err.code === 'MODULE_NOT_FOUND') {
18
+ // Source checkouts do not commit wasm-pack output. Build it lazily so
19
+ // `node -e "require('./entroly-wasm')"` and local smoke tests exercise the
20
+ // same module shape that the published npm tarball contains.
21
+ const { execFileSync } = require('child_process');
22
+ execFileSync('wasm-pack', ['build', '--target', 'nodejs', '--out-dir', 'pkg'], {
23
+ cwd: __dirname,
24
+ stdio: 'inherit',
25
+ });
26
+ ({ WasmEntrolyEngine } = require('./pkg/entroly_wasm'));
27
+ } else {
28
+ throw err;
29
+ }
30
+ }
31
+ const { EntrolyConfig } = require('./js/config');
32
+ const { autoIndex, startIncrementalWatcher, estimateTokens } = require('./js/auto_index');
33
+ const { CheckpointManager, persistIndex, loadIndex } = require('./js/checkpoint');
34
+ const { EntrolyMCPServer } = require('./js/server');
35
+ const { runAutotune, startAutotuneDaemon, TaskProfileOptimizer, FeedbackJournal } = require('./js/autotune');
36
+ const { ValueTracker, EVOLUTION_TAX_RATE, estimateCost } = require('./js/value_tracker');
37
+ const { exportPromoted: exportAgentSkills } = require('./js/agentskills_export');
38
+ const { TelegramGateway, DiscordGateway, SlackGateway } = require('./js/gateways');
39
+ const { VaultObserver } = require('./js/vault_observer');
40
+
41
+ module.exports = {
42
+ // Core engine (wasm)
43
+ EntrolyEngine: WasmEntrolyEngine,
44
+ WasmEntrolyEngine,
45
+
46
+ // Configuration
47
+ EntrolyConfig,
48
+
49
+ // Codebase scanning
50
+ autoIndex,
51
+ startIncrementalWatcher,
52
+ estimateTokens,
53
+
54
+ // State persistence
55
+ CheckpointManager,
56
+ persistIndex,
57
+ loadIndex,
58
+
59
+ // MCP Server
60
+ EntrolyMCPServer,
61
+
62
+ // Autotune + Task-Conditioned Profiles
63
+ runAutotune,
64
+ startAutotuneDaemon,
65
+ TaskProfileOptimizer,
66
+ FeedbackJournal,
67
+
68
+ // Self-funded evolution budget (C_spent ≤ τ·S(t))
69
+ ValueTracker,
70
+ EVOLUTION_TAX_RATE,
71
+ estimateCost,
72
+
73
+ // agentskills.io portable export
74
+ exportAgentSkills,
75
+
76
+ // Chat gateways (live self-evolution event stream)
77
+ TelegramGateway,
78
+ DiscordGateway,
79
+ SlackGateway,
80
+
81
+ // Observe the shared vault — works with skills promoted by the
82
+ // Python daemon OR any node-side orchestrator. No daemon required.
83
+ VaultObserver,
84
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * agentskills.io export — JS port.
3
+ * Exports promoted vault skills to the portable agentskills.io v0.1 spec
4
+ * so any compatible runtime can consume them.
5
+ *
6
+ * CLI: node js/agentskills_export.js [out_dir]
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const SPEC_VERSION = '0.1';
15
+
16
+ function parseFrontmatter(text) {
17
+ if (!text.startsWith('---')) return { meta: {}, body: text };
18
+ const end = text.indexOf('\n---', 3);
19
+ if (end < 0) return { meta: {}, body: text };
20
+ const header = text.slice(3, end).trim();
21
+ const body = text.slice(end + 4).replace(/^\n+/, '');
22
+ const meta = {};
23
+ for (const line of header.split('\n')) {
24
+ const i = line.indexOf(':');
25
+ if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim();
26
+ }
27
+ return { meta, body };
28
+ }
29
+
30
+ function loadSkill(dir) {
31
+ const skillMd = path.join(dir, 'SKILL.md');
32
+ const toolPy = path.join(dir, 'tool.py');
33
+ if (!fs.existsSync(skillMd) || !fs.existsSync(toolPy)) return null;
34
+
35
+ const { meta, body } = parseFrontmatter(fs.readFileSync(skillMd, 'utf8'));
36
+ if ((meta.status || '') !== 'promoted') return null;
37
+
38
+ let metrics = {};
39
+ const mp = path.join(dir, 'metrics.json');
40
+ if (fs.existsSync(mp)) {
41
+ try { metrics = JSON.parse(fs.readFileSync(mp, 'utf8')); } catch (_) {}
42
+ }
43
+ return {
44
+ meta,
45
+ procedure: body,
46
+ toolCode: fs.readFileSync(toolPy, 'utf8'),
47
+ metrics,
48
+ };
49
+ }
50
+
51
+ function exportPromoted({
52
+ vaultPath = '.entroly/vault',
53
+ outDir = './dist/agentskills',
54
+ } = {}) {
55
+ const skillsDir = path.join(vaultPath, 'evolution', 'skills');
56
+ if (!fs.existsSync(skillsDir)) {
57
+ return { status: 'no_vault', skillsDir };
58
+ }
59
+ fs.mkdirSync(outDir, { recursive: true });
60
+
61
+ const exported = [];
62
+ const skipped = [];
63
+
64
+ for (const name of fs.readdirSync(skillsDir).sort()) {
65
+ const dir = path.join(skillsDir, name);
66
+ if (!fs.statSync(dir).isDirectory()) continue;
67
+
68
+ const loaded = loadSkill(dir);
69
+ if (!loaded) { skipped.push(name); continue; }
70
+
71
+ const target = path.join(outDir, name);
72
+ fs.rmSync(target, { recursive: true, force: true });
73
+ fs.mkdirSync(target, { recursive: true });
74
+
75
+ const skillJson = {
76
+ spec_version: SPEC_VERSION,
77
+ id: loaded.meta.skill_id || name,
78
+ name: loaded.meta.name || name,
79
+ entity: loaded.meta.entity || '',
80
+ description: `Skill for handling ${loaded.meta.entity || name} queries`,
81
+ status: loaded.meta.status || 'promoted',
82
+ created_at: loaded.meta.created_at || '',
83
+ metrics: {
84
+ fitness_score: loaded.metrics.fitness_score || 0,
85
+ runs: loaded.metrics.runs || 0,
86
+ successes: loaded.metrics.successes || 0,
87
+ failures: loaded.metrics.failures || 0,
88
+ },
89
+ entrypoint: {
90
+ runtime: 'python',
91
+ module: 'tool',
92
+ match: 'matches',
93
+ execute: 'execute',
94
+ },
95
+ origin: {
96
+ runtime: 'entroly',
97
+ synthesis: 'structural',
98
+ token_cost: 0.0,
99
+ },
100
+ };
101
+
102
+ fs.writeFileSync(path.join(target, 'skill.json'), JSON.stringify(skillJson, null, 2));
103
+ fs.writeFileSync(path.join(target, 'procedure.md'), loaded.procedure);
104
+ fs.writeFileSync(path.join(target, 'tool.py'), loaded.toolCode);
105
+ fs.writeFileSync(path.join(target, 'tests.json'), '[]');
106
+
107
+ exported.push(skillJson.id);
108
+ }
109
+
110
+ fs.writeFileSync(
111
+ path.join(outDir, 'manifest.json'),
112
+ JSON.stringify({
113
+ spec_version: SPEC_VERSION,
114
+ exported_at: new Date().toISOString(),
115
+ source: 'entroly',
116
+ skills: exported,
117
+ }, null, 2),
118
+ );
119
+
120
+ return { status: 'ok', outDir, exported, skipped };
121
+ }
122
+
123
+ module.exports = { exportPromoted, SPEC_VERSION };
124
+
125
+ if (require.main === module) {
126
+ const out = process.argv[2] || './dist/agentskills';
127
+ const result = exportPromoted({ outDir: out });
128
+ console.log(JSON.stringify(result, null, 2));
129
+ }
@@ -0,0 +1,262 @@
1
+ // Entroly Auto-Index — JS port of auto_index.py
2
+ // Git-aware codebase discovery and ingestion.
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { execSync } = require('child_process');
7
+
8
+ const SUPPORTED_EXTENSIONS = new Set([
9
+ // Systems
10
+ '.rs', '.c', '.cpp', '.h', '.hpp', '.cc', '.hxx', '.zig',
11
+ // Web / JS / TS
12
+ '.js', '.ts', '.jsx', '.tsx', '.mjs', '.mts', '.cjs', '.cts', '.vue', '.svelte',
13
+ // Python
14
+ '.py', '.pyi',
15
+ // JVM
16
+ '.java', '.kt', '.scala',
17
+ // .NET
18
+ '.cs', '.csx', '.fs',
19
+ // Go
20
+ '.go',
21
+ // Swift
22
+ '.swift',
23
+ // Ruby
24
+ '.rb',
25
+ // PHP
26
+ '.php',
27
+ // Dart
28
+ '.dart',
29
+ // Elixir
30
+ '.ex', '.exs',
31
+ // Lua
32
+ '.lua',
33
+ // R
34
+ '.r',
35
+ // Shell / Config
36
+ '.sh', '.bash', '.zsh', '.toml', '.yaml', '.yml', '.json',
37
+ // Terraform
38
+ '.tf', '.hcl',
39
+ // Docs
40
+ '.md', '.rst',
41
+ // SQL
42
+ '.sql',
43
+ // Docker
44
+ '.dockerfile',
45
+ ]);
46
+
47
+ const SKIP_PATTERNS = new Set([
48
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'Cargo.lock',
49
+ 'poetry.lock', 'Pipfile.lock', 'composer.lock', 'Gemfile.lock',
50
+ '.DS_Store', 'thumbs.db',
51
+ ]);
52
+
53
+ const BINARY_EXTENSIONS = new Set([
54
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg', '.webp', '.tiff',
55
+ '.mp3', '.mp4', '.avi', '.mov', '.wav', '.flac', '.ogg', '.webm',
56
+ '.zip', '.tar', '.gz', '.bz2', '.7z', '.rar', '.xz',
57
+ '.wasm', '.so', '.dll', '.dylib', '.a', '.o', '.obj', '.exe', '.bin',
58
+ '.pyc', '.pyo', '.class', '.jar',
59
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
60
+ '.ttf', '.otf', '.woff', '.woff2', '.eot',
61
+ '.db', '.sqlite', '.sqlite3',
62
+ '.dat', '.pak', '.map',
63
+ ]);
64
+
65
+ const MAX_FILE_BYTES = 50 * 1024;
66
+ const ABSOLUTE_MAX_BYTES = 500 * 1024;
67
+ const MAX_FILES = parseInt(process.env.ENTROLY_MAX_FILES || '5000', 10);
68
+
69
+ let ignorePatterns = [];
70
+
71
+ function gitLsFiles(projectDir) {
72
+ try {
73
+ const result = execSync('git ls-files --cached --others --exclude-standard', {
74
+ cwd: projectDir, encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
75
+ });
76
+ return result.split('\n').map(f => f.trim()).filter(Boolean);
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
81
+
82
+ function walkFallback(projectDir) {
83
+ const files = [];
84
+ const skipDirs = new Set(['node_modules', '__pycache__', 'target', 'dist', 'build', '.git', 'venv', '.venv', 'env']);
85
+
86
+ function walk(dir) {
87
+ if (files.length >= MAX_FILES) return;
88
+ let entries;
89
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
90
+ for (const entry of entries) {
91
+ if (files.length >= MAX_FILES) return;
92
+ if (entry.name.startsWith('.') || skipDirs.has(entry.name)) {
93
+ if (entry.isDirectory()) continue;
94
+ }
95
+ const full = path.join(dir, entry.name);
96
+ if (entry.isDirectory()) { walk(full); }
97
+ else { files.push(path.relative(projectDir, full)); }
98
+ }
99
+ }
100
+ walk(projectDir);
101
+ return files;
102
+ }
103
+
104
+ function loadEntrolyIgnore(projectDir) {
105
+ const ignorePath = path.join(projectDir, '.entrolyignore');
106
+ try {
107
+ const content = fs.readFileSync(ignorePath, 'utf-8');
108
+ return content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
109
+ } catch { return []; }
110
+ }
111
+
112
+ function matchesIgnore(relPath) {
113
+ const { minimatch } = (() => {
114
+ try { return require('minimatch'); } catch { return { minimatch: null }; }
115
+ })();
116
+ for (const pattern of ignorePatterns) {
117
+ if (minimatch) {
118
+ if (minimatch(relPath, pattern)) return true;
119
+ if (minimatch(path.basename(relPath), pattern)) return true;
120
+ } else {
121
+ // Simple glob fallback
122
+ if (relPath.includes(pattern.replace('*', '')) || path.basename(relPath).includes(pattern.replace('*', ''))) return true;
123
+ }
124
+ }
125
+ return false;
126
+ }
127
+
128
+ function shouldIndex(relPath) {
129
+ const basename = path.basename(relPath);
130
+ if (SKIP_PATTERNS.has(basename)) return false;
131
+ const ext = path.extname(basename).toLowerCase();
132
+ if (BINARY_EXTENSIONS.has(ext)) return false;
133
+ if (ignorePatterns.length && matchesIgnore(relPath)) return false;
134
+ if (basename.startsWith('Dockerfile')) return true;
135
+ return SUPPORTED_EXTENSIONS.has(ext);
136
+ }
137
+
138
+ function estimateTokens(content) {
139
+ return Math.max(1, Math.floor(content.length / 4));
140
+ }
141
+
142
+ /**
143
+ * Auto-index a project's codebase into the Entroly wasm engine.
144
+ * @param {WasmEntrolyEngine} engine
145
+ * @param {string} [projectDir]
146
+ * @param {boolean} [force=false]
147
+ * @returns {object} Summary with indexed file count, tokens, and duration.
148
+ */
149
+ function autoIndex(engine, projectDir, force = false) {
150
+ projectDir = projectDir || process.cwd();
151
+ projectDir = path.resolve(projectDir);
152
+
153
+ ignorePatterns = loadEntrolyIgnore(projectDir);
154
+
155
+ if (!force && engine.fragment_count() > 0) {
156
+ return {
157
+ status: 'skipped', reason: 'persistent_index_loaded',
158
+ existing_fragments: engine.fragment_count(),
159
+ };
160
+ }
161
+
162
+ const t0 = Date.now();
163
+
164
+ let files = gitLsFiles(projectDir);
165
+ let discovery = 'git';
166
+ if (!files.length) { files = walkFallback(projectDir); discovery = 'walk'; }
167
+
168
+ const allIndexable = files.filter(shouldIndex);
169
+ const indexable = allIndexable.slice(0, MAX_FILES);
170
+
171
+ let indexed = 0, totalTokens = 0, skippedSize = 0, skippedRead = 0;
172
+
173
+ for (const relPath of indexable) {
174
+ const absPath = path.join(projectDir, relPath);
175
+ let stat;
176
+ try { stat = fs.statSync(absPath); } catch { continue; }
177
+
178
+ if (stat.size > ABSOLUTE_MAX_BYTES) { skippedSize++; continue; }
179
+ if (stat.size > MAX_FILE_BYTES || stat.size === 0) { skippedSize++; continue; }
180
+
181
+ // Binary detection
182
+ try {
183
+ const fd = fs.openSync(absPath, 'r');
184
+ const buf = Buffer.alloc(Math.min(8192, stat.size));
185
+ fs.readSync(fd, buf, 0, buf.length, 0);
186
+ fs.closeSync(fd);
187
+ if (buf.includes(0)) { skippedRead++; continue; }
188
+ } catch { skippedRead++; continue; }
189
+
190
+ let content;
191
+ try { content = fs.readFileSync(absPath, 'utf-8'); } catch { skippedRead++; continue; }
192
+ if (!content.trim()) continue;
193
+
194
+ const tokens = estimateTokens(content);
195
+ engine.ingest(content, `file:${relPath}`, tokens, false);
196
+ indexed++;
197
+ totalTokens += tokens;
198
+ }
199
+
200
+ const elapsed = ((Date.now() - t0) / 1000).toFixed(2);
201
+
202
+ // Trigger dep graph build
203
+ if (indexed > 0) {
204
+ try { engine.optimize(1, ''); } catch {}
205
+ }
206
+
207
+ return {
208
+ status: 'indexed', files_indexed: indexed, total_tokens: totalTokens,
209
+ duration_s: parseFloat(elapsed), discovery_method: discovery,
210
+ skipped_too_large: skippedSize, skipped_unreadable: skippedRead,
211
+ project_dir: projectDir,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Start incremental file watcher (background interval).
217
+ * @param {WasmEntrolyEngine} engine
218
+ * @param {string} [projectDir]
219
+ * @param {number} [intervalMs=120000]
220
+ * @returns {NodeJS.Timeout}
221
+ */
222
+ function startIncrementalWatcher(engine, projectDir, intervalMs = 120000) {
223
+ projectDir = projectDir || process.cwd();
224
+ projectDir = path.resolve(projectDir);
225
+ const indexedMtimes = {};
226
+
227
+ // Initial snapshot
228
+ const files = gitLsFiles(projectDir);
229
+ for (const rel of files) {
230
+ try { indexedMtimes[rel] = fs.statSync(path.join(projectDir, rel)).mtimeMs; } catch {}
231
+ }
232
+
233
+ return setInterval(() => {
234
+ try {
235
+ const files = gitLsFiles(projectDir);
236
+ let count = 0;
237
+ for (const rel of files) {
238
+ if (!shouldIndex(rel)) continue;
239
+ const abs = path.join(projectDir, rel);
240
+ let mtime;
241
+ try { mtime = fs.statSync(abs).mtimeMs; } catch { continue; }
242
+ if (indexedMtimes[rel] === undefined || mtime > indexedMtimes[rel]) {
243
+ indexedMtimes[rel] = mtime;
244
+ try {
245
+ const stat = fs.statSync(abs);
246
+ if (stat.size > MAX_FILE_BYTES || stat.size === 0) continue;
247
+ const content = fs.readFileSync(abs, 'utf-8');
248
+ if (!content.trim()) continue;
249
+ engine.ingest(content, `file:${rel}`, estimateTokens(content), false);
250
+ count++;
251
+ } catch { continue; }
252
+ }
253
+ if (count >= 100) break;
254
+ }
255
+ if (count > 0) console.error(`[entroly] Incremental re-index: ${count} new/modified files`);
256
+ } catch (e) {
257
+ console.error(`[entroly] Incremental re-index error: ${e.message}`);
258
+ }
259
+ }, intervalMs);
260
+ }
261
+
262
+ module.exports = { autoIndex, startIncrementalWatcher, estimateTokens };