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

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 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 and 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, 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. This TypedDict enforces a strict structure:

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. This dataclass requires:

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). The file location matters because 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:

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 and add an import for your new module:

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


# 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:

from .analyzers import todo_detector  # noqa: F401

Your analyzer will now execute automatically alongside built-in analyzers like static_patterns_tool_misuse.py.

Testing Your Custom Analyzer

Validate your implementation using the existing test framework:

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:

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 and the state types from 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 to activate automatic discovery via pkgutil.iter_modules.
  • Ordering: Modify 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 automatically discovers analyzers using pkgutil.iter_modules on the analyzers package. Simply importing your module in 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →