gobby-wiki 0.7.0

Gobby wiki CLI shell
use gobby_core::ai_context::AiConfigSource;
use gobby_core::config::FalkorConfig;
use gobby_core::gobby_home;

use crate::graph::context::{GraphContextOptions, build_context_pack};
use crate::search::SearchScope;
use crate::support::config::shared_code_graph_limits_from_conn;
use crate::support::env::database_url_for;
use crate::support::scope::resolve_selection_context;
use crate::support::search::PostgresConfigSource;
use crate::{CommandOutcome, ScopeSelection, WikiError};

pub(crate) fn execute(selection: ScopeSelection) -> Result<CommandOutcome, WikiError> {
    let resolved = resolve_selection_context(&selection)?;
    let database_url =
        database_url_for("gwiki graph-context")?.ok_or_else(|| WikiError::Config {
            detail: "gwiki graph-context requires PostgreSQL index configuration".to_string(),
        })?;
    let mut conn = gobby_core::postgres::connect_readonly(&database_url).map_err(|error| {
        WikiError::Config {
            detail: format!("failed to connect to PostgreSQL for gwiki graph-context: {error}"),
        }
    })?;

    let falkor = optional_falkor_config(&mut conn)?;
    let mut degraded_sources = Vec::new();
    let mut truncated_components = Vec::new();
    let limits = shared_code_graph_limits_from_conn(&mut conn)?;
    let mut facts = crate::falkor_graph::load_wiki_graph_facts(&mut conn, &resolved.search_scope)?;
    match (falkor, &resolved.search_scope) {
        (Some(falkor), SearchScope::Project { project_id }) => {
            match crate::falkor_graph::load_code_graph_edges(
                &falkor,
                project_id,
                &facts.documents,
                limits,
            ) {
                Ok(code_graph) => {
                    if code_graph.truncation.is_truncated() {
                        degraded_sources.push(
                            crate::falkor_graph::SHARED_CODE_GRAPH_TRUNCATED_SOURCE.to_string(),
                        );
                        truncated_components.extend(code_graph.truncation.components);
                    }
                    facts.code_edges = code_graph.edges;
                }
                Err(error) => {
                    log::warn!("failed to load shared code graph for gwiki graph-context: {error}");
                    degraded_sources.push("shared_code_graph_unavailable".to_string());
                }
            }
        }
        (Some(_), SearchScope::Global | SearchScope::Topic { .. }) => {
            degraded_sources.push("shared_code_graph_unavailable".to_string());
        }
        (None, _) => {
            degraded_sources.push("falkordb_unavailable".to_string());
            degraded_sources.push("shared_code_graph_unavailable".to_string());
        }
    }
    let options = if degraded_sources.is_empty() {
        GraphContextOptions::available()
    } else {
        GraphContextOptions::degraded(degraded_sources)
    }
    .with_truncated_components(truncated_components);
    let pack = build_context_pack(&facts, options);
    let text = format!(
        "Built graph context pack\nScope: {}\nNeighborhoods: {}\nWarnings: {}",
        resolved.output_scope,
        pack.neighborhoods.len(),
        pack.warnings.len()
    );

    Ok(super::scoped_outcome(
        "graph-context",
        &resolved.output_scope,
        serde_json::to_value(pack).map_err(|error| WikiError::Config {
            detail: format!("failed to serialize graph context pack: {error}"),
        })?,
        text,
    ))
}

fn optional_falkor_config(conn: &mut postgres::Client) -> Result<Option<FalkorConfig>, WikiError> {
    let gobby_home = gobby_home().map_err(|error| WikiError::Config {
        detail: format!("failed to resolve Gobby home for gwiki graph-context: {error}"),
    })?;
    let primary = PostgresConfigSource { conn };
    let mut source =
        AiConfigSource::with_primary_from_gobby_home(primary, &gobby_home).map_err(|error| {
            WikiError::Config {
                detail: format!("failed to resolve optional graph-context config: {error}"),
            }
        })?;

    Ok(gobby_core::config::resolve_falkordb_config(&mut source))
}