Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
26 changes: 13 additions & 13 deletions .github/docs/state-machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ This document maps the 16 GitHub Actions workflows and AI agents in this reposit
| 11 | `labelops-pr-security-scan.md` | ⏰ hourly, 👤 dispatch | none | add-labels, add-comment, repo-memory write |
| 12 | `msbuild-quality-review.md` | ⏰ weekly, 👤 dispatch | none | create-issue, create-pull-request (draft) |
| 13 | `regression-pr-shepherd.md` | ⏰ every 4h, 👤 dispatch | none | push-to-PR, add-comment, remove-labels |
| 14 | `repo-assist.md` | ⏰ every 12h, 👤 dispatch, 👤 slash_command | none | create-pull-request, add-comment, add/remove-labels, create/update-issue, push-to-PR |
| 14 | `repo-assist-scheduled.md` | ⏰ every 12h, 👤 dispatch | none | create-pull-request, add-comment, add/remove-labels, create/update-issue, push-to-PR |
| 15 | `repository_lockdown_check.yml` | 👤 pull_request_target | none | PR comment (lockdown warning) |
| 16 | `skill-validation.yml` | 👤 PR, ⚙️ push (main), 👤 dispatch | none | validate skills/agents |

Expand All @@ -56,15 +56,15 @@ Cross-workflow interactions (producer → consumer):
| Signal | Producer | Consumer | Mechanism |
|--------|----------|----------|-----------|
| `AI-Auto-Resolve-CI/Conflicts` labels | Human maintainer | `labelops-pr-maintenance` | Label filter on PR list |
| `AI-Issue-Regression-PR` label | `repo-assist` | `regression-pr-shepherd` | Label filter on PR list |
| `AI-thinks-issue-fixed` label | `repo-assist` | `regression-pr-shepherd` (remove) | Label on linked issue |
| `AI-Issue-Regression-PR` label | `repo-assist-scheduled` | `regression-pr-shepherd` | Label filter on PR list |
| `AI-thinks-issue-fixed` label | `repo-assist-scheduled` | `regression-pr-shepherd` (remove) | Label on linked issue |
| `dispatch-workflow: labelops-flake-fix` | `labelops-pr-maintenance` | `labelops-flake-fix` | workflow_dispatch with inputs |
| `Flaky` label | `labelops-flake-fix` | Human triage | always-applied on PR/issue |
| `AI-needs-CI-fix-input` label | `labelops-pr-maintenance` | Human maintainer | escalation signal |
| `⚠️ Affects-*` labels | `labelops-pr-security-scan` | Human reviewer | informational |
| `Needs-Triage` label | `add_to_project.yml` | Human triage | imperative on new issues |
| State-store `safety/scanned-PRs` | `labelops-pr-security-scan` | `labelops-pr-security-scan` | repo-memory persistence |
| State-store `memory/repo-assist` | `repo-assist` | `repo-assist` | repo-memory persistence |
| State-store `memory/repo-assist` | `repo-assist-scheduled` | `repo-assist-scheduled` | repo-memory persistence |

## Group A — LabelOps Ecosystem

Expand Down Expand Up @@ -141,16 +141,16 @@ stateDiagram-v2

## Group B — Regression Test Pipeline

Workflows: `repo-assist` (RA), `regression-pr-shepherd` (RPS).
Workflows: `repo-assist-scheduled` (RA), `regression-pr-shepherd` (RPS).

RA creates regression test PRs and labels issues. RPS shepherds those PRs to merge.

```mermaid
stateDiagram-v2
direction LR

state "repo-assist" as RA {
[*] --> RA_FetchData : ⏰ every 12h / 👤 dispatch / 👤 slash_command
state "repo-assist-scheduled" as RA {
[*] --> RA_FetchData : ⏰ every 12h / 👤 dispatch
RA_FetchData --> RA_Task1 : ⚙️ task selection
RA_Task1 --> RA_Task3 : ⚙️ issue investigation
RA_Task3 --> RA_Task2 : ⚙️ windows-only revisit
Expand Down Expand Up @@ -341,12 +341,12 @@ gh-aw safe-output defaults (suppressed below): `target: "*"`, `noop.report-as-is
| `regression-pr-shepherd` | `push-to-pull-request-branch` | 10 | allowed-files: tests/**, vsintegration/tests/** |
| `regression-pr-shepherd` | `add-comment` | 5 | hide-older-comments: true |
| `regression-pr-shepherd` | `remove-labels` | 5 | allowed: AI-thinks-issue-fixed |
| `repo-assist` | `create-pull-request` | 10 | title `Add regression test: `, labels: NO_RELEASE_NOTES+AI-Issue-Regression-PR, reviewers: abonie+T-Gro, auto-merge: true |
| `repo-assist` | `add-comment` | 10 | hide-older-comments: true |
| `repo-assist` | `add-labels` | 30 | allowed: AI-thinks-issue-fixed, AI-thinks-windows-only |
| `repo-assist` | `remove-labels` | 10 | allowed: AI-thinks-issue-fixed, AI-thinks-windows-only |
| `repo-assist` | `create-issue` | 4 | title `[Repo Assist] `, labels: automation+repo-assist |
| `repo-assist` | `push-to-pull-request-branch` | 4 | title `[Repo Assist] `, protected-files: fallback-to-issue |
| `repo-assist-scheduled` | `create-pull-request` | 10 | title `Add regression test: `, labels: NO_RELEASE_NOTES+AI-Issue-Regression-PR, reviewers: abonie+T-Gro, auto-merge: true |
| `repo-assist-scheduled` | `add-comment` | 10 | hide-older-comments: true |
| `repo-assist-scheduled` | `add-labels` | 30 | allowed: AI-thinks-issue-fixed, AI-thinks-windows-only |
| `repo-assist-scheduled` | `remove-labels` | 10 | allowed: AI-thinks-issue-fixed, AI-thinks-windows-only |
| `repo-assist-scheduled` | `create-issue` | 4 | title `[Repo Assist] `, labels: automation+repo-assist |
| `repo-assist-scheduled` | `push-to-pull-request-branch` | 4 | title `[Repo Assist] `, protected-files: fallback-to-issue |

## Label Index

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/agentic-state-machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ You are a workflow-automation documentor. You read all workflow files in `.githu
Place the glossary IMMEDIATELY after the title and intro paragraph, BEFORE the Overview table. A first-time reader rated 2/5 on a 5-point readability scale citing exactly these gaps. Missing glossary entry for a term used 3+ times = MAJOR. Missing emoji legend = MAJOR.

45. **Self-contained — never use source-file pointers as documentation.** Any phrase like `"(see file.md L100–110)"`, `"per source line N"`, `"refer to <file>"`, or `"as defined in <other-doc>"` in PLACE of actual content is a documentation failure. Inline the content. Citations `(src Lnn)` are permitted ONLY as provenance markers AFTER the documented content, never AS the content. Example:
- ❌ WRONG: `RA_T2_SkipCheck --> RA_TaskFinal : ⚙️ check skip conditions (repo-assist.md L296–306)`
- ❌ WRONG: `RA_T2_SkipCheck --> RA_TaskFinal : ⚙️ check skip conditions (repo-assist-scheduled.md L296–306)`
- ✅ CORRECT: `RA_T2_SkipCheck --> RA_TaskFinal : ⚙️ check 6 skip conditions` + an inline `> **Skip conditions**: 1. closed; 2. existing PR; 3. existing coverage; 4. test-link comment; 5. untestable comment; 6. human coverage comment.` callout below the diagram.
Any source-pointer-as-content = MAJOR. Inlined skip conditions, taxonomy enumerations, and predicate lists belong in the doc itself.

Expand Down
60 changes: 45 additions & 15 deletions .github/workflows/check_release_notes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ jobs:
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }}
PR_NUMBER: ${{ github.event.pull_request.number }}
OPT_OUT_RELEASE_NOTES: ${{ contains(github.event.pull_request.labels.*.name, 'NO_RELEASE_NOTES') }}
VNEXT: ${{ vars.VNEXT }}
runs-on: ubuntu-latest
steps:
Expand All @@ -40,9 +38,6 @@ jobs:
[[ "$PR_BASE_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::Unexpected base SHA: $PR_BASE_SHA"; exit 1; }
[[ "$PR_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::Unexpected head SHA: $PR_HEAD_SHA"; exit 1; }

echo "PR Tags: $PR_LABELS"
echo "Opt out of release notes: $OPT_OUT_RELEASE_NOTES"

_current_head_sha=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.head.sha')

if [[ "$_current_head_sha" != "$PR_HEAD_SHA" ]]; then
Expand Down Expand Up @@ -269,39 +264,54 @@ jobs:
if [[ $RELEASE_NOTES_MESSAGE_DETAILS == "" ]]; then
RELEASE_NOTES_MESSAGE+=$'## :white_check_mark: No release notes required\n\n'
else
RELEASE_NOTES_MESSAGE+=$'## :heavy_exclamation_mark: Release notes required\n\n'
RELEASE_NOTES_MESSAGE+=$"**You can open this PR in browser to add release notes: [open in github.dev](https://github.dev/dotnet/fsharp/pull/${PR_NUMBER})**"
RELEASE_NOTES_MESSAGE+=$'\n\n'
if [[ $RELEASE_NOTES_NOT_FOUND != "" ]]; then
RELEASE_NOTES_MESSAGE+=$'## :heavy_exclamation_mark: Release notes required\n\n'
RELEASE_NOTES_MESSAGE+=$"**You can open this PR in browser to add release notes: [open in github.dev](https://github.dev/dotnet/fsharp/pull/${PR_NUMBER})**"
RELEASE_NOTES_MESSAGE+=$'\n\n'
else
RELEASE_NOTES_MESSAGE+=$'## :white_check_mark: Release notes checked\n\n'
fi
RELEASE_NOTES_MESSAGE+=$RELEASE_NOTES_MESSAGE_DETAILS
fi

_current_head_sha=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.head.sha')
_current_pr=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}")
_current_head_sha=$(jq -r '.head.sha' <<< "$_current_pr")

if [[ "$_current_head_sha" != "$PR_HEAD_SHA" ]]; then
echo "::notice::Discarding stale release-note result for ${PR_HEAD_SHA}; current head is ${_current_head_sha}."
exit 0
fi

OPT_OUT_RELEASE_NOTES=$(jq -r 'any(.labels[]; .name == "NO_RELEASE_NOTES")' <<< "$_current_pr")
echo "Opt out of release notes: $OPT_OUT_RELEASE_NOTES"
RELEASE_NOTES_REQUIRED=false
if [[ $RELEASE_NOTES_NOT_FOUND != "" && "$OPT_OUT_RELEASE_NOTES" != true ]]; then
RELEASE_NOTES_REQUIRED=true
fi
RELEASE_NOTES_COMMENT_NEEDED=$RELEASE_NOTES_REQUIRED
if [[ "$PULL_REQUEST_FOUND" = false && "$OPT_OUT_RELEASE_NOTES" != true ]]; then
RELEASE_NOTES_COMMENT_NEEDED=true
fi

{
echo "release-notes-comment-needed=$RELEASE_NOTES_COMMENT_NEEDED"
echo "release-notes-exempt=$OPT_OUT_RELEASE_NOTES"
echo "release-notes-check-message<<$EOF"

if [[ "$OPT_OUT_RELEASE_NOTES" = true ]]; then
echo "<!-- DO_NOT_REMOVE: release_notes_check -->"
echo ""
echo "## :warning: Release notes required, but author opted out"
echo ""
echo "## :white_check_mark: Release-note check exempted"
echo ""
echo "> [!WARNING]"
echo "> **Author opted out of release notes, check is disabled for this pull request.**"
echo "> cc @dotnet/fsharp-team-msft"
echo "The <kbd>NO_RELEASE_NOTES</kbd> label exempts this pull request."
else
echo "${RELEASE_NOTES_MESSAGE}"
fi

echo "$EOF"
} >> "$GITHUB_OUTPUT"

if [[ $RELEASE_NOTES_NOT_FOUND != "" && ${OPT_OUT_RELEASE_NOTES} != true ]]; then
if [[ "$RELEASE_NOTES_REQUIRED" = true ]]; then
exit 1
fi
# Keep one bot comment current without evaluating pull request content as JavaScript.
Expand All @@ -316,6 +326,8 @@ jobs:
uses: actions/github-script@v9
env:
COMMENT_BODY: ${{ steps.release_notes_changes.outputs.release-notes-check-message }}
COMMENT_NEEDED: ${{ steps.release_notes_changes.outputs.release-notes-comment-needed }}
RELEASE_NOTES_EXEMPT: ${{ steps.release_notes_changes.outputs.release-notes-exempt }}
with:
github-token: ${{ github.token }}
script: |
Expand All @@ -330,6 +342,24 @@ jobs:
const existing = comments.find(comment =>
comment.user?.login === 'github-actions[bot]' && comment.body?.includes(marker));

if (existing?.body === process.env.COMMENT_BODY) {
return existing.id;
}
if (!existing && process.env.COMMENT_NEEDED !== 'true') {
return;
}

const { data: current } = await github.rest.pulls.get({
...context.repo,
pull_number: context.issue.number
});
const exempt = current.labels.some(label => label.name === 'NO_RELEASE_NOTES');
if (current.head.sha !== process.env.PR_HEAD_SHA ||
String(exempt) !== process.env.RELEASE_NOTES_EXEMPT) {
core.info('Skipping outdated release-note comment.');
return;
}

if (existing) {
const comment = await github.rest.issues.updateComment({
owner: context.repo.owner,
Expand Down
Loading
Loading