@mrciphersmith/keryx 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4745 -2482
- package/dist/core.js +66 -10
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +578 -2
- package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +81 -21
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
- package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
- package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Use when implementing or extending a feature in a C#/.NET service or library -- DI registration and lifetimes, async/await (Task vs ValueTask, avoiding async void), nullable reference type annotations, records vs classes, EF Core query shape, and IDisposable/IAsyncDisposable cleanup.",
|
|
4
|
+
"decision": "fork",
|
|
5
|
+
"topMatch": "csharp-dotnet/dotnet-code-review",
|
|
6
|
+
"recordedAt": "2026-09-25T15:34:51.958Z",
|
|
7
|
+
"skillName": "dotnet-implementation",
|
|
8
|
+
"justification": "Top match dotnet-code-review is this same pack's read-only review skill (different intent, action verb, and workflow), not a real implementation-skill duplicate; no existing implement skill covers C#/.NET DI lifetimes, Task/ValueTask, nullable reference types, or EF Core query shape."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"query": "Use when a C#/.NET test project needs writing, extending, or fixing -- xUnit/NUnit Fact/Theory and InlineData/TestCase tables, async test methods, and disciplined Moq/NSubstitute mocking that stubs external seams (HTTP, repository, clock) rather than internal collaborators.",
|
|
12
|
+
"decision": "create",
|
|
13
|
+
"topMatch": "swift-ios/swift-testing",
|
|
14
|
+
"recordedAt": "2026-09-25T15:33:42.746Z",
|
|
15
|
+
"skillName": "dotnet-testing"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"query": "Use when reviewing a C#/.NET change for async and resource-safety risks -- async void outside event handlers, sync-over-async (.Result/.Wait()), swallowed/blanket exception catches, missing IDisposable/IAsyncDisposable cleanup, and nullable-annotation gaps. Read-only, no edits.",
|
|
19
|
+
"decision": "fork",
|
|
20
|
+
"topMatch": "ts-js-node/nodejs-code-review",
|
|
21
|
+
"recordedAt": "2026-09-25T15:34:52.239Z",
|
|
22
|
+
"skillName": "dotnet-code-review",
|
|
23
|
+
"justification": "Nearest match ts-js-node/nodejs-code-review (0.24-ish overlap) reviews TypeScript/Node concurrency and idiom, not C#'s async-void/sync-over-async/IDisposable vocabulary; no existing review skill covers .NET-specific async and resource-safety anti-patterns."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"query": "Use when dotnet build/dotnet test fails, or a NuGet restore is broken -- resolves package/target-framework mismatches, compiler errors, nullable-annotation warnings, analyzer/StyleCop findings, and a failing test, with the smallest root-cause fix rather than a suppression.",
|
|
27
|
+
"decision": "fork",
|
|
28
|
+
"topMatch": "kotlin-android/kotlin-android-build-fix",
|
|
29
|
+
"recordedAt": "2026-09-25T15:34:52.517Z",
|
|
30
|
+
"skillName": "dotnet-build-fix",
|
|
31
|
+
"justification": "Nearest match kotlin-android/kotlin-android-build-fix fixes Gradle/AGP/Kotlin compiler errors, not dotnet build/NuGet/target-framework/StyleCop findings; each build-fix skill is scoped to its own toolchain's specific failure surface, sharing only the generic 'root cause not suppression' framing."
|
|
32
|
+
}
|
|
33
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "csharp-dotnet",
|
|
3
|
+
"family": "language",
|
|
4
|
+
"modules": ["csharp-dotnet-rules", "csharp-dotnet-skills"],
|
|
5
|
+
"detectionMarkers": ["csharp", "dotnet", "c#"],
|
|
6
|
+
"provenance": { "origin": "authored", "sourceRef": "flow 336, Wave 4 batch 4" },
|
|
7
|
+
"stability": "experimental",
|
|
8
|
+
"skills": {
|
|
9
|
+
"implement": ["dotnet-implementation"],
|
|
10
|
+
"test": ["dotnet-testing"],
|
|
11
|
+
"review": ["dotnet-code-review"],
|
|
12
|
+
"build-fix": ["dotnet-build-fix"],
|
|
13
|
+
"migrate": []
|
|
14
|
+
},
|
|
15
|
+
"agentProfile": {
|
|
16
|
+
"displayName": "C#/.NET",
|
|
17
|
+
"auditFocus": [
|
|
18
|
+
"async void used outside an event handler, swallowing any exception the method throws",
|
|
19
|
+
"a blocking `.Result`/`.Wait()` call on a Task from synchronous code, risking a sync-over-async deadlock",
|
|
20
|
+
"a public API returning a nullable-looking reference type with nullable annotations disabled, or an unannotated parameter that is dereferenced without a null check",
|
|
21
|
+
"IDisposable/IAsyncDisposable fields or locals not wrapped in `using`/`await using`, leaking unmanaged resources or connections",
|
|
22
|
+
"SQL built by string interpolation/concatenation into SqlCommand.CommandText instead of parameterized queries",
|
|
23
|
+
"a unit test mocking an internal collaborator (a private helper, a concrete implementation detail) instead of the external seam (HTTP client, repository interface, database)"
|
|
24
|
+
],
|
|
25
|
+
"buildCommands": [
|
|
26
|
+
"dotnet build",
|
|
27
|
+
"dotnet test",
|
|
28
|
+
"dotnet format --verify-no-changes",
|
|
29
|
+
"dotnet build /p:TreatWarningsAsErrors=true /p:EnforceCodeStyleInBuild=true"
|
|
30
|
+
],
|
|
31
|
+
"fixGuardrails": [
|
|
32
|
+
"never add `#pragma warning disable` or `[SuppressMessage]` to silence a compiler/analyzer warning without fixing or explaining the underlying issue",
|
|
33
|
+
"never downgrade or disable nullable reference types (`<Nullable>disable</Nullable>` or a blanket `#nullable disable`) just to make a nullability warning disappear",
|
|
34
|
+
"never pin or downgrade a NuGet package to route around a real incompatibility without saying so in the report",
|
|
35
|
+
"fix the smallest root cause; do not refactor unrelated code while resolving a build/test/analyzer failure"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.cs"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# C#/.NET coding style
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic style rules to modern C#
|
|
11
|
+
idiom (new project templates enable nullable reference types by
|
|
12
|
+
default since .NET 6/C# 10 — the language itself still defaults
|
|
13
|
+
annotations/warnings off, so an existing project may need `<Nullable>
|
|
14
|
+
enable</Nullable>` added explicitly; primary constructors and
|
|
15
|
+
collection expressions since C# 12/.NET 8).
|
|
16
|
+
Applies only to `*.cs` files — everything not C#-specific still comes
|
|
17
|
+
from the common rules this file `extends`.
|
|
18
|
+
|
|
19
|
+
## Naming and structure
|
|
20
|
+
|
|
21
|
+
- `PascalCase` for types, methods, properties, and public/protected
|
|
22
|
+
members; `camelCase` for locals and parameters; a private field is
|
|
23
|
+
`_camelCase` with a leading underscore, not `m_field` or bare
|
|
24
|
+
`camelCase` that can collide with a parameter name.
|
|
25
|
+
- Interfaces are prefixed `I` (`IOrderRepository`), never suffixed
|
|
26
|
+
(`OrderRepositoryInterface`).
|
|
27
|
+
- One public type per file, file named after the type
|
|
28
|
+
(`OrderService.cs` for `class OrderService`), unless the type is a
|
|
29
|
+
small private nested/support type used only inside its owner.
|
|
30
|
+
- File-scoped namespaces (`namespace Orders;`) instead of a nested
|
|
31
|
+
block, unless the project's existing files already use block-scoped
|
|
32
|
+
namespaces — match what is there.
|
|
33
|
+
|
|
34
|
+
## Records, classes, and immutability
|
|
35
|
+
|
|
36
|
+
- Prefer a `record`/`record class` for a type whose identity is its
|
|
37
|
+
value (a DTO, a command, an event, an immutable snapshot) — it gets
|
|
38
|
+
structural equality and a `with` expression for free. Reach for `class`
|
|
39
|
+
when the type has identity beyond its data, mutable state that changes
|
|
40
|
+
over its lifetime, or entity semantics an ORM tracks by reference/key.
|
|
41
|
+
- Use `record struct` for a small, frequently-allocated value type where
|
|
42
|
+
avoiding heap allocation matters; default to a reference `record`
|
|
43
|
+
otherwise.
|
|
44
|
+
- Favor `init`-only properties (or a primary constructor) over a
|
|
45
|
+
public setter on anything that should not change after construction —
|
|
46
|
+
a mutable public setter on a "value" type invites accidental aliasing
|
|
47
|
+
bugs.
|
|
48
|
+
|
|
49
|
+
## Pattern matching and modern idiom
|
|
50
|
+
|
|
51
|
+
- Prefer pattern matching (`is`, `switch` expressions, property patterns,
|
|
52
|
+
list patterns) over a chain of `if`/`else if` type checks or casts —
|
|
53
|
+
`if (shape is Circle { Radius: > 0 } c)` reads its own intent; a cast
|
|
54
|
+
followed by a separate null/type check does not.
|
|
55
|
+
- Use a primary constructor (`class OrderService(IOrderRepository repo)`)
|
|
56
|
+
for a type whose constructor only assigns its parameters to fields —
|
|
57
|
+
it removes the boilerplate field-and-assignment block. Fall back to an
|
|
58
|
+
explicit constructor body once it needs validation, logic, or more than
|
|
59
|
+
trivial assignment.
|
|
60
|
+
- Use collection expressions (`int[] xs = [1, 2, 3];`, `List<T> ys = [];`)
|
|
61
|
+
for new collection-literal code instead of `new[] { ... }`/
|
|
62
|
+
`new List<T> { ... }`, unless the project's existing style has not
|
|
63
|
+
adopted them yet — match what is there rather than mixing both styles
|
|
64
|
+
in one file.
|
|
65
|
+
- Expression-bodied members (`public int Total => Items.Sum(i => i.Price);`)
|
|
66
|
+
for a single-expression method/property/operator; keep a full block
|
|
67
|
+
body once the member needs more than one statement or local reasoning
|
|
68
|
+
step — do not force multi-statement logic into one expression via `;`
|
|
69
|
+
chaining or nested ternaries.
|
|
70
|
+
|
|
71
|
+
## Nullable reference types
|
|
72
|
+
|
|
73
|
+
- New projects have the nullable annotation and warning contexts on by
|
|
74
|
+
default (`<Nullable>enable</Nullable>` in the project file) — keep it
|
|
75
|
+
on; do not add `<Nullable>disable</Nullable>` or a blanket
|
|
76
|
+
`#nullable disable` to make warnings go away.
|
|
77
|
+
- A parameter, property, or return type that can genuinely be absent is
|
|
78
|
+
annotated `?` (`string? middleName`); a public member with no `?` is a
|
|
79
|
+
promise to callers that it is never null — do not violate that promise
|
|
80
|
+
with a hidden `null` return "just this once."
|
|
81
|
+
- Use `ArgumentNullException.ThrowIfNull(value)` at a public API's entry
|
|
82
|
+
point for a reference-typed parameter that must not be null, instead of
|
|
83
|
+
a hand-rolled `if (value is null) throw new ArgumentNullException(...)`.
|
|
84
|
+
- Resolve a nullability warning by fixing the actual null path (a real
|
|
85
|
+
null check, a non-null initializer, restructuring so the compiler can
|
|
86
|
+
see the invariant) — never with the null-forgiving operator (`value!`)
|
|
87
|
+
as a substitute for an actual guard, except at a boundary where you can
|
|
88
|
+
state in a comment exactly why the value is provably non-null there.
|
|
89
|
+
|
|
90
|
+
## `var` usage
|
|
91
|
+
|
|
92
|
+
- Use `var` when the right-hand side already makes the type obvious
|
|
93
|
+
(`var order = new Order();`, `var items = order.Items.ToList();`) or
|
|
94
|
+
when the concrete type is long/generic and adds no clarity
|
|
95
|
+
(`var lookup = new Dictionary<string, List<OrderLine>>();`).
|
|
96
|
+
- Use an explicit type when `var` would hide the type at a call site that
|
|
97
|
+
matters for reading correctness (`var result = Parse(input);` where
|
|
98
|
+
`Parse`'s return type is not obvious from the name) or when the
|
|
99
|
+
declared type differs meaningfully from the initializer's runtime type
|
|
100
|
+
(assigning a derived type but declaring the base).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.cs"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# C#/.NET patterns
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic design guidance to
|
|
11
|
+
idiomatic C#/.NET design and its common anti-patterns: async/await,
|
|
12
|
+
dependency injection, LINQ, and disposal. Applies only to `*.cs` files.
|
|
13
|
+
|
|
14
|
+
## Async/await
|
|
15
|
+
|
|
16
|
+
- Never use `async void` except for an actual event handler
|
|
17
|
+
(`private async void Button_Click(object sender, EventArgs e)`) — any
|
|
18
|
+
exception an `async void` method throws cannot be awaited or caught by
|
|
19
|
+
its caller and crashes the process instead. Every other async method
|
|
20
|
+
returns `Task` or `Task<T>`.
|
|
21
|
+
- Return `Task`/`Task<T>` for a method whose async work is on the common,
|
|
22
|
+
usually-awaited path; return `ValueTask`/`ValueTask<T>` only for a
|
|
23
|
+
hot-path method that frequently completes synchronously (a cache hit),
|
|
24
|
+
where avoiding the `Task` allocation is measured to matter — a
|
|
25
|
+
`ValueTask` must be awaited (or converted) exactly once and never
|
|
26
|
+
stored, unlike a `Task`.
|
|
27
|
+
- In library code that does not need to resume on a captured context
|
|
28
|
+
(UI thread, ASP.NET classic `SynchronizationContext`), call
|
|
29
|
+
`.ConfigureAwait(false)` on an awaited `Task` to avoid an unnecessary
|
|
30
|
+
context-capture and reduce deadlock risk. Modern ASP.NET Core has no
|
|
31
|
+
such `SynchronizationContext`, so application-level request-handling
|
|
32
|
+
code in an ASP.NET Core project does not need it — match what the
|
|
33
|
+
project's own code already does.
|
|
34
|
+
- Never call `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()` on a
|
|
35
|
+
`Task` from otherwise-synchronous code to "call an async method
|
|
36
|
+
synchronously" — this is sync-over-async and can deadlock when the
|
|
37
|
+
awaited call needs to resume on a context the blocking thread is
|
|
38
|
+
itself occupying. Make the calling method `async` and `await` instead;
|
|
39
|
+
if the call truly cannot become async (a legacy synchronous interface
|
|
40
|
+
boundary), that is a design problem to flag, not to paper over.
|
|
41
|
+
|
|
42
|
+
## Dependency injection
|
|
43
|
+
|
|
44
|
+
- Register a service at the narrowest lifetime that is correct:
|
|
45
|
+
`Singleton` for stateless or thread-safe shared state, `Scoped` for
|
|
46
|
+
per-request/per-unit-of-work state (a `DbContext`, a request-scoped
|
|
47
|
+
cache), `Transient` for cheap, stateless, or intentionally
|
|
48
|
+
non-shared instances. Never inject a `Scoped` service into a
|
|
49
|
+
`Singleton` by capturing it directly — that captures the first
|
|
50
|
+
request's instance for the app's lifetime; resolve it per-use via
|
|
51
|
+
`IServiceScopeFactory`/`IServiceProvider.CreateScope()` instead.
|
|
52
|
+
- Depend on an interface (`IOrderRepository`), not a concrete
|
|
53
|
+
implementation, at a constructor boundary that DI resolves — this is
|
|
54
|
+
what makes the type unit-testable without a real database/HTTP call.
|
|
55
|
+
- Prefer constructor injection for required dependencies; reserve
|
|
56
|
+
property injection for genuinely optional dependencies with a working
|
|
57
|
+
default, which is uncommon.
|
|
58
|
+
|
|
59
|
+
## LINQ efficiency
|
|
60
|
+
|
|
61
|
+
- Materialize a query with `.ToList()`/`.ToArray()` before enumerating it
|
|
62
|
+
more than once (in a loop that also iterates it, or across a
|
|
63
|
+
`.Count()` followed by a `foreach`) — an `IEnumerable<T>` built from a
|
|
64
|
+
`IQueryable<T>`/deferred LINQ chain re-runs the whole query (or, worse,
|
|
65
|
+
a whole database round trip) on every enumeration unless it is
|
|
66
|
+
materialized once.
|
|
67
|
+
- Avoid stacking multiple `.Where()`/`.Select()` passes over the same
|
|
68
|
+
large sequence when one combined pass would do — each pass is a full
|
|
69
|
+
additional enumeration for an in-memory `IEnumerable<T>`.
|
|
70
|
+
- On an `IQueryable<T>` backed by a database (EF Core), keep filtering
|
|
71
|
+
(`.Where`) and projection (`.Select`) in the query itself so they
|
|
72
|
+
translate to SQL, rather than calling `.ToList()` early and then
|
|
73
|
+
filtering in memory — the latter pulls the whole table across the wire
|
|
74
|
+
first.
|
|
75
|
+
- Use `.Any()` to check for existence, never `.Count() > 0`, which forces
|
|
76
|
+
a full count when a short-circuiting existence check would do.
|
|
77
|
+
|
|
78
|
+
## Disposal patterns
|
|
79
|
+
|
|
80
|
+
- Any local or field holding an `IDisposable` is wrapped in a `using`
|
|
81
|
+
statement/declaration (`using var conn = new SqlConnection(...);`) or
|
|
82
|
+
disposed in a `Dispose()`/`finally` block — never left for the
|
|
83
|
+
finalizer, which is non-deterministic and, for something like an open
|
|
84
|
+
connection or file handle, holds the resource far longer than needed.
|
|
85
|
+
- Use `await using` for an `IAsyncDisposable` (most modern EF Core/
|
|
86
|
+
streaming/channel types expose one) so disposal itself is awaited
|
|
87
|
+
instead of blocking a thread on synchronous `Dispose()`.
|
|
88
|
+
- A class that owns one or more `IDisposable`/`IAsyncDisposable`
|
|
89
|
+
fields implements `IDisposable`/`IAsyncDisposable` itself and disposes
|
|
90
|
+
them in its own `Dispose`/`DisposeAsync`, rather than requiring every
|
|
91
|
+
caller to know which of its fields need cleanup.
|
|
92
|
+
|
|
93
|
+
## Anti-patterns to flag
|
|
94
|
+
|
|
95
|
+
- A "god service"/"utils" static class that accumulates unrelated
|
|
96
|
+
helper methods instead of being split along what the code actually
|
|
97
|
+
does — the same smell as a Go "god package," expressed as a static
|
|
98
|
+
class here.
|
|
99
|
+
- Catching `Exception` broadly and continuing as if nothing happened,
|
|
100
|
+
instead of catching the specific exception type a call can throw (or
|
|
101
|
+
letting an unexpected one propagate) — see `rules/security.mdc` and
|
|
102
|
+
`dotnet-code-review`'s focus list for the review-time version of this
|
|
103
|
+
check.
|
|
104
|
+
- Exposing a mutable `List<T>`/collection field directly as a public
|
|
105
|
+
property instead of `IReadOnlyList<T>`/`IReadOnlyCollection<T>` (or a
|
|
106
|
+
defensive copy), which lets any caller mutate internal state through a
|
|
107
|
+
reference they were only meant to read.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.cs"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# C#/.NET security
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security rules to
|
|
11
|
+
C#/.NET-specific, OWASP-relevant risks and the safe framework API to use
|
|
12
|
+
instead. Applies only to `*.cs` files.
|
|
13
|
+
|
|
14
|
+
## SQL and data access
|
|
15
|
+
|
|
16
|
+
- Use parameterized queries for any SQL built from user-influenced
|
|
17
|
+
input: `SqlCommand.Parameters.Add(...)`/`AddWithValue(...)` with `@`
|
|
18
|
+
placeholders, or an ORM's parameterized query API (EF Core LINQ,
|
|
19
|
+
Dapper's parameter objects) — never `string.Format`/interpolation/
|
|
20
|
+
concatenation to build `CommandText` with a value that came from a
|
|
21
|
+
caller. `$"SELECT * FROM Users WHERE Name = '{name}'"` is a SQL
|
|
22
|
+
injection sink even when it "looks like" it is just formatting.
|
|
23
|
+
- An ORM's raw-SQL escape hatch (EF Core's `FromSqlRaw`, Dapper's raw
|
|
24
|
+
string execution) carries the same rule: pass user-influenced values
|
|
25
|
+
as parameters, never interpolated directly into the raw SQL string.
|
|
26
|
+
Prefer `FromSqlInterpolated`, which parameterizes `$"..."`
|
|
27
|
+
interpolation holes automatically, over `FromSqlRaw` when the query
|
|
28
|
+
needs any caller-supplied value at all.
|
|
29
|
+
|
|
30
|
+
## Deserialization of untrusted data
|
|
31
|
+
|
|
32
|
+
- Never use `BinaryFormatter` to deserialize data from any source you do
|
|
33
|
+
not fully trust (or at all, on a current .NET version) — it is banned
|
|
34
|
+
by Microsoft for security reasons because a crafted payload can
|
|
35
|
+
execute arbitrary code during deserialization, and it is obsolete/
|
|
36
|
+
removed on modern .NET.
|
|
37
|
+
- Deserialize untrusted JSON with `System.Text.Json` using an explicit,
|
|
38
|
+
known target type — avoid `TypeNameHandling`-style polymorphic
|
|
39
|
+
deserialization from an untrusted source (whether via
|
|
40
|
+
`Newtonsoft.Json`'s `TypeNameHandling.Auto`/`All` or a custom
|
|
41
|
+
`$type`-driven resolver), which lets the payload itself choose what
|
|
42
|
+
type gets constructed.
|
|
43
|
+
- Validate/allowlist any type passed to `XmlSerializer`/`DataContractSerializer`
|
|
44
|
+
from untrusted input the same way; do not resolve a type name found
|
|
45
|
+
inside the untrusted payload without an allowlist.
|
|
46
|
+
|
|
47
|
+
## Secrets and configuration
|
|
48
|
+
|
|
49
|
+
- Never hard-code a connection string, API key, or signing secret in
|
|
50
|
+
source or commit it in `appsettings.json` — use User Secrets
|
|
51
|
+
(`dotnet user-secrets`) for local development and environment
|
|
52
|
+
variables, Azure Key Vault, or another secret store the project has
|
|
53
|
+
already standardized on for anything beyond local dev.
|
|
54
|
+
- A connection string or secret read from `IConfiguration` should come
|
|
55
|
+
from an environment-specific provider (environment variables, a
|
|
56
|
+
secret manager), not a value checked into `appsettings.json` or
|
|
57
|
+
`appsettings.Development.json` that happens to work.
|
|
58
|
+
|
|
59
|
+
## Path and file handling
|
|
60
|
+
|
|
61
|
+
- Never build a filesystem path for an untrusted, caller-supplied
|
|
62
|
+
filename/segment with plain string concatenation and open it directly
|
|
63
|
+
— validate the resulting path stays under the intended root (e.g.
|
|
64
|
+
resolve with `Path.GetFullPath` and confirm it starts with the root's
|
|
65
|
+
full path, or use an allowlist of known filenames) before calling
|
|
66
|
+
`File.Open`/`File.ReadAllText`/etc.; `Path.Combine` alone does not stop
|
|
67
|
+
a `..`-containing segment from climbing outside the intended
|
|
68
|
+
directory.
|
|
69
|
+
|
|
70
|
+
## Web and transport (ASP.NET Core)
|
|
71
|
+
|
|
72
|
+
- Never render user-influenced content into an HTML response outside of
|
|
73
|
+
Razor's default auto-encoding (`Html.Raw`/`@Html.Raw` on
|
|
74
|
+
caller-supplied content is an XSS vector) — let the framework's
|
|
75
|
+
default encoding do its job unless the content is provably
|
|
76
|
+
pre-sanitized.
|
|
77
|
+
- Never disable TLS certificate validation
|
|
78
|
+
(`ServerCertificateCustomValidationCallback` returning `true`
|
|
79
|
+
unconditionally, or `HttpClientHandler.ServerCertificateCustomValidationCallback`
|
|
80
|
+
bypassing validation) outside a short-lived local test, and never ship
|
|
81
|
+
it that way.
|
|
82
|
+
- Use the framework's built-in antiforgery support
|
|
83
|
+
(`[ValidateAntiForgeryToken]`/`IAntiforgery`) for any state-changing
|
|
84
|
+
endpoint reachable from a browser session, and rely on ASP.NET Core
|
|
85
|
+
Identity/`PasswordHasher<TUser>` (or an equivalent vetted library) for
|
|
86
|
+
password hashing — never a custom hash/roll-your-own scheme.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/*.cs"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# C#/.NET testing
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic testing rules to xUnit/NUnit
|
|
11
|
+
convention and disciplined mocking with Moq/NSubstitute. Applies only to
|
|
12
|
+
`*.cs` files.
|
|
13
|
+
|
|
14
|
+
## Framework and layout
|
|
15
|
+
|
|
16
|
+
- Match whichever test framework the project already uses (xUnit or
|
|
17
|
+
NUnit are both common; do not introduce a second one into a project
|
|
18
|
+
that has standardized on the other). xUnit: `[Fact]` for a single
|
|
19
|
+
case, `[Theory]` + `[InlineData(...)]`/`[MemberData(...)]` for
|
|
20
|
+
parameterized cases. NUnit: `[Test]` and `[TestCase(...)]` play the
|
|
21
|
+
same roles.
|
|
22
|
+
- Tests live in a mirrored `*.Tests`/`*.Test` project (the standard .NET
|
|
23
|
+
convention: `Orders/` production code, `Orders.Tests/` its tests), not
|
|
24
|
+
inside the production project itself.
|
|
25
|
+
- Name a test method for behavior, not implementation:
|
|
26
|
+
`MethodName_Scenario_ExpectedResult`
|
|
27
|
+
(`Validate_MissingOrderId_ThrowsArgumentException`) reads as a
|
|
28
|
+
specification; `Test1`/`ValidateTest` does not.
|
|
29
|
+
|
|
30
|
+
## Table-driven (`Theory`/`TestCase`) tests
|
|
31
|
+
|
|
32
|
+
- Use `[Theory]` + `[InlineData(...)]` (xUnit) or `[TestCase(...)]`
|
|
33
|
+
(NUnit) for a set of cases that exercise the same method with
|
|
34
|
+
different inputs/expected outputs, instead of copy-pasting a near-
|
|
35
|
+
identical `[Fact]`/`[Test]` per case — each data row becomes its own
|
|
36
|
+
reported test result, so a failure names exactly which input failed.
|
|
37
|
+
- Reach for `[MemberData(nameof(Cases))]`/a `TestCaseSource` when a case
|
|
38
|
+
needs a non-primitive value (an object, a collection) that attribute
|
|
39
|
+
arguments cannot express directly.
|
|
40
|
+
|
|
41
|
+
## Async tests
|
|
42
|
+
|
|
43
|
+
- A test that exercises `async` production code is itself `async Task`
|
|
44
|
+
(`public async Task Validate_ValidOrder_ReturnsSuccess()`), and awaits
|
|
45
|
+
the call under test — never `async void` (the test runner cannot await
|
|
46
|
+
it, and any assertion failure or exception inside it is lost instead
|
|
47
|
+
of failing the test) and never a blocking `.Result`/`.Wait()` on the
|
|
48
|
+
Task under test as a substitute for `await`.
|
|
49
|
+
|
|
50
|
+
## Mocking boundaries: mock the seam, not the internals
|
|
51
|
+
|
|
52
|
+
- Mock/stub an **external dependency the unit under test talks to
|
|
53
|
+
through an interface** — an `HttpClient`-backed API client behind an
|
|
54
|
+
interface, a repository interface wrapping the database, a clock/time
|
|
55
|
+
provider, a message publisher — using Moq (`Mock<IThing>`) or
|
|
56
|
+
NSubstitute (`Substitute.For<IThing>()`).
|
|
57
|
+
- Do **not** mock an internal collaborator that is itself part of what
|
|
58
|
+
the test is supposed to verify — a private helper method, a concrete
|
|
59
|
+
value object, a piece of the same class's own logic split into another
|
|
60
|
+
method for readability. Mocking one layer too deep proves only that
|
|
61
|
+
the mock returns what you told it to, not that the unit under test
|
|
62
|
+
actually behaves correctly; it also makes the test brittle to harmless
|
|
63
|
+
internal refactors that change nothing about observable behavior.
|
|
64
|
+
- The question to ask before adding a mock: "does the real system cross
|
|
65
|
+
a process/network/disk boundary here (HTTP, database, filesystem,
|
|
66
|
+
clock, random), or am I mocking a class my own code owns and could
|
|
67
|
+
just construct for real?" Only the former earns a mock.
|
|
68
|
+
- Verify a mock's interaction (`mock.Verify(...)`/
|
|
69
|
+
`receivedCall.Received()`) only for a call whose occurrence is itself
|
|
70
|
+
part of the contract being tested (e.g. "an email is sent exactly
|
|
71
|
+
once") — do not add interaction verification for calls that are
|
|
72
|
+
incidental implementation detail, which locks the test to today's
|
|
73
|
+
internal wiring.
|
|
74
|
+
|
|
75
|
+
## Assertions and determinism
|
|
76
|
+
|
|
77
|
+
- Use the project's already-adopted assertion style (xUnit's built-in
|
|
78
|
+
`Assert`, or FluentAssertions if already a dependency) — do not
|
|
79
|
+
introduce a second assertion library into a project that has
|
|
80
|
+
standardized on one.
|
|
81
|
+
- Never synchronize with `Thread.Sleep`/`Task.Delay` to wait for an
|
|
82
|
+
async operation or background work to finish — await the actual
|
|
83
|
+
`Task`, or use a real synchronization primitive
|
|
84
|
+
(`TaskCompletionSource`, a cancellation-bounded wait) so the test is
|
|
85
|
+
deterministic and not flaky under load.
|
|
86
|
+
- Reset/dispose any shared test fixture (`IClassFixture<T>`, a
|
|
87
|
+
`WebApplicationFactory<TEntryPoint>` for integration tests) between
|
|
88
|
+
cases that mutate shared state, so one test's leftovers cannot change
|
|
89
|
+
another's result depending on run order.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dotnet-build-fix
|
|
3
|
+
description: "Use when dotnet build/dotnet test fails, or a NuGet restore is broken -- resolves package/target-framework mismatches, compiler errors, nullable-annotation warnings, analyzer/StyleCop findings, and a failing test, with the smallest root-cause fix rather than a suppression."
|
|
4
|
+
triggers:
|
|
5
|
+
- "dotnet build is failing"
|
|
6
|
+
- "fix this NuGet package restore error"
|
|
7
|
+
- "resolve this nullable reference type warning"
|
|
8
|
+
- "dotnet build analyzer warning"
|
|
9
|
+
- "StyleCop is failing on this C# file"
|
|
10
|
+
- "dotnet test is failing, fix the build"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: build-fix
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# .NET build fix
|
|
20
|
+
|
|
21
|
+
Resolve a `dotnet build`/`dotnet test` failure, a NuGet restore error, a
|
|
22
|
+
compiler error, a nullable-reference-type warning, an analyzer/StyleCop
|
|
23
|
+
finding, or a failing test — with the smallest change that fixes the
|
|
24
|
+
actual root cause. `rules/coding-style.mdc` and `rules/security.mdc`
|
|
25
|
+
govern what a "correct" fix looks like; this skill never reaches for a
|
|
26
|
+
suppression instead of a fix.
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
### Step 1: Reproduce and classify
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
dotnet build
|
|
34
|
+
dotnet test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Read the exact error/warning text and classify it:
|
|
38
|
+
|
|
39
|
+
- **Compile error** (undefined symbol, type mismatch, wrong overload
|
|
40
|
+
resolution).
|
|
41
|
+
- **Package/restore** (NuGet version conflict, a `PackageReference`
|
|
42
|
+
pointing at a version that does not exist, a target-framework
|
|
43
|
+
mismatch between a project and one of its package dependencies).
|
|
44
|
+
- **Nullable warning** (`CS8600`-`CS8655` range — a possible null
|
|
45
|
+
reference, an unannotated parameter used where `?` was expected).
|
|
46
|
+
- **Analyzer/style finding** (a Roslyn analyzer, StyleCop, or an
|
|
47
|
+
`.editorconfig`-driven `IDE`/`CA` rule).
|
|
48
|
+
- **Failing test** (an assertion failure or unhandled exception under
|
|
49
|
+
`dotnet test`).
|
|
50
|
+
|
|
51
|
+
### Step 2: Fix by category
|
|
52
|
+
|
|
53
|
+
**Package/restore:** run `dotnet restore` when the lock file is simply
|
|
54
|
+
stale. For a genuine version conflict, check `dotnet list package
|
|
55
|
+
--include-transitive` to see what is pulling in the conflicting version
|
|
56
|
+
before bumping anything by hand. Only pin a package to an explicit
|
|
57
|
+
version for a real, understood reason (a known-bad release, an
|
|
58
|
+
intentional hold) — never to make a conflict disappear without
|
|
59
|
+
understanding it, and say so in the report either way.
|
|
60
|
+
|
|
61
|
+
**Compile error:** read the exact overload/type the compiler expected
|
|
62
|
+
versus what was supplied; fix the call site or the signature, whichever
|
|
63
|
+
is actually wrong relative to the feature's intent — do not change a
|
|
64
|
+
public signature just to make one call site compile if other callers
|
|
65
|
+
would break.
|
|
66
|
+
|
|
67
|
+
**Nullable warning:** fix the actual null path — add a real null check,
|
|
68
|
+
give the value a non-null initializer, or restructure so the compiler's
|
|
69
|
+
flow analysis can see the invariant. Only annotate the type `?` if the
|
|
70
|
+
value can genuinely be null by design. Never resolve the warning with
|
|
71
|
+
the null-forgiving operator (`value!`) as a substitute for an actual
|
|
72
|
+
guard, and never widen the fix into `<Nullable>disable</Nullable>` for
|
|
73
|
+
the file or project.
|
|
74
|
+
|
|
75
|
+
**Analyzer/StyleCop finding:** fix the underlying issue the rule names
|
|
76
|
+
(the real formatting/ordering/pattern it wants). Never add
|
|
77
|
+
`#pragma warning disable` or a `[SuppressMessage(...)]` attribute whose
|
|
78
|
+
only purpose is to make the analyzer stop complaining without addressing
|
|
79
|
+
what it found.
|
|
80
|
+
|
|
81
|
+
**Failing test:** read the assertion failure or stack trace; fix the
|
|
82
|
+
production code if the test correctly caught a real bug, or fix the test
|
|
83
|
+
if its expectation was wrong — state which one you concluded and why in
|
|
84
|
+
the report, never silently delete or skip the test to reach green.
|
|
85
|
+
|
|
86
|
+
### Step 3: Verify
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
dotnet build
|
|
90
|
+
dotnet test
|
|
91
|
+
dotnet format --verify-no-changes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
All must exit 0 before reporting done.
|
|
95
|
+
|
|
96
|
+
### Step 4: Report
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Fixed: NuGet version conflict between Package.A 3.0 and Package.B's
|
|
100
|
+
transitive dependency on Package.A 2.5
|
|
101
|
+
- Root cause: Package.B pinned an older Package.A that Package.A 3.0's
|
|
102
|
+
breaking change conflicted with; updated Package.B instead of
|
|
103
|
+
downgrading Package.A
|
|
104
|
+
- dotnet build/test/format all pass
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
State the root cause in one sentence, not just "fixed the error."
|
|
108
|
+
|
|
109
|
+
## Rules
|
|
110
|
+
|
|
111
|
+
- Find and fix the smallest change that addresses the actual root
|
|
112
|
+
cause — never widen a fix beyond what the failure requires.
|
|
113
|
+
- NEVER add `#pragma warning disable` or `[SuppressMessage(...)]` to
|
|
114
|
+
silence an analyzer/StyleCop finding instead of fixing what it found.
|
|
115
|
+
- NEVER add `<Nullable>disable</Nullable>` (project- or file-wide) or
|
|
116
|
+
reach for the null-forgiving operator (`value!`) as a substitute for
|
|
117
|
+
an actual null-safety fix.
|
|
118
|
+
- NEVER pin or downgrade a NuGet package to route around a real
|
|
119
|
+
incompatibility without understanding and stating why in the report.
|
|
120
|
+
- NEVER delete or skip a failing test to reach a green build.
|
|
121
|
+
|
|
122
|
+
## Red Flags
|
|
123
|
+
|
|
124
|
+
| Rationalization | Why it is wrong |
|
|
125
|
+
|---|---|
|
|
126
|
+
| "I'll add `#pragma warning disable CS8602` around this block" | Silences the finding without fixing the possible-null-dereference it caught; add the actual null check instead |
|
|
127
|
+
| "This nullable warning is annoying, I'll just add `!` here" | The null-forgiving operator tells the compiler to trust you without verifying anything; use it only at a boundary you can justify in a comment, never as a default fix |
|
|
128
|
+
| "I'll just downgrade this package to the version that used to work" | Papers over whatever actually changed without understanding it; check `dotnet list package --include-transitive` and fix the real conflict |
|
|
129
|
+
| "This test keeps failing, I'll mark it `[Fact(Skip = \"flaky\")]`" | Hides a real regression instead of fixing it; find out whether the test or the code is wrong before touching either |
|
|
130
|
+
|
|
131
|
+
## Verification
|
|
132
|
+
|
|
133
|
+
Do not report the fix done until all of the following hold:
|
|
134
|
+
|
|
135
|
+
- `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes`
|
|
136
|
+
all exit 0.
|
|
137
|
+
- The change is the smallest one that addresses the stated root cause —
|
|
138
|
+
no unrelated files touched.
|
|
139
|
+
- No `#pragma warning disable`, `[SuppressMessage(...)]`,
|
|
140
|
+
`<Nullable>disable</Nullable>`, or unexplained null-forgiving operator
|
|
141
|
+
was added as part of the fix.
|
|
142
|
+
- The report states the root cause in one sentence, not just "build now
|
|
143
|
+
passes."
|