| name | code-cleanup |
| description | Fix Solidity code quality issues using LSP diagnostics and structural analysis. Reacts to live diagnostics (naming, gas, safety, unused imports) published by the Solidity Language Server, and uses LSP findReferences for safe cross-file renames. Detects dead code via LSP reference counting. Use when asked to clean up, fix warnings, or improve code quality. Use when this capability is needed. |
| metadata | {"author":"asyncswap"} |
Solidity Code Cleanup
Fix code quality issues using live LSP diagnostics and LSP structural analysis. The Solidity Language Server continuously publishes diagnostics in <new-diagnostics> blocks as you read and edit files — this skill reads those diagnostics and applies fixes using LSP-guided refactoring.
How it works
The Solidity Language Server publishes diagnostics automatically when files are read or edited. These appear as <new-diagnostics> blocks in the conversation with:
- Code: the lint rule (e.g.,
screaming-snake-case-immutable, asm-keccak256)
- File: file path
- Line: line number
- Message: description
This skill collects those diagnostics, categorizes them, and uses LSP operations (findReferences, documentSymbol, goToDefinition) to apply safe fixes across the codebase.
Input
$ARGUMENTS — Parse the arguments:
- file-path (optional): Specific file to clean up. If omitted, fix all diagnostics visible in the current conversation.
- --fix (optional): Apply fixes automatically for safe categories (naming, style). Safety and gas fixes are always presented as choices.
- --category (optional):
naming, gas, safety, style, dead-code, all (default).
Phase 1: Collect Diagnostics
Step 1: Gather current diagnostics
Check the conversation for all <new-diagnostics> blocks. Extract each diagnostic entry.
If no diagnostics are visible yet, read the target file(s) with the Read tool — the LSP will publish diagnostics as a side effect of opening the file.
If a specific file was given but not yet read, read it to trigger diagnostics.
Step 2: Categorize
| Category | Lint Codes | Auto-fixable? |
|---|
| naming | screaming-snake-case-immutable, screaming-snake-case-constant | YES — mechanical rename via LSP findReferences |
| gas | asm-keccak256, asm-mstore | NO — present as suggestion |
| safety | erc20-unchecked-transfer, divide-before-multiply | NO — present options |
| style | unwrapped-modifier-logic, unused-import | YES — safe to apply |
Phase 2: LSP Structural Analysis
Step 3: Reference lookup for renames
For each naming diagnostic (e.g., router should be ROUTER):
- Use
LSP findReferences on the symbol at the flagged line and column
- Collect ALL reference sites across all files (definition + every usage)
- Verify the new name doesn't collide:
Grep for the proposed name in the codebase
- Generate Edit operations for the definition + every reference site
- Apply edits starting from the last file/line to avoid offset shifts
Step 4: Dead code detection
Use LSP documentSymbol on the target file(s) to list all functions. For each internal or private function:
LSP findReferences at the function definition line
- Count references excluding the definition line itself
- Zero external references = dead code candidate
Only flag internal/private functions. Public/external functions may be called by contracts outside the codebase.
Step 5: Unused import detection
If unused-import diagnostics are present from the LSP, use them directly.
Otherwise, for each import in the file:
- Extract the imported symbol name from the import statement
Grep for that symbol name in the file body (below the imports section)
- Zero matches in the body = unused import
Phase 3: Fix Application
Step 6: Apply fixes by category
Naming (auto-fix with --fix):
- Read the diagnostic message to determine the suggested name (e.g.,
router → ROUTER)
- Use
LSP findReferences to get every usage site across all files
- Apply
Edit with replace_all at each file, or targeted edits per reference
- Read the file after editing to trigger fresh diagnostics — verify the naming diagnostic is gone
Style (auto-fix with --fix):
unused-import: Read the file, identify the flagged import line, delete it with Edit
unwrapped-modifier-logic: Read the modifier body, extract logic into an _internal function, replace modifier body with a call to it. Use LSP findReferences to check the new function name doesn't collide.
Safety (always interactive — present options, do not auto-apply):
erc20-unchecked-transfer: Read the flagged line and surrounding context (5 lines). Present options:
- Wrap with return value check:
if (!token.transfer(...)) revert TRANSFER_FAILED()
- Use SafeERC20:
token.safeTransfer(...) (requires import)
- Use low-level call pattern (if the codebase already uses this elsewhere — check with Grep)
- Recommend the option that matches existing patterns in the codebase
Gas (suggestion only — do not apply unless explicitly asked):
asm-keccak256: Read the flagged keccak256(abi.encode(...)) expression. Show the inline assembly equivalent as a suggestion. Note the readability tradeoff.
- Present the suggestion but do NOT apply by default
Step 7: Verify after fixes
After applying any edit:
- Read the edited file to trigger fresh LSP diagnostics
- Check that the fixed diagnostic no longer appears in the new
<new-diagnostics> block
- If new errors appear (e.g., compilation error from a bad rename), report and revert
Output: Preview-Then-Apply Workflow
Never apply fixes silently. Always show the diff first, then apply only after presenting it.
Step 1: Show the cleanup summary
## Code Cleanup — <file or directory>
| Category | Issues | Fixable | Description |
|---|---|---|---|
| naming | 2 | 2 | `router` → `ROUTER`, ... |
| gas | 5 | 0 | keccak256 inline assembly |
| safety | 2 | 0 | unchecked ERC-20 transfer |
| style | 1 | 1 | unwrapped modifier logic |
| dead-code | 0 | — | — |
Step 2: Present each fix as a diff BEFORE applying
For each fixable issue, read the file, construct the old and new strings, and present the change as a diff block in your text output:
// File: AsyncSwap.sol:40
- AsyncRouter public immutable router;
+ AsyncRouter public immutable ROUTER;
// File: AsyncSwap.sol:292
- router.executeSwap{value: msg.value}(
+ ROUTER.executeSwap{value: msg.value}(
// File: AsyncSwap.sol:189
- router.withdrawNative(to, amount);
+ ROUTER.withdrawNative(to, amount);
For renames, list ALL reference sites from LSP findReferences so the user sees the full blast radius before any edits happen.
Step 3: Apply with Edit tool
After presenting the diffs, apply each change using the Edit tool. The Edit tool shows its own diff to the user for approval. Apply changes one file at a time, starting from the bottom of each file (highest line number first) to avoid offset drift.
For cross-file renames:
- Present ALL diffs across all files in one text block
- Apply edits file by file — definition site first, then usage sites
- After all edits, Read the files to trigger fresh LSP diagnostics
- Confirm the diagnostic is resolved (no longer appears)
Step 4: Present non-fixable issues as choices
For safety issues, present options with diffs for each:
erc20-unchecked-transfer — CurrencySettler.sol:18
Current:
IERC20(token).transferFrom(payer, address(manager), amount);
Option A:
```diff
- IERC20(token).transferFrom(payer, address(manager), amount);
+ if (!IERC20(token).transferFrom(payer, address(manager), amount)) revert();
Option B:
+ import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
...
- IERC20(token).transferFrom(payer, address(manager), amount);
+ SafeERC20.safeTransferFrom(IERC20(token), payer, address(manager), amount);
Option C: Keep as-is (codebase uses low-level .call() pattern elsewhere)
For gas suggestions, show the before/after but mark as optional:
asm-keccak256 — AsyncSwap.sol:537 (×5 instances)
- bytes32 orderId = keccak256(abi.encode(order));
+ bytes32 orderId;
+ assembly {
+ let ptr := mload(0x40)
+ mstore(ptr, mload(order))
+ mstore(add(ptr, 0x20), mload(add(order, 0x20)))
+ mstore(add(ptr, 0x40), mload(add(order, 0x40)))
+ orderId := keccak256(ptr, 0x60)
+ }
Saves ~30 gas per call. Reduces readability. Apply only if gas is a priority.
### Step 5: Verify
After all edits, Read each modified file. Check:
- The fixed diagnostic no longer appears in `<new-diagnostics>`
- No new error diagnostics were introduced
- If a new error appears, show it and offer to revert
## Reactive Mode
This skill works best reactively during development:
1. User writes or edits Solidity code
2. LSP publishes diagnostics in `<new-diagnostics>`
3. User says "fix those warnings" or "clean up"
4. Skill reads the diagnostics from conversation context
5. Presents diffs for each fix
6. Applies via Edit tool (user sees each change for approval)
7. Reads files after to verify diagnostics are resolved
---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/asyncswap) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-16 -->