Add migrate-xunit-to-mstest skill#718
Conversation
Adds a skill to migrate .NET test projects from xUnit.net (v2 or v3) to MSTest v4. Covers package replacement, attribute/assertion/fixture/lifecycle translation, ITestOutputHelper -> TestContext, [Trait] -> [TestCategory]/ [TestProperty], and xUnit v3 TestContext.Current.CancellationToken. Front-loads parallelization handling because xUnit parallelizes test classes by default while MSTest serializes — the single largest source of post- migration regressions. Step 11 enumerates three explicit choices and how to translate CollectionBehavior/MaxParallelThreads/[Collection] settings. Preserves the existing test platform (VSTest stays VSTest; MTP stays MTP) by default — bundling a platform migration would violate the test-migration agent's 'never mix migration steps' rule. - plugins/dotnet-test/skills/migrate-xunit-to-mstest/SKILL.md (workflow) - plugins/dotnet-test/skills/migrate-xunit-to-mstest/references/mapping-cheatsheet.md - tests/dotnet-test/migrate-xunit-to-mstest/eval.yaml (12 scenarios) - tests/dotnet-test/migrate-xunit-to-mstest/eval.vally.yaml (parity with sibling skill) - plugins/dotnet-test/README.md (table entry) - plugins/dotnet-test/agents/test-migration.agent.md (triage routing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skill Coverage Report
Uncovered:
|
There was a problem hiding this comment.
Pull request overview
Adds a new migrate-xunit-to-mstest skill to the dotnet-test plugin, providing an end-to-end workflow (plus a mapping reference) for migrating xUnit.net v2/v3 test projects to MSTest v4 while preserving the existing test platform (VSTest vs MTP). The PR also adds skill-validator and Vally eval coverage (with fixtures) and updates plugin documentation + the test-migration agent routing table to recognize the new migration path.
Changes:
- Introduces the
migrate-xunit-to-mstestskill with a 13-step migration workflow and a detailed mapping cheatsheet reference. - Adds
eval.yaml,eval.vally.yaml, and fixture projects to validate key migration behaviors (TFM gating, fixtures, parallelization, output, exception semantics, etc.). - Updates
plugins/dotnet-test/README.mdandtest-migration.agent.mdto document and route “xUnit → MSTest” migrations.
Show a summary per file
| File | Description |
|---|---|
| plugins/dotnet-test/skills/migrate-xunit-to-mstest/SKILL.md | New skill workflow document for migrating xUnit v2/v3 → MSTest v4 (preserving platform). |
| plugins/dotnet-test/skills/migrate-xunit-to-mstest/references/mapping-cheatsheet.md | Progressive-disclosure reference tables for attribute/assertion/fixture/lifecycle mappings. |
| plugins/dotnet-test/README.md | Adds the new skill to the migration list and updates the migration summary bullet. |
| plugins/dotnet-test/agents/test-migration.agent.md | Routes “Convert xUnit to MSTest” requests to the new skill and documents sequencing rules. |
| tests/dotnet-test/migrate-xunit-to-mstest/eval.yaml | Skill-validator eval scenarios (inline setup.files) covering 12 migration behaviors. |
| tests/dotnet-test/migrate-xunit-to-mstest/eval.vally.yaml | Vally mirror of the eval scenarios using fixture files. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-iclassfixture-to-classinitialize/TestProject.csproj | Fixture: xUnit v2 project using IClassFixture<T>. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-iclassfixture-to-classinitialize/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-iclassfixture-to-classinitialize/DbFixture.cs | Fixture: disposable class fixture type. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-iclassfixture-to-classinitialize/OrderTests.cs | Fixture: test class using IClassFixture<T>. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-itestoutputhelper-to-testcontext/TestProject.csproj | Fixture: xUnit v2 project using ITestOutputHelper. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-itestoutputhelper-to-testcontext/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-itestoutputhelper-to-testcontext/LoggingTests.cs | Fixture: writes to ITestOutputHelper. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-memberdata-and-theorydata-to-dynamicdata/TestProject.csproj | Fixture: xUnit v2 project using MemberData. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-memberdata-and-theorydata-to-dynamicdata/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-memberdata-and-theorydata-to-dynamicdata/DiscountTests.cs | Fixture: [Theory] + [MemberData] data-driven test. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-skip-trait-and-timeout/TestProject.csproj | Fixture: xUnit v2 project using Skip/Trait/Timeout. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-skip-trait-and-timeout/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-skip-trait-and-timeout/AnnotatedTests.cs | Fixture: [Fact(Skip=...)], [Trait], and Timeout. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-xunit-v3-testcontext-current-cancellationtoken/TestProject.csproj | Fixture: xUnit v3 project using TestContext.Current.CancellationToken. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-xunit-v3-testcontext-current-cancellationtoken/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-xunit-v3-testcontext-current-cancellationtoken/Directory.Build.props | Fixture: pins IsTestingPlatformApplication=false. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/convert-xunit-v3-testcontext-current-cancellationtoken/CancellationTests.cs | Fixture: uses xUnit v3 TestContext.Current.CancellationToken. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/handle-icollectionfixture-explicitly-do-not-silently-widen-scope/TestProject.csproj | Fixture: xUnit v2 collection fixtures setup. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/handle-icollectionfixture-explicitly-do-not-silently-widen-scope/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/handle-icollectionfixture-explicitly-do-not-silently-widen-scope/DbCollection.cs | Fixture: ICollectionFixture<T> + [CollectionDefinition(... DisableParallelization = true)]. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/handle-icollectionfixture-explicitly-do-not-silently-widen-scope/OrdersTests.cs | Fixture: member class using [Collection("Db")]. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/handle-icollectionfixture-explicitly-do-not-silently-widen-scope/CustomersTests.cs | Fixture: second member class using [Collection("Db")]. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/map-exception-assertions-correctly-xunit-throws-mstest-throwsexactly/TestProject.csproj | Fixture: xUnit v2 exception assertion semantics. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/map-exception-assertions-correctly-xunit-throws-mstest-throwsexactly/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/map-exception-assertions-correctly-xunit-throws-mstest-throwsexactly/ExceptionTests.cs | Fixture: Throws vs ThrowsAny (+ async variant). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v2-project-to-mstest-v4-preserving-vstest/TestProject.csproj | Fixture: baseline xUnit v2 → MSTest migration (VSTest preserved). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v2-project-to-mstest-v4-preserving-vstest/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v2-project-to-mstest-v4-preserving-vstest/CalculatorTests.cs | Fixture: basic assertions (Assert.Equal, Assert.True). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v3-project-to-mstest-v4-preserving-vstest/TestProject.csproj | Fixture: baseline xUnit v3 → MSTest migration (VSTest preserved). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v3-project-to-mstest-v4-preserving-vstest/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v3-project-to-mstest-v4-preserving-vstest/Directory.Build.props | Fixture: pins IsTestingPlatformApplication=false. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/migrate-basic-xunit-v3-project-to-mstest-v4-preserving-vstest/OrderTests.cs | Fixture: [Theory] + [InlineData] conversion target. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/preserve-xunit-parallelization-default-with-assembly-parallelize/TestProject.csproj | Fixture: baseline xUnit v2 project for parallelization preservation. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/preserve-xunit-parallelization-default-with-assembly-parallelize/global.json | Fixture: declares VSTest runner. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/preserve-xunit-parallelization-default-with-assembly-parallelize/AlphaTests.cs | Fixture: class-level parallelization scenario (class A). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/preserve-xunit-parallelization-default-with-assembly-parallelize/BetaTests.cs | Fixture: class-level parallelization scenario (class B). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/recognize-project-already-on-mstest/TestProject.csproj | Fixture: project already on MSTest (no xUnit). |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/recognize-project-already-on-mstest/ExistingTests.cs | Fixture: existing MSTest test class/method. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/stop-when-target-framework-is-unsupported-by-mstest-v4/TestProject.csproj | Fixture: net7.0 project to validate “stop on unsupported TFM for MSTest v4”. |
| tests/dotnet-test/migrate-xunit-to-mstest/fixtures/stop-when-target-framework-is-unsupported-by-mstest-v4/SampleTests.cs | Fixture: minimal xUnit test source for unsupported-TFM scenario. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 46/46 changed files
- Comments generated: 2
- Step 1.5 'Custom DataAttribute' bullet pointed to Step 6 (assertions); custom DataAttribute mapping lives in Step 5 (data-driven tests). - Step 7 lifecycle table 'Constructor' row pointed to Step 8 for ITestOutputHelper; ITestOutputHelper conversion is in Step 9 (output). - Commit-strategy callout said 'after Step 5 (asserts fixed)' and 'after Step 9 (fixtures/lifecycle rewritten)'; asserts are in Step 6 and fixtures/lifecycle complete after Step 8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both TestCategoryAttribute and TestPropertyAttribute target Assembly, Class, and Method, so an xUnit [assembly: Trait] does not need to be demoted to per-class/per-method MSTest attributes — assembly scope is preserved 1:1. - references/mapping-cheatsheet.md §8 now maps [assembly: Trait] to [assembly: TestCategory] / [assembly: TestProperty] instead of 'Remove'. - SKILL.md Step 12 promoted to 'Convert' (was 'Remove'); the same mapping is documented inline. - Notes after the Step 4 and §1 trait tables now disclose the assembly target so users do not assume class/method-only scope. Addresses PR feedback from @Evangelink. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for the review! Pushed two follow-up commits: c169d65 — Fix step cross-references (review bot findings)
7e6fc17 — Map You're right — both
On vally evalsThe skill already ships an
|
|
/evaluate |
From @Evangelink: - [Ignore]/[Timeout] do NOT discover a test on their own — they are modifiers. SKILL.md Step 4 + cheatsheet §1 now show [Fact(Skip)] -> [TestMethod] + [Ignore(...)] (and same for [Timeout]). Eval scenario 9 rubric updated to require both attributes. - TestProperty IS filterable (was incorrectly described as 'metadata that does not need filter integration'). Notes after the trait tables in both SKILL.md and cheatsheet §1 now show both filter syntaxes. - For environmental gates (OS-/CI-/arch-conditional), point at MSTest 3.10+'s condition attributes ([OSCondition], [CICondition], [ArchitectureCondition], [NonParallelizableCondition]) as the preferred alternative to overloading [TestCategory] or scattering Assert.Inconclusive through test bodies. Strengthened in cheatsheet §3.9, SKILL.md Step 6, Step 10 SkippableFact row, and the Common Pitfalls table. - TestDataRow<T> is also a supported MSTest data source (strongly typed with per-row DisplayName/Ignore metadata). Added to both SKILL.md Step 5 and cheatsheet §2 with link to the docs. - MSTest.Sdk + <UseVSTest>true</UseVSTest> pulls in Microsoft.NET.Test.Sdk automatically — the manual PackageReference was incorrect. Removed it from both files (Step 2, Step 3 'common mistakes', and Common Pitfalls). - Added the 'pin MSTest.Sdk in global.json msbuild-sdks' alternative as the recommended pattern for multi-project solutions. - Xunit.Combinatorial -> Combinatorial.MSTest (Youssef1313 community port). Updated both SKILL.md Step 10 and cheatsheet §10. From Copilot reviewer: - MSTest.Sdk example in SKILL.md Step 2 Option B hardcoded <TargetFramework>net9.0</TargetFramework>, contradicting the preserve-TFM rule. Replaced with a placeholder + explicit comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for the detailed review! Second round of fixes is in MSTest semantics
Condition attributes (MSTest 3.10+)
Data sources
MSTest.Sdk
Companion ports
Validator green locally (24 skills, 11 agents). Re-running |
|
/evaluate |
Skill Validation Results
[1] (Isolated) Quality improved but weighted score is -34.3% due to: completion (✓ → ✗), judgment, errors (0 → 1), tokens (69826 → 112590) Model: claude-opus-4.6 | Judge: claude-opus-4.6 🔍 Full Results - additional metrics and failure investigation steps
▶ Sessions Visualisation -- interactive replay of all evaluation sessions |
Eval results on PR #718 showed regressions on three rubric items where the agent did the migration correctly but did not COMMUNICATE the judgement-call decisions, so the judge could not verify the choice was deliberate: - ICollectionFixture scope decision (rubric: 'Explicitly explains the scope decision instead of silently widening' scored 1/5 because the agent said 'Let me convert everything' with zero scope discussion). Step 8 now requires a templated explanation of (source scope, target scope, what is shared, what is serialized, why the rejected alternative was rejected) before applying. - Parallelization 'MSTest default is serial' messaging (rubric scored 1.7/5 because the agent only implicitly conveyed it). Step 11 Choice A now demands a templated sentence explaining that the [assembly: Parallelize] is REQUIRED to match xUnit, otherwise the suite silently regresses to serial. - Response Guidelines now lists the three judgement-call decisions that MUST be communicated up-front: fixture scope, parallelization model, and the Throws/ThrowsExactly semantic flip. Additional fixes from this round: - Step 2: explicit guidance to default to Option A (MSTest metapackage) when the user says 'preserve VSTest', so the safer PackageReference path is picked over MSTest.Sdk + UseVSTest. - Step 4: pulled the '[Ignore]/[Timeout] are modifiers, need [TestMethod]' callout out of a table footer into a visible note above the table. - known-domains.txt: allowlist github.com/Youssef1313/Combinatorial.MSTest (referenced from SKILL.md Step 10 and cheatsheet §10 after the recent Xunit.Combinatorial -> Combinatorial.MSTest switch). Validator: green (24 skills, 11 agents). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/evaluate |
Skill Validation Results
[1] Model: claude-opus-4.6 | Judge: claude-opus-4.6 🔍 Full Results - additional metrics and failure investigation steps
▶ Sessions Visualisation -- interactive replay of all evaluation sessions |
|
👋 @Evangelink — this PR has 1 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 |
The CancellationToken scenario was failing/low-scoring because the skill only contained a one-line mapping with no concrete example. The agent often missed the requirement to: - Add constructor TestContext injection (even when ITestOutputHelper is absent) - Map Assert.False -> Assert.IsFalse alongside the rename - NOT fabricate a CancellationTokenSource as a replacement Add a full xUnit v3 -> MSTest worked example next to the TestContext.Current mapping (Step 9), and reinforce the CancellationTokenSource warning in the cheatsheet. The example covers all 3 rubric points for the scenario. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Pushed d10bc59b to address the Convert xUnit v3 TestContext.Current.CancellationToken scenario. Diagnosis (from inspecting the eval artifacts): The 1.0/5 score wasn't an activation bug -- in plugin mode the skill was activated and used �dit/bash/grep/view, but still scored 1.0. The judge rejected outputs that:
The previous wording at Step 9 was just three one-line mappings under Fix:
The NOT ACTIVATED marker on the isolated run looked like an SDK race (events.jsonl shows skill.invoked after session.shutdown), not a description-match issue -- isolated activation is high-variance (CV=85%) on this scenario. Making the skill more directly actionable should also reduce the chance the agent stalls on its first turn just absorbing the skill. /evaluate |
|
/evaluate |
Skill Validation Results
[1] Model: claude-opus-4.6 | Judge: claude-opus-4.6 🔍 Full Results - additional metrics and failure investigation steps
▶ Sessions Visualisation -- interactive replay of all evaluation sessions |
- Remove fictional MSTest condition attributes [ArchitectureCondition] and [NonParallelizableCondition] from SKILL.md and cheatsheet (only [OSCondition] and [CICondition] exist in MSTest 3.10+; for non-parallel intent use [DoNotParallelize], for architecture gating fall back to runtime Assert.Inconclusive). (Copilot) - TestPropertyAttribute targets only [Class|Method] (no AttributeTargets.Assembly), so [assembly: TestProperty(...)] does not compile. Update the trait notes in SKILL.md Step 4, cheatsheet section 1, Step 12, and cheatsheet section 8 to collapse a non-category xUnit [assembly: Trait] to [assembly: TestCategory] or push it down to per-class [TestProperty]. (@Evangelink) - Step 1 platform detection: stop inlining a buggy VSTest-vs-MTP matrix; delegate to the platform-detection skill. Note explicitly that <UseMicrosoftTestingPlatformRunner> only affects 'dotnet run' and is not a reliable runner signal. (@Youssef1313) - Step 2 Option A: add MTP code-coverage caveat -- Microsoft.NET.Test.Sdk pulls VSTest's Microsoft.CodeCoverage transitively, which can interfere with MTP's collector. Prefer Option B (MSTest.Sdk without UseVSTest) for MTP projects. (@Youssef1313) - Step 3: remove fictional MSBuild properties (<CaptureConsoleOutput>, <UseRoslynCompilers>); restate the bullet about xunit.runner.json -> Step 11 port instead. Restate the <UseMicrosoftTestingPlatformRunner> bullet to make clear it is not a runner switch. (@Youssef1313) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Round 3 review feedback addressed in 60d21b6. Highlights:
Follow-up: the platform-detection skill itself contains the same incorrect UseMTP-runner-as-signal claim. I'll fix that in a separate PR to keep this one scoped to the xUnit migration. /evaluate |
- mapping-cheatsheet.md (Assert.Contains predicate): the suggested fallback Assert.Contains(collection.First(x => predicate), collection) is buggy -- First() throws InvalidOperationException on no-match (changing the failure mode), and feeding the result back into Assert.Contains is circular. Keep only the correct translation: Assert.IsTrue(collection.Any(x => predicate)). - SKILL.md Step 4 / cheatsheet table: stop instructing 'mark every class sealed'. xUnit projects commonly use base/derived test classes (shared setup, generic base fixtures); mechanically sealing them would break compilation. Sealing is now framed as an optional follow-up handled by writing-mstest-tests, not part of the mechanical migration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds the
migrate-xunit-to-mstestskill, which migrates .NET test projects from xUnit.net (v2 or v3) to MSTest v4 — attributes, assertions, fixtures, lifecycle, output, cancellation, parallelization, and companion packages.Single skill (not two) for both v2 and v3 sources: ~90% of the conversion logic is shared, and v2-vs-v3 deltas are small and well-scoped — handled as conditional steps. Matches the precedent of
migrate-mstest-v1v2-to-v3.Why parallelization gets front-loaded
xUnit parallelizes test classes by default; MSTest serializes by default. This is the single largest source of post-migration regressions: tests that depend on isolation by parallel scheduling, on the lack of it, or on shared static state can pass differently after the switch.
Step 1 inventories shared state and flaky tests. Step 11 enumerates three explicit choices and provides a translation table for
CollectionBehavior(DisableTestParallelization),MaxParallelThreads,[Collection], and[CollectionDefinition]. The validation checklist treats unspecified parallelization as an error.Design constraints (validated with rubber-duck review)
IClassFixture<T>-> mechanical map to[ClassInitialize]/[ClassCleanup].ICollectionFixture<T>requires judgement — it conflates "shared instance across classes" with "serial execution". MSTest decouples them; do not silently widen scope to assembly-level.Assert.Throws<T>is exact-type and maps toAssert.ThrowsExactly<T>; xUnitAssert.ThrowsAny<T>maps toAssert.Throws<T>. The names invert.net8.0,net9.0,net462+,netstandard2.0(test library only), UAP/WinUI. Stop fornet5.0-net7.0.Files
plugins/dotnet-test/skills/migrate-xunit-to-mstest/SKILL.md— 13-step workflowplugins/dotnet-test/skills/migrate-xunit-to-mstest/references/mapping-cheatsheet.md— full attribute/assertion/fixture/lifecycle tables (progressive disclosure)tests/dotnet-test/migrate-xunit-to-mstest/eval.yaml— 12 scenarios (skill-validator format, inlinesetup.files)tests/dotnet-test/migrate-xunit-to-mstest/eval.vally.yaml+fixtures/— Vally-format mirror of the same scenarios (40 fixture files), matching the convention of every sibling migration skillplugins/dotnet-test/README.md— migration table row + intro bulletplugins/dotnet-test/agents/test-migration.agent.md— triage row + two multi-step rules (xUnit→MSTest, xUnit→MSTest+MTP)Evaluation scenarios (12)
net7.0)IClassFixture<T>->[ClassInitialize]/[ClassCleanup]ICollectionFixture<T>-> explicit scope decision ([DoNotParallelize])ITestOutputHelper->TestContextThrowsvsThrowsAnysemantic trapMemberData/TheoryData->DynamicData[Fact(Skip)]/[Trait]/Timeout->[Ignore]/[TestCategory]/[TestProperty]/Timeout[assembly: Parallelize(ClassLevel)]TestContext.Current.CancellationToken-> MSTestTestContext.CancellationTokenStatic checks
dotnet run --project eng/skill-validator/src -- check --plugin ./plugins/dotnet-test✅ All checks passed (24 skill(s), 11 agent(s), 1 plugin(s))
One soft warning: SKILL.md is 8.1k BPE tokens (above the "approaching comprehensive" threshold). Splitting was considered and rejected — the workflow is sequential and benefits from being one document; non-essential content already lives in
references/mapping-cheatsheet.md.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com