agent-lsp

0

Stateful MCP server over real language servers. 50 tools, 30 CI-verified languages, 20 agent workflows. Persistent sessions, speculative execution, single Go binary.

20 skills

lsp-cross-repo

Cross-repository analysis — find all callers of a library symbol in one or more consumer repos. Use when refactoring a shared library and need to understand how consumers use it.

> Requires the agent-lsp MCP server. # lsp-cross-repo Multi-root cross-repo caller analysis for library + consumer workflows. Finds all usages of a library symbol across one or more consumer codebases in a single call. Read-only — does not modify any files. ## When to use - Before changing a library API: find all callers in every consumer - Before deleting a symbol: verify it has no cross-repo dependents - When a change in repo A might break repo B or C - Auditing how internal packages are used across services Use `/lsp-impact` instead for single-repo blast-radius analysis. ## Workflow ### Step 1 — Initialize the primary workspace Start the language server on the library root if not already running: ``` mcp__lsp__start_lsp({ "root_dir": "/path/to/library" }) ``` ### Step 2 — Locate the library symbol Find the symbol's definition to get `file_path`, `line`, and `column`: ``` mcp__lsp__get_workspace_symbols({ "query": "<symbol-name>" }) ``` Pick the result in the library repo (not a test file). ### Step 3 — Find all cross-repo references (primary step) Call `get_cross_repo_references` with the symbol location and all consumer repo roots. This adds each consumer as a workspace folder, waits for indexing, runs `get_references` across all roots, and returns results partitioned by repo: ``` mcp__lsp__get_cross_repo_references({ "symbol_file": "/abs/path/to/library/file.go", "line": <line>, "column": <column>, "consumer_roots": [ "/abs/path/to/consumer-a", "/abs/path/to/consumer-b" ] }) ``` Returns: - `library_references` — usages within the library itself - `consumer_references` — a map of `consumer-root → [file:line ...]` - `warnings` — any roots that could not be indexed (check these manually) **Decision after Step 3:** | Result | Action | |--------|--------| | No consumer refs | Safe to change — verify `warnings` is empty first | | Consumer refs found | Run `/lsp-impact` on each call site before editing | | `warnings` non-empty | Re-add that root manually and retry Step 3 | ### Step 4 — Callers and implementations (optional) For a deeper look at how consumers call the symbol: ``` mcp__lsp__call_hierarchy({ "file_path": "<library-file>", "line": <line>, "column": <column>, "direction": "incoming" }) ``` For interfaces — all consumer-side implementations: ``` mcp__lsp__go_to_implementation({ "file_path": "<library-file>", "line": <line>, "column": <column> }) ``` ## Output format ``` ## Library-internal references - file:line — brief context ## Consumer references ### /path/to/consumer-a - file:line — brief context ### /path/to/consumer-b - file:line — brief context ``` ## Decision guide | Situation | Action | |-----------|--------| | No consumer refs, warnings empty | Safe to change | | Consumer refs found | Run `/lsp-impact` on each call site before editing | | `warnings` lists a consumer root | That root failed indexing — check LSP logs | | Consumer uses interface, not concrete type | Use `go_to_implementation` to find all implementors | ## Example ``` # Refactoring ParseConfig in a shared config library used by 3 services start_lsp(root_dir="/repos/config-lib") get_workspace_symbols(query="ParseConfig") # find definition → file:42:6 get_cross_repo_references( symbol_file="/repos/config-lib/pkg/config/parser.go", line=42, column=6, consumer_roots=["/repos/api-service", "/repos/worker-service", "/repos/batch-job"] ) # → library_references: 2 # → consumer_references: {api-service: [main.go:14, app.go:31], worker-service: [runner.go:8]} # → warnings: [] ```

lsp-dead-code

Enumerate exported symbols in a file and surface those with zero references across the workspace. Use when auditing for dead code, cleaning up APIs, or checking which exports are safe to remove.

> Requires the agent-lsp MCP server. # lsp-dead-code Audit an exported symbol list for zero-reference candidates. Calls `get_document_symbols` to enumerate symbols, then checks each exported symbol with `get_references` to find callers. Produces a classified report. ## When to Use Use this skill when you want to identify dead code in a file — exported symbols that are defined but never called anywhere in the workspace. Common use cases: - Cleaning up APIs before a release - Identifying legacy exports that can be safely removed - Auditing a package for unused public surface area **Important:** This skill surfaces *candidates*. Always review results manually before deleting anything. See the Caveats section below. ## What counts as "exported" | Language | Exported means... | |------------|------------------------------------------------------------------------| | Go | Identifier starts with an uppercase letter (e.g. `MyFunc`, `MyType`) | | TypeScript | Has `export` keyword; or is a public class member (no `private`) | | Python | Not prefixed with `_`; or explicitly listed in `__all__` | | Java/C# | Has `public` or `protected` visibility modifier | | Rust | Has `pub` keyword | ## Prerequisites If LSP is not yet initialized, call `mcp__lsp__start_lsp` with the workspace root first: ``` mcp__lsp__start_lsp({ "root_dir": "/your/workspace" }) ``` agent-lsp supports auto-inference from file paths, so explicit start is only required when switching workspaces or on a cold session. ## Step 0 — Verify indexing is complete (mandatory) **Do not skip this step.** An under-indexed workspace returns `[]` for symbols that ARE referenced, producing false dead-code candidates. Pick one symbol you know is actively used (e.g. the primary constructor, a widely-called utility function). Call `get_references` on it: ``` mcp__lsp__get_references({ "file_path": "/abs/path/to/file.go", "line": <known-active symbol line>, "column": <known-active symbol column>, "include_declaration": false }) ``` **If this returns `[]`:** the workspace is not indexed. Wait 3–5 seconds and retry. Do not proceed until a known-active symbol returns ≥1 reference. If it never returns results after 15 seconds, restart the LSP server with `mcp__lsp__restart_lsp_server` and re-open the target file. ## Step 1 — Open the file and enumerate symbols Open the file so the language server tracks it, then fetch all symbols: ``` mcp__lsp__open_document({ "file_path": "/abs/path/to/file.go" }) mcp__lsp__get_document_symbols({ "file_path": "/abs/path/to/file.go" }) ``` Collect the full symbol list. Filter to **exported symbols only** using the language-appropriate rule from the table above. **Coordinate note:** `get_document_symbols` returns 1-based coordinates. Pass `selectionRange.start.line` and `selectionRange.start.character` directly to `get_references` — no conversion needed. **"no identifier found" error:** This means the column points to whitespace or a keyword rather than the identifier name. This happens with methods whose receiver prefix shifts the name rightward (e.g. `func (c *Client) MethodName` — the name starts at column 21, not column 1). Fix: grep the declaration line for the symbol name to find its exact column: ``` grep -n "MethodName" file.go # count characters to find the 1-based column of the name ``` Then retry `get_references` with the corrected column. ## Step 2 — Check references for each exported symbol For each exported symbol, call `get_references` with `include_declaration: false` so the definition site itself is excluded from the count. A count of 0 means no callers, not no occurrences. ``` mcp__lsp__get_references({ "file_path": "/abs/path/to/file.go", "line": <selectionRange.start.line>, "column": <selectionRange.start.character>, "include_declaration": false }) ``` Record the result for each symbol: ``` { symbol_name, kind, line, reference_count, locations[] } ``` **Batching note:** For files with many exported symbols (>20), process in batches of 5–10 to avoid overwhelming the LSP server. **Zero-reference cross-check (required before classifying as dead):** When `get_references` returns `[]` for a symbol that looks foundational (a handler, a constructor, a type used as a field), do not trust LSP alone. LSP can miss references made through value-passing, interface satisfaction, or function registration patterns (e.g. `server.AddResource(HandleFoo)`). Before classifying as dead, run a text search in the primary wiring files: ``` grep -r "SymbolName" main.go server.go cmd/ internal/ ``` If grep finds the name in a registration or assignment context, the symbol is active — LSP just couldn't resolve the indirect reference. Update your classification accordingly. ## Step 3 — Classify and report Classify each exported symbol by reference count: - **Zero references (LSP + grep)** — confirmed dead candidate. Flag with WARNING. - **Zero LSP, found by grep** — active via registration/value pattern. Mark as ACTIVE. - **1–2 references** — review manually. May be test-only usage. - **3+ references** — active symbol. Not dead code. For test-only references: if all locations are in `_test.go` files (Go) or files named `*.test.*` / `*.spec.*`, mark the symbol as "test-only" in the report rather than "zero-reference". Produce the Dead Code Report using the format in [references/patterns.md](references/patterns.md). ## Caveats The following cases produce zero LSP references even though the symbol IS used at runtime. Do not delete any zero-reference candidate without manual review: 1. **Incomplete indexing.** `get_references` only searches files open or indexed by the language server. If the workspace is partially indexed, results may be incomplete. The Step 0 warm-up check catches this. 2. **Registration patterns.** Symbols passed as values to registration functions (e.g. `server.AddTool(HandleFoo)`, `http.HandleFunc("/", handler)`) appear as zero LSP references from the *definition site* because gopls tracks the call to the registrar, not the handler name. Always grep wiring files for zero-reference handlers before classifying as dead. 3. **Reflection and dynamic dispatch.** Symbols used via reflection (`reflect.TypeOf` in Go, `Class.forName` in Java) or dynamic dispatch have no static call sites visible to the LSP. 4. **`//go:linkname` and assembly.** Go symbols linked via `//go:linkname` or referenced from assembly files will show zero LSP references. 5. **Library public API.** Exported symbols called from external packages not present in the workspace will show zero references even if consumers exist. 6. **Declaration excluded from count.** The definition site is not counted (`include_declaration: false`). A count of 0 means no callers found, not that the symbol never appears in the source tree. 7. **Always review before deleting.** Zero LSP references is a signal to investigate, not a guarantee the symbol is unused. ## Step 4 — Next steps After generating the report: - **For each zero-reference symbol (confirmed by grep):** Run `lsp-impact` on the symbol to confirm. If `lsp-impact` also finds zero references, it is safe to consider for removal. Still check the Caveats section above. - **For symbols with only test-file references:** Mark as "test-only" in the report. These may be candidates for removal if the tests themselves are redundant, but should not be deleted without reviewing whether the tests serve a documentation or contract purpose. - **For symbols with 1–2 references in production code:** These are likely active but lightly used. Do not remove without checking whether they are part of a committed public API.

lsp-docs

Three-tier documentation lookup for any symbol — hover → offline toolchain doc → source definition. Use when hover text is absent, insufficient, or the symbol is in an unindexed dependency.

> Requires the agent-lsp MCP server. # lsp-docs Three-tier documentation lookup for any symbol. Works when the language server is unavailable, when hover returns empty results, or when the symbol lives in a transitive dependency that gopls or pyright does not index. Read-only — does not modify any files. **Invocation:** User provides `symbol_name` in fully-qualified form (e.g. `"fmt.Println"`, `"std::vec::Vec::new"`, `"os.path.join"`). Optionally provide `file_path` for any file in the same module, which improves Go package resolution. --- ## Decision table | Situation | Recommended tier | |-----------|-----------------| | Symbol in current workspace | Tier 1 (hover) | | Symbol in direct dependency | Tier 2 (toolchain doc) | | Symbol in transitive dep (not indexed by LSP) | Tier 2 | | No LSP server available | Tier 2 → Tier 3 | | No toolchain installed (e.g., Rust without cargo) | Tier 3 | --- ## Tier 1 — LSP hover (fast, live, position-based) Call `get_info_on_location` with the file path and cursor position (1-based). ``` mcp__lsp__get_info_on_location({ "file_path": "/abs/path/to/file.go", "line": 42, "column": 8 }) ``` If the result contains a non-empty `contents` field with useful type and doc information, **stop here and return it**. Hover is the fastest path and should always be tried first. If hover returns empty `contents`, or the language server is not initialized, proceed to Tier 2. --- ## Tier 2 — Offline toolchain documentation (authoritative, name-based) Call `get_symbol_documentation` with the fully-qualified symbol name and `language_id`. This fetches documentation from the local toolchain (go doc, pydoc, cargo doc) without requiring an LSP session. Works for transitive dependencies that the language server does not index. ``` mcp__lsp__get_symbol_documentation({ "symbol": "fmt.Println", "language_id": "go", "file_path": "/abs/path/to/any/file/in/the/module.go", // optional, improves Go pkg resolution "format": "markdown" // optional: wraps signature in code fence }) ``` **Interpreting the result:** - If `source == "toolchain"`: return the `doc` and `signature` fields to the user. These are authoritative — sourced directly from the installed toolchain, ANSI-stripped, and ready for display. - If `source == "error"`: note the `error` field (toolchain failure reason) and proceed to Tier 3. --- ## Tier 3 — Source definition (last resort) Call `go_to_definition` to navigate to the symbol definition, then call `get_symbol_source` to extract the source text. This always works when the symbol exists in the workspace or module cache, even without a language server. ``` mcp__lsp__go_to_definition({ "file_path": "/abs/path/to/caller.go", "line": 42, "column": 8 }) // → returns definition location mcp__lsp__get_symbol_source({ "file_path": "<definition file from above>", "line": <definition line from above> }) // → returns full function/type source text ``` Present the source text to the user with a note that it is raw source, not rendered documentation. --- ## lsp-impact integration note Before running `lsp-impact` on an unfamiliar symbol, call `get_symbol_documentation` to understand its signature and semantics. This prevents misinterpreting the impact report due to incorrect assumptions about what the symbol does. --- ## Example ``` Goal: look up documentation for http.ListenAndServe in a Go project Tier 1 — get_info_on_location: cursor on "ListenAndServe" in main.go:14:6 → contents: "" (empty — server not initialized) Proceed to Tier 2. Tier 2 — get_symbol_documentation: symbol: "net/http.ListenAndServe" language_id: "go" file_path: "/Users/you/code/myapp/main.go" format: "markdown" Result: { "symbol": "net/http.ListenAndServe", "language": "go", "source": "toolchain", "doc": "func ListenAndServe(addr string, handler http.Handler) error\n\nListenAndServe listens on the TCP network address addr and then calls Serve...", "signature": "func ListenAndServe(addr string, handler http.Handler) error", "error": "" } source == "toolchain" → return doc and signature to user. Done. Tier 3 — skipped (Tier 2 succeeded) ```

lsp-edit-export

Safe workflow for editing exported symbols or public APIs. Use when changing a function signature, modifying a public type, or altering any symbol used outside its own package — finds all callers first so nothing breaks silently.

> Requires the agent-lsp MCP server. # lsp-edit-export Safe workflow for editing exported symbols. Always discovers all callers before touching any code, then verifies the change is clean. ## When to Use Use this skill whenever you intend to change the signature, name, or behavior of an **exported symbol**: a symbol visible outside its defining package or module. **Language-specific definitions:** | Language | Exported means... | |------------|------------------------------------------------------------------------| | Go | Identifier starts with an uppercase letter (e.g. `MyFunc`, `MyType`) | | TypeScript | Has `export` keyword; or is a public class member (no `private`) | | Python | Not prefixed with `_`; or explicitly listed in `__all__` | | Java/C# | Has `public` or `protected` visibility modifier | | Rust | Has `pub` keyword | If you are unsure whether a symbol is exported, treat it as exported and run this workflow anyway. The cost is a few extra tool calls; the benefit is never breaking a hidden caller. **Do NOT skip this workflow** even when you believe there are zero callers. The confirmation gate in step 3 exists precisely for that case. ## Workflow **If LSP is not yet initialized**, call `mcp__lsp__start_lsp` with the workspace root first. (agent-lsp supports auto-inference from file paths, so explicit start is only required when switching workspaces or on a cold session.) ### Step 1 — Locate the symbol Use `go_to_symbol` to find the symbol's definition by name, without needing to know its file path or line number in advance: ``` mcp__lsp__go_to_symbol({ "symbol_path": "PackageName.ExportedFunction", "workspace_root": "/abs/path/to/repo" // optional, narrows scope }) ``` `symbol_path` uses dot notation. For a top-level function `Encode` in package `codec`, use `"codec.Encode"`. For a method `Reset` on type `Buffer`, use `"Buffer.Reset"`. The last component is the leaf name; any prefix is used to disambiguate when multiple symbols share the same leaf. The tool returns a `FormattedLocation` with the definition file and 1-indexed line/column. Record this position — you will need it in step 2. ### Step 2 — Discover all callers Call `get_references` using the `position_pattern` field to express the cursor position as a readable text pattern rather than raw coordinates. The `@@` marker indicates exactly where the cursor sits (the character immediately after `@@`): ``` mcp__lsp__get_references({ "file_path": "<definition file from step 1>", "position_pattern": "func @@ExportedFunction(", "include_declaration": false }) ``` The `@@` must immediately precede the first character of the symbol name. Examples: - `"func @@Encode("` — Go function declaration - `"type @@Buffer struct"` — Go type declaration - `"export function @@parse("` — TypeScript function - `"class @@Parser:"` — Python class - `"pub fn @@process("` — Rust function If `position_pattern` is unavailable on your MCP client, fall back to the `line` and `column` fields from the location returned in step 1. The tool returns a list of reference locations across the codebase. ### Step 3 — Confirmation gate (REQUIRED — never skip) Before making any change, present the impact summary to the user and ask for explicit confirmation. This gate is mandatory even when the caller count is zero. Format the gate as follows: ``` ## Impact Check: <SymbolName> - Definition: <file>:<line> - Callers found: N reference(s) in M file(s) Files with callers: - <file1> - <file2> ... Proceed with the edit? [y/n] ``` If the user answers **n**, stop. Do not make any edits. If the user answers **y**, proceed to step 4. **Why this gate exists even for 0 callers:** the LSP index may be incomplete (e.g. files not yet saved, workspace not fully loaded). Zero callers is a data point, not a guarantee. ### Step 4 — Make the edit Apply your intended change using `Edit` or `Write`. Follow the standard edit workflow for the language. If renaming, update all call sites identified in step 2 as well — do not leave broken callers. Collect diagnostics **before** the edit so you have a baseline for comparison in step 5: ``` mcp__lsp__get_diagnostics({ "file_path": "<definition file>" }) ``` Then apply the edit and collect diagnostics again after. ### Step 5 — Check diagnostics Compare before and after diagnostic snapshots using the format in [references/patterns.md](references/patterns.md). If new errors appear, fix them before proceeding. Do not run the build with known diagnostic errors outstanding. ### Step 6 — Run the build ``` mcp__lsp__run_build({ "workspace_root": "/abs/path/to/repo" }) ``` A clean build confirms no compilation errors across all affected packages. If the build fails, diagnose using the error output and diagnostic data from step 5. Fix and re-run until the build passes. ### Step 7 — Report Emit the final output block: ``` ## Edit Summary - Symbol: <name> (<kind>) - Callers found: N in M files - Diagnostics: net +N/-N - Build: PASSED / FAILED ``` If build is FAILED, include the first 3–5 error lines and a brief diagnosis. ## Example ``` Goal: rename exported function `ParseConfig` → `LoadConfig` in pkg/config Step 1 — go_to_symbol: symbol_path="config.ParseConfig" → pkg/config/parser.go:42:6 Step 2 — get_references: position_pattern="func @@ParseConfig(" → 7 references in 4 files Step 3 — gate: ## Impact Check: ParseConfig - Definition: pkg/config/parser.go:42 - Callers found: 7 in 4 files Files: cmd/main.go, internal/app.go, internal/loader.go, pkg/config/parser_test.go Proceed? [y/n] → y Step 4 — edit: rename declaration + all 7 call sites Step 5 — diagnostics: net 0 (no new errors) Step 6 — build: PASSED Step 7 — report: ## Edit Summary - Symbol: LoadConfig (function) - Callers found: 7 in 4 files - Diagnostics: net 0 - Build: PASSED ``` ## Note on position_pattern `position_pattern` with `@@` is a agent-lsp extension. If your MCP client or server does not support it, fall back to explicit `line` and `column` parameters from the location returned by `go_to_symbol` in step 1.

lsp-edit-symbol

Edit a named symbol without knowing its file or position. Use when you want to change a function, type, or variable by name and don't have exact coordinates. Resolves the symbol to its definition, retrieves its full range, and applies the edit.

# lsp-edit-symbol Edit a named symbol (function, type, variable) without needing its exact file path or line/column. Composes `go_to_symbol` → `get_document_symbols` → `apply_edit`. ## Workflow ### Step 1 — Locate the symbol ```json { "tool": "get_workspace_symbols", "query": "MyFunc" } ``` Returns a list of matching symbols with file URI and position. Pick the definition (not a test file, not a stub). If multiple matches, use the container name or file path to disambiguate. ### Step 2 — Get the full range ```json { "tool": "get_document_symbols", "file_path": "/path/to/file.go", "language_id": "go" } ``` Find `MyFunc` in the returned tree. The `range` field covers the entire symbol including its body; `selectionRange` covers only the name. Use `range` when replacing the full definition, `selectionRange` when renaming only. ### Step 3 — Apply the edit **Option A — text-match (recommended when you have the old text):** ```json { "tool": "apply_edit", "file_path": "/path/to/file.go", "old_text": "func MyFunc() {", "new_text": "func MyFunc() error {" } ``` No position needed. Tolerates indentation differences. **Option B — positional (when you have the exact range from Step 2):** ```json { "tool": "apply_edit", "workspace_edit": { "changes": { "file:///path/to/file.go": [{ "range": { "start": {"line": 12, "character": 0}, "end": {"line": 18, "character": 1} }, "newText": "func MyFunc() error {\n\treturn nil\n}" }] } } } ``` ## Decision guide | Situation | Approach | |-----------|----------| | Changing signature only | Step 1 → Step 3A with one-line old_text | | Replacing full body | Step 1 → Step 2 → Step 3B with full range | | Symbol name ambiguous | Use `get_workspace_symbols` query + container name filter | | After edit | Run `get_diagnostics` to verify no errors introduced | ## Notes - `get_workspace_symbols` returns declaration sites, not all references. The first non-test result is usually the definition. - Positions in `get_document_symbols` are **1-based** (shifted from LSP convention). `apply_edit` `workspace_edit` expects **0-based** — subtract 1 when using positional mode (Option B). Text-match mode (Option A) requires no position math. - For renames (not edits), use `/lsp-rename` instead — it updates all call sites.

lsp-explore

Tell me about this symbol": hover + implementations + call hierarchy + references in one pass — for navigating unfamiliar code.

> Requires the agent-lsp MCP server. # lsp-explore "Tell me about this symbol" — hover, implementations, call hierarchy, and references in a single pass. Use when navigating unfamiliar code: you get type info, doc comments, who calls it, what implements it, and every reference site without issuing four separate commands. Read-only — does not modify any files. **Invocation:** User provides a symbol name in dot notation (e.g. `"codec.Encode"`, `"Buffer.Reset"`). Optionally provide `workspace_root` to scope the search. --- ## Prerequisites If LSP is not yet initialized, call `mcp__lsp__start_lsp` with the workspace root first. Auto-inference applies when file paths are provided. --- ## Phase 1 — Locate the symbol Call `mcp__lsp__go_to_symbol` with `symbol_path` set to the user-provided name: ``` mcp__lsp__go_to_symbol({ "symbol_path": "Package.SymbolName", // dot notation; e.g. "codec.Encode" "workspace_root": "<root>" // optional }) → returns: file, line, column (1-indexed) ``` Record the returned `file`, `line`, and `column`. If `go_to_symbol` returns nothing, report: > Symbol not found: `<name>` > Check the dot-notation path (e.g. "Package.Symbol") and ensure the workspace > root covers the file. Stop immediately — do not proceed to Phase 2. Then open the file so the language server has it in view: ``` mcp__lsp__open_document({ "file_path": "<file from go_to_symbol>" }) ``` --- ## Phase 2 — Hover (always available) Call `mcp__lsp__get_info_on_location` at the definition location: ``` mcp__lsp__get_info_on_location({ "file_path": "<file from Phase 1>", "line": <line from Phase 1>, "column": <column from Phase 1> }) ``` Store the result as `hover_text`. If the call fails or returns nothing, set `hover_text` to an empty string. Do not stop. --- ## Phase 3 — Implementations (capability-gated) Call `mcp__lsp__get_server_capabilities` to see what the server supports: ``` mcp__lsp__get_server_capabilities() → returns: supported_tools list ``` If `go_to_implementation` appears in `supported_tools`, call it: ``` mcp__lsp__go_to_implementation({ "file_path": "<file from Phase 1>", "line": <line from Phase 1>, "column": <column from Phase 1> }) → returns: list of implementation locations (file, line) ``` Record locations as `implementations`. If `go_to_implementation` is **not** in `supported_tools`, record `"not supported by this server"` — do not stop. --- ## Phase 4 — Call hierarchy and references (run in parallel) Issue both calls in the same message — they are independent: ### 4a — Incoming callers Only if `call_hierarchy` appears in `supported_tools`: ``` mcp__lsp__call_hierarchy({ "file_path": "<file from Phase 1>", "line": <line from Phase 1>, "column": <column from Phase 1>, "direction": "incoming" }) → returns: list of caller functions with file and line ``` If `call_hierarchy` is **not** in `supported_tools`, note `"not supported by this server"` — do not stop. ### 4b — All reference sites ``` mcp__lsp__get_references({ "file_path": "<file from Phase 1>", "line": <line from Phase 1>, "column": <column from Phase 1>, "include_declaration": false }) → returns: list of reference locations (file, line) ``` Collect all reference locations. Group by file and count distinct files. --- ## Output format — Explore Report Produce the report in this format: ``` ## Explore Report: <SymbolName> ### Definition - File: <file>:<line> - Hover: <hover_text or "unavailable"> ### Implementations (<N> found, or "not supported") [list of file:line entries, or "none found", or "not supported by this server"] ### Callers (incoming call hierarchy) [list of caller function names with file:line, or "none", or "not supported"] ### References (<N> total across <M> files) [list of file:line entries grouped by file, or "none found"] ### Summary - Symbol kind: <inferred from hover or "unknown"> - Reference count: <N> - Files with refs: <M distinct files> - Callers: <K> - Implementations: <P or "not supported"> ``` Keep the report concise. The goal is "understand this symbol in one pass." --- ## Example ``` Goal: understand the exported function `ParseConfig` in pkg/config Phase 1 — go_to_symbol: symbol_path="config.ParseConfig" → pkg/config/parser.go:42:6 open_document: pkg/config/parser.go Phase 2 — get_info_on_location: line=42, column=6 → hover_text: "func ParseConfig(path string) (*Config, error) — reads and validates a config file from path" Phase 3 — get_server_capabilities → go_to_implementation: in supported_tools go_to_implementation: line=42, column=6 → 0 implementations (ParseConfig is a concrete function, not an interface method) Phase 4 (parallel): call_hierarchy direction=incoming → 3 callers: cmd.main (cmd/main.go:14), app.Start (internal/app.go:31), loader.Load (internal/loader.go:55) get_references include_declaration=false → 7 references in 4 files ## Explore Report: ParseConfig ### Definition - File: pkg/config/parser.go:42 - Hover: func ParseConfig(path string) (*Config, error) — reads and validates a config file from path ### Implementations (0 found) none found ### Callers (incoming call hierarchy) - cmd.main — cmd/main.go:14 - app.Start — internal/app.go:31 - loader.Load — internal/loader.go:55 ### References (7 total across 4 files) cmd/main.go: line 14 internal/app.go: lines 31, 87 internal/loader.go: line 55 pkg/config/parser_test.go: lines 12, 34, 56, 78 ### Summary - Symbol kind: function - Reference count: 7 - Files with refs: 4 - Callers: 3 - Implementations: 0 ```

lsp-extract-function

Extract a selected code block into a named function. Primary path uses the language server's extract-function code action; falls back to manual extraction when no code action is available. Validates captured variables, scope shadowing, and compilation after extraction.

> Requires the agent-lsp MCP server. # lsp-extract-function: Extract Code Block into a Named Function **This skill RESTRUCTURES existing code** — it takes code that already exists and moves it into a new function. This is distinct from `/lsp-generate`, which creates NEW code that does not yet exist (stubs, mocks, interface implementations). Use this skill when the code is already written; use `/lsp-generate` when you need to generate code from scratch. **Invocation:** User provides `file_path` (absolute path), `start_line` and `end_line` (1-indexed range), and `new_function_name` (desired name for the extracted function). --- ## Prerequisites If LSP is not yet initialized, call `mcp__lsp__start_lsp` with the workspace root first. Auto-inference applies when file paths are provided, but an explicit start is required when switching workspaces. --- ## Step 1 — Get context (document symbols) Call `mcp__lsp__open_document` to open the file, then call `mcp__lsp__get_document_symbols` to understand the containing function and scope: ``` mcp__lsp__open_document({ "file_path": "<file_path>" }) mcp__lsp__get_document_symbols({ "file_path": "<file_path>" }) ``` This establishes: - Which function contains the selection - Whether `new_function_name` already exists in the file (name collision check) **Mandatory name collision check:** If `new_function_name` already exists as a symbol in the document symbols list, report the conflict and stop immediately: > Cannot extract: function `new_function_name` already exists in this file. > Choose a different name and retry. --- ## Step 2 — Check server capabilities Call `mcp__lsp__get_server_capabilities` to understand what the language server supports: ``` mcp__lsp__get_server_capabilities({}) ``` Check for `codeActionProvider` in the response. Note whether `execute_command` is listed in `executeCommandProvider.commands`. This determines whether the primary path (Step 3) is available. --- ## Step 3 — Primary path: LSP code action Call `mcp__lsp__get_code_actions` with the selection range: ``` mcp__lsp__get_code_actions({ "file_path": "<file_path>", "start_line": N, "start_column": 1, "end_line": M, "end_column": 999 }) ``` Filter the returned actions for extract-function actions: include any action whose `kind` contains `"refactor.extract"` OR whose `title` contains both "Extract" and "function" (case-insensitive). **If an extract-function action is found:** - Display the action title to the user - If the action proposes a different name than `new_function_name`, ask for confirmation before proceeding - Execute via `mcp__lsp__execute_command` if the action has a `command` field: ``` mcp__lsp__execute_command({ "command": "<action.command.command>", "arguments": <action.command.arguments> }) ``` - OR apply directly via `mcp__lsp__apply_edit` if the action has an `edit` field: ``` mcp__lsp__apply_edit({ "workspace_edit": <action.edit> }) ``` - Skip to Step 5 after applying. **If no extract-function action is found:** fall through to Step 4 (manual fallback). --- ## Step 4 — Manual fallback When no code action is available, perform manual extraction: ### a) Analyze the selection Read the selected lines (`start_line` through `end_line`) and identify: - **Parameters:** Variables used inside the selection that are declared outside (captured from outer scope — must become function parameters) - **Return values:** Variables declared inside the selection that are used outside (must be returned from the extracted function) - **Early returns:** Return statements inside the selection (the extracted function must wrap these) ### b) Construct and confirm the proposed signature Build the extracted function signature based on the captured variables analysis. Display the proposed signature to the user before writing: > Proposed extraction: > ``` > func new_function_name(param1 Type1, param2 Type2) (ReturnType, error) { > // selected lines > } > ``` > Proceed with this signature? [y/n] Wait for user confirmation before applying any edit. ### c) Apply the extraction (order matters) Apply edits sequentially — do NOT batch edits from different line regions into a single `apply_edit` call: 1. **First:** Replace the selected lines with a call to the new function: ``` mcp__lsp__apply_edit({ "workspace_edit": { "changes": { "<file_path>": [{ "range": { "start": { "line": start_line-1, "character": 0 }, "end": { "line": end_line, "character": 0 } }, "newText": " result := new_function_name(args...)\n" }] } } }) ``` 2. **Second:** Insert the new function definition after the containing function's closing brace: ``` mcp__lsp__apply_edit({ "workspace_edit": { "changes": { "<file_path>": [{ "range": { "start": { "line": insert_line, "character": 0 }, "end": { "line": insert_line, "character": 0 } }, "newText": "\nfunc new_function_name(params) ReturnType {\n ...\n}\n" }] } } }) ``` Apply call-site replacement first, then insert the new function. This order preserves line numbers during editing: replacing call site does not shift the insertion point for the new function definition. --- ## Step 5 — Validate After extraction via either path: ### 1. Check diagnostics ``` mcp__lsp__get_diagnostics({ "file_path": "<file_path>" }) ``` If errors are reported, display them with the table of common causes below. ### 2. Common post-extraction errors | Error type | Likely cause | Fix | |------------|--------------|-----| | Undefined variable | Captured var not passed as parameter | Add parameter | | Type mismatch | Return type inferred incorrectly | Adjust return type in signature | | Name shadows outer | New function name matches outer scope | Choose different name | | Unused variable | Return value not captured at call site | Add variable at call site | ### 3. Format the document ``` mcp__lsp__format_document({ "file_path": "<file_path>" }) ``` This cleans up indentation introduced by the extraction. --- ## Output Format After completing extraction, display: ``` ## Extraction Summary - File: path/to/file.go - Extracted: lines N–M - New function: new_function_name - Path used: LSP code action / Manual fallback - Post-extraction errors: 0 ``` Follow with the Diagnostic Summary if any errors changed (format in [references/patterns.md](references/patterns.md)). --- ## Language-Specific Notes - **Go:** gopls may offer "Extract function" in code actions for selection ranges. Check code actions first; gopls support varies by version. - **TypeScript/JavaScript:** tsserver may offer "Extract to function in global scope" or "Extract to inner function" — filter for these titles in Step 3. - **Python:** pylsp and pyright-langserver typically do NOT offer extract-function code actions. Manual fallback (Step 4) is required for Python files.

lsp-fix-all

Apply available quick-fix code actions for all current diagnostics in a file, one at a time with re-collection between each fix. Use to bulk-resolve errors and warnings the language server can fix automatically.

> Requires the agent-lsp MCP server. # lsp-fix-all Apply available quick-fix code actions for all current diagnostics in a file, one at a time, re-collecting diagnostics between each fix because line numbers shift after each application. **Important distinction from `/lsp-safe-edit`:** This skill fixes **pre-existing** diagnostics in a file — errors and warnings that already exist before any edit session begins. `/lsp-safe-edit` has a code-action step (Step 7) for fixing errors **introduced by a specific edit you just made**. Use this skill for systematic bulk-fixing of existing issues, independent of any edit session. ## When to use / not use **Use this skill when:** - A file has accumulated errors or warnings you want to resolve automatically - You want to clean up a file before starting new work - You want to apply all available language-server quick-fixes in bulk **Do NOT use this skill when:** - You just made an edit and want to fix newly introduced errors — use `/lsp-safe-edit` - You want to apply structural refactors — this skill applies quick-fixes only (see filtering below) - The file has zero diagnostics (the skill will report clean and stop) ## Input - **file_path:** Absolute path to the file to fix. --- ## Workflow ### Step 1 — Open and collect initial diagnostics Call `mcp__lsp__open_document` with the target file path to ensure it is loaded in the language server. Then call `mcp__lsp__get_diagnostics` to retrieve all current diagnostics. If zero diagnostics are returned: report "No diagnostics found — file is clean." and stop. No further steps are needed. Record the initial count of errors and warnings for the summary output. ### Step 2 — Classify and filter code actions For EACH diagnostic (process one at a time, not in batch): 1. Call `mcp__lsp__get_code_actions` at the diagnostic's position/range. 2. Filter the returned actions to quick-fix kind only. 3. Skip any diagnostic for which no applicable quick-fix exists — note it in the summary. **Decision gate — which code actions to apply:** | Action kind | Apply? | |---|---| | `quickfix` | YES | | `quickfix.*` | YES | | `refactor` | NO — structural change | | `refactor.extract` | NO — structural change | | `refactor.inline` | NO — structural change | | `source.organizeImports` | YES — safe formatting | | `source.*` (others) | NO — skip unless organizeImports | | (no kind / empty) | NO — unknown, skip | A code action qualifies if: `kind == "quickfix"`, OR `kind` starts with `"quickfix."`, OR `kind == "source.organizeImports"`. Reject actions whose kind is `"refactor"`, starts with `"refactor."`, or has no kind field at all. ### Step 3 — Apply one fix and re-collect (the core loop) This is the critical correctness constraint: **never apply more than one fix per iteration.** After each `apply_edit` call, line numbers in the file shift. Always re-call `get_diagnostics` before processing the next diagnostic. **Loop:** ``` iteration = 0 max_iterations = 50 while iteration < max_iterations: diagnostics = mcp__lsp__get_diagnostics(file_path) if diagnostics is empty: break for each diagnostic in diagnostics: actions = mcp__lsp__get_code_actions(diagnostic.range) applicable = filter to quickfix / source.organizeImports kinds (see Step 2) if applicable is not empty: apply the first applicable action via mcp__lsp__apply_edit record: (line, message, action title) in "Fixed" list iteration += 1 break # restart the outer loop — line numbers have shifted if no diagnostic in this pass had an applicable quick-fix: break # no progress possible — exit loop ``` Exit the loop when: - The diagnostics list is empty, OR - No remaining diagnostic has an applicable quick-fix action, OR - The iteration counter reaches 50 (safety guard against edge cases where a fix introduces a new fixable diagnostic, preventing infinite loops) If `apply_edit` returns an error: stop the loop immediately and report the failure in the summary. Do not attempt further fixes. ### Step 4 — Verify and format After the loop exits: 1. Call `mcp__lsp__get_diagnostics` one final time to capture the post-fix state. 2. For any remaining diagnostics that had no applicable quick-fix, list them in the "Skipped" section with explanation. 3. Call `mcp__lsp__format_document` to clean up any indentation drift introduced by the applied edits. ### Output format ``` ## lsp-fix-all Summary File: /path/to/file.go Initial diagnostics: N errors, M warnings Fixes applied: K Remaining (no auto-fix available): J ### Fixed - line X: <message> → applied: <action title> ### Skipped (no quick-fix available) - line Y: <message> ``` If `apply_edit` failed mid-loop, append: ``` ### Loop stopped - apply_edit returned error on line Z: <error message> - Fixes applied before failure: K ``` --- ## Safety rules - Never apply more than one code action per loop iteration - Always re-collect diagnostics after each `apply_edit` before the next fix - Never apply refactor or structural code actions — quick-fix and source.organizeImports only - If `apply_edit` returns an error, stop the loop and report the failure; do not continue - Maximum iterations: 50 (safety guard against infinite loops in edge cases where a fix introduces a new fixable diagnostic) - Do not use `execute_command` — `apply_edit` is sufficient for all quick-fixes --- ## Prerequisites LSP must be running for the target workspace. If not yet initialized, call `mcp__lsp__start_lsp` with the workspace root before proceeding. Auto-init note: agent-lsp supports workspace auto-inference from file paths. Explicit `start_lsp` is only needed when switching workspace roots.

lsp-format-code

Format a file or selection using the language server's formatter. Use before committing to apply consistent style, or after generating code to clean up indentation and spacing. Supports full-file and range-based formatting.

> Requires the agent-lsp MCP server. # lsp-format-code Format a file or selection using the language server's formatter — the same formatting engine your IDE uses. Applies language-specific rules (gofmt, prettier, rustfmt, black) without requiring those tools to be on PATH separately. ## When to use - Before committing: ensure consistent style across edited files - After generating code: clean up AI-generated indentation and spacing - After a refactor that shifted indentation levels - When a linter flags style violations fixable by the formatter Use `/lsp-safe-edit` instead when you are making a logic change and want before/after diagnostic comparison alongside the edit. --- ## Workflow ### Step 1 — Check formatting is supported (optional) If unsure whether the language server supports formatting for this file, check capabilities first: ``` mcp__lsp__get_server_capabilities({ "file_path": "<file>" }) ``` Look for `documentFormattingProvider` (full-file) and `documentRangeFormattingProvider` (range). If neither is present, the server does not support formatting — stop and report. Skip this step if you know the language supports formatting (Go, TypeScript, Rust, Python all do via their standard servers). ### Step 2 — Open the file ``` mcp__lsp__open_document({ "file_path": "/abs/path/to/file.go", "language_id": "go" }) ``` ### Step 3 — Request formatting edits **Full file:** ``` mcp__lsp__format_document({ "file_path": "/abs/path/to/file.go" }) ``` **Selection only:** ``` mcp__lsp__format_range({ "file_path": "/abs/path/to/file.go", "start_line": <N>, "end_line": <M> }) ``` Both return `TextEdit[]` — a list of replacements to apply. They do **not** write to disk. If the list is empty, the file is already correctly formatted. ### Step 4 — Apply the edits Pass the `TextEdit[]` from Step 3 to `apply_edit`: ``` mcp__lsp__apply_edit({ "workspace_edit": <TextEdit[] from Step 3> }) ``` This writes the formatting changes to disk. ### Step 5 — Verify (optional but recommended) Call `get_diagnostics` to confirm formatting did not introduce any errors: ``` mcp__lsp__get_diagnostics({ "file_path": "/abs/path/to/file.go" }) ``` Formatting should never introduce errors — if it does, report immediately without committing. --- ## Output format ``` ## Format result: <filename> Changes applied: N edits Lines affected: <range or "whole file"> Formatter: <gopls | tsserver | rust-analyzer | ...> Status: FORMATTED ✓ ``` If no edits were returned: ``` Status: ALREADY FORMATTED — no changes needed ``` If formatting is not supported: ``` Status: NOT SUPPORTED — <server> does not expose documentFormattingProvider Fallback: run the formatter directly (gofmt, prettier, rustfmt, etc.) ``` --- ## Multi-file formatting For formatting multiple files (e.g. all files changed in a PR): 1. Call `format_document` for each file — these can run in parallel. 2. Collect all `TextEdit[]` responses. 3. Apply each file's edits via `apply_edit` sequentially. 4. Report total edits across all files. Do not apply edits from multiple files in a single `apply_edit` call — apply per-file to keep changes scoped and reversible. --- ## Decision guide | Situation | Action | |-----------|--------| | Formatting a whole file before commit | `format_document` → `apply_edit` | | Formatting only generated code in a function | `format_range` with the function's line range | | Empty `TextEdit[]` returned | File is already formatted — no action needed | | Server doesn't support formatting | Report and suggest running CLI formatter directly | | Formatting introduces diagnostics | Do not commit — report immediately | | Formatting a Go file in a workspace repo | Ensure `GOWORK=off` is set if running via shell fallback | --- ## Language notes | Language | Formatter | Server | |----------|-----------|--------| | Go | `gofmt` (via gopls) | `gopls` | | TypeScript / JavaScript | `prettier` or built-in (via tsserver) | `typescript-language-server` | | Rust | `rustfmt` (via rust-analyzer) | `rust-analyzer` | | Python | `black` or `autopep8` (via pyright/pylsp) | `pyright-langserver` or `pylsp` | | C / C++ | `clang-format` (via clangd) | `clangd` | The language server delegates to the language's standard formatter — results match what your IDE would produce.

lsp-generate

Trigger language server code generation — implement interface stubs, generate test skeletons, add missing methods, generate mock types. Uses get_code_actions to surface generator options and execute_command to run them.

> Requires the agent-lsp MCP server. # lsp-generate **lsp-generate creates NEW code that does not yet exist in the file** — stubs, mocks, implementations of interfaces, test functions. It is distinct from `lsp-extract-function`, which restructures code that already exists. Use `lsp-generate` when you want the language server to write something new; use `lsp-extract-function` when you want to reorganize existing code. ## Input - **`file_path`**: absolute path to the target file - **`line`, `column`** (or **`position_pattern`**): position in the file where generation is triggered (e.g., the line with the unimplemented interface, the missing method error, the type declaration) - **`intent`**: description of what to generate (e.g., "implement io.Reader", "generate test skeleton", "add missing methods", "generate mock for Handler") ## Prerequisites LSP must be running for the target workspace. If not yet initialized, call `mcp__lsp__start_lsp` with the workspace root before proceeding. Auto-init note: agent-lsp supports workspace auto-inference from file paths. Explicit `start_lsp` is only needed when switching workspace roots. --- ## Workflow ### Step 1 — Open document and locate position Call `mcp__lsp__open_document` for the target file: ``` mcp__lsp__open_document(file_path: "/abs/path/to/file.go", language_id: "go") ``` If using `position_pattern`, use the @@ marker convention from `references/patterns.md` to identify the exact cursor position. For example: ``` "position_pattern": "var _ io.Reade@@r = (*MyType)(nil)" ``` ### Step 2 — Get code actions at target position ``` mcp__lsp__get_code_actions({ "file_path": "...", "start_line": N, "start_column": C, "end_line": N, "end_column": C }) ``` Filter for generator actions: - Kind `"quickfix"` with titles matching the intent (e.g., "Implement interface", "Generate", "Add stub", "Create test") - Kind `"source"` for source-level generation If no matching action is found, report "No generator action available at this position for the given intent" and proceed to the Fallback section below. ### Step 3 — Select and confirm action Display available generator actions to the user. If multiple actions match the intent, list all of them and ask which to apply. Confirm the selected action before executing — do NOT auto-select when multiple candidates exist. ### Step 4 — Execute generator Execute one generator at a time. Do NOT batch multiple `execute_command` calls. - If the action has a `command` field: run via `mcp__lsp__execute_command` - If the action has an `edit` field: apply via `mcp__lsp__apply_edit` - If the action has both: apply the edit first, then run the command ### Step 5 — Format and verify ``` mcp__lsp__format_document({ "file_path": "..." }) mcp__lsp__get_diagnostics({ "file_path": "..." }) ``` Report remaining diagnostics. Stub methods typically leave TODO comments or `panic("not implemented")` bodies — this is expected behavior from the language server. Surface any unexpected errors. --- ## Per-Language Generator Patterns | Language | Generator | Trigger location | Code action kind | |----------|-----------|-----------------|-----------------| | Go (gopls) | Implement interface | Line with `var _ MyInterface = (*MyType)(nil)` or type declaration | `quickfix` — "Implement interface" | | Go (gopls) | Generate test file | Any .go file without _test.go counterpart | `source` — "Generate unit tests" | | Go (gopls) | Add missing method | Line with `undefined: method` error | `quickfix` | | TypeScript (tsserver) | Implement interface | Class declaration | `quickfix` — "Implement interface members" | | TypeScript (tsserver) | Add missing method | Method call with no definition | `quickfix` — "Add missing function declaration" | | Python (pyright) | Add import | Name not defined | `quickfix` — "Add import" | | Rust (rust-analyzer) | Implement trait | `impl Trait for Type {}` | `quickfix` — "Add missing impl members" | --- ## Fallback When No Code Action Is Available If `get_code_actions` returns no generator actions, the language server at this workspace may not support server-side generation for this intent. Explain this to the user and suggest a manual approach specific to the intent: - **Interface implementation:** Look up the interface definition first using `mcp__lsp__go_to_symbol` to discover all required methods, then implement them manually. - **Test skeleton:** Check `mcp__lsp__get_server_capabilities` to confirm whether the server advertises code action support; if not, generate the test skeleton manually using standard testing package conventions. - **Missing methods:** Use `mcp__lsp__get_diagnostics` to enumerate the missing symbols by name, then implement them one at a time. --- ## Constraints - Do NOT batch `execute_command` calls — run one generator at a time - Do NOT skip user confirmation when multiple generator actions are available

lsp-impact

Blast-radius analysis for a symbol or file — shows all callers, type supertypes/subtypes, and reference count before you change it. Use when refactoring, deleting, or changing the signature of any function, type, or method. Also accepts a file path to surface all exported-symbol impact in one shot.

> Requires the agent-lsp MCP server. # lsp-impact Blast-radius analysis for any symbol or file. Discovers all direct references, callers (via call hierarchy), and type relationships before you touch anything. Read-only — does not modify any files. Run this skill **before** lsp-edit-export: impact tells you what exists and how widespread the change is; lsp-edit-export tells you how to execute the change safely. **Invocation:** - **File path** (e.g. `"internal/lsp/client.go"`) → use the File-level entry (Step 0) to surface all exported-symbol impact at once. - **Symbol name** in dot notation (e.g. `"codec.Encode"`, `"Buffer.Reset"`) → skip Step 0; start at Prerequisites, then Step 1. --- ## Step 0 — File-level entry (when user provides a file path) Use this shortcut when the user is changing or auditing an entire file rather than a single symbol. `get_change_impact` enumerates all exported symbols in the file, resolves their references, and returns test callers (with enclosing test function names) and non-test callers in a single call. ``` mcp__lsp__get_change_impact({ "changed_files": ["/abs/path/to/file.go"], "include_transitive": false // set true to surface second-order callers }) ``` Returns: - `affected_symbols` — each exported symbol with its reference count - `test_callers` — test files + enclosing test function names - `non_test_callers` — production call sites **Decision after Step 0:** | Result | Action | |--------|--------| | 0 non-test callers | Low blast radius. Proceed with change. | | Few callers, known files | Medium risk. Update each call site. | | Many callers across packages | High risk. Consider staged rollout. | | Want symbol-level detail | Continue to Steps 1–5 for any specific symbol. | Skip Steps 1–5 if the file-level summary is sufficient. --- ## Prerequisites (for symbol-level Steps 1–5) If LSP is not yet initialized, call `mcp__lsp__start_lsp` with the workspace root first. Check what the server supports before proceeding — `call_hierarchy` and `type_hierarchy` are optional LSP features not implemented by all servers: ``` mcp__lsp__get_server_capabilities() ``` Note which tools appear in `supported_tools`. Steps 3 and 4 below depend on this result. --- ## Step 1 — Locate the symbol Use `go_to_symbol` with the symbol name provided by the user: ``` mcp__lsp__go_to_symbol({ "symbol_path": "Package.SymbolName", "workspace_root": "/abs/path" // optional, narrows scope }) → returns: file, line, column (1-indexed) ``` `symbol_path` uses dot notation. For a top-level function `Encode` in package `codec`, use `"codec.Encode"`. For a method `Reset` on type `Buffer`, use `"Buffer.Reset"`. Record the returned `file`, `line`, and `column` — you will pass them to every subsequent step. --- ## Step 2 — Enumerate all direct references (always available) Call `get_references` with `include_declaration: false` to find every usage site across the workspace: ``` mcp__lsp__get_references({ "file_path": "<file from Step 1>", "position_pattern": "func @@SymbolName(", // adjust prefix for symbol kind "include_declaration": false }) ``` Collect all reference locations. Group results by file. Record the total count and list of files — these feed the Impact Report. See [references/patterns.md](references/patterns.md) for `position_pattern` examples by language and symbol kind. --- ## Step 3 — Call hierarchy (callers and callees) Only if `call_hierarchy` appears in `supported_tools` from Step 0. ``` mcp__lsp__call_hierarchy({ "file_path": "<file from Step 1>", "line": <line from Step 1>, "column": <column from Step 1>, "direction": "incoming" // use "both" if callees are also needed }) ``` If `call_hierarchy` is **not** in `supported_tools`, skip this step entirely. Note `"call hierarchy not supported by this server"` in the Impact Report. --- ## Step 4 — Type hierarchy (supertypes and subtypes) Only applicable when the symbol is a **type, interface, or class** (not a plain function or method). Only if `type_hierarchy` appears in `supported_tools`. ``` mcp__lsp__type_hierarchy({ "file_path": "<file from Step 1>", "line": <line from Step 1>, "column": <column from Step 1>, "direction": "both" }) ``` If the symbol is a **function or method**: skip this step; note `"not applicable (function)"` in the report. If `type_hierarchy` is **not** in `supported_tools`: skip this step; note `"not supported by this server"` in the report. --- ## Step 5 — Report impact surface Produce the Impact Report using the format defined in [references/patterns.md](references/patterns.md). Include: - Symbol name, kind, and definition location - Reference count and list of files containing references - Callers from `call_hierarchy` incoming (or skip note) - Supertypes and subtypes from `type_hierarchy` (or skip note) - Blast radius: count of distinct files affected Then apply the decision guide: | Blast radius | Recommendation | |---|---| | 0 references | Likely dead code. Confirm with lsp-dead-code before deleting. | | 1–5 files | Low risk. Proceed. Update all callers. | | 6–20 files | Medium risk. Plan changes carefully. Stage in waves. | | > 20 files | High risk. Consider a deprecation path or feature flag. | --- ## Example ``` Goal: assess blast radius of exported function `ParseConfig` in pkg/config Prerequisites — get_server_capabilities: → supported_tools: [go_to_symbol, get_references, call_hierarchy, ...] → type_hierarchy: not in supported_tools Step 1 — go_to_symbol: symbol_path="config.ParseConfig" → pkg/config/parser.go:42:6 Step 2 — get_references: position_pattern="func @@ParseConfig(" → 7 references in 4 files → cmd/main.go, internal/app.go, internal/loader.go, pkg/config/parser_test.go Step 3 — call_hierarchy: direction="incoming" → callers: cmd.main (cmd/main.go:14), app.Start (internal/app.go:31), ... Step 4 — type_hierarchy: skipped (function), also not supported by server Step 5 — Impact Report: ## Impact Report: ParseConfig - Kind: function - Definition: pkg/config/parser.go:42:6 - References: 7 across 4 files ... - Risk level: low ``` ## Note on position_pattern `position_pattern` with `@@` is a agent-lsp extension. If your MCP client does not support it, fall back to explicit `line` and `column` parameters from the location returned by `go_to_symbol` in Step 1.

lsp-implement

Find all concrete implementations of an interface or abstract type. Use when you need to know what types satisfy an interface, or what subtypes exist before changing a base type.

> Requires the agent-lsp MCP server. # lsp-implement Find every concrete type that implements an interface, or every subtype of an abstract type. Read-only — does not modify any files. Use this skill **before** changing an interface signature, adding a method to an interface, or removing a base-type method. It tells you every type that must be updated. **Invocation:** User provides `type_name` (e.g. `"Handler"`, `"io.Reader"`). Optionally provide `workspace_root`. --- ## Prerequisites Check server capabilities — `go_to_implementation` and `type_hierarchy` are optional features not implemented by all language servers: ``` mcp__lsp__get_server_capabilities() ``` Note which of `go_to_implementation` and `type_hierarchy` appear in `supported_tools`. The steps below depend on this result. If neither is supported, report `"Server does not support implementation lookup"` and stop. --- ## Step 1 — Locate the interface or type ``` mcp__lsp__go_to_symbol({ "symbol_path": "<TypeName>", "workspace_root": "/abs/path" // optional }) → returns: file, line, column (1-indexed) ``` Open the file so the language server tracks it: ``` mcp__lsp__open_document({ "file_path": "<file from go_to_symbol>" }) ``` Record `file`, `line`, `column` for subsequent steps. --- ## Step 2 — Find all implementations Only if `go_to_implementation` appears in `supported_tools`. ``` mcp__lsp__go_to_implementation({ "file_path": "<file>", "line": <line>, "column": <column> }) ``` Returns a list of locations — each is a concrete type that satisfies the interface. Group by file. Record type names and locations. If `go_to_implementation` is **not** supported: skip; note in report. --- ## Step 3 — Type hierarchy (subtypes and supertypes) Only if `type_hierarchy` appears in `supported_tools`. ``` mcp__lsp__type_hierarchy({ "file_path": "<file>", "line": <line>, "column": <column>, "direction": "subtypes" // use "both" to also see what this type extends }) ``` `subtypes` returns concrete types that extend or embed this type. `supertypes` returns what this type itself implements. Cross-reference with Step 2 results — the union gives the complete implementation surface. If `type_hierarchy` is **not** supported: skip; note in report. --- ## Step 4 — Report ``` ## Implementation Report: <TypeName> ### Definition - File: <file>:<line> - Kind: interface / abstract type / base struct ### Concrete Implementations (<N> found) - TypeA — <file>:<line> - TypeB — <file>:<line> ... ### Type Hierarchy Supertypes: [list or "none"] Subtypes: [list or "same as implementations above" or "not supported"] ### Risk Assessment | N implementations | Recommendation | |---|---| | 0 | Interface unused or no external implementors found. May be internal-only. | | 1–3 | Low risk. All implementors can be updated together. | | 4–10 | Medium risk. Plan updates package by package. | | > 10 | High risk. Changing the interface is a breaking API change. | ``` --- ## Common use cases **Before adding a method to an interface:** Run lsp-implement to find all types that will need the new method. Each implementation site must be updated — this is your required change list. **Before removing a method:** Find all types that implement it. Check whether any external (outside this repo) packages may be affected. **Understanding polymorphism in an unfamiliar codebase:** Run lsp-implement on the primary interface to see the full type hierarchy before making any changes. --- ## Language notes | Language | `go_to_implementation` finds... | |---|---| | Go | All types with matching method sets | | TypeScript | All classes implementing the interface | | Java/C# | All classes/structs implementing the interface | | Rust | All structs with `impl Trait for ...` | For Go: `go_to_implementation` on an interface finds all types that satisfy it, even without an explicit `implements` declaration.

lsp-local-symbols

Fast file-scoped symbol analysis — find all usages of a symbol within the current file, list all symbols defined in the file, and get type info at a position. Use when you need local-scope analysis without a workspace-wide search.

> Requires the agent-lsp MCP server. # lsp-local-symbols File-scoped symbol analysis using the language server index. Faster than workspace-wide search for questions about a single file: what symbols are defined here, where is this symbol used within the file, and what type does it have. Read-only — does not modify any files. ## When to use - "Where is `x` used in this file?" — use `get_document_highlights` - "What functions and types are defined in this file?" — use `get_document_symbols` - "What type does this symbol have?" — use `get_info_on_location` - Reviewing a file before editing — get the full symbol map first - Local refactor scoping — confirm a symbol is only used in one place before inlining it Use `/lsp-impact` instead when you need workspace-wide callers and cross-file references. Use `/lsp-dead-code` when auditing exported symbols for zero callers. ## When NOT to use `get_document_highlights` is file-scoped by design — it only finds usages within the open file. If a symbol is used across multiple files, this skill will not find those. Use `get_references` (via `/lsp-impact`) for cross-file analysis. --- ## Workflow ### Step 1 — Open the file Open the file so the language server tracks it: ``` mcp__lsp__open_document file_path: "/abs/path/to/file.go" language_id: "go" # go, typescript, python, rust, etc. ``` ### Step 2 — List all symbols in the file Get the full symbol tree for the file: ``` mcp__lsp__get_document_symbols file_path: "/abs/path/to/file.go" ``` This returns all functions, types, variables, constants, and methods defined in the file — including nested symbols (methods on types, fields in structs). Use this to: - Understand the file's structure before editing - Find the exact position of a named symbol - See what a file exposes before reading it in full **Reading the output:** Each symbol has a `range` (full body including braces) and a `selectionRange` (just the name). Coordinates are 1-based. Use `selectionRange.start.line` and `selectionRange.start.character` as inputs to `get_document_highlights` and `get_info_on_location`. ### Step 3 — Find all usages within the file Call `get_document_highlights` at the symbol's position: ``` mcp__lsp__get_document_highlights file_path: "/abs/path/to/file.go" line: <selectionRange.start.line from Step 2> column: <selectionRange.start.character from Step 2> ``` Returns every occurrence of the symbol within the file, classified as: - `read` — the symbol is read here - `write` — the symbol is assigned/mutated here - `text` — a text match (fallback when semantic classification isn't available) **Speed note:** `get_document_highlights` is significantly faster than `get_references` for file-local queries — it does not scan the entire workspace index. Use it first; escalate to `get_references` only if you need cross-file results. ### Step 4 — Get type information (optional) For any position of interest, get the type signature and docs: ``` mcp__lsp__get_info_on_location file_path: "/abs/path/to/file.go" line: <line> column: <column> ``` Returns the hover text: type signature, documentation, and inferred types. Useful for confirming what a symbol is before deciding to rename or inline it. --- ## Output format Report results in three sections (omit any section with no content): ``` ## Symbols in <filename> ### Functions / Methods - `FuncName` — line N–M - `(Type) MethodName` — line N–M ### Types - `TypeName` (struct/interface/alias) — line N ### Variables / Constants - `ConstName` = value — line N --- ## Usages of `<symbol>` in <filename> N occurrences across M lines: - line 12 [write] — assignment - line 34 [read] — passed as argument - line 67 [read] — returned --- ## Type info `<symbol>`: <type signature from get_info_on_location> ``` --- ## Decision guide | Question | Tool | |----------|------| | What's in this file? | `get_document_symbols` | | Where is X used in this file? | `get_document_highlights` | | What type is X? | `get_info_on_location` | | Is X safe to inline (used once)? | `get_document_highlights` — count occurrences | | Is X used outside this file? | Use `/lsp-impact` instead | | Is X dead code (no callers anywhere)? | Use `/lsp-dead-code` instead | --- ## Example ``` # "Where is the `config` variable used in server.go?" open_document(file_path="/repo/server.go", language_id="go") get_document_symbols(file_path="/repo/server.go") → finds `config` at selectionRange line 42, col 2 get_document_highlights(file_path="/repo/server.go", line=42, column=2) → returns 7 occurrences: 1 write (line 42), 6 reads get_info_on_location(file_path="/repo/server.go", line=42, column=2) → "config *Config — the parsed server configuration" ```

lsp-refactor

End-to-end safe refactor workflow — blast-radius analysis, speculative preview, apply to disk, verify build, run affected tests. Inlines lsp-impact + lsp-safe-edit + lsp-verify + lsp-test-correlation into one coordinated sequence.

> Requires the agent-lsp MCP server. # lsp-refactor End-to-end safe refactor workflow. Sequences blast-radius analysis, speculative preview, disk apply, build verification, and targeted test execution in one coordinated pass. **This skill does NOT replace lsp-safe-edit or lsp-impact.** - `lsp-safe-edit` wraps a single edit with before/after diagnostic comparison — use it when you need to make one targeted change with careful error diffing. - `lsp-impact` is read-only blast-radius analysis — use it when you want to understand scope before deciding whether to proceed. - `lsp-refactor` sequences ALL four workflows (lsp-impact → lsp-safe-edit → lsp-verify → lsp-test-correlation) in order. Use it when you know your target and intent up front and want the complete workflow without switching skills. --- ## Input - **target**: symbol name in dot notation (e.g. `"codec.Encode"`, `"Buffer.Reset"`) OR file path (e.g. `"internal/lsp/client.go"`) - **intent**: description of the change to make (e.g. "rename to ParseConfigV2", "add a second parameter `timeout time.Duration`") - **workspace_root**: absolute path to the workspace root --- ## Phase 1 — Blast-Radius Analysis (inlined from lsp-impact) **This phase is mandatory. Do not skip it, even for "small" refactors.** Call `mcp__lsp__get_change_impact` with `changed_files` set to the file containing the target symbol. If the user provided a file path directly, use it. If the user provided a symbol name, resolve the file first (e.g. via `mcp__lsp__go_to_symbol`). ``` mcp__lsp__get_change_impact({ "changed_files": ["/abs/path/to/file"], "include_transitive": false }) ``` Returns: - `affected_symbols` — exported symbols with reference counts - `test_callers` — test files and enclosing test function names - `non_test_callers` — production call sites **Display:** - Affected symbol count - Test callers (each with enclosing test function name) - Non-test callers (each with file:line) - Total reference count **High blast-radius gate:** If the total reference count exceeds 20, STOP and ask the user to confirm before continuing: ``` High blast radius: N callers found. Proceed with refactor? [y/n] ``` If the user answers "n", abort. Do not proceed to Phase 2. --- ## Phase 2 — Speculative Preview (inlined from lsp-safe-edit) Only reached if Phase 1 blast radius is acceptable (≤ 20 callers, or user confirmed). ### 2a — Open file and capture baseline diagnostics ``` mcp__lsp__open_document({ "file_path": "/abs/path/to/file", "language_id": "go" }) mcp__lsp__get_diagnostics({ "file_path": "/abs/path/to/file" }) ``` Store baseline diagnostics as BEFORE. ### 2b — Speculative simulation For a **single-file change**: use `simulate_edit_atomic`: ``` mcp__lsp__simulate_edit_atomic({ "file_path": "/abs/path/to/file", "start_line": <N>, "start_column": <col>, "end_line": <N>, "end_column": <col>, "new_text": "<replacement text>" }) ``` For a **multi-file change** (e.g. rename + call site updates): use `simulate_chain`: ``` mcp__lsp__simulate_chain({ "workspace_root": "/abs/path/to/workspace", "language": "go", "edits": [ { "file_path": "/abs/path/to/file.go", "start_line": <N>, "start_column": <col>, "end_line": <N>, "end_column": <col>, "new_text": "<replacement>" } // additional dependent edits ... ] }) ``` ### 2c — Evaluate simulation result Display the speculative result using the Diagnostic Diff Output Format from [references/patterns.md](references/patterns.md). **Decision:** | `net_delta` | Action | |-------------|--------| | ≤ 0 | Safe. Proceed to Phase 3. | | > 0 | **Abort.** Report introduced errors. Do NOT apply to disk. | If `net_delta > 0`, stop and show the full list of errors the simulation introduced. Do not proceed to Phase 3. --- ## Phase 3 — Apply to Disk Only reached if Phase 2 `net_delta <= 0`. Apply the change using the Edit or Write tool. For edits computed by simulation, `mcp__lsp__apply_edit` may be used directly if the simulation returned an edit object: ``` Edit(file_path: "/abs/path/to/file", old_string: "...", new_string: "...") ``` For multi-file changes, apply each file's edits before moving to Phase 4. If any individual apply fails, stop and report before applying remaining files. After applying, format the changed file(s): ``` mcp__lsp__format_document({ "file_path": "/abs/path/to/file" }) ``` Apply the returned `TextEdit[]` via `mcp__lsp__apply_edit` if non-empty. --- ## Phase 4 — Build Verification (inlined from lsp-verify) Run in this order — LSP diagnostics first, then the compiler build: ``` mcp__lsp__get_diagnostics({ "file_path": "/abs/path/to/file" }) mcp__lsp__run_build({ "workspace_root": "/abs/path/to/workspace" }) ``` **Decision:** | Result | Action | |--------|--------| | Diagnostics clean, build passes | Proceed to Phase 5. | | Diagnostics show new errors | Display errors and stop. Do not proceed to Phase 5. | | Build fails | Display build output and stop. Do not proceed to Phase 5. | If build fails, report the full build error output and stop. Test execution is skipped until build passes. --- ## Phase 5 — Run Affected Tests (inlined from lsp-test-correlation) For each file changed in Phase 3, find correlated test files: ``` mcp__lsp__get_tests_for_file({ "file_path": "/abs/path/to/changed/file" }) ``` Deduplicate the resulting test files if multiple source files were changed. Run only the correlated test files: ``` mcp__lsp__run_tests({ "workspace_root": "/abs/path/to/workspace", "test_files": [...] }) ``` **If no correlated test files are found:** note "No test correlation found — run full suite manually to confirm." Do not attempt to run `./...` automatically. --- ## Abort Conditions The following conditions abort the workflow immediately. Each abort displays the relevant output before stopping. 1. **Phase 1:** blast radius > 20 callers AND user does not confirm → abort 2. **Phase 2:** `net_delta > 0` (simulation introduced errors) → abort, show errors 3. **Phase 4:** build fails → abort, show build output 4. **Any phase:** LSP tool returns an unexpected error → abort, report tool output verbatim --- ## Output Format After completing all phases, produce this structured report: ``` ## lsp-refactor Complete ### Phase 1 — Blast Radius Affected symbols: N Test callers: M (list each with enclosing test function) Non-test callers: K ### Phase 2 — Speculative Preview [Diagnostic Diff Output Format from patterns.md] net_delta: 0 → safe to apply ### Phase 3 — Applied Files changed: [list] ### Phase 4 — Build Verification Diagnostics: N errors (0 new) Build: PASS ### Phase 5 — Test Results Test files run: [list] Result: PASS / FAIL ``` If the workflow aborted at a phase, report only the phases completed and the abort reason: ``` ## lsp-refactor Aborted at Phase 2 ### Phase 1 — Blast Radius ... ### Phase 2 — Speculative Preview ABORTED: net_delta: +2 (errors introduced) Errors: - file.go:34 — undefined: NewType - file.go:51 — cannot use int as string ``` --- ## Example ``` Goal: rename exported function ParseConfig → ParseConfigV2 in pkg/config Phase 1 — Blast Radius get_change_impact(changed_files=["pkg/config/parser.go"]) → affected_symbols: 1 (ParseConfig) → non_test_callers: 3 (cmd/main.go, internal/app.go, internal/loader.go) → test_callers: 1 (pkg/config/parser_test.go — TestParseConfig) → total references: 4 — within threshold, proceeding Phase 2 — Speculative Preview open_document(file_path="pkg/config/parser.go") get_diagnostics → BEFORE: 0 errors simulate_chain(edits: [parser.go rename + 3 call-site updates]) → cumulative_delta: 0 → safe to apply Phase 3 — Applied Edit parser.go: func ParseConfig → func ParseConfigV2 Edit cmd/main.go, internal/app.go, internal/loader.go: update call sites format_document(parser.go) Phase 4 — Build Verification get_diagnostics → 0 errors run_build → success Phase 5 — Test Results get_tests_for_file(parser.go) → pkg/config/parser_test.go run_tests(test_files=["pkg/config/parser_test.go"]) → PASS ## lsp-refactor Complete ... ```

lsp-rename

Two-phase safe rename across the entire workspace. Use when renaming any symbol, function, method, variable, type, or identifier — shows all affected sites before executing atomically via LSP. Never renames without confirmation.

> Requires the agent-lsp MCP server. # lsp-rename: Safe Symbol Rename Renames a symbol across the workspace in two phases: preview first, then execute only after explicit confirmation. Never renames without showing impact. **Invocation:** User provides `old_name` (the symbol to rename) and `new_name` (the replacement). Optionally provide `workspace_root` to scope the search. --- ## Prerequisites If LSP is not yet initialized, call `mcp__lsp__start_lsp` with the workspace root first. Auto-inference applies when file paths are provided, but an explicit start is required when switching workspaces. --- ## Phase 1: Preview Find the symbol, enumerate all references, and produce a dry-run preview. **Do not apply any edits in this phase.** ### Step 1 — Locate the symbol Call `mcp__lsp__go_to_symbol` with `symbol_path` set to `old_name`: ``` mcp__lsp__go_to_symbol symbol_path: "old_name" # or "Package.OldName" for qualified paths workspace_root: "<root>" # optional; omit to search entire workspace ``` This returns the definition location (file, line, column). If not found, report the error and stop. ### Step 2 — Validate rename is possible Call `mcp__lsp__prepare_rename` at the definition location from Step 1: ``` mcp__lsp__prepare_rename file_path: "<file from Step 1>" line: <line from Step 1> column: <column from Step 1> ``` `prepare_rename` asks the language server whether a rename is valid at this position. If it returns an error (e.g. the symbol is a keyword, a built-in, or in a location the server cannot rename), **stop immediately** and report: > Cannot rename `OldName`: <server error message> > Common causes: built-in or keyword, imported external package, or position is > not on the symbol name. Try locating the declaration site directly. Only proceed to Step 3 if `prepare_rename` succeeds. ### Step 3 — Enumerate references Call `mcp__lsp__get_references` at the definition location from Step 1: ``` mcp__lsp__get_references file_path: "<file from Step 1>" position_pattern: "<symbol>@@" # @@ immediately after the symbol name # fallback: use line/column from Step 1 if position_pattern is unavailable ``` Collect all returned locations. Note the total count and the distinct files. ### Step 4 — Dry-run preview Call `mcp__lsp__rename_symbol` with `dry_run=true`. **Do not call `apply_edit`.** ``` mcp__lsp__rename_symbol file_path: "<file from Step 1>" line: <line from Step 1> column: <column from Step 1> new_name: "<new_name>" dry_run: true ``` The response includes a `workspace_edit` with all proposed changes and a `preview.note` describing the scope. ### Step 5 — Report impact and hard stop Display the preview summary to the user: ``` Rename preview: OldName -> NewName Locations to update: N (from get_references count) Files affected: M (distinct files in workspace_edit) Language server: <gopls | tsserver | rust-analyzer | ...> Changes: path/to/file1.go lines 12, 45, 78 path/to/file2.go line 3 ... ``` **REQUIRED hard stop — do not proceed without explicit user confirmation:** > Proceed with rename? [y/n] Wait for user input. Do not apply any edit until the user answers "y" or "yes". --- ## Edge Case: 0 References If `get_references` returns an empty list (the symbol exists but has no external usages), warn the user before stopping: > Warning: no references found for `OldName`. The symbol may be unexported, > dead code, or the LSP index may be stale. Renaming will update only the > declaration site. > Proceed anyway? [y/n] If user answers "n", stop. If "y", continue to Phase 2. --- ## Phase 2: Execute Only enter this phase after the user answers "y" or "yes" to the confirmation prompt in Phase 1. ### Step 1 — Capture pre-rename diagnostics Before applying changes, capture the current diagnostic state: ``` mcp__lsp__get_diagnostics file_path: "<one or more files in the workspace_edit>" ``` Store the result as `before_diagnostics`. ### Step 2 — Execute rename Call `mcp__lsp__rename_symbol` without `dry_run` (or with `dry_run=false`): ``` mcp__lsp__rename_symbol file_path: "<file from Phase 1 Step 1>" line: <line from Phase 1 Step 1> column: <column from Phase 1 Step 1> new_name: "<new_name>" ``` This returns a `workspace_edit` with the full set of changes. ### Step 3 — Apply the edit Call `mcp__lsp__apply_edit` with the `workspace_edit` from Step 2: ``` mcp__lsp__apply_edit workspace_edit: <workspace_edit from rename_symbol> ``` ### Step 4 — Check diagnostics Call `mcp__lsp__get_diagnostics` on the affected files and compare against `before_diagnostics`: ``` mcp__lsp__get_diagnostics file_path: "<affected files>" ``` Compute introduced vs. resolved errors and display the Diagnostic Summary (see [references/patterns.md](references/patterns.md)). --- ## Output Format After Phase 2 completes, display: ``` ## Rename Summary - Old name: OldName - New name: NewName - Files changed: M - Locations updated: N - Post-rename errors: 0 ``` Follow with the Diagnostic Summary if any errors changed (format in [references/patterns.md](references/patterns.md)). Only show Diagnostic Summary sections where N > 0. A net change of 0 means the rename is safe. --- ## Language Support The following language servers support `rename_symbol`: - **Go** — `gopls` - **TypeScript / JavaScript** — `tsserver` - **Rust** — `rust-analyzer` Other LSP-compliant servers that implement `textDocument/rename` also work. Check your server's capability list via `mcp__lsp__get_server_capabilities` if you are unsure.

lsp-safe-edit

Wrap any code edit with before/after diagnostic comparison. Speculatively previews the change first (simulate_edit_atomic), then applies to disk only if the error delta is acceptable. If post-edit errors appear, surfaces code actions for quick fixes. Handles single and multi-file edits.

> Requires the agent-lsp MCP server. # lsp-safe-edit Wrap any code edit with a before/after diagnostic comparison. Speculatively previews the change in-memory before touching disk, then diffs errors introduced vs. resolved after applying. If errors appear, surfaces code actions to fix them. ## Prerequisites LSP must be running for the target workspace. If not yet initialized, call `mcp__lsp__start_lsp` with the workspace root before proceeding. Auto-init note: agent-lsp supports workspace auto-inference from file paths. Explicit `start_lsp` is only needed when switching workspace roots. ## Input - **target file(s):** One or more files to be edited (absolute paths). - **description of change:** What you intend to edit and why. --- ## Workflow ### Step 1 — Open target file(s) Call `mcp__lsp__open_document` for each file that will be edited: ``` mcp__lsp__open_document(file_path: "/abs/path/to/file.go", language_id: "go") ``` ### Step 2 — Capture baseline diagnostics (BEFORE) Call `mcp__lsp__get_diagnostics` for each target file. Store as BEFORE. For multi-file edits, collect diagnostics for all files involved. ``` BEFORE = mcp__lsp__get_diagnostics(file_path: "/abs/path/to/file.go") ``` If the server returns an empty list immediately after open, wait briefly and retry — LSP analysis is async. ### Step 3 — Speculative preview (simulate_edit_atomic) Before touching disk, call `mcp__lsp__simulate_edit_atomic` to preview the error delta of the intended change: ``` mcp__lsp__simulate_edit_atomic( file_path: "/abs/path/to/file.go", start_line: <N>, start_column: <col>, end_line: <N>, end_column: <col>, new_text: "<replacement text>" ) ``` Returns `net_delta` (new errors introduced minus errors resolved) without writing to disk. **Decision:** | `net_delta` | Action | |-------------|--------| | ≤ 0 | Proceed — edit improves or does not worsen error state | | > 0 | **Pause.** Report introduced errors to user and ask: "Proceed anyway? [y/n]" | If `net_delta > 0` and user says "n", stop. Do not apply the edit. **Multi-file edits:** `simulate_edit_atomic` covers one file at a time. For edits spanning multiple files, run it for each file independently and sum the deltas. If any file shows `net_delta > 0`, pause before continuing. **When to skip Step 3:** If the intended change is a new file (Write), there is no existing file to simulate against. Skip to Step 4. ### Step 3b — Refactor preview with simulate_chain (renames and signature changes) Use this step **instead of or after** Step 3 when the change is a rename, signature change, or any edit with dependent follow-on edits (e.g., updating all call sites after adding a parameter). `simulate_chain` applies a sequence of speculative edits in-memory and reports whether the cumulative change is safe — without touching disk: ``` mcp__lsp__simulate_chain({ "workspace_root": "/abs/path/to/workspace", "language": "go", "edits": [ { "file_path": "/abs/path/to/file.go", "start_line": <N>, "start_column": <col>, "end_line": <N>, "end_column": <col>, "new_text": "<replacement>" }, // additional dependent edits (e.g. call site updates) ... ] }) ``` Returns: - `cumulative_delta` — net error change across all steps - `safe_to_apply_through_step` — how many steps are safe to apply in sequence **Decision:** | `cumulative_delta` | `safe_to_apply_through_step` | Action | |--------------------|------------------------------|--------| | ≤ 0 | = total steps | All steps safe. Proceed to Step 4. | | ≤ 0 | < total steps | Safe up to that step. Review remaining steps. | | > 0 | any | Net regression. Report to user before proceeding. | **When to use Step 3b:** - Renaming an exported symbol and updating its call sites - Adding/removing a parameter and updating all callers - Any multi-file refactor where edits are order-dependent **When to skip Step 3b:** - Simple in-place edits with no dependent follow-on edits (Step 3 is sufficient) - New file creation (no existing text to simulate against) See `docs/refactor-preview.md` for worked examples. ### Step 4 — Apply the edit to disk Apply the change using the Edit or Write tool: - Use `Edit` for targeted replacements in an existing file. - Use `Write` only when creating a new file or doing a full rewrite. ``` Edit(file_path: "/abs/path/to/file.go", old_string: "...", new_string: "...") ``` For multi-file edits, apply each file's changes before collecting post-edit diagnostics (Step 5). If any individual edit fails, stop and report before applying remaining files. ### Step 5 — Capture post-edit diagnostics (AFTER) Call `mcp__lsp__get_diagnostics` again for each edited file. Store as AFTER. ``` AFTER = mcp__lsp__get_diagnostics(file_path: "/abs/path/to/file.go") ``` For multi-file edits, collect diagnostics for all files and merge the results. ### Step 6 — Compute the diagnostic diff Compare BEFORE and AFTER: - **Introduced** = diagnostics in AFTER not in BEFORE (new problems). - **Resolved** = diagnostics in BEFORE not in AFTER (fixed problems). Match by `(file, line, message)` tuple to handle line-number shifts. Treat `error` and `warning` severity separately. ### Step 7 — Surface code actions if errors were introduced If any new `error`-severity diagnostics appear, call `mcp__lsp__get_code_actions` at each error location to surface quick fixes: ``` mcp__lsp__get_code_actions( file_path: "<file>", start_line: <error line>, start_column: 1, end_line: <error line>, end_column: 999 ) ``` Report available code actions to the user: ``` Errors introduced (2): file.go:34 — undefined: MyType → Code action: Import "mypackage" (quickfix) file.go:51 — cannot use int as string → No code actions available Apply code actions? [y/n/select] ``` If the user accepts, apply the code action's `WorkspaceEdit` via `mcp__lsp__apply_edit`, then re-collect diagnostics and re-diff. ### Step 8 — Format (optional) If the diagnostic diff is clean (net change ≤ 0), offer to format the edited file via the language server: ``` mcp__lsp__format_document({ "file_path": "/abs/path/to/file" }) ``` Returns `TextEdit[]`. If non-empty, apply immediately: ``` mcp__lsp__apply_edit({ "workspace_edit": <TextEdit[]> }) ``` Skip if the user did not ask for formatting, or if there are unresolved errors (fix errors before formatting). ### Step 9 — Report using DiagnosticDiffFormat Output the final summary: ``` ## Edit Summary Files changed: N Errors introduced: A → Errors resolved: B (net: A-B) Warnings introduced: C → Warnings resolved: D ### Introduced errors - file.go:34 — undefined: MyType ### Resolved errors - file.go:12 — unused variable: x ``` --- ## Decision Guide | Net change | Action | |------------|--------| | 0 | Safe. No new errors. | | Negative | Net improvement — errors resolved. Safe. | | Positive (after code actions) | **Do NOT commit.** Offer to revert. | When net change > 0 after code actions: 1. Show the full list of remaining introduced errors. 2. Offer to revert using the original `old_string` in a follow-up `Edit` call. 3. Wait for user decision before proceeding. Do not commit or stage files when net change > 0. --- ## Multi-file workflow For edits spanning multiple files (e.g., changing a function signature and all its call sites): 1. **Open all files** in Step 1. 2. **Collect BEFORE diagnostics** for all files. 3. **Simulate each file** independently in Step 3 — sum `net_delta` values. 4. **Apply edits file by file** in Step 4 — stop on first failure. 5. **Collect AFTER diagnostics** for all files and merge. 6. **Check code actions** on any file showing new errors. Report the combined diagnostic diff across all files in the final summary.

lsp-simulate

Speculative code editing session — simulate changes in memory before touching disk. Use when planning edits that might break things, exploring refactors across multiple files, or verifying an edit is safe before applying.

> Requires the agent-lsp MCP server. # lsp-simulate Simulate code edits in memory before writing to disk. The LSP server applies your changes to an in-memory overlay, runs diagnostics, and reports whether the edit is safe — without touching any files. ## Prerequisites LSP must be running for the target workspace. If not yet initialized, call `start_lsp` before any simulation tool. ``` mcp__lsp__start_lsp(root_dir: "/your/workspace") ``` Auto-init note: agent-lsp supports workspace auto-inference from file paths. Explicit `start_lsp` is only needed when switching workspace roots. ## Quick Start (single edit) For a single what-if check, use `simulate_edit_atomic` — it creates a session, applies the edit, evaluates, and destroys the session in one call: ``` mcp__lsp__simulate_edit_atomic( workspace_root: "/your/workspace", language: "go", file_path: "/abs/path/to/file.go", start_line: 42, start_column: 1, end_line: 42, end_column: 20, new_text: "replacement text" ) ``` Result: ``` { net_delta: 0 } -- safe to apply { net_delta: 2 } -- 2 new errors introduced; do NOT apply ``` `net_delta: 0` means no new errors were introduced. Positive values mean errors were introduced — inspect `errors_introduced` before deciding. ## Full Session Workflow (multiple edits) Use a full session when applying several edits that build on each other, or when you want to inspect the patch before deciding whether to write to disk. **Step 1 — Create a simulation session** ``` mcp__lsp__create_simulation_session( workspace_root: "/your/workspace", language: "go" ) → { session_id: "abc123" } ``` **Step 2 — Apply edits in-memory** Call `simulate_edit` one or more times. All edits are in-memory only. Positions are 1-indexed (matching editor line numbers and `cat -n` output). ``` mcp__lsp__simulate_edit( session_id: "abc123", file_path: "/abs/path/to/file.go", start_line: 10, start_column: 1, end_line: 10, end_column: 30, new_text: "func NewClient(cfg Config) *Client {" ) → { session_id: "abc123", edit_applied: true, version_after: 1 } ``` Repeat for additional edits as needed. **Step 3 — Evaluate the session** ``` mcp__lsp__evaluate_session( session_id: "abc123", scope: "file" ) → { net_delta: 0, confidence: "high", errors_introduced: [], errors_resolved: [], edit_risk_score: 0.0, affected_symbols: [] } ``` `scope: "file"` (default) is faster and returns `confidence: "high"`. `scope: "workspace"` catches cross-file type errors but returns `confidence: "eventual"` (results may not be fully settled). **Step 4 — Decision gate** If `net_delta == 0`, proceed to commit. Otherwise, discard: ``` mcp__lsp__discard_session(session_id: "abc123") ``` **Step 5 — Commit the session** ``` -- Preview patch only (no disk write): mcp__lsp__commit_session(session_id: "abc123", apply: false) -- Write to disk: mcp__lsp__commit_session(session_id: "abc123", apply: true) ``` **Step 6 — Destroy the session (always)** ``` mcp__lsp__destroy_session(session_id: "abc123") ``` Always call `destroy_session` after commit or discard to release server resources. See [Cleanup Rule](#cleanup-rule) below. ## Chained Mutations (simulate_chain) Use `simulate_chain` when you have a sequence of edits and want to find how far through the sequence is safe to apply. Unlike multiple `simulate_edit` calls, `simulate_chain` evaluates diagnostics after each step. ``` mcp__lsp__simulate_chain( session_id: "abc123", edits: [ { file_path: "/abs/file.go", start_line: 5, start_column: 1, end_line: 5, end_column: 40, new_text: "type Foo struct { Bar int }" }, { file_path: "/abs/file.go", start_line: 20, start_column: 1, end_line: 20, end_column: 10, new_text: "f.Bar" }, { file_path: "/abs/other.go", start_line: 8, start_column: 1, end_line: 8, end_column: 10, new_text: "x.Bar" } ] ) → { steps: [ { step: 1, net_delta: 0, errors_introduced: [] }, { step: 2, net_delta: 0, errors_introduced: [] }, { step: 3, net_delta: 1, errors_introduced: [...] } ], safe_to_apply_through_step: 2, cumulative_delta: 1 } ``` `safe_to_apply_through_step: 2` means steps 1 and 2 are safe; step 3 introduced errors. Commit the session after reviewing to apply steps 1–2, or discard to cancel everything. ## Decision Guide | net_delta | confidence | Action | |-----------|-------------|-------------------------------------------------------------------| | 0 | high | Safe. Commit or apply. | | 0 | eventual | Likely safe. Workspace scope — re-evaluate if risk matters. | | > 0 | any | Do NOT apply. Inspect `errors_introduced`. Discard session. | | > 0 | partial | Timeout. Results incomplete. Discard and retry with smaller scope.| ## Session States | State | Meaning | Next step | |-----------|---------------------------------------------------------------|------------------------| | created | Session initialized, no edits yet | simulate_edit | | mutated | One or more edits applied in-memory | evaluate_session | | evaluated | Diagnostics collected | commit or discard | | committed | Patch returned (and optionally written to disk) | destroy_session | | discarded | In-memory edits reverted, no disk write | destroy_session | | dirty | Revert failed or version mismatch; session is inconsistent | destroy_session only | A session in `dirty` state cannot be recovered — call `destroy_session` immediately. ## Cleanup Rule Always call `destroy_session` after finishing a session, even on error paths: ``` -- After commit: mcp__lsp__commit_session(session_id: "abc123", apply: true) mcp__lsp__destroy_session(session_id: "abc123") -- After discard: mcp__lsp__discard_session(session_id: "abc123") mcp__lsp__destroy_session(session_id: "abc123") ``` **MCP server restart:** Sessions are ephemeral — they live in server memory only. If the MCP server restarts, all session IDs become invalid. To preserve work across a restart, call `commit_session(apply: false)` first to get a portable patch, then re-apply it after the server restarts. See [references/patterns.md](references/patterns.md) for detailed field descriptions and confidence interpretation.

lsp-test-correlation

Find and run the tests that cover a source file. Use after editing a file to discover exactly which test files and test functions need to run — without running the entire test suite.

> Requires the agent-lsp MCP server. # lsp-test-correlation Discover which tests cover a source file, then run only those tests. Faster than running the full suite when you've changed one or two files and want targeted feedback. ## When to use - After editing a source file: "Which tests do I need to run for this change?" - Before committing: run only the tests that cover what you touched - Debugging a failure: find which test file corresponds to a broken source file - Code review: understand what test coverage exists for a file before merging Use `/lsp-verify` instead when you want to run the full suite and check all three layers (diagnostics + build + tests). Use this skill when you want fast, scoped test execution. --- ## Workflow ### Step 1 — Find correlated test files Call `get_tests_for_file` for each edited source file: ``` mcp__lsp__get_tests_for_file({ "file_path": "/abs/path/to/source.go" }) ``` Returns the test files that correspond to the source file. For multiple edited files, call once per file. **If no test files are returned:** the source file may have no dedicated test file, or the mapping is not resolvable (e.g. integration tests in a separate directory). See Step 2 for fallback. ### Step 2 — Enumerate test functions (fallback or enrichment) If `get_tests_for_file` returns test files, use `get_workspace_symbols` to list the test functions defined in those files: ``` mcp__lsp__get_workspace_symbols({ "query": "Test" }) ``` Filter results to the correlated test files from Step 1. This gives you the specific test function names to run rather than the whole test file. **Fallback (no test files found):** query `get_workspace_symbols` for test functions that contain the changed symbol's name: ``` mcp__lsp__get_workspace_symbols({ "query": "Test<ChangedFunctionName>" }) ``` This catches cases where `get_tests_for_file` misses indirect coverage. ### Step 3 — Report the correlation map Before running, report what was found: ``` ## Test correlation for <file> Source file: internal/tools/analysis.go Test files: → internal/tools/analysis_test.go Tests: TestHandleGetCodeActions, TestHandleGetCompletions, TestHandleGetDocumentSymbols No correlated test files found for: internal/lsp/normalize.go → Fallback: TestNormalizeCompletion, TestNormalizeDocumentSymbols (from workspace symbol search) ``` If the user provided `run=true` or asks to run, proceed to Step 4. Otherwise stop here and let the user decide. ### Step 4 — Run correlated tests Run only the correlated test files or functions. Scope as tightly as possible: **Go — run specific package:** ``` mcp__lsp__run_tests({ "workspace_dir": "<root>", "test_filter": "TestHandleGetCodeActions|TestHandleGetCompletions" }) ``` If `run_tests` does not support `test_filter`, pass the package path instead of the workspace root to narrow scope. The test output will be smaller and faster than running `./...`. **Output handling:** If test output is large, do not read it in full. Search for failures: ``` grep -E "^(FAIL|--- FAIL)" <output_file> ``` ### Step 5 — Report results ``` ## Test Results Ran 3 tests in internal/tools/analysis_test.go PASSED (2): TestHandleGetCodeActions TestHandleGetCompletions FAILED (1): TestHandleGetDocumentSymbols — expected 3 symbols, got 2 (analysis_test.go:87) Recommendation: Fix TestHandleGetDocumentSymbols before committing. ``` --- ## Multi-file workflow For changes spanning multiple source files: 1. Call `get_tests_for_file` for each changed file in parallel. 2. Deduplicate the resulting test files (the same test file may cover multiple source files). 3. Report the full correlation map before running. 4. Run the deduplicated test set once. ``` ## Test correlation for 3 changed files internal/tools/analysis.go → internal/tools/analysis_test.go internal/lsp/client.go → internal/lsp/client_test.go, internal/lsp/client_completion_test.go internal/resources/resources.go → (no dedicated test file) Deduplicated test files to run: 3 ``` --- ## Decision guide | Situation | Action | |-----------|--------| | `get_tests_for_file` returns test files | Use those; enumerate functions via `get_workspace_symbols` | | No test files returned | Fallback to `get_workspace_symbols` with changed symbol names | | Test files found but no matching test functions | Report gap — this source file may lack unit test coverage | | More than 10 test files returned | Don't run all; use `/lsp-verify` for full suite instead | | Test fails | Run `/lsp-verify` for full diagnostic picture | --- ## Example ``` # "I edited internal/tools/symbol_source.go — which tests should I run?" get_tests_for_file(file_path="/repo/internal/tools/symbol_source.go") → internal/tools/symbol_source_test.go get_workspace_symbols(query="TestGetSymbolSource") → TestGetSymbolSource_ContainsPosition (line 12) → TestGetSymbolSource_FindInnermost (line 34) → TestGetSymbolSource_PositionPattern (line 67) # Report correlation, then run: run_tests(workspace_dir="/repo", test_filter="TestGetSymbolSource") → 3 passed in 0.4s ```

lsp-understand

Deep-dive exploration of unfamiliar code — given a symbol or file, builds a complete Code Map showing type info, implementations, call hierarchy (2-level depth limit), all references, and source. Broader than lsp-explore: accepts files, synthesizes multi-symbol relationships, and produces a navigable dependency map.

> Requires the agent-lsp MCP server. # lsp-understand Deep-dive exploration of unfamiliar code — given a symbol or file, synthesizes hover info, implementations, call hierarchy (bounded to 2 levels), all references, and source into a structured Code Map. Read-only — does not modify any files. --- ## Differentiation from lsp-explore `/lsp-explore` is a single-symbol pass: given one symbol name, it runs hover + implementations + call hierarchy + references and produces a per-symbol report. Use lsp-explore for quick "what is this one thing" questions. `/lsp-understand` is broader in three ways: 1. **Accepts a file path as input** — explores all exported symbols in that file as a group (Mode B), rather than requiring a single symbol name. 2. **Synthesizes cross-symbol relationships** — produces a dependency map showing how entry points call each other, share callers, or implement the same interface, rather than isolated per-symbol reports. 3. **Enforces a 2-level call hierarchy depth limit** — prevents infinite recursion in deeply connected code. Use lsp-understand for "how does this module work as a whole." --- ## Input — Two Modes **Mode A (symbol):** User provides a symbol name in dot notation (e.g., `"codec.Encode"`, `"Handler.ServeHTTP"`). **Mode B (file):** User provides an absolute file path. All exported symbols in the file become the entry points. --- ## Prerequisites Call `mcp__lsp__get_server_capabilities` before Step 2 to determine which capabilities are available. Skip steps that require missing capabilities: - `go_to_implementation`: skip Step 2b if `implementationProvider: false` - `call_hierarchy`: skip Steps 2c and 2d if `callHierarchyProvider: false`; note in the Code Map output that call hierarchy was unavailable --- ## Step 1 — Entry Point Resolution ### Mode A: Single Symbol Call `mcp__lsp__go_to_symbol` to locate the symbol definition: ``` mcp__lsp__go_to_symbol({ "symbol_path": "<dot-notation name>", // e.g. "codec.Encode" "workspace_root": "<root>" // optional }) → returns: file_path, line, column (1-indexed) ``` Record `file_path`, `line`, and `column`. If `go_to_symbol` returns nothing, report: > Symbol not found: `<name>` > Check the dot-notation path (e.g. "Package.Symbol") and ensure the workspace > root covers the file. Stop immediately — do not proceed to Step 2. The single symbol becomes the sole entry point. ### Mode B: File Path Call `mcp__lsp__open_document` then `mcp__lsp__get_document_symbols`: ``` mcp__lsp__open_document({ "file_path": "<absolute path>" }) mcp__lsp__get_document_symbols({ "file_path": "<absolute path>" }) → returns: list of symbols with kind, line, column ``` Filter to exported symbols: - **Go:** uppercase first letter - **TypeScript/JavaScript:** `export` keyword - **Rust:** `pub` visibility Cap at **10 exported symbols maximum**. If more than 10 are found, prioritize top-level functions and types; skip constants and variables. Each filtered symbol becomes an entry point with its `file_path`, `line`, and `column`. --- ## Step 2 — Per-Symbol Analysis For each entry point, run the following sub-steps. Where possible, parallelize calls within each step. ### 2a — Type Info and Docs Call `mcp__lsp__get_info_on_location` using `position_pattern` with the `@@` marker (see references/patterns.md): ``` mcp__lsp__get_info_on_location({ "file_path": "<file>", "position_pattern": "<symbol@@name>", "line_scope_start": <line - 5>, "line_scope_end": <line + 5> }) → returns: hover text with type signature and doc comment ``` Store result as `hover_text`. If the call fails or returns nothing, set `hover_text` to an empty string. Do not stop. ### 2b — Implementations (capability-gated) If `implementationProvider` is available in server capabilities: ``` mcp__lsp__go_to_implementation({ "file_path": "<file>", "line": <line>, "column": <column> }) → returns: list of concrete implementation locations ``` Skip if capability is absent. Record `"not supported by this server"` rather than stopping. ### 2c — Incoming Call Hierarchy (bounded to 2 levels) If `callHierarchyProvider` is available: **Level 1 — Direct callers:** ``` mcp__lsp__call_hierarchy({ "file_path": "<file>", "line": <line>, "column": <column>, "direction": "incoming" }) → returns: list of direct caller functions with file and line ``` **Level 2 — Callers of callers:** For each Level 1 caller, call `mcp__lsp__call_hierarchy` once more: ``` mcp__lsp__call_hierarchy({ "file_path": "<caller file>", "line": <caller line>, "column": <caller column>, "direction": "incoming" }) → returns: Level 2 callers ``` **STOP at Level 2 — do not recurse further under any circumstances.** If Level 2 callers > 10: summarize by count and file, do not list individually. ### 2d — Outgoing Calls (Level 1 only) If `callHierarchyProvider` is available: ``` mcp__lsp__call_hierarchy({ "file_path": "<file>", "line": <line>, "column": <column>, "direction": "outgoing" }) → returns: list of functions this symbol calls ``` **Level 1 only — no recursion.** ### 2e — All References ``` mcp__lsp__get_references({ "file_path": "<file>", "line": <line>, "column": <column>, "include_declaration": false }) → returns: every usage site across the workspace ``` Group by file and count distinct files. ### 2f — Source ``` mcp__lsp__get_symbol_source({ "file_path": "<file>", "line": <line>, "column": <column> }) → returns: implementation body ``` --- ## Step 3 — Synthesize Relationships After analyzing all entry points, identify cross-symbol relationships: - **Internal calls:** Which entry points call each other? (from outgoing calls in Step 2d) - **Shared callers:** Which entry points are called by the same Level 1 callers? - **Shared interface:** Which entry points implement the same interface? (from Step 2b) This synthesis step is what distinguishes `/lsp-understand` from running multiple `/lsp-explore` calls. The output is a dependency map, not isolated per-symbol reports. --- ## Step 4 — Output: Code Map Produce a structured Code Map with these sections: ``` ## Code Map: <target> ### Summary <2-3 sentence description of what this code does, synthesized from hover docs and source reading> ### Symbols (<N> analyzed) #### <SymbolName> - **Type:** <type signature from hover> - **Source:** <file:line> - **Incoming callers (L1):** <list; count only if > 5> - **Incoming callers (L2):** <summarized; e.g., "called by 3 HTTP handlers"> - **Outgoing calls:** <what this symbol calls> - **Implements:** <interface name, if applicable> - **References:** N sites across M files ### Dependency Relationships <symbols that call each other, as a simple text diagram or list> e.g.: HandlerA → Parse → Validate HandlerB → Parse ### Entry Points to This Code <top-level callers that are NOT in this file — where does outside code call in?> ### Depth-limit Note Call hierarchy stopped at 2 levels. <N> additional callers exist beyond Level 2 — use /lsp-explore on specific symbols to drill deeper. ``` --- ## Depth Control Rules These limits are hard constraints — never exceed them: - Incoming call hierarchy recursion **stops at Level 2** - Outgoing calls: **Level 1 only**, no recursion - If Level 2 callers > 10: **summarize by count and file**, do not list individually - Do NOT follow call chains beyond these limits under any circumstances --- ## Example ``` Goal: understand how the file pkg/codec/encoder.go works as a whole Step 1 — Mode B (file path) open_document: pkg/codec/encoder.go get_document_symbols: pkg/codec/encoder.go → exported symbols: Encoder (type), Encode (func), Reset (func), NewEncoder (func) → 4 exported symbols (under 10 cap) get_server_capabilities → go_to_implementation: supported → call_hierarchy: supported Step 2 — Per-symbol analysis (run in parallel across symbols) Symbol: NewEncoder (pkg/codec/encoder.go:12) get_info_on_location → "func NewEncoder(w io.Writer) *Encoder" go_to_implementation → 0 (concrete function) call_hierarchy incoming L1 → 5 callers call_hierarchy incoming L2 → 3 callers of those callers call_hierarchy outgoing → calls: bufio.NewWriter get_references → 5 sites in 3 files get_symbol_source → implementation body Symbol: Encode (pkg/codec/encoder.go:28) get_info_on_location → "func (e *Encoder) Encode(v any) error" go_to_implementation → implements codec.Encoder interface call_hierarchy incoming L1 → 8 callers (listed) call_hierarchy incoming L2 → > 10: "12 additional callers across 5 files" call_hierarchy outgoing → calls: NewEncoder, e.w.Flush get_references → 8 sites in 5 files get_symbol_source → implementation body (similar for Encoder type and Reset func...) Step 3 — Synthesize relationships - Encode calls NewEncoder (internal dependency) - NewEncoder and Encode share callers in cmd/main.go - Encode implements codec.Encoder interface ## Code Map: pkg/codec/encoder.go ### Summary This file implements a streaming JSON encoder backed by a buffered writer. NewEncoder constructs an Encoder wrapping any io.Writer; Encode serializes values and flushes. Reset allows reuse without allocation. ### Symbols (4 analyzed) #### NewEncoder - **Type:** func NewEncoder(w io.Writer) *Encoder - **Source:** pkg/codec/encoder.go:12 - **Incoming callers (L1):** cmd.main, app.Start, loader.Load, test.Setup, bench.Run - **Incoming callers (L2):** 3 callers across 2 files - **Outgoing calls:** bufio.NewWriter - **Implements:** n/a - **References:** 5 sites across 3 files #### Encode - **Type:** func (e *Encoder) Encode(v any) error - **Source:** pkg/codec/encoder.go:28 - **Incoming callers (L1):** 8 callers (cmd/main.go, internal/app.go, ...) - **Incoming callers (L2):** 12 additional callers across 5 files (depth limit reached) - **Outgoing calls:** NewEncoder, e.w.Flush - **Implements:** codec.Encoder - **References:** 8 sites across 5 files ... ### Dependency Relationships cmd.main → NewEncoder → bufio.NewWriter cmd.main → Encode → NewEncoder Encode → Reset ### Entry Points to This Code - cmd.main (cmd/main.go:14) - app.Start (internal/app.go:31) - loader.Load (internal/loader.go:55) ### Depth-limit Note Call hierarchy stopped at 2 levels. 12 additional callers exist beyond Level 2 for Encode — use /lsp-explore on specific symbols to drill deeper. ```

lsp-verify

Full three-layer verification after any change — LSP diagnostics + compiler build + test suite, ranked by severity. Use after completing any edit, refactor, or feature to confirm nothing is broken before committing.

> Requires the agent-lsp MCP server. # lsp-verify: Three-Layer Verification ## When to Use Run this skill after any significant change to verify correctness at every level: - After editing source files (logic changes, refactors, new functions) - After merging or rebasing branches - After dependency updates or configuration changes - Before committing or pushing code ## Input - `workspace_dir` (required): absolute path to the workspace root (e.g. `/Users/you/code/myproject`) - `changed_files` (optional): list of files you edited — used for targeted diagnostics ## Execution ### Pre-step: Test correlation (when `changed_files` is provided) Before running the three layers, call `get_tests_for_file` for each changed source file to build a source → test file map: ``` mcp__lsp__get_tests_for_file({ "file_path": "<changed/source/file>" }) ``` Returns the test files that correspond to each source file. Store this map — it is used in Layer 3 to focus failure analysis. If `changed_files` is unknown, skip this step. **Run all three layers in parallel** — they are independent and do not need to be sequenced. Issue all three calls in the same message to minimize wall time. ### Layer 1: LSP Diagnostics Call `mcp__lsp__get_diagnostics` with `file_path` set to each changed file. `get_diagnostics` takes a file path, not a workspace directory. Note: requires LSP to be initialized. If not yet running, call `start_lsp` with the workspace root first. ``` mcp__lsp__get_diagnostics({ "file_path": "<path/to/changed/file>" }) ``` Call once per changed file. If you don't know which files changed, call it on the primary files touched in this session. Rank results by severity: errors first, then warnings. ### Layer 2: Build ``` mcp__lsp__run_build({ "workspace_dir": "<workspace_dir>" }) ``` Returns `{ "success": bool, "errors": [...] }`. A failed build means the code does not compile. Build errors are blocking — must be resolved before shipping. ### Layer 3: Tests ``` mcp__lsp__run_tests({ "workspace_dir": "<workspace_dir>" }) ``` Does NOT require `start_lsp`. Returns `{ "passed": bool, "failures": [...] }`. **Large output warning:** `run_tests` on large repos can return hundreds of thousands of characters and exceed the context window. If the result is saved to a file rather than returned inline, do NOT attempt to read the whole file. Instead, search it for failures: ```bash grep -E "^(FAIL|--- FAIL)" <output_file> ``` Or scope tests to the correlated test files from the pre-step to avoid the size issue entirely: ```bash GOWORK=off go test -count=1 -short ./internal/mypackage/... 2>&1 | grep -E "FAIL|ok" ``` **Using test correlation:** If the pre-step produced a source → test file map, cross-reference failing test names against that map. For each failure, note whether it is in a correlated test file (directly covers the changed code) or an unrelated test file (collateral failure from a shared dependency). This distinction guides where to investigate first. Test failures are blocking — they indicate regressions or unmet contracts. ## Output Format After running all three layers, produce a structured report: ``` ## Verification Report ### Layer 1: LSP Diagnostics [CLEAN / N errors, M warnings] <details if N > 0 or M > 0> Errors: - file:line - message Warnings: - file:line - message </details> ### Layer 2: Build [PASSED / FAILED - N errors] <details if FAILED> - error message (file:line) </details> ### Layer 3: Tests [PASSED / FAILED - N failures] <details if FAILED> - test name: message (file:line) [correlated / unrelated] </details> <if test correlation map exists> Test files covering changed source: changed/source/file.go → test/source_file_test.go </if> ### Summary Overall: CLEAN / NEEDS ATTENTION Blocking issues: [errors that must be fixed before shipping] ``` - **CLEAN**: no errors in any layer (warnings are advisory only) - **NEEDS ATTENTION**: one or more blocking issues found ## Blocking vs Advisory | Layer | Errors | Warnings | |-------|--------|----------| | LSP Diagnostics | Blocking | Advisory | | Build | All blocking | N/A | | Tests | All blocking | N/A | Build errors and test failures block shipping. LSP warnings and style suggestions are advisory — document them but do not treat as blockers unless they indicate logical errors. ## When Verification Passes: Optional Format If all three layers are CLEAN and `changed_files` is known, offer to format the changed files before committing: ``` mcp__lsp__format_document({ "file_path": "<changed-file>" }) ``` Apply the returned `TextEdit[]` via `apply_edit` if non-empty. Run once per changed file. Skip if the user did not request formatting. --- ## When Errors Are Found: Applying Code Actions If Layer 1 returns errors, the LSP may offer quick fixes. For each error location, call `get_code_actions` to surface available fixes: ``` mcp__lsp__get_code_actions({ "file_path": "<file>", "line": <error line>, "column": <error column> }) ``` Returns a list of available actions (e.g. "Add missing import", "Implement interface methods", "Remove unused variable"). Pick the most appropriate one and apply it: ``` mcp__lsp__apply_edit({ "file_path": "<file>", "old_text": "<text to replace>", "new_text": "<replacement>" }) ``` Or if the code action returns a `workspace_edit`, pass it directly to `apply_edit` via the `workspace_edit` parameter. After applying, **re-run Layer 1** on the affected file to confirm the error is resolved before moving on. Do not apply multiple code actions in bulk without verifying each one — they may interact. **When to use:** Compile errors from missing imports, unimplemented interface methods, or type mismatches often have one-click fixes available. Manual reasoning is still required for logic errors.