Opt-in language-server client for phase-5-execute — locate symbols by coordinate (definition/references/documentSymbol/workspace-symbol) and apply a verified multi-file rename WorkspaceEdit whose footprint is captured from the edit and re-checked by diagnostics; consumption only, fail-soft to Read/Edit when unconfigured
Resources
1Install
npx skillscat add cuioss/plan-marshall/lsp-client Install via the SkillsCat registry.
LSP Client Skill
The consumption surface for a warm language server, reachable from aphase-5-execute leaf for the two things a server is better at than reading
files: locating work by coordinate, and applying a verified multi-file
edit. workspace/symbol, definition, references and documentSymbol
return coordinates, not bodies; rename returns a WorkspaceEdit — one
complete, parser-verified, multi-file patch — and publishDiagnostics returns a
correctness signal without a build.
This skill is script-deterministic — every verb is a deterministic executor
call. The server is hosted inside the envelope: lsp_client.py spawns the
configured server (e.g. pyright-langserver --stdio) as a short-lived
subprocess, uses it for the call, and tears it down. There is no daemon, no
socket, and no long-lived child — cold start is paid once per call, and the
natural unit is a per-call batch of lookups or one edit-and-re-diagnose.
The client is deliberately fail-soft and opt-in. When no server is
configured for the language, or a configured server does not start, the verb
returns degraded with fallback: read_edit, and the caller takes today'sRead / Edit path — byte-identically to a project that never configured a
server.
Enforcement
Base contract: See manage-contract.md for shared enforcement rules, TOON output format, and error-response patterns.
Execution mode: Run the verbs via the executor; parse the TOON output (status, state, provider_count) and route accordingly.
Prohibited actions:
- Do not treat a
degradedreturn as an error — it is the opt-out signal. Fall back toRead/Editand proceed. - Do not derive an edit's footprint from a later
git diff— theeditverb captures it from theWorkspaceEdititself and returns it infiles[]. Trust that footprint. - Do not read the
diagnoseverb as a replacement for the quality gate — it supplements the gate, it does not replace it. Run the canonical build (./pw verify) before relying on a clean result. - Do not invent script arguments not listed in the Canonical invocations section below.
Constraints:
- Strictly comply with all rules from persona-plan-marshall-agent, especially tool usage and workflow step discipline.
- All script output uses TOON format (see
plan-marshall:ref-toon-format). - The entry-point script (
lsp_client.py) is invoked only throughpython3 .plan/execute-script.pywith the 3-part notation.
The coverage contract (no silent empty result)
Every verb keeps no server ran distinguishable from the server ran and found
nothing — the same fail-closed discipline the module graph applies one tier up
(resolver_count / attributor_count / files_scanned; seedoc/concepts/code-intelligence.adoc). The discriminator is state +provider_count:
state |
provider_count |
status |
Meaning |
|---|---|---|---|
not_configured |
0 | degraded |
No server is configured (or it is disabled) for this language. Absence of capability — fall back to Read/Edit. |
unreachable |
0 | degraded |
A server is configured but did not start/initialise; carries a reason. Fall back to Read/Edit. |
ok |
1 | success |
A server ran. An empty locations[] / diagnostics[] is then a real, positive answer, not a missing capability. |
not_configured and unreachable are separately representable, so an operator
can tell "install/configure a server" from "the configured server is broken".
preflight reports the same three situations but names the healthy one ready
(configured and reachable) rather than ok — ready is a precondition
check, ok is the outcome of a run verb that actually executed. A consumer that
gates before calling a run verb checks preflight for state: ready.
The write side (edit) — an edit nobody read, re-checked by the parser
The real risk of a WorkspaceEdit touching many files is that it is a mutation
nothing read. The edit verb ships, rather than asserts, the design rule
that answers it:
- Footprint from the edit. The touched-file list (
files[], each with anedit_count) is captured from theWorkspaceEdititself — never from a diff
taken afterwards. - Apply, then verify. The edit is applied to the footprint files, then
diagnostics are re-run over exactly those files. - A worsened diagnostic set fails the step. If the post-application error
count exceeds the pre-application count, the verb returnsstatus: failed
withreason: diagnostics_worsened, rolls the change back
(rolled_back: true), and reportserrors_before/errors_after/new_diagnostics[]. An edit that broke the parse never lands silently.
Diagnostics (diagnose) supplement the gate — they do not replace it
diagnose surfaces the class of errors a server sees ahead of the build
round-trip — unresolved imports, syntax errors, type errors. It is a pre-build
correctness signal, and every payload carries the boundary in boundary_note.
It does not run the project's quality gate, tests, linters, or coverage, and
a clean diagnose is not a green build. Run ./pw verify (the canonical
build) before relying on a clean result. Use diagnose to catch a broken edit
early and cheaply; use the gate to decide the change is correct.
Opt-in configuration
The client reads the machine-local language_servers section ofrun-configuration.json (see
`manage-run-config` §
"Language-Servers Section"). The binding is machine-local because a language
server is locally-installed tooling that differs per machine. An unconfigured or
disabled language yields state: not_configured and the Read/Edit fallback —
so a project that never configures a server loses nothing. The configuration
surface is shared with the resolver-configuration work; this skill does not ship
a parallel store.
Scripts
Script: plan-marshall:lsp-client:lsp_client
| Verb | Purpose |
|---|---|
preflight |
Report not_configured | ready (configured + reachable) | unreachable + reason |
lookup |
Locate a symbol by coordinate (definition / references / document-symbol / workspace-symbol) |
edit |
Apply a verified symbol rename; footprint from the edit; worsened diagnostics fail and roll back |
diagnose |
Diagnostics for a file as a pre-build signal (supplements the gate) |
Canonical invocations
The canonical argparse surface for lsp_client.py. The plugin-doctor analyzer
(_analyze_manage_invocation.py) reads this section as source-of-truth for themanage-invocation-invalid and missing-canonical-block rules. Consuming docs
xref this section by name instead of restating the command inline. See
`pm-plugin-development:plugin-script-architecture` cross-skill-integration.md § "Script invocation in documentation".
preflight
python3 .plan/execute-script.py plan-marshall:lsp-client:lsp_client preflight \
[--language LANGUAGE] [--project-path PROJECT_PATH]lookup
python3 .plan/execute-script.py plan-marshall:lsp-client:lsp_client lookup \
--kind (definition | references | document-symbol | workspace-symbol) \
[--file FILE] [--line LINE] [--character CHARACTER] [--symbol SYMBOL] \
[--language LANGUAGE] [--project-path PROJECT_PATH]--file is required for definition / references / document-symbol;--symbol is required for workspace-symbol.
edit
python3 .plan/execute-script.py plan-marshall:lsp-client:lsp_client edit \
--file FILE --line LINE --character CHARACTER --new-name NEW_NAME \
[--language LANGUAGE] [--project-path PROJECT_PATH]diagnose
python3 .plan/execute-script.py plan-marshall:lsp-client:lsp_client diagnose \
--file FILE [--language LANGUAGE] [--project-path PROJECT_PATH]Related
manage-run-config— the machine-locallanguage_serversconfiguration section this client reads.execute-task— the phase-5 leaf consumer that reaches lookup/edit when a server is configured, and falls back toRead/Editwhen it is not.ref-toon-format— the TOON output format every verb returns.