Skip to content

Writing Custom Detectors

Jacob Centner edited this page Apr 10, 2026 · 1 revision

Writing Custom Detectors

Sentinel supports third-party detectors via Python's entry-points plugin system. Install a detector package with pip install, and Sentinel auto-discovers it.

Detector interface

Every detector extends the Detector base class:

from sentinel.detectors.base import Detector
from sentinel.models import (
    DetectorContext, DetectorTier, Evidence, EvidenceType, Finding, Severity,
)


class MyDetector(Detector):
    @property
    def name(self) -> str:
        return "my-detector"

    @property
    def description(self) -> str:
        return "Short description of what this detector finds"

    @property
    def tier(self) -> DetectorTier:
        return DetectorTier.DETERMINISTIC  # or HEURISTIC, LLM_ASSISTED

    @property
    def categories(self) -> list[str]:
        return ["code-quality"]

    def detect(self, context: DetectorContext) -> list[Finding]:
        findings = []
        # Your detection logic here
        # Use context.repo_root, context.scope, context.changed_files, etc.
        
        findings.append(Finding(
            detector=self.name,
            category="code-quality",
            severity=Severity.MEDIUM,
            confidence=0.85,
            title="Issue found in example.py",
            description="Detailed description of the issue",
            evidence=[
                Evidence(
                    type=EvidenceType.CODE,
                    content="The problematic code snippet",
                    source="src/example.py",
                    line_range=(10, 20),
                ),
            ],
            file_path="src/example.py",
            line_start=10,
        ))
        return findings

DetectorContext

The context object provides:

Field Type Description
repo_root str Absolute path to the repository root
scope ScopeType FULL, INCREMENTAL, or TARGETED
changed_files list[str] Files changed since last run (incremental)
target_paths list[str] Specific paths to scan (targeted)
config dict Runtime config including provider, skip_llm, num_ctx, model_capability
conn Connection SQLite connection (optional)
run_id int Current run ID (optional)

Entry-point registration

In your package's pyproject.toml:

[project.entry-points."sentinel.detectors"]
my-detector = "my_package.detector:MyDetector"

After pip install my-package, Sentinel will auto-discover and register your detector.

LLM-assisted detectors

If your detector uses an LLM, set the capability tier:

from sentinel.models import CapabilityTier

class MyLLMDetector(Detector):
    @property
    def tier(self) -> DetectorTier:
        return DetectorTier.LLM_ASSISTED

    @property
    def capability_tier(self) -> CapabilityTier:
        return CapabilityTier.BASIC

    def detect(self, context: DetectorContext) -> list[Finding]:
        if context.config.get("skip_llm"):
            return []
        
        provider = context.config.get("provider")
        if provider is None or not provider.check_health():
            return []
        
        # Use provider.generate(prompt, ...) for LLM calls
        response = provider.generate("Analyze this code: ...")
        # Parse response and create findings

Local directory detectors

For detectors that don't need packaging, use detectors_dir in config:

[sentinel]
detectors_dir = "my-detectors/"

Sentinel loads all .py files from this directory and registers any Detector subclasses found.

Best practices

  • Set confidence realistically (0.0–1.0) — deterministic checks can be 1.0, heuristic ~0.8, LLM ~0.6
  • Include evidence with every finding — cite the specific code, config, or history
  • Respect context.scope — for incremental scans, only check changed_files
  • Handle errors gracefully — the runner catches exceptions per-detector, but clean error handling is better
  • Skip files in COMMON_SKIP_DIRS (available from sentinel.detectors.base)

Clone this wiki locally