Repository navigation
0.1.9
What changed
Node identity used to be the position in an array. One added declaration
renumbered everything after it, so any edit meant rebuilding the whole
solution and changes had to compare edges by display name — which cannot
tell Handle(CreateOrder) from Handle(CancelOrder).
Identity is now the compiler's symbol key, persisted with the graph. Most
of this release follows from that.
Indexing only what moved
csmesh index rebinds the files that changed and leaves the rest alone.
Symbols keep their ids, so every edge reaching them from an untouched file
survives. Queries accept --heal to catch the index up before answering
instead of marking rows [STALE].
It declines and runs a full pass whenever an edit touches something bound
across files — an interface declaration, a handler, a container
registration. A slower correct answer beats a fast one that drops an edge.
The index was competing with itself
Every bin/ under the repository was scanned for references, and every
project also builds into one. The compilation held each of your types
twice: once from source, once from the assembly built from it.
Ordinary calls survived because the source declaration usually wins.
Extension methods did not — their lookup gathers candidates from every
assembly in scope, so the compiler found the method in both places and
returned candidates with no symbol. What that produced was a pile of
ambiguous overloads in whichever file calls the most extension methods,
which is always the composition root.
On a 29-project solution: 83 unbound call sites to 0, and 44 call edges
that existed all along.
Extension methods were two nodes
A declaration gives the original symbol; a call site gives the reduced
form, with the receiver out of the parameter list. Keyed apart, one
method became two nodes and the call edge landed on the copy no query
resolves to — so blast-radius on an extension method reported no
callers at all.
Dependency injection
- Alias registrations are read:
sp => sp.GetRequiredService<Concrete>()
andActivatorUtilities.CreateInstance<T>(sp). Registering a concrete
type once and exposing it through several interfaces is how a lot of
modular codebases wire themselves, and none of it was visible. - Ranking keys on the DiBinding edge. It looked for a marker only the
Interface edge carries, while deduplication kept the other one — so a
registered class led the list when scores tied and insertion order
favoured it. Anything registered at less than full confidence sorted
below classes nobody had registered. - A registration that cannot be resolved now says so.
unresolved --kind di
answered "none" when the truth was "never attempted".
csmesh where
csmesh where discount
csmesh find "POST /orders"
Every other command takes a symbol; this is the one that finds it. Names,
namespaces, file paths and route templates are searched, and results are
ranked by how many entrypoints reach each hit — so the handler outranks
the request DTO sharing its name. The last line is the command to run next.
Also
changescompares by symbol key and registration note. Overloads no
longer collapse into one entry, and a scoped-to-singleton move is
reported.- Shared frameworks installed beside the running runtime are loaded.
- Native DLLs are detected by PE header rather than by file name.
- README documented 8 of 16 commands. All 16 are covered now.
Upgrading
Format version 12. Existing indexes are rebuilt on first use — no action
needed.
Known issue
The AOT binaries load no runtime references, because
RuntimeEnvironment.GetRuntimeDirectory() has no meaning under NativeAOT.
On a repository with a well-populated bin/ this is invisible; on a fresh
one it is not. Install as a .NET global tool until this is fixed.