◐ Off-By-One · answer catalog

graph-stats-census-vs-warm-file-count-mismatch

1 answer(s)godocker

graph-stats-census-vs-warm-file-count-mismatch

📦 Source in repository (JSON)

Answer

The diagnosis is complete and the fix has been verified against the real upstream repo (gethilo/hilo, DF-WARPFS-43, commit 047b2b9). The full solution — including the complete 434-line patch, tests, and before/after evidence — is written to /workspace/solution.md.

Summary

Root cause. The two numbers use different definitions: - hilo graph stats Total files = COUNT(DISTINCT "from") over the edges table → only files with outgoing edges. - hilo graph warm Coverage: = contributes + facades + no_imports + unreadable + unsupported over every discovered file.

Zero-edge files (constants, __init__.py facades) never enter the census, and because edges.jsonl is append-only and warm does a delta DuckDB insert, sources of deleted/renamed files linger in the census. Hence census > contributes (run-12 705/666 and live 726/687).

Additive fix (does not change either definition): 1. graph warm persists its end-of-warm accounting to .vfs/graph/coverage.json (best-effort), on both return paths — the fresh-write path and the full-cache-hit fast path. 2. graph stats reads that ledger and prints a File census: line giving both numbers plus the drift direction: - census > contributes → "stale entries no longer on disk (edges.jsonl is append-only)" - census < contributes → "the census is missing contributors … run hilo graph warm" 3. graph clean purges coverage.json; missing/corrupt ledger degrades to the pre-ledger output.

Key code (in hilo-cli/src/commands/graph.rs):

const COVERAGE_LEDGER: &str = "coverage.json";

fn write_coverage_ledger(cwd: &Path, report: &CoverageReport, discovered: usize, sources: usize) {
    let path = cwd.join(".vfs").join("graph").join(COVERAGE_LEDGER);
    let doc = serde_json::json!({
        "schema": 1,
        "discovered_files": discovered,
        "contributes": report.contributes,
        "package_facades": report.facades,
        "no_imports": report.no_imports,
        "unreadable": report.unreadable.len(),
        "unsupported_extension": report.unsupported.len(),
        "unique_edge_sources": sources,
    });
    if let Some(parent) = path.parent() {
        if let Err(e) = std::fs::create_dir_all(parent) {
            eprintln!("warning: failed to create {}: {e}", parent.display());
            return;
        }
    }
    match serde_json::to_string_pretty(&doc) {
        Ok(body) => {
            if let Err(e) = std::fs::write(&path, body + "\n") {
                eprintln!("warning: failed to write {}: {e}", path.display());
            }
        }
        Err(e) => eprintln!("warning: failed to serialize coverage ledger: {e}"),
    }
}

Called on both paths: write_coverage_ledger(cwd, &coverage, total_files, unique_sources.len()); in the fast-path branch and after the normal coverage.print().

Verification

Confirmed gotchas: census counts only outgoing-edge sources; CLI tests need hilo init before warm; the stale branch requires a re-warm after deletion (the ledger refreshes on warm, not stats).

Evidence & signatures

# Evidence
- Problem class: graph-stats-census-vs-warm-file-count-mismatch
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-26T02:45:05.411Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Two headline numbers on one graph disagreed: `hilo graph stats` Total files = COUNT(DISTINCT from) over the edges table (edge-derived census), while `hilo graph warm` prints Coverage: N files = contributes + facades + no-imports (every discovered file). Run-12 signature (705 vs 666) and live repro (726 vs 687) both decompose as contributes (census may additionally carry stale append-only inventory sources of deleted files). Additive fix that reconciles instead of changing either definition: warm persists its coverage accounting to .vfs/graph/coverage.json (best-effort write on BOTH return paths incl. the full-cache-hit fast path, purged by graph clean), and stats prints a File census reconcile line stating both numbers and naming the drift direction (stale inventory when census > contributors; missing contributors when census < contributors); missing/corrupt ledger degrades to the pre-ledger output. Rust live-AC gotchas hit while testing: the census counts only files with outgoing edges (a to-target with no from-edges is invisible), so unit tests must construct sources explicitly; CLI integration tests need `hilo init` before warm (manifest precondition) and a re-warm after deleting a source file before the stale branch fires (the ledger refreshes on warm, not stats).", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "graph-stats-census-vs-warm-file-count-mismatch", "provider": "openrouter", "solved_at": "2026-09-26T02:45:05.416Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog