Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,682 @@
//! Attention Pattern Learning Framework
//!
//! Four complementary attention mechanisms for intelligent code assistance:
//!
//! | Attention Type | Question Answered | Application |
//! |---------------|-------------------|-------------|
//! | **Neural** | What words matter? | Token/semantic relevance |
//! | **DAG** | What steps matter? | Execution order, dependencies |
//! | **Graph** | What relationships matter? | Code structure, call graphs |
//! | **State Space** | What history still matters? | Context persistence |
use std::collections::HashMap;
// ============================================================================
// NEURAL ATTENTION - "What words matter?"
// ============================================================================
/// Neural attention focuses on token-level and semantic relevance.
/// Used for: Code completion, error messages, documentation search.
#[derive(Debug, Clone)]
pub struct NeuralAttention {
/// Attention weights per token position
weights: Vec<f32>,
/// Token importance scores
token_scores: HashMap<String, f32>,
/// Semantic embeddings dimension
dim: usize,
}
impl NeuralAttention {
pub fn new(dim: usize) -> Self {
Self {
weights: Vec::new(),
token_scores: HashMap::new(),
dim,
}
}
/// Compute attention weights for tokens
/// Q: Query (what we're looking for)
/// K: Keys (what we're comparing against)
/// V: Values (what we extract)
pub fn attend(&mut self, query: &[f32], keys: &[Vec<f32>], values: &[String]) -> Vec<(String, f32)> {
if keys.is_empty() || keys.len() != values.len() {
return Vec::new();
}
// Scaled dot-product attention
let scale = (self.dim as f32).sqrt();
self.weights = keys.iter().map(|k| {
let dot: f32 = query.iter().zip(k.iter()).map(|(q, k)| q * k).sum();
dot / scale
}).collect();
// Softmax normalization
let max_weight = self.weights.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let exp_weights: Vec<f32> = self.weights.iter().map(|w| (w - max_weight).exp()).collect();
let sum: f32 = exp_weights.iter().sum();
self.weights = exp_weights.iter().map(|w| w / sum).collect();
// Return weighted values
values.iter()
.zip(self.weights.iter())
.map(|(v, w)| (v.clone(), *w))
.collect()
}
/// Learn which tokens are important from successful completions
pub fn learn_token_importance(&mut self, token: &str, success: bool) {
let score = self.token_scores.entry(token.to_string()).or_insert(0.5);
let reward = if success { 1.0 } else { 0.0 };
*score = *score + 0.1 * (reward - *score); // Q-learning update
}
/// Get importance score for a token
pub fn token_importance(&self, token: &str) -> f32 {
*self.token_scores.get(token).unwrap_or(&0.5)
}
}
// ============================================================================
// DAG ATTENTION - "What steps matter?"
// ============================================================================
/// DAG (Directed Acyclic Graph) attention for execution order and dependencies.
/// Used for: Build systems, test ordering, refactoring sequences.
#[derive(Debug, Clone)]
pub struct DagAttention {
/// Nodes in the DAG (tasks/steps)
nodes: Vec<DagNode>,
/// Edges (dependencies)
edges: Vec<(usize, usize, f32)>, // (from, to, weight)
/// Topological order cache
topo_order: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct DagNode {
pub id: String,
pub step_type: StepType,
pub importance: f32,
pub completed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepType {
/// Configuration/setup step
Config,
/// Source code modification
Source,
/// Test execution
Test,
/// Build/compile step
Build,
/// Deployment step
Deploy,
}
impl DagAttention {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
topo_order: Vec::new(),
}
}
/// Add a step to the DAG
pub fn add_step(&mut self, id: &str, step_type: StepType) -> usize {
let idx = self.nodes.len();
self.nodes.push(DagNode {
id: id.to_string(),
step_type,
importance: 0.5,
completed: false,
});
self.invalidate_topo();
idx
}
/// Add a dependency edge
pub fn add_dependency(&mut self, from: usize, to: usize, weight: f32) {
self.edges.push((from, to, weight));
self.invalidate_topo();
}
/// Invalidate topological order cache
fn invalidate_topo(&mut self) {
self.topo_order.clear();
}
/// Compute topological order (what order to execute steps)
pub fn compute_order(&mut self) -> Vec<&DagNode> {
if self.topo_order.is_empty() {
self.topo_order = self.kahn_sort();
}
self.topo_order.iter().map(|&i| &self.nodes[i]).collect()
}
/// Kahn's algorithm for topological sort
fn kahn_sort(&self) -> Vec<usize> {
let n = self.nodes.len();
let mut in_degree = vec![0usize; n];
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for &(from, to, _) in &self.edges {
adj[from].push(to);
in_degree[to] += 1;
}
let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
let mut result = Vec::new();
while let Some(node) = queue.pop() {
result.push(node);
for &next in &adj[node] {
in_degree[next] -= 1;
if in_degree[next] == 0 {
queue.push(next);
}
}
}
result
}
/// Get critical path (most important sequence of steps)
pub fn critical_path(&self) -> Vec<&DagNode> {
// Find path with highest total importance
let order = self.kahn_sort();
let mut max_path = Vec::new();
let mut max_importance = 0.0f32;
// Simple greedy: follow highest importance edges
if let Some(&start) = order.first() {
let mut path = vec![start];
let mut current = start;
let mut importance = self.nodes[start].importance;
while let Some(&(_, to, weight)) = self.edges.iter()
.filter(|(from, _, _)| *from == current)
.max_by(|a, b| a.2.partial_cmp(&b.2).unwrap())
{
path.push(to);
importance += self.nodes[to].importance * weight;
current = to;
}
if importance > max_importance {
max_importance = importance;
max_path = path;
}
}
max_path.iter().map(|&i| &self.nodes[i]).collect()
}
/// Learn step importance from execution outcomes
pub fn learn_step_importance(&mut self, step_id: &str, success: bool) {
if let Some(node) = self.nodes.iter_mut().find(|n| n.id == step_id) {
let reward = if success { 1.0 } else { 0.0 };
node.importance = node.importance + 0.1 * (reward - node.importance);
}
}
}
impl Default for DagAttention {
fn default() -> Self {
Self::new()
}
}
// ============================================================================
// GRAPH ATTENTION - "What relationships matter?"
// ============================================================================
/// Graph attention for code structure and relationships.
/// Used for: Call graphs, module dependencies, refactoring impact analysis.
#[derive(Debug, Clone)]
pub struct GraphAttention {
/// Nodes (functions, modules, files)
nodes: HashMap<String, GraphNode>,
/// Edges with attention weights
edges: Vec<GraphEdge>,
/// Multi-head attention heads
num_heads: usize,
}
#[derive(Debug, Clone)]
pub struct GraphNode {
pub id: String,
pub node_type: NodeType,
pub features: Vec<f32>,
pub attention_score: f32,
}
#[derive(Debug, Clone)]
pub struct GraphEdge {
pub source: String,
pub target: String,
pub edge_type: EdgeType,
pub weight: f32,
pub attention_weights: Vec<f32>, // Per head
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeType {
Function,
Module,
File,
Crate,
Test,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeType {
Calls,
Imports,
DependsOn,
Tests,
Contains,
}
impl GraphAttention {
pub fn new(num_heads: usize) -> Self {
Self {
nodes: HashMap::new(),
edges: Vec::new(),
num_heads,
}
}
/// Add a node to the graph
pub fn add_node(&mut self, id: &str, node_type: NodeType, features: Vec<f32>) {
self.nodes.insert(id.to_string(), GraphNode {
id: id.to_string(),
node_type,
features,
attention_score: 0.0,
});
}
/// Add an edge with relationship type
pub fn add_edge(&mut self, source: &str, target: &str, edge_type: EdgeType) {
self.edges.push(GraphEdge {
source: source.to_string(),
target: target.to_string(),
edge_type,
weight: 1.0,
attention_weights: vec![1.0 / self.num_heads as f32; self.num_heads],
});
}
/// Compute graph attention (simplified GAT-style)
pub fn compute_attention(&mut self, focus_node: &str) {
// Reset attention scores
for node in self.nodes.values_mut() {
node.attention_score = 0.0;
}
// Aggregate attention from edges
for edge in &self.edges {
if edge.source == focus_node || edge.target == focus_node {
let other = if edge.source == focus_node { &edge.target } else { &edge.source };
if let Some(node) = self.nodes.get_mut(other) {
// Sum multi-head attention
let attention: f32 = edge.attention_weights.iter().sum();
node.attention_score += attention * edge.weight;
}
}
}
// Normalize
let max_score = self.nodes.values()
.map(|n| n.attention_score)
.fold(0.0f32, f32::max);
if max_score > 0.0 {
for node in self.nodes.values_mut() {
node.attention_score /= max_score;
}
}
}
/// Get most important neighbors of a node
pub fn important_neighbors(&self, node_id: &str, top_k: usize) -> Vec<(&GraphNode, f32)> {
let mut neighbors: Vec<_> = self.edges.iter()
.filter(|e| e.source == node_id || e.target == node_id)
.filter_map(|e| {
let other = if e.source == node_id { &e.target } else { &e.source };
self.nodes.get(other).map(|n| (n, e.weight))
})
.collect();
neighbors.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
neighbors.truncate(top_k);
neighbors
}
/// Learn relationship importance from interaction patterns
pub fn learn_edge_importance(&mut self, source: &str, target: &str, success: bool) {
if let Some(edge) = self.edges.iter_mut()
.find(|e| e.source == source && e.target == target)
{
let reward = if success { 1.0 } else { 0.0 };
edge.weight = edge.weight + 0.1 * (reward - edge.weight);
}
}
}
// ============================================================================
// STATE SPACE ATTENTION - "What history still matters?"
// ============================================================================
/// State space model for context persistence and history relevance.
/// Used for: Session memory, conversation context, learning trajectories.
#[derive(Debug, Clone)]
pub struct StateSpaceAttention {
/// Hidden state dimension
state_dim: usize,
/// Current hidden state
hidden_state: Vec<f32>,
/// State transition matrix (learned)
transition: Vec<Vec<f32>>,
/// Input projection
input_proj: Vec<Vec<f32>>,
/// Output projection
output_proj: Vec<Vec<f32>>,
/// History buffer with decay
history: Vec<HistoryEntry>,
/// Maximum history length
max_history: usize,
/// Decay factor for old history
decay: f32,
}
#[derive(Debug, Clone)]
pub struct HistoryEntry {
pub content: String,
pub state: Vec<f32>,
pub relevance: f32,
pub timestamp: u64,
}
impl StateSpaceAttention {
pub fn new(state_dim: usize, max_history: usize) -> Self {
// Initialize with identity-like transition
let mut transition = vec![vec![0.0; state_dim]; state_dim];
for i in 0..state_dim {
transition[i][i] = 0.9; // Slight decay
}
Self {
state_dim,
hidden_state: vec![0.0; state_dim],
transition,
input_proj: vec![vec![0.1; state_dim]; state_dim],
output_proj: vec![vec![0.1; state_dim]; state_dim],
history: Vec::new(),
max_history,
decay: 0.95,
}
}
/// Update state with new input
pub fn update(&mut self, input: &[f32], content: &str) {
// State transition: h_t = A * h_{t-1} + B * x_t
let mut new_state = vec![0.0; self.state_dim];
// Apply transition matrix
for i in 0..self.state_dim {
for j in 0..self.state_dim {
new_state[i] += self.transition[i][j] * self.hidden_state[j];
}
}
// Add input contribution
let input_len = input.len().min(self.state_dim);
for i in 0..input_len {
for j in 0..self.state_dim {
new_state[j] += self.input_proj[j][i % self.state_dim] * input[i];
}
}
// Normalize
let norm: f32 = new_state.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for x in &mut new_state {
*x /= norm;
}
}
// Store in history
self.history.push(HistoryEntry {
content: content.to_string(),
state: self.hidden_state.clone(),
relevance: 1.0,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
});
// Trim history
while self.history.len() > self.max_history {
self.history.remove(0);
}
// Apply decay to old entries
for entry in &mut self.history {
entry.relevance *= self.decay;
}
self.hidden_state = new_state;
}
/// Query what history is still relevant
pub fn relevant_history(&self, query: &[f32], top_k: usize) -> Vec<&HistoryEntry> {
let mut scored: Vec<_> = self.history.iter()
.map(|entry| {
// Cosine similarity between query and historical state
let dot: f32 = query.iter()
.zip(entry.state.iter())
.map(|(q, s)| q * s)
.sum();
let query_norm: f32 = query.iter().map(|x| x * x).sum::<f32>().sqrt();
let state_norm: f32 = entry.state.iter().map(|x| x * x).sum::<f32>().sqrt();
let similarity = if query_norm > 0.0 && state_norm > 0.0 {
dot / (query_norm * state_norm)
} else {
0.0
};
(entry, similarity * entry.relevance)
})
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
scored.truncate(top_k);
scored.into_iter().map(|(e, _)| e).collect()
}
/// Get current state
pub fn current_state(&self) -> &[f32] {
&self.hidden_state
}
/// Learn from successful context usage
pub fn reinforce_history(&mut self, content: &str, success: bool) {
if let Some(entry) = self.history.iter_mut().find(|e| e.content == content) {
let reward = if success { 1.5 } else { 0.5 };
entry.relevance = (entry.relevance * reward).min(1.0);
}
}
}
// ============================================================================
// UNIFIED ATTENTION ORCHESTRATOR
// ============================================================================
/// Combines all four attention mechanisms for comprehensive code understanding.
pub struct AttentionOrchestrator {
pub neural: NeuralAttention,
pub dag: DagAttention,
pub graph: GraphAttention,
pub state_space: StateSpaceAttention,
}
impl AttentionOrchestrator {
pub fn new(embedding_dim: usize, state_dim: usize, max_history: usize) -> Self {
Self {
neural: NeuralAttention::new(embedding_dim),
dag: DagAttention::new(),
graph: GraphAttention::new(4), // 4 attention heads
state_space: StateSpaceAttention::new(state_dim, max_history),
}
}
/// Answer all four attention questions for a given context
pub fn analyze(&mut self, query: &str, file: &str) -> AttentionAnalysis {
AttentionAnalysis {
words_that_matter: self.analyze_words(query),
steps_that_matter: self.analyze_steps(file),
relationships_that_matter: self.analyze_relationships(file),
history_that_matters: self.analyze_history(query),
}
}
fn analyze_words(&self, query: &str) -> Vec<(String, f32)> {
query.split_whitespace()
.map(|word| (word.to_string(), self.neural.token_importance(word)))
.collect()
}
fn analyze_steps(&self, file: &str) -> Vec<String> {
self.dag.compute_order()
.iter()
.filter(|node| !node.completed)
.take(5)
.map(|node| node.id.clone())
.collect()
}
fn analyze_relationships(&self, file: &str) -> Vec<String> {
self.graph.important_neighbors(file, 5)
.iter()
.map(|(node, _)| node.id.clone())
.collect()
}
fn analyze_history(&self, query: &str) -> Vec<String> {
// Simple embedding from query
let query_embedding: Vec<f32> = query.chars()
.take(64)
.map(|c| (c as u8 as f32) / 255.0)
.collect();
self.state_space.relevant_history(&query_embedding, 5)
.iter()
.map(|entry| entry.content.clone())
.collect()
}
}
/// Result of attention analysis
#[derive(Debug, Clone)]
pub struct AttentionAnalysis {
/// Neural attention: What words matter?
pub words_that_matter: Vec<(String, f32)>,
/// DAG attention: What steps matter?
pub steps_that_matter: Vec<String>,
/// Graph attention: What relationships matter?
pub relationships_that_matter: Vec<String>,
/// State space: What history still matters?
pub history_that_matters: Vec<String>,
}
impl std::fmt::Display for AttentionAnalysis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "🧠 Attention Analysis")?;
writeln!(f, "━━━━━━━━━━━━━━━━━━━━")?;
writeln!(f, "\n📝 Neural Attention (What words matter?):")?;
for (word, score) in &self.words_that_matter {
writeln!(f, " • {} ({:.2})", word, score)?;
}
writeln!(f, "\n📊 DAG Attention (What steps matter?):")?;
for step in &self.steps_that_matter {
writeln!(f, " → {}", step)?;
}
writeln!(f, "\n🔗 Graph Attention (What relationships matter?):")?;
for rel in &self.relationships_that_matter {
writeln!(f, " ↔ {}", rel)?;
}
writeln!(f, "\n📚 State Space (What history still matters?):")?;
for hist in &self.history_that_matters {
writeln!(f, " ◷ {}", hist)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_neural_attention() {
let mut attn = NeuralAttention::new(64);
attn.learn_token_importance("fn", true);
attn.learn_token_importance("fn", true);
assert!(attn.token_importance("fn") > 0.5);
}
#[test]
fn test_dag_attention() {
let mut dag = DagAttention::new();
let config = dag.add_step("config", StepType::Config);
let build = dag.add_step("build", StepType::Build);
let test = dag.add_step("test", StepType::Test);
dag.add_dependency(config, build, 1.0);
dag.add_dependency(build, test, 1.0);
let order = dag.compute_order();
assert_eq!(order.len(), 3);
assert_eq!(order[0].id, "config");
}
#[test]
fn test_graph_attention() {
let mut graph = GraphAttention::new(4);
graph.add_node("main.rs", NodeType::File, vec![1.0; 64]);
graph.add_node("lib.rs", NodeType::File, vec![0.5; 64]);
graph.add_edge("main.rs", "lib.rs", EdgeType::Imports);
graph.compute_attention("main.rs");
let neighbors = graph.important_neighbors("main.rs", 5);
assert!(!neighbors.is_empty());
}
#[test]
fn test_state_space_attention() {
let mut ssm = StateSpaceAttention::new(32, 100);
ssm.update(&[0.5; 32], "First context");
ssm.update(&[0.7; 32], "Second context");
let relevant = ssm.relevant_history(&[0.6; 32], 2);
assert!(!relevant.is_empty());
}
#[test]
fn test_attention_orchestrator() {
let mut orch = AttentionOrchestrator::new(64, 32, 100);
let analysis = orch.analyze("implement error handling", "src/lib.rs");
assert!(!analysis.words_that_matter.is_empty());
println!("{}", analysis);
}
}

View File

@@ -0,0 +1,28 @@
# Learning Patterns Configuration
# This file teaches the hooks system about YAML file handling
learning:
enabled: true
rate: 0.1
patterns:
- type: yaml_config
confidence_threshold: 0.7
- type: rust_source
confidence_threshold: 0.8
- type: typescript
confidence_threshold: 0.75
agent_routing:
default_agent: coder
file_mappings:
"*.rs": rust-developer
"*.ts": typescript-developer
"*.yaml": config-specialist
"*.json": data-analyst
"*.sh": devops-engineer
"*.md": documentation-writer
metrics:
track_q_values: true
track_trajectories: true
export_interval: 300

View File

@@ -0,0 +1,53 @@
{
"schema_version": "1.0.0",
"description": "Pre-defined learning patterns for agent routing",
"patterns": {
"rust_development": {
"file_extensions": [".rs", ".toml"],
"suggested_agents": ["rust-developer", "coder"],
"learned_sequences": [
["Cargo.toml", "src/lib.rs", "src/main.rs"],
["lib.rs", "mod.rs", "types.rs"]
]
},
"typescript_development": {
"file_extensions": [".ts", ".tsx", ".js"],
"suggested_agents": ["typescript-developer", "frontend-dev"],
"learned_sequences": [
["package.json", "tsconfig.json", "src/index.ts"],
["types.ts", "utils.ts", "index.ts"]
]
},
"documentation": {
"file_extensions": [".md", ".mdx", ".rst"],
"suggested_agents": ["documentation-writer", "researcher"],
"learned_sequences": [
["README.md", "CONTRIBUTING.md", "docs/index.md"]
]
},
"devops": {
"file_extensions": [".yaml", ".yml", ".sh", ".dockerfile"],
"suggested_agents": ["devops-engineer", "cicd-engineer"],
"learned_sequences": [
[".github/workflows/ci.yml", "Dockerfile", "docker-compose.yml"]
]
}
},
"error_recovery_patterns": {
"E0308": {
"description": "Rust type mismatch",
"suggested_fix": "Check type annotations and conversions",
"agent": "rust-developer"
},
"E0433": {
"description": "Rust unresolved import",
"suggested_fix": "Add missing use statement or dependency",
"agent": "rust-developer"
},
"TS2339": {
"description": "TypeScript property does not exist",
"suggested_fix": "Add property to type definition",
"agent": "typescript-developer"
}
}
}

View File

@@ -0,0 +1,69 @@
#!/bin/bash
# Learning Scenarios Setup Script
# This teaches the hooks system about shell script handling
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
echo "🧠 RuVector Learning Scenarios Setup"
echo "====================================="
# Function to initialize learning patterns
init_patterns() {
echo "📊 Initializing learning patterns..."
# Check if intelligence file exists
if [[ -f "$PROJECT_ROOT/.ruvector/intelligence.json" ]]; then
local pattern_count=$(jq '.patterns | length' "$PROJECT_ROOT/.ruvector/intelligence.json" 2>/dev/null || echo "0")
echo " Found $pattern_count existing patterns"
else
echo " No existing patterns, starting fresh"
fi
}
# Function to record a learning event
record_event() {
local event_type="$1"
local file_path="$2"
local outcome="${3:-success}"
echo "📝 Recording: $event_type on $file_path ($outcome)"
# Use ruvector-cli if available
if command -v ruvector-cli &>/dev/null; then
ruvector-cli hooks remember "$event_type: $file_path" -t "$event_type" 2>/dev/null || true
fi
}
# Function to simulate diverse file operations
simulate_diversity() {
echo "🔄 Simulating diverse file operations..."
local file_types=(
"rs:rust-developer"
"ts:typescript-developer"
"yaml:config-specialist"
"json:data-analyst"
"sh:devops-engineer"
"md:documentation-writer"
)
for entry in "${file_types[@]}"; do
IFS=':' read -r ext agent <<< "$entry"
echo " .$ext -> $agent"
done
}
# Main execution
main() {
init_patterns
simulate_diversity
echo ""
echo "✅ Learning scenarios initialized"
echo " Run 'ruvector hooks stats' to see current patterns"
}
main "$@"

View File

@@ -0,0 +1,76 @@
/**
* Learning Pattern Types for RuVector Intelligence
* This teaches the hooks system about TypeScript file handling
*/
export interface LearningPattern {
state: string;
action: string;
qValue: number;
visits: number;
lastUpdate: number;
}
export interface VectorMemory {
id: string;
memoryType: 'edit' | 'file_access' | 'command' | 'search_pattern' | 'agent_spawn';
content: string;
embedding: number[];
timestamp: number;
}
export interface Trajectory {
id: string;
state: string;
action: string;
outcome: 'completed' | 'failed' | 'partial';
reward: number;
timestamp: number;
}
export interface AgentRouting {
filePattern: RegExp;
agentType: string;
confidence: number;
}
export type CognitivePattern =
| 'convergent'
| 'divergent'
| 'lateral'
| 'systems'
| 'critical'
| 'adaptive';
export class IntelligenceLayer {
private patterns: Map<string, LearningPattern> = new Map();
private memories: VectorMemory[] = [];
private trajectories: Trajectory[] = [];
recordPattern(state: string, action: string, reward: number): void {
const key = `${state}|${action}`;
const existing = this.patterns.get(key);
if (existing) {
// Q-learning update
existing.qValue = existing.qValue + 0.1 * (reward - existing.qValue);
existing.visits++;
existing.lastUpdate = Date.now();
} else {
this.patterns.set(key, {
state,
action,
qValue: reward * 0.1,
visits: 1,
lastUpdate: Date.now()
});
}
}
suggestAgent(filePath: string): string {
if (filePath.endsWith('.rs')) return 'rust-developer';
if (filePath.endsWith('.ts')) return 'typescript-developer';
if (filePath.endsWith('.yaml')) return 'config-specialist';
return 'coder';
}
}

View File

@@ -0,0 +1,139 @@
//! Error Pattern Learning Module
//!
//! This module intentionally includes patterns that might cause errors
//! to teach the self-learning system about error recovery strategies.
use std::collections::HashMap;
/// Error pattern types for learning
#[derive(Debug, Clone)]
pub enum ErrorPattern {
/// Type mismatch errors (E0308)
TypeMismatch { expected: String, found: String },
/// Unresolved import errors (E0433)
UnresolvedImport { path: String },
/// Borrow checker errors (E0502)
BorrowConflict { variable: String },
/// Missing trait implementation (E0277)
MissingTrait { trait_name: String, type_name: String },
}
/// Recovery strategy for each error type
#[derive(Debug, Clone)]
pub struct RecoveryStrategy {
pub error_code: String,
pub description: String,
pub fix_steps: Vec<String>,
pub suggested_agent: String,
}
impl RecoveryStrategy {
pub fn for_error(pattern: &ErrorPattern) -> Self {
match pattern {
ErrorPattern::TypeMismatch { expected, found } => Self {
error_code: "E0308".into(),
description: format!("Expected {}, found {}", expected, found),
fix_steps: vec![
"Check variable type annotations".into(),
"Add explicit type conversion".into(),
"Use .into() or .as_ref() as needed".into(),
],
suggested_agent: "rust-developer".into(),
},
ErrorPattern::UnresolvedImport { path } => Self {
error_code: "E0433".into(),
description: format!("Failed to resolve: {}", path),
fix_steps: vec![
"Add missing dependency to Cargo.toml".into(),
"Check module path spelling".into(),
"Ensure pub visibility".into(),
],
suggested_agent: "rust-developer".into(),
},
ErrorPattern::BorrowConflict { variable } => Self {
error_code: "E0502".into(),
description: format!("Borrow conflict on {}", variable),
fix_steps: vec![
"Clone the value if ownership is needed".into(),
"Use RefCell for interior mutability".into(),
"Restructure code to limit borrow scope".into(),
],
suggested_agent: "rust-developer".into(),
},
ErrorPattern::MissingTrait { trait_name, type_name } => Self {
error_code: "E0277".into(),
description: format!("{} not implemented for {}", trait_name, type_name),
fix_steps: vec![
"Derive the trait if possible".into(),
"Implement the trait manually".into(),
"Use a wrapper type that implements it".into(),
],
suggested_agent: "rust-developer".into(),
},
}
}
}
/// Learning tracker for error patterns
pub struct ErrorLearningTracker {
patterns: HashMap<String, u32>,
recoveries: HashMap<String, Vec<RecoveryStrategy>>,
}
impl ErrorLearningTracker {
pub fn new() -> Self {
Self {
patterns: HashMap::new(),
recoveries: HashMap::new(),
}
}
/// Record an error occurrence for learning
pub fn record_error(&mut self, error_code: &str) {
*self.patterns.entry(error_code.to_string()).or_insert(0) += 1;
}
/// Record a successful recovery for learning
pub fn record_recovery(&mut self, error_code: &str, strategy: RecoveryStrategy) {
self.recoveries
.entry(error_code.to_string())
.or_default()
.push(strategy);
}
/// Get the most successful recovery strategy for an error
pub fn best_recovery(&self, error_code: &str) -> Option<&RecoveryStrategy> {
self.recoveries.get(error_code).and_then(|v| v.last())
}
}
impl Default for ErrorLearningTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_recovery_strategy_for_type_mismatch() {
let pattern = ErrorPattern::TypeMismatch {
expected: "u32".into(),
found: "i32".into(),
};
let strategy = RecoveryStrategy::for_error(&pattern);
assert_eq!(strategy.error_code, "E0308");
assert_eq!(strategy.suggested_agent, "rust-developer");
}
#[test]
fn test_error_learning_tracker() {
let mut tracker = ErrorLearningTracker::new();
tracker.record_error("E0308");
tracker.record_error("E0308");
assert_eq!(tracker.patterns.get("E0308"), Some(&2));
}
}

View File

@@ -0,0 +1,3 @@
//! Error Recovery Learning Submodule
pub mod error_patterns;

View File

@@ -0,0 +1,219 @@
//! File Sequence Learning Module
//!
//! Tracks the order in which files are edited to learn optimal
//! multi-file refactoring patterns.
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
/// Represents a file edit event
#[derive(Debug, Clone)]
pub struct FileEdit {
pub file_path: String,
pub file_type: String,
pub crate_name: Option<String>,
pub timestamp: u64,
pub success: bool,
}
/// A sequence of file edits that form a pattern
#[derive(Debug, Clone)]
pub struct EditSequence {
pub id: String,
pub files: Vec<String>,
pub pattern_type: SequencePattern,
pub occurrences: u32,
pub avg_success_rate: f64,
}
/// Types of editing patterns we can learn
#[derive(Debug, Clone, PartialEq)]
pub enum SequencePattern {
/// Cargo.toml -> lib.rs -> specific modules
RustCrateSetup,
/// Types first, then implementation, then tests
TypesFirstDevelopment,
/// Tests first, then implementation (TDD)
TestDrivenDevelopment,
/// Config files, then source, then docs
FullStackChange,
/// Unknown pattern being learned
Learning,
}
/// Tracks file sequences for learning
pub struct SequenceTracker {
current_sequence: Vec<FileEdit>,
learned_sequences: HashMap<String, EditSequence>,
pattern_confidence: HashMap<SequencePattern, f64>,
}
impl SequenceTracker {
pub fn new() -> Self {
Self {
current_sequence: Vec::new(),
learned_sequences: HashMap::new(),
pattern_confidence: HashMap::new(),
}
}
/// Record a file edit in the current sequence
pub fn record_edit(&mut self, file_path: &str, success: bool) {
let file_type = Self::detect_file_type(file_path);
let crate_name = Self::extract_crate_name(file_path);
let edit = FileEdit {
file_path: file_path.to_string(),
file_type,
crate_name,
timestamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
success,
};
self.current_sequence.push(edit);
// Check if we've completed a recognizable pattern
if let Some(pattern) = self.detect_pattern() {
self.learn_pattern(pattern);
}
}
/// Detect file type from extension
fn detect_file_type(path: &str) -> String {
if path.ends_with(".rs") { "rust".into() }
else if path.ends_with(".ts") { "typescript".into() }
else if path.ends_with(".toml") { "toml".into() }
else if path.ends_with(".json") { "json".into() }
else if path.ends_with(".yaml") || path.ends_with(".yml") { "yaml".into() }
else if path.ends_with(".md") { "markdown".into() }
else if path.ends_with(".sh") { "shell".into() }
else { "unknown".into() }
}
/// Extract crate name from path
fn extract_crate_name(path: &str) -> Option<String> {
// Look for patterns like crates/ruvector-*/
if path.contains("crates/") {
path.split("crates/")
.nth(1)
.and_then(|s| s.split('/').next())
.map(|s| s.to_string())
} else if path.contains("ruvector-") {
path.split("ruvector-")
.nth(1)
.and_then(|s| s.split('/').next())
.map(|s| format!("ruvector-{}", s))
} else {
None
}
}
/// Detect if current sequence matches a known pattern
fn detect_pattern(&self) -> Option<SequencePattern> {
let files: Vec<&str> = self.current_sequence
.iter()
.map(|e| e.file_path.as_str())
.collect();
if files.len() < 2 {
return None;
}
// Detect Rust crate setup pattern
if files.iter().any(|f| f.ends_with("Cargo.toml"))
&& files.iter().any(|f| f.ends_with("lib.rs")) {
return Some(SequencePattern::RustCrateSetup);
}
// Detect TDD pattern
if files.iter().any(|f| f.contains("test"))
&& files.iter().position(|f| f.contains("test"))
< files.iter().position(|f| f.ends_with("lib.rs") || f.ends_with("mod.rs")) {
return Some(SequencePattern::TestDrivenDevelopment);
}
// Detect types-first pattern
if files.iter().any(|f| f.contains("types"))
&& files.iter().position(|f| f.contains("types")).unwrap_or(999) < 2 {
return Some(SequencePattern::TypesFirstDevelopment);
}
None
}
/// Learn from a detected pattern
fn learn_pattern(&mut self, pattern: SequencePattern) {
let confidence = self.pattern_confidence.entry(pattern.clone()).or_insert(0.5);
// Increase confidence if all edits in sequence were successful
let success_rate = self.current_sequence.iter()
.filter(|e| e.success)
.count() as f64 / self.current_sequence.len() as f64;
// Q-learning style update
*confidence = *confidence + 0.1 * (success_rate - *confidence);
// Clear sequence after learning
self.current_sequence.clear();
}
/// Suggest the next file to edit based on learned patterns
pub fn suggest_next_file(&self, current_file: &str) -> Option<String> {
let file_type = Self::detect_file_type(current_file);
match file_type.as_str() {
"toml" if current_file.contains("Cargo") => {
Some("src/lib.rs".into())
}
"rust" if current_file.contains("types") => {
Some("src/lib.rs".into())
}
"rust" if current_file.contains("lib.rs") => {
Some("src/tests.rs".into())
}
_ => None
}
}
/// Get learned patterns with their confidence scores
pub fn get_pattern_confidence(&self) -> &HashMap<SequencePattern, f64> {
&self.pattern_confidence
}
}
impl Default for SequenceTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_type_detection() {
assert_eq!(SequenceTracker::detect_file_type("src/lib.rs"), "rust");
assert_eq!(SequenceTracker::detect_file_type("config.yaml"), "yaml");
assert_eq!(SequenceTracker::detect_file_type("types.ts"), "typescript");
}
#[test]
fn test_crate_name_extraction() {
let name = SequenceTracker::extract_crate_name("crates/ruvector-core/src/lib.rs");
assert_eq!(name, Some("ruvector-core".into()));
}
#[test]
fn test_sequence_tracking() {
let mut tracker = SequenceTracker::new();
tracker.record_edit("Cargo.toml", true);
tracker.record_edit("src/lib.rs", true);
assert!(!tracker.current_sequence.is_empty() ||
tracker.pattern_confidence.contains_key(&SequencePattern::RustCrateSetup));
}
}

View File

@@ -0,0 +1,3 @@
//! File Sequence Learning Submodule
pub mod sequence_tracker;

View File

@@ -0,0 +1,532 @@
//! Enhanced MCP Tools for RuVector Learning Intelligence
//!
//! Provides MCP tool definitions that integrate with the self-learning
//! hooks system for intelligent code assistance.
use std::collections::HashMap;
/// MCP Tool definition for RuVector intelligence features
#[derive(Debug, Clone)]
pub struct McpToolDef {
pub name: String,
pub description: String,
pub input_schema: ToolInputSchema,
pub category: ToolCategory,
}
/// Tool input schema
#[derive(Debug, Clone)]
pub struct ToolInputSchema {
pub required: Vec<String>,
pub properties: HashMap<String, PropertyDef>,
}
/// Property definition for tool inputs
#[derive(Debug, Clone)]
pub struct PropertyDef {
pub prop_type: String,
pub description: String,
pub default: Option<String>,
pub enum_values: Option<Vec<String>>,
}
/// Tool categories for organization
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolCategory {
/// Vector database operations
VectorDb,
/// Learning and intelligence
Learning,
/// Memory and recall
Memory,
/// Swarm coordination
Swarm,
/// Telemetry and metrics
Telemetry,
/// Agent routing
AgentRouting,
}
impl ToolCategory {
pub fn as_str(&self) -> &'static str {
match self {
Self::VectorDb => "vector_db",
Self::Learning => "learning",
Self::Memory => "memory",
Self::Swarm => "swarm",
Self::Telemetry => "telemetry",
Self::AgentRouting => "agent_routing",
}
}
}
/// Get all RuVector MCP tools
pub fn get_ruvector_tools() -> Vec<McpToolDef> {
vec![
// === Learning Intelligence Tools ===
McpToolDef {
name: "ruvector_learn_pattern".into(),
description: "Record a Q-learning pattern for agent routing optimization".into(),
input_schema: ToolInputSchema {
required: vec!["state".into(), "action".into()],
properties: [
("state".into(), PropertyDef {
prop_type: "string".into(),
description: "State identifier (e.g., edit_rs_in_crate)".into(),
default: None,
enum_values: None,
}),
("action".into(), PropertyDef {
prop_type: "string".into(),
description: "Action taken (e.g., successful-edit, rust-developer)".into(),
default: None,
enum_values: None,
}),
("reward".into(), PropertyDef {
prop_type: "number".into(),
description: "Reward value (-1.0 to 1.0)".into(),
default: Some("1.0".into()),
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Learning,
},
McpToolDef {
name: "ruvector_suggest_agent".into(),
description: "Get recommended agent for a task based on learned patterns".into(),
input_schema: ToolInputSchema {
required: vec!["task".into()],
properties: [
("task".into(), PropertyDef {
prop_type: "string".into(),
description: "Task description".into(),
default: None,
enum_values: None,
}),
("file".into(), PropertyDef {
prop_type: "string".into(),
description: "File being worked on".into(),
default: None,
enum_values: None,
}),
("crate_name".into(), PropertyDef {
prop_type: "string".into(),
description: "Crate/module context".into(),
default: None,
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::AgentRouting,
},
McpToolDef {
name: "ruvector_record_error".into(),
description: "Record an error pattern for learning recovery strategies".into(),
input_schema: ToolInputSchema {
required: vec!["error_code".into(), "message".into()],
properties: [
("error_code".into(), PropertyDef {
prop_type: "string".into(),
description: "Error code (e.g., E0308, TS2322)".into(),
default: None,
enum_values: None,
}),
("message".into(), PropertyDef {
prop_type: "string".into(),
description: "Error message".into(),
default: None,
enum_values: None,
}),
("file".into(), PropertyDef {
prop_type: "string".into(),
description: "File with error".into(),
default: None,
enum_values: None,
}),
("fix_applied".into(), PropertyDef {
prop_type: "string".into(),
description: "Fix that resolved the error".into(),
default: None,
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Learning,
},
McpToolDef {
name: "ruvector_suggest_fix".into(),
description: "Get suggested fixes for an error code based on learned patterns".into(),
input_schema: ToolInputSchema {
required: vec!["error_code".into()],
properties: [
("error_code".into(), PropertyDef {
prop_type: "string".into(),
description: "Error code to get fixes for".into(),
default: None,
enum_values: None,
}),
("context".into(), PropertyDef {
prop_type: "string".into(),
description: "Additional context (file type, crate)".into(),
default: None,
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Learning,
},
// === Memory Tools ===
McpToolDef {
name: "ruvector_remember".into(),
description: "Store content in semantic vector memory for later recall".into(),
input_schema: ToolInputSchema {
required: vec!["content".into(), "memory_type".into()],
properties: [
("content".into(), PropertyDef {
prop_type: "string".into(),
description: "Content to remember".into(),
default: None,
enum_values: None,
}),
("memory_type".into(), PropertyDef {
prop_type: "string".into(),
description: "Type of memory".into(),
default: None,
enum_values: Some(vec![
"edit".into(), "command".into(), "decision".into(),
"pattern".into(), "error".into(), "agent_spawn".into(),
]),
}),
("metadata".into(), PropertyDef {
prop_type: "object".into(),
description: "Additional metadata".into(),
default: None,
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Memory,
},
McpToolDef {
name: "ruvector_recall".into(),
description: "Search semantic memory for relevant information".into(),
input_schema: ToolInputSchema {
required: vec!["query".into()],
properties: [
("query".into(), PropertyDef {
prop_type: "string".into(),
description: "Search query".into(),
default: None,
enum_values: None,
}),
("top_k".into(), PropertyDef {
prop_type: "integer".into(),
description: "Number of results to return".into(),
default: Some("5".into()),
enum_values: None,
}),
("memory_type".into(), PropertyDef {
prop_type: "string".into(),
description: "Filter by memory type".into(),
default: None,
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Memory,
},
// === Swarm Coordination Tools ===
McpToolDef {
name: "ruvector_swarm_register".into(),
description: "Register an agent in the coordination swarm".into(),
input_schema: ToolInputSchema {
required: vec!["agent_id".into(), "agent_type".into()],
properties: [
("agent_id".into(), PropertyDef {
prop_type: "string".into(),
description: "Unique agent identifier".into(),
default: None,
enum_values: None,
}),
("agent_type".into(), PropertyDef {
prop_type: "string".into(),
description: "Type of agent".into(),
default: None,
enum_values: Some(vec![
"researcher".into(), "coder".into(), "tester".into(),
"reviewer".into(), "planner".into(), "coordinator".into(),
]),
}),
("capabilities".into(), PropertyDef {
prop_type: "array".into(),
description: "Agent capabilities".into(),
default: None,
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Swarm,
},
McpToolDef {
name: "ruvector_swarm_coordinate".into(),
description: "Record coordination between agents for graph learning".into(),
input_schema: ToolInputSchema {
required: vec!["source".into(), "target".into()],
properties: [
("source".into(), PropertyDef {
prop_type: "string".into(),
description: "Source agent ID".into(),
default: None,
enum_values: None,
}),
("target".into(), PropertyDef {
prop_type: "string".into(),
description: "Target agent ID".into(),
default: None,
enum_values: None,
}),
("weight".into(), PropertyDef {
prop_type: "number".into(),
description: "Coordination weight (0.0-1.0)".into(),
default: Some("1.0".into()),
enum_values: None,
}),
("success".into(), PropertyDef {
prop_type: "boolean".into(),
description: "Whether coordination was successful".into(),
default: Some("true".into()),
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Swarm,
},
McpToolDef {
name: "ruvector_swarm_optimize".into(),
description: "Get optimal task distribution across swarm agents".into(),
input_schema: ToolInputSchema {
required: vec!["tasks".into()],
properties: [
("tasks".into(), PropertyDef {
prop_type: "array".into(),
description: "List of tasks to distribute".into(),
default: None,
enum_values: None,
}),
("strategy".into(), PropertyDef {
prop_type: "string".into(),
description: "Distribution strategy".into(),
default: Some("balanced".into()),
enum_values: Some(vec![
"balanced".into(), "specialized".into(), "adaptive".into(),
]),
}),
].into_iter().collect(),
},
category: ToolCategory::Swarm,
},
// === Telemetry Tools ===
McpToolDef {
name: "ruvector_telemetry_config".into(),
description: "Configure telemetry settings".into(),
input_schema: ToolInputSchema {
required: vec![],
properties: [
("disable_telemetry".into(), PropertyDef {
prop_type: "boolean".into(),
description: "Disable Statsig metrics".into(),
default: Some("false".into()),
enum_values: None,
}),
("disable_error_reporting".into(), PropertyDef {
prop_type: "boolean".into(),
description: "Disable Sentry error reporting".into(),
default: Some("false".into()),
enum_values: None,
}),
("retention_days".into(), PropertyDef {
prop_type: "integer".into(),
description: "Data retention period in days".into(),
default: Some("30".into()),
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Telemetry,
},
McpToolDef {
name: "ruvector_intelligence_stats".into(),
description: "Get intelligence layer statistics".into(),
input_schema: ToolInputSchema {
required: vec![],
properties: [
("detailed".into(), PropertyDef {
prop_type: "boolean".into(),
description: "Include detailed breakdown".into(),
default: Some("false".into()),
enum_values: None,
}),
("format".into(), PropertyDef {
prop_type: "string".into(),
description: "Output format".into(),
default: Some("json".into()),
enum_values: Some(vec!["json".into(), "text".into(), "markdown".into()]),
}),
].into_iter().collect(),
},
category: ToolCategory::Telemetry,
},
// === File Sequence Tools ===
McpToolDef {
name: "ruvector_suggest_next_file".into(),
description: "Suggest next files to edit based on learned patterns".into(),
input_schema: ToolInputSchema {
required: vec!["current_file".into()],
properties: [
("current_file".into(), PropertyDef {
prop_type: "string".into(),
description: "Currently edited file".into(),
default: None,
enum_values: None,
}),
("count".into(), PropertyDef {
prop_type: "integer".into(),
description: "Number of suggestions".into(),
default: Some("3".into()),
enum_values: None,
}),
].into_iter().collect(),
},
category: ToolCategory::Learning,
},
McpToolDef {
name: "ruvector_record_sequence".into(),
description: "Record file edit sequence for pattern learning".into(),
input_schema: ToolInputSchema {
required: vec!["files".into()],
properties: [
("files".into(), PropertyDef {
prop_type: "array".into(),
description: "Sequence of files edited".into(),
default: None,
enum_values: None,
}),
("success".into(), PropertyDef {
prop_type: "boolean".into(),
description: "Whether sequence was successful".into(),
default: Some("true".into()),
enum_values: None,
}),
("pattern_type".into(), PropertyDef {
prop_type: "string".into(),
description: "Type of editing pattern".into(),
default: None,
enum_values: Some(vec![
"rust_crate_setup".into(),
"tdd".into(),
"types_first".into(),
"refactoring".into(),
]),
}),
].into_iter().collect(),
},
category: ToolCategory::Learning,
},
]
}
/// Generate MCP tools list JSON
pub fn generate_tools_list_json() -> String {
let tools = get_ruvector_tools();
let tool_entries: Vec<String> = tools.iter().map(|tool| {
let props: Vec<String> = tool.input_schema.properties.iter().map(|(name, prop)| {
let mut prop_json = format!(
r#" "{}": {{
"type": "{}",
"description": "{}""#,
name, prop.prop_type, prop.description
);
if let Some(default) = &prop.default {
prop_json.push_str(&format!(r#",
"default": {}"#, default));
}
if let Some(enums) = &prop.enum_values {
let enum_str: Vec<String> = enums.iter().map(|e| format!("\"{}\"", e)).collect();
prop_json.push_str(&format!(r#",
"enum": [{}]"#, enum_str.join(", ")));
}
prop_json.push_str("\n }");
prop_json
}).collect();
let required: Vec<String> = tool.input_schema.required.iter().map(|r| format!("\"{}\"", r)).collect();
format!(
r#" {{
"name": "{}",
"description": "{}",
"inputSchema": {{
"type": "object",
"properties": {{
{}
}},
"required": [{}]
}}
}}"#,
tool.name, tool.description, props.join(",\n"), required.join(", ")
)
}).collect();
format!(
r#"{{
"tools": [
{}
]
}}"#,
tool_entries.join(",\n")
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_ruvector_tools() {
let tools = get_ruvector_tools();
assert!(!tools.is_empty());
// Check we have tools in each category
let categories: Vec<ToolCategory> = tools.iter().map(|t| t.category).collect();
assert!(categories.contains(&ToolCategory::Learning));
assert!(categories.contains(&ToolCategory::Memory));
assert!(categories.contains(&ToolCategory::Swarm));
}
#[test]
fn test_tool_has_required_properties() {
let tools = get_ruvector_tools();
for tool in tools {
// All required fields should be in properties
for req in &tool.input_schema.required {
assert!(
tool.input_schema.properties.contains_key(req),
"Tool {} missing required property {}", tool.name, req
);
}
}
}
#[test]
fn test_generate_tools_list_json() {
let json = generate_tools_list_json();
assert!(json.contains("\"tools\""));
assert!(json.contains("ruvector_learn_pattern"));
assert!(json.contains("ruvector_remember"));
}
}

View File

@@ -0,0 +1,72 @@
//! Learning Scenarios Module
//!
//! This module provides patterns and scenarios for training the
//! RuVector self-learning hooks system, with full Claude Agent SDK
//! and MCP integration support.
//!
//! ## Four Attention Mechanisms
//!
//! | Attention Type | Question Answered | Application |
//! |---------------|-------------------|-------------|
//! | **Neural** | What words matter? | Token/semantic relevance |
//! | **DAG** | What steps matter? | Execution order, dependencies |
//! | **Graph** | What relationships matter? | Code structure, call graphs |
//! | **State Space** | What history still matters? | Context persistence |
pub mod error_recovery;
pub mod file_sequences;
pub mod sdk_integration;
pub mod mcp_tools;
pub mod attention_patterns;
pub use error_recovery::error_patterns::{ErrorLearningTracker, ErrorPattern, RecoveryStrategy};
pub use file_sequences::sequence_tracker::{EditSequence, FileEdit, SequencePattern, SequenceTracker};
pub use sdk_integration::{
AgentDefinition, HookEventType, HookMatcher, McpServerConfig,
PermissionMode, QueryOptions, TelemetryConfig, generate_settings_json,
};
pub use mcp_tools::{
McpToolDef, PropertyDef, ToolCategory, ToolInputSchema,
get_ruvector_tools, generate_tools_list_json,
};
pub use attention_patterns::{
NeuralAttention, DagAttention, GraphAttention, StateSpaceAttention,
AttentionOrchestrator, AttentionAnalysis,
DagNode, StepType, GraphNode, GraphEdge, NodeType, EdgeType, HistoryEntry,
};
/// Initialize the learning scenarios system
pub fn init() {
log::info!("🧠 Learning scenarios initialized");
}
/// Learning statistics
#[derive(Debug, Default)]
pub struct LearningStats {
pub patterns_learned: u32,
pub errors_recovered: u32,
pub sequences_detected: u32,
pub agent_routings: u32,
}
impl LearningStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_pattern(&mut self) {
self.patterns_learned += 1;
}
pub fn record_recovery(&mut self) {
self.errors_recovered += 1;
}
pub fn record_sequence(&mut self) {
self.sequences_detected += 1;
}
pub fn record_routing(&mut self) {
self.agent_routings += 1;
}
}

View File

@@ -0,0 +1,402 @@
//! Claude Agent SDK Integration for RuVector
//!
//! Provides patterns and utilities for integrating RuVector's self-learning
//! intelligence with the Claude Agent SDK.
use std::collections::HashMap;
/// Permission modes matching Claude Code's permission system
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionMode {
/// Default mode - requires approval for most operations
Default,
/// Accept edits mode - auto-approves file edits
AcceptEdits,
/// Bypass permissions - runs without prompts (CI/CD)
BypassPermissions,
/// Plan mode - safe analysis without execution
Plan,
}
impl PermissionMode {
pub fn from_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"acceptedits" | "accept-edits" | "accept_edits" => Self::AcceptEdits,
"bypasspermissions" | "bypass-permissions" | "bypass" => Self::BypassPermissions,
"plan" => Self::Plan,
_ => Self::Default,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Default => "default",
Self::AcceptEdits => "acceptEdits",
Self::BypassPermissions => "bypassPermissions",
Self::Plan => "plan",
}
}
/// Check if this mode allows a specific operation
pub fn allows(&self, operation: &str) -> bool {
match self {
Self::BypassPermissions => true,
Self::AcceptEdits => matches!(operation, "read" | "edit" | "write" | "glob" | "grep"),
Self::Plan => matches!(operation, "read" | "glob" | "grep"),
Self::Default => false, // Requires explicit approval
}
}
}
/// Telemetry configuration matching Claude Code's telemetry options
#[derive(Debug, Clone)]
pub struct TelemetryConfig {
/// Disable Statsig metrics collection
pub disable_telemetry: bool,
/// Disable Sentry error reporting
pub disable_error_reporting: bool,
/// Disable /bug command
pub disable_bug_command: bool,
/// Disable all non-essential network traffic
pub disable_nonessential_traffic: bool,
/// Custom telemetry endpoint
pub custom_endpoint: Option<String>,
/// Data retention days (consumer: 5 years or 30 days, commercial: 30 days)
pub retention_days: u32,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
disable_telemetry: false,
disable_error_reporting: false,
disable_bug_command: false,
disable_nonessential_traffic: false,
custom_endpoint: None,
retention_days: 30,
}
}
}
impl TelemetryConfig {
/// Create config from environment variables
pub fn from_env() -> Self {
Self {
disable_telemetry: std::env::var("DISABLE_TELEMETRY").is_ok(),
disable_error_reporting: std::env::var("DISABLE_ERROR_REPORTING").is_ok(),
disable_bug_command: std::env::var("DISABLE_BUG_COMMAND").is_ok(),
disable_nonessential_traffic: std::env::var("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC").is_ok(),
custom_endpoint: std::env::var("RUVECTOR_TELEMETRY_ENDPOINT").ok(),
retention_days: std::env::var("RUVECTOR_RETENTION_DAYS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(30),
}
}
/// Check if telemetry is enabled
pub fn is_enabled(&self) -> bool {
!self.disable_telemetry && !self.disable_nonessential_traffic
}
/// Export as environment variables
pub fn to_env_vars(&self) -> HashMap<String, String> {
let mut vars = HashMap::new();
if self.disable_telemetry {
vars.insert("DISABLE_TELEMETRY".into(), "1".into());
}
if self.disable_error_reporting {
vars.insert("DISABLE_ERROR_REPORTING".into(), "1".into());
}
if self.disable_bug_command {
vars.insert("DISABLE_BUG_COMMAND".into(), "1".into());
}
if self.disable_nonessential_traffic {
vars.insert("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC".into(), "1".into());
}
if let Some(endpoint) = &self.custom_endpoint {
vars.insert("RUVECTOR_TELEMETRY_ENDPOINT".into(), endpoint.clone());
}
vars.insert("RUVECTOR_RETENTION_DAYS".into(), self.retention_days.to_string());
vars
}
}
/// Agent SDK query options
#[derive(Debug, Clone)]
pub struct QueryOptions {
/// Allowed tools for this query
pub allowed_tools: Vec<String>,
/// Permission mode
pub permission_mode: PermissionMode,
/// System prompt override
pub system_prompt: Option<String>,
/// Model to use (sonnet, opus, haiku)
pub model: String,
/// Session ID to resume
pub resume_session: Option<String>,
/// Maximum agentic turns
pub max_turns: Option<u32>,
/// Output format (text, json, stream-json)
pub output_format: String,
/// Custom agents/subagents
pub agents: HashMap<String, AgentDefinition>,
/// MCP servers to enable
pub mcp_servers: HashMap<String, McpServerConfig>,
}
impl Default for QueryOptions {
fn default() -> Self {
Self {
allowed_tools: vec![
"Read".into(),
"Edit".into(),
"Write".into(),
"Bash".into(),
"Glob".into(),
"Grep".into(),
],
permission_mode: PermissionMode::Default,
system_prompt: None,
model: "claude-sonnet-4-5-20250929".into(),
resume_session: None,
max_turns: None,
output_format: "text".into(),
agents: HashMap::new(),
mcp_servers: HashMap::new(),
}
}
}
/// Agent definition for custom subagents
#[derive(Debug, Clone)]
pub struct AgentDefinition {
pub description: String,
pub prompt: String,
pub tools: Vec<String>,
}
/// MCP server configuration
#[derive(Debug, Clone)]
pub struct McpServerConfig {
pub command: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
}
/// Hook event types matching Claude Code's hook system
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookEventType {
/// Before a tool is executed
PreToolUse,
/// After a tool execution completes
PostToolUse,
/// When a notification is received
Notification,
/// Before context compaction
PreCompact,
/// When a session starts
SessionStart,
/// When execution stops
Stop,
/// When user submits a prompt
UserPromptSubmit,
}
impl HookEventType {
pub fn as_str(&self) -> &'static str {
match self {
Self::PreToolUse => "PreToolUse",
Self::PostToolUse => "PostToolUse",
Self::Notification => "Notification",
Self::PreCompact => "PreCompact",
Self::SessionStart => "SessionStart",
Self::Stop => "Stop",
Self::UserPromptSubmit => "UserPromptSubmit",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"PreToolUse" => Some(Self::PreToolUse),
"PostToolUse" => Some(Self::PostToolUse),
"Notification" => Some(Self::Notification),
"PreCompact" => Some(Self::PreCompact),
"SessionStart" => Some(Self::SessionStart),
"Stop" => Some(Self::Stop),
"UserPromptSubmit" => Some(Self::UserPromptSubmit),
_ => None,
}
}
}
/// Hook matcher configuration
#[derive(Debug, Clone)]
pub struct HookMatcher {
pub event_type: HookEventType,
pub matcher: String, // Regex pattern for tool matching
pub command: String,
pub timeout_ms: u32,
}
impl HookMatcher {
pub fn new(event_type: HookEventType, matcher: &str, command: &str) -> Self {
Self {
event_type,
matcher: matcher.into(),
command: command.into(),
timeout_ms: 5000,
}
}
pub fn with_timeout(mut self, timeout_ms: u32) -> Self {
self.timeout_ms = timeout_ms;
self
}
}
/// Generate Claude Code settings JSON for RuVector integration
pub fn generate_settings_json(telemetry: &TelemetryConfig) -> String {
let env_vars = telemetry.to_env_vars();
let env_json: Vec<String> = env_vars
.iter()
.map(|(k, v)| format!(" \"{}\": \"{}\"", k, v))
.collect();
format!(
r#"{{
"env": {{
"RUVECTOR_INTELLIGENCE_ENABLED": "true",
"RUVECTOR_LEARNING_RATE": "0.1",
"RUVECTOR_MEMORY_BACKEND": "rvlite",
"INTELLIGENCE_MODE": "treatment",
{}
}},
"permissions": {{
"allow": [
"Bash(ruvector:*)",
"Bash(ruvector-cli:*)",
"Bash(npx ruvector:*)",
"Bash(cargo test:*)",
"Bash(git:*)"
],
"deny": [
"Bash(rm -rf /)"
]
}},
"hooks": {{
"PreToolUse": [
{{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{{
"type": "command",
"command": "ruvector-cli hooks pre-edit \"$TOOL_INPUT_file_path\""
}}]
}},
{{
"matcher": "Bash",
"hooks": [{{
"type": "command",
"command": "ruvector-cli hooks pre-command \"$TOOL_INPUT_command\""
}}]
}},
{{
"matcher": "Task",
"hooks": [{{
"type": "command",
"timeout": 1000,
"command": "ruvector-cli hooks remember \"Agent: $TOOL_INPUT_subagent_type\" -t agent_spawn"
}}]
}}
],
"PostToolUse": [
{{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{{
"type": "command",
"command": "ruvector-cli hooks post-edit \"$TOOL_INPUT_file_path\" --success"
}}]
}},
{{
"matcher": "Bash",
"hooks": [{{
"type": "command",
"command": "ruvector-cli hooks post-command \"$TOOL_INPUT_command\" --success"
}}]
}}
],
"SessionStart": [{{
"hooks": [{{
"type": "command",
"command": "ruvector-cli hooks session-start"
}}]
}}],
"Stop": [{{
"hooks": [{{
"type": "command",
"command": "ruvector-cli hooks session-end"
}}]
}}],
"UserPromptSubmit": [{{
"hooks": [{{
"type": "command",
"timeout": 2000,
"command": "ruvector-cli hooks suggest-context"
}}]
}}]
}}
}}"#,
env_json.join(",\n")
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permission_mode_from_str() {
assert_eq!(PermissionMode::from_str("acceptEdits"), PermissionMode::AcceptEdits);
assert_eq!(PermissionMode::from_str("bypass"), PermissionMode::BypassPermissions);
assert_eq!(PermissionMode::from_str("plan"), PermissionMode::Plan);
assert_eq!(PermissionMode::from_str("unknown"), PermissionMode::Default);
}
#[test]
fn test_permission_mode_allows() {
assert!(PermissionMode::BypassPermissions.allows("edit"));
assert!(PermissionMode::AcceptEdits.allows("read"));
assert!(!PermissionMode::AcceptEdits.allows("bash"));
assert!(PermissionMode::Plan.allows("grep"));
assert!(!PermissionMode::Plan.allows("edit"));
}
#[test]
fn test_telemetry_config_from_env() {
// Default should have telemetry enabled
let config = TelemetryConfig::default();
assert!(config.is_enabled());
assert!(!config.disable_telemetry);
}
#[test]
fn test_hook_event_type_roundtrip() {
for event in [
HookEventType::PreToolUse,
HookEventType::PostToolUse,
HookEventType::SessionStart,
] {
let s = event.as_str();
assert_eq!(HookEventType::from_str(s), Some(event));
}
}
#[test]
fn test_generate_settings_json() {
let config = TelemetryConfig::default();
let json = generate_settings_json(&config);
assert!(json.contains("RUVECTOR_INTELLIGENCE_ENABLED"));
assert!(json.contains("PreToolUse"));
assert!(json.contains("PostToolUse"));
}
}