{"owner":"mono","repo":"SkiaSharp","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"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. Never Edit Generated Files\n\nFiles matching `*.generated.cs` and `docs/` are auto-generated.\n\n- **NEVER** manually edit these files\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\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 release verification, see `/release-testing` command for the full platform matrix.\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` | No | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `docs/` | No | Auto-generated |\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 edits to `*.generated.cs` | Overwritten on regenerate |\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\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| Start release (Step 1/5) | `/release-branch` | \"release now\", \"start release X\" |\n| Check release status (Step 2/5) | `/release-status` | \"check release status\", \"how is the build\", \"pipeline status\" |\n| Test release (Step 3/5) | `/release-testing` | \"test the release\", \"verify packages\" |\n| Publish release (Step 4/5) | `/release-publish` | \"push to nuget\", \"tag release\" |\n| Release milestones (Step 5/5) | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\", \"close release milestone\" |\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| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Validate samples | `/validate-samples` | \"build samples\", \"test sample projects\" |\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| \"docs\", \"documentation\", \"XML\", \"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"},"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. Never Edit Generated Files\n\nFiles matching `*.generated.cs` and `docs/` are auto-generated.\n\n- **NEVER** manually edit these files\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\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 release verification, see `/release-testing` command for the full platform matrix.\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` | No | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `docs/` | No | Auto-generated |\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 edits to `*.generated.cs` | Overwritten on regenerate |\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\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| Start release (Step 1/5) | `/release-branch` | \"release now\", \"start release X\" |\n| Check release status (Step 2/5) | `/release-status` | \"check release status\", \"how is the build\", \"pipeline status\" |\n| Test release (Step 3/5) | `/release-testing` | \"test the release\", \"verify packages\" |\n| Publish release (Step 4/5) | `/release-publish` | \"push to nuget\", \"tag release\" |\n| Release milestones (Step 5/5) | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\", \"close release milestone\" |\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| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Validate samples | `/validate-samples` | \"build samples\", \"test sample projects\" |\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| \"docs\", \"documentation\", \"XML\", \"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"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","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. Never Edit Generated Files\n\nFiles matching `*.generated.cs` and `docs/` are auto-generated.\n\n- **NEVER** manually edit these files\n- **ALWAYS** regenerate after C API changes (see [Commands](#commands))\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 release verification, see `/release-testing` command for the full platform matrix.\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` | No | Run `pwsh -NoLogo -NoProfile -File ./utils/generate.ps1` |\n| `docs/` | No | Auto-generated |\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 edits to `*.generated.cs` | Overwritten on regenerate |\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\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| Start release (Step 1/5) | `/release-branch` | \"release now\", \"start release X\" |\n| Check release status (Step 2/5) | `/release-status` | \"check release status\", \"how is the build\", \"pipeline status\" |\n| Test release (Step 3/5) | `/release-testing` | \"test the release\", \"verify packages\" |\n| Publish release (Step 4/5) | `/release-publish` | \"push to nuget\", \"tag release\" |\n| Release milestones (Step 5/5) | `/release-milestones` | \"reconcile milestones\", \"advance milestone schedule\", \"close release milestone\" |\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| PR commit message | `/pr-commit-message` | \"write commit message for PR\" |\n| Validate samples | `/validate-samples` | \"build samples\", \"test sample projects\" |\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| \"docs\", \"documentation\", \"XML\", \"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","category":"root","tokens":5387}]}