Skip to content
Development
Skill

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

From plugin
dotnet-skills
5.1k96 skills16 agents
Install
$ npx -y skills add dotnet/skills --skill migrate-static-to-wrapper --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/migrate-static-to-wrapper

Context preview

The summary Claude sees to decide when to auto-load this skill.

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,

SKILL.md

migrate-static-to-wrapper.SKILL.md
name: migrate-static-to-wrapper
description: >
  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 class testable by adding an ambient
  TimeProvider/IFileSystem 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.
license: MIT

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.UtcNow` → `TimeProvider.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

| Input | Required | Description | |-------|----------|-------------| | Static pattern | Yes | What to replace (e.g., `DateTime.UtcNow`, `File.ReadAllText`) | | Replacement abstraction | Yes | What to use instead (e.g., `TimeProvider`, `IFileSystem`) | | Scope | Yes | File path, project (.csproj), namespace, or directory to migrate | | Injection strategy | No | `constructor` (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

| Category | Original | DI replacement | |----------|----------|----------------| | Time | `DateTime.Now` | `_timeProvider.GetLocalNow().LocalDateTime` | | Time | `DateTime.UtcNow` | `_timeProvider.GetUtcNow().UtcDateTime` | | Time | `DateTime.Today` | `_timeProvider.GetLocalNow().LocalDateTime.Date` | | Time | `DateTimeOffset.Now` | `_timeProvider.GetLocalNow()` | | Time | `DateTimeOffset.UtcNow` | `_timeProvider.GetUtcNow()` | | File | `File.ReadAllText(path)` | `_fileSystem.File.ReadAllText(path)` | | File | `File.WriteAllText(path, text)` | `_fileSystem.File.WriteAllText(path, text)` | | File | `File.Exists(path)` | `_fileSystem.File.Exists(path)` | | File | `Directory.Exists(path)` | `_fileSystem.Directory.Exists(path)` | | Env | `Environment.GetEnvironmentVariable(name)` | `_env.GetEnvironmentVariable(name)` | | Console | `Console.WriteLine(msg)` | `_console.WriteLine(msg)` | | Process | `Process.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 `

Read more
Ships withdotnet-skills

This repository contains the .NET team's curated set of core skills and custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io. 📊 Dashboard - Accuracy and efficiency scoring trends for contained plugins (

Get the whole plugin

Other skills on dotnet-skills.