sqlite-graphrag 1.2.5

Persistent GraphRAG memory for Claude Code, Codex, Cursor, and 27 AI agents — one self-contained ~19 MiB Rust binary, zero daemon. Never re-explain your codebase again. Hybrid retrieval (FTS5 BM25 + cosine similarity + multi-hop graph traversal) surfaces the right memory in milliseconds. Embedding and entity enrichment run as parallel REST calls against your cloud LLM — no fragile headless subprocesses, no ONNX runtime, no model downloads. Soft-delete with full version history, transactional atomic writes, BLAKE3-tracked mutations. OAuth-only: raw API keys ABORT the spawn.
Documentation
//! Keeps every shipped subcommand reachable from the operator documentation.
//!
//! A subcommand is added to `src/cli/commands.rs` and the help output grows for
//! free. The documents do not: they only grow when someone remembers. Nothing
//! coupled the two, so `docs/AGENTS.md` — the document written for the agents
//! that drive this CLI — reached v1.2.5 never naming `memory-entities` or
//! `split-body`, and `INTEGRATIONS.md` was missing thirteen of fifty.
//!
//! An undocumented command is not a cosmetic gap. The reader cannot invoke what
//! the reader cannot find, so the feature is shipped and invisible at once.
//!
//! Like `docs_consistency.rs`, this is a test rather than a CI job because this
//! project forbids CI by design; `cargo test` is the only automatic gate.

use std::collections::BTreeSet;
use std::process::Command;

/// Documents that must name every subcommand.
///
/// `CROSS_PLATFORM`, `TESTING`, `TEST_PLAN`, `MIGRATION`, `SECURITY` and
/// `DOCUMENTATION_FRAMEWORK` are deliberately absent: each covers one axis —
/// portability, test strategy, upgrade path, threat model, document structure —
/// and naming all fifty commands there would be noise, not coverage.
const INVENTORY_DOCS: [&str; 17] = [
    "README.md",
    "README.pt-BR.md",
    "docs/HOW_TO_USE.md",
    "docs/HOW_TO_USE.pt-BR.md",
    "docs/AGENTS.md",
    "docs/AGENTS.pt-BR.md",
    "docs/COOKBOOK.md",
    "docs/COOKBOOK.pt-BR.md",
    "docs/HEADLESS_INVOCATION.md",
    "docs/HEADLESS_INVOCATION.pt-BR.md",
    "INTEGRATIONS.md",
    "INTEGRATIONS.pt-BR.md",
    "llms.txt",
    "llms.pt-BR.txt",
    "llms-full.txt",
    "skills/sqlite-graphrag-en/SKILL.md",
    "skills/sqlite-graphrag-pt/SKILL.md",
];

/// Commands excluded from the inventory requirement.
///
/// `help` is generated by clap and is not a product surface.
const NOT_A_PRODUCT_SURFACE: [&str; 1] = ["help"];

/// Reads a repository file relative to the crate root.
fn read_repo_file(relative: &str) -> String {
    let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(relative);
    std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()))
}

/// Asks the binary itself which subcommands exist.
///
/// Parsing `src/cli/commands.rs` would read the enum, not the surface: clap
/// renames variants, hides some and adds aliases. The help output is what an
/// operator actually sees, so it is the honest inventory.
fn shipped_commands() -> BTreeSet<String> {
    let output = Command::new(env!("CARGO_BIN_EXE_sqlite-graphrag"))
        .arg("--help")
        .output()
        .expect("cannot run the binary to read its command inventory");
    let help = String::from_utf8_lossy(&output.stdout);

    let mut commands = BTreeSet::new();
    let mut inside = false;
    for line in help.lines() {
        if line.starts_with("Commands:") {
            inside = true;
            continue;
        }
        if inside {
            // The block ends at the first blank line or at the options header.
            if line.trim().is_empty() || line.starts_with("Options:") {
                break;
            }
            // Entries are indented; the first token is the command name.
            if !line.starts_with("  ") {
                continue;
            }
            if let Some(name) = line.split_whitespace().next() {
                if name.chars().all(|c| c.is_ascii_lowercase() || c == '-')
                    && !NOT_A_PRODUCT_SURFACE.contains(&name)
                {
                    commands.insert(name.to_string());
                }
            }
        }
    }
    commands
}

/// True when `doc` names `command` in a context an operator can copy.
///
/// Two shapes count: a real invocation (`sqlite-graphrag graph`) and a code
/// span, optionally carrying a subcommand (`` `fts` ``, `` `fts rebuild` ``).
/// A bare word does not: `export`, `list`, `read` and `related` are ordinary
/// English, and matching them by word boundary reported full coverage for a
/// document that never showed the command at all.
fn documents_command(doc: &str, command: &str) -> bool {
    let invocation = format!("sqlite-graphrag {command}");
    if doc
        .match_indices(&invocation)
        .any(|(index, _)| ends_on_boundary(doc, index + invocation.len()))
    {
        return true;
    }

    let span_start = format!("`{command}");
    doc.match_indices(&span_start).any(|(index, _)| {
        let rest = &doc[index + span_start.len()..];
        match rest.find('`') {
            // `cmd` — nothing between the name and the closing backtick.
            Some(0) => true,
            // `cmd sub` — one space plus a lowercase token.
            Some(end) => {
                let tail = &rest[..end];
                tail.starts_with(' ')
                    && tail.len() > 1
                    && tail[1..]
                        .chars()
                        .all(|c| c.is_ascii_lowercase() || c == '-' || c == ' ')
            }
            None => false,
        }
    })
}

/// True when position `at` is the end of the text or a non-name character.
fn ends_on_boundary(text: &str, at: usize) -> bool {
    text[at..]
        .chars()
        .next()
        .is_none_or(|c| !c.is_ascii_alphanumeric() && c != '-' && c != '_')
}

#[test]
fn every_shipped_command_is_named_in_every_inventory_document() {
    let commands = shipped_commands();
    assert!(
        commands.len() >= 49,
        "read only {} commands from the help block; the parser probably drifted \
         from the help layout, which would make this guard pass vacuously",
        commands.len()
    );

    for doc in INVENTORY_DOCS {
        let text = read_repo_file(doc);
        let missing: Vec<&String> = commands
            .iter()
            .filter(|c| !documents_command(&text, c))
            .collect();
        assert!(
            missing.is_empty(),
            "{doc} names {}/{} shipped commands; a reader cannot invoke what it \
             cannot find. Missing: {:?}",
            commands.len() - missing.len(),
            commands.len(),
            missing
        );
    }
}

#[test]
fn the_command_matcher_rejects_a_bare_english_word() {
    // This is the false positive that made an earlier sweep report 50/50 for a
    // document that never showed the command.
    let doc = "You can export the graph and read the list of related items.";
    assert!(!documents_command(doc, "export"));
    assert!(!documents_command(doc, "read"));
    assert!(!documents_command(doc, "list"));
    assert!(!documents_command(doc, "related"));
}

#[test]
fn the_command_matcher_accepts_an_invocation_and_a_code_span() {
    assert!(documents_command(
        "run `sqlite-graphrag export --json`",
        "export"
    ));
    assert!(documents_command("the `vec` family", "vec"));
    assert!(documents_command("call `fts rebuild` first", "fts"));
}

#[test]
fn the_command_matcher_rejects_a_longer_command_that_shares_a_prefix() {
    // `remember` must not be credited by a document that only shows
    // `remember-batch`, and `prune-ner` must not be credited by `prune-relations`.
    let doc = "use `remember-batch` for bulk writes";
    assert!(!documents_command(doc, "remember"));
    assert!(documents_command(doc, "remember-batch"));
}