SkillSpector Input Resolution Process: How Git URLs, ZIP Files, and Directories Are Normalized

SkillSpector normalizes Git URLs, ZIP archives, raw file URLs, and local directories into a standardized local directory path through a hierarchical detection system implemented in InputHandler.resolve(), creating temporary workspaces for remote inputs while returning local paths unchanged.

The NVIDIA SkillSpector repository accepts multiple input formats for static analysis, requiring a robust normalization layer. Located in src/skillspector/input_handler.py, the InputHandler class implements the input resolution process that converts any supported source into a scannable filesystem directory. This architecture ensures that whether you point the tool at a GitHub repository, a downloaded ZIP, or a local folder, the analysis engine receives a consistent local path.

Entry Point and Detection Order

The resolution pipeline begins at InputHandler.resolve() (lines 55‑66), which returns a tuple of (Path, source_type) where source_type is one of "git", "url", "zip", "file", or "directory".

The method applies a strict priority order to determine input type:

  1. Git URL – Validated via _is_git_url()
  2. File URL – Checked via _is_file_url() for raw content links
  3. ZIP archive – Identified by .zip extension
  4. Markdown file – Identified by .md extension
  5. Local directory – Verified via Path.is_dir()
  6. Local file – Verified via Path.is_file()

If the input matches the first three categories, the handler initializes a temporary working directory (self._temp_dir) that persists until cleanup() is invoked.

Git URL Detection and Cloning

Identifying Git Repositories (_is_git_url)

The _is_git_url() method (lines 105‑117) distinguishes between repository URLs and raw file links using multi-layer validation:


# src/skillspector/input_handler.py, lines 105-117

def _is_git_url(self, path: str) -> bool:
    if not path.startswith(("http://", "https://", "git@")):
        return False
    parsed = urlparse(path)
    git_hosts = ["github.com", "gitlab.com", "bitbucket.org"]
    if any(host in parsed.netloc for host in git_hosts):
        # Exclude raw file paths (e.g. /raw/, /blob/) that look like URLs

        if "/raw/" in path or "/blob/" in path or path.endswith((".md", ".py", ".sh")):
            return False
        return True
    if path.endswith(".git"):
        return True
    return False

The logic requires an HTTP/HTTPS or git@ scheme, checks against major Git hosts (GitHub, GitLab, Bitbucket), and explicitly rejects URLs containing /raw/ or /blob/ paths or file extensions like .md, .py, or .sh. A trailing .git suffix also triggers a positive match regardless of host.

Shallow Clone Implementation (_clone_git)

Once identified as a Git URL, the repository is cloned via _clone_git() (lines 125‑149):


# src/skillspector/input_handler.py, lines 125-149

def _clone_git(self, url: str) -> Path:
    temp_dir = self._get_temp_dir()
    clone_dir = temp_dir / "repo"
    subprocess.run(
        ["git", "clone", "--depth", "1", url, str(clone_dir)],
        check=True,
        capture_output=True,
        timeout=60,
        shell=False,
    )
    # Errors are wrapped into ValueError with helpful logging

    return clone_dir

The implementation performs a shallow clone (--depth 1) to minimize bandwidth and time, places the repository in {temp_dir}/repo, and enforces a 60‑second timeout. Clone failures raise ValueError with detailed logging.

ZIP Archive Processing

For ZIP inputs, the _extract_zip() method (lines 173‑189) handles extraction and normalization:


# src/skillspector/input_handler.py, lines 173-189

def _extract_zip(self, zip_path: Path) -> Path:
    temp_dir = self._get_temp_dir()
    extract_dir = temp_dir / "extracted"
    extract_dir.mkdir(exist_ok=True)
    with zipfile.ZipFile(zip_path, "r") as zf:
        zf.extractall(extract_dir)
    # If the zip contains a single top‑level folder, return that folder

    contents = list(extract_dir.iterdir())
    if len(contents) == 1 and contents[0].is_dir():
        return contents[0]
    return extract_dir

The method extracts archives into {temp_dir}/extracted. If the ZIP contains a single top‑level directory (common for GitHub download ZIPs), it returns that subdirectory directly, providing a clean path for the analysis graph.

Local Directory Normalization

When the input resolves to an existing directory on the host filesystem, resolve() returns Path(input_path).resolve() (line 80) with source_type set to "directory". This path requires no temporary storage or cleanup, as the tool reads directly from the provided location.

Temporary Resource Management

Remote inputs (Git, URL, ZIP) trigger temporary directory creation via _get_temp_dir(). The cleanup() method (lines 89‑93) removes these workspaces after analysis completes, preventing file system pollution. The temporary directory reference is tracked in src/skillspector/state.py (temp_dir_for_cleanup) to ensure the graph execution can invoke cleanup even if the handler instance goes out of scope.

Practical Usage Examples

Scanning a Public GitHub Repository

skillspector scan https://github.com/NVIDIA/SkillSpector

Internally, InputHandler.resolve() recognizes the URL via _is_git_url(), invokes _clone_git() for a shallow clone, and scans the resulting temporary directory.

Processing a Remote ZIP Archive

skillspector scan https://example.com/some_tool.zip

The URL passes _is_file_url() detection, downloads to a temporary location, and routes through _extract_zip() to normalize the archive structure.

Using the API Programmatically

from skillspector.input_handler import InputHandler

handler = InputHandler()
path, src_type = handler.resolve("https://github.com/NVIDIA/SkillSpector")
print(f"Resolved {src_type} to {path}")

# ... run the analysis ...

handler.cleanup()  # delete any temp directories

Summary

  • InputHandler.resolve() serves as the unified entry point for all input types, implementing a hierarchical detection system in src/skillspector/input_handler.py.
  • Git URLs are identified by scheme, host validation, and exclusion of raw file paths, then cloned with --depth 1 to minimize overhead.
  • ZIP archives are extracted to temporary directories, with automatic flattening of single-root archive structures.
  • Local directories bypass temporary storage and are returned as resolved absolute paths.
  • Resource cleanup is mandatory for remote inputs via handler.cleanup() to prevent temporary directory leakage.

Frequently Asked Questions

How does SkillSpector distinguish between a Git repository URL and a raw file URL?

According to the source code in src/skillspector/input_handler.py, the _is_git_url() method checks for http://, https://, or git@ schemes, validates against known Git hosts (GitHub, GitLab, Bitbucket), and explicitly excludes URLs containing /raw/, /blob/, or file extensions like .md or .py. URLs ending in .git are always treated as Git repositories.

Why does SkillSpector use shallow cloning for Git repositories?

The _clone_git() implementation uses git clone --depth 1 to reduce network bandwidth and cloning time. As implemented in lines 125‑149, this fetches only the most recent commit, which is sufficient for static analysis while minimizing temporary storage requirements.

What happens if a ZIP file contains a single top-level folder?

The _extract_zip() method (lines 173‑189) detects single-directory archives by checking if extract_dir contains exactly one subdirectory. If this condition is met, it returns that subdirectory's path directly rather than the parent extraction directory, eliminating unnecessary nesting in the analysis path.

Is manual cleanup required when using the Python API?

Yes. When resolving remote inputs (Git, ZIP, or URLs), you must call handler.cleanup() after analysis completes to remove temporary directories. The reference is stored in the state object at src/skillspector/state.py to ensure cleanup can be triggered from the graph execution context.

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 →