diff --git a/.github/agents/agentic-workflows.agent.md b/.agents/agents/agentic-workflows.agent.md similarity index 91% rename from .github/agents/agentic-workflows.agent.md rename to .agents/agents/agentic-workflows.agent.md index e2e2b2b58be..25f95c15fea 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.agents/agents/agentic-workflows.agent.md @@ -30,7 +30,7 @@ Workflows may optionally include: - Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` - Workflow lock files: `.github/workflows/*.lock.yml` - Shared components: `.github/workflows/shared/*.md` -- Configuration: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md +- Configuration: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/github-agentic-workflows.md ## Problems This Solves @@ -52,7 +52,7 @@ When you interact with this agent, it will: ### Create New Workflow **Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/create-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/create-agentic-workflow.md **Use cases**: - "Create a workflow that triages issues" @@ -62,7 +62,7 @@ When you interact with this agent, it will: ### Update Existing Workflow **Load when**: User wants to modify, improve, or refactor an existing workflow -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/update-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/update-agentic-workflow.md **Use cases**: - "Add web-fetch tool to the issue-classifier workflow" @@ -72,7 +72,7 @@ When you interact with this agent, it will: ### Debug Workflow **Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/debug-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/debug-agentic-workflow.md **Use cases**: - "Why is this workflow failing?" @@ -82,7 +82,7 @@ When you interact with this agent, it will: ### Upgrade Agentic Workflows **Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/upgrade-agentic-workflows.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/upgrade-agentic-workflows.md **Use cases**: - "Upgrade all workflows to the latest version" @@ -92,7 +92,7 @@ When you interact with this agent, it will: ### Create a Report-Generating Workflow **Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/report.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/report.md **Use cases**: - "Create a weekly CI health report" @@ -102,7 +102,7 @@ When you interact with this agent, it will: ### Create Shared Agentic Workflow **Load when**: User wants to create a reusable workflow component or wrap an MCP server -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/create-shared-agentic-workflow.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/create-shared-agentic-workflow.md **Use cases**: - "Create a shared component for Notion integration" @@ -112,7 +112,7 @@ When you interact with this agent, it will: ### Fix Dependabot PRs **Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/dependabot.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/dependabot.md **Use cases**: - "Fix the open Dependabot PRs for npm dependencies" @@ -122,7 +122,7 @@ When you interact with this agent, it will: ### Analyze Test Coverage **Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. -**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/test-coverage.md +**Prompt file**: https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/test-coverage.md **Use cases**: - "Create a workflow that comments coverage on PRs" @@ -169,10 +169,10 @@ gh aw compile --validate ## Important Notes -- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md for complete documentation +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/github-agentic-workflows.md for complete documentation - Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud - Workflows must be compiled to `.lock.yml` files before running in GitHub Actions - **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF - Follow security best practices: minimal permissions, explicit network access, no template injection -- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.71.1/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. - **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. diff --git a/.agents/skills/sample-scout/SKILL.md b/.agents/skills/sample-scout/SKILL.md new file mode 100644 index 00000000000..b30974e6bba --- /dev/null +++ b/.agents/skills/sample-scout/SKILL.md @@ -0,0 +1,157 @@ +--- +name: sample-scout +description: > + Scout Google's upstream Skia GM (golden master) samples to find demos worth porting to the + SkiaSharp Gallery. Fetches the full gm/ directory listing from google/skia on GitHub, analyzes + each .cpp file to understand what it demonstrates, checks whether the required APIs exist in + SkiaSharp, and cross-references against existing Gallery samples to identify gaps. Produces a + structured JSON report and a GitHub-flavored Markdown report for filtering by interest level, API + availability, and sample coverage status. Use this skill whenever the user asks about "what + samples should we build", "what demos are we missing", "find interesting Skia GMs", "sample gap + analysis", "what can we port from Skia", "gallery ideas", "scout GM samples", or any request to + discover demo opportunities from upstream Skia. Also use proactively after adding new APIs to + find samples that showcase them. +--- + +# Sample Scout + +You analyze Google's upstream Skia GM (golden master) sample files to discover demos worth porting +to the SkiaSharp Gallery. The goal is to find visually impressive, educationally valuable samples +that showcase SkiaSharp's capabilities — and identify which ones we can build today vs. which need +new APIs first. + +## Why This Matters + +Skia has 400+ GM samples that exercise every API and visual technique. These are a goldmine for +the SkiaSharp Gallery — each one is a proven, tested visual that demonstrates something users would +want to learn. But nobody can manually review 400+ C++ files to find the gems. This skill automates +the discovery. + +## Key References + +- **[references/analysis-instructions.md](references/analysis-instructions.md)** — How to classify samples +- **[references/sample-scout-schema.json](references/sample-scout-schema.json)** — JSON Schema for validation +- **[references/schema-cheatsheet.md](references/schema-cheatsheet.md)** — Human-readable schema docs + +## Workflow + +``` +Phase 1: Setup (list GM files, list existing Gallery samples) +Phase 2: Analyze GM files (parallel agents, each handles a chunk) +Phase 3: Cross-reference with existing Gallery samples +Phase 4: Validate and render +Phase 5: Present results +``` + +### Phase 1: Setup + +**1a. List all GM files from upstream** + +Use the GitHub API to get the complete file listing from `google/skia/gm/`: + +```bash +gh api repos/google/skia/contents/gm --jq '.[].name' | grep '\.cpp$' | sort > gm-files.txt +wc -l < gm-files.txt +``` + +IMPORTANT: Always use upstream `google/skia`, not our fork `mono/skia`. The fork may be behind. + +**1b. List existing Gallery samples** + +```bash +find samples/Gallery -name "*.cs" -path "*/Samples/*" | sort +``` + +For each sample, extract the `Title`, `Description`, and `Category` to build a coverage map. + +**1c. Split into chunks for parallel processing** + +With 400+ files, split into 5 chunks of ~80-90 files each for parallel analysis. + +### Phase 2: Analyze GM Files + +Launch **5 parallel background agents** (general-purpose), each analyzing one chunk. Each agent: + +1. For each `.cpp` file in its chunk, fetch from GitHub: + ``` + github-mcp-server-get_file_contents owner=google repo=skia path=gm/{filename} + ``` + +2. Read the file and extract: + - **What it demonstrates** (1-2 sentences) + - **Key Skia APIs used** (class::method names) + - **Interest level**: high / medium / low + - **API availability**: check if the required APIs exist in SkiaSharp by grepping `binding/SkiaSharp/` + - **Missing APIs**: list any APIs not available in SkiaSharp + - **Notes**: GPU-only, Graphite-specific, bug regression, etc. + +3. Save findings as JSON array to a temp file. + +See [references/analysis-instructions.md](references/analysis-instructions.md) for the classification +criteria and decision guidelines. + +**Agent prompt template:** +``` +Analyze Skia GM sample files from google/skia. For EACH file, fetch it using +github-mcp-server-get_file_contents (owner=google, repo=skia, path=gm/FILENAME), +read it, and produce a JSON entry. + +Files: {comma-separated list} + +Read .agents/skills/sample-scout/references/analysis-instructions.md for classification criteria. + +For each file output: file, name, description, interesting (high/medium/low), +apis_available (true/false), missing_apis [], key_apis [], notes, +visualGoal (what the rendered output looks like), suggestedControls [], +category (Gallery category), skiaSharpApis [] (C# equivalents). + +Check API availability by grepping binding/SkiaSharp/ for the C# equivalents. +Save as JSON array to {output_path}. +Must produce exactly {N} entries — count at the end to confirm. +``` + +### Phase 3: Cross-Reference with Existing Gallery Samples + +After all agents complete, merge their findings and cross-reference against existing Gallery samples: + +For each GM entry, check if an existing Gallery sample covers the same topic: +- **`existing`** — A Gallery sample directly covers this GM's main feature +- **`similar`** — A Gallery sample covers a related topic (e.g., gradient GM → Gradient sample exists) +- **`none`** — No Gallery sample covers this + +Tag each finding with `sampleStatus` and `matchedSample`. + +Save the merged findings as `sample-scout-report.json` in the working directory. + +### Phase 4: Validate and Render + +**4a. Validate** + +```bash +python3 .agents/skills/sample-scout/scripts/validate-sample-scout.py sample-scout-report.json +``` + +**4b. Render Markdown** + +```bash +python3 .agents/skills/sample-scout/scripts/render-sample-scout.py sample-scout-report.json sample-scout-report.md +``` + +This produces a `.md` file with `###`/`####` headers suitable for GitHub issues. + +### Phase 5: Present Results + +Show the summary with these key metrics: +- Total samples analyzed +- 🆕 No existing sample (opportunities) +- 🔶 Similar sample exists (enhancement opportunities) +- ✅ Already covered +- 🎯 **Opportunity count** = high interest + APIs ready + no existing sample + +Then present the top opportunities — samples that are high-interest, have all APIs available, +and have no existing Gallery coverage. These are the ones to build next. + +Offer: +1. "Want me to build Gallery samples for the top opportunities?" +2. "Should I focus on samples that need new APIs first?" +3. "Want to filter by a specific category (shaders, image filters, text, etc.)?" diff --git a/.agents/skills/sample-scout/evals/evals.json b/.agents/skills/sample-scout/evals/evals.json new file mode 100644 index 00000000000..8bb07189743 --- /dev/null +++ b/.agents/skills/sample-scout/evals/evals.json @@ -0,0 +1,35 @@ +{ + "skill_name": "sample-scout", + "evals": [ + { + "id": 1, + "prompt": "Scout the upstream Skia GM samples and find interesting demos we should port to the SkiaSharp Gallery. Do a full scan.", + "expected_output": "Full scan of 400+ GM files from google/skia/gm/. Should categorize each by interest level, check API availability, and cross-reference with existing Gallery samples. Should identify top opportunities.", + "files": [], + "expectations": [ + "Produces a JSON file that passes validate-sample-scout.py with exit code 0", + "Produces a Markdown report via render-sample-scout.py", + "JSON meta.source is 'google/skia/gm/'", + "JSON findings array has at least 300 entries", + "JSON summary.totalFindings equals actual findings count", + "At least 50 findings have interesting='high'", + "At least one finding references mesh.cpp with apis_available=false", + "At least one finding references runtimeshader.cpp with apis_available=true", + "sampleStatus field is present on findings", + "summary.opportunities count matches actual high+available+none count" + ] + }, + { + "id": 2, + "prompt": "What Skia GM samples can we build today with existing SkiaSharp APIs? I want to find demos we're missing.", + "expected_output": "Should filter to samples where apis_available=true and sampleStatus='none'. Focus on the actionable opportunities.", + "files": [], + "expectations": [ + "Produces a valid JSON report", + "Identifies samples with sampleStatus 'none' that have apis_available true", + "Presents a focused list of buildable opportunities", + "Each opportunity has a clear description of what it demonstrates" + ] + } + ] +} diff --git a/.agents/skills/sample-scout/references/analysis-instructions.md b/.agents/skills/sample-scout/references/analysis-instructions.md new file mode 100644 index 00000000000..4ddcdb914ad --- /dev/null +++ b/.agents/skills/sample-scout/references/analysis-instructions.md @@ -0,0 +1,93 @@ +# Sample Scout — Analysis Instructions + +Read this file before analyzing any GM samples. It contains the classification criteria, +interest level guidelines, and API availability checking methodology. + +## Classification Criteria + +### Interest Level + +| Level | Criteria | Examples | +|-------|----------|---------| +| **high** | Visually impressive, demonstrates a useful technique, showcases a powerful API that users would want to learn. Would make a great Gallery demo. | Runtime shaders, shadow utils, path effects showcase, gradient interpolation, mesh drawing, image filter chains, text effects | +| **medium** | Educational or useful reference but not visually exciting on its own. Could be combined with other samples. | Bitmap copy, color matrix, simple clip operations, tile modes, basic shapes | +| **low** | Internal test, bug regression, GPU-backend-specific test, stress test, or not relevant to SkiaSharp users. | crbug_* files, skbug_* files, Graphite-only tests, GrFragmentProcessor tests, Vulkan/Dawn internals, benchmark GMs | + +### Quick Classification Rules + +- Files starting with `crbug_` or `skbug` → **low** (bug regression tests) +- Files mentioning `Graphite`, `Dawn`, `GrFragmentProcessor`, `GrGeometryProcessor` → **low** (GPU internals) +- Files testing a single specific visual feature with nice output → **high** +- Files testing edge cases of an already-covered feature → **medium** +- Files showing off compositing, effects chains, or creative techniques → **high** + +### API Availability Check + +For each key Skia API used in a GM sample, check if SkiaSharp has a C# equivalent: + +| Skia C++ | SkiaSharp C# | Where to check | +|----------|-------------|----------------| +| `SkCanvas::drawRect` | `SKCanvas.DrawRect` | `binding/SkiaSharp/SKCanvas.cs` | +| `SkPaint` properties | `SKPaint` properties | `binding/SkiaSharp/SKPaint.cs` | +| `SkShader` factories | `SKShader.Create*` | `binding/SkiaSharp/SKShader.cs` | +| `SkImageFilter` factories | `SKImageFilter.Create*` | `binding/SkiaSharp/SKImageFilter.cs` | +| `SkPathEffect` factories | `SKPathEffect.Create*` | `binding/SkiaSharp/SKPathEffect.cs` | +| `SkColorFilter` factories | `SKColorFilter.Create*` | `binding/SkiaSharp/SKColorFilter.cs` | +| `SkRuntimeEffect` | `SKRuntimeEffect` | `binding/SkiaSharp/SKRuntimeEffect.cs` | +| `SkMesh` | ❌ Not available | — | +| `SkShadowUtils` | ❌ Not available | — | +| `SkImageFilters::RuntimeShader` | ❌ Not available | — | +| `SkColorFilters::HSLAMatrix` | ❌ Not available | — | +| GPU-specific (GrDirectContext, GrBackendTexture) | ❌ Not relevant | Internal GPU plumbing | +| `experimental_DrawEdgeAAQuad` | ❌ Not available | Experimental API | + +To verify, grep the binding directory: +```bash +grep -ril "MethodName\|method_name" binding/SkiaSharp/ +``` + +### What Makes a Good Gallery Sample + +A good SkiaSharp Gallery sample: +1. **Looks impressive** — visual output that makes people say "wow, SkiaSharp can do that?" +2. **Teaches something** — demonstrates an API or technique users would want to learn +3. **Is self-contained** — doesn't need external resources (or uses bundled assets) +4. **Has controls** — interactive parameters users can tweak to understand the API +5. **Works on all platforms** — no GPU-specific or platform-specific requirements + +### Notes Field + +Use the notes field to capture important context: +- "GPU-only" — requires GPU backend, won't work on CPU raster +- "Graphite-specific" — only relevant to Graphite backend (not SkiaSharp) +- "Bug regression test" — tests a specific fixed bug, not a feature demo +- "Stress test" — performance/edge case test, not visual +- "Requires test fonts" — needs specific font files +- "Animated" — has frame-based animation +- "Combines well with X" — could be merged with another GM for a richer sample + +## Output Schema + +Each finding must have these fields: + +```json +{ + "file": "mesh.cpp", + "name": "Custom Vertex Mesh Drawing", + "description": "Draws custom vertex meshes with SkSL vertex/fragment programs and per-vertex attributes.", + "interesting": "high", + "apis_available": false, + "missing_apis": ["SkMesh", "SkMeshSpecification", "SkCanvas::drawMesh"], + "key_apis": ["SkMesh::Make", "SkMeshSpecification::Make", "SkCanvas::drawMesh"], + "notes": "Requires SkMesh API which is not yet exposed in SkiaSharp", + "visualGoal": "A colorful warped grid of triangles with per-vertex colors, demonstrating programmable vertex displacement and custom fragment shading.", + "suggestedControls": ["Grid density slider (4–32)", "Warp amplitude slider (0–50)", "Color mode toggle (rainbow/grayscale)"], + "category": "Shaders", + "skiaSharpApis": ["SKCanvas.DrawVertices", "SKRuntimeEffect", "SKVertices"] +} +``` + +### Required fields +`file`, `name`, `description`, `interesting`, `apis_available`, `missing_apis`, `key_apis`, `visualGoal`, `suggestedControls`, `category`, `skiaSharpApis` + +All fields are required on every finding. `missing_apis` and `suggestedControls` should be empty arrays `[]` when not applicable. diff --git a/.agents/skills/sample-scout/references/sample-scout-schema.json b/.agents/skills/sample-scout/references/sample-scout-schema.json new file mode 100644 index 00000000000..d1279810c10 --- /dev/null +++ b/.agents/skills/sample-scout/references/sample-scout-schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/mono/SkiaSharp/sample-scout-schema.json", + "title": "SkiaSharp Sample Scout Report", + "description": "Audit of upstream Skia GM samples for SkiaSharp Gallery porting opportunities.", + "type": "object", + "required": ["meta", "summary", "findings"], + "additionalProperties": false, + "properties": { + "meta": { + "type": "object", + "required": ["date", "schemaVersion", "source", "totalFiles"], + "additionalProperties": false, + "properties": { + "date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, + "schemaVersion": { "type": "string", "const": "1.0" }, + "source": { "type": "string", "description": "e.g., google/skia/gm/" }, + "totalFiles": { "type": "integer", "minimum": 0 }, + "existingGallerySamples": { "type": "integer", "minimum": 0 } + } + }, + "summary": { + "type": "object", + "required": ["totalFindings"], + "additionalProperties": true, + "properties": { + "totalFindings": { "type": "integer", "minimum": 0 }, + "byInterest": { + "type": "object", + "additionalProperties": { "type": "integer", "minimum": 0 } + }, + "byApiAvailability": { + "type": "object", + "additionalProperties": { "type": "integer", "minimum": 0 } + }, + "bySampleStatus": { + "type": "object", + "additionalProperties": { "type": "integer", "minimum": 0 } + }, + "opportunities": { "type": "integer", "minimum": 0, "description": "high + apis_available + sampleStatus=none" } + } + }, + "findings": { + "type": "array", + "items": { "$ref": "#/$defs/finding" } + } + }, + "$defs": { + "finding": { + "type": "object", + "required": ["file", "name", "description", "interesting", "apis_available", "missing_apis", "key_apis", "visualGoal", "suggestedControls", "category", "skiaSharpApis"], + "additionalProperties": false, + "properties": { + "file": { "type": "string", "minLength": 1 }, + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "interesting": { "type": "string", "enum": ["high", "medium", "low"] }, + "apis_available": { "type": "boolean" }, + "missing_apis": { "type": "array", "items": { "type": "string" } }, + "key_apis": { "type": "array", "items": { "type": "string" } }, + "notes": { "type": "string" }, + "sampleStatus": { "type": "string", "enum": ["none", "similar", "existing"] }, + "matchedSample": { "type": "string" }, + "visualGoal": { "type": "string", "description": "What the user sees — 1-3 sentences describing the rendered output" }, + "suggestedControls": { "type": "array", "items": { "type": "string" }, "description": "Interactive controls: sliders, toggles, pickers with ranges" }, + "category": { "type": "string", "description": "Gallery category: Shaders, Text, Paths, Image Filters, General, etc." }, + "skiaSharpApis": { "type": "array", "items": { "type": "string" }, "description": "C# SkiaSharp API equivalents (not C++ names)" } + } + } + } +} diff --git a/.agents/skills/sample-scout/references/schema-cheatsheet.md b/.agents/skills/sample-scout/references/schema-cheatsheet.md new file mode 100644 index 00000000000..820a1e80eaa --- /dev/null +++ b/.agents/skills/sample-scout/references/schema-cheatsheet.md @@ -0,0 +1,57 @@ +# Sample Scout Report Schema (v1) + +## Top-Level Structure + +```json +{ + "meta": { ... }, + "summary": { ... }, + "findings": [ ... ] +} +``` + +## `meta` + +| Field | Required | Description | +|-------|----------|-------------| +| `date` | Yes | YYYY-MM-DD | +| `schemaVersion` | Yes | Must be `"1.0"` | +| `source` | Yes | e.g., `"google/skia/gm/"` | +| `totalFiles` | Yes | Number of .cpp files in gm/ | +| `existingGallerySamples` | No | Number of existing SkiaSharp Gallery samples | + +## `summary` + +| Field | Required | Description | +|-------|----------|-------------| +| `totalFindings` | Yes | Number of GM files analyzed | +| `byInterest` | No | `{"high": N, "medium": N, "low": N}` | +| `byApiAvailability` | No | `{"available": N, "blocked": N}` | +| `bySampleStatus` | No | `{"none": N, "similar": N, "existing": N}` | +| `opportunities` | No | Count of high + APIs ready + no existing sample | + +## `findings` — Array of GM Analyses + +### Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `file` | string | GM filename (e.g., `"mesh.cpp"`) | +| `name` | string | Human-readable name | +| `description` | string | What the sample demonstrates | +| `interesting` | string | `"high"`, `"medium"`, or `"low"` | +| `apis_available` | boolean | Whether all required APIs exist in SkiaSharp | +| `missing_apis` | string[] | APIs not available (empty if all available) | +| `key_apis` | string[] | Main Skia APIs used | +| `visualGoal` | string | What the user *sees* — 1-3 sentences describing the rendered output | +| `suggestedControls` | string[] | Interactive controls with ranges (e.g., `"Threshold slider (0–1)"`) | +| `category` | string | Gallery category: `"Shaders"`, `"Text"`, `"Paths"`, `"Image Filters"`, `"General"`, etc. | +| `skiaSharpApis` | string[] | C# SkiaSharp equivalents (e.g., `"SKRuntimeEffect"`, `"SKCanvas.DrawRect"`) | + +### Optional Fields + +| Field | Type | Description | +|-------|------|-------------| +| `notes` | string | Context (GPU-only, bug regression, etc.) | +| `sampleStatus` | string | `"none"`, `"similar"`, or `"existing"` — Gallery coverage | +| `matchedSample` | string | Name of matching Gallery sample if similar/existing | diff --git a/.agents/skills/sample-scout/scripts/render-sample-scout.py b/.agents/skills/sample-scout/scripts/render-sample-scout.py new file mode 100644 index 00000000000..43dde0408c3 --- /dev/null +++ b/.agents/skills/sample-scout/scripts/render-sample-scout.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""Render a Sample Scout JSON report as GitHub-flavored Markdown. + +Usage: python3 render-sample-scout.py [output.md] + +Headers start at ### so the output can be embedded directly in a GitHub issue. +""" +import json +import os +import sys +from pathlib import Path + +INTEREST_EMOJI = {'high': '🔥', 'medium': '🔶', 'low': '🔹'} +STATUS_EMOJI = {'none': '🆕', 'similar': '🔶', 'existing': '✅'} + + +def render_item(f): + interest = INTEREST_EMOJI.get(f.get('interesting', 'low'), '🔹') + avail = '✅' if f.get('apis_available') else '❌' + status = STATUS_EMOJI.get(f.get('sampleStatus', 'none'), '🆕') + + lines = ["
", f"{interest} {avail} {status} {f['name']}\n"] + lines.append(f"> {f['description']} ") + + detail = [] + if f.get('visualGoal'): + detail.append(f"**Visual goal:** {f['visualGoal']}") + if f.get('category'): + detail.append(f"**Category:** {f['category']}") + if f.get('key_apis'): + detail.append(f"**Key APIs:** {', '.join(f'`{a}`' for a in f['key_apis'][:5])}") + if f.get('skiaSharpApis'): + detail.append(f"**SkiaSharp APIs:** {', '.join(f'`{a}`' for a in f['skiaSharpApis'][:5])}") + if f.get('missing_apis') and len(f['missing_apis']) > 0: + detail.append(f"**Missing APIs:** {', '.join(f'`{a}`' for a in f['missing_apis'][:5])}") + if f.get('suggestedControls') and len(f['suggestedControls']) > 0: + detail.append(f"**Controls:** {', '.join(f['suggestedControls'][:5])}") + if f.get('matchedSample'): + detail.append(f"**Matched sample:** `{f['matchedSample']}`") + if f.get('notes'): + detail.append(f"**Notes:** {f['notes']}") + if f.get('file'): + detail.append(f"**Source:** `gm/{f['file']}`") + + if detail: + lines.append("> \n> " + " \n> ".join(detail)) + lines.append("\n
\n") + return '\n'.join(lines) + + +def main(): + if len(sys.argv) < 2: + print("Usage: python3 render-sample-scout.py [output.md]") + sys.exit(1) + + json_path = Path(sys.argv[1]) + if not json_path.exists(): + print(f"❌ JSON file not found: {json_path}") + sys.exit(1) + + output_path = Path(sys.argv[2]) if len(sys.argv) >= 3 else json_path.with_suffix(".md") + + try: + with open(json_path, encoding="utf-8") as f: + data = json.load(f) + except json.JSONDecodeError as e: + print(f"❌ Invalid JSON: {e}") + sys.exit(1) + + findings = data.get("findings", []) + meta = data.get("meta", {}) + + # Categorize + opportunities = [f for f in findings + if f.get('interesting') == 'high' + and f.get('apis_available') + and f.get('sampleStatus') == 'none'] + needs_apis = [f for f in findings + if f.get('interesting') == 'high' + and not f.get('apis_available') + and f.get('sampleStatus') == 'none'] + high = [f for f in findings if f.get('interesting') == 'high'] + medium = [f for f in findings if f.get('interesting') == 'medium'] + low = [f for f in findings if f.get('interesting') == 'low'] + no_sample = [f for f in findings if f.get('sampleStatus') == 'none'] + similar = [f for f in findings if f.get('sampleStatus') == 'similar'] + existing = [f for f in findings if f.get('sampleStatus') == 'existing'] + + md = [] + md.append("### Sample Scout Report\n") + md.append(f"> **{len(findings)} GM samples analyzed** · " + f"{len(opportunities)} ready to build · " + f"{len(needs_apis)} need new APIs ") + md.append(f"> Generated {meta.get('date', '?')}\n") + + # Legend + md.append("
") + md.append("Legend\n") + md.append("| Symbol | Meaning |") + md.append("|--------|---------|") + md.append("| 🔥 | High interest |") + md.append("| 🔶 | Medium interest |") + md.append("| 🔹 | Low interest |") + md.append("| ✅ | APIs available / already covered |") + md.append("| ❌ | Missing APIs |") + md.append("| 🆕 | No existing Gallery sample |") + md.append("") + md.append("
\n") + + # Summary table + md.append("| Category | Count |") + md.append("|----------|-------|") + md.append(f"| 🔥 High interest | {len(high)} |") + md.append(f"| 🔶 Medium interest | {len(medium)} |") + md.append(f"| 🔹 Low interest | {len(low)} |") + md.append(f"| 🆕 No existing sample | {len(no_sample)} |") + md.append(f"| 🔶 Similar sample exists | {len(similar)} |") + md.append(f"| ✅ Already covered | {len(existing)} |") + md.append(f"| 🎯 **Ready to build** | **{len(opportunities)}** |") + md.append(f"| 🔧 Needs new APIs | {len(needs_apis)} |") + md.append("") + + # Top opportunities + if opportunities: + md.append("#### 🎯 Ready to Build\n") + md.append("High interest, APIs available, no existing Gallery sample.\n") + for f in sorted(opportunities, key=lambda x: x['name']): + md.append(render_item(f)) + + # Needs APIs + if needs_apis: + md.append("#### 🔧 Needs New APIs First\n") + md.append("High interest but requires APIs not yet in SkiaSharp.\n") + for f in sorted(needs_apis, key=lambda x: x['name']): + md.append(render_item(f)) + + # Medium interest opportunities + medium_opps = [f for f in medium if f.get('apis_available') and f.get('sampleStatus') == 'none'] + if medium_opps: + md.append("
") + md.append(f"

🔶 Medium Interest Opportunities ({len(medium_opps)})

\n") + for f in sorted(medium_opps, key=lambda x: x['name']): + md.append(render_item(f)) + md.append("\n
\n") + + # Already covered + if existing: + md.append("
") + md.append(f"

✅ Already Covered ({len(existing)})

\n") + for f in sorted(existing, key=lambda x: x['name']): + matched = f" → `{f['matchedSample']}`" if f.get('matchedSample') else "" + md.append(f"- **{f['name']}**{matched} — {f['description'][:80]}") + md.append("\n
\n") + + # Low interest + if low: + md.append("
") + md.append(f"

🔹 Low Interest ({len(low)})

\n") + for f in sorted(low, key=lambda x: x['name']): + md.append(f"- **{f['name']}** — {f['description'][:80]}") + md.append("\n
\n") + + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, "w", encoding="utf-8") as f: + f.write('\n'.join(md)) + + size_kb = os.path.getsize(output_path) / 1024 + print(f"✅ {output_path.name} ({size_kb:.0f} KB)") + print(f" {len(findings)} samples · {len(opportunities)} ready to build · {len(needs_apis)} need APIs") + print(f" Output: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/.agents/skills/skia-feature-scout/scripts/requirements.txt b/.agents/skills/sample-scout/scripts/requirements.txt similarity index 100% rename from .agents/skills/skia-feature-scout/scripts/requirements.txt rename to .agents/skills/sample-scout/scripts/requirements.txt diff --git a/.agents/skills/sample-scout/scripts/validate-sample-scout.py b/.agents/skills/sample-scout/scripts/validate-sample-scout.py new file mode 100644 index 00000000000..dea2569f738 --- /dev/null +++ b/.agents/skills/sample-scout/scripts/validate-sample-scout.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Validate a Sample Scout JSON report against sample-scout-schema.json. + +Usage: python3 validate-sample-scout.py +Exits: 0=valid, 1=fixable errors (retry), 2=fatal. +""" +import json +import sys +from collections import Counter +from pathlib import Path + +if len(sys.argv) != 2: + print("Usage: python3 validate-sample-scout.py ") + sys.exit(2) + +path = Path(sys.argv[1]) +if not path.exists(): + print(f"❌ File not found: {path}") + sys.exit(2) + +schema_path = Path(__file__).resolve().parent / "../references/sample-scout-schema.json" +if not schema_path.exists(): + print(f"❌ Schema not found: {schema_path}") + sys.exit(2) + +try: + with open(path) as f: + data = json.load(f) +except json.JSONDecodeError as e: + print(f"❌ Invalid JSON: {e}") + sys.exit(2) + +with open(schema_path) as f: + schema = json.load(f) + +errors = [] +warnings = [] + +# --- JSON Schema validation --- +try: + from jsonschema import Draft202012Validator + validator = Draft202012Validator(schema) + for e in validator.iter_errors(data): + p = ".".join(str(x) for x in e.absolute_path) or "(root)" + errors.append(f"Schema: {p}: {e.message}") +except ImportError: + print("⚠️ jsonschema not installed — run: pip install jsonschema") + print(" Falling back to structural checks only") + +# --- Extra checks beyond schema --- + +meta = data.get("meta", {}) +summary = data.get("summary", {}) +findings = data.get("findings", []) + +# Check totalFindings matches actual +actual_total = len(findings) +if summary.get("totalFindings", 0) != actual_total: + errors.append( + f"summary.totalFindings ({summary.get('totalFindings')}) != actual findings count ({actual_total})" + ) + +# Check byInterest counts +by_interest = summary.get("byInterest", {}) +actual_interest = Counter(f.get("interesting") for f in findings) +for k, v in by_interest.items(): + if actual_interest.get(k, 0) != v: + warnings.append(f"summary.byInterest.{k} ({v}) != actual ({actual_interest.get(k, 0)})") + +# Check byApiAvailability counts +by_api = summary.get("byApiAvailability", {}) +actual_avail = sum(1 for f in findings if f.get("apis_available")) +actual_blocked = sum(1 for f in findings if not f.get("apis_available")) +if by_api.get("available", 0) != actual_avail: + warnings.append(f"summary.byApiAvailability.available ({by_api.get('available')}) != actual ({actual_avail})") +if by_api.get("blocked", 0) != actual_blocked: + warnings.append(f"summary.byApiAvailability.blocked ({by_api.get('blocked')}) != actual ({actual_blocked})") + +# Check bySampleStatus counts +by_ss = summary.get("bySampleStatus", {}) +actual_ss = Counter(f.get("sampleStatus") for f in findings) +for k, v in by_ss.items(): + if actual_ss.get(k, 0) != v: + warnings.append(f"summary.bySampleStatus.{k} ({v}) != actual ({actual_ss.get(k, 0)})") + +# Ensure names are unique-ish (file should be unique) +files = [f.get("file") for f in findings] +dupes = [n for n in set(files) if files.count(n) > 1] +if dupes: + warnings.append(f"Duplicate file entries: {dupes[:5]}") + +# Check opportunities count +opp_actual = sum( + 1 for f in findings + if f.get("interesting") == "high" + and f.get("apis_available") == True + and f.get("sampleStatus") == "none" +) +if summary.get("opportunities") is not None and summary["opportunities"] != opp_actual: + warnings.append(f"summary.opportunities ({summary['opportunities']}) != actual ({opp_actual})") + +# --- Output --- +if warnings: + for w in warnings: + print(f" ⚠️ {w}") + +if errors: + print(f"\n❌ {len(errors)} validation error(s) in {path.name}:\n") + for e in errors: + print(f" ❌ {e}") + sys.exit(1) + +total = meta.get("totalFiles", "?") +high = actual_interest.get("high", 0) +none_count = actual_ss.get("none", 0) +print( + f"✅ {path.name} is valid ({actual_total} findings, {high} high interest, " + f"{none_count} new opportunities, {opp_actual} actionable)" +) +sys.exit(0) diff --git a/.agents/skills/skia-analyst/SKILL.md b/.agents/skills/skia-analyst/SKILL.md new file mode 100644 index 00000000000..d629e6d1e1f --- /dev/null +++ b/.agents/skills/skia-analyst/SKILL.md @@ -0,0 +1,164 @@ +--- +name: skia-analyst +description: > + Analyze Skia features for SkiaSharp - produces a unified analysis of what shipped (upstream + engine benefits, PR links, migration guides) and what's missing (impact/priority/effort scoring, + hidden APIs). Given any combination of git refs, milestones, or no input at all, it scans upstream + Skia release notes, diffs bindings, and checks C++ headers to produce a unified report. Use + whenever the user asks to "write release notes", "generate changelog", "what changed between + versions", "what are we missing", "feature gap analysis", "what should we bind next", "what's + new in Skia", "scout features", "diff two tags", "what shipped in this release", "summarize + changes since v3.x", "prepare release announcement", or any request to analyze SkiaSharp versions + or Skia features. Also use proactively when the user mentions a Skia milestone bump, finishes a + release cycle, or asks what went into a specific version. +--- + +# Skia Analyst + +You analyze Skia features for SkiaSharp from two angles simultaneously: +1. **What shipped** — upstream engine benefits, PR links, migration guides +2. **What's missing** — gap analysis with impact/priority/effort scoring, hidden API scan, action items + +Every run produces both. Output is structured JSON and rendered GitHub-flavored Markdown. + +This skill always runs in a SkiaSharp checkout. It uses: +- `externals/skia/` submodule for the C API (our fork at `mono/skia`) +- `binding/SkiaSharp/SkiaApi.generated.cs` for the C API reflected as P/Invoke externs +- `binding/SkiaSharp/*.cs` for the C# wrappers +- Upstream `google/skia` headers fetched via GitHub for hidden API comparison + +## Key References + +- **[references/schema-cheatsheet.md](references/schema-cheatsheet.md)** — Human-readable schema +- **[references/skia-analyst-schema.json](references/skia-analyst-schema.json)** — JSON Schema (Draft 2020-12) +- **[references/analysis-instructions.md](references/analysis-instructions.md)** — Classification criteria + +## Input Flexibility + +The user may specify anything — infer the scan mode: + +| User says | Mode | What to do | +|-----------|------|------------| +| Nothing, "scan everything" | `full` | All milestones, full gap analysis | +| "what's new since m133" | `windowed` | milestoneFrom=133, milestoneTo=current | +| "between m133 and m147" | `windowed` | milestoneFrom=133, milestoneTo=147 | +| "diff v3.119.4..origin/main" | `diff` | Git diff + milestone analysis | +| "what changed in v4.147.0" | `diff` | Auto-detect previous tag, diff | + +If unclear, ask. But try to infer first. + +## Workflow + +``` +Phase 1: Setup (determine scan range, prepare sources) +Phase 2: Launch dual-model agents (Opus + GPT in parallel) +Phase 3: Synthesize — merge findings, dedupe, both lenses +Phase 4: Generate outputs (JSON → validate → Markdown) +Phase 5: Present results +``` + +### Phase 1: Setup + +**1a. Determine scan range** + +Based on user input, establish: +- `scanMode`: full, windowed, or diff +- For windowed: `milestoneFrom` and `milestoneTo` +- For diff: `refFrom` and `refTo` (resolve SHAs, dates, commit count) + +**1b. Determine current milestone** + +```bash +cat externals/skia/include/core/SkMilestone.h +``` + +Or check commit messages for the latest Skia bump PR. + +**1c. Fetch Skia release notes** + +Fetch `RELEASE_NOTES.md` from `google/skia` using the GitHub MCP tool or `gh api`: + +```bash +gh api repos/google/skia/contents/RELEASE_NOTES.md -H "Accept: application/vnd.github.raw" > skia-release-notes.md +``` + +Save to a file in the working directory for agents to reference. + +**1d. Prepare upstream headers for hidden API scan** + +Agents will fetch upstream C++ headers directly from GitHub during their scan: +``` +github-mcp-server-get_file_contents owner=google repo=skia path=include/core/SkImage.h +``` + +**1e. Locate binding sources** + +The C API is reflected in `binding/SkiaSharp/SkiaApi.generated.cs` as P/Invoke extern methods. +The C# wrappers are in `binding/SkiaSharp/*.cs`. Both are in the worktree. + +For the C API headers (our fork), check `externals/skia/include/c/` and `externals/skia/src/c/`. +If the submodule isn't checked out, agents can grep `SkiaApi.generated.cs` for `sk_*` and `gr_*` +extern function names — this reflects the full C API surface. + +### Phase 2: Launch Dual-Model Agents + +Launch **two** background agents simultaneously on **different models**: + +``` +task agent_type=general-purpose mode=background model=claude-opus-4.7 name=analyst-opus: +task agent_type=general-purpose mode=background model=gpt-5.4 name=analyst-gpt: +``` + +Each agent does the complete job independently: +1. **Release notes scan** — read Skia RELEASE_NOTES.md, extract features +2. **Hidden API scan** — fetch upstream C++ headers from `google/skia`, compare against + binding/SkiaSharp/SkiaApi.generated.cs for P/Invoke externs and binding/SkiaSharp/*.cs for wrappers +3. **Binding verification** — grep the actual code to set bindingStatus +4. **Git diff** (if diff mode) — analyze API/build/dep changes between refs + +For EVERY finding, classify with BOTH lenses: +- Changelog: `changeType` + `importance` +- Gap: `bindingStatus` + `impact` + `priority` + `effort` + +### Phase 3: Synthesize + +When both agents complete, merge their findings: + +1. **Deduplicate** by name/skiaApi — keep richer data from each +2. **Resolve conflicts** — more cautious bindingStatus wins, higher impact wins +3. **Union hidden APIs** — combine both agents' header scan discoveries + +### Phase 4: Generate Outputs + +**4a. Generate JSON** + +Save to `skia-analyst-report.json` in the working directory. + +**4b. Validate** + +```bash +python3 .agents/skills/skia-analyst/scripts/validate-skia-analyst.py skia-analyst-report.json +``` + +**4c. Render Markdown** + +```bash +python3 .agents/skills/skia-analyst/scripts/render-skia-analyst.py skia-analyst-report.json skia-analyst-report.md +``` + +This produces a GitHub-flavored Markdown file with collapsible details, suitable for pasting +into a GitHub issue or sharing as a gist. + +### Phase 5: Present Results + +Show highlights inline: + +- **Top gaps** — transformative and significant items +- **Quick wins** — partial binding status (C API exists, just needs C# wrapper) +- **Breaking changes** — if any findings have `importance: breaking` + +Then offer next steps: +1. "Want me to investigate any finding in more detail?" +2. "Should I use the api-add-review skill to start binding a feature?" +3. "Want to upload the markdown to a gist for sharing?" +4. "Should I create a GitHub issue with the gap analysis?" diff --git a/.agents/skills/skia-analyst/evals/evals.json b/.agents/skills/skia-analyst/evals/evals.json new file mode 100644 index 00000000000..a0d6e0e234a --- /dev/null +++ b/.agents/skills/skia-analyst/evals/evals.json @@ -0,0 +1,42 @@ +{ + "skill_name": "skia-analyst", + "evals": [ + { + "id": 1, + "prompt": "Analyze what's new in Skia that we should know about. Do a full scan.", + "expected_output": "Full scan of all milestones. Should find SkMesh, gradient interpolation, HDR pipeline, SkPathBuilder, runtime shader image filters. JSON should pass validation.", + "files": [], + "expectations": [ + "Produces a JSON that passes validate-skia-analyst.py", + "meta.scanMode is 'full'", + "findings array has at least 30 items", + "At least one finding with impact 'transformative'", + "At least one finding with bindingStatus 'missing'" + ] + }, + { + "id": 2, + "prompt": "What changed between v3.119.4-preview.1.1 and origin/main?", + "expected_output": "Diff mode between two refs. Should find variable fonts, color palettes, SKPathBuilder, security hardening, Skia m147 bump. Should have upstream benefits from m134-m147.", + "files": [], + "expectations": [ + "Produces a valid JSON report", + "meta.scanMode is 'diff'", + "meta.refFrom contains 'v3.119.4'", + "At least one finding with changeType 'upstream'", + "At least one finding with changeType 'added'" + ] + }, + { + "id": 3, + "prompt": "What landed between milestone 133 and 147?", + "expected_output": "Windowed scan m133-m147. Should find SkPath immutability, gradient interpolation in P3/Rec2020, HDR metadata, kR16_unorm, kRec709 correction.", + "files": [], + "expectations": [ + "meta.scanMode is 'windowed'", + "At least one finding mentions SkPath or SkPathBuilder", + "At least one finding mentions gradient interpolation" + ] + } + ] +} \ No newline at end of file diff --git a/.agents/skills/skia-analyst/references/analysis-instructions.md b/.agents/skills/skia-analyst/references/analysis-instructions.md new file mode 100644 index 00000000000..5d820c7123c --- /dev/null +++ b/.agents/skills/skia-analyst/references/analysis-instructions.md @@ -0,0 +1,155 @@ +# Skia Analyst — Analysis Instructions + +Read this before starting any analysis. It covers both lenses — changelog (what shipped) and gap analysis (what's missing) — because every finding needs both. + +## Input Flexibility + +The user may specify: +- **Nothing** → full scan (all milestones, everything) +- **One milestone** → "what's new since m133" (windowed from that milestone to current) +- **Two milestones** → "between m133 and m147" (windowed range) +- **One git ref** → "what changed since v3.119.4" (diff from ref to HEAD) +- **Two git refs** → "diff v3.119.4..origin/main" (explicit range) +- **Vague** → "scan for new stuff" (full scan) + +Infer the scan mode from context. If unclear, ask. + +## Data Sources + +### 1. Skia Release Notes +Fetch from: `https://raw.githubusercontent.com/google/skia/main/RELEASE_NOTES.md` +Extract features per milestone. For windowed/diff mode, focus on the relevant range but still scan a few milestones beyond. + +### 2. C++ Header Scan (Hidden API Discovery) +For types SkiaSharp already binds, fetch upstream C++ headers from `google/skia` and compare +against what SkiaSharp exposes: +- **Upstream C++ headers**: fetch from GitHub (`owner=google, repo=skia, path=include/core/SkImage.h`) +- **SkiaSharp C API**: grep `binding/SkiaSharp/SkiaApi.generated.cs` for `sk_*` / `gr_*` P/Invoke externs. + This reflects the full C API surface even if `externals/skia/include/c/` isn't checked out. +- **SkiaSharp C# wrappers**: `binding/SkiaSharp/*.cs` + +High-value headers to scan: +| C# | C API prefix | C++ header | +|----|-------------|------------| +| `SKCanvas` | `sk_canvas_*` | `include/core/SkCanvas.h` | +| `SKImage` | `sk_image_*` | `include/core/SkImage.h` | +| `SKPath` | `sk_path_*` | `include/core/SkPath.h` | +| `SKPathBuilder` | `sk_pathbuilder_*` | `include/core/SkPathBuilder.h` | +| `SKShader` | `sk_shader_*` | `include/core/SkShader.h` | +| `SKImageFilter` | `sk_imagefilter_*` | `include/effects/SkImageFilters.h` | +| `SKColorFilter` | `sk_colorfilter_*` | `include/core/SkColorFilter.h` | +| `SKCodec` | `sk_codec_*` | `include/codec/SkCodec.h` | +| `SKFont` | `sk_font_*` | `include/core/SkFont.h` | +| `SKTypeface` | `sk_typeface_*` | `include/core/SkTypeface.h` | +| `SKSurface` | `sk_surface_*` | `include/core/SkSurface.h` | +| `SKColorSpace` | `sk_colorspace_*` | `include/core/SkColorSpace.h` | + +### 3. Git Diff (when refs provided) +- C API diff: `git diff REF1..REF2 -- externals/skia/include/c/ externals/skia/src/c/` +- C# binding diff: `git diff REF1..REF2 -- binding/ ':!*.generated.cs'` +- Git log: `git log REF1..REF2 --oneline` +- Build/deps: `git diff REF1..REF2 -- native/ externals/skia/DEPS cgmanifest.json` + +## Classification: Both Lenses + +Every finding needs BOTH a changelog classification AND a gap classification. + +### Changelog lens (what happened) + +**changeType:** +| Value | Criteria | +|-------|----------| +| `added` | New API, class, method, capability | +| `changed` | Modified behavior or signature | +| `fixed` | Bug fix | +| `deprecated` | New `[Obsolete]` marker | +| `removed` | Removed API or feature | +| `dependency` | Dependency version bump | +| `platform` | New platform, build flag change | +| `upstream` | Benefit from Skia engine upgrade (automatic, no API change) | + +**importance:** +| Value | Criteria | +|-------|----------| +| `breaking` | Existing code may fail to compile or behave differently | +| `major` | New user-facing capability, significant feature | +| `minor` | Enhancement, quality improvement | +| `patch` | Bug fix, internal improvement | + +### Gap lens (what to do about it) + +**bindingStatus:** +| Value | Description | +|-------|-------------| +| `full` | C API + C# wrapper both exist | +| `partial` | C API exists but C# wrapper missing (quick win) | +| `missing` | Neither C API nor C# wrapper | +| `action_needed` | SkiaSharp wraps deprecated/removed Skia API | +| `correctly_absent` | Skia removed; SkiaSharp never wrapped | +| `not_applicable` | Doesn't need a binding (internal, auto-available) | + +**impact:** +| Value | Decision guide | +|-------|---------------| +| `transformative` | "Does this unlock a new *category* of app?" | +| `significant` | "Would a user *see* the difference or gain a major capability?" | +| `moderate` | "Is this useful but not exciting?" | +| `minor` | Default for small helpers | + +**priority:** +| Value | Criteria | +|-------|----------| +| `critical` | Will break on next Skia bump | +| `high` | Major new capability or highly requested | +| `medium` | Useful addition, quality improvement | +| `low` | Minor utility, internal concern | + +## What to Include vs Exclude + +**Include:** +- New classes, APIs, capabilities +- Codec/format support (AVIF, JPEG XL, HDR) +- Shader capabilities, image filter additions +- Color types, color spaces +- Performance improvements +- Behavior changes +- Deprecations and removals +- Security hardening, dependency updates +- Platform expansion + +**Exclude:** +- Internal refactoring (header moves) +- Graphite-only changes (SkiaSharp uses Ganesh) +- Dawn backend changes +- Build system internals (GN flags) +- Version bump commits +- CI/docs/skill-only changes + +## Upstream Benefits (changeType: "upstream") + +When a Skia bump is in the diff, extract "invisible benefits" — things users get automatically: +- Rendering quality (mipmap sharpening, noise rotation, backdrop tiling) +- Performance (noise raster speedup, canvas preallocation) +- Color accuracy (kRec709 BT.1886, kHLG/kPQ corrections) +- Codec improvements (Exif orientation, high bit-depth detection) +- Reliability (Vulkan device-lost, SkSL validation) + +For upstream findings: `bindingStatus` = `not_applicable`, `impact` and `priority` based on user value. + +## Optional Enrichment Fields + +For findings with `importance` of `major` or higher, include a `slideBullet` — a one-line marketing +bullet for downstream tooling (e.g., blog post drafts). Not rendered in the Markdown report but +captured in the JSON for other tools to use. + +For `importance: "breaking"` findings, include a `migrationGuide` with before/after code. + +For `changeType: "dependency"` findings, include `dependencyName`, `dependencyFrom`, `dependencyTo`. + +## Accuracy Tips + +- Don't confuse enum values with full support +- Check the actual C# method, not just the class +- Verify C# wrappers call the right C API +- Check SkiaApi.generated.cs for hidden plumbing +- milestone fields must be integers (133 not "m133") diff --git a/.agents/skills/skia-analyst/references/schema-cheatsheet.md b/.agents/skills/skia-analyst/references/schema-cheatsheet.md new file mode 100644 index 00000000000..b93f6e1602e --- /dev/null +++ b/.agents/skills/skia-analyst/references/schema-cheatsheet.md @@ -0,0 +1,85 @@ +# Skia Analyst Report Schema (v1) + +## Top-Level Structure + +```json +{ + "meta": { ... }, + "summary": { ... }, + "findings": [ ... ] +} +``` + +## `meta` + +| Field | Required | Description | +|-------|----------|-------------| +| `date` | Yes | YYYY-MM-DD | +| `schemaVersion` | Yes | `"1.0"` | +| `repo` | Yes | `"mono/SkiaSharp"` | +| `currentMilestone` | Yes | SkiaSharp's current Skia milestone | +| `latestUpstreamMilestone` | Yes | Latest milestone in upstream Skia | +| `releaseNotesSource` | Yes | URL to Skia RELEASE_NOTES.md | +| `scanMode` | No | `"full"`, `"windowed"`, or `"diff"` | +| `refFrom` / `refTo` | No | Git refs when diffing | +| `milestoneFrom` / `milestoneTo` | No | Milestone range when windowed | +| `commitCount` / `prCount` | No | Commit stats when diffing | + +## `summary` + +| Field | Required | Description | +|-------|----------|-------------| +| `totalFindings` | Yes | Number of findings | +| `byChangeType` | No | Counts per changeType | +| `byImportance` | No | Counts per importance | +| `byBindingStatus` | No | Counts per bindingStatus | +| `byImpact` | No | Counts per impact | +| `byPriority` | No | Counts per priority | +| `bySource` | No | Counts per source | + +## `findings` — Unified Array + +Every finding has BOTH lenses — what it means for the changelog AND for the gap analysis. + +### Required fields (every finding) + +| Field | Type | Description | +|-------|------|-------------| +| `name` | string | Feature title | +| `category` | enum | What area (new_feature, codec, image, image_filter, shader, color, canvas, path, font, utility, performance, behavior_change, deprecation, security, platform, dependency) | +| `description` | string | What changed / what's missing | +| `source` | enum | release-notes, header-scan, binding-audit, git-diff | +| `changeType` | enum | added, changed, fixed, deprecated, removed, dependency, platform, upstream | +| `importance` | enum | breaking, major, minor, patch | +| `bindingStatus` | enum | full, partial, missing, action_needed, correctly_absent, not_applicable | +| `impact` | enum | transformative, significant, moderate, minor | +| `priority` | enum | critical, high, medium, low | + +### Optional fields + +| Field | Type | When to include | +|-------|------|-----------------| +| `effort` | enum | trivial/small/medium/large — cost to implement | +| `labels` | string[] | Freeform tags for filtering | +| `milestone` | integer | Primary Skia milestone | +| `milestones` | integer[] | Additional milestones | +| `milestoneDeprecated` | integer | When deprecated | +| `milestoneRemoved` | integer | When removed | +| `pr` / `prUrl` | int/string | PR reference | +| `commit` / `commitUrl` | string | Commit reference | +| `issue` / `issueUrl` | int/string | Issue reference | +| `author` | string | PR/commit author | +| `affectedTypes` / `affectedMethods` | string[] | C# types/methods affected | +| `cApiFunction` / `cApiFile` | string | C API details | +| `csharpMethod` / `csharpFile` | string | C# binding details | +| `cppClass` / `cppHeader` / `cppMethod` | string | C++ header scan details | +| `platforms` | string[] | Affected platforms | +| `skiaApi` | string | Upstream Skia API name | +| `skiaFeature` / `skiaMilestone` | string/int | Upstream feature reference | +| `userValue` | string | Why an app developer would want this | +| `notes` | string | Additional context | +| `slideBullet` | string | Marketing bullet (for downstream tooling) | +| `migrationGuide` | string | Migration guide for breaking changes | +| `replacement` / `obsoleteMessage` | string | Deprecation details | +| `dependencyName` / `dependencyFrom` / `dependencyTo` | string | Dependency version tracking | +| `skillToUse` | string | Which skill to invoke for follow-up | diff --git a/.agents/skills/skia-analyst/references/skia-analyst-schema.json b/.agents/skills/skia-analyst/references/skia-analyst-schema.json new file mode 100644 index 00000000000..c538eda14f9 --- /dev/null +++ b/.agents/skills/skia-analyst/references/skia-analyst-schema.json @@ -0,0 +1,434 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/mono/SkiaSharp/skia-analyst-schema.json", + "title": "SkiaSharp Skia Analyst Report", + "description": "Unified analysis of Skia features: what shipped (changelog) and what's missing (gap analysis). Always produces both lenses from the same data.", + "type": "object", + "required": [ + "meta", + "summary", + "findings" + ], + "additionalProperties": false, + "properties": { + "meta": { + "type": "object", + "required": [ + "date", + "schemaVersion", + "repo", + "currentMilestone", + "latestUpstreamMilestone", + "releaseNotesSource" + ], + "additionalProperties": false, + "properties": { + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "schemaVersion": { + "type": "string", + "const": "1.0" + }, + "repo": { + "type": "string" + }, + "currentMilestone": { + "type": "integer", + "minimum": 79 + }, + "latestUpstreamMilestone": { + "type": "integer", + "minimum": 79 + }, + "releaseNotesSource": { + "type": "string", + "format": "uri" + }, + "refFrom": { + "type": "string", + "description": "Starting ref (tag, branch, SHA) if diffing" + }, + "refTo": { + "type": "string", + "description": "Ending ref if diffing" + }, + "shaFrom": { + "type": "string" + }, + "shaTo": { + "type": "string" + }, + "dateFrom": { + "type": "string" + }, + "dateTo": { + "type": "string" + }, + "commitCount": { + "type": "integer", + "minimum": 0 + }, + "prCount": { + "type": "integer", + "minimum": 0 + }, + "scanMode": { + "type": "string", + "enum": [ + "full", + "windowed", + "diff" + ], + "description": "full=all milestones, windowed=milestone range, diff=git ref range" + }, + "milestoneFrom": { + "type": "integer", + "minimum": 79 + }, + "milestoneTo": { + "type": "integer", + "minimum": 79 + }, + "skiaSubmoduleCommit": { + "type": "string" + } + } + }, + "summary": { + "type": "object", + "required": [ + "totalFindings" + ], + "additionalProperties": true, + "properties": { + "totalFindings": { + "type": "integer", + "minimum": 0 + }, + "byChangeType": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "byImportance": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "byBindingStatus": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "byImpact": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "byPriority": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "bySource": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "byLabel": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "findings": { + "type": "array", + "items": { + "$ref": "#/$defs/finding" + } + } + }, + "$defs": { + "changeType": { + "type": "string", + "enum": [ + "added", + "changed", + "fixed", + "deprecated", + "removed", + "dependency", + "platform", + "upstream" + ] + }, + "importance": { + "type": "string", + "enum": [ + "breaking", + "major", + "minor", + "patch" + ] + }, + "bindingStatus": { + "type": "string", + "enum": [ + "full", + "partial", + "missing", + "action_needed", + "correctly_absent", + "not_applicable" + ] + }, + "impact": { + "type": "string", + "enum": [ + "transformative", + "significant", + "moderate", + "minor" + ] + }, + "priority": { + "type": "string", + "enum": [ + "critical", + "high", + "medium", + "low" + ] + }, + "source": { + "type": "string", + "enum": [ + "release-notes", + "header-scan", + "binding-audit", + "git-diff" + ] + }, + "category": { + "type": "string", + "enum": [ + "new_feature", + "codec", + "image", + "image_filter", + "shader", + "color", + "canvas", + "path", + "font", + "utility", + "performance", + "behavior_change", + "deprecation", + "security", + "platform", + "dependency" + ] + }, + "finding": { + "type": "object", + "required": [ + "name", + "category", + "description", + "source", + "changeType", + "importance", + "bindingStatus", + "impact", + "priority" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "category": { + "$ref": "#/$defs/category" + }, + "description": { + "type": "string", + "minLength": 1 + }, + "source": { + "$ref": "#/$defs/source" + }, + "changeType": { + "$ref": "#/$defs/changeType" + }, + "importance": { + "$ref": "#/$defs/importance" + }, + "bindingStatus": { + "$ref": "#/$defs/bindingStatus" + }, + "impact": { + "$ref": "#/$defs/impact" + }, + "priority": { + "$ref": "#/$defs/priority" + }, + "effort": { + "type": "string", + "enum": [ + "trivial", + "small", + "medium", + "large" + ] + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "milestone": { + "type": "integer", + "minimum": 79 + }, + "milestones": { + "type": "array", + "items": { + "type": "integer", + "minimum": 79 + } + }, + "milestoneDeprecated": { + "type": "integer", + "minimum": 79 + }, + "milestoneRemoved": { + "type": "integer", + "minimum": 79 + }, + "skiaApi": { + "type": "string" + }, + "userValue": { + "type": "string" + }, + "notes": { + "type": "string" + }, + "pr": { + "type": "integer" + }, + "prUrl": { + "type": "string", + "format": "uri" + }, + "commit": { + "type": "string" + }, + "commitUrl": { + "type": "string", + "format": "uri" + }, + "issue": { + "type": "integer" + }, + "issueUrl": { + "type": "string", + "format": "uri" + }, + "author": { + "type": "string" + }, + "cApiFunction": { + "type": "string" + }, + "cApiFile": { + "type": "string" + }, + "csharpMethod": { + "type": "string" + }, + "csharpFile": { + "type": "string" + }, + "cppClass": { + "type": "string" + }, + "cppHeader": { + "type": "string" + }, + "cppMethod": { + "type": "string" + }, + "affectedTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "affectedMethods": { + "type": "array", + "items": { + "type": "string" + } + }, + "platforms": { + "type": "array", + "items": { + "type": "string" + } + }, + "replacement": { + "type": "string" + }, + "obsoleteMessage": { + "type": "string" + }, + "migrationGuide": { + "type": "string" + }, + "slideBullet": { + "type": "string" + }, + "dependencyName": { + "type": "string" + }, + "dependencyFrom": { + "type": "string" + }, + "dependencyTo": { + "type": "string" + }, + "skiaFeature": { + "type": "string" + }, + "skiaMilestone": { + "type": "integer", + "minimum": 79 + }, + "skillToUse": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/.agents/skills/skia-analyst/scripts/render-skia-analyst.py b/.agents/skills/skia-analyst/scripts/render-skia-analyst.py new file mode 100644 index 00000000000..3c0288a912b --- /dev/null +++ b/.agents/skills/skia-analyst/scripts/render-skia-analyst.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Render a Skia Analyst JSON report as GitHub-flavored Markdown. + +Usage: python3 render-skia-analyst.py [output.md] + +Headers start at ### so the output can be embedded directly in a GitHub issue +(# and ## are reserved for issue titles). +""" +import json +import os +import sys +from pathlib import Path + + +IMPACT_EMOJI = {'transformative': '🚀', 'significant': '⭐', 'moderate': '📦', 'minor': '🔹'} +IMPACT_DESC = { + 'transformative': 'Unlocks entirely new app categories', + 'significant': 'Major visible improvement users notice immediately', + 'moderate': 'Useful gap fill, improves safety or completeness', + 'minor': 'Small utility or completeness item', +} +PRI_EMOJI = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '⚪'} +PRI_ORDER = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3} + + +def render_item(f): + pri = PRI_EMOJI.get(f.get('priority', 'low'), '⚪') + ms_num = f['milestone'] if f.get('milestone') else 0 + if ms_num: + ms_text = f"m{ms_num}" + pad = " " * max(0, 4 - len(ms_text)) + ms = f"{pad}{ms_text}" + else: + ms = "    " + + lines = ["
", f"{pri} {ms} {f['name']}\n", + f"> {f['description']} "] + detail = [] + if f.get('skiaApi'): + detail.append(f"**Skia API:** `{f['skiaApi']}`") + if f.get('cppClass') and f.get('cppMethod'): + detail.append(f"**C++:** `{f['cppClass']}::{f['cppMethod']}`") + elif f.get('cppClass'): + detail.append(f"**C++:** `{f['cppClass']}`") + if f.get('userValue'): + detail.append(f"**User value:** {f['userValue']}") + if f.get('milestone'): + ms_detail = f"m{f['milestone']}" + if f.get('milestones'): + ms_detail += ", " + ", ".join(f"m{m}" for m in f['milestones']) + detail.append(f"**Milestones:** {ms_detail}") + if f.get('effort'): + detail.append(f"**Effort:** {f['effort']}") + if f.get('skillToUse'): + detail.append(f"**Skill:** `{f['skillToUse']}`") + if f.get('replacement'): + detail.append(f"**Replacement:** `{f['replacement']}`") + if f.get('notes'): + detail.append(f"**Notes:** {f['notes']}") + if detail: + lines.append("> \n> " + " \n> ".join(detail)) + lines.append("\n
\n") + return '\n'.join(lines) + + +def main(): + if len(sys.argv) < 2: + print("Usage: python3 render-skia-analyst.py [output.md]") + sys.exit(1) + + json_path = Path(sys.argv[1]) + if not json_path.exists(): + print(f"❌ JSON file not found: {json_path}") + sys.exit(1) + + output_path = Path(sys.argv[2]) if len(sys.argv) >= 3 else json_path.with_suffix(".md") + + try: + with open(json_path, encoding="utf-8") as f: + data = json.load(f) + except json.JSONDecodeError as e: + print(f"❌ Invalid JSON: {e}") + sys.exit(1) + + all_findings = data.get("findings", []) + actionable = [f for f in all_findings if f.get('bindingStatus') in ('missing', 'partial', 'action_needed')] + full_items = [f for f in all_findings if f.get('bindingStatus') == 'full'] + na_items = [f for f in all_findings if f.get('bindingStatus') in ('not_applicable', 'correctly_absent')] + meta = data.get("meta", {}) + + # Derive subtitle from scan mode + mode = meta.get("scanMode", "full") + if mode == "diff" and meta.get("refFrom"): + subtitle = f"{meta['refFrom']}..{meta.get('refTo', 'HEAD')}" + elif mode == "windowed" and meta.get("milestoneFrom"): + subtitle = f"m{meta['milestoneFrom']}-m{meta.get('milestoneTo', meta.get('currentMilestone', '?'))}" + else: + subtitle = f"m{meta.get('currentMilestone', '?')} (full scan)" + + missing_count = sum(1 for f in actionable if f['bindingStatus'] == 'missing') + partial_count = sum(1 for f in actionable if f['bindingStatus'] == 'partial') + action_count = sum(1 for f in actionable if f['bindingStatus'] == 'action_needed') + + md = [] + md.append("### Skia Analyst Report\n") + md.append(f"> **{len(actionable)} actionable items** · {len(full_items)} already bound · {len(na_items)} no action needed ") + md.append(f"> {subtitle} · Generated {meta.get('date', '?')}\n") + + # Collapsed legend + md.append("
") + md.append("Legend\n") + md.append("**Status** (squares)\n") + md.append("| Symbol | Meaning | Count |") + md.append("|--------|---------|-------|") + md.append(f"| 🟥 | Missing - no C API or C# wrapper | {missing_count} |") + md.append(f"| 🟨 | Partial - C API exists, needs C# wrapper | {partial_count} |") + md.append(f"| 🟧 | Action needed - wraps deprecated/removed API | {action_count} |") + md.append(f"| 🟩 | Already bound | {len(full_items)} |") + md.append(f"| ⬜ | No action needed | {len(na_items)} |") + md.append("") + md.append("**Priority** (circles)\n") + md.append("| Symbol | Meaning |") + md.append("|--------|---------|") + md.append("| 🔴 | Critical - will break on next Skia bump |") + md.append("| 🟠 | High - major capability or highly requested |") + md.append("| 🟡 | Medium - useful addition |") + md.append("| ⚪ | Low - minor utility |") + md.append("") + md.append("
\n") + + # Impact summary + md.append("| Impact | Count | What it means |") + md.append("|--------|-------|---------------|") + for imp in ['transformative', 'significant', 'moderate', 'minor']: + items = [f for f in actionable if f.get('impact') == imp] + if items: + md.append(f"| {IMPACT_EMOJI[imp]} **{imp.title()}** | {len(items)} | {IMPACT_DESC[imp]} |") + md.append("") + + # Findings by impact + for imp in ['transformative', 'significant', 'moderate', 'minor']: + items = [f for f in actionable if f.get('impact') == imp] + if not items: + continue + md.append(f"#### {IMPACT_EMOJI[imp]} {imp.title()}\n") + for f in sorted(items, key=lambda x: (PRI_ORDER.get(x.get('priority', 'low'), 3), x['name'])): + md.append(render_item(f)) + + # Already bound + md.append("
") + md.append(f"

✅ Already Bound ({len(full_items)})

\n") + md.append("These Skia features are fully available in SkiaSharp.\n") + for f in sorted(full_items, key=lambda x: x['name']): + ms = f" (m{f['milestone']})" if f.get('milestone') else "" + md.append(f"- **{f['name']}**{ms} - {f['description'][:100]}") + md.append("\n
\n") + + # No action needed + md.append("
") + md.append(f"

⚪ No Action Needed ({len(na_items)})

\n") + md.append("Internal Skia changes, automatic engine improvements, or correctly absent APIs.\n") + for f in sorted(na_items, key=lambda x: x['name']): + bs = '🚫' if f['bindingStatus'] == 'correctly_absent' else 'N/A' + ms = f" (m{f['milestone']})" if f.get('milestone') else "" + md.append(f"- {bs} **{f['name']}**{ms} - {f['description'][:100]}") + md.append("\n
\n") + + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, "w", encoding="utf-8") as f: + f.write('\n'.join(md)) + + size_kb = os.path.getsize(output_path) / 1024 + transformative = sum(1 for f in actionable if f.get('impact') == 'transformative') + print(f"✅ {output_path.name} ({size_kb:.0f} KB)") + print(f" {subtitle} · {len(actionable)} actionable · {missing_count} missing · {transformative} transformative") + print(f" Output: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/.agents/skills/skia-analyst/scripts/requirements.txt b/.agents/skills/skia-analyst/scripts/requirements.txt new file mode 100644 index 00000000000..b28700e4b17 --- /dev/null +++ b/.agents/skills/skia-analyst/scripts/requirements.txt @@ -0,0 +1 @@ +jsonschema>=4.20 diff --git a/.agents/skills/skia-analyst/scripts/validate-skia-analyst.py b/.agents/skills/skia-analyst/scripts/validate-skia-analyst.py new file mode 100644 index 00000000000..4f238381fa5 --- /dev/null +++ b/.agents/skills/skia-analyst/scripts/validate-skia-analyst.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Validate a Skia Analyst JSON report against skia-analyst-schema.json. + +Usage: python3 validate-skia-analyst.py +Exits: 0=valid, 1=fixable errors (retry), 2=fatal. +""" +import json +import sys +from pathlib import Path +from collections import Counter + +if len(sys.argv) != 2: + print("Usage: python3 validate-skia-analyst.py ") + sys.exit(2) + +path = Path(sys.argv[1]) +if not path.exists(): + print(f"❌ File not found: {path}") + sys.exit(2) + +schema_path = Path(__file__).resolve().parent / "../references/skia-analyst-schema.json" +if not schema_path.exists(): + print(f"❌ Schema not found: {schema_path}") + sys.exit(2) + +try: + with open(path) as f: + data = json.load(f) +except json.JSONDecodeError as e: + print(f"❌ Invalid JSON: {e}") + sys.exit(2) + +with open(schema_path) as f: + schema = json.load(f) + +errors = [] +warnings = [] + +# --- JSON Schema validation --- +try: + from jsonschema import Draft202012Validator + validator = Draft202012Validator(schema) + for e in validator.iter_errors(data): + p = ".".join(str(x) for x in e.absolute_path) or "(root)" + errors.append(f"Schema: {p}: {e.message}") +except ImportError: + print("⚠️ jsonschema not installed — run: pip install jsonschema") + +# --- Extra checks --- +meta = data.get("meta", {}) +summary = data.get("summary", {}) +findings = data.get("findings", []) + +actual_total = len(findings) +if summary.get("totalFindings", 0) != actual_total: + errors.append(f"summary.totalFindings ({summary.get('totalFindings')}) != actual ({actual_total})") + +# Check summary counts +for field, getter in [ + ("byChangeType", lambda f: f.get("changeType")), + ("byImportance", lambda f: f.get("importance")), + ("byBindingStatus", lambda f: f.get("bindingStatus")), + ("byImpact", lambda f: f.get("impact")), + ("byPriority", lambda f: f.get("priority")), + ("bySource", lambda f: f.get("source")), +]: + expected = summary.get(field, {}) + actual = Counter(getter(f) for f in findings) + for k, v in expected.items(): + if actual.get(k, 0) != v: + warnings.append(f"summary.{field}.{k} ({v}) != actual ({actual.get(k, 0)})") + +# Unique names +names = [f.get("name") for f in findings] +dupes = [n for n in set(names) if names.count(n) > 1] +if dupes: + warnings.append(f"Duplicate names: {dupes[:5]}") + +# --- Output --- +if warnings: + for w in warnings: + print(f" ⚠️ {w}") + +if errors: + print(f"\n❌ {len(errors)} validation error(s) in {path.name}:\n") + for e in errors: + print(f" ❌ {e}") + sys.exit(1) + +ms = meta.get("currentMilestone", "?") +mode = meta.get("scanMode", "?") +impact = Counter(f.get("impact") for f in findings) +missing = sum(1 for f in findings if f.get("bindingStatus") == "missing") +print(f"✅ {path.name} is valid (m{ms}, {mode}, {actual_total} findings, {missing} missing, " + f"{impact.get('transformative', 0)} transformative)") +sys.exit(0) diff --git a/.agents/skills/skia-feature-scout/SKILL.md b/.agents/skills/skia-feature-scout/SKILL.md deleted file mode 100644 index 958e733d244..00000000000 --- a/.agents/skills/skia-feature-scout/SKILL.md +++ /dev/null @@ -1,255 +0,0 @@ ---- -name: skia-feature-scout -description: > - Scout Google's Skia release notes for new features, APIs, and capabilities that SkiaSharp should - expose. Fetches upstream RELEASE_NOTES.md, extracts notable items per milestone, and cross-references - against SkiaSharp's C API shims and C# bindings to produce a gap analysis with prioritized - recommendations. Use this skill whenever the user asks about "what's new in Skia", "what are we - missing", "feature gap analysis", "new Skia APIs", "what should we bind next", "release notes - scout", "upstream feature audit", "what cool stuff did Google add", or "check for new Skia - features". Also use proactively when the user mentions a Skia milestone bump or asks what work - a bump will unlock. Do NOT use the release-notes-audit skill for this — this skill provides an - independent, complementary perspective focused on developer-facing value rather than API coverage - counts. ---- - -# Skia Feature Scout - -You are an analyst who reads Google's Skia release notes and identifies features that would add -value to SkiaSharp users. Your goal is to find the gems — new capabilities, formats, visual quality -improvements, performance wins, and powerful APIs — and determine which ones SkiaSharp is missing. - -You also go beyond the release notes: for types SkiaSharp already binds, you inspect the upstream -C++ headers to find new methods or overloads that Google added quietly without mentioning in -release notes. - -## Why This Matters - -SkiaSharp wraps Skia via a C API shim layer. When Google adds a major feature to Skia, it doesn't -automatically appear in SkiaSharp — someone needs to: -1. Notice the feature exists -2. Decide it's worth exposing -3. Create a C API wrapper in `externals/skia/include/c/` and `externals/skia/src/c/` -4. Generate P/Invoke bindings -5. Write the C# wrapper in `binding/SkiaSharp/` - -This skill handles step 1 and 2 so the team can prioritize steps 3-5. - -## Key References - -- **[references/audit-instructions.md](references/audit-instructions.md)** — Complete agent instructions: extraction criteria, binding verification, hidden API scan methodology, accuracy tips. **Agents read this before starting.** -- **[references/schema-cheatsheet.md](references/schema-cheatsheet.md)** — Human-readable JSON output schema -- **[references/feature-scout-schema.json](references/feature-scout-schema.json)** — JSON Schema (Draft 2020-12) for machine validation - -## Modes of Operation - -The skill supports two modes. The user may specify one or you can suggest the right one: - -- **Full scan** (default): Reads ALL milestones from the release notes and the full history of - C++ headers. Best for initial discovery or periodic strategic audits. -- **Windowed scan**: Reads only a milestone range (e.g., m119→m133). Best for auditing a specific - Skia bump. Ask the user for the previous and current milestones, or detect them. - -## Workflow - -The skill uses a **dual-model / synthesize / verify** architecture. Two different AI models each -independently do a complete audit (release notes + hidden APIs + binding checks), then you merge -their findings, resolve conflicts, and verify accuracy. This approach catches items that any single -model would miss — testing shows individual runs miss 15-30% of findings the other model catches, -with almost no overlap in their blind spots. - -``` -Phase 1: Setup (fetch notes, determine milestone, locate C API) -Phase 2: Launch two parallel full-audit agents (different models) - ├─ Agent 1 (Opus 4.7): Full audit — notes + hidden APIs + binding check - └─ Agent 2 (GPT 5.4): Full audit — notes + hidden APIs + binding check -Phase 3: Synthesize — merge both agents' findings, dedupe, resolve conflicts -Phase 4: Verify — you spot-check high-priority items directly -Phase 5: Generate outputs (JSON → validate → HTML → markdown) -Phase 6: Offer next steps -``` - -### Phase 1: Setup - -**1a. Fetch Release Notes** - -Fetch the full Skia release notes from upstream: - -``` -https://raw.githubusercontent.com/google/skia/main/RELEASE_NOTES.md -``` - -The file is large (100KB+). Fetch it in 20KB chunks using `web_fetch` with increasing `start_index` -until you've read the entire file. Save to a temp file so agents can read it: - -```bash -cat > /tmp/skia-release-notes.md << 'NOTES' - -NOTES -``` - -In **windowed mode**, you can stop once you've read past the target milestone range, but always -read at least a few milestones beyond the current one to catch future items. - -**1b. Determine Current Milestone** - -```bash -cd externals/skia && git log --oneline -1 -cat externals/skia/include/core/SkMilestone.h 2>/dev/null -``` - -The user may also tell you directly. Everything at or below this milestone is "available now". -Everything above is "coming with bump". - -**1c. Locate C API headers** - -The skia submodule may not be checked out in this worktree. Find the C API: - -```bash -ls externals/skia/include/c/ 2>/dev/null -# Fallback to main repo checkout -ls /path/to/main/SkiaSharp/externals/skia/include/c/ 2>/dev/null -``` - -Record the path for agents to use. - -### Phase 2: Launch Two Full-Audit Agents - -Launch **two** background `task` agents simultaneously, using **different models**. Each does the -same complete job — read ALL release notes, scan C++ headers for hidden APIs, check bindings. Their -different perspectives and blind spots complement each other. - -Give each agent this prompt (adapted with actual paths and milestone): - -``` -task agent_type=general-purpose mode=background model=claude-opus-4.7 name=scout-opus: -task agent_type=general-purpose mode=background model=gpt-5.4 name=scout-gpt: - - "You are auditing Skia release notes for SkiaSharp. SkiaSharp is on milestone M{current}. - - FIRST: Read .agents/skills/skia-feature-scout/references/audit-instructions.md — it contains - the complete criteria, methodology, type mapping, and accuracy tips for this audit. - - THEN: Do the three tasks described in that file: - 1. RELEASE NOTES SCAN — Read /tmp/skia-release-notes.md (ALL milestones). Extract features. - 2. HIDDEN API SCAN — Fetch upstream C++ headers, compare against C API at {c_api_path}. - 3. BINDING VERIFICATION — Check C API, C# wrappers, generated interop, deprecations. - - Save your complete findings as JSON to {output_path}/{agent_name}-findings.json" -``` - -### Phase 3: Synthesize - -When both agents complete, merge their findings. This is your most important job — -you are the quality gate. - -1. **Collect** all feature items from both agents into a combined list -2. **Deduplicate** — if both found the same feature (by skiaApi name or description), merge them: - - Keep the richer description / userValue - - If they disagree on bindingStatus, the more cautious one wins (e.g., `missing` beats `full`) - - If they disagree on priority, keep the higher one and note the disagreement -3. **Union the hidden APIs** — combine both agents' hidden API lists, deduplicating by method name. - Testing shows the two models find almost completely different hidden APIs (only ~6% overlap), so - the union is much richer than either alone. -4. **Cross-reference binding statuses**: - - If one agent says `full` and the other says `missing`, investigate directly (Phase 4) - - If one found `partial` bindings (C API exists, no C# wrapper), keep those — they're quick wins - - If either found implementation bugs (wrong C API calls), mark as `action_needed` - - If either found hidden generated interop fields, add as `partial` quick wins -5. **Track feature lifecycles** — merge entries for features that appear across multiple milestones - into single items with milestoneIntroduced + milestoneEnhanced + milestoneDeprecated + milestoneRemoved -6. **Extract performance notes** into the `performance` array -7. **Extract deprecations** into the `deprecations` array with concrete `[Obsolete]` messages -8. **Build `nextSteps`** ordered by priority, with `skillToUse` and `effort` - -### Phase 4: Verify High-Priority Findings - -For every item marked `critical` or `high` priority, and every item marked `full` (to confirm -they're not false positives), do a direct verification yourself: - -```bash -# For each high-priority "missing" item — confirm it's truly missing -grep -ril "function_name_or_keyword" binding/SkiaSharp/ externals/skia/include/c/ externals/skia/src/c/ - -# For each "full" item — confirm the C# wrapper exists AND calls the right native function -grep -n "MethodName" binding/SkiaSharp/SKRelevantFile.cs -``` - -This catches hallucinations where an agent claims something exists (or doesn't) incorrectly. -Pay special attention to the **implementation**, not just the signature — a method may exist by -name but forward to the wrong native function (the "ToRawShader bug" pattern). - -### Phase 5: Generate Outputs - -The synthesis from Phase 3 should have produced all the data. Now generate the three output -formats. - -**5a. Generate Structured JSON Report** - -Produce a JSON report following the schema in [references/schema-cheatsheet.md](references/schema-cheatsheet.md). -The formal JSON Schema is at [references/feature-scout-schema.json](references/feature-scout-schema.json). -Save to the artifacts directory as `skia-feature-scout-YYYY-MM-DD.json`. - -The JSON must include: -- `meta` — audit metadata (date, milestones, source) -- `summary` — counts by status and priority -- `findings` — every cataloged feature with full binding details -- `deprecations` — APIs needing `[Obsolete]` markers, with exact SkiaSharp API name/file, - the Skia milestone when deprecated, the replacement API, and a suggested `[Obsolete("...")]` msg -- `hiddenApis` — features discovered via C++ header scan (not in release notes) -- `performance` — performance-related changes worth noting -- `nextSteps` — prioritized action items, each with `skillToUse` and `effort` - -**5b. Validate JSON Report** - -> 🛑 **MANDATORY:** Always validate before rendering. - -```bash -pip3 install -r .agents/skills/skia-feature-scout/scripts/requirements.txt --quiet -python3 .agents/skills/skia-feature-scout/scripts/validate-feature-scout.py -``` - -Exit codes: 0=valid, 1=fixable (regenerate), 2=fatal. Fix and re-validate if it fails. - -**5c. Render HTML Report** - -> 🛑 **MANDATORY:** Always generate the HTML report. - -```bash -python3 .agents/skills/skia-feature-scout/scripts/render-feature-scout.py -``` - -**5d. Generate Markdown Summary** - -Present a concise markdown summary in the conversation, grouped by urgency: - -1. 🔴 **Critical** — Will break on next Skia bump -2. ⚠️ **Action Needed** — Deprecated APIs missing `[Obsolete]` markers -3. ❌ **Missing (High)** — Major features with no binding -4. 🔶 **Missing (Medium)** — Useful features to plan for -5. 🟢 **Full** — Features already bound -6. 🟡 **Quick Wins** — C API exists, just needs C# wrapper -7. 🆕 **Hidden APIs** — Discovered via C++ scan, not in release notes -8. ⚡ **Performance** — Speed/memory improvements -9. 🔄 **Behavior Changes** — Silent semantic changes - -Include: Before/After milestone split, Deprecation Watch, Recommended Action Plan with skill routing. - -### Phase 6: Offer Next Steps - -After presenting the report, offer: -1. "Want me to investigate any of these features in more detail?" -2. "Should I create issues or todos for the high-priority items?" -3. "Want me to use the `api-add-review` skill to start binding a specific feature?" -4. "Should I set up a periodic workflow to re-run this audit?" - ---- - -## Feature Extraction Criteria & Tips - -See [references/audit-instructions.md](references/audit-instructions.md) for the complete: -- Include/Exclude criteria -- Categories and priority classification -- Binding status definitions -- C++ header scan methodology and type mapping table -- Tips for accurate assessment (lessons from prior runs) diff --git a/.agents/skills/skia-feature-scout/evals/evals.json b/.agents/skills/skia-feature-scout/evals/evals.json deleted file mode 100644 index 338f152d650..00000000000 --- a/.agents/skills/skia-feature-scout/evals/evals.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "skill_name": "skia-feature-scout", - "evals": [ - { - "id": 1, - "prompt": "What cool new features has Google added to Skia that we're missing in SkiaSharp? We're on milestone 133.", - "expected_output": "Full scan report covering M78-M148. Should find SkMesh, gradient interpolation, SkImageFilters::RuntimeShader, HDR metadata, SkPath immutability, and other high-value gaps. Should produce JSON + HTML + markdown summary.", - "files": [], - "expectations": [ - "Fetches the full Skia RELEASE_NOTES.md from GitHub (all chunks, not just the first 20KB)", - "Launches two background audit agents on different models (e.g. Opus and GPT)", - "Produces a JSON file that passes validate-feature-scout.py with exit code 0", - "Produces an HTML viewer file via render-feature-scout.py", - "JSON meta.schemaVersion is '2.0'", - "JSON meta.mode is 'full'", - "JSON meta.currentMilestone is 133", - "JSON meta.latestUpstreamMilestone is >= 145", - "JSON items array contains at least 25 features", - "JSON summary.totalItems equals the actual length of the items array", - "JSON summary counts (full + partial + missing + actionNeeded + correctlyAbsent + notApplicable) equal totalItems", - "At least one item with name containing 'Mesh' or 'SkMesh', bindingStatus 'missing', priority 'high'", - "At least one item mentioning gradient interpolation or color space interpolation, priority 'high'", - "At least one item mentioning SkPath immutability or SkPathBuilder, priority 'critical' or 'high'", - "At least one item mentioning RuntimeShader in image filters, bindingStatus 'missing'", - "At least one item mentioning HDR metadata or skhdr::Metadata", - "Items with bindingStatus 'full' all have cApiStatus 'present' and csharpStatus 'present'", - "Items with bindingStatus 'partial' all have cApiStatus 'present'", - "JSON hiddenApis array has at least 3 entries from the C++ header scan", - "JSON hiddenApis entries all have non-empty cppClass, cppHeader, and cppMethod fields", - "JSON summary.hiddenApiCount equals the actual length of the hiddenApis array", - "JSON performance array has at least 1 entry", - "JSON summary.performanceCount equals the actual length of the performance array", - "JSON nextSteps array is non-empty and the first item has severity 'critical' or 'high'", - "JSON nextSteps includes at least one entry with a non-null skillToUse field", - "Markdown summary presented to the user includes a 'Before vs After' or timeline section", - "Markdown summary includes a Quick Wins or Partial section highlighting easy bindings", - "The orchestrator verifies at least 3 high-priority findings by running grep directly" - ] - }, - { - "id": 2, - "prompt": "I'm about to bump Skia from m132 to m133. Can you scout the release notes and tell me what changed in this window?", - "expected_output": "Windowed scan for m132-m133. Should find SkColorSpace::MakeCICP as the key new feature (already bound). Should flag GPU timing and canvas save prealloc. Smaller item count than full scan.", - "files": [], - "expectations": [ - "Produces a JSON file that passes validate-feature-scout.py with exit code 0", - "Produces an HTML viewer file via render-feature-scout.py", - "JSON meta.mode is 'windowed'", - "JSON meta.previousMilestone is 132", - "JSON meta.currentMilestone is 133", - "At least one item mentions MakeCICP or CICP with bindingStatus 'full'", - "JSON items array has at least 3 items and no more than 30 items (windowed scan should be focused)", - "JSON summary.totalItems equals the actual length of the items array", - "Items are primarily in the m132-m133 range (majority of milestoneIntroduced between 130 and 135)" - ] - }, - { - "id": 3, - "prompt": "scout for new Skia features — especially anything related to HDR, color management, or image formats we should support", - "expected_output": "Full scan with emphasis on HDR/color/codec features. Should prominently feature skhdr::Metadata, skhdr::Agtm, CICP color spaces, AVIF, JPEG XL, new color types (R16), and gradient interpolation color spaces.", - "files": [], - "expectations": [ - "Produces a JSON file that passes validate-feature-scout.py with exit code 0", - "At least one item mentions HDR metadata or skhdr::Metadata with category 'color' or 'new_feature'", - "At least one item mentions AGTM or Adaptive Global Tone Mapping", - "At least one item mentions AVIF with category 'codec'", - "At least one item mentions CICP or MakeCICP", - "At least one item mentions R16 or kR16 color type with category 'color'", - "At least one item mentions gradient interpolation or color space interpolation", - "All items related to HDR, color, or codec have priority 'high' or 'medium' (none are 'low')", - "JSON deprecations array flags the ICC profile removal in M142 encoder options if any deprecations are present", - "Markdown summary highlights HDR and color features prominently in the first sections" - ] - }, - { - "id": 4, - "prompt": "check what new Skia APIs we might be missing — also look at the C++ headers for SkImage and SkCanvas to see if there are hidden methods we haven't wrapped", - "expected_output": "Full scan plus C++ header discovery. hiddenApis array should be non-empty with finds from SkImage.h and SkCanvas.h.", - "files": [], - "expectations": [ - "Produces a JSON file that passes validate-feature-scout.py with exit code 0", - "JSON hiddenApis array has at least 3 entries", - "At least one hidden API entry has cppClass 'SkImage'", - "At least one hidden API entry has cppClass 'SkCanvas'", - "All hidden API entries have non-empty cppHeader field pointing to an include/ path", - "All hidden API entries have a non-empty description explaining user value", - "JSON summary.hiddenApiCount equals the actual length of the hiddenApis array", - "Regular items array is also present with at least 15 release-notes-based features", - "Markdown summary includes a 'Hidden APIs' section or mentions hidden/undocumented discoveries" - ] - }, - { - "id": 5, - "prompt": "what performance improvements has Skia made recently that would benefit our users? also check for any behavior changes we should know about", - "expected_output": "Should identify performance notes and behavior changes from release notes. Performance array and items with category 'performance' or 'behavior_change' should be populated.", - "files": [], - "expectations": [ - "Produces a JSON file that passes validate-feature-scout.py with exit code 0", - "JSON performance array has at least 2 entries", - "At least one performance entry mentions Perlin noise improvement or noise shader speedup", - "All performance entries have non-empty area, description, and impact fields", - "All performance entries have a boolean bindingActionNeeded field", - "JSON summary.performanceCount equals the actual length of the performance array", - "At least one item in the items array has category 'behavior_change'", - "At least one behavior_change item mentions kRec709 or transfer function change", - "Markdown summary includes a performance or optimization section", - "Markdown summary includes a behavior changes section or mentions silent changes" - ] - } - ] -} diff --git a/.agents/skills/skia-feature-scout/references/audit-instructions.md b/.agents/skills/skia-feature-scout/references/audit-instructions.md deleted file mode 100644 index 1585ef4909f..00000000000 --- a/.agents/skills/skia-feature-scout/references/audit-instructions.md +++ /dev/null @@ -1,233 +0,0 @@ -# Skia Feature Scout — Agent Audit Instructions - -Read this entire file before starting your audit. It contains everything you need: what to look -for, how to check bindings, how to scan C++ headers, and accuracy tips learned from prior runs. - ---- - -## Part 1: Feature Extraction Criteria - -### Include (high signal) - -- Brand new classes or APIs (SkMesh, skhdr::Metadata, SkAnimatedImage, etc.) -- New codec/format support (AVIF, JPEG XL, animated WebP encoding) -- New color types or color space features (SkColorInfo, reinterpretColorSpace, etc.) -- Shader capabilities (CoordClamp, gradient interpolation spaces) -- Image filter additions (RuntimeShader, Crop with TileMode) -- Canvas/Surface enhancements that affect rendering quality -- Utility methods that make common tasks easier -- Significant API migrations (SkPath → SkPathBuilder) -- **Performance improvements** — rendering speedups, memory reductions, decode optimizations -- **Behavior changes** — existing APIs that silently changed semantics -- **Codec introspection** — SelectionPolicy, getICCProfile, isAnimated, hasHighBitDepthEncodedData -- **GPU interop** — async rescale/readback, MSAA resolve, anisotropic filtering -- **Text APIs** — SkTextBlob::Iter, palette overrides, font argument extensions -- **Sampling options extensions** — anisotropic max level, new filter modes - -### Exclude (noise) - -- Internal refactoring (moving headers between directories) -- GPU backend-specific changes (Graphite, Dawn, Vulkan internals) unless they affect Ganesh/GL/Metal -- Build system changes (GN flags, defines) -- SkSL parser/compiler fixes (unless they unlock new capabilities) -- Thread safety or memory management internals -- Removals of already-deprecated APIs (unless SkiaSharp still uses them) - -### Output Fields per Feature - -| Field | Description | -|-------|-------------| -| **name** | Short descriptive name | -| **category** | See categories table below | -| **milestone** | When it first appeared | -| **milestones** | Later milestones that improved/extended it (comma-separated) | -| **milestoneDeprecated** | When it was deprecated (if applicable) | -| **milestoneRemoved** | When it was removed (if applicable) | -| **skiaApi** | The upstream Skia API name(s) | -| **description** | 2-3 sentences: what it does AND why it matters to SkiaSharp users | -| **userValue** | One sentence: why an app developer would want this | -| **priority** | `critical`, `high`, `medium`, or `low` | - -### Categories - -| Category | What belongs here | -|----------|-------------------| -| `new_feature` | Brand new class, API surface, or capability | -| `codec` | Image format encode/decode support | -| `image` | SkImage methods, factories, transformations | -| `image_filter` | SkImageFilter factories and enhancements | -| `shader` | Shader factories, gradient features, noise | -| `color` | Color types, color spaces, color filters | -| `canvas` | SkCanvas/SkSurface methods and flags | -| `path` | SkPath, SkPathBuilder, path effects | -| `font` | Font/typeface features | -| `utility` | Small helpers, data types, convenience APIs | -| `performance` | Speed improvements, memory optimizations, decode/encode perf | -| `behavior_change` | Existing API changed semantics silently | -| `deprecation` | API deprecated or removed | - -### Priority Classification - -| Priority | Criteria | -|----------|----------| -| `critical` | Will cause compile/link/runtime failures on next Skia bump. Migration required. | -| `high` | Major new capability, popular format, or highly requested feature | -| `medium` | Useful addition, quality improvement, or niche but valuable | -| `low` | Minor utility, internal concern, or auto-available | - ---- - -## Part 2: Binding Verification - -For each feature you extract from the release notes, check whether SkiaSharp already has it. - -### How to Check - -1. **C API layer** — Search `externals/skia/include/c/*.h` and `externals/skia/src/c/*.cpp`: - ```bash - grep -ril "keyword" externals/skia/include/c/ externals/skia/src/c/ - ``` - -2. **C# binding layer** — Search `binding/SkiaSharp/*.cs`: - ```bash - grep -ril "keyword" binding/SkiaSharp/ - ``` - -3. **Generated bindings** — Check `binding/SkiaSharp/SkiaApi.generated.cs` for P/Invoke entries - and internal struct fields that the public C# option types may not expose (especially encoder - options: ICC, XMP, gainmap, HDR metadata fields). - -If the submodule isn't checked out in this worktree, try the main repo checkout or use GitHub API: -```bash -# Main repo fallback -ls /path/to/main/SkiaSharp/externals/skia/include/c/ -# Or GitHub API -github-mcp-server-get_file_contents owner=mono repo=skia path=include/c/sk_image.h -``` - -### Binding Status Classification - -| Status | Badge | Description | -|--------|-------|-------------| -| `full` | ✅ | C API + C# wrapper both exist and cover the feature | -| `partial` | 🟡 | C API exists but C# wrapper is missing (**quick win!**) | -| `missing` | ❌ | Neither C API nor C# wrapper exist | -| `correctly_absent` | 🚫 | Skia removed this and SkiaSharp correctly never wrapped it | -| `action_needed` | ⚠️ | SkiaSharp wraps something Skia deprecated/removed without `[Obsolete]` | -| `not_applicable` | ⚪ | Doesn't need a binding (internal, auto-available, Graphite-only) | - -### Deprecation Checking - -Also look for SkiaSharp APIs that wrap things Skia has deprecated or removed. For each, record: -- The exact SkiaSharp API name and file -- The Skia milestone when deprecated -- The replacement API -- A suggested `[Obsolete("...")]` message - ---- - -## Part 3: Hidden API Discovery (C++ Header Scan) - -For types that SkiaSharp already binds, check the upstream C++ headers for new methods that were -added without a release notes mention. - -### How it works - -1. For each type in the mapping table below, fetch the upstream header from Google's Skia repo: - ``` - https://raw.githubusercontent.com/google/skia/main/include/core/SkImage.h - ``` - Also check `include/effects/` for filter/shader types. - -2. Extract public method signatures from the C++ header. - -3. Compare against the C API functions in `externals/skia/include/c/sk_image.h` (our fork). - -4. Any public C++ method that has no corresponding C API function is a **hidden gap**. Evaluate - whether it's worth binding based on user value. - -5. Skip internal/friend/protected methods and anything Graphite-specific. - -### Focus on these high-value headers first - -(Most likely to have hidden gems) -- `SkImage.h` — new factory methods, transformations -- `SkCanvas.h` — new draw methods -- `SkImageFilters.h` — new filter factories -- `SkShader.h` / `SkShaders` — new shader factories -- `SkCodec.h` — new decode capabilities -- `SkColorFilter.h` — new filter types -- `SkPath.h` + `SkPathBuilder.h` — path construction changes -- `SkBitmap.h` — pixel manipulation -- `SkTextBlob.h` — text blob iteration and construction -- `SkBlendMode.h` — blend mode queries (e.g., AsCoeff) - -### C# → C API → C++ Type Mapping - -| C# | C API prefix | C++ header | -|----|-------------|------------| -| `SKCanvas` | `sk_canvas_*` | `include/core/SkCanvas.h` | -| `SKImage` | `sk_image_*` | `include/core/SkImage.h` | -| `SKPaint` | `sk_paint_*` | `include/core/SkPaint.h` | -| `SKPath` | `sk_path_*` | `include/core/SkPath.h` | -| `SKShader` | `sk_shader_*` | `include/core/SkShader.h` | -| `SKColorFilter` | `sk_colorfilter_*` | `include/core/SkColorFilter.h` | -| `SKImageFilter` | `sk_imagefilter_*` | `include/effects/SkImageFilters.h` | -| `SKCodec` | `sk_codec_*` | `include/codec/SkCodec.h` | -| `SKBitmap` | `sk_bitmap_*` | `include/core/SkBitmap.h` | -| `SKPixmap` | `sk_pixmap_*` | `include/core/SkPixmap.h` | -| `SKSurface` | `sk_surface_*` | `include/core/SkSurface.h` | -| `SKFont` | `sk_font_*` | `include/core/SkFont.h` | -| `SKTypeface` | `sk_typeface_*` | `include/core/SkTypeface.h` | -| `SKData` | `sk_data_*` | `include/core/SkData.h` | -| `SKColorSpace` | `sk_colorspace_*` | `include/core/SkColorSpace.h` | -| `SKTextBlob` | `sk_textblob_*` | `include/core/SkTextBlob.h` | -| *(not bound)* | — | `include/core/SkPathBuilder.h` | -| *(not bound)* | — | `include/core/SkBlendMode.h` | - -### Hidden API Output Format - -For each hidden gap, output: -```json -{ "cppClass": "SkImage", "cppHeader": "include/core/SkImage.h", - "cppMethod": "reinterpretColorSpace(sk_sp)", - "description": "Reinterpret image as different color space without conversion", - "cApiStatus": "missing", "csharpStatus": "missing", - "priority": "medium", "notes": "Useful for metadata-driven image workflows" } -``` - ---- - -## Part 4: Tips for Accurate Assessment - -These tips come from lessons learned in prior audit runs. Ignoring them leads to false positives -and missed findings. - -- **Don't confuse enum values with full support.** Having `AVIF` in an encoded format enum doesn't - mean AVIF decoding is fully wired up in C#. -- **Check for the actual C# method, not just the class.** A class may exist but be missing specific - overloads (e.g., DropShadow exists but only with SKColor, not SKColor4f). -- **Verify C# wrappers actually call the right C API.** A wrapper may exist with the right name but - forward to the wrong native function. For example, check that `ToRawShader` actually calls a raw - shader C API and not the regular `makeShader`. **Read the implementation, not just the signature.** -- **Check SkiaApi.generated.cs for hidden plumbing.** The generated interop file may contain fields - (e.g., gainmap, ICC profile, XMP metadata) in internal structs that the public C# option types - don't expose. These are quick wins — the native plumbing exists, just needs a public wrapper. -- **Runtime effects children vs image filter children are different.** SKRuntimeEffect supporting - children doesn't mean SkImageFilters::RuntimeShader is bound. -- **Path features need special attention.** SkPath immutability is a massive migration that affects - the entire SkiaSharp path API surface. -- **Gradient interpolation is a high-value gap.** CSS Color Level 4 gradient interpolation produces - dramatically better gradients. This is a visible quality improvement users will notice. -- **Track API churn across milestones.** Some APIs are added then removed (e.g., ICC profile fields - in encoder options were added in M108 then removed in M142). Flag these lifecycle issues. -- **Performance notes matter.** A Perlin noise speedup or decode optimization benefits users without - any binding changes needed — but they should know about it. -- **Behavior changes can cause subtle bugs.** If Skia changed how `kRec709` transfer function works, - apps may see color shifts. Flag these even if no binding change is required. -- **The mono/skia fork may retain deprecated APIs** that upstream removed. This isn't a bug — it's - intentional for backward compatibility. Flag it but don't classify as broken. -- **C++ headers are the source of truth.** Release notes are curated highlights. The headers contain - everything. When in doubt, check the header. -- **Don't skip very old milestones.** Features from M78-M90 like SkTextBlob::Iter, SkBlendMode_AsCoeff, - SkColorInfo, and SkImage::reinterpretColorSpace are easily overlooked but still valuable. diff --git a/.agents/skills/skia-feature-scout/references/feature-scout-schema.json b/.agents/skills/skia-feature-scout/references/feature-scout-schema.json deleted file mode 100644 index cdfc99fa037..00000000000 --- a/.agents/skills/skia-feature-scout/references/feature-scout-schema.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/mono/SkiaSharp/feature-scout-schema.json", - "title": "Skia Feature Scout Report", - "description": "Structured output from the Skia Feature Scout skill. All findings (release notes features, hidden APIs, performance notes, deprecations) are in a single unified array with labels for filtering.", - "type": "object", - "required": ["meta", "summary", "findings"], - "additionalProperties": false, - "properties": { - "meta": { - "type": "object", - "required": ["date", "schemaVersion", "mode", "currentMilestone", "latestUpstreamMilestone", "releaseNotesSource"], - "additionalProperties": false, - "properties": { - "date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, - "schemaVersion": { "type": "string", "const": "3.0" }, - "mode": { "type": "string", "enum": ["full", "windowed"] }, - "previousMilestone": { "type": "integer", "minimum": 79 }, - "currentMilestone": { "type": "integer", "minimum": 79 }, - "latestUpstreamMilestone": { "type": "integer", "minimum": 79 }, - "skiaSubmoduleCommit": { "type": "string" }, - "releaseNotesSource": { "type": "string", "format": "uri" } - } - }, - "summary": { - "type": "object", - "required": ["totalFindings"], - "additionalProperties": true, - "properties": { - "totalFindings": { "type": "integer", "minimum": 0 }, - "byStatus": { - "type": "object", - "additionalProperties": { "type": "integer", "minimum": 0 } - }, - "byPriority": { - "type": "object", - "additionalProperties": { "type": "integer", "minimum": 0 } - }, - "bySource": { - "type": "object", - "additionalProperties": { "type": "integer", "minimum": 0 } - }, - "byLabel": { - "type": "object", - "additionalProperties": { "type": "integer", "minimum": 0 } - } - } - }, - "findings": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#/$defs/finding" } - }, - "nextSteps": { - "type": "array", - "items": { "$ref": "#/$defs/nextStep" } - } - }, - "$defs": { - "category": { - "type": "string", - "enum": [ - "new_feature", "codec", "image", "image_filter", "shader", - "color", "canvas", "path", "font", "utility", - "performance", "behavior_change", "deprecation" - ] - }, - "bindingStatus": { - "type": "string", - "enum": ["full", "partial", "missing", "action_needed", "correctly_absent", "not_applicable"] - }, - "priority": { - "type": "string", - "enum": ["critical", "high", "medium", "low"] - }, - "source": { - "type": "string", - "enum": ["release-notes", "header-scan", "binding-audit"] - }, - "finding": { - "type": "object", - "required": ["name", "category", "priority", "description", "bindingStatus", "source"], - "additionalProperties": false, - "properties": { - "name": { "type": "string", "minLength": 1 }, - "category": { "$ref": "#/$defs/category" }, - "priority": { "$ref": "#/$defs/priority" }, - "description": { "type": "string", "minLength": 1 }, - "bindingStatus": { "$ref": "#/$defs/bindingStatus" }, - "source": { "$ref": "#/$defs/source" }, - "labels": { - "type": "array", - "items": { "type": "string" }, - "description": "Freeform tags: quick-win, hidden-api, perf, behavior-change, action-needed, etc." - }, - - "milestone": { "type": "integer", "minimum": 79, "description": "Primary milestone (introduced or relevant)" }, - "milestones": { - "type": "array", - "items": { "type": "integer", "minimum": 79 }, - "description": "Additional milestones where this was enhanced/changed" - }, - "milestoneDeprecated": { "type": "integer", "minimum": 79 }, - "milestoneRemoved": { "type": "integer", "minimum": 79 }, - - "skiaApi": { "type": "string" }, - "userValue": { "type": "string" }, - "notes": { "type": "string" }, - - "cApiFunction": { "type": "string" }, - "cApiFile": { "type": "string" }, - "csharpMethod": { "type": "string" }, - "csharpFile": { "type": "string" }, - - "cppClass": { "type": "string" }, - "cppHeader": { "type": "string" }, - "cppMethod": { "type": "string" }, - - "replacement": { "type": "string" }, - "obsoleteMessage": { "type": "string" }, - - "impact": { "type": "string" }, - "bindingActionNeeded": { "type": "boolean" }, - - "skillToUse": { "type": "string" }, - "effort": { "type": "string", "enum": ["trivial", "small", "medium", "large"] } - } - }, - "nextStep": { - "type": "object", - "required": ["severity", "action", "reason"], - "additionalProperties": false, - "properties": { - "severity": { "$ref": "#/$defs/priority" }, - "action": { "type": "string" }, - "milestone": { "type": ["integer", "null"] }, - "reason": { "type": "string" }, - "skillToUse": { "type": ["string", "null"] }, - "effort": { - "type": ["string", "null"], - "enum": ["trivial", "small", "medium", "large", null] - } - } - } - } -} diff --git a/.agents/skills/skia-feature-scout/references/schema-cheatsheet.md b/.agents/skills/skia-feature-scout/references/schema-cheatsheet.md deleted file mode 100644 index a84de97367a..00000000000 --- a/.agents/skills/skia-feature-scout/references/schema-cheatsheet.md +++ /dev/null @@ -1,200 +0,0 @@ -# Feature Scout Report Schema (v3) - -JSON schema for the Skia Feature Scout report. One unified `findings` array instead of -separate arrays for items, hidden APIs, performance, and deprecations. The formal JSON Schema -is at `feature-scout-schema.json`. - -## Top-Level Structure - -```json -{ - "meta": { ... }, - "summary": { ... }, - "findings": [ ... ], - "nextSteps": [ ... ] -} -``` - -## `meta` — Audit Metadata - -```json -{ - "date": "2026-04-25", - "schemaVersion": "3.0", - "mode": "full", - "previousMilestone": 119, - "currentMilestone": 133, - "latestUpstreamMilestone": 148, - "skiaSubmoduleCommit": "fb76f3dd84ca...", - "releaseNotesSource": "https://raw.githubusercontent.com/google/skia/main/RELEASE_NOTES.md" -} -``` - -## `summary` — Overview Counts - -```json -{ - "totalFindings": 75, - "byStatus": { "missing": 38, "full": 14, "partial": 3, "action_needed": 2, "not_applicable": 12, "correctly_absent": 6 }, - "byPriority": { "critical": 2, "high": 12, "medium": 25, "low": 36 }, - "bySource": { "release-notes": 55, "header-scan": 17, "binding-audit": 3 }, - "byLabel": { "quick-win": 3, "hidden-api": 17, "perf": 6, "behavior-change": 4, "action-needed": 2 } -} -``` - -## `findings` — Unified Array - -Every finding (release notes features, hidden APIs, performance notes, deprecations) is in -one array with the same shape. Use `source` to know where it came from and `labels` for -filtering. - -### Required fields (every finding) - -| Field | Type | Description | -|-------|------|-------------| -| `name` | string | Human-readable name (used as identifier) | -| `category` | string | See category enum below | -| `priority` | string | `critical`, `high`, `medium`, `low` | -| `description` | string | What it is and why it matters | -| `bindingStatus` | string | See binding status enum below | -| `source` | string | `release-notes`, `header-scan`, or `binding-audit` | - -### Optional fields - -| Field | Type | When to include | -|-------|------|-----------------| -| `labels` | string[] | Freeform tags for filtering: `quick-win`, `hidden-api`, `perf`, `behavior-change`, `action-needed` | -| `milestone` | integer | Primary milestone (when introduced or relevant) | -| `milestones` | integer[] | Additional milestones where enhanced/changed | -| `milestoneDeprecated` | integer | When deprecated | -| `milestoneRemoved` | integer | When removed | -| `skiaApi` | string | Upstream Skia API name(s) | -| `userValue` | string | Why an app developer would want this | -| `notes` | string | Additional context | -| `cApiFunction` | string | C API function name if present | -| `cApiFile` | string | C API header file | -| `csharpMethod` | string | C# method/property name | -| `csharpFile` | string | C# file | -| `cppClass` | string | C++ class (for hidden APIs from header scan) | -| `cppHeader` | string | C++ header path | -| `cppMethod` | string | C++ method signature | -| `replacement` | string | Replacement API (for deprecations) | -| `obsoleteMessage` | string | Suggested `[Obsolete("...")]` text | -| `impact` | string | Impact description (for perf/behavior changes) | -| `bindingActionNeeded` | boolean | Whether binding work is needed (for perf notes) | -| `skillToUse` | string | Which skill to invoke for follow-up | -| `effort` | string | `trivial`, `small`, `medium`, `large` | - -### Example: Release notes feature - -```json -{ - "name": "SkMesh / Custom Vertex Mesh Drawing", - "category": "new_feature", - "priority": "high", - "description": "Draw custom vertex meshes with user-defined attributes and varyings using SkSL.", - "bindingStatus": "missing", - "source": "release-notes", - "milestone": 106, - "milestones": [110, 117, 119, 120], - "skiaApi": "SkMesh::Make, SkMeshSpecification", - "userValue": "Enables particle systems, deformable meshes, and custom geometry rendering.", - "skillToUse": "api-add-review", - "effort": "large" -} -``` - -### Example: Hidden API from header scan - -```json -{ - "name": "SkImage::reinterpretColorSpace", - "category": "image", - "priority": "medium", - "description": "Re-tag image with new color space without converting pixels.", - "bindingStatus": "missing", - "source": "header-scan", - "labels": ["hidden-api"], - "cppClass": "SkImage", - "cppHeader": "include/core/SkImage.h", - "cppMethod": "reinterpretColorSpace(sk_sp) const", - "skillToUse": "api-add-review", - "effort": "small" -} -``` - -### Example: Performance note - -```json -{ - "name": "Perlin noise raster performance", - "category": "performance", - "priority": "low", - "description": "Perlin noise shaders now properly rotate when transformed. Raster performance significantly improved.", - "bindingStatus": "not_applicable", - "source": "release-notes", - "labels": ["perf"], - "milestone": 124, - "impact": "Automatic speedup for apps using Perlin noise on raster surfaces.", - "bindingActionNeeded": false -} -``` - -### Example: Deprecation needing action - -```json -{ - "name": "SkPath mutable methods deprecation", - "category": "deprecation", - "priority": "critical", - "description": "SkPath is migrating to immutable. moveTo/lineTo/etc. will be removed.", - "bindingStatus": "action_needed", - "source": "release-notes", - "labels": ["action-needed"], - "milestone": 143, - "skiaApi": "SkPath::moveTo, lineTo, quadTo, cubicTo, etc.", - "csharpMethod": "SKPath.MoveTo(), LineTo(), etc.", - "csharpFile": "SKPath.cs", - "replacement": "SKPathBuilder (not yet bound)", - "obsoleteMessage": "SkPath is becoming immutable. Use SKPathBuilder to create paths." -} -``` - -## Category Enum - -`new_feature`, `codec`, `image`, `image_filter`, `shader`, `color`, `canvas`, `path`, -`font`, `utility`, `performance`, `behavior_change`, `deprecation` - -## Binding Status Enum - -| Value | Description | -|-------|-------------| -| `full` | C API + C# wrapper both exist | -| `partial` | C API exists but C# wrapper missing (quick win) | -| `missing` | Neither C API nor C# wrapper | -| `action_needed` | SkiaSharp wraps deprecated/removed Skia API | -| `correctly_absent` | Skia removed; SkiaSharp never wrapped | -| `not_applicable` | Doesn't need a binding | - -## Source Enum - -| Value | Description | -|-------|-------------| -| `release-notes` | Found in Skia RELEASE_NOTES.md | -| `header-scan` | Found by comparing C++ headers against C API | -| `binding-audit` | Found by auditing existing SkiaSharp bindings | - -## `nextSteps` — Prioritized Action Plan - -Separate from findings. Ordered list of what to do next. - -```json -{ - "severity": "critical", - "action": "Create SkPathBuilder bindings", - "milestone": 143, - "reason": "SkPath going immutable. All edit methods will be removed.", - "skillToUse": "api-add-review", - "effort": "large" -} -``` diff --git a/.agents/skills/skia-feature-scout/scripts/render-feature-scout.py b/.agents/skills/skia-feature-scout/scripts/render-feature-scout.py deleted file mode 100644 index 96e9c769963..00000000000 --- a/.agents/skills/skia-feature-scout/scripts/render-feature-scout.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -"""Render a Skia Feature Scout JSON report (v3) as a self-contained HTML file. - -Usage: - python3 render-feature-scout.py [output.html] - -If output path is omitted, writes to the same directory as the JSON -with a .html extension. - -Exit codes: 0=success, 1=error. -""" -import json -import os -import sys -from pathlib import Path - - -def main(): - if len(sys.argv) < 2: - print("Usage: python3 render-feature-scout.py [output.html]") - sys.exit(1) - - json_path = Path(sys.argv[1]) - if not json_path.exists(): - print(f"❌ JSON file not found: {json_path}") - sys.exit(1) - - if len(sys.argv) >= 3: - output_path = Path(sys.argv[2]) - else: - output_path = json_path.with_suffix(".html") - - try: - with open(json_path, encoding="utf-8") as f: - data = json.load(f) - except json.JSONDecodeError as e: - print(f"❌ Invalid JSON: {e}") - sys.exit(1) - - for key in ("meta", "summary", "findings"): - if key not in data: - print(f"❌ Missing required key: {key}") - sys.exit(1) - - data.setdefault("nextSteps", []) - - template_path = Path(__file__).parent / "viewer.html" - if not template_path.exists(): - print(f"❌ Template not found: {template_path}") - sys.exit(1) - - with open(template_path, encoding="utf-8") as f: - template = f.read() - - json_str = json.dumps(data, ensure_ascii=False) - json_str = json_str.replace("", "<\\/script>") - json_str = json_str.replace("", "<\\/Script>") - json_str = json_str.replace("", "<\\/SCRIPT>") - - data_script = f"" - - marker = "" - if marker not in template: - print(f"❌ Injection marker '{marker}' not found in template") - sys.exit(1) - - html = template.replace(marker, data_script) - - meta = data.get("meta", {}) - curr_ms = meta.get("currentMilestone", "?") - date = meta.get("date", "?") - mode = meta.get("mode", "full") - title_detail = f"m{curr_ms}" if mode == "full" else f"m{meta.get('previousMilestone', '?')}→m{curr_ms}" - html = html.replace( - "Skia Feature Scout", - f"Skia Feature Scout — {title_detail} ({date})" - ) - - output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w", encoding="utf-8") as f: - f.write(html) - - size_kb = os.path.getsize(output_path) / 1024 - summary = data.get("summary", {}) - total = summary.get("totalFindings", "?") - by_status = summary.get("byStatus", {}) - missing = by_status.get("missing", 0) - full = by_status.get("full", 0) - partial = by_status.get("partial", 0) - by_source = summary.get("bySource", {}) - hidden = by_source.get("header-scan", 0) - - print(f"✅ {output_path.name} ({size_kb:.0f} KB)") - print(f" {title_detail} • {date} • {total} findings") - print(f" ❌ {missing} missing · 🟡 {partial} partial · ✅ {full} full · 🆕 {hidden} hidden") - print(f" Output: {output_path}") - - -if __name__ == "__main__": - main() diff --git a/.agents/skills/skia-feature-scout/scripts/validate-feature-scout.py b/.agents/skills/skia-feature-scout/scripts/validate-feature-scout.py deleted file mode 100644 index 2844d71d47d..00000000000 --- a/.agents/skills/skia-feature-scout/scripts/validate-feature-scout.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -"""Validate a Skia Feature Scout JSON report (v3) against feature-scout-schema.json. - -Usage: python3 validate-feature-scout.py -Exits: 0=valid, 1=fixable errors (retry), 2=fatal. -""" -import json -import sys -from pathlib import Path - -if len(sys.argv) != 2: - print("Usage: python3 validate-feature-scout.py ") - sys.exit(2) - -path = Path(sys.argv[1]) -if not path.exists(): - print(f"❌ File not found: {path}") - sys.exit(2) - -schema_path = Path(__file__).parent / "../references/feature-scout-schema.json" -if not schema_path.exists(): - print(f"❌ Schema not found: {schema_path}") - sys.exit(2) - -with open(path) as f: - data = json.load(f) -with open(schema_path) as f: - schema = json.load(f) - -errors = [] -warnings = [] - -# --- JSON Schema validation --- -try: - from jsonschema import Draft202012Validator - validator = Draft202012Validator(schema) - for e in validator.iter_errors(data): - p = ".".join(str(x) for x in e.absolute_path) or "(root)" - errors.append(f"Schema: {p}: {e.message}") -except ImportError: - print("⚠️ jsonschema not installed — run: pip install jsonschema") - print(" Falling back to structural checks only") - -# --- Extra checks beyond schema --- - -meta = data.get("meta", {}) -summary = data.get("summary", {}) -findings = data.get("findings", []) -next_steps = data.get("nextSteps", []) - -# Check summary.totalFindings matches actual -actual_total = len(findings) -if summary.get("totalFindings", 0) != actual_total: - errors.append( - f"summary.totalFindings ({summary.get('totalFindings')}) != actual findings count ({actual_total})" - ) - -# Check byStatus counts -by_status = summary.get("byStatus", {}) -for status_val, expected_count in by_status.items(): - actual = sum(1 for f in findings if f.get("bindingStatus") == status_val) - if actual != expected_count: - warnings.append(f"summary.byStatus.{status_val} ({expected_count}) != actual ({actual})") - -# Check byPriority counts -by_priority = summary.get("byPriority", {}) -for pri_val, expected_count in by_priority.items(): - actual = sum(1 for f in findings if f.get("priority") == pri_val) - if actual != expected_count: - warnings.append(f"summary.byPriority.{pri_val} ({expected_count}) != actual ({actual})") - -# Check bySource counts -by_source = summary.get("bySource", {}) -for src_val, expected_count in by_source.items(): - actual = sum(1 for f in findings if f.get("source") == src_val) - if actual != expected_count: - warnings.append(f"summary.bySource.{src_val} ({expected_count}) != actual ({actual})") - -# Ensure names are unique -names = [f.get("name") for f in findings] -dupes = [n for n in set(names) if names.count(n) > 1] -if dupes: - warnings.append(f"Duplicate finding names: {dupes[:5]}") - -# Milestone sanity -current_ms = meta.get("currentMilestone", 0) -latest_ms = meta.get("latestUpstreamMilestone", 0) -if current_ms > latest_ms: - errors.append(f"currentMilestone ({current_ms}) > latestUpstreamMilestone ({latest_ms})") - -# Binding status consistency -for f in findings: - name = f.get("name", "?") - bs = f.get("bindingStatus") - if bs == "full" and not f.get("cApiFunction") and not f.get("csharpMethod"): - warnings.append(f"'{name}': bindingStatus=full but no cApiFunction or csharpMethod") - if bs == "partial" and not f.get("cApiFunction"): - warnings.append(f"'{name}': bindingStatus=partial but no cApiFunction") - -# milestones should be int arrays -for f in findings: - ms = f.get("milestones") - if ms is not None and not isinstance(ms, list): - errors.append(f"'{f.get('name')}': milestones should be array, got {type(ms).__name__}") - if ms and not all(isinstance(m, int) for m in ms): - errors.append(f"'{f.get('name')}': milestones array contains non-integers") - -# --- Output --- -if warnings: - for w in warnings: - print(f" ⚠️ {w}") - -if errors: - print(f"\n❌ {len(errors)} validation error(s) in {path.name}:\n") - for e in errors: - print(f" ❌ {e}") - sys.exit(1) - -mode = meta.get("mode", "?") -ms_info = f"m{meta.get('currentMilestone', '?')}" -if mode == "windowed": - ms_info = f"m{meta.get('previousMilestone', '?')}→{ms_info}" -print( - f"✅ {path.name} is valid ({mode} scan, {ms_info}, {len(findings)} findings)" -) -sys.exit(0) diff --git a/.agents/skills/skia-feature-scout/scripts/viewer.html b/.agents/skills/skia-feature-scout/scripts/viewer.html deleted file mode 100644 index e522b10662c..00000000000 --- a/.agents/skills/skia-feature-scout/scripts/viewer.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - Skia Feature Scout - - - - - -
-
-
Skia Feature Scout -
-
-
-
- - -
- - -
-
-
- Search: - -
-
- Milestone: - - - -
-
-
-
-
-
- Group by: - - - - Sort by: - - -
-
-
- - -
-
-
- - -
-
-
- Search: - -
-
-
-
-
-
- - -
-
-
- - -
- - - diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 73df5e9d5f2..63a5fc3c853 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -5,10 +5,15 @@ "version": "v9", "sha": "373c709c69115d41ff229c7e5df9f8788daa9553" }, - "github/gh-aw-actions/setup@v0.68.3": { + "github/gh-aw-actions/setup-cli@v0.71.1": { + "repo": "github/gh-aw-actions/setup-cli", + "version": "v0.71.1", + "sha": "239aec45b78c8799417efdd5bc6d8cc036629ec1" + }, + "github/gh-aw-actions/setup@v0.71.1": { "repo": "github/gh-aw-actions/setup", - "version": "v0.68.3", - "sha": "ba90f2186d7ad780ec640f364005fa24e797b360" + "version": "v0.71.1", + "sha": "239aec45b78c8799417efdd5bc6d8cc036629ec1" } } } diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml new file mode 100644 index 00000000000..0cb64d08973 --- /dev/null +++ b/.github/workflows/agentics-maintenance.yml @@ -0,0 +1,506 @@ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.71.1). DO NOT EDIT. +# +# To regenerate this workflow, run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Alternative regeneration methods: +# make recompile +# +# Or use the gh-aw CLI directly: +# ./gh-aw compile --validate --verbose +# +# The workflow is generated when any workflow uses the 'expires' field +# in create-discussions, create-issues, or create-pull-request safe-outputs configuration. +# Schedule frequency is automatically determined by the shortest expiration time. +# +name: Agentic Maintenance + +on: + schedule: + - cron: "37 0 * * *" # Daily (based on minimum expires: 30 days) + workflow_dispatch: + inputs: + operation: + description: 'Optional maintenance operation to run' + required: false + type: choice + default: '' + options: + - '' + - 'disable' + - 'enable' + - 'update' + - 'upgrade' + - 'safe_outputs' + - 'create_labels' + - 'activity_report' + - 'close_agentic_workflows_issues' + - 'clean_cache_memories' + - 'update_pull_request_branches' + - 'validate' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + workflow_call: + inputs: + operation: + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate)' + required: false + type: string + default: '' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + outputs: + operation_completed: + description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' + value: ${{ jobs.run_operation.outputs.operation || inputs.operation }} + applied_run_url: + description: 'The run URL that safe outputs were applied from' + value: ${{ jobs.apply_safe_outputs.outputs.run_url }} + +permissions: {} + +jobs: + close-expired-entities: + if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} + runs-on: ubuntu-slim + permissions: + discussions: write + issues: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Close expired discussions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs'); + await main(); + + - name: Close expired issues + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs'); + await main(); + + - name: Close expired pull requests + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs'); + await main(); + + cleanup-cache-memory: + if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }} + runs-on: ubuntu-slim + permissions: + actions: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Cleanup outdated cache-memory entries + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs'); + await main(); + + run_operation: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: write + contents: write + pull-requests: write + outputs: + operation: ${{ steps.record.outputs.operation }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + version: v0.71.1 + + - name: Run operation + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_OPERATION: ${{ inputs.operation }} + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs'); + await main(); + + - name: Record outputs + id: record + run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" + + update_pull_request_branches: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Update pull request branches + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs'); + await main(); + + apply_safe_outputs: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: read + contents: write + discussions: write + issues: write + pull-requests: write + outputs: + run_url: ${{ steps.record.outputs.run_url }} + steps: + - name: Checkout actions folder + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + actions + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Apply Safe Outputs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_RUN_URL: ${{ inputs.run_url }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs'); + await main(); + + - name: Record outputs + id: record + run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" + + create_labels: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + version: v0.71.1 + + - name: Create missing labels + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs'); + await main(); + + activity_report: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + timeout-minutes: 120 + permissions: + actions: read + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + version: v0.71.1 + + - name: Restore activity report logs cache + id: activity_report_logs_cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-activity-report-logs-${{ github.repository }}- + ${{ runner.os }}-activity-report-logs- + - name: Download activity report logs + timeout-minutes: 20 + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_CMD_PREFIX: gh aw + run: | + ${GH_AW_CMD_PREFIX} logs \ + --repo "${{ github.repository }}" \ + --start-date -1w \ + --count 100 \ + --output ./.cache/gh-aw/activity-report-logs \ + --format markdown \ + > ./.cache/gh-aw/activity-report-logs/report.md + + - name: Save activity report logs cache + if: ${{ always() }} + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }} + + - name: Generate activity report issue + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('node:fs'); + const reportPath = './.cache/gh-aw/activity-report-logs/report.md'; + if (!fs.existsSync(reportPath)) { + core.warning('Activity report markdown not found at ' + reportPath + '; skipping issue creation.'); + return; + } + let reportBody = ''; + try { + reportBody = fs.readFileSync(reportPath, 'utf8').trim(); + } catch (error) { + core.warning('Failed to read activity report markdown at ' + reportPath + ': ' + error.message); + return; + } + if (!reportBody) { + core.warning('Activity report markdown is empty at ' + reportPath + '; skipping issue creation.'); + return; + } + const repoSlug = context.repo.owner + '/' + context.repo.repo; + const body = [ + '### Agentic workflow activity report', + '', + 'Repository: ' + repoSlug, + 'Generated at: ' + new Date().toISOString(), + '', + reportBody, + ].join('\n'); + const createdIssue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '[aw] agentic status report', + body, + labels: ['agentic-workflows'], + }); + core.info('Created issue #' + createdIssue.data.number + ': ' + createdIssue.data.html_url); + + close_agentic_workflows_issues: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + issues: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Close no-repro agentic-workflows issues + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_agentic_workflows_issues.cjs'); + await main(); + + validate_workflows: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + version: v0.71.1 + + - name: Validate workflows and file issue on findings + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_validate_workflows.cjs'); + await main(); diff --git a/.github/workflows/auto-triage.lock.yml b/.github/workflows/auto-triage.lock.yml index 2c04df69870..fb622208596 100644 --- a/.github/workflows/auto-triage.lock.yml +++ b/.github/workflows/auto-triage.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b81c2fdd5fe171f1beadb8f03b193d47b393c1c1c18b7bef0e747e55366d70f0","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_WRITE_PROJECT_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b81c2fdd5fe171f1beadb8f03b193d47b393c1c1c18b7bef0e747e55366d70f0","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_WRITE_PROJECT_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -35,16 +35,17 @@ # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 +# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.20 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.20 -# - ghcr.io/github/gh-aw-mcpg:v0.2.19 -# - ghcr.io/github/github-mcp-server:v0.32.0 -# - node:lts-alpine +# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb +# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 +# - ghcr.io/github/gh-aw-mcpg:v0.3.0 +# - ghcr.io/github/github-mcp-server:v1.0.2 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Auto-Triage SkiaSharp Issue" "on": @@ -110,6 +111,7 @@ jobs: outputs: comment_id: "" comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} @@ -118,7 +120,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -129,16 +131,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.21" - GH_AW_INFO_AGENT_VERSION: "1.0.21" - GH_AW_INFO_CLI_VERSION: "v0.68.3" + GH_AW_INFO_VERSION: "1.0.35" + GH_AW_INFO_AGENT_VERSION: "1.0.35" + GH_AW_INFO_CLI_VERSION: "v0.71.1" GH_AW_INFO_WORKFLOW_NAME: "Auto-Triage SkiaSharp Issue" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","python"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.20" + GH_AW_INFO_AWF_VERSION: "v0.25.28" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -161,8 +163,19 @@ jobs: sparse-checkout: | .github .agents + .claude + .codex + .crush + .gemini + .opencode sparse-checkout-cone-mode: true fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -178,7 +191,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.68.3" + GH_AW_COMPILED_VERSION: "v0.71.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -315,6 +328,7 @@ jobs: /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base if-no-files-found: ignore retention-days: 1 @@ -346,7 +360,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -404,11 +418,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -416,8 +430,19 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config env: GH_AW_WRITE_PROJECT_TOKEN: ${{ secrets.GH_AW_WRITE_PROJECT_TOKEN }} @@ -633,10 +658,10 @@ jobs: GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="host.docker.internal" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" @@ -647,15 +672,19 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.19' + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_df587eb831437e09_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_df587eb831437e09_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "container": "ghcr.io/github/github-mcp-server:v1.0.2", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -701,11 +730,6 @@ jobs: } } GH_AW_MCP_CONFIG_df587eb831437e09_EOF - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -735,21 +759,25 @@ jobs: run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pip3)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pip3)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.68.3 + GH_AW_VERSION: v0.71.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -824,7 +852,7 @@ jobs: uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} with: @@ -860,9 +888,9 @@ jobs: env: AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs run: | - # Fix permissions on firewall logs so they can be uploaded as artifacts + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 2>/dev/null || true # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) if command -v awf &> /dev/null; then awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" @@ -922,7 +950,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - issues: write + issues: read pull-requests: write concurrency: group: "gh-aw-conclusion-auto-triage" @@ -935,7 +963,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1025,6 +1053,7 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "auto-triage" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} @@ -1061,7 +1090,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1091,7 +1120,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 - name: Check if detection needed id: detection_guard if: always() @@ -1109,7 +1138,7 @@ jobs: - name: Clear MCP configuration for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | - rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" rm -f /home/runner/.copilot/mcp-config.json rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files @@ -1144,12 +1173,17 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1158,19 +1192,23 @@ jobs: run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.68.3 + GH_AW_VERSION: v0.71.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1215,7 +1253,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1274,7 +1312,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - issues: write + issues: read pull-requests: write timeout-minutes: 15 env: @@ -1284,6 +1322,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.35" GH_AW_WORKFLOW_ID: "auto-triage" GH_AW_WORKFLOW_NAME: "Auto-Triage SkiaSharp Issue" outputs: @@ -1296,7 +1335,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1329,7 +1368,7 @@ jobs: uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_project\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_PROJECT_TOKEN }}\",\"max\":1,\"project\":\"https://github.com/orgs/mono/projects/1\"}}" diff --git a/.github/workflows/sample-scout.lock.yml b/.github/workflows/sample-scout.lock.yml new file mode 100644 index 00000000000..bf05e7eb56a --- /dev/null +++ b/.github/workflows/sample-scout.lock.yml @@ -0,0 +1,1308 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"18934b61abd3d1eb1a2fc55c6a41fe608371f77873b1220283e91926a916626f","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Run the sample-scout skill to discover Skia GM samples worth porting to the SkiaSharp Gallery. +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb +# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 +# - ghcr.io/github/gh-aw-mcpg:v0.3.0 +# - ghcr.io/github/github-mcp-server:v1.0.2 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + +name: "Weekly Sample Scout Report" +"on": + schedule: + - cron: "50 23 * * 3" + # Friendly format: weekly (scattered) + # skip-bots: # Skip-bots processed as bot check in pre-activation job + # - github-actions # Skip-bots processed as bot check in pre-activation job + # - copilot # Skip-bots processed as bot check in pre-activation job + # - dependabot # Skip-bots processed as bot check in pre-activation job + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + +permissions: {} + +concurrency: + cancel-in-progress: true + group: sample-scout + +run-name: "Weekly Sample Scout Report" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.35" + GH_AW_INFO_AGENT_VERSION: "1.0.35" + GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_WORKFLOW_NAME: "Weekly Sample Scout Report" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","python","github"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .claude + .codex + .crush + .gemini + .opencode + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_WORKFLOW_FILE: "sample-scout.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_COMPILED_VERSION: "v0.71.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_cc586914958b049e_EOF' + + GH_AW_PROMPT_cc586914958b049e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_cc586914958b049e_EOF' + + Tools: create_issue, missing_tool, missing_data, noop + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_cc586914958b049e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_cc586914958b049e_EOF' + + {{#runtime-import .github/workflows/sample-scout.md}} + GH_AW_PROMPT_cc586914958b049e_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: samplescout + outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Redirect step summary into agent-writable directory + run: |- + mkdir -p /tmp/gh-aw/agent + touch /tmp/gh-aw/agent/step-summary.md + rm -f /tmp/gh-aw/agent-step-summary.md + ln -s /tmp/gh-aw/agent/step-summary.md /tmp/gh-aw/agent-step-summary.md + + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Write Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4f12c6cfbf7cf0d2_EOF' + {"create_issue":{"close_older_issues":true,"expires":720,"labels":["report","area/Gallery"],"max":1,"title_prefix":"Sample Scout:"},"create_report_incomplete_issue":{},"max_bot_mentions":1,"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_4f12c6cfbf7cf0d2_EOF + - name: Write Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"Sample Scout:\". Labels [\"report\" \"area/Gallery\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_ede7957a0140827c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "repos,issues" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": [ + "mono/skiasharp", + "mono/skia", + "google/skia" + ], + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "private:mono/skiasharp", + "private:mono/skia", + "private:google/skia" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_ede7957a0140827c_EOF + - name: Clean git credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(cat) + # --allow-tool shell(cp) + # --allow-tool shell(date) + # --allow-tool shell(echo) + # --allow-tool shell(find) + # --allow-tool shell(gh:*) + # --allow-tool shell(git:*) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(mkdir) + # --allow-tool shell(pip3) + # --allow-tool shell(pwd) + # --allow-tool shell(python3) + # --allow-tool shell(sed) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 30 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,codeload.github.com,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cp)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(mkdir)'\'' --allow-tool '\''shell(pip3)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3)'\'' --allow-tool '\''shell(sed)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.71.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,codeload.github.com,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_GITHUB_REFS: "" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + concurrency: + group: "gh-aw-conclusion-sample-scout" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "sample-scout" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "30" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + WORKFLOW_NAME: "Weekly Sample Scout Report" + WORKFLOW_DESCRIPTION: "Run the sample-scout skill to discover Skia GM samples worth porting to the SkiaSharp Gallery." + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.71.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_bots.outputs.skip_bots_ok == 'true' }} + matched_command: '' + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + - name: Check skip-bots + id: check_skip_bots + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_SKIP_BOTS: "github-actions,copilot,dependabot" + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_skip_bots.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/sample-scout" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_WORKFLOW_ID: "sample-scout" + GH_AW_WORKFLOW_NAME: "Weekly Sample Scout Report" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,codeload.github.com,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":720,\"labels\":[\"report\",\"area/Gallery\"],\"max\":1,\"title_prefix\":\"Sample Scout:\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/.github/workflows/sample-scout.md b/.github/workflows/sample-scout.md new file mode 100644 index 00000000000..d23d8b4926e --- /dev/null +++ b/.github/workflows/sample-scout.md @@ -0,0 +1,74 @@ +--- +description: "Run the sample-scout skill to discover Skia GM samples worth porting to the SkiaSharp Gallery." +on: + schedule: weekly + workflow_dispatch: + skip-bots: [github-actions, copilot, dependabot] +concurrency: + group: sample-scout + cancel-in-progress: true +timeout-minutes: 30 +steps: + - name: Redirect step summary into agent-writable directory + run: | + mkdir -p /tmp/gh-aw/agent + touch /tmp/gh-aw/agent/step-summary.md + rm -f /tmp/gh-aw/agent-step-summary.md + ln -s /tmp/gh-aw/agent/step-summary.md /tmp/gh-aw/agent-step-summary.md +permissions: + contents: read + issues: read +tools: + github: + toolsets: [repos, issues] + allowed-repos: ["mono/skiasharp", "mono/skia", "google/skia"] + min-integrity: none + bash: ["python3", "pip3", "gh", "git", "jq", "cat", "grep", "find", "sed", "sort", "head", "tail", "wc", "cp", "mkdir", "echo"] +network: + allowed: + - defaults + - python + - github +safe-outputs: + mentions: false + allowed-github-references: [] + max-bot-mentions: 1 + create-issue: + title-prefix: "Sample Scout:" + labels: [report, area/Gallery] + close-older-issues: true + expires: 30 +--- + +# Weekly Sample Scout Report + +Scan all upstream Skia GM samples and publish a report of Gallery opportunities as a GitHub issue. + +## Step 1 — Run the sample-scout skill + +Read and follow the instructions in `.agents/skills/sample-scout/SKILL.md` to run a full scan. + +Complete all phases (Setup → Analyze → Cross-Reference → Validate & Render). +Analyze all GM samples from `google/skia/gm/` — no filtering by milestone, always scan everything. + +After Phase 4 completes, the outputs will be: +- `sample-scout-report.json` — the validated JSON report +- `sample-scout-report.md` — the rendered Markdown + +## Step 2 — Publish as GitHub issue + +Create a GitHub issue with the contents of `sample-scout-report.md` as the body. + +The issue title **must** start with `Sample Scout:` followed by date and key metric +(e.g. `Sample Scout: 2025-01-15 (42 opportunities)`) so `close-older-issues` matches correctly. + +## Step 3 — Upload artifacts and write step summary + +```bash +cp sample-scout-report.json /tmp/gh-aw/agent/ +cp sample-scout-report.md /tmp/gh-aw/agent/ +cat sample-scout-report.md >> /tmp/gh-aw/agent/step-summary.md +``` + +**IMPORTANT:** Write to the literal path `/tmp/gh-aw/agent/step-summary.md` — this file is symlinked +to the step summary. Do NOT use `$GITHUB_STEP_SUMMARY` as it resolves to an inaccessible path. diff --git a/.github/workflows/skia-analyst.lock.yml b/.github/workflows/skia-analyst.lock.yml new file mode 100644 index 00000000000..c6ccd286deb --- /dev/null +++ b/.github/workflows/skia-analyst.lock.yml @@ -0,0 +1,1308 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fc8b3caf5a6a6917005089f80c2bfd187c36047df4da0e9a3c068889512d3300","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Run the skia-analyst skill to produce a Skia feature analysis report as a GitHub issue. +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb +# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 +# - ghcr.io/github/gh-aw-mcpg:v0.3.0 +# - ghcr.io/github/github-mcp-server:v1.0.2 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + +name: "Daily Skia Analyst Report" +"on": + schedule: + - cron: "21 12 * * *" + # Friendly format: daily (scattered) + # skip-bots: # Skip-bots processed as bot check in pre-activation job + # - github-actions # Skip-bots processed as bot check in pre-activation job + # - copilot # Skip-bots processed as bot check in pre-activation job + # - dependabot # Skip-bots processed as bot check in pre-activation job + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + +permissions: {} + +concurrency: + cancel-in-progress: true + group: skia-analyst + +run-name: "Daily Skia Analyst Report" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.35" + GH_AW_INFO_AGENT_VERSION: "1.0.35" + GH_AW_INFO_CLI_VERSION: "v0.71.1" + GH_AW_INFO_WORKFLOW_NAME: "Daily Skia Analyst Report" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","python","github"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.28" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .claude + .codex + .crush + .gemini + .opencode + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_WORKFLOW_FILE: "skia-analyst.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_COMPILED_VERSION: "v0.71.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_9ae84c3fb7d7de7a_EOF' + + GH_AW_PROMPT_9ae84c3fb7d7de7a_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_9ae84c3fb7d7de7a_EOF' + + Tools: create_issue, missing_tool, missing_data, noop + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_9ae84c3fb7d7de7a_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_9ae84c3fb7d7de7a_EOF' + + {{#runtime-import .github/workflows/skia-analyst.md}} + GH_AW_PROMPT_9ae84c3fb7d7de7a_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: skiaanalyst + outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Redirect step summary into agent-writable directory + run: |- + mkdir -p /tmp/gh-aw/agent + touch /tmp/gh-aw/agent/step-summary.md + rm -f /tmp/gh-aw/agent-step-summary.md + ln -s /tmp/gh-aw/agent/step-summary.md /tmp/gh-aw/agent-step-summary.md + + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Write Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b5ae871e33473389_EOF' + {"create_issue":{"close_older_issues":true,"expires":720,"labels":["report","area/SkiaSharp"],"max":1,"title_prefix":"Skia Analyst:"},"create_report_incomplete_issue":{},"max_bot_mentions":1,"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_b5ae871e33473389_EOF + - name: Write Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"Skia Analyst:\". Labels [\"report\" \"area/SkiaSharp\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_0cbc0c843d33f138_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.2", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "repos,issues" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": [ + "mono/skiasharp", + "mono/skia", + "google/skia" + ], + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "private:mono/skiasharp", + "private:mono/skia", + "private:google/skia" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_0cbc0c843d33f138_EOF + - name: Clean git credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(cat) + # --allow-tool shell(cp) + # --allow-tool shell(date) + # --allow-tool shell(echo) + # --allow-tool shell(find) + # --allow-tool shell(gh:*) + # --allow-tool shell(git:*) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(mkdir) + # --allow-tool shell(pip3) + # --allow-tool shell(pwd) + # --allow-tool shell(python3) + # --allow-tool shell(sed) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 30 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,codeload.github.com,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cp)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(mkdir)'\'' --allow-tool '\''shell(pip3)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3)'\'' --allow-tool '\''shell(sed)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.71.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,codeload.github.com,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GH_AW_ALLOWED_GITHUB_REFS: "" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + concurrency: + group: "gh-aw-conclusion-skia-analyst" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "skia-analyst" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "30" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + WORKFLOW_NAME: "Daily Skia Analyst Report" + WORKFLOW_DESCRIPTION: "Run the skia-analyst skill to produce a Skia feature analysis report as a GitHub issue." + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.71.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_bots.outputs.skip_bots_ok == 'true' }} + matched_command: '' + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + - name: Check skip-bots + id: check_skip_bots + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_SKIP_BOTS: "github-actions,copilot,dependabot" + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_skip_bots.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/skia-analyst" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.35" + GH_AW_WORKFLOW_ID: "skia-analyst" + GH_AW_WORKFLOW_NAME: "Daily Skia Analyst Report" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,codeload.github.com,conda.anaconda.org,conda.binstar.org,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,files.pythonhosted.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":720,\"labels\":[\"report\",\"area/SkiaSharp\"],\"max\":1,\"title_prefix\":\"Skia Analyst:\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/.github/workflows/skia-analyst.md b/.github/workflows/skia-analyst.md new file mode 100644 index 00000000000..70363efc7d5 --- /dev/null +++ b/.github/workflows/skia-analyst.md @@ -0,0 +1,73 @@ +--- +description: "Run the skia-analyst skill to produce a Skia feature analysis report as a GitHub issue." +on: + schedule: daily + workflow_dispatch: + skip-bots: [github-actions, copilot, dependabot] +concurrency: + group: skia-analyst + cancel-in-progress: true +timeout-minutes: 30 +steps: + - name: Redirect step summary into agent-writable directory + run: | + mkdir -p /tmp/gh-aw/agent + touch /tmp/gh-aw/agent/step-summary.md + rm -f /tmp/gh-aw/agent-step-summary.md + ln -s /tmp/gh-aw/agent/step-summary.md /tmp/gh-aw/agent-step-summary.md +permissions: + contents: read + issues: read +tools: + github: + toolsets: [repos, issues] + allowed-repos: ["mono/skiasharp", "mono/skia", "google/skia"] + min-integrity: none + bash: ["python3", "pip3", "gh", "git", "jq", "cat", "grep", "find", "sed", "sort", "head", "tail", "wc", "cp", "mkdir", "echo"] +network: + allowed: + - defaults + - python + - github +safe-outputs: + mentions: false + allowed-github-references: [] + max-bot-mentions: 1 + create-issue: + title-prefix: "Skia Analyst:" + labels: [report, area/SkiaSharp] + close-older-issues: true + expires: 30 +--- + +# Daily Skia Analyst Report + +Run a full skia-analyst scan and publish the results as a GitHub issue. + +## Step 1 — Run the skia-analyst skill + +Read and follow the instructions in `.agents/skills/skia-analyst/SKILL.md` to run a **full scan**. + +Complete all phases (Setup → Agents → Synthesize → Generate Outputs). Use `scanMode: full`. + +After Phase 4 completes, the outputs will be: +- `skia-analyst-report.json` — the validated JSON report +- `skia-analyst-report.md` — the rendered Markdown + +## Step 2 — Publish as GitHub issue + +Create a GitHub issue with the contents of `skia-analyst-report.md` as the body. + +The issue title **must** start with `Skia Analyst:` followed by the milestone and date +(e.g. `Skia Analyst: m147 (2025-01-15)`) so `close-older-issues` matches correctly. + +## Step 3 — Upload artifacts and write step summary + +```bash +cp skia-analyst-report.json /tmp/gh-aw/agent/ +cp skia-analyst-report.md /tmp/gh-aw/agent/ +cat skia-analyst-report.md >> /tmp/gh-aw/agent/step-summary.md +``` + +**IMPORTANT:** Write to the literal path `/tmp/gh-aw/agent/step-summary.md` — this file is symlinked +to the step summary. Do NOT use `$GITHUB_STEP_SUMMARY` as it resolves to an inaccessible path. diff --git a/.github/workflows/update-release-notes.lock.yml b/.github/workflows/update-release-notes.lock.yml index a3f844956ec..c54518d98fd 100644 --- a/.github/workflows/update-release-notes.lock.yml +++ b/.github/workflows/update-release-notes.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c93a48d1afae6af3cfbc6c7d6843c44344a444c00ce7da8acab2172d7682f9cf","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c93a48d1afae6af3cfbc6c7d6843c44344a444c00ce7da8acab2172d7682f9cf","compiler_version":"v0.71.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"239aec45b78c8799417efdd5bc6d8cc036629ec1","version":"v0.71.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28","digest":"sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28","digest":"sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28","digest":"sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.0"},{"image":"ghcr.io/github/github-mcp-server:v1.0.2"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.71.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -35,16 +35,17 @@ # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 +# - github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.20 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.20 -# - ghcr.io/github/gh-aw-mcpg:v0.2.19 -# - ghcr.io/github/github-mcp-server:v0.32.0 -# - node:lts-alpine +# - ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb +# - ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 +# - ghcr.io/github/gh-aw-mcpg:v0.3.0 +# - ghcr.io/github/github-mcp-server:v1.0.2 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "Update Release Notes" "on": @@ -85,6 +86,7 @@ jobs: outputs: comment_id: "" comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} @@ -93,7 +95,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -104,16 +106,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.21" - GH_AW_INFO_AGENT_VERSION: "1.0.21" - GH_AW_INFO_CLI_VERSION: "v0.68.3" + GH_AW_INFO_VERSION: "1.0.35" + GH_AW_INFO_AGENT_VERSION: "1.0.35" + GH_AW_INFO_CLI_VERSION: "v0.71.1" GH_AW_INFO_WORKFLOW_NAME: "Update Release Notes" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.20" + GH_AW_INFO_AWF_VERSION: "v0.25.28" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -136,8 +138,19 @@ jobs: sparse-checkout: | .github .agents + .claude + .codex + .crush + .gemini + .opencode sparse-checkout-cone-mode: true fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -153,7 +166,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 env: - GH_AW_COMPILED_VERSION: "v0.68.3" + GH_AW_COMPILED_VERSION: "v0.71.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -289,6 +302,7 @@ jobs: /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base if-no-files-found: ignore retention-days: 1 @@ -319,7 +333,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -370,11 +384,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -385,15 +399,26 @@ jobs: script: | const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 ghcr.io/github/gh-aw-mcpg:v0.3.0 ghcr.io/github/github-mcp-server:v1.0.2 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Write Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1ca7b6987f122104_EOF' - {"create_pull_request":{"draft":false,"labels":["documentation"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[docs] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + {"create_pull_request":{"draft":false,"labels":["documentation"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/"],"title_prefix":"[docs] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_1ca7b6987f122104_EOF - name: Write Safe Outputs Tools env: @@ -410,6 +435,11 @@ jobs: "create_pull_request": { "defaultMax": 1, "fields": { + "base": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, "body": { "required": true, "type": "string", @@ -575,10 +605,10 @@ jobs: GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="host.docker.internal" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" @@ -589,15 +619,19 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.19' + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.0' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_d3f89015efe5a228_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_d3f89015efe5a228_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "container": "ghcr.io/github/github-mcp-server:v1.0.2", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -634,11 +668,6 @@ jobs: } } GH_AW_MCP_CONFIG_d3f89015efe5a228_EOF - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" @@ -677,21 +706,25 @@ jobs: run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3)'\'' --allow-tool '\''shell(sed)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(awk)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(python3)'\'' --allow-tool '\''shell(sed)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.68.3 + GH_AW_VERSION: v0.71.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -801,9 +834,9 @@ jobs: env: AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs run: | - # Fix permissions on firewall logs so they can be uploaded as artifacts + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 2>/dev/null || true # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) if command -v awf &> /dev/null; then awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" @@ -874,7 +907,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -964,6 +997,7 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "update-release-notes" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} @@ -1002,7 +1036,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1032,7 +1066,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.28@sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a ghcr.io/github/gh-aw-firewall/api-proxy:0.25.28@sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb ghcr.io/github/gh-aw-firewall/squid:0.25.28@sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474 - name: Check if detection needed id: detection_guard if: always() @@ -1050,7 +1084,7 @@ jobs: - name: Clear MCP configuration for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | - rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" rm -f /home/runner/.copilot/mcp-config.json rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files @@ -1085,12 +1119,17 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.35 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.28 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1099,19 +1138,23 @@ jobs: run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --image-tag 0.25.28,squid=sha256:844c18280f82cd1b06345eb2f4e91966b34185bfc51c9f237c3e022e848fb474,agent=sha256:a8834e285807654bf680154faa710d43fe4365a0868142f5c20e48c85e137a7a,api-proxy=sha256:93290f2393752252911bd7c39a047f776c0b53063575e7bde4e304962a9a61cb,cli-proxy=sha256:fdf310e4678ce58d248c466b89399e9680a3003038fd19322c388559016aaac7 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.68.3 + GH_AW_VERSION: v0.71.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1152,7 +1195,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1200,6 +1243,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.35" GH_AW_WORKFLOW_ID: "update-release-notes" GH_AW_WORKFLOW_NAME: "Update Release Notes" outputs: @@ -1214,7 +1258,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@239aec45b78c8799417efdd5bc6d8cc036629ec1 # v0.71.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1278,7 +1322,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"labels\":[\"documentation\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[docs] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"labels\":[\"documentation\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\",\".githooks/\",\".husky/\"],\"title_prefix\":\"[docs] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index ece01da046f..20bc7fcdc4c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -341,11 +341,12 @@ Custom slash commands are available for specialized workflows. Use these for com | Test release | `/release-testing` | "test the release", "verify packages" | | Publish release | `/release-publish` | "push to nuget", "tag release" | | Release notes | `/release-notes` | "generate release notes", "regenerate 3.119.x", "write release notes for" | -| Audit release notes | `/release-notes-audit` | "compare Skia changes", "API gap analysis" | +| Skia analyst | `/skia-analyst` | "what changed", "what are we missing", "feature gap", "changelog", "scout features", "diff tags" | | Update Skia | `/update-skia` | "update to milestone NNN", "bump Skia" | | Review Skia update | `/review-skia-update` | "review the Skia merge PR" | | PR commit message | `/pr-commit-message` | "write commit message for PR" | | Validate samples | `/validate-samples` | "build samples", "test sample projects" | +| Scout GM samples | `/sample-scout` | "find demos to port", "what samples are we missing", "gallery ideas" | | Create/improve skill | `/skill-creator` | "create a new skill", "improve skill X" | ### Issue Pipeline (3 steps)