python-lsp/SKILL.md
Semantic Python code queries via a stdio LSP client driving pyright-langserver. Provides binding-resolved go-to-definition, find-references, hover types, type diagnostics, file symbol outlines, and project-wide symbol search — name resolution and type inference that tree-sitter and ripgrep cannot do. Use when you need to follow an import to a definition, find all real uses of a symbol (excluding same-named-but-unrelated ones), get an inferred type, surface type errors, outline a file, or search symbols across a project. Triggers on "go to definition", "find references", "what type is", "resolve this symbol", "symbol outline", "find symbol in project", "pyright", "type-check this file".
npx skillsauth add oaustegard/claude-skills python-lspInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A thin, dependency-free Python client that owns the LSP lifecycle against
pyright-langserver --stdio and exposes high-value semantic queries.
Why, over tree-sitter / ripgrep: tree-sitter gives a CST — structural queries, call-site enumeration by name. It cannot do name resolution, type inference, or cross-file binding. ripgrep matches text, so it false-positives on shadowed / same-named symbols. pyright resolves bindings. This client is that semantic overlay.
The client bootstraps pyright on first use. Run the bootstrap explicitly, or
let LSPClient do it via ensure_pyright():
sh /mnt/skills/user/python-lsp/scripts/bootstrap.sh
# or, equivalently, the one-liner it wraps:
command -v pyright-langserver >/dev/null || uv tool install pyright
pyright wheels vendor the langserver JS bundle and run it on system node —
no npm install, no separate fetch when node is present. Measured cold (caches
wiped): uv tool install pyright ~0.7s, first working server ~1.8s total; warm
sub-second.
Node prerequisite. The clean path assumes system node (v18+) is present.
With no node, pyright-python falls back to downloading node from nodejs.org,
which may be blocked in locked-down containers. The bootstrap detects node and
fails loudly (exit 1, clear message) rather than hanging.
LSP=/mnt/skills/user/python-lsp/scripts/lsp_client.py
python3 $LSP bootstrap # ensure pyright installed
python3 $LSP <root> definition <file> <line> <col>
python3 $LSP <root> references <file> <line> <col>
python3 $LSP <root> hover <file> <line> <col>
python3 $LSP <root> diagnostics <file>
python3 $LSP <root> symbols <file> # documentSymbol outline
python3 $LSP <root> wsymbols <query> # workspace/symbol search
Positions are zero-based line/character (LSP spec). <file> is relative to
<root> or absolute.
The scripts/ module lands on the boot .pth, so it is importable directly.
import sys; sys.path.insert(0, "/mnt/skills/user/python-lsp/scripts")
from lsp_client import LSPClient
with LSPClient("/path/to/repo") as c: # context manager reaps the subprocess
c.open_all("pkg/service.py", "pkg/models.py")
c.wait_for_index() # REQUIRED before querying — see below
defs = c.definition("pkg/service.py", 4, 8) # -> [Location], follows imports
refs = c.references("pkg/models.py", 8, 4) # -> [Location], binding-resolved
typ = c.hover("pkg/service.py", 4, 4) # -> "(variable) u: User"
diags = c.diagnostics("pkg/bad.py") # -> [diagnostic dicts]
outln = c.document_symbols("pkg/models.py") # -> [SymbolInfo], file outline
hits = c.workspace_symbols("User") # -> [SymbolInfo], project-wide
Location has .path, .start_line, .start_char, .end_line, .end_char
(all zero-based) and .as_dict(). Convert 1-based UI input with
Position.from_one_based(line, col).
The root you pass is split into two roles. Relative <file> arguments resolve
against it (call that the scope), but pyright is rooted at the enclosing
project root — by default LSPClient climbs out of any package the scope
sits inside (every ancestor with an __init__.py) via find_project_root. This
is what makes a query scoped to a sub-package (scipy/optimize) still resolve
the project's own absolute imports (from scipy.optimize._x import Y); without
it, references silently undercount — same blindness as a text grep, opposite
direction. The promotion is announced on stderr. Pass auto_root=False to pin
pyright at the scope verbatim.
| Method | Returns | Notes |
|---|---|---|
| definition(file, line, col) | list[Location] | Go-to-definition across files/imports. |
| references(file, line, col) | list[Location] | Binding-resolved — the win over ripgrep. Excludes same-named, unrelated symbols. |
| hover(file, line, col) | str \| None | Inferred type / signature string. |
| diagnostics(file) | list[dict] | pyright type/error diagnostics for the file. |
| document_symbols(file) | list[SymbolInfo] | One file's outline (classes/functions/methods); nesting via .container. |
| workspace_symbols(query) | list[SymbolInfo] | Project-wide fuzzy symbol search. Empty query = every symbol (expensive). |
SymbolInfo has .name, .kind (int), .kind_name (e.g. "Class"), .location (a Location), .container, and .as_dict().
wait_for_index() blocks on
pyright's $/progress begin/end cycle (with a diagnostics-arrival fallback).
Always call it after did_open / open_all and before any query.workspace.configuration or workspace.workspaceFolders.
If the client claims either capability, pyright defers all analysis until
the corresponding negotiation completes — the server starts its service
instance and then goes silent (no diagnostics, no progress, queries hang).
start() advertises neither, so pyright uses its defaults and analyzes open
files immediately — no didChangeConfiguration nudge needed. Relevant if you
reimplement the lifecycle or add capabilities. (Bisected against the fixture;
workspace.symbol is safe to advertise.)stop()) so
sessions don't leak pyright-langserver processes. stop() sends
shutdown + exit, then waits/terminates/kills as needed.did_open their target file, but
for cross-file references open all relevant files first so pyright has
built their models.LSPClient auto-detects this (climbs out of the
enclosing package; see find_project_root) and prints the promotion to
stderr; auto_root=False opts out. This was a live silent-undercount bug:
--refs ScalarFunction over scipy/optimize returned 3 references rooted at
the sub-package vs 30 rooted at the project root.cd /mnt/skills/user/python-lsp
python3 -m pytest tests/test_lsp_client.py -v
Round-trips against tests/fixture/ (a small multi-file package): definition
follows an import, references excludes an unrelated same-named symbol, hover
returns an inferred type, diagnostics flags an intentional type error, the
indexing-wait is verified deterministic, and subprocess cleanup is checked for
orphans.
development
Write effective instructions for Claude: project instructions, standalone prompts, and skill content. Use when users need help writing prompts, setting up project instructions, choosing between instruction formats, or improving how they communicate with Claude. Covers writing principles, model-aware calibration, and format selection. For building and testing complete skills, use skill-creator instead.
data-ai
Discover and load skills on demand from /mnt/skills/user/. Use when you need a capability but don't know which skill provides it, when the boot-emitted skill list is names-only and you need a full description, or when you want to list the catalog. Verbs are list (names only), search (rank by name/description match against a query), and show (emit the full SKILL.md for a named skill).
documentation
Reads the visual content of slides, pages, and images the way a human would, not just their embedded text. Use when a PPTX or PDF has image slides, screenshots, charts, scanned figures, or flattened-to-image layouts that the built-in pptx/pdf skills read as empty; when asked to transcribe, describe, OCR, or extract what is shown in an image, slide deck, or document page; or when embedded-text extraction returned little or nothing from a visually rich file. Triggers on 'read this deck', 'what's on these slides', 'transcribe', 'OCR', 'extract text from image', 'describe this chart/diagram', .pptx/.pdf/.png/.jpg with visual content.
development
Portrait Mode for SVGs — foveated vectorization with 4-zone selective detail. Combines vision annotations, MediaPipe segmentation/landmarks, and optional saliency. Like phone portrait mode, but vectorized. Use when vectorizing a portrait or photo where subject detail should outrank background detail.