Skip to content

Per-crate summary

The crates/ directory in the repo. Linked diagram is in the architecture overview.

The type definitions every other crate depends on. Rule, Finding, Report, Verdict, Severity, Category, Scope, EvaluatorSpec, the Evaluator trait, the BaselineDelta and PrReport types for regression mode, plus a few helpers like SeverityCounts and VerdictThresholds. No I/O dependencies. That guarantee is what lets the crate stay stable while the surface crates churn — and makes it cheap to test.

Reads .sextant/config.toml into typed structs and merges with defaults. Returns the resolved config the engine runs with. Also hosts the hardcoded skip list (generated artifacts: Cargo.lock, target/, node_modules/, .git/, …) — every crate that walks files asks this crate which paths to skip. The list is not user-configurable.

Rule discovery, evaluators, and the vendor pack plumbing. Three layered concerns:

  1. Discovery & schema. Parses YAML frontmatter + markdown body via gray_matter, validates against the rule schema, returns ParsedRule values. Walks three sources: built-ins (embedded via rust-embed), repo-local rules under .sextant/rules/**/*.md (excluding vendor/), and vendor packs (one directory per pack under vendor/). The three-tier merge enforces vendor immutability — a repo rule that shadows a vendor id is a hard error.
  2. Evaluators. Four kinds: builtin (Rust impls of the seven shipped rules), regex (line-by-line text), ast (tree-sitter query with optional ancestor-skip), and llm (judge-driven).
  3. Vendor pack lifecycle. fetcher.rs parses pack specs and clones from GitHub via git2 (or copies from a file: path) into a hashed staging dir. lock.rs reads, writes, and verifies .sextant/rules.lock, the SHA-256 record that gates every grade.

Git diff acquisition via git2. Resolves base refs (merge-base with origin/main, falling back to HEAD~1). Walks blob contents — no git checkout required, so the working tree stays at the head commit. Returns the changed files and per-file line ranges that diff-mode and PR-mode grading need.

Tree-sitter parsers and queries for Rust, Python, Go, Java, TypeScript, TSX, JavaScript. Provides cached parsers (one per language, reused across files) and the captured-name queries built-in evaluators use to find functions, types, etc. Adding a language is isolated to this crate.

LLM-as-judge providers and cache. Wraps Anthropic and OpenAI HTTP APIs (via reqwest), enforces tool-use schemas so LLM responses are well-typed Findings, and caches by BLAKE3 hash of (file content, rule id, rule body, model). The cache makes repeat grades of unchanged files free.

Grading orchestration. Public API:

  • grade(cwd, GradeMode) -> Report
  • grade_pr(cwd, DiffOptions, PrOptions) -> PrReport
  • list_rules(cwd) -> Vec<RuleSummary>
  • explain_rule(cwd, id) -> Option<RuleSummary>
  • load_config(cwd) -> Config

That’s the whole engine API. Both binaries call into exactly these functions; the rest is wire-format wrapping. The engine’s job is to load config + rules, acquire files, run evaluators, build a report — no command-line parsing, no JSON-RPC framing, no markdown rendering.

The sextant binary. Wraps the engine with clap argument parsing and one render module per output format (human, json, markdown, sarif, review-json). Subcommands: grade, rules (with list / explain / check / add / update / remove), and init. The rules add-family subcommands delegate to sextant-rules::fetcher and sextant-rules::lock — the binary itself doesn’t open sockets or write hashes. Tests are largely snapshot-based via insta.

The sextant-mcp binary. JSON-RPC 2.0 server — stdio by default, HTTP via axum when --http <addr> is passed. Five tools: grade_diff, grade_files, list_rules, explain_rule, get_config. Each tool dispatches to one engine entry point and wraps the result in the MCP content envelope. Logging goes to stderr; stdout is reserved for the protocol stream.