mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-10 08:12:37 +02:00
feat: Vestige v1.3.0 — importance scoring, session checkpoints, duplicate detection
3 new MCP tools (16 → 19 total): - importance_score: 4-channel neuroscience importance scoring (novelty/arousal/reward/attention) - session_checkpoint: batch smart_ingest up to 20 items with PE Gating - find_duplicates: cosine similarity clustering with union-find for dedup CLI: vestige ingest command for memory ingestion via command line Core: made get_node_embedding public, added get_all_embeddings for dedup scanning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5cca386d6b
commit
04a3062328
9 changed files with 848 additions and 5 deletions
140
crates/vestige-mcp/src/tools/importance.rs
Normal file
140
crates/vestige-mcp/src/tools/importance.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
//! Importance Score Tool
|
||||
//!
|
||||
//! Exposes the 4-channel importance signaling system as an MCP tool.
|
||||
//! Wraps ImportanceSignals::compute_importance() from vestige-core's
|
||||
//! neuroscience module (dopamine/norepinephrine/acetylcholine/serotonin model).
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use vestige_core::{ImportanceContext, ImportanceSignals, Storage};
|
||||
|
||||
/// Input schema for importance_score tool
|
||||
pub fn schema() -> Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "The content to score for importance"
|
||||
},
|
||||
"context_topics": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Optional topics for novelty detection context"
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "Optional project/codebase name for context"
|
||||
}
|
||||
},
|
||||
"required": ["content"]
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ImportanceArgs {
|
||||
content: String,
|
||||
context_topics: Option<Vec<String>>,
|
||||
project: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
_storage: &Arc<Mutex<Storage>>,
|
||||
args: Option<Value>,
|
||||
) -> Result<Value, String> {
|
||||
let args: ImportanceArgs = match args {
|
||||
Some(v) => serde_json::from_value(v).map_err(|e| format!("Invalid arguments: {}", e))?,
|
||||
None => return Err("Missing arguments".to_string()),
|
||||
};
|
||||
|
||||
if args.content.trim().is_empty() {
|
||||
return Err("Content cannot be empty".to_string());
|
||||
}
|
||||
|
||||
let signals = ImportanceSignals::new();
|
||||
|
||||
let mut context = ImportanceContext::current();
|
||||
if let Some(project) = args.project {
|
||||
context = context.with_project(project);
|
||||
}
|
||||
if let Some(topics) = args.context_topics {
|
||||
context = context.with_tags(topics);
|
||||
}
|
||||
|
||||
let score = signals.compute_importance(&args.content, &context);
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"composite": score.composite,
|
||||
"channels": {
|
||||
"novelty": score.novelty,
|
||||
"arousal": score.arousal,
|
||||
"reward": score.reward,
|
||||
"attention": score.attention
|
||||
},
|
||||
"encodingBoost": score.encoding_boost,
|
||||
"consolidationPriority": format!("{:?}", score.consolidation_priority),
|
||||
"weightsUsed": {
|
||||
"novelty": score.weights_used.novelty,
|
||||
"arousal": score.weights_used.arousal,
|
||||
"reward": score.weights_used.reward,
|
||||
"attention": score.weights_used.attention
|
||||
},
|
||||
"explanations": {
|
||||
"novelty": score.novelty_explanation.as_ref().map(|e| format!("{:?}", e)),
|
||||
"arousal": score.arousal_explanation.as_ref().map(|e| format!("{:?}", e)),
|
||||
"reward": score.reward_explanation.as_ref().map(|e| format!("{:?}", e)),
|
||||
"attention": score.attention_explanation.as_ref().map(|e| format!("{:?}", e))
|
||||
},
|
||||
"summary": score.summary(),
|
||||
"dominantSignal": score.dominant_signal()
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_schema_has_required_fields() {
|
||||
let schema = schema();
|
||||
assert_eq!(schema["type"], "object");
|
||||
assert!(schema["properties"]["content"].is_object());
|
||||
assert!(schema["required"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.contains(&serde_json::json!("content")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_empty_content_fails() {
|
||||
let storage = Arc::new(Mutex::new(
|
||||
Storage::new(Some(std::path::PathBuf::from("/tmp/test_importance.db"))).unwrap(),
|
||||
));
|
||||
let result = execute(&storage, Some(serde_json::json!({ "content": "" }))).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_basic_importance_score() {
|
||||
let storage = Arc::new(Mutex::new(
|
||||
Storage::new(Some(std::path::PathBuf::from("/tmp/test_importance2.db"))).unwrap(),
|
||||
));
|
||||
let result = execute(
|
||||
&storage,
|
||||
Some(serde_json::json!({
|
||||
"content": "CRITICAL: Production database migration failed with data loss!"
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
let value = result.unwrap();
|
||||
assert!(value["composite"].as_f64().is_some());
|
||||
assert!(value["channels"]["novelty"].as_f64().is_some());
|
||||
assert!(value["channels"]["arousal"].as_f64().is_some());
|
||||
assert!(value["dominantSignal"].is_string());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue