ruvector/.claude/intelligence/tests/validate.js
rUv e3cef7d5f1 Feat/ruvector postgres v2 (#82)
* feat(postgres): Add RuVector Postgres v2 implementation plan

Complete specification for RuVector Postgres v2 with:

Architecture:
- PostgreSQL extension (pgrx) with hybrid architecture
- SQL handles ACID/joins, RuVector engine handles vectors/graphs/learning
- Backward compatible with pgvector SQL surface
- Shared memory IPC with bounded contracts (64KB inline, 16MB shared)

4-Phase Implementation:
- Phase 1: pgvector-compatible search (1a: function-based, 1b: Index AM)
- Phase 2: Tiered storage with compression and exactness GUC
- Phase 3: Graph engine with Cypher and SQL join keys
- Phase 4: Dynamic mincut integrity gating (key differentiator)

Key Technical Details:
- lambda_cut: Minimum cut value via Stoer-Wagner (PRIMARY integrity metric)
- lambda2: Algebraic connectivity (OPTIONAL drift signal) - DIFFERENT from mincut!
- Contracted operational graph (~1000 nodes) - never compute on full similarity graph
- Hysteresis model with consecutive samples and cooldown
- Operation risk classification (Low/Medium/High)
- MVCC visibility with incremental paging API
- WAL replay with idempotency and LSN ordering
- Partition map versioning and epoch fencing for cluster mode

Files:
- 00-overview.md: Architecture, consistency contract, benchmark spec
- 01-sql-schema.md: SQL schema and types
- 02-background-workers.md: IPC contract, mincut worker
- 03-index-access-methods.md: Index AM specification
- 04-integrity-events.md: Events, hysteresis, operation classes
- 05-phase1-pgvector-compat.md: Phase 1a/1b incremental path
- 06-phase2-tiered-storage.md: Tiered storage with GUC exactness
- 07-phase3-graph-cypher.md: Graph engine with SQL joins
- 08-phase4-integrity-control.md: Mincut gating with Stoer-Wagner
- 09-migration-guide.md: Migration from pgvector
- 10-consistency-replication.md: Consistency and replication model

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(postgres): Rewrite v2 overview with compelling framing

Replace technical executive summary with clear explanation of why
RuVector matters:

- From symptom monitoring to causal monitoring
- Mincut as leading indicator, not metric
- Algorithm becomes control signal (control plane, not analytics)
- Failure mode class change: cascading → graceful degradation
- Explainable operations via witness edges

Key message: "We're not making vector search faster.
We're making vector infrastructure survivable."

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(postgres): Add hybrid search, multi-tenancy, and self-healing specs

Three high-impact additions to RuVector Postgres v2:

## 11-hybrid-search.md - BM25 + Vector Fusion
- Single query combines semantic and keyword search
- Proper BM25 implementation (not just ts_rank)
- Fusion algorithms: RRF (default), linear, learned
- Integrity-aware degradation (stress → single branch)
- Parallel branch execution
- GUC configuration

## 12-multi-tenancy.md - First-Class Tenant Isolation
- SET ruvector.tenant_id for transparent scoping
- Isolation levels: shared, partition, dedicated
- Automatic promotion based on vector count
- Per-tenant integrity (stress in one doesn't affect others)
- Per-tenant contracted graphs
- Resource quotas and rate limiting
- Fair scheduling (no noisy neighbors)
- RLS integration for defense in depth

## 13-self-healing.md - Automated Remediation
- Completes the control loop: sensor → actuator
- Problem classification from witness edges:
  - Hotspot congestion
  - Centroid skew
  - Replication lag
  - Maintenance contention
  - Index fragmentation
  - Memory pressure
- Built-in strategies:
  - Rebalance partitions
  - Pause maintenance jobs
  - Throttle ingestion
  - Scale read replicas (K8s)
  - Compact fragmented indexes
- Safety: reversible actions, blast radius limits
- Learning: outcome tracking, strategy weight updates
- The key insight: "We built the sensor. Now we build the actuator."

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(intelligence): Add self-learning intelligence layer with v3 features

Comprehensive intelligence system for Claude Code hooks:

Core Features (v2):
- VectorMemory with @ruvector/core native HNSW (150x faster)
- Hyperbolic distance (Poincaré ball) for hierarchical embeddings
- ReasoningBank with Q-learning and pattern decay (7-day half-life)
- Confidence Calibration tracking (predicted vs actual accuracy)
- A/B Testing with 10% holdout for measuring intelligence lift
- Feedback Loop for tracking suggestion follow-through
- Active Learning for identifying uncertain states

v3 Improvements:
- Error Pattern Learning (Rust E0xxx, TypeScript TSxxxx, npm errors)
- File Sequence Learning (tracks which files are edited together)
- Test Suggestion Triggers (suggests cargo test after source edits)
- Hive-Mind swarm coordination (11 agents, 38 edges)

Pretrained from memory.db:
- 7,697 commands processed
- 4,023 vector memories
- 117 Q-table states with decay metadata
- 8,520 calibration samples

Anti-overfitting measures:
- Q-values capped at 0.8, floored at -0.5
- Decaying learning rate: 0.3/sqrt(count)
- Pattern decay with timestamps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(intelligence): Fix Q-table lookups - learning now has real effect

Three critical bugs were preventing the intelligence layer from using
learned patterns:

1. State format mismatch: CLI used spaces ("editing rs in project")
   but Q-table used underscores ("edit_rs_in_project")
   - Fixed in cli.js: all states now use underscore format

2. stateKey() hyphen normalization: Function converted hyphens to
   underscores, but Q-table keys had hyphens (e.g. "ruvector-core")
   - Fixed regex: /[^a-z0-9-]+/g preserves hyphens

3. A/B testing control group: 10% random sessions ignored learning
   - Reduced holdout to 5% with persistent session assignment
   - Added INTELLIGENCE_MODE=treatment env override for development

Result: Agent recommendations now show 80% confidence for Rust files
using learned Q-values, instead of 0% with random selection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(hooks): Display intelligence guidance to Claude in foreground

Critical fix: PreToolUse hooks were running in background (&) which
meant Claude never saw the intelligence output. Now:

- PreToolUse: Foreground execution (Claude sees guidance)
  - pre-edit: Shows recommended agent + confidence + similar edits
  - pre-command: Shows command patterns + suggestions
  - Added 3s timeout to prevent blocking

- PostToolUse: Background execution (async learning)
  - post-edit: Records success/failure, learns patterns
  - post-command: Captures errors, updates Q-values

- SessionStart: New hook shows learned patterns at session start
  - Displays pattern count, memory stats
  - Shows top 3 learned state-action pairs with Q-values

Claude now receives self-learning guidance like:
  "🧠 Intelligence Analysis:
   📁 ruvector-core/lib.rs
   🤖 Recommended: rust-developer (80% confidence)
   📚 3 similar past edits found"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 17:02:55 -05:00

240 lines
7.9 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* RuVector Intelligence Validation Suite
*
* Validates pretrained data for:
* - Q-table integrity (no overfitting)
* - Vector memory retrieval
* - Swarm graph connectivity
* - Agent routing accuracy
*/
import { readFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_DIR = join(__dirname, '..', 'data');
const results = { passed: 0, failed: 0, warnings: 0 };
function test(name, fn) {
try {
const result = fn();
if (result === true) {
console.log(`${name}`);
results.passed++;
} else if (result === 'warn') {
console.log(` ⚠️ ${name}`);
results.warnings++;
} else {
console.log(`${name}: ${result}`);
results.failed++;
}
} catch (e) {
console.log(`${name}: ${e.message}`);
results.failed++;
}
}
console.log('\n🧠 RuVector Intelligence Validation');
console.log('====================================\n');
// === 1. Data Files Exist ===
console.log('📁 Data Files:');
const requiredFiles = ['patterns.json', 'memory.json', 'trajectories.json', 'coordination-graph.json', 'swarm-state.json'];
for (const file of requiredFiles) {
test(`${file} exists`, () => {
return existsSync(join(DATA_DIR, file)) || `File not found`;
});
}
// === 2. Q-Table Validation ===
console.log('\n📊 Q-Table (patterns.json):');
const patterns = JSON.parse(readFileSync(join(DATA_DIR, 'patterns.json'), 'utf-8'));
const states = Object.keys(patterns);
test(`Has learned states (${states.length})`, () => {
return states.length >= 10 || `Only ${states.length} states`;
});
test('No overfitting (Q-values < 0.85)', () => {
const overfit = [];
for (const [state, actions] of Object.entries(patterns)) {
for (const [action, value] of Object.entries(actions)) {
if (action !== '_count' && typeof value === 'number' && value > 0.85) {
overfit.push(`${state}:${action}=${value.toFixed(3)}`);
}
}
}
return overfit.length === 0 || `Overfit: ${overfit.slice(0, 3).join(', ')}...`;
});
test('No negative Q-values below -0.6', () => {
const tooNegative = [];
for (const [state, actions] of Object.entries(patterns)) {
for (const [action, value] of Object.entries(actions)) {
if (action !== '_count' && typeof value === 'number' && value < -0.6) {
tooNegative.push(`${state}:${action}=${value.toFixed(3)}`);
}
}
}
return tooNegative.length === 0 || `Too negative: ${tooNegative.slice(0, 3).join(', ')}`;
});
test('Sample counts are tracked', () => {
const withCounts = states.filter(s => patterns[s]._count > 0);
return withCounts.length > 0 || 'No _count fields found';
});
// Q-value distribution check
const qValues = [];
for (const actions of Object.values(patterns)) {
for (const [k, v] of Object.entries(actions)) {
if (k !== '_count' && typeof v === 'number') qValues.push(v);
}
}
const avgQ = qValues.reduce((a, b) => a + b, 0) / qValues.length;
const minQ = Math.min(...qValues);
const maxQ = Math.max(...qValues);
test(`Q-value range is reasonable (${minQ.toFixed(2)} to ${maxQ.toFixed(2)})`, () => {
return maxQ <= 0.85 && minQ >= -0.6 || `Range too extreme`;
});
test(`Average Q-value not too high (avg=${avgQ.toFixed(3)})`, () => {
return avgQ < 0.7 || 'warn';
});
// === 3. Vector Memory Validation ===
console.log('\n🧠 Vector Memory (memory.json):');
const memory = JSON.parse(readFileSync(join(DATA_DIR, 'memory.json'), 'utf-8'));
test(`Has memories (${memory.length})`, () => {
return memory.length > 100 || `Only ${memory.length} memories`;
});
test('Memories have embeddings', () => {
const withEmbeddings = memory.filter(m => m.embedding && m.embedding.length === 128);
return withEmbeddings.length === memory.length || `${memory.length - withEmbeddings.length} missing embeddings`;
});
test('Embeddings are normalized', () => {
const sample = memory.slice(0, 10);
for (const m of sample) {
if (!m.embedding) continue;
const magnitude = Math.sqrt(m.embedding.reduce((sum, v) => sum + v * v, 0));
if (Math.abs(magnitude - 1.0) > 0.01) {
return `Magnitude ${magnitude.toFixed(3)} not ~1.0`;
}
}
return true;
});
test('Memories have types', () => {
const types = new Set(memory.map(m => m.type));
return types.size > 0 || 'No types found';
});
// === 4. Trajectories Validation ===
console.log('\n📈 Trajectories (trajectories.json):');
const trajectories = JSON.parse(readFileSync(join(DATA_DIR, 'trajectories.json'), 'utf-8'));
test(`Has trajectories (${trajectories.length})`, () => {
return trajectories.length > 100 || `Only ${trajectories.length} trajectories`;
});
test('Trajectories have required fields', () => {
const required = ['state', 'action', 'reward'];
const missing = trajectories.slice(0, 50).filter(t => !required.every(f => t[f] !== undefined));
return missing.length === 0 || `${missing.length} missing fields`;
});
const rewardDistribution = { positive: 0, negative: 0, neutral: 0 };
for (const t of trajectories) {
if (t.reward > 0) rewardDistribution.positive++;
else if (t.reward < 0) rewardDistribution.negative++;
else rewardDistribution.neutral++;
}
test(`Reward distribution is realistic`, () => {
const negativeRatio = rewardDistribution.negative / trajectories.length;
// Expect some failures but not too many (real systems have ~10-30% failures)
return negativeRatio < 0.5 || `${(negativeRatio * 100).toFixed(0)}% negative rewards seems high`;
});
// === 5. Swarm Graph Validation ===
console.log('\n🔗 Swarm Graph (coordination-graph.json):');
const graph = JSON.parse(readFileSync(join(DATA_DIR, 'coordination-graph.json'), 'utf-8'));
test(`Has agent nodes (${Object.keys(graph.nodes || {}).length})`, () => {
return Object.keys(graph.nodes || {}).length >= 3 || 'Too few agents';
});
test(`Has coordination edges (${Object.keys(graph.edges || {}).length})`, () => {
return Object.keys(graph.edges || {}).length >= 5 || 'Too few edges';
});
test('Agents have capabilities', () => {
const withCaps = Object.values(graph.nodes || {}).filter(n => n.capabilities?.length > 0);
return withCaps.length > 0 || 'No capabilities defined';
});
test('Graph is connected', () => {
const nodes = Object.keys(graph.nodes || {});
const edges = Object.keys(graph.edges || {});
if (nodes.length <= 1) return true;
// Simple connectivity check
const connected = new Set();
connected.add(nodes[0]);
let changed = true;
while (changed) {
changed = false;
for (const edge of edges) {
const [a, b] = edge.split(':');
if (connected.has(a) && !connected.has(b)) {
connected.add(b);
changed = true;
}
if (connected.has(b) && !connected.has(a)) {
connected.add(a);
changed = true;
}
}
}
return connected.size === nodes.length || `Only ${connected.size}/${nodes.length} nodes connected`;
});
// === 6. Swarm State Validation ===
console.log('\n📋 Swarm State (swarm-state.json):');
const swarmState = JSON.parse(readFileSync(join(DATA_DIR, 'swarm-state.json'), 'utf-8'));
test('Pretrained flag is set', () => {
return swarmState.pretrained === true || 'Not marked as pretrained';
});
test('Has pretraining timestamp', () => {
return swarmState.pretrainedAt ? true : 'No timestamp';
});
test('Has stats', () => {
return swarmState.stats && swarmState.stats.commands > 0 || 'No stats';
});
// === Summary ===
console.log('\n====================================');
console.log(`📊 Results: ${results.passed} passed, ${results.failed} failed, ${results.warnings} warnings`);
if (results.failed > 0) {
console.log('\n❌ Validation FAILED - issues found');
process.exit(1);
} else if (results.warnings > 0) {
console.log('\n⚠ Validation PASSED with warnings');
process.exit(0);
} else {
console.log('\n✅ Validation PASSED - system is healthy');
process.exit(0);
}