Skip to content

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.

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.

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.

The design calls for two intermediate representations:

  1. Symbol graph — references, exports, reachability. Per-language, built by the language-native semantic tool (the TS compiler API today). Implemented.
  2. Syntactic IR — block tree + branch counts for complexity/duplication detectors. Language-agnostic, tree-sitter-fed. Implemented.

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).