Skip to main content

lean_ctx/core/semantic/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Semantic code intelligence on top of the tree-sitter structure.
3//!
4//! Tree-sitter is the always-available baseline: it extracts symbols, imports
5//! and call sites, and resolves callees within the caller's scope. Where that
6//! is not enough — an ambiguous name, or a name only unique project-wide —
7//! a local semantic backend (language server or live JetBrains IDE, via
8//! [`crate::lsp::router`]) is asked where the call actually goes. Its answer
9//! becomes [`EdgeEvidence`] on the property-graph edge, which ranking and
10//! impact analysis weight by grade.
11//!
12//! Guarantees: local only (no network, no source leaves the machine); no
13//! automatic installs; `semantic_mode = "auto"` never starts a server in the
14//! background; every target is confined to the indexed project; without any
15//! backend the graph is exactly the structural graph.
16
17pub mod coverage;
18pub mod enrich;
19pub mod evidence;
20pub mod relations;
21pub mod resolve;
22
23#[cfg(test)]
24mod e2e_tests;
25
26pub use enrich::{Escalation, EscalationBudget, EscalationStats, SemanticVerdict, escalate_calls};
27pub use evidence::{EdgeEvidence, EvidenceGrade, EvidenceOrigin};
28
29use crate::core::config::SemanticMode;
30use crate::core::property_graph::{CodeGraph, EdgeKind};
31
32/// One-line evidence summary of the file-level graph, e.g.
33/// `Semantic: mode=auto | calls 120 verified · 340 resolved · 45 heuristic | implements 12 · extends 3 · references 40`.
34/// Edges without typed evidence (older graphs, other producers) are counted
35/// as `unannotated` only when present.
36pub fn status_line(graph: &CodeGraph, mode: SemanticMode, project_root: &str) -> String {
37    let mode = match mode {
38        SemanticMode::Off => "off",
39        SemanticMode::Auto => "auto",
40        SemanticMode::Eager => "eager",
41    };
42    let coverage = coverage::coverage_by_language(graph);
43    let mut all = coverage::GradeCounts::default();
44    for c in coverage.values() {
45        all.verified += c.verified;
46        all.resolved += c.resolved;
47        all.heuristic += c.heuristic;
48        all.unannotated += c.unannotated;
49    }
50    let count = |kind: EdgeKind| graph.file_edges_of_kind(&kind).map_or(0, |e| e.len());
51    let (implements, extends, references) = (
52        count(EdgeKind::Implements),
53        count(EdgeKind::Extends),
54        count(EdgeKind::References),
55    );
56    let extra = if all.unannotated > 0 {
57        format!(" · {} unannotated", all.unannotated)
58    } else {
59        String::new()
60    };
61    let mut out = format!(
62        "Semantic: mode={mode} | calls {} verified · {} resolved · {} heuristic{extra} | implements {implements} · extends {extends} · references {references}",
63        all.verified, all.resolved, all.heuristic
64    );
65    for line in coverage::coverage_lines(&coverage, project_root) {
66        out.push('\n');
67        out.push_str(&line);
68    }
69    out
70}