From 0216e91684c27d1ccfdf1dd906ae410d9374e01a Mon Sep 17 00:00:00 2001 From: Arthur Date: Mon, 21 Sep 2026 13:32:55 +0200 Subject: [PATCH] docs: tell agents how to load and warm the language server Agents in this repo kept answering symbol questions with grep although a language server was wired up. In Claude Code the `LSP` tool is deferred, listed by name only, so it reads as unavailable. When it was used, the first find-references came from a cold server and returned only the definition. - AGENTS.md: send symbol questions to the language server when the harness has one, warm it with a throwaway query, and grep as well before a rename, since string references are not symbols. - CLAUDE.md: load `LSP` via ToolSearch, and the one-line warm-up query. - famstack-lsp plugin: a SessionStart hook that tells the agent both, or reports that `basedpyright-langserver` is missing. --- .claude/skills/famstack-lsp/README.md | 4 +++- .claude/skills/famstack-lsp/hooks/hooks.json | 14 ++++++++++++++ .claude/skills/famstack-lsp/hooks/session-start.sh | 14 ++++++++++++++ AGENTS.md | 2 ++ CLAUDE.md | 11 +++++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/famstack-lsp/hooks/hooks.json create mode 100755 .claude/skills/famstack-lsp/hooks/session-start.sh diff --git a/.claude/skills/famstack-lsp/README.md b/.claude/skills/famstack-lsp/README.md index 76768a6a..0eff95c8 100644 --- a/.claude/skills/famstack-lsp/README.md +++ b/.claude/skills/famstack-lsp/README.md @@ -2,7 +2,9 @@ A skills-directory plugin: Claude Code discovers it in place as `famstack-lsp@skills-dir` on the next session, with no marketplace and no install step. It only starts once you have accepted the workspace trust dialog for this repo. -It wires one thing: `basedpyright-langserver` over stdio for `.py` and `.pyi`. That gives an agent working in this repo live diagnostics after each edit, plus go-to-definition and find-references, instead of grepping for a symbol and hoping. +It wires `basedpyright-langserver` over stdio for `.py` and `.pyi`. That gives an agent working in this repo live diagnostics after each edit, plus go-to-definition and find-references, instead of grepping for a symbol and hoping. + +It also carries a SessionStart hook (`hooks/`). A hook cannot reach the server Claude Code spawned, so it does not warm the index itself: it tells the agent to load the deferred `LSP` tool and run one throwaway query, or reports that the binary is missing. Changes to the hook take effect after `/reload-plugins`. ## Requirement diff --git a/.claude/skills/famstack-lsp/hooks/hooks.json b/.claude/skills/famstack-lsp/hooks/hooks.json new file mode 100644 index 00000000..5465b0e3 --- /dev/null +++ b/.claude/skills/famstack-lsp/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh\"" + } + ] + } + ] + } +} diff --git a/.claude/skills/famstack-lsp/hooks/session-start.sh b/.claude/skills/famstack-lsp/hooks/session-start.sh new file mode 100755 index 00000000..6955d167 --- /dev/null +++ b/.claude/skills/famstack-lsp/hooks/session-start.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# SessionStart: stdout becomes context for the agent. +# +# A hook cannot reach the language server Claude Code spawned, so it cannot +# warm the index itself. It tells the agent to, because the LSP tool is +# deferred (listed by name only, easy to read as unavailable) and a cold +# server answers from a partial index instead of waiting. + +if ! command -v basedpyright-langserver >/dev/null 2>&1; then + echo "famstack-lsp: basedpyright-langserver is not on PATH, so there is no language server this session. Install it with 'uv tool install basedpyright' (or run tools/init-repo), then /reload-plugins. Until then, use 'uvx basedpyright ' for diagnostics and grep for navigation." + exit 0 +fi + +echo "famstack-lsp: a Python language server is available through the deferred LSP tool. Load it with ToolSearch 'select:LSP' before the first question about a symbol, then warm it with one throwaway workspaceSymbol query for 'HookResolver' on lib/stack/hooks.py and ignore the result: a cold server answers from a partial index. See CLAUDE.md." diff --git a/AGENTS.md b/AGENTS.md index 04803e72..4487d3dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,8 @@ If you might do both, load both. They are short on purpose. The type checker is worth one look before you start, whatever your harness: the tree carries a standing error count, so a bare run tells you nothing about your own change. The "Static checks" section of the engineer file has the two commands that separate the two, and works from any agent that can run a shell. +If your harness gives you a language server, questions about a symbol (where it is defined, who calls it, what type it is) go to it rather than to grep. Warm it with one throwaway query first: a cold server answers from a partial index instead of waiting, and a first find-references on `compose_up` returned only its definition where the warm answer was five. Before a rename, grep for the name as well. String references such as `monkeypatch.setattr("stack.docker.compose_up", ...)` are text, and the server does not count them. The engineer file covers reading its answers. + ## Approach (universal) Six principles. The first four are distilled from [Andrej Karpathy's observations on LLM coding pitfalls](https://github.com/multica-ai/andrej-karpathy-skills); the fifth is classic separation of concerns; the sixth is what we test and why. Apply to every change, every role. **Tradeoff:** these bias toward caution over speed. For trivial tasks (typos, obvious one-liners), use judgment. diff --git a/CLAUDE.md b/CLAUDE.md index f6145af0..fe0aadad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,14 @@ @AGENTS.md @docs/agent/ops.md @docs/agent/dev.md + +## Claude Code: the language server + +The `famstack-lsp` plugin (`.claude/skills/famstack-lsp/`) runs basedpyright for this repo. Two things about it are specific to this harness: + +- **The `LSP` tool is deferred.** It is listed by name only, not with the other tools, so it reads as unavailable. Load it with ToolSearch `select:LSP` before the first question about a symbol. +- **Warm it before trusting it.** Run one `workspaceSymbol` query for `HookResolver` on `lib/stack/hooks.py` and ignore the result. It returns a single line, and it makes the server index the workspace. + +The plugin's SessionStart hook says both at the start of every session, and says when `basedpyright-langserver` is missing instead. If neither message appears, the plugin is not loaded: run `claude plugin list` (see CONTRIBUTING.md § Language server). + +Use it for definitions, references, callers (`incomingCalls`) and types (`hover`). Use grep for text. How to read its answers is in the engineer file, § Static checks.