| id | 133 |
|---|---|
| title | LSP hover for rule and directive docs |
| status | ✅ |
| model | sonnet |
| summary | Add `textDocument/hover` to `mdsmith lsp` so editors and Claude Code see rule help on hover over a diagnostic squiggle and directive docs on hover inside a `<?…?>` block. Rule docs reuse the body `mdsmith help rule <id>` prints; directive docs come from the existing files under `docs/guides/directives/`. |
Surface rule and directive documentation inline in
the editor without leaving the buffer. Hovering
over an MDS001 squiggle should show the
line-length help body; hovering inside a
<?catalog?> block should show the catalog
directive docs. Same content the CLI prints, no
duplication.
Plan 121 shipped diagnostics and code actions.
Plan 131 shipped navigation. Hover is the only
surface in Claude Code's code intelligence
list still missing from mdsmith lsp ("get type
info on hover").
Plan 122 originally bundled hover with a set of VS Code palette commands. The two ship independently: hover is server-side and benefits every LSP client (Claude Code, Neovim, Helix, JetBrains); the palette is VS Code-only chrome. This plan extracts the hover work so it can land without waiting on the palette decisions.
- Hover content for non-mdsmith concepts (front- matter schemas, Markdown spec quirks). Scope is rule docs and directive docs only.
- A new
mdsmith help directivesCLI topic. The directive content is loaded directly by the LSP server from the existing files under docs/guides/directives/. - Hover on link targets, headings, or kind values. Definition / implementation already cover those with a click-through; hover here is reserved for documentation, not navigation preview.
Advertise hoverProvider = true in the server's
initialize response.
A textDocument/hover request arrives with a
position. The server resolves in two passes:
Diagnostic-first. If the position falls inside
any active diagnostic range for the document, the
server returns a MarkupContent with kind
markdown. The body holds the diagnostic message
on one line, a blank line, then the rule's docs.
Rule docs load via
internal/rules.LookupRule
— the same lookup mdsmith help rule <id|name>
uses.
Directive fallback. If no diagnostic covers the
cursor, the server checks for a <?directive ...?>
block at that position (via
piparser/pi_parser.go).
If one matches, the server returns the directive
docs from docs/guides/directives/.
If neither matches, the server returns null (no
hover).
Each hover response sets range to the matched
span — the diagnostic range, or the directive
block range. Clients render the hover anchored to
that span.
VS Code, Claude Code, and most LSP clients render
hover content as Markdown and strip inline HTML.
Rule and directive docs in this repo today are
clean of HTML outside code blocks, but a future
contributor could add a <span> or <details>
that would silently disappear from the hover.
This plan adds a preventive guard: enable
no-inline-html (MDS041) on the existing
rule-readme kind in .mdsmith.yml.
The rule is opt-in by default. The same kind
already targets internal/rules/MDS*/README.md
and pins required-structure.schema. Adding the
opt-in setting:
kinds:
rule-readme:
rules:
required-structure:
schema: internal/rules/proto.md
no-inline-html:
allow: [kbd]A spot audit of the existing rule README files found no raw HTML outside code blocks; this is purely preventive.
Editing .mdsmith.yml requires explicit user
consent per CLAUDE.md. The task
that lands the kind change surfaces the diff
first.
hoverProvider is additive. Clients that ignore
the capability see the same server. The
no-inline-html opt-in only fires on the
rule-readme kind; no other Markdown is affected.
- ✅ Wire hover's rule lookup through
internal/rulesAPIs (LookupRule(query) (string, error)andListRules()). Cover known rules, unknown rules, and rules whose docs have no rule-specific body (fall back to a generic "seemdsmith help rule <id>" line). - ✅ Add
textDocument/hoverto the LSP server ininternal/lsp/server.go. Implement the diagnostic-first then directive- fallback resolution. Returnnullwhen neither matches. - ✅ Add a directive-doc loader that reads from docs/guides/directives/ keyed by directive name. Cache the parsed contents on first read.
- ✅ Add an integration test in
cmd/mdsmiththat drives aninitialize→didOpen→hoverround trip for both the diagnostic and directive cases, and the no-match case. - ✅ Add
hoverProviderto the capability table indocs/reference/cli/lsp.mdand document the resolution order. - ✅ Propose enabling
no-inline-htmlon therule-readmekind in .mdsmith.yml. Surface the diff to the user before applying. Add a regression test fixture: a rule README with a raw<span>outside a code block failsmdsmith checkwithMDS041.
-
hoverProviderappears in theinitializecapabilities response. - Hovering over an
MDS006diagnostic returns aMarkupContentwhose body contains the rule help text. - Hovering inside a
<?catalog?>directive returns the catalog directive docs even when no diagnostic is present at the cursor. - Hovering on plain prose (no diagnostic, no
directive) returns
null. - After the
rule-readmekind change lands, a fixture rule README containing a raw<span>outside a code block failsmdsmith checkwithMDS041. -
docs/reference/cli/lsp.mdlistshoverProviderin the capability table. - All tests pass:
go test ./.... -
go tool golangci-lint runreports no issues. -
mdsmith check .passes.