mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-02 22:01:01 +02:00
Tool Consolidation v2.2.0, Layer 1 commit 3/6 (3a). Advertised tools 27 → 24. Folds system_status + memory_health + memory_timeline + memory_changelog into one view-dispatched tool: view = health (default) | retention | timeline | changelog - Thin facade: each view forwards the same args envelope to the existing handler. No underlying arg struct uses deny_unknown_fields, so the `view` discriminator is ignored by each handler and per-view fields validate as before. The cognitive lock is never held across a forwarded call. - view='health' returns the byte-for-byte system_status shape (audit scripts parse it), incl. schema_introspection passthrough — verified by test_default_view_is_health asserting equality with execute_system_status. - All 4 old names remain hidden warn!+redirect aliases (removed v2.3.0). - Size annotation moved: memory_timeline (200k) → memory_status, kept in sync across the real loop, expected_max_result_size(), and both annotation tests. - Tests: count 27→24, 4 negative asserts, test_memory_status_views_and_aliases exercising all 4 views + 4 aliases. Gates: cargo test --workspace, cargo clippy -D warnings — clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
141 lines
6.1 KiB
Rust
141 lines
6.1 KiB
Rust
//! Unified `memory_status` Tool (v2.2 — Tool Consolidation)
|
|
//!
|
|
//! Folds four read-only status/health/temporal tools into one
|
|
//! view-dispatched surface:
|
|
//!
|
|
//! view = health (default) | retention | timeline | changelog
|
|
//!
|
|
//! - `health` → full system health + statistics (the former `system_status`).
|
|
//! Returns the byte-for-byte `system_status` shape (audit scripts parse it),
|
|
//! including `schema_introspection` passthrough.
|
|
//! - `retention` → the lightweight retention dashboard (former `memory_health`).
|
|
//! - `timeline` → chronological browse (former `memory_timeline`).
|
|
//! - `changelog` → audit trail of memory changes (former `memory_changelog`).
|
|
//!
|
|
//! This is a thin facade: each view forwards the *same* args envelope to the
|
|
//! existing handler. None of the underlying arg structs use
|
|
//! `deny_unknown_fields`, so the discriminator `view` is simply ignored by each
|
|
//! handler — no lossy re-scoping required, and per-view fields validate as
|
|
//! before. The `cognitive` lock is never held across a forwarded call.
|
|
|
|
use serde_json::Value;
|
|
use std::sync::Arc;
|
|
use tokio::sync::Mutex;
|
|
|
|
use vestige_core::{OutputConfig, Storage};
|
|
|
|
use crate::cognitive::CognitiveEngine;
|
|
|
|
/// Discriminated-union schema for the unified `memory_status` tool.
|
|
pub fn schema() -> Value {
|
|
serde_json::json!({
|
|
"type": "object",
|
|
"properties": {
|
|
"view": {
|
|
"type": "string",
|
|
"enum": ["health", "retention", "timeline", "changelog"],
|
|
"default": "health",
|
|
"description": "Which status view. 'health' (default): full system health + stats + FSRS preview + warnings + recommendations. 'retention': lightweight retention dashboard (avg/distribution/trend). 'timeline': browse memories chronologically. 'changelog': audit trail of memory state changes."
|
|
},
|
|
// --- [health view] ---
|
|
"schema_introspection": {
|
|
"type": "boolean",
|
|
"description": "[health view] Include the response-schema description in the output."
|
|
},
|
|
// --- [timeline view] ---
|
|
"start": { "type": "string", "description": "[timeline/changelog view] Start of range (ISO 8601 date or datetime)." },
|
|
"end": { "type": "string", "description": "[timeline/changelog view] End of range (ISO 8601 date or datetime)." },
|
|
"node_type": { "type": "string", "description": "[timeline view] Filter by node type (e.g. 'fact', 'decision')." },
|
|
"tags": { "type": "array", "items": { "type": "string" }, "description": "[timeline view] Filter by tags (ANY match)." },
|
|
"detail_level": {
|
|
"type": "string", "enum": ["brief", "summary", "full"],
|
|
"description": "[timeline view] Level of detail (default 'summary')."
|
|
},
|
|
// --- [changelog view] ---
|
|
"memory_id": { "type": "string", "description": "[changelog view] Per-memory mode: state transitions for this memory id." },
|
|
// --- shared: limit (per-view ranges differ; clamped internally) ---
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Max results. [timeline] default 50, max 200. [changelog] default 20, clamped to 100. Ignored by health/retention.",
|
|
"minimum": 1, "maximum": 200
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Unified dispatcher for `memory_status`. Routes on `view` (default `health`).
|
|
pub async fn execute(
|
|
storage: &Arc<Storage>,
|
|
cognitive: &Arc<Mutex<CognitiveEngine>>,
|
|
output_config: &OutputConfig,
|
|
args: Option<Value>,
|
|
) -> Result<Value, String> {
|
|
let view = args
|
|
.as_ref()
|
|
.and_then(|a| a.get("view"))
|
|
.and_then(|v| v.as_str())
|
|
.unwrap_or("health")
|
|
.to_string();
|
|
|
|
match view.as_str() {
|
|
// Byte-for-byte system_status shape (incl. schema_introspection passthrough).
|
|
"health" => super::maintenance::execute_system_status(storage, cognitive, args).await,
|
|
"retention" => super::health::execute(storage, args).await,
|
|
"timeline" => super::timeline::execute(storage, output_config, args).await,
|
|
"changelog" => super::changelog::execute(storage, args).await,
|
|
other => Err(format!(
|
|
"Unknown memory_status view '{other}'. Use health|retention|timeline|changelog."
|
|
)),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::cognitive::CognitiveEngine;
|
|
|
|
fn test_storage() -> Arc<Storage> {
|
|
let dir = tempfile::TempDir::new().unwrap();
|
|
let storage = Storage::new(Some(dir.path().join("test.db"))).unwrap();
|
|
// Keep the tempdir alive for the duration of the process by leaking it;
|
|
// these are short-lived unit tests.
|
|
std::mem::forget(dir);
|
|
Arc::new(storage)
|
|
}
|
|
|
|
#[test]
|
|
fn test_schema_views() {
|
|
let s = schema();
|
|
let views = s["properties"]["view"]["enum"].as_array().unwrap();
|
|
assert_eq!(views.len(), 4);
|
|
assert_eq!(s["properties"]["view"]["default"], "health");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_default_view_is_health() {
|
|
let storage = test_storage();
|
|
let cognitive = Arc::new(Mutex::new(CognitiveEngine::new()));
|
|
let oc = OutputConfig::default();
|
|
// No args → health view → must match system_status output exactly.
|
|
let unified = execute(&storage, &cognitive, &oc, None).await.unwrap();
|
|
let direct = super::super::maintenance::execute_system_status(&storage, &cognitive, None)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(
|
|
unified, direct,
|
|
"memory_status view=health must equal system_status byte-for-byte"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_all_views_resolve() {
|
|
let storage = test_storage();
|
|
let cognitive = Arc::new(Mutex::new(CognitiveEngine::new()));
|
|
let oc = OutputConfig::default();
|
|
for view in ["health", "retention", "timeline", "changelog"] {
|
|
let args = Some(serde_json::json!({ "view": view }));
|
|
let r = execute(&storage, &cognitive, &oc, args).await;
|
|
assert!(r.is_ok(), "view={view} should resolve, got {r:?}");
|
|
}
|
|
}
|
|
}
|