mirror of
https://github.com/ruvnet/RuView
synced 2026-08-04 19:31:42 +00:00
226 lines
8.1 KiB
JavaScript
226 lines
8.1 KiB
JavaScript
"use strict";
|
|
/**
|
|
* External Intelligence Providers for SONA Learning (ADR-043)
|
|
*
|
|
* TypeScript bindings for the IntelligenceProvider trait, enabling
|
|
* external systems to feed quality signals into RuvLLM's learning loops.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { IntelligenceLoader, FileSignalProvider, QualitySignal } from '@ruvector/ruvllm';
|
|
*
|
|
* const loader = new IntelligenceLoader();
|
|
* loader.registerProvider(new FileSignalProvider('./signals.json'));
|
|
*
|
|
* const { signals, errors } = loader.loadAllSignals();
|
|
* console.log(`Loaded ${signals.length} signals`);
|
|
* ```
|
|
*/
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.IntelligenceLoader = exports.FileSignalProvider = void 0;
|
|
const fs = __importStar(require("fs"));
|
|
const path = __importStar(require("path"));
|
|
/** Maximum signal file size (10 MiB) */
|
|
const MAX_SIGNAL_FILE_SIZE = 10 * 1024 * 1024;
|
|
/** Maximum number of signals per file */
|
|
const MAX_SIGNALS_PER_FILE = 10000;
|
|
/** Valid outcome values */
|
|
const VALID_OUTCOMES = new Set(['success', 'partial_success', 'failure']);
|
|
/** Valid human verdict values */
|
|
const VALID_VERDICTS = new Set(['approved', 'rejected']);
|
|
function asOptionalNumber(val) {
|
|
if (val === undefined || val === null)
|
|
return undefined;
|
|
const n = Number(val);
|
|
return Number.isFinite(n) && n >= 0 && n <= 1 ? n : undefined;
|
|
}
|
|
function validateOutcome(val) {
|
|
const s = String(val ?? 'failure');
|
|
return VALID_OUTCOMES.has(s) ? s : 'failure';
|
|
}
|
|
function validateVerdict(val) {
|
|
if (val === undefined || val === null)
|
|
return undefined;
|
|
const s = String(val);
|
|
return VALID_VERDICTS.has(s) ? s : undefined;
|
|
}
|
|
function validateScore(val) {
|
|
const n = Number(val ?? 0);
|
|
if (!Number.isFinite(n) || n < 0 || n > 1)
|
|
return 0;
|
|
return n;
|
|
}
|
|
function mapQualityFactors(raw) {
|
|
return {
|
|
acceptanceCriteriaMet: asOptionalNumber(raw.acceptance_criteria_met),
|
|
testsPassing: asOptionalNumber(raw.tests_passing),
|
|
noRegressions: asOptionalNumber(raw.no_regressions),
|
|
lintClean: asOptionalNumber(raw.lint_clean),
|
|
typeCheckClean: asOptionalNumber(raw.type_check_clean),
|
|
followsPatterns: asOptionalNumber(raw.follows_patterns),
|
|
contextRelevance: asOptionalNumber(raw.context_relevance),
|
|
reasoningCoherence: asOptionalNumber(raw.reasoning_coherence),
|
|
executionEfficiency: asOptionalNumber(raw.execution_efficiency),
|
|
};
|
|
}
|
|
/**
|
|
* Built-in file-based intelligence provider.
|
|
*
|
|
* Reads quality signals from a JSON file. This is the default provider
|
|
* for non-Rust integrations that write signal files.
|
|
*/
|
|
class FileSignalProvider {
|
|
constructor(filePath) {
|
|
this.filePath = path.resolve(filePath);
|
|
}
|
|
name() {
|
|
return 'file-signals';
|
|
}
|
|
loadSignals() {
|
|
if (!fs.existsSync(this.filePath)) {
|
|
return [];
|
|
}
|
|
// Check file size before reading (prevent OOM)
|
|
const stat = fs.statSync(this.filePath);
|
|
if (stat.size > MAX_SIGNAL_FILE_SIZE) {
|
|
throw new Error(`Signal file exceeds max size (${stat.size} bytes, limit ${MAX_SIGNAL_FILE_SIZE})`);
|
|
}
|
|
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
|
const data = JSON.parse(raw);
|
|
if (!Array.isArray(data)) {
|
|
return [];
|
|
}
|
|
// Check signal count
|
|
if (data.length > MAX_SIGNALS_PER_FILE) {
|
|
throw new Error(`Signal file contains ${data.length} signals, max is ${MAX_SIGNALS_PER_FILE}`);
|
|
}
|
|
return data.map((item) => {
|
|
const qfRaw = (item.quality_factors ?? item.qualityFactors);
|
|
return {
|
|
id: String(item.id ?? ''),
|
|
taskDescription: String(item.task_description ?? item.taskDescription ?? ''),
|
|
outcome: validateOutcome(item.outcome),
|
|
qualityScore: validateScore(item.quality_score ?? item.qualityScore),
|
|
humanVerdict: validateVerdict(item.human_verdict ?? item.humanVerdict),
|
|
qualityFactors: qfRaw ? mapQualityFactors(qfRaw) : undefined,
|
|
completedAt: String(item.completed_at ?? item.completedAt ?? new Date().toISOString()),
|
|
};
|
|
});
|
|
}
|
|
qualityWeights() {
|
|
try {
|
|
const weightsPath = path.join(path.dirname(this.filePath), 'quality-weights.json');
|
|
if (!fs.existsSync(weightsPath))
|
|
return undefined;
|
|
const raw = fs.readFileSync(weightsPath, 'utf-8');
|
|
const data = JSON.parse(raw);
|
|
return {
|
|
taskCompletion: Number(data.task_completion ?? data.taskCompletion ?? 0.5),
|
|
codeQuality: Number(data.code_quality ?? data.codeQuality ?? 0.3),
|
|
process: Number(data.process ?? 0.2),
|
|
};
|
|
}
|
|
catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
exports.FileSignalProvider = FileSignalProvider;
|
|
/**
|
|
* Aggregates quality signals from multiple registered providers.
|
|
*
|
|
* If no providers are registered, loadAllSignals returns empty arrays
|
|
* with zero overhead.
|
|
*/
|
|
class IntelligenceLoader {
|
|
constructor() {
|
|
this.providers = [];
|
|
}
|
|
/** Register an external intelligence provider */
|
|
registerProvider(provider) {
|
|
this.providers.push(provider);
|
|
}
|
|
/** Returns the number of registered providers */
|
|
get providerCount() {
|
|
return this.providers.length;
|
|
}
|
|
/** Returns the names of all registered providers */
|
|
get providerNames() {
|
|
return this.providers.map(p => p.name());
|
|
}
|
|
/**
|
|
* Load signals from all registered providers.
|
|
*
|
|
* Non-fatal: if a provider fails, its error is captured but
|
|
* other providers continue loading.
|
|
*/
|
|
loadAllSignals() {
|
|
const signals = [];
|
|
const errors = [];
|
|
for (const provider of this.providers) {
|
|
try {
|
|
const providerSignals = provider.loadSignals();
|
|
signals.push(...providerSignals);
|
|
}
|
|
catch (e) {
|
|
errors.push({
|
|
providerName: provider.name(),
|
|
message: e instanceof Error ? e.message : String(e),
|
|
});
|
|
}
|
|
}
|
|
return { signals, errors };
|
|
}
|
|
/** Load signals grouped by provider with weight overrides */
|
|
loadGrouped() {
|
|
return this.providers.map(provider => {
|
|
let providerSignals = [];
|
|
try {
|
|
providerSignals = provider.loadSignals();
|
|
}
|
|
catch {
|
|
// Non-fatal
|
|
}
|
|
return {
|
|
providerName: provider.name(),
|
|
signals: providerSignals,
|
|
weights: provider.qualityWeights?.(),
|
|
};
|
|
});
|
|
}
|
|
}
|
|
exports.IntelligenceLoader = IntelligenceLoader;
|
|
//# sourceMappingURL=intelligence.js.map
|