{"owner":"mono","repo":"SkiaSharp","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":6580,"categories":["root-instruction","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"SkiaSharp":{"command":"npx","args":["-y","@modelcontextprotocol/server-SkiaSharp"]}}},"found":["AGENTS.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"AGENTS.md":"# SkiaSharp\n\nSkiaSharp is a cross-platform 2D graphics API for .NET wrapping Google's Skia library.\n\n**Architecture:** `C# Wrapper` -> `P/Invoke` -> `C API` -> `C++ Skia`\n**Principle:** C# validates parameters, C API trusts and passes through.\n\n---\n\n## Critical Rules (Read First)\n\nThese rules are **non-negotiable**. Violating them causes broken builds, crashes, or downstream breakage.\n\n### 1. Bootstrap First\n\nBefore C# code can build, native binaries must exist in `output/native/`. **How** you produce them depends on what you're changing:\n\n| You are changing… | Bootstrap with |\n|---|---|\n| **Only C# code** (no files under `externals/skia/`, no `DEPS`, no submodule bump) | `dotnet cake --target=externals-download` (downloads pre-built natives from the **current** milestone) |\n| **Native code, C API, `DEPS`, or the Skia submodule** (incl. milestone updates) | `dotnet cake --target=externals-{platform} --arch={arch}` — build from source. |\n\n> **🛑 If you are doing a Skia milestone update, a C API change, or anything under `externals/skia/`, STOP. Do not run `externals-download` — ever. The downloaded binaries are from the OLD milestone and do not contain your changes; using them produces silently-wrong builds and `EntryPointNotFoundException` at runtime.** When source builds fail (missing `gn`, network errors, etc.), debug the source build — do not fall back to download.\n\n### 2. Never `externals-download` After Native Changes\n\nIf you have modified **any** of the following, `externals-download` is FORBIDDEN until your changes ship to the pre-built artifact server (which only happens after merge):\n\n- `externals/skia/**` (including the submodule SHA)\n- `externals/skia/src/c/**`, `externals/skia/include/c/**`\n- `externals/skia/DEPS`\n- Any milestone bump or version file (`VERSIONS.txt`, `sk_types.h SK_C_INCREMENT`)\n\nFalling back to `externals-download` because a native build failed is the #1 way agents corrupt milestone updates. Fix the source build instead.\n\n### 3. Preserve Generated Bindings\n\nFiles matching `*.generated.cs` are auto-generated.\n\n- **NEVER** manually edit generated declarations or interop code\n- **DO** edit source-controlled `///` comment trivia directly when public API\n  prose changes, then regenerate and verify preservation\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\n\nC# `///` comments are authoritative public API prose. Managed builds generate\ncompiler XML, which packages place beside managed `lib/` and `ref` assemblies;\nthe `ref` copy is the consumer-visible surface. The external\n`mono/SkiaSharp-API-docs` repository consumes published package/XML/media inputs\nand owns ECMA/mdoc generation and Microsoft Learn publication.\n\n### 4. ABI Stability\n\nSkiaSharp maintains stable ABI. Breaking changes break downstream apps.\n\n| Allowed | Never |\n|---------|-------|\n| Add new overloads | Modify existing signatures |\n| Add new methods | Remove public APIs |\n| Add new classes | Change return types |\n\n### 5. Tests Are Mandatory\n\n**Building alone is NOT sufficient.** Run tests before claiming completion (see [Commands](#commands)).\n\n### 6. Branch Protection (COMPLIANCE REQUIRED)\n\n**Direct commits to protected branches are a policy violation.**\n\n| Repository | Protected Branches |\n|------------|-------------------|\n| SkiaSharp (parent) | `main` |\n| externals/skia (submodule) | `main`, `skiasharp` |\n\n**Required workflow:**\n\n1. **Create a feature branch FIRST** — Human-driven changes use `dev/issue-NNNN-description`\n2. **Make all commits on the feature branch** — Never commit directly to protected branches\n3. **Submit a Pull Request** — Fill in the PR template (`.github/pull_request_template.md`) completely; changes must be reviewed before merging\n\nRepository-owned automation may use a dedicated branch convention explicitly defined by its\nworkflow. It may update only branches owned by that workflow and must use `--force-with-lease`\nfor any approved force update; an unguarded `--force` remains forbidden. This never permits\ndirect commits to a protected branch.\n\n```bash\n# CORRECT — Always create a feature branch first\ngit checkout -b dev/issue-1234-fix-description\n\n# For submodule changes:\ncd externals/skia\ngit checkout -b dev/issue-1234-add-c-api\n\n# NEVER DO THIS — Policy violation\ngit checkout main && git commit  # FORBIDDEN\ngit checkout skiasharp && git commit  # FORBIDDEN (in skia submodule)\n```\n\n**This applies to BOTH repositories.** The skia submodule has its own protected branches that must be respected.\n\n**Always use the PR template.** When opening a pull request, populate every section of the repository's `.github/pull_request_template.md` — do **not** open a PR with an empty or default body. Keep the ABI-critical **Changes** and **Required skia PR** sections (write `None.` instead of deleting them), tick the relevant **Areas Affected**, describe how you verified the change under **Testing**, and attach before/after screenshots for any rendering change. The `externals/skia` submodule ships its own matching template for C API PRs.\n\nAn automated workflow may use a dedicated PR body only when the workflow owns and renders the\ncomplete body deterministically. That body must satisfy the same requirements as the repository's\ncontributor template.\n\n---\n\n## Commands\n\nSingle source of truth for all commands:\n\n| Task | Command |\n|------|---------|\n| **Bootstrap (C#-only work — see Rule #1, FORBIDDEN for native changes)** | `dotnet cake --target=externals-download` |\n| **Build Native (macOS ARM64)** | `dotnet cake --target=externals-macos --arch=arm64` |\n| **Build Native (macOS Intel)** | `dotnet cake --target=externals-macos --arch=x64` |\n| **Build Native (Windows x64)** | `dotnet cake --target=externals-windows --arch=x64` |\n| **Build Native (Linux x64)** | `dotnet cake --target=externals-linux --arch=x64` |\n| **Build Native (Linux ARM64)** | `dotnet cake --target=externals-linux --arch=arm64` |\n| **Build C#** | `dotnet build binding/SkiaSharp/SkiaSharp.csproj` |\n| **Test** | `dotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0` |\n| **Regenerate** | `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| **Regenerate release-notes (Prepare: api diffs + facts + index)** | `.agents/skills/release-notes/scripts/prepare.sh [--force] [--min-version X --max-version Y]` |\n| **Render all pages + TOC/index (offline, from committed JSON)** | `.agents/skills/release-notes/scripts/render.sh [--min-version X --max-version Y]` |\n\n### When to Use Which Bootstrap\n\n| What You Changed | Command Required |\n|------------------|------------------|\n| C# code only (`binding/SkiaSharp/*.cs`) | `externals-download` (pre-built natives) |\n| C API (`externals/skia/src/c/`, `externals/skia/include/c/`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Dependencies (`externals/skia/DEPS`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Skia submodule SHA / milestone update | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n\n> **CRITICAL:** If you modify ANY native code (C API headers/implementations), you MUST rebuild\n> the native library with `dotnet cake --target=externals-{platform}`. Using `externals-download`\n> after native changes will cause `EntryPointNotFoundException` at runtime because the downloaded\n> binaries don't contain your new functions.\n\n> **Note:** For publication, verify the exact producing Build/Tests runs and\n> BAR before the protected human approval in the MAUI release pipeline; see\n> [`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n**Recovery Commands:**\n\n| Problem | Command |\n|---------|---------|\n| Clean rebuild (**C#-only work**) | `dotnet cake --target=clean && dotnet cake --target=externals-download` |\n| Clean rebuild (**any native or milestone work**) | `dotnet cake --target=clean && dotnet cake --target=externals-{platform} --arch={arch}` |\n| Reset submodule | `git submodule update --init --recursive` |\n\n> **Native build failing?** Do **NOT** \"fall back\" to `externals-download`. Common causes: missing `gn`/`ninja`, missing depot_tools on PATH, missing network access to `chromium.googlesource.com`. Diagnose and fix the source build. Using `externals-download` to make the failure go away will produce a build that runs against stale binaries and silently corrupts milestone updates.\n\n---\n\n## Architecture & Directories\n\n### Layer Overview\n\n```\nC# Wrapper (binding/SkiaSharp/)  ->  P/Invoke  ->  C API (externals/skia/src/c/)  ->  C++ Skia\n```\n\n### Directory Guide\n\n| Directory | Editable? | Notes |\n|-----------|-----------|-------|\n| `binding/SkiaSharp/` | Yes | C# wrappers |\n| `externals/skia/src/c/` | Yes | C API implementation (our shim) |\n| `externals/skia/include/c/` | Yes | C API headers (our shim) |\n| `externals/skia/**` (other) | Conditional | Do not modify during ordinary binding work. The `update-skia` and `native-dependency-update` workflows may resolve upstream conflicts and maintain deliberate fork patches by following their dedicated audit rules. |\n| `*.generated.cs` | Documentation only | Edit `///` trivia directly, then run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` and verify preservation |\n| `documentation/api-media/` | Yes | Versioned `_DocsMedia` source; the external API-docs repo consumes the nonshipping artifact |\n| `documentation/dev/` | Yes | Architecture guides |\n| `documentation/docfx/releases/<version>.md` | No | Generated by `release-notes-render.py` — edit `_sources/<version>.prose.json` (or `.notes.md`), never the page |\n| `documentation/docfx/releases/_sources/<version>.notes.md` | Yes | Manual additions sidecar — maintainer prose that survives re-render (spec §3.7) |\n\n---\n\n## Writing Code\n\nThis section covers memory management, code patterns, and error handling together — they're tightly coupled when writing wrappers.\n\n### Step 1: Identify Pointer Type\n\n```\nIs it wrapped in sk_sp<T>?\n+- Yes -> SkRefCnt?      -> ISKReferenceCounted\n|         SkNVRefCnt<T>? -> ISKNonVirtualReferenceCounted\n+- No  -> Parameter?     -> owns: false\n          Otherwise      -> DisposeNative()\n```\n\n| Type | C++ | C# | Examples |\n|------|-----|-----|----------|\n| Raw | `T*` param | `owns: false` | Temporary refs |\n| Owned | Manual delete | `DisposeNative()` | Canvas, Paint, Path |\n| Ref-counted | `sk_sp<T>` | `ISKReferenceCounted` | Image, Shader, Surface |\n\n### Step 2: Choose Pattern\n\n**Factory method** — return null on failure, validate inputs:\n\n```csharp\npublic static SKImage FromPixels(SKImageInfo info, SKData data, int rowBytes)\n{\n    if (data == null)\n        throw new ArgumentNullException(nameof(data));\n    var cinfo = SKImageInfoNative.FromManaged(ref info);\n    return GetObject(SkiaApi.sk_image_new_raster_data(&cinfo, data.Handle, (IntPtr)rowBytes));\n}\n```\n\n**Instance method** — validate then call:\n\n```csharp\npublic void DrawRect(SKRect rect, SKPaint paint)\n{\n    if (paint == null)\n        throw new ArgumentNullException(nameof(paint));\n    SkiaApi.sk_canvas_draw_rect(Handle, &rect, paint.Handle);\n}\n```\n\n**C API** — naming convention `sk_<type>_<action>`:\n\n```cpp\nsk_image_t* sk_image_new_from_encoded(const sk_data_t* cdata) {\n    return ToImage(SkImages::DeferredFromEncodedData(sk_ref_sp(AsData(cdata))).release());\n}\n```\n\n### Step 3: Error Handling\n\n| Layer | On Failure |\n|-------|------------|\n| C API | Return `nullptr` or `false` |\n| C# Factory | Return `null` |\n| C# Constructor | Throw |\n\n### Step 4: Same-Instance Returns\n\nSome methods return the **same instance**. Always check before disposing:\n\n```csharp\n// CORRECT — always use this pattern\nvar source = GetImage();\nvar result = source.Subset(bounds);\nif (result != source)\n    source.Dispose();\nreturn result;\n```\n\n**Methods that may return same instance:** `Subset()`, `ToRasterImage()`, `ToRasterImage(false)`\n\n### API Design Rules\n\n- **Overloads, not defaults** — Default parameters break ABI\n- **Deprecate, don't remove** — Use `[Obsolete]` with migration guidance\n- **Naming:** `SK` prefix, PascalCase methods, camelCase parameters\n\n**Adding overloads (ABI-safe):**\n\n```csharp\n// Existing method (don't modify)\npublic void DrawText(string text, float x, float y, SKPaint paint)\n\n// New overload (safe to add)\npublic void DrawText(string text, SKPoint point, SKPaint paint)\n    => DrawText(text, point.X, point.Y, paint);\n```\n\n### Threading Rules\n\nSkia is **NOT thread-safe**.\n\n| Never share between threads | Safe to share (immutable) |\n|-----------------------------|---------------------------|\n| `SKCanvas`, `SKPaint`, `SKPath` | `SKImage`, `SKShader`, `SKData` |\n\n```csharp\n// Thread-safe pattern — each thread gets own Paint\nThreadLocal<SKPaint> paint = new(() => new SKPaint());\n```\n\n### Anti-Patterns (Never Do This)\n\n| Anti-Pattern | Why |\n|-------------|-----|\n| `canvas.Dispose()` while using derived objects | Crashes |\n| Sharing `SKPaint` between threads | Race conditions |\n| Modifying method signatures | ABI breaking |\n| Manual generated declaration or interop edits | Overwritten on regenerate; `///` comment trivia is preserved |\n| Using default parameters in public APIs | ABI breaking |\n| **Skipping failing tests** | **Unacceptable — tests must pass** |\n| **Using `externals-download` after C API changes** | **Causes `EntryPointNotFoundException`** |\n| Passing `fixed` pointers to native objects that outlive the block | GC moves memory -> corruption. Use `GCHandle.Alloc(Pinned)` or `Marshal.AllocCoTaskMem` |\n| Testing WASM version changes without cleaning `bin/obj/_framework` | Stale cached native `.wasm` files produce false results |\n\n---\n\n## Testing & Debugging\n\n### Running Tests\n\n```bash\ndotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0\n```\n\nThe unfiltered solution is the primary test entry point and the only final validation.\nMicrosoft.Testing.Platform treats a solution project with zero filtered matches as a failure,\nso do not apply a single-test filter to the `.slnx`. After an unfiltered solution run identifies\na failure, use that test's owning host project for filtered diagnostic iterations:\n\n| Failing host | Diagnostic project |\n|---|---|\n| Core/base | `tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj` |\n| Singleton initialization | `tests/SkiaSharp.Tests.SingletonInit.Console/SkiaSharp.Tests.SingletonInit.Console.csproj` |\n| Vulkan | `tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj` |\n| Direct3D | `tests/SkiaSharp.Direct3D.Tests.Console/SkiaSharp.Direct3D.Tests.Console.csproj` |\n\nExample:\n\n```bash\ndotnet test tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj \\\n  -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0 \\\n  -- --filter-method \"*CreateVkContextIsValid*\"\n```\n\nOnce the focused failure passes, rerun the unfiltered `.slnx`. A project-level run never\nsatisfies the final test gate.\n\n### Tests MUST Pass\n\n> **NON-NEGOTIABLE:** Tests must PASS before claiming completion.\n>\n> - Do NOT skip failing tests\n> - Do NOT claim completion if tests fail\n> - Do NOT use `SkipException` to work around failures\n>\n> **A skip must always be DECLARED, never inferred from an exception.**\n>\n> For **GPU tests** the rule is enforced by `GpuPolicy` — see\n> [documentation/dev/gpu-test-policy.md](documentation/dev/gpu-test-policy.md).\n> A backend is *required* on every platform we build it for; \"no device\", \"no\n> driver\", \"no ICD\" and \"no display\" are **failures**. Skips are owned by the\n> existing platform/host policy; an agent investigating or fixing a failure must\n> not add or expand a GPU skip. Never wrap a GPU bring-up in\n> `try/catch { Assert.Skip }`.\n>\n> For **non-GPU** tests, skipping is acceptable only for a genuine capability\n> gap that is checked explicitly (no system font manager, no XPS support, no\n> display for GTK).\n\n### Writing Tests\n\n```csharp\n[SkippableFact]\npublic void FeatureWorks()\n{\n    using var data = SKData.Create(Path.Combine(PathToImages, \"baboon.jpg\"));\n    using var image = SKImage.FromEncodedData(data);\n    Assert.NotNull(image);\n}\n```\n\n**BaseTest helpers:** `PathToImages`, `PathToFonts`, `IsWindows/Mac/Linux`\n\n**Philosophy:** Tests fail when wrong. GPU tests skip only when `GpuPolicy`\ndeclares it; other tests skip only for an explicitly checked capability gap.\n\n### Debugging Methodology\n\n1. **Establish baseline** — What's the known-good state?\n2. **One change at a time** — Verify each change before proceeding\n3. **Track changes in a table** — Log what you changed and the result\n4. **Platform differences are signals** — If X works and Y fails, the difference IS the answer\n5. **Revert if worse** — Don't pile fixes on top of failures\n\n### Failure Recognition\n\n| Error | Likely Cause | Fix |\n|-------|--------------|-----|\n| `error CS0246` (missing type) | Missing binding | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `LNK2001 unresolved external` | C API signature mismatch | Check C function names match |\n| `AccessViolationException` | Memory management bug | Check disposal patterns |\n| `NullReferenceException` | Factory returned null | Check C API return value |\n| Random crashes | Threading violation | Check Canvas/Paint thread scope |\n| **`EntryPointNotFoundException`** | **Native library not rebuilt after C API change** | **Run `dotnet cake --target=externals-{platform}`** |\n\nSee [documentation/dev/debugging-methodology.md](documentation/dev/debugging-methodology.md).\n\n---\n\n## Slash Commands\n\nCustom slash commands are available for specialized workflows. Use these for complex tasks that benefit from structured processes.\n\nRepository release branches are normally created with the **Release - Prepare**\nGitHub workflow, which runs `scripts/infra/publishing/prepare-release.ps1`. The\n`release-publish` skill queues the protected MAUI package pipeline on an\nexplicit \"push the packages\" request, then uses the **Release - Finish**\nworkflow after public-package verification and separate confirmation. Local\nscripts are fallback tools. Other release skills cover milestone maintenance\nand optional pre-publication BAR/CI package testing. See\n[`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n### When to Use Commands\n\n| Task | Command | Triggers |\n|------|---------|----------|\n| Triage issue | `/issue-triage` | \"triage #NNNN\", \"classify issue\", \"analyze issue\" |\n| Reproduce bug | `/issue-repro` | \"repro #NNNN\", \"reproduce issue\", \"create reproduction\" |\n| Fix bug | `/issue-fix` | \"investigate #NNNN\", \"fix issue\", crash, exception, segfault, \"doesn't work\" |\n| Scan/fix memory leak | `/memory-leak-fixer` | \"memory leak\", \"leak scan\", \"undisposed handle\", \"owns flag\", \"double free\", \"fix the leak\" |\n| Scan/fix performance | `/performance-fixer` | \"performance\", \"perf scan\", \"optimize\", \"make it faster\", \"hot path\", \"reduce allocations\", \"P/Invoke overhead\", \"port to managed\" |\n| Bulk process issues | `/issue-bulk-process` | \"triage these issues\", \"process issues #1 #2 #3\" |\n| Add new API | `/api-add-review` | \"expose\", \"wrap method\", issue requests new functionality |\n| Update dependency | `/native-dependency-update` | \"bump libpng\", \"fix CVE in zlib\" |\n| Write XML docs | `/api-docs` | \"document\", \"fill in missing docs\" |\n| Security check | `/security-audit` | \"audit CVEs\", \"security overview\" (read-only) |\n| Prepare release branches | `/release-branch` | \"release now\", \"start release X\" |\n| Publish packages or finish release | `/release-publish` | \"push the packages\", \"publish BAR\", \"finish release\", \"tag release\" |\n| Maintain release milestones | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\" |\n| Release notes | `/release-notes` | \"generate release notes\", \"regenerate 3.119.x\", \"write release notes for\" |\n| Skia analyst | `/skia-analyst` | \"what changed\", \"what are we missing\", \"feature gap\", \"api diff\", \"scout features\", \"diff tags\" |\n| Update Skia | `/update-skia` | \"update to milestone NNN\", \"bump Skia\" |\n| Review Skia update | `/review-skia-update` | \"review the Skia merge PR\" |\n| Merge Skia update | `/merge-skia-update` | \"merge the Skia bump\", \"land the Skia sync PRs\" |\n| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Scout GM samples | `/sample-scout` | \"find demos to port\", \"what samples are we missing\", \"gallery ideas\" |\n| Create/improve skill | `/skill-creator` | \"create a new skill\", \"improve skill X\" |\n\n### Issue Pipeline (3 steps)\n\nThe first three commands form a pipeline. Each can run standalone, but they work best in sequence:\n\n| Step | Command | Produces |\n|------|---------|----------|\n| 1 | `/issue-triage` | `ai-triage/{n}.json` |\n| 2 | `/issue-repro` | `ai-repro/{n}.json` |\n| 3 | `/issue-fix` | `ai-fix/{n}.json` + PR |\n\nSee [documentation/dev/issue-pipeline.md](documentation/dev/issue-pipeline.md) for handoff contracts and feedback loop.\n\n### Issue Classification (#NNNN)\n\n| If Issue Contains... | Type | Command |\n|---------------------|------|---------|\n| \"triage\", \"classify\", \"analyze issue\" | Triage | `/issue-triage` |\n| \"repro\", \"reproduce\", \"reproduction\" | Reproduction | `/issue-repro` |\n| \"crash\", \"exception\", \"wrong\", \"fails\", \"broken\", \"segfault\" | Bug | `/issue-fix` |\n| \"memory leak\", \"not disposed\", \"handle leak\", \"owns flag\", \"double free\" | Memory leak | `/memory-leak-fixer` |\n| \"slow\", \"performance\", \"optimize\", \"faster\", \"hot path\", \"reduce allocations\", \"interop overhead\" | Performance | `/performance-fixer` |\n| \"add\", \"expose\", \"missing API\", \"feature request\" | New API | `/api-add-review` |\n| \"API docs\", \"XML comments\", \"public comments\" | Docs | `/api-docs` |\n| CVE, security, vulnerability | Security | `/security-audit` then `/native-dependency-update` |\n\n### When NOT to Use Commands\n\nWork directly for:\n- Trivial fixes (typos, whitespace, obvious one-liners)\n- Changes only to `documentation/dev/` (non-generated docs)\n- Build/test-only tasks (no reported bug)\n- Questions about code or architecture\n- Refactoring without a reported problem\n- Performance optimization when the caller already knows the exact one-line change (otherwise use `/performance-fixer`, which proves the win with a benchmark + parity test)\n\n---\n\n## Further Reading\n\n| Topic | Document |\n|-------|----------|\n| Architecture | `documentation/dev/architecture.md` |\n| Memory Management | `documentation/dev/memory-management.md` |\n| Adding APIs | `documentation/dev/adding-apis.md` |\n| API Design | `documentation/dev/api-design.md` |\n| Error Handling | `documentation/dev/error-handling.md` |\n| Debugging | `documentation/dev/debugging-methodology.md` |\n| NuGet Packages | `documentation/dev/packages.md` |\n| Release Notes & API Diffs | `documentation/dev/release-notes-and-api-diffs.md` |\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/plugin.json)"},"files":{"AGENTS.md":"# SkiaSharp\n\nSkiaSharp is a cross-platform 2D graphics API for .NET wrapping Google's Skia library.\n\n**Architecture:** `C# Wrapper` -> `P/Invoke` -> `C API` -> `C++ Skia`\n**Principle:** C# validates parameters, C API trusts and passes through.\n\n---\n\n## Critical Rules (Read First)\n\nThese rules are **non-negotiable**. Violating them causes broken builds, crashes, or downstream breakage.\n\n### 1. Bootstrap First\n\nBefore C# code can build, native binaries must exist in `output/native/`. **How** you produce them depends on what you're changing:\n\n| You are changing… | Bootstrap with |\n|---|---|\n| **Only C# code** (no files under `externals/skia/`, no `DEPS`, no submodule bump) | `dotnet cake --target=externals-download` (downloads pre-built natives from the **current** milestone) |\n| **Native code, C API, `DEPS`, or the Skia submodule** (incl. milestone updates) | `dotnet cake --target=externals-{platform} --arch={arch}` — build from source. |\n\n> **🛑 If you are doing a Skia milestone update, a C API change, or anything under `externals/skia/`, STOP. Do not run `externals-download` — ever. The downloaded binaries are from the OLD milestone and do not contain your changes; using them produces silently-wrong builds and `EntryPointNotFoundException` at runtime.** When source builds fail (missing `gn`, network errors, etc.), debug the source build — do not fall back to download.\n\n### 2. Never `externals-download` After Native Changes\n\nIf you have modified **any** of the following, `externals-download` is FORBIDDEN until your changes ship to the pre-built artifact server (which only happens after merge):\n\n- `externals/skia/**` (including the submodule SHA)\n- `externals/skia/src/c/**`, `externals/skia/include/c/**`\n- `externals/skia/DEPS`\n- Any milestone bump or version file (`VERSIONS.txt`, `sk_types.h SK_C_INCREMENT`)\n\nFalling back to `externals-download` because a native build failed is the #1 way agents corrupt milestone updates. Fix the source build instead.\n\n### 3. Preserve Generated Bindings\n\nFiles matching `*.generated.cs` are auto-generated.\n\n- **NEVER** manually edit generated declarations or interop code\n- **DO** edit source-controlled `///` comment trivia directly when public API\n  prose changes, then regenerate and verify preservation\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\n\nC# `///` comments are authoritative public API prose. Managed builds generate\ncompiler XML, which packages place beside managed `lib/` and `ref` assemblies;\nthe `ref` copy is the consumer-visible surface. The external\n`mono/SkiaSharp-API-docs` repository consumes published package/XML/media inputs\nand owns ECMA/mdoc generation and Microsoft Learn publication.\n\n### 4. ABI Stability\n\nSkiaSharp maintains stable ABI. Breaking changes break downstream apps.\n\n| Allowed | Never |\n|---------|-------|\n| Add new overloads | Modify existing signatures |\n| Add new methods | Remove public APIs |\n| Add new classes | Change return types |\n\n### 5. Tests Are Mandatory\n\n**Building alone is NOT sufficient.** Run tests before claiming completion (see [Commands](#commands)).\n\n### 6. Branch Protection (COMPLIANCE REQUIRED)\n\n**Direct commits to protected branches are a policy violation.**\n\n| Repository | Protected Branches |\n|------------|-------------------|\n| SkiaSharp (parent) | `main` |\n| externals/skia (submodule) | `main`, `skiasharp` |\n\n**Required workflow:**\n\n1. **Create a feature branch FIRST** — Human-driven changes use `dev/issue-NNNN-description`\n2. **Make all commits on the feature branch** — Never commit directly to protected branches\n3. **Submit a Pull Request** — Fill in the PR template (`.github/pull_request_template.md`) completely; changes must be reviewed before merging\n\nRepository-owned automation may use a dedicated branch convention explicitly defined by its\nworkflow. It may update only branches owned by that workflow and must use `--force-with-lease`\nfor any approved force update; an unguarded `--force` remains forbidden. This never permits\ndirect commits to a protected branch.\n\n```bash\n# CORRECT — Always create a feature branch first\ngit checkout -b dev/issue-1234-fix-description\n\n# For submodule changes:\ncd externals/skia\ngit checkout -b dev/issue-1234-add-c-api\n\n# NEVER DO THIS — Policy violation\ngit checkout main && git commit  # FORBIDDEN\ngit checkout skiasharp && git commit  # FORBIDDEN (in skia submodule)\n```\n\n**This applies to BOTH repositories.** The skia submodule has its own protected branches that must be respected.\n\n**Always use the PR template.** When opening a pull request, populate every section of the repository's `.github/pull_request_template.md` — do **not** open a PR with an empty or default body. Keep the ABI-critical **Changes** and **Required skia PR** sections (write `None.` instead of deleting them), tick the relevant **Areas Affected**, describe how you verified the change under **Testing**, and attach before/after screenshots for any rendering change. The `externals/skia` submodule ships its own matching template for C API PRs.\n\nAn automated workflow may use a dedicated PR body only when the workflow owns and renders the\ncomplete body deterministically. That body must satisfy the same requirements as the repository's\ncontributor template.\n\n---\n\n## Commands\n\nSingle source of truth for all commands:\n\n| Task | Command |\n|------|---------|\n| **Bootstrap (C#-only work — see Rule #1, FORBIDDEN for native changes)** | `dotnet cake --target=externals-download` |\n| **Build Native (macOS ARM64)** | `dotnet cake --target=externals-macos --arch=arm64` |\n| **Build Native (macOS Intel)** | `dotnet cake --target=externals-macos --arch=x64` |\n| **Build Native (Windows x64)** | `dotnet cake --target=externals-windows --arch=x64` |\n| **Build Native (Linux x64)** | `dotnet cake --target=externals-linux --arch=x64` |\n| **Build Native (Linux ARM64)** | `dotnet cake --target=externals-linux --arch=arm64` |\n| **Build C#** | `dotnet build binding/SkiaSharp/SkiaSharp.csproj` |\n| **Test** | `dotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0` |\n| **Regenerate** | `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| **Regenerate release-notes (Prepare: api diffs + facts + index)** | `.agents/skills/release-notes/scripts/prepare.sh [--force] [--min-version X --max-version Y]` |\n| **Render all pages + TOC/index (offline, from committed JSON)** | `.agents/skills/release-notes/scripts/render.sh [--min-version X --max-version Y]` |\n\n### When to Use Which Bootstrap\n\n| What You Changed | Command Required |\n|------------------|------------------|\n| C# code only (`binding/SkiaSharp/*.cs`) | `externals-download` (pre-built natives) |\n| C API (`externals/skia/src/c/`, `externals/skia/include/c/`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Dependencies (`externals/skia/DEPS`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Skia submodule SHA / milestone update | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n\n> **CRITICAL:** If you modify ANY native code (C API headers/implementations), you MUST rebuild\n> the native library with `dotnet cake --target=externals-{platform}`. Using `externals-download`\n> after native changes will cause `EntryPointNotFoundException` at runtime because the downloaded\n> binaries don't contain your new functions.\n\n> **Note:** For publication, verify the exact producing Build/Tests runs and\n> BAR before the protected human approval in the MAUI release pipeline; see\n> [`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n**Recovery Commands:**\n\n| Problem | Command |\n|---------|---------|\n| Clean rebuild (**C#-only work**) | `dotnet cake --target=clean && dotnet cake --target=externals-download` |\n| Clean rebuild (**any native or milestone work**) | `dotnet cake --target=clean && dotnet cake --target=externals-{platform} --arch={arch}` |\n| Reset submodule | `git submodule update --init --recursive` |\n\n> **Native build failing?** Do **NOT** \"fall back\" to `externals-download`. Common causes: missing `gn`/`ninja`, missing depot_tools on PATH, missing network access to `chromium.googlesource.com`. Diagnose and fix the source build. Using `externals-download` to make the failure go away will produce a build that runs against stale binaries and silently corrupts milestone updates.\n\n---\n\n## Architecture & Directories\n\n### Layer Overview\n\n```\nC# Wrapper (binding/SkiaSharp/)  ->  P/Invoke  ->  C API (externals/skia/src/c/)  ->  C++ Skia\n```\n\n### Directory Guide\n\n| Directory | Editable? | Notes |\n|-----------|-----------|-------|\n| `binding/SkiaSharp/` | Yes | C# wrappers |\n| `externals/skia/src/c/` | Yes | C API implementation (our shim) |\n| `externals/skia/include/c/` | Yes | C API headers (our shim) |\n| `externals/skia/**` (other) | Conditional | Do not modify during ordinary binding work. The `update-skia` and `native-dependency-update` workflows may resolve upstream conflicts and maintain deliberate fork patches by following their dedicated audit rules. |\n| `*.generated.cs` | Documentation only | Edit `///` trivia directly, then run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` and verify preservation |\n| `documentation/api-media/` | Yes | Versioned `_DocsMedia` source; the external API-docs repo consumes the nonshipping artifact |\n| `documentation/dev/` | Yes | Architecture guides |\n| `documentation/docfx/releases/<version>.md` | No | Generated by `release-notes-render.py` — edit `_sources/<version>.prose.json` (or `.notes.md`), never the page |\n| `documentation/docfx/releases/_sources/<version>.notes.md` | Yes | Manual additions sidecar — maintainer prose that survives re-render (spec §3.7) |\n\n---\n\n## Writing Code\n\nThis section covers memory management, code patterns, and error handling together — they're tightly coupled when writing wrappers.\n\n### Step 1: Identify Pointer Type\n\n```\nIs it wrapped in sk_sp<T>?\n+- Yes -> SkRefCnt?      -> ISKReferenceCounted\n|         SkNVRefCnt<T>? -> ISKNonVirtualReferenceCounted\n+- No  -> Parameter?     -> owns: false\n          Otherwise      -> DisposeNative()\n```\n\n| Type | C++ | C# | Examples |\n|------|-----|-----|----------|\n| Raw | `T*` param | `owns: false` | Temporary refs |\n| Owned | Manual delete | `DisposeNative()` | Canvas, Paint, Path |\n| Ref-counted | `sk_sp<T>` | `ISKReferenceCounted` | Image, Shader, Surface |\n\n### Step 2: Choose Pattern\n\n**Factory method** — return null on failure, validate inputs:\n\n```csharp\npublic static SKImage FromPixels(SKImageInfo info, SKData data, int rowBytes)\n{\n    if (data == null)\n        throw new ArgumentNullException(nameof(data));\n    var cinfo = SKImageInfoNative.FromManaged(ref info);\n    return GetObject(SkiaApi.sk_image_new_raster_data(&cinfo, data.Handle, (IntPtr)rowBytes));\n}\n```\n\n**Instance method** — validate then call:\n\n```csharp\npublic void DrawRect(SKRect rect, SKPaint paint)\n{\n    if (paint == null)\n        throw new ArgumentNullException(nameof(paint));\n    SkiaApi.sk_canvas_draw_rect(Handle, &rect, paint.Handle);\n}\n```\n\n**C API** — naming convention `sk_<type>_<action>`:\n\n```cpp\nsk_image_t* sk_image_new_from_encoded(const sk_data_t* cdata) {\n    return ToImage(SkImages::DeferredFromEncodedData(sk_ref_sp(AsData(cdata))).release());\n}\n```\n\n### Step 3: Error Handling\n\n| Layer | On Failure |\n|-------|------------|\n| C API | Return `nullptr` or `false` |\n| C# Factory | Return `null` |\n| C# Constructor | Throw |\n\n### Step 4: Same-Instance Returns\n\nSome methods return the **same instance**. Always check before disposing:\n\n```csharp\n// CORRECT — always use this pattern\nvar source = GetImage();\nvar result = source.Subset(bounds);\nif (result != source)\n    source.Dispose();\nreturn result;\n```\n\n**Methods that may return same instance:** `Subset()`, `ToRasterImage()`, `ToRasterImage(false)`\n\n### API Design Rules\n\n- **Overloads, not defaults** — Default parameters break ABI\n- **Deprecate, don't remove** — Use `[Obsolete]` with migration guidance\n- **Naming:** `SK` prefix, PascalCase methods, camelCase parameters\n\n**Adding overloads (ABI-safe):**\n\n```csharp\n// Existing method (don't modify)\npublic void DrawText(string text, float x, float y, SKPaint paint)\n\n// New overload (safe to add)\npublic void DrawText(string text, SKPoint point, SKPaint paint)\n    => DrawText(text, point.X, point.Y, paint);\n```\n\n### Threading Rules\n\nSkia is **NOT thread-safe**.\n\n| Never share between threads | Safe to share (immutable) |\n|-----------------------------|---------------------------|\n| `SKCanvas`, `SKPaint`, `SKPath` | `SKImage`, `SKShader`, `SKData` |\n\n```csharp\n// Thread-safe pattern — each thread gets own Paint\nThreadLocal<SKPaint> paint = new(() => new SKPaint());\n```\n\n### Anti-Patterns (Never Do This)\n\n| Anti-Pattern | Why |\n|-------------|-----|\n| `canvas.Dispose()` while using derived objects | Crashes |\n| Sharing `SKPaint` between threads | Race conditions |\n| Modifying method signatures | ABI breaking |\n| Manual generated declaration or interop edits | Overwritten on regenerate; `///` comment trivia is preserved |\n| Using default parameters in public APIs | ABI breaking |\n| **Skipping failing tests** | **Unacceptable — tests must pass** |\n| **Using `externals-download` after C API changes** | **Causes `EntryPointNotFoundException`** |\n| Passing `fixed` pointers to native objects that outlive the block | GC moves memory -> corruption. Use `GCHandle.Alloc(Pinned)` or `Marshal.AllocCoTaskMem` |\n| Testing WASM version changes without cleaning `bin/obj/_framework` | Stale cached native `.wasm` files produce false results |\n\n---\n\n## Testing & Debugging\n\n### Running Tests\n\n```bash\ndotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0\n```\n\nThe unfiltered solution is the primary test entry point and the only final validation.\nMicrosoft.Testing.Platform treats a solution project with zero filtered matches as a failure,\nso do not apply a single-test filter to the `.slnx`. After an unfiltered solution run identifies\na failure, use that test's owning host project for filtered diagnostic iterations:\n\n| Failing host | Diagnostic project |\n|---|---|\n| Core/base | `tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj` |\n| Singleton initialization | `tests/SkiaSharp.Tests.SingletonInit.Console/SkiaSharp.Tests.SingletonInit.Console.csproj` |\n| Vulkan | `tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj` |\n| Direct3D | `tests/SkiaSharp.Direct3D.Tests.Console/SkiaSharp.Direct3D.Tests.Console.csproj` |\n\nExample:\n\n```bash\ndotnet test tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj \\\n  -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0 \\\n  -- --filter-method \"*CreateVkContextIsValid*\"\n```\n\nOnce the focused failure passes, rerun the unfiltered `.slnx`. A project-level run never\nsatisfies the final test gate.\n\n### Tests MUST Pass\n\n> **NON-NEGOTIABLE:** Tests must PASS before claiming completion.\n>\n> - Do NOT skip failing tests\n> - Do NOT claim completion if tests fail\n> - Do NOT use `SkipException` to work around failures\n>\n> **A skip must always be DECLARED, never inferred from an exception.**\n>\n> For **GPU tests** the rule is enforced by `GpuPolicy` — see\n> [documentation/dev/gpu-test-policy.md](documentation/dev/gpu-test-policy.md).\n> A backend is *required* on every platform we build it for; \"no device\", \"no\n> driver\", \"no ICD\" and \"no display\" are **failures**. Skips are owned by the\n> existing platform/host policy; an agent investigating or fixing a failure must\n> not add or expand a GPU skip. Never wrap a GPU bring-up in\n> `try/catch { Assert.Skip }`.\n>\n> For **non-GPU** tests, skipping is acceptable only for a genuine capability\n> gap that is checked explicitly (no system font manager, no XPS support, no\n> display for GTK).\n\n### Writing Tests\n\n```csharp\n[SkippableFact]\npublic void FeatureWorks()\n{\n    using var data = SKData.Create(Path.Combine(PathToImages, \"baboon.jpg\"));\n    using var image = SKImage.FromEncodedData(data);\n    Assert.NotNull(image);\n}\n```\n\n**BaseTest helpers:** `PathToImages`, `PathToFonts`, `IsWindows/Mac/Linux`\n\n**Philosophy:** Tests fail when wrong. GPU tests skip only when `GpuPolicy`\ndeclares it; other tests skip only for an explicitly checked capability gap.\n\n### Debugging Methodology\n\n1. **Establish baseline** — What's the known-good state?\n2. **One change at a time** — Verify each change before proceeding\n3. **Track changes in a table** — Log what you changed and the result\n4. **Platform differences are signals** — If X works and Y fails, the difference IS the answer\n5. **Revert if worse** — Don't pile fixes on top of failures\n\n### Failure Recognition\n\n| Error | Likely Cause | Fix |\n|-------|--------------|-----|\n| `error CS0246` (missing type) | Missing binding | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `LNK2001 unresolved external` | C API signature mismatch | Check C function names match |\n| `AccessViolationException` | Memory management bug | Check disposal patterns |\n| `NullReferenceException` | Factory returned null | Check C API return value |\n| Random crashes | Threading violation | Check Canvas/Paint thread scope |\n| **`EntryPointNotFoundException`** | **Native library not rebuilt after C API change** | **Run `dotnet cake --target=externals-{platform}`** |\n\nSee [documentation/dev/debugging-methodology.md](documentation/dev/debugging-methodology.md).\n\n---\n\n## Slash Commands\n\nCustom slash commands are available for specialized workflows. Use these for complex tasks that benefit from structured processes.\n\nRepository release branches are normally created with the **Release - Prepare**\nGitHub workflow, which runs `scripts/infra/publishing/prepare-release.ps1`. The\n`release-publish` skill queues the protected MAUI package pipeline on an\nexplicit \"push the packages\" request, then uses the **Release - Finish**\nworkflow after public-package verification and separate confirmation. Local\nscripts are fallback tools. Other release skills cover milestone maintenance\nand optional pre-publication BAR/CI package testing. See\n[`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n### When to Use Commands\n\n| Task | Command | Triggers |\n|------|---------|----------|\n| Triage issue | `/issue-triage` | \"triage #NNNN\", \"classify issue\", \"analyze issue\" |\n| Reproduce bug | `/issue-repro` | \"repro #NNNN\", \"reproduce issue\", \"create reproduction\" |\n| Fix bug | `/issue-fix` | \"investigate #NNNN\", \"fix issue\", crash, exception, segfault, \"doesn't work\" |\n| Scan/fix memory leak | `/memory-leak-fixer` | \"memory leak\", \"leak scan\", \"undisposed handle\", \"owns flag\", \"double free\", \"fix the leak\" |\n| Scan/fix performance | `/performance-fixer` | \"performance\", \"perf scan\", \"optimize\", \"make it faster\", \"hot path\", \"reduce allocations\", \"P/Invoke overhead\", \"port to managed\" |\n| Bulk process issues | `/issue-bulk-process` | \"triage these issues\", \"process issues #1 #2 #3\" |\n| Add new API | `/api-add-review` | \"expose\", \"wrap method\", issue requests new functionality |\n| Update dependency | `/native-dependency-update` | \"bump libpng\", \"fix CVE in zlib\" |\n| Write XML docs | `/api-docs` | \"document\", \"fill in missing docs\" |\n| Security check | `/security-audit` | \"audit CVEs\", \"security overview\" (read-only) |\n| Prepare release branches | `/release-branch` | \"release now\", \"start release X\" |\n| Publish packages or finish release | `/release-publish` | \"push the packages\", \"publish BAR\", \"finish release\", \"tag release\" |\n| Maintain release milestones | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\" |\n| Release notes | `/release-notes` | \"generate release notes\", \"regenerate 3.119.x\", \"write release notes for\" |\n| Skia analyst | `/skia-analyst` | \"what changed\", \"what are we missing\", \"feature gap\", \"api diff\", \"scout features\", \"diff tags\" |\n| Update Skia | `/update-skia` | \"update to milestone NNN\", \"bump Skia\" |\n| Review Skia update | `/review-skia-update` | \"review the Skia merge PR\" |\n| Merge Skia update | `/merge-skia-update` | \"merge the Skia bump\", \"land the Skia sync PRs\" |\n| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Scout GM samples | `/sample-scout` | \"find demos to port\", \"what samples are we missing\", \"gallery ideas\" |\n| Create/improve skill | `/skill-creator` | \"create a new skill\", \"improve skill X\" |\n\n### Issue Pipeline (3 steps)\n\nThe first three commands form a pipeline. Each can run standalone, but they work best in sequence:\n\n| Step | Command | Produces |\n|------|---------|----------|\n| 1 | `/issue-triage` | `ai-triage/{n}.json` |\n| 2 | `/issue-repro` | `ai-repro/{n}.json` |\n| 3 | `/issue-fix` | `ai-fix/{n}.json` + PR |\n\nSee [documentation/dev/issue-pipeline.md](documentation/dev/issue-pipeline.md) for handoff contracts and feedback loop.\n\n### Issue Classification (#NNNN)\n\n| If Issue Contains... | Type | Command |\n|---------------------|------|---------|\n| \"triage\", \"classify\", \"analyze issue\" | Triage | `/issue-triage` |\n| \"repro\", \"reproduce\", \"reproduction\" | Reproduction | `/issue-repro` |\n| \"crash\", \"exception\", \"wrong\", \"fails\", \"broken\", \"segfault\" | Bug | `/issue-fix` |\n| \"memory leak\", \"not disposed\", \"handle leak\", \"owns flag\", \"double free\" | Memory leak | `/memory-leak-fixer` |\n| \"slow\", \"performance\", \"optimize\", \"faster\", \"hot path\", \"reduce allocations\", \"interop overhead\" | Performance | `/performance-fixer` |\n| \"add\", \"expose\", \"missing API\", \"feature request\" | New API | `/api-add-review` |\n| \"API docs\", \"XML comments\", \"public comments\" | Docs | `/api-docs` |\n| CVE, security, vulnerability | Security | `/security-audit` then `/native-dependency-update` |\n\n### When NOT to Use Commands\n\nWork directly for:\n- Trivial fixes (typos, whitespace, obvious one-liners)\n- Changes only to `documentation/dev/` (non-generated docs)\n- Build/test-only tasks (no reported bug)\n- Questions about code or architecture\n- Refactoring without a reported problem\n- Performance optimization when the caller already knows the exact one-line change (otherwise use `/performance-fixer`, which proves the win with a benchmark + parity test)\n\n---\n\n## Further Reading\n\n| Topic | Document |\n|-------|----------|\n| Architecture | `documentation/dev/architecture.md` |\n| Memory Management | `documentation/dev/memory-management.md` |\n| Adding APIs | `documentation/dev/adding-apis.md` |\n| API Design | `documentation/dev/api-design.md` |\n| Error Handling | `documentation/dev/error-handling.md` |\n| Debugging | `documentation/dev/debugging-methodology.md` |\n| NuGet Packages | `documentation/dev/packages.md` |\n| Release Notes & API Diffs | `documentation/dev/release-notes-and-api-diffs.md` |\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# SkiaSharp\n\nSkiaSharp is a cross-platform 2D graphics API for .NET wrapping Google's Skia library.\n\n**Architecture:** `C# Wrapper` -> `P/Invoke` -> `C API` -> `C++ Skia`\n**Principle:** C# validates parameters, C API trusts and passes through.\n\n---\n\n## Critical Rules (Read First)\n\nThese rules are **non-negotiable**. Violating them causes broken builds, crashes, or downstream breakage.\n\n### 1. Bootstrap First\n\nBefore C# code can build, native binaries must exist in `output/native/`. **How** you produce them depends on what you're changing:\n\n| You are changing… | Bootstrap with |\n|---|---|\n| **Only C# code** (no files under `externals/skia/`, no `DEPS`, no submodule bump) | `dotnet cake --target=externals-download` (downloads pre-built natives from the **current** milestone) |\n| **Native code, C API, `DEPS`, or the Skia submodule** (incl. milestone updates) | `dotnet cake --target=externals-{platform} --arch={arch}` — build from source. |\n\n> **🛑 If you are doing a Skia milestone update, a C API change, or anything under `externals/skia/`, STOP. Do not run `externals-download` — ever. The downloaded binaries are from the OLD milestone and do not contain your changes; using them produces silently-wrong builds and `EntryPointNotFoundException` at runtime.** When source builds fail (missing `gn`, network errors, etc.), debug the source build — do not fall back to download.\n\n### 2. Never `externals-download` After Native Changes\n\nIf you have modified **any** of the following, `externals-download` is FORBIDDEN until your changes ship to the pre-built artifact server (which only happens after merge):\n\n- `externals/skia/**` (including the submodule SHA)\n- `externals/skia/src/c/**`, `externals/skia/include/c/**`\n- `externals/skia/DEPS`\n- Any milestone bump or version file (`VERSIONS.txt`, `sk_types.h SK_C_INCREMENT`)\n\nFalling back to `externals-download` because a native build failed is the #1 way agents corrupt milestone updates. Fix the source build instead.\n\n### 3. Preserve Generated Bindings\n\nFiles matching `*.generated.cs` are auto-generated.\n\n- **NEVER** manually edit generated declarations or interop code\n- **DO** edit source-controlled `///` comment trivia directly when public API\n  prose changes, then regenerate and verify preservation\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\n\nC# `///` comments are authoritative public API prose. Managed builds generate\ncompiler XML, which packages place beside managed `lib/` and `ref` assemblies;\nthe `ref` copy is the consumer-visible surface. The external\n`mono/SkiaSharp-API-docs` repository consumes published package/XML/media inputs\nand owns ECMA/mdoc generation and Microsoft Learn publication.\n\n### 4. ABI Stability\n\nSkiaSharp maintains stable ABI. Breaking changes break downstream apps.\n\n| Allowed | Never |\n|---------|-------|\n| Add new overloads | Modify existing signatures |\n| Add new methods | Remove public APIs |\n| Add new classes | Change return types |\n\n### 5. Tests Are Mandatory\n\n**Building alone is NOT sufficient.** Run tests before claiming completion (see [Commands](#commands)).\n\n### 6. Branch Protection (COMPLIANCE REQUIRED)\n\n**Direct commits to protected branches are a policy violation.**\n\n| Repository | Protected Branches |\n|------------|-------------------|\n| SkiaSharp (parent) | `main` |\n| externals/skia (submodule) | `main`, `skiasharp` |\n\n**Required workflow:**\n\n1. **Create a feature branch FIRST** — Human-driven changes use `dev/issue-NNNN-description`\n2. **Make all commits on the feature branch** — Never commit directly to protected branches\n3. **Submit a Pull Request** — Fill in the PR template (`.github/pull_request_template.md`) completely; changes must be reviewed before merging\n\nRepository-owned automation may use a dedicated branch convention explicitly defined by its\nworkflow. It may update only branches owned by that workflow and must use `--force-with-lease`\nfor any approved force update; an unguarded `--force` remains forbidden. This never permits\ndirect commits to a protected branch.\n\n```bash\n# CORRECT — Always create a feature branch first\ngit checkout -b dev/issue-1234-fix-description\n\n# For submodule changes:\ncd externals/skia\ngit checkout -b dev/issue-1234-add-c-api\n\n# NEVER DO THIS — Policy violation\ngit checkout main && git commit  # FORBIDDEN\ngit checkout skiasharp && git commit  # FORBIDDEN (in skia submodule)\n```\n\n**This applies to BOTH repositories.** The skia submodule has its own protected branches that must be respected.\n\n**Always use the PR template.** When opening a pull request, populate every section of the repository's `.github/pull_request_template.md` — do **not** open a PR with an empty or default body. Keep the ABI-critical **Changes** and **Required skia PR** sections (write `None.` instead of deleting them), tick the relevant **Areas Affected**, describe how you verified the change under **Testing**, and attach before/after screenshots for any rendering change. The `externals/skia` submodule ships its own matching template for C API PRs.\n\nAn automated workflow may use a dedicated PR body only when the workflow owns and renders the\ncomplete body deterministically. That body must satisfy the same requirements as the repository's\ncontributor template.\n\n---\n\n## Commands\n\nSingle source of truth for all commands:\n\n| Task | Command |\n|------|---------|\n| **Bootstrap (C#-only work — see Rule #1, FORBIDDEN for native changes)** | `dotnet cake --target=externals-download` |\n| **Build Native (macOS ARM64)** | `dotnet cake --target=externals-macos --arch=arm64` |\n| **Build Native (macOS Intel)** | `dotnet cake --target=externals-macos --arch=x64` |\n| **Build Native (Windows x64)** | `dotnet cake --target=externals-windows --arch=x64` |\n| **Build Native (Linux x64)** | `dotnet cake --target=externals-linux --arch=x64` |\n| **Build Native (Linux ARM64)** | `dotnet cake --target=externals-linux --arch=arm64` |\n| **Build C#** | `dotnet build binding/SkiaSharp/SkiaSharp.csproj` |\n| **Test** | `dotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0` |\n| **Regenerate** | `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| **Regenerate release-notes (Prepare: api diffs + facts + index)** | `.agents/skills/release-notes/scripts/prepare.sh [--force] [--min-version X --max-version Y]` |\n| **Render all pages + TOC/index (offline, from committed JSON)** | `.agents/skills/release-notes/scripts/render.sh [--min-version X --max-version Y]` |\n\n### When to Use Which Bootstrap\n\n| What You Changed | Command Required |\n|------------------|------------------|\n| C# code only (`binding/SkiaSharp/*.cs`) | `externals-download` (pre-built natives) |\n| C API (`externals/skia/src/c/`, `externals/skia/include/c/`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Dependencies (`externals/skia/DEPS`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Skia submodule SHA / milestone update | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n\n> **CRITICAL:** If you modify ANY native code (C API headers/implementations), you MUST rebuild\n> the native library with `dotnet cake --target=externals-{platform}`. Using `externals-download`\n> after native changes will cause `EntryPointNotFoundException` at runtime because the downloaded\n> binaries don't contain your new functions.\n\n> **Note:** For publication, verify the exact producing Build/Tests runs and\n> BAR before the protected human approval in the MAUI release pipeline; see\n> [`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n**Recovery Commands:**\n\n| Problem | Command |\n|---------|---------|\n| Clean rebuild (**C#-only work**) | `dotnet cake --target=clean && dotnet cake --target=externals-download` |\n| Clean rebuild (**any native or milestone work**) | `dotnet cake --target=clean && dotnet cake --target=externals-{platform} --arch={arch}` |\n| Reset submodule | `git submodule update --init --recursive` |\n\n> **Native build failing?** Do **NOT** \"fall back\" to `externals-download`. Common causes: missing `gn`/`ninja`, missing depot_tools on PATH, missing network access to `chromium.googlesource.com`. Diagnose and fix the source build. Using `externals-download` to make the failure go away will produce a build that runs against stale binaries and silently corrupts milestone updates.\n\n---\n\n## Architecture & Directories\n\n### Layer Overview\n\n```\nC# Wrapper (binding/SkiaSharp/)  ->  P/Invoke  ->  C API (externals/skia/src/c/)  ->  C++ Skia\n```\n\n### Directory Guide\n\n| Directory | Editable? | Notes |\n|-----------|-----------|-------|\n| `binding/SkiaSharp/` | Yes | C# wrappers |\n| `externals/skia/src/c/` | Yes | C API implementation (our shim) |\n| `externals/skia/include/c/` | Yes | C API headers (our shim) |\n| `externals/skia/**` (other) | Conditional | Do not modify during ordinary binding work. The `update-skia` and `native-dependency-update` workflows may resolve upstream conflicts and maintain deliberate fork patches by following their dedicated audit rules. |\n| `*.generated.cs` | Documentation only | Edit `///` trivia directly, then run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` and verify preservation |\n| `documentation/api-media/` | Yes | Versioned `_DocsMedia` source; the external API-docs repo consumes the nonshipping artifact |\n| `documentation/dev/` | Yes | Architecture guides |\n| `documentation/docfx/releases/<version>.md` | No | Generated by `release-notes-render.py` — edit `_sources/<version>.prose.json` (or `.notes.md`), never the page |\n| `documentation/docfx/releases/_sources/<version>.notes.md` | Yes | Manual additions sidecar — maintainer prose that survives re-render (spec §3.7) |\n\n---\n\n## Writing Code\n\nThis section covers memory management, code patterns, and error handling together — they're tightly coupled when writing wrappers.\n\n### Step 1: Identify Pointer Type\n\n```\nIs it wrapped in sk_sp<T>?\n+- Yes -> SkRefCnt?      -> ISKReferenceCounted\n|         SkNVRefCnt<T>? -> ISKNonVirtualReferenceCounted\n+- No  -> Parameter?     -> owns: false\n          Otherwise      -> DisposeNative()\n```\n\n| Type | C++ | C# | Examples |\n|------|-----|-----|----------|\n| Raw | `T*` param | `owns: false` | Temporary refs |\n| Owned | Manual delete | `DisposeNative()` | Canvas, Paint, Path |\n| Ref-counted | `sk_sp<T>` | `ISKReferenceCounted` | Image, Shader, Surface |\n\n### Step 2: Choose Pattern\n\n**Factory method** — return null on failure, validate inputs:\n\n```csharp\npublic static SKImage FromPixels(SKImageInfo info, SKData data, int rowBytes)\n{\n    if (data == null)\n        throw new ArgumentNullException(nameof(data));\n    var cinfo = SKImageInfoNative.FromManaged(ref info);\n    return GetObject(SkiaApi.sk_image_new_raster_data(&cinfo, data.Handle, (IntPtr)rowBytes));\n}\n```\n\n**Instance method** — validate then call:\n\n```csharp\npublic void DrawRect(SKRect rect, SKPaint paint)\n{\n    if (paint == null)\n        throw new ArgumentNullException(nameof(paint));\n    SkiaApi.sk_canvas_draw_rect(Handle, &rect, paint.Handle);\n}\n```\n\n**C API** — naming convention `sk_<type>_<action>`:\n\n```cpp\nsk_image_t* sk_image_new_from_encoded(const sk_data_t* cdata) {\n    return ToImage(SkImages::DeferredFromEncodedData(sk_ref_sp(AsData(cdata))).release());\n}\n```\n\n### Step 3: Error Handling\n\n| Layer | On Failure |\n|-------|------------|\n| C API | Return `nullptr` or `false` |\n| C# Factory | Return `null` |\n| C# Constructor | Throw |\n\n### Step 4: Same-Instance Returns\n\nSome methods return the **same instance**. Always check before disposing:\n\n```csharp\n// CORRECT — always use this pattern\nvar source = GetImage();\nvar result = source.Subset(bounds);\nif (result != source)\n    source.Dispose();\nreturn result;\n```\n\n**Methods that may return same instance:** `Subset()`, `ToRasterImage()`, `ToRasterImage(false)`\n\n### API Design Rules\n\n- **Overloads, not defaults** — Default parameters break ABI\n- **Deprecate, don't remove** — Use `[Obsolete]` with migration guidance\n- **Naming:** `SK` prefix, PascalCase methods, camelCase parameters\n\n**Adding overloads (ABI-safe):**\n\n```csharp\n// Existing method (don't modify)\npublic void DrawText(string text, float x, float y, SKPaint paint)\n\n// New overload (safe to add)\npublic void DrawText(string text, SKPoint point, SKPaint paint)\n    => DrawText(text, point.X, point.Y, paint);\n```\n\n### Threading Rules\n\nSkia is **NOT thread-safe**.\n\n| Never share between threads | Safe to share (immutable) |\n|-----------------------------|---------------------------|\n| `SKCanvas`, `SKPaint`, `SKPath` | `SKImage`, `SKShader`, `SKData` |\n\n```csharp\n// Thread-safe pattern — each thread gets own Paint\nThreadLocal<SKPaint> paint = new(() => new SKPaint());\n```\n\n### Anti-Patterns (Never Do This)\n\n| Anti-Pattern | Why |\n|-------------|-----|\n| `canvas.Dispose()` while using derived objects | Crashes |\n| Sharing `SKPaint` between threads | Race conditions |\n| Modifying method signatures | ABI breaking |\n| Manual generated declaration or interop edits | Overwritten on regenerate; `///` comment trivia is preserved |\n| Using default parameters in public APIs | ABI breaking |\n| **Skipping failing tests** | **Unacceptable — tests must pass** |\n| **Using `externals-download` after C API changes** | **Causes `EntryPointNotFoundException`** |\n| Passing `fixed` pointers to native objects that outlive the block | GC moves memory -> corruption. Use `GCHandle.Alloc(Pinned)` or `Marshal.AllocCoTaskMem` |\n| Testing WASM version changes without cleaning `bin/obj/_framework` | Stale cached native `.wasm` files produce false results |\n\n---\n\n## Testing & Debugging\n\n### Running Tests\n\n```bash\ndotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0\n```\n\nThe unfiltered solution is the primary test entry point and the only final validation.\nMicrosoft.Testing.Platform treats a solution project with zero filtered matches as a failure,\nso do not apply a single-test filter to the `.slnx`. After an unfiltered solution run identifies\na failure, use that test's owning host project for filtered diagnostic iterations:\n\n| Failing host | Diagnostic project |\n|---|---|\n| Core/base | `tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj` |\n| Singleton initialization | `tests/SkiaSharp.Tests.SingletonInit.Console/SkiaSharp.Tests.SingletonInit.Console.csproj` |\n| Vulkan | `tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj` |\n| Direct3D | `tests/SkiaSharp.Direct3D.Tests.Console/SkiaSharp.Direct3D.Tests.Console.csproj` |\n\nExample:\n\n```bash\ndotnet test tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj \\\n  -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0 \\\n  -- --filter-method \"*CreateVkContextIsValid*\"\n```\n\nOnce the focused failure passes, rerun the unfiltered `.slnx`. A project-level run never\nsatisfies the final test gate.\n\n### Tests MUST Pass\n\n> **NON-NEGOTIABLE:** Tests must PASS before claiming completion.\n>\n> - Do NOT skip failing tests\n> - Do NOT claim completion if tests fail\n> - Do NOT use `SkipException` to work around failures\n>\n> **A skip must always be DECLARED, never inferred from an exception.**\n>\n> For **GPU tests** the rule is enforced by `GpuPolicy` — see\n> [documentation/dev/gpu-test-policy.md](documentation/dev/gpu-test-policy.md).\n> A backend is *required* on every platform we build it for; \"no device\", \"no\n> driver\", \"no ICD\" and \"no display\" are **failures**. Skips are owned by the\n> existing platform/host policy; an agent investigating or fixing a failure must\n> not add or expand a GPU skip. Never wrap a GPU bring-up in\n> `try/catch { Assert.Skip }`.\n>\n> For **non-GPU** tests, skipping is acceptable only for a genuine capability\n> gap that is checked explicitly (no system font manager, no XPS support, no\n> display for GTK).\n\n### Writing Tests\n\n```csharp\n[SkippableFact]\npublic void FeatureWorks()\n{\n    using var data = SKData.Create(Path.Combine(PathToImages, \"baboon.jpg\"));\n    using var image = SKImage.FromEncodedData(data);\n    Assert.NotNull(image);\n}\n```\n\n**BaseTest helpers:** `PathToImages`, `PathToFonts`, `IsWindows/Mac/Linux`\n\n**Philosophy:** Tests fail when wrong. GPU tests skip only when `GpuPolicy`\ndeclares it; other tests skip only for an explicitly checked capability gap.\n\n### Debugging Methodology\n\n1. **Establish baseline** — What's the known-good state?\n2. **One change at a time** — Verify each change before proceeding\n3. **Track changes in a table** — Log what you changed and the result\n4. **Platform differences are signals** — If X works and Y fails, the difference IS the answer\n5. **Revert if worse** — Don't pile fixes on top of failures\n\n### Failure Recognition\n\n| Error | Likely Cause | Fix |\n|-------|--------------|-----|\n| `error CS0246` (missing type) | Missing binding | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `LNK2001 unresolved external` | C API signature mismatch | Check C function names match |\n| `AccessViolationException` | Memory management bug | Check disposal patterns |\n| `NullReferenceException` | Factory returned null | Check C API return value |\n| Random crashes | Threading violation | Check Canvas/Paint thread scope |\n| **`EntryPointNotFoundException`** | **Native library not rebuilt after C API change** | **Run `dotnet cake --target=externals-{platform}`** |\n\nSee [documentation/dev/debugging-methodology.md](documentation/dev/debugging-methodology.md).\n\n---\n\n## Slash Commands\n\nCustom slash commands are available for specialized workflows. Use these for complex tasks that benefit from structured processes.\n\nRepository release branches are normally created with the **Release - Prepare**\nGitHub workflow, which runs `scripts/infra/publishing/prepare-release.ps1`. The\n`release-publish` skill queues the protected MAUI package pipeline on an\nexplicit \"push the packages\" request, then uses the **Release - Finish**\nworkflow after public-package verification and separate confirmation. Local\nscripts are fallback tools. Other release skills cover milestone maintenance\nand optional pre-publication BAR/CI package testing. See\n[`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n### When to Use Commands\n\n| Task | Command | Triggers |\n|------|---------|----------|\n| Triage issue | `/issue-triage` | \"triage #NNNN\", \"classify issue\", \"analyze issue\" |\n| Reproduce bug | `/issue-repro` | \"repro #NNNN\", \"reproduce issue\", \"create reproduction\" |\n| Fix bug | `/issue-fix` | \"investigate #NNNN\", \"fix issue\", crash, exception, segfault, \"doesn't work\" |\n| Scan/fix memory leak | `/memory-leak-fixer` | \"memory leak\", \"leak scan\", \"undisposed handle\", \"owns flag\", \"double free\", \"fix the leak\" |\n| Scan/fix performance | `/performance-fixer` | \"performance\", \"perf scan\", \"optimize\", \"make it faster\", \"hot path\", \"reduce allocations\", \"P/Invoke overhead\", \"port to managed\" |\n| Bulk process issues | `/issue-bulk-process` | \"triage these issues\", \"process issues #1 #2 #3\" |\n| Add new API | `/api-add-review` | \"expose\", \"wrap method\", issue requests new functionality |\n| Update dependency | `/native-dependency-update` | \"bump libpng\", \"fix CVE in zlib\" |\n| Write XML docs | `/api-docs` | \"document\", \"fill in missing docs\" |\n| Security check | `/security-audit` | \"audit CVEs\", \"security overview\" (read-only) |\n| Prepare release branches | `/release-branch` | \"release now\", \"start release X\" |\n| Publish packages or finish release | `/release-publish` | \"push the packages\", \"publish BAR\", \"finish release\", \"tag release\" |\n| Maintain release milestones | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\" |\n| Release notes | `/release-notes` | \"generate release notes\", \"regenerate 3.119.x\", \"write release notes for\" |\n| Skia analyst | `/skia-analyst` | \"what changed\", \"what are we missing\", \"feature gap\", \"api diff\", \"scout features\", \"diff tags\" |\n| Update Skia | `/update-skia` | \"update to milestone NNN\", \"bump Skia\" |\n| Review Skia update | `/review-skia-update` | \"review the Skia merge PR\" |\n| Merge Skia update | `/merge-skia-update` | \"merge the Skia bump\", \"land the Skia sync PRs\" |\n| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Scout GM samples | `/sample-scout` | \"find demos to port\", \"what samples are we missing\", \"gallery ideas\" |\n| Create/improve skill | `/skill-creator` | \"create a new skill\", \"improve skill X\" |\n\n### Issue Pipeline (3 steps)\n\nThe first three commands form a pipeline. Each can run standalone, but they work best in sequence:\n\n| Step | Command | Produces |\n|------|---------|----------|\n| 1 | `/issue-triage` | `ai-triage/{n}.json` |\n| 2 | `/issue-repro` | `ai-repro/{n}.json` |\n| 3 | `/issue-fix` | `ai-fix/{n}.json` + PR |\n\nSee [documentation/dev/issue-pipeline.md](documentation/dev/issue-pipeline.md) for handoff contracts and feedback loop.\n\n### Issue Classification (#NNNN)\n\n| If Issue Contains... | Type | Command |\n|---------------------|------|---------|\n| \"triage\", \"classify\", \"analyze issue\" | Triage | `/issue-triage` |\n| \"repro\", \"reproduce\", \"reproduction\" | Reproduction | `/issue-repro` |\n| \"crash\", \"exception\", \"wrong\", \"fails\", \"broken\", \"segfault\" | Bug | `/issue-fix` |\n| \"memory leak\", \"not disposed\", \"handle leak\", \"owns flag\", \"double free\" | Memory leak | `/memory-leak-fixer` |\n| \"slow\", \"performance\", \"optimize\", \"faster\", \"hot path\", \"reduce allocations\", \"interop overhead\" | Performance | `/performance-fixer` |\n| \"add\", \"expose\", \"missing API\", \"feature request\" | New API | `/api-add-review` |\n| \"API docs\", \"XML comments\", \"public comments\" | Docs | `/api-docs` |\n| CVE, security, vulnerability | Security | `/security-audit` then `/native-dependency-update` |\n\n### When NOT to Use Commands\n\nWork directly for:\n- Trivial fixes (typos, whitespace, obvious one-liners)\n- Changes only to `documentation/dev/` (non-generated docs)\n- Build/test-only tasks (no reported bug)\n- Questions about code or architecture\n- Refactoring without a reported problem\n- Performance optimization when the caller already knows the exact one-line change (otherwise use `/performance-fixer`, which proves the win with a benchmark + parity test)\n\n---\n\n## Further Reading\n\n| Topic | Document |\n|-------|----------|\n| Architecture | `documentation/dev/architecture.md` |\n| Memory Management | `documentation/dev/memory-management.md` |\n| Adding APIs | `documentation/dev/adding-apis.md` |\n| API Design | `documentation/dev/api-design.md` |\n| Error Handling | `documentation/dev/error-handling.md` |\n| Debugging | `documentation/dev/debugging-methodology.md` |\n| NuGet Packages | `documentation/dev/packages.md` |\n| Release Notes & API Diffs | `documentation/dev/release-notes-and-api-diffs.md` |\n","isInternal":false,"tokens":5680,"sizeBytes":22873},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":193},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":179},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":172}],"systemPromptSnippet":"<yakaai_skills repo=\"mono/SkiaSharp\">\n<!-- File: AGENTS.md (Tokens: ~5680 | Category: root-instruction) -->\n# SkiaSharp\n\nSkiaSharp is a cross-platform 2D graphics API for .NET wrapping Google's Skia library.\n\n**Architecture:** `C# Wrapper` -> `P/Invoke` -> `C API` -> `C++ Skia`\n**Principle:** C# validates parameters, C API trusts and passes through.\n\n---\n\n## Critical Rules (Read First)\n\nThese rules are **non-negotiable**. Violating them causes broken builds, crashes, or downstream breakage.\n\n### 1. Bootstrap First\n\nBefore C# code can build, native binaries must exist in `output/native/`. **How** you produce them depends on what you're changing:\n\n| You are changing… | Bootstrap with |\n|---|---|\n| **Only C# code** (no files under `externals/skia/`, no `DEPS`, no submodule bump) | `dotnet cake --target=externals-download` (downloads pre-built natives from the **current** milestone) |\n| **Native code, C API, `DEPS`, or the Skia submodule** (incl. milestone updates) | `dotnet cake --target=externals-{platform} --arch={arch}` — build from source. |\n\n> **🛑 If you are doing a Skia milestone update, a C API change, or anything under `externals/skia/`, STOP. Do not run `externals-download` — ever. The downloaded binaries are from the OLD milestone and do not contain your changes; using them produces silently-wrong builds and `EntryPointNotFoundException` at runtime.** When source builds fail (missing `gn`, network errors, etc.), debug the source build — do not fall back to download.\n\n### 2. Never `externals-download` After Native Changes\n\nIf you have modified **any** of the following, `externals-download` is FORBIDDEN until your changes ship to the pre-built artifact server (which only happens after merge):\n\n- `externals/skia/**` (including the submodule SHA)\n- `externals/skia/src/c/**`, `externals/skia/include/c/**`\n- `externals/skia/DEPS`\n- Any milestone bump or version file (`VERSIONS.txt`, `sk_types.h SK_C_INCREMENT`)\n\nFalling back to `externals-download` because a native build failed is the #1 way agents corrupt milestone updates. Fix the source build instead.\n\n### 3. Preserve Generated Bindings\n\nFiles matching `*.generated.cs` are auto-generated.\n\n- **NEVER** manually edit generated declarations or interop code\n- **DO** edit source-controlled `///` comment trivia directly when public API\n  prose changes, then regenerate and verify preservation\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\n\nC# `///` comments are authoritative public API prose. Managed builds generate\ncompiler XML, which packages place beside managed `lib/` and `ref` assemblies;\nthe `ref` copy is the consumer-visible surface. The external\n`mono/SkiaSharp-API-docs` repository consumes published package/XML/media inputs\nand owns ECMA/mdoc generation and Microsoft Learn publication.\n\n### 4. ABI Stability\n\nSkiaSharp maintains stable ABI. Breaking changes break downstream apps.\n\n| Allowed | Never |\n|---------|-------|\n| Add new overloads | Modify existing signatures |\n| Add new methods | Remove public APIs |\n| Add new classes | Change return types |\n\n### 5. Tests Are Mandatory\n\n**Building alone is NOT sufficient.** Run tests before claiming completion (see [Commands](#commands)).\n\n### 6. Branch Protection (COMPLIANCE REQUIRED)\n\n**Direct commits to protected branches are a policy violation.**\n\n| Repository | Protected Branches |\n|------------|-------------------|\n| SkiaSharp (parent) | `main` |\n| externals/skia (submodule) | `main`, `skiasharp` |\n\n**Required workflow:**\n\n1. **Create a feature branch FIRST** — Human-driven changes use `dev/issue-NNNN-description`\n2. **Make all commits on the feature branch** — Never commit directly to protected branches\n3. **Submit a Pull Request** — Fill in the PR template (`.github/pull_request_template.md`) completely; changes must be reviewed before merging\n\nRepository-owned automation may use a dedicated branch convention explicitly defined by its\nworkflow. It may update only branches owned by that workflow and must use `--force-with-lease`\nfor any approved force update; an unguarded `--force` remains forbidden. This never permits\ndirect commits to a protected branch.\n\n```bash\n# CORRECT — Always create a feature branch first\ngit checkout -b dev/issue-1234-fix-description\n\n# For submodule changes:\ncd externals/skia\ngit checkout -b dev/issue-1234-add-c-api\n\n# NEVER DO THIS — Policy violation\ngit checkout main && git commit  # FORBIDDEN\ngit checkout skiasharp && git commit  # FORBIDDEN (in skia submodule)\n```\n\n**This applies to BOTH repositories.** The skia submodule has its own protected branches that must be respected.\n\n**Always use the PR template.** When opening a pull request, populate every section of the repository's `.github/pull_request_template.md` — do **not** open a PR with an empty or default body. Keep the ABI-critical **Changes** and **Required skia PR** sections (write `None.` instead of deleting them), tick the relevant **Areas Affected**, describe how you verified the change under **Testing**, and attach before/after screenshots for any rendering change. The `externals/skia` submodule ships its own matching template for C API PRs.\n\nAn automated workflow may use a dedicated PR body only when the workflow owns and renders the\ncomplete body deterministically. That body must satisfy the same requirements as the repository's\ncontributor template.\n\n---\n\n## Commands\n\nSingle source of truth for all commands:\n\n| Task | Command |\n|------|---------|\n| **Bootstrap (C#-only work — see Rule #1, FORBIDDEN for native changes)** | `dotnet cake --target=externals-download` |\n| **Build Native (macOS ARM64)** | `dotnet cake --target=externals-macos --arch=arm64` |\n| **Build Native (macOS Intel)** | `dotnet cake --target=externals-macos --arch=x64` |\n| **Build Native (Windows x64)** | `dotnet cake --target=externals-windows --arch=x64` |\n| **Build Native (Linux x64)** | `dotnet cake --target=externals-linux --arch=x64` |\n| **Build Native (Linux ARM64)** | `dotnet cake --target=externals-linux --arch=arm64` |\n| **Build C#** | `dotnet build binding/SkiaSharp/SkiaSharp.csproj` |\n| **Test** | `dotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0` |\n| **Regenerate** | `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| **Regenerate release-notes (Prepare: api diffs + facts + index)** | `.agents/skills/release-notes/scripts/prepare.sh [--force] [--min-version X --max-version Y]` |\n| **Render all pages + TOC/index (offline, from committed JSON)** | `.agents/skills/release-notes/scripts/render.sh [--min-version X --max-version Y]` |\n\n### When to Use Which Bootstrap\n\n| What You Changed | Command Required |\n|------------------|------------------|\n| C# code only (`binding/SkiaSharp/*.cs`) | `externals-download` (pre-built natives) |\n| C API (`externals/skia/src/c/`, `externals/skia/include/c/`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Dependencies (`externals/skia/DEPS`) | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n| Skia submodule SHA / milestone update | **`externals-{platform}` (MUST rebuild natives — `externals-download` is FORBIDDEN)** |\n\n> **CRITICAL:** If you modify ANY native code (C API headers/implementations), you MUST rebuild\n> the native library with `dotnet cake --target=externals-{platform}`. Using `externals-download`\n> after native changes will cause `EntryPointNotFoundException` at runtime because the downloaded\n> binaries don't contain your new functions.\n\n> **Note:** For publication, verify the exact producing Build/Tests runs and\n> BAR before the protected human approval in the MAUI release pipeline; see\n> [`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n**Recovery Commands:**\n\n| Problem | Command |\n|---------|---------|\n| Clean rebuild (**C#-only work**) | `dotnet cake --target=clean && dotnet cake --target=externals-download` |\n| Clean rebuild (**any native or milestone work**) | `dotnet cake --target=clean && dotnet cake --target=externals-{platform} --arch={arch}` |\n| Reset submodule | `git submodule update --init --recursive` |\n\n> **Native build failing?** Do **NOT** \"fall back\" to `externals-download`. Common causes: missing `gn`/`ninja`, missing depot_tools on PATH, missing network access to `chromium.googlesource.com`. Diagnose and fix the source build. Using `externals-download` to make the failure go away will produce a build that runs against stale binaries and silently corrupts milestone updates.\n\n---\n\n## Architecture & Directories\n\n### Layer Overview\n\n```\nC# Wrapper (binding/SkiaSharp/)  ->  P/Invoke  ->  C API (externals/skia/src/c/)  ->  C++ Skia\n```\n\n### Directory Guide\n\n| Directory | Editable? | Notes |\n|-----------|-----------|-------|\n| `binding/SkiaSharp/` | Yes | C# wrappers |\n| `externals/skia/src/c/` | Yes | C API implementation (our shim) |\n| `externals/skia/include/c/` | Yes | C API headers (our shim) |\n| `externals/skia/**` (other) | Conditional | Do not modify during ordinary binding work. The `update-skia` and `native-dependency-update` workflows may resolve upstream conflicts and maintain deliberate fork patches by following their dedicated audit rules. |\n| `*.generated.cs` | Documentation only | Edit `///` trivia directly, then run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` and verify preservation |\n| `documentation/api-media/` | Yes | Versioned `_DocsMedia` source; the external API-docs repo consumes the nonshipping artifact |\n| `documentation/dev/` | Yes | Architecture guides |\n| `documentation/docfx/releases/<version>.md` | No | Generated by `release-notes-render.py` — edit `_sources/<version>.prose.json` (or `.notes.md`), never the page |\n| `documentation/docfx/releases/_sources/<version>.notes.md` | Yes | Manual additions sidecar — maintainer prose that survives re-render (spec §3.7) |\n\n---\n\n## Writing Code\n\nThis section covers memory management, code patterns, and error handling together — they're tightly coupled when writing wrappers.\n\n### Step 1: Identify Pointer Type\n\n```\nIs it wrapped in sk_sp<T>?\n+- Yes -> SkRefCnt?      -> ISKReferenceCounted\n|         SkNVRefCnt<T>? -> ISKNonVirtualReferenceCounted\n+- No  -> Parameter?     -> owns: false\n          Otherwise      -> DisposeNative()\n```\n\n| Type | C++ | C# | Examples |\n|------|-----|-----|----------|\n| Raw | `T*` param | `owns: false` | Temporary refs |\n| Owned | Manual delete | `DisposeNative()` | Canvas, Paint, Path |\n| Ref-counted | `sk_sp<T>` | `ISKReferenceCounted` | Image, Shader, Surface |\n\n### Step 2: Choose Pattern\n\n**Factory method** — return null on failure, validate inputs:\n\n```csharp\npublic static SKImage FromPixels(SKImageInfo info, SKData data, int rowBytes)\n{\n    if (data == null)\n        throw new ArgumentNullException(nameof(data));\n    var cinfo = SKImageInfoNative.FromManaged(ref info);\n    return GetObject(SkiaApi.sk_image_new_raster_data(&cinfo, data.Handle, (IntPtr)rowBytes));\n}\n```\n\n**Instance method** — validate then call:\n\n```csharp\npublic void DrawRect(SKRect rect, SKPaint paint)\n{\n    if (paint == null)\n        throw new ArgumentNullException(nameof(paint));\n    SkiaApi.sk_canvas_draw_rect(Handle, &rect, paint.Handle);\n}\n```\n\n**C API** — naming convention `sk_<type>_<action>`:\n\n```cpp\nsk_image_t* sk_image_new_from_encoded(const sk_data_t* cdata) {\n    return ToImage(SkImages::DeferredFromEncodedData(sk_ref_sp(AsData(cdata))).release());\n}\n```\n\n### Step 3: Error Handling\n\n| Layer | On Failure |\n|-------|------------|\n| C API | Return `nullptr` or `false` |\n| C# Factory | Return `null` |\n| C# Constructor | Throw |\n\n### Step 4: Same-Instance Returns\n\nSome methods return the **same instance**. Always check before disposing:\n\n```csharp\n// CORRECT — always use this pattern\nvar source = GetImage();\nvar result = source.Subset(bounds);\nif (result != source)\n    source.Dispose();\nreturn result;\n```\n\n**Methods that may return same instance:** `Subset()`, `ToRasterImage()`, `ToRasterImage(false)`\n\n### API Design Rules\n\n- **Overloads, not defaults** — Default parameters break ABI\n- **Deprecate, don't remove** — Use `[Obsolete]` with migration guidance\n- **Naming:** `SK` prefix, PascalCase methods, camelCase parameters\n\n**Adding overloads (ABI-safe):**\n\n```csharp\n// Existing method (don't modify)\npublic void DrawText(string text, float x, float y, SKPaint paint)\n\n// New overload (safe to add)\npublic void DrawText(string text, SKPoint point, SKPaint paint)\n    => DrawText(text, point.X, point.Y, paint);\n```\n\n### Threading Rules\n\nSkia is **NOT thread-safe**.\n\n| Never share between threads | Safe to share (immutable) |\n|-----------------------------|---------------------------|\n| `SKCanvas`, `SKPaint`, `SKPath` | `SKImage`, `SKShader`, `SKData` |\n\n```csharp\n// Thread-safe pattern — each thread gets own Paint\nThreadLocal<SKPaint> paint = new(() => new SKPaint());\n```\n\n### Anti-Patterns (Never Do This)\n\n| Anti-Pattern | Why |\n|-------------|-----|\n| `canvas.Dispose()` while using derived objects | Crashes |\n| Sharing `SKPaint` between threads | Race conditions |\n| Modifying method signatures | ABI breaking |\n| Manual generated declaration or interop edits | Overwritten on regenerate; `///` comment trivia is preserved |\n| Using default parameters in public APIs | ABI breaking |\n| **Skipping failing tests** | **Unacceptable — tests must pass** |\n| **Using `externals-download` after C API changes** | **Causes `EntryPointNotFoundException`** |\n| Passing `fixed` pointers to native objects that outlive the block | GC moves memory -> corruption. Use `GCHandle.Alloc(Pinned)` or `Marshal.AllocCoTaskMem` |\n| Testing WASM version changes without cleaning `bin/obj/_framework` | Stale cached native `.wasm` files produce false results |\n\n---\n\n## Testing & Debugging\n\n### Running Tests\n\n```bash\ndotnet test tests/SkiaSharp.Tests.Console.slnx -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0\n```\n\nThe unfiltered solution is the primary test entry point and the only final validation.\nMicrosoft.Testing.Platform treats a solution project with zero filtered matches as a failure,\nso do not apply a single-test filter to the `.slnx`. After an unfiltered solution run identifies\na failure, use that test's owning host project for filtered diagnostic iterations:\n\n| Failing host | Diagnostic project |\n|---|---|\n| Core/base | `tests/SkiaSharp.Tests.Console/SkiaSharp.Tests.Console.csproj` |\n| Singleton initialization | `tests/SkiaSharp.Tests.SingletonInit.Console/SkiaSharp.Tests.SingletonInit.Console.csproj` |\n| Vulkan | `tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj` |\n| Direct3D | `tests/SkiaSharp.Direct3D.Tests.Console/SkiaSharp.Direct3D.Tests.Console.csproj` |\n\nExample:\n\n```bash\ndotnet test tests/SkiaSharp.Vulkan.Tests.Console/SkiaSharp.Vulkan.Tests.Console.csproj \\\n  -p:TargetFramework=net10.0 -p:TargetFrameworks=net10.0 \\\n  -- --filter-method \"*CreateVkContextIsValid*\"\n```\n\nOnce the focused failure passes, rerun the unfiltered `.slnx`. A project-level run never\nsatisfies the final test gate.\n\n### Tests MUST Pass\n\n> **NON-NEGOTIABLE:** Tests must PASS before claiming completion.\n>\n> - Do NOT skip failing tests\n> - Do NOT claim completion if tests fail\n> - Do NOT use `SkipException` to work around failures\n>\n> **A skip must always be DECLARED, never inferred from an exception.**\n>\n> For **GPU tests** the rule is enforced by `GpuPolicy` — see\n> [documentation/dev/gpu-test-policy.md](documentation/dev/gpu-test-policy.md).\n> A backend is *required* on every platform we build it for; \"no device\", \"no\n> driver\", \"no ICD\" and \"no display\" are **failures**. Skips are owned by the\n> existing platform/host policy; an agent investigating or fixing a failure must\n> not add or expand a GPU skip. Never wrap a GPU bring-up in\n> `try/catch { Assert.Skip }`.\n>\n> For **non-GPU** tests, skipping is acceptable only for a genuine capability\n> gap that is checked explicitly (no system font manager, no XPS support, no\n> display for GTK).\n\n### Writing Tests\n\n```csharp\n[SkippableFact]\npublic void FeatureWorks()\n{\n    using var data = SKData.Create(Path.Combine(PathToImages, \"baboon.jpg\"));\n    using var image = SKImage.FromEncodedData(data);\n    Assert.NotNull(image);\n}\n```\n\n**BaseTest helpers:** `PathToImages`, `PathToFonts`, `IsWindows/Mac/Linux`\n\n**Philosophy:** Tests fail when wrong. GPU tests skip only when `GpuPolicy`\ndeclares it; other tests skip only for an explicitly checked capability gap.\n\n### Debugging Methodology\n\n1. **Establish baseline** — What's the known-good state?\n2. **One change at a time** — Verify each change before proceeding\n3. **Track changes in a table** — Log what you changed and the result\n4. **Platform differences are signals** — If X works and Y fails, the difference IS the answer\n5. **Revert if worse** — Don't pile fixes on top of failures\n\n### Failure Recognition\n\n| Error | Likely Cause | Fix |\n|-------|--------------|-----|\n| `error CS0246` (missing type) | Missing binding | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `LNK2001 unresolved external` | C API signature mismatch | Check C function names match |\n| `AccessViolationException` | Memory management bug | Check disposal patterns |\n| `NullReferenceException` | Factory returned null | Check C API return value |\n| Random crashes | Threading violation | Check Canvas/Paint thread scope |\n| **`EntryPointNotFoundException`** | **Native library not rebuilt after C API change** | **Run `dotnet cake --target=externals-{platform}`** |\n\nSee [documentation/dev/debugging-methodology.md](documentation/dev/debugging-methodology.md).\n\n---\n\n## Slash Commands\n\nCustom slash commands are available for specialized workflows. Use these for complex tasks that benefit from structured processes.\n\nRepository release branches are normally created with the **Release - Prepare**\nGitHub workflow, which runs `scripts/infra/publishing/prepare-release.ps1`. The\n`release-publish` skill queues the protected MAUI package pipeline on an\nexplicit \"push the packages\" request, then uses the **Release - Finish**\nworkflow after public-package verification and separate confirmation. Local\nscripts are fallback tools. Other release skills cover milestone maintenance\nand optional pre-publication BAR/CI package testing. See\n[`documentation/dev/releasing.md`](documentation/dev/releasing.md).\n\n### When to Use Commands\n\n| Task | Command | Triggers |\n|------|---------|----------|\n| Triage issue | `/issue-triage` | \"triage #NNNN\", \"classify issue\", \"analyze issue\" |\n| Reproduce bug | `/issue-repro` | \"repro #NNNN\", \"reproduce issue\", \"create reproduction\" |\n| Fix bug | `/issue-fix` | \"investigate #NNNN\", \"fix issue\", crash, exception, segfault, \"doesn't work\" |\n| Scan/fix memory leak | `/memory-leak-fixer` | \"memory leak\", \"leak scan\", \"undisposed handle\", \"owns flag\", \"double free\", \"fix the leak\" |\n| Scan/fix performance | `/performance-fixer` | \"performance\", \"perf scan\", \"optimize\", \"make it faster\", \"hot path\", \"reduce allocations\", \"P/Invoke overhead\", \"port to managed\" |\n| Bulk process issues | `/issue-bulk-process` | \"triage these issues\", \"process issues #1 #2 #3\" |\n| Add new API | `/api-add-review` | \"expose\", \"wrap method\", issue requests new functionality |\n| Update dependency | `/native-dependency-update` | \"bump libpng\", \"fix CVE in zlib\" |\n| Write XML docs | `/api-docs` | \"document\", \"fill in missing docs\" |\n| Security check | `/security-audit` | \"audit CVEs\", \"security overview\" (read-only) |\n| Prepare release branches | `/release-branch` | \"release now\", \"start release X\" |\n| Publish packages or finish release | `/release-publish` | \"push the packages\", \"publish BAR\", \"finish release\", \"tag release\" |\n| Maintain release milestones | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\" |\n| Release notes | `/release-notes` | \"generate release notes\", \"regenerate 3.119.x\", \"write release notes for\" |\n| Skia analyst | `/skia-analyst` | \"what changed\", \"what are we missing\", \"feature gap\", \"api diff\", \"scout features\", \"diff tags\" |\n| Update Skia | `/update-skia` | \"update to milestone NNN\", \"bump Skia\" |\n| Review Skia update | `/review-skia-update` | \"review the Skia merge PR\" |\n| Merge Skia update | `/merge-skia-update` | \"merge the Skia bump\", \"land the Skia sync PRs\" |\n| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Scout GM samples | `/sample-scout` | \"find demos to port\", \"what samples are we missing\", \"gallery ideas\" |\n| Create/improve skill | `/skill-creator` | \"create a new skill\", \"improve skill X\" |\n\n### Issue Pipeline (3 steps)\n\nThe first three commands form a pipeline. Each can run standalone, but they work best in sequence:\n\n| Step | Command | Produces |\n|------|---------|----------|\n| 1 | `/issue-triage` | `ai-triage/{n}.json` |\n| 2 | `/issue-repro` | `ai-repro/{n}.json` |\n| 3 | `/issue-fix` | `ai-fix/{n}.json` + PR |\n\nSee [documentation/dev/issue-pipeline.md](documentation/dev/issue-pipeline.md) for handoff contracts and feedback loop.\n\n### Issue Classification (#NNNN)\n\n| If Issue Contains... | Type | Command |\n|---------------------|------|---------|\n| \"triage\", \"classify\", \"analyze issue\" | Triage | `/issue-triage` |\n| \"repro\", \"reproduce\", \"reproduction\" | Reproduction | `/issue-repro` |\n| \"crash\", \"exception\", \"wrong\", \"fails\", \"broken\", \"segfault\" | Bug | `/issue-fix` |\n| \"memory leak\", \"not disposed\", \"handle leak\", \"owns flag\", \"double free\" | Memory leak | `/memory-leak-fixer` |\n| \"slow\", \"performance\", \"optimize\", \"faster\", \"hot path\", \"reduce allocations\", \"interop overhead\" | Performance | `/performance-fixer` |\n| \"add\", \"expose\", \"missing API\", \"feature request\" | New API | `/api-add-review` |\n| \"API docs\", \"XML comments\", \"public comments\" | Docs | `/api-docs` |\n| CVE, security, vulnerability | Security | `/security-audit` then `/native-dependency-update` |\n\n### When NOT to Use Commands\n\nWork directly for:\n- Trivial fixes (typos, whitespace, obvious one-liners)\n- Changes only to `documentation/dev/` (non-generated docs)\n- Build/test-only tasks (no reported bug)\n- Questions about code or architecture\n- Refactoring without a reported problem\n- Performance optimization when the caller already knows the exact one-line change (otherwise use `/performance-fixer`, which proves the win with a benchmark + parity test)\n\n---\n\n## Further Reading\n\n| Topic | Document |\n|-------|----------|\n| Architecture | `documentation/dev/architecture.md` |\n| Memory Management | `documentation/dev/memory-management.md` |\n| Adding APIs | `documentation/dev/adding-apis.md` |\n| API Design | `documentation/dev/api-design.md` |\n| Error Handling | `documentation/dev/error-handling.md` |\n| Debugging | `documentation/dev/debugging-methodology.md` |\n| NuGet Packages | `documentation/dev/packages.md` |\n| Release Notes & API Diffs | `documentation/dev/release-notes-and-api-diffs.md` |\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/mono/SkiaSharp/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}