lsp-safe-edit

$npx mdskill add blackwell-systems/agent-lsp/lsp-safe-edit

Prevents errors by validating code edits with LSP diagnostics

  • Reduces risk of breaking code during edits with diagnostic checks
  • Uses LSP server capabilities like diagnostics and code actions
  • Compares pre- and post-edit diagnostics to ensure correctness
  • Applies changes to disk only if errors are resolved or unchanged

SKILL.md

.github/skills/lsp-safe-editView on GitHub ↗
---
name: lsp-safe-edit
description: Wrap any code edit with before/after diagnostic comparison. Speculatively previews the change first (preview_edit), 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.
user-invocable: true
allowed-tools: mcp__lsp__start_lsp mcp__lsp__open_document mcp__lsp__get_diagnostics mcp__lsp__preview_edit mcp__lsp__simulate_chain mcp__lsp__suggest_fixes mcp__lsp__format_document mcp__lsp__apply_edit Edit Write Bash
license: MIT
compatibility: Requires the agent-lsp MCP server (github.com/blackwell-systems/agent-lsp)
metadata:
  optional-capabilities: codeActionProvider documentFormattingProvider
  tool_permissions:
    phases:
      setup:
        description: "Open files and capture baseline diagnostics"
        allowed:
          - "mcp__lsp__start_lsp"
          - "mcp__lsp__open_document"
          - "mcp__lsp__get_diagnostics"
        forbidden:
          - "mcp__lsp__apply_edit"
          - "Edit"
          - "Write"
      speculative_preview:
        description: "Simulate the edit in memory before touching disk"
        allowed:
          - "mcp__lsp__preview_edit"
          - "mcp__lsp__simulate_chain"
        forbidden:
          - "mcp__lsp__apply_edit"
          - "Edit"
          - "Write"
      apply:
        description: "Write the change to disk"
        allowed:
          - "Edit"
          - "Write"
          - "mcp__lsp__apply_edit"
        forbidden:
          - "mcp__lsp__simulate_*"
      verify_and_fix:
        description: "Collect post-edit diagnostics, surface code actions, format"
        allowed:
          - "mcp__lsp__get_diagnostics"
          - "mcp__lsp__suggest_fixes"
          - "mcp__lsp__apply_edit"        # for applying code action fixes
          - "mcp__lsp__format_document"
        forbidden:
          - "mcp__lsp__simulate_*"
          - "mcp__lsp__run_build"
          - "mcp__lsp__run_tests"
    global_forbidden:
      - "mcp__lsp__rename_symbol"          # safe-edit uses direct edits
      - "mcp__lsp__blast_radius"      # blast radius is lsp-impact's job
---

> 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 (preview_edit)

Before touching disk, call `mcp__lsp__preview_edit` to preview the
error delta of the intended change:

```
mcp__lsp__preview_edit(
  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:** `preview_edit` 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)

### 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__suggest_fixes`
at each error location to surface quick fixes:

```
mcp__lsp__suggest_fixes(
  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.

More from blackwell-systems/agent-lsp

SkillDescription
lsp-architectureGenerate a structural architecture overview of a codebase: languages, package map, entry points, dependency graph, and hotspots. One call for the big picture.
lsp-concurrency-auditConcurrency safety audit for a type or file. Maps all fields, traces which are accessed from concurrent contexts (goroutines, threads, async tasks), and flags fields that lack synchronization. Produces a field-level safety report. Language-agnostic across 4 concurrency families.
lsp-cross-repoCross-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.
lsp-dead-codeEnumerate 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.
lsp-docsThree-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.
lsp-edit-exportSafe 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.
lsp-edit-symbolEdit 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-explore"Tell me about this symbol": hover + implementations + call hierarchy + references in one pass — for navigating unfamiliar code.
lsp-extract-functionExtract 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.
lsp-fix-allApply 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.