gobby-code 1.3.2

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
use std::collections::BTreeMap;
use std::path::Path;

use chrono::{SecondsFormat, Utc};

use super::{
    CodewikiTruthDigest, CodewikiTruthStackEntry, CodewikiTruthSuperseded, DocPruneScope,
    SystemModel, build::infra_descriptor, io::write_doc, structural_repo_summary,
};

pub(crate) const TRUTH_DIGEST_META_PATH: &str = "_meta/truth_digest.json";

const SCHEMA_VERSION: u8 = 1;
const STACK_AUTHORITY_COMPLETE: &str = "complete_current_set";
const STACK_AUTHORITY_PARTIAL: &str = "partial";
const MAX_STACK_ENTRIES: usize = 12;
const MAX_PULL_IN_BY: usize = 1;
const MAX_REPO_SUMMARY_CHARS: usize = 160;
const MAX_FIELD_CHARS: usize = 56;
const MAX_PATH_CHARS: usize = 96;

pub(crate) fn build_truth_digest(
    system_model: &SystemModel,
    project_id: &str,
    file_count: usize,
    module_count: usize,
) -> CodewikiTruthDigest {
    let mut stack = system_model
        .services
        .iter()
        .map(|boundary| {
            let descriptor = infra_descriptor(boundary.kind);
            CodewikiTruthStackEntry {
                service: bounded(&boundary.name, MAX_FIELD_CHARS),
                kind: boundary.kind.kind_slug().to_string(),
                adapter_module: bounded(descriptor.adapter_module, MAX_PATH_CHARS),
                pulled_in_by: boundary
                    .pulled_in_by
                    .iter()
                    .take(MAX_PULL_IN_BY)
                    .map(|source| bounded(source, MAX_FIELD_CHARS))
                    .collect(),
                summary: bounded(descriptor.summary, MAX_FIELD_CHARS),
                degradation: bounded(descriptor.degradation, MAX_FIELD_CHARS),
            }
        })
        .collect::<Vec<_>>();
    stack.sort_by(|a, b| a.service.cmp(&b.service));
    stack.truncate(MAX_STACK_ENTRIES);

    let key_paths = stack
        .iter()
        .map(|entry| (entry.service.clone(), entry.adapter_module.clone()))
        .collect::<BTreeMap<_, _>>();
    let stack_authority = if stack.is_empty() || !system_model.notes.is_empty() {
        STACK_AUTHORITY_PARTIAL
    } else {
        STACK_AUTHORITY_COMPLETE
    };

    CodewikiTruthDigest {
        schema_version: SCHEMA_VERSION,
        generated_at: Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true),
        project_id: project_id.to_string(),
        repo_summary: bounded(
            &structural_repo_summary(file_count, module_count),
            MAX_REPO_SUMMARY_CHARS,
        ),
        stack_authority: stack_authority.to_string(),
        stack,
        key_paths,
        superseded: Vec::<CodewikiTruthSuperseded>::new(),
    }
}

pub(crate) fn write_truth_digest(
    out_dir: &Path,
    doc_scope: &DocPruneScope,
    digest: &CodewikiTruthDigest,
) -> anyhow::Result<()> {
    if !doc_scope.is_unscoped() {
        return Ok(());
    }
    let content = serde_json::to_string(digest)?;
    write_doc(out_dir, TRUTH_DIGEST_META_PATH, &(content + "\n"))
}

fn bounded(value: &str, max_chars: usize) -> String {
    let value = value.trim();
    if value.chars().count() <= max_chars {
        return value.to_string();
    }
    let keep = max_chars.saturating_sub(3);
    let mut truncated = value.chars().take(keep).collect::<String>();
    truncated.push_str("...");
    truncated
}