//! System prompt snapshot builder.
//!
//! Extracts and decomposes a session's effective system prompt into
//! structured sections (workspace, instruction, env, skill, tool guide,
//! external memory, task list) for observability and diagnostics.

use bamboo_agent_core::{parse_prompt_external_memory_sections, Session};
use bamboo_domain::types::PromptSnapshot;
use bamboo_engine::runner::read_prompt_snapshot;

// ---------------------------------------------------------------------------
// Markers
// ---------------------------------------------------------------------------

const SKILL_CONTEXT_START_MARKER: &str = "<!-- BAMBOO_SKILL_CONTEXT_START -->";
const SKILL_CONTEXT_END_MARKER: &str = "<!-- BAMBOO_SKILL_CONTEXT_END -->";
const TOOL_GUIDE_START_MARKER: &str = "<!-- BAMBOO_TOOL_GUIDE_START -->";
const TOOL_GUIDE_END_MARKER: &str = "<!-- BAMBOO_TOOL_GUIDE_END -->";
const EXTERNAL_MEMORY_START_MARKER: &str = "<!-- BAMBOO_EXTERNAL_MEMORY_START -->";
const EXTERNAL_MEMORY_END_MARKER: &str = "<!-- BAMBOO_EXTERNAL_MEMORY_END -->";
const TASK_LIST_START_MARKER: &str = "<!-- BAMBOO_TASK_LIST_START -->";
const TASK_LIST_END_MARKER: &str = "<!-- BAMBOO_TASK_LIST_END -->";
const LEGACY_TODO_LIST_START_MARKER: &str = "<!-- BAMBOO_TODO_LIST_START -->";
const LEGACY_TODO_LIST_END_MARKER: &str = "<!-- BAMBOO_TODO_LIST_END -->";

const WORKSPACE_CONTEXT_START_MARKER: &str = bamboo_engine::context::WORKSPACE_CONTEXT_START_MARKER;
const WORKSPACE_CONTEXT_END_MARKER: &str = bamboo_engine::context::WORKSPACE_CONTEXT_END_MARKER;
const WORKSPACE_CONTEXT_PREFIX: &str = bamboo_engine::context::WORKSPACE_CONTEXT_PREFIX;

const INSTRUCTION_CONTEXT_START_MARKER: &str =
    bamboo_engine::context::instruction::INSTRUCTION_CONTEXT_START_MARKER;
const INSTRUCTION_CONTEXT_END_MARKER: &str =
    bamboo_engine::context::instruction::INSTRUCTION_CONTEXT_END_MARKER;

const ENV_CONTEXT_START_MARKER: &str = bamboo_engine::context::ENV_CONTEXT_START_MARKER;
const ENV_CONTEXT_END_MARKER: &str = bamboo_engine::context::ENV_CONTEXT_END_MARKER;

// ---------------------------------------------------------------------------
// Public builder
// ---------------------------------------------------------------------------

/// Build a structured snapshot of a session's system prompt.
///
/// If the session carries a `runtime_prompt_snapshot` metadata entry (written
/// by the runtime during chat), that snapshot is returned directly. Otherwise
/// the prompt is decomposed from the raw system message using marker-based
/// extraction.
///
/// `default_prompt` is the global fallback used when the session has no system
/// message and no `base_system_prompt` metadata. The caller typically reads it
/// from the filesystem.
pub fn build_system_prompt_snapshot(session: &Session, default_prompt: &str) -> PromptSnapshot {
    if let Some(snapshot) = read_prompt_snapshot(session) {
        return snapshot;
    }

    let effective_system_prompt = resolve_effective_system_prompt(session, default_prompt);
    let skill_context = extract_wrapped_section(
        &effective_system_prompt,
        SKILL_CONTEXT_START_MARKER,
        SKILL_CONTEXT_END_MARKER,
    );
    let tool_guide_context = extract_wrapped_section(
        &effective_system_prompt,
        TOOL_GUIDE_START_MARKER,
        TOOL_GUIDE_END_MARKER,
    );
    let external_memory = extract_wrapped_section(
        &effective_system_prompt,
        EXTERNAL_MEMORY_START_MARKER,
        EXTERNAL_MEMORY_END_MARKER,
    );
    let external_memory_parts = parse_prompt_external_memory_sections(external_memory.as_deref());
    let task_list = extract_wrapped_section(
        &effective_system_prompt,
        TASK_LIST_START_MARKER,
        TASK_LIST_END_MARKER,
    )
    .or_else(|| {
        extract_wrapped_section(
            &effective_system_prompt,
            LEGACY_TODO_LIST_START_MARKER,
            LEGACY_TODO_LIST_END_MARKER,
        )
    });

    let prompt_without_generated_sections = strip_generated_sections(&effective_system_prompt);
    let (prompt_without_workspace, workspace_from_prompt) =
        split_workspace_context(&prompt_without_generated_sections);
    let (prompt_without_instruction, instruction_from_prompt) =
        split_instruction_context(&prompt_without_workspace);
    let (prompt_without_env, env_context) = split_env_context(&prompt_without_instruction);

    let base_system_prompt = metadata_value(session, "base_system_prompt").unwrap_or_else(|| {
        let derived = prompt_without_env.trim();
        if derived.is_empty() {
            default_prompt.to_string()
        } else {
            derived.to_string()
        }
    });

    let enhancement_prompt = metadata_value(session, "enhance_prompt")
        .or_else(|| derive_enhancement_prompt_legacy(&base_system_prompt, &prompt_without_env));

    let workspace_context = metadata_value(session, "workspace_path")
        .as_deref()
        .and_then(bamboo_engine::context::build_workspace_prompt_context)
        .or(workspace_from_prompt);

    let instruction_context = metadata_value(session, "workspace_path")
        .as_deref()
        .and_then(|workspace_path| {
            bamboo_engine::context::instruction::build_instruction_prompt_context(workspace_path)
        })
        .or(instruction_from_prompt);

    PromptSnapshot {
        base_system_prompt,
        enhancement_prompt,
        workspace_context,
        instruction_context,
        env_context,
        skill_context,
        tool_guide_context,
        dream_notebook: external_memory_parts.dream_notebook,
        session_memory_note: external_memory_parts.session_memory_note,
        project_memory_index: external_memory_parts.project_memory_index,
        relevant_durable_memories: external_memory_parts.relevant_durable_memories,
        project_dream: external_memory_parts.project_dream,
        global_dream_fallback: external_memory_parts.global_dream_fallback,
        prompt_memory_observability: None,
        external_memory,
        task_list,
        effective_system_prompt,
    }
}

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------

fn resolve_effective_system_prompt(session: &Session, default_prompt: &str) -> String {
    use bamboo_agent_core::Role;

    let system_message = session
        .messages
        .iter()
        .find(|message| matches!(message.role, Role::System))
        .map(|message| message.content.trim().to_string())
        .filter(|content| !content.is_empty());

    system_message
        .or_else(|| metadata_value(session, "base_system_prompt"))
        .unwrap_or_else(|| default_prompt.to_string())
}

fn metadata_value(session: &Session, key: &str) -> Option<String> {
    session
        .metadata
        .get(key)
        .map(String::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(ToString::to_string)
}

fn extract_wrapped_section(prompt: &str, start_marker: &str, end_marker: &str) -> Option<String> {
    let start_idx = prompt.find(start_marker)?;
    let section_start = start_idx + start_marker.len();
    let end_rel_idx = prompt[section_start..].find(end_marker)?;
    let end_idx = section_start + end_rel_idx;
    let section = prompt[section_start..end_idx].trim();
    if section.is_empty() {
        None
    } else {
        Some(section.to_string())
    }
}

fn strip_wrapped_sections(prompt: &str, start_marker: &str, end_marker: &str) -> String {
    let mut current = prompt.to_string();

    loop {
        let Some(start_idx) = current.find(start_marker) else {
            break;
        };
        let search_from = start_idx + start_marker.len();
        let Some(end_rel_idx) = current[search_from..].find(end_marker) else {
            break;
        };
        let end_idx = search_from + end_rel_idx + end_marker.len();

        let before = current[..start_idx].trim_end();
        let after = current[end_idx..].trim_start();
        current = match (before.is_empty(), after.is_empty()) {
            (true, true) => String::new(),
            (true, false) => after.to_string(),
            (false, true) => before.to_string(),
            (false, false) => format!("{before}\n\n{after}"),
        };
    }

    current
}

fn strip_generated_sections(prompt: &str) -> String {
    let prompt = strip_wrapped_sections(
        prompt,
        EXTERNAL_MEMORY_START_MARKER,
        EXTERNAL_MEMORY_END_MARKER,
    );
    let prompt = strip_wrapped_sections(&prompt, TASK_LIST_START_MARKER, TASK_LIST_END_MARKER);
    let prompt = strip_wrapped_sections(
        &prompt,
        LEGACY_TODO_LIST_START_MARKER,
        LEGACY_TODO_LIST_END_MARKER,
    );
    let prompt = strip_wrapped_sections(
        &prompt,
        SKILL_CONTEXT_START_MARKER,
        SKILL_CONTEXT_END_MARKER,
    );
    let prompt = strip_wrapped_sections(&prompt, TOOL_GUIDE_START_MARKER, TOOL_GUIDE_END_MARKER);
    strip_wrapped_sections(
        &prompt,
        INSTRUCTION_CONTEXT_START_MARKER,
        INSTRUCTION_CONTEXT_END_MARKER,
    )
}

fn split_workspace_context(prompt: &str) -> (String, Option<String>) {
    let marker_workspace = extract_wrapped_section(
        prompt,
        WORKSPACE_CONTEXT_START_MARKER,
        WORKSPACE_CONTEXT_END_MARKER,
    );
    if marker_workspace.is_some() {
        let stripped = strip_wrapped_sections(
            prompt,
            WORKSPACE_CONTEXT_START_MARKER,
            WORKSPACE_CONTEXT_END_MARKER,
        );
        return (stripped.trim().to_string(), marker_workspace);
    }
    split_legacy_workspace_context(prompt)
}

fn split_instruction_context(prompt: &str) -> (String, Option<String>) {
    let instruction_context = extract_wrapped_section(
        prompt,
        INSTRUCTION_CONTEXT_START_MARKER,
        INSTRUCTION_CONTEXT_END_MARKER,
    );
    if instruction_context.is_some() {
        let stripped = strip_wrapped_sections(
            prompt,
            INSTRUCTION_CONTEXT_START_MARKER,
            INSTRUCTION_CONTEXT_END_MARKER,
        );
        return (stripped.trim().to_string(), instruction_context);
    }
    (prompt.trim().to_string(), None)
}

fn split_env_context(prompt: &str) -> (String, Option<String>) {
    let env_context =
        extract_wrapped_section(prompt, ENV_CONTEXT_START_MARKER, ENV_CONTEXT_END_MARKER);
    if env_context.is_some() {
        let stripped =
            strip_wrapped_sections(prompt, ENV_CONTEXT_START_MARKER, ENV_CONTEXT_END_MARKER);
        return (stripped.trim().to_string(), env_context);
    }
    (prompt.trim().to_string(), None)
}

fn split_legacy_workspace_context(prompt: &str) -> (String, Option<String>) {
    let Some(start_idx) = prompt.find(WORKSPACE_CONTEXT_PREFIX) else {
        return (prompt.trim().to_string(), None);
    };
    let guidance = bamboo_engine::context::workspace_prompt_guidance();
    let end_idx = if let Some(guidance_rel_idx) = prompt[start_idx..].find(&guidance) {
        start_idx + guidance_rel_idx + guidance.len()
    } else {
        prompt.len()
    };

    let workspace_context = prompt[start_idx..end_idx].trim().to_string();
    let before = prompt[..start_idx].trim_end();
    let after = prompt[end_idx..].trim_start();
    let stripped = match (before.is_empty(), after.is_empty()) {
        (true, true) => String::new(),
        (true, false) => after.to_string(),
        (false, true) => before.to_string(),
        (false, false) => format!("{before}\n\n{after}"),
    };

    let workspace_context = if workspace_context.is_empty() {
        None
    } else {
        Some(workspace_context)
    };

    (stripped, workspace_context)
}

fn derive_enhancement_prompt_legacy(
    base_system_prompt: &str,
    prompt_without_workspace: &str,
) -> Option<String> {
    let base = base_system_prompt.trim();
    let prompt = prompt_without_workspace.trim();

    if base.is_empty() || prompt.is_empty() || prompt == base {
        return None;
    }

    if !prompt.starts_with(base) {
        return None;
    }

    let enhancement = prompt[base.len()..].trim();
    if enhancement.is_empty() {
        None
    } else {
        Some(enhancement.to_string())
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::build_system_prompt_snapshot;
    use bamboo_agent_core::{Message, Session};
    use bamboo_domain::types::PromptMemoryObservability;

    const TEST_DEFAULT_PROMPT: &str = "You are a helpful assistant.";

    #[test]
    fn snapshot_extracts_dream_notebook_and_session_memory_note_from_external_memory() {
        let mut session = Session::new("session-memory-split", "gpt-5");
        session.add_message(Message::system(
            "Base prompt\n\n<!-- BAMBOO_EXTERNAL_MEMORY_START -->\n## External Memory (Persistent)\n\n### Cross-session Dream Notebook (read-only)\n````md\nDream note content\n````\n\n### Session Memory Note (markdown)\n````md\nSession note content\n````\n<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
        ));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(
            snapshot.dream_notebook.as_deref(),
            Some("Dream note content")
        );
        assert_eq!(
            snapshot.session_memory_note.as_deref(),
            Some("Session note content")
        );
        assert!(snapshot
            .external_memory
            .as_deref()
            .is_some_and(|value| value.contains("Dream note content")));
    }

    #[test]
    fn snapshot_extracts_project_dream_heading_from_external_memory() {
        let mut session = Session::new("session-memory-project-dream", "gpt-5");
        session.add_message(Message::system(
            "Base prompt\n\n<!-- BAMBOO_EXTERNAL_MEMORY_START -->\n## External Memory (Persistent)\n\n### Project Dream Summary\n````md\nProject dream content\n````\n\n### Session Memory Note (markdown)\n````md\nSession note content\n````\n<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
        ));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(
            snapshot.dream_notebook.as_deref(),
            Some("Project dream content")
        );
        assert_eq!(
            snapshot.session_memory_note.as_deref(),
            Some("Session note content")
        );
    }

    #[test]
    fn snapshot_extracts_global_dream_fallback_heading_from_external_memory() {
        let mut session = Session::new("session-memory-fallback-dream", "gpt-5");
        session.add_message(Message::system(
            "Base prompt\n\n<!-- BAMBOO_EXTERNAL_MEMORY_START -->\n## External Memory (Persistent)\n\n### Global Dream Summary (fallback)\n````md\nDream fallback content\n````\n\n### Session Memory Note (markdown)\n````md\nSession note content\n````\n<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
        ));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(
            snapshot.dream_notebook.as_deref(),
            Some("Dream fallback content")
        );
        assert_eq!(
            snapshot.session_memory_note.as_deref(),
            Some("Session note content")
        );
    }

    #[test]
    fn snapshot_extracts_multi_topic_session_memory_note_from_external_memory() {
        let mut session = Session::new("session-memory-topics", "gpt-5");
        session.add_message(Message::system(
            "Base prompt\n\n<!-- BAMBOO_EXTERNAL_MEMORY_START -->\n## External Memory (Persistent)\n\n### Cross-session Dream Notebook (read-only)\n````md\nDream note content\n````\n\n### Session Memory Topic: `backend-api`\n````md\n/users and /orders finalized\n````\n\n### Session Memory Topic: `ui-copy`\n````md\nCTA wording approved\n````\n<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
        ));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(
            snapshot.dream_notebook.as_deref(),
            Some("Dream note content")
        );
        let merged = snapshot
            .session_memory_note
            .as_deref()
            .expect("session memory note should be merged from topic blocks");
        assert!(merged.contains("### Session Memory Topic: `backend-api`"));
        assert!(merged.contains("/users and /orders finalized"));
        assert!(merged.contains("### Session Memory Topic: `ui-copy`"));
        assert!(merged.contains("CTA wording approved"));
    }

    #[test]
    fn snapshot_preserves_truncated_topic_placeholder_in_multi_topic_memory_note() {
        let mut session = Session::new("session-memory-truncated-topic", "gpt-5");
        session.add_message(Message::system(
            "Base prompt\n\n<!-- BAMBOO_EXTERNAL_MEMORY_START -->\n## External Memory (Persistent)\n\n### Session Memory Topic: `backend-api`\n````md\n_(truncated — use action=read topic=backend-api to view)_\n````\n\n### Session Memory Topic: `ui-copy`\n````md\nCTA wording approved\n````\n<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
        ));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        let merged = snapshot
            .session_memory_note
            .as_deref()
            .expect("session memory note should be merged from topic blocks");
        assert!(merged.contains("### Session Memory Topic: `backend-api`"));
        assert!(merged.contains("_(truncated — use action=read topic=backend-api to view)_"));
        assert!(merged.contains("### Session Memory Topic: `ui-copy`"));
        assert!(merged.contains("CTA wording approved"));
    }

    #[test]
    fn snapshot_extracts_fine_grained_external_memory_sections() {
        let mut session = Session::new("session-memory-fine-grained", "gpt-5");
        session.add_message(Message::system(
            "Base prompt\n\n<!-- BAMBOO_EXTERNAL_MEMORY_START -->\n## External Memory (Persistent)\n\n### Relevant Durable Memories\nTurn-specific historical memories shortlisted for the latest user request.\n- [active][project] Release rule\n  Summary: Use the release checklist.\n\n### Project Durable Memory Index\n````md\n# Bamboo Memory Index\n- memory entry\n````\n\n### Project Dream Summary\n````md\nProject dream content\n````\n\n### Session Memory Note (markdown)\n````md\nSession note content\n````\n<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
        ));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert!(snapshot
            .relevant_durable_memories
            .as_deref()
            .is_some_and(|value| value.contains("Release rule")));
        assert_eq!(
            snapshot.project_memory_index.as_deref(),
            Some("# Bamboo Memory Index\n- memory entry")
        );
        assert_eq!(
            snapshot.project_dream.as_deref(),
            Some("Project dream content")
        );
        assert!(snapshot.global_dream_fallback.is_none());
        assert_eq!(
            snapshot.dream_notebook.as_deref(),
            Some("Project dream content")
        );
    }

    #[test]
    fn snapshot_extracts_workspace_from_legacy_unwrapped_context() {
        let mut session = Session::new("session-legacy", "gpt-5");
        let guidance = bamboo_engine::context::workspace_prompt_guidance();
        session.add_message(Message::system(format!(
            "Base prompt\n\nExtra guidance\n\nWorkspace path: /tmp/legacy-workspace\n{guidance}\n\n<!-- BAMBOO_SKILL_CONTEXT_START -->\n## Skill System\n\nSkill details\n<!-- BAMBOO_SKILL_CONTEXT_END -->"
        )));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(snapshot.base_system_prompt, "Base prompt\n\nExtra guidance");
        assert!(snapshot.enhancement_prompt.is_none());
        assert!(snapshot
            .workspace_context
            .as_deref()
            .is_some_and(|value| value.contains("/tmp/legacy-workspace")));
    }

    #[test]
    fn snapshot_uses_default_prompt_when_session_has_no_system_message() {
        let session = Session::new("session-1", "gpt-5");
        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);

        assert_eq!(snapshot.base_system_prompt, TEST_DEFAULT_PROMPT);
        assert_eq!(snapshot.effective_system_prompt, TEST_DEFAULT_PROMPT);
        assert!(snapshot.enhancement_prompt.is_none());
    }

    #[test]
    fn snapshot_derives_enhancement_when_metadata_is_missing() {
        let mut session = Session::new("session-1", "gpt-5");
        session
            .metadata
            .insert("base_system_prompt".to_string(), "Base prompt".to_string());
        session.add_message(Message::system("Base prompt\n\nDerived enhancement"));

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(
            snapshot.enhancement_prompt.as_deref(),
            Some("Derived enhancement")
        );
    }

    #[test]
    fn snapshot_prefers_shared_prompt_snapshot_metadata_when_present() {
        let mut session = Session::new("session-shared", "gpt-5");
        session.add_message(Message::system("legacy system prompt"));
        session.metadata.insert(
            "runtime_prompt_snapshot".to_string(),
            serde_json::to_string(&bamboo_engine::runner::PromptSnapshot {
                base_system_prompt: "Shared base".to_string(),
                enhancement_prompt: Some("Shared enhancement".to_string()),
                workspace_context: Some("Shared workspace".to_string()),
                instruction_context: Some("Shared instruction".to_string()),
                env_context: Some("Shared env".to_string()),
                skill_context: Some("Shared skill".to_string()),
                tool_guide_context: Some("Shared tool guide".to_string()),
                dream_notebook: Some("Dream block".to_string()),
                session_memory_note: Some("Session note block".to_string()),
                project_memory_index: Some("Shared project index".to_string()),
                relevant_durable_memories: Some("Shared relevant memories".to_string()),
                project_dream: Some("Shared project dream".to_string()),
                global_dream_fallback: Some("Shared global fallback".to_string()),
                prompt_memory_observability: Some(PromptMemoryObservability {
                    project_prompt_injection_enabled: true,
                    relevant_recall_enabled: true,
                    relevant_recall_rerank_enabled: false,
                    project_first_dream_enabled: true,
                    latest_user_query_present: true,
                    resolved_project_key: Some("shared-project".to_string()),
                    session_notes_status: "loaded".to_string(),
                    project_memory_index_status: "loaded".to_string(),
                    relevant_memory_status: "lexical".to_string(),
                    project_dream_status: "loaded".to_string(),
                    global_dream_fallback_status: "skipped_project_memory_or_dream_present"
                        .to_string(),
                    dream_source: "project".to_string(),
                    session_topic_count: 1,
                    truncated_session_topic_count: 0,
                    relevant_memory_count: 1,
                    session_note_section_chars: 10,
                    project_memory_index_section_chars: 20,
                    relevant_memory_section_chars: 30,
                    project_dream_section_chars: 40,
                    global_dream_fallback_section_chars: 0,
                    context_pressure_warning_chars: 0,
                    external_memory_section_chars: 120,
                }),
                external_memory: Some("Shared memory".to_string()),
                task_list: Some("Shared task list".to_string()),
                effective_system_prompt: "Shared effective prompt".to_string(),
            })
            .expect("snapshot should serialize"),
        );

        let snapshot = build_system_prompt_snapshot(&session, TEST_DEFAULT_PROMPT);
        assert_eq!(snapshot.base_system_prompt, "Shared base");
        assert_eq!(
            snapshot.enhancement_prompt.as_deref(),
            Some("Shared enhancement")
        );
        assert_eq!(
            snapshot.workspace_context.as_deref(),
            Some("Shared workspace")
        );
        assert_eq!(
            snapshot.project_memory_index.as_deref(),
            Some("Shared project index")
        );
        assert_eq!(
            snapshot.relevant_durable_memories.as_deref(),
            Some("Shared relevant memories")
        );
        assert_eq!(
            snapshot.project_dream.as_deref(),
            Some("Shared project dream")
        );
        assert_eq!(
            snapshot.global_dream_fallback.as_deref(),
            Some("Shared global fallback")
        );
        assert_eq!(
            snapshot
                .prompt_memory_observability
                .as_ref()
                .and_then(|item| item.resolved_project_key.as_deref()),
            Some("shared-project")
        );
        assert_eq!(snapshot.effective_system_prompt, "Shared effective prompt");
    }
}
