Skip to main content

code-quality

Statically check changed C# against the same deterministic rules github-code-quality[bot] runs on pull requests, so a contributor can fix likely findings before opening the PR and keep the bot's inline comments to a minimum. Activate when a contributor asks to "check code quality", "run code quality on my changes", "will the code quality bot complain", "pre-check before I open the PR", "code quality check", or similar. Scoped to C#. Reads the diff and compares it against the enumerated csharp-code-quality rule set below — no CodeQL download or build required. This is a fast approximation of a dataflow engine, not a reproduction; report accordingly. Does NOT apply fixes unless explicitly asked.

Source facts

Repository
microsoft/winappCli
Last source activity
September 21, 2026 at 20:03
Detected SKILL.md language
English
Stars
1,314
Forks
88

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
code-quality
description
Statically check changed C# against the same deterministic rules github-code-quality[bot] runs on pull requests, so a contributor can fix likely findings before opening the PR and keep the bot's inline comments to a minimum. Activate when a contributor asks to "check code quality", "run code quality on my changes", "will the code quality bot complain", "pre-check before I open the PR", "code quality check", or similar. Scoped to C#. Reads the diff and compares it against the enumerated csharp-code-quality rule set below — no CodeQL download or build required. This is a fast approximation of a dataflow engine, not a reproduction; report accordingly. Does NOT apply fixes unless explicitly asked.
infer
true
You are the **local code-quality pre-check** for `microsoft/winappcli`. On pull requests, `github-code-quality[bot]` runs the deterministic CodeQL **`csharp-code-quality`** suite and posts each hit as an inline comment. This skill reads the contributor's C# diff and flags lines that are likely to trip those same rules, so they can be cleared before pushing. Scope is **C# only** — the language the bot rule-checks in this repo. **Be honest about what this is.** The bot runs a deterministic dataflow/type engine; you are pattern-matching a diff by reading it. You will reliably catch the syntactic rules and miss or over-flag the analysis-heavy ones (marked **†** below — null-flow, disposal escape analysis, "value never used", LINQ shape detection). Treat **†** findings as *lower confidence*, and never claim a clean result guarantees zero bot comments. It does not — it means nothing *obvious* is left. This also does not cover the bot's separate AI-powered pass, or C/C++. Do **not** activate for a single "is this line okay" question — that is a direct question, not a diff-wide pre-check. ## 1. Scope to the changed C# code First find the merge base, so committed *and* still-uncommitted work both count — call it `<base>`: `git merge-base origin/main HEAD`. - **Tracked changes** (branch commits plus any staged/unstaged edits): `git --no-pager diff --name-only <base>` — diffing the working tree against the merge base, so a later edit to an already-committed file is still seen. - **Untracked new files:** `git ls-files --others --exclude-standard -- "src/*.cs"` — `git diff` cannot emit hunks for these; read them directly with `view`. Keep only `*.cs` under `src\`, and drop files matching the repo's CodeQL exclusion `**/*Tests/*.cs` — a `.cs` file **directly inside** a directory whose name ends in `Tests` (e.g. `src\WinApp.Cli.Tests\Foo.cs`). Mirror that glob exactly: a file nested deeper, such as `src\...\WinApp.Cli.Tests\TestApps\App.xaml.cs`, does **not** match it, so the bot may scan it — keep those in scope. If nothing survives, say so and stop. Then read the changed lines. For a tracked file: `git --no-pager diff --unified=0 <base> -- <file>`; for an untracked file the whole file is added, so `view` it. Either way `view` each changed region for context. **Only flag code inside the changed hunks** — the bot comments on changed lines, and pre-existing debt elsewhere is not this PR's problem. ## 2. Check the diff against the rule set For every changed hunk, walk the rule tables below and flag each line that matches. For a match, record the `cs/...` id, the exact `file:line`, the offending snippet, and the smallest fix. Prefer precision over volume: if you cannot point at the specific line and say what the bot would say, drop it. Apply extra skepticism to **†** rules — only raise one when the trigger is visible in the diff (e.g. a `new HttpRequestMessage(...)` with no `using` and no `return`/field assignment), not on a hunch. ### Exception handling | Rule | Flags → do instead | |---|---| | `cs/catch-of-all-exceptions` | `catch (Exception)` (or bare `catch`) → catch the specific type you can handle | | `cs/catch-nullreferenceexception` | catching `NullReferenceException` → fix the deref; don't catch it | | `cs/empty-catch-block` | `catch {}` that swallows → handle, log, or let it propagate | | `cs/rethrown-exception-variable` | `throw ex;` (resets stack) → `throw;` | ### Null & dereference — **†** | Rule | Flags → do instead | |---|---| | `cs/dereferenced-value-may-be-null` **†** | using a value that a prior branch/API can leave null → null-check or `?.` | | `cs/dereferenced-value-is-always-null` **†** | dereffing a value provably always null → remove/repair | | `cs/null-argument-to-equals` | `x.Equals(null)` → `x is null` | ### Disposal & resources | Rule | Flags → do instead | |---|---| | `cs/local-not-disposed` **†** | a local `IDisposable` you own, never disposed → `using` | | `cs/missed-using-statement` **†** | `Dispose()` in a `finally` you could express as `using` → `using` | ### Equality & hashing | Rule | Flags → do instead | |---|---| | `cs/gethashcode-is-not-defined` | overriding `Equals` without `GetHashCode` → override both | | `cs/useless-gethashcode-call` | `GetHashCode()` on a small integral type (e.g. `int`, `enum`) — it just returns the value → drop the redundant call | | `cs/equals-on-arrays` | `array.Equals(other)` (reference eq) → `SequenceEqual` | | `cs/equals-on-unrelated-types` | `Equals` across incomparable types → compare compatible types | | `cs/unchecked-cast-in-equals` | unchecked cast of the arg in an `Equals` override → `is`/`as` guard | | `cs/recursive-equals-call` | `Equals` calling itself unboundedly → fix the recursion | | `cs/reference-equality-on-valuetypes` | `ReferenceEquals` on value types (always false) → `==`/`Equals` | | `cs/equality-on-floats` | `==` on `double`/`float` → compare with a tolerance | | `cs/comparison-of-identical-expressions` | a comparison operator with identical operands (`x == x`, `a < a`) → remove or fix the typo | ### Dead & useless code | Rule | Flags → do instead | |---|---| | `cs/empty-block` | empty `if`/loop body → remove or fill | | `cs/useless-if-statement` | `if` whose branches do the same thing → collapse | | `cs/constant-condition` **†** | condition provably always true/false → remove the dead path | | `cs/simplifiable-boolean-expression` | `x ? true : false`, `!(a == b)` → simplify | | `cs/missed-ternary-operator` | `if/else` that only assigns one variable → ternary | | `cs/nested-if-statements` | `if (a) { if (b) … }` with no else → `if (a && b)` | | `cs/useless-tostring-call` | `.ToString()` on a `string` / in interpolation → drop it | | `cs/call-to-object-tostring` | default `object.ToString()` (type name) → override or format explicitly | | `cs/self-assignment` | `x = x;` → remove (or fix the intended target) | | `cs/unused-label` | a `label:` never targeted → remove | | `cs/unused-property-value` **†** | setter that ignores `value` → use it | | `cs/empty-collection` **†** | a collection created empty and then read/used but never given any elements → add elements or drop the reads | | `cs/unused-collection` **†** | a collection filled but never read → remove or use | ### LINQ opportunities — **†** | Rule | Flags → do instead | |---|---| | `cs/linq/missed-where` **†** | `foreach` with a filtering `if` → `.Where(...)` | | `cs/linq/missed-select` **†** | `foreach` that only projects → `.Select(...)` | | `cs/linq/missed-all` **†** | loop checking a condition holds for all → `.All(...)` | | `cs/linq/missed-cast` **†** | loop casting every element → `.Cast<T>()` | | `cs/linq/missed-oftype` **†** | loop filtering by type → `.OfType<T>()` | | `cs/linq/useless-select` | `.Select(x => x)` → drop it | ### Collections & indexing | Rule | Flags → do instead | |---|---| | `cs/inefficient-containskey` | `ContainsKey` then indexer → `TryGetValue` | | `cs/test-for-negative-container-size` | `Count < 0` / `Length < 0` (never true) → fix the check | | `cs/index-out-of-bounds` **†** | off-by-one vs `Length`/`Count` → use `< Length` | | `cs/nested-loops-with-same-variable` | nested `for`/`foreach` that reuse the same counter (inner loop also updates `i`) → use a distinct loop variable | ### Concurrency & locking | Rule | Flags → do instead | |---|---| | `cs/lock-this` | `lock (this)` → lock a private `object` | | `cs/empty-lock-statement` | `lock (x) {}` → remove or fill | | `cs/unsafe-sync-on-field` | locking a field that is **reassigned inside the lock** (threads then lock different objects) → lock a private `readonly` object | | `cs/inconsistent-lock-sequence` **†** | acquiring locks in differing orders → fix ordering | | `cs/locked-wait` **†** | a blocking `.Wait()` (`Task`/`WaitHandle`) while holding a lock — **not** `Monitor.Wait` on the locked object, which correctly requires it → don't block on unrelated waits inside the lock | | `cs/non-short-circuit` | non-short-circuit `&`/`\|` on bools whose right operand has a risky access (e.g. a possible null-deref) that `&&`/`\|\|` would guard → short-circuit | ### `this`, casts & inheritance | Rule | Flags → do instead | |---|---| | `cs/cast-of-this-to-type-parameter` | casting `this` to a type param → rethink the API | | `cs/downcast-of-this` | downcasting `this` to a derived type → virtual method | | `cs/type-test-of-this` | `this is DerivedType` → virtual method | | `cs/impossible-array-cast` | array cast that always fails at runtime → fix the type | | `cs/class-name-matches-base-class` | subclass named like its base → rename | | `cs/field-masks-base-field` | field shadowing a base field → rename or reuse | | `cs/local-shadows-member` | local hiding a field/property/param → rename | ### API & design smells | Rule | Flags → do instead | |---|---| | `cs/path-combine` **†** | `Path.Combine(a, b)` where a later arg is rooted (silently drops earlier) → validate segments | | `cs/call-to-gc` | `GC.Collect()` → remove; let the runtime manage it | | `cs/call-to-obsolete-method` | calling `[Obsolete]` API → use the replacement | | `cs/call-to-unmanaged-code`, `cs/unmanaged-code` | P/Invoke / unsafe surface → review necessity (often expected for CsWin32) | | `cs/class-implements-icloneable` | `ICloneable` (ambiguous deep/shallow) → a typed clone method | | `cs/expose-implementation` **†** | returning/storing a reference to an internal mutable array/collection that is **later modified** through the alias → return a copy or read-only view | | `cs/static-field-written-by-instance` | instance method writing a static field → rethink ownership | | `cs/missed-readonly-modifier` **†** | a private field written only at its declaration and/or in the (static) constructor, never elsewhere → `readonly` | | `cs/loss-of-precision` | integer division/multiplication whose result is converted to `float`/`double`/`decimal` — precision is already lost before the conversion (e.g. `(double)(a / b)`) → cast an operand first | | `cs/invalid-string-formatting` | format string vs args mismatch → fix placeholders | | `cs/string-concatenation-in-loop` **†** | `s += …` in a loop → `StringBuilder` | | `cs/stringbuilder-creation-in-loop` **†** | `new StringBuilder()` inside a loop → hoist it out **only if** it doesn't escape the iteration, calling `.Clear()` at the start of each pass; otherwise leave it | | `cs/stringbuilder-initialized-with-character` | `new StringBuilder('x')` (that's capacity!) → `.Append('x')` or `"x"` | | `cs/asp/response-write` | `Response.Write` of a single block → not relevant to this CLI; skip unless ASP code appears | ## 3. Report Print to stdout. No file output and **no fixes** unless asked. ```markdown # Code Quality pre-check — <head> vs origin/main (csharp-code-quality, static) ## Verdict <nothing obvious | N likely finding(s)> — <one plain sentence> ## Likely bot comments ### <rule id> — <short plain title> <mark (lower confidence) for † rules> - **What it flags:** <one sentence> - **Where:** `<path>:<line>` - **Show me:** <the snippet from the diff> - **Smallest fix:** <least-complex change> <Repeat per finding, or "None obvious on your changed lines."> ## Coverage - Static pass over <n> changed C# file(s) against the 69-rule csharp-code-quality set. - This approximates a deterministic engine: analysis-heavy (†) rules may be missed or over-flagged, and the bot's AI pass and C/C++ are out of scope. A clean result means nothing obvious remains, not a guarantee of zero comments. ``` ## If asked to fix Apply the **smallest** change that clears each finding on the changed lines, one rule at a time. Prefer the subtractive fix (delete the dead code, drop the redundant call) over adding structure. Do not touch pre-existing findings unless explicitly asked. ## Maintaining this skill The tables above are a **point-in-time snapshot** of the `csharp-code-quality` suite (69 rules). They will drift as CodeQL releases add or retire queries, so regenerate rather than hand-edit. With the CodeQL bundle available, the current membership is: ```powershell codeql resolve queries codeql/csharp-queries:codeql-suites/csharp-code-quality.qls # each resolved .ql file's header carries its @id / @name / @precision ``` If that list diverges from the tables, reconcile it — add new rules, remove retired ones — and keep the **†** markers on the flow/type-analysis queries so their lower confidence stays visible. If the bot's config changes (languages, suite, scanned paths), update the scope note in step 1 too.
View on GitHub