Skip to content
Development
Skill

/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

From plugin
managedcode-dotnet-skills
481182 skills17 agents
Install
$ npx -y skills add managedcode/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.

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

SKILL.md

migrate-static-to-wrapper.SKILL.md
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. **Confirm production composition exists**: Check `Program.cs`, `Startup.cs`, or manual construction sites. If package, wrapper, or registration work is missing, add it only when the user explicitly authorized those dependency/composition changes. Otherwise stop before editing call sites and report the exact prerequisite; do not turn a scoped migration into first-time abstraction design.

3. **Identify all files in scope**: List the `.cs` files th

Read more
Ships withmanagedcode-dotnet-skills

Stop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.

Get the whole plugin

Other skills on managedcode-dotnet-skills.