Overview
This section is for contributors. It explains how a scan flows through the engine and the design rules that keep Necro extensible.
For the full rationale and locked decisions, see the design spec in the repo.
The pipeline
Section titled “The pipeline”A scan is a pipeline of small, independently testable stages:
discover files → build symbol graph (ts-morph; language-specific) → resolve entries (prod entries + framework plugins) → two-color reachability (+ taint) → classify into tiers → render (terminal / JSON)Each stage is a focused module with a clear interface — see Project layout.
The core invariant
Section titled “The core invariant”Language-specific code lives only in the symbol-graph adapter.
Detectors and the reachability/classification logic are language-agnostic. They operate on a generic graph, never on TypeScript specifics. Adding a new language (Python is planned) means writing one new symbol-graph adapter — the rest of the engine is reused unchanged. A detector that special-cases a language is a leak.
Two IRs (by design)
Section titled “Two IRs (by design)”The design calls for two intermediate representations:
- Symbol graph — references, exports, reachability. Per-language, built by the language-native semantic tool (the TS compiler API today). Implemented.
- Syntactic IR — block tree + branch counts for complexity/duplication detectors. Language-agnostic, tree-sitter-fed. Implemented.
What exists today
Section titled “What exists today”The dead-code vertical slice (discovery, symbol graph, plugin/entry resolution,
two-color reachability with taint, tier classification, evidence chains,
terminal/JSON output), plus the tree-sitter syntactic detectors (complexity,
duplication), CRAP × churn hotspot scoring, safe dead-code removal (fix), the
LLM triage/refactor layer, a read-only mcp server, and SARIF + --fail-on
CI gating. Still planned: more framework plugins, and
Python (a second symbol-graph adapter).