/actually-lsp-doctor
Parse the user's args from the invocation:
fixas the first arg means skip the diagnostic report and jump straight to action.rust|typescript|rubyas an arg narrows focus to that ecosystem.- No args: full diagnostic report followed by interactive fix.
Step 1: Read project state
Read .claude/actually-lsp.json in the current project root. If missing, run detection: source lib/detect.sh and lib/ecosystems.sh from the plugin root (the env var CLAUDE_PLUGIN_ROOT points there), call detect_ecosystems "$PWD", and for each detected ecosystem compute the current state per the rules in CONTEXT.md.
Step 2: Diagnose (unless fix arg present)
Output a per-ecosystem report. For each ecosystem:
- State (from
CONTEXT.md's six states) - For non-
readyecosystems: what's needed to reachready - For
dismissedecosystems: note them but don't propose action
Keep tone terse. No celebration messaging.
Step 3: Act
For each ecosystem in no-lsp-plugin, server-not-runnable, or error:
no-lsp-plugin: try claude plugin install <recommended_plugin>@claude-plugins-official via Bash. The user gets a permission prompt. If denied, output the slash command form (/plugin install <recommended_plugin>@claude-plugins-official) and ask the user to run it themselves.
server-not-runnable: run env fixes per ecosystem via Bash. All env fixes auto-run; the user has implicit project consent.
- Rust:
cargo build - TypeScript:
npm install - Ruby:
bundle install, plusgem install ruby-lspifcommand -v ruby-lspis empty
error: surface the cached last_error from the state file. Ask the user how to proceed.
Step 4: Re-detect
Re-run detection (same as Step 1's "if missing" path) and compute the new state per ecosystem. Hold the result in memory; don't write the state file yet. Step 5 may downgrade ready ecosystems before persistence.
Step 5: Probe ready ecosystems via LSP
The goal is to ground a ready verdict in an actual LSP response, not just env state. Env can say "server should run" while the server fails to answer queries (not indexed yet, wrong workspace root, crashed).
For each ecosystem whose computed state is ready:
-
Ensure the
LSPtool is loaded. If it isn't available in this session, callToolSearchwith queryselect:LSP. IfToolSearchreturns no match, skip the probe for every ecosystem and note "LSP tool unavailable in session" in the report. Do not downgrade any state. This is an environment issue, not an ecosystem failure. -
Find a sample source file under
$PROJECT_DIR:- rust:
find "$PROJECT_DIR" -maxdepth 4 -type f -name '*.rs' -print -quit - typescript:
find "$PROJECT_DIR" -maxdepth 4 -type f \( -name '*.ts' -o -name '*.tsx' \) -print -quit - ruby:
find "$PROJECT_DIR" -maxdepth 4 -type f -name '*.rb' -print -quit
If no file is found, skip the probe for this ecosystem and note "no sample file" in the report. Do not downgrade.
- rust:
-
Call
LSP documentSymbolagainst the sample file atline: 1, character: 1. -
On error or non-array response: downgrade the ecosystem to
errorand setlast_errorto the stringified LSP response (or a short summary if the response is large). Env said the server should be runnable, so a probe failure is a real LSP failure worth surfacing on the next SessionStart. -
On success: count the symbols in the response array. State stays
ready. The count is for the report only; nothing extra goes into the state file.
Step 6: Write state and final report
Write the (possibly probe-updated) per-ecosystem state to .claude/actually-lsp.json using write_state from lib/state.sh. Output one status line per ecosystem:
readywith successful probe:<ecosystem>: ready (LSP: N symbols)readywith skipped probe:<ecosystem>: ready (LSP: skipped, <reason>)errorfrom probe failure:<ecosystem>: error (LSP probe failed: <summary>)- Other states:
<ecosystem>: <state>plus the recovery hint from Step 2
Keep tone terse. No celebration messaging.