[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-dotnet-migrate-static-to-wrapper":3,"mdc-b76unx-key":34,"related-org-dotnet-migrate-static-to-wrapper":2399,"related-repo-dotnet-migrate-static-to-wrapper":2563},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":22,"repoUrl":23,"updatedAt":24,"license":25,"forks":26,"topics":27,"repo":29,"sourceUrl":32,"mdContent":33},"migrate-static-to-wrapper","migrate static calls to wrapper abstractions","Replace existing static dependency call sites with a wrapper or built-in abstraction that already exists or is registered in DI, across a bounded scope (file, project, namespace). USE FOR: replace DateTime.UtcNow\u002FDateTime.Now with TimeProvider and add the constructor parameter, migrate static call sites to a wrapper already in DI, bulk replace File.* with IFileSystem, scoped migration of statics in only certain files, update unit tests to a fake time source, make an existing static or utility class testable by adding an ambient TimeProvider\u002FIFileSystem seam while every current call site keeps compiling, behavior-preserving time refactors that must keep the same DateTimeKind. DO NOT USE FOR: detecting statics (use detect-static-dependencies), designing a brand-new wrapper interface that does not exist yet (use generate-testability-wrappers), migrating between test frameworks.\n",{"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],{"name":13,"slug":14,"type":15},".NET","net","tag",{"name":17,"slug":18,"type":15},"Migration","migration",{"name":20,"slug":21,"type":15},"Code Analysis","code-analysis",4576,"https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fskills","2026-08-01T05:42:11.17328","MIT",332,[28],"agent-skills",{"repoUrl":23,"stars":22,"forks":26,"topics":30,"description":31},[28],"Repository for skills to assist AI coding agents with .NET and C#","https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fskills\u002Ftree\u002FHEAD\u002Fplugins\u002Fdotnet-test\u002Fskills\u002Fmigrate-static-to-wrapper","---\nname: migrate-static-to-wrapper\ndescription: >\n  Replace existing static dependency call sites with a wrapper or built-in\n  abstraction that already exists or is registered in DI, across a bounded scope\n  (file, project, namespace).\n  USE FOR: replace DateTime.UtcNow\u002FDateTime.Now with TimeProvider and add the\n  constructor parameter, migrate static call sites to a wrapper already in DI,\n  bulk replace File.* with IFileSystem, scoped migration of statics in only\n  certain files, update unit tests to a fake time source, make an existing\n  static or utility class testable by adding an ambient\n  TimeProvider\u002FIFileSystem seam while every current call site keeps compiling,\n  behavior-preserving time refactors that must keep the same DateTimeKind.\n  DO NOT USE FOR: detecting statics (use detect-static-dependencies), designing a\n  brand-new wrapper interface that does not exist yet (use\n  generate-testability-wrappers), migrating between test frameworks.\nlicense: MIT\n---\n\n# Migrate Static to Wrapper\n\nPerform mechanical, codemod-style replacement of static dependency call sites with calls to injected wrapper interfaces or built-in abstractions. Operates on a bounded scope (single file, project, or namespace) so migrations can be done incrementally.\n\n## When to Use\n\n- After wrappers have been generated (via `generate-testability-wrappers`) or built-in abstractions identified\n- Migrating `DateTime.UtcNow` → `TimeProvider.GetUtcNow()` across a project\n- Migrating `File.*` → `IFileSystem.File.*` across a namespace\n- Adding constructor injection for the new abstraction to affected classes\n- Making a `static` utility class testable by adding an ambient seam (Step 3) while its existing call sites keep\n  compiling unchanged\n- Incremental migration: one project or namespace at a time\n\n## When Not to Use\n\n- No wrapper or abstraction exists yet and one must be designed from scratch (use `generate-testability-wrappers` first).\n  A built-in abstraction such as `TimeProvider` or `IFileSystem` always counts as existing.\n- The user wants to detect statics, not migrate them (use `detect-static-dependencies`)\n- Migrating between test frameworks (use the appropriate migration skill)\n\n> A class that is `static`, or a project with no DI container, is **not** a reason to skip this skill — that is exactly\n> what the ambient seam in Step 3 is for. Use it whenever the call sites must keep compiling unchanged.\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Static pattern | Yes | What to replace (e.g., `DateTime.UtcNow`, `File.ReadAllText`) |\n| Replacement abstraction | Yes | What to use instead (e.g., `TimeProvider`, `IFileSystem`) |\n| Scope | Yes | File path, project (.csproj), namespace, or directory to migrate |\n| Injection strategy | No | `constructor` (default), `primary-constructor`, or `ambient` |\n\n## Workflow\n\n### Step 1: Verify prerequisites\n\nBefore modifying any code:\n\n1. **Confirm the wrapper\u002Fabstraction exists**: Check that the interface or built-in abstraction is available in the project. For `TimeProvider`, verify the target framework is .NET 8+ or `Microsoft.Bcl.TimeProvider` is referenced. For `System.IO.Abstractions`, verify the NuGet package is referenced.\n\n2. **Confirm DI registration exists**: Check `Program.cs` or `Startup.cs` for the service registration. If missing, add it before proceeding.\n\n3. **Identify all files in scope**: List the `.cs` files that will be modified. Exclude test projects, `obj\u002F`, `bin\u002F`, and generated code.\n\n### Step 2: Plan the migration for each file\n\n**Migrate exactly what was asked — nothing adjacent.** If the user named a member (`DateTime.UtcNow`), migrate only that member and leave siblings such as `DateTime.Now` untouched. If the user named files, do not touch other files. Never migrate a call site whose comment or name marks it as deliberate (e.g. `\u002F\u002F intentional local time`). List everything you deliberately left alone under \"Remaining (out of scope)\" so the user can ask for it in a follow-up; suggesting is fine, silently widening the scope is not.\n\nFor each file containing the static pattern, determine:\n\n1. **Which class(es) contain the call sites** — identify the class declarations\n2. **Whether the class already has the dependency injected** — check constructors for existing `TimeProvider`, `IFileSystem`, etc. parameters\n3. **The replacement expression** for each call site\n\n#### Replacement mapping\n\n| Category | Original | DI replacement |\n|----------|----------|----------------|\n| Time | `DateTime.Now` | `_timeProvider.GetLocalNow().LocalDateTime` |\n| Time | `DateTime.UtcNow` | `_timeProvider.GetUtcNow().UtcDateTime` |\n| Time | `DateTime.Today` | `_timeProvider.GetLocalNow().LocalDateTime.Date` |\n| Time | `DateTimeOffset.Now` | `_timeProvider.GetLocalNow()` |\n| Time | `DateTimeOffset.UtcNow` | `_timeProvider.GetUtcNow()` |\n| File | `File.ReadAllText(path)` | `_fileSystem.File.ReadAllText(path)` |\n| File | `File.WriteAllText(path, text)` | `_fileSystem.File.WriteAllText(path, text)` |\n| File | `File.Exists(path)` | `_fileSystem.File.Exists(path)` |\n| File | `Directory.Exists(path)` | `_fileSystem.Directory.Exists(path)` |\n| Env | `Environment.GetEnvironmentVariable(name)` | `_env.GetEnvironmentVariable(name)` |\n| Console | `Console.WriteLine(msg)` | `_console.WriteLine(msg)` |\n| Process | `Process.Start(info)` | `_processRunner.Start(info)` |\n\nApply the same pattern for other members in each category.\n\n> **Preserve `DateTimeKind` — this is the most common silent regression.** `TimeProvider.GetUtcNow()` \u002F `GetLocalNow()` return a `DateTimeOffset`. Converting back to `DateTime` **must keep the original `Kind`**, otherwise you introduce a behavioral change even though the code still compiles:\n>\n> - `DateTime.UtcNow` has `Kind == Utc` → use `.UtcDateTime` (**not** `.DateTime`, which yields `Kind == Unspecified`).\n> - `DateTime.Now` has `Kind == Local` → use `.LocalDateTime` (**not** `.DateTime`).\n> - When a call site consumes a `DateTimeOffset` directly (a field\u002Fparameter\u002Freturn already typed `DateTimeOffset`), drop the `.UtcDateTime`\u002F`.LocalDateTime` suffix and assign the `DateTimeOffset` as-is — don't force it back through `DateTime`.\n>\n> Match the **target member's type**: if the surrounding field\u002Fproperty is `DateTime`, keep it `DateTime` (via the Kind-correct property above); do not change it to `DateTimeOffset` as part of a \"mechanical\" migration — that is a design change, not a delegation.\n\n### Step 3: Add constructor injection\n\nAdd the new dependency following the class's existing pattern:\n\n- **Primary constructor** (C# 12+): Add parameter to primary constructor: `public class OrderProcessor(ILogger\u003COrderProcessor> logger, TimeProvider timeProvider)`\n- **Traditional constructor**: Add `private readonly` field + constructor parameter, matching the existing field naming convention (`_camelCase` or `m_camelCase`)\n\n#### Static classes: use ambient context (no constructor injection)\n\nA `static` class with only static members **cannot** receive constructor injection — adding an instance constructor or instance field would break it. Do **not** convert it to a non-static class just to inject the dependency; that changes its design and every call site. Instead, apply the **ambient context** pattern: expose a static, settable seam that defaults to the real implementation and is overridden once at composition\u002Ftest setup.\n\nWhen the user wants to keep the class static, the ambient seam below **is the answer** — present it as *the* solution and implement it directly. Do **not** hedge by offering \"convert it to a non-static class\" or \"pass `TimeProvider` as a method parameter\" as co-equal alternatives; those change the class's design or public API and are not what was asked. Lead with the seam, then note the parallelism trade-off.\n\n```csharp\npublic static class TimestampFormatter\n{\n    \u002F\u002F Ambient seam — defaults to the real clock, swap in tests.\n    public static TimeProvider Clock { get; set; } = TimeProvider.System;\n\n    public static string Now() => Clock.GetUtcNow().ToString(\"O\");\n}\n```\n\n- Production: leave `Clock` at its `TimeProvider.System` default, or assign the DI-resolved `TimeProvider` once at startup (`TimestampFormatter.Clock = app.Services.GetRequiredService\u003CTimeProvider>();`).\n- Tests: override `Clock` with a `FakeTimeProvider` and **always restore it in a `finally`** so a failing assertion can't leak the fake into other tests:\n\n  ```csharp\n  var original = TimestampFormatter.Clock;\n  TimestampFormatter.Clock = new FakeTimeProvider(instant);\n  try\n  {\n      \u002F\u002F exercise code under test\n  }\n  finally\n  {\n      TimestampFormatter.Clock = original;\n  }\n  ```\n\n- **Parallelism caveat**: a mutable static seam is process-global. Tests that mutate it must **not** run in parallel with each other (or with code that reads it) — put them in a non-parallel collection\u002Fclass (e.g. xUnit `[Collection]` with parallelization disabled, or MSTest `[DoNotParallelize]`). Only if the class is *not* required to stay static and its tests must run fully parallel should you consider converting the caller to an instance with constructor injection instead — otherwise keep the ambient seam.\n- The same seam works for other statics (`IFileSystem`, custom wrappers): a `public static \u003CAbstraction> X { get; set; }` defaulting to the real implementation, with the same restore-in-`finally` and non-parallel discipline.\n\n### Step 4: Replace call sites\n\nPerform each replacement mechanically. For each call site:\n\n1. Replace the static call with the wrapper call\n2. Preserve the surrounding code structure (whitespace, comments, chaining)\n3. Add required `using` directives if not already present\n\n#### Adding using directives\n\n| Abstraction | Using directive |\n|------------|-----------------|\n| `TimeProvider` | None (in `System` namespace) |\n| `IFileSystem` | `using System.IO.Abstractions;` |\n| `IHttpClientFactory` | `using System.Net.Http;` (usually already present) |\n| Custom wrappers | `using \u003Cwrapper namespace>;` |\n\n### Step 5: Update affected test files\n\nIf test files exist for the migrated classes:\n\n1. **Update constructor calls** — add the new parameter to test class instantiation\n2. **Use test doubles**:\n   - `TimeProvider` → `new FakeTimeProvider()` from `Microsoft.Extensions.TimeProvider.Testing`\n   - `IFileSystem` → `new MockFileSystem()` from `System.IO.Abstractions.TestingHelpers`\n   - Custom wrappers → `new Mock\u003CIWrapperName>()` or hand-rolled fake\n\n### Step 6: Build verification\n\nAfter all changes in the current scope:\n\n```bash\ndotnet build \u003Cproject.csproj>\n```\n\n**Report the build result you actually observed.** Only write \"build succeeded\" when the command exited 0; if it failed — including restore\u002FNuGet failures such as \"assets file not found\" — say so, quote the error, and either fix it (`dotnet restore`, add the missing package) or hand the user a precise blocker. A false success claim is worse than an unfinished migration.\n\nIf the build fails:\n- **Missing using**: Add the required `using` directive\n- **Missing NuGet package**: Run `dotnet add package \u003Cname>`\n- **Constructor mismatch in tests**: Update test instantiation (Step 5)\n- **Ambiguous call**: Fully qualify the wrapper call\n\n### Step 7: Report changes\n\nSummarize what was done:\n\n```\n## Migration Summary\n\n**Pattern**: DateTime.UtcNow → TimeProvider.GetUtcNow()\n**Scope**: MyProject\u002FServices\u002F\n\n### Files Modified (production)\n| File | Call Sites Replaced | Injection Added |\n|------|--------------------:|:----------------|\n| OrderProcessor.cs | 3 | Yes (constructor) |\n| NotificationService.cs | 1 | Yes (primary ctor) |\n\n### Files Modified (tests)\n| File | Change |\n|------|--------|\n| OrderProcessorTests.cs | Added FakeTimeProvider parameter |\n\n### Remaining (out of scope)\n- MyProject\u002FLegacy\u002F — 8 call sites not migrated (different namespace)\n```\n\n## Validation\n\n- [ ] All call sites in scope were replaced (none missed)\n- [ ] No call site outside the requested member\u002Ffile scope was modified\n- [ ] Call sites documented as intentional (e.g. local time) were left untouched and reported\n- [ ] Constructor injection added to all affected classes\n- [ ] Field naming follows existing class conventions\n- [ ] Required `using` directives added\n- [ ] Required NuGet packages referenced\n- [ ] Build succeeds after migration, and the reported result matches the actual command exit code\n- [ ] Test files updated with appropriate test doubles\n- [ ] No behavioral changes introduced (wrapper delegates directly to the static)\n- [ ] `DateTimeKind` preserved — former `DateTime.UtcNow` stays `Utc` (`.UtcDateTime`), former `DateTime.Now` stays `Local` (`.LocalDateTime`)\n\n## Common Pitfalls\n\n| Pitfall | Solution |\n|---------|----------|\n| Replacing statics in test code | Only replace in production code; tests should use fakes\u002Fmocks |\n| Breaking static classes | Static classes can't have constructors — use the ambient context seam (Step 3) instead of converting them to non-static |\n| Missing `FakeTimeProvider` NuGet | Add `Microsoft.Extensions.TimeProvider.Testing` to test project |\n| Replacing a `DateTime` value with `.DateTime` off a `DateTimeOffset` | `DateTimeOffset.DateTime` returns `Kind == Unspecified` — use `.UtcDateTime` (for former `DateTime.UtcNow`) or `.LocalDateTime` (for former `DateTime.Now`) to preserve the original `DateTimeKind`. Only change the field\u002Freturn type to `DateTimeOffset` if the user asked for it. |\n| Migrating too much at once | Stick to the defined scope — one project or namespace per run |\n| Migrating `DateTime.Now` when only `UtcNow` was requested | Respect the literal request; list the other call sites as out-of-scope suggestions instead of rewriting them |\n| Claiming \"Build succeeded\" after a failed restore | Read the exit code and output; report the real failure and fix it or surface it as a blocker |\n| Forgetting DI registration | Always verify `Program.cs`\u002F`Startup.cs` has the registration before replacing call sites |\n",{"data":35,"body":36},{"name":4,"description":6,"license":25},{"type":37,"children":38},"root",[39,47,53,60,142,148,197,221,227,376,382,389,394,489,495,528,533,579,586,916,921,1149,1155,1160,1211,1217,1249,1282,1359,1581,1587,1592,1618,1624,1730,1736,1741,1825,1831,1836,1881,1899,1904,1960,1966,1971,1981,1987,2147,2153,2393],{"type":40,"tag":41,"props":42,"children":43},"element","h1",{"id":4},[44],{"type":45,"value":46},"text","Migrate Static to Wrapper",{"type":40,"tag":48,"props":49,"children":50},"p",{},[51],{"type":45,"value":52},"Perform mechanical, codemod-style replacement of static dependency call sites with calls to injected wrapper interfaces or built-in abstractions. Operates on a bounded scope (single file, project, or namespace) so migrations can be done incrementally.",{"type":40,"tag":54,"props":55,"children":57},"h2",{"id":56},"when-to-use",[58],{"type":45,"value":59},"When to Use",{"type":40,"tag":61,"props":62,"children":63},"ul",{},[64,79,100,119,124,137],{"type":40,"tag":65,"props":66,"children":67},"li",{},[68,70,77],{"type":45,"value":69},"After wrappers have been generated (via ",{"type":40,"tag":71,"props":72,"children":74},"code",{"className":73},[],[75],{"type":45,"value":76},"generate-testability-wrappers",{"type":45,"value":78},") or built-in abstractions identified",{"type":40,"tag":65,"props":80,"children":81},{},[82,84,90,92,98],{"type":45,"value":83},"Migrating ",{"type":40,"tag":71,"props":85,"children":87},{"className":86},[],[88],{"type":45,"value":89},"DateTime.UtcNow",{"type":45,"value":91}," → ",{"type":40,"tag":71,"props":93,"children":95},{"className":94},[],[96],{"type":45,"value":97},"TimeProvider.GetUtcNow()",{"type":45,"value":99}," across a project",{"type":40,"tag":65,"props":101,"children":102},{},[103,104,110,111,117],{"type":45,"value":83},{"type":40,"tag":71,"props":105,"children":107},{"className":106},[],[108],{"type":45,"value":109},"File.*",{"type":45,"value":91},{"type":40,"tag":71,"props":112,"children":114},{"className":113},[],[115],{"type":45,"value":116},"IFileSystem.File.*",{"type":45,"value":118}," across a namespace",{"type":40,"tag":65,"props":120,"children":121},{},[122],{"type":45,"value":123},"Adding constructor injection for the new abstraction to affected classes",{"type":40,"tag":65,"props":125,"children":126},{},[127,129,135],{"type":45,"value":128},"Making a ",{"type":40,"tag":71,"props":130,"children":132},{"className":131},[],[133],{"type":45,"value":134},"static",{"type":45,"value":136}," utility class testable by adding an ambient seam (Step 3) while its existing call sites keep\ncompiling unchanged",{"type":40,"tag":65,"props":138,"children":139},{},[140],{"type":45,"value":141},"Incremental migration: one project or namespace at a time",{"type":40,"tag":54,"props":143,"children":145},{"id":144},"when-not-to-use",[146],{"type":45,"value":147},"When Not to Use",{"type":40,"tag":61,"props":149,"children":150},{},[151,179,192],{"type":40,"tag":65,"props":152,"children":153},{},[154,156,161,163,169,171,177],{"type":45,"value":155},"No wrapper or abstraction exists yet and one must be designed from scratch (use ",{"type":40,"tag":71,"props":157,"children":159},{"className":158},[],[160],{"type":45,"value":76},{"type":45,"value":162}," first).\nA built-in abstraction such as ",{"type":40,"tag":71,"props":164,"children":166},{"className":165},[],[167],{"type":45,"value":168},"TimeProvider",{"type":45,"value":170}," or ",{"type":40,"tag":71,"props":172,"children":174},{"className":173},[],[175],{"type":45,"value":176},"IFileSystem",{"type":45,"value":178}," always counts as existing.",{"type":40,"tag":65,"props":180,"children":181},{},[182,184,190],{"type":45,"value":183},"The user wants to detect statics, not migrate them (use ",{"type":40,"tag":71,"props":185,"children":187},{"className":186},[],[188],{"type":45,"value":189},"detect-static-dependencies",{"type":45,"value":191},")",{"type":40,"tag":65,"props":193,"children":194},{},[195],{"type":45,"value":196},"Migrating between test frameworks (use the appropriate migration skill)",{"type":40,"tag":198,"props":199,"children":200},"blockquote",{},[201],{"type":40,"tag":48,"props":202,"children":203},{},[204,206,211,213,219],{"type":45,"value":205},"A class that is ",{"type":40,"tag":71,"props":207,"children":209},{"className":208},[],[210],{"type":45,"value":134},{"type":45,"value":212},", or a project with no DI container, is ",{"type":40,"tag":214,"props":215,"children":216},"strong",{},[217],{"type":45,"value":218},"not",{"type":45,"value":220}," a reason to skip this skill — that is exactly\nwhat the ambient seam in Step 3 is for. Use it whenever the call sites must keep compiling unchanged.",{"type":40,"tag":54,"props":222,"children":224},{"id":223},"inputs",[225],{"type":45,"value":226},"Inputs",{"type":40,"tag":228,"props":229,"children":230},"table",{},[231,255],{"type":40,"tag":232,"props":233,"children":234},"thead",{},[235],{"type":40,"tag":236,"props":237,"children":238},"tr",{},[239,245,250],{"type":40,"tag":240,"props":241,"children":242},"th",{},[243],{"type":45,"value":244},"Input",{"type":40,"tag":240,"props":246,"children":247},{},[248],{"type":45,"value":249},"Required",{"type":40,"tag":240,"props":251,"children":252},{},[253],{"type":45,"value":254},"Description",{"type":40,"tag":256,"props":257,"children":258},"tbody",{},[259,292,321,338],{"type":40,"tag":236,"props":260,"children":261},{},[262,268,273],{"type":40,"tag":263,"props":264,"children":265},"td",{},[266],{"type":45,"value":267},"Static pattern",{"type":40,"tag":263,"props":269,"children":270},{},[271],{"type":45,"value":272},"Yes",{"type":40,"tag":263,"props":274,"children":275},{},[276,278,283,285,291],{"type":45,"value":277},"What to replace (e.g., ",{"type":40,"tag":71,"props":279,"children":281},{"className":280},[],[282],{"type":45,"value":89},{"type":45,"value":284},", ",{"type":40,"tag":71,"props":286,"children":288},{"className":287},[],[289],{"type":45,"value":290},"File.ReadAllText",{"type":45,"value":191},{"type":40,"tag":236,"props":293,"children":294},{},[295,300,304],{"type":40,"tag":263,"props":296,"children":297},{},[298],{"type":45,"value":299},"Replacement abstraction",{"type":40,"tag":263,"props":301,"children":302},{},[303],{"type":45,"value":272},{"type":40,"tag":263,"props":305,"children":306},{},[307,309,314,315,320],{"type":45,"value":308},"What to use instead (e.g., ",{"type":40,"tag":71,"props":310,"children":312},{"className":311},[],[313],{"type":45,"value":168},{"type":45,"value":284},{"type":40,"tag":71,"props":316,"children":318},{"className":317},[],[319],{"type":45,"value":176},{"type":45,"value":191},{"type":40,"tag":236,"props":322,"children":323},{},[324,329,333],{"type":40,"tag":263,"props":325,"children":326},{},[327],{"type":45,"value":328},"Scope",{"type":40,"tag":263,"props":330,"children":331},{},[332],{"type":45,"value":272},{"type":40,"tag":263,"props":334,"children":335},{},[336],{"type":45,"value":337},"File path, project (.csproj), namespace, or directory to migrate",{"type":40,"tag":236,"props":339,"children":340},{},[341,346,351],{"type":40,"tag":263,"props":342,"children":343},{},[344],{"type":45,"value":345},"Injection strategy",{"type":40,"tag":263,"props":347,"children":348},{},[349],{"type":45,"value":350},"No",{"type":40,"tag":263,"props":352,"children":353},{},[354,360,362,368,370],{"type":40,"tag":71,"props":355,"children":357},{"className":356},[],[358],{"type":45,"value":359},"constructor",{"type":45,"value":361}," (default), ",{"type":40,"tag":71,"props":363,"children":365},{"className":364},[],[366],{"type":45,"value":367},"primary-constructor",{"type":45,"value":369},", or ",{"type":40,"tag":71,"props":371,"children":373},{"className":372},[],[374],{"type":45,"value":375},"ambient",{"type":40,"tag":54,"props":377,"children":379},{"id":378},"workflow",[380],{"type":45,"value":381},"Workflow",{"type":40,"tag":383,"props":384,"children":386},"h3",{"id":385},"step-1-verify-prerequisites",[387],{"type":45,"value":388},"Step 1: Verify prerequisites",{"type":40,"tag":48,"props":390,"children":391},{},[392],{"type":45,"value":393},"Before modifying any code:",{"type":40,"tag":395,"props":396,"children":397},"ol",{},[398,431,456],{"type":40,"tag":65,"props":399,"children":400},{},[401,406,408,413,415,421,423,429],{"type":40,"tag":214,"props":402,"children":403},{},[404],{"type":45,"value":405},"Confirm the wrapper\u002Fabstraction exists",{"type":45,"value":407},": Check that the interface or built-in abstraction is available in the project. For ",{"type":40,"tag":71,"props":409,"children":411},{"className":410},[],[412],{"type":45,"value":168},{"type":45,"value":414},", verify the target framework is .NET 8+ or ",{"type":40,"tag":71,"props":416,"children":418},{"className":417},[],[419],{"type":45,"value":420},"Microsoft.Bcl.TimeProvider",{"type":45,"value":422}," is referenced. For ",{"type":40,"tag":71,"props":424,"children":426},{"className":425},[],[427],{"type":45,"value":428},"System.IO.Abstractions",{"type":45,"value":430},", verify the NuGet package is referenced.",{"type":40,"tag":65,"props":432,"children":433},{},[434,439,441,447,448,454],{"type":40,"tag":214,"props":435,"children":436},{},[437],{"type":45,"value":438},"Confirm DI registration exists",{"type":45,"value":440},": Check ",{"type":40,"tag":71,"props":442,"children":444},{"className":443},[],[445],{"type":45,"value":446},"Program.cs",{"type":45,"value":170},{"type":40,"tag":71,"props":449,"children":451},{"className":450},[],[452],{"type":45,"value":453},"Startup.cs",{"type":45,"value":455}," for the service registration. If missing, add it before proceeding.",{"type":40,"tag":65,"props":457,"children":458},{},[459,464,466,472,474,480,481,487],{"type":40,"tag":214,"props":460,"children":461},{},[462],{"type":45,"value":463},"Identify all files in scope",{"type":45,"value":465},": List the ",{"type":40,"tag":71,"props":467,"children":469},{"className":468},[],[470],{"type":45,"value":471},".cs",{"type":45,"value":473}," files that will be modified. Exclude test projects, ",{"type":40,"tag":71,"props":475,"children":477},{"className":476},[],[478],{"type":45,"value":479},"obj\u002F",{"type":45,"value":284},{"type":40,"tag":71,"props":482,"children":484},{"className":483},[],[485],{"type":45,"value":486},"bin\u002F",{"type":45,"value":488},", and generated code.",{"type":40,"tag":383,"props":490,"children":492},{"id":491},"step-2-plan-the-migration-for-each-file",[493],{"type":45,"value":494},"Step 2: Plan the migration for each file",{"type":40,"tag":48,"props":496,"children":497},{},[498,503,505,510,512,518,520,526],{"type":40,"tag":214,"props":499,"children":500},{},[501],{"type":45,"value":502},"Migrate exactly what was asked — nothing adjacent.",{"type":45,"value":504}," If the user named a member (",{"type":40,"tag":71,"props":506,"children":508},{"className":507},[],[509],{"type":45,"value":89},{"type":45,"value":511},"), migrate only that member and leave siblings such as ",{"type":40,"tag":71,"props":513,"children":515},{"className":514},[],[516],{"type":45,"value":517},"DateTime.Now",{"type":45,"value":519}," untouched. If the user named files, do not touch other files. Never migrate a call site whose comment or name marks it as deliberate (e.g. ",{"type":40,"tag":71,"props":521,"children":523},{"className":522},[],[524],{"type":45,"value":525},"\u002F\u002F intentional local time",{"type":45,"value":527},"). List everything you deliberately left alone under \"Remaining (out of scope)\" so the user can ask for it in a follow-up; suggesting is fine, silently widening the scope is not.",{"type":40,"tag":48,"props":529,"children":530},{},[531],{"type":45,"value":532},"For each file containing the static pattern, determine:",{"type":40,"tag":395,"props":534,"children":535},{},[536,546,569],{"type":40,"tag":65,"props":537,"children":538},{},[539,544],{"type":40,"tag":214,"props":540,"children":541},{},[542],{"type":45,"value":543},"Which class(es) contain the call sites",{"type":45,"value":545}," — identify the class declarations",{"type":40,"tag":65,"props":547,"children":548},{},[549,554,556,561,562,567],{"type":40,"tag":214,"props":550,"children":551},{},[552],{"type":45,"value":553},"Whether the class already has the dependency injected",{"type":45,"value":555}," — check constructors for existing ",{"type":40,"tag":71,"props":557,"children":559},{"className":558},[],[560],{"type":45,"value":168},{"type":45,"value":284},{"type":40,"tag":71,"props":563,"children":565},{"className":564},[],[566],{"type":45,"value":176},{"type":45,"value":568},", etc. parameters",{"type":40,"tag":65,"props":570,"children":571},{},[572,577],{"type":40,"tag":214,"props":573,"children":574},{},[575],{"type":45,"value":576},"The replacement expression",{"type":45,"value":578}," for each call site",{"type":40,"tag":580,"props":581,"children":583},"h4",{"id":582},"replacement-mapping",[584],{"type":45,"value":585},"Replacement mapping",{"type":40,"tag":228,"props":587,"children":588},{},[589,610],{"type":40,"tag":232,"props":590,"children":591},{},[592],{"type":40,"tag":236,"props":593,"children":594},{},[595,600,605],{"type":40,"tag":240,"props":596,"children":597},{},[598],{"type":45,"value":599},"Category",{"type":40,"tag":240,"props":601,"children":602},{},[603],{"type":45,"value":604},"Original",{"type":40,"tag":240,"props":606,"children":607},{},[608],{"type":45,"value":609},"DI replacement",{"type":40,"tag":256,"props":611,"children":612},{},[613,638,662,687,712,737,763,788,813,838,864,890],{"type":40,"tag":236,"props":614,"children":615},{},[616,621,629],{"type":40,"tag":263,"props":617,"children":618},{},[619],{"type":45,"value":620},"Time",{"type":40,"tag":263,"props":622,"children":623},{},[624],{"type":40,"tag":71,"props":625,"children":627},{"className":626},[],[628],{"type":45,"value":517},{"type":40,"tag":263,"props":630,"children":631},{},[632],{"type":40,"tag":71,"props":633,"children":635},{"className":634},[],[636],{"type":45,"value":637},"_timeProvider.GetLocalNow().LocalDateTime",{"type":40,"tag":236,"props":639,"children":640},{},[641,645,653],{"type":40,"tag":263,"props":642,"children":643},{},[644],{"type":45,"value":620},{"type":40,"tag":263,"props":646,"children":647},{},[648],{"type":40,"tag":71,"props":649,"children":651},{"className":650},[],[652],{"type":45,"value":89},{"type":40,"tag":263,"props":654,"children":655},{},[656],{"type":40,"tag":71,"props":657,"children":659},{"className":658},[],[660],{"type":45,"value":661},"_timeProvider.GetUtcNow().UtcDateTime",{"type":40,"tag":236,"props":663,"children":664},{},[665,669,678],{"type":40,"tag":263,"props":666,"children":667},{},[668],{"type":45,"value":620},{"type":40,"tag":263,"props":670,"children":671},{},[672],{"type":40,"tag":71,"props":673,"children":675},{"className":674},[],[676],{"type":45,"value":677},"DateTime.Today",{"type":40,"tag":263,"props":679,"children":680},{},[681],{"type":40,"tag":71,"props":682,"children":684},{"className":683},[],[685],{"type":45,"value":686},"_timeProvider.GetLocalNow().LocalDateTime.Date",{"type":40,"tag":236,"props":688,"children":689},{},[690,694,703],{"type":40,"tag":263,"props":691,"children":692},{},[693],{"type":45,"value":620},{"type":40,"tag":263,"props":695,"children":696},{},[697],{"type":40,"tag":71,"props":698,"children":700},{"className":699},[],[701],{"type":45,"value":702},"DateTimeOffset.Now",{"type":40,"tag":263,"props":704,"children":705},{},[706],{"type":40,"tag":71,"props":707,"children":709},{"className":708},[],[710],{"type":45,"value":711},"_timeProvider.GetLocalNow()",{"type":40,"tag":236,"props":713,"children":714},{},[715,719,728],{"type":40,"tag":263,"props":716,"children":717},{},[718],{"type":45,"value":620},{"type":40,"tag":263,"props":720,"children":721},{},[722],{"type":40,"tag":71,"props":723,"children":725},{"className":724},[],[726],{"type":45,"value":727},"DateTimeOffset.UtcNow",{"type":40,"tag":263,"props":729,"children":730},{},[731],{"type":40,"tag":71,"props":732,"children":734},{"className":733},[],[735],{"type":45,"value":736},"_timeProvider.GetUtcNow()",{"type":40,"tag":236,"props":738,"children":739},{},[740,745,754],{"type":40,"tag":263,"props":741,"children":742},{},[743],{"type":45,"value":744},"File",{"type":40,"tag":263,"props":746,"children":747},{},[748],{"type":40,"tag":71,"props":749,"children":751},{"className":750},[],[752],{"type":45,"value":753},"File.ReadAllText(path)",{"type":40,"tag":263,"props":755,"children":756},{},[757],{"type":40,"tag":71,"props":758,"children":760},{"className":759},[],[761],{"type":45,"value":762},"_fileSystem.File.ReadAllText(path)",{"type":40,"tag":236,"props":764,"children":765},{},[766,770,779],{"type":40,"tag":263,"props":767,"children":768},{},[769],{"type":45,"value":744},{"type":40,"tag":263,"props":771,"children":772},{},[773],{"type":40,"tag":71,"props":774,"children":776},{"className":775},[],[777],{"type":45,"value":778},"File.WriteAllText(path, text)",{"type":40,"tag":263,"props":780,"children":781},{},[782],{"type":40,"tag":71,"props":783,"children":785},{"className":784},[],[786],{"type":45,"value":787},"_fileSystem.File.WriteAllText(path, text)",{"type":40,"tag":236,"props":789,"children":790},{},[791,795,804],{"type":40,"tag":263,"props":792,"children":793},{},[794],{"type":45,"value":744},{"type":40,"tag":263,"props":796,"children":797},{},[798],{"type":40,"tag":71,"props":799,"children":801},{"className":800},[],[802],{"type":45,"value":803},"File.Exists(path)",{"type":40,"tag":263,"props":805,"children":806},{},[807],{"type":40,"tag":71,"props":808,"children":810},{"className":809},[],[811],{"type":45,"value":812},"_fileSystem.File.Exists(path)",{"type":40,"tag":236,"props":814,"children":815},{},[816,820,829],{"type":40,"tag":263,"props":817,"children":818},{},[819],{"type":45,"value":744},{"type":40,"tag":263,"props":821,"children":822},{},[823],{"type":40,"tag":71,"props":824,"children":826},{"className":825},[],[827],{"type":45,"value":828},"Directory.Exists(path)",{"type":40,"tag":263,"props":830,"children":831},{},[832],{"type":40,"tag":71,"props":833,"children":835},{"className":834},[],[836],{"type":45,"value":837},"_fileSystem.Directory.Exists(path)",{"type":40,"tag":236,"props":839,"children":840},{},[841,846,855],{"type":40,"tag":263,"props":842,"children":843},{},[844],{"type":45,"value":845},"Env",{"type":40,"tag":263,"props":847,"children":848},{},[849],{"type":40,"tag":71,"props":850,"children":852},{"className":851},[],[853],{"type":45,"value":854},"Environment.GetEnvironmentVariable(name)",{"type":40,"tag":263,"props":856,"children":857},{},[858],{"type":40,"tag":71,"props":859,"children":861},{"className":860},[],[862],{"type":45,"value":863},"_env.GetEnvironmentVariable(name)",{"type":40,"tag":236,"props":865,"children":866},{},[867,872,881],{"type":40,"tag":263,"props":868,"children":869},{},[870],{"type":45,"value":871},"Console",{"type":40,"tag":263,"props":873,"children":874},{},[875],{"type":40,"tag":71,"props":876,"children":878},{"className":877},[],[879],{"type":45,"value":880},"Console.WriteLine(msg)",{"type":40,"tag":263,"props":882,"children":883},{},[884],{"type":40,"tag":71,"props":885,"children":887},{"className":886},[],[888],{"type":45,"value":889},"_console.WriteLine(msg)",{"type":40,"tag":236,"props":891,"children":892},{},[893,898,907],{"type":40,"tag":263,"props":894,"children":895},{},[896],{"type":45,"value":897},"Process",{"type":40,"tag":263,"props":899,"children":900},{},[901],{"type":40,"tag":71,"props":902,"children":904},{"className":903},[],[905],{"type":45,"value":906},"Process.Start(info)",{"type":40,"tag":263,"props":908,"children":909},{},[910],{"type":40,"tag":71,"props":911,"children":913},{"className":912},[],[914],{"type":45,"value":915},"_processRunner.Start(info)",{"type":40,"tag":48,"props":917,"children":918},{},[919],{"type":45,"value":920},"Apply the same pattern for other members in each category.",{"type":40,"tag":198,"props":922,"children":923},{},[924,985,1116],{"type":40,"tag":48,"props":925,"children":926},{},[927,940,942,947,949,955,957,963,965,971,972,983],{"type":40,"tag":214,"props":928,"children":929},{},[930,932,938],{"type":45,"value":931},"Preserve ",{"type":40,"tag":71,"props":933,"children":935},{"className":934},[],[936],{"type":45,"value":937},"DateTimeKind",{"type":45,"value":939}," — this is the most common silent regression.",{"type":45,"value":941}," ",{"type":40,"tag":71,"props":943,"children":945},{"className":944},[],[946],{"type":45,"value":97},{"type":45,"value":948}," \u002F ",{"type":40,"tag":71,"props":950,"children":952},{"className":951},[],[953],{"type":45,"value":954},"GetLocalNow()",{"type":45,"value":956}," return a ",{"type":40,"tag":71,"props":958,"children":960},{"className":959},[],[961],{"type":45,"value":962},"DateTimeOffset",{"type":45,"value":964},". Converting back to ",{"type":40,"tag":71,"props":966,"children":968},{"className":967},[],[969],{"type":45,"value":970},"DateTime",{"type":45,"value":941},{"type":40,"tag":214,"props":973,"children":974},{},[975,977],{"type":45,"value":976},"must keep the original ",{"type":40,"tag":71,"props":978,"children":980},{"className":979},[],[981],{"type":45,"value":982},"Kind",{"type":45,"value":984},", otherwise you introduce a behavioral change even though the code still compiles:",{"type":40,"tag":61,"props":986,"children":987},{},[988,1035,1069],{"type":40,"tag":65,"props":989,"children":990},{},[991,996,998,1004,1006,1012,1014,1018,1019,1025,1027,1033],{"type":40,"tag":71,"props":992,"children":994},{"className":993},[],[995],{"type":45,"value":89},{"type":45,"value":997}," has ",{"type":40,"tag":71,"props":999,"children":1001},{"className":1000},[],[1002],{"type":45,"value":1003},"Kind == Utc",{"type":45,"value":1005}," → use ",{"type":40,"tag":71,"props":1007,"children":1009},{"className":1008},[],[1010],{"type":45,"value":1011},".UtcDateTime",{"type":45,"value":1013}," (",{"type":40,"tag":214,"props":1015,"children":1016},{},[1017],{"type":45,"value":218},{"type":45,"value":941},{"type":40,"tag":71,"props":1020,"children":1022},{"className":1021},[],[1023],{"type":45,"value":1024},".DateTime",{"type":45,"value":1026},", which yields ",{"type":40,"tag":71,"props":1028,"children":1030},{"className":1029},[],[1031],{"type":45,"value":1032},"Kind == Unspecified",{"type":45,"value":1034},").",{"type":40,"tag":65,"props":1036,"children":1037},{},[1038,1043,1044,1050,1051,1057,1058,1062,1063,1068],{"type":40,"tag":71,"props":1039,"children":1041},{"className":1040},[],[1042],{"type":45,"value":517},{"type":45,"value":997},{"type":40,"tag":71,"props":1045,"children":1047},{"className":1046},[],[1048],{"type":45,"value":1049},"Kind == Local",{"type":45,"value":1005},{"type":40,"tag":71,"props":1052,"children":1054},{"className":1053},[],[1055],{"type":45,"value":1056},".LocalDateTime",{"type":45,"value":1013},{"type":40,"tag":214,"props":1059,"children":1060},{},[1061],{"type":45,"value":218},{"type":45,"value":941},{"type":40,"tag":71,"props":1064,"children":1066},{"className":1065},[],[1067],{"type":45,"value":1024},{"type":45,"value":1034},{"type":40,"tag":65,"props":1070,"children":1071},{},[1072,1074,1079,1081,1086,1088,1093,1095,1100,1102,1107,1109,1114],{"type":45,"value":1073},"When a call site consumes a ",{"type":40,"tag":71,"props":1075,"children":1077},{"className":1076},[],[1078],{"type":45,"value":962},{"type":45,"value":1080}," directly (a field\u002Fparameter\u002Freturn already typed ",{"type":40,"tag":71,"props":1082,"children":1084},{"className":1083},[],[1085],{"type":45,"value":962},{"type":45,"value":1087},"), drop the ",{"type":40,"tag":71,"props":1089,"children":1091},{"className":1090},[],[1092],{"type":45,"value":1011},{"type":45,"value":1094},"\u002F",{"type":40,"tag":71,"props":1096,"children":1098},{"className":1097},[],[1099],{"type":45,"value":1056},{"type":45,"value":1101}," suffix and assign the ",{"type":40,"tag":71,"props":1103,"children":1105},{"className":1104},[],[1106],{"type":45,"value":962},{"type":45,"value":1108}," as-is — don't force it back through ",{"type":40,"tag":71,"props":1110,"children":1112},{"className":1111},[],[1113],{"type":45,"value":970},{"type":45,"value":1115},".",{"type":40,"tag":48,"props":1117,"children":1118},{},[1119,1121,1126,1128,1133,1135,1140,1142,1147],{"type":45,"value":1120},"Match the ",{"type":40,"tag":214,"props":1122,"children":1123},{},[1124],{"type":45,"value":1125},"target member's type",{"type":45,"value":1127},": if the surrounding field\u002Fproperty is ",{"type":40,"tag":71,"props":1129,"children":1131},{"className":1130},[],[1132],{"type":45,"value":970},{"type":45,"value":1134},", keep it ",{"type":40,"tag":71,"props":1136,"children":1138},{"className":1137},[],[1139],{"type":45,"value":970},{"type":45,"value":1141}," (via the Kind-correct property above); do not change it to ",{"type":40,"tag":71,"props":1143,"children":1145},{"className":1144},[],[1146],{"type":45,"value":962},{"type":45,"value":1148}," as part of a \"mechanical\" migration — that is a design change, not a delegation.",{"type":40,"tag":383,"props":1150,"children":1152},{"id":1151},"step-3-add-constructor-injection",[1153],{"type":45,"value":1154},"Step 3: Add constructor injection",{"type":40,"tag":48,"props":1156,"children":1157},{},[1158],{"type":45,"value":1159},"Add the new dependency following the class's existing pattern:",{"type":40,"tag":61,"props":1161,"children":1162},{},[1163,1179],{"type":40,"tag":65,"props":1164,"children":1165},{},[1166,1171,1173],{"type":40,"tag":214,"props":1167,"children":1168},{},[1169],{"type":45,"value":1170},"Primary constructor",{"type":45,"value":1172}," (C# 12+): Add parameter to primary constructor: ",{"type":40,"tag":71,"props":1174,"children":1176},{"className":1175},[],[1177],{"type":45,"value":1178},"public class OrderProcessor(ILogger\u003COrderProcessor> logger, TimeProvider timeProvider)",{"type":40,"tag":65,"props":1180,"children":1181},{},[1182,1187,1189,1195,1197,1203,1204,1210],{"type":40,"tag":214,"props":1183,"children":1184},{},[1185],{"type":45,"value":1186},"Traditional constructor",{"type":45,"value":1188},": Add ",{"type":40,"tag":71,"props":1190,"children":1192},{"className":1191},[],[1193],{"type":45,"value":1194},"private readonly",{"type":45,"value":1196}," field + constructor parameter, matching the existing field naming convention (",{"type":40,"tag":71,"props":1198,"children":1200},{"className":1199},[],[1201],{"type":45,"value":1202},"_camelCase",{"type":45,"value":170},{"type":40,"tag":71,"props":1205,"children":1207},{"className":1206},[],[1208],{"type":45,"value":1209},"m_camelCase",{"type":45,"value":191},{"type":40,"tag":580,"props":1212,"children":1214},{"id":1213},"static-classes-use-ambient-context-no-constructor-injection",[1215],{"type":45,"value":1216},"Static classes: use ambient context (no constructor injection)",{"type":40,"tag":48,"props":1218,"children":1219},{},[1220,1222,1227,1229,1234,1236,1240,1242,1247],{"type":45,"value":1221},"A ",{"type":40,"tag":71,"props":1223,"children":1225},{"className":1224},[],[1226],{"type":45,"value":134},{"type":45,"value":1228}," class with only static members ",{"type":40,"tag":214,"props":1230,"children":1231},{},[1232],{"type":45,"value":1233},"cannot",{"type":45,"value":1235}," receive constructor injection — adding an instance constructor or instance field would break it. Do ",{"type":40,"tag":214,"props":1237,"children":1238},{},[1239],{"type":45,"value":218},{"type":45,"value":1241}," convert it to a non-static class just to inject the dependency; that changes its design and every call site. Instead, apply the ",{"type":40,"tag":214,"props":1243,"children":1244},{},[1245],{"type":45,"value":1246},"ambient context",{"type":45,"value":1248}," pattern: expose a static, settable seam that defaults to the real implementation and is overridden once at composition\u002Ftest setup.",{"type":40,"tag":48,"props":1250,"children":1251},{},[1252,1254,1259,1261,1267,1269,1273,1275,1280],{"type":45,"value":1253},"When the user wants to keep the class static, the ambient seam below ",{"type":40,"tag":214,"props":1255,"children":1256},{},[1257],{"type":45,"value":1258},"is the answer",{"type":45,"value":1260}," — present it as ",{"type":40,"tag":1262,"props":1263,"children":1264},"em",{},[1265],{"type":45,"value":1266},"the",{"type":45,"value":1268}," solution and implement it directly. Do ",{"type":40,"tag":214,"props":1270,"children":1271},{},[1272],{"type":45,"value":218},{"type":45,"value":1274}," hedge by offering \"convert it to a non-static class\" or \"pass ",{"type":40,"tag":71,"props":1276,"children":1278},{"className":1277},[],[1279],{"type":45,"value":168},{"type":45,"value":1281}," as a method parameter\" as co-equal alternatives; those change the class's design or public API and are not what was asked. Lead with the seam, then note the parallelism trade-off.",{"type":40,"tag":1283,"props":1284,"children":1289},"pre",{"className":1285,"code":1286,"language":1287,"meta":1288,"style":1288},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","public static class TimestampFormatter\n{\n    \u002F\u002F Ambient seam — defaults to the real clock, swap in tests.\n    public static TimeProvider Clock { get; set; } = TimeProvider.System;\n\n    public static string Now() => Clock.GetUtcNow().ToString(\"O\");\n}\n","csharp","",[1290],{"type":40,"tag":71,"props":1291,"children":1292},{"__ignoreMap":1288},[1293,1304,1313,1322,1331,1341,1350],{"type":40,"tag":1294,"props":1295,"children":1298},"span",{"class":1296,"line":1297},"line",1,[1299],{"type":40,"tag":1294,"props":1300,"children":1301},{},[1302],{"type":45,"value":1303},"public static class TimestampFormatter\n",{"type":40,"tag":1294,"props":1305,"children":1307},{"class":1296,"line":1306},2,[1308],{"type":40,"tag":1294,"props":1309,"children":1310},{},[1311],{"type":45,"value":1312},"{\n",{"type":40,"tag":1294,"props":1314,"children":1316},{"class":1296,"line":1315},3,[1317],{"type":40,"tag":1294,"props":1318,"children":1319},{},[1320],{"type":45,"value":1321},"    \u002F\u002F Ambient seam — defaults to the real clock, swap in tests.\n",{"type":40,"tag":1294,"props":1323,"children":1325},{"class":1296,"line":1324},4,[1326],{"type":40,"tag":1294,"props":1327,"children":1328},{},[1329],{"type":45,"value":1330},"    public static TimeProvider Clock { get; set; } = TimeProvider.System;\n",{"type":40,"tag":1294,"props":1332,"children":1334},{"class":1296,"line":1333},5,[1335],{"type":40,"tag":1294,"props":1336,"children":1338},{"emptyLinePlaceholder":1337},true,[1339],{"type":45,"value":1340},"\n",{"type":40,"tag":1294,"props":1342,"children":1344},{"class":1296,"line":1343},6,[1345],{"type":40,"tag":1294,"props":1346,"children":1347},{},[1348],{"type":45,"value":1349},"    public static string Now() => Clock.GetUtcNow().ToString(\"O\");\n",{"type":40,"tag":1294,"props":1351,"children":1353},{"class":1296,"line":1352},7,[1354],{"type":40,"tag":1294,"props":1355,"children":1356},{},[1357],{"type":45,"value":1358},"}\n",{"type":40,"tag":61,"props":1360,"children":1361},{},[1362,1397,1516,1554],{"type":40,"tag":65,"props":1363,"children":1364},{},[1365,1367,1373,1375,1381,1383,1388,1390,1396],{"type":45,"value":1366},"Production: leave ",{"type":40,"tag":71,"props":1368,"children":1370},{"className":1369},[],[1371],{"type":45,"value":1372},"Clock",{"type":45,"value":1374}," at its ",{"type":40,"tag":71,"props":1376,"children":1378},{"className":1377},[],[1379],{"type":45,"value":1380},"TimeProvider.System",{"type":45,"value":1382}," default, or assign the DI-resolved ",{"type":40,"tag":71,"props":1384,"children":1386},{"className":1385},[],[1387],{"type":45,"value":168},{"type":45,"value":1389}," once at startup (",{"type":40,"tag":71,"props":1391,"children":1393},{"className":1392},[],[1394],{"type":45,"value":1395},"TimestampFormatter.Clock = app.Services.GetRequiredService\u003CTimeProvider>();",{"type":45,"value":1034},{"type":40,"tag":65,"props":1398,"children":1399},{},[1400,1402,1407,1409,1415,1417,1428,1430],{"type":45,"value":1401},"Tests: override ",{"type":40,"tag":71,"props":1403,"children":1405},{"className":1404},[],[1406],{"type":45,"value":1372},{"type":45,"value":1408}," with a ",{"type":40,"tag":71,"props":1410,"children":1412},{"className":1411},[],[1413],{"type":45,"value":1414},"FakeTimeProvider",{"type":45,"value":1416}," and ",{"type":40,"tag":214,"props":1418,"children":1419},{},[1420,1422],{"type":45,"value":1421},"always restore it in a ",{"type":40,"tag":71,"props":1423,"children":1425},{"className":1424},[],[1426],{"type":45,"value":1427},"finally",{"type":45,"value":1429}," so a failing assertion can't leak the fake into other tests:",{"type":40,"tag":1283,"props":1431,"children":1433},{"className":1285,"code":1432,"language":1287,"meta":1288,"style":1288},"var original = TimestampFormatter.Clock;\nTimestampFormatter.Clock = new FakeTimeProvider(instant);\ntry\n{\n    \u002F\u002F exercise code under test\n}\nfinally\n{\n    TimestampFormatter.Clock = original;\n}\n",[1434],{"type":40,"tag":71,"props":1435,"children":1436},{"__ignoreMap":1288},[1437,1445,1453,1461,1468,1476,1483,1491,1499,1508],{"type":40,"tag":1294,"props":1438,"children":1439},{"class":1296,"line":1297},[1440],{"type":40,"tag":1294,"props":1441,"children":1442},{},[1443],{"type":45,"value":1444},"var original = TimestampFormatter.Clock;\n",{"type":40,"tag":1294,"props":1446,"children":1447},{"class":1296,"line":1306},[1448],{"type":40,"tag":1294,"props":1449,"children":1450},{},[1451],{"type":45,"value":1452},"TimestampFormatter.Clock = new FakeTimeProvider(instant);\n",{"type":40,"tag":1294,"props":1454,"children":1455},{"class":1296,"line":1315},[1456],{"type":40,"tag":1294,"props":1457,"children":1458},{},[1459],{"type":45,"value":1460},"try\n",{"type":40,"tag":1294,"props":1462,"children":1463},{"class":1296,"line":1324},[1464],{"type":40,"tag":1294,"props":1465,"children":1466},{},[1467],{"type":45,"value":1312},{"type":40,"tag":1294,"props":1469,"children":1470},{"class":1296,"line":1333},[1471],{"type":40,"tag":1294,"props":1472,"children":1473},{},[1474],{"type":45,"value":1475},"    \u002F\u002F exercise code under test\n",{"type":40,"tag":1294,"props":1477,"children":1478},{"class":1296,"line":1343},[1479],{"type":40,"tag":1294,"props":1480,"children":1481},{},[1482],{"type":45,"value":1358},{"type":40,"tag":1294,"props":1484,"children":1485},{"class":1296,"line":1352},[1486],{"type":40,"tag":1294,"props":1487,"children":1488},{},[1489],{"type":45,"value":1490},"finally\n",{"type":40,"tag":1294,"props":1492,"children":1494},{"class":1296,"line":1493},8,[1495],{"type":40,"tag":1294,"props":1496,"children":1497},{},[1498],{"type":45,"value":1312},{"type":40,"tag":1294,"props":1500,"children":1502},{"class":1296,"line":1501},9,[1503],{"type":40,"tag":1294,"props":1504,"children":1505},{},[1506],{"type":45,"value":1507},"    TimestampFormatter.Clock = original;\n",{"type":40,"tag":1294,"props":1509,"children":1511},{"class":1296,"line":1510},10,[1512],{"type":40,"tag":1294,"props":1513,"children":1514},{},[1515],{"type":45,"value":1358},{"type":40,"tag":65,"props":1517,"children":1518},{},[1519,1524,1526,1530,1532,1538,1540,1546,1548,1552],{"type":40,"tag":214,"props":1520,"children":1521},{},[1522],{"type":45,"value":1523},"Parallelism caveat",{"type":45,"value":1525},": a mutable static seam is process-global. Tests that mutate it must ",{"type":40,"tag":214,"props":1527,"children":1528},{},[1529],{"type":45,"value":218},{"type":45,"value":1531}," run in parallel with each other (or with code that reads it) — put them in a non-parallel collection\u002Fclass (e.g. xUnit ",{"type":40,"tag":71,"props":1533,"children":1535},{"className":1534},[],[1536],{"type":45,"value":1537},"[Collection]",{"type":45,"value":1539}," with parallelization disabled, or MSTest ",{"type":40,"tag":71,"props":1541,"children":1543},{"className":1542},[],[1544],{"type":45,"value":1545},"[DoNotParallelize]",{"type":45,"value":1547},"). Only if the class is ",{"type":40,"tag":1262,"props":1549,"children":1550},{},[1551],{"type":45,"value":218},{"type":45,"value":1553}," required to stay static and its tests must run fully parallel should you consider converting the caller to an instance with constructor injection instead — otherwise keep the ambient seam.",{"type":40,"tag":65,"props":1555,"children":1556},{},[1557,1559,1564,1566,1572,1574,1579],{"type":45,"value":1558},"The same seam works for other statics (",{"type":40,"tag":71,"props":1560,"children":1562},{"className":1561},[],[1563],{"type":45,"value":176},{"type":45,"value":1565},", custom wrappers): a ",{"type":40,"tag":71,"props":1567,"children":1569},{"className":1568},[],[1570],{"type":45,"value":1571},"public static \u003CAbstraction> X { get; set; }",{"type":45,"value":1573}," defaulting to the real implementation, with the same restore-in-",{"type":40,"tag":71,"props":1575,"children":1577},{"className":1576},[],[1578],{"type":45,"value":1427},{"type":45,"value":1580}," and non-parallel discipline.",{"type":40,"tag":383,"props":1582,"children":1584},{"id":1583},"step-4-replace-call-sites",[1585],{"type":45,"value":1586},"Step 4: Replace call sites",{"type":40,"tag":48,"props":1588,"children":1589},{},[1590],{"type":45,"value":1591},"Perform each replacement mechanically. For each call site:",{"type":40,"tag":395,"props":1593,"children":1594},{},[1595,1600,1605],{"type":40,"tag":65,"props":1596,"children":1597},{},[1598],{"type":45,"value":1599},"Replace the static call with the wrapper call",{"type":40,"tag":65,"props":1601,"children":1602},{},[1603],{"type":45,"value":1604},"Preserve the surrounding code structure (whitespace, comments, chaining)",{"type":40,"tag":65,"props":1606,"children":1607},{},[1608,1610,1616],{"type":45,"value":1609},"Add required ",{"type":40,"tag":71,"props":1611,"children":1613},{"className":1612},[],[1614],{"type":45,"value":1615},"using",{"type":45,"value":1617}," directives if not already present",{"type":40,"tag":580,"props":1619,"children":1621},{"id":1620},"adding-using-directives",[1622],{"type":45,"value":1623},"Adding using directives",{"type":40,"tag":228,"props":1625,"children":1626},{},[1627,1643],{"type":40,"tag":232,"props":1628,"children":1629},{},[1630],{"type":40,"tag":236,"props":1631,"children":1632},{},[1633,1638],{"type":40,"tag":240,"props":1634,"children":1635},{},[1636],{"type":45,"value":1637},"Abstraction",{"type":40,"tag":240,"props":1639,"children":1640},{},[1641],{"type":45,"value":1642},"Using directive",{"type":40,"tag":256,"props":1644,"children":1645},{},[1646,1670,1690,1713],{"type":40,"tag":236,"props":1647,"children":1648},{},[1649,1657],{"type":40,"tag":263,"props":1650,"children":1651},{},[1652],{"type":40,"tag":71,"props":1653,"children":1655},{"className":1654},[],[1656],{"type":45,"value":168},{"type":40,"tag":263,"props":1658,"children":1659},{},[1660,1662,1668],{"type":45,"value":1661},"None (in ",{"type":40,"tag":71,"props":1663,"children":1665},{"className":1664},[],[1666],{"type":45,"value":1667},"System",{"type":45,"value":1669}," namespace)",{"type":40,"tag":236,"props":1671,"children":1672},{},[1673,1681],{"type":40,"tag":263,"props":1674,"children":1675},{},[1676],{"type":40,"tag":71,"props":1677,"children":1679},{"className":1678},[],[1680],{"type":45,"value":176},{"type":40,"tag":263,"props":1682,"children":1683},{},[1684],{"type":40,"tag":71,"props":1685,"children":1687},{"className":1686},[],[1688],{"type":45,"value":1689},"using System.IO.Abstractions;",{"type":40,"tag":236,"props":1691,"children":1692},{},[1693,1702],{"type":40,"tag":263,"props":1694,"children":1695},{},[1696],{"type":40,"tag":71,"props":1697,"children":1699},{"className":1698},[],[1700],{"type":45,"value":1701},"IHttpClientFactory",{"type":40,"tag":263,"props":1703,"children":1704},{},[1705,1711],{"type":40,"tag":71,"props":1706,"children":1708},{"className":1707},[],[1709],{"type":45,"value":1710},"using System.Net.Http;",{"type":45,"value":1712}," (usually already present)",{"type":40,"tag":236,"props":1714,"children":1715},{},[1716,1721],{"type":40,"tag":263,"props":1717,"children":1718},{},[1719],{"type":45,"value":1720},"Custom wrappers",{"type":40,"tag":263,"props":1722,"children":1723},{},[1724],{"type":40,"tag":71,"props":1725,"children":1727},{"className":1726},[],[1728],{"type":45,"value":1729},"using \u003Cwrapper namespace>;",{"type":40,"tag":383,"props":1731,"children":1733},{"id":1732},"step-5-update-affected-test-files",[1734],{"type":45,"value":1735},"Step 5: Update affected test files",{"type":40,"tag":48,"props":1737,"children":1738},{},[1739],{"type":45,"value":1740},"If test files exist for the migrated classes:",{"type":40,"tag":395,"props":1742,"children":1743},{},[1744,1754],{"type":40,"tag":65,"props":1745,"children":1746},{},[1747,1752],{"type":40,"tag":214,"props":1748,"children":1749},{},[1750],{"type":45,"value":1751},"Update constructor calls",{"type":45,"value":1753}," — add the new parameter to test class instantiation",{"type":40,"tag":65,"props":1755,"children":1756},{},[1757,1762,1764],{"type":40,"tag":214,"props":1758,"children":1759},{},[1760],{"type":45,"value":1761},"Use test doubles",{"type":45,"value":1763},":\n",{"type":40,"tag":61,"props":1765,"children":1766},{},[1767,1790,1812],{"type":40,"tag":65,"props":1768,"children":1769},{},[1770,1775,1776,1782,1784],{"type":40,"tag":71,"props":1771,"children":1773},{"className":1772},[],[1774],{"type":45,"value":168},{"type":45,"value":91},{"type":40,"tag":71,"props":1777,"children":1779},{"className":1778},[],[1780],{"type":45,"value":1781},"new FakeTimeProvider()",{"type":45,"value":1783}," from ",{"type":40,"tag":71,"props":1785,"children":1787},{"className":1786},[],[1788],{"type":45,"value":1789},"Microsoft.Extensions.TimeProvider.Testing",{"type":40,"tag":65,"props":1791,"children":1792},{},[1793,1798,1799,1805,1806],{"type":40,"tag":71,"props":1794,"children":1796},{"className":1795},[],[1797],{"type":45,"value":176},{"type":45,"value":91},{"type":40,"tag":71,"props":1800,"children":1802},{"className":1801},[],[1803],{"type":45,"value":1804},"new MockFileSystem()",{"type":45,"value":1783},{"type":40,"tag":71,"props":1807,"children":1809},{"className":1808},[],[1810],{"type":45,"value":1811},"System.IO.Abstractions.TestingHelpers",{"type":40,"tag":65,"props":1813,"children":1814},{},[1815,1817,1823],{"type":45,"value":1816},"Custom wrappers → ",{"type":40,"tag":71,"props":1818,"children":1820},{"className":1819},[],[1821],{"type":45,"value":1822},"new Mock\u003CIWrapperName>()",{"type":45,"value":1824}," or hand-rolled fake",{"type":40,"tag":383,"props":1826,"children":1828},{"id":1827},"step-6-build-verification",[1829],{"type":45,"value":1830},"Step 6: Build verification",{"type":40,"tag":48,"props":1832,"children":1833},{},[1834],{"type":45,"value":1835},"After all changes in the current scope:",{"type":40,"tag":1283,"props":1837,"children":1841},{"className":1838,"code":1839,"language":1840,"meta":1288,"style":1288},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","dotnet build \u003Cproject.csproj>\n","bash",[1842],{"type":40,"tag":71,"props":1843,"children":1844},{"__ignoreMap":1288},[1845],{"type":40,"tag":1294,"props":1846,"children":1847},{"class":1296,"line":1297},[1848,1853,1859,1865,1870,1876],{"type":40,"tag":1294,"props":1849,"children":1851},{"style":1850},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[1852],{"type":45,"value":8},{"type":40,"tag":1294,"props":1854,"children":1856},{"style":1855},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1857],{"type":45,"value":1858}," build",{"type":40,"tag":1294,"props":1860,"children":1862},{"style":1861},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[1863],{"type":45,"value":1864}," \u003C",{"type":40,"tag":1294,"props":1866,"children":1867},{"style":1855},[1868],{"type":45,"value":1869},"project.cspro",{"type":40,"tag":1294,"props":1871,"children":1873},{"style":1872},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[1874],{"type":45,"value":1875},"j",{"type":40,"tag":1294,"props":1877,"children":1878},{"style":1861},[1879],{"type":45,"value":1880},">\n",{"type":40,"tag":48,"props":1882,"children":1883},{},[1884,1889,1891,1897],{"type":40,"tag":214,"props":1885,"children":1886},{},[1887],{"type":45,"value":1888},"Report the build result you actually observed.",{"type":45,"value":1890}," Only write \"build succeeded\" when the command exited 0; if it failed — including restore\u002FNuGet failures such as \"assets file not found\" — say so, quote the error, and either fix it (",{"type":40,"tag":71,"props":1892,"children":1894},{"className":1893},[],[1895],{"type":45,"value":1896},"dotnet restore",{"type":45,"value":1898},", add the missing package) or hand the user a precise blocker. A false success claim is worse than an unfinished migration.",{"type":40,"tag":48,"props":1900,"children":1901},{},[1902],{"type":45,"value":1903},"If the build fails:",{"type":40,"tag":61,"props":1905,"children":1906},{},[1907,1924,1940,1950],{"type":40,"tag":65,"props":1908,"children":1909},{},[1910,1915,1917,1922],{"type":40,"tag":214,"props":1911,"children":1912},{},[1913],{"type":45,"value":1914},"Missing using",{"type":45,"value":1916},": Add the required ",{"type":40,"tag":71,"props":1918,"children":1920},{"className":1919},[],[1921],{"type":45,"value":1615},{"type":45,"value":1923}," directive",{"type":40,"tag":65,"props":1925,"children":1926},{},[1927,1932,1934],{"type":40,"tag":214,"props":1928,"children":1929},{},[1930],{"type":45,"value":1931},"Missing NuGet package",{"type":45,"value":1933},": Run ",{"type":40,"tag":71,"props":1935,"children":1937},{"className":1936},[],[1938],{"type":45,"value":1939},"dotnet add package \u003Cname>",{"type":40,"tag":65,"props":1941,"children":1942},{},[1943,1948],{"type":40,"tag":214,"props":1944,"children":1945},{},[1946],{"type":45,"value":1947},"Constructor mismatch in tests",{"type":45,"value":1949},": Update test instantiation (Step 5)",{"type":40,"tag":65,"props":1951,"children":1952},{},[1953,1958],{"type":40,"tag":214,"props":1954,"children":1955},{},[1956],{"type":45,"value":1957},"Ambiguous call",{"type":45,"value":1959},": Fully qualify the wrapper call",{"type":40,"tag":383,"props":1961,"children":1963},{"id":1962},"step-7-report-changes",[1964],{"type":45,"value":1965},"Step 7: Report changes",{"type":40,"tag":48,"props":1967,"children":1968},{},[1969],{"type":45,"value":1970},"Summarize what was done:",{"type":40,"tag":1283,"props":1972,"children":1976},{"className":1973,"code":1975,"language":45},[1974],"language-text","## Migration Summary\n\n**Pattern**: DateTime.UtcNow → TimeProvider.GetUtcNow()\n**Scope**: MyProject\u002FServices\u002F\n\n### Files Modified (production)\n| File | Call Sites Replaced | Injection Added |\n|------|--------------------:|:----------------|\n| OrderProcessor.cs | 3 | Yes (constructor) |\n| NotificationService.cs | 1 | Yes (primary ctor) |\n\n### Files Modified (tests)\n| File | Change |\n|------|--------|\n| OrderProcessorTests.cs | Added FakeTimeProvider parameter |\n\n### Remaining (out of scope)\n- MyProject\u002FLegacy\u002F — 8 call sites not migrated (different namespace)\n",[1977],{"type":40,"tag":71,"props":1978,"children":1979},{"__ignoreMap":1288},[1980],{"type":45,"value":1975},{"type":40,"tag":54,"props":1982,"children":1984},{"id":1983},"validation",[1985],{"type":45,"value":1986},"Validation",{"type":40,"tag":61,"props":1988,"children":1991},{"className":1989},[1990],"contains-task-list",[1992,2004,2013,2022,2031,2040,2056,2065,2074,2083,2092],{"type":40,"tag":65,"props":1993,"children":1996},{"className":1994},[1995],"task-list-item",[1997,2002],{"type":40,"tag":1998,"props":1999,"children":2001},"input",{"disabled":1337,"type":2000},"checkbox",[],{"type":45,"value":2003}," All call sites in scope were replaced (none missed)",{"type":40,"tag":65,"props":2005,"children":2007},{"className":2006},[1995],[2008,2011],{"type":40,"tag":1998,"props":2009,"children":2010},{"disabled":1337,"type":2000},[],{"type":45,"value":2012}," No call site outside the requested member\u002Ffile scope was modified",{"type":40,"tag":65,"props":2014,"children":2016},{"className":2015},[1995],[2017,2020],{"type":40,"tag":1998,"props":2018,"children":2019},{"disabled":1337,"type":2000},[],{"type":45,"value":2021}," Call sites documented as intentional (e.g. local time) were left untouched and reported",{"type":40,"tag":65,"props":2023,"children":2025},{"className":2024},[1995],[2026,2029],{"type":40,"tag":1998,"props":2027,"children":2028},{"disabled":1337,"type":2000},[],{"type":45,"value":2030}," Constructor injection added to all affected classes",{"type":40,"tag":65,"props":2032,"children":2034},{"className":2033},[1995],[2035,2038],{"type":40,"tag":1998,"props":2036,"children":2037},{"disabled":1337,"type":2000},[],{"type":45,"value":2039}," Field naming follows existing class conventions",{"type":40,"tag":65,"props":2041,"children":2043},{"className":2042},[1995],[2044,2047,2049,2054],{"type":40,"tag":1998,"props":2045,"children":2046},{"disabled":1337,"type":2000},[],{"type":45,"value":2048}," Required ",{"type":40,"tag":71,"props":2050,"children":2052},{"className":2051},[],[2053],{"type":45,"value":1615},{"type":45,"value":2055}," directives added",{"type":40,"tag":65,"props":2057,"children":2059},{"className":2058},[1995],[2060,2063],{"type":40,"tag":1998,"props":2061,"children":2062},{"disabled":1337,"type":2000},[],{"type":45,"value":2064}," Required NuGet packages referenced",{"type":40,"tag":65,"props":2066,"children":2068},{"className":2067},[1995],[2069,2072],{"type":40,"tag":1998,"props":2070,"children":2071},{"disabled":1337,"type":2000},[],{"type":45,"value":2073}," Build succeeds after migration, and the reported result matches the actual command exit code",{"type":40,"tag":65,"props":2075,"children":2077},{"className":2076},[1995],[2078,2081],{"type":40,"tag":1998,"props":2079,"children":2080},{"disabled":1337,"type":2000},[],{"type":45,"value":2082}," Test files updated with appropriate test doubles",{"type":40,"tag":65,"props":2084,"children":2086},{"className":2085},[1995],[2087,2090],{"type":40,"tag":1998,"props":2088,"children":2089},{"disabled":1337,"type":2000},[],{"type":45,"value":2091}," No behavioral changes introduced (wrapper delegates directly to the static)",{"type":40,"tag":65,"props":2093,"children":2095},{"className":2094},[1995],[2096,2099,2100,2105,2107,2112,2114,2120,2121,2126,2128,2133,2134,2140,2141,2146],{"type":40,"tag":1998,"props":2097,"children":2098},{"disabled":1337,"type":2000},[],{"type":45,"value":941},{"type":40,"tag":71,"props":2101,"children":2103},{"className":2102},[],[2104],{"type":45,"value":937},{"type":45,"value":2106}," preserved — former ",{"type":40,"tag":71,"props":2108,"children":2110},{"className":2109},[],[2111],{"type":45,"value":89},{"type":45,"value":2113}," stays ",{"type":40,"tag":71,"props":2115,"children":2117},{"className":2116},[],[2118],{"type":45,"value":2119},"Utc",{"type":45,"value":1013},{"type":40,"tag":71,"props":2122,"children":2124},{"className":2123},[],[2125],{"type":45,"value":1011},{"type":45,"value":2127},"), former ",{"type":40,"tag":71,"props":2129,"children":2131},{"className":2130},[],[2132],{"type":45,"value":517},{"type":45,"value":2113},{"type":40,"tag":71,"props":2135,"children":2137},{"className":2136},[],[2138],{"type":45,"value":2139},"Local",{"type":45,"value":1013},{"type":40,"tag":71,"props":2142,"children":2144},{"className":2143},[],[2145],{"type":45,"value":1056},{"type":45,"value":191},{"type":40,"tag":54,"props":2148,"children":2150},{"id":2149},"common-pitfalls",[2151],{"type":45,"value":2152},"Common Pitfalls",{"type":40,"tag":228,"props":2154,"children":2155},{},[2156,2172],{"type":40,"tag":232,"props":2157,"children":2158},{},[2159],{"type":40,"tag":236,"props":2160,"children":2161},{},[2162,2167],{"type":40,"tag":240,"props":2163,"children":2164},{},[2165],{"type":45,"value":2166},"Pitfall",{"type":40,"tag":240,"props":2168,"children":2169},{},[2170],{"type":45,"value":2171},"Solution",{"type":40,"tag":256,"props":2173,"children":2174},{},[2175,2188,2201,2228,2314,2327,2354,2367],{"type":40,"tag":236,"props":2176,"children":2177},{},[2178,2183],{"type":40,"tag":263,"props":2179,"children":2180},{},[2181],{"type":45,"value":2182},"Replacing statics in test code",{"type":40,"tag":263,"props":2184,"children":2185},{},[2186],{"type":45,"value":2187},"Only replace in production code; tests should use fakes\u002Fmocks",{"type":40,"tag":236,"props":2189,"children":2190},{},[2191,2196],{"type":40,"tag":263,"props":2192,"children":2193},{},[2194],{"type":45,"value":2195},"Breaking static classes",{"type":40,"tag":263,"props":2197,"children":2198},{},[2199],{"type":45,"value":2200},"Static classes can't have constructors — use the ambient context seam (Step 3) instead of converting them to non-static",{"type":40,"tag":236,"props":2202,"children":2203},{},[2204,2216],{"type":40,"tag":263,"props":2205,"children":2206},{},[2207,2209,2214],{"type":45,"value":2208},"Missing ",{"type":40,"tag":71,"props":2210,"children":2212},{"className":2211},[],[2213],{"type":45,"value":1414},{"type":45,"value":2215}," NuGet",{"type":40,"tag":263,"props":2217,"children":2218},{},[2219,2221,2226],{"type":45,"value":2220},"Add ",{"type":40,"tag":71,"props":2222,"children":2224},{"className":2223},[],[2225],{"type":45,"value":1789},{"type":45,"value":2227}," to test project",{"type":40,"tag":236,"props":2229,"children":2230},{},[2231,2255],{"type":40,"tag":263,"props":2232,"children":2233},{},[2234,2236,2241,2243,2248,2250],{"type":45,"value":2235},"Replacing a ",{"type":40,"tag":71,"props":2237,"children":2239},{"className":2238},[],[2240],{"type":45,"value":970},{"type":45,"value":2242}," value with ",{"type":40,"tag":71,"props":2244,"children":2246},{"className":2245},[],[2247],{"type":45,"value":1024},{"type":45,"value":2249}," off a ",{"type":40,"tag":71,"props":2251,"children":2253},{"className":2252},[],[2254],{"type":45,"value":962},{"type":40,"tag":263,"props":2256,"children":2257},{},[2258,2264,2266,2271,2273,2278,2280,2285,2287,2292,2293,2298,2300,2305,2307,2312],{"type":40,"tag":71,"props":2259,"children":2261},{"className":2260},[],[2262],{"type":45,"value":2263},"DateTimeOffset.DateTime",{"type":45,"value":2265}," returns ",{"type":40,"tag":71,"props":2267,"children":2269},{"className":2268},[],[2270],{"type":45,"value":1032},{"type":45,"value":2272}," — use ",{"type":40,"tag":71,"props":2274,"children":2276},{"className":2275},[],[2277],{"type":45,"value":1011},{"type":45,"value":2279}," (for former ",{"type":40,"tag":71,"props":2281,"children":2283},{"className":2282},[],[2284],{"type":45,"value":89},{"type":45,"value":2286},") or ",{"type":40,"tag":71,"props":2288,"children":2290},{"className":2289},[],[2291],{"type":45,"value":1056},{"type":45,"value":2279},{"type":40,"tag":71,"props":2294,"children":2296},{"className":2295},[],[2297],{"type":45,"value":517},{"type":45,"value":2299},") to preserve the original ",{"type":40,"tag":71,"props":2301,"children":2303},{"className":2302},[],[2304],{"type":45,"value":937},{"type":45,"value":2306},". Only change the field\u002Freturn type to ",{"type":40,"tag":71,"props":2308,"children":2310},{"className":2309},[],[2311],{"type":45,"value":962},{"type":45,"value":2313}," if the user asked for it.",{"type":40,"tag":236,"props":2315,"children":2316},{},[2317,2322],{"type":40,"tag":263,"props":2318,"children":2319},{},[2320],{"type":45,"value":2321},"Migrating too much at once",{"type":40,"tag":263,"props":2323,"children":2324},{},[2325],{"type":45,"value":2326},"Stick to the defined scope — one project or namespace per run",{"type":40,"tag":236,"props":2328,"children":2329},{},[2330,2349],{"type":40,"tag":263,"props":2331,"children":2332},{},[2333,2334,2339,2341,2347],{"type":45,"value":83},{"type":40,"tag":71,"props":2335,"children":2337},{"className":2336},[],[2338],{"type":45,"value":517},{"type":45,"value":2340}," when only ",{"type":40,"tag":71,"props":2342,"children":2344},{"className":2343},[],[2345],{"type":45,"value":2346},"UtcNow",{"type":45,"value":2348}," was requested",{"type":40,"tag":263,"props":2350,"children":2351},{},[2352],{"type":45,"value":2353},"Respect the literal request; list the other call sites as out-of-scope suggestions instead of rewriting them",{"type":40,"tag":236,"props":2355,"children":2356},{},[2357,2362],{"type":40,"tag":263,"props":2358,"children":2359},{},[2360],{"type":45,"value":2361},"Claiming \"Build succeeded\" after a failed restore",{"type":40,"tag":263,"props":2363,"children":2364},{},[2365],{"type":45,"value":2366},"Read the exit code and output; report the real failure and fix it or surface it as a blocker",{"type":40,"tag":236,"props":2368,"children":2369},{},[2370,2375],{"type":40,"tag":263,"props":2371,"children":2372},{},[2373],{"type":45,"value":2374},"Forgetting DI registration",{"type":40,"tag":263,"props":2376,"children":2377},{},[2378,2380,2385,2386,2391],{"type":45,"value":2379},"Always verify ",{"type":40,"tag":71,"props":2381,"children":2383},{"className":2382},[],[2384],{"type":45,"value":446},{"type":45,"value":1094},{"type":40,"tag":71,"props":2387,"children":2389},{"className":2388},[],[2390],{"type":45,"value":453},{"type":45,"value":2392}," has the registration before replacing call sites",{"type":40,"tag":2394,"props":2395,"children":2396},"style",{},[2397],{"type":45,"value":2398},"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":2400,"total":2562},[2401,2417,2430,2445,2463,2477,2496,2506,2518,2528,2541,2552],{"slug":2402,"name":2402,"fn":2403,"description":2404,"org":2405,"tags":2406,"stars":2414,"repoUrl":2415,"updatedAt":2416},"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},[2407,2408,2411],{"name":13,"slug":14,"type":15},{"name":2409,"slug":2410,"type":15},"Engineering","engineering",{"name":2412,"slug":2413,"type":15},"Performance","performance",5535,"https:\u002F\u002Fgithub.com\u002Fdotnet\u002Fmsbuild","2026-07-22T05:37:33.965588",{"slug":2418,"name":2418,"fn":2419,"description":2420,"org":2421,"tags":2422,"stars":22,"repoUrl":23,"updatedAt":2429},"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},[2423,2424,2425,2428],{"name":13,"slug":14,"type":15},{"name":20,"slug":21,"type":15},{"name":2426,"slug":2427,"type":15},"Debugging","debugging",{"name":2412,"slug":2413,"type":15},"2026-07-12T08:23:25.400375",{"slug":2431,"name":2431,"fn":2432,"description":2433,"org":2434,"tags":2435,"stars":22,"repoUrl":23,"updatedAt":2444},"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},[2436,2437,2440,2441],{"name":13,"slug":14,"type":15},{"name":2438,"slug":2439,"type":15},"Android","android",{"name":2426,"slug":2427,"type":15},{"name":2442,"slug":2443,"type":15},"Microsoft","microsoft","2026-07-12T08:23:21.595572",{"slug":2446,"name":2446,"fn":2447,"description":2448,"org":2449,"tags":2450,"stars":22,"repoUrl":23,"updatedAt":2462},"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},[2451,2452,2453,2456,2459],{"name":13,"slug":14,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2454,"slug":2455,"type":15},"iOS","ios",{"name":2457,"slug":2458,"type":15},"macOS","macos",{"name":2460,"slug":2461,"type":15},"Observability","observability","2026-07-12T08:23:20.369986",{"slug":2464,"name":2464,"fn":2465,"description":2466,"org":2467,"tags":2468,"stars":22,"repoUrl":23,"updatedAt":2476},"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},[2469,2470,2473],{"name":20,"slug":21,"type":15},{"name":2471,"slug":2472,"type":15},"QA","qa",{"name":2474,"slug":2475,"type":15},"Testing","testing","2026-07-12T08:23:51.277743",{"slug":2478,"name":2478,"fn":2479,"description":2480,"org":2481,"tags":2482,"stars":22,"repoUrl":23,"updatedAt":2495},"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},[2483,2484,2487,2489,2492],{"name":13,"slug":14,"type":15},{"name":2485,"slug":2486,"type":15},"Blazor","blazor",{"name":2488,"slug":1287,"type":15},"C#",{"name":2490,"slug":2491,"type":15},"UI Components","ui-components",{"name":2493,"slug":2494,"type":15},"Web Development","web-development","2026-07-15T06:03:29.216359",{"slug":2497,"name":2497,"fn":2498,"description":2499,"org":2500,"tags":2501,"stars":22,"repoUrl":23,"updatedAt":2505},"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},[2502,2503,2504],{"name":20,"slug":21,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2442,"slug":2443,"type":15},"2026-07-12T08:21:34.637923",{"slug":2507,"name":2507,"fn":2508,"description":2509,"org":2510,"tags":2511,"stars":22,"repoUrl":23,"updatedAt":2517},"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},[2512,2515,2516],{"name":2513,"slug":2514,"type":15},"Build","build",{"name":2426,"slug":2427,"type":15},{"name":2409,"slug":2410,"type":15},"2026-07-19T05:38:19.340791",{"slug":2519,"name":2519,"fn":2520,"description":2521,"org":2522,"tags":2523,"stars":22,"repoUrl":23,"updatedAt":2527},"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},[2524,2525,2526],{"name":13,"slug":14,"type":15},{"name":2409,"slug":2410,"type":15},{"name":2412,"slug":2413,"type":15},"2026-07-19T05:38:18.364937",{"slug":2529,"name":2529,"fn":2530,"description":2531,"org":2532,"tags":2533,"stars":22,"repoUrl":23,"updatedAt":2540},"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},[2534,2535,2538,2539],{"name":2409,"slug":2410,"type":15},{"name":2536,"slug":2537,"type":15},"Monitoring","monitoring",{"name":2412,"slug":2413,"type":15},{"name":2474,"slug":2475,"type":15},"2026-07-12T08:21:35.865649",{"slug":2542,"name":2542,"fn":2543,"description":2544,"org":2545,"tags":2546,"stars":22,"repoUrl":23,"updatedAt":2551},"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},[2547,2548,2549,2550],{"name":13,"slug":14,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2409,"slug":2410,"type":15},{"name":2412,"slug":2413,"type":15},"2026-07-12T08:21:40.961722",{"slug":2553,"name":2553,"fn":2554,"description":2555,"org":2556,"tags":2557,"stars":22,"repoUrl":23,"updatedAt":2561},"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},[2558,2559,2560],{"name":2426,"slug":2427,"type":15},{"name":2409,"slug":2410,"type":15},{"name":2471,"slug":2472,"type":15},"2026-07-19T05:38:14.336279",144,{"items":2564,"total":2613},[2565,2572,2579,2587,2593,2601,2607],{"slug":2418,"name":2418,"fn":2419,"description":2420,"org":2566,"tags":2567,"stars":22,"repoUrl":23,"updatedAt":2429},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2568,2569,2570,2571],{"name":13,"slug":14,"type":15},{"name":20,"slug":21,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2412,"slug":2413,"type":15},{"slug":2431,"name":2431,"fn":2432,"description":2433,"org":2573,"tags":2574,"stars":22,"repoUrl":23,"updatedAt":2444},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2575,2576,2577,2578],{"name":13,"slug":14,"type":15},{"name":2438,"slug":2439,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2442,"slug":2443,"type":15},{"slug":2446,"name":2446,"fn":2447,"description":2448,"org":2580,"tags":2581,"stars":22,"repoUrl":23,"updatedAt":2462},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2582,2583,2584,2585,2586],{"name":13,"slug":14,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2454,"slug":2455,"type":15},{"name":2457,"slug":2458,"type":15},{"name":2460,"slug":2461,"type":15},{"slug":2464,"name":2464,"fn":2465,"description":2466,"org":2588,"tags":2589,"stars":22,"repoUrl":23,"updatedAt":2476},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2590,2591,2592],{"name":20,"slug":21,"type":15},{"name":2471,"slug":2472,"type":15},{"name":2474,"slug":2475,"type":15},{"slug":2478,"name":2478,"fn":2479,"description":2480,"org":2594,"tags":2595,"stars":22,"repoUrl":23,"updatedAt":2495},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2596,2597,2598,2599,2600],{"name":13,"slug":14,"type":15},{"name":2485,"slug":2486,"type":15},{"name":2488,"slug":1287,"type":15},{"name":2490,"slug":2491,"type":15},{"name":2493,"slug":2494,"type":15},{"slug":2497,"name":2497,"fn":2498,"description":2499,"org":2602,"tags":2603,"stars":22,"repoUrl":23,"updatedAt":2505},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2604,2605,2606],{"name":20,"slug":21,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2442,"slug":2443,"type":15},{"slug":2507,"name":2507,"fn":2508,"description":2509,"org":2608,"tags":2609,"stars":22,"repoUrl":23,"updatedAt":2517},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[2610,2611,2612],{"name":2513,"slug":2514,"type":15},{"name":2426,"slug":2427,"type":15},{"name":2409,"slug":2410,"type":15},96]