From 4e374683fc1701ac72527ad4eeb85975bb7a09cf Mon Sep 17 00:00:00 2001 From: rUv Date: Sat, 21 Feb 2026 18:16:19 +0000 Subject: [PATCH] feat(npm): add intelligence module to @ruvector/ruvllm 2.5.0 TypeScript IntelligenceProvider, FileSignalProvider, and IntelligenceLoader matching the Rust ADR-043 implementation. Also fixes invalid category slug for ruvllm crate publish. Co-Authored-By: claude-flow --- crates/ruvllm/Cargo.toml | 2 +- npm/packages/ruvllm/package.json | 2 +- npm/packages/ruvllm/src/index.ts | 3 + npm/packages/ruvllm/src/intelligence.ts | 248 ++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 npm/packages/ruvllm/src/intelligence.ts diff --git a/crates/ruvllm/Cargo.toml b/crates/ruvllm/Cargo.toml index e2f19295..233e55e5 100644 --- a/crates/ruvllm/Cargo.toml +++ b/crates/ruvllm/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true readme = "README.md" description = "LLM serving runtime with Ruvector integration - Paged attention, KV cache, and SONA learning" keywords = ["llm", "inference", "paged-attention", "kv-cache", "ruvector"] -categories = ["science", "algorithms", "machine-learning"] +categories = ["science", "algorithms"] [dependencies] # Ruvector integration diff --git a/npm/packages/ruvllm/package.json b/npm/packages/ruvllm/package.json index 430a7c97..24e1bc2e 100644 --- a/npm/packages/ruvllm/package.json +++ b/npm/packages/ruvllm/package.json @@ -1,6 +1,6 @@ { "name": "@ruvector/ruvllm", - "version": "2.3.0", + "version": "2.5.0", "description": "Self-learning LLM orchestration with SONA adaptive learning, HNSW memory, FastGRNN routing, and SIMD inference", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/npm/packages/ruvllm/src/index.ts b/npm/packages/ruvllm/src/index.ts index 9a4d9037..4578f470 100644 --- a/npm/packages/ruvllm/src/index.ts +++ b/npm/packages/ruvllm/src/index.ts @@ -89,6 +89,9 @@ export * from './models'; // Benchmarks for Claude Code use cases export * from './benchmarks'; +// External Intelligence Providers (ADR-043) +export * from './intelligence'; + // Native bindings utilities export { version, hasSimdSupport } from './native'; diff --git a/npm/packages/ruvllm/src/intelligence.ts b/npm/packages/ruvllm/src/intelligence.ts new file mode 100644 index 00000000..0e9e6ba4 --- /dev/null +++ b/npm/packages/ruvllm/src/intelligence.ts @@ -0,0 +1,248 @@ +/** + * 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`); + * ``` + */ + +/** + * A quality signal from an external system. + * + * Represents one completed task with quality assessment data + * that can feed into SONA trajectories, the embedding classifier, + * and model router calibration. + */ +export interface QualitySignal { + /** Unique identifier for this signal */ + id: string; + /** Human-readable task description (used for embedding generation) */ + taskDescription: string; + /** Execution outcome */ + outcome: 'success' | 'partial_success' | 'failure'; + /** Composite quality score (0.0 - 1.0) */ + qualityScore: number; + /** Optional human verdict */ + humanVerdict?: 'approved' | 'rejected'; + /** Optional structured quality factors for detailed analysis */ + qualityFactors?: QualityFactors; + /** ISO 8601 timestamp of task completion */ + completedAt: string; +} + +/** + * Granular quality factor breakdown. + * + * Not all providers will have all factors. Undefined fields mean + * "not assessed" (distinct from 0.0, which means "assessed as zero"). + */ +export interface QualityFactors { + acceptanceCriteriaMet?: number; + testsPassing?: number; + noRegressions?: number; + lintClean?: number; + typeCheckClean?: number; + followsPatterns?: number; + contextRelevance?: number; + reasoningCoherence?: number; + executionEfficiency?: number; +} + +/** + * Quality weight overrides from a provider. + * + * Weights should sum to approximately 1.0. + */ +export interface ProviderQualityWeights { + taskCompletion: number; + codeQuality: number; + process: number; +} + +/** + * Error from a single provider during batch loading. + */ +export interface ProviderError { + providerName: string; + message: string; +} + +/** + * Result from a single provider during grouped loading. + */ +export interface ProviderResult { + providerName: string; + signals: QualitySignal[]; + weights?: ProviderQualityWeights; +} + +/** + * Interface for external systems that supply quality signals to RuvLLM. + * + * Implement this interface and register with IntelligenceLoader. + */ +export interface IntelligenceProvider { + /** Human-readable name for this provider */ + name(): string; + /** Load quality signals from this provider's data source */ + loadSignals(): QualitySignal[]; + /** Optional quality weight overrides */ + qualityWeights?(): ProviderQualityWeights | undefined; +} + +/** + * 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. + */ +export class FileSignalProvider implements IntelligenceProvider { + private readonly filePath: string; + + constructor(filePath: string) { + this.filePath = filePath; + } + + name(): string { + return 'file-signals'; + } + + loadSignals(): QualitySignal[] { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const fs = require('fs'); + if (!fs.existsSync(this.filePath)) { + return []; + } + const raw = fs.readFileSync(this.filePath, 'utf-8'); + const data = JSON.parse(raw); + if (!Array.isArray(data)) { + return []; + } + return data.map((item: Record) => ({ + id: String(item.id ?? ''), + taskDescription: String(item.task_description ?? item.taskDescription ?? ''), + outcome: String(item.outcome ?? 'failure') as QualitySignal['outcome'], + qualityScore: Number(item.quality_score ?? item.qualityScore ?? 0), + humanVerdict: item.human_verdict ?? item.humanVerdict + ? String(item.human_verdict ?? item.humanVerdict) as QualitySignal['humanVerdict'] + : undefined, + qualityFactors: (item.quality_factors || item.qualityFactors) + ? mapQualityFactors((item.quality_factors ?? item.qualityFactors) as Record) + : undefined, + completedAt: String(item.completed_at ?? item.completedAt ?? new Date().toISOString()), + })); + } catch { + return []; + } + } + + qualityWeights(): ProviderQualityWeights | undefined { + try { + const fs = require('fs'); + const path = require('path'); + 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; + } + } +} + +function mapQualityFactors(raw: Record): QualityFactors { + return { + acceptanceCriteriaMet: raw.acceptance_criteria_met as number | undefined, + testsPassing: raw.tests_passing as number | undefined, + noRegressions: raw.no_regressions as number | undefined, + lintClean: raw.lint_clean as number | undefined, + typeCheckClean: raw.type_check_clean as number | undefined, + followsPatterns: raw.follows_patterns as number | undefined, + contextRelevance: raw.context_relevance as number | undefined, + reasoningCoherence: raw.reasoning_coherence as number | undefined, + executionEfficiency: raw.execution_efficiency as number | undefined, + }; +} + +/** + * Aggregates quality signals from multiple registered providers. + * + * If no providers are registered, loadAllSignals returns empty arrays + * with zero overhead. + */ +export class IntelligenceLoader { + private providers: IntelligenceProvider[] = []; + + /** Register an external intelligence provider */ + registerProvider(provider: IntelligenceProvider): void { + this.providers.push(provider); + } + + /** Returns the number of registered providers */ + get providerCount(): number { + return this.providers.length; + } + + /** Returns the names of all registered providers */ + get providerNames(): string[] { + 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(): { signals: QualitySignal[]; errors: ProviderError[] } { + const signals: QualitySignal[] = []; + const errors: ProviderError[] = []; + + 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(): ProviderResult[] { + return this.providers.map(provider => { + let providerSignals: QualitySignal[] = []; + try { + providerSignals = provider.loadSignals(); + } catch { + // Non-fatal + } + return { + providerName: provider.name(), + signals: providerSignals, + weights: provider.qualityWeights?.(), + }; + }); + } +}