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": "45 skills (incl. 14 slash-command workflows), 10 agents, 10 rules, 5 templates, Roslyn MCP server for .NET 10 / C# 14",
"version": "0.10.0",
"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",
"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.10.0",
"description": "The definitive Claude Code companion for .NET developers. 45 skills (including 14 slash-command workflows), 10 agents, 10 rules, 5 templates, 15 MCP tools, and automation hooks for modern .NET 10 / C# 14.",
"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.",
"author": {
"name": "Mukesh Murugan",
"url": "https://codewithmukesh.com"
Expand Down
12 changes: 0 additions & 12 deletions .claude/rules/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,3 @@ description: >

- **DON'T** start implementation without checking if a relevant skill exists.
Rationale: Re-discovering best practices wastes time when they are already codified.

## Quick Reference

| Need | Tool / Approach |
|---|---|
| Find where a type is defined | `find_symbol` |
| Understand who calls a method | `find_callers` |
| Check public API surface | `get_public_api` |
| Verify no regressions | `get_diagnostics` |
| Parallel research | Subagent |
| Architecture decision | Opus + specialist agent |
| Routine refactor | Sonnet |
9 changes: 0 additions & 9 deletions .claude/rules/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ public sealed record Money(decimal Amount, string Currency);
// DO
List<int> ids = [1, 2, 3];
int[] arr = [4, 5, 6];

// DON'T
var ids = new List<int> { 1, 2, 3 };
```

- **Pattern matching over if-else chains.** Switch expressions and `is` patterns are more readable and exhaustiveness-checked.
Expand All @@ -62,12 +59,6 @@ var label = status switch
OrderStatus.Shipped => "On the way",
_ => "Unknown"
};

// DON'T
string label;
if (status == OrderStatus.Pending) label = "Awaiting payment";
else if (status == OrderStatus.Shipped) label = "On the way";
else label = "Unknown";
```

## Naming and Modifiers
Expand Down
10 changes: 0 additions & 10 deletions .claude/rules/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,3 @@ description: >

- **DON'T** defensively validate inside internal/private methods.
Rationale: Internal code should trust validated data. Double-validation adds noise without safety.

## Quick Reference

| Scenario | Approach |
|---|---|
| User input invalid | Result with Validation error |
| Entity not found | Result with NotFound error |
| Unhandled crash | IExceptionHandler middleware |
| External API failure | Catch specific exception, return Result |
| Concurrent update | Result with Conflict error |
16 changes: 1 addition & 15 deletions .claude/rules/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ description: >
## Branch Safety

- **DON'T** force-push to main or master. Ever.
Rationale: Force-push rewrites shared history and can destroy other contributors' work.

- **DON'T** skip pre-commit hooks with `--no-verify`.
Rationale: Hooks catch real issues. Bypassing them pushes broken code upstream.
Rationale: Force-push rewrites shared history and can destroy other contributors' work. (Hook bypass rules live in `hooks.md`.)

## PR Process

Expand All @@ -52,14 +49,3 @@ description: >

- **DO** keep PRs focused on a single concern. Split large changes into stacked PRs.
Rationale: Smaller PRs get faster, higher-quality reviews.

## Quick Reference

| Action | Convention |
|---|---|
| New feature | `feat: add order export endpoint` |
| Bug fix | `fix: prevent duplicate payments on retry` |
| Refactor | `refactor: extract pricing calculator from OrderService` |
| Tests only | `test: add edge cases for discount calculation` |
| Branch for feature | `feature/order-export` |
| Branch for fix | `fix/duplicate-payment` |
9 changes: 0 additions & 9 deletions .claude/rules/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,3 @@ description: >

- **DO** wait for post-scaffold-restore to complete after `.csproj` changes before building.
Rationale: NuGet restore must finish before the build can resolve dependencies. Building too early produces false errors.

## Quick Reference

| Hook | Correct Response |
|---|---|
| Post-edit format | Accept the changes |
| Pre-commit failure | Fix the issue, commit again |
| Post-test-analyze (manual pipe) | Read and act on insights |
| Post-scaffold-restore | Wait for completion before building |
10 changes: 0 additions & 10 deletions .claude/rules/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ public sealed class AuditService(TimeProvider clock)
{
public DateTimeOffset Now => clock.GetUtcNow();
}

// DON'T
var now = DateTime.UtcNow;
```

## Resource Management
Expand All @@ -53,13 +50,6 @@ var order = await cache.GetOrCreateAsync(
$"order:{id}",
async ct => await db.Orders.FindAsync([id], ct),
cancellationToken: ct);

// DON'T — manual cache-aside with no stampede protection
if (!memoryCache.TryGetValue(key, out var order))
{
order = await db.Orders.FindAsync(id);
memoryCache.Set(key, order, TimeSpan.FromMinutes(5));
}
```

## EF Core and Hot Paths
Expand Down
7 changes: 1 addition & 6 deletions .claude/rules/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,7 @@ app.MapGet("/orders", GetOrders);

## Transport and Data Protection

- **Use HTTPS everywhere.** Enforce via HSTS in production. Redirect HTTP to HTTPS. No exceptions.

```csharp
app.UseHsts();
app.UseHttpsRedirection();
```
- **Use HTTPS everywhere.** Enforce via HSTS in production (`app.UseHsts()` + `app.UseHttpsRedirection()`). Redirect HTTP to HTTPS. No exceptions.

- **Use Data Protection API for encrypting user data at rest.** Never roll your own encryption. The Data Protection API handles key rotation and algorithm selection correctly.
- **CORS: explicit origins only, never wildcard in production.** `AllowAnyOrigin()` in production exposes your API to every domain on the internet.
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task CreateOrder_ValidRequest_ReturnsCreated()
var response = await client.PostAsJsonAsync("/orders", request);

// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}
```

Expand Down
5 changes: 3 additions & 2 deletions .codex/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Skills live in `skills/<skill-name>/SKILL.md` and follow the Agent Skills open s
api-versioning, architecture-advisor, aspire, authentication, caching, ci-cd, clean-architecture, configuration, ddd, dependency-injection, docker, ef-core, error-handling, httpclient-factory, logging, messaging, minimal-api, modern-csharp, openapi, opentelemetry, project-setup, project-structure, resilience, scalar, serilog, testing, vertical-slice, container-publish

### Workflow Skills (each registers a slash command and carries its methodology inline)
build-fix, checkpoint, code-review, de-sloppify, dotnet-init, health-check, migrate, plan, scaffold, security-scan, spec, tdd, verify, wrap-up
arch-check, build-fix, checkpoint, code-review, de-sloppify, dotnet-init, health-check, migrate, outdated, plan, scaffold, security-scan, spec, tdd, verify, wrap-up

### Workflow & Learning Skills
convention-learner, workflow-mastery, instinct-system
Expand Down Expand Up @@ -56,7 +56,7 @@ Always prefer MCP tools over reading full source files to conserve context windo

## Rules

Always-applied coding conventions live in `rules/dotnet/`:
Always-applied coding conventions live in `.claude/rules/`:

- `coding-style.md` -- C# 14 style, naming, file organization
- `architecture.md` -- Dependency direction, feature folders, data access
Expand All @@ -67,3 +67,4 @@ Always-applied coding conventions live in `rules/dotnet/`:
- `git-workflow.md` -- Conventional commits, atomic commits, branch safety
- `agents.md` -- MCP-first tools, subagent routing, skill loading
- `hooks.md` -- Format hooks, pre-commit, post-test analysis
- `packages.md` -- Latest stable NuGet versions, central package management
4 changes: 2 additions & 2 deletions .cursor/rules/dotnet-rules.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# .NET Development Rules (Cursor IDE)

> **Auto-generated** from `rules/dotnet/` directory in dotnet-claude-kit.
> Do not edit this file directly. Update the individual rule files in `rules/dotnet/` and regenerate.
> **Auto-generated** from the `.claude/rules/` directory in dotnet-claude-kit.
> Do not edit this file directly. Update the individual rule files in `.claude/rules/` and regenerate.

---

Expand Down
155 changes: 155 additions & 0 deletions .github/scripts/check_docs_consistency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Verify component counts claimed in docs match the actual repository contents.

Ground truth (computed dynamically from the working tree):
skills : skills/*/SKILL.md directories
agents : agents/*.md files
rules : .claude/rules/*.md (plus rules/**/*.md if present)
templates : templates/*/ directories
mcp tools : [McpServerTool(...)] attribute occurrences in mcp/**/Tools/*.cs
workflows : rows of the "## Slash Commands" table in README.md
(each listed /name must also exist as a skill directory)

Claim sources scanned: README.md, docs/shorthand-guide.md,
.claude-plugin/plugin.json, .claude-plugin/marketplace.json.
Every numeric claim found (e.g. "45 skills", "15 MCP tools",
"## Slash Commands (14)") must equal the computed actual. Sources without
counts pass trivially.
"""

from __future__ import annotations

import glob
import os
import re
import sys

CLAIM_SOURCES = [
"README.md",
"docs/shorthand-guide.md",
".claude-plugin/plugin.json",
".claude-plugin/marketplace.json",
]

# category -> list of regexes whose single capture group is the claimed count
CLAIM_PATTERNS: dict[str, list[re.Pattern[str]]] = {
"skills": [re.compile(r"\b(\d+)\s+skills\b", re.I)],
"agents": [re.compile(r"\b(\d+)\s+(?:specialist\s+)?agents\b", re.I)],
"rules": [re.compile(r"\b(\d+)\s+rules\b", re.I)],
"templates": [re.compile(r"\b(\d+)\s+(?:project\s+)?templates\b", re.I)],
"mcp tools": [
re.compile(r"\b(\d+)\s+(?:read-only\s+)?(?:Roslyn\s+)?MCP\s+tools\b", re.I),
re.compile(r"\((\d+)\s+tools\)", re.I),
],
"workflows": [
re.compile(r"\b(\d+)\s+slash[- ]command", re.I),
re.compile(r"^##\s+Slash\s+Commands\s+\((\d+)\)", re.I),
],
}

MCP_TOOL_ATTR = re.compile(r"\[\s*McpServerTool\s*[(,\]]")
SLASH_HEADING = re.compile(r"^##\s+Slash\s+Commands\b", re.I)
SLASH_ROW = re.compile(r"^\|\s*`/([A-Za-z0-9_-]+)`")


def count_mcp_tools() -> int:
total = 0
for path in glob.glob("mcp/**/Tools/*.cs", recursive=True):
with open(path, encoding="utf-8") as f:
total += len(MCP_TOOL_ATTR.findall(f.read()))
return total


def slash_commands_from_readme(errors: list[str]) -> int | None:
"""Return the number of /commands listed in README's Slash Commands table,
verifying each maps to an existing skill directory."""
if not os.path.isfile("README.md"):
errors.append("README.md not found")
return None
with open("README.md", encoding="utf-8") as f:
lines = f.readlines()
names: list[str] = []
in_section = False
for line in lines:
if SLASH_HEADING.match(line):
in_section = True
continue
if in_section and line.startswith("## "):
break
if in_section:
match = SLASH_ROW.match(line)
if match:
names.append(match.group(1))
if not in_section:
return None
for name in names:
if not os.path.isfile(f"skills/{name}/SKILL.md"):
errors.append(
f"README.md slash-command table lists /{name} "
f"but skills/{name}/SKILL.md does not exist"
)
return len(names)


def compute_actuals(errors: list[str]) -> dict[str, int | None]:
rules = set(glob.glob(".claude/rules/*.md")) | set(
glob.glob("rules/**/*.md", recursive=True)
)
return {
"skills": len(glob.glob("skills/*/SKILL.md")),
"agents": len(glob.glob("agents/*.md")),
"rules": len(rules),
"templates": len([d for d in glob.glob("templates/*") if os.path.isdir(d)]),
"mcp tools": count_mcp_tools(),
"workflows": slash_commands_from_readme(errors),
}


def scan_claims(actuals: dict[str, int | None], errors: list[str]) -> int:
checked = 0
for source in CLAIM_SOURCES:
if not os.path.isfile(source):
print(f"note: {source} not found, skipping")
continue
with open(source, encoding="utf-8") as f:
lines = f.readlines()
for lineno, line in enumerate(lines, start=1):
for category, patterns in CLAIM_PATTERNS.items():
for pattern in patterns:
for match in pattern.finditer(line):
checked += 1
claimed = int(match.group(1))
actual = actuals[category]
if actual is None:
errors.append(
f"{source}:{lineno} claims {claimed} {category} but "
"the actual count could not be computed "
"(no Slash Commands table found in README.md)"
)
elif claimed != actual:
errors.append(
f"{source}:{lineno} claims {claimed} {category}, "
f"actual is {actual} — '{match.group(0).strip()}'"
)
return checked


def main() -> int:
errors: list[str] = []
actuals = compute_actuals(errors)
print("actual counts:")
for category, value in actuals.items():
print(f" {category}: {value if value is not None else 'n/a'}")
checked = scan_claims(actuals, errors)
print(f"checked {checked} numeric claim(s) across {len(CLAIM_SOURCES)} source(s)")
for error in errors:
print(f"ERROR: {error}")
if errors:
print(f"{len(errors)} docs-consistency error(s) found")
return 1
print("all documented counts match the repository contents")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading