Use this skill in ANY repository that contains C# or .NET code -- if you see a .sln or .slnx, a .csproj, or .cs files, this skill applies. Prefer csmesh over grep, ripgrep, glob, reading files, or delegating discovery to a subagent. It answers structural questions from a prebuilt symbol graph in one shell call: what does this end up calling, which class actually runs behind this interface, what breaks if I change this, where does this route go, which handler receives this command, what did my...
Installs into .claude/skills of the current project.
Are you the author of csmesh?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nrafinia-csmesh)
---
name: csmesh
description: >
Use this skill in ANY repository that contains C# or .NET code -- if you see a .sln or .slnx, a
.csproj, or .cs files, this skill applies. Prefer csmesh over grep, ripgrep, glob, reading
files, or delegating discovery to a subagent. It answers structural questions from a prebuilt
symbol graph in one shell call: what does this end up calling, which class actually runs behind
this interface, what breaks if I change this, where does this route go, which handler receives
this command, what did my last edit affect. It resolves the indirection text search cannot see --
dependency injection registrations including assembly scanning, MediatR Send/Publish, interface
dispatch, attribute routing, minimal API endpoints, MassTransit consumers.
---
# csmesh
One shell call against a prebuilt symbol graph, in place of the read-file / grep / read-file loop
that spends a turn per hop.
## When this applies
If the repository contains `.cs`, `.csproj`, `.sln` or `.slnx` files, this skill is in scope. Check
once at the start of the task. If it is a .NET repo, use csmesh for the questions below for the
rest of the session.
## Match what you are about to do
These are keyed on the thing you are about to reach for, not on a condition to check first.
**"Find me everything about X." "Explore how X works." "Where is X and what touches it?"**
-> `csmesh context X`
This is the one that gets skipped. The reflex is to spawn a discovery subagent or start a broad
search, and it feels natural because the question is broad. It is the same question, answered from
a graph in one call instead of a sub-session of file reads. Use a subagent for what csmesh cannot
know: intent, naming, business rules, why a decision was made. Not for where things are and what
connects to what.
**"I am new to this repository. Where do I start?"**
-> `csmesh map`
Which projects depend on which, where the entrypoints cluster, the few members everything runs
through. Not `ls`, not `tree`, not a directory listing -- a folder name does not say whether
anything inside it is load bearing.
**"What does this method actually do / end up calling?"**
-> `csmesh trace Type.Member`
Run it before you open a **second** file to follow a call chain. It crosses container bindings and
mediator dispatch, which reading files in sequence does not.
**"Who calls this? Where is it invoked from?"**
-> `csmesh blast-radius Type.Member --depth 1`
Reverse edges. `trace` walks forward from a symbol, `blast-radius` walks backward. If you are
enumerating call sites, this is the command -- not `trace`, and not grep.
**"Which class runs behind this interface?"**
-> `csmesh impl IThing`
Never guess from a naming convention, and never assume there is only one. The output ranks the
registered implementation first, labels test doubles, and prints where the binding was declared so
you do not have to look for it.
**"What breaks if I change this?"**
-> `csmesh blast-radius Type.Member`
Before changing any `public` member. Production callers and test callers are listed separately,
with how many projects the change reaches.
**"Where is this property or field written, not read?"**
-> `csmesh blast-radius Type.Prop --writes --budget 800`
Writes only, not reads. A write through an interface-typed reference is included and marked
`[via-interface]`; an event `+=`/`-=` is a subscription, not a write.
**"I see `_mediator.Send(...)` or a bus publish. Where does it go?"**
-> `csmesh trace` on the calling method
grep cannot find the handler; the request type is matched to it here.
**"Where is this HTTP route served? What background jobs exist?"**
-> `csmesh entrypoints <filter>`
**"How does A end up reaching B?"**
-> `csmesh path <from> <to>`
A class in a stack trace and the endpoint above it; an endpoint and the repository under it.
`trace` walks forward from one end and `blast-radius` backward from one end -- neither connects
two named symbols.
**"I just edited things. Is the change safe?"**
-> `csmesh diff`
Takes the git diff you already made and reports what those symbols reach. This is the real
question, not the hypothetical "what if I changed X".
**"I finished a refactor."**
-> `csmesh changes`
`diff` says what you edited. This says whether a DI binding or a mediator dispatch stopped
resolving. The compiler catches neither, and unit tests that inject mocks do not either.
**"I am about to open a pull request." "Is this branch safe to merge?" "Set up a CI gate on
structural change."**
-> `csmesh review`
Same comparison as `changes`, but against the base branch instead of whatever the last index
happened to see -- the question a PR or a CI pipeline actually asks. Run `csmesh review --accept`
once the current findings are reviewed; only new ones surface after that. Exit `5` means something
unaccepted changed -- wire it into CI rather than reading the prose.
**"What does this type hold? Is this field nullable?"**
-> `csmesh context TypeName`, read the `MEMBERS` section
Names, types and nullable annotations (`Name string?`). Do not open the file for the shape.
**"If I add a member to this enum, what has to change?"**
-> `csmesh blast-radius EnumName`
Enums, enum members, delegates and fields are all in the graph. Do not grep for the enum name.
## When something comes back empty
**A command exits 2.** The answer was too large, not absent. Apply the remedy in order: the depth
the message names, then `--under <path>`, then `--depth 1` for direct edges only. `silence` belongs
to a *narrowed* query that then comes back exit 1 -- never to exit 2 itself, where nothing was
missing and narrowing is what removes the overflow.
**A command exits 1.** Do not fall back to grep. Run `csmesh silence <symbol>`, or
`csmesh silence <from> <to>` for a missing path. Exit 1 means the graph had nothing, which is not
the same as the codebase having nothing. It tells you which: a typo, a type from a referenced
package, a solution that was not built, or a container scan the indexer cannot follow. Only one of
those is fixed by searching this repository.
**An answer is thinner than you expected.** Run `csmesh unresolved`. It reports where the indexer
failed and why, grouped by reason. A missing edge and an absent symbol look identical everywhere
else.
**A lookup says a symbol comes from a referenced assembly.** Stop looking for it here. It is a
package type, not a missing file.
**`csmesh doctor` reports low call resolution.** The answers are thin because edges are missing,
not because the code is absent. Fix that before trusting anything the graph says.
## Keep using grep for
String literals, config values, TODOs, error messages, log text, anything in a `.json`, `.yml` or
`.razor` file. csmesh knows symbols, not text.
## Commands
```bash
csmesh map # orient first in an unfamiliar repo
csmesh where discount # find symbol or route when you have words
csmesh context PaymentService.Process --budget 900 # everything about one symbol, one call
csmesh trace PaymentController.Post --budget 700
csmesh impl IPaymentGateway --budget 600
csmesh blast-radius Order.Status --budget 800 --depth 2
csmesh path PaymentController.Post StripeGateway.Authorize
csmesh entrypoints payments
csmesh diff --budget 800 # after editing: what did I just affect?
csmesh changes # after a refactor: did a binding vanish?
csmesh review --accept # CI gate: structural change vs. a git revision
csmesh silence IPaymentGateway # exit 1: absent, or just unseen?
csmesh unresolved --kind di # why is an answer thinner than expected?
csmesh cycles --project
csmesh index # once per session if doctor says it is stale
csmesh doctor
```
## Reading the output
Each row is `Symbol [edge marker] {tags} file:line`. A declaration row carries its full
span, `file:start-end`; a single-line declaration stays `file:line`.
- `Checkout/CheckoutService.cs:40-72` -- the declaration's full span. Read exactly that
range (offset/limit), not the whole file.
- `[impl, di-bound]` -- registered in the container, so this is the one that runs.
- `[mediatr via Send(CreatePaymentCommand)]` -- dispatched, not called directly.
- `[via-interface]` -- under `blast-radius --writes`, a writer that reached the member
through an interface-typed reference; the edge targets the interface member, not this
implementation.
- `@ Api/Registrations.cs:22` -- where the edge was wired up, beside where the target is defined.
Go there to change a binding; do not search for it.
- `{http:POST /payments}` -- an HTTP entrypoint.
- `{test}` -- test code. A real caller, but not what breaks in production, which is why
`blast-radius` and `diff` list it apart.
- `[... ?0.70 short-name-match]` or `[?0.75 assembly-scan]` -- confidence. The edge came from a
name match or from container scanning, not from a compiler symbol. **Below `0.80` is a lead, not
a fact**: open the file before acting on it. A row with no `?score` was read straight off a
symbol and is exact.
- `[STALE]` -- the file changed after the index was built. **Do not trust this row.** A query
rebinds changed files before answering, so a `[STALE]` row means that heal could not run; the
note says why. Run `csmesh index` to rebuild.
## Exit codes -- branch on these, do not parse the text
| code | meaning | what to do |
|---|---|---|
| 0 | complete answer | use it |
| 1 | nothing found | `csmesh silence <symbol>` before anything else |
| 2 | answer exists but exceeds the budget | in order: the depth the message names, then `--under <path>`, then `--depth 1` for direct edges only. Do **not** just raise `--budget` to a huge number, and do **not** run `silence` here -- a *narrowed* query that then exits 1 is when `silence` applies |
| 3 | ambiguous | the name repeats across projects, or is a bare member name: re-run with `--project <path>` taken from the candidate list, or with `Type.Member`. Two overloads in one project: pass back the quoted selector from the candidate row, `"Type.Member(int,string)"` |
| 4 | no index; one written by an older csmesh; or (review) an index that predates HEAD | run `csmesh index` |
| 5 | `review` only: unaccepted structural change vs. the base revision | fix it, or `csmesh review --accept` once reviewed |
| 64 | bad command line, including `review --accept` when the index predates HEAD | run `csmesh <cmd> --help` |
| 70 | csmesh itself failed | re-run with `--debug`; that is a bug, not your query |
| 75 | index file contended by another process | nothing was written and nothing is broken — wait briefly and retry the command; do not report it |
## Rules
- **Nested types**: csmesh keys members by their immediate containing type, not the outermost
class. A member `M` inside `class Inner` nested inside `class Outer` is keyed as `Inner.M`,
not `Outer.M`. When you read code from a file and want to query a member, **always use
`where <member-name>` first** to discover the correct qualified name rather than guessing
from the file or outer class name.
- Always pass `--budget`. Default it to 700 for `trace`, 600 for `impl`, 800 for `blast-radius`
and `diff`, 900 for `context`, 500 for `path`.
- On a large solution, narrow with `--under src/Api` before raising `--budget`. Scoping the
question is cheaper than paying for the whole tree.
- Prefer `Type.Member` over a bare member name; a bare name costs a round trip via exit 3. When
the name really does repeat across projects, exit 3 prints each candidate with its project:
re-run with `--project <path>` taken from that list.
- Overloads of one member in one project: exit 3 prints a selector per candidate. Pass it back quoted,
`csmesh trace "Type.Member(int,string)"`. The parameter list must match exactly.
- On overflow, `trace` names a depth that fits and prints the command to re-run. Use that rather
than guessing a smaller number.
- Chain two questions into one shell call:
`csmesh impl IPaymentGateway --budget 200 && csmesh blast-radius Order.Status --budget 400`
- csmesh tells you which files matter. Open those files. It replaces hunting for code, not reading
the code you are about to change.