Add .NET API design review agent and skill - #15
Conversation
|
Validated with Humanizer. |
artl93
left a comment
There was a problem hiding this comment.
Hold off until I've done some work.
| @@ -0,0 +1,126 @@ | |||
| --- | |||
| description: "Use this agent when the user wants to review, design, or improve .NET API surfaces for consistency with established C# conventions.\n\nTrigger phrases include:\n- 'review my API design'\n- 'is this API consistent with .NET conventions?'\n- 'check my public API surface'\n- 'help me design this .NET API'\n- 'review my naming conventions'\n- 'should this be a class or struct?'\n- 'is this a breaking change?'\n- 'prepare an API proposal'\n- 'check my exception design'\n- 'review my library API'\n\nExamples:\n- User asks 'Does my API follow .NET naming conventions?' → invoke this agent to review naming against C# conventions\n- User shares a class and asks 'Is this API well-designed?' → invoke this agent to perform a full API design review\n- User asks 'Should I use a class or struct for this type?' → invoke this agent to analyze type design\n- User says 'I need to add a new overload without breaking existing callers' → invoke this agent to assess breaking change risk\n- User asks 'Review this API proposal before I submit it' → invoke this agent to apply the full API review checklist" | |||
There was a problem hiding this comment.
@artl93 - note to self - trim the heck out of this and everything else. Reduce using the best practices from Anthropic using copilot.
Agent (126→43 lines, -66%): - Rename to reviewing-dotnet-api-design (gerund form) - Rewrite description: third person, no trigger phrases/examples - Remove redundant sections: Mission, Core Design Philosophy, Review Methodology (duplicated skill), When to Ask, Tone - Keep: key principles, output format, skill reference, escalation SKILL.md (360→109 lines, -70%): - Rename to reviewing-dotnet-api-design (gerund form) - Convert from inline conventions to router pattern - Steps 2-5 now point to reference files instead of duplicating content - Remove When to Use, When Not to Use, Failure Modes - Consolidate validation checklist and common pitfalls Reference files (1,003→689 lines, -31%): - naming-conventions.md: 177→71 (-60%) — removed prose, kept tables - type-design-patterns.md: 199→135 (-32%) — removed explanations Claude knows - member-design-patterns.md: 266→157 (-41%) — kept non-obvious patterns - error-handling-patterns.md: 195→176 (-10%) — light trim, unique value - api-review-checklist.md: 166→150 (-10%) — trimmed process prose Total: 1,489→841 lines (-44%)
Test asset (test-api.cs) contains 6 deliberate API design violations: - Mutable struct with reference-type field (Critical) - List<T> return in public API (Critical) - Noun-named method (Warning) - Missing paramName on exception (Warning) - Unsealed leaf class (Warning) - Expensive property (Suggestion) Plus 3 correctly implemented patterns (event, IDisposable, EventArgs). README defines 10-point evaluation criteria comparing with/without skill. bad.md and good.md show expected behavior difference.
Replace easy violations (labeled comments, obvious issues) with a realistic message broker API containing 14 violations that require .NET-specific convention knowledge to catch: Hard catches (unlikely without skill): - Mutable struct >16 bytes with Dictionary<> field - [Flags] enum with non-power-of-two values (ExactlyOnce = 3) - Parameter name inconsistency across related methods - Async argument validation inside Task.Run instead of synchronous - Unpaired operator == (missing !=, Equals, GetHashCode) - Missing TryParse companion for Parse method - Abbreviated parameter name in public API (msg vs message) The test code compiles and works correctly — violations are invisible without convention knowledge from the skill's reference files.
Removed instruction to avoid citing specific resources.
bartonjs
left a comment
There was a problem hiding this comment.
I've remembered today is a holiday and I'm not working. I'll look more later.
|
|
||
| ## ToString Pattern | ||
|
|
||
| Every type should override `ToString()` with a human-readable representation. |
There was a problem hiding this comment.
No, no it shouldn't.
The guideline is "override ToString whenever an interesting human-readable string
can be returned." "whenever" is a very important word there.
| - [ ] Properties are cheap and idempotent | ||
| - [ ] Methods used for operations, conversions, expensive work | ||
| - [ ] Overloads have consistent parameter order | ||
| - [ ] Simplest overload delegates to most complete |
There was a problem hiding this comment.
Some of these are about implementation (which can be visible to the caller), but aren't really visible in an API proposal. Like which ctor delegates to which. (The asmmeta or ref.cs just says they exist, it doesn't show the deferral)
| - [ ] Standard exception types used | ||
| - [ ] No direct `Exception` or `SystemException` throwing | ||
| - [ ] `paramName` set on argument exceptions | ||
| - [ ] Exception messages are clear and actionable |
There was a problem hiding this comment.
All of these parts about exceptions aren't measurable during an API Review, so they couldn't be assessed in an API Review preflight.
|
|
||
| Note: Use `value` as the parameter name in property setter exceptions. | ||
|
|
||
| ### Legacy Pattern (pre-.NET 6) |
There was a problem hiding this comment.
I'm not sure where "pre-.NET 6" comes from. SetName would be correct if it can only be set once, or the setter would have an interdependency with other properties, or a few other reasons why a property setter would be bad.
| 1. Parameter order is consistent across all overloads | ||
| 2. Simpler overloads delegate to the most complete one | ||
| 3. Parameter names are identical across overloads | ||
| 4. `CancellationToken` is always the last parameter |
There was a problem hiding this comment.
CancellationToken should also (almost) always be defaulted.
| 2. Simpler overloads delegate to the most complete one | ||
| 3. Parameter names are identical across overloads | ||
| 4. `CancellationToken` is always the last parameter | ||
| 5. `params` array overload is the most flexible variant |
There was a problem hiding this comment.
We probably don't want any new params[]. Maybe params ReadOnlySpan. Or just make people type square braces. But probably isn't something really to be encouraged.
|
|
||
| ## Property Names | ||
|
|
||
| Boolean properties use `Is`/`Can`/`Has` prefix. |
There was a problem hiding this comment.
Sometimes. Like "CanRead" is better than "Read", but "Closed" is probably better than "IsClosed" But, if there's a Closed event, then the property ends up as IsClosed because the event wins the name conflict.
| // Abstract: derived types MUST implement | ||
| public abstract int Read(byte[] buffer, int offset, int count); | ||
| public abstract void Write(byte[] buffer, int offset, int count); | ||
| public abstract long Length { get; } |
There was a problem hiding this comment.
public abstract and public virtual (for things that take parameters) are almost an anti-pattern at this point. Instead, public int Read(byte[] buffer, int offset, int count) should do argument validation and call protected abstract int ReadCore(...). And, of course, these days the ReadCore would probably be span-based.
jeffschwMSFT
left a comment
There was a problem hiding this comment.
please rebase into the new repro structure and run the skill validator
|
@jeffschwMSFT - ran evals on this - I don't think this skill is worth it compared to what the models can do. |
.NET API Design Review Agent & Skill
This PR introduces a reviewing-dotnet-api-design agent and skill for reviewing .NET API designs against established C# conventions. The skill explicitly avoids citing the copyrighted Framework Design Guidelines book — all conventions are sourced from established patterns in the BCL itself.
What it does
Reviews public .NET API surfaces for consistency with established C# conventions across 9 areas: naming, type design, member design, error handling, collections, resource management, extensibility, and breaking changes.
What the skill adds over base Claude
List<T>in public API (valid code)Collection<T>/ReadOnlyCollection<T>paramName, recommendsThrowIfNullhelpersArchitecture
SKILL.md acts as a router — it defines the 9-step workflow and points to 5 reference files loaded on-demand via progressive disclosure:
Total: 841 lines (agent + skill + references). Trimmed per Claude skill authoring best practices — only adds context Claude doesn't already have.
Test cases
tests/reviewing-dotnet-api-design/includes a test asset (test-api.cs) with 6 embedded violations and 3 correctly implemented patterns. The README defines a 10-point evaluation rubric comparing with-skill vs without-skill output.Closes #10