/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,
$ npx -y skills add dotnet/skills --skill migrate-static-to-wrapper --agent claude-codeHow 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.mdname: 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
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 `
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 (
Repo: dotnet/skills
Other skills on dotnet-skills.
- /csharp-scripts
Run file-based C# apps with the .NET CLI when the user explicitly wants C#/.NET code without creating a project. Use for C# language/API experiments, one-file C# apps, small multi-file C# apps composed with `#:include`/`#:exclude`, or C# file-based apps linked with `#:ref`. Do
Open skill - /dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging
Open skill - /nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to
Open skill - /technology-selection
Guides technology selection and implementation of AI and ML features in .NET 8+ applications using ML.NET, Microsoft.Extensions.AI (MEAI), Microsoft Agent Framework (MAF), GitHub Copilot SDK, ONNX Runtime, and OllamaSharp. Covers the full spectrum from classic ML through modern
Open skill - /configuring-opentelemetry-dotnet
Configure OpenTelemetry distributed tracing, metrics, and logging in ASP.NET Core using the .NET OpenTelemetry SDK. Use when adding observability, setting up OTLP exporters, creating custom metrics/spans, or troubleshooting distributed trace correlation.
Open skill - /convert-blazor-server-to-webapp
Guides conversion of a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. USE FOR: migrating apps that use AddServerSideBlazor and MapBlazorHub to the AddRazorComponents/MapRazorComponents model, converting _Host.cshtml to an App.razor root component, replacing
Open skill

