chore: license AGPL-3.0, zero clippy warnings, CHANGELOG through v1.6.0

License:
- Replace MIT/Apache-2.0 with AGPL-3.0-only across all crates and npm packages
- Replace LICENSE file with official GNU AGPL-3.0 text
- Remove LICENSE-MIT and LICENSE-APACHE

Code quality:
- Fix all 44 clippy warnings (zero remaining)
- Collapsible if statements, redundant closures, manual Option::map
- Remove duplicate #[allow(dead_code)] attributes in deprecated tool modules
- Add Default impl for CognitiveEngine
- Replace manual sort_by with sort_by_key

Documentation:
- Update CHANGELOG with v1.2.0, v1.3.0, v1.5.0, v1.6.0 entries
- Update README with v1.6.0 highlights and accurate stats (52K lines, 1100+ tests)
- Add fastembed-rs/ to .gitignore
- Add fastembed-rs to workspace exclude

1115 tests passing, zero warnings, RUSTFLAGS="-Dwarnings" clean.
This commit is contained in:
Sam Valladares 2026-02-19 03:00:39 -06:00
parent 495a88331f
commit ce520bb246
40 changed files with 953 additions and 424 deletions

View file

@ -4,7 +4,7 @@ version = "1.6.0"
edition = "2024"
description = "Cognitive memory MCP server for Claude - FSRS-6, spreading activation, synaptic tagging, and 130 years of memory research"
authors = ["samvallad33"]
license = "MIT OR Apache-2.0"
license = "AGPL-3.0-only"
keywords = ["mcp", "ai", "memory", "fsrs", "neuroscience", "cognitive-science", "spaced-repetition"]
categories = ["command-line-utilities", "database"]
repository = "https://github.com/samvallad33/vestige"

View file

@ -561,11 +561,10 @@ fn run_backup(output: PathBuf) -> anyhow::Result<()> {
}
// Create parent directories if needed
if let Some(parent) = output.parent() {
if !parent.exists() {
if let Some(parent) = output.parent()
&& !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
// Copy the database file
println!("Copying database...");
@ -638,11 +637,10 @@ fn run_export(
.iter()
.filter(|node| {
// Date filter
if let Some(ref since_dt) = since_date {
if node.created_at < *since_dt {
if let Some(ref since_dt) = since_date
&& node.created_at < *since_dt {
return false;
}
}
// Tag filter: node must contain ALL specified tags
if !tag_filter.is_empty() {
for tag in &tag_filter {
@ -671,11 +669,10 @@ fn run_export(
println!();
// Create parent directories if needed
if let Some(parent) = output.parent() {
if !parent.exists() {
if let Some(parent) = output.parent()
&& !parent.exists() {
std::fs::create_dir_all(parent)?;
}
}
let file = std::fs::File::create(&output)?;
let mut writer = BufWriter::new(file);

View file

@ -61,10 +61,16 @@ pub struct CognitiveEngine {
pub temporal_searcher: TemporalSearcher,
}
impl Default for CognitiveEngine {
fn default() -> Self {
Self::new()
}
}
impl CognitiveEngine {
/// Initialize all cognitive modules with default configurations.
pub fn new() -> Self {
let engine = Self {
Self {
// Neuroscience
activation_network: ActivationNetwork::new(),
synaptic_tagging: SynapticTaggingSystem::new(),
@ -98,8 +104,6 @@ impl CognitiveEngine {
// Search
reranker: Reranker::new(RerankerConfig::default()),
temporal_searcher: TemporalSearcher::new(),
};
engine
}
}
}

View file

@ -87,7 +87,7 @@ impl McpServer {
// Version negotiation: use client's version if older than server's
// Claude Desktop rejects servers with newer protocol versions
let negotiated_version = if request.protocol_version < MCP_VERSION.to_string() {
let negotiated_version = if request.protocol_version.as_str() < MCP_VERSION {
info!("Client requested older protocol version {}, using it", request.protocol_version);
request.protocol_version.clone()
} else {
@ -593,7 +593,7 @@ impl McpServer {
let should_consolidate = self.cognitive.try_lock()
.ok()
.map(|cog| cog.consolidation_scheduler.should_consolidate())
.unwrap_or(count % 100 == 0); // Fallback to count-based if lock unavailable
.unwrap_or(count.is_multiple_of(100)); // Fallback to count-based if lock unavailable
if should_consolidate {
let storage_clone = Arc::clone(&self.storage);

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Codebase Tools (Deprecated - use codebase_unified instead)
//!
//! Remember patterns, decisions, and context about codebases.
@ -131,12 +130,12 @@ pub async fn execute_pattern(
// Build content with structured format
let mut content = format!("# Code Pattern: {}\n\n{}", args.name, args.description);
if let Some(ref files) = args.files {
if !files.is_empty() {
content.push_str("\n\n## Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
if let Some(ref files) = args.files
&& !files.is_empty()
{
content.push_str("\n\n## Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
}
@ -189,21 +188,21 @@ pub async fn execute_decision(
args.decision
);
if let Some(ref alternatives) = args.alternatives {
if !alternatives.is_empty() {
content.push_str("\n\n## Alternatives Considered:\n");
for alt in alternatives {
content.push_str(&format!("- {}\n", alt));
}
if let Some(ref alternatives) = args.alternatives
&& !alternatives.is_empty()
{
content.push_str("\n\n## Alternatives Considered:\n");
for alt in alternatives {
content.push_str(&format!("- {}\n", alt));
}
}
if let Some(ref files) = args.files {
if !files.is_empty() {
content.push_str("\n\n## Affected Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
if let Some(ref files) = args.files
&& !files.is_empty()
{
content.push_str("\n\n## Affected Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
}
@ -239,7 +238,7 @@ pub async fn execute_context(
args: Option<Value>,
) -> Result<Value, String> {
let args: ContextArgs = args
.map(|v| serde_json::from_value(v))
.map(serde_json::from_value)
.transpose()
.map_err(|e| format!("Invalid arguments: {}", e))?
.unwrap_or(ContextArgs {

View file

@ -127,12 +127,12 @@ async fn execute_remember_pattern(
// Build content with structured format
let mut content = format!("# Code Pattern: {}\n\n{}", name, description);
if let Some(ref files) = args.files {
if !files.is_empty() {
content.push_str("\n\n## Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
if let Some(ref files) = args.files
&& !files.is_empty()
{
content.push_str("\n\n## Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
}
@ -211,21 +211,21 @@ async fn execute_remember_decision(
decision
);
if let Some(ref alternatives) = args.alternatives {
if !alternatives.is_empty() {
content.push_str("\n\n## Alternatives Considered:\n");
for alt in alternatives {
content.push_str(&format!("- {}\n", alt));
}
if let Some(ref alternatives) = args.alternatives
&& !alternatives.is_empty()
{
content.push_str("\n\n## Alternatives Considered:\n");
for alt in alternatives {
content.push_str(&format!("- {}\n", alt));
}
}
if let Some(ref files) = args.files {
if !files.is_empty() {
content.push_str("\n\n## Affected Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
if let Some(ref files) = args.files
&& !files.is_empty()
{
content.push_str("\n\n## Affected Files:\n");
for f in files {
content.push_str(&format!("- {}\n", f));
}
}
@ -336,23 +336,23 @@ async fn execute_get_context(
// COGNITIVE: Cross-project knowledge discovery
// ====================================================================
let mut universal_patterns = Vec::new();
if let Some(codebase_name) = &args.codebase {
if let Ok(cog) = cognitive.try_lock() {
let context = vestige_core::advanced::cross_project::ProjectContext {
path: None,
name: Some(codebase_name.clone()),
languages: Vec::new(),
frameworks: Vec::new(),
file_types: std::collections::HashSet::new(),
dependencies: Vec::new(),
structure: Vec::new(),
};
let applicable = cog.cross_project.detect_applicable(&context);
for knowledge in applicable {
universal_patterns.push(serde_json::json!({
"pattern": format!("{:?}", knowledge),
}));
}
if let Some(codebase_name) = &args.codebase
&& let Ok(cog) = cognitive.try_lock()
{
let context = vestige_core::advanced::cross_project::ProjectContext {
path: None,
name: Some(codebase_name.clone()),
languages: Vec::new(),
frameworks: Vec::new(),
file_types: std::collections::HashSet::new(),
dependencies: Vec::new(),
structure: Vec::new(),
};
let applicable = cog.cross_project.detect_applicable(&context);
for knowledge in applicable {
universal_patterns.push(serde_json::json!({
"pattern": format!("{:?}", knowledge),
}));
}
}

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Consolidation Tool (Deprecated)
//!
//! Run memory consolidation cycle with FSRS decay and embedding generation.

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Context-Dependent Memory Tool (Deprecated)
//!
//! Retrieval based on encoding context match.

View file

@ -175,7 +175,7 @@ pub async fn execute(
for i in 0..n {
for j in (i + 1)..n {
let sim = cosine_similarity(&filtered_embeddings[i].2, &filtered_embeddings[j].2);
let sim = cosine_similarity(filtered_embeddings[i].2, filtered_embeddings[j].2);
if sim >= threshold {
uf.union(i, j);
similarities.push((i, j, sim));
@ -195,7 +195,7 @@ pub async fn execute(
.into_values()
.filter(|c| c.len() > 1)
.collect();
clusters.sort_by(|a, b| b.len().cmp(&a.len()));
clusters.sort_by_key(|b| std::cmp::Reverse(b.len()));
clusters.truncate(limit);
// Build similarity lookup for formatting

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Feedback Tools
//!
//! Promote and demote memories based on outcome quality.

View file

@ -140,7 +140,7 @@ pub async fn execute(
run_post_ingest(cognitive, &node_id, &node_content, &node_type, importance_composite);
return Ok(serde_json::json!({
Ok(serde_json::json!({
"success": true,
"nodeId": node_id,
"decision": result.decision,
@ -150,7 +150,7 @@ pub async fn execute(
"reason": result.reason,
"isNovel": is_novel,
"embeddingStrategy": embedding_strategy,
}));
}))
}
Err(_) => {
let node = storage_guard.ingest(fallback_input).map_err(|e| e.to_string())?;
@ -162,7 +162,7 @@ pub async fn execute(
run_post_ingest(cognitive, &node_id, &node_content, &node_type, importance_composite);
return Ok(serde_json::json!({
Ok(serde_json::json!({
"success": true,
"nodeId": node_id,
"decision": "create",
@ -170,7 +170,7 @@ pub async fn execute(
"hasEmbedding": has_embedding,
"isNovel": is_novel,
"embeddingStrategy": embedding_strategy,
}));
}))
}
}
}

View file

@ -257,38 +257,38 @@ async fn execute_set(
if let Ok(cog) = cognitive.try_lock() {
// 8A. Try NLP parsing when no explicit trigger is provided
if args.trigger.is_none() {
if let Ok(parsed) = cog.intention_parser.parse(description) {
nlp_parsed = true;
// Extract trigger info from parsed intention
let (t_type, t_data) = match &parsed.trigger {
ProspectiveTrigger::TimeBased { .. } => {
("time".to_string(), serde_json::json!({"type": "time"}).to_string())
}
ProspectiveTrigger::DurationBased { after, .. } => {
let mins = after.num_minutes();
("time".to_string(), serde_json::json!({"type": "time", "in_minutes": mins}).to_string())
}
ProspectiveTrigger::EventBased { condition, .. } => {
("event".to_string(), serde_json::json!({"type": "event", "condition": condition}).to_string())
}
ProspectiveTrigger::ContextBased { context_match } => {
("context".to_string(), serde_json::json!({"type": "context", "topic": format!("{:?}", context_match)}).to_string())
}
ProspectiveTrigger::Recurring { .. } => {
("recurring".to_string(), serde_json::json!({"type": "recurring"}).to_string())
}
_ => {
("event".to_string(), serde_json::json!({"type": "event"}).to_string())
}
};
nlp_trigger_type = Some(t_type);
nlp_trigger_data = Some(t_data);
// Use NLP-detected priority if user didn't specify one
if args.priority.is_none() {
nlp_priority = Some(parsed.priority);
if args.trigger.is_none()
&& let Ok(parsed) = cog.intention_parser.parse(description)
{
nlp_parsed = true;
// Extract trigger info from parsed intention
let (t_type, t_data) = match &parsed.trigger {
ProspectiveTrigger::TimeBased { .. } => {
("time".to_string(), serde_json::json!({"type": "time"}).to_string())
}
ProspectiveTrigger::DurationBased { after, .. } => {
let mins = after.num_minutes();
("time".to_string(), serde_json::json!({"type": "time", "in_minutes": mins}).to_string())
}
ProspectiveTrigger::EventBased { condition, .. } => {
("event".to_string(), serde_json::json!({"type": "event", "condition": condition}).to_string())
}
ProspectiveTrigger::ContextBased { context_match } => {
("context".to_string(), serde_json::json!({"type": "context", "topic": format!("{:?}", context_match)}).to_string())
}
ProspectiveTrigger::Recurring { .. } => {
("recurring".to_string(), serde_json::json!({"type": "recurring"}).to_string())
}
_ => {
("event".to_string(), serde_json::json!({"type": "event"}).to_string())
}
};
nlp_trigger_type = Some(t_type);
nlp_trigger_data = Some(t_data);
// Use NLP-detected priority if user didn't specify one
if args.priority.is_none() {
nlp_priority = Some(parsed.priority);
}
}
@ -355,10 +355,8 @@ async fn execute_set(
DateTime::parse_from_rfc3339(at)
.ok()
.map(|dt| dt.with_timezone(&Utc))
} else if let Some(mins) = trigger.in_minutes {
Some(now + Duration::minutes(mins))
} else {
None
trigger.in_minutes.map(|mins| now + Duration::minutes(mins))
}
} else {
None
@ -410,21 +408,21 @@ async fn execute_check(
// ====================================================================
// COGNITIVE: Update prospective memory context
// ====================================================================
if let Some(ctx) = &args.context {
if let Ok(cog) = cognitive.try_lock() {
let mut prospective_ctx = ProspectiveContext::new();
if let Some(codebase) = &ctx.codebase {
prospective_ctx.project_name = Some(codebase.clone());
}
if let Some(file) = &ctx.file {
prospective_ctx.active_files = vec![file.clone()];
}
if let Some(topics) = &ctx.topics {
prospective_ctx.active_topics = topics.clone();
}
// Update context on prospective memory (triggers internal monitoring)
let _ = cog.prospective_memory.update_context(prospective_ctx);
if let Some(ctx) = &args.context
&& let Ok(cog) = cognitive.try_lock()
{
let mut prospective_ctx = ProspectiveContext::new();
if let Some(codebase) = &ctx.codebase {
prospective_ctx.project_name = Some(codebase.clone());
}
if let Some(file) = &ctx.file {
prospective_ctx.active_files = vec![file.clone()];
}
if let Some(topics) = &ctx.topics {
prospective_ctx.active_topics = topics.clone();
}
// Update context on prospective memory (triggers internal monitoring)
let _ = cog.prospective_memory.update_context(prospective_ctx);
}
let storage = storage.lock().await;

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Intentions Tools (Deprecated - use intention_unified instead)
//!
//! Prospective memory tools for setting and checking future intentions.
@ -264,10 +263,8 @@ pub async fn execute_set(
let trigger_at = if let Some(trigger) = &args.trigger {
if let Some(at) = &trigger.at {
DateTime::parse_from_rfc3339(at).ok().map(|dt| dt.with_timezone(&Utc))
} else if let Some(mins) = trigger.in_minutes {
Some(now + Duration::minutes(mins))
} else {
None
trigger.in_minutes.map(|mins| now + Duration::minutes(mins))
}
} else {
None

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Knowledge Tools (Deprecated - use memory_unified instead)
//!
//! Get and delete specific knowledge nodes.

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Memory States Tool (Deprecated - use memory_unified instead)
//!
//! Query and manage memory states (Active, Dormant, Silent, Unavailable).

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Recall Tool (Deprecated - use search_unified instead)
//!
//! Search and retrieve knowledge from memory.

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Review Tool (Deprecated)
//!
//! Mark memories as reviewed using FSRS-6 algorithm.

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Search Tools (Deprecated - use search_unified instead)
//!
//! Semantic and hybrid search implementations.

View file

@ -226,19 +226,19 @@ pub async fn execute(
// ====================================================================
// STAGE 5: Context matching (Tulving 1973 encoding specificity)
// ====================================================================
if let Some(ref topics) = args.context_topics {
if !topics.is_empty() {
let retrieval_ctx = EncodingContext::new()
.with_topical(TopicalContext::with_topics(topics.clone()));
if let Ok(cog) = cognitive.try_lock() {
for result in &mut filtered_results {
// Build encoding context from memory's tags
let encoding_ctx = EncodingContext::new()
.with_topical(TopicalContext::with_topics(result.node.tags.clone()));
let context_score = cog.context_matcher.match_contexts(&encoding_ctx, &retrieval_ctx);
// Blend: context match boosts relevance up to +30%
result.combined_score *= 1.0 + (context_score as f32 * 0.3);
}
if let Some(ref topics) = args.context_topics
&& !topics.is_empty()
{
let retrieval_ctx = EncodingContext::new()
.with_topical(TopicalContext::with_topics(topics.clone()));
if let Ok(cog) = cognitive.try_lock() {
for result in &mut filtered_results {
// Build encoding context from memory's tags
let encoding_ctx = EncodingContext::new()
.with_topical(TopicalContext::with_topics(result.node.tags.clone()));
let context_score = cog.context_matcher.match_contexts(&encoding_ctx, &retrieval_ctx);
// Blend: context match boosts relevance up to +30%
result.combined_score *= 1.0 + (context_score as f32 * 0.3);
}
}
}
@ -270,23 +270,23 @@ pub async fn execute(
// STAGE 5B: Retrieval competition (Anderson et al. 1994)
// ====================================================================
let mut suppressed_count = 0_usize;
if filtered_results.len() > 1 {
if let Ok(mut cog) = cognitive.try_lock() {
let candidates: Vec<CompetitionCandidate> = filtered_results
.iter()
.map(|r| CompetitionCandidate {
memory_id: r.node.id.clone(),
relevance_score: r.combined_score as f64,
similarity_to_query: r.semantic_score.unwrap_or(0.0) as f64,
})
.collect();
if let Some(result) = cog.competition_mgr.run_competition(&candidates, 0.7) {
// Apply suppression: losers get penalized
for suppressed_id in &result.suppressed_ids {
if let Some(r) = filtered_results.iter_mut().find(|r| &r.node.id == suppressed_id) {
r.combined_score *= 0.85; // 15% suppression penalty
suppressed_count += 1;
}
if filtered_results.len() > 1
&& let Ok(mut cog) = cognitive.try_lock()
{
let candidates: Vec<CompetitionCandidate> = filtered_results
.iter()
.map(|r| CompetitionCandidate {
memory_id: r.node.id.clone(),
relevance_score: r.combined_score as f64,
similarity_to_query: r.semantic_score.unwrap_or(0.0) as f64,
})
.collect();
if let Some(result) = cog.competition_mgr.run_competition(&candidates, 0.7) {
// Apply suppression: losers get penalized
for suppressed_id in &result.suppressed_ids {
if let Some(r) = filtered_results.iter_mut().find(|r| &r.node.id == suppressed_id) {
r.combined_score *= 0.85; // 15% suppression penalty
suppressed_count += 1;
}
}
}

View file

@ -170,7 +170,7 @@ pub async fn execute(
// Post-ingest cognitive side effects
run_post_ingest(cognitive, &node_id, &node_content, &node_type, importance_composite);
return Ok(serde_json::json!({
Ok(serde_json::json!({
"success": true,
"decision": result.decision,
"nodeId": node_id,
@ -191,7 +191,7 @@ pub async fn execute(
"add_context" => "Added new content as context to existing memory",
_ => "Memory processed successfully"
}
}));
}))
}
#[cfg(not(all(feature = "embeddings", feature = "vector-search")))]

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Stats Tools (Deprecated - use memory_unified instead)
//!
//! Memory statistics and health check.

View file

@ -1,4 +1,3 @@
#![allow(dead_code)]
//! Synaptic Tagging Tool (Deprecated)
//!
//! Retroactive importance assignment based on Synaptic Tagging & Capture theory.