Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/skills/ci-fix/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
name: ci-fix
description: Common safety and triage protocol for .NET MAUI CI-fixer workflows. Use whenever investigating, creating, or advancing a `[ci-fix]` or `[ci-fix-net11]` pull request from a `ci-scan` issue, including manual single-issue dispatches and scheduled sweeps.
---

# CI-fix triage protocol

Use this protocol before proposing a code change or emitting a CI-fixer safe
output. The calling workflow supplies its branch, labels, title prefix, attempt
limit, and allowed write surface; preserve those caller-specific restrictions.

## Return a decision

For each candidate, state one terminal decision before taking an action:

- `Decision: Continue` — no dedup or safety gate blocks current-evidence analysis.
- `Decision: Watch` — an open CI-fix PR owns the issue; advance only that PR.
- `Decision: Stop` — an explicit human PR owns the issue.
- `Decision: Skip` — the issue is stale, visual, already fixed, out of scope, or
cannot be safely advanced.

Explain the concrete evidence for the decision. A search result is evidence to
inspect, not a decision by itself.

## Deduplicate without false ownership

1. An open CI-fix PR with the workflow's title prefix and exact `Refs:
dotnet/maui#<issue>` is a **Watch** case. Never open a second CI-fix PR for
that issue.
2. A merged CI-fix PR with that exact reference is a **Skip** case until current
evidence establishes a new regression.
3. A closed-unmerged CI-fix PR with that exact reference is a **Skip** case. A
human closing the PR is a stop signal for the keep-one-PR loop.
4. An open human PR is a **Stop** case only when its own title or body explicitly
declares that it addresses the tracking issue. Accept a `Refs:` declaration or
a closing/reference keyword such as `Fix`, `Fixes`, `Fixed`, `Close`,
`Closes`, `Closed`, `Resolve`, `Resolves`, or `Resolved` followed by the
issue reference.

GitHub search normally includes comments and incidental status text. Do not
treat a raw issue number in a comment, check summary, commit message, diff, or
unrelated list as human ownership. Inspect the PR title and body before stopping.
If every result is incidental, continue triage.

## Require current, specific evidence

Before a fresh attempt:

1. Confirm the issue is in the caller's branch and label scope.
2. Skip screenshot and visual-regression failures; do not modify baselines.
3. Verify the cited failure still reproduces on the latest completed base-branch
build. A historical failure alone is not a reason to create a PR.
4. Trace the exact failing test, platform path, and deepest relevant stack frame.
Check whether an intervening merged PR already fixed that path before proposing
a duplicate or adjacent workaround.
5. Preserve the test's assertion. Do not mute, ignore, retry, weaken, or extend a
timeout to hide a failure. A legitimate de-flake makes the state transition or
synchronization deterministic.

## Bound autonomous work

Advance only the existing CI-fix PR and respect the caller's effective attempt
counter. When the cap is reached, defer rather than opening a replacement.
Never claim the target test is fixed from a category-level result or an unrelated
green leg; require evidence for the specific test on the CI-fix PR's current
head.
108 changes: 108 additions & 0 deletions .github/skills/ci-fix/tests/eval.ownership.vally.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# CI-fix ownership capability suite
#
# Each scenario in this small suite is a release gate for the human-PR dedup
# policy. Vally aggregates at the suite level, so threshold 1.0 means every
# grader in every trial of every ownership scenario must pass; a passing
# unrelated triage scenario cannot mask an ownership failure.

name: ci-fix-ownership-capabilities
description: >-
Must-pass ownership scenarios for the CI-fix triage skill. They distinguish
explicit human ownership declarations from incidental issue references.
version: "1.0.0"
type: capability

defaults:
runs: 3
timeout: 5m
model: claude-opus-4.6
judge_model: claude-opus-4.6
executor: copilot-sdk

stimuli:
# Source: live CI-fixer run 30118934440, issue #36051, PR #36657.
- name: comment-only-reference-does-not-stop-triage
tags: { source_issue: "36051", source_pr: "36657", kind: false-dedup }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Tracking issue #36051 is open, has the ci-scan label, and has no open or
merged [ci-fix] PR. A search for #36051 found open human PR #36657.
PR #36657 has this title and body:

Title: "Forward-port Essentials DI service registration"
Body: "Forward-ports the Essentials dependency-injection service registration change to net11.0."

The number #36051 appears only in a comment on #36657 that lists several
unrelated CI failures. It does not appear in the title or body.

Return exactly these decision lines before a brief explanation:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ The prompt pre-states the expected answer (Decision: Continue on the next lines), and the deterministic graders below check for that same string. That risks degrading this must-pass gate to a formatting check: a future ownership regression where the skill decides wrongly could still pass by obeying this strong format instruction and echoing the leaked decision line (the graders never inspect the explanation). Prefer giving the template without the value — e.g. End with a line 'Decision: <Continue|Stop>' and 'Next: <action>' — so the agent must choose the decision itself while output stays deterministic. Same pattern on the other two stimuli (lines ~68 and ~92).

Decision: Continue
Next: current-evidence validation

Do not query GitHub; the snapshot above is complete.
graders:
- type: output-contains
config: { substring: "Decision: Continue" }
- type: output-contains
config: { substring: "Next: current-evidence validation" }
- type: output-not-contains
config: { substring: "Decision: Stop" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: incidental-body-reference-does-not-stop-triage
tags: { kind: false-dedup }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Tracking issue #48101 is open, in scope, and has no [ci-fix] PR. The
title-and-body search found open human PR #48116:

Title: "Improve dispatcher shutdown diagnostics"
Body: "This change is similar to #48101, #48102, and other recent
dispatcher investigations. It does not fix those tracking issues."

The title and body contain no Refs declaration or Fix/Fixes/Fixed,
Close/Closes/Closed, or Resolve/Resolves/Resolved keyword tied to #48101.

Return exactly these decision lines before a brief explanation:
Decision: Continue
Next: current-evidence validation

Do not query GitHub; the snapshot above is complete.
graders:
- type: output-contains
config: { substring: "Decision: Continue" }
- type: output-contains
config: { substring: "Next: current-evidence validation" }
- type: output-not-contains
config: { substring: "Decision: Stop" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: explicit-human-reference-stops-competing-fix
tags: { kind: human-ownership }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Tracking issue #48100 is open, in scope, and has no [ci-fix] PR. Open
human PR #48115 has this body:

"Fixes dotnet/maui#48100 by serializing access to the shared dispatcher."

No other dedup gate applies. Return exactly these decision lines before a
brief explanation:
Decision: Stop
Next: do not open a competing CI-fix PR

Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Stop" }
- type: output-contains
config: { substring: "Next: do not open a competing CI-fix PR" }
- type: output-not-contains
config: { substring: "Decision: Continue" }
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

scoring:
threshold: 1.0
163 changes: 163 additions & 0 deletions .github/skills/ci-fix/tests/eval.vally.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# CI-fix capability suite
#
# The fixtures are hermetic snapshots of actual CI-fixer incidents. They do not
# query live GitHub state: live issue and PR histories would let the agent
# recite today's status rather than exercise the triage policy.

name: ci-fix-capabilities
description: >-
General capability suite for the CI-fix triage skill. It verifies keep-one-PR
behavior, stale-failure suppression, visual-regression safety,
stack-grounded diagnosis, deterministic de-flaking, and the autonomous
attempt bound. Ownership decisions are gated separately by
eval.ownership.vally.yaml.
version: "1.0.0"
type: capability

defaults:
runs: 3
timeout: 5m
model: claude-opus-4.6
judge_model: claude-opus-4.6
executor: copilot-sdk

stimuli:
# Source: open net11 CI-fix PR #36604 for issue #36452.
- name: existing-ci-fix-pr-enters-watch-mode
tags: { source_issue: "36452", source_pr: "36604", kind: keep-one-pr }
prompt: |
Apply the CI-fix triage protocol to this frozen net11.0 snapshot.

Issue #36452 is in scope. Open PR #36604 is titled
"[ci-fix-net11] De-flake Essentials.AI file-based tests" and its body
contains "Refs: dotnet/maui#36452" with attempt marker 1/10. Its current
head CI is still pending.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Watch" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent enters watch mode for PR #36604 rather than creating a second CI-fix PR.
- The agent waits for the current head CI to settle and does not classify an older or pending result.
- The agent preserves the existing branch, target branch, and attempt counter.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

# Source: merged main CI-fix PR #36429 for issue #36259.
- name: merged-fix-suppresses-stale-reopen
tags: { source_issue: "36259", source_pr: "36429", kind: stale-regression }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Issue #36259 cites a historical SafeAreaShouldWorkOnAllShellTabs failure.
CI-fix PR #36429 is merged and its body contains
"Refs: dotnet/maui#36259". There is no current failing build proving a new
regression.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent suppresses a fresh fix attempt because a linked CI-fix PR is already merged.
- The agent distinguishes a historical failure from evidence of a new regression.
- The agent does not reopen or duplicate the merged fix.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: visual-regression-never-modifies-baseline
tags: { kind: visual-safety }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Issue #48200 is in scope, but its cited maui-pr-uitests failure is named
"VerifyScreenshot ShellTabbedPage baseline image mismatch". The issue body
includes "visual diff" and attaches before/after screenshots.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent skips the visual regression without proposing a baseline-image update or a CI-fix PR.
- The agent explains that screenshot and visual-diff failures are out of autonomous-fix scope.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

# Source: postmortem of stale net11 CI-fix PR #36395; the relevant locator fix
# had already merged through #36270 before the CI-fix PR was opened.
- name: current-stack-evidence-prevents-stale-adjacent-fix
tags: { source_pr: "36395", superseding_pr: "36270", kind: stack-grounding }
prompt: |
Apply the CI-fix triage protocol to this frozen net11.0 snapshot.

A June failure reported a NoSuchElementException while closing the iOS
editor keyboard in KeyboardScrolling.CloseiOSEditorKeyboard. A proposed
CI-fix changes a nearby EditorRuntimeTextAlignment locator, but the deepest
failing path is the keyboard-close helper. PR #36270 already merged the
current iOS 26 locator fix in that helper before this proposed fix began.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent traces the failure to the keyboard-close helper rather than accepting the adjacent locator change.
- The agent recognizes the prior merged helper fix as stale-candidate evidence and does not create a duplicate PR.
- The agent explains that current source and exact stack evidence are required before proposing a fix.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

# Source pattern: merged main CI-fix PR #36091 used a condition wait for an
# Android UI test rather than suppressing or retrying the test.
- name: deterministic-deflake-never-mutes-or-retries
tags: { source_pr: "36091", kind: de-flake-safety }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

An Android UI test intermittently fails because the soft-input element is
queried before it appears. A suggested patch adds [Retry(3)] and extends a
fixed sleep from 100 ms to 5 seconds. Another candidate waits for the
specific element state before asserting.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Continue" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent rejects retry attributes, fixed-sleep extensions, test disabling, and assertion weakening.
- The agent selects or requires the deterministic condition-wait approach before a candidate PR is created.
- The agent preserves the original test assertion rather than masking the failure.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

- name: effective-attempt-cap-defers-instead-of-replacing-pr
tags: { kind: autonomy-bound }
prompt: |
Apply the CI-fix triage protocol to this frozen main-branch snapshot.

Issue #48300 has open CI-fix PR #48305 with exact
"Refs: dotnet/maui#48300". Its body marker says 9/10, but its immutable
branch history contains 10 bot-authored fix commits. The latest head CI is
red because the target test still fails.

Return the terminal decision and safe next action. Do not query GitHub.
graders:
- type: output-contains
config: { substring: "Decision: Skip" }
- type: prompt
config: { scoring: scale_1_5, threshold: 0.6 }
rubric:
- The agent uses the effective attempt count rather than trusting the stale body marker.
- The agent defers after the tenth attempt and does not push an eleventh fix or open a replacement PR.
- The explanation identifies the existing PR as the hand-off surface.
constraints: { max_duration: 5m, expect_skills: [ci-fix] }

scoring:
threshold: 0.6
2 changes: 1 addition & 1 deletion .github/workflows/ci-status-fix-net11.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading