[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M699EAYW03HFQ553Z0KDRSCS":3,"skill-same:01M699EAYW03HFQ553Z0KDRSCS":68},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":6,"ownerHtmlUrl":50,"ownerBlogUrl":51,"ownerDescription":52,"updatedAt":53,"install":54,"resources":67},"01M699EAYW03HFQ553Z0KDRSCS","generate-testability-wrappers",null,"DO NOT USE when the target already consumes an injected interface or built-in abstraction such as IFileSystem or TimeProvider, even if the request says \"generate a wrapper\"; no new wrapper is needed. Use only when C# source calls an ambient\u002Fstatic dependency and no injectable seam exists: first-time TimeProvider, IHttpClientFactory, or System.IO.Abstractions adoption; minimal Environment\u002FConsole\u002FProcess wrappers; IProcessRunner; DI registration; or an ambient seam that preserves a static API. Exclude static detection (detect-static-dependencies), migration to an existing\u002Fregistered abstraction (migrate-static-to-wrapper), one blocked behavior plus deterministic tests (testability-obstacle), and general interface design.","microsoft\u002Ftestfx","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M699C71CJ91B5WZ3AXG3GG2M",1059,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fgenerate-testability-wrappers\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"generate testability wrappers","generate","testability","when","design","request","static","seam","source","interface","interface design","io","as","time","deterministic","Wrapper","dependency","only","migrate","api","2026-11-10 10:12:30",false,"---\nname: generate-testability-wrappers\ndescription: >\n  DO NOT USE when the target already consumes an injected interface or built-in\n  abstraction such as IFileSystem or TimeProvider, even if the request says\n  \"generate a wrapper\"; no new wrapper is needed. Use only when C# source calls\n  an ambient\u002Fstatic dependency and no injectable seam exists: first-time\n  TimeProvider, IHttpClientFactory, or System.IO.Abstractions adoption; minimal\n  Environment\u002FConsole\u002FProcess wrappers; IProcessRunner; DI registration; or an\n  ambient seam that preserves a static API. Exclude static detection\n  (detect-static-dependencies), migration to an existing\u002Fregistered abstraction\n  (migrate-static-to-wrapper), one blocked behavior plus deterministic tests\n  (testability-obstacle), and general interface design.\nlicense: MIT\n---\n\n# Generate Testability Wrappers\n\nGenerate wrapper interfaces, default implementations, and DI service registration code for untestable static dependencies. For statics that already have .NET built-in abstractions (`TimeProvider`, `IHttpClientFactory`), guide adoption of the built-in. For statics without built-in alternatives, generate custom minimal wrappers.\n\n## When to Use\n\n- After running `detect-static-dependencies` and identifying which statics to wrap\n- When the user asks to make a class testable by replacing statics with injected abstractions\n- When adopting `TimeProvider` (.NET 8+) or `System.IO.Abstractions`\n- When creating a custom wrapper for `Environment.*`, `Console.*`, or `Process.*`\n- When a released static API needs an ambient seam because signatures cannot change\n\n## When Not to Use\n\n- The user wants to find statics first (use `detect-static-dependencies`)\n- The user wants to bulk-replace call sites (use `migrate-static-to-wrapper`)\n- The static is already behind an interface\n\nIf the target already consumes an injected interface or built-in abstraction, stop:\ndo not add a second wrapper, project, registration, or test around that seam.\n\n> A missing DI package does not by itself force an ambient seam. For an\n> instantiable class, prefer constructor injection and compose it explicitly or\n> show the requested registration. Use Step 5 when the API is static and its\n> signatures must stay static, or when the user explicitly forbids caller\n> construction\u002FDI changes.\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Static category | Yes | Which category: `time`, `filesystem`, `environment`, `network`, `console`, `process` |\n| Target framework | Yes | The `TargetFramework` from `.csproj` (affects which built-in abstractions exist) |\n| Composition | No | Existing DI framework, explicit\u002Fmanual construction, or immutable static API |\n| Namespace | No | Target namespace for generated wrapper code |\n\n## Workflow\n\n### Step 1: Determine the abstraction strategy\n\nBased on the category and target framework:\n\n| Category | .NET 8+ | .NET 6-7 | .NET Framework |\n|----------|---------|----------|----------------|\n| Time | `TimeProvider` (built-in) | `TimeProvider` via `Microsoft.Bcl.TimeProvider` NuGet | Custom `ISystemClock` |\n| File system | `System.IO.Abstractions` (NuGet) | Same | Same |\n| HTTP | `IHttpClientFactory` (built-in) | Same | Same |\n| Environment | Custom `IEnvironmentProvider` | Same | Same |\n| Console | Custom `IConsole` | Same | Same |\n| Process | Custom `IProcessRunner` | Same | Same |\n\nThe table picks *which abstraction*. How it reaches the code under test is a\nseparate axis:\n\n- instantiable class: constructor injection, even if current callers compose the\n  object manually;\n- existing container: add compile-ready registration following its conventions;\n- public static API\u002Fsignatures that cannot change: Step 5's ambient seam.\n\nCheck for a host builder, `IServiceCollection`, existing registrations, and\nconstruction sites. Do not infer \"must remain static\" merely because the project\ncurrently has no container.\n\n### Step 2: Generate built-in abstraction adoption (Time, HTTP)\n\n#### TimeProvider (.NET 8+)\n\nNo wrapper code needed. Complete all four parts: production registration,\nconstructor injection, a `FakeTimeProvider` test, and the testing package.\n\n1. Register in DI:\n```csharp\nbuilder.Services.AddSingleton(TimeProvider.System);\n```\n\n2. Inject into classes:\n```csharp\npublic class OrderProcessor(TimeProvider timeProvider)\n{\n    public bool IsExpired(Order order)\n        => timeProvider.GetUtcNow() > order.ExpiresAt;\n}\n```\n\n3. Test with `FakeTimeProvider`:\n```csharp\n\u002F\u002F Requires Microsoft.Extensions.TimeProvider.Testing NuGet\nvar fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 1, 15, 0, 0, 0, TimeSpan.Zero));\nvar processor = new OrderProcessor(fakeTime);\nfakeTime.Advance(TimeSpan.FromDays(1));\nAssert.True(processor.IsExpired(order));\n```\n\nThe assertion must prove a time-dependent result after the fake is pinned or\nadvanced. Merely constructing `FakeTimeProvider` is not a test. When the project\nhas no container but the target is an instantiable class, inject\n`TimeProvider` anyway and show explicit production construction with\n`TimeProvider.System`; do not replace it with a custom static clock.\n\nBefore calling the adoption complete, verify the repository contains or the\nanswer supplies every required artifact: the testing package reference, the\nproduction composition\u002Fregistration, every affected constructor call, and a\nrunnable fake-time test. A code fragment that omits one of those integration\npoints is guidance, not a completed adoption.\n\n#### TimeProvider (pre-.NET 8)\n\nGuide: install `Microsoft.Bcl.TimeProvider` NuGet. Same API as above.\n\n#### IHttpClientFactory\n\nNo wrapper code needed. Register a typed client via\n`builder.Services.AddHttpClient\u003CMyService>()` and inject `HttpClient` directly\ninto the class constructor. Preserve cancellation by passing the caller's token\nto the HTTP operation.\n\nFor tests, provide a complete fake `HttpMessageHandler` whose `SendAsync`\nreturns a deterministic `HttpResponseMessage`, construct `HttpClient` with that\nhandler, and exercise the typed client without network access. Compile and run\nthe focused test when the task asks for implementation; do not stop at a\nschematic handler method.\n\nWhen production uses a typed-client registration, test that same registration\npipeline: configure its primary handler in `ServiceCollection`, resolve the typed\nclient, and call it. A test that manually constructs `HttpClient` proves the class\nbut not the DI registration the task asked to adopt.\n\n### Step 3: Generate custom wrappers (Environment, Console, Process)\n\nFor categories without built-in abstractions, follow this template:\n\n#### Interface — define the minimal surface\n\nOnly include methods that were actually detected in the codebase. Do NOT generate a wrapper for every possible member — wrap only what is used.\n\nPrefer a stateless operation-shaped interface. For example, if a caller only\nneeds to start a process, wait, and return its exit code, expose one `Run`\noperation rather than a stateful wrapper that leaks `Process` lifecycle.\n\n```csharp\nnamespace \u003CNamespace>;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Abstraction over \u003Cstatic class> for testability. \n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic interface I\u003CWrapperName>\n{\n    \u002F\u002F One method per detected static call\n    \u003Creturn type> \u003CMethodName>(\u003Cparameters>);\n}\n```\n\n#### Default implementation — delegate to the real static\n\n```csharp\nnamespace \u003CNamespace>;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Default implementation that delegates to \u003Cstatic class>.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic sealed class \u003CWrapperName> : I\u003CWrapperName>\n{\n    public \u003Creturn type> \u003CMethodName>(\u003Cparameters>)\n        => \u003CStaticClass>.\u003CMethod>(\u003Carguments>);\n}\n```\n\n#### DI registration\n\n```csharp\n\u002F\u002F In Program.cs or Startup.cs:\nbuilder.Services.AddSingleton\u003CI\u003CWrapperName>, \u003CWrapperName>>();\n```\n\nTreat registration as a deliverable, not a sentence in the summary. Add it to\nthe repository's existing registration surface when one exists. Otherwise show\nthe exact compile-ready statement and identify where the caller should place\nit. Stateless delegating wrappers are singleton; if state must be retained,\nexplain why a shorter lifetime is required.\n\n### Step 4: Generate file system wrapper adoption\n\nPrefer the established `System.IO.Abstractions` NuGet package over custom wrappers:\n\n1. Install the package:\n```\ndotnet add package System.IO.Abstractions\n```\n\n2. Register in DI:\n```csharp\nbuilder.Services.AddSingleton\u003CIFileSystem, FileSystem>();\n```\n\n3. Inject `IFileSystem` into classes:\n```csharp\npublic class ConfigLoader(IFileSystem fileSystem)\n{\n    public string LoadConfig(string path)\n        => fileSystem.File.ReadAllText(path);\n}\n```\n\n4. Test with `MockFileSystem`:\n```\ndotnet add \u003CTestProject> package System.IO.Abstractions.TestingHelpers\n```\n```csharp\nvar mockFs = new MockFileSystem(new Dictionary\u003Cstring, MockFileData>\n{\n    { \"\u002Fconfig.json\", new MockFileData(\"{\\\"key\\\": \\\"value\\\"}\") }\n});\nvar loader = new ConfigLoader(mockFs);\nAssert.Equal(\"{\\\"key\\\": \\\"value\\\"}\", loader.LoadConfig(\"\u002Fconfig.json\"));\n```\n\nPackage-first adoption is exclusive: add both package references, use\n`IFileSystem` in production, register or explicitly compose `FileSystem`, and\nseed `MockFileSystem` before exercising the consumer. Do not also generate a\nsecond custom filesystem interface, and do not present an unseeded mock whose\ntest could pass without proving the requested read\u002Fwrite behavior.\n\n### Step 5: Generate a signature-preserving ambient context\n\nUse this pattern when the API must remain static or its released signatures\ncannot accept a dependency:\n\n```csharp\npublic static class Clock\n{\n    private static readonly AsyncLocal\u003CFunc\u003CDateTime>?> s_override = new();\n    public static DateTime UtcNow\n        => s_override.Value?.Invoke() ?? TimeProvider.System.GetUtcNow().UtcDateTime;\n\n    internal static IDisposable Override(DateTime fixedUtcTime)\n    {\n        if (fixedUtcTime.Kind != DateTimeKind.Utc)\n            throw new ArgumentException(\"The override must be UTC.\", nameof(fixedUtcTime));\n\n        var previous = s_override.Value;\n        s_override.Value = () => fixedUtcTime;\n        return new Scope(previous);\n    }\n    private sealed class Scope : IDisposable\n    {\n        private readonly Func\u003CDateTime>? _previous;\n        private bool _disposed;\n\n        public Scope(Func\u003CDateTime>? previous)\n        {\n            _previous = previous;\n        }\n\n        public void Dispose()\n        {\n            if (_disposed)\n                return;\n\n            s_override.Value = _previous;\n            _disposed = true;\n        }\n    }\n}\n```\n\nKey trade-offs: `AsyncLocal\u003CT>` ensures parallel tests don't interfere; production cost is one null check per call; the `static readonly` field is essentially free.\n\nThree properties this pattern must keep, because each has broken a real migration:\n\n- **Scope the override and make it reversible.** Return an `IDisposable` that restores the previous value, so a test cannot leak a pinned time into the next one. A bare setter, or a manual `try`\u002F`finally` at each call site, puts that burden on every test author.\n- **Use `AsyncLocal\u003CT>`, never `[ThreadStatic]`.** `[ThreadStatic]` does not flow across `await`, so the override silently disappears mid-test.\n- **Preserve the semantics of the member you are replacing.** Substituting `DateTime.UtcNow` with a local-time source changes the `DateTimeKind` every existing caller and stored value depends on — pair `UtcNow` with `GetUtcNow()`, and `Now` with `GetLocalNow()`.\n- **Prove the test assembly can reach the override.** An `internal` override is\n  inaccessible from a separate test assembly unless the production project adds\n  the exact `InternalsVisibleTo` for that test assembly. Otherwise use an\n  already-public seam only when a public API change is authorized. Never show a\n  test calling an inaccessible member.\n\nThe same shape works for non-time statics: swap `TimeProvider.System.GetUtcNow()` for the real static call and keep the override slot, the disposable scope, and the original semantics.\n\n### Step 6: Place generated files\n\nGenerate files following the project's existing conventions:\n- If there is an `Abstractions\u002F` or `Interfaces\u002F` folder, place the interface there\n- If there is an `Infrastructure\u002F` or `Services\u002F` folder, place the implementation there\n- Otherwise, create files next to the code that uses the static\n\nAlways generate:\n1. The interface file (or adoption instructions for built-in abstractions)\n2. The default implementation file\n3. The compile-ready DI registration, applied to an existing registration\n   surface or shown at the exact composition point.\n4. A deterministic substitution example or focused test that exercises the\n   consumer without the ambient resource.\n\nSkip registration entirely on the ambient-seam path: there is no container to\nregister into, and offering one anyway is the failure mode that made a user ask\nfor the seam in the first place.\n\nBefore reporting completion, verify the delivered output contains every item\nthe prompt requested. In particular, do not summarize \"singleton registration\"\nwhen no registration code was added or shown, and do not claim testability\nwithout demonstrating how the consumer receives a fake.\nFor console wrappers, exercise the consumer with a fake that both captures the\nprompt and supplies the returned input; a build or banner-only run does not prove\nthe prompt flow.\n\n## Validation\n\n- [ ] Generated interface only wraps statics that were actually detected (not speculative)\n- [ ] Default implementation delegates to the real static with no behavior changes\n- [ ] DI registration uses `AddSingleton` for stateless wrappers, `AddTransient` for stateful ones\n- [ ] NuGet packages are recommended where established libraries exist (System.IO.Abstractions, etc.)\n- [ ] For .NET 8+, `TimeProvider` is recommended over custom `ISystemClock`\n- [ ] TimeProvider adoption includes injection, production composition,\n      `FakeTimeProvider`, its testing package, and an assertion on a\n      time-dependent result\n- [ ] HTTP adoption includes a complete fake-handler test and preserves\n      cancellation\n- [ ] On injection paths, registration or explicit composition is compile-ready,\n      and a fake demonstrates the consumer without the real ambient dependency\n- [ ] Ambient context pattern includes `AsyncLocal\u003CT>`, a scoped `IDisposable` that restores the previous value, and trade-off explanation\n- [ ] On the ambient-seam path, no `IServiceCollection` registration is proposed, the separate test assembly can reach the override, and the replaced member's return type and semantics (`UtcNow` vs `Now`, and its `DateTimeKind`) are preserved\n\n## Common Pitfalls\n\n| Pitfall | Solution |\n|---------|----------|\n| Treating \"no DI package\" as \"must be ambient\" | Inject into instantiable classes and compose explicitly; reserve Step 5 for static\u002Fsignature-preserving APIs |\n| Wrapping ALL members of a static class | Only wrap methods actually called in the codebase |\n| Custom time wrapper on .NET 8+ | Use built-in `TimeProvider` instead |\n| Custom file system wrapper | Prefer `System.IO.Abstractions` NuGet — battle-tested, complete |\n| Registering scoped when singleton suffices | Stateless wrappers should be `AddSingleton` |\n| Forgetting test helper packages | `Microsoft.Extensions.TimeProvider.Testing` for time, `System.IO.Abstractions.TestingHelpers` for filesystem |\n| Ambient context without `AsyncLocal` | Non-async `[ThreadStatic]` breaks with `async`\u002F`await` — always use `AsyncLocal\u003CT>` |\n| Showing an internal ambient override to external tests | Add the exact friend assembly or use an authorized public seam; compile the test project |\n","# Generate Testability Wrappers\n\nGenerate wrapper interfaces, default implementations, and DI service registration code for untestable static dependencies. For statics that already have .NET built-in abstractions (`TimeProvider`, `IHttpClientFactory`), guide adoption of the built-in. For statics without built-in alternatives, generate custom minimal wrappers.\n\n## When to Use\n\n- After running `detect-static-dependencies` and identifying which statics to wrap\n- When the user asks to make a class testable by replacing statics with injected abstractions\n- When adopting `TimeProvider` (.NET 8+) or `System.IO.Abstractions`\n- When creating a custom wrapper for `Environment.*`, `Console.*`, or `Process.*`\n- When a released static API needs an ambient seam because signatures cannot change\n\n## When Not to Use\n\n- The user wants to find statics first (use `detect-static-dependencies`)\n- The user wants to bulk-replace call sites (use `migrate-static-to-wrapper`)\n- The static is already behind an interface\n\nIf the target already consumes an injected interface or built-in abstraction, stop:\ndo not add a second wrapper, project, registration, or test around that seam.\n\n> A missing DI package does not by itself force an ambient seam. For an\n> instantiable class, prefer constructor injection and compose it explicitly or\n> show the requested registration. Use Step 5 when the API is static and its\n> signatures must stay static, or when the user explicitly forbids caller\n> construction\u002FDI changes.\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Static category | Yes | Which category: `time`, `filesystem`, `environment`, `network`, `console`, `process` |\n| Target framework | Yes | The `TargetFramework` from `.csproj` (affects which built-in abstractions exist) |\n| Composition | No | Existing DI framework, explicit\u002Fmanual construction, or immutable static API |\n| Namespace | No | Target namespace for generated wrapper code |\n\n## Workflow\n\n### Step 1: Determine the abstraction strategy\n\nBased on the category and target framework:\n\n| Category | .NET 8+ | .NET 6-7 | .NET Framework |\n|----------|---------|----------|----------------|\n| Time | `TimeProvider` (built-in) | `TimeProvider` via `Microsoft.Bcl.TimeProvider` NuGet | Custom `ISystemClock` |\n| File system | `System.IO.Abstractions` (NuGet) | Same | Same |\n| HTTP | `IHttpClientFactory` (built-in) | Same | Same |\n| Environment | Custom `IEnvironmentProvider` | Same | Same |\n| Console | Custom `IConsole` | Same | Same |\n| Process | Custom `IProcessRunner` | Same | Same |\n\nThe table picks *which abstraction*. How it reaches the code under test is a\nseparate axis:\n\n- instantiable class: constructor injection, even if current callers compose the\n  object manually;\n- existing container: add compile-ready registration following its conventions;\n- public static API\u002Fsignatures that cannot change: Step 5's ambient seam.\n\nCheck for a host builder, `IServiceCollection`, existing registrations, and\nconstruction sites. Do not infer \"must remain static\" merely because the project\ncurrently has no container.\n\n### Step 2: Generate built-in abstraction adoption (Time, HTTP)\n\n#### TimeProvider (.NET 8+)\n\nNo wrapper code needed. Complete all four parts: production registration,\nconstructor injection, a `FakeTimeProvider` test, and the testing package.\n\n1. Register in DI:\n```csharp\nbuilder.Services.AddSingleton(TimeProvider.System);\n```\n\n2. Inject into classes:\n```csharp\npublic class OrderProcessor(TimeProvider timeProvider)\n{\n    public bool IsExpired(Order order)\n        => timeProvider.GetUtcNow() > order.ExpiresAt;\n}\n```\n\n3. Test with `FakeTimeProvider`:\n```csharp\n\u002F\u002F Requires Microsoft.Extensions.TimeProvider.Testing NuGet\nvar fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 1, 15, 0, 0, 0, TimeSpan.Zero));\nvar processor = new OrderProcessor(fakeTime);\nfakeTime.Advance(TimeSpan.FromDays(1));\nAssert.True(processor.IsExpired(order));\n```\n\nThe assertion must prove a time-dependent result after the fake is pinned or\nadvanced. Merely constructing `FakeTimeProvider` is not a test. When the project\nhas no container but the target is an instantiable class, inject\n`TimeProvider` anyway and show explicit production construction with\n`TimeProvider.System`; do not replace it with a custom static clock.\n\nBefore calling the adoption complete, verify the repository contains or the\nanswer supplies every required artifact: the testing package reference, the\nproduction composition\u002Fregistration, every affected constructor call, and a\nrunnable fake-time test. A code fragment that omits one of those integration\npoints is guidance, not a completed adoption.\n\n#### TimeProvider (pre-.NET 8)\n\nGuide: install `Microsoft.Bcl.TimeProvider` NuGet. Same API as above.\n\n#### IHttpClientFactory\n\nNo wrapper code needed. Register a typed client via\n`builder.Services.AddHttpClient\u003CMyService>()` and inject `HttpClient` directly\ninto the class constructor. Preserve cancellation by passing the caller's token\nto the HTTP operation.\n\nFor tests, provide a complete fake `HttpMessageHandler` whose `SendAsync`\nreturns a deterministic `HttpResponseMessage`, construct `HttpClient` with that\nhandler, and exercise the typed client without network access. Compile and run\nthe focused test when the task asks for implementation; do not stop at a\nschematic handler method.\n\nWhen production uses a typed-client registration, test that same registration\npipeline: configure its primary handler in `ServiceCollection`, resolve the typed\nclient, and call it. A test that manually constructs `HttpClient` proves the class\nbut not the DI registration the task asked to adopt.\n\n### Step 3: Generate custom wrappers (Environment, Console, Process)\n\nFor categories without built-in abstractions, follow this template:\n\n#### Interface — define the minimal surface\n\nOnly include methods that were actually detected in the codebase. Do NOT generate a wrapper for every possible member — wrap only what is used.\n\nPrefer a stateless operation-shaped interface. For example, if a caller only\nneeds to start a process, wait, and return its exit code, expose one `Run`\noperation rather than a stateful wrapper that leaks `Process` lifecycle.\n\n```csharp\nnamespace \u003CNamespace>;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Abstraction over \u003Cstatic class> for testability. \n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic interface I\u003CWrapperName>\n{\n    \u002F\u002F One method per detected static call\n    \u003Creturn type> \u003CMethodName>(\u003Cparameters>);\n}\n```\n\n#### Default implementation — delegate to the real static\n\n```csharp\nnamespace \u003CNamespace>;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F Default implementation that delegates to \u003Cstatic class>.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic sealed class \u003CWrapperName> : I\u003CWrapperName>\n{\n    public \u003Creturn type> \u003CMethodName>(\u003Cparameters>)\n        => \u003CStaticClass>.\u003CMethod>(\u003Carguments>);\n}\n```\n\n#### DI registration\n\n```csharp\n\u002F\u002F In Program.cs or Startup.cs:\nbuilder.Services.AddSingleton\u003CI\u003CWrapperName>, \u003CWrapperName>>();\n```\n\nTreat registration as a deliverable, not a sentence in the summary. Add it to\nthe repository's existing registration surface when one exists. Otherwise show\nthe exact compile-ready statement and identify where the caller should place\nit. Stateless delegating wrappers are singleton; if state must be retained,\nexplain why a shorter lifetime is required.\n\n### Step 4: Generate file system wrapper adoption\n\nPrefer the established `System.IO.Abstractions` NuGet package over custom wrappers:\n\n1. Install the package:\n```\ndotnet add package System.IO.Abstractions\n```\n\n2. Register in DI:\n```csharp\nbuilder.Services.AddSingleton\u003CIFileSystem, FileSystem>();\n```\n\n3. Inject `IFileSystem` into classes:\n```csharp\npublic class ConfigLoader(IFileSystem fileSystem)\n{\n    public string LoadConfig(string path)\n        => fileSystem.File.ReadAllText(path);\n}\n```\n\n4. Test with `MockFileSystem`:\n```\ndotnet add \u003CTestProject> package System.IO.Abstractions.TestingHelpers\n```\n```csharp\nvar mockFs = new MockFileSystem(new Dictionary\u003Cstring, MockFileData>\n{\n    { \"\u002Fconfig.json\", new MockFileData(\"{\\\"key\\\": \\\"value\\\"}\") }\n});\nvar loader = new ConfigLoader(mockFs);\nAssert.Equal(\"{\\\"key\\\": \\\"value\\\"}\", loader.LoadConfig(\"\u002Fconfig.json\"));\n```\n\nPackage-first adoption is exclusive: add both package references, use\n`IFileSystem` in production, register or explicitly compose `FileSystem`, and\nseed `MockFileSystem` before exercising the consumer. Do not also generate a\nsecond custom filesystem interface, and do not present an unseeded mock whose\ntest could pass without proving the requested read\u002Fwrite behavior.\n\n### Step 5: Generate a signature-preserving ambient context\n\nUse this pattern when the API must remain static or its released signatures\ncannot accept a dependency:\n\n```csharp\npublic static class Clock\n{\n    private static readonly AsyncLocal\u003CFunc\u003CDateTime>?> s_override = new();\n    public static DateTime UtcNow\n        => s_override.Value?.Invoke() ?? TimeProvider.System.GetUtcNow().UtcDateTime;\n\n    internal static IDisposable Override(DateTime fixedUtcTime)\n    {\n        if (fixedUtcTime.Kind != DateTimeKind.Utc)\n            throw new ArgumentException(\"The override must be UTC.\", nameof(fixedUtcTime));\n\n        var previous = s_override.Value;\n        s_override.Value = () => fixedUtcTime;\n        return new Scope(previous);\n    }\n    private sealed class Scope : IDisposable\n    {\n        private readonly Func\u003CDateTime>? _previous;\n        private bool _disposed;\n\n        public Scope(Func\u003CDateTime>? previous)\n        {\n            _previous = previous;\n        }\n\n        public void Dispose()\n        {\n            if (_disposed)\n                return;\n\n            s_override.Value = _previous;\n            _disposed = true;\n        }\n    }\n}\n```\n\nKey trade-offs: `AsyncLocal\u003CT>` ensures parallel tests don't interfere; production cost is one null check per call; the `static readonly` field is essentially free.\n\nThree properties this pattern must keep, because each has broken a real migration:\n\n- **Scope the override and make it reversible.** Return an `IDisposable` that restores the previous value, so a test cannot leak a pinned time into the next one. A bare setter, or a manual `try`\u002F`finally` at each call site, puts that burden on every test author.\n- **Use `AsyncLocal\u003CT>`, never `[ThreadStatic]`.** `[ThreadStatic]` does not flow across `await`, so the override silently disappears mid-test.\n- **Preserve the semantics of the member you are replacing.** Substituting `DateTime.UtcNow` with a local-time source changes the `DateTimeKind` every existing caller and stored value depends on — pair `UtcNow` with `GetUtcNow()`, and `Now` with `GetLocalNow()`.\n- **Prove the test assembly can reach the override.** An `internal` override is\n  inaccessible from a separate test assembly unless the production project adds\n  the exact `InternalsVisibleTo` for that test assembly. Otherwise use an\n  already-public seam only when a public API change is authorized. Never show a\n  test calling an inaccessible member.\n\nThe same shape works for non-time statics: swap `TimeProvider.System.GetUtcNow()` for the real static call and keep the override slot, the disposable scope, and the original semantics.\n\n### Step 6: Place generated files\n\nGenerate files following the project's existing conventions:\n- If there is an `Abstractions\u002F` or `Interfaces\u002F` folder, place the interface there\n- If there is an `Infrastructure\u002F` or `Services\u002F` folder, place the implementation there\n- Otherwise, create files next to the code that uses the static\n\nAlways generate:\n1. The interface file (or adoption instructions for built-in abstractions)\n2. The default implementation file\n3. The compile-ready DI registration, applied to an existing registration\n   surface or shown at the exact composition point.\n4. A deterministic substitution example or focused test that exercises the\n   consumer without the ambient resource.\n\nSkip registration entirely on the ambient-seam path: there is no container to\nregister into, and offering one anyway is the failure mode that made a user ask\nfor the seam in the first place.\n\nBefore reporting completion, verify the delivered output contains every item\nthe prompt requested. In particular, do not summarize \"singleton registration\"\nwhen no registration code was added or shown, and do not claim testability\nwithout demonstrating how the consumer receives a fake.\nFor console wrappers, exercise the consumer with a fake that both captures the\nprompt and supplies the returned input; a build or banner-only run does not prove\nthe prompt flow.\n\n## Validation\n\n- [ ] Generated interface only wraps statics that were actually detected (not speculative)\n- [ ] Default implementation delegates to the real static with no behavior changes\n- [ ] DI registration uses `AddSingleton` for stateless wrappers, `AddTransient` for stateful ones\n- [ ] NuGet packages are recommended where established libraries exist (System.IO.Abstractions, etc.)\n- [ ] For .NET 8+, `TimeProvider` is recommended over custom `ISystemClock`\n- [ ] TimeProvider adoption includes injection, production composition,\n      `FakeTimeProvider`, its testing package, and an assertion on a\n      time-dependent result\n- [ ] HTTP adoption includes a complete fake-handler test and preserves\n      cancellation\n- [ ] On injection paths, registration or explicit composition is compile-ready,\n      and a fake demonstrates the consumer without the real ambient dependency\n- [ ] Ambient context pattern includes `AsyncLocal\u003CT>`, a scoped `IDisposable` that restores the previous value, and trade-off explanation\n- [ ] On the ambient-seam path, no `IServiceCollection` registration is proposed, the separate test assembly can reach the override, and the replaced member's return type and semantics (`UtcNow` vs `Now`, and its `DateTimeKind`) are preserved\n\n## Common Pitfalls\n\n| Pitfall | Solution |\n|---------|----------|\n| Treating \"no DI package\" as \"must be ambient\" | Inject into instantiable classes and compose explicitly; reserve Step 5 for static\u002Fsignature-preserving APIs |\n| Wrapping ALL members of a static class | Only wrap methods actually called in the codebase |\n| Custom time wrapper on .NET 8+ | Use built-in `TimeProvider` instead |\n| Custom file system wrapper | Prefer `System.IO.Abstractions` NuGet — battle-tested, complete |\n| Registering scoped when singleton suffices | Stateless wrappers should be `AddSingleton` |\n| Forgetting test helper packages | `Microsoft.Extensions.TimeProvider.Testing` for time, `System.IO.Abstractions.TestingHelpers` for filesystem |\n| Ambient context without `AsyncLocal` | Non-async `[ThreadStatic]` breaks with `async`\u002F`await` — always use `AsyncLocal\u003CT>` |\n| Showing an internal ambient override to external tests | Add the exact friend assembly or use an authorized public seam; compile the test project |\n",{"name":5,"license":15,"description":44},"DO NOT USE when the target already consumes an injected interface or built-in abstraction such as IFileSystem or TimeProvider, even if the request says \"generate a wrapper\"; no new wrapper is needed. Use only when C# source calls an ambient\u002Fstatic dependency and no injectable seam exists: first-time TimeProvider, IHttpClientFactory, or System.IO.Abstractions adoption; minimal Environment\u002FConsole\u002FProcess wrappers; IProcessRunner; DI registration; or an ambient seam that preserves a static API. Exclude static detection (detect-static-dependencies), migration to an existing\u002Fregistered abstraction (migrate-static-to-wrapper), one blocked behavior plus deterministic tests (testability-obstacle), and general interface design.\n","main","f8f2bfd77819bb789d4736ce7db4a567a37fcf7e",".agents\u002Fskills\u002Fgenerate-testability-wrappers",315,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-14 14:01:43",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add microsoft\u002Ftestfx --skill generate-testability-wrappers",{"tool":60,"command":61},"bunx","bunx skills add microsoft\u002Ftestfx --skill generate-testability-wrappers",{"tool":63,"command":64},"pnpm","pnpm dlx skills add microsoft\u002Ftestfx --skill generate-testability-wrappers","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fgenerate-testability-wrappers\u002FSKILL.md",true,[],[69,95,124,145,165,190],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":39},"01M70GYY0AV0JGMTHE2CN6TNYK","dotnet-breaking-changes","Use before changing .NET\u002FC# code whose callers you do not control: a NuGet library, or a repo with PublicAPI.Shipped\u002FUnshipped.txt, ApiCompat, multiple TargetFrameworks, InternalsVisibleTo, or generated (.g.cs) partials. Green builds miss silent breaks from optional parameters, parameter names, sealing, const values, nullability, and #if. USE FOR: \"without breaking existing callers\"; adding a public method, overload, optional parameter, or interface member to a shipped library; renaming a public method or an internal helper a friend assembly uses; consolidating duplicated logic; changing a signature, nullability, default, or attribute; adding a target framework; RS0016\u002FRS0017 or ApiCompat errors; \"is this PR breaking?\". For a rename, extract, or merge, also invoke csharp-refactoring. DO NOT USE FOR: an executable whose callers all live in one project; renaming a local variable or private member, even in a shipped library; formatting; framework\u002FSDK\u002FNuGet upgrades (use dotnet-upgrade).","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fdotnet-breaking-changes\u002FSKILL.md",[],[76,77,78,79,80,81,82,83,28,84,85,86,87,88,89,90,91,92,93,94],"dotnet","dotnet breaking changes","changes","green","project","default","local","errors","generated","upgrade","invoke","you","code","refactoring","merge","rename","library","live","assembly",{"skillNo":96,"name":97,"nameZh":6,"description":98,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":106,"publishedAt":39},"01M699HHE9DTZWAMPB1ZR7ZKSW","testfx-test-value-evidence","Apply the vendored test-value-evidence skill with TestFx toolchain, assertion, parallel-safety, packed-consumer, and review conventions. Use when implementing or reviewing behavior-changing TestFx PRs or commits, including source-only changes.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-test-value-evidence\u002FSKILL.md",[101],{"categoryNo":102,"categoryName":103,"taxonomyType":104,"taxonomyTypeDesc":105},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[107,108,109,110,111,112,113,114,115,116,117,36,118,22,119,120,78,121,122,123],"test","testfx test value evidence","test value evidence","evidence","value","review","parallel","toolchain","commits","implementing","prs","use","apply","with","behavior","Safety","conventions",{"skillNo":125,"name":126,"nameZh":6,"description":127,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":128,"categories":129,"keywords":130,"publishedAt":39},"01M699HEYWH1B9XGNBFY8J2FZS","testfx-parallel-safety-preflight","Check concurrency and isolation before implementing or refactoring TestFx tests, lifecycle code, helpers, shared-state declarations, or production paths those tests exercise. Use before narrowing DoNotParallelize or ResourceLock, changing parallelization settings, or introducing asynchronous workers; repeat against the final diff.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-parallel-safety-preflight\u002FSKILL.md",[],[131,122,132,113,133,134,116,135,88,136,89,137,138,139,140,141,142,143,118,144],"testfx parallel safety preflight","preflight","shared","asynchronous","exercise","concurrency","paths","isolation","helpers","diff","parallelization","lifecycle","tests","workers",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":149,"categories":150,"keywords":151,"publishedAt":39},"01M699HC61ZV9GJ2C8JV03951G","testfx-localization-validation","Validate generated TestFx localization catalogs, resource IDs, placeholders and locked tokens. Use for RESX\u002FXLF changes, OneLoc PRs, localization build failures, duplicate targets or units, and orphaned translation entries.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-localization-validation\u002FSKILL.md",[],[152,153,154,155,156,157,158,159,160,117,84,118,161,162,78,163,164],"testfx localization validation","validation","localization","validate","translation","ids","for","tokens","entries","units","duplicate","targets","build",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":172,"publishedAt":39},"01M699H9J2NWQXE4BDT6R0FH7S","testfx-acceptance-validation","Validate TestFx shipping paths and capable CI execution using packed consumers, package\u002Fcache provenance, exact exits and artifacts, and selected-versus-executed test evidence. Use for acceptance regressions, package\u002FMSBuild\u002Flauncher\u002Fprotocol changes, or skipped-only CI results; not for unrelated unit-only or documentation changes.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-acceptance-validation\u002FSKILL.md",[171],{"categoryNo":102,"categoryName":103,"taxonomyType":104,"taxonomyTypeDesc":105},[173,174,153,107,175,110,176,177,178,36,179,118,180,181,182,183,184,185,186,187,188,189,137,158],"testfx acceptance validation","acceptance","msbuild","launcher","ci","execution","package","cache","artifacts","provenance","documentation","unrelated","shipping","results","protocol","unit","ci results",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":194,"categories":195,"keywords":196,"publishedAt":39},"01M699H70BB999BT4F627T1PHE","session-delivery-hygiene","Gate local-session scope, persistent delivery, live PR state, integration provenance, and owned cleanup. Use before implementing guidance changes, changing or pushing PR work, diagnosing package\u002Foutput mismatches, or handing off implementation.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fsession-delivery-hygiene\u002FSKILL.md",[],[197,198,199,200,201,116,82,202,203,93,204,179,118,182,205,206,78,207,208,209],"session delivery hygiene","delivery","session","hygiene","scope","cleanup","work","pr","output","state","integration","guidance","gate"]