Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
{
"name": "dotnet-claude-kit",
"source": "./",
"description": "47 skills (incl. 16 slash-command workflows), 10 agents, 10 rules, 5 templates, 20-tool Roslyn MCP server for .NET 10 / C# 14",
"version": "0.11.0",
"description": "47 skills (incl. 16 slash-command workflows), 10 agents, 10 rules, 5 templates, 22-tool Roslyn MCP server for .NET 10 / C# 14",
"version": "0.12.0",
"license": "MIT",
"keywords": [
"dotnet",
Expand Down
4 changes: 2 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "dotnet-claude-kit",
"displayName": "dotnet Claude Kit",
"version": "0.11.0",
"description": "The definitive Claude Code companion for .NET developers. 47 skills (including 16 slash-command workflows), 10 agents, 10 rules, 5 templates, 20 MCP tools, and automation hooks for modern .NET 10 / C# 14.",
"version": "0.12.0",
"description": "The definitive Claude Code companion for .NET developers. 47 skills (including 16 slash-command workflows), 10 agents, 10 rules, 5 templates, 22 MCP tools, and automation hooks for modern .NET 10 / C# 14.",
"author": {
"name": "Mukesh Murugan",
"url": "https://codewithmukesh.com"
Expand Down
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.12.0] — 2026-08-07

Roslyn Navigator correctness release. Symbol resolution, response contracts, and workspace reload behaviour are corrected, and two tools are added: stack trace resolution and change impact analysis.

### Added
- **2 new MCP tools (20 → 22)** in CWM.RoslynNavigator 0.10.0:
- `resolve_stack_trace` — maps a .NET exception onto the solution. Undoes compiler rewrites for async state machines, lambdas, and local functions (the frames hardest to grep for), marks which frames are yours, and points at the topmost one in your own code
- `analyze_change_impact` — "what breaks if I change this?" in one call: references grouped by project and by file, implementations and overrides that must change alongside a signature, transitive callers, assembly-boundary exposure, and a risk rating with its rationale
- **Container distribution** for the MCP server via SDK container publishing (no Dockerfile), removing the host-SDK and `DOTNET_ROOT` setup that trips up macOS and Linux installs

### Changed
- **CWM.RoslynNavigator 0.9.0 → 0.10.0 — symbol resolution and response-contract correctness pass**:
- **Qualified symbol names now resolve.** Lookup accepted only a bare declared name, so `find_references("OrderService.CreateOrderAsync")` returned empty — indistinguishable from a symbol with no references
- **Structured error codes replace empty results.** Nine tools returned an empty list for an unresolvable symbol, and ambiguity was resolved by `return symbols[0]` — a bare name matching several types answered about the wrong one with full confidence. Now `SymbolNotFound`, `AmbiguousMatch` (with candidates), `WrongSymbolKind`, `FileNotFound`, `NoSource`
- **`Truncated` and `Limit` on every list response**, so truncation is stated rather than inferred from `Count` vs `TotalFound` — a comparison that is wrong when the result set is exactly the limit
- **`IsGenerated` on navigation results**, so an agent does not edit a `.g.cs` file the next build overwrites
- **`find_dead_code` grades against reflection.** A type resolved via `Type.GetType("...")` has zero references and was reported at high confidence; a name in any string literal now drops to `low`, and `AssemblyScanningDetected` flags solutions that register types by scanning
- **Reloads only on real build-file changes.** csproj comparison was timestamp-only, so a branch switch re-evaluated MSBuild for identical content. Build files now carry a content hash, and `Directory.Build.props` / `Directory.Packages.props` / `Directory.Build.targets` / `global.json` / `nuget.config` are tracked — previously invisible despite governing every project beneath them
- **Structural file scan moved to the background**, so no tool call pays for a recursive directory walk
- **Fixed: compilation cache survived a reload**, leaving orphaned `ProjectId` entries pinning whole Roslyn compilations in memory
- **`hooks/pre-commit-antipattern.sh` rewritten** ([#23](https://github.com/codewithmukesh/dotnet-claude-kit/issues/23)) — the naive whole-file grep is replaced by a comment- and string-aware scanner (`hooks/lib/antipattern-scan.awk`) that mirrors the Roslyn detectors' rule IDs (AP001–AP004), severities, and `SourceKind` exemptions:
- Only the lines a commit **adds** are checked — a legacy `DateTime.Now` no longer blocks unrelated edits to the same file
- Comments, string, verbatim, and raw-string literals are stripped, tracking state across line boundaries
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<p align="center">
<strong>Make Claude Code an expert .NET developer.</strong>
<br />
47 skills &bull; 10 specialist agents &bull; 16 slash commands &bull; 10 rules &bull; 5 project templates &bull; 20 MCP tools &bull; automation hooks
47 skills &bull; 10 specialist agents &bull; 16 slash commands &bull; 10 rules &bull; 5 project templates &bull; 22 MCP tools &bull; automation hooks
<br />
Built for .NET 10 / C# 14. Architecture-aware. Token-efficient.
</p>
Expand Down Expand Up @@ -65,7 +65,7 @@ An **action layer** on top of the knowledge layer — Claude doesn't just know t

| Capability | What It Does |
|-----------|-------------|
| **Surgical Code Analysis** | 20 Roslyn-powered MCP tools with guaranteed bounded responses. `get_symbol_source` reads ONE method body instead of the whole file. `get_file_outline` shows what's in a file before reading it. Every list-returning tool is capped with `TotalFound` — no tool can blow your context window. |
| **Surgical Code Analysis** | 22 Roslyn-powered MCP tools with guaranteed bounded responses. `get_symbol_source` reads ONE method body instead of the whole file. `get_file_outline` shows what's in a file before reading it. `resolve_stack_trace` turns an exception into file:line in your own code. `analyze_change_impact` answers "what breaks if I change this?" in one call. Every list-returning tool reports `Truncated` — no tool can blow your context window. |
| **Architecture Enforcement** | `/arch-check` verifies the code still matches its declared architecture (VSA, Clean, DDD, Modular Monolith): dependency direction, layer violations, module leaks, cycles — with file:line evidence and fixes. |
| **Dependency Health** | `/outdated` reports stale packages, CVEs, and commercial-license traps (MediatR 13+, MassTransit 9+, FluentAssertions 8+, AutoMapper 15+) before an innocent update-all changes your legal position. |
| **Security Mapping** | `get_endpoint_map` inventories every route with its auth posture (`authorized`/`anonymous`/`unmarked`) in one token-cheap call — `/security-scan` starts every auth audit there. |
Expand Down Expand Up @@ -151,7 +151,7 @@ Replace `[ProjectName]`, update tech stack, choose your architecture.

</details>

Start Claude Code — 47 skills, 10 agents, and 20 MCP tools activate automatically. Copy the 10 rules into your project's `.claude/rules/` to make them always-loaded.
Start Claude Code — 47 skills, 10 agents, and 22 MCP tools activate automatically. Copy the 10 rules into your project's `.claude/rules/` to make them always-loaded.

That's it. Claude now writes .NET code the way a senior .NET engineer would.

Expand Down Expand Up @@ -413,7 +413,7 @@ dotnet-claude-kit/
├── .claude/rules/ # 10 always-loaded rules
├── templates/ # 5 drop-in CLAUDE.md templates
├── knowledge/ # Living reference documents + ADRs
├── mcp/CWM.RoslynNavigator/ # Roslyn MCP server (20 tools)
├── mcp/CWM.RoslynNavigator/ # Roslyn MCP server (22 tools)
├── mcp-configs/ # MCP server config templates
├── hooks/ # Claude Code hooks + git hooks + utilities
├── docs/ # Shorthand + longform guides
Expand Down
160 changes: 149 additions & 11 deletions mcp/CWM.RoslynNavigator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,74 @@ CWM.RoslynNavigator is a Model Context Protocol (MCP) server that provides Claud
| `get_nuget_packages` | PackageReference inventory per project with versions (CPM-aware, no network calls) |
| `get_endpoint_map` | ASP.NET Core route inventory: Minimal APIs (MapGroup-composed) + controllers, with auth posture per endpoint |
| `get_di_registrations` | DI registration map with duplicate detection and captive-dependency (singleton→scoped) risk flags |
| `resolve_stack_trace` | Map an exception stack trace onto the solution — undoes async/lambda/local-function rewrites, marks which frames are yours |
| `analyze_change_impact` | Blast radius of changing one symbol: references by project and file, implementations and overrides that must move with it, transitive callers, risk rating |

### Symbol names

Every tool that takes a symbol accepts three forms, from loosest to tightest:

```
CreateOrderAsync # bare — may be ambiguous
OrderService.CreateOrderAsync # type-qualified
MyApp.Orders.OrderService.CreateOrderAsync # fully qualified
```

Parameter lists, generic arguments, and metadata arity are ignored, so
`IRepository<Order>.GetByIdAsync(Guid, CancellationToken)` resolves the same as
`IRepository.GetByIdAsync`. Qualifiers match on segment boundaries — `derService.Get`
will not match `OrderService.Get`.

When a bare name matches several distinct symbols, tools return an `AmbiguousMatch` error
listing the candidates rather than silently picking one. Overloads, partial declarations,
and per-target-framework duplicates count as one symbol, not an ambiguity.

### Errors

Failures return a structured response with a stable `Error` code, never an empty result
set — so "this symbol does not exist" is always distinguishable from "this symbol exists
and has no references".

| Code | Meaning |
|------|---------|
| `SymbolNotFound` | No symbol by that name. Check spelling, or search with `find_symbol`. |
| `AmbiguousMatch` | Several distinct symbols match. `Candidates` lists them; re-query fully qualified, or pass `file`/`line`. |
| `WrongSymbolKind` | Resolved, but to the wrong kind (e.g. a method where a type was required). |
| `FileNotFound` | The path is not part of any project in the solution. |
| `NoSource` | A metadata symbol, or one with no member declaration. |
| `InvalidArgument` | Malformed input — an empty stack trace, for instance. |

### Result caps

Every list-returning tool accepts a `maxResults` parameter and reports the uncapped match
count as `TotalFound` in its response (`get_dependency_graph` reports a `Truncated` flag
instead). Defaults: 50 for symbol/list tools (`find_symbol`, `find_references`,
Every list-returning tool accepts `maxResults` and reports four fields:

| Field | Meaning |
|-------|---------|
| `Count` | Entries in this response |
| `TotalFound` | Entries that matched in total |
| `Truncated` | Whether the cap dropped entries — **the field to branch on** |
| `Limit` | The cap that was applied; re-query above it to see the rest |

`Truncated` is stated rather than inferred from `Count` vs `TotalFound`, which gets the
boundary case wrong when the result set happens to be exactly the size of the limit.

Defaults: 50 for symbol/list tools (`find_symbol`, `find_references`,
`find_implementations`, `find_callers`, `find_overrides`, `find_dead_code`,
`get_public_api`, `get_type_hierarchy`, `get_test_coverage_map`), 100 for
`detect_antipatterns`, `get_diagnostics`, `get_endpoint_map`, `get_di_registrations`,
`get_nuget_packages`, and `get_dependency_graph` nodes, 200 for `get_file_outline`
members. `get_symbol_source` caps by characters instead (`maxChars`, default 8000, with a
`Truncated` flag). When `TotalFound` exceeds the returned `Count`, re-query with a higher
`maxResults`. `get_diagnostics` orders errors first and always includes per-severity
totals, so a capped response never hides the important picture.
`get_public_api`, `get_type_hierarchy`, `get_test_coverage_map`,
`resolve_stack_trace`, `analyze_change_impact`), 100 for `detect_antipatterns`,
`get_diagnostics`, `get_endpoint_map`, `get_di_registrations`, `get_nuget_packages`, and
`get_dependency_graph` nodes, 200 for `get_file_outline` members. `get_symbol_source`
caps by characters instead (`maxChars`, default 8000, with a `Truncated` flag).
`get_diagnostics` orders errors first and always includes per-severity totals, so a
capped response never hides the important picture.

### Generated code

`find_symbol`, `find_references`, `find_callers`, `find_implementations`, and
`find_overrides` mark each result with `IsGenerated`, so an agent does not follow a hit
into a `.g.cs` file that the next build overwrites. Detection uses file conventions
(`.g.cs`, `.Designer.cs`, `obj/`), an `<auto-generated>` header, and Roslyn's
`IsImplicitlyDeclared` for compiler-synthesized members.

## Signal Quality

Expand Down Expand Up @@ -191,6 +245,43 @@ Then add to your project's `.mcp.json`:
dotnet run --project mcp/CWM.RoslynNavigator/src/CWM.RoslynNavigator.csproj -- --solution /path/to/your/Solution.sln
```

### As a Container

Use this when you do not want a .NET 10 SDK on the host — it sidesteps the
`DOTNET_ROOT` setup that a global-tool install needs on macOS and Linux (see
[Troubleshooting](#no-net-sdks-were-found-on-startup-macoslinux)).

The image is produced by the .NET SDK's container publishing, so there is no
Dockerfile to maintain:

```bash
cd mcp/CWM.RoslynNavigator
dotnet publish src/CWM.RoslynNavigator.csproj -c Release --os linux --arch x64 /t:PublishContainer
```

That builds `cwm-roslyn-navigator:latest` locally. Register it with Claude Code by
bind-mounting the solution at the image's working directory, `/workspace`:

```bash
claude mcp add --scope user cwm-roslyn-navigator -- \
docker run -i --rm -v "$PWD:/workspace" cwm-roslyn-navigator:latest --solution /workspace
```

`-i` is required: the server speaks MCP over stdio, so the container must keep
stdin open. The mount is read-only as far as this server is concerned — every tool
is read-only — but leave it writable if the same checkout is being built on the host.

**Base image**: `mcr.microsoft.com/dotnet/sdk:10.0`, not a runtime image. `MSBuildLocator`
needs a real SDK installation to open a solution; on `aspnet` or `runtime` the server
starts and then fails every call with "No .NET SDKs were found".

To publish somewhere other than the local daemon, set the registry on the command line:

```bash
dotnet publish src/CWM.RoslynNavigator.csproj -c Release --os linux --arch x64 \
/t:PublishContainer -p:ContainerRegistry=ghcr.io -p:ContainerRepository=you/cwm-roslyn-navigator
```

## Solution Discovery

The server resolves the solution file in this order:
Expand All @@ -214,7 +305,7 @@ WorkspaceManager.cs → MSBuildWorkspace lifecycle, file watching, compilati
WorkspaceInitializer.cs → BackgroundService triggers workspace load on startup
SolutionDiscovery.cs → Auto-detect .sln/.slnx from args or working directory
SymbolResolver.cs → Cross-project symbol resolution with disambiguation
Tools/ → MCP tool implementations (20 read-only tools)
Tools/ → MCP tool implementations (22 read-only tools)
Responses/ → Token-optimized JSON response DTOs
```

Expand Down Expand Up @@ -268,6 +359,53 @@ dotnet run --project mcp/CWM.RoslynNavigator/src/CWM.RoslynNavigator.csproj -- -

## Changelog

### 0.10.0

Correctness pass on symbol resolution and response contracts.

- **Qualified symbol names** — resolution accepted only a bare declared name, so
`find_references("OrderService.CreateOrderAsync")` returned an empty result,
indistinguishable from a symbol with no references. Type- and namespace-qualified
names now resolve, with parameter lists, generic arguments, and metadata arity
ignored, and suffix matching anchored on segment boundaries. See [Symbol names](#symbol-names).
- **Structured errors** — nine tools returned an empty list when a symbol could not be
resolved, and `ResolveSymbolAsync` ended in `return symbols[0]`, so a bare name matching
several types silently answered about the wrong one with full confidence. Tools now
return `SymbolNotFound` / `AmbiguousMatch` (with candidates) / `WrongSymbolKind` /
`FileNotFound` / `NoSource`. See [Errors](#errors).
- **`Truncated` and `Limit`** on every list response, so callers stop inferring
truncation from `Count` vs `TotalFound` — a comparison that gets the exactly-at-limit
case wrong. See [Result caps](#result-caps).
- **`IsGenerated`** on navigation results, so an agent does not follow a hit into a
`.g.cs` file the next build overwrites. See [Generated code](#generated-code).
- **`find_dead_code` grades against reflection** — a type resolved via
`Type.GetType("...")` has zero references and was reported at high confidence.
A name appearing in any string literal now drops to `low` with the reason attached,
and `AssemblyScanningDetected` reports whether the solution registers types by scanning
at all.
- **New: `resolve_stack_trace`** — maps an exception onto the solution, undoing compiler
rewrites for async state machines, lambdas, and local functions, and pointing at the
topmost frame in your own code.
- **New: `analyze_change_impact`** — blast radius for one symbol in a single call:
references by project and file, implementations and overrides that must change with a
signature, transitive callers, assembly-boundary exposure, and a rated risk with its
rationale.
- **Reload only on real build-file changes** — csproj comparison was timestamp-only, so a
branch switch or formatter rewrite triggered a full MSBuild re-evaluation for identical
content. Build files now carry a content hash, with the timestamp kept as the pre-filter.
`Directory.Build.props`, `Directory.Packages.props`, `Directory.Build.targets`,
`global.json`, and `nuget.config` are tracked too — previously invisible despite
governing every project beneath them.
- **Structural scan moved off the request path** — the recursive walk for newly added
source files ran inline, so one arbitrary tool call per cooldown paid for a full
directory enumeration. It now runs in the background with the same staleness bound.
- **Container distribution** — `dotnet publish /t:PublishContainer` produces an image on
the SDK base (required by MSBuildLocator), removing the host-SDK and `DOTNET_ROOT`
setup. See [As a Container](#as-a-container).
- **Fixed: compilation cache survived a reload** — a reload mints fresh `ProjectId`s, and
two of four `LoadSolutionAsync` call sites did not clear the cache first, leaving
orphaned entries pinning whole Roslyn compilations in memory.

### 0.9.0

Analysis accuracy pass. On a 34-project, 106K-line codebase, `detect_antipatterns`
Expand Down
Loading