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
Findingobjects wrapped in theAnalyzerNodeResponsestructure
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
SkillspectorStateand returningAnalyzerNodeResponseobjects. - Contracts: Import
Finding,Location, andSeverityfromsrc/skillspector/models.pyand the state types fromsrc/skillspector/state.py. - Implementation: Create a module in
src/skillspector/nodes/analyzers/with anodefunction that returns{"findings": [...]}. - Registration: Import your module in
src/skillspector/nodes/__init__.pyto activate automatic discovery viapkgutil.iter_modules. - Ordering: Modify
src/skillspector/graph.pydirectly 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →