lsp-inspect
关于
lsp-inspect performs comprehensive code quality audits using LSP analysis to detect issues like dead code, concurrency bugs, and silent failures across 25+ languages. It features batch scanning, diff-based comparisons, and provides severity-ranked reports with fix suggestions. Use this skill for deep, automated code reviews and maintaining quality across entire projects or specific changes.
快速安装
Claude Code
推荐npx skills add blackwell-systems/agent-lsp -a claude-code/plugin add https://github.com/blackwell-systems/agent-lspgit clone https://github.com/blackwell-systems/agent-lsp.git ~/.claude/skills/lsp-inspect在 Claude Code 中复制并粘贴此命令以安装该技能
技能文档
Requires the agent-lsp MCP server.
lsp-inspect
Full code quality audit for a file, package, or directory. Combines LSP batch
analysis (blast_radius) with targeted per-symbol checks and LLM-driven
heuristic analysis. Produces a severity-tiered findings report with confidence
tiers and fix suggestions.
When to Use
- Auditing a package before a release or major refactor
- Finding dead code, untested exports, and error handling gaps in unfamiliar code
- Reviewing code quality of an external codebase for contribution opportunities
- Pre-merge quality gate on a set of changed files
- Batch inspection of an entire directory with ranked output
- Comparing branch changes against main to find newly introduced issues
Input
/lsp-inspect <target> [--checks <type1>,<type2>] [--json] [--top N] [--diff]
Target can be:
- A file path:
/lsp-inspect src/handlers/auth.go - A directory/package:
/lsp-inspect internal/runnables/ - Multiple targets:
/lsp-inspect pkg/a.go pkg/b.go
Directory detection: When target is a directory, walk all .go, .ts, .py
files in it recursively. Produce a ranked report: "Top N findings sorted by
severity then blast radius."
Flags:
--checks <type1>,<type2>: only run listed check types (default: all applicable)--json: emit structured JSON instead of markdown--top N: Maximum findings to report (default 20). Only applies to directory/batch mode.--diff: Only inspect files changed vs main branch. Filter findings to lines within the diff ranges. Output header: "New issues introduced by this branch."
Check Taxonomy
| Check | What it finds | LSP strategy |
|---|---|---|
dead_symbol | Exported symbol with zero references | Tier 1A: blast_radius batch; Tier 1B: find_references per-symbol |
test_coverage | Exported symbol with no test callers | Tier 1A: blast_radius test_callers field |
silent_failure | Error/exception suppressed without re-raise or logging | Read code, identify bare except:, empty if err != nil {}, swallowed returns |
error_wrapping | Error returned/raised without context | Read code, identify return err without fmt.Errorf wrapping or raise without from |
coverage_gap | Unhandled input, error path, or code branch | Read code, identify switch/match without default, unchecked type assertions |
doc_drift | Docstring/comment that doesn't match the actual signature | Compare inspect_symbol hover text against source |
panic_not_recovered | Unhandled crash in a goroutine or async context | Read code, identify go func() without recover, unguarded .unwrap() |
context_propagation | Function receives context but creates a fresh root for callees | Read code, identify context.Background() in functions with ctx parameter |
unrecovered_concurrent_entry | Concurrent entry point without recovery | Read code, identify goroutines/threads/tasks without try-catch or recover |
unchecked_shared_state | Type assertion or cast on concurrent data structure without safety check | Read code, identify bare .(*Type) on sync.Map, unchecked casts on ConcurrentHashMap |
channel_never_closed | Channel or queue created but never closed in the same package | Read code + grep, find creation sites without matching close/shutdown |
shared_field_without_sync | Field accessed from concurrent contexts without synchronization | blast_radius (sync_guarded) + find_callers (cross_concurrent) |
Execution
Step 0: Initialize and verify workspace
mcp__lsp__start_lsp(root_dir="<repo_root>")
Open one file per package being audited:
mcp__lsp__open_document(file_path="<target_file>", language_id="<lang>")
Warm-up check (mandatory): Pick one symbol you know is actively used.
Call find_references on it. If it returns [], wait 3-5 seconds and retry.
Do not proceed until a known-active symbol returns >= 1 reference.
Step 0.5: Diff mode file selection
When --diff is set:
- Run
git diff --name-only mainto get changed files - Run
git diff mainto get line-level change ranges - Use only changed files as inspection targets
- After Step 3, filter findings: keep only those whose File:Line falls within a changed line range from the diff
- Prepend output with: '## New issues introduced by this branch'
Step 1: Batch analysis (Tier 1A)
Call blast_radius once per file in the target:
mcp__lsp__blast_radius(changed_files=["/abs/path/file.go"], include_transitive=false)
This returns all exported symbols with:
non_test_callers: count of production code referencestest_callers: count of test file references
Classify immediately:
non_test_callers == 0 AND test_callers == 0-> dead symbol candidate (confidence: verified)non_test_callers == 0 AND test_callers > 0-> test-only (may be dead, confidence: suspected)non_test_callers > 0 AND test_callers == 0-> untested export (confidence: verified)
If blast_radius fails or is unavailable, fall back to Tier 1B
(find_references per-symbol) for dead_symbol checks.
Step 2: Heuristic checks (LLM-driven)
Read the source code of each file (use offset/limit for files over 500 lines). Apply the following checks by reading and reasoning about the code:
silent_failure: Look for:
- Go:
if err != nil { return }(no error returned), bare_ = fn() - Python: bare
except:orexcept Exception: pass - TypeScript: empty
.catch(() => {}),try {} catch(e) {} - Rust:
.unwrap_or_default()on fallible ops that should propagate
error_wrapping: Look for:
- Go:
return errwithoutfmt.Errorf("context: %w", err) - Python:
raise ValueError(str(e))withoutfrom e - TypeScript:
throw ewithout wrapping in a contextual error
coverage_gap: Look for:
- Switch/match without exhaustive cases or default branch
- Unchecked type assertions (
v := x.(Type)vsv, ok := x.(Type)) - Missing nil/null checks before dereference after fallible calls
doc_drift: For exported functions, compare:
- Parameter names in docstring vs actual signature
- Return type described in doc vs actual return
- Use
inspect_symbolhover text to cross-reference
panic_not_recovered: Look for:
- Go:
go func() { ... }()withoutdefer recover() - Rust:
.unwrap()or.expect()in non-test, non-main code - Python: bare thread creation without exception handling
context_propagation: Look for:
- Functions that accept
ctx context.Contextbut callcontext.Background()orcontext.TODO()internally
unrecovered_concurrent_entry: Detect concurrent entry points without recovery. Language-specific patterns (check by language family):
- Go:
go func() { ... }()where the function body has nodefer func() { if r := recover()pattern. Weight: library transport code (error severity), application code with middleware protection (info). - Java/Kotlin/Scala:
new Thread(...)orExecutorService.submit(...)without try-catch wrapping the Runnable body, and noUncaughtExceptionHandlerset on the thread. - C#:
Task.Run(...)ornew Thread(...)without try-catch in the delegate body. - C/C++:
pthread_createorstd::threadwithout exception handling in the thread function. - Rust:
std::thread::spawnwithoutcatch_unwindin the closure. Also flag.unwrap()inside spawned threads (panics kill only that thread but lose the error). - Swift:
DispatchQueue.asyncorTask { }without do-catch. - Python:
threading.Thread(target=...)without try-except in the target function.asyncio.create_task()without error handling on the awaited result. - TypeScript/JavaScript:
new Worker()withoutonerrororerrorevent handler.Promiseconstructor without.catch()on the chain. - Zig:
try std.Thread.spawnwithout error handling on the spawned function. - Elixir/Erlang/Gleam: Skip (actor model with supervisors; unrecovered processes are by design).
- Lua/Bash/SQL: Skip (no concurrency primitives).
unchecked_shared_state: Detect unsafe type operations on concurrent data structures:
- Go:
sync.Map.Load(),.LoadOrStore(), or.LoadAndDelete()followed by a bare type assertionactual.(*Type)without the, okpattern. The safe pattern isv, ok := actual.(*Type). - Java:
ConcurrentHashMap.get()with unchecked cast and noinstanceofguard. - C#:
ConcurrentDictionaryvalue retrieval with unchecked cast. - Other languages: skip (dynamic typing or type system prevents this class of bug).
channel_never_closed: Detect channels or queues that are created but never closed:
- Go:
make(chan T)ormake(chan T, N)whereclose(channelName)does not appear in the same package. May indicate goroutine leaks (receivers block onrangeforever). - Python:
queue.Queue()creation without a sentinel value pattern (queue.put(None)+if item is None: break). - Rust:
mpsc::channel()where the sender is never dropped or explicitly closed. - TypeScript:
new MessageChannel()ornew BroadcastChannel()without.close(). - Java:
BlockingQueuecreation without a poison pill or shutdown pattern. - Other languages: skip if no channel/queue primitives.
shared_field_without_sync: Detect struct/class fields accessed from multiple concurrent contexts without synchronization. This check composes two tools:
- Call
blast_radiuson the target file. For each symbol wheresync_guarded: false(or absent), the symbol's type lacks sync primitives. - For each such symbol, call
find_callerswithcross_concurrent: true. Ifconcurrent_callersis non-empty, the symbol is called from a concurrent context (goroutine, thread, async task) without synchronization. - Flag any symbol where: (a) it modifies state (writes to fields, not a pure read-only function), AND (b) it has concurrent callers, AND (c) its parent type is not sync-guarded.
Language-agnostic: blast_radius provides sync_guarded, find_callers
provides concurrent_callers. The check logic is identical regardless of
whether the concurrent boundary is a goroutine, thread, or async task.
Severity:
- error: field written from 2+ concurrent contexts with no sync (data race)
- warning: field written from 1 concurrent context (potential race under load)
- info: field read-only from concurrent contexts (likely safe, but flag for review)
Step 3: Cross-check and classify
For each finding, assign:
Severity (calibrated by blast radius):
error: Will cause runtime failure, data loss, or resource leak, OR any finding wherenon_test_callers >= 10(high blast radius amplifies severity)warning: May cause confusion, maintenance burden, or subtle bugs, OR findings wherenon_test_callersis 3-9info: Style issue or improvement opportunity, ORnon_test_callers <= 2
Use the non_test_callers count from Step 1's blast_radius result as a
severity multiplier. A silent failure in a function with 50 callers is
error-severity; the same pattern with 2 callers is info.
Cross-file impact scoring: For every finding, look up the symbol's
non_test_callers count from Step 1's blast_radius result. Use this as a
severity multiplier: if non_test_callers >= 10, escalate severity by one tier
(info->warning, warning->error). Document the caller count in the finding.
Confidence tiers:
verified: LSP-confirmed (Tier 1A/1B) or unambiguous code pattern (act immediately)suspected: Heuristic match with possible false positive (pattern match, investigate first)advisory: Grep-based or uncertain pattern match (style, optional)
Step 4: Output
Produce the findings report:
## Inspection Report: <target>
**Files analyzed:** N
**Checks applied:** [list]
**Findings:** E errors, W warnings, I info
### Errors
| # | Check | File:Line | Finding | Confidence | Fix |
|---|-------|-----------|---------|------------|-----|
| 1 | dead_symbol | pkg/foo.go:42 | `UnusedHelper` has 0 references (0 callers) | verified (LSP) | Remove lines 42-55 (function `UnusedHelper`) |
### Warnings
| # | Check | File:Line | Finding | Confidence | Fix |
|---|-------|-----------|---------|------------|-----|
| 1 | error_wrapping | pkg/bar.go:88 | `return err` without context wrapping (5 callers) | verified | Change `return err` to `return fmt.Errorf("funcName: %w", err)` |
| 2 | test_coverage | pkg/foo.go:15 | `ProcessInput` has 0 test callers (8 callers) | verified (LSP) | Add test for `ProcessInput` in foo_test.go |
### Info
| # | Check | File:Line | Finding | Confidence | Fix |
|---|-------|-----------|---------|------------|-----|
| 1 | doc_drift | pkg/foo.go:20 | Docstring mentions `timeout` param, signature has `deadline` (1 caller) | suspected | Update docstring parameter name from timeout to deadline |
Fix suggestions per check type:
dead_symbol: "Remove lines N-M (functionFuncName)"error_wrapping: "Changereturn errtoreturn fmt.Errorf(\"funcName: %w\", err)"silent_failure: "Addreturn fmt.Errorf(...)after the if block"test_coverage: "Add test forFuncNamein file_test.go"coverage_gap: "Add default case to switch statement at line N"doc_drift: "Update docstring parameter name from X to Y"panic_not_recovered: "Adddefer func() { if r := recover()... }()at goroutine start"context_propagation: "Replacecontext.Background()withctxparameter"
When --json is passed, emit structured JSON with the same fields.
Step 4.5: Batch ranking
When multiple files are analyzed, sort all findings by: (1) severity tier (error > warning > info), (2) blast radius (non_test_callers descending), (3) file path alphabetically. Emit only the top N findings (default 20, controlled by --top flag). Append a summary line: 'Showing N of M total findings.'
Step 5: Persist results
After producing the findings report, write a JSON file to
.agent-lsp/last-inspection.json in the workspace root. The JSON schema:
{
"target": "<original target path>",
"timestamp": "<ISO 8601>",
"files_analyzed": N,
"findings": [
{
"severity": "error|warning|info",
"confidence": "verified|suspected|advisory",
"check": "<check_type>",
"file": "<path>",
"line": N,
"finding": "<description>",
"fix": "<exact fix text>",
"blast_radius": N
}
],
"summary": {"errors": N, "warnings": N, "info": N}
}
This file is served by the inspect://last MCP resource for programmatic access.
Caveats
-
Re-exports: Symbols in
__init__.py(Python),index.ts(TypeScript), or public API surface files may appear dead locally but are consumed externally. Check__all__, barrel exports, and package-level re-exports before classifying. -
Registration patterns: Symbols passed as values to framework registrars (HTTP handlers, plugin hooks) show zero LSP references. Grep wiring files before confirming dead.
-
Library public API: If the target is a library consumed by external repos, zero internal references doesn't mean dead. Use
--consumer-reposor note as "library export, verify externally." -
Heuristic checks are advisory. Silent failure and error wrapping checks depend on LLM reasoning about intent. False positives are expected; always review before acting on findings.
-
Large files: For files over 500 lines, read targeted sections (use offset/limit). Do not read entire large files into context.
GitHub 仓库
相关推荐技能
evaluating-llms-harness
测试该Skill通过60+个学术基准测试(如MMLU、GSM8K等)评估大语言模型质量,适用于模型对比、学术研究及训练进度追踪。它支持HuggingFace、vLLM和API接口,被EleutherAI等行业领先机构广泛采用。开发者可通过简单命令行快速对模型进行多任务批量评估。
cloudflare-cron-triggers
测试这个Claude Skill提供了关于Cloudflare Cron Triggers的完整知识库,用于通过cron表达式定时执行Workers。它支持配置周期性任务、维护作业和自动化工作流,并能处理常见的cron触发错误。开发者可以用它来设置定时任务、测试cron处理器,并集成Workflows和Green Compute功能。
webapp-testing
测试该Skill为开发者提供了基于Playwright的本地Web应用测试工具集,支持自动化测试前端功能、调试UI行为、捕获屏幕截图和查看浏览器日志。它包含管理服务器生命周期的辅助脚本,可直接作为黑盒工具运行而无需阅读源码。适用于需要快速验证本地Web应用界面和交互功能的开发场景。
finishing-a-development-branch
测试这个Skill用于开发分支完成后的集成决策,当代码实现完成且测试通过时,它会引导开发者选择合适的工作流。它首先验证测试状态,然后提供合并、创建PR或清理等结构化选项。核心价值在于确保代码质量的同时,标准化分支收尾流程。
