diff --git a/.agents/skills/dependabot-rollup/SKILL.md b/.agents/skills/dependabot-rollup/SKILL.md index 0a0058629939f..ac9187f11f9fa 100644 --- a/.agents/skills/dependabot-rollup/SKILL.md +++ b/.agents/skills/dependabot-rollup/SKILL.md @@ -1,7 +1,7 @@ --- name: dependabot-rollup description: >- - Review and optionally combine at most 11 open Dependabot patch and minor pull requests into a validated draft rollup PR. Use this skill to test Dependabot bundling locally or in a cloud agent without adding a scheduled GitHub Actions workflow. Always presents a dry-run plan and requires explicit approval before changing branches or GitHub pull requests. + Review and optionally combine at most 11 open individual Dependabot patch and minor pull requests into a validated draft rollup PR. Use this skill as a local or cloud-agent fallback to native Dependabot groups without adding a custom scheduled GitHub Actions workflow. Always presents a dry-run plan and requires explicit approval before changing branches or GitHub pull requests. disable-model-invocation: true argument-hint: '[--repo owner/repo] [--base branch] [--max count] [--push-remote remote]' allowed-tools: Bash Read Grep Glob @@ -9,7 +9,7 @@ allowed-tools: Bash Read Grep Glob # Dependabot Rollup -Build a reviewable rollup of compatible Dependabot updates without scheduled automation. The default operation is read-only: discover candidates, classify them, and present a plan. Never create a branch, merge commits, push, close pull requests, or open a rollup PR until the user explicitly approves the proposed candidates. +Build a reviewable manual rollup of compatible individual Dependabot updates as a fallback to the repository's native Dependabot groups. The default operation is read-only: discover candidates, classify them, and present a plan. Never create a branch, merge commits, push, close pull requests, or open a rollup PR until the user explicitly approves the proposed candidates. ## Defaults @@ -35,7 +35,7 @@ git remote -v Unless `--push-remote` was provided, resolve `PUSH_REMOTE` from `remote.pushDefault`, then the current branch's configured remote. If neither is set, ask the user to select a writable remote before any publish step. Do not assume `origin` is writable. -Do not require a clean current working tree. Approved rollups must use a temporary Git worktree so unrelated local changes remain untouched. +Do not create or switch branches during prerequisite checks or analysis. Unrelated local changes may remain in the checkout, but stop if the root `yarn.lock` has staged or unstaged changes because they could contaminate conflict resolution. Record the current branch, or the current commit when detached, as `START_REF` so it can be restored after the rollup is published or cancelled. If creating the rollup branch would overwrite a local change, stop and ask the user to resolve it. ### Step 2 - Discover open Dependabot PRs @@ -48,14 +48,16 @@ gh pr list \ --app dependabot \ --base "$BASE_BRANCH" \ --limit 200 \ - --json number,title,url,updatedAt,baseRefName + --json number,title,url,updatedAt,baseRefName,headRefName ``` Do not rely on the `dependencies` label: repositories may customize or omit it. ### Step 3 - Classify candidates -Parse each title as `bump from to `. Use the parsed dependency name as the deduplication key. Normalize a leading `v` in versions and accept only strict three-part numeric versions (`major.minor.patch`). +Before parsing individual updates, strip an optional conventional commit prefix of `chore(deps): ` or `chore(deps-dev): ` from the title. Detect native grouped PRs by either the remaining title being in the form `bump the group ...` (case-insensitive) or a `headRefName` containing one of the configured group identifiers: `github-actions-minor-patch`, `github-actions-security`, `production-dependencies`, `development-dependencies`, or `security-dependencies`. Exclude and report these PRs as `already grouped by Dependabot`; never place one rollup inside another. + +After removing the optional conventional commit prefix, parse each title case-insensitively as `bump from to `. Use the parsed dependency name as the deduplication key. Normalize a leading `v` in versions and accept only strict three-part numeric versions (`major.minor.patch`). Classify an update as eligible only when: @@ -67,12 +69,19 @@ Classify an update as eligible only when: Exclude and report: - Semver-major updates. +- PRs already grouped by Dependabot. - Non-semver or unparseable updates, including action tags such as date-based releases. - Downgrades and updates with no version change. Before applying the batch limit, group eligible PRs by dependency. For each dependency, retain the PR with the highest target version. If target versions are equal, retain the most recently updated PR. Report every other PR in the group as superseded, including the retained PR number and target version. -Sort the deduplicated candidates by `updatedAt`, oldest first, and select at most `MAX_PRS` candidates. Do not infer eligibility from labels or branch names. Do not include superseded PRs in the eligible or selected counts. +Check each retained candidate against the dependency versions currently declared on `BASE_BRANCH` before applying the batch limit. Inspect only the `package.json` files changed by that PR and parse them as JSON; do not infer versions with text matching. Ignore range operators when comparing the declared semantic version with the target version. + +- Exclude the candidate as obsolete when the dependency was removed from every changed manifest or every current declaration is equal to or newer than the target. +- Exclude and report the candidate as ambiguous when changed manifests contain conflicting current versions that cannot be compared safely. +- Keep lockfile-only updates eligible because they have no direct manifest declaration to compare. + +Sort the deduplicated candidates by `updatedAt`, oldest first, then by PR number ascending when timestamps are equal. Select at most `MAX_PRS` candidates. Apart from detecting configured native group identifiers, do not infer eligibility from labels or branch names. Do not include superseded PRs in the eligible or selected counts. ### Step 4 - Present the dry-run plan @@ -107,52 +116,80 @@ Show a compact report before doing anything else: If there are no selected PRs, stop after reporting that result. Otherwise ask the user to approve all candidates, approve specific PR numbers, edit the batch, or cancel. Do not treat the initial skill invocation as mutation approval. -### Step 5 - Create an isolated rollup +### Step 5 - Create the rollup branch -Run this step only after explicit approval. Use only the approved PR numbers, even if new candidates appear after the dry run. +Run this step only after the dry-run analysis and explicit approval. Use only the approved PR numbers, even if new candidates appear after the dry run. -Fetch the target base, record its SHA, and create a uniquely named temporary worktree and branch: +Immediately before merging each approved PR, repeat the base-version check against the current rollup branch. Skip the PR as obsolete if another merged update made its target unnecessary or removed its dependency. This preflight is required even when Git predicts a clean merge; never allow a stale PR to downgrade or reintroduce a dependency. + +Fetch the target base, record its SHA, and create a uniquely named branch in the current checkout. The rollup pull request will target `BASE_BRANCH`; never commit directly to the base branch: ```bash TARGET_URL="https://github.com/${REPO}.git" ROLLUP_BRANCH="dependabot-rollup/$(date -u +%Y%m%d-%H%M%S)" -ROLLUP_DIR="$(mktemp -d)/fluentui-dependabot-rollup" +START_REF="$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD)" +test -z "$(git status --porcelain -- yarn.lock)" git fetch "$TARGET_URL" "$BASE_BRANCH" BASE_SHA="$(git rev-parse FETCH_HEAD)" -git worktree add --detach "$ROLLUP_DIR" "$BASE_SHA" -git -C "$ROLLUP_DIR" switch -c "$ROLLUP_BRANCH" +git switch -c "$ROLLUP_BRANCH" "$BASE_SHA" ``` For each approved PR, fetch and merge its head in the order shown in the plan: ```bash -git -C "$ROLLUP_DIR" fetch "$TARGET_URL" "pull/$PR_NUMBER/head" -git -C "$ROLLUP_DIR" merge --no-ff --no-edit FETCH_HEAD +git fetch "$TARGET_URL" "pull/$PR_NUMBER/head" +git merge --no-ff --no-edit FETCH_HEAD ``` -If a merge conflicts, abort that merge, report the PR as skipped, and continue with the remaining approved PRs: +Dependency rollups commonly conflict because several PRs modify the same manifests and lockfile. If a merge conflicts, list the unmerged files: + +```bash +git diff --name-only --diff-filter=U +``` + +Resolve the conflict only when every unmerged file is a `package.json` file or the root `yarn.lock`: + +1. For each conflicted `package.json`, preserve the current rollup branch content and apply only the approved dependency's target version from the PR title. Preserve the existing range operator. If the current version is already equal to or newer than the target, abort the merge and report the PR as obsolete. +2. Reject ambiguous manifest changes. Do not copy the PR's entire stale manifest or select all of either side of a conflict. +3. If `yarn.lock` is conflicted, restore its current rollup branch version. Regenerate it from the resolved manifests instead of manually editing lockfile conflict markers: ```bash -git -C "$ROLLUP_DIR" merge --abort +git diff --name-only --diff-filter=U -- yarn.lock | grep -q . && \ + git checkout HEAD -- yarn.lock +yarn install ``` -Never resolve dependency conflicts automatically. If no PRs merge successfully, report the result, remove the temporary worktree, and stop without creating an issue or PR. +4. Stage the resolved manifests and lockfile, then inspect the staged diff against the merge's first parent. It must contain only the approved dependency update and lockfile changes derived from it. +5. Complete the merge with `git commit --no-edit` and report the PR as merged with resolved dependency conflicts. + +If any conflict is outside dependency manifests and the root lockfile, the intended version change is unclear, lockfile generation fails, or the reviewed diff contains unrelated stale changes, abort that merge, report the PR as skipped with the specific reason, and continue with the remaining approved PRs: + +```bash +git merge --abort +``` + +If no PRs merge successfully, report the result, switch back to `START_REF`, delete the empty rollup branch, and stop without creating an issue or PR: + +```bash +git switch "$START_REF" +git branch -D "$ROLLUP_BRANCH" +``` ### Step 6 - Validate the rollup -Run validation from the temporary worktree through the repository's Nx workflow: +Run validation from the rollup branch through the repository's Nx workflow: ```bash -yarn --cwd "$ROLLUP_DIR" install --immutable -yarn --cwd "$ROLLUP_DIR" nx affected \ +yarn install --immutable +yarn nx affected \ -t build test lint type-check \ --nxBail \ --base="$BASE_SHA" \ --head=HEAD ``` -If installation or validation fails, report the failing command and keep the worktree available for inspection. Do not push the branch, open a PR, or create a tracking issue. +If installation or validation fails, report the failing command and leave the rollup branch checked out for inspection. Do not push the branch, open a PR, or create a tracking issue. ### Step 7 - Publish only after validation @@ -162,7 +199,7 @@ After validation succeeds, summarize the merged and skipped PRs and ask for expl PUSH_REPO="$(gh repo view "$(git remote get-url "$PUSH_REMOTE")" --json nameWithOwner --jq .nameWithOwner)" PUSH_OWNER="${PUSH_REPO%%/*}" -git -C "$ROLLUP_DIR" push "$PUSH_REMOTE" "$ROLLUP_BRANCH" +git push "$PUSH_REMOTE" "$ROLLUP_BRANCH" gh pr create \ --repo "$REPO" \ --base "$BASE_BRANCH" \ @@ -174,12 +211,12 @@ gh pr create \ The PR body must list merged PRs, skipped PRs with reasons, and the exact validation commands. Do not close or modify the original Dependabot PRs automatically. -### Step 8 - Clean up and report +### Step 8 - Restore the starting branch and report -After publishing, or when the user declines publication, remove the temporary worktree unless the user asks to keep it: +After publishing, or when the user declines publication, return to the starting ref only when no merge is in progress and doing so will not overwrite local changes. Keep the local rollup branch for PR follow-up: ```bash -git worktree remove "$ROLLUP_DIR" +git switch "$START_REF" ``` Report: @@ -188,7 +225,7 @@ Report: - Exclusion, superseded, and skip reasons. - Validation commands and outcome. - Draft PR URL when one was created. -- Temporary worktree path when retained for investigation. +- Local rollup branch name when retained for investigation or PR follow-up. ## Guardrails @@ -196,9 +233,11 @@ Report: - Obtain a second confirmation before pushing or opening a draft PR. - Never run on a schedule or add a GitHub Actions workflow. - Never request or print a GitHub token; use the user's existing `gh` authentication. +- Never include a PR already grouped by Dependabot. - Never include semver-major, non-semver, downgrade, or unparseable updates. - Never propose, merge, or publish a rollup containing more than 11 updates. - Never include more than one PR for the same dependency in a proposed rollup. -- Never mutate the user's current working tree. -- Never auto-resolve merge conflicts or bypass failed validation. +- Never change branches or files before approval, and never proceed when the root `yarn.lock` has local changes. +- Never resolve conflicts by blindly choosing an entire side. Resolve only reviewed dependency manifest and lockfile conflicts as described above. +- Never bypass failed validation. - Never create failure-tracking issues or close source Dependabot PRs. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c825be9fd12d9..6cd949267f2bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,6 +12,13 @@ updates: update-types: - 'minor' - 'patch' + github-actions-security: + applies-to: security-updates + patterns: + - '*' + update-types: + - 'minor' + - 'patch' # npm dependencies - weekly minor and patch updates - package-ecosystem: 'npm' @@ -24,3 +31,25 @@ updates: # Ignore major version bumps for scheduled npm version updates - dependency-name: '*' update-types: ['version-update:semver-major'] + groups: + production-dependencies: + dependency-type: 'production' + patterns: + - '*' + update-types: + - 'minor' + - 'patch' + development-dependencies: + dependency-type: 'development' + patterns: + - '*' + update-types: + - 'minor' + - 'patch' + security-dependencies: + applies-to: security-updates + patterns: + - '*' + update-types: + - 'minor' + - 'patch' diff --git a/.github/instructions/dependabot-security-fixes.instructions.md b/.github/instructions/dependabot-security-fixes.instructions.md index 66dc004285d14..615e475a56c14 100644 --- a/.github/instructions/dependabot-security-fixes.instructions.md +++ b/.github/instructions/dependabot-security-fixes.instructions.md @@ -10,28 +10,28 @@ This instruction guide explains how Dependabot automation works for security fix Dependabot is configured to automatically create pull requests for: -1. **Security updates** - Advisory-driven npm pull requests created independently for each update -2. **npm dependencies** - Weekly minor and patch version updates created as individual pull requests -3. **GitHub Actions** - Weekly updates for workflow dependencies +1. **Security updates** - Advisory-driven minor and patch updates grouped by ecosystem +2. **npm dependencies** - Weekly minor and patch updates grouped by production or development dependency type +3. **GitHub Actions** - Weekly minor and patch version and security update groups ## Configuration The Dependabot configuration is defined in `.github/dependabot.yml`: -- **npm dependencies**: Weekly minor and patch version updates as individual pull requests -- **GitHub Actions**: Weekly updates -- **Security updates**: Individual pull requests not limited by the version update schedule -- **Rollups**: Maintainers can use `/dependabot-rollup` to combine at most 11 eligible non-major updates +- **npm dependencies**: Weekly minor and patch version updates grouped into production and development pull requests +- **GitHub Actions**: Weekly minor and patch version updates grouped separately from security updates +- **Security updates**: Minor and patch updates are grouped; major remediations are excluded from groups for isolated review +- **Manual rollups**: Maintainers can use `/dependabot-rollup` as a fallback to combine at most 11 eligible non-major updates -The repository's Advanced Security **Grouped security updates** setting must remain disabled. Dependabot does not support a maximum dependency count for automatic groups, so enabling that setting would bypass the 11-update rollup limit. +The repository's Advanced Security **Grouped security updates** setting must remain disabled. The explicit groups in `.github/dependabot.yml` provide narrower control over update types, while the repository setting would group as many available security updates as possible. -The npm `open-pull-requests-limit` controls the number of scheduled version-update pull requests. It does not limit the number of dependencies in a pull request or change Dependabot's separate security-update pull request limit. +The npm `open-pull-requests-limit` controls the number of scheduled version-update pull requests. It does not limit the number of dependencies in a grouped pull request or change Dependabot's separate security-update pull request limit. Native groups have no dependency-count ceiling; the 11-update ceiling applies only to `/dependabot-rollup`. ## Security Vulnerability Resolution ### Automatic Security Updates -GitHub triggers automatic security updates independently of the configured version update schedule. Each npm security update remains a separate pull request. Major security remediations are never included in `/dependabot-rollup`, so compatibility work stays isolated for focused review. +GitHub triggers automatic security updates independently of the configured version update schedule. Dependabot groups only minor and patch security updates under the explicit YAML rules. Major security remediations do not match those groups and are never included in `/dependabot-rollup`, so compatibility work stays isolated for focused review. ### Manual Resolution via Yarn Resolutions