# How to Extend the SkillSpector Analyzer Pipeline with Custom Analyzers

> Learn to extend the SkillSpector analyzer pipeline by creating custom analyzers. Follow our guide to add new analysis nodes and integrate them seamlessly into the SkillSpector workflow.

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

---

**To extend the SkillSpector analyzer pipeline, create a new Python module in `src/skillspector/nodes/analyzers/` that implements a `node(state: SkillspectorState) -> AnalyzerNodeResponse` function, register it via an import in [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py), and optionally adjust execution order in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py).**

SkillSpector is NVIDIA’s open-source LangGraph-based analysis framework for evaluating AI skill implementations. When you need to detect domain-specific patterns, integrate proprietary security checks, or add custom linting rules, you can extend the analyzer pipeline without modifying core engine code—provided you follow the strict state contract defined in the source.

## Understanding the SkillSpector Analysis Architecture

SkillSpector orchestrates analysis through a **LangGraph workflow** where each analyzer operates as an independent node. The graph passes a shared state object through every node, and each analyzer contributes findings back via a standardized response payload.

### Core State Components

Three types define the contract between your custom code and the execution engine:

- **`SkillspectorState`** (defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py)): A `TypedDict` containing all data flowing through the graph, including `file_cache`, `manifest`, `findings`, and model configuration.
- **`AnalyzerNodeResponse`** (defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py)): A `TypedDict` with a single field `findings: list[Finding]` that every analyzer must return.
- **`Finding`** (defined in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py)): A dataclass describing a single issue with fields for `file_path`, `location`, `severity`, `title`, `description`, and `remediation`.

The graph reducer automatically appends your returned findings to `state["findings"]` using the `operator.add` annotation specified in `SkillspectorState`.

## How Built-in Analyzers Work

Analyzers are stateless functions that read from the shared state and return structured findings. Examine the implementation in [`src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py) to see the pattern:

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

def node(state: SkillspectorState) -> AnalyzerNodeResponse:
    # 1. Retrieve file contents from the state cache

    content = state["file_cache"][file_path]
    
    # 2. Run analysis logic to generate raw findings

    raw_findings = _run_tool_misuse_checks(content)
    
    # 3. Convert to standardized Finding objects

    findings = [f.to_finding() for f in raw_findings]
    
    # 4. Return strict response type (no side effects)

    return {"findings": findings}

```

The function performs **no side effects**—it only reads from `state` and returns the `AnalyzerNodeResponse` payload. The graph handles persistence and aggregation.

## Step-by-Step Guide to Adding Custom Analyzers

Follow these steps to integrate your own analysis logic into the SkillSpector pipeline.

### Step 1: Create the Analyzer Module

Create a new Python file in `src/skillspector/nodes/analyzers/`, for example [`my_custom_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/my_custom_analyzer.py).

### Step 2: Import Required Types

At the top of your module, import the state and model contracts:

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

```

### Step 3: Implement the Node Function

Define a `node` function with the exact signature `node(state: SkillspectorState) -> AnalyzerNodeResponse`. Inside this function:

- Pull data from `state` (commonly `state["file_cache"]` for source content or `state["manifest"]` for skill metadata).
- Execute your custom analysis logic.
- Construct `Finding` objects with all required fields:

```python
Finding(
    file_path="relative/path/to/file.py",
    location=Location(line=start, col=start_col, end_line=end, end_col=end_col),
    severity=Severity.HIGH,
    title="Custom Issue Title",
    description="Detailed explanation of the problem.",
    remediation="Suggested fix or mitigation.",
)

```

- Return `{"findings": findings}`.

### Step 4: 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 my_custom_analyzer  # noqa: F401

```

The graph builder uses `pkgutil.iter_modules` to auto-discover analyzers, so this import side-effect is sufficient for registration.

### Step 5: Adjust Execution Order (Optional)

If your analyzer must run before or after specific built-ins, edit [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) where the `analyzer_nodes` list is assembled. Insert your `my_custom_analyzer.node` reference at the desired position in the list.

### Step 6: Test Your Implementation

Run the test suite to verify compatibility:

```bash
pytest -k my_custom_analyzer

```

Add dedicated unit tests under `tests/nodes/analyzers/` that instantiate a synthetic `SkillspectorState` and assert the expected `Finding` objects are produced.

## Complete Custom Analyzer Example

Below is a minimal working analyzer that flags Python files containing `TODO` markers:

```python

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

"""A tiny example that flags any Python file containing the literal string 'TODO'."""

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:
            loc = Location(
                line=lineno, 
                col=line.find("TODO") + 1, 
                end_line=lineno, 
                end_col=line.find("TODO") + 5
            )
            findings.append(
                Finding(
                    file_path="",  # populated by caller

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


def node(state: SkillspectorState) -> AnalyzerNodeResponse:
    findings: list[Finding] = []
    # The graph guarantees state["file_cache"] contains all skill files

    for path, content in state["file_cache"].items():
        for f in _find_todos(content):
            f.file_path = Path(path).relative_to(state["skill_path"] or ".").as_posix()
            findings.append(f)
    
    return {"findings": findings}

```

After adding the import to [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py), this analyzer automatically executes alongside built-in static and semantic checks.

## Summary

- **SkillSpector** uses a LangGraph workflow where analyzers receive `SkillspectorState` and return `AnalyzerNodeResponse` payloads.
- Create custom analyzers in `src/skillspector/nodes/analyzers/` by implementing a `node(state) -> AnalyzerNodeResponse` function that returns a list of `Finding` objects.
- Register analyzers via import side-effects in [`src/skillspector/nodes/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/__init__.py).
- Modify [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) to control execution order if dependencies exist between analyzers.
- Store all custom logic in the analyzers directory to maintain compatibility with the existing execution engine and test infrastructure.

## Frequently Asked Questions

### What data is available in the SkillspectorState during analysis?

The `SkillspectorState` dictionary (defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py)) contains the complete execution context, including `file_cache` (a mapping of absolute file paths to content strings), `manifest` (skill metadata), `findings` (accumulated results from previous nodes), and model configuration parameters. Analyzers should treat this state as read-only and return findings via the `AnalyzerNodeResponse` type rather than mutating the state directly.

### Can I execute external tools or API calls from a custom analyzer?

Yes, provided the operation is synchronous and returns within the LangGraph node timeout. Implement external tool calls inside your `node` function (e.g., calling a linter, security scanner, or proprietary API), then transform the external output into `Finding` objects. Ensure you handle exceptions gracefully to prevent one analyzer from crashing the entire pipeline.

### How do I control the severity levels for my custom findings?

Import the `Severity` enum from [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) and assign one of the standard levels (`LOW`, `MEDIUM`, `HIGH`, `CRITICAL`) when constructing `Finding` objects. The severity determines how findings are prioritized in the final report and whether they trigger pipeline failures based on configured thresholds.

### Where should I place unit tests for my custom analyzer?

Add test files under `tests/nodes/analyzers/` following the pattern of existing tests. Construct a mock `SkillspectorState` dictionary with synthetic `file_cache` entries, pass it to your `node` function, and assert that the returned `AnalyzerNodeResponse` contains the expected `Finding` objects with correct file paths, locations, and descriptions.