/migrate-static-to-wrapper
ALWAYS USE when asked to migrate, replace, or make testable existing C# static calls with a named wrapper or built-in abstraction: DateTime.UtcNow/Now or DateTimeOffset.UtcNow to TimeProvider/IClock, File.* to IFileSystem or an existing store such as ITextFileStore, and
Claude Code
$ npx -y skills add dotnet/skills --skill migrate-static-to-wrapper --agent claude-code
Codex
$ npx skills add dotnet/skills --skill migrate-static-to-wrapper -a codex -y
Cursor
$ npx skills add dotnet/skills --skill migrate-static-to-wrapper -a cursor -y
Gemini CLI
$ npx skills add dotnet/skills --skill migrate-static-to-wrapper -a gemini-cli -y
GitHub Copilot
$ npx skills add dotnet/skills --skill migrate-static-to-wrapper -a github-copilot -y
OpenCode
$ npx skills add dotnet/skills --skill migrate-static-to-wrapper -a opencode -y
Windsurf
$ npx skills add dotnet/skills --skill migrate-static-to-wrapper -a windsurf -y
SKILL.md
migrate-static-to-wrapper.SKILL.mdname: migrate-static-to-wrapper
description: >
ALWAYS USE when asked to migrate, replace, or make testable existing C# static
calls with a named wrapper or built-in abstraction: DateTime.UtcNow/Now or
DateTimeOffset.UtcNow to TimeProvider/IClock, File.* to IFileSystem or an
existing store such as ITextFileStore, and Environment.* to an existing reader
such as IEnvironmentReader. Covers scoped files/projects, constructor
injection, replacing temp-file or process-environment tests with fakes,
"already registered" abstractions, and static classes whose callers/signatures
must stay unchanged. Preserves DateTimeKind and call count. DO NOT USE for finding
statics (detect-static-dependencies), choosing/designing a new wrapper
(generate-testability-wrappers), behavior tests with no chosen seam
(testability-obstacle), or test-framework migration.
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
- Updating affected tests with fakes when the requested migration names the
replacement abstraction
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)
- The user primarily asks for a deterministic behavior test and has not selected
the production seam (use `testability-obstacle`)
> 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 | No | Infer from the request and discovered call sites (e.g., `DateTime.UtcNow`, `File.ReadAllText`) | | Replacement abstraction | No | Infer from the request and existing project abstractions; stop only when no named/existing abstraction is available | | Scope | No | Infer from the requested file/project/namespace, otherwise discover the narrowest relevant workspace scope | | Injection strategy | No | `constructor` (default), `primary-constructor`, or `ambient` |
Workflow
Non-negotiable migration boundaries
- **Missing abstraction means stop.** If the named interface/package is absent and
the request only authorizes call-site replacement, do not add a package, invent a local lookalike interface, or edit production code. Report the exact missing prerequisite and the authorization needed to continue.
- **One source read stays one replacement read.** Do not hoist or coalesce calls,
even when sharing a captured timestamp looks cleaner.
- **The requested scope is exhaustive and exclusive.** Replace every named call
in scope and no adjacent member or file.
- **Repository-backed requests require repository work.** Start by discovering
files from the current workspace. Do not claim the repository is unavailable or ask the user for a path or file contents until workspace-relative discovery found no target. Do not say work was implemented unless the diff proves it.
- **Discovered workspace files must be completed in this turn when permitted.**
Use a host-native shell reader (`sed`/`cat` or `Get-Content`) only after a confirmed reader availability, transport, or path-normalization failure and only after verifying the canonical path remains inside the current workspace. Stop on content-exclusion, permission/policy, workspace-boundary, or unknown read failures. Use a shell edit fallback only for a confirmed editor availability, transport, or path-normalization failure, never for a stale context, concurrent change, permission/policy denial, or path-boundary error. Before fallback, resolve the canonical path inside the current workspace, freshly read the file, and require an anchored replacement with the expected old text and exact match count; abort if either changed. Then re-open the file, inspect the diff, and validate. Do not ask the user to paste a readable discovered file or report a proposed patch as completed work.
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. A package that could provide an abstraction is not the same as an abstraction already available to this project.
2. **Trace production composition**: Check `Program.cs`, `Startup.cs`, factories, and manual construction sites before editing. An explicit migration to an available seam includes the minimal constructor/caller/registration changes needed to keep that scope runnable, unless the user forbids those edits. Register `TimeProvider.System` in an existing DI host when the new dependency is not already registered; update manual callers instea
Read more
name: migrate-static-to-wrapper description: > ALWAYS USE when asked to migrate, replace, or make testable existing C# static calls with a named wrapper or built-in abstraction: DateTime.UtcNow/Now or DateTimeOffset.UtcNow to TimeProvider/IClock, File.* to IFileSystem or an existing store such as ITextFileStore, and Environment.* to an existing reader such as IEnvironmentReader. Covers scoped files/projects, constructor injection, replacing temp-file or process-environment tests with fakes, "already registered" abstractions, and static classes whose callers/signatures must stay unchanged. Preserves DateTimeKind and call count. DO NOT USE for finding statics (detect-static-dependencies), choosing/designing a new wrapper (generate-testability-wrappers), behavior tests with no chosen seam (testability-obstacle), or test-framework migration. 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
- Updating affected tests with fakes when the requested migration names the
replacement abstraction
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)
- The user primarily asks for a deterministic behavior test and has not selected
the production seam (use `testability-obstacle`)
> 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 | No | Infer from the request and discovered call sites (e.g., `DateTime.UtcNow`, `File.ReadAllText`) | | Replacement abstraction | No | Infer from the request and existing project abstractions; stop only when no named/existing abstraction is available | | Scope | No | Infer from the requested file/project/namespace, otherwise discover the narrowest relevant workspace scope | | Injection strategy | No | `constructor` (default), `primary-constructor`, or `ambient` |
Workflow
Non-negotiable migration boundaries
- **Missing abstraction means stop.** If the named interface/package is absent and
the request only authorizes call-site replacement, do not add a package, invent a local lookalike interface, or edit production code. Report the exact missing prerequisite and the authorization needed to continue.
- **One source read stays one replacement read.** Do not hoist or coalesce calls,
even when sharing a captured timestamp looks cleaner.
- **The requested scope is exhaustive and exclusive.** Replace every named call
in scope and no adjacent member or file.
- **Repository-backed requests require repository work.** Start by discovering
files from the current workspace. Do not claim the repository is unavailable or ask the user for a path or file contents until workspace-relative discovery found no target. Do not say work was implemented unless the diff proves it.
- **Discovered workspace files must be completed in this turn when permitted.**
Use a host-native shell reader (`sed`/`cat` or `Get-Content`) only after a confirmed reader availability, transport, or path-normalization failure and only after verifying the canonical path remains inside the current workspace. Stop on content-exclusion, permission/policy, workspace-boundary, or unknown read failures. Use a shell edit fallback only for a confirmed editor availability, transport, or path-normalization failure, never for a stale context, concurrent change, permission/policy denial, or path-boundary error. Before fallback, resolve the canonical path inside the current workspace, freshly read the file, and require an anchored replacement with the expected old text and exact match count; abort if either changed. Then re-open the file, inspect the diff, and validate. Do not ask the user to paste a readable discovered file or report a proposed patch as completed work.
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. A package that could provide an abstraction is not the same as an abstraction already available to this project.
2. **Trace production composition**: Check `Program.cs`, `Startup.cs`, factories, and manual construction sites before editing. An explicit migration to an available seam includes the minimal constructor/caller/registration changes needed to keep that scope runnable, unless the user forbids those edits. Register `TimeProvider.System` in an existing DI host when the new dependency is not already registered; update manual callers instea
This repository contains the .NET team's curated set of portable skills and host-specific custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io.
Other skills on skills.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about migrate-static-to-wrapper.
- Which coding agents can use migrate-static-to-wrapper?
- migrate-static-to-wrapper installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

