Skip to content

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

Open
Evangelink wants to merge 8 commits into
mainfrom
dev/amauryleve/dotnet-test-eval-followups
Open

Fix dotnet-test findings from the refreshed cross-family eval (#899)#945
Evangelink wants to merge 8 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, and one that lied

  • .gitignore (coverage*.xml) silently swallowed crap-score/fixtures/refactor-required/coverage.cobertura.xml, so three scenarios would have failed at setup in CI. Added a negation for tests/**/fixtures/**/coverage* and hardened verification to assert fixtures are git-tracked, not merely present on disk.
  • That fixture declared line-rate="0.55" while its <lines> implied 0.80. The skill documents both parse paths, so the arms could read different inputs. Rebuilt it consistent. The new invariant check found 6 further inconsistent methods in two pre-existing fixtures — filed as Two Cobertura eval fixtures declare a line-rate their own line data contradicts #950 rather than bundled here, since their rubrics quote the declared figures.

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 30268608409) — 6 improved / 4 not credible

Starting point for this PR was 5 improved / 5 (comment).

Skill Result Δ Preference [95% CI] W/T/L
test-tagging +47.5% [+8.6%, +86.4%] 5/3/0
crap-score +43.3% [+9.7%, +77.0%] 5/1/0
detect-static-dependencies +40.0% [+40.0%, +40.0%] 6/0/0
assertion-quality +33.3% [+16.2%, +50.5%] 5/1/0
test-anti-patterns +30.0% [+14.5%, +45.5%] 6/2/0
test-gap-analysis +26.7% [+5.0%, +48.3%] 4/2/0
coverage-analysis +60.0% [−26.1%, +146.1%] 3/0/0
migrate-static-to-wrapper +44.0% [−0.4%, +88.4%] 4/1/0
test-smell-detection +20.0% [−16.7%, +56.7%] 2/2/0
code-testing-agent −13.3% [−128.1%, +101.4%] 1/0/2

Known limitation: the gate is unstable at one trial per scenario

Three of the four remaining ❌ have zero losses. The gate is mean > 0 ∧ ci_low > 0, i.e. sqrt(n) × (mean/sd) > t(n-1), which demands a mean/sd ratio of 2.48 at n=3:

  • migrate-static-to-wrapper is 4W/1T/0L and missed by 0.4 of a percentage point (ci_low = −0.4%). It passed at +52.0% the previous run with the same eval.
  • coverage-analysis has now won 100% of its trials in four consecutive runs (+40%, +60%, +60%, +60%) and failed all four.
  • test-smell-detection is 2W/2T/0L — no losses.

None of these is a content regression and none has been chased by weakening or deleting scenarios. The durable fix is runs=3, which turns 3 scenarios into 9 trials and drops the required ratio to 0.77. Run-to-run churn among untouched evals is large: test-tagging went 6W/0T/2L → 5W/3T/0L and test-smell-detection 4W/0T/0L → 2W/2T/0L with no eval change between runs.

Open: code-testing-agent judges on test volume

This is the one remaining case with real losses, and it is a consistent, diagnosable pattern rather than variance. Across three runs the judge broke near-ties on raw test count:

Run Verdict text
30253193859 "Response A achieved slightly more coverage (12 vs 11 new tests)"
30261630092 1W/2T/0L — ties
30268608409 "41 vs 35 tests"; "12 tests vs 10"

Both affected scenarios already carry an explicit rubric instruction not to reward a higher raw test count, and the skilled arm scores 4.3–4.9/5 quality against a 3.8–4.0 baseline — it produces focused, correct suites that the judge then ranks below larger ones. This is a rubric-weighting problem rather than a skill defect, and it needs its own change (the scenarios are expensive, so runs=3 would also help here).

Refs #899. Follow-up filed as #950.

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] [negative] (line 194)
  • [CodePattern] [TestMethod] (line 119)
  • [CodePattern] [TestCategory] (line 119)
  • [CodePattern] [boundary] (line 194)
  • [CodePattern] [Fact] (line 127)
  • [CodePattern] [Trait] (line 127)
  • [CodePattern] [Category] (line 135)
  • [CodePattern] [Test] (line 135)

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

Copy link
Copy Markdown
Member Author

/evaluate

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: 25/26 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 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

10 skill(s) evaluated — 6 improved, 4 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 +40.0% [+40.0%, +40.0%] 6/0/0 4.7/5 4.1/5 ✅ 0.19 5/5 · 5/5 (plugin)
code-testing-agent +13.3% [-101.4%, +128.1%] 2/0/1 4.6/5 3.9/5 🟡 0.36 ⚠️ 3/3 · 2/3 (plugin)
coverage-analysis +60.0% [-26.1%, +146.1%] 3/0/0 4.3/5 2.6/5 ✅ 0.10 3/3 · 3/3 (plugin)
crap-score +50.0% [+24.3%, +75.7%] 6/0/0 4.7/5 4.1/5 ✅ 0.09 6/6 · 6/6 (plugin)
detect-static-dependencies +50.0% [+24.3%, +75.7%] 6/0/0 4.4/5 3.8/5 ✅ 0.10 ⚠️ 5/6 · 6/6 (plugin)
migrate-static-to-wrapper +36.0% [-14.9%, +86.9%] 3/2/0 4.9/5 3.8/5 ✅ 0.08 ⚠️ 4/5 · 4/5 (plugin)
test-anti-patterns +40.0% [+40.0%, +40.0%] 8/0/0 4.9/5 4.4/5 ✅ 0.16 ⚠️ 8/8 · 7/8 (plugin)
test-gap-analysis +6.7% [-24.9%, +38.3%] 2/3/1 4.6/5 4.6/5 ✅ 0.09 5/5 · 5/5 (plugin)
test-smell-detection +40.0% [+40.0%, +40.0%] 4/0/0 4.9/5 4.4/5 🟡 0.37 3/3 · 3/3 (plugin)
test-tagging +45.0% [+13.1%, +76.9%] 6/2/0 4.6/5 4.0/5 🟡 0.29 ⚠️ 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 +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 request to write new tests from scratch +40.0% 1/0/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
▲ 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 +13.3% [95% CI -101.4%, 128.1%], win rate 66.7% (2W/0T/1L over 3 trial(s)) — not credible (95% CI includes 0)

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) +40.0% 1/0/0
❌ coverage-analysis — 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)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +100.0% 1/0/0
▲ Run coverage from scratch without existing data +40.0% 1/0/0
✅ crap-score — 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)
▲ 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 +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 +100.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 +36.0% [95% CI -14.9%, 86.9%], win rate 60.0% (3W/2T/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 +0.0% 0/1/0
▲ Preserve DateTimeKind when migrating to TimeProvider +40.0% 1/0/0
✅ test-anti-patterns — details

Reason: Mean preference +40.0% [95% CI 40.0%, 40.0%], win rate 100.0% (8W/0T/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 +40.0% 1/0/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 +40.0% 1/0/0
▲ Separate false-confidence assertions from cosmetic ones +40.0% 1/0/0
❌ test-gap-analysis — details

Reason: Mean preference +6.7% [95% CI -24.9%, 38.3%], win rate 33.3% (2W/3T/1L over 6 trial(s)) — not credible (95% CI includes 0)

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 +0.0% 0/1/0
▼ Decline request to write new tests from scratch -40.0% 0/0/1
= 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 +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 13.1%, 76.9%], win rate 75.0% (6W/2T/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 +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 30253193859 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/c09d9c67de442e63026f3e115fcd5186911d8400/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.

1. test-gap-analysis — regression this PR introduced

Previous run: PASS, +33.3% [16.2, 50.5], 5W/1T/0L.
Latest run:   FAIL, +6.7%, 2W/3T/1L, with -40% on the "Decline request to
write new tests from scratch" guard.

Removing `reject_skills: ["*"]` from that guard (c09d9c6) was correct — it
stopped comparing two identical skill-free arms — but it was only half the
change. Once the skill is actually loaded, the judge scores the stimulus against
its rubric, and this rubric said only:

    - Wrote test methods for the ShoppingCart class
    - Covered the AddItem and GetTotal methods

Neither item mentions the property the guard exists to measure, so the judge
fell back to comparing volume: "Response A produced 12 tests ... Response B
produced 10 active tests ... A edges ahead with more comprehensive test
coverage." Raw test count decides an off-target-request guard.

assertion-quality's equivalent guard got the matching rubric item in the same
commit ("Did not derail into an assertion-quality audit of code the user never
asked about") and scored +40%, 6W/0T/0L. The rubric half was simply not applied
to the other three evals.

Added the anti-hijack criterion and an explicit instruction not to reward raw
test count to the test-gap-analysis, test-smell-detection and test-tagging
guards, so all four now measure the same property.

(The three win->tie collapses in the same skill are all logged
"Position-swap inconsistent" — judge variance at one trial per scenario, not a
content change.)

2. code-testing-agent — "Extend an existing suite to the untested method only"

Lost -40% on: "Both responses successfully completed the task with passing tests
covering all required areas. Response A achieved slightly more coverage (12 vs
11 new tests)." Both satisfied every requirement; the judge broke the tie on one
extra test.

This eval already guards against exactly that, on the other two scenarios:

    Vitest:    "Avoided redundant tests ... a smaller focused suite is not
                penalized merely for having fewer tests than another passing suite"
    workspace: "evaluates suite quality by meaningful, nonredundant cases rather
                than rewarding a higher raw test count"

The scenario I added in 39b406a did not inherit that convention. Added it.

3. code-testing-agent — plugin-arm activation was 2/3

The missing activation is this same scenario (isolated True, plugin False), and
it is correct routing rather than a bug: the fixture was MSTest, and this
skill's own description says "DO NOT USE FOR: MSTest-specific test authoring or
modernization (use writing-mstest-tests)". In the plugin arm the sibling
legitimately won the request.

The scenario is about extending coverage to an untested method with mocked
seams, not about MSTest authoring, so the fixture is the thing that was wrong.
Converted it to xUnit v3 (with TestingPlatformDotnetTestSupport, matching the
repo's Microsoft.Testing.Platform requirement), which removes the overlap and
adds framework diversity — the three scenarios are now xUnit, pytest and Vitest.
Graders are unchanged: they key on the csproj path and the retained test-method
name, both of which survive the conversion.

Verification: the converted fixture restores, builds and passes its 4 seed tests
under `dotnet test` in a clean workspace outside the repo; skill-validator check
passes (20 skills, 10 agents); 257/257 fixture references parse, exist and are
git-tracked; markdownlint clean. The verification script now also asserts that
every `expect_activation: false` guard carries no `reject_skills` and does have
an anti-hijack rubric item.

Refs #899

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

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: 25/26 changed files
  • Comments generated: 1

Comment on lines +168 to +186
environment:
files:
- src: fixtures/csharp-shipping-quotes
dest: fixtures/csharp-shipping-quotes
graders:
- type: run-command
config:
command: sh -c "dotnet test fixtures/csharp-shipping-quotes/tests/ShippingQuotes.Tests.csproj"
expected_exit_code: 0
timeout: 10m
- type: run-command
config:
command: sh -c "grep -q 'BillableWeight_UnderOneKilo_BillsTheOneKiloMinimum' fixtures/csharp-shipping-quotes/tests/BillableWeightTests.cs"
expected_exit_code: 0
timeout: 1m
- type: output-matches
config:
pattern: \|\s*Requirement\s*\|\s*Evidence\s*\|
- type: prompt

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.

Good catch — that guard was too weak for a scenario whose whole purpose is scope containment. Fixed in 5ad3743.

The single grep is replaced by a byte-for-byte assertion:

  • setup snapshots the suite to .eval-baseline/ before the run
  • a grader diff -us the two, so any edit fails
  • a second grader requires some other .cs file under tests/ to reference QuoteAsync, so the new coverage has to land in a new file — and has to exist

I replayed the graders over the fixture with each cheat applied rather than assuming the discrimination:

case new graders old grep
correct: new file, baseline untouched PASS PASS
cheat: appended tests into baseline file FAIL PASS
cheat: rewrote baseline, kept grepped symbol FAIL PASS
cheat: no QuoteAsync tests added at all FAIL PASS

Two robustness details came out of writing it:

  • The snapshot lives in the workspace, not /tmp. No existing eval relies on /tmp surviving from environment.commands into graders, and if those ran in different containers the diff would fail for every run and silently break the scenario. .eval-baseline/ sits outside the test project so it is never compiled.
  • --include='*.cs' is quoted so the shell can't expand the glob before grep sees it.

The prompt now states the requirement explicitly ("put them in a new test file and leave BillableWeightTests.cs exactly as it is"), so the tightened grader checks a stated requirement rather than acting as a trap.

@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate

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

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

10 skill(s) evaluated — 6 improved, 4 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 +20.0% [-15.1%, +55.1%] 4/1/1 4.9/5 4.2/5 🟡 0.22 5/5 · 5/5 (plugin)
code-testing-agent -13.3% [-128.1%, +101.4%] 1/0/2 4.3/5 3.8/5 🟡 0.36 3/3 · 3/3 (plugin)
coverage-analysis +60.0% [-26.1%, +146.1%] 3/0/0 4.6/5 3.0/5 ✅ 0.10 3/3 · 3/3 (plugin)
crap-score +33.3% [+16.2%, +50.5%] 5/1/0 4.6/5 4.0/5 ✅ 0.09 6/6 · 6/6 (plugin)
detect-static-dependencies +40.0% [+40.0%, +40.0%] 6/0/0 5.0/5 4.2/5 ✅ 0.10 ⚠️ 5/6 · 6/6 (plugin)
migrate-static-to-wrapper +52.0% [+18.7%, +85.3%] 5/0/0 4.9/5 3.8/5 ✅ 0.08 ⚠️ 4/5 · 4/5 (plugin)
test-anti-patterns +25.0% [+7.7%, +42.3%] 5/3/0 4.9/5 4.5/5 ✅ 0.07 ⚠️ 6/8 · 8/8 (plugin)
test-gap-analysis +33.3% [+16.2%, +50.5%] 5/1/0 4.7/5 4.7/5 ✅ 0.11 5/5 · 5/5 (plugin)
test-smell-detection +30.0% [-1.8%, +61.8%] 3/1/0 4.8/5 4.4/5 🟡 0.36 3/3 · 3/3 (plugin)
test-tagging +60.0% [+22.1%, +97.9%] 6/2/0 4.7/5 4.1/5 🟡 0.39 ⚠️ 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 +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 -13.3% [95% CI -128.1%, 101.4%], win rate 33.3% (1W/0T/2L 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 -40.0% 0/0/1
▲ Generate Vitest tests for the shopping-cart library (TypeScript polyglot) +40.0% 1/0/0
❌ coverage-analysis — 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)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +100.0% 1/0/0
▲ Run coverage from scratch without existing data +40.0% 1/0/0
✅ crap-score — 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)
▲ Calculate CRAP score for a single method with partial coverage +40.0% 1/0/0
▲ Generate coverage then compute CRAP score +40.0% 1/0/0
▲ Identify riskiest methods across a file +40.0% 1/0/0
= Recognize when complexity alone blocks the CRAP threshold +0.0% 0/1/0
▲ Recompute complexity instead of trusting a stale source comment +40.0% 1/0/0
▲ Report a fully covered method at its complexity floor +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 +52.0% [95% CI 18.7%, 85.3%], win rate 100.0% (5W/0T/0L over 5 trial(s)) — credibly better

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% 1/0/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 +25.0% [95% CI 7.7%, 42.3%], win rate 62.5% (5W/3T/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 +0.0% 0/1/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 +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)
= Decline request to write new tests from scratch +0.0% 0/1/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 +60.0% [95% CI 22.1%, 97.9%], win rate 75.0% (6W/2T/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 +100.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 +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 30254317283 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/c09d9c67de442e63026f3e115fcd5186911d8400/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.

@github-actions

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

10 skill(s) evaluated — 7 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 +33.3% [+16.2%, +50.5%] 5/1/0 4.8/5 4.5/5 🟡 0.23 5/5 · 5/5 (plugin)
code-testing-agent +13.3% [-44.0%, +70.7%] 1/2/0 4.2/5 4.0/5 🟡 0.36 ⚠️ 3/3 · 2/3 (plugin)
coverage-analysis +60.0% [-26.1%, +146.1%] 3/0/0 4.6/5 3.6/5 ✅ 0.08 3/3 · 3/3 (plugin)
crap-score +43.3% [+9.7%, +77.0%] 5/1/0 4.4/5 4.2/5 ✅ 0.09 6/6 · 6/6 (plugin)
detect-static-dependencies +40.0% [+40.0%, +40.0%] 6/0/0 4.4/5 4.1/5 ✅ 0.10 ⚠️ 5/6 · 6/6 (plugin)
migrate-static-to-wrapper +52.0% [+18.7%, +85.3%] 5/0/0 4.9/5 3.8/5 ✅ 0.08 ⚠️ 4/5 · 4/5 (plugin)
test-anti-patterns +20.0% [+2.1%, +37.9%] 4/4/0 5.0/5 4.6/5 ✅ 0.16 ⚠️ 7/8 · 8/8 (plugin)
test-gap-analysis +26.7% [+5.0%, +48.3%] 4/2/0 4.7/5 4.5/5 ✅ 0.10 5/5 · 5/5 (plugin)
test-smell-detection +40.0% [+40.0%, +40.0%] 4/0/0 5.0/5 4.3/5 🟡 0.39 3/3 · 3/3 (plugin)
test-tagging +35.0% [-25.5%, +95.5%] 6/0/2 4.4/5 4.2/5 🟡 0.26 ⚠️ 6/7 · 5/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 +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)
= 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
▲ 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 +13.3% [95% CI -44.0%, 70.7%], win rate 33.3% (1W/2T/0L over 3 trial(s)) — not credible (95% CI includes 0)

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 +0.0% 0/1/0
= Generate Vitest tests for the shopping-cart library (TypeScript polyglot) +0.0% 0/1/0
❌ coverage-analysis — 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)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +100.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
✅ migrate-static-to-wrapper — details

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

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% 1/0/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 +20.0% [95% CI 2.1%, 37.9%], win rate 50.0% (4W/4T/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 +0.0% 0/1/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 +26.7% [95% CI 5.0%, 48.3%], win rate 66.7% (4W/2T/0L over 6 trial(s)) — credibly better

Scenario Mean preference Trials (W/T/L)
▲ Acknowledge well-tested code with few surviving mutations +40.0% 1/0/0
= Analyse error propagation gaps in a Rust crate +0.0% 0/1/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
▲ 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 +35.0% [95% CI -25.5%, 95.5%], win rate 75.0% (6W/0T/2L over 8 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
▼ Decline request to write new tests -100.0% 0/0/1
▲ 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 +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 30261630092 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/ae459271bf63e1ba2a461a0d486e6eddb12b5b61/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

Review finding on the "Extend an existing suite to the untested method only"
scenario: the guard meant to enforce "leave BillableWeightTests.cs untouched"
only grepped for a single method name, so a run could rewrite the file, or
duplicate the BillableWeight coverage into it, and still pass — in a scenario
whose entire purpose is scope containment.

Replaced it with a byte-for-byte assertion:

- setup copies the suite to `.eval-baseline/` before the run
- a grader diffs the two, so any edit at all fails
- a second grader requires some *other* .cs file under tests/ to reference
  QuoteAsync, so the new coverage has to land in a new file and has to exist

Verified the discrimination rather than assuming it, by replaying the graders
over the fixture with each cheat applied:

  correct: new file, baseline untouched          new=PASS  old=PASS
  cheat:   appended tests into baseline file     new=FAIL  old=PASS
  cheat:   rewrote baseline, kept grepped symbol new=FAIL  old=PASS
  cheat:   no QuoteAsync tests added at all      new=FAIL  old=PASS

Two robustness details while writing it:

- The snapshot lives in the workspace, not /tmp. No existing eval relies on
  /tmp surviving from `environment.commands` into graders, and if the two ran
  in different containers the diff would fail for every run and silently break
  the scenario. `.eval-baseline/` sits outside the test project, so it is never
  compiled.
- `--include='*.cs'` is quoted so the shell cannot expand the glob before grep
  sees it.

The prompt now states the requirement explicitly ("put them in a new test file
and leave BillableWeightTests.cs exactly as it is"), so the tightened grader
checks a stated requirement rather than acting as a trap.

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 13:04
@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate

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: 25/26 changed files
  • Comments generated: 0 new

@github-actions

Copy link
Copy Markdown
Contributor

📊 Skill Evaluation Results

10 skill(s) evaluated — 6 improved, 4 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 +33.3% [+16.2%, +50.5%] 5/1/0 4.6/5 4.6/5 🟡 0.24 5/5 · 5/5 (plugin)
code-testing-agent -13.3% [-128.1%, +101.4%] 1/0/2 5.0/5 4.0/5 🟡 0.36 ⚠️ 3/3 · 2/3 (plugin)
coverage-analysis +60.0% [-26.1%, +146.1%] 3/0/0 4.6/5 3.4/5 ✅ 0.10 3/3 · 3/3 (plugin)
crap-score +43.3% [+9.7%, +77.0%] 5/1/0 4.1/5 4.2/5 ✅ 0.10 6/6 · 6/6 (plugin)
detect-static-dependencies +40.0% [+40.0%, +40.0%] 6/0/0 4.6/5 4.2/5 ✅ 0.11 ⚠️ 5/6 · 6/6 (plugin)
migrate-static-to-wrapper +44.0% [-0.4%, +88.4%] 4/1/0 4.8/5 3.9/5 ✅ 0.08 ⚠️ 4/5 · 4/5 (plugin)
test-anti-patterns +30.0% [+14.5%, +45.5%] 6/2/0 4.9/5 4.5/5 ✅ 0.08 ⚠️ 7/8 · 7/8 (plugin)
test-gap-analysis +26.7% [+5.0%, +48.3%] 4/2/0 4.7/5 4.7/5 ✅ 0.10 5/5 · 5/5 (plugin)
test-smell-detection +20.0% [-16.7%, +56.7%] 2/2/0 5.0/5 4.4/5 🟡 0.36 3/3 · 3/3 (plugin)
test-tagging +47.5% [+8.6%, +86.4%] 5/3/0 4.7/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 +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)
▲ Decline request to write new tests from scratch +40.0% 1/0/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 +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 -13.3% [95% CI -128.1%, 101.4%], win rate 33.3% (1W/0T/2L 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 -40.0% 0/0/1
▲ Generate Vitest tests for the shopping-cart library (TypeScript polyglot) +40.0% 1/0/0
❌ coverage-analysis — 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)
▲ Coverage plateau diagnosis +40.0% 1/0/0
▲ Project-wide coverage analysis with existing Cobertura data +100.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
❌ 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 +40.0% 1/0/0
= Migrate only in scoped files, leaving others untouched +0.0% 0/1/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 +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 +26.7% [95% CI 5.0%, 48.3%], win rate 66.7% (4W/2T/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 +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
▲ Skip trivial and generated code while tracing private call chains +40.0% 1/0/0
❌ test-smell-detection — details

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

Scenario Mean preference Trials (W/T/L)
= Decline request to write new tests from scratch +0.0% 0/1/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 +0.0% 0/1/0
▲ Recognize well-written tests with no significant smells +40.0% 1/0/0
✅ test-tagging — details

Reason: Mean preference +47.5% [95% CI 8.6%, 86.4%], win rate 62.5% (5W/3T/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 +40.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 +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 30268608409 --repo dotnet/skills --pattern "vally-results-*" --dir ./eval-results, then fetch https://raw.githubusercontent.com/dotnet/skills/5ad3743af7727c8455e25c8bb54dd47c6015a587/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

A repo-wide audit of all 97 eval specs, rather than another round of
scenario-by-scenario patching.

## Audit findings (measured)

| 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 |

`dotnet-skills.experiment.yaml` sets `runs: 1`, so n is the scenario count and a
single judge call decides each scenario. That is the root cause behind results
like `coverage-analysis` winning 100% of its trials in four consecutive runs and
failing all four.

## 1. eng/eval-quality — a CI gate for defect classes that cost real results

Every failing check corresponds to a bug that has already cost an evaluation on
this repo, and every one was invisible to the existing checks: the specs parsed,
skill-validator passed, and the damage surfaced only as a skill losing to its
own baseline.

Fails on (all structural — file existence, git state, YAML keys, so they cannot
fire spuriously on prose):

- a referenced fixture missing on disk
- a referenced fixture not tracked by git (`.gitignore` silently swallowed a
  Cobertura fixture; it passed locally and would have failed at setup in CI)
- a Cobertura fixture whose declared line-rate contradicts its own <lines>
- a dormancy guard that also sets `reject_skills` (makes the skilled arm
  identical to the baseline arm, so the score is judge noise)

Reports without failing: statistical power, orphaned fixtures, uncovered skills,
and dormancy guards that appear to lack an anti-hijack rubric item. That last
one needs phrase matching over free text and will always have false positives —
it flagged a well-formed guard in `system-text-json-net11` whose rubric says
"Does NOT load or reference the skill" — so it warns rather than blocks. A gate
that fails spuriously is a gate the team switches off. Failing on power would
also break 28 existing evals across 8 plugins, which is a maintainer decision.

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

## 2. Fixed #950 — 6 inconsistent Cobertura methods

`crap-score/partial-coverage` (ProcessOrder, GetOrderStatus, CancelOrder) and
`coverage-analysis/partial-coverage` (Enroll, CalculateGpa, Search).

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 that the risk ordering each rubric asserts still holds:
ProcessOrder remains the top hotspot at CRAP 26.6, CalculateGpa at 52.2.

## 3. Wired up four orphaned fixtures (12 -> 8)

Purpose-built discriminators that were committed but never referenced:

- `test-smell-detection` n=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 contextually obvious counts that must NOT
  be flagged as magic numbers). This skill last failed at 2W/2T/0L — no losses,
  purely n.
- `test-tagging` n=8 -> 9: `go-report-only`. Go has no attribute-based trait
  mechanism, so the scenario checks the skill classifies and reports instead of
  inventing a [TestCategory] equivalent that does not compile.
- `find-untested-sources` n=3 -> 4: `pairing-repo`, a src/ + tests/ split where
  OrderProcessor is unpaired and CustomerService is already covered.

## Verification

- eval quality gate: 0 errors across 97 specs; self-test 6/6
- `actionlint` clean on the new workflow
- skill-validator: 20 skills, 10 agents
- Cobertura fixtures internally consistent and rubric intent preserved

Refs #899. Closes #950.

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 16:21
@Evangelink

Copy link
Copy Markdown
Member Author

/evaluate

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/check_eval_quality.py:164

  • open(...).read() should use a context manager (with open(...) as f:) to ensure the handle is closed promptly, especially when scanning many eval specs.
        fx = os.path.join(os.path.dirname(spec), "fixtures")
        if not os.path.isdir(fx):
            continue
        raw = open(spec, encoding="utf-8").read()
        found += [f"{spec}: fixture '{n}' is committed but no stimulus references it"

eng/eval-quality/check_eval_quality.py:201

  • Like the earlier helper, this YAML load uses open(...) without a context manager. Using with open(...) avoids leaving file descriptors open while iterating all eval specs.
        try:
            doc = yaml.safe_load(open(spec, encoding="utf-8")) or {}
        except yaml.YAMLError as exc:
  • Files reviewed: 32/33 changed files
  • Comments generated: 3

Comment on lines +295 to +311
environment:
files:
- src: fixtures/go-report-only/calculator/go.mod
dest: calculator/go.mod
- src: fixtures/go-report-only/calculator/calculator.go
dest: calculator/calculator.go
- src: fixtures/go-report-only/calculator/calculator_test.go
dest: calculator/calculator_test.go
graders:
- type: output-matches
config:
pattern: (TestDivide_ByZero_ReturnsError|by.?zero|error path|negative)
- type: run-command
config:
command: sh -c "git diff --quiet -- calculator/calculator_test.go || test ! -d .git"
expected_exit_code: 0
timeout: 1m
Comment on lines +21 to +23
paths:
- "tests/**"
- "eng/eval-quality/**"
Comment on lines +141 to +143
for spec in specs:
doc = yaml.safe_load(open(spec, encoding="utf-8")) or {}
n = len(doc.get("stimuli") or [])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-author PR state label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants