entroly-wasm 0.19.1 → 0.19.2
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/bin/entroly-wasm.js +3 -0
- package/index.js +84 -0
- package/js/agentskills_export.js +129 -0
- package/js/auto_index.js +262 -0
- package/js/autotune.js +627 -0
- package/js/checkpoint.js +108 -0
- package/js/cli.js +321 -0
- package/js/cogops.js +513 -0
- package/js/config.js +53 -0
- package/js/distill.js +155 -0
- package/js/evolution_daemon.js +335 -0
- package/js/federation.js +444 -0
- package/js/gateways.js +170 -0
- package/js/multimodal.js +64 -0
- package/js/repo_map.js +112 -0
- package/js/server.js +465 -0
- package/js/skills.js +124 -0
- package/js/value_tracker.js +181 -0
- package/js/vault.js +237 -0
- package/js/vault_observer.js +129 -0
- package/js/workspace.js +116 -0
- package/package.json +65 -7
- package/{entroly_wasm_bg.wasm → pkg/entroly_wasm_bg.wasm} +0 -0
- /package/{entroly_wasm.d.ts → pkg/entroly_wasm.d.ts} +0 -0
- /package/{entroly_wasm.js → pkg/entroly_wasm.js} +0 -0
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
|
+
}
|
package/js/auto_index.js
ADDED
|
@@ -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 };
|