entroly-wasm 0.9.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/index.js +67 -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 +51 -0
- package/pkg/entroly_wasm.d.ts +156 -0
- package/pkg/entroly_wasm.js +468 -0
- package/pkg/entroly_wasm_bg.wasm +0 -0
package/js/cogops.js
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CogOps Engine — pure-JS port of entroly's epistemic layer
|
|
3
|
+
*
|
|
4
|
+
* Ports: epistemic_router.py, belief_compiler.py, verification_engine.py,
|
|
5
|
+
* change_pipeline.py, flow_orchestrator.py, evolution_logger.py
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { randomUUID } = require('crypto');
|
|
13
|
+
const { VaultManager, parseFrontmatter, extractSources, extractBody, safeFilename, nowISO, SOURCE_EXTS } = require('./vault');
|
|
14
|
+
|
|
15
|
+
// ── Intent Classification ───────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const INTENTS = {
|
|
18
|
+
INCIDENT: 'incident', AUDIT: 'audit', REPAIR: 'repair', TEST_GAP: 'test_gap',
|
|
19
|
+
RELEASE: 'release', PR_BRIEF: 'pr_brief', CODE_GENERATION: 'code_generation',
|
|
20
|
+
REPORT: 'report', RESEARCH: 'research', REUSE: 'reuse',
|
|
21
|
+
ARCHITECTURE: 'architecture', ONBOARDING: 'onboarding', GENERAL: 'general',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const FLOWS = {
|
|
25
|
+
FAST_ANSWER: 'fast_answer', VERIFY_BEFORE: 'verify_before',
|
|
26
|
+
COMPILE_ON_DEMAND: 'compile_on_demand', CHANGE_DRIVEN: 'change_driven',
|
|
27
|
+
SELF_IMPROVEMENT: 'self_improvement',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const INTENT_PATTERNS = [
|
|
31
|
+
[INTENTS.INCIDENT, /\b(incident|outage|spike|latency|down|crash|alert|page|5\d\d|timeout|spiking|degraded|error.rate|p99|on.?call)\b/i],
|
|
32
|
+
[INTENTS.AUDIT, /\b(audit|compliance|pii|gdpr|hipaa|security.review|vulnerability|cve|leak|injection|xss|csrf|secrets?)\b/i],
|
|
33
|
+
[INTENTS.REPAIR, /\b(fail|broke|broken|regression|bisect|root.?cause|fix|debug|stack.?trace|exception|error|traceback|started.failing)\b/i],
|
|
34
|
+
[INTENTS.TEST_GAP, /\b(test.gap|missing.test|uncovered|coverage|untested|edge.case|what.tests)\b/i],
|
|
35
|
+
[INTENTS.RELEASE, /\b(release.?ready|ship|deploy|rollout|canary|staging|production.ready|go.?live|launch)\b/i],
|
|
36
|
+
[INTENTS.PR_BRIEF, /\b(pr|pull.request|diff|code.review|review.this|patch|changeset|commit|merge)\b/i],
|
|
37
|
+
[INTENTS.CODE_GENERATION, /\b(generate|implement|create|write|scaffold|migration|boilerplate|add.?feature|build.?a|code.?for)\b/i],
|
|
38
|
+
[INTENTS.REPORT, /\b(report|diagram|slide|presentation|chart|graph|visuali|mermaid|marp|draw|render)\b/i],
|
|
39
|
+
[INTENTS.RESEARCH, /\b(research|benchmark|compare|evaluate|survey|trade.?off|pros.?cons|analysis|investigate)\b/i],
|
|
40
|
+
[INTENTS.REUSE, /\b(already.have|existing|reuse|duplicate|reinvent|shared.?util|helper|library|common)\b/i],
|
|
41
|
+
[INTENTS.ARCHITECTURE, /\b(architect|design|structure|module|service|component|layer|talk.?to|dependency|flow|pipeline|system|how.does.+work|connect|interact|communicat)\b/i],
|
|
42
|
+
[INTENTS.ONBOARDING, /\b(onboard|overview|walkthrough|tutorial|new.?engineer|explain.+to|introduction|getting.started|for.?beginners)\b/i],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const HIGH_RISK = /\b(security|compliance|pii|gdpr|hipaa|credential|secret|key|token|password|auth|encrypt|permission|rbac|acl|cve|vulnerability|injection|deletion|drop.table|rm.\-rf|production|customer.data)\b/i;
|
|
46
|
+
const MED_RISK = /\b(migration|schema.change|breaking|backward|deprecat|refactor|database|payment|billing|transaction|rollback|deploy)\b/i;
|
|
47
|
+
|
|
48
|
+
const STOP_WORDS = new Set([
|
|
49
|
+
'how', 'does', 'the', 'what', 'is', 'explain', 'show', 'can', 'you', 'please',
|
|
50
|
+
'work', 'works', 'about', 'for', 'and', 'this', 'that', 'with', 'from', 'have',
|
|
51
|
+
'are', 'module', 'architecture', 'design', 'pipeline', 'system', 'component', 'service',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
function classifyIntent(query) {
|
|
55
|
+
for (const [intent, re] of INTENT_PATTERNS) { if (re.test(query)) return intent; }
|
|
56
|
+
return INTENTS.GENERAL;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assessRisk(query, intent) {
|
|
60
|
+
if (intent === INTENTS.AUDIT) return 'high';
|
|
61
|
+
if (HIGH_RISK.test(query)) return 'high';
|
|
62
|
+
if (MED_RISK.test(query)) return 'medium';
|
|
63
|
+
return 'low';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractEntityKey(query) {
|
|
67
|
+
const compounds = query.match(/[a-zA-Z][a-zA-Z0-9]*(?:[_.:][a-zA-Z][a-zA-Z0-9]*)+/g);
|
|
68
|
+
if (compounds) return compounds.sort((a, b) => b.length - a.length)[0].toLowerCase().replace(/::/g, '_').replace(/\./g, '_');
|
|
69
|
+
const tokens = (query.match(/[a-zA-Z_]\w+/g) || []).filter(w => w.length > 3 && !STOP_WORDS.has(w.toLowerCase()));
|
|
70
|
+
if (tokens.length) return tokens.sort((a, b) => b.length - a.length)[0].toLowerCase();
|
|
71
|
+
return 'unknown';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Epistemic Router ────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
class EpistemicRouter {
|
|
77
|
+
constructor(vault, opts = {}) {
|
|
78
|
+
this._vault = vault;
|
|
79
|
+
this._missThreshold = opts.missThreshold || 3;
|
|
80
|
+
this._freshnessHours = opts.freshnessHours || 24;
|
|
81
|
+
this._minConfidence = opts.minConfidence || 0.6;
|
|
82
|
+
this._missCounts = {};
|
|
83
|
+
this._history = [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
route(query, isEvent = false, eventType = '') {
|
|
87
|
+
const intent = isEvent ? this._classifyEventIntent(eventType) : classifyIntent(query);
|
|
88
|
+
const coverage = this._checkCoverage(query);
|
|
89
|
+
const risk = assessRisk(query, intent);
|
|
90
|
+
const entityKey = extractEntityKey(query);
|
|
91
|
+
const missCount = this._missCounts[entityKey] || 0;
|
|
92
|
+
const [flow, reasoning] = this._selectFlow(intent, coverage, risk, isEvent, missCount);
|
|
93
|
+
if (!coverage.exists) this._missCounts[entityKey] = missCount + 1;
|
|
94
|
+
else this._missCounts[entityKey] = 0;
|
|
95
|
+
const decision = { routing_id: randomUUID().slice(0, 12), flow, intent, risk, reasoning, coverage: { exists: coverage.exists, fresh: coverage.fresh, verified: coverage.verified, confidence: coverage.confidence, matching_claims: coverage.matching_claims }, timestamp: Date.now() / 1000 };
|
|
96
|
+
this._history.push(decision);
|
|
97
|
+
return decision;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
stats() {
|
|
101
|
+
const flowCounts = {}, intentCounts = {};
|
|
102
|
+
for (const d of this._history) { flowCounts[d.flow] = (flowCounts[d.flow] || 0) + 1; intentCounts[d.intent] = (intentCounts[d.intent] || 0) + 1; }
|
|
103
|
+
return { total_routed: this._history.length, flow_distribution: flowCounts, intent_distribution: intentCounts, active_miss_entities: Object.fromEntries(Object.entries(this._missCounts).filter(([, v]) => v > 0)), evolution_triggers: this._history.filter(d => d.flow === FLOWS.SELF_IMPROVEMENT).length };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_selectFlow(intent, coverage, risk, isEvent, missCount) {
|
|
107
|
+
if (isEvent) return [FLOWS.CHANGE_DRIVEN, 'Event-triggered: route through Truth → Belief → Verification → Action'];
|
|
108
|
+
if (missCount >= this._missThreshold) return [FLOWS.SELF_IMPROVEMENT, `Repeated miss (count=${missCount}) on this entity: logging for Evolution skill synthesis`];
|
|
109
|
+
if (!coverage.exists) return [FLOWS.COMPILE_ON_DEMAND, 'No relevant beliefs found: compile from Truth first'];
|
|
110
|
+
if (!coverage.fresh || coverage.confidence < this._minConfidence) return [FLOWS.VERIFY_BEFORE, `Beliefs exist but stale/low-confidence (fresh=${coverage.fresh}, conf=${coverage.confidence.toFixed(2)}): verify before answering`];
|
|
111
|
+
if (!coverage.verified) return [FLOWS.VERIFY_BEFORE, 'Beliefs exist and are fresh but unverified: verify first'];
|
|
112
|
+
if (risk === 'high') return [FLOWS.VERIFY_BEFORE, 'High-risk domain: re-verify before answering'];
|
|
113
|
+
if (intent === INTENTS.CODE_GENERATION) return [FLOWS.VERIFY_BEFORE, 'Code generation: always verify before shipping'];
|
|
114
|
+
return [FLOWS.FAST_ANSWER, `Beliefs are fresh, verified, and confident (conf=${coverage.confidence.toFixed(2)}): fast answer`];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_checkCoverage(query) {
|
|
118
|
+
const empty = { exists: false, fresh: false, verified: false, confidence: 0, matching_claims: [] };
|
|
119
|
+
const beliefs = this._vault.listBeliefs();
|
|
120
|
+
const terms = new Set((query.match(/[a-zA-Z_]\w+/g) || []).filter(w => w.length > 2).map(w => w.toLowerCase()));
|
|
121
|
+
if (!terms.size) return empty;
|
|
122
|
+
const matching = []; let minConf = 1, allFresh = true, allVerified = true;
|
|
123
|
+
for (const b of beliefs) {
|
|
124
|
+
const entity = (b.entity || '').toLowerCase();
|
|
125
|
+
const stem = path.basename(b.file || '', '.md').toLowerCase();
|
|
126
|
+
let matched = false;
|
|
127
|
+
for (const t of terms) { if (entity.includes(t) || stem.includes(t)) { matched = true; break; } }
|
|
128
|
+
if (!matched) continue;
|
|
129
|
+
matching.push(b.entity || stem);
|
|
130
|
+
minConf = Math.min(minConf, b.confidence || 0);
|
|
131
|
+
if (b.status === 'stale') allFresh = false;
|
|
132
|
+
if (b.status !== 'verified') allVerified = false;
|
|
133
|
+
if (b.last_checked) {
|
|
134
|
+
try { const h = (Date.now() - new Date(b.last_checked).getTime()) / 3600000; if (h > this._freshnessHours) allFresh = false; } catch {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!matching.length) return empty;
|
|
138
|
+
return { exists: true, fresh: allFresh, verified: allVerified, confidence: minConf <= 1 ? minConf : 0, matching_claims: matching };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_classifyEventIntent(et) {
|
|
142
|
+
const t = et.toLowerCase();
|
|
143
|
+
if (/pr|pull|merge/.test(t)) return INTENTS.PR_BRIEF;
|
|
144
|
+
if (/incident|alert/.test(t)) return INTENTS.INCIDENT;
|
|
145
|
+
if (/release|deploy/.test(t)) return INTENTS.RELEASE;
|
|
146
|
+
if (/schedule|nightly|cron/.test(t)) return INTENTS.AUDIT;
|
|
147
|
+
return INTENTS.GENERAL;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Belief Compiler ─────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
const ENTITY_PATTERNS = {
|
|
154
|
+
py: [
|
|
155
|
+
[/^class\s+(\w+)/gm, 'class'],
|
|
156
|
+
[/^def\s+(\w+)/gm, 'function'],
|
|
157
|
+
[/^(\w+)\s*=/gm, 'variable'],
|
|
158
|
+
],
|
|
159
|
+
rs: [
|
|
160
|
+
[/^pub\s+(?:fn|struct|enum|trait|type|const)\s+(\w+)/gm, 'rust_item'],
|
|
161
|
+
[/^fn\s+(\w+)/gm, 'function'],
|
|
162
|
+
[/^(?:struct|enum|trait)\s+(\w+)/gm, 'type'],
|
|
163
|
+
],
|
|
164
|
+
ts: [
|
|
165
|
+
[/^(?:export\s+)?(?:class|interface|type|enum)\s+(\w+)/gm, 'type'],
|
|
166
|
+
[/^(?:export\s+)?(?:function|const|let)\s+(\w+)/gm, 'function'],
|
|
167
|
+
],
|
|
168
|
+
js: [
|
|
169
|
+
[/^(?:class|function)\s+(\w+)/gm, 'type'],
|
|
170
|
+
[/^(?:const|let|var)\s+(\w+)/gm, 'variable'],
|
|
171
|
+
[/^module\.exports\s*=\s*\{\s*([^}]+)\}/gm, 'exports'],
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
ENTITY_PATTERNS.tsx = ENTITY_PATTERNS.ts;
|
|
175
|
+
ENTITY_PATTERNS.jsx = ENTITY_PATTERNS.js;
|
|
176
|
+
ENTITY_PATTERNS.go = [[/^(?:func|type|var|const)\s+(\w+)/gm, 'go_item']];
|
|
177
|
+
ENTITY_PATTERNS.java = [[/^(?:public|private|protected)?\s*(?:class|interface|enum)\s+(\w+)/gm, 'type'], [/(?:public|private|protected)\s+\w+\s+(\w+)\s*\(/gm, 'method']];
|
|
178
|
+
|
|
179
|
+
function extractEntities(content, ext) {
|
|
180
|
+
const patterns = ENTITY_PATTERNS[ext] || ENTITY_PATTERNS.js;
|
|
181
|
+
const entities = [];
|
|
182
|
+
for (const [re, kind] of patterns) {
|
|
183
|
+
re.lastIndex = 0;
|
|
184
|
+
let m;
|
|
185
|
+
while ((m = re.exec(content)) !== null) {
|
|
186
|
+
const name = m[1];
|
|
187
|
+
if (name && name.length > 1 && !name.startsWith('_')) entities.push({ name, kind });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return entities;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
class BeliefCompiler {
|
|
194
|
+
constructor(vault) { this._vault = vault; }
|
|
195
|
+
|
|
196
|
+
compileDirectory(directory, maxFiles = 200) {
|
|
197
|
+
const sourceFiles = this._findSourceFiles(directory, maxFiles);
|
|
198
|
+
let beliefsWritten = 0, entitiesExtracted = 0;
|
|
199
|
+
const errors = [];
|
|
200
|
+
for (const fp of sourceFiles) {
|
|
201
|
+
try {
|
|
202
|
+
const content = fs.readFileSync(fp, 'utf-8');
|
|
203
|
+
const ext = path.extname(fp).slice(1);
|
|
204
|
+
const entities = extractEntities(content, ext);
|
|
205
|
+
const relPath = path.relative(directory, fp).replace(/\\/g, '/');
|
|
206
|
+
for (const ent of entities) {
|
|
207
|
+
const entity = `${relPath}::${ent.name}`;
|
|
208
|
+
this._vault.writeBelief({
|
|
209
|
+
claim_id: randomUUID(),
|
|
210
|
+
entity,
|
|
211
|
+
status: 'inferred',
|
|
212
|
+
confidence: 0.6,
|
|
213
|
+
sources: [`${relPath}`],
|
|
214
|
+
last_checked: nowISO(),
|
|
215
|
+
derived_from: ['belief_compiler'],
|
|
216
|
+
title: `${ent.kind}: ${ent.name}`,
|
|
217
|
+
body: `Extracted ${ent.kind} \`${ent.name}\` from \`${relPath}\`.\n\nAuto-compiled by entroly belief compiler.`,
|
|
218
|
+
});
|
|
219
|
+
beliefsWritten++;
|
|
220
|
+
entitiesExtracted++;
|
|
221
|
+
}
|
|
222
|
+
} catch (e) { errors.push(`${fp}: ${e.message}`); }
|
|
223
|
+
}
|
|
224
|
+
return { status: 'compiled', files_processed: sourceFiles.length, beliefs_written: beliefsWritten, entities_extracted: entitiesExtracted, errors: errors.slice(0, 10), engine: 'javascript' };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_findSourceFiles(dir, max) {
|
|
228
|
+
const results = [];
|
|
229
|
+
const walk = (d) => {
|
|
230
|
+
if (results.length >= max) return;
|
|
231
|
+
let entries;
|
|
232
|
+
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
233
|
+
for (const e of entries) {
|
|
234
|
+
if (results.length >= max) break;
|
|
235
|
+
if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === '__pycache__' || e.name === 'target' || e.name === '.git') continue;
|
|
236
|
+
const full = path.join(d, e.name);
|
|
237
|
+
if (e.isDirectory()) walk(full);
|
|
238
|
+
else if (SOURCE_EXTS.has(path.extname(e.name))) results.push(full);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
walk(dir);
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Verification Engine ─────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
class VerificationEngine {
|
|
249
|
+
constructor(vault, opts = {}) {
|
|
250
|
+
this._vault = vault;
|
|
251
|
+
this._freshnessHours = opts.freshnessHours || 24;
|
|
252
|
+
this._minConfidence = opts.minConfidence || 0.5;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fullVerificationPass() {
|
|
256
|
+
const beliefs = this._vault.listBeliefs();
|
|
257
|
+
const stale = [], lowConf = [], contradictions = [];
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
for (const b of beliefs) {
|
|
260
|
+
if (b.status === 'stale') stale.push(b.entity);
|
|
261
|
+
else if (b.last_checked) {
|
|
262
|
+
try { const h = (now - new Date(b.last_checked).getTime()) / 3600000; if (h > this._freshnessHours) stale.push(b.entity); } catch {}
|
|
263
|
+
}
|
|
264
|
+
if (b.confidence < this._minConfidence) lowConf.push(b.entity);
|
|
265
|
+
}
|
|
266
|
+
// Simple contradiction detection: same base entity, divergent confidence
|
|
267
|
+
const byBase = {};
|
|
268
|
+
for (const b of beliefs) {
|
|
269
|
+
const base = (b.entity || '').split('::')[0];
|
|
270
|
+
if (!byBase[base]) byBase[base] = [];
|
|
271
|
+
byBase[base].push(b);
|
|
272
|
+
}
|
|
273
|
+
for (const [base, group] of Object.entries(byBase)) {
|
|
274
|
+
if (group.length < 2) continue;
|
|
275
|
+
const confs = group.map(g => g.confidence);
|
|
276
|
+
if (Math.max(...confs) - Math.min(...confs) > 0.4) contradictions.push({ entity: base, spread: (Math.max(...confs) - Math.min(...confs)).toFixed(2) });
|
|
277
|
+
}
|
|
278
|
+
const report = { total_beliefs: beliefs.length, stale_count: stale.length, low_confidence_count: lowConf.length, contradiction_count: contradictions.length, stale_entities: stale.slice(0, 20), low_confidence_entities: lowConf.slice(0, 20), contradictions: contradictions.slice(0, 10), engine: 'javascript' };
|
|
279
|
+
// Write verification artifact
|
|
280
|
+
this._vault.writeVerification({ challenges: 'full_pass', result: stale.length + lowConf.length + contradictions.length > 0 ? 'issues_found' : 'confirmed', confidence_delta: 0, checked_at: nowISO(), method: 'full_verification_pass', title: 'Full Verification Pass', body: `Found ${stale.length} stale, ${lowConf.length} low-confidence, ${contradictions.length} contradictions across ${beliefs.length} beliefs.` });
|
|
281
|
+
return report;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
blastRadius(changedFiles) {
|
|
285
|
+
const beliefs = this._vault.listBeliefs();
|
|
286
|
+
const changedStems = new Set(changedFiles.map(f => path.basename(f).replace(/\.[^.]+$/, '').toLowerCase()));
|
|
287
|
+
const changedPaths = new Set(changedFiles.map(f => f.replace(/\\/g, '/').toLowerCase()));
|
|
288
|
+
const affected = [], affectedEntities = [];
|
|
289
|
+
for (const b of beliefs) {
|
|
290
|
+
const entity = (b.entity || '').toLowerCase();
|
|
291
|
+
let hit = [...changedStems].some(s => entity.includes(s));
|
|
292
|
+
if (!hit) {
|
|
293
|
+
// Read full belief to check sources
|
|
294
|
+
const full = this._vault.readBelief(b.entity);
|
|
295
|
+
if (full) {
|
|
296
|
+
const srcs = extractSources(`---\n${Object.entries(full.frontmatter).map(([k, v]) => `${k}: ${v}`).join('\n')}\nsources:\n${(full.frontmatter.sources || '').split(',').map(s => ` - ${s.trim()}`).join('\n')}\n---`);
|
|
297
|
+
hit = srcs.some(s => { const sp = s.split(':')[0].replace(/\\/g, '/').toLowerCase(); return changedPaths.has(sp) || changedStems.has(path.basename(sp).replace(/\.[^.]+$/, '')); });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (hit) { affected.push(b); affectedEntities.push(b.entity); }
|
|
301
|
+
}
|
|
302
|
+
const risk = affected.length > 10 ? 'high' : affected.length > 3 ? 'medium' : 'low';
|
|
303
|
+
return { affected_beliefs: affected.length, affected_entities: affectedEntities.slice(0, 30), risk_level: risk, description: `${affected.length} beliefs affected by changes to ${changedFiles.length} file(s)`, engine: 'javascript' };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
coverageGaps(directory) {
|
|
307
|
+
const sourceFiles = new BeliefCompiler(this._vault)._findSourceFiles(directory, 500);
|
|
308
|
+
const beliefs = this._vault.listBeliefs();
|
|
309
|
+
const coveredStems = new Set(beliefs.map(b => {
|
|
310
|
+
const e = (b.entity || '').toLowerCase();
|
|
311
|
+
const parts = e.split('::');
|
|
312
|
+
return parts[0].replace(/\.[^.]+$/, '');
|
|
313
|
+
}));
|
|
314
|
+
const gaps = [];
|
|
315
|
+
for (const fp of sourceFiles) {
|
|
316
|
+
const rel = path.relative(directory, fp).replace(/\\/g, '/');
|
|
317
|
+
const stem = rel.replace(/\.[^.]+$/, '').toLowerCase();
|
|
318
|
+
if (!coveredStems.has(stem)) gaps.push({ file: rel, reason: 'no_belief', suggested_entity: `${rel}::main` });
|
|
319
|
+
}
|
|
320
|
+
return { gaps: gaps.slice(0, 50), total_gaps: gaps.length, engine: 'javascript' };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── Change Pipeline ─────────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
function parseDiff(diffText) {
|
|
327
|
+
const files = new Set(), added = [], removed = [];
|
|
328
|
+
let linesAdded = 0, linesRemoved = 0;
|
|
329
|
+
for (const line of diffText.split('\n')) {
|
|
330
|
+
if (line.startsWith('diff --git') || line.startsWith('---') || line.startsWith('+++')) {
|
|
331
|
+
const m = line.match(/[ab]\/(.+)/);
|
|
332
|
+
if (m) files.add(m[1]);
|
|
333
|
+
} else if (line.startsWith('+') && !line.startsWith('+++')) { linesAdded++; added.push(line.slice(1)); }
|
|
334
|
+
else if (line.startsWith('-') && !line.startsWith('---')) { linesRemoved++; removed.push(line.slice(1)); }
|
|
335
|
+
}
|
|
336
|
+
return { files_modified: [...files], lines_added: linesAdded, lines_removed: linesRemoved, added_content: added.join('\n'), removed_content: removed.join('\n') };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function classifyDiffIntent(diffText, commitMsg) {
|
|
340
|
+
const text = (commitMsg + ' ' + diffText).toLowerCase();
|
|
341
|
+
if (/fix|bug|patch|hotfix|resolve/.test(text)) return 'bug-fix';
|
|
342
|
+
if (/test|spec|assert/.test(text)) return 'test';
|
|
343
|
+
if (/security|cve|vuln|auth/.test(text)) return 'security';
|
|
344
|
+
if (/perf|optimi|speed|cache|fast/.test(text)) return 'performance';
|
|
345
|
+
if (/refactor|rename|move|clean/.test(text)) return 'refactor';
|
|
346
|
+
return 'feature';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function reviewDiff(addedContent) {
|
|
350
|
+
const findings = [];
|
|
351
|
+
const checks = [
|
|
352
|
+
[/(?:password|secret|api.?key|token)\s*[=:]\s*['"][^'"]{8,}/gi, 'Possible hardcoded secret', 'critical'],
|
|
353
|
+
[/TODO|FIXME|HACK|XXX/gi, 'TODO/FIXME marker', 'info'],
|
|
354
|
+
[/except\s*:|catch\s*\(/gi, 'Broad exception handler', 'medium'],
|
|
355
|
+
[/eval\s*\(|exec\s*\(/gi, 'Dynamic code execution', 'high'],
|
|
356
|
+
[/rm\s+-rf|DROP\s+TABLE|DELETE\s+FROM/gi, 'Destructive operation', 'high'],
|
|
357
|
+
];
|
|
358
|
+
for (const [re, msg, sev] of checks) {
|
|
359
|
+
const matches = addedContent.match(re);
|
|
360
|
+
if (matches) findings.push({ message: msg, severity: sev, count: matches.length });
|
|
361
|
+
}
|
|
362
|
+
return findings;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
class ChangePipeline {
|
|
366
|
+
constructor(vault, verifier) { this._vault = vault; this._verifier = verifier; }
|
|
367
|
+
|
|
368
|
+
processDiff(diffText, commitMessage = '', prTitle = '') {
|
|
369
|
+
const parsed = parseDiff(diffText);
|
|
370
|
+
const intent = classifyDiffIntent(diffText, commitMessage);
|
|
371
|
+
const findings = reviewDiff(parsed.added_content);
|
|
372
|
+
const blast = this._verifier.blastRadius(parsed.files_modified);
|
|
373
|
+
const summary = `${intent} change: ${parsed.files_modified.length} files, +${parsed.lines_added}/-${parsed.lines_removed} lines`;
|
|
374
|
+
// Write action
|
|
375
|
+
this._vault.writeAction(prTitle || summary, `## Summary\n${summary}\n\n## Intent\n${intent}\n\n## Files\n${parsed.files_modified.map(f => `- ${f}`).join('\n')}\n\n## Review Findings\n${findings.length ? findings.map(f => `- [${f.severity}] ${f.message} (${f.count}x)`).join('\n') : 'No issues found'}`, 'pr_brief');
|
|
376
|
+
return { title: prTitle || summary, summary, risk_level: blast.risk_level, intent, files_modified: parsed.files_modified, lines_added: parsed.lines_added, lines_removed: parsed.lines_removed, findings_count: findings.length, findings, blast_radius: blast, engine: 'javascript' };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
refreshDocs(changedFiles) {
|
|
380
|
+
const result = this._vault.markBeliefsStaleForFiles(changedFiles);
|
|
381
|
+
result.engine = 'javascript';
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── Flow Orchestrator ───────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
class FlowOrchestrator {
|
|
389
|
+
constructor(vault, router, compiler, verifier, changePipe, sourceDir) {
|
|
390
|
+
this._vault = vault;
|
|
391
|
+
this._router = router;
|
|
392
|
+
this._compiler = compiler;
|
|
393
|
+
this._verifier = verifier;
|
|
394
|
+
this._changePipe = changePipe;
|
|
395
|
+
this._sourceDir = sourceDir;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
execute(query, diffText = '', isEvent = false, eventType = '') {
|
|
399
|
+
const decision = this._router.route(query, isEvent, eventType);
|
|
400
|
+
const steps = [];
|
|
401
|
+
const result = { flow: decision.flow, intent: decision.intent, risk: decision.risk, reasoning: decision.reasoning, steps, engine: 'javascript' };
|
|
402
|
+
|
|
403
|
+
switch (decision.flow) {
|
|
404
|
+
case FLOWS.FAST_ANSWER:
|
|
405
|
+
steps.push({ step: 'belief_lookup', status: 'done' });
|
|
406
|
+
result.beliefs = this._vault.coverageIndex();
|
|
407
|
+
break;
|
|
408
|
+
|
|
409
|
+
case FLOWS.VERIFY_BEFORE:
|
|
410
|
+
steps.push({ step: 'belief_lookup', status: 'done' });
|
|
411
|
+
const vr = this._verifier.fullVerificationPass();
|
|
412
|
+
steps.push({ step: 'verification', status: 'done', findings: vr.stale_count + vr.low_confidence_count + vr.contradiction_count });
|
|
413
|
+
result.verification = vr;
|
|
414
|
+
break;
|
|
415
|
+
|
|
416
|
+
case FLOWS.COMPILE_ON_DEMAND:
|
|
417
|
+
const cr = this._compiler.compileDirectory(this._sourceDir);
|
|
418
|
+
steps.push({ step: 'compile', status: 'done', beliefs_written: cr.beliefs_written });
|
|
419
|
+
const vr2 = this._verifier.fullVerificationPass();
|
|
420
|
+
steps.push({ step: 'verification', status: 'done' });
|
|
421
|
+
result.compilation = cr;
|
|
422
|
+
result.verification = vr2;
|
|
423
|
+
break;
|
|
424
|
+
|
|
425
|
+
case FLOWS.CHANGE_DRIVEN:
|
|
426
|
+
if (diffText) {
|
|
427
|
+
const pr = this._changePipe.processDiff(diffText);
|
|
428
|
+
steps.push({ step: 'change_analysis', status: 'done', intent: pr.intent });
|
|
429
|
+
result.change_brief = pr;
|
|
430
|
+
}
|
|
431
|
+
const cr2 = this._compiler.compileDirectory(this._sourceDir);
|
|
432
|
+
steps.push({ step: 'recompile', status: 'done', beliefs_written: cr2.beliefs_written });
|
|
433
|
+
result.compilation = cr2;
|
|
434
|
+
break;
|
|
435
|
+
|
|
436
|
+
case FLOWS.SELF_IMPROVEMENT:
|
|
437
|
+
steps.push({ step: 'miss_logged', status: 'done' });
|
|
438
|
+
result.message = 'Repeated misses logged for Evolution skill synthesis. Use create_skill to address the gap.';
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ── Compile Docs ────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
function compileDocs(vault, directory, maxFiles = 50) {
|
|
449
|
+
const root = directory;
|
|
450
|
+
const docPatterns = ['README', 'ARCHITECTURE', 'CONTRIBUTING', 'CHANGELOG', 'DESIGN', 'ADR'];
|
|
451
|
+
let compiled = 0;
|
|
452
|
+
const entities = [];
|
|
453
|
+
const walk = (dir, depth = 0) => {
|
|
454
|
+
if (depth > 2 || compiled >= maxFiles) return;
|
|
455
|
+
let entries;
|
|
456
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
457
|
+
for (const e of entries) {
|
|
458
|
+
if (compiled >= maxFiles) break;
|
|
459
|
+
const full = path.join(dir, e.name);
|
|
460
|
+
if (e.isDirectory() && (e.name === 'docs' || e.name === 'doc') && depth < 2) { walk(full, depth + 1); continue; }
|
|
461
|
+
if (!e.isFile() || !e.name.endsWith('.md')) continue;
|
|
462
|
+
const stem = e.name.replace(/\.md$/i, '').toUpperCase();
|
|
463
|
+
if (!docPatterns.some(p => stem.startsWith(p)) && dir === root) continue;
|
|
464
|
+
try {
|
|
465
|
+
const content = fs.readFileSync(full, 'utf-8');
|
|
466
|
+
const entity = `doc/${e.name.replace(/\.md$/i, '').toLowerCase()}`;
|
|
467
|
+
vault.writeBelief({
|
|
468
|
+
claim_id: randomUUID(),
|
|
469
|
+
entity,
|
|
470
|
+
status: 'inferred',
|
|
471
|
+
confidence: 0.8,
|
|
472
|
+
sources: [path.relative(root, full).replace(/\\/g, '/')],
|
|
473
|
+
last_checked: nowISO(),
|
|
474
|
+
derived_from: ['compile_docs'],
|
|
475
|
+
title: `Documentation: ${e.name}`,
|
|
476
|
+
body: content.slice(0, 3000),
|
|
477
|
+
});
|
|
478
|
+
entities.push(entity);
|
|
479
|
+
compiled++;
|
|
480
|
+
} catch {}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
walk(root);
|
|
484
|
+
return { status: 'compiled', docs_found: compiled, docs_compiled: compiled, entities, engine: 'javascript' };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── Export Training Data ────────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
function exportTrainingData(vault, outputPath = 'training_data.jsonl') {
|
|
490
|
+
const beliefs = vault.listBeliefs();
|
|
491
|
+
const lines = [];
|
|
492
|
+
let skipped = 0;
|
|
493
|
+
for (const b of beliefs) {
|
|
494
|
+
if (b.confidence < 0.5 || b.status === 'stale') { skipped++; continue; }
|
|
495
|
+
const full = vault.readBelief(b.entity);
|
|
496
|
+
if (!full) continue;
|
|
497
|
+
const body = full.body.slice(0, 2000);
|
|
498
|
+
lines.push(JSON.stringify({ messages: [
|
|
499
|
+
{ role: 'system', content: `You are an expert on the ${b.entity} codebase.` },
|
|
500
|
+
{ role: 'user', content: `What does ${b.entity} do?` },
|
|
501
|
+
{ role: 'assistant', content: body },
|
|
502
|
+
] }));
|
|
503
|
+
}
|
|
504
|
+
fs.writeFileSync(outputPath, lines.join('\n'), 'utf-8');
|
|
505
|
+
return { status: 'exported', output_path: outputPath, format: 'jsonl', beliefs_used: lines.length, beliefs_skipped: skipped, training_pairs: lines.length, engine: 'javascript' };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
module.exports = {
|
|
509
|
+
EpistemicRouter, BeliefCompiler, VerificationEngine, ChangePipeline, FlowOrchestrator,
|
|
510
|
+
classifyIntent, assessRisk, extractEntityKey, parseDiff, classifyDiffIntent, reviewDiff,
|
|
511
|
+
compileDocs, exportTrainingData,
|
|
512
|
+
INTENTS, FLOWS,
|
|
513
|
+
};
|
package/js/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Entroly Configuration — JS port of config.py
|
|
2
|
+
// Central configuration for the context optimization engine.
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const crypto = require('crypto');
|
|
8
|
+
|
|
9
|
+
function projectCheckpointDir() {
|
|
10
|
+
const explicit = process.env.ENTROLY_DIR;
|
|
11
|
+
if (explicit) return explicit;
|
|
12
|
+
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const projectHash = crypto.createHash('sha256').update(cwd).digest('hex').slice(0, 12);
|
|
15
|
+
const defaultDir = path.join(os.homedir(), '.entroly', 'checkpoints', projectHash);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
fs.mkdirSync(defaultDir, { recursive: true });
|
|
19
|
+
const probe = path.join(defaultDir, '.entroly_write_probe');
|
|
20
|
+
fs.writeFileSync(probe, 'ok');
|
|
21
|
+
fs.unlinkSync(probe);
|
|
22
|
+
return defaultDir;
|
|
23
|
+
} catch {
|
|
24
|
+
const fallback = path.join(os.tmpdir(), 'entroly', 'checkpoints', projectHash);
|
|
25
|
+
fs.mkdirSync(fallback, { recursive: true });
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class EntrolyConfig {
|
|
31
|
+
constructor(opts = {}) {
|
|
32
|
+
this.defaultTokenBudget = opts.defaultTokenBudget ?? 128_000;
|
|
33
|
+
this.maxFragments = opts.maxFragments ?? 10_000;
|
|
34
|
+
this.weightRecency = opts.weightRecency ?? 0.30;
|
|
35
|
+
this.weightFrequency = opts.weightFrequency ?? 0.25;
|
|
36
|
+
this.weightSemanticSim = opts.weightSemanticSim ?? 0.25;
|
|
37
|
+
this.weightEntropy = opts.weightEntropy ?? 0.20;
|
|
38
|
+
this.decayHalfLifeTurns = opts.decayHalfLifeTurns ?? 15;
|
|
39
|
+
this.minRelevanceThreshold = opts.minRelevanceThreshold ?? 0.05;
|
|
40
|
+
this.dedupSimilarityThreshold = opts.dedupSimilarityThreshold ?? 0.92;
|
|
41
|
+
this.prefetchDepth = opts.prefetchDepth ?? 2;
|
|
42
|
+
this.maxPrefetchFragments = opts.maxPrefetchFragments ?? 10;
|
|
43
|
+
this.checkpointDir = opts.checkpointDir ?? projectCheckpointDir();
|
|
44
|
+
this.autoCheckpointInterval = opts.autoCheckpointInterval ?? 5;
|
|
45
|
+
this.serverName = opts.serverName ?? 'entroly';
|
|
46
|
+
this.serverVersion = opts.serverVersion ?? (() => {
|
|
47
|
+
try { return require('../package.json').version; }
|
|
48
|
+
catch { return '0.0.0'; }
|
|
49
|
+
})();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { EntrolyConfig, projectCheckpointDir };
|