[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-dotnet-exp-test-maintainability":3,"mdc-xky4d9-key":37,"related-repo-dotnet-exp-test-maintainability":1605,"related-org-dotnet-exp-test-maintainability":1709},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":25,"repoUrl":26,"updatedAt":27,"license":28,"forks":29,"topics":30,"repo":32,"sourceUrl":35,"mdContent":36},"exp-test-maintainability","improve maintainability of .NET test suites","Detects duplicate boilerplate, copy-paste tests, and structural maintainability issues across .NET test suites. Use when the user asks to reduce repetition, consolidate similar test methods, convert copy-paste tests to data-driven parameterized tests, suggest a better test structure, or identify refactoring opportunities. Identifies repeated construction, assertion patterns, copy-paste methods convertible to DataRow\u002FTheory\u002FTestCase, redundant setup\u002Fteardown, and shared infrastructure. Produces an analysis report with concrete before\u002Fafter suggestions. Works with MSTest, xUnit, NUnit, and TUnit. DO NOT USE FOR: writing new tests (use writing-mstest-tests), reviewing test quality or anti-patterns (use test-anti-patterns), or deep mock auditing (use exp-mock-usage-analysis).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"dotnet",".NET (Microsoft)","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fdotnet.png",[12,16,19,22],{"name":13,"slug":14,"type":15},".NET","net","tag",{"name":17,"slug":18,"type":15},"Engineering","engineering",{"name":20,"slug":21,"type":15},"Code Analysis","code-analysis",{"name":23,"slug":24,"type":15},"Testing","testing",4576,"https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fskills","2026-07-12T08:24:18.753334","MIT",332,[31],"agent-skills",{"repoUrl":26,"stars":25,"forks":29,"topics":33,"description":34},[31],"Repository for skills to assist AI coding agents with .NET and C#","https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fskills\u002Ftree\u002FHEAD\u002Fplugins\u002Fdotnet-experimental\u002Fskills\u002Fexp-test-maintainability","---\nname: exp-test-maintainability\ndescription: \"Detects duplicate boilerplate, copy-paste tests, and structural maintainability issues across .NET test suites. Use when the user asks to reduce repetition, consolidate similar test methods, convert copy-paste tests to data-driven parameterized tests, suggest a better test structure, or identify refactoring opportunities. Identifies repeated construction, assertion patterns, copy-paste methods convertible to DataRow\u002FTheory\u002FTestCase, redundant setup\u002Fteardown, and shared infrastructure. Produces an analysis report with concrete before\u002Fafter suggestions. Works with MSTest, xUnit, NUnit, and TUnit. DO NOT USE FOR: writing new tests (use writing-mstest-tests), reviewing test quality or anti-patterns (use test-anti-patterns), or deep mock auditing (use exp-mock-usage-analysis).\"\nlicense: MIT\n---\n\n# Test Maintainability Assessment\n\nAnalyze .NET test code for maintainability issues: duplicated boilerplate, copy-paste test methods, and structural repetition across test methods and classes. Produce a report of refactoring opportunities with concrete before\u002Fafter suggestions. The goal is analysis only — do not modify any files.\n\n## When to Use\n\n- User asks to find duplicated code or boilerplate in tests\n- User wants to know where test code can be DRY-ed up\n- User asks to reduce test duplication, improve test readability, or clean up test boilerplate\n- User asks for refactoring opportunities in a test suite\n- User wants to identify shared setup or teardown candidates\n- User asks \"what patterns repeat across my tests?\"\n- User wants to centralize test data, introduce builders or helpers\n\n## When Not to Use\n\n- User wants to write new tests from scratch (use `writing-mstest-tests`)\n- User wants to detect anti-patterns or code smells (use `test-anti-patterns`)\n- User wants to actually perform the refactoring (help them directly, this skill only analyzes)\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Test code | Yes | One or more test files or a test project directory to analyze |\n| Production code | No | The code under test, for context on what abstractions might help |\n| Scope | No | Whether to analyze within a single class or across multiple classes |\n\n## Workflow\n\n### Step 1: Gather the test code\n\nRead all test files the user provides or references. If the user points to a directory or project, scan for all test files using these framework markers:\n\n| Framework | Test class markers | Test method markers |\n|-----------|--------------------|---------------------|\n| MSTest | `[TestClass]` | `[TestMethod]`, `[DataTestMethod]` |\n| xUnit | *(none — convention-based)* | `[Fact]`, `[Theory]` |\n| NUnit | `[TestFixture]` | `[Test]`, `[TestCase]`, `[TestCaseSource]` |\n| TUnit | *(none — convention-based)* | `[Test]` |\n\n### Step 2: Identify maintainability issues\n\nScan for these categories:\n\n#### Category 1: Repeated object construction\n\nLook for the same object being constructed in 3+ test methods with identical or near-identical parameters.\n\n**Indicators:**\n- `new ClassName(...)` appearing with identical arguments in multiple tests\n- Multiple tests creating the same \"system under test\" with similar configuration\n- Repeated mock\u002Ffake\u002Fstub creation with the same setup\n\n**Potential refactorings:**\n- Extract a factory method or test helper (e.g., `CreateSut()`, `CreateDefaultOrder()`)\n- Use `[TestInitialize]`\u002Fconstructor\u002F`[SetUp]` for shared construction\n- Introduce a builder pattern for complex objects with many variations\n\n**Example — before:**\n```csharp\n[TestMethod]\npublic void Process_ValidOrder_Succeeds()\n{\n    var logger = new FakeLogger();\n    var email = new FakeEmailService();\n    var inventory = new FakeInventory(stock: 100);\n    var processor = new OrderProcessor(logger, email, inventory);\n    \u002F\u002F ...\n}\n\n[TestMethod]\npublic void Process_EmptyItems_Fails()\n{\n    var logger = new FakeLogger();\n    var email = new FakeEmailService();\n    var inventory = new FakeInventory(stock: 100);\n    var processor = new OrderProcessor(logger, email, inventory);\n    \u002F\u002F ...\n}\n```\n\n**After — extract factory:**\n```csharp\nprivate static OrderProcessor CreateProcessor(int stock = 100)\n{\n    return new OrderProcessor(new FakeLogger(), new FakeEmailService(), new FakeInventory(stock));\n}\n```\n\n#### Category 2: Repeated assertion patterns\n\nLook for the same sequence of assertions appearing in 3+ test methods.\n\n**Indicators:**\n- Multiple tests asserting the same set of properties on a result object\n- Repeated null-check-then-value-check sequences\n- Same collection of `Assert.AreEqual` calls across methods\n\n**Potential refactorings:**\n- Extract a custom assertion helper (e.g., `AssertValidOrder(order, expectedTotal, expectedStatus)`)\n- Use framework-specific assertion extensions\n- Introduce a `Verify` method that checks a standard set of properties\n\n#### Category 3: Copy-paste test methods\n\nLook for test methods with near-identical bodies differing only in input values or a single parameter.\n\n**Indicators:**\n- 3+ methods with the same structure but different literal values\n- Methods that could be collapsed into `[DataRow]`\u002F`[Theory]`\u002F`[TestCase]`\n- Test names that follow a pattern like `Method_Input1_Result`, `Method_Input2_Result`\n\n**Potential refactorings:**\n- Convert to parameterized tests with `[DataRow]`\u002F`[InlineData]`\u002F`[TestCase]`\n- Use `[DynamicData]`\u002F`[MemberData]`\u002F`[TestCaseSource]` for complex inputs\n- Prefer `[DataRow]` with `DisplayName` over `[DynamicData]` when all values are compile-time constants. Reserve `[DynamicData]` for computed or complex values.\n- Add `DisplayName` for non-obvious parameter values. `[DataRow(\"Gold\", 100.0, 90.0)]` is self-explanatory; `[DataRow(3, 7, 42)]` is not.\n\n#### Category 4: Duplicated setup\u002Fteardown logic\n\nLook for initialization or cleanup code repeated across test classes.\n\n**Indicators:**\n- Multiple `[TestInitialize]`\u002F`[SetUp]` methods with similar bodies\n- Repeated database seeding, file creation, or HTTP client configuration\n- Same `using`\u002F`IDisposable` cleanup pattern across classes\n\n**Potential refactorings:**\n- Extract a shared test base class or fixture\n- Use composition with a shared helper class\n- Create a test context factory\n\n#### Category 5: Repeated test infrastructure\n\nLook for structural patterns shared across test classes.\n\n**Indicators:**\n- Same mock interfaces configured identically in multiple classes\n- Repeated `HttpClient` setup with similar `DelegatingHandler` patterns\n- Same logging\u002Fconfiguration scaffolding across test classes\n\n**Potential refactorings:**\n- Extract a shared test fixture or helper library\n- Create reusable fake implementations\n- Introduce a test harness class\n\n### Step 3: Apply calibration rules\n\nBefore reporting, filter findings through these rules:\n\n- **Only report at 3+ occurrences.** Two similar setups are not boilerplate — they may be intentional clarity.\n- **Don't flag simple constructors.** `new Calculator()` or `new List\u003Cint>()` is not meaningful boilerplate. Don't recommend builders for `new User(1, \"Alice\")` either.\n- **Respect intentional verbosity.** If each test is self-contained and reads clearly on its own, explicit setup per test is a valid choice. Note it but don't flag it as a problem.\n- **Distinguish structural similarity from true duplication.** Tests that follow AAA (Arrange-Act-Assert) will look similar by nature. Only flag when the actual code (not just the structure) is duplicated.\n- **Consider the blast radius of refactoring.** A helper shared across 20 tests creates coupling. Note the trade-off.\n- **If tests are already well-maintained, say so.** A report finding only minor opportunities is perfectly valid. Acknowledge what's already good.\n\n### Step 4: Report findings\n\nPresent findings in this structure:\n\n1. **Summary** — How many patterns found, broken down by category. If the test suite is clean, lead with that.\n2. **Findings by category** — For each pattern found:\n   - Category name and description\n   - Locations: list the specific test methods and files involved\n   - The duplicated code pattern (show a representative sample)\n   - Suggested refactoring with a concrete before\u002Fafter example\n   - Estimated impact: how many lines\u002Fmethods would be simplified\n3. **Refactoring priority** — Rank findings by:\n   - Occurrence count (more occurrences = higher value)\n   - Complexity of the duplicated code (complex setup > simple construction)\n   - Risk (low-risk extractions first)\n4. **Trade-offs** — For each suggestion, note:\n   - What readability is gained\n   - What locality\u002Findependence is lost\n   - Whether it's worth it given the occurrence count\n\n## Validation\n\n- [ ] Every finding includes specific file and method locations\n- [ ] Every finding shows the actual duplicated code, not just a description\n- [ ] Every suggestion includes a concrete before\u002Fafter example\n- [ ] Findings are filtered through the 3+ occurrence threshold\n- [ ] Simple constructors are not flagged\n- [ ] Trade-offs are acknowledged for each suggestion\n- [ ] If tests are clean, the report says so upfront\n\n## Common Pitfalls\n\n| Pitfall | Solution |\n|---------|----------|\n| Flagging AAA structure as duplication | The Arrange-Act-Assert pattern is not boilerplate — flag only when the actual code repeats |\n| Suggesting extraction for 2 occurrences | Wait for 3+ before recommending extraction |\n| Recommending base classes for everything | Prefer composition (helpers, factories) over inheritance |\n| Ignoring the readability cost | Every extraction adds indirection — note the trade-off |\n| Flagging simple `new X()` as boilerplate | Only flag complex construction with multiple parameters or configuration |\n| Recommending DRY at the expense of test isolation | Tests that share mutable state through helpers become coupled — warn about this |\n",{"data":38,"body":39},{"name":4,"description":6,"license":28},{"type":40,"children":41},"root",[42,51,57,64,104,110,144,150,236,242,249,254,411,417,422,429,434,443,467,475,523,531,708,716,753,759,764,771,797,804,837,843,848,855,905,912,1025,1031,1036,1043,1089,1096,1114,1120,1125,1132,1166,1173,1191,1197,1202,1289,1295,1300,1408,1414,1485,1491,1599],{"type":43,"tag":44,"props":45,"children":47},"element","h1",{"id":46},"test-maintainability-assessment",[48],{"type":49,"value":50},"text","Test Maintainability Assessment",{"type":43,"tag":52,"props":53,"children":54},"p",{},[55],{"type":49,"value":56},"Analyze .NET test code for maintainability issues: duplicated boilerplate, copy-paste test methods, and structural repetition across test methods and classes. Produce a report of refactoring opportunities with concrete before\u002Fafter suggestions. The goal is analysis only — do not modify any files.",{"type":43,"tag":58,"props":59,"children":61},"h2",{"id":60},"when-to-use",[62],{"type":49,"value":63},"When to Use",{"type":43,"tag":65,"props":66,"children":67},"ul",{},[68,74,79,84,89,94,99],{"type":43,"tag":69,"props":70,"children":71},"li",{},[72],{"type":49,"value":73},"User asks to find duplicated code or boilerplate in tests",{"type":43,"tag":69,"props":75,"children":76},{},[77],{"type":49,"value":78},"User wants to know where test code can be DRY-ed up",{"type":43,"tag":69,"props":80,"children":81},{},[82],{"type":49,"value":83},"User asks to reduce test duplication, improve test readability, or clean up test boilerplate",{"type":43,"tag":69,"props":85,"children":86},{},[87],{"type":49,"value":88},"User asks for refactoring opportunities in a test suite",{"type":43,"tag":69,"props":90,"children":91},{},[92],{"type":49,"value":93},"User wants to identify shared setup or teardown candidates",{"type":43,"tag":69,"props":95,"children":96},{},[97],{"type":49,"value":98},"User asks \"what patterns repeat across my tests?\"",{"type":43,"tag":69,"props":100,"children":101},{},[102],{"type":49,"value":103},"User wants to centralize test data, introduce builders or helpers",{"type":43,"tag":58,"props":105,"children":107},{"id":106},"when-not-to-use",[108],{"type":49,"value":109},"When Not to Use",{"type":43,"tag":65,"props":111,"children":112},{},[113,127,139],{"type":43,"tag":69,"props":114,"children":115},{},[116,118,125],{"type":49,"value":117},"User wants to write new tests from scratch (use ",{"type":43,"tag":119,"props":120,"children":122},"code",{"className":121},[],[123],{"type":49,"value":124},"writing-mstest-tests",{"type":49,"value":126},")",{"type":43,"tag":69,"props":128,"children":129},{},[130,132,138],{"type":49,"value":131},"User wants to detect anti-patterns or code smells (use ",{"type":43,"tag":119,"props":133,"children":135},{"className":134},[],[136],{"type":49,"value":137},"test-anti-patterns",{"type":49,"value":126},{"type":43,"tag":69,"props":140,"children":141},{},[142],{"type":49,"value":143},"User wants to actually perform the refactoring (help them directly, this skill only analyzes)",{"type":43,"tag":58,"props":145,"children":147},{"id":146},"inputs",[148],{"type":49,"value":149},"Inputs",{"type":43,"tag":151,"props":152,"children":153},"table",{},[154,178],{"type":43,"tag":155,"props":156,"children":157},"thead",{},[158],{"type":43,"tag":159,"props":160,"children":161},"tr",{},[162,168,173],{"type":43,"tag":163,"props":164,"children":165},"th",{},[166],{"type":49,"value":167},"Input",{"type":43,"tag":163,"props":169,"children":170},{},[171],{"type":49,"value":172},"Required",{"type":43,"tag":163,"props":174,"children":175},{},[176],{"type":49,"value":177},"Description",{"type":43,"tag":179,"props":180,"children":181},"tbody",{},[182,201,219],{"type":43,"tag":159,"props":183,"children":184},{},[185,191,196],{"type":43,"tag":186,"props":187,"children":188},"td",{},[189],{"type":49,"value":190},"Test code",{"type":43,"tag":186,"props":192,"children":193},{},[194],{"type":49,"value":195},"Yes",{"type":43,"tag":186,"props":197,"children":198},{},[199],{"type":49,"value":200},"One or more test files or a test project directory to analyze",{"type":43,"tag":159,"props":202,"children":203},{},[204,209,214],{"type":43,"tag":186,"props":205,"children":206},{},[207],{"type":49,"value":208},"Production code",{"type":43,"tag":186,"props":210,"children":211},{},[212],{"type":49,"value":213},"No",{"type":43,"tag":186,"props":215,"children":216},{},[217],{"type":49,"value":218},"The code under test, for context on what abstractions might help",{"type":43,"tag":159,"props":220,"children":221},{},[222,227,231],{"type":43,"tag":186,"props":223,"children":224},{},[225],{"type":49,"value":226},"Scope",{"type":43,"tag":186,"props":228,"children":229},{},[230],{"type":49,"value":213},{"type":43,"tag":186,"props":232,"children":233},{},[234],{"type":49,"value":235},"Whether to analyze within a single class or across multiple classes",{"type":43,"tag":58,"props":237,"children":239},{"id":238},"workflow",[240],{"type":49,"value":241},"Workflow",{"type":43,"tag":243,"props":244,"children":246},"h3",{"id":245},"step-1-gather-the-test-code",[247],{"type":49,"value":248},"Step 1: Gather the test code",{"type":43,"tag":52,"props":250,"children":251},{},[252],{"type":49,"value":253},"Read all test files the user provides or references. If the user points to a directory or project, scan for all test files using these framework markers:",{"type":43,"tag":151,"props":255,"children":256},{},[257,278],{"type":43,"tag":155,"props":258,"children":259},{},[260],{"type":43,"tag":159,"props":261,"children":262},{},[263,268,273],{"type":43,"tag":163,"props":264,"children":265},{},[266],{"type":49,"value":267},"Framework",{"type":43,"tag":163,"props":269,"children":270},{},[271],{"type":49,"value":272},"Test class markers",{"type":43,"tag":163,"props":274,"children":275},{},[276],{"type":49,"value":277},"Test method markers",{"type":43,"tag":179,"props":279,"children":280},{},[281,315,348,388],{"type":43,"tag":159,"props":282,"children":283},{},[284,289,298],{"type":43,"tag":186,"props":285,"children":286},{},[287],{"type":49,"value":288},"MSTest",{"type":43,"tag":186,"props":290,"children":291},{},[292],{"type":43,"tag":119,"props":293,"children":295},{"className":294},[],[296],{"type":49,"value":297},"[TestClass]",{"type":43,"tag":186,"props":299,"children":300},{},[301,307,309],{"type":43,"tag":119,"props":302,"children":304},{"className":303},[],[305],{"type":49,"value":306},"[TestMethod]",{"type":49,"value":308},", ",{"type":43,"tag":119,"props":310,"children":312},{"className":311},[],[313],{"type":49,"value":314},"[DataTestMethod]",{"type":43,"tag":159,"props":316,"children":317},{},[318,323,332],{"type":43,"tag":186,"props":319,"children":320},{},[321],{"type":49,"value":322},"xUnit",{"type":43,"tag":186,"props":324,"children":325},{},[326],{"type":43,"tag":327,"props":328,"children":329},"em",{},[330],{"type":49,"value":331},"(none — convention-based)",{"type":43,"tag":186,"props":333,"children":334},{},[335,341,342],{"type":43,"tag":119,"props":336,"children":338},{"className":337},[],[339],{"type":49,"value":340},"[Fact]",{"type":49,"value":308},{"type":43,"tag":119,"props":343,"children":345},{"className":344},[],[346],{"type":49,"value":347},"[Theory]",{"type":43,"tag":159,"props":349,"children":350},{},[351,356,365],{"type":43,"tag":186,"props":352,"children":353},{},[354],{"type":49,"value":355},"NUnit",{"type":43,"tag":186,"props":357,"children":358},{},[359],{"type":43,"tag":119,"props":360,"children":362},{"className":361},[],[363],{"type":49,"value":364},"[TestFixture]",{"type":43,"tag":186,"props":366,"children":367},{},[368,374,375,381,382],{"type":43,"tag":119,"props":369,"children":371},{"className":370},[],[372],{"type":49,"value":373},"[Test]",{"type":49,"value":308},{"type":43,"tag":119,"props":376,"children":378},{"className":377},[],[379],{"type":49,"value":380},"[TestCase]",{"type":49,"value":308},{"type":43,"tag":119,"props":383,"children":385},{"className":384},[],[386],{"type":49,"value":387},"[TestCaseSource]",{"type":43,"tag":159,"props":389,"children":390},{},[391,396,403],{"type":43,"tag":186,"props":392,"children":393},{},[394],{"type":49,"value":395},"TUnit",{"type":43,"tag":186,"props":397,"children":398},{},[399],{"type":43,"tag":327,"props":400,"children":401},{},[402],{"type":49,"value":331},{"type":43,"tag":186,"props":404,"children":405},{},[406],{"type":43,"tag":119,"props":407,"children":409},{"className":408},[],[410],{"type":49,"value":373},{"type":43,"tag":243,"props":412,"children":414},{"id":413},"step-2-identify-maintainability-issues",[415],{"type":49,"value":416},"Step 2: Identify maintainability issues",{"type":43,"tag":52,"props":418,"children":419},{},[420],{"type":49,"value":421},"Scan for these categories:",{"type":43,"tag":423,"props":424,"children":426},"h4",{"id":425},"category-1-repeated-object-construction",[427],{"type":49,"value":428},"Category 1: Repeated object construction",{"type":43,"tag":52,"props":430,"children":431},{},[432],{"type":49,"value":433},"Look for the same object being constructed in 3+ test methods with identical or near-identical parameters.",{"type":43,"tag":52,"props":435,"children":436},{},[437],{"type":43,"tag":438,"props":439,"children":440},"strong",{},[441],{"type":49,"value":442},"Indicators:",{"type":43,"tag":65,"props":444,"children":445},{},[446,457,462],{"type":43,"tag":69,"props":447,"children":448},{},[449,455],{"type":43,"tag":119,"props":450,"children":452},{"className":451},[],[453],{"type":49,"value":454},"new ClassName(...)",{"type":49,"value":456}," appearing with identical arguments in multiple tests",{"type":43,"tag":69,"props":458,"children":459},{},[460],{"type":49,"value":461},"Multiple tests creating the same \"system under test\" with similar configuration",{"type":43,"tag":69,"props":463,"children":464},{},[465],{"type":49,"value":466},"Repeated mock\u002Ffake\u002Fstub creation with the same setup",{"type":43,"tag":52,"props":468,"children":469},{},[470],{"type":43,"tag":438,"props":471,"children":472},{},[473],{"type":49,"value":474},"Potential refactorings:",{"type":43,"tag":65,"props":476,"children":477},{},[478,497,518],{"type":43,"tag":69,"props":479,"children":480},{},[481,483,489,490,496],{"type":49,"value":482},"Extract a factory method or test helper (e.g., ",{"type":43,"tag":119,"props":484,"children":486},{"className":485},[],[487],{"type":49,"value":488},"CreateSut()",{"type":49,"value":308},{"type":43,"tag":119,"props":491,"children":493},{"className":492},[],[494],{"type":49,"value":495},"CreateDefaultOrder()",{"type":49,"value":126},{"type":43,"tag":69,"props":498,"children":499},{},[500,502,508,510,516],{"type":49,"value":501},"Use ",{"type":43,"tag":119,"props":503,"children":505},{"className":504},[],[506],{"type":49,"value":507},"[TestInitialize]",{"type":49,"value":509},"\u002Fconstructor\u002F",{"type":43,"tag":119,"props":511,"children":513},{"className":512},[],[514],{"type":49,"value":515},"[SetUp]",{"type":49,"value":517}," for shared construction",{"type":43,"tag":69,"props":519,"children":520},{},[521],{"type":49,"value":522},"Introduce a builder pattern for complex objects with many variations",{"type":43,"tag":52,"props":524,"children":525},{},[526],{"type":43,"tag":438,"props":527,"children":528},{},[529],{"type":49,"value":530},"Example — before:",{"type":43,"tag":532,"props":533,"children":538},"pre",{"className":534,"code":535,"language":536,"meta":537,"style":537},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[TestMethod]\npublic void Process_ValidOrder_Succeeds()\n{\n    var logger = new FakeLogger();\n    var email = new FakeEmailService();\n    var inventory = new FakeInventory(stock: 100);\n    var processor = new OrderProcessor(logger, email, inventory);\n    \u002F\u002F ...\n}\n\n[TestMethod]\npublic void Process_EmptyItems_Fails()\n{\n    var logger = new FakeLogger();\n    var email = new FakeEmailService();\n    var inventory = new FakeInventory(stock: 100);\n    var processor = new OrderProcessor(logger, email, inventory);\n    \u002F\u002F ...\n}\n","csharp","",[539],{"type":43,"tag":119,"props":540,"children":541},{"__ignoreMap":537},[542,553,562,571,580,589,598,607,616,625,635,643,652,660,668,676,684,692,700],{"type":43,"tag":543,"props":544,"children":547},"span",{"class":545,"line":546},"line",1,[548],{"type":43,"tag":543,"props":549,"children":550},{},[551],{"type":49,"value":552},"[TestMethod]\n",{"type":43,"tag":543,"props":554,"children":556},{"class":545,"line":555},2,[557],{"type":43,"tag":543,"props":558,"children":559},{},[560],{"type":49,"value":561},"public void Process_ValidOrder_Succeeds()\n",{"type":43,"tag":543,"props":563,"children":565},{"class":545,"line":564},3,[566],{"type":43,"tag":543,"props":567,"children":568},{},[569],{"type":49,"value":570},"{\n",{"type":43,"tag":543,"props":572,"children":574},{"class":545,"line":573},4,[575],{"type":43,"tag":543,"props":576,"children":577},{},[578],{"type":49,"value":579},"    var logger = new FakeLogger();\n",{"type":43,"tag":543,"props":581,"children":583},{"class":545,"line":582},5,[584],{"type":43,"tag":543,"props":585,"children":586},{},[587],{"type":49,"value":588},"    var email = new FakeEmailService();\n",{"type":43,"tag":543,"props":590,"children":592},{"class":545,"line":591},6,[593],{"type":43,"tag":543,"props":594,"children":595},{},[596],{"type":49,"value":597},"    var inventory = new FakeInventory(stock: 100);\n",{"type":43,"tag":543,"props":599,"children":601},{"class":545,"line":600},7,[602],{"type":43,"tag":543,"props":603,"children":604},{},[605],{"type":49,"value":606},"    var processor = new OrderProcessor(logger, email, inventory);\n",{"type":43,"tag":543,"props":608,"children":610},{"class":545,"line":609},8,[611],{"type":43,"tag":543,"props":612,"children":613},{},[614],{"type":49,"value":615},"    \u002F\u002F ...\n",{"type":43,"tag":543,"props":617,"children":619},{"class":545,"line":618},9,[620],{"type":43,"tag":543,"props":621,"children":622},{},[623],{"type":49,"value":624},"}\n",{"type":43,"tag":543,"props":626,"children":628},{"class":545,"line":627},10,[629],{"type":43,"tag":543,"props":630,"children":632},{"emptyLinePlaceholder":631},true,[633],{"type":49,"value":634},"\n",{"type":43,"tag":543,"props":636,"children":638},{"class":545,"line":637},11,[639],{"type":43,"tag":543,"props":640,"children":641},{},[642],{"type":49,"value":552},{"type":43,"tag":543,"props":644,"children":646},{"class":545,"line":645},12,[647],{"type":43,"tag":543,"props":648,"children":649},{},[650],{"type":49,"value":651},"public void Process_EmptyItems_Fails()\n",{"type":43,"tag":543,"props":653,"children":655},{"class":545,"line":654},13,[656],{"type":43,"tag":543,"props":657,"children":658},{},[659],{"type":49,"value":570},{"type":43,"tag":543,"props":661,"children":663},{"class":545,"line":662},14,[664],{"type":43,"tag":543,"props":665,"children":666},{},[667],{"type":49,"value":579},{"type":43,"tag":543,"props":669,"children":671},{"class":545,"line":670},15,[672],{"type":43,"tag":543,"props":673,"children":674},{},[675],{"type":49,"value":588},{"type":43,"tag":543,"props":677,"children":679},{"class":545,"line":678},16,[680],{"type":43,"tag":543,"props":681,"children":682},{},[683],{"type":49,"value":597},{"type":43,"tag":543,"props":685,"children":687},{"class":545,"line":686},17,[688],{"type":43,"tag":543,"props":689,"children":690},{},[691],{"type":49,"value":606},{"type":43,"tag":543,"props":693,"children":695},{"class":545,"line":694},18,[696],{"type":43,"tag":543,"props":697,"children":698},{},[699],{"type":49,"value":615},{"type":43,"tag":543,"props":701,"children":703},{"class":545,"line":702},19,[704],{"type":43,"tag":543,"props":705,"children":706},{},[707],{"type":49,"value":624},{"type":43,"tag":52,"props":709,"children":710},{},[711],{"type":43,"tag":438,"props":712,"children":713},{},[714],{"type":49,"value":715},"After — extract factory:",{"type":43,"tag":532,"props":717,"children":719},{"className":534,"code":718,"language":536,"meta":537,"style":537},"private static OrderProcessor CreateProcessor(int stock = 100)\n{\n    return new OrderProcessor(new FakeLogger(), new FakeEmailService(), new FakeInventory(stock));\n}\n",[720],{"type":43,"tag":119,"props":721,"children":722},{"__ignoreMap":537},[723,731,738,746],{"type":43,"tag":543,"props":724,"children":725},{"class":545,"line":546},[726],{"type":43,"tag":543,"props":727,"children":728},{},[729],{"type":49,"value":730},"private static OrderProcessor CreateProcessor(int stock = 100)\n",{"type":43,"tag":543,"props":732,"children":733},{"class":545,"line":555},[734],{"type":43,"tag":543,"props":735,"children":736},{},[737],{"type":49,"value":570},{"type":43,"tag":543,"props":739,"children":740},{"class":545,"line":564},[741],{"type":43,"tag":543,"props":742,"children":743},{},[744],{"type":49,"value":745},"    return new OrderProcessor(new FakeLogger(), new FakeEmailService(), new FakeInventory(stock));\n",{"type":43,"tag":543,"props":747,"children":748},{"class":545,"line":573},[749],{"type":43,"tag":543,"props":750,"children":751},{},[752],{"type":49,"value":624},{"type":43,"tag":423,"props":754,"children":756},{"id":755},"category-2-repeated-assertion-patterns",[757],{"type":49,"value":758},"Category 2: Repeated assertion patterns",{"type":43,"tag":52,"props":760,"children":761},{},[762],{"type":49,"value":763},"Look for the same sequence of assertions appearing in 3+ test methods.",{"type":43,"tag":52,"props":765,"children":766},{},[767],{"type":43,"tag":438,"props":768,"children":769},{},[770],{"type":49,"value":442},{"type":43,"tag":65,"props":772,"children":773},{},[774,779,784],{"type":43,"tag":69,"props":775,"children":776},{},[777],{"type":49,"value":778},"Multiple tests asserting the same set of properties on a result object",{"type":43,"tag":69,"props":780,"children":781},{},[782],{"type":49,"value":783},"Repeated null-check-then-value-check sequences",{"type":43,"tag":69,"props":785,"children":786},{},[787,789,795],{"type":49,"value":788},"Same collection of ",{"type":43,"tag":119,"props":790,"children":792},{"className":791},[],[793],{"type":49,"value":794},"Assert.AreEqual",{"type":49,"value":796}," calls across methods",{"type":43,"tag":52,"props":798,"children":799},{},[800],{"type":43,"tag":438,"props":801,"children":802},{},[803],{"type":49,"value":474},{"type":43,"tag":65,"props":805,"children":806},{},[807,819,824],{"type":43,"tag":69,"props":808,"children":809},{},[810,812,818],{"type":49,"value":811},"Extract a custom assertion helper (e.g., ",{"type":43,"tag":119,"props":813,"children":815},{"className":814},[],[816],{"type":49,"value":817},"AssertValidOrder(order, expectedTotal, expectedStatus)",{"type":49,"value":126},{"type":43,"tag":69,"props":820,"children":821},{},[822],{"type":49,"value":823},"Use framework-specific assertion extensions",{"type":43,"tag":69,"props":825,"children":826},{},[827,829,835],{"type":49,"value":828},"Introduce a ",{"type":43,"tag":119,"props":830,"children":832},{"className":831},[],[833],{"type":49,"value":834},"Verify",{"type":49,"value":836}," method that checks a standard set of properties",{"type":43,"tag":423,"props":838,"children":840},{"id":839},"category-3-copy-paste-test-methods",[841],{"type":49,"value":842},"Category 3: Copy-paste test methods",{"type":43,"tag":52,"props":844,"children":845},{},[846],{"type":49,"value":847},"Look for test methods with near-identical bodies differing only in input values or a single parameter.",{"type":43,"tag":52,"props":849,"children":850},{},[851],{"type":43,"tag":438,"props":852,"children":853},{},[854],{"type":49,"value":442},{"type":43,"tag":65,"props":856,"children":857},{},[858,863,887],{"type":43,"tag":69,"props":859,"children":860},{},[861],{"type":49,"value":862},"3+ methods with the same structure but different literal values",{"type":43,"tag":69,"props":864,"children":865},{},[866,868,874,876,881,882],{"type":49,"value":867},"Methods that could be collapsed into ",{"type":43,"tag":119,"props":869,"children":871},{"className":870},[],[872],{"type":49,"value":873},"[DataRow]",{"type":49,"value":875},"\u002F",{"type":43,"tag":119,"props":877,"children":879},{"className":878},[],[880],{"type":49,"value":347},{"type":49,"value":875},{"type":43,"tag":119,"props":883,"children":885},{"className":884},[],[886],{"type":49,"value":380},{"type":43,"tag":69,"props":888,"children":889},{},[890,892,898,899],{"type":49,"value":891},"Test names that follow a pattern like ",{"type":43,"tag":119,"props":893,"children":895},{"className":894},[],[896],{"type":49,"value":897},"Method_Input1_Result",{"type":49,"value":308},{"type":43,"tag":119,"props":900,"children":902},{"className":901},[],[903],{"type":49,"value":904},"Method_Input2_Result",{"type":43,"tag":52,"props":906,"children":907},{},[908],{"type":43,"tag":438,"props":909,"children":910},{},[911],{"type":49,"value":474},{"type":43,"tag":65,"props":913,"children":914},{},[915,938,963,997],{"type":43,"tag":69,"props":916,"children":917},{},[918,920,925,926,932,933],{"type":49,"value":919},"Convert to parameterized tests with ",{"type":43,"tag":119,"props":921,"children":923},{"className":922},[],[924],{"type":49,"value":873},{"type":49,"value":875},{"type":43,"tag":119,"props":927,"children":929},{"className":928},[],[930],{"type":49,"value":931},"[InlineData]",{"type":49,"value":875},{"type":43,"tag":119,"props":934,"children":936},{"className":935},[],[937],{"type":49,"value":380},{"type":43,"tag":69,"props":939,"children":940},{},[941,942,948,949,955,956,961],{"type":49,"value":501},{"type":43,"tag":119,"props":943,"children":945},{"className":944},[],[946],{"type":49,"value":947},"[DynamicData]",{"type":49,"value":875},{"type":43,"tag":119,"props":950,"children":952},{"className":951},[],[953],{"type":49,"value":954},"[MemberData]",{"type":49,"value":875},{"type":43,"tag":119,"props":957,"children":959},{"className":958},[],[960],{"type":49,"value":387},{"type":49,"value":962}," for complex inputs",{"type":43,"tag":69,"props":964,"children":965},{},[966,968,973,975,981,983,988,990,995],{"type":49,"value":967},"Prefer ",{"type":43,"tag":119,"props":969,"children":971},{"className":970},[],[972],{"type":49,"value":873},{"type":49,"value":974}," with ",{"type":43,"tag":119,"props":976,"children":978},{"className":977},[],[979],{"type":49,"value":980},"DisplayName",{"type":49,"value":982}," over ",{"type":43,"tag":119,"props":984,"children":986},{"className":985},[],[987],{"type":49,"value":947},{"type":49,"value":989}," when all values are compile-time constants. Reserve ",{"type":43,"tag":119,"props":991,"children":993},{"className":992},[],[994],{"type":49,"value":947},{"type":49,"value":996}," for computed or complex values.",{"type":43,"tag":69,"props":998,"children":999},{},[1000,1002,1007,1009,1015,1017,1023],{"type":49,"value":1001},"Add ",{"type":43,"tag":119,"props":1003,"children":1005},{"className":1004},[],[1006],{"type":49,"value":980},{"type":49,"value":1008}," for non-obvious parameter values. ",{"type":43,"tag":119,"props":1010,"children":1012},{"className":1011},[],[1013],{"type":49,"value":1014},"[DataRow(\"Gold\", 100.0, 90.0)]",{"type":49,"value":1016}," is self-explanatory; ",{"type":43,"tag":119,"props":1018,"children":1020},{"className":1019},[],[1021],{"type":49,"value":1022},"[DataRow(3, 7, 42)]",{"type":49,"value":1024}," is not.",{"type":43,"tag":423,"props":1026,"children":1028},{"id":1027},"category-4-duplicated-setupteardown-logic",[1029],{"type":49,"value":1030},"Category 4: Duplicated setup\u002Fteardown logic",{"type":43,"tag":52,"props":1032,"children":1033},{},[1034],{"type":49,"value":1035},"Look for initialization or cleanup code repeated across test classes.",{"type":43,"tag":52,"props":1037,"children":1038},{},[1039],{"type":43,"tag":438,"props":1040,"children":1041},{},[1042],{"type":49,"value":442},{"type":43,"tag":65,"props":1044,"children":1045},{},[1046,1064,1069],{"type":43,"tag":69,"props":1047,"children":1048},{},[1049,1051,1056,1057,1062],{"type":49,"value":1050},"Multiple ",{"type":43,"tag":119,"props":1052,"children":1054},{"className":1053},[],[1055],{"type":49,"value":507},{"type":49,"value":875},{"type":43,"tag":119,"props":1058,"children":1060},{"className":1059},[],[1061],{"type":49,"value":515},{"type":49,"value":1063}," methods with similar bodies",{"type":43,"tag":69,"props":1065,"children":1066},{},[1067],{"type":49,"value":1068},"Repeated database seeding, file creation, or HTTP client configuration",{"type":43,"tag":69,"props":1070,"children":1071},{},[1072,1074,1080,1081,1087],{"type":49,"value":1073},"Same ",{"type":43,"tag":119,"props":1075,"children":1077},{"className":1076},[],[1078],{"type":49,"value":1079},"using",{"type":49,"value":875},{"type":43,"tag":119,"props":1082,"children":1084},{"className":1083},[],[1085],{"type":49,"value":1086},"IDisposable",{"type":49,"value":1088}," cleanup pattern across classes",{"type":43,"tag":52,"props":1090,"children":1091},{},[1092],{"type":43,"tag":438,"props":1093,"children":1094},{},[1095],{"type":49,"value":474},{"type":43,"tag":65,"props":1097,"children":1098},{},[1099,1104,1109],{"type":43,"tag":69,"props":1100,"children":1101},{},[1102],{"type":49,"value":1103},"Extract a shared test base class or fixture",{"type":43,"tag":69,"props":1105,"children":1106},{},[1107],{"type":49,"value":1108},"Use composition with a shared helper class",{"type":43,"tag":69,"props":1110,"children":1111},{},[1112],{"type":49,"value":1113},"Create a test context factory",{"type":43,"tag":423,"props":1115,"children":1117},{"id":1116},"category-5-repeated-test-infrastructure",[1118],{"type":49,"value":1119},"Category 5: Repeated test infrastructure",{"type":43,"tag":52,"props":1121,"children":1122},{},[1123],{"type":49,"value":1124},"Look for structural patterns shared across test classes.",{"type":43,"tag":52,"props":1126,"children":1127},{},[1128],{"type":43,"tag":438,"props":1129,"children":1130},{},[1131],{"type":49,"value":442},{"type":43,"tag":65,"props":1133,"children":1134},{},[1135,1140,1161],{"type":43,"tag":69,"props":1136,"children":1137},{},[1138],{"type":49,"value":1139},"Same mock interfaces configured identically in multiple classes",{"type":43,"tag":69,"props":1141,"children":1142},{},[1143,1145,1151,1153,1159],{"type":49,"value":1144},"Repeated ",{"type":43,"tag":119,"props":1146,"children":1148},{"className":1147},[],[1149],{"type":49,"value":1150},"HttpClient",{"type":49,"value":1152}," setup with similar ",{"type":43,"tag":119,"props":1154,"children":1156},{"className":1155},[],[1157],{"type":49,"value":1158},"DelegatingHandler",{"type":49,"value":1160}," patterns",{"type":43,"tag":69,"props":1162,"children":1163},{},[1164],{"type":49,"value":1165},"Same logging\u002Fconfiguration scaffolding across test classes",{"type":43,"tag":52,"props":1167,"children":1168},{},[1169],{"type":43,"tag":438,"props":1170,"children":1171},{},[1172],{"type":49,"value":474},{"type":43,"tag":65,"props":1174,"children":1175},{},[1176,1181,1186],{"type":43,"tag":69,"props":1177,"children":1178},{},[1179],{"type":49,"value":1180},"Extract a shared test fixture or helper library",{"type":43,"tag":69,"props":1182,"children":1183},{},[1184],{"type":49,"value":1185},"Create reusable fake implementations",{"type":43,"tag":69,"props":1187,"children":1188},{},[1189],{"type":49,"value":1190},"Introduce a test harness class",{"type":43,"tag":243,"props":1192,"children":1194},{"id":1193},"step-3-apply-calibration-rules",[1195],{"type":49,"value":1196},"Step 3: Apply calibration rules",{"type":43,"tag":52,"props":1198,"children":1199},{},[1200],{"type":49,"value":1201},"Before reporting, filter findings through these rules:",{"type":43,"tag":65,"props":1203,"children":1204},{},[1205,1215,1249,1259,1269,1279],{"type":43,"tag":69,"props":1206,"children":1207},{},[1208,1213],{"type":43,"tag":438,"props":1209,"children":1210},{},[1211],{"type":49,"value":1212},"Only report at 3+ occurrences.",{"type":49,"value":1214}," Two similar setups are not boilerplate — they may be intentional clarity.",{"type":43,"tag":69,"props":1216,"children":1217},{},[1218,1223,1225,1231,1233,1239,1241,1247],{"type":43,"tag":438,"props":1219,"children":1220},{},[1221],{"type":49,"value":1222},"Don't flag simple constructors.",{"type":49,"value":1224}," ",{"type":43,"tag":119,"props":1226,"children":1228},{"className":1227},[],[1229],{"type":49,"value":1230},"new Calculator()",{"type":49,"value":1232}," or ",{"type":43,"tag":119,"props":1234,"children":1236},{"className":1235},[],[1237],{"type":49,"value":1238},"new List\u003Cint>()",{"type":49,"value":1240}," is not meaningful boilerplate. Don't recommend builders for ",{"type":43,"tag":119,"props":1242,"children":1244},{"className":1243},[],[1245],{"type":49,"value":1246},"new User(1, \"Alice\")",{"type":49,"value":1248}," either.",{"type":43,"tag":69,"props":1250,"children":1251},{},[1252,1257],{"type":43,"tag":438,"props":1253,"children":1254},{},[1255],{"type":49,"value":1256},"Respect intentional verbosity.",{"type":49,"value":1258}," If each test is self-contained and reads clearly on its own, explicit setup per test is a valid choice. Note it but don't flag it as a problem.",{"type":43,"tag":69,"props":1260,"children":1261},{},[1262,1267],{"type":43,"tag":438,"props":1263,"children":1264},{},[1265],{"type":49,"value":1266},"Distinguish structural similarity from true duplication.",{"type":49,"value":1268}," Tests that follow AAA (Arrange-Act-Assert) will look similar by nature. Only flag when the actual code (not just the structure) is duplicated.",{"type":43,"tag":69,"props":1270,"children":1271},{},[1272,1277],{"type":43,"tag":438,"props":1273,"children":1274},{},[1275],{"type":49,"value":1276},"Consider the blast radius of refactoring.",{"type":49,"value":1278}," A helper shared across 20 tests creates coupling. Note the trade-off.",{"type":43,"tag":69,"props":1280,"children":1281},{},[1282,1287],{"type":43,"tag":438,"props":1283,"children":1284},{},[1285],{"type":49,"value":1286},"If tests are already well-maintained, say so.",{"type":49,"value":1288}," A report finding only minor opportunities is perfectly valid. Acknowledge what's already good.",{"type":43,"tag":243,"props":1290,"children":1292},{"id":1291},"step-4-report-findings",[1293],{"type":49,"value":1294},"Step 4: Report findings",{"type":43,"tag":52,"props":1296,"children":1297},{},[1298],{"type":49,"value":1299},"Present findings in this structure:",{"type":43,"tag":1301,"props":1302,"children":1303},"ol",{},[1304,1314,1352,1380],{"type":43,"tag":69,"props":1305,"children":1306},{},[1307,1312],{"type":43,"tag":438,"props":1308,"children":1309},{},[1310],{"type":49,"value":1311},"Summary",{"type":49,"value":1313}," — How many patterns found, broken down by category. If the test suite is clean, lead with that.",{"type":43,"tag":69,"props":1315,"children":1316},{},[1317,1322,1324],{"type":43,"tag":438,"props":1318,"children":1319},{},[1320],{"type":49,"value":1321},"Findings by category",{"type":49,"value":1323}," — For each pattern found:\n",{"type":43,"tag":65,"props":1325,"children":1326},{},[1327,1332,1337,1342,1347],{"type":43,"tag":69,"props":1328,"children":1329},{},[1330],{"type":49,"value":1331},"Category name and description",{"type":43,"tag":69,"props":1333,"children":1334},{},[1335],{"type":49,"value":1336},"Locations: list the specific test methods and files involved",{"type":43,"tag":69,"props":1338,"children":1339},{},[1340],{"type":49,"value":1341},"The duplicated code pattern (show a representative sample)",{"type":43,"tag":69,"props":1343,"children":1344},{},[1345],{"type":49,"value":1346},"Suggested refactoring with a concrete before\u002Fafter example",{"type":43,"tag":69,"props":1348,"children":1349},{},[1350],{"type":49,"value":1351},"Estimated impact: how many lines\u002Fmethods would be simplified",{"type":43,"tag":69,"props":1353,"children":1354},{},[1355,1360,1362],{"type":43,"tag":438,"props":1356,"children":1357},{},[1358],{"type":49,"value":1359},"Refactoring priority",{"type":49,"value":1361}," — Rank findings by:\n",{"type":43,"tag":65,"props":1363,"children":1364},{},[1365,1370,1375],{"type":43,"tag":69,"props":1366,"children":1367},{},[1368],{"type":49,"value":1369},"Occurrence count (more occurrences = higher value)",{"type":43,"tag":69,"props":1371,"children":1372},{},[1373],{"type":49,"value":1374},"Complexity of the duplicated code (complex setup > simple construction)",{"type":43,"tag":69,"props":1376,"children":1377},{},[1378],{"type":49,"value":1379},"Risk (low-risk extractions first)",{"type":43,"tag":69,"props":1381,"children":1382},{},[1383,1388,1390],{"type":43,"tag":438,"props":1384,"children":1385},{},[1386],{"type":49,"value":1387},"Trade-offs",{"type":49,"value":1389}," — For each suggestion, note:\n",{"type":43,"tag":65,"props":1391,"children":1392},{},[1393,1398,1403],{"type":43,"tag":69,"props":1394,"children":1395},{},[1396],{"type":49,"value":1397},"What readability is gained",{"type":43,"tag":69,"props":1399,"children":1400},{},[1401],{"type":49,"value":1402},"What locality\u002Findependence is lost",{"type":43,"tag":69,"props":1404,"children":1405},{},[1406],{"type":49,"value":1407},"Whether it's worth it given the occurrence count",{"type":43,"tag":58,"props":1409,"children":1411},{"id":1410},"validation",[1412],{"type":49,"value":1413},"Validation",{"type":43,"tag":65,"props":1415,"children":1418},{"className":1416},[1417],"contains-task-list",[1419,1431,1440,1449,1458,1467,1476],{"type":43,"tag":69,"props":1420,"children":1423},{"className":1421},[1422],"task-list-item",[1424,1429],{"type":43,"tag":1425,"props":1426,"children":1428},"input",{"disabled":631,"type":1427},"checkbox",[],{"type":49,"value":1430}," Every finding includes specific file and method locations",{"type":43,"tag":69,"props":1432,"children":1434},{"className":1433},[1422],[1435,1438],{"type":43,"tag":1425,"props":1436,"children":1437},{"disabled":631,"type":1427},[],{"type":49,"value":1439}," Every finding shows the actual duplicated code, not just a description",{"type":43,"tag":69,"props":1441,"children":1443},{"className":1442},[1422],[1444,1447],{"type":43,"tag":1425,"props":1445,"children":1446},{"disabled":631,"type":1427},[],{"type":49,"value":1448}," Every suggestion includes a concrete before\u002Fafter example",{"type":43,"tag":69,"props":1450,"children":1452},{"className":1451},[1422],[1453,1456],{"type":43,"tag":1425,"props":1454,"children":1455},{"disabled":631,"type":1427},[],{"type":49,"value":1457}," Findings are filtered through the 3+ occurrence threshold",{"type":43,"tag":69,"props":1459,"children":1461},{"className":1460},[1422],[1462,1465],{"type":43,"tag":1425,"props":1463,"children":1464},{"disabled":631,"type":1427},[],{"type":49,"value":1466}," Simple constructors are not flagged",{"type":43,"tag":69,"props":1468,"children":1470},{"className":1469},[1422],[1471,1474],{"type":43,"tag":1425,"props":1472,"children":1473},{"disabled":631,"type":1427},[],{"type":49,"value":1475}," Trade-offs are acknowledged for each suggestion",{"type":43,"tag":69,"props":1477,"children":1479},{"className":1478},[1422],[1480,1483],{"type":43,"tag":1425,"props":1481,"children":1482},{"disabled":631,"type":1427},[],{"type":49,"value":1484}," If tests are clean, the report says so upfront",{"type":43,"tag":58,"props":1486,"children":1488},{"id":1487},"common-pitfalls",[1489],{"type":49,"value":1490},"Common Pitfalls",{"type":43,"tag":151,"props":1492,"children":1493},{},[1494,1510],{"type":43,"tag":155,"props":1495,"children":1496},{},[1497],{"type":43,"tag":159,"props":1498,"children":1499},{},[1500,1505],{"type":43,"tag":163,"props":1501,"children":1502},{},[1503],{"type":49,"value":1504},"Pitfall",{"type":43,"tag":163,"props":1506,"children":1507},{},[1508],{"type":49,"value":1509},"Solution",{"type":43,"tag":179,"props":1511,"children":1512},{},[1513,1526,1539,1552,1565,1586],{"type":43,"tag":159,"props":1514,"children":1515},{},[1516,1521],{"type":43,"tag":186,"props":1517,"children":1518},{},[1519],{"type":49,"value":1520},"Flagging AAA structure as duplication",{"type":43,"tag":186,"props":1522,"children":1523},{},[1524],{"type":49,"value":1525},"The Arrange-Act-Assert pattern is not boilerplate — flag only when the actual code repeats",{"type":43,"tag":159,"props":1527,"children":1528},{},[1529,1534],{"type":43,"tag":186,"props":1530,"children":1531},{},[1532],{"type":49,"value":1533},"Suggesting extraction for 2 occurrences",{"type":43,"tag":186,"props":1535,"children":1536},{},[1537],{"type":49,"value":1538},"Wait for 3+ before recommending extraction",{"type":43,"tag":159,"props":1540,"children":1541},{},[1542,1547],{"type":43,"tag":186,"props":1543,"children":1544},{},[1545],{"type":49,"value":1546},"Recommending base classes for everything",{"type":43,"tag":186,"props":1548,"children":1549},{},[1550],{"type":49,"value":1551},"Prefer composition (helpers, factories) over inheritance",{"type":43,"tag":159,"props":1553,"children":1554},{},[1555,1560],{"type":43,"tag":186,"props":1556,"children":1557},{},[1558],{"type":49,"value":1559},"Ignoring the readability cost",{"type":43,"tag":186,"props":1561,"children":1562},{},[1563],{"type":49,"value":1564},"Every extraction adds indirection — note the trade-off",{"type":43,"tag":159,"props":1566,"children":1567},{},[1568,1581],{"type":43,"tag":186,"props":1569,"children":1570},{},[1571,1573,1579],{"type":49,"value":1572},"Flagging simple ",{"type":43,"tag":119,"props":1574,"children":1576},{"className":1575},[],[1577],{"type":49,"value":1578},"new X()",{"type":49,"value":1580}," as boilerplate",{"type":43,"tag":186,"props":1582,"children":1583},{},[1584],{"type":49,"value":1585},"Only flag complex construction with multiple parameters or configuration",{"type":43,"tag":159,"props":1587,"children":1588},{},[1589,1594],{"type":43,"tag":186,"props":1590,"children":1591},{},[1592],{"type":49,"value":1593},"Recommending DRY at the expense of test isolation",{"type":43,"tag":186,"props":1595,"children":1596},{},[1597],{"type":49,"value":1598},"Tests that share mutable state through helpers become coupled — warn about this",{"type":43,"tag":1600,"props":1601,"children":1602},"style",{},[1603],{"type":49,"value":1604},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":1606,"total":1708},[1607,1622,1637,1655,1667,1686,1696],{"slug":1608,"name":1608,"fn":1609,"description":1610,"org":1611,"tags":1612,"stars":25,"repoUrl":26,"updatedAt":1621},"analyzing-dotnet-performance","analyze .NET code for performance anti-patterns","Scans .NET code for ~50 performance anti-patterns across async, memory, strings, collections, LINQ, regex, serialization, and I\u002FO with tiered severity classification. Use when analyzing .NET code for optimization opportunities, reviewing hot paths, or auditing allocation-heavy patterns.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1613,1614,1615,1618],{"name":13,"slug":14,"type":15},{"name":20,"slug":21,"type":15},{"name":1616,"slug":1617,"type":15},"Debugging","debugging",{"name":1619,"slug":1620,"type":15},"Performance","performance","2026-07-12T08:23:25.400375",{"slug":1623,"name":1623,"fn":1624,"description":1625,"org":1626,"tags":1627,"stars":25,"repoUrl":26,"updatedAt":1636},"android-tombstone-symbolication","symbolicate .NET runtime frames in Android tombstones","Symbolicate the .NET runtime frames in an Android tombstone file. Extracts BuildIds and PC offsets from the native backtrace, downloads debug symbols from the Microsoft symbol server, and runs llvm-symbolizer to produce function names with source file and line numbers. USE FOR triaging a .NET MAUI or Mono Android app crash from a tombstone, resolving native backtrace frames in libmonosgen-2.0.so or libcoreclr.so to .NET runtime source code, or investigating SIGABRT, SIGSEGV, or other native signals originating from the .NET runtime on Android. DO NOT USE FOR pure Java\u002FKotlin crashes, managed .NET exceptions that are already captured in logcat, or iOS crash logs. INVOKES Symbolicate-Tombstone.ps1 script, llvm-symbolizer, Microsoft symbol server.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1628,1629,1632,1633],{"name":13,"slug":14,"type":15},{"name":1630,"slug":1631,"type":15},"Android","android",{"name":1616,"slug":1617,"type":15},{"name":1634,"slug":1635,"type":15},"Microsoft","microsoft","2026-07-12T08:23:21.595572",{"slug":1638,"name":1638,"fn":1639,"description":1640,"org":1641,"tags":1642,"stars":25,"repoUrl":26,"updatedAt":1654},"apple-crash-symbolication","symbolicate .NET runtime frames in crash logs","Symbolicate .NET runtime frames in Apple platform .ips crash logs (iOS, tvOS, Mac Catalyst, macOS). Extracts UUIDs and addresses from the native backtrace, locates dSYM debug symbols, and runs atos to produce function names with source file and line numbers. Automatically downloads .dwarf symbols from the Microsoft symbol server using Mach-O UUIDs. USE FOR triaging a .NET MAUI or Mono app crash from an .ips file on any Apple platform, resolving native backtrace frames in libcoreclr or libmonosgen-2.0 to .NET runtime source code, retrieving .ips crash logs from a connected iOS device or iPhone, or investigating EXC_CRASH, EXC_BAD_ACCESS, SIGABRT, or SIGSEGV originating from the .NET runtime. DO NOT USE FOR pure Swift\u002FObjective-C crashes with no .NET components, or Android tombstone files. INVOKES Symbolicate-Crash.ps1 script, atos, dwarfdump, idevicecrashreport.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1643,1644,1645,1648,1651],{"name":13,"slug":14,"type":15},{"name":1616,"slug":1617,"type":15},{"name":1646,"slug":1647,"type":15},"iOS","ios",{"name":1649,"slug":1650,"type":15},"macOS","macos",{"name":1652,"slug":1653,"type":15},"Observability","observability","2026-07-12T08:23:20.369986",{"slug":1656,"name":1656,"fn":1657,"description":1658,"org":1659,"tags":1660,"stars":25,"repoUrl":26,"updatedAt":1666},"assertion-quality","evaluate assertion quality in test suites","Analyzes the variety and depth of assertions across test suites in any language. Use when the user asks to evaluate assertion quality, find shallow tests, identify assertion-free tests (no assertions or only trivial ones like Assert.IsNotNull \u002F toBeTruthy()), flag self-referential or tautological assertions, measure assertion diversity, or audit whether tests verify different facets of behavior. Polyglot: .NET, Python, TS\u002FJS, Java, Go, Ruby, Rust, Swift, Kotlin, PowerShell, C++. DO NOT USE FOR: writing new tests (use code-testing-agent \u002F writing-mstest-tests), mutation reasoning about whether tests would catch a bug (use test-gap-analysis), or a general severity-ranked anti-pattern audit (use test-anti-patterns), fixing or rewriting assertions, or writing, fixing, or modernizing MSTest tests, assertions, or attributes (use writing-mstest-tests).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1661,1662,1665],{"name":20,"slug":21,"type":15},{"name":1663,"slug":1664,"type":15},"QA","qa",{"name":23,"slug":24,"type":15},"2026-07-12T08:23:51.277743",{"slug":1668,"name":1668,"fn":1669,"description":1670,"org":1671,"tags":1672,"stars":25,"repoUrl":26,"updatedAt":1685},"author-component","create and review Blazor components","Create or review Blazor components (.razor files) with correct architecture. USE FOR: writing new Blazor components that do NOT involve JavaScript interop, implementing parameters and EventCallback, RenderFragment slots, component lifecycle (OnInitializedAsync, OnParametersSet), async patterns, IAsyncDisposable, CancellationToken, CSS isolation, code-behind. DO NOT USE FOR: creating new projects (use create-blazor-project), JavaScript interop or calling browser APIs from Blazor (use use-js-interop), forms and validation (use collect-user-input), prerendering issues (use support-prerendering), HTTP data fetching patterns (use fetch-and-send-data), coordinating state between unrelated components (use coordinate-components).\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1673,1674,1677,1679,1682],{"name":13,"slug":14,"type":15},{"name":1675,"slug":1676,"type":15},"Blazor","blazor",{"name":1678,"slug":536,"type":15},"C#",{"name":1680,"slug":1681,"type":15},"UI Components","ui-components",{"name":1683,"slug":1684,"type":15},"Web Development","web-development","2026-07-15T06:03:29.216359",{"slug":1687,"name":1687,"fn":1688,"description":1689,"org":1690,"tags":1691,"stars":25,"repoUrl":26,"updatedAt":1695},"binlog-failure-analysis","analyze MSBuild binary logs","Analyze MSBuild binary logs to diagnose build failures. USE FOR: build errors that are unclear from console output, diagnosing cascading failures across multi-project builds, tracing MSBuild target execution order, and generally any MSBuild build issues. Requires an existing .binlog file. DO NOT USE FOR: generating binlogs (use binlog-generation), non-MSBuild build systems.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1692,1693,1694],{"name":20,"slug":21,"type":15},{"name":1616,"slug":1617,"type":15},{"name":1634,"slug":1635,"type":15},"2026-07-12T08:21:34.637923",{"slug":1697,"name":1697,"fn":1698,"description":1699,"org":1700,"tags":1701,"stars":25,"repoUrl":26,"updatedAt":1707},"binlog-generation","generate MSBuild binary logs for diagnostics","Generate MSBuild binary logs (binlogs) for build diagnostics and analysis. USE FOR: adding \u002Fbl:{} to any dotnet build, test, pack, publish, or restore command to capture a full build execution trace, prerequisite for binlog-failure-analysis and build-perf-diagnostics skills, enabling post-build investigation of errors or performance. Requires MSBuild 17.8+ \u002F .NET 8 SDK+ for {} placeholder; PowerShell needs -bl:{{}}. DO NOT USE FOR: non-MSBuild build systems (npm, Maven, CMake), analyzing an existing binlog (use binlog-failure-analysis instead).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1702,1705,1706],{"name":1703,"slug":1704,"type":15},"Build","build",{"name":1616,"slug":1617,"type":15},{"name":17,"slug":18,"type":15},"2026-07-19T05:38:19.340791",96,{"items":1710,"total":1815},[1711,1723,1730,1737,1745,1751,1759,1765,1771,1781,1794,1805],{"slug":1712,"name":1712,"fn":1713,"description":1714,"org":1715,"tags":1716,"stars":1720,"repoUrl":1721,"updatedAt":1722},"multithreaded-task-migration","migrate MSBuild tasks to multithreaded mode","Guide for migrating MSBuild tasks to multithreaded mode support, including compatibility red-team review. Use this when converting tasks to thread-safe versions, implementing IMultiThreadableTask, adding TaskEnvironment support, or auditing migrations for behavioral compatibility.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1717,1718,1719],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":1619,"slug":1620,"type":15},5535,"https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fmsbuild","2026-07-22T05:37:33.965588",{"slug":1608,"name":1608,"fn":1609,"description":1610,"org":1724,"tags":1725,"stars":25,"repoUrl":26,"updatedAt":1621},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1726,1727,1728,1729],{"name":13,"slug":14,"type":15},{"name":20,"slug":21,"type":15},{"name":1616,"slug":1617,"type":15},{"name":1619,"slug":1620,"type":15},{"slug":1623,"name":1623,"fn":1624,"description":1625,"org":1731,"tags":1732,"stars":25,"repoUrl":26,"updatedAt":1636},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1733,1734,1735,1736],{"name":13,"slug":14,"type":15},{"name":1630,"slug":1631,"type":15},{"name":1616,"slug":1617,"type":15},{"name":1634,"slug":1635,"type":15},{"slug":1638,"name":1638,"fn":1639,"description":1640,"org":1738,"tags":1739,"stars":25,"repoUrl":26,"updatedAt":1654},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1740,1741,1742,1743,1744],{"name":13,"slug":14,"type":15},{"name":1616,"slug":1617,"type":15},{"name":1646,"slug":1647,"type":15},{"name":1649,"slug":1650,"type":15},{"name":1652,"slug":1653,"type":15},{"slug":1656,"name":1656,"fn":1657,"description":1658,"org":1746,"tags":1747,"stars":25,"repoUrl":26,"updatedAt":1666},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1748,1749,1750],{"name":20,"slug":21,"type":15},{"name":1663,"slug":1664,"type":15},{"name":23,"slug":24,"type":15},{"slug":1668,"name":1668,"fn":1669,"description":1670,"org":1752,"tags":1753,"stars":25,"repoUrl":26,"updatedAt":1685},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1754,1755,1756,1757,1758],{"name":13,"slug":14,"type":15},{"name":1675,"slug":1676,"type":15},{"name":1678,"slug":536,"type":15},{"name":1680,"slug":1681,"type":15},{"name":1683,"slug":1684,"type":15},{"slug":1687,"name":1687,"fn":1688,"description":1689,"org":1760,"tags":1761,"stars":25,"repoUrl":26,"updatedAt":1695},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1762,1763,1764],{"name":20,"slug":21,"type":15},{"name":1616,"slug":1617,"type":15},{"name":1634,"slug":1635,"type":15},{"slug":1697,"name":1697,"fn":1698,"description":1699,"org":1766,"tags":1767,"stars":25,"repoUrl":26,"updatedAt":1707},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1768,1769,1770],{"name":1703,"slug":1704,"type":15},{"name":1616,"slug":1617,"type":15},{"name":17,"slug":18,"type":15},{"slug":1772,"name":1772,"fn":1773,"description":1774,"org":1775,"tags":1776,"stars":25,"repoUrl":26,"updatedAt":1780},"build-parallelism","optimize MSBuild build parallelism","Diagnose and fix under-parallelized MSBuild builds. USE WHEN a multi-project solution build is slower than expected, doesn't speed up when you add cores, pegs a single core while others idle, or you want to know why `-m` isn't helping. Note: `\u002Fmaxcpucount` default is 1 (sequential) — always pass `-m` for parallel builds. Covers finding the critical path (longest serial ProjectReference chain), graph build (`\u002Fgraph`), BuildInParallel, and solution filters (`.slnf`). DO NOT USE FOR: single-project builds, incremental issues (use incremental-build), compilation slowness inside one project (use build-perf-diagnostics), non-MSBuild build systems.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1777,1778,1779],{"name":13,"slug":14,"type":15},{"name":17,"slug":18,"type":15},{"name":1619,"slug":1620,"type":15},"2026-07-19T05:38:18.364937",{"slug":1782,"name":1782,"fn":1783,"description":1784,"org":1785,"tags":1786,"stars":25,"repoUrl":26,"updatedAt":1793},"build-perf-baseline","establish and optimize build performance baselines","Establish build performance baselines and apply systematic optimization techniques. USE FOR: diagnosing slow builds, establishing before\u002Fafter measurements (cold, warm, no-op scenarios), applying optimization strategies like MSBuild Server, static graph builds, artifacts output, and dependency graph trimming. Start here before diving into build-perf-diagnostics, incremental-build, or build-parallelism. DO NOT USE FOR: non-MSBuild build systems, detailed bottleneck analysis (use build-perf-diagnostics after baselining).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1787,1788,1791,1792],{"name":17,"slug":18,"type":15},{"name":1789,"slug":1790,"type":15},"Monitoring","monitoring",{"name":1619,"slug":1620,"type":15},{"name":23,"slug":24,"type":15},"2026-07-12T08:21:35.865649",{"slug":1795,"name":1795,"fn":1796,"description":1797,"org":1798,"tags":1799,"stars":25,"repoUrl":26,"updatedAt":1804},"build-perf-diagnostics","diagnose MSBuild build performance bottlenecks","Diagnose MSBuild build performance bottlenecks using binary log analysis. USE FOR: identifying why builds are slow by analyzing binlog performance summaries, detecting ResolveAssemblyReference (RAR) taking >5s, Roslyn analyzers consuming >30% of Csc time, single targets dominating >50% of build time, node utilization below 80%, excessive Copy tasks, NuGet restore running every build. Covers timeline analysis, Target\u002FTask Performance Summary interpretation, and 7 common bottleneck categories. Use after build-perf-baseline has established measurements. DO NOT USE FOR: establishing initial baselines (use build-perf-baseline first), fixing incremental build issues (use incremental-build), parallelism tuning (use build-parallelism), non-MSBuild build systems.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1800,1801,1802,1803],{"name":13,"slug":14,"type":15},{"name":1616,"slug":1617,"type":15},{"name":17,"slug":18,"type":15},{"name":1619,"slug":1620,"type":15},"2026-07-12T08:21:40.961722",{"slug":1806,"name":1806,"fn":1807,"description":1808,"org":1809,"tags":1810,"stars":25,"repoUrl":26,"updatedAt":1814},"check-bin-obj-clash","detect MSBuild output path conflicts","Detects MSBuild projects with conflicting OutputPath or IntermediateOutputPath. USE FOR: builds failing with 'Cannot create a file when that file already exists', 'The process cannot access the file because it is being used by another process', intermittent build failures that succeed on retry, or missing\u002Foverwritten outputs in multi-project or multi-targeting builds where bin\u002Fobj (or project.assets.json) collide. Common causes: shared OutputPath, missing AppendTargetFrameworkToOutputPath, extra global properties (e.g. PublishReadyToRun), or SetTargetFramework on a ProjectReference to a single-targeting project. DO NOT USE FOR: file access errors unrelated to MSBuild (OS-level locking), single-project single-TFM builds, non-MSBuild build systems.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1811,1812,1813],{"name":1616,"slug":1617,"type":15},{"name":17,"slug":18,"type":15},{"name":1663,"slug":1664,"type":15},"2026-07-19T05:38:14.336279",144]