---
name: cs-optimization
description: Audits a C# application or project path for performance issues — both syntactic (sync I/O on calling thread, LINQ inefficiency, excessive allocations, missing cancellation, improper async/await, memory leaks, inefficient collections, blocking thread pool) and semantic/data-flow (per-row DB commits, per-call DbContext/options rebuild, missing keyed indexes, DbContext-pooling and transaction-retry hazards, idempotent re-syncs lacking skip-if-clean, double broadcasts, unbounded fan-out) discovered by tracing hot paths through their callees, base classes, DI registrations, and notification handlers — then delegates to the feature-planning skill to produce a self-contained, phased, agent-ready optimization plan. Does NOT execute optimizations. A single failing test in the plan's Phase 0 baseline gate is a hard stop.
---

# C# Optimization Skill

You are an expert C# performance engineer. Your job is to **audit** a project path, discover performance issues, and then invoke the **feature-planning skill** to produce
a self-contained, agent-ready optimization plan. You do not execute any optimization code yourself.

**Input:** `$ARGUMENTS` — the project path to audit (e.g. `src/MyApp`, `src/MyApp.Core`, `src/Services`).

---

## Step 0 — Validate input

Parse `$ARGUMENTS`. Extract:

- `PROJECT_PATH` — first positional argument (required). If missing or the path does not exist, abort with:
  ```
  Error: PROJECT_PATH is required. Usage: /cs-optimization <path/to/project> [additional context]
  ```
- `EXTRA_CONTEXT` — everything after the first positional argument (optional). Free-form text the caller provides about known issues, architectural decisions, or
  constraints the automated audit may not discover (e.g. "the ConfigService is a singleton loaded on every request", "the report export is known to be slow for large
  datasets"). Preserve it verbatim.
- `AUDIT_ONLY` — set to `true` if `EXTRA_CONTEXT` contains the flag `--audit-only`. When set, **stop after Step 3**
  and emit the findings summary. Do **not** invoke feature-planning. This mode is used when cs-optimization is called as a sub-audit from another optimization skill (e.g.
  avalonia-optimization) that will handle the feature-planning handoff itself.

Derive `PROJECT_NAME` from the last meaningful path segment (if last segment is `src`, use its parent).

---

## Step 1 — Detect project type

Read the `.csproj` file (s) in `PROJECT_PATH`. Classify the project:

| Signal                                                                              | Classification               |
|-------------------------------------------------------------------------------------|------------------------------|
| `<OutputType>Exe</OutputType>` or `<OutputType>WinExe</OutputType>`                 | **Console / Desktop App**    |
| `Microsoft.AspNetCore` or `Microsoft.NET.Sdk.Web` in SDK or `PackageReference`      | **ASP.NET Core Application** |
| `OutputType` is `Library` with no framework-specific hosting dependency             | **Class Library**            |
| Multiple `.csproj` files with shared `Domain`/`Application`/`Infrastructure` layers | **Multi-project Solution**   |

Record as `PROJECT_TYPE`. Scope rules per type:

- **Console / Desktop App** — out of scope: anything outside `PROJECT_PATH`, `obj/`, `bin/`
- **ASP.NET Core Application** — out of scope: anything outside `PROJECT_PATH`, `obj/`, `bin/`
- **Class Library** — out of scope: host application code, `bin/`, `obj/`
- **Multi-project Solution** — out of scope: projects not under `PROJECT_PATH`, `bin/`, `obj/`

Determine test root:

- Application: `tests/` at repo root, or `{PROJECT_PATH}.Tests/`
- Library: `{PROJECT_PATH}.Tests/` or nearest `Tests/` sibling
- Multi-project: project-local `Tests/` preferred, repo root `tests/` as fallback

Emit before continuing:

```
Project type: {PROJECT_TYPE}
Project:      {PROJECT_NAME} ({PROJECT_PATH})
Test root:    {TEST_ROOT}
```

If classification is ambiguous, state your best guess and the reason, then continue.

---

## Step 1.5 — Discover project standards & policies (mandatory)

Skip this step when `AUDIT_ONLY` is `true` — the calling skill (e.g. avalonia-optimization) discovers standards and owns the feature-planning handoff. Otherwise locate
where this project documents its coding standards, conventions, and policies. The optimization plan must not violate a single one, and the audit itself must not flag a
pattern the standards actually mandate.

1. Read the repo-root `README.md` and `AGENTS.md` (and any per-project `AGENTS.md` / `README.md` for `PROJECT_PATH`) and follow every link they make to
   standards/policy/convention documents (e.g. `docs/standards/`, `docs/policies.md`, `CONTRIBUTING.md`, a `standards/` directory).
2. If neither file names a standards location, search with `Grep`/`Glob`: `docs/standards/`, `docs/policies*.md`, `docs/conventions*.md`, `CONTRIBUTING.md`,
   `.editorconfig`, `.editorconfig`-driven analyzers, `Directory.Build.props`, `*.ruleset`, `.globalconfig`, and any file whose name contains `standard`,
   `policy`, or `convention`.
3. When the location was not explicitly declared, confirm with the developer which document (s) you believe are the project's standards before relying on them; if you
   find none, say so explicitly.
4. Read them in full and record the concrete rules as `PROJECT_STANDARDS`. Pass this to feature-planning in Step 4 so every optimization step is held against it.

---

## Step 2 — Audit the project

Systematically search `PROJECT_PATH` for every problem category below. For each hit, **read the actual file to confirm line numbers before recording**. Never approximate.

Record each finding as:

- **Category**
- **Class::Method ()** (or class name)
- **File path** (exact, relative to repo root)
- **Line range**
- **One-sentence description of the specific problem**

> **Audit method — trace the call graph, do not just grep.** The grep tables in Step 2b catch *syntactic* anti-patterns.
> The highest-value findings — per-row DB commits, redundant broadcasts, missing indexes, DbContext-pooling and
> transaction-retry hazards — are *semantic*: they only appear when you follow a hot path through its callees. For every
> entry point that runs often (hosted/background services and their timer ticks, SignalR hub methods, controller actions,
> MediatR/`INotificationHandler` handlers, and any `foreach`/`for` that runs per-item or per-row), **read the full call
> chain**, not just the entry file: the method body, the repository/service it delegates to, **that repository's base
> class** (e.g. a shared `ExecuteWithFallbackAsync`/context-acquisition helper), the **DI registration** of every
> dependency it touches, and any **handler subscribed to what it publishes**. Confirm every finding against the *current*
> code — line numbers drift and a prior audit's citations may be stale. Record hits from BOTH the semantic categories
> (Step 2a) and the syntactic tables (Step 2b).

## Step 2a — Semantic / data-flow patterns (require reading callees; greps miss these)

### Persistence & EF Core semantics

| Problem                                            | How to detect (read the call chain)                                                                                                                                                                                                                                                                                                                          |
|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Per-call `DbContext` + per-call options rebuild    | A provider/factory method doing `new DbContextOptionsBuilder<…>().UseX(...).Options` + `new TContext(options)` on **every** call. Options are constant — detect server version once, build options once, hand out **pooled** contexts (`IDbContextFactory`/`AddDbContextPool`).                                                                              |
| Per-row `SaveChanges` (missing unit-of-work batch) | A repository write method that opens its own context and `SaveChanges` per call, invoked inside a `foreach`/sweep (trace loop → service → repo → base helper). Batch to **one context + one `SaveChanges` per logical group**, preserving the loop's existing per-iteration `try/catch` isolation boundary.                                                  |
| `Migrate()` / schema ops on a **pooled** context   | A startup path resolving the context (`GetRequiredService<TContext>()`) and calling `Database.Migrate()`. If that registration is (or becomes) `AddDbContextPool`, migration must run on a **non-pooled / factory-owned** context — `Migrate()` must not run on a pooled instance.                                                                           |
| Retry strategy + user-initiated transaction        | `EnableRetryOnFailure(...)` in options **and** any `Database.BeginTransaction[Async]` not wrapped in `CreateExecutionStrategy().ExecuteAsync(...)`. Read the transaction helper: if it already wraps, the retry is safe (note it); if a repo calls `BeginTransaction` raw, flag it (`MySqlRetryingExecutionStrategy` throws on user-initiated transactions). |
| Captive/leaked scope under pooling                 | Moving a scoped `AddDbContext` to `AddDbContextPool` while a consumer mutates/holds the context across more than one logical operation, or a singleton captures it. Verify each scoped consumer does a single operation per request before pooling its path.                                                                                                 |
| O(N) scan where a keyed index belongs              | A lookup-by-id that `FirstOrDefault`/linear-scans a collection (or scans **every entry** of a large cache) per call on a hot path. Add a `Dictionary`/`ConcurrentDictionary` side-index keyed by the lookup id, maintained on load/mutate/evict; reuse it for **every** method doing that lookup (read **and** write).                                       |
| Double-scan lookup (`FirstOrDefault` + `IndexOf`)  | A method that finds an element with `FirstOrDefault(p)` then `IndexOf(result)` — two O(N) passes plus a latent `IndexOf(null)` bug. Use one indexed loop or the keyed index above.                                                                                                                                                                           |
| `Task<T>` for a synchronously-completing hot path  | A hundreds-per-second method returning `Task<T>` that actually completes synchronously (e.g. wraps a pooled factory's sync `CreateDbContext()`). Return `ValueTask<T>` to drop one `Task` allocation per call; all `await` call sites stay source-compatible.                                                                                                |
| Dead disposal plumbing                             | A type still implementing `IDisposable`/`IAsyncDisposable` (and `await Task.CompletedTask` no-ops) only to release state that a refactor removed. Drop the disposal surface so it does not imply ownership it no longer has.                                                                                                                                 |

### Redundant work & idempotency (sweeps, fan-out, notifications)

| Problem                                           | How to detect (read the call chain)                                                                                                                                                                                                                                                                                                                                                           |
|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| No skip-if-clean on an idempotent re-sync         | A periodic sweep that unconditionally writes + notifies for **every** item **every** tick even when the source is unchanged (e.g. `existing with { …, LastModified = now }` → update → broadcast, with no equality check). Compare against current state and **skip unchanged rows** so a no-change tick does zero writes and zero broadcasts. This is usually higher-leverage than batching. |
| Double broadcast / double cache-write via handler | A write path that broadcasts directly **and** publishes a notification whose `INotificationHandler`/MediatR handler broadcasts (or re-writes cache) for the **same** change. Trace the `Publish(...)` → handler. Emit once; drop the redundant publish (after confirming no other handler needs it).                                                                                          |
| Recurring full re-sync without a change watermark | A poll that re-walks the **entire** source every tick with no `since`/version/hash gate (often paired with the per-row writes above). Add a change-gate (hash or source version) so an unchanged source skips its I/O + sweep entirely.                                                                                                                                                       |
| Unbounded parallel fan-out                        | `Task.WhenAll(items.Select(async …))` over an unbounded set doing SOAP/HTTP + DB writes + broadcasts concurrently. Cap with `Parallel.ForEachAsync` + `MaxDegreeOfParallelism`; add a per-call timeout; add ±jitter if it is a timer so co-deployed instances do not beat in lockstep.                                                                                                        |
| Serial per-row await fan-out                      | A loop doing `await NotifyAsync(...)`/`await broadcaster.…(...)` per item, serializing N round-trips. Batch into one notification, or fan out without serial awaits.                                                                                                                                                                                                                          |
| Per-row secondary writes outside the batch        | New-row side-writes (mapping/audit/link rows) each opening their **own** context + `SaveChanges` inside the row loop. Fold them into the **same** unit of work as the primary batch (one `SaveChanges` flushes both).                                                                                                                                                                         |
| Self-signalling drain loop with no delay          | A background drainer that re-signals itself immediately after a full batch, spinning a thread until drained. Cap batches per wake and add a small inter-batch delay; let the periodic failsafe carry the remainder.                                                                                                                                                                           |

## Step 2b — Syntactic / grep-detectable patterns

### Async and threading patterns

| Problem                                         | How to detect                                                                                                                      |
|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `Task.Result` or `.Wait()` deadlock risk        | Grep `\.Result\b` or `\.Wait\(\)` — flag all occurrences; deadlock risk in any synchronization context                             |
| `async void` outside event handlers             | `async void` method signatures where enclosing class does not inherit `Control`/`Page`/`Window` and method is not an event handler |
| `Task.Run` wrapping already-async code          | `Task.Run(() => SomeAsync())` — redundant wrapper that wastes a thread pool thread                                                 |
| `Thread.Sleep` instead of `Task.Delay`          | Grep `Thread\.Sleep\(` in any async or service context                                                                             |
| CPU-bound work on caller thread without offload | Loops processing large collections synchronously in a method that could be awaited by a UI or web request                          |
| Missing `CancellationToken` propagation         | `async` methods accepting `CancellationToken` but callers passing `CancellationToken.None` or omitting the parameter               |
| `CancellationTokenSource` not disposed          | `new CancellationTokenSource(...)` with no `using`, `Dispose()`, or `Cancel()` in a `finally`/destructor                           |
| `Parallel.ForEach` over async work              | `Parallel.ForEach` with `async` lambda — the async work runs fire-and-forget; use `Task.WhenAll` with `Select`                     |

### Memory allocation patterns

| Problem                                                | How to detect                                                                                                                         |
|--------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| `string` concatenation in loop                         | `+=` on a `string` variable inside `for`/`foreach`/`while` — use `StringBuilder`                                                      |
| `string.Format` / interpolation for logging (hot path) | `$"..."` or `string.Format(...)` passed to a logger call that checks no enabled level — use structured logging with message templates |
| `ToList()` / `ToArray()` on intermediate LINQ          | `.ToList()` or `.ToArray()` on a LINQ chain that is immediately consumed by another LINQ operator — materialize once at the end       |
| `new List<T>()` without capacity hint for known size   | `new List<T>()` inside a loop or before an `AddRange` where the final count is known                                                  |
| Boxing value types via `object` parameter              | Value types passed to `object` parameters or non-generic collections (`ArrayList`, `Hashtable`, `DictionaryEntry`)                    |
| Large struct passed by value                           | `struct` with more than 4 fields passed as a method parameter without `in`, `ref`, or `readonly`                                      |
| `IEnumerable<T>` enumerated multiple times             | A local `IEnumerable<T>` variable used in two or more `foreach`/LINQ chains without a `ToList()`/`ToArray()` materialization first    |
| `Regex` compiled per-call                              | `new Regex(...)` inside a method body instead of a `static readonly` field (or `[GeneratedRegex]` in .NET 7+)                         |

### LINQ and collection patterns

| Problem                                               | How to detect                                                                                                  |
|-------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| `.Count() > 0` instead of `.Any()`                    | Grep `\.Count\(\)\s*[>!]=\s*0` — `.Any()` short-circuits; `.Count()` enumerates fully                          |
| `.Count` on `IEnumerable<T>` (non-collection)         | `.Count\(\)` called on a type that does not implement `ICollection<T>` — full enumeration every call           |
| N+1: query/lookup inside loop                         | `foreach`/`for` bodies containing `Dictionary.TryGetValue`, `List.Find`, `Where(...).First`, or database calls |
| `.FirstOrDefault()` then `.Value` / null check        | `.FirstOrDefault()` result used without null guard — use `.TryGetFirst()` or a null check before dereference   |
| LINQ `OrderBy` + `First` instead of `MinBy`           | `.OrderBy(x => x.Prop).First()` — use `.MinBy(x => x.Prop)` (.NET 6+) which is O(n) not O(n log n)             |
| `Distinct()` before `Where()` instead of after        | `.Distinct().Where(...)` — filter first to reduce the set before deduplication                                 |
| `Dictionary` lookup done twice                        | `ContainsKey` check followed by `dictionary[key]` — use `TryGetValue` instead                                  |
| `HashSet` / `Dictionary` not used for membership test | `List.Contains(x)` in a hot path where the collection is populated once and queried many times                 |

### I/O and resource patterns

| Problem                                         | How to detect                                                                                                       |
|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| Sync I/O on async code path                     | `File.ReadAllText`, `File.WriteAllText`, `StreamReader.ReadToEnd`, `HttpClient.Send` (non-async) in `async` methods |
| `HttpClient` instantiated per-call              | `new HttpClient(...)` inside a method body — use `IHttpClientFactory` or a static/singleton instance                |
| `IDisposable` not disposed                      | `new` of a type implementing `IDisposable` (streams, connections, writers) without `using` or explicit `Dispose()`  |
| `Stream` read without buffer                    | Reading a stream byte-by-byte or with no `BufferedStream` wrapper in a performance-sensitive path                   |
| File/DB opened and not closed on exception path | `try` block opening a resource with no `finally` or `using` to guarantee closure                                    |
| Synchronous database call in async service      | `DbContext.SaveChanges()` / `DbContext.Find()` (non-async) inside an `async Task` method                            |

### Caching patterns

| Problem                                       | How to detect                                                                                                                 |
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
| Expensive computation repeated per-call       | Methods without caching that read config, perform heavy computation, or query a data source on every invocation in a hot path |
| `IMemoryCache` / `IDistributedCache` not used | Services injected with `IConfiguration` or a repository that is called identically on every request with no cache wrapper     |
| Cache key not deterministic                   | Cache keys built from `DateTime.Now`, `Guid.NewGuid()`, or mutable objects instead of stable, input-derived values            |
| No cache expiry set                           | `IMemoryCache.Set(key, value)` without `MemoryCacheEntryOptions` specifying `AbsoluteExpiration` or `SlidingExpiration`       |
| `lock` on a shared cache dictionary           | `lock (_cache)` around a `Dictionary` used as a cache — use `ConcurrentDictionary` or `IMemoryCache`                          |

### Concurrency and thread safety patterns

| Problem                                         | How to detect                                                                                                          |
|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| `lock` on `this` or a public object             | `lock (this)` or `lock (somePublicField)` — use a private `readonly object _lock = new()`                              |
| Non-thread-safe collection used across threads  | `List<T>`, `Dictionary<TK,TV>`, or `Queue<T>` written from multiple threads without a lock or `Concurrent*` equivalent |
| `static` mutable field without synchronization  | `static` non-readonly field of a mutable type with writes in non-constructor methods and no `lock` or `Interlocked`    |
| `Lazy<T>` without thread-safety mode            | `new Lazy<T>(factory)` without specifying `LazyThreadSafetyMode` in a multi-threaded context                           |
| `volatile` used as substitute for `Interlocked` | `volatile` on a numeric field that is incremented/decremented — `volatile` does not make compound operations atomic    |

### Exception handling patterns

| Problem                                       | How to detect                                                                                                                                    |
|-----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| `catch (Exception)` swallowing all exceptions | `catch (Exception)` or `catch` blocks with empty body or only a log statement — no rethrow                                                       |
| Exception used for control flow               | `try`/`catch` wrapping `int.Parse`, `Dictionary[key]`, or `File.Open` where a `TryParse`/`TryGetValue`/`File.Exists` check would avoid the throw |
| `throw ex` instead of `throw`                 | Grep `throw \w` (non-bare throw) — resets stack trace; use bare `throw`                                                                          |
| `finally` with `return`                       | `return` statement inside a `finally` block — swallows exceptions from the `try`/`catch`                                                         |

### Dependency injection and service lifetime patterns

| Problem                                                | How to detect                                                                                            |
|--------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| Captive dependency (scoped injected into singleton)    | `Singleton` service constructor taking a parameter whose registered lifetime is `Scoped` or `Transient`  |
| `IServiceProvider` resolved inside service constructor | `provider.GetService<T>()` called in a constructor — delays resolution errors and hides dependencies     |
| `new` used for services that should be injected        | `new SomeService(...)` inside a class that is itself DI-registered — bypasses lifetime management        |
| `HttpContext` captured in singleton                    | `IHttpContextAccessor.HttpContext` stored in a field on a singleton service                              |
| Transient `IDisposable` not released                   | Transient service implementing `IDisposable` resolved via `IServiceProvider` without wrapping in a scope |

---

## Step 3 — Compile findings

Group findings by category. Count totals. Prepare this structured summary:

```
## Audit results: {PROJECT_NAME}

Project type:    {PROJECT_TYPE}
Project path:    {PROJECT_PATH}
Test root:       {TEST_ROOT}
Caller context:  {EXTRA_CONTEXT | "(none)"}

Issues found: N total
  Persistence/EF Core: N
  Redundant work:      N
  Async/Threading:     N
  Memory allocation:   N
  LINQ/Collections:    N
  I/O & Resources:     N
  Caching:             N
  Concurrency:         N
  Exception handling:  N
  DI & Lifetimes:      N

### Issues

**Persistence & EF Core**  *(semantic)*
- `ClassName::Method()` at `path/File.cs:10-25` — description
[...]

**Redundant work & idempotency**  *(semantic)*
[...]

**Async / Threading**
- `ClassName::Method()` at `path/File.cs:10-25` — description
[...]

**Memory allocation**
[...]

**LINQ / Collections**
[...]

**I/O & Resources**
[...]

**Caching**
[...]

**Concurrency**
[...]

**Exception handling**
[...]

**DI & Lifetimes**
[...]
```

Omit any category with zero findings.

---

## Step 4 — Invoke feature-planning (skipped in audit-only mode)

If `AUDIT_ONLY` is `true`, stop here. Emit the Step 3 findings summary and return — do not proceed further.

---

Hand off to the **feature-planning skill** with the full audit summary as context. Use the following as the feature description passed to feature-planning (feed it
programmatically — do not ask the user to retype it):

---

> Optimization plan for `{PROJECT_NAME}` (`{PROJECT_PATH}`).
>
> Project type: `{PROJECT_TYPE}`
>
> This is NOT a new feature — it is a performance optimization plan for an existing C# project. The plan must follow the
> standard feature-planning plan structure with these overrides:
>
> **Replace "Implementation steps" with two phases:**
>
> **Phase 0 — Baseline test coverage (mandatory, non-negotiable)**
> - Run existing test suite filtered to this project. Record all passing tests.
> - If any pre-existing failures exist, stop — they must be fixed before optimization work begins.
> - For every issue in "Issues addressed" with no existing test pinning current behavior, write an xUnit or NUnit baseline test.
> - Baseline tests must assert current (pre-optimization) behavior, not desired behavior.
> - Commit baseline tests separately before Phase 1: `test({project}): baseline tests before optimization`
> - Re-run suite. All tests including new baselines must pass before proceeding.
>
> **Phase 1 — Optimizations (one numbered step per issue)**
> - Each step: names file and method, shows exact before/after code snippet, includes a grep/search command to verify no other callers are broken.
> - After every individual step: run the test suite. A single failing test = that step is a failure. Revert and fix before continuing to the next step.
> - One PR per phase.
>
> **Hard constraints to embed in the plan:**
> 1. Every issue cites exact file path and line number range — no approximations.
> 2. Every fix includes a working code snippet using the project's own namespace and conventions.
> 3. Phase 0 is non-negotiable. No Phase 1 step ships without green baseline tests.
> 4. A single test failure after any Phase 1 step = hard stop. Revert, fix, re-run.
> 5. No new infrastructure dependencies (no new NuGet packages unless zero-dependency and widely adopted).
> 6. Do not touch files outside `{PROJECT_PATH}` except test files for code inside it.
> 7. `Task.Result` / `.Wait()` → always replace with `await`. No exceptions.
> 8. `async void` outside event handlers → always replace with `async Task`. No exceptions.
> 9. `.Count() > 0` / `.Count() != 0` → always replace with `.Any()`. No exceptions.
> 10. `new HttpClient()` per-call → always replace with `IHttpClientFactory` or a singleton. No exceptions.
> 11. Every `IDisposable` `new`-ed in a method must be wrapped in `using`. No exceptions.
> 12. `string` concatenation in a loop → always replace with `StringBuilder`. No exceptions.
> 13. `ContainsKey` + `dictionary[key]` → always replace with `TryGetValue`. No exceptions.
> 14. `lock (this)` or `lock` on a public field → always replace with a `private readonly object _lock = new()`. No exceptions.
> 15. Captive dependencies (scoped/transient injected into singleton) → fix the registration lifetime; do not change the consuming class unless the fix requires it. The
      plan step must name the DI registration file and the exact `AddSingleton`/`AddScoped`/`AddTransient` call to change.
> 16. Never recommend switching to manual memory management, unsafe code, or `stackalloc` unless the audit explicitly found a hot-path allocation in a profiler-confirmed
      bottleneck.
> 17. Never recommend partial model selects or projection-only queries as a blanket optimization — only flag if the projected result is the only consumer and the full
      model is provably unused.
> 18. Per-row `SaveChanges` inside a loop → batch to one context + one `SaveChanges` per logical group, preserving the loop's existing per-iteration `try/catch` isolation
      boundary. No exceptions.
> 19. `Database.Migrate()` / schema operations must never run on a pooled (`AddDbContextPool`) context → resolve a factory-owned / non-pooled context for the migration
      path. No exceptions.
> 20. Idempotent re-syncs must skip-if-clean → no DB write and no broadcast when the source row is unchanged from the current state. No exceptions.
> 21. A single change persists/broadcasts **once** → if a direct write already broadcasts, do not also publish a notification whose handler re-broadcasts the same change.
      The plan step must name both the write call and the handler, and confirm no other subscriber needs the notification.
> 22. Before preserving `EnableRetryOnFailure`, verify every user-initiated `BeginTransaction[Async]` is wrapped in
      `CreateExecutionStrategy().ExecuteAsync(...)`; never pair a retrying execution strategy with a raw `BeginTransaction`.
> 23. Unbounded `Task.WhenAll(items.Select(async …))` over I/O or DB work → cap with `Parallel.ForEachAsync` +
      `MaxDegreeOfParallelism`, add a per-call timeout, and add ±jitter to any fixed timer interval. No exceptions.
> 24. A synchronously-completing hot-path method returning `Task<T>` → return `ValueTask<T>` only when the call sites await-once and do not store the result; do not
      introduce `ValueTask` where a result is awaited multiple times.
>
> 25. Every optimization step must comply with the project's documented standards and policies (see below). No fix may violate a naming, structure, testing, logging,
      dependency, or formatting policy. Where an optimization would conflict with a policy, honor the policy and note the constraint on the step; if the two genuinely
      cannot be reconciled, flag it for developer review rather than shipping the violation.
>
> **Out of scope:** New infrastructure dependencies, database schema changes, files outside `{PROJECT_PATH}`.
>
> **Project standards & policies to comply with** (discovered in Step 1.5 — the plan must not violate a single rule; give these extra attention):
>
> {PROJECT_STANDARDS | "(none found — state this explicitly in the plan)"}
>
> **Caller-supplied context** (treat as authoritative — may describe issues not discoverable by static analysis):
>
> {EXTRA_CONTEXT | "(none provided)"}
>
> **Audit findings to address:**
>
> {FULL_AUDIT_SUMMARY_FROM_STEP_3}

---

The feature-planning skill handles the rest: discovers the planning directory, drafts the plan, applies review lenses, iterates with the user, and writes the final
agent-ready plan to disk.
