palace-rs 0.20.0

Local memory palace for AI assistants — Rust edition
Documentation
use palace::normalize::normalize_content;

#[test]
fn passthrough_when_already_has_markers() {
    let text = "> Hello\nWorld\n> Goodbye\nBye\n> Again\nYes";
    let result = normalize_content(text, "txt");
    assert_eq!(result, text);
}

#[test]
fn empty_content_passes_through() {
    let result = normalize_content("   ", "txt");
    assert_eq!(result, "   ");
}

#[test]
fn claude_ai_json_flat_list() {
    let json = serde_json::json!([
        {"role": "user", "content": "What is Rust?"},
        {"role": "assistant", "content": "Rust is a systems programming language."},
        {"role": "user", "content": "Is it fast?"},
        {"role": "assistant", "content": "Yes, very fast."},
    ])
    .to_string();
    let result = normalize_content(&json, "json");
    assert!(result.contains("> What is Rust?"), "result: {result}");
    assert!(
        result.contains("Rust is a systems programming language."),
        "result: {result}"
    );
}

#[test]
fn claude_code_jsonl() {
    let jsonl = r#"{"type":"human","message":{"content":"Hello claude"}}
{"type":"assistant","message":{"content":"Hi there!"}}
{"type":"human","message":{"content":"How are you?"}}
{"type":"assistant","message":{"content":"Doing well."}}"#;
    let result = normalize_content(jsonl, "jsonl");
    assert!(result.contains("> Hello claude"), "result: {result}");
    assert!(result.contains("Hi there!"), "result: {result}");
}

#[test]
fn codex_jsonl_session() {
    let jsonl = r#"{"type":"session_meta","session_id":"abc"}
{"type":"event_msg","payload":{"type":"user_message","message":"What is 2+2?"}}
{"type":"event_msg","payload":{"type":"agent_message","message":"It is 4."}}
{"type":"event_msg","payload":{"type":"user_message","message":"Thanks!"}}
{"type":"event_msg","payload":{"type":"agent_message","message":"No problem."}}"#;
    let result = normalize_content(jsonl, "jsonl");
    assert!(result.contains("> What is 2+2?"), "result: {result}");
    assert!(result.contains("It is 4."), "result: {result}");
}

#[test]
fn slack_json() {
    let json = serde_json::json!([
        {"type": "message", "user": "U001", "text": "Hey!"},
        {"type": "message", "user": "U002", "text": "Hey back!"},
        {"type": "message", "user": "U001", "text": "How are you?"},
        {"type": "message", "user": "U002", "text": "Great thanks."},
    ])
    .to_string();
    let result = normalize_content(&json, "json");
    assert!(result.contains("> Hey!"), "result: {result}");
}

#[test]
fn plain_text_passes_through() {
    let text = "Just a regular file with no special format.";
    let result = normalize_content(text, "txt");
    assert_eq!(result, text);
}

fn claude_code_jsonl_from(messages: &[(&str, &str)]) -> String {
    messages
        .iter()
        .map(|(kind, text)| {
            serde_json::json!({"type": kind, "message": {"content": text}}).to_string()
        })
        .collect::<Vec<_>>()
        .join("\n")
}

#[test]
fn quote_in_assistant_reply_is_not_a_user_turn() {
    let jsonl = claude_code_jsonl_from(&[
        ("human", "How do I configure the cache?"),
        (
            "assistant",
            "Set the TTL in config.toml:\n> Note: values are in seconds, not minutes.\nThen restart the server so it picks up the change.",
        ),
        ("human", "Where do the cache files live?"),
        ("assistant", "Under the data directory, in a folder named cache."),
        ("human", "Can I clear it safely?"),
        ("assistant", "Yes, deleting the folder only costs a slower first request."),
    ]);

    let transcript = normalize_content(&jsonl, "jsonl");
    let chunks = palace::convo_miner::chunk_exchanges(&transcript);

    assert_eq!(chunks.len(), 3, "one chunk per real exchange: {chunks:#?}");
    let cache = &chunks[0].0;
    assert!(
        cache.starts_with("> How do I configure the cache?"),
        "{cache}"
    );
    assert!(cache.contains("values are in seconds"), "{cache}");
    assert!(cache.contains("Then restart the server"), "{cache}");
}

#[test]
fn quote_on_a_later_line_of_a_user_message_is_not_a_new_turn() {
    let jsonl = claude_code_jsonl_from(&[
        (
            "human",
            "The deploy failed with this log:\n> error: migration 42 timed out\nWhat went wrong?",
        ),
        (
            "assistant",
            "Migration 42 holds a table lock longer than the timeout.",
        ),
        ("human", "How do I raise the timeout?"),
        (
            "assistant",
            "Set lock_timeout in the migration settings file.",
        ),
        ("human", "Will that slow other queries?"),
        (
            "assistant",
            "Only queries waiting on that table during the migration.",
        ),
    ]);

    let transcript = normalize_content(&jsonl, "jsonl");
    let chunks = palace::convo_miner::chunk_exchanges(&transcript);

    assert_eq!(chunks.len(), 3, "one chunk per real exchange: {chunks:#?}");
    let deploy = &chunks[0].0;
    assert!(deploy.starts_with("> The deploy failed"), "{deploy}");
    assert!(deploy.contains("migration 42 timed out"), "{deploy}");
    assert!(deploy.contains("holds a table lock"), "{deploy}");
}