# How to Extend SkillSpector by Adding Custom Analyzers: A Step-by-Step Guide

> Learn to extend SkillSpector by adding custom analyzers. Follow this step-by-step guide to implement and register your custom analysis nodes in the NVIDIA SkillSpector pipeline for enhanced functionality.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: how-to-guide
- Published: 2026-06-18

---

**To extend SkillSpector with custom analyzers, create a Python module in `src/skillspector/nodes/analyzers/` that implements a `node(state: SkillspectorState) -> AnalyzerNodeResponse` function, then register it via an import in [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py) to automatically include it in the LangGraph analysis pipeline.**

SkillSpector is NVIDIA's open-source framework for analyzing AI skill codebases using a LangGraph-based workflow. The architecture treats each analyzer as an independent node that receives shared state and returns structured findings. By understanding the contracts defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) and [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py), you can extend SkillSpector by adding custom analyzers that detect domain-specific patterns, security vulnerabilities, or proprietary code standards.

## Understanding the SkillSpector Analysis Architecture

Before writing code, you must understand the three core contracts that govern how analyzers interact with the execution graph.

### The State Contract (SkillspectorState)

The `SkillspectorState` TypedDict, defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py), serves as the shared data bus flowing through the LangGraph pipeline. It contains the `file_cache` (mapping file paths to contents), accumulated `findings`, manifest data, and model configurations. Analyzers read from this state but never modify it directly; instead, they return updates via the response contract.

### The Response Contract (AnalyzerNodeResponse)

Every analyzer must return an `AnalyzerNodeResponse`, also defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py). This TypedDict enforces a strict structure:

```python
AnalyzerNodeResponse = {"findings": list[Finding]}

```

The graph reducer (using `operator.add`) automatically appends these findings to the master state. Your analyzer function must produce this exact return type to remain compatible with the pipeline.

### The Finding Dataclass

Individual issues are reported as `Finding` objects from [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py). This dataclass requires:

```python
Finding(
    file_path="relative/path/to/file.py",
    location=Location(line=1, col=1, end_line=1, end_col=5),
    severity=Severity.HIGH,
    title="Issue Title",
    description="Detailed explanation",
    remediation="Suggested fix",
)

```

## How to Extend SkillSpector by Adding Custom Analyzers

Follow these steps to create and register a new analyzer that integrates seamlessly with the existing workflow.

### Step 1: Create the Analyzer Module

Create a new Python file under `src/skillspector/nodes/analyzers/`. Name it descriptively (e.g., [`custom_security_checks.py`](https://github.com/NVIDIA/SkillSpector/blob/main/custom_security_checks.py)). The file location matters because [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) uses `pkgutil.iter_modules` to discover all analyzers in this package.

### Step 2: Implement the Node Function

Your module must expose a `node` function with this exact signature:

```python
from skillspector.state import AnalyzerNodeResponse, SkillspectorState
from skillspector.models import Finding, Severity, Location

def node(state: SkillspectorState) -> AnalyzerNodeResponse:
    # Analysis logic here

    findings: list[Finding] = []
    # ... populate findings ...

    return {"findings": findings}

```

The function must:
- Read input data from `state` (e.g., `state["file_cache"]` for source files)
- Perform analysis without side effects
- Return a list of `Finding` objects wrapped in the `AnalyzerNodeResponse` structure

### Step 3: Register the Analyzer

Open [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py) and add an import for your new module:

```python
from .analyzers import custom_security_checks  # noqa: F401

```

This import triggers the module loading that makes your analyzer discoverable. The `noqa: F401` comment prevents linting errors since the import exists solely for side effects.

### Step 4: Configure Execution Order (Optional)

If your analyzer must run before or after specific built-in checks, edit [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py). Locate where the `analyzer_nodes` list is assembled and insert your `custom_security_checks.node` function at the desired position. The default behavior appends analyzers in the order they are discovered.

## Complete Working Example

Here is a minimal runnable analyzer that flags `TODO` comments in Python files:

```python

# src/skillspector/nodes/analyzers/todo_detector.py

"""Flags TODO markers in Python files as medium-severity findings."""

from pathlib import Path
from skillspector.state import AnalyzerNodeResponse, SkillspectorState
from skillspector.models import Finding, Severity, Location


def _find_todos(content: str) -> list[Finding]:
    findings: list[Finding] = []
    for lineno, line in enumerate(content.splitlines(), start=1):
        if "TODO" in line:
            start_col = line.find("TODO") + 1
            loc = Location(
                line=lineno, 
                col=start_col, 
                end_line=lineno, 
                end_col=start_col + 4
            )
            findings.append(
                Finding(
                    file_path="",  # Populated later

                    location=loc,
                    severity=Severity.MEDIUM,
                    title="TODO marker detected",
                    description="TODO comments may indicate unfinished code.",
                    remediation="Remove or resolve the TODO before deployment.",
                )
            )
    return findings


def node(state: SkillspectorState) -> AnalyzerNodeResponse:
    """Analyze files in state['file_cache'] for TODO markers."""
    findings: list[Finding] = []
    
    for path, content in state["file_cache"].items():
        for finding in _find_todos(content):
            # Convert absolute path to relative path

            finding.file_path = Path(path).relative_to(
                state["skill_path"] or "."
            ).as_posix()
            findings.append(finding)
    
    return {"findings": findings}

```

After creating this file, add the following line to [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py):

```python
from .analyzers import todo_detector  # noqa: F401

```

Your analyzer will now execute automatically alongside built-in analyzers like [`static_patterns_tool_misuse.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_tool_misuse.py).

## Testing Your Custom Analyzer

Validate your implementation using the existing test framework:

```bash
pytest -k todo_detector

```

For comprehensive coverage, add a unit test in `tests/nodes/analyzers/` that constructs a synthetic `SkillspectorState` and asserts the expected `Finding` objects are returned:

```python
def test_todo_detector_finds_markers():
    state = SkillspectorState(
        file_cache={"test.py": "# TODO: fix this"},

        skill_path="/tmp",
        # ... other required fields ...

    )
    response = todo_detector.node(state)
    assert len(response["findings"]) == 1
    assert response["findings"][0].title == "TODO marker detected"

```

## Summary

- **Architecture**: SkillSpector uses LangGraph where analyzers are pure functions receiving `SkillspectorState` and returning `AnalyzerNodeResponse` objects.
- **Contracts**: Import `Finding`, `Location`, and `Severity` from [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) and the state types from [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py).
- **Implementation**: Create a module in `src/skillspector/nodes/analyzers/` with a `node` function that returns `{"findings": [...]}`.
- **Registration**: Import your module in [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py) to activate automatic discovery via `pkgutil.iter_modules`.
- **Ordering**: Modify [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) directly if you need explicit control over analyzer execution order.

## Frequently Asked Questions

### Do I need to modify the LangGraph configuration directly?

No. The graph builder in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) automatically discovers analyzers using `pkgutil.iter_modules` on the `analyzers` package. Simply importing your module in [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py) registers it without touching graph configuration files.

### Can my analyzer access external APIs or databases?

Yes. While analyzers typically read from `state["file_cache"]`, you can implement any logic inside the `node` function, including external API calls. Ensure the function remains stateless regarding the `SkillspectorState` (read-only) and returns findings via the required `AnalyzerNodeResponse` structure.

### What severity levels are available?

The `Severity` enum in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) provides standardized levels including `LOW`, `MEDIUM`, `HIGH`, and `CRITICAL`. Use these consistently to ensure proper filtering and reporting in the SkillSpector output format.

### How do I debug my analyzer if it is not producing findings?

Run SkillSpector with verbose logging enabled and check that your module appears in the loaded analyzers list. Verify your `node` function returns the exact `AnalyzerNodeResponse` type (a dict with a `findings` key containing a list of `Finding` objects). Type mismatches will cause the LangGraph reducer to fail silently or raise validation errors.