[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M699GGVGYXKBX5VCQETBHG5P":3,"skill-same:01M699GGVGYXKBX5VCQETBHG5P":78},{"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":28,"publishedAt":50,"linkOnly":51,"markdownContent":52,"markdownBody":53,"frontMatter":54,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M699GGVGYXKBX5VCQETBHG5P","testability-obstacle",null,"MUST USE for C#\u002F.NET deterministic tests that require the smallest production seam for DateTime\u002FTask.Delay\u002FFile\u002FEnvironment\u002FGuid\u002FRandom, static API preservation, nested\u002Fparallel overrides, or no real I\u002FO. USE ONLY when the target workspace contains C# source plus a .csproj or .sln. DO NOT USE for audits, bulk migration, code that already has an injectable seam, or an explicit migration to a user-named existing abstraction (migrate-static-to-wrapper). Use instead of general test generation when the requested test is impossible without a production edit and seam selection is still open.","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\u002Ftestability-obstacle\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49],"testability obstacle","testability","when","test","static","seam","csproj","source","datetime","bulk","deterministic","do","selection","Wrapper","only","edit","parallel","migrate","open","api","code","2026-11-10 10:12:30",false,"---\nname: testability-obstacle\ndescription: >-\n  MUST USE for C#\u002F.NET deterministic tests that require the smallest production seam for\n  DateTime\u002FTask.Delay\u002FFile\u002FEnvironment\u002FGuid\u002FRandom, static API preservation,\n  nested\u002Fparallel overrides, or no real I\u002FO. USE ONLY when the target workspace\n  contains C# source plus a .csproj or .sln. DO NOT USE for audits, bulk\n  migration, code that already has an injectable seam, or an explicit migration\n  to a user-named existing abstraction (migrate-static-to-wrapper). Use instead\n  of general test generation when the requested test is impossible without a\n  production edit and seam selection is still open.\nlicense: MIT\n---\n\n# Resolve a Testability Obstacle\n\nIntroduce the smallest behavior-preserving seam needed to test a specific C#\nbehavior, then add deterministic tests that prove both the behavior and the seam.\nThe production edit is a means to the requested test, not an invitation to\nredesign adjacent code.\n\n## When to Use\n\n- A requested test would otherwise read\u002Fwrite the real filesystem.\n- Behavior depends on the current time, delay, random value, environment, console,\n  process, or another ambient dependency.\n- The user explicitly permits or requests a safe production seam.\n- Existing tests cannot control a dependency without process-global mutation.\n\n## When Not to Use\n\n- The dependency is already injected or passed as an argument. Write tests with\n  a fake through the existing seam using `code-testing`.\n- The user wants a repository-wide testability audit. Use\n  `detect-static-dependencies`.\n- The user wants wrappers generated but not call sites\u002Ftests changed. Use\n  `generate-testability-wrappers`.\n- The user requests a broad mechanical migration. Use\n  `migrate-static-to-wrapper`, then generate tests separately.\n- The user already selected an existing replacement such as `TimeProvider` or\n  `IFileSystem` and asks to migrate call sites to it. Use\n  `migrate-static-to-wrapper`, which also updates affected tests.\n- The code is not C#\u002F.NET.\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Behavior to test | Yes | The method\u002Fworkflow and expected observable behavior |\n| Target scope | No | Discover the narrowest relevant file\u002Fproject when omitted |\n| Allowed production changes | No | Default to the minimum internal\u002Fconstructor seam |\n\n## Workflow\n\n### Step 1: Prove the obstacle\n\nRead the target production path and its existing tests. Identify the exact ambient\noperation preventing a deterministic test and the behavior that must remain\nunchanged. Do not run a repository-wide static scan for a single-class request.\n\nIf an adequate seam already exists, stop refactoring and use it. This skill adds\nno value when a fake can already be supplied.\n\n### Step 2: Select the smallest safe seam\n\nChoose by dependency and repository constraints:\n\n| Dependency | Preferred seam |\n|------------|----------------|\n| Current time \u002F timers | Inject `TimeProvider`; use `FakeTimeProvider` in tests |\n| Filesystem | Existing repository abstraction; for one write\u002Fread operation use an injected delegate when conventions allow, otherwise a one-member interface or an already accepted `System.IO.Abstractions` |\n| HTTP | Existing typed `HttpClient`\u002Fhandler or `IHttpClientFactory` seam |\n| Randomness | One final generated value: inject `Func\u003Cint>` and keep range selection in the real default; inject `Func\u003Cint, int, int>` only when range arguments are behavior the test must verify; multiple operations\u002Fstate: inject `Random` or a minimal generator interface |\n| Environment\u002Fconsole\u002Fprocess | Minimal interface containing only members used by the target |\n\nThe scoped `AsyncLocal\u003CT>` rule applies to every static API that must retain its\npublic static shape — clocks, filesystem access, environment lookups, identity\ngeneration, and randomness. The scope captures and restores the previous value;\nnever implement `Dispose()` as an unconditional assignment to `null`.\nStore the provider\u002Fvalue itself in `AsyncLocal\u003CT>`. Do not put a mutable\n`Stack\u003CT>`, list, or other shared mutable collection in the slot: child\nexecution contexts can inherit the same object and corrupt each other's nesting.\nWhen the provider itself is mutable (for example an in-memory store or fake time\nprovider), establish a fresh provider inside each parallel flow rather than\nmutating one inherited instance from a parent context.\n\nConstructor injection is the default for instance classes. Reuse the repository's\nDI and naming conventions, but do not add a DI container to a class library just\nto satisfy this workflow.\n\nPreserve the existing public construction surface unless the user authorizes an\nAPI change. Keep a public parameterless constructor as the real-dependency default\nand place a test-only delegate\u002Fprovider constructor at the narrowest visibility\nthe test project can reach. Do not turn the seam into a new public optional\nparameter merely for test convenience.\n\nFor a static class or a public API that cannot change, use a scoped ambient seam\nonly when constructor\u002Fparameter injection is impossible. The override must:\n\n- flow across `await` (`AsyncLocal\u003CT>`, not `[ThreadStatic]`);\n- return `IDisposable` and restore the previous value, including nested scopes;\n- default to the real production dependency;\n- avoid a process-global mutable fake that makes tests non-parallel.\n\nUse built-in fake-time-aware overloads instead of inventing an `IDelay` wrapper:\n\n| Ambient operation | Replacement |\n|-------------------|-------------|\n| `Task.Delay(delay, token)` | `Task.Delay(delay, timeProvider, token)` |\n| `new CancellationTokenSource(delay)` | `new CancellationTokenSource(delay, timeProvider)` |\n| `PeriodicTimer(period)` | `new PeriodicTimer(period, timeProvider)` when the target framework provides it |\n\nTest delayed behavior by starting the operation, proving it is incomplete,\nadvancing `FakeTimeProvider`, then awaiting it. For a deadline or boundary,\nadvance to immediately before the deadline and assert the task is still\nincomplete before advancing across it; an immediate post-start assertion alone\ndoes not prove the boundary. Never wait for wall-clock time.\n\nFor a nested ambient override, each scope captures the value active when it\nstarts and restores that value exactly once. Dispose scopes in LIFO order with\n`using`\u002F`finally`; never reset the slot unconditionally to `null`. Tests must\nobserve the outer value after an inner scope ends normally and, when requested,\nafter an exception unwinds the inner scope. Use distinct values so clearing the\nslot cannot accidentally pass. Also overlap independent async flows and assert\nthat each sees only its own fresh override. Do not mutate process environment\nvariables to test an environment seam.\n\n```csharp\nvar previous = s_provider.Value;\ns_provider.Value = provider;\nreturn new RestoreScope(() => s_provider.Value = previous);\n```\n\n### Step 3: Preserve behavior and API shape\n\nKeep the production change mechanical:\n\n- Wrap only members used by the target behavior.\n- Default implementations delegate directly to the original API.\n- Preserve exceptions, path handling, time zone, and `DateTime.Kind`.\n- Keep existing public signatures unless the user explicitly permits an API change.\n- Do not move business logic into the wrapper or fix unrelated production bugs.\n\nDeterministic serialized text is a deliberate exception to preserving ambient\nplatform formatting. If the user asks for exact reproducible output across\nplatforms, use the format's explicit separator (use literal `\\n` when none is\nspecified) and assert that literal content. Keep `Environment.NewLine` only\nwhen platform-native output is part of the existing contract.\n\nFor time replacements:\n\n- `DateTime.UtcNow` -> `timeProvider.GetUtcNow().UtcDateTime`\n- `DateTime.Now` -> `timeProvider.GetLocalNow().LocalDateTime`\n- `DateTimeOffset.UtcNow` -> `timeProvider.GetUtcNow()`\n- `DateTimeOffset.Now` -> `timeProvider.GetLocalNow()`\n\n### Step 4: Keep production defaults wired\n\nUpdate every composition root or constructor call affected by the seam. Production\nmust still use real time\u002Ffilesystem\u002Fetc. by default. If the project uses DI,\nregister the default implementation with the lifetime matching repository\nconventions. If it does not use DI, compose explicitly; do not introduce a\ncontainer.\nAn existing manual factory must pass the real dependency explicitly (for example,\n`new ExpirationPolicy(TimeProvider.System)`). Do not move responsibility into an\noptional constructor or add an optional provider parameter to the factory.\n\nBuild the affected production project before writing tests. A compile failure here\nis a seam problem, not a test problem.\n\n### Step 5: Write deterministic tests\n\nUse the repository's existing test project. If none exists, invoke\n`scaffold-dotnet-test-project` first.\n\nTests must supply controlled dependencies:\n\n- fixed\u002Fadvanced time rather than wall-clock waiting;\n- an in-memory fake filesystem or hand-rolled fake rather than temp\u002Freal files;\n- no environment mutation, external process, console input, or network.\n\nBefore authoring a test, inspect its test project and follow the existing framework,\nglobal-using, and assertion conventions. Use the framework packages already referenced\nby that project; never add a hand-rolled `FactAttribute`, a substitute test-framework\ntype, or unrelated test-project plumbing to make a test compile.\n\nAssert the requested business result and at least one interaction\u002Fstate observable\nthat proves the fake dependency drove the path. Include a production-default test\nonly when it can remain deterministic; never touch the real filesystem merely to\nprove the adapter delegates.\n\nCover every explicitly requested behavior and edge case. A theory or shared\nhelper may keep the suite compact, but do not drop a case to minimize test count\nor replace retained tests with a smaller set. The seam should be minimal; the\nverification should still be complete.\n\nChoose the narrowest seam that supports the behavior. A single\n`File.WriteAllText` call can be an injected `Action\u003Cstring, string>` with a real\ndefault; do not create an interface, implementation, friend-assembly setting,\nand extra project wiring unless repository conventions or multiple operations\njustify them.\n\nPreserve the public API surface as well as existing signatures. Do not add a\npublic dependency-injecting constructor solely for tests. When a class currently\nhas only its implicit public parameterless constructor and the exact test\nassembly is known, keep that constructor behavior and make the test-only\nconstructor internal; an `InternalsVisibleTo` entry is justified in this narrow\ncase because it prevents the seam from becoming public API. Prefer an existing\nrepository friend-assembly convention when one is present.\n\nDo not add `InternalsVisibleTo` when an existing public seam already accepts the\nfake or the test project can otherwise supply it. Friend-assembly access is\njustified only when the chosen minimum constructor\u002Fdelegate seam must remain\ninternal to preserve the public API and the exact test assembly is known.\n\n### Step 6: Verify the complete path\n\nRun the affected production build and the narrowest targeted test command.\nRun a repository-level test command only when the user requested broad\nvalidation, the repository contract requires that entry point, or the seam\nchanges shared composition used beyond the target. Re-read the diff and confirm:\n\n1. every production change is required by the seam;\n2. no real ambient resource is used by the new tests;\n3. current-time semantics and public behavior are preserved;\n4. existing tests were not replaced or duplicated.\n\nInspect the test summary, not only the exit code. Zero discovered tests, a build\nwithout the requested test run, or any failing\u002Ferroring test means the task is\nincomplete. Fix discovery\u002Fexecution and rerun before reporting success.\nWhen a new test does not compile, correct its imports, assertion overload, or async\ntest shape against the existing test framework before changing the production seam;\ndo not emulate missing framework APIs in source.\nFor a static ambient seam, completion requires executed tests for substitution,\nnested restoration, exception restoration when requested, and overlapping\nasync-flow isolation; production compilation alone is never sufficient. Capture\nthe passing test count or requested test names in the handoff. If no test was\ndiscovered or the output does not prove execution, correct the project\u002Ftest\nsource and rerun rather than reporting the seam as validated.\n\n## Output Contract\n\nProvide a compact `Requirement | Evidence` table. Cite the production seam,\nproduction default wiring, exact test names, and passing commands. If a package\nrestore or build blocks validation, report that blocker rather than claiming the\ntests pass.\n\n## Validation\n\n- [ ] The original obstacle was concrete and in the requested path.\n- [ ] An existing seam was reused when available.\n- [ ] The new abstraction exposes only members required by the target behavior.\n- [ ] Production defaults still delegate to the original dependency.\n- [ ] The seam did not enlarge the public API when an internal test seam was sufficient.\n- [ ] Time conversions preserve local\u002FUTC and `DateTime.Kind` semantics.\n- [ ] Static ambient overrides are async-safe, scoped, nested, and reversible.\n- [ ] New tests use fixed\u002Fin-memory dependencies and no real I\u002FO or wall clock.\n- [ ] Production build and targeted\u002Frepository tests pass with at least one requested test discovered.\n\n## Common Pitfalls\n\n| Pitfall | Corrective action |\n|---------|-------------------|\n| Refactoring before proving a blocker | Reuse an existing seam and write the test directly |\n| Wrapping an entire static API | Expose only members exercised by the target |\n| Converting `UtcNow` with `.DateTime` | Use `.UtcDateTime` to preserve `DateTimeKind.Utc` |\n| Mutable static fake shared by tests | Use constructor injection or a scoped `AsyncLocal\u003CT>` override |\n| Adding DI to a library with no container | Compose the dependency explicitly |\n| Using temp files as a shortcut | Supply an in-memory fake; the scenario requires no real I\u002FO |\n| Stopping after the refactor builds | Write and run the behavior tests that justified the seam |\n| Reporting a zero-test run as success | Fix discovery and require the requested tests to execute and pass |\n","# Resolve a Testability Obstacle\n\nIntroduce the smallest behavior-preserving seam needed to test a specific C#\nbehavior, then add deterministic tests that prove both the behavior and the seam.\nThe production edit is a means to the requested test, not an invitation to\nredesign adjacent code.\n\n## When to Use\n\n- A requested test would otherwise read\u002Fwrite the real filesystem.\n- Behavior depends on the current time, delay, random value, environment, console,\n  process, or another ambient dependency.\n- The user explicitly permits or requests a safe production seam.\n- Existing tests cannot control a dependency without process-global mutation.\n\n## When Not to Use\n\n- The dependency is already injected or passed as an argument. Write tests with\n  a fake through the existing seam using `code-testing`.\n- The user wants a repository-wide testability audit. Use\n  `detect-static-dependencies`.\n- The user wants wrappers generated but not call sites\u002Ftests changed. Use\n  `generate-testability-wrappers`.\n- The user requests a broad mechanical migration. Use\n  `migrate-static-to-wrapper`, then generate tests separately.\n- The user already selected an existing replacement such as `TimeProvider` or\n  `IFileSystem` and asks to migrate call sites to it. Use\n  `migrate-static-to-wrapper`, which also updates affected tests.\n- The code is not C#\u002F.NET.\n\n## Inputs\n\n| Input | Required | Description |\n|-------|----------|-------------|\n| Behavior to test | Yes | The method\u002Fworkflow and expected observable behavior |\n| Target scope | No | Discover the narrowest relevant file\u002Fproject when omitted |\n| Allowed production changes | No | Default to the minimum internal\u002Fconstructor seam |\n\n## Workflow\n\n### Step 1: Prove the obstacle\n\nRead the target production path and its existing tests. Identify the exact ambient\noperation preventing a deterministic test and the behavior that must remain\nunchanged. Do not run a repository-wide static scan for a single-class request.\n\nIf an adequate seam already exists, stop refactoring and use it. This skill adds\nno value when a fake can already be supplied.\n\n### Step 2: Select the smallest safe seam\n\nChoose by dependency and repository constraints:\n\n| Dependency | Preferred seam |\n|------------|----------------|\n| Current time \u002F timers | Inject `TimeProvider`; use `FakeTimeProvider` in tests |\n| Filesystem | Existing repository abstraction; for one write\u002Fread operation use an injected delegate when conventions allow, otherwise a one-member interface or an already accepted `System.IO.Abstractions` |\n| HTTP | Existing typed `HttpClient`\u002Fhandler or `IHttpClientFactory` seam |\n| Randomness | One final generated value: inject `Func\u003Cint>` and keep range selection in the real default; inject `Func\u003Cint, int, int>` only when range arguments are behavior the test must verify; multiple operations\u002Fstate: inject `Random` or a minimal generator interface |\n| Environment\u002Fconsole\u002Fprocess | Minimal interface containing only members used by the target |\n\nThe scoped `AsyncLocal\u003CT>` rule applies to every static API that must retain its\npublic static shape — clocks, filesystem access, environment lookups, identity\ngeneration, and randomness. The scope captures and restores the previous value;\nnever implement `Dispose()` as an unconditional assignment to `null`.\nStore the provider\u002Fvalue itself in `AsyncLocal\u003CT>`. Do not put a mutable\n`Stack\u003CT>`, list, or other shared mutable collection in the slot: child\nexecution contexts can inherit the same object and corrupt each other's nesting.\nWhen the provider itself is mutable (for example an in-memory store or fake time\nprovider), establish a fresh provider inside each parallel flow rather than\nmutating one inherited instance from a parent context.\n\nConstructor injection is the default for instance classes. Reuse the repository's\nDI and naming conventions, but do not add a DI container to a class library just\nto satisfy this workflow.\n\nPreserve the existing public construction surface unless the user authorizes an\nAPI change. Keep a public parameterless constructor as the real-dependency default\nand place a test-only delegate\u002Fprovider constructor at the narrowest visibility\nthe test project can reach. Do not turn the seam into a new public optional\nparameter merely for test convenience.\n\nFor a static class or a public API that cannot change, use a scoped ambient seam\nonly when constructor\u002Fparameter injection is impossible. The override must:\n\n- flow across `await` (`AsyncLocal\u003CT>`, not `[ThreadStatic]`);\n- return `IDisposable` and restore the previous value, including nested scopes;\n- default to the real production dependency;\n- avoid a process-global mutable fake that makes tests non-parallel.\n\nUse built-in fake-time-aware overloads instead of inventing an `IDelay` wrapper:\n\n| Ambient operation | Replacement |\n|-------------------|-------------|\n| `Task.Delay(delay, token)` | `Task.Delay(delay, timeProvider, token)` |\n| `new CancellationTokenSource(delay)` | `new CancellationTokenSource(delay, timeProvider)` |\n| `PeriodicTimer(period)` | `new PeriodicTimer(period, timeProvider)` when the target framework provides it |\n\nTest delayed behavior by starting the operation, proving it is incomplete,\nadvancing `FakeTimeProvider`, then awaiting it. For a deadline or boundary,\nadvance to immediately before the deadline and assert the task is still\nincomplete before advancing across it; an immediate post-start assertion alone\ndoes not prove the boundary. Never wait for wall-clock time.\n\nFor a nested ambient override, each scope captures the value active when it\nstarts and restores that value exactly once. Dispose scopes in LIFO order with\n`using`\u002F`finally`; never reset the slot unconditionally to `null`. Tests must\nobserve the outer value after an inner scope ends normally and, when requested,\nafter an exception unwinds the inner scope. Use distinct values so clearing the\nslot cannot accidentally pass. Also overlap independent async flows and assert\nthat each sees only its own fresh override. Do not mutate process environment\nvariables to test an environment seam.\n\n```csharp\nvar previous = s_provider.Value;\ns_provider.Value = provider;\nreturn new RestoreScope(() => s_provider.Value = previous);\n```\n\n### Step 3: Preserve behavior and API shape\n\nKeep the production change mechanical:\n\n- Wrap only members used by the target behavior.\n- Default implementations delegate directly to the original API.\n- Preserve exceptions, path handling, time zone, and `DateTime.Kind`.\n- Keep existing public signatures unless the user explicitly permits an API change.\n- Do not move business logic into the wrapper or fix unrelated production bugs.\n\nDeterministic serialized text is a deliberate exception to preserving ambient\nplatform formatting. If the user asks for exact reproducible output across\nplatforms, use the format's explicit separator (use literal `\\n` when none is\nspecified) and assert that literal content. Keep `Environment.NewLine` only\nwhen platform-native output is part of the existing contract.\n\nFor time replacements:\n\n- `DateTime.UtcNow` -> `timeProvider.GetUtcNow().UtcDateTime`\n- `DateTime.Now` -> `timeProvider.GetLocalNow().LocalDateTime`\n- `DateTimeOffset.UtcNow` -> `timeProvider.GetUtcNow()`\n- `DateTimeOffset.Now` -> `timeProvider.GetLocalNow()`\n\n### Step 4: Keep production defaults wired\n\nUpdate every composition root or constructor call affected by the seam. Production\nmust still use real time\u002Ffilesystem\u002Fetc. by default. If the project uses DI,\nregister the default implementation with the lifetime matching repository\nconventions. If it does not use DI, compose explicitly; do not introduce a\ncontainer.\nAn existing manual factory must pass the real dependency explicitly (for example,\n`new ExpirationPolicy(TimeProvider.System)`). Do not move responsibility into an\noptional constructor or add an optional provider parameter to the factory.\n\nBuild the affected production project before writing tests. A compile failure here\nis a seam problem, not a test problem.\n\n### Step 5: Write deterministic tests\n\nUse the repository's existing test project. If none exists, invoke\n`scaffold-dotnet-test-project` first.\n\nTests must supply controlled dependencies:\n\n- fixed\u002Fadvanced time rather than wall-clock waiting;\n- an in-memory fake filesystem or hand-rolled fake rather than temp\u002Freal files;\n- no environment mutation, external process, console input, or network.\n\nBefore authoring a test, inspect its test project and follow the existing framework,\nglobal-using, and assertion conventions. Use the framework packages already referenced\nby that project; never add a hand-rolled `FactAttribute`, a substitute test-framework\ntype, or unrelated test-project plumbing to make a test compile.\n\nAssert the requested business result and at least one interaction\u002Fstate observable\nthat proves the fake dependency drove the path. Include a production-default test\nonly when it can remain deterministic; never touch the real filesystem merely to\nprove the adapter delegates.\n\nCover every explicitly requested behavior and edge case. A theory or shared\nhelper may keep the suite compact, but do not drop a case to minimize test count\nor replace retained tests with a smaller set. The seam should be minimal; the\nverification should still be complete.\n\nChoose the narrowest seam that supports the behavior. A single\n`File.WriteAllText` call can be an injected `Action\u003Cstring, string>` with a real\ndefault; do not create an interface, implementation, friend-assembly setting,\nand extra project wiring unless repository conventions or multiple operations\njustify them.\n\nPreserve the public API surface as well as existing signatures. Do not add a\npublic dependency-injecting constructor solely for tests. When a class currently\nhas only its implicit public parameterless constructor and the exact test\nassembly is known, keep that constructor behavior and make the test-only\nconstructor internal; an `InternalsVisibleTo` entry is justified in this narrow\ncase because it prevents the seam from becoming public API. Prefer an existing\nrepository friend-assembly convention when one is present.\n\nDo not add `InternalsVisibleTo` when an existing public seam already accepts the\nfake or the test project can otherwise supply it. Friend-assembly access is\njustified only when the chosen minimum constructor\u002Fdelegate seam must remain\ninternal to preserve the public API and the exact test assembly is known.\n\n### Step 6: Verify the complete path\n\nRun the affected production build and the narrowest targeted test command.\nRun a repository-level test command only when the user requested broad\nvalidation, the repository contract requires that entry point, or the seam\nchanges shared composition used beyond the target. Re-read the diff and confirm:\n\n1. every production change is required by the seam;\n2. no real ambient resource is used by the new tests;\n3. current-time semantics and public behavior are preserved;\n4. existing tests were not replaced or duplicated.\n\nInspect the test summary, not only the exit code. Zero discovered tests, a build\nwithout the requested test run, or any failing\u002Ferroring test means the task is\nincomplete. Fix discovery\u002Fexecution and rerun before reporting success.\nWhen a new test does not compile, correct its imports, assertion overload, or async\ntest shape against the existing test framework before changing the production seam;\ndo not emulate missing framework APIs in source.\nFor a static ambient seam, completion requires executed tests for substitution,\nnested restoration, exception restoration when requested, and overlapping\nasync-flow isolation; production compilation alone is never sufficient. Capture\nthe passing test count or requested test names in the handoff. If no test was\ndiscovered or the output does not prove execution, correct the project\u002Ftest\nsource and rerun rather than reporting the seam as validated.\n\n## Output Contract\n\nProvide a compact `Requirement | Evidence` table. Cite the production seam,\nproduction default wiring, exact test names, and passing commands. If a package\nrestore or build blocks validation, report that blocker rather than claiming the\ntests pass.\n\n## Validation\n\n- [ ] The original obstacle was concrete and in the requested path.\n- [ ] An existing seam was reused when available.\n- [ ] The new abstraction exposes only members required by the target behavior.\n- [ ] Production defaults still delegate to the original dependency.\n- [ ] The seam did not enlarge the public API when an internal test seam was sufficient.\n- [ ] Time conversions preserve local\u002FUTC and `DateTime.Kind` semantics.\n- [ ] Static ambient overrides are async-safe, scoped, nested, and reversible.\n- [ ] New tests use fixed\u002Fin-memory dependencies and no real I\u002FO or wall clock.\n- [ ] Production build and targeted\u002Frepository tests pass with at least one requested test discovered.\n\n## Common Pitfalls\n\n| Pitfall | Corrective action |\n|---------|-------------------|\n| Refactoring before proving a blocker | Reuse an existing seam and write the test directly |\n| Wrapping an entire static API | Expose only members exercised by the target |\n| Converting `UtcNow` with `.DateTime` | Use `.UtcDateTime` to preserve `DateTimeKind.Utc` |\n| Mutable static fake shared by tests | Use constructor injection or a scoped `AsyncLocal\u003CT>` override |\n| Adding DI to a library with no container | Compose the dependency explicitly |\n| Using temp files as a shortcut | Supply an in-memory fake; the scenario requires no real I\u002FO |\n| Stopping after the refactor builds | Write and run the behavior tests that justified the seam |\n| Reporting a zero-test run as success | Fix discovery and require the requested tests to execute and pass |\n",{"name":5,"license":15,"description":7},"main","f8f2bfd77819bb789d4736ce7db4a567a37fcf7e",".agents\u002Fskills\u002Ftestability-obstacle",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-12 02:10:33",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add microsoft\u002Ftestfx --skill testability-obstacle",{"tool":70,"command":71},"bunx","bunx skills add microsoft\u002Ftestfx --skill testability-obstacle",{"tool":73,"command":74},"pnpm","pnpm dlx skills add microsoft\u002Ftestfx --skill testability-obstacle","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Ftestability-obstacle\u002FSKILL.md",true,[],[79,105,128,149,169,194],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":50},"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",[],[86,87,88,89,90,91,92,93,94,95,96,97,98,49,99,100,101,102,103,104],"dotnet","dotnet breaking changes","changes","green","project","default","local","errors","interface","generated","upgrade","invoke","you","refactoring","merge","rename","library","live","assembly",{"skillNo":106,"name":107,"nameZh":6,"description":108,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":112,"publishedAt":50},"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",[111],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[32,113,114,115,116,117,45,118,119,120,121,43,122,31,123,124,88,125,126,127],"testfx test value evidence","test value evidence","evidence","value","review","toolchain","commits","implementing","prs","use","apply","with","behavior","Safety","conventions",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":50},"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",[],[135,126,136,45,137,138,120,139,49,140,99,141,142,143,144,145,146,147,122,148],"testfx parallel safety preflight","preflight","shared","asynchronous","exercise","concurrency","paths","isolation","helpers","diff","parallelization","lifecycle","tests","workers",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":155,"publishedAt":50},"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",[],[156,157,158,159,160,161,162,163,164,121,95,122,165,166,88,167,168],"testfx localization validation","validation","localization","validate","translation","ids","for","tokens","entries","units","duplicate","targets","build",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":176,"publishedAt":50},"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",[175],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[177,178,157,32,179,115,180,181,182,43,183,122,184,185,186,187,188,189,190,191,192,193,141,162],"testfx acceptance validation","acceptance","msbuild","launcher","ci","execution","package","cache","artifacts","provenance","documentation","unrelated","shipping","results","protocol","unit","ci results",{"skillNo":195,"name":196,"nameZh":6,"description":197,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":198,"categories":199,"keywords":200,"publishedAt":50},"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",[],[201,202,203,204,205,120,92,206,207,103,208,183,122,186,209,210,88,211,212,213],"session delivery hygiene","delivery","session","hygiene","scope","cleanup","work","pr","output","state","integration","guidance","gate"]