Source profileQuality 95/100

dotnet/skills/plugins/dotnet-test/skills/migrate-static-to-wrapper/SKILL.md

migrate-static-to-wrapper

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/DateTime.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 cl

Source repository stars
5,248
Declared platforms
0
Static risk flags
0
Last source update
2026-08-26
Source checked
2026-08-26

Decision brief

What it does: where it fits

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.

Best for

  • After wrappers have been generated (via generate-testability-wrappers) or built-in abstractions identified
  • Migrating DateTime.UtcNow → TimeProvider.GetUtcNow() across a project
  • Migrating File. → IFileSystem.File. across a namespace

Not for

  • No wrapper or abstraction exists yet and one must be designed from scratch (use generate-testability-wrappers first).
  • The user wants to detect statics, not migrate them (use detect-static-dependencies)

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-test/skills/migrate-static-to-wrapper"
Safe inspection promptEditorial

Inspect the Agent Skill "migrate-static-to-wrapper" from https://github.com/dotnet/skills/blob/1b896e91feb0f613cb54a914f1efd2897810ae02/plugins/dotnet-test/skills/migrate-static-to-wrapper/SKILL.md at commit 1b896e91feb0f613cb54a914f1efd2897810ae02. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    Workflow

    Before modifying any code:

    Confirm the wrapper/abstraction 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.…Confirm DI registration exists: Check Program.cs or Startup.cs for the service registration. If missing, add it before proceeding.Identify all files in scope: List the .cs files that will be modified. Exclude test projects, obj/, bin/, and generated code.
  2. 02

    Step 1: Verify prerequisites

    Before modifying any code:

    Confirm the wrapper/abstraction 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.…Confirm DI registration exists: Check Program.cs or Startup.cs for the service registration. If missing, add it before proceeding.Identify all files in scope: List the .cs files that will be modified. Exclude test projects, obj/, bin/, and generated code.
  3. 03

    Step 2: Plan the migration for each file

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

    Which class(es) contain the call sites — identify the class declarationsWhether the class already has the dependency injected — check constructors for existing TimeProvider, IFileSystem, etc. parametersThe replacement expression for each call site
  4. 04

    Step 3: Add constructor injection

    Add the new dependency following the class's existing pattern:

    Primary constructor (C 12+): Add parameter to primary constructor: public class OrderProcessor(ILogger logger, TimeProvider timeProvider)Traditional constructor: Add private readonly field + constructor parameter, matching the existing field naming convention (camelCase or mcamelCase)Production: leave Clock at its TimeProvider.System default, or assign the DI-resolved TimeProvider once at startup (TimestampFormatter.Clock = app.Services.GetRequiredService();).
  5. 05

    Step 4: Replace call sites

    Perform each replacement mechanically. For each call site:

    Replace the static call with the wrapper callPreserve the surrounding code structure (whitespace, comments, chaining)Add required using directives if not already present

Permission review

Static risk signals and limitations

No configured static risk pattern was detected

This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score95/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars5,248SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
dotnet/skills
Skill path
plugins/dotnet-test/skills/migrate-static-to-wrapper/SKILL.md
Commit
1b896e91feb0f613cb54a914f1efd2897810ae02
License
MIT
Collected
2026-08-26
Default branch
main
View the original SKILL.md

Migrate Static to Wrapper

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.

When to Use

  • After wrappers have been generated (via generate-testability-wrappers) or built-in abstractions identified
  • Migrating DateTime.UtcNowTimeProvider.GetUtcNow() across a project
  • Migrating File.*IFileSystem.File.* across a namespace
  • Adding constructor injection for the new abstraction to affected classes
  • Making a static utility class testable by adding an ambient seam (Step 3) while its existing call sites keep compiling unchanged
  • Incremental migration: one project or namespace at a time

When Not to Use

  • No wrapper or abstraction exists yet and one must be designed from scratch (use generate-testability-wrappers first). A built-in abstraction such as TimeProvider or IFileSystem always counts as existing.
  • The user wants to detect statics, not migrate them (use detect-static-dependencies)
  • Migrating between test frameworks (use the appropriate migration skill)

A class that is static, or a project with no DI container, is not a reason to skip this skill — that is exactly what the ambient seam in Step 3 is for. Use it whenever the call sites must keep compiling unchanged.

Inputs

InputRequiredDescription
Static patternYesWhat to replace (e.g., DateTime.UtcNow, File.ReadAllText)
Replacement abstractionYesWhat to use instead (e.g., TimeProvider, IFileSystem)
ScopeYesFile path, project (.csproj), namespace, or directory to migrate
Injection strategyNoconstructor (default), primary-constructor, or ambient

Workflow

Step 1: Verify prerequisites

Before modifying any code:

  1. Confirm the wrapper/abstraction 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.

  2. Confirm DI registration exists: Check Program.cs or Startup.cs for the service registration. If missing, add it before proceeding.

  3. Identify all files in scope: List the .cs files that will be modified. Exclude test projects, obj/, bin/, and generated code.

Step 2: Plan the migration for each file

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

For each file containing the static pattern, determine:

  1. Which class(es) contain the call sites — identify the class declarations
  2. Whether the class already has the dependency injected — check constructors for existing TimeProvider, IFileSystem, etc. parameters
  3. The replacement expression for each call site

Replacement mapping

CategoryOriginalDI replacement
TimeDateTime.Now_timeProvider.GetLocalNow().LocalDateTime
TimeDateTime.UtcNow_timeProvider.GetUtcNow().UtcDateTime
TimeDateTime.Today_timeProvider.GetLocalNow().LocalDateTime.Date
TimeDateTimeOffset.Now_timeProvider.GetLocalNow()
TimeDateTimeOffset.UtcNow_timeProvider.GetUtcNow()
FileFile.ReadAllText(path)_fileSystem.File.ReadAllText(path)
FileFile.WriteAllText(path, text)_fileSystem.File.WriteAllText(path, text)
FileFile.Exists(path)_fileSystem.File.Exists(path)
FileDirectory.Exists(path)_fileSystem.Directory.Exists(path)
EnvEnvironment.GetEnvironmentVariable(name)_env.GetEnvironmentVariable(name)
ConsoleConsole.WriteLine(msg)_console.WriteLine(msg)
ProcessProcess.Start(info)_processRunner.Start(info)

Apply the same pattern for other members in each category.

Preserve DateTimeKind — this is the most common silent regression. TimeProvider.GetUtcNow() / 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:

  • DateTime.UtcNow has Kind == Utc → use .UtcDateTime (not .DateTime, which yields Kind == Unspecified).
  • DateTime.Now has Kind == Local → use .LocalDateTime (not .DateTime).
  • When a call site consumes a DateTimeOffset directly (a field/parameter/return already typed DateTimeOffset), drop the .UtcDateTime/.LocalDateTime suffix and assign the DateTimeOffset as-is — don't force it back through DateTime.

Match the target member's type: if the surrounding field/property 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.

Step 3: Add constructor injection

Add the new dependency following the class's existing pattern:

  • Primary constructor (C# 12+): Add parameter to primary constructor: public class OrderProcessor(ILogger<OrderProcessor> logger, TimeProvider timeProvider)
  • Traditional constructor: Add private readonly field + constructor parameter, matching the existing field naming convention (_camelCase or m_camelCase)

Static classes: use ambient context (no constructor injection)

A 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/test setup.

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

public static class TimestampFormatter
{
    // Ambient seam — defaults to the real clock, swap in tests.
    public static TimeProvider Clock { get; set; } = TimeProvider.System;

    public static string Now() => Clock.GetUtcNow().ToString("O");
}
  • Production: leave Clock at its TimeProvider.System default, or assign the DI-resolved TimeProvider once at startup (TimestampFormatter.Clock = app.Services.GetRequiredService<TimeProvider>();).

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

    var original = TimestampFormatter.Clock;
    TimestampFormatter.Clock = new FakeTimeProvider(instant);
    try
    {
        // exercise code under test
    }
    finally
    {
        TimestampFormatter.Clock = original;
    }
    
  • 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/class (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.

  • The same seam works for other statics (IFileSystem, custom wrappers): a public static <Abstraction> X { get; set; } defaulting to the real implementation, with the same restore-in-finally and non-parallel discipline.

Step 4: Replace call sites

Perform each replacement mechanically. For each call site:

  1. Replace the static call with the wrapper call
  2. Preserve the surrounding code structure (whitespace, comments, chaining)
  3. Add required using directives if not already present

Adding using directives

AbstractionUsing directive
TimeProviderNone (in System namespace)
IFileSystemusing System.IO.Abstractions;
IHttpClientFactoryusing System.Net.Http; (usually already present)
Custom wrappersusing <wrapper namespace>;

Step 5: Update affected test files

If test files exist for the migrated classes:

  1. Update constructor calls — add the new parameter to test class instantiation
  2. Use test doubles:
    • TimeProvidernew FakeTimeProvider() from Microsoft.Extensions.TimeProvider.Testing
    • IFileSystemnew MockFileSystem() from System.IO.Abstractions.TestingHelpers
    • Custom wrappers → new Mock<IWrapperName>() or hand-rolled fake

Step 6: Build verification

After all changes in the current scope:

dotnet build <project.csproj>

Report the build result you actually observed. Only write "build succeeded" when the command exited 0; if it failed — including restore/NuGet 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.

If the build fails:

  • Missing using: Add the required using directive
  • Missing NuGet package: Run dotnet add package <name>
  • Constructor mismatch in tests: Update test instantiation (Step 5)
  • Ambiguous call: Fully qualify the wrapper call

Step 7: Report changes

Summarize what was done:

## Migration Summary

**Pattern**: DateTime.UtcNow → TimeProvider.GetUtcNow()
**Scope**: MyProject/Services/

### Files Modified (production)
| File | Call Sites Replaced | Injection Added |
|------|--------------------:|:----------------|
| OrderProcessor.cs | 3 | Yes (constructor) |
| NotificationService.cs | 1 | Yes (primary ctor) |

### Files Modified (tests)
| File | Change |
|------|--------|
| OrderProcessorTests.cs | Added FakeTimeProvider parameter |

### Remaining (out of scope)
- MyProject/Legacy/ — 8 call sites not migrated (different namespace)

Validation

  • All call sites in scope were replaced (none missed)
  • No call site outside the requested member/file scope was modified
  • Call sites documented as intentional (e.g. local time) were left untouched and reported
  • Constructor injection added to all affected classes
  • Field naming follows existing class conventions
  • Required using directives added
  • Required NuGet packages referenced
  • Build succeeds after migration, and the reported result matches the actual command exit code
  • Test files updated with appropriate test doubles
  • No behavioral changes introduced (wrapper delegates directly to the static)
  • DateTimeKind preserved — former DateTime.UtcNow stays Utc (.UtcDateTime), former DateTime.Now stays Local (.LocalDateTime)

Common Pitfalls

PitfallSolution
Replacing statics in test codeOnly replace in production code; tests should use fakes/mocks
Breaking static classesStatic classes can't have constructors — use the ambient context seam (Step 3) instead of converting them to non-static
Missing FakeTimeProvider NuGetAdd Microsoft.Extensions.TimeProvider.Testing to test project
Replacing a DateTime value with .DateTime off a DateTimeOffsetDateTimeOffset.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/return type to DateTimeOffset if the user asked for it.
Migrating too much at onceStick to the defined scope — one project or namespace per run
Migrating DateTime.Now when only UtcNow was requestedRespect the literal request; list the other call sites as out-of-scope suggestions instead of rewriting them
Claiming "Build succeeded" after a failed restoreRead the exit code and output; report the real failure and fix it or surface it as a blocker
Forgetting DI registrationAlways verify Program.cs/Startup.cs has the registration before replacing call sites

Frequently asked questions

What to verify before installation and use

What does the migrate-static-to-wrapper source document cover?

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.

How do I install migrate-static-to-wrapper?

The source record exposes this install command: npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-test/skills/migrate-static-to-wrapper". Inspect the command and pinned source before running it.

Alternatives

Compare before choosing

Computed 10029,095

garrytan/gbrain

bulk-ingestion

End-to-end discipline for turning any large data source (audio libraries, email takeouts, document corpora, chat exports, API dumps) into brain pages at scale. The lifecycle spine: SCHEMA → ACCESS → TRIAL → EVALUATE → IMPROVE → CODIFY → TEST → SKILLIFY → BULK → MONITOR. State is tracked in a durable JSON manifest (see MANIFEST-PATTERN.md) so any crash, session boundary, or subagent fan-out resumes from ground truth instead of memory.

Computed 10024,975

alirezarezvani/claude-skills

app-store-optimization

App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist

Computed 1005,248

dotnet/skills

migrate-vstest-to-mtp

Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing

Computed 991,259

vipshop/cache-dit

cache-dit-model-integration

High-level guide for integrating a new DiT model into cache-dit: Cache (BlockAdapter/ForwardPattern), Context Parallelism, Tensor Parallelism, Text Encoder Parallelism (TE-P), VAE Parallelism (VAE-P), generate CLI, installation, testing workflow, and detailed references. Use when adding support for a new diffusion transformer model in cache-dit.