Skip to content

Fix dotnet-test findings from the refreshed cross-family eval (#899) - #945

Merged
Evangelink merged 13 commits into
mainfrom
dev/amauryleve/dotnet-test-eval-followups
Jul 29, 2026
Merged

Fix dotnet-test findings from the refreshed cross-family eval (#899)#945
Evangelink merged 13 commits into
mainfrom
dev/amauryleve/dotnet-test-eval-followups

Conversation

@Evangelink

@Evangelink Evangelink commented Jul 25, 2026

Copy link
Copy Markdown
Member

What & why

Fixes the findings from the refreshed dotnet-test cross-family evaluation posted to #899 (run 30108473397 plus the three recovery runs). Every change is driven by the judge evidence from the losing trials, not by style preference — I pulled the per-trial evidence out of the artifacts and fixed the specific behaviour the judges penalised.

1. Dormancy guards were measuring nothing

assertion-quality, test-gap-analysis, test-smell-detection and test-tagging each have a decline stimulus carrying constraints.reject_skills: ["*"], which forces the skilled arm skill-free. Treatment then equals control by construction, so the head-to-head score is pure judge noise — across the four evals it landed on −0.4, +0.4, +0.4 and 0, and twice cost a skill its pass.

Fixed in two halves:

  • Constraint — dropped reject_skills, matching the repo's own convention (agent.test-quality-auditor, agent.test-migration, system-text-json-net11 all use expect_activation: false alone). The skill is now actually loaded, so the guard measures the real property.
  • Rubric — the guards' rubrics only said "wrote test methods", so once both arms wrote tests the judge fell back to comparing volume ("Response A produced 12 tests … B produced 10"). Every guard now carries an anti-hijack criterion plus an explicit instruction not to reward raw test count.

Applying only the first half is what briefly regressed test-gap-analysis; with both halves it is green again.

2. Skill fixes, one per failure mode

Skill What the judges said Fix
test-gap-analysis Baselines ran the tests and were right; the skill reasoned statically and reported survivors the tests actually kill New Step 4b: confirm each survivor by applying it, re-running, reverting. Static reasoning only as a labelled fallback
test-anti-patterns Baselines won on depth, not polish Depth bar: account for every test, exact expected values, adjacent gaps, consistent counts
detect-static-dependencies Every loss was counting accuracy One authoritative total, classify by resource touched, exclude pure helpers, require file:line
test-smell-detection Both losses hinged on calibrating a fixed Thread.Sleep(3000) away as an "integration style issue" Sleepy Test no longer downgrades for integration tests
crap-score Coverage collection failed, the skill estimated coverage Documented recovery path; never publish a CRAP score built on assumed coverage
coverage-analysis Numeric inconsistencies; claimed one method was the entire gap Answer first, reconcile every number, list every below-threshold member
migrate-static-to-wrapper Migrated out of scope and claimed "Build succeeded" after a restore failure Strict scope discipline plus truthful build reporting
code-testing-agent Vague evidence rows; a coverage run that exited 1 Per-requirement traceability; cite a clean run

3. Two activation bugs, found via skillActivationCount = 0

  • migrate-static-to-wrapper contradicted itself. Step 3 documents the ambient-seam pattern for static classes and says the seam "is the answer", but When Not to Use said "the code does not use dependency injection and the user hasn't chosen ambient context" — describing the failing scenario exactly. Both losing scenarios therefore ran unassisted, and one reproduced the very GetLocalNow().DateTime DateTimeKind regression Fix migrate-static-to-wrapper DateTimeKind regression (#899) #906 fixed.
  • code-testing-agent did not fire on "standard test-generation workflow" phrasing for a sparse workspace. Its extend-suite fixture was also MSTest, so in the plugin arm the sibling writing-mstest-tests correctly won the routing (this skill's own description disclaims MSTest authoring). Converted that fixture to xUnit v3, which removed the overlap and gives the three scenarios framework diversity (xUnit / pytest / Vitest).

4. A fixture that was never committed, one that lied, and six more like it

  • .gitignore carries coverage*.xml (correct for Coverlet output) and silently swallowed
    crap-score/fixtures/refactor-required/coverage.cobertura.xml. git add -A reported success, the eval passed
    locally, and three scenarios would have failed at setup in CI. Added a negation for
    tests/**/fixtures/**/coverage*.
  • That same fixture declared line-rate="0.55" while its <lines> implied 0.80. The crap-score skill documents
    both parse paths, so the two arms of a comparison can read different coverage inputs and the eval measures the
    disagreement instead of the skill — a scenario lost −40% to exactly this. Rebuilt it consistent.
  • The same inconsistency turned out to affect six more methods across crap-score/partial-coverage and
    coverage-analysis/partial-coverage (Two Cobertura eval fixtures declare a line-rate their own line data contradicts #950). Fixed here too. The declared rate is the intent — the rubrics are
    written against it — so the <lines> data was corrected to match, not the reverse, and class/package totals were
    rebuilt. Verified the risk ordering each rubric asserts still holds: ProcessOrder remains the top hotspot at
    CRAP 26.6, CalculateGpa at 52.2.

5. Wired up four orphaned fixtures

Purpose-built discriminators that were committed but that no stimulus referenced — the cheapest way to raise n
without new CI cost:

Eval n Fixture
test-smell-detection 4 → 6 junit-smells (all 10 catalogued smells in JUnit, plus a well-written test for contrast) and skip-and-magic (a documented skip vs a bare one, and self-documenting counts that must not be flagged as magic numbers)
test-tagging 8 → 9 go-report-only — Go has no attribute-based trait mechanism, so the skill must classify and report rather than invent a [TestCategory] equivalent that does not compile
find-untested-sources 3 → 4 pairing-repo — a src/ + tests/ split where OrderProcessor is unpaired and CustomerService is already covered

Checks

  • skill-validator check --plugin ./plugins/dotnet-test — ✅ 20 skills, 10 agents; plugin skill-menu inside the 15,000-char budget
  • markdownlint-cli2 — 0 errors
  • 257/257 fixture references parse, exist and are git-tracked; every dormancy guard verified free of reject_skills and carrying an anti-hijack rubric item
  • The converted xUnit fixture restores, builds and passes its 4 seed tests in a clean workspace

Evaluation (run 30292432365) — 6 of 11

Skill Result Δ Preference W/T/L n
crap-score +43.3% 5/1/0 6
coverage-analysis +40.0% 3/0/0 3
find-untested-sources +40.0% 4/0/0 4
detect-static-dependencies +33.3% 5/1/0 6
test-anti-patterns +30.0% 6/2/0 8
test-smell-detection +26.7% 4/2/0 6
migrate-static-to-wrapper +44.0% 4/1/0 5
test-tagging +33.3% 5/2/2 9
assertion-quality +26.7% 5/0/1 6
test-gap-analysis +26.7% 5/0/1 6
code-testing-agent +13.3% 1/2/0 3

coverage-analysis passed for the first time in six runs, and find-untested-sources
passed after its orphaned fixture raised n from 3 to 4. Two of the five remaining
failures have zero losses.

The gate, not the skills, is the binding constraint — now measured directly

Splitting the CI gate out of this PR produced an accidental A/A test: the only
difference between the two evaluated commits is four infrastructure files the harness
never reads.

$ git diff --name-only 29edcbcdb caa0d2d61 -- tests/ plugins/ dotnet-skills.experiment.yaml
(empty)

Byte-identical inputs, and 3 of 11 verdicts flipped:

Skill before after
coverage-analysis ❌ +60.0% 3/0/0 ✅ +40.0% 3/0/0 flipped
find-untested-sources ❌ +30.0% 3/1/0 ✅ +40.0% 4/0/0 flipped
test-tagging ✅ +40.0% 7/1/1 ❌ +33.3% 5/2/2 flipped

coverage-analysis is the clearest case: it had failed five consecutive runs while
winning 100% of its trials
, then passed with the same 3W/0T/0L record. The judge
scored one trial +0.4 instead of +0.6, sd collapsed to 0, and the CI became
degenerate. At n=3 the verdict turns on the magnitude the judge assigns, not on
whether the skill won.

Consequence for review: verdict changes across runs should not be attributed to
code changes without checking the diff first.
Full analysis and the proposal
(per-eval runs overrides rather than a blanket runs: 3, which would push the
~75-minute job past the 360-minute cap) is in #952.

Nothing here has been chased by weakening or deleting scenarios.

The repo-wide eval quality gate that came out of this work was split into #953 at
review request: it gates every plugin and warns about fixtures and skills in plugins
this PR does not own, so it carries a different blast radius and reviewer set.

Refs #899. Closes #950. Power analysis: #952. Quality gate: #953.

Every change below is driven by judge evidence from the losing trials of the
refreshed 5-family dotnet-test matrix (runs 30108473397 + recovery runs), not by
style preference.

Eval measurement fix — the "discovery" P2s were an artifact:
- assertion-quality, test-gap-analysis, test-smell-detection, and test-tagging
  each have a decline stimulus with `constraints.reject_skills: ["*"]`, so the
  skill cannot activate there by construction. Without `expect_activation:
  false` the adapter counted those dormant runs as missed activations, which is
  exactly the 75-88% invocation rates reported in the scorecard. Annotating them
  (the convention already used by agent.test-quality-auditor) removes the false
  signal; the non-activations were the only ones observed for these skills.

Skill fixes:
- test-gap-analysis: baselines won by actually running the suite while the skill
  reasoned statically and reported survivors that the tests in fact kill. Added
  Step 4b: confirm every reported survivor by applying it, re-running the
  covering tests, and reverting; fall back to reasoning only when the suite
  cannot run, labelled unverified. Calibrated severity down for strong suites.
- test-anti-patterns: baselines won on depth, not polish. Added a depth bar —
  account for every test in scope, give exact expected values in fixes, name the
  adjacent error-path/boundary gaps, and keep counts consistent. Trimmed three
  pitfall rows that duplicated the calibration step so the skill stays under the
  profiler's "comprehensive" threshold.
- detect-static-dependencies: losses were all counting accuracy. One
  authoritative total (no findings parked outside it), classify by the resource
  touched rather than by the `static` keyword, exclude pure helpers such as
  Path.Combine from the needs-wrapping total, require file:line, and add the
  missing randomness/culture/serialization categories.
- test-smell-detection: the calibration rule told models to downgrade Sleepy
  Test for integration tests, which is what lost both losing scenarios. Fixed
  sleeps now stay High in any category; Mystery Guest and Eager Test still
  downgrade.
- crap-score: losses came from estimating coverage after collection failed.
  Added the dotnet-coverage/ReportGenerator recovery path and a hard rule never
  to publish a CRAP score built on assumed coverage.
- coverage-analysis: answer the asked question first, reconcile every number
  against the script output, and list every below-threshold member instead of
  declaring one method the entire gap.
- migrate-static-to-wrapper: migrate exactly what was requested (no adjacent
  DateTime.Now rewrites, respect intentional-use comments) and never report
  "build succeeded" when the build or restore failed.
- code-testing-agent: quote each requirement verbatim in the evidence table so
  multi-condition requirements map to a test that covers the whole combination,
  and cite a clean run rather than a coverage attempt that exited non-zero.

Validation: skill-validator check passes (20 skills, 10 agents); markdownlint
clean; eval specs parse and the adapter now reports all four decline stimuli as
expect-dormant.

Refs #899

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
Copilot AI review requested due to automatic review settings July 25, 2026 07:23
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Skill Coverage Report

Plugin Skill Covered Coverage
dotnet-test code-testing-agent 0/3 0%
dotnet-test crap-score 2/6 33.3%
dotnet-test detect-static-dependencies 0/22 0%
dotnet-test migrate-static-to-wrapper 0/26 0%
dotnet-test test-anti-patterns 0/25 0%
dotnet-test test-gap-analysis 0/30 0%
dotnet-test test-smell-detection 0/26 0%
dotnet-test assertion-quality 5/22 22.7%
dotnet-test test-tagging 0/28 0%
Uncovered: dotnet-test/code-testing-agent
  • [WorkflowStep] Step 1: Determine the user request (line 75)
  • [WorkflowStep] Step 2: Invoke the Test Generator (line 80)
  • [WorkflowStep] Step 3: Execute with bounded context (line 94)
Uncovered: dotnet-test/crap-score
  • [WorkflowStep] Step 1: Collect code coverage data (line 62)
  • [WorkflowStep] Step 2: Compute cyclomatic complexity (line 89)
  • [WorkflowStep] Step 3: Extract per-method coverage from Cobertura XML (line 114)
  • [WorkflowStep] Step 5: Present results (line 128)
Uncovered: dotnet-test/detect-static-dependencies
  • [Validation] All .cs files in scope were scanned (check count) (line 147)
  • [Validation] Report includes category totals, top patterns, and affected files (line 148)
  • [Validation] Category totals, top patterns, and per-file counts reconcile to the same grand total (line 149)
  • [Validation] Every occurrence carries a file:line location (line 150)
  • [Validation] No findings are held outside the totals in an "additional" section (line 151)
  • [Validation] Deterministic pure helpers (Path.Combine, Math.*) are not counted as testability blockers (line 152)
  • [Validation] Each detected pattern has a recommended replacement listed (line 153)
  • [Validation] obj/ and bin/ directories were excluded (line 154)
  • [Validation] Migration priority is ordered by impact (count × ease of replacement) (line 155)
  • [Pitfall] Scanning obj/ or generated code (line 161)
  • [Pitfall] Counting wrapped calls as statics (line 162)
  • [Pitfall] Missing statics inside lambdas/LINQ (line 163)
  • [Pitfall] Recommending TimeProvider on < .NET 8 (line 164)
  • [Pitfall] Ignoring test projects (line 165)
  • [Pitfall] Under-counting by relegating findings (line 166)
  • [Pitfall] Calling an instance member a static (line 167)
  • [Pitfall] Recommending a wrapper for Path.Combine (line 168)
  • [WorkflowStep] Step 1: Determine scan scope (line 50)
  • [WorkflowStep] Step 2: Search for static dependency patterns (line 60)
  • [WorkflowStep] Step 3: Aggregate and rank results (line 75)
  • [WorkflowStep] Step 4: Present the report (line 101)
  • [WorkflowStep] Step 5: Suggest next steps (line 139)
Uncovered: dotnet-test/migrate-static-to-wrapper
  • [Validation] All call sites in scope were replaced (none missed) (line 214)
  • [Validation] No call site outside the requested member/file scope was modified (line 215)
  • [Validation] Call sites documented as intentional (e.g. local time) were left untouched and reported (line 216)
  • [Validation] Constructor injection added to all affected classes (line 217)
  • [Validation] Field naming follows existing class conventions (line 218)
  • [Validation] Required using directives added (line 219)
  • [Validation] Required NuGet packages referenced (line 220)
  • [Validation] Build succeeds after migration, and the reported result matches the actual command exit code (line 221)
  • [Validation] Test files updated with appropriate test doubles (line 222)
  • [Validation] No behavioral changes introduced (wrapper delegates directly to the static) (line 223)
  • [Validation] DateTimeKind preserved — former DateTime.UtcNow stays Utc (.UtcDateTime), former DateTime.Now stays Local (.LocalDateTime) (line 224)
  • [Pitfall] Replacing statics in test code (line 230)
  • [Pitfall] Breaking static classes (line 231)
  • [Pitfall] Missing FakeTimeProvider NuGet (line 232)
  • [Pitfall] Replacing a DateTime value with .DateTime off a DateTimeOffset (line 233)
  • [Pitfall] Migrating too much at once (line 234)
  • [Pitfall] Migrating DateTime.Now when only UtcNow was requested (line 235)
  • [Pitfall] Claiming "Build succeeded" after a failed restore (line 236)
  • [Pitfall] Forgetting DI registration (line 237)
  • [WorkflowStep] Step 1: Verify prerequisites (line 55)
  • [WorkflowStep] Step 2: Plan the migration for each file (line 65)
  • [WorkflowStep] Step 3: Add constructor injection (line 102)
  • [WorkflowStep] Step 4: Replace call sites (line 144)
  • [WorkflowStep] Step 5: Update affected test files (line 161)
  • [WorkflowStep] Step 6: Build verification (line 171)
  • [WorkflowStep] Step 7: Report changes (line 187)
Uncovered: dotnet-test/test-anti-patterns
  • [Validation] Every test method in scope is accounted for (reviewed count stated; none silently skipped) (line 161)
  • [Validation] Every finding includes a specific location (not just a general warning) (line 162)
  • [Validation] Every Critical/High finding includes a concrete fix with exact expected values (line 163)
  • [Validation] Adjacent untested error paths and boundary values are called out (line 164)
  • [Validation] Summary counts match the enumerated findings (line 165)
  • [Validation] Report covers all categories (assertions, isolation, naming, structure) (line 166)
  • [Validation] Positive observations are included alongside problems (line 167)
  • [Validation] Recommendations are prioritized by severity (line 168)
  • [Pitfall] Reporting style issues as critical (line 174)
  • [Pitfall] Suggesting rewrites instead of targeted fixes (line 175)
  • [Pitfall] Flagging intentional design choices (line 176)
  • [Pitfall] Inventing false positives on clean code (line 177)
  • [Pitfall] Flagging separate boundary tests as duplicates (line 178)
  • [Pitfall] Rating cosmetic issues as Medium (line 179)
  • [Pitfall] Ignoring the test framework (line 180)
  • [Pitfall] Missing the forest for the trees (line 181)
  • [Pitfall] Trading depth for tidiness (line 182)
  • [Pitfall] Contradicting yourself in the report (line 183)
  • [Pitfall] Counts that don't add up (line 184)
  • [WorkflowStep] Step 1: Detect language and load extension (line 57)
  • [WorkflowStep] Step 2: Gather the test code (line 61)
  • [WorkflowStep] Step 3: Scan for anti-patterns (line 67)
  • [WorkflowStep] Step 4: Calibrate severity honestly (line 114)
  • [WorkflowStep] Step 5: Report findings (line 131)
  • [WorkflowStep] Step 6: Prioritize recommendations (line 151)
Uncovered: dotnet-test/test-gap-analysis
  • [Validation] The suite was run and every reported survivor was empirically confirmed (or all findings are explicitly labelled unverified) (line 212)
  • [Validation] All applied mutations were reverted and the suite is green again (line 213)
  • [Validation] Every mutation point was classified (Killed / Survived / No coverage / Equivalent) (line 214)
  • [Validation] Every survived mutation includes the original code, the hypothetical change, and why tests miss it (line 215)
  • [Validation] Every survived mutation includes a concrete recommended fix (a test assertion or test case) (line 216)
  • [Validation] Equivalent mutations are correctly identified and excluded from the score (line 217)
  • [Validation] Trivial code (simple getters, auto-properties) is excluded from analysis (line 218)
  • [Validation] Findings are prioritized by risk, not just listed in source order (line 219)
  • [Validation] Report includes strengths (killed mutations) alongside gaps (line 220)
  • [Validation] Mutation categories are correctly labeled (line 221)
  • [Pitfall] Reporting survivors you never ran (line 227)
  • [Pitfall] Overstating severity on a strong suite (line 228)
  • [Pitfall] Publishing your reasoning as it changes (line 229)
  • [Pitfall] Leaving a mutation applied (line 230)
  • [Pitfall] Analyzing trivial code (line 231)
  • [Pitfall] Reporting equivalent mutations as gaps (line 232)
  • [Pitfall] Ignoring call chains (line 233)
  • [Pitfall] Over-counting mutations in generated code (line 234)
  • [Pitfall] Recommending a new test for every survived mutation (line 235)
  • [Pitfall] Ignoring production context (line 236)
  • [Pitfall] Claiming 100% kill rate is required (line 237)
  • [Pitfall] Not considering integration with other skills (line 238)
  • [Pitfall] Forgetting Go's error idiom (line 239)
  • [Pitfall] Forgetting Rust's ? operator (line 240)
  • [WorkflowStep] Step 1: Detect language and load extension (line 55)
  • [WorkflowStep] Step 2: Gather production and test code (line 59)
  • [WorkflowStep] Step 3: Identify mutation points (line 65)
  • [WorkflowStep] Step 4: Evaluate each mutation against tests (line 137)
  • [WorkflowStep] Step 5: Calibrate findings (line 164)
  • [WorkflowStep] Step 6: Report findings (line 176)
Uncovered: dotnet-test/test-smell-detection
  • [Validation] Every finding includes the specific test method name and file location (line 215)
  • [Validation] Every finding includes a code snippet showing the smell in context (line 216)
  • [Validation] Every finding includes a concrete fix example (not just "fix this") (line 217)
  • [Validation] Integration tests are not penalized for using real resources, but their fixed sleeps are still reported as High (line 218)
  • [Validation] Each smell is reported under its own taxonomy name (Unknown Test, Empty Test, Assertion Roulette are distinct — do not merge them) (line 219)
  • [Validation] Simple foreach-assert loops are not flagged as conditional test logic (line 220)
  • [Validation] Contextually obvious numbers are not flagged as magic numbers (line 221)
  • [Validation] If the test suite is clean, the report says so upfront (line 222)
  • [Validation] Severity levels are justified, not arbitrary (line 223)
  • [Pitfall] Flagging integration tests for using real resources (line 229)
  • [Pitfall] Calibrating away a real sleep as an "integration style issue" (line 230)
  • [Pitfall] Flagging loop-over-collection-assert as conditional logic (line 231)
  • [Pitfall] Flagging Go/Rust table-driven loops as Conditional Test Logic (line 232)
  • [Pitfall] Flagging parametrized tests as Duplicate Assert (line 233)
  • [Pitfall] Flagging obvious count assertions after adding N items (line 234)
  • [Pitfall] Missing framework-specific assertion syntax (line 235)
  • [Pitfall] Treating mock-call verifications as assertion-free (line 236)
  • [Pitfall] Missing the async-test silent-pass trap (line 237)
  • [Pitfall] Over-flagging try/catch that captures for assertion (line 238)
  • [Pitfall] Treating skip annotations with reasons same as bare skips (line 239)
  • [Pitfall] Flagging DoesNotThrow-style tests as assertion-free (line 240)
  • [WorkflowStep] Step 1: Detect language and load extension (line 67)
  • [WorkflowStep] Step 2: Gather the test code (line 71)
  • [WorkflowStep] Step 3: Scan for test smells (line 77)
  • [WorkflowStep] Step 4: Apply calibration rules (line 168)
  • [WorkflowStep] Step 5: Report findings (line 184)
Uncovered: dotnet-test/assertion-quality
  • [Validation] Every assertion in the test suite was classified into at least one category (line 156)
  • [Validation] Metrics are computed correctly (counts add up) (line 157)
  • [Validation] Trivial-assertion tests are correctly identified (not over-flagged) (line 158)
  • [Validation] Exception tests are not penalized for low assertion count (line 159)
  • [Validation] Boolean assertions on meaningful properties are not classified as trivial (line 160)
  • [Validation] Recommendations are concrete (name specific test methods and suggest specific assertion types) (line 161)
  • [Validation] If the suite has good diversity, the report acknowledges this (line 162)
  • [Pitfall] Penalizing exception tests for low assertion count (line 168)
  • [Pitfall] Counting any Boolean assertion as trivial (line 170)
  • [Pitfall] Ignoring framework differences (line 171)
  • [Pitfall] Treating bare assertion forms as missing-framework (line 172)
  • [Pitfall] Treating mock-call verifications as assertion-free (line 173)
  • [Pitfall] Recommending diversity for diversity's sake (line 174)
  • [Pitfall] Missing implicit assertions (line 175)
  • [Pitfall] Async tests with unawaited assertions (line 176)
  • [WorkflowStep] Step 1: Detect language and load extension (line 51)
  • [WorkflowStep] Step 2: Gather the test code (line 55)
Uncovered: dotnet-test/test-tagging
  • [Validation] Every test method has at least one trait classification (positive or negative at minimum) — in the report for report-only frameworks, or as an attribute for auto-edit frameworks (line 238)
  • [Validation] No invented trait values outside the taxonomy table (line 239)
  • [Validation] Existing trait attributes were preserved, not duplicated (line 240)
  • [Validation] The trait summary table was generated (line 241)
  • [Validation] For auto-edit frameworks, the project still builds / tests still discover after changes (dotnet build / pytest --collect-only / mvn test-compile / go vet ./... / cargo check --tests / npm run test:list / Invoke-Pester -PassThru -Skip / equivalent) (line 242)
  • [Validation] For report-only frameworks, no source files were modified (line 243)
  • [Validation] For convention-based frameworks, edits were applied ONLY when a project convention was confirmed (line 244)
  • [Pitfall] Guessing traits without reading the test body (line 250)
  • [Pitfall] Tagging a test only as boundary without positive/negative (line 251)
  • [Pitfall] Using the wrong attribute syntax for the detected framework (line 252)
  • [Pitfall] Duplicating an existing category attribute (line 253)
  • [Pitfall] Over-tagging as critical-path (line 254)
  • [Pitfall] Editing Go / plain Jest / plain Rust / plain XCTest / plain GoogleTest source (line 255)
  • [Pitfall] Inventing tag prefixes for convention-based frameworks (line 256)
  • [Pitfall] Missing language-specific concurrency / async primitives (line 257)
  • [WorkflowStep] Step 1: Detect the language, framework, and tagging capability (line 60)
  • [WorkflowStep] Step 2: Scan existing traits (line 70)
  • [WorkflowStep] Step 3: Classify each test method (line 92)
  • [WorkflowStep] Step 4: Apply trait attributes (or report only) (line 114)
  • [WorkflowStep] Step 5: Generate trait summary (line 202)
  • [CodePattern] [Test] (line 135)
  • [CodePattern] [negative] (line 194)
  • [CodePattern] [TestMethod] (line 119)
  • [CodePattern] [Fact] (line 127)
  • [CodePattern] [TestCategory] (line 119)
  • [CodePattern] [boundary] (line 194)
  • [CodePattern] [Category] (line 135)
  • [CodePattern] [Trait] (line 127)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes dotnet-test cross-family eval findings by (1) correcting evaluation metadata so “decline / reject-skills” stimuli are treated as expected non-activations, and (2) tightening several skill instructions to match judge evidence (accuracy, calibration, and reporting discipline).

Changes:

  • Added expect_activation: false to decline stimuli that explicitly reject skills, preventing dormant runs from being counted as missed activations.
  • Updated multiple dotnet-test skill guides to address observed failure modes (e.g., empirically verifying pseudo-mutation “survivors”, not downgrading fixed sleeps in integration tests, stricter scope/build-result reporting).
  • Strengthened reporting/validation checklists in several skills to reduce inconsistencies and under-specified evidence.
Show a summary per file
File Description
tests/dotnet-test/test-tagging/eval.yaml Marks reject-skills decline stimulus as expected-dormant via expect_activation: false.
tests/dotnet-test/test-smell-detection/eval.yaml Marks reject-skills decline stimulus as expected-dormant via expect_activation: false.
tests/dotnet-test/test-gap-analysis/eval.yaml Marks reject-skills decline stimulus as expected-dormant via expect_activation: false.
tests/dotnet-test/assertion-quality/eval.yaml Marks reject-skills decline stimulus as expected-dormant via expect_activation: false.
plugins/dotnet-test/skills/test-smell-detection/SKILL.md Keeps Sleepy Test severity high even for integration tests; updates calibration/validation/pitfalls accordingly.
plugins/dotnet-test/skills/test-gap-analysis/SKILL.md Adds Step 4b to verify reported survivors by actually applying mutations and re-running tests; calibrates messaging for strong suites.
plugins/dotnet-test/skills/test-anti-patterns/SKILL.md Adds explicit “depth bar” and validation checks to avoid shallow/inconsistent reports.
plugins/dotnet-test/skills/migrate-static-to-wrapper/SKILL.md Enforces strict migration scope and truthful build-result reporting; expands validation/pitfalls.
plugins/dotnet-test/skills/detect-static-dependencies/SKILL.md Improves counting rules and categorization (hidden deps, reconcile totals, exclude pure helpers), adds missing categories, trims promotional next steps.
plugins/dotnet-test/skills/crap-score/SKILL.md Adds a “never estimate coverage” rule with fallback collection paths before computing CRAP.
plugins/dotnet-test/skills/coverage-analysis/SKILL.md Requires answering the user’s question early and reconciling all numbers; ensures all below-threshold members are listed.
plugins/dotnet-test/skills/code-testing-agent/SKILL.md Requires verbatim requirement quoting per evidence row and citing only clean (exit 0) runs.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 12/12 changed files
  • Comments generated: 0

@github-actions github-actions Bot added the pr-state/ready-for-eval PR is mergeable and awaiting evaluation label Jul 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

10 skill(s) evaluated — 5 improved, 5 no credible improvement. A skill passes only on a credible improvement over baseline (mean preference > 0 with its 95% CI above 0); ⚠️ marks a comparison that couldn't complete (errored/unmatched trials).

Skill Result Δ Preference [95% CI] W/T/L Quality Baseline Overfit Skills Loaded
assertion-quality +32.0% [+9.8%, +54.2%] 4/1/0 4.9/5 4.2/5 🟡 0.28 4/4 · 4/4 (plugin)
code-testing-agent +20.0% [-234.1%, +274.1%] 1/1/0 5.0/5 3.7/5 🟡 0.33 2/2 · 2/2 (plugin)
coverage-analysis +40.0% [+40.0%, +40.0%] 3/0/0 4.5/5 3.2/5 ✅ 0.10 3/3 · 3/3 (plugin)
crap-score +60.0% [-26.1%, +146.1%] 3/0/0 3.8/5 3.3/5 ✅ 0.14 3/3 · 3/3 (plugin)
detect-static-dependencies +50.0% [+24.3%, +75.7%] 6/0/0 4.6/5 4.2/5 ✅ 0.13 ⚠️ 5/6 · 5/6 (plugin)
migrate-static-to-wrapper +60.0% [-26.1%, +146.1%] 3/0/0 4.8/5 3.2/5 ✅ 0.07 3/3 · 3/3 (plugin)
test-anti-patterns +20.0% [-3.0%, +43.0%] 3/3/0 4.8/5 4.8/5 ✅ 0.07 6/6 · 6/6 (plugin)
test-gap-analysis +30.0% [-1.8%, +61.8%] 3/1/0 4.7/5 4.5/5 ✅ 0.07 3/3 · 3/3 (plugin)
test-smell-detection +40.0% [+40.0%, +40.0%] 4/0/0 4.8/5 4.9/5 🟡 0.35 3/3 · 3/3 (plugin)
test-tagging +45.0% [+8.4%, +81.6%] 7/0/1 4.8/5 4.1/5 🟡 0.36 ⚠️ 6/7 · 4/7 (plugin)
ℹ️ Column legend
  • Δ Preference — mean head-to-head preference of skilled vs baseline (−100%…+100%), judged by vally compare.
  • [95% CI] — 95% confidence interval on that mean; a skill passes only when the whole interval is above 0.
  • W/T/L — wins / ties / losses across trials.
  • Quality / Baseline — mean absolute judge score 0–5 (skilled isolated vs skill-free control).
  • Overfit — overfitting-judge severity (✅ Low, 🟡 Moderate, 🔴 High, — none) with its score.
  • Skills Loaded — of the scenarios that expect activation, how many actually activated / that total (plugin run shown when present); ⚠️ marks a scenario that expected activation but didn't activate.
✅ assertion-quality — details

Reason: Mean preference +32.0% [95% CI 9.8%, 54.2%], win rate 80.0% (4W/1T/0L over 5 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
= Decline request to write new tests from scratch +0.0% 0/1/0
▲ Flag assertion-free tests and trivial-only assertions +40.0% 1/0/0
▲ Identify low assertion diversity in equality-dominated test suite +40.0% 1/0/0
▲ Identify self-referential assertions in identity and round-trip tests +40.0% 1/0/0
▲ Recognize well-diversified assertion usage +40.0% 1/0/0
❌ code-testing-agent — details

Reason: Mean preference +20.0% [95% CI -234.1%, 274.1%], win rate 50.0% (1W/1T/0L over 2 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
= Does not revert a gutted-looking workspace (workspace integrity) +0.0% 0/1/0
▲ Generate Vitest tests for the shopping-cart library (TypeScript polyglot) +40.0% 1/0/0
✅ coverage-analysis — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (3W/0T/0L over 3 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +40.0% 1/0/0
▲ Run coverage from scratch without existing data +40.0% 1/0/0
❌ crap-score — details

Reason: Mean preference +60.0% [95% CI -26.1%, 146.1%], win rate 100.0% (3W/0T/0L over 3 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Calculate CRAP score for a single method with partial coverage +40.0% 1/0/0
▲ Generate coverage then compute CRAP score +100.0% 1/0/0
▲ Identify riskiest methods across a file +40.0% 1/0/0
✅ detect-static-dependencies — details

Reason: Mean preference +50.0% [95% CI 24.3%, 75.7%], win rate 100.0% (6W/0T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Decline scan for non-C# project +40.0% 1/0/0
▲ Detect statics inside lambda expressions and LINQ queries +40.0% 1/0/0
▲ Detect time-related statics and recommend TimeProvider +40.0% 1/0/0
▲ Exclude obj and bin directories from the scan +40.0% 1/0/0
▲ Identify static dependencies in a multi-class project +40.0% 1/0/0
▲ Verify structured report includes file count, categories, and top patterns +100.0% 1/0/0
❌ migrate-static-to-wrapper — details

Reason: Mean preference +60.0% [95% CI -26.1%, 146.1%], win rate 100.0% (3W/0T/0L over 3 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Decline migration when wrapper does not exist yet +100.0% 1/0/0
▲ Migrate DateTime.UtcNow to TimeProvider in a service class +40.0% 1/0/0
▲ Migrate only in scoped files, leaving others untouched +40.0% 1/0/0
❌ test-anti-patterns — details

Reason: Mean preference +20.0% [95% CI -3.0%, 43.0%], win rate 50.0% (3W/3T/0L over 6 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Detect coverage-touching pattern across a service facade +40.0% 1/0/0
= Detect duplicated tests and magic values +0.0% 0/1/0
▲ Detect flakiness indicators and test coupling +40.0% 1/0/0
= Detect mixed severity anti-patterns in repository service tests +0.0% 0/1/0
= Detect self-referential assertions in round-trip and identity tests +0.0% 0/1/0
▲ Recognize well-written tests without inventing false positives +40.0% 1/0/0
❌ test-gap-analysis — details

Reason: Mean preference +30.0% [95% CI -1.8%, 61.8%], win rate 75.0% (3W/1T/0L over 4 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Acknowledge well-tested code with few surviving mutations +40.0% 1/0/0
= Decline request to write new tests from scratch +0.0% 0/1/0
▲ Find boundary mutation gaps in tiered discount and shipping logic +40.0% 1/0/0
▲ Find logic and null-check mutation gaps in access control code +40.0% 1/0/0
✅ test-smell-detection — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (4W/0T/0L over 4 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Decline request to write new tests from scratch +40.0% 1/0/0
▲ Detect multiple test smells in order processing test suite +40.0% 1/0/0
▲ Recognize integration tests and avoid false positives for external resources +40.0% 1/0/0
▲ Recognize well-written tests with no significant smells +40.0% 1/0/0
✅ test-tagging — details

Reason: Mean preference +45.0% [95% CI 8.4%, 81.6%], win rate 87.5% (7W/0T/1L over 8 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Accurately classify NUnit tests with misleading method names +40.0% 1/0/0
▼ Audit test distribution without modifying files -40.0% 0/0/1
▲ Decline request to write new tests +40.0% 1/0/0
▲ Tag MSTest tests and verify the project still builds +40.0% 1/0/0
▲ Tag a partially-tagged MSTest suite without duplicating existing traits +40.0% 1/0/0
▲ Tag an untagged MSTest test suite +100.0% 1/0/0
▲ Tag an untagged NUnit test suite +40.0% 1/0/0
▲ Tag an untagged xUnit test suite +100.0% 1/0/0

🔍 Full Results - additional metrics and failure investigation steps

To investigate failures, paste this to your AI coding agent:

For PR 945 in dotnet/skills, download eval artifacts with gh run download 30150316653 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/44a013c101c4224eb24acbd3c2145fe9c2d66d67/eng/vally-adapter/InvestigatingResults.md and follow it to analyze the results.json files. Diagnose each failure, suggest fixes to the eval.yaml and skill content, and tell me what to fix first.

▶ Sessions Visualisation -- interactive replay of all evaluation sessions
📊 Session Analytics (preview) -- aggregated metrics across evaluation sessions

@github-actions github-actions Bot added waiting-on-review PR state label and removed pr-state/ready-for-eval PR is mergeable and awaiting evaluation labels Jul 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Evaluation passed for 44a013c. cc @dotnet/dotnet-testing — please review.

The PR eval reported 5 of 10 skills as "no credible improvement". Reproducing
the gate arithmetic from the artifacts shows the dominant cause is statistical
power, not skill quality.

The gate is `mean > 0 AND ci_low > 0` with a t-based CI over per-trial scores,
which reduces to `sqrt(n) * (mean/sd) > t(n-1)`. The required mean/sd ratio is
brutal at small n:

  n=2 -> 8.98    n=3 -> 2.48    n=4 -> 1.59
  n=5 -> 1.24    n=6 -> 1.05    n=8 -> 0.84

Recomputing each reported CI from the per-trial scores reproduces the published
numbers exactly, which confirms the mechanism:

  crap-score                [0.4,1.0,0.4]        n=3 CI [-0.261, 1.461]
  migrate-static-to-wrapper [1.0,0.4,0.4]        n=3 CI [-0.261, 1.461]
  test-gap-analysis         [0.4,0,0.4,0.4]      n=4 CI [-0.018, 0.618]
  test-anti-patterns        [0.4,0,0.4,0,0,0.4]  n=6 CI [-0.030, 0.430]
  code-testing-agent        [0,0.4]              n=2 CI [-2.341, 2.741]

crap-score and migrate-static-to-wrapper won 100% of their trials (3W/0T/0L)
and still failed: at n=3 nothing short of three identically-sized wins can
clear the gate. That is a property of a thin eval, not of the skill.

Scenario counts are raised with discriminating cases, four of them by wiring up
fixtures that were already committed but had no stimulus referencing them:

- test-gap-analysis 4 -> 6, using the orphaned `report-quality` fixture (trivial
  auto-properties and an auto-generated .g.cs to skip, private helpers reachable
  only through the public API, and a deliberately weak Assert.IsTrue that cannot
  kill arithmetic mutations) and the orphaned `rust-error-propagation` fixture
  (an untested `?` propagation path and an untested `<=` boundary).
- test-anti-patterns 6 -> 8, using the orphaned `pytest-mixed` fixture (which
  also checks the calibration rule that pytest's bare `assert` must not be
  flagged) and the orphaned `assertion-problems` fixture (which separates
  Critical false-confidence assertions from a Low-severity message nit).
- crap-score 3 -> 6, with a new `refactor-required` fixture whose numbers are
  self-consistent: ApplySurcharges has complexity 13 behind a stale
  "Complexity: 4" comment (CRAP 28.4, needs 77.2% coverage), ClassifyAccount has
  complexity 17 so coverage alone can never reach CRAP < 15, and RoundToCurrency
  is 100% covered so its CRAP equals its complexity exactly.
- migrate-static-to-wrapper 3 -> 5, adding a DateTimeKind-preservation scenario
  over the existing fixture and a new `static-helper` fixture where a static
  class must gain an ambient TimeProvider seam without breaking its callers.
- code-testing-agent 2 -> 3, with a compact C# fixture that must extend an
  existing suite to the untested method only. This eval stays the weakest: each
  scenario is expensive, so raising `runs` is a better lever than adding more
  heavyweight scenarios.

Verification:
- every eval spec parses and all 254 fixture references resolve
- the three new fixtures compile; the shipping-quotes fixture restores, builds
  and its three seed tests pass under `dotnet test` in a clean workspace
- skill-validator check passes (20 skills, 10 agents)
- markdownlint clean

Refs #899

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
Copilot AI review requested due to automatic review settings July 26, 2026 13:16
@Evangelink

Copy link
Copy Markdown
Member Author

Diagnosing the 5 "no credible improvement" results

I pulled the artifacts from run 30150316653 and recomputed the gate arithmetic from the per-trial scores. The dominant cause is statistical power, not skill quality.

The gate is mean > 0 AND ci_low > 0 over a t-based CI, which reduces to:

sqrt(n) × (mean / sd) > t(n-1)

The required mean/sd ratio is brutal at small n:

n 2 3 4 5 6 8
required mean/sd 8.98 2.48 1.59 1.24 1.05 0.84

Recomputing each published CI from its per-trial scores reproduces the reported numbers exactly, which confirms the mechanism:

Skill Per-trial scores n Recomputed CI Reported CI
crap-score 0.4, 1.0, 0.4 3 [-0.261, 1.461] [-26.1%, 146.1%] ✓
migrate-static-to-wrapper 1.0, 0.4, 0.4 3 [-0.261, 1.461] [-26.1%, 146.1%] ✓
test-gap-analysis 0.4, 0, 0.4, 0.4 4 [-0.018, 0.618] [-1.8%, 61.8%] ✓
test-anti-patterns 0.4, 0, 0.4, 0, 0, 0.4 6 [-0.030, 0.430] [-3.0%, 43.0%] ✓
code-testing-agent 0, 0.4 2 [-2.341, 2.741] [-234.1%, 274.1%] ✓

The headline consequence: crap-score and migrate-static-to-wrapper won 100% of their trials (3W/0T/0L) and still failed. At n=3, nothing short of three identically sized wins can clear the gate. That is a property of a thin eval, not of the skill. Symmetrically, coverage-analysis and test-smell-detection passed only because every trial landed on exactly +0.4 (sd = 0) — a fragile pass that a single differently-sized win would flip.

Secondary cause: two structural sources of zeros

  • Dormancy guards. A reject_skills: ["*"] stimulus forces the skilled arm to be skill-free, so it is compared against an identical baseline. The judges said as much — "Both responses are virtually identical", "Overall quality is equivalent". Its expected preference is 0, so it can only ever drag the mean down and add variance. test-gap-analysis's sole tie was exactly this.
  • Position-swap instability. All three test-anti-patterns ties and the code-testing-agent tie were logged as "Position-swap inconsistent" — the skilled answer was better in one direction only. That is a real signal (marginal, not decisive advantage), and it is what the depth-bar and evidence-table changes already in this PR target.

What this commit changes

Scenario counts are raised with discriminating cases. Four of the new scenarios wire up fixtures that were already committed to the repo but had no stimulus referencing them — purpose-built discriminators that were simply never connected:

Eval n Added
crap-score 3 → 6 New refactor-required fixture
test-gap-analysis 4 → 6 Orphaned report-quality + rust-error-propagation
test-anti-patterns 6 → 8 Orphaned pytest-mixed + assertion-problems
migrate-static-to-wrapper 3 → 5 DateTimeKind scenario + new static-helper fixture
code-testing-agent 2 → 3 Compact csharp-shipping-quotes fixture

The new crap-score fixture is arithmetically self-consistent and targets three things baselines routinely get wrong:

  • ApplySurcharges — complexity 13 hiding behind a stale // Complexity: 4 comment (CRAP 28.4; needs 77.2% coverage to reach <15)
  • ClassifyAccount — complexity 17, so coverage alone can never reach CRAP < 15 → refactor required
  • RoundToCurrency — 100% covered, so CRAP equals its complexity exactly

Projected effect, assuming observed per-scenario behaviour repeats and the new scenarios win at the modal +0.4:

Skill Before After
crap-score [-0.261, 1.461] ❌ [+0.243, +0.757] ✅
migrate-static-to-wrapper [-0.261, 1.461] ❌ [+0.187, +0.853] ✅
test-gap-analysis [-0.018, 0.618] ❌ [+0.162, +0.505] ✅
test-anti-patterns [-0.030, 0.430] ❌ [+0.077, +0.423] ✅
code-testing-agent [-2.341, 2.741] ❌ still underpowered ⚠️

Known residual: code-testing-agent

At n=3 it passes only if all three trials score identically. I deliberately did not pad it with more heavyweight scenarios — each one runs npm ci or a full pytest install, and this skill already dominates eval runtime (it was a major contributor to the 300-minute cell timeouts in the #899 matrix). For expensive skills the correct lever is runs, not scenario count: runs=3 turns 3 scenarios into 9 trials (required ratio drops to 0.77) with no new fixtures. That is consistent with the cross-family matrix, which ran runs=3 and did credit this skill with a pass on GPT.

Verification

  • every eval spec parses; all 254 fixture references resolve
  • all three new fixtures compile; csharp-shipping-quotes restores, builds and its 3 seed tests pass under dotnet test in a clean workspace (it uses the repo's Microsoft.Testing.Platform convention)
  • skill-validator check passes — 20 skills, 10 agents
  • markdownlint clean

Follow-up worth considering separately

Three fixtures remain orphaned in evals this PR does not touch: test-smell-detection/{junit-smells, skip-and-magic} and test-tagging/go-report-only. Wiring them up would harden two currently fragile passes (both rest on sd = 0). I left them out to keep this PR scoped.

Also worth a maintainer decision: since a dormancy-guard scenario can only ever score ≤ 0 in a preference comparison, the adapter could exclude expect_activation: false stimuli from the preference verdict — the activation metric is already the correct instrument for policing dormancy. I have not changed the adapter here, because it would alter scoring for every plugin in a PR whose own numbers it would improve.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 24/24 changed files
  • Comments generated: 2

Comment thread plugins/dotnet-test/skills/detect-static-dependencies/SKILL.md Outdated
@github-actions github-actions Bot added waiting-on-author PR state label and removed waiting-on-review PR state label labels Jul 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 @Evangelink — this PR has 2 unresolved review thread(s). When you're ready, please address the feedback and push an update; the triage bot will pick up the next state automatically. (Add the no-stale label to silence further pings.)

- BillableWeightTests: the ZeroOrNegative test only asserted the zero case, so
  its name overstated what it covered. Made it data-driven over 0 and -1 so the
  name matches the assertions. This matters more than usual here: the file is
  the seed suite for a test-quality eval, and a misleading test name is exactly
  what these skills are supposed to flag.

- detect-static-dependencies: the Step 3 lead-in said to count each "static call
  pattern", which contradicted the rule immediately below it that instance
  members reaching the same untestable resource must also be counted. Reworded
  to "call site" and made the instance-member inclusion explicit.

Verified: the fixture restores, builds and now passes 4 tests (was 3);
skill-validator check passes; markdownlint clean.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
Copilot AI review requested due to automatic review settings July 26, 2026 19:37
@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate

@Evangelink
Evangelink enabled auto-merge (squash) July 26, 2026 19:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 24/24 changed files
  • Comments generated: 2

Comment thread tests/dotnet-test/crap-score/eval.yaml
@github-actions

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

9 skill(s) evaluated — 6 improved, 3 no credible improvement. A skill passes only on a credible improvement over baseline (mean preference > 0 with its 95% CI above 0); ⚠️ marks a comparison that couldn't complete (errored/unmatched trials).

Skill Result Δ Preference [95% CI] W/T/L Quality Baseline Overfit Skills Loaded
assertion-quality +16.0% [-28.4%, +60.4%] 3/1/1 4.7/5 4.5/5 🟡 0.24 4/4 · 4/4 (plugin)
code-testing-agent +0.0% [-99.4%, +99.4%] 1/1/1 4.6/5 3.7/5 🟡 0.35 ⚠️ 2/3 · 3/3 (plugin)
coverage-analysis +40.0% [+40.0%, +40.0%] 3/0/0 4.4/5 3.6/5 ✅ 0.08 3/3 · 3/3 (plugin)
detect-static-dependencies +40.0% [+40.0%, +40.0%] 6/0/0 4.5/5 4.4/5 ✅ 0.11 ⚠️ 5/6 · 6/6 (plugin)
migrate-static-to-wrapper +20.0% [-54.5%, +94.5%] 3/0/2 4.7/5 4.0/5 ✅ 0.09 ⚠️ 3/5 · 4/5 (plugin)
test-anti-patterns +30.0% [+14.5%, +45.5%] 6/2/0 4.8/5 4.1/5 🟡 0.20 ⚠️ 7/8 · 8/8 (plugin)
test-gap-analysis +33.3% [+16.2%, +50.5%] 5/1/0 4.8/5 4.7/5 ✅ 0.11 5/5 · 5/5 (plugin)
test-smell-detection +40.0% [+40.0%, +40.0%] 4/0/0 5.0/5 4.5/5 🟡 0.38 3/3 · 3/3 (plugin)
test-tagging +42.5% [+1.1%, +83.9%] 4/4/0 4.5/5 4.1/5 🟡 0.33 ⚠️ 6/7 · 6/7 (plugin)
ℹ️ Column legend
  • Δ Preference — mean head-to-head preference of skilled vs baseline (−100%…+100%), judged by vally compare.
  • [95% CI] — 95% confidence interval on that mean; a skill passes only when the whole interval is above 0.
  • W/T/L — wins / ties / losses across trials.
  • Quality / Baseline — mean absolute judge score 0–5 (skilled isolated vs skill-free control).
  • Overfit — overfitting-judge severity (✅ Low, 🟡 Moderate, 🔴 High, — none) with its score.
  • Skills Loaded — of the scenarios that expect activation, how many actually activated / that total (plugin run shown when present); ⚠️ marks a scenario that expected activation but didn't activate.
❌ assertion-quality — details

Reason: Mean preference +16.0% [95% CI -28.4%, 60.4%], win rate 60.0% (3W/1T/1L over 5 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▼ Decline request to write new tests from scratch -40.0% 0/0/1
▲ Flag assertion-free tests and trivial-only assertions +40.0% 1/0/0
▲ Identify low assertion diversity in equality-dominated test suite +40.0% 1/0/0
= Identify self-referential assertions in identity and round-trip tests +0.0% 0/1/0
▲ Recognize well-diversified assertion usage +40.0% 1/0/0
❌ code-testing-agent — details

Reason: Mean preference +0.0% [95% CI -99.4%, 99.4%], win rate 33.3% (1W/1T/1L over 3 trial(s)) — no improvement

Scenario Mean preference Trials (W/T/L)
▼ Does not revert a gutted-looking workspace (workspace integrity) -40.0% 0/0/1
= Extend an existing suite to the untested method only +0.0% 0/1/0
▲ Generate Vitest tests for the shopping-cart library (TypeScript polyglot) +40.0% 1/0/0
✅ coverage-analysis — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (3W/0T/0L over 3 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +40.0% 1/0/0
▲ Run coverage from scratch without existing data +40.0% 1/0/0
✅ detect-static-dependencies — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (6W/0T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Decline scan for non-C# project +40.0% 1/0/0
▲ Detect statics inside lambda expressions and LINQ queries +40.0% 1/0/0
▲ Detect time-related statics and recommend TimeProvider +40.0% 1/0/0
▲ Exclude obj and bin directories from the scan +40.0% 1/0/0
▲ Identify static dependencies in a multi-class project +40.0% 1/0/0
▲ Verify structured report includes file count, categories, and top patterns +40.0% 1/0/0
❌ migrate-static-to-wrapper — details

Reason: Mean preference +20.0% [95% CI -54.5%, 94.5%], win rate 60.0% (3W/0T/2L over 5 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Decline migration when wrapper does not exist yet +100.0% 1/0/0
▲ Migrate DateTime.UtcNow to TimeProvider in a service class +40.0% 1/0/0
▼ Migrate a static helper class without breaking its callers -40.0% 0/0/1
▲ Migrate only in scoped files, leaving others untouched +40.0% 1/0/0
▼ Preserve DateTimeKind when migrating to TimeProvider -40.0% 0/0/1
✅ test-anti-patterns — details

Reason: Mean preference +30.0% [95% CI 14.5%, 45.5%], win rate 75.0% (6W/2T/0L over 8 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Audit a pytest suite using Python-specific anti-pattern markers +40.0% 1/0/0
▲ Detect coverage-touching pattern across a service facade +40.0% 1/0/0
= Detect duplicated tests and magic values +0.0% 0/1/0
▲ Detect flakiness indicators and test coupling +40.0% 1/0/0
= Detect mixed severity anti-patterns in repository service tests +0.0% 0/1/0
▲ Detect self-referential assertions in round-trip and identity tests +40.0% 1/0/0
▲ Recognize well-written tests without inventing false positives +40.0% 1/0/0
▲ Separate false-confidence assertions from cosmetic ones +40.0% 1/0/0
✅ test-gap-analysis — details

Reason: Mean preference +33.3% [95% CI 16.2%, 50.5%], win rate 83.3% (5W/1T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
= Acknowledge well-tested code with few surviving mutations +0.0% 0/1/0
▲ Analyse error propagation gaps in a Rust crate +40.0% 1/0/0
▲ Decline request to write new tests from scratch +40.0% 1/0/0
▲ Find boundary mutation gaps in tiered discount and shipping logic +40.0% 1/0/0
▲ Find logic and null-check mutation gaps in access control code +40.0% 1/0/0
▲ Skip trivial and generated code while tracing private call chains +40.0% 1/0/0
✅ test-smell-detection — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (4W/0T/0L over 4 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Decline request to write new tests from scratch +40.0% 1/0/0
▲ Detect multiple test smells in order processing test suite +40.0% 1/0/0
▲ Recognize integration tests and avoid false positives for external resources +40.0% 1/0/0
▲ Recognize well-written tests with no significant smells +40.0% 1/0/0
✅ test-tagging — details

Reason: Mean preference +42.5% [95% CI 1.1%, 83.9%], win rate 50.0% (4W/4T/0L over 8 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Accurately classify NUnit tests with misleading method names +40.0% 1/0/0
= Audit test distribution without modifying files +0.0% 0/1/0
= Decline request to write new tests +0.0% 0/1/0
= Tag MSTest tests and verify the project still builds +0.0% 0/1/0
= Tag a partially-tagged MSTest suite without duplicating existing traits +0.0% 0/1/0
▲ Tag an untagged MSTest test suite +100.0% 1/0/0
▲ Tag an untagged NUnit test suite +100.0% 1/0/0
▲ Tag an untagged xUnit test suite +100.0% 1/0/0

🔍 Full Results - additional metrics and failure investigation steps

To investigate failures, paste this to your AI coding agent:

For PR 945 in dotnet/skills, download eval artifacts with gh run download 30217274869 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/d612728fd4edf188f187bbce8714b763b27ff58a/eng/vally-adapter/InvestigatingResults.md and follow it to analyze the results.json files. Diagnose each failure, suggest fixes to the eval.yaml and skill content, and tell me what to fix first.

▶ Sessions Visualisation -- interactive replay of all evaluation sessions
📊 Session Analytics (preview) -- aggregated metrics across evaluation sessions

Diagnosed from run 30217274869 artifacts (per-trial evidence plus the raw
_experiment trajectories), not from the summary table.

1. Untracked Cobertura fixture (review finding, real bug)

`.gitignore` line 168 (`coverage*.xml`) silently swallowed
tests/dotnet-test/crap-score/fixtures/refactor-required/coverage.cobertura.xml,
so `git add -A` never staged it. Three crap-score scenarios reference that file
and would have failed at setup in CI. My previous verification only checked the
working tree, which is why it passed locally.

Added a negation for `tests/**/fixtures/**/coverage*.{xml,json,info}` so eval
fixtures are never silently dropped again — the three pre-existing coverage
fixtures in the repo had to be force-added for the same reason. The verification
script now asserts every referenced fixture is *tracked by git*, not merely
present on disk.

2. migrate-static-to-wrapper — missed activation, self-inflicted

Both losing scenarios recorded skillActivationCount = 0: the skill never loaded,
so the "skilled" arm was effectively unassisted and lost on its own merits. The
skill content was right; discovery was broken.

Root cause is an internal contradiction. Step 3 documents the ambient-seam
pattern for static classes and states the seam "is the answer", yet
`When Not to Use` said "the code does not use dependency injection and the user
hasn't chosen ambient context" — which describes the static-helper scenario
exactly, so the model correctly followed the skill's own guidance and declined.
`DO NOT USE FOR` also excluded "creating or registering the wrapper when it does
not exist yet", which reads as excluding built-ins like TimeProvider.

Fixed the contradiction and added the missing triggers (static/utility class
needing an ambient seam; behavior-preserving refactors that must keep
DateTimeKind). Descriptions were then trimmed to stay inside the 1,024-char
per-skill and 15,000-char plugin menu budgets.

Worth noting the unassisted arm produced `GetLocalNow().DateTime` — the exact
DateTimeKind regression #906 fixed — and migrated out of scope. The scenario is
doing its job; it just never had the skill loaded.

3. code-testing-agent — missed activation + format-shaped rubrics

The workspace-integrity loss also had skillActivationCount = 0, so no completion
contract was produced and the judge scored it marginally below baseline. Made
the trigger explicit for "standard test-generation workflow" phrasing, sparse
workspaces, and extending an existing suite.

Overfitting was the highest in the plugin (0.35), driven by three rubric items
scored vocabulary/0.85 that demanded the literal `Requirement | Evidence` table:
"a developer who writes perfect tests but doesn't produce this exact table
format would fail". Reshaped all three to the underlying outcome — every
requirement individually traceable to a named test. The graders still enforce
the skill's stated contract, so the requirement is not weakened.

4. assertion-quality — dormancy guard measured nothing

The -40% loss was on a stimulus carrying `reject_skills: ["*"]`, which forces
the skilled arm skill-free. Treatment then equals control by construction, so
the head-to-head score is pure judge noise: across the four evals using this
pattern it landed on -0.4, +0.4, +0.4 and 0. Here it cost the pass.

The repo's own convention for dormancy guards is `expect_activation: false`
with no `reject_skills` (agent.test-quality-auditor, agent.test-migration,
system-text-json-net11). Aligned this guard with it, so the skill is loaded and
the property actually under test is that it stays dormant on an off-target
request. Left the three passing evals' guards alone to avoid churning results
this change does not need to touch.

Also wired up the orphaned `jest-shallow` fixture — shallow toBeDefined /
toBeTruthy assertions, an always-true, a self-comparison, an un-awaited async
assertion that silently passes, plus two legitimately good assertions for
calibration — which raises n from 5 to 6 and adds polyglot coverage.

Reshaped the technique-shaped rubric items flagged in assertion-quality and
migrate-static-to-wrapper so they score the result rather than whether the
answer used the skill's taxonomy words.

Verification: skill-validator check passes (20 skills, 10 agents, plugin menu
15,000-char budget respected); all 257 fixture references parse, exist and are
git-tracked; markdownlint clean.

Refs #899

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
Copilot AI review requested due to automatic review settings July 27, 2026 07:49
Evangelink added a commit that referenced this pull request Jul 28, 2026
Split out of #945, where every defect class below was first found the hard way:
as a skill mysteriously losing to its own baseline, with the eval specs parsing
cleanly and skill-validator passing throughout.

## Why

An audit of all 97 eval specs found:

| Finding | Scale |
| --- | --- |
| Skills with no eval at all | 5 |
| Evals at n=1 (one judge call decides pass/fail) | 18 |
| Evals at n=2 | 10 |
| Evals at n=3 | 21 |
| **Underpowered (n<=3) of 93 skill evals** | **49 (53%)** |
| Orphaned fixtures (committed, never referenced) | 12 |
| Cobertura methods whose line-rate contradicts own <lines> | 6 |

None of this was visible to any existing check.

## What fails the build

All four are structural — they inspect file existence, git state, or YAML keys,
never prose — so they cannot fire spuriously on a well-written eval:

1. A referenced fixture missing on disk.
2. A referenced fixture not tracked by git. `.gitignore` carries `coverage*.xml`
   (correct for Coverlet output) and silently swallowed a committed Cobertura
   *fixture*: `git add -A` reported success, the eval passed locally, and three
   scenarios would have failed at setup in CI. Only the git index catches this.
3. A Cobertura fixture whose declared `line-rate` contradicts its own `<lines>`.
   The crap-score skill documents both parse paths, so the two arms of a
   comparison can read different coverage inputs and the eval measures the
   disagreement instead of the skill. Observed live: a scenario lost -40% with
   the judge writing "Response B made a critical error by manually counting line
   hits (12/15 = 80%) instead of using the XML's recorded line-rate of 0.55" —
   the fixture was wrong, not the response.
4. A dormancy guard (`expect_activation: false`) that also sets `reject_skills`.
   That forces the skilled arm skill-free, making it identical to the baseline
   arm, so the score is judge noise. The same guard scored -0.4, +0.4, +0.4 and
   0 across four evals and twice cost a skill its pass.

## What only warns

Statistical power, orphaned fixtures, uncovered skills, and dormancy guards that
appear to lack an anti-hijack rubric item.

Two deliberate calls here:

- The anti-hijack check began as an error and immediately false-positived on a
  well-formed guard in `system-text-json-net11` whose rubric reads "Does NOT
  load or reference the system-text-json-net11 skill". Detecting that property
  needs phrase matching over free text and will always have false positives, so
  it warns. A gate that blocks a PR spuriously is a gate the team switches off.
- Failing on power would break 28 existing evals across 8 plugins, and the fix
  (raising `runs`) is a CI cost decision. See #952.

## Self-test

`selftest_eval_quality.py` injects each defect into a scratch tree, asserts the
gate rejects it, and asserts a clean tree passes. CI runs the self-test *before*
the gate, so the gate cannot silently stop working.

Refs #899, #952.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
Evangelink added a commit that referenced this pull request Jul 28, 2026
A grader whose config is absent, null, or missing its required key
parses as valid YAML and enforces nothing, so the scenario appears to
carry one more assertion than it really does. The usual cause is an
indentation slip during an edit, which leaves `- type: output-matches` /
`config:` with the pattern attached to the next list item.

This is not hypothetical: it shipped in 705b876 on #945 and was caught
only by review. It survived YAML validation (the document parses) and a
bespoke regex validator, which did `(g.get("config") or {}).get(
"pattern")` and silently skipped the broken entry - so the pattern count
was 262 both before and after the fix.

Verified end to end: restoring the broken eval.yaml from 705b876 into
the working tree makes the gate exit 1 with

  'Detect time-related statics and recommend TimeProvider' grader[3]
  (output-matches) has no config; it silently enforces nothing. Check the
  indentation of the 'pattern:' line.

and the fixed tree is clean. The self-test gains a matching case (now
8/8) that injects the same indentation slip.

Swept the repo while adding this: 1,906 graders across 97 specs, no other
instance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate 9b2b3bb

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Comments suppressed due to low confidence (2)

tests/dotnet-test/migrate-static-to-wrapper/eval.yaml:131

  • The new "Migrate a static helper class" stimulus graders only assert that RetentionPolicy.cs contains the words "TimeProvider" and "static". That can still pass even if the migration leaves DateTime.UtcNow in place, doesn’t default the seam to TimeProvider.System, or changes method signatures (all of which the rubric says must not happen). Strengthen the graders to enforce the key invariants directly from the file content.
    graders:
      - type: file-contains
        config:
          path: "**/RetentionPolicy.cs"
          value: TimeProvider

tests/dotnet-test/migrate-static-to-wrapper/fixtures/static-helper/Utilities/RetentionPolicy.cs:6

  • This fixture’s XML doc comment claims there are "~40 call sites" in the codebase, but this fixture directory currently contains only RetentionPolicy.cs and its .csproj (no callers). That makes the comment misleading for anyone reading the fixture in isolation; consider describing the fixture’s purpose/invariants instead of referencing non-existent files.
/// <summary>
/// Called from ~40 call sites across the codebase as <c>RetentionPolicy.IsExpired(...)</c>.
/// Callers must keep working unchanged.
/// </summary>
  • Files reviewed: 29/30 changed files
  • Comments generated: 0 new

@github-actions github-actions Bot added pr-state/ready-for-eval PR is mergeable and awaiting evaluation and removed waiting-on-author PR state label labels Jul 28, 2026
@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate 9b2b3bb

@Evangelink Evangelink left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/evaluate

@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate 9b2b3bb

@AbhitejJohn AbhitejJohn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/evaluate

* Add a CI quality gate for evaluation specs and fixtures

Split out of #945, where every defect class below was first found the hard way:
as a skill mysteriously losing to its own baseline, with the eval specs parsing
cleanly and skill-validator passing throughout.

## Why

An audit of all 97 eval specs found:

| Finding | Scale |
| --- | --- |
| Skills with no eval at all | 5 |
| Evals at n=1 (one judge call decides pass/fail) | 18 |
| Evals at n=2 | 10 |
| Evals at n=3 | 21 |
| **Underpowered (n<=3) of 93 skill evals** | **49 (53%)** |
| Orphaned fixtures (committed, never referenced) | 12 |
| Cobertura methods whose line-rate contradicts own <lines> | 6 |

None of this was visible to any existing check.

## What fails the build

All four are structural — they inspect file existence, git state, or YAML keys,
never prose — so they cannot fire spuriously on a well-written eval:

1. A referenced fixture missing on disk.
2. A referenced fixture not tracked by git. `.gitignore` carries `coverage*.xml`
   (correct for Coverlet output) and silently swallowed a committed Cobertura
   *fixture*: `git add -A` reported success, the eval passed locally, and three
   scenarios would have failed at setup in CI. Only the git index catches this.
3. A Cobertura fixture whose declared `line-rate` contradicts its own `<lines>`.
   The crap-score skill documents both parse paths, so the two arms of a
   comparison can read different coverage inputs and the eval measures the
   disagreement instead of the skill. Observed live: a scenario lost -40% with
   the judge writing "Response B made a critical error by manually counting line
   hits (12/15 = 80%) instead of using the XML's recorded line-rate of 0.55" —
   the fixture was wrong, not the response.
4. A dormancy guard (`expect_activation: false`) that also sets `reject_skills`.
   That forces the skilled arm skill-free, making it identical to the baseline
   arm, so the score is judge noise. The same guard scored -0.4, +0.4, +0.4 and
   0 across four evals and twice cost a skill its pass.

## What only warns

Statistical power, orphaned fixtures, uncovered skills, and dormancy guards that
appear to lack an anti-hijack rubric item.

Two deliberate calls here:

- The anti-hijack check began as an error and immediately false-positived on a
  well-formed guard in `system-text-json-net11` whose rubric reads "Does NOT
  load or reference the system-text-json-net11 skill". Detecting that property
  needs phrase matching over free text and will always have false positives, so
  it warns. A gate that blocks a PR spuriously is a gate the team switches off.
- Failing on power would break 28 existing evals across 8 plugins, and the fix
  (raising `runs`) is a CI cost decision. See #952.

## Self-test

`selftest_eval_quality.py` injects each defect into a scratch tree, asserts the
gate rejects it, and asserts a clean tree passes. CI runs the self-test *before*
the gate, so the gate cannot silently stop working.

Refs #899, #952.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa

* Address review: index-only tracking check, file-level Cobertura totals

git_tracked_files() unioned `git ls-files` with `git diff --cached
--name-only`. The second arm is redundant for additions (ls-files already
reports the index) and wrong for deletions: a fixture staged for removal
but left on disk appears in diff --cached and was counted back as
"tracked", a false negative for the exact untracked-fixture class the
check exists to catch.

It was invisible because scratch() ran `git init; git add -A` with no
commit, so there was no HEAD, diff --cached errored, and the arm was
inert. scratch() now commits. Verified: with the commit in place and the
two-arm implementation restored, the self-test fails on "fixture present
but NOT tracked by git" (expected=FAIL got=PASS), so the regression is
genuinely covered rather than accidentally passing.

Added a fifth failing check for whole-file Cobertura totals. Review found
coverage-analysis/partial-coverage declaring line-rate="0.47" alongside
lines-covered="35" lines-valid="60" (58.3%); every method was internally
consistent, so the method-level check could not see it. Compares two
declared values, so it cannot fire on well-formed input.

Also reports, as a warning, file/package/class line-rate disagreeing with
the <lines> beneath it. Warning rather than error because the only live
instance (coverage-analysis/fixtures/plateau, declaring 75% against a 47%
payload) cannot be recomputed: its prompt quotes 75% and its rubric
requires CalculateGpa to remain the 0% blocker, which caps the achievable
rate at 48.9%. Failing on it would compel rewriting a passing scenario.

Extended the push trigger to match the pull_request paths. The gate reads
plugins/* and .gitignore, so a merge touching only those could previously
land on main without it running.

Replaced the three bare open() calls with context managers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa

* Add a check for graders that silently enforce nothing

A grader whose config is absent, null, or missing its required key
parses as valid YAML and enforces nothing, so the scenario appears to
carry one more assertion than it really does. The usual cause is an
indentation slip during an edit, which leaves `- type: output-matches` /
`config:` with the pattern attached to the next list item.

This is not hypothetical: it shipped in 705b876 on #945 and was caught
only by review. It survived YAML validation (the document parses) and a
bespoke regex validator, which did `(g.get("config") or {}).get(
"pattern")` and silently skipped the broken entry - so the pattern count
was 262 both before and after the fix.

Verified end to end: restoring the broken eval.yaml from 705b876 into
the working tree makes the gate exit 1 with

  'Detect time-related statics and recommend TimeProvider' grader[3]
  (output-matches) has no config; it silently enforces nothing. Check the
  indentation of the 'pattern:' line.

and the fixed tree is clean. The self-test gains a matching case (now
8/8) that injects the same indentation slip.

Swept the repo while adding this: 1,906 graders across 97 specs, no other
instance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1947263a-0ef9-47bd-ac53-5af5afa3ddaa
Copilot AI review requested due to automatic review settings July 29, 2026 08:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Comments suppressed due to low confidence (2)

eng/eval-quality/selftest_eval_quality.py:90

  • This git rm --cached call ignores the return code. If it fails (path typo, git not installed, etc.), the “untracked fixture” case may no longer be exercising the intended bug class and could yield a false sense of coverage. The self-test should enforce success explicitly.
def untracked_fixture(d):
    # Present on disk but excluded from git — the .gitignore class of bug.
    with open(os.path.join(d, ".gitignore"), "w") as f:
        f.write("Thing.cs\n")
    subprocess.run(["git", "rm", "--cached", "-q",
                    "tests/demo/widget/fixtures/sample/Thing.cs"], cwd=d, capture_output=True)

eng/eval-quality/selftest_eval_quality.py:59

  • In the self-test harness, git add -A is run without check=True, so a failing git command (e.g., bad PATH, permissions, or an unexpected repo state) can be silently ignored and the test may report misleading PASS/FAIL results. Since this script’s purpose is to prove the gate fires reliably, it should fail fast if staging fails.

This issue also appears on line 84 of the same file.

        mutate(d)
        subprocess.run(["git", "add", "-A"], cwd=d, capture_output=True)
        code, out = run_gate(d)
  • Files reviewed: 33/34 changed files
  • Comments generated: 0 new

@Evangelink Evangelink left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/evaluate

@Evangelink
Evangelink temporarily deployed to copilot-pat-pool July 29, 2026 08:06 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

11 skill(s) evaluated — 5 improved, 5 no credible improvement, 1 inconclusive. A skill passes only on a credible improvement over baseline (mean preference > 0 with its 95% CI above 0); ⚠️ marks a comparison that couldn't complete (errored/unmatched trials).

Skill Result Δ Preference [95% CI] W/T/L Quality Baseline Overfit Skills Loaded
assertion-quality +20.0% [-15.1%, +55.1%] 4/1/1 4.9/5 4.3/5 🟡 0.23 5/5 · 5/5 (plugin)
code-testing-agent +0.0% [-99.4%, +99.4%] 1/1/1 5.0/5 3.9/5 🟡 0.37 ⚠️ 3/3 · 2/3 (plugin)
coverage-analysis +40.0% [+40.0%, +40.0%] 3/0/0 4.4/5 3.6/5 ✅ 0.10 3/3 · 3/3 (plugin)
crap-score +43.3% [+9.7%, +77.0%] 5/1/0 4.1/5 4.0/5 ✅ 0.11 6/6 · 6/6 (plugin)
detect-static-dependencies +40.0% [+40.0%, +40.0%] 6/0/0 4.4/5 4.4/5 ✅ 0.08 ⚠️ 5/6 · 6/6 (plugin)
find-untested-sources +30.0% [-1.8%, +61.8%] 3/1/0 4.8/5 4.1/5 🟡 0.36 ⚠️ 4/4 · 3/4 (plugin)
migrate-static-to-wrapper +44.0% [-0.4%, +88.4%] 4/1/0 4.9/5 3.9/5 ✅ 0.07 ⚠️ 5/5 · 4/5 (plugin)
test-anti-patterns +30.0% [+14.5%, +45.5%] 6/2/0 5.0/5 4.6/5 ✅ 0.20 ⚠️ 7/8 · 7/8 (plugin)
test-gap-analysis ⚠️ +8.0% [-33.5%, +49.5%] 2/2/1 4.8/5 4.7/5 ✅ 0.09 ⚠️ 4/5 · 5/5 (plugin)
test-smell-detection +33.3% [+16.2%, +50.5%] 5/1/0 5.0/5 4.8/5 🟡 0.37 ⚠️ 4/5 · 5/5 (plugin)
test-tagging +26.7% [-13.3%, +66.6%] 5/2/2 4.8/5 4.3/5 ✅ 0.19 ⚠️ 5/8 · 4/8 (plugin)
ℹ️ Column legend
  • Δ Preference — mean head-to-head preference of skilled vs baseline (−100%…+100%), judged by vally compare.
  • [95% CI] — 95% confidence interval on that mean; a skill passes only when the whole interval is above 0.
  • W/T/L — wins / ties / losses across trials.
  • Quality / Baseline — mean absolute judge score 0–5 (skilled isolated vs skill-free control).
  • Overfit — overfitting-judge severity (✅ Low, 🟡 Moderate, 🔴 High, — none) with its score.
  • Skills Loaded — of the scenarios that expect activation, how many actually activated / that total (plugin run shown when present); ⚠️ marks a scenario that expected activation but didn't activate.
❌ assertion-quality — details

Reason: Mean preference +20.0% [95% CI -15.1%, 55.1%], win rate 66.7% (4W/1T/1L over 6 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▼ Decline request to write new tests from scratch -40.0% 0/0/1
▲ Flag assertion-free tests and trivial-only assertions +40.0% 1/0/0
▲ Identify low assertion diversity in equality-dominated test suite +40.0% 1/0/0
= Identify self-referential assertions in identity and round-trip tests +0.0% 0/1/0
▲ Judge assertion strength in a shallow Jest suite +40.0% 1/0/0
▲ Recognize well-diversified assertion usage +40.0% 1/0/0
❌ code-testing-agent — details

Reason: Mean preference +0.0% [95% CI -99.4%, 99.4%], win rate 33.3% (1W/1T/1L over 3 trial(s)) — no improvement

Scenario Mean preference Trials (W/T/L)
▲ Does not revert a gutted-looking workspace (workspace integrity) +40.0% 1/0/0
▼ Extend an existing suite to the untested method only -40.0% 0/0/1
= Generate Vitest tests for the shopping-cart library (TypeScript polyglot) +0.0% 0/1/0
✅ coverage-analysis — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (3W/0T/0L over 3 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +40.0% 1/0/0
▲ Run coverage from scratch without existing data +40.0% 1/0/0
✅ crap-score — details

Reason: Mean preference +43.3% [95% CI 9.7%, 77.0%], win rate 83.3% (5W/1T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Calculate CRAP score for a single method with partial coverage +40.0% 1/0/0
▲ Generate coverage then compute CRAP score +100.0% 1/0/0
▲ Identify riskiest methods across a file +40.0% 1/0/0
▲ Recognize when complexity alone blocks the CRAP threshold +40.0% 1/0/0
▲ Recompute complexity instead of trusting a stale source comment +40.0% 1/0/0
= Report a fully covered method at its complexity floor +0.0% 0/1/0
✅ detect-static-dependencies — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (6W/0T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Decline scan for non-C# project +40.0% 1/0/0
▲ Detect statics inside lambda expressions and LINQ queries +40.0% 1/0/0
▲ Detect time-related statics and recommend TimeProvider +40.0% 1/0/0
▲ Exclude obj and bin directories from the scan +40.0% 1/0/0
▲ Identify static dependencies in a multi-class project +40.0% 1/0/0
▲ Verify structured report includes file count, categories, and top patterns +40.0% 1/0/0
❌ find-untested-sources — details

Reason: Mean preference +30.0% [95% CI -1.8%, 61.8%], win rate 75.0% (3W/1T/0L over 4 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Disambiguate duplicate C# type names by namespace +40.0% 1/0/0
▲ Disambiguate duplicate C# types by nested test namespace +40.0% 1/0/0
▲ Identify an unpaired TypeScript module +40.0% 1/0/0
= Pair sources to tests across a src/tests directory split +0.0% 0/1/0
❌ migrate-static-to-wrapper — details

Reason: Mean preference +44.0% [95% CI -0.4%, 88.4%], win rate 80.0% (4W/1T/0L over 5 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Decline migration when wrapper does not exist yet +100.0% 1/0/0
▲ Migrate DateTime.UtcNow to TimeProvider in a service class +40.0% 1/0/0
= Migrate a static helper class without breaking its callers +0.0% 0/1/0
▲ Migrate only in scoped files, leaving others untouched +40.0% 1/0/0
▲ Preserve DateTimeKind when migrating to TimeProvider +40.0% 1/0/0
✅ test-anti-patterns — details

Reason: Mean preference +30.0% [95% CI 14.5%, 45.5%], win rate 75.0% (6W/2T/0L over 8 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Audit a pytest suite using Python-specific anti-pattern markers +40.0% 1/0/0
▲ Detect coverage-touching pattern across a service facade +40.0% 1/0/0
= Detect duplicated tests and magic values +0.0% 0/1/0
▲ Detect flakiness indicators and test coupling +40.0% 1/0/0
▲ Detect mixed severity anti-patterns in repository service tests +40.0% 1/0/0
▲ Detect self-referential assertions in round-trip and identity tests +40.0% 1/0/0
= Recognize well-written tests without inventing false positives +0.0% 0/1/0
▲ Separate false-confidence assertions from cosmetic ones +40.0% 1/0/0
⚠️ test-gap-analysis — details

Reason: Mean preference +8.0% [95% CI -33.5%, 49.5%], win rate 40.0% (2W/2T/1L over 5 trial(s), 1 unmatched) — inconclusive (unmatched trajectories)

Scenario Mean preference Trials (W/T/L)
= Acknowledge well-tested code with few surviving mutations +0.0% 0/0/0
▼ Analyse error propagation gaps in a Rust crate -40.0% 0/0/1
= Decline request to write new tests from scratch +0.0% 0/1/0
= Find boundary mutation gaps in tiered discount and shipping logic +0.0% 0/1/0
▲ Find logic and null-check mutation gaps in access control code +40.0% 1/0/0
▲ Skip trivial and generated code while tracing private call chains +40.0% 1/0/0
✅ test-smell-detection — details

Reason: Mean preference +33.3% [95% CI 16.2%, 50.5%], win rate 83.3% (5W/1T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Audit a JUnit suite using Java-specific smell markers +40.0% 1/0/0
= Calibrate skips and contextually obvious numbers +0.0% 0/1/0
▲ Decline request to write new tests from scratch +40.0% 1/0/0
▲ Detect multiple test smells in order processing test suite +40.0% 1/0/0
▲ Recognize integration tests and avoid false positives for external resources +40.0% 1/0/0
▲ Recognize well-written tests with no significant smells +40.0% 1/0/0
❌ test-tagging — details

Reason: Mean preference +26.7% [95% CI -13.3%, 66.6%], win rate 55.6% (5W/2T/2L over 9 trial(s)) — not credible (95% CI includes 0)

Scenario Mean preference Trials (W/T/L)
▲ Accurately classify NUnit tests with misleading method names +40.0% 1/0/0
▼ Audit test distribution without modifying files -40.0% 0/0/1
= Classify Go tests where no trait attribute mechanism exists +0.0% 0/1/0
▼ Decline request to write new tests -40.0% 0/0/1
▲ Tag MSTest tests and verify the project still builds +100.0% 1/0/0
= Tag a partially-tagged MSTest suite without duplicating existing traits +0.0% 0/1/0
▲ Tag an untagged MSTest test suite +100.0% 1/0/0
▲ Tag an untagged NUnit test suite +40.0% 1/0/0
▲ Tag an untagged xUnit test suite +40.0% 1/0/0

🔍 Full Results - additional metrics and failure investigation steps

To investigate failures, paste this to your AI coding agent:

For PR 945 in dotnet/skills, download eval artifacts with gh run download 30434166431 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/6ef2aca556932a918e2ffeff92b60d9f5a3abbd9/eng/vally-adapter/InvestigatingResults.md and follow it to analyze the results.json files. Diagnose each failure, suggest fixes to the eval.yaml and skill content, and tell me what to fix first.

▶ Sessions Visualisation -- interactive replay of all evaluation sessions
📊 Session Analytics (preview) -- aggregated metrics across evaluation sessions

@github-actions github-actions Bot added waiting-on-review PR state label and removed pr-state/ready-for-eval PR is mergeable and awaiting evaluation labels Jul 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Evaluation passed for 6ef2aca. cc @AbhitejJohn @JanKrivanek @webreidi @dotnet/dotnet-testing — please review.

@Evangelink
Evangelink merged commit f2eb897 into main Jul 29, 2026
48 checks passed
@Evangelink
Evangelink deleted the dev/amauryleve/dotnet-test-eval-followups branch July 29, 2026 14:28
Evangelink added a commit that referenced this pull request Jul 31, 2026
…aint

Run 30611635547 reported 0 of 5 skills improved. The skills were not the
problem: across 25 trials they scored 16W/8T/1L, every skill winning and
none regressing. Four of the five could not have passed at any record.

The sign test conditions on discordant (non-tie) trials, so at exactly 5
trials the only passing record is a clean 5W/0T/0L sweep. One tie drops
the discordant count to 4, which is back below the floor. My previous
commit raised all five evals to exactly 5 - the eligibility floor, which
the README I wrote says is 'not a guarantee of adequate power'. At the
32% tie rate this run measured, a genuinely-helping skill parked there is
certified about one run in ten; at 15 trials it is about nine in ten.

- runs: 3 on the four evals whose scenarios discriminate (5 -> 15 trials).
- generate-testability-wrappers had a real self-contradiction: When Not to
  Use disclaimed the no-DI case while Step 5 IS the answer for it, so the
  ambient-seam scenario never activated and lost unassisted. Same defect
  #945 fixed in migrate-static-to-wrapper; fixed the same way.
- The already-abstracted scenario is a dormancy guard and now says so.
- code-testing-agent is left at 5 trials on purpose. The only grader its
  baseline ever fails is the Requirement|Evidence table regex - every
  functional grader passes identically in both arms - so it is inert on
  outcomes and more runs would only re-measure a tie at the highest cost
  in the plugin. Removed the skill trigger phrase the overfitting judge
  flagged from the one scenario this PR added.
- adapt.mjs said 'not credible (p=0.500)' for a 1W/4T/0L record, which
  describes a measured null rather than an unwinnable test. It now names
  the tie count and says no record could have passed.
- The gate warns on evals sitting at 5-7 trials; 21 across the repo do.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 024b3241-d7af-418a-b9cd-3bb9ee9bf0ee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-review PR state label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two Cobertura eval fixtures declare a line-rate their own line data contradicts

4 participants