Find code without grepping
SkillAI & modelsFind where a C# symbol is defined and what calls it in AlRunner/, using the language server instead of grep. Use BEFORE grepping for a type, method, or field name, "who calls X", "where is X defined", "what would break if I change X", "find every path that reaches X". Works inside subagents, where the built-in LSP tool does not.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Find code without grepping skill
What this skill tells your AI
The instructions your AI receives, as published by stefanmaron/businesscentral.al.runner in .claude/skills/find-code/SKILL.md and read by ahel’s review.
AlRunner/ is ~139,000 lines across 341 tracked .cs files, the largest Program.cs
at 6,952 (git ls-files | xargs wc -l, 2026-09-10). #3676 split the two files that used
to dominate this — RecordPatches.cs and MockTestPage.cs — into partials by surface. Grep gives you line numbers you then have to read windows around; measured on
one implementation agent, that loop was 63% of all its tool calls. The language
server answers the same questions exactly, in one call.
The built-in LSP tool does not work in subagents on this Claude Code build —
it returns No such tool available: LSP. This script gives you the same answers
through Bash, which you do have.
Commands
tools/lsp-query.py callers <SymbolName> # what calls it, and where it is defined
tools/lsp-query.py symbol <SymbolName> # where it is defined
tools/lsp-query.py refs <file> <line> <col> # references at a position (1-based)
tools/lsp-query.py def <file> <line> <col> # definition of the symbol here
callers is the one you usually want, and it needs no line or column:
$ tools/lsp-query.py callers GetDataAccessForTableCore
object RecordPatches.GetDataAccessForTableCore(object self, NCLMetaTable table, bool isTemporary) [defined AlRunner/Patches/RecordPatches.DataAccessDispatch.cs:87]
AlRunner/Patches/RecordPatches.InstallBaselineDisk.cs:68:20
AlRunner/Patches/RecordPatches.DataAccessDispatch.cs:71:26
That is complete — including the call site in a different partial-class file, which a grep for the name in one file would have missed.
Read the exit code. The three outcomes are NOT the same
| exit | meaning | what to do |
|---|---|---|
| 0 | answered, results printed | use them |
| 1 | answered, genuinely nothing found | a real negative — you may rely on it |
| 2 | the server failed, timed out, or is not installed | NOT a negative. Say so and fall back to grep |
Never treat exit 2 as "nothing calls this". That mistake reverses the meaning of
your result and any conclusion built on it. If it says csharp-ls is not installed,
tell the user to install it (README, tooling section) rather than silently grepping
for the rest of the session.
Cost
Measured on this repo, one process per query, cold: ~8.5s for a hit, ~10s for a genuine miss. There is no daemon and none is needed. That is far cheaper than the grep-then-read-several-windows loop it replaces.
What it cannot tell you
It is static analysis. It cannot tell you whether a Hook(...) registration or a
Cecil rewrite actually fires at runtime — an orphaned hook and a live one look
identical to it. Use AL_RUNNER_HOOK_AUDIT=1 for that question. For orientation in
an unfamiliar area ("what is near this"), the knowledge graph is better; see
CLAUDE.md.
If you are the main session briefing a subagent
Resolve the symbols first and paste the answers into the brief, so the subagent does not have to go looking:
# LSP CONTEXT (pre-resolved)
GetDataAccessForTableCore — AlRunner/Patches/RecordPatches.DataAccessDispatch.cs:87
callers: RecordPatches.DataAccessDispatch.cs:71, RecordPatches.InstallBaselineDisk.cs:68
Signals
- GitHub stars
- 36
- Forks
- 13
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
find-code- Source
- github.com/stefanmaron/businesscentral.al.runner