@mrciphersmith/keryx 0.3.3 → 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.
Files changed (67) hide show
  1. package/dist/cli.js +125 -51
  2. package/package.json +1 -1
  3. package/src/gdskills/bundled/install-manifest.json +231 -2
  4. package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
  5. package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
  6. package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
  7. package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
  8. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
  9. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
  10. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
  11. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
  12. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
  13. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
  14. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
  15. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
  16. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
  17. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
  18. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
  19. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
  20. package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
  21. package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
  22. package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
  23. package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
  24. package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
  25. package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
  26. package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
  27. package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
  28. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
  29. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
  30. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
  31. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
  32. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
  33. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
  34. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
  35. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
  36. package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
  37. package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
  38. package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
  39. package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
  40. package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
  41. package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
  42. package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
  43. package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
  44. package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
  45. package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
  46. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
  47. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
  48. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
  49. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
  50. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
  51. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
  52. package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
  53. package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
  54. package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
  55. package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
  56. package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
  57. package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
  58. package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
  59. package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
  60. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
  61. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
  62. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
  63. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
  64. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
  65. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
  66. package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
  67. package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
@@ -0,0 +1,77 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "The compiler is complaining about a possible null reference here, fix it",
5
+ "This NuGet package version conflict is blocking my build, resolve it",
6
+ "An analyzer rule is failing on this awaited call in library code",
7
+ "dotnet test just started failing after I touched this class",
8
+ "My project won't restore anymore, something's wrong with the package graph",
9
+ "This CS error is blocking the build, what's the actual fix?",
10
+ "Sort out this StyleCop violation without just turning the rule off"
11
+ ],
12
+ "negative": [
13
+ "npm install is failing with a peer dependency conflict",
14
+ "cargo build is failing for this Rust crate",
15
+ "pip install is failing for this Python project",
16
+ "This Xcode build is failing with a Swift compiler error",
17
+ "Gradle sync is failing for this Kotlin Android project",
18
+ "flutter pub get is failing with a version solving conflict",
19
+ "Implement a new C# feature in the order service",
20
+ "Review this C# diff for async void misuse"
21
+ ]
22
+ },
23
+ "scenarios": [
24
+ {
25
+ "id": "nullable-warning-root-cause",
26
+ "prompt": "The compiler reports CS8602 (possible null reference) on the line right after `orders.TryGetValue(orderId, out var order);` where I then call `order.Total`. How do I fix it correctly?",
27
+ "strictness": "high",
28
+ "expected_behavior": [
29
+ {
30
+ "grader": "judge",
31
+ "rubric": "A correct answer fixes the actual null path -- checking whether TryGetValue found a value (its bool return, or an explicit `order is not null` check) before dereferencing `order.Total` -- rather than silencing CS8602 with the null-forgiving operator (`order!`), a #pragma warning disable, or disabling nullable reference types for the file/project.",
32
+ "pass_criteria": [
33
+ "Fixes the root cause with a concrete null check before the dereference -- e.g. branching on TryGetValue's bool return value, or an explicit `if (order is not null)`/similar check -- shown as actual code, not just described.",
34
+ "Does not use the null-forgiving operator (`order!`) as the fix.",
35
+ "Does not recommend `#pragma warning disable`/`<Nullable>disable</Nullable>` as the fix instead of the null check."
36
+ ],
37
+ "fail_criteria": [
38
+ "Recommends using the null-forgiving operator (e.g. `order!.Total`) to silence CS8602 without an actual null check. Mentioning the null-forgiving operator only to warn against it is not a failure.",
39
+ "Recommends `#pragma warning disable CS8602` or `<Nullable>disable</Nullable>` instead of adding a real null check."
40
+ ]
41
+ }
42
+ ],
43
+ "calibration": {
44
+ "known_right": "`TryGetValue` returns a `bool` telling you whether it actually found something, and CS8602 is correctly pointing out that `order` can still be null on the not-found path even though the compiler can't yet see that you've handled it. Fix it by branching on that return value: `if (orders.TryGetValue(orderId, out var order)) { UseTotal(order.Total); } else { /* handle not-found */ }` -- the compiler's flow analysis then knows `order` is non-null inside the `if` branch, and the warning goes away because you actually handled the null case, not because you told the compiler to stop checking. Don't reach for `order!.Total` here -- that just asserts to the compiler that `order` is non-null without you having verified it, which is exactly the case TryGetValue's bool return exists to let you check safely. And don't add `#pragma warning disable CS8602`/`<Nullable>disable</Nullable>` either -- that turns off the warning everywhere it could apply, not just this one call site.",
45
+ "known_wrong": "Easiest fix: just change `order.Total` to `order!.Total` -- that null-forgiving operator tells the compiler you know what you're doing and it stops flagging CS8602 on that line. You don't need to restructure anything around the `TryGetValue` call or add extra branching just to satisfy the null checker.",
46
+ "vague": "Handle the case where the lookup doesn't find anything, and the null warning should go away.",
47
+ "subtle_wrong": "Check `orders.ContainsKey(orderId)` right before the `TryGetValue` call and only proceed if it returns true -- that clears CS8602 in most cases. If the compiler still complains after adding the ContainsKey check, just add `order!.Total` on that line to finish clearing the warning without restructuring further."
48
+ }
49
+ },
50
+ {
51
+ "id": "no-suppressmessage-analyzer",
52
+ "prompt": "A Roslyn analyzer (CA2007, 'ConfigureAwait') is flagging an `await httpClient.GetAsync(url)` call inside a public method of a shared library project (not application/ASP.NET Core code). How should I fix it?",
53
+ "strictness": "high",
54
+ "expected_behavior": [
55
+ {
56
+ "grader": "judge",
57
+ "rubric": "A correct answer fixes the actual root cause CA2007 is flagging -- adding `.ConfigureAwait(false)` to the awaited call, since this is library code with no captured SynchronizationContext to preserve -- rather than silencing the analyzer with `#pragma warning disable CA2007` or a `[SuppressMessage(...)]` attribute, and rather than disabling the rule globally in the ruleset/.editorconfig as a substitute for the local fix.",
58
+ "pass_criteria": [
59
+ "Fixes the root cause by adding `.ConfigureAwait(false)` to the awaited call, shown concretely (e.g. `await httpClient.GetAsync(url).ConfigureAwait(false)`).",
60
+ "States why this is the correct fix for library code specifically (no need to resume on a captured context, unlike UI/legacy ASP.NET code).",
61
+ "Does not rely on `#pragma warning disable`/`[SuppressMessage(...)]` or a global rule disable as the fix."
62
+ ],
63
+ "fail_criteria": [
64
+ "Recommends suppressing CA2007 with `#pragma warning disable CA2007`, a `[SuppressMessage(...)]` attribute, or disabling the rule in the ruleset/.editorconfig, instead of adding `.ConfigureAwait(false)`. Mentioning suppression only to warn against it is not a failure."
65
+ ]
66
+ }
67
+ ],
68
+ "calibration": {
69
+ "known_right": "CA2007 is telling you exactly what's missing: in shared library code, an awaited call should use `.ConfigureAwait(false)` so it doesn't try to resume on whatever `SynchronizationContext` the caller happened to be running under -- that matters for a library because you don't control or know what kind of caller (UI app, legacy ASP.NET, console app) will end up calling into it. Fix it by adding the call: `await httpClient.GetAsync(url).ConfigureAwait(false);`. That's the actual fix the analyzer is asking for -- don't reach for `#pragma warning disable CA2007` or a `[SuppressMessage(\"Reliability\", \"CA2007\")]` attribute instead, and don't turn the rule off globally in the project's ruleset/.editorconfig either; both of those just stop the analyzer from telling you about every future missing `ConfigureAwait(false)` in this library, rather than fixing the one it already found.",
70
+ "known_wrong": "The fastest way to clear this is to add `[SuppressMessage(\"Reliability\", \"CA2007:DoNotDirectlyAwaitATask\")]` above the method -- that tells the analyzer to stop flagging this specific spot without you needing to touch the actual await call. If it keeps popping up elsewhere in the library, you can also just disable CA2007 in the project's `.editorconfig` so it stops being noisy across the board.",
71
+ "vague": "Handle the ConfigureAwait situation properly for library code so the analyzer stops complaining.",
72
+ "subtle_wrong": "Add `#pragma warning disable CA2007` right above this specific await call and `#pragma warning restore CA2007` right after it -- that scopes the suppression tightly to just this one line rather than disabling it project-wide, which feels like a reasonable middle ground without needing to add `.ConfigureAwait(false)` everywhere the analyzer might eventually flag."
73
+ },
74
+ "anti_patterns": ["SuppressMessage"]
75
+ }
76
+ ]
77
+ }
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: dotnet-code-review
3
+ description: "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."
4
+ triggers:
5
+ - "review this C# diff for async void misuse"
6
+ - "check this .NET change for sync-over-async deadlock risk"
7
+ - "review this C# pull request for swallowed exceptions"
8
+ - "any missing IDisposable cleanup in this C# change"
9
+ - "check nullable annotation gaps in this C# diff"
10
+ - "review this ASP.NET Core diff for DI lifetime mistakes"
11
+ metadata:
12
+ origin: authored
13
+ category: review
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # .NET code review
20
+
21
+ Read-only review of a C#/.NET change for async-safety, resource-safety,
22
+ and idiom risks specific to C#/.NET: `async void` misuse, sync-over-async
23
+ blocking, blanket exception handling, missing disposal, nullable-
24
+ annotation gaps, and DI lifetime mistakes. This skill never edits code —
25
+ it reports findings. `rules/coding-style.mdc`, `rules/patterns.mdc`, and
26
+ `rules/security.mdc` are the rule set findings are checked against.
27
+
28
+ ## Workflow
29
+
30
+ ### Step 1: Scope the review
31
+
32
+ 1. Identify the changed files (`git diff` against the review base) —
33
+ review only `*.cs` files in the diff, not the whole repository.
34
+ 2. Read enough of the surrounding, unchanged code to know whether a
35
+ flagged pattern is new in this diff or pre-existing; note pre-existing
36
+ issues separately from ones the diff introduces.
37
+
38
+ ### Step 2: Check each changed member against the focus list
39
+
40
+ **Async**
41
+ - A method declared `async void` that is not a genuine event handler —
42
+ flag it; any exception it throws bypasses normal `try`/`catch` at the
43
+ call site and crashes the process instead.
44
+ - A blocking `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()` call on
45
+ a `Task` from otherwise-synchronous code — flag as sync-over-async
46
+ deadlock risk.
47
+ - `ValueTask`/`ValueTask<T>` awaited or converted more than once, or
48
+ stored for later awaiting — flag it; a `ValueTask` supports exactly one
49
+ await/consumption.
50
+
51
+ **Exception handling**
52
+ - A broad `catch (Exception)` (or bare `catch`) that swallows the
53
+ exception (no rethrow, no logging, no narrowing to the specific type
54
+ actually expected) — flag it; the diff should catch the specific
55
+ exception type a call can throw, or let an unexpected one propagate.
56
+ - `catch (Exception ex) { }` with an empty or no-op body — flag as a
57
+ silent failure regardless of whether the outer catch is otherwise
58
+ reasonable.
59
+
60
+ **Nullability**
61
+ - A new public parameter/return type with no nullable annotation that is
62
+ later dereferenced without a null check, or a `!` null-forgiving
63
+ operator used to bypass a nullability warning without a comment
64
+ justifying why the value is provably non-null there — flag either.
65
+
66
+ **Resources**
67
+ - An `IDisposable`/`IAsyncDisposable` local or field created without a
68
+ `using`/`await using` and with no corresponding `Dispose`/`DisposeAsync`
69
+ call on the owning type — flag it as a resource leak.
70
+
71
+ **Dependency injection**
72
+ - A `Scoped` service (most commonly a `DbContext`) captured directly into
73
+ a `Singleton`'s field or constructor parameter — flag it; the
74
+ `Singleton` will hold the first-resolved instance for the app's whole
75
+ lifetime instead of a fresh one per request/scope.
76
+
77
+ ### Step 3: Report
78
+
79
+ For each finding: file:line, the pattern, why it matters (crash, deadlock,
80
+ leak, silent failure), and the fix direction — but do not apply it.
81
+
82
+ ```
83
+ src/Orders/OrderNotifier.cs:18 — async void SendAsync() is not an event
84
+ handler. Risk: any exception it throws bypasses the caller's try/catch
85
+ and crashes the process. Fix direction: return Task and await it from
86
+ the caller.
87
+ ```
88
+
89
+ ## Rules
90
+
91
+ - NEVER edit code — findings and fix direction only.
92
+ - Flag `async void` misuse, sync-over-async blocking, blanket exception
93
+ swallowing, nullable-annotation gaps, missing disposal, and Scoped-
94
+ into-Singleton DI captures; do not report generic style nits already
95
+ covered by `dotnet format`/analyzers (those are noise here).
96
+ - Distinguish a finding the diff introduces from a pre-existing one in
97
+ code the diff merely touches.
98
+ - When a suspected deadlock or leak is not certain from reading alone,
99
+ say "reproduce under load / run with a resource profiler to confirm"
100
+ rather than asserting it with certainty absent evidence.
101
+
102
+ ## Red Flags
103
+
104
+ | Rationalization | Why it is wrong |
105
+ |---|---|
106
+ | "This `.Result` call is on a path that never actually deadlocks in practice" | "Never in practice" is not a guarantee; sync-over-async deadlocks are timing- and load-dependent, and the fix (making the caller `async`) costs little |
107
+ | "The blanket `catch (Exception)` here is fine, it's just cleanup code" | Cleanup code failing silently still hides a real bug; catch the specific exception type expected, or log and rethrow |
108
+ | "I'll just fix the missing `using` myself since it's a one-line change" | This skill is read-only; report the finding and its fix direction, do not edit the file |
109
+ | "Injecting the DbContext straight into this Singleton is fine, it's only used for one query" | Every future request reuses that same first-resolved DbContext instance regardless of scope; the lifetime mismatch is the bug, not how it's currently used |
110
+
111
+ ## Verification
112
+
113
+ Do not report the review done until all of the following hold:
114
+
115
+ - Every changed `*.cs` file in the diff was read, not just files named in
116
+ the PR description.
117
+ - Every finding names a concrete file:line, the specific risk category
118
+ from Step 2, and a fix direction.
119
+ - No source file was modified by this review.
120
+ - Findings distinguish diff-introduced issues from pre-existing ones in
121
+ touched files.
@@ -0,0 +1,77 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Check this pull request for fire-and-forget async methods that could crash",
5
+ "Does this C# change block a thread waiting on a Task anywhere?",
6
+ "Look over this diff for a database context leaking into a long-lived service",
7
+ "Audit this .NET change for exception handling that hides real failures",
8
+ "Any missing using/await using in this controller change?",
9
+ "Review this code change for nullable annotation holes before merge",
10
+ "Take a look at this diff, read-only, for resource cleanup issues"
11
+ ],
12
+ "negative": [
13
+ "Review this Go diff for goroutine leaks and data races",
14
+ "Review this Python code for SQL injection",
15
+ "Review this TypeScript diff for missing null checks",
16
+ "Review this Swift diff for retain cycles and force-unwraps",
17
+ "Review this Kotlin diff for coroutine scope leaks",
18
+ "Review this Flutter diff for setState calls after dispose",
19
+ "Implement a new C# endpoint that queries the order repository",
20
+ "dotnet test is failing on this project, fix it"
21
+ ]
22
+ },
23
+ "scenarios": [
24
+ {
25
+ "id": "async-void-review",
26
+ "prompt": "Review this C# diff: a new method was added as `public async void ProcessOrderAsync(Order order)` on OrderController, and it's called from a regular controller action (not a UI event handler). What do you find?",
27
+ "strictness": "high",
28
+ "expected_behavior": [
29
+ {
30
+ "grader": "judge",
31
+ "rubric": "A correct answer identifies async void as the problem on a method that is not a genuine event handler, explains the concrete risk (an exception it throws bypasses the caller's try/catch and crashes the process, or is otherwise unobservable, instead of failing the request normally), and reports this as a finding with a fix direction (change it to return Task and have the caller await it) -- it never actually edits or claims to have edited the code, since this skill is read-only.",
32
+ "pass_criteria": [
33
+ "Identifies `async void` on ProcessOrderAsync as the problem, noting it is not a genuine event handler.",
34
+ "States the concrete risk: an exception thrown inside bypasses the caller's exception handling and crashes the process (or is otherwise silently lost) instead of failing the request the normal way.",
35
+ "Gives the fix direction (make it Task-returning and have the caller await it) as a finding, with no code change applied or described as already made."
36
+ ],
37
+ "fail_criteria": [
38
+ "Actually edits, patches, or claims to have already made even a partial or proof-of-concept code change (this skill is strictly read-only: findings and fix direction only)."
39
+ ]
40
+ }
41
+ ],
42
+ "calibration": {
43
+ "known_right": "Finding: `ProcessOrderAsync` is declared `async void`, but it's called from a regular controller action, not a UI event handler -- the one legitimate use of `async void`. That's a real problem: if anything inside it throws (a downstream call failing, a validation error), the exception can't be caught by the caller's try/catch at all -- it escapes the normal async exception-propagation path and crashes the process instead of surfacing as an ordinary failed request. Fix direction: change the signature to `async Task ProcessOrderAsync(Order order)` and have the calling action `await` it, so any exception it throws propagates normally and can be handled like any other failure in the request pipeline. I'm reporting this as a finding rather than making the change myself, since this review is read-only.",
44
+ "known_wrong": "`async void` here is a known Go-to for fire-and-forget style calls, so I went ahead and fixed it directly instead of just flagging it: I changed the signature to `async Task` and updated the controller action to await it. The change is already in the diff, build passes, so this should be fine to merge as-is.",
45
+ "vague": "That async void method looks like it could cause some issues -- might be worth double-checking before this merges.",
46
+ "subtle_wrong": "Finding: `ProcessOrderAsync` is `async void`, which is generally discouraged outside event handlers -- worth fixing at some point. I went ahead and made a small proof-of-concept edit to the signature so you can see the shape of the change, though the calling action would still need to be updated to await it before this compiles cleanly."
47
+ },
48
+ "anti_patterns": ["async void"]
49
+ },
50
+ {
51
+ "id": "swallowed-exception-review",
52
+ "prompt": "Review this C# diff: a new block was added around a database call as `catch (Exception) { }` with no logging, no rethrow, and no narrower exception type. What's wrong here?",
53
+ "strictness": "high",
54
+ "expected_behavior": [
55
+ {
56
+ "grader": "judge",
57
+ "rubric": "A correct answer identifies the empty catch (Exception) block as silently swallowing any failure from the database call -- including ones that are not the specific, expected failure mode -- and flags this as a finding: a real failure (connectivity loss, a constraint violation, a timeout) disappears with no log, no rethrow, and no signal to the caller or to operations. It gives a fix direction (catch the specific expected exception type, or log and rethrow) as a finding, without applying it, and does not accept 'it's just cleanup/best-effort code' as an excuse to leave it empty.",
58
+ "pass_criteria": [
59
+ "Identifies the empty/blanket catch (Exception) block as the issue -- swallowing the failure with no log, rethrow, or narrower type.",
60
+ "States the concrete consequence: a real underlying failure from the database call is hidden with no signal to the caller or to operations/logs.",
61
+ "Names a fix direction (catch the specific expected exception type, or log and rethrow) as a finding, not as a code change already made."
62
+ ],
63
+ "fail_criteria": [
64
+ "Dismisses the empty catch (Exception) as acceptable because the surrounding code is 'just cleanup' or 'best-effort', instead of requiring it to at least log, rethrow, or narrow the caught type."
65
+ ]
66
+ }
67
+ ],
68
+ "calibration": {
69
+ "known_right": "Finding: this new `catch (Exception) { }` around the database call swallows absolutely everything -- a lost connection, a constraint violation, a timeout, anything -- with no log statement, no rethrow, and no narrowing to the specific exception type actually expected here. That means a real failure in this database call now disappears completely: nothing shows up in logs, nothing propagates to the caller, and there's no signal anything went wrong at all, which makes this exact bug invisible in production until someone notices missing data or a silent no-op. Fix direction: either catch the specific exception type this call can actually throw and handle it deliberately, or if the intent really is 'don't let this fail the whole operation', at minimum log the exception before continuing so there's a record of what happened. I'm flagging this as a finding rather than fixing it myself, since this review is read-only.",
70
+ "known_wrong": "This is fine -- it's just wrapping a database call defensively so one bad call doesn't take down the whole request, and an empty `catch (Exception)` block is a common enough pattern for that. No changes needed here; broad catches like this are pretty normal around I/O calls that might occasionally fail.",
71
+ "vague": "That empty catch block looks a little risky -- might be worth reconsidering before this merges.",
72
+ "subtle_wrong": "Finding: the `catch (Exception) { }` around the database call is pretty broad and doesn't log anything, but since this is inside what looks like a best-effort background sync path rather than a user-facing request, silently swallowing failures here is probably acceptable -- if it starts causing real problems you'd notice via the missing data downstream anyway."
73
+ },
74
+ "anti_patterns": ["catch (Exception)"]
75
+ }
76
+ ]
77
+ }
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: dotnet-implementation
3
+ description: "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
+ triggers:
5
+ - "implement this feature in C#"
6
+ - "add a new endpoint to this ASP.NET Core service"
7
+ - "register this service in the DI container"
8
+ - "design this async method's Task/ValueTask return type"
9
+ - "add nullable annotations to this new public API"
10
+ - "implement this as a record instead of a class"
11
+ - "write an EF Core query for this feature"
12
+ metadata:
13
+ origin: authored
14
+ category: implement
15
+ version: "1.0.0"
16
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
17
+ license: "MIT"
18
+ ---
19
+
20
+ # .NET implementation
21
+
22
+ Implement or extend a feature in a C#/.NET codebase: type design (record
23
+ vs class, primary constructors), nullable reference types, async/await
24
+ shape, dependency injection lifetimes, and EF Core query design. Scoped
25
+ to C#/.NET specifically — `rules/coding-style.mdc`, `rules/patterns.mdc`,
26
+ and `rules/security.mdc` carry the full stack-specific rule set this
27
+ skill's checklist is built from; read them before writing code, not just
28
+ this summary.
29
+
30
+ ## Workflow
31
+
32
+ ### Step 1: Discover the project's own conventions
33
+
34
+ 1. Read the `.csproj`/`Directory.Build.props` for the target framework
35
+ (`<TargetFramework>`) and whether `<Nullable>enable</Nullable>` and
36
+ `<ImplicitUsings>enable</ImplicitUsings>` are already set — do not use
37
+ a language feature the project's target framework does not support.
38
+ 2. Find the existing layout: where DI registration happens
39
+ (`Program.cs`, a `ServiceCollectionExtensions` class), where
40
+ interfaces live relative to their implementations, and whether the
41
+ project already uses records for DTOs/commands or sticks to classes.
42
+ 3. Read 1-2 neighboring files in the area you are touching for: async
43
+ patterns already in use (`ConfigureAwait` usage, `Task` vs
44
+ `ValueTask`), the data-access approach (EF Core, Dapper, a repository
45
+ abstraction), and the existing test project's naming so your new code
46
+ stays testable the same way.
47
+
48
+ ### Step 2: Design before writing
49
+
50
+ - Decide record vs class per `rules/coding-style.mdc`: identity-by-value
51
+ (DTOs, commands, events) is a `record`; identity-by-reference or
52
+ mutable-over-time state is a `class`.
53
+ - Trace nullability: which parameters/returns can genuinely be absent
54
+ (`?`), and which public members are promising callers a non-null
55
+ value. Do not leave a public API unannotated "for now."
56
+ - For any async method, decide `Task` vs `Task<T>` vs `ValueTask<T>`
57
+ (`rules/patterns.mdc`) before writing the signature, and confirm the
58
+ method is never `async void` unless it is a genuine event handler.
59
+ - For any new service registered in DI, pick its lifetime
60
+ (Singleton/Scoped/Transient) based on what state it holds and what it
61
+ depends on — check whether it will end up depending on something
62
+ `Scoped` (like a `DbContext`) before defaulting to `Singleton`.
63
+
64
+ ### Step 3: Implement
65
+
66
+ 1. Use a primary constructor for simple dependency-assignment
67
+ constructors; keep an explicit constructor body once real validation
68
+ or setup logic is needed.
69
+ 2. Apply `ArgumentNullException.ThrowIfNull(value)` at public entry
70
+ points for required reference-typed parameters.
71
+ 3. Thread `CancellationToken` through async methods that call into I/O
72
+ (HTTP, database, file) so callers can cancel; accept it as the last
73
+ parameter, often with a default of `default`.
74
+ 4. Wrap every `IDisposable`/`IAsyncDisposable` local or field in
75
+ `using`/`await using`, or have the owning type implement
76
+ `IDisposable`/`IAsyncDisposable` itself (`rules/patterns.mdc`).
77
+ 5. For EF Core, keep filtering/projection in the `IQueryable<T>` chain
78
+ (`.Where`, `.Select`) so it translates to SQL, and materialize with
79
+ `.ToListAsync()`/`.SingleOrDefaultAsync()` at the point results are
80
+ actually needed, not earlier.
81
+
82
+ ### Step 4: Verify
83
+
84
+ ```bash
85
+ dotnet build
86
+ dotnet test
87
+ dotnet format --verify-no-changes
88
+ ```
89
+
90
+ Fix findings at the root cause per `rules/security.mdc` and
91
+ `rules/coding-style.mdc`; a build/analyzer failure at this step is a
92
+ signal to fix the implementation, not to reach for `dotnet-build-fix`'s
93
+ scope unless the failure is purely a build/package/target-framework
94
+ problem unrelated to the feature logic.
95
+
96
+ ### Step 5: Report
97
+
98
+ ```
99
+ Implemented: src/Orders/OrderService.cs, src/Orders/IOrderRepository.cs
100
+ - New OrderService registered Scoped, depends on IOrderRepository
101
+ - dotnet build/test/format all pass
102
+ ```
103
+
104
+ ## Rules
105
+
106
+ - Never write `async void` except for a genuine UI/event-handler method.
107
+ - Never call `.Result`/`.Wait()`/`.GetAwaiter().GetResult()` on a `Task`
108
+ from otherwise-synchronous code — make the caller `async` instead.
109
+ - Never leave a new public API's nullability unannotated; every
110
+ parameter and return type states whether `null` is a valid value.
111
+ - Never capture a `Scoped` service (like a `DbContext`) directly into a
112
+ `Singleton`'s field — resolve it per-use via `IServiceScopeFactory`.
113
+
114
+ ## Red Flags
115
+
116
+ | Rationalization | Why it is wrong |
117
+ |---|---|
118
+ | "I'll make this handler `async void`, it's just a quick fire-and-forget call" | Only a real event handler gets `async void`; anywhere else, an exception it throws cannot be awaited or caught and crashes the process instead |
119
+ | "I'll just call `.Result` here, this one path isn't really async anyway" | Blocking on a `Task` from sync code is sync-over-async and can deadlock the moment the awaited call needs to resume on a context the blocking thread occupies |
120
+ | "Nullable warnings are noisy, I'll add `<Nullable>disable</Nullable>` to this file" | Turns off the compiler's null-safety net for the whole file instead of fixing the actual null path the warning is pointing at |
121
+ | "I'll inject this Scoped DbContext straight into my Singleton cache service" | Captures the first request's `DbContext` instance for the app's whole lifetime; resolve it per-use through `IServiceScopeFactory` instead |
122
+
123
+ ## Verification
124
+
125
+ Do not report the work done until all of the following hold:
126
+
127
+ - `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes`
128
+ all exit 0.
129
+ - Every new async method is `Task`/`Task<T>`/`ValueTask<T>`-returning,
130
+ never `async void`, unless it is a genuine event handler.
131
+ - Every new public parameter/return type has an explicit nullability
132
+ annotation matching its actual contract.
133
+ - Every new `IDisposable`/`IAsyncDisposable` local, field, or owning
134
+ type is wrapped in `using`/`await using` or disposes it correctly.
@@ -0,0 +1,76 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Build a new order-placement endpoint in this ASP.NET Core API",
5
+ "I need a background email sender that doesn't block the caller in C#",
6
+ "How should I wire this new repository into the DI container?",
7
+ "Should this order confirmation type be a record or a class?",
8
+ "Add cancellation support to this async database call",
9
+ "What's the right way to inject a database context into a singleton cache service?",
10
+ "Write an EF Core query for fetching recent orders with their line items"
11
+ ],
12
+ "negative": [
13
+ "Implement this feature in Java using Spring Boot's @Service annotation",
14
+ "Add a Go worker pool under cmd/worker that bounds concurrency with errgroup",
15
+ "Implement this feature in a Python FastAPI service with Pydantic models",
16
+ "Implement this SwiftUI view with @State and a binding for the text field",
17
+ "Add this Jetpack Compose composable for the account settings screen",
18
+ "Implement this Flutter widget as a StatefulWidget with a form field",
19
+ "Review this C# diff for async void misuse and swallowed exceptions",
20
+ "dotnet build is failing with a NuGet version conflict, fix it"
21
+ ]
22
+ },
23
+ "scenarios": [
24
+ {
25
+ "id": "async-void-avoidance",
26
+ "prompt": "I need to send an order-shipped email notification from OrderService without making the caller of PlaceOrder wait for the email to go out. What's the right way to write this method in C#?",
27
+ "strictness": "high",
28
+ "expected_behavior": [
29
+ {
30
+ "grader": "judge",
31
+ "rubric": "A correct answer keeps the notification method Task-returning (never async void, since this is not an event handler) so exceptions it throws remain observable, and achieves 'don't make the caller wait' some other way -- by not awaiting the call at the call site while still logging/observing any exception (e.g. via a continuation, a background queue, or an IHostedService), rather than declaring the method itself async void to get fire-and-forget behavior for free.",
32
+ "pass_criteria": [
33
+ "States that the notification method itself should remain Task-returning (async Task), not async void.",
34
+ "Gives a concrete way for the caller to avoid waiting on it without making the method async void -- e.g. not awaiting the call at the call site while still observing/logging a failure, queuing it to a background worker/IHostedService, or an equivalent named mechanism, not just 'handle it asynchronously'.",
35
+ "States the concrete risk async void carries here: an exception thrown inside it cannot be caught by the caller's try/catch and crashes the process instead of surfacing as a normal failure."
36
+ ],
37
+ "fail_criteria": [
38
+ "Recommends declaring the notification method `async void` to get fire-and-forget behavior, on the grounds that the caller shouldn't wait for it and it isn't returning a value. Mentioning async void only to explain why it must be avoided here is not a failure."
39
+ ]
40
+ }
41
+ ],
42
+ "calibration": {
43
+ "known_right": "Keep `SendOrderShippedEmailAsync` as `async Task`, not `async void` -- it isn't a UI event handler, so making it `async void` would mean any exception it throws (a failed SMTP call, a malformed address) bypasses normal exception handling entirely and crashes the process instead of being something the caller, or anything else, can catch. To avoid making `PlaceOrder` wait for the email, don't await the call inline in the hot path -- either hand it off to a background queue/`IHostedService` that processes notifications out-of-band, or if you do call it fire-and-forget style from `PlaceOrder`, capture the resulting `Task` and attach a continuation that logs any exception (`SendOrderShippedEmailAsync(order).ContinueWith(t => Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted)`) rather than discarding it silently. Either way, the method signature itself stays `Task`-returning so its failures stay observable.",
44
+ "known_wrong": "Just make it `async void SendOrderShippedEmailAsync(Order order)` -- that's literally what `async void` is for: a method you call and don't wait on. Since `PlaceOrder` doesn't need the result and shouldn't block on the email going out, `async void` gives you fire-and-forget with zero extra plumbing; call it like `SendOrderShippedEmailAsync(order);` with no `await` and move on.",
45
+ "vague": "Handle the email sending asynchronously so it doesn't block the order placement, and make sure errors are dealt with properly.",
46
+ "subtle_wrong": "Declare it `async void SendOrderShippedEmailAsync(Order order)` since the caller genuinely doesn't need to await it or get a result back -- `async void` is designed exactly for this 'call it and move on' case, unlike UI event handlers being the only place people usually see it. Just make sure the method itself has a top-level `try/catch` inside it that logs any failure, so you're not completely blind to errors even without the caller being able to observe them."
47
+ },
48
+ "anti_patterns": ["async void"]
49
+ },
50
+ {
51
+ "id": "scoped-into-singleton",
52
+ "prompt": "I have a Singleton `OrderCacheService` in my ASP.NET Core app that occasionally needs to read from the database. Can I just inject my Scoped `OrderDbContext` into its constructor?",
53
+ "strictness": "high",
54
+ "expected_behavior": [
55
+ {
56
+ "grader": "judge",
57
+ "rubric": "A correct answer says no -- a Scoped OrderDbContext must not be captured directly into a Singleton's constructor/field -- and gives the concrete fix: inject IServiceScopeFactory (or IServiceProvider) into the Singleton and resolve a fresh Scoped OrderDbContext per use via CreateScope(), because a direct capture would pin the first-resolved DbContext instance for the entire application lifetime instead of getting a fresh one per request/operation.",
58
+ "pass_criteria": [
59
+ "States plainly that injecting/capturing the Scoped OrderDbContext directly into the Singleton's constructor is wrong.",
60
+ "Names the concrete fix: inject IServiceScopeFactory (or IServiceProvider) and call CreateScope() to resolve a fresh Scoped OrderDbContext each time it's needed, not once at construction.",
61
+ "States the concrete consequence of getting this wrong: the captured DbContext instance would be reused for the app's entire lifetime instead of being fresh per operation."
62
+ ],
63
+ "fail_criteria": [
64
+ "Approves injecting/capturing the Scoped OrderDbContext directly into the Singleton's constructor or field, with no per-use scope resolution."
65
+ ]
66
+ }
67
+ ],
68
+ "calibration": {
69
+ "known_right": "No -- don't inject `OrderDbContext` directly into `OrderCacheService`'s constructor. `OrderDbContext` is Scoped, meaning the container hands out a fresh instance per request/scope, but a Singleton is constructed exactly once for the app's whole lifetime; if you capture the Scoped instance at construction time, every later use of that DbContext -- across every request from then on -- reuses whatever instance was live the moment the Singleton was built, which is both wrong (stale state, no per-request isolation) and eventually throws once that DbContext is disposed at the end of its original scope. The fix is to inject `IServiceScopeFactory` into `OrderCacheService` instead, and whenever it actually needs to hit the database, call `using var scope = scopeFactory.CreateScope();` and resolve `OrderDbContext` from `scope.ServiceProvider` right there -- that gives you a properly-scoped, disposed-when-done instance each time, with no lifetime mismatch.",
70
+ "known_wrong": "Sure, just add `OrderDbContext` as a constructor parameter on `OrderCacheService` like any other dependency -- the DI container will resolve it and inject it same as it would for a Scoped or Transient service. It's a bit of an edge case with the lifetimes technically being different, but in practice it usually works fine since the container doesn't stop you from doing it, and it saves you from having to bring in `IServiceScopeFactory` just for one occasional database read.",
71
+ "vague": "That's a lifetime mismatch you'll want to handle carefully -- make sure the database access in the Singleton is set up the right way.",
72
+ "subtle_wrong": "Technically you can register `OrderDbContext` as Scoped and still take it as a constructor parameter on the Singleton -- the container will just resolve it once at startup and hand `OrderCacheService` that instance, and since it's only used for occasional reads rather than every request, the staleness usually isn't a big deal in practice. If you want to be a bit safer you could add a comment noting the DbContext is effectively long-lived here, but a full `IServiceScopeFactory`/`CreateScope()` setup is probably more machinery than this occasional-read case needs."
73
+ }
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: dotnet-testing
3
+ description: "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."
4
+ triggers:
5
+ - "write xUnit tests for this C# class"
6
+ - "add Theory/InlineData cases for this method"
7
+ - "fix this failing dotnet test"
8
+ - "mock this HTTP client with Moq"
9
+ - "write async test methods for this service"
10
+ - "add NUnit TestCase coverage for this validator"
11
+ metadata:
12
+ origin: authored
13
+ category: test
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # .NET testing
20
+
21
+ Write, extend, or fix a C#/.NET test project's xUnit/NUnit test suite:
22
+ `Fact`/`Theory` (or `Test`/`TestCase`) structure, async test methods, and
23
+ disciplined mocking with Moq/NSubstitute. `rules/testing.mdc` carries the
24
+ full rule set this skill's checklist is built from — read it, not just
25
+ this summary, before writing tests.
26
+
27
+ ## Workflow
28
+
29
+ ### Step 1: Discover the project's test conventions
30
+
31
+ 1. Identify the test framework already in use (xUnit vs NUnit) from the
32
+ test project's package references — match it; do not introduce a
33
+ second framework.
34
+ 2. Find the layout: a mirrored `*.Tests` project, existing naming
35
+ pattern (`MethodName_Scenario_ExpectedResult`), and whether
36
+ FluentAssertions or the framework's own `Assert` is already the house
37
+ style.
38
+ 3. Read 1-2 neighboring test files for: how mocks are constructed (Moq
39
+ vs NSubstitute), which layer they mock at (a repository interface? an
40
+ `HttpClient` wrapper?), and any shared fixture
41
+ (`IClassFixture<T>`/`WebApplicationFactory<TEntryPoint>`) already in
42
+ use.
43
+
44
+ ### Step 2: Plan test cases
45
+
46
+ **Methods:** happy path, edge cases (null/empty/default-value inputs),
47
+ exception cases (assert the specific exception type and, where relevant,
48
+ its message/property), boundary values.
49
+
50
+ **Table-driven:** use `[Theory]`+`[InlineData(...)]` (xUnit) or
51
+ `[Test]`+`[TestCase(...)]` (NUnit) for one method exercised across
52
+ several input/expected-output pairs; reach for `[MemberData]`/a
53
+ `TestCaseSource` when a case needs a non-primitive argument.
54
+
55
+ **Async code:** a test method covering `async` production code is
56
+ itself `async Task`, never `async void`.
57
+
58
+ **Mocking boundary (the part most likely to go wrong):** identify which
59
+ collaborators are external seams (HTTP client, repository/database,
60
+ clock, message publisher) — those get mocked. Anything that is the unit
61
+ under test's own internal logic, split into a private helper or another
62
+ method of the same class for readability, is exercised for real, not
63
+ mocked out.
64
+
65
+ ### Step 3: Write
66
+
67
+ 1. Create/extend the test file at the project's own convention path
68
+ (mirrored `*.Tests` project, matching namespace).
69
+ 2. Construct mocks only for the external seam(s) identified in Step 2,
70
+ via `Mock<IThing>` (Moq) or `Substitute.For<IThing>()` (NSubstitute).
71
+ 3. Use `[Theory]`/`[TestCase]` tables for multi-case coverage instead of
72
+ copy-pasted near-identical test methods.
73
+ 4. Await the call under test in every async test; never block with
74
+ `.Result`/`.Wait()`.
75
+ 5. Verify a mock interaction (`mock.Verify(...)`) only when the
76
+ occurrence itself is part of the contract being tested.
77
+
78
+ ### Step 4: Run and fix
79
+
80
+ ```bash
81
+ dotnet test
82
+ ```
83
+
84
+ Fix failing tests (max 3 iterations) — fix the test, not the source
85
+ under test, unless the test itself has correctly caught a real bug (say
86
+ so in the report rather than silently changing production code).
87
+
88
+ ### Step 5: Report
89
+
90
+ ```
91
+ Generated: src/Orders.Tests/OrderServiceTests.cs
92
+ - 7 test cases (4 via Theory/InlineData), all passing
93
+ - Mocked IOrderRepository (external seam); OrderService's own
94
+ validation logic exercised directly, not mocked
95
+ ```
96
+
97
+ ## Rules
98
+
99
+ - ALWAYS match the project's existing framework, naming, and assertion
100
+ conventions found in Step 1, not a different project's style.
101
+ - NEVER mock an internal collaborator that is part of what the test is
102
+ meant to verify — only mock a genuine external seam (HTTP, database,
103
+ filesystem, clock, message bus).
104
+ - NEVER use `async void` for a test method, or block on `.Result`/
105
+ `.Wait()` instead of `await`ing the call under test.
106
+ - NEVER synchronize with `Thread.Sleep`/`Task.Delay` to wait for
107
+ background/async work — await the real `Task` or a real
108
+ synchronization primitive.
109
+
110
+ ## Red Flags
111
+
112
+ | Rationalization | Why it is wrong |
113
+ |---|---|
114
+ | "I'll mock the private validation helper too, it's easier than setting up real inputs" | That helper is part of the unit under test; mocking it proves only that your mock returns what you told it to, not that the real logic works |
115
+ | "I'll write this test method as `async void`, it's simpler" | The test runner cannot await it, so a failing assertion or thrown exception inside it is silently lost instead of failing the test |
116
+ | "I'll add a short `Task.Delay(200)` so the background operation finishes before I assert" | Non-deterministic and flaky under load; await the actual `Task` or a real completion signal instead |
117
+ | "I'll just check `Assert.NotNull(result)` for the error case instead of the specific exception type" | Loses exactly which failure occurred; assert the specific exception type (and relevant properties/message) so a regression in *which* error occurs is caught |
118
+
119
+ ## Verification
120
+
121
+ Do not report the work done until all of the following hold:
122
+
123
+ - The test file sits at the project's own convention path, matching the
124
+ framework and naming style read in Step 1.
125
+ - `dotnet test` exits 0 with every generated test passing.
126
+ - Every mock constructed in the new/changed tests is for a genuine
127
+ external seam, not an internal collaborator of the unit under test.
128
+ - `git status` shows only test files added or modified; no source file
129
+ under test changed (unless a real bug the test caught was fixed and
130
+ called out explicitly in the report).