dhive-core 0.1.0

D-HIVE Trust Protocol — Rust core: BM25 search, LCS similarity, canonicalHash, pack format, curation scoring
Documentation
//! N-API bindings — 将 Rust 内核函数暴露给 Node.js

use napi_derive::napi;
use serde_json::json;

use crate::search::{tokenize as rs_tokenize, Bm25Index};
use crate::similarity::{lcs_similarity as rs_lcs, canonical_hash as rs_canonical};
use crate::curation::{calculate_score as rs_score, assess_text_quality as rs_quality};
use crate::pack::{validate_pack, HivePack};
use crate::memory::Mem;

// ---- Search ----

#[napi]
pub fn tokenize(text: String) -> Vec<String> {
    rs_tokenize(&text)
}

#[napi]
pub fn lcs_similarity(a: String, b: String) -> f64 {
    rs_lcs(&a, &b)
}

#[napi]
pub fn canonical_hash(content: String) -> String {
    rs_canonical(&content)
}

// ---- Curation ----

#[napi]
pub fn assess_text_quality(content: String) -> f64 {
    rs_quality(&content)
}

#[napi]
pub fn calculate_curation_score(params_json: String) -> napi::Result<serde_json::Value> {
    let params: serde_json::Value = serde_json::from_str(&params_json)
        .map_err(|e| napi::Error::from_reason(format!("Invalid JSON: {}", e)))?;

    let weight = params["weight"].as_f64().unwrap_or(0.5);
    let confidence_factor = params["confidenceFactor"].as_f64().unwrap_or(0.4);
    let days_since_update = params["daysSinceUpdate"].as_u64().unwrap_or(0) as u32;
    let loaded_count = params["loadedCount"].as_u64().unwrap_or(0) as u32;
    let referenced_count = params["referencedCount"].as_u64().unwrap_or(0) as u32;
    let dispute_decay = params["disputeDecay"].as_f64().unwrap_or(0.0);
    let level = params["level"].as_str().unwrap_or("fact");

    let freshness = (1.0 - days_since_update as f64 / 180.0).max(0.0);
    let text_quality = rs_quality(params["content"].as_str().unwrap_or(""));
    let usage_bonus = {
        let loaded = loaded_count as f64;
        let referenced = referenced_count as f64;
        (loaded * 0.01 + referenced * 0.02).min(0.5)
    };
    let level_bonus = match level {
        "cornerstone" => 0.2,
        "fact" => 0.0,
        _ => -0.1,
    };

    let score = weight * confidence_factor * freshness * text_quality * (1.0 + usage_bonus)
        - dispute_decay + level_bonus;

    Ok(json!({
        "score": score,
        "textQuality": text_quality,
        "freshness": freshness,
        "usageBonus": usage_bonus,
    }))
}

// ---- Pack ----

#[napi]
pub fn validate_pack_json(json_str: String) -> napi::Result<serde_json::Value> {
    let pack: HivePack = serde_json::from_str(&json_str)
        .map_err(|e| napi::Error::from_reason(format!("Invalid pack JSON: {}", e)))?;

    let result = validate_pack(&pack);
    Ok(json!({
        "valid": result.valid,
        "errors": result.errors,
        "warnings": result.warnings,
        "memoryCount": result.memory_count,
    }))
}

// ---- BM25 Search ----

#[napi]
pub fn bm25_search_json(query: String, memories_json: String) -> napi::Result<serde_json::Value> {
    use crate::search::bm25_search;

    let raw_mems: Vec<serde_json::Value> = serde_json::from_str(&memories_json)
        .map_err(|e| napi::Error::from_reason(format!("Invalid memories JSON: {}", e)))?;

    let mems: Vec<Mem> = raw_mems.iter().map(|v| {
        // Minimal Mem reconstruction for search purposes
        Mem {
            id: v["id"].as_str().unwrap_or("?").to_string(),
            level: crate::memory::MemLevel::Fact,
            zone: crate::memory::MemoryZone::General,
            content: v["content"].as_str().unwrap_or("").to_string(),
            weight: v["weight"].as_f64().unwrap_or(0.5),
            tags: vec![],
            source: None,
            metadata: None,
            last_matched: None,
            match_count: 0,
            loaded_count: v["loadedCount"].as_u64().unwrap_or(0) as u32,
            referenced_count: v["referencedCount"].as_u64().unwrap_or(0) as u32,
            last_loaded: None,
            last_referenced: None,
            supersedes: None,
            superseded_at: None,
            superseded_by: None,
            trust: None,
            created_at: "".to_string(),
            updated_at: "".to_string(),
        }
    }).collect();

    let mem_refs: Vec<&Mem> = mems.iter().collect();
    let index = Bm25Index::build(&mem_refs);
    let results = bm25_search(&query, &index, &mem_refs);

    let hits: Vec<serde_json::Value> = results.iter().map(|r| {
        let mem = &mems.iter().find(|m| m.id == r.id).unwrap();
        json!({
            "id": r.id,
            "score": r.score,
            "content": mem.content,
        })
    }).collect();

    Ok(json!({ "hits": hits, "total": hits.len() }))
}