Files
wifi-densepose/vendor/ruvector/.claude/intelligence/index.js

1162 lines
35 KiB
JavaScript

/**
* RuVector Intelligence Layer v2 for Claude Code
*
* Enhanced with:
* 1. Native HNSW rebuild on startup (150x faster search)
* 2. Hyperbolic distance for hierarchical embeddings
* 3. Confidence Calibration (track predicted vs actual)
* 4. A/B Testing (holdout group comparison)
* 5. Feedback Loop (learn from followed/ignored suggestions)
* 6. Active Learning (identify uncertain states)
* 7. Pattern Decay (time-weighted trajectories)
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { createHash } from 'crypto';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_DIR = join(__dirname, 'data');
const MEMORY_FILE = join(DATA_DIR, 'memory.json');
const TRAJECTORIES_FILE = join(DATA_DIR, 'trajectories.json');
const PATTERNS_FILE = join(DATA_DIR, 'patterns.json');
const CALIBRATION_FILE = join(DATA_DIR, 'calibration.json');
const FEEDBACK_FILE = join(DATA_DIR, 'feedback.json');
const ERROR_PATTERNS_FILE = join(DATA_DIR, 'error-patterns.json');
const SEQUENCES_FILE = join(DATA_DIR, 'sequences.json');
// Ensure data directory exists
if (!existsSync(DATA_DIR)) {
mkdirSync(DATA_DIR, { recursive: true });
}
// Try to load @ruvector/core VectorDB
let VectorDB = null;
let ruvectorAvailable = false;
try {
const ruvector = await import('@ruvector/core');
VectorDB = ruvector.VectorDB;
ruvectorAvailable = true;
console.error('✅ @ruvector/core loaded - using native HNSW vector search');
} catch (e) {
console.error('⚠️ @ruvector/core not available, using fallback cosine similarity');
}
// Try to load attention WASM for hyperbolic distance
let attentionWasm = null;
try {
attentionWasm = await import('../../crates/ruvector-attention-wasm/pkg/ruvector_attention_wasm.js');
console.error('✅ Hyperbolic attention WASM loaded');
} catch (e) {
// Hyperbolic not available - use fallback
}
/**
* Hyperbolic distance in Poincaré ball model
* Better for hierarchical/tree-like data (crates, packages, file paths)
*/
function poincareDistance(u, v, c = 1.0) {
const EPS = 1e-7;
const sqrtC = Math.sqrt(c);
let normDiffSq = 0, normUSq = 0, normVSq = 0;
for (let i = 0; i < u.length; i++) {
const diff = u[i] - (v[i] || 0);
normDiffSq += diff * diff;
normUSq += u[i] * u[i];
normVSq += (v[i] || 0) * (v[i] || 0);
}
const lambdaU = 1.0 - c * normUSq;
const lambdaV = 1.0 - c * normVSq;
const numerator = 2.0 * c * normDiffSq;
const denominator = Math.max(EPS, lambdaU * lambdaV);
const arg = Math.max(1.0, 1.0 + numerator / denominator);
return (1.0 / sqrtC) * Math.acosh(arg);
}
/**
* Text to embedding with hierarchical awareness
*/
function textToEmbedding(text, dims = 128) {
const embedding = new Float32Array(dims).fill(0);
const normalized = text.toLowerCase().replace(/[^a-z0-9\s]/g, ' ');
const words = normalized.split(/\s+/).filter(w => w.length > 1);
const wordFreq = {};
for (const word of words) {
wordFreq[word] = (wordFreq[word] || 0) + 1;
}
for (const [word, freq] of Object.entries(wordFreq)) {
const hash = createHash('sha256').update(word).digest();
const idfWeight = 1 / Math.log(1 + freq);
for (let i = 0; i < dims; i++) {
const byteIdx = i % hash.length;
const val = ((hash[byteIdx] & 0xFF) / 127.5) - 1;
embedding[i] += val * idfWeight;
}
}
// L2 normalize
const magnitude = Math.sqrt(embedding.reduce((sum, v) => sum + v * v, 0));
if (magnitude > 0) {
for (let i = 0; i < dims; i++) embedding[i] /= magnitude;
}
// Scale down to fit in Poincaré ball (|x| < 1)
const maxNorm = 0.95;
for (let i = 0; i < dims; i++) embedding[i] *= maxNorm;
return Array.from(embedding);
}
/**
* Cosine similarity (fallback)
*/
function cosineSimilarity(a, b) {
let dot = 0, magA = 0, magB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * (b[i] || 0);
magA += a[i] * a[i];
magB += (b[i] || 0) * (b[i] || 0);
}
return dot / (Math.sqrt(magA) * Math.sqrt(magB) || 1);
}
/**
* Vector Memory with Native HNSW + Hyperbolic distance option
*/
class VectorMemory {
constructor(options = {}) {
this.useHyperbolic = options.hyperbolic ?? true;
this.curvature = options.curvature ?? 1.0;
this.db = null;
this.memories = this.loadMemories();
this.dimensions = 128;
}
loadMemories() {
if (existsSync(MEMORY_FILE)) {
try { return JSON.parse(readFileSync(MEMORY_FILE, 'utf-8')); }
catch { return []; }
}
return [];
}
saveMemories() {
writeFileSync(MEMORY_FILE, JSON.stringify(this.memories, null, 2));
}
async init() {
if (ruvectorAvailable && VectorDB && !this.db) {
try {
this.db = new VectorDB({
dimensions: this.dimensions,
distanceMetric: 'Cosine', // Native HNSW uses cosine, we post-process with hyperbolic
hnswConfig: { m: 16, efConstruction: 200, efSearch: 100, maxElements: 50000 }
});
// Rebuild index from stored memories
let rebuilt = 0;
for (const mem of this.memories) {
if (mem.embedding) {
await this.db.insert({ id: mem.id, vector: new Float32Array(mem.embedding) });
rebuilt++;
}
}
console.error(`📊 VectorDB rebuilt with ${rebuilt} memories (HNSW index ready)`);
} catch (e) {
console.error('VectorDB init failed:', e.message);
this.db = null;
}
}
}
async store(type, content, metadata = {}) {
const id = `${type}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const embedding = textToEmbedding(content, this.dimensions);
const memory = {
id, type, content, embedding,
metadata: { ...metadata, timestamp: new Date().toISOString() }
};
this.memories.push(memory);
if (this.db) {
try { await this.db.insert({ id, vector: new Float32Array(embedding) }); }
catch (e) { /* fallback works */ }
}
this.saveMemories();
return id;
}
async search(query, limit = 5) {
const queryEmbedding = textToEmbedding(query, this.dimensions);
// Use native HNSW for candidate retrieval
let candidates = this.memories;
if (this.db) {
try {
const results = await this.db.search({
vector: new Float32Array(queryEmbedding),
k: Math.min(limit * 3, 50) // Get more candidates for reranking
});
candidates = results.map(r => this.memories.find(m => m.id === r.id)).filter(Boolean);
} catch (e) { /* use all memories */ }
}
// Rerank with hyperbolic distance if enabled
const scored = candidates.map(mem => {
let score;
if (this.useHyperbolic && mem.embedding) {
const dist = poincareDistance(queryEmbedding, mem.embedding, this.curvature);
score = 1 / (1 + dist); // Convert distance to similarity
} else {
score = cosineSimilarity(queryEmbedding, mem.embedding || []);
}
return { ...mem, score };
});
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
}
getStats() {
const typeCount = {};
for (const mem of this.memories) {
typeCount[mem.type] = (typeCount[mem.type] || 0) + 1;
}
return {
total: this.memories.length,
byType: typeCount,
usingNativeHNSW: !!this.db,
usingHyperbolic: this.useHyperbolic
};
}
}
/**
* Calibration Tracker - measures if confidence matches reality
*/
class CalibrationTracker {
constructor() {
this.data = this.load();
}
load() {
if (existsSync(CALIBRATION_FILE)) {
try { return JSON.parse(readFileSync(CALIBRATION_FILE, 'utf-8')); }
catch { return { buckets: {}, predictions: [] }; }
}
return { buckets: {}, predictions: [] };
}
save() {
writeFileSync(CALIBRATION_FILE, JSON.stringify(this.data, null, 2));
}
record(predicted, actual, confidence) {
const correct = predicted === actual;
const bucket = Math.floor(confidence * 10) / 10; // 0.0, 0.1, ..., 0.9
if (!this.data.buckets[bucket]) {
this.data.buckets[bucket] = { total: 0, correct: 0 };
}
this.data.buckets[bucket].total++;
if (correct) this.data.buckets[bucket].correct++;
this.data.predictions.push({
predicted, actual, correct, confidence,
timestamp: new Date().toISOString()
});
// Keep last 500 predictions
if (this.data.predictions.length > 500) {
this.data.predictions = this.data.predictions.slice(-500);
}
this.save();
return correct;
}
getCalibrationError() {
let totalError = 0, count = 0;
for (const [bucket, { total, correct }] of Object.entries(this.data.buckets)) {
if (total >= 5) {
const expectedRate = parseFloat(bucket) + 0.05;
const actualRate = correct / total;
totalError += Math.abs(expectedRate - actualRate);
count++;
}
}
return count > 0 ? totalError / count : 0;
}
getStats() {
const stats = {};
for (const [bucket, { total, correct }] of Object.entries(this.data.buckets)) {
stats[bucket] = {
total,
accuracy: (correct / total).toFixed(3),
expected: (parseFloat(bucket) + 0.05).toFixed(2)
};
}
return { buckets: stats, calibrationError: this.getCalibrationError().toFixed(3) };
}
}
/**
* Feedback Loop - track if suggestions were followed
*/
class FeedbackLoop {
constructor() {
this.data = this.load();
}
load() {
if (existsSync(FEEDBACK_FILE)) {
try { return JSON.parse(readFileSync(FEEDBACK_FILE, 'utf-8')); }
catch { return { suggestions: [], followRates: {} }; }
}
return { suggestions: [], followRates: {} };
}
save() {
writeFileSync(FEEDBACK_FILE, JSON.stringify(this.data, null, 2));
}
recordSuggestion(suggestionId, suggested, confidence) {
this.data.suggestions.push({
id: suggestionId,
suggested,
confidence,
followed: null,
outcome: null,
timestamp: new Date().toISOString()
});
this.save();
return suggestionId;
}
recordOutcome(suggestionId, actualUsed, success) {
const suggestion = this.data.suggestions.find(s => s.id === suggestionId);
if (suggestion) {
suggestion.followed = suggestion.suggested === actualUsed;
suggestion.outcome = success;
// Update follow rates
const key = suggestion.suggested;
if (!this.data.followRates[key]) {
this.data.followRates[key] = { total: 0, followed: 0, followedSuccess: 0, ignoredSuccess: 0 };
}
const r = this.data.followRates[key];
r.total++;
if (suggestion.followed) {
r.followed++;
if (success) r.followedSuccess++;
} else {
if (success) r.ignoredSuccess++;
}
this.save();
}
}
getAdviceValue() {
const result = {};
for (const [key, r] of Object.entries(this.data.followRates)) {
if (r.total >= 5) {
const followRate = r.followed / r.total;
const followedSuccessRate = r.followed > 0 ? r.followedSuccess / r.followed : 0;
const ignoredSuccessRate = (r.total - r.followed) > 0
? r.ignoredSuccess / (r.total - r.followed) : 0;
result[key] = {
followRate: followRate.toFixed(3),
followedSuccessRate: followedSuccessRate.toFixed(3),
ignoredSuccessRate: ignoredSuccessRate.toFixed(3),
adviceValue: (followedSuccessRate - ignoredSuccessRate).toFixed(3)
};
}
}
return result;
}
}
/**
* ReasoningBank with A/B Testing, Decay, and Active Learning
*/
class ReasoningBank {
constructor() {
this.trajectories = this.loadTrajectories();
this.qTable = this.loadPatterns();
this.alpha = 0.1;
this.gamma = 0.9;
this.epsilon = 0.1;
// A/B testing: Use environment override, or persistent session-based assignment
// INTELLIGENCE_MODE=treatment forces learning mode (for development/testing)
// INTELLIGENCE_MODE=control forces control mode (for baseline comparison)
this.abTestGroup = process.env.INTELLIGENCE_MODE ||
(this.getSessionId() % 100 < 5 ? 'control' : 'treatment'); // 5% holdout
this.decayHalfLife = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
}
loadTrajectories() {
if (existsSync(TRAJECTORIES_FILE)) {
try { return JSON.parse(readFileSync(TRAJECTORIES_FILE, 'utf-8')); }
catch { return []; }
}
return [];
}
loadPatterns() {
if (existsSync(PATTERNS_FILE)) {
try { return JSON.parse(readFileSync(PATTERNS_FILE, 'utf-8')); }
catch { return {}; }
}
return {};
}
/**
* Get persistent session ID for consistent A/B assignment
* Uses process PID + startup time hash for session-stable assignment
*/
getSessionId() {
// Combine PID with a time bucket (hourly) for session-stable but varied assignment
const hourBucket = Math.floor(Date.now() / (60 * 60 * 1000));
return (process.pid || 0) + hourBucket;
}
save() {
writeFileSync(TRAJECTORIES_FILE, JSON.stringify(this.trajectories.slice(-1000), null, 2));
writeFileSync(PATTERNS_FILE, JSON.stringify(this.qTable, null, 2));
}
stateKey(state) {
// Preserve hyphens in crate names (e.g., ruvector-core, micro-hnsw-wasm)
return state.toLowerCase().replace(/[^a-z0-9-]+/g, '_').slice(0, 80);
}
/**
* Calculate decay weight based on trajectory age
*/
getDecayWeight(timestamp) {
const age = Date.now() - new Date(timestamp).getTime();
return Math.pow(0.5, age / this.decayHalfLife);
}
/**
* Record trajectory with time-weighted learning
*/
recordTrajectory(state, action, outcome, reward) {
const stateKey = this.stateKey(state);
const trajectory = {
id: `traj-${Date.now()}`,
state: stateKey,
action, outcome, reward,
timestamp: new Date().toISOString(),
abGroup: this.abTestGroup
};
this.trajectories.push(trajectory);
// Time-weighted Q-learning with decay
if (!this.qTable[stateKey]) this.qTable[stateKey] = { _meta: { lastUpdate: null, updateCount: 0 } };
const meta = this.qTable[stateKey]._meta || { lastUpdate: null, updateCount: 0 };
const decayWeight = meta.lastUpdate ? this.getDecayWeight(meta.lastUpdate) : 1.0;
// Decayed current Q + new update
const currentQ = (this.qTable[stateKey][action] || 0) * decayWeight;
const updateCount = (meta.updateCount || 0) + 1;
const adaptiveLR = Math.max(0.01, this.alpha / Math.sqrt(updateCount));
this.qTable[stateKey][action] = Math.min(0.8, Math.max(-0.5,
currentQ + adaptiveLR * (reward - currentQ)
));
this.qTable[stateKey]._meta = {
lastUpdate: new Date().toISOString(),
updateCount
};
this.save();
return trajectory.id;
}
/**
* Get best action with A/B testing and active learning
*/
getBestAction(state, availableActions) {
const stateKey = this.stateKey(state);
const qValues = this.qTable[stateKey] || {};
// A/B Testing: Control group gets random actions
if (this.abTestGroup === 'control') {
const action = availableActions[Math.floor(Math.random() * availableActions.length)];
return { action, confidence: 0, reason: 'control-group', qValues, abGroup: 'control' };
}
// Exploration with probability ε
if (Math.random() < this.epsilon) {
const action = availableActions[Math.floor(Math.random() * availableActions.length)];
return { action, confidence: 0, reason: 'exploration', qValues, abGroup: 'treatment' };
}
// Exploitation
let bestAction = availableActions[0];
let bestQ = -Infinity;
let secondBestQ = -Infinity;
for (const action of availableActions) {
const q = qValues[action] || 0;
if (q > bestQ) {
secondBestQ = bestQ;
bestQ = q;
bestAction = action;
} else if (q > secondBestQ) {
secondBestQ = q;
}
}
const confidence = 1 / (1 + Math.exp(-bestQ * 2));
// Active Learning: flag uncertain states
const uncertainty = bestQ - secondBestQ;
const isUncertain = uncertainty < 0.1 && bestQ < 0.5;
return {
action: bestAction,
confidence: bestQ > 0 ? confidence : 0,
reason: bestQ > 0 ? 'learned-preference' : 'no-data',
qValues,
abGroup: 'treatment',
isUncertain,
uncertaintyGap: uncertainty.toFixed(3)
};
}
/**
* Get uncertain states for active learning
*/
getUncertainStates(threshold = 0.1) {
const uncertain = [];
for (const [state, actions] of Object.entries(this.qTable)) {
if (state === '_meta') continue;
const qVals = Object.entries(actions)
.filter(([k]) => k !== '_meta')
.map(([, v]) => v)
.sort((a, b) => b - a);
if (qVals.length >= 2) {
const gap = qVals[0] - qVals[1];
if (gap < threshold && qVals[0] < 0.5) {
uncertain.push({ state, gap, topQ: qVals[0] });
}
}
}
return uncertain.sort((a, b) => a.gap - b.gap).slice(0, 10);
}
getTopPatterns(limit = 10) {
const patterns = [];
for (const [state, actions] of Object.entries(this.qTable)) {
const sorted = Object.entries(actions)
.filter(([k]) => k !== '_meta')
.sort((a, b) => b[1] - a[1]);
if (sorted.length > 0) {
patterns.push({
state,
bestAction: sorted[0][0],
qValue: sorted[0][1].toFixed(3),
alternatives: sorted.slice(1, 3).map(([a, q]) => `${a}:${q.toFixed(2)}`)
});
}
}
return patterns.sort((a, b) => parseFloat(b.qValue) - parseFloat(a.qValue)).slice(0, limit);
}
getABStats() {
const treatment = this.trajectories.filter(t => t.abGroup === 'treatment');
const control = this.trajectories.filter(t => t.abGroup === 'control');
const treatmentSuccess = treatment.filter(t => t.reward > 0).length;
const controlSuccess = control.filter(t => t.reward > 0).length;
return {
treatment: { total: treatment.length, successRate: treatment.length > 0 ? (treatmentSuccess / treatment.length).toFixed(3) : 'N/A' },
control: { total: control.length, successRate: control.length > 0 ? (controlSuccess / control.length).toFixed(3) : 'N/A' },
lift: treatment.length > 10 && control.length > 10
? ((treatmentSuccess / treatment.length) - (controlSuccess / control.length)).toFixed(3)
: 'insufficient-data'
};
}
}
/**
* Error Pattern Tracker - learns from specific error types
*/
class ErrorPatternTracker {
constructor() {
this.data = this.load();
}
load() {
if (existsSync(ERROR_PATTERNS_FILE)) {
try { return JSON.parse(readFileSync(ERROR_PATTERNS_FILE, 'utf-8')); }
catch { return { patterns: {}, fixes: {}, recentErrors: [] }; }
}
return { patterns: {}, fixes: {}, recentErrors: [] };
}
save() {
writeFileSync(ERROR_PATTERNS_FILE, JSON.stringify(this.data, null, 2));
}
/**
* Parse error output to extract error codes and types
*/
parseError(stderr) {
const errors = [];
// Rust error codes (E0308, E0433, etc.)
const rustErrors = stderr.match(/error\[E\d{4}\]/g) || [];
for (const e of rustErrors) {
const code = e.match(/E\d{4}/)[0];
errors.push({ type: 'rust', code, category: this.categorizeRustError(code) });
}
// TypeScript errors (TS2304, TS2322, etc.)
const tsErrors = stderr.match(/TS\d{4}/g) || [];
for (const code of tsErrors) {
errors.push({ type: 'typescript', code, category: this.categorizeTsError(code) });
}
// npm/node errors
if (stderr.includes('ENOENT')) errors.push({ type: 'npm', code: 'ENOENT', category: 'file-not-found' });
if (stderr.includes('EACCES')) errors.push({ type: 'npm', code: 'EACCES', category: 'permission' });
if (stderr.includes('MODULE_NOT_FOUND')) errors.push({ type: 'node', code: 'MODULE_NOT_FOUND', category: 'missing-module' });
return errors;
}
categorizeRustError(code) {
const categories = {
'E0308': 'type-mismatch',
'E0433': 'missing-import',
'E0412': 'undefined-type',
'E0425': 'undefined-value',
'E0599': 'missing-method',
'E0277': 'trait-not-implemented',
'E0382': 'use-after-move',
'E0502': 'borrow-conflict',
'E0507': 'cannot-move-out',
'E0515': 'return-local-reference'
};
return categories[code] || 'other';
}
categorizeTsError(code) {
const categories = {
'TS2304': 'undefined-name',
'TS2322': 'type-mismatch',
'TS2339': 'missing-property',
'TS2345': 'argument-type',
'TS2769': 'overload-mismatch'
};
return categories[code] || 'other';
}
/**
* Record an error occurrence
*/
recordError(command, stderr, file = null, crate = null) {
const errors = this.parseError(stderr);
const timestamp = new Date().toISOString();
for (const error of errors) {
const key = `${error.type}:${error.code}`;
if (!this.data.patterns[key]) {
this.data.patterns[key] = { count: 0, category: error.category, contexts: [], lastSeen: null };
}
this.data.patterns[key].count++;
this.data.patterns[key].lastSeen = timestamp;
if (crate && !this.data.patterns[key].contexts.includes(crate)) {
this.data.patterns[key].contexts.push(crate);
}
}
// Store recent errors for sequence detection
if (errors.length > 0) {
this.data.recentErrors.push({ errors, command, file, crate, timestamp });
if (this.data.recentErrors.length > 100) {
this.data.recentErrors = this.data.recentErrors.slice(-100);
}
}
this.save();
return errors;
}
/**
* Record a successful fix for an error pattern
*/
recordFix(errorCode, fixDescription) {
if (!this.data.fixes[errorCode]) {
this.data.fixes[errorCode] = [];
}
this.data.fixes[errorCode].push({
fix: fixDescription,
timestamp: new Date().toISOString()
});
// Keep last 5 fixes per error
if (this.data.fixes[errorCode].length > 5) {
this.data.fixes[errorCode] = this.data.fixes[errorCode].slice(-5);
}
this.save();
}
/**
* Suggest fixes for an error code
*/
suggestFix(errorCode) {
const fixes = this.data.fixes[errorCode] || [];
const pattern = this.data.patterns[errorCode];
return {
errorCode,
category: pattern?.category || 'unknown',
occurrences: pattern?.count || 0,
commonContexts: pattern?.contexts?.slice(0, 3) || [],
recentFixes: fixes.slice(-3).map(f => f.fix)
};
}
getStats() {
const totalErrors = Object.values(this.data.patterns).reduce((s, p) => s + p.count, 0);
const topErrors = Object.entries(this.data.patterns)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 5)
.map(([code, p]) => ({ code, count: p.count, category: p.category }));
return { totalErrors, topErrors, fixesRecorded: Object.keys(this.data.fixes).length };
}
}
/**
* File Sequence Tracker - learns which files are often edited together
*/
class SequenceTracker {
constructor() {
this.data = this.load();
this.sessionEdits = []; // Track edits in current session
}
load() {
if (existsSync(SEQUENCES_FILE)) {
try { return JSON.parse(readFileSync(SEQUENCES_FILE, 'utf-8')); }
catch { return { sequences: {}, coedits: {}, testPairs: {} }; }
}
return { sequences: {}, coedits: {}, testPairs: {} };
}
save() {
writeFileSync(SEQUENCES_FILE, JSON.stringify(this.data, null, 2));
}
/**
* Record a file edit and learn sequences
*/
recordEdit(file) {
const timestamp = Date.now();
const normalizedFile = this.normalizePath(file);
// Check for sequence from previous edit
if (this.sessionEdits.length > 0) {
const lastEdit = this.sessionEdits[this.sessionEdits.length - 1];
const timeDiff = timestamp - lastEdit.timestamp;
// If edited within 5 minutes, consider it a sequence
if (timeDiff < 5 * 60 * 1000) {
this.recordSequence(lastEdit.file, normalizedFile);
}
}
// Detect test file pairing
this.detectTestPair(normalizedFile);
this.sessionEdits.push({ file: normalizedFile, timestamp });
// Keep session to last 20 edits
if (this.sessionEdits.length > 20) {
this.sessionEdits = this.sessionEdits.slice(-20);
}
this.save();
}
normalizePath(file) {
// Normalize to relative path from crates/ or src/
const match = file.match(/(crates\/[^/]+\/.*|src\/.*|tests\/.*)/);
return match ? match[1] : file.split('/').slice(-3).join('/');
}
recordSequence(from, to) {
if (from === to) return;
if (!this.data.sequences[from]) {
this.data.sequences[from] = {};
}
if (!this.data.sequences[from][to]) {
this.data.sequences[from][to] = { count: 0, lastSeen: null };
}
this.data.sequences[from][to].count++;
this.data.sequences[from][to].lastSeen = new Date().toISOString();
// Also record as co-edit (bidirectional)
const pairKey = [from, to].sort().join('|');
if (!this.data.coedits[pairKey]) {
this.data.coedits[pairKey] = { count: 0, files: [from, to] };
}
this.data.coedits[pairKey].count++;
}
detectTestPair(file) {
// Match source file to test file patterns
let testFile = null;
let sourceFile = null;
if (file.includes('/tests/') || file.includes('.test.') || file.includes('_test.')) {
testFile = file;
// Try to find corresponding source
sourceFile = file
.replace('/tests/', '/src/')
.replace('.test.', '.')
.replace('_test.', '.');
} else if (file.includes('/src/')) {
sourceFile = file;
// Construct potential test file paths
const ext = file.split('.').pop();
testFile = file
.replace('/src/', '/tests/')
.replace(`.${ext}`, `.test.${ext}`);
}
if (testFile && sourceFile) {
const pairKey = [sourceFile, testFile].sort().join('|');
if (!this.data.testPairs[pairKey]) {
this.data.testPairs[pairKey] = { source: sourceFile, test: testFile, editCount: 0 };
}
this.data.testPairs[pairKey].editCount++;
}
}
/**
* Suggest next files based on current file
*/
suggestNextFiles(currentFile, limit = 3) {
const normalized = this.normalizePath(currentFile);
const sequences = this.data.sequences[normalized] || {};
const suggestions = Object.entries(sequences)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, limit)
.map(([file, data]) => ({
file,
probability: Math.min(0.9, data.count / 10),
timesSequenced: data.count
}));
// Also check for test file suggestion
const testSuggestion = this.suggestTestFile(currentFile);
if (testSuggestion && !suggestions.find(s => s.file === testSuggestion.file)) {
suggestions.push(testSuggestion);
}
return suggestions.slice(0, limit);
}
/**
* Suggest test file for a source file
*/
suggestTestFile(sourceFile) {
const normalized = this.normalizePath(sourceFile);
// Find matching test pair
for (const [, pair] of Object.entries(this.data.testPairs)) {
if (pair.source === normalized || normalized.includes(pair.source)) {
return {
file: pair.test,
type: 'test-file',
probability: 0.8,
reason: 'Corresponding test file'
};
}
}
// Generate test file path if not found
if (sourceFile.includes('/src/') && !sourceFile.includes('test')) {
const ext = sourceFile.split('.').pop();
const testPath = sourceFile
.replace('/src/', '/tests/')
.replace(`.${ext}`, ext === 'rs' ? `_test.${ext}` : `.test.${ext}`);
return {
file: this.normalizePath(testPath),
type: 'suggested-test',
probability: 0.5,
reason: 'Suggested test location'
};
}
return null;
}
/**
* Suggest running tests after editing source files
*/
shouldSuggestTests(file) {
const normalized = this.normalizePath(file);
// Always suggest tests for Rust source files
if (file.endsWith('.rs') && file.includes('/src/') && !file.includes('test')) {
const crateMatch = file.match(/crates\/([^/]+)/);
const crate = crateMatch ? crateMatch[1] : null;
return {
suggest: true,
command: crate ? `cargo test -p ${crate}` : 'cargo test',
reason: 'Source file modified'
};
}
// Suggest tests for TypeScript source files
if ((file.endsWith('.ts') || file.endsWith('.tsx')) && !file.includes('.test.')) {
return {
suggest: true,
command: 'npm test',
reason: 'TypeScript source modified'
};
}
return { suggest: false };
}
getStats() {
return {
totalSequences: Object.keys(this.data.sequences).length,
totalCoedits: Object.keys(this.data.coedits).length,
testPairs: Object.keys(this.data.testPairs).length,
sessionEdits: this.sessionEdits.length
};
}
}
/**
* Neural Router with enhanced intelligence
*/
class NeuralRouter {
constructor(memory, reasoning, calibration, feedback) {
this.memory = memory;
this.reasoning = reasoning;
this.calibration = calibration;
this.feedback = feedback;
}
async route(task, context = {}) {
const { fileType, crate, operation = 'edit' } = context;
// Use underscore format to match pretrained Q-table
const state = `${operation}_${fileType || 'file'}_in_${crate || 'project'}`;
const agents = this.getAgentsForContext(fileType, crate);
const suggestion = this.reasoning.getBestAction(state, agents);
const similar = await this.memory.search(task, 3);
let finalAgent = suggestion.action;
let finalConf = suggestion.confidence;
if (similar.length > 0 && similar[0].score > 0.7) {
const pastAgent = similar[0].metadata?.agent;
if (pastAgent && agents.includes(pastAgent)) {
finalAgent = pastAgent;
finalConf = Math.min(1, finalConf + 0.2);
}
}
// Record for feedback tracking
const suggestionId = `sug-${Date.now()}`;
this.feedback.recordSuggestion(suggestionId, finalAgent, finalConf);
return {
recommended: finalAgent,
confidence: finalConf,
reason: this.buildReason(finalAgent, suggestion.reason, similar),
alternatives: agents.filter(a => a !== finalAgent).slice(0, 3),
context: { state, agents, similar: similar.slice(0, 2) },
suggestionId,
abGroup: suggestion.abGroup,
isUncertain: suggestion.isUncertain
};
}
getAgentsForContext(fileType, crate) {
const base = ['coder', 'reviewer', 'tester'];
const typeMap = {
'rs': ['rust-developer', 'code-analyzer'],
'ts': ['typescript-developer', 'backend-dev'],
'js': ['javascript-developer', 'backend-dev'],
'md': ['technical-writer'],
'json': ['config-specialist'],
'py': ['python-developer'],
'css': ['frontend-developer'],
'html': ['frontend-developer'],
'tsx': ['frontend-developer'],
'yml': ['devops-engineer'],
'yaml': ['devops-engineer'],
'sql': ['database-expert'],
'sh': ['system-admin']
};
if (typeMap[fileType]) base.push(...typeMap[fileType]);
// Crate-specific specializations
if (fileType === 'rs') {
if (crate?.includes('wasm') || crate === 'rvlite') base.push('production-validator');
if (crate?.includes('gnn') || crate?.includes('attention') || crate === 'sona') base.push('ml-developer');
if (crate?.includes('postgres')) base.push('backend-dev', 'system-architect');
if (crate?.includes('mincut') || crate?.includes('graph')) base.push('system-architect');
}
return [...new Set(base)];
}
buildReason(agent, qReason, similar) {
const parts = [];
if (qReason === 'learned-preference') parts.push('learned from past success');
if (similar.length > 0 && similar[0].score > 0.6) parts.push('similar past task succeeded');
if (parts.length === 0) parts.push('default selection');
return `${agent}: ${parts.join(', ')}`;
}
}
/**
* Main Intelligence API v2
*/
class RuVectorIntelligence {
constructor(options = {}) {
this.memory = new VectorMemory({ hyperbolic: options.hyperbolic ?? true });
this.reasoning = new ReasoningBank();
this.calibration = new CalibrationTracker();
this.feedback = new FeedbackLoop();
this.errorPatterns = new ErrorPatternTracker();
this.sequences = new SequenceTracker();
this.router = new NeuralRouter(this.memory, this.reasoning, this.calibration, this.feedback);
this.initialized = false;
}
async init() {
if (!this.initialized) {
await this.memory.init();
this.initialized = true;
}
}
async remember(type, content, metadata = {}) {
await this.init();
return this.memory.store(type, content, metadata);
}
async recall(query, limit = 5) {
await this.init();
return this.memory.search(query, limit);
}
learn(state, action, outcome, reward) {
return this.reasoning.recordTrajectory(state, action, outcome, reward);
}
suggest(state, actions) {
return this.reasoning.getBestAction(state, actions);
}
async route(task, context = {}) {
await this.init();
return this.router.route(task, context);
}
recordCalibration(predicted, actual, confidence) {
return this.calibration.record(predicted, actual, confidence);
}
recordFeedback(suggestionId, actualUsed, success) {
this.feedback.recordOutcome(suggestionId, actualUsed, success);
}
// === New v3 Features ===
/**
* Record an error from command output
*/
recordError(command, stderr, file = null, crate = null) {
return this.errorPatterns.recordError(command, stderr, file, crate);
}
/**
* Record a fix for an error pattern
*/
recordFix(errorCode, fixDescription) {
this.errorPatterns.recordFix(errorCode, fixDescription);
}
/**
* Get suggested fixes for an error
*/
suggestFix(errorCode) {
return this.errorPatterns.suggestFix(errorCode);
}
/**
* Record a file edit for sequence learning
*/
recordFileEdit(file) {
this.sequences.recordEdit(file);
}
/**
* Suggest next files based on current file
*/
suggestNextFiles(file, limit = 3) {
return this.sequences.suggestNextFiles(file, limit);
}
/**
* Check if tests should be suggested after editing a file
*/
shouldSuggestTests(file) {
return this.sequences.shouldSuggestTests(file);
}
stats() {
return {
memory: this.memory.getStats(),
trajectories: this.reasoning.trajectories.length,
patterns: Object.keys(this.reasoning.qTable).length,
topPatterns: this.reasoning.getTopPatterns(5),
calibration: this.calibration.getStats(),
abTest: this.reasoning.getABStats(),
adviceValue: this.feedback.getAdviceValue(),
uncertainStates: this.reasoning.getUncertainStates(0.15),
// v3 stats
errorPatterns: this.errorPatterns.getStats(),
sequences: this.sequences.getStats(),
ruvectorNative: ruvectorAvailable
};
}
}
export { RuVectorIntelligence, VectorMemory, ReasoningBank, NeuralRouter, CalibrationTracker, FeedbackLoop, ErrorPatternTracker, SequenceTracker };
export default RuVectorIntelligence;